Răsfoiți Sursa

Trim app and recovery binaries

Sebastien L 1 an în urmă
părinte
comite
484d8c54a8
34 a modificat fișierele cu 1617 adăugiri și 789 ștergeri
  1. 124 0
      components/metrics/Batch.cpp
  2. 46 0
      components/metrics/Batch.h
  3. 5 0
      components/metrics/CMakeLists.txt
  4. 98 0
      components/metrics/Events.cpp
  5. 53 0
      components/metrics/Events.h
  6. 148 0
      components/metrics/Metrics.cpp
  7. 18 0
      components/metrics/Metrics.h
  8. 163 0
      components/metrics/http_handlers.c
  9. 11 0
      components/metrics/http_handlers.h
  10. 4 4
      components/platform_config/nvs_utilities.h
  11. 45 2
      components/platform_config/platform_config.c
  12. 26 17
      components/platform_config/platform_config.h
  13. 1 1
      components/platform_console/CMakeLists.txt
  14. 1 1
      components/platform_console/app_recovery/CMakeLists.txt
  15. 9 3
      components/platform_console/app_recovery/recovery.c
  16. 16 3
      components/platform_console/app_squeezelite/cmd_squeezelite.c
  17. 105 73
      components/platform_console/cmd_config.c
  18. 498 500
      components/platform_console/cmd_i2ctools.c
  19. 1 0
      components/platform_console/cmd_nvs.c
  20. 1 1
      components/platform_console/cmd_ota.c
  21. 70 78
      components/platform_console/cmd_system.c
  22. 1 1
      components/platform_console/cmd_system.h
  23. 6 28
      components/platform_console/cmd_wifi.c
  24. 13 5
      components/platform_console/platform_console.c
  25. 1 1
      components/services/CMakeLists.txt
  26. 11 2
      components/services/accessors.c
  27. 2 1
      components/services/accessors.h
  28. 1 1
      components/tools/CMakeLists.txt
  29. 21 1
      components/tools/tools.c
  30. 7 0
      components/tools/tools.h
  31. 1 1
      components/wifi-manager/http_server_handlers.c
  32. 1 1
      main/CMakeLists.txt
  33. 10 0
      main/Kconfig.projbuild
  34. 99 64
      main/esp_app_main.c

+ 124 - 0
components/metrics/Batch.cpp

@@ -0,0 +1,124 @@
+#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
+#include "Batch.h"
+#include "esp_event.h"
+#include "esp_http_client.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+#include "esp_ota_ops.h"
+#include "esp_tls.h"
+#include "nvs_flash.h"
+#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
+#include "esp_crt_bundle.h"
+#endif
+#include "esp_system.h"
+#include "http_handlers.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "nvs_utilities.h"
+#include "tools.h"
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <sys/param.h>
+#if CONFIG_WITH_METRICS
+static const char* const TAG = "MetricsBatch";
+static const char* const feature_evt_name = "$feature_flag_called";
+static const char* const feature_flag_name = "$feature_flag";
+static const char* const feature_flag_response_name = "$feature_flag_response";
+
+namespace Metrics {
+
+Event& Batch::add_feature_event() { return add_event(feature_evt_name); }
+void Batch::add_remove_feature_event(const char* name, bool active) {
+    if (!active) {
+        remove_feature_event(name);
+    } else {
+        add_event(feature_evt_name).add_property(feature_flag_name, name);
+    }
+}
+Event& Batch::add_feature_variant_event(const char* const name, const char* const value) {
+    return add_event(feature_evt_name)
+        .add_property(feature_flag_name, name)
+        .add_property(feature_flag_response_name, value);
+}
+void Batch::remove_feature_event(const char* name) {
+    for (Metrics::Event& e : _events) {
+        if (strcmp(e.get_name(), feature_evt_name) == 0) {
+            e.remove_property(feature_flag_name, name);
+            return;
+        }
+    }
+}
+cJSON* Batch::to_json() {
+    cJSON* batch_json = cJSON_CreateArray();
+    for (Metrics::Event& e : _events) {
+        cJSON_AddItemToArray(batch_json, e.to_json(_metrics_uid.c_str()));
+    }
+    cJSON* message = cJSON_CreateObject();
+    cJSON_AddItemToObject(message, "batch", batch_json);
+    cJSON_AddStringToObject(message, "api_key", _api_key);
+    return batch_json;
+}
+char* Batch::to_json_str() {
+    cJSON* json = to_json();
+    char* json_str = cJSON_PrintUnformatted(json);
+    cJSON_Delete(json);
+    return json_str;
+}
+
+void Batch::push() {
+    int status_code = 0;
+    if (_metrics_uid.empty() && !_warned) {
+        ESP_LOGW(TAG, "Metrics disabled; no CID found");
+        _warned = true;
+        return;
+    }
+
+    char* json_str = to_json_str();
+    ESP_LOGV(TAG, "Metrics payload: %s", json_str);
+    time_t start_time = millis();
+
+    status_code = metrics_http_post_request(json_str, _url);
+
+    if (status_code == 200 || status_code == 204) {
+        _events.clear();
+    }
+    FREE_AND_NULL(json_str)
+    ESP_LOGD(TAG, "Total duration for metrics call: %lu. ", millis() - start_time);
+}
+
+void Batch::build_guid() {
+    uint8_t raw[16];
+    std::ostringstream oss;
+    esp_fill_random(raw, 16);
+    std::for_each(std::begin(raw), std::end(raw), [&oss](const uint8_t& byte) {
+        oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
+    });
+    _metrics_uid = oss.str();
+}
+void Batch::assign_id() {
+    size_t size = 0;
+    esp_err_t esp_err = ESP_OK;
+    _metrics_uid = std::string((char*)get_nvs_value_alloc_for_partition(
+        NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB, "cid", &size));
+    if (_metrics_uid[0] == 'G') {
+        ESP_LOGW(TAG, "Invalid ID. %s", _metrics_uid.c_str());
+        _metrics_uid.clear();
+    }
+    if (_metrics_uid.empty()) {
+        build_guid();
+        if (_metrics_uid.empty()) {
+            ESP_LOGE(TAG, "ID Failed");
+            return;
+        }
+        ESP_LOGW(TAG, "Metrics ID: %s", _metrics_uid.c_str());
+        esp_err = store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB,
+            "cid", _metrics_uid.c_str(), _metrics_uid.length() + 1);
+        if (esp_err != ESP_OK) {
+            ESP_LOGE(TAG, "Store ID failed: %s", esp_err_to_name(esp_err));
+        }
+    }
+}
+} // namespace Metrics
+#endif

+ 46 - 0
components/metrics/Batch.h

@@ -0,0 +1,46 @@
+#pragma once
+#include "Events.h"
+#include <string>
+#ifdef __cplusplus
+namespace Metrics {
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+
+class Batch {
+  private:
+    std::list<Event> _events;
+    bool _warned = false;
+    std::string _metrics_uid = nullptr;
+    const char* _api_key = nullptr;
+    const char* _url = nullptr;
+    void build_guid();
+    void assign_id();
+
+  public:
+    Batch() = default;
+    void configure(const char* api_key, const char* url) {
+        _api_key = api_key;
+        _url = url;
+        assign_id();
+    }
+    Event& add_feature_event();
+    void add_remove_feature_event(const char* name, bool active);
+    Event& add_feature_variant_event(const char* const name, const char* const value);
+    Event& add_event(const char* name) {
+        _events.emplace_back(name);
+        return _events.back();
+    }
+
+    bool has_events() const { return !_events.empty(); }
+    void remove_feature_event(const char* name);
+    cJSON* to_json();
+    char* to_json_str();
+    void push();
+};
+}
+#endif
+#ifdef __cplusplus
+}
+#endif

+ 5 - 0
components/metrics/CMakeLists.txt

@@ -0,0 +1,5 @@
+idf_component_register(SRC_DIRS .
+						INCLUDE_DIRS .
+						REQUIRES json tools platform_config wifi-manager esp-tls platform_config
+						PRIV_REQUIRES esp32 freertos
+)

+ 98 - 0
components/metrics/Events.cpp

@@ -0,0 +1,98 @@
+#include "Events.h"
+#include <algorithm>
+#include "esp_app_format.h"
+#include "esp_ota_ops.h"
+#if CONFIG_WITH_METRICS
+static const char* const TAG = "MetricsEvent";
+namespace Metrics {
+Event& Event::add_property(const char* name, const char* value) {
+    ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
+    char* mutable_name = strdup_psram(name); // Cast away const-ness, be careful with this
+    auto elem = properties.find(mutable_name);
+    FREE_AND_NULL(mutable_name)
+    if (elem == properties.end()) {
+        ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
+        properties.insert({strdup_psram(name), strdup_psram(value)});
+    } else {
+        ESP_LOGV(TAG, "Replacing value for property %s. Old: %s New: %s, Event: %s",name,elem->second,value,name);
+        FREE_AND_NULL(elem->second)
+        elem->second = strdup_psram(value);
+    }
+    return *this;
+}
+
+bool Event::has_property_value(const char* name, const char* value) const {
+    ESP_LOGV(TAG, "Checking if event %s property %s has value %s",_name, name,value);
+    return std::any_of(properties.begin(), properties.end(),
+        [name, value](const std::pair<const char* const, char*>& kv) {
+            ESP_LOGV(TAG, "Found property %s=%s", name,value);
+            return strcmp(kv.first, name) == 0 && strcmp(kv.second, value) == 0;
+        });
+}
+
+void Event::remove_property(const char* name, const char* value) {
+    auto it = properties.begin();
+    ESP_LOGV(TAG, "Removing event %s property %s=%s",_name, name,value);
+    while (it != properties.end()) {
+        if (strcmp(it->first, name) == 0 && strcmp(it->second, value)) {
+            properties.erase(it);
+            return;
+        }
+    }
+    ESP_LOGV(TAG, "Property %s=%s not found.", name,value);
+}
+cJSON* Event::properties_to_json() {
+    ESP_LOGV(TAG, "Event %s properties to json.",_name);
+    const esp_app_desc_t* desc = esp_ota_get_app_description();
+#ifdef CONFIG_FW_PLATFORM_NAME
+    const char* platform = CONFIG_FW_PLATFORM_NAME;
+#else
+    const char* platform = desc->project_name;
+#endif
+    cJSON* prop_json = cJSON_CreateObject();
+    auto it = properties.begin();
+
+    while (it != properties.end()) {
+        cJSON_AddStringToObject(prop_json, it->first, it->second);
+        ++it;
+    }
+    cJSON_AddStringToObject(prop_json, "platform", platform);
+    cJSON_AddStringToObject(prop_json, "build", desc->version);
+    dump_json_content("User properties for event:", prop_json, ESP_LOG_VERBOSE);
+    return prop_json;
+}
+cJSON* Event::to_json(const char* distinct_id) {
+    // The target structure looks like this
+    //  {
+    //   "event": "batched_event_name_1",
+    //   "properties": {
+    //     "distinct_id": "user distinct id",
+    //     "account_type": "pro"
+    //   },
+    //   "timestamp": "[optional timestamp in ISO 8601 format]"
+    // }
+    ESP_LOGV(TAG,"Event %s to json",_name);
+
+    free_json();
+    _json = cJSON_CreateObject();
+    cJSON_AddStringToObject(_json, "name", _name);
+    cJSON_AddItemToObject(_json, "properties", properties_to_json());
+
+    char buf[26] = {};
+    strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&_time));
+    // this will work too, if your compiler doesn't support %F or %T:
+    // strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+    cJSON_AddStringToObject(_json, "timestamp", buf);
+    cJSON* prop_json = properties_to_json();
+    cJSON_AddStringToObject(prop_json, "distinct_id", distinct_id);
+    dump_json_content("Full Event:", _json, ESP_LOG_VERBOSE);
+    return _json;
+}
+void Event::free_json() { cJSON_Delete(_json); }
+void Event::update_time() {
+    if (_time == 0) {
+        _time = time(nullptr);
+    }
+}
+} // namespace Metrics
+#endif

