fix: per-cycle control slot to close reconnect race, idempotent battery icon

pull/3/head
lambda 7 days ago
parent 9ec7b78e98
commit d194d2a226
No known key found for this signature in database
GPG Key ID: CF45A8EE158BBCB6

@ -46,15 +46,17 @@ const CONTROL_POLL_INTERVAL: Duration = Duration::from_millis(50);
/// Реальный источник данных через btleplug.
pub(crate) struct BleSource {
adapter: Adapter,
/// Last control result (set by the notification router, awaited by `request_control`).
control: Arc<Mutex<Option<ControlResult>>>,
/// Current cycle's control-result slot. Each `stream()` swaps in a fresh slot so a
/// still-winding-down router from the previous cycle writes to a discarded slot,
/// never the one the new cycle's `request_control` polls.
control: Mutex<Arc<Mutex<Option<ControlResult>>>>,
}
impl BleSource {
pub(crate) fn new(adapter: Adapter) -> Self {
Self {
adapter,
control: Arc::new(Mutex::new(None)),
control: Mutex::new(Arc::new(Mutex::new(None))),
}
}
@ -130,7 +132,10 @@ impl SensorSource for BleSource {
// This keeps the control-result flow concurrent with the telemetry consumer so that
// `request_control` can await confirmation while telemetry is still being consumed.
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Telemetry>();
let control = self.control.clone();
// Swap in a fresh control slot for this cycle; the previous cycle's router holds
// the old slot and can only write there, so it cannot race this cycle's poll.
let control = Arc::new(Mutex::new(None));
*self.control.lock().unwrap() = control.clone();
let (cancel_tx, cancel_rx) = oneshot::channel::<()>();
tokio::spawn(async move {
@ -191,7 +196,8 @@ impl SensorSource for BleSource {
.ok_or_else(|| BleError::DeviceNotFound(cp_uuid.to_string()))?;
// Reset and send Request Control (opcode 0x00).
*self.control.lock().unwrap() = None;
let slot = self.control.lock().unwrap().clone();
*slot.lock().unwrap() = None;
let (_, payload) = encode_command(Command::FtmsRequestControl);
peripheral
.write(&ch, &payload, WriteType::WithoutResponse)
@ -199,9 +205,9 @@ impl SensorSource for BleSource {
.map_err(BleError::WriteFailed)?;
// Wait for the Control Point Response (routed by the stream's notification task).
let result = tokio::time::timeout(CONTROL_CONFIRM_TIMEOUT, async {
let result = tokio::time::timeout(CONTROL_CONFIRM_TIMEOUT, async move {
loop {
let current = *self.control.lock().unwrap();
let current = *slot.lock().unwrap();
if let Some(r) = current {
return r;
}

@ -142,7 +142,10 @@ once("devices-loaded", (event) => {
listen("battery", (event) => {
const { device_id, percent } = event.payload;
const card = deviceCards.get(device_id);
if (card) card.querySelector(".device__head").append(batteryIcon(percent));
if (!card) return;
const head = card.querySelector(".device__head");
head.querySelector(".battery")?.remove();
head.append(batteryIcon(percent));
});
listen("telemetry", (event) => {

Loading…
Cancel
Save