13 Commits

Author SHA1 Message Date
Emanuele Trabattoni
4a1e944ea2 set ntp parameters via config file 2025-08-03 12:11:00 +02:00
Emanuele Trabattoni
a1a66ebf8e RTC fix time lag correction 2025-08-03 11:27:07 +02:00
Emanuele Trabattoni
b19ed89158 clock correction 2025-08-02 18:04:40 +02:00
Emanuele Trabattoni
25251785fa led flash not working ma vabbeh 2025-08-02 17:39:02 +02:00
0e842294be Values vs Value 2025-08-01 11:31:05 +02:00
57957740d9 Merge remote-tracking branch 'origin/pro-develop' into pro-develop 2025-08-01 10:55:47 +02:00
Emanuele Trabattoni
25aa2d6cb6 led class refactor 2025-08-01 10:38:41 +02:00
Emanuele Trabattoni
eaa643bf3c fixed typo "values" 2025-08-01 10:38:29 +02:00
Emanuele Trabattoni
abe0cb0839 improved responses content for commands and cronjobs 2025-07-31 16:16:06 +02:00
Emanuele Trabattoni
fc2316b0f2 refactor tyoes + added callbacks 2025-07-31 16:15:36 +02:00
fa1b288f4d added setBuzz demo test command 2025-07-30 16:26:18 +02:00
Emanuele Trabattoni
1110648978 added set time via ntp as command and retrieve all cron jobs 2025-07-30 15:24:11 +02:00
Emanuele Trabattoni
581eca124e added time drift check command 2025-07-30 10:15:13 +02:00
17 changed files with 496 additions and 167 deletions

Binary file not shown.

54
esp32-s3-waveshare8.json Normal file
View File

@@ -0,0 +1,54 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "app3M_fat9M_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"
],
"partitions": "app3M_fat9M_16MB.csv",
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"bluetooth",
"wifi",
"ethernet"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif ESP32-S3-Waveshare_8RO-8DI",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html",
"vendor": "Espressif"
}

View File

@@ -56,7 +56,7 @@ public:
file.close(); // close config file before unmounting filesystem file.close(); // close config file before unmounting filesystem
}; };
ArduinoJson::JsonDocument& getConfig() ArduinoJson::JsonDocument &getConfig()
{ {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
serialize(); serialize();
@@ -152,6 +152,7 @@ private:
ntp["timezone"] = m_ntpTimezone; ntp["timezone"] = m_ntpTimezone;
ntp["updateInterval"] = m_ntpUpdateInterval; ntp["updateInterval"] = m_ntpUpdateInterval;
ntp["retries"] = m_ntpRetries; ntp["retries"] = m_ntpRetries;
ntp["ntpRtcOffsetRegister"] = m_ntpRtcOffsetRegister;
}; };
{ {
@@ -220,9 +221,10 @@ private:
{ {
auto ntp = m_configJson["ntp"]; auto ntp = m_configJson["ntp"];
m_ntpPool = ntp["pool"].as<std::string>(); m_ntpPool = ntp["pool"].as<std::string>();
m_ntpTimezone = ntp["timezone"].as<uint16_t>(); m_ntpTimezone = ntp["timezone"].as<int8_t>();
m_ntpUpdateInterval = ntp["updateInterval"].as<uint16_t>(); m_ntpUpdateInterval = ntp["updateInterval"].as<uint16_t>();
m_ntpRetries = ntp["retries"].as<uint8_t>(); m_ntpRetries = ntp["retries"].as<uint8_t>();
m_ntpRtcOffsetRegister = ntp["ntpRtcOffsetRegister"].as<uint8_t>();
}; };
{ {
@@ -232,7 +234,7 @@ private:
m_mqttLoopTime = mqtt["loopTime"].as<uint16_t>(); m_mqttLoopTime = mqtt["loopTime"].as<uint16_t>();
m_mqttKeepalive = mqtt["keepalive"].as<uint8_t>(); m_mqttKeepalive = mqtt["keepalive"].as<uint8_t>();
m_mqttRetries = mqtt["retries"].as<uint8_t>(); m_mqttRetries = mqtt["retries"].as<uint8_t>();
auto subscribe = mqtt["subsribe"].as<ArduinoJson::JsonObject>(); auto subscribe = mqtt["subscribe"].as<ArduinoJson::JsonObject>();
for (auto v : subscribe) for (auto v : subscribe)
{ {
m_mqttSubscribe[v.key().c_str()] = v.value().as<std::string>(); m_mqttSubscribe[v.key().c_str()] = v.value().as<std::string>();
@@ -272,9 +274,10 @@ public:
// NTP // NTP
std::string m_ntpPool = "pool.ntp.org"; std::string m_ntpPool = "pool.ntp.org";
uint16_t m_ntpTimezone = 3600; // GTM +1 int8_t m_ntpTimezone = +1; // GMT +1
uint16_t m_ntpUpdateInterval = 3600; // every hour uint16_t m_ntpUpdateInterval = 3600; // every hour
uint8_t m_ntpRetries = 5; uint8_t m_ntpRetries = 5;
uint8_t m_ntpRtcOffsetRegister = 0xE7; // -25 pulses in fast mode
// MQTT // MQTT
std::string m_mqttHost = "10.0.2.249"; std::string m_mqttHost = "10.0.2.249";
@@ -287,6 +290,7 @@ public:
std::map<const std::string, std::string> m_mqttSubscribe = { std::map<const std::string, std::string> m_mqttSubscribe = {
{"commands", "etcontroller/hw/commands"}}; {"commands", "etcontroller/hw/commands"}};
std::map<const std::string, std::string> m_mqttPublish = { std::map<const std::string, std::string> m_mqttPublish = {
{"cronjobs", "etcontroller/hw/cronjobs"},
{"answers", "etcontroller/hw/answers"}, {"answers", "etcontroller/hw/answers"},
{"heatpump", "etcontroller/hw/heatpump"}, {"heatpump", "etcontroller/hw/heatpump"},
{"temperatures", "etcontroller/hw/temperatures"}, {"temperatures", "etcontroller/hw/temperatures"},

View File

@@ -3,12 +3,12 @@
namespace drivers namespace drivers
{ {
Ethernet::Ethernet(const std::string hostname) : m_hostname(hostname), m_connected(false), m_localIP(IPAddress()), m_udp(NetworkUDP()), m_timeClient(m_udp) Ethernet::Ethernet(const std::string &hostname, const std::string &ntpPool, const int8_t tz, const uint16_t updateInterval) : m_hostname(hostname), m_ntpPool(ntpPool), m_connected(false), m_localIP(IPAddress()), m_udp(NetworkUDP()), m_timeClient(m_udp)
{ {
SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI); SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI);
ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI); ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI);
m_timeClient = std::move(NTPClient(m_udp, "pool.ntp.org", 0, 3600)); // NTP server, time offset in seconds, update interval m_timeClient = std::move(NTPClient(m_udp, m_ntpPool.c_str(), tz * 3600, updateInterval)); // NTP server, time offset in seconds, update interval
m_timeClient.begin(); m_timeClient.begin();
} }
@@ -30,6 +30,17 @@ namespace drivers
return false; return false;
} }
const bool Ethernet::setNtpTimeOffset(const int8_t tz)
{
if (m_connected)
{
m_timeClient.setTimeOffset(tz * 3600);
LOG_DEBUG("Time zone UTC ", tz);
return true;
}
return false;
}
const bool Ethernet::isConnected() const bool Ethernet::isConnected()
{ {
return m_connected; return m_connected;

View File

@@ -30,15 +30,17 @@ namespace drivers
{ {
public: public:
Ethernet(const std::string hostname); Ethernet(const std::string &hostname, const std::string &ntpPool, const int8_t tz, const uint16_t updateInterval);
~Ethernet(); ~Ethernet();
void onEvent(arduino_event_id_t event, arduino_event_info_t info); void onEvent(arduino_event_id_t event, arduino_event_info_t info);
const bool isConnected(); const bool isConnected();
const bool getNtpTime(time_t &time); const bool getNtpTime(time_t &time);
const bool setNtpTimeOffset(const int8_t tz);
private: private:
const std::string m_hostname; const std::string m_hostname;
const std::string m_ntpPool;
bool m_connected; bool m_connected;
NetworkUDP m_udp; NetworkUDP m_udp;
IPAddress m_localIP; IPAddress m_localIP;

View File

@@ -10,8 +10,8 @@ namespace drivers
{ {
LOG_INFO("Inizializing RGB Led"); LOG_INFO("Inizializing RGB Led");
pinMode(c_ledPin, OUTPUT); pinMode(c_ledPin, OUTPUT);
m_lp.pin = c_ledPin; m_blinkTask = NULL;
m_lp.blinkTask = NULL; m_flashTimer = NULL;
} }
Led::~Led() Led::~Led()
@@ -22,54 +22,89 @@ namespace drivers
void Led::setColor(const color_t color) void Led::setColor(const color_t color)
{ {
std::lock_guard<std::mutex> lock(m_ledMutex);
blinkStop(); blinkStop();
m_colorDefault = color;
rgbLedWrite(c_ledPin, color.g, color.r, color.b); rgbLedWrite(c_ledPin, color.g, color.r, color.b);
} }
void Led::flashHandle(TimerHandle_t th)
{
Led *led = (Led *)pvTimerGetTimerID(th);
rgbLedWrite(led->c_ledPin, led->m_colorDefault.g, led->m_colorDefault.r, led->m_colorDefault.b); // reset color to saved color
return;
}
void Led::flashColor(const uint16_t tOn, const color_t color)
{
std::lock_guard<std::mutex> lock(m_ledMutex);
rgbLedWrite(c_ledPin, color.g, color.r, color.b); // set color to flash
if (m_flashTimer == NULL)
{
m_flashTimer = xTimerCreate("flasher", pdMS_TO_TICKS(tOn), pdFALSE, NULL, flashHandle);
xTimerStart(m_flashTimer, 0);
LOG_INFO("Led Flash timer created");
return;
}
xTimerStop(m_flashTimer, 0);
if (!xTimerChangePeriod(m_flashTimer, pdMS_TO_TICKS(tOn), pdMS_TO_TICKS(1)) || !xTimerReset(m_flashTimer, pdMS_TO_TICKS(1)))
{
LOG_ERROR("Led Flash timer failed reset");
xTimerDelete(m_flashTimer, 0);
m_flashTimer = NULL;
}
}
void Led::blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color) void Led::blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color)
{ {
std::lock_guard<std::mutex> lock(m_ledMutex);
blinkStop(); blinkStop();
m_lp.color1 = color; m_color1 = color;
m_lp.color2 = {0, 0, 0}; m_color2 = {0, 0, 0};
m_lp.tOn = tOn; m_tOn = tOn;
m_lp.tOff = tOff; m_tOff = tOff;
xTaskCreate(blinkTask, "blinker", TASK_STACK, static_cast<void *>(&m_lp), TASK_PRIORITY, &m_lp.blinkTask); xTaskCreate(blinkTask, "blinker", TASK_STACK, this, TASK_PRIORITY, &m_blinkTask);
} }
void Led::blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2) void Led::blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2)
{ {
{ std::lock_guard<std::mutex> lock(m_ledMutex);
blinkStop(); blinkStop();
m_lp.color1 = color1; m_color1 = color1;
m_lp.color2 = color2; m_color2 = color2;
m_lp.tOn = tOn; m_tOn = tOn;
m_lp.tOff = tOff; m_tOff = tOff;
xTaskCreate(blinkTask, "blinker", TASK_STACK, static_cast<void *>(&m_lp), TASK_PRIORITY, &m_lp.blinkTask); xTaskCreate(blinkTask, "blinker", TASK_STACK, this, TASK_PRIORITY, &m_blinkTask);
}
} }
void Led::blinkStop() void Led::blinkStop()
{ {
if (m_lp.blinkTask != NULL) if (m_blinkTask != NULL)
vTaskDelete(m_lp.blinkTask); vTaskDelete(m_blinkTask);
m_lp.blinkTask = NULL; m_blinkTask = NULL;
} }
void Led::blinkTask(void *params) void Led::blinkTask(void *params)
{ {
Led *led = static_cast<Led *>(params);
LOG_DEBUG("Blinker Task Created"); LOG_DEBUG("Blinker Task Created");
led_params_t *lPar = static_cast<led_params_t *>(params);
while (true) while (true)
{ {
rgbLedWrite(lPar->pin, lPar->color1.g, lPar->color1.r, lPar->color1.b); {
delay(lPar->tOn); std::lock_guard<std::mutex> lock(led->m_ledMutex);
rgbLedWrite(lPar->pin, lPar->color2.g, lPar->color2.r, lPar->color2.b); // off rgbLedWrite(led->c_ledPin, led->m_color1.g, led->m_color1.r, led->m_color1.b);
if (lPar->tOff == 0) }
delay(led->m_tOn);
{
std::lock_guard<std::mutex> lock(led->m_ledMutex);
rgbLedWrite(led->c_ledPin, led->m_color2.g, led->m_color2.r, led->m_color2.b); // off
}
if (led->m_tOff == 0)
break; break;
delay(lPar->tOff); delay(led->m_tOff);
} }
LOG_DEBUG("Blinker Task Ended"); LOG_DEBUG("Blinker Task Ended");
lPar->blinkTask = NULL; led->m_blinkTask = NULL;
vTaskDelete(NULL); vTaskDelete(NULL);
} }
} }

View File

@@ -5,21 +5,22 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO #define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
#include <DebugLog.h> #include <DebugLog.h>
#include <mutex>
namespace drivers namespace drivers
{ {
class Led class Led
{ {
const uint8_t c_ledPin = 38;
public:
public:
typedef struct typedef struct
{ {
uint8_t r; uint8_t r;
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
} color_t; } color_t;
const color_t COLOR_RED = {255, 0, 0}; const color_t COLOR_RED = {255, 0, 0};
const color_t COLOR_ORANGE = {255, 127, 0}; const color_t COLOR_ORANGE = {255, 127, 0};
const color_t COLOR_YELLOW = {255, 255, 0}; const color_t COLOR_YELLOW = {255, 255, 0};
@@ -30,32 +31,37 @@ namespace drivers
const color_t COLOR_BLUE = {0, 0, 255}; const color_t COLOR_BLUE = {0, 0, 255};
const color_t COLOR_VIOLET = {127, 0, 255}; const color_t COLOR_VIOLET = {127, 0, 255};
const color_t COLOR_MAGENTA = {255, 0, 255}; const color_t COLOR_MAGENTA = {255, 0, 255};
private: public:
typedef struct
{
color_t color1;
color_t color2;
uint8_t pin;
uint16_t tOn;
uint16_t tOff;
TaskHandle_t blinkTask;
} led_params_t;
public:
Led(); Led();
~Led(); ~Led();
void setColor(const color_t color); void setColor(const color_t color);
void flashColor(const uint16_t tOn, const color_t color);
void blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color); void blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color);
void blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2); void blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2);
void blinkStop(); void blinkStop();
private: private:
static void flashHandle(TimerHandle_t th);
static void blinkTask(void *params); static void blinkTask(void *params);
private:
const uint8_t c_ledPin = 38;
color_t m_color1;
color_t m_color2;
color_t m_colorDefault;
private: uint16_t m_tOn;
led_params_t m_lp; uint16_t m_tOff;
TaskHandle_t m_blinkTask;
TimerHandle_t m_flashTimer;
bool m_flashing;
std::mutex m_ledMutex;
}; };
} }

View File

@@ -1,5 +1,6 @@
#include "PCF85063_Driver.h" #include "PCF85063_Driver.h"
#include <ctime> #include <ctime>
#include <utils.h>
namespace drivers namespace drivers
{ {
@@ -157,6 +158,23 @@ namespace drivers
return false; return false;
} }
const bool PCF85063::setOffset(const uint8_t ofst)
{
LOG_DEBUG("RTC set offset [", printHex(ofst).c_str(), "]");
return m_i2c.write(m_address, RTC_OFFSET_ADDR, {ofst});
}
const uint8_t PCF85063::getOffset()
{
std::vector<uint8_t> buf;
if (m_i2c.read(m_address, RTC_OFFSET_ADDR, 1, buf))
{
LOG_DEBUG("RTC get offset [", printHex(buf.front()).c_str(), "]");
return buf.front();
}
return UINT8_MAX;
}
const std::string PCF85063::getTimeStr() const std::string PCF85063::getTimeStr()
{ {
datetime_t dt; datetime_t dt;
@@ -182,16 +200,17 @@ namespace drivers
{ {
tm dtime = datetime2tm(datetime); tm dtime = datetime2tm(datetime);
const std::string buf(std::asctime(&dtime)); const std::string buf(std::asctime(&dtime));
return buf.substr(0, std::min(buf.find('\n'),buf.find('\r'))); return buf.substr(0, std::min(buf.find('\n'), buf.find('\r')));
} }
const std::string PCF85063::tm2str(const std::tm &datetime) const std::string PCF85063::tm2str(const std::tm &datetime)
{ {
const std::string buf(std::asctime(&datetime)); const std::string buf(std::asctime(&datetime));
return buf.substr(0, std::min(buf.find('\n'),buf.find('\r'))); return buf.substr(0, std::min(buf.find('\n'), buf.find('\r')));
} }
const std::tm PCF85063::datetime2tm(const datetime_t& datetime) { const std::tm PCF85063::datetime2tm(const datetime_t &datetime)
{
tm dtime; tm dtime;
dtime.tm_sec = datetime.second; dtime.tm_sec = datetime.second;
dtime.tm_min = datetime.minute; dtime.tm_min = datetime.minute;

View File

@@ -5,6 +5,7 @@
#include <DebugLog.h> #include <DebugLog.h>
#include "I2C_Driver.h" #include "I2C_Driver.h"
#include <string> #include <string>
#include <time.h>
// PCF85063_ADDRESS // PCF85063_ADDRESS
#define PCF85063_ADDRESS (0x51) #define PCF85063_ADDRESS (0x51)
@@ -82,7 +83,7 @@ namespace drivers
} datetime_t; } datetime_t;
public: public:
PCF85063(I2C &i2c, const uint8_t address, const uint8_t ctrl1 = RTC_CTRL_1_DEFAULT, const uint8_t ctrl2 = RTC_CTRL_2_DEFAULT); PCF85063(I2C &i2c, const uint8_t address = PCF85063_ADDRESS, const uint8_t ctrl1 = RTC_CTRL_1_DEFAULT, const uint8_t ctrl2 = RTC_CTRL_2_DEFAULT);
const bool reset(void); const bool reset(void);
@@ -99,6 +100,9 @@ namespace drivers
const bool readAlarm(datetime_t &time); const bool readAlarm(datetime_t &time);
const bool getAlarmFlag(uint8_t &flags); const bool getAlarmFlag(uint8_t &flags);
const bool setOffset(const uint8_t ofst);
const uint8_t getOffset();
const std::string getTimeStr(); const std::string getTimeStr();
static const std::string datetime2str(const datetime_t &datetime); static const std::string datetime2str(const datetime_t &datetime);

View File

@@ -9,6 +9,13 @@ namespace commands
esp_restart(); esp_restart();
} }
const ArduinoJson::JsonDocument Commands::setBuzz(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response;
dev.buzzer.beep(500, NOTE_Bb);
return response;
}
// CONFIG // // CONFIG //
// CONFIG // // CONFIG //
const ArduinoJson::JsonDocument Commands::setConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::setConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params)
@@ -51,11 +58,13 @@ namespace commands
// CRONJOBS // // CRONJOBS //
// CRONJOBS // // CRONJOBS //
const ArduinoJson::JsonDocument Commands::loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params){ const ArduinoJson::JsonDocument Commands::loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "loadCronjob"; response["cmd"] = "loadCronJob";
auto& cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
if(!cron.loadEvents()){ if (!cron.loadEvents())
{
LOG_ERROR("loadCronJob failed to load events from flash"); LOG_ERROR("loadCronJob failed to load events from flash");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
@@ -64,44 +73,68 @@ namespace commands
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::setCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "setCronjob"; response["cmd"] = "setCronJob";
const auto &jobName = params["name"].as<std::string>(); const auto &eventName = params["name"].as<std::string>();
const auto &timeStr = params["cronExpr"].as<std::string>(); const auto &timeStr = params["cronExpr"].as<std::string>();
const auto &actionStr = params["action"].as<std::string>(); const auto &actionStr = params["action"].as<std::string>();
response["values"]["name"] = eventName;
ArduinoJson::JsonDocument action; ArduinoJson::JsonDocument action;
if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok) if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok)
{ {
LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]"); LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]");
response["value"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
auto &cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
if (!cron.addEvent(jobName, timeStr, action)) if (!cron.addEvent(eventName, timeStr, action))
{ {
LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]"); LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]");
response["value"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
LOG_INFO("setCronJob added job [", actionStr.c_str(), "]"); LOG_INFO("setCronJob added job [", actionStr.c_str(), "]");
response["value"]["status"] = "valid"; response["values"]["status"] = "valid";
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "getCronjob"; response["cmd"] = "getCronJob";
auto &cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>(); auto eventName = params["name"].as<std::string>();
Cron::CronEvent event;
if (eventName.empty() || !cron.getEvent(eventName, event)) if (eventName.empty())
{ {
LOG_ERROR("delCronjob failed to get job [", eventName.c_str(), "]"); LOG_ERROR("getCronJob empty job name");
response["values"]["status"] = "invalid";
return response;
}
if (eventName == "all")
{
const auto &eventMap = cron.getAllEvents();
uint8_t eventNum(0);
for (const auto &[name, event] : eventMap)
{
const auto cmd = std::get<0>(event);
response["values"][name] = cmd;
eventNum++;
}
LOG_INFO("getCronJob got [", eventNum, "] events");
return response;
}
Cron::CronEvent event;
response["values"]["name"] = eventName;
if (!cron.getEvent(eventName, event))
{
LOG_ERROR("getCronJob failed to get job [", eventName.c_str(), "]");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
@@ -113,22 +146,22 @@ namespace commands
ArduinoJson::JsonDocument action; ArduinoJson::JsonDocument action;
action["cmd"] = cmd; action["cmd"] = cmd;
action["params"] = cmdParams; action["params"] = cmdParams;
response["values"]["name"] = eventName;
response["values"]["cronExpr"] = cron::to_cronstr(cronExpr); response["values"]["cronExpr"] = cron::to_cronstr(cronExpr);
response["values"]["action"] = action; response["values"]["action"] = action;
LOG_INFO("getCronjob get job [", eventName.c_str(), "]"); LOG_INFO("getCronJob get job [", eventName.c_str(), "]");
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::delCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "delCronjob"; response["cmd"] = "delCronJob";
auto &cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>(); auto eventName = params["name"].as<std::string>();
response["values"]["name"] = eventName;
if (eventName.empty() || !cron.delEvent(eventName)) if (eventName.empty() || !cron.delEvent(eventName))
{ {
LOG_ERROR("delCronjob failed to delete job [", eventName.c_str(), "]"); LOG_ERROR("delCronJob failed to delete job [", eventName.c_str(), "]");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
@@ -136,12 +169,13 @@ namespace commands
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::storeCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "storeCronjob"; response["cmd"] = "storeCronJob";
auto& cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
if(!cron.storeEvents()){ if (!cron.storeEvents())
{
LOG_ERROR("storeCronJob failed to store events in flash"); LOG_ERROR("storeCronJob failed to store events in flash");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
@@ -157,62 +191,62 @@ namespace commands
const ArduinoJson::JsonDocument Commands::setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
{ response["cmd"] = "setHPlimit";
std::string msg;
serializeJson(params, msg);
LOG_INFO("setHPlimit params ->", msg.c_str());
};
if (!params["level"].is<std::string>()) if (!params["level"].is<std::string>())
{ {
LOG_ERROR("setHPlimit incorrect parameters"); LOG_ERROR("setHPlimit incorrect parameters");
return response; return response;
} }
const auto level = params["level"].as<std::string>(); const auto level = params["level"].as<std::string>();
response["values"]["level"] = level;
if (!c_hpLimitsMap.contains(level)) if (!c_hpLimitsMap.contains(level))
{ {
LOG_ERROR("setHPlimit invalid level", level.c_str()); LOG_ERROR("setHPlimit invalid level", level.c_str());
response["values"]["status"] = "invalid";
return response; return response;
} }
for (auto k : c_hpLimitsMap) for (const auto [lvl, ro] : c_hpLimitsMap)
{ {
if (level == k.first && level != "UNLIMITED") if (level == lvl && level != "UNLIMITED")
dev.io.digitalOutWrite(k.second, true); dev.io.digitalOutWrite(ro, true);
else else
dev.io.digitalOutWrite(k.second, false); dev.io.digitalOutWrite(ro, false);
} }
LOG_INFO("setHPlimit -> level", level.c_str()); LOG_INFO("setHPlimit -> level", level.c_str());
response["values"]["status"] = "valid";
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::setHeating(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::setHeating(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
{ response["cmd"] = "setHeating";
std::string msg;
serializeJson(params, msg);
LOG_INFO("setHeating params ->", msg.c_str());
};
if (params.isNull()) if (params.isNull())
{ {
LOG_ERROR("setHeating incorrect paramaters"); LOG_ERROR("setHeating incorrect paramaters");
return response; return response;
} }
for (auto v : c_heatingValveMap) for (const auto [lvl, ro] : c_heatingValveMap)
{ {
if (params[v.first].isNull()) if (params[lvl].isNull())
continue; continue;
if (params[v.first] == "ON") if (params[lvl] == "ON")
{ {
dev.io.digitalOutWrite(v.second, true); dev.io.digitalOutWrite(ro, true);
LOG_INFO("setHeating -> ", v.first.c_str(), "ON"); response["values"][lvl] = "ON";
LOG_INFO("setHeating -> ", lvl.c_str(), "ON");
} }
else if (params[v.first] == "OFF") else if (params[lvl] == "OFF")
{ {
dev.io.digitalOutWrite(v.second, false); dev.io.digitalOutWrite(ro, false);
LOG_INFO("setHeating -> ", v.first.c_str(), "OFF"); response["values"][lvl] = "OFF";
LOG_INFO("setHeating -> ", lvl.c_str(), "OFF");
} }
else else
{
response["values"][lvl] = "invalid";
LOG_ERROR("setHeating invalid valve state"); LOG_ERROR("setHeating invalid valve state");
}
} }
return response; return response;
} }
@@ -245,11 +279,6 @@ namespace commands
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
auto &conf = Config::getInstance(); auto &conf = Config::getInstance();
{
std::string msg;
serializeJson(params, msg);
LOG_INFO("setIrrigation params ->", msg.c_str());
};
response["cmd"] = "setIrrigation"; response["cmd"] = "setIrrigation";
if (params.isNull()) if (params.isNull())
{ {
@@ -260,6 +289,8 @@ namespace commands
const uint16_t tOn(params["timeOn"].as<uint16_t>()); const uint16_t tOn(params["timeOn"].as<uint16_t>());
const uint16_t tPause(params["timePause"].as<uint16_t>()); const uint16_t tPause(params["timePause"].as<uint16_t>());
response["values"]["zone"] = zone;
if (zone == "stop") if (zone == "stop")
{ // stop all zones and reset timers { // stop all zones and reset timers
LOG_INFO("setIrrigation stop all zones"); LOG_INFO("setIrrigation stop all zones");
@@ -289,6 +320,8 @@ namespace commands
return response; return response;
} }
response["values"]["timeOn"] = tOn;
response["values"]["timePause"] = tPause;
if (!c_irrigationValveMap.contains(zone) || tOn <= 0 || tPause <= 0) // verify if zone is a valid map key if (!c_irrigationValveMap.contains(zone) || tOn <= 0 || tPause <= 0) // verify if zone is a valid map key
{ {
LOG_ERROR("setIrrigation incorrect zone[", zone.c_str(), "] or time values tOn[", tOn, "] tPause[", tPause, "]"); LOG_ERROR("setIrrigation incorrect zone[", zone.c_str(), "] or time values tOn[", tOn, "] tPause[", tPause, "]");
@@ -334,11 +367,36 @@ namespace commands
dev.io.digitalOutWrite(zoneIoNumber, true); dev.io.digitalOutWrite(zoneIoNumber, true);
xTimerStart(shTimer, 0); xTimerStart(shTimer, 0);
timerHandle = shTimer; timerHandle = shTimer;
response["values"]["status"] = "ok"; response["values"]["status"] = "valid";
LOG_INFO("setIrrigation zone [", timerName, "] tOn[", tOn, "] tPause[", tPause, "]"); LOG_INFO("setIrrigation zone [", timerName, "] tOn[", tOn, "] tPause[", tPause, "]");
} }
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response;
response["cmd"] = "setTimeNTP";
auto &eth = dev.eth;
auto &rtc = dev.rtc;
time_t ntpTime;
auto ntpOk = eth.getNtpTime(ntpTime);
drivers::PCF85063::datetime_t rtcTime(drivers::PCF85063::fromEpoch(ntpTime));
auto rtcOk = rtc.setDatetime(rtcTime);
if (!rtcOk || !ntpOk)
{
response["values"]["status"] = "invalid";
return response;
}
response["values"]["status"] = "valid";
response["values"]["time"] = rtc.getTimeStr();
LOG_INFO("setTimeNTP -> RTC is [", response["status"]["time"].as<std::string>().c_str(), "]");
return response;
}
// SETTERS // // SETTERS //
// SETTERS // // SETTERS //
@@ -347,8 +405,8 @@ namespace commands
const ArduinoJson::JsonDocument Commands::getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
const auto pinfo = dev.seneca.getAll();
response["cmd"] = "getHPpower"; response["cmd"] = "getHPpower";
const auto pinfo = dev.seneca.getAll();
auto values = response["values"].to<JsonObject>(); auto values = response["values"].to<JsonObject>();
values["power"] = pinfo.pAct; values["power"] = pinfo.pAct;
values["current"] = pinfo.a; values["current"] = pinfo.a;
@@ -406,6 +464,41 @@ namespace commands
LOG_WARN("Comand not yet implemented"); LOG_WARN("Comand not yet implemented");
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response;
response["cmd"] = "getTimeDrift";
auto &eth = dev.eth;
auto &rtc = dev.rtc;
time_t ntpTime;
auto ntpOk = eth.getNtpTime(ntpTime);
drivers::PCF85063::datetime_t rtcTime;
auto rtcOk = rtc.readDatetime(rtcTime);
auto rtcTimeTm = drivers::PCF85063::datetime2tm(rtcTime);
if (!rtcOk || !ntpOk)
{
response["values"]["status"] = "invalid";
return response;
}
auto ntpTimePoint = std::chrono::system_clock::from_time_t(ntpTime);
auto rtcTimePoint = std::chrono::system_clock::from_time_t(std::mktime(&rtcTimeTm));
auto timeDiff = std::chrono::duration_cast<std::chrono::seconds>(ntpTimePoint - rtcTimePoint);
auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD";
response["values"]["status"] = "valid";
response["values"]["drift"] = (uint32_t)timeDiff.count();
response["values"]["direction"] = "RTC is [" + std::string(direction) + "] NTP time";
LOG_INFO("getTimeDrift -> RTC is [", (int32_t)timeDiff.count(), "] sec, [", std::string(direction).c_str(), "] NTP time");
return response;
}
// GETTERS // // GETTERS //
// GETTERS // // GETTERS //

View File

@@ -64,21 +64,25 @@ namespace commands
Commands() = delete; Commands() = delete;
public: public:
// TEST //
static const ArduinoJson::JsonDocument setBuzz(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// CONFIG // // CONFIG //
static const ArduinoJson::JsonDocument setConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// CRONJOBS // // CRONJOBS //
static const ArduinoJson::JsonDocument loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument delCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument storeCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// SETTERS // // SETTERS //
static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setHeating(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setHeating(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// GETTERS // // GETTERS //
static const ArduinoJson::JsonDocument getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params);
@@ -89,17 +93,21 @@ namespace commands
static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument &params);
}; };
static const std::map<const std::string, Command> commandMap = { static const std::map<const std::string, Command> s_commandMap = {
{"setBuzz", Commands::setBuzz},
{"setConfig", Commands::setConfig}, {"setConfig", Commands::setConfig},
{"getConfig", Commands::getConfig}, {"getConfig", Commands::getConfig},
{"loadCronjob", Commands::loadCronjob}, {"loadCronJob", Commands::loadCronJob},
{"setCronjob", Commands::setCronjob}, {"setCronJob", Commands::setCronJob},
{"getCronjob", Commands::getCronjob}, {"getCronJob", Commands::getCronJob},
{"delCronjob", Commands::delCronjob}, {"delCronJob", Commands::delCronJob},
{"storeCronjob", Commands::storeCronjob}, {"storeCronJob", Commands::storeCronJob},
{"setHPlimit", Commands::setHPlimit}, {"setHPlimit", Commands::setHPlimit},
{"setHeating", Commands::setHeating}, {"setHeating", Commands::setHeating},
@@ -107,6 +115,9 @@ namespace commands
{"getHPpower", Commands::getHPpower}, {"getHPpower", Commands::getHPpower},
{"setHeating", Commands::setHeating}, {"setHeating", Commands::setHeating},
{"getTimeDrift", Commands::getTimeDrift},
{"setTimeNTP", Commands::setTimeNTP},
}; };
} }

View File

@@ -67,7 +67,7 @@ const bool Cron::storeEvents()
ArduinoJson::JsonDocument thisJob; ArduinoJson::JsonDocument thisJob;
thisJob["name"] = eventName; thisJob["name"] = eventName;
thisJob["cronExpr"] = cron::to_cronstr(cronExpr); thisJob["cronExpr"] = cron::to_cronstr(cronExpr);
thisJob["action"]["cmd"] = cmd; thisJob["action"]["cmd"] = cmd;
thisJob["action"]["params"] = cmdParams; thisJob["action"]["params"] = cmdParams;
@@ -102,7 +102,7 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons
const auto eventExpr(cron::make_cron(expr)); const auto eventExpr(cron::make_cron(expr));
const auto cmd = action["cmd"].as<std::string>(); const auto cmd = action["cmd"].as<std::string>();
const auto params = action["params"]; const auto params = action["params"];
if (!commands::commandMap.contains(cmd)) if (!commands::s_commandMap.contains(cmd))
{ {
LOG_ERROR("Cron unknown command [", cmd.c_str(), "]"); LOG_ERROR("Cron unknown command [", cmd.c_str(), "]");
return false; return false;
@@ -153,9 +153,14 @@ const bool Cron::delEvent(const std::string &name)
return true; return true;
} }
void cronLoop(void *dev) const Cron::CronEventMap &Cron::getAllEvents()
{ {
auto &cron = Cron::getInstance(*(devices_t *)dev); return m_cronMap;
}
void cronLoop(void *cronPtr)
{
auto &cron = *(Cron *)cronPtr;
while (true) while (true)
{ {
cron.processEvents(); cron.processEvents();
@@ -168,7 +173,7 @@ void Cron::startCron()
if (!m_cronTaskHandle) if (!m_cronTaskHandle)
{ {
LOG_INFO("Cron starting loop"); LOG_INFO("Cron starting loop");
xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, (void *)&m_dev, PRIORITY, &m_cronTaskHandle); xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle);
} }
} }
@@ -217,7 +222,17 @@ const bool Cron::processEvents()
next = cron::cron_next(cronexrp, nowTm); // update next execution time only if event was executed next = cron::cron_next(cronexrp, nowTm); // update next execution time only if event was executed
// otherwise time tracking is lost // otherwise time tracking is lost
LOG_INFO("Cron running event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(next).c_str(), "]"); LOG_INFO("Cron running event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(next).c_str(), "]");
commands::commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens auto action = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens
ArduinoJson::JsonDocument resp;
resp["cmd"] = "logCronJob";
resp["values"]["name"] = eventName;
resp["values"]["now"] = drivers::PCF85063::tm2str(nowTm).c_str();
resp["values"]["next"] = drivers::PCF85063::tm2str(next).c_str();
resp["values"]["action"] = action;
if (m_callback)
{
m_callback(resp);
}
} }
} }
return true; return true;

