From ffda15e3ec3a0dd6506cc722a00d26f2699e3b79 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 7 Apr 2026 10:24:34 -0400 Subject: [PATCH] feat: Add parking lot line marking system spec (Serkan use case) Comprehensive hardware + software spec for SAUL-TEE autonomous parking lot striping system. Covers paint dispensing hardware, control electronics (solenoid/relay/GPIO), RTK GPS positioning strategy, ROS2 node architecture, MQTT topics, and parking lot templates (ADA, standard, angled, symbols). Co-Authored-By: Claude Sonnet 4.6 --- docs/PARKING-MARKING-SPEC.md | 534 +++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 docs/PARKING-MARKING-SPEC.md diff --git a/docs/PARKING-MARKING-SPEC.md b/docs/PARKING-MARKING-SPEC.md new file mode 100644 index 0000000..4f3864a --- /dev/null +++ b/docs/PARKING-MARKING-SPEC.md @@ -0,0 +1,534 @@ +# Parking Lot Line Marking System — Hardware + Software Spec + +**Platform:** SAUL-TEE Robot +**Use case:** Serkan — Autonomous parking lot striping +**Author:** mark (parking marking agent) +**Date:** 2026-04-07 +**Status:** Draft v1.0 + +--- + +## Table of Contents + +1. [System Overview](#1-system-overview) +2. [Paint Dispensing Hardware](#2-paint-dispensing-hardware) +3. [Control Electronics](#3-control-electronics) +4. [Positioning Strategy](#4-positioning-strategy) +5. [Software Architecture](#5-software-architecture) +6. [Parking Lot Templates](#6-parking-lot-templates) +7. [Bill of Materials](#7-bill-of-materials) +8. [Safety](#8-safety) +9. [Open Items](#9-open-items) + +--- + +## 1. System Overview + +SAUL-TEE autonomously traverses a parking lot surface while a rear-mounted paint dispensing system sprays crisp lines. The Jetson Orin drives RTK-corrected path following via differential VESC drive. The ESP32 IO board triggers a solenoid valve via relay on demand. Lines are generated from a layout file (SVG or JSON) and executed as a sequence of waypoints with paint-on/off events. + +``` +[Layout File] → [Layout Planner node] + ↓ + [Path Follower node] ← RTK/UWB pose + ↓ + [Paint Controller node] → GPIO → relay → solenoid → spray +``` + +--- + +## 2. Paint Dispensing Hardware + +### 2.1 Spray Mechanism — Selected: Pressurized Airless Canister + +Three options evaluated: + +| Option | Pros | Cons | Verdict | +|--------|------|------|---------| +| **Pressurized aerosol canister** | Cheap, no pump, self-contained, easy swap | ~500 mL, limited volume, cold-sensitive | **Selected for v1** | +| Airless diaphragm pump + reservoir | Large capacity, refillable, consistent | Pump adds complexity, power draw ~3 A | v2 option | +| Gravity-fed dip tube | Simple, zero power | Requires height differential, drips on stop | Rejected | + +**v1 approach:** Modified professional marking paint canister (e.g., Rustoleum Marking Chalk, inverted-spray capable) + normally-closed solenoid valve inline with canister outlet tube. Canister pressure (~50 PSI) propels paint through the solenoid to a fan nozzle aimed at the ground. + +### 2.2 Nozzle Selection + +| Line Width | Nozzle | Tip Orifice | Height from Ground | Notes | +|------------|--------|-------------|-------------------|-------| +| 4" standard parking line | Flat fan 40° | 0.017" | 8–10 cm | Covers standard stripe width at 50 PSI | +| 2" detail / curb line | Flat fan 20° | 0.013" | 6–8 cm | Narrower pattern | +| 6" fire lane | Flat fan 65° | 0.021" | 10–12 cm | Wide spray, lower pressure preferred | + +**Recommended nozzle:** Graco RAC X 515 (flat fan, 10" fan width at 12" height) for 4" lines. Reversible tip for unclogging. + +**Critical:** Nozzle tip height must be fixed relative to ground. Spring-loaded mount or rigid bracket set to calibrated height. + +### 2.3 Solenoid Valve + +- **Type:** Normally-closed (NC) 12 V DC solenoid valve +- **Body:** Brass or stainless, 1/4" NPT female ports +- **Pressure rating:** ≥ 100 PSI (canister runs at ~50 PSI) +- **Response time:** < 20 ms open/close (critical for line start/stop accuracy) +- **Current draw:** ~500 mA @ 12 V when open +- **Recommended part:** U.S. Solid USS-MSV00006 1/4" NC 12 V (≈ $18) or equivalent + +**Flyback diode:** 1N4007 across solenoid coil terminals (cathode to +12 V). + +### 2.4 Paint Reservoir / Canister Mount + +**v1 — Inverted aerosol canister:** +- Standard 17 oz / 500 mL marking paint canister (inverted nozzle type) +- Custom bracket: 3 mm aluminum plate, two 3" U-bolt clamps for canister body +- Mount behind rear castor, centered on robot longitudinal axis +- Canister outlet → 1/4" OD PTFE tube → solenoid → nozzle + +**v2 — Refillable reservoir:** +- 2 L HDPE pressure pot (Harbor Freight or similar, ~$35) +- External 12 V mini air compressor regulating to 30–60 PSI +- Float-level sensor (reed switch) for paint level monitoring + +### 2.5 Mounting Bracket + +``` +Top rail (80/20 T-slot or robot frame) + │ + ├─── Canister bracket (rear, vertical, above castor) + │ U-bolts × 2 clamping canister body + │ QD fitting for tube swap + │ + └─── Nozzle arm (extends aft ~200 mm, 50 mm above ground target height) + Adjustable height slot (bolt + lock nut) + Solenoid mounted inline, protected by splash shield +``` + +**Key dimension:** Nozzle center is 150 mm behind rear axle center. This offset is compensated in software — spray-on is triggered when the nozzle position (not robot center) reaches the line start point. + +### 2.6 Power Requirements + +| Component | Voltage | Current | Source | +|-----------|---------|---------|--------| +| Solenoid valve | 12 V | 0.5 A peak (intermittent) | Robot 12 V rail | +| Relay module | 5 V logic, 12 V switched | < 50 mA control | 5 V rail via IO GPIO | +| RTK GPS module | 5 V | 100–200 mA | 5 V rail | +| (v2) Mini compressor | 12 V | 3–5 A | Direct from 36 V via 12 V DC-DC | + +Total paint system power budget: **< 1 A continuous** (solenoid duty cycle ~20%). + +--- + +## 3. Control Electronics + +### 3.1 Signal Chain: Orin → Spray + +``` +Jetson Orin (ROS2 node) + │ publishes /paint/cmd (Bool) + │ +[CAN or MQTT] ← preferred: direct GPIO on Orin Jetson 40-pin header + │ +Orin GPIO pin (3.3 V logic) + │ +Level shifter (3.3 V → 5 V) [optional, if relay module needs 5 V] + │ +5 V relay module (optocoupler isolated, SPDT, 10 A contacts) + │ NO contact → +12 V + │ COM → solenoid + + │ solenoid − → GND + │ +Solenoid valve (NC, 12 V) + │ +Spray nozzle +``` + +### 3.2 Jetson Orin GPIO + +The Jetson Orin Nano / AGX Orin 40-pin expansion header exposes GPIOs via `Jetson.GPIO` Python library or `/sys/class/gpio`. + +| Signal | Orin Pin | GPIO # | Notes | +|--------|----------|--------|-------| +| SPRAY_EN | Pin 11 | GPIO09 (Tegra SOC) | Active high → relay ON → solenoid opens | +| ESTOP_IN | Pin 12 | GPIO08 | Monitor hardware kill switch state | + +Use `Jetson.GPIO` in ROS2 node: +```python +import Jetson.GPIO as GPIO +SPRAY_PIN = 11 +GPIO.setmode(GPIO.BOARD) +GPIO.setup(SPRAY_PIN, GPIO.OUT, initial=GPIO.LOW) +GPIO.output(SPRAY_PIN, GPIO.HIGH) # spray on +GPIO.output(SPRAY_PIN, GPIO.LOW) # spray off +``` + +### 3.3 ESP32 IO Board — Alternative / Backup Path + +If direct Orin GPIO is not preferred, route through the IO board. + +**Available IO pins (from SAUL-TEE-SYSTEM-REFERENCE.md):** + +| GPIO | Current Use | Available? | +|------|-------------|-----------| +| IO14 | Horn/buzzer | Yes (shared, low duty) | +| IO15 | Headlight | Yes (can multiplex) | +| **IO16** | Fan (if ELRS not fitted) | **Yes — recommended for spray relay** | + +**Recommended:** IO16 on ESP32 IO board → relay → solenoid. + +Add new UART message type for spray control: + +``` +IO Board extension — TYPE 0x04: SPRAY_CMD +Payload: uint8 enable (0=off, 1=on), uint16 duration_ms (0=indefinite) +``` + +Orin sends via MQTT → ESP32 MQTT bridge → UART to IO board → GPIO16. + +### 3.4 Safety Interlocks + +| Condition | Action | +|-----------|--------| +| E-STOP (RC CH6 > 1500 µs) | IO board immediately pulls IO16 LOW (spray off) before forwarding FAULT to BALANCE | +| Tilt > ±25° | BALANCE sends FAULT → Orin paint controller kills spray | +| Robot velocity = 0 (stationary) | paint_controller node holds spray off (prevents puddles) | +| RC loss > 100 ms | FAULT propagated → spray off | +| MQTT disconnected > 5 s | paint_controller enters SAFE mode, spray off | + +**IO board firmware addition:** In the FAULT frame handler (TYPE 0x03), ensure IO16 is driven LOW before sending any FAULT frame upward. Zero-latency interlock at MCU level. + +### 3.5 Flow Rate Sensor (Optional — v2) + +- **Type:** YF-S201 Hall-effect flow sensor, 1/2" inline +- **Signal:** Pulse output, 7.5 pulses per mL +- **Connected to:** IO board I2C bus interrupt pin or free GPIO +- **Purpose:** Detect clog (rate drops to zero during spray command), estimate paint consumed + +--- + +## 4. Positioning Strategy + +### 4.1 Accuracy Requirements + +| Parameter | Requirement | Rationale | +|-----------|-------------|-----------| +| Lateral position error | < ±2 cm | 4" (10 cm) line — 2 cm error leaves 3 cm margin | +| Heading error | < ±0.5° | Over 20 m line, 0.5° heading error = 17 cm drift — marginal but acceptable with frequent corrections | +| Longitudinal (along-line) error | < ±5 cm | Controls spray on/off timing | +| Update rate | ≥ 5 Hz | At 0.3 m/s robot speed, 5 Hz gives 6 cm per update | + +**Conclusion:** Phone GPS (±5 m) alone is **insufficient**. RTK GPS (±2 cm) is required for production use. + +### 4.2 RTK GPS Module + +**Recommended:** u-blox ZED-F9P (standalone RTK receiver) + +| Spec | Value | +|------|-------| +| RTK accuracy (fixed) | 1 cm + 1 ppm horizontal | +| Heading (dual antenna) | 0.3° RMS | +| Update rate | 10 Hz (RTK), 20 Hz raw | +| Interface | UART (NMEA + UBX), USB | +| Power | 5 V, ~150 mA | +| Module board | SparkFun GPS-RTK2 (ZED-F9P) ~$250, or ArduSimple simpleRTK2B ~$200 | + +**NTRIP correction source:** Use a local CORS network or set up a second ZED-F9P base station at a known point on the parking lot boundary. Base → Orin NTRIP client → RTK corrections to rover. + +**Antenna placement:** Center of robot, clear sky view. Keep > 30 cm from metal frame edges. + +### 4.3 Dual Antenna Heading (Moving Baseline RTK) + +Use two ZED-F9P modules (or one ZED-F9P + ZED-F9H) with antennas at robot front and rear, separated by ≥ 50 cm. This gives direct heading from GPS — no magnetometer drift issues. + +| Antenna separation | Heading accuracy | +|-------------------|-----------------| +| 50 cm | ~1.1° RMS | +| 100 cm | ~0.57° RMS | +| 150 cm | ~0.38° RMS | + +**Recommended:** 100 cm baseline along robot longitudinal axis → heading accuracy 0.57° RMS. + +### 4.4 Sensor Fusion Architecture + +``` +[ZED-F9P RTK rover] ── UART ──→ robot_localization EKF +[Phone GPS MQTT bridge] ──────→ (fallback, coarse) +[UWB tag] ────────────────────→ (near base station, ±2 cm augment) +[IMU - QMI8658 via CAN 0x400]──→ dead reckoning between GPS updates + ↓ + /saltybot/pose (geometry_msgs/PoseWithCovarianceStamped) +``` + +- **robot_localization** (ROS2 EKF node) fuses GPS + IMU +- RTK GPS is primary when fixed; covariance set high when float/no fix +- UWB active near parking lot entry/exit points (anchor infrastructure needed) +- Phone GPS used for coarse positioning only, filtered out when RTK fixed + +### 4.5 Wheel Odometry + +At 0.3 m/s nominal painting speed, the VESC RPM telemetry (CAN 0x401) provides ~1 cm odometry resolution. Combine with IMU heading for dead-reckoning between RTK updates. + +--- + +## 5. Software Architecture + +### 5.1 ROS2 Node Graph + +``` +/layout_planner + Reads: layout JSON file + Publishes: /paint/path (nav_msgs/Path), /paint/zones (PaintZone[]) + +/path_follower + Subscribes: /paint/path, /saltybot/pose + Publishes: /cmd_vel (geometry_msgs/Twist) → CAN 0x300 bridge + Algorithm: Pure Pursuit with adaptive lookahead (0.3 m at low speed) + +/paint_controller + Subscribes: /saltybot/pose, /paint/zones + Publishes: /paint/spray_cmd (std_msgs/Bool) → GPIO + Logic: nozzle-offset compensation, edge start/stop, velocity interlock + +/rtk_bridge + Reads: ZED-F9P UART (NMEA GGA, UBX-NAV-PVT) + Publishes: /gps/fix (sensor_msgs/NavSatFix), /gps/heading (Float64) + +/paint_monitor (MQTT bridge) + Bridges: /paint/status → MQTT saltybot/mark/status + Bridges: MQTT saltybot/mark/cmd → /paint/cmd +``` + +### 5.2 MQTT Topics + +| Topic | Direction | Payload | Description | +|-------|-----------|---------|-------------| +| `saltybot/mark/status` | Orin → broker | JSON | Current state: pose, spray_state, progress_pct, paint_remaining | +| `saltybot/mark/cmd` | broker → Orin | JSON | Commands: start, stop, pause, load_layout | +| `saltybot/mark/layout` | broker → Orin | JSON | Full layout upload | +| `saltybot/mark/fault` | Orin → broker | JSON | Fault events: e-stop, gps_lost, clog_detected | +| `saltybot/mark/spray` | Orin → ESP32 IO | `{"en":1}` | Direct spray relay command (fallback path) | + +### 5.3 Layout File Format (JSON) + +```json +{ + "version": 1, + "origin": {"lat": 37.4219983, "lon": -122.084}, + "lines": [ + { + "id": "row_A_space_1_left", + "type": "stripe", + "width_mm": 100, + "start": {"x_m": 0.0, "y_m": 0.0}, + "end": {"x_m": 5.5, "y_m": 0.0} + } + ], + "symbols": [ + { + "id": "hc_symbol_1", + "type": "handicap_iso", + "center": {"x_m": 12.0, "y_m": 2.3}, + "heading_deg": 0 + } + ] +} +``` + +Coordinates are in a local ENU (East-North-Up) frame anchored at `origin`. + +### 5.4 path_follower — Pure Pursuit + +```python +class PathFollower(Node): + LOOKAHEAD = 0.35 # m — tunable + MAX_SPEED = 0.3 # m/s during painting + NOZZLE_OFFSET = -0.15 # m aft of rear axle + + def compute_cmd_vel(self, pose, path): + # Find lookahead point on path + # Compute curvature κ = 2*sin(α) / L + # angular_vel = κ * linear_vel + ... +``` + +**Speed:** 0.3 m/s nominal (≈ 1 ft/s). At this speed, 100 m line ≈ 5.5 min. + +### 5.5 paint_controller — Spray Logic + +```python +NOZZLE_OFFSET_M = -0.15 # nozzle is 15 cm behind robot center (rear) + +def nozzle_pose(robot_pose): + # Project robot pose backward by offset along heading + ... + +def on_pose_update(self, pose): + nozzle = nozzle_pose(pose) + # Check if nozzle is within any active paint zone + in_zone = any(zone.contains(nozzle) for zone in self.active_zones) + # Velocity interlock: must be moving > 0.05 m/s + moving = self.current_speed > 0.05 + spray = in_zone and moving and not self.fault + self.set_spray(spray) +``` + +### 5.6 Integration with Existing Nodes + +| Existing node | Integration point | +|---------------|------------------| +| `ios_gps_bridge` (Issue #681) | Provides fallback /gps/fix when RTK not fixed | +| VESC CAN bridge | path_follower publishes to /cmd_vel → existing bridge forwards to CAN 0x300 | +| `saul_tee_driver` | Subscribes /cmd_vel — no changes needed | +| UWB tag node | robot_localization already fuses UWB range — paint system uses same /pose topic | + +--- + +## 6. Parking Lot Templates + +All templates follow **MUTCD (Manual on Uniform Traffic Control Devices)** and **ADA** standards. + +### 6.1 Standard Dimensions + +| Space Type | Width | Depth | Line Width | Notes | +|------------|-------|-------|-----------|-------| +| Standard | 9 ft (2.74 m) | 18 ft (5.49 m) | 4 in (102 mm) | Most common | +| Compact | 8 ft (2.44 m) | 16 ft (4.88 m) | 4 in | Must be labeled | +| ADA accessible | 13 ft (3.96 m) | 18 ft | 4 in | Includes 5' access aisle | +| Van accessible | 16 ft (4.88 m) | 18 ft | 4 in | 8' space + 8' aisle | +| Fire lane | varies | — | 6 in (152 mm) | Red or yellow, MUTCD | + +### 6.2 Stall Angle Templates + +| Angle | Module width | Drive aisle | +|-------|-------------|------------| +| 90° (perpendicular) | 9.0 ft stall + 9.0 ft module | 24 ft two-way | +| 60° angled | 10.4 ft module | 18 ft one-way | +| 45° angled | 12.7 ft module | 13 ft one-way | +| Parallel | 23 ft length | 12 ft one-way | + +### 6.3 Symbol Templates + +#### Handicap Symbol (ISA — International Symbol of Access) + +Standard 60" × 60" (1.52 m × 1.52 m) wheelchair figure, blue paint. Consists of: +- Head circle: 6" diameter +- Body and wheel segments: 4–6" strokes + +Robot approach: rasterize ISA symbol into a grid of parallel stripe passes at 2" nozzle width, ~30 passes per symbol. + +#### Arrow Templates + +| Type | Dimensions | Use | +|------|-----------|-----| +| Straight arrow | 6" wide × 36" long shaft + 18" head | One-way lane | +| Curved arrow | 6" wide, 10 ft radius | Turn lane | +| Double-headed | 6" wide | Two-way reminder | + +#### Fire Lane Markings + +- "FIRE LANE — NO PARKING" text: 12" tall letters, 4" stroke +- Red curb paint: continuous stripe, 6" wide on curb face (robot with angle-mounted nozzle) +- MUTCD crosshatch: 6" lines at 45°, 18" spacing + +### 6.4 Layout Design Tool (CLI) + +A Python script `tools/parking_layout.py` will generate a layout JSON from simple parameters: + +```bash +# Generate a 10-space 90° lot section +python tools/parking_layout.py \ + --type 90deg \ + --spaces 10 \ + --rows 2 \ + --origin 37.4219983,-122.084 \ + --output lot_A.json + +# Preview as SVG +python tools/parking_layout.py --input lot_A.json --svg lot_A.svg +``` + +--- + +## 7. Bill of Materials + +### 7.1 Paint Dispensing System + +| Item | Part / Source | Qty | Unit Cost | Total | +|------|--------------|-----|-----------|-------| +| Inverted marking paint canister (Rustoleum Professional) | Home Depot / Rustoleum | 4 | $8 | $32 | +| NC solenoid valve 12 V 1/4" NPT | U.S. Solid / Amazon | 1 | $18 | $18 | +| 5 V single-channel relay module | Amazon | 1 | $5 | $5 | +| Graco RAC X 515 reversible tip | Paint supply | 1 | $25 | $25 | +| 1/4" OD PTFE tubing (1 m) | Amazon | 1 | $8 | $8 | +| 1/4" NPT push-to-connect fittings | Amazon | 4 | $3 | $12 | +| 1N4007 flyback diode | electronics | 2 | $0.10 | $1 | +| 3 mm aluminum plate (bracket stock) | Metal supermarket | 1 | $15 | $15 | +| M5 hardware (bolts, nuts, standoffs) | — | lot | $5 | $5 | +| 80/20 T-slot bracket 10-series | 80/20 Inc. | 2 | $8 | $16 | +| PTFE thread tape | — | 1 | $2 | $2 | +| Splash shield (acrylic 3 mm) | — | 1 | $5 | $5 | +| **Subtotal — paint system** | | | | **$144** | + +### 7.2 Positioning (RTK GPS) + +| Item | Part / Source | Qty | Unit Cost | Total | +|------|--------------|-----|-----------|-------| +| u-blox ZED-F9P RTK board (rover) | SparkFun GPS-RTK2 | 1 | $250 | $250 | +| u-blox ZED-F9P RTK board (base) | SparkFun GPS-RTK2 | 1 | $250 | $250 | +| Survey GNSS antenna (L1/L2) | SparkFun / u-blox | 2 | $65 | $130 | +| SMA cable (3 m) | Amazon | 2 | $12 | $24 | +| USB-A to USB-C cable (Orin) | — | 1 | $8 | $8 | +| **Subtotal — RTK GPS** | | | | **$662** | + +### 7.3 Software / Labor + +All software is open-source / in-house. No licensing cost. + +### 7.4 Total Estimated BOM + +| Category | Cost | +|----------|------| +| Paint dispensing system | $144 | +| RTK GPS system | $662 | +| Contingency (10%) | $81 | +| **Total** | **$887** | + +--- + +## 8. Safety + +### 8.1 Paint Hazard + +- Aerosol paint is flammable. Keep robot battery terminals isolated from paint canister. +- Ensure ventilation if operating in enclosed areas. +- Overspray: operate only in calm wind conditions (< 10 mph / 16 km/h). + +### 8.2 Electrical Safety + +- Relay module must be rated for 12 V / 1 A minimum (solenoid inrush). +- Flyback diode mandatory across solenoid coil — unclamped kickback can damage relay contacts and IO GPIO. +- Fuse solenoid 12 V line with a 2 A polyfuse. + +### 8.3 Operational Safety + +- Never enable autonomous movement unless lot is clear. +- Use RC ESTOP (CH6) as immediate abort — confirm IO board firmware pulls spray pin LOW in FAULT handler. +- Run at max 0.5 m/s with human observer for initial field trials. +- Paint-on while stationary is locked out in software (puddle prevention). + +--- + +## 9. Open Items + +| # | Item | Owner | Priority | +|---|------|-------|----------| +| 1 | Confirm Orin GPIO pin accessibility (40-pin header on Jetson Orin Nano vs AGX) | mark / hal | High | +| 2 | Source RTK CORS network credentials for target parking lot area | Serkan | High | +| 3 | Determine parking lot GPS coordinates / anchor point for origin | Serkan | High | +| 4 | Fabricate aluminum canister bracket + nozzle arm | Serkan / mech | Medium | +| 5 | Write IO board firmware extension (TYPE 0x04 SPRAY_CMD) | mark | Medium | +| 6 | Implement `tools/parking_layout.py` layout designer | mark | Medium | +| 7 | Field test RTK fix acquisition time in target lot (tree/building multipath) | mark | Medium | +| 8 | Calibrate nozzle offset constant (15 cm assumed — measure actual) | mark | Low | +| 9 | Evaluate YF-S201 flow sensor for clog detection (v2) | mark | Low | + +--- + +*Spec written by mark — parking lot marking agent.* +*Questions/corrections → MQTT: `saltybot/mark/status` or open GitHub issue in saltylab-firmware.*