From 7227decc36333ea38bf689f116b1a83964b21624 Mon Sep 17 00:00:00 2001 From: 0xEmm Date: Tue, 9 Jan 2024 19:27:31 +0100 Subject: [PATCH] (Late) initial commit, finalized controler for communication between TSE box serial interface and MQTT --- .gitignore | 2 + relay_board/relay_board_controler.py | 2 + serialTests.py | 18 +++++++++ tse_serial/tse_serial_controler.py | 59 ++++++++++++++++++++++++++++ tse_serial/tse_serial_interpreter.py | 35 +++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 .gitignore create mode 100644 relay_board/relay_board_controler.py create mode 100644 serialTests.py create mode 100644 tse_serial/tse_serial_controler.py create mode 100644 tse_serial/tse_serial_interpreter.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a047a94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +__pycache__/ \ No newline at end of file diff --git a/relay_board/relay_board_controler.py b/relay_board/relay_board_controler.py new file mode 100644 index 0000000..ce76e39 --- /dev/null +++ b/relay_board/relay_board_controler.py @@ -0,0 +1,2 @@ +import gpiozero as GPIO + diff --git a/serialTests.py b/serialTests.py new file mode 100644 index 0000000..ba3ec96 --- /dev/null +++ b/serialTests.py @@ -0,0 +1,18 @@ +import unittest + +from tse_serial.tse_serial_interpreter import * + + +class TestStringMethods(unittest.TestCase): + + def test_response_parser(self): + self.assertEquals(resp_to_relay_state("T5A_ON"), RelayState(relay_id=5, state=True)) + self.assertEquals(resp_to_relay_state("T3B_OFF"), RelayState(relay_id=11, state=False)) + self.assertEquals(resp_to_relay_state("re_2A=OFF"), RelayState(relay_id=2, state=False)) + self.assertEquals(resp_to_relay_state("re_1B=OFF"), RelayState(relay_id=9, state=False)) + + def test_command_parser(self): + self.assertEquals(relay_state_to_cmd(RelayState(3, False)), "r39") + self.assertEquals(relay_state_to_cmd(RelayState(5, True)), "r58") + self.assertEquals(relay_state_to_cmd(RelayState(9, True)), "r16") + self.assertEquals(relay_state_to_cmd(RelayState(11, False)), "r37") \ No newline at end of file diff --git a/tse_serial/tse_serial_controler.py b/tse_serial/tse_serial_controler.py new file mode 100644 index 0000000..63c721e --- /dev/null +++ b/tse_serial/tse_serial_controler.py @@ -0,0 +1,59 @@ +import asyncio +import serial +import aioserial +import aiomqtt +from tse_serial_interpreter import * + +aser: aioserial.AioSerial = aioserial.AioSerial( + port='/dev/cu.usbserial-14240', + baudrate=1200, + parity=serial.PARITY_NONE, + bytesize=serial.EIGHTBITS, + stopbits=serial.STOPBITS_ONE + ) +# TODO adjust serial on actual TSE interface + +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) + publishTopic = f"p1/tseRelays/{relState.relay_id}/status" + publishPayload = "ON" if relState.state else "OFF" + print("Publishing [" + publishPayload + "] to topic [" + publishTopic + "]") + await statusClient.publish(publishTopic, payload=publishPayload) + + +async def task_command2serial(controlClient: aiomqtt.Client): + await controlClient.subscribe("p1/tseRelays/#") + async with controlClient.messages() as msgs: + async for mesg in msgs: + mesg: aiomqtt.Message + if mesg.topic.matches('p1/tseRelays/+/command'): + msgTopic = mesg.topic.value + cmnd = mesg.payload.decode() + print("Received: [" + msgTopic + "] payload: [" + cmnd + "]") + relay = int(mesg.topic.value.split("/")[-2]) + cmnd = cmnd == "ON" + relState = RelayState(relay, cmnd) + setRelay = relay_state_to_cmd(relState) + print("Sending to TSE box: " + setRelay) + await aser.write_async(bytes(setRelay + '\r\n', "ascii")) + + publishTopic = f"p1/tseRelays/{relState.relay_id}/status" + publishPayload = "ON" if relState.state else "OFF" + print("Also publishing topic [" + publishTopic + "] with status [" + publishPayload + "]") + await controlClient.publish(publishTopic, payload=publishPayload) + + 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()) \ No newline at end of file diff --git a/tse_serial/tse_serial_interpreter.py b/tse_serial/tse_serial_interpreter.py new file mode 100644 index 0000000..609dfaa --- /dev/null +++ b/tse_serial/tse_serial_interpreter.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass + + +@dataclass +class RelayState: + relay_id: int + state: bool + + +def resp_to_relay_state(cmd: str) -> RelayState: + state = True if cmd.find("ON") > -1 else False + #state = False if cmd.find("OFF") > -1 and state == None else None + + r_state = RelayState(0, state) + id = 0 + + if cmd.startswith("T"): + id = int(cmd[1]) + (8 if cmd[2] == "B" else 0) + elif cmd.startswith("re_"): + id = int(cmd[3]) + (8 if cmd[4] == "B" else 0) + + r_state = RelayState(id, state) + return r_state + + +def relay_state_to_cmd(r_state: RelayState) -> str: + #r[NUM][CMD] + cmd = 8 if r_state.state else 9 + id = r_state.relay_id + # banka B + if id > 8: + cmd -= 2 + id -= 8 + return f"r{id}{cmd}" + #return "r" + str(id) + str(cmd) \ No newline at end of file