first commit

This commit is contained in:
2026-03-19 16:06:28 +01:00
commit 361d0bac37
19 changed files with 5611 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"idf.currentSetup": "/home/tomasg/esp/v5.3.4/esp-idf"
}

3
CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(thread_node_c6)

196
README.md Normal file
View File

@ -0,0 +1,196 @@
# Cviceni: Thread a CoAP na ESP32-C6
Tento projekt je pripraven jako kostra pro pocitacove cviceni. Cilem je:
1. nastavit uzel do site `Thread` pomoci aktivniho datasetu,
2. vytvorit CoAP `POST` pozadavek,
3. odeslat simulovana senzorova data ve formatu JSON.
V projektu jsou nektere casti jiz hotove a nektere jsou pripraveny jako `TODO` pro doplneni.
## Co je Thread
Thread je sitovy protokol pro zarizeni nad IEEE 802.15.4. Umoznuje IPv6 komunikaci mezi uzly s malou rezii a nizkou spotrebou. V siti se mohou objevit napriklad role:
- `leader`
- `router`
- `child`
Aby se uzel pripojil do konkretni site, musi znat parametry aktivniho datasetu:
- `Network Name`
- `Channel`
- `PAN ID`
- `Network Key`
- `Extended PAN ID`
- `Mesh-Local Prefix`
Tyto hodnoty se v projektu vyplnuji do [src/thread_dataset.h](src/thread_dataset.h).
## Co je CoAP
CoAP je lehky aplikacni protokol pro vestavene systemy. Bezi nad UDP a pripomina zjednodusene HTTP. V tomto cviceni budeme pouzivat:
- metodu `POST`
- URI cestu `/sensor`
- payload ve formatu JSON
Server, na ktery bude klient odesilat data, ma adresu:
```text
coap://[fd11:2233:4455:6677::1]:5683/sensor
```
Parsovani teto URL je v projektu jiz hotove. Studenti implementuji jen vytvoreni JSON payloadu a sestaveni CoAP zpravy.
## Postup cviceni
### 1. Doplneni Thread datasetu
Otevri [src/thread_dataset.h](src/thread_dataset.h). V souboru jsou placeholdery, ktere je potreba nahradit hodnotami ze zadani.
Vypln:
- `THREAD_NETWORK_NAME = "OpenThread-ESP"`
- `THREAD_CHANNEL = 15`
- `THREAD_PANID = 0x1234`
- `THREAD_MESH_LOCAL_PREFIX_STRING = "fd11:2233:4455:6677"`
- `THREAD_GATEWAY_STATIC_ADDR = "fd11:2233:4455:6677::1"`
- `THREAD_NODE_STATIC_ADDR = "fd11:2233:4455:6677::10"`
- `THREAD_GATEWAY_COAP_URL = "coap://[fd11:2233:4455:6677::1]:5683/sensor"`
`Network Key`:
```c
{
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00,
}
```
`Extended PAN ID`:
```c
{
0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xfe,
}
```
`Mesh-Local Prefix` jako pole bajtu:
```c
{
0xfd, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
}
```
Po doplneni by mel byt uzel schopny se pripojit do site Thread.
### 2. Sestaveni JSON payloadu
Otevri [src/coap_client.c](src/coap_client.c) a dopln funkci `coap_client_build_json_payload()`.
Tato funkce ma:
- vzit hodnoty ze struktury `sensor_data_t`
- vytvorit z nich textovy JSON
- ulozit vysledek do bufferu `payload`
- vratit `true`, pokud se payload podarilo vytvorit
Ocekavany format:
```json
{"node_id":"node_1","temp":23.4,"humidity":48,"button":1}
```
K tomu je vhodne pouzit funkci `snprintf()`.
`snprintf()`:
- zapisuje formatovany text do bufferu
- hlida maximalni velikost bufferu
- vraci pocet znaku, ktere by byly zapsany
Co je potreba zkontrolovat:
- navratova hodnota neni zaporna
- vysledny text se vesel do `payload_size`
### 3. Vytvoreni CoAP POST zpravy
Ve stejnem souboru dopln funkci `coap_client_create_post_request()`.
Tato funkce ma z vytvoreneho JSON payloadu pripravit CoAP zpravu pro OpenThread.
Postup:
1. vytvorit zpravu
2. nastavit typ zpravy
3. nastavit kod `POST`
4. pridat URI path
5. pridat informaci, ze payload je `JSON`
6. pridat samotny payload
Pouzij tyto OpenThread funkce:
- `otCoapNewMessage()`
Vytvori novou CoAP zpravu.
- `otCoapMessageInit()`
Nastavi typ a kod zpravy. Zde pouzij `OT_COAP_TYPE_CONFIRMABLE` a `OT_COAP_CODE_POST`.
- `otCoapMessageGenerateToken()`
Vygeneruje token, podle ktereho lze sparovat odpoved.
- `otCoapMessageAppendUriPathOptions()`
Prida URI cestu, napriklad `sensor`.
- `otCoapMessageAppendContentFormatOption()`
Nastavi format obsahu. Zde pouzij `OT_COAP_OPTION_CONTENT_FORMAT_JSON`.
- `otCoapMessageSetPayloadMarker()`
Oddeli CoAP options od samotneho payloadu.
- `otMessageAppend()`
Prida do zpravy data payloadu.
Pokud nektery krok selze:
- uvolni zpravu pomoci `otMessageFree(message)`
- vrat `NULL`
### 4. Prepinani intervalu pomoci D1
Na kitu je pripojen pin `D1`, ktery bude slouzit pro prepinani periody odesilani.
V [src/main.c](src/main.c) je pripraveno:
- inicializovani vstupu pro tlacitko na `D1`
- funkce `application_get_send_interval_ms()`, kterou je potreba doplnit
Student ma dopsat logiku:
- pokud je tlacitko stisknute, pouzij rychly interval `1000 ms`
- pokud tlacitko stisknute neni, pouzij pomaly interval `5000 ms`
Napoveda:
- pro cteni vstupu lze pouzit `gpio_get_level()`
- pri zapnutem `pull-up` byva stisk tlacitka casto reprezentovan hodnotou `0`
### 5. Otestovani
Po doplneni sleduj log na seriove lince. Zajimaji te hlavne:
- zmena role uzlu v Thread siti
- informace o inicializaci CoAP klienta
- vypis odesilaneho payloadu
- zmena intervalu po stisku tlacitka `D1`
- pripadna odpoved od CoAP serveru
## Co uz je hotove
V projektu je jiz pripraveno:
- spusteni OpenThread stacku v [src/main.c](src/main.c)
- nacitani datasetu a start site v [src/main.c](src/main.c)
- parsovani CoAP URL v [src/coap_client.c](src/coap_client.c)
- volani `coap_send_data()` a odesilani periodickych dat
## Poznamka
V [src/thread_dataset.h](src/thread_dataset.h) jsou zamerne placeholdery. Projekt se zkompiluje, ale bez spravnych hodnot se uzel nepripoji do pozadovane Thread site.

