From 118d7cd4b0369bba86ece9703882999f35410aad Mon Sep 17 00:00:00 2001 From: davesmeghead Date: Mon, 14 Oct 2024 09:29:32 +0100 Subject: [PATCH] 0.9.9.6 - Syntax changes only, no functional changes Converted format output on strings to embedded curly brackets Removed some commented out stuff to clean it up A couple of extra checks for client being none Removed partition number on siren (as it is not associated with a particular partition) In pyvisonic all global constants are consistently named --- custom_components/visonic/__init__.py | 5 +- .../visonic/alarm_control_panel.py | 5 +- custom_components/visonic/binary_sensor.py | 2 +- custom_components/visonic/client.py | 21 +- .../visonic/examples/complete_example.py | 30 +- .../visonic/examples/simple_example.py | 28 +- custom_components/visonic/manifest.json | 2 +- custom_components/visonic/pyconst.py | 19 +- custom_components/visonic/pyhelper.py | 76 +- custom_components/visonic/pyvisonic.py | 653 +++++++++--------- custom_components/visonic/select.py | 4 +- custom_components/visonic/siren.py | 8 +- 12 files changed, 421 insertions(+), 432 deletions(-) diff --git a/custom_components/visonic/__init__.py b/custom_components/visonic/__init__.py index 5774bbc..ef6156e 100644 --- a/custom_components/visonic/__init__.py +++ b/custom_components/visonic/__init__.py @@ -377,7 +377,7 @@ async def service_sensor_image(call): async def handle_reload(call) -> None: """Handle reload service call.""" - _LOGGER.info("Domain {0} call {1} reload called: reloading integration".format(DOMAIN, call)) + _LOGGER.info(f"Domain {DOMAIN} call {call} reload called: reloading integration") current_entries = hass.config_entries.async_entries(DOMAIN) reload_tasks = [ hass.config_entries.async_reload(entry.entry_id) @@ -450,7 +450,7 @@ def configured_hosts(hass): # Listener to handle fired events def handle_core_config_updated(event): - _LOGGER.debug(f"[Visonic Setup] event {str(event)}") + _LOGGER.debug(f"[Visonic Setup] Core configuration has been Updated") #hass = async_get_hass() translateLanguage(hass) @@ -458,7 +458,6 @@ def handle_core_config_updated(event): translatedLanguageAlready = True translateLanguage(hass) # Listen for when EVENT_CORE_CONFIG_UPDATE is fired - # hass.bus.async_listen(EVENT_HOMEASSISTANT_STARTED, handle_core_config_updated) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, handle_core_config_updated) # combine and convert python settings map to dictionary diff --git a/custom_components/visonic/alarm_control_panel.py b/custom_components/visonic/alarm_control_panel.py index 22bbbce..7cb7d75 100644 --- a/custom_components/visonic/alarm_control_panel.py +++ b/custom_components/visonic/alarm_control_panel.py @@ -65,7 +65,6 @@ def async_add_alarm() -> None: ) #_LOGGER.debug("alarm control panel async_setup_entry exit") - class VisonicAlarm(alarm.AlarmControlPanelEntity): """Representation of a Visonic alarm control panel.""" @@ -219,6 +218,8 @@ def extra_state_attributes(self): # def supported_features(self) -> int: """Return the list of supported features.""" #_LOGGER.debug(f"alarm control panel supported_features {self.entity_id=}") + if self._client is None: + return 0 if self._client.isDisableAllCommands(): return 0 #_LOGGER.debug(f"[AlarmcontrolPanel] Getting Supported Features {self._client.isArmHome()} {self._client.isArmNight()}") @@ -242,6 +243,8 @@ def code_format(self): #_LOGGER.debug(f"alarm control panel code_format {self.entity_id=}") # Do not show the code panel if the integration is just starting up and # connecting to the panel + if self._client is None: + return None if self._client.isDisableAllCommands(): return None if self.isPanelConnected(): diff --git a/custom_components/visonic/binary_sensor.py b/custom_components/visonic/binary_sensor.py index 312b03d..00e9c27 100644 --- a/custom_components/visonic/binary_sensor.py +++ b/custom_components/visonic/binary_sensor.py @@ -236,7 +236,7 @@ def available(self) -> bool: def extra_state_attributes(self): """Return the state attributes of the device.""" # _LOGGER.debug("in device_state_attributes") - if self._visonic_device is not None: + if self._client is not None and self._visonic_device is not None: stype = self._visonic_device.getSensorType() attr = {} diff --git a/custom_components/visonic/client.py b/custom_components/visonic/client.py index 76a9f62..9d661fa 100644 --- a/custom_components/visonic/client.py +++ b/custom_components/visonic/client.py @@ -122,7 +122,7 @@ # "trigger", #] -CLIENT_VERSION = "0.9.9.5" +CLIENT_VERSION = "0.9.9.6" MAX_CLIENT_LOG_ENTRIES = 300 @@ -313,7 +313,7 @@ def __call__(self): class VisonicClient: """Set up for Visonic devices.""" - _LOGGER.debug("Initialising Client - Version {0}".format(CLIENT_VERSION)) + _LOGGER.debug(f"Initialising Client - Version {CLIENT_VERSION}") def __init__(self, hass: HomeAssistant, panelident: int, cf: dict, entry: ConfigEntry): """Initialize the Visonic Client.""" @@ -625,7 +625,7 @@ def _savePanelEventLogFiles(self, available, total): output = template.render( entries=self.templatedata, total=total, - available="{0}".format(available), + available=f"{available}", ) with open(self.config.get(CONF_LOG_XML_FN), "w") as f: self.logstate_debug("Panel Event Log - Writing xml file") @@ -1258,7 +1258,7 @@ def pmGetPin(self, code: str, forcedKeypad: bool): return False, None # Return invalid as panel downloading EEPROM else: # If the panel mode is UNKNOWN, PROBLEM. - self.logstate_warning("Warning: Valid 4 digit PIN not found, panelmode is {0}".format(panelmode)) + self.logstate_warning(f"Warning: Valid 4 digit PIN not found, panelmode is {panelmode}") return False, None # Return invalid as panel not in correct state to do anything return True, code @@ -1273,7 +1273,7 @@ def pmGetPinSimple(self, code: str): # Powerlink or StdPlus and so we downloaded the code codes return True, None else: - self.logstate_warning("Warning: [pmGetPinSimple] Valid 4 digit PIN not found, panelmode is {0}".format(panelmode)) + self.logstate_warning(f"Warning: [pmGetPinSimple] Valid 4 digit PIN not found, panelmode is {panelmode}") return False, None return True, code @@ -1676,7 +1676,7 @@ def _createSocketConnection(self, address, port): return sock except socket.error as err: - self.logstate_debug("Setting TCP socket Options Exception {0}".format(err)) + self.logstate_debug(f"Setting TCP socket Options Exception {err}") if sock is not None: sock.close() @@ -1864,7 +1864,7 @@ async def async_service_panel_start(self, force : bool): attemptCounter = 0 #self.logstate_debug(f" {attemptCounter} of {self.totalAttempts}") while force or attemptCounter < self.totalAttempts: - self.logstate_debug("........... connection attempt {0} of {1}".format(attemptCounter + 1, self.totalAttempts)) + self.logstate_debug(f"........... connection attempt {attemptCounter + 1} of {self.totalAttempts}") if await self.connect_to_alarm(): self.logstate_debug("........... connection made") self._fireHAEvent(event_id = PanelCondition.CONNECTION, datadictionary = {"state": "connected", "attempt": attemptCounter + 1}) @@ -1873,7 +1873,7 @@ async def async_service_panel_start(self, force : bool): attemptCounter = attemptCounter + 1 force = False if attemptCounter < self.totalAttempts: - self.logstate_debug("........... connection attempt delay {0} seconds".format(self.delayBetweenAttempts)) + self.logstate_debug(f"........... connection attempt delay {self.delayBetweenAttempts} seconds") await asyncio.sleep(self.delayBetweenAttempts) self.createNotification( @@ -1948,7 +1948,6 @@ async def connect(self): except (ConnectTimeout, HTTPError) as ex: createNotification( AvailableNotifications.CONNECTION_PROBLEM, - "Visonic Panel Connection Error: {}
" - "You will need to restart hass after fixing." - "".format(ex)) + "Visonic Panel Connection Error: {ex}
" + "You will need to restart hass after fixing.") #return False diff --git a/custom_components/visonic/examples/complete_example.py b/custom_components/visonic/examples/complete_example.py index e7859b7..70eaeac 100644 --- a/custom_components/visonic/examples/complete_example.py +++ b/custom_components/visonic/examples/complete_example.py @@ -35,9 +35,9 @@ # config parameters for myconfig, just to make the defaults easier CONF_DOWNLOAD_CODE = "download_code" -CONF_LANGUAGE = "language" +#CONF_LANGUAGE = "language" CONF_EMULATION_MODE = "emulation_mode" -CONF_SIREN_SOUNDING = "siren_sounding" +#CONF_SIREN_SOUNDING = "siren_sounding" class ConnectionMode(Enum): POWERLINK = 1 @@ -54,8 +54,8 @@ class PrintMode(Enum): myconfig = { CONF_DOWNLOAD_CODE: "", CONF_EMULATION_MODE: ConnectionMode.POWERLINK, - CONF_LANGUAGE: "Panel", - CONF_SIREN_SOUNDING: ["Intruder"] + #CONF_LANGUAGE: "EN", + #CONF_SIREN_SOUNDING: ["Intruder"] } string_type="string" @@ -109,7 +109,7 @@ def format(self, record): elapsed_seconds = record.created - self.start_time # using timedelta here for convenient default formatting elapsed = str(timedelta(seconds=elapsed_seconds)) - return "{: <15} <{: <15}:{: >5}> {: >8} {}".format(elapsed, record.filename, record.lineno, record.levelname, record.getMessage()) + return f"{elapsed: <15} <{record.filename: <15}:{record.lineno: >5}> {record.levelname: >8} {record.getMessage()}" # remove existing handlers while root_logger.hasHandlers(): @@ -151,7 +151,7 @@ def ConfigureLogger(mode, console = None): console.print("Setting output mode to ERROR") else: if console is not None: - console.print("Not Setting output mode, unknown mode {0}".format(mode)) + console.print(f"Not Setting output mode, unknown mode {mode}") class MyTransport(AlTransport): @@ -223,12 +223,12 @@ def __init__(self, loop, config): def onSensorChange(self, sensor : AlSensorDevice, s : AlSensorCondition): if self.process_sensor is not None: self.process_sensor(sensor) -# print("onSensorChange {0} {1}".format(s.name, sensor) ) +# print(f"onSensorChange {s.name} {sensor}") def onSwitchChange(self, switch : AlSwitchDevice): if self.process_x10 is not None: self.process_x10(switch) -# print("onSwitchChange {0}".format(switch)) +# print(f"onSwitchChange {switch}") def onNewSwitch(self, switch: AlSwitchDevice): """Process a new x10.""" @@ -369,11 +369,11 @@ async def async_create_tcp_visonic_connection(self, address, port, panelConfig : except socket.error as _: err = _ - print("Setting TCP socket Options Exception {0}".format(err)) + print(f"Setting TCP socket Options Exception {err}") if sock is not None: sock.close() except Exception as exc: - print("Setting TCP Options Exception {0}".format(exc)) + print(f"Setting TCP Options Exception {exc}") return None, None @@ -649,7 +649,7 @@ async def controller(client : VisonicClient, console : MyAsyncConsole): def process_event(event_id : AlCondition, data : dict = None): # event means there's been a panel state change if event_id is not AlCondition.PUSH_CHANGE: - console.print("Visonic update event condition {0} {1}".format(str(event_id), data)) + console.print(f"Visonic update event condition {str(event_id)} {data}") def process_log(event_log_entry : AlLogPanelEvent): """ Process a sequence of panel log events """ @@ -770,7 +770,7 @@ def help(): command = result[0] ar = result.split(' ') processedInput = False - #print("Command Received {0}".format(command)) + #print(f"Command Received {command}") if client.isSystemStarted(): # There must be a panel connection to do the following commands if command == 'c': @@ -829,7 +829,7 @@ def help(): # output mode if len(ar) > 1: mode=str(ar[1].strip()).lower() - #console.print("Setting output mode to {0} :{1}:".format(mode, mode[0])) + #console.print(f"Setting output mode to {mode} :{mode[0]}:") ConfigureLogger(mode, console) else: console.print("Current output level is " + str(logger_level)) @@ -853,7 +853,7 @@ def help(): console.print("") for key, value in myconfig.items(): s = str(key) - console.print("{0} : {1} = {2}".format(c, s, value)) + console.print(f"{c} : {s} = {value}") c = c + 1 console.print("") elif command.isnumeric() == True: @@ -879,7 +879,7 @@ def help(): ex_type, ex_value, ex_traceback = sys.exc_info() if str(ex_value) != terminating_clean: - print("Exception {0} {1}".format(len(terminating_clean),len(ex_value))) + print(f"Exception {len(terminating_clean)} {len(ex_value)}") print("Exception: ") print(f" type : {ex_type.__name__}") print(f" message : {ex_value}") diff --git a/custom_components/visonic/examples/simple_example.py b/custom_components/visonic/examples/simple_example.py index 9c910c7..b192083 100644 --- a/custom_components/visonic/examples/simple_example.py +++ b/custom_components/visonic/examples/simple_example.py @@ -27,7 +27,7 @@ CONF_DOWNLOAD_CODE = "download_code" CONF_LANGUAGE = "language" CONF_EMULATION_MODE = "emulation_mode" -CONF_SIREN_SOUNDING = "siren_sounding" +#CONF_SIREN_SOUNDING = "siren_sounding" class ConnectionMode(Enum): POWERLINK = 1 @@ -36,9 +36,9 @@ class ConnectionMode(Enum): myconfig = { CONF_DOWNLOAD_CODE: "", - CONF_EMULATION_MODE: ConnectionMode.POWERLINK, - CONF_LANGUAGE: "Panel", - CONF_SIREN_SOUNDING: ["Intruder"] + CONF_EMULATION_MODE: ConnectionMode.POWERLINK +# CONF_LANGUAGE: "EN" +# CONF_SIREN_SOUNDING: ["Intruder"] } def toBool(val) -> bool: @@ -108,9 +108,9 @@ def getConfigData() -> PanelConfig: return { AlConfiguration.DownloadCode: myconfig.get(CONF_DOWNLOAD_CODE, ""), AlConfiguration.ForceStandard: ForceStandardMode, - AlConfiguration.DisableAllCommands: DisableAllCommands, - AlConfiguration.PluginLanguage: myconfig.get(CONF_LANGUAGE, "Panel"), - AlConfiguration.SirenTriggerList: myconfig.get(CONF_SIREN_SOUNDING, ["Intruder"]) + AlConfiguration.DisableAllCommands: DisableAllCommands + #AlConfiguration.PluginLanguage: myconfig.get(CONF_LANGUAGE, "Panel"), + #AlConfiguration.SirenTriggerList: myconfig.get(CONF_SIREN_SOUNDING, ["Intruder"]) } def callback_handler(visonic_devices, dict={}): @@ -119,14 +119,14 @@ def callback_handler(visonic_devices, dict={}): _LOGGER.debug("Visonic attempt to add device when sensor is undefined") return if type(visonic_devices) == defaultdict: - _LOGGER.debug("Visonic got new sensors {0}".format(visonic_devices)) + _LOGGER.debug(f"Visonic got new sensors {visonic_devices}") elif type(visonic_devices) == pyvisonic.SensorDevice: # This is an update of an existing device - _LOGGER.debug("Visonic got a sensor update {0}".format(visonic_devices)) + _LOGGER.debug(f"Visonic got a sensor update {visonic_devices}") elif type(visonic_devices) == int: - _LOGGER.debug("Visonic got an Event {0} {1}".format(visonic_devices,dict)) + _LOGGER.debug(f"Visonic got an Event {visonic_devices} {dict}") else: - _LOGGER.debug("Visonic attempt to add device with type {0} device is {1}".format(type(visonic_devices), visonic_devices)) + _LOGGER.debug(f"Visonic attempt to add device with type {type(visonic_devices)} device is {visonic_devices}") def onNewSwitch(dev: AlSwitchDevice): """Process a new x10.""" @@ -215,11 +215,11 @@ async def async_create_tcp_visonic_connection(address, port, panelConfig : Panel except socket.error as _: err = _ - print("Setting TCP socket Options Exception {0}".format(err)) + print(f"Setting TCP socket Options Exception {err}") if sock is not None: sock.close() except Exception as exc: - print("Setting TCP Options Exception {0}".format(exc)) + print(f"Setting TCP Options Exception {exc}") return None, None # Create a connection using asyncio through a linux port (usb or rs232) @@ -281,7 +281,7 @@ def format(self, record): elapsed_seconds = record.created - self.start_time # using timedelta here for convenient default formatting elapsed = str(timedelta(seconds=elapsed_seconds)) - return "{: <15} <{: <15}:{: >5}> {: >8} {}".format(elapsed, record.filename, record.lineno, record.levelname, record.getMessage()) + return f"{elapsed: <15} <{record.filename: <15}:{record.lineno: >5}> {record.levelname: >8} {record.getMessage()}" # add custom formatter to root logger formatter = ElapsedFormatter() diff --git a/custom_components/visonic/manifest.json b/custom_components/visonic/manifest.json index 0cf88b3..60c42b5 100644 --- a/custom_components/visonic/manifest.json +++ b/custom_components/visonic/manifest.json @@ -11,5 +11,5 @@ "loggers": ["visonic"], "requirements": ["Pillow", "pyserial_asyncio"], "single_config_entry": false, - "version": "0.9.9.5" + "version": "0.9.9.6" } diff --git a/custom_components/visonic/pyconst.py b/custom_components/visonic/pyconst.py index 07c8df8..f51b4b1 100644 --- a/custom_components/visonic/pyconst.py +++ b/custom_components/visonic/pyconst.py @@ -325,13 +325,13 @@ def __init__(self): def __str__(self): strn = "" - strn = strn + ("part=None" if self.partition is None else "part={0:<2}".format(self.partition)) - strn = strn + (" current=None" if self.current is None else " current={0:<2}".format(self.current)) - strn = strn + (" total=None" if self.total is None else " total={0:<2}".format(self.total)) - strn = strn + (" time=None" if self.time is None else " time={0:<2}".format(self.time)) - strn = strn + (" date=None" if self.date is None else " date={0:<2}".format(self.date)) - strn = strn + (" zone=None" if self.zone is None else " zone={0:<2}".format(self.zone)) - strn = strn + (" event=None" if self.event is None else " event={0:<2}".format(self.event)) + strn = strn + ("part=None" if self.partition is None else f"part={self.partition:<2}") + strn = strn + (" current=None" if self.current is None else f" current={self.current:<2}") + strn = strn + (" total=None" if self.total is None else f" total={self.total:<2}") + #strn = strn + (" time=None" if self.time is None else f" time={self.time:<2}") + strn = strn + (" date=None" if self.dateandtime is None else f" date={self.dateandtime}") + strn = strn + (" zone=None" if self.zone is None else f" zone={self.zone:<2}") + strn = strn + (" event=None" if self.event is None else f" event={self.event:<2}") return strn @@ -405,7 +405,7 @@ def getMotionDelayTime(self) -> str: # Do not override me def createFriendlyName(self) -> str: - return "Z{0:0>2}".format(self.getDeviceID()) + return f"Z{self.getDeviceID():0>2}" # Return the sensor model. This is a string such as "Visonic MTT-302" to show in the HA frontend def getSensorModel(self) -> str: @@ -451,7 +451,7 @@ def onChange(self, callback : Callable = None): def createFriendlyName(self) -> str: if self.getDeviceID() == 0: return "PGM" - return "X{0:0>2}".format(self.getDeviceID()) + return f"X{self.getDeviceID():0>2}" class PanelConfig(TypedDict): @@ -515,6 +515,7 @@ def getPanelMode(self) -> AlPanelMode: """ Get the panel Mode e.g. Standard, Powerlink etc. """ return AlPanelMode.UNKNOWN + @abstractmethod def isPowerMaster(self) -> bool: """ Get the panel type, PowerMaster or not """ return False diff --git a/custom_components/visonic/pyhelper.py b/custom_components/visonic/pyhelper.py index 6eec65d..01048ad 100644 --- a/custom_components/visonic/pyhelper.py +++ b/custom_components/visonic/pyhelper.py @@ -195,27 +195,27 @@ def __str__(self): else: stypestr = "Unknown" strn = "" - strn = strn + ("id=None" if self.id == None else "id={0:<2}".format(self.id)) - #strn = strn + (" Zone=None" if self.dname == None else " Zone={0:<4}".format(self.dname[:4])) - strn = strn + (" Type={0:<8}".format(stypestr)) - # temporarily miss it out to shorten the line in debug messages strn = strn + (" model=None" if self.model == None else " model={0:<8}".format(self.model[:14])) - # temporarily miss it out to shorten the line in debug messages strn = strn + (" sid=None" if self.sid == None else " sid={0:<3}".format(self.sid, type(self.sid))) - # temporarily miss it out to shorten the line in debug messages strn = strn + (" ztype=None" if self.ztype == None else " ztype={0:<2}".format(self.ztype, type(self.ztype))) - strn = strn + (" Loc=None " if self.zname == None else " Loc={0:<14}".format(self.zname[:14])) - strn = strn + (" ztypeName=None " if self.ztypeName == None else " ztypeName={0:<10}".format(self.ztypeName[:10])) - strn = strn + (" ztamper=--" if self.ztamper == None else " ztamper={0:<2}".format(self.ztamper)) - strn = strn + (" ztrip=--" if self.ztrip == None else " ztrip={0:<2}".format(self.ztrip)) - strn = strn + (" zchime=None " if self.zchime == None else " zchime={0:<16}".format(self.zchime, type(self.zchime))) - # temporarily miss it out to shorten the line in debug messages strn = strn + (" partition=None" if self.partition == None else " partition={0}".format(self.partition, type(self.partition))) - strn = strn + (" bypass=--" if self.bypass == None else " bypass={0:<2}".format(self.bypass)) - strn = strn + (" lowbatt=--" if self.lowbatt == None else " lowbatt={0:<2}".format(self.lowbatt)) - strn = strn + (" status=--" if self.status == None else " status={0:<2}".format(self.status)) - strn = strn + (" tamper=--" if self.tamper == None else " tamper={0:<2}".format(self.tamper)) - strn = strn + (" enrolled=--" if self.enrolled == None else " enrolled={0:<2}".format(self.enrolled)) - strn = strn + (" triggered=--" if self.triggered == None else " triggered={0:<2}".format(self.triggered)) + strn = strn + ("id=None" if self.id == None else f"id={self.id:<2}") + #strn = strn + (" Zone=None" if self.dname == None else f" Zone={self.dname[:4]:<4}") + strn = strn + (f" Type={stypestr:<8}") + # temporarily miss it out to shorten the line in debug messages strn = strn + (" model=None" if self.model == None else f" model={self.model[:14]:<8}") + # temporarily miss it out to shorten the line in debug messages strn = strn + (" sid=None" if self.sid == None else f" sid={self.sid:<3}") + # temporarily miss it out to shorten the line in debug messages strn = strn + (" ztype=None" if self.ztype == None else f" ztype={self.ztype:<2}") + strn = strn + (" Loc=None " if self.zname == None else f" Loc={self.zname[:14]:<14}") + strn = strn + (" ztypeName=None " if self.ztypeName == None else f" ztypeName={self.ztypeName[:10]:<10}") + strn = strn + (" ztamper=--" if self.ztamper == None else f" ztamper={self.ztamper:<2}") + strn = strn + (" ztrip=--" if self.ztrip == None else f" ztrip={self.ztrip:<2}") + strn = strn + (" zchime=None " if self.zchime == None else f" zchime={self.zchime:<16}") + strn = strn + (" partition=None " if self.partition == None else f" partition={pt:<7}") + strn = strn + (" bypass=--" if self.bypass == None else f" bypass={self.bypass:<2}") + strn = strn + (" lowbatt=--" if self.lowbatt == None else f" lowbatt={self.lowbatt:<2}") + strn = strn + (" status=--" if self.status == None else f" status={self.status:<2}") + strn = strn + (" tamper=--" if self.tamper == None else f" tamper={self.tamper:<2}") + strn = strn + (" enrolled=--" if self.enrolled == None else f" enrolled={self.enrolled:<2}") + strn = strn + (" triggered=--" if self.triggered == None else f" triggered={self.triggered:<2}") if self.motiondelaytime is not None and (self.stype == AlSensorType.MOTION or self.stype == AlSensorType.CAMERA): - strn = strn + (" delay={0:<7}".format("Not Set" if self.motiondelaytime == 0xFFFF else str(self.motiondelaytime))) + strn = strn + f" delay={'Not Set' if self.motiondelaytime == 0xFFFF else str(self.motiondelaytime):<7}" return strn @@ -327,23 +327,23 @@ def getMotionDelayTime(self) -> str: return NO_DELAY_SET def _updateContactSensor(self, status = None, trigger = None): - #log.debug("[UpdateContactSensor] Sensor {0} before".format(self.id)) + #log.debug(f"[UpdateContactSensor] Sensor {self.id} before") #self._dumpSensorsToLogFile() if trigger is not None and trigger: # If trigger is set then the caller is confident that it is a motion or camera sensor - log.debug("[UpdateContactSensor] Sensor {0} triggered to True".format(self.id)) + log.debug(f"[UpdateContactSensor] Sensor {self.id} triggered to True") self.triggered = True self.triggertime = getTimeFunction() self.pushChange(AlSensorCondition.STATE) elif status is not None and self.status != status: # The current setting is different if status: - log.debug("[UpdateContactSensor] Sensor {0} triggered to True".format(self.id)) + log.debug(f"[UpdateContactSensor] Sensor {self.id} triggered to True") self.triggered = True self.triggertime = getTimeFunction() if self.getSensorType() != AlSensorType.MOTION and self.getSensorType() != AlSensorType.CAMERA: # Not a motion or camera to set status - log.debug("[UpdateContactSensor] Sensor {0} status from {1} to {2}".format(self.id, self.status, status)) + log.debug(f"[UpdateContactSensor] Sensor {self.id} status from {self.status} to {status}") self.status = status #if status is not None and not status: # self.SensorList[sensor].pushChange(AlSensorCondition.RESET) @@ -418,9 +418,10 @@ def do_tamper(self, val : bool) -> bool: return True # The value has changed return False # The value has not changed +""" # JSON conversions def fromJSON(self, decode): - #log.debug(" In sensor fromJSON start {0}".format(self)) + #log.debug(f" In sensor fromJSON start {self}") if "triggered" in decode: self.triggered = toBool(decode["triggered"]) if "open" in decode: @@ -450,7 +451,7 @@ def fromJSON(self, decode): self.model = titlecase(decode["sensor_model"]) if "motion_delay_time" in decode: self.motiondelaytime = titlecase(decode["motion_delay_time"]) - #log.debug(" In sensor fromJSON end {0}".format(self)) + #log.debug(f" In sensor fromJSON end {self}") self.hasJPG = False def toJSON(self) -> dict: @@ -471,7 +472,7 @@ def toJSON(self) -> dict: "motion_delay_time": "" if self.getMotionDelayTime() is None else self.getMotionDelayTime(), "chime": str(self.getChimeType()) }) # , ensure_ascii=True return dd - +""" class AlSwitchDeviceHelper(AlSwitchDevice): @@ -486,12 +487,12 @@ def __init__(self, **kwargs): def __str__(self): strn = "" - strn = strn + ("id=None" if self.id == None else "id={0:<2}".format(self.id)) - #strn = strn + (" name=None" if self.name == None else " name={0:<4}".format(self.name)) - strn = strn + (" Type=None " if self.type == None else " Type={0:<15}".format(self.type)) - strn = strn + (" Loc=None " if self.location == None else " Loc={0:<14}".format(self.location)) - strn = strn + (" enabled=None" if self.enabled == None else " enabled={0:<2}".format(self.enabled)) - strn = strn + (" state=None" if self.state == None else " state={0:<8}".format(self.state)) + strn = strn + ("id=None" if self.id == None else f"id={self.id:<2}") + #strn = strn + (" name=None" if self.name == None else f" name={self.name:<4}") + strn = strn + (" Type=None " if self.type == None else f" Type={self.type:<15}") + strn = strn + (" Loc=None " if self.location == None else f" Loc={self.location:<14}") + strn = strn + (" enabled=None" if self.enabled == None else f" enabled={self.enabled:<2}") + strn = strn + (" state=None" if self.state == None else f" state={self.state:<8}") return strn def __eq__(self, other): @@ -536,6 +537,7 @@ def getLocation(self) -> str: def isOn(self) -> bool: return self.state # +""" def fromJSON(self, decode): if "enabled" in decode: self.enabled = toBool(decode["enabled"]) @@ -556,7 +558,7 @@ def toJSON(self) -> dict: "location": str(self.getLocation()), "state": "On" if self.state else "Off" }) # , ensure_ascii=True return dd - +""" class ImageRecord: # The details of an individual image @@ -745,11 +747,11 @@ def _validatePDU(self, packet: bytearray) -> bool: return True if packet[-2:-1][0] == self._calculateCRC(packet[1:-2])[0] + 1: - log.debug("[_validatePDU] Validated a Packet with a checksum that is 1 more than the actual checksum!!!! {0} and {1} alt calc is {2}".format(toString(packet), hex(self._calculateCRC(packet[1:-2])[0]).upper(), hex(self._calculateCRCAlt(packet[1:-2])[0]).upper())) + log.debug(f"[_validatePDU] Validated a Packet with a checksum that is 1 more than the actual checksum!!!! {toString(packet)} and {hex(self._calculateCRC(packet[1:-2])[0]).upper()} alt calc is {hex(self._calculateCRCAlt(packet[1:-2])[0]).upper()}") return True if packet[-2:-1][0] == self._calculateCRC(packet[1:-2])[0] - 1: - log.debug("[_validatePDU] Validated a Packet with a checksum that is 1 less than the actual checksum!!!! {0} and {1} alt calc is {2}".format(toString(packet), hex(self._calculateCRC(packet[1:-2])[0]).upper(), hex(self._calculateCRCAlt(packet[1:-2])[0]).upper())) + log.debug(f"[_validatePDU] Validated a Packet with a checksum that is 1 less than the actual checksum!!!! {toString(packet)} and {hex(self._calculateCRC(packet[1:-2])[0]).upper()} alt calc is {hex(self._calculateCRCAlt(packet[1:-2])[0]).upper()}") return True log.debug("[_validatePDU] Not valid packet, CRC failed, may be ongoing and not final 0A") @@ -841,10 +843,10 @@ def _initVars(self): def _dumpSensorsToLogFile(self, incX10 = False): log.debug(" ================================================================================ Display Status ================================================================================") for key, sensor in self.SensorList.items(): - log.debug(" key {0:<2} Sensor {1}".format(key, sensor)) + log.debug(f" key {key:<2} Sensor {sensor}") if incX10: for key, device in self.SwitchList.items(): - log.debug(" key {0:<2} X10 {1}".format(key, device)) + log.debug(f" key {key:<2} X10 {device}") log.debug(" Model {: <18} PowerMaster {: <18} Ready {: <13}".format(self.PanelModel, 'Yes' if self.PowerMaster else 'No', 'Yes' if self.PanelReady else 'No')) diff --git a/custom_components/visonic/pyvisonic.py b/custom_components/visonic/pyvisonic.py index 504586e..fa13ad3 100644 --- a/custom_components/visonic/pyvisonic.py +++ b/custom_components/visonic/pyvisonic.py @@ -105,7 +105,7 @@ def convertByteArray(s) -> bytearray: from pyhelper import (toString, MyChecksumCalc, AlImageManager, ImageRecord, titlecase, AlPanelInterfaceHelper, AlSensorDeviceHelper, AlSwitchDeviceHelper) -PLUGIN_VERSION = "1.4.3.3" +PLUGIN_VERSION = "1.4.3.4" # Obfuscate sensitive data, regardless of the other Debug settings. # Setting this to True limits the logging of messages sent to the panel to CMD or NONE @@ -248,7 +248,7 @@ def peek_nowait(self): # Panel Names for each panel type (0-16). # 0 : "PowerMax" is not a supported panel type # Assume 360R is Panel 16 for this release as it was released after the PM33, also I've an old log file from a user that indicates this -pmPanelType_t = { +pmPanelType = { 0 : "PowerMax", 1 : "PowerMax+", 2 : "PowerMax Pro", @@ -269,7 +269,7 @@ def peek_nowait(self): # So make column 16 the same as column 13 # Don't know what 9, 11, 12 or 14 are so just copy other settings. I know that there are Commercial/Industry Panel versions so it might be them # This data defines each panel type's maximum capability -pmPanelConfig_t = { # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 See pmPanelType_t above +pmPanelConfig = { # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 See pmPanelType above "CFG_SUPPORTED" : ( False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True ), # Supported Panels i.e. not a PowerMax "CFG_KEEPALIVE" : ( 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 15, 25, 25, 15 ), # Keep Alive message interval if no other messages sent "CFG_DLCODE_1" : ( "", "5650", "5650", "5650", "5650", "5650", "5650", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA", "AAAA" ), # Default download codes (for reset panels or panels that have not been changed) @@ -323,7 +323,7 @@ def peek_nowait(self): "MSG_SER_TYPE" : VisonicCommand(convertByteArray('5A 30 04 01 00 00 00 00 00 00 00') , [0x33] , False, False, SendDebugM, 0.0, "Get Serial Type" ), "MSG_EVENTLOG" : VisonicCommand(convertByteArray('A0 00 00 00 99 99 00 00 00 00 00 43'), [0xA0] , False, False, SendDebugC, 0.0, "Retrieving Event Log" ), - "MSG_ARM" : VisonicCommand(convertByteArray('A1 00 00 99 99 99 07 00 00 00 00 43'), None , True, False, SendDebugC, 0.0, "(Dis)Arming System" ), # Including 07 + "MSG_ARM" : VisonicCommand(convertByteArray('A1 00 00 99 99 99 07 00 00 00 00 43'), None , True, False, SendDebugC, 0.0, "(Dis)Arming System" ), # Including 07 to arm all 3 partitions "MSG_MUTE_SIREN" : VisonicCommand(convertByteArray('A1 00 00 0B 99 99 00 00 00 00 00 43'), None , True, False, SendDebugC, 0.0, "Mute Siren" ), # "MSG_STATUS" : VisonicCommand(convertByteArray('A2 00 00 3F 00 00 00 00 00 00 00 43'), [0xA5] , True, False, SendDebugM, 0.0, "Getting Status" ), # Ask for A5 messages, the 0x3F asks for 01 02 03 04 05 06 messages "MSG_STATUS_SEN" : VisonicCommand(convertByteArray('A2 00 00 08 00 00 00 00 00 00 00 43'), [0xA5] , True, False, SendDebugM, 0.0, "Getting A5 04 Status" ), # Ask for A5 messages, the 0x08 asks for 04 message only @@ -364,7 +364,7 @@ def peek_nowait(self): } # B0 Messages subset that we can send to a Powermaster, embed within MSG_POWERMASTER to use -pmSendMsgB0_t = { +pmSendMsgB0 = { "ZONE_STAT04" : 0x04, "ZONE_STAT07" : 0x07, "ZONE_OPENCLOSE" : 0x18, # Sensor Open/Close State @@ -406,18 +406,18 @@ def peek_nowait(self): # 5/D/F/25 Arm Away # 14/1C/1E Arm Home Instant # 15/1D/1F Arm Away Instant -pmArmMode_t = { +pmArmMode = { AlPanelCommand.DISARM : 0x00, AlPanelCommand.ARM_HOME : 0x04, AlPanelCommand.ARM_AWAY : 0x05, AlPanelCommand.ARM_HOME_INSTANT : 0x14, AlPanelCommand.ARM_AWAY_INSTANT : 0x15 # "usertest" : 0x06, } # Data to embed in the MSG_PM_SIREN_MODE message # PowerMaster to command the siren mode -pmSirenMode_t = { +pmSirenMode = { AlPanelCommand.EMERGENCY : 0x23, AlPanelCommand.FIRE : 0x20, AlPanelCommand.PANIC : 0x0C } # Data to embed in the MSG_X10PGM message -pmX10State_t = { +pmX10State = { AlX10Command.OFF : 0x00, AlX10Command.ON : 0x01, AlX10Command.DIMMER : 0x0A, AlX10Command.BRIGHTEN : 0x0B } @@ -433,7 +433,7 @@ def peek_nowait(self): # ignorechecksum is for messages that do not have a checksum. These are F1 and F4 messages (so far) # When length is 0 then we stop processing the message on the first PACKET_FOOTER. This is only used for the short messages (4 or 5 bytes long) like ack, stop, denied and timeout PanelCallBack = collections.namedtuple("PanelCallBack", 'length ackneeded isvariablelength varlenbytepos flexiblelength ignorechecksum debugprint msg' ) -pmReceiveMsg_t = { +pmReceiveMsg = { 0x00 : PanelCallBack( 0, True, False, -1, 0, False, DebugLevel.NONE, "Dummy Message" ), # Dummy message used in the algorithm when the message type is unknown. The -1 is used to indicate an unknown message in the algorithm 0x02 : PanelCallBack( 0, False, False, 0, 0, False, DebugLevel.NONE, "Acknowledge" ), # Ack 0x06 : PanelCallBack( 0, True, False, 0, 0, False, RecvDebugC, "Timeout" ), # Timeout. See the receiver function for ACK handling @@ -496,7 +496,7 @@ def peek_nowait(self): pmPanelIgnoreSet = ( EVENT_TYPE_DELAY_RESTORE, EVENT_TYPE_CONFIRM_ALARM, EVENT_TYPE_INTERIOR_RESTORE, EVENT_TYPE_PERIMETER_RESTORE) # These 2 dictionaries are subsets of pmLogEvent_t -pmPanelAlarmType_t = { +pmPanelAlarmType = { 0x00 : AlAlarmType.NONE, 0x01 : AlAlarmType.INTRUDER, 0x02 : AlAlarmType.INTRUDER, 0x03 : AlAlarmType.INTRUDER, 0x04 : AlAlarmType.INTRUDER, 0x05 : AlAlarmType.INTRUDER, 0x06 : AlAlarmType.TAMPER, 0x07 : AlAlarmType.TAMPER, 0x08 : AlAlarmType.TAMPER, 0x09 : AlAlarmType.TAMPER, 0x0B : AlAlarmType.PANIC, 0x0C : AlAlarmType.PANIC, @@ -504,7 +504,7 @@ def peek_nowait(self): # 0x75 : AlAlarmType.TAMPER } -pmPanelTroubleType_t = { +pmPanelTroubleType = { # 0x00 : AlTroubleType.NONE, 0x01 : AlTroubleType.GENERAL, 0x0A : AlTroubleType.COMMUNICATION, 0x0F : AlTroubleType.GENERAL, 0x01 is already in AlarmType, it is not a General Trouble indication 0x00 : AlTroubleType.NONE, 0x0A : AlTroubleType.COMMUNICATION, 0x0F : AlTroubleType.GENERAL, 0x29 : AlTroubleType.BATTERY, 0x2B : AlTroubleType.POWER, 0x2D : AlTroubleType.BATTERY, 0x2F : AlTroubleType.JAMMING, @@ -515,7 +515,7 @@ def peek_nowait(self): # Zone names are taken from the panel, so no langauage support needed, these are updated when EEPROM is downloaded or the B0 message is received # TODO : Ensure that the EPROM and downloaded strings match the lower case with underscores to match the language files where possible -pmZoneName_t = [ +pmZoneName = [ "attic", "back_door", "basement", "bathroom", "bedroom", "child_room", "conservatory", "play_room", "dining_room", "downstairs", "emergency", "fire", "front_door", "garage", "garage_door", "guest_room", "hall", "kitchen", "laundry_room", "living_room", "master_bathroom", "master_bedroom", "office", "upstairs", "utility_room", "yard", "custom_1", "custom_2", "custom_3", @@ -533,7 +533,7 @@ def peek_nowait(self): # PMAX EEPROM CONFIGURATION version 1_2 SettingsCommand = collections.namedtuple('SettingsCommand', 'show count type poff psize pstep pbitoff name values') -DecodePanelSettings = { +pmDecodePanelSettings = { "jamDetect" : SettingsCommand( True, 1, "BYTE", 256, 8, 0, -1, "Jamming Detection", { '1':"UL 20/20", '2':"EN 30/60", '3':"Class 6", '4':"Other", '0':"Disable"} ), "entryDelays" : SettingsCommand( True, 2, "BYTE", 257, 8, 1, 2, ["Entry Delay 1","Entry Delay 2"], { '0':"None", '15':"15 Seconds", '30':"30 Seconds", '45':"45 Seconds", '60':"1 Minute", '180':"3 Minutes", '240':"4 Minutes"}), # 257, 258 "exitDelay" : SettingsCommand( True, 1, "BYTE", 259, 8, 0, -1, "Exit Delay", { '30':"30 Seconds", '60':"60 Seconds", '90':"90 Seconds", '120':"2 Minutes", '180':"3 Minutes", '240':"4 Minutes"}), @@ -658,7 +658,7 @@ def peek_nowait(self): "x10ZoneNames" : SettingsCommand( Dumpy, 16, "BYTE", 2864, 8, 1, -1, "X10 Location Name references", {} ), # #"MaybeScreenSaver":SettingsCommand( Dumpy, 75, "BYTE", 5888, 8, 1, -1, "Maybe the screen saver", {} ), # Structure not known -# "ZoneStringNames": SettingsCommand( Dumpy, 32,"STRING", 6400, 128, 16, -1, "Zone String Names", {} ), # Zone String Names e.g "Attic", "Back door", "Basement", "Bathroom" etc 32 strings of 16 characters each, replace pmZoneName_t +# "ZoneStringNames": SettingsCommand( Dumpy, 32,"STRING", 6400, 128, 16, -1, "Zone String Names", {} ), # Zone String Names e.g "Attic", "Back door", "Basement", "Bathroom" etc 32 strings of 16 characters each, replace pmZoneName #PowerMax Only @@ -704,7 +704,7 @@ def peek_nowait(self): } # 'show count type poff psize pstep pbitoff name values' -# These are the panel settings to keep a track of, most come from PanelSettingCodes and the EPROM/B0 +# These are the panel settings to keep a track of, most come from pmPanelSettingCodes and the EPROM/B0 class PanelSetting(IntEnum): UserCodes = 1 PanelSerial = 2 @@ -728,7 +728,7 @@ class PanelSetting(IntEnum): B0All = not OBFUS PanelSettingsCollection = collections.namedtuple('PanelSettingsCollection', 'length display datatype datacount msg') # overall length in bytes, datatype in bits -pmPanelSettingsB0_t = { +pmPanelSettingsB0 = { 0x0000 : PanelSettingsCollection( 6, B0All, 1, 6, "Central Station Account Number 1"), # size of each entry is 6 nibbles 0x0100 : PanelSettingsCollection( 6, B0All, 1, 6, "Central Station Account Number 1"), # size of each entry is 6 nibbles 0x0200 : PanelSettingsCollection( 7, B0All, 2, 0, "Panel Serial Number"), @@ -750,7 +750,7 @@ class PanelSetting(IntEnum): 0x5800 : PanelSettingsCollection( 4, B0All, 4, 2, "Unknown D"), } -# PanelSettingCodes represents the ways that we can get data to populate the PanelSettings +# pmPanelSettingCodes represents the ways that we can get data to populate the PanelSettings # A PowerMax Panel only has 1 way and that is to download the EPROM = PowerMaxEPROM # A PowerMaster Panel has 3 ways: # 1. Download the EPROM = PowerMasterEPROM @@ -760,26 +760,26 @@ class PanelSetting(IntEnum): PanelSettingCodesType = collections.namedtuple('PanelSettingCodesType', 'item PowerMaxEPROM PowerMasterEPROM PowerMasterB0Panel PowerMasterB0Message PowerMasterB0Index default') # For PowerMasterB0Message there is an assumption that the message type is 0x03, and this is the subtype # PowerMasterB0Index index 3 is Sensor data, I should have an enum for this -PanelSettingCodes = { # These are used to create the self.PanelSettings dictionary to create a common set of settings across the different ways of obtaining them - PanelSetting.UserCodes : PanelSettingCodesType( None, "userCodeMax", "userCodeMaster", 0x0800, None, None, [bytearray([0,0])] ), - PanelSetting.PanelSerial : PanelSettingCodesType( 0, "panelSerial", "panelSerial", 0x0200, None, None, ["Undefined"] ), - PanelSetting.Keypad_1Way : PanelSettingCodesType( None, "Keypad1PMax", None, "" , None, None, bytearray()), # PowerMaster Panels do not have 1 way keypads - PanelSetting.Keypad_2Way : PanelSettingCodesType( None, "Keypad2PMax", "KeypadPMaster", "" , None, None, bytearray()), - PanelSetting.KeyFob : PanelSettingCodesType( None, "KeyFobsPMax", "", "" , None, None, bytearray()), - PanelSetting.Sirens : PanelSettingCodesType( None, "SirensPMax", "SirensPMaster", "" , None, None, bytearray()), - PanelSetting.AlarmLED : PanelSettingCodesType( None, None, "AlarmLED", "" , None, None, bytearray()), - PanelSetting.PartitionData : PanelSettingCodesType( None, "PartitionData", "PartitionData", "" , None, None, bytearray()), - PanelSetting.ZoneNames : PanelSettingCodesType( None, "ZoneNamePMax", "ZoneNamePMaster", "" , pmSendMsgB0_t["ZONE_NAMES"], 3, bytearray()), - PanelSetting.ZoneTypes : PanelSettingCodesType( None, None, None, "" , pmSendMsgB0_t["ZONE_TYPES"], 3, bytearray()), # Indirectly from EPROM but needs to be calculated/extracted - PanelSetting.ZoneExt : PanelSettingCodesType( None, None, "ZoneExtPMaster", "" , None, None, bytearray()), - PanelSetting.ZoneDelay : PanelSettingCodesType( None, None, "ZoneDelay", "" , None, None, bytearray()), - PanelSetting.ZoneSignal : PanelSettingCodesType( None, "ZoneSignalPMax", "", "" , None, None, bytearray()), - PanelSetting.ZoneData : PanelSettingCodesType( None, "ZoneDataPMax", "ZoneDataPMaster", "" , None, None, bytearray()), - PanelSetting.ZoneEnrolled : PanelSettingCodesType( None, None, None, "" , None, None, {} ), - PanelSetting.PanicAlarm : PanelSettingCodesType( 0, "panicAlarm", "panicAlarm", "" , None, None, [False]), - PanelSetting.PanelBypass : PanelSettingCodesType( 0, "panelbypass", "panelbypass", "" , None, None, ["No Bypass"]), - PanelSetting.PanelModel : PanelSettingCodesType( 0, "panelModelCode", "panelModelCode", "" , None, None, [bytearray([0,0,0,0])]), - PanelSetting.ZoneChime : PanelSettingCodesType( None, None, None, "" , None, None, {} ) +pmPanelSettingCodes = { # These are used to create the self.PanelSettings dictionary to create a common set of settings across the different ways of obtaining them + PanelSetting.UserCodes : PanelSettingCodesType( None, "userCodeMax", "userCodeMaster", 0x0800, None, None, [bytearray([0,0])] ), + PanelSetting.PanelSerial : PanelSettingCodesType( 0, "panelSerial", "panelSerial", 0x0200, None, None, ["Undefined"] ), + PanelSetting.Keypad_1Way : PanelSettingCodesType( None, "Keypad1PMax", None, "" , None, None, bytearray()), # PowerMaster Panels do not have 1 way keypads + PanelSetting.Keypad_2Way : PanelSettingCodesType( None, "Keypad2PMax", "KeypadPMaster", "" , None, None, bytearray()), + PanelSetting.KeyFob : PanelSettingCodesType( None, "KeyFobsPMax", "", "" , None, None, bytearray()), + PanelSetting.Sirens : PanelSettingCodesType( None, "SirensPMax", "SirensPMaster", "" , None, None, bytearray()), + PanelSetting.AlarmLED : PanelSettingCodesType( None, None, "AlarmLED", "" , None, None, bytearray()), + PanelSetting.PartitionData : PanelSettingCodesType( None, "PartitionData", "PartitionData", "" , None, None, bytearray()), + PanelSetting.ZoneNames : PanelSettingCodesType( None, "ZoneNamePMax", "ZoneNamePMaster", "" , pmSendMsgB0["ZONE_NAMES"].data, 3, bytearray()), + PanelSetting.ZoneTypes : PanelSettingCodesType( None, None, None, "" , pmSendMsgB0["ZONE_TYPES"].data, 3, bytearray()), # Indirectly from EPROM but needs to be calculated/extracted + PanelSetting.ZoneExt : PanelSettingCodesType( None, None, "ZoneExtPMaster", "" , None, None, bytearray()), + PanelSetting.ZoneDelay : PanelSettingCodesType( None, None, "ZoneDelay", "" , None, None, bytearray()), + PanelSetting.ZoneSignal : PanelSettingCodesType( None, "ZoneSignalPMax", "", "" , None, None, bytearray()), + PanelSetting.ZoneData : PanelSettingCodesType( None, "ZoneDataPMax", "ZoneDataPMaster", "" , None, None, bytearray()), + PanelSetting.ZoneEnrolled : PanelSettingCodesType( None, None, None, "" , None, None, {} ), + PanelSetting.PanicAlarm : PanelSettingCodesType( 0, "panicAlarm", "panicAlarm", "" , None, None, [False]), + PanelSetting.PanelBypass : PanelSettingCodesType( 0, "panelbypass", "panelbypass", "" , None, None, ["No Bypass"]), + PanelSetting.PanelModel : PanelSettingCodesType( 0, "panelModelCode", "panelModelCode", "" , None, None, [bytearray([0,0,0,0])]), + PanelSetting.ZoneChime : PanelSettingCodesType( None, None, None, "" , None, None, {} ) } # These blocks are not value specific, they are used to download blocks of EEPROM data that we need without reference to what the data means @@ -838,7 +838,7 @@ class PanelSetting(IntEnum): #0x75 : ZoneSensorType("Next+ K9-85 MCW", AlSensorType.MOTION ), # Jan #0x86 : ZoneSensorType("MCT-426", AlSensorType.SMOKE ), # Jan ZoneSensorType = collections.namedtuple("ZoneSensorType", 'name func' ) -pmZoneSensorMax_t = { +pmZoneSensorMax = { 0x6D : ZoneSensorType("MCX-601 Wireless Repeater", AlSensorType.IGNORED ), # Joao-Sousa ********************* Wireless Repeater so exclude it ************** 0x08 : ZoneSensorType("MCT-302", AlSensorType.MAGNET ), # Fabio72 0x09 : ZoneSensorType("MCT-302", AlSensorType.MAGNET ), # Fabio72 @@ -877,7 +877,7 @@ class PanelSetting(IntEnum): # SMD-426 PG2 (photoelectric smoke detector) # SMD-427 PG2 (heat and photoelectric smoke detector) # SMD-429 PG2 (Smoke and Heat Detector) -pmZoneSensorMaster_t = { +pmZoneSensorMaster = { 0x01 : ZoneSensorType("Next PG2", AlSensorType.MOTION ), 0x03 : ZoneSensorType("Clip PG2", AlSensorType.MOTION ), 0x04 : ZoneSensorType("Next CAM PG2", AlSensorType.CAMERA ), @@ -907,7 +907,7 @@ class PanelSetting(IntEnum): ############################################################################################################################################################################################################################################## PanelArmedStatusCollection = collections.namedtuple('PanelArmedStatusCollection', 'disarmed armed entry state eventmapping') -PanelArmedStatus = { # disarmed armed entry state +pmPanelArmedStatus = { # disarmed armed entry state 0x00 : PanelArmedStatusCollection( True, False, False, AlPanelStatus.DISARMED , 85), # Disarmed 0x01 : PanelArmedStatusCollection( False, True, False, AlPanelStatus.ARMING_HOME , -1), # Arming Home 0x02 : PanelArmedStatusCollection( False, True, False, AlPanelStatus.ARMING_AWAY , -1), # Arming Away @@ -935,7 +935,7 @@ class PanelSetting(IntEnum): } ZoneEventActionCollection = collections.namedtuple('ZoneEventActionCollection', 'func problem parameter') -ZoneEventAction = { +pmZoneEventAction = { 0 : ZoneEventActionCollection("", "none", None ), # "None", 1 : ZoneEventActionCollection("do_tamper", "tamper", True ), # "Tamper Alarm", 2 : ZoneEventActionCollection("do_tamper", "none", False ), # "Tamper Restore", @@ -995,10 +995,10 @@ def __init__(self, command = None, raw = None, options = None, response = None): def __str__(self): if self.command is not None: - return ("Command:{0} Options:{1}".format(self.command.msg, self.options)) + return f"Command:{self.command.msg} Options:{self.options}" elif self.raw is not None: - return ("Raw: {0}".format(toString(self.raw))) - return ("Command:None") + return f"Raw: {toString(self.raw)}" + return "Command:None" def __lt__(self, other: object) -> bool: # Implement < based on the creation time if not isinstance(other, VisonicListEntry): @@ -1011,15 +1011,13 @@ def insertOptions(self, data : bytearray) -> bytearray: # the length of instruction.options has to be an even number # it is a list of couples: bitoffset , bytearray to insert #op = int(len(instruction.options) / 2) - # log.debug("[sendPdu] Options {0} {1}".format(instruction.options, op)) + # log.debug(f"[sendPdu] Options {instruction.options} {op}") for o in range(0, len(self.options)): s = self.options[o][0] # [o * 2] # bit offset as an integer a = self.options[o][1] # [o * 2 + 1] # the bytearray to insert if isinstance(a, int): - # log.debug("[sendPdu] Options {0} {1} {2} {3}".format(type(s), type(a), s, a)) data[s] = a else: - # log.debug("[sendPdu] Options {0} {1} {2} {3} {4}".format(type(s), type(a), s, a, len(a))) for i in range(0, len(a)): data[s + i] = a[i] return data @@ -1046,7 +1044,7 @@ def hexify(v : int) -> str: class ProtocolBase(AlPanelInterfaceHelper, AlPanelDataStream, MyChecksumCalc): """Manage low level Visonic protocol.""" - log.debug("Initialising Protocol - Protocol Version {0}".format(PLUGIN_VERSION)) + log.debug(f"Initialising Protocol - Protocol Version {PLUGIN_VERSION}") def __init__(self, loop=None, panelConfig : PanelConfig = None, panel_id : int = None, packet_callback: Callable = None) -> None: super().__init__(panel_id=panel_id) @@ -1086,7 +1084,7 @@ def __init__(self, loop=None, panelConfig : PanelConfig = None, panel_id : int = ######################################################################## self.pmIncomingPduLen = 0 # The length of the incoming message self.pmCrcErrorCount = 0 # The CRC Error Count for Received Messages - self.pmCurrentPDU = pmReceiveMsg_t[0] # The current receiving message type + self.pmCurrentPDU = pmReceiveMsg[0] # The current receiving message type self.pmFlexibleLength = 0 # How many bytes less then the proper message size do we start checking for PACKET_FOOTER and a valid CRC # The receive byte array for receiving a message self.ReceiveData = bytearray(b"") @@ -1140,7 +1138,7 @@ def resetGlobals(self): self.PanelSettings = {} # This is the record of settings for the integration to work for key in PanelSetting: - self.PanelSettings[key] = PanelSettingCodes[key].default # populate each setting with the default + self.PanelSettings[key] = pmPanelSettingCodes[key].default # populate each setting with the default self.B0_Message_Count = 0 self.B0_Message_Wanted = set() @@ -1219,20 +1217,20 @@ def resetGlobals(self): def updateSettings(self, newdata: PanelConfig): if newdata is not None: - # log.debug("[updateSettings] Settings refreshed - Using panel config {0}".format(newdata)) + # log.debug(f"[updateSettings] Settings refreshed - Using panel config {newdata}") if AlConfiguration.ForceStandard in newdata: # Get user variable from HA to force standard mode or try for PowerLink self.ForceStandardMode = newdata[AlConfiguration.ForceStandard] - log.debug("[Settings] Force Standard set to {0}".format(self.ForceStandardMode)) + log.debug(f"[Settings] Force Standard set to {self.ForceStandardMode}") if AlConfiguration.DisableAllCommands in newdata: # Get user variable from HA to Disable All Commands self.DisableAllCommands = newdata[AlConfiguration.DisableAllCommands] - log.debug("[Settings] Disable All Commands set to {0}".format(self.DisableAllCommands)) + log.debug(f"[Settings] Disable All Commands set to {self.DisableAllCommands}") if AlConfiguration.DownloadCode in newdata: tmpDLCode = newdata[AlConfiguration.DownloadCode] # INTERFACE : Get the download code if len(tmpDLCode) == 4 and type(tmpDLCode) is str: self.DownloadCode = tmpDLCode[0:2] + " " + tmpDLCode[2:4] - log.debug("[Settings] Download Code set to {0}".format(self.DownloadCode)) + log.debug(f"[Settings] Download Code set to {self.DownloadCode}") if self.DisableAllCommands: self.ForceStandardMode = True @@ -1267,7 +1265,6 @@ def vp_connection_lost(self, exc): log.error(f"ERROR Connection Lost : disconnected because the Ethernet/USB connection was externally terminated. {exc}") if exc is not None: - # log.exception("ERROR Connection Lost : disconnected due to exception <{0}>".format(exc)) log.error(f"ERROR Connection Lost : disconnected due to external error, exception data = {exc}") self._performDisconnect(AlTerminationType.EXTERNAL_TERMINATION) else: @@ -1358,18 +1355,18 @@ def setNextDownloadCode(self, paneltype) -> str: # The first time its called it leaves DownloadCode alone if self.nextDownloadCode is None: tmp = self.DownloadCode[:2] + self.DownloadCode[2:] - if tmp == pmPanelConfig_t["CFG_DLCODE_1"][paneltype]: # The base setting is the same as DLCODE 1 so set the next one to be DLCODE 2 - self.nextDownloadCode = pmPanelConfig_t["CFG_DLCODE_2"][paneltype] + if tmp == pmPanelConfig["CFG_DLCODE_1"][paneltype]: # The base setting is the same as DLCODE 1 so set the next one to be DLCODE 2 + self.nextDownloadCode = pmPanelConfig["CFG_DLCODE_2"][paneltype] else: - self.nextDownloadCode = pmPanelConfig_t["CFG_DLCODE_1"][paneltype] - elif self.nextDownloadCode == pmPanelConfig_t["CFG_DLCODE_1"][paneltype]: - self.DownloadCode = pmPanelConfig_t["CFG_DLCODE_1"][paneltype][:2] + " " + pmPanelConfig_t["CFG_DLCODE_1"][paneltype][2:] - self.nextDownloadCode = pmPanelConfig_t["CFG_DLCODE_2"][paneltype] - elif self.nextDownloadCode == pmPanelConfig_t["CFG_DLCODE_2"][paneltype]: - self.DownloadCode = pmPanelConfig_t["CFG_DLCODE_2"][paneltype][:2] + " " + pmPanelConfig_t["CFG_DLCODE_2"][paneltype][2:] - self.nextDownloadCode = pmPanelConfig_t["CFG_DLCODE_3"][paneltype] - elif self.nextDownloadCode == pmPanelConfig_t["CFG_DLCODE_3"][paneltype]: - self.DownloadCode = pmPanelConfig_t["CFG_DLCODE_3"][paneltype][:2] + " " + pmPanelConfig_t["CFG_DLCODE_3"][paneltype][2:] + self.nextDownloadCode = pmPanelConfig["CFG_DLCODE_1"][paneltype] + elif self.nextDownloadCode == pmPanelConfig["CFG_DLCODE_1"][paneltype]: + self.DownloadCode = pmPanelConfig["CFG_DLCODE_1"][paneltype][:2] + " " + pmPanelConfig["CFG_DLCODE_1"][paneltype][2:] + self.nextDownloadCode = pmPanelConfig["CFG_DLCODE_2"][paneltype] + elif self.nextDownloadCode == pmPanelConfig["CFG_DLCODE_2"][paneltype]: + self.DownloadCode = pmPanelConfig["CFG_DLCODE_2"][paneltype][:2] + " " + pmPanelConfig["CFG_DLCODE_2"][paneltype][2:] + self.nextDownloadCode = pmPanelConfig["CFG_DLCODE_3"][paneltype] + elif self.nextDownloadCode == pmPanelConfig["CFG_DLCODE_3"][paneltype]: + self.DownloadCode = pmPanelConfig["CFG_DLCODE_3"][paneltype][:2] + " " + pmPanelConfig["CFG_DLCODE_3"][paneltype][2:] self.nextDownloadCode = "" # not None and invalid, so it goes to else next time else: ra = random.randint(10, 240) @@ -1422,7 +1419,7 @@ def setTimeInPanel(self, paneltime = None): if paneltime is not None and t.year > 2000: self.Panel_Integration_Time_Difference = t - paneltime d = self.Panel_Integration_Time_Difference.total_seconds() - log.debug("[setTimeInPanel] Local time is {0} time difference {1} seconds".format(t, d)) # + log.debug(f"[setTimeInPanel] Local time is {t} time difference {d} seconds") # if abs(d) < TIME_INTERVAL_ERROR: log.debug(f"[setTimeInPanel] Not Correcting Time in Panel as less than {TIME_INTERVAL_ERROR} seconds difference.") settime = False @@ -1434,7 +1431,7 @@ def setTimeInPanel(self, paneltime = None): self._addMessageToSendList("MSG_BUMP", urgent = True) else: self._addMessageToSendList("MSG_DOWNLOAD_TIME", urgent = True, options=[ [3, convertByteArray(self.DownloadCode)] ]) # Can only set the time in the panel in DOWNLOADING state - log.debug("[setTimeInPanel] Setting time in panel {0}".format(t)) + log.debug(f"[setTimeInPanel] Setting time in panel {t}") timePdu = bytearray([t.second + 1, t.minute, t.hour, t.day, t.month, t.year - 2000]) # add about 2 seconds on as it takes over 1 to get to the panel to set it self._addMessageToSendList("MSG_SETTIME", urgent = True, options=[ [3, timePdu] ]) self._addMessageToSendList("MSG_EXIT", urgent = True) @@ -1561,8 +1558,8 @@ def sleepytime(interval) -> float: self.pmExpectedResponse = set() # If it's only for an acknowledge response then ignore it else: st = '[{}]'.format(', '.join(hex(x) for x in self.pmExpectedResponse)) - log.debug("[_despatcher] ****************************** Response Timer Expired ********************************") - log.debug("[_despatcher] While Waiting for: {0}".format(st)) + log.debug(f"[_despatcher] ****************************** Response Timer Expired ********************************") + log.debug(f"[_despatcher] While Waiting for: {st}") # Reset Send state (clear queue and reset flags) self._clearReceiveResponseList() #self._emptySendQueue(pri_level = 1) @@ -1571,8 +1568,8 @@ def sleepytime(interval) -> float: # If there's a timeout then resend the previous message. If that doesn't work then dump the message and continue, but log the error if not self.pmLastSentMessage.triedResendingMessage: # resend the last message - log.debug("[_despatcher] ****************************** Resend Timer Expired ********************************") - log.debug("[_despatcher] Re-Sending last message {0}".format(self.pmLastSentMessage.command.msg)) + log.debug(f"[_despatcher] ****************************** Resend Timer Expired ********************************") + log.debug(f"[_despatcher] Re-Sending last message {self.pmLastSentMessage.command.msg}") self.pmLastSentMessage.triedResendingMessage = True post_delay = self._sendPdu(self.pmLastSentMessage) else: @@ -1729,7 +1726,7 @@ def processPanelErrorMessages() -> PanelErrorStates: if self.AccessDeniedMessage is not None: lastCommandData = self.AccessDeniedMessage.command.data if lastCommandData is not None: - log.debug("[_sequencer] AccessDenied last command {0}".format(toString(lastCommandData[:3] if OBFUS else lastCommandData))) + log.debug(f"[_sequencer] AccessDenied last command {toString(lastCommandData[:3] if OBFUS else lastCommandData)}") # Check download first, then pin, then stop if lastCommandData[0] == 0x24: log.debug("[_sequencer] Got an Access Denied and we have sent a Bump or a Download command to the Panel") @@ -1840,7 +1837,6 @@ def reset_vars(): no_data_received_counter = 0 # calculate the time interval back to the last receipt of any data interval = self._getUTCTimeFunction() - self.lastRecvTimeOfPanelData - # log.debug("Checking last receive time {0}".format(interval)) if interval >= timedelta(seconds=LAST_RECEIVE_DATA_TIMEOUT): log.error( "[_sequencer] Visonic Plugin has suspended all operations, there is a problem with the communication with the panel (i.e. data has not been received from the panel in " + str(interval) + ")" ) self._performDisconnect(AlTerminationType.NO_DATA_FROM_PANEL_DISCONNECTED) @@ -1941,7 +1937,6 @@ def reset_vars(): elif _sequencerState == SequencerType.InitialiseEPROMDownload: ################################################################ InitialiseEPROMDownload ############################################## interval = self._getUTCTimeFunction() - self.firstSendOfDownloadEprom - #log.debug("[_sequencer] interval={0} timeout={1} self.firstSendOfDownloadEprom(UTC)={2} timenow(UTC)={3}".format(interval, DOWNLOAD_TIMEOUT, self.firstSendOfDownloadEprom, self._getUTCTimeFunction())) if self.DownloadCounter >= DOWNLOAD_RETRY_COUNT or (not EEPROM_DOWNLOAD_ALL and interval > timedelta(seconds=DOWNLOAD_TIMEOUT)): # Give it DOWNLOAD_RETRY_COUNT attempts start the download @@ -2020,7 +2015,7 @@ def reset_vars(): _sequencerState = SequencerType.InitialisePanel else: interval = self._getUTCTimeFunction() - self.lastSendOfDownloadEprom - log.debug("[_sequencer] interval={0} td={1} self.lastSendOfDownloadEprom(UTC)={2} timenow(UTC)={3}".format(interval, DOWNLOAD_RETRY_DELAY, self.lastSendOfDownloadEprom, self._getUTCTimeFunction())) + log.debug(f"[_sequencer] interval={interval} td={DOWNLOAD_RETRY_DELAY} self.lastSendOfDownloadEprom(UTC)={self.lastSendOfDownloadEprom} timenow(UTC)={self._getUTCTimeFunction()}") if interval > timedelta(seconds=DOWNLOAD_RETRY_DELAY): # Give it this number of seconds to start the downloading _sequencerState = SequencerType.InitialiseEPROMDownload @@ -2140,14 +2135,14 @@ def reset_vars(): #self._addMessageToSendList(To_Send) # , res = [0xB0]) # Wait for B0 # Request Sensor Information and State - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_NAMES"]) # 21 - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_TYPES"]) # 2D - self.B0_Message_Wanted.add(pmSendMsgB0_t["SENSOR_STATUS"]) # 1F - self.B0_Message_Wanted.add(pmSendMsgB0_t["PANEL_STATE"]) # 24 - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_LAST_EVENT"]) # 4B - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_OPENCLOSE"]) # 18 - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_TEMPS"]) # 3D - self.B0_Message_Wanted.add(pmSendMsgB0_t["SENSOR_ENROL"]) # 1D + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_NAMES"]) # 21 + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_TYPES"]) # 2D + self.B0_Message_Wanted.add(pmSendMsgB0["SENSOR_STATUS"]) # 1F + self.B0_Message_Wanted.add(pmSendMsgB0["PANEL_STATE"]) # 24 + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_LAST_EVENT"]) # 4B + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_OPENCLOSE"]) # 18 + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_TEMPS"]) # 3D + self.B0_Message_Wanted.add(pmSendMsgB0["SENSOR_ENROL"]) # 1D else: # PowerMax get ZONE_NAMES, ZONE_TYPES etc self._addMessageToSendList("MSG_ZONENAME") self._addMessageToSendList("MSG_ZONETYPE") @@ -2275,16 +2270,17 @@ def reset_vars(): # If Std+ or PL then periodically check and then maybe update the time in the panel if self.AutoSyncTime: if self.isPowerMaster() and self.PanelMode in [AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK, AlPanelMode.POWERLINK_BRIDGED]: + # PowerMaster Panels if counter % POWERMASTER_CHECK_TIME_INTERVAL == 0 or (counter % 10 == 0 and (self.Panel_Integration_Time_Difference is None or (self.Panel_Integration_Time_Difference is not None and abs(self.Panel_Integration_Time_Difference.total_seconds()) > 5))): # Request Sensor Information and State # remember that self.B0_Message_Wanted is a set so can only be added once log.debug("[_sequencer] Adding Panel and Sensor State requests") - self.B0_Message_Wanted.add(pmSendMsgB0_t["SENSOR_STATUS"]) # 1F - self.B0_Message_Wanted.add(pmSendMsgB0_t["PANEL_STATE"]) # 24 - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_LAST_EVENT"]) # 4B - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_OPENCLOSE"]) # 18 - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_TEMPS"]) # 3D - #self.B0_Message_Wanted.add(pmSendMsgB0_t["SENSOR_ENROL"]) # 1D + self.B0_Message_Wanted.add(pmSendMsgB0["SENSOR_STATUS"]) # 1F + self.B0_Message_Wanted.add(pmSendMsgB0["PANEL_STATE"]) # 24 + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_LAST_EVENT"]) # 4B + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_OPENCLOSE"]) # 18 + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_TEMPS"]) # 3D + #self.B0_Message_Wanted.add(pmSendMsgB0["SENSOR_ENROL"]) # 1D elif self.PanelMode in [AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK]: # Powermax panels # We set the time and then check it periodically, and then set it again if different by more than 5 seconds # every 4 hours (approx) or if not set yet or a big difference (set from B0 data) @@ -2386,7 +2382,7 @@ def reset_vars(): # # As this does not use acknowledges or checksums then prevent the expected response timer from kicking in # self.ImageManager.terminateIfExceededTimeout(40) - # log.debug("[_sequencer] is {0}".format(self.watchdog_counter)) + # log.debug(f"[_sequencer] is {self.watchdog_counter}") # We create a B0 message to request other B0 messages from a PowerMaster panel. # Wait 1 second per B0 request between sending again to give the panel a chance to send them @@ -2454,11 +2450,11 @@ def _handle_received_byte(self, data): # Determine total length of the message by getting the variable part int(data) and adding it to the fixed length part self.pmIncomingPduLen = self.pmCurrentPDU.length + int(data) self.pmFlexibleLength = self.pmCurrentPDU.flexiblelength - #log.debug("[data receiver] Variable length Message Being Received Message Type {0} pmIncomingPduLen {1} data var {2}".format(hex(self.ReceiveData[1]).upper(), self.pmIncomingPduLen, int(data))) + #log.debug(f"[data receiver] Variable length Message Being Received Message Type {hex(self.ReceiveData[1]).upper()} pmIncomingPduLen {self.pmIncomingPduLen} data var {int(data)}") # If we were expecting a message of a particular length (i.e. self.pmIncomingPduLen > 0) and what we have is already greater then that length then dump the message and resynchronise. if 0 < self.pmIncomingPduLen <= pdu_len: # waiting for pmIncomingPduLen bytes but got more and haven't been able to validate a PDU - log.info("[data receiver] PDU Too Large: Dumping current buffer {0} The next byte is {1}".format(toString(self.ReceiveData), hex(data).upper())) + log.info(f"[data receiver] PDU Too Large: Dumping current buffer {toString(self.ReceiveData)} The next byte is {hex(data).upper()}") pdu_len = 0 # Reset the incoming data to 0 length self._resetMessageData() @@ -2473,30 +2469,30 @@ def _handle_received_byte(self, data): elif pdu_len == 1: #log.debug("[data receiver] Received message Type %d", data) - if data != 0x00 and data in pmReceiveMsg_t: # Is it a message type that we know about - self.pmCurrentPDU = pmReceiveMsg_t[data] # set to current message type parameter settings for length, does it need an ack etc + if data != 0x00 and data in pmReceiveMsg: # Is it a message type that we know about + self.pmCurrentPDU = pmReceiveMsg[data] # set to current message type parameter settings for length, does it need an ack etc self.ReceiveData.append(data) # Add on the message type to the buffer if not isinstance(self.pmCurrentPDU, dict): self.pmIncomingPduLen = self.pmCurrentPDU.length # for variable length messages this is the fixed length and will work with this algorithm until updated. - #log.debug("[data receiver] Building PDU: It's a message {0}; pmIncomingPduLen = {1} variable = {2}".format(hex(data).upper(), self.pmIncomingPduLen, self.pmCurrentPDU.isvariablelength)) + #log.debug(f"[data receiver] Building PDU: It's a message {hex(data).upper()}; pmIncomingPduLen = {self.pmIncomingPduLen} variable = {self.pmCurrentPDU.isvariablelength}") elif data == 0x00 or data == 0xFD: # Special case for pocket and PowerMaster 10 - #log.info("[data receiver] Received message type {0} so not processing it".format(hex(data).upper())) + #log.info(f"[data receiver] Received message type {hex(data).upper()} so not processing it") self._resetMessageData() else: # build an unknown PDU. As the length is not known, leave self.pmIncomingPduLen set to 0 so we just look for PACKET_FOOTER as the end of the PDU - self.pmCurrentPDU = pmReceiveMsg_t[0] # Set to unknown message structure to get settings, varlenbytepos is -1 + self.pmCurrentPDU = pmReceiveMsg[0] # Set to unknown message structure to get settings, varlenbytepos is -1 self.pmIncomingPduLen = 0 # self.pmIncomingPduLen should already be set to 0 but just to make sure !!! - log.warning("[data receiver] Warning : Construction of incoming packet unknown - Message Type {0}".format(hex(data).upper())) + log.warning(f"[data receiver] Warning : Construction of incoming packet unknown - Message Type {hex(data).upper()}") self.ReceiveData.append(data) # Add on the message type to the buffer elif pdu_len == 2 and isinstance(self.pmCurrentPDU, dict): - #log.debug("[data receiver] Building PDU: It's a variable message {0} {1}".format(hex(self.ReceiveData[0]).upper(), hex(data).upper())) + #log.debug(f"[data receiver] Building PDU: It's a variable message {hex(self.ReceiveData[0]).upper()} {hex(data).upper()}") if data in self.pmCurrentPDU: self.pmCurrentPDU = self.pmCurrentPDU[data] #log.debug("[data receiver] Building PDU: doing it properly") else: self.pmCurrentPDU = self.pmCurrentPDU[0] # All should have a 0 entry so use as default when unknown - log.debug("[data receiver] Building PDU: It's a variable message {0} {1} BUT it is unknown".format(hex(self.ReceiveData[0]).upper(), hex(data).upper())) + log.debug(f"[data receiver] Building PDU: It's a variable message {hex(self.ReceiveData[0]).upper()} {hex(data).upper()} BUT it is unknown") self.pmIncomingPduLen = self.pmCurrentPDU.length # for variable length messages this is the fixed length and will work with this algorithm until updated. self.ReceiveData.append(data) # Add on the message type to the buffer @@ -2527,7 +2523,7 @@ def _handle_received_byte(self, data): # We've got a validated message #log.debug("[data receiver] Building PDU: Got Validated PDU type 0x%02x data %s", int(msgType), toString(self.ReceiveData)) if self.pmCurrentPDU.varlenbytepos < 0: # is it an unknown message i.e. varlenbytepos is -1 - log.warning("[data receiver] Received Valid but Unknown PDU {0}".format(hex(msgType))) + log.warning(f"[data receiver] Received Valid but Unknown PDU {hex(msgType)}") self._sendAck() # assume we need to send an ack for an unknown message else: # Process the received known message self._processReceivedMessage(ackneeded=self.pmCurrentPDU.ackneeded, debugp=self.pmCurrentPDU.debugprint, msg=self.pmCurrentPDU.msg, data=self.ReceiveData) @@ -2537,13 +2533,13 @@ def _handle_received_byte(self, data): a = self._calculateCRC(self.ReceiveData[1:-2])[0] # this is just used to output to the log file if len(self.ReceiveData) > PACKET_MAX_SIZE: # If the length exceeds the max PDU size from the panel then stop and resync - log.warning("[data receiver] PDU with CRC error Message = {0} checksum calcs {1}".format(toString(self.ReceiveData), hex(a).upper())) + log.warning(f"[data receiver] PDU with CRC error Message = {toString(self.ReceiveData)} checksum calcs {hex(a).upper()}") self._processCRCFailure() self._resetMessageData() elif self.pmIncomingPduLen == 0: - if msgType in pmReceiveMsg_t: + if msgType in pmReceiveMsg: # A known message with zero length and an incorrect checksum. Reset the message data and resync - log.warning("[data receiver] Warning : Construction of zero length incoming packet validation failed - Message = {0} checksum calcs {1}".format(toString(self.ReceiveData), hex(a).upper())) + log.warning(f"[data receiver] Warning : Construction of zero length incoming packet validation failed - Message = {toString(self.ReceiveData)} checksum calcs {hex(a).upper()}") # Send an ack even though the its an invalid packet to prevent the panel getting confused if self.pmCurrentPDU.ackneeded: @@ -2555,10 +2551,10 @@ def _handle_received_byte(self, data): self._resetMessageData() else: # if msgType != 0xF1: # ignore CRC errors on F1 message # When self.pmIncomingPduLen == 0 then the message is unknown, the length is not known and we're waiting for a PACKET_FOOTER where the checksum is correct, so carry on - log.debug("[data receiver] Building PDU: Length is {0} bytes (apparently PDU not complete) {1} checksum calcs {2}".format(len(self.ReceiveData), toString(self.ReceiveData), hex(a).upper()) ) + log.debug(f"[data receiver] Building PDU: Length is {len(self.ReceiveData)} bytes (apparently PDU not complete) {toString(self.ReceiveData)} checksum calcs {hex(a).upper()}") else: # When here then the message is a known message type of the correct length but has failed it's validation - log.warning("[data receiver] Warning : Construction of incoming packet validation failed - Message = {0} checksum calcs {1}".format(toString(self.ReceiveData), hex(a).upper())) + log.warning(f"[data receiver] Warning : Construction of incoming packet validation failed - Message = {toString(self.ReceiveData)} checksum calcs {hex(a).upper()}") # Send an ack even though the its an invalid packet to prevent the panel getting confused if self.pmCurrentPDU.ackneeded: @@ -2581,7 +2577,7 @@ def _resetMessageData(self): # clear our buffer again so we can receive a new packet. self.ReceiveData = bytearray(b"") # messages should never be longer than PACKET_MAX_SIZE # Reset control variables ready for next time - self.pmCurrentPDU = pmReceiveMsg_t[0] + self.pmCurrentPDU = pmReceiveMsg[0] self.pmIncomingPduLen = 0 self.pmFlexibleLength = 0 @@ -2609,13 +2605,9 @@ def _processReceivedMessage(self, ackneeded, debugp, data, msg): #tmplength = len(self.pmExpectedResponse) if len(self.pmExpectedResponse) > 0: # and msgType != 2: # 2 is a simple acknowledge from the panel so ignore those # We've sent something and are waiting for a reponse - this is it - # log.debug("[data receiver] msgType {0} expected one of {1}".format(hex(msgType).upper(), [hex(no).upper() for no in self.pmExpectedResponse])) if msgType in self.pmExpectedResponse: # while msgType in self.pmExpectedResponse: self.pmExpectedResponse.remove(msgType) - #log.debug("[data receiver] msgType {0} got it so removed from list, list is now {1}".format(hex(msgType).upper(), [hex(no).upper() for no in self.pmExpectedResponse])) - #else: - # log.debug("[data receiver] msgType not in self.pmExpectedResponse Waiting for next PDU : expected {0} got {1}".format([hex(no).upper() for no in self.pmExpectedResponse], hex(msgType).upper())) if data is not None and debugp == DebugLevel.FULL: log.debug(f"[_processReceivedMessage] Received {msg} raw data {toString(data)} response list {[hex(no).upper() for no in self.pmExpectedResponse]}") @@ -2631,7 +2623,6 @@ def _sendAck(self, data=bytearray(b"")): """ Send ACK if packet is valid """ iscommand = data[1] >= 0x40 # command message types - #ispm = self.receivedPowerlinkAcknowledge or (len(data) > 3 and (data[1] == 0xAB or (data[1] < 0x10 and data[-2] == 0x43))) panel_state_enrolled = not self.pmDownloadMode and self.PanelMode in [AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.POWERLINK] # There are 2 types of acknowledge that we can send to the panel @@ -2698,23 +2689,21 @@ def _sendPdu(self, instruction: VisonicListEntry) -> float: # return the else: log.debug("[sendPdu] Comms transport has been set to none, must be in process of terminating comms") - # log.debug("[sendPdu] waiting for message response {}".format([hex(no).upper() for no in self.pmExpectedResponse])) - if command is not None and command.download: self.pmDownloadMode = True self.triggeredDownload = False log.debug("[sendPdu] Setting Download Mode to true") if command is not None and command.debugprint == DebugLevel.FULL: - log.debug("[sendPdu] Sent Command ({0}) raw data {1} waiting for message response {2}".format(command.msg, toString(sData), [hex(no).upper() for no in self.pmExpectedResponse])) + log.debug(f"[sendPdu] Sent Command ({command.msg}) raw data {toString(sData)} waiting for message response {[hex(no).upper() for no in self.pmExpectedResponse]}") elif command is not None and command.debugprint == DebugLevel.CMD: - log.debug("[sendPdu] Sent Command ({0}) waiting for message response {1}".format(command.msg, [hex(no).upper() for no in self.pmExpectedResponse])) + log.debug(f"[sendPdu] Sent Command ({command.msg}) waiting for message response {[hex(no).upper() for no in self.pmExpectedResponse]}") elif instruction.raw is not None: # Assume raw data to send is not obfuscated for now - log.debug("[sendPdu] Sent Raw Command raw data {0} waiting for message response {1}".format(toString(sData[:4] if OBFUS else sData), [hex(no).upper() for no in self.pmExpectedResponse])) + log.debug(f"[sendPdu] Sent Raw Command raw data {toString(sData[:4] if OBFUS else sData)} waiting for message response {[hex(no).upper() for no in self.pmExpectedResponse]}") #elif command is not None: # # Do not log the full raw data as it may contain the user code - # log.debug("[sendPdu] Sent Command ({0}) waiting for message response {1}".format(command.msg, [hex(no).upper() for no in self.pmExpectedResponse])) + # log.debug(f"[sendPdu] Sent Command ({command.msg}) waiting for message response {[hex(no).upper() for no in self.pmExpectedResponse]}") if command is not None and command.waittime > 0.0: return command.waittime @@ -2758,20 +2747,20 @@ def _updateSensorNamesAndTypes(self, force = False) -> bool: retval = None if self.PanelType is not None and 0 <= self.PanelType <= 16: retval = False - zoneCnt = pmPanelConfig_t["CFG_WIRELESS"][self.PanelType] + pmPanelConfig_t["CFG_WIRED"][self.PanelType] + zoneCnt = pmPanelConfig["CFG_WIRELESS"][self.PanelType] + pmPanelConfig["CFG_WIRED"][self.PanelType] if self.isPowerMaster(): if force or len(self.PanelSettings[PanelSetting.ZoneNames]) < zoneCnt: retval = True log.debug("[updateSensorNamesAndTypes] Trying to get the zone names, zone count = " + str(zoneCnt) + " I've only got " + str(len(self.PanelSettings[PanelSetting.ZoneNames])) + " zone names") - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_NAMES"]) + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_NAMES"]) if force or len(self.PanelSettings[PanelSetting.ZoneTypes]) < zoneCnt: retval = True log.debug("[updateSensorNamesAndTypes] Trying to get the zone types, zone count = " + str(zoneCnt) + " I've only got " + str(len(self.PanelSettings[PanelSetting.ZoneTypes])) + " zone types") - self.B0_Message_Wanted.add(pmSendMsgB0_t["ZONE_TYPES"]) + self.B0_Message_Wanted.add(pmSendMsgB0["ZONE_TYPES"]) #if force or len(self.SensorList) == 0: # retval = True # log.debug("[updateSensorNamesAndTypes] Trying to get the sensor status") - # self.B0_Message_Wanted.add(pmSendMsgB0_t["SENSOR_STATUS"]) + # self.B0_Message_Wanted.add(pmSendMsgB0["SENSOR_STATUS"]) else: if force or len(self.PanelSettings[PanelSetting.ZoneNames]) < zoneCnt: retval = True @@ -2793,7 +2782,6 @@ def _validateEPROMSettingsBlock(self, block) -> bool: retlen = settings_len retval = bytearray() - #log.debug("[_readEPROMSettingsPageIndex] Entering Function page {0} index {1} length {2}".format(page, index, settings_len)) while page in self.pmRawSettings and retlen > 0: rawset = self.pmRawSettings[page][index : index + retlen] retval = retval + rawset @@ -2873,7 +2861,7 @@ def _saveEPROMSettings(self, page, index, setting): wrap = index + settings_len - 0x100 sett = [bytearray(b""), bytearray(b"")] - #log.debug("[Write Settings] Entering Function page {0} index {1} length {2}".format(page, index, settings_len)) + #log.debug(f"[Write Settings] Entering Function page {page} index {index} length {settings_len}") if settings_len > 0xB1: log.debug("[Write Settings] ********************* Write Settings too long ********************") return @@ -2882,7 +2870,7 @@ def _saveEPROMSettings(self, page, index, setting): # log.debug("[Write Settings] The write settings data is Split across 2 pages") sett[0] = setting[: settings_len - wrap] # bug fix in 0.0.6, removed the -1 sett[1] = setting[settings_len - wrap :] - # log.debug("[Write Settings] Wrapping original len {0} left len {1} right len {2}".format(len(setting), len(sett[0]), len(sett[1]))) + # log.debug(f"[Write Settings] Wrapping original len {len(setting)} left len {len(sett[0])} right len {len(sett[1])}") wrap = 1 else: sett[0] = setting @@ -2894,19 +2882,19 @@ def _saveEPROMSettings(self, page, index, setting): for dummy in range(0, 256): self.pmRawSettings[page + i].append(255) if len(self.pmRawSettings[page + i]) != 256: - log.debug("[Write Settings] the EEPROM settings is incorrect for page {0}".format(page + i)) + log.debug(f"[Write Settings] the EEPROM settings is incorrect for page {page + i}") # else: # log.debug("[Write Settings] WHOOOPEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE") settings_len = len(sett[i]) if i == 1: index = 0 - #log.debug("[Write Settings] Writing settings page {0} index {1} length {2}".format(page+i, index, settings_len)) + #log.debug(f"[Write Settings] Writing settings page {page+i} index {index} length {settings_len}") self.pmRawSettings[page + i] = self.pmRawSettings[page + i][0:index] + sett[i] + self.pmRawSettings[page + i][index + settings_len :] #if len(self.pmRawSettings[page + i]) != 256: - # log.debug("[Write Settings] OOOOOOOOOOOOOOOOOOOO len = {0}".format(len(self.pmRawSettings[page + i]))) + # log.debug(f"[Write Settings] OOOOOOOOOOOOOOOOOOOO len = {len(self.pmRawSettings[page + i])}") # else: - # log.debug("[Write Settings] Page {0} is now {1}".format(page+i, toString(self.pmRawSettings[page + i]))) + # log.debug(f"[Write Settings] Page {page+i} is now {toString(self.pmRawSettings[page + i])}") # _readEPROMSettingsPageIndex # This function retrieves the downloaded status and EEPROM data @@ -2918,7 +2906,7 @@ def _readEPROMSettingsPageIndex(self, page, index, settings_len): index = index - 256 if self.pmDownloadComplete: - #log.debug("[_readEPROMSettingsPageIndex] Entering Function page {0} index {1} length {2}".format(page, index, settings_len)) + #log.debug(f"[_readEPROMSettingsPageIndex] Entering Function page {page} index {index} length {settings_len}") while page in self.pmRawSettings and retlen > 0: rawset = self.pmRawSettings[page][index : index + retlen] retval = retval + rawset @@ -2926,9 +2914,8 @@ def _readEPROMSettingsPageIndex(self, page, index, settings_len): retlen = retlen - len(rawset) index = 0 if settings_len == len(retval): - #log.debug("[_readEPROMSettingsPageIndex] Length " + str(settings_len) + " returning (just the 1st value) " + toString(retval[:1])) return retval - log.debug("[_readEPROMSettingsPageIndex] Sorry but you havent downloaded that part of the EEPROM data page={0} index={1} length={2}".format(hex(page), hex(index), settings_len)) + log.debug(f"[_readEPROMSettingsPageIndex] Sorry but you havent downloaded that part of the EEPROM data page={hex(page)} index={hex(index)} length={settings_len}") # return a bytearray filled with 0xFF values retval = bytearray() @@ -2952,7 +2939,7 @@ def _dumpEPROMSettings(self): if EEPROM_DOWNLOAD_ALL or ((p != 1 or j != 240) and (p != 2 or j != 0) and (p != 10 or j <= 140)): if j <= len(self.pmRawSettings[p]): s = toString(self.pmRawSettings[p][j : j + 0x10]) - log.debug("{0:3}:{1:3} {2}".format(p, j, s)) + log.debug(f"{p:3}:{j:3} {s}") def _calcBoolFromIntMask(self, val, mask) -> bool: return True if val & mask != 0 else False @@ -3027,20 +3014,20 @@ def _lookupEprom(self, val: SettingsCommand): return retval def _lookupEpromSingle(self, key): - v = self._lookupEprom(DecodePanelSettings[key]) + v = self._lookupEprom(pmDecodePanelSettings[key]) if len(v) >= 1: return v[0] return None def mySensorChangeHandler(self, sensor : SensorDevice, s : AlSensorCondition): log.debug("=============================================================== Sensor Change ===========================================================================") - log.debug(" {0:<18} {1:<11} Sensor {2}".format(self.PanelMode.name, str(s), sensor)) + log.debug(f" {self.PanelMode.name:<18} {str(s):<11} Sensor {sensor}") #log.debug("=========================================================================================================================================================") #self._dumpSensorsToLogFile() def mySwitchChangeHandler(self, switch : X10Device): log.debug("=============================================================== Switch Change ===========================================================================") - log.debug(" {0:<18} X10 {1}".format(self.PanelMode.name, switch)) + log.debug(f" {self.PanelMode.name:<18} X10 {switch}") #log.debug("=========================================================================================================================================================") #self._dumpSensorsToLogFile(True) @@ -3056,7 +3043,7 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None zoneName = "unknown" if i < len(self.PanelSettings[PanelSetting.ZoneNames]): # - zoneName = pmZoneName_t[self.PanelSettings[PanelSetting.ZoneNames][i] & 0x1F] + zoneName = pmZoneName[self.PanelSettings[PanelSetting.ZoneNames][i] & 0x1F] if zoneName is not None and self.SensorList[i].zname != zoneName: updated = True @@ -3067,9 +3054,9 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None sensorModel = "Model Unknown" if self.isPowerMaster(): # PowerMaster models - if sensor_type in pmZoneSensorMaster_t: - sensorType = pmZoneSensorMaster_t[sensor_type].func - sensorModel = pmZoneSensorMaster_t[sensor_type].name + if sensor_type in pmZoneSensorMaster: + sensorType = pmZoneSensorMaster[sensor_type].func + sensorModel = pmZoneSensorMaster[sensor_type].name if motiondelaytime is not None and motiondelaytime == 0xFFFF and (sensorType == AlSensorType.MOTION or sensorType == AlSensorType.CAMERA): log.debug(f"[_updateSensor] PowerMaster Sensor Z{i+1:0>2} has no motion delay set (Sensor will only be useful when the panel is armed)") else: @@ -3092,9 +3079,9 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None # [handle_msgtype3C] PanelType=4 : PowerMax Pro Part , Model=81 Powermaster False # Sensor Types 0x96 0xC0 and 0xE5 I hope that E5 is a Motion as that is what it has been previously - if sensor_type in pmZoneSensorMax_t: - sensorType = pmZoneSensorMax_t[sensor_type].func - sensorModel = pmZoneSensorMax_t[sensor_type].name + if sensor_type in pmZoneSensorMax: + sensorType = pmZoneSensorMax[sensor_type].func + sensorModel = pmZoneSensorMax[sensor_type].name elif tmpid in pmZoneSensorMaxGeneric_t: # if tmpid in pmZoneSensorMaxGeneric_t: sensorType = pmZoneSensorMaxGeneric_t[tmpid] @@ -3114,8 +3101,11 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None if self.SensorList[i].zchime != pmZoneChimeKey[zoneChime]: updated = True self.SensorList[i].zchimeref = zoneChime - self.SensorList[i].zchime = pmZoneChimeKey[zoneChime] - + if zoneChime < len(pmZoneChimeKey): + self.SensorList[i].zchime = pmZoneChimeKey[zoneChime] + else: + self.SensorList[i].zchime = "undefined " + str(zoneChime) + if zoneType is not None: self.PanelSettings[PanelSetting.ZoneTypes][i] = zoneType elif i < len(self.PanelSettings[PanelSetting.ZoneTypes]): # @@ -3126,7 +3116,10 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None if zoneType is not None and self.SensorList[i].ztype != zoneType: updated = True self.SensorList[i].ztype = zoneType - self.SensorList[i].ztypeName = pmZoneTypeKey[zoneType] + if zoneType < len(pmZoneTypeKey): + self.SensorList[i].ztypeName = pmZoneTypeKey[zoneType] + else: + self.SensorList[i].ztypeName = "undefined " + str(zoneType) # undefined if motiondelaytime is not None and motiondelaytime != 0xFFFF: if self.SensorList[i].motiondelaytime != motiondelaytime: @@ -3157,14 +3150,12 @@ def _updateSensor(self, i, zoneType = None, zoneChime = None, sensor_type = None else: self.SensorList[i].pushChange(AlSensorCondition.RESET) -# log.debug("[_updateSensor] Z{0:0>2} : sensor_type={1:0>2} zoneInfo={2:0>2} ZTypeName={3} Chime={4} SensorType={5} zoneName={6}".format( -# i+1, hex(sensor_type).upper(), hex(zoneInfo).upper(), pmZoneTypeKey[zoneType], pmZoneChimeKey[zoneChime], sensorType, zoneName)) return retval def _processKeypadsAndSirens(self, pmPanelTypeNr) -> str: - sirenCnt = pmPanelConfig_t["CFG_SIRENS"][pmPanelTypeNr] - keypad1wCnt = pmPanelConfig_t["CFG_1WKEYPADS"][pmPanelTypeNr] - keypad2wCnt = pmPanelConfig_t["CFG_2WKEYPADS"][pmPanelTypeNr] + sirenCnt = pmPanelConfig["CFG_SIRENS"][pmPanelTypeNr] + keypad1wCnt = pmPanelConfig["CFG_1WKEYPADS"][pmPanelTypeNr] + keypad2wCnt = pmPanelConfig["CFG_2WKEYPADS"][pmPanelTypeNr] # ------------------------------------------------------------------------------------------------------------------------------------------------ # Process Devices (Sirens and Keypads) @@ -3172,38 +3163,38 @@ def _processKeypadsAndSirens(self, pmPanelTypeNr) -> str: deviceStr = "" if self.isPowerMaster(): # PowerMaster models # Process keypad settings - setting = self._lookupEprom(DecodePanelSettings["KeypadPMaster"]) + setting = self._lookupEprom(pmDecodePanelSettings["KeypadPMaster"]) for i in range(0, min(len(setting), keypad2wCnt)): if setting[i][0] != 0 or setting[i][1] != 0 or setting[i][2] != 0 or setting[i][3] != 0 or setting[i][4] != 0: - log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMaster keypad {0}".format(i)) - deviceStr = "{0},K2{1:0>2}".format(deviceStr, i) + log.debug(f"[_processKeypadsAndSirens] Found an enrolled PowerMaster keypad {i}") + deviceStr = f"{deviceStr},K2{i:0>2}" # Process siren settings - setting = self._lookupEprom(DecodePanelSettings["SirensPMaster"]) + setting = self._lookupEprom(pmDecodePanelSettings["SirensPMaster"]) for i in range(0, min(len(setting), sirenCnt)): if setting[i][0] != 0 or setting[i][1] != 0 or setting[i][2] != 0 or setting[i][3] != 0 or setting[i][4] != 0: - log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMaster siren {0}".format(i)) - deviceStr = "{0},S{1:0>2}".format(deviceStr, i) + log.debug(f"[_processKeypadsAndSirens] Found an enrolled PowerMaster siren {i}") + deviceStr = f"{deviceStr},S{i:0>2}" else: # Process keypad settings - setting = self._lookupEprom(DecodePanelSettings["Keypad1PMax"]) + setting = self._lookupEprom(pmDecodePanelSettings["Keypad1PMax"]) for i in range(0, min(len(setting), keypad1wCnt)): if setting[i][0] != 0 or setting[i][1] != 0: - log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMax 1-way keypad {0}".format(i)) - deviceStr = "{0},K1{1:0>2}".format(deviceStr, i) + log.debug(f"[_processKeypadsAndSirens] Found an enrolled PowerMax 1-way keypad {i}") + deviceStr = f"{deviceStr},K1{i:0>2}" - setting = self._lookupEprom(DecodePanelSettings["Keypad2PMax"]) + setting = self._lookupEprom(pmDecodePanelSettings["Keypad2PMax"]) for i in range(0, min(len(setting), keypad2wCnt)): if setting[i][0] != 0 or setting[i][1] != 0 or setting[i][2] != 0: - log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMax 2-way keypad {0}".format(i)) - deviceStr = "{0},K2{1:0>2}".format(deviceStr, i) + log.debug(f"[_processKeypadsAndSirens] Found an enrolled PowerMax 2-way keypad {i}") + deviceStr = f"{deviceStr},K2{i:0>2}" # Process siren settings - setting = self._lookupEprom(DecodePanelSettings["SirensPMax"]) + setting = self._lookupEprom(pmDecodePanelSettings["SirensPMax"]) for i in range(0, min(len(setting), sirenCnt)): if setting[i][0] != 0 or setting[i][1] != 0 or setting[i][2] != 0: - log.debug("[_processKeypadsAndSirens] Found a PowerMax siren {0}".format(i)) - deviceStr = "{0},S{1:0>2}".format(deviceStr, i) + log.debug(f"[_processKeypadsAndSirens] Found a PowerMax siren {i}") + deviceStr = f"{deviceStr},S{i:0>2}" return deviceStr[1:] @@ -3212,8 +3203,8 @@ def processEEPROMData(self, addToLog): # Add the "True" values to the self.Panelstatus # If val.show is True and addToLog is True then: # Add all (either PowerMax / PowerMaster) values to the self.Panelstatus and the log file - for key in DecodePanelSettings: - val = DecodePanelSettings[key] + for key in pmDecodePanelSettings: + val = pmDecodePanelSettings[key] if val.show: result = self._lookupEprom(val) if result is not None: @@ -3221,11 +3212,11 @@ def processEEPROMData(self, addToLog): if isinstance(result[0], (bytes, bytearray)): tmpdata = toString(result[0]) if addToLog: - log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, val.name, tmpdata)) + log.debug( f"[Process Settings] {key:<18} {val.name:<40} {tmpdata}") self.PanelStatus[val.name] = tmpdata else: if addToLog: - log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, val.name, result[0])) + log.debug( f"[Process Settings] {key:<18} {val.name:<40} {result[0]}") self.PanelStatus[val.name] = result[0] elif type(val.name) is list and len(result) == len(val.name): @@ -3233,11 +3224,11 @@ def processEEPROMData(self, addToLog): if isinstance(result[0], (bytes, bytearray)): tmpdata = toString(result[i]) if addToLog: - log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, val.name[i], tmpdata)) + log.debug( f"[Process Settings] {key:<18} {val.name[i]:<40} {tmpdata}") self.PanelStatus[val.name[i]] = tmpdata else: if addToLog: - log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, val.name[i], result[i])) + log.debug( f"[Process Settings] {key:<18} {val.name[i]:<40} {result[i]}") self.PanelStatus[val.name[i]] = result[i] elif len(result) > 1 and type(val.name) is str: @@ -3250,24 +3241,24 @@ def processEEPROMData(self, addToLog): # there's at least 2 so this will not exception tmpdata = tmpdata[:-2] if addToLog: - log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, val.name, tmpdata)) + log.debug( f"[Process Settings] {key:<18} {val.name:<40} {tmpdata}") self.PanelStatus[val.name] = tmpdata else: - log.debug( "[Process Settings] ************************** NOTHING DONE ************************ {0:<18} {1} {2}".format(key, val.name, result)) + log.debug( f"[Process Settings] ************************** NOTHING DONE ************************ {key:<18} {val.name} {result}") def _setDataFromPanelType(self, p) -> bool: - if p in pmPanelType_t: + if p in pmPanelType: self.PanelType = p - if 0 <= self.PanelType <= len(pmPanelConfig_t["CFG_SUPPORTED"]) - 1: - isSupported = pmPanelConfig_t["CFG_SUPPORTED"][self.PanelType] + if 0 <= self.PanelType <= len(pmPanelConfig["CFG_SUPPORTED"]) - 1: + isSupported = pmPanelConfig["CFG_SUPPORTED"][self.PanelType] if isSupported: - self.PanelModel = pmPanelType_t[self.PanelType] if self.PanelType in pmPanelType_t else "UNKNOWN" # INTERFACE : PanelType set to model - self.PowerMaster = pmPanelConfig_t["CFG_POWERMASTER"][self.PanelType] - self.AutoEnroll = pmPanelConfig_t["CFG_AUTO_ENROLL"][self.PanelType] - self.AutoSyncTime = pmPanelConfig_t["CFG_AUTO_SYNCTIME"][self.PanelType] - self.KeepAlivePeriod = pmPanelConfig_t["CFG_KEEPALIVE"][self.PanelType] - self.pmInitSupportedByPanel = pmPanelConfig_t["CFG_INIT_SUPPORT"][self.PanelType] + self.PanelModel = pmPanelType[self.PanelType] if self.PanelType in pmPanelType else "UNKNOWN" # INTERFACE : PanelType set to model + self.PowerMaster = pmPanelConfig["CFG_POWERMASTER"][self.PanelType] + self.AutoEnroll = pmPanelConfig["CFG_AUTO_ENROLL"][self.PanelType] + self.AutoSyncTime = pmPanelConfig["CFG_AUTO_SYNCTIME"][self.PanelType] + self.KeepAlivePeriod = pmPanelConfig["CFG_KEEPALIVE"][self.PanelType] + self.pmInitSupportedByPanel = pmPanelConfig["CFG_INIT_SUPPORT"][self.PanelType] return True # Panel 0 i.e original PowerMax log.error(f"Lookup of Visonic Panel type reveals that this seems to be a PowerMax Panel and supports EEPROM Download only with no capability, this Panel cannot be used with this Integration") @@ -3295,17 +3286,16 @@ def _processEPROMSettings(self): # This checks whether the EEPROM settings have been downloaded OK #pmDisplayName = self._lookupEpromSingle("displayName") - #log.debug("[Process Settings] old Panel Type: {0} Model: {1} Panels Displayed Name: {2}".format(self.PanelType, self.PanelModel, pmDisplayName)) # ------------------------------------------------------------------------------------------------------------------------------------------------ # Need the panel type to be valid so we can decode some of the remaining downloaded data correctly - # when we get here then self.PanelType is set and it's a known panel type i.e. if self.PanelType is not None and self.PanelType in pmPanelType_t is TRUE + # when we get here then self.PanelType is set and it's a known panel type i.e. if self.PanelType is not None and self.PanelType in pmPanelType is TRUE # ------------------------------------------------------------------------------------------------------------------------------------------------ # self._dumpEPROMSettings() - # log.debug("[Process Settings] Panel Type Number " + str(self.PanelType) + " serial string " + toString(panelSerialType)) - zoneCnt = pmPanelConfig_t["CFG_WIRELESS"][self.PanelType] + pmPanelConfig_t["CFG_WIRED"][self.PanelType] + #log.debug(f"[Process Settings] Panel Type Number {str(self.PanelType)} serial string {toString(panelSerialType)}") + zoneCnt = pmPanelConfig["CFG_WIRELESS"][self.PanelType] + pmPanelConfig["CFG_WIRED"][self.PanelType] # ------------------------------------------------------------------------------------------------------------------------------------------------ # Process Panel Status to display in the user interface @@ -3315,23 +3305,23 @@ def _processEPROMSettings(self): # Process Panel Settings to use as a common panel settings regardless of how they were obtained. This way gets them from EPROM. if self.isPowerMaster(): # PowerMaster models for key in PanelSetting: - if key in PanelSettingCodes and PanelSettingCodes[key].PowerMasterEPROM is not None and len(PanelSettingCodes[key].PowerMasterEPROM) > 0: - if PanelSettingCodes[key].item is not None: - self.PanelSettings[key] = self._lookupEprom(DecodePanelSettings[PanelSettingCodes[key].PowerMasterEPROM])[PanelSettingCodes[key].item] + if key in pmPanelSettingCodes and pmPanelSettingCodes[key].PowerMasterEPROM is not None and len(pmPanelSettingCodes[key].PowerMasterEPROM) > 0: + if pmPanelSettingCodes[key].item is not None: + self.PanelSettings[key] = self._lookupEprom(pmDecodePanelSettings[pmPanelSettingCodes[key].PowerMasterEPROM])[pmPanelSettingCodes[key].item] else: - self.PanelSettings[key] = self._lookupEprom(DecodePanelSettings[PanelSettingCodes[key].PowerMasterEPROM]) + self.PanelSettings[key] = self._lookupEprom(pmDecodePanelSettings[pmPanelSettingCodes[key].PowerMasterEPROM]) else: for key in PanelSetting: - if key in PanelSettingCodes and PanelSettingCodes[key].PowerMaxEPROM is not None and len(PanelSettingCodes[key].PowerMaxEPROM) > 0: - if PanelSettingCodes[key].item is not None: - self.PanelSettings[key] = self._lookupEprom(DecodePanelSettings[PanelSettingCodes[key].PowerMaxEPROM])[PanelSettingCodes[key].item] # [PanelSettingCodes[key].item] + if key in pmPanelSettingCodes and pmPanelSettingCodes[key].PowerMaxEPROM is not None and len(pmPanelSettingCodes[key].PowerMaxEPROM) > 0: + if pmPanelSettingCodes[key].item is not None: + self.PanelSettings[key] = self._lookupEprom(pmDecodePanelSettings[pmPanelSettingCodes[key].PowerMaxEPROM])[pmPanelSettingCodes[key].item] # [pmPanelSettingCodes[key].item] else: - self.PanelSettings[key] = self._lookupEprom(DecodePanelSettings[PanelSettingCodes[key].PowerMaxEPROM]) + self.PanelSettings[key] = self._lookupEprom(pmDecodePanelSettings[pmPanelSettingCodes[key].PowerMaxEPROM]) log.debug(f"[Process Settings] UpdatePanelSettings") # ------------------------------------------------------------------------------------------------------------------------------------------------ # Store partition info & check if partitions are on - partitionCnt = pmPanelConfig_t["CFG_PARTITIONS"][self.PanelType] + partitionCnt = pmPanelConfig["CFG_PARTITIONS"][self.PanelType] partition = None if partitionCnt > 1: # Could the panel have more than 1 partition? # If that panel type can have more than 1 partition, then check to see if the panel has defined more than 1 @@ -3343,10 +3333,10 @@ def _processEPROMSettings(self): # ------------------------------------------------------------------------------------------------------------------------------------------------ # Process panel type and serial pmPanelTypeCodeStr = self.PanelSettings[PanelSetting.PanelModel] # self._lookupEpromSingle("panelModelCode") - idx = "{0:0>2}{1:0>2}".format(hex(self.PanelType).upper()[2:], hex(int(pmPanelTypeCodeStr)).upper()[2:]) + idx = f"{hex(self.PanelType).upper()[2:]:0>2}{hex(int(pmPanelTypeCodeStr)).upper()[2:]:0>2}" #pmPanelName = pmPanelName_t[idx] if idx in pmPanelName_t else "Unknown_" + idx - #log.debug("[Process Settings] Processing settings - panel code index {0}".format(idx)) + #log.debug(f"[Process Settings] Processing settings - panel code index {idx}") # INTERFACE : Add this param to the status panel first #self.PanelStatus["Panel Name"] = pmPanelName @@ -3377,9 +3367,9 @@ def _processEPROMSettings(self): # This is 640 bytes, PowerMaster only. # It is 64 zones, each is 10 bytes # 5 = Sensor Type - pmaster_zone_ext_data = self.PanelSettings[PanelSetting.ZoneExt] # self._lookupEprom(DecodePanelSettings["ZoneExtPMaster"]) + pmaster_zone_ext_data = self.PanelSettings[PanelSetting.ZoneExt] # self._lookupEprom(pmDecodePanelSettings["ZoneExtPMaster"]) - log.debug("[Process Settings] Zones Data Buffer len settings {0} len zoneNames {1} zoneCnt {2}".format(len(zone_data), len(self.PanelSettings[PanelSetting.ZoneNames]), zoneCnt)) + log.debug(f"[Process Settings] Zones Data Buffer len settings {len(zone_data)} len zoneNames {len(self.PanelSettings[PanelSetting.ZoneNames])} zoneCnt {zoneCnt}") log.debug(f"[Process Settings] Zones Names Buffer : {toString(self.PanelSettings[PanelSetting.ZoneNames])}") #log.debug(f"[Process Settings] Zones Data Buffer : {zone_data}") @@ -3425,16 +3415,16 @@ def _processEPROMSettings(self): if sensorType is not None and i in self.SensorList: if sensorType == AlSensorType.MAGNET or sensorType == AlSensorType.WIRED: - doorZoneStr = "{0},Z{1:0>2}".format(doorZoneStr, i + 1) + doorZoneStr = f"{doorZoneStr},Z{i+1:0>2}" elif sensorType == AlSensorType.MOTION or sensorType == AlSensorType.CAMERA: - motionZoneStr = "{0},Z{1:0>2}".format(motionZoneStr, i + 1) + motionZoneStr = f"{motionZoneStr},Z{i+1:0>2}" elif sensorType == AlSensorType.SMOKE or sensorType == AlSensorType.GAS: - smokeZoneStr = "{0},Z{1:0>2}".format(smokeZoneStr, i + 1) + smokeZoneStr = f"{smokeZoneStr},Z{i+1:0>2}" else: - otherZoneStr = "{0},Z{1:0>2}".format(otherZoneStr, i + 1) + otherZoneStr = f"{otherZoneStr},Z{i+1:0>2}" elif i in self.SensorList: - log.debug("[Process Settings] Removing sensor {0} as it is not enrolled in Panel EEPROM Data".format(i+1)) + log.debug(f"[Process Settings] Removing sensor {i+1} as it is not enrolled in Panel EEPROM Data") del self.SensorList[i] # self.SensorList[i] = None # remove zone if needed @@ -3450,17 +3440,17 @@ def _processEPROMSettings(self): log.debug("[Process Settings] Processing X10 devices") s = [] - s.append(self._lookupEprom(DecodePanelSettings["x10ByArmAway"])) # 0 = pgm, 1 = X01 - s.append(self._lookupEprom(DecodePanelSettings["x10ByArmHome"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ByDisarm"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ByDelay"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ByMemory"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ByKeyfob"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ActZoneA"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ActZoneB"])) - s.append(self._lookupEprom(DecodePanelSettings["x10ActZoneC"])) - - x10Names = self._lookupEprom(DecodePanelSettings["x10ZoneNames"]) # 0 = X01 + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByArmAway"])) # 0 = pgm, 1 = X01 + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByArmHome"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByDisarm"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByDelay"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByMemory"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ByKeyfob"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ActZoneA"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ActZoneB"])) + s.append(self._lookupEprom(pmDecodePanelSettings["x10ActZoneC"])) + + x10Names = self._lookupEprom(pmDecodePanelSettings["x10ZoneNames"]) # 0 = X01 log.debug(f"[Process Settings] X10 device EPROM Name Data {toString(x10Names)}") for i in range(0, 16): @@ -3471,7 +3461,7 @@ def _processEPROMSettings(self): x10Name = (x10Names[i - 1] & 0x1F) if i > 0 else 0x1F # PGM needs to be set by x10Enabled if x10Enabled or x10Name != 0x1F: - x10Location = pmZoneName_t[x10Name] if i > 0 else "PGM" + x10Location = pmZoneName[x10Name] if i > 0 else "PGM" x10Type = "onoff" if i == 0 else "dimmer" # Assume PGM is onoff switch, all other devices are dimmer Switches if i in self.SwitchList: self.SwitchList[i].type = x10Type @@ -3510,15 +3500,15 @@ def ProcessPanelStateUpdate(self, sysStatus, sysFlags): sysStatus = sysStatus & 0x1F # Mark-Mills with a PowerMax Complete Part, sometimes this has the 0x20 bit set and I'm not sure why oldPS = self.PanelState - if sysStatus in PanelArmedStatus: - disarmed = PanelArmedStatus[sysStatus].disarmed - armed = PanelArmedStatus[sysStatus].armed - entry = PanelArmedStatus[sysStatus].entry - self.PanelState = PanelArmedStatus[sysStatus].state - - if PanelArmedStatus[sysStatus].eventmapping >= 0: - log.debug(f"[ProcessPanelStateUpdate] self.PanelState is {self.PanelState} using event mapping {PanelArmedStatus[sysStatus].eventmapping} for event data") - self.addPanelEventData(AlPanelEventData(0, PanelArmedStatus[sysStatus].eventmapping)) + if sysStatus in pmPanelArmedStatus: + disarmed = pmPanelArmedStatus[sysStatus].disarmed + armed = pmPanelArmedStatus[sysStatus].armed + entry = pmPanelArmedStatus[sysStatus].entry + self.PanelState = pmPanelArmedStatus[sysStatus].state + + if pmPanelArmedStatus[sysStatus].eventmapping >= 0: + log.debug(f"[ProcessPanelStateUpdate] self.PanelState is {self.PanelState} using event mapping {pmPanelArmedStatus[sysStatus].eventmapping} for event data") + self.addPanelEventData(AlPanelEventData(0, pmPanelArmedStatus[sysStatus].eventmapping)) else: log.debug("[ProcessPanelStateUpdate] Unknown state {0}, assuming Panel state of Unknown".format(hexify(sysStatus))) @@ -3583,19 +3573,19 @@ def ProcessPanelStateUpdate(self, sysStatus, sysFlags): def ProcessZoneEvent(self, eventZone, eventType): - log.debug("[ProcessZoneEvent] Zone Event Zone: {0} Type: {1}".format(eventZone, eventType)) + log.debug(f"[ProcessZoneEvent] Zone Event Zone: {eventZone} Type: {eventType}") key = eventZone - 1 # get the key from the zone - 1 if self.PanelMode in [AlPanelMode.STANDARD, AlPanelMode.MINIMAL_ONLY, AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.POWERLINK] and key not in self.SensorList and eventType > 0: log.debug("[ProcessZoneEvent] Got a Zone Sensor that I did not know about so creating it") self._updateSensor(i = key, enrolled = True) - if key in self.SensorList and eventType in ZoneEventAction: - sf = getattr(self.SensorList[key], ZoneEventAction[eventType].func if eventType in ZoneEventAction else "") + if key in self.SensorList and eventType in pmZoneEventAction: + sf = getattr(self.SensorList[key], pmZoneEventAction[eventType].func if eventType in pmZoneEventAction else "") if sf is not None: - log.debug(f"[ProcessZoneEvent] Processing event {eventType} calling {ZoneEventAction[eventType].func}({str(ZoneEventAction[eventType].parameter)})") - sf(ZoneEventAction[eventType].parameter) - self.SensorList[key].setProblem(ZoneEventAction[eventType].problem) + log.debug(f"[ProcessZoneEvent] Processing event {eventType} calling {pmZoneEventAction[eventType].func}({str(pmZoneEventAction[eventType].parameter)})") + sf(pmZoneEventAction[eventType].parameter) + self.SensorList[key].setProblem(pmZoneEventAction[eventType].problem) else: log.debug(f"[ProcessZoneEvent] Not processing zone {eventZone} event {eventType}") @@ -3610,7 +3600,7 @@ def ProcessX10StateUpdate(self, x10status, total = 16): self.SwitchList[i].state = bool(status) # Check to see if the state has changed if (oldstate and not self.SwitchList[i].state) or (not oldstate and self.SwitchList[i].state): - log.debug("[handle_msgtypeA5] X10 device {0} changed to {2} ({1})".format(i, status, self.SwitchList[i].state)) + log.debug(f"[handle_msgtypeA5] X10 device {i} changed to {self.SwitchList[i].state} ({status})") self.SwitchList[i].pushChange() @@ -3726,7 +3716,7 @@ def handle_msgtype02(self, data): # ACK """ Handle Acknowledges from the panel """ # Normal acknowledges have msgtype 0x02 but no data, when in powerlink the panel also sends data byte 0x43 # I have not found this on the internet, this is my hypothesis - #log.debug("[handle_msgtype02] Ack Received data = {0}".format(toString(data))) + #log.debug(f"[handle_msgtype02] Ack Received data = {toString(data)}") processAB = not self.pmDownloadMode and self.PanelMode in [AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK] if processAB and len(data) > 0 and data[0] == 0x43: @@ -3744,24 +3734,24 @@ def handle_msgtype06(self, data): def handle_msgtype07(self, data): """MsgType=07 - No idea what this means""" - log.debug("[handle_msgtype07] No idea what this message means, data = {0}".format(toString(data))) + log.debug(f"[handle_msgtype07] No idea what this message means, data = {toString(data)}") # Assume that we need to send an ack def handle_msgtype08(self, data): - log.debug("[handle_msgtype08] Access Denied len {0} data {1}".format(len(data), toString(data))) + log.debug(f"[handle_msgtype08] Access Denied len {len(data)} data {toString(data)}") self.AccessDeniedReceived = True self.AccessDeniedMessage = self._getLastSentMessage() def handle_msgtype0B(self, data): # LOOPBACK TEST SUCCESS, STOP COMMAND (0x0B) IS THE FIRST COMMAND SENT TO THE PANEL WHEN THIS INTEGRATION STARTS """ Handle LOOPBACK """ - #log.debug("[handle_msgtype0B] Loopback test assumed {0}".format(toString(data))) + #log.debug(f"[handle_msgtype0B] Loopback test assumed {toString(data)}") self.loopbackTest = True self.loopbackCounter = self.loopbackCounter + 1 - log.warning("[handle_msgtype0B] LOOPBACK TEST SUCCESS, Counter is {0}".format(self.loopbackCounter)) + log.warning(f"[handle_msgtype0B] LOOPBACK TEST SUCCESS, Counter is {self.loopbackCounter}") def handle_msgtype0F(self, data): # EXIT """ Handle EXIT from the panel """ - log.debug("[handle_msgtype0F] Exit data is {0}".format(toString(data))) + log.debug(f"[handle_msgtype0F] Exit data is {toString(data)}") # This is sent by the panel during download to tell us to stop the download self.ExitReceived = True @@ -3769,7 +3759,7 @@ def handle_msgtype25(self, data): # Download retry """ MsgType=25 - Download retry. Unit is not ready to enter download mode """ # Format: iDelay = data[2] - log.debug("[handle_msgtype25] Download Retry, have to wait {0} seconds data is {1}".format(iDelay, toString(data))) + log.debug(f"[handle_msgtype25] Download Retry, have to wait {iDelay} seconds data is {toString(data)}") self.DownloadRetryReceived = True def handle_msgtype33(self, data): @@ -3777,8 +3767,8 @@ def handle_msgtype33(self, data): Message sent after a MSG_START. We will store the information in an internal array/collection""" if len(data) != 10: - log.debug("[handle_msgtype33] ERROR: MSGTYPE=0x33 Expected len=14, Received={0}".format(len(data))) - log.debug("[handle_msgtype33] " + toString(data)) + log.debug(f"[handle_msgtype33] ERROR: MSGTYPE=0x33 Expected len=14, Received={len(data)}") + log.debug(f"[handle_msgtype33] {toString(data)}") return # Data Format is: <8 data bytes> @@ -3802,8 +3792,8 @@ def handle_msgtype3C(self, data): # Panel Info Messsage when start the download if self.DownloadCode == DEFAULT_DL_CODE: # If the panel still has its startup default Download Code, or if it hasn't been set by the user to something different - self.DownloadCode = pmPanelConfig_t["CFG_DLCODE_1"][self.PanelType][:2] + " " + pmPanelConfig_t["CFG_DLCODE_1"][self.PanelType][2:] - log.debug("[handle_msgtype3C] Setting Download Code from the Default value {0} to the default Panel Value {1}".format(DEFAULT_DL_CODE, self.DownloadCode)) + self.DownloadCode = pmPanelConfig["CFG_DLCODE_1"][self.PanelType][:2] + " " + pmPanelConfig["CFG_DLCODE_1"][self.PanelType][2:] + log.debug(f"[handle_msgtype3C] Setting Download Code from the Default value {DEFAULT_DL_CODE} to the default Panel Value {self.DownloadCode}") else: log.debug("[handle_msgtype3C] Using the user defined Download Code") @@ -3842,8 +3832,8 @@ def findLength(page, index) -> int | None: # PowerMaster 10 (Model 7) and PowerMaster 33 (Model 10) has a very specific problem with downloading the Panel EEPROM and doesn't respond with the correct number of bytes #if self.PanelType is not None and self.ModelType is not None and ((self.PanelType == 7 and self.ModelType == 68) or (self.PanelType == 10 and self.ModelType == 71)): # if iLength != len(data) - 3: - # log.debug("[handle_msgtype3F] Not checking data length as it could be incorrect. We requested {0} and received {1}".format(iLength, len(data) - 3)) - # log.debug("[handle_msgtype3F] " + toString(data)) + # log.debug(f"[handle_msgtype3F] Not checking data length as it could be incorrect. We requested {iLength} and received {len(data) - 3}") + # log.debug(f"[handle_msgtype3F] {toString(data)}") # # Write to memory map structure, but remove the first 3 bytes (index/page/length) from the data # self._saveEPROMSettings(iPage, iIndex, data[3:]) @@ -3869,19 +3859,14 @@ def findLength(page, index) -> int | None: self.pmDownloadInProgress = True self._addMessageToSendList("MSG_DL", options=[ [1, self.myDownloadList.pop(0)] ]) # Read the next block of EEPROM data elif self.pmDownloadRetryCount <= DOWNLOAD_PDU_RETRY_COUNT: - log.warning("[handle_msgtype3F] Invalid EPROM data block length (received: {0}, Expected: {1}, blocklen: {2}). Adding page {2} Index {3} to the end of the list to redownload".format(len(data)-3, iLength, blocklen, iPage, iIndex)) - log.warning("[handle_msgtype3F] " + toString(data)) + log.warning(f"[handle_msgtype3F] Invalid EPROM data block length (received: {len(data)-3}, Expected: {iLength}, blocklen: {blocklen}). Adding page {iPage} Index {iIndex} to the end of the list to redownload") + log.warning(f"[handle_msgtype3F] {toString(data)}") # Add it back on to the end to re-download it - repeatDownloadCommand = bytearray(4) - repeatDownloadCommand[0] = iIndex - repeatDownloadCommand[1] = iPage - repeatDownloadCommand[2] = blocklen - repeatDownloadCommand[3] = 0 - self.myDownloadList.append(repeatDownloadCommand) + self.myDownloadList.append(bytearray([iIndex, iPage, blocklen, 0])) # Increment counter self.pmDownloadRetryCount = self.pmDownloadRetryCount + 1 else: - log.warning("[handle_msgtype3F] Invalid EPROM data block length (received: {0}, Expected: {1}, blocklen: {2}). Giving up on page {2} Index {3}".format(len(data)-3, iLength, blocklen, iPage, iIndex)) + log.warning(f"[handle_msgtype3F] Invalid EPROM data block length (received: {len(data)-3}, Expected: {iLength}, blocklen: {blocklen}). Giving up on page {iPage} Index {iIndex}") self.myDownloadList = [] log.debug("[handle_msgtype3F] Download InComplete") self.pmDownloadInProgress = False @@ -3891,12 +3876,24 @@ def findLength(page, index) -> int | None: def handle_msgtypeA0(self, data): """ MsgType=A0 - Event Log """ - # From my Powermaster30 [handle_MsgTypeA0] Packet = 5f 02 01 64 58 5c 58 d3 41 51 43 + # From my Powermaster30 [handle_MsgTypeA0] Packet = 5f 02 01 64 58 5c 58 d3 41 51 + + # My PowerMax + # To Ct Pt ---- time ---- Zo Ev Time does not have the seconds value + # fb 01 00 00 00 00 00 00 03 00 + # fb 02 01 1c 15 06 0a 18 1f 55 6/10/24 at 21:28:01 Disarmed FOB-01 why are all the seconds 0 or 1 + # fb 03 01 09 12 06 0a 18 1f 52 6/10/24 at 18:09:01 Armed Away FOB-01 + + # From a PM10: + # To Ct Pt -- time --- Y Zo Ev Don't know what Y and data[7] is. It could be the panel state e.g. 0x52 is Armed Away + # fb 02 00 3f 71 02 67 04 01 5c + # fb 03 00 69 3a 01 67 53 00 1c + # fb 04 01 69 3a 01 67 52 61 1b eventNum = data[1] # Check for the first entry, it only contains the number of events if eventNum == 0x01: - log.debug("[handle_msgtypeA0] Eventlog received") + log.debug("[handle_msgtypeA0] Eventlog received") self.eventCount = data[0] - 1 ## the number of messages (including this one) minus 1 else: iSec = data[2] @@ -3928,7 +3925,7 @@ def handle_msgtypeA0(self, data): # Create an event log array self.pmEventLogDictionary[idx] = AlLogPanelEvent() - if pmPanelConfig_t["CFG_PARTITIONS"][self.PanelType] > 1: + if pmPanelConfig["CFG_PARTITIONS"][self.PanelType] > 1: part = 0 for i in range(1, 4): part = (iSec % (2 * i) >= i) and i or part @@ -3954,10 +3951,10 @@ def handle_msgtypeA0(self, data): def handle_msgtypeA3(self, data): """ MsgType=A3 - Zone Names """ - log.debug("[handle_MsgTypeA3] Packet = {0}".format(toString(data))) + log.debug(f"[handle_MsgTypeA3] Packet = {toString(data)}") msgCnt = int(data[0]) offset = 8 * (int(data[1]) - 1) - log.debug(" Message Count is {0} offset={1} self.PanelMode = {2}".format( msgCnt, offset, str(self.PanelMode) )) + log.debug(f" Message Count is {msgCnt} offset={offset} self.PanelMode = {str(self.PanelMode)}") if len(self.PanelSettings[PanelSetting.ZoneNames]) < offset+8: self.PanelSettings[PanelSetting.ZoneNames].extend(bytearray(offset+8-len(self.PanelSettings[PanelSetting.ZoneNames]))) @@ -4072,27 +4069,25 @@ def handle_msgtypeA5(self, data): # Status Message def handle_msgtypeA6(self, data): """ MsgType=A6 - Zone Types """ - log.debug("[handle_MsgTypeA6] Packet = {0}".format(toString(data))) + log.debug(f"[handle_MsgTypeA6] Packet = {toString(data)}") msgCnt = int(data[0]) offset = 8 * (int(data[1]) - 1) - log.debug(" Message Count is {0} offset={1} self.PanelMode={2}".format( msgCnt, offset, str(self.PanelMode) )) + log.debug(f" Message Count is {msgCnt} offset={offset} self.PanelMode={str(self.PanelMode)}") if len(self.PanelSettings[PanelSetting.ZoneTypes]) < offset+8: self.PanelSettings[PanelSetting.ZoneTypes].extend(bytearray(offset+8-len(self.PanelSettings[PanelSetting.ZoneTypes]))) for i in range(0, 8): # Save the Zone Type self.PanelSettings[PanelSetting.ZoneTypes][offset+i] = ((int(data[2+i])) - 0x1E) & 0x0F - log.debug(" Zone type for sensor {0} is {1} : {2}".format( offset+i+1, (int(data[2+i])) - 0x1E, pmZoneTypeKey[self.PanelSettings[PanelSetting.ZoneTypes][offset+i]] )) + log.debug(f" Zone type for sensor {offset+i+1} is {(int(data[2+i])) - 0x1E} : {pmZoneTypeKey[self.PanelSettings[PanelSetting.ZoneTypes][offset+i]]}") if self.PanelMode != AlPanelMode.POWERLINK and self.PanelMode != AlPanelMode.POWERLINK_BRIDGED and (offset+i) in self.SensorList: self._updateSensor(offset+i, zoneType = self.PanelSettings[PanelSetting.ZoneTypes][offset+i]) - # This function may change global variables: - # self.SirenActive - # self.PanelAlarmStatus - # self.PanelTroubleStatus - # self.pmForceArmSetInPanel def handle_msgtypeA7(self, data): """ MsgType=A7 - Panel Status Change """ #log.debug("[handle_msgtypeA7] Panel Status Change " + toString(data)) + # 01 00 27 51 02 ff 00 02 00 00 + # ff 5d 00 2d 00 00 11 0c 00 00 + msgCnt = int(data[0]) # If message count is FF then it looks like the first message is valid so decode it (this is experimental) @@ -4108,9 +4103,9 @@ def handle_msgtypeA7(self, data): zoneCnt = 0 # this means it wont work in the case we're in standard mode and the panel type is not set if self.PanelType is not None: - zoneCnt = pmPanelConfig_t["CFG_WIRELESS"][self.PanelType] + pmPanelConfig_t["CFG_WIRED"][self.PanelType] + zoneCnt = pmPanelConfig["CFG_WIRELESS"][self.PanelType] + pmPanelConfig["CFG_WIRED"][self.PanelType] - # If there are multiple messages in the same A7 message then alarmStatus represents the last "not None" valid message i.e. in pmPanelAlarmType_t + # If there are multiple messages in the same A7 message then alarmStatus represents the last "not None" valid message i.e. in pmPanelAlarmType #oldTroubleStatus = self.PanelTroubleStatus #oldAlarmStatus = self.PanelAlarmStatus @@ -4140,16 +4135,16 @@ def handle_msgtypeA7(self, data): self.PanelTamper = eventType in pmPanelTamperSet # Update trouble status - if eventType in pmPanelTroubleType_t: # Trouble state - self.PanelTroubleStatus = pmPanelTroubleType_t[eventType] + if eventType in pmPanelTroubleType: # Trouble state + self.PanelTroubleStatus = pmPanelTroubleType[eventType] else: self.PanelTroubleStatus = AlTroubleType.NONE # Update siren status siren = False - if eventType in pmPanelAlarmType_t: - self.PanelAlarmStatus = pmPanelAlarmType_t[eventType] + if eventType in pmPanelAlarmType: + self.PanelAlarmStatus = pmPanelAlarmType[eventType] #alarmStatus = self.PanelAlarmStatus if self.PanelAlarmStatus == AlAlarmType.INTRUDER: # If any of the A7 messages are in the SirenTriggerList then assume the Siren is triggered @@ -4189,7 +4184,7 @@ def handle_msgtypeA7(self, data): # pmHandlePowerlink (0xAB) def handle_msgtypeAB(self, data) -> bool: # PowerLink Message """ MsgType=AB - Panel Powerlink Messages """ - log.debug("[handle_msgtypeAB] data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeAB] data {toString(data)}") # Restart the timer self._reset_watchdog_timeout() @@ -4200,11 +4195,11 @@ def handle_msgtypeAB(self, data) -> bool: # PowerLink Message log.debug("[handle_msgtypeAB] ***************************** Got Panel Time ****************************") pt = datetime(2000 + data[7], data[6], data[5], data[4], data[3], data[2]).astimezone() - log.debug("[handle_msgtypeAB] Panel time is {0}".format(pt)) + log.debug(f"[handle_msgtypeAB] Panel time is {pt}") if self.AutoSyncTime: self.setTimeInPanel(pt) - elif self.PanelMode in [AlPanelMode.POWERLINK, AlPanelMode.STANDARD_PLUS] and subType == 3: # keepalive message + elif subType == 3 and self.PanelMode in [AlPanelMode.POWERLINK, AlPanelMode.STANDARD_PLUS]: # keepalive message # Example 0D AB 03 00 1E 00 31 2E 31 35 00 00 43 2A 0A # 03 00 1e 00 33 33 31 34 00 00 43 From a Powermax+ PanelType=1, Model=33 log.debug("[handle_msgtypeAB] ***************************** Got PowerLink Keep-Alive ****************************") @@ -4214,7 +4209,7 @@ def handle_msgtypeAB(self, data) -> bool: # PowerLink Message self._reset_powerlink_counter() # reset when received keep-alive from the panel if self.PanelMode in [AlPanelMode.POWERLINK, AlPanelMode.STANDARD_PLUS]: - self._addMessageToSendList("MSG_ALIVE") # EXPERIMENTAL 29/8/2022. The Powerlink module sends this when it gets an i'm alive from the panel. + self._addMessageToSendList("MSG_ALIVE") # The Powerlink module sends this when it gets an i'm alive from the panel. if self.PanelMode == AlPanelMode.STANDARD_PLUS: log.debug("[handle_msgtypeAB] Got alive message while Powerlink mode pending, going to full powerlink and calling Restore") @@ -4222,12 +4217,6 @@ def handle_msgtypeAB(self, data) -> bool: # PowerLink Message self._triggerRestoreStatus() #self._dumpSensorsToLogFile() - # There is no point in setting the time here as we need to be in DOWNLOAD mode with the panel - # We set the time at the end of download - # Get the time from the panel - #if self.AutoSyncTime: - # self._addMessageToSendList("MSG_GETTIME") - elif subType == 3: # keepalive message log.debug("[handle_msgtypeAB] ***************************** Got PowerLink Keep-Alive ****************************") log.debug("[handle_msgtypeAB] ********************* Panel Mode not Powerlink / Standard Plus **********************") @@ -4246,19 +4235,14 @@ def handle_msgtypeAB(self, data) -> bool: # PowerLink Message # pmMessage("User " .. pmUserCalling .. " acknowledged by phone.", 2) # pmUserCalling = 1 else: - log.debug("[handle_msgtypeAB] PowerLink Phone: Unknown Action {0}".format(hex(data[1]).upper())) + log.debug(f"[handle_msgtypeAB] PowerLink Phone: Unknown Action {hex(data[1]).upper()}") elif self.PanelMode == AlPanelMode.POWERLINK and subType == 10 and data[2] == 0: - log.debug("[handle_msgtypeAB] PowerLink telling us what the code {0} {1} is for downloads, currently commented out as I'm not certain of this".format(data[3], data[4])) - # data[3] data[4] + log.debug(f"[handle_msgtypeAB] PowerLink telling us what the code {data[3]} {data[4]} is for downloads, currently commented out as I'm not certain of this") elif subType == 10 and data[2] == 1: if self.PanelMode == AlPanelMode.POWERLINK: log.debug("[handle_msgtypeAB] ************************** PowerLink, Panel wants to auto-enroll but not acted on (already in powerlink) **************************") -# elif self.PanelMode == AlPanelMode.STANDARD_PLUS: -# # The panel is asking to enroll, so give it a try -# log.debug("[handle_msgtypeAB] ************************** PowerLink, Panel wants to auto-enroll, so do it **************************") -# self._sendMsgENROLL() elif not self.ForceStandardMode: self.PanelWantsToEnrol = True log.debug("[handle_msgtypeAB] ************************** PowerLink, Panel wants to auto-enroll **************************") @@ -4268,11 +4252,11 @@ def handle_msgtypeAB(self, data) -> bool: # PowerLink Message # X10 Names (0xAC) I think def handle_msgtypeAC(self, data): # PowerLink Message """ MsgType=AC - ??? """ - log.debug("[handle_msgtypeAC] data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeAC] data {toString(data)}") def handle_msgtypeAD(self, data): # PowerLink Message """ MsgType=AD - Panel Powerlink Messages """ - log.debug("[handle_msgtypeAD] data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeAD] data {toString(data)}") #if data[2] == 0x00: # the request was accepted by the panel # self._addMessageToSendList("MSG_NO_IDEA") @@ -4333,7 +4317,7 @@ def processChunk(self, msgType, subType, ch : chunky): beezerodebug = True #log.debug(f"[handle_msgtypeB0] Z {hex(msgType)} {hex(subType)} chunky {ch}") - if msgType == 0x03 and subType == pmSendMsgB0_t["PANEL_STATE"] and ch.datasize == 8 and ch.index == 255 and ch.length == 21: + if msgType == 0x03 and subType == pmSendMsgB0["PANEL_STATE"] and ch.datasize == 8 and ch.index == 255 and ch.length == 21: # Panel state change # 0d b0 03 24 1a ff 08 ff 15 06 00 00 00 02 00 00 00 1a 03 0c 01 01 00 14 07 01 00 85 00 00 06 43 d2 0a # 0d b0 03 24 1a ff 08 ff 15 06 00 00 00 02 00 00 00 09 1a 0e 04 08 18 14 01 01 00 85 00 00 0f 43 a6 0a @@ -4373,7 +4357,7 @@ def processChunk(self, msgType, subType, ch : chunky): log.debug("[handle_msgtypeB0] It also claims to have a zone event") #self.ProcessZoneEvent(eventZone=eventZone, eventType=eventType) - elif msgType == 0x03 and subType == pmSendMsgB0_t["ZONE_OPENCLOSE"] and ch.datasize == 1 and ch.index == 3: + elif msgType == 0x03 and subType == pmSendMsgB0["ZONE_OPENCLOSE"] and ch.datasize == 1 and ch.index == 3: # I'm 100% sure this is correct zoneLen = ch.length * 8 # 8 bits in a byte log.debug("[handle_msgtypeB0] Received message, open/close information, zone length = {0}".format(zoneLen)) @@ -4381,7 +4365,7 @@ def processChunk(self, msgType, subType, ch : chunky): if zoneLen >= 32: self.do_sensor_update(ch.data[4:8], "do_status", "[handle_msgtypeB0] Zone Status 64-33", 32) - elif msgType == 0x03 and subType == pmSendMsgB0_t["ZONE_BYPASS"] and ch.datasize == 1 and ch.index == 3: + elif msgType == 0x03 and subType == pmSendMsgB0["ZONE_BYPASS"] and ch.datasize == 1 and ch.index == 3: # I'm 50% sure this is correct # 0d b0 03 19 0d ff 01 03 08 01 00 00 00 00 00 00 00 6f 43 66 0a Z01 (sensor 0) has been bypassed zoneLen = ch.length * 8 # 8 bits in a byte @@ -4390,7 +4374,7 @@ def processChunk(self, msgType, subType, ch : chunky): if zoneLen >= 32: self.do_sensor_update(ch.data[4:8], "do_bypass", "[handle_msgtypeB0] Zone Bypass 64-33", 32) - elif msgType == 0x03 and subType == pmSendMsgB0_t["SENSOR_ENROL"] and ch.datasize == 1 and ch.index == 3: # index 3 is zone information + elif msgType == 0x03 and subType == pmSendMsgB0["SENSOR_ENROL"] and ch.datasize == 1 and ch.index == 3: # index 3 is zone information # I'm 100% sure this is correct zoneLen = ch.length * 8 # 8 bits in a byte log.debug("[handle_msgtypeB0] Received message, zone enrolled information, use this to set the enrolled flag in each sensor, zone length = {0}".format(zoneLen)) @@ -4398,7 +4382,7 @@ def processChunk(self, msgType, subType, ch : chunky): if zoneLen >= 32: self.do_sensor_update(ch.data[4:8], "do_enrolled", "[handle_msgtypeB0] Zone Enrolled 64-33", 32) - elif msgType == 0x03 and subType == pmSendMsgB0_t["SENSOR_STATUS"] and ch.datasize == 8 and ch.index == 3: + elif msgType == 0x03 and subType == pmSendMsgB0["SENSOR_STATUS"] and ch.datasize == 8 and ch.index == 3: # I'm 100% sure this is correct log.debug("[handle_msgtypeB0] Received message, sensor list, length = {0}".format(ch.length)) for i in range(0, ch.length): @@ -4406,15 +4390,15 @@ def processChunk(self, msgType, subType, ch : chunky): if v > 0: # Is it a sensor? log.debug("[handle_msgtypeB0] sensor type for sensor {0} is {1}".format( i+1, v )) # Create the sensor - if v in pmZoneSensorMaster_t: # PowerMaster models, we assume that only PowerMaster models send B0 PDUs + if v in pmZoneSensorMaster: # PowerMaster models, we assume that only PowerMaster models send B0 PDUs self._updateSensor(i = i, sensor_type = v, enrolled = True) else: self._updateSensor(i = i, enrolled = True) log.debug("[handle_msgtypeB0] Found unknown sensor type " + hex(v)) - elif msgType == 0x03 and subType == pmSendMsgB0_t["ZONE_NAMES"] and ch.datasize == 8 and ch.index == 3: + elif msgType == 0x03 and subType == pmSendMsgB0["ZONE_NAMES"] and ch.datasize == 8 and ch.index == 3: # I'm 100% sure this is correct - zoneCnt = pmPanelConfig_t["CFG_WIRELESS"][self.PanelType] + pmPanelConfig_t["CFG_WIRED"][self.PanelType] + zoneCnt = pmPanelConfig["CFG_WIRELESS"][self.PanelType] + pmPanelConfig["CFG_WIRED"][self.PanelType] log.debug(f"[handle_msgtypeB0] Received message, zone names, length = {ch.length} zonecount = {zoneCnt}") if ch.length == zoneCnt: for i in range(0, len(ch.data)): @@ -4424,9 +4408,9 @@ def processChunk(self, msgType, subType, ch : chunky): if i in self.SensorList: self._updateSensor(i) - elif msgType == 0x03 and subType == pmSendMsgB0_t["ZONE_TYPES"] and ch.datasize == 8 and ch.index == 3: + elif msgType == 0x03 and subType == pmSendMsgB0["ZONE_TYPES"] and ch.datasize == 8 and ch.index == 3: # I'm 100% sure this is correct - zoneCnt = pmPanelConfig_t["CFG_WIRELESS"][self.PanelType] + pmPanelConfig_t["CFG_WIRED"][self.PanelType] + zoneCnt = pmPanelConfig["CFG_WIRELESS"][self.PanelType] + pmPanelConfig["CFG_WIRED"][self.PanelType] log.debug(f"[handle_msgtypeB0] Received message, zone types, length = {ch.length} zonecount = {zoneCnt}") if ch.length == zoneCnt: for i in range(0, len(ch.data)): @@ -4436,7 +4420,7 @@ def processChunk(self, msgType, subType, ch : chunky): if i in self.SensorList: self._updateSensor(i, zoneType = self.PanelSettings[PanelSetting.ZoneTypes][i]) - elif msgType == 0x03 and subType == pmSendMsgB0_t["PANEL_SETTINGS"]: + elif msgType == 0x03 and subType == pmSendMsgB0["PANEL_SETTINGS"]: #03 35 0b ff 08 ff 06 00 00 01 00 00 00 02 43 dataContentA = ch.data[0] dataContentB = ch.data[1] @@ -4445,8 +4429,8 @@ def processChunk(self, msgType, subType, ch : chunky): datalen = ch.length - 3 log.debug("[handle_msgtypeB0] ***************************** Panel Settings ********************************") if experimental: - if dataContent in pmPanelSettingsB0_t: - d = pmPanelSettingsB0_t[dataContent] + if dataContent in pmPanelSettingsB0: + d = pmPanelSettingsB0[dataContent] if (d.length == 0 or ch.length == d.length) and datatype == d.datatype: val = None if d.datatype == 1: # 4 bit integers, possibly an array @@ -4477,11 +4461,11 @@ def processChunk(self, msgType, subType, ch : chunky): # Check the PanelSettings to see if there's one that refers to this dataContent if val is not None: for key in PanelSetting: - if PanelSettingCodes[key].PowerMasterB0Panel == dataContent: + if pmPanelSettingCodes[key].PowerMasterB0Panel == dataContent: log.debug(f" {key=} ({d.msg}) replacing {self.PanelSettings[key]} with {val}") - if PanelSettingCodes[key].item is not None: - if len(val) > PanelSettingCodes[key].item: - self.PanelSettings[key] = val[PanelSettingCodes[key].item] + if pmPanelSettingCodes[key].item is not None: + if len(val) > pmPanelSettingCodes[key].item: + self.PanelSettings[key] = val[pmPanelSettingCodes[key].item] else: self.PanelSettings[key] = val break @@ -4491,7 +4475,7 @@ def processChunk(self, msgType, subType, ch : chunky): log.debug(f" dataContent={hex(dataContent)} panel setting unknown") log.debug("[handle_msgtypeB0] ***************************** Panel Settings Exit ***************************") - elif subType == pmSendMsgB0_t["ZONE_LAST_EVENT"] and ch.datasize == 40 and ch.index == 3: # Each entry is ch.datasize=40 bits (or 5 bytes) + elif subType == pmSendMsgB0["ZONE_LAST_EVENT"] and ch.datasize == 40 and ch.index == 3: # Each entry is ch.datasize=40 bits (or 5 bytes) # I'm 100% sure this is correct if msgType == 0x02: # Zone Last Event @@ -4517,7 +4501,7 @@ def processChunk(self, msgType, subType, ch : chunky): o = i * 5 self._decode_4B(i + self.beezero_024B_sensorcount, ch.data[o:o+5]) else: # Assume PM10 - # Assume that when the PowerMaster panel has less than 32 sensors then it just sends this and not msgType == 0x02, subType == pmSendMsgB0_t["ZONE_LAST_EVENT"] + # Assume that when the PowerMaster panel has less than 32 sensors then it just sends this and not msgType == 0x02, subType == pmSendMsgB0["ZONE_LAST_EVENT"] sensorcount = int(ch.length / 5) for i in range(0, sensorcount): o = i * 5 @@ -4525,7 +4509,7 @@ def processChunk(self, msgType, subType, ch : chunky): self.beezero_024B_sensorcount = None # If theres a next time so they are coordinated - elif msgType == 0x03 and subType == pmSendMsgB0_t["ASK_ME_1"] and ch.datasize == 8 and ch.index == 0xFF: + elif msgType == 0x03 and subType == pmSendMsgB0["ASK_ME_1"] and ch.datasize == 8 and ch.index == 0xFF: log.debug(f"[handle_msgtypeB0] Received ASK_ME_1 pop message {ch}") if self.PanelMode in [AlPanelMode.POWERLINK, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.STANDARD_PLUS, AlPanelMode.STANDARD]: if ch.length > 0: @@ -4533,7 +4517,7 @@ def processChunk(self, msgType, subType, ch : chunky): self._addMessageToSendList(s) - elif msgType == 0x03 and subType == pmSendMsgB0_t["ASK_ME_2"] and ch.datasize == 8 and ch.index == 0xFF: + elif msgType == 0x03 and subType == pmSendMsgB0["ASK_ME_2"] and ch.datasize == 8 and ch.index == 0xFF: log.debug(f"[handle_msgtypeB0] Received ASK_ME_2 pop message {ch}") if self.PanelMode in [AlPanelMode.POWERLINK, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.STANDARD_PLUS, AlPanelMode.STANDARD]: if ch.length > 0: @@ -4577,7 +4561,7 @@ def processChunk(self, msgType, subType, ch : chunky): # ModelType = data[21] # PanelType = data[22] # PowerMaster = (PanelType >= 7) -# PanelModel = pmPanelType_t[PanelType] if PanelType in pmPanelType_t else "UNKNOWN" # INTERFACE : PanelType set to model +# PanelModel = pmPanelType[PanelType] if PanelType in pmPanelType else "UNKNOWN" # INTERFACE : PanelType set to model # log.debug("[handle_msgtypeB0] PanelType={0} : {2} , Model={1} Powermaster {3}".format(PanelType, ModelType, PanelModel, PowerMaster)) else: if beezerodebug: @@ -4646,11 +4630,11 @@ def chunkme(data) -> list: #log.debug(f" {toString(data[:2])} Decode Chunk {str(chunk)}") # Check the PanelSettings to see if there's one that refers to this message for key in PanelSetting: - if PanelSettingCodes[key].PowerMasterB0Message == subType and PanelSettingCodes[key].PowerMasterB0Index == chunk.index: + if pmPanelSettingCodes[key].PowerMasterB0Message == subType and pmPanelSettingCodes[key].PowerMasterB0Index == chunk.index: log.debug(f" {subType=} {key=} replacing {toString(self.PanelSettings[key])} with {toString(chunk.data)}") - if PanelSettingCodes[key].item is not None: - if len(val) > PanelSettingCodes[key].item: - self.PanelSettings[key] = chunk.data[PanelSettingCodes[key].item] + if pmPanelSettingCodes[key].item is not None: + if len(val) > pmPanelSettingCodes[key].item: + self.PanelSettings[key] = chunk.data[pmPanelSettingCodes[key].item] else: self.PanelSettings[key] = chunk.data break @@ -4685,7 +4669,7 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image """ MsgType=F4 - Static JPG Image """ from PIL import Image, UnidentifiedImageError - #log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) + #log.debug(f"[handle_msgtypeF4] data {toString(data)}") # 0 - message type ==> 3=start, 5=data # 1 - always 0 @@ -4698,7 +4682,7 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image pushchange = False if msgtype == 0x03: # JPG Header - log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeF4] data {toString(data)}") pushchange = True zone = (10 * int(data[5] // 16)) + (data[5] % 16) # the // does integer floor division so always rounds down unique_id = data[6] @@ -4799,7 +4783,7 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image log.debug(f"[handle_msgtypeF4] Got Image width {width} height {height} total = {total} = {hex(total)}") # Got all the data so write it out to a jpg file - #fn = "camera_image_z{0:0>2}_{1:0>2}{2:0>2}{3:0>2}_{4:0>2}{5:0>2}{6:0>2}.jpg".format(zone, t.day, t.month, t.year - 2000, t.hour, t.minute, t.second, ) + #fn = f"camera_image_z{zone:0>2}_{t.day:0>2}{t.month:0>2}{t.year - 2000:0>2}_{t.hour:0>2}{t.minute:0>2}{t.second:0>2}.jpg" #with open(fn, 'wb') as f1: # f1.write(buffer) # f1.close() @@ -4836,13 +4820,12 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image self.ImageManager.terminateImage() elif msgtype == 0x01: - log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) - #log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeF4] data {toString(data)}") log.debug(f"[handle_msgtypeF4] Message Type not processed") pushchange = True else: - log.debug("[handle_msgtypeF4] not seen data {0}".format(toString(data))) + log.debug(f"[handle_msgtypeF4] not seen data {toString(data)}") log.debug(f"[handle_msgtypeF4] Message Type not processed") return pushchange @@ -4929,10 +4912,10 @@ def requestPanelCommand(self, state : AlPanelCommand, code : str = "") -> AlComm if self.PanelMode in [AlPanelMode.STANDARD, AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.POWERLINK]: bpin = self._createPin(code) # Ensure that the state is valid - if state in pmArmMode_t: + if state in pmArmMode: armCode = bytearray() # Retrieve the code to send to the panel - armCode.append(pmArmMode_t[state]) + armCode.append(pmArmMode[state]) self._addMessageToSendList("MSG_ARM", immediate = True, options=[ [3, armCode], [4, bpin] ]) # self._addMessageToSendList("MSG_STATUS_SEN", immediate = True) return AlCommandStatus.SUCCESS @@ -4942,10 +4925,10 @@ def requestPanelCommand(self, state : AlPanelCommand, code : str = "") -> AlComm elif state == AlPanelCommand.TRIGGER: self._addMessageToSendList("MSG_PM_SIREN", immediate = True, options=[ [4, bpin] ]) # return AlCommandStatus.SUCCESS - elif state in pmSirenMode_t: + elif state in pmSirenMode: sirenCode = bytearray() # Retrieve the code to send to the panel - sirenCode.append(pmSirenMode_t[state]) + sirenCode.append(pmSirenMode[state]) self._addMessageToSendList("MSG_PM_SIREN_MODE", immediate = True, options=[ [4, bpin], [11, sirenCode] ]) # return AlCommandStatus.SUCCESS return AlCommandStatus.FAIL_INVALID_STATE @@ -4954,7 +4937,7 @@ def requestPanelCommand(self, state : AlPanelCommand, code : str = "") -> AlComm def setX10(self, device : int, state : AlX10Command) -> AlCommandStatus: # This is untested # "MSG_X10PGM" : VisonicCommand(convertByteArray('A4 00 00 00 00 00 99 99 99 00 00 43'), None , False, "X10 Data" ), - #log.debug("[SendX10Command] Processing {0} {1}".format(device, type(device))) + #log.debug(f"[SendX10Command] Processing {device} {type(device)}") if not self.pmDownloadMode: if self.PanelMode in [AlPanelMode.STANDARD, AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.POWERLINK]: if device >= 0 and device <= 15: @@ -4962,8 +4945,8 @@ def setX10(self, device : int, state : AlX10Command) -> AlCommandStatus: calc = 1 << device byteA = calc & 0xFF byteB = (calc >> 8) & 0xFF - if state in pmX10State_t: - what = pmX10State_t[state] + if state in pmX10State: + what = pmX10State[state] self._addMessageToSendList("MSG_X10PGM", immediate = True, options=[ [6, what], [7, byteA], [8, byteB] ]) self._addMessageToSendList("MSG_STATUS_SEN", immediate = True) return AlCommandStatus.SUCCESS @@ -5032,7 +5015,9 @@ def getEventLog(self, pin : str = "") -> AlCommandStatus: if self.PanelMode in [AlPanelMode.STANDARD, AlPanelMode.STANDARD_PLUS, AlPanelMode.POWERLINK_BRIDGED, AlPanelMode.POWERLINK]: log.debug("getEventLog") self.eventCount = 0 - self.pmEventLogDictionary = {} + #if self.isPowerMaster(): + # self.B0_Message_Wanted.add("EVENT_LOG") + #else: bpin = self._createPin(pin) self._addMessageToSendList("MSG_EVENTLOG", options=[ [4, bpin] ]) return AlCommandStatus.SUCCESS diff --git a/custom_components/visonic/select.py b/custom_components/visonic/select.py index d75b657..038bcaa 100644 --- a/custom_components/visonic/select.py +++ b/custom_components/visonic/select.py @@ -163,9 +163,9 @@ def select_option(self, option: str) -> None: ) if self._pending_state_is_armed is not None: - _LOGGER.debug("Currently Pending {0} so ignoring request to select option".format(self.unique_id)) + _LOGGER.debug(f"Currently Pending {self.unique_id} so ignoring request to select option") elif option is not None and option in self.options: - #_LOGGER.debug("Sending Option {0} to {1}".format(option, self.unique_id)) + #_LOGGER.debug(f"Sending Option {option} to {self.unique_id}") result = self._client.sendBypass(self._visonic_device.getDeviceID(), option == BYPASS, "") # pin code to "" to use default if set if result == AlCommandStatus.SUCCESS: self._pending_state_is_armed = (option == ARMED) diff --git a/custom_components/visonic/siren.py b/custom_components/visonic/siren.py index b98aca9..5c4d6cd 100644 --- a/custom_components/visonic/siren.py +++ b/custom_components/visonic/siren.py @@ -40,7 +40,7 @@ async def async_setup_entry( def async_add_siren() -> None: """Add Visonic Siren""" entities: list[Entity] = [] - entities.append(VisonicSiren(hass, client, 1)) + entities.append(VisonicSiren(hass, client)) _LOGGER.debug(f"siren adding entity") async_add_entities(entities) @@ -59,12 +59,12 @@ class VisonicSiren(SirenEntity): _attr_translation_key: str = "alarm_panel_key" _attr_should_poll = False - def __init__(self, hass: HomeAssistant, client: VisonicClient, partition_id: int): + def __init__(self, hass: HomeAssistant, client: VisonicClient): """Initialize a Visonic security alarm.""" self._client = client self.hass = hass client.onChange(self.onClientChange) - self._partition_id = partition_id + #self._partition_id = partition_id self._mystate = False pname = client.getMyString() self._myname = pname + "s01" @@ -117,7 +117,7 @@ def isPanelConnected(self) -> bool: @property def unique_id(self) -> str: """Return a unique ID.""" - return self._myname + "_" + str(self._partition_id) + return self._myname # + "_" + str(self._partition_id) @property def name(self):