@@ -10,7 +10,10 @@ use crate::orb::dfu::BlockIterator;
1010use crate :: orb:: revision:: OrbRevision ;
1111use crate :: orb:: { dfu, BatteryStatus } ;
1212use 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+ } ;
1417use orb_mcu_interface:: can:: canfd:: CanRawMessaging ;
1518use orb_mcu_interface:: can:: isotp:: { CanIsoTpMessaging , IsoTpNodeIdentifier } ;
1619use 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