diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 9508d66..6f06b68 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -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 "." - REQUIRES ad5941 ad5941_port bt nvs_flash) + REQUIRES ad5941 ad5941_port bt nvs_flash esp_wifi esp_netif esp_event) diff --git a/main/ble.c b/main/ble.c index 8da6d30..1b31087 100644 --- a/main/ble.c +++ b/main/ble.c @@ -1,4 +1,5 @@ #include "ble.h" +#include "wifi_transport.h" #include #include #include "freertos/FreeRTOS.h" @@ -100,7 +101,7 @@ static void encode_u16(uint16_t val, uint8_t *out) 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]; b[0] = d[1] | ((d[0] & 1) << 7); @@ -112,7 +113,7 @@ static float decode_float(const uint8_t *d) return v; } -static uint16_t decode_u16(const uint8_t *d) +uint16_t ble_decode_u16(const uint8_t *d) { uint8_t b[2]; 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) { case CMD_SET_SWEEP: if (mlen < 16) return; - cmd.sweep.freq_start = decode_float(&midi[3]); - cmd.sweep.freq_stop = decode_float(&midi[8]); - cmd.sweep.ppd = decode_u16(&midi[13]); + 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; @@ -153,37 +154,37 @@ static void parse_one_sysex(const uint8_t *midi, uint16_t mlen) break; case CMD_START_LSV: if (mlen < 19) return; - cmd.lsv.v_start = decode_float(&midi[3]); - cmd.lsv.v_stop = decode_float(&midi[8]); - cmd.lsv.scan_rate = decode_float(&midi[13]); + 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 = decode_float(&midi[3]); - cmd.amp.interval_ms = decode_float(&midi[8]); - cmd.amp.duration_s = decode_float(&midi[13]); + 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 = decode_float(&midi[3]); - cmd.cl.t_cond_ms = decode_float(&midi[8]); - cmd.cl.v_free = decode_float(&midi[13]); - cmd.cl.v_total = decode_float(&midi[18]); - cmd.cl.t_dep_ms = decode_float(&midi[23]); - cmd.cl.t_meas_ms = decode_float(&midi[28]); + 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 = decode_float(&midi[3]); + cmd.ph.stabilize_s = ble_decode_float(&midi[3]); break; case CMD_START_CLEAN: if (mlen < 13) return; - cmd.clean.v_mv = decode_float(&midi[3]); - cmd.clean.duration_s = decode_float(&midi[8]); + cmd.clean.v_mv = ble_decode_float(&midi[3]); + cmd.clean.duration_s = ble_decode_float(&midi[8]); break; case CMD_START_SWEEP: 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) { + /* UDP: raw SysEx to all WiFi clients */ + wifi_send_sysex(sysex, len); + if (conn_count == 0) 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; } +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]; diff --git a/main/ble.h b/main/ble.h index 929d41e..20bab11 100644 --- a/main/ble.h +++ b/main/ble.h @@ -68,6 +68,13 @@ 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); diff --git a/main/eis4.c b/main/eis4.c index ef0fdb0..383c491 100644 --- a/main/eis4.c +++ b/main/eis4.c @@ -4,11 +4,14 @@ #include "eis.h" #include "echem.h" #include "ble.h" +#include "wifi_transport.h" #include "temp.h" #include "refs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" #include "esp_log.h" #define AD5941_EXPECTED_ADIID 0x4144 @@ -55,8 +58,12 @@ void app_main(void) eis_load_open_cal(); temp_init(); + esp_netif_init(); + esp_event_loop_create_default(); + esp_log_level_set("NimBLE", ESP_LOG_WARN); ble_init(); + wifi_transport_init(); printf("Waiting for BLE connection...\n"); ble_wait_for_connection(); ble_send_config(&cfg); diff --git a/main/wifi_transport.c b/main/wifi_transport.c new file mode 100644 index 0000000..99823d4 --- /dev/null +++ b/main/wifi_transport.c @@ -0,0 +1,269 @@ +#include "wifi_transport.h" +#include "ble.h" +#include +#include +#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; +} diff --git a/main/wifi_transport.h b/main/wifi_transport.h new file mode 100644 index 0000000..4ff0873 --- /dev/null +++ b/main/wifi_transport.h @@ -0,0 +1,9 @@ +#ifndef WIFI_TRANSPORT_H +#define WIFI_TRANSPORT_H + +#include + +int wifi_transport_init(void); +int wifi_send_sysex(const uint8_t *sysex, uint16_t len); + +#endif