#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO #include #include #include #include #include #include #include #include #include #include #include /////////////// 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()) { LOG_ERROR("Invalid Json Command"); return; } const std::string cmd = doc["cmd"].as(); 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(); 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 //////// //////////////////////////////////////// }