+ 53 - 0
components/metrics/Events.h

@@ -0,0 +1,53 @@
+#pragma once
+
+#ifdef __cplusplus
+#include "esp_log.h"
+#include "tools.h"
+#include <cJSON.h>
+#include <ctime>
+#include <list>
+#include <map>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+
+namespace Metrics {
+struct StrCompare {
+    bool operator()(const char* a, const char* b) const { return strcmp(a, b) < 0; }
+};
+
+class Event {
+
+  public:
+    std::map<char*, char*, StrCompare> properties;
+    Event& add_property(const char* name, const char* value);
+    bool has_property_value(const char* name, const char* value) const;
+    void remove_property(const char* name, const char* value);
+    cJSON* properties_to_json();
+    cJSON* to_json(const char* distinct_id);
+    void free_json();
+    void update_time();
+    explicit Event(const char* name) {
+        _name = strdup_psram(name);
+        memset(&_time, 0x00, sizeof(_time));
+    }
+    const char* get_name() const { return _name; }
+    ~Event() {
+        FREE_AND_NULL(_name);
+
+        // Iterate through the map and free the elements
+        for (auto& kv : properties) {
+            free((void*)kv.first);
+            free(kv.second);
+        }
+        properties.clear(); // Clear the map after freeing memory
+        FREE_AND_NULL(_json);
+    }
+  private:
+    char* _name = nullptr;
+    time_t _time;
+    cJSON* _json = nullptr;    
+};
+
+} // namespace Metrics
+#endif

+ 148 - 0
components/metrics/Metrics.cpp

@@ -0,0 +1,148 @@
+#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
+#include "Metrics.h"
+#include "Batch.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+#include "esp_ota_ops.h"
+#include "esp_system.h"
+#include "esp_tls.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "nvs_flash.h"
+#include "tools.h"
+#include <cstdarg>
+#include <cstdio>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+
+#include "cJSON.h"
+#include "freertos/timers.h"
+#include "network_manager.h"
+#include "platform_config.h"
+
+static const char* TAG = "metrics";
+
+#if CONFIG_WITH_METRICS
+extern bool is_network_connected();
+#define METRICS_CLIENT_ID_LEN 50
+#define MAX_HTTP_RECV_BUFFER 512
+
+static bool metrics_usage_gen = false;
+static time_t metrics_usage_gen_time = 0;
+#ifndef METRICS_API_KEY
+    #pragma message "Metrics API key needs to be passed from the environment"
+    #define METRICS_API_KEY "ZZZ"
+#endif
+static const char* metrics_api_key = 
+static const char* parms_str = "params";
+static const char* properties_str = "properties";
+static const char* user_properties_str = "user_properties";
+static const char* items_str = "items";
+static const char* quantity_str = "quantity";
+static const char* metrics_url = "https://app.posthog.com";
+static TimerHandle_t timer;
+extern cJSON* get_cmd_list();
+Metrics::Batch batch;
+
+static void metrics_timer_cb(void* timer_id) {
+    if (batch.has_events()) {
+        if (!is_network_connected()) {
+            ESP_LOGV(TAG, "Network not connected. can't flush");
+        } else {
+            ESP_LOGV(TAG, "Pushing events");
+            batch.push();
+        }
+    }
+    if (millis() > metrics_usage_gen_time && !metrics_usage_gen) {
+        metrics_usage_gen = true;
+        ESP_LOGV(TAG, "Generate command list to pull features");
+        cJSON* cmdlist = get_cmd_list();
+        dump_json_content("generated cmd list", cmdlist, ESP_LOG_VERBOSE);
+        cJSON_Delete(cmdlist);
+    }
+}
+void metrics_init() {
+    ESP_LOGV(TAG, "Initializing metrics");
+    batch.configure(metrics_api_key, metrics_url);
+    if (!timer) {
+        ESP_LOGE(TAG, "Metrics Timer failure");
+    } else {
+        ESP_LOGV(TAG, "Starting timer");
+        xTimerStart(timer, portMAX_DELAY);
+    }
+    // set a 20 seconds delay before generating the
+    // features so the system has time to boot
+    metrics_usage_gen_time = millis() + 20000;
+}
+
+void metrics_event_playback(const char* source) {
+    ESP_LOGV(TAG, "Playback event: %s", source);
+    auto event = batch.add_event("play").add_property("source", source);
+}
+void metrics_event_boot(const char* partition) {
+    ESP_LOGV(TAG, "Boot event %s", partition);
+    auto event = batch.add_event("start");
+    event.add_property("partition", partition);
+}
+void metrics_add_feature_variant(const char* name, const char* format, ...) {
+    va_list args;
+    ESP_LOGV(TAG, "Feature %s", name);
+    va_start(args, format);
+
+    // Determine the required buffer size
+    int size = vsnprintf(nullptr, 0, format, args);
+    va_end(args); // Reset the va_list
+
+    // Allocate buffer and format the string
+    std::vector<char> buffer(size + 1); // +1 for the null-terminator
+    va_start(args, format);
+    vsnprintf(buffer.data(), buffer.size(), format, args);
+    va_end(args);
+
+    // Now buffer.data() contains the formatted string
+    batch.add_feature_variant_event(name, buffer.data());
+}
+void metrics_add_feature(const char* name, bool active) {
+    ESP_LOGV(TAG, "Adding feature %s: %s", name, active ? "ACTIVE" : "INACTIVE");
+    batch.add_remove_feature_event(name, active);
+}
+void metrics_event(const char* name) {
+    ESP_LOGV(TAG, "Adding Event %s", name);
+    batch.add_event(name);
+}
+#else
+static const char * not_enabled =  " - (metrics not enabled, this is just marking where the call happens)";
+void metrics_init(){
+#pragma message("Metrics disabled")
+    ESP_LOGD(TAG,"Metrics init%s",not_enabled);
+}
+void metrics_event_boot(const char* partition){
+    ESP_LOGD(TAG,"Metrics Event Boot from partition %s%s",partition,not_enabled);
+}
+void metrics_event(const char* name){
+    ESP_LOGD(TAG,"Metrics Event %s%s",name,not_enabled);
+}
+void metrics_add_feature(const char* name, bool active) {
+    ESP_LOGD(TAG,"Metrics add feature %s%s%s",name,active?"ACTIVE":"INACTIVE",not_enabled);
+}
+void metrics_add_feature_variant(const char* name, const char* format, ...){
+    va_list args;
+    ESP_LOGV(TAG, "Feature %s", name);
+    va_start(args, format);
+
+    // Determine the required buffer size
+    int size = vsnprintf(nullptr, 0, format, args);
+    va_end(args); // Reset the va_list
+
+    // Allocate buffer and format the string
+    std::vector<char> buffer(size + 1); // +1 for the null-terminator
+    va_start(args, format);
+    vsnprintf(buffer.data(), buffer.size(), format, args);
+    va_end(args);
+
+    ESP_LOGD(TAG,"Metrics add feature %s variant %s%s",name,buffer.data(),not_enabled);
+}
+#endif

+ 18 - 0
components/metrics/Metrics.h

@@ -0,0 +1,18 @@
+#pragma once
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void metrics_event_playback(const char* source);
+void metrics_event_boot(const char* partition);
+void metrics_event(const char* name);
+void metrics_add_feature(const char* name, bool active);
+void metrics_add_feature_variant(const char* name, const char* format, ...);
+void metrics_init();
+void metrics_flush();
+
+#ifdef __cplusplus
+}
+#endif

+ 163 - 0
components/metrics/http_handlers.c

@@ -0,0 +1,163 @@
+#include "http_handlers.h"
+#include "esp_http_client.h"
+#include "esp_log.h"
+#include "esp_tls.h"
+#include "tools.h"
+#include <sys/param.h>
+#if CONFIG_WITH_METRICS
+static const char* TAG = "metrics_http";
+static char* output_buffer; // Buffer to store response of http request from
+                            // event handler
+static int output_len = 0;  // Stores number of bytes read
+#define MAX_HTTP_OUTPUT_BUFFER 2048
+// Common function signature for event handlers
+typedef void (*HttpEventHandler)(esp_http_client_event_t* evt);
+
+static void handle_http_error(esp_http_client_event_t* evt) { ESP_LOGV(TAG, "ERROR"); }
+
+static void handle_http_connected(esp_http_client_event_t* evt) {
+    ESP_LOGV(TAG, "ON_CONNECTED");
+}
+
+static void handle_http_header_sent(esp_http_client_event_t* evt) {
+    ESP_LOGV(TAG, "HEADER_SENT");
+}
+
+static void handle_http_on_header(esp_http_client_event_t* evt) {
+    ESP_LOGV(TAG, "ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
+}
+
+static void handle_http_on_data(esp_http_client_event_t* evt) {
+    ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
+    ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
+    // Clean the buffer in case of a new request
+    if (output_len == 0 && evt->user_data) {
+        // we are just starting to copy the output data into the use
+        ESP_LOGV(TAG, "Resetting buffer");
+        memset(evt->user_data, 0, MAX_HTTP_OUTPUT_BUFFER);
+    }
+    /*
+     *  Check for chunked encoding is added as the URL for chunked encoding used in this example
+     * returns binary data. However, event handler can also be used in case chunked encoding is
+     * used.
+     */
+
+    // If user_data buffer is configured, copy the response into the buffer
+    int copy_len = 0;
+    if (evt->user_data) {
+        ESP_LOGV(TAG, "Not Chunked response, with user data");
+        // The last byte in evt->user_data is kept for the NULL character in
+        // case of out-of-bound access.
+        copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
+        if (copy_len) {
+            memcpy(evt->user_data + output_len, evt->data, copy_len);
+        }
+    } else {
+        int content_len = esp_http_client_get_content_length(evt->client);
+        if (esp_http_client_is_chunked_response(evt->client)) {
+            esp_http_client_get_chunk_length(evt->client, &content_len);
+        } 
+
+        if (output_buffer == NULL) {
+            // We initialize output_buffer with 0 because it is used by
+            // strlen() and similar functions therefore should be null
+            // terminated.
+            size_t len=(content_len + 1) * sizeof(char);
+            ESP_LOGV(TAG, "Init buffer %d",len);
+            output_buffer = (char*)malloc_init_external(len);
+            output_len = 0;
+            if (output_buffer == NULL) {
+                ESP_LOGE(TAG, "Buffer alloc failed.");
+                return;
+            }
+        }
+        copy_len = MIN(evt->data_len, (content_len - output_len));
+        if (copy_len) {
+            memcpy(output_buffer + output_len, evt->data, copy_len);
+        }
+    }
+    output_len += copy_len;
+}
+
+static void handle_http_on_finish(esp_http_client_event_t* evt) {
+    ESP_LOGD(TAG, "ON_FINISH");
+    if (output_buffer != NULL) {
+        ESP_LOGV(TAG, "Response: %s", output_buffer);
+        free(output_buffer);
+        output_buffer = NULL;
+    }
+    output_len = 0;
+}
+static void handle_http_disconnected(esp_http_client_event_t* evt) {
+    ESP_LOGI(TAG, "DISCONNECTED");
+    int mbedtls_err = 0;
+    esp_err_t err =
+        esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
+    if (err != 0) {
+        ESP_LOGI(TAG, "Last error : %s", esp_err_to_name(err));
+        ESP_LOGI(TAG, "Last mbedtls err 0x%x", mbedtls_err);
+    }
+    if (output_buffer != NULL) {
+        free(output_buffer);
+        output_buffer = NULL;
+    }
+    output_len = 0;
+}
+static const HttpEventHandler eventHandlers[] = {
+    handle_http_error,       // HTTP_EVENT_ERROR
+    handle_http_connected,   // HTTP_EVENT_ON_CONNECTED
+    handle_http_header_sent, // HTTP_EVENT_HEADERS_SENT
+    handle_http_header_sent, // HTTP_EVENT_HEADER_SENT (alias for HTTP_EVENT_HEADERS_SENT)
+    handle_http_on_header,   // HTTP_EVENT_ON_HEADER
+    handle_http_on_data,     // HTTP_EVENT_ON_DATA
+    handle_http_on_finish,   // HTTP_EVENT_ON_FINISH
+    handle_http_disconnected // HTTP_EVENT_DISCONNECTED
+};
+esp_err_t metrics_http_event_handler(esp_http_client_event_t* evt) {
+
+    if (evt->event_id < 0 || evt->event_id >= sizeof(eventHandlers) / sizeof(eventHandlers[0])) {
+        ESP_LOGE(TAG, "Invalid event ID: %d", evt->event_id);
+        return ESP_FAIL;
+    }
+
+    eventHandlers[evt->event_id](evt);
+
+    return ESP_OK;
+}
+int metrics_http_post_request(const char* payload, const char* url) {
+    int status_code = 0;
+    esp_http_client_config_t config = {.url = url,
+        .disable_auto_redirect = false,
+        .event_handler = metrics_http_event_handler,
+        .transport_type = HTTP_TRANSPORT_OVER_SSL,
+        .user_data = NULL, // local_response_buffer,        // Pass address of
+                           // local buffer to get response
+        .skip_cert_common_name_check = true
+
+    };
+    esp_http_client_handle_t client = esp_http_client_init(&config);
+    esp_err_t err = esp_http_client_set_method(client, HTTP_METHOD_POST);
+
+    if (err == ESP_OK) {
+        err = esp_http_client_set_header(client, "Content-Type", "application/json");
+    }
+    if (err == ESP_OK) {
+        ESP_LOGV(TAG, "Setting payload: %s", payload);
+        err = esp_http_client_set_post_field(client, payload, strlen(payload));
+    }
+    if (err == ESP_OK) {
+        err = esp_http_client_perform(client);
+    }
+    if (err == ESP_OK) {
+        status_code = esp_http_client_get_status_code(client);
+        ESP_LOGD(TAG, "metrics call Status = %d, content_length = %d",
+            esp_http_client_get_status_code(client), esp_http_client_get_content_length(client));
+
+    } else {
+        status_code = 500;
+        ESP_LOGW(TAG, "metrics call Status failed: %s", esp_err_to_name(err));
+    }
+    esp_http_client_cleanup(client);
+    return status_code;
+}
+#endif

+ 11 - 0
components/metrics/http_handlers.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int metrics_http_post_request(const char* payload, const char* url);
+
+#ifdef __cplusplus
+}
+#endif

+ 4 - 4
components/platform_config/nvs_utilities.h

@@ -13,14 +13,14 @@ esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, siz
 esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
 esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
 void * get_nvs_value_alloc(nvs_type_t type, const char *key);
-void * get_nvs_value_alloc_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, size_t * size);
-esp_err_t erase_nvs_for_partition(const char * partition, const char * ns,const char *key);
-esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, const void * data,size_t data_len);
+void * get_nvs_value_alloc_for_partition(const char * partition,const char * name_space,nvs_type_t type, const char *key, size_t * size);
+esp_err_t erase_nvs_for_partition(const char * partition, const char * name_space,const char *key);
+esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * name_space,nvs_type_t type, const char *key, const void * data,size_t data_len);
 esp_err_t erase_nvs(const char *key);
 void print_blob(const char *blob, size_t len);
 const char *type_to_str(nvs_type_t type);
 nvs_type_t str_to_type(const char *type);
-esp_err_t erase_nvs_partition(const char * partition, const char * ns);
+esp_err_t erase_nvs_partition(const char * partition, const char * name_space);
 void erase_settings_partition();
 #ifdef __cplusplus
 }

+ 45 - 2
components/platform_config/platform_config.c

@@ -634,7 +634,7 @@ cJSON * config_alloc_get_cjson(const char *key){
 	}
 	return conf_json;
 }
-esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
+esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson){
 	char * value_str = cJSON_PrintUnformatted(value);
 	if(value_str==NULL){
 		ESP_LOGE(TAG, "Unable to print cJSON for key [%s]", key);
@@ -642,9 +642,14 @@ esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
 	}
 	esp_err_t err = config_set_value(NVS_TYPE_STR,key, value_str);
 	free(value_str);
-	cJSON_Delete(value);
+	if(free_cjson){
+		cJSON_Delete(value);
+	}
 	return err;
 }
+esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
+	return config_set_cjson(key, value, true);
+}
 void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value){
 	char * str_value = config_alloc_get(NVS_TYPE_STR, key);
 	if(str_value == NULL){
@@ -786,6 +791,44 @@ cJSON* cjson_update_number(cJSON** root, const char* key, int value) {
 	}
     return *root;
 }
+bool config_parse_param_int(const char * config,const char * param, char delimiter,int * value){
+	const char *p;
+	if(!value){
+		return false;
+	}	
+	if ((p = strcasestr(config, param)) && (p = strchr(p, delimiter))) {
+		*value = atoi(p+1);
+		return true;
+	}
+	return false;
+}
+bool config_parse_param_float(const char * config,const char * param, char delimiter,double * value){
+	const  char *p;
+	if(!value){
+		return false;
+	}	
+	if ((p = strcasestr(config, param)) && (p = strchr(p, delimiter))) {
+		*value = atof(p+1);
+		return true;
+	}
+	return false;
+}
+bool config_parse_param_str(const char *source, const char *param, char delimiter, char *value, size_t value_size) {
+    char *p;
+    if ((p = strstr(source, param)) && (p = strchr(p, delimiter))) {
+        while (*++p == ' ');  // Skip spaces
+        // Read the value into the buffer, making sure not to overflow
+        snprintf(value, value_size, "%s", p);
+        char *end = strchr(value, ',');
+        if (end) {
+            *end = '\0';  // Null-terminate at the comma, if found
+        }
+        return true;
+    }
+    return false;
+}
+												
+
 IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8);
 IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8);
 IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16);

+ 26 - 17
components/platform_config/platform_config.h

@@ -8,25 +8,30 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
+#define PARSE_WITH_FUNC 1
+#ifdef PARSE_WITH_FUNC
+#define PARSE_PARAM(S,P,C,V) config_parse_param_int(S,P,C,(int*)&V)
+#define PARSE_PARAM_STR(S,P,C,V,I) config_parse_param_str(S,P,C,V,I)
+#define PARSE_PARAM_FLOAT(S,P,C,V) config_parse_param_float(S,P,C,&V)
+#else
+#define PARSE_PARAM(S,P,C,V)   do {		                      	    			\
+ 	char *__p;																	\
+ 	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); 	\
+ } while (0)
 
-#define PARSE_PARAM(S,P,C,V) do {												\
-	char *__p;																	\
-	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); 	\
-} while (0)
-
-#define PARSE_PARAM_FLOAT(S,P,C,V) do {												\
-	char *__p;																	\
-	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); 	\
-} while (0)
-
-#define PARSE_PARAM_STR(S,P,C,V,I) do {						\
-	char *__p;                                              \
-	if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) {	\
-		while (*++__p == ' ');								\
-		sscanf(__p,"%" #I "[^,]", V);						\
-	}   													\
-} while (0)
+#define PARSE_PARAM_FLOAT(S,P,C,V) do {  							            \
+ 	char *__p;																	\
+ 	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); 	\
+ } while (0)
 
