diff --git a/CLC-qthing/SiliconTorch/FxCyanF.cpp b/CLC-qthing/SiliconTorch/FxCyanF.cpp index 4730d174efaff642b47e6187c88bfe13a9a4523e..9805186b34b248d9115a85ce2b606dac6f861298 100644 --- a/CLC-qthing/SiliconTorch/FxCyanF.cpp +++ b/CLC-qthing/SiliconTorch/FxCyanF.cpp @@ -3,6 +3,9 @@ #include "esp_err.h" #include "esp_log.h" #include "driver/ledc.h" +#include "tcpip_adapter.h" + +#include <nlohmann/json.hpp> #include <qthing> #include <qthing/mqtt_common.hpp> @@ -17,6 +20,7 @@ using namespace qthing; +using json = nlohmann::json; const char* SiliconTorch::TAG = "fxCyan"; // TODO: maybe suffix with exact protocol…? @@ -35,10 +39,13 @@ static float bytes2float(const char *bytes) { } -SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : metrics(Metrics::Metrics("fxCyan", 60*60)), baseChannel(baseChannel) { +SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : metrics(Metrics::Metrics("fxCyan")), baseChannel(baseChannel) { + + this->metrics.registerMetric("frames", "frameCounter"); + this->metrics.registerMetric("errors", "errorCounter"); - metrics.registerMetric("frames", "FrameCounter"); - metrics.registerMetric("errors", "ErrorCounter"); + this->frameCntInc = this->metrics.generateMetricIncrementer("frames"); + this->errorCntInc = this->metrics.generateMetricIncrementer("errors"); // TODO: make vector! @@ -127,6 +134,10 @@ SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : metrics(Metrics::Metrics( this->publishFrqRes(); }; + std::function<void(const std::string&)> getListener = [&](const std::string& ignored) { + this->publishListenerInfo(); + }; + // device-local setters add_message_callback(this->genDeviceTopic("frqres/set"), setFrqRes); @@ -153,6 +164,11 @@ SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : metrics(Metrics::Metrics( add_message_callback(this->genServiceTopic("frqres/get"), getFrqRes); add_message_callback(this->genServiceTopic("channel/get"), getBCh); add_message_callback(this->genServiceTopic("channelCnt/get"), getChs); + + // Listener info getters and auto-publish + add_message_callback(this->genDeviceTopic("listener/get"), getListener); + add_message_callback(this->genServiceTopic("listener/get"), getListener); + add_mqtt_connected_callback(std::bind(&SiliconTorch::FxCyanF::publishListenerInfo, this)); } @@ -186,6 +202,9 @@ bool SiliconTorch::FxCyanF::handleUnicast(const char *data, std::size_t length) int32_t diff = length - size; if (diff < 0) { ESP_LOGE(TAG, "Invalid data length[ %i ]: Received ΔB = %i bytes too few", length, -diff); + + this->errorCntInc(); + return false; } @@ -194,6 +213,7 @@ bool SiliconTorch::FxCyanF::handleUnicast(const char *data, std::size_t length) this->setPWM(ch, f); } + this->frameCntInc(); this->callPacketCallback(); return true; @@ -206,6 +226,9 @@ bool SiliconTorch::FxCyanF::handleBroadcast(const char *data, std::size_t length int32_t diff = length - offset - size; if (diff < 0) { // TODO: test thoroughly! ESP_LOGE(TAG, "Invalid data length[ %i ]: Received ΔB = %i bytes too few", length, -diff); + + this->errorCntInc(); + return false; } @@ -214,6 +237,7 @@ bool SiliconTorch::FxCyanF::handleBroadcast(const char *data, std::size_t length this->setPWM(ch, f); } + this->frameCntInc(); this->callPacketCallback(); return true; @@ -342,3 +366,26 @@ void SiliconTorch::FxCyanF::publishFrqRes() { publish_message(this->genDeviceTopic("frqres"), tmp); } +void SiliconTorch::FxCyanF::publishListenerInfo() { + if (qthing::is_mqtt_connected()) { + + // TODO: get IP Address of ethernet adapter + + tcpip_adapter_ip_info_t ipInfo; + esp_err_t err; + err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + + if (err == ESP_OK) { + char ip[16]; + snprintf(ip, 16, "%d.%d.%d.%d", ipInfo.ip.addr & 0xFF, (ipInfo.ip.addr >> 8) & 0xFF, (ipInfo.ip.addr >> 16) & 0xFF, ipInfo.ip.addr >> 24); + + json j; + j["IPv4"] = ip; + j["port"] = "4213"; // TODO: get from qthing (currently not possible) + + publish_message(this->genDeviceTopic("listener"), j.dump().c_str()); + } else { + ESP_LOGW(TAG, "Can't determine IP"); + } + } +} diff --git a/CLC-qthing/SiliconTorch/FxCyanF.hpp b/CLC-qthing/SiliconTorch/FxCyanF.hpp index 5d410cca3f27d1f3b6c0c3337ee51e3f604378b4..d1f805bf89819a25de1a768253c62d0685ecf405 100644 --- a/CLC-qthing/SiliconTorch/FxCyanF.hpp +++ b/CLC-qthing/SiliconTorch/FxCyanF.hpp @@ -54,6 +54,7 @@ namespace SiliconTorch { void publishResolution(); void publishBaseChannel(); void publishChannelCount(); + void publishListenerInfo(); private: @@ -73,6 +74,8 @@ namespace SiliconTorch { void callPacketCallback(); Metrics::Metrics metrics; + Metrics::Incrementer frameCntInc; + Metrics::Incrementer errorCntInc; }; } diff --git a/CLC-qthing/SiliconTorch/Metrics.cpp b/CLC-qthing/SiliconTorch/Metrics.cpp index 12c2a4291b9e177a8f07abed8751df89c5916bc5..980c44d3724c7d90974eac81df68d87fd234a010 100644 --- a/CLC-qthing/SiliconTorch/Metrics.cpp +++ b/CLC-qthing/SiliconTorch/Metrics.cpp @@ -9,11 +9,16 @@ #include "esp_err.h" #include "esp_log.h" -#include <ctime> +#include <qthing> + #include <climits> #include <cinttypes> +using namespace qthing; +using json = nlohmann::json; + + static const char* TAG = "Metrics"; @@ -28,11 +33,17 @@ namespace SiliconTorch { namespace Metrics { - Metrics::Metrics(const std::string& nameSpace, uint32_t saveInterval) : nameSpace(nameSpace), saveInterval(saveInterval) { + uint32_t uptime_ms() { + return (uint32_t)(esp_timer_get_time() / 1000L); + } + + Metrics::Metrics(const std::string& nameSpace) : nameSpace(nameSpace) { - nvs_flash_init(); + nvs_flash_init(); // TODO: WTF does it crash???? nvs_open(nameSpace.c_str(), NVS_READWRITE, &this->nvs); + nvs_get_u64(this->nvs, "lifetime", &this->lifeTime); // read lifetime value from NVS + lifeTimeSaved = uptime_ms(); char taskName[64]; @@ -40,8 +51,16 @@ namespace SiliconTorch { std::function<void()> f = std::bind(&SiliconTorch::Metrics::Metrics::tickTask, this); std::function<void()> *_f = new std::function<void()>(f); - xTaskCreate(methodTaskWrapper, taskName, 8192, (void*)_f, 1, NULL); + xTaskCreatePinnedToCore(methodTaskWrapper, taskName, 8192, (void*)_f, 1, NULL, 0); + + std::function<void(const std::string&)> getMetrics = [&](const std::string& ignored) { + publishData(); + }; + + add_message_callback(genDeviceTopic("listener/get"), getMetrics); + add_message_callback(std::string("service/") + nameSpace + std::string("/listener/get"), getMetrics); + add_mqtt_connected_callback(std::bind(&SiliconTorch::Metrics::Metrics::publishData, this)); } @@ -51,36 +70,104 @@ namespace SiliconTorch { if (result != counters.end()) { ESP_LOGW(TAG, "Can't register Metric[ %s ]: Already exists", shortName.c_str()); } else { - counters[shortName] = new CounterMetric(shortName, fullName, 10, 10); + CounterMetric* counter = new CounterMetric(shortName, fullName); + + counter->load(&this->nvs); + counters[shortName] = counter; } } + void Metrics::publishData() { + json j; + + for (auto it = counters.begin(); it != counters.end(); ++it) { + CounterMetric* metric = it->second; + j[metric->fullName.c_str()] = metric->getValue(); + } + + j["uptime"] = uptime_ms() / 1000UL; + j["lifetime"] = (lifeTime + (uptime_ms() - lifeTimeSaved)) / 1000UL; + + publish_message(genDeviceTopic("metrics"), j.dump().c_str()); + lastPublished = uptime_ms(); + } void Metrics::saveData() { - // TODO: implementation! - lastSaved = std::time(NULL); + for (auto it = counters.begin(); it != counters.end(); ++it) + it->second->save(&this->nvs); + + + uint32_t now = uptime_ms(); + + lifeTime += now - lifeTimeSaved; + lifeTimeSaved = now; + + esp_err_t err = nvs_set_u64(this->nvs, "lifetime", this->lifeTime); + + if (true) { + // if (err != ESP_OK) { // !!!!!! + const char* errorName = esp_err_to_name(err); + ESP_LOGE(TAG, "Error[ %s ] saving LifeTimeMetric[ %lli ]", errorName, lifeTime); + } + + nvs_commit(this->nvs); + + lastSaved = uptime_ms(); } void Metrics::tickTask() { + TickType_t lastWakeTime = xTaskGetTickCount(); + while (true) { - std::time_t now = std::time(NULL); - if (now - lastSaved > saveInterval) saveData(); + uint32_t now = uptime_ms(); + + if ((now - lastSaved ) / 1000 > saveInterval ) saveData(); + if ((now - lastPublished) / 1000 > publishInterval) publishData(); + + ESP_LOGW(TAG, "TickTack… now = %i lastSaved = %i lastPublished = %i", now, lastSaved, lastPublished); - vTaskDelay((tickInterval * 1000) / portTICK_PERIOD_MS); + vTaskDelayUntil(&lastWakeTime, tickPeriod / portTICK_PERIOD_MS); } } - uint64_t getCounter(const std::string& shortName) { - return 0; + uint64_t Metrics::getCounterValue(const std::string& shortName) { + + auto result = counters.find(shortName); + + if (result != counters.end()) { + return result->second->getValue(); + } else if (shortName.compare("uptime") == 0) { + return uptime_ms() / 1000UL; + } else if (shortName.compare("lifetime") == 0) { + return (lifeTime + (uptime_ms() - lifeTimeSaved)) / 1000UL; + } else { + return 0; // TODO: log warning…? + } + } + + std::string Metrics::genDeviceTopic(const char *suffix) { + return std::string(DEVICE_NAMESPACE) + nameSpace + std::string("/") + std::string(suffix); } + Incrementer Metrics::generateMetricIncrementer(const std::string& shortName) { - CounterMetric::CounterMetric(const std::string& shortName, const std::string& fullName, uint32_t saveInterval, uint32_t publishInterval) : - shortName(shortName), fullName(fullName), saveInterval(saveInterval), publishInterval(publishInterval) { + auto result = counters.find(shortName); + + if (result != counters.end()) { + CounterMetric* counter = result->second; + + return [counter]() { counter->inc(); }; + } else { + return [](){}; // TODO: log warning…? + } + } + + + CounterMetric::CounterMetric(const std::string& shortName, const std::string& fullName) : shortName(shortName), fullName(fullName) { // TODO: implement! @@ -89,18 +176,15 @@ namespace SiliconTorch { void CounterMetric::load(nvs_handle_t* nvs) { - lastSaved = std::time(NULL); - nvs_get_u64(*nvs, shortName.c_str(), &this->value); } void CounterMetric::save(nvs_handle_t* nvs) { - if (std::time(NULL) - lastSaved > saveInterval) forceSave(nvs); + if (dirty) forceSave(nvs); } // TODO: synchronize! void CounterMetric::forceSave(nvs_handle_t* nvs) { - lastSaved = std::time(NULL); dirty = false; esp_err_t err = nvs_set_u64(*nvs, shortName.c_str(), this->value); @@ -112,6 +196,10 @@ namespace SiliconTorch { } } + uint64_t CounterMetric::getValue() { + return value; + } + void CounterMetric::inc() { if (value < ULLONG_MAX) { diff --git a/CLC-qthing/SiliconTorch/Metrics.hpp b/CLC-qthing/SiliconTorch/Metrics.hpp index b3b81a0d532816c02a312168f11ac47f64da9ca3..c5066e6a6424362c0d358b97a0fde1fcb3264c2d 100644 --- a/CLC-qthing/SiliconTorch/Metrics.hpp +++ b/CLC-qthing/SiliconTorch/Metrics.hpp @@ -15,17 +15,23 @@ namespace SiliconTorch { typedef std::function<void()> Incrementer; + uint32_t uptime_ms(); // rolls over after about 136 years + class CounterMetric { public: - CounterMetric(const std::string& shortName, const std::string& fullName, uint32_t saveInterval, uint32_t publishInterval); + CounterMetric(const std::string& shortName, const std::string& fullName); + + const std::string fullName; + const std::string shortName; + + bool dirty = false; - const std::string& fullName; - const std::string& shortName; + uint64_t getValue(); void inc(); void dec(); - void load(nvs_handle_t* nvs); // load value from NVS + void load(nvs_handle_t* nvs); // load value from NVS void save(nvs_handle_t* nvs); // saves only if enoughg time elapsed void forceSave(nvs_handle_t* nvs); // saves value immediately @@ -36,39 +42,44 @@ namespace SiliconTorch { uint64_t operator--(int); // post-decrement private: - uint64_t value = 0; - bool dirty = false; - - uint32_t saveInterval; - uint32_t publishInterval; + uint64_t value = 0; - std::time_t lastSaved; - std::time_t lastPublished; }; class Metrics { public: - Metrics(const std::string& nameSpace, uint32_t saveInterval = 60*60); + Metrics(const std::string& nameSpace); void registerMetric(const std::string& shortName, const std::string& fullName); - uint64_t getCounter(const std::string& shortName); // returns the actual value or 0 for unknown counters + /* + * returns the actual value or 0 for unknown counters + * also supports getting the special uptime and lifetime metrics + */ + uint64_t getCounterValue(const std::string& shortName); Incrementer generateMetricIncrementer(const std::string& shortName); - void saveData(); + void publishData(); + const std::string nameSpace; - const std::string& nameSpace; + std::string genDeviceTopic(const char *suffix); private: - uint32_t saveInterval; // seconds - uint32_t tickInterval = 60; // seconds; internal tick task interval + uint32_t saveInterval = 60; // *60; // seconds + uint32_t publishInterval = 15; // *60; // seconds + + uint32_t lastSaved = uptime_ms(); // unix timestamp + uint32_t lastPublished = uptime_ms(); // unix timestamp + + uint32_t tickPeriod = 3000; // milli seconds - std::time_t lastSaved = std::time(NULL); // unix timestamp + uint64_t lifeTime = 0; // milli seconds + uint64_t lifeTimeSaved = 0; // milli seconds void tickTask(); diff --git a/CLC-qthing/device_main.cpp b/CLC-qthing/device_main.cpp index 755fce820f58c9203fd91d2f04c32a6271aa24d3..7ff3f2e3fabe28dfea97940acf8efc03929edc48 100644 --- a/CLC-qthing/device_main.cpp +++ b/CLC-qthing/device_main.cpp @@ -13,7 +13,7 @@ qthing::Config cfg; -CyanLight::CyanLightControl ctrl(3); +CyanLight::CyanLightControl* ctrl; @@ -21,23 +21,24 @@ void device_main() { cfg.apply(); - ctrl.setFrqRes(100, 19); + ctrl = new CyanLight::CyanLightControl(3); + ctrl->setFrqRes(100, 19); for (uint8_t i = 0; i < 100; i++) { bool _f = i % 2 == 0; - ctrl.setPWM(0, _f * 1.0f); - ctrl.setPWM(2, (_f ^ 1) * 1.0f); + ctrl->setPWM(0, _f * 1.0f); + ctrl->setPWM(2, (_f ^ 1) * 1.0f); vTaskDelay(50 / portTICK_PERIOD_MS); } - ctrl.setPWM(0, 0.0f); - ctrl.setPWM(1, 0.0f); - ctrl.setPWM(2, 1.0f); + ctrl->setPWM(0, 0.0f); + ctrl->setPWM(1, 0.0f); + ctrl->setPWM(2, 1.0f); qthing::enable_wifi(); @@ -51,13 +52,13 @@ void device_main() { while (true) { for (uint8_t i = 0; i < limit; i++) { float pwm = i / limit; - ctrl.setPWM(0, pwm*pwm); + ctrl->setPWM(0, pwm*pwm); vTaskDelay(delay); } for (uint8_t i = 0; i < limit; i++) { float pwm = i / limit; - ctrl.setPWM(0, 1.0f - pwm*pwm); + ctrl->setPWM(0, 1.0f - pwm*pwm); vTaskDelay(delay); } }