HB, OTA etc

This commit is contained in:
2026-04-22 20:11:55 +03:00
parent 4100931deb
commit cb1014c950
76 changed files with 3157 additions and 232 deletions

View File

@@ -1,8 +1,30 @@
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"
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"
"gpio_init.cpp"
"ds18b20.cpp"
"heartbeat_task.cpp"
"fw_command.cpp"
"json_utils.cpp"
INCLUDE_DIRS "."
REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer esp_adc esp_websocket_client
REQUIRES nvs_flash
esp_wifi
esp_netif
freertos
log
esp_timer
esp_adc
esp_websocket_client
driver
esp_driver_gpio
)
# добавляем кастомный Kconfig
set(COMPONENT_KCONFIG "Kconfig")

85
esp32/main/ds18b20.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "ds18b20.h"
extern "C" {
#include "onewire_bus.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
}
static const char *TAG = "ds18b20";
static onewire_bus_handle_t bus;
uint8_t ds_crc8(const uint8_t *data, int len)
{
uint8_t crc = 0;
for (int i = 0; i < len; i++) {
uint8_t inbyte = data[i];
for (int j = 0; j < 8; j++) {
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
void ds18b20_init(gpio_num_t pin)
{
onewire_bus_config_t bus_config = {};
bus_config.bus_gpio_num = pin;
onewire_bus_rmt_config_t rmt_config = {};
rmt_config.max_rx_bytes = 10;
ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config, &rmt_config, &bus));
ESP_LOGI(TAG, "OneWire bus initialized");
}
float ds18b20_read()
{
uint8_t data[9];
uint8_t buf[4] = {0x4E, 0, 0, 0x3F}; // 10-bit (0x3F)
// reset
ESP_ERROR_CHECK(onewire_bus_reset(bus));
// SKIP ROM (один датчик)
uint8_t cmd = 0xCC;
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
//ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, buf, 4));
// CONVERT T
cmd = 0x44;
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
vTaskDelay(pdMS_TO_TICKS(750));
// reset снова
ESP_ERROR_CHECK(onewire_bus_reset(bus));
// SKIP ROM
cmd = 0xCC;
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
// READ SCRATCHPAD
cmd = 0xBE;
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
ESP_ERROR_CHECK(onewire_bus_read_bytes(bus, data, 9));
ESP_LOGI(TAG,
"RAW: %02X %02X %02X %02X %02X %02X %02X %02X | CRC %02X",
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7], data[8]);
uint8_t crc = ds_crc8(data, 8);
if (crc != data[8]) {
ESP_LOGW(TAG, "CRC error");
}
int16_t raw = (data[1] << 8) | data[0];
return raw / 16.0f;
}

6
esp32/main/ds18b20.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include "driver/gpio.h"
void ds18b20_init(gpio_num_t pin);
float ds18b20_read();

18
esp32/main/fw_command.cpp Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by eugene on 22.04.2026.
//
#include "fw_command.h"
#include "json_utils.h"
#include <cstring>
bool parse_fw_command(const char *msg, fw_cmd_t *out) {
if (!strstr(msg, "\"t\":\"c\"")) return false;
if (!strstr(msg, "\"t\":\"fw\"")) return false;
if (!json_get_string(msg, "\"u\"", out->url, sizeof(out->url))) return false;
if (!json_get_string(msg, "\"s\"", out->sha256, sizeof(out->sha256))) return false;
out->is_fw = true;
return true;
}

9
esp32/main/fw_command.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
typedef struct {
bool is_fw;
char url[256];
char sha256[65]; // 64 + \0
} fw_cmd_t;
bool parse_fw_command(const char *msg, fw_cmd_t *out);

20
esp32/main/gpio_init.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "gpio_init.h"
#include "hal/gpio_types.h"
#include "driver/gpio.h"
#define PIN_OUT GPIO_NUM_22
void init_gpio()
{
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << PIN_OUT),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
gpio_set_level(PIN_OUT, 1); // HIGH ≈ 3.3В
}

