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 color_eyre::{eyre::eyre, Result};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::debug;
|
use serde_json::Value;
|
||||||
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
pub(crate) struct HomeAssistant {
|
pub(crate) struct HomeAssistant {
|
||||||
token: String,
|
token: String,
|
||||||
entity: String,
|
entity: String,
|
||||||
insecure: bool,
|
insecure: bool,
|
||||||
host: String,
|
host: String,
|
||||||
|
id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
struct Message {
|
struct Message {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
message_type: MessageType,
|
message_type: MessageType,
|
||||||
|
@ -21,6 +23,47 @@ struct Message {
|
||||||
access_token: Option<String>,
|
access_token: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
message: Option<String>,
|
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)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
@ -48,14 +91,37 @@ enum MessageType {
|
||||||
ValidateConfig,
|
ValidateConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Message {
|
impl Default for MessageType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
MessageType::Pong
|
||||||
message_type: MessageType::Pong,
|
}
|
||||||
ha_version: Default::default(),
|
}
|
||||||
access_token: Default::default(),
|
|
||||||
message: Default::default(),
|
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,
|
entity,
|
||||||
insecure,
|
insecure,
|
||||||
host,
|
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!(
|
let api_url = format!(
|
||||||
"{}://{}/api/websocket",
|
"{}://{}/api/websocket",
|
||||||
if self.insecure { "ws" } else { "wss" },
|
if self.insecure { "ws" } else { "wss" },
|
||||||
|
@ -82,8 +155,16 @@ impl HomeAssistant {
|
||||||
|
|
||||||
while let Some(msg) = ws_stream.next().await {
|
while let Some(msg) = ws_stream.next().await {
|
||||||
let msg = msg?;
|
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> {
|
async fn handle_message(
|
||||||
let message: Message = serde_json::from_slice(&msg.into_data()).unwrap();
|
&mut self,
|
||||||
debug!("Received: {message:?}");
|
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 {
|
let response = match message.message_type {
|
||||||
MessageType::AuthRequired => {
|
MessageType::AuthRequired => {
|
||||||
debug!("Sending Authentication Data");
|
debug!("Sending Authentication Data");
|
||||||
let mut auth = Message::default();
|
let mut auth = Message::auth();
|
||||||
auth.message_type = MessageType::Auth;
|
|
||||||
auth.access_token = Some(self.token.clone());
|
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)),
|
_ => return Err(eyre!("TODO: Handle {:?}", message.message_type)),
|
||||||
};
|
};
|
||||||
Ok(response)
|
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