Files
ETcontroller_PRO/src/main.cpp
2025-08-29 22:18:49 +02:00

244 lines
7.7 KiB
C++

#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
#include <Arduino.h>
#include <DebugLog.h>
#include <DebugLogEnable.h>
#include <config.h>
#include <commands.h>
#include <cronjobs.h>
#include <mqtt.h>
#include <ota.h>
#include <devices.h>
#include <utils.h>
#include <pinlist.h>
/////////////// GLOBALS ///////////////
Config &conf = Config::getInstance();
/////////////// GLOBALS ///////////////
void setup()
{
Serial.begin(9600);
LOG_ATTACH_SERIAL(Serial);
LOG_SET_LEVEL(DebugLogLevel::LVL_INFO);
conf.init(); // read the configuration from internal flash
LOG_INFO("ESP32 Chip:", ESP.getChipModel());
LOG_INFO("ESP32 PSram:", ESP.getPsramSize());
LOG_INFO("ESP32 Flash:", ESP.getFlashChipSize());
LOG_INFO("ESP32 Heap:", ESP.getHeapSize());
LOG_INFO("ESP32 Sketch:", ESP.getFreeSketchSpace());
}
void loop()
{
uint16_t k(0);
uint8_t sensors(0);
bool buzzing(false);
NetworkClient logStream;
LOG_ATTACH_STREAM(logStream);
//////////////// DEVICES ////////////////
// Declared here to keep devices local to the main loop otherwise the kernel crashes //
auto i2c = drivers::I2C();
auto bus = drivers::MODBUS(9600, SERIAL_8N1);
auto rtc = drivers::PCF85063(i2c);
auto eth = drivers::Ethernet(conf.m_ethHostname, conf.m_ntpPool, conf.m_ntpTimezone, conf.m_ntpUpdateInterval);
auto tmp = drivers::R4DCB08(bus, conf.m_modbusTemperatureAddr);
auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr);
auto buzzer = drivers::Buzzer();
auto led = drivers::Led();
auto io = digitalIO(i2c, bus, {conf.m_modbusRelayAddr});
// Create device structure to pass all devices in the callbacks as needed
devices_t devices(eth, rtc, tmp, seneca, buzzer, led, io);
//
// get RTC time drift offset value
rtc.setOffset(conf.m_ntpRtcOffsetRegister);
LOG_INFO("RTC offset register -> ", printHex(rtc.getOffset()).c_str());
// Initialize temperature sensors
sensors = tmp.getNum();
tmp.setCorrection(conf.m_tempCorrectionValues);
LOG_INFO("Temperature sensors connected ->", sensors);
// Initialize OTA updater if needed
auto ota = OTA(devices);
//////////////// DEVICES ////////////////
//////////////// MQTT ////////////////
auto mqtt = MQTTwrapper();
//////////////// MQTT ////////////////
//////////////// MQTT //////////////
/////////////// CALLBACK //////////////
MQTTwrapper::ActionCallback commandsCallback =
[&mqtt, &devices](const ArduinoJson::JsonDocument &doc)
{
if (!doc["cmd"].is<std::string>())
{
LOG_ERROR("Invalid Json Command");
return;
}
const std::string cmd = doc["cmd"].as<std::string>();
const ArduinoJson::JsonDocument params = doc["params"];
if (commands::s_commandMap.contains(cmd))
{ // call command from command map in this same thread (the MQTT thread)
LOG_INFO("Executing command", cmd.c_str());
const auto answer = std::move(commands::s_commandMap.at(cmd)(devices, params)); // here the magic happens
if (answer.isNull())
return;
mqtt.publish(conf.m_mqttPublish["answers"], answer);
}
else
{
LOG_ERROR("Unknown command", cmd.c_str());
}
};
MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message)
{
LOG_DEBUG("onMessage callback [", topic.c_str(), "]\n", message.c_str());
devices.led.setColor(devices.led.COLOR_MAGENTA);
};
MQTTwrapper::MessageCallback onPublish = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message)
{
LOG_DEBUG("onPublish callback [", topic.c_str(), "]\n", message.c_str());
devices.led.setColor(devices.led.COLOR_SKYBLUE);
};
///////////// CRONJOB //////////////
/////////////// CALLBACK //////////////
Cron::CronCallback cronCallback = [&mqtt](const ArduinoJson::JsonDocument &resp)
{
if (resp.isNull())
return;
mqtt.publish(conf.m_mqttPublish["cronjobs"], resp);
};
//////////////// CRONJOB ////////////////
auto &cron = Cron::getInstance(devices);
cron.setResponseCallback(cronCallback);
cron.loadEvents();
cron.startCron();
//////////////// CRONJOB ////////////////
//////////////// NETWORK ////////////////
/////////////// CALLBACK ////////////////
Network.onEvent(
[&](arduino_event_id_t event, arduino_event_info_t info) -> void
{
eth.onEvent(event, info); // Arduino Ethernet event handler
if (!eth.isConnected())
{
led.setColor(led.COLOR_RED);
logStream.stop();
return;
}
if (io.digitalInRead(DI::OTAENABLE)) // Initialize OTA, BLUE
{
buzzer.beepRepeat(25, 25, NOTE_A);
delay(1000);
if (io.digitalInRead(DI::OTAENABLE))
{ // maintain keyPress for 1s
ota.begin();
}
buzzer.beep(100, NOTE_G);
delay(100);
}
// Get RTC time at ethernet connection
time_t ntpTime;
uint8_t timeRetries(0);
uint8_t mqttRetries(0);
while (timeRetries++ < conf.m_ntpRetries)
{
eth.setNtpTimeOffset(conf.m_ntpTimezone);
LOG_INFO("NTP Timezone UTC", conf.m_ntpTimezone >= 0 ? "+" : "", conf.m_ntpTimezone);
if (eth.getNtpTime(ntpTime))
{ // skip NTP update for drift testing
buzzer.beep(250, NOTE_A);
led.setColor(led.COLOR_ORANGE);
const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime));
LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str());
break;
}
delay(250);
}
while (mqttRetries++ < conf.m_mqttRetries)
{
if (mqtt.connect())
{
buzzer.beep(250, NOTE_B);
led.setColor(led.COLOR_GREEN);
mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback);
mqtt.setOnMessageCb(onMessage);
mqtt.setOnPublishCb(onPublish);
break;
}
delay(250);
}
});
////////////////////////////////////////
///////// MAIN LOOP INSIDE LOOP ////////
////////////////////////////////////////
while (true)
{
const uint32_t start(millis());
drivers::PCF85063::datetime_t datetime;
if (!logStream.connected())
{
logStream.stop();
logStream.clearWriteError();
logStream.setConnectionTimeout(100);
logStream.connect(conf.m_mqttHost.c_str(), 9876);
LOG_WARN("TCP LogStream Connected");
}
rtc.readDatetime(datetime);
const std::string timeStr(drivers::PCF85063::datetime2str(datetime));
LOG_INFO("[", k++, "] Loop - Current Datetime UTC", timeStr.c_str());
{
ArduinoJson::JsonDocument poll;
poll["cmd"] = "POLL";
auto params = poll["values"].to<ArduinoJson::JsonObject>();
params["time"] = timeStr;
params["number"] = k;
mqtt.publish(conf.m_mqttPublish["answers"], poll);
};
{
ArduinoJson::JsonDocument ti;
auto tempinfo = tmp.getTempAll();
ti["solar"] = tempinfo.at(0);
ti["acs"] = tempinfo.at(1);
ti["heating"] = tempinfo.at(2);
mqtt.publish(conf.m_mqttPublish["temperatures"], ti);
};
if (io.digitalInRead(DI::CONFRESET)) // ROSSO - Config Reset
{
LOG_WARN("Config RESET!");
buzzer.beep(450, NOTE_E);
delay(500);
conf.resetConfig();
}
if (io.digitalInRead(DI::RESTART)) // GIALLO - Restart
{
LOG_WARN("RESTART!");
buzzer.beep(450, NOTE_D);
delay(450);
esp_restart();
}
delay(conf.m_globalLoopDelay - (start - millis())); // to avoid too fast loop, keep precise timing computing loop time
}
////////////////////////////////////////
///////// MAIN LOOP INSIDE LOOP ////////
////////////////////////////////////////
}