add optional ImageUpload for Webhook (#3174)

* WIP add Webhook

* fix config html for webhook
add tooltips for webhook

* webhook: fix not enabling webhook

* send webhook as json

* Update ApiKey.md

* webhook: fix only sending last "Number"

* webhook JSON is now closer to the data log in CSV format

* webhook: add img upload

* webhoop added config for imgupload

* webhook html fixes

* webhook: drop timeStampTimeUTC and switch from timeStamp to lastvalue like lokal csv to fix no timestamp on error

* add checkbox for Webhook_UploadImg

* Update sd-card/html/edit_config_template.html

* Update edit_config_template.html

* Update edit_config_template.html

* Update edit_config_template.html

* added a long timestamp to both webhook requests

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
This commit is contained in:
Raphael Hehl
2024-09-01 08:33:46 +02:00
committed by GitHub
parent 3a34564ee2
commit cd29690b96
8 changed files with 117 additions and 8 deletions

View File

@@ -8,6 +8,7 @@
#include "interface_webhook.h" #include "interface_webhook.h"
#include "ClassFlowPostProcessing.h" #include "ClassFlowPostProcessing.h"
#include "ClassFlowAlignment.h"
#include "esp_log.h" #include "esp_log.h"
#include "../../include/defines.h" #include "../../include/defines.h"
@@ -20,11 +21,13 @@ static const char* TAG = "WEBHOOK";
void ClassFlowWebhook::SetInitialParameter(void) void ClassFlowWebhook::SetInitialParameter(void)
{ {
uri = ""; uri = "";
flowpostprocessing = NULL; flowpostprocessing = NULL;
flowAlignment = NULL;
previousElement = NULL; previousElement = NULL;
ListFlowControll = NULL; ListFlowControll = NULL;
disabled = false; disabled = false;
WebhookEnable = false; WebhookEnable = false;
WebhookUploadImg = 0;
} }
ClassFlowWebhook::ClassFlowWebhook() ClassFlowWebhook::ClassFlowWebhook()
@@ -43,6 +46,11 @@ ClassFlowWebhook::ClassFlowWebhook(std::vector<ClassFlow*>* lfc)
{ {
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i]; flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
} }
if (((*ListFlowControll)[i])->name().compare("ClassFlowAlignment") == 0)
{
flowAlignment = (ClassFlowAlignment*) (*ListFlowControll)[i];
}
} }
} }
@@ -59,6 +67,10 @@ ClassFlowWebhook::ClassFlowWebhook(std::vector<ClassFlow*>* lfc, ClassFlow *_pre
{ {
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i]; flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
} }
if (((*ListFlowControll)[i])->name().compare("ClassFlowAlignment") == 0)
{
flowAlignment = (ClassFlowAlignment*) (*ListFlowControll)[i];
}
} }
} }
@@ -93,6 +105,16 @@ bool ClassFlowWebhook::ReadParameter(FILE* pfile, string& aktparamgraph)
{ {
this->apikey = splitted[1]; this->apikey = splitted[1];
} }
if (((toUpper(_param) == "UPLOADIMG")) && (splitted.size() > 1))
{
if (toUpper(splitted[1]) == "1")
{
this->WebhookUploadImg = 1;
} else if (toUpper(splitted[1]) == "2")
{
this->WebhookUploadImg = 2;
}
}
} }
WebhookInit(uri,apikey); WebhookInit(uri,apikey);
@@ -135,7 +157,13 @@ bool ClassFlowWebhook::doFlow(string zwtime)
if (flowpostprocessing) if (flowpostprocessing)
{ {
printf("vor sende WebHook"); printf("vor sende WebHook");
WebhookPublish(flowpostprocessing->GetNumbers()); bool numbersWithError = WebhookPublish(flowpostprocessing->GetNumbers());
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
if ((WebhookUploadImg == 1 || (WebhookUploadImg != 0 && numbersWithError)) && flowAlignment && flowAlignment->AlgROI) {
WebhookUploadPic(flowAlignment->AlgROI);
}
#endif
} }
return true; return true;

