apps: sync cell constant K with device, verify export format compatibility

This commit is contained in:
jess 2026-03-31 20:59:44 -07:00
parent 4e0dfecce0
commit 34b298dfe2
6 changed files with 49 additions and 0 deletions

View File

@ -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)
}
}

View File

@ -209,6 +209,7 @@ extension BLEManager: CBPeripheralDelegate {
if characteristic.isNotifying {
state = .connected
sendCommand(buildSysexGetConfig())
sendCommand(buildSysexGetCellK())
}
}

View File

@ -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]
}

View File

@ -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)

View File

@ -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();

View File

@ -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<EisMessage> {
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<u8> {
pub fn build_sysex_clear_refs() -> Vec<u8> {
vec![0xF0, SYSEX_MFR, CMD_CLEAR_REFS, 0xF7]
}
pub fn build_sysex_set_cell_k(k: f32) -> Vec<u8> {
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<u8> {
vec![0xF0, SYSEX_MFR, CMD_GET_CELL_K, 0xF7]
}