[sw] ESP-NOW, test dosahu

Odpovědět
Pablo74
Příspěvky: 133
Registrován: 03 lis 2019, 17:00

[sw] ESP-NOW, test dosahu

Příspěvek od Pablo74 » 18 srp 2024, 17:55

Jsem mimo domov, mám omezenou zásobu HW.

Udělal jsem si jednoduchý test dosahu bezdrátové komunikace protokolem ESP-NOW. Teorii vynechám, je dostupná a srozumitelná v textu i videu. Jen zběžně: ESP-NOW je protokol od firmy Espressif (ano, to je ta, co vyrábí ESP8266 a ESP32), běží na frekvenci 2.4 GHz (ale není to WiFi).

Můj HW (dočasně omezené zásoby):
vysílač - Ideaspark 8266 (NodeMCU ESP8266 Development Board with 0.96‘’ OLED Display): https://www.aliexpress.com/item/1005005242283189.html
přijímač: TTGO T-Display: https://www.aliexpress.com/item/33048962331.html

Běžně se ESP-NOW používá tak, že vysílač i přijímač musí znát MAC adresu toho druhého zařízení; existuje i možnost broadcast, kdy vysílač neposílá konkrétnímu zařízení, ale všem. A toho jsem právě využil.

program pro vysílač:

Kód: Vybrat vše

/* 
Ideaspark ESP8266
*/

#include <ESP8266WiFi.h>
#include <espnow.h>
#include <Wire.h>
#include <U8g2lib.h>

// Inicializace U8g2 pro SH1106 128x64 OLED displej
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0,/*clock=*/14,/*data=*/12,U8X8_PIN_NONE);

// Struktura pro odesílaná data
typedef struct struct_message {
    char text[20];
    int number;
} struct_message;

// Vytvoření struktury pro odesílaná data
struct_message myData;

// MAC adresa pro broadcast
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Callback funkce při odeslání dat
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Status posledního paketu: ");
  Serial.println(sendStatus == 0 ? "Doručeno" : "Chyba při doručení");
}

void setup() {
  Serial.begin(115200);
  
  // Inicializace OLED displeje
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.drawStr(0, 10, "Init...");
  u8g2.sendBuffer();
  delay(1500);

  // Inicializace Wi-Fi
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  // Nastavení maximálního výkonu vysílače
  WiFi.setOutputPower(20.5);

  // Nastavení nižší přenosové rychlosti pro lepší dosah
  WiFi.setPhyMode(WIFI_PHY_MODE_11B);

  // Inicializace ESP-NOW
  if (esp_now_init() != 0) {
    Serial.println("ESP-NOW Init error!");
    u8g2.clearBuffer();
    u8g2.drawStr(15, 15, "ESP-NOW ERROR");
    u8g2.sendBuffer();
    delay(1500);
    return;
  }

  // Nastavení ESP-NOW role
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);

  // Registrace callback funkce
  esp_now_register_send_cb(OnDataSent);
  
  // Registrace peer (broadcast)
  esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

  u8g2.clearBuffer();
  u8g2.drawStr(0, 10, "Ready to bradcast");
  u8g2.sendBuffer();
  delay(1500);
}

void loop() {
  // Příprava dat k odeslání
  strcpy(myData.text, "Connection Test");
  static int counter = 0;
  myData.number = counter;
  
  // Odeslání zprávy přes ESP-NOW
  esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));

  // Zobrazení čísla na displeji
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.drawStr(15, 15, "ESP-NOW");
  char buf[20];
  snprintf(buf, sizeof(buf), "Sent: %d", counter);
  u8g2.drawStr(25, 48, buf);
  u8g2.sendBuffer();

  // Inkrementace čítače
  counter = (counter + 1) % 100;

  // Čekání 2.5 sekundy
  delay(2500);
}
program pro přijímač:

Kód: Vybrat vše

#include <esp_now.h>
#include <WiFi.h>
#include <TFT_eSPI.h>

// Inicializace TFT displeje
TFT_eSPI tft = TFT_eSPI();

// Struktura pro přijímaná data
typedef struct struct_message {
    char text[20];
    int number;
} struct_message;

// Vytvoření struktury pro přijímaná data
struct_message myData;

