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

Artykuł ukazał się w
Elektronika Praktyczna
wrzesień 2020
DO POBRANIA
Pobierz PDF Download icon
Materiały dodatkowe

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik kwiecień 2021

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio kwiecień - maj 2021

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka Podzespoły Aplikacje kwiecień 2021

Automatyka Podzespoły Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna kwiecień 2021

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich maj 2021

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów