Compare commits
18 Commits
DEPLOYED
...
pro-develo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1457c6f1d2 | ||
|
|
d07ee18a22 | ||
|
|
9a0bc4c03f | ||
|
|
df66a9d076 | ||
|
|
145698d3b9 | ||
|
|
0952be3141 | ||
|
|
6a6931bde0 | ||
|
|
4aeffc76b0 | ||
|
|
cde86a7f99 | ||
|
|
f9c5ab86ef | ||
|
|
fc2687947a | ||
|
|
637304781c | ||
|
|
c973632fb8 | ||
| a79c4f8ca7 | |||
|
|
bbf604e1a8 | ||
|
|
e37aa58398 | ||
|
|
5bff567863 | ||
|
|
80fda62344 |
72
.vscode/settings.json
vendored
72
.vscode/settings.json
vendored
@@ -1,5 +1,75 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"esp32-hal.h": "c"
|
||||
"*.h": "cpp",
|
||||
"esp32-hal.h": "c",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"netfwd": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"source_location": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp"
|
||||
}
|
||||
}
|
||||
63
README.md
63
README.md
@@ -1,3 +1,64 @@
|
||||
# ETcontroller_PRO
|
||||
|
||||
Nuova versione di ETcontroller hardware (lo scatolo) basata su scheda ESP 32 Waveshare [https://www.waveshare.com/wiki/ESP32-S3-ETH-8DI-8RO#Resources]
|
||||
### Nuova versione di ETcontroller hardware (lo scatolo) basata su scheda ESP32-S3 Waveshare
|
||||
[https://www.waveshare.com/wiki/ESP32-S3-ETH-8DI-8RO#Resources]
|
||||
|
||||
## Tool necessari:
|
||||
|
||||
### Plugin VScode
|
||||
* VScode
|
||||
* PlatformIO Plugin per VScode
|
||||
* GitGraph
|
||||
* C/C++ Extension Pack
|
||||
|
||||
### Toolchain e Librerie
|
||||
Dipendenze e toolchain vengono installate direttamente da PlatformIO.
|
||||
Il firmware e' basato sul framework _Arduino_ e piattaforma _esp-idf_ di Espressif.
|
||||
La versione della piattaforma inclusa in PlatformIO e' deprecata, quindi e' necessario scaicarne una indipendente da GitHub. La versione corrente e' listata nel file `platformio.ini` nella root directory, se fosse necessario aggiornarla si trova a: [https://github.com/pioarduino/platform-espressif32/releases]
|
||||
Vale lo stesso per le librerie dipendenti, se non fossero disponibili tramite PlatformIO si possono cercare su GitHub e scaricare manualmente.
|
||||
|
||||
### Documentazione aggiuntiva
|
||||
Nella cartella `docs` sono presenti i datasheet di tutti i device collegati.
|
||||
I driver sono spesso scritti a manina usando quei documenti come reference.
|
||||
|
||||
## Configurazione Hardware e Build
|
||||
|
||||
### Definizione della board
|
||||
Prima di poter compilare e' necessario copiare il file di descrizione della board `esp32-s3-waveshare8.json` che si trova in questa root directory nella cartella delle board di PlatformIO.
|
||||
* Per Windows `%USERPROFILE%\.platformio\platforms\espressif32\boards`
|
||||
* Per Linux `$HOME/.platformio/platforms/espressif32/boards`
|
||||
* Per MAC `dovrebbe essere come linux`
|
||||
|
||||
### Cofigurazioni di build
|
||||
Le configurazioni disponibili sono:
|
||||
* **Release**: `esp32-s3-waveshare8`
|
||||
Build adatta per il deploy, log su seriale ma non e' possibile il debug con PlatformIO
|
||||
* **Debug**: `esp32-s3-waveshare8-debug`
|
||||
Build per il debug del codice in circuit, attenzione che quando il debugger e' attivo tutti i procesi temporizzati dallo scheduler vanno in pausa per cui le funzioni di rete e comunicazione con le perriferche potrebbero non funzionare correttamente.
|
||||
|
||||
Il cambio di configurazione tra Release e Debug causa un rebuild completo del codice.
|
||||
|
||||
### Partizioni della flash
|
||||
La flash dell'ESP32 e' partizionata secondo lo schema definito in `fatfs_partitions.csv`, che deve rimanere nella root del progetto.
|
||||
Le partizioni sono come segue"
|
||||
* **NVS + OTADATA**: Non toccare assoutamente, contengono il bootloader, se si toccano queste addio programmazione via USB, ci vuole il tool apposta.
|
||||
* **APP_0 + APP_1**: contengono entrambe il firmware, quando avviene un aggiornamento via OTA una partizione e' in stby e riceve il firmware aggiornato. Se l'aggiornamento va a buon fine il boot successivo avviene dalla partizione aggiornata e cosi' via per i successivi.
|
||||
* **FFAT** e' una partizione accessibile dal firmware per essere usata come memoria permanente. Montata dalla classe FSMount. E' di circa 9MB e si comporta come un filesystem FAT32.
|
||||
**Attenzione che e' la flash integrata nel micro, evitare letture e scritture troppo frequenti per non bruciarla**
|
||||
|
||||
### Metodi di upload
|
||||
La porta di upload e' configurata con `upload_protocol` nel file `platformio.ini`.
|
||||
I valori possibili sono:
|
||||
* **_esptool_** per upload USB
|
||||
* **_espota_** per upload via Rete.
|
||||
In questo caso il valore di _upload_port_ deve essere l'indirizzo IP della scheda, che sia settato statico o da DHCP.
|
||||
E' possibile ce si debba permettere a VScode di aggiungere una regola al firewall del PC per permettere il collegamento "unsafe" via UDP
|
||||
|
||||
Il metodo di defaut e' tramite la porta USB, che ha un nome diverso a seconda del sistema operativo host e della porta a cui viene collegata.
|
||||
Se si vuole utilizzare il metoto OTA via rete, questo va abilitato dalla scheda (per motivi di sicurezza).
|
||||
Per abilitare OTA resettare la scheda e nel momento del boot tenere premuto il pulsante blu fino a che il buzzer smette di bippare e il led inizia a lampeggiare verde e giallo alternati: da quel momento e' possibile aggiornare via rete.
|
||||
_Ogni aggiornamento causa il reboot della scheda._
|
||||
|
||||
### Logica del Firmware, come funziona?
|
||||
|
||||
[TODO]
|
||||
Binary file not shown.
82
include/pinlist.h
Normal file
82
include/pinlist.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
enum RO // relay output channels
|
||||
{
|
||||
P4 = 0,
|
||||
P3 = 1,
|
||||
P2 = 2,
|
||||
P1 = 3,
|
||||
RO_4 = 4,
|
||||
FST_FLOOR = 5,
|
||||
GND_FLOOR = 6,
|
||||
PUMP_HT = 7,
|
||||
PUMP_IRR = 8,
|
||||
RETURN = 9,
|
||||
ZONE1 = 10,
|
||||
ZONE2 = 11,
|
||||
ZONE3 = 12,
|
||||
DRIP = 13,
|
||||
RO_14 = 14,
|
||||
RO_15 = 15,
|
||||
RO_MAX = 16 // unused to detect invalid values
|
||||
};
|
||||
|
||||
static const std::map<const int, const char *> RO_2str = {
|
||||
{RO::P1, "HPLimit1"},
|
||||
{RO::P2, "HPLimit2"},
|
||||
{RO::P3, "HPLimit3"},
|
||||
{RO::P4, "HPLimit4"},
|
||||
{RO::RO_4, "Out4"},
|
||||
{RO::FST_FLOOR, "PianoPrimo"},
|
||||
{RO::GND_FLOOR, "PianoTerra"},
|
||||
{RO::PUMP_HT, "PompaRisc"},
|
||||
{RO::PUMP_IRR, "PompaIrr"},
|
||||
{RO::RETURN, "Ricircolo"},
|
||||
{RO::ZONE1, "IrrZona1"},
|
||||
{RO::ZONE2, "IrrZona2"},
|
||||
{RO::ZONE3, "IrrZona3"},
|
||||
{RO::DRIP, "IrrDrip"},
|
||||
{RO::RO_14, "Out14"},
|
||||
{RO::RO_15, "Out15"},
|
||||
{RO::RO_MAX, "Invalid"}};
|
||||
|
||||
enum DI // digital input channels
|
||||
{
|
||||
CONFRESET = 0,
|
||||
RESTART = 1,
|
||||
DI_2 = 2,
|
||||
DI_3 = 3,
|
||||
DI_4 = 4,
|
||||
DI_5 = 5,
|
||||
DI_6 = 6,
|
||||
OTAENABLE = 7,
|
||||
PUMP_PRESSURE = 8,
|
||||
RAIN = 9,
|
||||
IRR_OVERRIDE = 10,
|
||||
DI_11 = 11,
|
||||
DI_12 = 12,
|
||||
DI_13 = 13,
|
||||
DI_14 = 14,
|
||||
DI_15 = 15,
|
||||
DI_MAX = 16 // unused to detect invalid values
|
||||
};
|
||||
|
||||
static const std::map<const int, const char *> DI_2str =
|
||||
{
|
||||
{DI::CONFRESET, "ConfigReset"},
|
||||
{DI::RESTART, "Restart"},
|
||||
{DI::DI_2, "In2"},
|
||||
{DI::DI_3, "In3"},
|
||||
{DI::DI_4, "In4"},
|
||||
{DI::DI_5, "In5"},
|
||||
{DI::DI_6, "In6"},
|
||||
{DI::OTAENABLE, "OtaEnable"},
|
||||
{DI::PUMP_PRESSURE, "IrrPumpPressure"},
|
||||
{DI::RAIN, "IrrRainSensor"},
|
||||
{DI::IRR_OVERRIDE, "IrrRainOverride"},
|
||||
{DI::DI_11, "In11"},
|
||||
{DI::DI_12, "In12"},
|
||||
{DI::DI_13, "In13"},
|
||||
{DI::DI_14, "In14"},
|
||||
{DI::DI_15, "In15"},
|
||||
{DI::DI_MAX, "Invalid"}};
|
||||
@@ -12,6 +12,7 @@ namespace drivers
|
||||
pinMode(c_ledPin, OUTPUT);
|
||||
m_blinkTask = NULL;
|
||||
m_flashTimer = NULL;
|
||||
m_enforce = false;
|
||||
}
|
||||
|
||||
Led::~Led()
|
||||
@@ -20,9 +21,16 @@ namespace drivers
|
||||
pinMode(c_ledPin, INPUT);
|
||||
}
|
||||
|
||||
void Led::setEnforce(const bool enf)
|
||||
{
|
||||
m_enforce = enf;
|
||||
}
|
||||
|
||||
void Led::setColor(const color_t color)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_ledMutex);
|
||||
if (m_enforce)
|
||||
return;
|
||||
blinkStop();
|
||||
m_colorDefault = color;
|
||||
rgbLedWrite(c_ledPin, color.g, color.r, color.b);
|
||||
@@ -58,6 +66,8 @@ namespace drivers
|
||||
void Led::blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_ledMutex);
|
||||
if (m_enforce)
|
||||
return;
|
||||
blinkStop();
|
||||
m_color1 = color;
|
||||
m_color2 = {0, 0, 0};
|
||||
@@ -69,6 +79,8 @@ namespace drivers
|
||||
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);
|
||||
if (m_enforce)
|
||||
return;
|
||||
blinkStop();
|
||||
m_color1 = color1;
|
||||
m_color2 = color2;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace drivers
|
||||
uint8_t b;
|
||||
} color_t;
|
||||
|
||||
const color_t COLOR_OFF = {0, 0, 0};
|
||||
const color_t COLOR_RED = {255, 0, 0};
|
||||
const color_t COLOR_ORANGE = {255, 127, 0};
|
||||
const color_t COLOR_YELLOW = {255, 255, 0};
|
||||
@@ -36,6 +37,7 @@ namespace drivers
|
||||
Led();
|
||||
~Led();
|
||||
|
||||
void setEnforce(const bool enf);
|
||||
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);
|
||||
@@ -60,6 +62,7 @@ namespace drivers
|
||||
TimerHandle_t m_flashTimer;
|
||||
|
||||
bool m_flashing;
|
||||
bool m_enforce;
|
||||
|
||||
std::mutex m_ledMutex;
|
||||
};
|
||||
|
||||
@@ -23,17 +23,19 @@ build_type = release
|
||||
board_build.filesystem = ffat
|
||||
board_build.partitions = fatfs_partition.csv ; se stai usando uno custom
|
||||
|
||||
upload_protocol = espota
|
||||
upload_port = 10.0.2.139
|
||||
|
||||
|
||||
[env:esp32-s3-waveshare8-debug]
|
||||
platform = ${env:esp32-s3-waveshare8.platform}
|
||||
board = ${env:esp32-s3-waveshare8.board}
|
||||
framework = ${env:esp32-s3-waveshare8.framework}
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.4.2
|
||||
arduino-libraries/NTPClient@^3.2.1
|
||||
knolleary/PubSubClient@^2.8
|
||||
robtillaart/CRC@^1.0.3
|
||||
hideakitai/DebugLog@^0.8.4
|
||||
lib_deps = ${env:esp32-s3-waveshare8.lib_deps}
|
||||
|
||||
board_build.filesystem = ffat
|
||||
board_build.partitions = fatfs_partition.csv ; se stai usando uno custom
|
||||
|
||||
build_type = debug
|
||||
build_flags =
|
||||
-O0
|
||||
@@ -44,5 +46,3 @@ build_flags =
|
||||
-fno-tree-sra
|
||||
-fno-builtin
|
||||
|
||||
board_build.filesystem = ffat
|
||||
board_build.partitions = fatfs_partition.csv ; se stai usando uno custom
|
||||
|
||||
162
src/commands.cpp
162
src/commands.cpp
@@ -8,7 +8,6 @@ namespace commands
|
||||
{
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setBuzz(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -72,11 +71,10 @@ namespace commands
|
||||
response["values"]["status"] = "valid";
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
const ArduinoJson::JsonDocument Commands::addCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
response["cmd"] = "setCronJob";
|
||||
response["cmd"] = "addCronJob";
|
||||
|
||||
const auto &eventName = params["name"].as<std::string>();
|
||||
const auto &timeStr = params["cronExpr"].as<std::string>();
|
||||
@@ -86,7 +84,7 @@ namespace commands
|
||||
ArduinoJson::JsonDocument action;
|
||||
if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok)
|
||||
{
|
||||
LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]");
|
||||
LOG_ERROR("addCronJob unable to deserialize cron job [", actionStr.c_str(), "]");
|
||||
response["values"]["status"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
@@ -94,11 +92,31 @@ namespace commands
|
||||
auto &cron = Cron::getInstance(dev);
|
||||
if (!cron.addEvent(eventName, timeStr, action))
|
||||
{
|
||||
LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]");
|
||||
LOG_ERROR("addCronJob unable to add job [", actionStr.c_str(), "]");
|
||||
response["values"]["status"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
LOG_INFO("setCronJob added job [", actionStr.c_str(), "]");
|
||||
LOG_INFO("addCronJob added job [", actionStr.c_str(), "]");
|
||||
response["values"]["status"] = "valid";
|
||||
return response;
|
||||
}
|
||||
const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
response["cmd"] = "setCronJob";
|
||||
const auto &eventName = params["name"].as<std::string>();
|
||||
const auto &statusStr = params["status"].as<std::string>();
|
||||
response["values"]["name"] = eventName;
|
||||
|
||||
auto &cron = Cron::getInstance(dev);
|
||||
if (Cron::str2Enum(statusStr) == Cron::str2Enum("INVALID"))
|
||||
{
|
||||
LOG_ERROR("setCronJob invalid status [", statusStr.c_str(), "]");
|
||||
response["values"]["status"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
cron.setEvent(eventName, Cron::str2Enum(statusStr));
|
||||
LOG_INFO("setCronJob set job [", eventName.c_str(), "] to [", statusStr.c_str(), "]");
|
||||
response["values"]["status"] = "valid";
|
||||
return response;
|
||||
}
|
||||
@@ -122,14 +140,18 @@ namespace commands
|
||||
uint8_t eventNum(0);
|
||||
for (const auto &[name, event] : eventMap)
|
||||
{
|
||||
const auto cmd = std::get<0>(event);
|
||||
response["values"][name] = cmd;
|
||||
ArduinoJson::JsonDocument action;
|
||||
action["cmd"] = event.cmd;
|
||||
action["params"] = event.cmdParams;
|
||||
action["status"] = Cron::enum2Str(event.status);
|
||||
action["next"] = drivers::PCF85063::tm2str(event.next);
|
||||
response["values"][name] = action;
|
||||
eventNum++;
|
||||
}
|
||||
LOG_INFO("getCronJob got [", eventNum, "] events");
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
Cron::CronEvent event;
|
||||
response["values"]["name"] = eventName;
|
||||
if (!cron.getEvent(eventName, event))
|
||||
@@ -138,17 +160,14 @@ namespace commands
|
||||
response["values"]["status"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto cmd = std::get<0>(event);
|
||||
auto cronExpr = std::get<1>(event);
|
||||
auto cmdParams = std::get<3>(event);
|
||||
|
||||
|
||||
ArduinoJson::JsonDocument action;
|
||||
action["cmd"] = cmd;
|
||||
action["params"] = cmdParams;
|
||||
response["values"]["cronExpr"] = cron::to_cronstr(cronExpr);
|
||||
action["cmd"] = event.cmd;
|
||||
action["params"] = event.cmdParams;
|
||||
action["status"] = Cron::enum2Str(event.status);
|
||||
action["next"] = drivers::PCF85063::tm2str(event.next);
|
||||
action["cronExpr"] = cron::to_cronstr(event.cronExpr);
|
||||
response["values"]["action"] = action;
|
||||
|
||||
LOG_INFO("getCronJob get job [", eventName.c_str(), "]");
|
||||
return response;
|
||||
}
|
||||
@@ -168,7 +187,6 @@ namespace commands
|
||||
response["values"]["status"] = "valid";
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -188,6 +206,15 @@ namespace commands
|
||||
|
||||
// SETTERS //
|
||||
// SETTERS //
|
||||
const ArduinoJson::JsonDocument Commands::resetHPcounters(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
response["cmd"] = "resetHPcounters";
|
||||
response["values"]["status"] = "valid";
|
||||
dev.seneca.resetPartialCounters();
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -207,6 +234,8 @@ namespace commands
|
||||
}
|
||||
for (const auto [lvl, ro] : c_hpLimitsMap)
|
||||
{
|
||||
if (ro == RO::RO_MAX)
|
||||
continue; // avoid overshooting relay range
|
||||
if (level == lvl && level != "UNLIMITED")
|
||||
dev.io.digitalOutWrite(ro, true);
|
||||
else
|
||||
@@ -216,7 +245,6 @@ namespace commands
|
||||
response["values"]["status"] = "valid";
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setHeating(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -250,7 +278,6 @@ namespace commands
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
void resetZone(TimerHandle_t th)
|
||||
{
|
||||
devices_t *dev = (devices_t *)pvTimerGetTimerID(th);
|
||||
@@ -265,16 +292,14 @@ namespace commands
|
||||
c_irrigationTimerMap.at(timerName).second = NULL; // reset timer handle for this timer
|
||||
xTimerDelete(th, 0); // delete the timer on expiry
|
||||
}
|
||||
|
||||
void resetWaterPump(TimerHandle_t th)
|
||||
{
|
||||
devices_t *dev = (devices_t *)pvTimerGetTimerID(th);
|
||||
LOG_INFO("setIrrigation shutdown pump");
|
||||
dev->io.digitalOutWrite(RO::IRR_PUMP, false);
|
||||
dev->io.digitalOutWrite(RO::PUMP_IRR, false);
|
||||
s_irrigationPumpTimer = NULL;
|
||||
xTimerDelete(th, 0); // delete the timer on expiry
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -288,7 +313,6 @@ namespace commands
|
||||
const std::string zone(params["zone"].as<std::string>());
|
||||
const uint16_t tOn(params["timeOn"].as<uint16_t>());
|
||||
const uint16_t tPause(params["timePause"].as<uint16_t>());
|
||||
|
||||
response["values"]["zone"] = zone;
|
||||
|
||||
if (zone == "stop")
|
||||
@@ -309,7 +333,7 @@ namespace commands
|
||||
}
|
||||
}
|
||||
LOG_INFO("setIrrigation closing", zoneName.c_str());
|
||||
dev.io.digitalOutWrite(c_irrigationValveMap.at(zoneName), false); // shuto down the valve
|
||||
dev.io.digitalOutWrite(c_irrigationValveMap.at(zoneName), false); // shutdown the valve
|
||||
}
|
||||
if (s_irrigationPumpTimer)
|
||||
{
|
||||
@@ -320,6 +344,13 @@ namespace commands
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!s_rainOverride && !dev.io.digitalInRead(DI::RAIN)) // verify rain sensor and override value (rain sensor input is inverted)
|
||||
{
|
||||
LOG_WARN("setIrrigation skipping zone [", zone.c_str(), "] because its raining");
|
||||
response["values"]["status"] = "rain";
|
||||
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
|
||||
@@ -347,7 +378,7 @@ namespace commands
|
||||
if (!s_irrigationPumpTimer) // Pump has not yet started
|
||||
{
|
||||
s_irrigationPumpTimer = xTimerCreate("pumpTimer", pdMS_TO_TICKS(pumpTime), false, (void *)&dev, resetWaterPump);
|
||||
dev.io.digitalOutWrite(RO::IRR_PUMP, true);
|
||||
dev.io.digitalOutWrite(RO::PUMP_IRR, true);
|
||||
xTimerStart(s_irrigationPumpTimer, 0); // immediate start pump timer
|
||||
LOG_INFO("setIrrigation pump time", pumpTime);
|
||||
}
|
||||
@@ -365,6 +396,7 @@ namespace commands
|
||||
if (shTimer)
|
||||
{
|
||||
dev.io.digitalOutWrite(zoneIoNumber, true);
|
||||
// controllare riempimento serbatoio con controllo del pressostato, magari in un timer
|
||||
xTimerStart(shTimer, 0);
|
||||
timerHandle = shTimer;
|
||||
response["values"]["status"] = "valid";
|
||||
@@ -372,7 +404,20 @@ namespace commands
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::setRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
response["cmd"] = "setRainOverride";
|
||||
if (params.isNull())
|
||||
{
|
||||
LOG_ERROR("setRainOverride incorrect paramaters");
|
||||
return response;
|
||||
}
|
||||
s_rainOverride = params["status"].as<std::string>() == "True" ? true : false;
|
||||
response["values"]["status"] = "valid";
|
||||
LOG_INFO("setRainOverride [", s_rainOverride ? "True]" : "False]");
|
||||
return response;
|
||||
}
|
||||
const ArduinoJson::JsonDocument Commands::setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -394,7 +439,7 @@ namespace commands
|
||||
|
||||
response["values"]["status"] = "valid";
|
||||
response["values"]["time"] = rtc.getTimeStr();
|
||||
LOG_INFO("setTimeNTP -> RTC is [", response["status"]["time"].as<std::string>().c_str(), "]");
|
||||
LOG_INFO("setTimeNTP -> RTC is [", response["values"]["time"].as<std::string>().c_str(), "]");
|
||||
return response;
|
||||
}
|
||||
// SETTERS //
|
||||
@@ -415,56 +460,84 @@ namespace commands
|
||||
LOG_INFO("getHPpower -> power", pinfo.pAct, "current", pinfo.a, "voltage", pinfo.v, "energy", pinfo.whPar);
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getInputStatus(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
response["cmd"] = "getInputStatus";
|
||||
const std::vector<bool> inStatus(dev.io.digitalInReadPort());
|
||||
if (inStatus.empty() || inStatus.size() != dev.io.getInNum())
|
||||
{
|
||||
response["values"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
uint8_t i(0);
|
||||
for (auto s : inStatus)
|
||||
{
|
||||
response["values"][DI_2str.at(i++)] = s;
|
||||
}
|
||||
LOG_INFO("getInputStatus ->", printBoolVec(inStatus).c_str());
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getOutputStatus(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
response["cmd"] = "getOutputStatus";
|
||||
const std::vector<bool> inStatus(dev.io.digitalOutReadPort());
|
||||
if (inStatus.empty() || inStatus.size() != dev.io.getOutNum())
|
||||
{
|
||||
response["values"] = "invalid";
|
||||
return response;
|
||||
}
|
||||
uint8_t i(0);
|
||||
for (auto s : inStatus)
|
||||
{
|
||||
response["values"][RO_2str.at(i++)] = s;
|
||||
}
|
||||
LOG_INFO("getOutputStatus ->", printBoolVec(inStatus).c_str());
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getTemperatures(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getWaterInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
const auto rain = !dev.io.digitalInRead(DI::RAIN) ? "True" : "False";
|
||||
response["cmd"] = "getRainInfo";
|
||||
response["values"]["status"] = rain;
|
||||
LOG_INFO("getRainInfo -> ", rain);
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
LOG_WARN("Comand not yet implemented");
|
||||
return response;
|
||||
}
|
||||
|
||||
const ArduinoJson::JsonDocument Commands::getRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
const auto ovr = s_rainOverride ? "True" : "False";
|
||||
response["cmd"] = "getRainOverride";
|
||||
response["values"]["rainOverride"] = ovr;
|
||||
LOG_INFO("getRainOverride -> ", ovr);
|
||||
return response;
|
||||
}
|
||||
const ArduinoJson::JsonDocument Commands::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms)
|
||||
{
|
||||
ArduinoJson::JsonDocument response;
|
||||
@@ -490,13 +563,12 @@ namespace commands
|
||||
|
||||
auto timeDiff = std::chrono::duration_cast<std::chrono::seconds>(ntpTimePoint - rtcTimePoint);
|
||||
auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD";
|
||||
const int32_t drift = timeDiff.count();
|
||||
|
||||
response["values"]["status"] = "valid";
|
||||
response["values"]["drift"] = (uint32_t)timeDiff.count();
|
||||
response["values"]["drift"] = drift;
|
||||
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");
|
||||
|
||||
LOG_INFO("getTimeDrift -> RTC is [", drift, "] sec, [", direction, "] NTP time");
|
||||
return response;
|
||||
}
|
||||
// GETTERS //
|
||||
|
||||
@@ -8,45 +8,25 @@
|
||||
|
||||
#include <config.h>
|
||||
#include <devices.h>
|
||||
#include <pinlist.h>
|
||||
|
||||
namespace commands
|
||||
{
|
||||
enum RO // relay output channels
|
||||
{
|
||||
P1,
|
||||
P2,
|
||||
P3,
|
||||
P4,
|
||||
NC_1,
|
||||
FST_FLOOR,
|
||||
GND_FLOOR,
|
||||
PUMP_HT,
|
||||
IRR_PUMP,
|
||||
Z1,
|
||||
Z2,
|
||||
Z3,
|
||||
AUX,
|
||||
RETURN,
|
||||
NC_3,
|
||||
NC_4,
|
||||
RO_MAX // unused to detect invalid values
|
||||
};
|
||||
|
||||
static const std::map<const std::string, uint8_t> c_hpLimitsMap = {{"P1", RO::P1},
|
||||
{"P2", RO::P2},
|
||||
{"P3", RO::P3},
|
||||
{"P4", RO::P4},
|
||||
{"UNLIMITED", RO::P1}};
|
||||
{"UNLIMITED", RO::RO_MAX}};
|
||||
|
||||
static const std::map<const std::string, uint8_t> c_heatingValveMap = {{"pump", RO::PUMP_HT},
|
||||
{"first", RO::FST_FLOOR},
|
||||
{"ground", RO::GND_FLOOR}};
|
||||
|
||||
static const std::map<const std::string, uint8_t> c_irrigationValveMap = {{"ricircolo", RO::RETURN},
|
||||
{"zone1", RO::Z1},
|
||||
{"zone2", RO::Z2},
|
||||
{"zone3", RO::Z3},
|
||||
{"rubinetti", RO::AUX}};
|
||||
{"zone1", RO::ZONE1},
|
||||
{"zone2", RO::ZONE2},
|
||||
{"zone3", RO::ZONE3},
|
||||
{"rubinetti", RO::DRIP}};
|
||||
|
||||
static std::map<const std::string, std::pair<const char *, TimerHandle_t>> c_irrigationTimerMap = {{"ricircolo", {"ricircolo", NULL}},
|
||||
{"zone1", {"zone1", NULL}},
|
||||
@@ -55,6 +35,7 @@ namespace commands
|
||||
{"rubinetti", {"rubinetti", NULL}}};
|
||||
|
||||
static TimerHandle_t s_irrigationPumpTimer = NULL;
|
||||
static bool s_rainOverride = false;
|
||||
|
||||
// define command callback type
|
||||
using Command = std::function<const ArduinoJson::JsonDocument(const devices_t &, const ArduinoJson::JsonDocument &)>;
|
||||
@@ -73,15 +54,18 @@ namespace commands
|
||||
|
||||
// CRONJOBS //
|
||||
static const ArduinoJson::JsonDocument loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument addCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
|
||||
// SETTERS //
|
||||
static const ArduinoJson::JsonDocument resetHPcounters(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setHeating(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
|
||||
// GETTERS //
|
||||
@@ -93,31 +77,38 @@ namespace commands
|
||||
static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument getRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||
};
|
||||
|
||||
static const std::map<const std::string, Command> s_commandMap = {
|
||||
|
||||
// TEST
|
||||
{"setBuzz", Commands::setBuzz},
|
||||
|
||||
// CONFIG
|
||||
{"setConfig", Commands::setConfig},
|
||||
{"getConfig", Commands::getConfig},
|
||||
|
||||
// CRONJOBS
|
||||
{"loadCronJob", Commands::loadCronJob},
|
||||
{"addCronJob", Commands::addCronJob},
|
||||
{"setCronJob", Commands::setCronJob},
|
||||
{"getCronJob", Commands::getCronJob},
|
||||
{"delCronJob", Commands::delCronJob},
|
||||
{"storeCronJob", Commands::storeCronJob},
|
||||
|
||||
// SETTERS
|
||||
{"resetHPcounters", Commands::resetHPcounters},
|
||||
{"setHPlimit", Commands::setHPlimit},
|
||||
{"setHeating", Commands::setHeating},
|
||||
{"setIrrigation", Commands::setIrrigation},
|
||||
|
||||
{"setRainOverride", Commands::setRainOverride},
|
||||
// GETTERS
|
||||
{"getHPpower", Commands::getHPpower},
|
||||
{"setHeating", Commands::setHeating},
|
||||
|
||||
{"getRainInfo", Commands::getRainInfo},
|
||||
{"getInputStatus", Commands::getInputStatus},
|
||||
{"getOutputStatus", Commands::getOutputStatus},
|
||||
{"getRainOverride", Commands::getRainOverride},
|
||||
// NTP and Time
|
||||
{"getTimeDrift", Commands::getTimeDrift},
|
||||
{"setTimeNTP", Commands::setTimeNTP},
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
109
src/cronjobs.cpp
109
src/cronjobs.cpp
@@ -4,6 +4,8 @@
|
||||
|
||||
#define STACK_DEPTH 4096
|
||||
#define PRIORITY 3
|
||||
#define PROCESS_INTERVAL 1000
|
||||
#define PROCESS_CORE 0
|
||||
|
||||
const bool Cron::loadEvents()
|
||||
{
|
||||
@@ -22,8 +24,9 @@ const bool Cron::loadEvents()
|
||||
}
|
||||
|
||||
std::string buf;
|
||||
ArduinoJson::serializeJsonPretty(cronFileContent, buf);
|
||||
LOG_INFO("Cron loadEvents loaded cronjobs.json\n", buf.c_str());
|
||||
ArduinoJson::serializeJson(cronFileContent, buf);
|
||||
LOG_INFO("Cron loadEvents loaded cronjobs.json");
|
||||
LOG_INFO(buf.c_str());
|
||||
|
||||
ArduinoJson::JsonArray cronjobList = cronFileContent.as<JsonArray>();
|
||||
LOG_INFO("Cron loadEvents loaded [", cronjobList.size(), "] events");
|
||||
@@ -31,12 +34,12 @@ const bool Cron::loadEvents()
|
||||
{
|
||||
const auto &eventName = job["name"].as<std::string>();
|
||||
const auto &cronExpr = job["cronExpr"].as<std::string>();
|
||||
const auto status = str2Enum(job["status"].as<std::string>());
|
||||
ArduinoJson::JsonDocument action(job["action"]);
|
||||
if (!addEvent(eventName, cronExpr, action))
|
||||
if (!addEvent(eventName, cronExpr, action, status))
|
||||
LOG_ERROR("Cron failed to load event [", eventName.c_str(), "]");
|
||||
else
|
||||
LOG_INFO("Cron loaded event [", eventName.c_str(), "]");
|
||||
delay(10);
|
||||
}
|
||||
cronFile.close();
|
||||
return true;
|
||||
@@ -56,34 +59,28 @@ const bool Cron::storeEvents()
|
||||
ArduinoJson::JsonDocument cronFileContent;
|
||||
ArduinoJson::JsonArray cronFileArray = cronFileContent.to<JsonArray>();
|
||||
|
||||
for (const auto &job : m_cronMap) // convert cron events map to json file
|
||||
for (const auto &[eventName, eventParams] : m_cronMap) // convert cron events map to json file
|
||||
{
|
||||
const auto &eventName = job.first;
|
||||
const auto ¶ms = job.second;
|
||||
|
||||
const auto &cmd = std::get<0>(params);
|
||||
const auto &cronExpr = std::get<1>(params);
|
||||
const auto &cmdParams = std::get<3>(params);
|
||||
|
||||
ArduinoJson::JsonDocument thisJob;
|
||||
thisJob["name"] = eventName;
|
||||
thisJob["cronExpr"] = cron::to_cronstr(cronExpr);
|
||||
thisJob["action"]["cmd"] = cmd;
|
||||
thisJob["action"]["params"] = cmdParams;
|
||||
|
||||
thisJob["cronExpr"] = cron::to_cronstr(eventParams.cronExpr);
|
||||
thisJob["status"] = enum2Str(eventParams.status);
|
||||
thisJob["action"]["cmd"] = eventParams.cmd;
|
||||
thisJob["action"]["params"] = eventParams.cmdParams;
|
||||
cronFileArray.add(thisJob);
|
||||
}
|
||||
|
||||
std::string buf;
|
||||
ArduinoJson::serializeJsonPretty(cronFileContent, buf);
|
||||
LOG_INFO("Cron storeEvents generated cronjobs.json\n", buf.c_str());
|
||||
ArduinoJson::serializeJson(cronFileContent, buf);
|
||||
LOG_INFO("Cron storeEvents generated cronjobs.json");
|
||||
LOG_INFO(buf.c_str());
|
||||
|
||||
ArduinoJson::serializeJson(cronFileContent, cronFile);
|
||||
cronFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool Cron::addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action)
|
||||
const bool Cron::addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action, const CronStatus status)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_cronMap.contains(name))
|
||||
@@ -99,9 +96,9 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons
|
||||
|
||||
try
|
||||
{
|
||||
const auto eventExpr(cron::make_cron(expr));
|
||||
const auto cmd = action["cmd"].as<std::string>();
|
||||
const auto params = action["params"];
|
||||
const auto cronExpr(cron::make_cron(expr));
|
||||
if (!commands::s_commandMap.contains(cmd))
|
||||
{
|
||||
LOG_ERROR("Cron unknown command [", cmd.c_str(), "]");
|
||||
@@ -113,11 +110,11 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons
|
||||
LOG_ERROR("Cron unable to update current time");
|
||||
return false;
|
||||
}
|
||||
std::tm nowTm = drivers::PCF85063::datetime2tm(now);
|
||||
auto next = cron::cron_next(eventExpr, nowTm);
|
||||
JsonDocument act(params);
|
||||
const std::tm nowTm = drivers::PCF85063::datetime2tm(now);
|
||||
const std::tm next = cron::cron_next(cronExpr, nowTm);
|
||||
JsonDocument cmdParams(params); // create a copy of command parameters
|
||||
LOG_INFO("Cron adding event [", name.c_str(), "] next execution [", drivers::PCF85063::tm2str(next).c_str(), "]");
|
||||
m_cronMap[name] = std::make_tuple(cmd, eventExpr, next, act);
|
||||
m_cronMap[name] = CronEvent(cmd, cmdParams, cronExpr, next, status);
|
||||
}
|
||||
catch (cron::bad_cronexpr const &ex)
|
||||
{
|
||||
@@ -127,6 +124,19 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool Cron::setEvent(const std::string &name, const CronStatus status)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_cronMap.contains(name))
|
||||
{
|
||||
LOG_ERROR("Cron event [", name.c_str(), "] does not exist");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Cron set event [", name.c_str(), "] status [", enum2Str(status).c_str(), "]");
|
||||
m_cronMap.at(name).status = status;
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool Cron::getEvent(const std::string &name, CronEvent &event)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
@@ -164,7 +174,7 @@ void cronLoop(void *cronPtr)
|
||||
while (true)
|
||||
{
|
||||
cron.processEvents();
|
||||
delay(1000);
|
||||
delay(PROCESS_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +183,11 @@ void Cron::startCron()
|
||||
if (!m_cronTaskHandle)
|
||||
{
|
||||
LOG_INFO("Cron starting loop");
|
||||
xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle);
|
||||
if (xTaskCreatePinnedToCore(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle, PROCESS_CORE) != pdPASS)
|
||||
{
|
||||
LOG_ERROR("Cron failed to start loop");
|
||||
m_cronTaskHandle = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,38 +215,43 @@ const bool Cron::processEvents()
|
||||
|
||||
std::tm nowTm = drivers::PCF85063::datetime2tm(now);
|
||||
|
||||
for (auto &event : m_cronMap)
|
||||
for (auto &[eventName, eventParams] : m_cronMap)
|
||||
{
|
||||
auto &eventName = event.first;
|
||||
auto &eventAction = event.second;
|
||||
|
||||
auto &cmd = std::get<0>(eventAction);
|
||||
auto &cronexrp = std::get<1>(eventAction);
|
||||
auto &next = std::get<2>(eventAction);
|
||||
auto &cmdParams = std::get<3>(eventAction);
|
||||
|
||||
const auto nowPoint = std::chrono::system_clock::from_time_t(std::mktime(&nowTm));
|
||||
const auto nextEventPoint = std::chrono::system_clock::from_time_t(std::mktime(&next));
|
||||
const auto nextEventPoint = std::chrono::system_clock::from_time_t(std::mktime(&eventParams.next));
|
||||
|
||||
LOG_DEBUG("Cron current time [", std::asctime(&nowTm), "]");
|
||||
LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", drivers::PCF85063::tm2str(next).c_str(), "]");
|
||||
LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]");
|
||||
|
||||
if (nextEventPoint <= nowPoint) // execution time hs passed, run event
|
||||
{
|
||||
next = cron::cron_next(cronexrp, nowTm); // update next execution time only if event was executed
|
||||
// otherwise time tracking is lost
|
||||
LOG_INFO("Cron running event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(next).c_str(), "]");
|
||||
auto action = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens
|
||||
ArduinoJson::JsonDocument resp;
|
||||
ArduinoJson::JsonDocument action;
|
||||
eventParams.next = cron::cron_next(eventParams.cronExpr, nowTm); // update next execution time only if event was executed, otherwise time tracking is lost
|
||||
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)
|
||||
resp["values"]["next"] = drivers::PCF85063::tm2str(eventParams.next).c_str();
|
||||
resp["values"]["status"] = enum2Str(eventParams.status).c_str();
|
||||
switch (eventParams.status)
|
||||
{
|
||||
m_callback(resp);
|
||||
case CronStatus::ACTIVE:
|
||||
LOG_INFO("Cron running ACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]");
|
||||
action = commands::s_commandMap.at(eventParams.cmd)(m_dev, eventParams.cmdParams); // here the magic happens
|
||||
resp["values"]["action"] = action;
|
||||
break;
|
||||
case CronStatus::INACTIVE:
|
||||
LOG_INFO("Cron skipping INACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]");
|
||||
break;
|
||||
case CronStatus::SKIP:
|
||||
LOG_INFO("Cron skipping one time ACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]");
|
||||
eventParams.status = CronStatus::ACTIVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (m_callback)
|
||||
m_callback(resp); // execute cronLog callback action
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -13,10 +13,39 @@
|
||||
#include <filesystem>
|
||||
#include <croncpp.h>
|
||||
|
||||
enum class CronStatus
|
||||
{
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
SKIP,
|
||||
INVALID
|
||||
};
|
||||
|
||||
static const std::map<const CronStatus, std::string> c_statusEnum2Str = {
|
||||
{CronStatus::ACTIVE, "ACTIVE"},
|
||||
{CronStatus::INACTIVE, "INACTIVE"},
|
||||
{CronStatus::SKIP, "SKIP"},
|
||||
{CronStatus::INVALID, "INVALID"}};
|
||||
|
||||
static const std::map<const std::string, CronStatus> c_statusStr2Enum = {
|
||||
{"ACTIVE", CronStatus::ACTIVE},
|
||||
{"INACTIVE", CronStatus::INACTIVE},
|
||||
{"SKIP", CronStatus::SKIP},
|
||||
{"INVALID", CronStatus::INVALID}};
|
||||
|
||||
class Cron
|
||||
{
|
||||
public: // eventName cronExpression nextExec command parameters
|
||||
using CronEvent = std::tuple<std::string, cron::cronexpr, std::tm, ArduinoJson::JsonDocument>;
|
||||
public:
|
||||
|
||||
struct CronEvent
|
||||
{
|
||||
std::string cmd;
|
||||
ArduinoJson::JsonDocument cmdParams;
|
||||
cron::cronexpr cronExpr;
|
||||
std::tm next;
|
||||
CronStatus status;
|
||||
};
|
||||
|
||||
using CronEventMap = std::map<std::string, CronEvent>;
|
||||
using CronCallback = std::function<void(const ArduinoJson::JsonDocument &)>;
|
||||
|
||||
@@ -40,7 +69,8 @@ public:
|
||||
|
||||
const bool loadEvents();
|
||||
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 CronStatus status = CronStatus::ACTIVE);
|
||||
const bool setEvent(const std::string &name, const CronStatus status);
|
||||
const bool getEvent(const std::string &name, CronEvent &event);
|
||||
const bool delEvent(const std::string &name);
|
||||
const CronEventMap &getAllEvents();
|
||||
@@ -49,6 +79,20 @@ public:
|
||||
void stopCron();
|
||||
const bool processEvents();
|
||||
|
||||
static const std::string enum2Str(const CronStatus status)
|
||||
{
|
||||
if (!c_statusEnum2Str.contains(status))
|
||||
return "INVALID";
|
||||
return c_statusEnum2Str.at(status);
|
||||
}
|
||||
|
||||
static const CronStatus str2Enum(const std::string &status)
|
||||
{
|
||||
if (!c_statusStr2Enum.contains(status))
|
||||
return CronStatus::INVALID;
|
||||
return c_statusStr2Enum.at(status);
|
||||
}
|
||||
|
||||
private:
|
||||
const devices_t &m_dev;
|
||||
CronCallback m_callback;
|
||||
|
||||
57
src/main.cpp
57
src/main.cpp
@@ -8,9 +8,11 @@
|
||||
#include <commands.h>
|
||||
#include <cronjobs.h>
|
||||
#include <mqtt.h>
|
||||
#include <ota.h>
|
||||
|
||||
#include <devices.h>
|
||||
#include "utils.h"
|
||||
#include <utils.h>
|
||||
#include <pinlist.h>
|
||||
|
||||
/////////////// GLOBALS ///////////////
|
||||
Config &conf = Config::getInstance();
|
||||
@@ -22,6 +24,11 @@ void setup()
|
||||
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()
|
||||
@@ -29,6 +36,8 @@ 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 //
|
||||
@@ -40,8 +49,10 @@ void loop()
|
||||
auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr);
|
||||
auto buzzer = drivers::Buzzer();
|
||||
auto led = drivers::Led();
|
||||
delay(500);
|
||||
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());
|
||||
@@ -49,9 +60,8 @@ void loop()
|
||||
sensors = tmp.getNum();
|
||||
tmp.setCorrection(conf.m_tempCorrectionValues);
|
||||
LOG_INFO("Temperature sensors connected ->", sensors);
|
||||
|
||||
// Create device structure to pass all devices in the callbacks as needed
|
||||
devices_t devices(eth, rtc, tmp, seneca, buzzer, led, io);
|
||||
// Initialize OTA updater if needed
|
||||
auto ota = OTA(devices);
|
||||
//////////////// DEVICES ////////////////
|
||||
|
||||
//////////////// MQTT ////////////////
|
||||
@@ -86,13 +96,13 @@ void loop()
|
||||
|
||||
MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message)
|
||||
{
|
||||
LOG_DEBUG("onMessage callback [", topic.c_str(), "]");
|
||||
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(), "]");
|
||||
LOG_DEBUG("onPublish callback [", topic.c_str(), "]\n", message.c_str());
|
||||
devices.led.setColor(devices.led.COLOR_SKYBLUE);
|
||||
};
|
||||
|
||||
@@ -121,8 +131,20 @@ void loop()
|
||||
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);
|
||||
@@ -135,7 +157,6 @@ void loop()
|
||||
{ // skip NTP update for drift testing
|
||||
buzzer.beep(250, NOTE_A);
|
||||
led.setColor(led.COLOR_ORANGE);
|
||||
// rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime));
|
||||
const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime));
|
||||
LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str());
|
||||
break;
|
||||
@@ -165,6 +186,16 @@ void loop()
|
||||
{
|
||||
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());
|
||||
@@ -182,12 +213,12 @@ void loop()
|
||||
ArduinoJson::JsonDocument ti;
|
||||
auto tempinfo = tmp.getTempAll();
|
||||
ti["solar"] = tempinfo.at(0);
|
||||
ti["acs"] = tempinfo.at(0);
|
||||
ti["heating"] = tempinfo.at(0);
|
||||
ti["acs"] = tempinfo.at(1);
|
||||
ti["heating"] = tempinfo.at(2);
|
||||
mqtt.publish(conf.m_mqttPublish["temperatures"], ti);
|
||||
};
|
||||
|
||||
if (io.digitalInRead(0)) // ROSSO - Config Reset
|
||||
if (io.digitalInRead(DI::CONFRESET)) // ROSSO - Config Reset
|
||||
{
|
||||
LOG_WARN("Config RESET!");
|
||||
buzzer.beep(450, NOTE_E);
|
||||
@@ -195,7 +226,7 @@ void loop()
|
||||
conf.resetConfig();
|
||||
}
|
||||
|
||||
if (io.digitalInRead(1)) // GIALLO - Restart
|
||||
if (io.digitalInRead(DI::RESTART)) // GIALLO - Restart
|
||||
{
|
||||
LOG_WARN("RESTART!");
|
||||
buzzer.beep(450, NOTE_D);
|
||||
@@ -209,4 +240,4 @@ void loop()
|
||||
////////////////////////////////////////
|
||||
///////// MAIN LOOP INSIDE LOOP ////////
|
||||
////////////////////////////////////////
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#define STACK_DEPTH 8192
|
||||
#define BUFFER_SIZE 2048
|
||||
#define PRIORITY 2
|
||||
#define PROCESS_CORE 1
|
||||
|
||||
MQTTwrapper::MQTTwrapper() : m_config(Config::getInstance()), m_tcp(NetworkClient()), m_client(PubSubClient(m_tcp)), m_loopHandle(NULL)
|
||||
{
|
||||
@@ -27,7 +28,11 @@ const bool MQTTwrapper::connect()
|
||||
LOG_INFO("MQTT client connected to", m_config.m_mqttHost.c_str());
|
||||
if (m_loopHandle == NULL)
|
||||
{
|
||||
xTaskCreate(clientLoop, "mqttLoop", STACK_DEPTH, this, PRIORITY, &m_loopHandle);
|
||||
if (xTaskCreatePinnedToCore(clientLoop, "mqttLoop", STACK_DEPTH, this, PRIORITY, &m_loopHandle, PROCESS_CORE) != pdPASS)
|
||||
{
|
||||
m_loopHandle = NULL;
|
||||
return false;
|
||||
}
|
||||
m_client.setCallback(MQTTwrapper::callback);
|
||||
}
|
||||
return true;
|
||||
|
||||
134
src/ota.cpp
Normal file
134
src/ota.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include <ota.h>
|
||||
|
||||
#define STACK_DEPTH 4096
|
||||
#define TASK_PRIORITY 2
|
||||
#define PROCESS_CORE 1
|
||||
|
||||
OTA::OTA(const devices_t &dev) : m_dev(dev), m_taskHandle(NULL), m_updating(false), m_prevPercent(0)
|
||||
{
|
||||
LOG_WARN("OTA begin, waiting for connection on [", dev.eth.localIP().toString().c_str(), "]");
|
||||
}
|
||||
|
||||
OTA::~OTA()
|
||||
{
|
||||
end();
|
||||
LOG_WARN("OTA end");
|
||||
}
|
||||
|
||||
void OTA::begin()
|
||||
{
|
||||
if (m_taskHandle)
|
||||
{
|
||||
LOG_ERROR("OTA already started");
|
||||
return;
|
||||
}
|
||||
ArduinoOTA.setRebootOnSuccess(true);
|
||||
ArduinoOTA.onStart(s_onStart);
|
||||
ArduinoOTA.onEnd(s_onEnd);
|
||||
ArduinoOTA.onProgress(s_onProgress);
|
||||
ArduinoOTA.onError(s_onError);
|
||||
if (xTaskCreatePinnedToCore(handle, "otaUpdate", STACK_DEPTH, this, TASK_PRIORITY, &m_taskHandle, PROCESS_CORE) != pdPASS)
|
||||
{
|
||||
m_taskHandle = NULL;
|
||||
LOG_ERROR("OTA failed to create handle task");
|
||||
return;
|
||||
}
|
||||
ArduinoOTA.begin(); // start the OTA server
|
||||
m_dev.led.blinkAlternate(100, 100, m_dev.led.COLOR_ORANGE, m_dev.led.COLOR_SKYBLUE);
|
||||
m_dev.led.setEnforce(true); // take unique control of the LED
|
||||
m_active = true;
|
||||
LOG_WARN("OTA started");
|
||||
return;
|
||||
}
|
||||
|
||||
void OTA::end()
|
||||
{
|
||||
if (m_updating)
|
||||
{
|
||||
LOG_WARN("OTA cannot cancel update while running");
|
||||
return;
|
||||
}
|
||||
if (m_taskHandle)
|
||||
{
|
||||
vTaskDelete(m_taskHandle);
|
||||
m_taskHandle = NULL;
|
||||
m_updating = false;
|
||||
ArduinoOTA.end();
|
||||
m_active = false;
|
||||
m_dev.led.setColor(m_dev.led.COLOR_GREEN);
|
||||
m_dev.led.setEnforce(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool OTA::isActive()
|
||||
{
|
||||
return m_active;
|
||||
}
|
||||
|
||||
void OTA::onProgress(const uint32_t progress, const uint32_t total)
|
||||
{
|
||||
float percent = (progress * 100.0f) / total;
|
||||
if (percent > m_prevPercent + 5.0f)
|
||||
{
|
||||
LOG_INFO("OTA progress [", percent, "]%");
|
||||
m_prevPercent = percent;
|
||||
}
|
||||
}
|
||||
|
||||
void OTA::onStart()
|
||||
{
|
||||
LOG_WARN("OTA update started");
|
||||
m_updating = true;
|
||||
m_dev.led.setEnforce(false);
|
||||
m_dev.led.blinkAlternate(25, 50, m_dev.led.COLOR_BLUE, m_dev.led.COLOR_OFF);
|
||||
m_dev.led.setEnforce(true);
|
||||
m_prevPercent = 0;
|
||||
}
|
||||
|
||||
void OTA::onEnd()
|
||||
{
|
||||
LOG_WARN("OTA update end");
|
||||
m_updating = false;
|
||||
m_dev.led.setEnforce(false);
|
||||
m_dev.led.blinkAlternate(50, 50, m_dev.led.COLOR_GREEN, m_dev.led.COLOR_YELLOW);
|
||||
m_dev.led.setEnforce(true);
|
||||
}
|
||||
|
||||
void OTA::onError(const ota_error_t err)
|
||||
{
|
||||
LOG_ERROR("OTA Error [", err, "]");
|
||||
switch (err)
|
||||
{
|
||||
case OTA_AUTH_ERROR:
|
||||
LOG_ERROR("OTA authentication error");
|
||||
break;
|
||||
case OTA_BEGIN_ERROR:
|
||||
LOG_ERROR("OTA begin errror");
|
||||
break;
|
||||
case OTA_CONNECT_ERROR:
|
||||
LOG_ERROR("OTA connection error");
|
||||
break;
|
||||
case OTA_RECEIVE_ERROR:
|
||||
LOG_ERROR("OTA receive error");
|
||||
break;
|
||||
case OTA_END_ERROR:
|
||||
LOG_ERROR("OTA end error");
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("OTA unknown error");
|
||||
};
|
||||
m_updating = false;
|
||||
end(); // end ota on error
|
||||
}
|
||||
|
||||
void OTA::handle(void *params)
|
||||
{
|
||||
OTA *ota = (OTA *)params;
|
||||
while (true)
|
||||
{ // task never returns
|
||||
ArduinoOTA.handle();
|
||||
delay(50);
|
||||
}
|
||||
vTaskDelete(ota->m_taskHandle);
|
||||
ota->m_taskHandle = NULL;
|
||||
}
|
||||
52
src/ota.h
Normal file
52
src/ota.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
|
||||
|
||||
#include <DebugLog.h>
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <devices.h>
|
||||
|
||||
class OTA
|
||||
{
|
||||
|
||||
public:
|
||||
OTA(const devices_t &dev);
|
||||
~OTA();
|
||||
|
||||
void begin();
|
||||
void end();
|
||||
bool isActive();
|
||||
|
||||
private:
|
||||
void onProgress(const uint32_t progress, const uint32_t total);
|
||||
void onStart();
|
||||
void onEnd();
|
||||
void onError(const ota_error_t err);
|
||||
static void handle(void *params);
|
||||
|
||||
private:
|
||||
const devices_t &m_dev;
|
||||
TaskHandle_t m_taskHandle;
|
||||
bool m_updating;
|
||||
bool m_active;
|
||||
float m_prevPercent;
|
||||
|
||||
private: // callbacks, do not init in code
|
||||
ArduinoOTAClass::THandlerFunction s_onStart = [this]()
|
||||
{
|
||||
this->onStart();
|
||||
};
|
||||
ArduinoOTAClass::THandlerFunction s_onEnd = [this]()
|
||||
{
|
||||
this->onEnd();
|
||||
};
|
||||
ArduinoOTAClass::THandlerFunction_Progress s_onProgress = [this](const uint32_t progress, const uint32_t total)
|
||||
{
|
||||
this->onProgress(progress, total);
|
||||
};
|
||||
ArduinoOTAClass::THandlerFunction_Error s_onError = [this](const ota_error_t err)
|
||||
{
|
||||
this->onError(err);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user