[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/bringauto/example-module/master/python_client/run_buttons.py [Back]  [Original]

import json
import logging
from dataclasses import dataclass
import asyncio
import os
import argparse

import yaml
from rich.logging import RichHandler

from internal_client import InternalClient, exceptions
from button_logic import ButtonLogic, ButtonState, ButtonLedState

MODULE_ID = 1000


@dataclass
class ButtonClientConfig:
    device_name: str
    device_type: int
    device_role: str
    device_priority: int = 0

    def __repr__(self) -> str:
        return f"(name={self.device_name}, type={self.device_type}, role={self.device_role}, priority={self.device_priority})"

    @staticmethod
    def load_config(file_name: str) -> list["ButtonClientConfig"]:
        if not os.path.isfile(file_name):
            raise FileNotFoundError(f"Config file not found: {file_name}")
        with open(file_name, "r") as f_in:
            try:
                cfg = yaml.safe_load(f_in)
            except yaml.parser.ParserError:
                raise ValueError(f"Config file can not be parsed as YAML") from None

        button_configs = []
        for button in cfg:
            button_name = list(button.keys())[0]
            try:
                button_role = button[button_name]["role"]
                button_type = int(button[button_name].get("type", 0))
                button_priority = int(button[button_name].get("priority", 0))
            except KeyError:
                raise ValueError(f"Missing required field in {button_name} config.") from None
            except ValueError:
                raise ValueError(f"Invalid value in {button_name} config.") from None

            button_configs.append(
                ButtonClientConfig(button_name, button_type, button_role, button_priority)
            )

        return button_configs


class ButtonClient:

    def __init__(self, server_ip: str, server_port: int, button_config: ButtonClientConfig, manual_mode: bool) -> None:
        self.server_connection = (server_ip, server_port)
        self.device_name = button_config.device_name
        self.device_type = button_config.device_type
        self.device_role = button_config.device_role
        self.manual_mode = manual_mode

        self.device_priority = button_config.device_priority

        self.logger = logging.getLogger(f"{self.device_name}")
        self.button = ButtonLogic(logger=self.logger, manual_mode=manual_mode)

    async def start(self) -> None:
        if not self._connect():
            return

        while True:
            state_data = self._get_state_data()
            try:
                self.client.send_status(state_data, timeout=5)
            except (exceptions.CommunicationExceptions, exceptions.ConnectExceptions) as e:
                self.logger.error(f"Send status unsuccessful: {e}")
                break

            command_data = self.client.get_command()
            try:
                self._handle_command_data(command_data)
            except ValueError as e:
                self.logger.error(f"Invalid Command: {e}")
                break
            if not self.manual_mode:
                await asyncio.sleep(5)

        self.client.destroy()

    def _connect(self) -> bool:
        try:
            self.client = InternalClient(
                MODULE_ID,
                self.server_connection[0],
                self.server_connection[1],
                self.device_name,
                self.device_type,
                self.device_role,
                self.device_priority,
            )
        except exceptions.ConnectExceptions as e:
            self.logger.error(
                f"Button could not connect because server responsed with: {type(e)}"
            )
            return False
        except exceptions.CommunicationExceptions as e:
            self.logger.error(f"Connection to server could not be established due to: {e}")
            return False

        return True

    def _get_state_data(self) -> bytes:
        button_state = self.button.get_button_state()
        binary_data = json.dumps({"pressed": button_state == ButtonState.PRESSED}).encode()
        return binary_data

    def _handle_command_data(self, command_data: bytes) -> None:
        try:
            data_json = json.loads(command_data)
        except json.JSONDecodeError:
            raise ValueError(f"Couldn't parse json from binary commandData")

        try:
            led_command = data_json["lit_up"]
            assert type(led_command) == bool
        except (KeyError, AssertionError):
            raise ValueError(f"commandData json has invalid structure")

        self.button.set_led_state(ButtonLedState(led_command))


async def run_buttons(
    server_ip: str, server_port: int, button_configs: list[ButtonClientConfig], manual_mode: bool
) -> None:
    logger = logging.getLogger("TaskRunner")
    buttons = [ButtonClient(server_ip, server_port, cfg, manual_mode) for cfg in button_configs]
    tasks = []
    for button, button_cfg in zip(buttons, button_configs):
        logger.info(f"Starting button {button_cfg}")
        tasks.append(asyncio.create_task(button.start()))

    for task in tasks:
        await task

    logger.info("All buttons finished")


if __name__ == "__main__":
    FORMAT = "%(name)s:%(message)s"
    logging.basicConfig(
        level=logging.DEBUG, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
    )

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-i", "--ip", type=str, default="127.0.0.1", help="IP address of server"
    )
    parser.add_argument("-m", "--manual", type=bool, default=False, help="Set manual mode, buttons will expect input from keyboard in order to be pressed")
    parser.add_argument("-p", "--port", type=int, default=8888, help="Port of server")
    parser.add_argument(
        "-cfg",
        "--config",
        type=str,
        default="buttons.yaml",
        help="Path to yaml button config file",
    )
    args = parser.parse_args()

    try:
        buttons = ButtonClientConfig.load_config(args.config)
    except (FileNotFoundError, ValueError) as e:
        logging.error(e)
        exit(1)

    try:
        asyncio.run(run_buttons(args.ip, args.port, buttons, args.manual))
    except KeyboardInterrupt:
        logging.info("Shuting down.")

Web Proxy Viewer  |  New URL  |  Original Page