use super::{
response::{self, Response},
Button, Device, EndpointBase, KeyEvent, Result,
};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde_json::Value;
use std::result::Result as StdResult;
#[derive(Debug, Copy, Clone)]
pub enum RequestType {
Get,
Put,
}
#[allow(unused)] #[derive(Debug)]
pub(super) enum CommandDetail {
StartPairing {
client_name: String,
client_id: String,
},
FinishPairing {
client_id: String,
pairing_token: u32,
challenge: u32,
response_value: String,
},
CancelPairing {
client_id: String,
pairing_token: u32,
challenge: u32,
},
GetPowerState,
GetDeviceInfo,
RemoteButtonPress(KeyEvent, Button),
GetCurrentInput,
GetInputList,
ChangeInput {
name: String,
hashval: u32,
},
GetCurrentApp,
LaunchApp(Value),
ReadSettings(EndpointBase, String),
WriteSettings(String, u32, Value),
Custom(RequestType, String, Option<Value>),
}
impl CommandDetail {
pub fn endpoint(&self, settings_root: String) -> String {
match self {
Self::StartPairing { .. } => "/pairing/start".into(),
Self::FinishPairing { .. } => "/pairing/pair".into(),
Self::CancelPairing { .. } => "/pairing/cancel".into(),
Self::GetPowerState => "/state/device/power_mode".into(),
Self::GetDeviceInfo => "/state/device/deviceinfo".into(),
Self::RemoteButtonPress { .. } => "/key_command/".into(),
Self::GetCurrentInput => format!(
"/menu_native/dynamic/{}/devices/current_input",
settings_root
),
Self::GetInputList => {
format!("/menu_native/dynamic/{}/devices/name_input", settings_root)
}
Self::ChangeInput { .. } => format!(
"/menu_native/dynamic/{}/devices/current_input",
settings_root
),
Self::GetCurrentApp => "/app/current".into(),
Self::LaunchApp(_) => "/app/launch".into(),
Self::ReadSettings(base, endpoint) => base.as_str() + endpoint,
Self::WriteSettings(endpoint, _, _) => format!("/menu_native/dynamic{}", endpoint),
Self::Custom(_, endpoint, _) => endpoint.into(),
}
}
pub fn request_type(&self) -> RequestType {
match self {
Self::StartPairing { .. }
| Self::FinishPairing { .. }
| Self::CancelPairing { .. }
| Self::RemoteButtonPress { .. }
| Self::ChangeInput { .. }
| Self::LaunchApp(_)
| Self::WriteSettings(_, _, _) => RequestType::Put,
Self::GetPowerState
| Self::GetDeviceInfo
| Self::GetCurrentInput
| Self::GetInputList
| Self::GetCurrentApp
| Self::ReadSettings(_, _) => RequestType::Get,
Self::Custom(req_type, _, _) => *req_type,
}
}
}
pub(super) struct Command {
detail: CommandDetail,
endpoint: String,
device: Device,
}
impl Command {
pub fn new(device: Device, detail: CommandDetail) -> Self {
let endpoint = detail.endpoint(device.settings_root());
Self {
detail,
endpoint,
device,
}
}
pub async fn send(self) -> Result<Response> {
let device = self.device.clone();
let client = device.inner.client.clone();
let url: String = format!(
"https://{}:{}{}",
device.ip(),
device.port(),
self.detail.endpoint(device.settings_root())
);
let res = {
let mut req = match self.detail.request_type() {
RequestType::Get => client.get(url),
RequestType::Put => {
client
.put(url)
.header("Content-Type", "application/json")
.body(serde_json::to_string(&self).unwrap())
}
};
if let Some(token) = &device.auth_token().await {
req = req.header("Auth", token.to_string())
}
log::debug!("req: {:?}", req);
req
}
.send()
.await?
.text()
.await?;
response::process(res)
}
}
impl Serialize for Command {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let mut command = serializer.serialize_struct("", 5)?;
command.serialize_field("_url", &self.endpoint)?;
match &self.detail {
CommandDetail::StartPairing {
client_name,
client_id,
} => {
command.serialize_field("DEVICE_NAME", client_name)?;
command.serialize_field("DEVICE_ID", client_id)?;
command.end()
}
CommandDetail::CancelPairing {
client_id,
pairing_token,
challenge,
} => {
command.serialize_field("DEVICE_ID", client_id)?;
command.serialize_field("CHALLENGE_TYPE", challenge)?;
command.serialize_field("RESPONSE_VALUE", "1111")?;
command.serialize_field("PAIRING_REQ_TOKEN", pairing_token)?;
command.end()
}
CommandDetail::FinishPairing {
client_id,
pairing_token,
challenge,
response_value,
} => {
command.serialize_field("DEVICE_ID", client_id)?;
command.serialize_field("CHALLENGE_TYPE", challenge)?;
command.serialize_field("RESPONSE_VALUE", response_value)?;
command.serialize_field("PAIRING_REQ_TOKEN", pairing_token)?;
command.end()
}
CommandDetail::RemoteButtonPress(event, button) => {
#[derive(serde::Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
struct Helper {
codeset: u8,
code: u8,
action: String,
}
let helper = Helper {
codeset: button.codeset(),
code: button.code(),
action: event.to_string(),
};
command.serialize_field("KEYLIST", &(vec![helper]))?;
command.end()
}
CommandDetail::ChangeInput { name, hashval } => {
command.serialize_field("REQUEST", "MODIFY")?;
command.serialize_field("VALUE", name)?;
command.serialize_field("HASHVAL", hashval)?;
command.end()
}
CommandDetail::LaunchApp(payload) => {
command.serialize_field("VALUE", payload)?;
command.end()
}
CommandDetail::WriteSettings(_, hashval, value) => {
command.serialize_field("REQUEST", "MODIFY")?;
command.serialize_field("HASHVAL", hashval)?;
command.serialize_field("VALUE", value)?;
command.end()
}
_ => command.end(),
}
}
}