From 633fb6b368297596153552ab2aa83f7bd0ac2f1f Mon Sep 17 00:00:00 2001
From: Jochen Vothknecht <jochen3120@gmail.com>
Date: Tue, 4 Jan 2022 08:38:12 +0100
Subject: [PATCH] Begin metrics development (untested)

---
 10W/TransportProtection.scad        |  25 +++--
 CLC-qthing/CMakeLists.txt           |   2 +
 CLC-qthing/CyanLight.hpp            |   1 -
 CLC-qthing/SiliconTorch/FxCyanF.cpp |  14 ++-
 CLC-qthing/SiliconTorch/Metrics.cpp | 151 ++++++++++++++++++++++++++++
 CLC-qthing/SiliconTorch/Metrics.hpp |  82 +++++++++++++++
 CLC-qthing/device_config.h          |   2 +-
 CLC-qthing/device_main.cpp          |  34 ++++---
 8 files changed, 284 insertions(+), 27 deletions(-)
 create mode 100644 CLC-qthing/SiliconTorch/Metrics.cpp
 create mode 100644 CLC-qthing/SiliconTorch/Metrics.hpp

diff --git a/10W/TransportProtection.scad b/10W/TransportProtection.scad
index 96b1990..69e87ca 100644
--- a/10W/TransportProtection.scad
+++ b/10W/TransportProtection.scad
@@ -1,15 +1,19 @@
 
+use <../WebWeaver/scad/Util.scad>;
+
+
 $fs = 0.5;
 $fa = 1.0;
 
 
 
