From a9820001b4fecf208bc0543a7760f83cd82d4bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Frange=C5=BE?= Date: Tue, 29 Oct 2024 21:07:41 +0100 Subject: [PATCH] Old Barco projectors (initial implementation) --- barco_rlmw_http/barco_rlwm_http.py | 67 +++++++++++++++++++++ barco_rlmw_http/main.py | 79 ++++++++++++++++++++++++ barco_rlmw_tcp/barco_rlmw_tcp.py | 96 ++++++++++++++++++++++++++++++ barco_rlmw_tcp/main.py | 72 ++++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 barco_rlmw_http/barco_rlwm_http.py create mode 100644 barco_rlmw_http/main.py create mode 100644 barco_rlmw_tcp/barco_rlmw_tcp.py create mode 100644 barco_rlmw_tcp/main.py diff --git a/barco_rlmw_http/barco_rlwm_http.py b/barco_rlmw_http/barco_rlwm_http.py new file mode 100644 index 0000000..04327e7 --- /dev/null +++ b/barco_rlmw_http/barco_rlwm_http.py @@ -0,0 +1,67 @@ +import re +import asyncio +import aiohttp +from asyncio.exceptions import CancelledError + +PORT = 43680 +RE_STATUS = re.compile(r".stats.txt(.+).value='(.+)';") +REMOTEKEY_TIMEOUT = aiohttp.ClientTimeout(total=1) + + +class BarcoRLM_Control: + def __init__(self, projector_ip): + self.projector_ip = projector_ip + self.session = None + + async def _request(self, method, path, *args, **kwargs): + if not self.session: + self.session = aiohttp.ClientSession() + url = f"http://{self.projector_ip}{path}" + resp = await self.session.request(method, url, *args, **kwargs) + resp.raise_for_status() + return await resp.text() + + async def get_status(self): + resp = await self._request("GET", "/tgi/firststatus.tgi") + matches = RE_STATUS.findall(resp) + status = dict(matches) + return status + + async def toggle_power(self): + await self._request("GET", "/tgi/general.tgi?powertog") + + async def click_remote(self, key): + try: + await self._request("GET", f"/tgi/remote.tgi?{key}", timeout=REMOTEKEY_TIMEOUT, raise_for_status=False) + except TimeoutError: + pass + + async def set_shutter(self, shutter): + endpoint = f"/tgi/general.tgi?pause{onoff(shutter)}" + await self._request("GET", endpoint) + + async def set_power(self, power): + key = onoff(power) + "ky" + await self.click_remote(key) + + async def set_input(self, input): + endpoint = f"/tgi/input.tgi?{input}" + await self._request("GET", endpoint) + + +def onoff(state: bool) -> str: + if state: + return "on" + else: + return "off" + +if __name__ == "__main__": + + async def main(): + barco = BarcoRLM_Control("192.168.192.12") + status = await barco.get_status() + print(status) + + await barco.click_remote("kymenu") + + asyncio.run(main()) diff --git a/barco_rlmw_http/main.py b/barco_rlmw_http/main.py new file mode 100644 index 0000000..eb5c3ba --- /dev/null +++ b/barco_rlmw_http/main.py @@ -0,0 +1,79 @@ +import asyncio +import aiomqtt +from collections import defaultdict + +from barco_legacy import BarcoRLM_Control + +# TODO MAKE THIS CONFIGURALBE +PROJECTOR_IP = "192.168.192.12" +MQTT_PREFIX = "p22/projektorji/glavni" +MQTT_HOST = "-----" +POLLING_PERIOD_SEC = 10 + + +class BarcoRLM_MQTT: + def __init__(self, projector_ip, mqtt_prefix): + self.projector_ip = projector_ip + self.mqtt_prefix = mqtt_prefix + self.barco = BarcoRLM_Control(projector_ip) + self.last_status = defaultdict(lambda: '') + + async def run(self): + async with aiomqtt.Client(MQTT_HOST, 1883) as client: + self.client = client + task_polling = asyncio.create_task(self.task_polling()) + task_control = asyncio.create_task(self.task_control()) + await asyncio.gather(task_control, task_polling) + + async def task_control(self): + topicMatch = f"{self.mqtt_prefix}/ukaz/+" + await self.client.subscribe(topicMatch) + + async for mesg in self.client.messages: + cmd = mesg.topic.value.split("/")[-1] + val = mesg.payload.decode() + + if cmd == "power": + await self.barco.set_power(onoff(val)) + elif cmd == "shutter": + await self.barco.set_shutter(onoff(val)) + + async def task_polling(self): + while True: + status = await self.barco.get_status() + for key, val in status.items(): + if self.last_status[key] != val: + print(f"Status change {key}={val}") + await self.on_status_change(key, val) + + self.last_status = status + await asyncio.sleep(POLLING_PERIOD_SEC) + + async def on_status_change(self, key, val): + mkey = mval = None + if key == "status": + mkey = "power" + if val == "Imaging": + mval = "1" + if val == "Standby": + mval = "0" + if key == "src": + mkey = "input" + mval = val + if key == "fmr": + mkey = "input_format" + mval = val + if mkey is not None and mval is not None: + await self.client.publish(f"{self.mqtt_prefix}/status/{mkey}", payload=mval) + + +def onoff(input): + if input == "1": + return True + elif input == "0": + return False + + +if __name__ == '__main__': + barco = BarcoRLM_MQTT(PROJECTOR_IP, MQTT_PREFIX) + asyncio.run(barco.run()) diff --git a/barco_rlmw_tcp/barco_rlmw_tcp.py b/barco_rlmw_tcp/barco_rlmw_tcp.py new file mode 100644 index 0000000..905dc4f --- /dev/null +++ b/barco_rlmw_tcp/barco_rlmw_tcp.py @@ -0,0 +1,96 @@ +import re +import asyncio +import telnetlib3 +from dataclasses import dataclass +from enum import StrEnum + +PORT = 43680 +RE_STATUS = re.compile(r"OP (.+?)(?:$| = (.+)$)") + + +@dataclass(frozen=True) +class ACK: + command: str + +@dataclass(frozen=True) +class ValueUpdate: + key: str + value: str + +class Status(StrEnum): + standby = "0" + warmup = "1" + imaging = "2" + cooling = "3" + warning = "4" + +class LampMode(StrEnum): + economy = "0" + standard = "1" + dimming = "2" + +class ValueType(StrEnum): + status = "status" + picture_mute = "picture.mute" + lamp_mode = "lamp.mode" + + +class BarcoRLMW_TCP: + def __init__(self, projector_ip): + self.projector_ip = projector_ip + self.session = None + + async def init(self): + self.reader, self.writer = await telnetlib3.open_connection(self.projector_ip, 3023) + + async def exec(self, cmd): + await self.writer.write("op " + cmd + "\r") + + async def set(self, key, val): + await self.writer.write("op " + key + " = " + val + "\r") + + async def query(self, key): + await self.writer.write("op " + key + " ?\r") + + async def set_power(self, power): + await self.exec(f"power.{onoff(power)}") + + async def set_shutter(self, shutter): + await self.set("picture.mute", int(shutter)) + + async def iter_messages(self): + """Async iterator that processes feedback from the projector. + Yields ACK (acknowledgement) and ValueUpdate (value has changed) messages.""" + while True: + line = await self.reader.readuntil('\r').decode().strip() + msg = self.parse_response(line) + if msg: + yield msg + + def parse_response(self, line): + matches = RE_STATUS.findall(line) + if len(matches) == 0: + return None + match = matches[0] + if len(match) == 2: + return ACK(match[1]) + if len(match) == 3: + return ValueUpdate(match[1], match[2]) + + +def onoff(state: bool) -> str: + if state: + return "on" + else: + return "off" + +if __name__ == "__main__": + + async def main(): + barco = BarcoRLMW_TCP("192.168.192.12") + status = await barco.get_status() + print(status) + + await barco.click_remote("kymenu") + + asyncio.run(main()) diff --git a/barco_rlmw_tcp/main.py b/barco_rlmw_tcp/main.py new file mode 100644 index 0000000..04eb690 --- /dev/null +++ b/barco_rlmw_tcp/main.py @@ -0,0 +1,72 @@ +import asyncio +import aiomqtt +from collections import defaultdict + +from barco_rlmw_tcp import BarcoRLMW_TCP, ValueUpdate, Status, ACK, ValueType + +# TODO MAKE THIS CONFIGURALBE +PROJECTOR_IP = "192.168.192.12" +MQTT_PREFIX = "p22/projektorji/glavni" +MQTT_HOST = "-------" +POLLING_PERIOD_SEC = 10 + + +class BarcoRLMW_TCP_MQTT: + def __init__(self, projector_ip, mqtt_prefix): + self.projector_ip = projector_ip + self.mqtt_prefix = mqtt_prefix + self.barco = BarcoRLMW_TCP(projector_ip) + self.last_status = defaultdict(lambda: '') + + async def run(self): + async with aiomqtt.Client(MQTT_HOST, 1883) as client: + self.client = client + task_polling = asyncio.create_task(self.task_polling()) + task_control = asyncio.create_task(self.task_control()) + await asyncio.gather(task_control, task_polling) + + async def task_control(self): + topicMatch = f"{self.mqtt_prefix}/ukaz/+" + await self.client.subscribe(topicMatch) + + async for mesg in self.client.messages: + cmd = mesg.topic.value.split("/")[-1] + val = mesg.payload.decode() + + if cmd == "power": + await self.barco.set_power(onoff(val)) + elif cmd == "shutter": + await self.barco.set_shutter(onoff(val)) + + async def task_polling(self): + while True: + await self.barco.query("status") + await asyncio.sleep(POLLING_PERIOD_SEC) + + async def write_status(self, status, value): + await self.client.publish(f"{self.mqtt_prefix}/status/{status}", payload=value) + + async def task_process_messages(self): + async for msg in self.barco.iter_messages(): + # We only care about value updates + if isinstance(msg, ValueUpdate): + # Power status + if msg.key == ValueType.status: + if msg.value in (Status.imaging, Status.warmup): + self.write_status("power", 1) + elif msg.value in (Status.standby, Status.cooling): + self.write_status("power", 0) + # Video mute + if msg.key == ValueType.picture_mute: + self.write_status("shutter", msg.value) + +def onoff(input): + if input == "1": + return True + elif input == "0": + return False + + +if __name__ == '__main__': + barco = BarcoRLMW_TCP_MQTT(PROJECTOR_IP, MQTT_PREFIX) + asyncio.run(barco.run())