firmware: strip BLE/NimBLE, WiFi-only transport with session sync protocol
This commit is contained in:
parent
fc0ff900f1
commit
d13909c400
|
|
@ -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
|
||||||
|
|
|
||||||
897
main/ble.c
897
main/ble.c
|
|
@ -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,
|
|
||||||
¶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));
|
|
||||||
}
|
|
||||||
119
main/ble.h
119
main/ble.h
|
|
@ -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
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
98
main/eis4.c
98
main/eis4.c
|
|
@ -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(&_cfg, amp_results, ECHEM_MAX_POINTS, ble_send_amp_point);
|
int got = echem_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
72
main/refs.c
72
main/refs.c
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue