Files
OpenBK7231T_App/src/httpserver/http_fns.c
2025-11-30 20:26:22 +01:00

3470 lines
118 KiB
C

#include "../new_common.h"
#include "http_fns.h"
#include "../new_pins.h"
#include "../new_cfg.h"
#include "../hal/hal_ota.h"
// Commands register, execution API and cmd tokenizer
#include "../cmnds/cmd_public.h"
#include "../cmnds/cmd_enums.h"
#include "../driver/drv_tuyaMCU.h"
#include "../driver/drv_girierMCU.h"
#include "../driver/drv_public.h"
#include "../driver/drv_bl_shared.h"
#include "../hal/hal_wifi.h"
#include "../hal/hal_pins.h"
#include "../hal/hal_flashConfig.h"
#include "../logging/logging.h"
#include "../devicegroups/deviceGroups_public.h"
#include "../mqtt/new_mqtt.h"
#include "hass.h"
#include "../cJSON/cJSON.h"
#include <time.h>
#include "../driver/drv_ntp.h"
#include "../driver/drv_local.h"
#ifdef PLATFORM_BEKEN
#include "start_type_pub.h"
#endif
#ifdef WINDOWS
// nothing
#elif PLATFORM_BL602
#include <bl_sys.h>
#include <bl_adc.h> // For BL602 ADC HAL
#include <bl602_adc.h> // For BL602 ADC Standard Driver
#include <bl602_glb.h> // For BL602 Global Register Standard Driver
#include <wifi_mgmr_ext.h> //For BL602 WiFi AP Scan
#elif PLATFORM_W600 || PLATFORM_W800
#elif PLATFORM_XRADIO
#include <image/flash.h>
#include <ota/ota.h>
#elif defined(PLATFORM_BK7231N)
// tuya-iotos-embeded-sdk-wifi-ble-bk7231n/sdk/include/tuya_hal_storage.h
#include "tuya_hal_storage.h"
#include "BkDriverFlash.h"
#include "temp_detect_pub.h"
#elif defined(PLATFORM_LN882H)
#elif defined(PLATFORM_TR6260)
#elif defined(PLATFORM_REALTEK) && !PLATFORM_REALTEK_NEW
#include "wifi_structures.h"
#include "wifi_constants.h"
#include "wifi_conf.h"
extern uint32_t current_fw_idx;
#ifdef PLATFORM_RTL87X0C
#include "hal_sys_ctrl.h"
extern hal_reset_reason_t reset_reason;
#endif
SemaphoreHandle_t scan_hdl;
#elif PLATFORM_REALTEK_NEW
#include "lwip_netconf.h"
#include "ameba_soc.h"
#include "ameba_ota.h"
extern uint32_t current_fw_idx;
#elif defined(PLATFORM_ESPIDF) || PLATFORM_ESP8266
#include "esp_wifi.h"
#include "esp_system.h"
#elif defined(PLATFORM_BK7231T)
// REALLY? A typo in Tuya SDK? Storge?
// tuya-iotos-embeded-sdk-wifi-ble-bk7231t/platforms/bk7231t/tuya_os_adapter/include/driver/tuya_hal_storge.h
#include "tuya_hal_storge.h"
#include "BkDriverFlash.h"
#include "temp_detect_pub.h"
#elif defined(PLATFORM_ECR6600)
#include "hal_system.h"
#endif
#if (defined(PLATFORM_BK7231T) || defined(PLATFORM_BK7231N)) && !defined(PLATFORM_BEKEN_NEW)
int tuya_os_adapt_wifi_all_ap_scan(AP_IF_S** ap_ary, unsigned int* num);
int tuya_os_adapt_wifi_release_ap(AP_IF_S* ap);
#endif
static const char SUBMIT_AND_END_FORM[] = "<br><input type=\"submit\" value=\"Submit\"></form>";
const char* g_typesOffLowMidHigh[] = { "Off","Low","Mid","High" };
const char* g_typesOffLowMidHighHighest[] = { "Off", "Low","Mid","High","Highest" };
const char* g_typesOffLowestLowMidHighHighest[] = { "Off", "Lowest", "Low", "Mid", "High", "Highest" };
const char* g_typesLowMidHighHighest[] = { "Low","Mid","High","Highest" };
const char* g_typesOffOnRemember[] = { "Off", "On", "Remember" };
const char* g_typeLowMidHigh[] = { "Low","Mid","High" };
const char* g_typesLowestLowMidHighHighest[] = { "Lowest", "Low", "Mid", "High", "Highest" };;
const char* g_typeOpenStopClose[] = { "Open","Stop","Close" };
const char* g_typeStopUpDown[] = { "Stop","Up","Down" };
#define ADD_OPTION(t,a) if(type == t) { *numTypes = sizeof(a)/sizeof(a[0]); return a; }
const char **Channel_GetOptionsForChannelType(int type, int *numTypes) {
ADD_OPTION(ChType_OffLowMidHigh, g_typesOffLowMidHigh);
ADD_OPTION(ChType_OffLowestLowMidHighHighest, g_typesOffLowestLowMidHighHighest);
ADD_OPTION(ChType_LowestLowMidHighHighest, g_typesLowestLowMidHighHighest);
ADD_OPTION(ChType_OffLowMidHighHighest, g_typesOffLowMidHighHighest);
ADD_OPTION(ChType_LowMidHighHighest, g_typesLowMidHighHighest);
ADD_OPTION(ChType_OffOnRemember, g_typesOffOnRemember);
ADD_OPTION(ChType_LowMidHigh, g_typeLowMidHigh);
ADD_OPTION(ChType_OpenStopClose, g_typeOpenStopClose);
ADD_OPTION(ChType_StopUpDown, g_typeStopUpDown);
*numTypes = 0;
return 0;
}
unsigned char hexdigit(char hex) {
return (hex <= '9') ? hex - '0' :
toupper((unsigned char)hex) - 'A' + 10;
}
unsigned char hexbyte(const char* hex) {
return (hexdigit(*hex) << 4) | hexdigit(*(hex + 1));
}
int http_fn_empty_url(http_request_t* request) {
poststr(request, "HTTP/1.1 302 OK\nLocation: /index\nConnection: close\n\n");
poststr(request, NULL);
return 0;
}
void postFormAction(http_request_t* request, char* action, char* value) {
//"<form action=\"cfg_pins\"><input type=\"submit\" value=\"Configure Module\"/></form>"
hprintf255(request, "<form action=\"%s\"><input type=\"submit\" value=\"%s\"/></form>", action, value);
}
void poststr_h2(http_request_t* request, const char* content) {
hprintf255(request, "<h2>%s</h2>", content);
}
void poststr_h4(http_request_t* request, const char* content) {
hprintf255(request, "<h4>%s</h4>", content);
}
/// @brief Generate a pair of label and field elements for Name type entry. The field is limited to entry of a-zA-Z0-9_- characters.
/// @param request
/// @param label
/// @param fieldId This also gets used as the field name
/// @param value
/// @param preContent
void add_label_name_field(http_request_t* request, char* label, char* fieldId, const char* value, char* preContent) {
if (strlen(preContent) > 0) {
poststr(request, preContent);
}
hprintf255(request, "<label for=\"%s\">%s:</label><br>", fieldId, label);
hprintf255(request, "<input type=\"text\" id=\"%s\" name=\"%s\" value=\"%s\" ", fieldId, fieldId, value);
poststr(request, "pattern=\"^[a-zA-Z0-9_-]+$\" title=\"Only alphanumerics, underscore and hyphen characters allowed.\">");
}
/// @brief Generate a pair of label and field elements.
/// @param request
/// @param label
/// @param fieldId This also gets used as the field name
/// @param value
/// @param preContent
void add_label_input(http_request_t* request, char* inputType, char* label, char* fieldId, const char* value, char* preContent) {
if (strlen(preContent) > 0) {
poststr(request, preContent);
}
hprintf255(request, "<label for=\"%s\">%s:</label><br>", fieldId, label);
hprintf255(request, "<input type=\"%s\" id=\"%s\" name=\"%s\" value=\"", inputType, fieldId, fieldId);
poststr(request, value);
hprintf255(request, "\">");
}
/// @brief Generates a pair of label and text field elements.
/// @param request
/// @param label Label for the field
/// @param fieldId Field id, this also gets used as the name
/// @param value String value
/// @param preContent Content before the label
void add_label_text_field(http_request_t* request, char* label, char* fieldId, const char* value, char* preContent) {
add_label_input(request, "text", label, fieldId, value, preContent);
}
/// @brief Generates a pair of label and text field elements.
/// @param request
/// @param label Label for the field
/// @param fieldId Field id, this also gets used as the name
/// @param value String value
/// @param preContent Content before the label
void add_label_password_field(http_request_t* request, char* label, char* fieldId, const char* value, char* preContent) {
add_label_input(request, "password", label, fieldId, value, preContent);
}
/// @brief Generate a pair of label and numeric field elements.
/// @param request
/// @param label Label for the field
/// @param fieldId Field id, this also gets used as the name
/// @param value Integer value
/// @param preContent Content before the label
void add_label_numeric_field(http_request_t* request, char* label, char* fieldId, int value, char* preContent) {
char strValue[32];
sprintf(strValue, "%i", value);
add_label_input(request, "number", label, fieldId, strValue, preContent);
}
int http_fn_testmsg(http_request_t* request) {
poststr(request, "This is just a test msg\n\n");
poststr(request, NULL);
return 0;
}
// bit mask telling which channels are hidden from HTTP
// If given bit is set, then given channel is hidden
extern int g_hiddenChannels;
int http_fn_index(http_request_t* request) {
int j, i, ch1, ch2;
char tmpA[128];
int bRawPWMs;
bool bForceShowRGBCW;
float fValue;
int iValue;
bool bForceShowRGB;
const char* inputName;
int channelType;
bRawPWMs = CFG_HasFlag(OBK_FLAG_LED_RAWCHANNELSMODE);
bForceShowRGBCW = CFG_HasFlag(OBK_FLAG_LED_FORCESHOWRGBCWCONTROLLER);
bForceShowRGB = CFG_HasFlag(OBK_FLAG_LED_FORCE_MODE_RGB);
// user override is always stronger, so if no override set
if (bForceShowRGB == false && bForceShowRGBCW == false) {
#ifndef OBK_DISABLE_ALL_DRIVERS
if (DRV_IsRunning("SM16703P")) {
bForceShowRGB = true;
}
if (DRV_IsRunning("DMX")) {
bForceShowRGB = true;
}
#endif
}
http_setup(request, httpMimeTypeHTML); //Add mimetype regardless of the request
// use ?state URL parameter to only request current state
if (!http_getArg(request->url, "state", tmpA, sizeof(tmpA))) {
// full update - include header
http_html_start(request, NULL);
poststr(request, "<div id=\"changed\">");
#if defined(PLATFORM_BEKEN) || defined(WINDOWS)
if (DRV_IsRunning("PWMToggler")) {
DRV_Toggler_ProcessChanges(request);
}
#endif
#if defined(PLATFORM_BEKEN) || defined(WINDOWS)
if (DRV_IsRunning("httpButtons")) {
DRV_HTTPButtons_ProcessChanges(request);
}
#endif
if (http_getArg(request->url, "tgl", tmpA, sizeof(tmpA))) {
j = atoi(tmpA);
if (j == SPECIAL_CHANNEL_LEDPOWER) {
hprintf255(request, "<h3>Toggled LED power!</h3>", j);
}
else {
hprintf255(request, "<h3>Toggled %s!</h3>", CHANNEL_GetLabel(j));
}
CHANNEL_Toggle(j);
}
if (http_getArg(request->url, "on", tmpA, sizeof(tmpA))) {
j = atoi(tmpA);
hprintf255(request, "<h3>Enabled %s!</h3>", CHANNEL_GetLabel(j));
CHANNEL_Set(j, 255, 1);
}
#if ENABLE_LED_BASIC
if (http_getArg(request->url, "rgb", tmpA, sizeof(tmpA))) {
hprintf255(request, "<h3>Set RGB to %s!</h3>", tmpA);
LED_SetBaseColor(0, "led_basecolor", tmpA, 0);
// auto enable - but only for changes made from WWW panel
// This happens when users changes COLOR
if (CFG_HasFlag(OBK_FLAG_LED_AUTOENABLE_ON_WWW_ACTION)) {
LED_SetEnableAll(true);
}
}
#endif
if (http_getArg(request->url, "off", tmpA, sizeof(tmpA))) {
j = atoi(tmpA);
hprintf255(request, "<h3>Disabled %s!</h3>", CHANNEL_GetLabel(j));
CHANNEL_Set(j, 0, 1);
}
if (http_getArg(request->url, "pwm", tmpA, sizeof(tmpA))) {
int newPWMValue = atoi(tmpA);
http_getArg(request->url, "pwmIndex", tmpA, sizeof(tmpA));
j = atoi(tmpA);
if (j == SPECIAL_CHANNEL_TEMPERATURE) {
hprintf255(request, "<h3>Changed Temperature to %i!</h3>", newPWMValue);
}
else {
hprintf255(request, "<h3>Changed pwm %i to %i!</h3>", j, newPWMValue);
}
CHANNEL_Set(j, newPWMValue, 1);
#if ENABLE_LED_BASIC
if (j == SPECIAL_CHANNEL_TEMPERATURE) {
// auto enable - but only for changes made from WWW panel
// This happens when users changes TEMPERATURE
if (CFG_HasFlag(OBK_FLAG_LED_AUTOENABLE_ON_WWW_ACTION)) {
LED_SetEnableAll(true);
}
}
#endif
}
if (http_getArg(request->url, "dim", tmpA, sizeof(tmpA))) {
int newDimmerValue = atoi(tmpA);
http_getArg(request->url, "dimIndex", tmpA, sizeof(tmpA));
j = atoi(tmpA);
if (j == SPECIAL_CHANNEL_BRIGHTNESS) {
hprintf255(request, "<h3>Changed LED brightness to %i!</h3>", newDimmerValue);
}
else {
hprintf255(request, "<h3>Changed dimmer %i to %i!</h3>", j, newDimmerValue);
}
CHANNEL_Set(j, newDimmerValue, 1);
#if ENABLE_LED_BASIC
if (j == SPECIAL_CHANNEL_BRIGHTNESS) {
// auto enable - but only for changes made from WWW panel
// This happens when users changes DIMMER
if (CFG_HasFlag(OBK_FLAG_LED_AUTOENABLE_ON_WWW_ACTION)) {
LED_SetEnableAll(true);
}
}
#endif
}
if (http_getArg(request->url, "set", tmpA, sizeof(tmpA))) {
int newSetValue = atoi(tmpA);
http_getArg(request->url, "setIndex", tmpA, sizeof(tmpA));
j = atoi(tmpA);
hprintf255(request, "<h3>Changed channel %s to %i!</h3>", CHANNEL_GetLabel(j), newSetValue);
CHANNEL_Set(j, newSetValue, 1);
}
if (http_getArg(request->url, "restart", tmpA, sizeof(tmpA))) {
poststr(request, "<h5> Module will restart soon</h5>");
RESET_ScheduleModuleReset(3);
}
if (http_getArg(request->url, "unsafe", tmpA, sizeof(tmpA))) {
poststr(request, "<h5> Will try to do unsafe init in few seconds</h5>");
MAIN_ScheduleUnsafeInit(3);
}
poststr(request, "</div>"); // end div#change
#if ENABLE_OBK_BERRY
void Berry_SaveRequest(http_request_t *r);
Berry_SaveRequest(request);
CMD_Berry_RunEventHandlers_StrPtr(CMD_EVENT_ON_HTTP, "prestate", request);
#endif
#ifndef OBK_DISABLE_ALL_DRIVERS
DRV_AppendInformationToHTTPIndexPage(request, true);
#endif
poststr(request, "<div id=\"state\">"); // replaceable content follows
}
#if ENABLE_OBK_BERRY
void Berry_SaveRequest(http_request_t *r);
Berry_SaveRequest(request);
CMD_Berry_RunEventHandlers_StrPtr(CMD_EVENT_ON_HTTP, "state", request);
#endif
if (!CFG_HasFlag(OBK_FLAG_HTTP_NO_ONOFF_WORDS)){
poststr(request, "<table>"); //Table default to 100% width in stylesheet
for (i = 0; i < CHANNEL_MAX; i++) {
channelType = CHANNEL_GetType(i);
// check ability to hide given channel from gui
if (BIT_CHECK(g_hiddenChannels, i)) {
continue; // hidden
}
bool bToggleInv = channelType == ChType_Toggle_Inv;
if (h_isChannelRelay(i) || channelType == ChType_Toggle) {
if (i <= 1) {
hprintf255(request, "<tr>");
}
if (CHANNEL_Check(i) != bToggleInv) {
poststr(request, "<td class='on'>ON</td>");
}
else {
poststr(request, "<td class='off'>OFF</td>");
}
if (i == CHANNEL_MAX - 1) {
poststr(request, "</tr>");
}
}
}
poststr(request, "</table>");
}
poststr(request, "<table>"); //Table default to 100% width in stylesheet
for (i = 0; i < CHANNEL_MAX; i++) {
// check ability to hide given channel from gui
if (BIT_CHECK(g_hiddenChannels, i)) {
continue; // hidden
}
channelType = CHANNEL_GetType(i);
bool bToggleInv = channelType == ChType_Toggle_Inv;
if (h_isChannelRelay(i) || channelType == ChType_Toggle || bToggleInv) {
const char* c;
const char* prefix;
if (i <= 1) {
hprintf255(request, "<tr>");
}
if (CHANNEL_Check(i) != bToggleInv) {
c = "bgrn";
}
else {
c = "bred";
}
poststr(request, "<td><form action=\"index\">");
hprintf255(request, "<input type=\"hidden\" name=\"tgl\" value=\"%i\">", i);
if (CHANNEL_ShouldAddTogglePrefixToUI(i)) {
prefix = "Toggle ";
}
else {
prefix = "";
}
hprintf255(request, "<input class=\"%s\" type=\"submit\" value=\"%s%s\"/></form></td>", c, prefix, CHANNEL_GetLabel(i));
if (i == CHANNEL_MAX - 1) {
poststr(request, "</tr>");
}
}
}
poststr(request, "</table>");
poststr(request, "<table>"); //Table default to 100% width in stylesheet
for (i = 0; i < PLATFORM_GPIO_MAX; i++) {
int role;
role = PIN_GetPinRoleForPinIndex(i);
if (IS_PIN_DHT_ROLE(role)) {
// DHT pin has two channels - temperature and humidity
poststr(request, "<tr><td>");
ch1 = PIN_GetPinChannelForPinIndex(i);
ch2 = PIN_GetPinChannel2ForPinIndex(i);
iValue = CHANNEL_Get(ch1);
hprintf255(request, "Sensor %s on pin %i temperature %.2fC", PIN_RoleToString(role), i, (float)(iValue * 0.1f));
iValue = CHANNEL_Get(ch2);
hprintf255(request, ", humidity %.1f%%<br>", (float)iValue);
if (ch1 == ch2) {
hprintf255(request, "WARNING: you have the same channel set twice for DHT, please fix in pins config, set two different channels");
}
poststr(request, "</td></tr>");
}
}
for (i = 0; i < CHANNEL_MAX; i++) {
const char **types;
int numTypes;
// check ability to hide given channel from gui
if (BIT_CHECK(g_hiddenChannels, i)) {
continue; // hidden
}
channelType = CHANNEL_GetType(i);
if (channelType == ChType_TimerSeconds) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "Timer Channel %s value ", CHANNEL_GetLabel(i));
if (iValue < 60) {
hprintf255(request, "%i seconds<br>", iValue);
}
else if (iValue < 3600) {
int minutes = iValue / 60;
int seconds = iValue % 60;
hprintf255(request, "%i minutes %i seconds<br>", minutes, seconds);
}
else {
int hours = iValue / 3600;
int remainingSeconds = iValue % 3600;
int minutes = remainingSeconds / 60;
int seconds = remainingSeconds % 60;
hprintf255(request, "%i hours %i minutes %i seconds<br>", hours, minutes, seconds);
}
poststr(request, "</td></tr>");
} else if (channelType == ChType_ReadOnlyLowMidHigh) {
const char* types[] = { "Low","Mid","High" };
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
if (iValue >= 0 && iValue <= 2) {
hprintf255(request, "Channel %s = %s", CHANNEL_GetLabel(i), types[iValue]);
}
else {
hprintf255(request, "Channel %s = %i", CHANNEL_GetLabel(i), iValue);
}
poststr(request, "</td></tr>");
} else if (channelType == ChType_Enum) {
iValue = CHANNEL_Get(i);
channelEnum_t *en;
// if setChannelEnum has not been defined, treat ChType_Enum as a textfield
if (g_enums == NULL || g_enums[i]->numOptions == 0 ) {
//en = g_enums[i];
poststr(request, "<tr><td>");
hprintf255(request, "<p>Change channel %s enum:</p><form action=\"index\">", CHANNEL_GetLabel(i));
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
hprintf255(request, "<input type=\"number\" name=\"set\" value=\"%i\" onblur=\"this.form.submit()\">", iValue);
hprintf255(request, "<input type=\"submit\" value=\"Set!\"/></form>");
hprintf255(request, "</form>");
poststr(request, "</td></tr>");
} else {
en = g_enums[i];
poststr(request, "<tr><td>");
hprintf255(request, "<form action=\"index\"><label for=\"select%i\">Channel %s Enum:</label>", i, CHANNEL_GetLabel(i));
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
hprintf255(request, "<select id=\"select%i\" name=\"set\" onchange=\"this.form.submit()\">", i);
bool found = false;
for (int o = 0; o < en->numOptions; o++) {
const char* selected;
if (en->options[o].value == iValue) {
selected = "selected";
found = true;
} else
selected = "";
hprintf255(request, "<option value=\"%i\" %s>%s [%i]</option>", en->options[o].value, selected, en->options[o].label,en->options[o].value);
}
if (!found) // create an item if no label is found
hprintf255(request, "<option value=\"%i\" selected>undefined enum [%i]</option>", iValue,iValue);
hprintf255(request, "</select></form>");
poststr(request, "</td></tr>");
}
}
else if (channelType == ChType_ReadOnlyEnum) {
iValue = CHANNEL_Get(i);
const char* oLabel;
if (g_enums == NULL || g_enums[i]->numOptions == 0)
oLabel = CHANNEL_GetLabel(i);
else
oLabel = CMD_FindChannelEnumLabel(g_enums[i], iValue);
poststr(request, "<tr><td>");
hprintf255(request, "Channel %s = %s [%i]", CHANNEL_GetLabel(i), oLabel, iValue);
poststr(request, "</td></tr>");
}
else if ((types = Channel_GetOptionsForChannelType(channelType, &numTypes)) != 0) {
const char *what;
if (channelType == ChType_OffOnRemember) {
what = "memory";
}
else if (channelType == ChType_OpenStopClose || channelType == ChType_StopUpDown) {
what = "mode";
}
else {
what = "speed";
}
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "<p>Select %s:</p><form action=\"index\">", what);
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
for (j = 0; j < numTypes; j++) {
const char* check;
if (j == iValue)
check = "checked";
else
check = "";
hprintf255(request, "<input type=\"radio\" name=\"set\" value=\"%i\" onchange=\"this.form.submit()\" %s>%s", j, check, types[j]);
}
hprintf255(request, "</form>");
poststr(request, "</td></tr>");
}
else if (channelType == ChType_TextField) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "<p>Change channel %s value:</p><form action=\"index\">", CHANNEL_GetLabel(i));
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
hprintf255(request, "<input type=\"number\" name=\"set\" value=\"%i\" onblur=\"this.form.submit()\">", iValue);
hprintf255(request, "<input type=\"submit\" value=\"Set!\"/></form>");
hprintf255(request, "</form>");
poststr(request, "</td></tr>");
}
else if (channelType == ChType_ReadOnly) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "Channel %s = %i", CHANNEL_GetLabel(i), iValue);
poststr(request, "</td></tr>");
}
else if (channelType == ChType_Motion || channelType == ChType_Motion_n) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
if (iValue == (channelType != ChType_Motion)) {
hprintf255(request, "No motion (ch %i)", i);
}
else {
hprintf255(request, "Motion! (ch %i)", i);
}
poststr(request, "</td></tr>");
}
else if (channelType == ChType_OpenClosed) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
if (iValue) {
hprintf255(request, "CLOSED (ch %i)", i);
}
else {
hprintf255(request, "OPEN (ch %i)", i);
}
poststr(request, "</td></tr>");
}
else if (channelType == ChType_OpenClosed_Inv) {
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
if (!iValue) {
hprintf255(request, "CLOSED (ch %i)", i);
}
else {
hprintf255(request, "OPEN (ch %i)", i);
}
poststr(request, "</td></tr>");
}
else if (h_isChannelRelay(i) || channelType == ChType_Toggle || channelType == ChType_Toggle_Inv) {
// HANDLED ABOVE in previous loop
}
else if ((bRawPWMs && h_isChannelPWM(i)) ||
(channelType == ChType_Dimmer) || (channelType == ChType_Dimmer256) || (channelType == ChType_Dimmer1000)
|| channelType == ChType_Percent) {
int maxValue;
// PWM and dimmer both use a slider control
inputName = h_isChannelPWM(i) ? "pwm" : "dim";
int pwmValue;
if (channelType == ChType_Dimmer256) {
maxValue = 255;
}
else if (channelType == ChType_Dimmer1000) {
maxValue = 1000;
}
else {
maxValue = 100;
}
pwmValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "Channel %s:<br><form action=\"index\" id=\"form%i\">", CHANNEL_GetLabel(i), i);
hprintf255(request, "<input type=\"range\" min=\"0\" max=\"%i\" name=\"%s\" id=\"slider%i\" value=\"%i\" onchange=\"this.form.submit()\">", maxValue, inputName, i, pwmValue);
hprintf255(request, "<input type=\"hidden\" name=\"%sIndex\" value=\"%i\">", inputName, i);
hprintf255(request, "<input type=\"submit\" class='disp-none' value=\"Toggle %s\"/></form>", CHANNEL_GetLabel(i));
poststr(request, "</td></tr>");
}
else if (channelType == ChType_OffDimBright) {
const char* types[] = { "Off","Dim","Bright" };
iValue = CHANNEL_Get(i);
poststr(request, "<tr><td>");
hprintf255(request, "<p>Select level:</p><form action=\"index\">");
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
for (j = 0; j < 3; j++) {
const char* check;
if (j == iValue)
check = "checked";
else
check = "";
hprintf255(request, "<input type=\"radio\" name=\"set\" value=\"%i\" onchange=\"this.form.submit()\" %s>%s", j, check, types[j]);
}
hprintf255(request, "</form>");
poststr(request, "</td></tr>");
}
else {
const char *channelTitle;
channelTitle = ChannelType_GetTitle(channelType);
if (*channelTitle) {
int div;
const char *channelUnit;
char formatStr[16];
strcpy(formatStr, " %.4f");
div = ChannelType_GetDivider(channelType);
channelUnit = ChannelType_GetUnit(channelType);
iValue = CHANNEL_Get(i);
fValue = (float)iValue / (float)div;
poststr(request, "<tr><td>");
poststr(request, channelTitle);
// how many decimal places?
formatStr[3] = '0'+ChannelType_GetDecimalPlaces(channelType);
hprintf255(request, formatStr, fValue);
poststr(request, channelUnit);
hprintf255(request, " (%s)", CHANNEL_GetLabel(i));
poststr(request, "</td></tr>");
}
}
}
bool bForceShowSingleDimmer = 0;
#if ENABLE_DRIVER_GOSUNDSW2
if (DRV_IsRunning("GosundSW2")) {
bForceShowSingleDimmer = 1;
}
#endif
#if ENABLE_LED_BASIC
if (bRawPWMs == 0 || bForceShowRGBCW || bForceShowRGB
|| bForceShowSingleDimmer || LED_IsLedDriverChipRunning()) {
int c_pwms;
int lm;
int c_realPwms = 0;
lm = LED_GetMode();
//c_pwms = PIN_CountPinsWithRoleOrRole(IOR_PWM, IOR_PWM_n);
// This will treat multiple PWMs on a single channel as one.
// Thanks to this users can turn for example RGB LED controller
// into high power 3-outputs single colors LED controller
PIN_get_Relay_PWM_Count(0, &c_pwms, 0);
c_realPwms = c_pwms;
if (LED_IsLedDriverChipRunning()) {
c_pwms = CFG_CountLEDRemapChannels();
}
if (bForceShowSingleDimmer) {
c_pwms = 1;
}
else if (bForceShowRGBCW) {
c_pwms = 5;
}
else if (bForceShowRGB) {
c_pwms = 3;
}
if (c_pwms > 0) {
const char* c;
if (CHANNEL_Check(SPECIAL_CHANNEL_LEDPOWER)) {
c = "bgrn";
}
else {
c = "bred";
}
poststr(request, "<tr><td>");
poststr(request, "<form action=\"index\">");
hprintf255(request, "<input type=\"hidden\" name=\"tgl\" value=\"%i\">", SPECIAL_CHANNEL_LEDPOWER);
hprintf255(request, "<input class=\"%s\" type=\"submit\" value=\"Toggle Light\"/></form>", c);
poststr(request, "</td></tr>");
}
if (c_pwms > 0) {
int pwmValue;
inputName = "dim";
pwmValue = LED_GetDimmer();
poststr(request, "<tr><td>");
hprintf255(request, "<h5>LED Dimmer/Brightness</h5>");
hprintf255(request, "<form action=\"index\" id=\"form%i\">", SPECIAL_CHANNEL_BRIGHTNESS);
hprintf255(request, "<input type=\"range\" min=\"0\" max=\"100\" name=\"%s\" id=\"slider%i\" value=\"%i\" onchange=\"this.form.submit()\">", inputName, SPECIAL_CHANNEL_BRIGHTNESS, pwmValue);
hprintf255(request, "<input type=\"hidden\" name=\"%sIndex\" value=\"%i\">", inputName, SPECIAL_CHANNEL_BRIGHTNESS);
hprintf255(request, "<input type=\"submit\" class='disp-none' value=\"Toggle %i\"/></form>", SPECIAL_CHANNEL_BRIGHTNESS);
poststr(request, "</td></tr>");
}
if (c_pwms >= 3) {
char colorValue[16];
inputName = "rgb";
const char* activeStr = "";
if (lm == Light_RGB) {
activeStr = "[ACTIVE]";
}
LED_GetBaseColorString(colorValue);
poststr(request, "<tr><td>");
hprintf255(request, "<h5>LED RGB Color %s</h5>", activeStr);
hprintf255(request, "<form action=\"index\" id=\"form%i\">", SPECIAL_CHANNEL_BASECOLOR);
// onchange would fire only if colour was changed
// onblur will fire every time
hprintf255(request, "<input type=\"color\" name=\"%s\" id=\"color%i\" value=\"#%s\" oninput=\"this.form.submit()\" >", inputName, SPECIAL_CHANNEL_BASECOLOR, colorValue);
hprintf255(request, "<input type=\"hidden\" name=\"%sIndex\" value=\"%i\">", inputName, SPECIAL_CHANNEL_BASECOLOR);
hprintf255(request, "<input type=\"submit\" class='disp-none' value=\"Toggle Light\"/></form>");
poststr(request, "</td></tr>");
}
bool bShowCWForPixelAnim = false;
#if ENABLE_DRIVER_PIXELANIM
if (DRV_IsRunning("PixelAnim")) {
if (c_realPwms == 2)
bShowCWForPixelAnim = true;
PixelAnim_CreatePanel(request);
}
#endif
if (c_pwms == 2 || c_pwms >= 4 || bShowCWForPixelAnim) {
// TODO: temperature slider
int pwmValue;
const char* activeStr = "";
if (lm == Light_Temperature) {
activeStr = "[ACTIVE]";
}
inputName = "pwm";
pwmValue = LED_GetTemperature();
long pwmKelvin = HASS_TO_KELVIN(pwmValue);
long pwmKelvinMax = HASS_TO_KELVIN(led_temperature_min);
long pwmKelvinMin = HASS_TO_KELVIN(led_temperature_max);
poststr(request, "<tr><td>");
hprintf255(request, "<h5>LED Temperature Slider %s (%ld K) (Warm <--- ---> Cool)</h5>", activeStr, pwmKelvin);
hprintf255(request, "<form class='r' action=\"index\" id=\"form%i\">", SPECIAL_CHANNEL_TEMPERATURE);
//(KELVIN_TEMPERATURE_MAX - KELVIN_TEMPERATURE_MIN) / (HASS_TEMPERATURE_MAX - HASS_TEMPERATURE_MIN) = 13
hprintf255(request, "<input type=\"range\" step='13' min=\"%ld\" max=\"%ld\" ", pwmKelvinMin, pwmKelvinMax);
hprintf255(request, "value=\"%ld\" onchange=\"submitTemperature(this);\"/>", pwmKelvin);
hprintf255(request, "<input type=\"hidden\" name=\"%sIndex\" value=\"%i\"/>", inputName, SPECIAL_CHANNEL_TEMPERATURE);
hprintf255(request, "<input id=\"kelvin%i\" type=\"hidden\" name=\"%s\" />", SPECIAL_CHANNEL_TEMPERATURE, inputName);
poststr(request, "</form></td></tr>");
}
}
#endif
#if defined(PLATFORM_BEKEN) || defined(WINDOWS)
if (DRV_IsRunning("PWMToggler")) {
DRV_Toggler_AddToHtmlPage(request);
}
#endif
#if defined(PLATFORM_BEKEN) || defined(WINDOWS)
if (DRV_IsRunning("httpButtons")) {
DRV_HTTPButtons_AddToHtmlPage(request);
}
#endif
poststr(request, "</table>");
#ifndef OBK_DISABLE_ALL_DRIVERS
DRV_AppendInformationToHTTPIndexPage(request, false);
#endif
if (1) {
int bFirst = true;
hprintf255(request, "<h5>");
for (i = 0; i < CHANNEL_MAX; i++) {
if (CHANNEL_IsInUse(i)) {
float value = CHANNEL_GetFloat(i);
if (bFirst == false) {
hprintf255(request, ", ");
}
hprintf255(request, "Channel %i = %.2f", i, value);
bFirst = false;
}
}
if (1) {
i = RepeatingEvents_GetActiveCount();
if (i) {
if (bFirst == false) {
hprintf255(request, ", ");
}
hprintf255(request, "%i repeating events", i);
bFirst = false;
}
i = EventHandlers_GetActiveCount();
if (i) {
if (bFirst == false) {
hprintf255(request, ", ");
}
hprintf255(request, "%i event handlers", i);
bFirst = false;
}
#if defined(WINDOWS) || defined(PLATFORM_BEKEN)
i = CMD_GetCountActiveScriptThreads();
if (i) {
if (bFirst == false) {
hprintf255(request, ", ");
}
hprintf255(request, "%i script threads", i);
bFirst = false;
}
#endif
}
hprintf255(request, "</h5>");
}
hprintf255(request, "<h5>Cfg size: %i, change counter: %i, ota counter: %i, incomplete boots: %i</h5>",
sizeof(g_cfg), g_cfg.changeCounter, g_cfg.otaCounter, g_bootFailures);
// display temperature - thanks to giedriuslt
// only in Normal mode, and if boot is not failing
#ifndef NO_CHIP_TEMPERATURE
hprintf255(request, "<h5>Chip temperature: %.1f°C</h5>", g_wifi_temperature);
#endif
#if ENABLE_PING_WATCHDOG
inputName = CFG_GetPingHost();
if (inputName && *inputName && CFG_GetPingDisconnectedSecondsToRestart()) {
hprintf255(request, "<h5>Ping watchdog (%s) - ", inputName);
if (g_startPingWatchDogAfter > 0) {
hprintf255(request, "will start in %i!</h5>", g_startPingWatchDogAfter);
}
else {
hprintf255(request, "%i lost, %i ok, last reply was %is ago!</h5>",
PingWatchDog_GetTotalLost(), PingWatchDog_GetTotalReceived(), g_timeSinceLastPingReply);
}
}
#endif
if (Main_HasWiFiConnected())
{
int rssi = HAL_GetWifiStrength();
hprintf255(request, "<h5>Wifi RSSI: %s (%idBm)</h5>", str_rssi[wifi_rssi_scale(rssi)], rssi);
}
#if PLATFORM_BEKEN
/*
typedef enum {
RESET_SOURCE_POWERON = 0,
RESET_SOURCE_REBOOT = 1,
RESET_SOURCE_WATCHDOG = 2,
RESET_SOURCE_DEEPPS_GPIO = 3,
RESET_SOURCE_DEEPPS_RTC = 4,
RESET_SOURCE_CRASH_XAT0 = 5,
RESET_SOURCE_CRASH_UNDEFINED = 6,
RESET_SOURCE_CRASH_PREFETCH_ABORT = 7,
RESET_SOURCE_CRASH_DATA_ABORT = 8,
RESET_SOURCE_CRASH_UNUSED = 9,
} RESET_SOURCE_STATUS;
*/
{
const char* s = "Unk";
if (g_rebootReason == 0)
s = "Pwr";
else if (g_rebootReason == 1)
s = "Rbt";
else if (g_rebootReason == 2)
s = "Wdt";
else if (g_rebootReason == 3)
s = "Pin Interrupt";
else if (g_rebootReason == 4)
s = "Sleep Timer";
hprintf255(request, "<h5>Reboot reason: %i - %s</h5>", g_rebootReason, s);
}
#elif PLATFORM_BL602
char reason[26];
bl_sys_rstinfo_getsting(reason);
hprintf255(request, "<h5>Reboot reason: %s</h5>", reason);
#elif PLATFORM_LN882H
// type is chip_reboot_cause_t
g_rebootReason = ln_chip_get_reboot_cause();
{
const char* s = "Unk";
if (g_rebootReason == 0)
s = "Pwr";
else if (g_rebootReason == 1)
s = "Soft";
else if (g_rebootReason == 2)
s = "Wdt";
hprintf255(request, "<h5>Reboot reason: %i - %s</h5>", g_rebootReason, s);
}
#elif PLATFORM_ESPIDF
esp_reset_reason_t reason = esp_reset_reason();
const char* s = "Unknown";
switch(reason)
{
case ESP_RST_UNKNOWN: s = "ESP_RST_UNKNOWN"; break;
case ESP_RST_POWERON: s = "ESP_RST_POWERON"; break;
case ESP_RST_EXT: s = "ESP_RST_EXT"; break;
case ESP_RST_SW: s = "ESP_RST_SW"; break;
case ESP_RST_PANIC: s = "ESP_RST_PANIC"; break;
case ESP_RST_INT_WDT: s = "ESP_RST_INT_WDT"; break;
case ESP_RST_TASK_WDT: s = "ESP_RST_TASK_WDT"; break;
case ESP_RST_WDT: s = "ESP_RST_WDT"; break;
case ESP_RST_DEEPSLEEP: s = "ESP_RST_DEEPSLEEP"; break;
case ESP_RST_BROWNOUT: s = "ESP_RST_BROWNOUT"; break;
case ESP_RST_SDIO: s = "ESP_RST_SDIO"; break;
case ESP_RST_USB: s = "ESP_RST_USB"; break;
case ESP_RST_JTAG: s = "ESP_RST_JTAG"; break;
case ESP_RST_EFUSE: s = "ESP_RST_EFUSE"; break;
case ESP_RST_PWR_GLITCH: s = "ESP_RST_PWR_GLITCH"; break;
case ESP_RST_CPU_LOCKUP: s = "ESP_RST_CPU_LOCKUP"; break;
default: break;
}
hprintf255(request, "<h5>Reboot reason: %i - %s</h5>", reason, s);
#elif PLATFORM_RTL87X0C
const char* s = "Unk";
switch(reset_reason)
{
case HAL_RESET_REASON_POWER_ON: s = "Pwr"; break;
case HAL_RESET_REASON_SOFTWARE: s = "Soft"; break;
case HAL_RESET_REASON_WATCHDOG: s = "Wdt"; break;
default: break;
}
hprintf255(request, "<h5>Reboot reason: %i - %s</h5>", reset_reason, s);
hprintf255(request, "<h5>Current fw: FW%i</h5>", current_fw_idx);
#elif PLATFORM_RTL8710B || PLATFORM_RTL8720D || PLATFORM_REALTEK_NEW
hprintf255(request, "<h5>Current fw: FW%i</h5>", current_fw_idx + 1);
#elif PLATFORM_ECR6600
RST_TYPE reset_type = hal_get_reset_type();
const char* s;
switch(reset_type)
{
case RST_TYPE_POWER_ON: s = "POWER_ON"; break;
case RST_TYPE_FATAL_EXCEPTION: s = "FATAL_EXCEPTION"; break;
case RST_TYPE_SOFTWARE_REBOOT: s = "SOFTWARE_REBOOT"; break;
case RST_TYPE_HARDWARE_REBOOT: s = "HARDWARE_REBOOT"; break;
case RST_TYPE_OTA: s = "OTA"; break;
case RST_TYPE_WAKEUP: s = "WAKEUP"; break;
case RST_TYPE_HARDWARE_WDT_TIMEOUT: s = "HARDWARE_WDT_TIMEOUT"; break;
case RST_TYPE_SOFTWARE_WDT_TIMEOUT: s = "SOFTWARE_WDT_TIMEOUT"; break;
case RST_TYPE_UNKOWN: s = "UNKNOWN"; break;
default: s = "ERROR"; break;
}
hprintf255(request, "<h5>Reboot reason: %i - %s</h5>", reset_type, s);
#endif
#if ENABLE_MQTT
if (CFG_GetMQTTHost()[0] == 0) {
hprintf255(request, "<h5>MQTT State: not configured<br>");
}
else {
const char* stateStr;
const char* colorStr;
if (mqtt_reconnect > 0) {
stateStr = "awaiting reconnect";
colorStr = "orange";
}
else if (Main_HasMQTTConnected() == 1) {
stateStr = "connected";
colorStr = "green";
}
else {
stateStr = "disconnected";
colorStr = "yellow";
}
hprintf255(request, "<h5>MQTT State: <span style=\"color:%s\">%s</span> RES: %d(%s)<br>", colorStr,
stateStr, MQTT_GetConnectResult(), get_error_name(MQTT_GetConnectResult()));
hprintf255(request, "MQTT ErrMsg: %s <br>", (MQTT_GetStatusMessage() != NULL) ? MQTT_GetStatusMessage() : "");
hprintf255(request, "MQTT Stats: CONN: %d PUB: %d RECV: %d ERR: %d </h5>", MQTT_GetConnectEvents(),
MQTT_GetPublishEventCounter(), MQTT_GetReceivedEventCounter(), MQTT_GetPublishErrorCounter());
}
#endif
/* Format current PINS input state for all unused pins */
if (CFG_HasFlag(OBK_FLAG_HTTP_PINMONITOR))
{
for (i = 0; i < PLATFORM_GPIO_MAX; i++)
{
if ((PIN_GetPinRoleForPinIndex(i) == IOR_None) && (i != 0) && (i != 1))
{
HAL_PIN_Setup_Input(i);
}
}
hprintf255(request, "<h5> PIN States<br>");
for (i = 0; i < PLATFORM_GPIO_MAX; i++)
{
if ((PIN_GetPinRoleForPinIndex(i) != IOR_None) || (i == 0) || (i == 1))
{
hprintf255(request, "P%02i: NA ", i);
}
else
{
hprintf255(request, "P%02i: %i ", i, (int)HAL_PIN_ReadDigitalInput(i));
}
if (i % 10 == 9)
{
hprintf255(request, "<br>");
}
}
hprintf255(request, "</h5>");
}
#if ENABLE_DRIVER_CHARTS
/* // moved from drv_charts.c:
// on every "state" request, JS code will be loaded and canvas is redrawn
// this leads to a flickering graph
// so put this right below the "state" div
// with a "#ifdef
// drawback : We need to take care, if driver is loaded and canvas will be displayed only on a reload of the page
// or we might try and hide/unhide it ...
*/
// since we can't simply stop showing the graph in updated status, we need to "hide" it if driver was stopped
if (! DRV_IsRunning("Charts")) {
poststr(request, "<style onload=\"document.getElementById('obkChart').style.display='none'\"></style>");
};
#endif
if (OTA_GetProgress() >= 0)
{
hprintf255(request, "<h5>OTA In Progress. Downloaded: %i B Flashed: %06lXh</h5>", OTA_GetTotalBytes(), OTA_GetProgress());
}
if (bSafeMode) {
hprintf255(request, "<h5 class='safe'>You are in safe mode (AP mode) because full reboot failed %i times. ",
g_bootFailures);
hprintf255(request, "Pins, relays, etc are disabled.</h5>");
}
// for normal page loads, show the rest of the HTML
if (!http_getArg(request->url, "state", tmpA, sizeof(tmpA))) {
poststr(request, "</div>"); // end div#state
#if ENABLE_DRIVER_CHARTS
/* // moved from drv_charts.c:
// on every "state" request, JS code will be loaded and canvas is redrawn
// this leads to a flickering graph
// so put this right below the "state" div
// with a "#ifdef
// drawback : We need to take care, if driver is loaded and canvas will be displayed only on a reload of the page
// or we might try and hide/unhide it ...
*/
// if (DRV_IsRunning("Charts")) {
poststr(request, "<canvas style='display: none' id=\"obkChart\" width=\"400\" height=\"200\"></canvas>");
poststr(request, "<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>");
// };
#endif
// Shared UI elements
poststr(request, "<form action=\"cfg\"><input type=\"submit\" value=\"Config\"/></form>");
poststr(request, "<form action=\"/index\">"
"<input type=\"hidden\" id=\"restart\" name=\"restart\" value=\"1\">"
"<input class=\"bred\" type=\"submit\" value=\"Restart\" onclick=\"return confirm('Are you sure to restart module?')\">"
"</form>");
if (bSafeMode) {
poststr(request, "<form action=\"/index\">"
"<input type=\"hidden\" id=\"unsafe\" name=\"unsafe\" value=\"1\">"
"<input class=\"bred\" type=\"submit\" value=\"Exit safe mode\" onclick=\"");
poststr(request, "return confirm('Are you sure to try exiting safe mode? NOTE: This will enable rest interface etc, but still wont run autoexec')\">"
"</form>");
}
poststr(request, "<form action=\"/app\" target=\"_blank\"><input type=\"submit\" value=\"Launch Web Application\"></form> ");
poststr(request, "<form action=\"about\"><input type=\"submit\" value=\"About\"/></form>");
poststr(request, htmlFooterRefreshLink);
http_html_end(request);
}
poststr(request, NULL);
return 0;
}
int http_fn_about(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "About");
poststr_h2(request, "Open source firmware for BK7231N, BK7231T, T34, BL2028N, XR809, W600/W601, W800/W801, BL602, LF686 and LN882H by OpenSHWProjects");
poststr(request, htmlFooterReturnToMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#if ENABLE_HTTP_MQTT
int http_fn_cfg_mqtt_set(http_request_t* request) {
char tmpA[128];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Saving MQTT");
if (http_getArg(request->url, "host", tmpA, sizeof(tmpA))) {
}
// FIX: always set, so people can clear field
CFG_SetMQTTHost(tmpA);
if (http_getArg(request->url, "port", tmpA, sizeof(tmpA))) {
CFG_SetMQTTPort(atoi(tmpA));
}
#if MQTT_USE_TLS
CFG_SetMQTTUseTls(http_getArg(request->url, "mqtt_use_tls", tmpA, sizeof(tmpA)));
CFG_SetMQTTVerifyTlsCert(http_getArg(request->url, "mqtt_verify_tls_cert", tmpA, sizeof(tmpA)));
http_getArg(request->url, "mqtt_cert_file", tmpA, sizeof(tmpA));
CFG_SetMQTTCertFile(tmpA);
#endif
if (http_getArg(request->url, "user", tmpA, sizeof(tmpA))) {
CFG_SetMQTTUserName(tmpA);
}
if (http_getArg(request->url, "password", tmpA, sizeof(tmpA))) {
CFG_SetMQTTPass(tmpA);
}
if (http_getArg(request->url, "client", tmpA, sizeof(tmpA))) {
CFG_SetMQTTClientId(tmpA);
}
if (http_getArg(request->url, "group", tmpA, sizeof(tmpA))) {
CFG_SetMQTTGroupTopic(tmpA);
}
CFG_Save_SetupTimer();
poststr(request, "Please wait for module to connect... if there is problem, restart it from Index html page...");
#if ENABLE_MQTT
g_mqtt_bBaseTopicDirty = 1;
#endif
poststr(request, "<br><a href=\"cfg_mqtt\">Return to MQTT settings</a><br>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_cfg_mqtt(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "MQTT");
poststr_h2(request, "Use this to connect to your MQTT");
poststr_h4(request, "To disable MQTT, clear the host field.");
hprintf255(request, "<h4>Command topic: cmnd/%s/[Command]</h4>", CFG_GetMQTTClientId());
hprintf255(request, "<h4>Publish data topic: %s/[Channel]/get</h4>", CFG_GetMQTTClientId());
hprintf255(request, "<h4>Receive data topic: %s/[Channel]/set</h4>", CFG_GetMQTTClientId());
add_label_text_field(request, "Host", "host", CFG_GetMQTTHost(), "<form action=\"/cfg_mqtt_set\">");
add_label_numeric_field(request, "Port", "port", CFG_GetMQTTPort(), "<br>");
#if MQTT_USE_TLS
hprintf255(request, "<input type=\"checkbox\" id=\"mqtt_use_tls\" name=\"mqtt_use_tls\" value=\"1\"");
if (CFG_GetMQTTUseTls()) {
hprintf255(request, " checked>");
}
hprintf255(request, "<label for=\"mqtt_use_tls\">Use TLS</label><br>");
hprintf255(request, "<input type=\"checkbox\" id=\"mqtt_verify_tls_cert\" name=\"mqtt_verify_tls_cert\" value=\"1\"");
if (CFG_GetMQTTVerifyTlsCert()) {
hprintf255(request, " checked>");
}
hprintf255(request, "<label for=\"mqtt_use_tls\">Verify TLS Certificate</label><br>");
add_label_text_field(request, "Certificate File (CA Root or Public Certificate PEM format)", "mqtt_cert_file", CFG_GetMQTTCertFile(), "<br>");
#endif
add_label_text_field(request, "Client Topic (Base Topic)", "client", CFG_GetMQTTClientId(), "<br><br>");
add_label_text_field(request, "Group Topic (Secondary Topic to only receive cmnds)", "group", CFG_GetMQTTGroupTopic(), "<br>");
add_label_text_field(request, "User", "user", CFG_GetMQTTUserName(), "<br>");
add_label_password_field(request, "Password", "password", CFG_GetMQTTPass(), "<br>");
poststr(request, "<br><input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure? Please check MQTT data twice?')\"></form> ");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HTTP_IP
int http_fn_cfg_ip(http_request_t* request) {
char tmp[64];
int g_changes = 0;
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "IP");
poststr_h2(request, "Here you can set static IP or DHCP");
hprintf255(request, "<h4>This setting applies only to WiFi client mode.</h4>");
hprintf255(request, "<h4>You must restart manually for changes to take place.</h4>");
hprintf255(request, "<h4>Currently, DHCP is enabled by default and works when you set IP to 0.0.0.0.</h4>");
if (http_getArg(request->url, "IP", tmp, sizeof(tmp))) {
str_to_ip(tmp, g_cfg.staticIP.localIPAddr);
hprintf255(request, "<br>IP=%s (%02x %02x %02x %02x)<br>",tmp,g_cfg.staticIP.localIPAddr[0],g_cfg.staticIP.localIPAddr[1],g_cfg.staticIP.localIPAddr[2],g_cfg.staticIP.localIPAddr[3]);
g_changes++;
}
if (http_getArg(request->url, "mask", tmp, sizeof(tmp))) {
str_to_ip(tmp, g_cfg.staticIP.netMask);
hprintf255(request, "<br>Mask=%s (%02x %02x %02x %02x)<br>",tmp, g_cfg.staticIP.netMask[0], g_cfg.staticIP.netMask[1], g_cfg.staticIP.netMask[2], g_cfg.staticIP.netMask[3]);
g_changes++;
}
if (http_getArg(request->url, "dns", tmp, sizeof(tmp))) {
str_to_ip(tmp, g_cfg.staticIP.dnsServerIpAddr);
hprintf255(request, "<br>DNS=%s (%02x %02x %02x %02x)<br>",tmp, g_cfg.staticIP.dnsServerIpAddr[0], g_cfg.staticIP.dnsServerIpAddr[1], g_cfg.staticIP.dnsServerIpAddr[2], g_cfg.staticIP.dnsServerIpAddr[3]);
g_changes++;
}
if (http_getArg(request->url, "gate", tmp, sizeof(tmp))) {
str_to_ip(tmp, g_cfg.staticIP.gatewayIPAddr);
hprintf255(request, "<br>GW=%s (%02x %02x %02x %02x)<br>",tmp, g_cfg.staticIP.gatewayIPAddr[0], g_cfg.staticIP.gatewayIPAddr[1], g_cfg.staticIP.gatewayIPAddr[2], g_cfg.staticIP.gatewayIPAddr[3]);
g_changes++;
}
if (g_changes) {
CFG_MarkAsDirty();
hprintf255(request, "<h4>Saved.</h4>");
}
convert_IP_to_string(tmp, g_cfg.staticIP.localIPAddr);
add_label_text_field(request, "IP", "IP", tmp, "<form action=\"/cfg_ip\">");
convert_IP_to_string(tmp, g_cfg.staticIP.netMask);
add_label_text_field(request, "Mask", "mask", tmp, "<br><br>");
convert_IP_to_string(tmp, g_cfg.staticIP.dnsServerIpAddr);
add_label_text_field(request, "DNS", "dns", tmp, "<br>");
convert_IP_to_string(tmp, g_cfg.staticIP.gatewayIPAddr);
add_label_text_field(request, "Gate", "gate", tmp, "<br>");
poststr(request, "<br><input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure? Remember that you need to reboot manually to apply changes')\"></form> ");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HTTP_WEBAPP
int http_fn_cfg_webapp(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set Webapp");
add_label_text_field(request, "URL of the Webapp", "url", CFG_GetWebappRoot(), "<form action=\"/cfg_webapp_set\">");
#if MQTT_USE_TLS
hprintf255(request, "<input type=\"checkbox\" id=\"enable_web_server\" name=\"enable_web_server\" value=\"1\"");
if (!CFG_GetDisableWebServer()) {
hprintf255(request, " checked>");
}
hprintf255(request, "<label for=\"enable_web_server\">Web Server Enabled</label><br>");
#endif
poststr(request, SUBMIT_AND_END_FORM);
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_cfg_webapp_set(http_request_t* request) {
char tmpA[128];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Saving Webapp");
if (http_getArg(request->url, "url", tmpA, sizeof(tmpA))) {
CFG_SetWebappRoot(tmpA);
CFG_Save_IfThereArePendingChanges();
hprintf255(request, "Webapp url set to %s", tmpA);
}
else {
poststr(request, "Webapp url not set because you didn't specify the argument.");
}
#if MQTT_USE_TLS
CFG_SetDisableWebServer(!http_getArg(request->url, "enable_web_server", tmpA, sizeof(tmpA)));
if (CFG_GetDisableWebServer()) {
poststr(request, "<br>");
poststr(request, "Webapp will be disabled on next boot!");
}
#endif
poststr(request, "<br>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HTTP_PING
int http_fn_cfg_ping(http_request_t* request) {
char tmpA[128];
int bChanged;
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set Watchdog");
bChanged = 0;
poststr(request, "<h3>Ping watchdog (backup reconnect mechanism)</h3>");
poststr(request, "<p> By default, all OpenBeken devices automatically try to reconnect to WiFi when a connection is lost.");
poststr(request, " I have tested the reconnect mechanism many times by restarting my router and it always worked reliably.");
poststr(request, " However, according to some reports, there are still some edge cases where a device fails to reconnect to WiFi.");
poststr(request, " This is why <b>this mechanism</b> has been added.</p>");
poststr(request, "<p>This mechanism continuously pings a specified host and reconnects to WiFi if it doesn't respond for the specified number of seconds.</p>");
poststr(request, "<p>USAGE: For the host, choose the main address of your router and ensure it responds to pings. The interval is around 1 second, and the timeout can be set by the user, for example, to 60 seconds.</p>");
if (http_getArg(request->url, "host", tmpA, sizeof(tmpA))) {
CFG_SetPingHost(tmpA);
poststr_h4(request, "New ping host set!");
bChanged = 1;
}
/* if(http_getArg(request->url,"interval",tmpA,sizeof(tmpA))) {
CFG_SetPingIntervalSeconds(atoi(tmpA));
poststr(request,"<h4> New ping interval set!</h4>");
bChanged = 1;
}*/
if (http_getArg(request->url, "disconnectTime", tmpA, sizeof(tmpA))) {
CFG_SetPingDisconnectedSecondsToRestart(atoi(tmpA));
poststr_h4(request, "New ping disconnectTime set!");
bChanged = 1;
}
if (http_getArg(request->url, "clear", tmpA, sizeof(tmpA))) {
CFG_SetPingDisconnectedSecondsToRestart(0);
CFG_SetPingIntervalSeconds(0);
CFG_SetPingHost("");
poststr_h4(request, "Ping watchdog disabled!");
bChanged = 1;
}
if (bChanged) {
CFG_Save_IfThereArePendingChanges();
poststr_h4(request, "Changes will be applied after restarting");
}
poststr(request, "<form action=\"/cfg_ping\">\
<input type=\"hidden\" id=\"clear\" name=\"clear\" value=\"1\">\
<input type=\"submit\" value=\"Disable ping watchdog!\">\
</form>");
poststr_h2(request, "Use this to enable pinger");
add_label_text_field(request, "Host", "host", CFG_GetPingHost(), "<form action=\"/cfg_ping\">");
add_label_numeric_field(request, "Take action after this number of seconds with no reply", "disconnectTime",
CFG_GetPingDisconnectedSecondsToRestart(), "<br>");
poststr(request, "<br><br>\
<input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure?')\">\
</form>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
int http_fn_cfg_wifi(http_request_t* request) {
// for a test, show password as well...
char tmpA[128];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set Wifi");
/*bChanged = 0;
if(http_getArg(recvbuf,"ssid",tmpA,sizeof(tmpA))) {
CFG_SetWiFiSSID(tmpA);
poststr(request,"<h4> WiFi SSID set!</h4>");
bChanged = 1;
}
if(http_getArg(recvbuf,"pass",tmpA,sizeof(tmpA))) {
CFG_SetWiFiPass(tmpA);
poststr(request,"<h4> WiFi Password set!</h4>");
bChanged = 1;
}
if(bChanged) {
poststr(request,"<h4> Device will reconnect after restarting</h4>");
}*/
poststr(request, "<h2> Check networks reachable by module</h2> This will take a few seconds<br>");
if (http_getArg(request->url, "scan", tmpA, sizeof(tmpA))) {
#ifdef WINDOWS
poststr(request, "Not available on Windows<br>");
#elif PLATFORM_BL602
wifi_mgmr_ap_item_t *ap_info;
uint32_t i, ap_num;
bk_printf("Scan begin...\r\n");
wifi_mgmr_all_ap_scan(&ap_info, &ap_num);
bk_printf("Scan returned %li networks\r\n", ap_num);
for (i = 0; i < ap_num; i++) {
hprintf255(request, "[%i/%i] SSID: %s, Channel: %i, Signal %i<br>", (i+1), (int)ap_num, ap_info[i].ssid, ap_info[i].channel, ap_info[i].rssi);
}
vPortFree(ap_info);
#elif defined(PLATFORM_BK7231T) && !defined(PLATFORM_BEKEN_NEW)
int i;
AP_IF_S* ar;
uint32_t num;
bk_printf("Scan begin...\r\n");
tuya_hal_wifi_all_ap_scan(&ar, &num);
bk_printf("Scan returned %i networks\r\n", num);
for (i = 0; i < num; i++) {
hprintf255(request, "[%i/%i] SSID: %s, Channel: %i, Signal %i<br>", i, (int)num, ar[i].ssid, ar[i].channel, ar[i].rssi);
}
tuya_hal_wifi_release_ap(ar);
#elif defined(PLATFORM_BK7231N) && !defined(PLATFORM_BEKEN_NEW)
int i;
AP_IF_S* ar;
uint32_t num;
bk_printf("Scan begin...\r\n");
tuya_os_adapt_wifi_all_ap_scan(&ar, (unsigned int*)&num);
bk_printf("Scan returned %i networks\r\n", num);
for (i = 0; i < num; i++) {
hprintf255(request, "[%i/%i] SSID: %s, Channel: %i, Signal %i<br>", i + 1, (int)num, ar[i].ssid, ar[i].channel, ar[i].rssi);
}
tuya_os_adapt_wifi_release_ap(ar);
#elif PLATFORM_ESPIDF || PLATFORM_ESP8266
// doesn't work in ap mode, only sta/apsta
uint16_t ap_count = 0, number = 30;
wifi_ap_record_t ap_info[number];
memset(ap_info, 0, sizeof(ap_info));
bk_printf("Scan begin...\r\n");
esp_wifi_scan_start(NULL, true);
esp_wifi_scan_get_ap_num(&ap_count);
bk_printf("Scan returned %i networks, max allowed: %i\r\n", ap_count, number);
esp_wifi_scan_get_ap_records(&number, ap_info);
for(int i = 0; i < number; i++)
{
hprintf255(request, "[%i/%u] SSID: %s, Channel: %i, Signal %i<br>", i + 1, number, ap_info[i].ssid, ap_info[i].primary, ap_info[i].rssi);
}
#elif defined(PLATFORM_REALTEK) && !PLATFORM_REALTEK_NEW
#ifndef PLATFORM_RTL87X0C
extern void rltk_wlan_enable_scan_with_ssid_by_extended_security(bool);
#endif
rtw_result_t scan_result_handler(rtw_scan_handler_result_t* result)
{
http_request_t* request = (http_request_t*)result->user_data;
if(result->scan_complete == RTW_TRUE)
{
xSemaphoreGive(scan_hdl);
return RTW_SUCCESS;
}
rtw_scan_result_t* record = &result->ap_details;
record->SSID.val[record->SSID.len] = 0;
hprintf255(request, "SSID: %s, Channel: %i, Signal %i<br>", record->SSID.val, record->channel, record->signal_strength);
return 0;
}
scan_hdl = xSemaphoreCreateBinary();
rltk_wlan_enable_scan_with_ssid_by_extended_security(1);
xSemaphoreTake(scan_hdl, 1);
if(wifi_scan_networks(scan_result_handler, request) != RTW_SUCCESS)
{
poststr(request, "Wifi scan failed!<br>");
}
xSemaphoreTake(scan_hdl, pdMS_TO_TICKS(10 * 1000));
vSemaphoreDelete(scan_hdl);
#else
hprintf255(request, "TODO %s<br>", PLATFORM_MCU_NAME);
#endif
}
poststr(request, "<form action=\"/cfg_wifi\">\
<input type=\"hidden\" id=\"scan\" name=\"scan\" value=\"1\">\
<input type=\"submit\" value=\"Scan Local Networks\">\
</form>");
poststr_h4(request, "Use this to disconnect from your WiFi");
poststr(request, "<form action=\"/cfg_wifi_set\">\
<input type=\"hidden\" id=\"open\" name=\"open\" value=\"1\">\
<input type=\"submit\" value=\"Convert to Open Access WiFi\" onclick=\"return confirm('Are you sure you want to switch to open access WiFi?')\">\
</form>");
poststr_h2(request, "Use this to connect to your WiFi");
add_label_text_field(request, "SSID", "ssid", CFG_GetWiFiSSID(), "<form action=\"/cfg_wifi_set\">");
add_label_password_field(request, "", "pass", CFG_GetWiFiPass(), "<br>Password<span style=\"float:right;\"><input type=\"checkbox\" onclick=\"e=getElement('pass');if(this.checked){e.value='';e.type='text'}else e.type='password'\" > enable clear text password (clears existing)</span>");
poststr_h2(request, "Alternate WiFi (used when first one is not responding)");
poststr(request, "Note: It is possible to retain used SSID using command setStartupSSIDChannel in early.bat");
#ifndef PLATFORM_BEKEN
poststr_h2(request, "SSID2 only on Beken Platform (BK7231T, BK7231N)");
#endif
add_label_text_field(request, "SSID2", "ssid2", CFG_GetWiFiSSID2(), "");
add_label_password_field(request, "", "pass2", CFG_GetWiFiPass2(), "<br>Password2<span style=\"float:right;\"><input type=\"checkbox\" onclick=\"e=getElement('pass2');if(this.checked){e.value='';e.type='text'}else e.type='password'\" > enable clear text password (clears existing)</span>");
#if ALLOW_WEB_PASSWORD
int web_password_enabled = strcmp(CFG_GetWebPassword(), "") == 0 ? 0 : 1;
poststr_h2(request, "Web Authentication");
poststr(request, "<p>Enabling web authentication will protect this web interface and API using basic HTTP authentication. Username is always <b>admin</b>.</p>");
hprintf255(request, "<div><input type=\"checkbox\" name=\"web_admin_password_enabled\" id=\"web_admin_password_enabled\" value=\"1\"%s>", (web_password_enabled > 0 ? " checked" : ""));
poststr(request, "<label for=\"web_admin_password_enabled\">Enable web authentication</label></div>");
add_label_password_field(request, "Admin Password", "web_admin_password", CFG_GetWebPassword(), "");
#endif
poststr(request, "<br><br>\
<input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure? Please double-check SSID and password.')\">\
</form>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#if ENABLE_HTTP_NAMES
int http_fn_cfg_name(http_request_t* request) {
// for a test, show password as well...
char tmpA[128];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set name");
poststr_h2(request, "Change device names for display");
if (http_getArg(request->url, "shortName", tmpA, sizeof(tmpA))) {
if (STR_ReplaceWhiteSpacesWithUnderscore(tmpA)) {
poststr_h2(request, "You cannot have whitespaces in short name!");
}
CFG_SetShortDeviceName(tmpA);
}
if (http_getArg(request->url, "name", tmpA, sizeof(tmpA))) {
CFG_SetDeviceName(tmpA);
}
CFG_Save_IfThereArePendingChanges();
poststr_h2(request, "Use this to change device names");
add_label_name_field(request, "ShortName", "shortName", CFG_GetShortDeviceName(), "<form action=\"/cfg_name\">");
add_label_name_field(request, "Full Name", "name", CFG_GetDeviceName(), "<br>");
poststr(request, "<br><br>");
poststr(request, "<input type=\"submit\" value=\"Submit\" "
"onclick=\"return confirm('Are you sure? "
"Short name might be used by Home Assistant, "
"so you will have to reconfig some stuff.')\">");
poststr(request, "</form>");
//poststr(request,htmlReturnToCfg);
//HTTP_AddBuildFooter(request);
//poststr(request,htmlEnd);
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
int http_fn_cfg_wifi_set(http_request_t* request) {
char tmpA[128];
int bChanged;
addLogAdv(LOG_INFO, LOG_FEATURE_HTTP, "HTTP_ProcessPacket: generating cfg_wifi_set \r\n");
bChanged = 0;
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Saving Wifi");
if (http_getArg(request->url, "open", tmpA, sizeof(tmpA))) {
bChanged |= CFG_SetWiFiSSID("");
bChanged |= CFG_SetWiFiPass("");
poststr(request, "WiFi mode set: open access point.");
}
else {
if (http_getArg(request->url, "ssid", tmpA, sizeof(tmpA))) {
bChanged |= CFG_SetWiFiSSID(tmpA);
}
if (http_getArg(request->url, "pass", tmpA, sizeof(tmpA))) {
bChanged |= CFG_SetWiFiPass(tmpA);
}
poststr(request, "WiFi mode set: connect to WLAN.");
if(bChanged) HAL_DisableEnhancedFastConnect();
}
if (http_getArg(request->url, "ssid2", tmpA, sizeof(tmpA))) {
bChanged |= CFG_SetWiFiSSID2(tmpA);
}
if (http_getArg(request->url, "pass2", tmpA, sizeof(tmpA))) {
bChanged |= CFG_SetWiFiPass2(tmpA);
}
#if ALLOW_WEB_PASSWORD
if (http_getArg(request->url, "web_admin_password_enabled", tmpA, sizeof(tmpA))) {
int web_password_enabled = atoi(tmpA);
if (web_password_enabled > 0 && http_getArg(request->url, "web_admin_password", tmpA, sizeof(tmpA))) {
if (strlen(tmpA) < 5) {
poststr_h4(request, "Web password needs to be at least 5 characters long!");
} else {
poststr(request, "<p>Web password has been changed.</p>");
CFG_SetWebPassword(tmpA);
}
}
} else {
CFG_SetWebPassword("");
}
#endif
CFG_Save_SetupTimer();
if (bChanged == 0) {
poststr(request, "<p>WiFi: No changes detected.</p>");
}
else {
poststr(request, "<p>WiFi: Please wait for module to reset...</p>");
RESET_ScheduleModuleReset(3);
}
poststr(request, "<br><a href=\"cfg_wifi\">Return to WiFi settings</a><br>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_cfg_loglevel_set(http_request_t* request) {
char tmpA[128];
addLogAdv(LOG_INFO, LOG_FEATURE_HTTP, "HTTP_ProcessPacket: generating cfg_loglevel_set \r\n");
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set log level");
if (http_getArg(request->url, "loglevel", tmpA, sizeof(tmpA))) {
#if WINDOWS
#else
g_loglevel = atoi(tmpA);
#endif
poststr(request, "LOG level changed.");
}
tmpA[0] = 0;
#if WINDOWS
add_label_text_field(request, "Loglevel", "loglevel", "", "<form action=\"/cfg_loglevel_set\">");
#else
add_label_numeric_field(request, "Loglevel", "loglevel", g_loglevel, "<form action=\"/cfg_loglevel_set\">");
#endif
poststr(request, "<br><br>\
<input type=\"submit\" value=\"Submit\" >\
</form>");
poststr(request, "<br><a href=\"cfg\">Return to config settings</a><br>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#if ENABLE_HTTP_MAC
int http_fn_cfg_mac(http_request_t* request) {
// must be unsigned, else print below prints negatives as e.g. FFFFFFFe
unsigned char mac[6];
char tmpA[128];
int i;
char macStr[16];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set MAC address");
if (http_getArg(request->url, "mac", tmpA, sizeof(tmpA))) {
for (i = 0; i < 6; i++)
{
mac[i] = hexbyte(&tmpA[i * 2]);
}
//sscanf(tmpA,"%02X%02X%02X%02X%02X%02X",&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]);
if (WiFI_SetMacAddress((char*)mac)) {
poststr_h4(request, "New MAC set!");
}
else {
poststr_h4(request, "MAC change error?");
}
CFG_Save_IfThereArePendingChanges();
}
WiFI_GetMacAddress((char*)mac);
poststr_h2(request, "Here you can change MAC address.");
sprintf(macStr, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
add_label_text_field(request, "MAC", "mac", macStr, "<form action=\"/cfg_mac\">");
poststr(request, "<br><br>\
<input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure? Please check MAC hex string twice?')\">\
</form>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
const char* CMD_GetResultString(commandResult_t r) {
if (r == CMD_RES_OK)
return "OK";
if (r == CMD_RES_EMPTY_STRING)
return "No command entered";
if (r == CMD_RES_ERROR)
return "Command found but returned error";
if (r == CMD_RES_NOT_ENOUGH_ARGUMENTS)
return "Not enough arguments for this command";
if (r == CMD_RES_UNKNOWN_COMMAND)
return "Unknown command";
if (r == CMD_RES_BAD_ARGUMENT)
return "Bad argument";
return "Unknown error";
}
// all log printfs made by command will be sent also to request
void LOG_SetCommandHTTPRedirectReply(http_request_t* request);
int http_fn_cmd_tool(http_request_t* request) {
commandResult_t res;
const char* resStr;
char tmpA[128];
char* long_str_alloced = 0;
int commandLen;
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Command tool");
poststr_h4(request, "Command Tool");
poststr(request, "This is a basic command line. <br>");
poststr(request, "Please consider using the 'Web Application' console for more options and real-time log viewing. <br>");
poststr(request, "Remember that some commands are added after a restart when a driver is activated. <br>");
commandLen = http_getArg(request->url, "cmd", tmpA, sizeof(tmpA));
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "http_fn_cmd_tool: len %i",commandLen);
if (commandLen) {
poststr(request, "<br>");
// all log printfs made by command will be sent also to request
LOG_SetCommandHTTPRedirectReply(request);
if (commandLen > (sizeof(tmpA) - 5)) {
commandLen += 8;
long_str_alloced = (char*)malloc(commandLen);
if (long_str_alloced) {
http_getArg(request->url, "cmd", long_str_alloced, commandLen);
res = CMD_ExecuteCommand(long_str_alloced, COMMAND_FLAG_SOURCE_CONSOLE);
free(long_str_alloced);
}
else {
res = CMD_RES_ERROR;
}
}
else {
res = CMD_ExecuteCommand(tmpA, COMMAND_FLAG_SOURCE_CONSOLE);
}
LOG_SetCommandHTTPRedirectReply(0);
resStr = CMD_GetResultString(res);
hprintf255(request, "<h3>%s</h3>", resStr);
poststr(request, "<br>");
}
add_label_text_field(request, "Command", "cmd", tmpA, "<form action=\"/cmd_tool\">");
poststr(request, SUBMIT_AND_END_FORM);
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#if ENABLE_HTTP_STARTUP
int http_fn_startup_command(http_request_t* request) {
char tmpA[8];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Set startup command");
poststr_h4(request, "Set/Change/Clear startup command line");
poststr(request, "<p>Startup command is a shorter, smaller alternative to LittleFS autoexec.bat. "
"The startup commands are run at device startup. "
"You can use them to init peripherals and drivers, like BL0942 energy sensor. "
"Use backlog cmd1; cmd2; cmd3; etc to enter multiple commands</p>");
if (http_getArg(request->url, "startup_cmd", tmpA, sizeof(tmpA))) {
// direct config access to remove buffer on stack
int realSize = http_getArg(request->url, "data", g_cfg.initCommandLine, sizeof(g_cfg.initCommandLine));
// mark as dirty (value has changed)
g_cfg_pendingChanges++;
if (realSize >= sizeof(g_cfg.initCommandLine)) {
hprintf255(request, "<h3 style='color:red'>Command trimmed from %i to %i!</h3>",realSize, sizeof(g_cfg.initCommandLine));
} else {
hprintf255(request, "<h3>Command changed!</h3>");
}
CFG_Save_IfThereArePendingChanges();
}
#if ENABLE_OBK_SCRIPTING
poststr(request, "<form action=\"/startup_command\">");
poststr(request, "<label for='data'>Startup command</label><br>");
poststr(request, "<textarea id='data' name='data' rows='15' cols='40'>");
poststr(request, CFG_GetShortStartupCommand());
poststr(request, "</textarea><br>");
#else
add_label_text_field(request, "Startup command", "data", CFG_GetShortStartupCommand(), "<form action=\"/startup_command\">");
#endif
poststr(request, "<input type='hidden' name='startup_cmd' value='1'>");
poststr(request, SUBMIT_AND_END_FORM);
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HA_DISCOVERY
HassDeviceInfo *hass_createEnumChannelInfo(int i) {
HassDeviceInfo *dev_info = 0;
channelEnum_t *en;
if (g_enums != NULL && g_enums[i]->numOptions != 0) {
en = g_enums[i];
}
else {
// revert to textfield if no enums are defined
dev_info = hass_init_textField_info(i);
return dev_info;;
}
char **options = (char**)malloc(en->numOptions * sizeof(char *));
for (int o = 0; o < en->numOptions; o++) {
options[o] = en->options[o].label;
}
if (en->options != NULL && en->numOptions > 0) {
// backlog setChannelType 1 Enum; setChannelEnum 0:red 2:blue 3:green; scheduleHADiscovery 1
char stateTopic[32];
char cmdTopic[32];
char title[64];
char value_tmp[1024];
char command_tmp[1024];
CMD_GenEnumValueTemplate(en, value_tmp, sizeof(value_tmp));
CMD_GenEnumCommandTemplate(en, command_tmp, sizeof(command_tmp));
strcpy(title, CHANNEL_GetLabel(i));
sprintf(stateTopic, "~/%i/get", i);
sprintf(cmdTopic, "~/%i/set", i);
dev_info = hass_createSelectEntityIndexedCustom(
stateTopic,
cmdTopic,
en->numOptions,
(const char**)options,
title,
value_tmp,
command_tmp
);
}
os_free(options);
return dev_info;
}
void doHomeAssistantDiscovery(const char* topic, http_request_t* request) {
int i;
int relayCount;
int pwmCount;
int dInputCount;
int excludedCount = 0;
bool ledDriverChipRunning;
HassDeviceInfo* dev_info = NULL;
bool measuringPower = false;
bool measuringBattery = false;
struct cJSON_Hooks hooks;
bool discoveryQueued = false;
int type;
// warning - this is 32 bit
int flagsChannelPublished;
int ch;
int dimmer, toggle, brightness_scale = 0;
// no channels published yet
flagsChannelPublished = 0;
for (i = 0; i < CHANNEL_MAX; i++) {
if (CHANNEL_HasNeverPublishFlag(i)) {
BIT_SET(flagsChannelPublished, i);
excludedCount++;
}
}
if (topic == 0 || *topic == 0) {
topic = "homeassistant";
}
#ifdef ENABLE_DRIVER_BL0937
measuringPower = DRV_IsMeasuringPower();
#endif
measuringBattery = DRV_IsMeasuringBattery();
PIN_get_Relay_PWM_Count(&relayCount, &pwmCount, &dInputCount);
addLogAdv(LOG_INFO, LOG_FEATURE_HTTP, "HASS counts: %i rels, %i pwms, %i inps, %i excluded", relayCount, pwmCount, dInputCount, excludedCount);
#if ENABLE_LED_BASIC
ledDriverChipRunning = LED_IsLedDriverChipRunning();
#else
ledDriverChipRunning = 0;
#endif
#if PLATFORM_TXW81X
hooks.malloc_fn = _os_malloc;
hooks.free_fn = _os_free;
#else
hooks.malloc_fn = os_malloc;
hooks.free_fn = os_free;
#endif
cJSON_InitHooks(&hooks);
DRV_OnHassDiscovery(topic);
EventHandlers_FireEvent(CMD_EVENT_ON_DISCOVERY, 0);
#if ENABLE_ADVANCED_CHANNELTYPES_DISCOVERY
// try to pair toggles with dimmers. This is needed only for TuyaMCU,
// where custom channel types are used. This is NOT used for simple
// CW/RGB/RGBCW/etc lights.
if (CFG_HasFlag(OBK_FLAG_DISCOVERY_DONT_MERGE_LIGHTS) == false) {
while (true) {
// find first dimmer
dimmer = -1;
for (i = 0; i < CHANNEL_MAX; i++) {
type = g_cfg.pins.channelTypes[i];
if (BIT_CHECK(flagsChannelPublished, i)) {
continue;
}
if (type == ChType_Dimmer) {
brightness_scale = 100;
dimmer = i;
break;
}
if (type == ChType_Dimmer1000) {
brightness_scale = 1000;
dimmer = i;
break;
}
if (type == ChType_Dimmer256) {
brightness_scale = 256;
dimmer = i;
break;
}
}
// find first togle
toggle = -1;
for (i = 0; i < CHANNEL_MAX; i++) {
type = g_cfg.pins.channelTypes[i];
if (BIT_CHECK(flagsChannelPublished, i)) {
continue;
}
if (type == ChType_Toggle) {
toggle = i;
break;
}
}
// if nothing found, stop
if (toggle == -1 || dimmer == -1) {
break;
}
BIT_SET(flagsChannelPublished, toggle);
BIT_SET(flagsChannelPublished, dimmer);
dev_info = hass_init_light_singleColor_onChannels(toggle, dimmer, brightness_scale);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
}
#endif
#if ENABLE_LED_BASIC
if (ledDriverChipRunning) {
pwmCount = CFG_CountLEDRemapChannels();
}
if (pwmCount == 5 || (pwmCount == 4 && CFG_HasFlag(OBK_FLAG_LED_EMULATE_COOL_WITH_RGB))) {
if (dev_info == NULL) {
dev_info = hass_init_light_device_info(LIGHT_RGBCW);
}
// Enable + RGB control + CW control
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = NULL;
discoveryQueued = true;
}
else if (pwmCount > 0) {
if (pwmCount == 4) {
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "4 PWM device not yet handled\r\n");
}
else if (pwmCount == 3) {
// Enable + RGB control
dev_info = hass_init_light_device_info(LIGHT_RGB);
}
else if (pwmCount == 2) {
// PWM + Temperature (https://github.com/openshwprojects/OpenBK7231T_App/issues/279)
dev_info = hass_init_light_device_info(LIGHT_PWMCW);
}
else {
dev_info = hass_init_light_device_info(LIGHT_PWM);
}
if (dev_info != NULL) {
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = NULL;
discoveryQueued = true;
}
}
#endif
#ifdef ENABLE_DRIVER_BL0937
if (measuringPower == true) {
for (i = OBK__FIRST; i <= OBK__LAST; i++)
{
dev_info = hass_init_energy_sensor_device_info(i, BL_SENSORS_IX_0);
if (dev_info) {
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
if (i == OBK_VOLTAGE) {
//20250319 XJIKKA to simplify and save space in flash frequency together with voltage
dev_info = hass_init_sensor_device_info(FREQUENCY_SENSOR, SPECIAL_CHANNEL_OBK_FREQUENCY, -1, -1, -1);
if (dev_info) {
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
}
}
#if ENABLE_BL_TWIN
//BL_SENSORS_IX_1 - mqtt hass discovery using hass_uniq_id_suffix (_b) from drv_bl_shared.c
if (BL_IsMeteringDeviceIndexActive(BL_SENSORS_IX_1)) {
for (i = OBK__FIRST; i <= OBK__LAST; i++)
{
dev_info = hass_init_energy_sensor_device_info(i, BL_SENSORS_IX_1);
if (dev_info) {
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
}
}
#endif
}
#endif
if (measuringBattery == true) {
dev_info = hass_init_sensor_device_info(BATTERY_SENSOR, 0, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = hass_init_sensor_device_info(BATTERY_VOLTAGE_SENSOR, 0, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
for (i = 0; i < PLATFORM_GPIO_MAX; i++) {
if (IS_PIN_DHT_ROLE(g_cfg.pins.roles[i]) || IS_PIN_TEMP_HUM_SENSOR_ROLE(g_cfg.pins.roles[i])) {
ch = PIN_GetPinChannelForPinIndex(i);
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, ch);
dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, ch, 2, 1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
ch = PIN_GetPinChannel2ForPinIndex(i);
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, ch);
dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, ch, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
else if (IS_PIN_AIR_SENSOR_ROLE(g_cfg.pins.roles[i])) {
ch = PIN_GetPinChannelForPinIndex(i);
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, ch);
dev_info = hass_init_sensor_device_info(CO2_SENSOR, ch, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
ch = PIN_GetPinChannel2ForPinIndex(i);
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, ch);
dev_info = hass_init_sensor_device_info(TVOC_SENSOR, ch, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
}
//{
// HassDeviceInfo*dev_info = hass_createGarageEntity("~/1/get", "~/1/set",
// "Main Door");
// MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
// hass_free_device_info(dev_info);
// discoveryQueued = true;
//}
#if ENABLE_ADVANCED_CHANNELTYPES_DISCOVERY
for (i = 0; i < CHANNEL_MAX; i++) {
type = g_cfg.pins.channelTypes[i];
// TODO: flags are 32 bit and there are 64 max channels
if (BIT_CHECK(flagsChannelPublished, i)) {
continue;
}
dev_info = 0;
switch (type)
{
case ChType_Motion:
{
dev_info = hass_init_binary_sensor_device_info(i, true);
cJSON_AddStringToObject(dev_info->root, "dev_cla", "motion");
}
break;
case ChType_Motion_n:
{
dev_info = hass_init_binary_sensor_device_info(i, false);
cJSON_AddStringToObject(dev_info->root, "dev_cla", "motion");
}
break;
case ChType_OpenClosed:
{
dev_info = hass_init_binary_sensor_device_info(i, false);
}
break;
case ChType_OpenClosed_Inv:
{
dev_info = hass_init_binary_sensor_device_info(i, true);
}
break;
case ChType_Voltage_div10:
{
dev_info = hass_init_sensor_device_info(VOLTAGE_SENSOR, i, 2, 1, 1);
}
break;
case ChType_Voltage_div100:
{
dev_info = hass_init_sensor_device_info(VOLTAGE_SENSOR, i, 2, 2, 1);
}
break;
case ChType_ReadOnlyLowMidHigh:
{
dev_info = hass_init_sensor_device_info(READONLYLOWMIDHIGH_SENSOR, i, -1, -1, 1);
}
break;
case ChType_BatteryLevelPercent:
{
dev_info = hass_init_sensor_device_info(BATTERY_CHANNEL_SENSOR, i, -1, -1, 1);
}
break;
case ChType_SmokePercent:
{
dev_info = hass_init_sensor_device_info(SMOKE_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Illuminance:
{
dev_info = hass_init_sensor_device_info(ILLUMINANCE_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Custom:
case ChType_ReadOnly:
{
dev_info = hass_init_sensor_device_info(CUSTOM_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Temperature:
{
dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Temperature_div2:
{
dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, 2, 1, 5);
}
break;
case ChType_Temperature_div10:
{
dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, 2, 1, 1);
}
break;
case ChType_ReadOnly_div10:
{
dev_info = hass_init_sensor_device_info(CUSTOM_SENSOR, i, 2, 1, 1);
}
break;
case ChType_Temperature_div100:
{
dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, 2, 2, 1);
}
break;
case ChType_ReadOnly_div100:
{
dev_info = hass_init_sensor_device_info(CUSTOM_SENSOR, i, 2, 2, 1);
}
break;
case ChType_Humidity:
{
dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Humidity_div10:
{
dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i, 2, 1, 1);
}
break;
case ChType_Current_div100:
{
dev_info = hass_init_sensor_device_info(CURRENT_SENSOR, i, 3, 2, 1);
}
break;
case ChType_ReadOnly_div1000:
{
dev_info = hass_init_sensor_device_info(CUSTOM_SENSOR, i, 3, 3, 1);
}
break;
case ChType_LeakageCurrent_div1000:
case ChType_Current_div1000:
{
dev_info = hass_init_sensor_device_info(CURRENT_SENSOR, i, 3, 3, 1);
}
break;
case ChType_Power:
{
dev_info = hass_init_sensor_device_info(POWER_SENSOR, i, -1, -1, 1);
}
break;
case ChType_Power_div10:
{
dev_info = hass_init_sensor_device_info(POWER_SENSOR, i, 2, 1, 1);
}
break;
case ChType_Power_div100:
{
dev_info = hass_init_sensor_device_info(POWER_SENSOR, i, 3, 2, 1);
}
break;
case ChType_PowerFactor_div100:
{
dev_info = hass_init_sensor_device_info(POWERFACTOR_SENSOR, i, 3, 2, 1);
}
break;
case ChType_Pressure_div100:
{
dev_info = hass_init_sensor_device_info(PRESSURE_SENSOR, i, 3, 2, 1);
}
break;
case ChType_PowerFactor_div1000:
{
dev_info = hass_init_sensor_device_info(POWERFACTOR_SENSOR, i, 4, 3, 1);
}
break;
case ChType_Frequency_div100:
{
dev_info = hass_init_sensor_device_info(FREQUENCY_SENSOR, i, 3, 2, 1);
}
break;
case ChType_Percent:
{
dev_info = hass_init_sensor_device_info(HASS_PERCENT, i, 3, 2, 1);
}
break;
case ChType_Frequency_div1000:
{
dev_info = hass_init_sensor_device_info(FREQUENCY_SENSOR, i, 4, 3, 1);
}
break;
case ChType_Frequency_div10:
{
dev_info = hass_init_sensor_device_info(FREQUENCY_SENSOR, i, 3, 1, 1);
}
break;
case ChType_EnergyTotal_kWh_div100:
{
dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 2, 1);
}
break;
case ChType_EnergyExport_kWh_div1000:
{
dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1);
}
break;
case ChType_EnergyImport_kWh_div1000:
{
dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1);
}
break;
case ChType_EnergyTotal_kWh_div1000:
{
dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1);
}
break;
case ChType_Ph:
{
dev_info = hass_init_sensor_device_info(WATER_QUALITY_PH, i, 2, 2, 1);
}
break;
case ChType_Orp:
{
dev_info = hass_init_sensor_device_info(WATER_QUALITY_ORP, i, -1, 2, 1);
}
break;
case ChType_Tds:
{
dev_info = hass_init_sensor_device_info(WATER_QUALITY_TDS, i, -1, 2, 1);
}
break;
case ChType_TextField:
{
dev_info = hass_init_textField_info(i);
}
break;
case ChType_ReadOnlyEnum:
{
dev_info = hass_init_sensor_device_info(HASS_READONLYENUM, i, -1, -1, -1);
}
break;
case ChType_Enum:
{
dev_info = hass_createEnumChannelInfo(i);
}
break;
default:
{
int numOptions;
const char **options = Channel_GetOptionsForChannelType(type, &numOptions);
if (options && numOptions) {
// backlog setChannelType 2 LowMidHigh; scheduleHADiscovery 1
// backlog setChannelType 3 OpenStopClose; scheduleHADiscovery 1
char stateTopic[16];
char cmdTopic[16];
// TODO: lengths
sprintf(stateTopic, "~/%i/get", i);
sprintf(cmdTopic, "~/%i/set", i);
dev_info = hass_createSelectEntityIndexed(
stateTopic,
cmdTopic,
numOptions,
options,
CHANNEL_GetLabel(i)
);
}
}
break;
}
if (dev_info) {
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
BIT_SET(flagsChannelPublished, i);
discoveryQueued = true;
}
}
#endif
//if (relayCount > 0) {
for (i = 0; i < CHANNEL_MAX; i++) {
// if already included by light, skip
if (BIT_CHECK(flagsChannelPublished, i)) {
continue;
}
bool bToggleInv = g_cfg.pins.channelTypes[i] == ChType_Toggle_Inv;
if (h_isChannelRelay(i) || g_cfg.pins.channelTypes[i] == ChType_Toggle || bToggleInv) {
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, i);
if (CFG_HasFlag(OBK_FLAG_MQTT_HASS_ADD_RELAYS_AS_LIGHTS)) {
dev_info = hass_init_relay_device_info(i, LIGHT_ON_OFF, bToggleInv);
}
else {
dev_info = hass_init_relay_device_info(i, RELAY, bToggleInv);
}
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = NULL;
discoveryQueued = true;
}
}
//}
if (dInputCount > 0) {
for (i = 0; i < CHANNEL_MAX; i++) {
if (h_isChannelDigitalInput(i)) {
if (BIT_CHECK(flagsChannelPublished, i)) {
continue;
}
// TODO: flags are 32 bit and there are 64 max channels
BIT_SET(flagsChannelPublished, i);
dev_info = hass_init_binary_sensor_device_info(i, false);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = NULL;
discoveryQueued = true;
}
}
}
if(CFG_HasFlag(OBK_FLAG_MQTT_BROADCASTSELFSTATEPERMINUTE) || CFG_HasFlag(OBK_FLAG_MQTT_BROADCASTSELFSTATEONCONNECT)) {
//use -1 for channel as these don't correspond to channels
#ifndef NO_CHIP_TEMPERATURE
dev_info = hass_init_sensor_device_info(HASS_TEMP, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
#endif
dev_info = hass_init_sensor_device_info(HASS_RSSI, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = hass_init_sensor_device_info(HASS_UPTIME, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = hass_init_sensor_device_info(HASS_BUILD, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = hass_init_sensor_device_info(HASS_SSID, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
dev_info = hass_init_sensor_device_info(HASS_IP, -1, -1, -1, 1);
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
hass_free_device_info(dev_info);
discoveryQueued = true;
}
if (discoveryQueued) {
MQTT_InvokeCommandAtEnd(PublishChannels);
}
else {
const char* msg = "No relay, PWM, sensor or power driver running.";
if (request) {
poststr(request, msg);
poststr(request, NULL);
}
else {
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "HA discovery: %s\r\n", msg);
}
}
}
/// @brief Sends HomeAssistant discovery MQTT messages.
/// @param request
/// @return
int http_fn_ha_discovery(http_request_t* request) {
char topic[32];
http_setup(request, httpMimeTypeText);
if (MQTT_IsReady() == false) {
poststr(request, "MQTT not running.");
poststr(request, NULL);
return 0;
}
// even if it returns the empty HA topic,
// the function call below will set default
http_getArg(request->url, "prefix", topic, sizeof(topic));
doHomeAssistantDiscovery(topic, request);
poststr(request, "MQTT discovery queued.");
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_OLD_YAML_GENERATOR
void http_generate_singleColor_cfg(http_request_t* request, const char* clientId) {
hprintf255(request, " command_topic: \"cmnd/%s/led_enableAll\"\n", clientId);
hprintf255(request, " state_topic: \"%s/led_enableAll/get\"\n", clientId);
hprintf255(request, " availability_topic: \"%s/connected\"\n", clientId);
hprintf255(request, " payload_on: 1\n");
hprintf255(request, " payload_off: 0\n");
hprintf255(request, " brightness_command_topic: \"cmnd/%s/led_dimmer\"\n", clientId);
hprintf255(request, " brightness_state_topic: \"%s/led_dimmer/get\"\n", clientId);
hprintf255(request, " brightness_scale: 100\n");
}
void http_generate_rgb_cfg(http_request_t* request, const char* clientId) {
hprintf255(request, " rgb_command_template: \"{{ '#%%02x%%02x%%02x0000' | format(red, green, blue)}}\"\n");
hprintf255(request, " rgb_value_template: \"{{ value[0:2]|int(base=16) }},{{ value[2:4]|int(base=16) }},{{ value[4:6]|int(base=16) }}\"\n");
hprintf255(request, " rgb_state_topic: \"%s/led_basecolor_rgb/get\"\n", clientId);
hprintf255(request, " rgb_command_topic: \"cmnd/%s/led_basecolor_rgb\"\n", clientId);
http_generate_singleColor_cfg(request, clientId);
}
void http_generate_cw_cfg(http_request_t* request, const char* clientId) {
hprintf255(request, " color_temp_command_topic: \"cmnd/%s/led_temperature\"\n", clientId);
hprintf255(request, " color_temp_state_topic: \"%s/led_temperature/get\"\n", clientId);
http_generate_singleColor_cfg(request, clientId);
}
void hprintf_qos_payload(http_request_t* request, const char* clientId) {
poststr(request, " qos: 1\n");
poststr(request, " payload_on: 1\n");
poststr(request, " payload_off: 0\n");
poststr(request, " retain: true\n");
hprintf255(request, " availability:\n");
hprintf255(request, " - topic: \"%s/connected\"\n", clientId);
}
#endif
#if ENABLE_HA_DISCOVERY
int http_fn_ha_cfg(http_request_t* request) {
int relayCount;
int pwmCount;
int dInputCount;
const char* shortDeviceName;
const char* clientId;
int i;
char mqttAdded = 0;
char switchAdded = 0;
char lightAdded = 0;
i = 0;
shortDeviceName = CFG_GetShortDeviceName();
clientId = CFG_GetMQTTClientId();
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Home Assistant Setup");
poststr_h4(request, "Home Assistant Cfg");
hprintf255(request, "<h4>Note that your short device name is: %s</h4>", shortDeviceName);
#if ENABLE_OLD_YAML_GENERATOR
poststr_h4(request, "Paste this to configuration yaml");
poststr(request, "<h5>Make sure that you have \"switch:\" keyword only once! Home Assistant doesn't like dup keywords.</h5>");
poststr(request, "<h5>You can also use \"switch MyDeviceName:\" to avoid keyword duplication!</h5>");
poststr(request, "<textarea rows=\"40\" cols=\"50\">");
PIN_get_Relay_PWM_Count(&relayCount, &pwmCount, &dInputCount);
if (relayCount > 0) {
for (i = 0; i < CHANNEL_MAX; i++) {
if (h_isChannelRelay(i)) {
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " switch:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", RELAY, i, 0);
hprintf255(request, " name: %i\n", i);
hprintf255(request, " state_topic: \"%s/%i/get\"\n", clientId, i);
hprintf255(request, " command_topic: \"%s/%i/set\"\n", clientId, i);
hprintf_qos_payload(request, clientId);
}
}
}
if (dInputCount > 0) {
for (i = 0; i < CHANNEL_MAX; i++) {
if (h_isChannelDigitalInput(i)) {
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " binary_sensor:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", BINARY_SENSOR, i, 0);
hprintf255(request, " name: %i\n", i);
hprintf255(request, " state_topic: \"%s/%i/get\"\n", clientId, i);
hprintf_qos_payload(request, clientId);
}
}
}
#if ENABLE_LED_BASIC
if (pwmCount == 5 || LED_IsLedDriverChipRunning()) {
// Enable + RGB control + CW control
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " light:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", LIGHT_RGBCW, i, 0);
hprintf255(request, " name: %i\n", i);
http_generate_rgb_cfg(request, clientId);
//hprintf255(request, " #brightness_value_template: \"{{ value }}\"\n");
hprintf255(request, " color_temp_command_topic: \"cmnd/%s/led_temperature\"\n", clientId);
hprintf255(request, " color_temp_state_topic: \"%s/led_temperature/get\"\n", clientId);
//hprintf255(request, " #color_temp_value_template: \"{{ value }}\"\n");
}
else
if (pwmCount == 3) {
// Enable + RGB control
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " light:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", LIGHT_RGB, i, 0);
hprintf255(request, " name: Light\n");
http_generate_rgb_cfg(request, clientId);
}
else if (pwmCount == 1) {
// single color
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " light:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", LIGHT_PWM, i, 0);
hprintf255(request, " name: Light\n");
http_generate_singleColor_cfg(request, clientId);
}
else if (pwmCount == 2) {
// CW
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (switchAdded == 0) {
poststr(request, " light:\n");
switchAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", LIGHT_PWMCW, i, 0);
hprintf255(request, " name: Light\n");
http_generate_cw_cfg(request, clientId);
}
else if (pwmCount > 0) {
for (i = 0; i < CHANNEL_MAX; i++) {
if (h_isChannelPWM(i)) {
if (mqttAdded == 0) {
poststr(request, "mqtt:\n");
mqttAdded = 1;
}
if (lightAdded == 0) {
poststr(request, " light:\n");
lightAdded = 1;
}
hass_print_unique_id(request, " - unique_id: \"%s\"\n", LIGHT_PWM, i, 0);
hprintf255(request, " name: %i\n", i);
hprintf255(request, " state_topic: \"%s/%i/get\"\n", clientId, i);
hprintf255(request, " command_topic: \"%s/%i/set\"\n", clientId, i);
hprintf255(request, " brightness_command_topic: \"%s/%i/set\"\n", clientId, i);
poststr(request, " on_command_type: \"brightness\"\n");
poststr(request, " brightness_scale: 99\n");
poststr(request, " qos: 1\n");
poststr(request, " payload_on: 99\n");
poststr(request, " payload_off: 0\n");
poststr(request, " retain: true\n");
poststr(request, " optimistic: true\n");
hprintf255(request, " availability:\n");
hprintf255(request, " - topic: \"%s/connected\"\n", clientId);
}
}
}
#endif
poststr(request, "</textarea>");
#endif
poststr(request, "<br/><div><label for=\"ha_disc_topic\">Discovery topic:</label><input id=\"ha_disc_topic\" value=\"homeassistant\"><button onclick=\"send_ha_disc();\">Start Home Assistant Discovery</button>&nbsp;<form action=\"cfg_mqtt\" class='disp-inline'><button type=\"submit\">Configure MQTT</button></form></div><br/>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, ha_discovery_script);
poststr(request, NULL);
return 0;
}
#endif
void runHTTPCommandInternal(http_request_t* request, const char *cmd) {
bool bEchoHack = strncmp(cmd, "echo", 4) == 0;
CMD_ExecuteCommand(cmd, COMMAND_FLAG_SOURCE_HTTP);
#if ENABLE_TASMOTA_JSON
if (!bEchoHack) {
JSON_ProcessCommandReply(cmd, skipToNextWord(cmd), request, (jsonCb_t)hprintf255, COMMAND_FLAG_SOURCE_HTTP);
}
else {
const char *s = Tokenizer_GetArg(0);
poststr(request, s);
}
#endif
}
int http_fn_cm(http_request_t* request) {
char tmpA[128];
char* long_str_alloced = 0;
int commandLen = 0;
http_setup(request, httpMimeTypeJson);
// exec command
if (request->method == HTTP_GET) {
commandLen = http_getArg(request->url, "cmnd", tmpA, sizeof(tmpA));
//ADDLOG_INFO(LOG_FEATURE_HTTP, "Got here (GET) %s;%s;%d\n", request->url, tmpA, commandLen);
} else if (request->method == HTTP_POST || request->method == HTTP_PUT) {
commandLen = http_getRawArg(request->bodystart, "cmnd", tmpA, sizeof(tmpA));
//ADDLOG_INFO(LOG_FEATURE_HTTP, "Got here (POST) %s;%s;%d\n", request->bodystart, tmpA, commandLen);
}
if (commandLen) {
if (commandLen > (sizeof(tmpA) - 5)) {
commandLen += 8;
long_str_alloced = (char*)malloc(commandLen);
if (long_str_alloced) {
if (request->method == HTTP_GET) {
http_getArg(request->url, "cmnd", long_str_alloced, commandLen);
} else if (request->method == HTTP_POST || request->method == HTTP_PUT) {
http_getRawArg(request->bodystart, "cmnd", long_str_alloced, commandLen);
}
CMD_ExecuteCommand(long_str_alloced, COMMAND_FLAG_SOURCE_HTTP);
runHTTPCommandInternal(request, long_str_alloced);
free(long_str_alloced);
}
}
else {
runHTTPCommandInternal(request, tmpA);
}
}
poststr(request, NULL);
return 0;
}
int http_fn_cfg(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Config");
postFormAction(request, "cfg_pins", "Configure Module");
#if ENABLE_HTTP_FLAGS
postFormAction(request, "cfg_generic", "Configure General/Flags");
#endif
#if ENABLE_HTTP_STARTUP
postFormAction(request, "cfg_startup", "Configure Startup");
#endif
#if ENABLE_HTTP_DGR
postFormAction(request, "cfg_dgr", "Configure Device Groups");
#endif
postFormAction(request, "cfg_wifi", "Configure WiFi &amp; Web");
#if ENABLE_HTTP_IP
postFormAction(request, "cfg_ip", "Configure IP");
#endif
#if (ENABLE_DRIVER_DS1820_FULL)
postFormAction(request, "cfg_ds18b20", "Configure DS18B20 Sensors");
#endif
postFormAction(request, "cfg_mqtt", "Configure MQTT");
#if ENABLE_HTTP_NAMES
postFormAction(request, "cfg_name", "Configure Names");
#endif
#if ENABLE_HTTP_MAC
postFormAction(request, "cfg_mac", "Change MAC");
#endif
#if ENABLE_HTTP_PING
postFormAction(request, "cfg_ping", "Ping Watchdog (network lost restarter)");
#endif
#if ENABLE_HTTP_WEBAPP
postFormAction(request, "cfg_webapp", "Configure WebApp");
#endif
#if ENABLE_HA_DISCOVERY
postFormAction(request, "ha_cfg", "Home Assistant Configuration");
#endif
postFormAction(request, "ota", "OTA (update software by WiFi)");
postFormAction(request, "cmd_tool", "Execute Custom Command");
#if ENABLE_HTTP_STARTUP
postFormAction(request, "startup_command", "Change Startup Command Text");
#endif
#if 0
#if PLATFORM_BK7231T | PLATFORM_BK7231N
{
int i, j, k;
k = config_get_tableOffsets(BK_PARTITION_NET_PARAM, &i, &j);
hprintf255(request, "BK_PARTITION_NET_PARAM: bOk %i, at %i, len %i<br>", k, i, j);
k = config_get_tableOffsets(BK_PARTITION_RF_FIRMWARE, &i, &j);
hprintf255(request, "BK_PARTITION_RF_FIRMWARE: bOk %i, at %i, len %i<br>", k, i, j);
k = config_get_tableOffsets(BK_PARTITION_OTA, &i, &j);
hprintf255(request, "BK_PARTITION_OTA: bOk %i, at %i, len %i<br>", k, i, j);
}
#endif
#endif
poststr(request, htmlFooterReturnToMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_cfg_pins(http_request_t* request) {
int iChanged = 0;
int iChangedRequested = 0;
int i;
char tmpA[128];
char tmpB[64];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Pin config");
#if 0
poststr(request, "<script src=\"https://openbekeniot.github.io/webapp/test1.js\"></script>");
//poststr(request, "<script src=\"http://localhost:8080/test1.js\"></script>");
poststr(request, "<script>createBeforeMain();</script>");
#endif
poststr(request, "<p>The first field assigns a role to the given pin. The next field is used to enter channel index (relay index), used to support multiple relays and buttons. ");
poststr(request, "So, first button and first relay should have channel 1, second button and second relay have channel 2, etc.</p>");
poststr(request, "<p>Only for button roles another field will be provided to enter channel to toggle when doing double click. ");
poststr(request, "It shows up when you change role to button.</p>");
#if PLATFORM_BK7231N || PLATFORM_BK7231T
poststr(request, "<p>BK7231N/BK7231T supports PWM only on pins 6, 7, 8, 9, 24 and 26!</p>");
#endif
for (i = 0; i < PLATFORM_GPIO_MAX; i++) {
sprintf(tmpA, "%i", i);
if (http_getArg(request->url, tmpA, tmpB, sizeof(tmpB))) {
int role;
int pr;
iChangedRequested++;
role = atoi(tmpB);
pr = PIN_GetPinRoleForPinIndex(i);
if (pr != role) {
PIN_SetPinRoleForPinIndex(i, role);
iChanged++;
}
}
sprintf(tmpA, "r%i", i);
if (http_getArg(request->url, tmpA, tmpB, sizeof(tmpB))) {
int rel;
int prevRel;
iChangedRequested++;
rel = atoi(tmpB);
prevRel = PIN_GetPinChannelForPinIndex(i);
if (prevRel != rel) {
PIN_SetPinChannelForPinIndex(i, rel);
iChanged++;
}
}
sprintf(tmpA, "e%i", i);
if (http_getArg(request->url, tmpA, tmpB, sizeof(tmpB))) {
int rel;
int prevRel;
iChangedRequested++;
rel = atoi(tmpB);
prevRel = PIN_GetPinChannel2ForPinIndex(i);
if (prevRel != rel) {
PIN_SetPinChannel2ForPinIndex(i, rel);
iChanged++;
}
}
}
if (iChangedRequested > 0) {
// Anecdotally, if pins are configured badly, the
// second-timer breaks. To reconfigure, force
// saving the configuration instead of waiting.
//CFG_Save_SetupTimer();
CFG_Save_IfThereArePendingChanges();
// Invoke Hass discovery if configuration has changed and not in safe mode.
#if ENABLE_HA_DISCOVERY
if (!bSafeMode && CFG_HasFlag(OBK_FLAG_AUTOMAIC_HASS_DISCOVERY)) {
Main_ScheduleHomeAssistantDiscovery(1);
}
#endif
hprintf255(request, "Pins update - %i reqs, %i changed!<br><br>", iChangedRequested, iChanged);
}
// strcat(outbuf,"<button type=\"button\">Click Me!</button>");
poststr(request, "<form action=\"cfg_pins\" id=\"x\">");
poststr(request, "<script> var r = [");
for (i = 0; i < IOR_Total_Options; i++) {
if (i) {
poststr(request, ",");
}
// print array with ["name_of_role",<Number of channnels for this role>]
hprintf255(request, "[\"%s\",%i]", htmlPinRoleNames[i],PIN_IOR_NofChan(i));
}
poststr(request, "];");
poststr(request, "var sr = r.map((e,i)=>{return e[0]+'#'+i}).sort(Intl.Collator().compare).map(e=>e.split('#'));");
poststr(request, "function hide_show() {"
"n=this.name;"
"er=getElement('r'+n);"
"ee=getElement('e'+n);"
"ch=r[this.value][1];" // since we might have skiped PWM entries in options list, don't use "selectedIndex" but "value" (it's even shorter ;-)
"er.disabled = (ch<1); er.style.display= ch<1 ? 'none' : 'inline';"
"ee.disabled = (ch<2); ee.style.display= ch<2 ? 'none' : 'inline';"
"}");
poststr(request, "function f(alias, id, c, b, ch1, ch2) {"
"let f = document.getElementById(\"x\");"
"let d = document.createElement(\"div\");"
"d.className = \"hdiv\";"
"d.innerHTML = \"<span class='disp-inline' style='min-width: 15ch'>\"+alias+\"</span>\";"
"f.appendChild(d);"
"let s = document.createElement(\"select\");"
"s.className = \"hele\";"
"s.name = id;"
"d.appendChild(s);"
" for (var i = 0; i < sr.length; i++) {"
" if(b && sr[i][0].startsWith(\"PWM\")) continue; "
"var o = document.createElement(\"option\");"
" o.text = sr[i][0];"
" o.value = sr[i][1];"
" o.selected = (sr[i][1] == c);"
"s.add(o);s.onchange = hide_show;"
"}"
"var y = document.createElement(\"input\");"
"y.className = \"hele\";"
"y.name = \"r\"+id;"
"y.id = \"r\"+id;"
"y.disabled = ch1==null;"
"y.style.display = ch1==null ? 'none' :'inline' ;"
"y.value = ch1==null ? 0 : ch1;"
"d.appendChild(y);"
"y = document.createElement(\"input\");"
"y.className = \"hele\";"
"y.name = \"e\"+id;"
"y.id = \"e\"+id;"
"y.disabled = ch2==null ;"
"y.style.display = ch2==null ? 'none' :'inline' ;"
"y.value = ch2==null ? 0 : ch2;"
"d.appendChild(y);"
" }");
for (i = 0; i < PLATFORM_GPIO_MAX; i++) {
// On BL602, any GPIO can be mapped to one of 5 PWM channels
// But on Beken chips, only certain pins can be PWM
int bCanThisPINbePWM;
int si, ch, ch2;
const char* alias;
si = PIN_GetPinRoleForPinIndex(i);
ch = PIN_GetPinChannelForPinIndex(i);
ch2 = PIN_GetPinChannel2ForPinIndex(i);
// if available..
alias = HAL_PIN_GetPinNameAlias(i);
bCanThisPINbePWM = HAL_PIN_CanThisPinBePWM(i);
si = PIN_GetPinRoleForPinIndex(i);
hprintf255(request, "f(\"");
if (alias) {
#if defined(PLATFORM_BEKEN) || defined(WINDOWS)
hprintf255(request, "P%i (%s) ", i, alias);
#else
poststr(request, alias);
poststr(request, " ");
#endif
}
else {
hprintf255(request, "P%i ", i);
}
hprintf255(request, "\",%i,%i, %i,", i, si, !bCanThisPINbePWM);
// Primary linked channel
int NofC = PIN_IOR_NofChan(si);
if (NofC >= 1)
{
hprintf255(request, "%i,", ch);
}
// Some roles do not need any channels
else {
hprintf255(request, "null,", ch);
}
// Secondary linked channel
if (NofC > 1)
{
hprintf255(request, "%i,", ch2);
}
else {
hprintf255(request, "null,", ch);
}
hprintf255(request, ");");
}
poststr(request, " </script>");
poststr(request, "<input type=\"submit\" value=\"Save\"/></form>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#if ENABLE_HTTP_FLAGS
const char* g_obk_flagNames[] = {
"[MQTT] Broadcast led params together (send dimmer and color when dimmer or color changes, topic name: YourDevName/led_basecolor_rgb/get, YourDevName/led_dimmer/get)",
"[MQTT] Broadcast led final color (topic name: YourDevName/led_finalcolor_rgb/get)",
"[MQTT] Broadcast self state every N (def: 60) seconds (delay configurable by 'mqtt_broadcastInterval' and 'mqtt_broadcastItemsPerSec' commands)",
"[LED][Debug] Show raw PWM controller on WWW index instead of new LED RGB/CW/etc picker",
"[LED] Force show RGBCW controller (for example, for SM2135 LEDs, or for DGR sender)",
"[CMD] Enable TCP console command server (for PuTTY, etc)",
"[BTN] Instant touch reaction instead of waiting for release (aka SetOption 13)",
"[MQTT] [Debug] Always set Retain flag to all published values",
"[LED] Alternate CW light mode (first PWM for warm/cold slider, second for brightness)",
"[SM2135] Use separate RGB/CW modes instead of writing all 5 values as RGB",
"[MQTT] Broadcast self state on MQTT connect",
"[PWM] BK7231 use 600hz instead of 1khz default",
"[LED] Remember LED driver state (RGBCW, enable, brightness, temperature) after reboot",
"[HTTP] Show actual PIN logic level for unconfigured pins",
"[IR] Do MQTT publish (RAW STRING) for incoming IR data",
"[IR] Allow 'unknown' protocol",
"[MQTT] Broadcast led final color RGBCW (topic name: YourDevName/led_finalcolor_rgbcw/get)",
"[LED] Automatically enable Light when changing brightness, color or temperature on WWW panel",
"[LED] Smooth transitions for LED (EXPERIMENTAL)",
"[MQTT] Always publish channels used by TuyaMCU",
"[LED] Force RGB mode (3 PWMs for LEDs) and ignore further PWMs if they are set",
"[MQTT] Retain power channels (Relay channels, etc)",
"[IR] Do MQTT publish (Tasmota JSON format) for incoming IR data",
"[LED] Automatically enable Light on any change of brightness, color or temperature",
"[LED] Emulate Cool White with RGB in device with four PWMs - Red is 0, Green 1, Blue 2, and Warm is 4",
"[POWER] Allow negative current/power for power measurement (all chips, BL0937, BL0942, etc)",
// On BL602, if marked, uses /dev/ttyS1, otherwise S0
// On Beken, if marked, uses UART2, otherwise UART1
"[UART] Use alternate UART for BL0942, CSE, TuyaMCU, etc",
"[HASS] Invoke HomeAssistant discovery on change to ip address, configuration",
"[LED] Setting RGB white (FFFFFF) enables temperature mode",
"[NETIF] Use short device name as a hostname instead of a long name",
"[MQTT] Enable Tasmota TELE etc publishes (for ioBroker etc)",
"[UART] Enable UART command line",
"[LED] Use old linear brightness mode, ignore gamma ramp",
"[MQTT] Apply channel type multiplier on (if any) on channel value before publishing it",
"[MQTT] In HA discovery, add relays as lights",
"[HASS] Deactivate avty_t flag when publishing to HASS (permit to keep value). You must restart HASS discovery for change to take effect.",
"[DRV] Deactivate Autostart of all drivers",
"[WiFi] Quick connect to WiFi on reboot (TODO: check if it works for you and report on github)",
"[Power] Set power and current to zero if all relays are open",
"[MQTT] [Debug] Publish all channels (don't enable it, it will be publish all 64 possible channels on connect)",
"[MQTT] Use kWh unit for energy consumption (total, last hour, today) instead of Wh",
"[BTN] Ignore all button events (aka child lock)",
"[DoorSensor] Invert state",
"[TuyaMCU] Use queue",
"[HTTP] Disable authentication in safe mode (not recommended)",
"[MQTT Discovery] Don't merge toggles and dimmers into lights",
"[TuyaMCU] Store raw data",
"[TuyaMCU] Store ALL data",
"[PWR] Invert AC dir",
"[HTTP] Hide ON/OFF for relays (only red/green buttons)",
"[MQTT] Never add GET suffix",
"[WiFi] (RTL/BK/BL602) Enhanced fast connect by saving AP data to flash (preferable with Flag 37 & static ip). Quick reset 3 times to connect normally",
"error",
"error",
"error",
"error",
};
void uint64_to_str(uint64_t num, char* str) {
char temp[21]; // uint64_t 20 numbers + \0
int i = 0;
if (num == 0) {
temp[i++] = '0';
} else {
while (num > 0) {
temp[i++] = '0' + (num % 10);
num /= 10;
}
}
temp[i] = '\0';
int j;
for (j = 0; j < i; j++) {
str[j] = temp[i - j - 1];
}
str[j] = '\0';
}
int http_fn_cfg_generic(http_request_t* request) {
int i;
char tmpA[64];
char tmpB[64];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Generic config");
if (http_getArg(request->url, "boot_ok_delay", tmpA, sizeof(tmpA))) {
i = atoi(tmpA);
if (i <= 0) {
poststr(request, "<h5>Boot ok delay must be at least 1 second<h5>");
i = 1;
}
hprintf255(request, "<h5>Setting boot OK delay to %i<h5>", i);
CFG_SetBootOkSeconds(i);
}
if (http_getArg(request->url, "setFlags", tmpA, sizeof(tmpA))) {
for (i = 0; i < OBK_TOTAL_FLAGS; i++) {
int ni;
sprintf(tmpB, "flag%i", i);
if (http_getArg(request->url, tmpB, tmpA, sizeof(tmpA))) {
ni = atoi(tmpA);
}
else {
ni = 0;
}
//hprintf255(request, "<h5>Setting flag %i to %i<h5>", i, ni);
CFG_SetFlag(i, ni);
}
}
CFG_Save_IfThereArePendingChanges();
// 32 bit type
//hprintf255(request, "<h4>Flags (Current value=%i)</h4>", CFG_GetFlags());
// 64 bit - TODO fixme
//hprintf255(request, "<h4>Flags (Current value=%llu)</h4>", CFG_GetFlags64());
char buf[21];
uint64_to_str(CFG_GetFlags64(), buf);
hprintf255(request, "<h4>Flags (Current value=%s)</h4>", buf);
poststr(request, "<form action=\"/cfg_generic\">");
for (i = 0; i < OBK_TOTAL_FLAGS; i++) {
const char* flagName = g_obk_flagNames[i];
/*
<div><input type="checkbox" name="flag0" id="flag0" value="1" checked>
<label for="flag0">Flag 0 - [MQTT] Broadcast led params together (send dimmer and color when dimmer or color changes, topic name: YourDevName/led_basecolor_rgb/get, YourDevName/led_dimmer/get)</label>
</div>
*/
hprintf255(request, "<div><input type=\"checkbox\" name=\"flag%i\" id=\"flag%i\" value=\"1\"%s>",
i, i, (CFG_HasFlag(i) ? " checked" : "")); //this is less that 128 char
hprintf255(request, "<label for=\"flag%i\">Flag %i - ", i, i);
poststr(request, flagName);
poststr(request, "</label></div>");
}
poststr(request, "<input type=\"hidden\" id=\"setFlags\" name=\"setFlags\" value=\"1\">");
poststr(request, SUBMIT_AND_END_FORM);
add_label_numeric_field(request, "Uptime in seconds required to mark boot as OK", "boot_ok_delay",
CFG_GetBootOkSeconds(), "<form action=\"/cfg_generic\">");
poststr(request, "<br><input type=\"submit\" value=\"Save\"/></form>");
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HTTP_STARTUP
int http_fn_cfg_startup(http_request_t* request) {
int channelIndex;
int newValue;
int i;
char tmpA[128];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Config startup");
poststr_h4(request, "Here you can set pin start values");
poststr(request, "<ul><li>For relays, use 1 or 0</li>");
poststr(request, "<li>To 'remember last power state', use -1 as a special value</li>");
poststr(request, "<li>For dimmers, range is 0 to 100</li>");
poststr(request, "<li>For custom values, you can set any numeric value</li>");
poststr(request, "<li>Remember that you can also use short <a href='startup_command'>startup command</a> to run commands like led_baseColor #FF0000 and led_enableAll 1 etc</li>");
hprintf255(request, "<li>To remember last state of LED driver, set ");
hprintf255(request, "<a href='cfg_generic'>Flag 12 - %s</a>", g_obk_flagNames[12]);
poststr(request, "</li></ul>");
if (http_getArg(request->url, "idx", tmpA, sizeof(tmpA))) {
channelIndex = atoi(tmpA);
if (http_getArg(request->url, "value", tmpA, sizeof(tmpA))) {
newValue = atoi(tmpA);
CFG_SetChannelStartupValue(channelIndex, newValue);
// also save current value if marked as saved
Channel_SaveInFlashIfNeeded(channelIndex);
hprintf255(request, "<h5>Setting channel %i start value to %i<h5>", channelIndex, newValue);
CFG_Save_IfThereArePendingChanges();
}
}
poststr_h4(request, "New start values");
for (i = 0; i < CHANNEL_MAX; i++) {
if (CHANNEL_IsInUse(i)) {
int startValue = CFG_GetChannelStartupValue(i);
poststr(request, "<form action='/cfg_startup' class='indent'>");
hprintf255(request, "<input type=\"hidden\" id=\"idx\" name=\"idx\" value=\"%i\"/>", i);
sprintf(tmpA, "Channel %i", i);
add_label_numeric_field(request, tmpA, "value", startValue, "");
poststr(request, "<input type=\"submit\" value=\"Save\"/></form><br/>");
}
}
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
#if ENABLE_HTTP_DGR
int http_fn_cfg_dgr(http_request_t* request) {
char tmpA[128];
bool bForceSet;
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "Device groups");
hprintf255(request, "<h5>Here you can configure Tasmota Device Groups<h5>");
if (http_getArg(request->url, "bSet", tmpA, sizeof(tmpA))) {
bForceSet = true;
}
else {
bForceSet = false;
}
if (http_getArg(request->url, "name", tmpA, sizeof(tmpA)) || bForceSet) {
int newSendFlags;
int newRecvFlags;
newSendFlags = 0;
newRecvFlags = 0;
if (http_getArgInteger(request->url, "s_pwr"))
newSendFlags |= DGR_SHARE_POWER;
if (http_getArgInteger(request->url, "r_pwr"))
newRecvFlags |= DGR_SHARE_POWER;
if (http_getArgInteger(request->url, "s_lbr"))
newSendFlags |= DGR_SHARE_LIGHT_BRI;
if (http_getArgInteger(request->url, "r_lbr"))
newRecvFlags |= DGR_SHARE_LIGHT_BRI;
if (http_getArgInteger(request->url, "s_lcl"))
newSendFlags |= DGR_SHARE_LIGHT_COLOR;
if (http_getArgInteger(request->url, "r_lcl"))
newRecvFlags |= DGR_SHARE_LIGHT_COLOR;
CFG_DeviceGroups_SetName(tmpA);
CFG_DeviceGroups_SetSendFlags(newSendFlags);
CFG_DeviceGroups_SetRecvFlags(newRecvFlags);
if (tmpA[0] != 0) {
#ifndef OBK_DISABLE_ALL_DRIVERS
DRV_StartDriver("DGR");
#endif
}
CFG_Save_IfThereArePendingChanges();
}
{
int newSendFlags;
int newRecvFlags;
const char* groupName = CFG_DeviceGroups_GetName();
newSendFlags = CFG_DeviceGroups_GetSendFlags();
newRecvFlags = CFG_DeviceGroups_GetRecvFlags();
add_label_text_field(request, "Group name", "name", groupName, "<form action=\"/cfg_dgr\">");
poststr(request, "<br><table><tr><th>Name</th><th>Tasmota Code</th><th>Receive</th><th>Send</th></tr><tr><td>Power</td><td>1</td>");
poststr(request, "<td><input type=\"checkbox\" name=\"r_pwr\" value=\"1\"");
if (newRecvFlags & DGR_SHARE_POWER)
poststr(request, " checked");
poststr(request, "></td><td><input type=\"checkbox\" name=\"s_pwr\" value=\"1\"");
if (newSendFlags & DGR_SHARE_POWER)
poststr(request, " checked");
poststr(request, "></td> ");
poststr(request, "</tr><tr><td>Light Brightness</td><td>2</td>");
poststr(request, "<td><input type=\"checkbox\" name=\"r_lbr\" value=\"1\"");
if (newRecvFlags & DGR_SHARE_LIGHT_BRI)
poststr(request, " checked");
poststr(request, "></td><td><input type=\"checkbox\" name=\"s_lbr\" value=\"1\"");
if (newSendFlags & DGR_SHARE_LIGHT_BRI)
poststr(request, " checked");
poststr(request, "></td> ");
poststr(request, "</tr><tr><td>Light Color</td><td>16</td>");
poststr(request, "<td><input type=\"checkbox\" name=\"r_lcl\" value=\"1\"");
if (newRecvFlags & DGR_SHARE_LIGHT_COLOR)
poststr(request, " checked");
poststr(request, "></td><td><input type=\"checkbox\" name=\"s_lcl\" value=\"1\"");
if (newSendFlags & DGR_SHARE_LIGHT_COLOR)
poststr(request, " checked");
poststr(request, "></td> ");
poststr(request, "<input type=\"hidden\" name=\"bSet\" value=\"1\">");
poststr(request, "</tr></table>");
poststr(request, SUBMIT_AND_END_FORM);
}
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
#endif
void OTA_RequestDownloadFromHTTP(const char* s) {
#if PLATFORM_BEKEN
otarequest(s);
#elif PLATFORM_ECR6600
extern int http_client_download_file(const char* url);
extern int ota_done(bool reset);
delay_ms(100);
int ret = http_client_download_file(s);
if(ret != -1) ota_done(1);
else ota_done(0);
#elif PLATFORM_W600 || PLATFORM_W800
t_http_fwup(s);
#elif PLATFORM_XRADIO
uint32_t* verify_value;
ota_verify_t verify_type;
ota_verify_data_t verify_data;
if(ota_get_image(OTA_PROTOCOL_HTTP, s) != OTA_STATUS_OK)
{
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "OTA http get image failed");
return;
}
if(ota_get_verify_data(&verify_data) != OTA_STATUS_OK)
{
verify_type = OTA_VERIFY_NONE;
verify_value = NULL;
}
else
{
verify_type = verify_data.ov_type;
verify_value = (uint32_t*)(verify_data.ov_data);
}
if(ota_verify_image(verify_type, verify_value) != OTA_STATUS_OK)
{
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "OTA http verify image failed");
return;
}
ota_reboot();
#elif PLATFORM_REALTEK_NEW
ota_context* ctx = NULL;
ctx = (ota_context*)malloc(sizeof(ota_context));
if(ctx == NULL) goto exit;
memset(ctx, 0, sizeof(ota_context));
char url[256] = { 0 };
char resource[256] = { 0 };
uint16_t port;
parser_url(s, &url, &port, &resource, 256);
int ret = ota_update_init(ctx, &url, port, &resource, OTA_HTTP);
if(ret != 0)
{
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "ota_update_init failed");
goto exit;
}
ret = ota_update_start(ctx);
if(!ret)
{
addLogAdv(LOG_INFO, LOG_FEATURE_HTTP, "OTA finished");
sys_clear_ota_signature();
delay_ms(50);
sys_reset();
}
exit:
ota_update_deinit(ctx);
addLogAdv(LOG_ERROR, LOG_FEATURE_HTTP, "OTA failed");
if(ctx) free(ctx);
#endif
}
int http_fn_ota_exec(http_request_t* request) {
char tmpA[128];
//char tmpB[64];
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "OTA request");
if (http_getArg(request->url, "host", tmpA, sizeof(tmpA))) {
hprintf255(request, "<h3>OTA requested for %s!</h3>", tmpA);
addLogAdv(LOG_INFO, LOG_FEATURE_HTTP, "http_fn_ota_exec: will try to do OTA for %s \r\n", tmpA);
OTA_RequestDownloadFromHTTP(tmpA);
}
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_ota(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
http_html_start(request, "OTA system");
#ifndef OBK_OTA_EXTENSION
poststr(request, "<h3>Sorry, OTA update not implemented for " DEVICENAME_PREFIX_FULL " </h3>");
#else
poststr(request, "<p>It's recommended to use the OTA option in the Web Application, where you can easily drag and drop files.<br><br>If you have an HTTP server providing the OTA file, you may enter the URL below. "
#if PLATFORM_BEKEN
" On Beken platforms, the .rbl file is used for OTA updates."
#endif
"</p>");
add_label_text_field(request, "URL for ota firmware file", "host", "", "<form action=\"/ota_exec\">");
poststr(request, "<br>\
<input type=\"submit\" value=\"Submit\" onclick=\"return confirm('Are you sure?')\">\
</form>");
const char htmlOTA[] = "<script>var o=document.getElementById('otafile'),d=document.querySelector('dialog'),h=document.getElementById('hint'),D='OTA started! Please wait ',R=/" DEVICENAME_PREFIX_FULL "_.*"
#ifdef OBK_OTA_NAME_EXTENSION
OBK_OTA_NAME_EXTENSION
#endif
OBK_OTA_EXTENSION "/,SR=R.source,mr=(e)=>e.name.match(R);doota=()=>{f=o.files[0];if(f&&(f)){d.showModal();var t=30;setTimeout(()=>{d.close(),location.href='/'},1e3*t),setInterval(()=>d.innerHTML=D+t--+' secs',1e3),fetch('/api/ota',{method:'POST',body:f}).then((e)=>{e.ok&&fetch('/index?restart=1')})}else alert(f?'filename invalid':'no file selected')};d.innerHTML=D,o.addEventListener('change',((e)=>{const t=e.target.files[0];if(!t)return;h.innerHTML=mr(t)?'':'Selected file does <b>not</b> match required format '+SR+'!'}))</script>";
poststr(request, "<br><br><br>Expert feature: Upload firmware OTA file.<br>If unsure, please use Web App!<br><span id='hint' style='color: yellow;'></span><br><br>");
poststr(request, "<input id='otafile' type='file' accept='" OBK_OTA_EXTENSION "'>");
poststr(request, "<input type='button' class='bred' onclick='doota();' value='START OTA - No file check - will reboot after OTA'><dialog></dialog>");
poststr(request, htmlOTA);
#endif
poststr(request, htmlFooterReturnToCfgOrMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}
int http_fn_other(http_request_t* request) {
http_setup(request, httpMimeTypeHTML);
#if ENABLE_OBK_BERRY
if (CMD_Berry_RunEventHandlers_StrPtr(CMD_EVENT_ON_HTTP, request->url, request)) {
return 0;
}
#endif
http_html_start(request, "Not found");
poststr(request, "Not found.<br/>");
poststr(request, htmlFooterReturnToMainPage);
http_html_end(request);
poststr(request, NULL);
return 0;
}