1use crate::{error::Error, pose::field_layout::AprilTagFieldLayout};
2use std::{
3 collections::HashMap,
4 fs::File,
5 io::{Read, Write},
6 path::Path,
7};
8
9macro_rules! def_cfg {
10 ($(
11 $struct_ident:ident {
12 $(
13 $(# [ $attr:ident $( ( $tt:tt ) )* ])?
14 $ident:ident : $ty:ty ,
15 )*
16 }
17 )*) => {
18 $(
19 #[derive(Deserialize, Serialize, Debug, Clone)]
20 #[cfg_attr(feature = "web", derive(utopia::ToSchema))]
21 pub struct $struct_ident {
22 $(
23 $(#[$attr $( ($tt) )?])?
24 pub $ident: $ty,
25 )*
26 }
27 )*
28 };
29}
30
31def_cfg! {
32 Config {
33 team_number: u16,
34 ntables_ip: Option<String>,
35 rerun: Option<Rerun>,
36 cameras: Option<Vec<Camera>>,
37 device_name: Option<String>,
38
39 field_layout: Option<String>,
40 field_layouts: Option<HashMap<String, AprilTagFieldLayout>>,
41
42 custom_subsystems: HashMap<String, CustomSubsystem>,
43 }
44 Rerun {
45 server_address: Option<String>,
46 }
47 Camera {
48 id: String,
49 name: String,
50 settings: Option<CameraSettings>,
51 possible_settings: Option<Vec<CameraSettings>>,
53 subsystems: CameraSubsystems,
54 calib: Option<serde_json::Value>,
55 auto_exposure: bool,
56 manual_exposure: Option<u32>,
57 orientation: VideoOrientation,
58 cam_offsets: CameraOffsets,
59 }
60 CameraSettings {
61 width: u32,
62 height: u32,
63 frame_rate: Option<CfgFraction>,
64 format: Option<String>,
65 }
66 CfgFraction {
67 num: u32,
68 den: u32,
69 }
70 CameraOffsets {
71 translation: CameraOffsetDimensions,
72 rotation: CameraOffsetDimensions,
73 }
74 CameraOffsetDimensions {
75 x: f64,
76 y: f64,
77 z: f64,
78 }
79 CameraSubsystems {
80 capriltags: Option<CAprilTagsSubsys>,
81 ml: Option<MlSubsys>,
82 custom: Vec<String>,
83 }
84 CAprilTagsSubsys {
85 max_frame_rate: u8,
86 }
87 MlSubsys {
88 }
89 CustomSubsystem {
90 code: String,
91 }
92}
93
94impl Config {
95 pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
97 let mut f = File::open(path).map_err(|_| Error::FailedToReadConfig)?;
98 let mut buf = String::new();
99 f.read_to_string(&mut buf).unwrap();
100 toml::from_str(&buf).map_err(|_| Error::InvalidConfig)
101 }
102
103 pub async fn save(&self, path: impl AsRef<Path>) -> Result<(), Error> {
105 let mut f = File::create(path).unwrap();
106 let toml_cfgg = toml::to_string_pretty(&self).unwrap();
107 f.write_all(toml_cfgg.as_bytes()).unwrap();
108 f.flush().unwrap();
109
110 Ok(())
111 }
112}
113impl Default for Config {
114 fn default() -> Self {
115 Self {
116 team_number: u16::MAX,
117 ntables_ip: None,
118 rerun: None,
119 cameras: None,
120 device_name: None,
121
122 field_layout: None,
123 field_layouts: None,
124
125 custom_subsystems: HashMap::new(),
126 }
127 }
128}
129impl Default for Camera {
130 fn default() -> Self {
131 Self {
132 id: String::new(),
133 name: String::new(),
134 settings: None,
135 auto_exposure: true,
136 manual_exposure: None,
137 possible_settings: None,
138 subsystems: CameraSubsystems {
139 capriltags: Some(CAprilTagsSubsys { max_frame_rate: 40 }),
140 ml: None,
141 custom: Vec::new(),
142 },
143 calib: None,
144 orientation: VideoOrientation::None,
145 cam_offsets: CameraOffsets {
146 translation: CameraOffsetDimensions {
147 x: 0.0,
148 y: 0.0,
149 z: 0.0,
150 },
151 rotation: CameraOffsetDimensions {
152 x: 0.0,
153 y: 0.0,
154 z: 0.0,
155 },
156 },
157 }
158 }
159}
160
161impl Default for CameraSettings {
162 fn default() -> Self {
163 Self {
164 width: 0,
165 height: 0,
166 frame_rate: None,
167 format: None,
168 }
169 }
170}
171
172#[derive(Deserialize, Serialize, Clone)]
173#[cfg_attr(feature = "web", derive(utopia::ToSchema))]
174#[serde(rename_all = "snake_case")]
175pub enum CameraKind {
176 PiCam,
177 Usb,
178}
179
180#[derive(Deserialize, Serialize, Debug, Clone)]
181#[cfg_attr(feature = "web", derive(utopia::ToSchema))]
182#[serde(rename_all = "kebab-case")]
183pub enum VideoOrientation {
184 None = 0,
185 Clockwise = 1,
186 #[serde(rename = "rotate-180")]
187 Rotate180 = 2,
188 Counterclockwise = 3,
189}