Krolyxon
Commit 9dea828

Branches

Refs →

budctlcratesbudctl-coresrcstatus.rs

243 lines · 7.4 KB · Rust

1//! What a session reports, in device-neutral terms. Front ends print these, and `--json`
2//! serializes them as they are.
3
4use serde::{Serialize, Serializer};
5
6use crate::bt::BdAddr;
7
8/// Bumped whenever the JSON shape changes incompatibly.
9pub const JSON_SCHEMA: u32 = 1;
10
11#[derive(Debug, Default, Clone, Serialize)]
12pub struct Status {
13 pub battery: Battery,
14 pub noise_control: Option<NoiseControl>,
15 /// Name of the current EQ preset.
16 pub eq: Option<String>,
17 pub bass_level: Option<i8>,
18 /// On/off settings, serialized as `{"game": false, …}`.
19 #[serde(serialize_with = "switches_as_map")]
20 pub switches: Vec<Switch>,
21 /// Audio codec of the link the buds report.
22 pub codec: Option<String>,
23 pub firmware: Option<String>,
24 /// Reads that failed. The rest of the status is still valid.
25 pub warnings: Vec<String>,
26}
27
28#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
29pub struct Battery {
30 pub left: Option<Charge>,
31 pub right: Option<Charge>,
32 pub case: Option<Charge>,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
36pub struct Charge {
37 /// Percent.
38 pub level: u8,
39 pub charging: bool,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
43pub struct NoiseControl {
44 /// `off`, `transparency` or `anc`, or `unknown (…)` for a value the device module cannot name.
45 pub mode: String,
46 /// The noise-cancelling level, when the device reports one.
47 pub level: Option<String>,
48}
49
50/// A noise-control mode to set.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum NoiseMode {
53 Off,
54 Transparency,
55 Anc(AncLevel),
56}
57
58/// How strongly noise cancelling works.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum AncLevel {
61 Light,
62 Medium,
63 Deep,
64}
65
66impl AncLevel {
67 /// The name [`NoiseControl::level`] uses for it.
68 pub fn name(self) -> &'static str {
69 match self {
70 AncLevel::Light => "light",
71 AncLevel::Medium => "medium",
72 AncLevel::Deep => "deep",
73 }
74 }
75}
76
77impl NoiseMode {
78 /// The name [`NoiseControl::mode`] uses for it.
79 pub fn name(self) -> &'static str {
80 match self {
81 NoiseMode::Off => "off",
82 NoiseMode::Transparency => "transparency",
83 NoiseMode::Anc(_) => "anc",
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
89pub struct Switch {
90 /// Stable name used on the command line and in JSON (`game`, `dual`…).
91 pub key: &'static str,
92 /// Human name (`Game mode`).
93 pub label: &'static str,
94 pub on: bool,
95}
96
97#[derive(Debug, Default, Clone, Serialize)]
98pub struct TouchControls {
99 pub gestures: Vec<Gesture>,
100 /// Noise-control modes a hold steps through, when the device has that setting.
101 pub hold_cycle: Option<Vec<String>>,
102 /// The actions each gesture can be given, when the device can change them.
103 pub choices: Vec<GestureChoice>,
104 /// The modes the hold cycle can include, when the device can change it.
105 pub cycle_modes: Vec<String>,
106 pub warnings: Vec<String>,
107}
108
109/// The actions one gesture can be given, as the device offers them, in its order.
110#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
111pub struct GestureChoice {
112 /// `double-tap`, `triple-tap`, `hold`…
113 pub gesture: String,
114 /// `none`, `play-pause`…
115 pub actions: Vec<String>,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
119pub struct Gesture {
120 /// `left`, `right` or `both`.
121 pub side: String,
122 /// `double-tap`, `triple-tap`, `hold`…
123 pub gesture: String,
124 /// `play-pause`, `next`, `none`…
125 pub action: String,
126 /// Raw button group, for entries outside the main gestures (calls). `None` for the main group.
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub group: Option<u8>,
129}
130
131/// The EQ presets the buds have, and which one is in use.
132#[derive(Debug, Default, Clone, Serialize)]
133pub struct EqPresets {
134 /// Name of the preset in use, as in [`Status::eq`].
135 pub current: Option<String>,
136 /// Built-in presets first, then custom ones.
137 pub presets: Vec<EqPreset>,
138 /// Reads that failed. The rest is still valid.
139 pub warnings: Vec<String>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
143pub struct EqPreset {
144 /// Built-in presets: the name `eq <preset>` takes. Custom ones: the name stored on the buds.
145 pub name: String,
146 /// The buds' id for it.
147 pub id: u8,
148 pub custom: bool,
149 /// Custom presets only: gain per band. Empty for built-in presets.
150 pub bands: Vec<EqBand>,
151 /// Custom presets only: the lowest and the highest gain a band takes, dB.
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub range: Option<(i8, i8)>,
154}
155
156/// A frequency band of bass boost, on buds that set its level band by band.
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
158#[serde(rename_all = "lowercase")]
159pub enum BassBand {
160 Low,
161 Medium,
162 High,
163}
164
165impl BassBand {
166 pub const ALL: [BassBand; 3] = [BassBand::Low, BassBand::Medium, BassBand::High];
167
168 /// `low`, `medium` or `high`, as the command line and JSON name it.
169 pub fn name(self) -> &'static str {
170 match self {
171 BassBand::Low => "low",
172 BassBand::Medium => "medium",
173 BassBand::High => "high",
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
179pub struct EqBand {
180 pub hz: u16,
181 /// dB.
182 pub gain: i8,
183}
184
185/// A phone or computer the buds keep for dual connection.
186#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
187pub struct DualDevice {
188 pub address: BdAddr,
189 /// The name the device gave the buds.
190 pub name: String,
191 pub connected: bool,
192 /// The device that asked: the computer budctl runs on.
193 pub this_computer: bool,
194 /// The device the buds take audio from.
195 pub main_audio: bool,
196 pub playing: bool,
197}
198
199/// A touch-control change: what changes, the write that does it and, once sent, the table the
200/// buds report afterwards.
201#[derive(Debug, Serialize)]
202pub struct TouchChange {
203 /// The entries that change. Empty when every target already has the wanted action.
204 pub changes: Vec<GestureChange>,
205 /// The write that makes the change, or `None` when there is nothing to change.
206 pub write: Option<RawWrite>,
207 /// Whether the write went out. `false` on a dry run and when nothing changes.
208 pub sent: bool,
209 /// The whole table: as read back after the write, or as it would be after a dry run.
210 pub gestures: Vec<Gesture>,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
214pub struct GestureChange {
215 pub side: String,
216 pub gesture: String,
217 pub from: String,
218 pub to: String,
219}
220
221/// One command and its payload, as `--dry-run` shows them. The frame around it (sequence byte
222/// and so on) is added when it is sent.
223#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
224pub struct RawWrite {
225 /// `0x0401`.
226 #[serde(serialize_with = "command_as_hex")]
227 pub command: u16,
228 /// `06 01 01 02 01 …`.
229 #[serde(serialize_with = "bytes_as_hex")]
230 pub payload: Vec<u8>,
231}
232
233fn command_as_hex<S: Serializer>(command: &u16, serializer: S) -> Result<S::Ok, S::Error> {
234 serializer.serialize_str(&format!("0x{command:04X}"))
235}
236
237fn bytes_as_hex<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
238 serializer.serialize_str(&crate::debug::hex(bytes))
239}
240
241fn switches_as_map<S: Serializer>(switches: &[Switch], serializer: S) -> Result<S::Ok, S::Error> {
242 serializer.collect_map(switches.iter().map(|s| (s.key, s.on)))
243}