diff --git a/.env.exaple b/.env.exaple index 41472c9..57e8367 100644 --- a/.env.exaple +++ b/.env.exaple @@ -2,7 +2,7 @@ WEBSOCKET_HOST=хост WEBSOCKET_PORT=порт -# Команды +# Команды робота FORWARD=вперёд BACKWARD=назад LEFT=влево diff --git a/robot_pi_service/gpio_control.py b/robot_pi_service/gpio_control.py index 3b80da3..0a1651a 100644 --- a/robot_pi_service/gpio_control.py +++ b/robot_pi_service/gpio_control.py @@ -1,5 +1,6 @@ -from gpiod import request_lines, LineSettings +from gpiod import request_lines, LineSettings, RequestReleasedError from gpiod.line import Direction, Value +import asyncio from settings import settings @@ -19,6 +20,7 @@ class LedLineGpio: ) } ) + self._task: asyncio.Task | None = None def on(self) -> None: """Метод для включения LED""" @@ -27,9 +29,34 @@ class LedLineGpio: def off(self) -> None: """Метод для выключения LED""" - + self._request.set_value(self.line, Value.INACTIVE) + async def _blinking(self) -> None: + """Метод для мигания LED""" + + try: + while True: + self._request.set_value(self.line, Value.ACTIVE) + await asyncio.sleep(0.2) + self._request.set_value(self.line, Value.INACTIVE) + await asyncio.sleep(0.2) + except RequestReleasedError: + pass + + def start_blinking(self) -> None: + """Метод для добавления асинхронной задачи мигания LED""" + + if not self._task or self._task.done(): + self._task = asyncio.create_task(self._blinking()) + + def stop_blinking(self) -> None: + """Метод для остановки мигания LED""" + + if self._task: + self._task.cancel() + self._request.set_value(self.line, Value.INACTIVE) + def close(self) -> None: """Метод для освобождения ресурса""" @@ -107,7 +134,29 @@ class RobotControl: consumer=settings.gpio_lines.right_motor_consumer ) + self._led_indicator = LedLineGpio( + line=settings.gpio_lines.led_line, + gpio_path=settings.gpio_lines.gpio_path, + consumer=settings.gpio_lines.led_consumer + ) + self.stop() + self.ready_to_connect() + + def ready_to_connect(self) -> None: + """Готовность робота к подключению (индикация)""" + + self._led_indicator.start_blinking() + + def connected(self) -> None: + """Робот подключен (индикация)""" + + self._led_indicator.on() + + def blinking_off(self) -> None: + """Выключение моргания LED""" + + self._led_indicator.stop_blinking() def forward(self) -> None: """Движение робота вперёд""" @@ -124,15 +173,15 @@ class RobotControl: def left(self) -> None: """Поворот робота налево""" - self._right_motor.forward_motor() - self._left_motor.backward_motor() + self._right_motor.backward_motor() + self._left_motor.forward_motor() def right(self) -> None: """Поворот робота направо""" - self._right_motor.backward_motor() - self._left_motor.forward_motor() - + self._right_motor.forward_motor() + self._left_motor.backward_motor() + def stop(self) -> None: """Остановка робота""" @@ -144,3 +193,4 @@ class RobotControl: self._left_motor.close() self._right_motor.close() + self._led_indicator.close() diff --git a/robot_pi_service/robot_pi_service.py b/robot_pi_service/robot_pi_service.py index 554096f..9d029c7 100755 --- a/robot_pi_service/robot_pi_service.py +++ b/robot_pi_service/robot_pi_service.py @@ -1,4 +1,6 @@ import asyncio +from functools import partial +from gpiod import exception from socket import gethostbyname from websockets import serve, exceptions from websockets.legacy.server import WebSocketServerProtocol @@ -9,20 +11,25 @@ from gpio_control import RobotControl from response_data import FormResponse -async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None: +async def robot_control_gpio( + websocket: WebSocketServerProtocol, + robot_control: RobotControl, + commands_status: FormResponse +) -> None: """ Асинхронная функция для управлением gpio робота (через websocket) """ try: - robot_control = RobotControl() - commands_status = FormResponse - while True: try: command = await asyncio.wait_for(websocket.recv(), timeout=30.0) data = loads(command) command_name = data.get('command') + + if command_name != settings.commands_robot.stop: + robot_control.blinking_off() + robot_control.connected() except asyncio.TimeoutError as err: print('Таймаут ожидания команды от клиента') continue # продолжение цикла, чтобы не закрывать соединение @@ -41,6 +48,7 @@ async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None: robot_control.right() case settings.commands_robot.stop: robot_control.stop() + robot_control.ready_to_connect() case _: data.update(commands_status.NOT_FOUND_COMMAND.response) await websocket.send(message=dumps(data)) @@ -48,9 +56,9 @@ async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None: if settings.commands_robot.is_command(command=command_name): data.update(commands_status.OK_COMMAND.response) await websocket.send(message=dumps(data)) - except exceptions.ConnectionClosed: + except (exceptions.ConnectionClosed, exception.RequestReleasedError): pass - except (exceptions.ConnectionClosedOK, exceptions.InvalidMessage, exceptions.InvalidState) as err: + except (exceptions.ConnectionClosedOK, exceptions.InvalidMessage, exceptions.InvalidState, OSError) as err: message_err = f'{err.__class__.__name__}: {err}' print(message_err) data.update( @@ -60,8 +68,6 @@ async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None: ) ) await websocket.send(message=dumps(data)) - finally: - robot_control.close() async def start() -> None: @@ -69,13 +75,24 @@ async def start() -> None: print('Старт сервиса робота для приёма команд.') - async with serve( - handler=robot_control_gpio, - host=gethostbyname(settings.websocket_host), - port=settings.websocket_port - ): - # бесконечный цикл - await asyncio.Future() + try: + robot_control: RobotControl = RobotControl() + commands_status: FormResponse = FormResponse + + async with serve( + handler=partial(robot_control_gpio, robot_control=robot_control, commands_status=commands_status), + host=gethostbyname(settings.websocket_host), + port=settings.websocket_port + ): + # бесконечный цикл + await asyncio.Future() + except asyncio.CancelledError: + if robot_control is not None: + robot_control.blinking_off() + robot_control.close() + + # пробрасываем CancelledError, чтобы asyncio.run() всё корректно закрыл + raise def run_app() -> None: