use crate::service::{
AlertEffectType, CIEColor, ColorFeatureBasic, EffectType, GradientMode, GroupDimmingState,
OnState, ParseColorError, PowerupOnState, PowerupPresetType, ProductArchetype,
ResourceIdentifier, SceneAction, ScenePalette, SceneStatus, Schedule, SignalType,
TimedEffectType, ZigbeeChannel, ZoneArchetype,
};
use json_patch::merge;
use serde::{ser::SerializeMap, Serialize};
use serde_json::json;
pub fn merge_commands<S: Serialize>(commands: &[S]) -> serde_json::Value {
let mut map = json!({});
for cmd in commands {
merge(&mut map, &serde_json::to_value(cmd).unwrap());
}
map
}
pub enum CommandType {
BehaviorInstance(BehaviorInstanceCommand),
Bridge(BridgeCommand),
Button(ButtonCommand),
CameraMotion(CameraMotionCommand),
Contact(BasicCommand),
Device(DeviceCommand),
DevicePower(DevicePowerCommand),
EntertainmentConfiguration(EntertainmentConfigurationCommand),
GeofenceClient(GeofenceClientCommand),
Geolocation(GeolocationCommand),
GroupedLight(GroupCommand),
HomeKit(HomeKitCommand),
Light(String, LightCommand),
LightLevel(BasicCommand),
Matter(MatterCommand),
MatterFabric(MatterFabricCommand),
Motion(MotionCommand),
RelativeRotary(RelativeRotaryCommand),
Room(ZoneCommand),
Scene(SceneCommand),
SmartScene(SmartSceneCommand),
Tamper(TamperCommand),
Temperature(BasicCommand),
ZigbeeConnectivity(ZigbeeConnectivityCommand),
ZigbeeDeviceDiscovery(ZigbeeDeviceDiscoveryCommand),
Zone(ZoneCommand),
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BasicCommand {
Enabled(bool),
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BehaviorInstanceCommand {
Enabled(bool),
Configuration(serde_json::Value),
Trigger(serde_json::Value),
Metadata {
name: String,
},
}
pub struct BridgeCommand;
pub struct ButtonCommand;
pub struct CameraMotionCommand;
#[derive(Debug)]
pub enum DeviceCommand {
Identify,
Metadata {
name: Option<String>,
archetype: Option<ProductArchetype>,
},
UserTest(bool),
}
impl Serialize for DeviceCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Identify => {
map.serialize_entry("identify", &json!({ "action": "identify" }))?;
}
Self::Metadata { name, archetype } => {
map.serialize_entry("metadata", &json!({ "name": name, "archetype": archetype }))?;
}
Self::UserTest(u) => {
map.serialize_entry("usertest", &json!({ "usertest": u }))?;
}
}
map.end()
}
}
pub struct DevicePowerCommand;
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EntertainmentConfigurationCommand {
Action(EntertainmentAction),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EntertainmentAction {
Start,
Stop,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GeofenceClientCommand {
IsAtHome(bool),
Name(String),
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum GeolocationCommand {
Coordinates {
#[serde(rename = "latitude")]
lat: f32,
#[serde(rename = "longitude")]
lon: f32,
},
}
#[derive(Debug)]
pub enum GroupCommand {
Alert(AlertEffectType),
Color {
x: f32,
y: f32,
},
ColorTemp(u16),
ColorTempDelta {
action: DeltaAction,
mirek_delta: Option<u16>,
},
Dim(f32),
DimDelta {
action: DeltaAction,
brightness_delta: Option<f32>,
},
Dynamics {
duration: Option<usize>,
},
On(bool),
Signaling {
signal: SignalType,
duration: usize,
colors: Option<SignalColor>,
},
}
impl GroupCommand {
pub fn color_from_rgb(rgb: [u8; 3]) -> GroupCommand {
let cie = CIEColor::from_rgb(rgb);
GroupCommand::Color { x: cie.x, y: cie.y }
}
pub fn color_from_hex(hex: impl Into<String>) -> Result<GroupCommand, ParseColorError> {
match CIEColor::from_hex(hex) {
Ok(cie) => Ok(GroupCommand::Color { x: cie.x, y: cie.y }),
Err(e) => Err(e),
}
}
}
impl Serialize for GroupCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Alert(effect) => {
map.serialize_entry("alert", &json!({ "action": effect }))?;
}
Self::Color { x, y } => {
map.serialize_entry("color", &json!({ "xy": { "x": x, "y": y } }))?;
}
Self::ColorTemp(mirek) => {
map.serialize_entry("color_temperature", &json!({ "mirek": mirek }))?;
}
Self::ColorTempDelta {
action,
mirek_delta,
} => {
map.serialize_entry(
"color_temperature_delta",
&json!({ "action": action, "mirek_delta": mirek_delta}),
)?;
}
Self::Dim(pct) => {
map.serialize_entry("dimming", &json!({ "brightness": pct }))?;
}
Self::DimDelta {
action,
brightness_delta,
} => {
map.serialize_entry(
"dimming_delta",
&json!({ "action": action, "brightness_delta": brightness_delta }),
)?;
}
Self::Dynamics { duration } => {
map.serialize_entry("dynamics", &json!({ "duration": duration }))?;
}
Self::Signaling {
signal,
duration,
colors,
} => {
map.serialize_entry(
"signaling",
&json!({
"signal": signal,
"duration": duration,
"colors": colors,
}),
)?;
}
Self::On(on) => {
map.serialize_entry("on", &OnState { on: *on })?;
}
}
map.end()
}
}
#[derive(Debug)]
pub enum HomeKitCommand {
Reset,
}
impl Serialize for HomeKitCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
match self {
Self::Reset => map.serialize_entry("action", "homekit_reset")?,
}
map.end()
}
}
#[derive(Debug)]
pub enum LightCommand {
Alert(AlertEffectType),
Color {
x: f32,
y: f32,
},
ColorTemp(u16),
ColorTempDelta {
action: DeltaAction,
mirek_delta: Option<u16>,
},
Dim(f32),
DimDelta {
action: Option<DeltaAction>,
brightness_delta: Option<f32>,
},
Dynamics {
duration: Option<usize>,
speed: Option<f32>,
},
Gradient {
points: Vec<CIEColor>,
mode: Option<GradientMode>,
},
Effect(EffectType),
Identify,
Metadata {
name: Option<String>,
archetype: Option<ProductArchetype>,
},
On(bool),
PowerUp {
preset: PowerupPresetType,
on: Option<PowerupOnState>,
dimming: Option<PowerupDimming>,
color: Option<PowerupColor>,
},
Signaling {
signal: SignalType,
duration: usize,
colors: Option<SignalColor>,
},
TimedEffect {
effect: TimedEffectType,
duration: Option<usize>,
},
}
impl LightCommand {
pub fn color_from_rgb(rgb: [u8; 3]) -> LightCommand {
let cie = CIEColor::from_rgb(rgb);
LightCommand::Color { x: cie.x, y: cie.y }
}
pub fn color_from_hex(hex: impl Into<String>) -> Result<LightCommand, ParseColorError> {
match CIEColor::from_hex(hex) {
Ok(cie) => Ok(LightCommand::Color { x: cie.x, y: cie.y }),
Err(e) => Err(e),
}
}
}
#[derive(Debug)]
pub struct PowerupColor {
pub mode: PowerupColorMode,
pub color: Option<CIEColor>,
pub color_temperature: Option<u16>,
}
impl Serialize for PowerupColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("mode", &self.mode)?;
if let Some(xy) = &self.color {
map.serialize_entry("color", &ColorFeatureBasic { xy: xy.clone() })?;
}
if let Some(temp) = self.color_temperature {
map.serialize_entry("color_temperature", &json!({ "mirek": temp }))?;
}
map.end()
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PowerupColorMode {
Color,
#[serde(rename = "color_temperature")]
ColorTemp,
Previous,
}
#[derive(Debug)]
pub struct PowerupDimming {
pub mode: PowerupDimmingMode,
pub brightness: Option<f32>,
}
impl Serialize for PowerupDimming {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("mode", &self.mode)?;
if let Some(bri) = self.brightness {
map.serialize_entry("dimming", &json!({ "brightness": bri }))?;
}
map.end()
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PowerupDimmingMode {
Dimming,
Previous,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DeltaAction {
Up,
Down,
Stop,
}
#[derive(Debug)]
pub enum SignalColor {
One(CIEColor),
Two(CIEColor, CIEColor),
}
impl Serialize for SignalColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
SignalColor::One(inner) => {
serializer.collect_seq([ColorFeatureBasic { xy: inner.clone() }])
}
SignalColor::Two(inner_a, inner_b) => serializer.collect_seq([
ColorFeatureBasic {
xy: inner_a.clone(),
},
ColorFeatureBasic {
xy: inner_b.clone(),
},
]),
}
}
}
impl Serialize for LightCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Alert(effect) => {
map.serialize_entry("alert", &json!({ "action": effect }))?;
}
Self::Color { x, y } => {
map.serialize_entry("color", &json!({ "xy": { "x": x, "y": y } }))?;
}
Self::ColorTemp(mirek) => {
map.serialize_entry("color_temperature", &json!({ "mirek": mirek }))?;
}
Self::ColorTempDelta {
action,
mirek_delta,
} => {
map.serialize_entry(
"color_temperature_delta",
&json!({ "action": action, "mirek_delta": mirek_delta}),
)?;
}
Self::Dim(pct) => {
map.serialize_entry("dimming", &json!({ "brightness": pct }))?;
}
Self::DimDelta {
action,
brightness_delta,
} => {
map.serialize_entry(
"dimming_delta",
&json!({ "action": action, "brightness_delta": brightness_delta }),
)?;
}
Self::Dynamics { duration, speed } => {
map.serialize_entry("dynamics", &json!({ "duration": duration, "speed": speed }))?;
}
Self::Effect(effect) => {
map.serialize_entry("effects", &json!({ "effect": effect }))?;
}
Self::Gradient { points, mode } => {
let points = points
.iter()
.map(|xy| ColorFeatureBasic { xy: xy.clone() })
.collect::<Vec<ColorFeatureBasic>>();
map.serialize_entry("gradient", &json!({ "points": points, "mode": mode }))?;
}
Self::Identify => {
map.serialize_entry("identify", &json!({ "action": "identify" }))?;
}
Self::Metadata { name, archetype } => {
map.serialize_entry("metadata", &json!({ "name": name, "archetype": archetype }))?;
}
Self::On(on) => {
map.serialize_entry("on", &OnState { on: *on })?;
}
Self::PowerUp {
preset,
on,
dimming,
color,
} => {
map.serialize_entry(
"powerup",
&json!({
"preset": preset,
"on": on,
"dimming": dimming,
"color": color
}),
)?;
}
Self::Signaling {
signal,
duration,
colors,
} => {
map.serialize_entry(
"signaling",
&json!({
"signal": signal,
"duration": duration,
"colors": colors,
}),
)?;
}
Self::TimedEffect { effect, duration } => {
map.serialize_entry(
"timed_effects",
&json!({ "effect": effect, "duration": duration }),
)?;
}
}
map.end()
}
}
#[derive(Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DeviceIdentifyType {
Bridge,
#[default]
Lights,
Sensors,
}
#[derive(Debug)]
pub enum MatterCommand {
Reset,
}
impl Serialize for MatterCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Reset => map.serialize_entry("action", "matter_reset")?,
}
map.end()
}
}
pub struct MatterFabricCommand;
#[derive(Debug)]
pub enum MotionCommand {
Enabled(bool),
Sensitivity(usize),
}
impl Serialize for MotionCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Enabled(e) => {
map.serialize_entry("enabled", e)?;
}
Self::Sensitivity(s) => {
map.serialize_entry("sensitivity", &json!({ "sensitivity": s }))?;
}
}
map.end()
}
}
pub struct RelativeRotaryCommand;
#[derive(Debug)]
pub enum ZoneCommand {
Children(Vec<ResourceIdentifier>),
Metadata {
name: Option<String>,
archetype: Option<ZoneArchetype>,
},
}
impl Serialize for ZoneCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Children(rids) => {
map.serialize_entry("children", rids)?;
}
Self::Metadata { name, archetype } => {
map.serialize_entry("metadata", &json!({ "name": name, "archetype": archetype }))?;
}
}
map.end()
}
}
#[derive(Debug)]
pub enum SceneCommand {
Actions(Vec<SceneAction>),
AutoDynamic(bool),
Metadata {
name: Option<String>,
appdata: Option<String>,
},
Palette(ScenePalette),
Recall {
action: Option<SceneStatus>,
duration: Option<usize>,
dimming: Option<GroupDimmingState>,
},
Speed(f32),
}
impl Serialize for SceneCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Actions(actions) => {
map.serialize_entry("actions", actions)?;
}
Self::AutoDynamic(a) => {
map.serialize_entry("auto_dynamic", a)?;
}
Self::Metadata { name, appdata } => {
map.serialize_entry("metadata", &json!({ "name": name, "appdata": appdata}))?;
}
Self::Palette(palette) => {
map.serialize_entry("palette", palette)?;
}
Self::Recall {
action,
duration,
dimming,
} => {
map.serialize_entry(
"recall",
&json!({ "action": action, "duration": duration, "dimming": dimming }),
)?;
}
Self::Speed(s) => {
map.serialize_entry("speed", s)?;
}
}
map.end()
}
}
#[derive(Debug)]
pub enum SmartSceneCommand {
Enabled(bool),
Metadata {
name: Option<String>,
appdata: Option<String>,
},
Schedule(Vec<Schedule>),
TransitionDuration(usize),
}
impl SmartSceneCommand {
pub fn create_schedule() -> Schedule {
Schedule::new()
}
}
impl Serialize for SmartSceneCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Enabled(b) => {
map.serialize_entry(
"recall",
&json!({ "action": if *b { "activate" } else { "deactivate"} }),
)?;
}
Self::Metadata { name, appdata } => {
map.serialize_entry("metadata", &json!({ "name": name, "appdata": appdata}))?;
}
Self::Schedule(ts) => {
map.serialize_entry("week_timeslots", ts)?;
}
Self::TransitionDuration(ms) => {
map.serialize_entry("transition_duration", ms)?;
}
}
map.end()
}
}
pub struct TamperCommand;
#[derive(Debug)]
pub enum ZigbeeConnectivityCommand {
Channel(ZigbeeChannel),
}
impl Serialize for ZigbeeConnectivityCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Channel(ch) => {
map.serialize_entry("channel", &json!({ "value": ch }))?;
}
}
map.end()
}
}
#[derive(Debug)]
pub enum ZigbeeDeviceDiscoveryCommand {
Action {
search_codes: Vec<String>,
install_codes: Vec<String>,
},
}
impl Serialize for ZigbeeDeviceDiscoveryCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
Self::Action {
search_codes,
install_codes,
} => {
map.serialize_entry(
"action",
&json!({
"action_type": "search",
"search_codes": search_codes,
"install_codes": install_codes
}),
)?;
}
}
map.end()
}
}