use core::cell::Cell;
use core::future::{pending, ready, Future};
use core::ops::Mul;
use core::pin::pin;
use embassy_futures::select::{select, select3, Either, Either3};
use embassy_time::{Duration, Instant};
use crate::dm::clusters::app::on_off::{OnOffHooks, FULL_CLUSTER as ON_OFF_FULL_CLUSTER};
use crate::dm::clusters::app::{level_control, on_off::OnOffHandler};
pub use crate::dm::clusters::decl::level_control::*;
use crate::dm::clusters::decl::scenes_management::{
AttributeValuePairStruct, AttributeValuePairStructArrayBuilder,
};
use crate::dm::clusters::scenes::{SceneClusterHandler, SceneInvalidator};
use crate::dm::{
AttrId, Cluster, ClusterId, Dataver, EndptId, HandlerContext, InvokeContext, ReadContext,
WriteContext,
};
use crate::error::{Error, ErrorCode};
use crate::tlv::{Nullable, TLVArray, TLVBuilderParent};
use crate::utils::cell::RefCell;
use crate::utils::sync::blocking::Mutex;
use crate::utils::sync::Signal;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OutOfBandMessage {
Update(u8),
MoveToLevel {
with_on_off: bool,
level: u8,
transition_time: Option<u16>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
},
Move {
with_on_off: bool,
move_mode: MoveModeEnum,
rate: Option<u8>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
},
Step {
with_on_off: bool,
step_mode: StepModeEnum,
step_size: u8,
transition_time: Option<u16>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
},
Stop,
}
enum Task {
MoveToLevel {
with_on_off: bool,
target: u8,
transition_time: u16,
scene_apply: bool,
},
Move {
with_on_off: bool,
move_mode: MoveModeEnum,
event_duration: Duration,
},
Stop,
OnOffStateChange {
on: bool,
},
}
struct LevelControlState {
on_level: Nullable<u8>,
options: OptionsBitmap,
remaining_time: u16,
on_off_transition_time: u16,
on_transition_time: Nullable<u16>,
off_transition_time: Nullable<u16>,
default_move_rate: Nullable<u8>,
previous_current_level: Option<u8>,
last_current_level_notification: Instant,
}
impl LevelControlState {
fn new(attribute_defaults: AttributeDefaults) -> Self {
Self {
on_level: attribute_defaults.on_level,
options: attribute_defaults.options,
remaining_time: 0,
on_off_transition_time: attribute_defaults.on_off_transition_time,
on_transition_time: attribute_defaults.on_transition_time,
off_transition_time: attribute_defaults.off_transition_time,
default_move_rate: attribute_defaults.default_move_rate,
previous_current_level: None,
last_current_level_notification: Instant::from_millis(0),
}
}
fn write_remaining_time_quietly(
&mut self,
remaining_time: Duration,
is_start_of_transition: bool,
) -> bool {
let remaining_time_ds = remaining_time.as_millis().div_ceil(100) as u16;
let previous_remaining_time = self.remaining_time;
let changed_to_zero = remaining_time_ds == 0 && previous_remaining_time != 0;
let changed_from_zero_gt_10 = previous_remaining_time == 0 && remaining_time_ds > 10;
let changed_by_gt_10 =
remaining_time_ds.abs_diff(previous_remaining_time) > 10 && is_start_of_transition;
self.remaining_time = remaining_time_ds;
if changed_to_zero || changed_from_zero_gt_10 || changed_by_gt_10 {
return true;
}
false
}
}
pub struct LevelControlHandler<'a, H: LevelControlHooks, OH: OnOffHooks> {
dataver: Dataver,
endpoint_id: EndptId,
hooks: H,
on_off_handler: Mutex<Cell<Option<&'a OnOffHandler<'a, OH, H>>>>,
scene_invalidator: Mutex<Cell<Option<&'a dyn SceneInvalidator>>>,
state: Mutex<RefCell<LevelControlState>>,
task_signal: Signal<Option<Task>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AttributeDefaults {
pub on_level: Nullable<u8>,
pub options: OptionsBitmap,
pub on_off_transition_time: u16,
pub on_transition_time: Nullable<u16>,
pub off_transition_time: Nullable<u16>,
pub default_move_rate: Nullable<u8>,
}
impl AttributeDefaults {
pub const fn new() -> Self {
Self {
on_level: Nullable::none(),
options: OptionsBitmap::from_bits(0).unwrap(),
on_off_transition_time: 0,
on_transition_time: Nullable::none(),
off_transition_time: Nullable::none(),
default_move_rate: Nullable::none(),
}
}
}
impl Default for AttributeDefaults {
fn default() -> Self {
Self::new()
}
}
impl<H: LevelControlHooks> LevelControlHandler<'_, H, NoOnOff> {
pub fn new_standalone(
dataver: Dataver,
endpoint_id: EndptId,
hooks: H,
attribute_defaults: AttributeDefaults,
) -> Self {
let this = Self::new(dataver, endpoint_id, hooks, attribute_defaults);
this.init(None);
this
}
}
impl<'a, H: LevelControlHooks, OH: OnOffHooks> LevelControlHandler<'a, H, OH> {
const MAXIMUM_LEVEL: u8 = 254;
pub fn new(
dataver: Dataver,
endpoint_id: EndptId,
hooks: H,
attribute_defaults: AttributeDefaults,
) -> Self {
Self {
dataver,
endpoint_id,
hooks,
on_off_handler: Mutex::new(Cell::new(None)),
scene_invalidator: Mutex::new(Cell::new(None)),
state: Mutex::new(RefCell::new(LevelControlState::new(attribute_defaults))),
task_signal: Signal::new(None),
}
}
pub fn with_scene_invalidator(self, invalidator: &'a dyn SceneInvalidator) -> Self {
self.scene_invalidator
.lock(|cell| cell.set(Some(invalidator)));
self
}
fn notify_scenable_changed(&self) {
if let Some(inv) = self.scene_invalidator.lock(|cell| cell.get()) {
inv.scenable_attribute_changed(self.endpoint_id);
}
}
fn validate(&self) {
if H::CLUSTER.revision != 6 {
panic!(
"LevelControl validation: incorrect version number: expected 6 got {}",
H::CLUSTER.revision
);
}
if H::CLUSTER
.attribute(AttributeId::CurrentLevel as _)
.is_none()
|| H::CLUSTER.attribute(AttributeId::OnLevel as _).is_none()
|| H::CLUSTER.attribute(AttributeId::Options as _).is_none()
{
panic!("LevelControl validation: missing required attributes: CurrentLevel, OnLevel, or Options");
}
if H::CLUSTER.command(CommandId::MoveToLevel as _).is_none()
|| H::CLUSTER.command(CommandId::Move as _).is_none()
|| H::CLUSTER.command(CommandId::Step as _).is_none()
|| H::CLUSTER.command(CommandId::Stop as _).is_none()
|| H::CLUSTER
.command(CommandId::MoveToLevelWithOnOff as _)
.is_none()
|| H::CLUSTER.command(CommandId::MoveWithOnOff as _).is_none()
|| H::CLUSTER.command(CommandId::StepWithOnOff as _).is_none()
|| H::CLUSTER.command(CommandId::StopWithOnOff as _).is_none()
{
panic!("LevelControl validation: missing required commands: MoveToLevel, Move, Step, Stop, MoveToLevelWithOnOff, MoveWithOnOff, StepWithOnOff or StopWithOnOff");
}
if H::CLUSTER.feature_map & level_control::Feature::ON_OFF.bits() != 0 {
if self.on_off_handler.lock(|h| h.get()).is_none() {
panic!("LevelControl validation: a reference to the OnOff cluster must be set when the ON_OFF feature is enabled");
}
}
if H::MAX_LEVEL > Self::MAXIMUM_LEVEL {
panic!(
"LevelControl validation: the MAX_LEVEL cannot be higher than {}",
Self::MAXIMUM_LEVEL
);
}
if H::CLUSTER.feature_map & level_control::Feature::LIGHTING.bits() != 0 {
if H::MIN_LEVEL == 0 {
panic!("LevelControl validation: MIN_LEVEL cannot be 0 when the LIGHTING feature is enabled");
}
if H::CLUSTER
.attribute(AttributeId::RemainingTime as _)
.is_none()
|| H::CLUSTER
.attribute(AttributeId::StartUpCurrentLevel as _)
.is_none()
{
panic!("LevelControl validation: the RemainingTime and StartUpCurrentLevel attributes are required by the LIGHTING feature");
}
}
}
pub fn init(&self, on_off_handler: Option<&'a OnOffHandler<'a, OH, H>>) {
self.on_off_handler.lock(|h| h.set(on_off_handler));
self.validate();
if let Ok(Some(startup_current_level)) = self.hooks.start_up_current_level() {
let level = if startup_current_level < H::MIN_LEVEL {
H::MIN_LEVEL
} else if startup_current_level > H::MAX_LEVEL {
H::MAX_LEVEL
} else {
startup_current_level
};
match self.hooks.set_device_level(level) {
Ok(current_level) => self.hooks.set_current_level(current_level),
Err(_) => error!("Failed to set Current Level to Start Up Current Level."),
}
}
}
pub const fn adapt(self) -> HandlerAsyncAdaptor<Self> {
HandlerAsyncAdaptor(self)
}
fn with_state<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut LevelControlState) -> R,
{
self.state.lock(|state| {
let mut state = state.borrow_mut();
f(&mut state)
})
}
fn with_state_notify<F, R>(&self, ctx: impl WriteContext, f: F) -> R
where
F: FnOnce(&mut LevelControlState) -> R,
{
let result = self.with_state(f);
ctx.notify_changed();
result
}
fn set_level(
&self,
state: &mut LevelControlState,
level: u8,
is_end_of_transition: bool,
set_device: bool,
scene_apply: bool,
) -> Result<(Option<u8>, bool), Error> {
state.previous_current_level = self.hooks.current_level();
let current_level = match set_device {
true => self
.hooks
.set_device_level(level)
.map_err(|_| ErrorCode::Failure)?,
false => Some(level),
};
self.hooks.set_current_level(current_level);
if !scene_apply {
self.notify_scenable_changed();
}
let last_notification = Instant::now() - state.last_current_level_notification;
if last_notification.ge(&Duration::from_secs(1))
|| is_end_of_transition
|| state.previous_current_level.is_none()
|| current_level.is_none()
{
state.last_current_level_notification = Instant::now();
return Ok((current_level, true));
}
Ok((current_level, false))
}
fn should_continue(
&self,
with_on_off: bool,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<bool, Error> {
if with_on_off {
return Ok(true);
}
let Some(on_off_handler) = self.on_off_handler.lock(|h| h.get()) else {
return Ok(true);
};
if on_off_handler.on_off() {
return Ok(true);
}
if options_mask.contains(level_control::OptionsBitmap::EXECUTE_IF_OFF) {
return Ok(options_override.contains(level_control::OptionsBitmap::EXECUTE_IF_OFF));
}
Ok(self
.with_state(|state| state.options)
.contains(level_control::OptionsBitmap::EXECUTE_IF_OFF))
}
async fn task_manager(&self, ctx: impl HandlerContext, task: Task) {
match task {
Task::MoveToLevel {
with_on_off,
target,
transition_time,
scene_apply,
} => {
if let Err(e) = self
.move_to_level_transition(
ctx,
with_on_off,
target,
transition_time,
scene_apply,
)
.await
{
error!("Task::MoveToLevel: {:?}", e);
}
}
Task::Move {
with_on_off,
move_mode,
event_duration,
} => {
if let Err(e) = self
.move_transition(ctx, with_on_off, move_mode, event_duration)
.await
{
error!("Task::Move: {:?}", e);
}
}
Task::Stop => (),
Task::OnOffStateChange { on } => {
if let Err(e) = self.handle_on_off_state_change(ctx, on).await {
error!("Task::OnOffStateChange: {:?}", e);
}
}
}
}
pub(crate) fn coupled_on_off_cluster_on_off_state_change(&self, on: bool) {
self.task_signal.signal(Task::OnOffStateChange { on });
}
async fn handle_on_off_state_change(
&self,
ctx: impl HandlerContext,
on: bool,
) -> Result<(), Error> {
info!("handle_on_off_state_change");
let (target_level, transition_time, bitmap, temp_current_level) =
self.with_state(|state| {
let temp_current_level = self.hooks.current_level();
let bitmap = OptionsBitmap::from_bits(0).unwrap();
let mut transition_time = state.on_off_transition_time;
if on {
let (level, should_notify) =
self.set_level(state, H::MIN_LEVEL, false, true, false)?;
if should_notify {
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
if level.is_none() {
Err(ErrorCode::Failure)?;
}
let target_level = match state.on_level.as_opt_ref() {
Some(on_level) => *on_level,
None => temp_current_level.ok_or(ErrorCode::Failure)?,
};
if let Some(tt) = state.on_transition_time.as_opt_ref() {
transition_time = *tt;
}
Ok::<_, Error>((target_level, transition_time, bitmap, temp_current_level))
} else {
if let Some(tt) = state.off_transition_time.as_opt_ref() {
transition_time = *tt;
}
Ok((H::MIN_LEVEL, transition_time, bitmap, temp_current_level))
}
})?;
self.move_to_level_blocking(
&ctx,
true,
target_level,
Some(transition_time),
bitmap,
bitmap,
)
.await?;
if !on {
let restored = self.with_state(|state| {
if state.on_level.is_none() {
self.hooks.set_current_level(temp_current_level);
true
} else {
false
}
});
if restored {
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
}
Ok(())
}
fn update_coupled_on_off(&self, current_level: u8, with_on_off: bool) -> Result<(), Error> {
if !with_on_off {
return Ok(());
}
let new_on_off_value = current_level > H::MIN_LEVEL;
if let Some(on_off) = self.on_off_handler.lock(|h| h.get()) {
let current_on_off = on_off.on_off();
if current_on_off != new_on_off_value {
info!(
"Updating the OnOff cluster with on_off = {}",
new_on_off_value
);
on_off.coupled_cluster_set_on_off(new_on_off_value);
}
}
Ok(())
}
fn move_to_level_validation(
&self,
level: &mut u8,
with_on_off: bool,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<bool, Error> {
if *level > Self::MAXIMUM_LEVEL {
return Err(ErrorCode::InvalidCommand.into());
}
if !self.should_continue(with_on_off, options_mask, options_override)? {
return Ok(false);
}
if *level > H::MAX_LEVEL {
*level = H::MAX_LEVEL;
debug!("target level > MAX_LEVEL. level set to MAX_LEVEL")
} else if *level < H::MIN_LEVEL {
*level = H::MIN_LEVEL;
debug!("target level < MIN_LEVEL. level set to MIN_LEVEL")
}
Ok(true)
}
fn move_to_level(
&self,
with_on_off: bool,
mut level: u8,
transition_time: Option<u16>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
scene_apply: bool,
) -> Result<(), Error> {
if let Ok(false) =
self.move_to_level_validation(&mut level, with_on_off, options_mask, options_override)
{
return Ok(());
}
info!(
"setting level to {} with transition time {:?}",
level, transition_time
);
self.task_signal.signal(Task::Stop);
if self.hooks.current_level() == Some(level) {
self.update_coupled_on_off(level, with_on_off)?;
return Ok(());
}
let t_time = transition_time.unwrap_or(0);
self.task_signal.signal(Task::MoveToLevel {
with_on_off,
target: level,
transition_time: t_time,
scene_apply,
});
Ok(())
}
async fn move_to_level_blocking(
&self,
ctx: impl HandlerContext,
with_on_off: bool,
mut level: u8,
transition_time: Option<u16>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<(), Error> {
if let Ok(false) =
self.move_to_level_validation(&mut level, with_on_off, options_mask, options_override)
{
return Ok(());
}
info!(
"setting level to {} with transition time {:?}",
level, transition_time
);
if self.hooks.current_level() == Some(level) {
self.update_coupled_on_off(level, with_on_off)?;
return Ok(());
}
let t_time = transition_time.unwrap_or(0);
self.move_to_level_transition(ctx, with_on_off, level, t_time, false)
.await?;
Ok(())
}
async fn move_to_level_transition(
&self,
ctx: impl HandlerContext,
with_on_off: bool,
target_level: u8,
transition_time: u16,
scene_apply: bool,
) -> Result<(), Error> {
let event_start_time = Instant::now();
let mut current_level = match self.hooks.current_level() {
Some(cl) => cl,
None => return Err(ErrorCode::Failure.into()),
};
let increasing = current_level < target_level;
let steps = target_level.abs_diff(current_level);
if steps == 0 {
return Ok(());
}
let mut remaining_time = Duration::from_millis(transition_time as u64 * 100);
let event_duration = Duration::from_millis_floor(remaining_time.as_millis() / steps as u64);
let startup_latency = Instant::now() - event_start_time;
loop {
let event_start_time = Instant::now();
if transition_time == 0 {
current_level = target_level;
} else {
match increasing {
true => current_level += 1,
false => current_level -= 1,
}
}
let is_transition_start = remaining_time.as_millis() == (transition_time as u64 * 100);
let is_transition_end = current_level == target_level;
debug!(
"move_to_level_transition: Setting current level: {}",
current_level
);
let (current_level, should_notify) = self.with_state(|state| {
self.set_level(state, current_level, is_transition_end, true, scene_apply)
})?;
let current_level = match current_level {
Some(level) => level,
None => return Err(ErrorCode::Failure.into()),
};
if is_transition_start || is_transition_end {
self.update_coupled_on_off(current_level, with_on_off)?;
}
if is_transition_end {
if should_notify
|| self.with_state(|state| {
state.write_remaining_time_quietly(
Duration::from_millis(0),
is_transition_start,
)
})
{
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
return Ok(());
}
match remaining_time > event_duration {
true => remaining_time -= event_duration,
false => {
warn!("remaining time is 0 before level reached target");
remaining_time = Duration::from_millis(0)
}
}
if should_notify
|| self.with_state(|state| {
state.write_remaining_time_quietly(remaining_time, is_transition_start)
})
{
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
let latency = match is_transition_start {
false => embassy_time::Instant::now() - event_start_time,
true => (embassy_time::Instant::now() - event_start_time) + startup_latency,
};
match event_duration.checked_sub(latency) {
Some(wait_time) => embassy_time::Timer::after(wait_time).await,
None => warn!("no wait time. Consider dynamically adjusting the step size?"),
}
}
}
fn move_command(
&self,
state: &mut LevelControlState,
with_on_off: bool,
move_mode: MoveModeEnum,
rate: Option<u8>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<(), Error> {
let rate = match rate {
Some(0) => return Ok(()),
Some(val) => val,
None => match state.default_move_rate.as_opt_ref() {
Some(val) => *val,
None => H::FASTEST_RATE,
},
};
if rate == 0 {
return Err(Error::new(ErrorCode::InvalidCommand));
}
if !self.should_continue(with_on_off, options_mask, options_override)? {
return Ok(());
}
if let Some(current_level) = self.hooks.current_level() {
if (current_level == H::MIN_LEVEL && move_mode == MoveModeEnum::Down)
|| (current_level == H::MAX_LEVEL && move_mode == MoveModeEnum::Up)
{
return Ok(());
}
}
let event_duration = Duration::from_hz(rate as u64);
info!("moving with rate {}", rate);
self.task_signal.signal(Task::Move {
with_on_off,
move_mode,
event_duration,
});
Ok(())
}
async fn move_transition(
&self,
ctx: impl HandlerContext,
with_on_off: bool,
move_mode: MoveModeEnum,
event_duration: Duration,
) -> Result<(), Error> {
loop {
let event_start_time = Instant::now();
let current_level = match self.hooks.current_level() {
Some(cl) => cl,
None => return Err(ErrorCode::InvalidState.into()),
};
let new_level = match move_mode {
MoveModeEnum::Up => current_level.checked_add(1),
MoveModeEnum::Down => current_level.checked_sub(1),
};
let new_level = match new_level {
Some(nl) => nl,
None => return Ok(()),
};
if current_level == H::MIN_LEVEL && new_level > H::MIN_LEVEL {
self.update_coupled_on_off(new_level, with_on_off)?;
}
let is_end_of_transition = (new_level == H::MAX_LEVEL) || (new_level == H::MIN_LEVEL);
let (new_level, should_notify) = self.with_state(|state| {
self.set_level(state, new_level, is_end_of_transition, true, false)
})?;
if should_notify {
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
let new_level = match new_level {
Some(level) => level,
None => return Err(ErrorCode::Failure.into()),
};
if is_end_of_transition {
self.update_coupled_on_off(new_level, with_on_off)?;
return Ok(());
}
let latency = embassy_time::Instant::now() - event_start_time;
match event_duration.checked_sub(latency) {
Some(wait_time) => embassy_time::Timer::after(wait_time).await,
None => warn!("no wait time. Consider dynamically adjusting the step size?"),
}
}
}
fn step(
&self,
with_on_off: bool,
step_mode: StepModeEnum,
step_size: u8,
transition_time: Option<u16>,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<(), Error> {
if step_size == 0 {
return Err(ErrorCode::InvalidCommand.into());
}
if !self.should_continue(with_on_off, options_mask, options_override)? {
return Ok(());
}
let current_level = match self.hooks.current_level() {
Some(val) => val,
None => return Err(ErrorCode::InvalidState.into()),
};
let new_level = match step_mode {
StepModeEnum::Up => current_level.saturating_add(step_size).min(H::MAX_LEVEL),
StepModeEnum::Down => current_level.saturating_sub(step_size).max(H::MIN_LEVEL),
};
let transition_time = match transition_time {
Some(val) => {
if current_level.abs_diff(new_level) != step_size {
let new_step_size = current_level.abs_diff(new_level);
val.mul(new_step_size as u16).div_euclid(step_size as u16)
} else {
val
}
}
None => 0,
};
self.move_to_level(
with_on_off,
new_level,
Some(transition_time),
options_mask,
options_override,
false,
)
}
fn stop(
&self,
ctx: impl HandlerContext,
with_on_off: bool,
options_mask: OptionsBitmap,
options_override: OptionsBitmap,
) -> Result<(), Error> {
if !self.should_continue(with_on_off, options_mask, options_override)? {
return Ok(());
}
self.task_signal.signal(Task::Stop);
if self
.with_state(|state| state.write_remaining_time_quietly(Duration::from_millis(0), false))
{
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::RemainingTime as _,
);
}
Ok(())
}
fn handle_out_of_band_message(&self, ctx: impl HandlerContext, message: OutOfBandMessage) {
self.with_state(|state| {
match message {
OutOfBandMessage::Update(current_level) => {
self.task_signal.signal(Task::Stop);
match self.set_level(state, current_level, true, false, false) {
Ok((_, should_notify)) => {
if should_notify
|| state.write_remaining_time_quietly(Duration::from_millis(0), false)
{
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::CurrentLevel as _,
);
}
}
Err(e) => {
error!("OutOfBandMessage::Update failed: set_level failed unexpectedly with set_device == false: {}", e);
}
}
}
OutOfBandMessage::MoveToLevel {
with_on_off,
level,
transition_time,
options_mask,
options_override,
} => {
if let Err(e) = self.move_to_level(
with_on_off,
level,
transition_time,
options_mask,
options_override,
false,
) {
error!(
"Device initiated MoveToLevel failed: {} | with_on_off: {}, level: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
e, with_on_off, level, transition_time, options_mask, options_override
);
}
}
OutOfBandMessage::Move {
with_on_off,
move_mode,
rate,
options_mask,
options_override,
} => {
if let Err(e) =
self.move_command(state, with_on_off, move_mode, rate, options_mask, options_override)
{
error!(
"Device initiated Move failed: {} | with_on_off: {}, move_mode: {:?}, rate: {:?}, options_mask: {:?}, options_override: {:?}",
e, with_on_off, move_mode, rate, options_mask, options_override
);
}
}
OutOfBandMessage::Step {
with_on_off,
step_mode,
step_size,
transition_time,
options_mask,
options_override,
} => {
if let Err(e) = self.step(
with_on_off,
step_mode,
step_size,
transition_time,
options_mask,
options_override,
) {
error!(
"Device initiated Step failed: {} | with_on_off: {}, step_mode: {:?}, step_size: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
e, with_on_off, step_mode, step_size, transition_time, options_mask, options_override
);
}
}
OutOfBandMessage::Stop => {
self.task_signal.signal(Task::Stop);
if state.write_remaining_time_quietly(Duration::from_millis(0), false) {
ctx.notify_attr_changed(
self.endpoint_id,
Self::CLUSTER.id,
AttributeId::RemainingTime as _,
);
}
}
}
})
}
}
impl<H: LevelControlHooks, OH: OnOffHooks> ClusterAsyncHandler for LevelControlHandler<'_, H, OH> {
const CLUSTER: Cluster<'static> = H::CLUSTER;
async fn run(&self, ctx: impl HandlerContext) -> Result<(), Error> {
let mut hooks_fut = pin!(self
.hooks
.run(|message| self.handle_out_of_band_message(&ctx, message)));
loop {
let mut task = match select(
&mut hooks_fut,
self.task_signal.wait_signalled(),
).await {
Either::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
Either::Second(task) => task,
};
loop {
match select3(
&mut hooks_fut,
self.task_manager(&ctx, task),
self.task_signal.wait_signalled(),
)
.await
{
Either3::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
Either3::Second(_) => break,
Either3::Third(new_task) => task = new_task,
};
}
}
}
fn dataver(&self) -> u32 {
self.dataver.get()
}
fn dataver_changed(&self) {
self.dataver.changed();
}
fn current_level(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u8>, Error>> {
ready(match self.hooks.current_level() {
Some(level) => Ok(Nullable::some(level)),
None => Ok(Nullable::none()),
})
}
fn on_level(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u8>, Error>> {
ready(Ok(self.with_state(|state| state.on_level.clone())))
}
fn set_on_level(
&self,
ctx: impl WriteContext,
value: Nullable<u8>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
if let Some(level) = value.clone().into_option() {
if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
break 'a Err(ErrorCode::ConstraintError.into());
}
}
self.with_state_notify(ctx, |state| {
state.on_level = value;
});
Ok(())
})
}
fn options(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<OptionsBitmap, Error>> {
ready(Ok(self.with_state(|state| state.options)))
}
fn set_options(
&self,
ctx: impl WriteContext,
value: OptionsBitmap,
) -> impl Future<Output = Result<(), Error>> {
ready({
self.with_state_notify(ctx, |state| {
state.options = value;
});
Ok(())
})
}
fn remaining_time(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
ready(Ok(self.with_state(|state| state.remaining_time)))
}
fn max_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
ready(Ok(H::MAX_LEVEL))
}
fn min_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
ready(Ok(H::MIN_LEVEL))
}
fn on_off_transition_time(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<u16, Error>> {
ready(Ok(self.with_state(|state| state.on_off_transition_time)))
}
fn set_on_off_transition_time(
&self,
ctx: impl WriteContext,
value: u16,
) -> impl Future<Output = Result<(), Error>> {
ready({
self.with_state_notify(ctx, |state| {
state.on_off_transition_time = value;
});
Ok(())
})
}
fn on_transition_time(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u16>, Error>> {
ready(Ok(self.with_state(|state| state.on_transition_time.clone())))
}
fn set_on_transition_time(
&self,
ctx: impl WriteContext,
value: Nullable<u16>,
) -> impl Future<Output = Result<(), Error>> {
ready({
self.with_state_notify(ctx, |state| {
state.on_transition_time = value;
});
Ok(())
})
}
fn off_transition_time(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u16>, Error>> {
ready(Ok(
self.with_state(|state| state.off_transition_time.clone())
))
}
fn set_off_transition_time(
&self,
ctx: impl WriteContext,
value: Nullable<u16>,
) -> impl Future<Output = Result<(), Error>> {
ready({
self.with_state_notify(ctx, |state| {
state.off_transition_time = value;
});
Ok(())
})
}
fn default_move_rate(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u8>, Error>> {
ready(Ok(self.with_state(|state| state.default_move_rate.clone())))
}
fn set_default_move_rate(
&self,
ctx: impl WriteContext,
value: Nullable<u8>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
if Some(0) == value.clone().into_option() {
break 'a Err(ErrorCode::InvalidData.into());
}
self.with_state_notify(ctx, |state| {
state.default_move_rate = value;
});
Ok(())
})
}
fn start_up_current_level(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<u8>, Error>> {
ready(match self.hooks.start_up_current_level() {
Ok(Some(val)) => Ok(Nullable::some(val)),
Ok(None) => Ok(Nullable::none()),
Err(e) => Err(e),
})
}
fn set_start_up_current_level(
&self,
ctx: impl WriteContext,
value: Nullable<u8>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
if let Some(level) = value.clone().into_option() {
if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
break 'a Err(ErrorCode::ConstraintError.into());
}
}
match self.hooks.set_start_up_current_level(value.into_option()) {
Ok(()) => {
ctx.notify_changed();
Ok(())
}
Err(e) => Err(e),
}
})
}
fn handle_move_to_level(
&self,
_ctx: impl InvokeContext,
request: MoveToLevelRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let level = match request.level() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let transition_time = match request.transition_time() {
Ok(v) => v.into_option(),
Err(e) => break 'a Err(e),
};
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.move_to_level(
false,
level,
transition_time,
options_mask,
options_override,
false,
)
})
}
fn handle_move(
&self,
_ctx: impl InvokeContext,
request: MoveRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready(self.with_state(|state| {
self.move_command(
state,
false,
request.move_mode()?,
request.rate()?.into_option(),
request.options_mask()?,
request.options_override()?,
)
}))
}
fn handle_step(
&self,
_ctx: impl InvokeContext,
request: StepRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let step_mode = match request.step_mode() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let step_size = match request.step_size() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let transition_time = match request.transition_time() {
Ok(v) => v.into_option(),
Err(e) => break 'a Err(e),
};
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.step(
false,
step_mode,
step_size,
transition_time,
options_mask,
options_override,
)
})
}
fn handle_stop(
&self,
ctx: impl InvokeContext,
request: StopRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.stop(&ctx, false, options_mask, options_override)
})
}
fn handle_move_to_level_with_on_off(
&self,
_ctx: impl InvokeContext,
request: MoveToLevelWithOnOffRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let level = match request.level() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let transition_time = match request.transition_time() {
Ok(v) => v.into_option(),
Err(e) => break 'a Err(e),
};
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.move_to_level(
true,
level,
transition_time,
options_mask,
options_override,
false,
)
})
}
fn handle_move_with_on_off(
&self,
_ctx: impl InvokeContext,
request: MoveWithOnOffRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready(self.with_state(|state| {
self.move_command(
state,
true,
request.move_mode()?,
request.rate()?.into_option(),
request.options_mask()?,
request.options_override()?,
)
}))
}
fn handle_step_with_on_off(
&self,
_ctx: impl InvokeContext,
request: StepWithOnOffRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let step_mode = match request.step_mode() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let step_size = match request.step_size() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let transition_time = match request.transition_time() {
Ok(v) => v.into_option(),
Err(e) => break 'a Err(e),
};
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.step(
true,
step_mode,
step_size,
transition_time,
options_mask,
options_override,
)
})
}
fn handle_stop_with_on_off(
&self,
ctx: impl InvokeContext,
request: StopWithOnOffRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready('a: {
let options_mask = match request.options_mask() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
let options_override = match request.options_override() {
Ok(v) => v,
Err(e) => break 'a Err(e),
};
self.stop(&ctx, true, options_mask, options_override)
})
}
fn handle_move_to_closest_frequency(
&self,
_ctx: impl InvokeContext,
_request: MoveToClosestFrequencyRequest<'_>,
) -> impl Future<Output = Result<(), Error>> {
ready(Err(ErrorCode::InvalidCommand.into()))
}
}
pub trait LevelControlHooks {
const MIN_LEVEL: u8;
const MAX_LEVEL: u8;
const FASTEST_RATE: u8;
const CLUSTER: Cluster<'static>;
#[allow(clippy::result_unit_err)]
fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()>;
fn current_level(&self) -> Option<u8>;
fn set_current_level(&self, level: Option<u8>);
fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
Err(ErrorCode::AttributeNotFound.into())
}
fn set_start_up_current_level(&self, _value: Option<u8>) -> Result<(), Error> {
Err(ErrorCode::AttributeNotFound.into())
}
fn run<F: Fn(OutOfBandMessage)>(&self, _notify: F) -> impl Future<Output = ()> {
pending::<()>()
}
}
impl<T> LevelControlHooks for &T
where
T: LevelControlHooks,
{
const MIN_LEVEL: u8 = T::MIN_LEVEL;
const MAX_LEVEL: u8 = T::MAX_LEVEL;
const FASTEST_RATE: u8 = T::FASTEST_RATE;
const CLUSTER: Cluster<'static> = T::CLUSTER;
fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
(*self).set_device_level(level)
}
fn current_level(&self) -> Option<u8> {
(*self).current_level()
}
fn set_current_level(&self, level: Option<u8>) {
(*self).set_current_level(level)
}
fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
(*self).start_up_current_level()
}
fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
(*self).set_start_up_current_level(value)
}
fn run<F: Fn(OutOfBandMessage)>(&self, notify: F) -> impl Future<Output = ()> {
(*self).run(notify)
}
}
pub struct NoOnOff;
impl OnOffHooks for NoOnOff {
const CLUSTER: Cluster<'static> = ON_OFF_FULL_CLUSTER;
fn on_off(&self) -> bool {
panic!("NoOnOff: on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
}
fn set_on_off(&self, _on: bool) {
panic!("NoOnOff: set_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
}
fn start_up_on_off(&self) -> Nullable<super::on_off::StartUpOnOffEnum> {
panic!("NoOnOff: start_up_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
}
fn set_start_up_on_off(
&self,
_value: Nullable<super::on_off::StartUpOnOffEnum>,
) -> Result<(), Error> {
panic!("NoOnOff: set_start_up_on_off called unexpectedly - this method should not be called when LevelControl is not coupled with OnOff")
}
async fn handle_off_with_effect(&self, _effect: super::on_off::EffectVariantEnum) {
panic!("NoOnOff: handle_off_with_effect called unexpectedly - this phantom type should not be used for OnOff functionality")
}
}
impl<H, OH> SceneClusterHandler for LevelControlHandler<'_, H, OH>
where
H: LevelControlHooks,
OH: OnOffHooks,
{
const CLUSTER_ID: ClusterId = FULL_CLUSTER.id;
fn endpoint_id(&self) -> EndptId {
self.endpoint_id
}
fn is_scenable_attribute(attribute_id: AttrId) -> bool {
attribute_id == AttributeId::CurrentLevel as AttrId
}
fn capture<P: TLVBuilderParent>(
&self,
avp_array: AttributeValuePairStructArrayBuilder<P>,
) -> Result<AttributeValuePairStructArrayBuilder<P>, Error> {
if let Some(level) = self.hooks.current_level() {
avp_array.push_u8(AttributeId::CurrentLevel as _, level)
} else {
Ok(avp_array)
}
}
async fn apply<C: HandlerContext>(
&self,
_ctx: &C,
avp_list: &TLVArray<'_, AttributeValuePairStruct<'_>>,
transition_time_ms: u32,
) -> Result<(), Error> {
for avp in avp_list.iter() {
let avp = avp?;
if avp.attribute_id()? != AttributeId::CurrentLevel as _ {
continue;
}
let Some(level) = avp.value_unsigned_8()? else {
continue;
};
let transition_ds = (transition_time_ms / 100).min(u16::MAX as u32) as u16;
return self.move_to_level(
false,
level,
Some(transition_ds),
OptionsBitmap::empty(),
OptionsBitmap::empty(),
true,
);
}
Ok(())
}
}
pub mod test {
use crate::dm::clusters::app::level_control::{
AttributeId, CommandId, Feature, LevelControlHooks, FULL_CLUSTER,
};
use crate::dm::Cluster;
use crate::error::Error;
use crate::utils::cell::RefCell;
use crate::utils::sync::blocking::Mutex;
use crate::with;
struct TestLevelControlState {
current_level: Option<u8>,
start_up_current_level: Option<u8>,
}
impl TestLevelControlState {
const fn new() -> Self {
Self {
current_level: Some(1),
start_up_current_level: None,
}
}
}
pub struct TestLevelControlDeviceLogic {
state: Mutex<RefCell<TestLevelControlState>>,
}
impl TestLevelControlDeviceLogic {
pub const fn new() -> Self {
Self {
state: Mutex::new(RefCell::new(TestLevelControlState::new())),
}
}
}
impl Default for TestLevelControlDeviceLogic {
fn default() -> Self {
Self::new()
}
}
impl LevelControlHooks for TestLevelControlDeviceLogic {
const MIN_LEVEL: u8 = 1;
const MAX_LEVEL: u8 = 254;
const FASTEST_RATE: u8 = 50;
const CLUSTER: Cluster<'static> = FULL_CLUSTER
.with_revision(6)
.with_features(Feature::ON_OFF.bits())
.with_attrs(with!(
required;
AttributeId::CurrentLevel
| AttributeId::MinLevel
| AttributeId::MaxLevel
| AttributeId::OnLevel
| AttributeId::Options
))
.with_cmds(with!(
CommandId::MoveToLevel
| CommandId::Move
| CommandId::Step
| CommandId::Stop
| CommandId::MoveToLevelWithOnOff
| CommandId::MoveWithOnOff
| CommandId::StepWithOnOff
| CommandId::StopWithOnOff
));
fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
Ok(Some(level))
}
fn current_level(&self) -> Option<u8> {
self.state.lock(|state| state.borrow().current_level)
}
fn set_current_level(&self, level: Option<u8>) {
info!(
"LevelControlDeviceLogic::set_current_level: setting level to {:?}",
level
);
self.state
.lock(|state| state.borrow_mut().current_level = level);
}
fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
Ok(self
.state
.lock(|state| state.borrow().start_up_current_level))
}
fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
self.state
.lock(|state| state.borrow_mut().start_up_current_level = value);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::test::TestLevelControlDeviceLogic;
use super::{AttributeDefaults, LevelControlHandler};
use crate::dm::clusters::app::on_off::test::TestOnOffDeviceLogic;
use crate::dm::clusters::app::on_off::OnOffHandler;
use crate::dm::Dataver;
#[test]
fn test_logic_passes_handler_validate() {
let level_logic = TestLevelControlDeviceLogic::new();
let on_off_logic = TestOnOffDeviceLogic::new(false);
let level = LevelControlHandler::new(
Dataver::new(1),
1,
&level_logic,
AttributeDefaults::default(),
);
let on_off = OnOffHandler::new(Dataver::new(2), 1, &on_off_logic);
on_off.init(Some(&level));
level.init(Some(&on_off));
}
}