460 lines
12 KiB
C
460 lines
12 KiB
C
#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: keepalive ---- */
|
|
|
|
int send_keepalive(void)
|
|
{
|
|
uint8_t sx[] = { 0xF0, 0x7D, RSP_KEEPALIVE, 0xF7 };
|
|
return send_sysex(sx, sizeof(sx));
|
|
}
|
|
|
|
/* ---- 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 calibration ---- */
|
|
|
|
int send_ph_cal(float slope, float offset)
|
|
{
|
|
uint8_t sx[16];
|
|
uint16_t p = 0;
|
|
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_PH_CAL;
|
|
encode_float(slope, &sx[p]); p += 5;
|
|
encode_float(offset, &sx[p]); p += 5;
|
|
sx[p++] = 0xF7;
|
|
return send_sysex(sx, p);
|
|
}
|
|
|
|
/* ---- 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: chlorine factor ---- */
|
|
|
|
int send_cl_factor(float f)
|
|
{
|
|
uint8_t sx[12];
|
|
uint16_t p = 0;
|
|
sx[p++] = 0xF0; sx[p++] = 0x7D; sx[p++] = RSP_CL_FACTOR;
|
|
encode_float(f, &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));
|
|
}
|