System komunikacji bezprzewodowej z użyciem modułów ESP32 oraz protokołu ESP-MESH (3)

System komunikacji bezprzewodowej z użyciem modułów ESP32 oraz protokołu ESP-MESH (3)
Pobierz PDF Download icon

Słowo „Mesh” – określające sieci bezprzewodowe o topologii kratowej – przylgnęło do technologii Bluetooth. Organizacja Bluetooth SIG na swojej stronie internetowej reklamuje się hasłem „Mesh networking is blue”. Choć topologia kratowa wykorzystywana jest z powodzeniem od czasów powstania pierwszych sieci komputerowych, to dopiero wprowadzenie standardu Bluetooth Mesh w połowie 2017 roku zaowocowało wzrostem zainteresowania konstruktorów urządzeń IoT tą technologią. Czy zatem przystępując do budowy sieci czujników, połączonych w łatwo konfigurowalną i skalowaną sieć Mesh, jesteśmy skazani na Bluetooth? Oczywiście, że nie!

W poprzednich częściach artykułu omówione zostały zagadnienia związane z architekturą sieci, typami węzłów oraz „procesem wyborczym” węzła ROOT. Na potrzeby pierwszych testów sieci przygotowano również prostą aplikację z obsługą zdarzeń protokołu ESP-MESH. W tej części zajmiemy się rozbudowaniem kodu programu o proste mechanizmy komunikacji pomiędzy węzłami.

ESP-MESH – tablice tras połączeń

W ramach sieci tworzonej z użyciem protokołu ESP-MESH każdy węzeł przechowuje i zarządza własną tablicą tras połączeń (routing table), niezbędną do prawidłowego przekierowania i dostarczenia pakietu danych do węzła docelowego. W skład tablicy tras połączeń wchodzą adresy MAC wszystkich urządzeń, podłączonych w ramach podsieci danego węzła, oraz adres węzła, który tę tablicę przechowuje. Dodatkowo tablica może zostać podzielona na podsieci, a więc tablice poszczególnych węzłów typu child. Przykładowy podział na tabele i podtabele został pokazany na rysunku 6.

Rysunek 6. Tablice tras połączeń dla sieci ESP-MESH (źródło: https://docs.espressif.com

Węzeł B w tablicy tras połączeń zawiera węzły od B do I. Tablica ta może zostać podzielona na dwie mniejsze tabele – z węzłami od C do F oraz od G do I.

Listing 1. Pełny kod głównej funkcji programu – app_main()

void app_main(void)
{
/* init default NVS partition */
ESP_ERROR_CHECK(nvs_flash_init());

/* init LwIP stack */
tcpip_adapter_init();

/* stop DHCP server/client */
ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP));
ESP_ERROR_CHECK(tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA));

/* event initialization */
ESP_ERROR_CHECK(esp_event_loop_create_default());

/* Wi-Fi initialization */
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&config));

/* register IP events handler */
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));

/* start Wi-Fi */
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_FLASH));
ESP_ERROR_CHECK(esp_wifi_start());

/* mesh initialization */
ESP_ERROR_CHECK(esp_mesh_init());

/* register mesh events handler */
ESP_ERROR_CHECK(esp_event_handler_register(MESH_EVENT, ESP_EVENT_ANY_ID, &mesh_event_handler, NULL));

/* initialize mesh_cfg_t with default values */
mesh_cfg_t cfg = MESH_INIT_CONFIG_DEFAULT();

/* mesh ID */
memcpy((uint8_t *) &cfg.mesh_id, MESH_ID, 6);

/* router */
cfg.channel = MESH_CHANNEL;
cfg.router.ssid_len = strlen(MESH_ROUTER_SSID);
memcpy((uint8_t *) &cfg.router.ssid, MESH_ROUTER_SSID, cfg.router.ssid_len);
memcpy((uint8_t *) &cfg.router.password, MESH_ROUTER_PASSWD, strlen(MESH_ROUTER_PASSWD));

/* mesh softAP */
ESP_ERROR_CHECK(esp_mesh_set_ap_authmode(MESH_AP_AUTHMODE));
cfg.mesh_ap.max_connection = MESH_AP_CONNECTIONS;
memcpy((uint8_t *) &cfg.mesh_ap.password, MESH_AP_PASSWD, strlen(MESH_AP_PASSWD));