37
include/README Normal file
View File

@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

21
platformio.ini Normal file
View File

@ -0,0 +1,21 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32-c6-devkitc-1]
platform = espressif32
board = esp32-c6-devkitc-1
framework = espidf
monitor_speed = 115200
board_build.flash_mode = dio
board_build.flash_size = 4MB
board_upload.flash_size = 4MB
build_flags =
-UOPENTHREAD_BUILD_DATETIME
-DOPENTHREAD_BUILD_DATETIME=\"unknown\"

9
sdkconfig.defaults Normal file
View File

@ -0,0 +1,9 @@
CONFIG_OPENTHREAD_ENABLED=y
CONFIG_OPENTHREAD_FTD=y
CONFIG_OPENTHREAD_RADIO_NATIVE=y
CONFIG_OPENTHREAD_COAP=y
CONFIG_IEEE802154_ENABLED=y
CONFIG_LWIP_IPV6=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# CONFIG_OPENTHREAD_BORDER_ROUTER is not set

2538
sdkconfig.esp32-c6-devkitc-1 Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

10
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,10 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(
SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES mqtt esp_wifi esp_event esp_netif nvs_flash openthread vfs
)

169
src/coap_client.c Normal file
View File

@ -0,0 +1,169 @@
#include "coap_client.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "openthread/coap.h"
#include "openthread/ip6.h"
#include "openthread/message.h"
#include "openthread/thread.h"
static const char *TAG = "CoAPClient";
bool coap_client_parse_url(const char *url, otIp6Address *peer_addr, uint16_t *peer_port, const char **uri_path)
{
const char *scheme = "coap://[";
const char *host_start;
const char *host_end;
const char *port_start;
const char *path_start;
char host[OT_IP6_ADDRESS_STRING_SIZE];
unsigned int parsed_port = OT_DEFAULT_COAP_PORT;
int host_len;
if (strncmp(url, scheme, strlen(scheme)) != 0) {
return false;
}
host_start = url + strlen(scheme);
host_end = strchr(host_start, ']');
if (host_end == NULL) {
return false;
}
host_len = (int)(host_end - host_start);
if (host_len <= 0 || host_len >= (int)sizeof(host)) {
return false;
}
memcpy(host, host_start, (size_t)host_len);
host[host_len] = '\0';
if (otIp6AddressFromString(host, peer_addr) != OT_ERROR_NONE) {
return false;
}
if (host_end[1] == ':') {
port_start = host_end + 2;
path_start = strchr(port_start, '/');
if (path_start == NULL || sscanf(port_start, "%u", &parsed_port) != 1 || parsed_port > 65535) {
return false;
}
} else if (host_end[1] == '/') {
path_start = host_end + 1;
} else {
return false;
}
if (path_start[0] != '/' || path_start[1] == '\0') {
return false;
}
*peer_port = (uint16_t)parsed_port;
*uri_path = path_start + 1;
return true;
}
static void coap_response_handler(void *context, otMessage *message, const otMessageInfo *message_info, otError error)
{
char peer_addr[OT_IP6_ADDRESS_STRING_SIZE];
(void)context;
if (error != OT_ERROR_NONE) {
ESP_LOGW(TAG, "CoAP response error: %s", otThreadErrorToString(error));
return;
}
otIp6AddressToString(&message_info->mPeerAddr, peer_addr, sizeof(peer_addr));
ESP_LOGI(TAG, "CoAP response from %s, code=%u", peer_addr, otCoapMessageGetCode(message));
}
void coap_client_init(otInstance *instance) {
if (otCoapStart(instance, OT_DEFAULT_COAP_PORT) != OT_ERROR_NONE) {
ESP_LOGE(TAG, "Failed to start CoAP client");
return;
}
ESP_LOGI(TAG, "CoAP client initialized.");
}
bool coap_client_build_json_payload(const sensor_data_t *data, char *payload, size_t payload_size)
{
(void)data;
(void)payload;
(void)payload_size;
ESP_LOGW(TAG, "TODO: doplnit sestaveni JSON payloadu");
//TO DO
return false;
}
otMessage *coap_client_create_post_request(otInstance *instance, const char *uri_path, const char *payload)
{
(void)uri_path;
(void)payload;
otMessage *message = otCoapNewMessage(instance, NULL);
if (message == NULL) {
ESP_LOGE(TAG, "Failed to allocate CoAP message");
return NULL;
}
ESP_LOGW(TAG, "TODO: doplnit vytvoreni CoAP POST zpravy");
//TO DO
otMessageFree(message);
return NULL;
}
void coap_send_data(otInstance *instance, const char *url, sensor_data_t *data) {
otMessage *message;
otMessageInfo message_info;
otIp6Address peer_addr_bin;
char peer_addr[OT_IP6_ADDRESS_STRING_SIZE];
char payload[128];
uint16_t peer_port;
const char *uri_path;
if (otThreadGetDeviceRole(instance) == OT_DEVICE_ROLE_DISABLED ||
otThreadGetDeviceRole(instance) == OT_DEVICE_ROLE_DETACHED) {
ESP_LOGW(TAG, "Thread node is not attached yet, skipping CoAP send");
return;
}
if (!coap_client_build_json_payload(data, payload, sizeof(payload))) {
ESP_LOGW(TAG, "Payload nebyl vytvoren, CoAP odeslani se preskakuje");
return;
}
if (!coap_client_parse_url(url, &peer_addr_bin, &peer_port, &uri_path)) {
ESP_LOGW(TAG, "Invalid CoAP URL: %s", url);
return;
}
message = coap_client_create_post_request(instance, uri_path, payload);
if (message == NULL) {
ESP_LOGW(TAG, "CoAP zprava nebyla vytvorena");
return;
}
memset(&message_info, 0, sizeof(message_info));
message_info.mPeerAddr = peer_addr_bin;
message_info.mPeerPort = peer_port;
message_info.mSockAddr = *otThreadGetMeshLocalEid(instance);
message_info.mSockPort = OT_DEFAULT_COAP_PORT;
otIp6AddressToString(&peer_addr_bin, peer_addr, sizeof(peer_addr));
ESP_LOGI(TAG, "CoAP POST to [%s]:%u/%s payload=%s", peer_addr, peer_port, uri_path, payload);
if (otCoapSendRequest(instance, message, &message_info, coap_response_handler, NULL) != OT_ERROR_NONE) {
ESP_LOGE(TAG, "Failed to send CoAP request");
otMessageFree(message);
}
}

