diff --git a/enocean/communicators/ESP2communicator.py b/enocean/communicators/ESP2communicator.py new file mode 100644 index 0000000..67726b4 --- /dev/null +++ b/enocean/communicators/ESP2communicator.py @@ -0,0 +1,108 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function, unicode_literals, division, absolute_import +import logging + +import threading +try: + import queue +except ImportError: + import Queue as queue +from enocean.protocol.packet import Packet +from enocean.protocol.constants import PACKET, PARSE_RESULT, RETURN_CODE + + +class ESP2Communicator(threading.Thread): + ''' + Communicator base-class for EnOcean. + Not to be used directly, only serves as base class for SerialCommunicator etc. + ''' + logger = logging.getLogger('enocean.communicators.ESP2Communicator') + + def __init__(self, callback=None, teach_in=True): + super(ESP2Communicator, self).__init__() + # Create an event to stop the thread + self._stop_flag = threading.Event() + # Input buffer + self._buffer = [] + # Setup packet queues + self.transmit = queue.Queue() + self.receive = queue.Queue() + # Set the callback method + self.__callback = callback + # Internal variable for the Base ID of the module. + self._base_id = None + # Should new messages be learned automatically? Defaults to True. + # TODO: Not sure if we should use CO_WR_LEARNMODE?? + self.teach_in = teach_in + + def _get_from_send_queue(self): + ''' Get message from send queue, if one exists ''' + try: + packet = self.transmit.get(block=False) + #self.logger.info('Sending packet') + #self.logger.debug(packet) + return packet + except queue.Empty: + pass + return None + + def send(self, packet): + if not isinstance(packet, Packet): + self.logger.error('Object to send must be an instance of Packet') + return False + self.transmit.put(packet) + return True + + def stop(self): + self._stop_flag.set() + + def parse(self): + ''' Parses messages and puts them to receive queue ''' + # Loop while we get new messages + while True: + status, self._buffer, packet = Packet.parse_msg_ESP2(self._buffer, communicator=self) + # If message is incomplete -> break the loop + if status == PARSE_RESULT.INCOMPLETE: + return status + + # If message is OK, add it to receive queue or send to the callback method + if status == PARSE_RESULT.OK and packet: + if self.__callback is None: + self.receive.put(packet) + else: + self.__callback(packet) + #self.logger.debug(packet) + + @property + def base_id(self): + ''' Fetches Base ID from the transmitter, if required. Otherwise returns the currently set Base ID. ''' + # If base id is already set, return it. + if self._base_id is not None: + return self._base_id + + # Send COMMON_COMMAND 0x08, CO_RD_IDBASE request to the module + self.send(Packet(PACKET.COMMON_COMMAND, data=[0x08])) + # Loop over 10 times, to make sure we catch the response. + # Thanks to timeout, shouldn't take more than a second. + # Unfortunately, all other messages received during this time are ignored. + for i in range(0, 10): + try: + packet = self.receive.get(block=True, timeout=0.1) + # We're only interested in responses to the request in question. + if packet.packet_type == PACKET.RESPONSE and packet.response == RETURN_CODE.OK and len(packet.response_data) == 4: + # Base ID is set in the response data. + self._base_id = packet.response_data + # Put packet back to the Queue, so the user can also react to it if required... + self.receive.put(packet) + break + # Put other packets back to the Queue. + self.receive.put(packet) + except queue.Empty: + continue + # Return the current Base ID (might be None). + return self._base_id + + @base_id.setter + def base_id(self, base_id): + ''' Sets the Base ID manually, only for testing purposes. ''' + self._base_id = base_id diff --git a/enocean/communicators/ESP2serialcommunicator.py b/enocean/communicators/ESP2serialcommunicator.py new file mode 100644 index 0000000..9b5e11b --- /dev/null +++ b/enocean/communicators/ESP2serialcommunicator.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function, unicode_literals, division, absolute_import +import logging +import serial +import time + +from enocean.communicators.ESP2communicator import ESP2Communicator + + +class ESP2SerialCommunicator(ESP2Communicator): + ''' Serial port communicator class for EnOcean radio ''' + logger = logging.getLogger('enocean.communicators.ESP2SerialCommunicator') + + def __init__(self, port='/dev/ttyS0', callback=None): + super(ESP2SerialCommunicator, self).__init__(callback) + # Initialize serial port + self.__ser = serial.Serial(port, 57600, timeout=0.1) + + def run(self): + self.logger.info('SerialCommunicator started') + while not self._stop_flag.is_set(): + # If there's messages in transmit queue + # send them + while True: + packet = self._get_from_send_queue() + if not packet: + break + try: + self.__ser.write(bytearray(packet.build_ESP2())) + except serial.SerialException: + self.stop() + + # Read chars from serial port as hex numbers + try: + self._buffer.extend(bytearray(self.__ser.read(16))) + except serial.SerialException: + self.logger.error('Serial port exception! (device disconnected or multiple access on port?)') + self.stop() + self.parse() + time.sleep(0) + + self.__ser.close() + self.logger.info('SerialCommunicator stopped') diff --git a/enocean/manufacturer/eltako.py b/enocean/manufacturer/eltako.py new file mode 100644 index 0000000..cab2bca --- /dev/null +++ b/enocean/manufacturer/eltako.py @@ -0,0 +1,555 @@ +# -*- encoding: utf-8 -*- +from enum import Enum +from enum import IntEnum +from enocean.protocol.packet import RadioPacket +from enocean.protocol.constants import MSGSTATUS, RORG +import datetime + + +class HCState(Enum): + Normal = 2 + Absenkung = 1 + Aus = 0 + Frostschutz = -1 + + +class FSB14State(Enum): + open_ack = 2 + down_ack = -2 + run_up = 1 + run_down = -1 + stop = 0 + + +class FSB14Cmd(IntEnum): + up = 1 + down = -1 + stop = 0 + + +class RockerButton(IntEnum): + RightTop = 0x70 + RightBottom = 0x50 + LeftTop = 0x30 + LeftBottom = 0x10 + Off = 0x00 + + +class RockerEvent(IntEnum): + Press = 1 + Longpress_2s = 2 + Longpress_5s = 5 + + +def checkEqual(lst): + return lst[1:] == lst[:-1] + +class ButtonEvent(): + def __init__(self, addr, time, button): + self.addr = addr + self.time = time + self.button = button + + +class FTS14EM: + def __init__(self): + self.states = [] + + def new_event(self, packet): + timeNow = datetime.datetime.now() + if packet.sender[0:2] == [0x00, 0x00] and packet.sender[2] <= 0x19 and packet.sender[2] >= 0x10: + if packet.rorg == RORG.RPS: + if packet.data[1] != RockerButton.Off: + self.states.insert(0, ButtonEvent(packet.sender_hex, timeNow, RockerButton(packet.data[1]))) + return None + else: + for i, item in enumerate(self.states): + if item.addr == packet.sender_hex: + delta_s = (timeNow - item.time).seconds + delta_us = (timeNow - item.time).microseconds + delta = delta_s + delta_us/1000000.0 + if delta < 2: + ev_type = RockerEvent.Press + elif delta >= 2 and delta <= 5: + ev_type = RockerEvent.Longpress_2s + elif delta > 5: + ev_type = RockerEvent.Longpress_5s + else: + ev_type = RockerEvent.Press + msg_id = packet.sender_hex + self.states.pop(i) + return msg_id, ev_type + else: + #might be the case on start when a button has just been pressed + return None + + +class FTR65HS: + def __init__(self, ID, Name): + self.state = HCState.Normal + self.NRValue = 0 + self.SetPoint = [0.0, 0.0, 0.0, 0.0, 0.0] + self.Temp = [0.0, 0.0, 0.0, 0.0, 0.0] + self.ID = ID + self.Name = Name + + def decode(self, packet): + if packet.rorg == RORG.BS4: + if packet.data[1] == 0x00: + self.NRValue = 0 + self.state = HCState.Normal + elif packet.data[1] == 0x19: + self.NRValue = -4 + self.state = HCState.Absenkung + elif packet.data[1] == 0x0C: + self.NRValue = -2 + self.state = HCState.Absenkung + elif packet.data[1] == 0x06: + self.NRValue = -1 + self.state = HCState.Absenkung + else: + self.NRValue = 0 + + #update data point lists + self.SetPoint.insert(0, packet.data[2]*(40.0 / 255)) + self.Temp.insert(0, (0xFF - packet.data[3])*(40.0 / 255)) + self.SetPoint.pop() + self.Temp.pop() + + # check if is actually turned of... + if self.state is not HCState.Absenkung: + if self.SetPoint[0] != self.Temp[0]: + self.state = HCState.Normal + else: + if (self.SetPoint[1] == self.Temp[1] and self.SetPoint[2] != self.SetPoint[1]) or \ + (self.SetPoint == self.Temp): # and not checkEqual(self.Temp) + # last two messages setpoint and temp were equal and setpoint actually changed + self.state = HCState.Aus + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + #print('%s, Sollwert: %.2f °C' % (self.Name, self.SetPoint[0])) + #print('%s, Istwert: %.2f °C' % (self.Name, self.Temp[0])) + return [self.Temp[0], self.SetPoint[0], self.state.value] + + +class FAE14: + def __init__(self, ID, Name, sID): + self.state = HCState.Normal + self.NRValue = 0 + self.SetPoint = [0.0, 0.0, 0.0, 0.0, 0.0] + self.Temp = [0.0, 0.0, 0.0, 0.0, 0.0] + self.ID = ID + self.Name = Name + self.sID = sID + + def decode(self, packet): + if packet.rorg == RORG.BS4: + if packet.data[1] == 0x00: + self.NRValue = 0 + self.state = HCState.Normal + elif packet.data[1] == 0x19: + self.NRValue = -4 + self.state = HCState.Absenkung + elif packet.data[1] == 0x0C: + self.NRValue = -2 + self.state = HCState.Absenkung + elif packet.data[1] == 0x06: + self.NRValue = -1 + self.state = HCState.Absenkung + else: + self.NRValue = 0 + + #update data point lists + self.SetPoint.insert(0, packet.data[2]*(40.0 / 255)) + self.Temp.insert(0, (0xFF - packet.data[3])*(40.0 / 255)) + self.SetPoint.pop() + self.Temp.pop() + + # check if is actually turned of... + if self.state is not HCState.Absenkung: + if self.SetPoint[0] != self.Temp[0]: + self.state = HCState.Normal + else: + if (self.SetPoint[1] == self.Temp[1] and self.SetPoint[2] != self.SetPoint[1]) or \ + (self.SetPoint == self.Temp): # and not checkEqual(self.Temp) + # last two messages setpoint and temp were equal and setpoint actually changed + self.state = HCState.Aus + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + #print('%s, Sollwert: %.2f °C' % (self.Name, self.SetPoint[0])) + #print('%s, Istwert: %.2f °C' % (self.Name, self.Temp[0])) + return [self.Temp[0], self.SetPoint[0], self.state.value] + + elif packet.rorg == RORG.RPS: + if packet.data[1] == 0x70: + self.NRValue = 0 + if self.state == HCState.Normal: + self.state = HCState.Normal + else: + self.state = HCState.Aus + elif packet.data[1] == 0x50: + self.NRValue = -4 + self.state = HCState.Absenkung + elif packet.data[1] == 0x30: + self.NRValue = -2 + self.state = HCState.Absenkung + elif packet.data[1] == 0x10: + self.NRValue = 0 + self.state = HCState.Frostschutz + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + return [self.state.value] + + def send_SetPoint(self, SetPoint, learn=0, block=0): + if not learn: + stateByte = ((int(block) & 0x01) << 1) | 0x01 << 3 + + spTemp = SetPoint*(255/40.0) + + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[0x00, int(spTemp), 0x00, stateByte], + status=MSGSTATUS.BS4 + ) + + else: + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[0x40, 0x30, 0x0D, 0x85], + status=MSGSTATUS.BS4 + ) + + return [sendMsg] + + def send_Release(self): + stateByte = (0x01 << 1) | 0x01 << 3 + + spTemp = 0x00 + + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[0x00, int(spTemp), 0x00, stateByte], + status=MSGSTATUS.BS4 + ) + + return [sendMsg] + + +class TFUTH: + def __init__(self, ID, Name): + self.state = HCState.Normal + self.NRValue = 0 + self.SetPoint = [0.0, 0.0, 0.0, 0.0, 0.0] + self.Temp = [0.0, 0.0, 0.0, 0.0, 0.0] + self.RHL = 0.0 + self.ID = ID + self.Name = Name + + def decode(self, packet): + if packet.rorg == RORG.BS4: + if packet.data[1] == 0x00: + self.NRValue = 0 + self.state = HCState.Normal + elif packet.data[1] == 0x19: + self.NRValue = -4 + self.state = HCState.Absenkung + elif packet.data[1] == 0x0C: + self.NRValue = -2 + self.state = HCState.Absenkung + elif packet.data[1] == 0x06: + self.NRValue = -1 + self.state = HCState.Absenkung + else: + self.NRValue = 0 + + # update data point lists + self.SetPoint.insert(0, packet.data[2]*(40.0 / 255)) + self.Temp.insert(0, (0xFF - packet.data[3])*(40.0 / 255)) + self.SetPoint.pop() + self.Temp.pop() + + # check if is actually turned of... + if self.state is not HCState.Absenkung: + if self.SetPoint[0] != self.Temp[0]: + self.state = HCState.Normal + else: + if (self.SetPoint[1] == self.Temp[1] and self.SetPoint[2] != self.SetPoint[1]) or \ + (self.SetPoint == self.Temp): # and not checkEqual(self.Temp) + # last two messages setpoint and temp were equal and setpoint actually changed + self.state = HCState.Aus + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + #print('%s, Sollwert: %.2f °C' % (self.Name, self.SetPoint[0])) + #print('%s, Istwert: %.2f °C' % (self.Name, self.Temp[0])) + return [self.Temp[0], self.SetPoint[0], self.state.value] + + def decode_humidity(self, packet): + if packet.rorg == RORG.BS4: + self.RHL = packet.data[2]*(100.0/250) + #print('%s, rel. Feuchte: %.2f%%' % (self.Name, self.RHL)) + return self.RHL + + +class FSB14: + def __init__(self, ID, Name, tRun_s, tFull_s, sID): + self.state = FSB14State.open_ack + self.pos = 0.0 + self.ID = ID + self.Name = Name + self.tRun_s = tRun_s + self.tFull_s = tFull_s + self.sID = sID + self.isMoving = False + + def decode(self, packet): + if packet.rorg == RORG.RPS: + if packet.data[1] == 0x70: + self.pos = 0.0 + self.state = FSB14State.open_ack + self.isMoving = False + if packet.data[1] == 0x50: + self.pos = 100.0 + self.state = FSB14State.down_ack + self.isMoving = False + if packet.data[1] == 0x01: + self.state = FSB14State.run_up + self.isMoving = True + if packet.data[1] == 0x02: + self.state = FSB14State.run_down + self.isMoving = True + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + #print('%s, Position: %.2f%%' % (self.Name, self.pos)) + return [self.pos, self.state.value] + + if packet.rorg == RORG.BS4: + self.state = FSB14State.stop + self.isMoving = False + + drivetime_ms = (packet.data[1] << 8) | packet.data[2] + drivetime_s = drivetime_ms/10.0 + + if packet.data[3] == 0x01: + self.pos -= drivetime_s/self.tRun_s*100.0 + if packet.data[3] == 0x02: + self.pos += drivetime_s/self.tRun_s*100.0 + + #print('%s, Status: %s ' % (self.Name, self.state.value)) + #print('%s, Position: %.2f%%' % (self.Name, self.pos)) + return [self.pos, self.state.value] + + def send_Move(self, newpos=None, newState=None, learn=0, block=0): + if not learn: + if newState is not None: + newState = FSB14Cmd(newState) + + stateByte = ((int(block) & 0x01) << 2) | (0x01 << 1) | 0x01 << 3 + + if newState == FSB14Cmd.stop: + compState = 0x00 + self.isMoving = False + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[0x00, 0x00, compState, stateByte], + status=MSGSTATUS.BS4 + ) + else: + if newpos is None: + if newState == FSB14Cmd.up: + newpos = 0.0 + elif newState == FSB14Cmd.down: + newpos = 100.0 + else: + return [] + + deltapos = abs(newpos - self.pos) + runtime = (self.tRun_s/100.0)*deltapos*10 + + if newpos == 0.0 or newpos == 100.0: + runtime = self.tFull_s * 10 + + if newpos > self.pos: + #down + compState = 0x02 + else: + #up + compState = 0x01 + + #override, not properly initialized... + if self.pos < 0.0 or self.pos > 100.0: + #do an init run...sorry + runtime = self.tFull_s * 10 + compState = 0x01 + + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[((int(runtime) & 0xFF00) >> 8), int(runtime) & 0xFF, compState, stateByte], + status=MSGSTATUS.BS4 + ) + + self.isMoving = True + + else: + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sID, + command=[0xFF, 0xF8, 0x0D, 0x80], + status=MSGSTATUS.BS4 + ) + + return [sendMsg] + + +class FWS61: + def __init__(self, ID, Name): + self.ID = ID + self.Name = Name + + def decode(self, packet): + if packet.data[4] == 0x1A or packet.data[4] == 0x18: + dawn = packet.data[1]*(1000.0/255) + temp = (packet.data[2]*(120.0/255))-40.0 + wind = packet.data[3]*(70.0/255) + if packet.data[4] == 0x1A: + rain = 1 + elif packet.data[4] == 0x18: + rain = 0 + + #print('%s, Dämmerung: %.2f Lux' % (self.Name, dawn)) + #print('%s, Temperatur: %.2f °C' % (self.Name, temp)) + #print('%s, Wind: %.2f m/s' % (self.Name, wind)) + #print('%s, %s' % (self.Name, rain)) + return [0, dawn, temp, wind, rain] + elif packet.data[4] == 0x28: + sun_west = packet.data[1]*(150.0/255) + sun_south = packet.data[2]*(150.0/255) + sun_east = packet.data[3]*(150.0/255) + #print('%s, Sonne West: %.2f kLux' % (self.Name, sun_west)) + #print('%s, Sonne Süd: %.2f kLux' % (self.Name, sun_south)) + #print('%s, Sonne Ost: %.2f kLux' % (self.Name, sun_east)) + return [1, sun_west, sun_south, sun_east] + +class FUD14: + def __init__(self, ID, Name, sIDdim, sIDsleep=[0, 0, 0, 0], sIDwake=[0, 0, 0, 0]): + self.ID = ID + self.Name = Name + self.isOn = 0 + self.dimval = 0.0 + self.sIDdim = sIDdim + self.sIDsleep = sIDsleep + self.sIDwake = sIDwake + + def decode(self, packet): + if packet.rorg == RORG.BS4: + if packet.data[4] == 0x08: + self.isOn = 0 + elif packet.data[4] == 0x09: + self.isOn = 1 + else: + self.isOn = 0 + self.dimval = packet.data[2] + + #print('%s, Dimmwert: %d%% Status: %d' % (self.Name, self.dimval, self.isOn)) + return [self.dimval, self.isOn] + if packet.rorg == RORG.RPS: + if packet.data[1] == 0x50: + self.isOn = 0 + elif packet.data[1] == 0x70: + self.isOn = 1 + #print('%s, Status: %d ' % (self.Name, self.isOn)) + return [self.dimval, self.isOn] + + def send_DimMsg(self, newVal=None, newState=None, dimSpeed=0, learn=0, block=0): + if not learn: + if newVal is None and newState is None: + return [] + + if newVal == 0: + newState = 0 + newVal = self.dimval #safe current value in actuator for manual switch use + elif newVal is None: + if newState == 1: + newVal = 100.0 + else: + newVal = self.dimval + + if newState is None: + if self.isOn == 0 and newVal != 0: + newState = 1 + else: + newState = self.isOn + + stateByte = (int(newState) & 0x01) | ((int(block) & 0x01) << 2) | 0x01 << 3 + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDdim, + command=[0x02, int(newVal) & 0xFF, int(dimSpeed) & 0xFF, stateByte], + status=MSGSTATUS.BS4 + ) + else: + sendMsg = RadioPacket.create_ESP2(rorg=RORG.BS4, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDdim, + command=[0xE0, 0x40, 0x0D, 0x80], + status=MSGSTATUS.BS4 + ) + return [sendMsg] + + def send_SleepDimMsg(self): + onMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDsleep, + command=[0x30, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2NMsg + ) + + offMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDsleep, + command=[0x00, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2UMsg + ) + return [onMsg, offMsg] + + + def send_WakeDimMsg(self): + onMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDwake, + command=[0x30, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2NMsg + ) + + offMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDwake, + command=[0x00, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2UMsg + ) + return [onMsg, offMsg] + + +class FSR_FTN_14: + def __init__(self, ID, Name, sID): + self.ID = ID + self.Name = Name + self.isOn = 0 + self.sIDonoff = sID + + def decode(self, packet): + if packet.rorg == RORG.RPS: + if packet.data[1] == 0x50: + self.isOn = 0 + elif packet.data[1] == 0x70: + self.isOn = 1 + #print('%s, Status: %d ' % (self.Name, self.isOn)) + + return self.isOn + + def send_toggle(self, RockerPos = RockerButton.RightTop): + onMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDonoff, + command=[RockerPos, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2NMsg + ) + + offMsg = RadioPacket.create_ESP2(rorg=RORG.RPS, rorg_func=0x00, rorg_type=0x00, + sender=self.sIDonoff, + command=[RockerButton.Off, 0x00, 0x00, 0x00], + status=MSGSTATUS.T2UMsg + ) + return [onMsg, offMsg] + diff --git a/enocean/protocol/constants.py b/enocean/protocol/constants.py index 1425193..22ad601 100644 --- a/enocean/protocol/constants.py +++ b/enocean/protocol/constants.py @@ -35,6 +35,26 @@ class EVENT_CODE(IntEnum): CO_EVENT_SECUREDEVICES = 0x05 +# ESP2 ORG EnOcean_Equipment_Profiles_2.0.pdf +class ORG(IntEnum): + BS4 = 0x07 + BS1 = 0x06 + RPS = 0x05 + +#ESP2 Message Status Field EnOcean_Equipment_Profiles_2.0.pdf +class MSGSTATUS(IntEnum): + #RPS definitions + T2Msg = 0x20 + NMsg = 0x10 + T2NMsg = 0x30 + T2UMsg = 0x20 + T1NMsg = 0x10 + T1UMsg = 0x00 + #BS4 definitions + BS4 = 0x00 #must be zero in BS4 messages with RP count of 0 + #BS1 definitions + BS1 = 0x00 #must be zero in BS1 too + # EnOcean_Equipment_Profiles_EEP_V2.61_public.pdf / 8 class RORG(IntEnum): UNDEFINED = 0x00 diff --git a/enocean/protocol/crc8.py b/enocean/protocol/crc8.py index 5dd9b9d..b057724 100644 --- a/enocean/protocol/crc8.py +++ b/enocean/protocol/crc8.py @@ -39,3 +39,10 @@ def calc(msg): for byte in msg: checksum = CRC_TABLE[checksum & 0xFF ^ byte & 0xFF] return checksum + +def calc_ESP2(msg): + checksum=0 + for byte in msg: + checksum += byte + checksum &= 0xFF + return checksum \ No newline at end of file diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index 1cba721..7c6ffdf 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -6,7 +6,7 @@ import enocean.utils from enocean.protocol import crc8 from enocean.protocol.eep import EEP -from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 +from enocean.protocol.constants import PACKET as PACKET, RORG, ORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 class Packet(object): @@ -160,6 +160,93 @@ def parse_msg(buf, communicator=None): return PARSE_RESULT.OK, buf, packet + + @staticmethod + def parse_msg_ESP2(buf, communicator=None): + ''' + Parses message from buffer. + returns: + - PARSE_RESULT + - remaining buffer + - Packet -object (if message was valid, else None) + ''' + # If the buffer doesn't contain 0xA5 (start char) + # the message isn't needed -> ignore + # if 0xA5 not in buf: + # return PARSE_RESULT.INCOMPLETE, [], None + # else: + # if buf[list(buf).index(0xA5)+1] != 0x5A: + # return PARSE_RESULT.INCOMPLETE, [], None + + if not buf: + return PARSE_RESULT.INCOMPLETE, [], None + + if 0xA5 not in buf: + return PARSE_RESULT.INCOMPLETE, [], None + + msg_len = 0 + + for index,value in enumerate(buf): + try: + if buf[index] == 0xA5 and buf[index+1] == 0x5A: + data_len = buf[index+2] & 0x1F + HSEQ = buf[index+2] >> 5 + opt_len = 0 + msg_len = data_len + if len(buf[index+2:]) < msg_len: + # If buffer isn't long enough, the message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + crcval = crc8.calc_ESP2(buf[index+2:index+2+data_len]) + if buf[index+2+data_len] == crcval: + buf = buf[index+2:] + break + else: + Packet.logger.error('Data CRC error!') + return PARSE_RESULT.CRC_MISMATCH, buf, None + except IndexError: + # If the fields don't exist, message is incomplete + return PARSE_RESULT.INCOMPLETE, buf, None + + if msg_len == 0: + Packet.logger.error('Data Length is Zero!') + return PARSE_RESULT.INCOMPLETE, [], None + + msg = buf[0:msg_len] + buf = buf[msg_len+1:] + + packet_type = HSEQ + data = msg[1:data_len] + opt_data = [] + + #Adopt ORG to RORG for ESP2 + if data[0] == ORG.BS1: + data[0] = RORG.BS1 + if data[0] == ORG.BS4: + data[0] = RORG.BS4 + if data[0] == ORG.RPS: + data[0] = RORG.RPS + + # If we got this far, everything went ok (?) + if packet_type == PACKET.RADIO: + # Need to handle UTE Teach-in here, as it's a separate packet type... + if data[0] == RORG.UTE: + packet = UTETeachIn(packet_type, data, opt_data, communicator=communicator) + # Send a response automatically, works only if + # - communicator is set + # - communicator.teach_in == True + packet.send_response() + else: + packet = RadioPacket(packet_type, data, opt_data) + elif packet_type == PACKET.RESPONSE: + packet = ResponsePacket(packet_type, data, opt_data) + elif packet_type == PACKET.EVENT: + #packet = EventPacket(packet_type, data, opt_data) + packet = RadioPacket(packet_type, data, opt_data) + else: + packet = Packet(packet_type, data, opt_data) + + return PARSE_RESULT.OK, buf, packet + @staticmethod def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, destination=None, @@ -242,6 +329,94 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None packet.parse_eep(rorg_func, rorg_type, direction, command) return packet + @staticmethod + def create_ESP2(packet_type, rorg, direction=None, command=None, + destination=None, + sender=None, + learn=False, + status=0x00, **kwargs): + ''' + Creates an packet ready for sending. + Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. + Additional arguments (**kwargs) are used for setting the values. + + Currently only supports: + - PACKET.RADIO + - RORGs RPS, BS1, BS4, VLD. + + TODO: + - Require sender to be set? Would force the "correct" sender to be set. + - Do we need to set telegram control bits? + Might be useful for acting as a repeater? + ''' + + if packet_type != PACKET.RADIO: + # At least for now, only support PACKET.RADIO. + raise ValueError('Packet type not supported by this function.') + + if rorg not in [RORG.RPS, RORG.BS1, RORG.BS4]: + # At least for now, only support these RORGS. + raise ValueError('RORG not supported by this function.') + + if destination is None: + destination = [0xFF, 0xFF, 0xFF, 0xFF] + + # TODO: Should use the correct Base ID as default. + # Might want to change the sender to be an offset from the actual address? + if sender is None: + Packet.logger.warning('Replacing sender with default address.') + sender = [0xDE, 0xAD, 0xBE, 0xEF] + + if not isinstance(destination, list) or len(destination) != 4: + raise ValueError('Destination must a list containing 4 (numeric) values.') + + if not isinstance(sender, list) or len(sender) != 4: + raise ValueError('Sender must a list containing 4 (numeric) values.') + + packet = Packet(packet_type, data=[], optional=[]) + packet.rorg = rorg + + if packet.rorg == RORG.BS1: + packet.rorg = ORG.BS1 + if packet.rorg == RORG.BS4: + packet.rorg = ORG.BS4 + if packet.rorg == RORG.RPS: + packet.rorg = ORG.RPS + + packet.data = [packet.rorg] + # Select EEP at this point, so we know how many bits we're dealing with (for VLD). + #packet.select_eep(rorg_func, rorg_type, direction, command) + + # Initialize data depending on the profile. + # if rorg in [RORG.RPS, RORG.BS1]: + # packet.data.extend([0]) + #elif rorg == RORG.BS4: + packet.data.extend(command) + #else: + # packet.data.extend([0] * int(packet._profile.get('bits', '1'))) + packet.data.extend(sender) + packet.data.extend([0]) + # Always use sub-telegram 3, maximum dbm (as per spec, when sending), + # and no security (security not supported as per EnOcean Serial Protocol). + #packet.optional = [3] + destination + [0xFF] + [0] + + #if command: + # Set CMD to command, if applicable.. Helps with VLD. + #kwargs['CMD'] = command + + #packet.set_eep(kwargs) + if rorg in [RORG.BS1, RORG.BS4] and not learn: + if rorg == RORG.BS1: + packet.data[1] |= (1 << 3) + if rorg == RORG.BS4: + packet.data[4] |= (1 << 3) + packet.data[-1] = status + + # Parse the built packet, so it corresponds to the received packages + # For example, stuff like RadioPacket.learn should be set. + packet = Packet.parse_msg_ESP2(packet.build_ESP2())[2] + return packet + def parse(self): ''' Parse data from Packet ''' # Parse status from messages @@ -287,6 +462,23 @@ def build(self): ords.append(crc8.calc(ords[6:])) return ords + def build_ESP2(self): + ''' Build Packet for sending to EnOcean controller ''' + data_length = len(self.data)+1 + ords = [0xA5, 0x5A, (data_length & 0x1F | ((int(self.packet_type)&0x07)<<5))] + + if self.data[0] in [RORG.RPS, RORG.BS4, RORG.BS1]: + if self.data[0] == RORG.RPS: + self.data[0] = ORG.RPS + if self.data[0] == RORG.BS1: + self.data[0] = ORG.BS1 + if self.data[0] == RORG.BS4: + self.data[0] = ORG.BS4 + + ords.extend(self.data) + ords.append(crc8.calc_ESP2(ords[2:])) + return ords + class RadioPacket(Packet): destination = [0xFF, 0xFF, 0xFF, 0xFF] @@ -304,6 +496,11 @@ def create(rorg, rorg_func, rorg_type, direction=None, command=None, destination=None, sender=None, learn=False, **kwargs): return Packet.create(PACKET.RADIO, rorg, rorg_func, rorg_type, direction, command, destination, sender, learn, **kwargs) + @staticmethod + def create_ESP2(rorg, rorg_func, rorg_type, direction=None, command=None, + destination=None, sender=None, learn=False, **kwargs): + return Packet.create_ESP2(PACKET.RADIO, rorg, direction, command, destination, sender, learn, **kwargs) + @property def sender_int(self): return enocean.utils.combine_hex(self.sender) @@ -321,8 +518,10 @@ def destination_hex(self): return enocean.utils.to_hex_string(self.destination) def parse(self): - self.destination = self.optional[1:5] - self.dBm = -self.optional[5] + if len(self.optional): + self.destination = self.optional[1:5] + self.dBm = -self.optional[5] + self.sender = self.data[-5:-1] # Default to learn == True, as some devices don't have a learn button self.learn = True diff --git a/examples/stoney/devicelist_example.py b/examples/stoney/devicelist_example.py new file mode 100644 index 0000000..bb69630 --- /dev/null +++ b/examples/stoney/devicelist_example.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- +from enocean.manufacturer.eltako import * + +roomsensors = [] +roomsensors.append(FTR65HS(u'EL:TA:KO:ID', 'my/mqtt/topic')) +roomsensors.append(FTR65HS(u'EL:TA:KO:ID', 'my/mqtt/topic')) +roomsensors.append(FTR65HS(u'EL:TA:KO:ID', 'my/mqtt/topic')) + +shutters = [] +shutters.append(FSB14(u'EL:TA:KO:ID', 'my/mqtt/topic', 25.0, 30.0, [0x00, 0x01, 0x02, 0x03])) +shutters.append(FSB14(u'EL:TA:KO:ID', 'my/mqtt/topic', 25.0, 30.0, [0x00, 0x01, 0x02, 0x03])) +shutters.append(FSB14(u'EL:TA:KO:ID', 'my/mqtt/topic', 25.0, 30.0, [0x00, 0x01, 0x02, 0x03])) + +dimmer = [] +dim1 = FUD14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03]) +dimmer.append(dim1) +dim2 = FUD14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03], [0x00, 0x01, 0x02, 0x03]) +dimmer.append(dim2) +dim3 = FUD14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03], [0x00, 0x01, 0x02, 0x03], [0x00, 0x01, 0x02, 0x03]) +dimmer.append(dim3) + + +hygrometer = [] +hygrometer.append(TFUTH(u'EL:TA:KO:ID', 'my/mqtt/topic')) +hygrometer.append(TFUTH(u'EL:TA:KO:ID', 'my/mqtt/topic')) + +thermostate = [] +thermostate.append(FAE14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03])) +thermostate.append(FAE14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03])) + +weatherstation = FWS61(u'EL:TA:KO:ID', 'my/mqtt/topic') +rockers = FTS14EM() + +switches = [] +sw1 = FSR_FTN_14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03]) +switches.append(sw1) +sw2 = FSR_FTN_14(u'EL:TA:KO:ID', 'my/mqtt/topic', [0x00, 0x01, 0x02, 0x03]) +switches.append(sw2) \ No newline at end of file diff --git a/examples/stoney/enocean_backend.py b/examples/stoney/enocean_backend.py new file mode 100644 index 0000000..028cf36 --- /dev/null +++ b/examples/stoney/enocean_backend.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +from enocean.consolelogger import init_logging +import enocean.utils +from enocean.communicators.ESP2serialcommunicator import ESP2SerialCommunicator +from enocean.protocol.constants import PACKET, RORG +from enocean.manufacturer.eltako import * +import sys +import traceback +from time import sleep +import paho.mqtt.client as paho +from enocean.example.devicelist_example import * +import threading, logging + +try: + import queue +except ImportError: + import Queue as queue + +class BreakIt(Exception): pass + +def eno_worker(wstop): + # endless loop receiving radio packets + mainlogger.info("Worker started...") + while 1: + try: + if wstop.isSet(): + break + + # Loop to empty the queue... + packet = esp2com.receive.get(block=True, timeout=1) + + if (packet.packet_type in [PACKET.RADIO, PACKET.EVENT]) and (packet.rorg in [RORG.RPS, RORG.BS4, RORG.BS1]): + + button = rockers.new_event(packet) + if button is not None: + if button[1] is RockerEvent.Press: + cmqtt.publish("my/home/automation/topic/button/press", payload=button[0], qos=2, retain=False) + if button[1] is RockerEvent.Longpress_2s: + cmqtt.publish("my/home/automation/topic/button/longpress2", payload=button[0], qos=2, retain=False) + if button[1] is RockerEvent.Longpress_5s: + cmqtt.publish("my/home/automation/topic/button/longpress5", payload=button[0], qos=2, retain=False) + + for item in hygrometer: + if packet.sender_hex == item.ID: + EnoValList = item.decode_humidity(packet) + cmqtt.publish(item.Name, payload=EnoValList, qos=2, retain=True) + raise BreakIt + + for item in roomsensors: + if packet.sender_hex == item.ID: + EnoValList = item.decode(packet) + cmqtt.publish(item.Name + "/temperature", payload=EnoValList[0], qos=2, retain=True) + cmqtt.publish(item.Name + "/setpoint", payload=EnoValList[1], qos=2, retain=True) + cmqtt.publish(item.Name + "/state", payload=EnoValList[2], qos=2, retain=True) + raise BreakIt + + if packet.sender_hex == weatherstation.ID: + item = weatherstation + EnoValList = item.decode(packet) + if EnoValList[0] == 0: + cmqtt.publish(item.Name + "/lightsensor", payload=EnoValList[1], qos=2, retain=True) + cmqtt.publish(item.Name + "/temperature", payload=EnoValList[2], qos=2, retain=True) + cmqtt.publish(item.Name + "/windspeed", payload=EnoValList[3], qos=2, retain=True) + cmqtt.publish(item.Name + "/rain", payload=EnoValList[4], qos=2, retain=True) + else: + cmqtt.publish(item.Name + "/sun/west", payload=EnoValList[1], qos=2, retain=True) + cmqtt.publish(item.Name + "/sun/south", payload=EnoValList[2], qos=2, retain=True) + cmqtt.publish(item.Name + "/sun/east", payload=EnoValList[3], qos=2, retain=True) + + raise BreakIt + + for item in thermostate: + if packet.sender_hex == item.ID: + EnoValList = item.decode(packet) + if len(EnoValList) == 3: + cmqtt.publish(item.Name + "/temperature", payload=EnoValList[0], qos=2, retain=True) + cmqtt.publish(item.Name + "/setpoint", payload=EnoValList[1], qos=2, retain=True) + cmqtt.publish(item.Name + "/state", payload=EnoValList[2], qos=2, retain=True) + else: + cmqtt.publish(item.Name + "/state", payload=EnoValList[0], qos=2, retain=True) + + raise BreakIt + + for item in shutters: + if packet.sender_hex == item.ID: + EnoValList = item.decode(packet) + cmqtt.publish(item.Name + "/position", payload=EnoValList[0], qos=2, retain=True) + cmqtt.publish(item.Name + "/state", payload=EnoValList[1], qos=2, retain=True) + raise BreakIt + + for item in dimmer: + if packet.sender_hex == item.ID: + EnoValList = item.decode(packet) + cmqtt.publish(item.Name + "/dimmvalue", payload=EnoValList[0], qos=2, retain=True) + cmqtt.publish(item.Name + "/state", payload=EnoValList[1], qos=2, retain=True) + raise BreakIt + + for item in switches: + if packet.sender_hex == item.ID: + EnoValList = item.decode(packet) + cmqtt.publish(item.Name + "/state", payload=EnoValList, qos=2, retain=True) + raise BreakIt + + except queue.Empty: + continue + except BreakIt: + continue + except KeyboardInterrupt: + break + except Exception: + traceback.print_exc(file=sys.stdout) + break + + +def SendSwToggle(communicator, messages): + communicator.send(messages[0]) + sleep(0.1) + communicator.send(messages[1]) + + +def on_message_set_dimmerstate(client, userdata, message): + for item in dimmer: + if item.Name in message.topic and message.payload != None: + try: + newvalue = int(message.payload) + except ValueError: + mainlogger.info("Value Error happened with " + str(message.topic)) + return + + esp2com.send(item.send_DimMsg(newState=newvalue)[0]) + + +def on_message_light_toggle(client, userdata, message): + for item in schwitches: + if item.Name in message.topic: + eno_msg = item.send_toggle() + SendSwToggle(esp2com, eno_msg) + + +def on_message_set_brightness(client, userdata, message): + for item in dimmer: + if item.Name in message.topic and message.payload != None: + try: + newvalue = float(message.payload) + except ValueError: + mainlogger.info("Value Error happened with " + str(message.topic)) + return + + esp2com.send(item.send_DimMsg(newVal=newvalue)[0]) + + +def on_message_shutter_set_state(client, userdata, message): + for item in shutters: + if item.Name in message.topic and message.payload != None: + try: + newvalue = int(message.payload) + except ValueError: + mainlogger.info("Value Error happened with " + str(message.topic)) + return + + esp2com.send(item.send_Move(newState=newvalue)[0]) + + +def on_message_shutter_set_pos(client, userdata, message): + for item in schutters: + if item.Name in message.topic and message.payload != None: + if item.isMoving: + #if shutter is moving, stop it first and wait for a position update... + #tested ok, works perfectly + runpos = item.pos + esp2com.send(item.send_Move(newState="Stop")[0]) + timesslept = 0 + while runpos == item.pos: + sleep(0.05) + timesslept+=1 + if timesslept*0.05 >= item.tFull_s: + #seems like we already were in an endstop, eltako behaviour... + break + #print("Sleep loop delay " + str(timesslept*50.0) + "ms, ran " + str(timesslept) + " times...") + try: + newvalue = float(message.payload) + except ValueError: + mainlogger.info("Value Error happened with " + str(message.topic)) + return + + esp2com.send(item.send_Move(newpos=float(message.payload))[0]) + + +def on_message_thermo_set_temp(client, userdata, message): + for item in thermostate: + if item.Name in message.topic and message.payload != None: + try: + newvalue = float(message.payload) + except ValueError: + mainlogger.info("Value Error happened with " + str(message.topic)) + return + + esp2com.send(item.send_SetPoint(SetPoint=newvalue, block=1)[0]) + + +def on_message_thermo_set_release(client, userdata, message): + for item in thermostate: + if item.Name in message.topic and message.payload != None: + esp2com.send(item.send_Release()[0]) + + +def on_connect(client, userdata, flags, rc): + cmqtt.subscribe("my/home/automation/topic/#", qos=2) + mainlogger.info("Connected to MQTT-Broker with result code "+str(rc)) + + +def on_disconnect(cmqtt, obj, rc): + mainlogger.info("Disconnected from MQTT-Broker with result code "+str(rc)) + + +def on_message(mosq, obj, msg): + mainlogger.info("From mqtt-broker: " + msg.topic + " " + str(msg.qos) + " " + str(msg.payload)) + + +init_logging() + +mainlogger = logging.getLogger('eno_backend') +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +mainlogger.setLevel(logging.DEBUG) +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.DEBUG) +stream_handler.setFormatter(formatter) +mainlogger.addHandler(stream_handler) + +esp2com = ESP2SerialCommunicator() +cmqtt = paho.Client(client_id="", clean_session=True) +cmqtt.username_pw_set(username="", password="") +cmqtt.message_callback_add("my/home/automation/topic", on_message_set_brightness) +cmqtt.message_callback_add("my/home/automation/topic", on_message_set_dimmerstate) +cmqtt.message_callback_add("my/home/automation/topic", on_message_light_toggle) +cmqtt.message_callback_add("my/home/automation/topic", on_message_shutter_set_state) +cmqtt.message_callback_add("my/home/automation/topic", on_message_shutter_set_pos) +cmqtt.message_callback_add("my/home/automation/topic", on_message_thermo_set_temp) +cmqtt.message_callback_add("my/home/automation/topic", on_message_thermo_set_release) +cmqtt.on_message = on_message +cmqtt.on_connect = on_connect +cmqtt.on_disconnect = on_disconnect + +cmqtt.connect("", port=0000) +esp2com.start() + +wstop = threading.Event() +t = threading.Thread(name='eno-worker', target=eno_worker, args=(wstop,)) +t.start() + +mainlogger.info("Starting MQTT...") + +cmqtt.loop_forever() + +mainlogger.info('MQTT Exited, stopping other workers...') + +if t.is_alive(): + wstop.set() + t.join(timeout=10) + +if esp2com.is_alive(): + esp2com.stop() diff --git a/examples/stoney/enocean_mqtt_influx.py b/examples/stoney/enocean_mqtt_influx.py new file mode 100644 index 0000000..5571cd8 --- /dev/null +++ b/examples/stoney/enocean_mqtt_influx.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import paho.mqtt.client as mqtt +import datetime +import time +from influxdb import InfluxDBClient +import logging + +def on_connect(client, userdata, flags, rc): + print("Connected with result code "+str(rc)) + client.subscribe("my/home/automation/topic/#", qos=2) + client.subscribe("my/home/automation/topic/#", qos=2) + client.subscribe("my/home/automation/topic/#", qos=2) + client.subscribe("my/home/automation/topic/hygro", qos=2) + client.subscribe("my/home/automation/topic/#", qos=2) + +def on_message(client, userdata, msg): + # Use utc as timestamp + receiveTime=datetime.datetime.utcnow() + + isfloatValue=False + try: + # Convert the string to a float so that it is stored as a number and not a string in the database + message=msg.payload.decode("utf-8") + val = float(message) + isfloatValue=True + except ValueError: + isfloatValue=False + mainlogger.info("got a non-float from " + str(msg.topic)) + + if isfloatValue: + mainlogger.info(str(msg.topic) + ": " + str(msg.payload)) + + json_body = [ + { + "measurement": msg.topic, + "time": receiveTime, + "fields": { + "value": val + } + } + ] + + try: + dbclient.write_points(json_body) + except: + mainlogger.info("ERROR WHILE SENDING TO INFLUX!!") + + # else: + # print(str(receiveTime) + ": " + msg.topic + " " + message) + # + # json_body = [ + # { + # "measurement": msg.topic, + # "time": receiveTime, + # "fields": { + # "value": message + # } + # } + # ] + # + # dbclient.write_points(json_body) + +# Set up a client for InfluxDB + + +mainlogger = logging.getLogger('eno_influx_logger') +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +mainlogger.setLevel(logging.DEBUG) +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.DEBUG) +stream_handler.setFormatter(formatter) +mainlogger.addHandler(stream_handler) + +mainlogger.info("Starting...") +dbclient = InfluxDBClient(host='', port=0000, database='') + +mainlogger.info("InfluxConn OK...") + +# Initialize the MQTT client that should connect to the Mosquitto broker +client = mqtt.Client(client_id="") +client.username_pw_set(username="", password="") +client.on_connect = on_connect +client.on_message = on_message +connOK=False +while(connOK == False): + try: + client.connect("", port=0000, keepalive=60, ) + connOK = True + mainlogger.info("MQTTConn OK...") + except: + connOK = False + time.sleep(2) + +# Blocking loop to the Mosquitto broker +client.loop_forever() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fb7ca9a..4a63d02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ enum-compat>=0.0.2 pyserial>=3.0 beautifulsoup4>=4.3.2 +paho-mqtt +influxdb