// Proměnné pro ukládání posledních tří čísel
int lastNumbers[3] = {0, 0, 0};

// Callback funkce při přijetí dat
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Text: ");
  Serial.println(myData.text);
  Serial.print("Number: ");
  Serial.println(myData.number);
  
  // Aktualizace posledních tří čísel
  lastNumbers[2] = lastNumbers[1];
  lastNumbers[1] = lastNumbers[0];
  lastNumbers[0] = myData.number;
  
  // Aktualizace displeje
  updateDisplay();
}

void updateDisplay() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  // text
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(3);
  tft.setCursor(40, 60);
  tft.print("> ESP-NOW <");
  
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  // Nejnovější číslo - největší
  tft.setTextSize(5);
  tft.setCursor(80, 90);
  tft.print(lastNumbers[0]);
  
  // Předchozí číslo - střední
  tft.setTextSize(5);
  tft.setCursor(80, 140);
  tft.print(lastNumbers[1]);
  
  // Nejstarší číslo - nejmenší
  tft.setTextSize(5);
  tft.setCursor(160, 140);
  tft.print(lastNumbers[2]);
}

void setup() {
  Serial.begin(115200);
  
  // Inicializace Wi-Fi
  WiFi.mode(WIFI_STA);

  // Inicializace ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Chyba při inicializaci ESP-NOW");
    return;
  }

  // Nastavení kanálu Wi-Fi (musí být stejný jako u vysílače)
  WiFi.channel(1);

  // Registrace callback funkce
  esp_now_register_recv_cb(OnDataRecv);
  
  // Inicializace TFT displeje
  tft.init();
  //tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  
  updateDisplay();
}

void loop() {
  // Hlavní smyčka je prázdná, protože veškerá logika
  // je zpracovávána v callback funkci OnDataRecv
}
Vysílač periodicky vysílá celé číslo, začne od 0 a po čísle 99 se jde znovu od 0. Na přijímači se zobrazují tři poslední skutečně přijatá čísla (ideálně tedy čísla n, n-1, n-2). Je to celkem praktická věc, hned je vidět, kolik zpráv nebylo při testu přijato.

Programy jsou vhodné pro testování dosahu ESP-NOW komunikace, vysílač nechám stacionárně doma a s přijímačem jdu okolo domu. Na displeji vidím tři poslední přijatá čísla, interval mám na vysílači nastavený na dvě a půl sekundy.

Jistě vás teď napadlo, jaký je reálný dosah. Krátká odpověď: otestujte si to ve vašich podmínkách sami. Delší odpověď: protože je přenos přes 2.4 GHz a s malým výkonem, dosah tomu odpovídá. Na volném prostoru jsem se dostal spolehlivě na 40 metrů, záleží ovšem i na tom prostoru jako takovém - mlha nebo mokré listy vzrostlých stromů jsou překážkou. V zástavbě pak záleží na tloušťce stěn, kovových konstrukcích, rušení WiFi a dalších faktorech.

Prostě je potřeba to vyzkoušet na místě.

Budu rád, pokud můj příspěvek někomu pomůže nebo ho inspiruje.

Pablo74
Příspěvky: 133
Registrován: 03 lis 2019, 17:00

Re: [sw] ESP-NOW, test dosahu

Příspěvek od Pablo74 » 07 črc 2025, 13:33

Modifikace použitého HW i SW.

Jako vysílač i přijímač používám TTGO T-Display, kód mi napsala AI Sonnet 4 od Claude.ai, protokol ESP-NOW a přenos broadcast (nepoužívá se tedy MAC adresa).

Na vysílači i přijímači si zvolím jeden ze dvou režimů:
A) vysílač odesílá jedno číslo (čísla od 0 do 99) každých 2.5 sekundy, přijímač zobrazuje tři poslední přijatá čísla,
B) vysílač odesílá každé číslo stokrát (čísla od 0 do 99), přijímač zobrazí tři poslední přijatá rozdílná čísla a jejich počet.

Režim A se hodí pro rychlou představu, kde ještě je příjem spolehlivý a kde už ne, režim B se hodí pro vyhodnocení ztrátových "paketů".

kód pro vysílač:

