300 lines
8.9 KiB
C++
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;
|
|
} |