From 34b298dfe20004bf79667c7e414720ddec39a2b1 Mon Sep 17 00:00:00 2001 From: jess Date: Tue, 31 Mar 2026 20:59:44 -0700 Subject: [PATCH] apps: sync cell constant K with device, verify export format compatibility --- cue-ios/CueIOS/AppState.swift | 4 ++++ cue-ios/CueIOS/BLE/BLEManager.swift | 1 + cue-ios/CueIOS/Models/Protocol.swift | 18 ++++++++++++++++++ cue-ios/CueIOS/Views/CalibrateView.swift | 1 + cue/src/app.rs | 6 ++++++ cue/src/protocol.rs | 19 +++++++++++++++++++ 6 files changed, 49 insertions(+) diff --git a/cue-ios/CueIOS/AppState.swift b/cue-ios/CueIOS/AppState.swift index 5ea4b21..210f244 100644 --- a/cue-ios/CueIOS/AppState.swift +++ b/cue-ios/CueIOS/AppState.swift @@ -270,6 +270,10 @@ final class AppState { if !hasRefs { status = "No device refs" } + + case .cellK(let k): + calCellConstant = Double(k) + status = String(format: "Device cell constant: %.4f cm\u{207B}\u{00B9}", k) } } diff --git a/cue-ios/CueIOS/BLE/BLEManager.swift b/cue-ios/CueIOS/BLE/BLEManager.swift index 5a90611..ce03e4c 100644 --- a/cue-ios/CueIOS/BLE/BLEManager.swift +++ b/cue-ios/CueIOS/BLE/BLEManager.swift @@ -209,6 +209,7 @@ extension BLEManager: CBPeripheralDelegate { if characteristic.isNotifying { state = .connected sendCommand(buildSysexGetConfig()) + sendCommand(buildSysexGetCellK()) } } diff --git a/cue-ios/CueIOS/Models/Protocol.swift b/cue-ios/CueIOS/Models/Protocol.swift index 61770ce..a0702f3 100644 --- a/cue-ios/CueIOS/Models/Protocol.swift +++ b/cue-ios/CueIOS/Models/Protocol.swift @@ -24,6 +24,7 @@ let RSP_CL_RESULT: UInt8 = 0x0D let RSP_CL_END: UInt8 = 0x0E let RSP_PH_RESULT: UInt8 = 0x0F let RSP_TEMP: UInt8 = 0x10 +let RSP_CELL_K: UInt8 = 0x11 let RSP_REF_FRAME: UInt8 = 0x20 let RSP_REF_LP_RANGE: UInt8 = 0x21 let RSP_REFS_DONE: UInt8 = 0x22 @@ -43,6 +44,8 @@ let CMD_STOP_AMP: UInt8 = 0x22 let CMD_START_CL: UInt8 = 0x23 let CMD_START_PH: UInt8 = 0x24 let CMD_START_CLEAN: UInt8 = 0x25 +let CMD_SET_CELL_K: UInt8 = 0x28 +let CMD_GET_CELL_K: UInt8 = 0x29 let CMD_START_REFS: UInt8 = 0x30 let CMD_GET_REFS: UInt8 = 0x31 let CMD_CLEAR_REFS: UInt8 = 0x32 @@ -119,6 +122,7 @@ enum EisMessage { case refLpRange(mode: UInt8, lowIdx: UInt8, highIdx: UInt8) case refsDone case refStatus(hasRefs: Bool) + case cellK(Float) } // MARK: - Response parser @@ -247,6 +251,9 @@ func parseSysex(_ data: [UInt8]) -> EisMessage? { case RSP_REF_STATUS where p.count >= 1: return .refStatus(hasRefs: p[0] != 0) + case RSP_CELL_K where p.count >= 5: + return .cellK(decodeFloat(p, at: 0)) + default: return nil } @@ -353,3 +360,14 @@ func buildSysexGetRefs() -> [UInt8] { func buildSysexClearRefs() -> [UInt8] { [0xF0, sysexMfr, CMD_CLEAR_REFS, 0xF7] } + +func buildSysexSetCellK(_ k: Float) -> [UInt8] { + var sx: [UInt8] = [0xF0, sysexMfr, CMD_SET_CELL_K] + sx.append(contentsOf: encodeFloat(k)) + sx.append(0xF7) + return sx +} + +func buildSysexGetCellK() -> [UInt8] { + [0xF0, sysexMfr, CMD_GET_CELL_K, 0xF7] +} diff --git a/cue-ios/CueIOS/Views/CalibrateView.swift b/cue-ios/CueIOS/Views/CalibrateView.swift index 16acea2..95d41e7 100644 --- a/cue-ios/CueIOS/Views/CalibrateView.swift +++ b/cue-ios/CueIOS/Views/CalibrateView.swift @@ -101,6 +101,7 @@ struct CalibrateView: View { let k = cellConstant(kappaMsCm: kappa, rsOhm: Double(rs)) state.calRs = Double(rs) state.calCellConstant = k + state.send(buildSysexSetCellK(Float(k))) state.status = String(format: "K = %.4f cm\u{207B}\u{00B9} (Rs = %.1f \u{2126})", k, rs) } .disabled(state.eisPoints.isEmpty) diff --git a/cue/src/app.rs b/cue/src/app.rs index 91b8ef4..71e56f8 100644 --- a/cue/src/app.rs +++ b/cue/src/app.rs @@ -571,6 +571,7 @@ impl App { self.cmd_tx = Some(tx); self.ble_connected = true; self.send_cmd(&protocol::build_sysex_get_config()); + self.send_cmd(&protocol::build_sysex_get_cell_k()); } Message::BleStatus(s) => { if s.contains("Reconnecting") || s.contains("Looking") { @@ -740,6 +741,10 @@ impl App { self.status = "No device refs".into(); } } + EisMessage::CellK(k) => { + self.cal_cell_constant = Some(k); + self.status = format!("Device cell constant: {:.4} cm-1", k); + } }, Message::TabSelected(t) => { if t == Tab::Browse { @@ -951,6 +956,7 @@ impl App { if let Some(rs) = extract_rs(&self.eis_points) { let k = cell_constant(kappa, rs); self.cal_cell_constant = Some(k); + self.send_cmd(&protocol::build_sysex_set_cell_k(k)); self.status = format!("Cell constant: {:.4} cm-1 (Rs={:.1} ohm)", k, rs); } else { self.status = "No valid EIS data for Rs extraction".into(); diff --git a/cue/src/protocol.rs b/cue/src/protocol.rs index 9eedf73..9ab45d6 100644 --- a/cue/src/protocol.rs +++ b/cue/src/protocol.rs @@ -24,6 +24,7 @@ pub const RSP_TEMP: u8 = 0x10; pub const RSP_REF_FRAME: u8 = 0x20; pub const RSP_REF_LP_RANGE: u8 = 0x21; pub const RSP_REFS_DONE: u8 = 0x22; +pub const RSP_CELL_K: u8 = 0x11; pub const RSP_REF_STATUS: u8 = 0x23; /* Cue → ESP32 */ @@ -41,6 +42,8 @@ pub const CMD_GET_TEMP: u8 = 0x17; pub const CMD_START_CL: u8 = 0x23; pub const CMD_START_PH: u8 = 0x24; pub const CMD_START_CLEAN: u8 = 0x25; +pub const CMD_SET_CELL_K: u8 = 0x28; +pub const CMD_GET_CELL_K: u8 = 0x29; pub const CMD_START_REFS: u8 = 0x30; pub const CMD_GET_REFS: u8 = 0x31; pub const CMD_CLEAR_REFS: u8 = 0x32; @@ -254,6 +257,7 @@ pub enum EisMessage { RefLpRange { mode: u8, low_idx: u8, high_idx: u8 }, RefsDone, RefStatus { has_refs: bool }, + CellK(f32), } fn decode_u16(data: &[u8]) -> u16 { @@ -405,6 +409,10 @@ pub fn parse_sysex(data: &[u8]) -> Option { RSP_REF_STATUS if data.len() >= 3 => { Some(EisMessage::RefStatus { has_refs: data[2] != 0 }) } + RSP_CELL_K if data.len() >= 7 => { + let p = &data[2..]; + Some(EisMessage::CellK(decode_float(&p[0..5]))) + } _ => None, } } @@ -508,3 +516,14 @@ pub fn build_sysex_get_refs() -> Vec { pub fn build_sysex_clear_refs() -> Vec { vec![0xF0, SYSEX_MFR, CMD_CLEAR_REFS, 0xF7] } + +pub fn build_sysex_set_cell_k(k: f32) -> Vec { + let mut sx = vec![0xF0, SYSEX_MFR, CMD_SET_CELL_K]; + sx.extend_from_slice(&encode_float(k)); + sx.push(0xF7); + sx +} + +pub fn build_sysex_get_cell_k() -> Vec { + vec![0xF0, SYSEX_MFR, CMD_GET_CELL_K, 0xF7] +}