firmware: WiFi AP + UDP transport — parallel to BLE, same protocol

This commit is contained in:
jess 2026-03-31 19:55:24 -07:00
parent 7570510491
commit 201d9881ce
6 changed files with 323 additions and 22 deletions

View File

@ -1,3 +1,3 @@
idf_component_register(SRCS "eis4.c" "eis.c" "echem.c" "ble.c" "temp.c" "refs.c" idf_component_register(SRCS "eis4.c" "eis.c" "echem.c" "ble.c" "wifi_transport.c" "temp.c" "refs.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES ad5941 ad5941_port bt nvs_flash) REQUIRES ad5941 ad5941_port bt nvs_flash esp_wifi esp_netif esp_event)

View File

@ -1,4 +1,5 @@
#include "ble.h" #include "ble.h"
#include "wifi_transport.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@ -100,7 +101,7 @@ static void encode_u16(uint16_t val, uint8_t *out)
out[2] = p[1] & 0x7F; out[2] = p[1] & 0x7F;
} }
static float decode_float(const uint8_t *d) float ble_decode_float(const uint8_t *d)
{ {
uint8_t b[4]; uint8_t b[4];
b[0] = d[1] | ((d[0] & 1) << 7); b[0] = d[1] | ((d[0] & 1) << 7);
@ -112,7 +113,7 @@ static float decode_float(const uint8_t *d)
return v; return v;
} }
static uint16_t decode_u16(const uint8_t *d) uint16_t ble_decode_u16(const uint8_t *d)
{ {
uint8_t b[2]; uint8_t b[2];
b[0] = d[1] | ((d[0] & 1) << 7); b[0] = d[1] | ((d[0] & 1) << 7);
@ -135,9 +136,9 @@ static void parse_one_sysex(const uint8_t *midi, uint16_t mlen)
switch (cmd.type) { switch (cmd.type) {
case CMD_SET_SWEEP: case CMD_SET_SWEEP:
if (mlen < 16) return; if (mlen < 16) return;
cmd.sweep.freq_start = decode_float(&midi[3]); cmd.sweep.freq_start = ble_decode_float(&midi[3]);
cmd.sweep.freq_stop = decode_float(&midi[8]); cmd.sweep.freq_stop = ble_decode_float(&midi[8]);
cmd.sweep.ppd = decode_u16(&midi[13]); cmd.sweep.ppd = ble_decode_u16(&midi[13]);
break; break;
case CMD_SET_RTIA: case CMD_SET_RTIA:
if (mlen < 4) return; if (mlen < 4) return;
@ -153,37 +154,37 @@ static void parse_one_sysex(const uint8_t *midi, uint16_t mlen)
break; break;
case CMD_START_LSV: case CMD_START_LSV:
if (mlen < 19) return; if (mlen < 19) return;
cmd.lsv.v_start = decode_float(&midi[3]); cmd.lsv.v_start = ble_decode_float(&midi[3]);
cmd.lsv.v_stop = decode_float(&midi[8]); cmd.lsv.v_stop = ble_decode_float(&midi[8]);
cmd.lsv.scan_rate = decode_float(&midi[13]); cmd.lsv.scan_rate = ble_decode_float(&midi[13]);
cmd.lsv.lp_rtia = midi[18]; cmd.lsv.lp_rtia = midi[18];
break; break;
case CMD_START_AMP: case CMD_START_AMP:
if (mlen < 19) return; if (mlen < 19) return;
cmd.amp.v_hold = decode_float(&midi[3]); cmd.amp.v_hold = ble_decode_float(&midi[3]);
cmd.amp.interval_ms = decode_float(&midi[8]); cmd.amp.interval_ms = ble_decode_float(&midi[8]);
cmd.amp.duration_s = decode_float(&midi[13]); cmd.amp.duration_s = ble_decode_float(&midi[13]);
cmd.amp.lp_rtia = midi[18]; cmd.amp.lp_rtia = midi[18];
break; break;
case CMD_START_CL: case CMD_START_CL:
if (mlen < 34) return; if (mlen < 34) return;
cmd.cl.v_cond = decode_float(&midi[3]); cmd.cl.v_cond = ble_decode_float(&midi[3]);
cmd.cl.t_cond_ms = decode_float(&midi[8]); cmd.cl.t_cond_ms = ble_decode_float(&midi[8]);
cmd.cl.v_free = decode_float(&midi[13]); cmd.cl.v_free = ble_decode_float(&midi[13]);
cmd.cl.v_total = decode_float(&midi[18]); cmd.cl.v_total = ble_decode_float(&midi[18]);
cmd.cl.t_dep_ms = decode_float(&midi[23]); cmd.cl.t_dep_ms = ble_decode_float(&midi[23]);
cmd.cl.t_meas_ms = decode_float(&midi[28]); cmd.cl.t_meas_ms = ble_decode_float(&midi[28]);
cmd.cl.lp_rtia = midi[33]; cmd.cl.lp_rtia = midi[33];
break; break;
case CMD_START_PH: case CMD_START_PH:
if (mlen < 8) return; if (mlen < 8) return;
cmd.ph.stabilize_s = decode_float(&midi[3]); cmd.ph.stabilize_s = ble_decode_float(&midi[3]);
break; break;
case CMD_START_CLEAN: case CMD_START_CLEAN:
if (mlen < 13) return; if (mlen < 13) return;
cmd.clean.v_mv = decode_float(&midi[3]); cmd.clean.v_mv = ble_decode_float(&midi[3]);
cmd.clean.duration_s = decode_float(&midi[8]); cmd.clean.duration_s = ble_decode_float(&midi[8]);
break; break;
case CMD_START_SWEEP: case CMD_START_SWEEP:
case CMD_GET_CONFIG: case CMD_GET_CONFIG:
@ -599,6 +600,9 @@ static void host_task(void *param)
static int send_sysex(const uint8_t *sysex, uint16_t len) 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) if (conn_count == 0)
return -1; return -1;
@ -675,6 +679,11 @@ int ble_recv_command(BleCommand *cmd, uint32_t timeout_ms)
return xQueueReceive(cmd_queue, cmd, ticks) == pdTRUE ? 0 : -1; 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) int ble_send_sweep_start(uint32_t num_points, float freq_start, float freq_stop)
{ {
uint8_t sx[20]; uint8_t sx[20];

View File

@ -68,6 +68,13 @@ void ble_wait_for_connection(void);
/* blocking receive from command queue */ /* blocking receive from command queue */
int ble_recv_command(BleCommand *cmd, uint32_t timeout_ms); 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 */ /* outbound: EIS */
int ble_send_sweep_start(uint32_t num_points, float freq_start, float freq_stop); 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_eis_point(uint16_t index, const EISPoint *pt);

View File

@ -4,11 +4,14 @@
#include "eis.h" #include "eis.h"
#include "echem.h" #include "echem.h"
#include "ble.h" #include "ble.h"
#include "wifi_transport.h"
#include "temp.h" #include "temp.h"
#include "refs.h" #include "refs.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_log.h" #include "esp_log.h"
#define AD5941_EXPECTED_ADIID 0x4144 #define AD5941_EXPECTED_ADIID 0x4144
@ -55,8 +58,12 @@ void app_main(void)
eis_load_open_cal(); eis_load_open_cal();
temp_init(); temp_init();
esp_netif_init();
esp_event_loop_create_default();
esp_log_level_set("NimBLE", ESP_LOG_WARN); esp_log_level_set("NimBLE", ESP_LOG_WARN);
ble_init(); ble_init();
wifi_transport_init();
printf("Waiting for BLE connection...\n"); printf("Waiting for BLE connection...\n");
ble_wait_for_connection(); ble_wait_for_connection();
ble_send_config(&cfg); ble_send_config(&cfg);

269
main/wifi_transport.c Normal file
View File

@ -0,0 +1,269 @@
#include "wifi_transport.h"
#include "ble.h"
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "lwip/sockets.h"
#define WIFI_SSID "EIS4"
#define WIFI_PASS "eis4data"
#define WIFI_CHANNEL 1
#define WIFI_MAX_CONN 2
#define UDP_PORT 5941
#define UDP_BUF_SIZE 128
#define MAX_UDP_CLIENTS 4
#define CLIENT_TIMEOUT_MS 30000
static int udp_sock = -1;
static struct {
struct sockaddr_in addr;
TickType_t last_seen;
bool active;
} clients[MAX_UDP_CLIENTS];
static int client_count;
static void client_touch(const struct sockaddr_in *addr)
{
TickType_t now = xTaskGetTickCount();
for (int i = 0; i < client_count; i++) {
if (clients[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr &&
clients[i].addr.sin_port == addr->sin_port) {
clients[i].last_seen = now;
return;
}
}
if (client_count < MAX_UDP_CLIENTS) {
clients[client_count].addr = *addr;
clients[client_count].last_seen = now;
clients[client_count].active = true;
client_count++;
printf("UDP: client added (%d/%d)\n", client_count, MAX_UDP_CLIENTS);
}
}
static void clients_expire(void)
{
TickType_t now = xTaskGetTickCount();
TickType_t timeout = pdMS_TO_TICKS(CLIENT_TIMEOUT_MS);
for (int i = 0; i < client_count; ) {
if ((now - clients[i].last_seen) > timeout) {
clients[i] = clients[--client_count];
printf("UDP: client expired (%d/%d)\n", client_count, MAX_UDP_CLIENTS);
} else {
i++;
}
}
}
static void parse_udp_sysex(const uint8_t *data, uint16_t len)
{
/* raw SysEx: F0 7D CMD ...payload F7 */
if (len < 3 || data[0] != 0xF0 || data[1] != 0x7D)
return;
/* find F7 terminator */
uint16_t end = 0;
for (uint16_t i = 2; i < len; i++) {
if (data[i] == 0xF7) { end = i; break; }
}
if (!end) return;
BleCommand cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.type = data[2];
switch (cmd.type) {
case CMD_SET_SWEEP:
if (len < 16) return;
cmd.sweep.freq_start = ble_decode_float(&data[3]);
cmd.sweep.freq_stop = ble_decode_float(&data[8]);
cmd.sweep.ppd = ble_decode_u16(&data[13]);
break;
case CMD_SET_RTIA:
if (len < 4) return;
cmd.rtia = data[3];
break;
case CMD_SET_RCAL:
if (len < 4) return;
cmd.rcal = data[3];
break;
case CMD_SET_ELECTRODE:
if (len < 4) return;
cmd.electrode = data[3];
break;
case CMD_START_LSV:
if (len < 19) return;
cmd.lsv.v_start = ble_decode_float(&data[3]);
cmd.lsv.v_stop = ble_decode_float(&data[8]);
cmd.lsv.scan_rate = ble_decode_float(&data[13]);
cmd.lsv.lp_rtia = data[18];
break;
case CMD_START_AMP:
if (len < 19) return;
cmd.amp.v_hold = ble_decode_float(&data[3]);
cmd.amp.interval_ms = ble_decode_float(&data[8]);
cmd.amp.duration_s = ble_decode_float(&data[13]);
cmd.amp.lp_rtia = data[18];
break;
case CMD_START_CL:
if (len < 34) return;
cmd.cl.v_cond = ble_decode_float(&data[3]);
cmd.cl.t_cond_ms = ble_decode_float(&data[8]);
cmd.cl.v_free = ble_decode_float(&data[13]);
cmd.cl.v_total = ble_decode_float(&data[18]);
cmd.cl.t_dep_ms = ble_decode_float(&data[23]);
cmd.cl.t_meas_ms = ble_decode_float(&data[28]);
cmd.cl.lp_rtia = data[33];
break;
case CMD_START_PH:
if (len < 8) return;
cmd.ph.stabilize_s = ble_decode_float(&data[3]);
break;
case CMD_START_CLEAN:
if (len < 13) return;
cmd.clean.v_mv = ble_decode_float(&data[3]);
cmd.clean.duration_s = ble_decode_float(&data[8]);
break;
case CMD_START_SWEEP:
case CMD_GET_CONFIG:
case CMD_STOP_AMP:
case CMD_GET_TEMP:
case CMD_START_REFS:
case CMD_GET_REFS:
case CMD_CLEAR_REFS:
case CMD_OPEN_CAL:
case CMD_CLEAR_OPEN_CAL:
break;
default:
return;
}
ble_push_command(&cmd);
}
static void udp_rx_task(void *param)
{
(void)param;
uint8_t buf[UDP_BUF_SIZE];
struct sockaddr_in src;
socklen_t slen = sizeof(src);
for (;;) {
int n = recvfrom(udp_sock, buf, sizeof(buf), 0,
(struct sockaddr *)&src, &slen);
if (n <= 0) continue;
client_touch(&src);
clients_expire();
parse_udp_sysex(buf, (uint16_t)n);
}
}
int wifi_send_sysex(const uint8_t *sysex, uint16_t len)
{
if (udp_sock < 0 || client_count == 0)
return -1;
int sent = 0;
for (int i = 0; i < client_count; i++) {
int r = sendto(udp_sock, sysex, len, 0,
(struct sockaddr *)&clients[i].addr,
sizeof(clients[i].addr));
if (r > 0) sent++;
}
return sent > 0 ? 0 : -1;
}
static void wifi_event_handler(void *arg, esp_event_base_t base,
int32_t id, void *data)
{
(void)arg; (void)data;
if (base == WIFI_EVENT) {
if (id == WIFI_EVENT_AP_STACONNECTED)
printf("WiFi: station connected\n");
else if (id == WIFI_EVENT_AP_STADISCONNECTED)
printf("WiFi: station disconnected\n");
}
}
static int wifi_ap_init(void)
{
esp_netif_create_default_wifi_ap();
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err = esp_wifi_init(&wifi_cfg);
if (err) return err;
esp_event_handler_instance_t inst;
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
wifi_event_handler, NULL, &inst);
wifi_config_t ap_cfg = {
.ap = {
.ssid = WIFI_SSID,
.ssid_len = sizeof(WIFI_SSID) - 1,
.channel = WIFI_CHANNEL,
.password = WIFI_PASS,
.max_connection = WIFI_MAX_CONN,
.authmode = WIFI_AUTH_WPA2_PSK,
},
};
esp_wifi_set_mode(WIFI_MODE_APSTA);
esp_wifi_set_config(WIFI_IF_AP, &ap_cfg);
err = esp_wifi_start();
if (err) return err;
printf("WiFi: AP \"%s\" on channel %d\n", WIFI_SSID, WIFI_CHANNEL);
return 0;
}
static int udp_init(void)
{
udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (udp_sock < 0) return -1;
struct sockaddr_in bind_addr = {
.sin_family = AF_INET,
.sin_port = htons(UDP_PORT),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
if (bind(udp_sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
close(udp_sock);
udp_sock = -1;
return -1;
}
printf("UDP: listening on port %d\n", UDP_PORT);
return 0;
}
int wifi_transport_init(void)
{
int rc = wifi_ap_init();
if (rc) {
printf("WiFi: AP init failed: %d\n", rc);
return rc;
}
rc = udp_init();
if (rc) {
printf("UDP: init failed\n");
return rc;
}
xTaskCreate(udp_rx_task, "udp_rx", 4096, NULL, 5, NULL);
return 0;
}

9
main/wifi_transport.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef WIFI_TRANSPORT_H
#define WIFI_TRANSPORT_H
#include <stdint.h>
int wifi_transport_init(void);
int wifi_send_sysex(const uint8_t *sysex, uint16_t len);
#endif