Kód: Vybrat vše

// (c) 2025 Claude Sonnet AI + Pablo74
// ESP-NOW Vysílač pro TTGO T-Display
// Optimalizován pro maximální vzdálenost

#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include <TFT_eSPI.h>
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI();

// Broadcast MAC adresa (všechny FF)
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Struktura pro data
typedef struct struct_message {
  int value;
} struct_message;

struct_message myData;

// Proměnné pro režimy
int currentMode = 0; // 0 = výběr režimu, 1 = režim A, 2 = režim B
int currentValue = 0;
int sendCount = 0; // Pro režim B - počítadlo pro 100 opakování
unsigned long lastSendTime = 0;
bool dataDelivered = false;

// Callback pro informace o doručení
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  dataDelivered = (status == ESP_NOW_SEND_SUCCESS);
}

void setup() {
  Serial.begin(115200);
  
  // Inicializace displeje
  tft.init();
  tft.setRotation(3); // Landscape
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextSize(2);
  
  // Zobrazení úvodní informace
  tft.setCursor(60, 10);
  tft.println("ESP-NOW VYSILAC");
  tft.setCursor(0, 40);
  tft.println("Zvol rezim vysilani:");
  tft.setCursor(0, 70);
  tft.println("A) 0-99 / 2.5 s");
  tft.setCursor(0, 100);
  tft.println("B) 0-99 100x / 40 ms");
  
  // Nastavení WiFi v station mode
  WiFi.mode(WIFI_STA);
  
  // Nastavení maximálního vysílacího výkonu (20 dBm)
  WiFi.setTxPower(WIFI_POWER_20dBm);
  
  // Nastavení minimální přenosové rychlosti pro maximální dosah
  esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B);
  
  // Inicializace ESP-NOW
  if (esp_now_init() != ESP_OK) {
    tft.setCursor(10, 130);
    tft.setTextColor(TFT_RED);
    tft.println("ESP-NOW init failed");
    return;
  }
  
  // Registrace callback funkce pro odesílání
  esp_now_register_send_cb(OnDataSent);
  
  // Přidání broadcast peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    tft.setCursor(10, 145);
    tft.setTextColor(TFT_RED);
    tft.println("Failed to add peer");
    return;
  }
  
  // Nastavení pinů tlačítek
  pinMode(0, INPUT_PULLUP);  // Boot tlačítko
  pinMode(35, INPUT_PULLUP); // Pravé tlačítko
}

void loop() {
  // Kontrola tlačítek pro výběr režimu
  if (currentMode == 0) {
    if (digitalRead(0) == LOW) {
      currentMode = 1; // Režim A
      startModeA();
      delay(300); // Debounce
    }
    if (digitalRead(35) == LOW) {
      currentMode = 2; // Režim B
      startModeB();
      delay(300); // Debounce
    }
    return;
  }
  
  // Režim A - posílání každých 2.5 sekundy
  if (currentMode == 1) {
    if (millis() - lastSendTime >= 2500) {
      sendData();
      currentValue = (currentValue + 1) % 100;
      lastSendTime = millis();
      updateDisplayModeA();
    }
  }
  
  // Režim B - posílání každých 40ms, 100x stejné číslo
  if (currentMode == 2) {
    if (millis() - lastSendTime >= 40) {
      sendData();
      sendCount++;
      
      if (sendCount >= 100) {
        currentValue = (currentValue + 1) % 100;
        sendCount = 0;
      }
      
      lastSendTime = millis();
      updateDisplayModeB();
    }
  }
}

void sendData() {
  myData.value = currentValue;
  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
}

void startModeA() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(10, 10);
  tft.println("REZIM A");
  tft.setCursor(10, 40);
  tft.println("Posilam cisla 0-99");
  
  currentValue = 0;
  lastSendTime = 0;
}

void startModeB() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(10, 10);
  tft.println("REZIM B");
  tft.setCursor(10, 40);
  tft.println("Kazde cislo 100x");
  
  currentValue = 0;
  sendCount = 0;
  lastSendTime = 0;
}