19
src/coap_client.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef COAP_CLIENT_H
#define COAP_CLIENT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sensor_sim.h"
#include "openthread/instance.h"
#include "openthread/ip6.h"
#include "openthread/message.h"
void coap_client_init(otInstance *instance);
bool coap_client_build_json_payload(const sensor_data_t *data, char *payload, size_t payload_size);
bool coap_client_parse_url(const char *url, otIp6Address *peer_addr, uint16_t *peer_port, const char **uri_path);
otMessage *coap_client_create_post_request(otInstance *instance, const char *uri_path, const char *payload);
void coap_send_data(otInstance *instance, const char *url, sensor_data_t *data);
#endif

184
src/main.c Normal file
View File

@ -0,0 +1,184 @@
#include <inttypes.h>
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sensor_sim.h"
#include "coap_client.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_openthread.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_netif_glue.h"
#include "esp_vfs_eventfd.h"
#include "nvs_flash.h"
#include "openthread/dataset.h"
#include "openthread/instance.h"
#include "openthread/ip6.h"
#include "openthread/thread.h"
#include "thread_dataset.h"
static const char *TAG = "ThreadNode";
#define LAB_INTERVAL_BUTTON_GPIO GPIO_NUM_1
#define LAB_SEND_INTERVAL_SLOW_MS 5000
#define LAB_SEND_INTERVAL_FAST_MS 1000
static const char *ot_role_to_string(otDeviceRole role)
{
return otThreadDeviceRoleToString(role);
}
#define ESP_OPENTHREAD_DEFAULT_NODE_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_NATIVE, \
}
#define ESP_OPENTHREAD_DEFAULT_NODE_HOST_CONFIG() \
{ \
.host_connection_mode = HOST_CONNECTION_MODE_NONE, \
}
#define ESP_OPENTHREAD_DEFAULT_NODE_PORT_CONFIG() \
{ \
.storage_partition_name = "nvs", \
.netif_queue_size = 10, \
.task_queue_size = 10, \
}
static void log_node_state(otInstance *instance, const char *reason)
{
ESP_LOGI(
TAG,
"%s role=%s commissioned=%s channel=%u panid=0x%04x rloc16=0x%04x net='%s'",
reason,
ot_role_to_string(otThreadGetDeviceRole(instance)),
otDatasetIsCommissioned(instance) ? "yes" : "no",
otLinkGetChannel(instance),
otLinkGetPanId(instance),
otThreadGetRloc16(instance),
otThreadGetNetworkName(instance)
);
}
static void ot_state_changed_callback(otChangedFlags flags, void *context)
{
otInstance *instance = context;
ESP_LOGI(TAG, "state changed flags=0x%08" PRIx32, (uint32_t)flags);
log_node_state(instance, "callback");
}
static void ensure_static_thread_address(otInstance *instance)
{
otError error = thread_add_static_unicast_address(instance, THREAD_NODE_STATIC_ADDR);
if (error == OT_ERROR_NONE) {
ESP_LOGI(TAG, "Configured stable Mesh-Local address %s", THREAD_NODE_STATIC_ADDR);
} else {
ESP_LOGW(TAG, "Failed to configure stable Mesh-Local address %s: %s",
THREAD_NODE_STATIC_ADDR, otThreadErrorToString(error));
}
}
static void thread_start_network(otInstance *instance, otOperationalDatasetTlvs *dataset_tlvs)
{
if (otSetStateChangedCallback(instance, ot_state_changed_callback, instance) != OT_ERROR_NONE) {
ESP_LOGW(TAG, "Failed to register OpenThread state callback");
}
log_node_state(instance, "before_auto_start");
ensure_static_thread_address(instance);
ESP_ERROR_CHECK(esp_openthread_auto_start(dataset_tlvs));
log_node_state(instance, "after_auto_start");
}
static void interval_button_init(void)
{
const gpio_config_t button_config = {
.pin_bit_mask = 1ULL << LAB_INTERVAL_BUTTON_GPIO,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&button_config));
ESP_LOGI(TAG, "Interval button initialized on GPIO %d", LAB_INTERVAL_BUTTON_GPIO);
}
static uint32_t application_get_send_interval_ms(void)
{
/*
* TODO :
* - prectete stav tlacitka na pinu D1
* - pri stisku prepnite na rychly interval
* - bez stisku pouzijte pomaly interval
*/
return LAB_SEND_INTERVAL_SLOW_MS;
}
static void application_send_sensor_sample(otInstance *instance)
{
sensor_data_t data = sensor_read();
ESP_LOGI(TAG, "Node %s sending data: temp=%.1f hum=%d button=%d",
data.node_id, data.temp, data.humidity, data.button);
coap_send_data(instance, THREAD_GATEWAY_COAP_URL, &data);
}
void app_main(void)
{
esp_err_t ret;
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 4,
};
otOperationalDatasetTlvs dataset_tlvs;
static esp_openthread_config_t ot_config = {
.netif_config = ESP_NETIF_DEFAULT_OPENTHREAD(),
.platform_config = {
.radio_config = ESP_OPENTHREAD_DEFAULT_NODE_RADIO_CONFIG(),
.host_config = ESP_OPENTHREAD_DEFAULT_NODE_HOST_CONFIG(),
.port_config = ESP_OPENTHREAD_DEFAULT_NODE_PORT_CONFIG(),
},
};
otInstance *instance;
ESP_LOGI(TAG, "Initializing Thread node...");
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
ESP_ERROR_CHECK(esp_openthread_start(&ot_config));
instance = esp_openthread_get_instance();
thread_dataset_get_tlvs(&dataset_tlvs);
sensor_init();
interval_button_init();
esp_openthread_lock_acquire(portMAX_DELAY);
thread_start_network(instance, &dataset_tlvs);
coap_client_init(instance);
esp_openthread_lock_release();
while (1) {
uint32_t delay_ms = application_get_send_interval_ms();
esp_openthread_lock_acquire(portMAX_DELAY);
application_send_sensor_sample(instance);
esp_openthread_lock_release();
ESP_LOGI(TAG, "Next send in %" PRIu32 " ms", delay_ms);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
}

