Context
sweep_node.py (simulation) sweeps 0° → 180° at 1° steps for TF-Luna distance. Parameter settle_ms=120 (was 50 in the first iteration). Research questions: (a) real SG90 angular accuracy, (b) optimal settle time, © storage strategy for sweep ranges before publishing a LaserScan.
Final recommendation — accuracy + settle
| Parameter | Value | Reasoning |
|---|---|---|
| Step | 1° | TASK-001 contract |
settle_ms |
120 ms (production), 50 ms (fast smoke, no accuracy) | TF-Luna 10 Hz → 100 ms per cycle + 20 ms margin for servo mechanical settling |
| Total sweep time | ~22 s (181 × 120 ms) | critical for the exploration cost model |
| SG90 angular accuracy | ±1° typical, ±3° worst-case (dead band + backlash) | hobby-grade servo — not precision |
| Real on-axis variance | ±5° → much for precision 3D scan, OK for obstacle detection | trade-off |
Sample freshness check — mandatory:
if self._last_range_t < self._step_started_t:
return # stale buffer — sensor still reading previous angle
Without it, the servo motion shifts the sensor but the ROS topic holds the old value → range from the previous angle.
Storage strategy — sweep data
Ring buffer 181 ranges in RAM. On sweep completion — publish sensor_msgs/LaserScan in one shot.
class SweepNode(Node):
def __init__(self):
super().__init__('sweep_node')
self._ranges = [float('inf')] * 181 # ring buffer, 181 cells
self._step_idx = 0
self._step_started_t = None
self._last_range_t = None
def _on_tf_luna(self, msg):
self._last_range_t = msg.header.stamp
if self._last_range_t >= self._step_started_t:
self._ranges[self._step_idx] = msg.range # record actual sample
def _tick(self):
# 50 Hz tick
if self._sweeping and (time.now() - self._step_started_t) >= settle_ms:
self._step_idx += 1
self._step_started_t = time.now()
self._move_servo(self._step_idx)
if self._step_idx >= 181:
self._finish_sweep()
def _finish_sweep(self):
msg = LaserScan()
msg.ranges = list(self._ranges)
msg.angle_min = 0.0
msg.angle_max = math.pi
msg.angle_increment = math.pi / 180
msg.range_min = 0.2
msg.range_max = 8.0
msg.header.frame_id = 'tf_luna_sweep'
self.pub_scan.publish(msg)
self.pub_status.publish(String(data='COMPLETE'))
Don’t write to disk during the sweep — IO overhead kills the timing. Disk persistence — only if replay/debug is required; separate path ~/drone_media/sim/scans/sweep_<ts>.json.
Hardware caveats (for the real-world phase)
DroneBot forum artifacts with TF-Luna + SG90 in a real build:
- Voltage drop under servo current draw on shared 5V → MCU interference, TF-Luna freezes.
- Fix: isolated servo power (separate regulator) + common GND.
- Hardware timer conflicts (PWM servo + UART TF-Luna) on some MCUs.
- Fix: split timers; on ESP32 the RMT for PWM works standalone.
- Silent failures in I2C/UART validation chains — sensor returns 0 distance, not an error.
- Fix: explicit error reporting per validation step.
- Settle delay is actually larger than just mechanical — account for power-supply settling, not only arm settling.
Storage capacity considerations (if we’ll log a dataset for RL)
One sweep = 181 floats = ~1.5 KB. At 22 s/sweep that’s ~250 KB/hour. A 30-min flight → ~125 KB. Not a bottleneck for SD card or onboard storage.
If video + depth logging — another story (10+ MB/s). Out of scope.
Sources
- Tower Pro SG90 datasheet — standard specs
- DroneBot Workshop — forum on TF-Luna + servo hardware artifacts
- TF-Luna A03 datasheet Appendix I — frame timing, amplitude field
sweep_node.pysimulation TASK-002 —settle_ms=120, sample freshness pattern