Getting Started¶
Installation¶
Clone the repository and install dependencies with uv:
git clone https://github.com/zauberzeug/feldfreund_devkit.git
cd feldfreund_devkit
uv sync
Running the Example¶
The repository includes a minimal example in main.py that demonstrates:
- Robot simulation with keyboard control
- Straight line navigation automation
- Real-time 3D visualization
Start it with:
uv run main.py
Open http://localhost:8080 in your browser. Hold SHIFT and use the arrow keys to steer the robot, or use the automation controls to run a straight line navigation.
Understanding the Example¶
#! /usr/bin/env python
import rosys
from nicegui import app, ui
from rosys.automation import Automator, automation_controls
from rosys.driving import Driver, Steerer, keyboard_control, robot_object
import feldfreund_devkit
from feldfreund_devkit.config import FeldfreundConfiguration, Secrets, config_from_id
from feldfreund_devkit.implement import ImplementDummy
from feldfreund_devkit.navigation import (
RecordedTrackNavigation,
RecordedTrackProvider,
StraightLineNavigation,
TrackRecordingController,
WaypointNavigation,
)
class System(feldfreund_devkit.System):
def __init__(self, config: FeldfreundConfiguration, secrets: Secrets) -> None:
super().__init__(config, secrets=secrets)
self.steerer = Steerer(self.feldfreund.wheels, speed_scaling=0.25)
self.driver = Driver(self.feldfreund.wheels, self.odometer, parameters=self.config.driver)
self.shape = rosys.geometry.Prism.default_robot_shape()
self.automator = Automator(self.steerer, on_interrupt=self.feldfreund.stop, notify=False)
self.recorded_track_provider = RecordedTrackProvider().persistent()
self.track_recording_controller = TrackRecordingController(
self.recorded_track_provider, pose_provider=self.odometer, gnss=self.feldfreund.gnss)
common = {'implement': ImplementDummy(), 'driver': self.driver, 'pose_provider': self.odometer}
self.navigations: dict[str, WaypointNavigation] = {n.name: n for n in [
StraightLineNavigation(**common),
RecordedTrackNavigation(recorded_track_provider=self.recorded_track_provider,
track_recording_controller=self.track_recording_controller,
gnss=self.feldfreund.gnss,
automator=self.automator,
**common),
]}
self._current_navigation: WaypointNavigation = next(iter(self.navigations.values()))
self.automator.default_automation = self._current_navigation.start
@property
def current_navigation(self) -> WaypointNavigation:
return self._current_navigation
@current_navigation.setter
def current_navigation(self, navigation: WaypointNavigation) -> None:
self._current_navigation = navigation
self.automator.default_automation = navigation.start
def startup() -> None:
secrets = Secrets()
config = config_from_id('example', secrets=secrets)
system = System(config, secrets).persistent()
@ui.page('/')
def ui_content() -> None:
keyboard_control(system.steerer)
with ui.scene():
robot_object(system.shape, system.odometer)
@ui.refreshable
def navigation_settings() -> None:
system.current_navigation.settings_ui()
def select_navigation(name: str) -> None:
system.current_navigation = system.navigations[name]
navigation_settings.refresh()
with ui.card():
ui.label('hold SHIFT to steer with the keyboard arrow keys or use the automation controls')
ui.select(list(system.navigations), value=system.current_navigation.name, label='Navigation',
on_change=lambda e: select_navigation(e.value)).classes('w-64')
with ui.row():
navigation_settings()
with ui.row():
automation_controls(system.automator)
app.on_startup(startup)
ui.run(title='Feldfreund_devkit')
The System class extends feldfreund_devkit.System which initializes the robot hardware (or simulation) based on the configuration. Key components:
- config: Loaded from
config/example.pyviaconfig_from_id('example') - steerer: Manual steering control
- driver: Path-following driver for automations
- navigation:
StraightLineNavigationdrives forward for a configurable distance - automator: Manages automation lifecycle (play/pause/stop)
Configuration¶
Robot configurations live in the config/ directory. See config/example.py:
from rosys.vision import ImageSize
from feldfreund_devkit.config import (
BluetoothConfiguration,
BumperConfiguration,
CameraConfiguration,
FeldfreundConfiguration,
FlashlightConfiguration,
GnssConfiguration,
ImuConfiguration,
MjpegCameraConfig,
ODriveTracksConfiguration,
RobotBrainConfiguration,
Secrets,
UsbCameraConfig,
)
def build_config(secrets: Secrets) -> FeldfreundConfiguration:
return FeldfreundConfiguration(
robot_id='Example',
bluetooth=BluetoothConfiguration(name='example', pin_code=123456),
bumper=BumperConfiguration(pin_front_top=21, pin_front_bottom=35, pin_back=18),
cameras=CameraConfiguration(
main=UsbCameraConfig(camera_id='example-usb-0', image_size=ImageSize(width=1280, height=720), fps=10),
front=MjpegCameraConfig(camera_id='example-mac-4', image_size=ImageSize(width=1280, height=720),
password=secrets.MJPEG_CAMERA_PASSWORD),
back=MjpegCameraConfig(camera_id='example-mac-3', image_size=ImageSize(width=1280, height=720),
password=secrets.MJPEG_CAMERA_PASSWORD),
right=MjpegCameraConfig(camera_id='example-mac-1', image_size=ImageSize(width=1280, height=720),
password=secrets.MJPEG_CAMERA_PASSWORD),
left=MjpegCameraConfig(camera_id='example-mac-2', image_size=ImageSize(width=1280, height=720),
password=secrets.MJPEG_CAMERA_PASSWORD),
),
flashlight=FlashlightConfiguration(),
gnss=GnssConfiguration(),
implement=None,
imu=ImuConfiguration(),
robot_brain=RobotBrainConfiguration(name='rbexample', nand=True),
wheels=ODriveTracksConfiguration(is_left_reversed=True,
is_right_reversed=False,
left_back_can_address=0x000,
left_front_can_address=0x100,
right_back_can_address=0x200,
right_front_can_address=0x300,
odrive_version=6),
)
In simulation mode (when no hardware is detected), mock implementations are used automatically.
Next Steps¶
- Browse the Module Reference in the navigation for API documentation
- Check the Tutorials for hardware calibration guides
- See Troubleshooting for common issues