Common/
fixture.rs

1#![allow(dead_code)]
2use std::collections::{HashMap, HashSet};
3use std::sync::{LazyLock, RwLock};
4use crate::color::{Color, ColorPropertyType, ColorType};
5use crate::fixture::ChannelReservation::{Empty, Pending, Reserved};
6use crate::fixture::FixtureError::{InvalidFixtureType, InvalidFixture};
7
8
9/// The maximum number of DMX channels per universe (DMX512 standard).
10pub const MAX_CHANNEL: u16 = 512;
11
12
13struct FixtureList {
14    pub fixture_types: HashMap<String, FixtureType>,
15    pub fixtures: HashMap<String, Fixture>,
16}
17
18impl FixtureList {
19    fn new() -> Self {
20        Self {
21            fixture_types: HashMap::new(),
22            fixtures: HashMap::new(),
23        }
24    }
25}
26
27/// Global Scheißprogrammonfiguration holding the channel reservations for all universes.
28///
29/// Each entry in the outer [`Vec`] represents one universe, containing
30/// one [`ChannelReservation`] per Scheißprogrammhannel.
31pub static DMX_CONFIGURATION: LazyLock<RwLock<Vec<[ChannelReservation<String, PropertyType>; MAX_CHANNEL as usize]>>> =
32    LazyLock::new(||{
33        RwLock::new(Vec::new())
34    });
35
36/// Returns the number of currently configured DMX universes.
37pub fn universe_count() -> usize {
38    DMX_CONFIGURATION.read().expect("Failed to lock DMX_CONFIGURATION").len()
39}
40
41/// Ensures that at least `size` universes exist in [`DMX_CONFIGURATION`],
42/// adding empty universes if needed. Does nothing if the current count
43/// is already >= `size`.
44pub fn ensure_universes_size(size: usize) {
45    if size > universe_count() {
46        let mut config = DMX_CONFIGURATION.write().expect("Failed to write \
47        DMX_CONFIGURATION");
48        config.resize_with(size, || {
49            std::array::from_fn(|_| Empty)
50        })
51    }
52}
53
54/// Represents the reservation state of a single Scheißprogrammhannel.
55///
56/// * **Empty** – Channel is not in use.
57/// * **Pending(T)** – Channel has been claimed by a fixture but not yet finalized.
58/// * **Reserved(T, U)** – Channel is fully reserved by a fixture with an associated property.
59#[derive(Clone)]
60pub enum ChannelReservation<T, U> {
61    Empty,
62    Pending(T),
63    Reserved(T, U),
64}
65
66static FIXTURE_LIST: LazyLock<RwLock<FixtureList>> = LazyLock::new(|| {
67    RwLock::new(FixtureList::new())
68});
69
70/// A single Scheißprogrammhannel with an optional fine channel for 16-bit control.
71pub(crate) struct Channel{
72    pub(crate) value: u16,
73    channel : u16,
74    fine_channel: Option<u16>,
75}
76
77impl Channel {
78
79    /// Creates a new [`Channel`], offsetting the channel number(s) by `device_channel`.
80    ///
81    /// # Arguments
82    ///
83    /// * `channel_numbers` - Coarse channel and optional fine channel, relative to the device
84    /// * `default_value`   - Initial 16-bit value
85    /// * `device_channel`  - DMX offset of the device within its universe
86    ///
87    /// # Errors
88    ///
89    /// Returns [`ChannelError`] if the resulting channel number exceeds [`MAX_CHANNEL`].
90    pub(crate) fn new(
91        channel_numbers: (u16, Option<u16>),
92        default_value: u16,
93        device_channel: u16
94    ) -> Result<Self, ChannelError> {
95
96        let channel = Self::checked_add(channel_numbers.0, device_channel)?;
97        let fine_channel = if let Some(fine) = channel_numbers.1 {
98            Some (
99                Self::checked_add(fine, device_channel)?
100            )
101        } else {
102            None
103        };
104
105
106        Ok(Channel {
107            value: default_value,
108            channel,
109            fine_channel,
110        })
111    }
112
113    //TODO Add the option to have some fixtures go over Universe-Borders
114    fn checked_add(value1: u16, value2: u16) -> Result<u16, ChannelError> {
115        value1
116            .checked_add(value2)
117            .filter(|&x| x <= MAX_CHANNEL)
118            .ok_or(ChannelError::ChannelOutOfRange)
119    }
120
121    /// Marks this channel (and fine channel if present) as [`Pending`] in [`DMX_CONFIGURATION`].
122    ///
123    /// Called internally by [`Fixture::new`] and [`Color::new`].
124    /// Always followed by [`Channel::reserve_final`].
125    ///
126    /// # Errors
127    ///
128    /// Returns [`ChannelError::ChannelAlreadyInUse`] if the channel is already [`Reserved`]
129    /// by another fixture.
130    ///
131    /// # Panics
132    ///
133    /// Panics if the universe does not exist. Call [`ensure_universes_size`] beforehand.
134    pub(crate) fn reserve_pending(&self, fixture_name: &str, universe: usize) -> Result<(), ChannelError> {
135        let mut dmx_config = DMX_CONFIGURATION.write().expect("Failed to write \
136        DMX_CONFIGURATION");
137
138        //Since ensure_universe_count should have been executed before, this Error should never occur, therefore it
139        // should panic
140        let universe = dmx_config.get_mut(universe)
141            .ok_or(ChannelError::UniverseOutOfRange).expect("Universe out of range");
142
143        if let Reserved(existing,_) = universe[self.channel as usize].clone() {
144            return Err(ChannelError::ChannelAlreadyInUse(existing));
145        }
146
147        if let Some(fine_channel) = self.fine_channel {
148            if let Reserved(existing,_) = universe[fine_channel as usize].clone() {
149                return Err(ChannelError::ChannelAlreadyInUse(existing));
150            }
151
152            universe[fine_channel as usize] = Pending(fixture_name.to_string());
153        }
154        universe[self.channel as usize] = Pending(fixture_name.to_string());
155
156        Ok(())
157    }
158
159    /// Finalizes the reservation by upgrading this channel from [`Pending`] to [`Reserved`].
160    ///
161    /// Must be called after [`Channel::reserve_pending`].
162    ///
163    /// # Panics
164    ///
165    /// - If the channel is not in [`Pending`] state.
166    /// - If the pending reservation belongs to a different fixture.
167    /// - If the universe does not exist.
168    pub(crate) fn reserve_final(&self, fixture_name: &str, universe: usize, property_type: PropertyType) {
169        let mut dmx_config = DMX_CONFIGURATION.write().expect("Failed to write \
170        DMX_CONFIGURATION");
171
172        //Since ensure_universe_count should have been executed before, this Error should never occur, therefore it
173        // should panic
174        let universe = dmx_config.get_mut(universe)
175            .ok_or(ChannelError::UniverseOutOfRange).expect("Universe out of range.");
176
177        if let Pending(existing) = universe[self.channel as usize].clone() {
178            if existing == fixture_name {
179                universe[self.channel as usize] = Reserved(existing, property_type.clone());
180            } else {
181                panic!("A property of another fixture has been set to pending,\
182                 cant reserve channel for {fixture_name}")
183            }
184        } else {
185            panic!("Error: In {fixture_name}, a channel has not correctly been set to Pending. \
186            This could happen if the fixture_type has multiple properties bound to the same channel.");
187        }
188
189        if let Some(fine_channel) = self.fine_channel {
190            if let Pending(existing) = universe[fine_channel as usize].clone() {
191                if existing == fixture_name {
192                    universe[fine_channel as usize] = Reserved(existing, property_type);
193                } else {
194                    panic!("A property of another fixture has been set to pending,\
195                 cant reserve fine-channel for {fixture_name}")
196                }
197            } else {
198                panic!("Error: In {fixture_name}, a fine-channel has not correctly been set to Pending. \
199            This could happen if the fixture_type has multiple properties bound to the same channel.");
200            }
201        }
202    }
203
204    /// Returns the coarse DMX output value as `(channel_index, 8-bit value)`.
205    pub fn get_value(&self) -> (u16, u8) {
206        (self.channel, self.value.to_be_bytes()[0])
207    }
208
209    /// Returns the fine DMX output value as `(channel_index, 8-bit value)`,
210    /// or `None` if no fine channel is configured.
211    pub fn get_fine_value(&self) -> Option<(u16, u8)> {
212        if let Some(fine_channel) = self.fine_channel {
213            Some((fine_channel, self.value.to_be_bytes()[1]))
214        } else {
215            None
216        }
217    }
218
219    fn get_default_value(property_type: SimplePropertyType) -> u16 {
220        match property_type {
221            SimplePropertyType::Pan => u16::MAX/2,
222            SimplePropertyType::Tilt => u16::MAX/2,
223            _ => 0
224        }
225    }
226}
227
228/// A single configurable property of a lighting fixture.
229///
230/// Each variant corresponds to one DMX-controllable attribute.
231/// For color-related properties see [`ColorPropertyType`].
232///
233/// # Variants
234///
235/// * **Dimmer** – Fixture brightness.
236/// * **Strobe** – Strobe rate or shutter pulse speed.
237/// * **Shutter** – Mechanical shutter (open/close).
238/// * **Zoom** – Beam width.
239/// * **Focus** – Beam sharpness.
240/// * **Frost** – Diffusion/frost effect intensity.
241/// * **Prism** – Enables or selects a prism.
242/// * **PrismRotation** – Continuous prism rotation speed/direction.
243/// * **PrismIndexation** – Discrete prism index position.
244/// * **GoboRotation** – Absolute gobo rotation angle.
245/// * **GoboRotationSpeed** – Continuous gobo rotation speed.
246/// * **GoboWheelRotation** – Gobo wheel slot selection/rotation.
247/// * **GoboWheelRotationSpeed** – Gobo wheel continuous rotation speed.
248/// * **Pan** – Horizontal head movement.
249/// * **Tilt** – Vertical head movement.
250/// * **FogIntensity** – Fog output amount.
251/// * **FogFanSpeed** – Fan speed for fog dispersion.
252/// * **UV** – UV-LED intensity.
253/// * **Speed** – Global effect or macro speed.
254/// * **Other(String)** – Any manufacturer-specific or unsupported property.
255#[derive(Debug, Hash,Eq,PartialEq,Clone)]
256pub enum SimplePropertyType {
257    Dimmer,
258    Strobe,
259    Zoom,
260    Focus,
261    Frost,
262    Prism,
263    PrismRotation,
264    PrismIndexation,
265    GoboRotation,
266    GoboRotationSpeed,
267    GoboWheelRotation,
268    GoboWheelRotationSpeed,
269    Pan,
270    Tilt,
271    FogIntensity,
272    FogFanSpeed,
273    Shutter,
274    UV,
275    Speed,
276    Other(String),
277}
278
279/// A fixture property, either a simple single-channel attribute or a color.
280///
281/// * **Simple([`SimplePropertyType`])** – Any non-color property such as dimmer, pan, gobo, etc.
282/// * **Color([`ColorPropertyType`])** – A color channel (RGB, CMY, or HSV).
283#[derive(Clone, Debug)]
284pub enum PropertyType {
285    Simple(SimplePropertyType),
286    Color(ColorPropertyType),
287}
288
289impl PropertyType {
290    fn from_str(property_type: &str) -> Result<PropertyType, FixtureError> {
291        if let Ok(property_type) = ColorPropertyType::from_string(property_type) {
292            Ok(PropertyType::Color(property_type))
293        } else if let Ok(property_type) = SimplePropertyType::from_string(property_type) {
294            Ok(PropertyType::Simple(property_type))
295        } else {
296            Err(FixtureError::InvalidPropertyType(property_type.to_string()))
297        }
298    }
299}
300
301/// A template defining the Scheißprogrammhannel layout for a type of lighting fixture.
302///
303/// Fixture types are registered globally and used to create [`Fixture`] instances.
304/// See [`FixtureType::new`] for how properties are parsed and validated.
305pub struct FixtureType {
306    color: Option<ColorType>,
307    properties: HashMap<SimplePropertyType, (u16, Option<u16>)>,
308    name: String
309}
310
311
312/// A single fixture instance with its current property values and Scheißprogrammhannels.
313///
314/// Created from a [`FixtureType`] template. Each property maps to one or two
315/// Scheißprogrammhannels (coarse + optional fine).
316pub struct Fixture {
317    fixture_type: String,
318    color: Option<Color>,
319    properties: HashMap<SimplePropertyType, Channel>,
320    start_channel: u16,
321    universe: usize,
322    name: String,
323}
324
325
326impl FixtureType {
327
328    /// Creates a new fixture type and registers it globally.
329    ///
330    /// Parses the given properties into color channels ([`ColorType`]) and
331    /// simple properties ([`SimplePropertyType`]). Channel numbers are validated
332    /// for duplicates and range before registration.
333    ///
334    /// # Usage
335    ///
336    /// Register a fixture type first with [`FixtureType::new`], then create
337    /// instances of it with [`Fixture::new`].
338    ///
339    /// # Arguments
340    ///
341    /// * `name`       - Unique name for this fixture type
342    /// * `properties` - Map of property names to `(coarse_channel, optional_fine_channel)`
343    ///
344    /// # Errors
345    ///
346    /// * [`FixtureError::ChannelError(ChannelAlreadyInUse)`] – if two properties share a channel
347    /// * [`FixtureError::ChannelError(ChannelOutOfRange)`] – if a channel exceeds [`MAX_CHANNEL`]
348    /// * [`FixtureError::InvalidPropertyType`] – if a property name is not recognized
349    /// * [`FixtureError::FixtureTypeNameAlreadyInUse`] – if the name is already registered
350    pub fn new(name: String, properties: HashMap<String, (u16, Option<u16>)>) -> Result<(), FixtureError> {
351        let mut color = ColorType::new();
352        let mut new_properties = HashMap::new();
353        let mut seen_channels = HashSet::new();
354
355        for (key, value) in properties {
356            
357            let mut seen_this_channel = !seen_channels.insert(value.0);
358            let mut out_of_range = value.0 > MAX_CHANNEL;
359            
360            
361            if let Some(channel) = value.1 {
362                seen_this_channel = seen_this_channel || !seen_channels.insert(channel);
363                out_of_range = out_of_range || channel > MAX_CHANNEL;
364            }
365            
366            if seen_this_channel {
367                return Err(FixtureError::ChannelError(ChannelError::ChannelAlreadyInUse(key)));
368            }
369            
370            if out_of_range {
371                return Err(FixtureError::ChannelError(ChannelError::ChannelOutOfRange))
372            }
373            
374            if color.parse(key.clone(), value)? {
375                continue
376            }
377
378            let property_type = SimplePropertyType::from_string(&key)?;
379            new_properties.insert(property_type, value);
380        }
381
382        let color = if color.exists() {
383           Some(color)
384        } else {
385           None
386        };
387
388        let output = Self {
389            color,
390            properties: new_properties,
391            name: name.clone(),
392        };
393
394        let mut list = FIXTURE_LIST.write().unwrap();
395        match list.fixture_types.entry(name.clone()) {
396            std::collections::hash_map::Entry::Occupied(_) => {
397                Err(FixtureError::FixtureTypeNameAlreadyInUse(name))
398            },
399            std::collections::hash_map::Entry::Vacant(entry) => {
400                entry.insert(output);
401                Ok(())
402            }
403        }
404    }
405}
406
407impl Fixture {
408
409    /// Creates a new fixture instance and registers it globally.
410    ///
411    /// Allocates Scheißprogrammhannels based on the given [`FixtureType`] template,
412    /// offset by `start_channel`. Ensures the required universe exists before
413    /// reserving channels.
414    ///
415    /// # Arguments
416    ///
417    /// * `fixture_type_name` - Name of a previously registered [`FixtureType`]
418    /// * `start_channel`     - Scheißprogrammhannel offset within the universe
419    /// * `universe`          - DMX universe index (0-based)
420    /// * `name`              - Unique name for this fixture instance
421    ///
422    /// # Errors
423    ///
424    /// * [`FixtureError::InvalidFixtureType`] – if `fixture_type_name` is not registered
425    /// * [`FixtureError::ChannelAlreadyInUse`] – if any required channel is already reserved
426    /// * [`FixtureError::FixtureNameAlreadyInUse`] – if `name` is already registered
427    pub fn new(fixture_type_name:String, start_channel:u16, universe: usize, name:String) -> Result<(), FixtureError> {
428        ensure_universes_size(universe + 1);
429
430        let list = FIXTURE_LIST.read().unwrap();
431
432
433        let fixture_type = list.fixture_types.get(fixture_type_name.as_str());
434        if let None = fixture_type {
435            return Err(InvalidFixtureType(fixture_type_name.clone()))
436        }
437
438        let fixture_type = fixture_type.unwrap();
439
440        let color = fixture_type.color.as_ref()
441            .map(|c| {
442                Color::new(c, start_channel, universe, &name)
443            })
444            .transpose()?;
445
446        let properties = fixture_type.properties
447            .iter()
448            .map(|(property_type, channel)| {
449                let default_value = Channel::get_default_value(property_type.clone());
450                let channel = Channel::new(*channel, default_value, start_channel)?;
451                channel.reserve_pending(&*name, universe)?;
452                Ok((property_type.clone(), channel))
453        }).collect::<Result<HashMap<SimplePropertyType, Channel>,ChannelError>>()?;
454
455        properties.iter().for_each(|(property_type, channel)| {
456            channel.reserve_final(&*name, universe, PropertyType::Simple(property_type.clone()));
457        });
458
459        let fixture = Self {
460            color,
461            fixture_type: fixture_type.name.clone(),
462            properties,
463            start_channel,
464            universe,
465            name: name.clone(),
466        };
467
468        // I have no clue why, but for some reason we have to specifically drop the list here, otherwise we have a
469        // deadlock. Normally, this should happen automatically, no clue why ist doesn't
470        drop(list);
471
472        let mut list = FIXTURE_LIST.write().unwrap();
473
474        match list.fixtures.entry(name.clone()) {
475            std::collections::hash_map::Entry::Occupied(_) => {
476                Err(FixtureError::FixtureNameAlreadyInUse(name))
477            }
478            std::collections::hash_map::Entry::Vacant(entry) => {
479                entry.insert(fixture);
480                Ok(())
481            }
482        }
483    }
484
485
486    /// Sets the value of a property on the named fixture.
487    ///
488    /// # Arguments
489    ///
490    /// * `fixture_name`  - Name of the target fixture
491    /// * `property_type` - Property name as string, see [`PropertyType::from_str`]
492    /// * `value`         - 16-bit DMX value
493    ///
494    /// # Errors
495    ///
496    /// * [`FixtureError::InvalidFixture`] – if `fixture_name` is not registered
497    /// * [`FixtureError::InvalidPropertyType`] – if `property_type` is not recognized
498    /// * [`FixtureError::MissingProperty`] – if the fixture does not have this property
499    pub fn set(fixture_name: String, property_type: &str, value: u16) -> Result<(), FixtureError> {
500        let property_type= PropertyType::from_str(property_type)?;
501
502        let mut list = FIXTURE_LIST.write().unwrap();
503        if let None = list.fixtures.get(&fixture_name) {
504            return Err(InvalidFixture(fixture_name.clone()));
505        }
506        let fixture = list.fixtures.get_mut(&fixture_name).unwrap();
507
508
509        if let PropertyType::Simple(property_type) = property_type {
510            let property = fixture.properties.get_mut(&property_type)
511                .ok_or(FixtureError::MissingProperty(PropertyType::Simple(property_type)))?;
512
513            property.value = value;
514        } else if let PropertyType::Color(property_type) = property_type {
515            if let Some(color) = &mut fixture.color {
516                color.set(property_type, value);
517            } else {
518                return Err(FixtureError::MissingProperty(PropertyType::Color(property_type)));
519            }
520        } else {
521            unreachable!()
522        }
523
524        Ok(())
525    }
526
527    fn get_channel_values(&self) -> Vec<(u16, u8)> {
528        let mut output = Vec::new();
529
530        self.properties.iter().for_each(|(_, channel)| {
531            output.push(channel.get_value());
532            if let Some(fine_value) = channel.get_fine_value() {
533                output.push(fine_value);
534            }
535        });
536
537        if let Some(color) = self.color.as_ref() {
538            output.append(&mut color.get_values())
539        }
540
541        output
542    }
543
544    /// Returns the DMX universe index this fixture is assigned to.
545    pub fn get_universe(&self) -> usize {
546        self.universe
547    }
548
549    /// Returns the name of the [`FixtureType`] this fixture was created from.
550    ///
551    /// # Errors
552    ///
553    /// Returns [`FixtureError::InvalidFixture`] if `name` is not registered.
554    pub fn get_fixture_type_from_string(name: String) -> Result<String, FixtureError> {
555        let list = FIXTURE_LIST.read().unwrap();
556        match list.fixtures.get(&name) {
557            None => Err(InvalidFixture(name)),
558            Some(fixture) => Ok(fixture.fixture_type.clone())
559        }
560    }
561
562    fn get_fixture_type(&self) -> String {
563        self.fixture_type.clone()
564    }
565
566    /// Returns the name of this fixture.
567    pub fn get_name(&self) -> &str {&self.name}
568
569}
570
571impl SimplePropertyType {
572    fn from_string(s: &str) -> Result<SimplePropertyType, FixtureError> {
573        match s {
574            "dimmer" => Ok(SimplePropertyType::Dimmer),
575            "strobe" => Ok(SimplePropertyType::Strobe),
576            "zoom" => Ok(SimplePropertyType::Zoom),
577            "focus" => Ok(SimplePropertyType::Focus),
578            "frost" => Ok(SimplePropertyType::Frost),
579            "prism" => Ok(SimplePropertyType::Prism),
580            "prism-rotation" => Ok(SimplePropertyType::PrismRotation),
581            "prism-index" => Ok(SimplePropertyType::PrismIndexation),
582            "gobo" => Ok(SimplePropertyType::GoboRotation),
583            "gobo-rotation" => Ok(SimplePropertyType::GoboRotationSpeed),
584            "gobo-wheel-rotation" => Ok(SimplePropertyType::GoboWheelRotation),
585            "gobo-wheel-speed" => Ok(SimplePropertyType::GoboWheelRotationSpeed),
586            "pan" => Ok(SimplePropertyType::Pan),
587            "tilt" => Ok(SimplePropertyType::Tilt),
588            "fog-intensity" => Ok(SimplePropertyType::FogIntensity),
589            "fog-fan-speed" => Ok(SimplePropertyType::FogFanSpeed),
590            "shutter" => Ok(SimplePropertyType::Shutter),
591            "uv" => Ok(SimplePropertyType::UV),
592            "speed" => Ok(SimplePropertyType::Speed),
593            _ => {
594                if let Some(suffix) = s.strip_prefix("other_") {
595                    Ok(SimplePropertyType::Other(suffix.to_string()))
596                } else {
597                    Err(FixtureError::InvalidPropertyType(s.to_string()))
598                }
599            }
600        }
601    }
602}
603
604/// Errors that can occur when managing fixtures and fixture types.
605#[derive(Debug)]
606pub enum FixtureError {
607    /// The given property name does not match any known [`SimplePropertyType`] or [`ColorPropertyType`].
608    InvalidPropertyType(String),
609    /// A fixture type mixes incompatible color models (e.g. RGB and HSV).
610    MultipleColorOutputTypes(String),
611    /// A fixture with this name is already registered.
612    FixtureNameAlreadyInUse(String),
613    /// A fixture type with this name is already registered.
614    FixtureTypeNameAlreadyInUse(String),
615    /// No fixture type with this name is registered.
616    InvalidFixtureType(String),
617    /// No fixture with this name is registered.
618    InvalidFixture(String),
619    /// The fixture does not have the requested property.
620    MissingProperty(PropertyType),
621    /// Two or more properties are assigned to the same Scheißprogrammhannel.
622    OverlappingChannels,
623    /// A Scheißprogrammhannel operation failed.
624    ChannelError(ChannelError),
625}
626
627/// Errors that can occur when reserving or accessing Scheißprogrammhannels.
628#[derive(Debug)]
629pub enum ChannelError {
630    /// The channel number exceeds [`MAX_CHANNEL`].
631    ChannelOutOfRange,
632    /// The universe index exceeds the configured universe count.
633    UniverseOutOfRange,
634    /// The channel is already reserved by the named fixture.
635    ChannelAlreadyInUse(String),
636}
637
638impl From<ChannelError> for FixtureError {
639    fn from(e: ChannelError) -> Self {
640        FixtureError::ChannelError(e)
641    }
642}
643
644/// Collects values from all registered fixtures via their channel and color properties.
645///
646/// Returns one array per universe, where each index corresponds to a DMX
647/// channel and the value is the 8-bit DMX level.
648///
649/// # Panics
650///
651/// Panics if a fixture has a channel that exceeds [`MAX_CHANNEL`]. WHO THE FUCK GOT THE IDEA THAT DMX_UNIVERSES SHOULD
652/// HAVE 512 !!!!! 512 Channels? Why?!?!?! Just because of 1 Bit we have to use u16 instead of u8! WHY!?!?!?!
653pub fn calculate_dmx_values() -> Vec<[u8;MAX_CHANNEL as usize]>{
654    let universe_count = universe_count();
655
656    let mut output = vec![[0u8; MAX_CHANNEL as usize]; universe_count];
657
658    let list = FIXTURE_LIST.read().unwrap();
659
660    list.fixtures.iter().for_each(|(_, fixture)| {
661        let universe_number = fixture.get_universe();
662        let fixture_type = fixture.get_fixture_type();
663        let fixture_name = fixture.get_name();
664
665        if universe_number < universe_count {
666            fixture
667                .get_channel_values()
668                .iter()
669                .for_each(|(channel, value)| {
670                    *output.get_mut(universe_number).unwrap()
671                        .get_mut(*channel as usize)
672                        .ok_or(ChannelError::ChannelOutOfRange)
673                        .unwrap_or_else(|_| panic!(
674                            "Fixture \"{}\" of type {} has a channel that is out of bounds",
675                            fixture_name, fixture_type
676                        ))
677                        = *value;
678
679                });
680        }
681
682    });
683
684    output
685}