De noodrem
VERPLICHT LEZEN!!!
Op de Amiga zit een prachtige grote rode knop. Dit is de noodrem. De ‘noodrem’ REMT de robot niet, maar zorgt ervoor dat de motoren uitvallen. Dit betekent dat je de robot op de noodrem nog gewoon vooruit kan duwen, en dat als je op een heuvel staat de robot ervandoor gaat. Pas dus op!
Zet de noodrem er ALTIJD op als:
- Je van de robot wegloopt.
- Je de robot niet hoeft te laden rijden.
Voer ook nooit code gerelateerd aan de motoren uit als je niet fysiek in de buurt bent van de robot. De Amiga is geen speelgoed.
De E-Stop
Je zal opmerken dat je, ondanks het van de noodrem afhalen, niet altijd met de robot kan rijden. Dit komt door de ‘e-stop’, een elektronische beveiliging. Deze kan je eraf halen door op het kleine schermpje op de robot op enkele knopjes te drukken.
Als je het volgende ziet kan je nog niet rijden, en moet je op de ‘auto control’ knop drukken:

Als je het volgende ziet kan je rijden:

Tevens zie je op het dashboard rechtsboven dan ook ‘auto’ staan:

De ‘pendant’
De ‘pendant’ is de zwarte controller die bij de intelligence kit van de Amiga hoort. Met de pendant kan je de robot handmatig besturen:

