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 if should_notify
939 || self.with_state(|state| {
940 state.write_remaining_time_quietly(
941 Duration::from_millis(0),
942 is_transition_start,
943 )
944 })
945 {
946 ctx.notify_attr_changed(
947 self.endpoint_id,
948 Self::CLUSTER.id,
949 AttributeId::CurrentLevel as _,
950 );
951 }
952 return Ok(());
953 }
954
955 match remaining_time > event_duration {
956 true => remaining_time -= event_duration,
957 false => {
958 warn!("remaining time is 0 before level reached target");
959 remaining_time = Duration::from_millis(0)
960 }
961 }
962
963 if should_notify
964 || self.with_state(|state| {
965 state.write_remaining_time_quietly(remaining_time, is_transition_start)
966 })
967 {
968 ctx.notify_attr_changed(
969 self.endpoint_id,
970 Self::CLUSTER.id,
971 AttributeId::CurrentLevel as _,
972 );
973 }
974
975 let latency = match is_transition_start {
976 false => embassy_time::Instant::now() - event_start_time,
977 true => (embassy_time::Instant::now() - event_start_time) + startup_latency,
978 };
979 match event_duration.checked_sub(latency) {
980 Some(wait_time) => embassy_time::Timer::after(wait_time).await,
981 None => warn!("no wait time. Consider dynamically adjusting the step size?"),
982 }
983 }
984 }
985
986 fn move_command(
988 &self,
989 state: &mut LevelControlState,
990 with_on_off: bool,
991 move_mode: MoveModeEnum,
992 rate: Option<u8>,
993 options_mask: OptionsBitmap,
994 options_override: OptionsBitmap,
995 ) -> Result<(), Error> {
996 let rate = match rate {
1003 Some(0) => return Ok(()),
1005 Some(val) => val,
1006 None => match state.default_move_rate.as_opt_ref() {
1007 Some(val) => *val,
1008 None => H::FASTEST_RATE,
1009 },
1010 };
1011
1012 if rate == 0 {
1017 return Err(Error::new(ErrorCode::InvalidCommand));
1018 }
1019
1020 if !self.should_continue(with_on_off, options_mask, options_override)? {
1021 return Ok(());
1022 }
1023
1024 if let Some(current_level) = self.hooks.current_level() {
1026 if (current_level == H::MIN_LEVEL && move_mode == MoveModeEnum::Down)
1027 || (current_level == H::MAX_LEVEL && move_mode == MoveModeEnum::Up)
1028 {
1029 return Ok(());
1030 }
1031 }
1032
1033 let event_duration = Duration::from_hz(rate as u64);
1034
1035 info!("moving with rate {}", rate);
1036
1037 self.task_signal.signal(Task::Move {
1038 with_on_off,
1039 move_mode,
1040 event_duration,
1041 });
1042
1043 Ok(())
1044 }
1045
1046 async fn move_transition(
1048 &self,
1049 ctx: impl HandlerContext,
1050 with_on_off: bool,
1051 move_mode: MoveModeEnum,
1052 event_duration: Duration,
1053 ) -> Result<(), Error> {
1054 loop {
1055 let event_start_time = Instant::now();
1056
1057 let current_level = match self.hooks.current_level() {
1058 Some(cl) => cl,
1059 None => return Err(ErrorCode::InvalidState.into()),
1060 };
1061
1062 let new_level = match move_mode {
1063 MoveModeEnum::Up => current_level.checked_add(1),
1064 MoveModeEnum::Down => current_level.checked_sub(1),
1065 };
1066
1067 let new_level = match new_level {
1068 Some(nl) => nl,
1069 None => return Ok(()),
1070 };
1071
1072 if current_level == H::MIN_LEVEL && new_level > H::MIN_LEVEL {
1074 self.update_coupled_on_off(new_level, with_on_off)?;
1075 }
1076
1077 let is_end_of_transition = (new_level == H::MAX_LEVEL) || (new_level == H::MIN_LEVEL);
1078
1079 let (new_level, should_notify) = self.with_state(|state| {
1082 self.set_level(state, new_level, is_end_of_transition, true, false)
1083 })?;
1084 if should_notify {
1085 ctx.notify_attr_changed(
1086 self.endpoint_id,
1087 Self::CLUSTER.id,
1088 AttributeId::CurrentLevel as _,
1089 );
1090 }
1091 let new_level = match new_level {
1092 Some(level) => level,
1093 None => return Err(ErrorCode::Failure.into()),
1094 };
1095
1096 if is_end_of_transition {
1097 self.update_coupled_on_off(new_level, with_on_off)?;
1098 return Ok(());
1099 }
1100
1101 let latency = embassy_time::Instant::now() - event_start_time;
1102 match event_duration.checked_sub(latency) {
1103 Some(wait_time) => embassy_time::Timer::after(wait_time).await,
1104 None => warn!("no wait time. Consider dynamically adjusting the step size?"),
1105 }
1106 }
1107 }
1108
1109 fn step(
1111 &self,
1112 with_on_off: bool,
1113 step_mode: StepModeEnum,
1114 step_size: u8,
1115 transition_time: Option<u16>,
1116 options_mask: OptionsBitmap,
1117 options_override: OptionsBitmap,
1118 ) -> Result<(), Error> {
1119 if step_size == 0 {
1124 return Err(ErrorCode::InvalidCommand.into());
1125 }
1126
1127 if !self.should_continue(with_on_off, options_mask, options_override)? {
1128 return Ok(());
1129 }
1130
1131 let current_level = match self.hooks.current_level() {
1132 Some(val) => val,
1133 None => return Err(ErrorCode::InvalidState.into()),
1134 };
1135
1136 let new_level = match step_mode {
1137 StepModeEnum::Up => current_level.saturating_add(step_size).min(H::MAX_LEVEL),
1138 StepModeEnum::Down => current_level.saturating_sub(step_size).max(H::MIN_LEVEL),
1139 };
1140
1141 let transition_time = match transition_time {
1148 Some(val) => {
1149 if current_level.abs_diff(new_level) != step_size {
1150 let new_step_size = current_level.abs_diff(new_level);
1151 val.mul(new_step_size as u16).div_euclid(step_size as u16)
1152 } else {
1153 val
1154 }
1155 }
1156 None => 0,
1157 };
1158
1159 self.move_to_level(
1163 with_on_off,
1164 new_level,
1165 Some(transition_time),
1166 options_mask,
1167 options_override,
1168 false,
1169 )
1170 }
1171
1172 fn stop(
1174 &self,
1175 ctx: impl HandlerContext,
1176 with_on_off: bool,
1177 options_mask: OptionsBitmap,
1178 options_override: OptionsBitmap,
1179 ) -> Result<(), Error> {
1180 if !self.should_continue(with_on_off, options_mask, options_override)? {
1181 return Ok(());
1182 }
1183 self.task_signal.signal(Task::Stop);
1184 if self
1185 .with_state(|state| state.write_remaining_time_quietly(Duration::from_millis(0), false))
1186 {
1187 ctx.notify_attr_changed(
1188 self.endpoint_id,
1189 Self::CLUSTER.id,
1190 AttributeId::RemainingTime as _,
1191 );
1192 }
1193
1194 Ok(())
1195 }
1196
1197 fn handle_out_of_band_message(&self, ctx: impl HandlerContext, message: OutOfBandMessage) {
1198 self.with_state(|state| {
1199 match message {
1200 OutOfBandMessage::Update(current_level) => {
1201 self.task_signal.signal(Task::Stop);
1202
1203 match self.set_level(state, current_level, true, false, false) {
1206 Ok((_, should_notify)) => {
1207 if should_notify
1208 || state.write_remaining_time_quietly(Duration::from_millis(0), false)
1209 {
1210 ctx.notify_attr_changed(
1211 self.endpoint_id,
1212 Self::CLUSTER.id,
1213 AttributeId::CurrentLevel as _,
1214 );
1215 }
1216 }
1217 Err(e) => {
1218 error!("OutOfBandMessage::Update failed: set_level failed unexpectedly with set_device == false: {}", e);
1219 }
1220 }
1221 }
1222 OutOfBandMessage::MoveToLevel {
1223 with_on_off,
1224 level,
1225 transition_time,
1226 options_mask,
1227 options_override,
1228 } => {
1229 if let Err(e) = self.move_to_level(
1230 with_on_off,
1231 level,
1232 transition_time,
1233 options_mask,
1234 options_override,
1235 false,
1236 ) {
1237 error!(
1238 "Device initiated MoveToLevel failed: {} | with_on_off: {}, level: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
1239 e, with_on_off, level, transition_time, options_mask, options_override
1240 );
1241 }
1242 }
1243 OutOfBandMessage::Move {
1244 with_on_off,
1245 move_mode,
1246 rate,
1247 options_mask,
1248 options_override,
1249 } => {
1250 if let Err(e) =
1251 self.move_command(state, with_on_off, move_mode, rate, options_mask, options_override)
1252 {
1253 error!(
1254 "Device initiated Move failed: {} | with_on_off: {}, move_mode: {:?}, rate: {:?}, options_mask: {:?}, options_override: {:?}",
1255 e, with_on_off, move_mode, rate, options_mask, options_override
1256 );
1257 }
1258 }
1259 OutOfBandMessage::Step {
1260 with_on_off,
1261 step_mode,
1262 step_size,
1263 transition_time,
1264 options_mask,
1265 options_override,
1266 } => {
1267 if let Err(e) = self.step(
1268 with_on_off,
1269 step_mode,
1270 step_size,
1271 transition_time,
1272 options_mask,
1273 options_override,
1274 ) {
1275 error!(
1276 "Device initiated Step failed: {} | with_on_off: {}, step_mode: {:?}, step_size: {}, transition_time: {:?}, options_mask: {:?}, options_override: {:?}",
1277 e, with_on_off, step_mode, step_size, transition_time, options_mask, options_override
1278 );
1279 }
1280 }
1281 OutOfBandMessage::Stop => {
1282 self.task_signal.signal(Task::Stop);
1283 if state.write_remaining_time_quietly(Duration::from_millis(0), false) {
1284 ctx.notify_attr_changed(
1285 self.endpoint_id,
1286 Self::CLUSTER.id,
1287 AttributeId::RemainingTime as _,
1288 );
1289 }
1290 }
1291 }
1292 })
1293 }
1294}
1295
1296impl<H: LevelControlHooks, OH: OnOffHooks> ClusterAsyncHandler for LevelControlHandler<'_, H, OH> {
1297 const CLUSTER: Cluster<'static> = H::CLUSTER;
1298
1299 async fn run(&self, ctx: impl HandlerContext) -> Result<(), Error> {
1301 let mut hooks_fut = pin!(self
1302 .hooks
1303 .run(|message| self.handle_out_of_band_message(&ctx, message)));
1304
1305 loop {
1306 let mut task = match select(
1307 &mut hooks_fut,
1308 self.task_signal.wait_signalled(),
1309 ).await {
1310 Either::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
1311 Either::Second(task) => task,
1312 };
1313
1314 loop {
1315 match select3(
1316 &mut hooks_fut,
1317 self.task_manager(&ctx, task),
1318 self.task_signal.wait_signalled(),
1319 )
1320 .await
1321 {
1322 Either3::First(_) => panic!("LevelControlHooks::run returned; implementers MUST not return. Implementations should loop forever or await core::future::pending::<()>()."),
1323 Either3::Second(_) => break,
1324 Either3::Third(new_task) => task = new_task,
1325 };
1326 }
1327 }
1328 }
1329
1330 fn dataver(&self) -> u32 {
1331 self.dataver.get()
1332 }
1333
1334 fn dataver_changed(&self) {
1335 self.dataver.changed();
1336 }
1337
1338 fn current_level(
1339 &self,
1340 _ctx: impl ReadContext,
1341 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1342 ready(match self.hooks.current_level() {
1343 Some(level) => Ok(Nullable::some(level)),
1344 None => Ok(Nullable::none()),
1345 })
1346 }
1347
1348 fn on_level(
1349 &self,
1350 _ctx: impl ReadContext,
1351 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1352 ready(Ok(self.with_state(|state| state.on_level.clone())))
1353 }
1354
1355 fn set_on_level(
1356 &self,
1357 ctx: impl WriteContext,
1358 value: Nullable<u8>,
1359 ) -> impl Future<Output = Result<(), Error>> {
1360 ready('a: {
1361 if let Some(level) = value.clone().into_option() {
1362 if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
1363 break 'a Err(ErrorCode::ConstraintError.into());
1364 }
1365 }
1366
1367 self.with_state_notify(ctx, |state| {
1368 state.on_level = value;
1369 });
1370
1371 Ok(())
1372 })
1373 }
1374
1375 fn options(
1376 &self,
1377 _ctx: impl ReadContext,
1378 ) -> impl Future<Output = Result<OptionsBitmap, Error>> {
1379 ready(Ok(self.with_state(|state| state.options)))
1380 }
1381
1382 fn set_options(
1383 &self,
1384 ctx: impl WriteContext,
1385 value: OptionsBitmap,
1386 ) -> impl Future<Output = Result<(), Error>> {
1387 ready({
1388 self.with_state_notify(ctx, |state| {
1389 state.options = value;
1390 });
1391
1392 Ok(())
1393 })
1394 }
1395
1396 fn remaining_time(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
1397 ready(Ok(self.with_state(|state| state.remaining_time)))
1398 }
1399
1400 fn max_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1401 ready(Ok(H::MAX_LEVEL))
1402 }
1403
1404 fn min_level(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1405 ready(Ok(H::MIN_LEVEL))
1406 }
1407
1408 fn on_off_transition_time(
1409 &self,
1410 _ctx: impl ReadContext,
1411 ) -> impl Future<Output = Result<u16, Error>> {
1412 ready(Ok(self.with_state(|state| state.on_off_transition_time)))
1413 }
1414
1415 fn set_on_off_transition_time(
1416 &self,
1417 ctx: impl WriteContext,
1418 value: u16,
1419 ) -> impl Future<Output = Result<(), Error>> {
1420 ready({
1421 self.with_state_notify(ctx, |state| {
1422 state.on_off_transition_time = value;
1423 });
1424
1425 Ok(())
1426 })
1427 }
1428
1429 fn on_transition_time(
1430 &self,
1431 _ctx: impl ReadContext,
1432 ) -> impl Future<Output = Result<Nullable<u16>, Error>> {
1433 ready(Ok(self.with_state(|state| state.on_transition_time.clone())))
1434 }
1435
1436 fn set_on_transition_time(
1437 &self,
1438 ctx: impl WriteContext,
1439 value: Nullable<u16>,
1440 ) -> impl Future<Output = Result<(), Error>> {
1441 ready({
1442 self.with_state_notify(ctx, |state| {
1443 state.on_transition_time = value;
1444 });
1445
1446 Ok(())
1447 })
1448 }
1449
1450 fn off_transition_time(
1451 &self,
1452 _ctx: impl ReadContext,
1453 ) -> impl Future<Output = Result<Nullable<u16>, Error>> {
1454 ready(Ok(
1455 self.with_state(|state| state.off_transition_time.clone())
1456 ))
1457 }
1458
1459 fn set_off_transition_time(
1460 &self,
1461 ctx: impl WriteContext,
1462 value: Nullable<u16>,
1463 ) -> impl Future<Output = Result<(), Error>> {
1464 ready({
1465 self.with_state_notify(ctx, |state| {
1466 state.off_transition_time = value;
1467 });
1468
1469 Ok(())
1470 })
1471 }
1472
1473 fn default_move_rate(
1474 &self,
1475 _ctx: impl ReadContext,
1476 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1477 ready(Ok(self.with_state(|state| state.default_move_rate.clone())))
1478 }
1479
1480 fn set_default_move_rate(
1481 &self,
1482 ctx: impl WriteContext,
1483 value: Nullable<u8>,
1484 ) -> impl Future<Output = Result<(), Error>> {
1485 ready('a: {
1486 if Some(0) == value.clone().into_option() {
1490 break 'a Err(ErrorCode::InvalidData.into());
1491 }
1492
1493 self.with_state_notify(ctx, |state| {
1494 state.default_move_rate = value;
1495 });
1496
1497 Ok(())
1498 })
1499 }
1500
1501 fn start_up_current_level(
1502 &self,
1503 _ctx: impl ReadContext,
1504 ) -> impl Future<Output = Result<Nullable<u8>, Error>> {
1505 ready(match self.hooks.start_up_current_level() {
1506 Ok(Some(val)) => Ok(Nullable::some(val)),
1507 Ok(None) => Ok(Nullable::none()),
1508 Err(e) => Err(e),
1509 })
1510 }
1511
1512 fn set_start_up_current_level(
1513 &self,
1514 ctx: impl WriteContext,
1515 value: Nullable<u8>,
1516 ) -> impl Future<Output = Result<(), Error>> {
1517 ready('a: {
1518 if let Some(level) = value.clone().into_option() {
1521 if level > H::MAX_LEVEL || level < H::MIN_LEVEL {
1522 break 'a Err(ErrorCode::ConstraintError.into());
1523 }
1524 }
1525
1526 match self.hooks.set_start_up_current_level(value.into_option()) {
1527 Ok(()) => {
1528 ctx.notify_changed();
1529 Ok(())
1530 }
1531 Err(e) => Err(e),
1532 }
1533 })
1534 }
1535
1536 fn handle_move_to_level(
1537 &self,
1538 _ctx: impl InvokeContext,
1539 request: MoveToLevelRequest<'_>,
1540 ) -> impl Future<Output = Result<(), Error>> {
1541 ready('a: {
1542 let level = match request.level() {
1543 Ok(v) => v,
1544 Err(e) => break 'a Err(e),
1545 };
1546 let transition_time = match request.transition_time() {
1547 Ok(v) => v.into_option(),
1548 Err(e) => break 'a Err(e),
1549 };
1550 let options_mask = match request.options_mask() {
1551 Ok(v) => v,
1552 Err(e) => break 'a Err(e),
1553 };
1554 let options_override = match request.options_override() {
1555 Ok(v) => v,
1556 Err(e) => break 'a Err(e),
1557 };
1558 self.move_to_level(
1559 false,
1560 level,
1561 transition_time,
1562 options_mask,
1563 options_override,
1564 false,
1565 )
1566 })
1567 }
1568
1569 fn handle_move(
1570 &self,
1571 _ctx: impl InvokeContext,
1572 request: MoveRequest<'_>,
1573 ) -> impl Future<Output = Result<(), Error>> {
1574 ready(self.with_state(|state| {
1575 self.move_command(
1576 state,
1577 false,
1578 request.move_mode()?,
1579 request.rate()?.into_option(),
1580 request.options_mask()?,
1581 request.options_override()?,
1582 )
1583 }))
1584 }
1585
1586 fn handle_step(
1587 &self,
1588 _ctx: impl InvokeContext,
1589 request: StepRequest<'_>,
1590 ) -> impl Future<Output = Result<(), Error>> {
1591 ready('a: {
1592 let step_mode = match request.step_mode() {
1593 Ok(v) => v,
1594 Err(e) => break 'a Err(e),
1595 };
1596 let step_size = match request.step_size() {
1597 Ok(v) => v,
1598 Err(e) => break 'a Err(e),
1599 };
1600 let transition_time = match request.transition_time() {
1601 Ok(v) => v.into_option(),
1602 Err(e) => break 'a Err(e),
1603 };
1604 let options_mask = match request.options_mask() {
1605 Ok(v) => v,
1606 Err(e) => break 'a Err(e),
1607 };
1608 let options_override = match request.options_override() {
1609 Ok(v) => v,
1610 Err(e) => break 'a Err(e),
1611 };
1612 self.step(
1613 false,
1614 step_mode,
1615 step_size,
1616 transition_time,
1617 options_mask,
1618 options_override,
1619 )
1620 })
1621 }
1622
1623 fn handle_stop(
1624 &self,
1625 ctx: impl InvokeContext,
1626 request: StopRequest<'_>,
1627 ) -> impl Future<Output = Result<(), Error>> {
1628 ready('a: {
1629 let options_mask = match request.options_mask() {
1630 Ok(v) => v,
1631 Err(e) => break 'a Err(e),
1632 };
1633 let options_override = match request.options_override() {
1634 Ok(v) => v,
1635 Err(e) => break 'a Err(e),
1636 };
1637 self.stop(&ctx, false, options_mask, options_override)
1638 })
1639 }
1640
1641 fn handle_move_to_level_with_on_off(
1642 &self,
1643 _ctx: impl InvokeContext,
1644 request: MoveToLevelWithOnOffRequest<'_>,
1645 ) -> impl Future<Output = Result<(), Error>> {
1646 ready('a: {
1647 let level = match request.level() {
1648 Ok(v) => v,
1649 Err(e) => break 'a Err(e),
1650 };
1651 let transition_time = match request.transition_time() {
1652 Ok(v) => v.into_option(),
1653 Err(e) => break 'a Err(e),
1654 };
1655 let options_mask = match request.options_mask() {
1656 Ok(v) => v,
1657 Err(e) => break 'a Err(e),
1658 };
1659 let options_override = match request.options_override() {
1660 Ok(v) => v,
1661 Err(e) => break 'a Err(e),
1662 };
1663 self.move_to_level(
1664 true,
1665 level,
1666 transition_time,
1667 options_mask,
1668 options_override,
1669 false,
1670 )
1671 })
1672 }
1673
1674 fn handle_move_with_on_off(
1675 &self,
1676 _ctx: impl InvokeContext,
1677 request: MoveWithOnOffRequest<'_>,
1678 ) -> impl Future<Output = Result<(), Error>> {
1679 ready(self.with_state(|state| {
1680 self.move_command(
1681 state,
1682 true,
1683 request.move_mode()?,
1684 request.rate()?.into_option(),
1685 request.options_mask()?,
1686 request.options_override()?,
1687 )
1688 }))
1689 }
1690
1691 fn handle_step_with_on_off(
1692 &self,
1693 _ctx: impl InvokeContext,
1694 request: StepWithOnOffRequest<'_>,
1695 ) -> impl Future<Output = Result<(), Error>> {
1696 ready('a: {
1697 let step_mode = match request.step_mode() {
1698 Ok(v) => v,
1699 Err(e) => break 'a Err(e),
1700 };
1701 let step_size = match request.step_size() {
1702 Ok(v) => v,
1703 Err(e) => break 'a Err(e),
1704 };
1705 let transition_time = match request.transition_time() {
1706 Ok(v) => v.into_option(),
1707 Err(e) => break 'a Err(e),
1708 };
1709 let options_mask = match request.options_mask() {
1710 Ok(v) => v,
1711 Err(e) => break 'a Err(e),
1712 };
1713 let options_override = match request.options_override() {
1714 Ok(v) => v,
1715 Err(e) => break 'a Err(e),
1716 };
1717 self.step(
1718 true,
1719 step_mode,
1720 step_size,
1721 transition_time,
1722 options_mask,
1723 options_override,
1724 )
1725 })
1726 }
1727
1728 fn handle_stop_with_on_off(
1729 &self,
1730 ctx: impl InvokeContext,
1731 request: StopWithOnOffRequest<'_>,
1732 ) -> impl Future<Output = Result<(), Error>> {
1733 ready('a: {
1734 let options_mask = match request.options_mask() {
1735 Ok(v) => v,
1736 Err(e) => break 'a Err(e),
1737 };
1738 let options_override = match request.options_override() {
1739 Ok(v) => v,
1740 Err(e) => break 'a Err(e),
1741 };
1742 self.stop(&ctx, true, options_mask, options_override)
1743 })
1744 }
1745
1746 fn handle_move_to_closest_frequency(
1747 &self,
1748 _ctx: impl InvokeContext,
1749 _request: MoveToClosestFrequencyRequest<'_>,
1750 ) -> impl Future<Output = Result<(), Error>> {
1751 ready(Err(ErrorCode::InvalidCommand.into()))
1752 }
1753}
1754
1755pub trait LevelControlHooks {
1756 const MIN_LEVEL: u8;
1757 const MAX_LEVEL: u8;
1758 const FASTEST_RATE: u8;
1759 const CLUSTER: Cluster<'static>;
1760
1761 #[allow(clippy::result_unit_err)]
1766 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()>;
1767
1768 fn current_level(&self) -> Option<u8>;
1776
1777 fn set_current_level(&self, level: Option<u8>);
1780
1781 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
1784 Err(ErrorCode::AttributeNotFound.into())
1785 }
1786 fn set_start_up_current_level(&self, _value: Option<u8>) -> Result<(), Error> {
1789 Err(ErrorCode::AttributeNotFound.into())
1790 }
1791
1792 fn run<F: Fn(OutOfBandMessage)>(&self, _notify: F) -> impl Future<Output = ()> {
1800 pending::<()>()
1801 }
1802}
1803
1804impl<T> LevelControlHooks for &T
1805where
1806 T: LevelControlHooks,
1807{
1808 const MIN_LEVEL: u8 = T::MIN_LEVEL;
1809 const MAX_LEVEL: u8 = T::MAX_LEVEL;
1810 const FASTEST_RATE: u8 = T::FASTEST_RATE;
1811 const CLUSTER: Cluster<'static> = T::CLUSTER;
1812
1813 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
1814 (*self).set_device_level(level)
1815 }
1816
1817 fn current_level(&self) -> Option<u8> {
1818 (*self).current_level()
1819 }
1820
1821 fn set_current_level(&self, level: Option<u8>) {
1822 (*self).set_current_level(level)
1823 }
1824
1825 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
1826 (*self).start_up_current_level()
1827 }
1828
1829 fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
1830 (*self).set_start_up_current_level(value)
1831 }
1832
1833 fn run<F: Fn(OutOfBandMessage)>(&self, notify: F) -> impl Future<Output = ()> {
1834 (*self).run(notify)
1835 }
1836}
1837
1838pub struct NoOnOff;
1842
1843impl OnOffHooks for NoOnOff {
1844 const CLUSTER: Cluster<'static> = ON_OFF_FULL_CLUSTER;
1845
1846 fn on_off(&self) -> bool {
1847 panic!("NoOnOff: on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1848 }
1849
1850 fn set_on_off(&self, _on: bool) {
1851 panic!("NoOnOff: set_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1852 }
1853
1854 fn start_up_on_off(&self) -> Nullable<super::on_off::StartUpOnOffEnum> {
1855 panic!("NoOnOff: start_up_on_off called unexpectedly - this phantom type should not be used for OnOff functionality")
1856 }
1857
1858 fn set_start_up_on_off(
1859 &self,
1860 _value: Nullable<super::on_off::StartUpOnOffEnum>,
1861 ) -> Result<(), Error> {
1862 panic!("NoOnOff: set_start_up_on_off called unexpectedly - this method should not be called when LevelControl is not coupled with OnOff")
1863 }
1864
1865 async fn handle_off_with_effect(&self, _effect: super::on_off::EffectVariantEnum) {
1866 panic!("NoOnOff: handle_off_with_effect called unexpectedly - this phantom type should not be used for OnOff functionality")
1867 }
1868}
1869
1870impl<H, OH> SceneClusterHandler for LevelControlHandler<'_, H, OH>
1874where
1875 H: LevelControlHooks,
1876 OH: OnOffHooks,
1877{
1878 const CLUSTER_ID: ClusterId = FULL_CLUSTER.id;
1879
1880 fn endpoint_id(&self) -> EndptId {
1881 self.endpoint_id
1882 }
1883
1884 fn is_scenable_attribute(attribute_id: AttrId) -> bool {
1885 attribute_id == AttributeId::CurrentLevel as AttrId
1886 }
1887
1888 fn capture<P: TLVBuilderParent>(
1889 &self,
1890 avp_array: AttributeValuePairStructArrayBuilder<P>,
1891 ) -> Result<AttributeValuePairStructArrayBuilder<P>, Error> {
1892 if let Some(level) = self.hooks.current_level() {
1894 avp_array.push_u8(AttributeId::CurrentLevel as _, level)
1895 } else {
1896 Ok(avp_array)
1897 }
1898 }
1899
1900 async fn apply<C: HandlerContext>(
1901 &self,
1902 _ctx: &C,
1903 avp_list: &TLVArray<'_, AttributeValuePairStruct<'_>>,
1904 transition_time_ms: u32,
1905 ) -> Result<(), Error> {
1906 for avp in avp_list.iter() {
1907 let avp = avp?;
1908 if avp.attribute_id()? != AttributeId::CurrentLevel as _ {
1909 continue;
1910 }
1911 let Some(level) = avp.value_unsigned_8()? else {
1912 continue;
1913 };
1914 let transition_ds = (transition_time_ms / 100).min(u16::MAX as u32) as u16;
1921 return self.move_to_level(
1922 false,
1923 level,
1924 Some(transition_ds),
1925 OptionsBitmap::empty(),
1926 OptionsBitmap::empty(),
1927 true,
1928 );
1929 }
1930 Ok(())
1931 }
1932}
1933
1934pub mod test {
1935 use crate::dm::clusters::app::level_control::{
1936 AttributeId, CommandId, Feature, LevelControlHooks, FULL_CLUSTER,
1937 };
1938 use crate::dm::Cluster;
1939 use crate::error::Error;
1940 use crate::utils::cell::RefCell;
1941 use crate::utils::sync::blocking::Mutex;
1942 use crate::with;
1943
1944 struct TestLevelControlState {
1945 current_level: Option<u8>,
1946 start_up_current_level: Option<u8>,
1947 }
1948
1949 impl TestLevelControlState {
1950 const fn new() -> Self {
1951 Self {
1952 current_level: Some(1),
1953 start_up_current_level: None,
1954 }
1955 }
1956 }
1957
1958 pub struct TestLevelControlDeviceLogic {
1959 state: Mutex<RefCell<TestLevelControlState>>,
1960 }
1961
1962 impl TestLevelControlDeviceLogic {
1963 pub const fn new() -> Self {
1964 Self {
1965 state: Mutex::new(RefCell::new(TestLevelControlState::new())),
1966 }
1967 }
1968 }
1969
1970 impl Default for TestLevelControlDeviceLogic {
1971 fn default() -> Self {
1972 Self::new()
1973 }
1974 }
1975
1976 impl LevelControlHooks for TestLevelControlDeviceLogic {
1977 const MIN_LEVEL: u8 = 1;
1978 const MAX_LEVEL: u8 = 254;
1979 const FASTEST_RATE: u8 = 50;
1980 const CLUSTER: Cluster<'static> = FULL_CLUSTER
1981 .with_revision(6)
1982 .with_features(Feature::ON_OFF.bits())
1983 .with_attrs(with!(
1984 required;
1985 AttributeId::CurrentLevel
1986 | AttributeId::MinLevel
1987 | AttributeId::MaxLevel
1988 | AttributeId::OnLevel
1989 | AttributeId::Options
1990 ))
1991 .with_cmds(with!(
1992 CommandId::MoveToLevel
1993 | CommandId::Move
1994 | CommandId::Step
1995 | CommandId::Stop
1996 | CommandId::MoveToLevelWithOnOff
1997 | CommandId::MoveWithOnOff
1998 | CommandId::StepWithOnOff
1999 | CommandId::StopWithOnOff
2000 ));
2001
2002 fn set_device_level(&self, level: u8) -> Result<Option<u8>, ()> {
2003 Ok(Some(level))
2005 }
2006
2007 fn current_level(&self) -> Option<u8> {
2008 self.state.lock(|state| state.borrow().current_level)
2009 }
2010
2011 fn set_current_level(&self, level: Option<u8>) {
2012 info!(
2013 "LevelControlDeviceLogic::set_current_level: setting level to {:?}",
2014 level
2015 );
2016 self.state
2017 .lock(|state| state.borrow_mut().current_level = level);
2018 }
2019
2020 fn start_up_current_level(&self) -> Result<Option<u8>, Error> {
2021 Ok(self
2022 .state
2023 .lock(|state| state.borrow().start_up_current_level))
2024 }
2025
2026 fn set_start_up_current_level(&self, value: Option<u8>) -> Result<(), Error> {
2027 self.state
2028 .lock(|state| state.borrow_mut().start_up_current_level = value);
2029 Ok(())
2030 }
2031 }
2032}
2033
2034#[cfg(test)]
2035mod tests {
2036 use super::test::TestLevelControlDeviceLogic;
2037 use super::{AttributeDefaults, LevelControlHandler};
2038 use crate::dm::clusters::app::on_off::test::TestOnOffDeviceLogic;
2039 use crate::dm::clusters::app::on_off::OnOffHandler;
2040 use crate::dm::Dataver;
2041
2042 #[test]
2045 fn test_logic_passes_handler_validate() {
2046 let level_logic = TestLevelControlDeviceLogic::new();
2047 let on_off_logic = TestOnOffDeviceLogic::new(false);
2048 let level = LevelControlHandler::new(
2049 Dataver::new(1),
2050 1,
2051 &level_logic,
2052 AttributeDefaults::default(),
2053 );
2054 let on_off = OnOffHandler::new(Dataver::new(2), 1, &on_off_logic);
2055 on_off.init(Some(&level));
2056 level.init(Some(&on_off));
2057 }
2058}