/* mesh settings */
ESP_ERROR_CHECK(esp_mesh_set_max_layer(MESH_MAX_LAYER));
ESP_ERROR_CHECK(esp_mesh_set_vote_percentage(1));
ESP_ERROR_CHECK(esp_mesh_set_ap_assoc_expire(10));

/* set mesh config */
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));

/* mesh start */
ESP_ERROR_CHECK(esp_mesh_start());
ESP_LOGI(TAG, "Mesh starts successfully: %s\n", esp_mesh_is_root_fixed() ? "root fixed" : "root not fixed");
}
Listing 2. Pełny kod funkcji obsługi zdarzeń protokołu ESP-MESH

static int mesh_layer = -1;
static mesh_addr_t mesh_parent_addr;

void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
mesh_addr_t id = {0,};
static uint8_t last_layer = 0;

switch (event_id) {

case MESH_EVENT_STARTED: {
esp_mesh_get_id(&id);
ESP_LOGI(TAG, "<MESH_EVENT_MESH_STARTED>ID:"MACSTR"", MAC2STR(id.addr));
mesh_layer = esp_mesh_get_layer();
}
break;

case MESH_EVENT_STOPPED: {
ESP_LOGI(TAG, "<MESH_EVENT_STOPPED>");
mesh_layer = esp_mesh_get_layer();
}
break;

case MESH_EVENT_CHILD_CONNECTED: {
mesh_event_child_connected_t *child_connected = (mesh_event_child_connected_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_CHILD_CONNECTED>aid:%d, "MACSTR"",
child_connected->aid,
MAC2STR(child_connected->mac));
}
break;

case MESH_EVENT_CHILD_DISCONNECTED: {
mesh_event_child_disconnected_t *child_disconnected = (mesh_event_child_disconnected_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_CHILD_DISCONNECTED>aid:%d, "MACSTR"",
child_disconnected->aid,
MAC2STR(child_disconnected->mac));
}
break;

case MESH_EVENT_ROUTING_TABLE_ADD: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(TAG, "<MESH_EVENT_ROUTING_TABLE_ADD>add %d, new:%d",
routing_table->rt_size_change,
routing_table->rt_size_new);
}
break;

case MESH_EVENT_ROUTING_TABLE_REMOVE: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(TAG, "<MESH_EVENT_ROUTING_TABLE_REMOVE>remove %d, new:%d",
routing_table->rt_size_change,
routing_table->rt_size_new);
}
break;

case MESH_EVENT_NO_PARENT_FOUND: {
mesh_event_no_parent_found_t *no_parent = (mesh_event_no_parent_found_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_NO_PARENT_FOUND>scan times:%d",
no_parent->scan_times);
}
break;

case MESH_EVENT_PARENT_CONNECTED: {
mesh_event_connected_t *connected = (mesh_event_connected_t *)event_data;
esp_mesh_get_id(&id);
mesh_layer = connected->self_layer;
memcpy(&mesh_parent_addr.addr, connected->connected.bssid, 6);
ESP_LOGI(TAG,
"<MESH_EVENT_PARENT_CONNECTED>layer:%d-->%d, parent:"MACSTR"%s, ID:"MACSTR"",
last_layer, mesh_layer, MAC2STR(mesh_parent_addr.addr),
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "", MAC2STR(id.addr));
last_layer = mesh_layer;
if (esp_mesh_is_root()) {
tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA);
}
}
break;

case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_disconnected_t *disconnected = (mesh_event_disconnected_t *)event_data;
ESP_LOGI(TAG,
"<MESH_EVENT_PARENT_DISCONNECTED>reason:%d",
disconnected->reason);
mesh_layer = esp_mesh_get_layer();
}
break;

case MESH_EVENT_LAYER_CHANGE: {
mesh_event_layer_change_t *layer_change = (mesh_event_layer_change_t *)event_data;
mesh_layer = layer_change->new_layer;
ESP_LOGI(TAG, "<MESH_EVENT_LAYER_CHANGE>layer:%d-->%d%s",
last_layer, mesh_layer,
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "");
last_layer = mesh_layer;
}
break;