Mocht je het nodig hebben kan je de staat van de pendant ook uitlezen in code. Zie hier het voorbeeld van hoe je dat kan doen.
Code
Om motoren uit te voeren moet je Twist2D berichten sturen naar de canbus. Hierin specificeer je:
- de gewenste beweging vooruit/achteruit in meter per seconde.
- de gewenste rotatie linksom/rechtsom in radialen per seconde.
Deze berichten moet je blijven sturen; als je enkel eenmaal een bericht stuurt zal de robot schokken en stoppen.
Hier staat een voorbeeld van Farm-ng, maar ik heb voor jullie al een kant-en-klare taak gemaakt die past bij onze custom template.
De IDriveHandler:
from abc import ABC, abstractmethod
class IDriveHandler(ABC):
@abstractmethod
async def forward(self, speed: float):
"""Sets the moving speed of the Amiga to the specified speed in m/s forward."""
pass
@abstractmethod
async def backward(self, speed: float):
"""Sets the moving speed of the Amiga to the specified speed in m/s backwards."""
pass
@abstractmethod
async def turn_left(self, rad: float):
"""Sets the turning speed of the Amiga to the specified speed in rad/s left."""
pass
@abstractmethod
async def turn_right(self, rad: float):
"""Sets the turning speed of the Amiga to the specified speed in rad/s right."""
pass
@abstractmethod
async def stop(self):
"""Stops the entire process of driving by setting both linear- and angular velocity to 0."""
passDe DriveHandler:
from pathlib import Path
from farm_ng.canbus.canbus_pb2 import Twist2d
from farm_ng.core.event_client import EventClient
from farm_ng.core.event_service_pb2 import EventServiceConfig
from farm_ng.core.events_file_reader import proto_from_json_file
from driver.IDriveHandler import IDriveHandler
from logger.ILogger import ILogger
from system.platform.EPlatform import EPlatform
from system.platform.IPlatformHandler import IPlatformHandler
class DriveHandler(IDriveHandler):
# NOTE: be careful with these values, they are in m/s and rad/s
DEFAULT_LINEAR_VELOCITY_MPS = 0.1
DEFAULT_ANGULAR_VELOCITY_RPS = 0.1
MAX_LINEAR_VELOCITY = 0.5
MAX_ANGULAR_VELOCITY = 0.5
def __init__(self, logger: ILogger, platform_handler: IPlatformHandler):
self.logger = logger
self.platform_handler = platform_handler
if self.platform_handler.get_current() == EPlatform.ROBOT:
service_config_path = Path() / 'src' / 'res' / 'service_config.json'
self.config: EventServiceConfig = proto_from_json_file(service_config_path, EventServiceConfig())
self.client: EventClient = EventClient(self.config)
async def _send_update(self, twist: Twist2d):
self.logger.info(f"Sending linear velocity: {twist.linear_velocity_x:.3f}, angular velocity: {twist.angular_velocity:.3f}")
await self.client.request_reply("/twist", twist)
async def _set_drive_values(self, linear_velocity_x: float, linear_velocity_y: float, angular_velocity: float):
if self.platform_handler.get_current() == EPlatform.ROBOT:
twist = Twist2d()
twist.linear_velocity_x = linear_velocity_x
twist.linear_velocity_y = linear_velocity_y
twist.angular_velocity = angular_velocity
await self._send_update(twist)
else:
self.logger.error(f"Could not set drive values because on platform {self.platform_handler.get_current()}")
async def forward(self, speed: float = DEFAULT_LINEAR_VELOCITY_MPS):
assert 0.0 < speed < DriveHandler.MAX_LINEAR_VELOCITY
await self._set_drive_values(speed, 0.0, 0.0)
async def backward(self, speed: float = DEFAULT_LINEAR_VELOCITY_MPS):
assert 0.0 < speed < DriveHandler.MAX_LINEAR_VELOCITY
await self._set_drive_values(-speed, 0.0, 0.0)
async def turn_left(self, rad: float = DEFAULT_ANGULAR_VELOCITY_RPS):
assert 0.0 < rad < DriveHandler.MAX_ANGULAR_VELOCITY
await self._set_drive_values(0.0, 0.0, rad)
async def turn_right(self, rad: float = DEFAULT_ANGULAR_VELOCITY_RPS):
assert 0.0 < rad < DriveHandler.MAX_ANGULAR_VELOCITY
await self._set_drive_values(0.0, 0.0, -rad)
async def stop(self):
await self._set_drive_values(0.0, 0.0, 0.0)De EDriveState:
from enum import IntEnum
class EDriveState(IntEnum):
STOP = 1,
FORWARD = 2,
BACKWARDS = 3,
LEFT = 4,
RIGHT = 5De DriveTask:
from typing import Optional
from driver.EDriveState import EDriveState
from driver.IDriveHandler import IDriveHandler
from system.threading.IThreadHandler import IThreadHandler
from tasks.BaseTask import BaseTask
class DriveTask(BaseTask):
drive_state: EDriveState = Optional[EDriveState] # We break on start for safety! :)
DEFAULT_SPEED = 0.1
def __init__(self, thread_handler: IThreadHandler, drive_handler: IDriveHandler):
super().__init__(thread_handler)
self.drive_handler = drive_handler
def can_run(self) -> bool:
return True
async def act(self) -> None:
if DriveTask.drive_state is not None:
if self.drive_state == EDriveState.STOP:
await self.drive_handler.stop()
elif self.drive_state == EDriveState.FORWARD:
await self.drive_handler.forward(DriveTask.DEFAULT_SPEED)
elif self.drive_state == EDriveState.LEFT:
await self.drive_handler.turn_left(DriveTask.DEFAULT_SPEED)
elif self.drive_state == EDriveState.BACKWARDS:
await self.drive_handler.backward(DriveTask.DEFAULT_SPEED)
elif self.drive_state == EDriveState.RIGHT:
await self.drive_handler.turn_right(DriveTask.DEFAULT_SPEED)
await self.thread_handler.sleep(0.02)
@property
def name(self) -> str:
return 'Drive'Een voorbeeld van hoe je de robot nu vooruit kan laten rijden:
def act_drive_forward(self):
DriveTask.drive_state = EDriveState.FORWARDService config
Zoals je in bovenstaande code (en in het voorbeeld van Farm-ng) ziet moet je een service_config.json bestandje toevoegen in je res mapje. Dit is hier de inhoud van:
{
"name": "canbus",
"port": 6001,
"host": "localhost",
"log_level": "INFO"
}Wiskunde
Voor de mensen die niet meer weten wat ‘radian’ inhoud:
En hier een uitleg over hoe je in Python kan rekenen met graden van en naar radialen:
| Methode | Wat doet het? |
|---|---|
| math.radians(x) | Converts degrees to radians |
| math.degrees(x) | Converts radians to degrees |