1use core::cell::Cell;
32use core::future::{pending, ready, Future};
33use core::ops::Mul;
34use core::pin::pin;
35
36use embassy_futures::select::{select, select3, Either, Either3};
37use embassy_time::{Duration, Instant};
38
39use crate::dm::clusters::app::on_off::{OnOffHooks, FULL_CLUSTER as ON_OFF_FULL_CLUSTER};
40use crate::dm::clusters::app::{level_control, on_off::OnOffHandler};
41pub use crate::dm::clusters::decl::level_control::*;
42use crate::dm::clusters::decl::scenes_management::{
43 AttributeValuePairStruct, AttributeValuePairStructArrayBuilder,
44};
45use crate::dm::clusters::scenes::{SceneClusterHandler, SceneInvalidator};
46use crate::dm::{
47 AttrId, Cluster, ClusterId, Dataver, EndptId, HandlerContext, InvokeContext, ReadContext,
48 WriteContext,
49};
50use crate::error::{Error, ErrorCode};
51use crate::tlv::{Nullable, TLVArray, TLVBuilderParent};
52use crate::utils::cell::RefCell;
53use crate::utils::sync::blocking::Mutex;
54use crate::utils::sync::Signal;
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub enum OutOfBandMessage {
60 Update(u8),
63 MoveToLevel {
67 with_on_off: bool,
68 level: u8,
69 transition_time: Option<u16>,
70 options_mask: OptionsBitmap,
71 options_override: OptionsBitmap,
72 },
73 Move {
77 with_on_off: bool,
78 move_mode: MoveModeEnum,
79 rate: Option<u8>,
80 options_mask: OptionsBitmap,
81 options_override: OptionsBitmap,
82 },
83 Step {
87 with_on_off: bool,
88 step_mode: StepModeEnum,
89 step_size: u8,
90 transition_time: Option<u16>,
91 options_mask: OptionsBitmap,
92 options_override: OptionsBitmap,
93 },
94 Stop,
96}
97
98enum Task {
99 MoveToLevel {
100 with_on_off: bool,
101 target: u8,
102 transition_time: u16,
103 scene_apply: bool,
107 },
108 Move {
109 with_on_off: bool,
110 move_mode: MoveModeEnum,
111 event_duration: Duration,
112 },
113 Stop,
114 OnOffStateChange {
115 on: bool,
116 },
117}
118
119struct LevelControlState {
120 on_level: Nullable<u8>,
121 options: OptionsBitmap,
122 remaining_time: u16,
123 on_off_transition_time: u16,
124 on_transition_time: Nullable<u16>,
125 off_transition_time: Nullable<u16>,
126 default_move_rate: Nullable<u8>,
127 previous_current_level: Option<u8>,
128 last_current_level_notification: Instant,
129}
130
131impl LevelControlState {
132 fn new(attribute_defaults: AttributeDefaults) -> Self {
133 Self {
134 on_level: attribute_defaults.on_level,
135 options: attribute_defaults.options,
136 remaining_time: 0,
137 on_off_transition_time: attribute_defaults.on_off_transition_time,
138 on_transition_time: attribute_defaults.on_transition_time,
139 off_transition_time: attribute_defaults.off_transition_time,
140 default_move_rate: attribute_defaults.default_move_rate,
141 previous_current_level: None,
142 last_current_level_notification: Instant::from_millis(0),
143 }
144 }
145
146 fn write_remaining_time_quietly(
153 &mut self,
154 remaining_time: Duration,
155 is_start_of_transition: bool,
156 ) -> bool {
157 let remaining_time_ds = remaining_time.as_millis().div_ceil(100) as u16;
158
159 let previous_remaining_time = self.remaining_time;
164 let changed_to_zero = remaining_time_ds == 0 && previous_remaining_time != 0;
165 let changed_from_zero_gt_10 = previous_remaining_time == 0 && remaining_time_ds > 10;
166 let changed_by_gt_10 =
167 remaining_time_ds.abs_diff(previous_remaining_time) > 10 && is_start_of_transition;
168
169 self.remaining_time = remaining_time_ds;
170
171 if changed_to_zero || changed_from_zero_gt_10 || changed_by_gt_10 {
172 return true;
173 }
174
175 false
176 }
177}
178
179pub struct LevelControlHandler<'a, H: LevelControlHooks, OH: OnOffHooks> {
198 dataver: Dataver,
199 endpoint_id: EndptId,
200 hooks: H,
201 on_off_handler: Mutex<Cell<Option<&'a OnOffHandler<'a, OH, H>>>>,
202 scene_invalidator: Mutex<Cell<Option<&'a dyn SceneInvalidator>>>,
205 state: Mutex<RefCell<LevelControlState>>,
206 task_signal: Signal<Option<Task>>,
207}
208
209#[derive(Clone, Debug, PartialEq, Eq, Hash)]
211#[cfg_attr(feature = "defmt", derive(defmt::Format))]
212pub struct AttributeDefaults {
213 pub on_level: Nullable<u8>,
214 pub options: OptionsBitmap,
215 pub on_off_transition_time: u16,
216 pub on_transition_time: Nullable<u16>,
217 pub off_transition_time: Nullable<u16>,
218 pub default_move_rate: Nullable<u8>,
219}
220
221impl AttributeDefaults {
222 pub const fn new() -> Self {
232 Self {
233 on_level: Nullable::none(),
234 options: OptionsBitmap::from_bits(0).unwrap(),
235 on_off_transition_time: 0,
236 on_transition_time: Nullable::none(),
237 off_transition_time: Nullable::none(),
238 default_move_rate: Nullable::none(),
239 }
240 }
241}
242
243impl Default for AttributeDefaults {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249impl<H: LevelControlHooks> LevelControlHandler<'_, H, NoOnOff> {
250 pub fn new_standalone(
257 dataver: Dataver,
258 endpoint_id: EndptId,
259 hooks: H,
260 attribute_defaults: AttributeDefaults,
261 ) -> Self {
262 let this = Self::new(dataver, endpoint_id, hooks, attribute_defaults);
263
264 this.init(None);
265
266 this
267 }
268}
269
270impl<'a, H: LevelControlHooks, OH: OnOffHooks> LevelControlHandler<'a, H, OH> {
271 const MAXIMUM_LEVEL: u8 = 254;
272
273 pub fn new(
281 dataver: Dataver,
282 endpoint_id: EndptId,
283 hooks: H,
284 attribute_defaults: AttributeDefaults,
285 ) -> Self {
286 Self {
287 dataver,
288 endpoint_id,
289 hooks,
290 on_off_handler: Mutex::new(Cell::new(None)),
291 scene_invalidator: Mutex::new(Cell::new(None)),
292 state: Mutex::new(RefCell::new(LevelControlState::new(attribute_defaults))),
293 task_signal: Signal::new(None),
294 }
295 }
296
297 pub fn with_scene_invalidator(self, invalidator: &'a dyn SceneInvalidator) -> Self {
303 self.scene_invalidator
304 .lock(|cell| cell.set(Some(invalidator)));
305 self
306 }
307
308 fn notify_scenable_changed(&self) {
309 if let Some(inv) = self.scene_invalidator.lock(|cell| cell.get()) {
310 inv.scenable_attribute_changed(self.endpoint_id);
311 }
312 }
313
314 fn validate(&self) {
320 if H::CLUSTER.revision != 6 {
321 panic!(
322 "LevelControl validation: incorrect version number: expected 6 got {}",
323 H::CLUSTER.revision
324 );
325 }
326
327 if H::CLUSTER
329 .attribute(AttributeId::CurrentLevel as _)
330 .is_none()
331 || H::CLUSTER.attribute(AttributeId::OnLevel as _).is_none()
332 || H::CLUSTER.attribute(AttributeId::Options as _).is_none()
333 {
334 panic!("LevelControl validation: missing required attributes: CurrentLevel, OnLevel, or Options");
335 }
336
337 if H::CLUSTER.command(CommandId::MoveToLevel as _).is_none()
339 || H::CLUSTER.command(CommandId::Move as _).is_none()
340 || H::CLUSTER.command(CommandId::Step as _).is_none()
341 || H::CLUSTER.command(CommandId::Stop as _).is_none()
342 || H::CLUSTER
343 .command(CommandId::MoveToLevelWithOnOff as _)
344 .is_none()
345 || H::CLUSTER.command(CommandId::MoveWithOnOff as _).is_none()
346 || H::CLUSTER.command(CommandId::StepWithOnOff as _).is_none()
347 || H::CLUSTER.command(CommandId::StopWithOnOff as _).is_none()
348 {
349 panic!("LevelControl validation: missing required commands: MoveToLevel, Move, Step, Stop, MoveToLevelWithOnOff, MoveWithOnOff, StepWithOnOff or StopWithOnOff");
350 }
351
352 if H::CLUSTER.feature_map & level_control::Feature::ON_OFF.bits() != 0 {
354 if self.on_off_handler.lock(|h| h.get()).is_none() {
356 panic!("LevelControl validation: a reference to the OnOff cluster must be set when the ON_OFF feature is enabled");
357 }
358 }
359
360 if H::MAX_LEVEL > Self::MAXIMUM_LEVEL {
361 panic!(
362 "LevelControl validation: the MAX_LEVEL cannot be higher than {}",
363 Self::MAXIMUM_LEVEL
364 );
365 }
366
367 if H::CLUSTER.feature_map & level_control::Feature::LIGHTING.bits() != 0 {
368 if H::MIN_LEVEL == 0 {
373 panic!("LevelControl validation: MIN_LEVEL cannot be 0 when the LIGHTING feature is enabled");
374 }
375
376 if H::CLUSTER
378 .attribute(AttributeId::RemainingTime as _)
379 .is_none()
380 || H::CLUSTER
381 .attribute(AttributeId::StartUpCurrentLevel as _)
382 .is_none()
383 {
384 panic!("LevelControl validation: the RemainingTime and StartUpCurrentLevel attributes are required by the LIGHTING feature");
385 }
386 }
387 }
388
389 pub fn init(&self, on_off_handler: Option<&'a OnOffHandler<'a, OH, H>>) {
401 self.on_off_handler.lock(|h| h.set(on_off_handler));
416
417 self.validate();
418
419 if let Ok(Some(startup_current_level)) = self.hooks.start_up_current_level() {
422 let level = if startup_current_level < H::MIN_LEVEL {
424 H::MIN_LEVEL
425 } else if startup_current_level > H::MAX_LEVEL {
426 H::MAX_LEVEL
427 } else {
428 startup_current_level
429 };
430
431 match self.hooks.set_device_level(level) {
432 Ok(current_level) => self.hooks.set_current_level(current_level),
433 Err(_) => error!("Failed to set Current Level to Start Up Current Level."),
434 }
435 }
436 }
437
438 pub const fn adapt(self) -> HandlerAsyncAdaptor<Self> {
440 HandlerAsyncAdaptor(self)
441 }
442
443 fn with_state<F, R>(&self, f: F) -> R
444 where
445 F: FnOnce(&mut LevelControlState) -> R,
446 {
447 self.state.lock(|state| {
448 let mut state = state.borrow_mut();
449
450 f(&mut state)
451 })
452 }
453
454 fn with_state_notify<F, R>(&self, ctx: impl WriteContext, f: F) -> R
455 where
456 F: FnOnce(&mut LevelControlState) -> R,
457 {
458 let result = self.with_state(f);
459
460 ctx.notify_changed();
461
462 result
463 }
464
465 fn set_level(
477 &self,
478 state: &mut LevelControlState,
479 level: u8,
480 is_end_of_transition: bool,
481 set_device: bool,
482 scene_apply: bool,
483 ) -> Result<(Option<u8>, bool), Error> {
484 state.previous_current_level = self.hooks.current_level();
486 let current_level = match set_device {
487 true => self
488 .hooks
489 .set_device_level(level)
490 .map_err(|_| ErrorCode::Failure)?,
491 false => Some(level),
492 };
493 self.hooks.set_current_level(current_level);
494 if !scene_apply {
498 self.notify_scenable_changed();
499 }
500 let last_notification = Instant::now() - state.last_current_level_notification;
501
502 if last_notification.ge(&Duration::from_secs(1))
507 || is_end_of_transition
508 || state.previous_current_level.is_none()
509 || current_level.is_none()
510 {
511 state.last_current_level_notification = Instant::now();
512 return Ok((current_level, true));
513 }
514
515 Ok((current_level, false))
516 }
517
518 fn should_continue(
528 &self,
529 with_on_off: bool,
530 options_mask: OptionsBitmap,
531 options_override: OptionsBitmap,
532 ) -> Result<bool, Error> {
533 if with_on_off {
534 return Ok(true);
535 }
536
537 let Some(on_off_handler) = self.on_off_handler.lock(|h| h.get()) else {
538 return Ok(true);
541 };
542
543 if on_off_handler.on_off() {
544 return Ok(true);
545 }
546
547 if options_mask.contains(level_control::OptionsBitmap::EXECUTE_IF_OFF) {
556 return Ok(options_override.contains(level_control::OptionsBitmap::EXECUTE_IF_OFF));
557 }
558
559 Ok(self
561 .with_state(|state| state.options)
562 .contains(level_control::OptionsBitmap::EXECUTE_IF_OFF))
563 }
564
565 async fn task_manager(&self, ctx: impl HandlerContext, task: Task) {
567 match task {
568 Task::MoveToLevel {
569 with_on_off,
570 target,
571 transition_time,
572 scene_apply,
573 } => {
574 if let Err(e) = self
575 .move_to_level_transition(
576 ctx,
577 with_on_off,
578 target,
579 transition_time,
580 scene_apply,
581 )
582 .await
583 {
584 error!("Task::MoveToLevel: {:?}", e);
585 }
586 }
587 Task::Move {
588 with_on_off,
589 move_mode,
590 event_duration,
591 } => {
592 if let Err(e) = self
593 .move_transition(ctx, with_on_off, move_mode, event_duration)
594 .await
595 {
596 error!("Task::Move: {:?}", e);
597 }
598 }
599 Task::Stop => (),
600 Task::OnOffStateChange { on } => {
601 if let Err(e) = self.handle_on_off_state_change(ctx, on).await {
602 error!("Task::OnOffStateChange: {:?}", e);
603 }
604 }
605 }
606 }
607
608 pub(crate) fn coupled_on_off_cluster_on_off_state_change(&self, on: bool) {
611 self.task_signal.signal(Task::OnOffStateChange { on });
612 }
613
614 async fn handle_on_off_state_change(
624 &self,
625 ctx: impl HandlerContext,
626 on: bool,
627 ) -> Result<(), Error> {
628 info!("handle_on_off_state_change");
629
630 let (target_level, transition_time, bitmap, temp_current_level) =
631 self.with_state(|state| {
632 let temp_current_level = self.hooks.current_level();
633
634 let bitmap = OptionsBitmap::from_bits(0).unwrap();
636
637 let mut transition_time = state.on_off_transition_time;
638
639 if on {
643 let (level, should_notify) =
646 self.set_level(state, H::MIN_LEVEL, false, true, false)?;
647 if should_notify {
648 ctx.notify_attr_changed(
649 self.endpoint_id,
650 Self::CLUSTER.id,
651 AttributeId::CurrentLevel as _,
652 );
653 }
654 if level.is_none() {
655 Err(ErrorCode::Failure)?;
656 }
657
658 let target_level = match state.on_level.as_opt_ref() {
659 Some(on_level) => *on_level,
660 None => temp_current_level.ok_or(ErrorCode::Failure)?,
661 };
662
663 if let Some(tt) = state.on_transition_time.as_opt_ref() {
669 transition_time = *tt;
670 }
671
672 Ok::<_, Error>((target_level, transition_time, bitmap, temp_current_level))
673 } else {
674 if let Some(tt) = state.off_transition_time.as_opt_ref() {
680 transition_time = *tt;
681 }
682
683 Ok((H::MIN_LEVEL, transition_time, bitmap, temp_current_level))
684 }
685 })?;
686
687 self.move_to_level_blocking(
688 &ctx,
689 true,
690 target_level,
691 Some(transition_time),
692 bitmap,
693 bitmap,
694 )
695 .await?;
696
697 if !on {
698 let restored = self.with_state(|state| {
699 if state.on_level.is_none() {
700 self.hooks.set_current_level(temp_current_level);
701 true
702 } else {
703 false
704 }
705 });
706 if restored {
707 ctx.notify_attr_changed(
709 self.endpoint_id,
710 Self::CLUSTER.id,
711 AttributeId::CurrentLevel as _,
712 );
713 }
714 }
715
716 Ok(())
717 }
718
719 fn update_coupled_on_off(&self, current_level: u8, with_on_off: bool) -> Result<(), Error> {
725 if !with_on_off {
730 return Ok(());
731 }
732
733 let new_on_off_value = current_level > H::MIN_LEVEL;
734
735 if let Some(on_off) = self.on_off_handler.lock(|h| h.get()) {
737 let current_on_off = on_off.on_off();
738 if current_on_off != new_on_off_value {
739 info!(
740 "Updating the OnOff cluster with on_off = {}",
741 new_on_off_value
742 );
743 on_off.coupled_cluster_set_on_off(new_on_off_value);
744 }
745 }
746
747 Ok(())
748 }
749
750 fn move_to_level_validation(
754 &self,
755 level: &mut u8,
756 with_on_off: bool,
757 options_mask: OptionsBitmap,
758 options_override: OptionsBitmap,
759 ) -> Result<bool, Error> {
760 if *level > Self::MAXIMUM_LEVEL {
761 return Err(ErrorCode::InvalidCommand.into());
762 }
763
764 if !self.should_continue(with_on_off, options_mask, options_override)? {
765 return Ok(false);
766 }
767
768 if *level > H::MAX_LEVEL {
769 *level = H::MAX_LEVEL;
770 debug!("target level > MAX_LEVEL. level set to MAX_LEVEL")
771 } else if *level < H::MIN_LEVEL {
772 *level = H::MIN_LEVEL;
773 debug!("target level < MIN_LEVEL. level set to MIN_LEVEL")
774 }
775
776 Ok(true)
777 }
778
779 fn move_to_level(
791 &self,
792 with_on_off: bool,
793 mut level: u8,
794 transition_time: Option<u16>,
795 options_mask: OptionsBitmap,
796 options_override: OptionsBitmap,
797 scene_apply: bool,
798 ) -> Result<(), Error> {
799 if let Ok(false) =
800 self.move_to_level_validation(&mut level, with_on_off, options_mask, options_override)
801 {
802 return Ok(());
803 }
804
805 info!(
806 "setting level to {} with transition time {:?}",
807 level, transition_time
808 );
809
810 self.task_signal.signal(Task::Stop);
813 if self.hooks.current_level() == Some(level) {
814 self.update_coupled_on_off(level, with_on_off)?;
815 return Ok(());
816 }
817
818 let t_time = transition_time.unwrap_or(0);
819
820 self.task_signal.signal(Task::MoveToLevel {
821 with_on_off,
822 target: level,
823 transition_time: t_time,
824 scene_apply,
825 });
826
827 Ok(())
828 }
829
830 async fn move_to_level_blocking(
843 &self,
844 ctx: impl HandlerContext,
845 with_on_off: bool,
846 mut level: u8,
847 transition_time: Option<u16>,
848 options_mask: OptionsBitmap,
849 options_override: OptionsBitmap,
850 ) -> Result<(), Error> {
851 if let Ok(false) =
852 self.move_to_level_validation(&mut level, with_on_off, options_mask, options_override)
853 {
854 return Ok(());
855 }
856
857 info!(
858 "setting level to {} with transition time {:?}",
859 level, transition_time
860 );
861
862 if self.hooks.current_level() == Some(level) {
863 self.update_coupled_on_off(level, with_on_off)?;
864 return Ok(());
865 }
866
867 let t_time = transition_time.unwrap_or(0);
868
869 self.move_to_level_transition(ctx, with_on_off, level, t_time, false)
871 .await?;
872
873 Ok(())
874 }
875
876 async fn move_to_level_transition(
879 &self,
880 ctx: impl HandlerContext,
881 with_on_off: bool,
882 target_level: u8,
883 transition_time: u16,
884 scene_apply: bool,
885 ) -> Result<(), Error> {
886 let event_start_time = Instant::now();
887
888 let mut current_level = match self.hooks.current_level() {
890 Some(cl) => cl,
891 None => return Err(ErrorCode::Failure.into()),
892 };
893
894 let increasing = current_level < target_level;
895
896 let steps = target_level.abs_diff(current_level);
897
898 if steps == 0 {
899 return Ok(());
900 }
901
902 let mut remaining_time = Duration::from_millis(transition_time as u64 * 100);
903 let event_duration = Duration::from_millis_floor(remaining_time.as_millis() / steps as u64);
904
905 let startup_latency = Instant::now() - event_start_time;
906 loop {
907 let event_start_time = Instant::now();
908
909 if transition_time == 0 {
910 current_level = target_level;
911 } else {
912 match increasing {
913 true => current_level += 1,
914 false => current_level -= 1,
915 }
916 }
917
918 let is_transition_start = remaining_time.as_millis() == (transition_time as u64 * 100);
919 let is_transition_end = current_level == target_level;
920
921 debug!(
922 "move_to_level_transition: Setting current level: {}",
923 current_level
924 );
925 let (current_level, should_notify) = self.with_state(|state| {
926 self.set_level(state, current_level, is_transition_end, true, scene_apply)
927 })?;
928 let current_level = match current_level {
929 Some(level) => level,
930 None => return Err(ErrorCode::Failure.into()),
931 };
932
933 if is_transition_start || is_transition_end {
934 self.update_coupled_on_off(current_level, with_on_off)?;
935 }
936
937 if is_transition_end {
938 let remaining_time_reportable = self.with_state(|state| {
942 state
943 .write_remaining_time_quietly(Duration::from_millis(0), is_transition_start)
944 });
945
946 if should_notify {
947 ctx.notify_attr_changed(
948 self.endpoint_id,
949 Self::CLUSTER.id,
950 AttributeId::CurrentLevel as _,
951 );
952 }
953
954 if remaining_time_reportable {
958 ctx.notify_attr_changed(
959 self.endpoint_id,
960 Self::CLUSTER.id,
961 AttributeId::RemainingTime as _,
962 );
963 }
964
965 return Ok(());
966 }
967
968 match remaining_time > event_duration {
969 true => remaining_time -= event_duration,
970 false => {
971 warn!("remaining time is 0 before level reached target");
972 remaining_time = Duration::from_millis(0)
973 }
974 }
975
976 let remaining_time_reportable = self.with_state(|state| {
977 state.write_remaining_time_quietly(remaining_time, is_transition_start)
978 });
979
980 if should_notify {
981 ctx.notify_attr_changed(
982 self.endpoint_id,
983 Self::CLUSTER.id,
984 AttributeId::CurrentLevel as _,
985 );
986 }
987
988 if remaining_time_reportable {
989 ctx.notify_attr_changed(
990 self.endpoint_id,
991 Self::CLUSTER.id,
992 AttributeId::RemainingTime as _,
993 );
994 }
995
996 let latency = match is_transition_start {
997 false => embassy_time::Instant::now() - event_start_time,
998 true => (embassy_time::Instant::now() - event_start_time) + startup_latency,
999 };
1000 match event_duration.checked_sub(latency) {
1001 Some(wait_time) => embassy_time::Timer::after(wait_time).await,
1002 None => warn!("no wait time. Consider dynamically adjusting the step size?"),
1003 }
1004 }
1005 }
1006
1007 fn move_command(
1009 &self,
1010 state: &mut LevelControlState,
1011 with_on_off: bool,
1012 move_mode: MoveModeEnum,
1013 rate: Option<u8>,
1014 options_mask: OptionsBitmap,
1015 options_override: OptionsBitmap,
1016 ) -> Result<(), Error> {
1017 let rate = match rate {
1024 Some(0) => return Err(ErrorCode::InvalidCommand.into()),
1030 Some(val) => val,
1031 None => match state.default_move_rate.as_opt_ref() {
1032 Some(val) => *val,
1033 None => H::FASTEST_RATE,
1034 },
1035 };
1036
1037 if rate == 0 {
1042 return Err(Error::new(ErrorCode::InvalidCommand));
1043 }
1044
1045 if !self.should_continue(with_on_off, options_mask, options_override)? {
1046 return Ok(());
1047 }
1048
1049 if let Some(current_level) = self.hooks.current_level() {
1051 if (current_level == H::MIN_LEVEL && move_mode == MoveModeEnum::Down)
1052 || (current_level == H::MAX_LEVEL && move_mode == MoveModeEnum::Up)
1053 {
1054 return Ok(());
1055 }
1056 }
1057
1058 let event_duration = Duration::from_hz(rate as u64);
1059
1060 info!("moving with rate {}", rate);
1061
1062 self.task_signal.signal(Task::Move {
1063 with_on_off,
1064 move_mode,
1065 event_duration,
1066 });
1067
1068 Ok(())
1069 }
1070
1071 async fn move_transition(
1073 &self,
1074 ctx: impl HandlerContext,
1075 with_on_off: bool,
1076 move_mode: MoveModeEnum,
1077 event_duration: Duration,
1078 ) -> Result<(), Error> {
1079 loop {
1080 let event_start_time = Instant::now();
1081
1082 let current_level = match self.hooks.current_level() {
1083 Some(cl) => cl,
1084 None => return Err(ErrorCode::InvalidState.into()),
1085 };
1086
1087 let new_level = match move_mode {
1088 MoveModeEnum::Up => current_level.checked_add(1),
1089 MoveModeEnum::Down => current_level.checked_sub(1),
1090 };
1091
1092 let new_level = match new_level {
1093 Some(nl) => nl,
1094 None => return Ok(()),
1095 };
1096
1097 if current_level == H::MIN_LEVEL && new_level > H::MIN_LEVEL {
1099 self.update_coupled_on_off(new_level, with_on_off)?;
1100 }
1101
1102 let is_end_of_transition = (new_level == H::MAX_LEVEL) || (new_level == H::MIN_LEVEL);
1103
1104 let (new_level, should_notify) = self.with_state(|state| {
1107 self.set_level(state, new_level, is_end_of_transition, true, false)
1108 })?;
1109 if should_notify {
1110 ctx.notify_attr_changed(
1111 self.endpoint_id,
1112 Self::CLUSTER.id,
1113 AttributeId::CurrentLevel as _,
1114 );
1115 }
1116 let new_level = match new_level {
1117 Some(level) => level,
1118 None => return Err(ErrorCode::Failure.into()),
1119 };
1120
1121 if is_end_of_transition {
1122 self.update_coupled_on_off(new_level, with_on_off)?;
1123 return Ok(());
1124 }
1125
1126 let latency = embassy_time::Instant::now() - event_start_time;
1127 match event_duration.checked_sub(latency) {
1128 Some(wait_time) => embassy_time::Timer::after(wait_time).await,
1129 None => warn!("no wait time. Consider dynamically adjusting the step size?"),
1130 }
1131 }
1132 }
1133
1134 fn step(
1136 &self,
1137 with_on_off: bool,
1138 step_mode: StepModeEnum,
1139 step_size: u8,
1140 transition_time: Option<u16>,
1141 options_mask: OptionsBitmap,
1142 options_override: OptionsBitmap,
1143 ) -> Result<(), Error> {
1144 if step_size == 0 {
1149 return Err(ErrorCode::InvalidCommand.into());
1150 }
1151
1152 if !self.should_continue(with_on_off, options_mask, options_override)? {
1153 return Ok(());
1154 }
1155
1156 let current_level = match self.hooks.current_level() {
1157 Some(val) => val,
1158 None => return Err(ErrorCode::InvalidState.into()),
1159 };
1160
1161 let new_level = match step_mode {
1162 StepModeEnum::Up => current_level.saturating_add(step_size).min(H::MAX_LEVEL),
1163 StepModeEnum::Down => current_level.saturating_sub(step_size).max(H::MIN_LEVEL),
1164 };
1165
1166 let transition_time = match transition_time {
1173 Some(val) => {
1174 if current_level.abs_diff(new_level) != step_size {
1175 let new_step_size = current_level.abs_diff(new_level);
1176 val.mul(new_step_size as u16).div_euclid(step_size as u16)
1177 } else {
1178 val
1179 }
1180 }
1181 None => 0,
1182 };
1183
1184 self.move_to_level(
1188 with_on_off,
1189 new_level,
1190 Some(transition_time),
1191 options_mask,
1192 options_override,
1193 false,
1194 )
1195 }
1196
1197 fn stop(
1199 &self,
1200 ctx: impl HandlerContext,
1201 with_on_off: bool,
1202 options_mask: OptionsBitmap,
1203 options_override: OptionsBitmap,
1204 ) -> Result<(), Error> {
1205 if !self.should_continue(with_on_off, options_mask, options_override)? {
1206 return Ok(());
1207 }
1208 self.task_signal.signal(Task::Stop);
1209 if self
1210 .with_state(|state| state.write_remaining_time_quietly(Duration::from_millis(0), false))
1211 {
1212 ctx.notify_attr_changed(
1213 self.endpoint_id,
1214 Self::CLUSTER.id,
1215 AttributeId::RemainingTime as _,
1216 );
1217 }
1218
1219 Ok(())
1220 }
1221
1222 fn handle_out_of_band_message(&self, ctx: impl HandlerContext, message: OutOfBandMessage) {
1223 self.with_state(|state| {
1224 match message {
1225 OutOfBandMessage::Update(current_level) => {
1226 self.task_signal.signal(Task::Stop);
1227
1228 match self.set_level(state, current_level, true, false, false) {
1231 Ok((_, should_notify)) => {
1232 let remaining_time_reportable = state
1233 .write_remaining_time_quietly(Duration::from_millis(0), false);
1234
1235 if should_notify {
1236 ctx.notify_attr_changed(
1237 self.endpoint_id,
1238 Self::CLUSTER.id,
1239 AttributeId::CurrentLevel as _,
1240 );
1241 }
1242
1243 if remaining_time_reportable {
1244 ctx.notify_attr_changed(
1245 self.endpoint_id,
1246 Self::CLUSTER.id,
1247 AttributeId::RemainingTime as _,
1248 );
1249 }
1250 }
1251 Err(e) => {
1252 error!("OutOfBandMessage::Update failed: set_level failed unexpectedly with set_device == false: {}", e);
1253 }
1254 }
1255 }
1256 OutOfBandMessage::MoveToLevel {
1257 with_on_off,
1258 level,
1259 transition_time,
1260 options_mask,
1261 options_override,
1262 } => {
1263 if let Err(e) = self.move_to_level(
1264 with_on_off,
1265 level,
1266 transition_time,
1267 options_mask,
1268 options_override,
1269 false,
1270 ) {
1271 error!(
1272 "Device initiated MoveToLevel failed: {} | with_on_off: {}, level: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
1273 e, with_on_off, level, transition_time, options_mask, options_override
1274 );
1275 }
1276 }
1277 OutOfBandMessage::Move {
1278 with_on_off,
1279 move_mode,
1280 rate,
1281 options_mask,
1282 options_override,
1283 } => {
1284 if let Err(e) =
1285 self.move_command(state, with_on_off, move_mode, rate, options_mask, options_override)
1286 {
1287 error!(
1288 "Device initiated Move failed: {} | with_on_off: {}, move_mode: {:?}, rate: {:?}, options_mask: {:?}, options_override: {:?}",
1289 e, with_on_off, move_mode, rate, options_mask, options_override
1290 );
1291 }
1292 }
1293 OutOfBandMessage::Step {
1294 with_on_off,
1295 step_mode,
1296 step_size,
1297 transition_time,
1298 options_mask,
1299 options_override,
1300 } => {
1301 if let Err(e) = self.step(
1302 with_on_off,
1303 step_mode,
1304 step_size,
1305 transition_time,
1306 options_mask,
1307 options_override,
1308 ) {
1309 error!(
1310 "Device initiated Step failed: {} | with_on_off: {}, step_mode: {:?}, step_size: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
1311 e, with_on_off, step_mode, step_size, transition_time, options_mask, options_override
1312 );
1313 }
1314 }
1315 OutOfBandMessage::Stop => {
1316 self.task_signal.signal(Task::Stop);
1317 if state.write_remaining_time_quietly(Duration::from_millis(0), false) {
1318 ctx.notify_attr_changed(
1319 self.endpoint_id,
1320 Self::CLUSTER.id,
1321 AttributeId::RemainingTime as _,
1322 );
1323 }
1324 }
1325 }
1326 })
1327 }
1328}
1329
1330impl<H: LevelControlHooks, OH: OnOffHooks> ClusterAsyncHandler for LevelControlHandler<'_, H, OH> {
1331 const CLUSTER: Cluster<'static> = H::CLUSTER;
1332
1333 async fn run(&self, ctx: impl HandlerContext) -> Result<(), Error> {
1335 let mut hooks_fut = pin!(self
1336 .hooks
1337 .run(|message| self.handle_out_of_band_message(&ctx, message)));
1338
1339 loop {
1340 let mut task = match select(
1341 &mut hooks_fut,
1342 self.task_signal.wait_signalled(),
1343 ).await {
1344 Either::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
1345 Either::Second(task) => task,
1346 };
1347
1348 loop {
1349 match select3(
1350 &mut hooks_fut,
1351 self.task_manager(&ctx, task),
1352 self.task_signal.wait_signalled(),
1353 )
1354 .await
1355 {
1356 Either3::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
1357 Either3::Second(_) => break,
1358 Either3::Third(new_task) => task = new_task,
1359 };
1360 }
1361 }
1362 }
1363
1364 fn dataver(&self) -> u32 {
1365 self.dataver.get()
1366 }
1367
1368 fn dataver_changed(&self) {
1369 self.dataver.changed();
1370 }
1371
1372 fn current_level(
1373 &self,
1374 _ctx: impl ReadContext,
1375 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1376 ready(match self.hooks.current_level() {
1377 Some(level) => Ok(Nullable::some(level)),
1378 None => Ok(Nullable::none()),
1379 })
1380 }
1381
1382 fn on_level(
1383 &self,
1384 _ctx: impl ReadContext,
1385 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1386 ready(Ok(self.with_state(|state| state.on_level.clone())))
1387 }
1388
1389 fn set_on_level(
1390 &self,
1391 ctx: impl WriteContext,
1392 value: Nullable<u8>,
1393 ) -> impl Future<Output = Result<(), Error>> {
1394 ready('a: {
1395 if let Some(level) = value.clone().into_option() {
1396 if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
1397 break 'a Err(ErrorCode::ConstraintError.into());
1398 }
1399 }
1400
1401 self.with_state_notify(ctx, |state| {
1402 state.on_level = value;
1403 });
1404
1405 Ok(())
1406 })
1407 }
1408
1409 fn options(
1410 &self,
1411 _ctx: impl ReadContext,
1412 ) -> impl Future<Output = Result<OptionsBitmap, Error>> {
1413 ready(Ok(self.with_state(|state| state.options)))
1414 }
1415
1416 fn set_options(
1417 &self,
1418 ctx: impl WriteContext,
1419 value: OptionsBitmap,
1420 ) -> impl Future<Output = Result<(), Error>> {
1421 ready({
1422 self.with_state_notify(ctx, |state| {
1423 state.options = value;
1424 });
1425
1426 Ok(())
1427 })
1428 }
1429
1430 fn remaining_time(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
1431 ready(Ok(self.with_state(|state| state.remaining_time)))
1432 }
1433
1434 fn max_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1435 ready(Ok(H::MAX_LEVEL))
1436 }
1437
1438 fn min_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1439 ready(Ok(H::MIN_LEVEL))
1440 }
1441
1442 fn on_off_transition_time(
1443 &self,
1444 _ctx: impl ReadContext,
1445 ) -> impl Future<Output = Result<u16, Error>> {
1446 ready(Ok(self.with_state(|state| state.on_off_transition_time)))
1447 }
1448
1449 fn set_on_off_transition_time(
1450 &self,
1451 ctx: impl WriteContext,
1452 value: u16,
1453 ) -> impl Future<Output = Result<(), Error>> {
1454 ready({
1455 self.with_state_notify(ctx, |state| {
1456 state.on_off_transition_time = value;
1457 });
1458
1459 Ok(())
1460 })
1461 }
1462
1463 fn on_transition_time(
1464 &self,
1465 _ctx: impl ReadContext,
1466 ) -> impl Future<Output = Result<Nullable<u16>, Error>> {
1467 ready(Ok(self.with_state(|state| state.on_transition_time.clone())))
1468 }
1469
1470 fn set_on_transition_time(
1471 &self,
1472 ctx: impl WriteContext,
1473 value: Nullable<u16>,
1474 ) -> impl Future<Output = Result<(), Error>> {
1475 ready({
1476 self.with_state_notify(ctx, |state| {
1477 state.on_transition_time = value;
1478 });
1479
1480 Ok(())
1481 })
1482 }
1483
1484 fn off_transition_time(
1485 &self,
1486 _ctx: impl ReadContext,
1487 ) -> impl Future<Output = Result<Nullable<u16>, Error>> {
1488 ready(Ok(
1489 self.with_state(|state| state.off_transition_time.clone())
1490 ))
1491 }
1492
1493 fn set_off_transition_time(
1494 &self,
1495 ctx: impl WriteContext,
1496 value: Nullable<u16>,
1497 ) -> impl Future<Output = Result<(), Error>> {
1498 ready({
1499 self.with_state_notify(ctx, |state| {
1500 state.off_transition_time = value;
1501 });
1502
1503 Ok(())
1504 })
1505 }
1506
1507 fn default_move_rate(
1508 &self,
1509 _ctx: impl ReadContext,
1510 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1511 ready(Ok(self.with_state(|state| state.default_move_rate.clone())))
1512 }
1513
1514 fn set_default_move_rate(
1515 &self,
1516 ctx: impl WriteContext,
1517 value: Nullable<u8>,
1518 ) -> impl Future<Output = Result<(), Error>> {
1519 ready('a: {
1520 if Some(0) == value.clone().into_option() {
1524 break 'a Err(ErrorCode::InvalidData.into());
1525 }
1526
1527 self.with_state_notify(ctx, |state| {
1528 state.default_move_rate = value;
1529 });
1530
1531 Ok(())
1532 })
1533 }
1534
1535 fn start_up_current_level(
1536 &self,
1537 _ctx: impl ReadContext,
1538 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1539 ready(match self.hooks.start_up_current_level() {
1540 Ok(Some(val)) => Ok(Nullable::some(val)),
1541 Ok(None) => Ok(Nullable::none()),
1542 Err(e) => Err(e),
1543 })
1544 }
1545
1546 fn set_start_up_current_level(
1547 &self,
1548 ctx: impl WriteContext,
1549 value: Nullable<u8>,
1550 ) -> impl Future<Output = Result<(), Error>> {
1551 ready('a: {
1552 if let Some(level) = value.clone().into_option() {
1555 if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
1556 break 'a Err(ErrorCode::ConstraintError.into());
1557 }
1558 }
1559
1560 match self.hooks.set_start_up_current_level(value.into_option()) {
1561 Ok(()) => {
1562 ctx.notify_changed();
1563 Ok(())
1564 }
1565 Err(e) => Err(e),
1566 }
1567 })
1568 }
1569
1570 fn handle_move_to_level(
1571 &self,
1572 _ctx: impl InvokeContext,
1573 request: MoveToLevelRequest<'_>,
1574 ) -> impl Future<Output = Result<(), Error>> {
1575 ready('a: {
1576 let level = match request.level() {
1577 Ok(v) => v,
1578 Err(e) => break 'a Err(e),
1579 };
1580 let transition_time = match request.transition_time() {
1581 Ok(v) => v.into_option(),
1582 Err(e) => break 'a Err(e),
1583 };
1584 let options_mask = match request.options_mask() {
1585 Ok(v) => v,
1586 Err(e) => break 'a Err(e),
1587 };
1588 let options_override = match request.options_override() {
1589 Ok(v) => v,
1590 Err(e) => break 'a Err(e),
1591 };
1592 self.move_to_level(
1593 false,
1594 level,
1595 transition_time,
1596 options_mask,
1597 options_override,
1598 false,
1599 )
1600 })
1601 }
1602
1603 fn handle_move(
1604 &self,
1605 _ctx: impl InvokeContext,
1606 request: MoveRequest<'_>,
1607 ) -> impl Future<Output = Result<(), Error>> {
1608 ready(self.with_state(|state| {
1609 self.move_command(
1610 state,
1611 false,
1612 request.move_mode()?,
1613 request.rate()?.into_option(),
1614 request.options_mask()?,
1615 request.options_override()?,
1616 )
1617 }))
1618 }
1619
1620 fn handle_step(
1621 &self,
1622 _ctx: impl InvokeContext,
1623 request: StepRequest<'_>,
1624 ) -> impl Future<Output = Result<(), Error>> {
1625 ready('a: {
1626 let step_mode = match request.step_mode() {
1627 Ok(v) => v,
1628 Err(e) => break 'a Err(e),
1629 };
1630 let step_size = match request.step_size() {
1631 Ok(v) => v,
1632 Err(e) => break 'a Err(e),
1633 };
1634 let transition_time = match request.transition_time() {
1635 Ok(v) => v.into_option(),
1636 Err(e) => break 'a Err(e),
1637 };
1638 let options_mask = match request.options_mask() {
1639 Ok(v) => v,
1640 Err(e) => break 'a Err(e),
1641 };
1642 let options_override = match request.options_override() {
1643 Ok(v) => v,
1644 Err(e) => break 'a Err(e),
1645 };
1646 self.step(
1647 false,
1648 step_mode,
1649 step_size,
1650 transition_time,
1651 options_mask,
1652 options_override,
1653 )
1654 })
1655 }
1656
1657 fn handle_stop(
1658 &self,
1659 ctx: impl InvokeContext,
1660 request: StopRequest<'_>,
1661 ) -> impl Future<Output = Result<(), Error>> {
1662 ready('a: {
1663 let options_mask = match request.options_mask() {
1664 Ok(v) => v,
1665 Err(e) => break 'a Err(e),
1666 };
1667 let options_override = match request.options_override() {
1668 Ok(v) => v,
1669 Err(e) => break 'a Err(e),
1670 };
1671 self.stop(&ctx, false, options_mask, options_override)
1672 })
1673 }
1674
1675 fn handle_move_to_level_with_on_off(
1676 &self,
1677 _ctx: impl InvokeContext,
1678 request: MoveToLevelWithOnOffRequest<'_>,
1679 ) -> impl Future<Output = Result<(), Error>> {
1680 ready('a: {
1681 let level = match request.level() {
1682 Ok(v) => v,
1683 Err(e) => break 'a Err(e),
1684 };
1685 let transition_time = match request.transition_time() {
1686 Ok(v) => v.into_option(),
1687 Err(e) => break 'a Err(e),
1688 };
1689 let options_mask = match request.options_mask() {
1690 Ok(v) => v,
1691 Err(e) => break 'a Err(e),
1692 };
1693 let options_override = match request.options_override() {
1694 Ok(v) => v,
1695 Err(e) => break 'a Err(e),
1696 };
1697 self.move_to_level(
1698 true,
1699 level,
1700 transition_time,
1701 options_mask,
1702 options_override,
1703 false,
1704 )
1705 })
1706 }
1707
1708 fn handle_move_with_on_off(
1709 &self,
1710 _ctx: impl InvokeContext,
1711 request: MoveWithOnOffRequest<'_>,
1712 ) -> impl Future<Output = Result<(), Error>> {
1713 ready(self.with_state(|state| {
1714 self.move_command(
1715 state,
1716 true,
1717 request.move_mode()?,
1718 request.rate()?.into_option(),
1719 request.options_mask()?,
1720 request.options_override()?,
1721 )
1722 }))
1723 }
1724
1725 fn handle_step_with_on_off(
1726 &self,
1727 _ctx: impl InvokeContext,
1728 request: StepWithOnOffRequest<'_>,
1729 ) -> impl Future<Output = Result<(), Error>> {
1730 ready('a: {
1731 let step_mode = match request.step_mode() {
1732 Ok(v) => v,
1733 Err(e) => break 'a Err(e),
1734 };
1735 let step_size = match request.step_size() {
1736 Ok(v) => v,
1737 Err(e) => break 'a Err(e),
1738 };
1739 let transition_time = match request.transition_time() {
1740 Ok(v) => v.into_option(),
1741 Err(e) => break 'a Err(e),
1742 };
1743 let options_mask = match request.options_mask() {
1744 Ok(v) => v,
1745 Err(e) => break 'a Err(e),
1746 };
1747 let options_override = match request.options_override() {
1748 Ok(v) => v,
1749 Err(e) => break 'a Err(e),
1750 };
1751 self.step(
1752 true,
1753 step_mode,
1754 step_size,
1755 transition_time,
1756 options_mask,
1757 options_override,
1758 )
1759 })
1760 }
1761
1762 fn handle_stop_with_on_off(
1763 &self,
1764 ctx: impl InvokeContext,
1765 request: StopWithOnOffRequest<'_>,
1766 ) -> impl Future<Output = Result<(), Error>> {
1767 ready('a: {
1768 let options_mask = match request.options_mask() {
1769 Ok(v) => v,
1770 Err(e) => break 'a Err(e),
1771 };
1772 let options_override = match request.options_override() {
1773 Ok(v) => v,
1774 Err(e) => break 'a Err(e),
1775 };
1776 self.stop(&ctx, true, options_mask, options_override)
1777 })
1778 }
1779
1780 fn handle_move_to_closest_frequency(
1781 &self,
1782 _ctx: impl InvokeContext,
1783 _request: MoveToClosestFrequencyRequest<'_>,
1784 ) -> impl Future<Output = Result<(), Error>> {
1785 ready(Err(ErrorCode::InvalidCommand.into()))
1786 }
1787}
1788
1789pub trait LevelControlHooks {
1790 const MIN_LEVEL: u8;
1791 const MAX_LEVEL: u8;
1792 const FASTEST_RATE: u8;
1793 const CLUSTER: Cluster<'static>;
1794
1795 #[allow(clippy::result_unit_err)]
1800 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()>;
1801
1802 fn current_level(&self) -> Option<u8>;
1810
1811 fn set_current_level(&self, level: Option<u8>);
1814
1815 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
1818 Err(ErrorCode::AttributeNotFound.into())
1819 }
1820 fn set_start_up_current_level(&self, _value: Option<u8>) -> Result<(), Error> {
1823 Err(ErrorCode::AttributeNotFound.into())
1824 }
1825
1826 fn run<F: Fn(OutOfBandMessage)>(&self, _notify: F) -> impl Future<Output = ()> {
1834 pending::<()>()
1835 }
1836}
1837
1838impl<T> LevelControlHooks for &T
1839where
1840 T: LevelControlHooks,
1841{
1842 const MIN_LEVEL: u8 = T::MIN_LEVEL;
1843 const MAX_LEVEL: u8 = T::MAX_LEVEL;
1844 const FASTEST_RATE: u8 = T::FASTEST_RATE;
1845 const CLUSTER: Cluster<'static> = T::CLUSTER;
1846
1847 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
1848 (*self).set_device_level(level)
1849 }
1850
1851 fn current_level(&self) -> Option<u8> {
1852 (*self).current_level()
1853 }
1854
1855 fn set_current_level(&self, level: Option<u8>) {
1856 (*self).set_current_level(level)
1857 }
1858
1859 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
1860 (*self).start_up_current_level()
1861 }
1862
1863 fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
1864 (*self).set_start_up_current_level(value)
1865 }
1866
1867 fn run<F: Fn(OutOfBandMessage)>(&self, notify: F) -> impl Future<Output = ()> {
1868 (*self).run(notify)
1869 }
1870}
1871
1872pub struct NoOnOff;
1876
1877impl OnOffHooks for NoOnOff {
1878 const CLUSTER: Cluster<'static> = ON_OFF_FULL_CLUSTER;
1879
1880 fn on_off(&self) -> bool {
1881 panic!("NoOnOff: on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1882 }
1883
1884 fn set_on_off(&self, _on: bool) {
1885 panic!("NoOnOff: set_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1886 }
1887
1888 fn start_up_on_off(&self) -> Nullable<super::on_off::StartUpOnOffEnum> {
1889 panic!("NoOnOff: start_up_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1890 }
1891
1892 fn set_start_up_on_off(
1893 &self,
1894 _value: Nullable<super::on_off::StartUpOnOffEnum>,
1895 ) -> Result<(), Error> {
1896 panic!("NoOnOff: set_start_up_on_off called unexpectedly - this method should not be called when LevelControl is not coupled with OnOff")
1897 }
1898
1899 async fn handle_off_with_effect(&self, _effect: super::on_off::EffectVariantEnum) {
1900 panic!("NoOnOff: handle_off_with_effect called unexpectedly - this phantom type should not be used for OnOff functionality")
1901 }
1902}
1903
1904impl<H, OH> SceneClusterHandler for LevelControlHandler<'_, H, OH>
1908where
1909 H: LevelControlHooks,
1910 OH: OnOffHooks,
1911{
1912 const CLUSTER_ID: ClusterId = FULL_CLUSTER.id;
1913
1914 fn endpoint_id(&self) -> EndptId {
1915 self.endpoint_id
1916 }
1917
1918 fn is_scenable_attribute(attribute_id: AttrId) -> bool {
1919 attribute_id == AttributeId::CurrentLevel as AttrId
1920 }
1921
1922 fn capture<P: TLVBuilderParent>(
1923 &self,
1924 avp_array: AttributeValuePairStructArrayBuilder<P>,
1925 ) -> Result<AttributeValuePairStructArrayBuilder<P>, Error> {
1926 if let Some(level) = self.hooks.current_level() {
1928 avp_array.push_u8(AttributeId::CurrentLevel as _, level)
1929 } else {
1930 Ok(avp_array)
1931 }
1932 }
1933
1934 async fn apply<C: HandlerContext>(
1935 &self,
1936 _ctx: &C,
1937 avp_list: &TLVArray<'_, AttributeValuePairStruct<'_>>,
1938 transition_time_ms: u32,
1939 ) -> Result<(), Error> {
1940 for avp in avp_list.iter() {
1941 let avp = avp?;
1942 if avp.attribute_id()? != AttributeId::CurrentLevel as _ {
1943 continue;
1944 }
1945 let Some(level) = avp.value_unsigned_8()? else {
1946 continue;
1947 };
1948 let transition_ds = (transition_time_ms / 100).min(u16::MAX as u32) as u16;
1955 return self.move_to_level(
1956 false,
1957 level,
1958 Some(transition_ds),
1959 OptionsBitmap::empty(),
1960 OptionsBitmap::empty(),
1961 true,
1962 );
1963 }
1964 Ok(())
1965 }
1966}
1967
1968pub mod test {
1969 use crate::dm::clusters::app::level_control::{
1970 AttributeId, CommandId, Feature, LevelControlHooks, FULL_CLUSTER,
1971 };
1972 use crate::dm::Cluster;
1973 use crate::error::Error;
1974 use crate::utils::cell::RefCell;
1975 use crate::utils::sync::blocking::Mutex;
1976 use crate::with;
1977
1978 struct TestLevelControlState {
1979 current_level: Option<u8>,
1980 start_up_current_level: Option<u8>,
1981 }
1982
1983 impl TestLevelControlState {
1984 const fn new() -> Self {
1985 Self {
1986 current_level: Some(1),
1987 start_up_current_level: None,
1988 }
1989 }
1990 }
1991
1992 pub struct TestLevelControlDeviceLogic {
1993 state: Mutex<RefCell<TestLevelControlState>>,
1994 }
1995
1996 impl TestLevelControlDeviceLogic {
1997 pub const fn new() -> Self {
1998 Self {
1999 state: Mutex::new(RefCell::new(TestLevelControlState::new())),
2000 }
2001 }
2002 }
2003
2004 impl Default for TestLevelControlDeviceLogic {
2005 fn default() -> Self {
2006 Self::new()
2007 }
2008 }
2009
2010 impl LevelControlHooks for TestLevelControlDeviceLogic {
2011 const MIN_LEVEL: u8 = 1;
2012 const MAX_LEVEL: u8 = 254;
2013 const FASTEST_RATE: u8 = 50;
2014 const CLUSTER: Cluster<'static> = FULL_CLUSTER
2015 .with_revision(6)
2016 .with_features(Feature::ON_OFF.bits())
2017 .with_attrs(with!(
2018 required;
2019 AttributeId::CurrentLevel
2020 | AttributeId::MinLevel
2021 | AttributeId::MaxLevel
2022 | AttributeId::OnLevel
2023 | AttributeId::Options
2024 ))
2025 .with_cmds(with!(
2026 CommandId::MoveToLevel
2027 | CommandId::Move
2028 | CommandId::Step
2029 | CommandId::Stop
2030 | CommandId::MoveToLevelWithOnOff
2031 | CommandId::MoveWithOnOff
2032 | CommandId::StepWithOnOff
2033 | CommandId::StopWithOnOff
2034 ));
2035
2036 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
2037 Ok(Some(level))
2039 }
2040
2041 fn current_level(&self) -> Option<u8> {
2042 self.state.lock(|state| state.borrow().current_level)
2043 }
2044
2045 fn set_current_level(&self, level: Option<u8>) {
2046 info!(
2047 "LevelControlDeviceLogic::set_current_level: setting level to {:?}",
2048 level
2049 );
2050 self.state
2051 .lock(|state| state.borrow_mut().current_level = level);
2052 }
2053
2054 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
2055 Ok(self
2056 .state
2057 .lock(|state| state.borrow().start_up_current_level))
2058 }
2059
2060 fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
2061 self.state
2062 .lock(|state| state.borrow_mut().start_up_current_level = value);
2063 Ok(())
2064 }
2065 }
2066}
2067
2068#[cfg(test)]
2069mod tests {
2070 use super::test::TestLevelControlDeviceLogic;
2071 use super::{AttributeDefaults, LevelControlHandler};
2072 use crate::dm::clusters::app::on_off::test::TestOnOffDeviceLogic;
2073 use crate::dm::clusters::app::on_off::OnOffHandler;
2074 use crate::dm::Dataver;
2075
2076 #[test]
2079 fn test_logic_passes_handler_validate() {
2080 let level_logic = TestLevelControlDeviceLogic::new();
2081 let on_off_logic = TestOnOffDeviceLogic::new(false);
2082 let level = LevelControlHandler::new(
2083 Dataver::new(1),
2084 1,
2085 &level_logic,
2086 AttributeDefaults::default(),
2087 );
2088 let on_off = OnOffHandler::new(Dataver::new(2), 1, &on_off_logic);
2089 on_off.init(Some(&level));
2090 level.init(Some(&on_off));
2091 }
2092}