use core::cell::Cell;
use core::pin::pin;
use embassy_futures::select::{select, Either};
use embassy_time::{Duration, Instant, Timer};
use crate::dm::types::EndptId;
use crate::dm::{
Cluster, Dataver, Handler, HandlerContext, InvokeContext, InvokeReply, MatchContext,
NonBlockingHandler, ReadContext, ReadReply, WriteContext,
};
use crate::error::Error;
use crate::utils::sync::blocking::Mutex;
use crate::utils::sync::Notification;
use crate::with;
pub use crate::dm::clusters::decl::identify::*;
pub const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(crate::reexport::defmt::Format))]
pub enum IdentifyAction {
Time(u16),
Effect(EffectIdentifierEnum, EffectVariantEnum),
Cancel,
}
pub trait IdentifyHooks {
fn identify_type(&self) -> IdentifyTypeEnum;
fn identify(&self, action: IdentifyAction);
}
impl<T> IdentifyHooks for &T
where
T: IdentifyHooks,
{
fn identify_type(&self) -> IdentifyTypeEnum {
(*self).identify_type()
}
fn identify(&self, action: IdentifyAction) {
(*self).identify(action)
}
}
impl IdentifyHooks for () {
fn identify_type(&self) -> IdentifyTypeEnum {
IdentifyTypeEnum::None
}
fn identify(&self, action: IdentifyAction) {
let _ = action;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(crate::reexport::defmt::Format))]
struct Session {
endpoint_id: EndptId,
duration: u16,
start: Instant,
}
pub struct IdentifyHandler<H = ()> {
dataver: Dataver,
session: Mutex<Cell<Option<Session>>>,
state_change: Notification,
hooks: H,
}
impl IdentifyHandler<()> {
pub const fn new(dataver: Dataver) -> Self {
Self::new_with(dataver, ())
}
}
impl<H> IdentifyHandler<H>
where
H: IdentifyHooks,
{
pub const fn new_with(dataver: Dataver, hooks: H) -> Self {
Self {
dataver,
session: Mutex::new(Cell::new(None)),
state_change: Notification::new(),
hooks,
}
}
fn remaining(&self) -> u16 {
let Some(Session {
duration, start, ..
}) = self.session.lock(|cell| cell.get())
else {
return 0;
};
let elapsed = start.elapsed().as_secs();
(duration as u64).saturating_sub(elapsed) as u16
}
fn set_identify_time_internal(&self, endpoint_id: EndptId, value: u16) {
self.session.lock(|cell| {
if value == 0 {
cell.set(None);
} else {
cell.set(Some(Session {
endpoint_id,
duration: value,
start: Instant::now(),
}));
}
});
self.state_change.notify();
self.hooks.identify(if value == 0 {
IdentifyAction::Cancel
} else {
IdentifyAction::Time(value)
});
}
}
impl<H> ClusterHandler for IdentifyHandler<H>
where
H: IdentifyHooks,
{
const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
fn dataver(&self) -> u32 {
self.dataver.get()
}
fn dataver_changed(&self) {
self.dataver.changed();
}
fn identify_time(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
Ok(self.remaining())
}
fn identify_type(&self, _ctx: impl ReadContext) -> Result<IdentifyTypeEnum, Error> {
Ok(self.hooks.identify_type())
}
fn set_identify_time(&self, ctx: impl WriteContext, value: u16) -> Result<(), Error> {
self.set_identify_time_internal(ctx.attr().endpoint_id, value);
ctx.notify_changed();
Ok(())
}
fn handle_identify(
&self,
ctx: impl InvokeContext,
request: IdentifyRequest<'_>,
) -> Result<(), Error> {
let time = request.identify_time()?;
self.set_identify_time_internal(ctx.cmd().endpoint_id, time);
ctx.notify_own_attr_changed(AttributeId::IdentifyTime as _);
Ok(())
}
fn handle_trigger_effect(
&self,
_ctx: impl InvokeContext,
request: TriggerEffectRequest<'_>,
) -> Result<(), Error> {
let effect = request.effect_identifier()?;
let variant = request.effect_variant()?;
self.hooks.identify(IdentifyAction::Effect(effect, variant));
Ok(())
}
}
impl<H> Handler for IdentifyHandler<H>
where
H: IdentifyHooks,
{
fn read(&self, ctx: impl ReadContext, reply: impl ReadReply) -> Result<(), Error> {
Handler::read(&HandlerAdaptor(self), ctx, reply)
}
fn write(&self, ctx: impl WriteContext) -> Result<(), Error> {
Handler::write(&HandlerAdaptor(self), ctx)
}
fn invoke(&self, ctx: impl InvokeContext, reply: impl InvokeReply) -> Result<(), Error> {
Handler::invoke(&HandlerAdaptor(self), ctx, reply)
}
fn bump_dataver(&self, ctx: impl MatchContext) {
Handler::bump_dataver(&HandlerAdaptor(self), ctx)
}
async fn run(&self, ctx: impl HandlerContext) -> Result<(), Error> {
loop {
let Some(Session {
endpoint_id,
duration,
start,
}) = self.session.lock(|cell| cell.get())
else {
self.state_change.wait().await;
continue;
};
let deadline = start + Duration::from_secs(duration as u64);
match select(Timer::at(deadline), pin!(self.state_change.wait())).await {
Either::First(_) => {
self.session.lock(|cell| cell.set(None));
self.hooks.identify(IdentifyAction::Cancel);
ctx.notify_attr_changed(
endpoint_id,
<Self as ClusterHandler>::CLUSTER.id,
AttributeId::IdentifyTime as _,
);
}
Either::Second(_) => {
}
}
}
}
}
impl<H> NonBlockingHandler for IdentifyHandler<H> where H: IdentifyHooks {}