- websocket client on ESP32

- telemetry protocol builder
- server-side message handling
- database migrations
- telemetry persistence
This commit is contained in:
2026-04-14 16:09:18 +03:00
parent 977227296e
commit 4100931deb
57 changed files with 4960 additions and 63 deletions

View File

@@ -1,7 +1,8 @@
idf_component_register(
SRCS "main.cpp" "protocol.cpp" "wifi_manager.cpp" "system_init.cpp" "time_sync.cpp" "adc_reader.cpp" "ringbuf.cpp" "sampler_task.cpp" "sender_task.cpp"
"ws.cpp"
INCLUDE_DIRS "."
REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer esp_adc
REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer esp_adc esp_websocket_client
)
# добавляем кастомный Kconfig
set(COMPONENT_KCONFIG "Kconfig")

View File

@@ -0,0 +1,17 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/esp_websocket_client: '*'

View File

@@ -19,8 +19,59 @@ static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
return pos + written;
}
int build_telemetry(
char* buf,
size_t buf_size,
uint32_t msg_id,
int64_t ts,
uint32_t device_id,
const char* metric,
const char* source,
const char* unit,
const int* values,
size_t count
) {
int pos = 0;
// --- header ---
pos = append(buf, buf_size, pos,
"{\"v\":1,\"t\":\"t\",\"id\":%lu,\"ts\":%lld,\"d\":%lu,\"p\":{",
(unsigned long)msg_id,
(long long)ts,
(unsigned long)device_id
);
if (pos < 0) return -1;
// --- payload meta ---
pos = append(buf, buf_size, pos,
"\"m\":\"%s\",\"s\":\"%s\",\"u\":\"%s\",\"v\":[",
metric,
source,
unit
);
if (pos < 0) return -1;
// --- values ---
for (size_t i = 0; i < count; i++) {
pos = append(buf, buf_size, pos,
"[%u,%d]%s",
(unsigned)i, // delta time (пока просто индекс)
values[i],
(i < count - 1) ? "," : ""
);
if (pos < 0) return -1;
}
// --- close ---
pos = append(buf, buf_size, pos, "]}}");
if (pos < 0) return -1;
return pos;
}
__attribute__((deprecated))
// --- TELEMETRY (single measurement) ---
int build_telemetry_single(
int build_telemetry_single(
char* buf,
size_t buf_size,
const char* id,
@@ -76,6 +127,7 @@ int build_telemetry_single(
return pos;
}
__attribute__((deprecated))
// --- EVENT ---
int build_event(
char* buf,

View File

@@ -7,6 +7,19 @@
extern "C" {
#endif
int build_telemetry(
char* buf,
size_t buf_size,
uint32_t msg_id,
int64_t ts,
uint32_t device_id,
const char* metric,
const char* source,
const char* unit,
const int* values,
size_t count
);
int build_telemetry_single(
char* buf,
size_t buf_size,

View File

@@ -1,9 +1,11 @@
#include "sender_task.h"
#include "sampler_task.h"
#include "ringbuf.h"
#include "protocol.h"
#include <stdio.h>
#include <time.h>
#include "ws.h"
extern "C" {
#include "freertos/FreeRTOS.h"
@@ -18,21 +20,35 @@ static void sender_task(void *arg) {
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 Гц
char buf[512];
ringbuf_t *rb = sampler_get_buffer();
ringbuf_copy(rb, data);
time_t now = time(NULL);
// формируем JSON
printf("{\"v\":1,\"t\":\"t\",\"id\":%lu,\"ts\":%lld,\"d\":1,\"p\":{",
(unsigned long)msg_id++, (long long)now);
printf("\"m\":\"v\",\"s\":\"adc35\",\"u\":\"raw\",\"v\":[");
int len = build_telemetry(
buf,
sizeof(buf),
msg_id++,
now,
1, // device_id (пока захардкожен)
"v", // metric
"adc35", // source
"raw", // unit
data,
RINGBUF_SIZE
);
for (int i = 0; i < RINGBUF_SIZE; i++) {
printf("[%d,%d]%s", i, data[i], (i < RINGBUF_SIZE - 1) ? "," : "");
if (len > 0) {
if (ws_is_connected()) {
ws_send(buf);
} else {
printf("%s\n", buf); // fallback
}
} else {
printf("build_telemetry failed\n");
}
printf("]}}\n");
}
}

View File

@@ -6,6 +6,7 @@
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "ws.h"
static const char* TAG = "system";
@@ -17,6 +18,7 @@ void system_init()
init_time(); // Установка времени
ws_go();
}
void system_finalize()

82
esp32/main/ws.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include "ws.h"
#include "esp_websocket_client.h"
#include "esp_log.h"
#include <string.h>
#include "sdkconfig.h"
static const char* TAG = "WS";
static esp_websocket_client_handle_t client = nullptr;
static bool connected = false;
static void ws_event_handler(void *handler_args,
esp_event_base_t base,
int32_t event_id,
void *event_data)
{
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
connected = true;
ESP_LOGI(TAG, "Connected");
break;
case WEBSOCKET_EVENT_DISCONNECTED:
connected = false;
ESP_LOGI(TAG, "Disconnected");
break;
case WEBSOCKET_EVENT_ERROR:
connected = false;
ESP_LOGE(TAG, "Error");
break;
case WEBSOCKET_EVENT_DATA: {
auto *data = (esp_websocket_event_data_t *)event_data;
ESP_LOGI(TAG, "Recv: %.*s", data->data_len, (char*)data->data_ptr);
break;
}
}
}
void ws_init(const char* uri)
{
esp_websocket_client_config_t config = {};
config.uri = uri;
config.reconnect_timeout_ms = 5000;
config.network_timeout_ms = 5000;
client = esp_websocket_client_init(&config);
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL);
}
void ws_start()
{
if (client) {
esp_websocket_client_start(client);
}
}
bool ws_is_connected()
{
return connected;
}
void ws_send(const char* data)
{
if (connected && client) {
esp_websocket_client_send_text(client, data, strlen(data), portMAX_DELAY);
}
}
void ws_go()
{
char uri[128];
snprintf(uri, sizeof(uri),
"ws://%s:%d/ws",
CONFIG_SERVER_HOST,
CONFIG_SERVER_PORT);
ws_init(uri);
ws_start();
}

15
esp32/main/ws.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void ws_init(const char* uri);
void ws_go();
void ws_start();
bool ws_is_connected();
void ws_send(const char* data);
#ifdef __cplusplus
}
#endif