1use std::convert::TryFrom;
2
3use serde_derive::{Deserialize, Serialize};
4use thiserror::Error;
5use validator::Validate;
6
7use crate::db::models as db_models;
8
9use super::{default_true, Color, Device, ServerConfig};
10
11#[derive(Debug, Error)]
12pub enum InstanceError {
13 #[error("error parsing date: {0}")]
14 Chrono(#[from] chrono::ParseError),
15}
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
18#[serde(rename_all = "camelCase", deny_unknown_fields)]
19pub struct Instance {
20 #[serde(skip)]
21 pub id: i32,
22 #[serde(default = "String::new")]
23 pub friendly_name: String,
24 #[serde(default = "default_true")]
25 pub enabled: bool,
26 #[serde(default = "chrono::Utc::now")]
27 pub last_use: chrono::DateTime<chrono::Utc>,
28}
29
30impl TryFrom<db_models::DbInstance> for Instance {
31 type Error = InstanceError;
32
33 fn try_from(db: db_models::DbInstance) -> Result<Self, Self::Error> {
34 Ok(Self {
35 id: db.instance,
36 friendly_name: db.friendly_name,
37 enabled: db.enabled != 0,
38 last_use: chrono::DateTime::parse_from_rfc3339(&db.last_use)?
39 .with_timezone(&chrono::Utc),
40 })
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
45#[serde(rename_all = "lowercase", deny_unknown_fields)]
46pub enum EffectType {
47 Color,
48 Effect,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
52#[serde(default, deny_unknown_fields)]
53pub struct BackgroundEffect {
54 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
55 pub color: Color,
56 pub effect: String,
57 pub enable: bool,
58 #[serde(rename = "type")]
59 pub ty: EffectType,
60}
61
62impl Default for BackgroundEffect {
63 fn default() -> Self {
64 Self {
65 enable: true,
66 ty: EffectType::Effect,
67 color: Color::from_components((255, 138, 0)),
68 effect: "Warm mood blobs".to_owned(),
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
74#[serde(rename_all = "lowercase", deny_unknown_fields)]
75pub enum BlackBorderDetectorMode {
76 Default,
77 Classic,
78 Osd,
79 Letterbox,
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
83#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
84pub struct BlackBorderDetector {
85 #[serde(default = "default_true")]
86 pub enable: bool,
87 #[validate(range(min = 0, max = 100))]
88 pub threshold: u32,
89 pub unknown_frame_cnt: u32,
90 pub border_frame_cnt: u32,
91 pub max_inconsistent_cnt: u32,
92 pub blur_remove_cnt: u16,
93 pub mode: BlackBorderDetectorMode,
94}
95
96impl Default for BlackBorderDetector {
97 fn default() -> Self {
98 Self {
99 enable: true,
100 threshold: 5,
101 unknown_frame_cnt: 600,
102 border_frame_cnt: 50,
103 max_inconsistent_cnt: 10,
104 blur_remove_cnt: 1,
105 mode: BlackBorderDetectorMode::Default,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
111#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
112pub struct BoblightServer {
113 pub enable: bool,
114 #[validate(range(min = 1024))]
115 pub port: u16,
116 #[validate(range(min = 100, max = 254))]
117 pub priority: i32,
118}
119
120impl Default for BoblightServer {
121 fn default() -> Self {
122 Self {
123 enable: false,
124 port: 19333,
125 priority: 128,
126 }
127 }
128}
129
130impl ServerConfig for BoblightServer {
131 fn port(&self) -> u16 {
132 self.port
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
137#[serde(rename_all = "snake_case", deny_unknown_fields)]
138pub enum ImageToLedMappingType {
139 MulticolorMean,
140 UnicolorMean,
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
144#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
145pub struct ColorAdjustment {
146 pub rgb_temperature: u32,
148 pub image_to_led_mapping_type: ImageToLedMappingType,
149 #[validate(nested)]
150 pub channel_adjustment: Vec<ChannelAdjustment>,
151}
152
153impl Default for ColorAdjustment {
154 fn default() -> Self {
155 Self {
156 rgb_temperature: 6600,
157 image_to_led_mapping_type: ImageToLedMappingType::MulticolorMean,
158 channel_adjustment: vec![ChannelAdjustment::default()],
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
164#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
165pub struct ChannelAdjustment {
166 pub id: String,
167 pub leds: String,
168 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
169 pub white: Color,
170 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
171 pub red: Color,
172 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
173 pub green: Color,
174 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
175 pub blue: Color,
176 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
177 pub cyan: Color,
178 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
179 pub magenta: Color,
180 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
181 pub yellow: Color,
182 #[validate(range(min = 0, max = 100))]
183 pub backlight_threshold: u32,
184 pub backlight_colored: bool,
185 #[validate(range(min = 0, max = 100))]
186 pub brightness: u32,
187 #[validate(range(min = 0, max = 100))]
188 pub brightness_compensation: u32,
189 #[validate(range(min = 0.1, max = 5.0))]
190 pub gamma_red: f32,
191 #[validate(range(min = 0.1, max = 5.0))]
192 pub gamma_green: f32,
193 #[validate(range(min = 0.1, max = 5.0))]
194 pub gamma_blue: f32,
195}
196
197impl Default for ChannelAdjustment {
198 fn default() -> Self {
199 Self {
200 id: "A userdefined name".to_owned(),
201 leds: "*".to_owned(),
202 white: Color::from_components((255, 255, 255)),
203 red: Color::from_components((255, 0, 0)),
204 green: Color::from_components((0, 255, 0)),
205 blue: Color::from_components((0, 0, 255)),
206 cyan: Color::from_components((0, 255, 255)),
207 magenta: Color::from_components((255, 0, 255)),
208 yellow: Color::from_components((255, 255, 0)),
209 backlight_threshold: 0,
210 backlight_colored: false,
211 brightness: 100,
212 brightness_compensation: 0,
213 gamma_red: 1.5,
214 gamma_green: 1.5,
215 gamma_blue: 1.5,
216 }
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
221#[serde(rename_all = "lowercase", deny_unknown_fields)]
222#[derive(Default)]
223pub enum ColorOrder {
224 #[default]
225 Rgb,
226 Bgr,
227 Rbg,
228 Brg,
229 Gbr,
230 Grb,
231}
232
233impl ColorOrder {
234 pub fn reorder_from_rgb(&self, color: Color) -> Color {
235 let (r, g, b) = color.into_components();
236
237 Color::from_components(match self {
238 ColorOrder::Rgb => (r, g, b),
239 ColorOrder::Bgr => (b, g, r),
240 ColorOrder::Rbg => (r, b, g),
241 ColorOrder::Brg => (b, r, g),
242 ColorOrder::Gbr => (g, b, r),
243 ColorOrder::Grb => (g, r, b),
244 })
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
249#[serde(rename_all = "camelCase", deny_unknown_fields)]
250pub struct Effects {
251 #[validate(length(min = 1))]
252 pub paths: Vec<String>,
253 pub disable: Vec<String>,
254}
255
256impl Default for Effects {
257 fn default() -> Self {
258 Self {
259 paths: vec!["$ROOT/custom-effects".to_owned()],
260 disable: vec![],
261 }
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
266#[serde(default, deny_unknown_fields)]
267pub struct ForegroundEffect {
268 #[serde(serialize_with = "crate::serde::serialize_color_as_array")]
269 pub color: Color,
270 pub effect: String,
271 pub enable: bool,
272 #[serde(rename = "type")]
273 pub ty: EffectType,
274 #[validate(range(min = 100))]
275 pub duration_ms: Option<i32>,
276}
277
278impl Default for ForegroundEffect {
279 fn default() -> Self {
280 Self {
281 enable: true,
282 ty: EffectType::Effect,
283 color: Color::from_components((255, 0, 0)),
284 effect: "Rainbow swirl fast".to_owned(),
285 duration_ms: Some(3000),
286 }
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
291#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
292pub struct InstanceCapture {
293 pub system_enable: bool,
294 #[validate(range(min = 100, max = 253))]
295 pub system_priority: i32,
296 pub v4l_enable: bool,
297 #[validate(range(min = 100, max = 253))]
298 pub v4l_priority: i32,
299}
300
301impl Default for InstanceCapture {
302 fn default() -> Self {
303 Self {
304 system_enable: true,
305 system_priority: 250,
306 v4l_enable: false,
307 v4l_priority: 240,
308 }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
313#[serde(default, deny_unknown_fields)]
314pub struct ClassicLedConfig {
315 pub top: u32,
316 pub bottom: u32,
317 pub left: u32,
318 pub right: u32,
319 pub glength: u32,
320 pub gpos: u32,
321 pub position: i32,
322 pub reverse: bool,
323 #[validate(range(min = 1, max = 100))]
324 pub hdepth: u32,
325 #[validate(range(min = 1, max = 100))]
326 pub vdepth: u32,
327 #[validate(range(min = 0, max = 100))]
328 pub overlap: u32,
329 #[validate(range(max = 50))]
330 pub edgegap: u32,
331 #[validate(range(max = 100))]
332 pub ptlh: u32,
333 #[validate(range(max = 100))]
334 pub ptlv: u32,
335 #[validate(range(max = 100))]
336 pub ptrh: u32,
337 #[validate(range(max = 100))]
338 pub ptrv: u32,
339 #[validate(range(max = 100))]
340 pub pblh: u32,
341 #[validate(range(max = 100))]
342 pub pblv: u32,
343 #[validate(range(max = 100))]
344 pub pbrh: u32,
345 #[validate(range(max = 100))]
346 pub pbrv: u32,
347}
348
349impl Default for ClassicLedConfig {
350 fn default() -> Self {
351 Self {
352 top: 1,
353 bottom: 0,
354 left: 0,
355 right: 0,
356 glength: 0,
357 gpos: 0,
358 position: 0,
359 reverse: false,
360 hdepth: 8,
361 vdepth: 5,
362 overlap: 0,
363 edgegap: 0,
364 ptlh: 0,
365 ptlv: 0,
366 ptrh: 0,
367 ptrv: 0,
368 pblh: 0,
369 pblv: 0,
370 pbrh: 0,
371 pbrv: 0,
372 }
373 }
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
377#[serde(rename_all = "kebab-case", deny_unknown_fields)]
378pub enum MatrixCabling {
379 Snake,
380 Parallel,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
384#[serde(rename_all = "kebab-case", deny_unknown_fields)]
385pub enum MatrixStart {
386 TopLeft,
387 TopRight,
388 BottomLeft,
389 BottomRight,
390}
391
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
393#[serde(default, deny_unknown_fields)]
394pub struct MatrixLedConfig {
395 #[validate(range(max = 50))]
396 pub ledshoriz: u32,
397 #[validate(range(max = 50))]
398 pub ledsvert: u32,
399 pub cabling: MatrixCabling,
400 pub start: MatrixStart,
401}
402
403impl Default for MatrixLedConfig {
404 fn default() -> Self {
405 Self {
406 ledshoriz: 1,
407 ledsvert: 1,
408 cabling: MatrixCabling::Snake,
409 start: MatrixStart::TopLeft,
410 }
411 }
412}
413
414#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
415#[serde(default, deny_unknown_fields)]
416pub struct LedConfig {
417 #[validate(nested)]
418 pub classic: ClassicLedConfig,
419 #[validate(nested)]
420 pub matrix: MatrixLedConfig,
421}
422
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
424#[validate(schema(function = "validate_scan_range", message = "invalid range"))]
425pub struct Led {
426 #[validate(range(min = 0., max = 1.))]
427 pub hmin: f32,
428 #[validate(range(min = 0., max = 1.))]
429 pub hmax: f32,
430 #[validate(range(min = 0., max = 1.))]
431 pub vmin: f32,
432 #[validate(range(min = 0., max = 1.))]
433 pub vmax: f32,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub color_order: Option<ColorOrder>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub name: Option<String>,
438}
439
440fn validate_scan_range(led: &Led) -> Result<(), validator::ValidationError> {
442 if led.hmin > led.hmax {
443 return Err(validator::ValidationError::new("invalid_range"));
444 }
445
446 if led.vmin > led.vmax {
447 return Err(validator::ValidationError::new("invalid_range"));
448 }
449
450 Ok(())
451}
452
453#[derive(Debug, Clone, PartialEq, Validate)]
454pub struct Leds {
455 #[validate(nested)]
456 pub leds: Vec<Led>,
457}
458
459impl Default for Leds {
460 fn default() -> Self {
461 Self {
462 leds: vec![Led {
463 hmin: 0.,
464 hmax: 1.,
465 vmin: 0.,
466 vmax: 1.,
467 color_order: None,
468 name: None,
469 }],
470 }
471 }
472}
473
474impl serde::Serialize for Leds {
475 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
476 where
477 S: serde::Serializer,
478 {
479 use serde::ser::SerializeSeq;
480 let mut seq = serializer.serialize_seq(Some(self.leds.len()))?;
481 for led in &self.leds {
482 seq.serialize_element(led)?;
483 }
484 seq.end()
485 }
486}
487
488impl<'de> serde::Deserialize<'de> for Leds {
489 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
490 where
491 D: serde::Deserializer<'de>,
492 {
493 Ok(Leds {
494 leds: <Vec<Led> as serde::Deserialize>::deserialize(deserializer)?,
495 })
496 }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
500#[serde(rename_all = "lowercase", deny_unknown_fields)]
501pub enum SmoothingType {
502 Linear,
503 Decay,
504}
505
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
507#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
508pub struct Smoothing {
509 pub enable: bool,
510 #[serde(rename = "type")]
511 pub ty: SmoothingType,
512 #[serde(rename = "time_ms")]
513 #[validate(range(min = 25, max = 5000))]
514 pub time_ms: u32,
515 #[validate(range(min = 1., max = 2000.))]
516 pub update_frequency: f32,
517 #[validate(range(min = 1., max = 1000.))]
518 pub interpolation_rate: f32,
519 #[validate(range(min = 1., max = 1000.))]
520 pub output_rate: f32,
521 #[validate(range(min = 1., max = 20.))]
522 pub decay: f32,
523 pub dithering: bool,
524 #[validate(range(max = 2048))]
525 pub update_delay: u32,
526 pub continuous_output: bool,
527}
528
529impl Default for Smoothing {
530 fn default() -> Self {
531 Self {
532 enable: true,
533 ty: SmoothingType::Linear,
534 time_ms: 200,
535 update_frequency: 25.0,
536 interpolation_rate: 1.0,
537 output_rate: 1.0,
538 decay: 1.0,
539 dithering: true,
540 update_delay: 0,
541 continuous_output: true,
542 }
543 }
544}
545
546#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
547#[serde(rename_all = "camelCase", deny_unknown_fields)]
548pub struct InstanceConfig {
549 #[validate(nested)]
550 pub instance: Instance,
551 #[validate(nested)]
552 #[serde(default = "Default::default")]
553 pub background_effect: BackgroundEffect,
554 #[validate(nested)]
555 #[serde(default = "Default::default")]
556 pub black_border_detector: BlackBorderDetector,
557 #[validate(nested)]
558 #[serde(default = "Default::default")]
559 pub boblight_server: BoblightServer,
560 #[validate(nested)]
561 #[serde(default = "Default::default")]
562 pub color: ColorAdjustment,
563 #[validate(nested)]
564 #[serde(default = "Default::default")]
565 pub device: Device,
566 #[validate(nested)]
567 #[serde(default = "Default::default")]
568 pub effects: Effects,
569 #[validate(nested)]
570 #[serde(default = "Default::default")]
571 pub foreground_effect: ForegroundEffect,
572 #[validate(nested)]
573 #[serde(default = "Default::default")]
574 pub instance_capture: InstanceCapture,
575 #[validate(nested)]
576 #[serde(default = "Default::default")]
577 pub led_config: LedConfig,
578 #[validate(nested)]
579 #[serde(default = "Default::default")]
580 pub leds: Leds,
581 #[validate(nested)]
582 #[serde(default = "Default::default")]
583 pub smoothing: Smoothing,
584}
585
586impl InstanceConfig {
587 pub fn new_dummy(id: i32) -> Self {
588 Self {
589 instance: Instance {
590 id,
591 friendly_name: "Dummy device".to_owned(),
592 enabled: true,
593 last_use: chrono::Utc::now(),
594 },
595 background_effect: Default::default(),
596 black_border_detector: Default::default(),
597 boblight_server: Default::default(),
598 color: Default::default(),
599 device: Default::default(),
600 effects: Default::default(),
601 foreground_effect: Default::default(),
602 instance_capture: Default::default(),
603 led_config: Default::default(),
604 leds: Default::default(),
605 smoothing: Default::default(),
606 }
607 }
608}