use crate::{ mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender}, mode_tree::{ModeStoreProvider, ModeStoreVec}, queue::{GenericSendError, GenericTargetedMessagingError}, request::{GenericMessage, RequestId}, ComponentId, }; use core::fmt::Debug; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ActiveModeCommandContext { pub target_mode: ModeAndSubmode, pub active_request_id: RequestId, } #[derive(Debug, Default, PartialEq, Eq)] pub enum DevManagerHelperResult { #[default] Idle, Busy, ModeCommandingDone(ActiveModeCommandContext), } #[derive(Debug)] pub enum DevManagerHelperError { ChildNotInStore, } pub trait DevManagerUserHook: Debug { fn send_mode_cmd_to_child( &self, request_id: RequestId, target_id: ComponentId, mode: ModeAndSubmode, forced: bool, children_mode_store: &mut ModeStoreVec, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError>; fn send_mode_cmds_to_children( &self, request_id: RequestId, commanded_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, request_id: RequestId, 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( request_id, child.id(), ModeRequest::SetMode { mode_and_submode: commanded_parent_mode, forced, }, )?; child.awaiting_reply = true; } Ok(()) } fn send_mode_cmd_to_child( &self, request_id: RequestId, target_id: ComponentId, mode: ModeAndSubmode, forced: bool, children_mode_store: &mut ModeStoreVec, mode_req_sender: &impl ModeRequestSender, ) -> Result<(), GenericTargetedMessagingError> { let mut_val = children_mode_store .get_mut(target_id) .ok_or(GenericSendError::TargetDoesNotExist(target_id))?; mut_val.awaiting_reply = true; mode_req_sender.send_mode_request( request_id, target_id, ModeRequest::SetMode { mode_and_submode: mode, forced, }, )?; Ok(()) } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum DevManagerCommandingState { #[default] Idle, AwaitingReplies(ActiveModeCommandContext), } impl DevManagerCommandingState { fn new_active_cmd(mode_and_submode: ModeAndSubmode, active_request_id: RequestId) -> Self { DevManagerCommandingState::AwaitingReplies(ActiveModeCommandContext { target_mode: mode_and_submode, active_request_id, }) } } /// 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, Default)] 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, pub state: DevManagerCommandingState, } impl DevManagerCommandingHelper { pub fn new(user_hook: UserHook) -> Self { Self { children_mode_store: Default::default(), user_hook, 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.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id); self.user_hook.send_mode_cmd_to_child( request_id, target_id, mode_and_submode, forced, &mut self.children_mode_store, mode_req_sender, )?; 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.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id); self.user_hook.send_mode_cmds_to_children( request_id, mode_and_submode, forced, &mut self.children_mode_store, mode_req_sender, )?; Ok(()) } pub fn target_mode(&self) -> Option { match self.state { DevManagerCommandingState::Idle => None, DevManagerCommandingState::AwaitingReplies(context) => Some(context.target_mode), } } pub fn state(&self) -> DevManagerCommandingState { 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 } pub fn handle_mode_reply( &mut self, mode_reply: &GenericMessage, ) -> Result { let context = match self.state { DevManagerCommandingState::Idle => return Ok(DevManagerHelperResult::Idle), DevManagerCommandingState::AwaitingReplies(active_mode_command_context) => { Some(active_mode_command_context) } }; 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 let DevManagerCommandingState::AwaitingReplies { .. } = self.state { 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 handle_awaition && !still_awating_replies.unwrap() { self.state = DevManagerCommandingState::Idle; return Ok(DevManagerHelperResult::ModeCommandingDone(context.unwrap())); } 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}, request::MessageMetadata, }; use super::*; pub enum ExampleId { Id1 = 1, Id2 = 2, } pub enum ExampleMode { Mode1 = 1, Mode2 = 2, } #[test] fn test_basic() { let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); assert_eq!(assy_helper.state(), DevManagerCommandingState::Idle); } #[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 dev_mgmt_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); let mode_req_sender = ModeReqSenderMock::default(); dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); let expected_mode = ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0); dev_mgmt_helper .send_mode_cmd_to_one_child( 1, ExampleId::Id1 as u64, expected_mode, false, &mode_req_sender, ) .unwrap(); assert_eq!(mode_req_sender.requests.borrow().len(), 1); let 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::SetMode { mode_and_submode: expected_mode, forced: false } ); matches!( dev_mgmt_helper.state(), DevManagerCommandingState::AwaitingReplies { .. } ); if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() { assert_eq!(ctx.target_mode, expected_mode); assert_eq!(ctx.active_request_id, 1); } let reply = GenericMessage::new( MessageMetadata::new(1, ExampleId::Id1 as u64), ModeReply::ModeReply(expected_mode), ); if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext { target_mode, active_request_id, }) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap() { assert_eq!(target_mode, expected_mode); assert_eq!(active_request_id, 1); } matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle); } #[test] fn test_mode_commanding_multi_child() { let mut dev_mgmt_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); let mode_req_sender = ModeReqSenderMock::default(); dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); dev_mgmt_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); let expected_mode = ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0); dev_mgmt_helper .send_mode_cmd_to_all_children(1, expected_mode, false, &mode_req_sender) .unwrap(); assert_eq!(mode_req_sender.requests.borrow().len(), 2); let 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::SetMode { mode_and_submode: expected_mode, forced: false } ); let 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::SetMode { mode_and_submode: expected_mode, forced: false } ); matches!( dev_mgmt_helper.state(), DevManagerCommandingState::AwaitingReplies { .. } ); if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() { assert_eq!(ctx.target_mode, expected_mode); assert_eq!(ctx.active_request_id, 1); } let reply = GenericMessage::new( MessageMetadata::new(1, ExampleId::Id1 as u64), ModeReply::ModeReply(expected_mode), ); assert_eq!( dev_mgmt_helper.handle_mode_reply(&reply).unwrap(), DevManagerHelperResult::Busy ); let reply = GenericMessage::new( MessageMetadata::new(1, ExampleId::Id2 as u64), ModeReply::ModeReply(expected_mode), ); if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext { target_mode, active_request_id, }) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap() { assert_eq!(target_mode, expected_mode); assert_eq!(active_request_id, 1); } matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle); } }