forked from sle118/squeezelite-esp32
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
34 changed files
with
1,973 additions
and
1,122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.