firmware: strip BLE/NimBLE, WiFi-only transport with session sync protocol

This commit is contained in:
jess 2026-04-01 00:33:11 -07:00
parent fc0ff900f1
commit d13909c400
11 changed files with 738 additions and 1126 deletions

View File

@ -1,6 +1,6 @@
idf_component_register(SRCS "eis4.c" "eis.c" "echem.c" "ble.c" "wifi_transport.c" "temp.c" "refs.c" idf_component_register(SRCS "eis4.c" "eis.c" "echem.c" "protocol.c" "wifi_transport.c" "temp.c" "refs.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES ad5941 ad5941_port bt nvs_flash esp_wifi esp_netif esp_event) REQUIRES ad5941 ad5941_port nvs_flash esp_wifi esp_netif esp_event)
if(DEFINED ENV{WIFI_SSID}) if(DEFINED ENV{WIFI_SSID})
target_compile_definitions(${COMPONENT_LIB} PRIVATE target_compile_definitions(${COMPONENT_LIB} PRIVATE

View File

@ -1,897 +0,0 @@
#include "ble.h"
#include "wifi_transport.h"
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "host/util/util.h"
#include "host/ble_store.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
void ble_store_config_init(void);
#define DEVICE_NAME "EIS4"
#define CONNECTED_BIT BIT0
#define CMD_QUEUE_LEN 8
/* BLE MIDI Service 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 */
static const ble_uuid128_t midi_svc_uuid = BLE_UUID128_INIT(
0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7,
0x33, 0x4b, 0xe8, 0xed, 0x5a, 0x0e, 0xb8, 0x03);
/* BLE MIDI Characteristic 7772E5DB-3868-4112-A1A9-F2669D106BF3 */
static const ble_uuid128_t midi_chr_uuid = BLE_UUID128_INIT(
0xf3, 0x6b, 0x10, 0x9d, 0x66, 0xf2, 0xa9, 0xa1,
0x12, 0x41, 0x68, 0x38, 0xdb, 0xe5, 0x72, 0x77);
#define MAX_CONNECTIONS 4
static EventGroupHandle_t ble_events;
static QueueHandle_t cmd_queue;
static uint16_t midi_val_hdl;
static uint16_t hid_input_val_hdl;
static struct {
uint16_t hdl;
bool midi_notify;
bool hid_notify;
} conns[MAX_CONNECTIONS];
static int conn_count;
/* ---- HID keyboard report map ---- */
static const uint8_t hid_report_map[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x06, /* Usage (Keyboard) */
0xA1, 0x01, /* Collection (Application) */
0x85, 0x01, /* Report ID (1) */
0x05, 0x07, /* Usage Page (Keyboard) */
0x19, 0xE0, 0x29, 0xE7, /* Usage Min/Max (modifiers) */
0x15, 0x00, 0x25, 0x01, /* Logical Min/Max */
0x75, 0x01, 0x95, 0x08, /* 8x1-bit modifier keys */
0x81, 0x02, /* Input (Variable) */
0x95, 0x01, 0x75, 0x08, /* 1x8-bit reserved */
0x81, 0x01, /* Input (Constant) */
0x05, 0x08, /* Usage Page (LEDs) */
0x19, 0x01, 0x29, 0x05, /* 5 LEDs */
0x95, 0x05, 0x75, 0x01,
0x91, 0x02, /* Output (Variable) */
0x95, 0x01, 0x75, 0x03,
0x91, 0x01, /* Output pad to byte */
0x05, 0x07, /* Usage Page (Keyboard) */
0x19, 0x00, 0x29, 0xFF, /* Key codes 0-255 */
0x15, 0x00, 0x26, 0xFF, 0x00, /* Logical Min/Max (0-255) */
0x95, 0x06, 0x75, 0x08, /* 6x8-bit key array */
0x81, 0x00, /* Input (Array) */
0xC0, /* End Collection */
};
/* bcdHID=1.11, bCountryCode=0, Flags=NormallyConnectable */
static const uint8_t hid_info[] = { 0x11, 0x01, 0x00, 0x02 };
/* PnP ID: src=USB-IF(2), VID=0x1209(pid.codes), PID=0x0001, ver=0x0100 */
static const uint8_t pnp_id[] = { 0x02, 0x09, 0x12, 0x01, 0x00, 0x00, 0x01 };
/* ---- 7-bit MIDI encoding ---- */
static void encode_float(float val, uint8_t *out)
{
uint8_t *p = (uint8_t *)&val;
out[0] = ((p[0] >> 7) & 1) | ((p[1] >> 6) & 2) |
((p[2] >> 5) & 4) | ((p[3] >> 4) & 8);
out[1] = p[0] & 0x7F;
out[2] = p[1] & 0x7F;
out[3] = p[2] & 0x7F;
out[4] = p[3] & 0x7F;
}
static void encode_u16(uint16_t val, uint8_t *out)
{
uint8_t *p = (uint8_t *)&val;
out[0] = ((p[0] >> 7) & 1) | ((p[1] >> 6) & 2);
out[1] = p[0] & 0x7F;
out[2] = p[1] & 0x7F;
}
float ble_decode_float(const uint8_t *d)
{
uint8_t b[4];
b[0] = d[1] | ((d[0] & 1) << 7);
b[1] = d[2] | ((d[0] & 2) << 6);
b[2] = d[3] | ((d[0] & 4) << 5);
b[3] = d[4] | ((d[0] & 8) << 4);
float v;
memcpy(&v, b, 4);
return v;
}
uint16_t ble_decode_u16(const uint8_t *d)
{
uint8_t b[2];
b[0] = d[1] | ((d[0] & 1) << 7);
b[1] = d[2] | ((d[0] & 2) << 6);
uint16_t v;
memcpy(&v, b, 2);
return v;
}
/* ---- command parsing from incoming SysEx ---- */
static void parse_one_sysex(const uint8_t *midi, uint16_t mlen)
{
if (mlen < 3 || midi[0] != 0xF0 || midi[1] != 0x7D) return;
BleCommand cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.type = midi[2];
switch (cmd.type) {
case CMD_SET_SWEEP:
if (mlen < 16) return;
cmd.sweep.freq_start = ble_decode_float(&midi[3]);
cmd.sweep.freq_stop = ble_decode_float(&midi[8]);
cmd.sweep.ppd = ble_decode_u16(&midi[13]);
break;
case CMD_SET_RTIA:
if (mlen < 4) return;
cmd.rtia = midi[3];
break;
case CMD_SET_RCAL:
if (mlen < 4) return;
cmd.rcal = midi[3];
break;
case CMD_SET_ELECTRODE:
if (mlen < 4) return;
cmd.electrode = midi[3];
break;
case CMD_START_LSV:
if (mlen < 19) return;
cmd.lsv.v_start = ble_decode_float(&midi[3]);
cmd.lsv.v_stop = ble_decode_float(&midi[8]);
cmd.lsv.scan_rate = ble_decode_float(&midi[13]);
cmd.lsv.lp_rtia = midi[18];
break;
case CMD_START_AMP:
if (mlen < 19) return;
cmd.amp.v_hold = ble_decode_float(&midi[3]);
cmd.amp.interval_ms = ble_decode_float(&midi[8]);
cmd.amp.duration_s = ble_decode_float(&midi[13]);
cmd.amp.lp_rtia = midi[18];
break;
case CMD_START_CL:
if (mlen < 34) return;
cmd.cl.v_cond = ble_decode_float(&midi[3]);
cmd.cl.t_cond_ms = ble_decode_float(&midi[8]);
cmd.cl.v_free = ble_decode_float(&midi[13]);
cmd.cl.v_total = ble_decode_float(&midi[18]);
cmd.cl.t_dep_ms = ble_decode_float(&midi[23]);
cmd.cl.t_meas_ms = ble_decode_float(&midi[28]);
cmd.cl.lp_rtia = midi[33];
break;
case CMD_START_PH:
if (mlen < 8) return;
cmd.ph.stabilize_s = ble_decode_float(&midi[3]);
break;
case CMD_START_CLEAN:
if (mlen < 13) return;
cmd.clean.v_mv = ble_decode_float(&midi[3]);
cmd.clean.duration_s = ble_decode_float(&midi[8]);
break;
case CMD_SET_CELL_K:
if (mlen < 8) return;
cmd.cell_k = ble_decode_float(&midi[3]);
break;
case CMD_START_SWEEP:
case CMD_GET_CONFIG:
case CMD_STOP_AMP:
case CMD_GET_TEMP:
case CMD_GET_CELL_K:
case CMD_START_REFS:
case CMD_GET_REFS:
case CMD_CLEAR_REFS:
case CMD_OPEN_CAL:
case CMD_CLEAR_OPEN_CAL:
break;
default:
return;
}
xQueueSend(cmd_queue, &cmd, 0);
}
static void parse_command(const uint8_t *data, uint16_t len)
{
if (len < 5) return;
uint16_t i = 1; /* skip BLE MIDI header byte */
while (i < len) {
/* skip timestamp bytes (bit 7 set, not F0/F7) */
if ((data[i] & 0x80) && data[i] != 0xF0 && data[i] != 0xF7) {
i++;
continue;
}
if (data[i] == 0xF0) {
uint8_t clean[64];
uint16_t clen = 0;
clean[clen++] = 0xF0;
i++;
while (i < len && data[i] != 0xF7) {
if (data[i] & 0x80) { i++; continue; } /* strip timestamps */
if (clen < sizeof(clean))
clean[clen++] = data[i];
i++;
}
if (i < len) i++; /* skip F7 */
parse_one_sysex(clean, clen);
} else {
i++;
}
}
}
/* ---- GATT access callbacks ---- */
static int midi_access_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
uint8_t buf[64];
if (len > sizeof(buf)) len = sizeof(buf);
os_mbuf_copydata(ctxt->om, 0, len, buf);
parse_command(buf, len);
}
return 0;
}
static int hid_report_map_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR)
os_mbuf_append(ctxt->om, hid_report_map, sizeof(hid_report_map));
return 0;
}
static int hid_info_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR)
os_mbuf_append(ctxt->om, hid_info, sizeof(hid_info));
return 0;
}
static int hid_input_report_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
static const uint8_t empty[8] = {0};
os_mbuf_append(ctxt->om, empty, sizeof(empty));
}
return 0;
}
static int hid_output_report_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
return 0;
}
static int hid_input_ref_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) {
static const uint8_t ref[] = { 0x01, 0x01 }; /* Report ID 1, Input */
os_mbuf_append(ctxt->om, ref, sizeof(ref));
}
return 0;
}
static int hid_output_ref_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) {
static const uint8_t ref[] = { 0x01, 0x02 }; /* Report ID 1, Output */
os_mbuf_append(ctxt->om, ref, sizeof(ref));
}
return 0;
}
static int hid_boot_input_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
static const uint8_t empty[8] = {0};
os_mbuf_append(ctxt->om, empty, sizeof(empty));
}
return 0;
}
static int hid_boot_output_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
return 0;
}
static int hid_ctrl_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
return 0;
}
static int hid_proto_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
uint8_t mode = 1; /* Report Protocol */
os_mbuf_append(ctxt->om, &mode, 1);
}
return 0;
}
static int bas_level_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
uint8_t level = 100;
os_mbuf_append(ctxt->om, &level, 1);
}
return 0;
}
static int dis_pnp_cb(uint16_t ch, uint16_t ah,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)ch; (void)ah; (void)arg;
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR)
os_mbuf_append(ctxt->om, pnp_id, sizeof(pnp_id));
return 0;
}
/* ---- GATT service table ---- */
static const struct ble_gatt_svc_def gatt_svcs[] = {
/* Device Information Service */
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180A),
.characteristics = (struct ble_gatt_chr_def[]) {
{ .uuid = BLE_UUID16_DECLARE(0x2A50), .access_cb = dis_pnp_cb,
.flags = BLE_GATT_CHR_F_READ },
{ 0 },
},
},
/* Battery Service */
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180F),
.characteristics = (struct ble_gatt_chr_def[]) {
{ .uuid = BLE_UUID16_DECLARE(0x2A19), .access_cb = bas_level_cb,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY },
{ 0 },
},
},
/* HID Service */
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x1812),
.characteristics = (struct ble_gatt_chr_def[]) {
{ .uuid = BLE_UUID16_DECLARE(0x2A4B), .access_cb = hid_report_map_cb,
.flags = BLE_GATT_CHR_F_READ },
{ .uuid = BLE_UUID16_DECLARE(0x2A4A), .access_cb = hid_info_cb,
.flags = BLE_GATT_CHR_F_READ },
/* Input Report */
{ .uuid = BLE_UUID16_DECLARE(0x2A4D), .access_cb = hid_input_report_cb,
.val_handle = &hid_input_val_hdl,
.descriptors = (struct ble_gatt_dsc_def[]) {
{ .uuid = BLE_UUID16_DECLARE(0x2908), .att_flags = BLE_ATT_F_READ,
.access_cb = hid_input_ref_cb },
{ 0 },
},
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC },
/* Output Report (LEDs) */
{ .uuid = BLE_UUID16_DECLARE(0x2A4D), .access_cb = hid_output_report_cb,
.descriptors = (struct ble_gatt_dsc_def[]) {
{ .uuid = BLE_UUID16_DECLARE(0x2908), .att_flags = BLE_ATT_F_READ,
.access_cb = hid_output_ref_cb },
{ 0 },
},
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_READ_ENC |
BLE_GATT_CHR_F_WRITE_ENC },
/* Boot Keyboard Input Report */
{ .uuid = BLE_UUID16_DECLARE(0x2A22), .access_cb = hid_boot_input_cb,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC },
/* Boot Keyboard Output Report */
{ .uuid = BLE_UUID16_DECLARE(0x2A32), .access_cb = hid_boot_output_cb,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_READ_ENC },
{ .uuid = BLE_UUID16_DECLARE(0x2A4C), .access_cb = hid_ctrl_cb,
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP },
{ .uuid = BLE_UUID16_DECLARE(0x2A4E), .access_cb = hid_proto_cb,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP },
{ 0 },
},
},
/* MIDI Service */
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &midi_svc_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]) {
{ .uuid = &midi_chr_uuid.u, .access_cb = midi_access_cb,
.val_handle = &midi_val_hdl,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY },
{ 0 },
},
},
{ 0 },
};
/* ---- send empty keyboard report ---- */
static int conn_find(uint16_t hdl)
{
for (int i = 0; i < conn_count; i++)
if (conns[i].hdl == hdl) return i;
return -1;
}
static void send_empty_hid_report(uint16_t hdl)
{
static const uint8_t empty[8] = {0};
struct os_mbuf *om = ble_hs_mbuf_from_flat(empty, sizeof(empty));
if (om)
ble_gatts_notify_custom(hdl, hid_input_val_hdl, om);
}
/* ---- GAP / advertising ---- */
static void start_adv(void);
static int gap_event_cb(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
uint16_t hdl = event->connect.conn_handle;
if (conn_count < MAX_CONNECTIONS) {
conns[conn_count].hdl = hdl;
conns[conn_count].midi_notify = false;
conns[conn_count].hid_notify = false;
conn_count++;
}
xEventGroupSetBits(ble_events, CONNECTED_BIT);
ble_att_set_preferred_mtu(128);
ble_gattc_exchange_mtu(hdl, NULL, NULL);
ble_gap_security_initiate(hdl);
printf("BLE: connected (%d/%d)\n", conn_count, MAX_CONNECTIONS);
if (conn_count < MAX_CONNECTIONS)
start_adv();
} else {
start_adv();
}
break;
case BLE_GAP_EVENT_DISCONNECT: {
uint16_t hdl = event->disconnect.conn.conn_handle;
int idx = conn_find(hdl);
if (idx >= 0) {
conns[idx] = conns[--conn_count];
}
if (conn_count == 0)
xEventGroupClearBits(ble_events, CONNECTED_BIT);
printf("BLE: disconnected (%d/%d)\n", conn_count, MAX_CONNECTIONS);
start_adv();
break;
}
case BLE_GAP_EVENT_SUBSCRIBE: {
int idx = conn_find(event->subscribe.conn_handle);
if (idx >= 0) {
if (event->subscribe.attr_handle == midi_val_hdl)
conns[idx].midi_notify = event->subscribe.cur_notify;
if (event->subscribe.attr_handle == hid_input_val_hdl) {
conns[idx].hid_notify = event->subscribe.cur_notify;
if (conns[idx].hid_notify)
send_empty_hid_report(event->subscribe.conn_handle);
}
}
break;
}
case BLE_GAP_EVENT_REPEAT_PAIRING: {
struct ble_gap_conn_desc desc;
ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
ble_store_util_delete_peer(&desc.peer_id_addr);
return BLE_GAP_REPEAT_PAIRING_RETRY;
}
default:
break;
}
return 0;
}
static void adv_task(void *param)
{
(void)param;
vTaskDelay(pdMS_TO_TICKS(200));
ble_gap_adv_stop();
struct ble_hs_adv_fields fields = {0};
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.name = (uint8_t *)DEVICE_NAME;
fields.name_len = strlen(DEVICE_NAME);
fields.name_is_complete = 1;
fields.appearance = 0x03C1; /* Keyboard */
fields.appearance_is_present = 1;
ble_uuid16_t adv_uuids[] = {
BLE_UUID16_INIT(0x1812), /* HID */
BLE_UUID16_INIT(0x180F), /* Battery */
};
fields.uuids16 = adv_uuids;
fields.num_uuids16 = 2;
fields.uuids16_is_complete = 0;
int rc = ble_gap_adv_set_fields(&fields);
if (rc) { printf("BLE: set_fields failed: %d\n", rc); goto done; }
struct ble_hs_adv_fields rsp = {0};
rsp.uuids128 = (ble_uuid128_t *)&midi_svc_uuid;
rsp.num_uuids128 = 1;
rsp.uuids128_is_complete = 1;
rc = ble_gap_adv_rsp_set_fields(&rsp);
if (rc) { printf("BLE: set_rsp failed: %d\n", rc); goto done; }
struct ble_gap_adv_params params = {0};
params.conn_mode = BLE_GAP_CONN_MODE_UND;
params.disc_mode = BLE_GAP_DISC_MODE_GEN;
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
&params, gap_event_cb, NULL);
if (rc)
printf("BLE: adv_start failed: %d\n", rc);
else
printf("BLE: advertising (%d connected)\n", conn_count);
done:
vTaskDelete(NULL);
}
static void start_adv(void)
{
xTaskCreate(adv_task, "adv", 2048, NULL, 5, NULL);
}
static void on_sync(void)
{
uint8_t addr_type;
ble_hs_id_infer_auto(0, &addr_type);
start_adv();
printf("BLE: advertising as \"%s\"\n", DEVICE_NAME);
}
static void on_reset(int reason) { (void)reason; }
static void host_task(void *param)
{
(void)param;
nimble_port_run();
nimble_port_freertos_deinit();
}
/* ---- SysEx send ---- */
static int send_sysex(const uint8_t *sysex, uint16_t len)
{
/* UDP: raw SysEx to all WiFi clients */
wifi_send_sysex(sysex, len);
if (conn_count == 0)
return -1;
uint16_t pkt_len = len + 3;
uint8_t pkt[80];
if (pkt_len > sizeof(pkt))
return -1;
pkt[0] = 0x80;
pkt[1] = 0x80;
memcpy(&pkt[2], sysex, len - 1);
pkt[len + 1] = 0x80;
pkt[len + 2] = 0xF7;
int sent = 0;
for (int i = 0; i < conn_count; i++) {
if (!conns[i].midi_notify) continue;
struct os_mbuf *om = ble_hs_mbuf_from_flat(pkt, pkt_len);
if (!om) continue;
if (ble_gatts_notify_custom(conns[i].hdl, midi_val_hdl, om) == 0)
sent++;
}
return sent > 0 ? 0 : -1;
}
/* ---- public API ---- */
int ble_init(void)
{
ble_events = xEventGroupCreate();
cmd_queue = xQueueCreate(CMD_QUEUE_LEN, sizeof(BleCommand));
int rc = nimble_port_init();
if (rc != ESP_OK) return rc;
ble_hs_cfg.sync_cb = on_sync;
ble_hs_cfg.reset_cb = on_reset;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_svc_gap_init();
ble_svc_gatt_init();
ble_svc_gap_device_name_set(DEVICE_NAME);
ble_svc_gap_device_appearance_set(0x03C1);
rc = ble_gatts_count_cfg(gatt_svcs);
if (rc) return rc;
rc = ble_gatts_add_svcs(gatt_svcs);
if (rc) return rc;
ble_store_config_init();
nimble_port_freertos_init(host_task);
return 0;
}
int ble_is_connected(void)
{
return conn_count > 0;
}
void ble_wait_for_connection(void)
{
xEventGroupWaitBits(ble_events, CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
int ble_recv_command(BleCommand *cmd, uint32_t timeout_ms)
{
TickType_t ticks = (timeout_ms == UINT32_MAX) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xQueueReceive(cmd_queue, cmd, ticks) == pdTRUE ? 0 : -1;
}
void ble_push_command(const BleCommand *cmd)
{
xQueueSend(cmd_queue, cmd, 0);
}
int ble_send_sweep_start(uint32_t num_points, float freq_start, float freq_stop)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_SWEEP_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
encode_float(freq_start, &sx[p]); p += 5;
encode_float(freq_stop, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_eis_point(uint16_t index, const EISPoint *pt)
{
uint8_t sx[64];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_DATA_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(pt->freq_hz, &sx[p]); p += 5;
encode_float(pt->mag_ohms, &sx[p]); p += 5;
encode_float(pt->phase_deg, &sx[p]); p += 5;
encode_float(pt->z_real, &sx[p]); p += 5;
encode_float(pt->z_imag, &sx[p]); p += 5;
encode_float(pt->rtia_mag_before, &sx[p]); p += 5;
encode_float(pt->rtia_mag_after, &sx[p]); p += 5;
encode_float(pt->rev_mag, &sx[p]); p += 5;
encode_float(pt->rev_phase, &sx[p]); p += 5;
encode_float(pt->pct_err, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_sweep_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_SWEEP_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_config(const EISConfig *cfg)
{
uint8_t sx[32];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CONFIG;
encode_float(cfg->freq_start_hz, &sx[p]); p += 5;
encode_float(cfg->freq_stop_hz, &sx[p]); p += 5;
encode_u16(cfg->points_per_decade, &sx[p]); p += 3;
sx[p++] = (uint8_t)cfg->rtia;
sx[p++] = (uint8_t)cfg->rcal;
sx[p++] = (uint8_t)cfg->electrode;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_lsv_start(uint32_t num_points, float v_start, float v_stop)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_LSV_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
encode_float(v_start, &sx[p]); p += 5;
encode_float(v_stop, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_lsv_point(uint16_t index, float v_mv, float i_ua)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_LSV_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(v_mv, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_lsv_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_LSV_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_amp_start(float v_hold)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_AMP_START;
encode_float(v_hold, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_amp_point(uint16_t index, float t_ms, float i_ua)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_AMP_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(t_ms, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_amp_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_AMP_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_cl_start(uint32_t num_points)
{
uint8_t sx[10];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_cl_point(uint16_t index, float t_ms, float i_ua, uint8_t phase)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(t_ms, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = phase & 0x7F;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_cl_result(float i_free_ua, float i_total_ua)
{
uint8_t sx[16];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_RESULT;
encode_float(i_free_ua, &sx[p]); p += 5;
encode_float(i_total_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_cl_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_CL_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_ph_result(float v_ocp_mv, float ph, float temp_c)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_PH_RESULT;
encode_float(v_ocp_mv, &sx[p]); p += 5;
encode_float(ph, &sx[p]); p += 5;
encode_float(temp_c, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_temp(float temp_c)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_TEMP;
encode_float(temp_c, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_cell_k(float k)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CELL_K;
encode_float(k, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int ble_send_ref_frame(uint8_t mode, uint8_t rtia_idx)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_FRAME, mode & 0x7F, rtia_idx & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_ref_lp_range(uint8_t mode, uint8_t low_idx, uint8_t high_idx)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_LP_RANGE, mode & 0x7F, low_idx & 0x7F, high_idx & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_refs_done(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REFS_DONE, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int ble_send_ref_status(uint8_t has_refs)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_STATUS, has_refs & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}

View File

@ -1,119 +0,0 @@
#ifndef BLE_H
#define BLE_H
#include "eis.h"
/* Commands: Cue → ESP32 (0x1x, 0x2x) */
#define CMD_SET_SWEEP 0x10
#define CMD_SET_RTIA 0x11
#define CMD_SET_RCAL 0x12
#define CMD_START_SWEEP 0x13
#define CMD_GET_CONFIG 0x14
#define CMD_SET_ELECTRODE 0x15
#define CMD_START_LSV 0x20
#define CMD_START_AMP 0x21
#define CMD_STOP_AMP 0x22
#define CMD_GET_TEMP 0x17
#define CMD_START_CL 0x23
#define CMD_START_PH 0x24
#define CMD_START_CLEAN 0x25
#define CMD_OPEN_CAL 0x26
#define CMD_CLEAR_OPEN_CAL 0x27
#define CMD_SET_CELL_K 0x28
#define CMD_GET_CELL_K 0x29
#define CMD_START_REFS 0x30
#define CMD_GET_REFS 0x31
#define CMD_CLEAR_REFS 0x32
/* Responses: ESP32 → Cue (0x0x) */
#define RSP_SWEEP_START 0x01
#define RSP_DATA_POINT 0x02
#define RSP_SWEEP_END 0x03
#define RSP_CONFIG 0x04
#define RSP_LSV_START 0x05
#define RSP_LSV_POINT 0x06
#define RSP_LSV_END 0x07
#define RSP_AMP_START 0x08
#define RSP_AMP_POINT 0x09
#define RSP_AMP_END 0x0A
#define RSP_CL_START 0x0B
#define RSP_CL_POINT 0x0C
#define RSP_CL_RESULT 0x0D
#define RSP_CL_END 0x0E
#define RSP_PH_RESULT 0x0F
#define RSP_TEMP 0x10
#define RSP_CELL_K 0x11
#define RSP_REF_FRAME 0x20
#define RSP_REF_LP_RANGE 0x21
#define RSP_REFS_DONE 0x22
#define RSP_REF_STATUS 0x23
typedef struct {
uint8_t type;
union {
struct { float freq_start, freq_stop; uint16_t ppd; } sweep;
uint8_t rtia;
uint8_t rcal;
uint8_t electrode;
struct { float v_start, v_stop, scan_rate; uint8_t lp_rtia; } lsv;
struct { float v_hold, interval_ms, duration_s; uint8_t lp_rtia; } amp;
struct { float v_cond, t_cond_ms, v_free, v_total, t_dep_ms, t_meas_ms; uint8_t lp_rtia; } cl;
struct { float stabilize_s; } ph;
struct { float v_mv; float duration_s; } clean;
float cell_k;
};
} BleCommand;
int ble_init(void);
int ble_is_connected(void);
void ble_wait_for_connection(void);
/* blocking receive from command queue */
int ble_recv_command(BleCommand *cmd, uint32_t timeout_ms);
/* push a parsed command onto the shared queue (used by wifi transport) */
void ble_push_command(const BleCommand *cmd);
/* 7-bit MIDI decode helpers (shared with wifi transport) */
float ble_decode_float(const uint8_t *d);
uint16_t ble_decode_u16(const uint8_t *d);
/* outbound: EIS */
int ble_send_sweep_start(uint32_t num_points, float freq_start, float freq_stop);
int ble_send_eis_point(uint16_t index, const EISPoint *pt);
int ble_send_sweep_end(void);
int ble_send_config(const EISConfig *cfg);
/* outbound: LSV */
int ble_send_lsv_start(uint32_t num_points, float v_start, float v_stop);
int ble_send_lsv_point(uint16_t index, float v_mv, float i_ua);
int ble_send_lsv_end(void);
/* outbound: Amperometry */
int ble_send_amp_start(float v_hold);
int ble_send_amp_point(uint16_t index, float t_ms, float i_ua);
int ble_send_amp_end(void);
/* outbound: Chlorine */
int ble_send_cl_start(uint32_t num_points);
int ble_send_cl_point(uint16_t index, float t_ms, float i_ua, uint8_t phase);
int ble_send_cl_result(float i_free_ua, float i_total_ua);
int ble_send_cl_end(void);
/* outbound: pH */
int ble_send_ph_result(float v_ocp_mv, float ph, float temp_c);
/* outbound: temperature */
int ble_send_temp(float temp_c);
/* outbound: cell constant */
int ble_send_cell_k(float k);
/* outbound: reference collection */
int ble_send_ref_frame(uint8_t mode, uint8_t rtia_idx);
int ble_send_ref_lp_range(uint8_t mode, uint8_t low_idx, uint8_t high_idx);
int ble_send_refs_done(void);
int ble_send_ref_status(uint8_t has_refs);
#endif

View File

@ -1,6 +1,6 @@
#include "echem.h" #include "echem.h"
#include "ad5940.h" #include "ad5940.h"
#include "ble.h" #include "protocol.h"
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -449,8 +449,8 @@ int echem_amp(const AmpConfig *cfg, AmpPoint *out, uint32_t max_points, amp_poin
uint32_t count = 0; uint32_t count = 0;
for (uint32_t i = 0; i < max_samples; i++) { for (uint32_t i = 0; i < max_samples; i++) {
BleCommand cmd; Command cmd;
if (ble_recv_command(&cmd, 0) == 0 && cmd.type == CMD_STOP_AMP) if (protocol_recv_command(&cmd, 0) == 0 && cmd.type == CMD_STOP_AMP)
break; break;
float i_ua = read_current_ua(rtia); float i_ua = read_current_ua(rtia);

View File

@ -3,7 +3,7 @@
#include "ad5941_port.h" #include "ad5941_port.h"
#include "eis.h" #include "eis.h"
#include "echem.h" #include "echem.h"
#include "ble.h" #include "protocol.h"
#include "wifi_transport.h" #include "wifi_transport.h"
#include "temp.h" #include "temp.h"
#include "refs.h" #include "refs.h"
@ -12,7 +12,6 @@
#include "nvs_flash.h" #include "nvs_flash.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_log.h"
#define AD5941_EXPECTED_ADIID 0x4144 #define AD5941_EXPECTED_ADIID 0x4144
static EISConfig cfg; static EISConfig cfg;
@ -27,10 +26,10 @@ static void do_sweep(void)
eis_init(&cfg); eis_init(&cfg);
uint32_t n = eis_calc_num_points(&cfg); uint32_t n = eis_calc_num_points(&cfg);
ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz);
int got = eis_sweep(results, n, ble_send_eis_point); int got = eis_sweep(results, n, send_eis_point);
printf("Sweep complete: %d points\n", got); printf("Sweep complete: %d points\n", got);
ble_send_sweep_end(); send_sweep_end();
} }
void app_main(void) void app_main(void)
@ -62,16 +61,13 @@ void app_main(void)
esp_netif_init(); esp_netif_init();
esp_event_loop_create_default(); esp_event_loop_create_default();
esp_log_level_set("NimBLE", ESP_LOG_WARN); protocol_init();
ble_init();
wifi_transport_init(); wifi_transport_init();
printf("Waiting for BLE connection...\n"); printf("EIS4: WiFi transport ready, waiting for clients\n");
ble_wait_for_connection();
ble_send_config(&cfg);
BleCommand cmd; Command cmd;
for (;;) { for (;;) {
if (ble_recv_command(&cmd, UINT32_MAX) != 0) if (protocol_recv_command(&cmd, UINT32_MAX) != 0)
continue; continue;
switch (cmd.type) { switch (cmd.type) {
@ -118,7 +114,7 @@ void app_main(void)
break; break;
case CMD_GET_CONFIG: case CMD_GET_CONFIG:
ble_send_config(&cfg); send_config(&cfg);
break; break;
case CMD_START_LSV: { case CMD_START_LSV: {
@ -131,10 +127,10 @@ void app_main(void)
lsv_cfg.v_start, lsv_cfg.v_stop, lsv_cfg.scan_rate, lsv_cfg.lp_rtia); lsv_cfg.v_start, lsv_cfg.v_stop, lsv_cfg.scan_rate, lsv_cfg.lp_rtia);
uint32_t n = echem_lsv_calc_steps(&lsv_cfg, ECHEM_MAX_POINTS); uint32_t n = echem_lsv_calc_steps(&lsv_cfg, ECHEM_MAX_POINTS);
ble_send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop); send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop);
int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, ble_send_lsv_point); int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, send_lsv_point);
printf("LSV complete: %d points\n", got); printf("LSV complete: %d points\n", got);
ble_send_lsv_end(); send_lsv_end();
break; break;
} }
@ -147,15 +143,15 @@ void app_main(void)
printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n", printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n",
amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s); amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s);
ble_send_amp_start(amp_cfg.v_hold); send_amp_start(amp_cfg.v_hold);
int got = echem_amp(&amp_cfg, amp_results, ECHEM_MAX_POINTS, ble_send_amp_point); int got = echem_amp(&amp_cfg, amp_results, ECHEM_MAX_POINTS, send_amp_point);
printf("Amp complete: %d points\n", got); printf("Amp complete: %d points\n", got);
ble_send_amp_end(); send_amp_end();
break; break;
} }
case CMD_GET_TEMP: case CMD_GET_TEMP:
ble_send_temp(temp_get()); send_temp(temp_get());
break; break;
case CMD_START_PH: { case CMD_START_PH: {
@ -169,7 +165,7 @@ void app_main(void)
echem_ph_ocp(&ph_cfg, &ph_result); echem_ph_ocp(&ph_cfg, &ph_result);
printf("pH: OCP=%.1f mV, pH=%.2f\n", printf("pH: OCP=%.1f mV, pH=%.2f\n",
ph_result.v_ocp_mv, ph_result.ph); ph_result.v_ocp_mv, ph_result.ph);
ble_send_ph_result(ph_result.v_ocp_mv, ph_result.ph, ph_result.temp_c); send_ph_result(ph_result.v_ocp_mv, ph_result.ph, ph_result.temp_c);
break; break;
} }
@ -196,10 +192,10 @@ void app_main(void)
printf("Open-circuit cal starting\n"); printf("Open-circuit cal starting\n");
eis_init(&cfg); eis_init(&cfg);
uint32_t n = eis_calc_num_points(&cfg); uint32_t n = eis_calc_num_points(&cfg);
ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz);
int got = eis_open_cal(results, n, ble_send_eis_point); int got = eis_open_cal(results, n, send_eis_point);
printf("Open-circuit cal: %d points\n", got); printf("Open-circuit cal: %d points\n", got);
ble_send_sweep_end(); send_sweep_end();
break; break;
} }
@ -210,12 +206,12 @@ void app_main(void)
case CMD_SET_CELL_K: case CMD_SET_CELL_K:
eis_set_cell_k(cmd.cell_k); eis_set_cell_k(cmd.cell_k);
ble_send_cell_k(cmd.cell_k); send_cell_k(cmd.cell_k);
printf("Cell K set: %.4f cm^-1\n", cmd.cell_k); printf("Cell K set: %.4f cm^-1\n", cmd.cell_k);
break; break;
case CMD_GET_CELL_K: case CMD_GET_CELL_K:
ble_send_cell_k(eis_get_cell_k()); send_cell_k(eis_get_cell_k());
break; break;
case CMD_START_CL: { case CMD_START_CL: {
@ -230,16 +226,56 @@ void app_main(void)
uint32_t n_per = (uint32_t)(cl_cfg.t_meas_ms / 50.0f + 0.5f); uint32_t n_per = (uint32_t)(cl_cfg.t_meas_ms / 50.0f + 0.5f);
if (n_per < 2) n_per = 2; if (n_per < 2) n_per = 2;
ble_send_cl_start(2 * n_per); send_cl_start(2 * n_per);
ClResult cl_result; ClResult cl_result;
int got = echem_chlorine(&cl_cfg, cl_results, ECHEM_MAX_POINTS, int got = echem_chlorine(&cl_cfg, cl_results, ECHEM_MAX_POINTS,
&cl_result, ble_send_cl_point); &cl_result, send_cl_point);
printf("Cl complete: %d points, free=%.3f uA, total=%.3f uA\n", printf("Cl complete: %d points, free=%.3f uA, total=%.3f uA\n",
got, cl_result.i_free_ua, cl_result.i_total_ua); got, cl_result.i_free_ua, cl_result.i_total_ua);
ble_send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua); send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua);
ble_send_cl_end(); send_cl_end();
break;
}
case CMD_SESSION_CREATE: {
uint8_t id = session_create(cmd.session_create.name,
cmd.session_create.name_len);
if (id != 0xFF) {
send_session_created(id, cmd.session_create.name,
cmd.session_create.name_len);
printf("Session created: %u \"%.*s\"\n",
id, cmd.session_create.name_len, cmd.session_create.name);
}
break;
}
case CMD_SESSION_SWITCH:
if (session_switch(cmd.session_switch.id) == 0) {
send_session_switched(cmd.session_switch.id);
printf("Session switched: %u\n", cmd.session_switch.id);
}
break;
case CMD_SESSION_LIST:
send_session_list();
break;
case CMD_SESSION_RENAME:
if (session_rename(cmd.session_rename.id, cmd.session_rename.name,
cmd.session_rename.name_len) == 0) {
send_session_renamed(cmd.session_rename.id, cmd.session_rename.name,
cmd.session_rename.name_len);
printf("Session renamed: %u \"%.*s\"\n",
cmd.session_rename.id, cmd.session_rename.name_len,
cmd.session_rename.name);
}
break;
case CMD_HEARTBEAT:
send_client_list((uint8_t)wifi_get_client_count());
send_session_list();
send_config(&cfg);
break; break;
} }
} }
}
} }

426
main/protocol.c Normal file
View File

@ -0,0 +1,426 @@
#include "protocol.h"
#include "wifi_transport.h"
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#define CMD_QUEUE_LEN 8
static QueueHandle_t cmd_queue;
/* session state */
static Session sessions[MAX_SESSIONS];
static uint8_t session_count;
static uint8_t current_session_id;
/* ---- 7-bit MIDI encoding ---- */
static void encode_float(float val, uint8_t *out)
{
uint8_t *p = (uint8_t *)&val;
out[0] = ((p[0] >> 7) & 1) | ((p[1] >> 6) & 2) |
((p[2] >> 5) & 4) | ((p[3] >> 4) & 8);
out[1] = p[0] & 0x7F;
out[2] = p[1] & 0x7F;
out[3] = p[2] & 0x7F;
out[4] = p[3] & 0x7F;
}
static void encode_u16(uint16_t val, uint8_t *out)
{
uint8_t *p = (uint8_t *)&val;
out[0] = ((p[0] >> 7) & 1) | ((p[1] >> 6) & 2);
out[1] = p[0] & 0x7F;
out[2] = p[1] & 0x7F;
}
float decode_float(const uint8_t *d)
{
uint8_t b[4];
b[0] = d[1] | ((d[0] & 1) << 7);
b[1] = d[2] | ((d[0] & 2) << 6);
b[2] = d[3] | ((d[0] & 4) << 5);
b[3] = d[4] | ((d[0] & 8) << 4);
float v;
memcpy(&v, b, 4);
return v;
}
uint16_t decode_u16(const uint8_t *d)
{
uint8_t b[2];
b[0] = d[1] | ((d[0] & 1) << 7);
b[1] = d[2] | ((d[0] & 2) << 6);
uint16_t v;
memcpy(&v, b, 2);
return v;
}
/* ---- command queue ---- */
int protocol_init(void)
{
cmd_queue = xQueueCreate(CMD_QUEUE_LEN, sizeof(Command));
if (!cmd_queue) return -1;
session_count = 0;
current_session_id = 0;
memset(sessions, 0, sizeof(sessions));
return 0;
}
int protocol_recv_command(Command *cmd, uint32_t timeout_ms)
{
TickType_t ticks = (timeout_ms == UINT32_MAX) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xQueueReceive(cmd_queue, cmd, ticks) == pdTRUE ? 0 : -1;
}
void protocol_push_command(const Command *cmd)
{
xQueueSend(cmd_queue, cmd, 0);
}
/* ---- session management ---- */
const Session *session_get_all(uint8_t *count)
{
*count = session_count;
return sessions;
}
uint8_t session_get_current(void)
{
return current_session_id;
}
uint8_t session_create(const char *name, uint8_t name_len)
{
if (session_count >= MAX_SESSIONS)
return 0xFF;
uint8_t id = session_count + 1;
Session *s = &sessions[session_count];
s->id = id;
if (name_len > MAX_SESSION_NAME)
name_len = MAX_SESSION_NAME;
memcpy(s->name, name, name_len);
s->name[name_len] = '\0';
s->active = 1;
session_count++;
current_session_id = id;
return id;
}
int session_switch(uint8_t id)
{
for (uint8_t i = 0; i < session_count; i++) {
if (sessions[i].id == id) {
current_session_id = id;
return 0;
}
}
return -1;
}
int session_rename(uint8_t id, const char *name, uint8_t name_len)
{
for (uint8_t i = 0; i < session_count; i++) {
if (sessions[i].id == id) {
if (name_len > MAX_SESSION_NAME)
name_len = MAX_SESSION_NAME;
memcpy(sessions[i].name, name, name_len);
sessions[i].name[name_len] = '\0';
return 0;
}
}
return -1;
}
/* ---- SysEx send ---- */
static int send_sysex(const uint8_t *sysex, uint16_t len)
{
return wifi_send_sysex(sysex, len);
}
/* ---- outbound: EIS ---- */
int send_sweep_start(uint32_t num_points, float freq_start, float freq_stop)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_SWEEP_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
encode_float(freq_start, &sx[p]); p += 5;
encode_float(freq_stop, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_eis_point(uint16_t index, const EISPoint *pt)
{
uint8_t sx[64];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_DATA_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(pt->freq_hz, &sx[p]); p += 5;
encode_float(pt->mag_ohms, &sx[p]); p += 5;
encode_float(pt->phase_deg, &sx[p]); p += 5;
encode_float(pt->z_real, &sx[p]); p += 5;
encode_float(pt->z_imag, &sx[p]); p += 5;
encode_float(pt->rtia_mag_before, &sx[p]); p += 5;
encode_float(pt->rtia_mag_after, &sx[p]); p += 5;
encode_float(pt->rev_mag, &sx[p]); p += 5;
encode_float(pt->rev_phase, &sx[p]); p += 5;
encode_float(pt->pct_err, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_sweep_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_SWEEP_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int send_config(const EISConfig *cfg)
{
uint8_t sx[32];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CONFIG;
encode_float(cfg->freq_start_hz, &sx[p]); p += 5;
encode_float(cfg->freq_stop_hz, &sx[p]); p += 5;
encode_u16(cfg->points_per_decade, &sx[p]); p += 3;
sx[p++] = (uint8_t)cfg->rtia;
sx[p++] = (uint8_t)cfg->rcal;
sx[p++] = (uint8_t)cfg->electrode;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
/* ---- outbound: LSV ---- */
int send_lsv_start(uint32_t num_points, float v_start, float v_stop)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_LSV_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
encode_float(v_start, &sx[p]); p += 5;
encode_float(v_stop, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_lsv_point(uint16_t index, float v_mv, float i_ua)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_LSV_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(v_mv, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_lsv_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_LSV_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
/* ---- outbound: Amperometry ---- */
int send_amp_start(float v_hold)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_AMP_START;
encode_float(v_hold, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_amp_point(uint16_t index, float t_ms, float i_ua)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_AMP_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(t_ms, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_amp_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_AMP_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
/* ---- outbound: Chlorine ---- */
int send_cl_start(uint32_t num_points)
{
uint8_t sx[10];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_START;
encode_u16((uint16_t)num_points, &sx[p]); p += 3;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_cl_point(uint16_t index, float t_ms, float i_ua, uint8_t phase)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_POINT;
encode_u16(index, &sx[p]); p += 3;
encode_float(t_ms, &sx[p]); p += 5;
encode_float(i_ua, &sx[p]); p += 5;
sx[p++] = phase & 0x7F;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_cl_result(float i_free_ua, float i_total_ua)
{
uint8_t sx[16];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_RESULT;
encode_float(i_free_ua, &sx[p]); p += 5;
encode_float(i_total_ua, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_cl_end(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_CL_END, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
/* ---- outbound: pH ---- */
int send_ph_result(float v_ocp_mv, float ph, float temp_c)
{
uint8_t sx[20];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_PH_RESULT;
encode_float(v_ocp_mv, &sx[p]); p += 5;
encode_float(ph, &sx[p]); p += 5;
encode_float(temp_c, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
/* ---- outbound: temperature ---- */
int send_temp(float temp_c)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_TEMP;
encode_float(temp_c, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
/* ---- outbound: cell constant ---- */
int send_cell_k(float k)
{
uint8_t sx[12];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CELL_K;
encode_float(k, &sx[p]); p += 5;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
/* ---- outbound: reference collection ---- */
int send_ref_frame(uint8_t mode, uint8_t rtia_idx)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_FRAME, mode & 0x7F, rtia_idx & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int send_ref_lp_range(uint8_t mode, uint8_t low_idx, uint8_t high_idx)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_LP_RANGE, mode & 0x7F, low_idx & 0x7F, high_idx & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int send_refs_done(void)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REFS_DONE, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int send_ref_status(uint8_t has_refs)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_REF_STATUS, has_refs & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
/* ---- outbound: session sync ---- */
int send_session_created(uint8_t id, const char *name, uint8_t name_len)
{
uint8_t sx[48];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_SESSION_CREATED;
sx[p++] = id & 0x7F;
sx[p++] = name_len & 0x7F;
for (uint8_t i = 0; i < name_len && p < sizeof(sx) - 1; i++)
sx[p++] = name[i] & 0x7F;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_session_switched(uint8_t id)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_SESSION_SWITCHED, id & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}
int send_session_list(void)
{
uint8_t sx[128];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_SESSION_LIST;
sx[p++] = session_count & 0x7F;
sx[p++] = current_session_id & 0x7F;
for (uint8_t i = 0; i < session_count && p < sizeof(sx) - 4; i++) {
sx[p++] = sessions[i].id & 0x7F;
uint8_t nlen = (uint8_t)strlen(sessions[i].name);
sx[p++] = nlen & 0x7F;
for (uint8_t j = 0; j < nlen && p < sizeof(sx) - 1; j++)
sx[p++] = sessions[i].name[j] & 0x7F;
}
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_session_renamed(uint8_t id, const char *name, uint8_t name_len)
{
uint8_t sx[48];
uint16_t p = 0;
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_SESSION_RENAMED;
sx[p++] = id & 0x7F;
sx[p++] = name_len & 0x7F;
for (uint8_t i = 0; i < name_len && p < sizeof(sx) - 1; i++)
sx[p++] = name[i] & 0x7F;
sx[p++] = 0xF7;
return send_sysex(sx, p);
}
int send_client_list(uint8_t count)
{
uint8_t sx[] = { 0xF0, 0x7D, RSP_CLIENT_LIST, count & 0x7F, 0xF7 };
return send_sysex(sx, sizeof(sx));
}

155
main/protocol.h Normal file
View File

@ -0,0 +1,155 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
#include "eis.h"
/* Commands: Client -> Firmware (0x1x, 0x2x, 0x3x) */
#define CMD_SET_SWEEP 0x10
#define CMD_SET_RTIA 0x11
#define CMD_SET_RCAL 0x12
#define CMD_START_SWEEP 0x13
#define CMD_GET_CONFIG 0x14
#define CMD_SET_ELECTRODE 0x15
#define CMD_START_LSV 0x20
#define CMD_START_AMP 0x21
#define CMD_STOP_AMP 0x22
#define CMD_GET_TEMP 0x17
#define CMD_START_CL 0x23
#define CMD_START_PH 0x24
#define CMD_START_CLEAN 0x25
#define CMD_OPEN_CAL 0x26
#define CMD_CLEAR_OPEN_CAL 0x27
#define CMD_SET_CELL_K 0x28
#define CMD_GET_CELL_K 0x29
#define CMD_START_REFS 0x30
#define CMD_GET_REFS 0x31
#define CMD_CLEAR_REFS 0x32
/* Session sync commands (0x4x) */
#define CMD_SESSION_CREATE 0x40
#define CMD_SESSION_SWITCH 0x41
#define CMD_SESSION_LIST 0x42
#define CMD_SESSION_RENAME 0x43
#define CMD_HEARTBEAT 0x44
/* Responses: Firmware -> Client (0x0x, 0x2x) */
#define RSP_SWEEP_START 0x01
#define RSP_DATA_POINT 0x02
#define RSP_SWEEP_END 0x03
#define RSP_CONFIG 0x04
#define RSP_LSV_START 0x05
#define RSP_LSV_POINT 0x06
#define RSP_LSV_END 0x07
#define RSP_AMP_START 0x08
#define RSP_AMP_POINT 0x09
#define RSP_AMP_END 0x0A
#define RSP_CL_START 0x0B
#define RSP_CL_POINT 0x0C
#define RSP_CL_RESULT 0x0D
#define RSP_CL_END 0x0E
#define RSP_PH_RESULT 0x0F
#define RSP_TEMP 0x10
#define RSP_CELL_K 0x11
#define RSP_REF_FRAME 0x20
#define RSP_REF_LP_RANGE 0x21
#define RSP_REFS_DONE 0x22
#define RSP_REF_STATUS 0x23
/* Session sync responses (0x4x) */
#define RSP_SESSION_CREATED 0x40
#define RSP_SESSION_SWITCHED 0x41
#define RSP_SESSION_LIST 0x42
#define RSP_SESSION_RENAMED 0x43
#define RSP_CLIENT_LIST 0x44
/* Session limits */
#define MAX_SESSIONS 8
#define MAX_SESSION_NAME 32
typedef struct {
uint8_t type;
union {
struct { float freq_start, freq_stop; uint16_t ppd; } sweep;
uint8_t rtia;
uint8_t rcal;
uint8_t electrode;
struct { float v_start, v_stop, scan_rate; uint8_t lp_rtia; } lsv;
struct { float v_hold, interval_ms, duration_s; uint8_t lp_rtia; } amp;
struct { float v_cond, t_cond_ms, v_free, v_total, t_dep_ms, t_meas_ms; uint8_t lp_rtia; } cl;
struct { float stabilize_s; } ph;
struct { float v_mv; float duration_s; } clean;
float cell_k;
struct { uint8_t name_len; char name[MAX_SESSION_NAME]; } session_create;
struct { uint8_t id; } session_switch;
struct { uint8_t id; uint8_t name_len; char name[MAX_SESSION_NAME]; } session_rename;
};
} Command;
typedef struct {
uint8_t id;
char name[MAX_SESSION_NAME + 1];
uint8_t active;
} Session;
int protocol_init(void);
int protocol_recv_command(Command *cmd, uint32_t timeout_ms);
void protocol_push_command(const Command *cmd);
/* 7-bit decode helpers */
float decode_float(const uint8_t *d);
uint16_t decode_u16(const uint8_t *d);
/* outbound: EIS */
int send_sweep_start(uint32_t num_points, float freq_start, float freq_stop);
int send_eis_point(uint16_t index, const EISPoint *pt);
int send_sweep_end(void);
int send_config(const EISConfig *cfg);
/* outbound: LSV */
int send_lsv_start(uint32_t num_points, float v_start, float v_stop);
int send_lsv_point(uint16_t index, float v_mv, float i_ua);
int send_lsv_end(void);
/* outbound: Amperometry */
int send_amp_start(float v_hold);
int send_amp_point(uint16_t index, float t_ms, float i_ua);
int send_amp_end(void);
/* outbound: Chlorine */
int send_cl_start(uint32_t num_points);
int send_cl_point(uint16_t index, float t_ms, float i_ua, uint8_t phase);
int send_cl_result(float i_free_ua, float i_total_ua);
int send_cl_end(void);
/* outbound: pH */
int send_ph_result(float v_ocp_mv, float ph, float temp_c);
/* outbound: temperature */
int send_temp(float temp_c);
/* outbound: cell constant */
int send_cell_k(float k);
/* outbound: reference collection */
int send_ref_frame(uint8_t mode, uint8_t rtia_idx);
int send_ref_lp_range(uint8_t mode, uint8_t low_idx, uint8_t high_idx);
int send_refs_done(void);
int send_ref_status(uint8_t has_refs);
/* session management */
const Session *session_get_all(uint8_t *count);
uint8_t session_get_current(void);
uint8_t session_create(const char *name, uint8_t name_len);
int session_switch(uint8_t id);
int session_rename(uint8_t id, const char *name, uint8_t name_len);
/* session sync responses */
int send_session_created(uint8_t id, const char *name, uint8_t name_len);
int send_session_switched(uint8_t id);
int send_session_list(void);
int send_session_renamed(uint8_t id, const char *name, uint8_t name_len);
int send_client_list(uint8_t count);
#endif

View File

@ -1,5 +1,5 @@
#include "refs.h" #include "refs.h"
#include "ble.h" #include "protocol.h"
#include "temp.h" #include "temp.h"
#include "ad5940.h" #include "ad5940.h"
#include <string.h> #include <string.h>
@ -8,12 +8,9 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
/* LP RTIA register mapping (mirrors echem.c) */
extern const uint32_t lp_rtia_map[]; extern const uint32_t lp_rtia_map[];
extern const float lp_rtia_ohms[]; extern const float lp_rtia_ohms[];
/* ---- ADC helpers ---- */
static int32_t read_adc_code(void) static int32_t read_adc_code(void)
{ {
AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY);
@ -32,8 +29,6 @@ static int32_t read_adc_code(void)
return (raw & (1UL << 15)) ? (int32_t)(raw | 0xFFFF0000UL) : (int32_t)raw; return (raw & (1UL << 15)) ? (int32_t)(raw | 0xFFFF0000UL) : (int32_t)raw;
} }
/* ---- Clipping detection ---- */
#define ADC_OK 0 #define ADC_OK 0
#define ADC_CLIPPED 1 #define ADC_CLIPPED 1
#define ADC_OSCILLATING 2 #define ADC_OSCILLATING 2
@ -76,8 +71,8 @@ static void init_lp_for_probe(uint32_t rtia_reg)
lp.LpDacCfg.LpDacRef = LPDACREF_2P5; lp.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp.LpDacCfg.DataRst = bFALSE; lp.LpDacCfg.DataRst = bFALSE;
lp.LpDacCfg.PowerEn = bTRUE; lp.LpDacCfg.PowerEn = bTRUE;
lp.LpDacCfg.DacData6Bit = 26; /* VZERO ~1094mV */ lp.LpDacCfg.DacData6Bit = 26;
lp.LpDacCfg.DacData12Bit = (uint16_t)((1093.75f - 200.0f) / 0.537f + 0.5f); /* 0mV cell */ lp.LpDacCfg.DacData12Bit = (uint16_t)((1093.75f - 200.0f) / 0.537f + 0.5f);
lp.LpAmpCfg.LpAmpSel = LPAMP0; lp.LpAmpCfg.LpAmpSel = LPAMP0;
lp.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_BOOST3; lp.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_BOOST3;
@ -195,7 +190,6 @@ static void lp_find_valid_range(LpRtiaRange *range)
uint8_t lo = 0, hi = LP_RTIA_512K; uint8_t lo = 0, hi = LP_RTIA_512K;
if (low_clip != ADC_OK) { if (low_clip != ADC_OK) {
/* binary search upward for first non-clipping */
uint8_t a = 0, b = LP_RTIA_512K; uint8_t a = 0, b = LP_RTIA_512K;
while (a < b) { while (a < b) {
uint8_t m = (a + b) / 2; uint8_t m = (a + b) / 2;
@ -208,7 +202,6 @@ static void lp_find_valid_range(LpRtiaRange *range)
} }
if (high_clip != ADC_OK) { if (high_clip != ADC_OK) {
/* binary search downward for last non-clipping */
uint8_t a = lo, b = LP_RTIA_512K; uint8_t a = lo, b = LP_RTIA_512K;
while (a < b) { while (a < b) {
uint8_t m = (a + b + 1) / 2; uint8_t m = (a + b + 1) / 2;
@ -225,13 +218,10 @@ static void lp_find_valid_range(LpRtiaRange *range)
range->valid = (lo <= hi) ? 1 : 0; range->valid = (lo <= hi) ? 1 : 0;
} }
/* ---- Collection ---- */
void refs_collect(RefStore *store, const EISConfig *cfg) void refs_collect(RefStore *store, const EISConfig *cfg)
{ {
memset(store, 0, sizeof(*store)); memset(store, 0, sizeof(*store));
/* EIS phase: sweep each RTIA 0-7 with RCAL=3K */
EISConfig ref_cfg = *cfg; EISConfig ref_cfg = *cfg;
ref_cfg.rcal = RCAL_3K; ref_cfg.rcal = RCAL_3K;
@ -239,95 +229,91 @@ void refs_collect(RefStore *store, const EISConfig *cfg)
ref_cfg.rtia = (EISRtia)r; ref_cfg.rtia = (EISRtia)r;
eis_init(&ref_cfg); eis_init(&ref_cfg);
ble_send_ref_frame(REF_MODE_EIS, (uint8_t)r); send_ref_frame(REF_MODE_EIS, (uint8_t)r);
uint32_t n = eis_calc_num_points(&ref_cfg); uint32_t n = eis_calc_num_points(&ref_cfg);
ble_send_sweep_start(n, ref_cfg.freq_start_hz, ref_cfg.freq_stop_hz); send_sweep_start(n, ref_cfg.freq_start_hz, ref_cfg.freq_stop_hz);
int got = eis_sweep(store->eis[r].pts, n, ble_send_eis_point); int got = eis_sweep(store->eis[r].pts, n, send_eis_point);
store->eis[r].n_points = (uint32_t)got; store->eis[r].n_points = (uint32_t)got;
store->eis[r].valid = (got > 0) ? 1 : 0; store->eis[r].valid = (got > 0) ? 1 : 0;
ble_send_sweep_end(); send_sweep_end();
printf("Ref EIS RTIA %d: %d pts\n", r, got); printf("Ref EIS RTIA %d: %d pts\n", r, got);
} }
/* LP phase: binary search valid RTIA range for each LP mode */
printf("Ref: LP range search (LSV)\n"); printf("Ref: LP range search (LSV)\n");
ble_send_ref_frame(REF_MODE_LSV, 0); send_ref_frame(REF_MODE_LSV, 0);
lp_find_valid_range(&store->lsv_range); lp_find_valid_range(&store->lsv_range);
if (store->lsv_range.valid) if (store->lsv_range.valid)
ble_send_ref_lp_range(REF_MODE_LSV, store->lsv_range.low_idx, store->lsv_range.high_idx); send_ref_lp_range(REF_MODE_LSV, store->lsv_range.low_idx, store->lsv_range.high_idx);
printf("Ref LSV range: %u-%u\n", store->lsv_range.low_idx, store->lsv_range.high_idx); printf("Ref LSV range: %u-%u\n", store->lsv_range.low_idx, store->lsv_range.high_idx);
printf("Ref: LP range search (Amp)\n"); printf("Ref: LP range search (Amp)\n");
ble_send_ref_frame(REF_MODE_AMP, 0); send_ref_frame(REF_MODE_AMP, 0);
lp_find_valid_range(&store->amp_range); lp_find_valid_range(&store->amp_range);
if (store->amp_range.valid) if (store->amp_range.valid)
ble_send_ref_lp_range(REF_MODE_AMP, store->amp_range.low_idx, store->amp_range.high_idx); send_ref_lp_range(REF_MODE_AMP, store->amp_range.low_idx, store->amp_range.high_idx);
printf("Ref Amp range: %u-%u\n", store->amp_range.low_idx, store->amp_range.high_idx); printf("Ref Amp range: %u-%u\n", store->amp_range.low_idx, store->amp_range.high_idx);
printf("Ref: LP range search (Cl)\n"); printf("Ref: LP range search (Cl)\n");
ble_send_ref_frame(REF_MODE_CL, 0); send_ref_frame(REF_MODE_CL, 0);
lp_find_valid_range(&store->cl_range); lp_find_valid_range(&store->cl_range);
if (store->cl_range.valid) if (store->cl_range.valid)
ble_send_ref_lp_range(REF_MODE_CL, store->cl_range.low_idx, store->cl_range.high_idx); send_ref_lp_range(REF_MODE_CL, store->cl_range.low_idx, store->cl_range.high_idx);
printf("Ref Cl range: %u-%u\n", store->cl_range.low_idx, store->cl_range.high_idx); printf("Ref Cl range: %u-%u\n", store->cl_range.low_idx, store->cl_range.high_idx);
/* pH phase: OCP measurement */
printf("Ref: pH OCP\n"); printf("Ref: pH OCP\n");
ble_send_ref_frame(REF_MODE_PH, 0); send_ref_frame(REF_MODE_PH, 0);
PhConfig ph_cfg; PhConfig ph_cfg;
ph_cfg.stabilize_s = 10.0f; ph_cfg.stabilize_s = 10.0f;
ph_cfg.temp_c = temp_get(); ph_cfg.temp_c = temp_get();
echem_ph_ocp(&ph_cfg, &store->ph_ref); echem_ph_ocp(&ph_cfg, &store->ph_ref);
store->ph_valid = 1; store->ph_valid = 1;
ble_send_ph_result(store->ph_ref.v_ocp_mv, store->ph_ref.ph, store->ph_ref.temp_c); send_ph_result(store->ph_ref.v_ocp_mv, store->ph_ref.ph, store->ph_ref.temp_c);
store->has_refs = 1; store->has_refs = 1;
ble_send_refs_done(); send_refs_done();
printf("Ref collection complete\n"); printf("Ref collection complete\n");
} }
/* ---- Send stored refs to client ---- */
void refs_send(const RefStore *store) void refs_send(const RefStore *store)
{ {
if (!store->has_refs) { if (!store->has_refs) {
ble_send_ref_status(0); send_ref_status(0);
return; return;
} }
ble_send_ref_status(1); send_ref_status(1);
for (int r = 0; r < REF_EIS_RTIA_COUNT; r++) { for (int r = 0; r < REF_EIS_RTIA_COUNT; r++) {
if (!store->eis[r].valid) continue; if (!store->eis[r].valid) continue;
ble_send_ref_frame(REF_MODE_EIS, (uint8_t)r); send_ref_frame(REF_MODE_EIS, (uint8_t)r);
uint32_t n = store->eis[r].n_points; uint32_t n = store->eis[r].n_points;
ble_send_sweep_start(n, store->eis[r].pts[0].freq_hz, send_sweep_start(n, store->eis[r].pts[0].freq_hz,
store->eis[r].pts[n - 1].freq_hz); store->eis[r].pts[n - 1].freq_hz);
for (uint32_t i = 0; i < n; i++) for (uint32_t i = 0; i < n; i++)
ble_send_eis_point((uint16_t)i, &store->eis[r].pts[i]); send_eis_point((uint16_t)i, &store->eis[r].pts[i]);
ble_send_sweep_end(); send_sweep_end();
} }
if (store->lsv_range.valid) if (store->lsv_range.valid)
ble_send_ref_lp_range(REF_MODE_LSV, store->lsv_range.low_idx, store->lsv_range.high_idx); send_ref_lp_range(REF_MODE_LSV, store->lsv_range.low_idx, store->lsv_range.high_idx);
if (store->amp_range.valid) if (store->amp_range.valid)
ble_send_ref_lp_range(REF_MODE_AMP, store->amp_range.low_idx, store->amp_range.high_idx); send_ref_lp_range(REF_MODE_AMP, store->amp_range.low_idx, store->amp_range.high_idx);
if (store->cl_range.valid) if (store->cl_range.valid)
ble_send_ref_lp_range(REF_MODE_CL, store->cl_range.low_idx, store->cl_range.high_idx); send_ref_lp_range(REF_MODE_CL, store->cl_range.low_idx, store->cl_range.high_idx);
if (store->ph_valid) { if (store->ph_valid) {
ble_send_ref_frame(REF_MODE_PH, 0); send_ref_frame(REF_MODE_PH, 0);
ble_send_ph_result(store->ph_ref.v_ocp_mv, store->ph_ref.ph, store->ph_ref.temp_c); send_ph_result(store->ph_ref.v_ocp_mv, store->ph_ref.ph, store->ph_ref.temp_c);
} }
ble_send_refs_done(); send_refs_done();
} }
void refs_clear(RefStore *store) void refs_clear(RefStore *store)
{ {
memset(store, 0, sizeof(*store)); memset(store, 0, sizeof(*store));
ble_send_ref_status(0); send_ref_status(0);
} }

View File

@ -1,5 +1,5 @@
#include "wifi_transport.h" #include "wifi_transport.h"
#include "ble.h" #include "protocol.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@ -13,7 +13,7 @@
#define WIFI_SSID "EIS4" #define WIFI_SSID "EIS4"
#define WIFI_PASS "eis4data" #define WIFI_PASS "eis4data"
#define WIFI_CHANNEL 1 #define WIFI_CHANNEL 1
#define WIFI_MAX_CONN 2 #define WIFI_MAX_CONN 4
#define UDP_PORT 5941 #define UDP_PORT 5941
#define UDP_BUF_SIZE 128 #define UDP_BUF_SIZE 128
@ -68,27 +68,25 @@ static void clients_expire(void)
static void parse_udp_sysex(const uint8_t *data, uint16_t len) static void parse_udp_sysex(const uint8_t *data, uint16_t len)
{ {
/* raw SysEx: F0 7D CMD ...payload F7 */
if (len < 3 || data[0] != 0xF0 || data[1] != 0x7D) if (len < 3 || data[0] != 0xF0 || data[1] != 0x7D)
return; return;
/* find F7 terminator */
uint16_t end = 0; uint16_t end = 0;
for (uint16_t i = 2; i < len; i++) { for (uint16_t i = 2; i < len; i++) {
if (data[i] == 0xF7) { end = i; break; } if (data[i] == 0xF7) { end = i; break; }
} }
if (!end) return; if (!end) return;
BleCommand cmd; Command cmd;
memset(&cmd, 0, sizeof(cmd)); memset(&cmd, 0, sizeof(cmd));
cmd.type = data[2]; cmd.type = data[2];
switch (cmd.type) { switch (cmd.type) {
case CMD_SET_SWEEP: case CMD_SET_SWEEP:
if (len < 16) return; if (len < 16) return;
cmd.sweep.freq_start = ble_decode_float(&data[3]); cmd.sweep.freq_start = decode_float(&data[3]);
cmd.sweep.freq_stop = ble_decode_float(&data[8]); cmd.sweep.freq_stop = decode_float(&data[8]);
cmd.sweep.ppd = ble_decode_u16(&data[13]); cmd.sweep.ppd = decode_u16(&data[13]);
break; break;
case CMD_SET_RTIA: case CMD_SET_RTIA:
if (len < 4) return; if (len < 4) return;
@ -104,52 +102,80 @@ static void parse_udp_sysex(const uint8_t *data, uint16_t len)
break; break;
case CMD_START_LSV: case CMD_START_LSV:
if (len < 19) return; if (len < 19) return;
cmd.lsv.v_start = ble_decode_float(&data[3]); cmd.lsv.v_start = decode_float(&data[3]);
cmd.lsv.v_stop = ble_decode_float(&data[8]); cmd.lsv.v_stop = decode_float(&data[8]);
cmd.lsv.scan_rate = ble_decode_float(&data[13]); cmd.lsv.scan_rate = decode_float(&data[13]);
cmd.lsv.lp_rtia = data[18]; cmd.lsv.lp_rtia = data[18];
break; break;
case CMD_START_AMP: case CMD_START_AMP:
if (len < 19) return; if (len < 19) return;
cmd.amp.v_hold = ble_decode_float(&data[3]); cmd.amp.v_hold = decode_float(&data[3]);
cmd.amp.interval_ms = ble_decode_float(&data[8]); cmd.amp.interval_ms = decode_float(&data[8]);
cmd.amp.duration_s = ble_decode_float(&data[13]); cmd.amp.duration_s = decode_float(&data[13]);
cmd.amp.lp_rtia = data[18]; cmd.amp.lp_rtia = data[18];
break; break;
case CMD_START_CL: case CMD_START_CL:
if (len < 34) return; if (len < 34) return;
cmd.cl.v_cond = ble_decode_float(&data[3]); cmd.cl.v_cond = decode_float(&data[3]);
cmd.cl.t_cond_ms = ble_decode_float(&data[8]); cmd.cl.t_cond_ms = decode_float(&data[8]);
cmd.cl.v_free = ble_decode_float(&data[13]); cmd.cl.v_free = decode_float(&data[13]);
cmd.cl.v_total = ble_decode_float(&data[18]); cmd.cl.v_total = decode_float(&data[18]);
cmd.cl.t_dep_ms = ble_decode_float(&data[23]); cmd.cl.t_dep_ms = decode_float(&data[23]);
cmd.cl.t_meas_ms = ble_decode_float(&data[28]); cmd.cl.t_meas_ms = decode_float(&data[28]);
cmd.cl.lp_rtia = data[33]; cmd.cl.lp_rtia = data[33];
break; break;
case CMD_START_PH: case CMD_START_PH:
if (len < 8) return; if (len < 8) return;
cmd.ph.stabilize_s = ble_decode_float(&data[3]); cmd.ph.stabilize_s = decode_float(&data[3]);
break; break;
case CMD_START_CLEAN: case CMD_START_CLEAN:
if (len < 13) return; if (len < 13) return;
cmd.clean.v_mv = ble_decode_float(&data[3]); cmd.clean.v_mv = decode_float(&data[3]);
cmd.clean.duration_s = ble_decode_float(&data[8]); cmd.clean.duration_s = decode_float(&data[8]);
break;
case CMD_SET_CELL_K:
if (len < 8) return;
cmd.cell_k = decode_float(&data[3]);
break;
case CMD_SESSION_CREATE:
if (len < 5) return;
cmd.session_create.name_len = data[3] & 0x7F;
if (cmd.session_create.name_len > MAX_SESSION_NAME)
cmd.session_create.name_len = MAX_SESSION_NAME;
for (uint8_t i = 0; i < cmd.session_create.name_len && (4 + i) < end; i++)
cmd.session_create.name[i] = data[4 + i];
break;
case CMD_SESSION_SWITCH:
if (len < 4) return;
cmd.session_switch.id = data[3] & 0x7F;
break;
case CMD_SESSION_RENAME:
if (len < 6) return;
cmd.session_rename.id = data[3] & 0x7F;
cmd.session_rename.name_len = data[4] & 0x7F;
if (cmd.session_rename.name_len > MAX_SESSION_NAME)
cmd.session_rename.name_len = MAX_SESSION_NAME;
for (uint8_t i = 0; i < cmd.session_rename.name_len && (5 + i) < end; i++)
cmd.session_rename.name[i] = data[5 + i];
break; break;
case CMD_START_SWEEP: case CMD_START_SWEEP:
case CMD_GET_CONFIG: case CMD_GET_CONFIG:
case CMD_STOP_AMP: case CMD_STOP_AMP:
case CMD_GET_TEMP: case CMD_GET_TEMP:
case CMD_GET_CELL_K:
case CMD_START_REFS: case CMD_START_REFS:
case CMD_GET_REFS: case CMD_GET_REFS:
case CMD_CLEAR_REFS: case CMD_CLEAR_REFS:
case CMD_OPEN_CAL: case CMD_OPEN_CAL:
case CMD_CLEAR_OPEN_CAL: case CMD_CLEAR_OPEN_CAL:
case CMD_SESSION_LIST:
case CMD_HEARTBEAT:
break; break;
default: default:
return; return;
} }
ble_push_command(&cmd); protocol_push_command(&cmd);
} }
static void udp_rx_task(void *param) static void udp_rx_task(void *param)
@ -185,6 +211,11 @@ int wifi_send_sysex(const uint8_t *sysex, uint16_t len)
return sent > 0 ? 0 : -1; return sent > 0 ? 0 : -1;
} }
int wifi_get_client_count(void)
{
return client_count;
}
static void wifi_event_handler(void *arg, esp_event_base_t base, static void wifi_event_handler(void *arg, esp_event_base_t base,
int32_t id, void *data) int32_t id, void *data)
{ {

View File

@ -5,5 +5,6 @@
int wifi_transport_init(void); int wifi_transport_init(void);
int wifi_send_sysex(const uint8_t *sysex, uint16_t len); int wifi_send_sysex(const uint8_t *sysex, uint16_t len);
int wifi_get_client_count(void);
#endif #endif

View File

@ -1,8 +1 @@
CONFIG_IDF_TARGET="esp32s3" CONFIG_IDF_TARGET="esp32s3"
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
CONFIG_BT_NIMBLE_ROLE_CENTRAL=y
CONFIG_BT_NIMBLE_ROLE_OBSERVER=n
CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y