+import time
+import json
+import logging
+import asyncio
+
+from hbmqtt.client import MQTTClient
+from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+
+from .nl_serial import NooliteSerial
+from .utils import Singleton
+
+from uuid import uuid1
+from socket import gethostname
+
+logger = logging.getLogger(__name__)
+
+client_id = gethostname()+'-'+str(uuid1())
+
+INPUT_TOPIC = '%s/send'
+OUTPUT_TOPIC = '%s/receive'
+
+class MqttDriver(metaclass=Singleton):
+ def __init__(self, mtrf_tty_name, loop, mqtt_uri='mqtt://127.0.0.1/', mqtt_topic='noolite', commands_delay=0.1):
+ self.mqtt_client = MQTTClient(client_id=client_id,config={'auto_reconnect': True})
+ self.mqtt_uri = mqtt_uri
+ self.commands_delay = commands_delay
+ self.noolite_serial = NooliteSerial(loop=loop, tty_name=mtrf_tty_name,
+ input_command_callback_method=self.input_serial_data)
+ self.commands_to_send_queue = asyncio.Queue()
+ self.read_topic = INPUT_TOPIC % mqtt_topic
+ self.subscriptions = [
+ ( self.read_topic+'/#', QOS_0),
+ ]
+ self.write_topic = OUTPUT_TOPIC % mqtt_topic
+ loop.create_task(self.send_command_to_noolite())
+
+ async def run(self):
+ await self.mqtt_client.connect(self.mqtt_uri)
+ await self.mqtt_client.subscribe(self.subscriptions)
+
+ while True:
+ logger.info('Waiting messages from mqtt...')
+ message = await self.mqtt_client.deliver_message()
+
+ topic = message.topic
+ payload = message.publish_packet.payload.data
+
+ logger.info('In message: {}\n{}'.format(topic, payload))
+
+ if topic.startswith(self.read_topic):
+ subtopic = topic[len(self.read_topic)+1:]
+ print(subtopic)
+
+ if subtopic == '':
+ try:
+ payload = json.loads(payload.decode())
+ except Exception as e:
+ logger.exception(e)
+ continue
+ await self.commands_to_send_queue.put(payload)
+ else:
+ try:
+ address = subtopic.split('/')
+ if len(address)==2:
+ channel = int(address[0])
+ command = address[1]
+ id = None
+ elif len(address)==3:
+ channel = int(address[0])
+ command = address[2]
+ id = address[1]
+ elif len(address)==1:
+ command = address[0]
+ channel = None
+ id = None
+ command = command.lower()
+ print("%s: %s (%s)" % (command,channel,id))
+ if command == "on":
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 2 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 2, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 2 }
+ elif command == "off":
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 0 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 0, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 0 }
+ elif command == "brightness":
+ brightness = int(payload)
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 6, "d0": brightness }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 6, "d0": brightness, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 6, "d0": brightness }
+ elif command == "state":
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 128 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 128, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ elif command == "load_preset":
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 7 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 7, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 7 }
+ elif command == "save_preset":
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 8 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 8, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 8 }
+ elif command == "temp_on":
+ delay = (int(payload) + 3)//5
+ d0 = delay % 256
+ d1 = delay // 256
+ if id == '.':
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
+ elif id:
+ mtrf_command = { "mode": 2, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
+ else:
+ mtrf_command = { "mode": 0, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
+ except Exception as e:
+ logger.exception(e)
+ continue
+ await self.commands_to_send_queue.put(mtrf_command)
+
+
+ async def send_command_to_noolite(self):
+ last_command_send_time = 0
+ while True:
+ logger.info('Waiting commands to send...')
+ payload = await self.commands_to_send_queue.get()
+ logger.info('Get command from queue: {}'.format(payload))
+
+ # Формируем и отправляем команду к noolite
+ noolite_cmd = self.payload_to_noolite_command(payload)
+
+ if time.time() - last_command_send_time < self.commands_delay:
+ logger.info('Wait before send next command: {}'.format(
+ self.commands_delay - (time.time() - last_command_send_time)))
+ await asyncio.sleep(self.commands_delay - (time.time() - last_command_send_time))
+
+ try:
+ await self.noolite_serial.send_command(**noolite_cmd)
+ except TypeError as e:
+ logger.exception(str(e))
+ last_command_send_time = time.time()
+
+ async def input_serial_data(self, command):
+ logger.info('Pub command: {}'.format(command))
+ command = self.noolite_response_to_payload(command.to_list())
+ try:
+ topic = "%s/%s/%s" % (self.write_topic, command['ch'], command['id'])
+ except:
+ topic = "%s/%s" % (self.write_topic, command['ch'])
+ await self.mqtt_client.publish(topic=topic, message=json.dumps(command).encode())
+
+ @staticmethod
+ def payload_to_noolite_command(payload):
+ return payload
+
+ @staticmethod
+ def noolite_response_to_payload(payload):
+
+ message = {}
+
+ try:
+ mode = [ 'TX', 'RX', 'TX-F', 'RX-F', 'SERVICE', 'FIRMWARE' ] [payload[1]]
+ message['mode'] = mode
+ finally:
+ None
+
+ try:
+ message['ctr'] = [ 'OK', 'NORESP', 'ERROR', 'BOUND' ] [payload[2]]
+ finally:
+ None
+
+ ch = payload[4]
+ message['ch'] = ch
+
+ cmd = payload[5]
+ message['cmd'] = cmd
+
+ fmt = payload[6]
+ message['fmt'] = fmt
+
+ data = payload[7:11]
+ message['data'] = data
+
+ if payload[1] >= 2:
+ message['id'] = '%0.2X%0.2X%0.2X%0.2X' % (payload[11], payload[12], payload[13], payload[14])
+
+ if cmd == 0:
+ message['command'] = 'OFF'
+ elif cmd == 1:
+ message['command'] = 'BRIGHT_DOWN'
+ elif cmd == 2:
+ message['command'] = 'ON'
+ elif cmd == 3:
+ message['command'] = 'BRIGHT_UP'
+ elif cmd == 4:
+ message['command'] = 'SWITCH'
+ elif cmd == 5:
+ message['command'] = 'SWITCH'
+ elif cmd == 5:
+ message['command'] = 'BRIGHT_BACK'
+ elif cmd == 5:
+ message['command'] = 'BRIGHT_BACK'
+ elif cmd == 6:
+ message['command'] = 'SET_BRIGHTNESS'
+ elif cmd == 7:
+ message['command'] = 'LOAD_PRESET'
+ elif cmd == 8:
+ message['command'] = 'SAVE_PRESET'
+ elif cmd == 9:
+ message['command'] = 'UNBIND'
+ elif cmd == 10:
+ message['command'] = 'STOP_REG'
+# elif cmd == 11:
+# message['command'] = 'BRIGHTNESS_STEP_DOWN'
+# elif cmd == 12:
+# message['command'] = 'BRIGHTNESS_STEP_UP'
+# elif cmd == 13:
+# message['command'] = 'BRIGHT_REG'
+ elif cmd == 15:
+ message['command'] = 'BIND'
+ elif cmd == 16:
+ message['command'] = 'ROLL_COLOUR'
+ elif cmd == 17:
+ message['command'] = 'SWITCH_COLOUR'
+ elif cmd == 18:
+ message['command'] = 'SWITCH_MODE'
+ elif cmd == 19:
+ message['command'] = 'SPEED_MODE_BACK'
+ elif cmd == 20:
+ message['command'] = 'BATTERY_LOW'
+ elif cmd == 21:
+ message['command'] = 'SENS_TEMP_HUMI'
+ t = data[0] + 256*(data[1] % 16)
+ if (data[1] % 16) // 8:
+ t = -(4096 - t )
+ t = t / 10
+ message['t'] = t
+ dev_type = (data[1] // 16) % 8
+ try:
+ message['dev_type'] = [ 'RESERVED', 'PT112', 'PT111' ][dev_type]
+ finally:
+ None
+ message['dev_battery_low'] = (data[1] // 128)
+ if dev_type == 2:
+ h = data[2]
+ message['h'] = h
+ message['aux'] = data[3]
+ elif cmd == 25:
+ message['command'] = 'TEMPORARY_ON'
+ if fmt == 5:
+ message['delay'] = data[0] * 5
+ elif fmt == 6:
+ message['delay'] = data[0] * 5 + data[1]*5*256
+ elif cmd == 26:
+ message['command'] = 'MODES'
+ elif cmd == 128:
+ message['command'] = 'READ_STATE'
+ elif cmd == 129:
+ message['command'] = 'WRITE_STATE'
+ elif cmd == 130:
+ message['command'] = 'SEND_STATE'
+ dev_type = data[0]
+ if dev_type == 5:
+ message['dev_type'] = 'SLU-1-300'
+ message['dev_firmware'] = data[1]
+ if fmt == 0:
+ dev_state = data[2] % 16
+ try:
+ message['dev_state'] = [ 'OFF', 'ON', 'TEMPORARY_ON' ][dev_state]
+ finally:
+ None
+ dev_mode = data[2] // 128
+ if dev_mode:
+ message['dev_binding'] = 'ON'
+ message['brightness'] = data[3]
+ elif fmt == 1:
+ message['dev_aux'] = data[2]
+ message['dev_legacy'] = data[3]
+ elif fmt == 2:
+ message['dev_free'] = data[3]
+ message['dev_free_legacy'] = data[2]
+ elif cmd == 131:
+ message['command'] = 'SERVICE'
+ elif cmd == 132:
+ message['command'] = 'CLEAR_MEMORY'
+
+ return message
+