-pcb_w   = 40.5;
-pcb_h   = 67.5;
-pcb_r   =    5;  // edge radius
-pcb_off =  0.4;
-pcb_t   =    5;  // thickness including LED and inductor
-
+pcb_w      = 40.5;
+pcb_h      = 67.5;
+pcb_r      =    5;  // edge radius
+pcb_off    =  0.4;
+pcb_t      =    5;  // thickness including LED and inductor
+pcb_conn_w =   20;
+pcb_conn_h =    3;
 
 
 module pcb(w=pcb_w, h=pcb_h, r=pcb_r, off=pcb_off) {
@@ -18,7 +22,14 @@ module pcb(w=pcb_w, h=pcb_h, r=pcb_r, off=pcb_off) {
   _h = h + 2*off;
   _r = r +   off;
 
-  hull() for (tx=[_r, _w-_r], ty=[_r, _h-_r]) translate([tx, ty, 0]) circle(r=r);
+  hull() {
+
+    // function of WebWeaver
+    roundRect(_w, _h, _r, true, true);
+  
+    translate([0, -_h/2 +1 -pcb_conn_h, 0]) square([pcb_conn_w, pcb_conn_h + 1], center=true);
+  
+  }
 }
 
 module pcbBox(wi, hi, ri, _str) {
diff --git a/CLC-qthing/CMakeLists.txt b/CLC-qthing/CMakeLists.txt
index 8b29cc8..bc7bdbc 100644
--- a/CLC-qthing/CMakeLists.txt
+++ b/CLC-qthing/CMakeLists.txt
@@ -8,4 +8,6 @@ idf_component_register(
   REQUIRES
   main
 )
+
 component_compile_options(-std=c++17)
+component_compile_options(-Wno-reorder)
diff --git a/CLC-qthing/CyanLight.hpp b/CLC-qthing/CyanLight.hpp
index 300ce77..5f40a0f 100644
--- a/CLC-qthing/CyanLight.hpp
+++ b/CLC-qthing/CyanLight.hpp
@@ -1,7 +1,6 @@
 #pragma once
 
 // TODO: should we include <cinttypes> here or accepts its ingress via FxCyanF.hpp ???
-
 #include "SiliconTorch/FxCyanF.hpp"
 
 
diff --git a/CLC-qthing/SiliconTorch/FxCyanF.cpp b/CLC-qthing/SiliconTorch/FxCyanF.cpp
index d212edf..806ee40 100644
--- a/CLC-qthing/SiliconTorch/FxCyanF.cpp
+++ b/CLC-qthing/SiliconTorch/FxCyanF.cpp
@@ -1,5 +1,9 @@
 #include "FxCyanF.hpp"
 
+#include "esp_err.h"
+#include "esp_log.h"
+#include "driver/ledc.h"
+
 #include <qthing>
 #include <qthing/mqtt_common.hpp>
 
@@ -9,9 +13,7 @@
 #include <algorithm>
 #include <functional>
 
-#include "esp_err.h"
-#include "esp_log.h"
-#include "driver/ledc.h"
+#include "Metrics.hpp"
 
 
 using namespace qthing;
@@ -33,7 +35,11 @@ static float bytes2float(const char *bytes) {
 }
 
 
-SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : baseChannel(baseChannel) {
+SiliconTorch::FxCyanF::FxCyanF(uint32_t baseChannel) : metrics(Metrics::Metrics("fxCyan", 60*60)), baseChannel(baseChannel) {
+
+  metrics.registerMetric("frames", "FrameCounter");
+  metrics.registerMetric("errors", "ErrorCounter");
+
 
   // TODO: make vector!
   this->channels = new SiliconTorch::Impl::PWMChannel*[MAX_CHANNELS];
diff --git a/CLC-qthing/SiliconTorch/Metrics.cpp b/CLC-qthing/SiliconTorch/Metrics.cpp
new file mode 100644
index 0000000..12c2a42
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Metrics.cpp
@@ -0,0 +1,151 @@
+#include "Metrics.hpp"
+
+#include "freertos/FreeRTOS.h"
+// #include "freertos/semphr.h"
+#include "freertos/task.h"
+
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "esp_err.h"
+#include "esp_log.h"
+
+#include <ctime>
+#include <climits>
+#include <cinttypes>
+
+
+static const char* TAG = "Metrics";
+
+
+static uint8_t nextTask = 0;
+static void methodTaskWrapper(void *parameter) {
+  std::function<void()> *f = (std::function<void()> *)parameter;
+  (*f)();
+}
+
+
+namespace SiliconTorch {
+
+  namespace Metrics {
+
+    Metrics::Metrics(const std::string& nameSpace, uint32_t saveInterval) : nameSpace(nameSpace), saveInterval(saveInterval) {
+
+      nvs_flash_init();
+      nvs_open(nameSpace.c_str(), NVS_READWRITE, &this->nvs);
+
+
+
+      char taskName[64];
+      snprintf(taskName, sizeof(taskName), "Metrics[%i]", nextTask++);  // TODO: increment thread-safe!
+
+      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);
+
+    }
+
+
+    void Metrics::registerMetric(const std::string& shortName, const std::string& fullName) {
+
+      auto result = counters.find(shortName);
+      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);
+      }
+    }
+
+
+    void Metrics::saveData() {
+      // TODO: implementation!
+
+      lastSaved = std::time(NULL);
+    }
+
+    void Metrics::tickTask() {
+
+      while (true) {
+
+        std::time_t now = std::time(NULL);
+        if (now - lastSaved > saveInterval) saveData();
+
+        vTaskDelay((tickInterval * 1000) / portTICK_PERIOD_MS);
+      }
+    }
+
+
+    uint64_t getCounter(const std::string& shortName) {
+      return 0;
+    }
+
+
+    CounterMetric::CounterMetric(const std::string& shortName, const std::string& fullName, uint32_t saveInterval, uint32_t publishInterval) :
+      shortName(shortName), fullName(fullName), saveInterval(saveInterval), publishInterval(publishInterval) {
+
+      // TODO: implement!
+
+      // TODO: implement? what to do???
+    }
+
+
+    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);
+    }
+
+    // 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);
+
+      if (true) {
+      // if (err != ESP_OK) {  // !!!!!!
+        const char* errorName = esp_err_to_name(err);
+        ESP_LOGE(TAG, "Error[ %s ] saving Metric[ %s ]", errorName, fullName.c_str());
+      }
+    }
+
+
+    void CounterMetric::inc() {
+      if (value < ULLONG_MAX) {
+        dirty = true;
+        value++;
+      }
+    }
+    
+    void CounterMetric::dec() {
+      if (value > 0) {
+        dirty = true;
+        value--;
+      }
+    }
+
+    // pre-increment
+    uint64_t& CounterMetric::CounterMetric::operator++() {
+      inc(); return value;
+    }
+
+    // pre-decrement
+    uint64_t& CounterMetric::CounterMetric::operator--() {
+      dec(); return value;
+    }
+
+    // post-increment
+    uint64_t CounterMetric::CounterMetric::operator++(int ignored) {
+      uint64_t out = value; inc(); return out;
+    }
+
+    // post-decrement
+    uint64_t CounterMetric::CounterMetric::operator--(int ignored) {
+      uint64_t out = value; dec(); return out;
+    }
+
+  }
+}
diff --git a/CLC-qthing/SiliconTorch/Metrics.hpp b/CLC-qthing/SiliconTorch/Metrics.hpp
new file mode 100644
index 0000000..b3b81a0
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Metrics.hpp
@@ -0,0 +1,82 @@
+#pragma once
+
+#include <map>
+#include <ctime>
+#include <string>
+#include <functional>
+
+#include "nvs.h"
+
+
+
+namespace SiliconTorch {
+
+  namespace Metrics {
+
+    typedef std::function<void()> Incrementer;
+
+    class CounterMetric {
+      public:
+        CounterMetric(const std::string& shortName, const std::string& fullName, uint32_t saveInterval, uint32_t publishInterval);
+
+        const std::string& fullName;
+        const std::string& shortName;
+
+        void inc();
+        void dec();
+
+        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
+
+        uint64_t& operator++();    // pre-increment
+        uint64_t& operator--();    // pre-decrement
+
+        uint64_t operator++(int);  // post-increment
+        uint64_t operator--(int);  // post-decrement
+
+      private:
+        uint64_t value = 0;
+
+        bool dirty = false;
+
+        uint32_t saveInterval;
+        uint32_t publishInterval;
+
+        std::time_t lastSaved;
+        std::time_t lastPublished;
+    };
+
+    class Metrics {
+      public:
+        Metrics(const std::string& nameSpace, uint32_t saveInterval = 60*60);
+
+        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
+
+        Incrementer generateMetricIncrementer(const std::string& shortName);
+
+
+        void saveData();
+
+
+        const std::string& nameSpace;
+
+      private:
+
+        uint32_t saveInterval;       // seconds
+        uint32_t tickInterval = 60;  // seconds; internal tick task interval
+
+        std::time_t lastSaved = std::time(NULL);      // unix timestamp
+
+        void tickTask();
+
+        std::map<std::string, CounterMetric*> counters;
+
+        nvs_handle_t nvs;
+
+    };
+
+  }
+}
diff --git a/CLC-qthing/device_config.h b/CLC-qthing/device_config.h
index b9bf347..41978b1 100644
--- a/CLC-qthing/device_config.h
+++ b/CLC-qthing/device_config.h
@@ -1,5 +1,5 @@
 // hostname and device namespace
