diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 09834e8..0c4470b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -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 "." - 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}) target_compile_definitions(${COMPONENT_LIB} PRIVATE diff --git a/main/ble.c b/main/ble.c deleted file mode 100644 index 34b768b..0000000 --- a/main/ble.c +++ /dev/null @@ -1,897 +0,0 @@ -#include "ble.h" -#include "wifi_transport.h" -#include -#include -#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, - ¶ms, 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)); -} diff --git a/main/ble.h b/main/ble.h deleted file mode 100644 index 5696dc0..0000000 --- a/main/ble.h +++ /dev/null @@ -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 diff --git a/main/echem.c b/main/echem.c index 50886ee..59f060d 100644 --- a/main/echem.c +++ b/main/echem.c @@ -1,6 +1,6 @@ #include "echem.h" #include "ad5940.h" -#include "ble.h" +#include "protocol.h" #include #include #include @@ -449,8 +449,8 @@ int echem_amp(const AmpConfig *cfg, AmpPoint *out, uint32_t max_points, amp_poin uint32_t count = 0; for (uint32_t i = 0; i < max_samples; i++) { - BleCommand cmd; - if (ble_recv_command(&cmd, 0) == 0 && cmd.type == CMD_STOP_AMP) + Command cmd; + if (protocol_recv_command(&cmd, 0) == 0 && cmd.type == CMD_STOP_AMP) break; float i_ua = read_current_ua(rtia); diff --git a/main/eis4.c b/main/eis4.c index 4b94641..5385e6d 100644 --- a/main/eis4.c +++ b/main/eis4.c @@ -3,7 +3,7 @@ #include "ad5941_port.h" #include "eis.h" #include "echem.h" -#include "ble.h" +#include "protocol.h" #include "wifi_transport.h" #include "temp.h" #include "refs.h" @@ -12,7 +12,6 @@ #include "nvs_flash.h" #include "esp_netif.h" #include "esp_event.h" -#include "esp_log.h" #define AD5941_EXPECTED_ADIID 0x4144 static EISConfig cfg; @@ -27,10 +26,10 @@ static void do_sweep(void) eis_init(&cfg); uint32_t n = eis_calc_num_points(&cfg); - ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); - int got = eis_sweep(results, n, ble_send_eis_point); + send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); + int got = eis_sweep(results, n, send_eis_point); printf("Sweep complete: %d points\n", got); - ble_send_sweep_end(); + send_sweep_end(); } void app_main(void) @@ -62,16 +61,13 @@ void app_main(void) esp_netif_init(); esp_event_loop_create_default(); - esp_log_level_set("NimBLE", ESP_LOG_WARN); - ble_init(); + protocol_init(); wifi_transport_init(); - printf("Waiting for BLE connection...\n"); - ble_wait_for_connection(); - ble_send_config(&cfg); + printf("EIS4: WiFi transport ready, waiting for clients\n"); - BleCommand cmd; + Command cmd; for (;;) { - if (ble_recv_command(&cmd, UINT32_MAX) != 0) + if (protocol_recv_command(&cmd, UINT32_MAX) != 0) continue; switch (cmd.type) { @@ -118,7 +114,7 @@ void app_main(void) break; case CMD_GET_CONFIG: - ble_send_config(&cfg); + send_config(&cfg); break; 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); 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); - int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, ble_send_lsv_point); + send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop); + int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, send_lsv_point); printf("LSV complete: %d points\n", got); - ble_send_lsv_end(); + send_lsv_end(); break; } @@ -147,15 +143,15 @@ void app_main(void) printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n", amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s); - ble_send_amp_start(amp_cfg.v_hold); - int got = echem_amp(&_cfg, amp_results, ECHEM_MAX_POINTS, ble_send_amp_point); + send_amp_start(amp_cfg.v_hold); + int got = echem_amp(&_cfg, amp_results, ECHEM_MAX_POINTS, send_amp_point); printf("Amp complete: %d points\n", got); - ble_send_amp_end(); + send_amp_end(); break; } case CMD_GET_TEMP: - ble_send_temp(temp_get()); + send_temp(temp_get()); break; case CMD_START_PH: { @@ -169,7 +165,7 @@ void app_main(void) echem_ph_ocp(&ph_cfg, &ph_result); printf("pH: OCP=%.1f mV, pH=%.2f\n", 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; } @@ -196,10 +192,10 @@ void app_main(void) printf("Open-circuit cal starting\n"); eis_init(&cfg); uint32_t n = eis_calc_num_points(&cfg); - ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); - int got = eis_open_cal(results, n, ble_send_eis_point); + send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); + int got = eis_open_cal(results, n, send_eis_point); printf("Open-circuit cal: %d points\n", got); - ble_send_sweep_end(); + send_sweep_end(); break; } @@ -210,12 +206,12 @@ void app_main(void) case CMD_SET_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); break; case CMD_GET_CELL_K: - ble_send_cell_k(eis_get_cell_k()); + send_cell_k(eis_get_cell_k()); break; 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); if (n_per < 2) n_per = 2; - ble_send_cl_start(2 * n_per); + send_cl_start(2 * n_per); ClResult cl_result; 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", 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); - ble_send_cl_end(); + send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua); + 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; } } } diff --git a/main/protocol.c b/main/protocol.c new file mode 100644 index 0000000..08a9c47 --- /dev/null +++ b/main/protocol.c @@ -0,0 +1,426 @@ +#include "protocol.h" +#include "wifi_transport.h" +#include +#include +#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)); +} diff --git a/main/protocol.h b/main/protocol.h new file mode 100644 index 0000000..54b756b --- /dev/null +++ b/main/protocol.h @@ -0,0 +1,155 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include +#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 diff --git a/main/refs.c b/main/refs.c index fd05bf7..94cc15e 100644 --- a/main/refs.c +++ b/main/refs.c @@ -1,5 +1,5 @@ #include "refs.h" -#include "ble.h" +#include "protocol.h" #include "temp.h" #include "ad5940.h" #include @@ -8,12 +8,9 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -/* LP RTIA register mapping (mirrors echem.c) */ extern const uint32_t lp_rtia_map[]; extern const float lp_rtia_ohms[]; -/* ---- ADC helpers ---- */ - static int32_t read_adc_code(void) { 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; } -/* ---- Clipping detection ---- */ - #define ADC_OK 0 #define ADC_CLIPPED 1 #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.DataRst = bFALSE; lp.LpDacCfg.PowerEn = bTRUE; - lp.LpDacCfg.DacData6Bit = 26; /* VZERO ~1094mV */ - lp.LpDacCfg.DacData12Bit = (uint16_t)((1093.75f - 200.0f) / 0.537f + 0.5f); /* 0mV cell */ + lp.LpDacCfg.DacData6Bit = 26; + lp.LpDacCfg.DacData12Bit = (uint16_t)((1093.75f - 200.0f) / 0.537f + 0.5f); lp.LpAmpCfg.LpAmpSel = LPAMP0; 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; if (low_clip != ADC_OK) { - /* binary search upward for first non-clipping */ uint8_t a = 0, b = LP_RTIA_512K; while (a < b) { uint8_t m = (a + b) / 2; @@ -208,7 +202,6 @@ static void lp_find_valid_range(LpRtiaRange *range) } if (high_clip != ADC_OK) { - /* binary search downward for last non-clipping */ uint8_t a = lo, b = LP_RTIA_512K; while (a < b) { 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; } -/* ---- Collection ---- */ - void refs_collect(RefStore *store, const EISConfig *cfg) { memset(store, 0, sizeof(*store)); - /* EIS phase: sweep each RTIA 0-7 with RCAL=3K */ EISConfig ref_cfg = *cfg; ref_cfg.rcal = RCAL_3K; @@ -239,95 +229,91 @@ void refs_collect(RefStore *store, const EISConfig *cfg) ref_cfg.rtia = (EISRtia)r; 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); - 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].valid = (got > 0) ? 1 : 0; - ble_send_sweep_end(); + send_sweep_end(); 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"); - ble_send_ref_frame(REF_MODE_LSV, 0); + send_ref_frame(REF_MODE_LSV, 0); lp_find_valid_range(&store->lsv_range); 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: 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); 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: 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); 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); - /* pH phase: OCP measurement */ printf("Ref: pH OCP\n"); - ble_send_ref_frame(REF_MODE_PH, 0); + send_ref_frame(REF_MODE_PH, 0); PhConfig ph_cfg; ph_cfg.stabilize_s = 10.0f; ph_cfg.temp_c = temp_get(); echem_ph_ocp(&ph_cfg, &store->ph_ref); 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; - ble_send_refs_done(); + send_refs_done(); printf("Ref collection complete\n"); } -/* ---- Send stored refs to client ---- */ - void refs_send(const RefStore *store) { if (!store->has_refs) { - ble_send_ref_status(0); + send_ref_status(0); return; } - ble_send_ref_status(1); + send_ref_status(1); for (int r = 0; r < REF_EIS_RTIA_COUNT; r++) { 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; - ble_send_sweep_start(n, store->eis[r].pts[0].freq_hz, - store->eis[r].pts[n - 1].freq_hz); + send_sweep_start(n, store->eis[r].pts[0].freq_hz, + store->eis[r].pts[n - 1].freq_hz); for (uint32_t i = 0; i < n; i++) - ble_send_eis_point((uint16_t)i, &store->eis[r].pts[i]); - ble_send_sweep_end(); + send_eis_point((uint16_t)i, &store->eis[r].pts[i]); + send_sweep_end(); } 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) - 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) - 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) { - ble_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_ref_frame(REF_MODE_PH, 0); + 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) { memset(store, 0, sizeof(*store)); - ble_send_ref_status(0); + send_ref_status(0); } diff --git a/main/wifi_transport.c b/main/wifi_transport.c index 57eced7..b5b6caa 100644 --- a/main/wifi_transport.c +++ b/main/wifi_transport.c @@ -1,5 +1,5 @@ #include "wifi_transport.h" -#include "ble.h" +#include "protocol.h" #include #include #include "freertos/FreeRTOS.h" @@ -13,7 +13,7 @@ #define WIFI_SSID "EIS4" #define WIFI_PASS "eis4data" #define WIFI_CHANNEL 1 -#define WIFI_MAX_CONN 2 +#define WIFI_MAX_CONN 4 #define UDP_PORT 5941 #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) { - /* raw SysEx: F0 7D CMD ...payload F7 */ if (len < 3 || data[0] != 0xF0 || data[1] != 0x7D) return; - /* find F7 terminator */ uint16_t end = 0; for (uint16_t i = 2; i < len; i++) { if (data[i] == 0xF7) { end = i; break; } } if (!end) return; - BleCommand cmd; + Command cmd; memset(&cmd, 0, sizeof(cmd)); cmd.type = data[2]; switch (cmd.type) { case CMD_SET_SWEEP: if (len < 16) return; - cmd.sweep.freq_start = ble_decode_float(&data[3]); - cmd.sweep.freq_stop = ble_decode_float(&data[8]); - cmd.sweep.ppd = ble_decode_u16(&data[13]); + cmd.sweep.freq_start = decode_float(&data[3]); + cmd.sweep.freq_stop = decode_float(&data[8]); + cmd.sweep.ppd = decode_u16(&data[13]); break; case CMD_SET_RTIA: if (len < 4) return; @@ -104,52 +102,80 @@ static void parse_udp_sysex(const uint8_t *data, uint16_t len) break; case CMD_START_LSV: if (len < 19) return; - cmd.lsv.v_start = ble_decode_float(&data[3]); - cmd.lsv.v_stop = ble_decode_float(&data[8]); - cmd.lsv.scan_rate = ble_decode_float(&data[13]); + cmd.lsv.v_start = decode_float(&data[3]); + cmd.lsv.v_stop = decode_float(&data[8]); + cmd.lsv.scan_rate = decode_float(&data[13]); cmd.lsv.lp_rtia = data[18]; break; case CMD_START_AMP: if (len < 19) return; - cmd.amp.v_hold = ble_decode_float(&data[3]); - cmd.amp.interval_ms = ble_decode_float(&data[8]); - cmd.amp.duration_s = ble_decode_float(&data[13]); + cmd.amp.v_hold = decode_float(&data[3]); + cmd.amp.interval_ms = decode_float(&data[8]); + cmd.amp.duration_s = decode_float(&data[13]); cmd.amp.lp_rtia = data[18]; break; case CMD_START_CL: if (len < 34) return; - cmd.cl.v_cond = ble_decode_float(&data[3]); - cmd.cl.t_cond_ms = ble_decode_float(&data[8]); - cmd.cl.v_free = ble_decode_float(&data[13]); - cmd.cl.v_total = ble_decode_float(&data[18]); - cmd.cl.t_dep_ms = ble_decode_float(&data[23]); - cmd.cl.t_meas_ms = ble_decode_float(&data[28]); + cmd.cl.v_cond = decode_float(&data[3]); + cmd.cl.t_cond_ms = decode_float(&data[8]); + cmd.cl.v_free = decode_float(&data[13]); + cmd.cl.v_total = decode_float(&data[18]); + cmd.cl.t_dep_ms = decode_float(&data[23]); + cmd.cl.t_meas_ms = decode_float(&data[28]); cmd.cl.lp_rtia = data[33]; break; case CMD_START_PH: if (len < 8) return; - cmd.ph.stabilize_s = ble_decode_float(&data[3]); + cmd.ph.stabilize_s = decode_float(&data[3]); break; case CMD_START_CLEAN: if (len < 13) return; - cmd.clean.v_mv = ble_decode_float(&data[3]); - cmd.clean.duration_s = ble_decode_float(&data[8]); + cmd.clean.v_mv = decode_float(&data[3]); + 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; 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: + case CMD_SESSION_LIST: + case CMD_HEARTBEAT: break; default: return; } - ble_push_command(&cmd); + protocol_push_command(&cmd); } 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; } +int wifi_get_client_count(void) +{ + return client_count; +} + static void wifi_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { diff --git a/main/wifi_transport.h b/main/wifi_transport.h index 4ff0873..3aa6353 100644 --- a/main/wifi_transport.h +++ b/main/wifi_transport.h @@ -5,5 +5,6 @@ int wifi_transport_init(void); int wifi_send_sysex(const uint8_t *sysex, uint16_t len); +int wifi_get_client_count(void); #endif diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 7ae2ec9..b01fdbe 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,8 +1 @@ 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