+#define PARSE_PARAM_STR(S,P,C,V,I)  do {						                \
+ 	char *__p;                                                                  \
+ 	if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) {                       \
+ 		while (*++__p == ' ');							                        \
+ 		sscanf(__p,"%" #I "[^,]", V);					                        \
+ 	}   												                        \
+ } while (0)
+#endif
 #define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t  value);
 #define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t *  value);
 #ifndef FREE_RESET
@@ -50,10 +55,14 @@ bool config_has_changes();
 void config_commit_to_nvs();
 void config_start_timer();
 void config_init();
+bool config_parse_param_int(const char * config,const char * param,  char  delimiter,int * value);
+bool config_parse_param_float(const char * config,const char * param,  char  delimiter,double * value);
+bool config_parse_param_str(const char *source, const char *param, char delimiter, char *value, size_t value_size);
 void * config_alloc_get_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
 void * config_alloc_get_str(const char *key, char *lead, char *fallback);
 cJSON * config_alloc_get_cjson(const char *key);
 esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value);
+esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson);
 void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value);
 void config_delete_key(const char *key);
 void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);

+ 1 - 1
components/platform_console/CMakeLists.txt

@@ -8,7 +8,7 @@ idf_component_register( SRCS
 							cmd_config.c
 						INCLUDE_DIRS .   
 						REQUIRES nvs_flash 
-						PRIV_REQUIRES console app_update tools services spi_flash  platform_config vfs pthread wifi-manager platform_config newlib  telnet display squeezelite tools)
+						PRIV_REQUIRES console app_update tools services spi_flash  platform_config vfs pthread wifi-manager platform_config newlib  telnet display squeezelite tools metrics)
 
 set_source_files_properties(cmd_config.c
     PROPERTIES COMPILE_FLAGS

+ 1 - 1
components/platform_console/app_recovery/CMakeLists.txt

@@ -1,6 +1,6 @@
 idf_component_register( SRC_DIRS .
 						INCLUDE_DIRS .   
-						PRIV_REQUIRES bootloader_support
+						PRIV_REQUIRES bootloader_support json
  )
  
 target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=esp_app_desc")	

+ 9 - 3
components/platform_console/app_recovery/recovery.c

@@ -3,9 +3,10 @@
 #include "application_name.h"
 #include "esp_err.h"
 #include "esp_app_format.h"
-
+#include "cJSON.h"
+#include "stdbool.h"
 extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length);
-
+extern cJSON * gpio_list;
 const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
     .magic_word = ESP_APP_DESC_MAGIC_WORD,
     .version = PROJECT_VER,
@@ -26,7 +27,12 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
     .date = "",
 #endif
 };
-
+cJSON * get_gpio_list(bool refresh){
+    if(!gpio_list){
+         gpio_list = cJSON_CreateArray();
+    }
+    return gpio_list;
+}
 void register_optional_cmd(void) {
 }    
 

+ 16 - 3
components/platform_console/app_squeezelite/cmd_squeezelite.c

@@ -43,12 +43,25 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
 extern void register_audio_config(void);
 extern void register_rotary_config(void);
 extern void register_ledvu_config(void);
-
+extern void register_nvs();
+extern cJSON * get_gpio_list_handler(bool refresh);
 void register_optional_cmd(void) {
+#if CONFIG_WITH_CONFIG_UI	
     register_rotary_config();
-	register_ledvu_config();
+#endif
     register_audio_config();
-}    
+	register_ledvu_config();
+	register_nvs();
+
+}
+cJSON * get_gpio_list(bool refresh){
+#if CONFIG_WITH_CONFIG_UI		
+	return get_gpio_list_handler(refresh);
+#else
+	return cJSON_CreateArray();
+#endif
+}
+
 
 extern int squeezelite_main(int argc, char **argv);
 

+ 105 - 73
components/platform_console/cmd_config.c

@@ -20,7 +20,10 @@
 #include "tools.h"
 #include "cJSON.h"
 #include "cmd_i2ctools.h"
-
+#if defined(CONFIG_WITH_METRICS)
+#include "metrics.h"
+#endif
+#include "cmd_system.h"
 const char * desc_squeezelite ="Squeezelite Options";
 const char * desc_dac= "DAC Options";
 const char * desc_cspotc= "Spotify (cSpot) Options";
@@ -330,9 +333,8 @@ static int do_bt_source_cmd(int argc, char **argv){
 	char *buf = NULL;
 	size_t buf_size = 0;
 //	char value[100] ={0};
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -441,9 +443,8 @@ static int do_audio_cmd(int argc, char **argv){
 	int nerrors = arg_parse(argc, argv,(void **)&audio_args);
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -529,9 +530,8 @@ static int do_spdif_cmd(int argc, char **argv){
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -568,9 +568,8 @@ static int do_rotary_cmd(int argc, char **argv){
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -640,9 +639,8 @@ static int do_cspot_config(int argc, char **argv){
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
 		return 1;
 	}
 
@@ -699,9 +697,8 @@ static int do_ledvu_cmd(int argc, char **argv){
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -759,10 +756,8 @@ static int do_i2s_cmd(int argc, char **argv)
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		ESP_LOGE(TAG, "do_i2s_cmd: Failed to open memstream");
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -873,7 +868,9 @@ cJSON * i2s_cb(){
 	cJSON * values = cJSON_CreateObject();
 
 	const i2s_platform_config_t * i2s_conf= 	config_dac_get( );
-	
+#if defined(CONFIG_WITH_METRICS)
+	metrics_add_feature("i2s",i2s_conf->pin.data_out_num>=0);
+#endif	
 	if(i2s_conf->pin.bck_io_num>0 ) {
 		cJSON_AddNumberToObject(values,i2s_args.clock->hdr.longopts,i2s_conf->pin.bck_io_num);
 	}
@@ -910,6 +907,11 @@ cJSON * i2s_cb(){
 cJSON * spdif_cb(){
 	cJSON * values = cJSON_CreateObject();
 	const i2s_platform_config_t * spdif_conf= 	config_spdif_get( );
+	if(spdif_conf->pin.data_out_num>=0) {
+#if defined(CONFIG_WITH_METRICS)		
+		metrics_add_feature("spdif","enabled");
+#endif
+	}
 	if(spdif_conf->pin.bck_io_num>0 ) {
 		cJSON_AddNumberToObject(values,"clock",spdif_conf->pin.bck_io_num);
 	}
@@ -928,7 +930,9 @@ cJSON * rotary_cb(){
 	bool raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
 	free(p);
 	const rotary_struct_t *rotary= config_rotary_get();
-	
+#if defined(CONFIG_WITH_METRICS)	
+	metrics_add_feature("rotary",GPIO_IS_VALID_GPIO(rotary->A ));
+#endif
 	if(GPIO_IS_VALID_GPIO(rotary->A ) && rotary->A>=0 && GPIO_IS_VALID_GPIO(rotary->B) && rotary->B>=0){
 		cJSON_AddNumberToObject(values,rotary_args.A->hdr.longopts,rotary->A);
 		cJSON_AddNumberToObject(values,rotary_args.B->hdr.longopts,rotary->B);
@@ -947,7 +951,11 @@ cJSON * rotary_cb(){
 cJSON * ledvu_cb(){
 	cJSON * values = cJSON_CreateObject();
 	const ledvu_struct_t *ledvu= config_ledvu_get();
-	
+	if(GPIO_IS_VALID_GPIO(ledvu->gpio )){
+#if defined(CONFIG_WITH_METRICS)
+		metrics_add_feature("led_vu","enabled");
+#endif
+	}
 	if(GPIO_IS_VALID_GPIO(ledvu->gpio) && ledvu->gpio>=0 && ledvu->length > 0){
 		cJSON_AddNumberToObject(values,"gpio",ledvu->gpio);
 		cJSON_AddNumberToObject(values,"length",ledvu->length);
@@ -965,8 +973,14 @@ cJSON * audio_cb(){
 	cJSON * values = cJSON_CreateObject();
 	char * 	p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
     cJSON_AddStringToObject(values,"jack_behavior",(strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0)?"Headphones":"Subwoofer");
+#if defined(CONFIG_WITH_METRICS)
+	metrics_add_feature("jack_mute",atoi(p)>=0);
+#endif
     FREE_AND_NULL(p);
     p = config_alloc_get_default(NVS_TYPE_STR, "loudness", "0", 0);
+#if defined(CONFIG_WITH_METRICS)
+	metrics_add_feature("loudness",atoi(p)>=0);
+#endif
     cJSON_AddStringToObject(values,"loudness",p);
     FREE_AND_NULL(p);     
 	return values;
@@ -976,6 +990,9 @@ cJSON * bt_source_cb(){
 	char * 	p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_sink_name", NULL, 0);
 	if(p){
 		cJSON_AddStringToObject(values,"sink_name",p);
+#if defined(CONFIG_WITH_METRICS)
+		metrics_add_feature("btsource",strlen(p)>0);		
+#endif
 	}
 	FREE_AND_NULL(p);    
 	// p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_ctmt", NULL, 0);
@@ -1026,9 +1043,8 @@ static int do_squeezelite_cmd(int argc, char **argv)
 	int nerrors = arg_parse_msg(argc, argv,(struct arg_hdr ** )&squeezelite_args);
     char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	fprintf(f,"Not yet implemented!");
@@ -1047,59 +1063,62 @@ cJSON * squeezelite_cb(){
 	char *buf = NULL;
 	size_t buf_size = 0;
     int nerrors=1;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		log_send_messaging(MESSAGING_ERROR,"Unable to parse squeezelite parameters");
+		return values;
 	}
-	else {
 
-		if(nvs_config && strlen(nvs_config)>0){
-			ESP_LOGD(TAG,"Parsing command %s",nvs_config);
-			argv = (char **) calloc(22, sizeof(char *));
-			if (argv == NULL) {
-				FREE_AND_NULL(nvs_config);
-				fclose(f);
-				return values;
-			}
-			size_t argc = esp_console_split_argv(nvs_config, argv,22);
-			if (argc != 0) {
-				nerrors = arg_parse(argc, argv,(void **)&squeezelite_args);
-				ESP_LOGD(TAG,"Parsing completed");
-			}
+	if(nvs_config && strlen(nvs_config)>0){
+		ESP_LOGD(TAG,"Parsing command %s",nvs_config);
+		argv = (char **) calloc(22, sizeof(char *));
+		if (argv == NULL) {
+			FREE_AND_NULL(nvs_config);
+			fclose(f);
+			return values;
 		}
-		if (nerrors == 0) {
-			get_str_parm_json(squeezelite_args.buffers, values);
-			get_str_parm_json(squeezelite_args.codecs, values);
-			get_lit_parm_json(squeezelite_args.header_format, values);
-			get_str_parm_json(squeezelite_args.log_level, values);
-			
-			// get_str_parm_json(squeezelite_args.log_level_all, values);
-			// get_str_parm_json(squeezelite_args.log_level_decode, values);
-			// get_str_parm_json(squeezelite_args.log_level_output, values);
-			// get_str_parm_json(squeezelite_args.log_level_slimproto, values);
-			// get_str_parm_json(squeezelite_args.log_level_stream, values);
-			get_str_parm_json(squeezelite_args.mac_addr, values);
-			get_str_parm_json(squeezelite_args.output_device, values);
-			get_str_parm_json(squeezelite_args.model_name, values);
-			get_str_parm_json(squeezelite_args.name, values);
-			get_int_parm_json(squeezelite_args.rate, values);
-			get_str_parm_json(squeezelite_args.rates, values);
-			get_str_parm_json(squeezelite_args.server, values);
-			get_int_parm_json(squeezelite_args.timeout, values);
-			char * p = cJSON_Print(values);
-			ESP_LOGD(TAG,"%s",p);
-			free(p);
+		size_t argc = esp_console_split_argv(nvs_config, argv,22);
+		if (argc != 0) {
+			nerrors = arg_parse(argc, argv,(void **)&squeezelite_args);
+			ESP_LOGD(TAG,"Parsing completed");
 		}
-		else {
-			arg_print_errors(f, squeezelite_args.end, desc_squeezelite);
-		}
-		fflush (f);
-		if(strlen(buf)>0){
-			log_send_messaging(nerrors?MESSAGING_ERROR:MESSAGING_INFO,"%s", buf);
+	}
+	if (nerrors == 0) {
+		get_str_parm_json(squeezelite_args.buffers, values);
+		get_str_parm_json(squeezelite_args.codecs, values);
+		get_lit_parm_json(squeezelite_args.header_format, values);
+		get_str_parm_json(squeezelite_args.log_level, values);
+		
+		// get_str_parm_json(squeezelite_args.log_level_all, values);
+		// get_str_parm_json(squeezelite_args.log_level_decode, values);
+		// get_str_parm_json(squeezelite_args.log_level_output, values);
+		// get_str_parm_json(squeezelite_args.log_level_slimproto, values);
+		// get_str_parm_json(squeezelite_args.log_level_stream, values);
+		get_str_parm_json(squeezelite_args.mac_addr, values);
+		get_str_parm_json(squeezelite_args.output_device, values);
+#if defined(CONFIG_WITH_METRICS)
+		if(squeezelite_args.output_device->sval[0]!=NULL && strlen(squeezelite_args.output_device->sval[0])>0){
+			metrics_add_feature_variant("output",squeezelite_args.output_device->sval[0]);
 		}
-		fclose(f);
-		FREE_AND_NULL(buf);
+#endif
+		get_str_parm_json(squeezelite_args.model_name, values);
+		get_str_parm_json(squeezelite_args.name, values);
+		get_int_parm_json(squeezelite_args.rate, values);
+		get_str_parm_json(squeezelite_args.rates, values);
+		get_str_parm_json(squeezelite_args.server, values);
+		get_int_parm_json(squeezelite_args.timeout, values);
+		char * p = cJSON_Print(values);
+		ESP_LOGD(TAG,"%s",p);
+		free(p);
 	}
+	else {
+		arg_print_errors(f, squeezelite_args.end, desc_squeezelite);
+	}
+	fflush (f);
+	if(strlen(buf)>0){
+		log_send_messaging(nerrors?MESSAGING_ERROR:MESSAGING_INFO,"%s", buf);
+	}
+	fclose(f);
+	FREE_AND_NULL(buf);
 	FREE_AND_NULL(nvs_config);
 	FREE_AND_NULL(argv);
 	return values;
@@ -1212,9 +1231,8 @@ static int do_register_known_templates_config(int argc, char **argv){
 	char *buf = NULL;
 	size_t buf_size = 0;
 	cJSON * config_name =NULL;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
 		return 1;
 	}
 	if(nerrors >0){
@@ -1396,7 +1414,7 @@ void register_ledvu_config(void){
 
 void register_audio_config(void){
 	audio_args.jack_behavior = arg_str0("j", "jack_behavior","Headphones|Subwoofer","On supported DAC, determines the audio jack behavior. Selecting headphones will cause the external amp to be muted on insert, while selecting Subwoofer will keep the amp active all the time.");
-    audio_args.loudness = arg_int0("l", "loudness","0-10","Sets the loudness level, from 0 to 10. 0 will disable the loudness completely.");	
+    audio_args.loudness = arg_int0("l", "loudness","0-10","Sets a loudness level, from 0 to 10. 0 will disable the loudness completely. Note that LMS has priority over setting this value, so use it only when away from your server.");	
     audio_args.end = arg_end(6);
     audio_args.end = arg_end(6);
 	const esp_console_cmd_t cmd = {
@@ -1468,22 +1486,36 @@ static void register_squeezelite_config(void){
     cmd_to_json_with_cb(&cmd,&squeezelite_cb);
     ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
 }
-
+void dummy_register_cmd(){
+	
+}
 void register_config_cmd(void){
 	if(!is_dac_config_locked()){
-	 	 register_known_templates_config();
-	
+	 	 register_known_templates_config();	
 	}
+
 #ifdef CONFIG_CSPOT_SINK	
 	register_cspot_config();
 #endif	
 	register_bt_source_config();
+#if CONFIG_WITH_CONFIG_UI	
 	if(!is_dac_config_locked()){
 		register_i2s_config();
 	}
+	else {
+#if defined(CONFIG_WITH_METRICS)
+		metrics_add_feature("i2s",true);
+#endif
+	}
 	if(!is_spdif_config_locked()){
 		register_spdif_config();
 	}
+	else {
+#if defined(CONFIG_WITH_METRICS)
+		metrics_add_feature("spdif",true);
+#endif
+	}
+#endif
     register_optional_cmd();    
 }
 

Fișier diff suprimat deoarece este prea mare
+ 498 - 500
components/platform_console/cmd_i2ctools.c


+ 1 - 0
components/platform_console/cmd_nvs.c

@@ -589,6 +589,7 @@ void register_nvs()
            .func = &list_entries,
            .argtable = &list_args
        };
+
     MEMTRACE_PRINT_DELTA_MESSAGE("registering list_entries_cmd");
     ESP_ERROR_CHECK(esp_console_cmd_register(&list_entries_cmd));
     MEMTRACE_PRINT_DELTA_MESSAGE("registering set_cmd");

+ 1 - 1
components/platform_console/cmd_ota.c

@@ -62,7 +62,7 @@ static int perform_ota_update(int argc, char **argv)
 
     const esp_console_cmd_t cmd = {
         .command = "update",
-        .help = "Updates the application binary from the provided URL",
+        .help = "Update from URL",
         .hint = NULL,
         .func = &perform_ota_update,
         .argtable = &ota_args

+ 70 - 78
components/platform_console/cmd_system.c

@@ -31,6 +31,9 @@
 #include "messaging.h"				  
 #include "platform_console.h"
 #include "tools.h"
+#if defined(CONFIG_WITH_METRICS)
+#include "Metrics.h"
+#endif
 
 #ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
 #pragma message("Runtime stats enabled")
@@ -73,24 +76,42 @@ static void register_set_services();
 static void register_tasks();
 #endif
 extern BaseType_t network_manager_task;
+FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size){
+	FILE *f = open_memstream(buf, buf_size);
+	if (f == NULL) {
+		cmd_send_messaging(cmdname,MESSAGING_ERROR,"Unable to open memory stream.");
+	}
+    return f;
+}
 void register_system()
 {
-    register_free();
+
     register_set_services();
+    register_setdevicename();
+    register_free();
     register_heap();
     register_dump_heap();
-    register_setdevicename();
     register_version();
     register_restart();
-    register_deep_sleep();
-    register_light_sleep();
     register_factory_boot();
     register_restart_ota();
 #if WITH_TASKS_INFO
     register_tasks();
 #endif
+#if CONFIG_WITH_CONFIG_UI
+    register_deep_sleep();
+    register_light_sleep();
+#endif
+}
+void simple_restart()
+{
+	log_send_messaging(MESSAGING_WARNING,"Rebooting.");
+	if(!wait_for_commit()){
+		log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
+	}
+	vTaskDelay(750/ portTICK_PERIOD_MS);
+    esp_restart();
 }
-
 /* 'version' command */
 static int get_version(int argc, char **argv)
 {
@@ -128,36 +149,23 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 {
     if(is_recovery_running){
         if(partition_subtype ==ESP_PARTITION_SUBTYPE_APP_FACTORY){
-            log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
-            if(!wait_for_commit()){
-                log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
-            }
-            
-            vTaskDelay(750/ portTICK_PERIOD_MS);
-            esp_restart();
-            return ESP_OK;
+            // log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
+            simple_restart();
         }
     }
     else {
         if(partition_subtype !=ESP_PARTITION_SUBTYPE_APP_FACTORY){
-            log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
-            if(!wait_for_commit()){
-                log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
-            }
-            
-            vTaskDelay(750/ portTICK_PERIOD_MS);
-            esp_restart();
-            return ESP_OK;
+            // log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
+            simple_restart();
         }
     }
 	esp_err_t err = ESP_OK;
-	bool bFound=false;
-    log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
+    // log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
     const esp_partition_t *partition;
 	esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
 
 	if(it == NULL){
-		log_send_messaging(MESSAGING_ERROR,"Reboot failed. Cannot iterate through partitions");
+		log_send_messaging(MESSAGING_ERROR,"Reboot failed. Partitions error");
 	}
 	else
 	{
@@ -166,15 +174,11 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 		ESP_LOGD(TAG, "Releasing partition iterator");
 		esp_partition_iterator_release(it);
 		if(partition != NULL){
-			log_send_messaging(MESSAGING_INFO, "Found application partition %s sub type %u", partition->label,partition_subtype);
+			log_send_messaging(MESSAGING_INFO, "Rebooting to %s", partition->label);
 			err=esp_ota_set_boot_partition(partition);
 			if(err!=ESP_OK){
-				bFound=false;
 				log_send_messaging(MESSAGING_ERROR,"Unable to select partition for reboot: %s",esp_err_to_name(err));
 			}
-			else{
-                bFound=true;
-			}
 		}
 		else
 		{
@@ -183,13 +187,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 		}
 		ESP_LOGD(TAG, "Yielding to other processes");
 		taskYIELD();
-		if(bFound) {
-			if(!wait_for_commit()){
-				log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration changes. ");
-			}
-			vTaskDelay(750/ portTICK_PERIOD_MS);
-			esp_restart();
-		}
+        simple_restart();
 	}
 
 	return ESP_OK;
@@ -197,46 +195,31 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 
 static int restart(int argc, char **argv)
 {
-	log_send_messaging(MESSAGING_WARNING, "\n\nPerforming a simple restart to the currently active partition.");
-	if(!wait_for_commit()){
-		cmd_send_messaging(argv[0],MESSAGING_WARNING,"Unable to commit configuration. ");
-	}
-    vTaskDelay(750/ portTICK_PERIOD_MS);
-    esp_restart();
+    simple_restart();
     return 0;
 }
 
-void simple_restart()
-{
-	log_send_messaging(MESSAGING_WARNING,"System reboot requested.");
-	if(!wait_for_commit()){
-		log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
-	}
 
-											   
-	vTaskDelay(750/ portTICK_PERIOD_MS);
-    esp_restart();
-}
 
 esp_err_t guided_restart_ota(){
-	log_send_messaging(MESSAGING_WARNING,"System reboot to Application requested");
+	log_send_messaging(MESSAGING_WARNING,"Booting to Squeezelite");
     guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
 	return ESP_FAIL; // return fail.  This should never return... we're rebooting!
 }
 esp_err_t guided_factory(){
-	log_send_messaging(MESSAGING_WARNING,"System reboot to recovery requested");
+	log_send_messaging(MESSAGING_WARNING,"Booting to recovery");
 	guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
 	return ESP_FAIL; // return fail.  This should never return... we're rebooting!
 }
 static int restart_factory(int argc, char **argv)
 {
-	cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into recovery");
+	cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Recovery");
 	guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
 	return 0; // return fail.  This should never return... we're rebooting!
 }
 static int restart_ota(int argc, char **argv)
 {
-	cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into ota app 0");
+	cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Squeezelite");
 	guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
 	return 0; // return fail.  This should never return... we're rebooting!
 }
@@ -248,7 +231,9 @@ static void register_restart()
         .hint = NULL,
         .func = &restart,
     };
+#if CONFIG_WITH_CONFIG_UI    
     cmd_to_json(&cmd);
+#endif    
     ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
 }
 static void register_restart_ota()
@@ -259,7 +244,9 @@ static void register_restart_ota()
         .hint = NULL,
         .func = &restart_ota,
     };
+#if CONFIG_WITH_CONFIG_UI    
     cmd_to_json(&cmd);
+#endif
     ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
 }
 
@@ -271,7 +258,9 @@ static void register_factory_boot()
         .hint = NULL,
         .func = &restart_factory,
     };
+#if CONFIG_WITH_CONFIG_UI    
     cmd_to_json(&cmd);
+#endif
     ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
 }
 /** 'free' command prints available heap memory */
@@ -287,11 +276,14 @@ static void register_free()
 {
     const esp_console_cmd_t cmd = {
         .command = "free",
-        .help = "Get the current size of free heap memory",
+        .help = "Get free heap memory",
         .hint = NULL,
         .func = &free_mem,
     };
+#if CONFIG_WITH_CONFIG_UI
     cmd_to_json(&cmd);
+#endif
+
     ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
 }
 static int dump_heap(int argc, char **argv)
@@ -303,16 +295,16 @@ static int dump_heap(int argc, char **argv)
 /* 'heap' command prints minumum heap size */
 static int heap_size(int argc, char **argv)
 {
-    ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
-						heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
-						heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
-						heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
-                        heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
-						heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
-						heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
-                        heap_caps_get_free_size(MALLOC_CAP_DMA),
-						heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
-						heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
+    // ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
+	// 					heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+	// 					heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
+	// 					heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
+    //                     heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
+	// 					heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
+	// 					heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
+    //                     heap_caps_get_free_size(MALLOC_CAP_DMA),
+	// 					heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
+	// 					heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
     cmd_send_messaging(argv[0],MESSAGING_INFO,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
 						heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
 						heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
@@ -457,9 +449,8 @@ static int setdevicename(int argc, char **argv)
 
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
 		return 1;
 	}
 	nerrors+=setnamevar("a2dp_dev_name", f, name);
@@ -488,11 +479,13 @@ static void register_heap()
 {
     const esp_console_cmd_t heap_cmd = {
         .command = "heap",
-        .help = "Get minimum size of free heap memory found during execution",
+        .help = "Get minimum size of free heap memory",
         .hint = NULL,
         .func = &heap_size,
     };
+#if CONFIG_WITH_CONFIG_UI
     cmd_to_json(&heap_cmd);
+#endif    
     ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
 
 }
@@ -521,6 +514,7 @@ static void register_setdevicename()
 			.func = &setdevicename,
 			.argtable = &name_args
 	};
+
 	cmd_to_json_with_cb(&set_name,&setdevicename_cb);
 	ESP_ERROR_CHECK(esp_console_cmd_register(&set_name));
 }
@@ -618,9 +612,7 @@ static void register_deep_sleep()
 
     const esp_console_cmd_t cmd = {
         .command = "deep_sleep",
-        .help = "Enter deep sleep mode. "
-        "Two wakeup modes are supported: timer and GPIO. "
-        "If no wakeup option is specified, will sleep indefinitely.",
+        .help = "Enter deep sleep mode. ",
         .hint = NULL,
         .func = &deep_sleep,
         .argtable = &deep_sleep_args
@@ -649,9 +641,8 @@ static int do_set_services(int argc, char **argv)
     }
 	char *buf = NULL;
 	size_t buf_size = 0;
-	FILE *f = open_memstream(&buf, &buf_size);
+	FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
 	if (f == NULL) {
-		cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
 		return 1;
 	}
 
@@ -674,7 +665,7 @@ static int do_set_services(int argc, char **argv)
         
         if(err!=ESP_OK){
             nerrors++;
-            fprintf(f,"Error setting telnet service to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
+            fprintf(f,"Error setting telnet to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
         }
         else {
             fprintf(f,"Telnet service changed to %s\n",set_services_args.telnet->sval[0]);
@@ -706,7 +697,6 @@ cJSON * set_services_cb(){
     #if WITH_TASKS_INFO        
     console_set_bool_parameter(values,"stats",set_services_args.stats);
     #endif
-
 	if ((p = config_alloc_get(NVS_TYPE_STR, "telnet_enable")) != NULL) {
         if(strcasestr("YX",p)!=NULL){
 		    cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Telnet Only");
@@ -717,7 +707,9 @@ cJSON * set_services_cb(){
         else {
             cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled");
         }
-
+#if defined(CONFIG_WITH_METRICS)
+        metrics_add_feature_variant("telnet",p);
+#endif
 		FREE_AND_NULL(p);
 	}
 

+ 1 - 1
components/platform_console/cmd_system.h

@@ -17,7 +17,7 @@ void register_system();
 esp_err_t guided_factory();
 esp_err_t guided_restart_ota();
 void simple_restart();
-
+FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
 #ifdef __cplusplus
 }
 #endif

+ 6 - 28
components/platform_console/cmd_wifi.c

@@ -37,6 +37,9 @@ extern bool bypass_network_manager;
 #define JOIN_TIMEOUT_MS (10000)
 #include "platform_console.h"
 
+// To enable wifi configuration from the command line, uncomment the line below
+// define WIFI_CMDLINE 1
+
 
 extern EventGroupHandle_t network_event_group;
 extern const int CONNECTED_BIT;
@@ -53,13 +56,6 @@ static struct {
 
 // todo: implement access point config - cmd_to_json(&i2cdetect_cmd);
 
-
-///** Arguments used by 'join' function */
-//static struct {
-//    struct arg_int *autoconnect;
-//    struct arg_end *end;
-//} auto_connect_args;
-
 static void event_handler(void* arg, esp_event_base_t event_base,
                                 int32_t event_id, void* event_data)
 {
@@ -72,27 +68,7 @@ static void event_handler(void* arg, esp_event_base_t event_base,
         xEventGroupSetBits(network_event_group, CONNECTED_BIT);
     }
 }
-//bool wait_for_wifi(){
-//
-//	bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
-//
-//	if(!connected){
-//		ESP_LOGD(TAG,"Waiting for WiFi...");
-//	    connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
-//	                                   pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
-//	    if(!connected){
-//	    	ESP_LOGD(TAG,"wifi timeout.");
-//	    }
-//	    else
-//	    {
-//	    	ESP_LOGI(TAG,"WiFi Connected!");
-//	    }
-//	}
-//
-//
-//    return connected;
-//
-//}
+
 static void initialise_wifi(void)
 {
     static bool initialized = false;
@@ -204,8 +180,10 @@ void register_wifi_join()
 
 void register_wifi()
 {
+    #ifdef WIFI_CMDLINE
     register_wifi_join();
     if(bypass_network_manager){
     	initialise_wifi();
     }
+    #endif
 }

+ 13 - 5
components/platform_console/platform_console.c

@@ -27,7 +27,9 @@
 #include "platform_config.h"
 #include "telnet.h" 
 #include "tools.h"
-
+#if defined(CONFIG_WITH_METRICS)
+#include "metrics.h"
+#endif
 #include "messaging.h"
 
 #include "config.h"
@@ -85,14 +87,22 @@ cJSON * get_cmd_list(){
 }
 void console_set_bool_parameter(cJSON * root,char * nvs_name, struct arg_lit *arg){
     char * p=NULL;
-    if(!root) {
+	bool enabled = false;
+	if(!root) {
         ESP_LOGE(TAG,"Invalid json parameter. Cannot set %s from %s",arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary,nvs_name);
         return;
     }
     if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) {
-        cJSON_AddBoolToObject(root,arg->hdr.longopts,strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0);
+		enabled = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
+		cJSON_AddBoolToObject(root,arg->hdr.longopts,enabled);
         FREE_AND_NULL(p);
     }
+#if defined(CONFIG_WITH_METRICS)	
+	if(enabled){
+		metrics_add_feature(nvs_name,"enabled");
+	}
+#endif
+	
 }
 struct arg_end *getParmsEnd(struct arg_hdr * * argtable){
 	if(!argtable) return NULL;
@@ -360,8 +370,6 @@ void console_start() {
 	register_system();
 	MEMTRACE_PRINT_DELTA_MESSAGE("Registering config commands");
 	register_config_cmd();
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering nvs commands");
-	register_nvs();
 	MEMTRACE_PRINT_DELTA_MESSAGE("Registering wifi commands");
 	register_wifi();
 

+ 1 - 1
components/services/CMakeLists.txt

@@ -1,5 +1,5 @@
 idf_component_register(SRC_DIRS .
 						INCLUDE_DIRS .
-						REQUIRES json tools platform_config display wifi-manager
+						REQUIRES json tools platform_config display wifi-manager esp-tls platform_config
 						PRIV_REQUIRES soc esp32
 )

+ 11 - 2
components/services/accessors.c

@@ -47,6 +47,7 @@ cJSON * gpio_list=NULL;
 	#define STR(macro)  QUOTE(macro)
 #endif
 
+extern cJSON * get_gpio_list(bool refresh);
 bool are_statistics_enabled(){
 #if defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) &&  defined (CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS)
 	return true;
@@ -592,7 +593,7 @@ const gpio_exp_config_t* config_gpio_exp_get(int index) {
 	PARSE_PARAM(item, "intr", '=', config.intr);
 	PARSE_PARAM(item, "base", '=', config.base);
 	PARSE_PARAM(item, "count", '=', config.count);
-	PARSE_PARAM_STR(item, "model", '=', config.model, 31);
+	PARSE_PARAM_STR(item, "model", '=', config.model, sizeof(config.model)-1);
 
 	if ((p = strcasestr(item, "port")) != NULL) {
 		char port[8] = "";
@@ -646,6 +647,12 @@ const set_GPIO_struct_t * get_gpio_struct(){
 #endif
 #ifdef CONFIG_LED_RED_GPIO
 		gpio_struct.red.gpio = CONFIG_LED_RED_GPIO;
+#endif	
+#if  defined(CONFIG_POWER_GPIO) && CONFIG_POWER_GPIO != -1
+		gpio_struct.power.gpio = CONFIG_POWER_GPIO;
+#endif	
+#ifdef CONFIG_POWER_GPIO_LEVEL
+		gpio_struct.power.level = CONFIG_POWER_GPIO_LEVEL;
 #endif	
 	if(nvs_item){
 		HANDLE_GPIO_STRUCT_MEMBER(amp,false);
@@ -658,6 +665,7 @@ const set_GPIO_struct_t * get_gpio_struct(){
 		HANDLE_GPIO_STRUCT_MEMBER(vcc,false);
 		HANDLE_GPIO_STRUCT_MEMBER(gnd,false);
 		HANDLE_GPIO_STRUCT_MEMBER(ir,false);
+		HANDLE_GPIO_STRUCT_MEMBER(power,false);
 		free(nvs_item);
 	}
 
@@ -823,6 +831,7 @@ cJSON * get_GPIO_nvs_list(cJSON * list) {
 	ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,jack,"other");
 	ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,green,"other");
 	ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,red,"other");
+	ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,power,"other");
 	ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,spkfault,"other");
 	return ilist;
 }
@@ -1169,7 +1178,7 @@ cJSON * get_psram_gpio_list(cJSON * list){
 /****************************************************************************************
  *
  */
-cJSON * get_gpio_list(bool refresh) {
+cJSON * get_gpio_list_handler(bool refresh) {
 	gpio_num_t gpio_num;
 	if(gpio_list && !refresh){
 		return gpio_list;

+ 2 - 1
components/services/accessors.h

@@ -73,6 +73,7 @@ typedef struct {
 	gpio_with_level_t green;
 	gpio_with_level_t red;
 	gpio_with_level_t spkfault;	
+	gpio_with_level_t power;	
 } set_GPIO_struct_t;
 
 typedef struct {
@@ -117,7 +118,7 @@ bool 						is_spdif_config_locked();
 esp_err_t 					free_gpio_entry( gpio_entry_t ** gpio);
 gpio_entry_t * 				get_gpio_by_name(char * name,char * group, bool refresh);
 gpio_entry_t * 				get_gpio_by_no(int gpionum, bool refresh);
-cJSON * 					get_gpio_list(bool refresh);
+
 bool 						is_dac_config_locked();
 bool 						are_statistics_enabled();
 const rotary_struct_t * 	config_rotary_get();

+ 1 - 1
components/tools/CMakeLists.txt

@@ -1,6 +1,6 @@
 idf_component_register( SRCS operator.cpp tools.c trace.c
 						REQUIRES esp_common pthread 
-						PRIV_REQUIRES esp_http_client esp-tls
+						PRIV_REQUIRES esp_http_client esp-tls json
 						INCLUDE_DIRS .
 )
 

+ 21 - 1
components/tools/tools.c

@@ -24,6 +24,7 @@
 #error CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS must be at least 2
 #endif
 
+#include "cJSON.h"
 const static char TAG[] = "tools";
 
 /****************************************************************************************
@@ -318,11 +319,30 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) {
 			return ESP_FAIL;
 		}
 		break;
+	}
 	default:
 		break;
-	}
+	
 	}
 
 	return ESP_OK;
 }
 
+ 
+time_t millis() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+}
+
+void dump_json_content(const char* prefix, cJSON* json, int level) {
+	if (!json) {
+		ESP_LOG_LEVEL(level,TAG, "%s: empty!", prefix);
+		return;
+	}
+	char* output = cJSON_Print(json);
+	if (output) {
+		ESP_LOG_LEVEL(level,TAG, "%s: \n%s", prefix, output);
+	}
+	FREE_AND_NULL(output);
+}

+ 7 - 0
components/tools/tools.h

@@ -9,6 +9,10 @@
  */
  
 #pragma once
+#include "cJSON.h"
+#include "time.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -70,6 +74,9 @@ void vTaskDeleteEXTRAM(TaskHandle_t xTask);
 
 extern const char unknown_string_placeholder[];
 
+time_t millis();
+void dump_json_content(const char* prefix, cJSON* json, int level);
+
 #ifdef __cplusplus
 }
 #endif

+ 1 - 1
components/wifi-manager/http_server_handlers.c

@@ -44,7 +44,7 @@ typedef struct session_context {
     char * sess_ip_address;
     u16_t port;
 } session_context_t;
-
+extern cJSON * get_gpio_list(bool refresh);
 
 union sockaddr_aligned {
 	struct sockaddr     sa;

+ 1 - 1
main/CMakeLists.txt

@@ -1,4 +1,4 @@
 idf_component_register(SRC_DIRS . 
-					PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets led_strip
+					PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets led_strip metrics
 					LDFRAGMENTS "linker.lf"                                            
                     	)

+ 10 - 0
main/Kconfig.projbuild

@@ -74,6 +74,16 @@ menu "Squeezelite-ESP32"
 				select I2C_LOCKED		
 				select TARGET_LOCKED				
 		endchoice	
+		config WITH_CONFIG_UI
+			bool "Enable config UI"
+			default n
+			help
+				Enable configuring system options with the UI
+		config WITH_METRICS
+			bool "Enable Metrics"
+			default n
+			help
+				Enable capturing and reporting anonymous metrics
 		config RELEASE_API
 			string "Software update URL"
 			default "https://api.github.com/repos/sle118/squeezelite-esp32/releases"

+ 99 - 64
main/esp_app_main.c

@@ -47,6 +47,9 @@
 #include "accessors.h"
 #include "cmd_system.h"
 #include "tools.h"
+#if defined(CONFIG_WITH_METRICS)
+#include "Metrics.h"
+#endif
 
 const char unknown_string_placeholder[] = "unknown";
 const char null_string_placeholder[] = "null";
@@ -68,7 +71,68 @@ bool cold_boot=true;
 extern const char _ctype_[];
 const char* __ctype_ptr__ = _ctype_;
 #endif
+typedef struct {
+    const char *key;
+    const char *value;
+} DefaultStringVal;
+typedef struct {
+    const char *key;
+    unsigned int uint_value;
+    bool is_signed;
+} DefaultNumVal;
 
+const DefaultNumVal defaultNumVals[] = {
+    {"ota_erase_blk", OTA_FLASH_ERASE_BLOCK, 0},
+    {"ota_stack", OTA_STACK_SIZE, 0},
+    {"ota_prio", OTA_TASK_PRIOTITY, 1}
+};
+const DefaultStringVal defaultStringVals[] = {
+    {"equalizer", ""},
+    {"loudness", "0"},
+    {"actrls_config", ""},
+    {"lms_ctrls_raw", "n"},
+    {"rotary_config", CONFIG_ROTARY_ENCODER},
+    {"display_config", CONFIG_DISPLAY_CONFIG},
+    {"eth_config", CONFIG_ETH_CONFIG},
+    {"i2c_config", CONFIG_I2C_CONFIG},
+    {"spi_config", CONFIG_SPI_CONFIG},
+    {"set_GPIO", CONFIG_SET_GPIO},
+    {"sleep_config", ""},
+    {"led_brightness", ""},
+    {"spdif_config", ""},
+    {"dac_config", ""},
+    {"dac_controlset", ""},
+    {"jack_mutes_amp", "n"},
+    {"gpio_exp_config", CONFIG_GPIO_EXP_CONFIG},
+    {"bat_config", ""},
+    {"metadata_config", ""},
+    {"telnet_enable", ""},
+    {"telnet_buffer", "40000"},
+    {"telnet_block", "500"},
+    {"stats", "n"},
+    {"rel_api", CONFIG_RELEASE_API},
+    {"pollmx", "600"},
+    {"pollmin", "15"},
+    {"ethtmout", "8"},
+    {"dhcp_tmout", "8"},
+    {"target", CONFIG_TARGET},
+    {"led_vu_config", ""},
+#ifdef CONFIG_BT_SINK
+    {"bt_sink_pin", STR(CONFIG_BT_SINK_PIN)},
+    {"bt_sink_volume", "127"},
+    // Note: register_default_with_mac("bt_name", CONFIG_BT_NAME); is a special case
+    {"enable_bt_sink", STR(CONFIG_BT_SINK)},
+    {"a2dp_dev_name", CONFIG_A2DP_DEV_NAME},
+    {"a2dp_ctmt", STR(CONFIG_A2DP_CONNECT_TIMEOUT_MS)},
+    {"a2dp_ctrld", STR(CONFIG_A2DP_CONTROL_DELAY_MS)},
+    {"a2dp_sink_name", CONFIG_A2DP_SINK_NAME},
+	{"autoexec", "1"},
+#ifdef CONFIG_AIRPLAY_SINK
+	{"airplay_port", CONFIG_AIRPLAY_PORT},
+	{"enable_airplay", STR(CONFIG_AIRPLAY_SINK)}
+#endif
+#endif	
+};
 static bool bNetworkConnected=false;
 
 // as an exception _init function don't need include
@@ -80,7 +144,9 @@ extern void target_init(char *target);
 const char * str_or_unknown(const char * str) { return (str?str:unknown_string_placeholder); }
 const char * str_or_null(const char * str) { return (str?str:null_string_placeholder); }
 bool is_recovery_running;
-
+bool is_network_connected(){
+	return bNetworkConnected;
+}
 void cb_connection_got_ip(nm_state_t new_state, int sub_state){
 	const char *hostname;
 	static ip4_addr_t ip;
@@ -163,7 +229,7 @@ void set_log_level(char * tag, char * level){
 }
 
 #define DEFAULT_NAME_WITH_MAC(var,defval) char var[strlen(defval)+sizeof(macStr)]; strcpy(var,defval); strcat(var,macStr)
-void register_default_string_val(const char * key, char * value){
+void register_default_string_val(const char * key, const char * value){
 	char * existing =(char *)config_alloc_get(NVS_TYPE_STR,key );
 	ESP_LOGD(TAG,"Register default called with:  %s= %s",key,value );
 	if(!existing) {
@@ -175,7 +241,15 @@ void register_default_string_val(const char * key, char * value){
 	}
 	FREE_AND_NULL(existing);
 }
-
+void register_single_default_num_val(const DefaultNumVal *entry) {
+    char number_buffer[101] = {};
+    if (entry->is_signed) {
+        snprintf(number_buffer, sizeof(number_buffer) - 1, "%d", entry->uint_value);
+    } else {
+        snprintf(number_buffer, sizeof(number_buffer) - 1, "%u", entry->uint_value);
+    }
+    register_default_string_val(entry->key, number_buffer);
+}
 char * alloc_get_string_with_mac(const char * val) {
     uint8_t mac[6];
     char macStr[LOCAL_MAC_SIZE + 1];
@@ -188,7 +262,7 @@ char * alloc_get_string_with_mac(const char * val) {
 		strcat(fullvalue, macStr);
 	}
 	else {
-		ESP_LOGE(TAG,"Memory allocation failed when getting mac value for %s", val);
+		ESP_LOGE(TAG,"malloc failed for value %s", val);
 	}
 	return fullvalue;	
 	
@@ -200,7 +274,7 @@ void register_default_with_mac(const char* key,  char* defval) {
 		FREE_AND_NULL(fullvalue);
 	}
 	else {
-		ESP_LOGE(TAG,"Memory allocation failed when registering default value for %s", key);
+		ESP_LOGE(TAG,"malloc failed for value %s", key);
 	}
 }
 
@@ -227,70 +301,20 @@ void register_default_nvs(){
 	
 #ifdef CONFIG_AIRPLAY_SINK
 	register_default_with_mac("airplay_name", CONFIG_AIRPLAY_NAME);
-	register_default_string_val("airplay_port", CONFIG_AIRPLAY_PORT);
-	register_default_string_val( "enable_airplay", STR(CONFIG_AIRPLAY_SINK));
 #endif
-
 #ifdef CONFIG_BT_SINK
-	register_default_string_val( "bt_sink_pin", STR(CONFIG_BT_SINK_PIN));
-	register_default_string_val( "bt_sink_volume", "127");
 	register_default_with_mac("bt_name", CONFIG_BT_NAME);
-	register_default_string_val( "enable_bt_sink", STR(CONFIG_BT_SINK));
-	register_default_string_val("a2dp_dev_name", CONFIG_A2DP_DEV_NAME);
-	register_default_string_val("a2dp_ctmt", STR(CONFIG_A2DP_CONNECT_TIMEOUT_MS));
-	register_default_string_val("a2dp_ctrld", STR(CONFIG_A2DP_CONTROL_DELAY_MS));
-	register_default_string_val("a2dp_sink_name", CONFIG_A2DP_SINK_NAME);
 #endif
-	
 	register_default_with_mac("host_name", DEFAULT_HOST_NAME);
 	register_default_with_mac("ap_ssid", CONFIG_DEFAULT_AP_SSID);
-	register_default_string_val("autoexec","1");
 	register_default_with_mac("autoexec1",CONFIG_DEFAULT_COMMAND_LINE " -n " DEFAULT_HOST_NAME);	
+    for (int i = 0; i < sizeof(defaultStringVals) / sizeof(DefaultStringVal); ++i) {
+        register_default_string_val(defaultStringVals[i].key, defaultStringVals[i].value);
+    }
+	for (int i = 0; i < sizeof(defaultNumVals) / sizeof(DefaultNumVal); ++i) {
+        register_single_default_num_val(&defaultNumVals[i]);
+    }
 
-	register_default_string_val("release_url", CONFIG_SQUEEZELITE_ESP32_RELEASE_URL);
-	register_default_string_val("ap_ip_address",CONFIG_DEFAULT_AP_IP);
-	register_default_string_val("ap_ip_gateway",CONFIG_DEFAULT_AP_GATEWAY );
-	register_default_string_val("ap_ip_netmask",CONFIG_DEFAULT_AP_NETMASK);
-	register_default_string_val("ap_channel",STR(CONFIG_DEFAULT_AP_CHANNEL));
-	register_default_string_val("ap_pwd", CONFIG_DEFAULT_AP_PASSWORD);
-	register_default_string_val("bypass_wm", "0");
-	register_default_string_val("equalizer", "");
-    register_default_string_val("loudness", "0");
-	register_default_string_val("actrls_config", "");	
-	register_default_string_val("lms_ctrls_raw", "n");	
-	register_default_string_val("rotary_config", CONFIG_ROTARY_ENCODER);
-	char number_buffer[101] = {};
-	snprintf(number_buffer,sizeof(number_buffer)-1,"%u",OTA_FLASH_ERASE_BLOCK);
-	register_default_string_val( "ota_erase_blk", number_buffer);
-	snprintf(number_buffer,sizeof(number_buffer)-1,"%u",OTA_STACK_SIZE);
-	register_default_string_val( "ota_stack", number_buffer);
-	snprintf(number_buffer,sizeof(number_buffer)-1,"%d",OTA_TASK_PRIOTITY);
-	register_default_string_val( "ota_prio", number_buffer);
-	register_default_string_val( "display_config", CONFIG_DISPLAY_CONFIG);
-	register_default_string_val( "eth_config", CONFIG_ETH_CONFIG);
-	register_default_string_val( "i2c_config", CONFIG_I2C_CONFIG);
-	register_default_string_val( "spi_config", CONFIG_SPI_CONFIG);
-	register_default_string_val( "set_GPIO", CONFIG_SET_GPIO);
-	register_default_string_val( "sleep_config", "");    
-	register_default_string_val( "led_brightness", "");
-	register_default_string_val( "spdif_config", "");
-	register_default_string_val( "dac_config", "");
-	register_default_string_val( "dac_controlset", "");
-	register_default_string_val( "jack_mutes_amp", "n");
-	register_default_string_val("gpio_exp_config", CONFIG_GPIO_EXP_CONFIG);
-	register_default_string_val( "bat_config", "");
-	register_default_string_val( "metadata_config", "");
-	register_default_string_val( "telnet_enable", "");
-	register_default_string_val( "telnet_buffer", "40000");
-	register_default_string_val( "telnet_block", "500");
-	register_default_string_val( "stats", "n");
-	register_default_string_val( "rel_api", CONFIG_RELEASE_API);
-	register_default_string_val("pollmx","600");
-    register_default_string_val("pollmin","15");
-    register_default_string_val("ethtmout","8");
-    register_default_string_val("dhcp_tmout","8");
-	register_default_string_val("target", CONFIG_TARGET);
-    register_default_string_val("led_vu_config", "");
 	wait_for_commit();
 	ESP_LOGD(TAG,"Done setting default values in nvs.");
 }
@@ -303,7 +327,7 @@ uint32_t halSTORAGE_RebootCounterUpdate(int32_t xValue) {
 	}
 	RebootCounter = (xValue != 0) ? (RebootCounter + xValue) : 0;
 	RecoveryRebootCounter  = (xValue != 0) && is_recovery_running ? (RecoveryRebootCounter + xValue) : 0;
-	return (RebootCounter) ; 
+	return RebootCounter ; 
 	}
 
 void handle_ap_connect(nm_state_t new_state, int sub_state){
@@ -356,11 +380,17 @@ void app_main()
 		}	
 	}
 
+
 	char * fwurl = NULL;
 	MEMTRACE_PRINT_DELTA();
 	ESP_LOGI(TAG,"Starting app_main");
 	initialize_nvs();
 	MEMTRACE_PRINT_DELTA();
+#if defined(CONFIG_WITH_METRICS)
+	ESP_LOGI(TAG,"Setting up metrics.");
+	metrics_init();	
+	MEMTRACE_PRINT_DELTA();
+#endif
 	ESP_LOGI(TAG,"Setting up telnet.");
 	init_telnet(); // align on 32 bits boundaries
 	MEMTRACE_PRINT_DELTA();
@@ -371,7 +401,6 @@ void app_main()
 	network_event_group = xEventGroupCreate();
 	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
 	xEventGroupClearBits(network_event_group, CONNECTED_BIT);
-
 	ESP_LOGI(TAG,"Registering default values");
 	register_default_nvs();
 	MEMTRACE_PRINT_DELTA();
@@ -399,7 +428,10 @@ void app_main()
 			led_vu_color_yellow(LED_VU_BRIGHT);
 		}
 	}
-
+	#if defined(CONFIG_WITH_METRICS)
+	metrics_event_boot(is_recovery_running?"recovery":"ota");
+	#endif
+	
 	ESP_LOGD(TAG,"Getting firmware OTA URL (if any)");
 	fwurl = process_ota_url();
 
@@ -457,6 +489,9 @@ void app_main()
 				taskYIELD();
 			}
 			ESP_LOGI(TAG,"Updating firmware from link: %s",fwurl);
+			#if defined(CONFIG_WITH_METRICS)
+			metrics_event("fw_update");
+			#endif
 			start_ota(fwurl, NULL, 0);
 		}
 		else {

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff