Skip to content

Commit e95507f

Browse files
authored
feat: mcu-util signup cmd (#1255)
It mimics the actions performed by orb-core duting a signup. Required for the temperature testing.
1 parent b90ac8a commit e95507f

2 files changed

Lines changed: 179 additions & 1 deletion

File tree

mcu-util/src/main.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,59 @@ enum OpticsOpts {
195195
/// Test camera trigger for 10 seconds with default options: 30fps, IR-LEDs 100us.
196196
#[clap(subcommand)]
197197
TriggerCamera(Camera),
198+
/// Replicate the MCU command sequence orb-core sends during a signup capture
199+
#[clap(action)]
200+
Signup(SignupOpts),
198201
/// Polarizer command
199202
#[clap(subcommand)]
200203
Polarizer(PolarizerOpts),
201204
}
202205

206+
/// IR wavelength selection, mirrors orb_messages InfraredLEDs.Wavelength.
207+
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
208+
pub enum IrWavelength {
209+
/// 850nm (orb-core default)
210+
L850,
211+
/// 940nm
212+
L940,
213+
/// 850nm, left side only
214+
L850Left,
215+
/// 850nm, right side only
216+
L850Right,
217+
/// 940nm, left side only
218+
L940Left,
219+
/// 940nm, right side only
220+
L940Right,
221+
/// 850nm, center only
222+
L850Center,
223+
/// 850nm, side only
224+
L850Side,
225+
/// 940nm, single emitter
226+
L940Single,
227+
}
228+
229+
/// Replicates orb-core's signup capture: enable IR LEDs, set FPS, start the IR
230+
/// eye + face camera triggers, hold for a few seconds, then stop the triggers
231+
/// and disable the IR LEDs.
232+
#[derive(Parser, Debug, Clone, Copy)]
233+
pub struct SignupOpts {
234+
/// IR LED wavelength (orb-core uses l850)
235+
#[clap(long, value_enum, default_value = "l850")]
236+
wavelength: IrWavelength,
237+
/// IR LED on-duration in microseconds (orb-core default is 350)
238+
#[clap(long, default_value = "350")]
239+
duration_us: u16,
240+
/// Camera frame rate (orb-core uses 30)
241+
#[clap(long, default_value = "30")]
242+
fps: u32,
243+
/// How long to hold the capture before tearing it down, in seconds
244+
#[clap(long, default_value = "5")]
245+
seconds: u64,
246+
/// Also start the RGB face camera trigger (Diamond orbs)
247+
#[clap(long)]
248+
rgb: bool,
249+
}
250+
203251
#[derive(Parser, Debug, Clone, Copy)]
204252
enum GimbalHomeOpts {
205253
/// Auto-home the gimbal by hitting the limits, more accurate but slow and noisy.
@@ -433,6 +481,9 @@ async fn execute(args: Args) -> Result<()> {
433481
};
434482
orb.main_board_mut().trigger_camera(camera, fps).await?
435483
}
484+
OpticsOpts::Signup(opts) => {
485+
orb.main_board_mut().signup_capture(opts).await?
486+
}
436487
OpticsOpts::Polarizer(PolarizerOpts::Stress {
437488
speed,
438489
repeat,

mcu-util/src/orb/main_board.rs

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use crate::orb::dfu::BlockIterator;
1010
use crate::orb::revision::OrbRevision;
1111
use crate::orb::{dfu, BatteryStatus};
1212
use crate::orb::{Board, OrbInfo};
13-
use crate::{Camera, GimbalHomeOpts, Leds, PolarizerOpts, RebootBehavior};
13+
use crate::{
14+
Camera, GimbalHomeOpts, IrWavelength, Leds, PolarizerOpts, RebootBehavior,
15+
SignupOpts,
16+
};
1417
use orb_mcu_interface::can::canfd::CanRawMessaging;
1518
use orb_mcu_interface::can::isotp::{CanIsoTpMessaging, IsoTpNodeIdentifier};
1619
use orb_mcu_interface::orb_messages;
@@ -318,6 +321,130 @@ impl MainBoard {
318321
Ok(())
319322
}
320323

324+
/// Replicates the MCU command sequence orb-core sends during a signup
325+
/// capture: enable the IR LEDs, set the on-duration and FPS, start the IR
326+
/// eye + face camera triggers, hold, then stop the triggers and disable the
327+
/// IR LEDs.
328+
pub async fn signup_capture(&mut self, opts: SignupOpts) -> Result<()> {
329+
use main_messaging::jetson_to_mcu::Payload;
330+
use orb_messages::main::infrared_le_ds::Wavelength as W;
331+
332+
let wavelength = match opts.wavelength {
333+
IrWavelength::L850 => W::Wavelength850nm,
334+
IrWavelength::L940 => W::Wavelength940nm,
335+
IrWavelength::L850Left => W::Wavelength850nmLeft,
336+
IrWavelength::L850Right => W::Wavelength850nmRight,
337+
IrWavelength::L940Left => W::Wavelength940nmLeft,
338+
IrWavelength::L940Right => W::Wavelength940nmRight,
339+
IrWavelength::L850Center => W::Wavelength850nmCenter,
340+
IrWavelength::L850Side => W::Wavelength850nmSide,
341+
IrWavelength::L940Single => W::Wavelength940nmSingle,
342+
};
343+
344+
// --- enable (orb-core: set_ir_wavelength, set_ir_duration, set_fps, start triggers) ---
345+
self.send_checked(
346+
Payload::InfraredLeds(main_messaging::InfraredLeDs {
347+
wavelength: wavelength as i32,
348+
}),
349+
&format!("⚡️ IR LEDs enabled ({:?})", opts.wavelength),
350+
)
351+
.await?;
352+
self.send_checked(
353+
Payload::LedOnTime(main_messaging::LedOnTimeUs {
354+
on_duration_us: u32::from(opts.duration_us),
355+
}),
356+
&format!("💡 IR LED on-duration set to {}us", opts.duration_us),
357+
)
358+
.await?;
359+
self.send_checked(
360+
Payload::Fps(main_messaging::Fps { fps: opts.fps }),
361+
&format!("🎥 FPS set to {}", opts.fps),
362+
)
363+
.await?;
364+
self.send_checked(
365+
Payload::StartTriggeringIrEyeCamera(
366+
main_messaging::StartTriggeringIrEyeCamera {},
367+
),
368+
"📸 IR eye camera trigger started",
369+
)
370+
.await?;
371+
self.send_checked(
372+
Payload::StartTriggeringIrFaceCamera(
373+
main_messaging::StartTriggeringIrFaceCamera {},
374+
),
375+
"📸 IR face camera trigger started",
376+
)
377+
.await?;
378+
if opts.rgb {
379+
self.send_checked(
380+
Payload::StartTriggeringRgbFaceCamera(
381+
main_messaging::StartTriggeringRgbFaceCamera {},
382+
),
383+
"📸 RGB face camera trigger started",
384+
)
385+
.await?;
386+
}
387+
388+
info!("⏳ holding capture for {}s ...", opts.seconds);
389+
time::sleep(Duration::from_secs(opts.seconds)).await;
390+
391+
// --- teardown (orb-core: stop triggers, disable_ir_led) ---
392+
self.send_checked(
393+
Payload::StopTriggeringIrEyeCamera(
394+
main_messaging::StopTriggeringIrEyeCamera {},
395+
),
396+
"🛑 IR eye camera trigger stopped",
397+
)
398+
.await?;
399+
self.send_checked(
400+
Payload::StopTriggeringIrFaceCamera(
401+
main_messaging::StopTriggeringIrFaceCamera {},
402+
),
403+
"🛑 IR face camera trigger stopped",
404+
)
405+
.await?;
406+
if opts.rgb {
407+
self.send_checked(
408+
Payload::StopTriggeringRgbFaceCamera(
409+
main_messaging::StopTriggeringRgbFaceCamera {},
410+
),
411+
"🛑 RGB face camera trigger stopped",
412+
)
413+
.await?;
414+
}
415+
self.send_checked(
416+
Payload::InfraredLeds(main_messaging::InfraredLeDs {
417+
wavelength: W::None as i32,
418+
}),
419+
"🌑 IR LEDs disabled",
420+
)
421+
.await?;
422+
self.send_checked(
423+
Payload::LedOnTime(main_messaging::LedOnTimeUs { on_duration_us: 0 }),
424+
"💡 IR LED on-duration reset to 0",
425+
)
426+
.await?;
427+
428+
Ok(())
429+
}
430+
431+
/// Sends a main-MCU payload and turns a non-success ack into an error,
432+
/// logging `what` on success.
433+
async fn send_checked(
434+
&mut self,
435+
payload: main_messaging::jetson_to_mcu::Payload,
436+
what: &str,
437+
) -> Result<()> {
438+
match self.send(McuPayload::ToMain(payload)).await {
439+
Ok(CommonAckError::Success) => {
440+
info!("{}", what);
441+
Ok(())
442+
}
443+
Ok(ack) => Err(eyre!("{what}: ack {ack:?}")),
444+
Err(e) => Err(eyre!("{what}: {e:?}")),
445+
}
446+
}
447+
321448
pub(crate) async fn polarizer(&mut self, opts: PolarizerOpts) -> Result<()> {
322449
let (command, angle) = match opts {
323450
PolarizerOpts::Home => (

0 commit comments

Comments
 (0)