/** * 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