Skip to content

Hardware

The other examples use simulated hardware for simplicity and easy execution on any development system. To be able to control real hardware we recommend to derive a Simulation and Hardware version from a shared interface. Depending on your environment you can then instantiate the correct implementation without bothering with it in the rest of your code.

Custom Implementation

For a differential-steering controlled robot, RoSys offers a Wheels base class plus a WheelsSimulation. The following example illustrates how to implement a CustomWheelsHardware module that derives from Wheels, reads the currrent velocity regularly and can be steered with linear and angular velocity.

#!/usr/bin/env python3
from nicegui import ui

import rosys


class CustomWheelsHardware(rosys.hardware.Wheels):

    def __init__(self) -> None:
        super().__init__()
        rosys.on_repeat(self.read_current_velocity, 0.01)

    async def drive(self, linear: float, angular: float) -> None:
        await super().drive(linear, angular)
        # TODO send hardware command to drive with given linear and angular velocity

    async def stop(self) -> None:
        await super().stop()
        # TODO send hardware command to stop the wheels

    async def read_current_velocity(self) -> None:
        velocities: list[rosys.geometry.Velocity] = []
        # TODO: read measured velocities from the hardware
        self.VELOCITY_MEASURED.emit(velocities)


try:
    wheels = CustomWheelsHardware()
    robot = rosys.hardware.Robot([wheels])
except Exception:
    wheels = rosys.hardware.WheelsSimulation()
    robot = rosys.hardware.RobotSimulation([wheels])
odometer = rosys.driving.Odometer(wheels)
steerer = rosys.driving.Steerer(wheels)

rosys.driving.keyboard_control(steerer)
rosys.driving.joystick(steerer)
ui.label().bind_text_from(wheels, 'linear_target_speed', lambda l: f'Linear: {l:.2f} m/s')
ui.label().bind_text_from(wheels, 'angular_target_speed', lambda a: f'Angular: {a:.2f} rad/s')

ui.run(title='RoSys')

Depending on your hardware you may need to modify a PWM signal, send commands via CAN bus or serial, use Protobuf over Ethernet or something else. By raising an exception if the real hardware is not available, a simulated robot is instantiated instead. The robot can be controlled by keyboard or joystick.

Robot Brain

The Zauberzeug Robot Brain is an industrial-grade controller which combines artificial intelligence with machinery. It has a built-in ESP32 microcontroller with Lizard installed to do the actual hardware communication in realtime.

Serial communication is used to send and receive messages between the built-in NVidia Jetson and the microcontroller. You can call SerialCommunication.is_possible() to automatically switch between simulation and real hardware. The module WheelsHardware expects a RobotBrain, which controls the SerialCommunication with the microcontroller.

#!/usr/bin/env python3
from nicegui import ui

from rosys.driving import Odometer, Steerer, joystick, keyboard_control
from rosys.hardware import (
    CanHardware,
    RobotBrain,
    RobotHardware,
    RobotSimulation,
    SerialCommunication,
    WheelsHardware,
    WheelsSimulation,
    communication,
)

is_real = SerialCommunication.is_possible()
if is_real:
    communication = SerialCommunication()
    robot_brain = RobotBrain(communication)
    can = CanHardware(robot_brain)
    wheels = WheelsHardware(robot_brain, can=can)
    robot = RobotHardware([can, wheels], robot_brain)
else:
    wheels = WheelsSimulation()
    robot = RobotSimulation([wheels])
odometer = Odometer(wheels)
steerer = Steerer(wheels)

keyboard_control(steerer)
joystick(steerer)

if is_real:
    communication.debug_ui()
    robot_brain.developer_ui()

ui.run(title='RoSys')

With communication.debug_ui() you can add some helpful UI elements for debugging the serial communication. Furthermore, with robot_brain.developer_ui() you can add UI elements to configure and reboot Lizard.

The Lizard configuration for a differential-steering controlled robot with an ODrive might look as follows:

can = Can(32, 33, 1000000)

l = ODriveMotor(can, 0x000)
r = ODriveMotor(can, 0x100)
l.m_per_tick = 0.0627
r.m_per_tick = 0.0627

wheels = ODriveWheels(l, r)
wheels.width = 0.515

core.output("core.millis wheels.linear_speed:3 wheels.angular_speed:3")