Handle events for the media player entity specified
This commit is contained in:
parent
f3bc613b31
commit
ae1a579aa2
1 changed files with 151 additions and 18 deletions
|
@ -2,16 +2,18 @@ use async_tungstenite::{tokio::connect_async, tungstenite};
|
|||
use color_eyre::{eyre::eyre, Result};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
pub(crate) struct HomeAssistant {
|
||||
token: String,
|
||||
entity: String,
|
||||
insecure: bool,
|
||||
host: String,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct Message {
|
||||
#[serde(rename = "type")]
|
||||
message_type: MessageType,
|
||||
|
@ -21,6 +23,47 @@ struct Message {
|
|||
access_token: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
message: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
event_type: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
success: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
event: Option<Event>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct Event {
|
||||
data: EventData,
|
||||
time_fired: String,
|
||||
origin: String,
|
||||
context: EventContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct EventData {
|
||||
new_state: EntityState,
|
||||
old_state: EntityState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct EventContext {
|
||||
id: String,
|
||||
parent_id: Option<String>,
|
||||
user_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct EntityState {
|
||||
entity_id: String,
|
||||
last_changed: String,
|
||||
state: String,
|
||||
attributes: Value,
|
||||
last_updated: String,
|
||||
context: EventContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
|
@ -48,14 +91,37 @@ enum MessageType {
|
|||
ValidateConfig,
|
||||
}
|
||||
|
||||
impl Default for Message {
|
||||
impl Default for MessageType {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
message_type: MessageType::Pong,
|
||||
ha_version: Default::default(),
|
||||
access_token: Default::default(),
|
||||
message: Default::default(),
|
||||
}
|
||||
MessageType::Pong
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn auth() -> Self {
|
||||
let mut msg = Self::default();
|
||||
msg.message_type = MessageType::Auth;
|
||||
msg
|
||||
}
|
||||
|
||||
fn subscribe_events() -> Self {
|
||||
let mut msg = Self::default();
|
||||
msg.message_type = MessageType::SubscribeEvents;
|
||||
msg
|
||||
}
|
||||
|
||||
fn get_states() -> Self {
|
||||
let mut msg = Self::default();
|
||||
msg.message_type = MessageType::GetStates;
|
||||
msg
|
||||
}
|
||||
|
||||
fn to_json(self) -> String {
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
|
||||
fn to_message(self) -> tungstenite::Message {
|
||||
tungstenite::Message::from(self.to_json())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,10 +132,17 @@ impl HomeAssistant {
|
|||
entity,
|
||||
insecure,
|
||||
host,
|
||||
id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn open_connection(self) -> Result<()> {
|
||||
fn incrementing_id(&mut self) -> u64 {
|
||||
let id = self.id;
|
||||
self.id = self.id.wrapping_add(1);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) async fn open_connection(mut self) -> Result<()> {
|
||||
let api_url = format!(
|
||||
"{}://{}/api/websocket",
|
||||
if self.insecure { "ws" } else { "wss" },
|
||||
|
@ -82,8 +155,16 @@ impl HomeAssistant {
|
|||
|
||||
while let Some(msg) = ws_stream.next().await {
|
||||
let msg = msg?;
|
||||
if let Ok(response) = self.handle_message(msg).await {
|
||||
ws_stream.send(response).await?;
|
||||
|
||||
match self.handle_message(msg).await {
|
||||
Ok(response_messages) => {
|
||||
for response in response_messages {
|
||||
ws_stream.send(response).await?
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,21 +175,73 @@ impl HomeAssistant {
|
|||
))
|
||||
}
|
||||
|
||||
async fn handle_message(&self, msg: tungstenite::Message) -> Result<tungstenite::Message> {
|
||||
let message: Message = serde_json::from_slice(&msg.into_data()).unwrap();
|
||||
debug!("Received: {message:?}");
|
||||
async fn handle_message(
|
||||
&mut self,
|
||||
msg: tungstenite::Message,
|
||||
) -> Result<Vec<tungstenite::Message>> {
|
||||
trace!("{}", msg.clone().into_text()?);
|
||||
let message: Message = serde_json::from_slice(&msg.into_data())?;
|
||||
trace!("Received: {message:?}");
|
||||
|
||||
let response = match message.message_type {
|
||||
MessageType::AuthRequired => {
|
||||
debug!("Sending Authentication Data");
|
||||
let mut auth = Message::default();
|
||||
auth.message_type = MessageType::Auth;
|
||||
let mut auth = Message::auth();
|
||||
auth.access_token = Some(self.token.clone());
|
||||
|
||||
async_tungstenite::tungstenite::Message::text(serde_json::to_string(&auth).unwrap())
|
||||
vec![auth.to_message()]
|
||||
}
|
||||
MessageType::AuthOk => {
|
||||
let mut subscribe = Message::subscribe_events();
|
||||
subscribe.event_type = Some("state_changed".into());
|
||||
subscribe.id = Some(self.incrementing_id());
|
||||
|
||||
let mut get_states = Message::get_states();
|
||||
get_states.id = Some(self.incrementing_id());
|
||||
|
||||
vec![subscribe.to_message(), get_states.to_message()]
|
||||
}
|
||||
MessageType::Event => {
|
||||
let event = message.event.unwrap();
|
||||
|
||||
if event.data.new_state.entity_id == self.entity {
|
||||
Self::print_state(event.data.new_state.state, event.data.new_state.attributes);
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
MessageType::Result => {
|
||||
debug!("{}", message.to_json());
|
||||
|
||||
vec![]
|
||||
}
|
||||
_ => return Err(eyre!("TODO: Handle {:?}", message.message_type)),
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn print_state(state: String, attributes: Value) {
|
||||
if state == "playing" {
|
||||
let maybe_channel = attributes["media_channel"].as_str();
|
||||
let artist = attributes["media_artist"].as_str().unwrap_or("No artist");
|
||||
let title = attributes["media_title"].as_str().unwrap_or("No title");
|
||||
let volume_raw = attributes["volume_level"].as_f64().unwrap_or(-1.);
|
||||
|
||||
let volume = if volume_raw >= 0. {
|
||||
format!("[{:3.0}%] ", volume_raw * 100.)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let now_playing = if let Some(channel) = maybe_channel {
|
||||
format!("{volume}[{channel}] {artist} - {title}")
|
||||
} else {
|
||||
format!("{volume}{artist} - {title}")
|
||||
};
|
||||
|
||||
println!("{}", html_escape::encode_text(&now_playing));
|
||||
} else {
|
||||
println!("Sonos {state}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue