Skip to content

Commit

Permalink
feat(publish service): Implement push-based publish service to a webh…
Browse files Browse the repository at this point in the history
…ook (#181)
  • Loading branch information
Slider0007 authored Oct 23, 2024
1 parent c1fa372 commit ea52a5c
Show file tree
Hide file tree
Showing 44 changed files with 1,352 additions and 40 deletions.
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AI-on-the-Edge device [SL Fork]
# AI-on-the-Edge Device [SLFork]
<img src="images/icon/watermeter.svg" width="80px">

Artificial intelligence is everywhere, from speech to image recognition. While most AI systems rely on powerful processors or cloud computing, **edge computing** brings AI closer to the end user by utilizing the capabilities of modern processors.
Expand All @@ -10,15 +10,20 @@ Let's explore how to make **AI on the Edge** a reality!
## Key features
- Tensorflow Lite (TFLite) integration – including easy-to-use wrapper
- Inline image processing (Image taking, Image alignment, ROI extraction, Post processing)
- Usage of **small** and **low-cost** devices ([Supported Hardware](#supported-hardware))
- Integrated camera and illumination (depending on hardware)
- Web interface for administration and control
- OTA interface for updating directly via web interface
- Full integration into [Home Assistant](docs/API/MQTT/home-assistant-discovery.md)
- InfluxDB v1.x + v2.x
- [MQTT v3.x](docs/API/MQTT/_OVERVIEW.md)
- Usage of **small** and **low-cost** AI-capable devices ([Supported Hardware](#supported-hardware))
- Integrated camera and illumination (depending on hardware capabilities)
- Web interface for visualization, control and administration
- Over the air (OTA) firmware update via web interface


## APIs / Publishing Services / Home Automation Integrations
- Home Assistant Integration ([Home Assistant Discovery](docs/API/MQTT/home-assistant-discovery.md))
- [REST API](docs/API/REST/_OVERVIEW.md)
- [Prometheus/OpenMetrics exporter](docs/API/Prometheus-OpenMetrics/_OVERVIEW.md)
- [MQTT v3](docs/API/MQTT/_OVERVIEW.md)
- InfluxDB v1
- InfluxDB v2
- [Webhook Publishing](docs/API/Webhook/_OVERVIEW.md)
- [Prometheus/OpenMetrics Exporter](docs/API/Prometheus-OpenMetrics/_OVERVIEW.md)


## Workflow
Expand Down Expand Up @@ -59,6 +64,8 @@ There are multiple options to install the firmware and the SD card content.
Officially released firmware packages can be downloaded from [releases](https://github.com/slider0007/AI-on-the-edge-device/releases) page.<br>
A possibly already available development version (upcoming release version) can be previewed [here](https://github.com/Slider0007/AI-on-the-edge-device/pulls?q=is%3Aopen+is%3Apr+label%3A%22autorelease%3A+pending%22).

⚠️ **Please do not use the source files directly from the repository, not even for the preparation of the SD card!** Use only files related to the download sources mentioned here (official precompiled release packages or test versions). Otherwise, full functionality cannot be guaranteed.<br>

### Option 1: Web Installer (Only For Released Versions)

Follow the instructions listed at [Web Installer](https://slider0007.github.io/AI-on-the-edge-device/) page.<br>
Expand All @@ -77,7 +84,15 @@ See [REST API Documentation](docs/API/REST/_OVERVIEW.md) in github repository or

### MQTT API
See [MQTT API Documentation](docs/API/MQTT/_OVERVIEW.md) in github repository or via device web interface (`System > Documentation > MQTT API`).<br>
⚠️ Read API documenation carefully. MQTT API is not fully compatible with jomjol's original firmware.
⚠️ Read API documenation carefully. Webhook API is not fully compatible with jomjol's original firmware.

### Prometheus Exporter
See [Prometheus API Documentation](docs/API/Prometheus-OpenMetrics/_OVERVIEW.md) in github repository or via device web interface (`System > Documentation > Prometheus API`).<br>
⚠️ Read API documenation carefully. Prometheus API is not fully compatible with jomjol's original firmware.

### Webhook API
See [Webhook API Documentation](docs/API/Webhook/_OVERVIEW.md) in github repository or via device web interface (`System > Documentation > Webhook API`).<br>
⚠️ Read API documenation carefully. Webhook API is not fully compatible with jomjol's original firmware.


## Build Yourself
Expand Down
23 changes: 23 additions & 0 deletions code/components/config_handling/cfgDataStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ enum HAMeterType {
};


enum WebhookUploadImage {
WEBHOOK_PUBLISH_IMAGE_DISABLED = 0,
WEBHOOK_PUBLISH_IMAGE_ENABLED = 1,
WEBHOOK_PUBLISH_IMAGE_ON_ERROR_ONLY = 2
};


enum GpioSmartledType {
LEDTYPE_WS2812 = 0,
LEDTYPE_WS2812B_UNIVERSAL = 1,
Expand Down Expand Up @@ -333,6 +340,22 @@ struct CfgData {
std::vector<InfluxDBPerSequence> sequence;
} sectionInfluxDBv2;

// Webhook service (push-based)
struct SectionWebhook {
bool enabled = false;
std::string uri = ""; // e.g. http://webhook.com/1234567890
std::string apiKey = "";
int publishImage = WEBHOOK_PUBLISH_IMAGE_DISABLED;
int authMode = AUTH_NONE;
std::string username = "";
std::string password = "";
struct TLS {
std::string caCert = "";
std::string clientCert = "";
std::string clientKey = "";
} tls;
} sectionWebhook;

// GPIO
struct SectionGpio {
bool customizationEnabled = false;
Expand Down
89 changes: 87 additions & 2 deletions code/components/config_handling/configClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ esp_err_t ConfigClass::parseConfig(httpd_req_t *req, bool init, bool unityTest)

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "influxdbv2"), "authmode");
if (cJSON_IsNumber(objEl))
cfgDataTemp.sectionInfluxDBv2.authMode = std::clamp(objEl->valueint, 0, 2);
cfgDataTemp.sectionInfluxDBv2.authMode = std::clamp(objEl->valueint, 1, 2);

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "influxdbv2"), "token");
if (cJSON_IsString(objEl) && strcmp(objEl->valuestring, "******") != 0) {
Expand Down Expand Up @@ -1076,6 +1076,62 @@ esp_err_t ConfigClass::parseConfig(httpd_req_t *req, bool init, bool unityTest)
}


// Webhook
// ***************************
objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "enabled");
if (cJSON_IsBool(objEl))
cfgDataTemp.sectionWebhook.enabled = objEl->valueint;

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "uri");
if (cJSON_IsString(objEl))
cfgDataTemp.sectionWebhook.uri = objEl->valuestring;

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "apikey");
if (cJSON_IsString(objEl))
cfgDataTemp.sectionWebhook.apiKey = objEl->valuestring;

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "publishimage");
if (cJSON_IsNumber(objEl))
cfgDataTemp.sectionWebhook.publishImage = std::clamp(objEl->valueint, 0, 2);

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "authmode");
if (cJSON_IsNumber(objEl))
cfgDataTemp.sectionWebhook.authMode = std::clamp(objEl->valueint, 0, 2);

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "username");
if (cJSON_IsString(objEl))
cfgDataTemp.sectionWebhook.username = objEl->valuestring;

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "password");
if (cJSON_IsString(objEl) && strcmp(objEl->valuestring, "******") != 0) {
cfgDataTemp.sectionWebhook.password = objEl->valuestring;
saveDataToNVS("webhook_pw", cfgDataTemp.sectionWebhook.password);
}
else {
if (!unityTest) {
loadDataFromNVS("webhook_pw", cfgDataTemp.sectionWebhook.password);
}
}

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "tls"), "cacert");
if (cJSON_IsString(objEl)) {
cfgDataTemp.sectionWebhook.tls.caCert = objEl->valuestring;
validateStructure(cfgDataTemp.sectionWebhook.tls.caCert);
}

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "tls"), "clientcert");
if (cJSON_IsString(objEl)) {
cfgDataTemp.sectionWebhook.tls.clientCert = objEl->valuestring;
validateStructure(cfgDataTemp.sectionWebhook.tls.clientCert);
}

objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "webhook"), "tls"), "clientkey");
if (cJSON_IsString(objEl)) {
cfgDataTemp.sectionWebhook.tls.clientKey = objEl->valuestring;
validateStructure(cfgDataTemp.sectionWebhook.tls.clientKey);
}


// GPIO
// ***************************
objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "gpio"), "customizationenabled");
Expand Down Expand Up @@ -1783,6 +1839,35 @@ esp_err_t ConfigClass::serializeConfig(bool unityTest)
}


// Webhook
// ***************************
cJSON *webhook, *webhookTls;
if (!cJSON_AddItemToObject(cJsonObject, "webhook", webhook = cJSON_CreateObject()))
retVal = ESP_FAIL;
if (cJSON_AddBoolToObject(webhook, "enabled", cfgDataTemp.sectionWebhook.enabled) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhook, "uri", cfgDataTemp.sectionWebhook.uri.c_str()) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhook, "apikey", cfgDataTemp.sectionWebhook.apiKey.c_str()) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddNumberToObject(webhook, "publishimage", cfgDataTemp.sectionWebhook.publishImage) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddNumberToObject(webhook, "authmode", cfgDataTemp.sectionWebhook.authMode) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhook, "username", cfgDataTemp.sectionWebhook.username.c_str()) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhook, "password", cfgDataTemp.sectionWebhook.password.empty() ? "" : "******") == NULL)
retVal = ESP_FAIL;
if (!cJSON_AddItemToObject(webhook, "tls", webhookTls = cJSON_CreateObject()))
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhookTls, "cacert", cfgDataTemp.sectionWebhook.tls.caCert.c_str()) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhookTls, "clientcert", cfgDataTemp.sectionWebhook.tls.clientCert.c_str()) == NULL)
retVal = ESP_FAIL;
if (cJSON_AddStringToObject(webhookTls, "clientkey", cfgDataTemp.sectionWebhook.tls.clientKey.c_str()) == NULL)
retVal = ESP_FAIL;


