Update: websocket optimization
- Use protobuf - Eliminate unnecessary optimization logic - UART, sensor, status data transmitted as pb data Signed-off-by: YoungSoo Shin <shinys000114@gmail.com>
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
set(WEB_APP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/page)
|
||||
set(GZ_OUTPUT_FILE ${WEB_APP_SOURCE_DIR}/dist/index.html.gz)
|
||||
|
||||
set(PROTO_DIR ${CMAKE_SOURCE_DIR}/proto)
|
||||
set(PROTO_OUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto)
|
||||
set(PROTO_FILE ${PROTO_DIR}/status.proto)
|
||||
set(PROTO_C_FILE ${PROTO_OUT_DIR}/status.pb.c)
|
||||
set(PROTO_H_FILE ${PROTO_OUT_DIR}/status.pb.h)
|
||||
|
||||
# Check npm is available
|
||||
find_program(NPM_EXECUTABLE npm)
|
||||
if (NOT NPM_EXECUTABLE)
|
||||
@@ -10,8 +16,8 @@ endif ()
|
||||
|
||||
# Register the component. Now, CMake knows how GZ_OUTPUT_FILE is generated
|
||||
# and can correctly handle the dependency for embedding.
|
||||
idf_component_register(SRC_DIRS "app" "nconfig" "wifi" "indicator" "system" "service"
|
||||
INCLUDE_DIRS "include"
|
||||
idf_component_register(SRC_DIRS "app" "nconfig" "wifi" "indicator" "system" "service" "proto"
|
||||
INCLUDE_DIRS "include" "proto"
|
||||
EMBED_FILES ${GZ_OUTPUT_FILE}
|
||||
)
|
||||
|
||||
@@ -48,4 +54,18 @@ add_custom_target(build_web_app ALL
|
||||
DEPENDS ${GZ_OUTPUT_FILE}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${PROTO_C_FILE} ${PROTO_H_FILE}
|
||||
COMMAND protoc --nanopb_out=${PROTO_OUT_DIR} status.proto
|
||||
WORKING_DIRECTORY ${PROTO_DIR}
|
||||
DEPENDS ${PROTO_FILE}
|
||||
COMMENT "Generating C sources from ${PROTO_FILE} using nanopb"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(protobuf_generate ALL
|
||||
DEPENDS ${PROTO_C_FILE} ${PROTO_H_FILE}
|
||||
)
|
||||
|
||||
add_dependencies(${COMPONENT_LIB} build_web_app)
|
||||
add_dependencies(${COMPONENT_LIB} protobuf_generate)
|
||||
@@ -3,3 +3,4 @@ dependencies:
|
||||
joltwallet/littlefs: ==1.20.1
|
||||
esp-idf-lib/ina3221: ^1.1.7
|
||||
esp-idf-lib/pca9557: ^1.0.7
|
||||
nikas-belogolov/nanopb: ^1.0.0
|
||||
|
||||
24
main/proto/status.pb.c
Normal file
24
main/proto/status.pb.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#include "status.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
PB_BIND(SensorChannelData, SensorChannelData, AUTO)
|
||||
|
||||
|
||||
PB_BIND(SensorData, SensorData, AUTO)
|
||||
|
||||
|
||||
PB_BIND(WifiStatus, WifiStatus, AUTO)
|
||||
|
||||
|
||||
PB_BIND(UartData, UartData, AUTO)
|
||||
|
||||
|
||||
PB_BIND(StatusMessage, StatusMessage, AUTO)
|
||||
|
||||
|
||||
|
||||
155
main/proto/status.pb.h
Normal file
155
main/proto/status.pb.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#ifndef PB_STATUS_PB_H_INCLUDED
|
||||
#define PB_STATUS_PB_H_INCLUDED
|
||||
#include <pb.h>
|
||||
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
/* Struct definitions */
|
||||
/* Represents data for a single sensor channel */
|
||||
typedef struct _SensorChannelData {
|
||||
float voltage;
|
||||
float current;
|
||||
float power;
|
||||
} SensorChannelData;
|
||||
|
||||
/* Contains data for all sensor channels and system info */
|
||||
typedef struct _SensorData {
|
||||
bool has_usb;
|
||||
SensorChannelData usb;
|
||||
bool has_main;
|
||||
SensorChannelData main;
|
||||
bool has_vin;
|
||||
SensorChannelData vin;
|
||||
uint32_t timestamp;
|
||||
uint32_t uptime_sec;
|
||||
} SensorData;
|
||||
|
||||
/* Contains WiFi connection status */
|
||||
typedef struct _WifiStatus {
|
||||
bool connected;
|
||||
pb_callback_t ssid;
|
||||
int32_t rssi;
|
||||
} WifiStatus;
|
||||
|
||||
/* Contains raw UART data */
|
||||
typedef struct _UartData {
|
||||
pb_callback_t data;
|
||||
} UartData;
|
||||
|
||||
/* Top-level message for all websocket communication */
|
||||
typedef struct _StatusMessage {
|
||||
pb_size_t which_payload;
|
||||
union {
|
||||
SensorData sensor_data;
|
||||
WifiStatus wifi_status;
|
||||
UartData uart_data;
|
||||
} payload;
|
||||
} StatusMessage;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define SensorChannelData_init_default {0, 0, 0}
|
||||
#define SensorData_init_default {false, SensorChannelData_init_default, false, SensorChannelData_init_default, false, SensorChannelData_init_default, 0, 0}
|
||||
#define WifiStatus_init_default {0, {{NULL}, NULL}, 0}
|
||||
#define UartData_init_default {{{NULL}, NULL}}
|
||||
#define StatusMessage_init_default {0, {SensorData_init_default}}
|
||||
#define SensorChannelData_init_zero {0, 0, 0}
|
||||
#define SensorData_init_zero {false, SensorChannelData_init_zero, false, SensorChannelData_init_zero, false, SensorChannelData_init_zero, 0, 0}
|
||||
#define WifiStatus_init_zero {0, {{NULL}, NULL}, 0}
|
||||
#define UartData_init_zero {{{NULL}, NULL}}
|
||||
#define StatusMessage_init_zero {0, {SensorData_init_zero}}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define SensorChannelData_voltage_tag 1
|
||||
#define SensorChannelData_current_tag 2
|
||||
#define SensorChannelData_power_tag 3
|
||||
#define SensorData_usb_tag 1
|
||||
#define SensorData_main_tag 2
|
||||
#define SensorData_vin_tag 3
|
||||
#define SensorData_timestamp_tag 4
|
||||
#define SensorData_uptime_sec_tag 5
|
||||
#define WifiStatus_connected_tag 1
|
||||
#define WifiStatus_ssid_tag 2
|
||||
#define WifiStatus_rssi_tag 3
|
||||
#define UartData_data_tag 1
|
||||
#define StatusMessage_sensor_data_tag 1
|
||||
#define StatusMessage_wifi_status_tag 2
|
||||
#define StatusMessage_uart_data_tag 3
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define SensorChannelData_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, voltage, 1) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, current, 2) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, power, 3)
|
||||
#define SensorChannelData_CALLBACK NULL
|
||||
#define SensorChannelData_DEFAULT NULL
|
||||
|
||||
#define SensorData_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, usb, 1) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, main, 2) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, vin, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, timestamp, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, uptime_sec, 5)
|
||||
#define SensorData_CALLBACK NULL
|
||||
#define SensorData_DEFAULT NULL
|
||||
#define SensorData_usb_MSGTYPE SensorChannelData
|
||||
#define SensorData_main_MSGTYPE SensorChannelData
|
||||
#define SensorData_vin_MSGTYPE SensorChannelData
|
||||
|
||||
#define WifiStatus_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, connected, 1) \
|
||||
X(a, CALLBACK, SINGULAR, STRING, ssid, 2) \
|
||||
X(a, STATIC, SINGULAR, INT32, rssi, 3)
|
||||
#define WifiStatus_CALLBACK pb_default_field_callback
|
||||
#define WifiStatus_DEFAULT NULL
|
||||
|
||||
#define UartData_FIELDLIST(X, a) \
|
||||
X(a, CALLBACK, SINGULAR, BYTES, data, 1)
|
||||
#define UartData_CALLBACK pb_default_field_callback
|
||||
#define UartData_DEFAULT NULL
|
||||
|
||||
#define StatusMessage_FIELDLIST(X, a) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload,sensor_data,payload.sensor_data), 1) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload,wifi_status,payload.wifi_status), 2) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload,uart_data,payload.uart_data), 3)
|
||||
#define StatusMessage_CALLBACK NULL
|
||||
#define StatusMessage_DEFAULT NULL
|
||||
#define StatusMessage_payload_sensor_data_MSGTYPE SensorData
|
||||
#define StatusMessage_payload_wifi_status_MSGTYPE WifiStatus
|
||||
#define StatusMessage_payload_uart_data_MSGTYPE UartData
|
||||
|
||||
extern const pb_msgdesc_t SensorChannelData_msg;
|
||||
extern const pb_msgdesc_t SensorData_msg;
|
||||
extern const pb_msgdesc_t WifiStatus_msg;
|
||||
extern const pb_msgdesc_t UartData_msg;
|
||||
extern const pb_msgdesc_t StatusMessage_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define SensorChannelData_fields &SensorChannelData_msg
|
||||
#define SensorData_fields &SensorData_msg
|
||||
#define WifiStatus_fields &WifiStatus_msg
|
||||
#define UartData_fields &UartData_msg
|
||||
#define StatusMessage_fields &StatusMessage_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* WifiStatus_size depends on runtime parameters */
|
||||
/* UartData_size depends on runtime parameters */
|
||||
/* StatusMessage_size depends on runtime parameters */
|
||||
#define STATUS_PB_H_MAX_SIZE SensorData_size
|
||||
#define SensorChannelData_size 15
|
||||
#define SensorData_size 63
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -2,10 +2,8 @@
|
||||
// Created by shinys on 25. 8. 18..
|
||||
//
|
||||
|
||||
|
||||
#include "monitor.h"
|
||||
#include <time.h>
|
||||
#include "cJSON.h"
|
||||
#include "datalog.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
@@ -13,16 +11,20 @@
|
||||
#include "esp_wifi_types_generic.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "ina3221.h"
|
||||
#include "pb.h"
|
||||
#include "pb_encode.h"
|
||||
#include "status.pb.h"
|
||||
#include "webserver.h"
|
||||
#include "wifi.h"
|
||||
|
||||
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
||||
#define PM_SCL CONFIG_I2C_GPIO_SCL
|
||||
|
||||
const char* channel_names[] = {"USB", "MAIN", "VIN"};
|
||||
#define PB_BUFFER_SIZE 256
|
||||
|
||||
static const char* TAG = "monitor";
|
||||
|
||||
ina3221_t ina3221 = {
|
||||
/* shunt values are 100 mOhm for each channel */
|
||||
.shunt = {10, 10, 10},
|
||||
.mask.mask_register = INA3221_DEFAULT_MASK,
|
||||
.i2c_dev = {0},
|
||||
@@ -40,69 +42,100 @@ ina3221_t ina3221 = {
|
||||
},
|
||||
};
|
||||
|
||||
// Timer callback function to read sensor data
|
||||
static bool encode_string(pb_ostream_t* stream, const pb_field_t* field, void* const* arg)
|
||||
{
|
||||
const char* str = (const char*)(*arg);
|
||||
if (!str)
|
||||
{
|
||||
return true; // Nothing to encode
|
||||
}
|
||||
if (!pb_encode_tag_for_field(stream, field))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return pb_encode_string(stream, (uint8_t*)str, strlen(str));
|
||||
}
|
||||
|
||||
static void send_pb_message(const pb_msgdesc_t* fields, const void* src_struct)
|
||||
{
|
||||
uint8_t buffer[PB_BUFFER_SIZE];
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
||||
|
||||
if (!pb_encode(&stream, fields, src_struct))
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to encode protobuf message: %s", PB_GET_ERROR(&stream));
|
||||
return;
|
||||
}
|
||||
|
||||
push_data_to_ws(buffer, stream.bytes_written);
|
||||
}
|
||||
|
||||
static void sensor_timer_callback(void* arg)
|
||||
{
|
||||
// Get system uptime
|
||||
int64_t uptime_us = esp_timer_get_time();
|
||||
uint32_t uptime_sec = (uint32_t)(uptime_us / 1000000);
|
||||
uint32_t timestamp = (uint32_t)time(NULL);
|
||||
|
||||
channel_data_t channel_data[NUM_CHANNELS];
|
||||
channel_data_t channel_data_log[NUM_CHANNELS];
|
||||
|
||||
StatusMessage message = StatusMessage_init_zero;
|
||||
message.which_payload = StatusMessage_sensor_data_tag;
|
||||
SensorData* sensor_data = &message.payload.sensor_data;
|
||||
|
||||
sensor_data->has_usb = true;
|
||||
sensor_data->has_main = true;
|
||||
sensor_data->has_vin = true;
|
||||
|
||||
SensorChannelData* channels[] = {&sensor_data->usb, &sensor_data->main, &sensor_data->vin};
|
||||
|
||||
// Create JSON object with sensor data
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
for (uint8_t i = 0; i < INA3221_BUS_NUMBER; i++)
|
||||
{
|
||||
float voltage, current, power;
|
||||
|
||||
ina3221_get_bus_voltage(&ina3221, i, &voltage);
|
||||
ina3221_get_shunt_value(&ina3221, i, NULL, ¤t);
|
||||
|
||||
current /= 1000.0f; // mA to A
|
||||
power = voltage * current;
|
||||
|
||||
// Populate data for datalog
|
||||
channel_data[i].voltage = voltage;
|
||||
channel_data[i].current = current;
|
||||
channel_data[i].power = power;
|
||||
// For datalog
|
||||
channel_data_log[i] = (channel_data_t){.voltage = voltage, .current = current, .power = power};
|
||||
|
||||
// Populate data for websocket
|
||||
cJSON* v = cJSON_AddObjectToObject(root, channel_names[i]);
|
||||
cJSON_AddNumberToObject(v, "voltage", voltage);
|
||||
cJSON_AddNumberToObject(v, "current", current);
|
||||
cJSON_AddNumberToObject(v, "power", power);
|
||||
// For protobuf
|
||||
channels[i]->voltage = voltage;
|
||||
channels[i]->current = current;
|
||||
channels[i]->power = power;
|
||||
}
|
||||
|
||||
// Add data to log file
|
||||
datalog_add(timestamp, channel_data);
|
||||
// datalog_add(timestamp, channel_data_log);
|
||||
|
||||
cJSON_AddStringToObject(root, "type", "sensor_data");
|
||||
cJSON_AddNumberToObject(root, "timestamp", timestamp);
|
||||
cJSON_AddNumberToObject(root, "uptime_sec", uptime_sec);
|
||||
sensor_data->timestamp = timestamp;
|
||||
sensor_data->uptime_sec = uptime_sec;
|
||||
|
||||
// Push data to WebSocket clients
|
||||
push_data_to_ws(root);
|
||||
send_pb_message(StatusMessage_fields, &message);
|
||||
}
|
||||
|
||||
static void status_wifi_callback(void* arg)
|
||||
{
|
||||
wifi_ap_record_t ap_info;
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
StatusMessage message = StatusMessage_init_zero;
|
||||
message.which_payload = StatusMessage_wifi_status_tag;
|
||||
WifiStatus* wifi_status = &message.payload.wifi_status;
|
||||
|
||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
||||
{
|
||||
cJSON_AddStringToObject(root, "type", "wifi_status");
|
||||
cJSON_AddBoolToObject(root, "connected", true);
|
||||
cJSON_AddStringToObject(root, "ssid", (const char*)ap_info.ssid);
|
||||
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
|
||||
wifi_status->connected = true;
|
||||
wifi_status->ssid.funcs.encode = &encode_string;
|
||||
wifi_status->ssid.arg = (void*)ap_info.ssid;
|
||||
wifi_status->rssi = ap_info.rssi;
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON_AddBoolToObject(root, "connected", false);
|
||||
wifi_status->connected = false;
|
||||
wifi_status->ssid.arg = ""; // Empty string
|
||||
wifi_status->rssi = 0;
|
||||
}
|
||||
|
||||
push_data_to_ws(root);
|
||||
send_pb_message(StatusMessage_fields, &message);
|
||||
}
|
||||
|
||||
static esp_timer_handle_t sensor_timer;
|
||||
@@ -111,24 +144,15 @@ static esp_timer_handle_t wifi_status_timer;
|
||||
void init_status_monitor()
|
||||
{
|
||||
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
||||
|
||||
// logger
|
||||
datalog_init();
|
||||
|
||||
// Timer configuration
|
||||
const esp_timer_create_args_t sensor_timer_args = {
|
||||
.callback = &sensor_timer_callback,
|
||||
.name = "sensor_reading_timer" // Optional name for debugging
|
||||
};
|
||||
|
||||
const esp_timer_create_args_t wifi_timer_args = {
|
||||
.callback = &status_wifi_callback,
|
||||
.name = "wifi_status_timer" // Optional name for debugging
|
||||
};
|
||||
const esp_timer_create_args_t sensor_timer_args = {.callback = &sensor_timer_callback,
|
||||
.name = "sensor_reading_timer"};
|
||||
const esp_timer_create_args_t wifi_timer_args = {.callback = &status_wifi_callback, .name = "wifi_status_timer"};
|
||||
|
||||
ESP_ERROR_CHECK(esp_timer_create(&sensor_timer_args, &sensor_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wifi_timer_args, &wifi_status_timer));
|
||||
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(sensor_timer, 1000000)); // 1sec
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5)); // 5s in microseconds
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(sensor_timer, 1000000));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5));
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ void start_webserver(void)
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.stack_size = 1024 * 8;
|
||||
config.max_uri_handlers = 10;
|
||||
config.task_priority = 12;
|
||||
|
||||
if (httpd_start(&server, &config) != ESP_OK)
|
||||
{
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
#ifndef ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||
#define ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||
#include "cJSON.h"
|
||||
|
||||
#include "esp_http_server.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void register_wifi_endpoint(httpd_handle_t server);
|
||||
void register_ws_endpoint(httpd_handle_t server);
|
||||
void register_control_endpoint(httpd_handle_t server);
|
||||
void push_data_to_ws(cJSON* data);
|
||||
void push_data_to_ws(const uint8_t* data, size_t len);
|
||||
void register_reboot_endpoint(httpd_handle_t server);
|
||||
esp_err_t change_baud_rate(int baud_rate);
|
||||
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
// Created by shinys on 25. 8. 18..
|
||||
//
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "nconfig.h"
|
||||
#include "pb.h"
|
||||
#include "pb_encode.h"
|
||||
#include "status.pb.h"
|
||||
#include "webserver.h"
|
||||
|
||||
#define UART_NUM UART_NUM_1
|
||||
@@ -16,13 +18,13 @@
|
||||
#define UART_TX_PIN CONFIG_GPIO_UART_TX
|
||||
#define UART_RX_PIN CONFIG_GPIO_UART_RX
|
||||
#define CHUNK_SIZE (1024)
|
||||
#define PB_UART_BUFFER_SIZE (CHUNK_SIZE + 64)
|
||||
|
||||
static const char* TAG = "ws-uart";
|
||||
|
||||
static int client_fd = -1;
|
||||
static SemaphoreHandle_t client_fd_mutex;
|
||||
|
||||
// Unified message structure for the websocket queue
|
||||
enum ws_message_type
|
||||
{
|
||||
WS_MSG_STATUS,
|
||||
@@ -32,25 +34,28 @@ enum ws_message_type
|
||||
struct ws_message
|
||||
{
|
||||
enum ws_message_type type;
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
cJSON* data;
|
||||
} status;
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
} uart;
|
||||
} content;
|
||||
struct bytes_arg
|
||||
{
|
||||
const void* data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static QueueHandle_t ws_queue;
|
||||
|
||||
// Unified task to send data from the queue to the websocket client
|
||||
static bool encode_bytes_callback(pb_ostream_t* stream, const pb_field_t* field, void* const* arg)
|
||||
{
|
||||
struct bytes_arg* br = (struct bytes_arg*)(*arg);
|
||||
if (!pb_encode_tag_for_field(stream, field))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return pb_encode_string(stream, (uint8_t*)br->data, br->len);
|
||||
}
|
||||
|
||||
static void unified_ws_sender_task(void* arg)
|
||||
{
|
||||
httpd_handle_t server = (httpd_handle_t)arg;
|
||||
@@ -67,41 +72,17 @@ static void unified_ws_sender_task(void* arg)
|
||||
if (fd <= 0)
|
||||
{
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
// Free memory if client is not connected
|
||||
if (msg.type == WS_MSG_STATUS)
|
||||
{
|
||||
cJSON_Delete(msg.content.status.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
free(msg.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
httpd_ws_frame_t ws_pkt = {0};
|
||||
esp_err_t err = ESP_FAIL;
|
||||
ws_pkt.payload = msg.data;
|
||||
ws_pkt.len = msg.len;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
||||
|
||||
if (msg.type == WS_MSG_STATUS)
|
||||
{
|
||||
char* json_string = cJSON_Print(msg.content.status.data);
|
||||
cJSON_Delete(msg.content.status.data);
|
||||
|
||||
ws_pkt.payload = (uint8_t*)json_string;
|
||||
ws_pkt.len = strlen(json_string);
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||
free(json_string);
|
||||
}
|
||||
else
|
||||
{
|
||||
// WS_MSG_UART
|
||||
ws_pkt.payload = msg.content.uart.data;
|
||||
ws_pkt.len = msg.content.uart.len;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
||||
err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
esp_err_t err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||
free(msg.data);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
@@ -114,7 +95,6 @@ static void unified_ws_sender_task(void* arg)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue receive timed out, send a PING to keep connection alive
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
int fd = client_fd;
|
||||
if (fd > 0)
|
||||
@@ -138,125 +118,77 @@ static void unified_ws_sender_task(void* arg)
|
||||
static void uart_polling_task(void* arg)
|
||||
{
|
||||
static uint8_t data_buf[BUF_SIZE];
|
||||
const TickType_t MIN_POLLING_INTERVAL = pdMS_TO_TICKS(1);
|
||||
const TickType_t MAX_POLLING_INTERVAL = pdMS_TO_TICKS(10);
|
||||
const TickType_t READ_TIMEOUT = pdMS_TO_TICKS(5);
|
||||
|
||||
TickType_t current_interval = MIN_POLLING_INTERVAL;
|
||||
int consecutive_empty_polls = 0;
|
||||
int cached_client_fd = -1;
|
||||
TickType_t last_client_check = 0;
|
||||
const TickType_t CLIENT_CHECK_INTERVAL = pdMS_TO_TICKS(100);
|
||||
static uint8_t pb_buffer[PB_UART_BUFFER_SIZE];
|
||||
|
||||
while (1)
|
||||
{
|
||||
TickType_t current_time = xTaskGetTickCount();
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
int fd = client_fd;
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
|
||||
if (current_time - last_client_check >= CLIENT_CHECK_INTERVAL)
|
||||
if (fd <= 0)
|
||||
{
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
cached_client_fd = client_fd;
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
last_client_check = current_time;
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t available_len;
|
||||
esp_err_t err = uart_get_buffered_data_len(UART_NUM, &available_len);
|
||||
uart_get_buffered_data_len(UART_NUM, &available_len);
|
||||
|
||||
if (err != ESP_OK || available_len == 0)
|
||||
if (available_len == 0)
|
||||
{
|
||||
consecutive_empty_polls++;
|
||||
if (consecutive_empty_polls > 5)
|
||||
{
|
||||
current_interval = MAX_POLLING_INTERVAL;
|
||||
}
|
||||
else if (consecutive_empty_polls > 2)
|
||||
{
|
||||
current_interval = pdMS_TO_TICKS(5);
|
||||
}
|
||||
|
||||
if (cached_client_fd <= 0)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
continue;
|
||||
}
|
||||
|
||||
vTaskDelay(current_interval);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
consecutive_empty_polls = 0;
|
||||
current_interval = MIN_POLLING_INTERVAL;
|
||||
size_t read_len = (available_len > BUF_SIZE) ? BUF_SIZE : available_len;
|
||||
int bytes_read = uart_read_bytes(UART_NUM, data_buf, read_len, pdMS_TO_TICKS(5));
|
||||
|
||||
if (cached_client_fd <= 0)
|
||||
{
|
||||
uart_flush_input(UART_NUM);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t total_processed = 0;
|
||||
while (available_len > 0 && total_processed < BUF_SIZE)
|
||||
{
|
||||
size_t read_size =
|
||||
(available_len > (BUF_SIZE - total_processed)) ? (BUF_SIZE - total_processed) : available_len;
|
||||
|
||||
int bytes_read = uart_read_bytes(UART_NUM, data_buf + total_processed, read_size, READ_TIMEOUT);
|
||||
|
||||
if (bytes_read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
total_processed += bytes_read;
|
||||
available_len -= bytes_read;
|
||||
|
||||
uart_get_buffered_data_len(UART_NUM, &available_len);
|
||||
}
|
||||
|
||||
if (total_processed > 0)
|
||||
if (bytes_read > 0)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < total_processed)
|
||||
while (offset < bytes_read)
|
||||
{
|
||||
const size_t chunk_size =
|
||||
(total_processed - offset > CHUNK_SIZE) ? CHUNK_SIZE : (total_processed - offset);
|
||||
size_t chunk_size = (bytes_read - offset > CHUNK_SIZE) ? CHUNK_SIZE : (bytes_read - offset);
|
||||
|
||||
StatusMessage message = StatusMessage_init_zero;
|
||||
message.which_payload = StatusMessage_uart_data_tag;
|
||||
struct bytes_arg arg = {.data = data_buf + offset, .len = chunk_size};
|
||||
message.payload.uart_data.data.funcs.encode = &encode_bytes_callback;
|
||||
message.payload.uart_data.data.arg = &arg;
|
||||
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(pb_buffer, sizeof(pb_buffer));
|
||||
if (!pb_encode(&stream, StatusMessage_fields, &message))
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to encode uart data: %s", PB_GET_ERROR(&stream));
|
||||
offset += chunk_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct ws_message msg;
|
||||
msg.type = WS_MSG_UART;
|
||||
msg.content.uart.data = malloc(chunk_size);
|
||||
if (!msg.content.uart.data)
|
||||
msg.len = stream.bytes_written;
|
||||
msg.data = malloc(msg.len);
|
||||
|
||||
if (!msg.data)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for uart ws msg");
|
||||
break;
|
||||
offset += chunk_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(msg.content.uart.data, data_buf + offset, chunk_size);
|
||||
msg.content.uart.len = chunk_size;
|
||||
memcpy(msg.data, pb_buffer, msg.len);
|
||||
|
||||
if (xQueueSend(ws_queue, &msg, 0) != pdPASS)
|
||||
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(10)) != pdPASS)
|
||||
{
|
||||
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(5)) != pdPASS)
|
||||
{
|
||||
ESP_LOGW(TAG, "ws sender queue full, dropping %zu bytes", chunk_size);
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
ESP_LOGW(TAG, "ws sender queue full, dropping %zu bytes", chunk_size);
|
||||
free(msg.data);
|
||||
}
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (available_len > 0)
|
||||
{
|
||||
vTaskDelay(MIN_POLLING_INTERVAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
vTaskDelay(current_interval);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
@@ -267,20 +199,17 @@ static esp_err_t ws_handler(httpd_req_t* req)
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
if (client_fd > 0)
|
||||
{
|
||||
// A client is already connected. Reject the new connection.
|
||||
ESP_LOGW(TAG, "Another client tried to connect, but a session is already active. Rejecting.");
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Another client is already connected");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// No client is connected. Accept the new one.
|
||||
int new_fd = httpd_req_to_sockfd(req);
|
||||
ESP_LOGI(TAG, "Accepting new websocket connection: %d", new_fd);
|
||||
client_fd = new_fd;
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
|
||||
// Reset queue and flush UART buffer for the new session
|
||||
xQueueReset(ws_queue);
|
||||
uart_flush_input(UART_NUM);
|
||||
return ESP_OK;
|
||||
@@ -312,7 +241,6 @@ static esp_err_t ws_handler(httpd_req_t* req)
|
||||
void register_ws_endpoint(httpd_handle_t server)
|
||||
{
|
||||
size_t baud_rate_len;
|
||||
|
||||
nconfig_get_str_len(UART_BAUD_RATE, &baud_rate_len);
|
||||
char buf[baud_rate_len];
|
||||
nconfig_read(UART_BAUD_RATE, buf, baud_rate_len);
|
||||
@@ -323,32 +251,38 @@ void register_ws_endpoint(httpd_handle_t server)
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
// .source_clk = UART_SCLK_APB,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 0, NULL, 0));
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, NULL, 0));
|
||||
|
||||
httpd_uri_t ws = {.uri = "/ws", .method = HTTP_GET, .handler = ws_handler, .user_ctx = NULL, .is_websocket = true};
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
|
||||
client_fd_mutex = xSemaphoreCreateMutex();
|
||||
ws_queue = xQueueCreate(10, sizeof(struct ws_message)); // Combined queue
|
||||
ws_queue = xQueueCreate(10, sizeof(struct ws_message));
|
||||
|
||||
xTaskCreate(uart_polling_task, "uart_polling_task", 1024 * 4, NULL, 8, NULL);
|
||||
xTaskCreate(unified_ws_sender_task, "ws_sender_task", 1024 * 6, server, 9, NULL);
|
||||
}
|
||||
|
||||
void push_data_to_ws(cJSON* data)
|
||||
void push_data_to_ws(const uint8_t* data, size_t len)
|
||||
{
|
||||
struct ws_message msg;
|
||||
msg.type = WS_MSG_STATUS;
|
||||
msg.content.status.data = data;
|
||||
msg.data = malloc(len);
|
||||
if (!msg.data)
|
||||
{ ESP_LOGE(TAG, "Failed to allocate memory for status ws msg");
|
||||
return;
|
||||
}
|
||||
memcpy(msg.data, data, len);
|
||||
msg.len = len;
|
||||
|
||||
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(10)) != pdPASS)
|
||||
{
|
||||
ESP_LOGW(TAG, "WS queue full, dropping status message");
|
||||
cJSON_Delete(data);
|
||||
free(msg.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user