diff --git a/crates/muninn-daemon/src/main.rs b/crates/muninn-daemon/src/main.rs index 78824c4..f5d5c61 100644 --- a/crates/muninn-daemon/src/main.rs +++ b/crates/muninn-daemon/src/main.rs @@ -4255,13 +4255,29 @@ fn publish_move_controller_states( } fn active_move_marker_camera_sources(options: &Options) -> Vec { - let move_colors: Vec<(String, [u8; 3])> = serve_move_state_sources(options, true) + let mut move_roster = serve_move_state_sources(options, true) .into_iter() - .map(|source| { - let (red, green, blue) = default_move_color_for_identity(&source.move_id); - (source.move_id, [red, green, blue]) + .map(|source| source.move_id) + .collect::>(); + move_roster.sort(); + move_roster.dedup(); + let now_ns = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() as i128; + let move_colors = move_roster + .iter() + .filter_map(|identity| { + scheduled_golden_move_color( + identity, + &move_roster, + 0, + i128::from(options.move_hue_cycle_ms) * 1_000_000, + now_ns, + ) + .map(|(color, _, _)| (identity.clone(), [color.0, color.1, color.2])) }) - .collect(); + .collect::>(); options .move_marker_camera_sources .iter() @@ -4278,6 +4294,7 @@ fn active_move_marker_camera_sources(options: &Options) -> Vec, + move_hue_cycle_ms: u64, ) -> mpsc::Receiver> { let (sender, receiver) = mpsc::sync_channel(1); thread::spawn(move || { @@ -4401,9 +4419,26 @@ fn start_psmoveapi_tracker_worker( let mut last_calibration_at = Instant::now(); loop { if last_calibration_at.elapsed() >= Duration::from_secs(30) { - tracker.calibrate_connected(); + tracker.observe_connected(); last_calibration_at = Instant::now(); } + let mut roster = colors.iter().map(|(identity, _)| identity.clone()).collect::>(); + roster.sort(); + let now_ns = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() as i128; + for identity in &roster { + if let Some((color, _, _)) = scheduled_golden_move_color( + identity, + &roster, + 0, + i128::from(move_hue_cycle_ms) * 1_000_000, + now_ns, + ) { + tracker.set_expected_color(identity, [color.0, color.1, color.2]); + } + } let observations = tracker.update(); match sender.try_send(observations) { Ok(()) | Err(mpsc::TrySendError::Full(_)) => {} diff --git a/crates/muninn-psmoveapi-tracker/src/lib.rs b/crates/muninn-psmoveapi-tracker/src/lib.rs index 1daa8e9..b8d9cde 100644 --- a/crates/muninn-psmoveapi-tracker/src/lib.rs +++ b/crates/muninn-psmoveapi-tracker/src/lib.rs @@ -52,13 +52,20 @@ mod linux { fn psmove_disconnect(controller: *mut c_void); fn psmove_tracker_new_with_camera(camera: c_int) -> *mut c_void; fn psmove_tracker_free(tracker: *mut c_void); - fn psmove_tracker_enable_with_color( + fn psmove_tracker_enable_color_observer( tracker: *mut c_void, controller: *mut c_void, red: c_uchar, green: c_uchar, blue: c_uchar, ) -> c_int; + fn psmove_tracker_set_expected_color( + tracker: *mut c_void, + controller: *mut c_void, + red: c_uchar, + green: c_uchar, + blue: c_uchar, + ) -> bool; fn psmove_tracker_disable(tracker: *mut c_void, controller: *mut c_void); fn psmove_tracker_set_exposure(tracker: *mut c_void, exposure: c_float); fn psmove_tracker_get_exposure(tracker: *mut c_void) -> c_float; @@ -117,7 +124,7 @@ mod linux { exposure, }, }; - result.calibrate_connected(); + result.observe_connected(); result.camera_info = unsafe { let info = psmove_tracker_get_camera_info(tracker); if info.is_null() { @@ -139,6 +146,10 @@ mod linux { } pub fn calibrate_connected(&mut self) -> usize { + self.observe_connected() + } + + pub fn observe_connected(&mut self) -> usize { let count = unsafe { psmove_count_connected() }.max(0); for index in 0..count { let handle = unsafe { psmove_connect_by_id(index) }; @@ -158,7 +169,7 @@ mod linux { continue; }; let status = unsafe { - psmove_tracker_enable_with_color( + psmove_tracker_enable_color_observer( self.tracker, handle, color[0], color[1], color[2] ) }; @@ -172,6 +183,23 @@ mod linux { self.controllers.len() } + pub fn set_expected_color(&mut self, move_id: &str, color: [u8; 3]) -> bool { + let Some(controller) = self.controllers.iter().find(|controller| { + controller.move_id.eq_ignore_ascii_case(move_id) + }) else { + return false; + }; + unsafe { + psmove_tracker_set_expected_color( + self.tracker, + controller.handle, + color[0], + color[1], + color[2], + ) + } + } + pub fn camera_info(&self) -> &PsmoveApiCameraInfo { &self.camera_info } @@ -255,6 +283,10 @@ impl PsmoveApiTracker { pub fn calibrate_connected(&mut self) -> usize { 0 } + pub fn observe_connected(&mut self) -> usize { 0 } + + pub fn set_expected_color(&mut self, _move_id: &str, _color: [u8; 3]) -> bool { false } + pub fn tracked_controller_count(&self) -> usize { 0 } pub fn update(&mut self) -> Vec { diff --git a/native/psmoveapi-muninn/README.md b/native/psmoveapi-muninn/README.md new file mode 100644 index 0000000..6742b1a --- /dev/null +++ b/native/psmoveapi-muninn/README.md @@ -0,0 +1,13 @@ +# Muninn PSMoveAPI Observer Patch + +Muninn owns PS Move LED output. PSMoveAPI owns camera exposure, HSV filtering, +morphology, contour extraction, and marker position estimation. + +`expected-color-observer.patch` adds only the missing authority boundary: + +- enable tracking without blinking or writing LEDs; +- update the expected camera hue without writing LEDs. + +The expected hue comes from Muninn's timestamp-driven `gold(x)` schedule. This +patch must be applied to the pinned PSMoveAPI source with `git apply --recount` +before building the Nightwing tracker libraries. diff --git a/native/psmoveapi-muninn/expected-color-observer.patch b/native/psmoveapi-muninn/expected-color-observer.patch new file mode 100644 index 0000000..94ded80 --- /dev/null +++ b/native/psmoveapi-muninn/expected-color-observer.patch @@ -0,0 +1,83 @@ +diff --git a/include/psmove_tracker.h b/include/psmove_tracker.h +index 7de998b..d44db20 100644 +--- a/include/psmove_tracker.h ++++ b/include/psmove_tracker.h +@@ -235,6 +235,25 @@ ADDCALL psmove_tracker_get_auto_update_leds(PSMoveTracker *tracker, PSMove *move + ADDAPI void + ADDCALL psmove_tracker_set_exposure(PSMoveTracker *tracker, float exposure); + ++/** ++ * Enable reference blob tracking without writing controller LEDs. ++ * The caller owns LED output and supplies the expected fully-saturated RGB hue. ++ */ ++ADDAPI enum PSMoveTracker_Status ++ADDCALL psmove_tracker_enable_color_observer(PSMoveTracker *tracker, PSMove *move, ++ unsigned char r, unsigned char g, unsigned char b); ++ ++/** ++ * Change the expected RGB hue for an observer-enabled controller without ++ * writing controller LEDs or running blinking calibration. ++ */ ++ADDAPI bool ++ADDCALL psmove_tracker_set_expected_color(PSMoveTracker *tracker, PSMove *move, ++ unsigned char r, unsigned char g, unsigned char b); ++ + /** + * \brief Get exposure as fraction (0.0 -> 1.0) + **/ +diff --git a/src/tracker/psmove_tracker.cpp b/src/tracker/psmove_tracker.cpp +index 23e33ed..621b9ea 100644 +--- a/src/tracker/psmove_tracker.cpp ++++ b/src/tracker/psmove_tracker.cpp +@@ -547,6 +547,49 @@ psmove_tracker_get_exposure(PSMoveTracker *tracker) + return tracker->settings.camera_exposure; + } + ++static void ++psmove_tracker_assign_observed_color(TrackedController *tc, ++ unsigned char r, unsigned char g, unsigned char b) ++{ ++ struct PSMove_RGBValue rgb = { r, g, b }; ++ CvScalar hsv = th_rgb2hsv(cvScalar(r, g, b, 255.0)); ++ tc->color = rgb; ++ tc->auto_update_leds = false; ++ tc->eColorHSV = tc->eFColorHSV = hsv; ++ tc->assignedHSV = hsv; ++} ++ ++enum PSMoveTracker_Status ++psmove_tracker_enable_color_observer(PSMoveTracker *tracker, PSMove *move, ++ unsigned char r, unsigned char g, unsigned char b) ++{ ++ psmove_return_val_if_fail(tracker != NULL, Tracker_CALIBRATION_ERROR); ++ psmove_return_val_if_fail(move != NULL, Tracker_CALIBRATION_ERROR); ++ ++ TrackedController *tc = psmove_tracker_find_controller(tracker, move); ++ if (tc == NULL) { ++ tc = psmove_tracker_find_controller(tracker, NULL); ++ } ++ if (tc == NULL) { ++ return Tracker_CALIBRATION_ERROR; ++ } ++ tc->move = move; ++ psmove_tracker_assign_observed_color(tc, r, g, b); ++ return Tracker_CALIBRATED; ++} ++ ++bool ++psmove_tracker_set_expected_color(PSMoveTracker *tracker, PSMove *move, ++ unsigned char r, unsigned char g, unsigned char b) ++{ ++ psmove_return_val_if_fail(tracker != NULL, false); ++ psmove_return_val_if_fail(move != NULL, false); ++ TrackedController *tc = psmove_tracker_find_controller(tracker, move); ++ if (tc == NULL) { ++ return false; ++ } ++ psmove_tracker_assign_observed_color(tc, r, g, b); ++ return true; ++} ++ + void + psmove_tracker_reset_color_calibration(PSMoveTracker *tracker) + {