void updateDisplayModeA() {
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(3);
  tft.setCursor(10, 80);
  tft.printf("Cislo: %2d  ", currentValue);
  
  tft.setTextSize(2);
  tft.setCursor(10, 110);
  tft.printf("Celkem poslano: %d", (currentValue == 0 && millis() > 3000) ? 100 : currentValue + 1);
}

void updateDisplayModeB() {
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(3);
  tft.setCursor(10, 80);
  tft.printf("Cislo: %2d", currentValue);
  
  tft.setTextSize(2);
  tft.setCursor(10, 110);

  tft.fillRect(10, 110, 220, 30, TFT_BLACK);
  tft.printf("Opakovani: %d", sendCount + 1);
}
kód pro přijímač

Kód: Vybrat vše

// (c) 2025 Claude Sonnet AI + Pablo74
// ESP-NOW Přijímač pro TTGO T-Display
// Optimalizován pro maximální vzdálenost

#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include <TFT_eSPI.h>
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI();

// Struktura pro data
typedef struct struct_message {
  int value;
} struct_message;

struct_message incomingData;

// Proměnné pro režimy
int currentMode = 0; // 0 = výběr režimu, 1 = režim A, 2 = režim B

// Režim A - poslední 3 přijatá čísla
int lastThreeA[3] = {-1, -1, -1};

// Režim B - poslední 3 různá čísla a jejich počty
int lastThreeB[3] = {-1, -1, -1};
int countThreeB[3] = {0, 0, 0};

int totalReceived = 0;
unsigned long lastUpdateTime = 0;
bool needsDisplayUpdate = false;

// Proměnné pro thread-safe komunikaci mezi callback a main loop
volatile bool newDataReceived = false;
volatile int receivedValue = -1;

// Callback pro příjem dat
void OnDataRecv(const esp_now_recv_info * mac, const uint8_t *incomingData, int len) {
  if (len == sizeof(struct_message)) {
    struct_message* data = (struct_message*)incomingData;
    receivedValue = data->value;
    newDataReceived = true;
  }
}

void setup() {
  Serial.begin(115200);
  
  // Inicializace displeje
  tft.init();
  tft.setRotation(3); // Landscape
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
  tft.setTextSize(2);
  
  // Zobrazení úvodní informace
  tft.setCursor(45, 10);
  tft.println("ESP-NOW PRIJIMAC");
  tft.setCursor(0, 40);
  tft.println("Zvol rezim prijmu:");
  tft.setCursor(0, 70);
  tft.println("A) Posledni 3 cisla");
  tft.setCursor(0, 100);
  tft.println("B) 3 cisla + pocty");

  // Nastavení WiFi v station mode
  WiFi.mode(WIFI_STA);
  
  // Nastavení maximálního vysílacího výkonu (pro ACK zprávy)
  WiFi.setTxPower(WIFI_POWER_20dBm);
  
  // Nastavení minimální přenosové rychlosti pro maximální dosah
  esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B);
  
  Serial.print("MAC Address: ");
  Serial.println(WiFi.macAddress());
  
  // Inicializace ESP-NOW
  if (esp_now_init() != ESP_OK) {
    tft.setCursor(10, 130);
    tft.setTextColor(TFT_RED);
    tft.println("ESP-NOW init failed");
    return;
  }
  
  // Registrace callback funkce pro příjem
  esp_now_register_recv_cb(OnDataRecv);
  
  // Nastavení pinů tlačítek
  pinMode(0, INPUT_PULLUP);  // Boot tlačítko
  pinMode(35, INPUT_PULLUP); // Pravé tlačítko
}

