Open SARDONYX-sard opened 1 year ago
By the way, the battery acquisition by AT command is as follows.
Note that there is a dependency on the io_bluetooth crate.
extern crate io_bluetooth;
use std::collections::HashMap;
use std::io;
use std::iter;
use regex::Regex;
use io_bluetooth::bt::{self, BtStream};
fn main() -> io::Result<()> {
let devices = bt::discover_devices()?;
println!("Devices:");
for (idx, device) in devices.iter().enumerate() {
println!("{}: {}", idx, *device);
}
if devices.len() == 0 {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"No Bluetooth devices found.",
));
}
let mut device_idx = 0;
if let Some(idx) = devices
.iter()
.position(|&x| x.to_string() == "your BT MAC")
{
device_idx = idx;
};
// let device_idx = request_device_idx(devices.len())?;
let socket = BtStream::connect(iter::once(&devices[device_idx]), bt::BtProtocol::RFCOMM)?;
match socket.peer_addr() {
Ok(name) => println!("Peername: {}.", name),
Err(err) => println!("An error occurred while retrieving the peername: {:?}", err),
}
match socket.local_addr() {
Ok(name) => println!("Socket name: {}", name),
Err(err) => println!("An error occurred while retrieving the sockname: {:?}", err),
}
let mut buffer = vec![0; 1024];
let result = loop {
match socket.recv(&mut buffer[..]) {
// recv & send data. AT Command
// In AT commands, AT stands for Attention and these commands are used for controlling MODEMs.
// - https://www.elprocus.com/at-commands-tutorial/
//
// Test Command e.g.: AD =?
// Read Command e.g.: AT+CBC =?
// Set Command e.g.: AT+CBC =”+923140”, 110
Ok(len) if len == 0 => continue,
Ok(len) => {
let recv_cmd = String::from_utf8_lossy(&buffer).to_string();
println!("Received {} bytes. {}", len, recv_cmd);
match recv_cmd {
recv_cmd if recv_cmd.contains("BRSF") => {
// BRSF: AG Supported features
// - https://stackoverflow.com/questions/26342372/android-communicating-to-hfp-device-via-at-commands
socket.send(b"\r\n+BRSF: 1024\r\n")?;
socket.send(b"\r\nOK\r\n")?;
println!("send BRSF");
}
recv_cmd if recv_cmd.contains("CIND=") => {
// Set device's indicator.
// - https://m2msupport.net/m2msupport/atcind-indicator-control/
socket.send(b"\r\n+CIND:(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"battchg\",(0-5))\r\n",)?;
socket.send(b"\r\nOK\r\n")?;
println!("send CIND");
}
recv_cmd if recv_cmd.contains("CIND?") => {
socket.send(b"\r\n+CIND: 0,0,0,0,3\r\n")?;
socket.send(b"\r\nOK\r\n")?;
println!("send CIND 3");
}
recv_cmd if recv_cmd.contains("BIND=?") => {
// Announce that we support the battery level HF indicator
// https://www.bluetooth.com/specifications/assigned-numbers/hands-free-profile/
socket.send(b"\r\n+BIND: (2)\r\n")?;
socket.send(b"\r\nOK\r\n")?;
println!("send BIND");
}
recv_cmd if recv_cmd.contains("BIND?") => {
// Enable battery level HF indicator
socket.send(b"\r\n+BIND: 2,1\r\n")?;
socket.send(b"\r\nOK\r\n")?;
println!("send BIND2,1");
}
recv_cmd if recv_cmd.contains("XAPL=") => {
// AT command to check if battery reporting is supported.
// - P.79(https://www.bluetooth.com/wp-content/uploads/attachments/BluetoothDesignGuidelines.pdf)
socket.send(b"\r\n+XAPL=iPhone,7\r\n")?;
socket.send(b"\r\nOK\r\n")?;
}
recv_cmd if recv_cmd.contains("IPHONEACCEV") => {
// # IPHONEACCEV
//
// Parse Battery level. Apple's own extension, but supported by many Bluetooth.
//
// - P.80(https://www.bluetooth.com/wp-content/uploads/attachments/BluetoothDesignGuidelines.pdf)
//
// Format: AT+IPHONEACCEV=Number of key/value pairs,key1,val1,key2,val2,...
// - Number of key/value pairs: The number of parameters coming next.
// - key: the type of change being reported:
// - 1 = Battery Level
// - 2 = Dock State
// - val: the value of the change:
// - Battery Level: string value between '0' and '9'
// - Dock State: 0 = undocked, 1 = docked
let re = Regex::new(r"(?:IPHONEACCEV=)(\d),(\d),(\d)").unwrap();
let binding = re
.find(recv_cmd.as_str())
.unwrap()
.as_str()
.replace("IPHONEACCEV=", "");
println!("{binding}");
let parts = binding.split(",").collect::<Vec<_>>()[1..].to_vec();
println!("parts: {:?}", parts);
if parts.len() > 1 && (parts.len() % 2) == 0 {
let mut parts = parts.iter();
let params = parts
.clone()
.zip(parts.skip(1))
.step_by(2)
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<_, _>>();
println!("{:?}", params);
if params.contains_key("1") {
// 0~9(9 == 100%)
break Some((params["1"].parse::<i32>().unwrap() + 1) * 10);
}
}
}
recv_cmd if recv_cmd.contains("BIEV=") => {
println!("try send BIEV");
let params = recv_cmd
.trim()
.split("=")
.nth(1)
.unwrap()
.split(",")
.collect::<Vec<_>>();
if params[0] == "2" {
break Some(params[1].parse::<i32>().unwrap());
}
socket.send(b"\r\nOK\r\n")?;
}
recv_cmd if recv_cmd.contains("XEVENT=BATTERY") => {
// For Android >=3.0
// https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/722
let params = recv_cmd
.trim()
.split("=")
.nth(1)
.unwrap()
.split(",")
.collect::<Vec<_>>();
if params.len() >= 3 {
// AT+XEVENT=BATTERY,6,11,461,0
break Some(
params[1].parse::<i32>().unwrap() * 100
/ params[2].parse::<i32>().unwrap(),
);
} else {
// AT+XEVENT=B
break Some((params[1].parse::<i32>().unwrap() + 1) * 10);
};
}
_ => {
socket.send(b"\r\nOK\r\n")?;
}
};
}
Err(err) => return Err(err),
};
};
match result {
Some(buf) => {
println!("{}", buf);
Ok(())
}
None => panic!("Failed to get recv."),
}
}
Currently this application is not at the paid level.
The current spec is to get the battery level via a PowerShell script at set time intervals (default is 1 hour). However, paid apps of the same type are likely to use some kind of Socket communication and event-triggered type processing instead of interval processing.
For example, the Windows API WSAPoll function.
However, the AT command call of the IPhone extension via Socket communication seems to be very useless as it disconnects the Bluetooth classic device with an error sound when sent (it may just be a bad usage).
In summary, we need the following two things.
Obtaining battery level information without generating an error and disconnection by socket communication
Use WSAPoll to drive AT Command triggering information updates (assuming the above error does not occur).
Possible reference sites
WSAPoll example
WSAPoll function (winsock2.h)
Windows-classic-samples
AT Commnad(soket send())