hyperion/models/
instance.rs

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    /// RGB color temperature in Kelvins
147    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
440/// Validate the bounds of a scan range
441fn 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}