View File

@@ -8,14 +8,17 @@
#include <PCF85063_Driver.h> #include <PCF85063_Driver.h>
#include <commands.h> #include <commands.h>
#include <mqtt.h>
#include <filesystem> #include <filesystem>
#include <croncpp.h> #include <croncpp.h>
class Cron class Cron
{ {
public: // eventName cronExpression nextExec command parameters public: // eventName cronExpression nextExec command parameters
using CronEvent = std::tuple<std::string, cron::cronexpr, std::tm, ArduinoJson::JsonDocument>; using CronEvent = std::tuple<std::string, cron::cronexpr, std::tm, ArduinoJson::JsonDocument>;
using CronEventMap = std::map<std::string, CronEvent>;
using CronCallback = std::function<void(const ArduinoJson::JsonDocument &)>;
public: public:
static Cron &getInstance(const devices_t &dev) static Cron &getInstance(const devices_t &dev)
@@ -30,11 +33,17 @@ private:
Cron &operator=(const Cron &) = delete; Cron &operator=(const Cron &) = delete;
public: public:
void setResponseCallback(CronCallback &cb)
{
m_callback = cb;
}
const bool loadEvents(); const bool loadEvents();
const bool storeEvents(); const bool storeEvents();
const bool addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action); const bool addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action);
const bool getEvent(const std::string &name, CronEvent &event); const bool getEvent(const std::string &name, CronEvent &event);
const bool delEvent(const std::string &name); const bool delEvent(const std::string &name);
const CronEventMap &getAllEvents();
void startCron(); void startCron();
void stopCron(); void stopCron();
@@ -42,7 +51,8 @@ public:
private: private:
const devices_t &m_dev; const devices_t &m_dev;
CronCallback m_callback;
CronEventMap m_cronMap;
TaskHandle_t m_cronTaskHandle; TaskHandle_t m_cronTaskHandle;
std::map<std::string, CronEvent> m_cronMap;
std::mutex m_mutex; std::mutex m_mutex;
}; };

