Source code for pfxbrick.pfxaction

#! /usr/bin/env python3
#
# Copyright (C) 2018  Fx Bricks Inc.
# This file is part of the pfxbrick python module.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# PFx Brick configuration data helpers

import pfxbrick.pfxdict as pd
from pfxbrick.pfx import *
from pfxbrick.pfxhelpers import *


[docs]class PFxAction: """ Action data structure class. This class reflects the 16 byte data structure used internally by the PFx Brick to execute a composite action of motor, lighting, and sound effects. Attributes: command (:obj:`int`): Command byte motorActionId (:obj:`int`): Motor action ID and motor channel mask byte motorParam1 (:obj:`int`): Motor parameter 1 motorParam2 (:obj:`int`): Motor parameter 2 lightFxId (:obj:`int`): Light Fx ID byte lightOutputMask (:obj:`int`): Light channel output mask lightPFOutputMask (:obj:`int`): Light channel on PF output mask lightParam1 (:obj:`int`): Lighting parameter 1 lightParam2 (:obj:`int`): Lighting parameter 2 lightParam3 (:obj:`int`): Lighting parameter 3 lightParam4 (:obj:`int`): Lighting parameter 4 lightParam5 (:obj:`int`): Lighting parameter 5 soundFxId (:obj:`int`): Sound Fx ID byte soundFileId (:obj:`int`): Sound file ID soundParam1 (:obj:`int`): Sound parameter 1 soundParam2 (:obj:`int`): Sound parameter 2 """ def __init__(self): self.command = 0 self.motorActionId = 0 self.motorParam1 = 0 self.motorParam2 = 0 self.lightFxId = 0 self.lightOutputMask = 0 self.lightPFOutputMask = 0 self.lightParam1 = 0 self.lightParam2 = 0 self.lightParam3 = 0 self.lightParam4 = 0 self.lightParam5 = 0 self.soundFxId = 0 self.soundFileId = 0 self.soundParam1 = 0 self.soundParam2 = 0 def __eq__(self, other): """Dunder method for equality""" for k, v in self.__dict__.items(): if k not in other.__dict__: return False if not v == other.__dict__[k]: return False return True
[docs] def all_off(self): """ Populates an action to turn off all motors, lights, and sound. """ self.clear() self.command = EVT_COMMAND_ALL_OFF return self
[docs] def set_motor_speed(self, ch, speed, duration=None): """ Populates an action to set the speed of specified motor channel(s). The motor speed is specified between -100% and +100% where negative values are in the reverse direction and positive values are in the forward direction. :param ch: [:obj:`int`] a list of motor channels (1-4) :param speed: :obj:`int` desired motor speed (-100 to +100) :param duration: :obj:`float` optional duration (in seconds) to run motor, runs indefinitely if not specified :returns: :obj:`PFxAction` self If the duration value is specified, it represents the desired motor run time in seconds. Note that this value will be rounded to the nearest fixed interval of the DURATION parameter as defined in the ICD ranging between 16 fixed values from 0.5 sec to 5 min. """ sf = float(speed) if sf > 100.0: sf = 100.0 if sf < -100.0: sf = -100.0 sf = (sf / 100.0) * 63.0 s = int(abs(sf)) & EVT_MOTOR_SPEED_HIRES_MASK s |= EVT_MOTOR_SPEED_HIRES if speed < 0: s |= EVT_MOTOR_SPEED_HIRES_REV self.motorParam1 = s m = ch_to_mask(listify(ch)) & EVT_MOTOR_OUTPUT_MASK if duration is not None: m |= EVT_MOTOR_SET_SPD_TIMED self.motorParam2 = duration_to_fixed_value(duration) else: m |= EVT_MOTOR_SET_SPD self.motorActionId = m return self
[docs] def stop_motor(self, ch): """ Populates an action to stop the specifed motor channel(s). :param ch: [:obj:`int`] a list of motor channels (1-4) :returns: :obj:`PFxAction` self """ m = ch_to_mask(listify(ch)) & EVT_MOTOR_OUTPUT_MASK m |= EVT_MOTOR_ESTOP self.motorActionId = m return self
[docs] def light_on(self, ch): """ Populates an action to turn on selected light outputs. :param ch: [:obj:`int`] a list of light channels (1-8) :returns: :obj:`PFxAction` self """ self.lightOutputMask = ch_to_mask(listify(ch)) self.lightFxId = EVT_LIGHTFX_ON_OFF_TOGGLE self.lightParam4 = EVT_TRANSITION_ON return self
[docs] def light_off(self, ch): """ Populates an action to turn off selected light outputs. :param ch: [:obj:`int`] a list of light channels (1-8) :returns: :obj:`PFxAction` self """ self.lightOutputMask = ch_to_mask(listify(ch)) self.lightFxId = EVT_LIGHTFX_ON_OFF_TOGGLE self.lightParam4 = EVT_TRANSITION_OFF return self
[docs] def light_toggle(self, ch): """ Populates an action to toggle the state of selected light outputs. :param ch: [:obj:`int`] a list of light channels (1-8) :returns: :obj:`PFxAction` self """ self.lightOutputMask = ch_to_mask(listify(ch)) self.lightFxId = EVT_LIGHTFX_ON_OFF_TOGGLE self.lightParam4 = EVT_TRANSITION_TOGGLE return self
[docs] def set_brightness(self, ch, brightness): """ Populates an action to set the brightness of selected light outputs. :param ch: [:obj:`int`] a list of light channels (1-8) :param brightness: :obj:`int` brightness (0 - 255 max) :returns: :obj:`PFxAction` self """ x = brightness if x > 255: x = 255 if x < 0: x = 0 self.lightOutputMask = ch_to_mask(listify(ch)) self.lightFxId = EVT_LIGHTFX_SET_BRIGHT self.lightParam1 = x return self
[docs] def combo_light_fx(self, fx, param=[0, 0, 0, 0, 0]): """ Populates an action with a user specified combination light effect and associated parameters. :param fx: :obj:`int` desired light effect :param param: [:obj:`int`] a list of up to 5 light parameters :returns: :obj:`PFxAction` self """ return self.light_fx([], fx | EVT_LIGHT_COMBO_MASK, param)
[docs] def light_fx(self, ch, fx, param=[0, 0, 0, 0, 0]): """ Populates an action with a user specified light effect and associated parameters. :param ch: [:obj:`int`] a list of light channels (1-8) :param fx: :obj:`int` desired light effect :param param: [:obj:`int`] a list of up to 5 light parameters :returns: :obj:`PFxAction` self The details of specifying the light **fx** and **param** items is described in detail in the ICD document. The **pfx.py** file contains convenient pre-defined constants for all of the light effect types and parameter values. An example of using this method is as follows:: p = [EVT_PERIOD_1S, EVT_DUTYCY_10, EVT_BURST_COUNT_2, EVT_TRANSITION_TOGGLE] a = PFxAction().light_fx([1,4], EVT_LIGHTFX_STROBE_P, p) This specifies a strobe light effect on channels 1 and 4 with a 1 second period, 10% duty cycle, two light pulses and with a toggle activation. """ self.lightOutputMask = ch_to_mask(listify(ch)) self.lightFxId = fx for i, p in enumerate(param): if i == 0: self.lightParam1 = p elif i == 1: self.lightParam2 = p elif i == 2: self.lightParam3 = p elif i == 3: self.lightParam4 = p elif i == 4: self.lightParam5 = p return self
[docs] def sound_fx(self, fx, param=[0, 0], fileID=None): """ Populates an action with a user specified sound effect and associated parameters. :param fx: :obj:`int` desired sound action :param param: [:obj:`int`] a list of up to 2 sound parameters :param fileID: :obj:`int` file ID of an audio file in the file system :returns: :obj:`PFxAction` self The details of specifying the sound **fx** and **param** items is described in detail in the ICD document. The **pfx.py** file contains convenient pre-defined constants for all of the sound effect types and parameter values. An example of using this method is as follows:: p = [EVT_SOUND_DUR_10S] a = PFxAction().sound_fx(EVT_SOUND_PLAY_DUR, p, 5) This specifies an action to playback an audio file with ID=5 for a fixed duration of 10 seconds. """ self.soundFxId = fx if fileID is not None: self.soundFileId = fileID for i, p in enumerate(param): if i == 0: self.soundParam1 = p elif i == 1: self.soundParam2 = p return self
[docs] def play_audio_file(self, fileID): """ Populates an action to play an audio file once. :param fileID: :obj:`int` file ID of an audio file in the file system :returns: :obj:`PFxAction` self This is a convenience wrapper for the sound_fx method. """ return self.sound_fx(EVT_SOUND_PLAY_ONCE, [EVT_SOUND_TOGGLE], fileID)
[docs] def stop_audio_file(self, fileID): """ Populates an action to stop playback of an audio file. :param fileID: :obj:`int` file ID of an audio file in the file system :returns: :obj:`PFxAction` self This is a convenience wrapper for the sound_fx method. """ return self.sound_fx(EVT_SOUND_STOP, [], fileID)
[docs] def repeat_audio_file(self, fileID): """ Populates an action for repeated playback of an audio file. :param fileID: :obj:`int` file ID of an audio file in the file system :returns: :obj:`PFxAction` self This is a convenience wrapper for the sound_fx method. """ return self.sound_fx(EVT_SOUND_PLAY_CONT, [], fileID)
[docs] def set_volume(self, volume): """ Populates an action to set the audio volume. :param volume: :obj:`int` desired audio volume (0 - 100%) :returns: :obj:`PFxAction` self This is a convenience wrapper for the sound_fx method. """ vf = float(volume) if vf > 100.0: vf = 100.0 if vf < 0.0: vf = 0.0 vf = (vf / 100.0) * 255.0 v = int(vf) return self.sound_fx(EVT_SOUND_SET_VOL, [0, v])
[docs] def is_empty(self): """Determines if the action is clear/undefined""" if self.command > 0: return False if self.motorActionId > 0: return False if self.motorParam1 > 0: return False if self.motorParam2 > 0: return False if self.lightFxId > 0: return False if self.lightOutputMask > 0: return False if self.lightPFOutputMask > 0: return False if self.lightParam1 > 0: return False if self.lightParam2 > 0: return False if self.lightParam3 > 0: return False if self.lightParam4 > 0: return False if self.lightParam5 > 0: return False if self.soundFxId > 0: return False if self.soundFileId > 0: return False if self.soundParam1 > 0: return False if self.soundParam2 > 0: return False return True
[docs] def clear(self): """ Sets all the action data in this class to zero. """ self.command = 0 self.motorActionId = 0 self.motorParam1 = 0 self.motorParam2 = 0 self.lightFxId = 0 self.lightOutputMask = 0 self.lightPFOutputMask = 0 self.lightParam1 = 0 self.lightParam2 = 0 self.lightParam3 = 0 self.lightParam4 = 0 self.lightParam5 = 0 self.soundFxId = 0 self.soundFileId = 0 self.soundParam1 = 0 self.soundParam2 = 0
[docs] def from_bytes(self, msg): """ Converts the message string bytes read from the PFx Brick into the corresponding data members of this class. """ self.command = msg[1] self.motorActionId = msg[2] self.motorParam1 = msg[3] self.motorParam2 = msg[4] self.lightFxId = msg[5] self.lightOutputMask = msg[6] self.lightPFOutputMask = msg[7] self.lightParam1 = msg[8] self.lightParam2 = msg[9] self.lightParam3 = msg[10] self.lightParam4 = msg[11] self.lightParam5 = msg[12] self.soundFxId = msg[13] self.soundFileId = msg[14] self.soundParam1 = msg[15] self.soundParam2 = msg[16]
[docs] def to_bytes(self): """ Converts the data members of this class to the message string bytes which can be sent to the PFx Brick. """ msg = [] msg.append(self.command) msg.append(self.motorActionId) msg.append(self.motorParam1) msg.append(self.motorParam2) msg.append(self.lightFxId) msg.append(self.lightOutputMask) msg.append(self.lightPFOutputMask) msg.append(self.lightParam1) msg.append(self.lightParam2) msg.append(self.lightParam3) msg.append(self.lightParam4) msg.append(self.lightParam5) msg.append(self.soundFxId) msg.append(self.soundFileId) msg.append(self.soundParam1) msg.append(self.soundParam2) return msg
[docs] def verbose_line_str(self, brick): """ Human readable string of action object in single line. :param brick: :obj:`PFxBrick` reference to a PFx Brick in order to query filename """ s = [] if self.command > 0: s.append("Cmd: %s" % shorter_str(pd.command_dict[self.command])) if self.motorActionId & EVT_MOTOR_OUTPUT_MASK: x = pd.motor_action_dict[self.motorActionId & EVT_MOTOR_ACTION_ID_MASK] s.append("%s%s" % (motor_ch_str(self.motorActionId), shorter_str(x))) if self.lightFxId & EVT_LIGHT_COMBO_MASK: sf = pd.combo_lightfx_dict[self.lightFxId & EVT_LIGHT_ID_MASK] s.append("Combo light: %s" % (shorter_str(sf))) elif self.lightFxId & EVT_LIGHT_ID_MASK: sf = pd.ind_lightfx_dict[self.lightFxId & EVT_LIGHT_ID_MASK] sc = light_ch_str(self.lightOutputMask) s.append("Light %s %s" % (sc, sf)) if self.soundFxId: x = pd.soundfx_dict[self.soundFxId] if self.soundFileId: fn = brick.filedir.get_filename(self.soundFileId) s.append('Sound %s "%s" (%d)' % (x, fn, self.soundFileId)) else: s.append("Sound %s" % (x)) return " ".join(s)
[docs] def __str__(self): """ Convenient human readable string of the action data structure. This allows a :py:class:`PFxAction` object to be used with :obj:`str` and :obj:`print` methods. """ sb = [] sb.append( "Command : [%02X] %s" % (self.command, pd.command_dict[self.command]) ) if self.motorActionId & EVT_MOTOR_OUTPUT_MASK == 0: sb.append("Motor Action ID : [%02X] None" % (self.motorActionId)) else: sb.append( "Motor Action ID : [%02X] %s %s" % ( self.motorActionId, pd.motor_action_dict[self.motorActionId & EVT_MOTOR_ACTION_ID_MASK], motor_ch_str(self.motorActionId), ) ) sb.append("Motor Param 1 : [%02X]" % (self.motorParam1)) sb.append("Motor Param 2 : [%02X]" % (self.motorParam2)) sf = "" if self.lightFxId & EVT_LIGHT_COMBO_MASK: sf = pd.combo_lightfx_dict[self.lightFxId & EVT_LIGHT_ID_MASK] else: sf = pd.ind_lightfx_dict[self.lightFxId & EVT_LIGHT_ID_MASK] sb.append("Light Fx ID : [%02X] %s" % (self.lightFxId, sf)) sb.append( "Light Output Mask : [%02X] %s" % (self.lightOutputMask, light_ch_str(self.lightOutputMask)) ) sb.append("Light PF Out Mask : [%02X]" % (self.lightPFOutputMask)) sb.append("Light Param 1 : [%02X]" % (self.lightParam1)) sb.append("Light Param 2 : [%02X]" % (self.lightParam2)) sb.append("Light Param 3 : [%02X]" % (self.lightParam3)) sb.append("Light Param 4 : [%02X]" % (self.lightParam4)) sb.append("Light Param 5 : [%02X]" % (self.lightParam5)) sb.append("Sound Fx ID : [%02X]" % (self.soundFxId)) sb.append("Sound File ID : [%02X]" % (self.soundFileId)) sb.append("Sound Param 1 : [%02X]" % (self.soundParam1)) sb.append("Sound Param 2 : [%02X]" % (self.soundParam2)) s = "\n".join(sb) return s