From a88f387442f55b2dbe48dc1d941d7431e83901ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Fouren?= Date: Tue, 22 Jul 2025 21:56:32 +0800 Subject: [PATCH 1/5] Add TAP commands mode --- src/main.rs | 15 +++++++++++++- src/protocol.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d425e86..6e5e796 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use thiserror::Error; use bose_dfu::device_ids::{DeviceCompat, DeviceMode, UsbId, identify_device}; use bose_dfu::dfu_file::parse as parse_dfu_file; -use bose_dfu::protocol::{download, ensure_idle, enter_dfu, leave_dfu, read_info_field}; +use bose_dfu::protocol::{download, ensure_idle, enter_dfu, leave_dfu, read_info_field, run_tap_commands}; #[derive(Parser, Debug)] #[command(version, about)] @@ -22,6 +22,12 @@ enum Opt { spec: DeviceSpec, }, + /// Run TAP commands on a specific device not in DFU mode + Tap { + #[command(flatten)] + spec: DeviceSpec, + }, + /// Put a device into DFU mode EnterDfu { #[command(flatten)] @@ -103,6 +109,13 @@ fn main() -> Result<()> { read_info_field(&dev, CurrentFirmware)? ); } + Opt::Tap { spec } => { + let spec = DeviceSpec { + required_mode: Some(DeviceMode::Normal), + ..spec + }; + run_tap_commands(&spec.get_device(&api)?.0)?; + } Opt::EnterDfu { spec } => { let spec = DeviceSpec { required_mode: Some(DeviceMode::Normal), diff --git a/src/protocol.rs b/src/protocol.rs index c64c070..e174860 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -184,6 +184,59 @@ pub fn read_info_field(device: &HidDevice, field: InfoField) -> Result Result<(), Error> { + const TAP_REPORT_ID: u8 = 2; + const TAP_REPORT_LEN: usize = 2048; + + loop { + print!("> "); + std::io::stdout().flush().unwrap(); + let mut tap_command = String::new(); + std::io::stdin() + .read_line(&mut tap_command) + .expect("Failed to read line"); + + tap_command = tap_command.trim().to_string(); + if tap_command.len() == 0 { + continue; + } else if tap_command == "." { + break; + } + let tap_bytes = tap_command.as_bytes(); + + // 1 byte report ID + 2 bytes field ID + 1 byte NUL + let mut request_report = vec![0u8; 1 + tap_bytes.len() + 1].into_boxed_slice(); + + // Packet captures indicate that "lc" is also a valid field type for some devices, but on mine + // it always returns a bus error (both when I send it and when the official updater does). + request_report[0] = TAP_REPORT_ID; + request_report[1..tap_bytes.len()+1].copy_from_slice(tap_bytes); + + device + .send_feature_report(&request_report) + .map_err(|e| Error::DeviceIoError { + source: e, + action: "running TAP command", + })?; + + let mut response_report = [0u8; 1 + TAP_REPORT_LEN]; + response_report[0] = TAP_REPORT_ID; + map_gfr( + device.get_feature_report(&mut response_report), + 1, + "reading TAP command response", + )?; + + trace!("Raw {:?} TAP command response: {:02x?}", tap_command, response_report); + + // Result is all the bytes after the report ID and before the first NUL. + let result = response_report[1..].split(|&x| x == 0).next().unwrap(); + println!("{:?}", std::str::from_utf8(result)); + } + + Ok(()) +} + /// Put a device running the normal firmware into DFU mode. `device` must NOT be in DFU mode. pub fn enter_dfu(device: &HidDevice) -> Result<(), Error> { const ENTER_DFU_REPORT_ID: u8 = 1; From b8156b5214069d8a03d52c5afb7c6c04ed248922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Fouren?= Date: Wed, 23 Jul 2025 11:54:18 +0800 Subject: [PATCH 2/5] Fix formatting --- src/main.rs | 4 +++- src/protocol.rs | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6e5e796..5e3a654 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,9 @@ use thiserror::Error; use bose_dfu::device_ids::{DeviceCompat, DeviceMode, UsbId, identify_device}; use bose_dfu::dfu_file::parse as parse_dfu_file; -use bose_dfu::protocol::{download, ensure_idle, enter_dfu, leave_dfu, read_info_field, run_tap_commands}; +use bose_dfu::protocol::{ + download, ensure_idle, enter_dfu, leave_dfu, read_info_field, run_tap_commands, +}; #[derive(Parser, Debug)] #[command(version, about)] diff --git a/src/protocol.rs b/src/protocol.rs index e174860..4127d75 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -210,7 +210,7 @@ pub fn run_tap_commands(device: &HidDevice) -> Result<(), Error> { // Packet captures indicate that "lc" is also a valid field type for some devices, but on mine // it always returns a bus error (both when I send it and when the official updater does). request_report[0] = TAP_REPORT_ID; - request_report[1..tap_bytes.len()+1].copy_from_slice(tap_bytes); + request_report[1..tap_bytes.len() + 1].copy_from_slice(tap_bytes); device .send_feature_report(&request_report) @@ -227,7 +227,10 @@ pub fn run_tap_commands(device: &HidDevice) -> Result<(), Error> { "reading TAP command response", )?; - trace!("Raw {:?} TAP command response: {:02x?}", tap_command, response_report); + trace!( + "Raw {:?} TAP command response: {:02x?}", + tap_command, response_report + ); // Result is all the bytes after the report ID and before the first NUL. let result = response_report[1..].split(|&x| x == 0).next().unwrap(); From 1eccc845ee6a0261a6fa3ab0505261fcd9850beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Fouren?= Date: Mon, 28 Jul 2025 10:52:40 +0800 Subject: [PATCH 3/5] Fix tchebb comments: 1: use .is_empty() 2: use rustyline 3: reduce common code 4: move tap loop into main.rs I also did a minor change to only strip the first byte of the response if it matches the report ID --- Cargo.lock | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 7 +++ src/main.rs | 56 +++++++++++++++++++++-- src/protocol.rs | 109 ++++++++++++++------------------------------ 5 files changed, 214 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e6043d..503510f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ dependencies = [ "hidapi", "log", "num_enum", + "rustyline", "thiserror", ] @@ -100,6 +101,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.36" @@ -141,6 +148,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -156,6 +172,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_filter" version = "0.1.3" @@ -194,6 +216,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -219,6 +258,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -283,6 +331,27 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -358,6 +427,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rustix" version = "1.0.5" @@ -371,6 +450,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustyline" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.59.0", +] + [[package]] name = "serde" version = "1.0.219" @@ -397,6 +498,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "strsim" version = "0.11.1" @@ -467,6 +574,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index a041bfb..55a84c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ crc32fast = "1.2" anyhow = "1.0" clap = { version = "4.0", features = ["derive", "wrap_help"] } env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] } +rustyline = "16.0.0" [profile.release] strip = "symbols" diff --git a/README.md b/README.md index 3303fa0..5155f31 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ OPTIONS: SUBCOMMANDS: list List all connected Bose HID devices (vendor ID 0x05a7) info Get information about a specific device not in DFU mode + tap Run TAP commands on a specific device not in DFU mode enter-dfu Put a device into DFU mode leave-dfu Take a device out of DFU mode download Write firmware to a device in DFU mode @@ -128,6 +129,12 @@ download`, and `bose-dfu leave-dfu`, in that order. The other subcommands help you inspect the current state of devices and firmware files. Notable is `info`, which tells you the current firmware version a device is running. +The `tap` subcommand can be used to start an interactvice shell with the device +allowing you to send maintenance commands to the device, useful for servicing +purposes (like putting the device into shipmode when changing the battery). +Refer to your products service manual for available commands. To exit the shell +you may use a single `.`, `` or ``. + Subcommands that perform an operation on a device support arguments for selecting which device to talk to. You can use `-p` to select by USB product ID, `-s` to select by USB serial number, or both together. Additionally, the diff --git a/src/main.rs b/src/main.rs index 5e3a654..fe03e27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,17 @@ use anyhow::{Context, Result, bail}; use clap::Parser; use hidapi::{DeviceInfo, HidApi, HidDevice}; use log::{info, warn}; +use rustyline::DefaultEditor; +use rustyline::error::ReadlineError; +use std::env; use std::io::Read; -use std::path::Path; +use std::path::{Path, PathBuf}; use thiserror::Error; use bose_dfu::device_ids::{DeviceCompat, DeviceMode, UsbId, identify_device}; use bose_dfu::dfu_file::parse as parse_dfu_file; use bose_dfu::protocol::{ - download, ensure_idle, enter_dfu, leave_dfu, read_info_field, run_tap_commands, + download, ensure_idle, enter_dfu, leave_dfu, read_info_field, run_tap_command, }; #[derive(Parser, Debug)] @@ -116,7 +119,7 @@ fn main() -> Result<()> { required_mode: Some(DeviceMode::Normal), ..spec }; - run_tap_commands(&spec.get_device(&api)?.0)?; + tap_command_loop(&spec.get_device(&api)?.0)?; } Opt::EnterDfu { spec } => { let spec = DeviceSpec { @@ -190,6 +193,53 @@ fn list_cmd(hidapi: &HidApi) { } } +pub fn tap_command_loop(device: &HidDevice) -> Result<()> { + const HISTORY_NAME: &str = ".bose-dfu_history"; + + let mut rl = DefaultEditor::new()?; + let history_file = match env::home_dir() { + Some(home_path) => home_path.join(HISTORY_NAME), + None => PathBuf::from(HISTORY_NAME), + }; + if rl.load_history(&history_file).is_err() { + println!("No previous history."); + } + + loop { + let readline = rl.readline("> "); + match readline { + Ok(line) => { + if line.is_empty() { + continue; + } else if line == "." { + break; + } + + let result = run_tap_command(device, line.as_bytes()); + println!("{result:?}"); + } + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + break; + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + break; + } + Err(err) => { + println!("Error: {err:?}"); + break; + } + } + } + + if rl.save_history(&history_file).is_err() { + println!("Cant save history.") + } + + Ok(()) +} + fn download_cmd(dev: &HidDevice, info: &DeviceInfo, path: &Path, wildcard_fw: bool) -> Result<()> { let mut file = std::fs::File::open(path)?; let suffix = parse_dfu_file(&mut file)?; diff --git a/src/protocol.rs b/src/protocol.rs index 4127d75..e892bed 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -139,105 +139,64 @@ pub enum InfoField { CurrentFirmware, } -/// Read an information field (as listed in [InfoField]) from the normal firmware. `device` must -/// NOT be in DFU mode. -pub fn read_info_field(device: &HidDevice, field: InfoField) -> Result { - const INFO_REPORT_ID: u8 = 2; - const INFO_REPORT_LEN: usize = 126; - - use InfoField::*; +// Run a "TAP command" on the device. This is the general way to communicate with Bose DFU devices +// 'device' must NOT be in DFU mode. +pub fn run_tap_command(device: &HidDevice, tap_bytes: &[u8]) -> Result { + const TAP_REPORT_ID: u8 = 2; + const TAP_REPORT_LEN: usize = 126; // 1 byte report ID + 2 bytes field ID + 1 byte NUL - let mut request_report = [0u8; 1 + 2 + 1]; + let mut request_report = vec![0u8; 1 + tap_bytes.len() + 1].into_boxed_slice(); - // Packet captures indicate that "lc" is also a valid field type for some devices, but on mine - // it always returns a bus error (both when I send it and when the official updater does). - request_report[0] = INFO_REPORT_ID; - request_report[1..3].copy_from_slice(match field { - DeviceModel => b"pl", - SerialNumber => b"sn", - CurrentFirmware => b"vr", - }); + request_report[0] = TAP_REPORT_ID; + request_report[1..tap_bytes.len() + 1].copy_from_slice(tap_bytes); device .send_feature_report(&request_report) .map_err(|e| Error::DeviceIoError { source: e, - action: "requesting info field", + action: "running TAP command", })?; - let mut response_report = [0u8; 1 + INFO_REPORT_LEN]; - response_report[0] = INFO_REPORT_ID; + let mut response_report = [0u8; 1 + TAP_REPORT_LEN]; + response_report[0] = TAP_REPORT_ID; map_gfr( device.get_feature_report(&mut response_report), 1, - "reading info field", + "reading TAP command response", )?; - trace!("Raw {field:?} info field: {response_report:02x?}"); + trace!( + "Raw {:?} TAP command response: {response_report:02x?}", + std::str::from_utf8(tap_bytes) + ); // Result is all the bytes after the report ID and before the first NUL. - let result = response_report[1..].split(|&x| x == 0).next().unwrap(); + let result = match response_report[0] { + TAP_REPORT_ID => response_report[1..].split(|&x| x == 0).next().unwrap(), + _ => response_report.split(|&x| x == 0).next().unwrap(), + }; Ok(std::str::from_utf8(result) .map_err(|e| Error::ProtocolError(e.into()))? .to_owned()) } -pub fn run_tap_commands(device: &HidDevice) -> Result<(), Error> { - const TAP_REPORT_ID: u8 = 2; - const TAP_REPORT_LEN: usize = 2048; - - loop { - print!("> "); - std::io::stdout().flush().unwrap(); - let mut tap_command = String::new(); - std::io::stdin() - .read_line(&mut tap_command) - .expect("Failed to read line"); - - tap_command = tap_command.trim().to_string(); - if tap_command.len() == 0 { - continue; - } else if tap_command == "." { - break; - } - let tap_bytes = tap_command.as_bytes(); - - // 1 byte report ID + 2 bytes field ID + 1 byte NUL - let mut request_report = vec![0u8; 1 + tap_bytes.len() + 1].into_boxed_slice(); - - // Packet captures indicate that "lc" is also a valid field type for some devices, but on mine - // it always returns a bus error (both when I send it and when the official updater does). - request_report[0] = TAP_REPORT_ID; - request_report[1..tap_bytes.len() + 1].copy_from_slice(tap_bytes); - - device - .send_feature_report(&request_report) - .map_err(|e| Error::DeviceIoError { - source: e, - action: "running TAP command", - })?; - - let mut response_report = [0u8; 1 + TAP_REPORT_LEN]; - response_report[0] = TAP_REPORT_ID; - map_gfr( - device.get_feature_report(&mut response_report), - 1, - "reading TAP command response", - )?; - - trace!( - "Raw {:?} TAP command response: {:02x?}", - tap_command, response_report - ); - - // Result is all the bytes after the report ID and before the first NUL. - let result = response_report[1..].split(|&x| x == 0).next().unwrap(); - println!("{:?}", std::str::from_utf8(result)); - } +/// Read an information field (as listed in [InfoField]) from the normal firmware. `device` must +/// NOT be in DFU mode. +pub fn read_info_field(device: &HidDevice, field: InfoField) -> Result { + use InfoField::*; - Ok(()) + // Packet captures indicate that "lc" is also a valid field type for some devices, but on mine + // it always returns a bus error (both when I send it and when the official updater does). + run_tap_command( + device, + match field { + DeviceModel => b"pl", + SerialNumber => b"sn", + CurrentFirmware => b"vr", + }, + ) } /// Put a device running the normal firmware into DFU mode. `device` must NOT be in DFU mode. From 6ae8c87885474e69f14a0a3eaf1e2fa1dabcb2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Fouren?= Date: Mon, 28 Jul 2025 12:51:31 +0800 Subject: [PATCH 4/5] Fix more comments: 1: Remove unneccesary features from rustyline 2: Fix command history 3: Remove history file 4: Fix typo's in README.md 5: Fix comment in run_tap_command 6: Use escape_ascii instead of from_utf8 7: Remove Soundlink mini II hack to get full output string --- Cargo.lock | 54 ------------------------------------------------- Cargo.toml | 2 +- README.md | 4 ++-- src/main.rs | 19 +++-------------- src/protocol.rs | 10 +++------ 5 files changed, 9 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 503510f..9e010d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,12 +172,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "env_filter" version = "0.1.3" @@ -222,17 +216,6 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "hashbrown" version = "0.15.2" @@ -258,15 +241,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "indexmap" version = "2.9.0" @@ -331,15 +305,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - [[package]] name = "nix" version = "0.30.1" @@ -427,16 +392,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rustix" version = "1.0.5" @@ -459,13 +414,10 @@ dependencies = [ "bitflags", "cfg-if", "clipboard-win", - "fd-lock", - "home", "libc", "log", "memchr", "nix", - "radix_trie", "unicode-segmentation", "unicode-width", "utf8parse", @@ -498,12 +450,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 55a84c5..1155626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ crc32fast = "1.2" anyhow = "1.0" clap = { version = "4.0", features = ["derive", "wrap_help"] } env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] } -rustyline = "16.0.0" +rustyline = { version = "16.0.0", default-features = false } [profile.release] strip = "symbols" diff --git a/README.md b/README.md index 5155f31..784c4fe 100644 --- a/README.md +++ b/README.md @@ -129,10 +129,10 @@ download`, and `bose-dfu leave-dfu`, in that order. The other subcommands help you inspect the current state of devices and firmware files. Notable is `info`, which tells you the current firmware version a device is running. -The `tap` subcommand can be used to start an interactvice shell with the device +The `tap` subcommand can be used to start an interactive shell with the device allowing you to send maintenance commands to the device, useful for servicing purposes (like putting the device into shipmode when changing the battery). -Refer to your products service manual for available commands. To exit the shell +Refer to your product's service manual for available commands. To exit the shell you may use a single `.`, `` or ``. Subcommands that perform an operation on a device support arguments for diff --git a/src/main.rs b/src/main.rs index fe03e27..133919e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,8 @@ use hidapi::{DeviceInfo, HidApi, HidDevice}; use log::{info, warn}; use rustyline::DefaultEditor; use rustyline::error::ReadlineError; -use std::env; use std::io::Read; -use std::path::{Path, PathBuf}; +use std::path::Path; use thiserror::Error; use bose_dfu::device_ids::{DeviceCompat, DeviceMode, UsbId, identify_device}; @@ -193,17 +192,8 @@ fn list_cmd(hidapi: &HidApi) { } } -pub fn tap_command_loop(device: &HidDevice) -> Result<()> { - const HISTORY_NAME: &str = ".bose-dfu_history"; - +fn tap_command_loop(device: &HidDevice) -> Result<()> { let mut rl = DefaultEditor::new()?; - let history_file = match env::home_dir() { - Some(home_path) => home_path.join(HISTORY_NAME), - None => PathBuf::from(HISTORY_NAME), - }; - if rl.load_history(&history_file).is_err() { - println!("No previous history."); - } loop { let readline = rl.readline("> "); @@ -214,6 +204,7 @@ pub fn tap_command_loop(device: &HidDevice) -> Result<()> { } else if line == "." { break; } + rl.add_history_entry(line.as_str())?; let result = run_tap_command(device, line.as_bytes()); println!("{result:?}"); @@ -233,10 +224,6 @@ pub fn tap_command_loop(device: &HidDevice) -> Result<()> { } } - if rl.save_history(&history_file).is_err() { - println!("Cant save history.") - } - Ok(()) } diff --git a/src/protocol.rs b/src/protocol.rs index e892bed..374664e 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -145,7 +145,7 @@ pub fn run_tap_command(device: &HidDevice, tap_bytes: &[u8]) -> Result Result response_report[1..].split(|&x| x == 0).next().unwrap(), - _ => response_report.split(|&x| x == 0).next().unwrap(), - }; + let result = response_report[1..].split(|&x| x == 0).next().unwrap(); Ok(std::str::from_utf8(result) .map_err(|e| Error::ProtocolError(e.into()))? From 69282af6ce7bfe880510858d3eea31e500cffd19 Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 28 Jul 2025 01:40:37 -0400 Subject: [PATCH 5/5] Use a doc comment (///) for run_tap_command() --- src/protocol.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol.rs b/src/protocol.rs index 374664e..b986206 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -139,8 +139,8 @@ pub enum InfoField { CurrentFirmware, } -// Run a "TAP command" on the device. This is the general way to communicate with Bose DFU devices -// 'device' must NOT be in DFU mode. +/// Run a "TAP command" on the device. This is the general way to communicate with Bose devices. +/// 'device' must NOT be in DFU mode. pub fn run_tap_command(device: &HidDevice, tap_bytes: &[u8]) -> Result { const TAP_REPORT_ID: u8 = 2; const TAP_REPORT_LEN: usize = 126;