-#define DEVICE_NAME "CyanLight"
+#define DEVICE_NAME "CLC"
 
 #define NTP_SERVER "pool.ntp.org"
 
diff --git a/CLC-qthing/device_main.cpp b/CLC-qthing/device_main.cpp
index fc83f73..755fce8 100644
--- a/CLC-qthing/device_main.cpp
+++ b/CLC-qthing/device_main.cpp
@@ -11,34 +11,40 @@
 
 
 
-CyanLight::CyanLightControl ctrl(1);
+qthing::Config cfg;
+
+CyanLight::CyanLightControl ctrl(3);
+
 
 
 void device_main() {
 
-  // ground the pin beneath our channel 0 IO
-  /* uint8_t GND = 23;
+  cfg.apply();
 
-  gpio_config_t conf;
+  ctrl.setFrqRes(100, 19);
 
-  conf.intr_type = GPIO_INTR_DISABLE;
-  conf.mode = GPIO_MODE_OUTPUT;
-  conf.pin_bit_mask = 1ULL << GND;
-  conf.pull_up_en = GPIO_PULLUP_DISABLE;
-  conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
 
-  gpio_config(&conf);
-  gpio_set_level((gpio_num_t)GND, 0); */
+  for (uint8_t i = 0; i < 100; i++) {
 
+    bool _f = i % 2 == 0;
 
-  qthing::Config cfg;
-  cfg.apply();
+    ctrl.setPWM(0,  _f      * 1.0f);
+    ctrl.setPWM(2, (_f ^ 1) * 1.0f);
 
+    vTaskDelay(50 / portTICK_PERIOD_MS);
+  }
 
-  qthing::enable_wifi();
 
+  ctrl.setPWM(0, 0.0f);
+  ctrl.setPWM(1, 0.0f);
+  ctrl.setPWM(2, 1.0f);
+
+
+  qthing::enable_wifi();
   return;
 
+
+
   auto delay = 10 / portTICK_PERIOD_MS;
   float limit = 50.0f;
 
-- 
GitLab