merge integration

# Conflicts:
#	cue-ios/CueIOS/AppState.swift
#	cue-ios/CueIOS/Models/Protocol.swift
#	cue/src/app.rs
#	cue/src/protocol.rs
#	main/protocol.h
#	main/wifi_transport.c
This commit is contained in:
jess 2026-04-03 02:30:58 -07:00
commit 5f550f031a
8 changed files with 65 additions and 9 deletions

View File

@ -323,6 +323,9 @@ final class AppState {
phSlope = Double(slope) phSlope = Double(slope)
phOffset = Double(offset) phOffset = Double(offset)
status = String(format: "pH cal: slope=%.4f offset=%.4f", slope, offset) status = String(format: "pH cal: slope=%.4f offset=%.4f", slope, offset)
case .keepalive:
break
} }
} }

View File

@ -31,6 +31,7 @@ let RSP_REFS_DONE: UInt8 = 0x22
let RSP_REF_STATUS: UInt8 = 0x23 let RSP_REF_STATUS: UInt8 = 0x23
let RSP_CL_FACTOR: UInt8 = 0x24 let RSP_CL_FACTOR: UInt8 = 0x24
let RSP_PH_CAL: UInt8 = 0x25 let RSP_PH_CAL: UInt8 = 0x25
let RSP_KEEPALIVE: UInt8 = 0x50
// Cue -> ESP32 // Cue -> ESP32
let CMD_SET_SWEEP: UInt8 = 0x10 let CMD_SET_SWEEP: UInt8 = 0x10
@ -131,6 +132,7 @@ enum EisMessage {
case cellK(Float) case cellK(Float)
case clFactor(Float) case clFactor(Float)
case phCal(slope: Float, offset: Float) case phCal(slope: Float, offset: Float)
case keepalive
} }
// MARK: - Response parser // MARK: - Response parser
@ -271,6 +273,9 @@ func parseSysex(_ data: [UInt8]) -> EisMessage? {
offset: decodeFloat(p, at: 5) offset: decodeFloat(p, at: 5)
) )
case RSP_KEEPALIVE:
return .keepalive
default: default:
return nil return nil
} }

View File

@ -866,6 +866,7 @@ impl App {
self.ph_offset = Some(offset); self.ph_offset = Some(offset);
self.status = format!("pH cal: slope={:.4} offset={:.4}", slope, offset); self.status = format!("pH cal: slope={:.4} offset={:.4}", slope, offset);
} }
EisMessage::Keepalive => {}
}, },
Message::TabSelected(t) => { Message::TabSelected(t) => {
if t == Tab::Browse { if t == Tab::Browse {

View File

@ -28,6 +28,7 @@ pub const RSP_CELL_K: u8 = 0x11;
pub const RSP_REF_STATUS: u8 = 0x23; pub const RSP_REF_STATUS: u8 = 0x23;
pub const RSP_CL_FACTOR: u8 = 0x24; pub const RSP_CL_FACTOR: u8 = 0x24;
pub const RSP_PH_CAL: u8 = 0x25; pub const RSP_PH_CAL: u8 = 0x25;
pub const RSP_KEEPALIVE: u8 = 0x50;
/* Cue → ESP32 */ /* Cue → ESP32 */
pub const CMD_SET_SWEEP: u8 = 0x10; pub const CMD_SET_SWEEP: u8 = 0x10;
@ -266,6 +267,7 @@ pub enum EisMessage {
CellK(f32), CellK(f32),
ClFactor(f32), ClFactor(f32),
PhCal { slope: f32, offset: f32 }, PhCal { slope: f32, offset: f32 },
Keepalive,
} }
fn decode_u16(data: &[u8]) -> u16 { fn decode_u16(data: &[u8]) -> u16 {
@ -432,6 +434,7 @@ pub fn parse_sysex(data: &[u8]) -> Option<EisMessage> {
offset: decode_float(&p[5..10]), offset: decode_float(&p[5..10]),
}) })
} }
RSP_KEEPALIVE => Some(EisMessage::Keepalive),
_ => None, _ => None,
} }
} }

View File

