########################################################################### # Copyright (c) 2018- Franco (nextime) Lanza # # Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom] # # This file is part of penguidom. # # penguidom is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################################## from mapping import EVENTMAP, REGISTERS import ctypes c_uint8 = ctypes.c_uint8 class LowNibble_bits( ctypes.LittleEndianStructure ): _fields_ = [ ("evt_disable", c_uint8, 1 ), ("alarm", c_uint8, 1 ), ("winload", c_uint8, 1 ), ("neware", c_uint8, 1 ), ] class Flags( ctypes.Union ): _anonymous_ = ("bit",) _fields_ = [ ("bit", LowNibble_bits ), ("asByte", c_uint8 ) ] """ flags = Flags() flags.asByte = 0xe2 print( "event_disable: %i" % flags.evt_disable ) print( "alarm: %i" % flags.alarm ) print( "winload : %i" % flags.winload ) print( "neware : %i" % flags.neware ) """ CMD_EVENT = 0xe # This is the only message type we fully know how to parse, and lucky enough # they are also really what you need to have. # Byte Value Description # 00 0xEX High Nibble : Command # Low Nibble : Bit 3 - Event Report Enable (0) / Disable (1) # Bit 2 - System in alarm # Bit 1 - Winload connected # Bit 0 - NEware connected # 01 0xXX Century # 02 0xXX Year # 03 0xXX Month # 04 0xXX Day # 05 0xXX Hour # 06 0xXX Minute # 07 0xXX Event Group Number # 08 0xXX Event Sub Group Number # 09 0xXX Partition Number # 10 0xXX Module Serial Number (Digit 1 and 2) # 11 0xXX Module Serial Number (Digit 3 and 4) # 12 0xXX Module Serial Number (Digit 5 and 6) # 13 0xXX Module Serial Number (Digit 6 and 8) # 14 0xXX Label Type # 0 = Zone Label # 1 = User Label # 2 = Partition Label # 3 = PGM Label # 4 = Bus Module Label # 5 = Wireless Repeater Label # 6 = Wireless Keypad Label # 15 0xXX Module / User / Partition Label Byte 00 # 16 0xXX Module / User / Partition Label Byte 01 # 17 0xXX Module / User / Partition Label Byte 02 # 18 0xXX Module / User / Partition Label Byte 03 # 19 0xXX Module / User / Partition Label Byte 04 # 20 0xXX Module / User / Partition Label Byte 05 # 21 0xXX Module / User / Partition Label Byte 06 # 22 0xXX Module / User / Partition Label Byte 07 # 23 0xXX Module / User / Partition Label Byte 08 # 24 0xXX Module / User / Partition Label Byte 09 # 25 0xXX Module / User / Partition Label Byte 10 # 26 0xXX Module / User / Partition Label Byte 11 # 27 0xXX Module / User / Partition Label Byte 12 # 28 0xXX Module / User / Partition Label Byte 13 # 29 0xXX Module / User / Partition Label Byte 14 # 30 0xXX Module / User / Partition Label Byte 15 # 31 0xXX Not Used (Reserved) # 32 0xXX Not Used (Reserved) # 33 0xXX Not Used (Reserved) # 34 0xXX Not Used (Reserved) # 35 0xXX Not Used (Reserved) # 36 0xXX Checksum def checkCmd(msg, cmd, nibble=False): if isinstance(cmd, str): if isinstance(msg, str): if len(msg)>=len(cmd): return msg[:len(cmd)] == cmd return False if isinstance(msg, str): msg=ord(msg[0]) if nibble: return (msg >> 4) == cmd return msg == cmd # XXX I don't really like i don't fully understand # the meaning of some messages (yet?), but those are needed and i know # what the give me back at least. # # To save computational timing there is no need to format those messages # at runtime, as they are static, so, full string included here. MSG_CONNECT= '\x72\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72' MSG_DISCONNECT= '\x70\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x76' MSG_GETSTATUS= '\x50\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD0' MSG_GETALARMSTATUS= '\x50\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD1' MSG_SYNC= '\x5F\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7F' MSG_UNKWNOWN_HS1= '\x50\x00\x1F\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4F' MSG_UNKWNOWN_HS2= '\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50' # Command sent starts with SEND_QUERY = '\x50' SEND_CONTROL = '\x40' # Expected replies command REPLY_CONNECT=0x7 REPLY_GETSTATUS=[0x5, 0x7] REPLY_SYNC=0x0 REPLY_QUERY=0x5 def checkSumCalc(message): checksum = 0 return chr(sum(map(ord, message)) % 256) def checkSumTest(message): if len(message) == 37: if message[36] == checkSumCalc(message[:36]): return True return False def format37ByteMessage(message): checksum = 0 message = message.ljust(36, '\x00') if len(message) % 37 != 0: message += checkSumCalc(message) # Add check to end of message return message def getPanelName(message): return str(message[28:36]).strip('\x00') ARM = REGISTERS.ARM DISARM = REGISTERS.DISARM STAY = REGISTERS.STAY SLEEP = REGISTERS.SLEEP PGM_ON = REGISTERS.ON PGM_OFF = REGISTERS.OFF #PGM_CHANGE = REGISTERS.CHANGE