case MESH_EVENT_ROOT_ADDRESS: {
mesh_event_root_address_t *root_addr = (mesh_event_root_address_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_ROOT_ADDRESS>root address:"MACSTR"",
MAC2STR(root_addr->addr));
}
break;

case MESH_EVENT_VOTE_STARTED: {
mesh_event_vote_started_t *vote_started = (mesh_event_vote_started_t *)event_data;
ESP_LOGI(TAG,
"<MESH_EVENT_VOTE_STARTED>attempts:%d, reason:%d, rc_addr:"MACSTR"",
vote_started->attempts,
vote_started->reason,
MAC2STR(vote_started->rc_addr.addr));
}
break;

case MESH_EVENT_VOTE_STOPPED: {
ESP_LOGI(TAG, "<MESH_EVENT_VOTE_STOPPED>");
}
break;

case MESH_EVENT_ROOT_SWITCH_REQ: {
mesh_event_root_switch_req_t *switch_req = (mesh_event_root_switch_req_t *)event_data;
ESP_LOGI(TAG,
"<MESH_EVENT_ROOT_SWITCH_REQ>reason:%d, rc_addr:"MACSTR"",
switch_req->reason,
MAC2STR( switch_req->rc_addr.addr));
}
break;

case MESH_EVENT_ROOT_SWITCH_ACK: {
/* new root */
mesh_layer = esp_mesh_get_layer();
esp_mesh_get_parent_bssid(&mesh_parent_addr);
ESP_LOGI(TAG, "<MESH_EVENT_ROOT_SWITCH_ACK>layer:%d, parent:"MACSTR"", mesh_layer, MAC2STR(mesh_parent_addr.addr));
}
break;

case MESH_EVENT_TODS_STATE: {
mesh_event_toDS_state_t *toDs_state = (mesh_event_toDS_state_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_TODS_REACHABLE>state:%d", *toDs_state);
}
break;

case MESH_EVENT_ROOT_FIXED: {
mesh_event_root_fixed_t *root_fixed = (mesh_event_root_fixed_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_ROOT_FIXED>%s",
root_fixed->is_fixed ? "fixed" : "not fixed");
}
break;

case MESH_EVENT_ROOT_ASKED_YIELD: {
mesh_event_root_conflict_t *root_conflict = (mesh_event_root_conflict_t *)event_data;
ESP_LOGI(TAG,
"<MESH_EVENT_ROOT_ASKED_YIELD>"MACSTR", rssi:%d, capacity:%d",
MAC2STR(root_conflict->addr),
root_conflict->rssi,
root_conflict->capacity);
}
break;

case MESH_EVENT_CHANNEL_SWITCH: {
mesh_event_channel_switch_t *channel_switch = (mesh_event_channel_switch_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_CHANNEL_SWITCH>new channel:%d", channel_switch->channel);
}
break;

case MESH_EVENT_SCAN_DONE: {
mesh_event_scan_done_t *scan_done = (mesh_event_scan_done_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_SCAN_DONE>number:%d",
scan_done->number);
}
break;

case MESH_EVENT_NETWORK_STATE: {
mesh_event_network_state_t *network_state = (mesh_event_network_state_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_NETWORK_STATE>is_rootless:%d",
network_state->is_rootless);
}
break;

case MESH_EVENT_STOP_RECONNECTION: {
ESP_LOGI(TAG, "<MESH_EVENT_STOP_RECONNECTION>");
}
break;

case MESH_EVENT_FIND_NETWORK: {
mesh_event_find_network_t *find_network = (mesh_event_find_network_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_FIND_NETWORK>new channel:%d, router BSSID:"MACSTR"",
find_network->channel, MAC2STR(find_network->router_bssid));
}
break;

case MESH_EVENT_ROUTER_SWITCH: {
mesh_event_router_switch_t *router_switch = (mesh_event_router_switch_t *)event_data;
ESP_LOGI(TAG, "<MESH_EVENT_ROUTER_SWITCH>new router:%s, channel:%d, "MACSTR"",
router_switch->ssid, router_switch->channel, MAC2STR(router_switch->bssid));
}
break;

default:
ESP_LOGI(TAG, "unknown id:%d", event_id);
break;
}
}

Zarządzanie tablicą tras w protokole ESP-MESH jest realizowane w sposób automatyczny. O dodaniu lub usunięciu nowego węzła, program użytkownika jest informowany poprzez zdarzenia MESH_EVENT_ROUTING_TABLE_ADD oraz MESH_EVENT_ROUTING_TABLE_REMOVE. Funkcja obsługi tych zdarzeń nie musi podejmować żadnych zadań, związanych z zarządzaniem tablicą. W większości przypadków ograniczy się wyłącznie do wyświetlenia prostych komunikatów w konsoli urządzenia, co pokazuje listing 3.

Listing 3. Obsługa zdarzeń związanych ze zmianą tablicy routingu

void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{

switch (event_id) {

/***/

case MESH_EVENT_ROUTING_TABLE_ADD: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(TAG, "<MESH_EVENT_ROUTING_TABLE_ADD>add %d, new:%d",
routing_table >rt_size_change,
routing_table >rt_size_new);
}
break;

case MESH_EVENT_ROUTING_TABLE_REMOVE: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(TAG, "<MESH_EVENT_ROUTING_TABLE_REMOVE>remove %d, new:%d",
routing_table >rt_size_change,
routing_table >rt_size_new);
}
break;

/***/

}
}

Użytkownik może uzyskać dostęp do informacji przechowywanych w tablicy tras poprzez wywołanie:

esp_err_t esp_mesh_get_routing_table (mesh_addr_t *mac, int len, int *size);

Pierwszy argument funkcji stanowi wskaźnik na tablicę tras (tabela struktur mesh_addr_t). Drugim jest wskaźnik na zmienną typu int, w której zostaną umieszczone informacje o wielkości tablicy (wyrażone w bajtach). Informację o wielkości tablicy, bez danych o adresach poszczególnych węzłów, można uzyskać również z wykorzystaniem funkcji esp_mesh_get_routing_table_size().

Analogiczny zestaw funkcji przygotowano również dla pobrania danych dotyczących podsieci wybranego węzła:

  • esp_mesh_get_subnet_nodes_list()
  • esp_mesh_get_subnet_nodes_num()

ESP-MESH – komunikacja pomiędzy węzłami

Posiadając już wiedzę o tablicach routingu oraz funkcjach związanych z dostępem do nich, przystąpmy do właściwej implementacji programu. Na potrzeby artykułu zrealizujmy najprostszy model komunikacji, w ramach której to węzeł ROOT przesyła w sposób cykliczny komunikaty „Hello World!” do wszystkich urządzeń w sieci (a więc do wszystkich urządzeń przechowywanych w jego tablicy tras połączeń).

Wprowadzanie zmian w kodzie rozpoczynamy od modyfikacji funkcji obsługi zdarzeń mesh_event_handler(), opisanej w pierwszej części artykułu. W tym celu, w obsłudze zdarzenia MESH_EVENT_PARENT_CONNECTED, informującego węzeł, że został podłączony do węzła rodzica i stał się pełnoprawnym uczestnikiem sieci, dodajemy teraz wywołanie funkcji mesh_tx_rx_start() (listing 4).

Listing 4. Obsługa zdarzenia MESH_EVENT_PARENT_CONNECTED

void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{

switch (event_id) {

/***/

case MESH_EVENT_PARENT_CONNECTED: {
mesh_event_connected_t *connected = (mesh_event_connected_t *)event_data;
esp_mesh_get_id(&id);
mesh_layer = connected >self_layer;
memcpy(&mesh_parent_addr.addr, connected >connected.bssid, 6);
ESP_LOGI(TAG,
"<MESH_EVENT_PARENT_CONNECTED>layer:%d >%d, parent:"MACSTR"%s, ID:"MACSTR"",
last_layer, mesh_layer, MAC2STR(mesh_parent_addr.addr),
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "", MAC2STR(id.addr));
last_layer = mesh_layer;
if (esp_mesh_is_root()) {
tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA);
}

mesh_tx_rx_start();
}
break;

/***/

}
}

Zadaniem funkcji mesh_tx_rx_start(), jest utworzenie dwóch nowych zadań: esp_mesh_tx_start oraz esp_mesh_rx_start, które będą odpowiedzialne za nadawanie i odbiór pakietów w sieci. Ponieważ środowisko esp-idf bazuje na systemie FreeRTOS, zadanie to może zostać zrealizowane za pomocą standardowych dla tego systemu wywołań xTaskCreate() (listing 5).

Listing 5. Uruchomienie zadań nadawania i odbioru pakietów danych

esp_err_t mesh_tx_rx_start(void)
{
static bool is_tx_rx_started = false;

if (!is_tx_rx_started) {

is_tx_rx_started = true;

xTaskCreate(esp_mesh_rx_task, "TASK_RX", 3072, NULL, 5, NULL);
xTaskCreate(esp_mesh_tx_task, "TASK_TX", 3072, NULL, 5, NULL);

}

return ESP_OK;
}

W przyjętych na potrzeby artykułu założeniach, za nadawanie danych odpowiada wyłącznie węzeł ROOT, tak więc implementację zadania esp_mesh_tx_task(), rozpoczynamy od sprawdzenia funkcji, jaką dany węzeł pełni w sieci ESP-MESH (listing 6).

Listing 6. Sprawdzenie roli węzła w zadaniu esp_mesh_tx_task()

void esp_mesh_tx_task(void *arg)
{

while (true) {

/* non root node do nothing */
if (!esp_mesh_is_root()) {

ESP_LOGI(TAG, "[TX TASK] non root node skip...");
vTaskDelay(10 * 1000 / portTICK_RATE_MS);
continue;
}

/***/

} /* while */

vTaskDelete(NULL);
}

Ponieważ funkcja przypisana do węzła może w sposób dynamiczny ulec zmianie, zadanie to nie jest usuwane dla węzłów niebędących węzłem ROOT.

Kolejnym zadaniem węzła ROOT jest pobranie danych do tablicy tras połączeń (listing 7) oraz przygotowanie wiadomości (tekstu „Hello World!”), która będzie przesłana do wszystkich węzłów w tablicy.

Listing 7. Pobranie danych to tablicy tras połączeń

status = esp_mesh_get_routing_table(&route_table, MESH_ROUTE_TABLE_SIZE * 6, &route_table_size);
if (status != ESP_OK) {

ESP_LOGW(TAG, "[TX TASK] cannot read routing table");
vTaskDelay(10 * 1000 / portTICK_RATE_MS);
continue;
}

Dane wysyłane i odbierane za pomocą funkcji esp_mesh_send() oraz esp_mesh_recv() muszą zostać „opakowane” w strukturę typu mesh_data_t:

struct mesh_data_t {
uint8_t *data,
uint16_t size,
mesh_proto_t proto,
mesh_tos_t tos
}

Pole data wskazuje na dane do wysłania, natomiast pole size określa ich rozmiar. Protokół transmisji danych może zostać zdefiniowany poprzez wyliczeniowy typ danych mesh_proto_t:

  • MESH_PROTO_BIN (wartość domyślna)
  • MESH_PROTO_HTTP
  • MESH_PROTO_JSON
  • MESH_PROTO_MQTT

Ostatnie z pól struktury mesh_data_t określa zachowanie sieci w przypadku wystąpienia potrzeby retransmisji pakietu. Aktualnie protokół ESP-MESH wspiera wyłącznie opcje retransmisji na poziomie punkt-punkt (MESH_TOS_P2P) lub pozwala na jej całkowite wyłączenie (MESH_TOS_DEF). Znając znaczenie poszczególnych pól struktury mesh_data_t, możemy do funkcji wysyłania komunikatów dopisać kolejne linie kodu, związane z przygotowaniem wiadomości „Hello World!”:

mesh_data_t msg;
msg.data = (uint8_t*)"Hello World!";
msg.size = sizeof("Hello World!");
msg.proto = MESH_PROTO_BIN;
msg.tos = MESH_TOS_P2P;

Ostatnim elementem zadania esp_mesh_tx_task() jest wywołanie funkcji esp_mesh_send() (odpowiedzialnej za wysłanie pakietu w ramach sieci) do wszystkich węzłów znajdujących się w tablicy routingu. Ogólną postać funkcji esp_mesh_send() przedstawiono poniżej:

esp_err_t esp_mesh_send (const mesh_addr_t *to,
const mesh_data_t *data,
int flag,
const mesh_opt_topt[],
int opt_count
);

Pierwsze dwa argumenty funkcji to adres węzła docelowego (lub wartość NULL, jeżeli pakiet jest adresowany do węzła ROOT) oraz dane do wysłania, w postaci struktury mesh_data_t. Kolejny argument stanowią flagi wiadomości, które opisują kierunek transferu danych, w tym dwie najważniejsze: wartość 0, w przypadku wysłania wiadomości do węzła ROOT, oraz wartość MESH_DATA_P2P, jeżeli dane kierowane są do wewnętrznego węzła w sieci. Dwa ostatnie argumenty to opcje dodatkowe, związane z routowaniem pakietów do grup węzłów oraz na poziomie bramy dostępowej a węzłami wewnętrznymi (w omawianym przypadku opcje nie są wykorzystywane). Pełny kod zadania esp_mesh_tx_task() pokazuje listing 8.

Listing 8. Pełny kod źródłowy funkcji esp_mesh_tx_task()

void esp_mesh_tx_task(void *arg)
{
/* status */
esp_err_t status;

/* routing table */
mesh_addr_t route_table[MESH_ROUTE_TABLE_SIZE];
int route_table_size;

while (true) {

/* non root node do nothing */
if (!esp_mesh_is_root()) {

ESP_LOGI(TAG, "[TX TASK] non root node skip...");
vTaskDelay(10 * 1000 / portTICK_RATE_MS);
continue;
}

/* get routing table */
status = esp_mesh_get_routing_table((mesh_addr_t*)&route_table, MESH_ROUTE_TABLE_SIZE * 6, &route_table_size);
if (status != ESP_OK) {

ESP_LOGW(TAG, "[TX TASK] cannot read routing table");
vTaskDelay(10 * 1000 / portTICK_RATE_MS);
continue;
}

/* create 'Hello World!' message for nodes */
mesh_data_t msg;
msg.data = (uint8_t*)"Hello World!";
msg.size = sizeof("Hello World!");
msg.proto = MESH_PROTO_BIN;
msg.tos = MESH_TOS_P2P;

/* for each node in this device’s sub network (including self) */
for (int cnt = 0; cnt < route_table_size; cnt++) {

ESP_LOGI(TAG, "[TX TASK][%d/%d] Sending '%s' message to child node ("MACSTR")", cnt+1, route_table_size, "Hello World", MAC2STR(route_table[cnt].addr));

status = esp_mesh_send(&route_table[cnt], &msg, MESH_DATA_P2P, NULL, 0);
if (status != ESP_OK) {

ESP_LOGW(TAG, "[TX TASK] cannot send message to "MACSTR" : heap:%d [err:0x%x, proto:%d, tos:%d]",
esp_get_free_heap_size(), MAC2STR(route_table[cnt].addr), status, msg.proto, msg.tos);
continue;
}
}

/* if route_table_size is less than 10, add delay to avoid watchdog in this task */
if (route_table_size < 10)
vTaskDelay(1 * 1000 / portTICK_RATE_MS);

} /* while */

vTaskDelete(NULL);
}

Ostatnim i niezbędnym etapem do przeprowadzenia poprawnej kompilacji kodu jest implementacja zadania esp_mesh_rx_task(). Ponieważ tym razem nie stawiamy ograniczeń na funkcję, jaką pełni węzeł (węzeł ROOT realizuje również odczyt danych), konstrukcja zadania będzie wyłącznie obudowanym wywołaniem funkcji esp_mesh_recv(), co pokazuje listing 9.

Listing 9. Pełny kod źródłowy funkcji esp_mesh_rx_task()

void esp_mesh_rx_task(void *arg)
{
mesh_addr_t from;
mesh_data_t data;

esp_err_t status;
int flag = 0;

data.data = rx_buf;
data.size = MESH_BUF_SIZE;

while (true) {

status = esp_mesh_recv(&from, &data, portMAX_DELAY, &flag, NULL, 0);
if (status != ESP_OK || !data.size) {

ESP_LOGW(TAG, "[RX TASK] message receive error: heap:%d [err:0x%x, size:%d]",
esp_get_free_heap_size(), status, data.size);
continue;
}

ESP_LOGI(TAG, "[RX TASK] Received '%s' message from node ("MACSTR")", rx_buf, MAC2STR(from.addr));
}

vTaskDelete(NULL);
}

Po kompilacji i wgraniu oprogramowania za pomocą poleceń:

idf.py build
idf.py p <identyfikator_urządzenia> flash (np. idf.py p /dev/ttyUSB0 flash)

Sprawdźmy następnie poprawność komunikacji w ramach tworzonej sieci. Zaczynamy od włączenia zasilania pierwszego z węzłów, któremu z powodu braku innych urządzeń automatycznie zostaje przypisana rola węzła ROOT:

ESP-MESH: <MESH_EVENT_PARENT_CONNECTED>layer:0>1, parent:53:66:50:76:b2:b0<ROOT>, ID:77:77:77:77:77:77
ESP-MESH: <MESH_EVENT_TODS_REACHABLE>state:0
ESP-MESH: <MESH_EVENT_ROOT_ADDRESS>root address:24:0a:c4:03:ac:85
ESP-MESH: <IP_EVENT_STA_GOT_IP> IP: 192.168.0.17

Zgodnie z implementacją, detekcja zdarzenia MESH_EVENT_PARENT_CONNECTED powiązana jest z uruchomieniem zadań esp_mesh_tx_task() oraz esp_mesh_rx_task():

ESP-MESH: [TXTASK][1/1] Sending ‘Hello World’ message to child node (24:0a:c4:03:ac:84)
ESP-MESH: [RXTASK] Received ‘Hello World!’ message from node (24:0a:c4:03:ac:84)

Ponieważ w tablicy tras połączeń węzła przechowywany jest również jego własny adres, urządzenie ROOT cyklicznie wysyła wiadomość „Hello World!” do samego siebie. Dołączenie kolejnych dwóch węzłów w sieci automatycznie zwiększa rozmiar tablicy routingu po stronie węzła ROOT i zwiększa liczbę iteracji pętli for():

ESP-MESH: [TXTASK][1/1] Sending ‘Hello World’ message to child node (24:0a:c4:03:ac:84)
ESP-MESH: [RXTASK] Received ‘Hello World!’ message from node (24:0a:c4:03:ac:84)
...
ESP-MESH: <MESH_EVENT_ROUTING_TABLE_ADD>add 1, new:2
ESP-MESH: <MESH_EVENT_CHILD_CONNECTED>aid:1, bc:dd:c2:c1:d0:d4
ESP-MESH: [TXTASK][1/2] Sending ‘Hello World’ message to child node (24:0a:c4:03:ac:84)
ESP-MESH: [RXTASK] Received ‘Hello World!’ message from node (24:0a:c4:03:ac:84)
ESP-MESH: [TXTASK][2/2] Sending ‘Hello World’ message to child node (bc:dd:c2:c1:d0:d4)
...
ESP-MESH: <MESH_EVENT_ROUTING_TABLE_ADD>add 1, new:3
ESP-MESH: <MESH_EVENT_CHILD_CONNECTED>aid:2, bc:dd:c2:c2:ca:60
ESP-MESH: [TXTASK][1/3] Sending ‘Hello World’ message to child node (24:0a:c4:03:ac:84)
ESP-MESH: [RXTASK] Received ‘Hello World!’ message from node (24:0a:c4:03:ac:84)
ESP-MESH: [TXTASK][2/3] Sending ‘Hello World’ message to child node (bc:dd:c2:c1:d0:d4)
ESP-MESH: [TXTASK][3/3] Sending ‘Hello World’ message to child node (bc:dd:c2:c2:ca:60)

Pokazana powyżej przykładowa realizacja stanowi jedynie niewielki fragment możliwości dostarczanych przez protokół ESP-MESH. Bardziej szczegółowe opisy interfejsu API oraz funkcjonalności protokołu zostały udostępnione przez firmę Espressif pod następującymi adresami: https://bit.ly/339ocYGhttps://bit.ly/3n2jVxR.

Łukasz Skalski
contact@lukasz-skalski.com

Artykuł ukazał się w
Elektronika Praktyczna
październik 2020
DO POBRANIA
Pobierz PDF Download icon
Materiały dodatkowe

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik kwiecień 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio marzec - kwiecień 2024

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje kwiecień 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna kwiecień 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich maj 2024

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów