Browse Source

backend: Add DI framework and logging from serial_communication.py

Andreas Berthoud 5 years ago
parent
commit
eabe05bc86
  1. 5
      backend/__init__.py
  2. 132
      backend/command.py
  3. 58
      backend/container.py
  4. 3
      backend/defaults/config.yml
  5. 1
      requirements.txt

5
backend/__init__.py

@ -4,6 +4,7 @@ from logging.config import dictConfig
from flask_api import FlaskAPI from flask_api import FlaskAPI
from . import command from . import command
from . import container
def create_app() -> FlaskAPI: def create_app() -> FlaskAPI:
@ -25,10 +26,12 @@ def create_app() -> FlaskAPI:
"formatter": "default", "formatter": "default",
}, },
}, },
"root": {"level": "INFO", "handlers": ["wsgi"]}, "root": {"level": "DEBUG", "handlers": ["wsgi"]},
}, },
) )
container.init_app(app)
if os.environ.get("WERKZEUG_RUN_MAIN") != "true": if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
# prevent from be called twice in debug mode # prevent from be called twice in debug mode
command.start_backgroup_process() command.start_backgroup_process()

132
backend/command.py

@ -2,6 +2,8 @@ import atexit
import logging import logging
import os import os
import time import time
from dataclasses import dataclass
from enum import Enum
from multiprocessing import Process from multiprocessing import Process
from multiprocessing import Queue from multiprocessing import Queue
from typing import Optional from typing import Optional
@ -10,6 +12,10 @@ from flask import Response
from flask import blueprints from flask import blueprints
from flask import request from flask import request
from flask_api import status from flask_api import status
from serial import Serial
from .container import get_container
from .container import initialize_container
bp = blueprints.Blueprint("command", __name__) bp = blueprints.Blueprint("command", __name__)
@ -35,28 +41,130 @@ def _end_running_process():
_process.kill() _process.kill()
def poll_from_usb(queue: Queue): def worker_process(
queue: Queue,
config,
# container: Container
):
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, 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 counter = 1
while True: with container.serial() as serial:
logger.info(f"Ping {counter} {os.getpid()}") while True:
counter += 1 logger.info(f"Ping {counter} {os.getpid()}")
time.sleep(1) 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: message = data[self.HEADER_SIZE :]
while not queue.empty(): self._logger.debug("Message: " + str(message))
command_id = queue.get()
logger.info(f"would execute command: {command_id}") 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(): def start_backgroup_process():
_logger.warning("start_backgroup_process called") _logger.warning("start_backgroup_process called")
global _process global _process
_process = Process(target=poll_from_usb, args=(_queue,)) _process = Process(
target=worker_process,
args=(
_queue,
get_container().config(),
),
)
_process.start() _process.start()
atexit.register(_end_running_process) atexit.register(_end_running_process)

58
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

3
backend/defaults/config.yml

@ -0,0 +1,3 @@
device_id: /dev/tty.usbmodem2067368F32521
baudrate: 115200
header_size: 4

1
requirements.txt

@ -1,3 +1,4 @@
dependency-injector[yaml]>=4.34.0,<5
flask-api>=3.0.post1,<4 flask-api>=3.0.post1,<4
pre-commit pre-commit
pyserial>=3.5,<4 pyserial>=3.5,<4

Loading…
Cancel
Save