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 #[serde(skip_deserializing)]
49 online: bool,
50 id: String,
51 name: String,
52 settings: Option<CameraSettings>,
53 possible_settings: Option<Vec<CameraSettings>>,
55 subsystems: CameraSubsystems,
56 calib: Option<serde_json::Value>,
57 auto_exposure: bool,
58 manual_exposure: Option<u32>,
59 orientation: VideoOrientation,
60 cam_offsets: CameraOffsets,
61 }
62 CameraSettings {
63 width: u32,
64 height: u32,
65 frame_rate: Option<CfgFraction>,
66 format: Option<String>,
67 }
68 CfgFraction {
69 num: u32,
70 den: u32,
71 }
72 CameraOffsets {
73 translation: CameraOffsetDimensions,
74 rotation: CameraOffsetDimensions,
75 }
76 CameraOffsetDimensions {
77 x: f64,
78 y: f64,
79 z: f64,
80 }
81 CameraSubsystems {
82 mjpeg: Option<MjpegSubsys>,
83 capriltags: Option<CAprilTagsSubsys>,
84 ml: Option<MlSubsys>,
85 custom: Vec<String>,
86 }
87 MjpegSubsys {
88 width: u32,
89 height: u32,
90 }
91 CAprilTagsSubsys {
92 max_frame_rate: u8,
93 }
94 MlSubsys {
95 }
96 CustomSubsystem {
97 code: String,
98 }
99}
100
101impl Config {
102 pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
104 let mut f = File::open(path).map_err(|_| Error::FailedToReadConfig)?;
105 let mut buf = String::new();
106 f.read_to_string(&mut buf).unwrap();
107 toml::from_str(&buf).map_err(|_| Error::InvalidConfig)
108 }
109
110 pub async fn save(&self, path: impl AsRef<Path>) -> Result<(), Error> {
112 let mut f = File::create(path).unwrap();
113 let toml_cfgg = toml::to_string_pretty(&self).unwrap();
114 f.write_all(toml_cfgg.as_bytes()).unwrap();
115 f.flush().unwrap();
116
117 Ok(())
118 }
119}
120impl Default for Config {
121 fn default() -> Self {
122 Self {
123 team_number: u16::MAX,
124 ntables_ip: None,
125 rerun: None,
126 cameras: None,
127 device_name: None,
128
129 field_layout: None,
130 field_layouts: None,
131
132 custom_subsystems: HashMap::new(),
133 }
134 }
135}
136impl Default for Camera {
137 fn default() -> Self {
138 Self {
139 online: false,
140 id: String::new(),
141 name: String::new(),
142 settings: None,
143 auto_exposure: true,
144 manual_exposure: None,
145 possible_settings: None,
146 subsystems: CameraSubsystems {
147 mjpeg: Some(MjpegSubsys {
148 width: 1280,
149 height: 720,
150 }),
151 capriltags: Some(CAprilTagsSubsys { max_frame_rate: 40 }),
152 ml: None,
153 custom: Vec::new(),
154 },
155 calib: None,
156 orientation: VideoOrientation::None,
157 cam_offsets: CameraOffsets {
158 translation: CameraOffsetDimensions {
159 x: 0.0,
160 y: 0.0,
161 z: 0.0,
162 },
163 rotation: CameraOffsetDimensions {
164 x: 0.0,
165 y: 0.0,
166 z: 0.0,
167 },
168 },
169 }
170 }
171}
172
173impl Default for CameraSettings {
174 fn default() -> Self {
175 Self {
176 width: 0,
177 height: 0,
178 frame_rate: None,
179 format: None,
180 }
181 }
182}
183
184#[derive(Deserialize, Serialize, Clone)]
185#[cfg_attr(feature = "web", derive(utopia::ToSchema))]
186#[serde(rename_all = "snake_case")]
187pub enum CameraKind {
188 PiCam,
189 Usb,
190}
191
192#[derive(Deserialize, Serialize, Debug, Clone)]
193#[cfg_attr(feature = "web", derive(utopia::ToSchema))]
194#[serde(rename_all = "kebab-case")]
195pub enum VideoOrientation {
196 None = 0,
197 Clockwise = 1,
198 #[serde(rename = "rotate-180")]
199 Rotate180 = 2,
200 Counterclockwise = 3,
201}