Exemple #1
0
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
Exemple #2
0
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()
Exemple #3
0
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))
Exemple #4
0
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()
Exemple #5
0
# 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()
Exemple #6
0
# 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()
Exemple #8
0
# 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()
Exemple #9
0
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()
Exemple #10
0
# 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()
Exemple #11
0
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)
Exemple #12
0
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
Exemple #13
0
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