enocean
Advanced tools
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| import time | ||
| import functools | ||
| from os import environ | ||
| def timing(rounds=1, limit=None): | ||
| ''' | ||
| Wrapper to implement simple timing of tests. | ||
| Allows running multiple rounds to calculate average time. | ||
| Limit (in milliseconds) can be set to assert, if (average) duration is too high. | ||
| ''' | ||
| def decorator(method): | ||
| @functools.wraps(method) | ||
| def f(): | ||
| if rounds == 1: | ||
| start = time.time() | ||
| method() | ||
| duration = time.time() - start | ||
| else: | ||
| start = time.time() | ||
| for i in range(rounds): | ||
| method() | ||
| duration = (time.time() - start) / rounds | ||
| # Use milliseconds for duration counter | ||
| duration = duration * 1e3 | ||
| print('Test "%s.%s" took %.06f ms.' % (method.__module__, method.__name__, duration)) | ||
| if limit is not None: | ||
| assert limit > duration, 'Timing failure: %.06f > %.06f' % (duration, limit) | ||
| # Run tests with timings, only if WITH_TIMINGS environment variable is set. | ||
| # This is because tests with multiple rounds can take long to process. | ||
| if environ.get('WITH_TIMINGS', None) == '1': | ||
| return f | ||
| return method | ||
| return decorator |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| def get_bit(byte, bit): | ||
| ''' Get bit value from byte ''' | ||
| return (byte >> bit) & 0x01 | ||
| def combine_hex(data): | ||
| ''' Combine list of integer values to one big integer ''' | ||
| output = 0x00 | ||
| for i, value in enumerate(reversed(data)): | ||
| output |= (value << i * 8) | ||
| return output | ||
| def to_bitarray(data, width=8): | ||
| ''' Convert data (list of integers, bytearray or integer) to bitarray ''' | ||
| if isinstance(data, list) or isinstance(data, bytearray): | ||
| data = combine_hex(data) | ||
| return [True if digit == '1' else False for digit in bin(data)[2:].zfill(width)] | ||
| def from_bitarray(data): | ||
| ''' Convert bit array back to integer ''' | ||
| return int(''.join(['1' if x else '0' for x in data]), 2) | ||
| def to_hex_string(data): | ||
| ''' Convert list of integers to a hex string, separated by ":" ''' | ||
| if isinstance(data, int): | ||
| return '%02X' % data | ||
| return ':'.join([('%02X' % o) for o in data]) | ||
| def from_hex_string(hex_string): | ||
| reval = [int(x, 16) for x in hex_string.split(':')] | ||
| if len(reval) == 1: | ||
| return reval[0] | ||
| return reval |
| Metadata-Version: 1.0 | ||
| Name: enocean | ||
| Version: 0.31 | ||
| Version: 0.40 | ||
| Summary: EnOcean serial protocol implementation | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/kipe/enocean |
| setup.py | ||
| enocean/__init__.py | ||
| enocean/consolelogger.py | ||
| enocean/decorators.py | ||
| enocean/utils.py | ||
| enocean.egg-info/PKG-INFO | ||
@@ -5,0 +7,0 @@ enocean.egg-info/SOURCES.txt |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import logging | ||
@@ -11,7 +11,5 @@ | ||
| from enocean.protocol.packet import Packet | ||
| from enocean.protocol.constants import PARSE_RESULT | ||
| from enocean.protocol.constants import PACKET, PARSE_RESULT, RETURN_CODE | ||
| logger = logging.getLogger('enocean.communicators.Communicator') | ||
| class Communicator(threading.Thread): | ||
@@ -22,3 +20,5 @@ ''' | ||
| ''' | ||
| def __init__(self, callback=None): | ||
| logger = logging.getLogger('enocean.communicators.Communicator') | ||
| def __init__(self, callback=None, teach_in=True): | ||
| super(Communicator, self).__init__() | ||
@@ -34,10 +34,15 @@ # Create an event to stop the thread | ||
| self.__callback = callback | ||
| # Internal variable for the Base ID of the module. | ||
| self._base_id = None | ||
| # Should new messages be learned automatically? Defaults to True. | ||
| # TODO: Not sure if we should use CO_WR_LEARNMODE?? | ||
| self.teach_in = teach_in | ||
| def _get_from_send_queue(self): | ||
| ''' Get message from send queue, if one exists ''' | ||
| try: | ||
| p = self.transmit.get(block=False) | ||
| logger.info('Sending packet') | ||
| logger.debug(p) | ||
| return p | ||
| packet = self.transmit.get(block=False) | ||
| self.logger.info('Sending packet') | ||
| self.logger.debug(packet) | ||
| return packet | ||
| except queue.Empty: | ||
@@ -49,3 +54,3 @@ pass | ||
| if not isinstance(packet, Packet): | ||
| logger.error('Object to send must be an instance of Packet') | ||
| self.logger.error('Object to send must be an instance of Packet') | ||
| return False | ||
@@ -62,3 +67,3 @@ self.transmit.put(packet) | ||
| while True: | ||
| status, self._buffer, p = Packet.parse_msg(self._buffer) | ||
| status, self._buffer, packet = Packet.parse_msg(self._buffer, communicator=self) | ||
| # If message is incomplete -> break the loop | ||
@@ -69,7 +74,41 @@ if status == PARSE_RESULT.INCOMPLETE: | ||
| # If message is OK, add it to receive queue or send to the callback method | ||
| if status == PARSE_RESULT.OK and p: | ||
| if status == PARSE_RESULT.OK and packet: | ||
| if self.__callback is None: | ||
| self.receive.put(p) | ||
| self.receive.put(packet) | ||
| else: | ||
| self.__callback(p) | ||
| logger.debug(p) | ||
| self.__callback(packet) | ||
| self.logger.debug(packet) | ||
| @property | ||
| def base_id(self): | ||
| ''' Fetches Base ID from the transmitter, if required. Otherwise returns the currently set Base ID. ''' | ||
| # If base id is already set, return it. | ||
| if self._base_id is not None: | ||
| return self._base_id | ||
| # Send COMMON_COMMAND 0x08, CO_RD_IDBASE request to the module | ||
| self.send(Packet(PACKET.COMMON_COMMAND, data=[0x08])) | ||
| # Loop over 10 times, to make sure we catch the response. | ||
| # Thanks to timeout, shouldn't take more than a second. | ||
| # Unfortunately, all other messages received during this time are ignored. | ||
| for i in range(0, 10): | ||
| try: | ||
| packet = self.receive.get(block=True, timeout=0.1) | ||
| # We're only interested in responses to the request in question. | ||
| if packet.packet_type == PACKET.RESPONSE and packet.response == RETURN_CODE.OK and len(packet.response_data) == 4: | ||
| # Base ID is set in the response data. | ||
| self._base_id = packet.response_data | ||
| # Put packet back to the Queue, so the user can also react to it if required... | ||
| self.receive.put(packet) | ||
| break | ||
| # Put other packets back to the Queue. | ||
| self.receive.put(packet) | ||
| except queue.Empty: | ||
| continue | ||
| # Return the current Base ID (might be None). | ||
| return self._base_id | ||
| @base_id.setter | ||
| def base_id(self, base_id): | ||
| ''' Sets the Base ID manually, only for testing purposes. ''' | ||
| self._base_id = base_id |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import logging | ||
@@ -8,7 +8,7 @@ import serial | ||
| logger = logging.getLogger('enocean.communicators.SerialCommunicator') | ||
| class SerialCommunicator(Communicator): | ||
| ''' Serial port communicator class for EnOcean radio ''' | ||
| logger = logging.getLogger('enocean.communicators.SerialCommunicator') | ||
| def __init__(self, port='/dev/ttyAMA0', callback=None): | ||
@@ -20,3 +20,3 @@ super(SerialCommunicator, self).__init__(callback) | ||
| def run(self): | ||
| logger.info('SerialCommunicator started') | ||
| self.logger.info('SerialCommunicator started') | ||
| while not self._stop_flag.is_set(): | ||
@@ -26,6 +26,6 @@ # If there's messages in transmit queue | ||
| while True: | ||
| p = self._get_from_send_queue() | ||
| if not p: | ||
| packet = self._get_from_send_queue() | ||
| if not packet: | ||
| break | ||
| self.__ser.write(bytearray(p.build())) | ||
| self.__ser.write(bytearray(packet.build())) | ||
@@ -36,3 +36,3 @@ # Read chars from serial port as hex numbers | ||
| except serial.SerialException: | ||
| logger.error('Serial port exception! (device disconnected or multiple access on port?)') | ||
| self.logger.error('Serial port exception! (device disconnected or multiple access on port?)') | ||
| break | ||
@@ -42,2 +42,2 @@ self.parse() | ||
| self.__ser.close() | ||
| logger.info('SerialCommunicator stopped') | ||
| self.logger.info('SerialCommunicator stopped') |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import logging | ||
@@ -8,7 +8,7 @@ import socket | ||
| logger = logging.getLogger('enocean.communicators.TCPCommunicator') | ||
| class TCPCommunicator(Communicator): | ||
| ''' Socket communicator class for EnOcean radio ''' | ||
| logger = logging.getLogger('enocean.communicators.TCPCommunicator') | ||
| def __init__(self, host='', port=9637): | ||
@@ -20,3 +20,3 @@ super(TCPCommunicator, self).__init__() | ||
| def run(self): | ||
| logger.info('TCPCommunicator started') | ||
| self.logger.info('TCPCommunicator started') | ||
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
@@ -32,16 +32,16 @@ sock.bind((self.host, self.port)) | ||
| continue | ||
| logger.debug('Client connected') | ||
| self.logger.debug('Client "%s" connected' % (addr)) | ||
| client.settimeout(0.5) | ||
| while True and not self._stop_flag.is_set(): | ||
| try: | ||
| d = client.recv(2048) | ||
| data = client.recv(2048) | ||
| except socket.timeout: | ||
| break | ||
| if not d: | ||
| if not data: | ||
| break | ||
| self._buffer.extend(bytearray(d)) | ||
| self._buffer.extend(bytearray(data)) | ||
| self.parse() | ||
| client.close() | ||
| logger.debug('Client disconnected') | ||
| self.logger.debug('Client disconnected') | ||
| sock.close() | ||
| logger.info('TCPCommunicator stopped') | ||
| self.logger.info('TCPCommunicator stopped') |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import socket | ||
@@ -7,5 +7,5 @@ | ||
| def send_to_tcp_socket(host, port, packet): | ||
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| s.connect((host, port)) | ||
| s.send(str(bytearray(packet.build()))) | ||
| s.close() | ||
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| sock.connect((host, port)) | ||
| sock.send(str(bytearray(packet.build()))) | ||
| sock.close() |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import logging | ||
| def init_logging(level=logging.DEBUG): | ||
| def init_logging(level=logging.DEBUG, log_to_file=False, logsize=1024, logcount=5): | ||
| formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | ||
@@ -12,5 +12,11 @@ | ||
| ch = logging.StreamHandler() | ||
| ch.setLevel(level) | ||
| ch.setFormatter(formatter) | ||
| logger.addHandler(ch) | ||
| stream_handler = logging.StreamHandler() | ||
| stream_handler.setLevel(level) | ||
| stream_handler.setFormatter(formatter) | ||
| logger.addHandler(stream_handler) | ||
| if log_to_file: | ||
| file_handler = logging.handlers.RotatingFileHandler('enocean.log', 'a', logsize*1000, logcount) | ||
| file_handler.setLevel(level) | ||
| file_handler.setFormatter(formatter) | ||
| logger.addHandler(file_handler) |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| from enum import IntEnum | ||
@@ -53,2 +53,3 @@ | ||
| SEC_ENCAPS = 0x31 | ||
| UTE = 0xD4 | ||
@@ -65,3 +66,3 @@ | ||
| # Starts from the end, so works on messages of all length. | ||
| class DB0: | ||
| class DB0(object): | ||
| BIT_0 = -1 | ||
@@ -77,3 +78,3 @@ BIT_1 = -2 | ||
| class DB1: | ||
| class DB1(object): | ||
| BIT_0 = -9 | ||
@@ -89,3 +90,3 @@ BIT_1 = -10 | ||
| class DB2: | ||
| class DB2(object): | ||
| BIT_0 = -17 | ||
@@ -101,3 +102,3 @@ BIT_1 = -18 | ||
| class DB3: | ||
| class DB3(object): | ||
| BIT_0 = -25 | ||
@@ -111,1 +112,34 @@ BIT_1 = -26 | ||
| BIT_7 = -32 | ||
| class DB4(object): | ||
| BIT_0 = -33 | ||
| BIT_1 = -34 | ||
| BIT_2 = -35 | ||
| BIT_3 = -36 | ||
| BIT_4 = -37 | ||
| BIT_5 = -38 | ||
| BIT_6 = -39 | ||
| BIT_7 = -40 | ||
| class DB5(object): | ||
| BIT_0 = -41 | ||
| BIT_1 = -42 | ||
| BIT_2 = -43 | ||
| BIT_3 = -44 | ||
| BIT_4 = -45 | ||
| BIT_5 = -46 | ||
| BIT_6 = -47 | ||
| BIT_7 = -48 | ||
| class DB6(object): | ||
| BIT_0 = -49 | ||
| BIT_1 = -50 | ||
| BIT_2 = -51 | ||
| BIT_3 = -52 | ||
| BIT_4 = -53 | ||
| BIT_5 = -54 | ||
| BIT_6 = -55 | ||
| BIT_7 = -56 |
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| # https://gist.github.com/hypebeast/3833758 | ||
| crcTable = ( | ||
| CRC_TABLE = ( | ||
| 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, | ||
@@ -38,5 +38,5 @@ 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, | ||
| def calc(msg): | ||
| runningCRC = 0 | ||
| for c in msg: | ||
| runningCRC = crcTable[runningCRC & 0xFF ^ c & 0xFF] | ||
| return runningCRC | ||
| checksum = 0 | ||
| for byte in msg: | ||
| checksum = CRC_TABLE[checksum & 0xFF ^ byte & 0xFF] | ||
| return checksum |
+177
-64
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| import os | ||
| import logging | ||
| from collections import OrderedDict | ||
| from bs4 import BeautifulSoup | ||
| import logging | ||
| logger = logging.getLogger('enocean.protocol.eep') | ||
| import enocean.utils | ||
| from enocean.protocol.constants import RORG | ||
| path = os.path.dirname(os.path.realpath(__file__)) | ||
| class EEP(object): | ||
| logger = logging.getLogger('enocean.protocol.eep') | ||
| class EEP(object): | ||
| def __init__(self): | ||
| self.ok = False | ||
| self.init_ok = False | ||
| self.telegrams = {} | ||
| try: | ||
| with open(os.path.join(path, 'EEP.xml'), 'r') as f: | ||
| self.soup = BeautifulSoup(f.read(), "html.parser") | ||
| self.ok = True | ||
| with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'EEP.xml'), 'r') as xml_file: | ||
| self.soup = BeautifulSoup(xml_file.read(), "html.parser") | ||
| self.init_ok = True | ||
| self.__load_xml() | ||
| except IOError: | ||
| logger.warn('Cannot load protocol file!') | ||
| pass | ||
| # Impossible to test with the current structure? | ||
| # To be honest, as the XML is included with the library, | ||
| # there should be no possibility of ever reaching this... | ||
| self.logger.warn('Cannot load protocol file!') | ||
| self.init_ok = False | ||
| def _get_hex(self, nmb): | ||
| ''' Get hex-representation of number ''' | ||
| return '0x%02X' % (nmb) | ||
| def __load_xml(self): | ||
| self.telegrams = { | ||
| enocean.utils.from_hex_string(telegram['rorg']): { | ||
| enocean.utils.from_hex_string(function['func']): { | ||
| enocean.utils.from_hex_string(type['type'], ): type | ||
| for type in function.find_all('profile') | ||
| } | ||
| for function in telegram.find_all('profiles') | ||
| } | ||
| for telegram in self.soup.find_all('telegram') | ||
| } | ||
| def _get_raw(self, value, bitarray): | ||
| @staticmethod | ||
| def _get_raw(source, bitarray): | ||
| ''' Get raw data as integer, based on offset and size ''' | ||
| offset = int(value['offset']) | ||
| size = int(value['size']) | ||
| offset = int(source['offset']) | ||
| size = int(source['size']) | ||
| return int(''.join(['1' if digit else '0' for digit in bitarray[offset:offset + size]]), 2) | ||
| def _get_value(self, value, data): | ||
| @staticmethod | ||
| def _set_raw(target, raw_value, bitarray): | ||
| ''' put value into bit array ''' | ||
| offset = int(target['offset']) | ||
| size = int(target['size']) | ||
| for digit in range(size): | ||
| bitarray[offset+digit] = (raw_value >> (size-digit-1)) & 0x01 != 0 | ||
| return bitarray | ||
| @staticmethod | ||
| def _get_rangeitem(source, raw_value): | ||
| for rangeitem in source.find_all('rangeitem'): | ||
| if raw_value in range(int(rangeitem.get('start', -1)), int(rangeitem.get('end', -1)) + 1): | ||
| return rangeitem | ||
| def _get_value(self, source, bitarray): | ||
| ''' Get value, based on the data in XML ''' | ||
| raw_value = self._get_raw(value, data) | ||
| raw_value = self._get_raw(source, bitarray) | ||
| rng = value.find('range') | ||
| rng = source.find('range') | ||
| rng_min = float(rng.find('min').text) | ||
| rng_max = float(rng.find('max').text) | ||
| scl = value.find('scale') | ||
| scl = source.find('scale') | ||
| scl_min = float(scl.find('min').text) | ||
@@ -46,5 +78,5 @@ scl_max = float(scl.find('max').text) | ||
| return { | ||
| value['shortcut']: { | ||
| 'description': value['description'], | ||
| 'unit': value['unit'], | ||
| source['shortcut']: { | ||
| 'description': source.get('description'), | ||
| 'unit': source['unit'], | ||
| 'value': (scl_max - scl_min) / (rng_max - rng_min) * (raw_value - rng_min) + scl_min, | ||
@@ -55,11 +87,14 @@ 'raw_value': raw_value, | ||
| def _get_enum(self, value, data): | ||
| def _get_enum(self, source, bitarray): | ||
| ''' Get enum value, based on the data in XML ''' | ||
| raw_value = self._get_raw(value, data) | ||
| value_desc = value.find('item', {'value': str(raw_value)}) | ||
| raw_value = self._get_raw(source, bitarray) | ||
| # Find value description. | ||
| value_desc = source.find('item', {'value': str(raw_value)}) or self._get_rangeitem(source, raw_value) | ||
| return { | ||
| value['shortcut']: { | ||
| 'description': value['description'], | ||
| 'unit': value.get('unit', ''), | ||
| 'value': value_desc['description'], | ||
| source['shortcut']: { | ||
| 'description': source.get('description'), | ||
| 'unit': source.get('unit', ''), | ||
| 'value': value_desc['description'].format(value=raw_value), | ||
| 'raw_value': raw_value, | ||
@@ -69,46 +104,124 @@ } | ||
| def find_profile(self, rorg, func, type): | ||
| def _get_boolean(self, source, bitarray): | ||
| ''' Get boolean value, based on the data in XML ''' | ||
| raw_value = self._get_raw(source, bitarray) | ||
| return { | ||
| source['shortcut']: { | ||
| 'description': source.get('description'), | ||
| 'unit': source.get('unit', ''), | ||
| 'value': True if raw_value else False, | ||
| 'raw_value': raw_value, | ||
| } | ||
| } | ||
| def _set_value(self, target, value, bitarray): | ||
| ''' set given numeric value to target field in bitarray ''' | ||
| # derive raw value | ||
| rng = target.find('range') | ||
| rng_min = float(rng.find('min').text) | ||
| rng_max = float(rng.find('max').text) | ||
| scl = target.find('scale') | ||
| scl_min = float(scl.find('min').text) | ||
| scl_max = float(scl.find('max').text) | ||
| raw_value = (value - scl_min) * (rng_max - rng_min) / (scl_max - scl_min) + rng_min | ||
| # store value in bitfield | ||
| return self._set_raw(target, int(raw_value), bitarray) | ||
| def _set_enum(self, target, value, bitarray): | ||
| ''' set given enum value (by string or integer value) to target field in bitarray ''' | ||
| # derive raw value | ||
| if isinstance(value, int): | ||
| # check whether this value exists | ||
| if target.find('item', {'value': value}) or self._get_rangeitem(target, value): | ||
| # set integer values directly | ||
| raw_value = value | ||
| else: | ||
| raise ValueError('Enum value "%s" not found in EEP.' % (value)) | ||
| else: | ||
| value_item = target.find('item', {'description': value}) | ||
| if value_item is None: | ||
| raise ValueError('Enum description for value "%s" not found in EEP.' % (value)) | ||
| raw_value = int(value_item['value']) | ||
| return self._set_raw(target, raw_value, bitarray) | ||
| @staticmethod | ||
| def _set_boolean(target, data, bitarray): | ||
| ''' set given value to target bit in bitarray ''' | ||
| bitarray[int(target['offset'])] = data | ||
| return bitarray | ||
| def find_profile(self, bitarray, eep_rorg, rorg_func, rorg_type, direction=None, command=None): | ||
| ''' Find profile and data description, matching RORG, FUNC and TYPE ''' | ||
| if not self.ok: | ||
| return None, None | ||
| if not self.init_ok: | ||
| self.logger.warn('EEP.xml not loaded!') | ||
| return None | ||
| rorg = self.soup.find('telegram', {'rorg': self._get_hex(rorg)}) | ||
| if not rorg: | ||
| logger.warn('Cannot find rorg in EEP!') | ||
| return None, None | ||
| if eep_rorg not in self.telegrams.keys(): | ||
| self.logger.warn('Cannot find rorg in EEP!') | ||
| return None | ||
| func = rorg.find('profiles', {'func': self._get_hex(func)}) | ||
| if not func: | ||
| logger.warn('Cannot find func in EEP!') | ||
| return None, None | ||
| if rorg_func not in self.telegrams[eep_rorg].keys(): | ||
| self.logger.warn('Cannot find func in EEP!') | ||
| return None | ||
| profile = func.find('profile', {'type': self._get_hex(type)}) | ||
| if not profile: | ||
| logger.warn('Cannot find type in EEP!') | ||
| return None, None | ||
| if rorg_type not in self.telegrams[eep_rorg][rorg_func].keys(): | ||
| self.logger.warn('Cannot find type in EEP!') | ||
| return None | ||
| data_description = profile.find('data') | ||
| if not data_description: | ||
| logger.warn('Cannot find data description in EEP!') | ||
| data_description = [] | ||
| profile = self.telegrams[eep_rorg][rorg_func][rorg_type] | ||
| return profile, data_description | ||
| if command: | ||
| # multiple commands can be defined, with the command id always in same location (per RORG-FUNC-TYPE). | ||
| eep_command = profile.find('command', recursive=False) | ||
| # If commands are not set in EEP, or command is None, | ||
| # get the first data as a "best guess". | ||
| if not eep_command: | ||
| return profile.find('data', recursive=False) | ||
| def get_values(self, rorg, func, type, data): | ||
| ''' Get keys and values from data, matching RORG, FUNC and TYPE ''' | ||
| if not self.ok: | ||
| return [], {} | ||
| # If eep_command is defined, so should be data.command | ||
| return profile.find('data', {'command': str(command)}, recursive=False) | ||
| profile, data_description = self.find_profile(rorg, func, type) | ||
| if not profile or not data_description: | ||
| # extract data description | ||
| # the direction tag is optional | ||
| if direction is None: | ||
| return profile.find('data', recursive=False) | ||
| return profile.find('data', {'direction': direction}, recursive=False) | ||
| def get_values(self, profile, bitarray, status): | ||
| ''' Get keys and values from bitarray ''' | ||
| if not self.init_ok or profile is None: | ||
| return [], {} | ||
| output = {} | ||
| for d in data_description.contents: | ||
| if not d.name: | ||
| output = OrderedDict({}) | ||
| for source in profile.contents: | ||
| if not source.name: | ||
| continue | ||
| if d.name == 'value': | ||
| output.update(self._get_value(d, data)) | ||
| if d.name == 'enum': | ||
| output.update(self._get_enum(d, data)) | ||
| if source.name == 'value': | ||
| output.update(self._get_value(source, bitarray)) | ||
| if source.name == 'enum': | ||
| output.update(self._get_enum(source, bitarray)) | ||
| if source.name == 'status': | ||
| output.update(self._get_boolean(source, status)) | ||
| return output.keys(), output | ||
| def set_values(self, profile, data, status, properties): | ||
| ''' Update data based on data contained in properties ''' | ||
| if not self.init_ok or profile is None: | ||
| return data, status | ||
| for shortcut, value in properties.items(): | ||
| # find the given property from EEP | ||
| target = profile.find(shortcut=shortcut) | ||
| if not target: | ||
| # TODO: Should we raise an error? | ||
| self.logger.warning('Cannot find data description for shortcut %s', shortcut) | ||
| continue | ||
| # update bit_data | ||
| if target.name == 'value': | ||
| data = self._set_value(target, value, data) | ||
| if target.name == 'enum': | ||
| data = self._set_enum(target, value, data) | ||
| if target.name == 'status': | ||
| status = self._set_boolean(target, value, status) | ||
| return data, status |
+899
-2
@@ -13,3 +13,3 @@ <?xml version="1.0" encoding="utf-8"?> | ||
| </enum> | ||
| <enum description="Energy bow" shortcut="EBO" offset="3" size="1"> | ||
| <enum description="Energy bow" shortcut="EB" offset="3" size="1"> | ||
| <item description="released" value="0" /> | ||
@@ -28,5 +28,34 @@ <item description="pressed" value="1" /> | ||
| </enum> | ||
| <status description="T21" shortcut="T21" offset="2" size="1" /> | ||
| <status description="NU" shortcut="NU" offset="3" size="1" /> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x05" description="Detectors"> | ||
| <profile type="0x01" description="Liquid Leakage Sensor (mechanic harvester)"> | ||
| <data> | ||
| <enum description="Water Sensor" shortcut="WAS" offset="0" size="8"> | ||
| <rangeitem description="not specified" start="0" end="16" /> | ||
| <item description="Water detected" value="17" /> | ||
| <rangeitem description="not specified" start="18" end="255" /> | ||
| </enum> | ||
| <status description="T21" shortcut="T21" offset="2" size="1" /> | ||
| <status description="NU" shortcut="NU" offset="3" size="1" /> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x10" description="Mechanical Handle"> | ||
| <profile type="0x00" description="Window Handle"> | ||
| <data> | ||
| <enum description="Window handle" shortcut="WIN" offset="2" size="2"> | ||
| <item description="Moved from up to vertical" value="0" /> | ||
| <item description="Moved from vertical to up" value="1" /> | ||
| <item description="Moved from down to vertical" value="2" /> | ||
| <item description="Moved from vertical to down" value="3" /> | ||
| </enum> | ||
| <status description="T21" shortcut="T21" offset="2" size="1" /> | ||
| <status description="NU" shortcut="NU" offset="3" size="1" /> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| </telegram> | ||
@@ -47,5 +76,61 @@ <telegram rorg="0xD5" type="1BS" description="1BS Telegram"> | ||
| <profiles func="0x02" description="Temperature Sensors"> | ||
| <profile type="0x01" description="Temperature Sensor Range -40°C to 0°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-40.000000</min> | ||
| <max>0.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x02" description="Temperature Sensor Range -30°C to +10°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-30.000000</min> | ||
| <max>10.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x03" description="Temperature Sensor Range -20°C to +20°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-20.000000</min> | ||
| <max>20.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x04" description="Temperature Sensor Range -10°C to +30°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-10.000000</min> | ||
| <max>30.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x05" description="Temperature Sensor Range 0°C to +40°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="C"> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
@@ -62,3 +147,313 @@ <min>255</min> | ||
| </profile> | ||
| <profile type="0x06" description="Temperature Sensor Range +10°C to +50°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>10.000000</min> | ||
| <max>50.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x07" description="Temperature Sensor Range +20°C to +60°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>20.000000</min> | ||
| <max>60.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x08" description="Temperature Sensor Range +30°C to +70°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>30.000000</min> | ||
| <max>70.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x09" description="Temperature Sensor Range +40°C to +80°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>40.000000</min> | ||
| <max>80.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x0A" description="Temperature Sensor Range +50°C to +90°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>50.000000</min> | ||
| <max>90.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x0B" description="Temperature Sensor Range +60°C to +100°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>60.000000</min> | ||
| <max>100.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x10" description="Temperature Sensor Range -60°C to +20°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-60.000000</min> | ||
| <max>20.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x11" description="Temperature Sensor Range -50°C to +30°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-50.000000</min> | ||
| <max>30.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x12" description="Temperature Sensor Range -40°C to +40°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-40.000000</min> | ||
| <max>40.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x13" description="Temperature Sensor Range -30°C to +50°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-30.000000</min> | ||
| <max>50.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x14" description="Temperature Sensor Range -20°C to +60°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-20.000000</min> | ||
| <max>60.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x15" description="Temperature Sensor Range -10°C to +70°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-10.000000</min> | ||
| <max>70.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x16" description="Temperature Sensor Range 0°C to +80°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>0.000000</min> | ||
| <max>80.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x17" description="Temperature Sensor Range +10°C to +90°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>10.000000</min> | ||
| <max>90.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x18" description="Temperature Sensor Range +20°C to +100°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>20.000000</min> | ||
| <max>100.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x19" description="Temperature Sensor Range +30°C to +110°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>30.000000</min> | ||
| <max>110.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x1A" description="Temperature Sensor Range +40°C to +120°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>40.000000</min> | ||
| <max>120.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x1B" description="Temperature Sensor Range +50°C to +130°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>50.000000</min> | ||
| <max>130.000000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x20" description="10 Bit Temperature Sensor Range -10°C to +41.2°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="14" size="10" unit="°C"> | ||
| <range> | ||
| <min>1023</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-10.000000</min> | ||
| <max>41.200000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile type="0x30" description="10 Bit Temperature Sensor Range -40°C to +62.3°C"> | ||
| <data> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="14" size="10" unit="°C"> | ||
| <range> | ||
| <min>1023</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>-40.000000</min> | ||
| <max>62.300000</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x04" description="Temperature and Humidity Sensor"> | ||
| <profile type="0x01" description="Range 0°C to +40°C and 0% to 100%"> | ||
| <data> | ||
| <value description="Rel. Humidity (linear)" shortcut="HUM" offset="8" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>250</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </scale> | ||
| </value> | ||
| <value description="Temperature (linear)" shortcut="TMP" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>250</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| <enum description="Availability of the Temperature Sensor" shortcut="TSN" offset="30" size="1"> | ||
| <item description="not available" value="0" /> | ||
| <item description="available" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x06" description="Light Sensor"> | ||
@@ -104,3 +499,505 @@ <profile type="0x01" description="Range 300lx to 60.000lx"> | ||
| </profiles> | ||
| <profiles func="0x08" description="Light, Temperature and Occupancy Sensor"> | ||
| <profile description="Range 0lx to 510lx, 0°C to +51°C and Occupancy Button" type="0x01"> | ||
| <data> | ||
| <value shortcut="SVC" description="Supply voltage (linear)" offset="0" size="8" unit="V"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>5.100000</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="ILL" description="Illumination (linear)" offset="8" size="8" unit="lx"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>510</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="TMP" description="Temperature (linear)" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>51</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="PIRS" description="PIR Status" offset="30" size="1"> | ||
| <item description="PIR on" value="0" /> | ||
| <item description="PIR off" value="1" /> | ||
| </enum> | ||
| <enum shortcut="OCC" description="Occupancy Button" offset="31" size="1"> | ||
| <item description="Button pressed" value="0" /> | ||
| <item description="Button released" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles description="Room Operating Panel" func="0x10"> | ||
| <profile description="Temperature Sensor and Set Point" type="0x03"> | ||
| <data> | ||
| <value shortcut="SP" description="Set Point (linear)" offset="8" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="TMP" description="Temperature (linear)" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| </profile> | ||
| <profile description="Temperature and Humidity Sensor and Set Point" type="0x12"> | ||
| <data> | ||
| <value shortcut="SP" description="Set Point (linear)" offset="0" size="8" unit=""> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="HUM" description="Rel. Humidity (linear)" offset="8" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>250</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="TMP" description="Temperature (linear)" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>250</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| <enum description="LRN Bit" shortcut="LRNB" offset="28" size="1"> | ||
| <item description="Teach-in telegram" value="0" /> | ||
| <item description="Data telegram" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles description="Controller Status" func="0x11"> | ||
| <profile description="Temperature Controller Output" type="0x02"> | ||
| <data> | ||
| <value shortcut="CVAR" description="Actual value of controller" offset="0" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="FAN" description="Actual value of fan" offset="8" size="8" unit="%"> | ||
| <item description="State 0 Manual" value="0" /> | ||
| <item description="State 1 Manual" value="1" /> | ||
| <item description="State 2 Manual" value="2" /> | ||
| <item description="State 3 Manual" value="3" /> | ||
| <item description="State 0 Automatic" value="16" /> | ||
| <item description="State 1 Automatic" value="17" /> | ||
| <item description="State 2 Automatic" value="18" /> | ||
| <item description="State 3 Automatic" value="19" /> | ||
| <item description="Not Available" value="255" /> | ||
| </enum> | ||
| <value shortcut="ASP" description="Actual Setpoint" offset="16" size="8" unit="C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>51.2</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="ALR" description="Alarm" offset="24" size="1"> | ||
| <item description="No alarm" value="0" /> | ||
| <item description="Alarm" value="1" /> | ||
| </enum> | ||
| <enum shortcut="CTM" description="Controller mode" offset="25" size="1"> | ||
| <item description="Heating" value="1" /> | ||
| <item description="Cooling" value="2" /> | ||
| <item description="Off" value="3" /> | ||
| </enum> | ||
| <enum shortcut="CTS" description="Controller state" offset="27" size="1"> | ||
| <item description="Automatic" value="0" /> | ||
| <item description="Override" value="1" /> | ||
| </enum> | ||
| <enum shortcut="ERH" description="Energy hold-off" offset="29" size="1"> | ||
| <item description="Normal" value="0" /> | ||
| <item description="Energy hold-off / Dew point" value="1" /> | ||
| </enum> | ||
| <enum shortcut="RO" description="Room occupancy" offset="30" size="2"> | ||
| <item description="Occupied" value="0" /> | ||
| <item description="Unoccupied" value="1" /> | ||
| <item description="StandBy" value="2" /> | ||
| <item description="Frost" value="3" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x14" description="Multi-Func Sensor"> | ||
| <profile type="0x01" description="Single Input Contact (Window/Door), Supply voltage monitor"> | ||
| <data> | ||
| <value description="Supply voltage / super cap. (linear); 251 - 255 reserved for error code" shortcut="SVC" offset="0" size="8" unit="V"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>250</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>5.0</max> | ||
| </scale> | ||
| </value> | ||
| <enum description="Contact" shortcut="CT" offset="31" size="1"> | ||
| <item description="open" value="1" /> | ||
| <item description="closed" value="0" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles description="HVAC Components" func="0x20"> | ||
| <profile description="Battery Powered Actuator (BI-DIR)" type="0x01"> | ||
| <data direction="1"> | ||
| <value shortcut="CV" description="Current Value" offset="0" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="SO" description="Service On" offset="8" size="1"> | ||
| <item description="off" value="0" /> | ||
| <item description="on" value="1" /> | ||
| </enum> | ||
| <enum shortcut="ENIE" description="Energy input enabled" offset="9" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="ES" description="Energy storage sufficiently charged" offset="10" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="BCAP" description="Battery capacity; change battery next days" offset="11" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="CCO" description="Contact, cover open" offset="12" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="FTS" description="Failure Temperature sensor, out of range" offset="13" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DWO" description="Detection, window open" offset="14" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="ACO" description="Actuator obstructed" offset="15" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <value shortcut="TMP" description="Temperature (linear)" offset="16" size="8" unit="°C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| </data> | ||
| <data direction="2"> | ||
| <value shortcut="SP" description="Valve Position" offset="0" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>100</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="TMP" description="Temperature from RCU" offset="8" size="8" unit="°C"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="RIN" description="Run init sequence" offset="16" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="LFS" description="Lift set" offset="17" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="VO" description="Valve open / maintenance" offset="18" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="VC" description="Valve closed" offset="19" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="SB" description="Summer bit, Reduction of energy consumption" offset="20" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="SPS" description="Set point selection" offset="21" size="1"> | ||
| <item description="Valve position" value="0" /> | ||
| <item description="Temperature set point" value="1" /> | ||
| </enum> | ||
| <enum shortcut="SPN" description="Set point inverse" offset="22" size="1"> | ||
| <item description="false" value="0" /> | ||
| <item description="true" value="1" /> | ||
| </enum> | ||
| <enum shortcut="RCU" description="Select function" offset="23" size="1"> | ||
| <item description="RCU" value="0" /> | ||
| <item description="service on" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x12" description="Atomated Meter Reading (AMR)"> | ||
| <profile type="0x01" description="Electricity"> | ||
| <data> | ||
| <value shortcut="MR" description="current value in W or cumulative value in kWh" offset="0" size="24" unit=""> | ||
| <range> | ||
| <min>0</min> | ||
| <max>16777215</max> | ||
| </range> | ||
| <scale> | ||
| <min>0.000000</min> | ||
| <max>16777215</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="TI" description="Tariff info" offset="24" size="4" unit=""> | ||
| <range> | ||
| <min>0</min> | ||
| <max>15</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>15</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="DT" description="Current value or cumulative value" offset="29" size="1"> | ||
| <item description="kWh" value="0" /> | ||
| <item description="W" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DIV" description="Divisor for value" offset="30" size="2"> | ||
| <item description="x/1" value="0" /> | ||
| <item description="x/10" value="1" /> | ||
| <item description="x/100" value="2" /> | ||
| <item description="x/1000" value="3" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x30" description="Digital Input"> | ||
| <profile type="0x03" description="Digital Inputs, Wake and Temperature"> | ||
| <data> | ||
| <value shortcut="TMP" description="Temperature (linear)" offset="8" size="8" unit="°C"> | ||
| <range> | ||
| <min>255</min> | ||
| <max>0</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>40</max> | ||
| </scale> | ||
| </value> | ||
| <enum shortcut="WA0" description="Value of wake signal" offset="19" size="1"> | ||
| <item description="Low" value="0" /> | ||
| <item description="High" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DI3" description="Digital Input 3" offset="20" size="1"> | ||
| <item description="Low" value="0" /> | ||
| <item description="High" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DI2" description="Digital Input 2" offset="21" size="1"> | ||
| <item description="Low" value="0" /> | ||
| <item description="High" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DI1" description="Digital Input 1" offset="22" size="1"> | ||
| <item description="Low" value="0" /> | ||
| <item description="High" value="1" /> | ||
| </enum> | ||
| <enum shortcut="DI0" description="Digital Input 0" offset="23" size="1"> | ||
| <item description="Low" value="0" /> | ||
| <item description="High" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| <profiles func="0x38" description="Central Command"> | ||
| <profile type="0x08" description="Gateway"> | ||
| <command description="Command ID" shortcut="CMD" offset="0" size="8"> | ||
| <item description="Dimming" value="2" /> | ||
| </command> | ||
| <data command="2"> | ||
| <enum shortcut="CMD" description="Command ID" offset="0" size="8"> | ||
| <rangeitem description="Command ID {value}" start="0" end="13" /> | ||
| </enum> | ||
| <value shortcut="EDIM" description="Dimming value" offset="8" size="8" unit="%"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </scale> | ||
| </value> | ||
| <value shortcut="RMP" description="Ramping time" offset="16" size="8" unit="s"> | ||
| <range> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </range> | ||
| <scale> | ||
| <min>0</min> | ||
| <max>255</max> | ||
| </scale> | ||
| </value> | ||
| <enum description="LRN Bit" shortcut="LRNB" offset="28" size="1"> | ||
| <item description="Teach-in Telegram" value="0" /> | ||
| <item description="Data Telegram" value="1" /> | ||
| </enum> | ||
| <enum description="Dimming Range" shortcut="EDIMR" offset="29" size="1"> | ||
| <item description="Absolute value" value="0" /> | ||
| <item description="Relative value" value="1" /> | ||
| </enum> | ||
| <enum description="Store final value" shortcut="STR" offset="30" size="1"> | ||
| <item description="No" value="0" /> | ||
| <item description="Yes" value="1" /> | ||
| </enum> | ||
| <enum description="Switching command" shortcut="SW" offset="31" size="1"> | ||
| <item description="Off" value="0" /> | ||
| <item description="On" value="1" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| </telegram> | ||
| <telegram rorg="0xD2" type="VLD" description="VLD Telegram"> | ||
| <profiles func="0x01" description="Electronic switches and dimmers with Energy Measurement and Local Control"> | ||
| <profile type="0x01" description="Electronic switch with Local Control"> | ||
| <command description="command indentifier" shortcut="CMD" offset="4" size="4"> | ||
| <item description="Actuator Set Output" value="1" /> | ||
| <!-- <item description="Actuator Set Local" value="2" /> --> | ||
| <!-- <item description="Actuator Status Query" value="3" /> --> | ||
| <item description="Actuator Status Response" value="4" /> | ||
| <!-- <item description="Actuator Set Measurement" value="5" /> --> | ||
| <!-- <item description="Actuator Measurement Query" value="6" /> --> | ||
| <!-- <item description="Actuator Measurement Response" value="7" /> --> | ||
| <!-- <item description="Actuator Set Pilot Wire Mode" value="8" /> --> | ||
| <!-- <item description="Actuator Pilot Wire Mode Query" value="9" /> --> | ||
| <!-- <item description="Actuator Pilot Wire Mode Response" value="10" /> --> | ||
| <!-- <item description="Actuator Set External Interface Settings" value="11" /> --> | ||
| <!-- <item description="Actuator External Interface Settings Query" value="12" /> --> | ||
| <!-- <item description="Actuator External Interface Settings Response" value="13" /> --> | ||
| </command> | ||
| <data command="4" bits="3"> | ||
| <enum description="Power Failure" shortcut="PF" offset="0" size="1"> | ||
| <item description="Power Failure Detection disabled/not supported" value="0" /> | ||
| <item description="Power Failure Detection enabled" value="1" /> | ||
| </enum> | ||
| <enum description="Power Failure Detection" shortcut="PFD" offset="1" size="1"> | ||
| <item description="Power Failure Detection not detected/not supported/disabled" value="0" /> | ||
| <item description="Power Failure Detection Detected" value="1" /> | ||
| </enum> | ||
| <enum description="Command indentifier" shortcut="CMD" offset="4" size="4"> | ||
| <rangeitem description="Command ID {value}" start="0" end="13" /> | ||
| </enum> | ||
| <enum description="Over current switch off" shortcut="OC" offset="8" size="1"> | ||
| <item description="Over current switch off: ready / not supported" value="0" /> | ||
| <item description="Over current switch off: executed" value="1" /> | ||
| </enum> | ||
| <enum description="Error level" shortcut="EL" offset="9" size="2"> | ||
| <item description="Error level 0: hardware OK" value="0" /> | ||
| <item description="Error level 1: hardware warning" value="1" /> | ||
| <item description="Error level 2: hardware failure" value="2" /> | ||
| <item description="Error level not supported" value="3" /> | ||
| </enum> | ||
| <enum description="I/O channel" shortcut="IO" offset="11" size="5"> | ||
| <rangeitem description="Output channel {value} (to load)" start="0" end="29" /> | ||
| <item description="Not applicable, do not use" value="30" /> | ||
| <item description="Input channel (from mains supply)" value="31" /> | ||
| </enum> | ||
| <enum description="Local control" shortcut="LC" offset="16" size="1"> | ||
| <item description="Local control disabled / not supported" value="0" /> | ||
| <item description="Local control enabled" value="1" /> | ||
| </enum> | ||
| <enum description="Output value" shortcut="OV" offset="17" size="7"> | ||
| <item description="Output value 0% or OFF" value="0" /> | ||
| <rangeitem description="Output value {value}% or ON" start="1" end="100" /> | ||
| <rangeitem description="Not used" start="101" end="126" /> | ||
| <item description="output value not valid / not set" value="127" /> | ||
| </enum> | ||
| </data> | ||
| <data command="1" bits="3"> | ||
| <enum description="Command indentifier" shortcut="CMD" offset="4" size="4"> | ||
| <rangeitem description="Command ID {value}" start="0" end="13" /> | ||
| </enum> | ||
| <enum description="Dim value" shortcut="DV" offset="8" size="3"> | ||
| <item description="Switch to new output value" value="0" /> | ||
| <item description="Dim to new output level - dim timer 1" value="1" /> | ||
| <item description="Dim to new output level - dim timer 2" value="2" /> | ||
| <item description="Dim to new output level - dim timer 3" value="3" /> | ||
| <item description="Stop dimming" value="4" /> | ||
| </enum> | ||
| <enum description="I/O channel" shortcut="IO" offset="11" size="5"> | ||
| <rangeitem description="Output channel {value} (to load)" start="0" end="29" /> | ||
| <item description="All output channels supported by the device" value="30" /> | ||
| <item description="Input channel (from mains supply)" value="31" /> | ||
| </enum> | ||
| <enum description="Output value" shortcut="OV" offset="17" size="7"> | ||
| <item description="Output value 0% or OFF" value="0" /> | ||
| <rangeitem description="Output value {value}% or ON" start="1" end="100" /> | ||
| <rangeitem description="Not used" start="101" end="126" /> | ||
| <item description="output value not valid / not set" value="127" /> | ||
| </enum> | ||
| </data> | ||
| </profile> | ||
| </profiles> | ||
| </telegram> | ||
| </telegrams> |
+302
-66
| # -*- encoding: utf-8 -*- | ||
| from __future__ import print_function, unicode_literals, division | ||
| from __future__ import print_function, unicode_literals, division, absolute_import | ||
| 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 | ||
| from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 | ||
| logger = logging.getLogger('enocean.protocol.packet') | ||
| eep = EEP() | ||
| class Packet(object): | ||
@@ -20,17 +19,33 @@ ''' | ||
| ''' | ||
| def __init__(self, type, data=[], optional=[]): | ||
| self.type = type | ||
| eep = EEP() | ||
| logger = logging.getLogger('enocean.protocol.packet') | ||
| def __init__(self, packet_type, data=None, optional=None): | ||
| self.packet_type = packet_type | ||
| self.rorg = RORG.UNDEFINED | ||
| self.rorg_func = None | ||
| self.rorg_type = None | ||
| self.data = data | ||
| self.optional = optional | ||
| self.bit_data = [] | ||
| self.bit_optional = [] | ||
| self.parsed = {} | ||
| self.rorg_manufacturer = None | ||
| if not isinstance(data, list) or data is None: | ||
| self.logger.warning('Replacing Packet.data with default value.') | ||
| self.data = [] | ||
| else: | ||
| self.data = data | ||
| if not isinstance(optional, list) or optional is None: | ||
| self.logger.warning('Replacing Packet.optional with default value.') | ||
| self.optional = [] | ||
| else: | ||
| self.optional = optional | ||
| self.status = 0 | ||
| self.parsed = OrderedDict({}) | ||
| self.repeater_count = 0 | ||
| self._profile = None | ||
| self.parse() | ||
| def __str__(self): | ||
| return '0x%02X %s %s %s' % (self.type, [hex(o) for o in self.data], [hex(o) for o in self.optional], self.parsed) | ||
| 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) | ||
@@ -41,31 +56,43 @@ def __unicode__(self): | ||
| def __eq__(self, other): | ||
| return self.type == other.type and self.rorg == other.rorg and self.data == other.data and self.optional == other.optional | ||
| return self.packet_type == other.packet_type and self.rorg == other.rorg and self.data == other.data and self.optional == other.optional | ||
| def _get_bit(self, byte, bit): | ||
| ''' Get bit value from byte ''' | ||
| return (byte >> bit) & 0x01 | ||
| @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) | ||
| def _combine_hex(self, data): | ||
| ''' Combine list of integer values to one big integer ''' | ||
| output = 0x00 | ||
| for i, d in enumerate(reversed(data)): | ||
| output |= (d << i * 8) | ||
| return output | ||
| @_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]) | ||
| def _to_bitarray(self, data, width=8): | ||
| ''' Convert data (list of integers, bytearray or integer) to bitarray ''' | ||
| if isinstance(data, list) or isinstance(data, bytearray): | ||
| data = self._combine_hex(data) | ||
| return [True if digit == '1' else False for digit in bin(data)[2:].zfill(width)] | ||
| # # 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)) | ||
| def _from_bitarray(self, data): | ||
| ''' Convert bit array back to integer ''' | ||
| return int(''.join(['1' if x else '0' for x in data]), 2) | ||
| # @_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]) | ||
| def _to_hex_string(self, data): | ||
| ''' Convert list of integers to a hex string, separated by ":" ''' | ||
| return ':'.join([('%02X' % o) for o in data]) | ||
| @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): | ||
| def parse_msg(buf, communicator=None): | ||
| ''' | ||
@@ -85,3 +112,3 @@ Parses message from buffer. | ||
| # Convert to list, as index -method isn't defined for bytearray | ||
| buf = buf[list(buf).index(0x55):] | ||
| buf = [ord(x) if not isinstance(x, int) else x for x in buf[list(buf).index(0x55):]] | ||
| try: | ||
@@ -110,3 +137,3 @@ data_len = (buf[1] << 8) | buf[2] | ||
| # Fail if doesn't match message | ||
| logger.error('Header CRC error!') | ||
| Packet.logger.error('Header CRC error!') | ||
| # Return CRC_MISMATCH | ||
@@ -116,3 +143,3 @@ return PARSE_RESULT.CRC_MISMATCH, buf, None | ||
| # Fail if doesn't match message | ||
| logger.error('Data CRC error!') | ||
| Packet.logger.error('Data CRC error!') | ||
| # Return CRC_MISMATCH | ||
@@ -123,24 +150,141 @@ return PARSE_RESULT.CRC_MISMATCH, buf, None | ||
| if packet_type == PACKET.RADIO: | ||
| p = RadioPacket(packet_type, data, opt_data) | ||
| # Need to handle UTE Teach-in here, as it's a separate packet type... | ||
| if data[0] == RORG.UTE: | ||
| packet = UTETeachIn(packet_type, data, opt_data, communicator=communicator) | ||
| # Send a response automatically, works only if | ||
| # - communicator is set | ||
| # - communicator.teach_in == True | ||
| packet.send_response() | ||
| else: | ||
| packet = RadioPacket(packet_type, data, opt_data) | ||
| elif packet_type == PACKET.RESPONSE: | ||
| p = ResponsePacket(packet_type, data, opt_data) | ||
| packet = ResponsePacket(packet_type, data, opt_data) | ||
| elif packet_type == PACKET.EVENT: | ||
| packet = EventPacket(packet_type, data, opt_data) | ||
| else: | ||
| p = Packet(packet_type, data, opt_data) | ||
| packet = Packet(packet_type, data, opt_data) | ||
| return PARSE_RESULT.OK, buf, p | ||
| 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, 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) | ||
| 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 | ||
| if self.rorg in [RORG.RPS, RORG.BS1, RORG.BS4]: | ||
| self.status = self.data[-1] | ||
| if self.rorg == RORG.VLD: | ||
| self.status = self.optional[-1] | ||
| if self.rorg in [RORG.RPS, RORG.BS1, RORG.BS4]: | ||
| # These message types should have repeater count in the last for bits of status. | ||
| self.repeater_count = enocean.utils.from_bitarray(self._bit_status[4:]) | ||
| return self.parsed | ||
| def parse_eep(self, func, type): | ||
| def select_eep(self, rorg_func, rorg_type, direction=None, command=None): | ||
| ''' Set EEP based on FUNC and TYPE ''' | ||
| # set EEP profile | ||
| self.rorg_func = rorg_func | ||
| self.rorg_type = rorg_type | ||
| self._profile = self.eep.find_profile(self._bit_data, self.rorg, rorg_func, rorg_type, direction, command) | ||
| return self._profile is not None | ||
| def parse_eep(self, rorg_func=None, rorg_type=None, direction=None, command=None): | ||
| ''' Parse EEP based on FUNC and TYPE ''' | ||
| provides, values = eep.get_values(self.rorg, func, type, self.bit_data) | ||
| # set EEP profile, if demanded | ||
| if rorg_func is not None and rorg_type is not None: | ||
| self.select_eep(rorg_func, rorg_type, direction, command) | ||
| # parse data | ||
| provides, values = self.eep.get_values(self._profile, self._bit_data, self._bit_status) | ||
| self.parsed.update(values) | ||
| return list(provides) | ||
| 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.type)] | ||
| ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] | ||
| ords.append(crc8.calc(ords[1:5])) | ||
@@ -154,8 +298,5 @@ ords.extend(self.data) | ||
| class RadioPacket(Packet): | ||
| destination = 0 | ||
| destination_hex = '' | ||
| destination = [0xFF, 0xFF, 0xFF, 0xFF] | ||
| dBm = 0 | ||
| status = 0 | ||
| sender = 0 | ||
| sender_hex = '' | ||
| sender = [0xFF, 0xFF, 0xFF, 0xFF] | ||
| learn = True | ||
@@ -168,9 +309,27 @@ 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, rorg, rorg_func, rorg_type, direction, command, destination, sender, learn, **kwargs) | ||
| @property | ||
| def sender_int(self): | ||
| return enocean.utils.combine_hex(self.sender) | ||
| @property | ||
| def sender_hex(self): | ||
| return enocean.utils.to_hex_string(self.sender) | ||
| @property | ||
| def destination_int(self): | ||
| return enocean.utils.combine_hex(self.destination) | ||
| @property | ||
| def destination_hex(self): | ||
| return enocean.utils.to_hex_string(self.destination) | ||
| def parse(self): | ||
| self.destination = self._combine_hex(self.optional[1:5]) | ||
| self.destination_hex = self._to_hex_string(self.optional[1:5]) | ||
| self.destination = self.optional[1:5] | ||
| self.dBm = -self.optional[5] | ||
| self.status = self.data[-1] | ||
| self.sender = self._combine_hex(self.data[-5:-1]) | ||
| self.sender_hex = self._to_hex_string(self.data[-5:-1]) | ||
| self.sender = self.data[-5:-1] | ||
| # Default to learn == True, as some devices don't have a learn button | ||
@@ -180,18 +339,16 @@ self.learn = True | ||
| self.rorg = self.data[0] | ||
| if self.rorg == RORG.RPS: | ||
| self.bit_data = self._to_bitarray(self.data[1], 8) | ||
| # parse learn bit and FUNC/TYPE, if applicable | ||
| if self.rorg == RORG.BS1: | ||
| self.bit_data = self._to_bitarray(self.data[1], 8) | ||
| self.learn = not self.bit_data[DB0.BIT_3] | ||
| self.learn = not self._bit_data[DB0.BIT_3] | ||
| if self.rorg == RORG.BS4: | ||
| self.bit_data = self._to_bitarray(self.data[1:5], 32) | ||
| self.learn = not self.bit_data[DB0.BIT_3] | ||
| self.learn = not self._bit_data[DB0.BIT_3] | ||
| if self.learn: | ||
| self.contains_eep = self.bit_data[DB0.BIT_7] | ||
| self.contains_eep = self._bit_data[DB0.BIT_7] | ||
| if self.contains_eep: | ||
| # Get rorg_func and rorg_type from an unidirectional learn packet | ||
| self.rorg_func = self._from_bitarray(self.bit_data[DB3.BIT_7:DB3.BIT_1]) | ||
| self.rorg_type = self._from_bitarray(self.bit_data[DB3.BIT_1:DB2.BIT_2]) | ||
| # Try to parse by EEP | ||
| self.parse_eep(self.rorg_func, self.rorg_type) | ||
| self.rorg_func = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_7:DB3.BIT_1]) | ||
| self.rorg_type = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_1:DB2.BIT_2]) | ||
| self.rorg_manufacturer = enocean.utils.from_bitarray(self._bit_data[DB2.BIT_2:DB0.BIT_7]) | ||
| self.logger.debug('learn received, EEP detected, RORG: 0x%02X, FUNC: 0x%02X, TYPE: 0x%02X, Manufacturer: 0x%02X' % (self.rorg, self.rorg_func, self.rorg_type, self.rorg_manufacturer)) | ||
@@ -201,2 +358,81 @@ return super(RadioPacket, self).parse() | ||
| class UTETeachIn(RadioPacket): | ||
| # Request types | ||
| TEACH_IN = 0b00 | ||
| DELETE = 0b01 | ||
| NOT_SPECIFIC = 0b10 | ||
| # Response types | ||
| NOT_ACCEPTED = [False, False] | ||
| TEACHIN_ACCEPTED = [False, True] | ||
| DELETE_ACCEPTED = [True, False] | ||
| EEP_NOT_SUPPORTED = [True, True] | ||
| unidirectional = False | ||
| response_expected = False | ||
| number_of_channels = 0xFF | ||
| rorg_of_eep = RORG.UNDEFINED | ||
| request_type = NOT_SPECIFIC | ||
| channel = None | ||
| contains_eep = True | ||
| def __init__(self, packet_type, data=None, optional=None, communicator=None): | ||
| self.__communicator = communicator | ||
| super(UTETeachIn, self).__init__(packet_type=packet_type, data=data, optional=optional) | ||
| @property | ||
| def bidirectional(self): | ||
| return not self.unidirectional | ||
| @property | ||
| def teach_in(self): | ||
| return self.request_type != self.DELETE | ||
| @property | ||
| def delete(self): | ||
| return self.request_type == self.DELETE | ||
| def parse(self): | ||
| super(UTETeachIn, self).parse() | ||
| self.unidirectional = not self._bit_data[DB6.BIT_7] | ||
| self.response_expected = not self._bit_data[DB6.BIT_6] | ||
| self.request_type = enocean.utils.from_bitarray(self._bit_data[DB6.BIT_5:DB6.BIT_3]) | ||
| self.rorg_manufacturer = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_2:DB2.BIT_7] + self._bit_data[DB4.BIT_7:DB3.BIT_7]) | ||
| self.channel = self.data[2] | ||
| self.rorg_type = self.data[5] | ||
| self.rorg_func = self.data[6] | ||
| self.rorg_of_eep = self.data[7] | ||
| if self.teach_in: | ||
| self.learn = True | ||
| return self.parsed | ||
| def _create_response_packet(self, sender_id, response=TEACHIN_ACCEPTED): | ||
| # Create data: | ||
| # - Respond with same RORG (UTE Teach-in) | ||
| # - Always use bidirectional communication, set response code, set command identifier. | ||
| # - Databytes 5 to 0 are copied from the original message | ||
| # - Set sender id and status | ||
| data = [self.rorg] + \ | ||
| [enocean.utils.from_bitarray([True, False] + response + [False, False, False, True])] + \ | ||
| self.data[2:8] + \ | ||
| sender_id + [0] | ||
| # Always use 0x03 to indicate sending, attach sender ID, dBm, and security level | ||
| optional = [0x03] + self.sender + [0xFF, 0x00] | ||
| return RadioPacket(PACKET.RADIO, data=data, optional=optional) | ||
| def send_response(self, response=TEACHIN_ACCEPTED): | ||
| if self.__communicator is None: | ||
| self.logger.error('Communicator not set, cannot send UTE teach-in response.') | ||
| return | ||
| if not self.__communicator.teach_in: | ||
| self.logger.info('Communicator not set to teach-in mode, not sending UTE teach-in response.') | ||
| return | ||
| self.logger.info('Sending response to UTE teach-in.') | ||
| self.__communicator.send( | ||
| self._create_response_packet(self.__communicator.base_id)) | ||
| class ResponsePacket(Packet): | ||
@@ -203,0 +439,0 @@ response = 0 |
| #!/usr/bin/env python | ||
| # -*- encoding: utf-8 -*- | ||
| from enocean.consolelogger import init_logging | ||
| import enocean.utils | ||
| from enocean.communicators.serialcommunicator import SerialCommunicator | ||
| from enocean.protocol.packet import Packet | ||
| from enocean.protocol.packet import RadioPacket | ||
| from enocean.protocol.constants import PACKET, RORG | ||
@@ -15,21 +16,40 @@ import sys | ||
| p = Packet(PACKET.COMMON_COMMAND, [0x08]) | ||
| def assemble_radio_packet(transmitter_id): | ||
| return RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, | ||
| sender=transmitter_id, | ||
| CV=50, | ||
| TMP=21.5, | ||
| ES='true') | ||
| init_logging() | ||
| c = SerialCommunicator() | ||
| c.start() | ||
| c.send(p) | ||
| while c.is_alive(): | ||
| communicator = SerialCommunicator() | ||
| communicator.start() | ||
| print('The Base ID of your module is %s.' % enocean.utils.to_hex_string(communicator.base_id)) | ||
| if communicator.base_id is not None: | ||
| print('Sending example package.') | ||
| communicator.send(assemble_radio_packet(communicator.base_id)) | ||
| # endless loop receiving radio packets | ||
| while communicator.is_alive(): | ||
| try: | ||
| # Loop to empty the queue... | ||
| p = c.receive.get(block=True, timeout=1) | ||
| if p.type == PACKET.RADIO and p.rorg == RORG.BS4: | ||
| for k in p.parse_eep(0x02, 0x05): | ||
| print('%s: %s' % (k, p.parsed[k])) | ||
| if p.type == PACKET.RADIO and p.rorg == RORG.BS1: | ||
| for k in p.parse_eep(0x00, 0x01): | ||
| print('%s: %s' % (k, p.parsed[k])) | ||
| if p.type == PACKET.RADIO and p.rorg == RORG.RPS: | ||
| for k in p.parse_eep(0x02, 0x04): | ||
| print('%s: %s' % (k, p.parsed[k])) | ||
| packet = communicator.receive.get(block=True, timeout=1) | ||
| if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS4: | ||
| # parse packet with given FUNC and TYPE | ||
| for k in packet.parse_eep(0x02, 0x05): | ||
| print('%s: %s' % (k, packet.parsed[k])) | ||
| if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS1: | ||
| # alternatively you can select FUNC and TYPE explicitely | ||
| packet.select_eep(0x00, 0x01) | ||
| # parse it | ||
| packet.parse_eep() | ||
| for k in packet.parsed: | ||
| print('%s: %s' % (k, packet.parsed[k])) | ||
| if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.RPS: | ||
| for k in packet.parse_eep(0x02, 0x02): | ||
| print('%s: %s' % (k, packet.parsed[k])) | ||
| except queue.Empty: | ||
@@ -43,3 +63,3 @@ continue | ||
| if c.is_alive(): | ||
| c.stop() | ||
| if communicator.is_alive(): | ||
| communicator.stop() |
+1
-1
| Metadata-Version: 1.0 | ||
| Name: enocean | ||
| Version: 0.31 | ||
| Version: 0.40 | ||
| Summary: EnOcean serial protocol implementation | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/kipe/enocean |
+0
-1
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
| tag_svn_revision = 0 | ||
+1
-1
@@ -9,3 +9,3 @@ #!/usr/bin/env python | ||
| name='enocean', | ||
| version='0.31', | ||
| version='0.40', | ||
| description='EnOcean serial protocol implementation', | ||
@@ -12,0 +12,0 @@ author='Kimmo Huoman', |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
85562
209.49%24
9.09%1050
73.84%