Initial commit — firmware WireClaw ESP32-S3 fonctionnel
- Heartbeat MAVLink v1 toutes les 1s (broadcast 192.168.4.255:14553) - Relais WiFi <-> UART (Jetson <-> Pi Zero) sur ports 14550-14553 - LED RGB WS2812 GPIO48 : rouge pulsé (attente), vert dim (idle), flash vert (heartbeat) - MODE_AP=true : hotspot WireClaw-CAM / false : connexion Jetson - Fix: retrait ARDUINO_USB_CDC_ON_BOOT (carte Freenove utilise CH343/UART0)
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
include/credentials.h
|
||||||
|
|
||||||
|
# Fichiers temporaires
|
||||||
|
src/main.cpp.old
|
||||||
95
CLAUDE.md
Normal file
95
CLAUDE.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# WireClaw ESP32-S3 — Contexte projet
|
||||||
|
|
||||||
|
## Carte
|
||||||
|
- **Freenove ESP32-S3-WROOM** (8MB Flash, PSRAM OPI)
|
||||||
|
- LED RGB WS2812 sur **GPIO48** (adressable, lib FastLED)
|
||||||
|
- UART2 : RX=GPIO16, TX=GPIO17 (vers Pi Zero, 57600 baud)
|
||||||
|
- Flash mode : QIO 80MHz (`board_build.flash_mode = qio`)
|
||||||
|
|
||||||
|
## Rôle de ce firmware
|
||||||
|
Nœud de communication drone WireClaw :
|
||||||
|
- Envoie un **HEARTBEAT MAVLink v1** toutes les 1 seconde
|
||||||
|
- Relaie les trames MAVLink **WiFi ↔ UART** (Jetson ↔ Pi Zero)
|
||||||
|
- Indique l'état par la **LED RGB** (voir section LED)
|
||||||
|
|
||||||
|
## Basculer entre mode AP et mode Jetson
|
||||||
|
En haut de `src/main.cpp`, une seule constante suffit :
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define MODE_AP true // true = ESP32 crée son propre hotspot (test sans Jetson)
|
||||||
|
// false = ESP32 se connecte au hotspot WireClaw du Jetson
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`MODE_AP true`** : hotspot `WireClaw-CAM`, IP fixe `192.168.4.1`, heartbeat broadcast `192.168.4.255`
|
||||||
|
- **`MODE_AP false`** : connexion à `WIFI_SSID`, heartbeat envoyé à `JETSON_IP:14553`
|
||||||
|
|
||||||
|
Les credentials sont dans `include/credentials.h` :
|
||||||
|
```cpp
|
||||||
|
// Mode AP
|
||||||
|
#define AP_SSID "WireClaw-CAM"
|
||||||
|
#define AP_PASSWORD ""
|
||||||
|
|
||||||
|
// Mode STA (Jetson)
|
||||||
|
#define WIFI_SSID "WireClaw"
|
||||||
|
#define WIFI_PASSWORD ""
|
||||||
|
#define JETSON_IP "10.42.0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## LED RGB (GPIO48, WS2812, FastLED)
|
||||||
|
| État | Couleur | Pattern |
|
||||||
|
|---|---|---|
|
||||||
|
| Attente WiFi / AP init | Rouge | Pulsé fade in/out |
|
||||||
|
| Connecté, idle | Vert | Très dim fixe |
|
||||||
|
| Heartbeat envoyé | Vert | Flash vif 80ms — 1 Hz |
|
||||||
|
|
||||||
|
## MAVLink Heartbeat
|
||||||
|
- Implémenté sans lib externe dans `include/mavlink_heartbeat.h`
|
||||||
|
- MAVLink v1, msg_id=0, SYS_ID=255 (GCS), CRC-16/MCRF4XX
|
||||||
|
- Séquence incrémentale `mavSeq` (uint8_t, overflow auto)
|
||||||
|
- Log hex dans le monitor série à chaque envoi
|
||||||
|
|
||||||
|
## Ports UDP
|
||||||
|
| Constante | Port | Usage |
|
||||||
|
|---|---|---|
|
||||||
|
| `LOCAL_PORT` | 14550 | Écoute ESP32 (commandes depuis Jetson) |
|
||||||
|
| `JETSON_PORT_MAVSDK` | 14551 | MAVLink vers Jetson (MAVLink SDK) |
|
||||||
|
| `JETSON_PORT_PYMAV` | 14552 | MAVLink vers Jetson (pymavlink) |
|
||||||
|
| `JETSON_PORT_HB` | 14553 | Heartbeat vers Jetson |
|
||||||
|
|
||||||
|
## Architecture réseau
|
||||||
|
|
||||||
|
### Mode AP (test)
|
||||||
|
```
|
||||||
|
PC/smartphone → WiFi "WireClaw-CAM" → ESP32-S3 (192.168.4.1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mode STA (production)
|
||||||
|
```
|
||||||
|
Jetson (10.42.0.1) ←→ WiFi "WireClaw" ←→ ESP32-S3 (10.42.0.77)
|
||||||
|
↕ UART 57600
|
||||||
|
Pi Zero (caméra CSI)
|
||||||
|
```
|
||||||
|
|
||||||
|
## platformio.ini points importants
|
||||||
|
- `board_build.flash_mode = qio` — obligatoire pour cette carte
|
||||||
|
- `board_build.f_flash = 80000000L` — 80MHz
|
||||||
|
- Ne PAS utiliser `-DARDUINO_USB_CDC_ON_BOOT=1` — la carte Freenove passe par un chip CH343 (UART0→COM11), pas par l'USB natif ESP32. Ce flag redirige Serial vers l'USB OTG qui n'est pas connecté → app muette.
|
||||||
|
- Ne pas utiliser `board_build.arduino.memory_type = qio_opi` — cause boot loop
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
```
|
||||||
|
wireclaw-esp32/
|
||||||
|
├── CLAUDE.md ← ce fichier
|
||||||
|
├── platformio.ini
|
||||||
|
├── include/
|
||||||
|
│ ├── credentials.h ← SSID, passwords, IP Jetson
|
||||||
|
│ └── mavlink_heartbeat.h ← construction trame MAVLink v1
|
||||||
|
└── src/
|
||||||
|
└── main.cpp ← firmware principal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Travail futur
|
||||||
|
- [ ] IP statique ESP32 via `WiFi.config()` en mode STA
|
||||||
|
- [ ] Valider réception heartbeat côté Jetson (pymavlink)
|
||||||
|
- [ ] Câblage UART ESP32 ↔ Pi Zero (GPIO16/17 ↔ GPIO14/15)
|
||||||
|
- [ ] Remplacer broadcast par IP Jetson fixe une fois en production
|
||||||
37
include/README
Normal file
37
include/README
Normal 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
|
||||||
73
include/mavlink_heartbeat.h
Normal file
73
include/mavlink_heartbeat.h
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// MAVLink v1 — HEARTBEAT (msg_id = 0)
|
||||||
|
// Trame complète : 17 octets
|
||||||
|
//
|
||||||
|
// Byte Field
|
||||||
|
// 0 STX 0xFE (MAVLink v1 magic)
|
||||||
|
// 1 LEN 9 (payload = 9 octets)
|
||||||
|
// 2 SEQ séquence incrémentale
|
||||||
|
// 3 SYS_ID system id (on se déclare comme GCS = 255)
|
||||||
|
// 4 COMP_ID component id (GCS = 0)
|
||||||
|
// 5 MSG_ID 0 (HEARTBEAT)
|
||||||
|
// 6-14 PAYLOAD 9 octets (voir ci-dessous)
|
||||||
|
// 15-16 CRC CRC-16/MCRF4XX avec CRC_EXTRA = 50
|
||||||
|
//
|
||||||
|
// Payload HEARTBEAT (9 octets, little-endian) :
|
||||||
|
// [0-3] custom_mode uint32 0x00000000
|
||||||
|
// [4] type uint8 MAV_TYPE_GCS = 6
|
||||||
|
// [5] autopilot uint8 MAV_AUTOPILOT_INVALID = 8
|
||||||
|
// [6] base_mode uint8 0
|
||||||
|
// [7] system_status uint8 MAV_STATE_ACTIVE = 4
|
||||||
|
// [8] mavlink_version uint8 3
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// CRC-16/MCRF4XX (algorithme MAVLink)
|
||||||
|
static inline void mavCrcAccumulate(uint8_t data, uint16_t &crc) {
|
||||||
|
uint8_t tmp = data ^ (uint8_t)(crc & 0xFF);
|
||||||
|
tmp ^= (tmp << 4);
|
||||||
|
crc = (crc >> 8) ^ ((uint16_t)tmp << 8) ^ ((uint16_t)tmp << 3) ^ ((uint16_t)tmp >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construit un paquet HEARTBEAT MAVLink v1 dans buf (doit faire >= 17 octets)
|
||||||
|
// Retourne la taille du paquet (toujours 17)
|
||||||
|
static inline uint8_t mavlinkBuildHeartbeat(uint8_t *buf, uint8_t &seq) {
|
||||||
|
const uint8_t PAYLOAD_LEN = 9;
|
||||||
|
const uint8_t SYS_ID = 255; // GCS
|
||||||
|
const uint8_t COMP_ID = 0; // GCS component
|
||||||
|
const uint8_t MSG_ID = 0; // HEARTBEAT
|
||||||
|
const uint8_t CRC_EXTRA = 50; // valeur fixe MAVLink pour msg #0
|
||||||
|
|
||||||
|
// Header
|
||||||
|
buf[0] = 0xFE; // STX
|
||||||
|
buf[1] = PAYLOAD_LEN;
|
||||||
|
buf[2] = seq++; // numéro de séquence (overflow géré par uint8_t)
|
||||||
|
buf[3] = SYS_ID;
|
||||||
|
buf[4] = COMP_ID;
|
||||||
|
buf[5] = MSG_ID;
|
||||||
|
|
||||||
|
// Payload (little-endian)
|
||||||
|
buf[6] = 0x00; // custom_mode [0]
|
||||||
|
buf[7] = 0x00; // custom_mode [1]
|
||||||
|
buf[8] = 0x00; // custom_mode [2]
|
||||||
|
buf[9] = 0x00; // custom_mode [3]
|
||||||
|
buf[10] = 6; // type = MAV_TYPE_GCS
|
||||||
|
buf[11] = 8; // autopilot = MAV_AUTOPILOT_INVALID
|
||||||
|
buf[12] = 0; // base_mode
|
||||||
|
buf[13] = 4; // system_status = MAV_STATE_ACTIVE
|
||||||
|
buf[14] = 3; // mavlink_version
|
||||||
|
|
||||||
|
// CRC sur header (sans STX) + payload + CRC_EXTRA
|
||||||
|
uint16_t crc = 0xFFFF;
|
||||||
|
for (uint8_t i = 1; i <= 5 + PAYLOAD_LEN; i++) {
|
||||||
|
mavCrcAccumulate(buf[i], crc);
|
||||||
|
}
|
||||||
|
mavCrcAccumulate(CRC_EXTRA, crc);
|
||||||
|
|
||||||
|
buf[15] = crc & 0xFF; // CRC low byte
|
||||||
|
buf[16] = (crc >> 8) & 0xFF; // CRC high byte
|
||||||
|
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[env:freenove_esp32s3]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
board_build.f_flash = 80000000L
|
||||||
|
board_build.partitions = default_8MB.csv
|
||||||
|
board_upload.flash_size = 8MB
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
fastled/FastLED @ ^3.6.0
|
||||||
|
|
||||||
|
monitor_speed = 115200
|
||||||
|
upload_speed = 460800
|
||||||
192
src/main.cpp
Normal file
192
src/main.cpp
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiUDP.h>
|
||||||
|
#include <FastLED.h>
|
||||||
|
#include "credentials.h"
|
||||||
|
#include "mavlink_heartbeat.h"
|
||||||
|
|
||||||
|
// ── Mode : true = ESP32 crée son propre hotspot false = connexion au Jetson ──
|
||||||
|
#define MODE_AP true
|
||||||
|
|
||||||
|
// ── Ports ────────────────────────────────────────────────
|
||||||
|
const uint16_t LOCAL_PORT = 14550;
|
||||||
|
const uint16_t JETSON_PORT_MAVSDK = 14551;
|
||||||
|
const uint16_t JETSON_PORT_PYMAV = 14552;
|
||||||
|
const uint16_t JETSON_PORT_HB = 14553;
|
||||||
|
|
||||||
|
// ── UART vers Pi Zero ─────────────────────────────────────
|
||||||
|
#define UART_BAUD 57600
|
||||||
|
#define UART_RX_PIN 16
|
||||||
|
#define UART_TX_PIN 17
|
||||||
|
|
||||||
|
// ── LED RGB WS2812 (GPIO48) ───────────────────────────────
|
||||||
|
#define LED_PIN 48
|
||||||
|
#define NUM_LEDS 1
|
||||||
|
CRGB leds[NUM_LEDS];
|
||||||
|
|
||||||
|
typedef enum { LED_WAITING_WIFI, LED_HEARTBEAT, LED_IDLE } LedState;
|
||||||
|
LedState currentLedState = LED_WAITING_WIFI;
|
||||||
|
unsigned long ledStateStart = 0;
|
||||||
|
|
||||||
|
// ── Variables globales ────────────────────────────────────
|
||||||
|
WiFiUDP udp;
|
||||||
|
HardwareSerial MavSerial(2);
|
||||||
|
bool wifiConnected = false;
|
||||||
|
unsigned long lastHeartbeat = 0;
|
||||||
|
uint8_t mavSeq = 0;
|
||||||
|
|
||||||
|
#if MODE_AP
|
||||||
|
const char* BROADCAST_IP = "192.168.4.255";
|
||||||
|
#else
|
||||||
|
const char* BROADCAST_IP = JETSON_IP;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ── LED non bloquante ─────────────────────────────────────
|
||||||
|
void updateLED() {
|
||||||
|
unsigned long elapsed = millis() - ledStateStart;
|
||||||
|
switch (currentLedState) {
|
||||||
|
case LED_WAITING_WIFI:
|
||||||
|
leds[0] = CRGB((uint8_t)(128 + 127 * sin(elapsed * 0.00628f)), 0, 0);
|
||||||
|
break;
|
||||||
|
case LED_HEARTBEAT:
|
||||||
|
if (elapsed < 80) {
|
||||||
|
leds[0] = CRGB(0, 255, 0);
|
||||||
|
} else {
|
||||||
|
currentLedState = LED_IDLE;
|
||||||
|
ledStateStart = millis();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LED_IDLE:
|
||||||
|
leds[0] = CRGB(0, 8, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
FastLED.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLedState(LedState s) {
|
||||||
|
currentLedState = s;
|
||||||
|
ledStateStart = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Heartbeat MAVLink ─────────────────────────────────────
|
||||||
|
void sendMavlinkHeartbeat() {
|
||||||
|
uint8_t buf[17];
|
||||||
|
uint8_t len = mavlinkBuildHeartbeat(buf, mavSeq);
|
||||||
|
|
||||||
|
udp.beginPacket(BROADCAST_IP, JETSON_PORT_HB);
|
||||||
|
udp.write(buf, len);
|
||||||
|
udp.endPacket();
|
||||||
|
|
||||||
|
setLedState(LED_HEARTBEAT);
|
||||||
|
|
||||||
|
Serial.printf("[HB] seq=%d [", mavSeq - 1);
|
||||||
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
|
Serial.printf("%02X", buf[i]);
|
||||||
|
if (i < len - 1) Serial.print(" ");
|
||||||
|
}
|
||||||
|
Serial.println("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reconnexion WiFi (mode STA uniquement) ────────────────
|
||||||
|
void checkWiFi() {
|
||||||
|
#if !MODE_AP
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
if (!wifiConnected) {
|
||||||
|
wifiConnected = true;
|
||||||
|
WiFi.setSleep(false);
|
||||||
|
udp.begin(LOCAL_PORT);
|
||||||
|
setLedState(LED_IDLE);
|
||||||
|
Serial.print("WiFi connecte. IP: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (wifiConnected) {
|
||||||
|
wifiConnected = false;
|
||||||
|
setLedState(LED_WAITING_WIFI);
|
||||||
|
Serial.println("WiFi perdu, reconnexion...");
|
||||||
|
}
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Setup ─────────────────────────────────────────────────
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
|
||||||
|
FastLED.setBrightness(80);
|
||||||
|
setLedState(LED_WAITING_WIFI);
|
||||||
|
|
||||||
|
Serial.println("\n=== WireClaw ESP32-S3 ===");
|
||||||
|
#if MODE_AP
|
||||||
|
Serial.println("Mode: AP (hotspot autonome)");
|
||||||
|
#else
|
||||||
|
Serial.println("Mode: STA (connexion Jetson)");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MavSerial.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);
|
||||||
|
Serial.printf("UART2: %d baud (RX=%d TX=%d)\n", UART_BAUD, UART_RX_PIN, UART_TX_PIN);
|
||||||
|
|
||||||
|
#if MODE_AP
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP(AP_SSID, AP_PASSWORD);
|
||||||
|
delay(500);
|
||||||
|
wifiConnected = true;
|
||||||
|
udp.begin(LOCAL_PORT);
|
||||||
|
setLedState(LED_IDLE);
|
||||||
|
Serial.printf("Hotspot: %s\n", AP_SSID);
|
||||||
|
Serial.printf("IP : %s\n", WiFi.softAPIP().toString().c_str());
|
||||||
|
#else
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.setSleep(false);
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
Serial.printf("Connexion a %s...\n", WIFI_SSID);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Serial.printf("UDP ecoute: port %d\n", LOCAL_PORT);
|
||||||
|
delay(500);
|
||||||
|
if (wifiConnected) sendMavlinkHeartbeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Loop ──────────────────────────────────────────────────
|
||||||
|
void loop() {
|
||||||
|
updateLED();
|
||||||
|
checkWiFi();
|
||||||
|
|
||||||
|
if (wifiConnected && millis() - lastHeartbeat > 1000) {
|
||||||
|
lastHeartbeat = millis();
|
||||||
|
sendMavlinkHeartbeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wifiConnected) return;
|
||||||
|
|
||||||
|
// WiFi → UART (Jetson → Pi Zero)
|
||||||
|
int packetSize = udp.parsePacket();
|
||||||
|
if (packetSize > 0) {
|
||||||
|
uint8_t buf[512];
|
||||||
|
int len = udp.read(buf, sizeof(buf));
|
||||||
|
if (len > 0) {
|
||||||
|
MavSerial.write(buf, len);
|
||||||
|
Serial.printf("[WiFi->UART] %d bytes\n", len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART → WiFi (Pi Zero → Jetson)
|
||||||
|
if (MavSerial.available()) {
|
||||||
|
uint8_t buf[512];
|
||||||
|
int len = 0;
|
||||||
|
while (MavSerial.available() && len < (int)sizeof(buf)) {
|
||||||
|
buf[len++] = MavSerial.read();
|
||||||
|
}
|
||||||
|
if (len > 0) {
|
||||||
|
udp.beginPacket(BROADCAST_IP, JETSON_PORT_MAVSDK);
|
||||||
|
udp.write(buf, len);
|
||||||
|
udp.endPacket();
|
||||||
|
udp.beginPacket(BROADCAST_IP, JETSON_PORT_PYMAV);
|
||||||
|
udp.write(buf, len);
|
||||||
|
udp.endPacket();
|
||||||
|
Serial.printf("[UART->WiFi] %d bytes\n", len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user