From 715e1dc6c20a7d72ef1f42576e34ef1b746209a3 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 10 Aug 2021 22:42:05 +0200 Subject: [PATCH 1/8] added support for esp2 based protocol messages based on the work of stoney --- enocean/communicators/communicator.py | 12 +- enocean/communicators/serialcommunicator.py | 6 +- enocean/communicators/tcpcommunicator.py | 6 +- enocean/protocol/constants.py | 19 ++ enocean/protocol/crc8.py | 9 +- enocean/protocol/esp2_packet.py | 223 ++++++++++++++++++ enocean/protocol/esp3_packet.py | 193 +++++++++++++++ enocean/protocol/packet.py | 174 +------------- enocean/protocol/tests/test_eep.py | 27 ++- enocean/protocol/tests/test_packet.py | 11 +- .../protocol/tests/test_packet_creation.py | 41 ++-- enocean/protocol/tests/test_teachin.py | 3 +- examples/enocean_example.py | 3 +- examples/example_D2-05-00.py | 3 +- examples/example_DO21-11B-E.py | 3 +- 15 files changed, 513 insertions(+), 220 deletions(-) create mode 100644 enocean/protocol/esp2_packet.py create mode 100644 enocean/protocol/esp3_packet.py diff --git a/enocean/communicators/communicator.py b/enocean/communicators/communicator.py index 9aff7c0..6689d1a 100644 --- a/enocean/communicators/communicator.py +++ b/enocean/communicators/communicator.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.esp2_packet import ESP2Packet +from enocean.protocol.esp3_packet import ESP3Packet import logging import datetime @@ -11,6 +13,11 @@ from enocean.protocol.packet import Packet, UTETeachInPacket from enocean.protocol.constants import PACKET, PARSE_RESULT, RETURN_CODE +from enum import Enum + +class ESP_Version(Enum): + ESP2 = 2 + ESP3 = 3 class Communicator(threading.Thread): ''' @@ -19,7 +26,7 @@ class Communicator(threading.Thread): ''' logger = logging.getLogger('enocean.communicators.Communicator') - def __init__(self, callback=None, teach_in=True): + def __init__(self, version: ESP_Version=ESP_Version.ESP3, callback=None, teach_in=True): super(Communicator, self).__init__() # Create an event to stop the thread self._stop_flag = threading.Event() @@ -35,6 +42,7 @@ def __init__(self, callback=None, teach_in=True): # Should new messages be learned automatically? Defaults to True. # TODO: Not sure if we should use CO_WR_LEARNMODE?? self.teach_in = teach_in + self._version = version def _get_from_send_queue(self): ''' Get message from send queue, if one exists ''' @@ -61,7 +69,7 @@ def parse(self): ''' Parses messages and puts them to receive queue ''' # Loop while we get new messages while True: - status, self._buffer, packet = Packet.parse_msg(self._buffer) + status, self._buffer, packet = ESP3Packet.parse_msg(self._buffer) if self._version == ESP_Version.ESP3 else ESP2Packet.parse_msg(self._buffer) # If message is incomplete -> break the loop if status == PARSE_RESULT.INCOMPLETE: return status diff --git a/enocean/communicators/serialcommunicator.py b/enocean/communicators/serialcommunicator.py index ffe4201..fc54214 100644 --- a/enocean/communicators/serialcommunicator.py +++ b/enocean/communicators/serialcommunicator.py @@ -4,15 +4,15 @@ import serial import time -from enocean.communicators.communicator import Communicator +from enocean.communicators.communicator import Communicator, ESP_Version class SerialCommunicator(Communicator): ''' Serial port communicator class for EnOcean radio ''' logger = logging.getLogger('enocean.communicators.SerialCommunicator') - def __init__(self, port='/dev/ttyAMA0', callback=None): - super(SerialCommunicator, self).__init__(callback) + def __init__(self, version: ESP_Version=ESP_Version.ESP3, port='/dev/ttyAMA0', callback=None): + super(SerialCommunicator, self).__init__(version = version, callback = callback) # Initialize serial port self.__ser = serial.Serial(port, 57600, timeout=0.1) diff --git a/enocean/communicators/tcpcommunicator.py b/enocean/communicators/tcpcommunicator.py index 239012d..7379c00 100644 --- a/enocean/communicators/tcpcommunicator.py +++ b/enocean/communicators/tcpcommunicator.py @@ -3,15 +3,15 @@ import logging import socket -from enocean.communicators.communicator import Communicator +from enocean.communicators.communicator import Communicator, ESP_Version class TCPCommunicator(Communicator): ''' Socket communicator class for EnOcean radio ''' logger = logging.getLogger('enocean.communicators.TCPCommunicator') - def __init__(self, host='', port=9637): - super(TCPCommunicator, self).__init__() + def __init__(self, version: ESP_Version=ESP_Version.ESP3, host='', port=9637): + super(TCPCommunicator, self).__init__(version = version) self.host = host self.port = port diff --git a/enocean/protocol/constants.py b/enocean/protocol/constants.py index 8dd3e74..f659195 100644 --- a/enocean/protocol/constants.py +++ b/enocean/protocol/constants.py @@ -34,6 +34,25 @@ class RETURN_CODE(IntEnum): WRONG_PARAM = 0x03 OPERATION_DENIED = 0x04 +# ESP2 ORG EnOcean_Equipment_Profiles_2.0.pdf +class ORG(IntEnum): + BS4 = 0x07 + BS1 = 0x06 + RPS = 0x05 + +#ESP2 Message Status Field EnOcean_Equipment_Profiles_2.0.pdf +class MSGSTATUS(IntEnum): + #RPS definitions + T2Msg = 0x20 + NMsg = 0x10 + T2NMsg = 0x30 + T2UMsg = 0x20 + T1NMsg = 0x10 + T1UMsg = 0x00 + #BS4 definitions + BS4 = 0x00 #must be zero in BS4 messages with RP count of 0 + #BS1 definitions + BS1 = 0x00 #must be zero in BS1 too # EnOceanSerialProtocol3.pdf / 20 class EVENT_CODE(IntEnum): diff --git a/enocean/protocol/crc8.py b/enocean/protocol/crc8.py index 5dd9b9d..8925d6c 100644 --- a/enocean/protocol/crc8.py +++ b/enocean/protocol/crc8.py @@ -34,8 +34,15 @@ 0xfa, 0xfd, 0xf4, 0xf3) -def calc(msg): +def calc_ESP3(msg): checksum = 0 for byte in msg: checksum = CRC_TABLE[checksum & 0xFF ^ byte & 0xFF] return checksum + +def calc_ESP2(msg): + checksum=0 + for byte in msg: + checksum += byte + checksum &= 0xFF + return checksum \ No newline at end of file diff --git a/enocean/protocol/esp2_packet.py b/enocean/protocol/esp2_packet.py new file mode 100644 index 0000000..145851d --- /dev/null +++ b/enocean/protocol/esp2_packet.py @@ -0,0 +1,223 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.packet import EventPacket, Packet, RadioPacket, ResponsePacket, UTETeachInPacket +import logging +from collections import OrderedDict + +import enocean.utils +from enocean.protocol import crc8 +from enocean.protocol.eep import EEP +from enocean.protocol.constants import ORG, PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 + + +class ESP2Packet(Packet): + ''' + Base class for ESP2 Packet. + Mainly used for for packet generation and + Packet.parse_msg(buf) for parsing message. + parse_msg() returns subclass, if one is defined for the data type. + ''' + + @staticmethod + def parse_msg(buf): + ''' + Parses message from buffer. + returns: + - PARSE_RESULT + - remaining buffer + - Packet -object (if message was valid, else None) + ''' + # If the buffer doesn't contain 0xA5 (start char) + # the message isn't needed -> ignore + # if 0xA5 not in buf: + # return PARSE_RESULT.INCOMPLETE, [], None + # else: + # if buf[list(buf).index(0xA5)+1] != 0x5A: + # return PARSE_RESULT.INCOMPLETE, [], None + + if not buf: + return PARSE_RESULT.INCOMPLETE, [], None + + if 0xA5 not in buf: + return PARSE_RESULT.INCOMPLETE, [], None + + msg_len = 0 + + for index,value in enumerate(buf): + try: + if buf[index] == 0xA5 and buf[index+1] == 0x5A: + data_len = buf[index+2] & 0x1F + HSEQ = buf[index+2] >> 5 + opt_len = 0 + msg_len = data_len + if len(buf[index+2:]) < msg_len: + # If buffer isn't long enough, the message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + crcval = crc8.calc_ESP2(buf[index+2:index+2+data_len]) + if buf[index+2+data_len] == crcval: + buf = buf[index+2:] + break + else: + ESP2Packet.logger.error('Data CRC error!') + return PARSE_RESULT.CRC_MISMATCH, buf, None + except IndexError: + # If the fields don't exist, message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + if msg_len == 0: + ESP2Packet.logger.error('Data Length is Zero!') + return PARSE_RESULT.INCOMPLETE, [], None + + msg = buf[0:msg_len] + buf = buf[msg_len+1:] + + packet_type = HSEQ + data = msg[1:data_len] + opt_data = [] + + #Adopt ORG to RORG for ESP2 + if data[0] == ORG.BS1: + data[0] = RORG.BS1 + if data[0] == ORG.BS4: + data[0] = RORG.BS4 + if data[0] == ORG.RPS: + data[0] = RORG.RPS + + # If we got this far, everything went ok (?) + if packet_type == PACKET.RADIO: + # Need to handle UTE Teach-in here, as it's a separate packet type... + if data[0] == RORG.UTE: + packet = ESP2UTETeachInPacket(packet_type, data, opt_data) + # Send a response automatically, works only if + # - communicator is set + # - communicator.teach_in == True + packet.send_response() + else: + packet = ESP2RadioPacket(packet_type, data, opt_data) + elif packet_type == PACKET.RESPONSE: + packet = ESP2ResponsePacket(packet_type, data, opt_data) + elif packet_type == PACKET.EVENT: + #packet = EventPacket(packet_type, data, opt_data) + packet = ESP2RadioPacket(packet_type, data, opt_data) + else: + packet = ESP2Packet(packet_type, data, opt_data) + + return PARSE_RESULT.OK, buf, packet + + @staticmethod + def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, + sender=None, + learn=False, **kwargs): + ''' + Creates an packet ready for sending. + Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. + Additional arguments (**kwargs) are used for setting the values. + Currently only supports: + - PACKET.RADIO + - RORGs RPS, BS1, BS4, VLD. + TODO: + - Require sender to be set? Would force the "correct" sender to be set. + - Do we need to set telegram control bits? + Might be useful for acting as a repeater? + ''' + + if packet_type != PACKET.RADIO: + # At least for now, only support PACKET.RADIO. + raise ValueError('Packet type not supported by this function.') + + if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4]: + # At least for now, only support these RORGS. + raise ValueError('RORG not supported by this function.') + + if destination is None: + destination = [0xFF, 0xFF, 0xFF, 0xFF] + + # TODO: Should use the correct Base ID as default. + # Might want to change the sender to be an offset from the actual address? + if sender is None: + ESP2Packet.logger.warning('Replacing sender with default address.') + sender = [0xDE, 0xAD, 0xBE, 0xEF] + + if not isinstance(destination, list) or len(destination) != 4: + raise ValueError('Destination must a list containing 4 (numeric) values.') + + if not isinstance(sender, list) or len(sender) != 4: + raise ValueError('Sender must a list containing 4 (numeric) values.') + + packet = ESP2Packet(packet_type, data=[], optional=[]) + packet.rorg = rorg + + if packet.rorg == RORG.BS1: + packet.rorg = ORG.BS1 + if packet.rorg == RORG.BS4: + packet.rorg = ORG.BS4 + if packet.rorg == RORG.RPS: + packet.rorg = ORG.RPS + + packet.data = [packet.rorg] + # Select EEP at this point, so we know how many bits we're dealing with (for VLD). + #packet.select_eep(rorg_func, rorg_type, direction, command) + + # Initialize data depending on the profile. + # if rorg in [RORG.RPS, RORG.BS1]: + # packet.data.extend([0]) + #elif rorg == RORG.BS4: + packet.data.extend(command) + #else: + # packet.data.extend([0] * int(packet._profile.get('bits', '1'))) + packet.data.extend(sender) + packet.data.extend([0]) + # Always use sub-telegram 3, maximum dbm (as per spec, when sending), + # and no security (security not supported as per EnOcean Serial Protocol). + #packet.optional = [3] + destination + [0xFF] + [0] + + #if command: + # Set CMD to command, if applicable.. Helps with VLD. + #kwargs['CMD'] = command + + #packet.set_eep(kwargs) + if rorg in [RORG.BS1, RORG.BS4] and not learn: + if rorg == RORG.BS1: + packet.data[1] |= (1 << 3) + if rorg == RORG.BS4: + packet.data[4] |= (1 << 3) + packet.data[-1] = packet.status + + # Parse the built packet, so it corresponds to the received packages + # For example, stuff like RadioPacket.learn should be set. + packet = ESP2Packet.parse(packet.build_ESP2())[2] + return packet + + def build(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data)+1 + ords = [0xA5, 0x5A, (data_length & 0x1F | ((int(self.packet_type)&0x07)<<5))] + + if self.data[0] in [RORG.RPS, RORG.BS4, RORG.BS1]: + if self.data[0] == RORG.RPS: + self.data[0] = ORG.RPS + if self.data[0] == RORG.BS1: + self.data[0] = ORG.BS1 + if self.data[0] == RORG.BS4: + self.data[0] = ORG.BS4 + + ords.extend(self.data) + ords.append(crc8.calc_ESP2(ords[2:])) + return ords + +class ESP2RadioPacket(RadioPacket): + @staticmethod + def create(rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, sender=None, learn=False, **kwargs): + return ESP2Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, + direction, command, destination, sender, learn, **kwargs) + +class ESP2ResponsePacket(ResponsePacket, ESP2Packet): + """ESP2 version of response package""" + +class ESP2EventPacket(EventPacket, ESP2Packet): + """ESP2 version of event package""" + +class ESP2UTETeachInPacket(UTETeachInPacket, ESP2RadioPacket): + """ESP2 version of UTE teachin package""" \ No newline at end of file diff --git a/enocean/protocol/esp3_packet.py b/enocean/protocol/esp3_packet.py new file mode 100644 index 0000000..c518923 --- /dev/null +++ b/enocean/protocol/esp3_packet.py @@ -0,0 +1,193 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.packet import EventPacket, Packet, RadioPacket, ResponsePacket, UTETeachInPacket +import logging +from collections import OrderedDict + +import enocean.utils +from enocean.protocol import crc8 +from enocean.protocol.eep import EEP +from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 + + +class ESP3Packet(Packet): + ''' + Base class for ESP3 Packet. + Mainly used for for packet generation and + Packet.parse_msg(buf) for parsing message. + parse_msg() returns subclass, if one is defined for the data type. + ''' + + @staticmethod + def parse_msg(buf): + ''' + Parses message from buffer. + returns: + - PARSE_RESULT + - remaining buffer + - Packet -object (if message was valid, else None) + ''' + # If the buffer doesn't contain 0x55 (start char) + # the message isn't needed -> ignore + if 0x55 not in buf: + return PARSE_RESULT.INCOMPLETE, [], None + + # Valid buffer starts from 0x55 + # Convert to list, as index -method isn't defined for bytearray + buf = [ord(x) if not isinstance(x, int) else x for x in buf[list(buf).index(0x55):]] + try: + data_len = (buf[1] << 8) | buf[2] + opt_len = buf[3] + except IndexError: + # If the fields don't exist, message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + # Header: 6 bytes, data, optional data and data checksum + msg_len = 6 + data_len + opt_len + 1 + if len(buf) < msg_len: + # If buffer isn't long enough, the message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + msg = buf[0:msg_len] + buf = buf[msg_len:] + + packet_type = msg[4] + data = msg[6:6 + data_len] + opt_data = msg[6 + data_len:6 + data_len + opt_len] + + # Check CRCs for header and data + if msg[5] != crc8.calc_ESP3(msg[1:5]): + # Fail if doesn't match message + ESP3Packet.logger.error('Header CRC error!') + # Return CRC_MISMATCH + return PARSE_RESULT.CRC_MISMATCH, buf, None + if msg[6 + data_len + opt_len] != crc8.calc_ESP3(msg[6:6 + data_len + opt_len]): + # Fail if doesn't match message + ESP3Packet.logger.error('Data CRC error!') + # Return CRC_MISMATCH + return PARSE_RESULT.CRC_MISMATCH, buf, None + + # If we got this far, everything went ok (?) + if packet_type == PACKET.RADIO_ERP1: + # Need to handle UTE Teach-in here, as it's a separate packet type... + if data[0] == RORG.UTE: + packet = ESP3UTETeachInPacket(packet_type, data, opt_data) + else: + packet = ESP3RadioPacket(packet_type, data, opt_data) + elif packet_type == PACKET.RESPONSE: + packet = ESP3ResponsePacket(packet_type, data, opt_data) + elif packet_type == PACKET.EVENT: + packet = ESP3EventPacket(packet_type, data, opt_data) + else: + packet = ESP3Packet(packet_type, data, opt_data) + + return PARSE_RESULT.OK, buf, packet + + @staticmethod + def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, + sender=None, + learn=False, **kwargs): + ''' + Creates an packet ready for sending. + Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. + Additional arguments (**kwargs) are used for setting the values. + + Currently only supports: + - PACKET.RADIO_ERP1 + - RORGs RPS, BS1, BS4, VLD. + + TODO: + - Require sender to be set? Would force the "correct" sender to be set. + - Do we need to set telegram control bits? + Might be useful for acting as a repeater? + ''' + + if packet_type != PACKET.RADIO_ERP1: + # At least for now, only support PACKET.RADIO_ERP1. + raise ValueError('ESP3 Packet type not supported by this function.') + + if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4, RORG.VLD]: + # At least for now, only support these RORGS. + raise ValueError('RORG not supported by this function.') + + if destination is None: + ESP3Packet.logger.warning('Replacing destination with broadcast address.') + destination = [0xFF, 0xFF, 0xFF, 0xFF] + + # TODO: Should use the correct Base ID as default. + # Might want to change the sender to be an offset from the actual address? + if sender is None: + ESP3Packet.logger.warning('Replacing sender with default address.') + sender = [0xDE, 0xAD, 0xBE, 0xEF] + + if not isinstance(destination, list) or len(destination) != 4: + raise ValueError('Destination must a list containing 4 (numeric) values.') + + if not isinstance(sender, list) or len(sender) != 4: + raise ValueError('Sender must a list containing 4 (numeric) values.') + + packet = ESP3Packet(packet_type, data=[], optional=[]) + packet.rorg = rorg + packet.data = [packet.rorg] + # Select EEP at this point, so we know how many bits we're dealing with (for VLD). + packet.select_eep(rorg_func, rorg_type, direction, command) + + # Initialize data depending on the profile. + if rorg in [RORG.RPS, RORG.BS1]: + packet.data.extend([0]) + elif rorg == RORG.BS4: + packet.data.extend([0, 0, 0, 0]) + else: + packet.data.extend([0] * int(packet._profile.get('bits', '1'))) + packet.data.extend(sender) + packet.data.extend([0]) + # Always use sub-telegram 3, maximum dbm (as per spec, when sending), + # and no security (security not supported as per EnOcean Serial Protocol). + packet.optional = [3] + destination + [0xFF] + [0] + + if command: + # Set CMD to command, if applicable.. Helps with VLD. + kwargs['CMD'] = command + + packet.set_eep(kwargs) + if rorg in [RORG.BS1, RORG.BS4] and not learn: + if rorg == RORG.BS1: + packet.data[1] |= (1 << 3) + if rorg == RORG.BS4: + packet.data[4] |= (1 << 3) + packet.data[-1] = packet.status + + # Parse the built packet, so it corresponds to the received packages + # For example, stuff like RadioPacket.learn should be set. + packet = ESP3Packet.parse_msg(packet.build())[2] + packet.rorg = rorg + packet.parse_eep(rorg_func, rorg_type, direction, command) + return packet + + def build(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data) + ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] + ords.append(crc8.calc_ESP3(ords[1:5])) + ords.extend(self.data) + ords.extend(self.optional) + ords.append(crc8.calc_ESP3(ords[6:])) + return ords + + +class ESP3RadioPacket(RadioPacket, ESP3Packet): + @staticmethod + def create(rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, sender=None, learn=False, **kwargs): + return ESP3Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, + direction, command, destination, sender, learn, **kwargs) + +class ESP3ResponsePacket(ResponsePacket, ESP3Packet): + """ESP3 version of response package""" + +class ESP3EventPacket(EventPacket, ESP3Packet): + """ESP3 version of event package""" + +class ESP3UTETeachInPacket(UTETeachInPacket, ESP3RadioPacket): + """ESP3 version of UTE teachin package""" \ No newline at end of file diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index 14fdc34..9348562 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -12,9 +12,6 @@ class Packet(object): ''' Base class for Packet. - Mainly used for for packet generation and - Packet.parse_msg(buf) for parsing message. - parse_msg() returns subclass, if one is defined for the data type. ''' eep = EEP() logger = logging.getLogger('enocean.protocol.packet') @@ -99,153 +96,6 @@ def _bit_status(self): def _bit_status(self, value): self.status = enocean.utils.from_bitarray(value) - @staticmethod - def parse_msg(buf): - ''' - Parses message from buffer. - returns: - - PARSE_RESULT - - remaining buffer - - Packet -object (if message was valid, else None) - ''' - # If the buffer doesn't contain 0x55 (start char) - # the message isn't needed -> ignore - if 0x55 not in buf: - return PARSE_RESULT.INCOMPLETE, [], None - - # Valid buffer starts from 0x55 - # Convert to list, as index -method isn't defined for bytearray - buf = [ord(x) if not isinstance(x, int) else x for x in buf[list(buf).index(0x55):]] - try: - data_len = (buf[1] << 8) | buf[2] - opt_len = buf[3] - except IndexError: - # If the fields don't exist, message is incomplete - return PARSE_RESULT.INCOMPLETE, buf, None - - # Header: 6 bytes, data, optional data and data checksum - msg_len = 6 + data_len + opt_len + 1 - if len(buf) < msg_len: - # If buffer isn't long enough, the message is incomplete - return PARSE_RESULT.INCOMPLETE, buf, None - - msg = buf[0:msg_len] - buf = buf[msg_len:] - - packet_type = msg[4] - data = msg[6:6 + data_len] - opt_data = msg[6 + data_len:6 + data_len + opt_len] - - # Check CRCs for header and data - if msg[5] != crc8.calc(msg[1:5]): - # Fail if doesn't match message - Packet.logger.error('Header CRC error!') - # Return CRC_MISMATCH - return PARSE_RESULT.CRC_MISMATCH, buf, None - if msg[6 + data_len + opt_len] != crc8.calc(msg[6:6 + data_len + opt_len]): - # Fail if doesn't match message - Packet.logger.error('Data CRC error!') - # Return CRC_MISMATCH - return PARSE_RESULT.CRC_MISMATCH, buf, None - - # If we got this far, everything went ok (?) - if packet_type == PACKET.RADIO_ERP1: - # Need to handle UTE Teach-in here, as it's a separate packet type... - if data[0] == RORG.UTE: - packet = UTETeachInPacket(packet_type, data, opt_data) - else: - packet = RadioPacket(packet_type, data, opt_data) - elif packet_type == PACKET.RESPONSE: - packet = ResponsePacket(packet_type, data, opt_data) - elif packet_type == PACKET.EVENT: - packet = EventPacket(packet_type, data, opt_data) - else: - packet = Packet(packet_type, data, opt_data) - - return PARSE_RESULT.OK, buf, packet - - @staticmethod - def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, - sender=None, - learn=False, **kwargs): - ''' - Creates an packet ready for sending. - Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. - Additional arguments (**kwargs) are used for setting the values. - - Currently only supports: - - PACKET.RADIO_ERP1 - - RORGs RPS, BS1, BS4, VLD. - - TODO: - - Require sender to be set? Would force the "correct" sender to be set. - - Do we need to set telegram control bits? - Might be useful for acting as a repeater? - ''' - - if packet_type != PACKET.RADIO_ERP1: - # At least for now, only support PACKET.RADIO_ERP1. - raise ValueError('Packet type not supported by this function.') - - if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4, RORG.VLD]: - # At least for now, only support these RORGS. - raise ValueError('RORG not supported by this function.') - - if destination is None: - Packet.logger.warning('Replacing destination with broadcast address.') - destination = [0xFF, 0xFF, 0xFF, 0xFF] - - # TODO: Should use the correct Base ID as default. - # Might want to change the sender to be an offset from the actual address? - if sender is None: - Packet.logger.warning('Replacing sender with default address.') - sender = [0xDE, 0xAD, 0xBE, 0xEF] - - if not isinstance(destination, list) or len(destination) != 4: - raise ValueError('Destination must a list containing 4 (numeric) values.') - - if not isinstance(sender, list) or len(sender) != 4: - raise ValueError('Sender must a list containing 4 (numeric) values.') - - packet = Packet(packet_type, data=[], optional=[]) - packet.rorg = rorg - packet.data = [packet.rorg] - # Select EEP at this point, so we know how many bits we're dealing with (for VLD). - packet.select_eep(rorg_func, rorg_type, direction, command) - - # Initialize data depending on the profile. - if rorg in [RORG.RPS, RORG.BS1]: - packet.data.extend([0]) - elif rorg == RORG.BS4: - packet.data.extend([0, 0, 0, 0]) - else: - packet.data.extend([0] * int(packet._profile.get('bits', '1'))) - packet.data.extend(sender) - packet.data.extend([0]) - # Always use sub-telegram 3, maximum dbm (as per spec, when sending), - # and no security (security not supported as per EnOcean Serial Protocol). - packet.optional = [3] + destination + [0xFF] + [0] - - if command: - # Set CMD to command, if applicable.. Helps with VLD. - kwargs['CMD'] = command - - packet.set_eep(kwargs) - if rorg in [RORG.BS1, RORG.BS4] and not learn: - if rorg == RORG.BS1: - packet.data[1] |= (1 << 3) - if rorg == RORG.BS4: - packet.data[4] |= (1 << 3) - packet.data[-1] = packet.status - - # Parse the built packet, so it corresponds to the received packages - # For example, stuff like RadioPacket.learn should be set. - packet = Packet.parse_msg(packet.build())[2] - packet.rorg = rorg - packet.parse_eep(rorg_func, rorg_type, direction, command) - return packet - def parse(self): ''' Parse data from Packet ''' # Parse status from messages @@ -281,17 +131,6 @@ def set_eep(self, data): ''' Update packet data based on EEP. Input data is a dictionary with keys corresponding to the EEP. ''' self._bit_data, self._bit_status = self.eep.set_values(self._profile, self._bit_data, self._bit_status, data) - def build(self): - ''' Build Packet for sending to EnOcean controller ''' - data_length = len(self.data) - ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] - ords.append(crc8.calc(ords[1:5])) - ords.extend(self.data) - ords.extend(self.optional) - ords.append(crc8.calc(ords[6:])) - return ords - - class RadioPacket(Packet): destination = [0xFF, 0xFF, 0xFF, 0xFF] dBm = 0 @@ -303,12 +142,6 @@ def __str__(self): packet_str = super(RadioPacket, self).__str__() return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str) - @staticmethod - def create(rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, sender=None, learn=False, **kwargs): - return Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, - direction, command, destination, sender, learn, **kwargs) - @property def sender_int(self): return enocean.utils.combine_hex(self.sender) @@ -325,9 +158,12 @@ def destination_int(self): def destination_hex(self): return enocean.utils.to_hex_string(self.destination) + def instance(self): RadioPacket + def parse(self): - self.destination = self.optional[1:5] - self.dBm = -self.optional[5] + if len(self.optional): + self.destination = self.optional[1:5] + self.dBm = -self.optional[5] self.sender = self.data[-5:-1] # Default to learn == True, as some devices don't have a learn button self.learn = True diff --git a/enocean/protocol/tests/test_eep.py b/enocean/protocol/tests/test_eep.py index acdecdf..0f827ad 100644 --- a/enocean/protocol/tests/test_eep.py +++ b/enocean/protocol/tests/test_eep.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.esp3_packet import ESP3Packet from enocean.protocol.packet import Packet from enocean.protocol.eep import EEP @@ -10,7 +11,7 @@ @timing(1000) def test_temperature(): ''' Tests RADIO message for EEP -profile 0xA5 0x02 0x05 ''' - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -36,7 +37,7 @@ def test_temperature(): @timing(1000) def test_magnetic_switch(): ''' Tests RADIO message for EEP -profile 0xD5 0x00 0x01 ''' - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -50,7 +51,7 @@ def test_magnetic_switch(): assert packet.status == 0x00 assert packet.repeater_count == 0 - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -68,7 +69,7 @@ def test_magnetic_switch(): @timing(1000) def test_switch(): - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -86,7 +87,7 @@ def test_switch(): assert packet.status == 0x30 assert packet.repeater_count == 0 - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -106,7 +107,7 @@ def test_switch(): @timing(1000) def test_eep_parsing(): - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -125,7 +126,7 @@ def test_eep_parsing(): @timing(1000) def test_eep_remaining(): # Magnetic switch -example - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -136,7 +137,7 @@ def test_eep_remaining(): assert packet.parse_eep(0x00, 0x01) == ['CO'] # Temperature-example - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -152,7 +153,7 @@ def test_eep_remaining(): @timing(1000) def test_eep_direction(): - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -168,7 +169,7 @@ def test_eep_direction(): @timing(1000) def test_vld(): - status, buf, p = Packet.parse_msg(bytearray([ + status, buf, p = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, @@ -200,7 +201,7 @@ def test_vld(): assert p.parsed['LC']['raw_value'] == 0 assert p.parsed['LC']['value'] == 'Local control disabled / not supported' - status, buf, p = Packet.parse_msg(bytearray([ + status, buf, p = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, @@ -234,7 +235,7 @@ def test_vld(): def test_fails(): - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -256,7 +257,7 @@ def test_fails(): assert eep.find_profile(packet._bit_data, 0xD5, 0xFF, 0x01) is None assert eep.find_profile(packet._bit_data, 0xD5, 0x00, 0xFF) is None - status, buf, packet = Packet.parse_msg(bytearray([ + status, buf, packet = ESP3Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, diff --git a/enocean/protocol/tests/test_packet.py b/enocean/protocol/tests/test_packet.py index 96fa4c3..b57869c 100644 --- a/enocean/protocol/tests/test_packet.py +++ b/enocean/protocol/tests/test_packet.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.esp3_packet import ESP3Packet from enocean.protocol.packet import Packet, EventPacket from enocean.protocol.constants import PACKET, PARSE_RESULT, EVENT_CODE @@ -106,7 +107,7 @@ def test_packet_examples(): } for packet, values in telegram_examples.items(): - status, remainder, pack = Packet.parse_msg(values['msg']) + status, remainder, pack = ESP3Packet.parse_msg(values['msg']) assert status == PARSE_RESULT.OK assert pack.packet_type != 0x00 assert pack.packet_type == packet @@ -160,7 +161,7 @@ def test_packet_fails(): ) for msg in fail_examples: - status, remainder, packet = Packet.parse_msg(msg) + status, remainder, packet = ESP3Packet.parse_msg(msg) assert status in [PARSE_RESULT.INCOMPLETE, PARSE_RESULT.CRC_MISMATCH] @@ -179,8 +180,8 @@ def test_packet_equals(): 0x08, 0x38 ]) - _, _, packet_1 = Packet.parse_msg(data_1) - _, _, packet_2 = Packet.parse_msg(data_2) + _, _, packet_1 = ESP3Packet.parse_msg(data_1) + _, _, packet_2 = ESP3Packet.parse_msg(data_2) assert str(packet_1) == '0x%02X %s %s %s' % (packet_1.packet_type, [hex(o) for o in packet_1.data], @@ -199,7 +200,7 @@ def test_event_packet(): 0x07 ]) - _, _, packet = Packet.parse_msg(data) + _, _, packet = ESP3Packet.parse_msg(data) assert isinstance(packet, EventPacket) assert packet.event == EVENT_CODE.SA_RECLAIM_NOT_SUCCESFUL assert packet.event_data == [] diff --git a/enocean/protocol/tests/test_packet_creation.py b/enocean/protocol/tests/test_packet_creation.py index 03a6a8b..3c455e9 100644 --- a/enocean/protocol/tests/test_packet_creation.py +++ b/enocean/protocol/tests/test_packet_creation.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.esp3_packet import ESP3Packet, ESP3RadioPacket from nose.tools import raises from enocean.protocol.packet import Packet, RadioPacket @@ -42,7 +43,7 @@ def test_packet_assembly(): ]) # manually assemble packet - packet = Packet(PACKET.RADIO_ERP1) + packet = ESP3Packet(PACKET.RADIO_ERP1) packet.rorg = RORG.BS4 sender_bytes = [(0xdeadbeef >> i & 0xff) for i in (24, 16, 8, 0)] data = [0, 0, 0, 0] @@ -82,7 +83,7 @@ def test_packet_assembly(): assert packet.rorg_type == 0x01 # Test the easier method of sending packets. - packet = Packet.create(PACKET.RADIO_ERP1, rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, + packet = ESP3Packet.create(PACKET.RADIO_ERP1, rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=1, **prop) packet_serialized = packet.build() assert len(packet_serialized) == len(PACKET_CONTENT_3) @@ -91,7 +92,7 @@ def test_packet_assembly(): assert packet.rorg_type == 0x01 # Test creating RadioPacket directly. - packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=2, SP=50) + packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=2, SP=50) packet_serialized = packet.build() assert len(packet_serialized) == len(PACKET_CONTENT_4) assert list(packet_serialized) == list(PACKET_CONTENT_4) @@ -110,7 +111,7 @@ def test_temperature(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x5C ]) - packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, + packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() assert len(packet_serialized) == len(TEMPERATURE) @@ -125,7 +126,7 @@ def test_temperature(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0 ]) - packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], + packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], learn=True, TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() assert len(packet_serialized) == len(TEMPERATURE) @@ -143,7 +144,7 @@ def test_magnetic_switch(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBA ]) - packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -158,7 +159,7 @@ def test_magnetic_switch(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x06 ]) - packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], learn=True, CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -174,7 +175,7 @@ def test_magnetic_switch(): 0x2E ]) - packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], CO='closed') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -190,7 +191,7 @@ def test_magnetic_switch(): 0x92 ]) - packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], learn=True, CO='closed') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -210,7 +211,7 @@ def test_switch(): ]) # test also enum setting by integer value with EB0 - packet = RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], + packet = ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], SA='No 2nd action', EB=1, R1='Button BI', @@ -230,7 +231,7 @@ def test_switch(): 0xD2 ]) - packet = RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], + packet = ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], SA='No 2nd action', EB='released', T21=True, @@ -244,13 +245,13 @@ def test_switch(): @timing(1000) @raises(ValueError) def test_illegal_eep_enum1(): - RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') + ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') @raises(ValueError) @timing(1000) def test_illegal_eep_enum2(): - RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) + ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) # Corresponds to the tests done in test_eep @@ -264,7 +265,7 @@ def test_packets_with_destination(): 0x03, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x00, 0x5F ]) - packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], + packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], destination=[0xDE, 0xAD, 0xBE, 0xEF], TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() @@ -282,7 +283,7 @@ def test_packets_with_destination(): 0x03, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x00, 0xB9 ]) - packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], destination=[0xDE, 0xAD, 0xBE, 0xEF], CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -300,7 +301,7 @@ def test_vld(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x5A ]) - packet = RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, command=1, DV=0, IO=0x1E, OV=0x64) + packet = ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, command=1, DV=0, IO=0x1E, OV=0x64) packet_serialized = packet.build() assert len(packet_serialized) == len(SWITCH) @@ -314,22 +315,22 @@ def test_vld(): def test_fails(): try: - Packet.create(PACKET.RESPONSE, 0xA5, 0x01, 0x01) + ESP3Packet.create(PACKET.RESPONSE, 0xA5, 0x01, 0x01) assert False except ValueError: assert True try: - Packet.create(PACKET.RADIO_ERP1, 0xA6, 0x01, 0x01) + ESP3Packet.create(PACKET.RADIO_ERP1, 0xA6, 0x01, 0x01) assert False except ValueError: assert True try: - Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, destination='ASDASDASD') + ESP3Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, destination='ASDASDASD') assert False except ValueError: assert True try: - Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, sender='ASDASDASD') + ESP3Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, sender='ASDASDASD') assert False except ValueError: assert True diff --git a/enocean/protocol/tests/test_teachin.py b/enocean/protocol/tests/test_teachin.py index d908a94..6c1f7a8 100644 --- a/enocean/protocol/tests/test_teachin.py +++ b/enocean/protocol/tests/test_teachin.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import +from enocean.protocol.esp3_packet import ESP3Packet from enocean.communicators import Communicator from enocean.protocol.packet import Packet @@ -12,7 +13,7 @@ def test_ute_in(): communicator = Communicator() communicator.base_id = [0xDE, 0xAD, 0xBE, 0xEF] - status, buf, packet = Packet.parse_msg( + status, buf, packet = ESP3Packet.parse_msg( bytearray([ 0x55, 0x00, 0x0D, 0x07, 0x01, diff --git a/examples/enocean_example.py b/examples/enocean_example.py index be9eb0c..97880b1 100755 --- a/examples/enocean_example.py +++ b/examples/enocean_example.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- +from enocean.protocol.esp3_packet import ESP3RadioPacket from enocean.consolelogger import init_logging import enocean.utils from enocean.communicators.serialcommunicator import SerialCommunicator @@ -15,7 +16,7 @@ def assemble_radio_packet(transmitter_id): - return RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, + return ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, sender=transmitter_id, CV=50, TMP=21.5, diff --git a/examples/example_D2-05-00.py b/examples/example_D2-05-00.py index 20f655d..6c0c7d2 100644 --- a/examples/example_D2-05-00.py +++ b/examples/example_D2-05-00.py @@ -7,6 +7,7 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' +from enocean.protocol.esp3_packet import ESP3RadioPacket import sys import time import traceback @@ -24,7 +25,7 @@ def set_position(destination, percentage): global communicator communicator.send( - RadioPacket.create(rorg=RORG.VLD, rorg_func=0x05, rorg_type=0x00, destination=destination, sender=communicator.base_id, command=1, POS=percentage) + ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x05, rorg_type=0x00, destination=destination, sender=communicator.base_id, command=1, POS=percentage) ) diff --git a/examples/example_DO21-11B-E.py b/examples/example_DO21-11B-E.py index 52464ec..5a194c9 100644 --- a/examples/example_DO21-11B-E.py +++ b/examples/example_DO21-11B-E.py @@ -7,6 +7,7 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' +from enocean.protocol.esp3_packet import ESP3RadioPacket import sys import time import traceback @@ -24,7 +25,7 @@ def send_command(destination, output_value): global communicator communicator.send( - RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, destination=destination, sender=communicator.base_id, command=1, IO=0x1E, OV=output_value) + ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, destination=destination, sender=communicator.base_id, command=1, IO=0x1E, OV=output_value) ) From fc7d1c4feb20d4a8be01874402abe97bb145b723 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 17 Aug 2021 21:44:09 +0200 Subject: [PATCH 2/8] adjusted support for esp2 packets as only two types are supported --- enocean/protocol/esp2_packet.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/enocean/protocol/esp2_packet.py b/enocean/protocol/esp2_packet.py index 145851d..99df30d 100644 --- a/enocean/protocol/esp2_packet.py +++ b/enocean/protocol/esp2_packet.py @@ -84,7 +84,8 @@ def parse_msg(buf): data[0] = RORG.RPS # If we got this far, everything went ok (?) - if packet_type == PACKET.RADIO: + # in ESP2 the exist only two type of packets, RadioPacket and Command + if packet_type == PACKET.RADIO or packet_type == PACKET.RESERVED or packet_type == PACKET.EVENT: # Need to handle UTE Teach-in here, as it's a separate packet type... if data[0] == RORG.UTE: packet = ESP2UTETeachInPacket(packet_type, data, opt_data) @@ -96,9 +97,6 @@ def parse_msg(buf): packet = ESP2RadioPacket(packet_type, data, opt_data) elif packet_type == PACKET.RESPONSE: packet = ESP2ResponsePacket(packet_type, data, opt_data) - elif packet_type == PACKET.EVENT: - #packet = EventPacket(packet_type, data, opt_data) - packet = ESP2RadioPacket(packet_type, data, opt_data) else: packet = ESP2Packet(packet_type, data, opt_data) From eb65f7c251bf482fe5f8642e6b7c30d36ea29c90 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 17 Aug 2021 22:06:41 +0200 Subject: [PATCH 3/8] adjusted implementation to stay backward compatible --- enocean/communicators/communicator.py | 2 +- enocean/protocol/esp2_packet.py | 5 +- enocean/protocol/esp3_packet.py | 193 ---------------- enocean/protocol/packet.py | 215 +++++++++++++++++- enocean/protocol/tests/test_eep.py | 28 +-- enocean/protocol/tests/test_packet.py | 12 +- .../protocol/tests/test_packet_creation.py | 42 ++-- enocean/protocol/tests/test_teachin.py | 4 +- 8 files changed, 259 insertions(+), 242 deletions(-) delete mode 100644 enocean/protocol/esp3_packet.py diff --git a/enocean/communicators/communicator.py b/enocean/communicators/communicator.py index 6689d1a..c764266 100644 --- a/enocean/communicators/communicator.py +++ b/enocean/communicators/communicator.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import from enocean.protocol.esp2_packet import ESP2Packet -from enocean.protocol.esp3_packet import ESP3Packet +from enocean.protocol.packet import ESP3Packet import logging import datetime diff --git a/enocean/protocol/esp2_packet.py b/enocean/protocol/esp2_packet.py index 99df30d..f663e96 100644 --- a/enocean/protocol/esp2_packet.py +++ b/enocean/protocol/esp2_packet.py @@ -1,13 +1,10 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import from enocean.protocol.packet import EventPacket, Packet, RadioPacket, ResponsePacket, UTETeachInPacket -import logging -from collections import OrderedDict -import enocean.utils from enocean.protocol import crc8 from enocean.protocol.eep import EEP -from enocean.protocol.constants import ORG, PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 +from enocean.protocol.constants import ORG, PACKET, RORG, PARSE_RESULT class ESP2Packet(Packet): diff --git a/enocean/protocol/esp3_packet.py b/enocean/protocol/esp3_packet.py deleted file mode 100644 index c518923..0000000 --- a/enocean/protocol/esp3_packet.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.packet import EventPacket, Packet, RadioPacket, ResponsePacket, UTETeachInPacket -import logging -from collections import OrderedDict - -import enocean.utils -from enocean.protocol import crc8 -from enocean.protocol.eep import EEP -from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 - - -class ESP3Packet(Packet): - ''' - Base class for ESP3 Packet. - Mainly used for for packet generation and - Packet.parse_msg(buf) for parsing message. - parse_msg() returns subclass, if one is defined for the data type. - ''' - - @staticmethod - def parse_msg(buf): - ''' - Parses message from buffer. - returns: - - PARSE_RESULT - - remaining buffer - - Packet -object (if message was valid, else None) - ''' - # If the buffer doesn't contain 0x55 (start char) - # the message isn't needed -> ignore - if 0x55 not in buf: - return PARSE_RESULT.INCOMPLETE, [], None - - # Valid buffer starts from 0x55 - # Convert to list, as index -method isn't defined for bytearray - buf = [ord(x) if not isinstance(x, int) else x for x in buf[list(buf).index(0x55):]] - try: - data_len = (buf[1] << 8) | buf[2] - opt_len = buf[3] - except IndexError: - # If the fields don't exist, message is incomplete - return PARSE_RESULT.INCOMPLETE, buf, None - - # Header: 6 bytes, data, optional data and data checksum - msg_len = 6 + data_len + opt_len + 1 - if len(buf) < msg_len: - # If buffer isn't long enough, the message is incomplete - return PARSE_RESULT.INCOMPLETE, buf, None - - msg = buf[0:msg_len] - buf = buf[msg_len:] - - packet_type = msg[4] - data = msg[6:6 + data_len] - opt_data = msg[6 + data_len:6 + data_len + opt_len] - - # Check CRCs for header and data - if msg[5] != crc8.calc_ESP3(msg[1:5]): - # Fail if doesn't match message - ESP3Packet.logger.error('Header CRC error!') - # Return CRC_MISMATCH - return PARSE_RESULT.CRC_MISMATCH, buf, None - if msg[6 + data_len + opt_len] != crc8.calc_ESP3(msg[6:6 + data_len + opt_len]): - # Fail if doesn't match message - ESP3Packet.logger.error('Data CRC error!') - # Return CRC_MISMATCH - return PARSE_RESULT.CRC_MISMATCH, buf, None - - # If we got this far, everything went ok (?) - if packet_type == PACKET.RADIO_ERP1: - # Need to handle UTE Teach-in here, as it's a separate packet type... - if data[0] == RORG.UTE: - packet = ESP3UTETeachInPacket(packet_type, data, opt_data) - else: - packet = ESP3RadioPacket(packet_type, data, opt_data) - elif packet_type == PACKET.RESPONSE: - packet = ESP3ResponsePacket(packet_type, data, opt_data) - elif packet_type == PACKET.EVENT: - packet = ESP3EventPacket(packet_type, data, opt_data) - else: - packet = ESP3Packet(packet_type, data, opt_data) - - return PARSE_RESULT.OK, buf, packet - - @staticmethod - def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, - sender=None, - learn=False, **kwargs): - ''' - Creates an packet ready for sending. - Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. - Additional arguments (**kwargs) are used for setting the values. - - Currently only supports: - - PACKET.RADIO_ERP1 - - RORGs RPS, BS1, BS4, VLD. - - TODO: - - Require sender to be set? Would force the "correct" sender to be set. - - Do we need to set telegram control bits? - Might be useful for acting as a repeater? - ''' - - if packet_type != PACKET.RADIO_ERP1: - # At least for now, only support PACKET.RADIO_ERP1. - raise ValueError('ESP3 Packet type not supported by this function.') - - if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4, RORG.VLD]: - # At least for now, only support these RORGS. - raise ValueError('RORG not supported by this function.') - - if destination is None: - ESP3Packet.logger.warning('Replacing destination with broadcast address.') - destination = [0xFF, 0xFF, 0xFF, 0xFF] - - # TODO: Should use the correct Base ID as default. - # Might want to change the sender to be an offset from the actual address? - if sender is None: - ESP3Packet.logger.warning('Replacing sender with default address.') - sender = [0xDE, 0xAD, 0xBE, 0xEF] - - if not isinstance(destination, list) or len(destination) != 4: - raise ValueError('Destination must a list containing 4 (numeric) values.') - - if not isinstance(sender, list) or len(sender) != 4: - raise ValueError('Sender must a list containing 4 (numeric) values.') - - packet = ESP3Packet(packet_type, data=[], optional=[]) - packet.rorg = rorg - packet.data = [packet.rorg] - # Select EEP at this point, so we know how many bits we're dealing with (for VLD). - packet.select_eep(rorg_func, rorg_type, direction, command) - - # Initialize data depending on the profile. - if rorg in [RORG.RPS, RORG.BS1]: - packet.data.extend([0]) - elif rorg == RORG.BS4: - packet.data.extend([0, 0, 0, 0]) - else: - packet.data.extend([0] * int(packet._profile.get('bits', '1'))) - packet.data.extend(sender) - packet.data.extend([0]) - # Always use sub-telegram 3, maximum dbm (as per spec, when sending), - # and no security (security not supported as per EnOcean Serial Protocol). - packet.optional = [3] + destination + [0xFF] + [0] - - if command: - # Set CMD to command, if applicable.. Helps with VLD. - kwargs['CMD'] = command - - packet.set_eep(kwargs) - if rorg in [RORG.BS1, RORG.BS4] and not learn: - if rorg == RORG.BS1: - packet.data[1] |= (1 << 3) - if rorg == RORG.BS4: - packet.data[4] |= (1 << 3) - packet.data[-1] = packet.status - - # Parse the built packet, so it corresponds to the received packages - # For example, stuff like RadioPacket.learn should be set. - packet = ESP3Packet.parse_msg(packet.build())[2] - packet.rorg = rorg - packet.parse_eep(rorg_func, rorg_type, direction, command) - return packet - - def build(self): - ''' Build Packet for sending to EnOcean controller ''' - data_length = len(self.data) - ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] - ords.append(crc8.calc_ESP3(ords[1:5])) - ords.extend(self.data) - ords.extend(self.optional) - ords.append(crc8.calc_ESP3(ords[6:])) - return ords - - -class ESP3RadioPacket(RadioPacket, ESP3Packet): - @staticmethod - def create(rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, sender=None, learn=False, **kwargs): - return ESP3Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, - direction, command, destination, sender, learn, **kwargs) - -class ESP3ResponsePacket(ResponsePacket, ESP3Packet): - """ESP3 version of response package""" - -class ESP3EventPacket(EventPacket, ESP3Packet): - """ESP3 version of event package""" - -class ESP3UTETeachInPacket(UTETeachInPacket, ESP3RadioPacket): - """ESP3 version of UTE teachin package""" \ No newline at end of file diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index 9348562..8082c24 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -6,7 +6,7 @@ import enocean.utils from enocean.protocol import crc8 from enocean.protocol.eep import EEP -from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 +from enocean.protocol.constants import PACKET, PARSE_RESULT, RORG, DB0, DB2, DB3, DB4, DB6 class Packet(object): @@ -44,6 +44,163 @@ def __init__(self, packet_type, data=None, optional=None): self.parse() + @staticmethod + def parse_msg(buf): + ''' + Parses message from buffer. + returns: + - PARSE_RESULT + - remaining buffer + - Packet -object (if message was valid, else None) + ''' + # If the buffer doesn't contain 0x55 (start char) + # the message isn't needed -> ignore + if 0x55 not in buf: + return PARSE_RESULT.INCOMPLETE, [], None + + # Valid buffer starts from 0x55 + # Convert to list, as index -method isn't defined for bytearray + buf = [ord(x) if not isinstance(x, int) else x for x in buf[list(buf).index(0x55):]] + try: + data_len = (buf[1] << 8) | buf[2] + opt_len = buf[3] + except IndexError: + # If the fields don't exist, message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + # Header: 6 bytes, data, optional data and data checksum + msg_len = 6 + data_len + opt_len + 1 + if len(buf) < msg_len: + # If buffer isn't long enough, the message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + msg = buf[0:msg_len] + buf = buf[msg_len:] + + packet_type = msg[4] + data = msg[6:6 + data_len] + opt_data = msg[6 + data_len:6 + data_len + opt_len] + + # Check CRCs for header and data + if msg[5] != crc8.calc_ESP3(msg[1:5]): + # Fail if doesn't match message + ESP3Packet.logger.error('Header CRC error!') + # Return CRC_MISMATCH + return PARSE_RESULT.CRC_MISMATCH, buf, None + if msg[6 + data_len + opt_len] != crc8.calc_ESP3(msg[6:6 + data_len + opt_len]): + # Fail if doesn't match message + ESP3Packet.logger.error('Data CRC error!') + # Return CRC_MISMATCH + return PARSE_RESULT.CRC_MISMATCH, buf, None + + # If we got this far, everything went ok (?) + if packet_type == PACKET.RADIO_ERP1: + # Need to handle UTE Teach-in here, as it's a separate packet type... + if data[0] == RORG.UTE: + packet = ESP3UTETeachInPacket(packet_type, data, opt_data) + else: + packet = ESP3RadioPacket(packet_type, data, opt_data) + elif packet_type == PACKET.RESPONSE: + packet = ESP3ResponsePacket(packet_type, data, opt_data) + elif packet_type == PACKET.EVENT: + packet = ESP3EventPacket(packet_type, data, opt_data) + else: + packet = ESP3Packet(packet_type, data, opt_data) + + return PARSE_RESULT.OK, buf, packet + + @staticmethod + def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, + sender=None, + learn=False, **kwargs): + ''' + Creates an packet ready for sending. + Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. + Additional arguments (**kwargs) are used for setting the values. + + Currently only supports: + - PACKET.RADIO_ERP1 + - RORGs RPS, BS1, BS4, VLD. + + TODO: + - Require sender to be set? Would force the "correct" sender to be set. + - Do we need to set telegram control bits? + Might be useful for acting as a repeater? + ''' + + if packet_type != PACKET.RADIO_ERP1: + # At least for now, only support PACKET.RADIO_ERP1. + raise ValueError('ESP3 Packet type not supported by this function.') + + if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4, RORG.VLD]: + # At least for now, only support these RORGS. + raise ValueError('RORG not supported by this function.') + + if destination is None: + ESP3Packet.logger.warning('Replacing destination with broadcast address.') + destination = [0xFF, 0xFF, 0xFF, 0xFF] + + # TODO: Should use the correct Base ID as default. + # Might want to change the sender to be an offset from the actual address? + if sender is None: + ESP3Packet.logger.warning('Replacing sender with default address.') + sender = [0xDE, 0xAD, 0xBE, 0xEF] + + if not isinstance(destination, list) or len(destination) != 4: + raise ValueError('Destination must a list containing 4 (numeric) values.') + + if not isinstance(sender, list) or len(sender) != 4: + raise ValueError('Sender must a list containing 4 (numeric) values.') + + packet = ESP3Packet(packet_type, data=[], optional=[]) + packet.rorg = rorg + packet.data = [packet.rorg] + # Select EEP at this point, so we know how many bits we're dealing with (for VLD). + packet.select_eep(rorg_func, rorg_type, direction, command) + + # Initialize data depending on the profile. + if rorg in [RORG.RPS, RORG.BS1]: + packet.data.extend([0]) + elif rorg == RORG.BS4: + packet.data.extend([0, 0, 0, 0]) + else: + packet.data.extend([0] * int(packet._profile.get('bits', '1'))) + packet.data.extend(sender) + packet.data.extend([0]) + # Always use sub-telegram 3, maximum dbm (as per spec, when sending), + # and no security (security not supported as per EnOcean Serial Protocol). + packet.optional = [3] + destination + [0xFF] + [0] + + if command: + # Set CMD to command, if applicable.. Helps with VLD. + kwargs['CMD'] = command + + packet.set_eep(kwargs) + if rorg in [RORG.BS1, RORG.BS4] and not learn: + if rorg == RORG.BS1: + packet.data[1] |= (1 << 3) + if rorg == RORG.BS4: + packet.data[4] |= (1 << 3) + packet.data[-1] = packet.status + + # Parse the built packet, so it corresponds to the received packages + # For example, stuff like RadioPacket.learn should be set. + packet = ESP3Packet.parse_msg(packet.build())[2] + packet.rorg = rorg + packet.parse_eep(rorg_func, rorg_type, direction, command) + return packet + + def build(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data) + ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] + ords.append(crc8.calc_ESP3(ords[1:5])) + ords.extend(self.data) + ords.extend(self.optional) + ords.append(crc8.calc_ESP3(ords[6:])) + return ords + def __str__(self): return '0x%02X %s %s %s' % ( self.packet_type, @@ -138,6 +295,11 @@ class RadioPacket(Packet): learn = True contains_eep = False + @staticmethod + def create(rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, sender=None, learn=False, **kwargs): + return Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, + direction, command, destination, sender, learn, **kwargs) def __str__(self): packet_str = super(RadioPacket, self).__str__() return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str) @@ -269,3 +431,54 @@ def parse(self): self.event = self.data[0] self.event_data = self.data[1:] return super(EventPacket, self).parse() + + + + +class ESP3Packet(Packet): + ''' + Base class for ESP3 Packet. + Mainly used for for packet generation and + Packet.parse_msg(buf) for parsing message. + parse_msg() returns subclass, if one is defined for the data type. + ''' + + @staticmethod + def parse_msg(buf): + ''' + Parses ESP3 message from buffer. + returns: + - PARSE_RESULT + - remaining buffer + - Packet -object (if message was valid, else None) + ''' + return Packet.parse_msg(buf) + + @staticmethod + def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, + sender=None, + learn=False, **kwargs): + ''' + Creates an ESP3 packet ready for sending. + Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. + Additional arguments (**kwargs) are used for setting the values. + ''' + return Packet.create(packet_type, rorg, rorg_func, rorg_type, direction, command, destination, sender, learn, **kwargs) + + +class ESP3RadioPacket(RadioPacket, ESP3Packet): + @staticmethod + def create(rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, sender=None, learn=False, **kwargs): + return RadioPacket.create(rorg, rorg_func, rorg_type, + direction, command, destination, sender, learn, **kwargs) + +class ESP3ResponsePacket(ResponsePacket, ESP3Packet): + """ESP3 version of response package""" + +class ESP3EventPacket(EventPacket, ESP3Packet): + """ESP3 version of event package""" + +class ESP3UTETeachInPacket(UTETeachInPacket, ESP3RadioPacket): + """ESP3 version of UTE teachin package""" \ No newline at end of file diff --git a/enocean/protocol/tests/test_eep.py b/enocean/protocol/tests/test_eep.py index 0f827ad..b9ab2a8 100644 --- a/enocean/protocol/tests/test_eep.py +++ b/enocean/protocol/tests/test_eep.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.esp3_packet import ESP3Packet +from enocean.protocol.packet import Packet from enocean.protocol.packet import Packet from enocean.protocol.eep import EEP @@ -11,7 +11,7 @@ @timing(1000) def test_temperature(): ''' Tests RADIO message for EEP -profile 0xA5 0x02 0x05 ''' - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -37,7 +37,7 @@ def test_temperature(): @timing(1000) def test_magnetic_switch(): ''' Tests RADIO message for EEP -profile 0xD5 0x00 0x01 ''' - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -51,7 +51,7 @@ def test_magnetic_switch(): assert packet.status == 0x00 assert packet.repeater_count == 0 - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -69,7 +69,7 @@ def test_magnetic_switch(): @timing(1000) def test_switch(): - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -87,7 +87,7 @@ def test_switch(): assert packet.status == 0x30 assert packet.repeater_count == 0 - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -107,7 +107,7 @@ def test_switch(): @timing(1000) def test_eep_parsing(): - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -126,7 +126,7 @@ def test_eep_parsing(): @timing(1000) def test_eep_remaining(): # Magnetic switch -example - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -137,7 +137,7 @@ def test_eep_remaining(): assert packet.parse_eep(0x00, 0x01) == ['CO'] # Temperature-example - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -153,7 +153,7 @@ def test_eep_remaining(): @timing(1000) def test_eep_direction(): - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, 0xEB, @@ -169,7 +169,7 @@ def test_eep_direction(): @timing(1000) def test_vld(): - status, buf, p = ESP3Packet.parse_msg(bytearray([ + status, buf, p = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, @@ -201,7 +201,7 @@ def test_vld(): assert p.parsed['LC']['raw_value'] == 0 assert p.parsed['LC']['value'] == 'Local control disabled / not supported' - status, buf, p = ESP3Packet.parse_msg(bytearray([ + status, buf, p = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, @@ -235,7 +235,7 @@ def test_vld(): def test_fails(): - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, 0x7A, @@ -257,7 +257,7 @@ def test_fails(): assert eep.find_profile(packet._bit_data, 0xD5, 0xFF, 0x01) is None assert eep.find_profile(packet._bit_data, 0xD5, 0x00, 0xFF) is None - status, buf, packet = ESP3Packet.parse_msg(bytearray([ + status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x09, 0x07, 0x01, 0x56, diff --git a/enocean/protocol/tests/test_packet.py b/enocean/protocol/tests/test_packet.py index b57869c..3b342e8 100644 --- a/enocean/protocol/tests/test_packet.py +++ b/enocean/protocol/tests/test_packet.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.esp3_packet import ESP3Packet +from enocean.protocol.packet import Packet from enocean.protocol.packet import Packet, EventPacket from enocean.protocol.constants import PACKET, PARSE_RESULT, EVENT_CODE @@ -107,7 +107,7 @@ def test_packet_examples(): } for packet, values in telegram_examples.items(): - status, remainder, pack = ESP3Packet.parse_msg(values['msg']) + status, remainder, pack = Packet.parse_msg(values['msg']) assert status == PARSE_RESULT.OK assert pack.packet_type != 0x00 assert pack.packet_type == packet @@ -161,7 +161,7 @@ def test_packet_fails(): ) for msg in fail_examples: - status, remainder, packet = ESP3Packet.parse_msg(msg) + status, remainder, packet = Packet.parse_msg(msg) assert status in [PARSE_RESULT.INCOMPLETE, PARSE_RESULT.CRC_MISMATCH] @@ -180,8 +180,8 @@ def test_packet_equals(): 0x08, 0x38 ]) - _, _, packet_1 = ESP3Packet.parse_msg(data_1) - _, _, packet_2 = ESP3Packet.parse_msg(data_2) + _, _, packet_1 = Packet.parse_msg(data_1) + _, _, packet_2 = Packet.parse_msg(data_2) assert str(packet_1) == '0x%02X %s %s %s' % (packet_1.packet_type, [hex(o) for o in packet_1.data], @@ -200,7 +200,7 @@ def test_event_packet(): 0x07 ]) - _, _, packet = ESP3Packet.parse_msg(data) + _, _, packet = Packet.parse_msg(data) assert isinstance(packet, EventPacket) assert packet.event == EVENT_CODE.SA_RECLAIM_NOT_SUCCESFUL assert packet.event_data == [] diff --git a/enocean/protocol/tests/test_packet_creation.py b/enocean/protocol/tests/test_packet_creation.py index 3c455e9..9a6cabd 100644 --- a/enocean/protocol/tests/test_packet_creation.py +++ b/enocean/protocol/tests/test_packet_creation.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.esp3_packet import ESP3Packet, ESP3RadioPacket +from enocean.protocol.packet import Packet, RadioPacket from nose.tools import raises from enocean.protocol.packet import Packet, RadioPacket @@ -43,7 +43,7 @@ def test_packet_assembly(): ]) # manually assemble packet - packet = ESP3Packet(PACKET.RADIO_ERP1) + packet = Packet(PACKET.RADIO_ERP1) packet.rorg = RORG.BS4 sender_bytes = [(0xdeadbeef >> i & 0xff) for i in (24, 16, 8, 0)] data = [0, 0, 0, 0] @@ -83,7 +83,7 @@ def test_packet_assembly(): assert packet.rorg_type == 0x01 # Test the easier method of sending packets. - packet = ESP3Packet.create(PACKET.RADIO_ERP1, rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, + packet = Packet.create(PACKET.RADIO_ERP1, rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=1, **prop) packet_serialized = packet.build() assert len(packet_serialized) == len(PACKET_CONTENT_3) @@ -92,7 +92,7 @@ def test_packet_assembly(): assert packet.rorg_type == 0x01 # Test creating RadioPacket directly. - packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=2, SP=50) + packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, learn=True, direction=2, SP=50) packet_serialized = packet.build() assert len(packet_serialized) == len(PACKET_CONTENT_4) assert list(packet_serialized) == list(PACKET_CONTENT_4) @@ -111,7 +111,7 @@ def test_temperature(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x5C ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, + packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() assert len(packet_serialized) == len(TEMPERATURE) @@ -126,7 +126,7 @@ def test_temperature(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0 ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], + packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], learn=True, TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() assert len(packet_serialized) == len(TEMPERATURE) @@ -144,7 +144,7 @@ def test_magnetic_switch(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBA ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -159,7 +159,7 @@ def test_magnetic_switch(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x06 ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], learn=True, CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -175,7 +175,7 @@ def test_magnetic_switch(): 0x2E ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], CO='closed') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -191,7 +191,7 @@ def test_magnetic_switch(): 0x92 ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], learn=True, CO='closed') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -211,7 +211,7 @@ def test_switch(): ]) # test also enum setting by integer value with EB0 - packet = ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], + packet = RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], SA='No 2nd action', EB=1, R1='Button BI', @@ -231,7 +231,7 @@ def test_switch(): 0xD2 ]) - packet = ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], + packet = RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], SA='No 2nd action', EB='released', T21=True, @@ -245,13 +245,13 @@ def test_switch(): @timing(1000) @raises(ValueError) def test_illegal_eep_enum1(): - ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') + RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') @raises(ValueError) @timing(1000) def test_illegal_eep_enum2(): - ESP3RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) + RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) # Corresponds to the tests done in test_eep @@ -265,7 +265,7 @@ def test_packets_with_destination(): 0x03, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x00, 0x5F ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], + packet = RadioPacket.create(rorg=RORG.BS4, rorg_func=0x02, rorg_type=0x05, sender=[0x01, 0x81, 0xB7, 0x44], destination=[0xDE, 0xAD, 0xBE, 0xEF], TMP=26.66666666666666666666666666666666666666666667) packet_serialized = packet.build() @@ -283,7 +283,7 @@ def test_packets_with_destination(): 0x03, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x00, 0xB9 ]) - packet = ESP3RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], + packet = RadioPacket.create(rorg=RORG.BS1, rorg_func=0x00, rorg_type=0x01, sender=[0x01, 0x82, 0x5D, 0xAB], destination=[0xDE, 0xAD, 0xBE, 0xEF], CO='open') packet_serialized = packet.build() assert len(packet_serialized) == len(MAGNETIC_SWITCH) @@ -301,7 +301,7 @@ def test_vld(): 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x5A ]) - packet = ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, command=1, DV=0, IO=0x1E, OV=0x64) + packet = RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, command=1, DV=0, IO=0x1E, OV=0x64) packet_serialized = packet.build() assert len(packet_serialized) == len(SWITCH) @@ -315,22 +315,22 @@ def test_vld(): def test_fails(): try: - ESP3Packet.create(PACKET.RESPONSE, 0xA5, 0x01, 0x01) + Packet.create(PACKET.RESPONSE, 0xA5, 0x01, 0x01) assert False except ValueError: assert True try: - ESP3Packet.create(PACKET.RADIO_ERP1, 0xA6, 0x01, 0x01) + Packet.create(PACKET.RADIO_ERP1, 0xA6, 0x01, 0x01) assert False except ValueError: assert True try: - ESP3Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, destination='ASDASDASD') + Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, destination='ASDASDASD') assert False except ValueError: assert True try: - ESP3Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, sender='ASDASDASD') + Packet.create(PACKET.RADIO_ERP1, 0xA5, 0x01, 0x01, sender='ASDASDASD') assert False except ValueError: assert True diff --git a/enocean/protocol/tests/test_teachin.py b/enocean/protocol/tests/test_teachin.py index 6c1f7a8..c990320 100644 --- a/enocean/protocol/tests/test_teachin.py +++ b/enocean/protocol/tests/test_teachin.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.esp3_packet import ESP3Packet +from enocean.protocol.packet import Packet from enocean.communicators import Communicator from enocean.protocol.packet import Packet @@ -13,7 +13,7 @@ def test_ute_in(): communicator = Communicator() communicator.base_id = [0xDE, 0xAD, 0xBE, 0xEF] - status, buf, packet = ESP3Packet.parse_msg( + status, buf, packet = Packet.parse_msg( bytearray([ 0x55, 0x00, 0x0D, 0x07, 0x01, From 165fe6a9dba0c6941a585dc111279d345b6f3449 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 17 Aug 2021 22:10:42 +0200 Subject: [PATCH 4/8] reverted changes in example --- examples/enocean_example.py | 4 ++-- examples/example_D2-05-00.py | 4 ++-- examples/example_DO21-11B-E.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/enocean_example.py b/examples/enocean_example.py index 97880b1..2e1d051 100755 --- a/examples/enocean_example.py +++ b/examples/enocean_example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from enocean.protocol.esp3_packet import ESP3RadioPacket +from enocean.protocol.packet import RadioPacket from enocean.consolelogger import init_logging import enocean.utils from enocean.communicators.serialcommunicator import SerialCommunicator @@ -16,7 +16,7 @@ def assemble_radio_packet(transmitter_id): - return ESP3RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, + return RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, sender=transmitter_id, CV=50, TMP=21.5, diff --git a/examples/example_D2-05-00.py b/examples/example_D2-05-00.py index 6c0c7d2..0427546 100644 --- a/examples/example_D2-05-00.py +++ b/examples/example_D2-05-00.py @@ -7,7 +7,7 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' -from enocean.protocol.esp3_packet import ESP3RadioPacket +from enocean.protocol.packet import RadioPacket import sys import time import traceback @@ -25,7 +25,7 @@ def set_position(destination, percentage): global communicator communicator.send( - ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x05, rorg_type=0x00, destination=destination, sender=communicator.base_id, command=1, POS=percentage) + RadioPacket.create(rorg=RORG.VLD, rorg_func=0x05, rorg_type=0x00, destination=destination, sender=communicator.base_id, command=1, POS=percentage) ) diff --git a/examples/example_DO21-11B-E.py b/examples/example_DO21-11B-E.py index 5a194c9..1835a6c 100644 --- a/examples/example_DO21-11B-E.py +++ b/examples/example_DO21-11B-E.py @@ -7,7 +7,7 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' -from enocean.protocol.esp3_packet import ESP3RadioPacket +from enocean.protocol.packet import RadioPacket import sys import time import traceback @@ -25,7 +25,7 @@ def send_command(destination, output_value): global communicator communicator.send( - ESP3RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, destination=destination, sender=communicator.base_id, command=1, IO=0x1E, OV=output_value) + RadioPacket.create(rorg=RORG.VLD, rorg_func=0x01, rorg_type=0x01, destination=destination, sender=communicator.base_id, command=1, IO=0x1E, OV=output_value) ) From 137e9232f9354572eabeebb1ea944be1d6e75f38 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 17 Aug 2021 22:15:10 +0200 Subject: [PATCH 5/8] rearranged methods to keep changes minimal --- enocean/protocol/packet.py | 134 +++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index 8082c24..ed3f2c4 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -12,6 +12,9 @@ class Packet(object): ''' Base class for Packet. + Mainly used for for packet generation and + Packet.parse_msg(buf) for parsing message. + parse_msg() returns subclass, if one is defined for the data type. ''' eep = EEP() logger = logging.getLogger('enocean.protocol.packet') @@ -44,6 +47,58 @@ def __init__(self, packet_type, data=None, optional=None): self.parse() + def __str__(self): + return '0x%02X %s %s %s' % ( + self.packet_type, + [hex(o) for o in self.data], + [hex(o) for o in self.optional], + self.parsed) + + def __unicode__(self): + return self.__str__() + + def __eq__(self, other): + return self.packet_type == other.packet_type and self.rorg == other.rorg \ + and self.data == other.data and self.optional == other.optional + + @property + def _bit_data(self): + # First and last 5 bits are always defined, so the data we're modifying is between them... + # TODO: This is valid for the packets we're currently manipulating. + # Needs the redefinition of Packet.data -> Packet.message. + # Packet.data would then only have the actual, documented data-bytes. + # Packet.message would contain the whole message. + # See discussion in issue #14 + return enocean.utils.to_bitarray(self.data[1:len(self.data) - 5], (len(self.data) - 6) * 8) + + @_bit_data.setter + def _bit_data(self, value): + # The same as getting the data, first and last 5 bits are ommitted, as they are defined... + for byte in range(len(self.data) - 6): + self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) + + # # COMMENTED OUT, AS NOTHING TOUCHES _bit_optional FOR NOW. + # # Thus, this is also untested. + # @property + # def _bit_optional(self): + # return enocean.utils.to_bitarray(self.optional, 8 * len(self.optional)) + + # @_bit_optional.setter + # def _bit_optional(self, value): + # if self.rorg in [RORG.RPS, RORG.BS1]: + # self.data[1] = enocean.utils.from_bitarray(value) + # if self.rorg == RORG.BS4: + # for byte in range(4): + # self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) + + @property + def _bit_status(self): + return enocean.utils.to_bitarray(self.status) + + @_bit_status.setter + def _bit_status(self, value): + self.status = enocean.utils.from_bitarray(value) + @staticmethod def parse_msg(buf): ''' @@ -191,68 +246,6 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None packet.parse_eep(rorg_func, rorg_type, direction, command) return packet - def build(self): - ''' Build Packet for sending to EnOcean controller ''' - data_length = len(self.data) - ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] - ords.append(crc8.calc_ESP3(ords[1:5])) - ords.extend(self.data) - ords.extend(self.optional) - ords.append(crc8.calc_ESP3(ords[6:])) - return ords - - def __str__(self): - return '0x%02X %s %s %s' % ( - self.packet_type, - [hex(o) for o in self.data], - [hex(o) for o in self.optional], - self.parsed) - - def __unicode__(self): - return self.__str__() - - def __eq__(self, other): - return self.packet_type == other.packet_type and self.rorg == other.rorg \ - and self.data == other.data and self.optional == other.optional - - @property - def _bit_data(self): - # First and last 5 bits are always defined, so the data we're modifying is between them... - # TODO: This is valid for the packets we're currently manipulating. - # Needs the redefinition of Packet.data -> Packet.message. - # Packet.data would then only have the actual, documented data-bytes. - # Packet.message would contain the whole message. - # See discussion in issue #14 - return enocean.utils.to_bitarray(self.data[1:len(self.data) - 5], (len(self.data) - 6) * 8) - - @_bit_data.setter - def _bit_data(self, value): - # The same as getting the data, first and last 5 bits are ommitted, as they are defined... - for byte in range(len(self.data) - 6): - self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) - - # # COMMENTED OUT, AS NOTHING TOUCHES _bit_optional FOR NOW. - # # Thus, this is also untested. - # @property - # def _bit_optional(self): - # return enocean.utils.to_bitarray(self.optional, 8 * len(self.optional)) - - # @_bit_optional.setter - # def _bit_optional(self, value): - # if self.rorg in [RORG.RPS, RORG.BS1]: - # self.data[1] = enocean.utils.from_bitarray(value) - # if self.rorg == RORG.BS4: - # for byte in range(4): - # self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) - - @property - def _bit_status(self): - return enocean.utils.to_bitarray(self.status) - - @_bit_status.setter - def _bit_status(self, value): - self.status = enocean.utils.from_bitarray(value) - def parse(self): ''' Parse data from Packet ''' # Parse status from messages @@ -266,6 +259,16 @@ def parse(self): self.repeater_count = enocean.utils.from_bitarray(self._bit_status[4:]) return self.parsed + def build(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data) + ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] + ords.append(crc8.calc_ESP3(ords[1:5])) + ords.extend(self.data) + ords.extend(self.optional) + ords.append(crc8.calc_ESP3(ords[6:])) + return ords + def select_eep(self, rorg_func, rorg_type, direction=None, command=None): ''' Set EEP based on FUNC and TYPE ''' # set EEP profile @@ -295,14 +298,15 @@ class RadioPacket(Packet): learn = True contains_eep = False + def __str__(self): + packet_str = super(RadioPacket, self).__str__() + return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str) + @staticmethod def create(rorg, rorg_func, rorg_type, direction=None, command=None, destination=None, sender=None, learn=False, **kwargs): return Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, direction, command, destination, sender, learn, **kwargs) - def __str__(self): - packet_str = super(RadioPacket, self).__str__() - return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str) @property def sender_int(self): From 0ed2b8c7addf56c6d4cdc80a54e4d4099c9769c3 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Tue, 17 Aug 2021 22:17:10 +0200 Subject: [PATCH 6/8] rearranged methods to keep changes minimal --- enocean/protocol/packet.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index ed3f2c4..bf0f05c 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -259,16 +259,6 @@ def parse(self): self.repeater_count = enocean.utils.from_bitarray(self._bit_status[4:]) return self.parsed - def build(self): - ''' Build Packet for sending to EnOcean controller ''' - data_length = len(self.data) - ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] - ords.append(crc8.calc_ESP3(ords[1:5])) - ords.extend(self.data) - ords.extend(self.optional) - ords.append(crc8.calc_ESP3(ords[6:])) - return ords - def select_eep(self, rorg_func, rorg_type, direction=None, command=None): ''' Set EEP based on FUNC and TYPE ''' # set EEP profile @@ -291,6 +281,16 @@ def set_eep(self, data): ''' Update packet data based on EEP. Input data is a dictionary with keys corresponding to the EEP. ''' self._bit_data, self._bit_status = self.eep.set_values(self._profile, self._bit_data, self._bit_status, data) + def build(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data) + ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] + ords.append(crc8.calc_ESP3(ords[1:5])) + ords.extend(self.data) + ords.extend(self.optional) + ords.append(crc8.calc_ESP3(ords[6:])) + return ords + class RadioPacket(Packet): destination = [0xFF, 0xFF, 0xFF, 0xFF] dBm = 0 From 0a24d617eeab4e240aa0d02d443c9f66cf3be567 Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Wed, 25 Aug 2021 21:27:54 +0200 Subject: [PATCH 7/8] fixed due to testing with omnio pm101 device --- enocean/communicators/communicator.py | 6 +++++- enocean/protocol/esp2_packet.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/enocean/communicators/communicator.py b/enocean/communicators/communicator.py index c764266..ccec026 100644 --- a/enocean/communicators/communicator.py +++ b/enocean/communicators/communicator.py @@ -69,11 +69,15 @@ def parse(self): ''' Parses messages and puts them to receive queue ''' # Loop while we get new messages while True: - status, self._buffer, packet = ESP3Packet.parse_msg(self._buffer) if self._version == ESP_Version.ESP3 else ESP2Packet.parse_msg(self._buffer) + status, self._buffer, packet = ESP3Packet.parse_msg(self._buffer) if self._version == ESP_Version.ESP3.value else ESP2Packet.parse_msg(self._buffer) # If message is incomplete -> break the loop if status == PARSE_RESULT.INCOMPLETE: return status + # if message has a crc error, ignore current buffer + if status == PARSE_RESULT.CRC_MISMATCH: + self._buffer.clear() + # If message is OK, add it to receive queue or send to the callback method if status == PARSE_RESULT.OK and packet: packet.received = datetime.datetime.now() diff --git a/enocean/protocol/esp2_packet.py b/enocean/protocol/esp2_packet.py index f663e96..7165307 100644 --- a/enocean/protocol/esp2_packet.py +++ b/enocean/protocol/esp2_packet.py @@ -89,7 +89,7 @@ def parse_msg(buf): # Send a response automatically, works only if # - communicator is set # - communicator.teach_in == True - packet.send_response() + # packet.send_response() else: packet = ESP2RadioPacket(packet_type, data, opt_data) elif packet_type == PACKET.RESPONSE: @@ -181,7 +181,7 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None # Parse the built packet, so it corresponds to the received packages # For example, stuff like RadioPacket.learn should be set. - packet = ESP2Packet.parse(packet.build_ESP2())[2] + packet = ESP2Packet.parse_msg(packet.build())[2] return packet def build(self): From 039e8892c38fa83ba51eb4cdcf6b79ac2412826c Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Sun, 17 Oct 2021 12:19:49 +0200 Subject: [PATCH 8/8] bumped enocean version for local installation --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3fea09c..2af5f69 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='enocean', - version='0.60.1', + version='0.60.2', description='EnOcean serial protocol implementation', author='Kimmo Huoman', author_email='kipenroskaposti@gmail.com',