3
esp32/main/gpio_init.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void init_gpio();

View File

@@ -0,0 +1,31 @@
#include "heartbeat_task.h"
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "protocol.h"
#include <time.h>
}
static const char *TAG = "heartbeat";
static void heartbeat_task(void *arg)
{
while (1) {
if (protocol_is_connected()) {
time_t now;
time(&now);
protocol_send_event_hb((uint32_t)now);
}
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 сек
}
}
void heartbeat_task_start()
{
xTaskCreate(heartbeat_task, "heartbeat", 4096, NULL, 5, NULL);
}

View File

@@ -0,0 +1,3 @@
#pragma once
void heartbeat_task_start(void);

View File

@@ -15,3 +15,4 @@ dependencies:
# # All dependencies of `main` are public by default.
# public: true
espressif/esp_websocket_client: '*'
espressif/onewire_bus: '*'

30
esp32/main/json_utils.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include <cstring>
#include <cstddef>
#include "json_utils.h"
bool json_get_string(const char *json, const char *key, char *out, size_t out_size) {
const char *k = strstr(json, key);
if (!k) return false;
const char *start = strchr(k, ':');
if (!start) return false;
start++;
// найти начало строки (первую кавычку)
while (*start && *start != '\"') start++;
if (!*start) return false;
start++; // после "
const char *end = strchr(start, '\"');
if (!end) return false;
size_t len = end - start;
if (len >= out_size) return false;
memcpy(out, start, len);
out[len] = '\0';
return true;
}

4
esp32/main/json_utils.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
#include <cstddef>
bool json_get_string(const char *json, const char *key, char *out, size_t out_size);

View File

@@ -1,5 +1,6 @@
#include "esp_log.h"
#include "system_init.h"
#include "heartbeat_task.h"
#include "freertos/FreeRTOS.h"
#include "freertos/projdefs.h"
#include "freertos/task.h"
@@ -13,8 +14,10 @@ extern "C" void app_main(void)
{
system_init();
sampler_task_start();
sender_task_start();
//sampler_task_start();
//sender_task_start();
heartbeat_task_start();
system_finalize();
}

View File

