diff --git a/matter/src/data_model/cluster_media_playback.rs b/matter/src/data_model/cluster_media_playback.rs new file mode 100644 index 0000000..556b423 --- /dev/null +++ b/matter/src/data_model/cluster_media_playback.rs @@ -0,0 +1,337 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + use super::objects::*; + use crate::{ + error::*, + interaction_model::{command::CommandReq, core::IMStatusCode}, + tlv::{TagType, TLVWriter}, + }; + use num_derive::FromPrimitive; + use chrono::{NaiveDate, DateTime}; + + pub const ID: u32 = 0x0506; + #[derive(FromPrimitive)] + pub enum Attributes { + CurrentState = 0x0, + StartTime = 0x1, + Duration = 0x2, + SampledPosition = 0x3, + PlaybackSpeed = 0x4, + SeekRangeEnd = 0x5, + SeekRangeStart = 0x6 + } + +struct ClusterCallback { + name: Commands, + callback: Box + } + + enum FeatureMap { + AdvancedSeek = 0, + VariableSpeed = 1 + } + #[derive(FromPrimitive)] + + enum PlaybackState { + Playing = 0, + Paused = 1, + NotPlaying = 2, + BUFFERING = 3 + } + #[derive(FromPrimitive)] + enum CommandStatus { + Success = 0, + InvalidStateForCommand = 1, + NotAllowed = 2, + NotActive = 3, + SpeedOutOfRange = 4, + SeekOutOfRange = 5 + } + + #[derive(FromPrimitive, PartialEq)] + pub enum Commands { + Play = 0x0, + Pause = 0x1, + Stop = 0x2, + StartOver = 0x3, + Previous = 0x4, + Next = 0x5, + Rewind = 0x6, + FastForward = 0x7, + SkipForward = 0x8, + SkipBackward = 0x9, + // Response is from us to server + PlaybackResponse = 0xa, + Seek = 0x0b + } + + struct PlaybackPosition { + updated_at: u64, + position: u64 + } +// Get microseconds since 2000, Jan 1, 00:00:00 + pub fn get_epoch_us() -> u64 { + let epoch_start = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_micro_opt(0, 0, 0, 0).unwrap().and_local_timezone(chrono::Utc).unwrap(); + DateTime::timestamp_micros(&epoch_start) as u64 + } + + pub struct MediaPlaybackCluster { + base: Cluster, + sampled_position: PlaybackPosition, + callbacks: Vec + } + + impl MediaPlaybackCluster { + pub fn new() -> Result, Error> { + let mut cluster = Box::new(MediaPlaybackCluster { + base: Cluster::new(ID)?, + sampled_position: PlaybackPosition { updated_at: 0, position: 0 }, + callbacks: vec!() + }); + + // List should be a Vec< + let attrs = [ + Attribute::new( + Attributes::CurrentState as u16, + AttrValue::Uint8(PlaybackState::NotPlaying as u8), + Access::RV, + Quality::PERSISTENT, + )?, + + // epoch-us + Attribute::new( + Attributes::StartTime as u16, + AttrValue::Uint64(0), + Access::RV, + Quality::PERSISTENT, + )?, + Attribute::new( + Attributes::Duration as u16, + AttrValue::Uint64(1), + Access::RV, + Quality::PERSISTENT, + )?, + + // Playback-Position + Attribute::new( + Attributes::SampledPosition as u16, + AttrValue::Custom, + Access::RV, + Quality::PERSISTENT, + )?, + + // Float + Attribute::new( + Attributes::PlaybackSpeed as u16, + AttrValue::Custom, + Access::RV, + Quality::PERSISTENT, + )?, + Attribute::new( + Attributes::SeekRangeEnd as u16, + AttrValue::Uint64(0), + Access::RV, + Quality::PERSISTENT, + )?, + Attribute::new( + Attributes::SeekRangeStart as u16, + AttrValue::Uint64(0), + Access::RV, + Quality::PERSISTENT, + )?, + // Options - probably want a custom type here for mapping cluster options to TLV bitmask + ]; + cluster.base.add_attributes(&attrs)?; + + // For now disable all features by default + cluster.base.set_feature_map(0)?; + Ok(cluster) + } + + pub fn add_callback(&mut self, name: Commands, callback: Box) { + self.callbacks.push(ClusterCallback { name, callback: callback}); + } + + fn run_callback(&mut self, name: Commands) { + for cmd in self.callbacks.iter_mut() { + if cmd.name == name { + (cmd.callback)() + } + } + } + + fn _set_state_buffering(&mut self)-> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + Err(IMStatusCode::Sucess) + } + + fn _set_duration(&mut self, duration: u64)-> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::Duration as u16, AttrValue::Uint64(duration))?; + Err(IMStatusCode::Sucess) + } + + // When rewinding / changing stream / etc we need to change absolute position and updateAt + fn update_position(&mut self, new_pos: u64) -> Result<(), IMStatusCode>{ + let now = get_epoch_us(); + + self.sampled_position.position = new_pos; + self.sampled_position.updated_at = now; + + Err(IMStatusCode::Sucess) + } + + fn enocde_sampled_position(&self, tag: TagType, tw: &mut TLVWriter) { + let _ = tw.start_struct(tag); + let _ = tw.u64(TagType::Context(0), self.sampled_position.position); + let _ = tw.u64(TagType::Context(1), self.sampled_position.updated_at); + let _ = tw.end_container(); + } + + } + + // Commmands + impl MediaPlaybackCluster { + fn handle_play(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.run_callback(Commands::Play); + self.send_playback_response(CommandStatus::Success); + Err(IMStatusCode::Sucess) + } + fn handle_pause(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Paused as u8))?; + + self.run_callback(Commands::Pause); + self.send_playback_response(CommandStatus::Success); + Err(IMStatusCode::Sucess) + } + fn handle_stop(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::NotPlaying as u8))?; + + self.run_callback(Commands::Stop); + self.send_playback_response(CommandStatus::Success); + Err(IMStatusCode::Sucess) + } + + // Start current thinbg over + fn handle_start_over(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.update_position(0)?; + + self.run_callback(Commands::StartOver); + self.send_playback_response(CommandStatus::Success); + Err(IMStatusCode::Sucess) + } + + fn handle_next(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + // self.update_position(0)?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + fn handle_previous(&mut self) -> Result<(), IMStatusCode>{ + + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + // self.update_position(0)?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + fn handle_rewind(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + fn handle_ff(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + fn handle_skip_forward(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + fn handle_skip_backward(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + + fn handle_seek(&mut self) -> Result<(), IMStatusCode>{ + self.base.write_attribute_raw(Attributes::CurrentState as u16, AttrValue::Uint8(PlaybackState::Playing as u8))?; + self.send_playback_response(CommandStatus::NotAllowed); + Err(IMStatusCode::UnsupportedCommand) + } + + // TODO: We send this to client + fn send_playback_response(&mut self, _status: CommandStatus) { + // Write status as u8 + // Err(IMStatusCode::Sucess) + } + + } + + + impl ClusterType for MediaPlaybackCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::SampledPosition) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + log::warn!("Encoding sampled position of self!"); + self.enocde_sampled_position(tag, tw) + })), + _ => log::error!("Attribute not supported!") + } + } + + fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + let cmd = cmd_req + .cmd + .path + .leaf + .map(num::FromPrimitive::from_u32) + .ok_or(IMStatusCode::UnsupportedCommand)? + .ok_or(IMStatusCode::UnsupportedCommand)?; + + match cmd { + Commands::Play => self.handle_play(), + Commands::Pause => self.handle_pause(), + Commands::Stop => self.handle_stop(), + Commands::StartOver => self.handle_start_over(), + Commands::Previous => self.handle_previous(), + Commands::Next => self.handle_next(), + Commands::Rewind => self.handle_rewind(), + Commands::FastForward => self.handle_ff(), + Commands::SkipForward => self.handle_skip_forward(), + Commands::SkipBackward => self.handle_skip_backward(), + Commands::PlaybackResponse => Err(IMStatusCode::InvalidCommand), + Commands::Seek => self.handle_seek(), + } + } + } \ No newline at end of file diff --git a/matter/src/data_model/mod.rs b/matter/src/data_model/mod.rs index dfc80c9..4e50dc0 100644 --- a/matter/src/data_model/mod.rs +++ b/matter/src/data_model/mod.rs @@ -21,6 +21,7 @@ pub mod objects; pub mod cluster_basic_information; pub mod cluster_on_off; +pub mod cluster_media_playback; pub mod cluster_template; pub mod sdm; pub mod system_model;