340 lines
9.6 KiB
C
340 lines
9.6 KiB
C
#include "wifi_transport.h"
|
|
#include "protocol.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 4
|
|
|
|
#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)
|
|
{
|
|
if (len < 3 || data[0] != 0xF0 || data[1] != 0x7D)
|
|
return;
|
|
|
|
uint16_t end = 0;
|
|
for (uint16_t i = 2; i < len; i++) {
|
|
if (data[i] == 0xF7) { end = i; break; }
|
|
}
|
|
if (!end) return;
|
|
|
|
Command cmd;
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.type = data[2];
|
|
|
|
switch (cmd.type) {
|
|
case CMD_SET_SWEEP:
|
|
if (len < 16) return;
|
|
cmd.sweep.freq_start = decode_float(&data[3]);
|
|
cmd.sweep.freq_stop = decode_float(&data[8]);
|
|
cmd.sweep.ppd = decode_u16(&data[13]);
|
|
break;
|
|
case CMD_SET_RTIA:
|
|
if (len < 4) return;
|
|
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 = decode_float(&data[3]);
|
|
cmd.lsv.v_stop = decode_float(&data[8]);
|
|
cmd.lsv.scan_rate = decode_float(&data[13]);
|
|
cmd.lsv.lp_rtia = data[18];
|
|
break;
|
|
case CMD_START_AMP:
|
|
if (len < 19) return;
|
|
cmd.amp.v_hold = decode_float(&data[3]);
|
|
cmd.amp.interval_ms = decode_float(&data[8]);
|
|
cmd.amp.duration_s = decode_float(&data[13]);
|
|
cmd.amp.lp_rtia = data[18];
|
|
break;
|
|
case CMD_START_CL:
|
|
if (len < 34) return;
|
|
cmd.cl.v_cond = decode_float(&data[3]);
|
|
cmd.cl.t_cond_ms = decode_float(&data[8]);
|
|
cmd.cl.v_free = decode_float(&data[13]);
|
|
cmd.cl.v_total = decode_float(&data[18]);
|
|
cmd.cl.t_dep_ms = decode_float(&data[23]);
|
|
cmd.cl.t_meas_ms = decode_float(&data[28]);
|
|
cmd.cl.lp_rtia = data[33];
|
|
break;
|
|
case CMD_START_PH:
|
|
if (len < 8) return;
|
|
cmd.ph.stabilize_s = decode_float(&data[3]);
|
|
break;
|
|
case CMD_START_CLEAN:
|
|
if (len < 13) return;
|
|
cmd.clean.v_mv = decode_float(&data[3]);
|
|
cmd.clean.duration_s = decode_float(&data[8]);
|
|
break;
|
|
case CMD_SET_CELL_K:
|
|
if (len < 8) return;
|
|
cmd.cell_k = decode_float(&data[3]);
|
|
break;
|
|
case CMD_SESSION_CREATE:
|
|
if (len < 5) return;
|
|
cmd.session_create.name_len = data[3] & 0x7F;
|
|
if (cmd.session_create.name_len > MAX_SESSION_NAME)
|
|
cmd.session_create.name_len = MAX_SESSION_NAME;
|
|
for (uint8_t i = 0; i < cmd.session_create.name_len && (4 + i) < end; i++)
|
|
cmd.session_create.name[i] = data[4 + i];
|
|
break;
|
|
case CMD_SESSION_SWITCH:
|
|
if (len < 4) return;
|
|
cmd.session_switch.id = data[3] & 0x7F;
|
|
break;
|
|
case CMD_SESSION_RENAME:
|
|
if (len < 6) return;
|
|
cmd.session_rename.id = data[3] & 0x7F;
|
|
cmd.session_rename.name_len = data[4] & 0x7F;
|
|
if (cmd.session_rename.name_len > MAX_SESSION_NAME)
|
|
cmd.session_rename.name_len = MAX_SESSION_NAME;
|
|
for (uint8_t i = 0; i < cmd.session_rename.name_len && (5 + i) < end; i++)
|
|
cmd.session_rename.name[i] = data[5 + i];
|
|
break;
|
|
case CMD_START_SWEEP:
|
|
case CMD_GET_CONFIG:
|
|
case CMD_STOP_AMP:
|
|
case CMD_GET_TEMP:
|
|
case CMD_GET_CELL_K:
|
|
case CMD_START_REFS:
|
|
case CMD_GET_REFS:
|
|
case CMD_CLEAR_REFS:
|
|
case CMD_OPEN_CAL:
|
|
case CMD_CLEAR_OPEN_CAL:
|
|
case CMD_SESSION_LIST:
|
|
case CMD_HEARTBEAT:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
protocol_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;
|
|
}
|
|
|
|
int wifi_get_client_count(void)
|
|
{
|
|
return client_count;
|
|
}
|
|
|
|
static void wifi_event_handler(void *arg, esp_event_base_t base,
|
|
int32_t id, void *data)
|
|
{
|
|
(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");
|
|
}
|
|
}
|
|
|
|
#ifndef STA_SSID
|
|
#define STA_SSID ""
|
|
#endif
|
|
#ifndef STA_PASS
|
|
#define STA_PASS ""
|
|
#endif
|
|
|
|
static void sta_event_handler(void *arg, esp_event_base_t base,
|
|
int32_t id, void *data)
|
|
{
|
|
(void)arg;
|
|
if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
printf("WiFi: STA disconnected, reconnecting...\n");
|
|
esp_wifi_connect();
|
|
} else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
|
|
ip_event_got_ip_t *evt = (ip_event_got_ip_t *)data;
|
|
printf("WiFi: STA connected, IP " IPSTR "\n", IP2STR(&evt->ip_info.ip));
|
|
}
|
|
}
|
|
|
|
static int wifi_ap_init(void)
|
|
{
|
|
esp_netif_create_default_wifi_ap();
|
|
esp_netif_create_default_wifi_sta();
|
|
|
|
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);
|
|
esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED,
|
|
sta_event_handler, NULL, &inst);
|
|
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
|
sta_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);
|
|
|
|
if (strlen(STA_SSID) > 0) {
|
|
wifi_config_t sta_cfg = {0};
|
|
strncpy((char *)sta_cfg.sta.ssid, STA_SSID, sizeof(sta_cfg.sta.ssid) - 1);
|
|
strncpy((char *)sta_cfg.sta.password, STA_PASS, sizeof(sta_cfg.sta.password) - 1);
|
|
sta_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
|
esp_wifi_set_config(WIFI_IF_STA, &sta_cfg);
|
|
}
|
|
|
|
err = esp_wifi_start();
|
|
if (err) return err;
|
|
|
|
if (strlen(STA_SSID) > 0) {
|
|
esp_wifi_connect();
|
|
printf("WiFi: STA connecting to \"%s\"\n", STA_SSID);
|
|
}
|
|
|
|
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;
|
|
}
|