Files
egorin/lora/node_lora_energy_v2.ino
T

153 lines
4.2 KiB
Arduino

/*
* ================================================================
* LoRa-УЗЕЛ v2: электроэнергия + реле → LoRa → шлюз
* Отличия от v1:
* - пакет info при старте и раз в 10 мин (саморегистрация)
* - поле "t": "info" | "d" — шлюз раскладывает по топикам nodes/
* Железо и распиновка — как в v1 (ESP32 DevKit + PZEM + RA-01H):
* PZEM: RX←GPIO17, TX→GPIO16 (Serial2)
* LoRa: SCK=18 MISO=19 MOSI=23 NSS=5 RST=14 DIO0=26
* Входы реле: 32, 33, 25, 27 · Выходы: 4, 13, 21, 22
* ================================================================
*/
#include <SPI.h>
#include <LoRa.h>
#include <PZEM004Tv30.h>
#include <ArduinoJson.h>
#define NODE_ID "eg01"
#define FW_VER "2.0"
#define SEND_PERIOD_MS 30000UL
#define INFO_PERIOD_MS 600000UL
#define LORA_FREQ 869000000E0
#define LORA_SF 9
#define LORA_BW 125E3
#define LORA_SYNC 0x2B
#define LORA_TX_DBM 20
#define LORA_NSS 5
#define LORA_RST 14
#define LORA_DIO0 26
const uint8_t RELAY_IN[] = {32, 33, 25, 27};
const uint8_t RELAY_OUT[] = {4, 13, 21, 22};
const uint8_t N_RELAYS = 4;
PZEM004Tv30 pzem(Serial2, 16, 17);
unsigned long lastSend = 0, lastInfo = 0;
uint16_t seq = 0;
void loraSend(const char *buf, size_t n) {
LoRa.beginPacket();
LoRa.write((const uint8_t*)buf, n);
LoRa.endPacket();
LoRa.receive();
}
void sendInfo() {
StaticJsonDocument<224> doc;
doc["id"] = NODE_ID;
doc["t"] = "info";
doc["fw"] = FW_VER;
doc["hw"] = "lora";
JsonObject caps = doc.createNestedObject("caps");
caps["pzem"] = true;
caps["relays"] = N_RELAYS;
caps["ctl"] = true;
char buf[224];
loraSend(buf, serializeJson(doc, buf));
Serial.println("TX info");
}
void sendTelemetry() {
StaticJsonDocument<288> doc;
doc["id"] = NODE_ID;
doc["t"] = "d";
doc["seq"] = seq++;
float v = pzem.voltage();
if (!isnan(v)) {
doc["v"] = serialized(String(v, 1));
doc["a"] = serialized(String(pzem.current(), 3));
doc["w"] = serialized(String(pzem.power(), 1));
doc["kwh"] = serialized(String(pzem.energy(), 3));
doc["pf"] = serialized(String(pzem.pf(), 2));
doc["hz"] = serialized(String(pzem.frequency(), 1));
} else {
doc["err"] = "pzem";
}
JsonArray r = doc.createNestedArray("r");
for (uint8_t i = 0; i < N_RELAYS; i++)
r.add(digitalRead(RELAY_IN[i]) == LOW ? 1 : 0);
char buf[288];
size_t n = serializeJson(doc, buf);
loraSend(buf, n);
Serial.printf("TX: %s\n", buf);
}
void handleCommand(const String &payload) {
StaticJsonDocument<192> doc;
if (deserializeJson(doc, payload)) return;
if (String((const char*)doc["id"]) != NODE_ID) return;
const char *cmd = doc["cmd"];
if (!cmd) return;
if (!strcmp(cmd, "relay")) {
int n = doc["n"] | -1;
int s = doc["s"] | 0;
if (n >= 0 && n < N_RELAYS) {
digitalWrite(RELAY_OUT[n], s ? HIGH : LOW);
delay(200);
sendTelemetry();
}
} else if (!strcmp(cmd, "poll")) {
sendTelemetry();
} else if (!strcmp(cmd, "info")) {
sendInfo();
}
}
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < N_RELAYS; i++) {
pinMode(RELAY_IN[i], INPUT_PULLUP);
pinMode(RELAY_OUT[i], OUTPUT);
digitalWrite(RELAY_OUT[i], LOW);
}
LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) { Serial.println("LoRa FAIL"); while (1) delay(1000); }
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setSyncWord(LORA_SYNC);
LoRa.setTxPower(LORA_TX_DBM);
LoRa.enableCrc();
LoRa.receive();
randomSeed(esp_random());
lastSend = millis() - random(0, SEND_PERIOD_MS);
delay(random(0, 3000)); // рассинхронизация info при массовом включении
sendInfo();
lastInfo = millis();
}
void loop() {
int sz = LoRa.parsePacket();
if (sz > 0) {
String s;
while (LoRa.available()) s += (char)LoRa.read();
handleCommand(s);
}
unsigned long now = millis();
if (now - lastSend >= SEND_PERIOD_MS + random(0, 2000)) {
lastSend = now; sendTelemetry();
}
if (now - lastInfo >= INFO_PERIOD_MS) {
lastInfo = now; sendInfo();
}
}