1use cu29_traits::{CuError, CuResult};
7use html_escape::encode_text;
8use petgraph::stable_graph::{EdgeIndex, StableDiGraph};
9use petgraph::visit::EdgeRef;
10pub use petgraph::Direction::Incoming;
11pub use petgraph::Direction::Outgoing;
12use ron::extensions::Extensions;
13use ron::value::Value as RonValue;
14use ron::{Number, Options};
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use std::collections::HashMap;
17use std::fmt;
18use std::fmt::Display;
19use std::fs::read_to_string;
20use ConfigGraphs::{Missions, Simple};
21
22pub type NodeId = u32;
25
26#[derive(Serialize, Deserialize, Debug, Clone, Default)]
30pub struct ComponentConfig(pub HashMap<String, Value>);
31
32impl Display for ComponentConfig {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 let mut first = true;
35 let ComponentConfig(config) = self;
36 write!(f, "{{")?;
37 for (key, value) in config.iter() {
38 if !first {
39 write!(f, ", ")?;
40 }
41 write!(f, "{key}: {value}")?;
42 first = false;
43 }
44 write!(f, "}}")
45 }
46}
47
48impl ComponentConfig {
50 #[allow(dead_code)]
51 pub fn new() -> Self {
52 ComponentConfig(HashMap::new())
53 }
54
55 #[allow(dead_code)]
56 pub fn get<T: From<Value>>(&self, key: &str) -> Option<T> {
57 let ComponentConfig(config) = self;
58 config.get(key).map(|v| T::from(v.clone()))
59 }
60
61 #[allow(dead_code)]
62 pub fn set<T: Into<Value>>(&mut self, key: &str, value: T) {
63 let ComponentConfig(config) = self;
64 config.insert(key.to_string(), value.into());
65 }
66}
67
68#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
77pub struct Value(RonValue);
78
79impl From<i32> for Value {
80 fn from(value: i32) -> Self {
81 Value(RonValue::Number(value.into()))
82 }
83}
84
85impl From<u32> for Value {
86 fn from(value: u32) -> Self {
87 Value(RonValue::Number((value as u64).into()))
88 }
89}
90
91impl From<u16> for Value {
92 fn from(value: u16) -> Self {
93 Value(RonValue::Number((value as u64).into()))
94 }
95}
96
97impl From<u8> for Value {
98 fn from(value: u8) -> Self {
99 Value(RonValue::Number((value as u64).into()))
100 }
101}
102
103impl From<f64> for Value {
104 fn from(value: f64) -> Self {
105 Value(RonValue::Number(value.into()))
106 }
107}
108
109impl From<Value> for bool {
110 fn from(value: Value) -> Self {
111 if let Value(RonValue::Bool(v)) = value {
112 v
113 } else {
114 panic!("Expected a Boolean variant but got {value:?}")
115 }
116 }
117}
118macro_rules! impl_from_value_for_int {
119 ($($target:ty),* $(,)?) => {
120 $(
121 impl From<Value> for $target {
122 fn from(value: Value) -> Self {
123 if let Value(RonValue::Number(num)) = value {
124 match num {
125 Number::I8(n) => n as $target,
126 Number::I16(n) => n as $target,
127 Number::I32(n) => n as $target,
128 Number::I64(n) => n as $target,
129 Number::U8(n) => n as $target,
130 Number::U16(n) => n as $target,
131 Number::U32(n) => n as $target,
132 Number::U64(n) => n as $target,
133 Number::F32(_) | Number::F64(_) => {
134 panic!("Expected an integer Number variant but got {num:?}")
135 }
136 }
137 } else {
138 panic!("Expected a Number variant but got {value:?}")
139 }
140 }
141 }
142 )*
143 };
144}
145
146impl_from_value_for_int!(u8, i8, u16, i16, u32, i32, u64, i64);
147
148impl From<Value> for f64 {
149 fn from(value: Value) -> Self {
150 if let Value(RonValue::Number(num)) = value {
151 num.into_f64()
152 } else {
153 panic!("Expected a Number variant but got {value:?}")
154 }
155 }
156}
157
158impl From<String> for Value {
159 fn from(value: String) -> Self {
160 Value(RonValue::String(value))
161 }
162}
163
164impl From<Value> for String {
165 fn from(value: Value) -> Self {
166 if let Value(RonValue::String(s)) = value {
167 s
168 } else {
169 panic!("Expected a String variant")
170 }
171 }
172}
173
174impl Display for Value {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 let Value(value) = self;
177 match value {
178 RonValue::Number(n) => {
179 let s = match n {
180 Number::I8(n) => n.to_string(),
181 Number::I16(n) => n.to_string(),
182 Number::I32(n) => n.to_string(),
183 Number::I64(n) => n.to_string(),
184 Number::U8(n) => n.to_string(),
185 Number::U16(n) => n.to_string(),
186 Number::U32(n) => n.to_string(),
187 Number::U64(n) => n.to_string(),
188 Number::F32(n) => n.0.to_string(),
189 Number::F64(n) => n.0.to_string(),
190 _ => panic!("Expected a Number variant but got {value:?}"),
191 };
192 write!(f, "{s}")
193 }
194 RonValue::String(s) => write!(f, "{s}"),
195 RonValue::Bool(b) => write!(f, "{b}"),
196 RonValue::Map(m) => write!(f, "{m:?}"),
197 RonValue::Char(c) => write!(f, "{c:?}"),
198 RonValue::Unit => write!(f, "unit"),
199 RonValue::Option(o) => write!(f, "{o:?}"),
200 RonValue::Seq(s) => write!(f, "{s:?}"),
201 RonValue::Bytes(bytes) => write!(f, "{bytes:?}"),
202 }
203 }
204}
205
206#[derive(Serialize, Deserialize, Debug, Clone)]
209pub struct Node {
210 id: String,
211
212 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
213 type_: Option<String>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
216 config: Option<ComponentConfig>,
217
218 missions: Option<Vec<String>>,
219}
220
221impl Node {
222 #[allow(dead_code)]
223 pub fn new(id: &str, ptype: &str) -> Self {
224 Node {
225 id: id.to_string(),
226 type_: Some(ptype.to_string()),
227 config: None,
229 missions: None,
230 }
231 }
232
233 #[allow(dead_code)]
234 pub fn get_id(&self) -> String {
235 self.id.clone()
236 }
237
238 #[allow(dead_code)]
239 pub fn set_type(mut self, name: Option<String>) -> Self {
240 self.type_ = name;
241 self
242 }
243
244 #[allow(dead_code)]
245 pub fn get_type(&self) -> &str {
246 self.type_.as_ref().unwrap()
247 }
248
249 #[allow(dead_code)]
250 pub fn get_instance_config(&self) -> Option<&ComponentConfig> {
251 self.config.as_ref()
252 }
253
254 #[allow(dead_code)]
255 pub fn get_param<T: From<Value>>(&self, key: &str) -> Option<T> {
256 let pc = self.config.as_ref()?;
257 let ComponentConfig(pc) = pc;
258 let v = pc.get(key)?;
259 Some(T::from(v.clone()))
260 }
261
262 #[allow(dead_code)]
263 pub fn set_param<T: Into<Value>>(&mut self, key: &str, value: T) {
264 if self.config.is_none() {
265 self.config = Some(ComponentConfig(HashMap::new()));
266 }
267 let ComponentConfig(config) = self.config.as_mut().unwrap();
268 config.insert(key.to_string(), value.into());
269 }
270}
271
272#[derive(Serialize, Deserialize, Debug, Clone)]
274pub struct Cnx {
275 src: String,
277
278 dst: String,
280
281 pub msg: String,
283
284 pub missions: Option<Vec<String>>,
286
287 pub store: Option<bool>,
289}
290
291pub type CuGraph = StableDiGraph<Node, Cnx, NodeId>;
292
293#[derive(Debug, Clone)]
294pub enum ConfigGraphs {
295 Simple(CuGraph),
296 Missions(HashMap<String, CuGraph>),
297}
298
299impl ConfigGraphs {
300 #[allow(dead_code)]
301 pub fn get_all_nodes(&self, mission_id: Option<&str>) -> Vec<(NodeId, &Node)> {
302 match self {
303 Simple(graph) => {
304 if mission_id.is_none() {
305 graph
306 .node_indices()
307 .map(|index| (index.index() as u32, &graph[index]))
308 .collect()
309 } else {
310 Vec::new()
311 }
312 }
313 Missions(graphs) => {
314 if let Some(id) = mission_id {
315 if let Some(graph) = graphs.get(id) {
316 graph
317 .node_indices()
318 .map(|index| (index.index() as u32, &graph[index]))
319 .collect()
320 } else {
321 Vec::new()
322 }
323 } else {
324 Vec::new()
325 }
326 }
327 }
328 }
329
330 pub fn node_indices(&self, mission_id: Option<&str>) -> Vec<petgraph::stable_graph::NodeIndex> {
331 match self {
332 Simple(graph) => {
333 if mission_id.is_none() {
334 graph.node_indices().collect()
335 } else {
336 Vec::new()
337 }
338 }
339 Missions(graphs) => {
340 if let Some(id) = mission_id {
341 graphs
342 .get(id)
343 .map(|graph| graph.node_indices().collect())
344 .unwrap_or_default()
345 } else {
346 Vec::new()
347 }
348 }
349 }
350 }
351
352 pub fn get_node_input_msg_type(
353 &self,
354 node_id: &str,
355 mission_id: Option<&str>,
356 ) -> Option<String> {
357 match self {
358 Simple(graph) => {
359 if mission_id.is_none() {
360 ConfigGraphs::get_node_input_msg_type_from_graph(graph, node_id)
361 } else {
362 None
363 }
364 }
365 Missions(graphs) => {
366 if let Some(id) = mission_id {
367 if let Some(graph) = graphs.get(id) {
368 ConfigGraphs::get_node_input_msg_type_from_graph(graph, node_id)
369 } else {
370 None
371 }
372 } else {
373 None
374 }
375 }
376 }
377 }
378 pub fn add_node(&mut self, node: Node, mission_id: Option<&str>) -> CuResult<NodeId> {
379 let graph = self.get_graph_mut(mission_id)?;
380 Ok(graph.add_node(node).index() as NodeId)
381 }
382
383 pub fn connect_ext(
384 &mut self,
385 source: NodeId,
386 target: NodeId,
387 msg_type: &str,
388 store: Option<bool>,
389 mission_id: Option<&str>,
390 missions: Option<Vec<String>>,
391 ) -> CuResult<()> {
392 let (src_id, dst_id) = (
393 self.get_node(source, mission_id)
394 .ok_or("Source node not found")?
395 .id
396 .clone(),
397 self.get_node(target, mission_id)
398 .ok_or("Target node not found")?
399 .id
400 .clone(),
401 );
402
403 let graph = self.get_graph_mut(mission_id)?;
404 graph.add_edge(
405 source.into(),
406 target.into(),
407 Cnx {
408 src: src_id,
409 dst: dst_id,
410 msg: msg_type.to_string(),
411 missions,
412 store,
413 },
414 );
415 Ok(())
416 }
417
418 pub fn get_graph(&self, mission_id: Option<&str>) -> CuResult<&CuGraph> {
419 match self {
420 Simple(graph) => {
421 if mission_id.is_none() {
422 Ok(graph)
423 } else {
424 Err("Cannot get mission graph from simple config".into())
425 }
426 }
427 Missions(graphs) => {
428 if let Some(id) = mission_id {
429 graphs
430 .get(id)
431 .ok_or_else(|| format!("Mission {id} not found").into())
432 } else {
433 Err("Mission ID required for mission configs".into())
434 }
435 }
436 }
437 }
438
439 pub fn get_node(&self, node_id: NodeId, mission_id: Option<&str>) -> Option<&Node> {
443 match self {
444 Simple(graph) => {
445 if mission_id.is_none() {
446 graph.node_weight(node_id.into())
447 } else {
448 None
449 }
450 }
451 Missions(graphs) => {
452 if let Some(id) = mission_id {
453 if let Some(graph) = graphs.get(id) {
454 graph.node_weight(node_id.into())
455 } else {
456 None
457 }
458 } else {
459 None
460 }
461 }
462 }
463 }
464
465 pub fn get_node_mut(&mut self, node_id: NodeId, mission_id: Option<&str>) -> Option<&mut Node> {
466 match self {
467 Simple(graph) => {
468 if mission_id.is_none() {
469 graph.node_weight_mut(node_id.into())
470 } else {
471 None
472 }
473 }
474 Missions(graphs) => {
475 if let Some(id) = mission_id {
476 if let Some(graph) = graphs.get_mut(id) {
477 graph.node_weight_mut(node_id.into())
478 } else {
479 None
480 }
481 } else {
482 None
483 }
484 }
485 }
486 }
487
488 pub fn get_node_output_msg_type(
489 &self,
490 node_id: &str,
491 mission_id: Option<&str>,
492 ) -> Option<String> {
493 match self {
494 Simple(graph) => {
495 if mission_id.is_none() {
496 ConfigGraphs::get_node_output_msg_type_from_graph(graph, node_id)
497 } else {
498 None
499 }
500 }
501 Missions(graphs) => {
502 if let Some(id) = mission_id {
503 if let Some(graph) = graphs.get(id) {
504 ConfigGraphs::get_node_output_msg_type_from_graph(graph, node_id)
505 } else {
506 None
507 }
508 } else {
509 None
510 }
511 }
512 }
513 }
514
515 pub fn get_graph_mut(&mut self, mission_id: Option<&str>) -> CuResult<&mut CuGraph> {
516 match self {
517 Simple(graph) => {
518 if mission_id.is_none() {
519 Ok(graph)
520 } else {
521 Err("Cannot get mission graph from simple config".into())
522 }
523 }
524 ConfigGraphs::Missions(graphs) => {
525 if let Some(id) = mission_id {
526 graphs
527 .get_mut(id)
528 .ok_or_else(|| format!("Mission {id} not found").into())
529 } else {
530 Err("Mission ID required for mission configs".into())
531 }
532 }
533 }
534 }
535
536 pub fn get_edge_weight(&self, index: usize, mission_id: Option<&str>) -> Option<Cnx> {
537 match self {
538 Simple(graph) => {
539 if mission_id.is_none() {
540 graph.edge_weight(EdgeIndex::new(index)).cloned()
541 } else {
542 None
543 }
544 }
545 Missions(graphs) => {
546 if let Some(id) = mission_id {
547 graphs.get(id)?.edge_weight(EdgeIndex::new(index)).cloned()
548 } else {
549 None
550 }
551 }
552 }
553 }
554
555 fn get_node_output_msg_type_from_graph(graph: &CuGraph, node_id: &str) -> Option<String> {
556 graph.node_indices().find_map(|node_index| {
557 if let Some(node) = graph.node_weight(node_index) {
558 if node.id != node_id {
559 return None;
560 }
561 let edges: Vec<_> = graph
562 .edges_directed(node_index, Outgoing)
563 .map(|edge| edge.id().index())
564 .collect();
565 if edges.is_empty() {
566 panic!("A CuSrcTask is configured with no task connected to it.")
567 }
568 let cnx = graph
569 .edge_weight(EdgeIndex::new(edges[0]))
570 .expect("Found an cnx id but could not retrieve it back");
571 return Some(cnx.msg.clone());
572 }
573 None
574 })
575 }
576
577 fn get_node_input_msg_type_from_graph(graph: &CuGraph, node_id: &str) -> Option<String> {
578 graph.node_indices().find_map(|node_index| {
579 if let Some(node) = graph.node_weight(node_index) {
580 if node.id != node_id {
581 return None;
582 }
583 let edges: Vec<_> = graph
584 .edges_directed(node_index, Incoming)
585 .map(|edge| edge.id().index())
586 .collect();
587 if edges.is_empty() {
588 panic!("A CuSinkTask is configured with no task connected to it.")
589 }
590 let cnx = graph
591 .edge_weight(EdgeIndex::new(edges[0]))
592 .expect("Found an cnx id but could not retrieve it back");
593 return Some(cnx.msg.clone());
594 }
595 None
596 })
597 }
598
599 fn get_edges_by_direction(
601 &self,
602 node_id: NodeId,
603 mission_id: Option<&str>,
604 direction: petgraph::Direction,
605 ) -> CuResult<Vec<usize>> {
606 match self {
607 Simple(graph) => {
608 if mission_id.is_none() {
609 Ok(graph
610 .edges_directed(node_id.into(), direction)
611 .map(|edge| edge.id().index())
612 .collect())
613 } else {
614 Err(CuError::from("Cannot get mission edges from simple graph"))
615 }
616 }
617 Missions(graphs) => {
618 if let Some(id) = mission_id {
619 if let Some(graph) = graphs.get(id) {
620 Ok(graph
621 .edges_directed(node_id.into(), direction)
622 .map(|edge| edge.id().index())
623 .collect())
624 } else {
625 Err(CuError::from(format!("Mission {id} not found")))
626 }
627 } else {
628 Err(CuError::from("Mission ID required for mission graphs"))
629 }
630 }
631 }
632 }
633
634 pub fn add_mission(&mut self, mission_id: &str) -> CuResult<()> {
635 match self {
636 Simple(_) => Err("Cannot add mission to simple config".into()),
637 Missions(graphs) => {
638 if graphs.contains_key(mission_id) {
639 Err(format!("Mission {mission_id} already exists").into())
640 } else {
641 graphs.insert(mission_id.to_string(), CuGraph::default());
642 Ok(())
643 }
644 }
645 }
646 }
647}
648
649#[derive(Debug, Clone)]
652pub struct CuConfig {
653 pub monitor: Option<MonitorConfig>,
655 pub logging: Option<LoggingConfig>,
656 pub graphs: ConfigGraphs,
657}
658
659#[derive(Serialize, Deserialize, Default, Debug, Clone)]
660pub struct MonitorConfig {
661 #[serde(rename = "type")]
662 type_: String,
663 #[serde(skip_serializing_if = "Option::is_none")]
664 config: Option<ComponentConfig>,
665}
666
667impl MonitorConfig {
668 #[allow(dead_code)]
669 pub fn get_type(&self) -> &str {
670 &self.type_
671 }
672
673 #[allow(dead_code)]
674 pub fn get_config(&self) -> Option<&ComponentConfig> {
675 self.config.as_ref()
676 }
677}
678
679fn default_as_true() -> bool {
680 true
681}
682
683#[derive(Serialize, Deserialize, Default, Debug, Clone)]
684pub struct LoggingConfig {
685 #[serde(skip_serializing_if = "Option::is_none")]
686 pub slab_size_mib: Option<u64>,
687 #[serde(skip_serializing_if = "Option::is_none")]
688 pub section_size_mib: Option<u64>,
689 #[serde(default = "default_as_true", skip_serializing_if = "Clone::clone")]
690 pub enable_task_logging: bool,
691}
692
693#[derive(Serialize, Deserialize, Debug, Clone)]
695pub struct MissionsConfig {
696 pub id: String,
697}
698
699#[derive(Serialize, Deserialize, Debug, Clone)]
701pub struct IncludesConfig {
702 pub path: String,
703 pub params: HashMap<String, Value>,
704 pub missions: Option<Vec<String>>,
705}
706
707#[derive(Serialize, Deserialize, Default)]
709struct CuConfigRepresentation {
710 tasks: Option<Vec<Node>>,
711 cnx: Option<Vec<Cnx>>,
712 monitor: Option<MonitorConfig>,
713 logging: Option<LoggingConfig>,
714 missions: Option<Vec<MissionsConfig>>,
715 includes: Option<Vec<IncludesConfig>>,
716}
717
718impl<'de> Deserialize<'de> for CuConfig {
719 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
721 where
722 D: Deserializer<'de>,
723 {
724 let representation =
725 CuConfigRepresentation::deserialize(deserializer).map_err(serde::de::Error::custom)?;
726 let mut cuconfig = CuConfig::default();
727
728 if let Some(mission_configs) = &representation.missions {
729 let mut missions = Missions(HashMap::new());
731
732 for mission_config in mission_configs {
733 let mission_id = mission_config.id.as_str();
734 missions
735 .add_mission(mission_id)
736 .map_err(serde::de::Error::custom)?;
737 if let Some(tasks) = &representation.tasks {
738 for task in tasks {
739 if let Some(task_missions) = &task.missions {
740 if task_missions.contains(&mission_id.to_owned()) {
742 missions
743 .add_node(task.clone(), Some(mission_id))
744 .map_err(serde::de::Error::custom)?;
745 }
746 } else {
747 missions
749 .add_node(task.clone(), Some(mission_id))
750 .map_err(serde::de::Error::custom)?;
751 }
752 }
753 }
754
755 if let Some(cnx) = &representation.cnx {
756 for c in cnx {
757 if let Some(cnx_missions) = &c.missions {
758 if cnx_missions.contains(&mission_id.to_owned()) {
760 let src = missions
761 .node_indices(Some(mission_id))
762 .into_iter()
763 .find(|i| {
764 missions
765 .get_node(i.index() as NodeId, Some(mission_id))
766 .unwrap()
767 .id
768 == c.src
769 })
770 .expect("Source node not found");
771 let dst = missions
772 .node_indices(Some(mission_id))
773 .into_iter()
774 .find(|i| {
775 missions
776 .get_node(i.index() as NodeId, Some(mission_id))
777 .unwrap()
778 .id
779 == c.dst
780 })
781 .unwrap_or_else(|| {
782 panic!("Destination {} node not found", c.dst)
783 });
784 missions
785 .connect_ext(
786 src.index() as NodeId,
787 dst.index() as NodeId,
788 &c.msg,
789 c.store,
790 Some(mission_id),
791 Some(cnx_missions.clone()),
792 )
793 .map_err(serde::de::Error::custom)?;
794 }
795 } else {
796 let src = missions
798 .node_indices(Some(mission_id))
799 .into_iter()
800 .find(|i| {
801 missions
802 .get_node(i.index() as NodeId, Some(mission_id))
803 .unwrap()
804 .id
805 == c.src
806 })
807 .expect("Source node not found");
808 let dst = missions
809 .node_indices(Some(mission_id))
810 .into_iter()
811 .find(|i| {
812 missions
813 .get_node(i.index() as NodeId, Some(mission_id))
814 .unwrap()
815 .id
816 == c.dst
817 })
818 .unwrap_or_else(|| panic!("Destination {} node not found", c.dst));
819 missions
820 .connect_ext(
821 src.index() as NodeId,
822 dst.index() as NodeId,
823 &c.msg,
824 c.store,
825 Some(mission_id),
826 None,
827 )
828 .map_err(serde::de::Error::custom)?;
829 }
830 }
831 }
832 }
833 cuconfig.graphs = missions;
834 } else {
835 let mut graphs = Simple(CuGraph::default());
837
838 if let Some(tasks) = representation.tasks {
839 for task in tasks {
840 graphs
841 .add_node(task, None)
842 .map_err(serde::de::Error::custom)?;
843 }
844 }
845
846 if let Some(cnx) = representation.cnx {
847 for c in cnx {
848 let src = graphs
849 .node_indices(None)
850 .into_iter()
851 .find(|i| graphs.get_node(i.index() as NodeId, None).unwrap().id == c.src)
852 .expect("Source node not found");
853 let dst = graphs
854 .node_indices(None)
855 .into_iter()
856 .find(|i| graphs.get_node(i.index() as NodeId, None).unwrap().id == c.dst)
857 .unwrap_or_else(|| panic!("Destination {} node not found", c.dst));
858 graphs
859 .connect_ext(
860 src.index() as NodeId,
861 dst.index() as NodeId,
862 &c.msg,
863 c.store,
864 None,
865 None,
866 )
867 .map_err(serde::de::Error::custom)?;
868 }
869 }
870 cuconfig.graphs = graphs;
871 }
872
873 cuconfig.monitor = representation.monitor;
874 cuconfig.logging = representation.logging;
875
876 Ok(cuconfig)
877 }
878}
879
880impl Serialize for CuConfig {
881 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
883 where
884 S: Serializer,
885 {
886 match &self.graphs {
887 Simple(graph) => {
888 let tasks: Vec<Node> = graph.node_indices().map(|idx| graph[idx].clone()).collect();
889
890 let cnx: Vec<Cnx> = graph
891 .edge_indices()
892 .map(|edge| graph[edge].clone())
893 .collect();
894
895 CuConfigRepresentation {
896 tasks: Some(tasks),
897 cnx: Some(cnx),
898 monitor: self.monitor.clone(),
899 logging: self.logging.clone(),
900 missions: None,
901 includes: None,
902 }
903 .serialize(serializer)
904 }
905 Missions(graphs) => {
906 let missions = graphs
907 .keys()
908 .map(|id| MissionsConfig { id: id.clone() })
909 .collect();
910
911 let mut tasks = Vec::new();
913 let mut cnx = Vec::new();
914
915 for graph in graphs.values() {
916 for node_idx in graph.node_indices() {
918 let node = &graph[node_idx];
919 if !tasks.iter().any(|n: &Node| n.id == node.id) {
920 tasks.push(node.clone());
921 }
922 }
923
924 for edge_idx in graph.edge_indices() {
926 let edge = &graph[edge_idx];
927 if !cnx.iter().any(|c: &Cnx| {
928 c.src == edge.src && c.dst == edge.dst && c.msg == edge.msg
929 }) {
930 cnx.push(edge.clone());
931 }
932 }
933 }
934
935 CuConfigRepresentation {
936 tasks: Some(tasks),
937 cnx: Some(cnx),
938 monitor: self.monitor.clone(),
939 logging: self.logging.clone(),
940 missions: Some(missions),
941 includes: None,
942 }
943 .serialize(serializer)
944 }
945 }
946 }
947}
948
949impl Default for CuConfig {
950 fn default() -> Self {
951 CuConfig {
952 graphs: Simple(StableDiGraph::new()),
953 monitor: None,
954 logging: None,
955 }
956 }
957}
958
959impl CuConfig {
962 #[allow(dead_code)]
963 pub fn new_simple_type() -> Self {
964 Self::default()
965 }
966
967 #[allow(dead_code)]
968 pub fn new_mission_type() -> Self {
969 CuConfig {
970 graphs: Missions(HashMap::new()),
971 monitor: None,
972 logging: None,
973 }
974 }
975
976 #[allow(dead_code)]
981 pub fn add_node(&mut self, node: Node, mission_id: Option<&str>) -> CuResult<NodeId> {
982 self.graphs.add_node(node, mission_id)
983 }
984
985 #[allow(dead_code)]
986 pub fn get_node(&self, node_id: NodeId, mission_id: Option<&str>) -> Option<&Node> {
987 self.graphs.get_node(node_id, mission_id)
988 }
989
990 #[allow(dead_code)] pub fn get_node_mut(&mut self, node_id: NodeId, mission_id: Option<&str>) -> Option<&mut Node> {
993 self.graphs.get_node_mut(node_id, mission_id)
994 }
995
996 #[allow(dead_code)] pub fn get_node_output_msg_type(
998 &self,
999 node_id: &str,
1000 mission_id: Option<&str>,
1001 ) -> Option<String> {
1002 self.graphs.get_node_output_msg_type(node_id, mission_id)
1003 }
1004
1005 #[allow(dead_code)] pub fn get_node_input_msg_type(
1007 &self,
1008 node_id: &str,
1009 mission_id: Option<&str>,
1010 ) -> Option<String> {
1011 self.graphs.get_node_input_msg_type(node_id, mission_id)
1012 }
1013
1014 pub fn get_src_edges(&self, node_id: NodeId, mission_id: Option<&str>) -> CuResult<Vec<usize>> {
1015 self.graphs
1016 .get_edges_by_direction(node_id, mission_id, Outgoing)
1017 }
1018
1019 pub fn get_dst_edges(&self, node_id: NodeId, mission_id: Option<&str>) -> CuResult<Vec<usize>> {
1021 self.graphs
1022 .get_edges_by_direction(node_id, mission_id, Incoming)
1023 }
1024
1025 #[allow(dead_code)]
1026 pub fn get_edge_weight(&self, index: usize, mission_id: Option<&str>) -> Option<Cnx> {
1027 self.graphs.get_edge_weight(index, mission_id)
1028 }
1029
1030 #[allow(dead_code)]
1034 pub fn get_all_nodes(&self, mission_id: Option<&str>) -> Vec<(NodeId, &Node)> {
1035 self.graphs.get_all_nodes(mission_id)
1036 }
1037
1038 #[allow(dead_code)]
1043 pub fn connect_ext(
1044 &mut self,
1045 source: NodeId,
1046 target: NodeId,
1047 msg_type: &str,
1048 store: Option<bool>,
1049 mission_id: Option<&str>,
1050 missions: Option<Vec<String>>,
1051 ) -> CuResult<()> {
1052 self.graphs
1053 .connect_ext(source, target, msg_type, store, mission_id, missions)
1054 }
1055
1056 #[allow(dead_code)]
1059 pub fn connect(&mut self, source: NodeId, target: NodeId, msg_type: &str) -> CuResult<()> {
1060 self.connect_ext(source, target, msg_type, None, None, None)
1061 }
1062
1063 fn get_options() -> Options {
1064 Options::default()
1065 .with_default_extension(Extensions::IMPLICIT_SOME)
1066 .with_default_extension(Extensions::UNWRAP_NEWTYPES)
1067 .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES)
1068 }
1069
1070 #[allow(dead_code)]
1071 pub fn serialize_ron(&self) -> String {
1072 let ron = Self::get_options();
1073 let pretty = ron::ser::PrettyConfig::default();
1074 ron.to_string_pretty(&self, pretty).unwrap()
1075 }
1076
1077 pub fn deserialize_ron(ron: &str) -> Self {
1078 match Self::get_options().from_str(ron) {
1079 Ok(ron) => ron,
1080 Err(e) => panic!(
1081 "Syntax Error in config: {} at position {}",
1082 e.code, e.position
1083 ),
1084 }
1085 }
1086
1087 pub fn render(
1089 &self,
1090 output: &mut dyn std::io::Write,
1091 mission_id: Option<&str>,
1092 ) -> CuResult<()> {
1093 writeln!(output, "digraph G {{").unwrap();
1094
1095 let graph = self.graphs.get_graph(mission_id)?;
1096
1097 for index in graph.node_indices() {
1098 let node = &graph[index];
1099 let config_str = match &node.config {
1100 Some(config) => {
1101 let config_str = config
1102 .0
1103 .iter()
1104 .map(|(k, v)| format!("<B>{k}</B> = {v}<BR ALIGN=\"LEFT\"/>"))
1105 .collect::<Vec<String>>()
1106 .join("\n");
1107 format!("____________<BR/><BR ALIGN=\"LEFT\"/>{config_str}")
1108 }
1109 None => String::new(),
1110 };
1111 writeln!(output, "{} [", index.index()).unwrap();
1112 writeln!(output, "shape=box,").unwrap();
1113 writeln!(output, "style=\"rounded, filled\",").unwrap();
1114 writeln!(output, "fontname=\"Noto Sans\"").unwrap();
1115
1116 let is_src = self
1117 .get_dst_edges(index.index() as NodeId, mission_id)
1118 .unwrap_or_default()
1119 .is_empty();
1120 let is_sink = self
1121 .get_src_edges(index.index() as NodeId, mission_id)
1122 .unwrap_or_default()
1123 .is_empty();
1124 if is_src {
1125 writeln!(output, "fillcolor=lightgreen,").unwrap();
1126 } else if is_sink {
1127 writeln!(output, "fillcolor=lightblue,").unwrap();
1128 } else {
1129 writeln!(output, "fillcolor=lightgrey,").unwrap();
1130 }
1131 writeln!(output, "color=grey,").unwrap();
1132
1133 writeln!(output, "labeljust=l,").unwrap();
1134 writeln!(
1135 output,
1136 "label=< <FONT COLOR=\"red\"><B>{}</B></FONT> <FONT COLOR=\"dimgray\">[{}]</FONT><BR ALIGN=\"LEFT\"/>{} >",
1137 node.id,
1138 node.get_type(),
1139 config_str
1140 )
1141 .unwrap();
1142
1143 writeln!(output, "];").unwrap();
1144 }
1145 for edge in graph.edge_indices() {
1146 let (src, dst) = graph.edge_endpoints(edge).unwrap();
1147
1148 let cnx = &graph[edge];
1149 let msg = encode_text(&cnx.msg);
1150 writeln!(
1151 output,
1152 "{} -> {} [label=< <B><FONT COLOR=\"gray\">{}</FONT></B> >];",
1153 src.index(),
1154 dst.index(),
1155 msg
1156 )
1157 .unwrap();
1158 }
1159 writeln!(output, "}}").unwrap();
1160 Ok(())
1161 }
1162
1163 #[allow(dead_code)]
1164 pub fn get_all_instances_configs(
1165 &self,
1166 mission_id: Option<&str>,
1167 ) -> Vec<Option<&ComponentConfig>> {
1168 self.get_all_nodes(mission_id)
1169 .iter()
1170 .map(|(_, node)| node.get_instance_config())
1171 .collect()
1172 }
1173
1174 #[allow(dead_code)]
1175 pub fn get_graph(&self, mission_id: Option<&str>) -> CuResult<&CuGraph> {
1176 self.graphs.get_graph(mission_id)
1177 }
1178
1179 #[allow(dead_code)]
1180 pub fn get_graph_mut(&mut self, mission_id: Option<&str>) -> CuResult<&mut CuGraph> {
1181 self.graphs.get_graph_mut(mission_id)
1182 }
1183
1184 #[allow(dead_code)]
1185 pub fn get_monitor_config(&self) -> Option<&MonitorConfig> {
1186 self.monitor.as_ref()
1187 }
1188
1189 pub fn validate_logging_config(&self) -> CuResult<()> {
1192 if let Some(logging) = &self.logging {
1193 return logging.validate();
1194 }
1195 Ok(())
1196 }
1197}
1198
1199impl LoggingConfig {
1200 pub fn validate(&self) -> CuResult<()> {
1202 if let Some(section_size_mib) = self.section_size_mib {
1203 if let Some(slab_size_mib) = self.slab_size_mib {
1204 if section_size_mib > slab_size_mib {
1205 return Err(CuError::from(format!("Section size ({section_size_mib} MiB) cannot be larger than slab size ({slab_size_mib} MiB). Adjust the parameters accordingly.")));
1206 }
1207 }
1208 }
1209
1210 Ok(())
1211 }
1212}
1213
1214pub fn read_configuration(config_filename: &str) -> CuResult<CuConfig> {
1216 let config_content = read_to_string(config_filename).map_err(|e| {
1217 CuError::from(format!(
1218 "Failed to read configuration file: {:?}",
1219 &config_filename
1220 ))
1221 .add_cause(e.to_string().as_str())
1222 })?;
1223 read_configuration_str(config_content)
1224}
1225
1226pub fn read_configuration_str(config_content: String) -> CuResult<CuConfig> {
1228 let cuconfig = CuConfig::deserialize_ron(&config_content);
1229 cuconfig.validate_logging_config()?;
1230
1231 Ok(cuconfig)
1232}
1233
1234#[cfg(test)]
1236mod tests {
1237 use super::*;
1238
1239 #[test]
1240 fn test_plain_serialize() {
1241 let mut config = CuConfig::default();
1242 let n1 = config
1243 .add_node(Node::new("test1", "package::Plugin1"), None)
1244 .unwrap();
1245 let n2 = config
1246 .add_node(Node::new("test2", "package::Plugin2"), None)
1247 .unwrap();
1248 config.connect(n1, n2, "msgpkg::MsgType").unwrap();
1249 let serialized = config.serialize_ron();
1250 let deserialized = CuConfig::deserialize_ron(&serialized);
1251 let graph = config.graphs.get_graph(None).unwrap();
1252 let deserialized_graph = deserialized.graphs.get_graph(None).unwrap();
1253 assert_eq!(graph.node_count(), deserialized_graph.node_count());
1254 assert_eq!(graph.edge_count(), deserialized_graph.edge_count());
1255 }
1256
1257 #[test]
1258 fn test_serialize_with_params() {
1259 let mut config = CuConfig::default();
1260 let mut camera = Node::new("copper-camera", "camerapkg::Camera");
1261 camera.set_param::<Value>("resolution-height", 1080.into());
1262 config.add_node(camera, None).unwrap();
1263 let serialized = config.serialize_ron();
1264 let deserialized = CuConfig::deserialize_ron(&serialized);
1265 assert_eq!(
1266 deserialized
1267 .get_node(0, None)
1268 .unwrap()
1269 .get_param::<i32>("resolution-height")
1270 .unwrap(),
1271 1080
1272 );
1273 }
1274
1275 #[test]
1276 #[should_panic(expected = "Syntax Error in config: Expected opening `[` at position 1:10")]
1277 fn test_deserialization_error() {
1278 let txt = r#"( tasks: (), cnx: [], monitor: (type: "ExampleMonitor", ) ) "#;
1280 CuConfig::deserialize_ron(txt);
1281 }
1282 #[test]
1283 fn test_missions() {
1284 let txt = r#"( missions: [ (id: "data_collection"), (id: "autonomous")])"#;
1285 let config = CuConfig::deserialize_ron(txt);
1286 let graph = config.graphs.get_graph(Some("data_collection")).unwrap();
1287 assert!(graph.node_count() == 0);
1288 let graph = config.graphs.get_graph(Some("autonomous")).unwrap();
1289 assert!(graph.node_count() == 0);
1290 }
1291
1292 #[test]
1293 fn test_monitor() {
1294 let txt = r#"( tasks: [], cnx: [], monitor: (type: "ExampleMonitor", ) ) "#;
1295 let config = CuConfig::deserialize_ron(txt);
1296 assert_eq!(config.monitor.as_ref().unwrap().type_, "ExampleMonitor");
1297
1298 let txt =
1299 r#"( tasks: [], cnx: [], monitor: (type: "ExampleMonitor", config: { "toto": 4, } )) "#;
1300 let config = CuConfig::deserialize_ron(txt);
1301 assert_eq!(
1302 config.monitor.as_ref().unwrap().config.as_ref().unwrap().0["toto"].0,
1303 4u8.into()
1304 );
1305 }
1306
1307 #[test]
1308 fn test_logging_parameters() {
1309 let txt = r#"( tasks: [], cnx: [], logging: ( slab_size_mib: 1024, section_size_mib: 100, enable_task_logging: false ),) "#;
1311
1312 let config = CuConfig::deserialize_ron(txt);
1313 assert!(config.logging.is_some());
1314 let logging_config = config.logging.unwrap();
1315 assert_eq!(logging_config.slab_size_mib.unwrap(), 1024);
1316 assert_eq!(logging_config.section_size_mib.unwrap(), 100);
1317 assert!(!logging_config.enable_task_logging);
1318
1319 let txt =
1321 r#"( tasks: [], cnx: [], logging: ( slab_size_mib: 1024, section_size_mib: 100, ),) "#;
1322 let config = CuConfig::deserialize_ron(txt);
1323 assert!(config.logging.is_some());
1324 let logging_config = config.logging.unwrap();
1325 assert_eq!(logging_config.slab_size_mib.unwrap(), 1024);
1326 assert_eq!(logging_config.section_size_mib.unwrap(), 100);
1327 assert!(logging_config.enable_task_logging);
1328 }
1329
1330 #[test]
1331 fn test_validate_logging_config() {
1332 let txt =
1334 r#"( tasks: [], cnx: [], logging: ( slab_size_mib: 1024, section_size_mib: 100 ) )"#;
1335 let config = CuConfig::deserialize_ron(txt);
1336 assert!(config.validate_logging_config().is_ok());
1337
1338 let txt =
1340 r#"( tasks: [], cnx: [], logging: ( slab_size_mib: 100, section_size_mib: 1024 ) )"#;
1341 let config = CuConfig::deserialize_ron(txt);
1342 assert!(config.validate_logging_config().is_err());
1343 }
1344
1345 #[test]
1347 fn test_deserialization_edge_id_assignment() {
1348 let txt = r#"(
1351 tasks: [(id: "src1", type: "a"), (id: "src2", type: "b"), (id: "sink", type: "c")],
1352 cnx: [(src: "src2", dst: "sink", msg: "msg1"), (src: "src1", dst: "sink", msg: "msg2")]
1353 )"#;
1354 let config = CuConfig::deserialize_ron(txt);
1355 assert!(config.validate_logging_config().is_ok());
1356
1357 let src1_id = 0;
1359 assert_eq!(config.get_node(src1_id, None).unwrap().id, "src1");
1360 let src2_id = 1;
1361 assert_eq!(config.get_node(src2_id, None).unwrap().id, "src2");
1362
1363 let src1_edge_id = *config
1366 .get_src_edges(src1_id, None)
1367 .unwrap()
1368 .first()
1369 .unwrap();
1370 assert_eq!(src1_edge_id, 1);
1371 let src2_edge_id = *config
1372 .get_src_edges(src2_id, None)
1373 .unwrap()
1374 .first()
1375 .unwrap();
1376 assert_eq!(src2_edge_id, 0);
1377 }
1378
1379 #[test]
1380 fn test_simple_missions() {
1381 let txt = r#"(
1383 missions: [ (id: "m1"),
1384 (id: "m2"),
1385 ],
1386 tasks: [(id: "src1", type: "a", missions: ["m1"]),
1387 (id: "src2", type: "b", missions: ["m2"]),
1388 (id: "sink", type: "c")],
1389
1390 cnx: [
1391 (src: "src1", dst: "sink", msg: "u32", missions: ["m1"]),
1392 (src: "src2", dst: "sink", msg: "u32", missions: ["m2"]),
1393 ],
1394 )
1395 "#;
1396
1397 let config = CuConfig::deserialize_ron(txt);
1398 let m1_graph = config.graphs.get_graph(Some("m1")).unwrap();
1399 assert_eq!(m1_graph.edge_count(), 1);
1400 assert_eq!(m1_graph.node_count(), 2);
1401 let index = EdgeIndex::new(0);
1402 let cnx = m1_graph.edge_weight(index).unwrap();
1403
1404 assert_eq!(cnx.src, "src1");
1405 assert_eq!(cnx.dst, "sink");
1406 assert_eq!(cnx.msg, "u32");
1407 assert_eq!(cnx.missions, Some(vec!["m1".to_string()]));
1408
1409 let m2_graph = config.graphs.get_graph(Some("m2")).unwrap();
1410 assert_eq!(m2_graph.edge_count(), 1);
1411 assert_eq!(m2_graph.node_count(), 2);
1412 let index = EdgeIndex::new(0);
1413 let cnx = m2_graph.edge_weight(index).unwrap();
1414 assert_eq!(cnx.src, "src2");
1415 assert_eq!(cnx.dst, "sink");
1416 assert_eq!(cnx.msg, "u32");
1417 assert_eq!(cnx.missions, Some(vec!["m2".to_string()]));
1418 }
1419 #[test]
1420 fn test_mission_serde() {
1421 let txt = r#"(
1423 missions: [ (id: "m1"),
1424 (id: "m2"),
1425 ],
1426 tasks: [(id: "src1", type: "a", missions: ["m1"]),
1427 (id: "src2", type: "b", missions: ["m2"]),
1428 (id: "sink", type: "c")],
1429
1430 cnx: [
1431 (src: "src1", dst: "sink", msg: "u32", missions: ["m1"]),
1432 (src: "src2", dst: "sink", msg: "u32", missions: ["m2"]),
1433 ],
1434 )
1435 "#;
1436
1437 let config = CuConfig::deserialize_ron(txt);
1438 let serialized = config.serialize_ron();
1439 let deserialized = CuConfig::deserialize_ron(&serialized);
1440 let m1_graph = deserialized.graphs.get_graph(Some("m1")).unwrap();
1441 assert_eq!(m1_graph.edge_count(), 1);
1442 assert_eq!(m1_graph.node_count(), 2);
1443 let index = EdgeIndex::new(0);
1444 let cnx = m1_graph.edge_weight(index).unwrap();
1445 assert_eq!(cnx.src, "src1");
1446 assert_eq!(cnx.dst, "sink");
1447 assert_eq!(cnx.msg, "u32");
1448 assert_eq!(cnx.missions, Some(vec!["m1".to_string()]));
1449 }
1450}