From c95d942fc4a6c14831b18041d97b36ce42842629 Mon Sep 17 00:00:00 2001 From: Julien Denizot Date: Sat, 3 Oct 2026 18:09:28 +0200 Subject: [PATCH] chore: establish Enuxia Vacuum project identity --- AGENTS.md | 667 +++++++++++++++++++++++++++++++++++++++ ATTRIBUTION.md | 53 ++++ CLAUDE.md | 507 +++++------------------------- README.md | 510 +++++++++++++++++++----------- dhruva-slam/README.md | 493 +++++++---------------------- drishti/README.md | 470 ++++----------------------- sangam-io/README.md | 715 ++++++------------------------------------ 7 files changed, 1400 insertions(+), 2015 deletions(-) create mode 100644 AGENTS.md create mode 100644 ATTRIBUTION.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c4cf09f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,667 @@ +# AGENTS.md + +# Enuxia Vacuum — Repository Guidance + +This repository is maintained as **Enuxia Vacuum**, an independent open-source fork of +[VacuumTiger](https://github.com/codetiger/VacuumTiger). + +The upstream project provided the original software foundation. Enuxia Vacuum preserves +that history and attribution while developing its own technical direction, architecture +and roadmap. + +This file is the main repository guidance for coding agents and automated contributors. + +--- + +## 1. Project intent + +Enuxia Vacuum is a local-first robotics stack for autonomous vacuum robots. + +The project is not intended to merely reproduce proprietary factory behavior. Its goal is +to provide an understandable, modifiable and self-hostable robotics platform with: + +- low-level hardware control; +- local telemetry; +- mapping and localization; +- robust autonomous navigation; +- cleaning coverage planning; +- docking; +- local APIs; +- engineering diagnostics; +- future user-facing PWA / Android clients. + +The primary development platform is currently the **Proscenic M6 Pro / 3irobotix CRL-200S**. + +--- + +## 2. Upstream provenance + +Enuxia Vacuum is derived from the open-source VacuumTiger project. + +Preserve: + +- upstream Git history; +- copyright notices; +- applicable license notices; +- attribution for upstream-originated code; +- component-specific licensing metadata. + +Do not rewrite history to imply that upstream code was originally authored by Enuxia. + +When making changes, Git history should remain the authoritative record of authorship and +evolution. + +--- + +## 3. Architecture + +The project is being progressively reorganized around clear subsystem boundaries. + +```text +User applications +PWA / Android / diagnostics + | + v +Mission Manager +map / clean / goto / dock + | + +-------------------+ + | | + v v +Explorer Cleaning Planner + \ / + \ / + v v + Global Goal + | + v + Global Planner + Theta* + | + v + Global Path + | + v + Layered Local Costmap + | + v + Local Planner + DWA + | + v + Velocity Commands + | + v + SangamIO + | + v + Robot +``` + +### Main responsibilities + +#### SangamIO + +Hardware abstraction and robot-specific drivers. + +Responsibilities: + +- MCU protocol handling; +- LiDAR communication; +- sensor telemetry; +- drive control; +- vacuum and brush actuators; +- platform initialization; +- robot-independent command/telemetry interface. + +Higher-level code must not depend directly on CRL-200S / GD32 protocol details when the +same information can be obtained through SangamIO. + +#### DhruvaSLAM + +Mapping and localization backend. + +Responsibilities include: + +- odometry; +- LiDAR preprocessing; +- scan matching; +- occupancy-grid mapping; +- submaps; +- keyframes; +- loop closure; +- pose-graph optimization; +- map persistence and publication. + +The current repository also contains legacy navigation/exploration logic in DhruvaSLAM. +That logic is a baseline, not the target architecture. + +#### Navigation V2 + +Navigation V2 is the next major architecture. + +It should separate: + +- global planning; +- local planning; +- obstacle handling; +- progress detection; +- stuck detection; +- recovery; +- exploration. + +New navigation development should move toward this architecture rather than increasing +coupling inside the legacy navigator. + +#### Drishti + +Engineering and diagnostic UI. + +It is intended for: + +- robot telemetry; +- LiDAR visualization; +- SLAM visualization; +- navigation debugging; +- actuator testing; +- development diagnostics. + +Drishti is not the final end-user application. + +--- + +## 4. Core architecture rules + +### Navigation is not SLAM + +SLAM answers: + +> Where is the robot, and what does the environment look like? + +Navigation answers: + +> How should the robot move safely from its current pose to a requested goal? + +Do not merge navigation policy into the SLAM core unless a true mapping/localization +dependency requires it. + +### Exploration is not navigation + +Exploration chooses **where to go next**. + +Navigation decides **how to get there**. + +The Explorer should provide a goal or approach pose to Navigation and receive a result +such as: + +```text +Reached +Unreachable +Blocked +Cancelled +SafetyStop +``` + +Navigation should not need to understand frontier semantics. + +### Global planning is not local control + +The global planner produces a path through the known map. + +The local planner is responsible for: + +- short-horizon trajectory selection; +- dynamic obstacle response; +- collision checking; +- robot footprint; +- clearance; +- immediate velocity commands. + +Do not use a global A* / Theta* planner as a direct wheel controller. + +### Mapping is not cleaning + +Mapping discovers and models the environment. + +Cleaning uses an existing map to cover floor space efficiently. + +Future cleaning should use room segmentation and coverage planning rather than frontier +exploration as the cleaning strategy. + +--- + +## 5. Navigation V2 direction + +Target flow: + +```text +Goal + | + v +Global Planner (Theta*) + | + v +Global Path + | + v +Layered Local Costmap + | + v +Local Planner (DWA) + | + v +cmd_vel +``` + +The local costmap should eventually combine: + +```text +Static SLAM map +Live LiDAR obstacles +Bumper/contact observations +Cliff observations +Virtual walls / no-go zones +Robot footprint +Inflation / clearance costs +``` + +### Recovery + +Recovery must be based on **measured robot motion**, not only elapsed time. + +Avoid logic equivalent to: + +```text +reverse for 1.5 seconds +then assume recovery succeeded +``` + +Prefer: + +```text +contact + -> stop + -> record obstacle/contact + -> backup + -> confirm actual encoder/pose displacement + -> choose clear direction + -> rotate + -> verify clearance + -> resume / replan +``` + +Repeated bumper hits in the same small region should influence future local navigation +instead of causing the robot to retry the same approach indefinitely. + +--- + +## 6. CRL-200S / M6 Pro hardware facts + +Current validated development platform: + +```text +Robot Proscenic M6 Pro / 3irobotix CRL-200S +SoC Allwinner A33 +MCU GD32F103 +LiDAR Delta-2D +MCU UART /dev/ttyS3 +LiDAR UART /dev/ttyS1 +``` + +Validated Enuxia fork work includes: + +- GD32 cold-boot initialization fixes; +- corrected Delta-2D angular decoding; +- stable full-revolution LiDAR scans; +- hardware telemetry; +- drive and actuator control; +- bumper persistence into SLAM state; +- SLAM pose-graph / submap corrections; +- navigation and frontier failure handling improvements. + +Do not regress these areas when restructuring the project. + +--- + +## 7. Hardware ownership and process safety + +On hardware where the factory controller and SangamIO compete for the same MCU/UART or +actuators, they must not run simultaneously. + +For the CRL-200S development setup: + +- SangamIO is the active hardware daemon; +- the original factory hardware controller must remain inactive during SangamIO use. + +Never introduce code or documentation that casually starts both hardware stacks. + +--- + +## 8. Physical safety + +This repository controls physical machinery. + +For motion-related work: + +- assume experimental code can fail; +- supervise tests; +- keep a reliable stop path available; +- test away from stairs and drops; +- do not disable cliff protection for convenience; +- distinguish bumper recovery from cliff recovery; +- do not blindly reverse after a cliff event; +- fail safely when motion feedback disagrees with commanded motion. + +Safety-critical behavior should be explicit and testable. + +--- + +## 9. Development style + +Prefer small, reviewable changes. + +Do not mix unrelated changes such as: + +- hardware protocol changes; +- SLAM changes; +- navigation tuning; +- UI changes; + +inside the same commit unless the change genuinely spans those layers. + +### Recommended workflow + +```text +1. reproduce a problem +2. identify one hypothesis +3. change one subsystem +4. build / test +5. run a supervised hardware test if required +6. inspect logs +7. commit the validated change +``` + +Avoid broad parameter tuning while fixing a logic bug. + +--- + +## 10. Rust checks + +For Rust components, prefer running: + +```bash +cargo fmt --check +cargo test +cargo build --release +``` + +Use `cargo clippy` when appropriate. + +Do not silence warnings by default if the warning points to a real design issue. + +--- + +## 11. Public repository hygiene + +This repository is intended to be public. + +Never commit: + +- Wi-Fi credentials; +- passwords; +- private keys; +- API tokens; +- private infrastructure credentials; +- NAND dumps; +- device partitions containing secrets; +- deployment-specific secrets; +- private maps or logs containing sensitive data; +- generated binaries unless intentionally published as release artifacts. + +Prefer example configuration files over real deployment configuration. + +Use placeholders such as: + +```text +192.0.2.10 +robot.local +server.local +``` + +instead of private infrastructure addresses in public documentation. + +--- + +## 12. Git and upstream workflow + +Recommended remote model: + +```text +origin -> Enuxia public repository +upstream -> codetiger/VacuumTiger +``` + +Do not force-push rewritten upstream history into the public Enuxia repository without a +very strong reason. + +When useful, upstream changes may be reviewed and selectively integrated. + +Do not blindly overwrite Enuxia-modified files with upstream versions. + +--- + +## 13. Legacy navigator policy + +The current Dhruva navigation stack has been useful for validation and debugging, but it +has known architectural limits. + +Do not treat the current `Navigator` behavior as the long-term design. + +In particular, avoid adding another local patch solely to preserve a design where: + +```text +frontier + -> global path + -> direct waypoint controller + -> timed escape + -> global replan +``` + +New work should move toward Navigation V2: + +```text +goal + -> global planner + -> local costmap + -> local planner + -> measured recovery +``` + +A legacy fix is still acceptable when required for: + +- safety; +- data integrity; +- regression prevention; +- preserving the validated baseline; +- enabling migration to Navigation V2. + +--- + +## 14. SLAM invariants + +Be careful when changing pose-graph or submap code. + +Previously observed failure class: + +```text +pose graph corrects keyframes/current pose +but submap origins remain in old geometry +``` + +This can make the corrected robot pose inconsistent with the rebuilt occupancy grid. + +Any future optimization change must consider: + +- current pose; +- keyframes; +- submap origins; +- global map invalidation/rebuild; +- active navigation path invalidation. + +Do not update only one representation of geometry. + +--- + +## 15. Bumper observations + +Bumper contacts are useful perception data. + +A contact should not exist only as a transient navigation event. + +Where appropriate: + +- record contact position; +- persist it into the active local mapping representation; +- expose it to local obstacle avoidance; +- deduplicate repeated nearby contacts; +- allow repeated contact history to influence navigation decisions. + +Do not let a global-map rebuild silently erase meaningful contact observations. + +--- + +## 16. Unknown space + +Current design intent: + +```text +UNKNOWN = not traversable by the robot center +``` + +But UNKNOWN should not necessarily be treated identically to a confirmed physical +obstacle for every inflation operation. + +Keep the distinction between: + +- unknown; +- free; +- occupied / physical obstacle. + +This distinction is important for frontier approach planning. + +--- + +## 17. Exploration targets + +A frontier cell is not necessarily a valid robot-center goal. + +Prefer: + +```text +unknown region + | +frontier boundary + | +safe approach pose in known free space +``` + +The approach pose may include orientation toward the unknown region. + +Avoid sending the robot center directly onto unknown cells. + +--- + +## 18. Testing philosophy + +A robotics feature is not considered validated because it worked once. + +Create repeatable physical scenarios. + +Important test cases include: + +### Bed / furniture leg + +Expected: + +- obstacle detected; +- no repeated charging into the same obstacle; +- local avoidance or clean abandonment. + +### Soft low obstacle + +Expected: + +- bumper observation persists; +- measured backup; +- repeated contact strengthens avoidance. + +### Rug / traction problem + +Expected: + +- commanded motion without encoder progress is recognized; +- recovery does not loop forever. + +### Narrow passage + +Expected: + +- robot footprint respected; +- sufficient clearance. + +### Dynamic obstacle + +Expected: + +- local planner stops or avoids; +- global replan only when necessary. + +### Cliff + +Expected: + +- immediate stop; +- no unsafe blind reverse; +- persistent forbidden region where appropriate. + +--- + +## 19. Public communication + +Describe Enuxia Vacuum as: + +> An independent open-source fork of VacuumTiger developed by Enuxia. + +Do not describe the repository as if it were created from scratch. + +At the same time, do not frame Enuxia Vacuum as a passive mirror. + +The project now has its own: + +- validated hardware baseline; +- technical decisions; +- architecture; +- development roadmap; +- navigation redesign; +- future cleaning and application layers. + +Use `ATTRIBUTION.md` for provenance details. + +--- + +## 20. Before submitting a change + +Check: + +```text +[ ] Does this change respect subsystem boundaries? +[ ] Does it preserve hardware safety? +[ ] Does it expose secrets or private infrastructure? +[ ] Does it regress validated M6 Pro behavior? +[ ] Does it add another legacy navigation workaround unnecessarily? +[ ] Are tests/builds passing? +[ ] Is the commit focused? +[ ] Does public documentation remain accurate? +``` + +When in doubt, prefer a smaller change with a measurable test. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md new file mode 100644 index 0000000..6a10777 --- /dev/null +++ b/ATTRIBUTION.md @@ -0,0 +1,53 @@ +# Attribution and fork history + +## Enuxia Vacuum + +Enuxia Vacuum is an independent derivative project maintained by Enuxia. + +The project is based on the open-source repository: + +- **Project:** VacuumTiger +- **Upstream:** https://github.com/codetiger/VacuumTiger +- **Upstream root copyright:** Copyright (c) 2024 Harishankar Narayanan + +The complete Git history is intentionally preserved so that the origin of upstream code and subsequent Enuxia work remain auditable. + +## What comes from upstream? + +The fork began with the VacuumTiger codebase, including major components such as: + +- SangamIO; +- CRL-200S hardware support; +- DhruvaSLAM; +- DhruvaNav; +- Drishti; +- VastuSLAM; +- protocol reverse-engineering tools; +- related tests and documentation. + +Exact authorship of individual files and changes should be determined from Git history. + +## Enuxia development + +The Enuxia fork adds and continues to develop work including, among other changes: + +- real-hardware validation on the Proscenic M6 Pro / CRL-200S; +- GD32 cold-boot initialization fixes; +- Delta-2D LiDAR packet decoding corrections; +- SLAM and submap correction work; +- persistent bumper observations; +- navigation and exploration fixes; +- a new navigation architecture; +- future local planning, recovery, cleaning, docking and application layers. + +Git history is the authoritative record of which changes were introduced after the fork. + +## Licensing + +Do not remove upstream copyright or licensing notices. + +The upstream repository contains components whose metadata references MIT, Apache-2.0, or dual MIT/Apache-2.0 licensing. The root LICENSE inherited from VacuumTiger is MIT. + +Each component must continue to comply with the license applicable to its source code and dependencies. + +This document is intended to preserve technical provenance. It is not legal advice. diff --git a/CLAUDE.md b/CLAUDE.md index 30e67b2..c6bc80d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,450 +1,97 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This repository is maintained as **Enuxia Vacuum**, an independent open-source fork of +VacuumTiger. -## Project Overview +## Read this first -VacuumTiger is an open-source robotic vacuum firmware project for the CRL-200S hardware platform. The project consists of three main components: +Before making changes, read: -1. **SangamIO**: A daemon providing hardware abstraction and real-time control via TCP/UDP streaming -2. **VastuSLAM**: A high-performance 2D SLAM library for robot vacuum cleaners -3. **Drishti**: Python visualization and control client for monitoring and commanding the robot +- [`AGENTS.md`](AGENTS.md) — authoritative development and architecture guidance +- [`README.md`](README.md) — public project overview +- [`ATTRIBUTION.md`](ATTRIBUTION.md) — upstream provenance and licensing context -**Target Platform**: Embedded Linux (ARM) - Allwinner A33 running Tina Linux -**Primary Language**: Rust (edition 2024) for SangamIO, Python 3.8+ for Drishti -**Cross-compilation Target**: `armv7-unknown-linux-musleabihf` -**Architecture**: Daemon-based with UDP/TCP streaming (not a library) +`AGENTS.md` is the source of truth for repository-level coding-agent instructions. -## System Architecture +--- -### Overview +## Important project rules -``` -┌─────────────────────────────────────────────────┐ -│ Client Applications (SLAM/Drishti) │ -└─────────────┬───────────────┬───────────────────┘ - │ UDP 5555 │ TCP 5555 - │ (sensors) │ (commands) -┌─────────────▼───────────────▼───────────────────┐ -│ SangamIO Daemon (Robot) │ -│ • Telemetry streaming @ 110Hz (UDP unicast) │ -│ • Lidar streaming @ 5Hz (360° scans) │ -│ • Command processing (TCP, reliable) │ -│ • Real-time control loop (20ms heartbeat) │ -└─────────────┬───────────────┬───────────────────┘ - │ /dev/ttyS3 │ /dev/ttyS1 - ┌──────▼─────────┐ ┌──▼────────────┐ - │ GD32F103 MCU │ │ Delta-2D Lidar│ - │ (Motor Control)│ │ (360° Scan) │ - └────────────────┘ └───────────────┘ +Do not assume the current upstream VacuumTiger architecture is the target architecture. + +Enuxia Vacuum has its own roadmap, including a Navigation V2 redesign with: + +```text +Global planner + ↓ +Layered local costmap + ↓ +DWA local planner + ↓ +Progress / stuck detector + ↓ +Recovery manager ``` -### Key Design Principles +The current Dhruva navigator is a validated baseline, not the long-term design. -1. **Daemon, Not Library**: SangamIO runs as a standalone service, not imported as a library -2. **Concrete Over Abstract**: Direct implementations instead of trait objects (except LidarDriver) -3. **Lock-Free Streaming**: Uses lock-free queues for sensor data flow -4. **Real-Time Guarantees**: OS threads for heartbeat (20ms) instead of async -5. **Hybrid Protocol**: UDP for sensor streaming (low latency), TCP for commands (reliable) +Do not keep adding workaround logic to the legacy navigator when the work belongs in +Navigation V2. -## Build and Deployment +--- -### Development (Host Machine) +## Hardware safety + +This code controls physical hardware. + +When touching robot motion, hardware drivers or recovery behavior: + +- preserve cliff and bumper protections; +- never assume a timed motion actually occurred; +- use encoder/pose feedback for recovery; +- do not run competing hardware controllers simultaneously; +- keep tests small and supervised; +- fail safely. + +--- + +## Public repository hygiene + +Never add: + +- credentials; +- private keys; +- tokens; +- private IP-specific deployment configuration; +- NAND dumps; +- private maps; +- sensitive logs. + +Use example values in documentation and configuration samples. + +--- + +## Development approach + +Prefer: + +```text +one problem +→ one hypothesis +→ one focused change +→ build/test +→ supervised runtime validation if needed +→ commit +``` + +For Rust components, normally run: ```bash -# Build daemon -cd sangam-io -cargo build --release - -# Run with configuration -cargo run --release -- --config sangamio.toml - -# Run with verbose logging -RUST_LOG=debug cargo run --release -``` - -### Production (ARM Robot) - -```bash -# Add ARM target -rustup target add armv7-unknown-linux-musleabihf - -# Build for ARM (produces static binary with musl) -cargo build --release --target armv7-unknown-linux-musleabihf - -# Strip debug symbols (reduces size ~40%) -arm-linux-gnueabihf-strip target/armv7-unknown-linux-musleabihf/release/sangam-io -``` - -### Deployment to Robot - -**IMPORTANT**: The robot monitor auto-restarts killed processes. Rename binaries to prevent this. - -**SSH Credentials**: `root@vacuum`, password: `vacuum@123` - -**Complete Deployment**: - -```bash -# Build ARM binary -cd sangam-io -cargo build --release --target armv7-unknown-linux-musleabihf - -# Deploy binary (device lacks sftp-server, use cat over SSH) -cat target/armv7-unknown-linux-musleabihf/release/sangam-io | \ - sshpass -p "vacuum@123" ssh root@vacuum "cat > /usr/sbin/sangamio && chmod +x /usr/sbin/sangamio" - -# Deploy configuration -sshpass -p "vacuum@123" ssh root@vacuum "cat > /etc/sangamio.toml" < sangamio.toml - -# Disable original firmware (rename to prevent auto-restart) -sshpass -p "vacuum@123" ssh root@vacuum "mv /usr/sbin/AuxCtrl /usr/sbin/AuxCtrl.bak && killall -9 AuxCtrl" - -# Run daemon -sshpass -p "vacuum@123" ssh root@vacuum "RUST_LOG=info /usr/sbin/sangamio" -``` - -**CRITICAL**: Always overwrite `/usr/sbin/sangamio` directly. Do NOT write to `/tmp` folder. - -## Network Protocol - -SangamIO uses a hybrid UDP/TCP architecture: -- **UDP** for sensor streaming (low latency, no head-of-line blocking) -- **TCP** for commands (reliable delivery) - -Both use the same port (5555) for simpler configuration. - -### Message Format - -All messages use length-prefixed framing with Protobuf payloads: - -``` -+------------------+------------------+ -| Length (4 bytes) | Protobuf Message | -| (big-endian) | (binary) | -+------------------+------------------+ -``` - -### Proto Schema - -See `sangam-io/proto/sangamio.proto` for SangamIO messages (sensors, commands). - -### Topics (UDP Streaming) - -**Outbound (Daemon → Client via UDP)**: -- `sensors/sensor_status`: Sensor data @ 110Hz (all 13 sensors) -- `sensors/device_version`: Version info (one-time) -- `sensors/lidar`: LidarScan @ 5Hz (point cloud) - -**Inbound (Client → Daemon via TCP)**: -- `command`: RobotCommand (SetVelocity, Stop, actuator control, etc.) - -### Client Registration - -UDP uses unicast (not broadcast). Clients are registered automatically: -1. Client connects TCP to port 5555 -2. Server records client IP address -3. UDP packets are sent to client on same port -4. When TCP disconnects, UDP streaming stops - -### Example Python Client - -```python -import socket -import struct -import threading -from proto import sangamio_pb2 - -ROBOT_IP = '192.168.68.101' -PORT = 5555 - -# TCP for commands -tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -tcp_sock.connect((ROBOT_IP, PORT)) - -# UDP for sensor data (same port) -udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -udp_sock.bind(('0.0.0.0', PORT)) - -def receive_sensors(): - while True: - data, addr = udp_sock.recvfrom(4096) - length = struct.unpack('>I', data[:4])[0] - msg = sangamio_pb2.Message() - msg.ParseFromString(data[4:4+length]) - - if msg.topic == 'sensors/sensor_status': - sg = msg.sensor_group - print(f"Battery: {sg.values['battery_level'].u8_val}%") - -# Start receiving in background -threading.Thread(target=receive_sensors, daemon=True).start() - -# Send commands via TCP -def send_command(cmd): - payload = cmd.SerializeToString() - tcp_sock.send(struct.pack('>I', len(payload)) + payload) -``` - -## Configuration - -Edit `sangamio.toml`: - -```toml -[device] -type = "crl200s" -name = "CRL-200S Vacuum Robot" - -[device.hardware] -gd32_port = "/dev/ttyS3" -lidar_port = "/dev/ttyS1" - -[network] -bind_address = "0.0.0.0:5555" -``` - -## Module Organization - -``` -VacuumTiger/ -├── sangam-io/ # Hardware abstraction daemon -│ ├── src/ -│ │ ├── main.rs # Entry point, TCP/UDP listener -│ │ ├── config.rs # TOML configuration loading -│ │ ├── error.rs # Error types -│ │ ├── core/ # Core abstractions -│ │ │ ├── mod.rs -│ │ │ ├── driver.rs # DeviceDriver trait -│ │ │ └── types.rs # SensorValue, Command types -│ │ ├── devices/ # Hardware drivers -│ │ │ ├── mod.rs # Device factory (crl200s, mock) -│ │ │ ├── crl200s/ # Real hardware driver -│ │ │ │ ├── mod.rs -│ │ │ │ ├── constants.rs # Offsets, flags, timing -│ │ │ │ ├── COMMANDS.md # GD32 command reference -│ │ │ │ ├── SENSORSTATUS.md # Sensor packet docs -│ │ │ │ ├── gd32/ # Motor controller driver -│ │ │ │ │ ├── mod.rs # Driver orchestration -│ │ │ │ │ ├── protocol.rs # RxPacket, PacketReader -│ │ │ │ │ ├── packet.rs # TxPacket, checksum -│ │ │ │ │ ├── heartbeat.rs # 20ms watchdog loop -│ │ │ │ │ ├── reader.rs # Status parsing -│ │ │ │ │ ├── commands.rs # Command handlers -│ │ │ │ │ └── state.rs # Shared atomic state -│ │ │ │ └── delta2d/ # 3iRobotix lidar -│ │ │ │ ├── mod.rs # LidarDriver impl -│ │ │ │ └── protocol.rs # Packet parsing -│ │ │ └── mock/ # Simulation driver (--features mock) -│ │ │ ├── mod.rs # MockDriver, simulation loop -│ │ │ ├── config.rs # SimulationConfig structs -│ │ │ ├── physics.rs # Differential drive kinematics -│ │ │ ├── lidar_sim.rs # Ray-casting lidar -│ │ │ ├── imu_sim.rs # IMU with noise -│ │ │ ├── encoder_sim.rs # Encoder with slip -│ │ │ ├── sensor_sim.rs # Bumpers, cliffs -│ │ │ ├── map_loader.rs # PGM+YAML loader -│ │ │ └── noise.rs # Noise generator -│ │ └── streaming/ # Network communication -│ │ ├── mod.rs # Re-exports -│ │ ├── wire.rs # Protobuf serialization -│ │ ├── udp_publisher.rs # UDP sensor streaming -│ │ └── tcp_receiver.rs # TCP command handler -│ ├── proto/ # Protobuf schema -│ │ └── sangamio.proto # Message definitions -│ ├── maps/ # Example simulation maps -│ ├── docs/ # Documentation -│ │ └── mock-device-guide.md -│ ├── sangamio.toml # Hardware configuration -│ └── mock.toml # Simulation configuration -│ -├── vastu-slam/ # High-performance 2D SLAM library -│ ├── src/ -│ │ ├── lib.rs # Library entry point -│ │ ├── core/ # Foundation types (Pose2D, WorldPoint, GridCoord, sensors) -│ │ ├── grid/ # Occupancy grid storage (SoA layout, log-odds updates) -│ │ ├── matching/ # Scan matching (correlative, branch-bound, loop closure) -│ │ ├── submap/ # Submap manager, pose graph, multi-submap matching -│ │ ├── config/ # YAML configuration loading -│ │ ├── io/ # Persistence (.vastu, .pgm, .svg formats) -│ │ ├── modes/ # Operation modes (localization-only) -│ │ └── evaluation/ # Cartographer-style evaluation metrics -│ ├── configs/ # YAML configuration files -│ ├── maps/ # Test maps (PGM format) -│ ├── scenarios/ # Test scenarios (YAML) -│ ├── benches/ # Performance benchmarks -│ └── tests/ # Integration tests -│ -└── drishti/ # Python visualization client - ├── drishti.py # Console client - ├── drishti_ui.py # GUI application - └── ui/ # PyQt widgets - ├── main_window.py - ├── processors/ # Data processing - ├── threads/ # Background workers - └── widgets/ # UI components -``` - -## Critical Hardware Constraints - -### GD32F103 Heartbeat -- **Requirement**: CMD=0x66 heartbeat every 20-50ms -- **Consequence**: Motors stop if heartbeat missed -- **Implementation**: Dedicated OS thread, not async -- **Recovery**: 5-second re-initialization sequence - -### Serial Port Exclusivity -- **Ports**: `/dev/ttyS3` (GD32), `/dev/ttyS1` (lidar) -- **Issue**: Only one process can open port -- **Solution**: Kill/rename AuxCtrl before running - -### Initialization Sequence -- **GD32 Wake**: CMD=0x08 repeated for up to 5 seconds -- **Required**: Must complete before any commands work -- **Automatic**: Driver handles this internally - -## Protocol Specifications - -### GD32 Protocol -- **Format**: `[0xFA 0xFB] [LEN] [CMD] [PAYLOAD] [CRC]` -- **CRC**: 16-bit big-endian word sum checksum -- **Status**: CMD=0x15, 96 bytes @ 110Hz -- **Location**: `src/devices/crl200s/gd32/protocol.rs` - -### Delta-2D Protocol -- **Format**: Variable-length with 8-byte headers -- **Data**: Angle (0.01° units), distance (0.25mm units) -- **Rate**: ~5Hz complete scans -- **Location**: `src/devices/crl200s/delta2d/protocol.rs` - -## Performance Characteristics - -- **Binary Size**: ~350KB statically linked -- **Memory Usage**: <10MB RSS -- **CPU Usage**: <1% on Allwinner A33 -- **Latency**: <25ms command-to-action -- **Streaming Rates**: - - Telemetry: 110Hz (every GD32 packet) - - Lidar: 5Hz (native scan rate) - - Commands: On-demand - -## Thread Model - -| Thread | Purpose | Timing | Priority | -|--------|---------|--------|----------| -| Main | Initialization | - | Normal | -| GD32 Heartbeat | Safety watchdog | 20ms ± 2ms | High | -| GD32 Reader | Parse status | Continuous | Normal | -| Lidar Reader | Parse scans | Continuous | Normal | -| TCP Publisher | Broadcast data | On-demand | Normal | -| TCP Receiver | Handle commands | On-demand | Normal | - -## Safety and Error Handling - -- **Never use `unwrap()` or `panic!()`** in production code -- **All errors through `sangam_io::Error` enum** -- **Motors stop on any error condition** -- **Heartbeat continues even if commands fail** -- **Use `Result` for fallible operations** - -## Testing and Debugging - -### Local Testing - -```bash -# Unit tests +cargo fmt --check cargo test - -# Virtual serial ports -socat -d -d pty,raw,echo=0 pty,raw,echo=0 - -# Run with PTY -RUST_LOG=debug cargo run -- --config test.toml +cargo build --release ``` -### Hardware Testing +Do not make broad refactors or mass renames unless explicitly requested. -```bash -# Deploy and run -./deploy.sh - -# Monitor logs -ssh root@vacuum "journalctl -u sangamio -f" - -# Check resources -ssh root@vacuum "top -p $(pgrep sangamio)" -``` - -## Drishti Visualization - -### GUI Application - -```bash -cd drishti -source venv/bin/activate -python drishti_ui.py --robot 192.168.68.101 -``` - -Features: -- Full-screen robot diagram with physically-correct layout -- Real-time sensor overlays (all 13 sensors) -- Bumper/cliff highlighting on trigger -- Wheel encoder tick counters -- Battery/charging status indicator -- Start/dock button states - -### Console Client (Legacy) - -```bash -python drishti.py --robot 192.168.68.101 --verbose -``` - -## Common Pitfalls and Solutions - -1. **AuxCtrl Auto-Restart**: Must rename binary, not just kill process -2. **Serial Port Access**: Check `/dev/ttyS3` for GD32, not `/dev/ttyS1` -3. **No SCP Support**: Use `cat | ssh` for file transfer -4. **Unit Confusion**: Always use meters, m/s, radians in API -5. **Blocking I/O**: Serial has timeouts, don't assume immediate response - -## Version History - -### SangamIO -- **v0.3.0**: UDP/TCP unified port architecture, mock device simulation -- **v0.2.0**: Daemon architecture, performance optimizations -- **v0.1.0**: Initial release with basic functionality - -### VastuSLAM -- **v0.1.0**: High-performance 2D SLAM library with scan matching, submap architecture, and loop closure - -## Documentation References - -- **sangam-io/README.md**: SangamIO daemon documentation -- **sangam-io/COMMANDS.md**: GD32 command reference with TCP API -- **sangam-io/SENSORSTATUS.md**: Sensor packet byte layout -- **vastu-slam/README.md**: VastuSLAM library documentation -- **vastu-slam/src/*/README.md**: Module-specific documentation (core, grid, matching, submap, config, io, modes, evaluation) -- **drishti/README.md**: Python client documentation -- **protocol-mitm/README.md**: MITM reverse engineering tools - -## Future Roadmap - -### Planned Features -- WebSocket interface for browser monitoring -- ROS2 bridge for ecosystem integration -- Additional lidar models (RPLIDAR, Hokuyo) -- Multi-robot coordination support - -### Explicitly Not Goals -- Generic HAL for all hardware -- Plugin system (static compilation preferred) -- Remote/cloud operation (LAN-only) -- AI/ML integration (keep in higher layers) - -## Development Guidelines - -When modifying this codebase: - -1. **Maintain simplicity**: Avoid unnecessary abstractions -2. **Prioritize reliability**: Real-time constraints matter -3. **Document changes**: Update relevant documentation -4. **Test on hardware**: Virtual tests insufficient -5. **Preserve safety**: Never bypass heartbeat mechanism \ No newline at end of file +Preserve upstream attribution and license notices. diff --git a/README.md b/README.md index a5d8595..fc0e0d2 100644 --- a/README.md +++ b/README.md @@ -1,246 +1,400 @@ -# VacuumTiger +# Enuxia Vacuum -**Open-source firmware for hackable vacuum robots** +**Open, local-first robotics stack for autonomous vacuum robots.** -VacuumTiger is a modular, configuration-driven firmware stack that makes it easy to build autonomous vacuum robots. Define your sensors and actuators in TOML, and the system handles everything else—from real-time control to network streaming. +Enuxia Vacuum is an independent open-source fork of [VacuumTiger](https://github.com/codetiger/VacuumTiger), now developed by **Enuxia** as a platform for fully local robot control, mapping, navigation and cleaning. -## Why VacuumTiger? +The project started from a practical goal: take commodity robot-vacuum hardware and turn it into a robot that can be understood, modified, self-hosted and improved without depending on a proprietary cloud. -- **Configurable**: Define sensors, actuators, and hardware in a single TOML file -- **Extensible**: Add new robot platforms by implementing one trait -- **Generic Protocol**: TCP streaming works with any SLAM application -- **Real-time**: 110Hz sensor updates, <25ms command latency -- **Hackable**: Small codebase (~4K lines Rust), no dependencies on proprietary SDKs +Today, our primary development platform is the **Proscenic M6 Pro / 3irobotix CRL-200S**. -## How It Works +> The long-term goal is not merely to reproduce the factory firmware. +> It is to build a cleaner, more modular and more capable robotics stack around the hardware. -``` -┌─────────────────────────────────────────────────┐ -│ SLAM Application (Planned) │ -│ Reads sangamio.toml to discover capabilities │ -└─────────────┬───────────────────────────────────┘ - │ TCP Protocol (Generic) -┌─────────────▼───────────────────────────────────┐ -│ SangamIO Daemon │ -│ Configuration-driven hardware abstraction │ -│ • Streams sensor groups defined in config │ -│ • Routes commands to configured actuators │ -└─────────────┬───────────────────────────────────┘ - │ DeviceDriver Trait -┌─────────────▼───────────────────────────────────┐ -│ Robot-Specific Driver (e.g., CRL-200S) │ -│ Implements protocol for specific hardware │ -└─────────────────────────────────────────────────┘ +--- + +## Why Enuxia Vacuum? + +Most consumer robot vacuums are closed systems: + +- proprietary firmware; +- vendor-controlled cloud services; +- undocumented hardware protocols; +- limited local APIs; +- difficult long-term maintenance; +- little room for experimentation. + +Enuxia Vacuum takes the opposite approach: + +- **local-first** — the robot can operate without a vendor cloud; +- **open** — hardware protocols and software architecture are inspectable; +- **modular** — hardware access, SLAM, navigation and UI are separated; +- **self-hostable** — high-level computation can run on your own Linux infrastructure; +- **hardware-aware** — the stack is built around real sensors, actuators and failure modes; +- **hackable** — the project is intended to be understood and extended. + +--- + +## Current status + +The CRL-200S / M6 Pro platform is already functional as a development robot. + +| Area | Status | +|---|---| +| Root / recovery access | ✅ Validated | +| Low-level hardware control | ✅ Validated | +| Wheel encoders | ✅ Validated | +| IMU | ✅ Validated | +| Bumpers | ✅ Validated | +| Cliff sensors | ✅ Validated | +| Battery / charging telemetry | ✅ Validated | +| Vacuum / brushes | ✅ Validated | +| Delta-2D LiDAR | ✅ Decoded and corrected | +| GD32 cold-boot initialization | ✅ Fixed | +| Local sensor streaming | ✅ Operational | +| 2D SLAM | 🟡 Functional, still being consolidated | +| Global navigation | 🟡 Existing baseline | +| Navigation V2 | 🚧 In development | +| Local obstacle avoidance | 🚧 Planned / in development | +| Cleaning coverage planner | 📋 Planned | +| Docking | 📋 Planned | +| Local API | 📋 Planned | +| PWA / Android application | 📋 Planned | + +--- + +## Architecture + +The current architecture keeps the hardware-facing code on the robot while allowing heavier robotics workloads to run on a Linux machine on the local network. + +```text +┌──────────────────────────────────────────────┐ +│ User layer │ +│ PWA / Android / diagnostics │ +└──────────────────────┬───────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────┐ +│ Mission Manager │ +│ map / clean / goto / dock │ +└──────────────────────┬───────────────────────┘ + │ + ┌──────────────┴──────────────┐ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ Exploration │ │ Cleaning Planner │ +│ frontier / info │ │ coverage / rooms │ +└────────┬─────────┘ └────────┬─────────┘ + └──────────────┬─────────────┘ + ▼ + ┌─────────────────┐ + │ Global Planner │ + │ Theta* │ + └────────┬────────┘ + ▼ + ┌─────────────────┐ + │ Local Planner │ + │ DWA │ + └────────┬────────┘ + ▼ + ┌─────────────────┐ + │ SangamIO │ + │ hardware layer │ + └────────┬────────┘ + ▼ + ┌─────────────────┐ + │ Robot │ + └─────────────────┘ ``` -## Configuration-Driven Design +A layered local costmap is planned between perception and the local planner: -Everything is defined in `sangamio.toml`: - -```toml -[device] -type = "crl200s" -name = "CRL-200S Vacuum Robot" - -[device.hardware] -gd32_port = "/dev/ttyS3" -lidar_port = "/dev/ttyS1" -heartbeat_interval_ms = 20 - -[network] -bind_address = "0.0.0.0:5555" +```text +SLAM static map +Live LiDAR +Bumper contacts +Cliff sensors +No-go zones +Robot footprint + │ + ▼ + Layered Costmap + │ + ▼ + Local Planner ``` -**SLAM applications read this same config** to discover what sensors and actuators are available, enabling truly portable navigation algorithms. +--- -## Generic TCP Protocol +## Repository layout -The protocol is robot-agnostic. Any client that speaks the wire format can control any VacuumTiger robot. - -### Message Format - -``` -[4-byte length (big-endian)][Protobuf payload] +```text +enuxia-vacuum/ +├── sangam-io/ # Hardware abstraction and robot drivers +├── dhruva-slam/ # SLAM, mapping and current navigation stack +├── dhruva-nav/ # Upstream navigation research / reusable building blocks +├── drishti/ # Engineering and diagnostic UI +├── vastu-slam/ # SLAM library +├── protocol-mitm/ # Reverse-engineering / protocol tooling +└── slam-test/ # Integration and algorithm tests ``` -All communication uses Protocol Buffers for efficient binary serialization. See `sangam-io/proto/sangamio.proto` and `dhruva-slam/proto/dhruva.proto` for schema definitions. +The internal component names are intentionally preserved during the first stages of the fork. Renaming them all at once would make upstream comparison and regression analysis unnecessarily difficult. -### Component Actions +--- -| Action | Description | Example | -|--------|-------------|---------| -| `Enable` | Activate component | `{"type": "Enable"}` | -| `Disable` | Deactivate component | `{"type": "Disable"}` | -| `Reset` | Emergency stop / factory reset | `{"type": "Reset"}` | -| `Configure` | Set parameters (velocity, speed) | `{"type": "Configure", "config": {...}}` | +## Primary hardware: Proscenic M6 Pro / CRL-200S -### Supported Components +The current development target uses: -| Component | Enable | Disable | Configure | -|-----------|--------|---------|-----------| -| `drive` | Nav mode | Stop | `{linear, angular}` or `{left, right}` | -| `vacuum` | 100% | Off | `{speed: U8}` | -| `main_brush` | 100% | Off | `{speed: U8}` | -| `side_brush` | 100% | Off | `{speed: U8}` | -| `lidar` | Power on | Power off | - | -| `led` | - | - | `{state: U8}` | +- **SoC:** Allwinner A33 +- **MCU:** GD32F103 +- **LiDAR:** Delta-2D +- **MCU UART:** `/dev/ttyS3` +- **LiDAR UART:** `/dev/ttyS1` +- **Linux:** Tina Linux / OpenWrt based userspace -## Adding a New Robot Platform +The hardware driver exposes the robot through SangamIO, which provides a robot-agnostic control and telemetry interface to higher-level software. -VacuumTiger is designed to support any robot hardware. Here's how to add a new platform: +### Validated CRL-200S work -### 1. Create Configuration +The Enuxia fork includes additional work validated on real M6 Pro hardware, including: -```toml -[device] -type = "my_robot" +- GD32 initialization reliable after cold boot; +- corrected Delta-2D angular decoding; +- stable full-revolution LiDAR scans; +- wheel / bumper / cliff / battery telemetry; +- drive, vacuum and brush control; +- persistent bumper observations in SLAM; +- SLAM pose-graph and submap correction work; +- exploration and navigation failure handling improvements. -[device.hardware] -motor_port = "/dev/ttyUSB0" -heartbeat_interval_ms = 20 +--- -[network] -bind_address = "0.0.0.0:5555" +## SangamIO + +[`sangam-io/`](sangam-io/) is the hardware abstraction layer. + +It is responsible for: + +- robot-specific protocol handling; +- sensor acquisition; +- wheel commands; +- actuators; +- LiDAR control; +- telemetry streaming; +- hardware-independent commands for upper layers. + +High-level navigation code should not need to understand the GD32 protocol directly. + +See [sangam-io/README.md](sangam-io/README.md). + +--- + +## DhruvaSLAM + +[`dhruva-slam/`](dhruva-slam/) provides the current mapping and localization stack. + +It includes: + +- wheel odometry; +- LiDAR preprocessing; +- scan matching; +- occupancy-grid mapping; +- submaps; +- loop closure; +- pose graph optimization; +- map streaming; +- the current navigation/exploration baseline. + +The existing navigator is considered a **baseline**, not the final architecture. + +See [dhruva-slam/README.md](dhruva-slam/README.md). + +--- + +## Navigation V2 + +Navigation is the next major Enuxia development milestone. + +The current architecture couples global planning, frontier exploration and recovery too tightly. Navigation V2 will separate these responsibilities. + +Target design: + +```text +Mission + ↓ +Goal + ↓ +Global planner (Theta*) + ↓ +Global path + ↓ +Local costmap + ↓ +Local planner (DWA) + ↓ +Velocity commands + ↓ +Progress / stuck detector + ↓ +Recovery manager ``` -### 2. Implement DeviceDriver Trait +### Goals -```rust -// src/devices/my_robot/mod.rs -pub struct MyRobotDriver { /* ... */ } +Navigation V2 is intended to provide: -impl DeviceDriver for MyRobotDriver { - fn initialize( - &mut self, - sensor_data: HashMap>> - ) -> Result<()> { - // Open serial ports - // Spawn reader threads - // Update sensor_data in real-time - } +- real local obstacle avoidance; +- footprint-aware collision checking; +- dynamic obstacle handling; +- measured, encoder-based recovery; +- repeated-contact memory; +- robust stuck detection; +- independent global and local planning; +- clean separation between exploration and navigation. - fn send_command(&mut self, cmd: Command) -> Result<()> { - match cmd { - Command::ComponentControl { id, action } => { - // Handle drive, vacuum, lidar, etc. - } - Command::Shutdown => { /* ... */ } - } - } -} +A bumper contact must become information, not simply trigger a timed reverse command. + +--- + +## Mapping vs cleaning + +Mapping and cleaning are intentionally treated as two separate problems. + +### Mapping + +```text +unknown space + ↓ +frontier detection + ↓ +safe approach poses + ↓ +navigation + ↓ +map expansion ``` -### 3. Register in Factory +### Cleaning -```rust -// src/devices/mod.rs -pub fn create_device(config: &Config) -> Result> { - match config.device.device_type.as_str() { - "crl200s" => Ok(Box::new(CRL200SDriver::new(...))), - "my_robot" => Ok(Box::new(MyRobotDriver::new(...))), - _ => Err(Error::UnknownDevice(...)), - } -} +Once a stable map exists: + +```text +saved map + ↓ +room segmentation + ↓ +coverage planning + ↓ +cleaning lanes + ↓ +local obstacle avoidance + ↓ +return to dock ``` -That's it! Your new robot works with any existing SLAM application. +Future cleaning modes may include: -## Repository Structure +- whole-home cleaning; +- room cleaning; +- zone cleaning; +- spot cleaning; +- multiple passes; +- no-go / no-mop areas; +- multi-floor maps. -``` -VacuumTiger/ -├── sangam-io/ # Hardware abstraction daemon (Rust) -│ ├── src/ -│ │ ├── core/ # DeviceDriver trait, SensorValue types -│ │ ├── devices/ # Robot-specific implementations -│ │ │ └── crl200s/ # CRL-200S driver (GD32 + Delta-2D) -│ │ └── streaming/ # TCP protocol, Protobuf wire format -│ ├── proto/ # Protobuf schema definitions -│ ├── sangamio.toml # Robot configuration -│ ├── COMMANDS.md # GD32 command reference -│ └── SENSORSTATUS.md # Sensor packet documentation -├── dhruva-slam/ # 2D SLAM daemon (Rust) -│ ├── src/ -│ │ ├── core/ # Foundation types (Pose2D, PointCloud2D) -│ │ ├── sensors/ # Odometry fusion, lidar preprocessing -│ │ ├── algorithms/ # Scan matching, mapping, loop closure -│ │ ├── engine/ # SLAM orchestration, pose graph -│ │ └── io/ # SangamIO client, TCP streaming -│ └── proto/ # SLAM message definitions -├── drishti/ # Diagnostic visualization (Python) -│ ├── drishti_ui.py # PyQt GUI with sensor overlays -│ ├── drishti.py # Console client -│ └── ui/ # PyQt widgets and processors -├── slam-test/ # SLAM integration test framework -└── protocol-mitm/ # Reverse engineering tools -``` +--- -## Quick Start +## Building -### Build SangamIO +### SangamIO ```bash -# Install Rust with ARM target +cd sangam-io + rustup target add armv7-unknown-linux-musleabihf -# Build for robot -cd sangam-io -cargo build --release --target armv7-unknown-linux-musleabihf +cargo build --release \ + --target armv7-unknown-linux-musleabihf ``` -### Deploy to Robot +Depending on your toolchain, an ARM cross-linker may be required. + +### DhruvaSLAM ```bash -# Copy binary to robot -cat target/armv7-unknown-linux-musleabihf/release/sangam-io | \ - ssh root@vacuum "cat > /usr/sbin/sangamio && chmod +x /usr/sbin/sangamio" +cd dhruva-slam -# Run daemon -ssh root@vacuum "RUST_LOG=info /usr/sbin/sangamio" +cargo test +cargo build --release ``` -### Connect with Drishti +### Drishti ```bash cd drishti + +python -m venv .venv +source .venv/bin/activate + pip install -r requirements.txt -python drishti_ui.py --robot 192.168.68.101 +python drishti_ui.py --help ``` -![Drishti UI](drishti/drishti.png) +--- -## Project Status +## Safety -| Component | Status | Notes | -|-----------|--------|-------| -| SangamIO Daemon | ✅ Complete | Config-driven, ~350KB binary | -| CRL-200S Driver | ✅ Verified | GD32 motor controller + Delta-2D lidar | -| TCP Protocol | ✅ Complete | Protobuf binary streaming | -| Drishti UI | ✅ Complete | Real-time sensor visualization | -| DhruvaSLAM | ✅ Complete | 2D SLAM with multi-algorithm support | -| SLAM Integration Tests | ✅ Complete | Round-trip test framework | -| Additional Platforms | 📋 Planned | Roomba, Turtlebot, etc. | +This project controls physical hardware. -## Contributing +During development: -Help us build the best open-source vacuum robot firmware: +- supervise all motion tests; +- keep a reliable way to stop the robot; +- test away from stairs and drops; +- do not bypass cliff protection; +- do not run the factory hardware controller and SangamIO at the same time on platforms where they compete for the same hardware; +- expect experimental navigation code to fail. -- **New platforms**: Implement DeviceDriver for your robot -- **SLAM**: Build navigation algorithms using the generic protocol -- **Sensors**: Add support for new lidar models, IMUs -- **Documentation**: Improve guides and examples +Enuxia Vacuum is research and development software. It is not currently a safety-certified robotic system. -## Safety Notice +--- -⚠️ This firmware controls physical hardware. Always: -- Test in a safe, controlled environment -- Keep emergency stop accessible -- Monitor battery voltage during operation +## Project origin + +Enuxia Vacuum did **not** start from a blank repository. + +It is derived from the open-source **VacuumTiger** project: + +- upstream repository: `codetiger/VacuumTiger` +- original root copyright notice: Copyright © 2024 Harishankar Narayanan +- upstream history is intentionally preserved in this repository + +The fork retains upstream code, history and applicable copyright/license notices while developing an independent technical direction under Enuxia. + +We are grateful to the upstream project for the reverse-engineering and software foundation that made this work possible. + +See [ATTRIBUTION.md](ATTRIBUTION.md) for details. + +--- ## License -Apache License 2.0. See [LICENSE](LICENSE) for details. +This repository is a derivative work containing code from multiple upstream components. -## Links +The root `LICENSE` inherited from VacuumTiger is MIT. Some subprojects also declare MIT, Apache-2.0, or dual MIT/Apache-2.0 licensing in their own metadata. -- [Original Research](https://github.com/codetiger/VacuumRobot) +**Applicable upstream notices and component-specific licenses must be preserved.** + +Before packaging or redistributing binaries, review the license metadata of the components you are distributing. + +--- + +## Enuxia + +Enuxia develops local-first systems that connect software, infrastructure and physical hardware. + +This project follows the same principle: + +> **The machine should remain understandable, controllable and useful without depending on somebody else's cloud.** diff --git a/dhruva-slam/README.md b/dhruva-slam/README.md index bf0de3f..2ac25d3 100644 --- a/dhruva-slam/README.md +++ b/dhruva-slam/README.md @@ -1,425 +1,146 @@ -# DhruvaSLAM +# DhruvaSLAM — Enuxia Vacuum mapping stack -Production-ready 2D SLAM (Simultaneous Localization and Mapping) implementation in Rust for robotic vacuum cleaners, targeting the Allwinner A33 embedded platform. +DhruvaSLAM is the current SLAM, mapping and robot-state backend used by Enuxia Vacuum. -## Overview +It originates from the upstream [VacuumTiger](https://github.com/codetiger/VacuumTiger) project and is being adapted by Enuxia around real-world CRL-200S / Proscenic M6 Pro testing. -DhruvaSLAM is a standalone SLAM daemon that: -1. Connects to [SangamIO](../sangam-io/README.md) daemon for sensor data (encoders, gyro, lidar) -2. Computes odometry from wheel encoders with IMU fusion -3. Performs scan-to-map matching for pose correction -4. Builds and maintains an occupancy grid map -5. Publishes corrected poses and maps over TCP for visualization +The current codebase is a **validated development baseline**, not the final navigation architecture. -``` -┌─────────────────────────────────────────────────────────────┐ -│ DhruvaSLAM Daemon │ -│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────┐ │ -│ │ Odometry │ │ SLAM │ │ TCP Publisher │ │ -│ │ Pipeline │ │ Engine │ │ (Port 5557) │ │ -│ │ │ │ │ │ │ │ -│ │ Encoders ──►│ │ Lidar ─────►│ │ ──► Pose @ 50Hz │ │ -│ │ Gyro ──────►│ │ Odometry ──►│ │ ──► Map @ 1Hz │ │ -│ │ │ │ │ │ ──► Scan @ 5Hz │ │ -│ └─────────────┘ └─────────────┘ └──────────────────────┘ │ -└────────────────────────┬────────────────────────────────────┘ - │ TCP 5555 (Protobuf) - ┌──────────▼──────────┐ - │ SangamIO Daemon │ - │ (Robot Hardware) │ - └─────────────────────┘ +--- + +## Responsibilities + +DhruvaSLAM currently handles: + +- wheel odometry; +- LiDAR preprocessing; +- scan matching; +- occupancy-grid mapping; +- submaps; +- keyframes; +- loop closure; +- pose-graph optimization; +- map persistence; +- telemetry / map streaming; +- exploration and navigation baseline logic. + +```text +SangamIO + │ + ├── wheel / sensors + └── LiDAR + │ + ▼ + DhruvaSLAM + │ + ┌────┴─────┐ + ▼ ▼ + Pose Map + │ │ + └────┬─────┘ + ▼ + Navigation / diagnostics ``` -### Key Features +--- -- **Configurable Odometry** - Wheel-only, Complementary, ESKF, or Mahony fusion -- **Multiple Scan Matchers** - ICP, P2L-ICP, Correlative, Multi-Resolution, Hybrid -- **Occupancy Grid Mapping** - Log-odds representation with Bresenham ray tracing -- **Feature Extraction** - Line and corner detection from occupancy maps -- **Loop Closure** - LiDAR-IRIS binary descriptors with sparse CG optimization -- **Bag Recording/Playback** - Record sensor data for offline development +## Enuxia baseline work -### Specifications +The Enuxia M6 Pro baseline contains a set of fixes developed from real robot runs, including: -| Property | Value | -|----------|-------| -| Language | Rust 2024 edition | -| Target | ARM Cortex-A7 (Allwinner A33) | -| Input | 110Hz sensors, 5Hz lidar | -| Output | 50Hz pose, 1Hz map, 5Hz scan | -| Memory | < 100MB for SLAM | +- configurable minimum LiDAR scan size; +- full initial 360° scan before autonomous exploration; +- corrected treatment of unknown space during planning; +- navigation invalidation after SLAM graph corrections; +- submap-origin corrections after pose-graph optimization; +- persistent bumper obstacles; +- improved frontier failure tracking; +- protection against repeated failed frontier selections; +- safer stuck/failure termination. -## Architecture +These changes improved the baseline substantially, but they also exposed architectural limitations in the current navigator. -DhruvaSLAM is organized as a binary crate (not a library) with 5 logical layers: +--- -``` -┌─────────────────────────────────────────────────────┐ -│ main.rs │ ← Entry point -└─────────────────────────────────────────────────────┘ - │ -┌─────────────────────────────────────────────────────┐ -│ io/ │ ← Infrastructure -│ (sangam_client, streaming) │ -└─────────────────────────────────────────────────────┘ - │ -┌─────────────────────────────────────────────────────┐ -│ engine/ │ ← Orchestration -│ (slam, graph optimization) │ -└─────────────────────────────────────────────────────┘ - │ -┌─────────────────────────────────────────────────────┐ -│ algorithms/ │ ← Core algorithms -│ (matching, mapping, localization, descriptors) │ -└─────────────────────────────────────────────────────┘ - │ -┌─────────────────────────────────────────────────────┐ -│ sensors/ │ ← Sensor processing -│ (odometry, preprocessing, calibration) │ -└─────────────────────────────────────────────────────┘ - │ -┌─────────────────────────────────────────────────────┐ -│ core/ │ ← Foundation -│ (types, math, simd) │ -└─────────────────────────────────────────────────────┘ +## Navigation status + +The existing navigation code is intentionally considered **legacy/baseline navigation**. + +The next Enuxia milestone is Navigation V2. + +### Navigation V2 target + +```text +Goal + ↓ +Global planner +Theta* + ↓ +Global path + ↓ +Layered local costmap + ↓ +DWA local planner + ↓ +cmd_vel + ↓ +SangamIO ``` -See individual module READMEs for details: -- [core/](src/core/README.md) - Foundation types (Pose2D, PointCloud2D, LaserScan) and SIMD -- [sensors/](src/sensors/README.md) - Odometry fusion and lidar preprocessing -- [algorithms/](src/algorithms/README.md) - Matching, mapping, localization, descriptors -- [engine/](src/engine/README.md) - SLAM orchestration, keyframes, pose graph -- [io/](src/io/README.md) - SangamIO client, TCP streaming +Navigation V2 will also introduce: -## Building +- footprint-aware collision checking; +- measured progress detection; +- encoder-based stuck detection; +- contact-memory from bumpers; +- recovery based on actual displacement rather than timers; +- clean separation between navigation and exploration. + +--- + +## Build ```bash -# Build for development -cargo build - -# Build optimized release +cargo test cargo build --release - -# Run tests -cargo test - -# Check without building -cargo check ``` -### Cross-Compilation (ARM) +Run with an explicit config: ```bash -# Add ARM target -rustup target add armv7-unknown-linux-musleabihf - -# Build for robot -cargo build --release --target armv7-unknown-linux-musleabihf +RUST_LOG=info \ +./target/release/dhruva-slam \ + --config /path/to/dhruva-slam.toml ``` -## Usage +Do not commit deployment-specific network addresses or credentials into the public repository. -### Running the SLAM Daemon +--- -```bash -# With default config (looks for dhruva-slam.toml) -cargo run --release +## Development philosophy -# With explicit config file -cargo run --release -- --config dhruva-slam.toml +SLAM answers: -# With command-line overrides -cargo run --release -- --sangam 192.168.68.101:5555 --port 5557 +> Where is the robot, and what does the world look like? -# With debug logging -RUST_LOG=debug cargo run --release -``` +Navigation answers: -**Arguments:** -| Argument | Description | Default | -|----------|-------------|---------| -| `-c, --config` | Configuration file | `dhruva-slam.toml` | +> How should the robot move safely from here to there? -## Configuration +Exploration answers: -Edit `dhruva-slam.toml`: +> Where should the robot go next to learn more about the environment? -```toml -[source] -sangam_address = "192.168.68.101:5555" +Cleaning answers: -[output] -bind_port = 5557 -odometry_rate_hz = 50.0 -map_rate_hz = 1.0 -feature_rate_hz = 0.2 +> How should the known floor be covered efficiently? -[slam] -min_range = 0.15 # Lidar min range (m) -max_range = 8.0 # Lidar max range (m) +Those responsibilities are being separated progressively in the Enuxia fork. -[lidar] -mounting_x = -0.0936 # Lidar X offset from robot center (m) -mounting_y = 0.0 -optical_offset = 0.0258 # Optical center offset (m) -angle_offset = 0.0 # Angular calibration (rad) +--- -[odometry] -algorithm = "mahony" # wheel, complementary, eskf, mahony -ticks_per_meter = 4464.0 # Encoder calibration -wheel_base = 0.233 # Distance between wheels (m) +## Origin -[matcher] -algorithm = "multi_res" # icp, p2l, correlative, multi_res, hybrid_icp, hybrid_p2l +DhruvaSLAM originates from VacuumTiger. -[loop_closure] -enabled = true -detection_interval = 5 # Check every N keyframes -min_keyframe_gap = 20 # Skip recent poses -optimization_threshold = 3 # Optimize after N loop closures - -[optimization] -solver = "sparse_cg" # dense_cholesky, sparse_cg -max_iterations = 50 -tolerance = 1e-6 -``` - -### Key Parameters - -| Parameter | Description | Typical Value | -|-----------|-------------|---------------| -| `odometry.algorithm` | Fusion algorithm | `mahony` (recommended) | -| `odometry.ticks_per_meter` | Encoder ticks per meter | 4464 (CRL-200S) | -| `odometry.wheel_base` | Distance between wheel centers | 0.233m | -| `matcher.algorithm` | Scan matching algorithm | `multi_res` (recommended) | -| `slam.min_range` | Minimum valid lidar range | 0.15m | -| `slam.max_range` | Maximum valid lidar range | 8.0m | - -## TCP Output Protocol - -The daemon publishes data on the configured port (default 5557) using length-prefixed Protobuf: - -``` -┌──────────────────┬─────────────────────┐ -│ Length (4 bytes) │ Protobuf Payload │ -│ Big-endian u32 │ (binary) │ -└──────────────────┴─────────────────────┘ -``` - -See `proto/dhruva.proto` for the complete schema. - -### Published Messages - -| Message Type | Rate | Description | -|--------------|------|-------------| -| `OdometryMessage` | 50Hz | Corrected pose (x, y, theta) | -| `SlamMapMessage` | 1Hz | Occupancy grid map (base64) | -| `SlamScanMessage` | 5Hz | Processed lidar scan with pose | -| `FeatureMessage` | 0.2Hz | Extracted lines and corners | - -## Module Structure - -``` -dhruva-slam/ -├── src/ -│ ├── main.rs # Entry point, config, main loop -│ │ -│ ├── core/ # Foundation layer -│ │ ├── types/ # Pose2D, Point2D, PointCloud2D, LaserScan -│ │ ├── math.rs # Angle normalization, interpolation -│ │ └── simd/ # SIMD vector types (Float4) -│ │ -│ ├── sensors/ # Sensor processing -│ │ ├── calibration.rs # Gyro bias estimation -│ │ ├── odometry/ # Wheel, Complementary, ESKF, Mahony -│ │ └── preprocessing/ # Range filter, downsampler, outlier removal -│ │ -│ ├── algorithms/ # Core algorithms -│ │ ├── matching/ # ICP, P2L-ICP, Correlative, Multi-Resolution -│ │ ├── mapping/ # OccupancyGrid, RayTracer, MapIntegrator -│ │ ├── planning/ # A* path planning -│ │ └── descriptors/ # LiDAR-IRIS for loop closure -│ │ -│ ├── engine/ # SLAM orchestration -│ │ ├── slam/ # OnlineSlam, Keyframe, Submap, Recovery -│ │ └── graph/ # PoseGraph, LoopDetector, SparseCG Optimizer -│ │ -│ ├── io/ # I/O infrastructure -│ │ ├── sangam_client.rs # SangamIO TCP/UDP client -│ │ ├── streaming/ # TCP/UDP publishers -│ │ ├── map_manager.rs # Map persistence -│ │ └── motion_controller.rs # Velocity commands -│ │ -│ ├── state/ # Multi-threaded state management -│ │ ├── shared.rs # Shared atomic state -│ │ └── commands.rs # Command processing -│ │ -│ ├── threads/ # Multi-threaded daemon architecture -│ │ ├── slam_thread.rs # Real-time SLAM processor -│ │ ├── publisher_thread.rs # Data stream publisher -│ │ ├── exploration_thread.rs # Autonomous exploration -│ │ └── navigation_thread.rs # Goal-based navigation -│ │ -│ ├── exploration/ # Frontier-based exploration -│ │ ├── frontier.rs # Frontier detection -│ │ └── strategy.rs # Selection strategies -│ │ -│ └── navigation/ # Path planning and execution -│ ├── navigator.rs # Navigation orchestrator -│ ├── path.rs # Path data structures -│ ├── state.rs # Navigation state machine -│ ├── target.rs # Goal management -│ └── features/ # Frontier, clearance, mapping features -│ -├── proto/ # Protobuf schemas -│ └── dhruva.proto # Output message definitions -│ -├── results/ # Benchmark results -├── docs/ # Design documents -│ -├── Cargo.toml # Dependencies -└── dhruva-slam.toml # Runtime configuration -``` - -## Data Flow - -``` -SangamIO (110Hz sensors, 5Hz lidar) - │ - ▼ -┌───────────────────────────────────────┐ -│ SangamClient │ -│ • TCP connection (Protobuf) │ -│ • Message deserialization │ -└───────────────────┬───────────────────┘ - │ - ┌───────────┴───────────┐ - ▼ ▼ -┌───────────────┐ ┌───────────────┐ -│ Odometry │ │ Preprocess │ -│ Pipeline │ │ + SLAM │ -│ │ │ │ -│ • Wheel odom │ │ • Range filter│ -│ • IMU fusion │─────►│ • Downsample │ -│ (Mahony/ESKF) │ │ • Match to map│ -│ │ │ • Update map │ -└───────┬───────┘ └───────┬───────┘ - │ │ - └──────────┬───────────┘ - ▼ - ┌──────────────────┐ - │ TCP Publisher │ - │ • Pose @ 50Hz │ - │ • Map @ 1Hz │ - │ • Scan @ 5Hz │ - │ • Features │ - └──────────────────┘ - │ - ▼ - Visualization Client - (Drishti) -``` - -## Testing - -```bash -# Run all tests -cargo test - -# Run specific test -cargo test test_wheel_odometry - -# Run with output -cargo test -- --nocapture -``` - -## Algorithm Selection Guide - -### Odometry Algorithms - -| Algorithm | Use Case | CPU | -|-----------|----------|-----| -| `wheel` | Testing, baseline | Very Low | -| `complementary` | Simple fusion | Low | -| `eskf` | Production with uncertainty | Medium | -| `mahony` | **Recommended** - auto gyro calibration | Low | - -### Scan Matchers - -| Algorithm | Use Case | CPU | Accuracy | -|-----------|----------|-----|----------| -| `icp` | Small displacements | Low | Good | -| `p2l` | Structured environments (walls) | Medium | Very Good | -| `correlative` | Large initial errors | High | Good | -| `multi_res` | **Recommended** - balanced | Medium | Good | -| `hybrid_icp` | Correlative + ICP refinement | High | Very Good | -| `hybrid_p2l` | Correlative + P2L refinement | High | Excellent | - -## Dependencies - -| Crate | Purpose | -|-------|---------| -| `prost` | Protobuf serialization | -| `serde` | Configuration serialization | -| `postcard` | Bag file format | -| `kiddo` | K-d tree for nearest neighbor | -| `basic_toml` | Config parsing | -| `env_logger` | Logging | -| `ctrlc` | Signal handling | -| `approx` | Floating-point comparison in tests | - -## Design Notes - -### Design Principles - -| Principle | Description | -|-----------|-------------| -| **Binary, Not Library** | Standalone daemon, not imported as a library | -| **Runtime Configuration** | Algorithm selection via TOML config | -| **Embedded-First** | Optimized for Allwinner A33 (ARM Cortex-A7) | -| **Testability** | Components unit-testable with mock data | - -### Platform Considerations (Allwinner A33) - -**Target specs:** Quad-core ARM Cortex-A7 @ 1.2 GHz, 512MB RAM, NEON SIMD - -**Performance Budget (5 Hz SLAM target):** - -| Component | Budget | Notes | -|-----------|--------|-------| -| Preprocessing | 5 ms | Range filter + downsample to 180 pts | -| Odometry Fusion | 1 ms | Mahony AHRS | -| Scan Matching | 50 ms | Multi-resolution correlative | -| Map Update | 10 ms | Log-odds with ray tracing | -| Feature Extraction | 20 ms | Line/corner detection | - -**Memory Budget (< 100 MB):** - -| Component | Allocation | -|-----------|------------| -| Occupancy Grid | 40 MB | -| Scan Buffers | 10 MB | -| K-d Trees | 10 MB | -| Working Memory | 40 MB | - -### Known Limitations - -- Loop closure detection is implemented but not fully integrated into pose correction -- Background graph optimization runs in publisher thread (could be separate thread for large maps) - -### Future Work - -See [IMPROVEMENTS.md](IMPROVEMENTS.md) for detailed roadmap: -- Full loop closure integration with pose graph correction -- Background optimization thread -- Multi-resolution map representation -- GICP scan matching - -## Related Documents - -- [IMPROVEMENTS.md](IMPROVEMENTS.md) - Technical improvement roadmap -- [docs/LOOP_CLOSURE_PROPOSAL.md](docs/LOOP_CLOSURE_PROPOSAL.md) - LiDAR-IRIS implementation plan - -## License - -See the root [LICENSE](../LICENSE) file. +Enuxia preserves upstream history and licensing while developing the M6 Pro integration and future navigation architecture independently. diff --git a/drishti/README.md b/drishti/README.md index 410012b..ff3c6ba 100644 --- a/drishti/README.md +++ b/drishti/README.md @@ -1,427 +1,95 @@ -# Drishti - Robot Diagnostic Tool +# Drishti — Enuxia Vacuum engineering console -PyQt5-based real-time visualization and control client for the CRL-200S robotic vacuum with two operating modes: +Drishti is the engineering and diagnostic interface used during development of Enuxia Vacuum. -- **Sensor Mode**: Connect to SangamIO for raw sensor visualization (robot view) -- **SLAM Mode**: Connect to DhruvaSLAM for map visualization and autonomous navigation +It provides real-time visibility into the robot and SLAM stack. + +Drishti originates from the upstream [VacuumTiger](https://github.com/codetiger/VacuumTiger) project and remains a development tool in the Enuxia fork. + +It is **not intended to be the final consumer application**. + +--- + +## Modes + +### Sensor mode + +Connects directly to SangamIO for: + +- sensor telemetry; +- LiDAR visualization; +- wheel/encoder data; +- bumper and cliff state; +- battery information; +- actuator testing; +- low-level diagnostics. + +### SLAM mode + +Connects to DhruvaSLAM for: + +- occupancy-grid visualization; +- robot pose; +- LiDAR overlay; +- navigation paths; +- mapping commands; +- mapping and navigation diagnostics. + +--- ## Installation ```bash -cd drishti -python -m venv venv -source venv/bin/activate # or venv\Scripts\activate on Windows +python -m venv .venv +source .venv/bin/activate + pip install -r requirements.txt ``` -### Dependencies - -- `protobuf>=4.0` - Protocol buffer messaging -- `PyQt5>=5.15.0` - GUI framework -- `pyqtgraph>=0.13.0` - High-performance plotting/visualization -- `PyOpenGL>=3.1.6` - 3D graphics via OpenGL -- `numpy>=1.21.0` - Numerical computing - -## Usage - -### Sensor Mode - -Connect to SangamIO for raw sensor data visualization: +Show available CLI options: ```bash -python drishti_ui.py sensor --robot 192.168.68.101 +python drishti_ui.py --help ``` -Options: -| Option | Default | Description | -|--------|---------|-------------| -| `--robot` | 192.168.68.101 | SangamIO hostname or IP | -| `--port` | 5555 | SangamIO port (TCP+UDP) | -| `--log-raw-packets` | disabled | Log raw GD32 status packets | -| `--verbose` | disabled | Enable debug logging | +--- -### SLAM Mode +## Role in Enuxia Vacuum -Connect to DhruvaSLAM for SLAM visualization and control: +Drishti should remain an engineering console. -```bash -python drishti_ui.py slam --host localhost +The future user-facing interface is expected to communicate with the local Enuxia Vacuum backend through a stable API/WebSocket layer. + +```text +Drishti + │ + └── engineering/debug + +PWA / Android + │ + └── user application + │ + ▼ + Enuxia Vacuum API + │ + ▼ + missions / mapping / cleaning ``` -Options: -| Option | Default | Description | -|--------|---------|-------------| -| `--host` | localhost | DhruvaSLAM hostname | -| `--port` | 5557 | DhruvaSLAM port (TCP+UDP) | -| `--command-port` | same as --port | Optional separate command port | -| `--verbose` | disabled | Enable debug logging | +--- -### Console Demo (Legacy) +## Known development caveats -```bash -python drishti.py --robot 192.168.68.101 --verbose -``` +UI state should not be treated as the sole source of truth during development. -## Features +Some current mapping/navigation states still require consolidation in the backend state model. -### Sensor Mode +Physical motion tests must always be supervised. -**3D Visualization:** -- Full-screen OpenGL wireframe robot model -- Animated wheels (spin based on encoder data) -- Animated lidar turret (spins when enabled) -- Animated brushes (main and side, when spinning) -- Animated vacuum fan (when active) -- Bumper indicators (green normal, red triggered) -- Cliff sensor indicators (4 pairs) -- Battery bar with charging animation -- IMU orientation overlay (roll/pitch/yaw) -- Multiple camera presets (iso, top, front, side) -- Auto-rotate camera when idle +--- -**Hardware Control:** -- Drive motor enable/disable with speed control -- Lidar enable/disable with scan counter -- Vacuum pump speed 0-100% -- Main brush speed 0-100% -- Side brush speed 0-100% -- Emergency stop (immediate watchdog reset) +## Origin -**Sensor Display:** -- Battery level + charging state -- Bumper state (left/right) -- Cliff sensors (4 pairs) -- Wheel encoder ticks -- Lidar range and quality -- IMU gyro/accelerometer data -- Motor current, voltage status -- Error codes and diagnostics +Drishti originates from VacuumTiger. -![Drishti UI](drishti.png) - -### SLAM Mode - -**Mapping:** -- Start/stop mapping sessions -- Clear current map -- Save maps to persistent storage -- List all saved maps with metadata (size, creation time, area) -- Enable/rename/delete saved maps - -**Visualization:** -- Real-time occupancy grid (10Hz update) -- Robot pose display (x, y, heading) -- Trajectory trail (2000-point history) -- Current lidar scan overlay -- Navigation path visualization -- Goal marker display -- Grid overlay with axis labels - -**Navigation:** -- Set goal via Ctrl+Click on map -- Visualize planned path (waypoints) -- Track navigation progress (distance remaining, ETA) -- Cancel active navigation goal - -**Camera Control:** -- Auto-follow mode (centers on robot) -- Manual pan (click+drag) -- Manual zoom (mouse wheel) - -## Keyboard Shortcuts - -### Sensor Mode - -| Key | Action | -|-----|--------| -| `M` | Toggle drive motor (enable/disable) | -| `L` | Toggle lidar | -| `V` | Toggle vacuum pump | -| `B` | Toggle main brush | -| `S` | Toggle side brush | -| `↑` | Drive forward | -| `↓` | Drive backward | -| `←` | Turn left | -| `→` | Turn right | -| `↑+←` | Drive forward while turning left | -| `↑+→` | Drive forward while turning right | -| `↓+←` | Drive backward while turning left | -| `↓+→` | Drive backward while turning right | -| `Space` | Emergency stop (immediate reset) | -| `Esc` | Quit application | - -### SLAM Mode - -| Key/Action | Description | -|------------|-------------| -| `Ctrl+Click` | Set navigation goal on map | -| Mouse wheel | Zoom map in/out | -| Click+drag | Pan map view | -| `Esc` | Quit application | - -## Architecture - -### Directory Structure - -``` -drishti/ -├── drishti_ui.py # Entry point (dual-mode launcher) -├── drishti.py # Legacy console client -├── debug_proto.py # Proto message debug tool -├── requirements.txt # Python dependencies -├── proto/ # Protocol buffer definitions -│ ├── sangamio_pb2.py # SangamIO protobuf (generated) -│ └── dhruva_pb2.py # DhruvaSLAM protobuf (generated) -└── ui/ - ├── main_window.py # Sensor mode main window - ├── slam_main_window.py # SLAM mode main window - ├── threads/ - │ ├── telemetry_thread.py # SangamIO data (TCP+UDP) - │ ├── slam_thread.py # DhruvaSLAM data (TCP) - │ └── odometry_thread.py # Legacy SLAM odometry - ├── widgets/ - │ ├── robot_3d_view.py # 3D wireframe visualization - │ ├── robot_3d_geometry.py # 3D geometry primitives - │ ├── lidar_scan_overlay.py # Polar lidar display - │ ├── slam_view.py # Map + trajectory view - │ ├── control_panel.py # Sensor mode controls - │ ├── slam_control_panel.py # SLAM mode controls - │ ├── navigation_panel.py # Navigation status - │ ├── diagnostics_panel.py # Timing diagnostics - │ ├── compact_actuator.py # Actuator controls - │ └── collapsible_group.py # Collapsible sections - └── processors/ - └── imu_processor.py # Mahony AHRS filter -``` - -### Thread Model - -**Sensor Mode (TelemetryThread):** -1. Connects via TCP (port 5555) for registration -2. Receives UDP unicast sensor data (port 5555) -3. TCP connection kept alive for sending commands -4. Emits: `sensor_data_received(dict)`, `connection_status(bool, str)` - -**SLAM Mode (SlamThread):** -1. Connects via TCP (port 5557, unified architecture) -2. Two separate connections: streaming (receive-only) and commands (request/response) -3. Emits: `robot_status_received`, `sensor_status_received`, `current_map_received`, `map_list_received`, `navigation_status_received` - -## Network Protocol - -### Sensor Mode (SangamIO) - -Uses hybrid UDP/TCP architecture on same port (5555): -- **UDP**: Sensor streaming (low latency, no head-of-line blocking) -- **TCP**: Commands (reliable delivery) - -**Client Registration:** -1. Client connects TCP to port 5555 -2. Server records client IP address -3. UDP packets sent to client on same port -4. When TCP disconnects, UDP streaming stops - -**Message Format:** Length-prefixed Protobuf -``` -[4-byte length (big-endian)][Protobuf payload] -``` - -**UDP Topics (Outbound @ 110Hz):** -- `sensors/sensor_status`: All 13 sensors -- `sensors/device_version`: Version info (one-time) -- `sensors/lidar`: LidarScan @ 5Hz - -**TCP Commands (Inbound):** -- `ComponentControl`: Enable/disable/configure components -- `ProtocolSync`: Synchronization -- `Shutdown`: Graceful shutdown - -**Sensor Data Available:** -| Sensor | Type | Description | -|--------|------|-------------| -| `is_charging` | bool | Charging state | -| `battery_level` | u8 | Battery percentage (0-100) | -| `bumper_left` | bool | Left bumper triggered | -| `bumper_right` | bool | Right bumper triggered | -| `cliff_left` | bool | Left cliff detected | -| `cliff_center` | bool | Center cliff detected | -| `cliff_right` | bool | Right cliff detected | -| `wheel_left` | u32 | Left encoder ticks | -| `wheel_right` | u32 | Right encoder ticks | -| `lidar_speed` | u16 | Lidar RPM (0 when disabled) | -| `gyro_x/y/z` | i16 | Gyroscope (0.1°/s units) | -| `tilt_x/y/z` | i16 | Accelerometer (mG units) | - -**Command Examples:** - -Enable drive motor: -```python -cmd = sangamio_pb2.Command() -cmd.component_control.id = "drive" -cmd.component_control.action.type = ComponentAction.ENABLE -``` - -Set velocity: -```python -cmd.component_control.id = "drive" -cmd.component_control.action.type = ComponentAction.CONFIGURE -cmd.component_control.action.config["linear"].f32_val = 0.2 -cmd.component_control.action.config["angular"].f32_val = 0.0 -``` - -### SLAM Mode (DhruvaSLAM) - -Uses unified port architecture (default: 5557) for both streaming and commands. - -**Protocol:** Length-prefixed Protobuf (see `dhruva-slam/proto/dhruva.proto`) - -**DhruvaStream (Inbound):** -| Message | Rate | Content | -|---------|------|---------| -| `robot_status` | 10Hz | Pose, state, battery, mapping progress | -| `sensor_status` | 10Hz | Lidar, encoders, IMU | -| `current_map` | 1Hz | Occupancy grid | -| `map_list` | on change | Saved maps | -| `navigation_status` | 5Hz | Path, goal, progress | - -**DhruvaCommand (Outbound):** -| Command | Description | -|---------|-------------| -| `StartMapping(map_name)` | Begin SLAM session | -| `StopMapping(save)` | End session, optionally save | -| `ClearMap()` | Discard current map | -| `EnableMap(map_id)` | Load saved map | -| `RenameMap(map_id, new_name)` | Rename saved map | -| `DeleteMap(map_id)` | Delete saved map | -| `SetGoal(x, y, theta)` | Set navigation goal | -| `CancelGoal()` | Stop navigation | -| `EmergencyStop()` | Halt all motion | - -**Occupancy Grid Encoding:** -| Value | Meaning | Display Color | -|-------|---------|---------------| -| 0 | Free space | Light gray | -| 100 | Occupied | White | -| 255 | Unknown | Dark gray | - -## IMU Processing - -The `imu_processor.py` implements a **Mahony AHRS filter** with quaternion-based orientation estimation. - -**Calibration:** -- Collects bias samples for ~3 seconds (330 samples @ 110Hz) -- Computes average gyro bias to subtract from measurements -- Returns zeros during calibration phase - -**Filter Parameters:** -- `KP = 5.0`: Proportional gain for fast settling -- `KI = 0.1`: Integral gain for bias correction - -**Output:** Roll, pitch, yaw in degrees (ROS REP-103 convention) - -## Coordinate Conventions - -All coordinates use **ROS REP-103** standard: - -``` - +X (forward) - ↑ - | -←------+------→ +Y (left) - | - +Z (up, out of page) - -Angles: 0 rad = +X direction, increases counter-clockwise -``` - -- **Roll**: Rotation around X (left/right tilt) -- **Pitch**: Rotation around Y (nose up/down) -- **Yaw**: Rotation around Z (heading, CCW positive) - -## Debug Utilities - -### debug_proto.py - -Capture and analyze DhruvaSLAM proto messages: - -```bash -python debug_proto.py -``` - -- Connects to localhost:5557 -- Sends StartMapping command -- Analyzes received proto messages -- Saves raw bytes to `/tmp/proto_message.bin` -- Useful for debugging protocol issues - -## Troubleshooting - -### Sensor Mode - -**No data received:** -- Verify SangamIO is running: `ssh root@vacuum "ps aux | grep sangam"` -- Check network: `ping 192.168.68.101` -- Verify port 5555 is accessible (both TCP and UDP) -- Check firewall allows UDP on port 5555 - -**Connection refused:** -- Robot may not be running SangamIO -- AuxCtrl may still be running (rename it first) - -**Drive not responding:** -- Make sure motor is enabled first (press `M`) -- Check status bar for "Drive ENABLED" -- Verify no error codes displayed - -**3D view not rendering:** -- Ensure PyOpenGL is installed -- Check OpenGL drivers are up to date - -### SLAM Mode - -**No map data:** -- Verify DhruvaSLAM is running: `ps aux | grep dhruva` -- Check connection status in UI status bar -- Start mapping session using control panel - -**Parse errors:** -- Failed proto messages saved to `/tmp/failed_proto.bin` -- Use `debug_proto.py` to analyze protocol issues - -**Navigation not working:** -- Ensure a map is loaded (Enable Map) -- Robot must be localized first -- Check localization confidence indicator - -**Map not updating:** -- Verify mapping is active (state = "Mapping") -- Check trajectory is being drawn -- Large maps may have slower update rate - -## UI Components - -### Control Panel (Sensor Mode) - -Collapsible sections: -- **Motion Control**: Drive motor enable + speed slider -- **Cleaning Systems**: Vacuum, main brush, side brush -- **Sensors**: Lidar toggle, cliff calibration -- **System**: LED control, dustbox status -- **Calibration**: IMU, compass calibration -- **Power Management**: Voltage displays - -### SLAM Control Panel - -- **Status Display**: State, active map, pose, confidence -- **Mapping Controls**: Start/Stop/Clear/Emergency Stop -- **Map Management**: Enable/Rename/Delete -- **Map List**: Scrollable list with metadata - -### Navigation Panel - -- Current state (Idle, Planning, Navigating, Goal Reached) -- Target description -- Distance remaining + ETA -- Progress bar -- Waypoint counter -- Cancel Navigation button +Enuxia preserves the upstream history and applicable notices while continuing development as part of Enuxia Vacuum. diff --git a/sangam-io/README.md b/sangam-io/README.md index b049716..34b444a 100644 --- a/sangam-io/README.md +++ b/sangam-io/README.md @@ -1,646 +1,121 @@ -# SangamIO +# SangamIO — Enuxia Vacuum hardware layer -Hardware abstraction daemon for robotic vacuum platforms, providing real-time sensor streaming via UDP and command control via TCP. +SangamIO is the hardware abstraction daemon used by Enuxia Vacuum. -Supported hardware: +It runs close to the robot hardware and translates platform-specific protocols into a stable interface for SLAM, navigation, diagnostics and future mission-control software. -- CRL-200S -- Roborock S5 Max (rooted firmware `4.1.2_1668`) +This component originates from the upstream [VacuumTiger](https://github.com/codetiger/VacuumTiger) project and is being extended and validated by Enuxia on real robotic vacuum hardware. -## Overview +--- -SangamIO acts as a bridge between high-level clients (SLAM, visualization) and low-level hardware (GD32 motor controller, Delta-2D lidar). It runs as a standalone daemon on the robot, streaming sensor data at 110Hz via UDP and accepting commands over TCP. +## Role -``` -┌─────────────────────────────────────────────────┐ -│ Client Applications (SLAM/Drishti) │ -└─────────────┬───────────────┬───────────────────┘ - │ UDP 5555 │ TCP 5555 - │ (sensors) │ (commands) -┌─────────────▼───────────────▼───────────────────┐ -│ SangamIO Daemon (Robot) │ -│ • Telemetry streaming @ 110Hz (UDP unicast) │ -│ • Lidar streaming @ 5Hz (360° scans) │ -│ • Command processing (TCP, reliable) │ -│ • Real-time control loop (20ms heartbeat) │ -└─────────────┬───────────────┬───────────────────┘ - │ /dev/ttyS3 │ /dev/ttyS1 - ┌──────▼─────────┐ ┌──▼────────────┐ - │ GD32F103 MCU │ │ Delta-2D Lidar│ - │ (Motor Control)│ │ (360° Scan) │ - └────────────────┘ └───────────────┘ +```text +Navigation / SLAM / diagnostics + │ + ▼ + SangamIO API + │ + ┌───────┴────────┐ + ▼ ▼ + Robot MCU LiDAR + / actuators / sensors ``` -### Key Specifications +SangamIO owns the hardware-specific details so higher-level software does not have to. -| Property | Value | -|----------|-------| -| Language | Rust 2024 edition | -| Version | 0.3.0 | -| Target | ARM (armv7-unknown-linux-musleabihf) or host (simulation) | -| Binary Size | ~350KB (statically linked) | -| Memory Usage | <10MB RSS | -| CPU Usage | <1% on Allwinner A33 | +Responsibilities include: -## Building +- low-level robot protocol handling; +- sensor telemetry; +- wheel control; +- vacuum and brush control; +- LiDAR control; +- platform-specific initialization; +- TCP command transport; +- UDP sensor streaming. -### Development (Host Machine) +--- + +## Current Enuxia development platform + +### Proscenic M6 Pro / 3irobotix CRL-200S + +Validated hardware: + +- Allwinner A33 Linux SoC; +- GD32F103 motor/control MCU; +- Delta-2D LiDAR; +- wheel encoders; +- IMU; +- left/right bumpers; +- cliff sensors; +- battery and charging telemetry; +- cleaning actuators. + +Relevant device nodes: + +```text +/dev/ttyS3 GD32 MCU +/dev/ttyS1 Delta-2D LiDAR +``` + +--- + +## Enuxia CRL-200S work + +The fork contains additional changes validated on physical M6 Pro hardware. + +### GD32 cold-boot initialization + +The original initialization path was not always sufficient after a true power cycle. + +The Enuxia baseline contains a revised initialization/wake sequence that restores continuous MCU telemetry after cold boot. + +### Delta-2D packet decoding + +The Delta-2D parser was corrected after validating the packet layout on real hardware. + +The measurement payload uses: + +```text +byte 0 motor speed +byte 1..2 offset angle +byte 3..4 start angle +byte 5.. 3-byte samples +``` + +Angular sampling is derived from the scan structure rather than treating the offset-angle field as the per-sample increment. + +This produces stable full-revolution scans on the M6 Pro. + +--- + +## Build ```bash -# Build for real hardware -cargo build --release --target armv7-unknown-linux-musleabihf - -# Build for simulation (mock device) -cargo build --release --features mock - -# Run with configuration file -cargo run --release --features mock -- mock.toml -cargo run --release --features mock -- --config mock.toml - -# Enable verbose logging -RUST_LOG=debug cargo run --release --features mock - -# Run tests -cargo test --features mock -``` - -### Production (ARM Robot) - -```bash -# Add ARM target (one-time) rustup target add armv7-unknown-linux-musleabihf -# Build for ARM (real hardware) -cargo build --release --target armv7-unknown-linux-musleabihf - -# Strip debug symbols (reduces size ~40%) -arm-linux-gnueabihf-strip \ - target/armv7-unknown-linux-musleabihf/release/sangam-io +cargo build --release \ + --target armv7-unknown-linux-musleabihf ``` -The Roborock S5 Max stock image uses glibc rather than musl: +A suitable ARM cross-linker may be required depending on the host system. -```bash -rustup target add armv7-unknown-linux-gnueabihf -cargo build --release --target armv7-unknown-linux-gnueabihf -``` +--- -The S5 Max sequence helper is firmware-specific. Its current offset is for -firmware `4.1.2_1668` and `libuart_api` Build ID -`8d639ff160b8766b7bb87e848e9b0a9c0ff12a08`; re-derive the offset before using -it with another firmware build. +## Safety -### Feature Flags +SangamIO can directly control wheels and cleaning actuators. -| Feature | Description | -|---------|-------------| -| (default) | Real hardware support only | -| `mock` | Enable mock device simulation | +Do not run two hardware controllers simultaneously if both attempt to own the same MCU/UARTs. -## Deployment +Always supervise real-hardware tests and keep a reliable stop path available. -**SSH**: `root@vacuum` (see project docs for credentials) +--- -```bash -# Deploy binary (device lacks sftp-server, use cat over SSH) -cat target/armv7-unknown-linux-musleabihf/release/sangam-io | \ - ssh root@vacuum "cat > /usr/sbin/sangamio && chmod +x /usr/sbin/sangamio" +## Origin -# Deploy configuration -ssh root@vacuum "cat > /etc/sangamio.toml" < sangamio.toml +SangamIO originates from the VacuumTiger project. -# Disable original firmware (rename to prevent auto-restart) -ssh root@vacuum "mv /usr/sbin/AuxCtrl /usr/sbin/AuxCtrl.bak && killall -9 AuxCtrl" - -# Run daemon -ssh root@vacuum "RUST_LOG=info /usr/sbin/sangamio" -``` - -> **Important**: Always overwrite `/usr/sbin/sangamio` directly. The robot monitor auto-restarts processes, so renaming AuxCtrl prevents conflicts. - -### Roborock S5 Max live takeover - -The S5 Max driver must exclusively own `/dev/ttyS2`, `/dev/ttyS1`, -`/dev/lds_motor`, and `/dev/watchdog`. The stock `rr_loader` and `WatchDoge` -processes therefore cannot run alongside SangamIO. The MCU also rejects stale -transmit sequence numbers, so a live handoff must capture the last stock -sequence while `rr_loader` is frozen. - -The procedure below was validated on rooted firmware `4.1.2_1668`. Keep a -second terminal open. A forced reboot is the recovery path and restores the -stock stack because the `/etc/inittab` override is a temporary bind mount. - -Build and upload the daemon, helper, and base configuration: - -```bash -TARGET=armv7-unknown-linux-gnueabihf -cargo build --release --target "$TARGET" \ - --bin sangam-io --bin roborock_s5max_sequence -scp -O \ - "target/$TARGET/release/sangam-io" \ - "target/$TARGET/release/roborock_s5max_sequence" \ - roborock-s5max.toml root@vacuum:/mnt/data/ -``` - -Then perform the handoff as root on the robot: - -```sh -BIN=/mnt/data/sangam-io -SEQ=/mnt/data/roborock_s5max_sequence -BASE=/mnt/data/roborock-s5max.toml -LIVE=/tmp/roborock-s5max-live.toml - -# Every exit after this point restores the stock stack by rebooting. -recover() -{ - trap - EXIT HUP INT TERM - sync - /sbin/reboot -f -} -trap recover EXIT HUP INT TERM - -chmod 755 "$BIN" "$SEQ" -WD_PIDS="$(pidof WatchDoge)" -RR_PIDS="$(pidof rr_loader)" - -# Select the rr_loader instance that owns the MCU UART. -RR_PID="" -for pid in $RR_PIDS; do - for fd in /proc/$pid/fd/*; do - if [ "$(readlink "$fd" 2>/dev/null)" = /dev/ttyS2 ]; then - RR_PID="$pid" - break 2 - fi - done -done -test -n "$WD_PIDS" && test -n "$RR_PID" || exit 1 - -# Prevent init from immediately respawning WatchDoge during takeover. -grep -v WatchDoge /etc/inittab > /tmp/inittab.no-watchdog -mount --bind /tmp/inittab.no-watchdog /etc/inittab -kill -HUP 1 -sleep 1 - -# Freeze every process that can race the handoff, then read the sequence. -kill -STOP $WD_PIDS $RR_PIDS -kill -STOP $(pidof RoboController AppProxy 2>/dev/null) 2>/dev/null -LAST_SEQUENCE="$($SEQ "$RR_PID")" || exit 1 -echo "Captured stock MCU sequence: $LAST_SEQUENCE" - -# Let TERM cleanup release the watchdog, UARTs, and LDS motor. -kill -TERM $WD_PIDS $RR_PIDS -kill -CONT $WD_PIDS $RR_PIDS -sleep 2 -for pid in $WD_PIDS $RR_PIDS; do - kill -0 "$pid" 2>/dev/null && kill -KILL "$pid" -done - -sed "s/^# last_stock_sequence = 0/last_stock_sequence = $LAST_SEQUENCE/" \ - "$BASE" > "$LIVE" -grep '^last_stock_sequence' "$LIVE" >/dev/null || exit 1 -RUST_LOG=info "$BIN" --config "$LIVE" -``` - -For the default read-only configuration, leave `allow_actuation = false`. -This provides MCU telemetry only: starting the LDS requires charger and -subsystem-control writes, so lidar remains disabled behind the actuation gate. -Set `allow_actuation = true` only after reviewing the safety limits and only -when a command client will control the robot. - -To stop SangamIO, send `SIGINT` from the second terminal. Its ordered shutdown -stops wheels, cleaning motors, and the LDS; the shell's exit trap then reboots -the robot and restores all stock processes: - -```sh -kill -INT $(pidof sangam-io) -``` - -If the daemon or SSH session fails, use `/sbin/reboot -f` from the second -terminal. If SSH is unavailable, the hardware watchdog should reboot the robot -after its 16-second timeout. Do not try to restart only part of the stock stack -after a takeover. - -## Configuration - -For the Roborock S5 Max, start with `roborock-s5max.toml`. Actuation is -disabled by default and live takeover must continue the stock MCU transmit -sequence using `roborock_s5max_sequence`. - -The S5 Max publishes its detailed native values alongside the established -`sensor_status` keys expected by Dhruva clients: - -| Common key | S5 Max source | -|------------|---------------| -| `wheel_left`, `wheel_right` | Low 16 bits of the signed 32-bit monotonic odometry counters; wrapping deltas remain valid | -| `gyro_x`, `gyro_y`, `gyro_z` | MCU angular rates converted from rad/s to signed milliradians/s | -| `bumper_left`, `bumper_right` | Conservative aliases: both become true when either raw bumper channel is active | -| `cliff_left_side`, `cliff_left_front`, `cliff_right_front`, `cliff_right_side` | Conservative aliases: all become true when any raw cliff channel is active | -| `battery_voltage`, `battery_level` | MCU BMS voltage and state-of-charge report | -| `is_dock_connected`, `is_charging` | MCU dock-state report | -| `dustbox_attached` | MCU dustbin presence report | - -The conservative bumper and cliff aliases avoid understating a safety event; -their physical channel ordering has not yet been proven. Exact values remain -available as `bumper_raw` and `cliff_raw`. Native signed wheel counters remain -available as `wheel_left_ticks` and `wheel_right_ticks`. - -The `c0` drive command fields are mean encoder ticks per 20 ms and body yaw in -radians per second. This was established from stock captures (forced turns use -exactly `pi/2`, while the measured yaw rate settles near `1.57`) and exercised -on hardware by the bounded wheel-deadman test and autonomous mapping runs. - -Edit `sangamio.toml`: - -```toml -[device] -type = "crl200s" -name = "CRL-200S Vacuum Robot" - -[device.hardware] -gd32_port = "/dev/ttyS3" -lidar_port = "/dev/ttyS1" -heartbeat_interval_ms = 20 - -# Coordinate frame transforms (ROS REP-103) -[device.hardware.frame_transforms.lidar] -scale = -1.0 # Convert CW to CCW -offset = 3.14159265 # Rotate 180° (lidar mounted backward) - -[device.hardware.frame_transforms.imu_gyro] -x = [2, 1] # output_x (Roll) = input_z -y = [1, 1] # output_y (Pitch) = input_y -z = [0, -1] # output_z (Yaw) = input_x * -1 - -[network] -bind_address = "0.0.0.0:5555" -``` - -| Parameter | Description | Valid Values | -|-----------|-------------|--------------| -| `gd32_port` | Motor controller serial port | Device path | -| `lidar_port` | Lidar sensor serial port | Device path | -| `heartbeat_interval_ms` | Safety heartbeat interval | **20-50ms only** | -| `bind_address` | TCP server bind address | `host:port` | -| `frame_transforms` | Coordinate transforms (optional) | See below | - -> **Critical**: The GD32 has a hardware watchdog requiring heartbeats every 20-50ms. Values outside this range will cause motors to stop. - -### Coordinate Frame Transforms - -SangamIO transforms raw sensor data to **ROS REP-103** convention: -- **X = forward** (direction robot drives) -- **Y = left** (port side) -- **Z = up** -- **Angles = counter-clockwise (CCW) positive** - -All transforms default to identity (no change) if not specified. The CRL-200S requires transforms because: -- **Lidar**: Mounted backward (0° = rear), clockwise angles -- **IMU**: Non-standard axis mapping (gyro_x = yaw, not roll) - -| Transform | Type | Formula | CRL-200S Value | -|-----------|------|---------|----------------| -| `lidar` | AffineTransform1D | `out = scale * in + offset` | `scale=-1, offset=π` | -| `imu_gyro` | AxisTransform3D | `[source_axis, sign]` | Remap + flip yaw | -| `imu_accel` | AxisTransform3D | `[source_axis, sign]` | Identity (default) - -## Network Protocol - -SangamIO uses a hybrid UDP/TCP architecture for optimal performance: - -| Channel | Protocol | Purpose | Why | -|---------|----------|---------|-----| -| Sensor data | UDP unicast | Low-latency streaming | No head-of-line blocking | -| Commands | TCP | Reliable control | Guaranteed delivery | - -### Message Format - -Both UDP and TCP use length-prefixed Protobuf framing: - -``` -┌──────────────────┬─────────────────────┐ -│ Length (4 bytes) │ Protobuf Payload │ -│ Big-endian u32 │ (binary) │ -└──────────────────┴─────────────────────┘ -``` - -See `proto/sangamio.proto` for the complete schema. - -### Topics (UDP Streaming) - -| Topic | Rate | Size | Description | -|-------|------|------|-------------| -| `sensors/sensor_status` | 110Hz | ~150B | Encoders, IMU, bumpers, cliffs, battery | -| `sensors/lidar` | 5Hz | ~2KB | 360° point cloud (angle, distance, quality) | -| `sensors/device_version` | Once | ~50B | Firmware version info | - -### Commands (TCP) - -| Command | Description | -|---------|-------------| -| `ComponentControl::drive` | Set linear/angular velocity | -| `ComponentControl::lidar` | Enable/disable lidar motor | -| `ComponentControl::vacuum` | Control vacuum motor (0-100%) | -| `ComponentControl::*_brush` | Control brushes (0-100%) | -| `Shutdown` | Graceful daemon shutdown | - -### Client Registration - -UDP uses unicast (not broadcast). Clients are registered automatically: - -1. Client connects TCP to port 5555 -2. Server records client IP address -3. UDP packets are sent to client on same port -4. When TCP disconnects, UDP streaming stops - -### Python Client Example - -```python -import socket -import struct -import threading -from proto import sangamio_pb2 - -ROBOT_IP = '192.168.68.101' -PORT = 5555 - -# TCP for commands -tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -tcp_sock.connect((ROBOT_IP, PORT)) - -# UDP for sensor data (same port) -udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -udp_sock.bind(('0.0.0.0', PORT)) - -def receive_sensors(): - while True: - data, addr = udp_sock.recvfrom(4096) - length = struct.unpack('>I', data[:4])[0] - msg = sangamio_pb2.Message() - msg.ParseFromString(data[4:4+length]) - - if msg.topic == 'sensors/sensor_status': - sg = msg.sensor_group - print(f"Battery: {sg.values['battery_level'].u8_val}%") - -# Start receiving in background -threading.Thread(target=receive_sensors, daemon=True).start() - -# Send commands via TCP -def send_command(cmd): - payload = cmd.SerializeToString() - tcp_sock.send(struct.pack('>I', len(payload)) + payload) -``` - -## Module Structure - -``` -sangam-io/ -├── src/ -│ ├── main.rs # Entry point, TCP/UDP listener -│ ├── config.rs # TOML configuration loading -│ ├── error.rs # Error types -│ │ -│ ├── core/ -│ │ ├── driver.rs # DeviceDriver trait -│ │ └── types.rs # SensorValue, Command types -│ │ -│ ├── devices/ -│ │ ├── mod.rs # Device factory (crl200s, mock) -│ │ ├── crl200s/ # Real hardware driver -│ │ │ ├── mod.rs # CRL200S orchestrator -│ │ │ ├── constants.rs # Hardware constants -│ │ │ ├── COMMANDS.md # GD32 command reference -│ │ │ ├── SENSORSTATUS.md # Sensor packet docs -│ │ │ ├── gd32/ # Motor controller driver -│ │ │ │ ├── mod.rs # Driver core -│ │ │ │ ├── commands.rs # Command handlers -│ │ │ │ ├── heartbeat.rs # 20ms watchdog -│ │ │ │ ├── reader.rs # Status parsing -│ │ │ │ ├── packet.rs # Packet encoding -│ │ │ │ ├── protocol.rs # Packet framing -│ │ │ │ └── state.rs # Atomic state -│ │ │ └── delta2d/ # Lidar driver -│ │ │ ├── mod.rs # Driver core -│ │ │ └── protocol.rs # Packet parsing -│ │ │ -│ │ └── mock/ # Simulation driver (--features mock) -│ │ ├── mod.rs # MockDriver, simulation loop -│ │ ├── config.rs # SimulationConfig structs -│ │ ├── physics.rs # Differential drive kinematics -│ │ ├── lidar_sim.rs # Ray-casting lidar -│ │ ├── imu_sim.rs # IMU with noise -│ │ ├── encoder_sim.rs # Encoder with slip -│ │ ├── sensor_sim.rs # Bumpers, cliffs -│ │ ├── map_loader.rs # PGM+YAML loader -│ │ └── noise.rs # Noise generator -│ │ -│ └── streaming/ -│ ├── mod.rs # Streaming module docs -│ ├── wire.rs # Protobuf serialization -│ ├── udp_publisher.rs # UDP sensor streaming -│ └── tcp_receiver.rs # TCP command handling -│ -├── proto/ -│ └── sangamio.proto # Protobuf schema -├── maps/ # Example simulation maps -│ ├── example.yaml # Map metadata -│ └── example.pgm # Occupancy grid -├── sangamio.toml # Hardware configuration -└── mock.toml # Simulation configuration -``` - -## Supported Components - -Commands use a unified `ComponentControl` interface: - -| Component | Enable | Disable | Configure | -|-----------|--------|---------|-----------| -| `drive` | Mode 0x02 | Stop + Mode 0x00 | `linear`, `angular` (m/s, rad/s) | -| `vacuum` | 100% | 0% | `speed` (0-100%) | -| `main_brush` | 100% (S5 Max: stock 71%) | 0% | `speed` (0-100%) | -| `side_brush` | 100% (S5 Max: stock 30%) | 0% | `speed` (0-100%) | -| `water_pump` | 100% | 0% | `speed` (0-100%) | -| `lidar` | Power on | Power off | - | -| `led` | - | - | `state` (0-18) | - -## Sensors - -The `sensor_status` group includes: - -| Sensor | Type | Description | -|--------|------|-------------| -| `bumper_left`, `bumper_right` | Bool | Bumper contact | -| `cliff_left_side`, `cliff_left_front` | Bool | Left cliff sensors | -| `cliff_right_front`, `cliff_right_side` | Bool | Right cliff sensors | -| `is_charging` | Bool | Charging state | -| `battery_voltage` | F32 | Battery voltage (V) | -| `battery_level` | U8 | Estimated charge (0-100%) | -| `wheel_left`, `wheel_right` | U16 | Encoder ticks | -| `gyro_x`, `gyro_y`, `gyro_z` | I16 | Angular velocity (Roll, Pitch, Yaw after transform) | -| `accel_x`, `accel_y`, `accel_z` | I16 | Acceleration (raw units) | -| `tilt_x`, `tilt_y`, `tilt_z` | I16 | LP-filtered gravity vector | -| `start_button`, `dock_button` | U16 | Button press states | -| `dustbox_attached` | Bool | Dustbox present | -| `is_dock_connected` | Bool | On charging dock | - -> **Note**: IMU values are in ROS REP-103 frame after applying `frame_transforms`. - -## Thread Model - -### Real Hardware (crl200s) - -| Thread | Purpose | Timing | -|--------|---------|--------| -| Main | TCP listener, client accept | - | -| GD32 Heartbeat | Safety watchdog | 20ms ±2ms | -| GD32 Reader | Status parsing → channel | Continuous | -| Lidar Reader | Scan accumulation | Continuous | -| UDP Publisher | Sensor streaming | <1ms loop | -| TCP Receiver | Per-client commands | On-demand | - -### Simulation (mock) - -| Thread | Purpose | Timing | -|--------|---------|--------| -| Main | TCP listener, client accept | - | -| Simulation Loop | Physics + sensors | 9ms (110Hz) | -| UDP Publisher | Sensor streaming | <1ms loop | -| TCP Receiver | Per-client commands | On-demand | - -## Hardware Constraints - -- **Heartbeat**: GD32 requires CMD=0x66 every 20-50ms or motors stop -- **Serial Exclusivity**: Only one process can open `/dev/ttyS3` or `/dev/ttyS1` -- **Initialization**: GD32 requires ~5 second wake sequence at boot -- **Baud Rate**: Both serial ports run at 115200 - -## Performance - -| Metric | Value | -|--------|-------| -| Sensor latency | ~20ms | -| Command latency | ~30ms | -| Protobuf bandwidth | ~95KB/s | - -## Mock Device Simulation - -The mock device driver enables algorithm development and testing without physical hardware. - -### Quick Start - -```bash -# Build with mock feature -cargo build --release --features mock - -# Run simulation -cargo run --release --features mock -- mock.toml -``` - -### Map Format (ROS Standard) - -Maps use PGM + YAML format compatible with ROS Navigation: - -**maps/example.yaml:** -```yaml -image: example.pgm # Grayscale occupancy grid -resolution: 0.02 # meters per pixel -origin: [-5.0, -5.0, 0.0] # [x, y, yaw] of bottom-left -occupied_thresh: 0.65 # Darker = occupied -cliff_mask: cliffs.pgm # Optional cliff layer -``` - -**PGM pixel values:** -- White (255) = Free space -- Black (0) = Wall/obstacle -- Gray (205) = Unknown - -### Configuration - -**mock.toml:** -```toml -[device] -type = "mock" -name = "Mock CRL-200S" - -[device.simulation] -map_file = "maps/example.yaml" -start_x = 1.5 # meters -start_y = 3.5 # meters -start_theta = 0.0 # radians -speed_factor = 1.0 # 2.0 = 2x speed -random_seed = 42 # 0 = random each run - -[device.simulation.robot] -wheel_base = 0.233 # meters -ticks_per_meter = 4464.0 -collision_mode = "stop" # "stop", "slide", "passthrough" - -[device.simulation.lidar] -num_rays = 360 -scan_rate_hz = 5.0 -max_range = 8.0 - -[device.simulation.lidar.noise] -range_stddev = 0.005 # 5mm noise -miss_rate = 0.01 # 1% invalid readings - -[network] -bind_address = "0.0.0.0:5555" -``` - -### Simulated Components - -| Component | Method | Configurability | -|-----------|--------|-----------------| -| Lidar | Ray-casting | Noise, miss rate, quality | -| Encoders | Kinematics | Slip, quantization | -| IMU | Physics-based | Per-axis noise, drift | -| Bumpers | Collision detect | Zone angles, trigger distance | -| Cliffs | Mask lookup | Sensor positions | -| Battery | Fixed values | Voltage, level, charging | - -### Speed Factor - -Accelerate simulation for faster testing: - -| Factor | sensor_status | lidar | Wall time for 1 min sim | -|--------|---------------|-------|-------------------------| -| 1.0 | 110 Hz | 5 Hz | 60 sec | -| 2.0 | 220 Hz | 10 Hz | 30 sec | -| 5.0 | 550 Hz | 25 Hz | 12 sec | - -### Creating Maps - -1. **GIMP/Photoshop**: Draw black (walls) on white (free space) -2. **ROS map_server**: Save maps from SLAM runs -3. **Cartographer**: Export as PGM -4. Save as 8-bit grayscale PGM with YAML metadata - -## Documentation - -### CRL-200S Hardware -- [src/devices/crl200s/COMMANDS.md](src/devices/crl200s/COMMANDS.md) - GD32 command reference (27 commands) -- [src/devices/crl200s/SENSORSTATUS.md](src/devices/crl200s/SENSORSTATUS.md) - Status packet byte layout (96 bytes) - -### Mock Device Simulation -- [docs/mock-device-guide.md](docs/mock-device-guide.md) - Mock device user guide - -## Debugging - -```bash -# Enable debug logging -RUST_LOG=debug ./sangamio - -# Test with virtual serial ports -socat -d -d pty,raw,echo=0 pty,raw,echo=0 - -# Monitor on robot -ssh root@vacuum "journalctl -u sangamio -f" - -# Check resource usage -ssh root@vacuum "top -p $(pgrep sangamio)" -``` - -## License - -See the root [LICENSE](../LICENSE) file. +Enuxia maintains this fork as part of the broader Enuxia Vacuum robotics stack. Upstream history and applicable license notices are preserved.