@ -327,7 +327,14 @@ int echem_clean(float v_mv, float duration_s)
AD5940_LPDAC0WriteS(code, VZERO_CODE); AD5940_LPDAC0WriteS(code, VZERO_CODE);
printf("Clean: %.0f mV for %.0f s\n", v_mv, duration_s); printf("Clean: %.0f mV for %.0f s\n", v_mv, duration_s);
vTaskDelay(pdMS_TO_TICKS((uint32_t)(duration_s * 1000.0f)));
uint32_t remain_ms = (uint32_t)(duration_s * 1000.0f);
while (remain_ms > 0) {
uint32_t chunk = remain_ms > 3000 ? 3000 : remain_ms;
vTaskDelay(pdMS_TO_TICKS(chunk));
remain_ms -= chunk;
if (remain_ms > 0) send_keepalive();
}
echem_shutdown_lp(); echem_shutdown_lp();
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
@ -497,7 +504,15 @@ static uint32_t sample_phase(float v_mv, float t_dep_ms, float t_meas_ms,
AD5940_LPDAC0WriteS(mv_to_vbias_code(v_mv), VZERO_CODE); AD5940_LPDAC0WriteS(mv_to_vbias_code(v_mv), VZERO_CODE);
/* settling — no samples recorded */ /* settling — no samples recorded */
vTaskDelay(pdMS_TO_TICKS((uint32_t)t_dep_ms)); {
uint32_t remain_ms = (uint32_t)t_dep_ms;
while (remain_ms > 0) {
uint32_t chunk = remain_ms > 3000 ? 3000 : remain_ms;
vTaskDelay(pdMS_TO_TICKS(chunk));
remain_ms -= chunk;
if (remain_ms > 0) send_keepalive();
}
}
/* measurement — sample at ~50ms intervals */ /* measurement — sample at ~50ms intervals */
uint32_t n_samples = (uint32_t)(t_meas_ms / 50.0f + 0.5f); uint32_t n_samples = (uint32_t)(t_meas_ms / 50.0f + 0.5f);
@ -541,7 +556,15 @@ int echem_chlorine(const ClConfig *cfg, ClPoint *out, uint32_t max_points,
printf("Cl: conditioning at %.0f mV for %.0f ms\n", cfg->v_cond, cfg->t_cond_ms); printf("Cl: conditioning at %.0f mV for %.0f ms\n", cfg->v_cond, cfg->t_cond_ms);
AD5940_LPDAC0WriteS(mv_to_vbias_code(cfg->v_cond), VZERO_CODE); AD5940_LPDAC0WriteS(mv_to_vbias_code(cfg->v_cond), VZERO_CODE);
vTaskDelay(pdMS_TO_TICKS((uint32_t)cfg->t_cond_ms)); {
uint32_t remain_ms = (uint32_t)cfg->t_cond_ms;
while (remain_ms > 0) {
uint32_t chunk = remain_ms > 3000 ? 3000 : remain_ms;
vTaskDelay(pdMS_TO_TICKS(chunk));
remain_ms -= chunk;
if (remain_ms > 0) send_keepalive();
}
}
printf("Cl: free chlorine at %.0f mV\n", cfg->v_free); printf("Cl: free chlorine at %.0f mV\n", cfg->v_free);
idx = sample_phase(cfg->v_free, cfg->t_dep_ms, cfg->t_meas_ms, idx = sample_phase(cfg->v_free, cfg->t_dep_ms, cfg->t_meas_ms,
@ -579,7 +602,14 @@ int echem_ph_ocp(const PhConfig *cfg, PhResult *result)
AD5940_ADCBaseCfgS(&adc); AD5940_ADCBaseCfgS(&adc);
printf("pH: stabilizing %0.f s\n", cfg->stabilize_s); printf("pH: stabilizing %0.f s\n", cfg->stabilize_s);
vTaskDelay(pdMS_TO_TICKS((uint32_t)(cfg->stabilize_s * 1000.0f)));
uint32_t remain_ms = (uint32_t)(cfg->stabilize_s * 1000.0f);
while (remain_ms > 0) {
uint32_t chunk = remain_ms > 3000 ? 3000 : remain_ms;
vTaskDelay(pdMS_TO_TICKS(chunk));
remain_ms -= chunk;
if (remain_ms > 0) send_keepalive();
}
/* average N readings of V(SE0) and V(RE0) */ /* average N readings of V(SE0) and V(RE0) */
#define PH_AVG_N 10 #define PH_AVG_N 10

View File

@ -144,6 +144,14 @@ static int send_sysex(const uint8_t *sysex, uint16_t len)
return wifi_send_sysex(sysex, len); return wifi_send_sysex(sysex, len);
} }
/* ---- outbound: keepalive ---- */
int send_keepalive(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_KEEPALIVE, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
/* ---- outbound: EIS ---- */ /* ---- outbound: EIS ---- */
int send_sweep_start(uint32_t num_points, float freq_start, float freq_stop) int send_sweep_start(uint32_t num_points, float freq_start, float freq_stop)

View File

@ -62,6 +62,7 @@
#define RSP_REF_STATUS 0x23 #define RSP_REF_STATUS 0x23
#define RSP_CL_FACTOR 0x24 #define RSP_CL_FACTOR 0x24
#define RSP_PH_CAL 0x25 #define RSP_PH_CAL 0x25
#define RSP_KEEPALIVE 0x50
/* Session sync responses (0x4x) */ /* Session sync responses (0x4x) */
#define RSP_SESSION_CREATED 0x40 #define RSP_SESSION_CREATED 0x40
@ -152,6 +153,9 @@ int send_ref_lp_range(uint8_t mode, uint8_t low_idx, uint8_t high_idx);
int send_refs_done(void); int send_refs_done(void);
int send_ref_status(uint8_t has_refs); int send_ref_status(uint8_t has_refs);
/* keepalive (sent during long blocking ops) */
int send_keepalive(void);
/* session management */ /* session management */
const Session *session_get_all(uint8_t *count); const Session *session_get_all(uint8_t *count);
uint8_t session_get_current(void); uint8_t session_get_current(void);

View File

@ -18,7 +18,9 @@
#define UDP_PORT 5941 #define UDP_PORT 5941
#define UDP_BUF_SIZE 128 #define UDP_BUF_SIZE 128
#define MAX_UDP_CLIENTS 10 #define UDP_CLIENTS_MAX 16
#define CLIENT_TIMEOUT_MS 30000
static int udp_sock = -1; static int udp_sock = -1;
static esp_netif_t *ap_netif; static esp_netif_t *ap_netif;
@ -26,7 +28,7 @@ static struct {
struct sockaddr_in addr; struct sockaddr_in addr;
uint8_t mac[6]; uint8_t mac[6];
bool active; bool active;
} clients[MAX_UDP_CLIENTS]; } clients[UDP_CLIENTS_MAX];
static int client_count; static int client_count;
@ -38,7 +40,7 @@ static void client_touch(const struct sockaddr_in *addr)
return; return;
} }
if (client_count < MAX_UDP_CLIENTS) { if (client_count < UDP_CLIENTS_MAX) {
clients[client_count].addr = *addr; clients[client_count].addr = *addr;
clients[client_count].active = true; clients[client_count].active = true;
memset(clients[client_count].mac, 0, 6); memset(clients[client_count].mac, 0, 6);
@ -56,7 +58,7 @@ static void client_touch(const struct sockaddr_in *addr)
} }
client_count++; client_count++;
printf("UDP: client added (%d/%d)\n", client_count, MAX_UDP_CLIENTS); printf("UDP: client added (%d)\n", client_count);
} }
} }
@ -65,7 +67,7 @@ static void client_remove_by_mac(const uint8_t *mac)
for (int i = 0; i < client_count; ) { for (int i = 0; i < client_count; ) {
if (memcmp(clients[i].mac, mac, 6) == 0) { if (memcmp(clients[i].mac, mac, 6) == 0) {
clients[i] = clients[--client_count]; clients[i] = clients[--client_count];
printf("UDP: client removed by MAC (%d/%d)\n", client_count, MAX_UDP_CLIENTS); printf("UDP: client removed by MAC (%d)\n", client_count);
} else { } else {
i++; i++;
} }