17
src/sensor_sim.c Normal file
View File

@ -0,0 +1,17 @@
#include "sensor_sim.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h> // 👈 DŮLEŽITÉ
void sensor_init() {
// nic speciálního
}
sensor_data_t sensor_read() {
sensor_data_t d;
snprintf(d.node_id, sizeof(d.node_id), "node_%d", rand()%10);
d.temp = 20.0 + (rand() % 1000)/100.0;
d.humidity = 40 + rand() % 20;
d.button = rand() % 2;
return d;
}

14
src/sensor_sim.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef SENSOR_SIM_H
#define SENSOR_SIM_H
typedef struct {
char node_id[16];
float temp;
int humidity;
int button;
} sensor_data_t;
void sensor_init();
sensor_data_t sensor_read();
#endif

83
src/thread_dataset.h Normal file
View File

@ -0,0 +1,83 @@
#ifndef THREAD_DATASET_H
#define THREAD_DATASET_H
#include <string.h>
#include "openthread/dataset.h"
#include "openthread/ip6.h"
/*
* TODO pro studenty:
* - nastavte parametry site Thread podle README.md
*/
#define THREAD_NETWORK_NAME "TODO_NETWORK_NAME"
#define THREAD_CHANNEL 0
#define THREAD_PANID 0x0000
#define THREAD_MESH_LOCAL_PREFIX_STRING "fd00:0000:0000:0000"
#define THREAD_GATEWAY_STATIC_ADDR THREAD_MESH_LOCAL_PREFIX_STRING "::1"
#define THREAD_NODE_STATIC_ADDR THREAD_MESH_LOCAL_PREFIX_STRING "::10"
#define THREAD_GATEWAY_COAP_URL "coap://[" THREAD_GATEWAY_STATIC_ADDR "]:5683/sensor"
static inline void thread_dataset_get_active(otOperationalDataset *dataset)
{
/*
* TODO:
* - doplnte Network Key podle README.md
* - doplnte Extended PAN ID podle README.md
* - zkontrolujte Mesh-Local prefix podle README.md
*/
static const uint8_t network_key[OT_NETWORK_KEY_SIZE] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t ext_pan_id[OT_EXT_PAN_ID_SIZE] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t mesh_local_prefix[OT_MESH_LOCAL_PREFIX_SIZE] = {
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
memset(dataset, 0, sizeof(*dataset));
dataset->mActiveTimestamp.mSeconds = 1;
dataset->mActiveTimestamp.mTicks = 0;
dataset->mActiveTimestamp.mAuthoritative = true;
memcpy(dataset->mNetworkKey.m8, network_key, sizeof(network_key));
memcpy(dataset->mExtendedPanId.m8, ext_pan_id, sizeof(ext_pan_id));
memcpy(dataset->mMeshLocalPrefix.m8, mesh_local_prefix, sizeof(mesh_local_prefix));
memcpy(dataset->mNetworkName.m8, THREAD_NETWORK_NAME, sizeof(THREAD_NETWORK_NAME));
dataset->mPanId = THREAD_PANID;
dataset->mChannel = THREAD_CHANNEL;
dataset->mComponents.mIsActiveTimestampPresent = true;
dataset->mComponents.mIsNetworkKeyPresent = true;
dataset->mComponents.mIsNetworkNamePresent = true;
dataset->mComponents.mIsExtendedPanIdPresent = true;
dataset->mComponents.mIsMeshLocalPrefixPresent = true;
dataset->mComponents.mIsPanIdPresent = true;
dataset->mComponents.mIsChannelPresent = true;
}
static inline otError thread_add_static_unicast_address(otInstance *instance, const char *address)
{
otIp6Address parsed_address;
otIp6InterfaceIdentifier iid;
/* Studenti si zde mohou overit, jak se IPv6 adresa mapuje do IID. */
if (otIp6AddressFromString(address, &parsed_address) != OT_ERROR_NONE) {
return OT_ERROR_INVALID_ARGS;
}
memcpy(iid.mFields.m8, &parsed_address.mFields.m8[8], sizeof(iid.mFields.m8));
return otIp6SetMeshLocalIid(instance, &iid);
}
static inline void thread_dataset_get_tlvs(otOperationalDatasetTlvs *dataset_tlvs)
{
otOperationalDataset dataset;
thread_dataset_get_active(&dataset);
otDatasetConvertToTlvs(&dataset, dataset_tlvs);
}
#endif

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html