use crate::{ mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender}, mode_tree::{ModeStoreProvider, ModeStoreVec}, queue::GenericTargetedMessagingError, request::{GenericMessage, RequestId}, subsystem::ModeTreeHelperState, ComponentId, }; use core::fmt::Debug; #[derive(Debug, Default)] pub enum DevManagerHelperResult { #[default] Idle, Busy, ModeCommandingDone, } #[derive(Debug)] pub enum DevManagerHelperError { ChildNotInStore, } pub trait DevManagerUserHook: Debug { fn send_mode_cmds_to_children( &self, parent_mode: ModeAndSubmode, forced: bool, children_mode_store: &mut ModeStoreVec, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError>; } #[derive(Debug, Default)] pub struct TransparentDevManagerHook {} impl DevManagerUserHook for TransparentDevManagerHook { fn send_mode_cmds_to_children( &self, commanded_parent_mode: ModeAndSubmode, forced: bool, children_mode_store: &mut ModeStoreVec, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError> { for child in children_mode_store { mode_req_sender.send_mode_request( 0, child.id(), ModeRequest::SetMode { mode_and_submode: commanded_parent_mode, forced, }, )?; child.awaiting_reply = true; } Ok(()) } } /// A generic helper for manager components which manage child components in a mode tree. /// /// Mode commands are usually forwarded to all children components transparently. /// For example, this could be used in an Assembly component which manages multiple redundant /// child components. It can also be used inside a manager component which only manages one device. #[derive(Debug)] pub struct DevManagerCommandingHelper { /// The IDs, modes and reply awaition status of all children are tracked in this data /// structure. pub children_mode_store: ModeStoreVec, pub user_hook: UserHook, /// Target mode used for mode commanding. target_mode: Option, /// Request ID of active mode commanding request. active_request_id: Option, state: ModeTreeHelperState, } impl DevManagerCommandingHelper { pub fn new(user_hook: UserHook) -> Self { Self { children_mode_store: Default::default(), target_mode: None, user_hook, active_request_id: None, state: Default::default(), } } pub fn send_mode_cmd_to_one_child( &mut self, request_id: RequestId, target_id: ComponentId, mode_and_submode: ModeAndSubmode, forced: bool, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError> { self.target_mode = Some(mode_and_submode); mode_req_sender.send_mode_request( request_id, target_id, ModeRequest::SetMode { mode_and_submode, forced, }, )?; self.active_request_id = Some(request_id); self.state = ModeTreeHelperState::ModeCommanding; Ok(()) } pub fn send_mode_cmd_to_all_children( &mut self, request_id: RequestId, mode_and_submode: ModeAndSubmode, forced: bool, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError> { self.target_mode = Some(mode_and_submode); self.user_hook.send_mode_cmds_to_children( mode_and_submode, forced, &mut self.children_mode_store, mode_req_sender, )?; self.active_request_id = Some(request_id); self.state = ModeTreeHelperState::ModeCommanding; Ok(()) } pub fn target_mode(&self) -> Option { self.target_mode } pub fn active_request_id(&self) -> Option { self.active_request_id } pub fn state(&self) -> ModeTreeHelperState { self.state } pub fn send_announce_mode_cmd_to_children( &self, request_id: RequestId, mode_req_sender: &impl ModeRequestSender, recursive: bool, ) -> Result<(), GenericTargetedMessagingError> { let mut request = ModeRequest::AnnounceMode; if recursive { request = ModeRequest::AnnounceModeRecursive; } for child in self.children_mode_store.0.iter() { mode_req_sender.send_mode_request(request_id, child.id(), request)?; } Ok(()) } pub fn add_mode_child(&mut self, target_id: ComponentId, mode: ModeAndSubmode) { self.children_mode_store.add_component(target_id, mode); } /// Helper method which counts the number of children which have a certain mode. pub fn count_number_of_children_with_mode(&self, mode_and_submode: ModeAndSubmode) -> usize { let mut children_in_target_mode = 0; for child in &self.children_mode_store { if child.mode_and_submode() == mode_and_submode { children_in_target_mode += 1; } } children_in_target_mode } /// Helper method which counts the number of children which have the mode of the assembly /// itself. /// /// This is useful for device managers where the child or the children devices should have the /// same mode as the device manager. pub fn count_number_of_children_with_target_mode(&self) -> Option { Some(self.count_number_of_children_with_mode(self.target_mode?)) } pub fn handle_mode_reply( &mut self, mode_reply: &GenericMessage, ) -> Result { if self.target_mode().is_none() || self.active_request_id().is_none() { return Ok(DevManagerHelperResult::Idle); } if !self .children_mode_store .has_component(mode_reply.sender_id()) { return Err(DevManagerHelperError::ChildNotInStore); } let mut generic_mode_reply_handler = |mode_and_submode: Option| { // Tying the reply awaition to the request ID ensures that something like replies // belonging to older requests do not interfere with the completion handling of // the mode commanding. This is important for forced mode commands. let mut handle_awaition = false; if self.state == ModeTreeHelperState::ModeCommanding && self.active_request_id.is_some() && mode_reply.request_id() == self.active_request_id.unwrap() { handle_awaition = true; } let still_awating_replies = self.children_mode_store.mode_reply_handler( mode_reply.sender_id(), mode_and_submode, handle_awaition, ); // It is okay to unwrap: If awaition should be handled, the returned value should // always be some valid value. if self.state == ModeTreeHelperState::ModeCommanding && handle_awaition && !still_awating_replies.unwrap() { self.state = ModeTreeHelperState::TargetKeeping; self.active_request_id = None; return Ok(DevManagerHelperResult::ModeCommandingDone); } Ok(DevManagerHelperResult::Busy) }; match mode_reply.message { ModeReply::ModeInfo(mode_and_submode) | ModeReply::ModeReply(mode_and_submode) => { generic_mode_reply_handler(Some(mode_and_submode)) } ModeReply::CantReachMode(_result_u16) => generic_mode_reply_handler(None), ModeReply::WrongMode { expected: _, reached, } => generic_mode_reply_handler(Some(reached)), } } } #[cfg(test)] mod tests { use crate::mode::{tests::ModeReqSenderMock, UNKNOWN_MODE}; use super::*; pub enum ExampleId { Id1 = 1, Id2 = 2, } #[test] fn test_basic() { let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); assert_eq!(assy_helper.state(), ModeTreeHelperState::Idle); assert!(assy_helper.active_request_id().is_none()); assert!(assy_helper.target_mode().is_none()); } #[test] fn test_mode_announce() { let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); let mode_req_sender = ModeReqSenderMock::default(); assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); assy_helper .send_announce_mode_cmd_to_children(1, &mode_req_sender, false) .unwrap(); assert_eq!(mode_req_sender.requests.borrow().len(), 2); let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); assert_eq!(req.target_id, ExampleId::Id1 as u64); assert_eq!(req.request_id, 1); assert_eq!(req.request, ModeRequest::AnnounceMode); req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); assert_eq!(req.target_id, ExampleId::Id2 as u64); assert_eq!(req.request_id, 1); assert_eq!(req.request, ModeRequest::AnnounceMode); } #[test] fn test_mode_announce_recursive() { let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); let mode_req_sender = ModeReqSenderMock::default(); assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); assy_helper .send_announce_mode_cmd_to_children(1, &mode_req_sender, true) .unwrap(); assert_eq!(mode_req_sender.requests.borrow().len(), 2); let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); assert_eq!(req.target_id, ExampleId::Id1 as u64); assert_eq!(req.request_id, 1); assert_eq!(req.request, ModeRequest::AnnounceModeRecursive); req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); assert_eq!(req.target_id, ExampleId::Id2 as u64); assert_eq!(req.request_id, 1); assert_eq!(req.request, ModeRequest::AnnounceModeRecursive); } #[test] fn test_mode_commanding_one_child() { let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); let mode_req_sender = ModeReqSenderMock::default(); //assy_helper.send_mode_cmd_to_all_children_with_reply_awaition(1, , forced, mode_req_sender) } }