| 1 | //! Device plugins. |
| 2 | //! |
| 3 | //! To support a new pair of buds, add one module here and list it in [`REGISTRY`]. Transport, |
| 4 | //! CLI and GUI code do not change. |
| 5 | |
| 6 | use crate::bluez::PairedDevice; |
| 7 | use crate::bt::Uuid; |
| 8 | use crate::error::{Error, Result}; |
| 9 | use crate::status::{EqBand, EqPreset, EqPresets, NoiseControl, NoiseMode, Status, Switch, TouchChange, TouchControls}; |
| 10 | use crate::transport::Transport; |
| 11 | |
| 12 | pub mod realme_t310; |
| 13 | |
| 14 | /// Every model budctl supports. |
| 15 | pub static REGISTRY: &[&dyn Device] = &[&realme_t310::RealmeT310]; |
| 16 | |
| 17 | /// One supported model. |
| 18 | pub trait Device: Sync { |
| 19 | /// Model name, e.g. `realme Buds T310`. |
| 20 | fn name(&self) -> &'static str; |
| 21 | |
| 22 | /// Whether a paired device is this model, judged from BlueZ data alone. |
| 23 | fn matches(&self, device: &PairedDevice) -> bool; |
| 24 | |
| 25 | /// The control service(s), in the order to try. SDP turns each into an RFCOMM channel. |
| 26 | fn rfcomm_uuids(&self) -> &'static [Uuid]; |
| 27 | |
| 28 | /// Run the model's init sequence on a fresh link and return a ready session. |
| 29 | fn open(&self, link: Box<dyn Transport>) -> Result<Box<dyn Session>>; |
| 30 | } |
| 31 | |
| 32 | /// An open control link to one pair of buds. Dropping it closes the link. |
| 33 | pub trait Session: Send { |
| 34 | /// Answer what the buds sent since the last call, without waiting. Some buds drop the link |
| 35 | /// when their own requests (the time request, for one) go unanswered, so a front end that |
| 36 | /// keeps a session open between commands calls this every so often. An error means the link |
| 37 | /// is gone. |
| 38 | fn serve(&mut self) -> Result<()> { |
| 39 | Ok(()) |
| 40 | } |
| 41 | |
| 42 | /// Battery, noise control, EQ, switches and whatever else the model can read. |
| 43 | fn status(&mut self) -> Result<Status>; |
| 44 | |
| 45 | /// The touch-control (gesture) settings of each bud. |
| 46 | fn touch_controls(&mut self) -> Result<TouchControls> { |
| 47 | Err(Error::Unsupported( |
| 48 | "these buds have no touch-control settings budctl can read".to_owned(), |
| 49 | )) |
| 50 | } |
| 51 | |
| 52 | /// Set the noise-control mode, then read it back. Returns the mode the buds now report. |
| 53 | fn set_noise_control(&mut self, _mode: NoiseMode) -> Result<NoiseControl> { |
| 54 | Err(Error::Unsupported( |
| 55 | "these buds have no noise-control setting budctl can change".to_owned(), |
| 56 | )) |
| 57 | } |
| 58 | |
| 59 | /// The EQ presets on the buds, built-in and custom, and the one in use. |
| 60 | fn eq_presets(&mut self) -> Result<EqPresets> { |
| 61 | Err(Error::Unsupported( |
| 62 | "budctl cannot list the EQ presets of these buds".to_owned(), |
| 63 | )) |
| 64 | } |
| 65 | |
| 66 | /// Select a built-in EQ preset by name, then read it back. Returns the preset now in use. |
| 67 | fn set_eq(&mut self, _preset: &str) -> Result<String> { |
| 68 | Err(Error::Unsupported( |
| 69 | "budctl cannot change the EQ on these buds".to_owned(), |
| 70 | )) |
| 71 | } |
| 72 | |
| 73 | /// Change the gains of some bands of a custom EQ preset (`custom-<id>`), save it and select |
| 74 | /// it, then read it back. With no bands, it only selects the preset. |
| 75 | fn set_custom_eq(&mut self, preset: &str, _bands: &[EqBand]) -> Result<EqPreset> { |
| 76 | Err(Error::Unsupported(format!( |
| 77 | "budctl cannot change {preset} on these buds" |
| 78 | ))) |
| 79 | } |
| 80 | |
| 81 | /// The lowest and the highest level [`Session::set_bass_level`] takes, when the buds have a |
| 82 | /// bass level to set. |
| 83 | fn bass_range(&self) -> Option<(i8, i8)> { |
| 84 | None |
| 85 | } |
| 86 | |
| 87 | /// Create a custom EQ preset called `name`, flat, with the bands of the buds' other custom |
| 88 | /// presets. Returns it as the buds list it afterwards. |
| 89 | fn create_custom_eq(&mut self, name: &str) -> Result<EqPreset> { |
| 90 | Err(Error::Unsupported(format!( |
| 91 | "budctl cannot create the preset {name} on these buds" |
| 92 | ))) |
| 93 | } |
| 94 | |
| 95 | /// Give a custom EQ preset (`custom-<id>`) a new name. The buds save it, which also selects |
| 96 | /// it. Returns it as the buds list it afterwards. |
| 97 | fn rename_custom_eq(&mut self, preset: &str, _name: &str) -> Result<EqPreset> { |
| 98 | Err(Error::Unsupported(format!( |
| 99 | "budctl cannot rename {preset} on these buds" |
| 100 | ))) |
| 101 | } |
| 102 | |
| 103 | /// Delete a custom EQ preset (`custom-<id>`). Returns the custom presets left, as the buds |
| 104 | /// list them afterwards: their ids can change. |
| 105 | fn delete_custom_eq(&mut self, preset: &str) -> Result<Vec<EqPreset>> { |
| 106 | Err(Error::Unsupported(format!( |
| 107 | "budctl cannot delete {preset} on these buds" |
| 108 | ))) |
| 109 | } |
| 110 | |
| 111 | /// Set the bass level. Only the ack can be checked when the buds cannot report the level. |
| 112 | fn set_bass_level(&mut self, _level: i8) -> Result<()> { |
| 113 | Err(Error::Unsupported( |
| 114 | "budctl cannot change the bass level on these buds".to_owned(), |
| 115 | )) |
| 116 | } |
| 117 | |
| 118 | /// Start (`true`) or stop ringing the buds so they can be found. Only the ack can be |
| 119 | /// checked: there is no state to read back. |
| 120 | fn find(&mut self, _ring: bool) -> Result<()> { |
| 121 | Err(Error::Unsupported("these buds cannot ring through budctl".to_owned())) |
| 122 | } |
| 123 | |
| 124 | /// Turn an on/off setting ([`Switch::key`]) on or off, then read it back. |
| 125 | fn set_switch(&mut self, key: &str, _on: bool) -> Result<Switch> { |
| 126 | Err(Error::Unsupported(format!("budctl cannot change {key} on these buds"))) |
| 127 | } |
| 128 | |
| 129 | /// Set the noise-control modes a hold steps through (`off`, `transparency`, `anc`), then |
| 130 | /// read it back. Returns the modes the buds now report. |
| 131 | fn set_hold_cycle(&mut self, _modes: &[&str]) -> Result<Vec<String>> { |
| 132 | Err(Error::Unsupported( |
| 133 | "budctl cannot change the hold cycle on these buds".to_owned(), |
| 134 | )) |
| 135 | } |
| 136 | |
| 137 | /// Give a touch gesture a new action, on `side` (`left`, `right` or `both`), then read the |
| 138 | /// table back. With `dry_run`, read the table and work out the write, but send nothing. |
| 139 | fn set_touch(&mut self, _side: &str, _gesture: &str, _action: &str, _dry_run: bool) -> Result<TouchChange> { |
| 140 | Err(Error::Unsupported( |
| 141 | "budctl cannot change the touch controls on these buds".to_owned(), |
| 142 | )) |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | /// The supported model a paired device is, if any. |
| 147 | pub fn identify(device: &PairedDevice) -> Option<&'static dyn Device> { |
| 148 | REGISTRY.iter().copied().find(|model| model.matches(device)) |
| 149 | } |