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(WEB_APP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/page)
|
||||||
set(GZ_OUTPUT_FILE ${WEB_APP_SOURCE_DIR}/dist/index.html.gz)
|
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
|
# Check npm is available
|
||||||
find_program(NPM_EXECUTABLE npm)
|
find_program(NPM_EXECUTABLE npm)
|
||||||
if (NOT NPM_EXECUTABLE)
|
if (NOT NPM_EXECUTABLE)
|
||||||
@@ -10,8 +16,8 @@ endif ()
|
|||||||
|
|
||||||
# Register the component. Now, CMake knows how GZ_OUTPUT_FILE is generated
|
# Register the component. Now, CMake knows how GZ_OUTPUT_FILE is generated
|
||||||
# and can correctly handle the dependency for embedding.
|
# and can correctly handle the dependency for embedding.
|
||||||
idf_component_register(SRC_DIRS "app" "nconfig" "wifi" "indicator" "system" "service"
|
idf_component_register(SRC_DIRS "app" "nconfig" "wifi" "indicator" "system" "service" "proto"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include" "proto"
|
||||||
EMBED_FILES ${GZ_OUTPUT_FILE}
|
EMBED_FILES ${GZ_OUTPUT_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,4 +54,18 @@ add_custom_target(build_web_app ALL
|
|||||||
DEPENDS ${GZ_OUTPUT_FILE}
|
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
|
joltwallet/littlefs: ==1.20.1
|
||||||
esp-idf-lib/ina3221: ^1.1.7
|
esp-idf-lib/ina3221: ^1.1.7
|
||||||
esp-idf-lib/pca9557: ^1.0.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..
|
// Created by shinys on 25. 8. 18..
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "cJSON.h"
|
|
||||||
#include "datalog.h"
|
#include "datalog.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
@@ -13,16 +11,20 @@
|
|||||||
#include "esp_wifi_types_generic.h"
|
#include "esp_wifi_types_generic.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "ina3221.h"
|
#include "ina3221.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "pb_encode.h"
|
||||||
|
#include "status.pb.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
|
|
||||||
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
||||||
#define PM_SCL CONFIG_I2C_GPIO_SCL
|
#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 = {
|
ina3221_t ina3221 = {
|
||||||
/* shunt values are 100 mOhm for each channel */
|
|
||||||
.shunt = {10, 10, 10},
|
.shunt = {10, 10, 10},
|
||||||
.mask.mask_register = INA3221_DEFAULT_MASK,
|
.mask.mask_register = INA3221_DEFAULT_MASK,
|
||||||
.i2c_dev = {0},
|
.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)
|
static void sensor_timer_callback(void* arg)
|
||||||
{
|
{
|
||||||
// Get system uptime
|
|
||||||
int64_t uptime_us = esp_timer_get_time();
|
int64_t uptime_us = esp_timer_get_time();
|
||||||
uint32_t uptime_sec = (uint32_t)(uptime_us / 1000000);
|
uint32_t uptime_sec = (uint32_t)(uptime_us / 1000000);
|
||||||
uint32_t timestamp = (uint32_t)time(NULL);
|
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++)
|
for (uint8_t i = 0; i < INA3221_BUS_NUMBER; i++)
|
||||||
{
|
{
|
||||||
float voltage, current, power;
|
float voltage, current, power;
|
||||||
|
|
||||||
ina3221_get_bus_voltage(&ina3221, i, &voltage);
|
ina3221_get_bus_voltage(&ina3221, i, &voltage);
|
||||||
ina3221_get_shunt_value(&ina3221, i, NULL, ¤t);
|
ina3221_get_shunt_value(&ina3221, i, NULL, ¤t);
|
||||||
|
|
||||||
current /= 1000.0f; // mA to A
|
current /= 1000.0f; // mA to A
|
||||||
power = voltage * current;
|
power = voltage * current;
|
||||||
|
|
||||||
// Populate data for datalog
|
// For datalog
|
||||||
channel_data[i].voltage = voltage;
|
channel_data_log[i] = (channel_data_t){.voltage = voltage, .current = current, .power = power};
|
||||||
channel_data[i].current = current;
|
|
||||||
channel_data[i].power = power;
|
|
||||||
|
|
||||||
// Populate data for websocket
|
// For protobuf
|
||||||
cJSON* v = cJSON_AddObjectToObject(root, channel_names[i]);
|
channels[i]->voltage = voltage;
|
||||||
cJSON_AddNumberToObject(v, "voltage", voltage);
|
channels[i]->current = current;
|
||||||
cJSON_AddNumberToObject(v, "current", current);
|
channels[i]->power = power;
|
||||||
cJSON_AddNumberToObject(v, "power", power);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data to log file
|
// datalog_add(timestamp, channel_data_log);
|
||||||
datalog_add(timestamp, channel_data);
|
|
||||||
|
|
||||||
cJSON_AddStringToObject(root, "type", "sensor_data");
|
sensor_data->timestamp = timestamp;
|
||||||
cJSON_AddNumberToObject(root, "timestamp", timestamp);
|
sensor_data->uptime_sec = uptime_sec;
|
||||||
cJSON_AddNumberToObject(root, "uptime_sec", uptime_sec);
|
|
||||||
|
|
||||||
// Push data to WebSocket clients
|
send_pb_message(StatusMessage_fields, &message);
|
||||||
push_data_to_ws(root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void status_wifi_callback(void* arg)
|
static void status_wifi_callback(void* arg)
|
||||||
{
|
{
|
||||||
wifi_ap_record_t ap_info;
|
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)
|
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "type", "wifi_status");
|
wifi_status->connected = true;
|
||||||
cJSON_AddBoolToObject(root, "connected", true);
|
wifi_status->ssid.funcs.encode = &encode_string;
|
||||||
cJSON_AddStringToObject(root, "ssid", (const char*)ap_info.ssid);
|
wifi_status->ssid.arg = (void*)ap_info.ssid;
|
||||||
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
|
wifi_status->rssi = ap_info.rssi;
|
||||||
}
|
}
|
||||||
else
|
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;
|
static esp_timer_handle_t sensor_timer;
|
||||||
@@ -111,24 +144,15 @@ static esp_timer_handle_t wifi_status_timer;
|
|||||||
void init_status_monitor()
|
void init_status_monitor()
|
||||||
{
|
{
|
||||||
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
||||||
|
|
||||||
// logger
|
|
||||||
datalog_init();
|
datalog_init();
|
||||||
|
|
||||||
// Timer configuration
|
const esp_timer_create_args_t sensor_timer_args = {.callback = &sensor_timer_callback,
|
||||||
const esp_timer_create_args_t sensor_timer_args = {
|
.name = "sensor_reading_timer"};
|
||||||
.callback = &sensor_timer_callback,
|
const esp_timer_create_args_t wifi_timer_args = {.callback = &status_wifi_callback, .name = "wifi_status_timer"};
|
||||||
.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
|
|
||||||
};
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(esp_timer_create(&sensor_timer_args, &sensor_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_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(sensor_timer, 1000000));
|
||||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5)); // 5s in microseconds
|
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();
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
config.stack_size = 1024 * 8;
|
config.stack_size = 1024 * 8;
|
||||||
config.max_uri_handlers = 10;
|
config.max_uri_handlers = 10;
|
||||||
|
config.task_priority = 12;
|
||||||
|
|
||||||
if (httpd_start(&server, &config) != ESP_OK)
|
if (httpd_start(&server, &config) != ESP_OK)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
|
|
||||||
#ifndef ODROID_REMOTE_HTTP_WEBSERVER_H
|
#ifndef ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||||
#define ODROID_REMOTE_HTTP_WEBSERVER_H
|
#define ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||||
#include "cJSON.h"
|
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
void register_wifi_endpoint(httpd_handle_t server);
|
void register_wifi_endpoint(httpd_handle_t server);
|
||||||
void register_ws_endpoint(httpd_handle_t server);
|
void register_ws_endpoint(httpd_handle_t server);
|
||||||
void register_control_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);
|
void register_reboot_endpoint(httpd_handle_t server);
|
||||||
esp_err_t change_baud_rate(int baud_rate);
|
esp_err_t change_baud_rate(int baud_rate);
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
// Created by shinys on 25. 8. 18..
|
// Created by shinys on 25. 8. 18..
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "cJSON.h"
|
|
||||||
#include "driver/uart.h"
|
#include "driver/uart.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
#include "nconfig.h"
|
#include "nconfig.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "pb_encode.h"
|
||||||
|
#include "status.pb.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
|
|
||||||
#define UART_NUM UART_NUM_1
|
#define UART_NUM UART_NUM_1
|
||||||
@@ -16,13 +18,13 @@
|
|||||||
#define UART_TX_PIN CONFIG_GPIO_UART_TX
|
#define UART_TX_PIN CONFIG_GPIO_UART_TX
|
||||||
#define UART_RX_PIN CONFIG_GPIO_UART_RX
|
#define UART_RX_PIN CONFIG_GPIO_UART_RX
|
||||||
#define CHUNK_SIZE (1024)
|
#define CHUNK_SIZE (1024)
|
||||||
|
#define PB_UART_BUFFER_SIZE (CHUNK_SIZE + 64)
|
||||||
|
|
||||||
static const char* TAG = "ws-uart";
|
static const char* TAG = "ws-uart";
|
||||||
|
|
||||||
static int client_fd = -1;
|
static int client_fd = -1;
|
||||||
static SemaphoreHandle_t client_fd_mutex;
|
static SemaphoreHandle_t client_fd_mutex;
|
||||||
|
|
||||||
// Unified message structure for the websocket queue
|
|
||||||
enum ws_message_type
|
enum ws_message_type
|
||||||
{
|
{
|
||||||
WS_MSG_STATUS,
|
WS_MSG_STATUS,
|
||||||
@@ -32,25 +34,28 @@ enum ws_message_type
|
|||||||
struct ws_message
|
struct ws_message
|
||||||
{
|
{
|
||||||
enum ws_message_type type;
|
enum ws_message_type type;
|
||||||
|
uint8_t* data;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
union
|
struct bytes_arg
|
||||||
{
|
{
|
||||||
struct
|
const void* data;
|
||||||
{
|
size_t len;
|
||||||
cJSON* data;
|
|
||||||
} status;
|
|
||||||
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
uint8_t* data;
|
|
||||||
size_t len;
|
|
||||||
} uart;
|
|
||||||
} content;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static QueueHandle_t ws_queue;
|
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)
|
static void unified_ws_sender_task(void* arg)
|
||||||
{
|
{
|
||||||
httpd_handle_t server = (httpd_handle_t)arg;
|
httpd_handle_t server = (httpd_handle_t)arg;
|
||||||
@@ -67,41 +72,17 @@ static void unified_ws_sender_task(void* arg)
|
|||||||
if (fd <= 0)
|
if (fd <= 0)
|
||||||
{
|
{
|
||||||
xSemaphoreGive(client_fd_mutex);
|
xSemaphoreGive(client_fd_mutex);
|
||||||
// Free memory if client is not connected
|
free(msg.data);
|
||||||
if (msg.type == WS_MSG_STATUS)
|
|
||||||
{
|
|
||||||
cJSON_Delete(msg.content.status.data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
free(msg.content.uart.data);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_ws_frame_t ws_pkt = {0};
|
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)
|
esp_err_t err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||||
{
|
free(msg.data);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
{
|
{
|
||||||
@@ -114,7 +95,6 @@ static void unified_ws_sender_task(void* arg)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Queue receive timed out, send a PING to keep connection alive
|
|
||||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||||
int fd = client_fd;
|
int fd = client_fd;
|
||||||
if (fd > 0)
|
if (fd > 0)
|
||||||
@@ -138,125 +118,77 @@ static void unified_ws_sender_task(void* arg)
|
|||||||
static void uart_polling_task(void* arg)
|
static void uart_polling_task(void* arg)
|
||||||
{
|
{
|
||||||
static uint8_t data_buf[BUF_SIZE];
|
static uint8_t data_buf[BUF_SIZE];
|
||||||
const TickType_t MIN_POLLING_INTERVAL = pdMS_TO_TICKS(1);
|
static uint8_t pb_buffer[PB_UART_BUFFER_SIZE];
|
||||||
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);
|
|
||||||
|
|
||||||
while (1)
|
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);
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
cached_client_fd = client_fd;
|
continue;
|
||||||
xSemaphoreGive(client_fd_mutex);
|
|
||||||
last_client_check = current_time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t available_len;
|
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++;
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
consecutive_empty_polls = 0;
|
size_t read_len = (available_len > BUF_SIZE) ? BUF_SIZE : available_len;
|
||||||
current_interval = MIN_POLLING_INTERVAL;
|
int bytes_read = uart_read_bytes(UART_NUM, data_buf, read_len, pdMS_TO_TICKS(5));
|
||||||
|
|
||||||
if (cached_client_fd <= 0)
|
if (bytes_read > 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)
|
|
||||||
{
|
{
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
|
while (offset < bytes_read)
|
||||||
while (offset < total_processed)
|
|
||||||
{
|
{
|
||||||
const size_t chunk_size =
|
size_t chunk_size = (bytes_read - offset > CHUNK_SIZE) ? CHUNK_SIZE : (bytes_read - offset);
|
||||||
(total_processed - offset > CHUNK_SIZE) ? CHUNK_SIZE : (total_processed - 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;
|
struct ws_message msg;
|
||||||
msg.type = WS_MSG_UART;
|
msg.type = WS_MSG_UART;
|
||||||
msg.content.uart.data = malloc(chunk_size);
|
msg.len = stream.bytes_written;
|
||||||
if (!msg.content.uart.data)
|
msg.data = malloc(msg.len);
|
||||||
|
|
||||||
|
if (!msg.data)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Failed to allocate memory for uart ws msg");
|
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);
|
memcpy(msg.data, pb_buffer, msg.len);
|
||||||
msg.content.uart.len = chunk_size;
|
|
||||||
|
|
||||||
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.data);
|
||||||
ESP_LOGW(TAG, "ws sender queue full, dropping %zu bytes", chunk_size);
|
|
||||||
free(msg.content.uart.data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += chunk_size;
|
offset += chunk_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available_len > 0)
|
|
||||||
{
|
|
||||||
vTaskDelay(MIN_POLLING_INTERVAL);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
vTaskDelay(current_interval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,20 +199,17 @@ static esp_err_t ws_handler(httpd_req_t* req)
|
|||||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||||
if (client_fd > 0)
|
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.");
|
ESP_LOGW(TAG, "Another client tried to connect, but a session is already active. Rejecting.");
|
||||||
xSemaphoreGive(client_fd_mutex);
|
xSemaphoreGive(client_fd_mutex);
|
||||||
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Another client is already connected");
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Another client is already connected");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No client is connected. Accept the new one.
|
|
||||||
int new_fd = httpd_req_to_sockfd(req);
|
int new_fd = httpd_req_to_sockfd(req);
|
||||||
ESP_LOGI(TAG, "Accepting new websocket connection: %d", new_fd);
|
ESP_LOGI(TAG, "Accepting new websocket connection: %d", new_fd);
|
||||||
client_fd = new_fd;
|
client_fd = new_fd;
|
||||||
xSemaphoreGive(client_fd_mutex);
|
xSemaphoreGive(client_fd_mutex);
|
||||||
|
|
||||||
// Reset queue and flush UART buffer for the new session
|
|
||||||
xQueueReset(ws_queue);
|
xQueueReset(ws_queue);
|
||||||
uart_flush_input(UART_NUM);
|
uart_flush_input(UART_NUM);
|
||||||
return ESP_OK;
|
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)
|
void register_ws_endpoint(httpd_handle_t server)
|
||||||
{
|
{
|
||||||
size_t baud_rate_len;
|
size_t baud_rate_len;
|
||||||
|
|
||||||
nconfig_get_str_len(UART_BAUD_RATE, &baud_rate_len);
|
nconfig_get_str_len(UART_BAUD_RATE, &baud_rate_len);
|
||||||
char buf[baud_rate_len];
|
char buf[baud_rate_len];
|
||||||
nconfig_read(UART_BAUD_RATE, 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,
|
.parity = UART_PARITY_DISABLE,
|
||||||
.stop_bits = UART_STOP_BITS_1,
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
.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_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_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_uri_t ws = {.uri = "/ws", .method = HTTP_GET, .handler = ws_handler, .user_ctx = NULL, .is_websocket = true};
|
||||||
httpd_register_uri_handler(server, &ws);
|
httpd_register_uri_handler(server, &ws);
|
||||||
|
|
||||||
client_fd_mutex = xSemaphoreCreateMutex();
|
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(uart_polling_task, "uart_polling_task", 1024 * 4, NULL, 8, NULL);
|
||||||
xTaskCreate(unified_ws_sender_task, "ws_sender_task", 1024 * 6, server, 9, 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;
|
struct ws_message msg;
|
||||||
msg.type = WS_MSG_STATUS;
|
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)
|
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(10)) != pdPASS)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "WS queue full, dropping status message");
|
ESP_LOGW(TAG, "WS queue full, dropping status message");
|
||||||
cJSON_Delete(data);
|
free(msg.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
740
page/package-lock.json
generated
740
page/package-lock.json
generated
@@ -12,14 +12,62 @@
|
|||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"chart.js": "^4.4.3"
|
"chart.js": "^4.4.3",
|
||||||
|
"protobufjs": "^7.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"protobufjs-cli": "^1.1.2",
|
||||||
"vite": "^7.0.4",
|
"vite": "^7.0.4",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-singlefile": "^2.0.1"
|
"vite-plugin-singlefile": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-string-parser": {
|
||||||
|
"version": "7.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||||
|
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/parser": {
|
||||||
|
"version": "7.28.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
|
||||||
|
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.28.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"parser": "bin/babel-parser.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/types": {
|
||||||
|
"version": "7.28.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||||
|
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
|
"@babel/helper-validator-identifier": "^7.27.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||||
@@ -436,6 +484,18 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jsdoc/salty": {
|
||||||
|
"version": "0.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
|
||||||
|
"integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@kurkle/color": {
|
"node_modules/@kurkle/color": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
@@ -451,6 +511,60 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@protobufjs/aspromise": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/base64": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/codegen": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/eventemitter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/fetch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.1",
|
||||||
|
"@protobufjs/inquire": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/float": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/inquire": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/path": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/pool": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/utf8": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.46.2",
|
"version": "4.46.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
|
||||||
@@ -717,6 +831,36 @@
|
|||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/linkify-it": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/markdown-it": {
|
||||||
|
"version": "14.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||||
|
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/linkify-it": "^5",
|
||||||
|
"@types/mdurl": "^2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/mdurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
||||||
|
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@xterm/addon-fit": {
|
"node_modules/@xterm/addon-fit": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.9.0.tgz",
|
||||||
@@ -730,6 +874,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-jsx": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
@@ -745,6 +910,24 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/argparse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/bluebird": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/bootstrap": {
|
"node_modules/bootstrap": {
|
||||||
"version": "5.3.7",
|
"version": "5.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
|
||||||
@@ -778,6 +961,15 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
@@ -790,6 +982,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/catharsis": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@@ -852,6 +1056,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-is": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.8",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||||
@@ -893,6 +1115,112 @@
|
|||||||
"@esbuild/win32-x64": "0.25.8"
|
"@esbuild/win32-x64": "0.25.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escodegen": {
|
||||||
|
"version": "1.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||||
|
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"esprima": "^4.0.1",
|
||||||
|
"estraverse": "^4.2.0",
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"optionator": "^0.8.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"escodegen": "bin/escodegen.js",
|
||||||
|
"esgenerate": "bin/esgenerate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"source-map": "~0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escodegen/node_modules/estraverse": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/espree": {
|
||||||
|
"version": "9.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||||
|
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.9.0",
|
||||||
|
"acorn-jsx": "^5.3.2",
|
||||||
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/estraverse": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esutils": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-levenshtein": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||||
@@ -933,6 +1261,12 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -947,6 +1281,26 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^5.0.1",
|
||||||
|
"once": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -962,6 +1316,23 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
@@ -971,6 +1342,44 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js2xmlparser": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"xmlcreate": "^2.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsdoc": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.20.15",
|
||||||
|
"@jsdoc/salty": "^0.2.1",
|
||||||
|
"@types/markdown-it": "^14.1.1",
|
||||||
|
"bluebird": "^3.7.2",
|
||||||
|
"catharsis": "^0.9.0",
|
||||||
|
"escape-string-regexp": "^2.0.0",
|
||||||
|
"js2xmlparser": "^4.0.2",
|
||||||
|
"klaw": "^3.0.0",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
|
"markdown-it-anchor": "^8.6.7",
|
||||||
|
"marked": "^4.0.10",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
|
"requizzle": "^0.2.3",
|
||||||
|
"strip-json-comments": "^3.1.0",
|
||||||
|
"underscore": "~1.13.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"jsdoc": "jsdoc.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsonfile": {
|
"node_modules/jsonfile": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
@@ -983,6 +1392,93 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/klaw": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.1.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/levn": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"prelude-ls": "~1.1.2",
|
||||||
|
"type-check": "~0.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/linkify-it": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"uc.micro": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
|
||||||
|
},
|
||||||
|
"node_modules/markdown-it": {
|
||||||
|
"version": "14.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^2.0.1",
|
||||||
|
"entities": "^4.4.0",
|
||||||
|
"linkify-it": "^5.0.0",
|
||||||
|
"mdurl": "^2.0.0",
|
||||||
|
"punycode.js": "^2.3.1",
|
||||||
|
"uc.micro": "^2.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"markdown-it": "bin/markdown-it.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/markdown-it-anchor": {
|
||||||
|
"version": "8.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz",
|
||||||
|
"integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/markdown-it": "*",
|
||||||
|
"markdown-it": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mdurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
@@ -1008,6 +1504,39 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimatch": {
|
||||||
|
"version": "5.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
|
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mkdirp": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -1032,6 +1561,32 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/optionator": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"deep-is": "~0.1.3",
|
||||||
|
"fast-levenshtein": "~2.0.6",
|
||||||
|
"levn": "~0.3.0",
|
||||||
|
"prelude-ls": "~1.1.2",
|
||||||
|
"type-check": "~0.3.2",
|
||||||
|
"word-wrap": "~1.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1078,6 +1633,84 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prelude-ls": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/protobufjs": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
"@protobufjs/codegen": "^2.0.4",
|
||||||
|
"@protobufjs/eventemitter": "^1.1.0",
|
||||||
|
"@protobufjs/fetch": "^1.1.0",
|
||||||
|
"@protobufjs/float": "^1.0.2",
|
||||||
|
"@protobufjs/inquire": "^1.1.0",
|
||||||
|
"@protobufjs/path": "^1.1.2",
|
||||||
|
"@protobufjs/pool": "^1.1.0",
|
||||||
|
"@protobufjs/utf8": "^1.1.0",
|
||||||
|
"@types/node": ">=13.7.0",
|
||||||
|
"long": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/protobufjs-cli": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-MqD10lqF+FMsOayFiNOdOGNlXc4iKDCf0ZQPkPR+gizYh9gqUeGTWulABUCdI+N67w5RfJ6xhgX4J8pa8qmMXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"escodegen": "^1.13.0",
|
||||||
|
"espree": "^9.0.0",
|
||||||
|
"estraverse": "^5.1.0",
|
||||||
|
"glob": "^8.0.0",
|
||||||
|
"jsdoc": "^4.0.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"semver": "^7.1.2",
|
||||||
|
"tmp": "^0.2.1",
|
||||||
|
"uglify-js": "^3.7.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pbjs": "bin/pbjs",
|
||||||
|
"pbts": "bin/pbts"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"protobufjs": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/punycode.js": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/requizzle": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.46.2",
|
"version": "4.46.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
|
||||||
@@ -1117,6 +1750,28 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -1126,6 +1781,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strip-json-comments": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
@@ -1154,6 +1821,15 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tmp": {
|
||||||
|
"version": "0.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||||
|
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@@ -1166,6 +1842,47 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/type-check": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"prelude-ls": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uc.micro": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/uglify-js": {
|
||||||
|
"version": "3.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||||
|
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"uglifyjs": "bin/uglifyjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/underscore": {
|
||||||
|
"version": "1.13.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
||||||
|
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
|
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="
|
||||||
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
@@ -1278,6 +1995,27 @@
|
|||||||
"rollup": "^4.44.1",
|
"rollup": "^4.44.1",
|
||||||
"vite": "^5.4.11 || ^6.0.0 || ^7.0.0"
|
"vite": "^5.4.11 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/word-wrap": {
|
||||||
|
"version": "1.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/xmlcreate": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,23 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "npm run build:proto && vite",
|
||||||
"build": "vite build",
|
"build": "npm run build:proto && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"build:proto": "pbjs -t static-module -w es6 -o src/proto.js ../proto/status.proto"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"protobufjs-cli": "^1.1.2",
|
||||||
"vite": "^7.0.4",
|
"vite": "^7.0.4",
|
||||||
"vite-plugin-singlefile": "^2.0.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-compression": "^0.5.1"
|
"vite-plugin-singlefile": "^2.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xterm/addon-fit": "^0.9.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"@xterm/addon-fit": "^0.9.0",
|
"chart.js": "^4.4.3",
|
||||||
"chart.js": "^4.4.3"
|
"protobufjs": "^7.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
page/src/main.js
107
page/src/main.js
@@ -10,9 +10,10 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
|||||||
import 'bootstrap-icons/font/bootstrap-icons.css';
|
import 'bootstrap-icons/font/bootstrap-icons.css';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
// --- Module Imports ---
|
// --- Module Imports -- -
|
||||||
import {initWebSocket} from './websocket.js';
|
import { StatusMessage } from './proto.js';
|
||||||
import {setupTerminal, term} from './terminal.js';
|
import { initWebSocket } from './websocket.js';
|
||||||
|
import { setupTerminal, term } from './terminal.js';
|
||||||
import {
|
import {
|
||||||
applyTheme,
|
applyTheme,
|
||||||
initUI,
|
initUI,
|
||||||
@@ -21,14 +22,13 @@ import {
|
|||||||
updateWebsocketStatus,
|
updateWebsocketStatus,
|
||||||
updateWifiStatusUI
|
updateWifiStatusUI
|
||||||
} from './ui.js';
|
} from './ui.js';
|
||||||
import {setupEventListeners} from './events.js';
|
import { setupEventListeners } from './events.js';
|
||||||
|
|
||||||
|
// --- Globals ---
|
||||||
|
// StatusMessage is imported directly from the generated proto.js file.
|
||||||
|
|
||||||
// --- WebSocket Event Handlers ---
|
// --- WebSocket Event Handlers ---
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback function for when the WebSocket connection is successfully opened.
|
|
||||||
* Updates the UI to show an 'Online' status and fetches the initial control status.
|
|
||||||
*/
|
|
||||||
function onWsOpen() {
|
function onWsOpen() {
|
||||||
updateWebsocketStatus(true);
|
updateWebsocketStatus(true);
|
||||||
if (term) {
|
if (term) {
|
||||||
@@ -36,85 +36,86 @@ function onWsOpen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback function for when the WebSocket connection is closed.
|
|
||||||
* Updates the UI to show an 'Offline' status and attempts to reconnect after a delay.
|
|
||||||
*/
|
|
||||||
function onWsClose() {
|
function onWsClose() {
|
||||||
updateWebsocketStatus(false);
|
updateWebsocketStatus(false);
|
||||||
if (term) {
|
if (term) {
|
||||||
term.write('\r\n\x1b[31mConnection closed. Reconnecting...\x1b[0m\r\n');
|
term.write('\r\n\x1b[31mConnection closed. Reconnecting...\x1b[0m\r\n');
|
||||||
}
|
}
|
||||||
// Attempt to re-establish the connection after 2 seconds
|
|
||||||
setTimeout(connect, 2000);
|
setTimeout(connect, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function for when a message is received from the WebSocket server.
|
* Callback for when a message is received from the WebSocket server.
|
||||||
* It handles both JSON messages (for sensor and status updates) and binary data (for the terminal).
|
* This version includes extensive logging for debugging purposes.
|
||||||
* @param {MessageEvent} event - The WebSocket message event.
|
* @param {MessageEvent} event - The WebSocket message event.
|
||||||
*/
|
*/
|
||||||
function onWsMessage(event) {
|
function onWsMessage(event) {
|
||||||
if (typeof event.data === 'string') {
|
// Log any incoming message to the console for debugging.
|
||||||
try {
|
|
||||||
const message = JSON.parse(event.data);
|
if (!(event.data instanceof ArrayBuffer)) {
|
||||||
if (message.type === 'sensor_data') {
|
console.warn('Message is not an ArrayBuffer, skipping protobuf decoding.');
|
||||||
updateSensorUI(message);
|
return;
|
||||||
} else if (message.type === 'wifi_status') {
|
}
|
||||||
updateWifiStatusUI(message);
|
|
||||||
|
const buffer = new Uint8Array(event.data);
|
||||||
|
try {
|
||||||
|
const decodedMessage = StatusMessage.decode(buffer);
|
||||||
|
const payloadType = decodedMessage.payload;
|
||||||
|
|
||||||
|
switch (payloadType) {
|
||||||
|
case 'sensorData': {
|
||||||
|
const sensorData = decodedMessage.sensorData;
|
||||||
|
if (sensorData) {
|
||||||
|
const sensorPayload = {
|
||||||
|
...sensorData,
|
||||||
|
USB: sensorData.usb,
|
||||||
|
MAIN: sensorData.main,
|
||||||
|
VIN: sensorData.vin,
|
||||||
|
};
|
||||||
|
// Log the exact data being sent to the UI function
|
||||||
|
updateSensorUI(sensorPayload);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// Ignore non-JSON string messages
|
case 'wifiStatus':
|
||||||
|
updateWifiStatusUI(decodedMessage.wifiStatus);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'uartData':
|
||||||
|
if (term && decodedMessage.uartData && decodedMessage.uartData.data) {
|
||||||
|
term.write(decodedMessage.uartData.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('Received message with unknown or empty payload type:', payloadType);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (term && event.data instanceof ArrayBuffer) {
|
} catch (e) {
|
||||||
// Write raw UART data to the terminal
|
console.error('Error decoding protobuf message:', e);
|
||||||
const data = new Uint8Array(event.data);
|
|
||||||
term.write(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Application Initialization ---
|
// --- Application Initialization ---
|
||||||
|
|
||||||
/**
|
|
||||||
* Establishes the connection-related parts of the application.
|
|
||||||
* Fetches initial status and initializes WebSocket.
|
|
||||||
*/
|
|
||||||
function connect() {
|
function connect() {
|
||||||
// Fetch initial status on page load or reconnect
|
|
||||||
updateControlStatus();
|
updateControlStatus();
|
||||||
|
initWebSocket({ onOpen: onWsOpen, onClose: onWsClose, onMessage: onWsMessage });
|
||||||
// Establish the WebSocket connection with the defined handlers
|
|
||||||
initWebSocket({
|
|
||||||
onOpen: onWsOpen,
|
|
||||||
onClose: onWsClose,
|
|
||||||
onMessage: onWsMessage
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the entire application.
|
|
||||||
* This function sets up the UI, theme, terminal, and event listeners.
|
|
||||||
* It should only be called once when the DOM is loaded.
|
|
||||||
*/
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
// Initialize basic UI components
|
|
||||||
initUI();
|
initUI();
|
||||||
|
|
||||||
// Set up the interactive components first
|
|
||||||
setupTerminal();
|
setupTerminal();
|
||||||
|
|
||||||
// Apply the saved theme or detect the user's preferred theme.
|
|
||||||
const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
applyTheme(savedTheme);
|
applyTheme(savedTheme);
|
||||||
|
|
||||||
// Attach all event listeners to the DOM elements
|
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
|
|
||||||
// Start the connection process
|
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Start Application ---
|
// --- Start Application ---
|
||||||
|
|
||||||
// Wait for the DOM to be fully loaded before initializing the application.
|
|
||||||
document.addEventListener('DOMContentLoaded', initialize);
|
document.addEventListener('DOMContentLoaded', initialize);
|
||||||
|
|||||||
38
proto/status.proto
Normal file
38
proto/status.proto
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
// Represents data for a single sensor channel
|
||||||
|
message SensorChannelData {
|
||||||
|
float voltage = 1;
|
||||||
|
float current = 2;
|
||||||
|
float power = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains data for all sensor channels and system info
|
||||||
|
message SensorData {
|
||||||
|
SensorChannelData usb = 1;
|
||||||
|
SensorChannelData main = 2;
|
||||||
|
SensorChannelData vin = 3;
|
||||||
|
uint32 timestamp = 4;
|
||||||
|
uint32 uptime_sec = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains WiFi connection status
|
||||||
|
message WifiStatus {
|
||||||
|
bool connected = 1;
|
||||||
|
string ssid = 2;
|
||||||
|
int32 rssi = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains raw UART data
|
||||||
|
message UartData {
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-level message for all websocket communication
|
||||||
|
message StatusMessage {
|
||||||
|
oneof payload {
|
||||||
|
SensorData sensor_data = 1;
|
||||||
|
WifiStatus wifi_status = 2;
|
||||||
|
UartData uart_data = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user