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

feat: added classes for work motors, added class FormResponse, added …
This commit was merged in pull request #3.
This commit is contained in:
Eugene
2025-09-16 09:58:00 +03:00
committed by GitHub
5 changed files with 225 additions and 39 deletions

21
.env.exaple Normal file
View File

@@ -0,0 +1,21 @@
# Настройки для websocket
WEBSOCKET_HOST=хост
WEBSOCKET_PORT=порт
# Команды
FORWARD=вперёд
BACKWARD=назад
LEFT=влево
RIGHT=вправо
STOP=стоп
# Настройки для линий GPIO
GPIO_PATH=путь до gpio
LED_LINE=номер линии
LED_CONSUMER=идентификатор клиента
LEFT_MOTOR_LINE_IN1=номер линии для левого (вращение вперёд)
LEFT_MOTOR_LINE_IN2=номер линии для левого (вращение назад)
LEFT_MOTOR_CONSUMER=идентификатор клиента для левого
RIGHT_MOTOR_LINE_IN1=номер линии для правого (вращение вперёд)
RIGHT_MOTOR_LINE_IN2=номер линии для правого (вращение назад)
RIGHT_MOTOR_CONSUMER=идентификатор клиента для правого

View File

@@ -1,17 +1,19 @@
from gpiod import request_lines, LineSettings from gpiod import request_lines, LineSettings
from gpiod.line import Direction, Value from gpiod.line import Direction, Value
from settings import settings
class LedLineGpio: class LedLineGpio:
"""Класс для управления динией gpio для LED""" """Класс для управления линией gpio для LED"""
def __init__(self, line: int) -> None: def __init__(self, line: int, gpio_path: str, consumer: str) -> None:
self.line = line self.line = line
self._request = request_lines( self._request = request_lines(
'/dev/gpiochip0', path=gpio_path,
consumer='led-blinker', consumer=consumer,
config={ config={
line: LineSettings( self.line: LineSettings(
direction=Direction.OUTPUT, direction=Direction.OUTPUT,
output_value=Value.INACTIVE output_value=Value.INACTIVE
) )
@@ -32,3 +34,113 @@ class LedLineGpio:
"""Метод для освобождения ресурса""" """Метод для освобождения ресурса"""
self._request.release() self._request.release()
class MotorDCLineGpio:
"""Класс для управления линией GPIO для DC мотора"""
def __init__(self, line_in1: int, line_in2: int, gpio_path: str, consumer: str) -> None:
self.line_in1 = line_in1
self._request_in1 = request_lines(
path=gpio_path,
consumer=consumer,
config={
self.line_in1: LineSettings(
direction=Direction.OUTPUT,
output_value=Value.INACTIVE
)
}
)
self.line_in2 = line_in2
self._request_in2 = request_lines(
path=gpio_path,
consumer=consumer,
config={
self.line_in2: LineSettings(
direction=Direction.OUTPUT,
output_value=Value.INACTIVE
)
}
)
def forward_motor(self) -> None:
"""Метод для вращения мотора вперёд"""
self._request_in1.set_value(self.line_in1, Value.ACTIVE)
self._request_in2.set_value(self.line_in2, Value.INACTIVE)
def backward_motor(self) -> None:
"""Метод для вращения мотора назад"""
self._request_in1.set_value(self.line_in1, Value.INACTIVE)
self._request_in2.set_value(self.line_in2, Value.ACTIVE)
def stop_motor(self) -> None:
"""Метод для остановки мотора"""
self._request_in1.set_value(self.line_in1, Value.INACTIVE)
self._request_in2.set_value(self.line_in2, Value.INACTIVE)
def close(self) -> None:
"""Метод для освобождения ресурса"""
self._request_in1.release()
self._request_in2.release()
class RobotControl:
"""Класс для управления роботом"""
def __init__(self) -> None:
self._left_motor = MotorDCLineGpio(
line_in1=settings.gpio_lines.left_motor_line_in1,
line_in2=settings.gpio_lines.left_motor_line_in2,
gpio_path=settings.gpio_lines.gpio_path,
consumer=settings.gpio_lines.left_motor_consumer
)
self._right_motor = MotorDCLineGpio(
line_in1=settings.gpio_lines.right_motor_line_in1,
line_in2=settings.gpio_lines.right_motor_line_in2,
gpio_path=settings.gpio_lines.gpio_path,
consumer=settings.gpio_lines.right_motor_consumer
)
self.stop()
def forward(self) -> None:
"""Движение робота вперёд"""
self._left_motor.forward_motor()
self._right_motor.forward_motor()
def backward(self) -> None:
"""Движение робота назад"""
self._left_motor.backward_motor()
self._right_motor.backward_motor()
def left(self) -> None:
"""Поворот робота налево"""
self._right_motor.forward_motor()
self._left_motor.backward_motor()
def right(self) -> None:
"""Поворот робота направо"""
self._right_motor.backward_motor()
self._left_motor.forward_motor()
def stop(self) -> None:
"""Остановка робота"""
self._left_motor.stop_motor()
self._right_motor.stop_motor()
def close(self) -> None:
"""Освобождение ресурса"""
self._left_motor.close()
self._right_motor.close()

View File

@@ -0,0 +1,27 @@
from enum import Enum
class FormResponse(Enum):
"""Класс для формирования ответа"""
NOT_FOUND_COMMAND = (404, 'Unknown command!')
OK_COMMAND = (200, 'Command executed')
SERVER_ERR = (500, 'Internal Server Error!')
@property
def response(self) -> dict[str, int | str]:
"""Словарь для формирования ответа серверу"""
return {
'status': self.value[0],
'message': self.value[1]
}
def get_response_err(self, name_err: str, message_err: str) -> dict[str, int | str]:
"""Метод для формирования ответа серверу с ошибкой"""
return {
'status': self.value[0],
'name_error': name_err,
'message': message_err
}

View File

@@ -1,79 +1,84 @@
import asyncio import asyncio
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
from json import loads, JSONDecodeError from json import loads, dumps, JSONDecodeError
from settings import settings from settings import settings
from gpio_control import LedLineGpio from gpio_control import RobotControl
from response_data import FormResponse
async def robot_control_gpio(websocket: WebSocketServerProtocol): async def robot_control_gpio(websocket: WebSocketServerProtocol) -> None:
""" """
Асинхронная функция для управлением gpio робота (через websocket) Асинхронная функция для управлением gpio робота (через websocket)
""" """
try: try:
led_line_gpio = LedLineGpio(line=6) robot_control = RobotControl()
action = None 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')
except asyncio.TimeoutError as err: except asyncio.TimeoutError as err:
print('Таймаут ожидания команды от клиента') print('Таймаут ожидания команды от клиента')
continue # продолжение цикла, чтобы не закрывать соединение continue # продолжение цикла, чтобы не закрывать соединение
except JSONDecodeError: except JSONDecodeError:
print("Ошибка при декодировании JSON") print('Ошибка при декодировании JSON!')
continue continue
match next(iter(data), None): match command_name:
case settings.commands_robot.forward: case settings.commands_robot.forward:
if data.get(settings.commands_robot.forward): robot_control.forward()
action = 'Робот едет вперёд'
led_line_gpio.on()
else:
action = 'Команда вперёд стоп'
led_line_gpio.off()
print(action)
await websocket.send(message=action)
case settings.commands_robot.backward: case settings.commands_robot.backward:
action = 'Робот едет назад' robot_control.backward()
print(action)
await websocket.send(message=action)
case settings.commands_robot.left: case settings.commands_robot.left:
action = 'Робот едет налево' robot_control.left()
print(action)
await websocket.send(message=action)
case settings.commands_robot.right: case settings.commands_robot.right:
action = 'Робот едет направо' robot_control.right()
print(action) case settings.commands_robot.stop:
await websocket.send(message=action) robot_control.stop()
case _:
data.update(commands_status.NOT_FOUND_COMMAND.response)
await websocket.send(message=dumps(data))
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:
pass pass
except (exceptions.ConnectionClosedOK, exceptions.InvalidMessage, exceptions.InvalidState) as err: except (exceptions.ConnectionClosedOK, exceptions.InvalidMessage, exceptions.InvalidState) as err:
message_err = f'{err.__class__.__name__}: {err}' message_err = f'{err.__class__.__name__}: {err}'
print(message_err) print(message_err)
await websocket.send(message=message_err) data.update(
except Exception as err: commands_status.SERVER_ERR.get_response_err(
message_err = f'{err.__class__.__name__}: {err}' name_err=err.__class__.__name__,
print(message_err) message_err=err
await websocket.send(message=message_err) )
)
await websocket.send(message=dumps(data))
finally: finally:
led_line_gpio.close() robot_control.close()
async def start(): async def start() -> None:
"""Асинхронная функция запуска вебсокета и бесконечного цикла""" """Асинхронная функция запуска вебсокета и бесконечного цикла"""
print('Старт сервиса робота для приёма команд.') print('Старт сервиса робота для приёма команд.')
async with serve(handler=robot_control_gpio, host=settings.websocket_host, port=settings.websocket_port): async with serve(
handler=robot_control_gpio,
host=gethostbyname(settings.websocket_host),
port=settings.websocket_port
):
# бесконечный цикл # бесконечный цикл
await asyncio.Future() await asyncio.Future()
def run_app(): def run_app() -> None:
"""Функция старата приложения""" """Функция старата приложения"""
try: try:

View File

@@ -18,6 +18,26 @@ class CommandsRobot(ModelConfig):
backward: str backward: str
left: str left: str
right: str right: str
stop: str
def is_command(self, command: str) -> bool:
"""Есть ли команда в командах"""
return command in self.model_dump().values()
class GpioLines(ModelConfig):
"""Класс для линий GPIO"""
gpio_path: str
led_line: int
led_consumer: str
left_motor_line_in1: int
left_motor_line_in2: int
left_motor_consumer: str
right_motor_line_in1: int
right_motor_line_in2: int
right_motor_consumer: str
class Settings(ModelConfig): class Settings(ModelConfig):
@@ -26,6 +46,7 @@ class Settings(ModelConfig):
websocket_host: str websocket_host: str
websocket_port: int websocket_port: int
commands_robot: CommandsRobot = CommandsRobot() commands_robot: CommandsRobot = CommandsRobot()
gpio_lines: GpioLines = GpioLines()
settings = Settings() settings = Settings()