@@ -1,10 +1,15 @@
#include "protocol.h"
#include "ws.h"
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#define PROTOCOL_VERSION 1
static inline int append(char* buf, size_t size, int pos, const char* fmt, ...) {
static uint32_t msg_id = 1;
static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
{
if ((size_t)pos >= size) return -1;
va_list args;
@@ -12,7 +17,8 @@ static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
int written = vsnprintf(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0 || (size_t)(pos + written) >= size) {
if (written < 0 || (size_t)(pos + written) >= size)
{
return -1;
}
@@ -22,7 +28,6 @@ static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
int build_telemetry(
char* buf,
size_t buf_size,
uint32_t msg_id,
int64_t ts,
uint32_t device_id,
const char* metric,
@@ -30,34 +35,36 @@ int build_telemetry(
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
"{\"v\":1,\"t\":\"t\",\"id\":%lu,\"ts\":%lld,\"d\":%lu,\"p\":{",
(unsigned long)protocol_next_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
"\"m\":\"%s\",\"s\":\"%s\",\"u\":\"%s\",\"v\":[",
metric,
source,
unit
);
if (pos < 0) return -1;
// --- values ---
for (size_t i = 0; i < count; i++) {
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) ? "," : ""
"[%u,%d]%s",
values[i*2 + 0], // delta time
values[i*2 + 1],
(i < count - 1) ? "," : ""
);
if (pos < 0) return -1;
}
@@ -71,7 +78,7 @@ int build_telemetry(
__attribute__((deprecated))
// --- TELEMETRY (single measurement) ---
int build_telemetry_single(
int build_telemetry_single(
char* buf,
size_t buf_size,
const char* id,
@@ -82,40 +89,44 @@ __attribute__((deprecated))
const char* unit,
const char* source,
int64_t m_ts_ms
) {
)
{
int pos = 0;
// header
pos = append(buf, buf_size, pos,
"{\"v\":%d,\"id\":\"%s\",\"type\":\"telemetry\",\"ts\":%lld,"
"\"deviceId\":\"%s\",\"payload\":{\"measurements\":[",
PROTOCOL_VERSION,
id,
(long long)ts_ms,
device_id
"{\"v\":%d,\"id\":\"%s\",\"type\":\"telemetry\",\"ts\":%lld,"
"\"deviceId\":\"%s\",\"payload\":{\"measurements\":[",
PROTOCOL_VERSION,
id,
(long long)ts_ms,
device_id
);
if (pos < 0) return -1;
// measurement start
pos = append(buf, buf_size, pos,
"{\"metric\":\"%s\",\"value\":%.3f",
metric,
value
"{\"metric\":\"%s\",\"value\":%.3f",
metric,
value
);
if (pos < 0) return -1;
// optional
if (unit) {
if (unit)
{
pos = append(buf, buf_size, pos, ",\"unit\":\"%s\"", unit);
if (pos < 0) return -1;
}
if (source) {
if (source)
{
pos = append(buf, buf_size, pos, ",\"source\":\"%s\"", source);
if (pos < 0) return -1;
}
if (m_ts_ms > 0) {
if (m_ts_ms > 0)
{
pos = append(buf, buf_size, pos, ",\"ts\":%lld", (long long)m_ts_ms);
if (pos < 0) return -1;
}
@@ -138,17 +149,18 @@ int build_event(
const char* name,
const char* severity,
const char* message
) {
)
{
int pos = 0;
// header
pos = append(buf, buf_size, pos,
"{\"v\":%d,\"id\":\"%s\",\"type\":\"event\",\"ts\":%lld,"
"\"deviceId\":\"%s\",\"payload\":{",
PROTOCOL_VERSION,
id,
(long long)ts_ms,
device_id
"{\"v\":%d,\"id\":\"%s\",\"type\":\"event\",\"ts\":%lld,"
"\"deviceId\":\"%s\",\"payload\":{",
PROTOCOL_VERSION,
id,
(long long)ts_ms,
device_id
);
if (pos < 0) return -1;
@@ -157,12 +169,14 @@ int build_event(
if (pos < 0) return -1;
// optional
if (severity) {
if (severity)
{
pos = append(buf, buf_size, pos, ",\"severity\":\"%s\"", severity);
if (pos < 0) return -1;
}
if (message) {
if (message)
{
pos = append(buf, buf_size, pos, ",\"message\":\"%s\"", message);
if (pos < 0) return -1;
}
@@ -172,4 +186,30 @@ int build_event(
if (pos < 0) return -1;
return pos;
}
uint32_t protocol_next_id()
{
return msg_id++;
};
void protocol_send_event_hb(int64_t ts)
{
// формируешь JSON строго по контракту
// пример:
char buf[128];
uint32_t id = protocol_next_id();
snprintf(buf, sizeof(buf),
"{\"v\":1,\"id\":%" PRIu32 ",\"t\":\"e\",\"ts\":%" PRIu64 ",\"d\":%u,\"p\":{\"type\":\"hb\"}}",
id, ts, CONFIG_DEVICE_ID);
ws_send(buf);
}
bool protocol_is_connected()
{
return ws_is_connected();
}

View File

@@ -2,6 +2,8 @@
#include <stdint.h>
#include <stddef.h>
#include "ringbuf.h"
#ifdef __cplusplus
extern "C" {
@@ -10,7 +12,6 @@ extern "C" {
int build_telemetry(
char* buf,
size_t buf_size,
uint32_t msg_id,
int64_t ts,
uint32_t device_id,
const char* metric,
@@ -44,6 +45,12 @@ int build_event(
const char* message // optional
);
uint32_t protocol_next_id();
void protocol_send_event_hb(int64_t ts);
bool protocol_is_connected();
#ifdef __cplusplus
}
#endif

View File

@@ -3,16 +3,18 @@
void ringbuf_init(ringbuf_t *rb) {
rb->head = 0;
for (int i = 0; i < RINGBUF_SIZE; i++) {
rb->values[i] = 0;
rb->values[i].ts_ms = 0;
rb->values[i].value = 0;
}
}
void ringbuf_push(ringbuf_t *rb, int v) {
rb->values[rb->head] = v;
void ringbuf_push(ringbuf_t *rb, uint32_t ts_ms, int v) {
rb->values[rb->head].ts_ms = ts_ms;
rb->values[rb->head].value = v;
rb->head = (rb->head + 1) % RINGBUF_SIZE;
}
void ringbuf_copy(const ringbuf_t *rb, int *out) {
void ringbuf_copy(const ringbuf_t *rb, sample_t *out) {
int idx = rb->head;
for (int i = 0; i < RINGBUF_SIZE; i++) {

View File

@@ -5,7 +5,12 @@
#define RINGBUF_SIZE 10
typedef struct {
int values[RINGBUF_SIZE];
uint32_t ts_ms;
int value;
} sample_t;
typedef struct {
sample_t values[RINGBUF_SIZE];
int head;
} ringbuf_t;
@@ -13,7 +18,6 @@ typedef struct {
void ringbuf_init(ringbuf_t *rb);
// Добавить значение
void ringbuf_push(ringbuf_t *rb, int v);
void ringbuf_push(ringbuf_t *rb, uint32_t ts_ms, int v);
// Скопировать данные (в порядке времени)
void ringbuf_copy(const ringbuf_t *rb, int *out);
void ringbuf_copy(const ringbuf_t *rb, sample_t *out);

View File

@@ -1,37 +1,43 @@
#include "sampler_task.h"
#include "adc_reader.h"
#include "ringbuf.h"
#include "ds18b20.h" // добавим
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
}
// буфер объявим здесь (глобальный для простоты)
// буфер
static ringbuf_t rb;
// дать доступ другим модулям (sender потом возьмёт)
ringbuf_t* sampler_get_buffer() {
return &rb;
}
static void sampler_task(void *arg) {
while (1) {
int val = adc_reader_read();
ringbuf_push(&rb, val);
const TickType_t period = pdMS_TO_TICKS(6000);
TickType_t last_wake = xTaskGetTickCount();
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Гц
while (1) {
int64_t now_ms = esp_timer_get_time() / 1000;
float temp = ds18b20_read();
ringbuf_push(&rb, now_ms, (int)(temp * 100));
vTaskDelayUntil(&last_wake, period);// 6 секунд
}
}
void sampler_task_start() {
ringbuf_init(&rb);
adc_reader_init();
ds18b20_init(GPIO_NUM_27); // новый драйвер
xTaskCreate(
sampler_task,
"sampler",
2048,
4096,
NULL,
5,
NULL

View File

@@ -5,54 +5,88 @@
#include <stdio.h>
#include <time.h>
#include "ws.h"
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
}
static uint32_t msg_id = 1;
static uint32_t last_send_ms = 0;
static int64_t last_send_ts = 0;
static void sender_task(void *arg) {
int data[RINGBUF_SIZE];
static void sender_task(void* arg)
{
ringbuf_t* rb = sampler_get_buffer();
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 Гц
last_send_ts = time(NULL);
last_send_ms = esp_timer_get_time() / 1000;
while (1)
{
vTaskDelay(pdMS_TO_TICKS(50000)); // 1 мин^-1
char buf[512];
uint32_t now_ms = esp_timer_get_time() / 1000;
time_t now_ts = time(NULL);
ringbuf_t *rb = sampler_get_buffer();
ringbuf_copy(rb, data);
sample_t tmp[RINGBUF_SIZE];
ringbuf_copy(rb, tmp);
time_t now = time(NULL);
int out_values[RINGBUF_SIZE * 2];
int count = 0;
for (int i = 0; i < RINGBUF_SIZE; i++)
{
if (tmp[i].ts_ms >= last_send_ms)
{
uint32_t delta = tmp[i].ts_ms - last_send_ms;
// кладём delta + value
out_values[count * 2 + 0] = delta;
out_values[count * 2 + 1] = tmp[i].value;
count++;
}
}
int len = build_telemetry(
buf,
sizeof(buf),
msg_id++,
now,
1, // device_id (пока захардкожен)
"v", // metric
"adc35", // source
"raw", // unit
data,
RINGBUF_SIZE
last_send_ts,
1, // device_id (пока захардкожен)
"t", // metric
"ds18b20", // source
"c_x100", // unit
out_values,
count
);
if (len > 0) {
if (ws_is_connected()) {
if (len > 0)
{
if (ws_is_connected())
{
ws_send(buf);
} else {
printf("%s\n", buf); // fallback
}
} else {
else
{
printf("%s\n", buf); // fallback
}
}
else
{
printf("build_telemetry failed\n");
}
last_send_ms = now_ms;
last_send_ts = now_ts;
}
}
void sender_task_start() {
void sender_task_start()
{
xTaskCreate(
sender_task,
"sender",
@@ -61,4 +95,4 @@ void sender_task_start() {
5,
NULL
);
}
}

View File

@@ -7,6 +7,7 @@
#include "esp_event.h"
#include "esp_netif.h"
#include "ws.h"
#include "gpio_init.h"
static const char* TAG = "system";
@@ -14,6 +15,8 @@ void system_init()
{
ESP_LOGI("SYSTEM", "Initializing system...");
init_gpio();
init_wifi(CONFIG_WIFI_SSID, CONFIG_WIFI_PASS); // Запуск WiFI
init_time(); // Установка времени

View File

@@ -3,19 +3,20 @@
#include "esp_log.h"
#include <string.h>
#include "sdkconfig.h"
#include "fw_command.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,
static void ws_event_handler(void* handler_args,
esp_event_base_t base,
int32_t event_id,
void *event_data)
void* event_data)
{
switch (event_id) {
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
connected = true;
ESP_LOGI(TAG, "Connected");
@@ -31,11 +32,36 @@ static void ws_event_handler(void *handler_args,
ESP_LOGE(TAG, "Error");
break;
case WEBSOCKET_EVENT_DATA: {
auto *data = (esp_websocket_event_data_t *)event_data;
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);
// ⚠️ делаем null-terminated копию
char buf[512]; // подбери размер под свой максимум
int len = data->data_len;
if (len >= sizeof(buf)) {
ESP_LOGW(TAG, "Message too large");
break;
}
memcpy(buf, data->data_ptr, len);
buf[len] = '\0';
fw_cmd_t cmd {};
if (parse_fw_command(buf, &cmd)) {
ESP_LOGI(TAG, "FW command received");
ESP_LOGI(TAG, "URL: %s", cmd.url);
ESP_LOGI(TAG, "SHA256: %s", cmd.sha256);
// пока просто лог, без OTA
}
break;
}
}
}
}
@@ -52,7 +78,8 @@ void ws_init(const char* uri)
void ws_start()
{
if (client) {
if (client)
{
esp_websocket_client_start(client);
}
}
@@ -64,7 +91,8 @@ bool ws_is_connected()
void ws_send(const char* data)
{
if (connected && client) {
if (connected && client)
{
esp_websocket_client_send_text(client, data, strlen(data), portMAX_DELAY);
}
}
@@ -79,4 +107,4 @@ void ws_go()
CONFIG_SERVER_PORT);
ws_init(uri);
ws_start();
}
}