View File

@@ -8,6 +8,7 @@
#include "ClassFlow.h" #include "ClassFlow.h"
#include "ClassFlowPostProcessing.h" #include "ClassFlowPostProcessing.h"
#include "ClassFlowAlignment.h"
#include <string> #include <string>
@@ -16,8 +17,11 @@ class ClassFlowWebhook :
{ {
protected: protected:
std::string uri, apikey; std::string uri, apikey;
ClassFlowPostProcessing* flowpostprocessing; ClassFlowPostProcessing* flowpostprocessing;
ClassFlowAlignment* flowAlignment;
bool WebhookEnable; bool WebhookEnable;
int WebhookUploadImg;
void SetInitialParameter(void); void SetInitialParameter(void);

View File

@@ -15,6 +15,7 @@ static const char *TAG = "WEBHOOK";
std::string _webhookURI; std::string _webhookURI;
std::string _webhookApiKey; std::string _webhookApiKey;
long _lastTimestamp;
static esp_err_t http_event_handler(esp_http_client_event_t *evt); static esp_err_t http_event_handler(esp_http_client_event_t *evt);
@@ -22,22 +23,27 @@ void WebhookInit(std::string _uri, std::string _apiKey)
{ {
_webhookURI = _uri; _webhookURI = _uri;
_webhookApiKey = _apiKey; _webhookApiKey = _apiKey;
_lastTimestamp = 0L;
} }
void WebhookPublish(std::vector<NumberPost*>* numbers) bool WebhookPublish(std::vector<NumberPost*>* numbers)
{ {
bool numbersWithError = false;
cJSON *jsonArray = cJSON_CreateArray(); cJSON *jsonArray = cJSON_CreateArray();
for (int i = 0; i < (*numbers).size(); ++i) for (int i = 0; i < (*numbers).size(); ++i)
{ {
string timezw = ""; string timezw = "";
char buffer[80]; char buffer[80];
struct tm* timeinfo = localtime(&(*numbers)[i]->timeStampLastPreValue); time_t &lastPreValue = (*numbers)[i]->timeStampLastPreValue;
struct tm* timeinfo = localtime(&lastPreValue);
_lastTimestamp = static_cast<long>(lastPreValue);
strftime(buffer, 80, PREVALUE_TIME_FORMAT_OUTPUT, timeinfo); strftime(buffer, 80, PREVALUE_TIME_FORMAT_OUTPUT, timeinfo);
timezw = std::string(buffer); timezw = std::string(buffer);
cJSON *json = cJSON_CreateObject(); cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "timestamp", timezw.c_str()); cJSON_AddStringToObject(json, "timestamp", timezw.c_str());
cJSON_AddStringToObject(json, "timestampLong", std::to_string(_lastTimestamp).c_str());
cJSON_AddStringToObject(json, "name", (*numbers)[i]->name.c_str()); cJSON_AddStringToObject(json, "name", (*numbers)[i]->name.c_str());
cJSON_AddStringToObject(json, "rawValue", (*numbers)[i]->ReturnRawValue.c_str()); cJSON_AddStringToObject(json, "rawValue", (*numbers)[i]->ReturnRawValue.c_str());
cJSON_AddStringToObject(json, "value", (*numbers)[i]->ReturnValue.c_str()); cJSON_AddStringToObject(json, "value", (*numbers)[i]->ReturnValue.c_str());
@@ -47,6 +53,10 @@ void WebhookPublish(std::vector<NumberPost*>* numbers)
cJSON_AddStringToObject(json, "error", (*numbers)[i]->ErrorMessageText.c_str()); cJSON_AddStringToObject(json, "error", (*numbers)[i]->ErrorMessageText.c_str());
cJSON_AddItemToArray(jsonArray, json); cJSON_AddItemToArray(jsonArray, json);
if ((*numbers)[i]->ErrorMessage) {
numbersWithError = true;
}
} }
char *jsonString = cJSON_PrintUnformatted(jsonArray); char *jsonString = cJSON_PrintUnformatted(jsonArray);
@@ -79,13 +89,51 @@ void WebhookPublish(std::vector<NumberPost*>* numbers)
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code: " + std::to_string(status_code)); LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code: " + std::to_string(status_code));
} else { } else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "HTTP request failed"); LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "HTTP request failed");
} }
esp_http_client_cleanup(http_client); esp_http_client_cleanup(http_client);
cJSON_Delete(jsonArray); cJSON_Delete(jsonArray);
free(jsonString); free(jsonString);
return numbersWithError;
} }
void WebhookUploadPic(ImageData *Img) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Starting WebhookUploadPic");
std::string fullURI = _webhookURI + "?timestamp=" + std::to_string(_lastTimestamp);
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
esp_http_client_config_t http_config = {
.url = fullURI.c_str(),
.user_agent = "ESP32 Meter reader",
.method = HTTP_METHOD_PUT,
.event_handler = http_event_handler,
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
.user_data = response_buffer
};
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
esp_http_client_set_header(http_client, "Content-Type", "image/jpeg");
esp_http_client_set_header(http_client, "APIKEY", _webhookApiKey.c_str());
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_set_post_field(http_client, (const char *)Img->data, Img->size));
err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
if (err == ESP_OK) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP PUT request was performed successfully");
int status_code = esp_http_client_get_status_code(http_client);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code: " + std::to_string(status_code));
} else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "HTTP PUT request failed");
}
esp_http_client_cleanup(http_client);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "WebhookUploadPic finished");
}
static esp_err_t http_event_handler(esp_http_client_event_t *evt) static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{ {
switch(evt->event_id) switch(evt->event_id)

View File

@@ -10,7 +10,8 @@
#include <ClassFlowDefineTypes.h> #include <ClassFlowDefineTypes.h>
void WebhookInit(std::string _webhookURI, std::string _apiKey); void WebhookInit(std::string _webhookURI, std::string _apiKey);
void WebhookPublish(std::vector<NumberPost*>* numbers); bool WebhookPublish(std::vector<NumberPost*>* numbers);
void WebhookUploadPic(ImageData *Img);
#endif //INTERFACE_WEBHOOK_H #endif //INTERFACE_WEBHOOK_H
#endif //ENABLE_WEBHOOK #endif //ENABLE_WEBHOOK

View File

@@ -0,0 +1,8 @@
# Parameter `UploadImg`
Default Value: `0` (`NEVER`)
Available options:
- `0`: `NEVER`
- `1`: `ALWAYS`
- `2`: `ON_ERROR`

View File

@@ -108,6 +108,7 @@ HomeassistantDiscovery = false
;[Webhook] ;[Webhook]
;Uri = undefined ;Uri = undefined
;ApiKey = undefined ;ApiKey = undefined
;UploadImg = 0
;[GPIO] ;[GPIO]
;MainTopicMQTT = wasserzaehler/GPIO ;MainTopicMQTT = wasserzaehler/GPIO

View File

@@ -1353,7 +1353,8 @@
<td colspan="3" style="padding-left: 0px; padding-bottom: 3px;"> <td colspan="3" style="padding-left: 0px; padding-bottom: 3px;">
<h4> <h4>
<input type="checkbox" id="Category_Webhook_enabled" value="1" onclick = 'UpdateAfterCategoryCheck()' unchecked > <input type="checkbox" id="Category_Webhook_enabled" value="1" onclick = 'UpdateAfterCategoryCheck()' unchecked >
<label for=Category_Webhook_enabled>Webhook</label></h4> <label for=Category_Webhook_enabled>Webhook</label>
</h4>
</td> </td>
</tr> </tr>
@@ -1379,6 +1380,20 @@
<td>$TOOLTIP_Webhook_ApiKey</td> <td>$TOOLTIP_Webhook_ApiKey</td>
</tr> </tr>
<tr class="WebhookItem">
<td class="indent1">
<input type="checkbox" id="Webhook_UploadImg_enabled" value="1" onclick = 'InvertEnableItem("Webhook", "UploadImg")' unchecked>
<label for=Webhook_UploadImg_enabled><class id="Webhook_UploadImg_text" style="color:black;">Upload Image</class></label>
</td>
<td>
<select id="Webhook_UploadImg_value1">
<option value="0" selected>NEVER</option>
<option value="1">ALWAYS</option>
<option value="2">ON_ERROR</option>
</select>
</td>
<td>$TOOLTIP_Webhook_UploadImg</td>
</tr>
<!------------- GPIO ------------------> <!------------- GPIO ------------------>
<tr style="border-bottom: 2px solid lightgray;"> <tr style="border-bottom: 2px solid lightgray;">
@@ -2310,6 +2325,7 @@ function UpdateInput() {
WriteParameter(param, category, "Webhook", "Uri", true); WriteParameter(param, category, "Webhook", "Uri", true);
WriteParameter(param, category, "Webhook", "ApiKey", true); WriteParameter(param, category, "Webhook", "ApiKey", true);
WriteParameter(param, category, "Webhook", "UploadImg", true);
WriteParameter(param, category, "GPIO", "IO0", true); WriteParameter(param, category, "GPIO", "IO0", true);
WriteParameter(param, category, "GPIO", "IO1", true); WriteParameter(param, category, "GPIO", "IO1", true);
@@ -2391,6 +2407,7 @@ function ReadParameterAll() {
category["MQTT"]["enabled"] = document.getElementById("Category_MQTT_enabled").checked; category["MQTT"]["enabled"] = document.getElementById("Category_MQTT_enabled").checked;
category["InfluxDB"]["enabled"] = document.getElementById("Category_InfluxDB_enabled").checked; category["InfluxDB"]["enabled"] = document.getElementById("Category_InfluxDB_enabled").checked;
category["InfluxDBv2"]["enabled"] = document.getElementById("Category_InfluxDBv2_enabled").checked; category["InfluxDBv2"]["enabled"] = document.getElementById("Category_InfluxDBv2_enabled").checked;
category["Webhook"]["enabled"] = document.getElementById("Category_Webhook_enabled").checked;
category["GPIO"]["enabled"] = document.getElementById("Category_GPIO_enabled").checked; category["GPIO"]["enabled"] = document.getElementById("Category_GPIO_enabled").checked;
ReadParameter(param, "TakeImage", "RawImagesLocation", true); ReadParameter(param, "TakeImage", "RawImagesLocation", true);
@@ -2474,6 +2491,7 @@ function ReadParameterAll() {
ReadParameter(param, "Webhook", "Uri", true); ReadParameter(param, "Webhook", "Uri", true);
ReadParameter(param, "Webhook", "ApiKey", true); ReadParameter(param, "Webhook", "ApiKey", true);
ReadParameter(param, "Webhook", "UploadImg", true);
ReadParameter(param, "GPIO", "IO0", true); ReadParameter(param, "GPIO", "IO0", true);
ReadParameter(param, "GPIO", "IO1", true); ReadParameter(param, "GPIO", "IO1", true);

View File

@@ -237,6 +237,7 @@ function ParseConfig() {
param[catname] = new Object(); param[catname] = new Object();
ParamAddValue(param, catname, "Uri"); ParamAddValue(param, catname, "Uri");
ParamAddValue(param, catname, "ApiKey"); ParamAddValue(param, catname, "ApiKey");
ParamAddValue(param, catname, "UploadImg");
var catname = "GPIO"; var catname = "GPIO";
category[catname] = new Object(); category[catname] = new Object();