e173 0.1.0-alpha.7

A library implementing various aspects of the ESTA BSR E1.73 standard
Documentation
use std::collections::HashMap;

use crate::e173_types;

use super::{
    command::{Command, CommandClass},
    localization::DefinitionLocalization,
    parameter::{Parameter, ParameterClass},
    resource::{Resource, ResourceClass},
    serializer::{Serializer, SerializerClass},
    structure::{Structure, StructureClass},
};

e173_types! {

#[serde(rename_all = "camelCase")]
pub struct DeviceClass {
    pub libraries: HashMap<String, String>,
    #[serde(rename = "@description")]
    pub description: String,
    pub publish_date: String,
    pub author: String,
    pub info: DeviceClassInfo,
    pub history: HashMap<String, String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub device_library: Option<DeviceLibrary>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub parameters: HashMap<String, Parameter>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub structures: HashMap<String, Structure>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub serializers: HashMap<String, Serializer>,
     #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub resources: HashMap<String, Resource>,
     #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub commands: HashMap<String, Command>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub localizations: HashMap<String, DefinitionLocalization>,
}

pub struct DeviceClassInfo {
    pub manufacturer: Manufacturer,
    pub model: Model,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub compatibility: Option<Compatibility>,
}

#[serde(rename_all = "camelCase")]
pub struct Manufacturer {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(
        default,
        with = "esta_id",
        skip_serializing_if = "Option::is_none"
    )]
    #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))]
    pub esta_id: Option<u16>,
}

pub struct Model {
    pub name: String,
    pub category: Category,
    pub subcategory: Subcategory,
}

#[serde(rename_all = "kebab-case")]
#[cfg_attr(target_arch = "wasm32", tsify(string_enum))]
pub enum Category {
    Lighting,
    Video,
    Audio,
    MachineryAutomation,
    Atmosphere,
    Effect,
    Infrastructure,
    Other
}

#[serde(rename_all = "kebab-case")]
#[cfg_attr(target_arch = "wasm32", tsify(string_enum))]
pub enum Subcategory {
    FixedProfile,
    FixedFresnel,
    FixedPebbleConvex,
    FixedWash,
    FixedPar,
    FixedStrip,
    FixedOther,
    MovingProfile,
    MovingFresnel,
    MovingPebbleConvex,
    MovingWash,
    MovingStrip,
    MovingMirror,
    MovingOther,
    AccessoryScroller,
    AccessoryGoboRotator,
    AccessoryAnimation,
    AccessoryIris,
    AccessoryOther,
    ArchitecturalSconce,
    ArchitecturalDownlight,
    ArchitecturalFlood,
    ArchitecturalSpot,
    ArchitecturalTracklight,
    ArchitecturalWallWash,
    ArchitecturalOther,
    PracticalFloorLamp,
    PracticalDeskLamp,
    PracticalTableLamp,
    PracticalPendant,
    PracticalChandelier,
    PracticalOther,
    Controller,
    MediaServer,
    Projector,
    Panel,
    Camera,
    Amplifier,
    SpeakerLineArray,
    SpeakerPointSource,
    SpeakerColumn,
    SpeakerHornLoaded,
    SpeakerMonitor,
    SpeakerStageWedge,
    SpeakerIem,
    SpeakerSubwoofer,
    SpeakerCeiling,
    SpeakerSurface,
    SpeakerOther,
    Processor,
    Mixer,
    Winch,
    Hoist,
    Drive,
    Revolve,
    LoadCell,
    Estop,
    Smoke,
    Haze,
    Pyro,
    Fire,
    Strobe,
    Laser,
    Water,
    Snow,
    Bubble,
    Fan,
    NetworkSwitch,
    Router,
    Security,
    Gateway,
    Wireless,
    Splitter,
    Management,
    Other,
}

#[serde(rename_all = "camelCase")]
pub struct Compatibility {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub firmware_versions: Option<Vec<String>>,
}

#[serde(rename_all = "camelCase")]
pub struct DeviceLibrary {
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub parameter_classes: HashMap<String, ParameterClass>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub structure_classes: HashMap<String, StructureClass>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub serializer_classes: HashMap<String, SerializerClass>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub resource_classes: HashMap<String, ResourceClass>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub command_classes: HashMap<String, CommandClass>,
}

#[serde(rename_all = "camelCase")]
pub struct Device {
    pub class: String,
    pub version: String,
    pub user_identifier: String,
    #[serde(rename = "@friendlyName", skip_serializing_if = "Option::is_none")]
    pub friendly_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unit_number: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub translate: Option<(f64, f64, f64)>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rotate: Option<(f64, f64, f64)>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transport: Option<DeviceTransport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub static_values: Option<DeviceStaticValues>,
}

#[serde(rename_all = "camelCase")]
pub struct DeviceTransport {
    pub methods: HashMap<String, DeviceTransportMethod>,
}

#[serde(rename_all = "camelCase")]
pub struct DeviceTransportMethod {
    pub serializer: String,
    pub address: String
}

#[serde(rename_all = "camelCase")]
pub struct DeviceOverlay {
    pub base: Option<String>,
    pub static_values: Option<DeviceStaticValues>
}

#[serde(rename_all = "camelCase")]
pub struct DeviceStaticValues {
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    #[cfg_attr(target_arch = "wasm32", tsify(type = "Record<string, string | number | boolean>"))]
    pub parameters: HashMap<String, serde_json::Value>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    #[cfg_attr(target_arch = "wasm32", tsify(type = "Record<string, string | number | boolean>"))]
    pub structures: HashMap<String, serde_json::Value>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    #[cfg_attr(target_arch = "wasm32", tsify(type = "Record<string, string | number | boolean>"))]
    pub serializers: HashMap<String, serde_json::Value>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub resources: HashMap<String, String>,
}

}

mod esta_id {
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let val: Option<String> = Option::<String>::deserialize(deserializer)?;
        if let Some(val) = val {
            let result = u16::from_str_radix(&val, 16).map_err(serde::de::Error::custom)?;
            return if result < 0x8000 {
                Ok(Some(result))
            } else {
                Err(serde::de::Error::custom(format!(
                    "Invalid ESTA manufacturer ID {val}"
                )))
            };
        }

        Ok(None)
    }

    pub fn serialize<S>(value: &Option<u16>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match value {
            Some(value) => {
                let s = format!("{value:x}");
                serializer.serialize_str(&s)
            }
            None => serializer.serialize_none(),
        }
    }
}