mirror of https://github.com/arendst/Tasmota.git
541 lines
17 KiB
C++
541 lines
17 KiB
C++
/**
|
|
* esp-knx-ip library for KNX/IP communication on an ESP8266
|
|
* Author: Nico Weichbrodt <envy>
|
|
* License: MIT
|
|
*/
|
|
|
|
#include "esp-knx-ip.h"
|
|
|
|
void ESPKNXIP::__handle_root()
|
|
{
|
|
String m = F("<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>");
|
|
#if USE_BOOTSTRAP
|
|
m += F("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css' integrity='sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm' crossorigin='anonymous'>");
|
|
m += F("<style>.input-group-insert > .input-group-text { border-radius: 0; }</style>");
|
|
#endif
|
|
m += F("</head><body><div class='container-fluid'>");
|
|
m += F("<h2>ESP KNX</h2>");
|
|
|
|
// Feedback
|
|
|
|
if (registered_feedbacks > 0)
|
|
{
|
|
m += F("<h4>Feedback</h4>");
|
|
for (feedback_id_t i = 0; i < registered_feedbacks; ++i)
|
|
{
|
|
if (feedbacks[i].cond && !feedbacks[i].cond())
|
|
{
|
|
continue;
|
|
}
|
|
m += F("<form action='" __FEEDBACK_PATH "' method='POST'>");
|
|
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
|
|
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
|
|
m += feedbacks[i].name;
|
|
m += F("</span></div>");
|
|
switch (feedbacks[i].type)
|
|
{
|
|
case FEEDBACK_TYPE_INT:
|
|
m += F("<span class='input-group-text'>");
|
|
m += String(*(int32_t *)feedbacks[i].data);
|
|
m += F("</span>");
|
|
break;
|
|
case FEEDBACK_TYPE_FLOAT:
|
|
m += F("<span class='input-group-text'>");
|
|
m += feedbacks[i].options.float_options.prefix;
|
|
m += String(*(float *)feedbacks[i].data, feedbacks[i].options.float_options.precision);
|
|
m += feedbacks[i].options.float_options.suffix;
|
|
m += F("</span>");
|
|
break;
|
|
case FEEDBACK_TYPE_BOOL:
|
|
m += F("<span class='input-group-text'>");
|
|
m += (*(bool *)feedbacks[i].data) ? F("True") : F("False");
|
|
m += F("</span>");
|
|
break;
|
|
case FEEDBACK_TYPE_ACTION:
|
|
m += F("<input class='form-control' type='hidden' name='id' value='");
|
|
m += i;
|
|
m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-primary'>");
|
|
m += feedbacks[i].options.action_options.btn_text;
|
|
m += F("</button></div>");
|
|
break;
|
|
}
|
|
m += F("</div></div></div>");
|
|
m += F("</form>");
|
|
}
|
|
}
|
|
|
|
if (registered_callbacks > 0)
|
|
m += F("<h4>Callbacks</h4>");
|
|
|
|
if (registered_callback_assignments > 0)
|
|
{
|
|
for (uint8_t i = 0; i < registered_callback_assignments; ++i)
|
|
{
|
|
// Skip empty slots
|
|
if ((callback_assignments[i].slot_flags & SLOT_FLAGS_USED) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
// Skip disabled callbacks
|
|
if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond())
|
|
{
|
|
continue;
|
|
}
|
|
address_t &addr = callback_assignments[i].address;
|
|
m += F("<form action='" __DELETE_PATH "' method='POST'>");
|
|
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
|
|
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
|
|
m += addr.ga.area;
|
|
m += F("/");
|
|
m += addr.ga.line;
|
|
m += F("/");
|
|
m += addr.ga.member;
|
|
m += F("</span>");
|
|
m += F("<span class='input-group-text'>");
|
|
m += callbacks[callback_assignments[i].callback_id].name;
|
|
m += F("</span></div>");
|
|
m += F("<input class='form-control' type='hidden' name='id' value='");
|
|
m += i;
|
|
m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-danger'>Delete</button></div>");
|
|
m += F("</div></div></div>");
|
|
m += F("</form>");
|
|
}
|
|
}
|
|
|
|
if (registered_callbacks > 0)
|
|
{
|
|
m += F("<form action='" __REGISTER_PATH "' method='POST'>");
|
|
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
|
|
m += F("<input class='form-control' type='number' name='area' min='0' max='31'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
|
|
m += F("<input class='form-control' type='number' name='line' min='0' max='7'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
|
|
m += F("<input class='form-control' type='number' name='member' min='0' max='255'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>-></span></div>");
|
|
m += F("<select class='form-control' name='cb'>");
|
|
for (callback_id_t i = 0; i < registered_callbacks; ++i)
|
|
{
|
|
// Skip empty slots
|
|
if ((callbacks[i].slot_flags & SLOT_FLAGS_USED) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
// Skip disabled callbacks
|
|
if (callbacks[i].cond && !callbacks[i].cond())
|
|
{
|
|
continue;
|
|
}
|
|
m += F("<option value=\"");
|
|
m += i;
|
|
m += F("\">");
|
|
m += callbacks[i].name;
|
|
m += F("</option>");
|
|
}
|
|
m += F("</select>");
|
|
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
|
|
m += F("</div></div></div>");
|
|
m += F("</form>");
|
|
}
|
|
|
|
m += F("<h4>Configuration</h4>");
|
|
|
|
// Physical address
|
|
m += F("<form action='" __PHYS_PATH "' method='POST'>");
|
|
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
|
|
m += F("<div class='input-group-prepend'><span class='input-group-text'>Physical address</span></div>");
|
|
m += F("<input class='form-control' type='number' name='area' min='0' max='15' value='");
|
|
m += physaddr.pa.area;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
|
|
m += F("<input class='form-control' type='number' name='line' min='0' max='15' value='");
|
|
m += physaddr.pa.line;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
|
|
m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
|
|
m += physaddr.pa.member;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
|
|
m += F("</div></div></div>");
|
|
m += F("</form>");
|
|
|
|
if (registered_configs > 0)
|
|
{
|
|
for (config_id_t i = 0; i < registered_configs; ++i)
|
|
{
|
|
// Check if this config option has a enable condition and if so check that condition
|
|
if (custom_configs[i].cond && !custom_configs[i].cond())
|
|
continue;
|
|
|
|
m += F("<form action='" __CONFIG_PATH "' method='POST'>");
|
|
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
|
|
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
|
|
m += custom_configs[i].name;
|
|
m += F("</span></div>");
|
|
|
|
switch (custom_configs[i].type)
|
|
{
|
|
case CONFIG_TYPE_STRING:
|
|
m += F("<input class='form-control' type='text' name='value' value='");
|
|
m += config_get_string(i);
|
|
m += F("' maxlength='");
|
|
m += custom_configs[i].len - 1; // Subtract \0 byte
|
|
m += F("'/>");
|
|
break;
|
|
case CONFIG_TYPE_INT:
|
|
m += F("<input class='form-control' type='number' name='value' value='");
|
|
m += config_get_int(i);
|
|
m += F("'/>");
|
|
break;
|
|
case CONFIG_TYPE_BOOL:
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>");
|
|
m += F("<input type='checkbox' name='value' ");
|
|
if (config_get_bool(i))
|
|
m += F("checked ");
|
|
m += F("/>");
|
|
m += F("</span></div>");
|
|
break;
|
|
case CONFIG_TYPE_OPTIONS:
|
|
{
|
|
m += F("<select class='custom-select' name='value'>");
|
|
option_entry_t *cur = custom_configs[i].data.options;
|
|
while (cur->name != nullptr)
|
|
{
|
|
if (config_get_options(i) == cur->value)
|
|
{
|
|
m += F("<option selected value='");
|
|
}
|
|
else
|
|
{
|
|
m += F("<option value='");
|
|
}
|
|
m += cur->value;
|
|
m += F("'>");
|
|
m += String(cur->name);
|
|
m += F("</option>");
|
|
cur++;
|
|
}
|
|
m += F("");
|
|
m += F("</select>");
|
|
break;
|
|
}
|
|
case CONFIG_TYPE_GA:
|
|
address_t a = config_get_ga(i);
|
|
m += F("<input class='form-control' type='number' name='area' min='0' max='31' value='");
|
|
m += a.ga.area;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
|
|
m += F("<input class='form-control' type='number' name='line' min='0' max='7' value='");
|
|
m += a.ga.line;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
|
|
m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
|
|
m += a.ga.member;
|
|
m += F("'/>");
|
|
break;
|
|
}
|
|
m += F("<input type='hidden' name='id' value='");
|
|
m += i;
|
|
m += F("'/>");
|
|
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
|
|
m += F("</div></div></div>");
|
|
m += F("</form>");
|
|
}
|
|
}
|
|
|
|
#if !(DISABLE_EEPROM_BUTTONS && DISABLE_RESTORE_BUTTON && DISABLE_REBOOT_BUTTON)
|
|
// EEPROM save and restore
|
|
m += F("<div class='row'>");
|
|
// Save to EEPROM
|
|
#if !DISABLE_EEPROM_BUTTONS
|
|
m += F("<div class='col-auto'>");
|
|
m += F("<form action='" __EEPROM_PATH "' method='POST'>");
|
|
m += F("<input type='hidden' name='mode' value='1'>");
|
|
m += F("<button type='submit' class='btn btn-success'>Save to EEPROM</button>");
|
|
m += F("</form>");
|
|
m += F("</div>");
|
|
// Restore from EEPROM
|
|
m += F("<div class='col-auto'>");
|
|
m += F("<form action='" __EEPROM_PATH "' method='POST'>");
|
|
m += F("<input type='hidden' name='mode' value='2'>");
|
|
m += F("<button type='submit' class='btn btn-info'>Restore from EEPROM</button>");
|
|
m += F("</form>");
|
|
m += F("</div>");
|
|
#endif
|
|
#if !DISABLE_RESTORE_BUTTON
|
|
// Load Defaults
|
|
m += F("<div class='col-auto'>");
|
|
m += F("<form action='" __RESTORE_PATH "' method='POST'>");
|
|
m += F("<button type='submit' class='btn btn-warning'>Restore defaults</button>");
|
|
m += F("</form>");
|
|
m += F("</div>");
|
|
#endif
|
|
#if !DISABLE_REBOOT_BUTTON
|
|
// Reboot
|
|
m += F("<div class='col-auto'>");
|
|
m += F("<form action='" __REBOOT_PATH "' method='POST'>");
|
|
m += F("<button type='submit' class='btn btn-danger'>Reboot</button>");
|
|
m += F("</form>");
|
|
m += F("</div>");
|
|
#endif
|
|
m += F("</div>"); // row
|
|
#endif
|
|
|
|
// End of page
|
|
m += F("</div></body></html>");
|
|
server->send(200, F("text/html"), m);
|
|
}
|
|
|
|
void ESPKNXIP::__handle_register()
|
|
{
|
|
DEBUG_PRINTLN(F("Register called"));
|
|
if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")) && server->hasArg(F("cb")))
|
|
{
|
|
uint8_t area = server->arg(F("area")).toInt();
|
|
uint8_t line = server->arg(F("line")).toInt();
|
|
uint8_t member = server->arg(F("member")).toInt();
|
|
callback_id_t cb = (callback_id_t)server->arg(F("cb")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(area);
|
|
DEBUG_PRINT(F("/"));
|
|
DEBUG_PRINT(line);
|
|
DEBUG_PRINT(F("/"));
|
|
DEBUG_PRINT(member);
|
|
DEBUG_PRINT(F("/"));
|
|
DEBUG_PRINT(cb);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (area > 31 || line > 7)
|
|
{
|
|
DEBUG_PRINTLN(F("Area or Line wrong"));
|
|
goto end;
|
|
}
|
|
|
|
if (!__callback_is_id_valid(cb))
|
|
{
|
|
DEBUG_PRINTLN(F("Invalid callback id"));
|
|
goto end;
|
|
}
|
|
address_t ga = {.ga={line, area, member}};
|
|
__callback_register_assignment(ga, cb);
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
|
|
void ESPKNXIP::__handle_delete()
|
|
{
|
|
DEBUG_PRINTLN(F("Delete called"));
|
|
if (server->hasArg(F("id")))
|
|
{
|
|
callback_assignment_id_t id = (callback_assignment_id_t)server->arg(F("id")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(id);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (id >= registered_callback_assignments || (callback_assignments[id].slot_flags & SLOT_FLAGS_USED) == 0)
|
|
{
|
|
DEBUG_PRINTLN(F("ID wrong"));
|
|
goto end;
|
|
}
|
|
|
|
__callback_delete_assignment(id);
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
|
|
void ESPKNXIP::__handle_set()
|
|
{
|
|
DEBUG_PRINTLN(F("Set called"));
|
|
if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")))
|
|
{
|
|
uint8_t area = server->arg(F("area")).toInt();
|
|
uint8_t line = server->arg(F("line")).toInt();
|
|
uint8_t member = server->arg(F("member")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(area);
|
|
DEBUG_PRINT(F("."));
|
|
DEBUG_PRINT(line);
|
|
DEBUG_PRINT(F("."));
|
|
DEBUG_PRINT(member);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (area > 31 || line > 7)
|
|
{
|
|
DEBUG_PRINTLN(F("Area or Line wrong"));
|
|
goto end;
|
|
}
|
|
|
|
physaddr.bytes.high = (area << 4) | line;
|
|
physaddr.bytes.low = member;
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
|
|
void ESPKNXIP::__handle_config()
|
|
{
|
|
DEBUG_PRINTLN(F("Config called"));
|
|
if (server->hasArg(F("id")))
|
|
{
|
|
config_id_t id = server->arg(F("id")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(id);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (id < 0 || id >= registered_configs)
|
|
{
|
|
DEBUG_PRINTLN(F("ID wrong"));
|
|
goto end;
|
|
}
|
|
|
|
switch (custom_configs[id].type)
|
|
{
|
|
case CONFIG_TYPE_STRING:
|
|
{
|
|
String v = server->arg(F("value"));
|
|
if (v.length() >= custom_configs[id].len)
|
|
goto end;
|
|
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
|
|
__config_set_string(id, v);
|
|
break;
|
|
}
|
|
case CONFIG_TYPE_INT:
|
|
{
|
|
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
|
|
__config_set_int(id, server->arg(F("value")).toInt());
|
|
break;
|
|
}
|
|
case CONFIG_TYPE_BOOL:
|
|
{
|
|
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
|
|
__config_set_bool(id, server->arg(F("value")).compareTo(F("on")) == 0);
|
|
break;
|
|
}
|
|
case CONFIG_TYPE_OPTIONS:
|
|
{
|
|
uint8_t val = (uint8_t)server->arg(F("value")).toInt();
|
|
DEBUG_PRINT(F("Value: "));
|
|
DEBUG_PRINTLN(val);
|
|
config_set_options(id, val);
|
|
break;
|
|
}
|
|
case CONFIG_TYPE_GA:
|
|
{
|
|
uint8_t area = server->arg(F("area")).toInt();
|
|
uint8_t line = server->arg(F("line")).toInt();
|
|
uint8_t member = server->arg(F("member")).toInt();
|
|
if (area > 31 || line > 7)
|
|
{
|
|
DEBUG_PRINTLN(F("Area or Line wrong"));
|
|
goto end;
|
|
}
|
|
address_t tmp;
|
|
tmp.bytes.high = (area << 3) | line;
|
|
tmp.bytes.low = member;
|
|
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
|
|
__config_set_ga(id, tmp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
|
|
void ESPKNXIP::__handle_feedback()
|
|
{
|
|
DEBUG_PRINTLN(F("Feedback called"));
|
|
if (server->hasArg(F("id")))
|
|
{
|
|
config_id_t id = server->arg(F("id")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(id);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (id < 0 || id >= registered_feedbacks)
|
|
{
|
|
DEBUG_PRINTLN(F("ID wrong"));
|
|
goto end;
|
|
}
|
|
|
|
switch (feedbacks[id].type)
|
|
{
|
|
case FEEDBACK_TYPE_ACTION:
|
|
{
|
|
feedback_action_fptr_t func = (feedback_action_fptr_t)feedbacks[id].data;
|
|
void *arg = feedbacks[id].options.action_options.arg;
|
|
func(arg);
|
|
break;
|
|
}
|
|
default:
|
|
DEBUG_PRINTLN(F("Feedback has no action"));
|
|
break;
|
|
}
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
|
|
#if !DISABLE_RESTORE_BUTTONS
|
|
void ESPKNXIP::__handle_restore()
|
|
{
|
|
DEBUG_PRINTLN(F("Restore called"));
|
|
memcpy(custom_config_data, custom_config_default_data, MAX_CONFIG_SPACE);
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
#endif
|
|
|
|
#if !DISABLE_REBOOT_BUTTONS
|
|
void ESPKNXIP::__handle_reboot()
|
|
{
|
|
DEBUG_PRINTLN(F("Rebooting!"));
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
delay(1000);
|
|
ESP.restart();
|
|
//while(1);
|
|
}
|
|
#endif
|
|
|
|
#if !DISABLE_EEPROM_BUTTONS
|
|
void ESPKNXIP::__handle_eeprom()
|
|
{
|
|
DEBUG_PRINTLN(F("EEPROM called"));
|
|
if (server->hasArg(F("mode")))
|
|
{
|
|
uint8_t mode = server->arg(F("mode")).toInt();
|
|
|
|
DEBUG_PRINT(F("Got args: "));
|
|
DEBUG_PRINT(mode);
|
|
DEBUG_PRINTLN(F(""));
|
|
|
|
if (mode == 1)
|
|
{
|
|
// save
|
|
save_to_eeprom();
|
|
}
|
|
else if (mode == 2)
|
|
{
|
|
// restore
|
|
restore_from_eeprom();
|
|
}
|
|
}
|
|
end:
|
|
server->sendHeader(F("Location"),F(__ROOT_PATH));
|
|
server->send(302);
|
|
}
|
|
#endif
|