Merge pull request #5 from Arduinum/arduinum/service-orange-pi-mvp-1

Arduinum/service orange pi mvp 1
This commit was merged in pull request #5.
This commit is contained in:
Eugene
2025-10-22 12:19:12 +03:00
committed by GitHub
3 changed files with 90 additions and 23 deletions

View File

@@ -2,7 +2,7 @@
WEBSOCKET_HOST=хост WEBSOCKET_HOST=хост
WEBSOCKET_PORT=порт WEBSOCKET_PORT=порт
# Команды # Команды робота
FORWARD=вперёд FORWARD=вперёд
BACKWARD=назад BACKWARD=назад
LEFT=влево LEFT=влево

View File

@@ -1,5 +1,6 @@
from gpiod import request_lines, LineSettings from gpiod import request_lines, LineSettings, RequestReleasedError
from gpiod.line import Direction, Value from gpiod.line import Direction, Value
import asyncio
from settings import settings from settings import settings
@@ -19,6 +20,7 @@ class LedLineGpio:
) )
} }
) )
self._task: asyncio.Task | None = None
def on(self) -> None: def on(self) -> None:
"""Метод для включения LED""" """Метод для включения LED"""
@@ -27,9 +29,34 @@ class LedLineGpio:
def off(self) -> None: def off(self) -> None:
"""Метод для выключения LED""" """Метод для выключения LED"""
self._request.set_value(self.line, Value.INACTIVE) 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: def close(self) -> None:
"""Метод для освобождения ресурса""" """Метод для освобождения ресурса"""
@@ -107,7 +134,29 @@ class RobotControl:
consumer=settings.gpio_lines.right_motor_consumer 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.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: def forward(self) -> None:
"""Движение робота вперёд""" """Движение робота вперёд"""
@@ -124,15 +173,15 @@ class RobotControl:
def left(self) -> None: def left(self) -> None:
"""Поворот робота налево""" """Поворот робота налево"""
self._right_motor.forward_motor() self._right_motor.backward_motor()
self._left_motor.backward_motor() self._left_motor.forward_motor()
def right(self) -> None: def right(self) -> None:
"""Поворот робота направо""" """Поворот робота направо"""
self._right_motor.backward_motor() self._right_motor.forward_motor()
self._left_motor.forward_motor() self._left_motor.backward_motor()
def stop(self) -> None: def stop(self) -> None:
"""Остановка робота""" """Остановка робота"""
@@ -144,3 +193,4 @@ class RobotControl:
self._left_motor.close() self._left_motor.close()
self._right_motor.close() self._right_motor.close()
self._led_indicator.close()

View File

@@ -1,4 +1,6 @@
import asyncio import asyncio
from functools import partial
from gpiod import exception
from socket import gethostbyname from socket import gethostbyname
from websockets import serve, exceptions from websockets import serve, exceptions
from websockets.legacy.server import WebSocketServerProtocol from websockets.legacy.server import WebSocketServerProtocol
@@ -9,20 +11,25 @@ from gpio_control import RobotControl
from response_data import FormResponse 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) Асинхронная функция для управлением gpio робота (через websocket)
""" """
try: try:
robot_control = RobotControl()
commands_status = FormResponse
while True: while True:
try: try:
command = await asyncio.wait_for(websocket.recv(), timeout=30.0) command = await asyncio.wait_for(websocket.recv(), timeout=30.0)
data = loads(command) data = loads(command)
command_name = data.get('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: except asyncio.TimeoutError as err:
print('Таймаут ожидания команды от клиента') print('Таймаут ожидания команды от клиента')
continue # продолжение цикла, чтобы не закрывать соединение continue # продолжение цикла, чтобы не закрывать соединение
@@ -41,6 +48,7 @@ async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None:
robot_control.right() robot_control.right()
case settings.commands_robot.stop: case settings.commands_robot.stop:
robot_control.stop() robot_control.stop()
robot_control.ready_to_connect()
case _: case _:
data.update(commands_status.NOT_FOUND_COMMAND.response) data.update(commands_status.NOT_FOUND_COMMAND.response)
await websocket.send(message=dumps(data)) 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): if settings.commands_robot.is_command(command=command_name):
data.update(commands_status.OK_COMMAND.response) data.update(commands_status.OK_COMMAND.response)
await websocket.send(message=dumps(data)) await websocket.send(message=dumps(data))
except exceptions.ConnectionClosed: except (exceptions.ConnectionClosed, exception.RequestReleasedError):
pass 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}' message_err = f'{err.__class__.__name__}: {err}'
print(message_err) print(message_err)
data.update( data.update(
@@ -60,8 +68,6 @@ async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None:
) )
) )
await websocket.send(message=dumps(data)) await websocket.send(message=dumps(data))
finally:
robot_control.close()
async def start() -> None: async def start() -> None:
@@ -69,13 +75,24 @@ async def start() -> None:
print('Старт сервиса робота для приёма команд.') print('Старт сервиса робота для приёма команд.')
async with serve( try:
handler=robot_control_gpio, robot_control: RobotControl = RobotControl()
host=gethostbyname(settings.websocket_host), commands_status: FormResponse = FormResponse
port=settings.websocket_port
): async with serve(
# бесконечный цикл handler=partial(robot_control_gpio, robot_control=robot_control, commands_status=commands_status),
await asyncio.Future() 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: def run_app() -> None: