Zostań w domu, zamów taniej!
Nie wychodź z domu i zamów online swoje ulubione pisma 20% taniej. Skorzystaj z kodu rabatowego: czytajwdomu

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

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

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 poprzedniej części artykułu omówiliśmy topologie sieci bezprzewodowych oraz dokładną architekturę sieci ESP-MESH. W tej części zaczniemy od strony praktycznej.

Konfiguracja środowiska esp-idf

Sprawdźmy zatem w praktyce, jak realizowany jest proces automatycznej konfiguracji sieci oraz wymiany danych pomiędzy węzłami w ramach protokołu ESP-MESH. Od strony sprzętowej wykorzystamy do tego celu zestawy deweloperskie ESP32-DEVKITC-32 [4], wyposażone w moduł ESP-WROOM-32D oraz konwerter USB-UART (bazujący na układzie Silabs CP2102). Całość zmontowana jest na płytce PCB z rastrem wyprowadzeń 2,54 mm (jak na fotografii tytułowej).

Działania rozpoczynamy od pobrania i konfiguracji środowiska programistycznego dla układów ESP32 – esp-idf (Espressif IoT Development Framework). Dla użytkowników systemu Windows przygotowano wygodny w użyciu instalator esp-idftools-setup-2.3.exe (zawierający m.in. kompilator, biblioteki oraz środowisko OpenOCD). Kompletna instrukcja dla systemów Windows (wraz z instalatorem) została udostępniona pod adresem: https://bit.ly/31AheLx.

W przypadku systemu Linux i dystrybucji Debian/Ubuntu, przed procesem pobrania środowiska esp-idf, niezbędne jest samodzielne doinstalowanie wymaganych pakietów:

sudo apt-get install git wget libncurses-dev flex \ bison gperf python python-pip python-setuptools \cmake ninja-build ccache libffi-dev libssl-dev

Niektóre wersje dystrybucji Debian/Ubuntu wciąż używają interpretera języka Python w wersji 2 (2.7) – środowisko esp-idf wymaga interpretera w wersji 3, zatem doinstalujmy brakujące pakiety:

sudo apt-get install python3 python3-pip \python3-setuptools

Ustawmy domyślny interpreter języka:

sudo update-alternatives --install /usr/bin/python \ python /usr/bin/python3 10

Następnie, w lokalizacji $HOME, utwórzmy katalog esp, w którym umieszczone zostanie kompletne środowisko esp-idf:

cd $HOME
mkdir esp

W kolejnym kroku pobieramy esp-idf (wybierając ostatnią stabilną wersję oznaczoną numerem v4.0.1) za pomocą narzędzia git:

cd ~/esp
git clone -b v4.0.1 --recursive https://github.com/\espressif/esp-idf.git

Instalacja środowiska w systemie Linux (dla systemu operacyjnego Windows przygotowano analogicznie pliki wsadowe BAT):

cd ~/esp/esp-idf
./install.sh

Nasz system jest już wyposażony w komplet niezbędnych narzędzi do poprawnej kompilacji kodu. Zgodnie ze wskazówkami wyświetlonymi przez program instalacyjny pozostaje nam jedynie ustawienie odpowiednich ścieżek dostępu do bibliotek i kompilatora. Najwygodniej zrobić to poprzez proste wywołanie dostarczonych wraz ze środowiskiem skryptów:

. $HOME/esp/esp-idf/export.sh

Możemy ustawić stały alias, wpisując:

alias get_idf=’. $HOME/esp/esp-idf/export.sh’

ESP-MESH – aplikacja użytkownika

Kompilacja programu z wykorzystaniem środowiska esp-idf wymaga uprzedniego przygotowania odpowiednich plików z regułami kompilacji (pliki CMake/Makefile). Aby pominąć ten etap przygotowań, wykorzystamy przygotowany przez firmę Espressif, gotowy szablon projektu, który pobierzemy za pomocą narzędzia git:

git clone https://github.com/espressif/esp-idf-\template.git

Edycję pliku esp-idftemplate/main/main.c rozpoczynamy od usunięcia domyślnej zawartości funkcji app_main(), a następnie dodania linii inicjalizującej stos sieciowy LwIP. Teoretycznie inicjalizacji stosu wymaga tylko urządzenie pełniące funkcję węzła ROOT, które jest odpowiedzialne za komunikację z bramą dostępową. Ponieważ w realizowanym przykładzie funkcja węzła ROOT nie będzie przypisana na stałe, a każdy z węzłów – w drodze głosowania – może zostać wybrany na węzeł główny, inicjalizacja jest niezbędna dla wszystkich urządzeń:

tcpip_adapter_init();
tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP);
tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);

W następnym kroku tworzymy domyślną pętlę zdarzeń:

esp_event_loop_create_default();

Inicjalizujemy interfejs Wi-Fi oraz wskazujemy funkcję obsługi zdarzeń dla protokołu IP – ip_event_handler(), która poinformuje węzeł ROOT o przydzieleniu adresu IP (zdarzenie IP_EVENT_STA_GOT_IP):

wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&config);
esp_event_handler_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&ip_event_handler,
NULL
);
esp_wifi_set_storage(WIFI_STORAGE_FLASH);
esp_wifi_start();

Poza ciałem funkcji app_main(), umieśćmy zatem również prostą funkcję obsługi zdarzeń IP – ip_event_handler(), która poprzez komunikat w konsoli poinformuje nas o detekcji zdarzenia IP_EVENT_STA_GOT_IP:

void ip_event_handler(
void *arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data
){
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(
TAG,
"<IP_EVENT_STA_GOT_IP> IP: %s",
ip4addr_ntoa(&event→ip_info.ip)
);
}

Po zainicjalizowaniu stosu LwIP oraz interfejsu Wi-Fi możemy przystąpić do właściwej konfiguracji sieci. Inicjalizację sieci rozpoczynamy od wywołania esp_mesh_init(), które sprawdza stan sieci Wi-Fi oraz przypisuje domyślnie ustawienia. O aktualnym stanie formowania sieci oraz zmianach w jej konfiguracji użytkownik informowany jest poprzez szereg zdarzeń mesh_event_id_t. Do implementacji funkcji obsługi tych zdarzeń oraz ich znaczenia przejdziemy w dalszej części artykułu. Na obecnym etapie wskażmy wyłącznie, jaka funkcja będzie odpowiedzialna za ich obsługę (w omawianym przypadku będzie to funkcja mesh_event_handler()):

esp_event_handler_register(
MESH_EVENT,
ESP_EVENT_ANY_ID,
&mesh_event_handler,
NULL
);

Kolejnym etapem jest konfiguracja parametrów sieci, tj. ustawienie unikalnego ID sieci oraz hasła, ustawienie numeru kanału, na którym będzie realizowana komunikacja i określenie nazwy SSID, hasła oraz metod szyfrowania dla bramy dostępowej/routera. Całość konfiguracji realizowana jest poprzez strukturę mesh_cfg_t oraz podstruktury mesh_addr_t, mesh_router_t i mesg_ap_cfg_t.

Edycję kodu rozpoczynamy od inicjalizacji struktury mesh_cfg_t wartościami domyślnymi:

mesh_cfg_t cfg = MESH_INIT_CONFIG_DEFAULT();

Następnie ustawiamy unikalny numer identyfikacyjny sieci (wspólny dla wszystkich węzłów w ramach jednej sieci):

static const uint8_t MESH_ID[6] = {0x77, 0x77, 0x77, 0x77, 0x77, 0x77};
memcpy((uint8_t *) &cfg.mesh_id, MESH_ID, 6);

W kolejnym kroku konfigurujemy numer kanału, na którym będzie realizowana komunikacja (kanały 0…14), oraz ustawiamy SSID i hasło dla bramy dostępowej/routera:

#define MESH_CHANNEL 7
#define MESH_ROUTER_SSID "network-ssid"
#define MESH_ROUTER_PASSWD "network-password"

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));

Następnie konfigurujemy hasło oraz szyfrowanie dla sieci Mesh oraz określamy maksymalną liczbę połączeń w ramach softAP:

#define MESH_AP_AUTHMODE WIFI_AUTH_WPA2_PSK
#define MESH_AP_PASSWD "Mesh AP Password"
#define MESH_AP_CONNECTIONS 6

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));

Ostatnim etapem konfiguracji jest ograniczenie liczby warstw tworzonej sieci oraz określenie wartości „progu wyborczego” dla procesu automatycznego wyboru węzła ROOT (wartości od 0.0 do 1.0):

#define MESH_MAX_LAYER 6

esp_mesh_set_max_layer(MESH_MAX_LAYER));
esp_mesh_set_vote_percentage(1));

Tak przygotowaną strukturę mesh_cfg_t przekazujemy do funkcji esp_mesh_set_config(), co pozwala nam ostatecznie uruchomić sieć za pomocą wywołania esp_mesh_start():

esp_mesh_set_config(&cfg);
esp_mesh_start();

Pełny kod funkcji app_main(), wraz z dodatkowymi makrami ESP_ERROR_CHECK(), umożliwiającymi śledzenie poprawności wykonania poszczególnych etapów inicjalizacji i konfiguracji sieci, pokazano na listingu 1.

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");
}

Do przeprowadzenia pierwszej, poprawnej kompilacji programu niezbędne jest zdefiniowanie wskazanej uprzednio funkcji obsługi zdarzeń sieci Mesh – mesh_event_handler(). Wybrane zdarzenia, zgłaszane przez sieć, zostały przedstawione w tabeli 1.

Bazując na danych z tabeli 1, zaimplementujmy funkcję obsługi zdarzeń – mesh_event_handler(), która na obecnym etapie realizacji projektu swoją funkcjonalność ograniczy niemal wyłącznie do logowania zdarzeń w konsoli, z wykorzystaniem wywołań ESP_LOGI(). Pełny kod funkcji mesh_event_handler() został pokazany na listingu 2.

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;
}
}

Kompilacja i zaprogramowanie modułu

Dzięki wyposażeniu zestawów ESP32-DEVKITC-32 w konwerter USB-UART oraz układ autoresetu (układ sterowania wyprowadzeniami EN oraz BOOT za pomocą linii RTS i DTR), kompilacja i wgranie programu zostaje ograniczone do dwóch komend:

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

Do monitorowania pracy poszczególnych węzłów możemy wykorzystać polecenie idf.py monitor lub dowolny inny emulator terminala, np. picocom:

idf.py -p /dev/ttyUSB0 monitor
picocom /dev/ttyUSB1 -b 115200

Analizując komunikaty wyświetlane poprzez interfejs szeregowy, sprawdźmy poprawność formowania sieci. Rozpoczynamy od podłączenia pierwszego z węzłów. Z szeregu komunikatów, dotyczących m.in. konfiguracji interfejsu Wi-Fi, wybierzmy te związane z protokołem ESP-MESH.

Pierwsze z komunikatów dotyczą uruchomienia sieci z wybranym numerem identyfikacyjnym:

ESP-MESH: <MESH_EVENT_MESH_STARTED>ID:77:77:77:77:77:77
ESP-MESH: Mesh starts successfully: root not fixed

Następnie, po jednogłośnym „procesie wyborczym” (dostępny jest tylko jeden węzeł w systemie), urządzenie ROOT przystępuje do próby połączenia z wybranym punktem dostępowym/routerem. Poprawne połączenie spowoduje utworzenie pierwszej warstwy sieci – warstwę 0 stanowi punkt dostępowy o adresie 53:66:50:76:b2:b0; oraz warstwę drugą – węzeł ROOT o adresie 24:0a:c4:03:ac:85, któremu został przydzielony adres IP 192.168.0.17:

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

Włączenie zasilania drugiego z węzłów (o adresie sprzętowym bc:dd:c2:c1:d0:d4) skutkuje dołączeniem do węzła ROOT nowego węzła child i dodaniem nowego urządzenia do tablicy routingu. Po stronie urządzenia ROOT zostaną wówczas wyświetlone następujące komunikaty:

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

Natomiast w konsoli szeregowej dołączonego węzła znajdziemy:

ESP-MESH: <MESH_EVENT_PARENT_CONNECTED>layer:0-->2, parent:24:0a:c4:03:ac:85<layer2>, 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

Tym samym została poprawnie uformowana druga warstwa sieci. Rodzicem nowego węzła jest węzeł ROOT o adresie 24:0a:c4:03:ac:85.

W ostatnim kroku wykonamy test awarii węzła. Po wyłączeniu zasilania urządzenia ROOT jedyny węzeł drugiej warstwy sieci dokona detekcji zdarzenia i podejmie próbę ponownego połączenia z węzłem rodzicem. Każda nieudana próba zostaje zakończona komunikatem:

ESP-MESH: <MESH_EVENT_PARENT_DISCONNECTED>reason:2

Przy utrzymującym się stanie braku połączenia następuje automatyczne przełączenie węzła ROOT:

ESP-MESH: <MESH_EVENT_PARENT_CONNECTED>layer:2-->1, parent:54:67:51:77:b3: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:bc:dd:c2:c1:d0:d5
ESP-MESH: <IP_EVENT_STA_GOT_IP> IP: 192.168.0.38

W kolejnej części

Następnym etapem będzie rozbudowanie kodu programu o komunikację na poziomie węzeł-węzeł oraz węzeł-root.

Łukasz Skalski
contact@lukasz-skalski.com

Przypisy:
[4] kamami.pl, 22.07.2020, https://bit.ly/2D2n1Qr

Bibliografia:
https://bit.ly/3jO9Zqe
https://bit.ly/2XbOy8P
https://bit.ly/33boD54

DO POBRANIA
Materiały dodatkowe
Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik listopad 2020

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio grudzień 2020

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka Podzespoły Aplikacje listopad 2020

Automatyka Podzespoły Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna listopad 2020

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Praktyczny Kurs Elektroniki 2018

Praktyczny Kurs Elektroniki

24 pasjonujące projekty elektroniczne

Elektronika dla Wszystkich listopad 2020

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów