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