From b7f0dc02b3a3ccacc7a04f8d077f8e08e8a39156 Mon Sep 17 00:00:00 2001 From: davesmeghead Date: Tue, 6 Aug 2024 21:50:52 +0100 Subject: [PATCH] 0.9.6.16 Added 2 Panel Entity Attributes and changed X10 decode Changed the way the X10 data is decoded to hopefully fix a bug Added 2 Panel Attributes: lastevent and lasteventtime --- custom_components/visonic/client.py | 2 +- .../visonic/examples/complete_example.py | 31 +- custom_components/visonic/manifest.json | 2 +- custom_components/visonic/pyconst.py | 4 +- custom_components/visonic/pyhelper.py | 41 +-- custom_components/visonic/pyvisonic.py | 332 +++++++++--------- 6 files changed, 226 insertions(+), 186 deletions(-) diff --git a/custom_components/visonic/client.py b/custom_components/visonic/client.py index b259645..72aeabe 100644 --- a/custom_components/visonic/client.py +++ b/custom_components/visonic/client.py @@ -122,7 +122,7 @@ # "trigger", #] -CLIENT_VERSION = "0.9.6.14" +CLIENT_VERSION = "0.9.6.16" MAX_CLIENT_LOG_ENTRIES = 300 diff --git a/custom_components/visonic/examples/complete_example.py b/custom_components/visonic/examples/complete_example.py index 2990de6..025d0ac 100644 --- a/custom_components/visonic/examples/complete_example.py +++ b/custom_components/visonic/examples/complete_example.py @@ -529,8 +529,8 @@ def updateConfig(self, conf=None): self.visonicProtocol.updateSettings(self.__getConfigData()) #print("[updateConfig] exit") - def getPanelLastEvent(self) -> str: - """ Is the siren active. """ + def getPanelLastEvent(self) -> (str, str): + """ Get Last Panel Event. """ if self.visonicProtocol is not None: return self.visonicProtocol.getPanelLastEvent() return False @@ -864,7 +864,7 @@ def help(): for device in devices: console.print("Device " + str(device)) else: - console.print("ERROR: There must be a panel connection to perform command " + result) + console.print("ERROR: invalid command " + result) print("Here ZZZZZZZ") @@ -891,11 +891,36 @@ def help(): raise e def handle_exception(loop, context): + + def _createPrefix() -> str: + previous_frame = currentframe().f_back + ( + filepath, + line_number, + function, + lines, + index, + ) = inspect.getframeinfo(previous_frame) + filename = filepath[filepath.rfind('/')+1:] + s = f"{filename} {line_number:<5} {function:<30} " + previous_frame = currentframe() + ( + filepath, + line_number, + function, + lines, + index, + ) = inspect.getframeinfo(previous_frame) + filename = filepath[filepath.rfind('/')+1:] + + return s + f"{filename} {line_number:<5} {function:<30} " + # context["message"] will always be there; but context["exception"] may not msg = context.get("exception", context["message"]) if str(msg) != terminating_clean: print(f"Caught exception: {msg}") print(f" {context}") + #print(f" {_createPrefix()}") asyncio.create_task(shutdown(loop)) async def shutdown(loop, signal=None): diff --git a/custom_components/visonic/manifest.json b/custom_components/visonic/manifest.json index ff9ce11..0f92845 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.6.13" + "version": "0.9.6.16" } diff --git a/custom_components/visonic/pyconst.py b/custom_components/visonic/pyconst.py index 60d7537..4c5734a 100644 --- a/custom_components/visonic/pyconst.py +++ b/custom_components/visonic/pyconst.py @@ -510,9 +510,9 @@ def isPanelBypass(self) -> bool: return False @abstractmethod - def getPanelLastEvent(self) -> str: + def getPanelLastEvent(self) -> (str, str): """ Return the panels last event string """ - return "" + return ("", "") # @abstractmethod # def getPanelTroubleStatus(self) -> str: diff --git a/custom_components/visonic/pyhelper.py b/custom_components/visonic/pyhelper.py index f1579aa..8117dcd 100644 --- a/custom_components/visonic/pyhelper.py +++ b/custom_components/visonic/pyhelper.py @@ -94,6 +94,10 @@ def _getUTCTime() -> datetime: 0x3B : AlTroubleType.BATTERY, 0x3C : AlTroubleType.BATTERY, 0x40 : AlTroubleType.BATTERY, 0x43 : AlTroubleType.BATTERY } +# Convert byte array to a string of hex values +def toString(array_alpha: bytearray, gap = " "): + return ("".join(("%02x"+gap) % b for b in array_alpha))[:-len(gap)] if len(gap) > 0 else ("".join("%02x" % b for b in array_alpha)) + class vloggerclass: def __init__(self, loggy, panel_id : int = -1, detail : bool = False): self.detail = detail @@ -654,11 +658,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(self._toString(packet), hex(self._calculateCRC(packet[1:-2])[0]).upper(), hex(self._calculateCRCAlt(packet[1:-2])[0]).upper())) + 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())) 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(self._toString(packet), hex(self._calculateCRC(packet[1:-2])[0]).upper(), hex(self._calculateCRCAlt(packet[1:-2])[0]).upper())) + 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())) return True log.debug("[_validatePDU] Not valid packet, CRC failed, may be ongoing and not final 0A") @@ -667,7 +671,7 @@ def _validatePDU(self, packet: bytearray) -> bool: # alternative to calculate the checksum for sending and receiving messages def _calculateCRCAlt(self, msg: bytearray): """ Calculate CRC Checksum """ - # log.debug("[_calculateCRC] Calculating for: %s", self._toString(msg)) + # log.debug("[_calculateCRC] Calculating for: %s", toString(msg)) # Calculate the checksum checksum = 0 for char in msg[0 : len(msg)]: @@ -678,13 +682,13 @@ def _calculateCRCAlt(self, msg: bytearray): checksum = 256 - (checksum % 255) if checksum == 256: checksum = 1 - # log.debug("[_calculateCRC] Calculating for: {self._toString(msg)} calculated CRC is: {self._toString(bytearray([checksum]))}") + # log.debug("[_calculateCRC] Calculating for: {toString(msg)} calculated CRC is: {toString(bytearray([checksum]))}") return bytearray([checksum]) # calculate the checksum for sending and receiving messages def _calculateCRC(self, msg: bytearray): """ Calculate CRC Checksum """ - # log.debug("[_calculateCRC] Calculating for: %s", self._toString(msg)) + # log.debug("[_calculateCRC] Calculating for: %s", toString(msg)) # Calculate the checksum checksum = 0 for char in msg[0 : len(msg)]: @@ -692,7 +696,7 @@ def _calculateCRC(self, msg: bytearray): checksum = 0xFF - (checksum % 0xFF) if checksum == 0xFF: checksum = 0x00 - # log.debug("[_calculateCRC] Calculating for: {self._toString(msg)} calculated CRC is: {self._toString(bytearray([checksum]))}") + # log.debug("[_calculateCRC] Calculating for: {toString(msg)} calculated CRC is: {toString(bytearray([checksum]))}") return bytearray([checksum]) @@ -729,7 +733,8 @@ def _initVars(self): self.PanelAlarmStatus = AlAlarmType.NONE self.PanelTroubleStatus = AlTroubleType.NONE - self.PanelLastEvent = "Unknown" + self.PanelLastEvent = "Startup/Startup" + self.PanelLastEventTime = self._getTimeFunction().strftime("%d/%m/%Y, %H:%M:%S") self.PanelStatusText = "Unknown" self.LastPanelEventData = {} @@ -755,7 +760,7 @@ def _dumpSensorsToLogFile(self, incX10 = False): log.debug(" key {0:<2} X10 {1}".format(key, device)) log.debug(" Model {: <18} PowerMaster {: <18} LastEvent {: <18} Ready {: <13}".format(self.PanelModel, - 'Yes' if self.PowerMaster else 'No', self.getPanelLastEvent(), 'Yes' if self.PanelReady else 'No')) + 'Yes' if self.PowerMaster else 'No', self.getPanelLastEvent()[0], 'Yes' if self.PanelReady else 'No')) pm = titlecase(self.PanelMode.name.replace("_"," ")) # str(AlPanelMode()[self.PanelMode]).replace("_"," ") ts = titlecase(self.PanelTroubleStatus.name.replace("_"," ")) # str(AlTroubleType()[self.PanelTroubleStatus]).replace("_"," ") al = titlecase(self.PanelAlarmStatus.name.replace("_"," ")) # str(AlAlarmType()[self.PanelAlarmStatus]).replace("_"," ") @@ -802,8 +807,8 @@ def isPanelBypass(self) -> bool: return self.PanelBypass return False - def getPanelLastEvent(self) -> str: - return self.PanelLastEvent + def getPanelLastEvent(self) -> (str, str): + return (self.PanelLastEvent, self.PanelLastEventTime) def requestPanelCommand(self, state : AlPanelCommand, code : str = "") -> AlCommandStatus: """ Send a request to the panel to Arm/Disarm """ @@ -838,10 +843,6 @@ def getEventLog(self, code : str = "") -> AlCommandStatus: """ Get Panel Event Log """ return AlCommandStatus.FAIL_ABSTRACT_CLASS_NOT_IMPLEMENTED - # Convert byte array to a string of hex values - def _toString(self, array_alpha: bytearray): - return ("".join("%02x " % b for b in array_alpha))[:-1] - # get the current date and time def _getTimeFunction(self) -> datetime: return datetime.now() @@ -865,12 +866,13 @@ def setLastPanelEventData(self, count=0, type=[ ], event=[ ], zonemode=[ ], name if count > 0: self.PanelLastEvent = name[count-1] + "/" + zonemode[count-1] + self.PanelLastEventTime = self._getTimeFunction().strftime("%d/%m/%Y, %H:%M:%S") for i in range(0, count): a = {} a["name"] = titlecase(name[i].replace("_"," ").lower()) a["event"] = titlecase(zonemode[i].replace("_"," ").lower()) log.debug(f"[PanelUpdate] {a}") - self.onPanelChangeHandler(AlCondition.PANEL_UPDATE, a) + self.sendPanelUpdate(AlCondition.PANEL_UPDATE, a) #log.debug(f"Last event {datadict}") return datadict @@ -886,6 +888,8 @@ def getEventData(self) -> dict: datadict["bypass"] = self.PanelBypass datadict["alarm"] = titlecase(self.PanelAlarmStatus.name.replace("_"," ").lower()) datadict["trouble"] = titlecase(self.PanelTroubleStatus.name.replace("_"," ").lower()) + datadict["lastevent"] = titlecase(self.PanelLastEvent.replace("_"," ").lower()) + datadict["lasteventtime"] = self.PanelLastEventTime return datadict # Set the onDisconnect callback handlers @@ -908,12 +912,9 @@ def onPanelLog(self, fn : Callable): # onPanelLog ( event_log_entry def onPanelChange(self, fn : Callable): # onPanelChange ( datadictionary : dict ) self.onPanelChangeHandler = fn - def sendPanelUpdate(self, ev : AlCondition): + def sendPanelUpdate(self, ev : AlCondition, d : dict = {} ): if self.onPanelChangeHandler is not None: - if ev == AlCondition.PANEL_UPDATE: - self.onPanelChangeHandler(AlCondition.PUSH_CHANGE, {}) - else: - self.onPanelChangeHandler(ev, {}) + self.onPanelChangeHandler(ev, d) def _searchDict(self, dict, v_search): for k, v in dict.items(): diff --git a/custom_components/visonic/pyvisonic.py b/custom_components/visonic/pyvisonic.py index 05480bf..71e1990 100644 --- a/custom_components/visonic/pyvisonic.py +++ b/custom_components/visonic/pyvisonic.py @@ -92,15 +92,15 @@ def convertByteArray(s) -> bytearray: try: from .pyconst import (AlTransport, AlPanelDataStream, NO_DELAY_SET, PanelConfig, AlConfiguration, AlPanelMode, AlPanelCommand, AlTroubleType, AlAlarmType, AlPanelStatus, AlSensorCondition, AlCommandStatus, AlX10Command, AlCondition, AlLogPanelEvent, AlSensorType) - from .pyhelper import (MyChecksumCalc, AlImageManager, ImageRecord, titlecase, pmPanelTroubleType_t, pmPanelAlarmType_t, AlPanelInterfaceHelper, + from .pyhelper import (toString, MyChecksumCalc, AlImageManager, ImageRecord, titlecase, pmPanelTroubleType_t, pmPanelAlarmType_t, AlPanelInterfaceHelper, AlSensorDeviceHelper, AlSwitchDeviceHelper) except: from pyconst import (AlTransport, AlPanelDataStream, NO_DELAY_SET, PanelConfig, AlConfiguration, AlPanelMode, AlPanelCommand, AlTroubleType, AlAlarmType, AlPanelStatus, AlSensorCondition, AlCommandStatus, AlX10Command, AlCondition, AlLogPanelEvent, AlSensorType) - from pyhelper import (MyChecksumCalc, AlImageManager, ImageRecord, titlecase, pmPanelTroubleType_t, pmPanelAlarmType_t, AlPanelInterfaceHelper, + from pyhelper import (toString, MyChecksumCalc, AlImageManager, ImageRecord, titlecase, pmPanelTroubleType_t, pmPanelAlarmType_t, AlPanelInterfaceHelper, AlSensorDeviceHelper, AlSwitchDeviceHelper) -PLUGIN_VERSION = "1.3.5.3" +PLUGIN_VERSION = "1.3.6.2" # Some constants to help readability of the code @@ -365,27 +365,27 @@ def convertByteArray(s) -> bytearray: DebugI = False # Debug incoming image data PanelCallBack = collections.namedtuple("PanelCallBack", 'length ackneeded isvariablelength varlenbytepos flexiblelength ignorechecksum debugprint' ) pmReceiveMsg_t = { - 0x00 : PanelCallBack( 0, True, False, -1, 0, False, DebugC ), # 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, DebugC ), # Ack - 0x06 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # Timeout. See the receiver function for ACK handling - 0x07 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # No idea what this means but decode it anyway - 0x08 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # Access Denied - 0x0B : PanelCallBack( 0, True, False, 0, 0, False, DebugC ), # Stop --> Download Complete - 0x0F : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # THE PANEL DOES NOT SEND THIS. THIS IS USED FOR A LOOP BACK TEST - 0x22 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Panel Info (older visonic powermax panels) - 0x25 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Download Retry - 0x33 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Download Settings - 0x3C : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Panel Info - 0x3F : PanelCallBack( 7, True, True, 4, 5, False, DebugM ), # Download Info in varying lengths (For variable length, the length is the fixed number of bytes). - 0xA0 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Event Log - 0xA3 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Zone Names - 0xA5 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Status Update Length was 15 but panel seems to send different lengths - 0xA6 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Zone Types I think!!!! - 0xA7 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Panel Status Change - 0xAB : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 Enroll Request 0x0A OR Ping 0x03 Length was 15 but panel seems to send different lengths - 0xAC : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 X10 Names ??? - 0xAD : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 Panel responds with this when we ask for JPG images - 0xB0 : PanelCallBack( 8, False, True, 4, 2, False, True ), # The B0 message comes in varying lengths, sometimes it is shorter than what it states and the CRC is sometimes wrong + 0x00 : PanelCallBack( 0, True, False, -1, 0, False, DebugC ), # 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, DebugC ), # Ack + 0x06 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # Timeout. See the receiver function for ACK handling + 0x07 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # No idea what this means but decode it anyway + 0x08 : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # Access Denied + 0x0B : PanelCallBack( 0, True, False, 0, 0, False, DebugC ), # Stop --> Download Complete + 0x0F : PanelCallBack( 0, False, False, 0, 0, False, DebugC ), # THE PANEL DOES NOT SEND THIS. THIS IS USED FOR A LOOP BACK TEST + 0x22 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Panel Info (older visonic powermax panels) + 0x25 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Download Retry + 0x33 : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Download Settings + 0x3C : PanelCallBack( 14, True, False, 0, 0, False, DebugC ), # 14 Panel Info + 0x3F : PanelCallBack( 7, True, True, 4, 5, False, DebugM ), # Download Info in varying lengths (For variable length, the length is the fixed number of bytes). + 0xA0 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Event Log + 0xA3 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Zone Names + 0xA5 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Status Update Length was 15 but panel seems to send different lengths + 0xA6 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Zone Types I think!!!! + 0xA7 : PanelCallBack( 15, True, False, 0, 0, False, DebugM ), # 15 Panel Status Change + 0xAB : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 Enroll Request 0x0A OR Ping 0x03 Length was 15 but panel seems to send different lengths + 0xAC : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 X10 Names ??? + 0xAD : PanelCallBack( 15, True, False, 0, 0, False, True ), # 15 Panel responds with this when we ask for JPG images + 0xB0 : PanelCallBack( 8, True, True, 4, 2, False, True ), # The B0 message comes in varying lengths, sometimes it is shorter than what it states and the CRC is sometimes wrong REDIRECT : PanelCallBack( 5, False, True, 2, 0, False, False ), # TESTING: These are redirected Powerlink messages. 0D C0 len cs 0A so 5 plus the original data length # The F1 message needs to be ignored, I have no idea what it is but the crc is always wrong and only Powermax+ panels seem to send it. Assume a minimum length of 9, a variable length and ignore the checksum calculation. 0xF1 : PanelCallBack( 9, True, True, 0, 0, True, DebugC ), # Ignore checksum on all F1 messages @@ -750,6 +750,7 @@ def convertByteArray(s) -> bytearray: ########################## EEPROM Decode ################################################################################################################################################################################################### ############################################################################################################################################################################################################################################## +# Set 1 of the following but not both, depending on the panel type XDumpy = False # True # Used to dump PowerMax Data to the log file SDumpy = False # False # Used to dump PowerMaster Data to the log file Dumpy = XDumpy or SDumpy @@ -1131,11 +1132,11 @@ def convertByteArray(s) -> bytearray: # Entry in a queue of commands (and PDUs) to send to the panel class VisonicListEntry: - def __init__(self, command = None, options = None, raw = None): + def __init__(self, command = None, options = None, raw = None, res = None): self.command = command # kwargs.get("command", None) self.options = options # kwargs.get("options", None) self.raw = raw - self.response = [] + self.response = [] if res is None else res if command is not None: if self.command.replytype is not None: self.response = self.command.replytype.copy() # list of message reply needed @@ -1443,6 +1444,8 @@ def _performDisconnect(self, reason : str, exc=None): # Save the sirens #self.pmSirenDev_t = {} + if self.transport is not None: + self.transport.close() self.transport = None #sleep(5.0) # a bit of time for the watchdog timers and keep alive loops to self terminate @@ -1502,7 +1505,7 @@ def _gotoStandardMode(self): self.GiveupTryingDownload = True self.pmPowerlinkModePending = False self.pmPowerlinkMode = False - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend if self.DisableAllCommands: # Clear the send list and empty the expected response list # Do not sent STOP, EXIT or INIT commands to the panel (as it already has Powerlink Hardware connected) @@ -1541,7 +1544,7 @@ def _updateSensorNamesAndTypes(self, force = False) -> bool: log.debug("[updateSensorNamesAndTypes] Trying to get the zone types again zone count = " + str(zoneCnt) + " I've only got " + str(len(self.ZoneTypes)) + " zone types") self._sendCommand("MSG_ZONETYPE") else: - log.debug(f"[updateSensorNamesAndTypes] Warning: Panel Type error {self.PanelType}") + log.debug(f"[updateSensorNamesAndTypes] Warning: Panel Type error {self.PanelType=}") return retval # This function performs a "soft" reset to the send comms, it resets the queues, clears expected responses, @@ -1635,7 +1638,6 @@ async def _keep_alive_and_watchdog_timer(self): no_packet_received_counter = 0 settime_counter = 0 mode_counter = 0 - prevent_status_updates = False image_delay_counter = 0 log_sensor_state_counter = 0 @@ -1751,7 +1753,7 @@ async def _keep_alive_and_watchdog_timer(self): self.PanelMode = AlPanelMode.PROBLEM # Remember that PowerlinkMode is False here anyway self.PanelReady = False - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend self.pmExpectedResponse = [] self.expectedResponseTimeout = 0 elif self.powerlink_counter >= POWERLINK_TIMEOUT: @@ -1811,7 +1813,7 @@ async def _keep_alive_and_watchdog_timer(self): # Drop out of Powerlink mode if there are problems with the panel connection (it is no longer reliable) self.pmPowerlinkMode = False self._reset_powerlink_counter() - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend self.PanelReady = False self._triggerRestoreStatus() # Clear message buffers and send a Restore (if in Powerlink) or Status (not in Powerlink) to the Panel @@ -1821,7 +1823,7 @@ async def _keep_alive_and_watchdog_timer(self): # TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE # TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE TESTING FROM HERE - if not self.pmDownloadMode and not prevent_status_updates: + if not self.pmDownloadMode: # We can't do any of this if the panel is in the downloading state or receiving a jpg image mode_counter = mode_counter + 1 if (mode_counter % 86400) == 0: @@ -1869,7 +1871,7 @@ async def _keep_alive_and_watchdog_timer(self): self._dumpSensorsToLogFile() # Is it time to send an I'm Alive message to the panel - if not prevent_status_updates and len(self.SendList) == 0 and not self.pmDownloadMode and self.keep_alive_counter >= self.KeepAlivePeriod: # + if len(self.SendList) == 0 and not self.pmDownloadMode and self.keep_alive_counter >= self.KeepAlivePeriod: # # Every self.KeepAlivePeriod seconds, unless watchdog has been reset self._reset_keep_alive_messages() @@ -1945,7 +1947,6 @@ async def _keep_alive_and_watchdog_timer(self): no_data_received_counter = 0 settime_counter = 0 mode_counter = 0 - prevent_status_updates = False no_packet_received_counter = 0 image_delay_counter = 0 log_sensor_state_counter = 0 @@ -1964,9 +1965,9 @@ def vp_data_received(self, data): if self.suspendAllOperations: return if not self.firstCmdSent: - log.debug("[data receiver] Ignoring garbage data: " + self._toString(data)) + log.debug("[data receiver] Ignoring garbage data: " + toString(data)) return - # log.debug('[data receiver] received data: %s', self._toString(data)) + # log.debug('[data receiver] received data: %s', toString(data)) self.lastRecvOfPanelData = self._getUTCTimeFunction() for databyte in data: # process a single byte at a time @@ -1996,7 +1997,7 @@ def _handle_received_byte(self, 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(self._toString(self.ReceiveData), hex(data).upper())) + log.info("[data receiver] PDU Too Large: Dumping current buffer {0} The next byte is {1}".format(toString(self.ReceiveData), hex(data).upper())) pdu_len = 0 # Reset the incoming data to 0 length self._resetMessageData() @@ -2006,7 +2007,7 @@ def _handle_received_byte(self, data): self._resetMessageData() if data == PACKET_HEADER: # preamble self.ReceiveData.append(data) - #log.debug("[data receiver] Starting PDU " + self._toString(self.ReceiveData)) + #log.debug("[data receiver] Starting PDU " + toString(self.ReceiveData)) # else we're trying to resync and walking through the bytes waiting for a PACKET_HEADER preamble byte elif pdu_len == 1: @@ -2052,18 +2053,18 @@ def _handle_received_byte(self, data): self.ReceiveData.append(data) # add byte to the message buffer if self.pmCurrentPDU.ignorechecksum or self._validatePDU(self.ReceiveData): # if the message passes CRC checks then process it # We've got a validated message - log.debug("[data receiver] Validated PDU: Got Validated PDU type 0x%02x data %s", int(self.ReceiveData[1]), self._toString(self.ReceiveData)) + log.debug("[data receiver] Validated PDU: Got Validated PDU type 0x%02x data %s", int(self.ReceiveData[1]), toString(self.ReceiveData)) self._processReceivedMessage(ackneeded=self.pmCurrentPDU.ackneeded, debugp=self.pmCurrentPDU.debugprint, data=self.ReceiveData) self._resetMessageData() elif (self.pmIncomingPduLen == 0 and data == PACKET_FOOTER) or (pdu_len + 1 == self.pmIncomingPduLen): # postamble (the +1 is to include the current data byte) # (waiting for PACKET_FOOTER and got it) OR (actual length == calculated expected length) self.ReceiveData.append(data) # add byte to the message buffer - # log.debug("[data receiver] Building PDU: Checking it " + self._toString(self.ReceiveData)) + # log.debug("[data receiver] Building PDU: Checking it " + toString(self.ReceiveData)) msgType = self.ReceiveData[1] if self.pmCurrentPDU.ignorechecksum or self._validatePDU(self.ReceiveData): # We've got a validated message - # log.debug("[data receiver] Building PDU: Got Validated PDU type 0x%02x data %s", int(msgType), self._toString(self.ReceiveData)) + # 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))) self._sendAck() # assume we need to send an ack for an unknown message @@ -2075,13 +2076,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(self._toString(self.ReceiveData), hex(a).upper())) + log.warning("[data receiver] PDU with CRC error Message = {0} checksum calcs {1}".format(toString(self.ReceiveData), hex(a).upper())) self._processCRCFailure() self._resetMessageData() elif self.pmIncomingPduLen == 0: if msgType in pmReceiveMsg_t: # 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(self._toString(self.ReceiveData), hex(a).upper())) + 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())) # Send an ack even though the its an invalid packet to prevent the panel getting confused if self.pmCurrentPDU.ackneeded: @@ -2093,10 +2094,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), self._toString(self.ReceiveData), hex(a).upper()) ) + 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()) ) 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(self._toString(self.ReceiveData), hex(a).upper())) + log.warning("[data receiver] Warning : Construction of incoming packet validation failed - Message = {0} checksum calcs {1}".format(toString(self.ReceiveData), hex(a).upper())) # Send an ack even though the its an invalid packet to prevent the panel getting confused if self.pmCurrentPDU.ackneeded: @@ -2108,12 +2109,12 @@ def _handle_received_byte(self, data): self._resetMessageData() elif pdu_len <= PACKET_MAX_SIZE: - # log.debug("[data receiver] Current PDU " + self._toString(self.ReceiveData) + " adding " + str(hex(data).upper())) + # log.debug("[data receiver] Current PDU " + toString(self.ReceiveData) + " adding " + str(hex(data).upper())) self.ReceiveData.append(data) else: - log.debug("[data receiver] Dumping Current PDU " + self._toString(self.ReceiveData)) + log.debug("[data receiver] Dumping Current PDU " + toString(self.ReceiveData)) self._resetMessageData() - # log.debug("[data receiver] Building PDU " + self._toString(self.ReceiveData)) + # log.debug("[data receiver] Building PDU " + toString(self.ReceiveData)) def _resetMessageData(self): # clear our buffer again so we can receive a new packet. @@ -2137,7 +2138,7 @@ def _processCRCFailure(self): def _processReceivedMessage(self, ackneeded, debugp, data): # Unknown Message has been received msgType = data[1] - # log.debug("[data receiver] *** Received validated message " + hex(msgType).upper() + " data " + self._toString(data)) + # log.debug("[data receiver] *** Received validated message " + hex(msgType).upper() + " data " + toString(data)) # Send an ACK if needed if ackneeded: # log.debug("[data receiver] Sending an ack as needed by last panel status message " + hex(msgType).upper()) @@ -2161,7 +2162,7 @@ def _processReceivedMessage(self, ackneeded, debugp, data): self.expectedResponseTimeout = 0 if debugp: - log.debug("[_processReceivedMessage] Received Packet %s", self._toString(data)) + log.debug("[_processReceivedMessage] Received Packet %s", toString(data)) # Handle the message if self.packet_callback is not None: @@ -2234,7 +2235,7 @@ async def _sendPdu(self, instruction: VisonicListEntry): for i in range(0, len(a)): data[s + i] = a[i] - # log.debug('[sendPdu] input data: %s', self._toString(packet)) + # log.debug('[sendPdu] input data: %s', toString(packet)) # First add header (PACKET_HEADER), then the packet, then crc and footer (PACKET_FOOTER) sData = b"\x0D" sData += data @@ -2273,12 +2274,12 @@ async def _sendPdu(self, instruction: VisonicListEntry): self.pmLastTransactionTime = self._getUTCTimeFunction() if command is not None and command.debugprint == FULL: - log.debug("[sendPdu] Sending Command ({0}) raw data {1} waiting for message response {2}".format(command.msg, self._toString(sData), [hex(no).upper() for no in self.pmExpectedResponse])) + log.debug("[sendPdu] Sending Command ({0}) raw data {1} waiting for message response {2}".format(command.msg, toString(sData), [hex(no).upper() for no in self.pmExpectedResponse])) elif command is not None and command.debugprint == CMD: log.debug("[sendPdu] Sending Command ({0}) waiting for message response {1}".format(command.msg, [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] Sending Raw Command raw data {0} waiting for message response {1}".format(self._toString(sData), [hex(no).upper() for no in self.pmExpectedResponse])) + log.debug("[sendPdu] Sending Raw Command raw data {0} waiting for message response {1}".format(toString(sData), [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] Sending Command ({0}) waiting for message response {1}".format(command.msg, [hex(no).upper() for no in self.pmExpectedResponse])) @@ -2294,9 +2295,9 @@ def _addMessageToSendList(self, message, options=[]): e = VisonicListEntry(command=m, options=options) self.SendList.append(e) - def _addPDUToSendList(self, m): + def _addPDUToSendList(self, m, res : list = None): if m is not None: - e = VisonicListEntry(raw=m) + e = VisonicListEntry(raw = m, res = res) self.SendList.append(e) @@ -2398,7 +2399,7 @@ def _startDownload(self): self.PanelMode = AlPanelMode.DOWNLOAD self.PanelState = AlPanelStatus.DOWNLOADING # Downloading self.triggeredDownload = True - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend elif self.pmDownloadComplete: log.debug("[StartDownload] Download has already completed (so not doing anything)") else: @@ -2526,7 +2527,7 @@ def _saveEPROMSettings(self, page, index, setting): #if len(self.pmRawSettings[page + i]) != 256: # log.debug("[Write Settings] OOOOOOOOOOOOOOOOOOOO len = {0}".format(len(self.pmRawSettings[page + i]))) # else: - # log.debug("[Write Settings] Page {0} is now {1}".format(page+i, self._toString(self.pmRawSettings[page + i]))) + # log.debug("[Write Settings] Page {0} is now {1}".format(page+i, toString(self.pmRawSettings[page + i]))) # _readEPROMSettingsPageIndex # This function retrieves the downloaded status and EEPROM data @@ -2542,7 +2543,7 @@ 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) " + self._toString(retval[:1])) + #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)) if not self.pmDownloadMode: @@ -2572,7 +2573,7 @@ def _dumpEPROMSettings(self): # if not (( p == 1 and j == 240 ) or (p == 2 and j == 0) or (p == 10 and j >= 140)): 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 = self._toString(self.pmRawSettings[p][j : j + 0x10]) + s = toString(self.pmRawSettings[p][j : j + 0x10]) log.debug("{0:3}:{1:3} {2}".format(p, j, s)) def _calcBoolFromIntMask(self, val, mask): @@ -2674,9 +2675,9 @@ def _createSensor(self, i, zoneInfo, sensor_type, motiondelaytime = None, part = sensorType = pmZoneSensorMaster_t[sensor_type].func sensorModel = pmZoneSensorMaster_t[sensor_type].name if motiondelaytime == 0xFFFF and (sensorType == AlSensorType.MOTION or sensorType == AlSensorType.CAMERA): - log.debug("[Process Settings] PowerMaster Sensor " + str(i) + " has no motion delay set (Sensor will only be useful when the panel is armed)") + log.debug("[_createSensor] PowerMaster Sensor " + str(i) + " has no motion delay set (Sensor will only be useful when the panel is armed)") else: - log.debug("[Process Settings] Found unknown sensor type " + hex(sensor_type)) + log.debug("[_createSensor] Found unknown sensor type " + hex(sensor_type)) else: # PowerMax models tmpid = sensor_type & 0x0F #sensorType = "UNKNOWN " + str(tmpid) @@ -2702,12 +2703,12 @@ def _createSensor(self, i, zoneInfo, sensor_type, motiondelaytime = None, part = # if tmpid in pmZoneSensorMaxGeneric_t: sensorType = pmZoneSensorMaxGeneric_t[tmpid] else: - log.debug("[Process Settings] Found unknown sensor type " + str(sensor_type)) + log.debug("[_createSensor] Found unknown sensor type " + str(sensor_type)) zoneType = zoneInfo & 0x0F zoneChime = (zoneInfo >> 4) & 0x03 - #log.debug("[Process Settings] Z{0:0>2} : sensor_type={1:0>2} zoneInfo={2:0>2} ZTypeName={3} Chime={4} SensorType={5} zoneName={6}".format( + #log.debug("[_createSensor] 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(), pmZoneType_t["EN"][zoneType], pmZoneChime_t["EN"][zoneChime], sensorType, zoneName)) if i in self.SensorList: @@ -2750,34 +2751,34 @@ def _processKeypadsAndSirens(self, pmPanelTypeNr) -> str: setting = self._lookupEprom(DecodePanelSettings["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("[Process Settings] Found an enrolled PowerMaster keypad {0}".format(i)) + log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMaster keypad {0}".format(i)) deviceStr = "{0},K2{1:0>2}".format(deviceStr, i) # Process siren settings setting = self._lookupEprom(DecodePanelSettings["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("[Process Settings] Found an enrolled PowerMaster siren {0}".format(i)) + log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMaster siren {0}".format(i)) deviceStr = "{0},S{1:0>2}".format(deviceStr, i) else: # Process keypad settings setting = self._lookupEprom(DecodePanelSettings["Keypad1PMax"]) for i in range(0, min(len(setting), keypad1wCnt)): if setting[i][0] != 0 or setting[i][1] != 0: - log.debug("[Process Settings] Found an enrolled PowerMax 1-way keypad {0}".format(i)) + log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMax 1-way keypad {0}".format(i)) deviceStr = "{0},K1{1:0>2}".format(deviceStr, i) setting = self._lookupEprom(DecodePanelSettings["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("[Process Settings] Found an enrolled PowerMax 2-way keypad {0}".format(i)) + log.debug("[_processKeypadsAndSirens] Found an enrolled PowerMax 2-way keypad {0}".format(i)) deviceStr = "{0},K2{1:0>2}".format(deviceStr, i) # Process siren settings setting = self._lookupEprom(DecodePanelSettings["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("[Process Settings] Found a PowerMax siren {0}".format(i)) + log.debug("[_processKeypadsAndSirens] Found a PowerMax siren {0}".format(i)) deviceStr = "{0},S{1:0>2}".format(deviceStr, i) return deviceStr[1:] @@ -2794,7 +2795,7 @@ def logEEPROMData(self, addToLog): if result is not None: if type(DecodePanelSettings[key].name) is str and len(result) == 1: if isinstance(result[0], (bytes, bytearray)): - tmpdata = self._toString(result[0]) + tmpdata = toString(result[0]) if addToLog: log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, DecodePanelSettings[key].name, tmpdata)) self.PanelStatus[DecodePanelSettings[key].name] = tmpdata @@ -2806,7 +2807,7 @@ def logEEPROMData(self, addToLog): elif type(DecodePanelSettings[key].name) is list and len(result) == len(DecodePanelSettings[key].name): for i in range(0, len(result)): if isinstance(result[0], (bytes, bytearray)): - tmpdata = self._toString(result[i]) + tmpdata = toString(result[i]) if addToLog: log.debug( "[Process Settings] {0:<18} {1:<40} {2}".format(key, DecodePanelSettings[key].name[i], tmpdata)) self.PanelStatus[DecodePanelSettings[key].name[i]] = tmpdata @@ -2819,7 +2820,7 @@ def logEEPROMData(self, addToLog): tmpdata = "" for i in range(0, len(result)): if isinstance(result[0], (bytes, bytearray)): - tmpdata = tmpdata + self._toString(result[i]) + ", " + tmpdata = tmpdata + toString(result[i]) + ", " else: tmpdata = tmpdata + str(result[i]) + ", " # there's at least 2 so this will not exception @@ -2904,7 +2905,7 @@ def _processEPROMSettings(self): # List of other sensors otherZoneStr = "" - # log.debug("[Process Settings] Panel Type Number " + str(self.PanelType) + " serial string " + self._toString(panelSerialType)) + # 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] #dummy_customCnt = pmPanelConfig_t["CFG_ZONECUSTOM"][self.PanelType] userCnt = pmPanelConfig_t["CFG_USERCODES"][self.PanelType] @@ -2947,15 +2948,15 @@ def _processEPROMSettings(self): self.pmPincode_t = uc self.pmGotUserCode = True #for i in range(0, userCnt): - # log.debug(f"[Process Settings] User {i} has code {self._toString(uc[i])}") + # log.debug(f"[Process Settings] User {i} has code {toString(uc[i])}") else: log.debug(f"[Process Settings] User code count is different {userCnt} != {len(uc)}") - #log.debug(f"[Process Settings] Installer Code {self._toString(self._lookupEpromSingle('installerCode'))}") - #log.debug(f"[Process Settings] Master DL Code {self._toString(self._lookupEpromSingle('masterDlCode'))}") + #log.debug(f"[Process Settings] Installer Code {toString(self._lookupEpromSingle('installerCode'))}") + #log.debug(f"[Process Settings] Master DL Code {toString(self._lookupEpromSingle('masterDlCode'))}") #if self.PowerMaster is not None and self.PowerMaster: - # log.debug(f"[Process Settings] Master Code {self._toString(self._lookupEpromSingle('masterCode'))}") - # log.debug(f"[Process Settings] Installer DL Code {self._toString(self._lookupEpromSingle('instalDlCode'))}") + # log.debug(f"[Process Settings] Master Code {toString(self._lookupEpromSingle('masterCode'))}") + # log.debug(f"[Process Settings] Installer DL Code {toString(self._lookupEpromSingle('instalDlCode'))}") # ------------------------------------------------------------------------------------------------------------------------------------------------ # Store partition info & check if partitions are on @@ -2966,7 +2967,7 @@ def _processEPROMSettings(self): if partition is None or partition[0] == 255: partitionCnt = 1 #else: - # log.debug("[Process Settings] Partition settings " + self._toString(partition)) + # log.debug("[Process Settings] Partition settings " + toString(partition)) # ------------------------------------------------------------------------------------------------------------------------------------------------ # Process zone settings @@ -2993,7 +2994,7 @@ def _processEPROMSettings(self): else: zoneNames = self._lookupEprom(DecodePanelSettings["ZoneNamePMax"]) zonesignalstrength = self._lookupEprom(DecodePanelSettings["ZoneSignalPMax"]) - #log.debug("[Process Settings] ZoneSignal " + self._toString(zonesignalstrength)) + #log.debug("[Process Settings] ZoneSignal " + toString(zonesignalstrength)) # These 2 get the same data block but they are structured differently @@ -3011,7 +3012,7 @@ def _processEPROMSettings(self): else: log.debug("[Process Settings] Zones: len settings {0} len zoneNames {1} zoneCnt {2}".format(len(pmax_zone_data), len(zoneNames), zoneCnt)) - #log.debug("[Process Settings] Zones Names Buffer : {0}".format(self._toString(zoneNames))) + #log.debug("[Process Settings] Zones Names Buffer : {0}".format(toString(zoneNames))) if len(pmax_zone_data) > 0 and len(pmaster_zone_data) > 0 and len(zoneNames) > 0: for i in range(0, zoneCnt): @@ -3027,9 +3028,9 @@ def _processEPROMSettings(self): if zoneEnrolled: #if self.PowerMaster: - # log.debug(f"[Process Settings] Zone data = {self._toString(pmaster_zone_ext_data[i])}") + # log.debug(f"[Process Settings] Zone data = {toString(pmaster_zone_ext_data[i])}") #else: - # log.debug(f"[Process Settings] Zone data = {self._toString(pmax_zone_data[i])}") + # log.debug(f"[Process Settings] Zone data = {toString(pmax_zone_data[i])}") sensor_type = int(pmaster_zone_ext_data[i][5]) if self.PowerMaster is not None and self.PowerMaster else int(pmax_zone_data[i][2]) motiondel = motiondelayarray[i][0] + (256 * motiondelayarray[i][1]) if self.PowerMaster is not None and self.PowerMaster else None @@ -3066,7 +3067,7 @@ def _processEPROMSettings(self): # ------------------------------------------------------------------------------------------------------------------------------------------------ # Process PGM/X10 settings - log.debug("[Process Settings] Processing X10 devices") + log.debug("[Process Settings] Processing X10 devices") s = [] s.append(self._lookupEprom(DecodePanelSettings["x10ByArmAway"])) # 0 = pgm, 1 = X01 @@ -3080,23 +3081,27 @@ def _processEPROMSettings(self): s.append(self._lookupEprom(DecodePanelSettings["x10ActZoneC"])) x10Names = self._lookupEprom(DecodePanelSettings["x10ZoneNames"]) # 0 = X01 + log.debug(f"[Process Settings] X10 device EPROM Name Data {toString(x10Names)}") for i in range(0, 16): - if i == 0 or x10Names[i - 1] != 0x1F: # python should process left to right, so i == 0 gets evaluated first. 0x1F is "Not Installed" in ZoneStringNames - for j in range(0, 9): - if i not in self.SwitchList and s[j][i] != 'Disable': - x10Location = "PGM" if i == 0 else pmZoneName_t[x10Names[i - 1] & 0x1F] - x10Type = "OnOff Switch" if i == 0 else "Dimmer Switch" - if i in self.SwitchList: - self.SwitchList[i].type = x10Type - self.SwitchList[i].location = x10Location - self.SwitchList[i].state = False - else: - self.SwitchList[i] = X10Device(type=x10Type, location=x10Location, id=i, enabled=True) - self.SwitchList[i].onChange(self.mySwitchChangeHandler) - if self.onNewSwitchHandler is not None: - self.onNewSwitchHandler(self.SwitchList[i]) - break # break the j loop and continue to the next i, we shouldn't need this (i not in self.SwitchList) in the if test but kept just in case! + x10Enabled = False + for j in range(0, 9): + x10Enabled = x10Enabled or s[j][i] != 'Disable' + + 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" + x10Type = "OnOff Switch" if i == 0 else "Dimmer Switch" # Assume PGM is onoff switch, all other devices are Dimmer Switches + if i in self.SwitchList: + self.SwitchList[i].type = x10Type + self.SwitchList[i].location = x10Location + self.SwitchList[i].state = False + else: + self.SwitchList[i] = X10Device(type=x10Type, location=x10Location, id=i, enabled=True) + self.SwitchList[i].onChange(self.mySwitchChangeHandler) + if self.onNewSwitchHandler is not None: + self.onNewSwitchHandler(self.SwitchList[i]) self.PanelStatus["Door Zones"] = doorZoneStr[1:] self.PanelStatus["Motion Zones"] = motionZoneStr[1:] @@ -3121,7 +3126,7 @@ def _processEPROMSettings(self): self.PanelMode = AlPanelMode.STANDARD_PLUS else: self.PanelMode = AlPanelMode.STANDARD - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend async def _postponeEventTimer(self): @@ -3130,7 +3135,8 @@ async def _postponeEventTimer(self): self.PostponeEventCounter = self.PostponeEventCounter - 1 #log.debug(f"[_postponeEventTimer] timer at {self.PostponeEventCounter}") if self.PostponeEventCounter == 0: - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + log.debug(f"[_postponeEventTimer] timer at {self.PostponeEventCounter}, sending panel update") + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend # This function handles a received message packet and processes it def _processReceivedPacket(self, packet): @@ -3152,11 +3158,11 @@ def statelist(): self.lastPacket = packet if self.lastPacketCounter == SAME_PACKET_ERROR: - log.debug("[_processReceivedPacket] Had the same packet for " + str(SAME_PACKET_ERROR) + " times in a row : %s", self._toString(packet)) + log.debug("[_processReceivedPacket] Had the same packet for " + str(SAME_PACKET_ERROR) + " times in a row : %s", toString(packet)) # _performDisconnect self._performDisconnect(reason="samepacketerror", exc="Same Packet for {0} times in a row".format(SAME_PACKET_ERROR)) # else: - # log.debug("[_processReceivedPacket] Parsing complete valid packet: %s", self._toString(packet)) + # log.debug("[_processReceivedPacket] Parsing complete valid packet: %s", toString(packet)) # Record all main variables to see if the message content changes any oldState = statelist() # make it a function so if it's changed it remains consistent @@ -3195,7 +3201,7 @@ def statelist(): 0xA0 : DecodeMessage( not self.pmDownloadMode , self.handle_msgtypeA0, False, None ), # Event log 0xA3 : DecodeMessage( True , self.handle_msgtypeA3, True, None ), # Zone Names 0xA6 : DecodeMessage( True , self.handle_msgtypeA6, True, None ), # Zone Types - 0xAB : DecodeMessage( not self.ForceStandardMode , self.handle_msgtypeAB, True, f"Received AB Message but we are in {self.PanelMode.name} mode (so I'm ignoring the message), data: {self._toString(packet)}"), # + 0xAB : DecodeMessage( not self.ForceStandardMode , self.handle_msgtypeAB, True, f"Received AB Message but we are in {self.PanelMode.name} mode (so I'm ignoring the message), data: {toString(packet)}"), # 0xAC : DecodeMessage( True , self.handle_msgtypeAC, True, None ), # X10 Names 0xAD : DecodeMessage( True , self.handle_msgtypeAD, True, None ), # No idea what this means, it might ... send it just before transferring F4 video data ????? 0xB0 : DecodeMessage( not self.pmDownloadMode , self.handle_msgtypeB0, True, None ), # @@ -3204,7 +3210,7 @@ def statelist(): } if len(packet) < 4: # there must at least be a header, command, checksum and footer - log.warning("[_processReceivedPacket] Received invalid packet structure, not processing it " + self._toString(packet)) + log.warning("[_processReceivedPacket] Received invalid packet structure, not processing it " + toString(packet)) elif packet[1] in _decodeMessageFunction: dm = _decodeMessageFunction[packet[1]] if dm.condition: @@ -3233,12 +3239,12 @@ def statelist(): if self.PostponeEventCounter > 0: # stop the time sending it as we've received an A7 before the timer triggered self.PostponeEventCounter = 0 - oldState = [False] # Force the sending of a AlCondition.PANEL_UPDATE further down + oldState = [False] # Force the sending of a AlCondition.PUSH_CHANGE further down else: - log.debug("[_processReceivedPacket] Unknown/Unhandled packet type " + self._toString(packet)) + log.debug("[_processReceivedPacket] Unknown/Unhandled packet type " + toString(packet)) if self.PostponeEventCounter == 0 and oldState != statelist(): # make statelist a function so if it's changed it remains consistent - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend elif oldPowerMaster != self.PowerMaster or pushchange: self.sendPanelUpdate(AlCondition.PUSH_CHANGE) @@ -3296,7 +3302,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(self._toString(data))) + #log.debug("[handle_msgtype02] Ack Received data = {0}".format(toString(data))) if not self.pmPowerlinkMode and len(data) > 0: if data[0] == 0x43: # Received a powerlink acknowledge #log.debug("[handle_msgtype02] Received a powerlink acknowledge, I am in {0} mode".format(self.PanelMode.name)) @@ -3325,7 +3331,7 @@ def _delayDownload(self): def handle_msgtype06(self, data): """MsgType=06 - Time out Timeout message from the PM, most likely we are/were in download mode""" - log.debug("[handle_msgtype06] Timeout Received data {0}".format(self._toString(data))) + log.debug("[handle_msgtype06] Timeout Received") # Clear the expected response to ensure that pending messages are sent self.pmExpectedResponse = [] @@ -3338,7 +3344,7 @@ 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(self._toString(data))) + log.debug("[handle_msgtype07] No idea what this message means, data = {0}".format(toString(data))) # Clear the expected response to ensure that pending messages are sent self.pmExpectedResponse = [] # Assume that we need to send an ack @@ -3346,11 +3352,11 @@ def handle_msgtype07(self, data): self._sendAck() def handle_msgtype08(self, data): - log.debug("[handle_msgtype08] Access Denied len {0} data {1}".format(len(data), self._toString(data))) + log.debug("[handle_msgtype08] Access Denied len {0} data {1}".format(len(data), toString(data))) if self._getLastSentMessage() is not None: lastCommandData = self._getLastSentMessage().command.data - log.debug("[handle_msgtype08] last command {0}".format(self._toString(lastCommandData))) + log.debug("[handle_msgtype08] last command {0}".format(toString(lastCommandData))) self._reset_watchdog_timeout() if lastCommandData is not None: self.pmExpectedResponse = [] ## really we should look at the response from the last command and only remove the appropriate responses from this list @@ -3390,14 +3396,14 @@ def handle_msgtype08(self, data): def handle_msgtype0B(self, data): # LOOPBACK TEST SUCCESS, STOP COMMAND (0x0B) IS THE FIRST COMMAND SENT TO THE PANEL WHEN THIS INTEGRATION STARTS """ Handle STOP from the panel """ - log.debug("[handle_msgtype0B] Stop data is {0}".format(self._toString(data))) + log.debug("[handle_msgtype0B] Stop data is {0}".format(toString(data))) self.loopbackTest = True self.loopbackCounter = self.loopbackCounter + 1 log.warning("[handle_msgtype0B] LOOPBACK TEST SUCCESS, Counter is {0}".format(self.loopbackCounter)) def handle_msgtype0F(self, data): # EXIT """ Handle STOP from the panel """ - log.debug("[handle_msgtype0F] Exit data is {0}".format(self._toString(data))) + log.debug("[handle_msgtype0F] Exit data is {0}".format(toString(data))) # This is sent by the panel during download to tell us to stop the download if self.pmDownloadMode: self._delayDownload() @@ -3409,7 +3415,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, self._toString(data))) + log.debug("[handle_msgtype25] Download Retry, have to wait {0} seconds data is {1}".format(iDelay, toString(data))) self._delayDownload() def handle_msgtype33(self, data): @@ -3418,7 +3424,7 @@ def handle_msgtype33(self, data): if len(data) != 10: log.debug("[handle_msgtype33] ERROR: MSGTYPE=0x33 Expected len=14, Received={0}".format(len(data))) - log.debug("[handle_msgtype33] " + self._toString(data)) + log.debug("[handle_msgtype33] " + toString(data)) return # Data Format is: <8 data bytes> @@ -3426,7 +3432,7 @@ def handle_msgtype33(self, data): iIndex = data[0] iPage = data[1] - # log.debug("[handle_msgtype33] Getting Data " + self._toString(data) + " page " + hex(iPage) + " index " + hex(iIndex)) + # log.debug("[handle_msgtype33] Getting Data " + toString(data) + " page " + hex(iPage) + " index " + hex(iIndex)) # Write to memory map structure, but remove the first 2 bytes from the data self._saveEPROMSettings(iPage, iIndex, data[2:]) @@ -3436,13 +3442,11 @@ def handle_msgtype3C(self, data): # Panel Info Messsage when start the download 5=PanelType e.g. PowerMax, PowerMaster 4=Sub model type of the panel - just informational, not used """ - log.debug("[handle_msgtype3C] Packet = {0}".format(self._toString(data))) + log.debug("[handle_msgtype3C] Packet = {0}".format(toString(data))) firsttime = self.PowerMaster is None self.ModelType = data[4] - #self.PanelType = data[5] - self._setDataFromPanelType(data[5]) if self.DownloadCode == DEFAULT_DL_CODE: @@ -3454,12 +3458,19 @@ def handle_msgtype3C(self, data): # Panel Info Messsage when start the download log.debug("[handle_msgtype3C] PanelType={0} : {2} , Model={1} Powermaster {3}".format(self.PanelType, self.ModelType, self.PanelModel, self.PowerMaster)) - if self.PowerMaster and self.ForceStandardMode: - # log.debug("[handle_msgtype3C] Queueing Powermaster Zone Names, Types and try to get all sensors") - self._updateSensorNamesAndTypes(firsttime) +# if self.PowerMaster and self.ForceStandardMode: +# # log.debug("[handle_msgtype3C] Queueing Powermaster Zone Names, Types and try to get all sensors") +# self._sendDataRequest() +# self._updateSensorNamesAndTypes(firsttime) self.pmGotPanelDetails = True - if not self.ForceStandardMode: + if self.ForceStandardMode: + self._sendCommand("MSG_EXIT") # when we receive a 3C we know that the panel is in download mode, so exit download mode + if self.PowerMaster is not None and self.PowerMaster: + log.debug("[handle_msgtype3C] Adding to wanted list") + #self.B0_Message_Wanted.update([0x20, 0x21, 0x2d, 0x1f, 0x07, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x11, 0x13, 0x14, 0x15, 0x18, 0x1a, 0x19, 0x1b, 0x1d, 0x2f, 0x31, 0x33, 0x1e, 0x24, 0x02, 0x23, 0x3a, 0x4b]) + self.B0_Message_Wanted.update([0x2D, 0x21, 0x1f, 0x24, 0x4b]) + else: # We got a first response, now we can Download the panel EEPROM settings interval = self._getUTCTimeFunction() - self.lastSendOfDownloadEprom td = timedelta(seconds=DOWNLOAD_RETRY_DELAY) # prevent multiple requests for the EEPROM panel settings, at least DOWNLOAD_RETRY_DELAY seconds @@ -3498,13 +3509,13 @@ def handle_msgtype3F(self, data): # 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] " + self._toString(data)) + # log.debug("[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:]) if self.pmDownloadRetryCount < DOWNLOAD_RETRY_COUNT and iLength != len(data) - 3: # 3 because --> index & page & length log.warning("[handle_msgtype3F] Invalid data block length, Received: {0}, Expected: {1} Adding page {2} Index {3} to the end of the list".format(len(data)-3, iLength, iPage, iIndex)) - log.warning("[handle_msgtype3F] " + self._toString(data)) + log.warning("[handle_msgtype3F] " + toString(data)) # Add it back on to the end to re-download it repeatDownloadCommand = bytearray(4) repeatDownloadCommand[0] = iIndex @@ -3527,7 +3538,7 @@ def handle_msgtype3F(self, data): self.pmDownloadComplete = True if self._getLastSentMessage() is not None: lastCommandData = self._getLastSentMessage().command.data - #log.debug("[handle_msgtype3F] Last Command {0}".format(self._toString(lastCommandData))) + #log.debug("[handle_msgtype3F] Last Command {0}".format(toString(lastCommandData))) if lastCommandData is not None: if lastCommandData[0] == 0x3E: # Download Data log.debug("[handle_msgtype3F] Powerlink Mode Pending") @@ -3543,7 +3554,7 @@ def handle_msgtype3F(self, data): year = t.year - 2000 values = [t.second, t.minute, t.hour, t.day, t.month, year] timePdu = bytearray(values) - log.debug("[handle_msgtype3F] Setting Time " + self._toString(timePdu)) + log.debug("[handle_msgtype3F] Setting Time " + toString(timePdu)) self._sendCommand("MSG_SETTIME", options=[ [3, timePdu] ]) else: log.debug("[handle_msgtype3F] Please correct your local time.") @@ -3634,7 +3645,7 @@ def handle_msgtypeA0(self, data): def handle_msgtypeA3(self, data): """ MsgType=A3 - Zone Names """ - log.debug("[handle_MsgTypeA3] Packet = {0}".format(self._toString(data))) + log.debug("[handle_MsgTypeA3] Packet = {0}".format(toString(data))) msgCnt = int(data[0]) offset = 8 * (int(data[1]) - 1) log.debug(" Message Count is {0} offset={1} self.pmPowerlinkMode={2}".format( msgCnt, offset, self.pmPowerlinkMode )) @@ -3864,7 +3875,7 @@ def handle_msgtypeA5(self, data): # Status Message # msgTot = data[0] eventType = data[1] - #log.debug("[handle_msgtypeA5] Parsing A5 packet " + self._toString(data)) + #log.debug("[handle_msgtypeA5] Parsing A5 packet " + toString(data)) if self.sensorsCreated and eventType == 0x01: # Zone alarm status log.debug("[handle_msgtypeA5] Zone Alarm Status") @@ -3947,7 +3958,6 @@ def handle_msgtypeA5(self, data): # Status Message elif eventType == 0x04: # Zone event # Assume that every zone event causes the need to push a change to the sensors etc - if not self.pmPowerlinkMode: #log.debug("[handle_msgtypeA5] Got A5 04 message, resetting watchdog") self._reset_watchdog_timeout() @@ -3969,6 +3979,11 @@ def handle_msgtypeA5(self, data): # Status Message x10stat2 = data[9] self.ProcessX10StateUpdate(x10status=x10stat1 + (x10stat2 * 0x100)) +# elif eventType == 0x05: # +# # 0d a5 10 05 00 00 00 00 00 00 12 34 43 bc 0a +# # Might be a coincidence but the "1st Account No" is set to 001234 +# pass + elif eventType == 0x06: # Status message enrolled/bypassed val = self._makeInt(data[2:6]) @@ -4010,11 +4025,11 @@ def handle_msgtypeA5(self, data): # Status Message self.SensorList[i].pushChange(AlSensorCondition.RESET) # else: - # log.debug("[handle_msgtypeA5] Unknown A5 Message: " + self._toString(data)) + # log.debug("[handle_msgtypeA5] Unknown A5 Message: " + toString(data)) def handle_msgtypeA6(self, data): """ MsgType=A6 - Zone Types """ - log.debug("[handle_MsgTypeA6] Packet = {0}".format(self._toString(data))) + log.debug("[handle_MsgTypeA6] Packet = {0}".format(toString(data))) msgCnt = int(data[0]) offset = 8 * (int(data[1]) - 1) log.debug(" Message Count is {0} offset={1} self.pmPowerlinkMode={2}".format( msgCnt, offset, self.pmPowerlinkMode )) @@ -4033,7 +4048,7 @@ def handle_msgtypeA6(self, data): # self.pmForceArmSetInPanel def handle_msgtypeA7(self, data): """ MsgType=A7 - Panel Status Change """ - log.debug("[handle_msgtypeA7] Panel Status Change " + self._toString(data)) + log.debug("[handle_msgtypeA7] Panel Status Change " + toString(data)) # # In a log file I reveiced from pocket, there was this A7 message 0d a7 ff fc 00 60 00 ff 00 0c 00 00 43 45 0a # In a log file I reveiced from UffeNisse, there was this A7 message 0d a7 ff 64 00 60 00 ff 00 0c 00 00 43 45 0a msgCnt is 0xFF and temp is 0x64 ???? @@ -4048,9 +4063,9 @@ def handle_msgtypeA7(self, data): msgCnt = 1 if msgCnt > 4: - log.warning("[handle_msgtypeA7] A7 message contains too many messages to process : {0} data={1}".format(msgCnt, self._toString(data))) + log.warning("[handle_msgtypeA7] A7 message contains too many messages to process : {0} data={1}".format(msgCnt, toString(data))) if msgCnt <= 0: - log.warning("[handle_msgtypeA7] A7 message contains no messages to process : {0} data={1}".format(msgCnt, self._toString(data))) + log.warning("[handle_msgtypeA7] A7 message contains no messages to process : {0} data={1}".format(msgCnt, toString(data))) else: ## message count 1 to 4 log.debug("[handle_msgtypeA7] A7 message contains {0} messages".format(msgCnt)) @@ -4094,10 +4109,10 @@ def handle_msgtypeA7(self, data): modeStr = pmLogEvent_t[self.pmLang][eventType] or "Unknown" - dictType.insert(0, eventType) - dictMode.insert(0, modeStr) - dictEvent.insert(0, eventZone) - dictName.insert(0, zoneStr) + dictType.append(eventType) + dictMode.append(modeStr) + dictEvent.append(eventZone) + dictName.append(zoneStr) # set the sensor data #if eventZone >= 1 and eventZone <= zoneCnt: @@ -4176,9 +4191,9 @@ def handle_msgtypeA7(self, data): # pass if len(dictType) > 0: - log.debug(f"[handle_msgtypeA7] setLastPanelEventData {len(dictType)}") + log.debug(f"[handle_msgtypeA7] setLastPanelEventData, event length = {len(dictType)}") # count=0, type=[], event=[], mode=[], name=[] - self.setLastPanelEventData(count=len(dictType), type=dictType, zonemode=dictMode, event=dictEvent, name=dictName) + self.setLastPanelEventData(count=len(dictType), type=dictType, event=dictEvent, zonemode=dictMode, name=dictName) self.PanelTamper = PanelTamper # reset=False @@ -4190,7 +4205,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(self._toString(data))) + #log.debug("[handle_msgtypeAB] data {0}".format(toString(data))) # Restart the timer self._reset_watchdog_timeout() @@ -4282,11 +4297,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(self._toString(data))) + log.debug("[handle_msgtypeAC] data {0}".format(toString(data))) def handle_msgtypeAD(self, data): # PowerLink Message """ MsgType=AD - Panel Powerlink Messages """ - log.debug("[handle_msgtypeAD] data {0}".format(self._toString(data))) + log.debug("[handle_msgtypeAD] data {0}".format(toString(data))) #if data[2] == 0x00: # the request was accepted by the panel # self._sendCommand("MSG_NO_IDEA") @@ -4321,10 +4336,8 @@ def _decode_4B(self, sensor, data): self.SensorList[sensor].statuslog = pmtime else: - log.debug(f"[handle_msgtypeB0] Sensor Not Updated as Timestamp the same = {sensor:>2} code {code} time {pmtime}") - - - + log.debug(f"[handle_msgtypeB0] Sensor {sensor:>2} Not Updated as Timestamp the same = code {code} sensor time {pmtime} {self.SensorList[sensor].statuslog}") + # Only Powermasters send this message def handle_msgtypeB0(self, data): # PowerMaster Message """ MsgType=B0 - Panel PowerMaster Message """ @@ -4337,13 +4350,14 @@ def handle_msgtypeB0(self, data): # PowerMaster Message # Whether to process the experimental code (and associated B0 message data) or not # If the "if" statement below includes this variable then I'm still trying to work out what the message data means + experimental = True beezerodebug = True dontprint = [0x0306, 0x0335] command = (msgType << 8) | subType if command not in dontprint: - log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, self._toString(data))) + log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, toString(data))) # A powermaster mainly interacts with B0 messages so reset watchdog on receipt self._reset_watchdog_timeout() @@ -4384,7 +4398,7 @@ def handle_msgtypeB0(self, data): # PowerMaster Message self.save0306 = data[3] log.debug("[handle_msgtypeB0] Received Updated 0x0306 Byte as Data {0} counter {1}".format(data[3], data[4])) else: - log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, self._toString(data))) + log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, toString(data))) elif experimental and msgType == 0x03 and subType == 0x07: # Received PowerMaster10 message 3/7 (len = 35) data = 03 07 23 ff 08 03 1e 03 00 00 03 00 00 <24 * 00> 0d 43 @@ -4616,7 +4630,7 @@ def handle_msgtypeB0(self, data): # PowerMaster Message # s = data[10:26] # log.debug("[handle_msgtypeB0] Data 3E {0}".format(s.decode())) else: - log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, self._toString(data))) + log.debug("[handle_msgtypeB0] Received {0} message {1}/{2} (len = {3}) data = {4}".format(self.PanelModel or "UNKNOWN", msgType, subType, msgLen, toString(data))) elif msgType == 0x03 and subType == 0x39: # 03 39 06 ff 08 ff 01 02 11 43 @@ -4713,7 +4727,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(self._toString(data))) + #log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) # 0 - message type ==> 3=start, 5=data # 1 - always 0 @@ -4726,7 +4740,7 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image pushchange = False if msgtype == 0x03: # JPG Header - log.debug("[handle_msgtypeF4] data {0}".format(self._toString(data))) + log.debug("[handle_msgtypeF4] data {0}".format(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] @@ -4864,13 +4878,13 @@ def handle_msgtypeF4(self, data) -> bool: # Static JPG Image self.ImageManager.terminateImage() elif msgtype == 0x01: - log.debug("[handle_msgtypeF4] data {0}".format(self._toString(data))) - #log.debug("[handle_msgtypeF4] data {0}".format(self._toString(data))) + log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) + #log.debug("[handle_msgtypeF4] data {0}".format(toString(data))) log.debug(f"[handle_msgtypeF4] Message Type not processed") pushchange = True else: - log.debug("[handle_msgtypeF4] not seen data {0}".format(self._toString(data))) + log.debug("[handle_msgtypeF4] not seen data {0}".format(toString(data))) log.debug(f"[handle_msgtypeF4] Message Type not processed") return pushchange @@ -4917,7 +4931,7 @@ def __init__(self, *args, **kwargs) -> None: def shutdownOperation(self): super().shutdownOperation() - self.sendPanelUpdate(AlCondition.PANEL_UPDATE) # push through a panel update to the HA Frontend + self.sendPanelUpdate(AlCondition.PUSH_CHANGE) # push through a panel update to the HA Frontend # A dictionary that is used to add to the attribute list of the Alarm Control Panel # If this is overridden then please include the items in the dictionary defined here by using super() @@ -5050,7 +5064,7 @@ def setSensorBypassState(self, sensor : int, bypassValue : bool, pin : str = "") #log.debug("[SensorArmState] setSensorBypassState A " + hex(bypassint)) y1, y2, y3, y4 = (bypassint & 0xFFFFFFFF).to_bytes(4, "little") bypass = bytearray([y1, y2, y3, y4]) - log.debug("[SensorArmState] setSensorBypassState bypass = " + self._toString(bypass)) + log.debug("[SensorArmState] setSensorBypassState bypass = " + toString(bypass)) if len(bypass) == 4: if bypassValue: self._addMessageToSendList("MSG_BYPASSEN", options=[ [1, bpin], [3, bypass] ])