138 lines
4.5 KiB
Python
138 lines
4.5 KiB
Python
import asyncio
|
|
from collections import defaultdict
|
|
import aiomqtt
|
|
import telnetlib3
|
|
import toml
|
|
import sys
|
|
import os
|
|
|
|
#GLOBALS
|
|
|
|
room: str
|
|
barcoPosition: str
|
|
barcoReached: bool
|
|
|
|
lastState = defaultdict(lambda: None)
|
|
|
|
cmdMap = {
|
|
'power': 'POWR',
|
|
'shutter': 'PMUT',
|
|
'freeze': 'FRZE',
|
|
#'test_pattern': 'TPRN',
|
|
}
|
|
reverseCmdMap = {v: k for k, v in cmdMap.items()}
|
|
|
|
# There needs to be a minimum time between writes. Since we have two "threads", we use a lock and a sleep in barco_send() to enforce it
|
|
lock = asyncio.Lock()
|
|
|
|
def parse_barco_response(raw: str):
|
|
raw = raw[1:-1] # strip square brackets
|
|
|
|
if raw.startswith("ERR"):
|
|
print("ERROR:", raw)
|
|
return None # TODO parse type error - "disabled control" is special case which shouldnt normally happen
|
|
|
|
cmd, status = raw.split("!", maxsplit=2)
|
|
|
|
cmd = reverseCmdMap[cmd]
|
|
status = int(status)
|
|
|
|
barcoReached = True
|
|
return cmd, status
|
|
|
|
|
|
async def barco_send(writer, value):
|
|
async with lock:
|
|
writer.write(value + '\r\n')
|
|
print("Writing", value)
|
|
await asyncio.sleep(0.2) # sleep between writes necessary, otherwise it gets confused.
|
|
|
|
async def barco_telnet_command(client, writer, select: str):
|
|
"""Receive commands from MQTT and send them to the projector"""
|
|
await client.subscribe(f"{room}/projectors/{select}/#")
|
|
|
|
async for mesg in client.messages:
|
|
|
|
if mesg.topic.matches(f"{room}/projectors/{select}/set/+"):
|
|
cmd = mesg.topic.value.rsplit("/", maxsplit=1)[-1]
|
|
val = mesg.payload.decode()
|
|
|
|
if val not in ("0", "1") or cmd not in cmdMap:
|
|
print("INVALID COMMAND OR VALUE:", cmd, val)
|
|
continue
|
|
|
|
barcoCmd = cmdMap[cmd]
|
|
# Send command to projector
|
|
await barco_send(writer, f"[{barcoCmd}{val}]")
|
|
# Immediately ask for a status
|
|
await barco_send(writer, f"[{barcoCmd}?]")
|
|
|
|
|
|
async def barco_telnet_read_status(client, reader, select: str):
|
|
"""Read status reports (we trigger them in the polling task as well as whenver sending a command)"""
|
|
while True:
|
|
output = await reader.readuntil(b']')
|
|
raw_response: str = output.decode()
|
|
print("Received: " + raw_response + " from Barco (" + select + ')')
|
|
|
|
try:
|
|
key, val = parse_barco_response(raw_response)
|
|
except:
|
|
print("NOT PARSED:", raw_response)
|
|
continue
|
|
|
|
await client.publish(f"{room}/projectors/{select}/status/{key}", payload=val)
|
|
|
|
|
|
async def barco_telnet_query_status(writer, select: str):
|
|
"""Periodically ask the projector for its status"""
|
|
while True:
|
|
# Most queries only work when turned on, so if we're not sure, only query power
|
|
if lastState[cmdMap["power"]] == "01":
|
|
queries = cmdMap.values()
|
|
else:
|
|
queries = [cmdMap["power"]]
|
|
|
|
for val in queries:
|
|
await barco_send(writer, f"[{val}?]")
|
|
|
|
await asyncio.sleep(2) # TODO find appropriate period
|
|
|
|
|
|
async def main():
|
|
global room, barcoReached, barcoPosition
|
|
if len(sys.argv) < 2:
|
|
sys.exit("No position provided")
|
|
else:
|
|
barcoPosition = sys.argv[1]
|
|
|
|
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
|
|
conf = toml.load(config_file)
|
|
room = conf['global']['room']
|
|
mqttHost = conf['global']['mqttHost']
|
|
mqttPort = conf['global']['mqttPort']
|
|
|
|
g62Barcos = {k: v for k,v in conf["barco_G62"].items()}
|
|
currentBarco = g62Barcos[barcoPosition]
|
|
barcoIP = currentBarco['ip']
|
|
telnetPort = int(currentBarco["port"])
|
|
|
|
barcoReached = False
|
|
try:
|
|
barcoReader, barcoWriter = await telnetlib3.open_connection(barcoIP, telnetPort)
|
|
barcoReached = True
|
|
except Exception as e:
|
|
print("Connection failed: " + barcoIP + ": " + str(e))
|
|
barcoReached = False
|
|
else:
|
|
async with aiomqtt.Client(mqttHost, mqttPort) as client:
|
|
task_status_query_barco = asyncio.create_task(barco_telnet_query_status(barcoWriter, barcoPosition))
|
|
task_status_reader_barco = asyncio.create_task(barco_telnet_read_status(client, barcoReader, barcoPosition))
|
|
task_control_barco = asyncio.create_task(barco_telnet_command(client, barcoWriter, barcoPosition))
|
|
|
|
await asyncio.gather(task_status_query_barco, task_status_reader_barco, task_control_barco)
|
|
await client.publish(f"{room}/projectors/{barcoPosition}/error", payload=("UNREACHABLE" if not barcoReached else "OK"))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main())
|