255 lines
No EOL
8.8 KiB
Python
255 lines
No EOL
8.8 KiB
Python
import asyncio
|
|
import serial
|
|
import aioserial
|
|
import aiomqtt
|
|
from tse_serial_interpreter import *
|
|
from dataclasses import dataclass
|
|
import os
|
|
import sys
|
|
import toml
|
|
import serial.tools.list_ports
|
|
|
|
#GLOBALS
|
|
|
|
room = 'undefined' #TODO make be do get fronm file of configuration
|
|
|
|
# serPath = '/dev/serial/by-id/'
|
|
# devLst = os.listdir(serPath)
|
|
# if len(devLst) < 1:
|
|
# sys.exit("No serial device found.")
|
|
# serDev = devLst[0]
|
|
|
|
portList = serial.tools.list_ports.comports()
|
|
if len(portList) < 1:
|
|
sys.exit("No serial port found")
|
|
#TODO if multiple ports idk, shouldn't ever happen but still
|
|
(serport, serdesc, serhwid) = portList[0] #TODO CHECK FOR TTYUSB0
|
|
#TODO if port provided from conf, set, otherwise autodetect magical thing just works
|
|
|
|
aser: aioserial.AioSerial = aioserial.AioSerial(
|
|
port=serport,
|
|
baudrate=1200,
|
|
parity=serial.PARITY_NONE,
|
|
bytesize=serial.EIGHTBITS,
|
|
stopbits=serial.STOPBITS_ONE
|
|
)
|
|
|
|
|
|
#TODO get this from file da ni hardcoded?
|
|
# altho itak je ta script za tist specific tse box in so vsi isti
|
|
mapping_toggles = {
|
|
"master": 1,
|
|
"audio": 2,
|
|
"projectors": 3,
|
|
}
|
|
# 4 is not connected to anything
|
|
platno_mapping = {
|
|
"main": {
|
|
"DOWN": 5,
|
|
"UP": 6
|
|
},
|
|
"side": {
|
|
"DOWN": 7,
|
|
"UP": 8
|
|
}
|
|
}
|
|
|
|
shades_mapping = {
|
|
"DOWN": 9,
|
|
"UP": 10
|
|
}
|
|
|
|
reverse_lookup = {
|
|
1: ("power", "master", ""),
|
|
2: ("power", "audio", ""),
|
|
3: ("power", "projectors", ""),
|
|
|
|
5: ("platno", "main", "MOVING_DOWN"),
|
|
6: ("platno", "main", "MOVING_UP"),
|
|
|
|
7: ("platno", "side", "MOVING_DOWN"),
|
|
8: ("platno", "side", "MOVING_UP"),
|
|
|
|
9: ("shades", "MOVING_DOWN"),
|
|
10: ("shades", "MOVING_UP")
|
|
}
|
|
|
|
#TODO finish this
|
|
#TODO add doc comment to every task funciton
|
|
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)
|
|
if relState.relay_id is None:
|
|
continue # TODO handling - nebi smelo do tega prit anyway
|
|
publishTopic = f"{room}/"
|
|
publishPayload = ""
|
|
lookup = reverse_lookup[relState.relay_id]
|
|
if lookup[0] == "power":
|
|
publishTopic += f"{lookup[0]}/{lookup[1]}/status"
|
|
publishPayload = '1' if relState.state else '0'
|
|
elif lookup[0] == "shades":
|
|
publishTopic += f"{lookup[0]}/status"
|
|
publishPayload = 'STOPPED' if not relState.state else lookup[1]
|
|
elif lookup[0] == "platno":
|
|
publishTopic += f"projectors/{lookup[1]}/{lookup[0]}/status"
|
|
publishPayload = 'UNKNOWN' if not relState.state else lookup[2]
|
|
#publishTopic = f"{room}/projectors/{}"
|
|
#publishPayload = "1" if relState.state else "0"
|
|
print("Publishing [" + publishPayload + "] to topic [" + publishTopic + "]")
|
|
await statusClient.publish(publishTopic, payload=publishPayload)
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
|
async def executeAndPublish(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}]")
|
|
print()
|
|
await aser.write_async(bytes(setRelayCmd + '\r\n', "ascii"))
|
|
await mqttClient.publish(pubTopic, payload=pubPayload)
|
|
await asyncio.sleep(0.1) #TODO probably remove
|
|
|
|
|
|
async def handleTsePower(client, sysSelect, cmd):
|
|
rel = RelayState(mapping_toggles[sysSelect], cmd == '1')
|
|
await executeAndPublish(client, f'{room}/power/{sysSelect}/status', cmd, rel)
|
|
|
|
async def handleTseSencilo(client, cmd):
|
|
#relName = tval.split('/')[3]
|
|
topicPub = f'{room}/shades/status'
|
|
if cmd == "MOVE_UP":
|
|
rel = RelayState(shades_mapping['UP'], True)
|
|
await executeAndPublish(client, topicPub, "MOVING_UP", rel)
|
|
elif cmd == "MOVE_DOWN":
|
|
rel = RelayState(shades_mapping['DOWN'], True)
|
|
await executeAndPublish(client, topicPub, "MOVING_DOWN", rel)
|
|
else:
|
|
await executeAndPublish(client, topicPub, "STOPPED", RelayState(shades_mapping['UP'], False))
|
|
await executeAndPublish(client, topicPub, "STOPPED", RelayState(shades_mapping['DOWN'], False))
|
|
|
|
|
|
platnoBckgdMoving = {
|
|
'main': False,
|
|
'side': False,
|
|
} # mucho importante variable prav zares dedoles
|
|
|
|
async def platnoTimeout(mqttClient, pubTopic, pubPayload, relStat: RelayState, intent, select):
|
|
global platnoBckgdMoving
|
|
await asyncio.sleep(25) #TODO time actual delay
|
|
relStat.state = False
|
|
await executeAndPublish(mqttClient, pubTopic, intent, relStat)
|
|
platnoBckgdMoving[select] = False #TODO properly document why this is here and what it does
|
|
|
|
async def handleTsePlatno(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/{proj}/platno/status'
|
|
if not (proj == "main" or proj == "side"):
|
|
return
|
|
if platnoBckgdMoving[proj]:
|
|
if cmd == 'STOP':
|
|
pubPld = 'UNKNOWN'
|
|
rel1 = RelayState(platno_mapping[proj]['UP'], False)
|
|
rel2 = RelayState(platno_mapping[proj]['DOWN'], False)
|
|
await executeAndPublish(client,pubTop, pubPld, rel1)
|
|
await executeAndPublish(client,pubTop, pubPld, rel2)
|
|
platnoBckgdMoving[proj] = False
|
|
else:
|
|
print("Ignored command because already moving", proj, cmdType, cmd)
|
|
return
|
|
|
|
if cmdType == 'move':
|
|
print('move')
|
|
rel: RelayState
|
|
if cmd == 'UP':
|
|
rel = RelayState(platno_mapping[proj]['UP'], True)
|
|
pubPld = 'MOVING_UP'
|
|
elif cmd == 'DOWN':
|
|
rel = RelayState(platno_mapping[proj]['DOWN'], True)
|
|
pubPld = 'MOVING_DOWN'
|
|
else:
|
|
return # in case of invalid input skip
|
|
platnoBckgdMoving[proj] = True #TODO rename to moving, add comment how it works
|
|
await executeAndPublish(client, pubTop, pubPld, rel)
|
|
#TODO WTF HAPPENS IF YOU SEND UP AND DOWN AT ONCE?? (screenshot?)
|
|
#TODO daj ignore print ko je locked up
|
|
|
|
elif cmdType == 'goto':
|
|
print('received GOTO')
|
|
rel: RelayState
|
|
if cmd == 'UP':
|
|
rel = RelayState(platno_mapping[proj]['UP'], True)
|
|
intent = 'UP'
|
|
pubPld = 'MOVING_UP'
|
|
elif cmd == 'DOWN':
|
|
rel = RelayState(platno_mapping[proj]['DOWN'], True)
|
|
intent = 'DOWN'
|
|
pubPld = 'MOVING_DOWN'
|
|
else:
|
|
return # in case of invalid input skip
|
|
platnoBckgdMoving[proj] = True
|
|
# pubPld = 'MOVING'
|
|
await executeAndPublish(client, pubTop, pubPld, rel)
|
|
asyncio.create_task(platnoTimeout(client, pubTop, pubPld, rel, intent, proj))
|
|
|
|
else:
|
|
print('unknown command')
|
|
|
|
async def task_command2serial(controlClient: aiomqtt.Client):
|
|
#print('oge')
|
|
|
|
await controlClient.subscribe(f"{room}/#")
|
|
#print('ogee')
|
|
#async with controlClient.messages as msgs:
|
|
async for mesg in controlClient.messages:
|
|
#print('oge')
|
|
#print(mesg, mesg.topic)
|
|
|
|
#mesg: aiomqtt.Message
|
|
topicVal = mesg.topic.value
|
|
msgTopic = mesg.topic.value.split('/')
|
|
cmnd = mesg.payload.decode()
|
|
#print('Received on: ', topicVal, ' with:', cmnd)
|
|
#print('bfr')
|
|
if mesg.topic.matches(f'{room}/projectors/+/platno/move') or mesg.topic.matches(f'{room}/projectors/+/platno/goto'):
|
|
proj = msgTopic[-3]
|
|
cmdType = msgTopic[-1] # move / goto
|
|
print(f'on {topicVal} received: {cmnd}')
|
|
await handleTsePlatno(controlClient, proj, cmdType, cmnd) #TODO odzadi index
|
|
|
|
elif mesg.topic.matches(f'{room}/power/+/set'):
|
|
systype = msgTopic[-2]
|
|
print(f'on {topicVal} received: {cmnd}')
|
|
print('calling power')
|
|
await handleTsePower(controlClient, systype, cmnd)
|
|
|
|
elif mesg.topic.matches(f'{room}/shades/move'):
|
|
print(f'on {topicVal} received: {cmnd}')
|
|
print('calling move')
|
|
await handleTseSencilo(controlClient, cmnd)
|
|
|
|
else:
|
|
continue
|
|
# code after if block doesnt execute in this case
|
|
#print("after")
|
|
await asyncio.sleep(0.01) #TODO do we need this? (probably)
|
|
|
|
|
|
async def main():
|
|
global room
|
|
conf = toml.load('./malinaConfig.toml')
|
|
room = conf['global']['room']
|
|
async with aiomqtt.Client('localhost', 1883) as client: #TODO omehčaj kodiranje
|
|
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()) |