#include #include namespace commands { void restart(TimerHandle_t t) { esp_restart(); } const ArduinoJson::JsonDocument Commands::setBuzz(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; dev.buzzer.beep(500, NOTE_Bb); return response; } // CONFIG // // CONFIG // const ArduinoJson::JsonDocument Commands::setConfig(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; auto &conf = Config::getInstance(); std::string buf; response["cmd"] = "setConfig"; auto values = response["values"].to(); if (params.isNull()) { values["status"] = "Invalid"; return response; } conf.setConfig(params); values["status"] = "Valid"; serializeJson(params, buf); LOG_INFO("setConfig ->", buf.c_str()); TimerHandle_t resetTimer(xTimerCreate("restartTimer", pdMS_TO_TICKS(5000), false, NULL, restart)); LOG_WARN("setConfig will cause restart!"); if (resetTimer) { xTimerStart(resetTimer, 0); } return response; } const ArduinoJson::JsonDocument Commands::getConfig(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; auto &conf = Config::getInstance(); std::string buf; response["cmd"] = "getConfig"; response["values"] = conf.getConfig(); serializeJson(response["values"], buf); LOG_INFO("getConfig ->", buf.c_str()); return response; } // CONFIG // // CONFIG // // CRONJOBS // // CRONJOBS // const ArduinoJson::JsonDocument Commands::loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "loadCronJob"; auto &cron = Cron::getInstance(dev); if (!cron.loadEvents()) { LOG_ERROR("loadCronJob failed to load events from flash"); response["values"]["status"] = "invalid"; return response; } 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(); const auto &timeStr = params["cronExpr"].as(); const auto &actionStr = params["action"].as(); response["values"]["name"] = eventName; ArduinoJson::JsonDocument action; if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok) { LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]"); response["values"]["status"] = "invalid"; return response; } auto &cron = Cron::getInstance(dev); if (!cron.addEvent(eventName, timeStr, action)) { LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]"); response["values"]["status"] = "invalid"; return response; } LOG_INFO("setCronJob added job [", actionStr.c_str(), "]"); response["values"]["status"] = "valid"; return response; } const ArduinoJson::JsonDocument Commands::getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "getCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); if (eventName.empty()) { 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"; 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); response["values"]["action"] = action; LOG_INFO("getCronJob get job [", eventName.c_str(), "]"); return response; } const ArduinoJson::JsonDocument Commands::delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "delCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); response["values"]["name"] = eventName; if (eventName.empty() || !cron.delEvent(eventName)) { LOG_ERROR("delCronJob failed to delete job [", eventName.c_str(), "]"); response["values"]["status"] = "invalid"; return response; } response["values"]["status"] = "valid"; return response; } const ArduinoJson::JsonDocument Commands::storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "storeCronJob"; auto &cron = Cron::getInstance(dev); if (!cron.storeEvents()) { LOG_ERROR("storeCronJob failed to store events in flash"); response["values"]["status"] = "invalid"; return response; } response["values"]["status"] = "valid"; return response; } // CRONJOBS // // CRONJOBS // // SETTERS // // SETTERS // const ArduinoJson::JsonDocument Commands::setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "setHPlimit"; if (!params["level"].is()) { LOG_ERROR("setHPlimit incorrect parameters"); return response; } const auto level = params["level"].as(); response["values"]["level"] = level; if (!c_hpLimitsMap.contains(level)) { LOG_ERROR("setHPlimit invalid level", level.c_str()); response["values"]["status"] = "invalid"; return response; } 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 dev.io.digitalOutWrite(ro, false); } LOG_INFO("setHPlimit -> level", level.c_str()); response["values"]["status"] = "valid"; return response; } const ArduinoJson::JsonDocument Commands::setHeating(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "setHeating"; if (params.isNull()) { LOG_ERROR("setHeating incorrect paramaters"); return response; } for (const auto [lvl, ro] : c_heatingValveMap) { if (params[lvl].isNull()) continue; if (params[lvl] == "ON") { dev.io.digitalOutWrite(ro, true); response["values"][lvl] = "ON"; LOG_INFO("setHeating -> ", lvl.c_str(), "ON"); } else if (params[lvl] == "OFF") { dev.io.digitalOutWrite(ro, false); response["values"][lvl] = "OFF"; LOG_INFO("setHeating -> ", lvl.c_str(), "OFF"); } else { response["values"][lvl] = "invalid"; LOG_ERROR("setHeating invalid valve state"); } } return response; } void resetZone(TimerHandle_t th) { devices_t *dev = (devices_t *)pvTimerGetTimerID(th); const char *timerName = pcTimerGetName(th); LOG_INFO("setIrrigation shutdown zone [", timerName, "]"); if (!c_irrigationValveMap.contains(timerName)) { LOG_ERROR("Irrigation timer name invalid"); return; } dev->io.digitalOutWrite(c_irrigationValveMap.at(timerName), false); 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::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; auto &conf = Config::getInstance(); response["cmd"] = "setIrrigation"; if (params.isNull()) { LOG_ERROR("setIrrigation incorrect paramaters"); return response; } const std::string zone(params["zone"].as()); const uint16_t tOn(params["timeOn"].as()); const uint16_t tPause(params["timePause"].as()); response["values"]["zone"] = zone; if (zone == "stop") { // stop all zones and reset timers LOG_INFO("setIrrigation stop all zones"); for (auto &h : c_irrigationTimerMap) { const auto zoneName = h.first; auto &timerHandle = h.second.second; // get the timer handle if (timerHandle) // if handle is not null (not from a deleted timer) { if (xTimerIsTimerActive(timerHandle)) // stop the timer if active { LOG_INFO("setIrrigation stopping timer", zoneName.c_str()); xTimerStop(timerHandle, 0); xTimerDelete(timerHandle, pdMS_TO_TICKS(10)); // delete it timerHandle = NULL; } } LOG_INFO("setIrrigation closing", zoneName.c_str()); dev.io.digitalOutWrite(c_irrigationValveMap.at(zoneName), false); // shuto down the valve } if (s_irrigationPumpTimer) { xTimerChangePeriod(s_irrigationPumpTimer, pdMS_TO_TICKS(30 * 1000), 0); // shutdown the pump in 30s after the stop xTimerReset(s_irrigationPumpTimer, 0); } response["values"]["status"] = "stop"; 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 { LOG_ERROR("setIrrigation incorrect zone[", zone.c_str(), "] or time values tOn[", tOn, "] tPause[", tPause, "]"); response["values"]["status"] = "invalid"; return response; } // verify if timer was already started, zone is already on const auto timerName = c_irrigationTimerMap.at(zone).first; const auto zoneIoNumber = c_irrigationValveMap.at(zone); auto &timerHandle = c_irrigationTimerMap.at(zone).second; if (timerHandle) { // this timer was alteady started, ignore command LOG_WARN("setIrrigation zone [", timerName, "] already started"); response["values"]["status"] = "conflict"; return response; } const uint32_t pumpTime((tOn + 30) * 1000); const uint32_t zoneTime(tOn * 1000); if (!s_irrigationPumpTimer) // Pump has not yet started { s_irrigationPumpTimer = xTimerCreate("pumpTimer", pdMS_TO_TICKS(pumpTime), false, (void *)&dev, resetWaterPump); dev.io.digitalOutWrite(RO::PUMP_IRR, true); xTimerStart(s_irrigationPumpTimer, 0); // immediate start pump timer LOG_INFO("setIrrigation pump time", pumpTime); } else { const auto currentRemaining(xTimerGetExpiryTime(s_irrigationPumpTimer) - xTaskGetTickCount()); const auto newRemaining(pumpTime); const auto newPeriod(std::max(newRemaining, currentRemaining)); xTimerChangePeriod(s_irrigationPumpTimer, newPeriod, 0); // set new period based on timing of new zone xTimerReset(s_irrigationPumpTimer, 0); // if timer was already started, restart LOG_INFO("setIrrigation pump time reset", newRemaining); } TimerHandle_t shTimer(xTimerCreate(timerName, pdMS_TO_TICKS(zoneTime), false, (void *)&dev, resetZone)); if (shTimer) { dev.io.digitalOutWrite(zoneIoNumber, true); xTimerStart(shTimer, 0); timerHandle = shTimer; response["values"]["status"] = "valid"; LOG_INFO("setIrrigation zone [", timerName, "] tOn[", tOn, "] tPause[", tPause, "]"); } return response; } const ArduinoJson::JsonDocument Commands::setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "setTimeNTP"; auto ð = 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().c_str(), "]"); return response; } // SETTERS // // SETTERS // // GETTERS // // GETTERS // const ArduinoJson::JsonDocument Commands::getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "getHPpower"; const auto pinfo = dev.seneca.getAll(); auto values = response["values"].to(); values["power"] = pinfo.pAct; values["current"] = pinfo.a; values["voltage"] = pinfo.v; values["energy"] = pinfo.whPar; 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; response["cmd"] = "getInputStatus"; const std::vector 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) { const std::string k("DI" + std::to_string(i)); response["values"][k.c_str()] = 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; response["cmd"] = "getOutputStatus"; const std::vector 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) { const std::string k("DO" + std::to_string(i)); response["values"][k.c_str()] = 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"); 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::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "getTimeDrift"; auto ð = 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(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 // }