diff --git a/enocean/__init__.py b/enocean/__init__.py
index dcbfb629d..2a0f9c241 100755
--- a/enocean/__init__.py
+++ b/enocean/__init__.py
@@ -21,172 +21,45 @@
#########################################################################
import serial
-import os
-import sys
-import struct
-import time
+import logging
import threading
-from lib.item import Items #what for?
-from . import eep_parser
-from . import prepare_packet_data
-from lib.model.smartplugin import *
+from time import sleep
+
+from lib.model.smartplugin import SmartPlugin
+from lib.item import Items
+
+from .protocol import CRC
+from .protocol.eep_parser import EEP_Parser
+from .protocol.packet_data import Packet_Data
+from .protocol.constants import (
+ PACKET, PACKET_TYPE, COMMON_COMMAND, SMART_ACK, EVENT, RETURN_CODE, RORG
+)
from .webif import WebInterface
-FCSTAB = [
- 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
- 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
- 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
- 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
- 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
- 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
- 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
- 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
- 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
- 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
- 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
- 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
- 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
- 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
- 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
- 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
- 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
- 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
- 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
- 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
- 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
- 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
- 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
- 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
- 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
- 0x76, 0x71, 0x78, 0x7f, 0x6A, 0x6d, 0x64, 0x63,
- 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
- 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
- 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
- 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8D, 0x84, 0x83,
- 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
- 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3
- ]
-
-################################
-### --- Packet Sync Byte --- ###
-################################
-PACKET_SYNC_BYTE = 0x55 # PACKET SYNC BYTE
-
-
-############################
-### --- Packet Types --- ###
-############################
-
-PACKET_TYPE_RADIO = 0x01 # RADIO ERP1
-PACKET_TYPE_RESPONSE = 0x02 # RESPONSE
-PACKET_TYPE_RADIO_SUB_TEL = 0x03 # RADIO_SUB_TEL
-PACKET_TYPE_EVENT = 0x04 # EVENT
-PACKET_TYPE_COMMON_COMMAND = 0x05 # COMMON COMMAND
-PACKET_TYPE_SMART_ACK_COMMAND = 0x06 # SMART ACK COMMAND
-PACKET_REMOTE_MAN_COMMAND = 0x07 # REMOTE MANAGEMENT COMMAND
-PACKET_TYPE_RADIO_MESSAGE = 0x09 # RADIO MESSAGE
-PACKET_TYPE_RADIO_ERP2 = 0x0A # RADIO ERP2
-PACKET_TYPE_RADIO_802_15_4 = 0x10 # RADIO_802_15_4
-PACKET_TYPE_COMMAND_2_4 = 0x11 # COMMAND_2_4
-
-
-############################################
-### --- List of Common Command Codes --- ###
-############################################
-
-CO_WR_SLEEP = 0x01 # Order to enter in energy saving mode
-CO_WR_RESET = 0x02 # Order to reset the device
-CO_RD_VERSION = 0x03 # Read the device (SW) version /(HW) version, chip ID etc.
-CO_RD_SYS_LOG = 0x04 # Read system log from device databank
-CO_WR_SYS_LOG = 0x05 # Reset System log from device databank
-CO_WR_BIST = 0x06 # Perform built in self test
-CO_WR_IDBASE = 0x07 # Write ID range base number
-CO_RD_IDBASE = 0x08 # Read ID range base number
-CO_WR_REPEATER = 0x09 # Write Repeater Level off,1,2
-CO_RD_REPEATER = 0x0A # Read Repeater Level off,1,2
-CO_WR_FILTER_ADD = 0x0B # Add filter to filter list
-CO_WR_FILTER_DEL = 0x0C # Delete filter from filter list
-CO_WR_FILTER_DEL_ALL = 0x0D # Delete all filter
-CO_WR_FILTER_ENABLE = 0x0E # Enable/Disable supplied filters
-CO_RD_FILTER = 0x0F # Read supplied filters
-CO_WR_WAIT_MATURITY = 0x10 # Waiting till end of maturity time before received radio telegrams will transmitted
-CO_WR_SUBTEL = 0x11 # Enable/Disable transmitting additional subtelegram info
-CO_WR_MEM = 0x12 # Write x bytes of the Flash, XRAM, RAM0 …
-CO_RD_MEM = 0x13 # Read x bytes of the Flash, XRAM, RAM0 ….
-CO_RD_MEM_ADDRESS = 0x14 # Feedback about the used address and length of the configarea and the Smart Ack Table
-CO_RD_SECURITY = 0x15 # Read own security information (level, key)
-CO_WR_SECURITY = 0x16 # Write own security information (level, key)
-CO_WR_LEARNMODE = 0x17 # Function: Enables or disables learn mode of Controller.
-CO_RD_LEARNMODE = 0x18 # Function: Reads the learn-mode state of Controller.
-CO_WR_SECUREDEVICE_ADD = 0x19 # Add a secure device
-CO_WR_SECUREDEVICE_DEL = 0x1A # Delete a secure device
-CO_RD_SECUREDEVICE_BY_INDEX = 0x1B # Read secure device by index
-CO_WR_MODE = 0x1C # Sets the gateway transceiver mode
-CO_RD_NUMSECUREDEVICES = 0x1D # Read number of taught in secure devices
-CO_RD_SECUREDEVICE_BY_ID = 0x1E # Read secure device by ID
-CO_WR_SECUREDEVICE_ADD_PSK = 0x1F # Add Pre-shared key for inbound secure device
-CO_WR_SECUREDEVICE_SENDTEACHIN = 0x20 # Send secure Teach-In message
-CO_WR_TEMPORARY_RLC_WINDOW = 0x21 # Set the temporary rolling-code window for every taught-in devic
-CO_RD_SECUREDEVICE_PSK = 0x22 # Read PSK
-CO_RD_DUTYCYCLE_LIMIT = 0x23 # Read parameters of actual duty cycle limit
-CO_SET_BAUDRATE = 0x24 # Modifies the baud rate of the EnOcean device
-CO_GET_FREQUENCY_INFO = 0x25 # Reads Frequency and protocol of the Device
-CO_GET_STEPCODE = 0x27 # Reads Hardware Step code and Revision of the Device
-
-
-###################################
-### --- List of Event Codes --- ###
-###################################
-
-SA_RECLAIM_NOT_SUCCESSFUL = 0x01 # Informs the backbone of a Smart Ack Client to not successful reclaim.
-SA_CONFIRM_LEARN = 0x02 # Used for SMACK to confirm/discard learn in/out
-SA_LEARN_ACK = 0x03 # Inform backbone about result of learn request
-CO_READY = 0x04 # Inform backbone about the readiness for operation
-CO_EVENT_SECUREDEVICES = 0x05 # Informs about a secure device
-CO_DUTYCYCLE_LIMIT = 0x06 # Informs about duty cycle limit
-CO_TRANSMIT_FAILED = 0x07 # Informs that the device was not able to send a telegram.
-
-
-###########################################
-### --- Smart Acknowledge Defines: --- ###
-###########################################
-
-SA_WR_LEARNMODE = 0x01 # Set/Reset Smart Ack learn mode
-SA_RD_LEARNMODE = 0x02 # Get Smart Ack learn mode state
-SA_WR_LEARNCONFIRM = 0x03 # Used for Smart Ack to add or delete a mailbox of a client
-SA_WR_CLIENTLEARNRQ = 0x04 # Send Smart Ack Learn request (Client)
-SA_WR_RESET = 0x05 # Send reset command to a Smart Ack client
-SA_RD_LEARNEDCLIENTS = 0x06 # Get Smart Ack learned sensors / mailboxes
-SA_WR_RECLAIMS = 0x07 # Set number of reclaim attempts
-SA_WR_POSTMASTER = 0x08 # Activate/Deactivate Post master functionality
-
-SENT_RADIO_PACKET = 0xFF
-SENT_ENCAPSULATED_RADIO_PACKET = 0xA6
class EnOcean(SmartPlugin):
ALLOW_MULTIINSTANCE = False
- PLUGIN_VERSION = "1.4.0"
+ PLUGIN_VERSION = "1.4.2"
-
def __init__(self, sh):
- """
- Initializes the plugin.
-
- """
+ """ Initializes the plugin. """
# Call init code of parent class (SmartPlugin)
super().__init__()
- self._sh = sh
self.port = self.get_parameter_value("serialport")
+ self._log_unknown_msg = self.get_parameter_value("log_unknown_messages")
+ self._connect_retries = self.get_parameter_value("retry")
+ self._retry_cycle = self.get_parameter_value("retry_cycle")
tx_id = self.get_parameter_value("tx_id")
- if (len(tx_id) < 8):
+ if len(tx_id) < 8:
self.tx_id = 0
self.logger.warning('No valid enocean stick ID configured. Transmitting is not supported')
else:
self.tx_id = int(tx_id, 16)
self.logger.info(f"Stick TX ID configured via plugin.conf to: {tx_id}")
- self._log_unknown_msg = self.get_parameter_value("log_unknown_messages")
+#
+ self._items = []
self._tcm = None
self._cmd_lock = threading.Lock()
self._response_lock = threading.Condition()
@@ -196,421 +69,168 @@ def __init__(self, sh):
self.UTE_listen = False
self.unknown_sender_id = 'None'
self._block_ext_out_msg = False
- # call init of eep_parser
- self.eep_parser = eep_parser.EEP_Parser()
- # call init of prepare_packet_data
- self.prepare_packet_data = prepare_packet_data.Prepare_Packet_Data(self)
-
- self.init_webinterface(WebInterface)
-
-
- def eval_telegram(self, sender_id, data, opt):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << eval_telegram >>")
- for item in self._items:
- # validate id for item id:
- if item.conf['enocean_id'] == sender_id:
- #print ("validated {0} for {1}".format(sender_id,item))
- #print ("try to get value for: {0} and {1}".format(item.conf['enocean_rorg'][0],item.conf['enocean_rorg'][1]))
- rorg = item.conf['enocean_rorg']
- eval_value = item.conf['enocean_value']
- if rorg in RADIO_PAYLOAD_VALUE: # check if RORG exists
- pl = eval(RADIO_PAYLOAD_VALUE[rorg]['payload_idx'])
- #could be nicer
- for entity in RADIO_PAYLOAD_VALUE:
- if (rorg == entity) and (eval_value in RADIO_PAYLOAD_VALUE[rorg]['entities']):
- value_dict = RADIO_PAYLOAD_VALUE[rorg]['entities']
- value = eval(RADIO_PAYLOAD_VALUE[rorg]['entities'][eval_value])
- if logger_debug:
- self.logger.debug(f"Resulting value: {value} for {item}")
- if value: # not sure about this
- item(value, self.get_shortname(), 'RADIO')
-
- def _process_packet_type_event(self, data, optional):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("call function << _process_packet_type_event >>")
- event_code = data[0]
- if(event_code == SA_RECLAIM_NOT_SUCCESSFUL):
- self.logger.error("SA reclaim was not successful")
- elif(event_code == SA_CONFIRM_LEARN):
- self.logger.info("Requesting how to handle confirm/discard learn in/out")
- elif(event_code == SA_LEARN_ACK):
- self.logger.info("SA lern acknowledged")
- elif(event_code == CO_READY):
- self.logger.info("Controller is ready for operation")
- elif(event_code == CO_TRANSMIT_FAILED):
- self.logger.error("Telegram transmission failed")
- elif(event_code == CO_DUTYCYCLE_LIMIT):
- self.logger.warning("Duty cycle limit reached")
- elif(event_code == CO_EVENT_SECUREDEVICES):
- self.logger.info("Secure device event packet received")
- else:
- self.logger.warning("Unknown event packet received")
-
- def _rocker_sequence(self, item, sender_id, sequence):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << _rocker_sequence >>")
- try:
- for step in sequence:
- event, relation, delay = step.split()
- #self.logger.debug("waiting for {} {} {}".format(event, relation, delay))
- if item._enocean_rs_events[event.upper()].wait(float(delay)) != (relation.upper() == "WITHIN"):
- if logger_debug:
- self.logger.debug(f"NOT {step} - aborting sequence!")
- return
- else:
- if logger_debug:
- self.logger.debug(f"{step}")
- item._enocean_rs_events[event.upper()].clear()
- continue
- value = True
- if 'enocean_rocker_action' in item.conf:
- if item.conf['enocean_rocker_action'].upper() == "UNSET":
- value = False
- elif item.conf['enocean_rocker_action'].upper() == "TOGGLE":
- value = not item()
- item(value, self.get_shortname(), "{:08X}".format(sender_id))
- except Exception as e:
- self.logger.error(f'Error handling enocean_rocker_sequence \"{sequence}\" - {e}'.format(sequence, e))
-
- def _process_packet_type_radio(self, data, optional):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << _process_packet_type_radio >>")
- #self.logger.warning("Processing radio message with data = [{}] / optional = [{}]".format(', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
-
- choice = data[0]
- payload = data[1:-5]
- sender_id = int.from_bytes(data[-5:-1], byteorder='big', signed=False)
- status = data[-1]
- repeater_cnt = status & 0x0F
- self.logger.info("Radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
-
- if (len(optional) == 7):
- subtelnum = optional[0]
- dest_id = int.from_bytes(optional[1:5], byteorder='big', signed=False)
- dBm = -optional[5]
- SecurityLevel = optional[6]
- if logger_debug:
- self.logger.debug("Radio message with additional info: subtelnum = {} / dest_id = {:08X} / signal = {}dBm / SecurityLevel = {}".format(subtelnum, dest_id, dBm, SecurityLevel))
- if (choice == 0xD4) and (self.UTE_listen == True):
- self.logger.info("Call send_UTE_response")
- self._send_UTE_response(data, optional)
- if sender_id in self._rx_items:
- if logger_debug:
- self.logger.debug("Sender ID found in item list")
- # iterate over all eep known for this id and get list of associated items
- for eep,items in self._rx_items[sender_id].items():
- # check if choice matches first byte in eep (this seems to be the only way to find right eep for this particular packet)
- if eep.startswith("{:02X}".format(choice)):
- # call parser for particular eep - returns dictionary with key-value pairs
- results = self.eep_parser.Parse(eep, payload, status)
- if logger_debug:
- self.logger.debug(f"Radio message results = {results}")
- if 'DEBUG' in results:
- self.logger.warning("DEBUG Info: processing radio message with data = [{}] / optional = [{}]".format(', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
- self.logger.warning(f"Radio message results = {results}")
- self.logger.warning("Radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
+ self.log_for_debug = self.logger.isEnabledFor(logging.DEBUG)
- for item in items:
- rx_key = item.conf['enocean_rx_key'].upper()
- if rx_key in results:
- if 'enocean_rocker_sequence' in item.conf:
- try:
- if hasattr(item, '_enocean_rs_thread') and item._enocean_rs_thread.is_alive():
- if results[rx_key]:
- if logger_debug:
- self.logger.debug("Sending pressed event")
- item._enocean_rs_events["PRESSED"].set()
- else:
- if logger_debug:
- self.logger.debug("Sending released event")
- item._enocean_rs_events["RELEASED"].set()
- elif results[rx_key]:
- item._enocean_rs_events = {'PRESSED': threading.Event(), 'RELEASED': threading.Event()}
- item._enocean_rs_thread = threading.Thread(target=self._rocker_sequence, name="enocean-rs", args=(item, sender_id, item.conf['enocean_rocker_sequence'].split(','), ))
- #self.logger.info("starting enocean_rocker_sequence thread")
- item._enocean_rs_thread.daemon = True
- item._enocean_rs_thread.start()
- except Exception as e:
- self.logger.error(f"Error handling enocean_rocker_sequence: {e}")
- else:
- item(results[rx_key], self.get_shortname(), "{:08X}".format(sender_id))
- elif (sender_id <= self.tx_id + 127) and (sender_id >= self.tx_id):
- if logger_debug:
- self.logger.debug("Received repeated enocean stick message")
- else:
- self.unknown_sender_id = "{:08X}".format(sender_id)
- if self._log_unknown_msg:
- self.logger.info("Unknown ID = {:08X}".format(sender_id))
- self.logger.warning("Unknown device sent radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
-
-
- def _process_packet_type_smart_ack_command(self, data, optional):
- self.logger.warning("Smart acknowledge command 0x06 received but not supported at the moment")
+ # init eep_parser
+ self.eep_parser = EEP_Parser(self.logger)
+ # init prepare_packet_data
+ self.prepare_packet_data = Packet_Data(self)
- def _process_packet_type_response(self, data, optional):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << _process_packet_type_response >>")
- RETURN_CODES = ['OK', 'ERROR', 'NOT SUPPORTED', 'WRONG PARAM', 'OPERATION DENIED']
- if (self._last_cmd_code == SENT_RADIO_PACKET) and (len(data) == 1):
- if logger_debug:
- self.logger.debug(f"Sending command returned code = {RETURN_CODES[data[0]]}")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_WR_RESET) and (len(data) == 1):
- self.logger.info(f"Reset returned code = {RETURN_CODES[data[0]]}")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_WR_LEARNMODE) and (len(data) == 1):
- self.logger.info(f"Write LearnMode returned code = {RETURN_CODES[data[0]]}")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_RD_VERSION):
- if (data[0] == 0) and (len(data) == 33):
- self.logger.info("Chip ID = 0x{} / Chip Version = 0x{}".format(''.join(['%02x' % b for b in data[9:13]]), ''.join(['%02x' % b for b in data[13:17]])))
- self.logger.info("APP version = {} / API version = {} / App description = {}".format('.'.join(['%d' % b for b in data[1:5]]), '.'.join(['%d' % b for b in data[5:9]]), ''.join(['%c' % b for b in data[17:33]])))
- elif (data[0] == 0) and (len(data) == 0):
- self.logger.error("Reading version: No answer")
- else:
- self.logger.error(f"Reading version returned code = {RETURN_CODES[data[0]]}, length = {len(data)}")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_RD_IDBASE):
- if (data[0] == 0) and (len(data) == 5):
- self.logger.info("Base ID = 0x{}".format(''.join(['%02x' % b for b in data[1:5]])))
- if (self.tx_id == 0):
- self.tx_id = int.from_bytes(data[1:5], byteorder='big', signed=False)
- self.logger.info("Transmit ID set set automatically by reading chips BaseID")
- if (len(optional) == 1):
- self.logger.info(f"Remaining write cycles for Base ID = {optional[0]}")
- elif (data[0] == 0) and (len(data) == 0):
- self.logger.error("Reading Base ID: No answer")
- else:
- self.logger.error(f"Reading Base ID returned code = {RETURN_CODES[data[0]]} and {len(data)} bytes")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_WR_BIST):
- if (data[0] == 0) and (len(data) == 2):
- if (data[1] == 0):
- self.logger.info("Built in self test result: All OK")
- else:
- self.logger.info(f"Built in self test result: Problem, code = {data[1]}")
- elif (data[0] == 0) and (len(data) == 0):
- self.logger.error("Doing built in self test: No answer")
- else:
- self.logger.error(f"Doing built in self test returned code = {RETURN_CODES[data[0]]}")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_RD_LEARNMODE):
- if (data[0] == 0) and (len(data) == 2):
- self.logger.info("Reading LearnMode = 0x{}".format(''.join(['%02x' % b for b in data[1]])))
- if (len(optional) == 1):
- self.logger.info("Learn channel = {}".format(optional[0]))
- elif (data[0] == 0) and (len(data) == 0):
- self.logger.error("Reading LearnMode: No answer")
- elif (self._last_packet_type == PACKET_TYPE_COMMON_COMMAND) and (self._last_cmd_code == CO_RD_NUMSECUREDEVICES):
- if (data[0] == 0) and (len(data) == 2):
- self.logger.info("Number of taught in devices = 0x{}".format(''.join(['%02x' % b for b in data[1]])))
- elif (data[0] == 0) and (len(data) == 0):
- self.logger.error("Reading NUMSECUREDEVICES: No answer")
- elif (data[0] == 2) and (len(data) == 1):
- self.logger.error("Reading NUMSECUREDEVICES: Command not supported")
- else:
- self.logger.error("Reading NUMSECUREDEVICES: Unknown error")
- elif (self._last_packet_type == PACKET_TYPE_SMART_ACK_COMMAND) and (self._last_cmd_code == SA_WR_LEARNMODE):
- self.logger.info(f"Setting SmartAck mode returned code = {RETURN_CODES[data[0]]}")
- elif (self._last_packet_type == PACKET_TYPE_SMART_ACK_COMMAND) and (self._last_cmd_code == SA_RD_LEARNEDCLIENTS):
- if (data[0] == 0):
- self.logger.info(f"Number of smart acknowledge mailboxes = {int((len(data)-1)/9)}")
- else:
- self.logger.error(f"Requesting SmartAck mailboxes returned code = {RETURN_CODES[data[0]]}")
- else:
- self.logger.error("Processing unexpected response with return code = {} / data = [{}] / optional = [{}]".format(RETURN_CODES[data[0]], ', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
- self._response_lock.acquire()
- self._response_lock.notify()
- self._response_lock.release()
+ # init crc parser
+ self.crc = CRC()
- def _startup(self):
- self.logger.debug("Call function << _startup >>")
- # request one time information
- self.logger.info("Resetting device")
- self._send_common_command(CO_WR_RESET)
- self.logger.info("Requesting id-base")
- self._send_common_command(CO_RD_IDBASE)
- self.logger.info("Requesting version information")
- self._send_common_command(CO_RD_VERSION)
- self.logger.debug("Ending connect-thread")
+ self.init_webinterface(WebInterface)
def run(self):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << run >>")
+ if self.log_for_debug:
+ self.logger.debug("Run method called")
+
self.alive = True
self.UTE_listen = False
-
- # open serial or serial2TCP device:
- try:
- self._tcm = serial.serial_for_url(self.port, 57600, timeout=1.5)
- except Exception as e:
- self._tcm = None
- self._init_complete = False
- self.logger.error(f"Exception occurred during serial open: {e}")
- return
- else:
- self.logger.info(f"Serial port successfully opened at port {self.port}")
-
- t = threading.Thread(target=self._startup, name="enocean-startup")
- # if you need to create child threads, do not make them daemon = True!
- # They will not shutdown properly. (It's a python bug)
- t.daemon = False
- t.start()
msg = []
+
while self.alive:
+
+ # just try connecting anytime the serial object is not initialized
+ connect_count = 0
+ while self._tcm is None and self.alive:
+ if self._connect_retries > 0 and connect_count >= self._connect_retries:
+ self.alive = False
+ break
+ if not self.connect():
+ connect_count += 1
+ self.logger.info(f'connecting failed {connect_count} times. Retrying after 5 seconds...')
+ sleep(self._retry_cycle)
+
+ # main loop, read from device
+ readin = None
try:
readin = self._tcm.read(1000)
except Exception as e:
- self.logger.error(f"Exception during tcm read occurred: {e}")
- break
- else:
- if readin:
- msg += readin
- if logger_debug:
- self.logger.debug("Data received")
- # check if header is complete (6bytes including sync)
- # 0x55 (SYNC) + 4bytes (HEADER) + 1byte(HEADER-CRC)
- while (len(msg) >= 6):
- #check header for CRC
- if (msg[0] == PACKET_SYNC_BYTE) and (self._calc_crc8(msg[1:5]) == msg[5]):
- # header bytes: sync; length of data (2); optional length; packet type; crc
- data_length = (msg[1] << 8) + msg[2]
- opt_length = msg[3]
- packet_type = msg[4]
- msg_length = data_length + opt_length + 7
- if logger_debug:
- self.logger.debug("Received header with data_length = {} / opt_length = 0x{:02x} / type = {}".format(data_length, opt_length, packet_type))
-
- # break if msg is not yet complete:
- if (len(msg) < msg_length):
- break
-
- # msg complete
- if (self._calc_crc8(msg[6:msg_length - 1]) == msg[msg_length - 1]):
- if logger_debug:
- self.logger.debug("Accepted package with type = 0x{:02x} / len = {} / data = [{}]!".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
- data = msg[6:msg_length - (opt_length + 1)]
- optional = msg[(6 + data_length):msg_length - 1]
- if (packet_type == PACKET_TYPE_RADIO):
- self._process_packet_type_radio(data, optional)
- elif (packet_type == PACKET_TYPE_SMART_ACK_COMMAND):
- self._process_packet_type_smart_ack_command(data, optional)
- elif (packet_type == PACKET_TYPE_RESPONSE):
- self._process_packet_type_response(data, optional)
- elif (packet_type == PACKET_TYPE_EVENT):
- self._process_packet_type_event(data, optional)
- else:
- self.logger.error("Received packet with unknown type = 0x{:02x} - len = {} / data = [{}]".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
+ if self.alive:
+ self.logger.error(f"Exception during tcm read occurred: {e}")
+ # reset serial device
+ try:
+ self._tcm.close()
+ except Exception:
+ pass
+ self._tcm = None
+ continue
+
+ if readin:
+ msg += readin
+ if self.log_for_debug:
+ self.logger.debug(f"Data received: {readin}")
+ # check if header is complete (6bytes including sync)
+ # 0x55 (SYNC) + 4bytes (HEADER) + 1byte(HEADER-CRC)
+ while len(msg) >= 6:
+ # check header for CRC
+ if msg[0] == PACKET.SYNC_BYTE and msg[5] == self.crc(msg[1:5]):
+ # header bytes: sync; length of data (2); optional length; packet type; crc
+ data_length = (msg[1] << 8) + msg[2]
+ opt_length = msg[3]
+ packet_type = msg[4]
+ msg_length = data_length + opt_length + 7
+ if self.log_for_debug:
+ self.logger.debug(f"Received header with data_length = {data_length} / opt_length = 0x{opt_length:02x} / type = {packet_type}")
+
+ # break if msg is not yet complete:
+ if len(msg) < msg_length:
+ break
+
+ # msg complete
+ if self.crc(msg[6:msg_length - 1]) == msg[msg_length - 1]:
+ if self.log_for_debug:
+ self.logger.debug("Accepted package with type = 0x{:02x} / len = {} / data = [{}]!".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
+ data = msg[6:msg_length - (opt_length + 1)]
+ optional = msg[data_length + 6:msg_length - 1]
+ if packet_type == PACKET_TYPE.RADIO:
+ self._process_packet_type_radio(data, optional)
+ elif packet_type == PACKET_TYPE.SMART_ACK_COMMAND:
+ self._process_packet_type_smart_ack_command(data, optional)
+ elif packet_type == PACKET_TYPE.RESPONSE:
+ self._process_packet_type_response(data, optional)
+ elif packet_type == PACKET_TYPE.EVENT:
+ self._process_packet_type_event(data, optional)
else:
- self.logger.error("Crc error - dumping packet with type = 0x{:02x} / len = {} / data = [{}]!".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
- msg = msg[msg_length:]
+ self.logger.error("Received packet with unknown type = 0x{:02x} - len = {} / data = [{}]".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
else:
- #self.logger.warning("Consuming [0x{:02x}] from input buffer!".format(msg[0]))
- msg.pop(0)
- try:
- self._tcm.close()
- except Exception as e:
- self.logger.error(f"Exception during tcm close occured: {e}")
- else:
- self.logger.info(f"Enocean serial device closed")
- self.logger.info("Run method stopped")
-
- def stop(self):
- self.logger.debug("Call function << stop >>")
- self.alive = False
-
- def get_tx_id_as_hex(self):
- hexstring = "{:08X}".format(self.tx_id)
- return hexstring
-
- def get_serial_status_as_string(self):
- if (self._tcm and self._tcm.is_open):
- return "open"
- else:
- return "not connected"
-
- def get_log_unknown_msg(self):
- return self._log_unknown_msg
+ self.logger.error("Crc error - dumping packet with type = 0x{:02x} / len = {} / data = [{}]!".format(packet_type, msg_length, ', '.join(['0x%02x' % b for b in msg])))
+ msg = msg[msg_length:]
+ else:
+ # self.logger.warning("Consuming [0x{:02x}] from input buffer!".format(msg[0]))
+ msg.pop(0)
- def toggle_log_unknown_msg(self):
- self._log_unknown_msg = not self._log_unknown_msg
-
- def _send_UTE_response(self, data, optional):
- self.logger.debug("Call function << _send_UTE_response >>")
- choice = data[0]
- payload = data[1:-5]
- #sender_id = int.from_bytes(data[-5:-1], byteorder='big', signed=False)
- #status = data[-1]
- #repeater_cnt = status & 0x0F
- SubTel = 0x03
- db = 0xFF
- Secu = 0x0
+ # self.alive is False or connect error caused loop exit
+ self.stop()
- self._send_radio_packet(self.learn_id, choice, [0x91, payload[1], payload[2], payload[3], payload[4], payload[5], payload[6]],[SubTel, data[-5], data[-4], data[-3], data[-2], db, Secu] )#payload[0] = 0x91: EEP Teach-in response, Request accepted, teach-in successful, bidirectional
- self.UTE_listen = False
- self.logger.info("Sending UTE response and end listening")
+ def stop(self):
+ self.logger.debug("Stop method called")
+ self.alive = False
+ self.disconnect()
def parse_item(self, item):
- self.logger.debug("Call function << parse_item >>")
+ self.logger.debug("parse_item method called")
if 'enocean_rx_key' in item.conf:
# look for info from the most specific info to the broadest (key->eep->id) - one id might use multiple eep might define multiple keys
eep_item = item
found_eep = True
- while (not 'enocean_rx_eep' in eep_item.conf):
+ while 'enocean_rx_eep' not in eep_item.conf:
eep_item = eep_item.return_parent()
- if (eep_item is self._sh):
+ if eep_item is Items.get_instance():
self.logger.error(f"Could not find enocean_rx_eep for item {item}")
found_eep = False
+ break
id_item = eep_item
found_rx_id = True
- while (not 'enocean_rx_id' in id_item.conf):
+ while 'enocean_rx_id' not in id_item.conf:
id_item = id_item.return_parent()
- if (id_item is self._sh):
+ if id_item is Items.get_instance():
self.logger.error(f"Could not find enocean_rx_id for item {item}")
found_rx_id = False
+ break
# Only proceed, if valid rx_id and eep could be found:
if found_rx_id and found_eep:
rx_key = item.conf['enocean_rx_key'].upper()
rx_eep = eep_item.conf['enocean_rx_eep'].upper()
- rx_id = int(id_item.conf['enocean_rx_id'],16)
+ rx_id = int(id_item.conf['enocean_rx_id'], 16)
# check if there is a function to parse payload
if self.eep_parser.CanParse(rx_eep):
-
+
if (rx_key in ['A0', 'A1', 'B0', 'B1']):
- self.logger.warning(f"Key \"{rx_key}\" does not match EEP - \"0\" (Zero, number) should be \"O\" (letter) (same for \"1\" and \"I\") - will be accepted for now")
+ self.logger.warning(f'Key "{rx_key}" does not match EEP - "0" (Zero, number) should be "O" (letter) (same for "1" and "I") - will be accepted for now')
rx_key = rx_key.replace('0', 'O').replace("1", 'I')
- if (not rx_id in self._rx_items):
+ if rx_id not in self._rx_items:
self._rx_items[rx_id] = {rx_eep: [item]}
- elif (not rx_eep in self._rx_items[rx_id]):
+ elif rx_eep not in self._rx_items[rx_id]:
self._rx_items[rx_id][rx_eep] = [item]
- elif (not item in self._rx_items[rx_id][rx_eep]):
+ elif item not in self._rx_items[rx_id][rx_eep]:
self._rx_items[rx_id][rx_eep].append(item)
- self.logger.info("Item {} listens to id {:08X} with eep {} key {}".format(item, rx_id, rx_eep, rx_key))
- #self.logger.info(f"self._rx_items = {self._rx_items}")
-
+ self.logger.info(f"Item {item} listens to id {rx_id:08X} with eep {rx_eep} key {rx_key}")
+ # self.logger.info(f"self._rx_items = {self._rx_items}")
+
if 'enocean_tx_eep' in item.conf:
self.logger.debug(f"TX eep found in item {item._name}")
-
- if not 'enocean_tx_id_offset' in item.conf:
+
+ if 'enocean_tx_id_offset' not in item.conf:
self.logger.error(f"TX eep found for item {item._name} but no tx id offset specified.")
return
tx_offset = item.conf['enocean_tx_id_offset']
- if not (tx_offset in self._used_tx_offsets):
+ if tx_offset not in self._used_tx_offsets:
self._used_tx_offsets.append(tx_offset)
self._used_tx_offsets.sort()
self.logger.debug(f"Debug offset list: {self._used_tx_offsets}")
for x in range(1, 127):
- if not x in self._used_tx_offsets:
+ if x not in self._used_tx_offsets:
self._unused_tx_offset = x
self.logger.debug(f"Next free offset set to {self._unused_tx_offset}")
break
@@ -620,59 +240,101 @@ def parse_item(self, item):
# register item for event handling via smarthomeNG core. Needed for sending control actions:
return self.update_item
-
-
def update_item(self, item, caller=None, source=None, dest=None):
- logger_debug = self.logger.isEnabledFor(logging.DEBUG)
- if logger_debug:
- self.logger.debug("Call function << update_item >>")
+ if self.log_for_debug:
+ self.logger.debug("update_item method called")
- #self.logger.warning(f"Debug: update item: caller: {caller}, shortname: {self.get_shortname()}, item: {item.id()}")
+ # self.logger.warning(f"Debug: update item: caller: {caller}, shortname: {self.get_shortname()}, item: {item.id()}")
if caller != self.get_shortname():
- if logger_debug:
- self.logger.debug(f'Item << {item} >> updated externally.')
+ if self.log_for_debug:
+ self.logger.debug(f'Item {item} updated externally.')
if self._block_ext_out_msg:
- self.logger.warning('Sending manually blocked by user. Aborting')
- return None
+ self.logger.warning('Transmitting manually blocked by user. Aborting')
+ return
if 'enocean_tx_eep' in item.conf:
if isinstance(item.conf['enocean_tx_eep'], str):
tx_eep = item.conf['enocean_tx_eep']
- if logger_debug:
+ if self.log_for_debug:
self.logger.debug(f'item << {item} >> has tx_eep')
# check if Data can be Prepared
- if not self.prepare_packet_data.CanDataPrepare(tx_eep):
+ if not self.prepare_packet_data.CanPrepareData(tx_eep):
self.logger.error(f'enocean-update_item: method missing for prepare telegram data for {tx_eep}')
else:
# call method prepare_packet_data(item, tx_eep)
id_offset, rorg, payload, optional = self.prepare_packet_data.PrepareData(item, tx_eep)
self._send_radio_packet(id_offset, rorg, payload, optional)
else:
- self.logger.error(f'tx_eep {tx_eep} is not a string value')
+ self.logger.error('tx_eep is not a string value')
else:
- if logger_debug:
- self.logger.debug(f'Item << {item} >>has no tx_eep value')
+ if self.log_for_debug:
+ self.logger.debug(f'Item {item} has no tx_eep value')
- def read_num_securedivices(self):
- self.logger.debug("Call function << read_num_securedivices >>")
- self._send_common_command(CO_RD_NUMSECUREDEVICES)
- self.logger.info("Read number of secured devices")
+ def connect(self, startup=True):
+ """ open serial or serial2TCP device """
+ self.logger.debug(f'trying to connect to device at {self.port}')
+ try:
+ self._tcm = serial.serial_for_url(self.port, 57600, timeout=1.5)
+ except Exception as e:
+ self._tcm = None
+ self.logger.error(f"Exception occurred during serial open: {e}")
+ return False
+ else:
+ self.logger.info(f"Serial port successfully opened at port {self.port}")
+
+# why startup in separate thread? time to startup? collision with receiving?
+ if startup:
+ t = threading.Thread(target=self._startup, name="enocean-startup")
+ t.daemon = False
+ t.start()
+ return True
+
+ def disconnect(self):
+ """ close serial or serial2TCP device """
+ try:
+ self._tcm.close()
+ except Exception:
+ pass
+ self.logger.info("Enocean serial device closed")
+
+ def _startup(self):
+ """ send startup sequence to device """
+ self.logger.debug("_startup method called")
+
+ # request one time information
+ self.logger.info("Resetting device")
+ self._send_common_command(COMMON_COMMAND.WR_RESET)
+ self.logger.info("Requesting id-base")
+ self._send_common_command(COMMON_COMMAND.RD_IDBASE)
+ self.logger.info("Requesting version information")
+ self._send_common_command(COMMON_COMMAND.RD_VERSION)
+ self.logger.debug("Ending startup-thread")
+
+#
+# public EnOcean interface methods
+#
+
+ def read_num_securedevices(self):
+ """ read number of secure devices """
+ self.logger.debug("read_num_securedevices method called")
+ self._send_common_command(COMMON_COMMAND.RD_NUMSECUREDEVICES)
+ self.logger.info("Read number of secured devices")
- # Request all taught in smart acknowledge devices that have a mailbox
def get_smart_ack_devices(self):
- self.logger.debug("Call function << get_smart_ack_devices >>")
- self._send_smart_ack_command(SA_RD_LEARNEDCLIENTS)
+ """ request all smart acknowledge devices """
+ self.logger.debug("get_smart_ack_devices method called")
+ self._send_smart_ack_command(SMART_ACK.RD_LEARNEDCLIENTS)
self.logger.info("Requesting all available smart acknowledge mailboxes")
-
def reset_stick(self):
- self.logger.debug("Call function << reset_stick >>")
+ """ reset EnOcean transmitter """
+ self.logger.debug("reset_stick method called")
self.logger.info("Resetting device")
- self._send_common_command(CO_WR_RESET)
+ self._send_common_command(COMMON_COMMAND.WR_RESET)
def block_external_out_messages(self, block=True):
- self.logger.debug("Call function << block_external_out_messages >>")
+ self.logger.debug("block_external_out_messages method called")
if block:
self.logger.info("Blocking of external out messages activated")
self._block_ext_out_msg = True
@@ -683,213 +345,435 @@ def block_external_out_messages(self, block=True):
self.logger.error("Invalid argument. Must be True/False")
def toggle_block_external_out_messages(self):
- self.logger.debug("Call function << toggle block_external_out_messages >>")
- if self._block_ext_out_msg == False:
+ self.logger.debug("toggle block_external_out_messages method called")
+ if not self._block_ext_out_msg:
self.logger.info("Blocking of external out messages activated")
self._block_ext_out_msg = True
else:
self.logger.info("Blocking of external out messages deactivated")
self._block_ext_out_msg = False
- def toggle_UTE_mode(self,id_offset=0):
+ def toggle_UTE_mode(self, id_offset=0):
self.logger.debug("Toggle UTE mode")
- if self.UTE_listen == True:
+ if self.UTE_listen:
self.logger.info("UTE mode deactivated")
self.UTE_listen = False
- elif (id_offset is not None) and not (id_offset == 0):
+ elif id_offset:
self.start_UTE_learnmode(id_offset)
- self.logger.info("UTE mode activated for ID offset")
+ self.logger.info(f"UTE mode activated for ID offset {id_offset}")
def send_bit(self):
self.logger.info("Trigger Built-In Self Test telegram")
- self._send_common_command(CO_WR_BIST)
+ self._send_common_command(COMMON_COMMAND.WR_BIST)
def version(self):
self.logger.info("Request stick version")
- self._send_common_command(CO_RD_VERSION)
+ self._send_common_command(COMMON_COMMAND.RD_VERSION)
- def _send_packet(self, packet_type, data=[], optional=[]):
- #self.logger.debug("Call function << _send_packet >>")
- length_optional = len(optional)
- if length_optional > 255:
- self.logger.error(f"Optional too long ({length_optional} bytes, 255 allowed)")
- return None
- length_data = len(data)
- if length_data > 65535:
- self.logger.error(f"Data too long ({length_data} bytes, 65535 allowed)")
- return None
+#
+# Utility methods
+#
- packet = bytearray([PACKET_SYNC_BYTE])
- packet += length_data.to_bytes(2, byteorder='big') + bytes([length_optional, packet_type])
- packet += bytes([self._calc_crc8(packet[1:5])])
- packet += bytes(data + optional)
- packet += bytes([self._calc_crc8(packet[6:])])
- self.logger.info("Sending packet with len = {} / data = [{}]!".format(len(packet), ', '.join(['0x%02x' % b for b in packet])))
-
- # Send out serial data:
- if not (self._tcm and self._tcm.is_open):
- self.logger.debug("Trying serial reinit")
- try:
- self._tcm = serial.serial_for_url(self.port, 57600, timeout=1.5)
- except Exception as e:
- self._tcm = None
- self.logger.error(f"Exception occurred during serial reinit: {e}")
- else:
- self.logger.debug("Serial reinit successful")
- if self._tcm:
- try:
- self._tcm.write(packet)
- except Exception as e:
- self.logger.error(f"Exception during tcm write occurred: {e}")
- self.logger.debug("Trying serial reinit after failed write")
- try:
- self._tcm = serial.serial_for_url(self.port, 57600, timeout=1.5)
- except Exception as e:
- self._tcm = None
- self.logger.error(f"Exception occurred during serial reinit after failed write: {e}")
- else:
- self.logger.debug("Serial reinit successful after failed write")
- try:
- self._tcm.write(packet)
- except Exception as e:
- self.logger.error(f"Exception occurred during tcm write after successful serial reinit: {e}")
-
- def _send_smart_ack_command(self, _code, data=[]):
- #self.logger.debug("Call function << _send_smart_ack_command >>")
+ def get_tx_id_as_hex(self):
+ hexstring = "{:08X}".format(self.tx_id)
+ return hexstring
+
+ def is_connected(self):
+ return self._tcm and self._tcm.is_open
+
+ def get_serial_status_as_string(self):
+ return "open" if self.is_connected() else "not connected"
+
+ def get_log_unknown_msg(self):
+ return self._log_unknown_msg
+
+ def toggle_log_unknown_msg(self):
+ self._log_unknown_msg = not self._log_unknown_msg
+
+#
+# (private) packet / protocol methods
+#
+
+ def _send_smart_ack_command(self, code, data=[]):
+ # self.logger.debug("_send_smart_ack_command method called")
self._cmd_lock.acquire()
- self._last_cmd_code = _code
- self._last_packet_type = PACKET_TYPE_SMART_ACK_COMMAND
- self._send_packet(PACKET_TYPE_SMART_ACK_COMMAND, [_code] + data)
+ self._last_cmd_code = code
+ self._last_packet_type = PACKET_TYPE.SMART_ACK_COMMAND
+ self._send_packet(PACKET_TYPE.SMART_ACK_COMMAND, [code] + data)
self._response_lock.acquire()
# wait 5sec for response
self._response_lock.wait(5)
self._response_lock.release()
self._cmd_lock.release()
- def _send_common_command(self, _code, data=[], optional=[]):
- #self.logger.debug("Call function << _send_common_command >>")
+ def _send_common_command(self, code, data=[], optional=[]):
+ # self.logger.debug("_send_common_command method called")
self._cmd_lock.acquire()
- self._last_cmd_code = _code
- self._last_packet_type = PACKET_TYPE_COMMON_COMMAND
- self._send_packet(PACKET_TYPE_COMMON_COMMAND, [_code] + data, optional)
+ self._last_cmd_code = code
+ self._last_packet_type = PACKET_TYPE.COMMON_COMMAND
+ self._send_packet(PACKET_TYPE.COMMON_COMMAND, [code] + data, optional)
self._response_lock.acquire()
# wait 5sec for response
self._response_lock.wait(5)
self._response_lock.release()
self._cmd_lock.release()
- def _send_radio_packet(self, id_offset, _code, data=[], optional=[]):
- #self.logger.debug("Call function << _send_radio_packet >>")
+ def _send_radio_packet(self, id_offset, code, data=[], optional=[]):
+ # self.logger.debug("_send_radio_packet method called")
if (id_offset < 0) or (id_offset > 127):
self.logger.error(f"Invalid base ID offset range. (Is {id_offset}, must be [0 127])")
return
self._cmd_lock.acquire()
- self._last_cmd_code = SENT_RADIO_PACKET
- self._send_packet(PACKET_TYPE_RADIO, [_code] + data + list((self.tx_id + id_offset).to_bytes(4, byteorder='big')) + [0x00], optional)
+ self._last_cmd_code = PACKET.SENT_RADIO
+ self._send_packet(PACKET_TYPE.RADIO, [code] + data + list((self.tx_id + id_offset).to_bytes(4, byteorder='big')) + [0x00], optional)
self._response_lock.acquire()
# wait 1sec for response
self._response_lock.wait(1)
self._response_lock.release()
self._cmd_lock.release()
-
-
+ def _send_UTE_response(self, data, optional):
+ self.logger.debug("_send_UTE_response method called")
+ choice = data[0]
+ payload = data[1:-5]
+ # sender_id = int.from_bytes(data[-5:-1], byteorder='big', signed=False)
+ # status = data[-1]
+ # repeater_cnt = status & 0x0F
+ db = 0xFF
+ Secu = 0x0
+ # payload[0] = 0x91: EEP Teach-in response, Request accepted, teach-in successful, bidirectional
+ self._send_radio_packet(self.learn_id, choice, [0x91, payload[1], payload[2], payload[3], payload[4], payload[5], payload[6]], [PACKET_TYPE.RADIO_SUB_TEL, data[-5], data[-4], data[-3], data[-2], db, Secu] )
+ self.UTE_listen = False
+ self.logger.info("Sent UTE response and ended listening")
+
+ def _rocker_sequence(self, item, sender_id, sequence):
+ if self.log_for_debug:
+ self.logger.debug("_rocker_sequence method called")
+ try:
+ for step in sequence:
+ event, relation, delay = step.split()
+ # self.logger.debug("waiting for {} {} {}".format(event, relation, delay))
+ if item._enocean_rs_events[event.upper()].wait(float(delay)) != (relation.upper() == "WITHIN"):
+ if self.log_for_debug:
+ self.logger.debug(f"NOT {step} - aborting sequence!")
+ return
+ else:
+ if self.log_for_debug:
+ self.logger.debug(f"{step}")
+ item._enocean_rs_events[event.upper()].clear()
+ continue
+ value = True
+ if 'enocean_rocker_action' in item.conf:
+ if item.conf['enocean_rocker_action'].upper() == "UNSET":
+ value = False
+ elif item.conf['enocean_rocker_action'].upper() == "TOGGLE":
+ value = not item()
+ item(value, self.get_shortname(), "{:08X}".format(sender_id))
+ except Exception as e:
+ self.logger.error(f'Error handling enocean_rocker_sequence \"{sequence}\" - {e}')
+
+ def _send_packet(self, packet_type, data=[], optional=[]):
+ # self.logger.debug("_send_packet method called")
+ length_optional = len(optional)
+ if length_optional > 255:
+ self.logger.error(f"Optional too long ({length_optional} bytes, 255 allowed)")
+ return
+ length_data = len(data)
+ if length_data > 65535:
+ self.logger.error(f"Data too long ({length_data} bytes, 65535 allowed)")
+ return
+
+ packet = bytearray([PACKET.SYNC_BYTE])
+ packet += length_data.to_bytes(2, byteorder='big') + bytes([length_optional, packet_type])
+ packet += bytes([self.crc(packet[1:5])])
+ packet += bytes(data + optional)
+ packet += bytes([self.crc(packet[6:])])
+ self.logger.info("Sending packet with len = {} / data = [{}]!".format(len(packet), ', '.join(['0x%02x' % b for b in packet])))
+
+ # check connection, reconnect
+ if not self.is_connected():
+ self.logger.debug("Trying serial reinit")
+ if not self.connect(startup=False):
+ self.logger.error('Connection failed, not sending.')
+ return
+ try:
+ self._tcm.write(packet)
+ return
+ except Exception as e:
+ self.logger.error(f"Exception during tcm write occurred: {e}")
+ self.logger.debug("Trying serial reinit after failed write")
+
+ if not self.connect(startup=False):
+ self.logger.error('Connection failed again, not sending. Giving up.')
+ return
+
+ try:
+ self._tcm.write(packet)
+ except Exception as e:
+ self.logger.error(f"Writing failed twice, giving up: {e}")
+
+ def _process_packet_type_event(self, data, optional):
+ if self.log_for_debug:
+ self.logger.debug("_process_packet_type_event method called")
+ event_code = data[0]
+ if event_code == EVENT.RECLAIM_NOT_SUCCESSFUL:
+ self.logger.error("SA reclaim was not successful")
+ elif event_code == EVENT.CONFIRM_LEARN:
+ self.logger.info("Requesting how to handle confirm/discard learn in/out")
+ elif event_code == EVENT.LEARN_ACK:
+ self.logger.info("SA lern acknowledged")
+ elif event_code == EVENT.READY:
+ self.logger.info("Controller is ready for operation")
+ elif event_code == EVENT.TRANSMIT_FAILED:
+ self.logger.error("Telegram transmission failed")
+ elif event_code == EVENT.DUTYCYCLE_LIMIT:
+ self.logger.warning("Duty cycle limit reached")
+ elif event_code == EVENT.EVENT_SECUREDEVICES:
+ self.logger.info("Secure device event packet received")
+ else:
+ self.logger.warning("Unknown event packet received")
+
+ def _process_packet_type_radio(self, data, optional):
+ if self.log_for_debug:
+ self.logger.debug("_process_packet_type_radio method called")
+ # self.logger.warning("Processing radio message with data = [{}] / optional = [{}]".format(', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
+
+ choice = data[0]
+ payload = data[1:-5]
+ sender_id = int.from_bytes(data[-5:-1], byteorder='big', signed=False)
+ status = data[-1]
+ repeater_cnt = status & 0x0F
+ self.logger.info("Radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
+
+ if len(optional) == 7:
+ subtelnum = optional[0]
+ dest_id = int.from_bytes(optional[1:5], byteorder='big', signed=False)
+ dBm = -optional[5]
+ SecurityLevel = optional[6]
+ if self.log_for_debug:
+ self.logger.debug(f"Radio message with additional info: subtelnum = {subtelnum} / dest_id = {dest_id:08X} / signal = {dBm} dBm / SecurityLevel = {SecurityLevel}")
+ if choice == 0xD4 and self.UTE_listen:
+ self.logger.info("Call send_UTE_response")
+ self._send_UTE_response(data, optional)
+
+ if sender_id in self._rx_items:
+ if self.log_for_debug:
+ self.logger.debug("Sender ID found in item list")
+ # iterate over all eep known for this id and get list of associated items
+ for eep, items in self._rx_items[sender_id].items():
+ # check if choice matches first byte in eep (this seems to be the only way to find right eep for this particular packet)
+ if eep.startswith("{:02X}".format(choice)):
+ # call parser for particular eep - returns dictionary with key-value pairs
+ results = self.eep_parser(eep, payload, status)
+ if self.log_for_debug:
+ self.logger.debug(f"Radio message results = {results}")
+ if 'DEBUG' in results:
+ self.logger.warning("DEBUG Info: processing radio message with data = [{}] / optional = [{}]".format(', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
+ self.logger.warning(f"Radio message results = {results}")
+ self.logger.warning("Radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
+
+ for item in items:
+ rx_key = item.conf['enocean_rx_key'].upper()
+ if rx_key in results:
+ if 'enocean_rocker_sequence' in item.conf:
+ try:
+ if hasattr(item, '_enocean_rs_thread') and item._enocean_rs_thread.is_alive():
+ if results[rx_key]:
+ if self.log_for_debug:
+ self.logger.debug("Sending pressed event")
+ item._enocean_rs_events["PRESSED"].set()
+ else:
+ if self.log_for_debug:
+ self.logger.debug("Sending released event")
+ item._enocean_rs_events["RELEASED"].set()
+ elif results[rx_key]:
+ item._enocean_rs_events = {'PRESSED': threading.Event(), 'RELEASED': threading.Event()}
+ item._enocean_rs_thread = threading.Thread(target=self._rocker_sequence, name="enocean-rs", args=(item, sender_id, item.conf['enocean_rocker_sequence'].split(','), ))
+ # self.logger.info("starting enocean_rocker_sequence thread")
+ item._enocean_rs_thread.start()
+ except Exception as e:
+ self.logger.error(f"Error handling enocean_rocker_sequence: {e}")
+ else:
+ item(results[rx_key], self.get_shortname(), f"{sender_id:08X}")
+ elif sender_id <= self.tx_id + 127 and sender_id >= self.tx_id:
+ if self.log_for_debug:
+ self.logger.debug("Received repeated enocean stick message")
+ else:
+ self.unknown_sender_id = f"{sender_id:08X}"
+ if self._log_unknown_msg:
+ self.logger.info(f"Unknown ID = {sender_id:08X}")
+ self.logger.warning("Unknown device sent radio message: choice = {:02x} / payload = [{}] / sender_id = {:08X} / status = {} / repeat = {}".format(choice, ', '.join(['0x%02x' % b for b in payload]), sender_id, status, repeater_cnt))
+
+ def _process_packet_type_smart_ack_command(self, data, optional):
+ self.logger.warning("Smart acknowledge command 0x06 received but not supported at the moment")
+
+ def _process_packet_type_response(self, data, optional):
+ if self.log_for_debug:
+ self.logger.debug("_process_packet_type_response method called")
+
+ # handle sent packet
+ if self._last_cmd_code == PACKET.SENT_RADIO and len(data) == 1:
+
+ if self.log_for_debug:
+ self.logger.debug(f"Sending command returned code = {RETURN_CODE(data[0])}")
+
+ # handle common commands
+ elif self._last_packet_type == PACKET_TYPE.COMMON_COMMAND:
+
+ if self._last_cmd_code == COMMON_COMMAND.WR_RESET and len(data) == 1:
+ self.logger.info(f"Reset returned code = {RETURN_CODE(data[0])}")
+
+ elif self._last_cmd_code == COMMON_COMMAND.WR_LEARNMODE and len(data) == 1:
+ self.logger.info(f"Write LearnMode returned code = {RETURN_CODE(data[0])}")
+
+ elif self._last_cmd_code == COMMON_COMMAND.RD_VERSION:
+ if data[0] == 0 and len(data) == 33:
+ self.logger.info("Chip ID = 0x{} / Chip Version = 0x{}".format(''.join(['%02x' % b for b in data[9:13]]), ''.join(['%02x' % b for b in data[13:17]])))
+ self.logger.info("APP version = {} / API version = {} / App description = {}".format('.'.join(['%d' % b for b in data[1:5]]), '.'.join(['%d' % b for b in data[5:9]]), ''.join(['%c' % b for b in data[17:33]])))
+ elif data[0] == 0 and len(data) == 0:
+ self.logger.error("Reading version: No answer")
+ else:
+ self.logger.error(f"Reading version returned code = {RETURN_CODE(data[0])}, length = {len(data)}")
+
+ elif self._last_cmd_code == COMMON_COMMAND.RD_IDBASE:
+ if data[0] == 0 and len(data) == 5:
+ self.logger.info("Base ID = 0x{}".format(''.join(['%02x' % b for b in data[1:5]])))
+ if self.tx_id == 0:
+ self.tx_id = int.from_bytes(data[1:5], byteorder='big', signed=False)
+ self.logger.info("Transmit ID set set automatically by reading chips BaseID")
+ if len(optional) == 1:
+ self.logger.info(f"Remaining write cycles for Base ID = {optional[0]}")
+ elif data[0] == 0 and len(data) == 0:
+ self.logger.error("Reading Base ID: No answer")
+ else:
+ self.logger.error(f"Reading Base ID returned code = {RETURN_CODE(data[0])} and {len(data)} bytes")
+
+ elif self._last_cmd_code == COMMON_COMMAND.WR_BIST:
+ if data[0] == 0 and len(data) == 2:
+ if data[1] == 0:
+ self.logger.info("Built in self test result: All OK")
+ else:
+ self.logger.info(f"Built in self test result: Problem, code = {data[1]}")
+ elif data[0] == 0 and len(data) == 0:
+ self.logger.error("Doing built in self test: No answer")
+ else:
+ self.logger.error(f"Doing built in self test returned code = {RETURN_CODE(data[0])}")
+
+ elif self._last_cmd_code == COMMON_COMMAND.RD_LEARNMODE:
+ if data[0] == 0 and len(data) == 2:
+ self.logger.info("Reading LearnMode = 0x{}".format(''.join(['%02x' % b for b in data[1]])))
+ if len(optional) == 1:
+ self.logger.info("Learn channel = {}".format(optional[0]))
+ elif data[0] == 0 and len(data) == 0:
+ self.logger.error("Reading LearnMode: No answer")
+
+ elif self._last_cmd_code == COMMON_COMMAND.RD_NUMSECUREDEVICES:
+ if data[0] == 0 and len(data) == 2:
+ self.logger.info("Number of taught in devices = 0x{}".format(''.join(['%02x' % b for b in data[1]])))
+ elif data[0] == 0 and len(data) == 0:
+ self.logger.error("Reading NUMSECUREDEVICES: No answer")
+ elif data[0] == 2 and len(data) == 1:
+ self.logger.error("Reading NUMSECUREDEVICES: Command not supported")
+ else:
+ self.logger.error("Reading NUMSECUREDEVICES: Unknown error")
+ elif self._last_packet_type == PACKET_TYPE.SMART_ACK_COMMAND:
+
+ # handle SmartAck commands
+ if self._last_cmd_code == SMART_ACK.WR_LEARNMODE:
+ self.logger.info(f"Setting SmartAck mode returned code = {RETURN_CODE(data[0])}")
+
+ elif self._last_cmd_code == SMART_ACK.RD_LEARNEDCLIENTS:
+ if data[0] == 0:
+ self.logger.info(f"Number of smart acknowledge mailboxes = {int((len(data)-1)/9)}")
+ else:
+ self.logger.error(f"Requesting SmartAck mailboxes returned code = {RETURN_CODE(data[0])}")
+ else:
+ self.logger.error("Processing unexpected response with return code = {} / data = [{}] / optional = [{}]".format(RETURN_CODE(data[0]), ', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))
+
+ self._response_lock.acquire()
+ self._response_lock.notify()
+ self._response_lock.release()
+
+#
+# Definitions of Learn Methods
+#
-####################################################
-### --- START - Definitions of Learn Methods --- ###
-####################################################
def send_learn_protocol(self, id_offset=0, device=10):
- self.logger.debug("Call function << send_learn_protocol >>")
+ self.logger.debug("send_learn_protocol method called")
# define RORG
- rorg = 0xA5
-
+ rorg = RORG.BS4
+
# check offset range between 0 and 127
- if (id_offset < 0) or (id_offset > 127):
+ if not 0 <= id_offset <= 127:
self.logger.error(f'ID offset with value = {id_offset} out of range (0-127). Aborting.')
return False
+
# device range 10 - 19 --> Learn protocol for switch actuators
- elif (device == 10):
+ if device == 10:
+
# Prepare Data for Eltako switch FSR61, Eltako FSVA-230V
payload = [0xE0, 0x40, 0x0D, 0x80]
self.logger.info('Sending learn telegram for switch command with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
+
# device range 20 - 29 --> Learn protocol for dim actuators
- elif (device == 20):
+ elif device == 20:
+
# Only for Eltako FSUD-230V
payload = [0x02, 0x00, 0x00, 0x00]
self.logger.info('Sending learn telegram for dim command with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
- elif (device == 21):
+ elif device == 21:
+
# For Eltako FHK61SSR dim device (EEP A5-38-08)
payload = [0xE0, 0x40, 0x0D, 0x80]
self.logger.info('Sending learn telegram for dim command with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
- elif (device == 22):
+ elif device == 22:
+
# For Eltako FRGBW71L RGB dim devices (EEP 07-3F-7F)
payload = [0xFF, 0xF8, 0x0D, 0x87]
self.logger.info('Sending learn telegram for rgbw dim command with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
+
# device range 30 - 39 --> Learn protocol for radiator valves
- elif (device == 30):
+ elif device == 30:
+
# Radiator Valve
payload = [0x00, 0x00, 0x00, 0x00]
self.logger.info('Sending learn telegram for radiator valve with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
+
# device range 40 - 49 --> Learn protocol for other actuators
- elif (device == 40):
+ elif device == 40:
+
# Eltako shutter actor FSB14, FSB61, FSB71
payload = [0xFF, 0xF8, 0x0D, 0x80]
self.logger.info('Sending learn telegram for actuator with [Device], [ID-Offset], [RORG], [payload] / [{}], [{:#04x}], [{:#04x}], [{}]'.format(device, id_offset, rorg, ', '.join('{:#04x}'.format(x) for x in payload)))
else:
- self.logger.error(f'Sending learn telegram with invalid device! Device {device} actually not defined!')
+ self.logger.error(f'Sending learn telegram with invalid device! Device {device} currently not defined!')
return False
+
# Send radio package
self._send_radio_packet(id_offset, rorg, payload)
return True
-
def start_UTE_learnmode(self, id_offset=0):
- self.logger.debug("Call function << start_UTE_learnmode >>")
+ self.logger.debug("start_UTE_learnmode method called")
self.UTE_listen = True
self.learn_id = id_offset
self.logger.info("Listening for UTE package ('D4')")
-
-
+
def enter_learn_mode(self, onoff=1):
- self.logger.debug("Call function << enter_learn_mode >>")
- if (onoff == 1):
- self._send_common_command(CO_WR_LEARNMODE, [0x01, 0x00, 0x00, 0x00, 0x00],[0xFF])
+ self.logger.debug("enter_learn_mode method called")
+ if onoff == 1:
+ self._send_common_command(COMMON_COMMAND.WR_LEARNMODE, [0x01, 0x00, 0x00, 0x00, 0x00], [0xFF])
self.logger.info("Entering learning mode")
- return None
else:
- self._send_common_command(CO_WR_LEARNMODE, [0x00, 0x00, 0x00, 0x00, 0x00],[0xFF])
+ self._send_common_command(COMMON_COMMAND.WR_LEARNMODE, [0x00, 0x00, 0x00, 0x00, 0x00], [0xFF])
self.logger.info("Leaving learning mode")
- return None
-
# This function enables/disables the controller's smart acknowledge mode
def set_smart_ack_learn_mode(self, onoff=1):
- self.logger.debug("Call function << set_smart_ack_learn_mode >>")
- if (onoff == 1):
- self._send_smart_ack_command(SA_WR_LEARNMODE, [0x01, 0x00, 0x00, 0x00, 0x00, 0x00])
+ self.logger.debug("set_smart_ack_learn_mode method called")
+ if onoff == 1:
+ self._send_smart_ack_command(SMART_ACK.WR_LEARNMODE, [0x01, 0x00, 0x00, 0x00, 0x00, 0x00])
self.logger.info("Enabling smart acknowledge learning mode")
- return None
else:
- self._send_smart_ack_command(SA_WR_LEARNMODE, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+ self._send_smart_ack_command(SMART_ACK.WR_LEARNMODE, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
self.logger.info("Disabling smart acknowledge learning mode")
- return None
-
-##################################################
-### --- END - Definitions of Learn Methods --- ###
-##################################################
-
-
-#################################
-### --- START - Calc CRC8 --- ###
-#################################
- def _calc_crc8(self, msg, crc=0):
- #self.logger.debug("Call function << _calc_crc8 >>")
- for i in msg:
- crc = FCSTAB[crc ^ i]
- return crc
-
-###############################
-### --- END - Calc CRC8 --- ###
-###############################
-
-
diff --git a/enocean/plugin.yaml b/enocean/plugin.yaml
index 1d677fb49..443a10827 100755
--- a/enocean/plugin.yaml
+++ b/enocean/plugin.yaml
@@ -16,11 +16,11 @@ plugin:
# url of the support thread
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/26542-featurewunsch-enocean-plugin/page13
- version: 1.4.0 # Plugin version
- sh_minversion: '1.3' # minimum shNG version to use this plugin
+ version: 1.4.2 # Plugin version
+ sh_minversion: '1.9' # minimum shNG version to use this plugin
#sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: False # plugin supports multi instance
- restartable: unknown
+ multi_instance: false # plugin supports multi instance
+ restartable: true
classname: EnOcean # class containing the plugin
parameters:
@@ -46,6 +46,19 @@ parameters:
en: 'Log messages from unknown devices to logfile'
default: 'False'
+ retry:
+ type: int
+ description:
+ de: 'Anzahl der Verbindungsversuche (0 = kein Limit)'
+ en: 'Number of connect retries (0 = no limit)'
+ default: 10
+
+ retry_cycle:
+ type: int
+ description:
+ de: 'Pause zwischen Verbindungsversuchen (in Sekunden)'
+ en: 'pause interval between connect retries (in seconds)'
+ default: 5
item_attributes:
# Definition of item attributes defined by this plugin
diff --git a/enocean/protocol/__init__.py b/enocean/protocol/__init__.py
new file mode 100644
index 000000000..c54f0df93
--- /dev/null
+++ b/enocean/protocol/__init__.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+#########################################################################
+# Enocean plugin for SmartHomeNG. https://github.com/smarthomeNG//
+#
+# This plugin is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This plugin is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this plugin. If not, see .
+#########################################################################
+
+# this module contains EnOcean protocol routines
+
+
+class CRC():
+ """ provides CRC calculations """
+
+ CRC_TABLE = (
+ 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
+ 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
+ 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
+ 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
+ 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
+ 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
+ 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
+ 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
+ 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
+ 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
+ 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
+ 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
+ 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
+ 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
+ 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
+ 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
+ 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
+ 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
+ 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
+ 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
+ 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
+ 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
+ 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
+ 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
+ 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
+ 0x76, 0x71, 0x78, 0x7f, 0x6A, 0x6d, 0x64, 0x63,
+ 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
+ 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
+ 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
+ 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8D, 0x84, 0x83,
+ 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
+ 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3
+ )
+
+ def __call__(self, msg, crc=0):
+ for i in msg:
+ crc = self.CRC_TABLE[crc ^ i]
+ return crc
diff --git a/enocean/protocol/constants.py b/enocean/protocol/constants.py
new file mode 100644
index 000000000..6f74c9c65
--- /dev/null
+++ b/enocean/protocol/constants.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+#########################################################################
+# Enocean plugin for SmartHomeNG. https://github.com/smarthomeNG//
+#
+# This plugin is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This plugin is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this plugin. If not, see .
+#########################################################################
+
+from enum import IntEnum
+
+
+class PACKET(IntEnum):
+ """ generic packet identifiers """
+ SYNC_BYTE = 0x55
+ SENT_RADIO = 0xFF
+ SENT_ENCAPSULATED_RADIO = 0xA6
+
+
+class RORG(IntEnum):
+ """ encapsulates EEP types from EnOcean Equipment Profiles v2.61 """
+ UNDEFINED = 0x00
+ RPS = 0xF6
+ BS1 = 0xD5
+ BS4 = 0xA5
+ VLD = 0xD2
+ MSC = 0xD1
+ ADT = 0xA6
+ SM_LRN_REQ = 0xC6
+ SM_LRN_ANS = 0xC7
+ SM_REC = 0xA7
+ SYS_EX = 0xC5
+ SEC = 0x30
+ SEC_ENCAPS = 0x31
+ UTE = 0xD4
+
+
+class PACKET_TYPE(IntEnum):
+ """ encapsulates packet types """
+ RESERVED = 0x00
+ RADIO = 0x01 # RADIO ERP1
+ RADIO_ERP1 = 0x01 # RADIO ERP1 => Kept for backwards compatibility reasons, for example custom packet. Generation shouldn't be affected...
+ RESPONSE = 0x02 # RESPONSE
+ RADIO_SUB_TEL = 0x03 # RADIO_SUB_TEL
+ EVENT = 0x04 # EVENT
+ COMMON_COMMAND = 0x05 # COMMON COMMAND
+ SMART_ACK_COMMAND = 0x06 # SMART ACK COMMAND
+ REMOTE_MAN_COMMAND = 0x07 # REMOTE MANAGEMENT COMMAND
+ RADIO_MESSAGE = 0x09 # RADIO MESSAGE
+ RADIO_ERP2 = 0x0A # RADIO ERP2
+ RADIO_802_15_4 = 0x10 # RADIO_802_15_4_RAW_Packet
+ COMMAND_2_4 = 0x11 # COMMAND 2.4 GHz
+
+
+class EVENT(IntEnum):
+ """ encapsulates Event Codes """
+ RECLAIM_NOT_SUCCESSFUL = 0x01 # Informs the backbone of a Smart Ack Client to not successful reclaim.
+ CONFIRM_LEARN = 0x02 # Used for SMACK to confirm/discard learn in/out
+ LEARN_ACK = 0x03 # Inform backbone about result of learn request
+ READY = 0x04 # Inform backbone about the readiness for operation
+ EVENT_SECUREDEVICES = 0x05 # Informs about a secure device
+ DUTYCYCLE_LIMIT = 0x06 # Informs about duty cycle limit
+ TRANSMIT_FAILED = 0x07 # Informs that the device was not able to send a telegram.
+ TX_DONE = 0x08 # Informs the external host that the device has finished all transmissions.
+ LRN_MODE_DISABLED = 0x09 # Informs the external host that the learn mode has been disabled due to timeout.
+
+
+class COMMON_COMMAND(IntEnum):
+ """ encapsulates Common Command Codes """
+ WR_SLEEP = 0x01 # Enter in energy saving mode
+ WR_RESET = 0x02 # Reset the device
+ RD_VERSION = 0x03 # Read the device (SW) version /(HW) version, chip ID etc.
+ RD_SYS_LOG = 0x04 # Read system log from device databank
+ WR_SYS_LOG = 0x05 # Reset System log from device databank
+ WR_BIST = 0x06 # Perform built in self test
+ WR_IDBASE = 0x07 # Write ID range base number
+ RD_IDBASE = 0x08 # Read ID range base number
+ WR_REPEATER = 0x09 # Write Repeater Level off,1,2
+ RD_REPEATER = 0x0A # Read Repeater Level off,1,2
+ WR_FILTER_ADD = 0x0B # Add filter to filter list
+ WR_FILTER_DEL = 0x0C # Delete filter from filter list
+ WR_FILTER_DEL_ALL = 0x0D # Delete all filter
+ WR_FILTER_ENABLE = 0x0E # Enable/Disable supplied filters
+ RD_FILTER = 0x0F # Read supplied filters
+ WR_WAIT_MATURITY = 0x10 # Waiting till end of maturity time before received radio telegrams will transmitted
+ WR_SUBTEL = 0x11 # Enable/Disable transmitting additional subtelegram info
+ WR_MEM = 0x12 # Write x bytes of the Flash, XRAM, RAM0 …
+ RD_MEM = 0x13 # Read x bytes of the Flash, XRAM, RAM0 ….
+ RD_MEM_ADDRESS = 0x14 # Feedback about the used address and length of the configarea and the Smart Ack Table
+ RD_SECURITY = 0x15 # Read own security information (level, key)
+ WR_SECURITY = 0x16 # Write own security information (level, key)
+ WR_LEARNMODE = 0x17 # Function: Enables or disables learn mode of Controller.
+ RD_LEARNMODE = 0x18 # Function: Reads the learn-mode state of Controller.
+ WR_SECUREDEVICE_ADD = 0x19 # Add a secure device
+ WR_SECUREDEVICE_DEL = 0x1A # Delete a secure device
+ RD_SECUREDEVICE_BY_INDEX = 0x1B # Read secure device by index
+ WR_MODE = 0x1C # Sets the gateway transceiver mode
+ RD_NUMSECUREDEVICES = 0x1D # Read number of taught in secure devices
+ RD_SECUREDEVICE_BY_ID = 0x1E # Read secure device by ID
+ WR_SECUREDEVICE_ADD_PSK = 0x1F # Add Pre-shared key for inbound secure device
+ WR_SECUREDEVICE_SENDTEACHIN = 0x20 # Send secure Teach-In message
+ WR_TEMPORARY_RLC_WINDOW = 0x21 # Set the temporary rolling-code window for every taught-in devic
+ RD_SECUREDEVICE_PSK = 0x22 # Read PSK
+ RD_DUTYCYCLE_LIMIT = 0x23 # Read parameters of actual duty cycle limit
+ SET_BAUDRATE = 0x24 # Modifies the baud rate of the EnOcean device
+ GET_FREQUENCY_INFO = 0x25 # Reads Frequency and protocol of the Device
+ GET_STEPCODE = 0x27 # Reads Hardware Step code and Revision of the Device
+ WR_REMAN_CODE = 0x2E # Set the security code to unlock Remote Management functionality via radio
+ WR_STARTUP_DELAY = 0x2F # Set the startup delay (time from power up until start of operation)
+ WR_REMAN_REPEATING = 0x30 # Select if REMAN telegrams originating from this module can be repeated
+ RD_REMAN_REPEATING = 0x31 # Check if REMAN telegrams originating from this module can be repeated
+ SET_NOISETHRESHOLD = 0x32 # Set the RSSI noise threshold level for telegram reception
+ GET_NOISETHRESHOLD = 0x33 # Read the RSSI noise threshold level for telegram reception
+ WR_RLC_SAVE_PERIOD = 0x36 # Set the period in which outgoing RLCs are saved to the EEPROM
+ WR_RLC_LEGACY_MODE = 0x37 # Activate the legacy RLC security mode allowing roll-over and using the RLC acceptance window for 24bit explicit RLC
+ WR_SECUREDEVICEV2_ADD = 0x38 # Add secure device to secure link table
+ RD_SECUREDEVICEV2_BY_INDEX = 0x39 # Read secure device from secure link table using the table index
+ WR_RSSITEST_MODE = 0x3A # Control the state of the RSSI-Test mode
+ RD_RSSITEST_MODE = 0x3B # Read the state of the RSSI-Test mode
+ WR_SECUREDEVICE_MAINTENANCEKEY = 0x3C # Add the maintenance key information into the secure link table
+ RD_SECUREDEVICE_MAINTENANCEKEY = 0x3D # Read by index the maintenance key information from the secure link table
+ WR_TRANSPARENT_MODE = 0x3E # Control the state of the transparent mode
+ RD_TRANSPARENT_MODE = 0x3F # Read the state of the transparent mode
+ WR_TX_ONLY_MODE = 0x40 # Control the state of the TX only mode
+ RD_TX_ONLY_MODE = 0x41 # Read the state of the TX only mode
+
+
+class SMART_ACK(IntEnum):
+ """ encapsulates Smart Acknowledge codes """
+ WR_LEARNMODE = 0x01 # Set/Reset Smart Ack learn mode
+ RD_LEARNMODE = 0x02 # Get Smart Ack learn mode state
+ WR_LEARNCONFIRM = 0x03 # Used for Smart Ack to add or delete a mailbox of a client
+ WR_CLIENTLEARNRQ = 0x04 # Send Smart Ack Learn request (Client)
+ WR_RESET = 0x05 # Send reset command to a Smart Ack client
+ RD_LEARNEDCLIENTS = 0x06 # Get Smart Ack learned sensors / mailboxes
+ WR_RECLAIMS = 0x07 # Set number of reclaim attempts
+ WR_POSTMASTER = 0x08 # Activate/Deactivate Post master functionality
+
+
+class RETURN_CODE(IntEnum):
+ """ encapsulates return codes """
+ OK = 0x00
+ ERROR = 0x01
+ NOT_SUPPORTED = 0x02
+ WRONG_PARAM = 0x03
+ OPERATION_DENIED = 0x04
+
+
+class PARSE_RESULT(IntEnum):
+ """ encapsulates parsing return codes """
+ OK = 0x00
+ INCOMPLETE = 0x01
+ CRC_MISMATCH = 0x03
diff --git a/enocean/eep_parser.py b/enocean/protocol/eep_parser.py
similarity index 71%
rename from enocean/eep_parser.py
rename to enocean/protocol/eep_parser.py
index e017f7014..d9d90def3 100755
--- a/enocean/eep_parser.py
+++ b/enocean/protocol/eep_parser.py
@@ -1,26 +1,58 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+# Copyright 2013-2014 Robert Budde robert@ing-budde.de
+# Copyright 2014 Alexander Schwithal aschwith
+#########################################################################
+# Enocean plugin for SmartHomeNG. https://github.com/smarthomeNG//
+#
+# This plugin is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This plugin is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this plugin. If not, see .
+#########################################################################
+
import logging
+
class EEP_Parser():
- def __init__(self):
+ def __init__(self, plg_logger=None):
self.logger = logging.getLogger(__name__)
- self.logger.info('Eep-parser instantiated')
+ self.logger.info('EEP-parser instantiated')
+
+ # create plugin logger for errors
+ self.plg_logger = plg_logger
+ if not self.plg_logger:
+ self.plg_logger = self.logger
def CanParse(self, eep):
found = callable(getattr(self, "_parse_eep_" + eep, None))
- if (not found):
+ if not found:
self.logger.error(f"eep-parser: missing parser for eep {eep} - there should be a _parse_eep_{eep}-function!")
return found
- def Parse(self, eep, payload, status):
- #self.logger.debug('Parser called with eep = {} / payload = {} / status = {}'.format(eep, ', '.join(hex(x) for x in payload), hex(status)))
- results = getattr(self, "_parse_eep_" + eep)(payload, status)
- #self.logger.info('Parser returns {results}')
+ def __call__(self, eep, payload, status):
+ # self.logger.debug('Parser called with eep = {} / payload = {} / status = {}'.format(eep, ', '.join(hex(x) for x in payload), hex(status)))
+ try:
+ results = getattr(self, "_parse_eep_" + eep)(payload, status)
+ except Exception as e:
+ self.plg_logger.warning(f'EEP-Parser: error on parsing eep {eep}: {e}')
+ return
+
+ # self.logger.info('Parser returns {results}')
return results
-#####################################################
-### --- Definitions for RORG = A5 / ORG = 07 --- ###
-#####################################################
+# Definitions for RORG = A5 / ORG = 07
+
def _parse_eep_A5_02_01(self, payload, status):
return {'TMP': (0 - (payload[2] * 40 / 255))}
@@ -112,23 +144,23 @@ def _parse_eep_A5_04_02(self, payload, status):
# temperature in degree Celsius from -20.0 degC - 60degC
result['TMP'] = -20.0 + (payload[2] / 250.0 * 80.0)
return result
-
+
def _parse_eep_A5_06_01(self, payload, status):
# Brightness sensor, for example Eltako FAH60
self.logger.debug('Parsing A5_06_01: Brightness sensor')
result = {}
# Calculation of brightness in lux
- if (payload[3] == 0x0F) and (payload[1] > 0x00) and (payload[1] <= 0xFF):
+ if payload[3] == 0x0F and payload[1] > 0x00 and payload[1] <= 0xFF:
# If Data-Messege AND DataByte 2 is between: 0x00 = 300 lux and 0xFF = 30.000 lux
result['BRI'] = round(((payload[1] / 255.0 * (30000 - 300)) + 300), 2)
- elif (payload[3] == 0x0F) and (payload[1] == 0x00):
+ elif payload[3] == 0x0F and payload[1] == 0x00:
# If Data-Messege AND DataByte 2: 0x00 then read DataByte 3
- result['BRI'] = (payload[0])
+ result['BRI'] = payload[0]
else:
# No Data Message
- result['BRI'] = (-1)
+ result['BRI'] = -1
# only trigger the logger info when 'BRI' > 0
- if (result['BRI'] > 0):
+ if result['BRI'] > 0:
self.logger.info(f"Brightness: {result['BRI']}")
return result
@@ -136,7 +168,7 @@ def _parse_eep_A5_07_03(self, payload, status):
# Occupancy sensor with supply voltage monitor, NodOne
self.logger.debug("Parsing A5_07_03: Occupancy sensor")
result = {}
- is_data = ((payload[3] & 0x08) == 0x08) # learn or data telegeram: 1:data, 0:learn
+ is_data = (payload[3] & 0x08) == 0x08 # learn or data telegeram: 1:data, 0:learn
if not is_data:
self.logger.info("Occupancy sensor: Received learn telegram.")
return result
@@ -144,9 +176,9 @@ def _parse_eep_A5_07_03(self, payload, status):
if payload[0] > 250:
self.logger.error(f"Occupancy sensor issued error code: {payload[0]}")
else:
- result['SVC'] = (payload[0] / 255.0 * 5.0) # supply voltage in volts
- result['ILL'] = (payload[1] << 2) + ((payload[2] & 0xC0) >> 6) # 10 bit illumination in lux
- result['PIR'] = ((payload[3] & 0x80) == 0x80) # Movement flag, 1:motion detected
+ result['SVC'] = payload[0] / 255.0 * 5.0 # supply voltage in volts
+ result['ILL'] = payload[1] << 2 + (payload[2] & 0xC0) >> 6 # 10 bit illumination in lux
+ result['PIR'] = (payload[3] & 0x80) == 0x80 # Movement flag, 1:motion detected
self.logger.debug(f"Occupancy: PIR:{result['PIR']} illumination: {result['ILL']}lx, voltage: {result['SVC']}V")
return result
@@ -154,9 +186,9 @@ def _parse_eep_A5_08_01(self, payload, status):
# Brightness and movement sensor, for example eltako FBH65TFB
self.logger.debug("Parsing A5_08_01: Movement sensor")
result = {}
- result['BRI'] = (payload[1] / 255.0 * 2048) # brightness in lux
- result['MOV'] = not ((payload[3] & 0x02) == 0x02) # movement
- #self.logger.debug(f"Movement: {result['MOV']}, brightness: {result['BRI']}")
+ result['BRI'] = payload[1] / 255.0 * 2048 # brightness in lux
+ result['MOV'] = not (payload[3] & 0x02) == 0x02 # movement
+ # self.logger.debug(f"Movement: {result['MOV']}, brightness: {result['BRI']}")
return result
def _parse_eep_A5_11_04(self, payload, status):
@@ -168,14 +200,14 @@ def _parse_eep_A5_11_04(self, payload, status):
# Data_byte0 = 0x08 = Dimmer aus, 0x09 = Dimmer an
self.logger.debug("Processing A5_11_04: Dimmer Status on/off")
results = {}
- # if !( (payload[0] == 0x02) and (payload[2] == 0x00)):
+ # if !( (payload[0] == 0x02 and payload[2] == 0x00)):
# self.logger.error("Error in processing A5_11_04: static byte missmatch")
# return results
results['D'] = payload[1]
- if (payload[3] == 0x08):
+ if payload[3] == 0x08:
# Dimmer is off
results['STAT'] = 0
- elif (payload[3] == 0x09):
+ elif payload[3] == 0x09:
# Dimmer is on
results['STAT'] = 1
return results
@@ -184,40 +216,39 @@ def _parse_eep_A5_12_01(self, payload, status):
# Status command from switch actor with powermeter, for example Eltako FSVA-230
results = {}
status_byte = payload[3]
- is_data = (status_byte & 0x08) == 0x08
- if(is_data == False):
+ is_data = status_byte & 0x08 == 0x08
+ if is_data is False:
self.logger.debug("Processing A5_12_01: powermeter: is learn telegram. Aborting.")
return results
- is_power = (status_byte & 0x04) == 0x04
- div_enum = (status_byte & 0x03)
+ is_power = status_byte & 0x04 == 0x04
+ div_enum = status_byte & 0x03
divisor = 1.0
- if(div_enum == 0):
+ if div_enum == 0:
divisor = 1.0
- elif(div_enum == 1):
+ elif div_enum == 1:
divisor = 10.0
- elif(div_enum == 2):
+ elif div_enum == 2:
divisor = 100.0
- elif(div_enum == 3):
+ elif div_enum == 3:
divisor = 1000.0
- else:
+ else:
self.logger.warning(f"Processing A5_12_01: Unknown enum ({div_enum}) for divisor")
self.logger.debug(f"Processing A5_12_01: divisor is {divisor}")
- if(is_power):
+ if is_power:
self.logger.debug("Processing A5_12_01: powermeter: Unit is Watts")
else:
self.logger.debug("Processing A5_12_01: powermeter: Unit is kWh")
- value = (payload[0] << 16) + (payload[1] << 8) + payload[2]
- value = value / divisor
+ value = (payload[0] << 16 + payload[1] << 8 + payload[2]) / divisor
self.logger.debug(f"Processing A5_12_01: powermeter: {value} W")
# It is confirmed by Eltako that with the use of multiple repeaters in an Eltako network, values can be corrupted in random cases.
# Catching these random errors via plausibility check:
if value > 2300:
self.logger.warning(f"A5_12_01 plausibility error: power value {value} is greater than 2300W, which is not plausible. Skipping.")
- #self.logger.warning(f"A5_12_01 exception: value {value}, divisor {divisor}, divenum {div_enum}, statusPayload {status_byte}, header status {status}")
- #self.logger.warning(f"A5_12_01 exception: payloads 0-3: {payload[0]},{payload[1]},{payload[2]},{payload[3]}")
+ # self.logger.warning(f"A5_12_01 exception: value {value}, divisor {divisor}, divenum {div_enum}, statusPayload {status_byte}, header status {status}")
+ # self.logger.warning(f"A5_12_01 exception: payloads 0-3: {payload[0]},{payload[1]},{payload[2]},{payload[3]}")
results['DEBUG'] = 1
return results
@@ -229,24 +260,24 @@ def _parse_eep_A5_20_04(self, payload, status):
self.logger.debug("Processing A5_20_04")
results = {}
status_byte = payload[3]
- #1: temperature setpoint, 0: feed temperature
- TS = ((status_byte & 1 << 6) == 1 << 6)
- #1: failure, 0: normal
- FL = ((status_byte & 1 << 7) == 1 << 7)
- #1: locked, 0: unlocked
- BLS= ((status_byte& 1 << 5) == 1 << 5)
+ # 1: temperature setpoint, 0: feed temperature
+ TS = status_byte & 1 << 6 == 1 << 6
+ # 1: failure, 0: normal
+ FL = status_byte & 1 << 7 == 1 << 7
+ # 1: locked, 0: unlocked
+ BLS = status_byte & 1 << 5 == 1 << 5
results['BLS'] = BLS
# current valve position 0-100%
results['CP'] = payload[0]
# Current feet temperature or setpoint
- if(TS == 1):
- results['TS'] = 10 + (payload[1]/255*20)
+ if TS == 1:
+ results['TS'] = 10 + payload[1] / 255 * 20
else:
- results['FT'] = 20 + (payload[1]/255*60)
+ results['FT'] = 20 + payload[1] / 255 * 60
# Current room temperature or failure code
- if (FL == 0):
- results['TMP'] = 10 + (payload[2]/255*20)
- else:
+ if FL == 0:
+ results['TMP'] = 10 + payload[2] / 255 * 20
+ else:
results['FC'] = payload[2]
results['STATUS'] = status_byte
return results
@@ -259,7 +290,7 @@ def _parse_eep_A5_30_01(self, payload, status):
self.logger.warning("A5_30_03 is learn telegram")
return results
# Data_byte1 = 0x00 / 0xFF
- results['ALARM'] = (payload[2] == 0x00)
+ results['ALARM'] = payload[2] == 0x00
# Battery linear: 0-120 (bat low), 121-255(bat high)
results['BAT'] = payload[1]
return results
@@ -276,27 +307,26 @@ def _parse_eep_A5_30_03(self, payload, status):
self.logger.error("EEP A5_30_03 not according to spec.")
return results
# Data_byte2 = Temperatur 0...40 °C (255...0)
- results['TEMP'] = 40 - (payload[1]/255*40)
+ results['TEMP'] = 40 - payload[1] / 255 * 40
# Data_byte1 = 0x0F = Alarm, 0x1F = kein Alarm
- results['ALARM'] = (payload[2] == 0x0F)
+ results['ALARM'] = payload[2] == 0x0F
return results
-
def _parse_eep_A5_38_08(self, payload, status):
results = {}
- if (payload[1] == 2): # Dimming
+ if payload[1] == 2: # Dimming
results['EDIM'] = payload[2]
results['RMP'] = payload[3]
- results['LRNB'] = ((payload[4] & 1 << 3) == 1 << 3)
- results['EDIM_R'] = ((payload[4] & 1 << 2) == 1 << 2)
- results['STR'] = ((payload[4] & 1 << 1) == 1 << 1)
- results['SW'] = ((payload[4] & 1 << 0) == 1 << 0)
+ results['LRNB'] = payload[4] & 1 << 3 == 1 << 3
+ results['EDIM_R'] = payload[4] & 1 << 2 == 1 << 2
+ results['STR'] = payload[4] & 1 << 1 == 1 << 1
+ results['SW'] = payload[4] & 1 << 0 == 1 << 0
return results
def _parse_eep_A5_3F_7F(self, payload, status):
self.logger.debug("Processing A5_3F_7F")
results = {'DI_3': (payload[3] & 1 << 3) == 1 << 3, 'DI_2': (payload[3] & 1 << 2) == 1 << 2, 'DI_1': (payload[3] & 1 << 1) == 1 << 1, 'DI_0': (payload[3] & 1 << 0) == 1 << 0}
- results['AD_0'] = (((payload[1] & 0x03) << 8) + payload[2]) * 1.8 / pow(2, 10)
+ results['AD_0'] = ((payload[1] & 0x03) << 8 + payload[2]) * 1.8 / pow(2, 10)
results['AD_1'] = (payload[1] >> 2) * 1.8 / pow(2, 6)
results['AD_2'] = payload[0] * 1.8 / pow(2, 8)
return results
@@ -316,26 +346,25 @@ def _parse_eep_A5_0G_03(self, payload, status):
self.logger.debug(f"eep-parser input status = {status}")
results = {}
runtime_s = ((payload[0] << 8) + payload[1]) / 10
- if (payload[2] == 1):
+ if payload[2] == 1:
self.logger.debug(f"Shutter moved {runtime_s} s 'upwards'")
results['MOVE'] = runtime_s * -1
- elif (payload[2] == 2):
+ elif payload[2] == 2:
self.logger.debug(f"Shutter moved {runtime_s} s 'downwards'")
results['MOVE'] = runtime_s
return results
-#####################################################
-### --- Definitions for RORG = D2 / ORG = D2 --- ###
-#####################################################
+# Definitions for RORG = D2 / ORG = D2
+
def _parse_eep_D2_01_07(self, payload, status):
# self.logger.debug("Processing D2_01_07: VLD Switch")
results = {}
# self.logger.info(f'D2 Switch Feedback 0:{payload[0]} 1:{payload[1]} 2:{payload[2]}')
- if (payload[2] == 0x80):
+ if payload[2] == 0x80:
# Switch is off
results['STAT'] = 0
self.logger.debug('D2 Switch off')
- elif (payload[2] == 0xe4):
+ elif payload[2] == 0xe4:
# Switch is on
results['STAT'] = 1
self.logger.debug('D2 Switch on')
@@ -345,55 +374,50 @@ def _parse_eep_D2_01_12(self, payload, status):
# self.logger.debug("Processing D2_01_12: VLD Switch")
results = {}
# self.logger.info(f'D2 Switch Feedback 0:{payload[0]} 1:{payload[1]} 2:{payload[2]}')
- if (payload[1] == 0x60) and (payload[2] == 0x80):
+ if payload[1] == 0x60 and payload[2] == 0x80:
# Switch is off
results['STAT_A'] = 0
self.logger.debug('D2 Switch Channel A: off')
- elif (payload[1] == 0x60) and (payload[2] == 0xe4):
+ elif payload[1] == 0x60 and payload[2] == 0xe4:
# Switch is on
results['STAT_A'] = 1
self.logger.debug('D2 Channel A: Switch on')
- elif (payload[1] == 0x61) and (payload[2] == 0x80):
+ elif payload[1] == 0x61 and payload[2] == 0x80:
# Switch is off
results['STAT_B'] = 0
self.logger.debug('D2 SwitchChannel A: off')
- elif (payload[1] == 0x61) and (payload[2] == 0xe4):
+ elif payload[1] == 0x61 and payload[2] == 0xe4:
# Switch is on
results['STAT_B'] = 1
self.logger.debug('D2 Switch Channel B: on')
return results
-####################################################
-### --- Definitions for RORG = D5 / ORG = 06 --- ###
-####################################################
+# Definitions for RORG = D5 / ORG = 06
+
def _parse_eep_D5_00_01(self, payload, status):
# Window/Door Contact Sensor, for example Eltako FTK, FTKB
self.logger.debug("Processing D5_00_01: Door contact")
- return {'STATUS': (payload[0] & 0x01) == 0x01}
+ return {'STATUS': payload[0] & 0x01 == 0x01}
+# Definitions for RORG = F6 / ORG = 05
-####################################################
-### --- Definitions for RORG = F6 / ORG = 05 --- ###
-####################################################
def _parse_eep_F6_02_01(self, payload, status):
self.logger.debug("Processing F6_02_01: Rocker Switch, 2 Rocker, Light and Blind Control - Application Style 1")
results = {}
R1 = (payload[0] & 0xE0) >> 5
- EB = (payload[0] & (1<<4) == (1<<4))
R2 = (payload[0] & 0x0E) >> 1
- SA = (payload[0] & (1<<0) == (1<<0))
- NU = (status & (1<<4) == (1<<4))
+ SA = payload[0] & 1 == 1
+ NU = status & (1 << 4) == (1 << 4)
- if (NU):
+ if NU:
results['AI'] = (R1 == 0) or (SA and (R2 == 0))
results['AO'] = (R1 == 1) or (SA and (R2 == 1))
results['BI'] = (R1 == 2) or (SA and (R2 == 2))
results['BO'] = (R1 == 3) or (SA and (R2 == 3))
- elif (not NU) and (payload[0] == 0x00):
+ elif not NU and payload[0] == 0x00:
results = {'AI': False, 'AO': False, 'BI': False, 'BO': False}
else:
self.logger.error("Parser detected invalid state encoding - check your switch!")
- pass
return results
def _parse_eep_F6_02_02(self, payload, status):
@@ -408,34 +432,34 @@ def _parse_eep_F6_02_03(self, payload, status):
self.logger.debug("Processing F6_02_03: Rocker Switch, 2 Rocker")
results = {}
# Button A1: Dimm light down
- results['AI'] = (payload[0]) == 0x10
+ results['AI'] = payload[0] == 0x10
# Button A0: Dimm light up
- results['AO'] = (payload[0]) == 0x30
+ results['AO'] = payload[0] == 0x30
# Button B1: Dimm light down
- results['BI'] = (payload[0]) == 0x50
+ results['BI'] = payload[0] == 0x50
# Button B0: Dimm light up
- results['BO'] = (payload[0]) == 0x70
- if (payload[0] == 0x70):
+ results['BO'] = payload[0] == 0x70
+ if payload[0] == 0x70:
results['B'] = True
- elif (payload[0] == 0x50):
+ elif payload[0] == 0x50:
results['B'] = False
- elif (payload[0] == 0x30):
+ elif payload[0] == 0x30:
results['A'] = True
- elif (payload[0] == 0x10):
+ elif payload[0] == 0x10:
results['A'] = False
- return results
+ return results
def _parse_eep_F6_10_00(self, payload, status):
self.logger.debug(f"Processing F6_10_00: Mechanical Handle sends payload {payload[0]}")
results = {}
# Eltako defines 0xF0 for closed status. Enocean spec defines masking of lower 4 bit:
- if (payload[0] & 0b11110000) == 0b11110000:
+ if payload[0] & 0b11110000 == 0b11110000:
results['STATUS'] = 0
# Eltako defines 0xE0 for window open (horizontal) up status. Enocean spec defines the following masking:
- elif (payload[0] & 0b11010000) == 0b11000000:
+ elif payload[0] & 0b11010000 == 0b11000000:
results['STATUS'] = 1
# Eltako defines 0xD0 for open/right up status. Enocean spec defines masking of lower 4 bit:
- elif (payload[0] & 0b11110000) == 0b11010000:
+ elif payload[0] & 0b11110000 == 0b11010000:
results['STATUS'] = 2
else:
self.logger.error(f"Error in F6_10_00 handle status, payload: {payload[0]} unknown")
@@ -453,19 +477,19 @@ def _parse_eep_F6_0G_03(self, payload, status):
'''
self.logger.debug("Processing F6_0G_03: shutter actor")
self.logger.debug("payload = [{}]".format(', '.join(['0x%02X' % b for b in payload])))
- self.logger.debug("status: {}".format(status))
+ self.logger.debug(f"status: {status}")
results = {}
- if (payload[0] == 0x70):
+ if payload[0] == 0x70:
results['POSITION'] = 0
results['B'] = 0
- elif (payload[0] == 0x50):
+ elif payload[0] == 0x50:
results['POSITION'] = 255
results['B'] = 0
- elif (payload[0] == 0x01):
+ elif payload[0] == 0x01:
results['STATUS'] = 'Start moving up'
results['B'] = 1
- elif (payload[0] == 0x02):
+ elif payload[0] == 0x02:
results['STATUS'] = 'Start moving down'
results['B'] = 2
- self.logger.debug('parse_eep_F6_0G_03 returns: {}'.format(results))
+ self.logger.debug(f'parse_eep_F6_0G_03 returns: {results}')
return results
diff --git a/enocean/prepare_packet_data.py b/enocean/protocol/packet_data.py
similarity index 80%
rename from enocean/prepare_packet_data.py
rename to enocean/protocol/packet_data.py
index 79f9f76e6..e4683bdb6 100755
--- a/enocean/prepare_packet_data.py
+++ b/enocean/protocol/packet_data.py
@@ -22,9 +22,10 @@
import logging
from lib.utils import Utils
+from .constants import RORG, PACKET_TYPE
-class Prepare_Packet_Data():
+class Packet_Data():
def __init__(self, plugin_instance):
"""
@@ -35,13 +36,13 @@ def __init__(self, plugin_instance):
# Get the plugin instance from encocean class
self._plugin_instance = plugin_instance
- def CanDataPrepare(self, tx_eep):
+ def CanPrepareData(self, tx_eep):
"""
This Method checks if there is an available Prepare Data Method for the tx_eep
"""
found = callable(getattr(self, '_prepare_data_for_tx_eep_' + tx_eep, None))
- if (not found):
- self.logger.error(f"enocean-CanDataPrepare: missing tx_eep for pepare send data {tx_eep} - there should be a _prepare_data_for_tx_eep_{tx_eep}-function!")
+ if not found:
+ self.logger.error(f"enocean-CanPrepareData: missing tx_eep for pepare send data {tx_eep} - there should be a _prepare_data_for_tx_eep_{tx_eep}-function!")
return found
def PrepareData(self, item, tx_eep):
@@ -54,40 +55,34 @@ def PrepareData(self, item, tx_eep):
if self._plugin_instance.has_iattr(item.conf, 'enocean_tx_id_offset'):
self.logger.debug("enocean-PrepareData: item has valid enocean_tx_id_offset")
id_offset = int(self._plugin_instance.get_iattr_value(item.conf, 'enocean_tx_id_offset'))
- if (id_offset < 0) or (id_offset > 127):
+ if id_offset < 0 or id_offset > 127:
self.logger.error('enocean-PrepareData: ID offset out of range (0-127). Aborting.')
- return None
+ return
else:
self.logger.info(f"enocean-PrepareData: {tx_eep} item has no attribute ''enocean_tx_id_offset''! Set to default = 0")
id_offset = 0
- # start prepare data
+ # start prepare data
rorg, payload, optional = getattr(self, '_prepare_data_for_tx_eep_' + tx_eep)(item, tx_eep)
- #self.logger.info('enocean-PrepareData: {} returns [{:#04x}], [{}], [{}]'.format(tx_eep, rorg, ', '.join('{:#04x}'.format(x) for x in payload), ', '.join('{:#04x}'.format(x) for x in optional)))
+ # self.logger.info('enocean-PrepareData: {} returns [{:#04x}], [{}], [{}]'.format(tx_eep, rorg, ', '.join('{:#04x}'.format(x) for x in payload), ', '.join('{:#04x}'.format(x) for x in optional)))
return id_offset, rorg, payload, optional
+# Definitions for RORG = A5 / ORG = 07
-#####################################################
-### --- Definitions for RORG = A5 / ORG = 07 --- ###
-### --> Definition of 4BS Telegrams ###
-#####################################################
-
-
def _prepare_data_for_tx_eep_A5_20_04(self, item, tx_eep):
"""
### --- Data for radiator valve command --- ###
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xa5
temperature = item()
# define default values:
- MC = 1 # off
- WUC = 3 # 120 seconds
- BLC = 0 # unlocked
- LRNB = 1 # data
- DSO = 0 # 0 degree
+ MC = 1 # off
+ WUC = 3 # 120 seconds
+ BLC = 0 # unlocked
+ LRNB = 1 # data
+ DSO = 0 # 0 degree
valve_position = 50
- for sibling in get_children(item.parent):
+ for sibling in item.return_parent().get_children():
if hasattr(sibling, 'MC'):
MC = sibling()
if hasattr(sibling, 'WUC'):
@@ -100,24 +95,22 @@ def _prepare_data_for_tx_eep_A5_20_04(self, item, tx_eep):
DSO = sibling()
if hasattr(sibling, 'VALVE_POSITION'):
valve_position = sibling()
- TSP = int((temperature -10)*255/30)
- status = 0 + (MC << 1) + (WUC << 2)
+ TSP = int((temperature - 10) * 255 / 30)
+ status = 0 + (MC << 1) + (WUC << 2)
status2 = (BLC << 5) + (LRNB << 4) + (DSO << 2)
- payload = [valve_position, TSP, status , status2]
+ payload = [valve_position, TSP, status, status2]
optional = []
- return rorg, payload, optional
-
-
- def _prepare_data_for_tx_eep_A5_38_08_01(self, item, tx_eep):
+ return RORG.BS4, payload, optional
+
+ def _prepare_data_for_tx_eep_A5_38_08_01(self, item, tx_eep):
"""
### --- Data for A5-38_08 command 1 --- ###
Eltako Devices:
- FSR14-2x, FSR14-4x, FSR14SSR, FSR71
+ FSR14-2x, FSR14-4x, FSR14SSR, FSR71
FSR61, FSR61NP, FSR61G, FSR61LN, FLC61NP
This method has the function to prepare the packet data in case of switching device on or off
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xa5
block = 0
# check if item has attribute block_switch
if self._plugin_instance.has_iattr(item.conf, 'block_switch'):
@@ -133,10 +126,9 @@ def _prepare_data_for_tx_eep_A5_38_08_01(self, item, tx_eep):
payload = [0x01, 0x00, 0x00, int(9 + block)]
self.logger.debug(f'enocean-PrepareData: {tx_eep} prepare data to switch on')
optional = []
- return rorg, payload, optional
-
-
- def _prepare_data_for_tx_eep_A5_38_08_02(self, item, tx_eep):
+ return RORG.BS4, payload, optional
+
+ def _prepare_data_for_tx_eep_A5_38_08_02(self, item, tx_eep):
"""
### --- Data for A5-38_08 command 2 --- ###
Eltako Devices:
@@ -145,8 +137,7 @@ def _prepare_data_for_tx_eep_A5_38_08_02(self, item, tx_eep):
This method has the function to prepare the packet data in case of switching the dimmer device on or off,
but calculate also the correct data of dim_speed and dim_value for further solutions.
"""
- #self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xa5
+ # self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
block = 0
# check if item has attribute block_dim_value
if self._plugin_instance.has_iattr(item.level.conf, 'block_dim_value'):
@@ -158,7 +149,7 @@ def _prepare_data_for_tx_eep_A5_38_08_02(self, item, tx_eep):
dim_speed = self._plugin_instance.get_iattr_value(item.level.conf, 'dim_speed')
# bound dim_speed values to [0 - 100] %
dim_speed = max(0, min(100, int(dim_speed)))
- #self.logger.debug(f'enocean-PrepareData: {tx_eep} use dim_speed = {dim_speed} %')
+ # self.logger.debug(f'enocean-PrepareData: {tx_eep} use dim_speed = {dim_speed} %')
# calculate dimspeed from percent into integer
# 0x01 --> fastest speed --> 100 %
# 0xFF --> slowest speed --> 0 %
@@ -166,29 +157,28 @@ def _prepare_data_for_tx_eep_A5_38_08_02(self, item, tx_eep):
else:
# use intern dim_speed of the dim device
dim_speed = 0
- #self.logger.debug('enocean-PrepareData: no attribute dim_speed --> use intern dim speed')
+ # self.logger.debug('enocean-PrepareData: no attribute dim_speed --> use intern dim speed')
if not item():
# if value is False --> Switch off
dim_value = 0
payload = [0x02, int(dim_value), int(dim_speed), int(8 + block)]
- #self.logger.debug('enocean-PrepareData: prepare data to switch off for command for A5_38_08_02')
+ # self.logger.debug('enocean-PrepareData: prepare data to switch off for command for A5_38_08_02')
else:
# check if reference dim value exists
if 'ref_level' in item.level.conf:
dim_value = int(item.level.conf['ref_level'])
# check range of dim_value [0 - 100] %
dim_value = max(0, min(100, int(dim_value)))
- #self.logger.debug(f'enocean-PrepareData: {tx_eep} ref_level {dim_value} % found for A5_38_08_02')
+ # self.logger.debug(f'enocean-PrepareData: {tx_eep} ref_level {dim_value} % found for A5_38_08_02')
else:
# set dim_value on 100 % == 0x64
dim_value = 0x64
self.logger.debug(f'enocean-PrepareData: {tx_eep} no ref_level found! Setting to default 100 %')
payload = [0x02, int(dim_value), int(dim_speed), int(9 + block)]
optional = []
- return rorg, payload, optional
-
-
- def _prepare_data_for_tx_eep_A5_38_08_03(self, item, tx_eep):
+ return RORG.BS4, payload, optional
+
+ def _prepare_data_for_tx_eep_A5_38_08_03(self, item, tx_eep):
"""
### --- Data for A5-38_08 command 3--- ###
Eltako Devices:
@@ -198,14 +188,13 @@ def _prepare_data_for_tx_eep_A5_38_08_03(self, item, tx_eep):
In case of dim_value == 0 the dimmer is switched off.
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xa5
block = 0
# check if item has attribute block_dim_value
if self._plugin_instance.has_iattr(item.conf, 'block_dim_value'):
block_value = self._plugin_instance.get_iattr_value(item.conf, 'block_dim_value')
if Utils.to_bool(block_value):
block = 4
- # check if item has attribite dim_speed
+ # check if item has attribite dim_speed
if self._plugin_instance.has_iattr(item.conf, 'dim_speed'):
dim_speed = self._plugin_instance.get_iattr_value(item.conf, 'dim_speed')
# bound dim_speed values to [0 - 100] %
@@ -214,7 +203,7 @@ def _prepare_data_for_tx_eep_A5_38_08_03(self, item, tx_eep):
# calculate dimspeed from percent into hex
# 0x01 --> fastest speed --> 100 %
# 0xFF --> slowest speed --> 0 %
- dim_speed = (255 - (254 * dim_speed/100))
+ dim_speed = (255 - (254 * dim_speed / 100))
else:
# use intern dim_speed of the dim device
dim_speed = 0x00
@@ -232,10 +221,9 @@ def _prepare_data_for_tx_eep_A5_38_08_03(self, item, tx_eep):
dim_value = dim_value
payload = [0x02, int(dim_value), int(dim_speed), int(9 + block)]
optional = []
- return rorg, payload, optional
-
-
- def _prepare_data_for_tx_eep_A5_3F_7F(self, item, tx_eep):
+ return RORG.BS4, payload, optional
+
+ def _prepare_data_for_tx_eep_A5_3F_7F(self, item, tx_eep):
"""
### --- Data for A5-3F-7F - Universal Actuator Command --- ###
Eltako Devices:
@@ -244,14 +232,13 @@ def _prepare_data_for_tx_eep_A5_3F_7F(self, item, tx_eep):
The Runtime is set in [0 - 255] s
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xa5
block = 0
# check if item has attribute block_switch
if self._plugin_instance.has_iattr(item.conf, 'block_switch'):
block_value = self._plugin_instance.get_iattr_value(item.conf, 'block_switch')
if Utils.to_bool(block_value):
block = 4
- # check if item has attribite enocean_rtime
+ # check if item has attribite enocean_rtime
if self._plugin_instance.has_iattr(item.conf, 'enocean_rtime'):
rtime = self._plugin_instance.get_iattr_value(item.conf, 'enocean_rtime')
# rtime [0 - 255] s
@@ -263,25 +250,24 @@ def _prepare_data_for_tx_eep_A5_3F_7F(self, item, tx_eep):
self.logger.debug(f'enocean-PrepareData: {tx_eep} actuator runtime not specified set to {rtime} s.')
# check command (up, stop, or down)
command = int(item())
- if(command == 0):
+ if command == 0:
# Stopp moving
command_hex_code = 0x00
- elif(command == 1):
+ elif command == 1:
# moving up
command_hex_code = 0x01
- elif(command == 2):
+ elif command == 2:
# moving down
command_hex_code = 0x02
else:
self.logger.error(f'enocean-PrepareData: {tx_eep} sending actuator command failed: invalid command {command}')
- return None
+ return
# define payload
payload = [0x00, rtime, command_hex_code, int(8 + block)]
optional = []
- return rorg, payload, optional
-
-
- def _prepare_data_for_tx_eep_07_3F_7F(self, item, tx_eep):
+ return RORG.BS4, payload, optional
+
+ def _prepare_data_for_tx_eep_07_3F_7F(self, item, tx_eep):
"""
### --- Data for 07-3F-7F Command --- ###
Eltako Devices:
@@ -294,8 +280,9 @@ def _prepare_data_for_tx_eep_07_3F_7F(self, item, tx_eep):
Color: bit0 = red, bit1= green, bit2 = blue, bit3 = white
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
+ # NOTE: not an official RORG value!
rorg = 0x07
- # check if item has attribite dim_speed
+ # check if item has attribite dim_speed
if self._plugin_instance.has_iattr(item.conf, 'dim_speed'):
dim_speed = self._plugin_instance.get_iattr_value(item.conf, 'dim_speed')
dim_speed = max(0, min(100, int(dim_speed)))
@@ -303,89 +290,87 @@ def _prepare_data_for_tx_eep_07_3F_7F(self, item, tx_eep):
# calculate dimspeed from percent into hex
# 0x01 --> fastest speed --> 100 %
# 0xFF --> slowest speed --> 0 %
- dim_speed = (255 - (254 * dim_speed/100))
+ dim_speed = (255 - (254 * dim_speed / 100))
else:
# use intern dim_speed of the dim device
dim_speed = 0x00
self.logger.debug(f'enocean-PrepareData: {tx_eep} no attribute dim_speed --> use intern dim speed')
+
# check the color of the item
if self._plugin_instance.has_iattr(item.conf, 'color'):
color = self._plugin_instance.get_iattr_value(item.conf, 'color')
- if (color == 'red'):
+ color_hex = ''
+ if color == 'red':
color_hex = 0x01
- elif (color == 'green'):
+ elif color == 'green':
color_hex = 0x02
- elif (color == 'blue'):
+ elif color == 'blue':
color_hex = 0x04
- elif (color == 'white'):
+ elif color == 'white':
color_hex = 0x08
else:
self.logger.error(f'enocean-PrepareData: {item} has no attribute color --> please specify color!')
- return None
+ return
+
# Aufdimmen: [dim_speed, color_hex, 0x30, 0x0F]
# Abdimmen: [dim_speed, color_hex, 0x31, 0x0F]
# Dimmstop: [dim_speed, color_hex, 0x32, 0x0F]
# check command (up, stop, or down)
command = int(item())
- if(command == 0):
+ if command == 0:
# dim up
command_hex_code = 0x30
- elif(command == 1):
+ elif command == 1:
# dim down
command_hex_code = 0x31
- elif(command == 2):
+ elif command == 2:
# stop dim
command_hex_code = 0x32
else:
self.logger.error(f'enocean-PrepareData: {tx_eep} sending actuator command failed: invalid command {command}')
- return None
+ return
# define payload
payload = [int(dim_speed), color_hex, command_hex_code, 0x0F]
optional = []
return rorg, payload, optional
-
-
-#############################################################
-### --- Definitions for RORG = D2 --- ###
-### --> Definition EnOcean Variable Length Telegram (VLD) ###
-#############################################################
- def _prepare_data_for_tx_eep_D2_01_07(self, item, tx_eep):
+# Definitions for RORG = D2, EnOcean Variable Length Telegram (VLD)
+
+ def _prepare_data_for_tx_eep_D2_01_07(self, item, tx_eep):
"""
### --- Data for D2_01_07 (VLD) --- ###
Prepare data for Devices with Varable Length Telegram.
- There is currently no device information available.
- Optional 'pulsewidth' - Attribute was removed, it can be realized with the smarthomeNG
+ There is currently no device information available.
+ Optional 'pulsewidth' - Attribute was removed, it can be realized with the smarthomeNG
build in function autotimer!
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xD2
- SubTel = 0x03
db = 0xFF
Secu = 0x0
if self._plugin_instance.has_iattr(item.conf, 'enocean_rx_id'):
rx_id = int(self._plugin_instance.get_iattr_value(item.conf, 'enocean_rx_id'), 16)
- if (rx_id < 0) or (rx_id > 0xFFFFFFFF):
+ if rx_id < 0 or rx_id > 0xFFFFFFFF:
self.logger.error(f'enocean-PrepareData: {tx_eep} rx-ID-Offset out of range (0-127). Aborting.')
- return None
+ return
self.logger.debug(f'enocean-PrepareData: {tx_eep} enocean_rx_id found.')
else:
rx_id = 0
self.logger.debug(f'enocean-PrepareData: {tx_eep} no enocean_rx_id found!')
# Prepare Data Packet
- if (item() == 0):
+ if item() == 0:
payload = [0x01, 0x1E, 0x00]
- optional = [SubTel, rx_id, db, Secu]
- elif (item() == 1):
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
+ elif item() == 1:
payload = [0x01, 0x1E, 0x01]
- optional = [SubTel, rx_id, db, Secu]
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
else:
self.logger.error(f'enocean-PrepareData: {tx_eep} undefined Value. Error!')
- return None
+ return
# packet_data_prepared = (id_offset, 0xD2, payload, [0x03, 0xFF, 0xBA, 0xD0, 0x00, 0xFF, 0x0])
self.logger.info(f'enocean-PrepareData: {tx_eep} Packet Data Prepared for {tx_eep} (VLD)')
- optional = [SubTel, rx_id, db, Secu]
- return rorg, payload, optional
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
+
+ return RORG.VLD, payload, optional
def _prepare_data_for_tx_eep_D2_01_12(self, item, tx_eep):
"""
@@ -396,24 +381,22 @@ def _prepare_data_for_tx_eep_D2_01_12(self, item, tx_eep):
build in function autotimer!
"""
self.logger.debug(f'enocean-PrepareData: prepare data for tx_eep {tx_eep}')
- rorg = 0xD2
- SubTel = 0x03
db = 0xFF
Secu = 0x0
if self._plugin_instance.has_iattr(item.conf, 'enocean_rx_id'):
rx_id = int(self._plugin_instance.get_iattr_value(item.conf, 'enocean_rx_id'), 16)
- if (rx_id < 0) or (rx_id > 0xFFFFFFFF):
+ if rx_id < 0 or rx_id > 0xFFFFFFFF:
self.logger.error(f'enocean-PrepareData: {tx_eep} rx-ID-Offset out of range (0-127). Aborting.')
- return None
+ return
self.logger.debug(f'enocean-PrepareData: {tx_eep} enocean_rx_id found.')
else:
rx_id = 0
self.logger.debug(f'enocean-PrepareData: {tx_eep} no enocean_rx_id found!')
if self._plugin_instance.has_iattr(item.conf, 'enocean_channel'):
schannel = self._plugin_instance.get_iattr_value(item.conf, 'enocean_channel')
- if (schannel == "A"):
+ if schannel == "A":
channel = 0x00
- elif (schannel == "B"):
+ elif schannel == "B":
channel = 0x01
else:
channel = 0x1E
@@ -422,16 +405,17 @@ def _prepare_data_for_tx_eep_D2_01_12(self, item, tx_eep):
channel = 0x1E
self.logger.debug(f'enocean-PrepareData: {tx_eep} no enocean_channel found!')
# Prepare Data Packet
- if (item() == 0):
+ if item() == 0:
payload = [0x01, channel, 0x00]
- optional = [SubTel, rx_id, db, Secu]
- elif (item() == 1):
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
+ elif item() == 1:
payload = [0x01, channel, 0x01]
- optional = [SubTel, rx_id, db, Secu]
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
else:
self.logger.error(f'enocean-PrepareData: {tx_eep} undefined Value. Error!')
- return None
+ return
# packet_data_prepared = (id_offset, 0xD2, payload, [0x03, 0xFF, 0xBA, 0xD0, 0x00, 0xFF, 0x0])
self.logger.info(f'enocean-PrepareData: {tx_eep} Packet Data Prepared for {tx_eep} (VLD)')
- optional = [SubTel, rx_id, db, Secu]
- return rorg, payload, optional
+ optional = [PACKET_TYPE.RADIO_SUB_TEL, rx_id, db, Secu]
+
+ return RORG.VLD, payload, optional