Fix web OTA upload and isolate OTA sessions across firmware and goTool.
Split ESP-NOW into core/master/slave modules, block non-OTA UART traffic during updates, and hold the host serial port exclusively so dashboard polling cannot interleave with firmware uploads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0cbc4d0644
commit
0eea27a876
@ -41,7 +41,7 @@ flowchart TB
|
|||||||
| Rolle | UART | ESP-NOW | Zentrale Datenhaltung |
|
| Rolle | UART | ESP-NOW | Zentrale Datenhaltung |
|
||||||
|-------|------|---------|------------------------|
|
|-------|------|---------|------------------------|
|
||||||
| **Master** | Ja — einziger Befehlseingang von außen | Discover (Broadcast), Unicast zu Slaves | `client_registry.c` |
|
| **Master** | Ja — einziger Befehlseingang von außen | Discover (Broadcast), Unicast zu Slaves | `client_registry.c` |
|
||||||
| **Slave** | Nein | Antwort auf Discover, Heartbeat, Events zum Master | Lokaler Zustand in `esp_now_comm.c` |
|
| **Slave** | Nein | Antwort auf Discover, Heartbeat, Events zum Master | `esp_now_slave.c` |
|
||||||
|
|
||||||
**Einstieg:** `main/powerpod.c` → `app_main()`.
|
**Einstieg:** `main/powerpod.c` → `app_main()`.
|
||||||
|
|
||||||
@ -158,11 +158,26 @@ flowchart LR
|
|||||||
| Protobuf + Framing | `main/uart_proto.c` | `uart_send_uart_message()` |
|
| Protobuf + Framing | `main/uart_proto.c` | `uart_send_uart_message()` |
|
||||||
| XOR-Rahmen | `main/uart.c` | `uart_send_framed()` |
|
| XOR-Rahmen | `main/uart.c` | `uart_send_framed()` |
|
||||||
|
|
||||||
### 4.3 Interner Befehlspost (ohne UART)
|
### 4.3 OTA-Sperre (keine parallelen Befehle)
|
||||||
|
|
||||||
`msg_post(id, data, len)` in `cmd_handler.c` legt dieselbe `generic_msg_t`-Struktur in `cmd_queue` — für zukünftige Firmware-interne Quellen (z. B. ESP-NOW → PC-Pfad). Aktuell ist **UART der einzige Produktiv-Eingang**.
|
Während einer OTA-Session (`ota_uart_is_active()` oder `ota_espnow_distribution_active()`) lehnt der Dispatcher alle UART-Befehle ab **außer**:
|
||||||
|
|
||||||
### 4.4 Bridge-Muster: UART-Befehl → ESP-NOW
|
- `OTA_START`, `OTA_PAYLOAD`, `OTA_END`, `OTA_START_ESPNOW`, `OTA_SLAVE_PROGRESS`
|
||||||
|
|
||||||
|
Implementierung: `ota_session.c`, Prüfung in `vCmdDispatcherTask` vor Handler-Aufruf.
|
||||||
|
|
||||||
|
Auf dem **Slave** verarbeitet `espnow_recv_cb` während `ota_uart_is_active()` nur noch OTA-Nachrichten vom joined Master (kein Discover, Stream, LED, …).
|
||||||
|
|
||||||
|
Auf dem **Master** während ESP-NOW-Verteilung nur noch `ESPNOW_OTA_STATUS` aus dem recv_cb.
|
||||||
|
|
||||||
|
### 4.4 UART-Request-Decode (strikt)
|
||||||
|
|
||||||
|
| Regel | Beispiele |
|
||||||
|
|-------|-----------|
|
||||||
|
| Leerer protobuf-Body (`len == 0`) erlaubt | `VERSION`, `CLIENT_INFO`, `CACHE_STATUS`, `BATTERY_STATUS` (Defaults) |
|
||||||
|
| `len > 0` → Decode muss gelingen, `which_payload` muss passen | `ACCEL_STREAM`, `TAP_NOTIFY`, `RESTART`, OTA, … |
|
||||||
|
|
||||||
|
### 4.5 Bridge-Muster: UART-Befehl → ESP-NOW
|
||||||
|
|
||||||
Viele Master-Handler folgen demselben Muster:
|
Viele Master-Handler folgen demselben Muster:
|
||||||
|
|
||||||
@ -237,13 +252,12 @@ Queue-Größe Master: **64** Einträge (`powerpod.c`); volle Queue → Warnung,
|
|||||||
|
|
||||||
### 6.1 Stack-Initialisierung
|
### 6.1 Stack-Initialisierung
|
||||||
|
|
||||||
`esp_now_comm_init()` (`main/esp_now_comm.c`):
|
`esp_now_comm_init()` (`esp_now_comm.c`) → `esp_now_core` (WiFi/Radio/Send) + Rolle:
|
||||||
|
|
||||||
1. `client_registry_init()`
|
1. `client_registry_init()` (Master)
|
||||||
2. WiFi STA, Kanal = `network` (1–13)
|
2. `esp_now_init()`, recv_cb leitet an `esp_now_master_on_recv` / `esp_now_slave_on_recv`
|
||||||
3. `esp_now_init()`, `esp_now_register_recv_cb(espnow_recv_cb)`
|
3. **Master** (`esp_now_master.c`): `espnow_disc`, `espnow_mon`
|
||||||
4. **Master:** Broadcast-Peer, Tasks `espnow_disc` (500 ms), `espnow_mon` (1 s)
|
4. **Slave** (`esp_now_slave.c`): `espnow_stx`, `espnow_hb`, `espnow_accel`, `ota_slave_work`
|
||||||
5. **Slave:** `slave_tx_task`, `slave_heartbeat_task`, `slave_accel_stream_task` (16 ms)
|
|
||||||
|
|
||||||
**Codec:** Rohes Paket = ein nanopb `EspNowMessage` — **kein** zusätzliches Framing (`main/esp_now_proto.c`).
|
**Codec:** Rohes Paket = ein nanopb `EspNowMessage` — **kein** zusätzliches Framing (`main/esp_now_proto.c`).
|
||||||
|
|
||||||
@ -276,6 +290,10 @@ sequenceDiagram
|
|||||||
|
|
||||||
**Master-Verlust:** Slave setzt Join zurück, wenn **5 s** kein Discover vom gleichen Master (`SLAVE_MASTER_LOST_MS`).
|
**Master-Verlust:** Slave setzt Join zurück, wenn **5 s** kein Discover vom gleichen Master (`SLAVE_MASTER_LOST_MS`).
|
||||||
|
|
||||||
|
**Join-Policy:** Master→Slave-Steuerung (Stream, Tap, LED, OTA, `UNICAST_TEST`, `SET_ACCEL_DEADZONE`, …) nur bei `s_slave_joined` und `src_addr == s_master_mac`. Während `ota_uart_is_active()` auf dem Slave verarbeitet der recv_cb nur OTA vom joined Master.
|
||||||
|
|
||||||
|
**Slave OTA:** Payload/End/Status-Sends laufen über `ota_slave_work_task` (Queue), nicht im ESP-NOW-recv_cb.
|
||||||
|
|
||||||
### 6.3 Master → Slave (Unicast)
|
### 6.3 Master → Slave (Unicast)
|
||||||
|
|
||||||
Alle Master-Sends laufen über `send_message()` / `send_message_ex()`:
|
Alle Master-Sends laufen über `send_message()` / `send_message_ex()`:
|
||||||
@ -361,7 +379,7 @@ flowchart TB
|
|||||||
| UART Handler-Boilerplate | `main/uart_cmd.c`, `main/uart_cmd.h` | Decode, Register, Send |
|
| UART Handler-Boilerplate | `main/uart_cmd.c`, `main/uart_cmd.h` | Decode, Register, Send |
|
||||||
| Command Dispatch | `main/cmd/cmd_handler.c` | Queue, Dispatcher |
|
| Command Dispatch | `main/cmd/cmd_handler.c` | Queue, Dispatcher |
|
||||||
| UART Commands | `main/cmd/cmd_*.c` | Pro Befehl ein Modul |
|
| UART Commands | `main/cmd/cmd_*.c` | Pro Befehl ein Modul |
|
||||||
| ESP-NOW Kern | `main/esp_now_comm.c`, `.h` | WiFi, Tasks, recv/send, Slave-Join |
|
| ESP-NOW | `esp_now_comm.c` Init/Router; `esp_now_core.c` Send/Peer; `esp_now_master.c` / `esp_now_slave.c` Rollenlogik |
|
||||||
| ESP-NOW Codec | `main/esp_now_proto.c` | nanopb encode/decode |
|
| ESP-NOW Codec | `main/esp_now_proto.c` | nanopb encode/decode |
|
||||||
| Registry | `main/client_registry.c` | Slave-Tabelle + Caches |
|
| Registry | `main/client_registry.c` | Slave-Tabelle + Caches |
|
||||||
| BMA456 | `main/bosch456.c` | Sensor, Tap, Deadzone-Filter |
|
| BMA456 | `main/bosch456.c` | Sensor, Tap, Deadzone-Filter |
|
||||||
@ -386,7 +404,7 @@ Regenerierung: `make proto_generate` (siehe `DOCUMENTATION.md`).
|
|||||||
|
|
||||||
## 11. Design-Entscheidungen
|
## 11. Design-Entscheidungen
|
||||||
|
|
||||||
1. **Eine Queue für alle Commands** — einheitliches Modell für UART und künftige interne Quellen.
|
1. **Eine Queue für UART-Commands** — einziger Eingang vom Host; während OTA nur OTA-Befehle.
|
||||||
2. **Kein ESP-NOW-Send im `recv_cb`** — Slave antwortet auf Discover über `slave_tx_task` (Deadlock-/Stack-Risiko vermeiden).
|
2. **Kein ESP-NOW-Send im `recv_cb`** — Slave antwortet auf Discover über `slave_tx_task` (Deadlock-/Stack-Risiko vermeiden).
|
||||||
3. **Registry-MAC = ESP-NOW-Quelladresse** — Protobuf-MAC ist optional/informativ.
|
3. **Registry-MAC = ESP-NOW-Quelladresse** — Protobuf-MAC ist optional/informativ.
|
||||||
4. **Gleiches Binary** — Konfiguration nur Hardware (DIP + Expander); reduziert Release-Komplexität.
|
4. **Gleiches Binary** — Konfiguration nur Hardware (DIP + Expander); reduziert Release-Komplexität.
|
||||||
|
|||||||
@ -140,8 +140,9 @@ Implementierung: `uart.c` — `parse_uart_byte()`, `uart_send_framed()`.
|
|||||||
1. `uart_read_task` parst Frames → `uart_enqueue_packet`
|
1. `uart_read_task` parst Frames → `uart_enqueue_packet`
|
||||||
2. `generic_msg_t` → `cmd_queue`
|
2. `generic_msg_t` → `cmd_queue`
|
||||||
3. `vCmdDispatcherTask` → registrierter `msg_callback_t`
|
3. `vCmdDispatcherTask` → registrierter `msg_callback_t`
|
||||||
4. Handler: `uart_cmd_decode(data, len, &msg)` — `data` ist **nur** protobuf-Teil
|
4. Handler: `uart_cmd_decode(data, len, &msg)` — `data` ist **nur** protobuf-Teil; bei `len > 0` strikt (Decode/`which_payload`-Fehler → Fehlerantwort)
|
||||||
5. Antwort: `uart_cmd_init_response()` + Felder setzen + `uart_cmd_send()`
|
5. `ota_session_uart_cmd_allowed()` — während OTA nur OTA-Befehle
|
||||||
|
6. Antwort: `uart_cmd_init_response()` + Felder setzen + `uart_cmd_send()`
|
||||||
|
|
||||||
Hilfsmakro für Request-Felder:
|
Hilfsmakro für Request-Felder:
|
||||||
|
|
||||||
@ -404,7 +405,10 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
|
|||||||
|
|
||||||
| Datei | Rolle |
|
| Datei | Rolle |
|
||||||
|-------|--------|
|
|-------|--------|
|
||||||
| `esp_now_comm.c` | WiFi, ESP-NOW, alle Rollen-Tasks |
|
| `esp_now_comm.c` | Init, recv-Router |
|
||||||
|
| `esp_now_core.c` | WiFi, Peer, gemeinsamer Send |
|
||||||
|
| `esp_now_master.c` | Master-Tasks, Registry, Unicast send |
|
||||||
|
| `esp_now_slave.c` | Join, Heartbeat, Accel/Tap send |
|
||||||
| `esp_now_proto.c` | nanopb für EspNowMessage |
|
| `esp_now_proto.c` | nanopb für EspNowMessage |
|
||||||
| `client_registry.c` | Slave-Tabelle + Telemetrie-Cache |
|
| `client_registry.c` | Slave-Tabelle + Telemetrie-Cache |
|
||||||
|
|
||||||
@ -436,7 +440,8 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
|
|||||||
| `pod_settings.c` | NVS |
|
| `pod_settings.c` | NVS |
|
||||||
| `pod_reboot.c` | Verzögerter Restart |
|
| `pod_reboot.c` | Verzögerter Restart |
|
||||||
| `ota_uart.c` | Flash-Puffer UART-OTA |
|
| `ota_uart.c` | Flash-Puffer UART-OTA |
|
||||||
| `ota_espnow.c` | OTA an Slaves |
|
| `ota_espnow.c` | OTA Master/Slave; Slave-Work-Queue |
|
||||||
|
| `ota_session.c` | OTA-Sperre für UART-Dispatcher |
|
||||||
|
|
||||||
### 14.5 Protobuf
|
### 14.5 Protobuf
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -146,7 +147,11 @@ func serveOTAUpload(w http.ResponseWriter, r *http.Request, link *managedSerial,
|
|||||||
if hub != nil {
|
if hub != nil {
|
||||||
hub.broadcastRaw(OTAProgress{Type: "ota_progress", Phase: "error", Message: err.Error()})
|
hub.broadcastRaw(OTAProgress{Type: "ota_progress", Phase: "error", Message: err.Error()})
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusServiceUnavailable, otaAPIResponse{Error: err.Error()})
|
status := http.StatusServiceUnavailable
|
||||||
|
if errors.Is(err, errOTAInProgress) {
|
||||||
|
status = http.StatusConflict
|
||||||
|
}
|
||||||
|
writeJSON(w, status, otaAPIResponse{Error: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, otaAPIResponse{
|
writeJSON(w, http.StatusOK, otaAPIResponse{
|
||||||
|
|||||||
@ -16,8 +16,7 @@ func runOTA(sp *serialPort, args []string) error {
|
|||||||
|
|
||||||
sp.mu.Lock()
|
sp.mu.Lock()
|
||||||
defer sp.mu.Unlock()
|
defer sp.mu.Unlock()
|
||||||
m := &managedSerial{quiet: false, sp: sp}
|
return runOTAOnPortUnlocked(sp, data, func(p OTAProgress) {
|
||||||
return runOTAOnPortUnlocked(m, data, func(p OTAProgress) {
|
|
||||||
switch p.Phase {
|
switch p.Phase {
|
||||||
case "preparing", "ready":
|
case "preparing", "ready":
|
||||||
fmt.Println(p.Message)
|
fmt.Println(p.Message)
|
||||||
|
|||||||
@ -101,7 +101,7 @@ func (h *wsHub) setState(st DashboardState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ func (h *wsHub) register(c *websocket.Conn) {
|
|||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
|
|
||||||
if data, err := json.Marshal(snap); err == nil {
|
if data, err := json.Marshal(snap); err == nil {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +122,30 @@ func (h *wsHub) unregister(c *websocket.Conn) {
|
|||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeJSON sends to one client; removes it on error or panic (closed connection).
|
||||||
|
func (h *wsHub) writeJSON(c *websocket.Conn, data []byte) {
|
||||||
|
defer func() {
|
||||||
|
if recover() != nil {
|
||||||
|
h.unregister(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := c.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
h.unregister(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *wsHub) broadcastJSON(data []byte) {
|
||||||
|
h.mu.RLock()
|
||||||
|
conns := make([]*websocket.Conn, 0, len(h.clients))
|
||||||
|
for c := range h.clients {
|
||||||
|
conns = append(conns, c)
|
||||||
|
}
|
||||||
|
h.mu.RUnlock()
|
||||||
|
for _, c := range conns {
|
||||||
|
h.writeJSON(c, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func applyAccelSamples(clients []ClientView, samples []*pb.AccelSample) []ClientView {
|
func applyAccelSamples(clients []ClientView, samples []*pb.AccelSample) []ClientView {
|
||||||
if len(samples) == 0 {
|
if len(samples) == 0 {
|
||||||
return clients
|
return clients
|
||||||
@ -338,7 +362,7 @@ func (h *wsHub) patchClientAccelStream(clientID uint32, enabled bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +421,7 @@ func (h *wsHub) patchLiveStream(enabled bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +454,7 @@ func (h *wsHub) patchClientTapNotify(clientID uint32, single, doubleTap, triple
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +479,7 @@ func (h *wsHub) mergeAccel(samples []*pb.AccelSample) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,25 +503,16 @@ func (h *wsHub) mergeTap(events []*pb.TapEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *wsHub) broadcastRaw(v any) {
|
func (h *wsHub) broadcastRaw(v any) {
|
||||||
h.mu.RLock()
|
|
||||||
conns := make([]*websocket.Conn, 0, len(h.clients))
|
|
||||||
for c := range h.clients {
|
|
||||||
conns = append(conns, c)
|
|
||||||
}
|
|
||||||
h.mu.RUnlock()
|
|
||||||
|
|
||||||
data, err := json.Marshal(v)
|
data, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
h.broadcastJSON(data)
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pollDashboard(link *managedSerial, portName string, last *DashboardState, streamCtl *accelStreamCtl, tapCtl *tapNotifyCtl) DashboardState {
|
func pollDashboard(link *managedSerial, portName string, last *DashboardState, streamCtl *accelStreamCtl, tapCtl *tapNotifyCtl) DashboardState {
|
||||||
@ -575,6 +590,9 @@ func pollDashboard(link *managedSerial, portName string, last *DashboardState, s
|
|||||||
|
|
||||||
func applyBatteryToState(link *managedSerial, st *DashboardState) {
|
func applyBatteryToState(link *managedSerial, st *DashboardState) {
|
||||||
bat, err := link.BatteryStatusPoll(&pb.BatteryStatusRequest{AllClients: true})
|
bat, err := link.BatteryStatusPoll(&pb.BatteryStatusRequest{AllClients: true})
|
||||||
|
if errors.Is(err, errUARTBusy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("battery poll: %v", err)
|
log.Printf("battery poll: %v", err)
|
||||||
return
|
return
|
||||||
@ -602,7 +620,7 @@ func (h *wsHub) mergeBattery(samples []batterySampleJSON) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
_ = c.WriteMessage(websocket.TextMessage, data)
|
h.writeJSON(c, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,7 +637,7 @@ func runBatteryPoller(link *managedSerial, hub *wsHub, interval time.Duration, s
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bat, err := link.BatteryStatusPoll(&pb.BatteryStatusRequest{AllClients: true})
|
bat, err := link.BatteryStatusPoll(&pb.BatteryStatusRequest{AllClients: true})
|
||||||
if err != nil {
|
if errors.Is(err, errUARTBusy) || err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hub.mergeBattery(batterySamplesFromPB(bat.GetSamples()))
|
hub.mergeBattery(batterySamplesFromPB(bat.GetSamples()))
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@ -69,16 +68,45 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func runOTAUpload(m *managedSerial, firmware []byte, onProgress otaProgressFn) error {
|
func runOTAUpload(m *managedSerial, firmware []byte, onProgress otaProgressFn) error {
|
||||||
|
push := func(phase, msg string) {
|
||||||
|
if onProgress == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onProgress(OTAProgress{
|
||||||
|
Type: "ota_progress", Phase: phase, Step: otaStepMaster,
|
||||||
|
Percent: 0, Message: msg, MasterMessage: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
push("preparing", "UART wird vorbereitet…")
|
||||||
|
|
||||||
|
// Block until the UART is free, then hold m.mu for the entire upload so
|
||||||
|
// dashboard/API polling cannot interleave on the serial port.
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
if m.otaActive {
|
||||||
err := runOTAOnPortUnlocked(m, firmware, onProgress)
|
m.mu.Unlock()
|
||||||
|
return errOTAInProgress
|
||||||
|
}
|
||||||
|
m.otaActive = true
|
||||||
|
if m.sp == nil {
|
||||||
|
if err := m.openLocked(); err != nil {
|
||||||
|
m.otaActive = false
|
||||||
|
m.mu.Unlock()
|
||||||
|
push("error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp := m.sp
|
||||||
|
|
||||||
|
err := runOTAOnPortUnlocked(sp, firmware, onProgress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.invalidateLocked(err)
|
m.invalidateLocked(err)
|
||||||
}
|
}
|
||||||
|
m.otaActive = false
|
||||||
|
m.mu.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgressFn) error {
|
func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgressFn) error {
|
||||||
if len(firmware) == 0 {
|
if len(firmware) == 0 {
|
||||||
return fmt.Errorf("empty firmware")
|
return fmt.Errorf("empty firmware")
|
||||||
}
|
}
|
||||||
@ -120,32 +148,31 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
|||||||
onProgress(p)
|
onProgress(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.sp == nil {
|
if err := sp.port.SetReadTimeout(readTimeout); err != nil {
|
||||||
if err := m.openLocked(); err != nil {
|
|
||||||
notify("error", "", 0, err.Error())
|
notify("error", "", 0, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sp := m.sp
|
|
||||||
if err := sp.port.SetReadTimeout(otaPrepareTimeout); err != nil {
|
|
||||||
notify("error", "", 0, err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sp.port.SetReadTimeout(readTimeout)
|
|
||||||
|
|
||||||
notify("preparing", otaStepMaster, 0, fmt.Sprintf("Master: OTA start (%d bytes)…", imageSize))
|
notify("preparing", otaStepMaster, 0, fmt.Sprintf("Master: OTA start (%d bytes)…", imageSize))
|
||||||
|
|
||||||
|
flushSerialInput(sp)
|
||||||
|
|
||||||
if err := writeUartMessage(sp, &pb.UartMessage{
|
if err := writeUartMessage(sp, &pb.UartMessage{
|
||||||
Type: pb.MessageType_OTA_START,
|
Type: pb.MessageType_OTA_START,
|
||||||
Payload: &pb.UartMessage_OtaStart{
|
Payload: &pb.UartMessage_OtaStart{
|
||||||
OtaStart: &pb.OtaStartPayload{TotalSize: uint32(imageSize)},
|
OtaStart: &pb.OtaStartPayload{TotalSize: uint32(imageSize)},
|
||||||
},
|
},
|
||||||
}, false); err != nil {
|
}); err != nil {
|
||||||
notify("error", "", 0, err.Error())
|
notify("error", "", 0, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := sp.port.SetReadTimeout(otaPrepareTimeout); err != nil {
|
||||||
|
notify("error", "", 0, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = sp.port.SetReadTimeout(readTimeout) }()
|
||||||
|
|
||||||
ready, err := waitOtaStatus(sp, otaStReady, otaPrepareTimeout, func(msg string) {
|
ready, err := waitOtaStatus(sp, otaStReady, otaPrepareTimeout, func(msg string) {
|
||||||
notify("preparing", otaStepMaster, 2, msg)
|
notify("preparing", otaStepMaster, 2, msg)
|
||||||
})
|
})
|
||||||
@ -179,7 +206,7 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
|||||||
Payload: &pb.UartMessage_OtaPayload{
|
Payload: &pb.UartMessage_OtaPayload{
|
||||||
OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk},
|
OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk},
|
||||||
},
|
},
|
||||||
}, false); err != nil {
|
}); err != nil {
|
||||||
notify("error", "", 0, err.Error())
|
notify("error", "", 0, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -219,7 +246,7 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
|||||||
Payload: &pb.UartMessage_OtaEnd{
|
Payload: &pb.UartMessage_OtaEnd{
|
||||||
OtaEnd: &pb.OtaEndPayload{},
|
OtaEnd: &pb.OtaEndPayload{},
|
||||||
},
|
},
|
||||||
}, false); err != nil {
|
}); err != nil {
|
||||||
notify("error", "", 0, err.Error())
|
notify("error", "", 0, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -333,7 +360,7 @@ func queryOtaSlaveProgressLocked(sp *serialPort, clientID uint32,
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := writeUartMessage(sp, req, false); err != nil {
|
if err := writeUartMessage(sp, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if queryTimeout <= 0 {
|
if queryTimeout <= 0 {
|
||||||
@ -487,14 +514,11 @@ func waitOtaComplete(sp *serialPort, timeout time.Duration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeUartMessage(sp *serialPort, msg *pb.UartMessage, logFrame bool) error {
|
func writeUartMessage(sp *serialPort, msg *pb.UartMessage) error {
|
||||||
frame, err := encodeUartMessage(msg)
|
frame, err := encodeUartMessage(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if logFrame {
|
|
||||||
log.Printf("sending %s (%d frame bytes)", msg.Type, len(frame))
|
|
||||||
}
|
|
||||||
_, err = sp.port.Write(frame)
|
_, err = sp.port.Write(frame)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -505,12 +529,24 @@ func waitOtaStatus(sp *serialPort, want uint32, timeout time.Duration, onPrepari
|
|||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
return nil, fmt.Errorf("timeout waiting for OTA status %d", want)
|
return nil, fmt.Errorf("timeout waiting for OTA status %d", want)
|
||||||
}
|
}
|
||||||
if err := sp.port.SetReadTimeout(time.Until(deadline)); err != nil {
|
readWait := time.Until(deadline)
|
||||||
|
if readWait > otaStatusPollTimeout {
|
||||||
|
readWait = otaStatusPollTimeout
|
||||||
|
}
|
||||||
|
if err := sp.port.SetReadTimeout(readWait); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
st, err := readOtaStatus(sp)
|
payload, err := uartframe.ReadFrame(sp.port, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
continue
|
||||||
|
}
|
||||||
|
msg, err := decodeUartPayload(payload)
|
||||||
|
if err != nil || msg.GetType() != pb.MessageType_OTA_STATUS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
st := msg.GetOtaStatus()
|
||||||
|
if st == nil {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
switch st.GetStatus() {
|
switch st.GetStatus() {
|
||||||
case want:
|
case want:
|
||||||
@ -553,6 +589,22 @@ func encodeUartMessage(msg *pb.UartMessage) ([]byte, error) {
|
|||||||
return uartframe.EncodeFrame(payload)
|
return uartframe.EncodeFrame(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushSerialInput drops stale RX bytes (not full frames — avoids ReadFrame blocking).
|
||||||
|
func flushSerialInput(sp *serialPort) {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = sp.port.SetReadTimeout(10 * time.Millisecond)
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
deadline := time.Now().Add(50 * time.Millisecond)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
n, err := sp.port.Read(buf)
|
||||||
|
if n == 0 || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func decodeUartPayload(payload []byte) (*pb.UartMessage, error) {
|
func decodeUartPayload(payload []byte) (*pb.UartMessage, error) {
|
||||||
if len(payload) == 0 {
|
if len(payload) == 0 {
|
||||||
return nil, fmt.Errorf("empty response")
|
return nil, fmt.Errorf("empty response")
|
||||||
|
|||||||
@ -11,6 +11,9 @@ import (
|
|||||||
// errUARTBusy is returned when the port is held for OTA (poller should not treat as unplug).
|
// errUARTBusy is returned when the port is held for OTA (poller should not treat as unplug).
|
||||||
var errUARTBusy = errors.New("uart busy (OTA in progress)")
|
var errUARTBusy = errors.New("uart busy (OTA in progress)")
|
||||||
|
|
||||||
|
// errOTAInProgress is returned when a second OTA upload is attempted while one is running.
|
||||||
|
var errOTAInProgress = errors.New("OTA upload already in progress")
|
||||||
|
|
||||||
// managedSerial keeps the UART open and reconnects after I/O failures or unplug.
|
// managedSerial keeps the UART open and reconnects after I/O failures or unplug.
|
||||||
type managedSerial struct {
|
type managedSerial struct {
|
||||||
portName string
|
portName string
|
||||||
@ -19,6 +22,7 @@ type managedSerial struct {
|
|||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
sp *serialPort
|
sp *serialPort
|
||||||
|
otaActive bool // UART held for firmware upload; poll/API must not interleave
|
||||||
}
|
}
|
||||||
|
|
||||||
func newManagedSerial(portName string, baud int) *managedSerial {
|
func newManagedSerial(portName string, baud int) *managedSerial {
|
||||||
@ -76,20 +80,17 @@ func (m *managedSerial) withPort(fn func(*serialPort) error) error {
|
|||||||
return m.withPortLocked(false, fn)
|
return m.withPortLocked(false, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// withPortPoll is like withPort but returns errUARTBusy instead of blocking during OTA.
|
// withPortPoll is like withPort but returns errUARTBusy during OTA (no TryLock race).
|
||||||
func (m *managedSerial) withPortPoll(fn func(*serialPort) error) error {
|
func (m *managedSerial) withPortPoll(fn func(*serialPort) error) error {
|
||||||
return m.withPortLocked(true, fn)
|
return m.withPortLocked(true, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *managedSerial) withPortLocked(try bool, fn func(*serialPort) error) error {
|
func (m *managedSerial) withPortLocked(poll bool, fn func(*serialPort) error) error {
|
||||||
if try {
|
m.mu.Lock()
|
||||||
if !m.mu.TryLock() {
|
defer m.mu.Unlock()
|
||||||
|
if m.otaActive {
|
||||||
return errUARTBusy
|
return errUARTBusy
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
m.mu.Lock()
|
|
||||||
}
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.sp == nil {
|
if m.sp == nil {
|
||||||
if err := m.openLocked(); err != nil {
|
if err := m.openLocked(); err != nil {
|
||||||
|
|||||||
@ -9,7 +9,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
StartMarker = 0xAA
|
StartMarker = 0xAA
|
||||||
StopMarker = 0xCC
|
StopMarker = 0xCC
|
||||||
MaxPayload = 252
|
// Must match main/uart.h MAX_PAYLOAD_SIZE (MAX_BUF_SIZE - 4).
|
||||||
|
MaxPayload = 248
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -938,8 +938,21 @@
|
|||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
applyOTAProgress(p) {
|
applyOTAProgress(p) {
|
||||||
this.ota.phase = p.phase || '';
|
const prevPhase = this.ota.phase;
|
||||||
this.ota.step = p.step || this.ota.step || '';
|
const prevStep = this.ota.step;
|
||||||
|
if (p.phase) {
|
||||||
|
// Ignore out-of-order master upload updates after distribution started.
|
||||||
|
if (!(p.phase === 'uploading' && prevPhase === 'distributing')) {
|
||||||
|
this.ota.phase = p.phase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p.step) {
|
||||||
|
if (!(p.step === 'master' && (prevStep === 'slaves' || prevPhase === 'distributing'))) {
|
||||||
|
this.ota.step = p.step;
|
||||||
|
}
|
||||||
|
} else if (!this.ota.step) {
|
||||||
|
this.ota.step = '';
|
||||||
|
}
|
||||||
this.ota.percent = p.percent ?? this.ota.percent;
|
this.ota.percent = p.percent ?? this.ota.percent;
|
||||||
this.ota.message = p.message || '';
|
this.ota.message = p.message || '';
|
||||||
if (p.image_size) this.ota.imageSize = p.image_size;
|
if (p.image_size) this.ota.imageSize = p.image_size;
|
||||||
|
|||||||
@ -31,8 +31,12 @@ idf_component_register(
|
|||||||
"cmd/cmd_ota_slave_progress.c"
|
"cmd/cmd_ota_slave_progress.c"
|
||||||
"ota_uart.c"
|
"ota_uart.c"
|
||||||
"ota_espnow.c"
|
"ota_espnow.c"
|
||||||
|
"ota_session.c"
|
||||||
"client_registry.c"
|
"client_registry.c"
|
||||||
"esp_now_comm.c"
|
"esp_now_comm.c"
|
||||||
|
"esp_now_core.c"
|
||||||
|
"esp_now_master.c"
|
||||||
|
"esp_now_slave.c"
|
||||||
"esp_now_proto.c"
|
"esp_now_proto.c"
|
||||||
"bosch456.c"
|
"bosch456.c"
|
||||||
"board_input.c"
|
"board_input.c"
|
||||||
|
|||||||
@ -178,7 +178,7 @@ Logging:
|
|||||||
|
|
||||||
## Command handler
|
## Command handler
|
||||||
|
|
||||||
Generic dispatch for host commands (UART today; `msg_post()` for in-firmware sources later).
|
Generic dispatch for host commands over UART only.
|
||||||
|
|
||||||
```
|
```
|
||||||
UART → generic_msg_t queue → vCmdDispatcherTask → registered handler
|
UART → generic_msg_t queue → vCmdDispatcherTask → registered handler
|
||||||
@ -188,7 +188,8 @@ UART → generic_msg_t queue → vCmdDispatcherTask → registered handler
|
|||||||
|-----|-------------|
|
|-----|-------------|
|
||||||
| `init_cmdHandler(queue)` | Start dispatcher task (priority 5) |
|
| `init_cmdHandler(queue)` | Start dispatcher task (priority 5) |
|
||||||
| `msg_register_handler(id, cb)` | Register callback; max 32 handlers |
|
| `msg_register_handler(id, cb)` | Register callback; max 32 handlers |
|
||||||
| `msg_post(id, data, len)` | Enqueue from firmware (e.g. future ESP-NOW → PC path) |
|
|
||||||
|
During an OTA session (`ota_session_busy()`), the dispatcher rejects all UART commands except OTA_* and `OTA_SLAVE_PROGRESS` (see `ota_session.c`).
|
||||||
|
|
||||||
```c
|
```c
|
||||||
typedef void (*msg_callback_t)(const uint8_t *data, size_t len);
|
typedef void (*msg_callback_t)(const uint8_t *data, size_t len);
|
||||||
@ -519,7 +520,10 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
|
|||||||
| `powerpod.c` | `app_main`, DIP/network config, init order |
|
| `powerpod.c` | `app_main`, DIP/network config, init order |
|
||||||
| `powerpod.h` | Pin defines |
|
| `powerpod.h` | Pin defines |
|
||||||
| `app_config.h` | `app_config_t` |
|
| `app_config.h` | `app_config_t` |
|
||||||
| `esp_now_comm.c/h` | WiFi, ESP-NOW, discover / slave info / OTA send |
|
| `esp_now_comm.c/h` | ESP-NOW init and recv router |
|
||||||
|
| `esp_now_core.c/h` | Shared WiFi, peer, send |
|
||||||
|
| `esp_now_master.c/h` | Master discover, monitor, unicast |
|
||||||
|
| `esp_now_slave.c/h` | Slave join, heartbeat, telemetry |
|
||||||
| `ota_uart.c/h` | Shared 4 KiB OTA flash buffer (UART + ESP-NOW) |
|
| `ota_uart.c/h` | Shared 4 KiB OTA flash buffer (UART + ESP-NOW) |
|
||||||
| `ota_espnow.c/h` | Master: distribute staged image to slaves |
|
| `ota_espnow.c/h` | Master: distribute staged image to slaves |
|
||||||
| `cmd/cmd_ota.c/h` | UART OTA command handlers (master only) |
|
| `cmd/cmd_ota.c/h` | UART OTA command handlers (master only) |
|
||||||
|
|||||||
@ -33,12 +33,20 @@ static void handle_accel_stream(const uint8_t *data, size_t len) {
|
|||||||
alox_UartMessage uart_msg;
|
alox_UartMessage uart_msg;
|
||||||
alox_AccelStreamRequest req = alox_AccelStreamRequest_init_zero;
|
alox_AccelStreamRequest req = alox_AccelStreamRequest_init_zero;
|
||||||
|
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
|
if (len > 0) {
|
||||||
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed");
|
||||||
|
reply(false, 0, false, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const alox_AccelStreamRequest *req_ptr = UART_CMD_REQ(
|
const alox_AccelStreamRequest *req_ptr = UART_CMD_REQ(
|
||||||
&uart_msg, alox_UartMessage_accel_stream_request_tag, accel_stream_request);
|
&uart_msg, alox_UartMessage_accel_stream_request_tag, accel_stream_request);
|
||||||
if (req_ptr != NULL) {
|
if (req_ptr == NULL) {
|
||||||
req = *req_ptr;
|
ESP_LOGW(TAG, "missing accel_stream_request");
|
||||||
|
reply(false, 0, false, 0);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
req = *req_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.write) {
|
if (req.write) {
|
||||||
|
|||||||
@ -67,15 +67,29 @@ static void handle_battery_status(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
alox_UartMessage uart_msg;
|
alox_UartMessage uart_msg;
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed");
|
||||||
|
alox_UartMessage response;
|
||||||
|
uart_cmd_init_response(&response, alox_MessageType_BATTERY_STATUS,
|
||||||
|
alox_UartMessage_battery_status_response_tag);
|
||||||
|
response.payload.battery_status_response.success = false;
|
||||||
|
uart_cmd_send(&response, TAG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const alox_BatteryStatusRequest *req_ptr = UART_CMD_REQ(
|
const alox_BatteryStatusRequest *req_ptr = UART_CMD_REQ(
|
||||||
&uart_msg, alox_UartMessage_battery_status_request_tag,
|
&uart_msg, alox_UartMessage_battery_status_request_tag,
|
||||||
battery_status_request);
|
battery_status_request);
|
||||||
if (req_ptr != NULL) {
|
if (req_ptr == NULL) {
|
||||||
|
ESP_LOGW(TAG, "missing battery_status_request");
|
||||||
|
alox_UartMessage response;
|
||||||
|
uart_cmd_init_response(&response, alox_MessageType_BATTERY_STATUS,
|
||||||
|
alox_UartMessage_battery_status_response_tag);
|
||||||
|
response.payload.battery_status_response.success = false;
|
||||||
|
uart_cmd_send(&response, TAG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
req = *req_ptr;
|
req = *req_ptr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alox_UartMessage response;
|
alox_UartMessage response;
|
||||||
uart_cmd_init_response(&response, alox_MessageType_BATTERY_STATUS,
|
uart_cmd_init_response(&response, alox_MessageType_BATTERY_STATUS,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "cmd_handler.h"
|
#include "cmd_handler.h"
|
||||||
|
#include "ota_session.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@ -88,33 +89,19 @@ esp_err_t msg_register_handler(uint16_t id, msg_callback_t cb) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t msg_post(uint16_t id, const uint8_t *data, size_t len) {
|
|
||||||
if (cmd_queue == NULL) {
|
|
||||||
return ESP_ERR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
generic_msg_t msg = {.msg_id = id, .len = len, .payload = NULL};
|
|
||||||
if (len > 0) {
|
|
||||||
msg.payload = malloc(len);
|
|
||||||
if (msg.payload == NULL) {
|
|
||||||
return ESP_ERR_NO_MEM;
|
|
||||||
}
|
|
||||||
memcpy(msg.payload, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xQueueSend(cmd_queue, &msg, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
free(msg.payload);
|
|
||||||
return ESP_ERR_TIMEOUT;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void vCmdDispatcherTask(void *param) {
|
void vCmdDispatcherTask(void *param) {
|
||||||
(void)param;
|
(void)param;
|
||||||
generic_msg_t msg;
|
generic_msg_t msg;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
if (xQueueReceive(cmd_queue, &msg, portMAX_DELAY) == pdPASS) {
|
if (xQueueReceive(cmd_queue, &msg, portMAX_DELAY) == pdPASS) {
|
||||||
|
if (!ota_session_uart_cmd_allowed(msg.msg_id)) {
|
||||||
|
ESP_LOGW(TAG, "reject %s (0x%02x) during OTA session",
|
||||||
|
message_type_name(msg.msg_id), (unsigned)msg.msg_id);
|
||||||
|
free(msg.payload);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
for (int i = 0; i < handler_count; i++) {
|
for (int i = 0; i < handler_count; i++) {
|
||||||
if (handlers[i].msg_id == msg.msg_id) {
|
if (handlers[i].msg_id == msg.msg_id) {
|
||||||
|
|||||||
@ -21,6 +21,5 @@ void init_cmdHandler(QueueHandle_t queue);
|
|||||||
void vCmdDispatcherTask(void *param);
|
void vCmdDispatcherTask(void *param);
|
||||||
|
|
||||||
esp_err_t msg_register_handler(uint16_t id, msg_callback_t cb);
|
esp_err_t msg_register_handler(uint16_t id, msg_callback_t cb);
|
||||||
esp_err_t msg_post(uint16_t id, const uint8_t *data, size_t len);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -81,8 +81,6 @@ static const ota_espnow_progress_cbs_t s_dist_progress = {
|
|||||||
static void ota_prepare_task(void *param) {
|
static void ota_prepare_task(void *param) {
|
||||||
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
||||||
|
|
||||||
send_ota_status(OTA_UART_ST_PREPARING, 0);
|
|
||||||
|
|
||||||
int slot = ota_uart_prepare(total_size);
|
int slot = ota_uart_prepare(total_size);
|
||||||
if (slot < 0) {
|
if (slot < 0) {
|
||||||
send_ota_failed(1);
|
send_ota_failed(1);
|
||||||
@ -116,9 +114,12 @@ static void handle_ota_start(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
const alox_OtaStartPayload *req_ptr =
|
const alox_OtaStartPayload *req_ptr =
|
||||||
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_start_tag, ota_start);
|
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_start_tag, ota_start);
|
||||||
if (req_ptr != NULL) {
|
if (req_ptr == NULL) {
|
||||||
req = *req_ptr;
|
ESP_LOGW(TAG, "OTA_START: missing ota_start payload");
|
||||||
|
send_ota_failed(3);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
req = *req_ptr;
|
||||||
|
|
||||||
if (req.total_size == 0) {
|
if (req.total_size == 0) {
|
||||||
ESP_LOGW(TAG, "OTA_START: total_size required");
|
ESP_LOGW(TAG, "OTA_START: total_size required");
|
||||||
@ -132,6 +133,8 @@ static void handle_ota_start(const uint8_t *data, size_t len) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
send_ota_status(OTA_UART_ST_PREPARING, 0);
|
||||||
|
|
||||||
if (xTaskCreate(ota_prepare_task, "ota_prepare", OTA_PREPARE_STACK,
|
if (xTaskCreate(ota_prepare_task, "ota_prepare", OTA_PREPARE_STACK,
|
||||||
(void *)(uintptr_t)req.total_size, OTA_PREPARE_PRIO,
|
(void *)(uintptr_t)req.total_size, OTA_PREPARE_PRIO,
|
||||||
NULL) != pdPASS) {
|
NULL) != pdPASS) {
|
||||||
@ -293,6 +296,42 @@ static void handle_ota_end(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ota_start_espnow_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
|
||||||
|
const esp_partition_t *part = NULL;
|
||||||
|
uint32_t image_size = 0;
|
||||||
|
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
||||||
|
send_ota_failed(41);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
send_ota_failed(42);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ota_uart_apply_boot();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
send_ota_failed((uint32_t)err);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alox_UartMessage response;
|
||||||
|
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
|
||||||
|
alox_UartMessage_ota_status_tag);
|
||||||
|
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_SUCCESS;
|
||||||
|
response.payload.ota_status.bytes_written = image_size;
|
||||||
|
response.payload.ota_status.error = 0;
|
||||||
|
uart_cmd_send(&response, TAG);
|
||||||
|
led_ring_ota_success();
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)len;
|
(void)len;
|
||||||
@ -309,26 +348,11 @@ static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
if (xTaskCreate(ota_start_espnow_task, "ota_espnow", OTA_DIST_STACK, NULL,
|
||||||
if (err != ESP_OK) {
|
OTA_DIST_PRIO, NULL) != pdPASS) {
|
||||||
send_ota_failed( 42);
|
ESP_LOGE(TAG, "failed to create ota_start_espnow task");
|
||||||
return;
|
send_ota_failed(43);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ota_uart_apply_boot();
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
send_ota_failed( (uint32_t)err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
alox_UartMessage response;
|
|
||||||
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
|
|
||||||
alox_UartMessage_ota_status_tag);
|
|
||||||
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_SUCCESS;
|
|
||||||
response.payload.ota_status.bytes_written = image_size;
|
|
||||||
response.payload.ota_status.error = 0;
|
|
||||||
uart_cmd_send(&response, TAG);
|
|
||||||
led_ring_ota_success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmd_ota_register(void) {
|
void cmd_ota_register(void) {
|
||||||
|
|||||||
@ -9,13 +9,29 @@ static void handle_ota_slave_progress(const uint8_t *data, size_t len) {
|
|||||||
alox_UartMessage uart_msg;
|
alox_UartMessage uart_msg;
|
||||||
uint32_t filter = 0;
|
uint32_t filter = 0;
|
||||||
|
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
|
if (len > 0) {
|
||||||
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed");
|
||||||
|
alox_UartMessage response;
|
||||||
|
uart_cmd_init_response(
|
||||||
|
&response, alox_MessageType_OTA_SLAVE_PROGRESS,
|
||||||
|
alox_UartMessage_ota_slave_progress_response_tag);
|
||||||
|
uart_cmd_send(&response, TAG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const alox_OtaSlaveProgressRequest *req =
|
const alox_OtaSlaveProgressRequest *req =
|
||||||
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_slave_progress_request_tag,
|
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_slave_progress_request_tag,
|
||||||
ota_slave_progress_request);
|
ota_slave_progress_request);
|
||||||
if (req != NULL) {
|
if (req == NULL) {
|
||||||
filter = req->client_id;
|
ESP_LOGW(TAG, "missing ota_slave_progress_request");
|
||||||
|
alox_UartMessage response;
|
||||||
|
uart_cmd_init_response(
|
||||||
|
&response, alox_MessageType_OTA_SLAVE_PROGRESS,
|
||||||
|
alox_UartMessage_ota_slave_progress_response_tag);
|
||||||
|
uart_cmd_send(&response, TAG);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
filter = req->client_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
alox_UartMessage response;
|
alox_UartMessage response;
|
||||||
|
|||||||
@ -39,12 +39,20 @@ static void handle_tap_notify(const uint8_t *data, size_t len) {
|
|||||||
alox_UartMessage uart_msg;
|
alox_UartMessage uart_msg;
|
||||||
alox_TapNotifyRequest req = alox_TapNotifyRequest_init_zero;
|
alox_TapNotifyRequest req = alox_TapNotifyRequest_init_zero;
|
||||||
|
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
|
if (len > 0) {
|
||||||
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed");
|
||||||
|
reply(0, false, 0, false, false, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const alox_TapNotifyRequest *req_ptr = UART_CMD_REQ(
|
const alox_TapNotifyRequest *req_ptr = UART_CMD_REQ(
|
||||||
&uart_msg, alox_UartMessage_tap_notify_request_tag, tap_notify_request);
|
&uart_msg, alox_UartMessage_tap_notify_request_tag, tap_notify_request);
|
||||||
if (req_ptr != NULL) {
|
if (req_ptr == NULL) {
|
||||||
req = *req_ptr;
|
ESP_LOGW(TAG, "missing tap_notify_request");
|
||||||
|
reply(0, false, 0, false, false, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
req = *req_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.write) {
|
if (req.write) {
|
||||||
|
|||||||
1255
main/esp_now_comm.c
1255
main/esp_now_comm.c
File diff suppressed because it is too large
Load Diff
172
main/esp_now_core.c
Normal file
172
main/esp_now_core.c
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#include "esp_now_core.h"
|
||||||
|
#include "esp_now_proto.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "esp_now.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/idf_additions.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const char *TAG = "[ESPNOW_CORE]";
|
||||||
|
|
||||||
|
static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff};
|
||||||
|
|
||||||
|
static app_config_t s_config;
|
||||||
|
static uint8_t s_wifi_channel;
|
||||||
|
static uint8_t s_own_mac[ESP_NOW_ETH_ALEN];
|
||||||
|
static SemaphoreHandle_t s_send_done;
|
||||||
|
static bool s_send_cb_ready;
|
||||||
|
|
||||||
|
static uint8_t network_to_channel(uint8_t network) {
|
||||||
|
if (network < 1 || network > 13) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void espnow_send_done_cb(const esp_now_send_info_t *tx_info,
|
||||||
|
esp_now_send_status_t status) {
|
||||||
|
(void)tx_info;
|
||||||
|
(void)status;
|
||||||
|
if (s_send_done != NULL) {
|
||||||
|
xSemaphoreGive(s_send_done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_now_core_store_config(const app_config_t *config) {
|
||||||
|
if (config == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(&s_config, 0, sizeof(s_config));
|
||||||
|
memcpy(&s_config, config, sizeof(s_config));
|
||||||
|
s_wifi_channel = network_to_channel(config->network);
|
||||||
|
}
|
||||||
|
|
||||||
|
const app_config_t *esp_now_core_get_config(void) { return &s_config; }
|
||||||
|
|
||||||
|
bool esp_now_core_is_master(void) { return s_config.master; }
|
||||||
|
|
||||||
|
uint8_t esp_now_core_network(void) { return s_config.network; }
|
||||||
|
|
||||||
|
uint8_t esp_now_core_wifi_channel(void) { return s_wifi_channel; }
|
||||||
|
|
||||||
|
const uint8_t *esp_now_core_own_mac(void) { return s_own_mac; }
|
||||||
|
|
||||||
|
uint32_t esp_now_core_now_ms(void) {
|
||||||
|
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esp_now_core_mac_equal(const uint8_t *a, const uint8_t *b) {
|
||||||
|
return memcmp(a, b, ESP_NOW_ETH_ALEN) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_now_core_mac_to_str(const uint8_t *mac, char *out, size_t out_len) {
|
||||||
|
snprintf(out, out_len, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1],
|
||||||
|
mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_ensure_peer(const uint8_t *mac) {
|
||||||
|
if (esp_now_is_peer_exist(mac)) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_now_peer_info_t peer = {0};
|
||||||
|
memcpy(peer.peer_addr, mac, ESP_NOW_ETH_ALEN);
|
||||||
|
peer.channel = s_wifi_channel;
|
||||||
|
peer.ifidx = WIFI_IF_STA;
|
||||||
|
peer.encrypt = false;
|
||||||
|
|
||||||
|
esp_err_t err = esp_now_add_peer(&peer);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "add peer failed: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_ensure_broadcast_peer(void) {
|
||||||
|
return esp_now_core_ensure_peer(ESPNOW_BCAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac,
|
||||||
|
const alox_EspNowMessage *msg) {
|
||||||
|
uint8_t buf[ESPNOW_PB_MAX_SIZE];
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "encode failed");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > ESP_NOW_MAX_DATA_LEN) {
|
||||||
|
ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len,
|
||||||
|
(unsigned)ESP_NOW_MAX_DATA_LEN);
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_now_core_ensure_peer(dest_mac) != ESP_OK) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_send_cb_ready && s_send_done != NULL) {
|
||||||
|
xSemaphoreTake(s_send_done, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_now_send(dest_mac, buf, len);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_send_cb_ready && s_send_done != NULL) {
|
||||||
|
if (xSemaphoreTake(s_send_done, pdMS_TO_TICKS(50)) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "send type=%u done timeout", (unsigned)msg->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_send(const uint8_t *dest_mac,
|
||||||
|
const alox_EspNowMessage *msg) {
|
||||||
|
return esp_now_core_send_wait(dest_mac, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_init_radio(uint8_t channel) {
|
||||||
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|
||||||
|
wifi_config_t wifi_config = {0};
|
||||||
|
wifi_config.sta.channel = channel;
|
||||||
|
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
|
||||||
|
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_read_mac(s_own_mac, ESP_MAC_WIFI_STA));
|
||||||
|
s_wifi_channel = channel;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_now_core_init_send_done(void) {
|
||||||
|
s_send_done = xSemaphoreCreateBinary();
|
||||||
|
if (s_send_done != NULL &&
|
||||||
|
esp_now_register_send_cb(espnow_send_done_cb) == ESP_OK) {
|
||||||
|
s_send_cb_ready = true;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "send-done callback unavailable (OTA may drop packets)");
|
||||||
|
}
|
||||||
|
}
|
||||||
32
main/esp_now_core.h
Normal file
32
main/esp_now_core.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef ESP_NOW_CORE_H
|
||||||
|
#define ESP_NOW_CORE_H
|
||||||
|
|
||||||
|
#include "app_config.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_now_messages.pb.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void esp_now_core_store_config(const app_config_t *config);
|
||||||
|
const app_config_t *esp_now_core_get_config(void);
|
||||||
|
bool esp_now_core_is_master(void);
|
||||||
|
uint8_t esp_now_core_network(void);
|
||||||
|
uint8_t esp_now_core_wifi_channel(void);
|
||||||
|
const uint8_t *esp_now_core_own_mac(void);
|
||||||
|
|
||||||
|
uint32_t esp_now_core_now_ms(void);
|
||||||
|
bool esp_now_core_mac_equal(const uint8_t *a, const uint8_t *b);
|
||||||
|
void esp_now_core_mac_to_str(const uint8_t *mac, char *out, size_t out_len);
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_ensure_peer(const uint8_t *mac);
|
||||||
|
esp_err_t esp_now_core_ensure_broadcast_peer(void);
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_send(const uint8_t *dest_mac,
|
||||||
|
const alox_EspNowMessage *msg);
|
||||||
|
esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac,
|
||||||
|
const alox_EspNowMessage *msg);
|
||||||
|
|
||||||
|
esp_err_t esp_now_core_init_radio(uint8_t channel);
|
||||||
|
void esp_now_core_init_send_done(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
484
main/esp_now_master.c
Normal file
484
main/esp_now_master.c
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
#include "esp_now_master.h"
|
||||||
|
#include "client_registry.h"
|
||||||
|
#include "esp_now_comm.h"
|
||||||
|
#include "esp_now_core.h"
|
||||||
|
#include "esp_now_proto.h"
|
||||||
|
#include "board_input.h"
|
||||||
|
#include "ota_espnow.h"
|
||||||
|
#include "ota_uart.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/idf_additions.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff};
|
||||||
|
|
||||||
|
#define ESPNOW_DISCOVER_INTERVAL_MS 500
|
||||||
|
#define ESPNOW_HEARTBEAT_INTERVAL_MS 1000
|
||||||
|
#define ESPNOW_HEARTBEAT_MISS_COUNT 3
|
||||||
|
#define ESPNOW_CLIENT_TIMEOUT_MS \
|
||||||
|
(ESPNOW_HEARTBEAT_INTERVAL_MS * ESPNOW_HEARTBEAT_MISS_COUNT)
|
||||||
|
#define ESPNOW_BATTERY_INTERVAL_MS 30000
|
||||||
|
|
||||||
|
static const char *TAG = "[ESPNOW_M]";
|
||||||
|
|
||||||
|
static esp_err_t send_accel_stream(const uint8_t *dest_mac, uint32_t client_id,
|
||||||
|
bool enable) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM;
|
||||||
|
msg.which_payload = alox_EspNowMessage_accel_stream_tag;
|
||||||
|
msg.payload.accel_stream.enable = enable;
|
||||||
|
msg.payload.accel_stream.client_id = client_id;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_accel_deadzone(const uint8_t *dest_mac, uint32_t client_id,
|
||||||
|
uint32_t deadzone) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_SET_ACCEL_DEADZONE;
|
||||||
|
msg.which_payload = alox_EspNowMessage_accel_deadzone_tag;
|
||||||
|
msg.payload.accel_deadzone.deadzone = deadzone;
|
||||||
|
msg.payload.accel_deadzone.client_id = client_id;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_unicast_test(const uint8_t *dest_mac, uint32_t seq) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_UNICAST_TEST;
|
||||||
|
msg.which_payload = alox_EspNowMessage_unicast_test_tag;
|
||||||
|
msg.payload.unicast_test.seq = seq;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_find_me(const uint8_t *dest_mac, uint32_t client_id) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_FIND_ME;
|
||||||
|
msg.which_payload = alox_EspNowMessage_find_me_tag;
|
||||||
|
msg.payload.find_me.client_id = client_id;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_led_ring(const uint8_t *dest_mac, uint32_t client_id,
|
||||||
|
const alox_LedRingProgressRequest *req) {
|
||||||
|
if (req == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_LED_RING;
|
||||||
|
msg.which_payload = alox_EspNowMessage_led_ring_tag;
|
||||||
|
msg.payload.led_ring.client_id = client_id;
|
||||||
|
msg.payload.led_ring.mode = req->mode;
|
||||||
|
msg.payload.led_ring.progress = req->progress;
|
||||||
|
msg.payload.led_ring.digit = req->digit;
|
||||||
|
msg.payload.led_ring.r = req->r;
|
||||||
|
msg.payload.led_ring.g = req->g;
|
||||||
|
msg.payload.led_ring.b = req->b;
|
||||||
|
msg.payload.led_ring.intensity = req->intensity;
|
||||||
|
msg.payload.led_ring.blink_ms = req->blink_ms;
|
||||||
|
msg.payload.led_ring.blink_count = req->blink_count;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_restart(const uint8_t *dest_mac, uint32_t client_id) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_RESTART;
|
||||||
|
msg.which_payload = alox_EspNowMessage_restart_tag;
|
||||||
|
msg.payload.restart.client_id = client_id;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_tap_notify(const uint8_t *dest_mac, uint32_t client_id,
|
||||||
|
bool single, bool double_tap, bool triple) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_SET_TAP_NOTIFY;
|
||||||
|
msg.which_payload = alox_EspNowMessage_tap_notify_tag;
|
||||||
|
msg.payload.tap_notify.client_id = client_id;
|
||||||
|
msg.payload.tap_notify.single = single;
|
||||||
|
msg.payload.tap_notify.double_tap = double_tap;
|
||||||
|
msg.payload.tap_notify.triple = triple;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_ota_start(const uint8_t *dest_mac, uint32_t total_size) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_OTA_START;
|
||||||
|
msg.which_payload = alox_EspNowMessage_ota_start_tag;
|
||||||
|
msg.payload.ota_start.total_size = total_size;
|
||||||
|
return esp_now_core_send_wait(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_ota_payload(const uint8_t *dest_mac, uint32_t seq,
|
||||||
|
const uint8_t *data, size_t len) {
|
||||||
|
if (data == NULL || len == 0 || len > OTA_UART_HOST_CHUNK_SIZE) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_OTA_PAYLOAD;
|
||||||
|
msg.which_payload = alox_EspNowMessage_ota_payload_tag;
|
||||||
|
msg.payload.ota_payload.seq = seq;
|
||||||
|
msg.payload.ota_payload.data.size = len;
|
||||||
|
memcpy(msg.payload.ota_payload.data.bytes, data, len);
|
||||||
|
return esp_now_core_send_wait(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_ota_end(const uint8_t *dest_mac) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_OTA_END;
|
||||||
|
msg.which_payload = alox_EspNowMessage_ota_end_tag;
|
||||||
|
return esp_now_core_send_wait(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_ota_start(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t total_size) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
return send_ota_start(mac, total_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_ota_payload(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t seq, const uint8_t *data,
|
||||||
|
size_t len) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
return send_ota_payload(mac, seq, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_ota_end(const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
return send_ota_end(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_restart(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_restart(mac, client_id);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast RESTART to %s client_id=%lu", mac_str,
|
||||||
|
(unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast RESTART to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_find_me(mac, client_id);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast FIND_ME to %s client_id=%lu", mac_str,
|
||||||
|
(unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast FIND_ME to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_led_ring(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id,
|
||||||
|
const alox_LedRingProgressRequest *req) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master() || req == NULL) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_led_ring(mac, client_id, req);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast LED_RING mode %lu to %s client_id=%lu",
|
||||||
|
(unsigned long)req->mode, mac_str, (unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast LED_RING to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t seq) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_unicast_test(mac, seq);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast TEST to %s seq=%lu", mac_str, (unsigned long)seq);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast TEST to %s failed: %s", mac_str, esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_accel_stream(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id, bool enable) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_accel_stream(mac, client_id, enable);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast SET_ACCEL_STREAM to %s: %s client_id=%lu", mac_str,
|
||||||
|
enable ? "on" : "off", (unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast SET_ACCEL_STREAM to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_tap_notify(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id, bool single,
|
||||||
|
bool double_tap, bool triple) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err =
|
||||||
|
send_tap_notify(mac, client_id, single, double_tap, triple);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"unicast SET_TAP_NOTIFY to %s: single=%d double=%d triple=%d "
|
||||||
|
"client_id=%lu",
|
||||||
|
mac_str, single, double_tap, triple, (unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast SET_TAP_NOTIFY to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_accel_deadzone(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t client_id,
|
||||||
|
uint32_t deadzone) {
|
||||||
|
if (mac == NULL || !esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
esp_err_t err = send_accel_deadzone(mac, client_id, deadzone);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "unicast SET_ACCEL_DEADZONE to %s: deadzone=%lu client_id=%lu",
|
||||||
|
mac_str, (unsigned long)deadzone, (unsigned long)client_id);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "unicast SET_ACCEL_DEADZONE to %s failed: %s", mac_str,
|
||||||
|
esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_accel_sample(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
const alox_EspNowAccelSample *sample) {
|
||||||
|
if (sample == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_err_t err = client_registry_update_accel(
|
||||||
|
mac, sample->slave_id, (int16_t)sample->x, (int16_t)sample->y,
|
||||||
|
(int16_t)sample->z);
|
||||||
|
if (err == ESP_ERR_NOT_FOUND) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "accel sample id mismatch from %02x:…:%02x", mac[0], mac[5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_tap_event(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
const alox_EspNowTapEvent *event) {
|
||||||
|
if (event == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_err_t err =
|
||||||
|
client_registry_update_tap(mac, event->slave_id, event->kind);
|
||||||
|
if (err == ESP_ERR_NOT_FOUND) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "tap event id=%lu kind=%lu rejected from %02x:…:%02x",
|
||||||
|
(unsigned long)event->slave_id, (unsigned long)event->kind, mac[0],
|
||||||
|
mac[5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_battery_report(const uint8_t mac[CLIENT_MAC_LEN],
|
||||||
|
const alox_EspNowBatteryReport *report) {
|
||||||
|
if (report == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_err_t err = client_registry_update_battery(
|
||||||
|
mac, report->client_id, report->lipo1_valid, report->lipo1_mv,
|
||||||
|
report->lipo2_valid, report->lipo2_mv);
|
||||||
|
if (err == ESP_ERR_NOT_FOUND) {
|
||||||
|
ESP_LOGW(TAG, "battery report from unregistered slave id=%lu",
|
||||||
|
(unsigned long)report->client_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "battery report id=%lu rejected: %s",
|
||||||
|
(unsigned long)report->client_id, esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "battery cached id=%lu L1=%s %lu mV L2=%s %lu mV",
|
||||||
|
(unsigned long)report->client_id,
|
||||||
|
report->lipo1_valid ? "ok" : "n/a",
|
||||||
|
(unsigned long)report->lipo1_mv, report->lipo2_valid ? "ok" : "n/a",
|
||||||
|
(unsigned long)report->lipo2_mv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_client_presence(const alox_EspNowSlavePresence *presence,
|
||||||
|
const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||||
|
if (presence->network != esp_now_core_network()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_now_core_ensure_peer(mac);
|
||||||
|
|
||||||
|
bool is_new = false;
|
||||||
|
bool reactivated = false;
|
||||||
|
esp_err_t err = client_registry_heartbeat(
|
||||||
|
mac, presence->slave_id, presence->version, presence->used, &is_new,
|
||||||
|
&reactivated);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "client registry full");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||||
|
if (is_new) {
|
||||||
|
ESP_LOGI(TAG, "client registered id=%lu mac=%s ver=%lu",
|
||||||
|
(unsigned long)presence->slave_id, mac_str,
|
||||||
|
(unsigned long)presence->version);
|
||||||
|
} else if (reactivated) {
|
||||||
|
ESP_LOGI(TAG, "client reconnected id=%lu mac=%s",
|
||||||
|
(unsigned long)presence->slave_id, mac_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_now_master_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||||
|
int len) {
|
||||||
|
if (info == NULL || data == NULL || len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
if (esp_now_proto_decode(data, (size_t)len, &msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed (%d bytes)", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ota_espnow_distribution_active()) {
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_ota_status_tag) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
ota_espnow_master_on_status(info->src_addr, &msg.payload.ota_status);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_ota_status_tag) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
ota_espnow_master_on_status(info->src_addr, &msg.payload.ota_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_accel_sample_tag) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
handle_accel_sample(info->src_addr, &msg.payload.accel_sample);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_tap_event_tag) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
handle_tap_event(info->src_addr, &msg.payload.tap_event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_battery_report_tag) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
handle_battery_report(info->src_addr, &msg.payload.battery_report);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.type == alox_EspNowMessageType_ESPNOW_BATTERY_REPORT &&
|
||||||
|
msg.which_payload != alox_EspNowMessage_battery_report_tag) {
|
||||||
|
ESP_LOGW(TAG, "BATTERY_REPORT type but which=%u", msg.which_payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
const alox_EspNowSlavePresence *presence = esp_now_proto_get_presence(&msg);
|
||||||
|
if (presence != NULL) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
handle_client_presence(presence, info->src_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void discover_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_DISCOVER;
|
||||||
|
msg.which_payload = alox_EspNowMessage_discover_tag;
|
||||||
|
msg.payload.discover.network = esp_now_core_network();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "discover on network %u ch %u", (unsigned)esp_now_core_network(),
|
||||||
|
(unsigned)esp_now_core_wifi_channel());
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
esp_now_core_send(ESPNOW_BCAST, &msg);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ESPNOW_DISCOVER_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void monitor_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
uint32_t last_local_battery_ms = 0;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "monitor (client timeout %u ms)",
|
||||||
|
(unsigned)ESPNOW_CLIENT_TIMEOUT_MS);
|
||||||
|
|
||||||
|
board_lipo_reading_t reading;
|
||||||
|
board_input_read_lipo(&reading);
|
||||||
|
client_registry_set_master_battery(&reading);
|
||||||
|
last_local_battery_ms = esp_now_core_now_ms();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ESPNOW_HEARTBEAT_INTERVAL_MS));
|
||||||
|
client_registry_check_timeouts(ESPNOW_CLIENT_TIMEOUT_MS);
|
||||||
|
|
||||||
|
uint32_t t = esp_now_core_now_ms();
|
||||||
|
if (t - last_local_battery_ms >= ESPNOW_BATTERY_INTERVAL_MS) {
|
||||||
|
board_input_read_lipo(&reading);
|
||||||
|
client_registry_set_master_battery(&reading);
|
||||||
|
last_local_battery_ms = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_master_start(void) {
|
||||||
|
ESP_ERROR_CHECK(esp_now_core_ensure_broadcast_peer());
|
||||||
|
|
||||||
|
if (xTaskCreate(discover_task, "espnow_disc", 4096, NULL, 4, NULL) != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create discover task");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
if (xTaskCreate(monitor_task, "espnow_mon", 4096, NULL, 4, NULL) != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create monitor task");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
11
main/esp_now_master.h
Normal file
11
main/esp_now_master.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef ESP_NOW_MASTER_H
|
||||||
|
#define ESP_NOW_MASTER_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_now.h"
|
||||||
|
|
||||||
|
esp_err_t esp_now_master_start(void);
|
||||||
|
void esp_now_master_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||||
|
int len);
|
||||||
|
|
||||||
|
#endif
|
||||||
579
main/esp_now_slave.c
Normal file
579
main/esp_now_slave.c
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
#include "esp_now_slave.h"
|
||||||
|
#include "bosch456.h"
|
||||||
|
#include "cmd_led_ring.h"
|
||||||
|
#include "esp_now_comm.h"
|
||||||
|
#include "esp_now_core.h"
|
||||||
|
#include "esp_now_proto.h"
|
||||||
|
#include "board_input.h"
|
||||||
|
#include "led_ring.h"
|
||||||
|
#include "ota_espnow.h"
|
||||||
|
#include "ota_uart.h"
|
||||||
|
#include "pod_reboot.h"
|
||||||
|
#include "pod_settings.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/idf_additions.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef POWERPOD_FW_VERSION
|
||||||
|
#define POWERPOD_FW_VERSION 1u
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ESPNOW_HEARTBEAT_INTERVAL_MS 1000
|
||||||
|
#define SLAVE_MASTER_LOST_MS (ESPNOW_HEARTBEAT_INTERVAL_MS * 5)
|
||||||
|
#define ESPNOW_ACCEL_INTERVAL_MS 16
|
||||||
|
#define ESPNOW_BATTERY_INTERVAL_MS 30000
|
||||||
|
#define SLAVE_BATTERY_AFTER_JOIN_MS 150
|
||||||
|
|
||||||
|
static const char *TAG = "[ESPNOW_S]";
|
||||||
|
|
||||||
|
static bool s_joined;
|
||||||
|
static bool s_accel_stream_enabled;
|
||||||
|
static bool s_tap_notify_single;
|
||||||
|
static bool s_tap_notify_double;
|
||||||
|
static bool s_tap_notify_triple;
|
||||||
|
static uint8_t s_master_mac[ESP_NOW_ETH_ALEN];
|
||||||
|
static uint32_t s_last_discover_ms;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SLAVE_TX_SLAVE_INFO = 1,
|
||||||
|
SLAVE_TX_BATTERY,
|
||||||
|
} slave_tx_op_t;
|
||||||
|
|
||||||
|
static QueueHandle_t s_tx_queue;
|
||||||
|
|
||||||
|
static bool from_joined_master(const uint8_t *master_mac) {
|
||||||
|
return s_joined && esp_now_core_mac_equal(master_mac, s_master_mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_presence(alox_EspNowSlavePresence *presence) {
|
||||||
|
const uint8_t *own = esp_now_core_own_mac();
|
||||||
|
presence->network = esp_now_core_network();
|
||||||
|
presence->version = POWERPOD_FW_VERSION;
|
||||||
|
presence->slave_id = own[5];
|
||||||
|
presence->available = true;
|
||||||
|
presence->used = false;
|
||||||
|
esp_now_proto_setup_presence_encode(presence, own);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_presence(const uint8_t *dest_mac, alox_EspNowMessageType type) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
alox_EspNowSlavePresence *presence = NULL;
|
||||||
|
|
||||||
|
msg.type = type;
|
||||||
|
if (type == alox_EspNowMessageType_ESPNOW_SLAVE_INFO) {
|
||||||
|
msg.which_payload = alox_EspNowMessage_slave_info_tag;
|
||||||
|
presence = &msg.payload.slave_info;
|
||||||
|
} else {
|
||||||
|
msg.which_payload = alox_EspNowMessage_heartbeat_tag;
|
||||||
|
presence = &msg.payload.heartbeat;
|
||||||
|
}
|
||||||
|
fill_presence(presence);
|
||||||
|
esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_accel_sample(const uint8_t *dest_mac, uint32_t slave_id,
|
||||||
|
int16_t x, int16_t y, int16_t z) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_ACCEL_SAMPLE;
|
||||||
|
msg.which_payload = alox_EspNowMessage_accel_sample_tag;
|
||||||
|
msg.payload.accel_sample.slave_id = slave_id;
|
||||||
|
msg.payload.accel_sample.x = x;
|
||||||
|
msg.payload.accel_sample.y = y;
|
||||||
|
msg.payload.accel_sample.z = z;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_tap_event(const uint8_t *dest_mac, uint32_t slave_id,
|
||||||
|
uint32_t kind) {
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_TAP_EVENT;
|
||||||
|
msg.which_payload = alox_EspNowMessage_tap_event_tag;
|
||||||
|
msg.payload.tap_event.slave_id = slave_id;
|
||||||
|
msg.payload.tap_event.kind = kind;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t send_battery_report(const uint8_t *dest_mac,
|
||||||
|
const alox_EspNowBatteryReport *report) {
|
||||||
|
if (report == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_BATTERY_REPORT;
|
||||||
|
msg.which_payload = alox_EspNowMessage_battery_report_tag;
|
||||||
|
msg.payload.battery_report = *report;
|
||||||
|
return esp_now_core_send(dest_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_join(void) {
|
||||||
|
s_joined = false;
|
||||||
|
s_accel_stream_enabled = false;
|
||||||
|
memset(s_master_mac, 0, sizeof(s_master_mac));
|
||||||
|
s_last_discover_ms = 0;
|
||||||
|
if (s_tx_queue != NULL) {
|
||||||
|
xQueueReset(s_tx_queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_tx(slave_tx_op_t op) {
|
||||||
|
if (s_tx_queue == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xQueueSend(s_tx_queue, &op, 0) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "tx queue full (op=%d)", (int)op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_battery_to_master(void) {
|
||||||
|
if (!s_joined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
board_lipo_reading_t reading;
|
||||||
|
board_input_read_lipo(&reading);
|
||||||
|
|
||||||
|
alox_EspNowBatteryReport report = alox_EspNowBatteryReport_init_zero;
|
||||||
|
report.client_id = esp_now_core_own_mac()[5];
|
||||||
|
report.lipo1_valid = reading.lipo1_valid;
|
||||||
|
report.lipo2_valid = reading.lipo2_valid;
|
||||||
|
report.lipo1_mv = reading.lipo1_mv;
|
||||||
|
report.lipo2_mv = reading.lipo2_mv;
|
||||||
|
|
||||||
|
esp_err_t err = send_battery_report(s_master_mac, &report);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "battery report send failed id=%lu: %s",
|
||||||
|
(unsigned long)report.client_id, esp_err_to_name(err));
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "battery report sent id=%lu L1=%s %lu mV L2=%s %lu mV",
|
||||||
|
(unsigned long)report.client_id,
|
||||||
|
report.lipo1_valid ? "ok" : "n/a",
|
||||||
|
(unsigned long)report.lipo1_mv,
|
||||||
|
report.lipo2_valid ? "ok" : "n/a",
|
||||||
|
(unsigned long)report.lipo2_mv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_comm_send_ota_status(const uint8_t master_mac[CLIENT_MAC_LEN],
|
||||||
|
uint32_t status, uint32_t bytes_written,
|
||||||
|
uint32_t error) {
|
||||||
|
if (master_mac == NULL || esp_now_core_is_master()) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
msg.type = alox_EspNowMessageType_ESPNOW_OTA_STATUS;
|
||||||
|
msg.which_payload = alox_EspNowMessage_ota_status_tag;
|
||||||
|
msg.payload.ota_status.status = status;
|
||||||
|
msg.payload.ota_status.bytes_written = bytes_written;
|
||||||
|
msg.payload.ota_status.error = error;
|
||||||
|
return esp_now_core_send_wait(master_mac, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esp_now_comm_get_master_mac(uint8_t mac_out[CLIENT_MAC_LEN]) {
|
||||||
|
return esp_now_slave_get_master_mac(mac_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esp_now_slave_get_master_mac(uint8_t mac_out[CLIENT_MAC_LEN]) {
|
||||||
|
if (mac_out == NULL || !s_joined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(mac_out, s_master_mac, CLIENT_MAC_LEN);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tx_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
slave_tx_op_t op;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "deferred tx task ready");
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (xQueueReceive(s_tx_queue, &op, portMAX_DELAY) != pdTRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!s_joined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (op) {
|
||||||
|
case SLAVE_TX_SLAVE_INFO:
|
||||||
|
send_presence(s_master_mac, alox_EspNowMessageType_ESPNOW_SLAVE_INFO);
|
||||||
|
break;
|
||||||
|
case SLAVE_TX_BATTERY:
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(SLAVE_BATTERY_AFTER_JOIN_MS));
|
||||||
|
send_battery_to_master();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_unicast_test(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowUnicastTest *test) {
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "UNICAST TEST OK from master %s seq=%lu (joined=%d)", mac_str,
|
||||||
|
(unsigned long)test->seq, (int)s_joined);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_restart(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowRestart *req) {
|
||||||
|
const uint8_t *own = esp_now_core_own_mac();
|
||||||
|
uint32_t my_id = own[5];
|
||||||
|
|
||||||
|
if (req->client_id != 0 && req->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "RESTART from master %s (id=%lu)", mac_str, (unsigned long)my_id);
|
||||||
|
pod_schedule_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_battery_query(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowBatteryQuery *query) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (query->client_id != 0 && query->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send_battery_to_master();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_led_ring(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowLedRing *msg) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (msg->client_id != 0 && msg->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alox_LedRingProgressRequest req = alox_LedRingProgressRequest_init_zero;
|
||||||
|
req.mode = msg->mode;
|
||||||
|
req.progress = msg->progress;
|
||||||
|
req.digit = msg->digit;
|
||||||
|
req.r = msg->r;
|
||||||
|
req.g = msg->g;
|
||||||
|
req.b = msg->b;
|
||||||
|
req.intensity = msg->intensity;
|
||||||
|
req.blink_ms = msg->blink_ms;
|
||||||
|
req.blink_count = msg->blink_count;
|
||||||
|
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "LED_RING mode %lu from master %s (id=%lu)",
|
||||||
|
(unsigned long)req.mode, mac_str, (unsigned long)my_id);
|
||||||
|
cmd_led_ring_apply(&req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_find_me(const uint8_t *master_mac, const alox_EspNowFindMe *req) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (req->client_id != 0 && req->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "FIND_ME from master %s (id=%lu)", mac_str, (unsigned long)my_id);
|
||||||
|
led_ring_find_me();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_accel_stream(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowAccelStream *cfg) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (cfg->client_id != 0 && cfg->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_accel_stream_enabled = cfg->enable;
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "accel stream %s from master %s (id=%lu)",
|
||||||
|
cfg->enable ? "on" : "off", mac_str, (unsigned long)my_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_tap_notify(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowTapNotify *cfg) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (cfg->client_id != 0 && cfg->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_tap_notify_single = cfg->single;
|
||||||
|
s_tap_notify_double = cfg->double_tap;
|
||||||
|
s_tap_notify_triple = cfg->triple;
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"tap notify single=%d double=%d triple=%d from master %s (id=%lu)",
|
||||||
|
cfg->single, cfg->double_tap, cfg->triple, mac_str,
|
||||||
|
(unsigned long)my_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_accel_deadzone(const uint8_t *master_mac,
|
||||||
|
const alox_EspNowAccelDeadzone *cfg) {
|
||||||
|
uint32_t my_id = esp_now_core_own_mac()[5];
|
||||||
|
if (cfg->client_id != 0 && cfg->client_id != my_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"accel deadzone from master %s: %lu LSB id=%lu (sensor %s)", mac_str,
|
||||||
|
(unsigned long)cfg->deadzone, (unsigned long)my_id,
|
||||||
|
bma456_is_ready() ? "ok" : "not installed");
|
||||||
|
bma456_set_accel_deadzone(cfg->deadzone);
|
||||||
|
if (pod_settings_save_accel_deadzone(cfg->deadzone) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "deadzone %lu applied but not saved to NVS",
|
||||||
|
(unsigned long)cfg->deadzone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_discover(const uint8_t *sender_mac,
|
||||||
|
const alox_EspNowDiscover *discover) {
|
||||||
|
if (discover->network != esp_now_core_network()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t now = esp_now_core_now_ms();
|
||||||
|
|
||||||
|
if (s_joined) {
|
||||||
|
if (!esp_now_core_mac_equal(sender_mac, s_master_mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((now - s_last_discover_ms) <= SLAVE_MASTER_LOST_MS) {
|
||||||
|
s_last_discover_ms = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG, "master lost, rejoining");
|
||||||
|
reset_join();
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(s_master_mac, sender_mac, ESP_NOW_ETH_ALEN);
|
||||||
|
s_joined = true;
|
||||||
|
s_last_discover_ms = now;
|
||||||
|
esp_now_core_ensure_peer(sender_mac);
|
||||||
|
|
||||||
|
char mac_str[18];
|
||||||
|
esp_now_core_mac_to_str(sender_mac, mac_str, sizeof(mac_str));
|
||||||
|
ESP_LOGI(TAG, "joined network %u, master %s", (unsigned)discover->network,
|
||||||
|
mac_str);
|
||||||
|
|
||||||
|
queue_tx(SLAVE_TX_SLAVE_INFO);
|
||||||
|
queue_tx(SLAVE_TX_BATTERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_master_timeout(void) {
|
||||||
|
if (!s_joined || s_last_discover_ms == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t now = esp_now_core_now_ms();
|
||||||
|
if ((now - s_last_discover_ms) > SLAVE_MASTER_LOST_MS) {
|
||||||
|
ESP_LOGW(TAG, "no master discover for %u ms, reconnecting",
|
||||||
|
(unsigned)(now - s_last_discover_ms));
|
||||||
|
reset_join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void accel_stream_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
const uint8_t *own = esp_now_core_own_mac();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "accel stream task (interval %u ms)",
|
||||||
|
(unsigned)ESPNOW_ACCEL_INTERVAL_MS);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS));
|
||||||
|
if (!s_joined || !s_accel_stream_enabled || !bma456_is_ready()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int16_t x = 0;
|
||||||
|
int16_t y = 0;
|
||||||
|
int16_t z = 0;
|
||||||
|
if (bma456_read_accel(&x, &y, &z) != ESP_OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(void)send_accel_sample(s_master_mac, own[5], x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_bma456_tap(bma456_tap_kind_t kind, void *ctx) {
|
||||||
|
(void)ctx;
|
||||||
|
if (!s_joined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool enabled = false;
|
||||||
|
switch (kind) {
|
||||||
|
case BMA456_TAP_SINGLE:
|
||||||
|
enabled = s_tap_notify_single;
|
||||||
|
break;
|
||||||
|
case BMA456_TAP_DOUBLE:
|
||||||
|
enabled = s_tap_notify_double;
|
||||||
|
break;
|
||||||
|
case BMA456_TAP_TRIPLE:
|
||||||
|
enabled = s_tap_notify_triple;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(void)send_tap_event(s_master_mac, esp_now_core_own_mac()[5], (uint32_t)kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void heartbeat_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
uint32_t last_battery_ms = 0;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "heartbeat task (interval %u ms)",
|
||||||
|
(unsigned)ESPNOW_HEARTBEAT_INTERVAL_MS);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ESPNOW_HEARTBEAT_INTERVAL_MS));
|
||||||
|
check_master_timeout();
|
||||||
|
if (!s_joined) {
|
||||||
|
last_battery_ms = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
send_presence(s_master_mac, alox_EspNowMessageType_ESPNOW_HEARTBEAT);
|
||||||
|
uint32_t now = esp_now_core_now_ms();
|
||||||
|
if (last_battery_ms == 0 ||
|
||||||
|
(now - last_battery_ms) >= ESPNOW_BATTERY_INTERVAL_MS) {
|
||||||
|
send_battery_to_master();
|
||||||
|
last_battery_ms = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||||
|
int len) {
|
||||||
|
if (info == NULL || data == NULL || len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||||
|
if (esp_now_proto_decode(data, (size_t)len, &msg) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "decode failed (%d bytes)", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
esp_now_core_ensure_peer(info->src_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ota_uart_is_active()) {
|
||||||
|
switch (msg.which_payload) {
|
||||||
|
case alox_EspNowMessage_ota_start_tag:
|
||||||
|
case alox_EspNowMessage_ota_payload_tag:
|
||||||
|
case alox_EspNowMessage_ota_end_tag:
|
||||||
|
if (!from_joined_master(info->src_addr)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (msg.which_payload == alox_EspNowMessage_ota_start_tag) {
|
||||||
|
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
|
||||||
|
} else if (msg.which_payload == alox_EspNowMessage_ota_payload_tag) {
|
||||||
|
ota_espnow_slave_on_payload(info->src_addr, &msg.payload.ota_payload);
|
||||||
|
} else {
|
||||||
|
ota_espnow_slave_on_end(info->src_addr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg.which_payload) {
|
||||||
|
case alox_EspNowMessage_discover_tag:
|
||||||
|
handle_discover(info->src_addr, &msg.payload.discover);
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_unicast_test_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_unicast_test(info->src_addr, &msg.payload.unicast_test);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_accel_deadzone_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_accel_stream_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_accel_stream(info->src_addr, &msg.payload.accel_stream);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_tap_notify_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_tap_notify(info->src_addr, &msg.payload.tap_notify);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_battery_query_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_battery_query(info->src_addr, &msg.payload.battery_query);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_led_ring_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_led_ring(info->src_addr, &msg.payload.led_ring);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_find_me_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_find_me(info->src_addr, &msg.payload.find_me);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case alox_EspNowMessage_restart_tag:
|
||||||
|
if (from_joined_master(info->src_addr)) {
|
||||||
|
handle_restart(info->src_addr, &msg.payload.restart);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGW(TAG, "unhandled which=%u type=%u", msg.which_payload,
|
||||||
|
(unsigned)msg.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_now_slave_start(void) {
|
||||||
|
reset_join();
|
||||||
|
|
||||||
|
s_tx_queue = xQueueCreate(4, sizeof(slave_tx_op_t));
|
||||||
|
if (s_tx_queue == NULL) {
|
||||||
|
ESP_LOGE(TAG, "failed to create tx queue");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
if (xTaskCreate(tx_task, "espnow_stx", 4096, NULL, 5, NULL) != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create tx task");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
if (xTaskCreate(heartbeat_task, "espnow_hb", 4096, NULL, 4, NULL) != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create heartbeat task");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
if (xTaskCreate(accel_stream_task, "espnow_accel", 4096, NULL, 5, NULL) !=
|
||||||
|
pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create accel stream task");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
ota_espnow_slave_init();
|
||||||
|
bma456_set_tap_handler(on_bma456_tap, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
14
main/esp_now_slave.h
Normal file
14
main/esp_now_slave.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef ESP_NOW_SLAVE_H
|
||||||
|
#define ESP_NOW_SLAVE_H
|
||||||
|
|
||||||
|
#include "client_registry.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_now.h"
|
||||||
|
|
||||||
|
esp_err_t esp_now_slave_start(void);
|
||||||
|
void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||||
|
int len);
|
||||||
|
|
||||||
|
bool esp_now_slave_get_master_mac(uint8_t mac_out[CLIENT_MAC_LEN]);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -35,7 +35,28 @@ static const char *TAG = "[OTA_ESPNOW]";
|
|||||||
|
|
||||||
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
|
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
|
||||||
|
|
||||||
|
#define OTA_SLAVE_WORK_QUEUE_LEN 12
|
||||||
|
#define OTA_SLAVE_WORK_STACK 8192
|
||||||
|
#define OTA_SLAVE_WORK_PRIO 5
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OTA_SLAVE_WORK_STATUS = 1,
|
||||||
|
OTA_SLAVE_WORK_PAYLOAD,
|
||||||
|
OTA_SLAVE_WORK_END,
|
||||||
|
} ota_slave_work_op_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ota_slave_work_op_t op;
|
||||||
|
uint8_t master_mac[6];
|
||||||
|
uint32_t status;
|
||||||
|
uint32_t bytes_written;
|
||||||
|
uint32_t error;
|
||||||
|
alox_EspNowOtaPayload payload;
|
||||||
|
} ota_slave_work_t;
|
||||||
|
|
||||||
static EventGroupHandle_t s_eg;
|
static EventGroupHandle_t s_eg;
|
||||||
|
static QueueHandle_t s_slave_work_queue;
|
||||||
|
static bool s_distribution_active;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
@ -152,56 +173,37 @@ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) {
|
|||||||
return (got & want_bits) == want_bits;
|
return (got & want_bits) == want_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ota_espnow_distribution_active(void) { return s_distribution_active; }
|
||||||
|
|
||||||
static void send_slave_status(const uint8_t master_mac[6], uint32_t status,
|
static void send_slave_status(const uint8_t master_mac[6], uint32_t status,
|
||||||
uint32_t bytes_written, uint32_t error) {
|
uint32_t bytes_written, uint32_t error) {
|
||||||
esp_now_comm_send_ota_status(master_mac, status, bytes_written, error);
|
esp_now_comm_send_ota_status(master_mac, status, bytes_written, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ota_slave_prepare_task(void *param) {
|
static bool queue_slave_work(const ota_slave_work_t *work) {
|
||||||
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
if (work == NULL || s_slave_work_queue == NULL) {
|
||||||
uint8_t master_mac[6];
|
return false;
|
||||||
|
}
|
||||||
if (!esp_now_comm_get_master_mac(master_mac)) {
|
if (xQueueSend(s_slave_work_queue, work, 0) != pdTRUE) {
|
||||||
vTaskDelete(NULL);
|
ESP_LOGW(TAG, "slave OTA work queue full (op=%d)", (int)work->op);
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_slave_status(master_mac, OTA_ST_PREPARING, 0, 0);
|
static void queue_slave_status(const uint8_t master_mac[6], uint32_t status,
|
||||||
|
uint32_t bytes_written, uint32_t error) {
|
||||||
int slot = ota_uart_prepare(total_size);
|
ota_slave_work_t work = {
|
||||||
if (slot < 0) {
|
.op = OTA_SLAVE_WORK_STATUS,
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 1);
|
.status = status,
|
||||||
vTaskDelete(NULL);
|
.bytes_written = bytes_written,
|
||||||
return;
|
.error = error,
|
||||||
|
};
|
||||||
|
memcpy(work.master_mac, master_mac, 6);
|
||||||
|
(void)queue_slave_work(&work);
|
||||||
}
|
}
|
||||||
|
|
||||||
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
static void process_slave_payload(const uint8_t master_mac[6],
|
||||||
led_ring_show_ota_progress(0, total_size, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
|
||||||
OTA_LED_ESPNOW_RX_B);
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ota_espnow_slave_on_start(const uint8_t master_mac[6],
|
|
||||||
const alox_EspNowOtaStart *start) {
|
|
||||||
if (start == NULL || start->total_size == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "ESP-NOW OTA_START (%lu bytes)", (unsigned long)start->total_size);
|
|
||||||
|
|
||||||
if (ota_uart_is_active()) {
|
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 4);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xTaskCreate(ota_slave_prepare_task, "ota_esp_prep", OTA_ESPNOW_PREPARE_STACK,
|
|
||||||
(void *)(uintptr_t)start->total_size, OTA_ESPNOW_PREPARE_PRIO,
|
|
||||||
NULL) != pdPASS) {
|
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
|
||||||
const alox_EspNowOtaPayload *payload) {
|
const alox_EspNowOtaPayload *payload) {
|
||||||
if (payload == NULL || payload->data.size == 0) {
|
if (payload == NULL || payload->data.size == 0) {
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 11);
|
send_slave_status(master_mac, OTA_ST_FAILED, 0, 11);
|
||||||
@ -246,7 +248,7 @@ void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
static void process_slave_end(const uint8_t master_mac[6]) {
|
||||||
ESP_LOGI(TAG, "ESP-NOW OTA_END");
|
ESP_LOGI(TAG, "ESP-NOW OTA_END");
|
||||||
if (!ota_uart_is_active()) {
|
if (!ota_uart_is_active()) {
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 20);
|
send_slave_status(master_mac, OTA_ST_FAILED, 0, 20);
|
||||||
@ -268,6 +270,113 @@ void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
|||||||
(unsigned long)written);
|
(unsigned long)written);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ota_slave_work_task(void *param) {
|
||||||
|
(void)param;
|
||||||
|
ota_slave_work_t work;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (xQueueReceive(s_slave_work_queue, &work, portMAX_DELAY) != pdTRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (work.op) {
|
||||||
|
case OTA_SLAVE_WORK_STATUS:
|
||||||
|
send_slave_status(work.master_mac, work.status, work.bytes_written,
|
||||||
|
work.error);
|
||||||
|
break;
|
||||||
|
case OTA_SLAVE_WORK_PAYLOAD:
|
||||||
|
process_slave_payload(work.master_mac, &work.payload);
|
||||||
|
break;
|
||||||
|
case OTA_SLAVE_WORK_END:
|
||||||
|
process_slave_end(work.master_mac);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_espnow_slave_init(void) {
|
||||||
|
if (s_slave_work_queue != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_slave_work_queue = xQueueCreate(OTA_SLAVE_WORK_QUEUE_LEN, sizeof(ota_slave_work_t));
|
||||||
|
if (s_slave_work_queue == NULL) {
|
||||||
|
ESP_LOGE(TAG, "failed to create slave OTA work queue");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xTaskCreate(ota_slave_work_task, "ota_slave_wrk", OTA_SLAVE_WORK_STACK, NULL,
|
||||||
|
OTA_SLAVE_WORK_PRIO, NULL) != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "failed to create slave OTA work task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ota_slave_prepare_task(void *param) {
|
||||||
|
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
||||||
|
uint8_t master_mac[6];
|
||||||
|
|
||||||
|
if (!esp_now_comm_get_master_mac(master_mac)) {
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_slave_status(master_mac, OTA_ST_PREPARING, 0, 0);
|
||||||
|
|
||||||
|
int slot = ota_uart_prepare(total_size);
|
||||||
|
if (slot < 0) {
|
||||||
|
send_slave_status(master_mac, OTA_ST_FAILED, 0, 1);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
||||||
|
led_ring_show_ota_progress(0, total_size, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||||
|
OTA_LED_ESPNOW_RX_B);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_espnow_slave_on_start(const uint8_t master_mac[6],
|
||||||
|
const alox_EspNowOtaStart *start) {
|
||||||
|
if (start == NULL || start->total_size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "ESP-NOW OTA_START (%lu bytes)", (unsigned long)start->total_size);
|
||||||
|
|
||||||
|
if (ota_uart_is_active()) {
|
||||||
|
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTaskCreate(ota_slave_prepare_task, "ota_esp_prep", OTA_ESPNOW_PREPARE_STACK,
|
||||||
|
(void *)(uintptr_t)start->total_size, OTA_ESPNOW_PREPARE_PRIO,
|
||||||
|
NULL) != pdPASS) {
|
||||||
|
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
||||||
|
const alox_EspNowOtaPayload *payload) {
|
||||||
|
if (payload == NULL) {
|
||||||
|
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 11);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ota_slave_work_t work = {.op = OTA_SLAVE_WORK_PAYLOAD, .payload = *payload};
|
||||||
|
memcpy(work.master_mac, master_mac, 6);
|
||||||
|
if (!queue_slave_work(&work)) {
|
||||||
|
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
||||||
|
ota_slave_work_t work = {.op = OTA_SLAVE_WORK_END};
|
||||||
|
memcpy(work.master_mac, master_mac, 6);
|
||||||
|
if (!queue_slave_work(&work)) {
|
||||||
|
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ota_espnow_master_on_status(const uint8_t slave_mac[6],
|
void ota_espnow_master_on_status(const uint8_t slave_mac[6],
|
||||||
const alox_EspNowOtaStatus *status) {
|
const alox_EspNowOtaStatus *status) {
|
||||||
if (status == NULL || s_eg == NULL) {
|
if (status == NULL || s_eg == NULL) {
|
||||||
@ -342,6 +451,8 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s_distribution_active = true;
|
||||||
|
|
||||||
memset(&s_dist.progress, 0, sizeof(s_dist.progress));
|
memset(&s_dist.progress, 0, sizeof(s_dist.progress));
|
||||||
if (progress != NULL) {
|
if (progress != NULL) {
|
||||||
s_dist.progress = *progress;
|
s_dist.progress = *progress;
|
||||||
@ -362,6 +473,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
ESP_LOGW(TAG, "OTA_START to slave %lu failed",
|
ESP_LOGW(TAG, "OTA_START to slave %lu failed",
|
||||||
(unsigned long)s_dist.id[i]);
|
(unsigned long)s_dist.id[i]);
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,6 +481,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
if (!wait_target_bits(target_mask, OTA_PREPARE_TIMEOUT_MS)) {
|
if (!wait_target_bits(target_mask, OTA_PREPARE_TIMEOUT_MS)) {
|
||||||
ESP_LOGE(TAG, "timeout waiting for slave OTA ready");
|
ESP_LOGE(TAG, "timeout waiting for slave OTA ready");
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,6 +505,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
ESP_LOGE(TAG, "partition read @%lu failed: %s", (unsigned long)offset,
|
ESP_LOGE(TAG, "partition read @%lu failed: %s", (unsigned long)offset,
|
||||||
esp_err_to_name(err));
|
esp_err_to_name(err));
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,6 +521,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
block_buf + sent, chunk);
|
block_buf + sent, chunk);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,6 +539,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
ESP_LOGE(TAG, "timeout block ack @%lu bytes",
|
ESP_LOGE(TAG, "timeout block ack @%lu bytes",
|
||||||
(unsigned long)s_dist.expected_bytes);
|
(unsigned long)s_dist.expected_bytes);
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "block ack @%lu/%lu (%lu%%)",
|
ESP_LOGI(TAG, "block ack @%lu/%lu (%lu%%)",
|
||||||
@ -445,6 +561,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
err = esp_now_comm_send_ota_end(s_dist.mac[i]);
|
err = esp_now_comm_send_ota_end(s_dist.mac[i]);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,11 +569,13 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
|||||||
if (!wait_target_bits(target_mask, OTA_END_TIMEOUT_MS)) {
|
if (!wait_target_bits(target_mask, OTA_END_TIMEOUT_MS)) {
|
||||||
ESP_LOGE(TAG, "timeout waiting for slave OTA success");
|
ESP_LOGE(TAG, "timeout waiting for slave OTA success");
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
prog_set_aggregate(size);
|
prog_set_aggregate(size);
|
||||||
prog_end();
|
prog_end();
|
||||||
|
s_distribution_active = false;
|
||||||
ESP_LOGI(TAG, "ESP-NOW OTA complete for %u slave(s)", (unsigned)s_dist.count);
|
ESP_LOGI(TAG, "ESP-NOW OTA complete for %u slave(s)", (unsigned)s_dist.count);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,4 +37,10 @@ void ota_espnow_slave_on_end(const uint8_t master_mac[6]);
|
|||||||
void ota_espnow_progress_query(uint32_t filter_client_id,
|
void ota_espnow_progress_query(uint32_t filter_client_id,
|
||||||
alox_OtaSlaveProgressResponse *out);
|
alox_OtaSlaveProgressResponse *out);
|
||||||
|
|
||||||
|
/** True while master is pushing a staged image to slaves. */
|
||||||
|
bool ota_espnow_distribution_active(void);
|
||||||
|
|
||||||
|
/** Slave: work queue for OTA (no esp_now_send from recv callback). */
|
||||||
|
void ota_espnow_slave_init(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
25
main/ota_session.c
Normal file
25
main/ota_session.c
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "ota_session.h"
|
||||||
|
#include "ota_espnow.h"
|
||||||
|
#include "ota_uart.h"
|
||||||
|
#include "uart_messages.pb.h"
|
||||||
|
|
||||||
|
bool ota_session_busy(void) {
|
||||||
|
return ota_uart_is_active() || ota_espnow_distribution_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ota_session_uart_cmd_allowed(uint16_t msg_id) {
|
||||||
|
if (!ota_session_busy()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ((alox_MessageType)msg_id) {
|
||||||
|
case alox_MessageType_OTA_START:
|
||||||
|
case alox_MessageType_OTA_PAYLOAD:
|
||||||
|
case alox_MessageType_OTA_END:
|
||||||
|
case alox_MessageType_OTA_START_ESPNOW:
|
||||||
|
case alox_MessageType_OTA_SLAVE_PROGRESS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
main/ota_session.h
Normal file
13
main/ota_session.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef OTA_SESSION_H
|
||||||
|
#define OTA_SESSION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** UART upload or ESP-NOW slave distribution in progress. */
|
||||||
|
bool ota_session_busy(void);
|
||||||
|
|
||||||
|
/** During OTA only UART OTA-related commands are accepted on the master. */
|
||||||
|
bool ota_session_uart_cmd_allowed(uint16_t msg_id);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
x
Reference in New Issue
Block a user