Source code for pfxbrick.pfxconfig

#! /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 import *
from pfxbrick.pfxhelpers import *


[docs]class PFxSettings: """ General settings container class. A member of PFxConfig This class contains miscellaneous user preference settings such as power saving modes. Attributes: statusLED (:obj:`int`): status LED mode volumeBeep (:obj:`int`): volume beep mode autoPowerDown (:obj:`int`): auto power down mode lockoutMode (:obj:`int`): IR lockout activation mode irAutoOff (:obj:`int`): auto IR disable mode bleAutoOff (:obj:`int`): auto BLE disable mode bleMotorWhenDisconnect (:obj:`int`): behaviour of motors on BLE disconnect bleAdvertPower (:obj:`int`): BLE RF power during advertising bleSessionPower (:obj:`int`): BLE RF power during connected session notchCount (:obj:`int`): number of motor index speed notches notchBounds ([:obj:`int`]): list of monotonic increasing speed notch boundaries """ def __init__(self): self.statusLED = 0 self.volumeBeep = 0 self.autoPowerDown = 0 self.lockoutMode = 0 self.irAutoOff = 0 self.bleAutoOff = 0 self.bleMotorWhenDisconnect = 0 self.bleAdvertPower = 0 self.bleSessionPower = 0 self.notchCount = 0 self.notchBounds = [0, 0, 0, 0, 0, 0, 0] self.rapidAccelThr = 0 self.rapidDecelThr = 0 self.brakeDecelThr = 0 self.brakeSpeedThr = 0 def __eq__(self, other): 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 def __str__(self): sb = [] sb.append("Status LED : %s" % (pd.status_led_dict[self.statusLED])) sb.append("Volume Beep : %s" % (pd.volume_beep_dict[self.volumeBeep])) sb.append( "Auto Power Down : %s" % (pd.power_save_dict[self.autoPowerDown]) ) sb.append("IR Lockout Mode : %s" % (pd.lockout_dict[self.lockoutMode])) sb.append("IR Auto Off : %s" % (pd.ir_off_dict[self.irAutoOff])) sb.append("BLE Auto Off : %s" % (pd.ble_off_dict[self.bleAutoOff])) sb.append( "BLE Motor Disconnect : %s" % (pd.ble_motor_dict[self.bleMotorWhenDisconnect]) ) sb.append("BLE Advert Power : %s" % (self.bleAdvertPower)) sb.append("BLE Session Power : %s" % (self.bleSessionPower)) sb.append("Motor sound notches : %s" % (self.notchCount)) mb = "".join("0x{:02X} ".format(x) for x in self.notchBounds) sb.append("Motor sound bounds : %s" % (mb)) sb.append("Rapid Accel Thr : %d" % (self.rapidAccelThr)) sb.append("Rapid Decel Thr : %d" % (self.rapidDecelThr)) sb.append("Brake Decel Thr : %d" % (self.brakeDecelThr)) sb.append("Brake Speed Thr : %d" % (self.brakeSpeedThr)) s = "\n".join(sb) return s
[docs]class PFxMotor: """ Motor settings container class. This class contains motor configuration data for one motor channel. Attributes: invert (:obj:`boolean`): invert the definition of forward/reverse torqueComp (:obj:`boolean`): activate low speed torque compensation with low frequency PWM tlgMode (:obj:`boolean`): enable LEGO® Power Functions compatible PWM mode accel (:obj:`int`): acceleration factor (0 - 15 max) decel (:obj:`int`): deceleration factor (0 - 15 max) vmin (:obj:`int`): speed curve minimum mapped speed (0 -> vmid-1) vmid (:obj:`int`): speed curve midpoint speed (vmin+1 -> vmax-1) vmax (:obj:`int`): speed curve maximum mapped speed (vmid+1 -> 255) """ def __init__(self): self.invert = False self.torqueComp = False self.tlgMode = False self.accel = 0 self.decel = 0 self.vmin = 0 self.vmid = 128 self.vmax = 255 def __repr__(self): s = "invert=%02X torqueComp=%02X tlgMode=%02X" % ( self.invert, self.torqueComp, self.tlgMode, ) s = s + "vmin=%02X vmid=%02X vmax=%02X" % (self.vmin, self.vmid, self.vmax) s = "%s(%s)" % ("PFxMotor." + self.__class__.__name__, s) return s def __str__(self): sb = [] sb.append( " Invert : %s Torque comp : %s PF mode : %s" % (self.invert, self.torqueComp, self.tlgMode) ) sb.append(" Accel : %s Decel : %s" % (self.accel, self.decel)) sb.append( " vMin : %s vMid : %s vMax : %s" % (self.vmin, self.vmid, self.vmax) ) s = "\n".join(sb) return s def __eq__(self, other): 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 def from_config_byte(self, byte): self.invert = set_with_bit(byte, PFX_CFG_MOTOR_INVERT) self.torqueComp = set_with_bit(byte, PFX_CFG_MOTOR_TRQCOMP) self.tlgMode = set_with_bit(byte, PFX_CFG_MOTOR_TLGMODE) def from_speed_bytes(self, msg): self.vmin = int(msg[0]) self.vmid = int(msg[1]) self.vmax = int(msg[2]) self.accel = int(msg[3]) self.decel = int(msg[4]) def to_config_byte(self): v = 0 if self.invert: v |= PFX_CFG_MOTOR_INVERT if self.torqueComp: v |= PFX_CFG_MOTOR_TRQCOMP if self.tlgMode: v |= PFX_CFG_MOTOR_TLGMODE return v def to_speed_bytes(self): v = [] v.append(self.vmin) v.append(self.vmid) v.append(self.vmax) v.append(self.accel) v.append(self.decel) return v
[docs]class PFxLights: """ Light settings container class. This class contains default startup brightness data for every light channel. All brightness values range from 0 (minimum) to 255 (maximum). Attributes: defaultBrightness (:obj:`int`): default global brightness, if 0, then individual brightness is used startupBrightness ([:obj:`int`]): list of 8 individual startup brightness values for each light output pfBrightnessA (:obj:`int`): startup brightness of PF channel A (when used for lights) pfBrightnessB (:obj:`int`): startup brightness of PF channel B pfBrightnessC (:obj:`int`): startup brightness of PF channel C pfBrightnessD (:obj:`int`): startup brightness of PF channel D """ def __init__(self): self.defaultBrightness = 0 self.startupBrightness = [0, 0, 0, 0, 0, 0, 0, 0] self.pfBrightnessA = 0 self.pfBrightnessB = 0 self.pfBrightnessC = 0 self.pfBrightnessD = 0 def __repr__(self): sb = "".join("{:02X} ".format(x) for x in self.startupBrightness) sp = "".join( "{:02X} ".format(x) for x in [ self.pfBrightnessA, self.pfBrightnessB, self.pfBrightnessC, self.pfBrightnessD, ] ) s = "defaultBrightness=%02X startupBrightness=%s pfBrightness=%s" % ( self.defaultBrightness, sb, sp, ) s = "%s(%s)" % ("PFxLights." + self.__class__.__name__, s) return s def __eq__(self, other): 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 def __str__(self): sb = [] sb.append("Default brightness : 0x%02X" % (self.defaultBrightness)) sb.append( "Startup brightness : " + "".join("0x{:02X} ".format(x) for x in self.startupBrightness) ) sb.append( "PF output brightness : " + "".join( "0x{:02X} ".format(x) for x in [ self.pfBrightnessA, self.pfBrightnessB, self.pfBrightnessC, self.pfBrightnessD, ] ) ) s = "\n".join(sb) return s
[docs]class PFxAudio: """ Audio settings container class. This class contains audio configuration data such as default volume, bass, treble, etc. Attributes: audioDRC (:obj:`boolean`): auto Dynamic Range Control (True/False) bass (:obj:`int`): startup bass EQ (-20 to 20 dB) treble (:obj:`int`): startup treble EQ (-20 to 20 dB) defaultVolume (:obj:`int`): startup volume (0 min - 255 max) """ def __init__(self): self.audioDRC = False self.bass = 0 self.treble = 0 self.defaultVolume = 0 def __eq__(self, other): 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 def __repr__(self): s = "drc=%d bass=%d treble=%d" % (self.audioDRC, self.bass, self.treble) s = "%s(%s)" % ("PFxAudio." + self.__class__.__name__, s) return s def __str__(self): ds = "" if self.audioDRC: ds = "ON" else: ds = "OFF" s = "Default Vol: 0x%02X Audio DRC: %s Bass: 0x%02X Treble: 0x%02X" % ( self.defaultVolume, ds, self.bass, self.treble, ) return s
[docs]class PFxConfig: """ Top level configuration data container class. This class contains catergorized container classes for groups of related settings. To change a configuration setting, simply access the setting value using a dotted path type notation, e.g. config.lights.startupBrightness[2] = 100 Attributes: settings (:obj:`PFxSettings`): container for general settings. motors ([:obj:`PFxMotor`]): list of 4 containers for motor settings lights (:obj:`PFxLights`): container for default brightness settings audio (:obj:`PFxAudio`): container for audio related settings """ def __init__(self, icd_rev=None): self.settings = PFxSettings() self.motors = [PFxMotor(), PFxMotor(), PFxMotor(), PFxMotor()] self.lights = PFxLights() self.audio = PFxAudio() if icd_rev is not None: self.icd_rev = icd_rev else: self.icd_rev = "3.38" def __eq__(self, other): 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 def settings_byte(self): v = 0 if self.settings.statusLED == PFX_CFG_STATLED_OFF: v |= PFX_CFG_STATLED_OFF else: v |= PFX_CFG_STATLED_ON if self.settings.volumeBeep == PFX_CFG_VOLBEEP_ON: v |= PFX_CFG_VOLBEEP_ON else: v |= PFX_CFG_VOLBEEP_OFF v |= self.settings.autoPowerDown v |= self.settings.lockoutMode if self.audio.audioDRC == PFX_CFG_AUDIO_DRC_ON: v |= PFX_CFG_AUDIO_DRC_ON else: v |= PFX_CFG_AUDIO_DRC_OFF return v
[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.lights.startupBrightness[0] = msg[1] self.lights.startupBrightness[1] = msg[2] self.lights.startupBrightness[2] = msg[3] self.lights.startupBrightness[3] = msg[4] self.lights.startupBrightness[4] = msg[5] self.lights.startupBrightness[5] = msg[6] self.settings.notchCount = msg[7] self.settings.notchBounds[0] = msg[8] self.settings.notchBounds[1] = msg[9] self.settings.notchBounds[2] = msg[10] self.settings.notchBounds[3] = msg[11] self.settings.notchBounds[4] = msg[12] self.settings.notchBounds[5] = msg[13] self.settings.notchBounds[6] = msg[14] if not is_version_less_than(self.icd_rev, "3.38"): self.settings.rapidAccelThr = msg[15] self.settings.rapidDecelThr = msg[16] self.settings.brakeDecelThr = msg[17] self.settings.brakeSpeedThr = msg[18] self.settings.irAutoOff = msg[26] self.settings.bleAutoOff = msg[27] self.settings.bleMotorWhenDisconnect = msg[28] self.settings.bleAdvertPower = msg[29] self.settings.bleSessionPower = msg[30] self.lights.startupBrightness[6] = msg[31] self.lights.startupBrightness[7] = msg[32] self.lights.pfBrightnessA = msg[33] self.lights.pfBrightnessB = msg[34] self.audio.bass = msg[35] self.audio.treble = msg[36] self.settings.statusLED = int(msg[37] & PFX_CFG_STATLED_MASK) self.settings.volumeBeep = int(msg[37] & PFX_CFG_VOLBEEP_MASK) self.settings.autoPowerDown = int(msg[37] & PFX_CFG_POWERSAVE_MASK) self.settings.lockoutMode = int(msg[37] & PFX_CFG_LOCK_MODE_MASK) self.audio.audioDRC = int(msg[37] & PFX_CFG_AUDIO_DRC_MASK) self.motors[0].from_config_byte(msg[38]) self.motors[0].from_speed_bytes(msg[39:44]) self.motors[1].from_config_byte(msg[44]) self.motors[1].from_speed_bytes(msg[45:50]) self.motors[2].from_config_byte(msg[50]) self.motors[2].from_speed_bytes(msg[51:56]) self.motors[3].from_config_byte(msg[56]) self.motors[3].from_speed_bytes(msg[57:62]) self.audio.defaultVolume = msg[62] self.lights.defaultBrightness = msg[63]
[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.settings.notchCount) msg.append(self.settings.notchBounds[0]) msg.append(self.settings.notchBounds[1]) msg.append(self.settings.notchBounds[2]) msg.append(self.settings.notchBounds[3]) msg.append(self.settings.notchBounds[4]) msg.append(self.settings.notchBounds[5]) msg.append(self.settings.notchBounds[6]) if is_version_less_than(self.icd_rev, "3.38"): msg.extend([0] * 11) else: msg.append(self.settings.rapidAccelThr) msg.append(self.settings.rapidDecelThr) msg.append(self.settings.brakeDecelThr) msg.append(self.settings.brakeSpeedThr) msg.extend([0] * 5) # include new ICD revision to help the PFx Brick # determine what version we are major, minor = ver_to_bytes(ICD_REV) msg.append(major) msg.append(minor) msg.append(self.settings.irAutoOff) msg.append(self.settings.bleAutoOff) msg.append(self.settings.bleMotorWhenDisconnect) msg.append(self.settings.bleAdvertPower) msg.append(self.settings.bleSessionPower) msg.append(self.audio.bass) msg.append(self.audio.treble) msg.append(self.settings_byte()) msg.append(self.motors[0].to_config_byte()) msg.extend(self.motors[0].to_speed_bytes()) msg.append(self.motors[1].to_config_byte()) msg.extend(self.motors[1].to_speed_bytes()) msg.append(self.motors[2].to_config_byte()) msg.extend(self.motors[2].to_speed_bytes()) msg.append(self.motors[3].to_config_byte()) msg.extend(self.motors[3].to_speed_bytes()) msg.append(self.audio.defaultVolume) msg.append(self.lights.defaultBrightness) msg.append(self.lights.startupBrightness[0]) msg.append(self.lights.startupBrightness[1]) msg.append(self.lights.startupBrightness[2]) msg.append(self.lights.startupBrightness[3]) msg.append(self.lights.startupBrightness[4]) msg.append(self.lights.startupBrightness[5]) msg.append(self.lights.startupBrightness[6]) msg.append(self.lights.startupBrightness[7]) msg.append(self.lights.pfBrightnessA) msg.append(self.lights.pfBrightnessB) return msg
def __str__(self): sb = [] sb.append(str(self.settings)) sb.append(str(self.lights)) sb.append(str(self.audio)) for i, motor in enumerate(self.motors): sb.append("Motor Channel %d" % (i)) sb.append(str(motor)) s = "\n".join(sb) return s