diff --git a/contract/ingest/common.schema.json b/contract/ingest/common.schema.json index 5d4c005..da4d607 100644 --- a/contract/ingest/common.schema.json +++ b/contract/ingest/common.schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "contract/ingest/common.schema.json", "type": "object", - "required": ["v", "id", "type", "ts", "deviceId", "payload"], + "required": ["v", "t", "id", "ts", "d", "p"], "additionalProperties": false, "properties": { "v": { @@ -10,28 +10,68 @@ "const": 1 }, "id": { - "type": "string", - "minLength": 1 + "type": "integer", + "minimum": 1, + "maximum": 4294967295, + "description": "Autoincrement message id. Unique per session" }, - "type": { + "t": { "type": "string", - "enum": ["telemetry", "event"] + "enum": ["t", "e", "c"], + "description": "Message type (elemetry, vent, ommand)" }, "ts": { "type": "integer", - "minimum": 1600000000000, - "description": "Unix time in milliseconds (UTC)" + "minimum": 1600000000, + "description": "Unix time in seconds (UTC)" }, - "deviceId": { - "type": "string", - "minLength": 1 + "d": { + "type": "integer", + "minimum": 1, + "description": "Device ID. Unique per scope" }, - "payload": { - "type": "object" - }, - "meta": { + "p": { "type": "object", - "additionalProperties": true + "description": "User data. Type-specific." } - } + }, + + "allOf": [ + { + "if": { + "required": ["t"], + "properties": { "t": { "const": "t" } } + }, + "then": { + "required": ["p"], + "properties": { + "p": { "$ref": "telemetry.schema.json" } + } + } + }, + { + "if": { + "required": ["t"], + "properties": { "t": { "const": "e" } } + }, + "then": { + "required": ["p"], + "properties": { + "p": { "$ref": "event.schema.json" } + } + } + }, + { + "if": { + "required": ["t"], + "properties": { "t": { "const": "c" } } + }, + "then": { + "required": ["p"], + "properties": { + "p": { "$ref": "command.schema.json" } + } + } + } + ] } \ No newline at end of file diff --git a/contract/ingest/telemetry.schema.json b/contract/ingest/telemetry.schema.json index 39a4131..0e7af50 100644 --- a/contract/ingest/telemetry.schema.json +++ b/contract/ingest/telemetry.schema.json @@ -1,46 +1,44 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "contract/ingest/telemetry.schema.json", - "allOf": [ - { "$ref": "contract/ingest/common.schema.json" }, - { - "type": "object", - "properties": { - "payload": { - "type": "object", - "required": ["measurements"], - "additionalProperties": false, - "properties": { - "measurements": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": ["metric", "value"], - "additionalProperties": false, - "properties": { - "metric": { - "type": "string", - "minLength": 1 - }, - "value": { - "type": ["number", "string", "boolean"] - }, - "unit": { - "type": "string" - }, - "source": { - "type": "string" - }, - "ts": { - "type": "integer" - } - } - } - } + "type": "object", + "required": ["m", "s", "u", "v"], + "additionalProperties": false, + "properties": { + "m": { + "type": "string", + "minLength": 1, + "description": "Metric type (e.g. t=temperature, h=humidity)" + }, + "s": { + "type": "string", + "minLength": 1, + "description": "Source identifier (sensor/channel)" + }, + "u": { + "type": "string", + "minLength": 1, + "description": "Unit (e.g. c, pct, v)" + }, + "v": { + "type": "array", + "minItems": 1, + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "type": "integer", + "minimum": 0, + "description": "Delta time (seconds) from base ts" + }, + { + "type": "number", + "description": "Measured value" } - } + ] } } - ] + } } \ No newline at end of file diff --git a/esp32/main/CMakeLists.txt b/esp32/main/CMakeLists.txt index 46b04b8..762fa47 100644 --- a/esp32/main/CMakeLists.txt +++ b/esp32/main/CMakeLists.txt @@ -1,7 +1,7 @@ idf_component_register( - SRCS "main.cpp" "protocol.cpp" "wifi_manager.cpp" "system_init.cpp" "time_sync.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" INCLUDE_DIRS "." - REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer + REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer esp_adc ) # добавляем кастомный Kconfig set(COMPONENT_KCONFIG "Kconfig") \ No newline at end of file diff --git a/esp32/main/adc_reader.cpp b/esp32/main/adc_reader.cpp new file mode 100644 index 0000000..a44ee77 --- /dev/null +++ b/esp32/main/adc_reader.cpp @@ -0,0 +1,26 @@ +#include "adc_reader.h" + +extern "C" { +#include "esp_adc/adc_oneshot.h" +} + +static adc_oneshot_unit_handle_t adc_handle; + +void adc_reader_init() { + adc_oneshot_unit_init_cfg_t init_config = {}; + init_config.unit_id = ADC_UNIT_1; + + adc_oneshot_new_unit(&init_config, &adc_handle); + + adc_oneshot_chan_cfg_t config = {}; + config.atten = ADC_ATTEN_DB_12; + config.bitwidth = ADC_BITWIDTH_DEFAULT; + + adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_7, &config); // GPIO35 +} + +int adc_reader_read() { + int val = 0; + adc_oneshot_read(adc_handle, ADC_CHANNEL_7, &val); + return val; +} \ No newline at end of file diff --git a/esp32/main/adc_reader.h b/esp32/main/adc_reader.h new file mode 100644 index 0000000..febdf95 --- /dev/null +++ b/esp32/main/adc_reader.h @@ -0,0 +1,7 @@ +#pragma once + +// Инициализация ADC +void adc_reader_init(); + +// Прочитать значение (GPIO35) +int adc_reader_read(); \ No newline at end of file diff --git a/esp32/main/main.cpp b/esp32/main/main.cpp index 1e3eef2..1450cdf 100644 --- a/esp32/main/main.cpp +++ b/esp32/main/main.cpp @@ -4,6 +4,8 @@ #include "freertos/projdefs.h" #include "freertos/task.h" #include "protocol.h" +#include "sampler_task.h" +#include "sender_task.h" static const char* TAG = "iot_fish"; @@ -11,16 +13,8 @@ extern "C" void app_main(void) { system_init(); - for (int i=0; i<5; i++) - { - char buf[512]; - int len = build_telemetry_single(buf, sizeof(buf), "123", "dev1", 123456, "temperature", 22.5, "C", nullptr, 0); - - if (len > 0) { - ESP_LOGI("protocol", "%s\n", buf); - } - vTaskDelay(pdMS_TO_TICKS(1000)); - } + sampler_task_start(); + sender_task_start(); system_finalize(); } \ No newline at end of file diff --git a/esp32/main/ringbuf.cpp b/esp32/main/ringbuf.cpp new file mode 100644 index 0000000..93fd0fe --- /dev/null +++ b/esp32/main/ringbuf.cpp @@ -0,0 +1,21 @@ +#include "ringbuf.h" + +void ringbuf_init(ringbuf_t *rb) { + rb->head = 0; + for (int i = 0; i < RINGBUF_SIZE; i++) { + rb->values[i] = 0; + } +} + +void ringbuf_push(ringbuf_t *rb, int v) { + rb->values[rb->head] = v; + rb->head = (rb->head + 1) % RINGBUF_SIZE; +} + +void ringbuf_copy(const ringbuf_t *rb, int *out) { + int idx = rb->head; + + for (int i = 0; i < RINGBUF_SIZE; i++) { + out[i] = rb->values[(idx + i) % RINGBUF_SIZE]; + } +} diff --git a/esp32/main/ringbuf.h b/esp32/main/ringbuf.h new file mode 100644 index 0000000..c3f64bb --- /dev/null +++ b/esp32/main/ringbuf.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#define RINGBUF_SIZE 10 + +typedef struct { + int values[RINGBUF_SIZE]; + int head; +} ringbuf_t; + +// Инициализация +void ringbuf_init(ringbuf_t *rb); + +// Добавить значение +void ringbuf_push(ringbuf_t *rb, int v); + +// Скопировать данные (в порядке времени) +void ringbuf_copy(const ringbuf_t *rb, int *out); \ No newline at end of file diff --git a/esp32/main/sampler_task.cpp b/esp32/main/sampler_task.cpp new file mode 100644 index 0000000..e7f09da --- /dev/null +++ b/esp32/main/sampler_task.cpp @@ -0,0 +1,39 @@ +#include "sampler_task.h" +#include "adc_reader.h" +#include "ringbuf.h" + +extern "C" { +#include "freertos/FreeRTOS.h" +#include "freertos/task.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); + + vTaskDelay(pdMS_TO_TICKS(100)); // 10 Гц + } +} + +void sampler_task_start() { + ringbuf_init(&rb); + adc_reader_init(); + + xTaskCreate( + sampler_task, + "sampler", + 2048, + NULL, + 5, + NULL + ); +} \ No newline at end of file diff --git a/esp32/main/sampler_task.h b/esp32/main/sampler_task.h new file mode 100644 index 0000000..0d64ab8 --- /dev/null +++ b/esp32/main/sampler_task.h @@ -0,0 +1,6 @@ +#pragma once + +#include "ringbuf.h" + +void sampler_task_start(); +ringbuf_t* sampler_get_buffer(); // ← ВОТ ЭТО \ No newline at end of file diff --git a/esp32/main/sender_task.cpp b/esp32/main/sender_task.cpp new file mode 100644 index 0000000..67d0f4c --- /dev/null +++ b/esp32/main/sender_task.cpp @@ -0,0 +1,48 @@ +#include "sender_task.h" +#include "sampler_task.h" +#include "ringbuf.h" + +#include +#include + +extern "C" { +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +} + +static uint32_t msg_id = 1; + +static void sender_task(void *arg) { + int data[RINGBUF_SIZE]; + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); // 1 Гц + + 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\":["); + + for (int i = 0; i < RINGBUF_SIZE; i++) { + printf("[%d,%d]%s", i, data[i], (i < RINGBUF_SIZE - 1) ? "," : ""); + } + + printf("]}}\n"); + } +} + +void sender_task_start() { + xTaskCreate( + sender_task, + "sender", + 4096, + NULL, + 5, + NULL + ); +} \ No newline at end of file diff --git a/esp32/main/sender_task.h b/esp32/main/sender_task.h new file mode 100644 index 0000000..b745a1a --- /dev/null +++ b/esp32/main/sender_task.h @@ -0,0 +1,4 @@ +#pragma once + +// Запуск задачи отправки (1 Гц) +void sender_task_start(); \ No newline at end of file