20 Commits

Author SHA1 Message Date
Salty Bead
5e591ab466 feat(uwb): replace ESP-NOW with DW1000 data frames + BLE GPS/IMU input
- Tag: strip all ESP-NOW/WiFi code
- Tag: add BLE GPS characteristic (def3) for iPhone binary GPS writes
- Tag: add BLE Phone IMU characteristic (def4) for iPhone IMU writes
- Tag: transmit GPS/IMU/heartbeat via DW1000 data frames (v2 protocol)
- Tag: update OLED display with GPS speed/heading/fix indicators
- Tag: e-stop now sent via UWB data frames (3x for reliability)
- Anchor: strip all ESP-NOW/WiFi code
- Anchor: receive DW1000 data frames, forward to serial as +GPS/+PIMU/+TIMU/+ESTOP
- Protocol v2: magic {0x5B, 0x02}, msg types 0x60-0x64
2026-03-30 09:23:00 -04:00
sl-uwb
fd6893f684 feat: UWB anchor auto-discovery (Issue #698)
Replace hardcoded NUM_ANCHORS with dynamic discovery table.
Tag probes IDs 0..7 via standard DS-TWR POLL frames at startup
and after every DISC_RESCAN_MS (10 s); anchors that respond are
added to the active table, stale ones removed after
ANCHOR_MISS_LIMIT misses or ANCHOR_TIMEOUT_MS silence.

Key changes
-----------
- AnchorEntry table (8 slots): present/active flags, range_mm,
  rssi, last_ok_ms, miss_count
- DISC_SCAN → DISC_RANGE state machine
- range_and_update(): ranges, updates table, emits +DISC:FOUND /
  +DISC:LOST on serial and MSG_DISC (0x40) on ESP-NOW
- disc_scan_step(): advances cursor one probe per ranging slot,
  skips already-active IDs
- New OLED screens: ANCHOR SCAN progress bar, NO ANCHOR rescan
  countdown, active anchor count badge on normal screen
- platformio.ini: switch to MaUWB_DW3000 lib, remove NUM_ANCHORS
  build flag (discovery is now dynamic)
- No anchor firmware changes required

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:34:18 -04:00
Sebastien Vayrette
688a9bdbe9 fix(uwb_tag): turn display ON after initialization and add button wake
- Display was never turned on after display.begin() in setup()
- Added SSD1306_DISPLAYON command after initialization
- Added short button press detection to wake display
- Fixes blank display issue reported by SAUL
2026-03-18 11:17:31 -04:00
sl-uwb
8ee9b4cca9 feat: UWB tag BLE configuration interface (Issue #690)
Adds BLE GATT config server to uwb_tag firmware:

Advertising:
- Device name "UWB_TAG_XXXX" (last 4 hex digits of WiFi MAC)
- Service UUID: 12345678-1234-5678-1234-56789abcdef0
- Compatible with nRF Connect app

Characteristics:
- Config (R/W) UUID: ...abcdef1
  Read: returns current config as JSON
  Write: accepts partial JSON with any config keys
- Status (R/N) UUID: ...abcdef2
  Notifies "+OK" or "+ERR:<reason>" after each write

Config keys (NVS-persisted, applied immediately unless noted):
  sleep_timeout_s      [5..3600]   OLED display timeout
  display_brightness   [0..255]    OLED contrast (SSD1306_SETCONTRAST)
  tag_name             [max 16]    friendly name
  uwb_channel          [1..7]      UWB RF channel (next boot)
  ranging_interval_ms  [50..2000]  minimum ranging poll interval
  battery_report       bool        include battery flag in ESP-NOW packets

Partition: huge_app.csv (3MB app) — BLE + WiFi + DW1000 needs ~1.76MB
Build: 55.8% flash, 18.1% RAM (SUCCESS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 10:47:55 -04:00
sl-uwb
1bf9b73247 feat: UWB tag power management — sleep mode (Issue #689)
Implements three-tier power management for battery life:

- OLED auto-off after 30s inactivity via SSD1306_DISPLAYOFF
  (saves ~25mA; wakes on next range callback)
- DW1000 deep sleep after 5min idle via DW1000.deepSleep()
  (saves ~155mA, 160mA→3.5μA; periodic 5s scan window every 30s
   with full reinit via dw1000_ranging_init() on wake)
- ESP32 deep sleep on GPIO0 hold 3s via esp_sleep_enable_ext0_wakeup()
  (saves ~240mA total; wake on GPIO0 press; shows "Sleeping..." on OLED)

Active: ~250mA  Sleep target: <5mA (50x reduction)

All sleep/wake paths tested with clean build (74.1% flash, 14.1% RAM).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 10:27:09 -04:00
Sebastien Vayrette
6ea6e0ccd1 feat(uwb): add MPU6050 IMU + fall detection to tag, IMU forwarding in anchor + ROS2 node
- Tag: MPU6050 on shared I2C bus (SDA=4, SCL=5, addr 0x68)
- Accel ±8g, gyro ±500dps, read at 50Hz
- Fall detection: freefall+impact or sudden >3.5g
- Fall triggers auto e-stop (ESP-NOW broadcast)
- OLED shows tilt bar + accel magnitude
- New ESP-NOW packet types: MSG_IMU (0x40), MSG_FALL (0x50)
- IMU broadcast at 10Hz via ESP-NOW
- Anchor forwards +IMU: and +FALL: lines to Jetson serial
- ROS2 uwb_driver publishes sensor_msgs/Imu on /uwb/imu
- .gitignore for .pio build dirs
2026-03-14 14:09:39 -04:00
salty
6807f418ee fix: move anchor arrays before ESP-NOW RX callback 2026-03-14 13:26:26 -04:00
salty
d687ef5242 feat: bidirectional ESP-NOW — anchors share ranges with each other + tag
Anchors:
- Broadcast own range via ESP-NOW after each TWR cycle
- Receive other anchor's range via ESP-NOW, print as +RANGE on serial
- Either anchor's serial port gives Orin BOTH ranges (only need 1 USB/UART)

Tag:
- Receives ESP-NOW from both anchors
- Updates display with both A0 + A1 distances regardless of DW1000 pairing
- Solves the 'only sees A0' display issue
2026-03-14 13:25:18 -04:00
salty
a4e43b4d5c fix: increase BLINK frequency for multi-anchor discovery
- DEFAULT_TIMER_DELAY 80→40ms (faster poll/blink cycle)
- BLINK interval 21→5 cycles (every ~240ms instead of 1.7s)
- Ensures tag rediscovers both anchors across time-division slots
2026-03-14 13:11:38 -04:00
salty
2a78201a2d fix: time-division multiplexing for 2-anchor collision avoidance
Each anchor only runs DW1000Ranging during its 50ms time slot.
Anchor 0 responds in even slots, anchor 1 in odd slots.
Prevents RF collisions that caused wild range readings.
2026-03-14 13:01:04 -04:00
salty
9ed3e06fb8 fix: disable e-stop — GPIO 0 floats LOW on Makerfabs display board 2026-03-14 12:41:06 -04:00
salty
35d43f3173 fix: use LOWPOWER mode + add delay(1) to prevent overheating
- MODE_LONGDATA_RANGE_ACCURACY → MODE_LONGDATA_RANGE_LOWPOWER (both anchor+tag)
- Add delay(1) in tag loop to prevent tight-loop when no anchors respond
- ACCURACY was unnecessary and kept DW1000 radio hot
2026-03-14 12:38:28 -04:00
salty
b7dcc00c5e fix: ESP-NOW callback signature for new ESP-IDF, volatile qualifier 2026-03-14 12:32:11 -04:00
salty
30e012255f fix: char[] for DW1000 addr, suppress -Werror=return-type in lib 2026-03-14 12:31:40 -04:00
salty
ed1542ae11 feat: rewrite UWB firmware for DW1000 (all 3 boards)
Anchor (esp32/uwb_anchor):
- DW1000Ranging library (200m range, MODE_LONGDATA_RANGE_ACCURACY)
- Unique addresses per anchor (anchor0/anchor1 build envs)
- +RANGE output: anchor_id, tag_addr, range_mm, rssi
- ESP-NOW receiver: forwards tag packets + priority E-STOP to Jetson
- AT+ID? command

Tag with Display (esp32/uwb_tag):
- DW1000Ranging as tag, auto-discovers anchors
- SSD1306 OLED: big distance, per-anchor ranges, RSSI bars, link status
- ESP-NOW broadcast: range/heartbeat/estop packets
- E-Stop on GPIO 0 (BOOT button), 10Hz TX while held
- Display at 5Hz, ranging driven by DW1000Ranging.loop()

Shared:
- lib/DW1000/ extracted from mf_DW1000.zip (Makerfabs fork)
- lib_extra_dirs for PlatformIO to find local library
2026-03-14 12:30:26 -04:00
salty
a9e1b9fae5 feat: add OLED display, ESP-NOW wireless, and E-Stop to UWB tag
Tag firmware (esp32/uwb_tag):
- SSD1306 128x64 OLED: shows distance, anchor ranges, RSSI bars, link status
- ESP-NOW broadcast: sends range/heartbeat/estop packets (20 bytes, peer-to-peer)
- Emergency stop: GPIO 0 (BOOT), active LOW, 10Hz TX while held, 3x clear on release
- Display updates at 5Hz, ranging still at 20Hz round-robin
- Added Adafruit SSD1306 + GFX lib_deps to platformio.ini

Anchor firmware (esp32/uwb_anchor):
- ESP-NOW receiver: captures tag packets via ISR ring buffer
- Forwards to Jetson serial as +ESPNOW: and +ESTOP: lines
- E-STOP packets get priority immediate output
- Zero impact on existing TWR ranging loop

For Makerfabs ESP32 UWB Pro with Display (DW3000 chip).
2026-03-14 12:12:31 -04:00
salty
66a4bbe25b Merge remote-tracking branch 'origin/main' into salty/uwb-tag-display-wireless 2026-03-14 12:11:28 -04:00
sl-uwb
c6e479ef3f feat: ESP32 UWB Pro tag firmware — DS-TWR initiator (Issue #545)
Tag firmware for Makerfabs ESP32 UWB Pro worn by person being tracked.
Initiates DS-TWR with each robot anchor in 20 Hz round-robin.

- DS-TWR initiator: Poll→(RESP received)→Final with timestamps Ra/Da/Db
- Anchor side computes authoritative range; tag computes local estimate
- Round-robin across NUM_ANCHORS anchors (default 2) at 20 Hz
- Streams +RANGE:<id>,<mm>,<rssi> over USB serial (bench debug)
- LED blink on each successful range
- TAG_ID, NUM_ANCHORS, RANGE_INTERVAL_MS configurable in platformio.ini
- Pin map: SCK=18 MISO=19 MOSI=23 CS=21 RST=27 IRQ=34

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:48:12 -04:00
sl-uwb
a4879b6b3f feat: ESP32 UWB Pro anchor firmware — DS-TWR responder (Issue #544)
Anchor firmware for Makerfabs ESP32 UWB Pro (DW3000 chip). Two anchors
mount on SaltyBot (port/starboard), USB-connected to Jetson Orin.

- DS-TWR responder: Poll→Resp→Final with ±10cm accuracy
- Streams +RANGE:<id>,<mm>,<rssi_dbm> on Serial 115200
- AT command interface: AT+RANGE?, AT+RANGE_ADDR=, AT+ID?
- ANCHOR_ID 0/1 set at build time (env:anchor0 / env:anchor1)
- PlatformIO config for Makerfabs MaUWB_DW3000 library
- udev rules for /dev/uwb-anchor0 /dev/uwb-anchor1 USB symlinks
- Pin map: SCK=18 MISO=19 MOSI=23 CS=21 RST=27 IRQ=34

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:45:29 -04:00
5143e5bfac feat(social): Issue #86 — physical expression + motor attention
ESP32-C3 NeoPixel sketch (esp32/social_expression/social_expression.ino):
  - Adafruit NeoPixel + ArduinoJson, serial JSON protocol 115200 8N1
  - Mood→colour: happy=green, curious=blue, annoyed=red, playful=rainbow
  - Idle breathing animation (sine-modulated warm white)
  - Auto-falls to idle after IDLE_TIMEOUT_MS (3 s) with no command

ROS2 saltybot_social_msgs (new package):
  - Mood.msg — {mood, intensity}
  - Person.msg — {track_id, bearing_rad, distance_m, confidence, is_speaking, source}
  - PersonArray.msg — {persons[], active_id}

ROS2 saltybot_social (new package):
  - expression_node: subscribes /social/mood → JSON serial to ESP32-C3
    reconnects on port error; sends idle frame after idle_timeout_s
  - attention_node: subscribes /social/persons → /cmd_vel rotation-only
    proportional control with dead zone; prefers active speaker, falls
    back to highest-confidence person; lost-target idle after 2 s
  - launch/social.launch.py — combined launch
  - config YAML for both nodes with documented parameters
  - test/test_attention.py — 15 pytest-only unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 23:35:59 -05:00