diff --git a/backend/__init__.py b/backend/__init__.py index 68d6812..9f7e785 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -4,6 +4,7 @@ from logging.config import dictConfig from flask_api import FlaskAPI from . import command +from . import container def create_app() -> FlaskAPI: @@ -25,10 +26,12 @@ def create_app() -> FlaskAPI: "formatter": "default", }, }, - "root": {"level": "INFO", "handlers": ["wsgi"]}, + "root": {"level": "DEBUG", "handlers": ["wsgi"]}, }, ) + container.init_app(app) + if os.environ.get("WERKZEUG_RUN_MAIN") != "true": # prevent from be called twice in debug mode command.start_backgroup_process() diff --git a/backend/command.py b/backend/command.py index a9d3398..3064d74 100644 --- a/backend/command.py +++ b/backend/command.py @@ -2,6 +2,8 @@ import atexit import logging import os import time +from dataclasses import dataclass +from enum import Enum from multiprocessing import Process from multiprocessing import Queue from typing import Optional @@ -10,6 +12,10 @@ from flask import Response from flask import blueprints from flask import request from flask_api import status +from serial import Serial + +from .container import get_container +from .container import initialize_container bp = blueprints.Blueprint("command", __name__) @@ -35,28 +41,130 @@ def _end_running_process(): _process.kill() -def poll_from_usb(queue: Queue): +def worker_process( + queue: Queue, + config, + # container: Container +): logging.basicConfig( level=logging.DEBUG, - format="[%(asctime)s] [%(levelname)-8s] --- %(message)s", + format="[%(asctime)s] [%(name)-20s] [%(levelname)-8s] --- %(message)s", ) - logger = logging.getLogger("poll_from_usb") + logger = logging.getLogger("worker_process") + + initialize_container(config) + container = get_container() + counter = 1 - while True: - logger.info(f"Ping {counter} {os.getpid()}") - counter += 1 - time.sleep(1) + with container.serial() as serial: + while True: + logger.info(f"Ping {counter} {os.getpid()}") + counter += 1 + time.sleep(0.5) + receive_and_log( + serial=serial, + header_size=container.config.header_size(), + ) + + if queue is not None: + while not queue.empty(): + command_id = queue.get() + logger.debug(f"device_id {container.config.device_id()}") + logger.info(f"would execute command: {command_id}") + + +class CommandId(Enum): + command_none = 0 + command_log = 0xFF + + +@dataclass +class CommandMeta: + command_id: CommandId + data_length: int + + +@dataclass +class LogCommand: + """Command ID: command_log""" + + level: int + message: str + + HEADER_SIZE = 1 # log level + + def __init__( + self, + data: bytes, + ) -> None: + self._logger = logging.getLogger(self.__class__.__name__) + self.received_logger = logging.getLogger("stm32wb55") + self.received_logger.setLevel(logging.DEBUG) + + self._logger.setLevel(logging.INFO) + + level = int(data[0]) + self._logger.debug(f"level: {level}") - if queue is not None: - while not queue.empty(): - command_id = queue.get() - logger.info(f"would execute command: {command_id}") + message = data[self.HEADER_SIZE :] + self._logger.debug("Message: " + str(message)) + + self.level = level + self.message = message.decode() + + def execute(self): + self.received_logger.log(level=self.level, msg=self.message) + + +def receive_and_log( + serial: Serial, + header_size: int, +): + logger = logging.getLogger("receive_and_log") + logger.setLevel(logging.INFO) + + bytes_read = serial.read(serial.in_waiting) + + while bytes_read: + + logger.debug(f"bytes: {bytes_read.hex()}") + + command_id = int(bytes_read[0]) + logger.debug(f"command_id: {command_id}") + + data_length = (int(bytes_read[1]) << 4) + int(bytes_read[2]) + logger.debug(f"data_length: {data_length}") + + meta = CommandMeta( + command_id=CommandId(command_id), + data_length=data_length, + ) + + if meta.command_id == CommandId.command_log: + command = LogCommand( + data=bytes_read[header_size : header_size + meta.data_length], + ) + command.execute() + else: + return + + stop_byte = bytes_read[header_size + meta.data_length] + logger.debug(f"stop_byte: {stop_byte}") + assert stop_byte == 0xFF + + bytes_read = bytes_read[header_size + meta.data_length + 1 :] def start_backgroup_process(): _logger.warning("start_backgroup_process called") global _process - _process = Process(target=poll_from_usb, args=(_queue,)) + _process = Process( + target=worker_process, + args=( + _queue, + get_container().config(), + ), + ) _process.start() atexit.register(_end_running_process) diff --git a/backend/container.py b/backend/container.py new file mode 100644 index 0000000..7c443ff --- /dev/null +++ b/backend/container.py @@ -0,0 +1,58 @@ +from pathlib import Path +from typing import Dict +from typing import Optional + +from dependency_injector import containers +from dependency_injector import providers +from flask_api import FlaskAPI +from serial import Serial + +DEFAULTS_DIR = Path(__file__).parent / "defaults" +CONFIG_FILE = DEFAULTS_DIR / "config.yml" + + +class Container(containers.DeclarativeContainer): + config = providers.Configuration("config") + + serial = providers.Factory( + Serial, + port=config.device_id.required(), + baudrate=config.baudrate.required(), + ) + + +__container: Optional[Container] = None + + +def initialize_container( + config: Optional[Dict] = None, + config_file: Optional[Path] = None, +) -> Container: + global __container + if __container is not None: + raise RuntimeError("Container already initialized") + + __container = Container() + __container.config.from_yaml(CONFIG_FILE) + + if config is not None: + __container.config.from_dict(config) + + if config_file is not None: + __container.config.from_yaml(config_file) + + return __container + + +def init_app(app: FlaskAPI): + initialize_container( + config=app.config.get("DI_CONFIG"), + config_file=app.config.get("DI_CONFIG_FILE"), + ) + + +def get_container() -> Container: + global __container + if __container is None: + raise RuntimeError("Container not initialized") + return __container diff --git a/backend/defaults/config.yml b/backend/defaults/config.yml new file mode 100644 index 0000000..8158c3d --- /dev/null +++ b/backend/defaults/config.yml @@ -0,0 +1,3 @@ +device_id: /dev/tty.usbmodem2067368F32521 +baudrate: 115200 +header_size: 4 diff --git a/requirements.txt b/requirements.txt index 98765ef..88ebcb4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +dependency-injector[yaml]>=4.34.0,<5 flask-api>=3.0.post1,<4 pre-commit pyserial>=3.5,<4