class enOcean_link(dict): # Public attribute packets = None def __init__(self, port, callback): # Initialize parent class super(enOcean_link, self).__init__() # Set debug mode if needed # from enocean.consolelogger import init_logging # init_logging() # Set port try: self.c = SerialCommunicator(port=port, callback=callback) self.c.start() except: print("Error opening serial port (", port, ")") raise return def stop(self): if self.c.is_alive(): self.c.stop() return
class Encocean_Sat(object): def __init__(self): self.communicator = SerialCommunicator(port='/dev/ttyUSB0') self.communicator.start() def assemble_radio_packet(transmitter_id): return RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, sender=transmitter_id, CV=50, TMP=21.5, ES='true') def monitor(self): while self.communicator.is_alive(): try: # Loop to empty the queue... packet = self.communicator.receive.get(block=True, timeout=1) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS4: # parse packet with given FUNC and TYPE for k in packet.parse_eep(0x02, 0x05): print('A %s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS1: # alternatively you can select FUNC and TYPE explicitely packet.select_eep(0x00, 0x01) # parse it packet.parse_eep() for k in packet.parsed: print('B %s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.RPS: received = packet.parse_eep(0x02, 0x02) print(packet.sender_hex, packet.parsed['EB']['raw_value']) # udp_send.send_to_server('Enocean.' + packet.sender_hex, packet.parsed['EB']['raw_value']) dicti = { 'Name': 'Enocean.' + str(packet.sender_hex), 'Value': str(packet.parsed['EB']['raw_value']) } mqtt_publish.mqtt_pub( 'Inputs/Satellite/' + constants.name + '/Enocean/' + str(packet.sender_hex), dicti) except queue.Empty: continue except KeyboardInterrupt: break except Exception: traceback.print_exc(file=sys.stdout) break if self.communicator.is_alive(): self.communicator.stop()
def seriallistener(): global on_raspi, sys_init, id_list senderID = None fire_event = False if(on_raspi is True): p = Packet(PACKET.COMMON_COMMAND, [0x08]) c = SerialCommunicator() c.start() c.send(p) while sys_init is True: try: p = c.receive.get(block=True, timeout=1) if(p.type == PACKET.RADIO and p.rorg == RORG.BS4): timestamp = datetime.datetime.now() senderID = p.data[5]*1677216+p.data[6]*65536+p.data[7]*256+p.data[8] if len(id_list) == 0: id_list.append(senderID) fire_event = True else: i=0 for i in range(0,len(id_list)): if id_list[i] == senderID: id_list.remove(senderID) fire_event = False break elif id_list[i] != senderID and i == (len(id_list)-1): id_list.append(senderID) fire_event = True break if fire_event is True: serialevent = pygame.event.Event(EV_SERIAL_INPUT, id=senderID, tm=timestamp) pygame.event.post(serialevent) fire_event = False except queue.Empty: continue except KeyboardInterrupt: break except Exception: traceback.print_exc(file=sys.stdout) break c.stop() else: while pygame.display.get_init() is True: pygame.event.post(serialevent) time.sleep(random.uniform(1,5))
class Encocean_Sat(object): def __init__(self): self.communicator = SerialCommunicator(port='/dev/ttyUSB0') self.communicator.start() def assemble_radio_packet(transmitter_id): return RadioPacket.create(rorg=RORG.BS4, rorg_func=0x20, rorg_type=0x01, sender=transmitter_id, CV=50, TMP=21.5, ES='true') def monitor(self): while self.communicator.is_alive(): try: # Loop to empty the queue... packet = self.communicator.receive.get(block=True, timeout=1) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS4: # parse packet with given FUNC and TYPE for k in packet.parse_eep(0x02, 0x05): print('A %s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS1: # alternatively you can select FUNC and TYPE explicitely packet.select_eep(0x00, 0x01) # parse it packet.parse_eep() for k in packet.parsed: print('B %s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.RPS: received = packet.parse_eep(0x02, 0x02) print packet.sender_hex, packet.parsed['EB']['raw_value'] # udp_send.send_to_server('Enocean.' + packet.sender_hex, packet.parsed['EB']['raw_value']) dicti = {'Name': 'Enocean.'+str(packet.sender_hex), 'Value': str(packet.parsed['EB']['raw_value'])} mqtt_publish.mqtt_pub('Inputs/Satellite/' + constants.name + '/Enocean/'+str(packet.sender_hex),dicti) except queue.Empty: continue except KeyboardInterrupt: break except Exception: traceback.print_exc(file=sys.stdout) break if self.communicator.is_alive(): self.communicator.stop()
# endless loop receiving radio packets while communicator.is_alive(): try: # Loop to empty the queue... packet = communicator.receive.get(block=True, timeout=1) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS4: # parse packet with given FUNC and TYPE for k in packet.parse_eep(0x02, 0x05): print('%s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.BS1: # alternatively you can select FUNC and TYPE explicitely packet.select_eep(0x00, 0x01) # parse it packet.parse_eep() for k in packet.parsed: print('%s: %s' % (k, packet.parsed[k])) if packet.packet_type == PACKET.RADIO and packet.rorg == RORG.RPS: for k in packet.parse_eep(0x02, 0x02): print('%s: %s' % (k, packet.parsed[k])) except queue.Empty: continue except KeyboardInterrupt: break except Exception: traceback.print_exc(file=sys.stdout) break if communicator.is_alive(): communicator.stop()
class Communicator: mqtt = None enocean = None CONNECTION_RETURN_CODE = [ "connection successful", "incorrect protocol version", "invalid client identifier", "server unavailable", "bad username or password", "not authorised", ] def __init__(self, config, sensors): self.conf = config self.sensors = sensors # check for mandatory configuration if 'mqtt_host' not in self.conf or 'enocean_port' not in self.conf: raise Exception( "Mandatory configuration not found: mqtt_host/enocean_port") mqtt_port = int( self.conf['mqtt_port']) if 'mqtt_port' in self.conf else 1883 mqtt_keepalive = int(self.conf['mqtt_keepalive'] ) if 'mqtt_keepalive' in self.conf else 0 self.mqtt_prefix = self.conf['mqtt_prefix'] # setup mqtt connection client_id = self.conf[ 'mqtt_client_id'] if 'mqtt_client_id' in self.conf else '' self.mqtt = mqtt.Client(client_id=client_id) self.mqtt.on_connect = self._on_connect self.mqtt.on_disconnect = self._on_disconnect self.mqtt.on_message = self._on_mqtt_message self.mqtt.on_publish = self._on_mqtt_publish if 'mqtt_user' in self.conf: logging.info("Authenticating: " + self.conf['mqtt_user']) self.mqtt.username_pw_set(self.conf['mqtt_user'], self.conf['mqtt_pwd']) if str(self.conf.get('mqtt_ssl')) in ("True", "true", "1"): logging.info("Enabling SSL") ca_certs = self.conf[ 'mqtt_ssl_ca_certs'] if 'mqtt_ssl_ca_certs' in self.conf else None certfile = self.conf[ 'mqtt_ssl_certfile'] if 'mqtt_ssl_certfile' in self.conf else None keyfile = self.conf[ 'mqtt_ssl_keyfile'] if 'mqtt_ssl_keyfile' in self.conf else None self.mqtt.tls_set(ca_certs=ca_certs, certfile=certfile, keyfile=keyfile) if str(self.conf.get('mqtt_ssl_insecure')) in ("True", "true", "1"): logging.warning("Disabling SSL certificate verification") self.mqtt.tls_insecure_set(True) if str(self.conf.get('mqtt_debug')) in ("True", "true", "1"): self.mqtt.enable_logger() logging.debug("Connecting to host " + self.conf['mqtt_host'] + ", port " + str(mqtt_port) + ", keepalive " + str(mqtt_keepalive)) self.mqtt.connect_async(self.conf['mqtt_host'], port=mqtt_port, keepalive=mqtt_keepalive) self.mqtt.loop_start() # setup enocean communication try: self.enocean = SerialCommunicator(self.conf['enocean_port']) except serial.serialutil.SerialException: logging.exception('Serial Error') sys.exit(1) self.enocean.start() # sender will be automatically determined self.enocean_sender = None def __del__(self): if self.enocean is not None and self.enocean.is_alive(): self.enocean.stop() def __publish(self, topic, msg, retain=False): self.mqtt.publish("%s/%s" % (self.mqtt_prefix, topic), msg, retain) def _on_connect(self, mqtt_client, userdata, flags, rc): '''callback for when the client receives a CONNACK response from the MQTT server.''' if rc == 0: logging.info("Succesfully connected to MQTT broker.") # listen to enocean send requests mqtt_client.subscribe("%s/#" % self.mqtt_prefix) else: logging.error("Error connecting to MQTT broker: %s", self.CONNECTION_RETURN_CODE[rc]) def _on_disconnect(self, mqtt_client, userdata, rc): '''callback for when the client disconnects from the MQTT server.''' if rc == 0: logging.warning("Successfully disconnected from MQTT broker") else: logging.warning("Unexpectedly disconnected from MQTT broker: " + self.CONNECTION_RETURN_CODE[rc]) def _on_mqtt_message(self, mqtt_client, userdata, msg): '''the callback for when a PUBLISH message is received from the MQTT server.''' #1 parse topic %prefix%/%device% topic = msg.topic.split('/') if len(topic) < 3 or topic[2] != 'cmd': return # search for sensor for cur_sensor in self.sensors: if cur_sensor['name'] in topic[1]: rorg = cur_sensor['rorg'] rorg_func = cur_sensor['func'] rorg_type = cur_sensor['type'] payload = json.loads(msg.payload) command = payload.pop('CMD', 1) packet = RadioPacket.create(rorg=rorg, rorg_func=rorg_func, rorg_type=rorg_type, sender=self.enocean.base_id, destination=cur_sensor['address'], command=command, **payload) self.enocean.send(packet) def _on_mqtt_publish(self, mqtt_client, userdata, mid): '''the callback for when a PUBLISH message is successfully sent to the MQTT server.''' #logging.debug("Published MQTT message "+str(mid)) pass def _read_packet(self, packet): '''interpret packet, read properties and publish to MQTT''' mqtt_publish_json = True if 'mqtt_publish_json' in self.conf and self.conf[ 'mqtt_publish_json'] == "True" else False mqtt_json = {} # loop through all configured devices for cur_sensor in self.sensors: # does this sensor match? if packet.sender == cur_sensor['address']: # found sensor configured in config file mqtt_json['address'] = enocean.utils.to_hex_string( packet.sender) if str(cur_sensor.get('publish_rssi')) in ("True", "true", "1"): if mqtt_publish_json: mqtt_json['RSSI'] = packet.dBm else: self.__publish(cur_sensor['name'] + "/RSSI", packet.dBm) if not packet.learn or str( cur_sensor.get('log_learn')) in ("True", "true", "1"): # data packet received found_property = False if packet.packet_type == PACKET.RADIO and packet.rorg == cur_sensor[ 'rorg']: # radio packet of proper rorg type received; parse EEP direction = cur_sensor.get('direction') properties = packet.parse_eep(cur_sensor['func'], cur_sensor['type'], direction) # loop through all EEP properties for prop_name in properties: found_property = True cur_prop = packet.parsed[prop_name] # we only extract numeric values, either the scaled ones or the raw values for enums if isinstance(cur_prop['value'], numbers.Number): value = cur_prop['value'] else: value = cur_prop['raw_value'] # publish extracted information logging.debug("{}: {} ({})={} {}".format( cur_sensor['name'], prop_name, cur_prop['description'], cur_prop['value'], cur_prop['unit'])) retain = str( cur_sensor.get('persistent')) in ("True", "true", "1") if mqtt_publish_json: mqtt_json[prop_name] = value else: self.__publish(cur_sensor['name'] + "/" + prop_name, value, retain=retain) if not found_property: logging.warn('message not interpretable: {}'.format( cur_sensor['name'])) elif mqtt_publish_json: name = cur_sensor['name'] value = json.dumps(mqtt_json) logging.debug("{}: Sent MQTT: {}".format(name, value)) self.__publish(name, value, retain=retain) else: # learn request received logging.info("learn request not emitted to mqtt") def _reply_packet(self, in_packet, sensor): '''send enocean message as a reply to an incoming message''' # prepare addresses destination = in_packet.sender # prepare packet if 'direction' in sensor: # we invert the direction in this reply direction = 1 if sensor['direction'] == 2 else 2 else: direction = None packet = RadioPacket.create(RORG.BS4, sensor['func'], sensor['type'], direction=direction, sender=self.enocean_sender, destination=destination, learn=in_packet.learn) # assemble data based on packet type (learn / data) if not in_packet.learn: # data packet received # start with default data packet.data[1:5] = [(sensor['default_data'] >> i & 0xff) for i in (24, 16, 8, 0)] # do we have specific data to send? if 'data' in sensor: # override with specific data settings packet.set_eep(sensor['data']) else: # what to do if we have no data to send yet? logging.warn('sending default data as answer to %s', sensor['name']) else: # learn request received # copy EEP and manufacturer ID packet.data[1:5] = in_packet.data[1:5] # update flags to acknowledge learn request packet.data[4] = 0xf0 # send it logging.info('sending: {}'.format(packet)) self.enocean.send(packet) def _process_radio_packet(self, packet): # first, look whether we have this sensor configured found_sensor = False for cur_sensor in self.sensors: if packet.sender == cur_sensor['address']: found_sensor = cur_sensor # skip ignored sensors if found_sensor and 'ignore' in found_sensor and found_sensor['ignore']: return # log packet, if not disabled if str(self.conf.get('log_packets')) in ("True", "true", "1"): logging.info('received: {}'.format(packet)) # abort loop if sensor not found if not found_sensor: logging.info('unknown sensor: {}'.format( enocean.utils.to_hex_string(packet.sender))) return # interpret packet, read properties and publish to MQTT self._read_packet(packet) # check for neccessary reply if str(found_sensor.get('answer')) in ("True", "true", "1"): self._reply_packet(packet, found_sensor) def run(self): # start endless loop for listening while self.enocean.is_alive(): # Request transmitter ID, if needed if self.enocean_sender is None: self.enocean_sender = self.enocean.base_id # Loop to empty the queue... try: # get next packet if (platform.system() == 'Windows'): # only timeout on Windows for KeyboardInterrupt checking packet = self.enocean.receive.get(block=True, timeout=1) else: packet = self.enocean.receive.get(block=True) # check packet type if packet.packet_type == PACKET.RADIO: self._process_radio_packet(packet) elif packet.packet_type == PACKET.RESPONSE: response_code = RETURN_CODE(packet.data[0]) logging.info("got response packet: {}".format( response_code.name)) else: logging.info("got non-RF packet: {}".format(packet)) continue except queue.Empty: continue except KeyboardInterrupt: logging.debug("Exception: KeyboardInterrupt") break # Run finished, close MQTT client and stop Enocean thread logging.debug("Cleaning up") self.mqtt.loop_stop() self.mqtt.disconnect() self.mqtt.loop_forever() # will block until disconnect complete self.enocean.stop()
# endless loop receiving radio packets while c.is_alive(): try: # Loop to empty the queue... p = c.receive.get(block=True, timeout=1) if p.type == PACKET.RADIO and p.rorg == RORG.BS4: # parse packet with given FUNC and TYPE for k in p.parse_eep(0x02, 0x05): print('%s: %s' % (k, p.parsed[k])) if p.type == PACKET.RADIO and p.rorg == RORG.BS1: # alternatively you can select FUNC and TYPE explicitely # p.select_eep(0x00, 0x01) # parse it for k in p.parse_eep(0x00, 0x01): print('%s: %s' % (k, p.parsed[k])) if p.type == PACKET.RADIO and p.rorg == RORG.RPS: for k in p.parse_eep(0x02, 0x02): print('%s: %s' % (k, p.parsed[k])) except queue.Empty: continue except KeyboardInterrupt: break except Exception: traceback.print_exc(file=sys.stdout) break if c.is_alive(): c.stop()
class Communicator: """the main working class providing the MQTT interface to the enocean packet classes""" mqtt = None enocean = None CONNECTION_RETURN_CODE = [ "connection successful", "incorrect protocol version", "invalid client identifier", "server unavailable", "bad username or password", "not authorised", ] def __init__(self, config, sensors): self.conf = config self.sensors = sensors # check for mandatory configuration if 'mqtt_host' not in self.conf or 'enocean_port' not in self.conf: raise Exception("Mandatory configuration not found: mqtt_host/enocean_port") mqtt_port = int(self.conf['mqtt_port']) if 'mqtt_port' in self.conf else 1883 mqtt_keepalive = int(self.conf['mqtt_keepalive']) if 'mqtt_keepalive' in self.conf else 60 # setup mqtt connection client_id = self.conf['mqtt_client_id'] if 'mqtt_client_id' in self.conf else '' self.mqtt = mqtt.Client(client_id=client_id) self.mqtt.on_connect = self._on_connect self.mqtt.on_disconnect = self._on_disconnect self.mqtt.on_message = self._on_mqtt_message self.mqtt.on_publish = self._on_mqtt_publish if 'mqtt_user' in self.conf: logging.info("Authenticating: %s", self.conf['mqtt_user']) self.mqtt.username_pw_set(self.conf['mqtt_user'], self.conf['mqtt_pwd']) if str(self.conf.get('mqtt_ssl')) in ("True", "true", "1"): logging.info("Enabling SSL") ca_certs = self.conf['mqtt_ssl_ca_certs'] if 'mqtt_ssl_ca_certs' in self.conf else None certfile = self.conf['mqtt_ssl_certfile'] if 'mqtt_ssl_certfile' in self.conf else None keyfile = self.conf['mqtt_ssl_keyfile'] if 'mqtt_ssl_keyfile' in self.conf else None self.mqtt.tls_set(ca_certs=ca_certs, certfile=certfile, keyfile=keyfile) if str(self.conf.get('mqtt_ssl_insecure')) in ("True", "true", "1"): logging.warning("Disabling SSL certificate verification") self.mqtt.tls_insecure_set(True) if str(self.conf.get('mqtt_debug')) in ("True", "true", "1"): self.mqtt.enable_logger() logging.debug("Connecting to host %s, port %s, keepalive %s", self.conf['mqtt_host'], mqtt_port, mqtt_keepalive) self.mqtt.connect_async(self.conf['mqtt_host'], port=mqtt_port, keepalive=mqtt_keepalive) self.mqtt.loop_start() # setup enocean communication self.enocean = SerialCommunicator(self.conf['enocean_port']) self.enocean.start() # sender will be automatically determined self.enocean_sender = None def __del__(self): if self.enocean is not None and self.enocean.is_alive(): self.enocean.stop() def _on_connect(self, mqtt_client, _userdata, _flags, return_code): '''callback for when the client receives a CONNACK response from the MQTT server.''' if return_code == 0: logging.info("Succesfully connected to MQTT broker.") # listen to enocean send requests for cur_sensor in self.sensors: # logging.debug("MQTT subscribing: %s", cur_sensor['name']+'/req/#') mqtt_client.subscribe(cur_sensor['name']+'/req/#') else: logging.error("Error connecting to MQTT broker: %s", self.CONNECTION_RETURN_CODE[return_code] if return_code < len(self.CONNECTION_RETURN_CODE) else return_code) def _on_disconnect(self, _mqtt_client, _userdata, return_code): '''callback for when the client disconnects from the MQTT server.''' if return_code == 0: logging.warning("Successfully disconnected from MQTT broker") else: logging.warning("Unexpectedly disconnected from MQTT broker: %s", self.CONNECTION_RETURN_CODE[return_code] if return_code < len(self.CONNECTION_RETURN_CODE) else return_code) def _on_mqtt_message(self, _mqtt_client, _userdata, msg): '''the callback for when a PUBLISH message is received from the MQTT server.''' # search for sensor found_topic = False logging.debug("Got MQTT message: %s", msg.topic) for cur_sensor in self.sensors: if cur_sensor['name'] in msg.topic: # get message topic prop = msg.topic[len(cur_sensor['name']+"/req/"):] # do we face a send request? if prop == "send": found_topic = True logging.debug("Trigger message to: %s", cur_sensor['name']) destination = [(cur_sensor['address'] >> i*8) & 0xff for i in reversed(range(4))] # Retrieve command from MQTT message and pass it to _send_packet() command = None command_shortcut = cur_sensor.get('command') if command_shortcut: # Check MQTT message has valid data if 'data' not in cur_sensor: logging.warning('No data to send from MQTT message!') return # Check MQTT message sets the command field if command_shortcut not in cur_sensor['data']: logging.warning( 'Command field %s must be set in MQTT message!', command_shortcut) return # Retrieve command id from MQTT message command = cur_sensor['data'][command_shortcut] logging.debug('Retrieved command id from MQTT message: %s', hex(command)) self._send_packet(cur_sensor, destination, command) # Clear sent data, if requested by the send message # MQTT payload is binary data, thus we need to decode it if msg.payload.decode('UTF-8') == "clear": logging.debug('Clearing data buffer.') del cur_sensor['data'] else: found_topic = True # parse message content value = None try: value = int(msg.payload) except ValueError: logging.warning("Cannot parse int value for %s: %s", msg.topic, msg.payload) # store received data logging.debug("%s: %s=%s", cur_sensor['name'], prop, value) if 'data' not in cur_sensor: cur_sensor['data'] = {} cur_sensor['data'][prop] = value if not found_topic: logging.warning("Unexpected MQTT message: %s", msg.topic) def _on_mqtt_publish(self, _mqtt_client, _userdata, _mid): '''the callback for when a PUBLISH message is successfully sent to the MQTT server.''' #logging.debug("Published MQTT message "+str(mid)) def _get_command_id(self, packet, cur_sensor): '''interpret packet to retrieve command id from VLD packets''' # Retrieve the first defined EEP profile matching sensor RORG-FUNC-TYPE # As we take the first defined profile, this suppose that command is # ALWAYS at the same offset and ALWAYS has the same size. profile = packet.eep.find_profile( packet._bit_data, cur_sensor['rorg'], cur_sensor['func'], cur_sensor['type']) # Loop over profile contents for source in profile.contents: if not source.name: continue # Check the current shortcut matches the command shortcut if source['shortcut'] == cur_sensor.get('command'): return packet.eep._get_raw(source, packet._bit_data) # If not found, return None for default handling of the packet return None def _read_packet(self, packet): '''interpret packet, read properties and publish to MQTT''' mqtt_publish_json = 'mqtt_publish_json' in self.conf and \ self.conf['mqtt_publish_json'] in ("True", "true", "1") mqtt_json = {} # loop through all configured devices for cur_sensor in self.sensors: # does this sensor match? if enocean.utils.combine_hex(packet.sender) == cur_sensor['address']: # found sensor configured in config file if str(cur_sensor.get('publish_rssi')) in ("True", "true", "1"): if mqtt_publish_json: mqtt_json['RSSI'] = packet.dBm else: self.mqtt.publish(cur_sensor['name']+"/RSSI", packet.dBm) if not packet.learn or str(cur_sensor.get('log_learn')) in ("True", "true", "1"): retain = str(cur_sensor.get('persistent')) in ("True", "true", "1") found_property = self._handle_data_packet( packet, cur_sensor, mqtt_json if mqtt_publish_json else None, retain ) if not found_property: logging.warning("message not interpretable: %s", cur_sensor['name']) elif mqtt_publish_json: name = cur_sensor['name'] value = json.dumps(mqtt_json) logging.debug("%s: Sent MQTT: %s", name, value) self.mqtt.publish(name, value, retain=retain) else: # learn request received logging.info("learn request not emitted to mqtt") def _handle_data_packet(self, packet, sensor, mqtt_json, retain: bool): # data packet received found_property = False if packet.packet_type == PACKET.RADIO and packet.rorg == sensor['rorg']: # radio packet of proper rorg type received; parse EEP direction = sensor.get('direction') # Retrieve command from the received packet and pass it to parse_eep() command = None if sensor.get('command'): command = self._get_command_id(packet, sensor) logging.debug('Retrieved command id from packet: %s', hex(command)) # Retrieve properties from EEP properties = packet.parse_eep( sensor['func'], sensor['type'], direction, command) # loop through all EEP properties for prop_name in properties: found_property = True cur_prop = packet.parsed[prop_name] # we only extract numeric values, either the scaled ones # or the raw values for enums if isinstance(cur_prop['value'], numbers.Number): value = cur_prop['value'] else: value = cur_prop['raw_value'] # publish extracted information logging.debug("%s: %s (%s)=%s %s", sensor['name'], prop_name, cur_prop['description'], cur_prop['value'], cur_prop['unit']) retain = str(sensor.get('persistent')) in ("True", "true", "1") if mqtt_json is not None: mqtt_json[prop_name] = value else: self.mqtt.publish(f"{sensor['name']}/{prop_name}", value, retain=retain) return found_property def _reply_packet(self, in_packet, sensor): '''send enocean message as a reply to an incoming message''' # prepare addresses destination = in_packet.sender self._send_packet(sensor, destination, None, True, in_packet.data if in_packet.learn else None) def _send_packet(self, sensor, destination, command=None, negate_direction=False, learn_data=None): '''triggers sending of an enocean packet''' # determine direction indicator if 'direction' in sensor: direction = sensor['direction'] if negate_direction: # we invert the direction in this reply direction = 1 if direction == 2 else 2 else: direction = None # is this a response to a learn packet? is_learn = learn_data is not None # Add possibility for the user to indicate a specific sender address # in sensor configuration using added 'sender' field. # So use specified sender address if any if 'sender' in sensor: sender = [(sensor['sender'] >> i*8) & 0xff for i in reversed(range(4))] else: sender = self.enocean_sender try: # Now pass command to RadioPacket.create() packet = RadioPacket.create(sensor['rorg'], sensor['func'], sensor['type'], direction=direction, command=command, sender=sender, destination=destination, learn=is_learn) except ValueError as err: logging.error("Cannot create RF packet: %s", err) return # assemble data based on packet type (learn / data) if not is_learn: # data packet received # start with default data # Initialize packet with default_data if specified if 'default_data' in sensor: packet.data[1:5] = [(sensor['default_data'] >> i*8) & 0xff for i in reversed(range(4))] # do we have specific data to send? if 'data' in sensor: # override with specific data settings logging.debug("sensor data: %s", sensor['data']) packet.set_eep(sensor['data']) packet.parse_eep() # ensure that the logging output of packet is updated else: # what to do if we have no data to send yet? logging.warning('sending only default data as answer to %s', sensor['name']) else: # learn request received # copy EEP and manufacturer ID packet.data[1:5] = learn_data[1:5] # update flags to acknowledge learn request packet.data[4] = 0xf0 # send it logging.info("sending: %s", packet) self.enocean.send(packet) def _process_radio_packet(self, packet): # first, look whether we have this sensor configured found_sensor = False for cur_sensor in self.sensors: if 'address' in cur_sensor and \ enocean.utils.combine_hex(packet.sender) == cur_sensor['address']: found_sensor = cur_sensor # skip ignored sensors if found_sensor and 'ignore' in found_sensor and found_sensor['ignore']: return # log packet, if not disabled if str(self.conf.get('log_packets')) in ("True", "true", "1"): logging.info("received: %s", packet) # abort loop if sensor not found if not found_sensor: logging.info("unknown sensor: %s", enocean.utils.to_hex_string(packet.sender)) return # interpret packet, read properties and publish to MQTT self._read_packet(packet) # check for neccessary reply if str(found_sensor.get('answer')) in ("True", "true", "1"): self._reply_packet(packet, found_sensor) def run(self): """the main loop with blocking enocean packet receive handler""" # start endless loop for listening while self.enocean.is_alive(): # Request transmitter ID, if needed if self.enocean_sender is None: self.enocean_sender = self.enocean.base_id # Loop to empty the queue... try: # get next packet if platform.system() == 'Windows': # only timeout on Windows for KeyboardInterrupt checking packet = self.enocean.receive.get(block=True, timeout=1) else: packet = self.enocean.receive.get(block=True) # check packet type if packet.packet_type == PACKET.RADIO: self._process_radio_packet(packet) elif packet.packet_type == PACKET.RESPONSE: response_code = RETURN_CODE(packet.data[0]) logging.info("got response packet: %s", response_code.name) else: logging.info("got non-RF packet: %s", packet) continue except queue.Empty: continue except KeyboardInterrupt: logging.debug("Exception: KeyboardInterrupt") break # Run finished, close MQTT client and stop Enocean thread logging.debug("Cleaning up") self.mqtt.loop_stop() self.mqtt.disconnect() self.mqtt.loop_forever() # will block until disconnect complete self.enocean.stop()
class enocean(): def __init__(self, name): self.name = name self.wait = threading.Event() self.comm = None self.queue = queue.Queue() self.clientid = config.clientid self.deviceid = config.get(self.name, 'deviceid') self.descr = config.get(self.name, 'descr', fallback='') self.port = config.get(self.name, 'port', fallback=None) self.addr = config.get(self.name, 'addr') csvfile = config.get(self.name, 'csvmap') csvmap = WCSVMap() self.mapping = [] self.mapping = csvmap.load(csvfile, WCSVType.Raw) cache.store_meta(self.deviceid, self.name, self.descr, self.mapping) def decode(self, packet): ut = time.time() measures = {} if packet.packet_type == PACKET.RADIO_ERP1 and packet.rorg == RORG.VLD: packet.select_eep(0x05, 0x00) packet.parse_eep() for k in packet.parsed: self.__measure_map(k, packet, measures) if packet.packet_type == PACKET.RADIO_ERP1 and packet.rorg == RORG.BS4: for k in packet.parse_eep(0x02, 0x05): self.__measure_map(k, packet, measures) if packet.packet_type == PACKET.RADIO_ERP1 and packet.rorg == RORG.BS1: packet.select_eep(0x00, 0x01) packet.parse_eep() for k in packet.parsed: self.__measure_map(k, packet, measures) if packet.packet_type == PACKET.RADIO_ERP1 and packet.rorg == RORG.RPS: for k in packet.parse_eep(0x02, 0x02): self.__measure_map(k, packet, measures) if measures: data = { 'ts': ut, 'client_id': self.clientid, 'device_id': packet.sender_hex, 'measures': measures } cache.store(data) def __measure_map(self, k, packet, measures): logger.debug('Decoded radio packet type %s: %s' % (k, packet.parsed[k])) for row in self.mapping: (name, descr, unit, datatype, scale, offset) = row scale = float(scale) offset = float(offset) if name == k: if datatype in ('c', 's'): value = packet.parsed[k]['value'] else: value = round( float(packet.parsed[k]['value']) * scale, 14) + offset unit = packet.parsed[k]['unit'] descr = packet.parsed[k]['description'] measures[k] = value logger.info( 'EnOcean sensor: %s packet type: %s measure: %s (%s) %s %s' % (packet.sender_hex, packet.packet_type, name, descr, value, unit)) def __wait(self): global waits if self.port in waits: logger.debug( "Plugin %s waiting for SerialCommunicator on port %s..." % (self.name, self.port)) self.wait = waits[self.port] self.wait.wait() waits[self.port] = self.wait def __ready(self): if not self.wait.is_set(): logger.debug("Plugin %s SerialCommunicator on port %s ready" % (self.name, self.port)) self.wait.set() def __get_comm(self): global comms if self.port in comms: self.comm = comms[self.port] def __set_comm(self): global comms comms[self.port] = self.comm def __enqueue(self, packet): global queues logger.debug("Received radio packet %s" % packet) for queue in queues: queues[queue].put(packet) def __set_queue(self): global queues queues[self.name] = self.queue def run(self): self.__wait() self.__get_comm() if self.comm is None: self.comm = SerialCommunicator(port=self.port, callback=self.__enqueue) logger.info("Starting EnOcean listener on port %s" % self.port) # Workaround for setting thread name coherent with plugin's thread name self.comm.name = self.name self.comm.start() if self.comm.base_id: logger.info("The Base ID of your module is %s" % utils.to_hex_string(self.comm.base_id)) self.__set_comm() self.__ready() self.__set_queue() while self.comm.is_alive(): if self.queue.qsize() > 0: packet = self.queue.get() if hasattr(packet, 'sender_hex'): if packet.sender_hex == self.addr: self.decode(packet) time.sleep(1) def stop(self): if self.comm and self.comm.is_alive: self.comm.stop() logger.info("Stopping EnOcean listener on port %s" % self.port)
class Communicator: mqtt = None enocean = None CONNECTION_RETURN_CODE = [ "connection successful", "incorrect protocol version", "invalid client identifier", "server unavailable", "bad username or password", "not authorised", ] def __init__(self, config, sensors): self.conf = config self.sensors = sensors # setup mqtt connection self.mqtt = mqtt.Client() self.mqtt.on_connect = self._on_connect self.mqtt.on_disconnect = self._on_disconnect self.mqtt.on_message = self._on_mqtt_message self.mqtt.on_publish = self._on_mqtt_publish if 'mqtt_user' in self.conf: logging.info("Authenticating: " + self.conf['mqtt_user']) self.mqtt.username_pw_set(self.conf['mqtt_user'], self.conf['mqtt_pwd']) self.mqtt.connect(self.conf['mqtt_host'], int(self.conf['mqtt_port'], 0)) self.mqtt.loop_start() # setup enocean communication self.enocean = SerialCommunicator(self.conf['enocean_port']) self.enocean.start() # sender will be automatically determined self.enocean_sender = None def __del__(self): if self.enocean is not None and self.enocean.is_alive(): self.enocean.stop() def _on_connect(self, mqtt_client, userdata, flags, rc): '''callback for when the client receives a CONNACK response from the MQTT server.''' if rc == 0: logging.info("Succesfully connected to MQTT broker.") # listen to enocean send requests for cur_sensor in self.sensors: mqtt_client.subscribe(cur_sensor['name'] + '/req/#') else: logging.error("Error connecting to MQTT broker: %s", self.CONNECTION_RETURN_CODE[rc]) def _on_disconnect(self, mqtt_client, userdata, rc): '''callback for when the client disconnects from the MQTT server.''' if rc == 0: logging.warning("Successfully disconnected from MQTT broker") else: logging.warning("Unexpectedly disconnected from MQTT broker: " + str(rc)) def _on_mqtt_message(self, mqtt_client, userdata, msg): '''the callback for when a PUBLISH message is received from the MQTT server.''' # search for sensor for cur_sensor in self.sensors: if cur_sensor['name'] in msg.topic: # store data for this sensor if 'data' not in cur_sensor: cur_sensor['data'] = {} prop = msg.topic[len(cur_sensor['name'] + "/req/"):] try: cur_sensor['data'][prop] = int(msg.payload) except ValueError: logging.warning("Cannot parse int value for %s: %s", msg.topic, msg.payload) def _on_mqtt_publish(self, mqtt_client, userdata, mid): '''the callback for when a PUBLISH message is successfully sent to the MQTT server.''' #logging.debug("Published MQTT message "+str(mid)) pass def _read_packet(self, packet): '''interpret packet, read properties and publish to MQTT''' # loop through all configured devices for cur_sensor in self.sensors: # does this sensor match? if enocean.utils.combine_hex( packet.sender) == cur_sensor['address']: # found sensor configured in config file if 'publish_rssi' in cur_sensor and cur_sensor['publish_rssi']: self.mqtt.publish(cur_sensor['name'] + "/RSSI", packet.dBm) if not packet.learn or ('log_learn' in cur_sensor and cur_sensor['log_learn']): # data packet received found_property = False if packet.packet_type == PACKET.RADIO and packet.rorg == cur_sensor[ 'rorg']: # radio packet of proper rorg type received; parse EEP direction = cur_sensor[ 'direction'] if 'direction' in cur_sensor else None properties = packet.parse_eep(cur_sensor['func'], cur_sensor['type'], direction) # loop through all EEP properties for prop_name in properties: found_property = True cur_prop = packet.parsed[prop_name] # we only extract numeric values, either the scaled ones or the raw values for enums if isinstance(cur_prop['value'], numbers.Number): value = cur_prop['value'] else: value = cur_prop['raw_value'] # publish extracted information logging.debug("{}: {} ({})={} {}".format( cur_sensor['name'], prop_name, cur_prop['description'], cur_prop['value'], cur_prop['unit'])) retain = 'persistent' in cur_sensor and cur_sensor[ 'persistent'] self.mqtt.publish(cur_sensor['name'] + "/" + prop_name, value, retain=retain) break if not found_property: logging.warn('message not interpretable: {}'.format( found_sensor['name'])) else: # learn request received logging.info("learn request not emitted to mqtt") def _reply_packet(self, in_packet, sensor): '''send enocean message as a reply to an incoming message''' # prepare addresses destination = in_packet.sender # prepare packet if 'direction' in sensor: # we invert the direction in this reply direction = 1 if sensor['direction'] == 2 else 2 else: direction = None packet = RadioPacket.create(RORG.BS4, sensor['func'], sensor['type'], direction=direction, sender=self.enocean_sender, destination=destination, learn=in_packet.learn) # assemble data based on packet type (learn / data) if not in_packet.learn: # data packet received # start with default data packet.data[1:5] = [(sensor['default_data'] >> i & 0xff) for i in (24, 16, 8, 0)] # do we have specific data to send? if 'data' in sensor: # override with specific data settings packet.set_eep(sensor['data']) else: # what to do if we have no data to send yet? logging.warn('sending default data as answer to %s', sensor['name']) else: # learn request received # copy EEP and manufacturer ID packet.data[1:5] = in_packet.data[1:5] # update flags to acknowledge learn request packet.data[4] = 0xf0 # send it logging.info('sending: {}'.format(packet)) self.enocean.send(packet) def _process_radio_packet(self, packet): # first, look whether we have this sensor configured found_sensor = False for cur_sensor in self.sensors: if enocean.utils.combine_hex( packet.sender) == cur_sensor['address']: found_sensor = cur_sensor # skip ignored sensors if found_sensor and 'ignore' in found_sensor and found_sensor['ignore']: return # log packet, if not disabled if int(self.conf['log_packets']): logging.info('received: {}'.format(packet)) # abort loop if sensor not found if not found_sensor: logging.info('unknown sensor: {}'.format( enocean.utils.to_hex_string(packet.sender))) return # interpret packet, read properties and publish to MQTT self._read_packet(packet) # check for neccessary reply if 'answer' in found_sensor and found_sensor['answer']: self._reply_packet(packet, found_sensor) def run(self): # start endless loop for listening while self.enocean.is_alive(): # Request transmitter ID, if needed if self.enocean_sender is None: self.enocean_sender = self.enocean.base_id # Loop to empty the queue... try: # get next packet packet = self.enocean.receive.get(block=True, timeout=1) # check packet type if packet.packet_type == PACKET.RADIO: self._process_radio_packet(packet) elif packet.packet_type == PACKET.RESPONSE: response_code = RETURN_CODE(packet.data[0]) logging.info("got response packet: {}".format( response_code.name)) else: logging.info("got non-RF packet: {}".format(packet)) continue except queue.Empty: continue
class Communicator: mqtt = None enocean = None CONNECTION_RETURN_CODE = [ "connection successful", "incorrect protocol version", "invalid client identifier", "server unavailable", "bad username or password", "not authorised", ] def __init__(self, config, sensors): self.conf = config self.sensors = sensors # setup mqtt connection self.mqtt = mqtt.Client() self.mqtt.on_connect = self._on_connect self.mqtt.on_disconnect = self._on_disconnect self.mqtt.on_message = self._on_mqtt_message self.mqtt.on_publish = self._on_mqtt_publish if 'mqtt_user' in self.conf: logging.info("Authenticating: " + self.conf['mqtt_user']) self.mqtt.username_pw_set(self.conf['mqtt_user'], self.conf['mqtt_pwd']) self.mqtt.connect(self.conf['mqtt_host'], int(self.conf['mqtt_port'],0)) self.mqtt.loop_start() # setup enocean communication self.enocean = SerialCommunicator(self.conf['enocean_port']) self.enocean.start() # sender will be automatically determined self.enocean_sender = None def __del__(self): if self.enocean is not None and self.enocean.is_alive(): self.enocean.stop() def _on_connect(self, mqtt_client, userdata, flags, rc): '''callback for when the client receives a CONNACK response from the MQTT server.''' if rc == 0: logging.info("Succesfully connected to MQTT broker.") # listen to enocean send requests for cur_sensor in self.sensors: mqtt_client.subscribe(cur_sensor['name']+'/req/#') else: logging.error("Error connecting to MQTT broker: %s", self.CONNECTION_RETURN_CODE[rc]) def _on_disconnect(self, mqtt_client, userdata, rc): '''callback for when the client disconnects from the MQTT server.''' if rc == 0: logging.warning("Successfully disconnected from MQTT broker") else: logging.warning("Unexpectedly disconnected from MQTT broker: "+str(rc)) def _on_mqtt_message(self, mqtt_client, userdata, msg): '''the callback for when a PUBLISH message is received from the MQTT server.''' # search for sensor for cur_sensor in self.sensors: if cur_sensor['name'] in msg.topic: # store data for this sensor if 'data' not in cur_sensor: cur_sensor['data'] = {} prop = msg.topic[len(cur_sensor['name']+"/req/"):] try: cur_sensor['data'][prop] = int(msg.payload) except ValueError: logging.warning("Cannot parse int value for %s: %s", msg.topic, msg.payload) def _on_mqtt_publish(self, mqtt_client, userdata, mid): '''the callback for when a PUBLISH message is successfully sent to the MQTT server.''' #logging.debug("Published MQTT message "+str(mid)) pass def _read_packet(self, packet): '''interpret packet, read properties and publish to MQTT''' # loop through all configured devices for cur_sensor in self.sensors: # does this sensor match? if enocean.utils.combine_hex(packet.sender) == cur_sensor['address']: # found sensor configured in config file if 'publish_rssi' in cur_sensor and cur_sensor['publish_rssi']: self.mqtt.publish(cur_sensor['name']+"/RSSI", packet.dBm) if not packet.learn or ('log_learn' in cur_sensor and cur_sensor['log_learn']): # data packet received found_property = False if packet.packet_type == PACKET.RADIO and packet.rorg == cur_sensor['rorg']: # radio packet of proper rorg type received; parse EEP direction = cur_sensor['direction'] if 'direction' in cur_sensor else None properties = packet.parse_eep(cur_sensor['func'], cur_sensor['type'], direction) # loop through all EEP properties for prop_name in properties: found_property = True cur_prop = packet.parsed[prop_name] # we only extract numeric values, either the scaled ones or the raw values for enums if isinstance(cur_prop['value'], numbers.Number): value = cur_prop['value'] else: value = cur_prop['raw_value'] # publish extracted information logging.debug("{}: {} ({})={} {}".format(cur_sensor['name'], prop_name, cur_prop['description'], cur_prop['value'], cur_prop['unit'])) retain = 'persistent' in cur_sensor and cur_sensor['persistent'] self.mqtt.publish(cur_sensor['name']+"/"+prop_name, value, retain=retain) break if not found_property: logging.warn('message not interpretable: {}'.format(found_sensor['name'])) else: # learn request received logging.info("learn request not emitted to mqtt") def _reply_packet(self, in_packet, sensor): '''send enocean message as a reply to an incoming message''' # prepare addresses destination = in_packet.sender # prepare packet if 'direction' in sensor: # we invert the direction in this reply direction = 1 if sensor['direction'] == 2 else 2 else: direction = None packet = RadioPacket.create(RORG.BS4, sensor['func'], sensor['type'], direction=direction, sender=self.enocean_sender, destination=destination, learn=in_packet.learn) # assemble data based on packet type (learn / data) if not in_packet.learn: # data packet received # start with default data packet.data[1:5] = [ (sensor['default_data'] >> i & 0xff) for i in (24,16,8,0) ] # do we have specific data to send? if 'data' in sensor: # override with specific data settings packet.set_eep(sensor['data']) else: # what to do if we have no data to send yet? logging.warn('sending default data as answer to %s', sensor['name']) else: # learn request received # copy EEP and manufacturer ID packet.data[1:5] = in_packet.data[1:5] # update flags to acknowledge learn request packet.data[4] = 0xf0 # send it logging.info('sending: {}'.format(packet)) self.enocean.send(packet) def _process_radio_packet(self, packet): # first, look whether we have this sensor configured found_sensor = False for cur_sensor in self.sensors: if enocean.utils.combine_hex(packet.sender) == cur_sensor['address']: found_sensor = cur_sensor # skip ignored sensors if found_sensor and 'ignore' in found_sensor and found_sensor['ignore']: return # log packet, if not disabled if int(self.conf['log_packets']): logging.info('received: {}'.format(packet)) # abort loop if sensor not found if not found_sensor: logging.info('unknown sensor: {}'.format(enocean.utils.to_hex_string(packet.sender))) return # interpret packet, read properties and publish to MQTT self._read_packet(packet) # check for neccessary reply if 'answer' in found_sensor and found_sensor['answer']: self._reply_packet(packet, found_sensor) def run(self): # start endless loop for listening while self.enocean.is_alive(): # Request transmitter ID, if needed if self.enocean_sender is None: self.enocean_sender = self.enocean.base_id # Loop to empty the queue... try: # get next packet packet = self.enocean.receive.get(block=True, timeout=1) # check packet type if packet.packet_type == PACKET.RADIO: self._process_radio_packet(packet) elif packet.packet_type == PACKET.RESPONSE: response_code = RETURN_CODE(packet.data[0]) logging.info("got response packet: {}".format(response_code.name)) else: logging.info("got non-RF packet: {}".format(packet)) continue except queue.Empty: continue