esp-knx-ip-webserver.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /**
  2. * esp-knx-ip library for KNX/IP communication on an ESP8266
  3. * Author: Nico Weichbrodt <envy>
  4. * License: MIT
  5. */
  6. #include "esp-knx-ip.h"
  7. void ESPKNXIP::__handle_root()
  8. {
  9. String m = F("<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>");
  10. #if USE_BOOTSTRAP
  11. 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'>");
  12. m += F("<style>.input-group-insert > .input-group-text { border-radius: 0; }</style>");
  13. #endif
  14. m += F("</head><body><div class='container-fluid'>");
  15. m += F("<h2>ESP KNX</h2>");
  16. // Feedback
  17. if (registered_feedbacks > 0)
  18. {
  19. m += F("<h4>Feedback</h4>");
  20. for (feedback_id_t i = 0; i < registered_feedbacks; ++i)
  21. {
  22. if (feedbacks[i].cond && !feedbacks[i].cond())
  23. {
  24. continue;
  25. }
  26. m += F("<form action='" __FEEDBACK_PATH "' method='POST'>");
  27. m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
  28. m += F("<div class='input-group-prepend'><span class='input-group-text'>");
  29. m += feedbacks[i].name;
  30. m += F("</span></div>");
  31. switch (feedbacks[i].type)
  32. {
  33. case FEEDBACK_TYPE_INT:
  34. m += F("<span class='input-group-text'>");
  35. m += String(*(int32_t *)feedbacks[i].data);
  36. m += F("</span>");
  37. break;
  38. case FEEDBACK_TYPE_FLOAT:
  39. m += F("<span class='input-group-text'>");
  40. m += feedbacks[i].options.float_options.prefix;
  41. m += String(*(float *)feedbacks[i].data, feedbacks[i].options.float_options.precision);
  42. m += feedbacks[i].options.float_options.suffix;
  43. m += F("</span>");
  44. break;
  45. case FEEDBACK_TYPE_BOOL:
  46. m += F("<span class='input-group-text'>");
  47. m += (*(bool *)feedbacks[i].data) ? F("True") : F("False");
  48. m += F("</span>");
  49. break;
  50. case FEEDBACK_TYPE_ACTION:
  51. m += F("<input class='form-control' type='hidden' name='id' value='");
  52. m += i;
  53. m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-primary'>");
  54. m += feedbacks[i].options.action_options.btn_text;
  55. m += F("</button></div>");
  56. break;
  57. }
  58. m += F("</div></div></div>");
  59. m += F("</form>");
  60. }
  61. }
  62. if (registered_callbacks > 0)
  63. m += F("<h4>Callbacks</h4>");
  64. if (registered_callback_assignments > 0)
  65. {
  66. for (uint8_t i = 0; i < registered_callback_assignments; ++i)
  67. {
  68. // Skip empty slots
  69. if ((callback_assignments[i].slot_flags & SLOT_FLAGS_USED) == 0)
  70. {
  71. continue;
  72. }
  73. // Skip disabled callbacks
  74. if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond())
  75. {
  76. continue;
  77. }
  78. address_t &addr = callback_assignments[i].address;
  79. m += F("<form action='" __DELETE_PATH "' method='POST'>");
  80. m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
  81. m += F("<div class='input-group-prepend'><span class='input-group-text'>");
  82. m += addr.ga.area;
  83. m += F("/");
  84. m += addr.ga.line;
  85. m += F("/");
  86. m += addr.ga.member;
  87. m += F("</span>");
  88. m += F("<span class='input-group-text'>");
  89. m += callbacks[callback_assignments[i].callback_id].name;
  90. m += F("</span></div>");
  91. m += F("<input class='form-control' type='hidden' name='id' value='");
  92. m += i;
  93. m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-danger'>Delete</button></div>");
  94. m += F("</div></div></div>");
  95. m += F("</form>");
  96. }
  97. }
  98. if (registered_callbacks > 0)
  99. {
  100. m += F("<form action='" __REGISTER_PATH "' method='POST'>");
  101. m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
  102. m += F("<input class='form-control' type='number' name='area' min='0' max='31'/>");
  103. m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
  104. m += F("<input class='form-control' type='number' name='line' min='0' max='7'/>");
  105. m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
  106. m += F("<input class='form-control' type='number' name='member' min='0' max='255'/>");
  107. m += F("<div class='input-group-insert'><span class='input-group-text'>-&gt;</span></div>");
  108. m += F("<select class='form-control' name='cb'>");
  109. for (callback_id_t i = 0; i < registered_callbacks; ++i)
  110. {
  111. // Skip empty slots
  112. if ((callbacks[i].slot_flags & SLOT_FLAGS_USED) == 0)
  113. {
  114. continue;
  115. }
  116. // Skip disabled callbacks
  117. if (callbacks[i].cond && !callbacks[i].cond())
  118. {
  119. continue;
  120. }
  121. m += F("<option value=\"");
  122. m += i;
  123. m += F("\">");
  124. m += callbacks[i].name;
  125. m += F("</option>");
  126. }
  127. m += F("</select>");
  128. m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
  129. m += F("</div></div></div>");
  130. m += F("</form>");
  131. }
  132. m += F("<h4>Configuration</h4>");
  133. // Physical address
  134. m += F("<form action='" __PHYS_PATH "' method='POST'>");
  135. m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
  136. m += F("<div class='input-group-prepend'><span class='input-group-text'>Physical address</span></div>");
  137. m += F("<input class='form-control' type='number' name='area' min='0' max='15' value='");
  138. m += physaddr.pa.area;
  139. m += F("'/>");
  140. m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
  141. m += F("<input class='form-control' type='number' name='line' min='0' max='15' value='");
  142. m += physaddr.pa.line;
  143. m += F("'/>");
  144. m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
  145. m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
  146. m += physaddr.pa.member;
  147. m += F("'/>");
  148. m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
  149. m += F("</div></div></div>");
  150. m += F("</form>");
  151. if (registered_configs > 0)
  152. {
  153. for (config_id_t i = 0; i < registered_configs; ++i)
  154. {
  155. // Check if this config option has a enable condition and if so check that condition
  156. if (custom_configs[i].cond && !custom_configs[i].cond())
  157. continue;
  158. m += F("<form action='" __CONFIG_PATH "' method='POST'>");
  159. m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
  160. m += F("<div class='input-group-prepend'><span class='input-group-text'>");
  161. m += custom_configs[i].name;
  162. m += F("</span></div>");
  163. switch (custom_configs[i].type)
  164. {
  165. case CONFIG_TYPE_STRING:
  166. m += F("<input class='form-control' type='text' name='value' value='");
  167. m += config_get_string(i);
  168. m += F("' maxlength='");
  169. m += custom_configs[i].len - 1; // Subtract \0 byte
  170. m += F("'/>");
  171. break;
  172. case CONFIG_TYPE_INT:
  173. m += F("<input class='form-control' type='number' name='value' value='");
  174. m += config_get_int(i);
  175. m += F("'/>");
  176. break;
  177. case CONFIG_TYPE_BOOL:
  178. m += F("<div class='input-group-insert'><span class='input-group-text'>");
  179. m += F("<input type='checkbox' name='value' ");
  180. if (config_get_bool(i))
  181. m += F("checked ");
  182. m += F("/>");
  183. m += F("</span></div>");
  184. break;
  185. case CONFIG_TYPE_OPTIONS:
  186. {
  187. m += F("<select class='custom-select' name='value'>");
  188. option_entry_t *cur = custom_configs[i].data.options;
  189. while (cur->name != nullptr)
  190. {
  191. if (config_get_options(i) == cur->value)
  192. {
  193. m += F("<option selected value='");
  194. }
  195. else
  196. {
  197. m += F("<option value='");
  198. }
  199. m += cur->value;
  200. m += F("'>");
  201. m += String(cur->name);
  202. m += F("</option>");
  203. cur++;
  204. }
  205. m += F("");
  206. m += F("</select>");
  207. break;
  208. }
  209. case CONFIG_TYPE_GA:
  210. address_t a = config_get_ga(i);
  211. m += F("<input class='form-control' type='number' name='area' min='0' max='31' value='");
  212. m += a.ga.area;
  213. m += F("'/>");
  214. m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
  215. m += F("<input class='form-control' type='number' name='line' min='0' max='7' value='");
  216. m += a.ga.line;
  217. m += F("'/>");
  218. m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
  219. m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
  220. m += a.ga.member;
  221. m += F("'/>");
  222. break;
  223. }
  224. m += F("<input type='hidden' name='id' value='");
  225. m += i;
  226. m += F("'/>");
  227. m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
  228. m += F("</div></div></div>");
  229. m += F("</form>");
  230. }
  231. }
  232. #if !(DISABLE_EEPROM_BUTTONS && DISABLE_RESTORE_BUTTON && DISABLE_REBOOT_BUTTON)
  233. // EEPROM save and restore
  234. m += F("<div class='row'>");
  235. // Save to EEPROM
  236. #if !DISABLE_EEPROM_BUTTONS
  237. m += F("<div class='col-auto'>");
  238. m += F("<form action='" __EEPROM_PATH "' method='POST'>");
  239. m += F("<input type='hidden' name='mode' value='1'>");
  240. m += F("<button type='submit' class='btn btn-success'>Save to EEPROM</button>");
  241. m += F("</form>");
  242. m += F("</div>");
  243. // Restore from EEPROM
  244. m += F("<div class='col-auto'>");
  245. m += F("<form action='" __EEPROM_PATH "' method='POST'>");
  246. m += F("<input type='hidden' name='mode' value='2'>");
  247. m += F("<button type='submit' class='btn btn-info'>Restore from EEPROM</button>");
  248. m += F("</form>");
  249. m += F("</div>");
  250. #endif
  251. #if !DISABLE_RESTORE_BUTTON
  252. // Load Defaults
  253. m += F("<div class='col-auto'>");
  254. m += F("<form action='" __RESTORE_PATH "' method='POST'>");
  255. m += F("<button type='submit' class='btn btn-warning'>Restore defaults</button>");
  256. m += F("</form>");
  257. m += F("</div>");
  258. #endif
  259. #if !DISABLE_REBOOT_BUTTON
  260. // Reboot
  261. m += F("<div class='col-auto'>");
  262. m += F("<form action='" __REBOOT_PATH "' method='POST'>");
  263. m += F("<button type='submit' class='btn btn-danger'>Reboot</button>");
  264. m += F("</form>");
  265. m += F("</div>");
  266. #endif
  267. m += F("</div>"); // row
  268. #endif
  269. // End of page
  270. m += F("</div></body></html>");
  271. server->send(200, F("text/html"), m);
  272. }
  273. void ESPKNXIP::__handle_register()
  274. {
  275. DEBUG_PRINTLN(F("Register called"));
  276. if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")) && server->hasArg(F("cb")))
  277. {
  278. uint8_t area = server->arg(F("area")).toInt();
  279. uint8_t line = server->arg(F("line")).toInt();
  280. uint8_t member = server->arg(F("member")).toInt();
  281. callback_id_t cb = (callback_id_t)server->arg(F("cb")).toInt();
  282. DEBUG_PRINT(F("Got args: "));
  283. DEBUG_PRINT(area);
  284. DEBUG_PRINT(F("/"));
  285. DEBUG_PRINT(line);
  286. DEBUG_PRINT(F("/"));
  287. DEBUG_PRINT(member);
  288. DEBUG_PRINT(F("/"));
  289. DEBUG_PRINT(cb);
  290. DEBUG_PRINTLN(F(""));
  291. if (area > 31 || line > 7)
  292. {
  293. DEBUG_PRINTLN(F("Area or Line wrong"));
  294. goto end;
  295. }
  296. if (!__callback_is_id_valid(cb))
  297. {
  298. DEBUG_PRINTLN(F("Invalid callback id"));
  299. goto end;
  300. }
  301. address_t ga = {.ga={line, area, member}};
  302. __callback_register_assignment(ga, cb);
  303. }
  304. end:
  305. server->sendHeader(F("Location"),F(__ROOT_PATH));
  306. server->send(302);
  307. }
  308. void ESPKNXIP::__handle_delete()
  309. {
  310. DEBUG_PRINTLN(F("Delete called"));
  311. if (server->hasArg(F("id")))
  312. {
  313. callback_assignment_id_t id = (callback_assignment_id_t)server->arg(F("id")).toInt();
  314. DEBUG_PRINT(F("Got args: "));
  315. DEBUG_PRINT(id);
  316. DEBUG_PRINTLN(F(""));
  317. if (id >= registered_callback_assignments || (callback_assignments[id].slot_flags & SLOT_FLAGS_USED) == 0)
  318. {
  319. DEBUG_PRINTLN(F("ID wrong"));
  320. goto end;
  321. }
  322. __callback_delete_assignment(id);
  323. }
  324. end:
  325. server->sendHeader(F("Location"),F(__ROOT_PATH));
  326. server->send(302);
  327. }
  328. void ESPKNXIP::__handle_set()
  329. {
  330. DEBUG_PRINTLN(F("Set called"));
  331. if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")))
  332. {
  333. uint8_t area = server->arg(F("area")).toInt();
  334. uint8_t line = server->arg(F("line")).toInt();
  335. uint8_t member = server->arg(F("member")).toInt();
  336. DEBUG_PRINT(F("Got args: "));
  337. DEBUG_PRINT(area);
  338. DEBUG_PRINT(F("."));
  339. DEBUG_PRINT(line);
  340. DEBUG_PRINT(F("."));
  341. DEBUG_PRINT(member);
  342. DEBUG_PRINTLN(F(""));
  343. if (area > 31 || line > 7)
  344. {
  345. DEBUG_PRINTLN(F("Area or Line wrong"));
  346. goto end;
  347. }
  348. physaddr.bytes.high = (area << 4) | line;
  349. physaddr.bytes.low = member;
  350. }
  351. end:
  352. server->sendHeader(F("Location"),F(__ROOT_PATH));
  353. server->send(302);
  354. }
  355. void ESPKNXIP::__handle_config()
  356. {
  357. DEBUG_PRINTLN(F("Config called"));
  358. if (server->hasArg(F("id")))
  359. {
  360. config_id_t id = server->arg(F("id")).toInt();
  361. DEBUG_PRINT(F("Got args: "));
  362. DEBUG_PRINT(id);
  363. DEBUG_PRINTLN(F(""));
  364. if (id < 0 || id >= registered_configs)
  365. {
  366. DEBUG_PRINTLN(F("ID wrong"));
  367. goto end;
  368. }
  369. switch (custom_configs[id].type)
  370. {
  371. case CONFIG_TYPE_STRING:
  372. {
  373. String v = server->arg(F("value"));
  374. if (v.length() >= custom_configs[id].len)
  375. goto end;
  376. __config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
  377. __config_set_string(id, v);
  378. break;
  379. }
  380. case CONFIG_TYPE_INT:
  381. {
  382. __config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
  383. __config_set_int(id, server->arg(F("value")).toInt());
  384. break;
  385. }
  386. case CONFIG_TYPE_BOOL:
  387. {
  388. __config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
  389. __config_set_bool(id, server->arg(F("value")).compareTo(F("on")) == 0);
  390. break;
  391. }
  392. case CONFIG_TYPE_OPTIONS:
  393. {
  394. uint8_t val = (uint8_t)server->arg(F("value")).toInt();
  395. DEBUG_PRINT(F("Value: "));
  396. DEBUG_PRINTLN(val);
  397. config_set_options(id, val);
  398. break;
  399. }
  400. case CONFIG_TYPE_GA:
  401. {
  402. uint8_t area = server->arg(F("area")).toInt();
  403. uint8_t line = server->arg(F("line")).toInt();
  404. uint8_t member = server->arg(F("member")).toInt();
  405. if (area > 31 || line > 7)
  406. {
  407. DEBUG_PRINTLN(F("Area or Line wrong"));
  408. goto end;
  409. }
  410. address_t tmp;
  411. tmp.bytes.high = (area << 3) | line;
  412. tmp.bytes.low = member;
  413. __config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
  414. __config_set_ga(id, tmp);
  415. break;
  416. }
  417. }
  418. }
  419. end:
  420. server->sendHeader(F("Location"),F(__ROOT_PATH));
  421. server->send(302);
  422. }
  423. void ESPKNXIP::__handle_feedback()
  424. {
  425. DEBUG_PRINTLN(F("Feedback called"));
  426. if (server->hasArg(F("id")))
  427. {
  428. config_id_t id = server->arg(F("id")).toInt();
  429. DEBUG_PRINT(F("Got args: "));
  430. DEBUG_PRINT(id);
  431. DEBUG_PRINTLN(F(""));
  432. if (id < 0 || id >= registered_feedbacks)
  433. {
  434. DEBUG_PRINTLN(F("ID wrong"));
  435. goto end;
  436. }
  437. switch (feedbacks[id].type)
  438. {
  439. case FEEDBACK_TYPE_ACTION:
  440. {
  441. feedback_action_fptr_t func = (feedback_action_fptr_t)feedbacks[id].data;
  442. void *arg = feedbacks[id].options.action_options.arg;
  443. func(arg);
  444. break;
  445. }
  446. default:
  447. DEBUG_PRINTLN(F("Feedback has no action"));
  448. break;
  449. }
  450. }
  451. end:
  452. server->sendHeader(F("Location"),F(__ROOT_PATH));
  453. server->send(302);
  454. }
  455. #if !DISABLE_RESTORE_BUTTONS
  456. void ESPKNXIP::__handle_restore()
  457. {
  458. DEBUG_PRINTLN(F("Restore called"));
  459. memcpy(custom_config_data, custom_config_default_data, MAX_CONFIG_SPACE);
  460. end:
  461. server->sendHeader(F("Location"),F(__ROOT_PATH));
  462. server->send(302);
  463. }
  464. #endif
  465. #if !DISABLE_REBOOT_BUTTONS
  466. void ESPKNXIP::__handle_reboot()
  467. {
  468. DEBUG_PRINTLN(F("Rebooting!"));
  469. server->sendHeader(F("Location"),F(__ROOT_PATH));
  470. server->send(302);
  471. delay(1000);
  472. ESP.restart();
  473. //while(1);
  474. }
  475. #endif
  476. #if !DISABLE_EEPROM_BUTTONS
  477. void ESPKNXIP::__handle_eeprom()
  478. {
  479. DEBUG_PRINTLN(F("EEPROM called"));
  480. if (server->hasArg(F("mode")))
  481. {
  482. uint8_t mode = server->arg(F("mode")).toInt();
  483. DEBUG_PRINT(F("Got args: "));
  484. DEBUG_PRINT(mode);
  485. DEBUG_PRINTLN(F(""));
  486. if (mode == 1)
  487. {
  488. // save
  489. save_to_eeprom();
  490. }
  491. else if (mode == 2)
  492. {
  493. // restore
  494. restore_from_eeprom();
  495. }
  496. }
  497. end:
  498. server->sendHeader(F("Location"),F(__ROOT_PATH));
  499. server->send(302);
  500. }
  501. #endif