// GPIO
// ***************************
cJSON *gpio, *gpiopin, *gpiopinEl, *gpiopinSmartled;
Expand Down Expand Up @@ -1993,7 +2078,7 @@ bool ConfigClass::loadDataFromNVS(std::string key, std::string &value)
// Get string length
size_t requiredSize = 0;
err = nvs_get_str(nvshandle, key.c_str(), NULL, &requiredSize);
if (err != ESP_OK) {
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "loadDataFromNVS: nvs_get_str | Key: " + key + " length | error: " + intToHexString(err));
nvs_close(nvshandle);
return false;
Expand Down
2 changes: 1 addition & 1 deletion code/components/mainprocess_ctrl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)

idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES esp_timer esp_wifi json tflite_ctrl misc_helper camera_ctrl mqtt_ctrl influxdb_ctrl fileserver_ota image_manipulation wlan_ctrl config_handling)
REQUIRES esp_timer esp_wifi json tflite_ctrl misc_helper camera_ctrl mqtt_ctrl influxdb_ctrl webhook_ctrl fileserver_ota image_manipulation wlan_ctrl config_handling)


26 changes: 26 additions & 0 deletions code/components/mainprocess_ctrl/ClassFlowControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ ClassFlowControl::ClassFlowControl()
flowInfluxDBv2 = NULL;
#endif //ENABLE_INFLUXDB

#ifdef ENABLE_WEBHOOK
flowWebhook = NULL;
#endif //ENABLE_WEBHOOK

setActualProcessState(std::string(FLOW_NO_TASK));
flowStateErrorInRow = 0;
flowStateDeviationInRow = 0;
Expand Down Expand Up @@ -202,6 +206,17 @@ bool ClassFlowControl::initFlow()
}
#endif //ENABLE_INFLUXDB

#ifdef ENABLE_WEBHOOK
if (cfgClassPtr->get()->sectionWebhook.enabled) {
flowWebhook = new ClassFlowWebhook(flowalignment);
FlowControlPublish.push_back(flowWebhook);
if (!flowWebhook->loadParameter()) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "Webhook: Init failed");
retVal = false;
}
}
#endif

// Load parameter handled in this class
this->loadParameter();

Expand All @@ -217,6 +232,12 @@ void ClassFlowControl::deinitFlow(void)
LogFile.writeToFile(ESP_LOG_DEBUG, TAG, "Deinit flow");
//LogFile.writeHeapInfo("deinitFlow start");

#ifdef ENABLE_WEBHOOK
delete flowWebhook;
flowWebhook = NULL;
//LogFile.writeHeapInfo("After WEBHOOK");
#endif //ENABLE_WEBHOOK

#ifdef ENABLE_INFLUXDB
delete flowInfluxDBv2;
flowInfluxDBv2 = NULL;
Expand Down Expand Up @@ -312,6 +333,7 @@ bool ClassFlowControl::doFlowImageEvaluation(std::string time)
LogFile.writeToFile(ESP_LOG_WARN, TAG, "Process deviation in state: \"" + getActualProcessState() + "\"");
flowStateDeviationInRow++;
flowStateErrorInRow = 0;
break;
}
}

Expand Down Expand Up @@ -356,6 +378,7 @@ bool ClassFlowControl::doFlowPublishData(std::string time)
LogFile.writeToFile(ESP_LOG_WARN, TAG, "Process deviation in state: \"" + getActualProcessState() + "\"");
flowStateDeviationInRow++;
flowStateErrorInRow = 0;
break;
}
}

Expand Down Expand Up @@ -527,6 +550,9 @@ std::string ClassFlowControl::translateActualProcessState(std::string classname)
else if (classname.compare("ClassFlowInfluxDBv2") == 0) {
return std::string(FLOW_PUBLISH_INFLUXDB2);
}
else if (classname.compare("ClassFlowWebhook") == 0) {
return std::string(FLOW_PUBLISH_WEBHOOK);
}
#endif //ENABLE_INFLUXDB
else {
return "Unkown State (" + classname + ")";
Expand Down
7 changes: 7 additions & 0 deletions code/components/mainprocess_ctrl/ClassFlowControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include "ClassFlowInfluxDBv2.h"
#endif // ENABLE_INFLUXDB

#ifdef ENABLE_WEBHOOK
#include "ClassFlowWebhook.h"
#endif // ENABLE_WEBHOOK


enum flowStateEvent {
MULTIPLE_ERROR_IN_ROW = -2,
Expand Down Expand Up @@ -51,6 +55,9 @@ class ClassFlowControl : public ClassFlow
ClassFlowInfluxDBv1 *flowInfluxDBv1;
ClassFlowInfluxDBv2 *flowInfluxDBv2;
#endif // ENABLE_INFLUXDB
#ifdef ENABLE_WEBHOOK
ClassFlowWebhook *flowWebhook;
#endif // ENABLE_WEBHOOK

std::string actualProcessState;
std::string actualProcessStateWithTime;
Expand Down
Loading

0 comments on commit ea52a5c

Please sign in to comment.