View File

@@ -10,6 +10,7 @@
typedef struct typedef struct
{ {
drivers::Ethernet &eth;
drivers::PCF85063 &rtc; drivers::PCF85063 &rtc;
drivers::R4DCB08 &tmp; drivers::R4DCB08 &tmp;
drivers::S50140 &seneca; drivers::S50140 &seneca;

View File

@@ -34,36 +34,33 @@ void loop()
// Declared here to keep devices local to the main loop otherwise the kernel crashes // // Declared here to keep devices local to the main loop otherwise the kernel crashes //
auto i2c = drivers::I2C(); auto i2c = drivers::I2C();
auto bus = drivers::MODBUS(9600, SERIAL_8N1); auto bus = drivers::MODBUS(9600, SERIAL_8N1);
auto rtc = drivers::PCF85063(i2c, PCF85063_ADDRESS); auto rtc = drivers::PCF85063(i2c);
auto eth = drivers::Ethernet(conf.m_ethHostname); 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 tmp = drivers::R4DCB08(bus, conf.m_modbusTemperatureAddr);
auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr); auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr);
auto buzzer = drivers::Buzzer(); auto buzzer = drivers::Buzzer();
auto led = drivers::Led(); auto led = drivers::Led();
delay(500); delay(500);
auto io = digitalIO(i2c, bus, {conf.m_modbusRelayAddr}); auto io = digitalIO(i2c, bus, {conf.m_modbusRelayAddr});
// get RTC time drift offset value
rtc.setOffset(conf.m_ntpRtcOffsetRegister);
LOG_INFO("RTC offset register -> ", printHex(rtc.getOffset()).c_str());
// Initialize temperature sensors // Initialize temperature sensors
sensors = tmp.getNum(); sensors = tmp.getNum();
tmp.setCorrection(conf.m_tempCorrectionValues); tmp.setCorrection(conf.m_tempCorrectionValues);
LOG_INFO("Temperature sensors connected ->", sensors); LOG_INFO("Temperature sensors connected ->", sensors);
// Create device structure to pass all devices in the callbacks as needed // Create device structure to pass all devices in the callbacks as needed
devices_t devices(rtc, tmp, seneca, buzzer, led, io); devices_t devices(eth, rtc, tmp, seneca, buzzer, led, io);
//////////////// DEVICES //////////////// //////////////// DEVICES ////////////////
//////////////// MQTT //////////////// //////////////// MQTT ////////////////
auto mqtt = MQTTwrapper(); auto mqtt = MQTTwrapper();
//////////////// MQTT //////////////// //////////////// MQTT ////////////////
//////////////// CRONJOB //////////////// //////////////// MQTT //////////////
auto &cron = Cron::getInstance(devices);
cron.loadEvents();
cron.startCron();
//////////////// CRONJOB ////////////////
//////////////// MQTT ////////////////
/////////////// CALLBACK ////////////// /////////////// CALLBACK //////////////
std::function<void(const ArduinoJson::JsonDocument &)> commandsCallback = MQTTwrapper::ActionCallback commandsCallback =
[&mqtt, &devices](const ArduinoJson::JsonDocument &doc) [&mqtt, &devices](const ArduinoJson::JsonDocument &doc)
{ {
if (!doc["cmd"].is<std::string>()) if (!doc["cmd"].is<std::string>())
@@ -72,11 +69,11 @@ void loop()
return; return;
} }
const std::string cmd = doc["cmd"].as<std::string>(); const std::string cmd = doc["cmd"].as<std::string>();
ArduinoJson::JsonDocument params = doc["params"]; const ArduinoJson::JsonDocument params = doc["params"];
if (commands::commandMap.contains(cmd)) if (commands::s_commandMap.contains(cmd))
{ // call command from command map in this same thread (the MQTT thread) { // call command from command map in this same thread (the MQTT thread)
LOG_INFO("Executing command", cmd.c_str()); LOG_INFO("Executing command", cmd.c_str());
auto answer = std::move(commands::commandMap.at(cmd)(devices, params)); // here the magic happens const auto answer = std::move(commands::s_commandMap.at(cmd)(devices, params)); // here the magic happens
if (answer.isNull()) if (answer.isNull())
return; return;
mqtt.publish(conf.m_mqttPublish["answers"], answer); mqtt.publish(conf.m_mqttPublish["answers"], answer);
@@ -87,6 +84,34 @@ void loop()
} }
}; };
MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message)
{
LOG_DEBUG("onMessage callback [", topic.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(), "]");
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 //////////////// //////////////// NETWORK ////////////////
/////////////// CALLBACK //////////////// /////////////// CALLBACK ////////////////
Network.onEvent( Network.onEvent(
@@ -104,25 +129,31 @@ void loop()
uint8_t mqttRetries(0); uint8_t mqttRetries(0);
while (timeRetries++ < conf.m_ntpRetries) while (timeRetries++ < conf.m_ntpRetries)
{ {
if (eth.getNtpTime(ntpTime) && rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime))) 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); buzzer.beep(250, NOTE_A);
led.setColor(led.COLOR_ORANGE); led.setColor(led.COLOR_ORANGE);
// rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime));
const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime)); const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime));
LOG_INFO("NTP Time Update: ", drivers::PCF85063::datetime2str(dt).c_str()); LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str());
break; break;
} }
delay(100); delay(250);
} }
while (mqttRetries++ < conf.m_mqttRetries) while (mqttRetries++ < conf.m_mqttRetries)
{ {
if (mqtt.connect()) if (mqtt.connect())
{ {
buzzer.beep(250, NOTE_B);
led.setColor(led.COLOR_GREEN); led.setColor(led.COLOR_GREEN);
mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback); mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback);
mqtt.setOnMessageCb(onMessage);
mqtt.setOnPublishCb(onPublish);
break; break;
} }
delay(100); delay(250);
} }
}); });
@@ -133,8 +164,10 @@ void loop()
while (true) while (true)
{ {
const uint32_t start(millis()); const uint32_t start(millis());
const std::string timeStr(rtc.getTimeStr()); drivers::PCF85063::datetime_t datetime;
LOG_INFO("[", k++, "] Loop - Current Datetime", timeStr.c_str()); rtc.readDatetime(datetime);
const std::string timeStr(drivers::PCF85063::datetime2str(datetime));
LOG_INFO("[", k++, "] Loop - Current Datetime UTC", timeStr.c_str());
{ {
ArduinoJson::JsonDocument poll; ArduinoJson::JsonDocument poll;
@@ -166,7 +199,7 @@ void loop()
{ {
LOG_WARN("RESTART!"); LOG_WARN("RESTART!");
buzzer.beep(450, NOTE_D); buzzer.beep(450, NOTE_D);
delay(100); delay(450);
esp_restart(); esp_restart();
} }

View File

@@ -44,7 +44,7 @@ const bool MQTTwrapper::disconnect()
return true; return true;
} }
const bool MQTTwrapper::subscribe(topic_t topic, action_t action) const bool MQTTwrapper::subscribe(const Topic &topic, const ActionCallback action)
{ {
if (m_actionMap.contains(topic)) if (m_actionMap.contains(topic))
{ {
@@ -61,7 +61,7 @@ const bool MQTTwrapper::subscribe(topic_t topic, action_t action)
return false; return false;
} }
const bool MQTTwrapper::unsubscribe(topic_t topic) const bool MQTTwrapper::unsubscribe(const Topic &topic)
{ {
if (!m_actionMap.contains(topic)) if (!m_actionMap.contains(topic))
{ {
@@ -83,7 +83,7 @@ const bool MQTTwrapper::connected()
return m_loopHandle != NULL; return m_loopHandle != NULL;
} }
const bool MQTTwrapper::publish(topic_t topic, const ArduinoJson::JsonDocument obj) const bool MQTTwrapper::publish(const Topic &topic, const ArduinoJson::JsonDocument obj)
{ {
std::string message; std::string message;
if (!m_client.connected()) if (!m_client.connected())
@@ -99,12 +99,32 @@ const bool MQTTwrapper::publish(topic_t topic, const ArduinoJson::JsonDocument o
if (m_client.publish(topic.c_str(), message.c_str())) if (m_client.publish(topic.c_str(), message.c_str()))
{ {
LOG_DEBUG("MQTT published topic [", topic.c_str(), "] - message [", message.c_str(), "]"); LOG_DEBUG("MQTT published topic [", topic.c_str(), "] - message [", message.c_str(), "]");
if (m_onPublish)
{
m_onPublish(topic, message);
}
return true; return true;
} }
LOG_ERROR("MQTT failed to publish topic [", topic.c_str(), "] - message [", message.c_str(), "]"); LOG_ERROR("MQTT failed to publish topic [", topic.c_str(), "] - message [", message.c_str(), "]");
return false; return false;
} }
void MQTTwrapper::setOnMessageCb(MessageCallback cb)
{
if (cb)
m_onReceive = cb;
else
LOG_ERROR("MQTT invalid onReceive Callback");
}
void MQTTwrapper::setOnPublishCb(MessageCallback cb)
{
if (cb)
m_onPublish = cb;
else
LOG_ERROR("MQTT invalid onPublish Callback");
}
void MQTTwrapper::callback(char *topic, uint8_t *payload, unsigned int length) void MQTTwrapper::callback(char *topic, uint8_t *payload, unsigned int length)
{ {
std::string pl; std::string pl;
@@ -120,13 +140,15 @@ void MQTTwrapper::callback(char *topic, uint8_t *payload, unsigned int length)
return; return;
} }
void MQTTwrapper::onMessage(const std::string topic, const std::string message) void MQTTwrapper::onMessage(const Topic topic, const Message message)
{ {
ArduinoJson::JsonDocument obj; ArduinoJson::JsonDocument obj;
LOG_DEBUG("MQTT received topic [", topic.c_str(), "] - message [", message.c_str(), "]"); LOG_DEBUG("MQTT received topic [", topic.c_str(), "] - message [", message.c_str(), "]");
if (ArduinoJson::deserializeJson(obj, message) == ArduinoJson::DeserializationError::Ok) if (ArduinoJson::deserializeJson(obj, message) == ArduinoJson::DeserializationError::Ok)
{ {
m_actionMap[topic](obj); m_actionMap[topic](obj);
if (m_onReceive)
m_onReceive(topic, message);
return; return;
} }
LOG_ERROR("MQTT failed to deserialize message\n", message.c_str()); LOG_ERROR("MQTT failed to deserialize message\n", message.c_str());

View File

@@ -13,12 +13,16 @@
#include <mutex> #include <mutex>
#include <functional> #include <functional>
typedef std::string topic_t;
typedef std::function<void(const ArduinoJson::JsonDocument &)> action_t; // the actions receive a JsonObject containing the received message
typedef std::map<topic_t, action_t> action_map_t;
class MQTTwrapper class MQTTwrapper
{ {
public:
using Topic = std::string;
using Message = std::string;
using MessageCallback = std::function<void(const Topic &topic, const Message &message)>;
using ActionCallback = std::function<void(const ArduinoJson::JsonDocument &)>; // the actions receive a JsonObject containing the received message
using StateChangeCallback = std::function<void(void)>;
using ActionMap = std::map<Topic, ActionCallback>;
private: private:
const std::map<int, std::string> stateMap = { const std::map<int, std::string> stateMap = {
@@ -31,8 +35,7 @@ private:
{2, "MQTT_CONNECT_BAD_CLIENT_ID"}, {2, "MQTT_CONNECT_BAD_CLIENT_ID"},
{3, "MQTT_CONNECT_UNAVAILABLE"}, {3, "MQTT_CONNECT_UNAVAILABLE"},
{4, "MQTT_CONNECT_BAD_CREDENTIALS"}, {4, "MQTT_CONNECT_BAD_CREDENTIALS"},
{5, "MQTT_CONNECT_UNAUTHORIZED"} {5, "MQTT_CONNECT_UNAUTHORIZED"}};
};
private: private:
static MQTTwrapper * static MQTTwrapper *
@@ -54,10 +57,13 @@ public:
const bool disconnect(); const bool disconnect();
const bool connected(); const bool connected();
const bool subscribe(topic_t topic, action_t action); const bool subscribe(const Topic &topic, const ActionCallback action);
const bool unsubscribe(topic_t topic); const bool unsubscribe(const Topic &topic);
const bool publish(topic_t topic, const ArduinoJson::JsonDocument obj); const bool publish(const Topic &topic, const ArduinoJson::JsonDocument obj);
void setOnMessageCb(MessageCallback cb);
void setOnPublishCb(MessageCallback cb);
private: private:
static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage
@@ -68,8 +74,11 @@ private:
private: private:
const Config &m_config; const Config &m_config;
action_map_t m_actionMap; ActionMap m_actionMap;
NetworkClient m_tcp; NetworkClient m_tcp;
PubSubClient m_client; PubSubClient m_client;
TaskHandle_t m_loopHandle; TaskHandle_t m_loopHandle;
MessageCallback m_onPublish;
MessageCallback m_onReceive;
}; };