import asyncio import serial import aioserial import aiomqtt from tse_serial_interpreter import * from dataclasses import dataclass room = 'p1' #TODO make be do get fronm file of configuration aser: aioserial.AioSerial = aioserial.AioSerial( port='/dev/cu.usbserial-14240', #TODO not hardcode it baudrate=1200, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE ) # TODO adjust serial on actual TSE interface #TODO get this from file da ni hardcoded mapping_toggles = { "master": 1, "audio": 2, "projectors": 3, } platno_mapping = { "glavni": { "dol": 5, "gor": 6 }, "stranski": { "dol": 7, "gor": 8 } } shades_mapping = { "dol": 9, "gor": 10 } #reverse_lookup = {v: k for k, v in mapping.items()} #fujto # TODO simple reverse lookup za ko kripa pove kaj def parse_topic_from_mqtt(topic: str): topicArr = topic.split() async def task_status2mqtt(statusClient: aiomqtt.Client): while True: data = await aser.read_until_async() data = data.decode(errors='ignore').strip() print("TSE box sent: " + data) #relState = resp_to_relay_state(data) #command = reverse_lookup[relState.relay_id] #action = relState.state #TODO havent figured out a clean way to # get out the action from topic yet as they are # not always on the same level # REWORK # probably just do it the most straight forward way # with some more code #publishTopic = f"{room}/" #publishPayload = "ON" if relState.state else "OFF" #print("Publishing [" + publishPayload + "] to topic [" + publishTopic + "]") #await statusClient.publish(publishTopic, payload=publishPayload) await asyncio.sleep(0.01) async def executeAndPublish(topicVal, mqttClient, pubTopic, pubPayload, relStat): setRelayCmd = relay_state_to_cmd(relStat) print("Received: [" + topicVal + "] payload: [" + pubPayload + "]") print("Sending to TSE box: " + setRelayCmd) print(f"Also publishing topic [{pubTopic}] with status [{pubPayload}]") #await aser.write_async(bytes(setRelayCmd + '\r\n', "ascii")) await mqttClient.publish(pubTopic, payload=pubPayload) async def handleTsePower(tval, client, sysSelect, cmd): rel = RelayState(mapping_toggles[sysSelect], cmd == '1') await executeAndPublish(tval, client, f'{room}/power/{sysSelect}/status', cmd, rel) async def handleTseSencilo(tval, client, cmd): #relName = tval.split('/')[3] topicPub = f'{room}/firanki/status' if cmd == "MOVE_UP": rel = RelayState(shades_mapping['gor'], True) await executeAndPublish(tval, client, topicPub, "MOVING_UP", rel) elif cmd == "MOVE_DOWN": rel = RelayState(shades_mapping['dol'], True) await executeAndPublish(tval, client, topicPub, "MOVING_DOWN", rel) else: await executeAndPublish(tval, client, topicPub, "STATIONARY", RelayState(shades_mapping['gor'], False)) await executeAndPublish(tval, client, topicPub, "STATIONARY", RelayState(shades_mapping['dol'], False)) platnoBckgdMoving = False # mucho importante variable prav zares dedoles async def platnoTimeout(topicVal, mqttClient, pubTopic, pubPayload, relStat): global platnoBckgdMoving await asyncio.sleep(3) #TODO time actual delay relStat.state = False executeAndPublish(topicVal, mqttClient, pubTopic, pubPayload, relStat) platnoBckgdMoving = False async def handleTsePlatno(tval, client, proj, cmdType, cmd): global platnoBckgdMoving topicSplit = tval.split('/') # {room} {projectors} {[select]} {platno} {move/goto} projector = topicSplit[2] command = topicSplit[4] pubTop = f'{room}/projectors/{projector}/status' if platnoBckgdMoving: if cmd == 'STOP': pubPld = 'UNKNOWN' rel1 = RelayState(platno_mapping[projector]['gor'], False) rel2 = RelayState(platno_mapping[projector]['dol'], False) executeAndPublish(tval,client,pubTop, pubPld, rel1) executeAndPublish(tval,client,pubTop, pubPld, rel2) else: return # ignore any other move commands while moving #movement cmds if cmdType == 'move': rel: RelayState if cmd == 'UP': rel = RelayState(platno_mapping[proj]['gor'], True) elif cmd == 'DOWN': rel = RelayState(platno_mapping[proj]['dol'], True) else: return # in case of invalid input skip platnoBckgdState = True pubPld = 'MOVING' executeAndPublish(tval, client, pubTop, pubPld, rel) elif cmdType == 'goto': rel: RelayState if cmd == 'UP': rel = RelayState(platno_mapping[proj]['gor'], True) elif cmd == 'DOWN': rel = RelayState(platno_mapping[proj]['dol'], True) else: return # in case of invalid input skip platnoBckgdState = True pubPld = 'MOVING' executeAndPublish(tval, client, pubTop, pubPld, rel) asyncio.create_task(platnoTimeout(tval, client, pubTop, pubPld, rel)) return async def task_command2serial(controlClient: aiomqtt.Client): await controlClient.subscribe(f"{room}/#") async with controlClient.messages as msgs: async for mesg in msgs: mesg: aiomqtt.Message topicVal = mesg.topic.value msgTopic = mesg.topic.value.split('/') cmnd = mesg.payload.decode() if mesg.topic.matches(f'{room}/projectors/+/platno/#'): proj = msgTopic[2] # glavni / stranski cmdType = msgTopic[4] # move / goto await handleTsePlatno(topicVal, controlClient, proj, cmdType, cmnd) elif mesg.topic.matches(f'{room}/power/#'): systype = msgTopic[2] await handleTsePower(topicVal, controlClient, systype, cmnd) elif mesg.topic.matches(f'{room}/firanki/command/#'): await handleTseSencilo(topicVal, controlClient, cmnd) else: continue # code after if block doesnt execute in this case await asyncio.sleep(0.01) async def main(): async with aiomqtt.Client('localhost', 1883) as client: task_status = asyncio.create_task(task_status2mqtt(client)) task_control = asyncio.create_task(task_command2serial(client)) await asyncio.gather(task_status, task_control) if __name__ == '__main__': asyncio.run(main())