use crate::messages::Request;
use crate::messages::events::StationState;
use crate::{Event, Frequency};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "type", content = "value")]
pub enum Command {
#[serde(rename = "kPttPressed")]
PttPressed,
#[serde(rename = "kPttReleased")]
PttReleased,
#[serde(rename = "kAddStation")]
AddStation(AddStation),
#[serde(rename = "kSetStationState")]
SetStationState(SetStationState),
#[serde(rename = "kChangeStationVolume")]
ChangeStationVolume(ChangeStationVolume),
#[serde(rename = "kChangeMainVolume")]
ChangeMainVolume(ChangeMainVolume),
#[serde(rename = "kGetMainVolume")]
GetMainVolume,
#[serde(rename = "kGetStationState")]
GetStationState(GetStationState),
#[serde(rename = "kGetStationStates")]
GetStationStates,
#[serde(rename = "kGetVoiceConnectedState")]
GetVoiceConnectedState,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct AddStation {
pub callsign: String,
}
impl Request for AddStation {
type Response = StationState;
fn into_command(self) -> Command {
Command::AddStation(self)
}
fn extract(event: &Event, cmd: &Command) -> Option<Self::Response> {
let Command::AddStation(AddStation { callsign }) = cmd else {
return None;
};
match event {
Event::StationStateUpdate(state)
if state.callsign.as_ref().is_some_and(|c| c == callsign) =>
{
Some(state.clone())
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetStationState {
pub frequency: Frequency,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_output_muted: Option<BoolOrToggle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rx: Option<BoolOrToggle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tx: Option<BoolOrToggle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub xca: Option<BoolOrToggle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headset: Option<BoolOrToggle>,
}
impl SetStationState {
#[must_use]
pub fn new(frequency: impl Into<Frequency>) -> Self {
Self {
frequency: frequency.into(),
is_output_muted: None,
rx: None,
tx: None,
xca: None,
headset: None,
}
}
#[must_use]
pub fn rx(mut self, value: impl Into<BoolOrToggle>) -> Self {
self.rx = Some(value.into());
self
}
#[must_use]
pub fn tx(mut self, value: impl Into<BoolOrToggle>) -> Self {
self.tx = Some(value.into());
self
}
#[must_use]
pub fn xca(mut self, value: impl Into<BoolOrToggle>) -> Self {
self.xca = Some(value.into());
self
}
#[must_use]
pub fn headset(mut self, value: impl Into<BoolOrToggle>) -> Self {
self.headset = Some(value.into());
self
}
#[must_use]
pub fn output_muted(mut self, value: impl Into<BoolOrToggle>) -> Self {
self.is_output_muted = Some(value.into());
self
}
}
impl Request for SetStationState {
type Response = StationState;
fn into_command(self) -> Command {
Command::SetStationState(self)
}
fn extract(event: &Event, cmd: &Command) -> Option<Self::Response> {
let Command::SetStationState(SetStationState { frequency, .. }) = cmd else {
return None;
};
match event {
Event::StationStateUpdate(state)
if state.frequency.is_some_and(|f| f == *frequency) =>
{
Some(state.clone())
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ChangeStationVolume {
pub frequency: Frequency,
pub amount: i8,
}
impl ChangeStationVolume {
pub fn new(frequency: impl Into<Frequency>, amount: i8) -> Self {
Self {
frequency: frequency.into(),
amount: amount.clamp(-100, 100),
}
}
}
impl Request for ChangeStationVolume {
type Response = StationState;
fn into_command(self) -> Command {
Command::ChangeStationVolume(self)
}
fn extract(event: &Event, cmd: &Command) -> Option<Self::Response> {
let Command::ChangeStationVolume(ChangeStationVolume { frequency, .. }) = cmd else {
return None;
};
match event {
Event::StationStateUpdate(state)
if state.frequency.is_some_and(|f| f == *frequency) =>
{
Some(state.clone())
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ChangeMainVolume {
pub amount: i8,
}
impl ChangeMainVolume {
#[must_use]
pub fn new(amount: i8) -> Self {
Self {
amount: amount.clamp(-100, 100),
}
}
}
impl Request for ChangeMainVolume {
type Response = f32;
fn into_command(self) -> Command {
Command::ChangeMainVolume(self)
}
fn extract(event: &Event, _cmd: &Command) -> Option<Self::Response> {
match event {
Event::MainVolumeChange(change) => Some(change.volume),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GetStationState {
pub callsign: String,
}
impl Request for GetStationState {
type Response = StationState;
fn into_command(self) -> Command {
Command::GetStationState(self)
}
fn extract(event: &Event, cmd: &Command) -> Option<Self::Response> {
let Command::GetStationState(GetStationState { callsign }) = cmd else {
return None;
};
match event {
Event::StationStateUpdate(state)
if state.callsign.as_ref().is_some_and(|c| c == callsign) =>
{
Some(state.clone())
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GetStationStates;
impl Request for GetStationStates {
type Response = Vec<StationState>;
fn into_command(self) -> Command {
Command::GetStationStates
}
fn extract(event: &Event, _cmd: &Command) -> Option<Self::Response> {
match event {
Event::StationStates(states) => Some(
states
.stations
.iter()
.map(|s| s.value.clone())
.collect::<_>(),
),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GetVoiceConnectedState;
impl Request for GetVoiceConnectedState {
type Response = bool;
fn into_command(self) -> Command {
Command::GetVoiceConnectedState
}
fn extract(event: &Event, _cmd: &Command) -> Option<Self::Response> {
match event {
Event::VoiceConnectedState(state) => Some(state.connected),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BoolOrToggle {
Bool(bool),
Toggle,
}
impl serde::Serialize for BoolOrToggle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
BoolOrToggle::Bool(b) => serializer.serialize_bool(*b),
BoolOrToggle::Toggle => serializer.serialize_str("toggle"),
}
}
}
impl<'de> serde::Deserialize<'de> for BoolOrToggle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BoolOrToggleVisitor;
impl serde::de::Visitor<'_> for BoolOrToggleVisitor {
type Value = BoolOrToggle;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a boolean or \"toggle\"")
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(BoolOrToggle::Bool(value))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if value == "toggle" {
Ok(BoolOrToggle::Toggle)
} else {
Err(E::custom(format!("expected \"toggle\", got {value}")))
}
}
}
deserializer.deserialize_any(BoolOrToggleVisitor)
}
}
impl From<bool> for BoolOrToggle {
fn from(b: bool) -> Self {
BoolOrToggle::Bool(b)
}
}
impl From<Option<bool>> for BoolOrToggle {
fn from(opt: Option<bool>) -> Self {
match opt {
Some(b) => BoolOrToggle::Bool(b),
None => BoolOrToggle::Toggle,
}
}
}
impl TryFrom<BoolOrToggle> for bool {
type Error = ();
fn try_from(b: BoolOrToggle) -> Result<Self, Self::Error> {
match b {
BoolOrToggle::Bool(b) => Ok(b),
BoolOrToggle::Toggle => Err(()),
}
}
}
impl BoolOrToggle {
#[must_use]
pub fn is_toggle(&self) -> bool {
matches!(self, BoolOrToggle::Toggle)
}
#[must_use]
pub fn is_true(&self) -> bool {
matches!(self, BoolOrToggle::Bool(true))
}
#[must_use]
pub fn is_false(&self) -> bool {
matches!(self, BoolOrToggle::Bool(false))
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self {
BoolOrToggle::Bool(b) => Some(*b),
BoolOrToggle::Toggle => None,
}
}
#[must_use]
pub fn unwrap_or(&self, default: bool) -> bool {
self.as_bool().unwrap_or(default)
}
}
#[cfg(test)]
mod tests {
use super::*;
mod bool_or_toggle {
use super::BoolOrToggle;
use serde_json;
#[test]
fn from_bool() {
let b = BoolOrToggle::from(true);
assert_eq!(b, BoolOrToggle::Bool(true));
}
#[test]
fn from_option_bool() {
let b = BoolOrToggle::from(Some(true));
assert_eq!(b, BoolOrToggle::Bool(true));
}
#[test]
fn from_option_none() {
let b = BoolOrToggle::from(None);
assert_eq!(b, BoolOrToggle::Toggle);
}
#[test]
fn bool_try_from() {
let b = BoolOrToggle::Bool(true);
assert_eq!(bool::try_from(b), Ok(true));
}
#[test]
fn toggle_try_from() {
let b = BoolOrToggle::Toggle;
assert_eq!(bool::try_from(b), Err(()));
}
#[test]
fn is_toggle() {
let b = BoolOrToggle::Toggle;
assert!(b.is_toggle());
}
#[test]
fn is_true() {
let b = BoolOrToggle::Bool(true);
assert!(b.is_true());
}
#[test]
fn is_false() {
let b = BoolOrToggle::Bool(false);
assert!(b.is_false());
}
#[test]
fn bool_as_bool() {
let b = BoolOrToggle::Bool(true);
assert_eq!(b.as_bool(), Some(true));
}
#[test]
fn toggle_as_bool() {
let b = BoolOrToggle::Toggle;
assert_eq!(b.as_bool(), None);
}
#[test]
fn bool_unwrap_or() {
let b = BoolOrToggle::Bool(true);
assert!(b.unwrap_or(false));
}
#[test]
fn toggle_unwrap_or() {
let b = BoolOrToggle::Toggle;
assert!(!b.unwrap_or(false));
}
#[test]
fn serialize_bool() {
assert_eq!(
serde_json::to_string(&BoolOrToggle::Bool(true)).unwrap(),
"true"
);
assert_eq!(
serde_json::to_string(&BoolOrToggle::Bool(false)).unwrap(),
"false"
);
}
#[test]
fn serialize_toggle() {
assert_eq!(
serde_json::to_string(&BoolOrToggle::Toggle).unwrap(),
"\"toggle\""
);
}
#[test]
fn deserialize_bool() {
assert_eq!(
serde_json::from_str::<BoolOrToggle>("true").unwrap(),
BoolOrToggle::Bool(true)
);
assert_eq!(
serde_json::from_str::<BoolOrToggle>("false").unwrap(),
BoolOrToggle::Bool(false)
);
}
#[test]
fn deserialize_toggle() {
assert_eq!(
serde_json::from_str::<BoolOrToggle>("\"toggle\"").unwrap(),
BoolOrToggle::Toggle
);
}
}
}