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:
21
.env.exaple
Normal file
21
.env.exaple
Normal 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=идентификатор клиента для правого
|
||||||
@@ -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()
|
||||||
|
|||||||
27
robot_pi_service/response_data.py
Normal file
27
robot_pi_service/response_data.py
Normal 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
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user