2025-06-22 18:28:29 +03:00

300 lines
8.9 KiB
C++

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <cstring>
#include <time.h>
#include "janitza.h"
#include "common.h"
#include "led.h"
#define MQTT_RECONNECT_SECONDS 3
#define LOOP_DELAY 500
#define READ_FREQ 5000
#define CONFIG_WIFI_SSID "ssid"
#define CONFIG_WIFI_PSK "11112222"
// Certificate
const char* mqttRootCA = \
"-----BEGIN CERTIFICATE-----\n" \
...
"-----END CERTIFICATE-----";
const char* mqttServer = CONFIG_MQTT_SERVER;
const char* mqttUserName = CONFIG_MQTT_USERNAME;
const char* mqttPassword = CONFIG_MQTT_PASSWORD;
static JanitzaRegister umg104_registers[] = {
{19000, JanitzaType::FLOAT, JanitzaUnit::V, "_ULN[0]", "Voltage L1-N"},
{19002, JanitzaType::FLOAT, JanitzaUnit::V, "_ULN[1]", "Voltage L2-N"},
{19004, JanitzaType::FLOAT, JanitzaUnit::V, "_ULN[2]", "Voltage L3-N"},
{19006, JanitzaType::FLOAT, JanitzaUnit::V, "_ULL[0]", "Voltage L1-L2"},
{19008, JanitzaType::FLOAT, JanitzaUnit::V, "_ULL[1]", "Voltage L2-L3"},
{19010, JanitzaType::FLOAT, JanitzaUnit::V, "_ULL[2]", "Voltage L3-L1"},
{19012, JanitzaType::FLOAT, JanitzaUnit::A, "_ILN[0]", "Apparent current L1"},
{19014, JanitzaType::FLOAT, JanitzaUnit::A, "_ILN[1]", "Apparent current L2"},
{19016, JanitzaType::FLOAT, JanitzaUnit::A, "_ILN[2]", "Apparent current L3"},
{19018, JanitzaType::FLOAT, JanitzaUnit::A, "_I_SUM3", "Vector sum; IN=I1+I2+I3"},
{19026, JanitzaType::FLOAT, JanitzaUnit::W, "_P_SUM3", "Sum; Psum3=P1+P2+P3"},
{19020, JanitzaType::FLOAT, JanitzaUnit::W, "_PLN[0]", "Real power L1"},
{19022, JanitzaType::FLOAT, JanitzaUnit::W, "_PLN[1]", "Real power L2"},
{19024, JanitzaType::FLOAT, JanitzaUnit::W, "_PLN[2]", "Real power L3"},
{19028, JanitzaType::FLOAT, JanitzaUnit::VA, "_SLN[0]", "Apparent power L1"},
{19030, JanitzaType::FLOAT, JanitzaUnit::VA, "_SLN[1]", "Apparent power L2"},
{19032, JanitzaType::FLOAT, JanitzaUnit::VA, "_SLN[2]", "Apparent power L3"},
{19054, JanitzaType::FLOAT, JanitzaUnit::Wh, "_WH[0]", "Real energy L1"},
{19056, JanitzaType::FLOAT, JanitzaUnit::Wh, "_WH[1]", "Real energy L2"},
{19058, JanitzaType::FLOAT, JanitzaUnit::Wh, "_WH[2]", "Real energy L3"}
};
static WiFiClientSecure espClient;
static PubSubClient mqtt(espClient);
static JanitzaReader jr;
static const char* MqttHelloTopic = "slrmn/%d/hello";
static const char* MqttOtaTopic = "slrmn/%d/ota";
static const char* MqttEnergyTopic = "slrmn/%d/energy";
static const char* MqttErrorTopic = "slrmn/%d/error";
struct __attribute__((__packed__)) MqttEnergyDataPayload {
float uln0;
float uln1;
float uln2;
float ull0;
float ull1;
float ull2;
float iln0;
float iln1;
float iln2;
float i_sum3;
float p_sum3;
float pln0;
float pln1;
float pln2;
float sln0;
float sln1;
float sln2;
float wh0;
float wh1;
float wh2;
uint32_t timestamp;
};
struct __attribute__((__packed__)) MqttErrorPayload {
uint32_t timestamp;
uint16_t addr;
uint8_t code;
};
struct __attribute__((__packed__)) MqttHelloPayload {
uint32_t timestamp;
uint16_t fw_version;
};
static volatile bool mqttNeedsReconnect = true;
static bool timeConfigured = false;
static bool wifiConnectionCalled = false;
// static bool wifiOnceConnected = false;
static long lastNTPCheck = 0;
static long lastJanitraRead = 0;
static long lastMqttReconnectAttempt = 0;
static long wifiReconnectStarted = 0;
static void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void mqttReconnect();
static void mqttCallback(char* topic, uint8_t* payload, size_t length);
static void getTopicName(char* buf, size_t size, const char* fmt);
static String strgen(int length);
void setup() {
#ifdef DEBUG
Serial.begin(115200);
#endif
#ifndef CONFIG_JANITZA_EMULATE
jr.configure();
#endif
WiFi.begin(CONFIG_WIFI_SSID, CONFIG_WIFI_PSK);
WiFi.setAutoReconnect(true);
WiFi.hostname(CONFIG_WIFI_HOSTNAME);
espClient.setCACert(mqttRootCA);
// espClient.setInsecure();
wifiConnectionCalled = true;
}
void loop() {
auto ws = WiFi.status();
if (ws != WL_CONNECTED) {
if (wifiReconnectStarted > 0 && millis() - wifiReconnectStarted > 60000) {
PRINTF("\nfailed to reconnect, restarting...\n");
ESP.restart();
return;
}
if (!wifiConnectionCalled) {
PRINT("Connecting to wifi..");
// WiFi.setAutoReconnect(true);
WiFi.disconnect();
WiFi.reconnect();
wifiConnectionCalled = true;
wifiReconnectStarted = millis();
}
PRINT(".");
mcu_led->blink(2, 50);
delay(LOOP_DELAY);
mqttNeedsReconnect = true;
return;
}
wifiConnectionCalled = false;
if (!timeConfigured) {
randomSeed(micros());
configTime(3600*3, 0, "pool.ntp.org", "ntp0.ntp-servers.net", "ntp4.ntp-servers.net");
timeConfigured = true;
PRINTLN("Waiting for time");
while (time(nullptr) < 24 * 3600) {
PRINT(".");
delay(LOOP_DELAY);
}
PRINTLN("");
}
time_t timestamp = time(nullptr);
long now = millis();
long delaytime = LOOP_DELAY;
bool firstTimeFetch = lastNTPCheck == 0;
int mqttState = mqtt.state();
if (!mqtt.connected() && (mqttNeedsReconnect && (!lastMqttReconnectAttempt || now-lastMqttReconnectAttempt > MQTT_RECONNECT_SECONDS*1000)) || mqttState < 0)
mqttReconnect();
if (mqtt.connected()) {
mqtt.loop();
if (!lastJanitraRead || now-lastJanitraRead > READ_FREQ) {
uint16_t buf[2];
char topic[32];
float val;
uint16_t failedAddr = 0;
uint8_t failedCode = 0;
uint32_t ts = timestamp;
MqttEnergyDataPayload energyPayload;
energyPayload.timestamp = ts;
JanitzaRegister* reg;
for (int i = 0; i < ARRAY_SIZE(umg104_registers); i++) {
reg = &umg104_registers[i];
if (!jr.readFloat(*reg, &val)) {
failedCode = jr.getLastErrorCode();
failedAddr = reg->addr;
break;
}
(*(float*)((uint8_t*)&energyPayload + sizeof(float)*i)) = val;
}
if (failedAddr != 0) {
MqttErrorPayload errorPayload = {
.timestamp = ts,
.addr = failedAddr,
.code = failedCode
};
getTopicName(topic, sizeof(topic), MqttErrorTopic);
mqtt.publish(topic, (uint8_t*)&errorPayload, sizeof(errorPayload));
} else {
getTopicName(topic, sizeof(topic), MqttEnergyTopic);
mqtt.publish(topic, (uint8_t*)&energyPayload, sizeof(energyPayload));
}
lastJanitraRead = now;
} else {
long timedelta = READ_FREQ - (now - lastJanitraRead);
delaytime = timedelta > LOOP_DELAY ? LOOP_DELAY : timedelta;
}
}
// PRINTLN(timestamp);
delay(delaytime);
}
static void mqttCallback(char* topic, uint8_t* payload, size_t length) {
}
static void mqttReconnect() {
char buf[32];
long now = millis();
mqttNeedsReconnect = false;
lastMqttReconnectAttempt = now;
mqtt.setBufferSize(1024);
mqtt.setServer(mqttServer, CONFIG_MQTT_PORT);
String clientId = String(CONFIG_WIFI_HOSTNAME) + "_" + strgen(4);
PRINTF("mqtt client id will be %s\n", clientId.c_str());
if (mqtt.connect(clientId.c_str(), mqttUserName, mqttPassword)) {
PRINTLN("mqtt: connected [1]");
mqtt.loop();
mqtt.setCallback(mqttCallback);
// subscribe to the OTA topic
getTopicName(buf, sizeof(buf), MqttOtaTopic);
mqtt.subscribe(buf);
// send hello payl
getTopicName(buf, sizeof(buf), MqttHelloTopic);
auto payload = MqttHelloPayload {
.timestamp = static_cast<uint32_t>(time(nullptr)),
.fw_version = CONFIG_FW_VERSION
};
mqtt.publish(buf, (uint8_t*)&payload, sizeof(payload));
PRINTLN("mqtt: connected [2]");
lastMqttReconnectAttempt = 0;
} else {
PRINTF("mqtt: failed to connect, rc=%d, retrying in %d seconds\n",
mqtt.state(), MQTT_RECONNECT_SECONDS);
mqttNeedsReconnect = true;
}
}
static void getTopicName(char* buf, size_t size, const char* fmt) {
memset(buf, 0, size);
snprintf(buf, size-1, fmt, CONFIG_NODE_ID);
}
static String strgen(int length) {
String randomString = "";
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (int i = 0; i < length; i++) {
int index = random(characters.length());
char nextChar = characters.charAt(index);
randomString += nextChar;
}
return randomString;
}