/* Session 6 — ESP-NOW gateway (center of the star) — receives all field nodes and prints JSON lines. Connect it by USB to the farm laptop: python serial_to_api.py forwards each line to the Flask API, or extend it to publish over MQTT (session 7). Detects lost packets with the sequence number and marks a node offline when nothing arrives for 3 minutes. ESP32 Arduino core 3.x callback signature (core 2.x: void onRecv(const uint8_t *mac, const uint8_t *data, int len)). */ #include #include #include const uint8_t CHANNEL = 6; typedef struct __attribute__((packed)) { uint8_t node; uint32_t seq; float temp; float hum; uint16_t soilRaw; float battery; } Reading; uint32_t lastSeq[21] = {0}; unsigned long lastSeen[21] = {0}; uint32_t lost[21] = {0}; void onRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) { if (len != sizeof(Reading)) return; Reading r; memcpy(&r, data, sizeof(r)); if (r.node > 20) return; if (lastSeq[r.node] && r.seq > lastSeq[r.node] + 1) lost[r.node] += r.seq - lastSeq[r.node] - 1; lastSeq[r.node] = r.seq; lastSeen[r.node] = millis(); Serial.printf("{\"node_id\":\"node_%02d\",\"seq\":%lu,\"air_temp_c\":%.1f,\"air_humidity_pct\":%.1f," "\"soil_raw\":%u,\"battery_v\":%.2f,\"rssi\":%d,\"lost\":%lu}\n", r.node, r.seq, r.temp, r.hum, r.soilRaw, r.battery, info->rx_ctrl->rssi, lost[r.node]); } void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE); esp_now_init(); esp_now_register_recv_cb(onRecv); Serial.print("# gateway MAC (copy into the nodes): "); Serial.println(WiFi.macAddress()); } void loop() { static unsigned long check = 0; if (millis() - check > 30000) { check = millis(); for (int n = 1; n <= 20; n++) if (lastSeen[n] && millis() - lastSeen[n] > 180000) Serial.printf("# node_%02d OFFLINE\n", n); } }