#[cfg(feature = "mqtt")]
mod broker_device_builder;
#[cfg(feature = "http")]
mod http_builder;
#[cfg(feature = "mqtt")]
pub(crate) use broker_device_builder::BrokerDeviceBuilder;
#[cfg(feature = "http")]
pub(crate) use http_builder::HttpDeviceBuilder;
use std::sync::Arc;
use crate::capabilities::Capabilities;
use crate::command::{
ColorTemperatureCommand, Command, DimmerCommand, EnergyCommand, FadeCommand,
FadeDurationCommand, HsbColorCommand, PowerCommand, SchemeCommand, StartupFadeCommand,
StatusCommand, WakeupDurationCommand,
};
use crate::error::{DeviceError, Error};
#[cfg(feature = "http")]
use crate::protocol::HttpClient;
use crate::protocol::{CommandResponse, Protocol};
use crate::response::{
ColorTemperatureResponse, DimmerResponse, EnergyResponse, FadeDurationResponse, FadeResponse,
HsbColorResponse, PowerResponse, RgbColorResponse, SchemeResponse, StartupFadeResponse,
StatusResponse, WakeupDurationResponse,
};
use crate::state::DeviceState;
use crate::subscription::CallbackRegistry;
use crate::types::{
ColorTemperature, Dimmer, FadeDuration, HsbColor, PowerIndex, PowerState, RgbColor, Scheme,
WakeupDuration,
};
pub struct Device<P: Protocol> {
protocol: Arc<P>,
capabilities: Capabilities,
callbacks: Arc<CallbackRegistry>,
}
impl<P: Protocol> Clone for Device<P> {
fn clone(&self) -> Self {
Self {
protocol: Arc::clone(&self.protocol),
capabilities: self.capabilities.clone(),
callbacks: Arc::clone(&self.callbacks),
}
}
}
impl<P: Protocol> std::fmt::Debug for Device<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
.field("capabilities", &self.capabilities)
.finish_non_exhaustive()
}
}
impl<P: Protocol> Device<P> {
pub(crate) fn new(protocol: P, capabilities: Capabilities) -> Self {
Self {
protocol: Arc::new(protocol),
capabilities,
callbacks: Arc::new(CallbackRegistry::new()),
}
}
#[must_use]
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
pub async fn send_command<C: Command + Sync>(
&self,
command: &C,
) -> Result<CommandResponse, Error> {
self.protocol
.send_command(command)
.await
.map_err(Error::Protocol)
}
pub async fn power_on(&self) -> Result<PowerResponse, Error> {
self.power_on_index(PowerIndex::one()).await
}
pub async fn power_on_index(&self, index: PowerIndex) -> Result<PowerResponse, Error> {
self.set_power(index, PowerState::On).await
}
pub async fn power_off(&self) -> Result<PowerResponse, Error> {
self.power_off_index(PowerIndex::one()).await
}
pub async fn power_off_index(&self, index: PowerIndex) -> Result<PowerResponse, Error> {
self.set_power(index, PowerState::Off).await
}
pub async fn power_toggle(&self) -> Result<PowerResponse, Error> {
self.power_toggle_index(PowerIndex::one()).await
}
pub async fn power_toggle_index(&self, index: PowerIndex) -> Result<PowerResponse, Error> {
let cmd = PowerCommand::Toggle { index };
let response = self.send_command(&cmd).await?;
let parsed: PowerResponse = response.parse().map_err(Error::Parse)?;
self.apply_power_response(&parsed);
Ok(parsed)
}
pub async fn set_power(
&self,
index: PowerIndex,
state: PowerState,
) -> Result<PowerResponse, Error> {
let cmd = PowerCommand::Set { index, state };
let response = self.send_command(&cmd).await?;
let parsed: PowerResponse = response.parse().map_err(Error::Parse)?;
self.apply_power_response(&parsed);
Ok(parsed)
}
pub async fn get_power(&self) -> Result<PowerResponse, Error> {
self.get_power_index(PowerIndex::one()).await
}
pub async fn get_power_index(&self, index: PowerIndex) -> Result<PowerResponse, Error> {
let cmd = PowerCommand::Get { index };
let response = self.send_command(&cmd).await?;
let parsed: PowerResponse = response.parse().map_err(Error::Parse)?;
self.apply_power_response(&parsed);
Ok(parsed)
}
fn apply_power_response(&self, response: &PowerResponse) {
for idx in 1..=8 {
if let Ok(Some(power_state)) = response.power_state(idx) {
let change = crate::state::StateChange::power(idx, power_state);
self.callbacks.dispatch(&change);
}
}
}
pub async fn status(&self) -> Result<StatusResponse, Error> {
let cmd = StatusCommand::all();
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn status_abbreviated(&self) -> Result<StatusResponse, Error> {
let cmd = StatusCommand::abbreviated();
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn set_dimmer(&self, value: Dimmer) -> Result<DimmerResponse, Error> {
self.check_capability("dimmer", self.capabilities.supports_dimmer_control())?;
let cmd = DimmerCommand::Set(value);
let response = self.send_command(&cmd).await?;
let parsed: DimmerResponse = response.parse().map_err(Error::Parse)?;
self.apply_dimmer_response(&parsed);
Ok(parsed)
}
pub async fn get_dimmer(&self) -> Result<DimmerResponse, Error> {
self.check_capability("dimmer", self.capabilities.supports_dimmer_control())?;
let cmd = DimmerCommand::Get;
let response = self.send_command(&cmd).await?;
let parsed: DimmerResponse = response.parse().map_err(Error::Parse)?;
self.apply_dimmer_response(&parsed);
Ok(parsed)
}
fn apply_dimmer_response(&self, response: &DimmerResponse) {
if let Ok(dimmer) = Dimmer::new(response.dimmer()) {
let change = crate::state::StateChange::dimmer(dimmer);
self.callbacks.dispatch(&change);
}
if let Ok(Some(power)) = response.power_state() {
let change = crate::state::StateChange::power(1, power);
self.callbacks.dispatch(&change);
}
}
pub async fn set_color_temperature(
&self,
value: ColorTemperature,
) -> Result<ColorTemperatureResponse, Error> {
self.check_capability(
"color temperature",
self.capabilities.supports_color_temperature_control(),
)?;
let cmd = ColorTemperatureCommand::Set(value);
let response = self.send_command(&cmd).await?;
let parsed: ColorTemperatureResponse = response.parse().map_err(Error::Parse)?;
self.apply_color_temperature_response(&parsed);
Ok(parsed)
}
pub async fn get_color_temperature(&self) -> Result<ColorTemperatureResponse, Error> {
self.check_capability(
"color temperature",
self.capabilities.supports_color_temperature_control(),
)?;
let cmd = ColorTemperatureCommand::Get;
let response = self.send_command(&cmd).await?;
let parsed: ColorTemperatureResponse = response.parse().map_err(Error::Parse)?;
self.apply_color_temperature_response(&parsed);
Ok(parsed)
}
fn apply_color_temperature_response(&self, response: &ColorTemperatureResponse) {
if let Ok(ct) = ColorTemperature::new(response.color_temperature()) {
let change = crate::state::StateChange::color_temperature(ct);
self.callbacks.dispatch(&change);
}
if let Ok(Some(power)) = response.power_state() {
let change = crate::state::StateChange::power(1, power);
self.callbacks.dispatch(&change);
}
}
pub async fn set_hsb_color(&self, color: HsbColor) -> Result<HsbColorResponse, Error> {
self.check_capability("RGB color", self.capabilities.supports_rgb_control())?;
let cmd = HsbColorCommand::Set(color);
let response = self.send_command(&cmd).await?;
let parsed: HsbColorResponse = response.parse().map_err(Error::Parse)?;
self.apply_hsb_color_response(&parsed);
Ok(parsed)
}
pub async fn get_hsb_color(&self) -> Result<HsbColorResponse, Error> {
self.check_capability("RGB color", self.capabilities.supports_rgb_control())?;
let cmd = HsbColorCommand::Get;
let response = self.send_command(&cmd).await?;
let parsed: HsbColorResponse = response.parse().map_err(Error::Parse)?;
self.apply_hsb_color_response(&parsed);
Ok(parsed)
}
fn apply_hsb_color_response(&self, response: &HsbColorResponse) {
if let Ok(color) = response.hsb_color() {
let change = crate::state::StateChange::hsb_color(color);
self.callbacks.dispatch(&change);
}
if let Some(dimmer_value) = response.dimmer()
&& let Ok(dimmer) = Dimmer::new(dimmer_value)
{
let change = crate::state::StateChange::dimmer(dimmer);
self.callbacks.dispatch(&change);
}
if let Ok(Some(power)) = response.power_state() {
let change = crate::state::StateChange::power(1, power);
self.callbacks.dispatch(&change);
}
}
pub async fn set_rgb_color(&self, color: RgbColor) -> Result<RgbColorResponse, Error> {
self.check_capability("RGB color", self.capabilities.supports_rgb_control())?;
let hsb = color.to_hsb();
let cmd = HsbColorCommand::Set(hsb);
let response = self.send_command(&cmd).await?;
let hsb_response: HsbColorResponse = response.parse().map_err(Error::Parse)?;
self.apply_hsb_color_response(&hsb_response);
let returned_hsb = hsb_response.hsb_color().unwrap_or(hsb);
Ok(RgbColorResponse::new(color, returned_hsb))
}
pub async fn set_scheme(&self, scheme: Scheme) -> Result<SchemeResponse, Error> {
let cmd = SchemeCommand::Set(scheme);
let response = self.send_command(&cmd).await?;
let parsed: SchemeResponse = response.parse().map_err(Error::Parse)?;
if let Ok(s) = parsed.scheme() {
let change = crate::state::StateChange::scheme(s);
self.callbacks.dispatch(&change);
}
Ok(parsed)
}
pub async fn get_scheme(&self) -> Result<SchemeResponse, Error> {
let cmd = SchemeCommand::Get;
let response = self.send_command(&cmd).await?;
let parsed: SchemeResponse = response.parse().map_err(Error::Parse)?;
if let Ok(s) = parsed.scheme() {
let change = crate::state::StateChange::scheme(s);
self.callbacks.dispatch(&change);
}
Ok(parsed)
}
pub async fn set_wakeup_duration(
&self,
duration: WakeupDuration,
) -> Result<WakeupDurationResponse, Error> {
let cmd = WakeupDurationCommand::Set(duration);
let response = self.send_command(&cmd).await?;
let parsed: WakeupDurationResponse = response.parse().map_err(Error::Parse)?;
if let Ok(d) = parsed.duration() {
let change = crate::state::StateChange::wakeup_duration(d);
self.callbacks.dispatch(&change);
}
Ok(parsed)
}
pub async fn get_wakeup_duration(&self) -> Result<WakeupDurationResponse, Error> {
let cmd = WakeupDurationCommand::Get;
let response = self.send_command(&cmd).await?;
let parsed: WakeupDurationResponse = response.parse().map_err(Error::Parse)?;
if let Ok(d) = parsed.duration() {
let change = crate::state::StateChange::wakeup_duration(d);
self.callbacks.dispatch(&change);
}
Ok(parsed)
}
pub async fn enable_fade(&self) -> Result<FadeResponse, Error> {
let cmd = FadeCommand::Enable;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn disable_fade(&self) -> Result<FadeResponse, Error> {
let cmd = FadeCommand::Disable;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn get_fade(&self) -> Result<FadeResponse, Error> {
let cmd = FadeCommand::Get;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn set_fade_duration(
&self,
duration: FadeDuration,
) -> Result<FadeDurationResponse, Error> {
let cmd = FadeDurationCommand::Set(duration);
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn get_fade_duration(&self) -> Result<FadeDurationResponse, Error> {
let cmd = FadeDurationCommand::Get;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn enable_fade_at_startup(&self) -> Result<StartupFadeResponse, Error> {
let cmd = StartupFadeCommand::Enable;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn disable_fade_at_startup(&self) -> Result<StartupFadeResponse, Error> {
let cmd = StartupFadeCommand::Disable;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn get_fade_at_startup(&self) -> Result<StartupFadeResponse, Error> {
let cmd = StartupFadeCommand::Get;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn energy(&self) -> Result<EnergyResponse, Error> {
self.check_capability(
"energy monitoring",
self.capabilities.supports_energy_monitoring(),
)?;
let cmd = EnergyCommand::Get;
let response = self.send_command(&cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn reset_energy_total(&self) -> Result<EnergyResponse, Error> {
self.check_capability(
"energy monitoring",
self.capabilities.supports_energy_monitoring(),
)?;
let cmd = EnergyCommand::ResetTotal;
self.send_command(&cmd).await?;
let query_cmd = EnergyCommand::Get;
let response = self.send_command(&query_cmd).await?;
response.parse().map_err(Error::Parse)
}
pub async fn run(
&self,
routine: &crate::command::Routine,
) -> Result<crate::response::RoutineResponse, Error> {
let backlog_cmd = routine.to_backlog_command();
tracing::debug!(
steps = routine.len(),
raw = %backlog_cmd,
"Running routine"
);
let response = self
.protocol
.send_raw(&backlog_cmd)
.await
.map_err(Error::Protocol)?;
let parsed: crate::response::RoutineResponse = response.parse().map_err(Error::Parse)?;
self.apply_routine_response(&parsed);
Ok(parsed)
}
fn apply_routine_response(&self, response: &crate::response::RoutineResponse) {
for idx in 1..=8u8 {
let keys = if idx == 1 {
vec!["POWER".to_string(), "POWER1".to_string()]
} else {
vec![format!("POWER{idx}")]
};
for key in keys {
if let Some(state_str) = response.try_get_as::<String>(&key)
&& let Ok(state) = state_str.parse::<PowerState>()
{
let change = crate::state::StateChange::power(idx, state);
self.callbacks.dispatch(&change);
break; }
}
}
if let Some(dimmer_value) = response.try_get_as::<u8>("Dimmer")
&& let Ok(dimmer) = Dimmer::new(dimmer_value)
{
let change = crate::state::StateChange::dimmer(dimmer);
self.callbacks.dispatch(&change);
}
if let Some(hsb_str) = response.try_get_as::<String>("HSBColor") {
let parts: Vec<&str> = hsb_str.split(',').map(str::trim).collect();
if parts.len() == 3 {
if let (Ok(h), Ok(s), Ok(b)) = (
parts[0].parse::<u16>(),
parts[1].parse::<u8>(),
parts[2].parse::<u8>(),
) {
if let Ok(color) = HsbColor::new(h, s, b) {
let change = crate::state::StateChange::hsb_color(color);
self.callbacks.dispatch(&change);
} else {
tracing::warn!(value = %hsb_str, "HSBColor values out of range in sequence response");
}
} else {
tracing::warn!(value = %hsb_str, "Failed to parse HSBColor components in sequence response");
}
} else {
tracing::warn!(value = %hsb_str, "Invalid HSBColor format in sequence response (expected 3 parts)");
}
}
if let Some(ct_value) = response.try_get_as::<u16>("CT")
&& let Ok(ct) = ColorTemperature::new(ct_value)
{
let change = crate::state::StateChange::color_temperature(ct);
self.callbacks.dispatch(&change);
}
if let Some(scheme_value) = response.try_get_as::<u8>("Scheme")
&& let Ok(scheme) = Scheme::new(scheme_value)
{
let change = crate::state::StateChange::scheme(scheme);
self.callbacks.dispatch(&change);
}
}
#[allow(clippy::too_many_lines)]
pub async fn query_state(&self) -> Result<DeviceState, Error> {
tracing::debug!(
energy_monitoring = self.capabilities.supports_energy_monitoring(),
dimmer = self.capabilities.supports_dimmer_control(),
rgb = self.capabilities.supports_rgb_control(),
cct = self.capabilities.supports_color_temperature_control(),
"Querying device state"
);
let mut state = DeviceState::new();
match self.get_power().await {
Ok(power_response) => {
if let Ok(power_state) = power_response.first_power_state() {
tracing::debug!(?power_state, "Got power state");
state.set_power(1, power_state);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get power state"),
}
if self.capabilities.supports_dimmer_control() {
match self.get_dimmer().await {
Ok(dimmer_response) => {
if let Ok(dimmer) = Dimmer::new(dimmer_response.dimmer()) {
tracing::debug!(dimmer = dimmer.value(), "Got dimmer");
state.set_dimmer(dimmer);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get dimmer"),
}
}
if self.capabilities.supports_color_temperature_control() {
match self.get_color_temperature().await {
Ok(ct_response) => {
if let Ok(ct) = ColorTemperature::new(ct_response.color_temperature()) {
tracing::debug!(ct = ct.value(), "Got color temperature");
state.set_color_temperature(ct);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get color temperature"),
}
}
if self.capabilities.supports_rgb_control() {
match self.get_hsb_color().await {
Ok(hsb_response) => {
if let Ok(hsb) = hsb_response.hsb_color() {
tracing::debug!(hue = hsb.hue(), sat = hsb.saturation(), "Got HSB color");
state.set_hsb_color(hsb);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get HSB color"),
}
}
if self.capabilities.supports_energy_monitoring() {
tracing::debug!("Querying energy data");
match self.energy().await {
Ok(energy_response) => {
tracing::debug!(?energy_response, "Got energy response");
if let Some(energy) = energy_response.energy() {
tracing::debug!(
power = energy.power,
voltage = energy.voltage,
current = energy.current,
"Setting energy data"
);
state.set_power_consumption(energy.power);
state.set_voltage(energy.voltage);
state.set_current(energy.current);
state.set_energy_today(energy.today);
state.set_energy_yesterday(energy.yesterday);
state.set_energy_total(energy.total);
state.set_apparent_power(energy.apparent_power);
state.set_reactive_power(energy.reactive_power);
state.set_power_factor(energy.factor);
if let Some(start_time) = &energy.total_start_time {
state.set_total_start_time(start_time.clone());
}
} else {
tracing::debug!("Energy response has no energy data");
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get energy data"),
}
}
if self.capabilities.supports_dimmer_control() {
match self.get_fade().await {
Ok(fade_response) => {
if let Ok(enabled) = fade_response.is_enabled() {
tracing::debug!(fade_enabled = enabled, "Got fade state");
state.set_fade_enabled(enabled);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get fade state"),
}
match self.get_fade_duration().await {
Ok(duration_response) => {
if let Ok(duration) = duration_response.duration() {
tracing::debug!(fade_duration = ?duration.as_duration(), "Got fade duration");
state.set_fade_duration(duration);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get fade duration"),
}
}
match self.status().await {
Ok(status_response) => {
let mut sys_info = crate::state::SystemInfo::new();
if let Some(prm) = &status_response.status_prm
&& let Some(uptime) = prm.uptime()
{
sys_info = sys_info.with_uptime(uptime);
tracing::debug!(uptime_secs = uptime.as_secs(), "Got uptime");
}
if let Some(mem) = &status_response.memory {
sys_info = sys_info.with_heap(mem.heap);
tracing::debug!(heap = mem.heap, "Got heap memory");
}
if let Some(net) = &status_response.network {
sys_info = sys_info.with_wifi_rssi(net.rssi);
tracing::debug!(rssi = net.rssi, "Got WiFi RSSI");
}
if !sys_info.is_empty() {
state.set_system_info(sys_info);
}
}
Err(e) => tracing::debug!(error = %e, "Failed to get status for system info"),
}
Ok(state)
}
#[allow(clippy::unused_self)]
fn check_capability(&self, name: &str, supported: bool) -> Result<(), Error> {
if supported {
Ok(())
} else {
Err(Error::Device(DeviceError::UnsupportedCapability {
capability: name.to_string(),
}))
}
}
}
#[cfg(feature = "http")]
impl Device<HttpClient> {
#[must_use]
pub fn http(host: impl Into<String>) -> HttpDeviceBuilder {
HttpDeviceBuilder::new(crate::protocol::HttpConfig::new(host))
}
#[must_use]
pub fn http_config(config: crate::protocol::HttpConfig) -> HttpDeviceBuilder {
HttpDeviceBuilder::new(config)
}
}
#[cfg(feature = "mqtt")]
use crate::protocol::SharedMqttClient;
#[cfg(feature = "mqtt")]
use crate::state::StateChange;
#[cfg(feature = "mqtt")]
use crate::subscription::{EnergyData, Subscribable, SubscriptionId};
#[cfg(feature = "mqtt")]
impl Device<SharedMqttClient> {
pub(crate) fn register_callbacks(&self) {
self.protocol.register_callbacks(&self.callbacks);
}
pub async fn disconnect(&self) {
self.protocol.disconnect().await;
}
#[must_use]
pub fn is_disconnected(&self) -> bool {
self.protocol.is_disconnected()
}
#[must_use]
pub fn topic(&self) -> &str {
self.protocol.topic()
}
}
#[cfg(feature = "mqtt")]
impl Subscribable for Device<SharedMqttClient> {
fn on_power_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(u8, PowerState) + Send + Sync + 'static,
{
self.callbacks.on_power_changed(callback)
}
fn on_dimmer_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(Dimmer) + Send + Sync + 'static,
{
self.callbacks.on_dimmer_changed(callback)
}
fn on_color_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(HsbColor) + Send + Sync + 'static,
{
self.callbacks.on_hsb_color_changed(callback)
}
fn on_color_temp_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(ColorTemperature) + Send + Sync + 'static,
{
self.callbacks.on_color_temp_changed(callback)
}
fn on_scheme_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(Scheme) + Send + Sync + 'static,
{
self.callbacks.on_scheme_changed(callback)
}
fn on_energy_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(EnergyData) + Send + Sync + 'static,
{
self.callbacks.on_energy_changed(callback)
}
fn on_connected<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(&DeviceState) + Send + Sync + 'static,
{
self.callbacks.on_connected(callback)
}
fn on_disconnected<F>(&self, callback: F) -> SubscriptionId
where
F: Fn() + Send + Sync + 'static,
{
self.callbacks.on_disconnected(callback)
}
fn on_reconnected<F>(&self, callback: F) -> SubscriptionId
where
F: Fn() + Send + Sync + 'static,
{
self.callbacks.on_reconnected(callback)
}
fn on_state_changed<F>(&self, callback: F) -> SubscriptionId
where
F: Fn(&StateChange) + Send + Sync + 'static,
{
self.callbacks.on_state_changed(callback)
}
fn unsubscribe(&self, id: SubscriptionId) -> bool {
self.callbacks.unsubscribe(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::HttpConfig;
#[test]
fn http_device_builder_from_config() {
let config = HttpConfig::new("192.168.1.100").with_credentials("admin", "pass");
let builder = Device::<HttpClient>::http_config(config)
.with_capabilities(Capabilities::neo_coolcam());
assert!(builder.capabilities().is_some());
}
#[test]
fn http_device_builder_from_host() {
let builder = Device::<HttpClient>::http("192.168.1.100")
.with_credentials("admin", "pass")
.with_capabilities(Capabilities::neo_coolcam());
assert!(builder.capabilities().is_some());
}
#[test]
fn device_state_default() {
let state = DeviceState::new();
assert!(state.power(1).is_none());
}
#[test]
fn device_is_clone() {
fn assert_clone<T: Clone>() {}
assert_clone::<Device<HttpClient>>();
}
#[test]
fn device_is_debug() {
fn assert_debug<T: std::fmt::Debug>() {}
assert_debug::<Device<HttpClient>>();
}
#[cfg(feature = "mqtt")]
#[test]
fn device_shared_mqtt_client_is_clone_and_debug() {
use crate::protocol::SharedMqttClient;
fn assert_clone<T: Clone>() {}
fn assert_debug<T: std::fmt::Debug>() {}
assert_clone::<Device<SharedMqttClient>>();
assert_debug::<Device<SharedMqttClient>>();
}
#[test]
fn device_clone_shares_callbacks() {
let client = HttpClient::new("192.168.1.100").unwrap();
let device = Device::new(client, Capabilities::basic());
let device_clone = device.clone();
assert!(Arc::ptr_eq(&device.callbacks, &device_clone.callbacks));
}
#[test]
fn device_clone_shares_protocol() {
let client = HttpClient::new("192.168.1.100").unwrap();
let device = Device::new(client, Capabilities::basic());
let device_clone = device.clone();
assert!(Arc::ptr_eq(&device.protocol, &device_clone.protocol));
}
}