void loop() {
  // Kontrola tlačítek pro výběr režimu
  if (currentMode == 0) {
    if (digitalRead(0) == LOW) {
      currentMode = 1; // Režim A
      startModeA();
      delay(300); // Debounce
    }
    if (digitalRead(35) == LOW) {
      currentMode = 2; // Režim B
      startModeB();
      delay(300); // Debounce
    }
    return;
  }
  
  // Zpracování přijatých dat (thread-safe)
  if (newDataReceived) {
    int value = receivedValue;
    newDataReceived = false;
    
    totalReceived++;
    needsDisplayUpdate = true;
    
    if (currentMode == 1) {
      // Režim A - prostě přidej do pole
      lastThreeA[2] = lastThreeA[1];
      lastThreeA[1] = lastThreeA[0];
      lastThreeA[0] = value;
    } else if (currentMode == 2) {
      // Režim B - pokud je číslo stejné jako poslední, jen zvyš počítadlo
      if (lastThreeB[0] == value) {
        countThreeB[0]++;
      } else {
        // Nové číslo - posuň pole
        lastThreeB[2] = lastThreeB[1];
        lastThreeB[1] = lastThreeB[0];
        lastThreeB[0] = value;
        
        countThreeB[2] = countThreeB[1];
        countThreeB[1] = countThreeB[0];
        countThreeB[0] = 1;
      }
    }
  }
  
  // Aktualizace displeje pouze při změně dat nebo každých 500ms
  if (needsDisplayUpdate || (millis() - lastUpdateTime >= 500)) {
    if (currentMode == 1) {
      updateDisplayModeA();
    } else if (currentMode == 2) {
      updateDisplayModeB();
    }
    lastUpdateTime = millis();
    needsDisplayUpdate = false;
  }
}

void startModeA() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(10, 10);
  tft.println("REZIM A");
  tft.setCursor(10, 40);
  tft.println("Posledni 3 cisla:");
  
  // Reset dat
  for (int i = 0; i < 3; i++) {
    lastThreeA[i] = -1;
  }
  totalReceived = 0;
  needsDisplayUpdate = true;
}

void startModeB() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(10, 10);
  tft.println("REZIM B");
  tft.setCursor(10, 40);
  tft.println("Posledni 3 cisla:");
  
  // Reset dat
  for (int i = 0; i < 3; i++) {
    lastThreeB[i] = -1;
    countThreeB[i] = 0;
  }
  totalReceived = 0;
  needsDisplayUpdate = true;
}

void updateDisplayModeA() {
  // Vymaž oblast pro čísla
  tft.fillRect(10, 60, 220, 60, TFT_BLACK);
  
  // Zobraz poslední 3 čísla
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(3);
  
  for (int i = 0; i < 3; i++) {
    tft.setCursor(10 + i * 70, 80);
    if (lastThreeA[i] >= 0) {
      tft.printf("%2d", lastThreeA[i]);
    } else {
      tft.print("--");
    }
  }
  
  // Celkový počet
  tft.setTextSize(2);
  tft.setCursor(10, 120);
  tft.printf("Celkem prijato: %d", totalReceived);
}

void updateDisplayModeB() {
  // Vymaž oblast pro čísla a počty
  tft.fillRect(10, 70, 220, 90, TFT_BLACK);
  
  // První řádek - čísla
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(3);
  
  for (int i = 0; i < 3; i++) {
    tft.setCursor(10 + i * 70, 80);
    if (lastThreeB[i] >= 0) {
      tft.printf("%2d", lastThreeB[i]);
    } else {
      tft.print("--");
    }
  }
  
  // Druhý řádek - počty
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(2);
  
  for (int i = 0; i < 3; i++) {
    tft.setCursor(10 + i * 70, 105);
    if (lastThreeB[i] >= 0) {
      tft.printf("(%d)", countThreeB[i]);
    } else {
      tft.print("(--)");
    }
  }
  
  // Celkový počet
  tft.setCursor(10, 120);
  tft.printf("Celkem prijato: %d", totalReceived);
}
Už jsem tu na fóru několikrát udiveně podotkl, na čem všem závisí bezdrátový přenos dat, teď jsem si opět potvrdil, že na stejném místě se dramaticky liší příjem podle toho, jak mám orientovanou (integrovanou) anténu. Pootočením desky o 90 stupňů dosáhnu na okraji dosahu dvou opačných výsledků: příjem je slušný, příjem není žádný,

Uživatelský avatar
kiRRow
Příspěvky: 1317
Registrován: 07 kvě 2019, 07:03
Bydliště: Opava

Re: [sw] ESP-NOW, test dosahu

Příspěvek od kiRRow » 07 črc 2025, 14:34

Tak zrovna tady tomuhle se nadává polarizace antény. Efekt je podobný jako s polarizačními skly, otoč je vůči sobě o 90°a stanou se neprůhledné.

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 1 host