| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- /**
- * esp-knx-ip library for KNX/IP communication on an ESP8266
- * Author: Nico Weichbrodt <envy>
- * License: MIT
- */
- #include "esp-knx-ip.h"
- char const *string_defaults[] =
- {
- "Do this",
- "True",
- "False",
- ""
- };
- ESPKNXIP::ESPKNXIP() : server(nullptr),
- registered_callback_assignments(0),
- free_callback_assignment_slots(0),
- registered_callbacks(0),
- free_callback_slots(0),
- registered_configs(0),
- registered_feedbacks(0)
- {
- DEBUG_PRINTLN();
- DEBUG_PRINTLN("ESPKNXIP starting up");
- // Default physical address is 1.1.0
- physaddr.bytes.high = (/*area*/1 << 4) | /*line*/1;
- physaddr.bytes.low = /*member*/0;
- memset(callback_assignments, 0, MAX_CALLBACK_ASSIGNMENTS * sizeof(callback_assignment_t));
- memset(callbacks, 0, MAX_CALLBACKS * sizeof(callback_fptr_t));
- memset(custom_config_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t));
- memset(custom_config_default_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t));
- memset(custom_configs, 0, MAX_CONFIGS * sizeof(config_t));
- }
- void ESPKNXIP::load()
- {
- memcpy(custom_config_default_data, custom_config_data, MAX_CONFIG_SPACE);
- EEPROM.begin(EEPROM_SIZE);
- restore_from_eeprom();
- }
- void ESPKNXIP::start(ESP8266WebServer *srv)
- {
- server = srv;
- __start();
- }
- void ESPKNXIP::start()
- {
- server = new ESP8266WebServer(80);
- __start();
- }
- void ESPKNXIP::__start()
- {
- if (server != nullptr)
- {
- server->on(ROOT_PREFIX, [this](){
- __handle_root();
- });
- server->on(__ROOT_PATH, [this](){
- __handle_root();
- });
- server->on(__REGISTER_PATH, [this](){
- __handle_register();
- });
- server->on(__DELETE_PATH, [this](){
- __handle_delete();
- });
- server->on(__PHYS_PATH, [this](){
- __handle_set();
- });
- #if !DISABLE_EEPROM_BUTTONS
- server->on(__EEPROM_PATH, [this](){
- __handle_eeprom();
- });
- #endif
- server->on(__CONFIG_PATH, [this](){
- __handle_config();
- });
- server->on(__FEEDBACK_PATH, [this](){
- __handle_feedback();
- });
- #if !DISABLE_RESTORE_BUTTON
- server->on(__RESTORE_PATH, [this](){
- __handle_restore();
- });
- #endif
- #if !DISABLE_REBOOT_BUTTON
- server->on(__REBOOT_PATH, [this](){
- __handle_reboot();
- });
- #endif
- server->begin();
- }
- udp.beginMulticast(WiFi.localIP(), MULTICAST_IP, MULTICAST_PORT);
- }
- void ESPKNXIP::save_to_eeprom()
- {
- uint32_t address = 0;
- uint64_t magic = EEPROM_MAGIC;
- EEPROM.put(address, magic);
- address += sizeof(uint64_t);
- EEPROM.put(address++, registered_callback_assignments);
- for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
- {
- EEPROM.put(address, callback_assignments[i].address);
- address += sizeof(address_t);
- }
- for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
- {
- EEPROM.put(address, callback_assignments[i].callback_id);
- address += sizeof(callback_id_t);
- }
- EEPROM.put(address, physaddr);
- address += sizeof(address_t);
- EEPROM.put(address, custom_config_data);
- address += sizeof(custom_config_data);
- EEPROM.commit();
- DEBUG_PRINT("Wrote to EEPROM: 0x");
- DEBUG_PRINTLN(address, HEX);
- }
- void ESPKNXIP::restore_from_eeprom()
- {
- uint32_t address = 0;
- uint64_t magic = 0;
- EEPROM.get(address, magic);
- if (magic != EEPROM_MAGIC)
- {
- DEBUG_PRINTLN("No valid magic in EEPROM, aborting restore.");
- DEBUG_PRINT("Expected 0x");
- DEBUG_PRINT((unsigned long)(EEPROM_MAGIC >> 32), HEX);
- DEBUG_PRINT(" 0x");
- DEBUG_PRINT((unsigned long)(EEPROM_MAGIC), HEX);
- DEBUG_PRINT(" got 0x");
- DEBUG_PRINT((unsigned long)(magic >> 32), HEX);
- DEBUG_PRINT(" 0x");
- DEBUG_PRINTLN((unsigned long)magic, HEX);
- return;
- }
- address += sizeof(uint64_t);
- EEPROM.get(address++, registered_callback_assignments);
- for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
- {
- EEPROM.get(address, callback_assignments[i].address);
- if (callback_assignments[i].address.value != 0)
- {
- // if address is not 0/0/0 then mark slot as used
- callback_assignments[i].slot_flags |= SLOT_FLAGS_USED;
- DEBUG_PRINTLN("used slot");
- }
- else
- {
- // if address is 0/0/0, then we found a free slot, yay!
- // however, only count those slots, if we have not reached registered_callback_assignments yet
- if (i < registered_callback_assignments)
- {
- DEBUG_PRINTLN("free slot before reaching registered_callback_assignments");
- free_callback_assignment_slots++;
- }
- else
- {
- DEBUG_PRINTLN("free slot");
- }
- }
- address += sizeof(address_t);
- }
- for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
- {
- EEPROM.get(address, callback_assignments[i].callback_id);
- address += sizeof(callback_id_t);
- }
- EEPROM.get(address, physaddr);
- address += sizeof(address_t);
- //EEPROM.get(address, custom_config_data);
- //address += sizeof(custom_config_data);
- uint32_t conf_offset = address;
- for (uint8_t i = 0; i < registered_configs; ++i)
- {
- // First byte is flags.
- config_flags_t flags = CONFIG_FLAGS_NO_FLAGS;
- flags = (config_flags_t)EEPROM.read(address);
- DEBUG_PRINT("Flag in EEPROM @ ");
- DEBUG_PRINT(address - conf_offset);
- DEBUG_PRINT(": ");
- DEBUG_PRINTLN(flags, BIN);
- custom_config_data[custom_configs[i].offset] = flags;
- if (flags & CONFIG_FLAGS_VALUE_SET)
- {
- DEBUG_PRINTLN("Non-default value");
- for (int j = 0; j < custom_configs[i].len - sizeof(uint8_t); ++j)
- {
- custom_config_data[custom_configs[i].offset + sizeof(uint8_t) + j] = EEPROM.read(address + sizeof(uint8_t) + j);
- }
- }
- address += custom_configs[i].len;
- }
- DEBUG_PRINT("Restored from EEPROM: 0x");
- DEBUG_PRINTLN(address, HEX);
- }
- uint16_t ESPKNXIP::__ntohs(uint16_t n)
- {
- return (uint16_t)((((uint8_t*)&n)[0] << 8) | (((uint8_t*)&n)[1]));
- }
- callback_assignment_id_t ESPKNXIP::__callback_register_assignment(address_t address, callback_id_t id)
- {
- if (registered_callback_assignments >= MAX_CALLBACK_ASSIGNMENTS)
- return -1;
- if (free_callback_assignment_slots == 0)
- {
- callback_assignment_id_t aid = registered_callback_assignments;
- callback_assignments[aid].slot_flags |= SLOT_FLAGS_USED;
- callback_assignments[aid].address = address;
- callback_assignments[aid].callback_id = id;
- registered_callback_assignments++;
- return aid;
- }
- else
- {
- // find the free slot
- for (callback_assignment_id_t aid = 0; aid < registered_callback_assignments; ++aid)
- {
- if (callback_assignments[aid].slot_flags & SLOT_FLAGS_USED)
- {
- // found a used slot
- continue;
- }
- // and now an empty one
- callback_assignments[aid].slot_flags |= SLOT_FLAGS_USED;
- callback_assignments[aid].address = address;
- callback_assignments[aid].callback_id = id;
- free_callback_assignment_slots--;
- return id;
- }
- }
- }
- void ESPKNXIP::__callback_delete_assignment(callback_assignment_id_t id)
- {
- // TODO this can be optimized if we are deleting the last element
- // as then we can decrement registered_callback_assignments
- // clear slot and mark it as empty
- callback_assignments[id].slot_flags = SLOT_FLAGS_EMPTY;
- callback_assignments[id].address.value = 0;
- callback_assignments[id].callback_id = 0;
- if (id == registered_callback_assignments - 1)
- {
- DEBUG_PRINTLN("last cba deleted");
- // If this is the last callback, we can delete it by decrementing registered_callbacks.
- registered_callback_assignments--;
- // However, if the assignment before this slot are also empty, we can decrement even further
- // First check if this was also the first element
- if (id == 0)
- {
- DEBUG_PRINTLN("really last cba");
- // If this was the last, then we are done.
- return;
- }
- id--;
- while(true)
- {
- DEBUG_PRINT("checking ");
- DEBUG_PRINTLN((int32_t)id);
- if ((callback_assignments[id].slot_flags & SLOT_FLAGS_USED) == 0)
- {
- DEBUG_PRINTLN("merged free slot");
- // Slot before is empty
- free_callback_assignment_slots--;
- registered_callback_assignments--;
- }
- else
- {
- DEBUG_PRINTLN("aborted on used slot");
- // Slot is used, abort
- return;
- }
- id--;
- if (id == CALLBACK_ASSIGNMENT_ID_MAX)
- {
- DEBUG_PRINTLN("abort on wrap");
- // Wrap around, abort
- return;
- }
- }
- }
- else
- {
- DEBUG_PRINTLN("free slot created");
- // there is now one more free slot
- free_callback_assignment_slots++;
- }
- }
- bool ESPKNXIP::__callback_is_id_valid(callback_id_t id)
- {
- if (id < registered_callbacks)
- return true;
- if (callbacks[id].slot_flags & SLOT_FLAGS_USED)
- return true;
- return false;
- }
- callback_id_t ESPKNXIP::callback_register(String name, callback_fptr_t cb, void *arg, enable_condition_t cond)
- {
- if (registered_callbacks >= MAX_CALLBACKS)
- return -1;
- if (free_callback_slots == 0)
- {
- callback_id_t id = registered_callbacks;
- callbacks[id].slot_flags |= SLOT_FLAGS_USED;
- callbacks[id].name = name;
- callbacks[id].fkt = cb;
- callbacks[id].cond = cond;
- callbacks[id].arg = arg;
- registered_callbacks++;
- return id;
- }
- else
- {
- // find the free slot
- for (callback_id_t id = 0; id < registered_callbacks; ++id)
- {
- if (callbacks[id].slot_flags & SLOT_FLAGS_USED)
- {
- // found a used slot
- continue;
- }
- // and now an empty one
- callbacks[id].slot_flags |= SLOT_FLAGS_USED;
- callbacks[id].name = name;
- callbacks[id].fkt = cb;
- callbacks[id].cond = cond;
- callbacks[id].arg = arg;
- free_callback_slots--;
- return id;
- }
- }
- }
- void ESPKNXIP::callback_deregister(callback_id_t id)
- {
- if (!__callback_is_id_valid(id))
- return;
- // clear slot and mark it as empty
- callbacks[id].slot_flags = SLOT_FLAGS_EMPTY;
- callbacks[id].fkt = nullptr;
- callbacks[id].cond = nullptr;
- callbacks[id].arg = nullptr;
- if (id == registered_callbacks - 1)
- {
- // If this is the last callback, we can delete it by decrementing registered_callbacks.
- registered_callbacks--;
- // However, if the callbacks before this slot are also empty, we can decrement even further
- // First check if this was also the first element
- if (id == 0)
- {
- // If this was the last, then we are done.
- return;
- }
- id--;
- while(true)
- {
- if ((callbacks[id].slot_flags & SLOT_FLAGS_USED) == 0)
- {
- // Slot is empty
- free_callback_slots--;
- registered_callbacks--;
- }
- else
- {
- // Slot is used, abort
- return;
- }
- id--;
- if (id == CALLBACK_ASSIGNMENT_ID_MAX)
- {
- // Wrap around, abort
- return;
- }
- }
- }
- else
- {
- // there is now one more free slot
- free_callback_slots++;
- }
- }
- callback_assignment_id_t ESPKNXIP::callback_assign(callback_id_t id, address_t val)
- {
- if (!__callback_is_id_valid(id))
- return -1;
- return __callback_register_assignment(val, id);
- }
- void ESPKNXIP::callback_unassign(callback_assignment_id_t id)
- {
- if (!__callback_is_id_valid(id))
- return;
- __callback_delete_assignment(id);
- }
- /**
- * Feedback functions start here
- */
- feedback_id_t ESPKNXIP::feedback_register_int(String name, int32_t *value, enable_condition_t cond)
- {
- if (registered_feedbacks >= MAX_FEEDBACKS)
- return -1;
- feedback_id_t id = registered_feedbacks;
- feedbacks[id].type = FEEDBACK_TYPE_INT;
- feedbacks[id].name = name;
- feedbacks[id].cond = cond;
- feedbacks[id].data = (void *)value;
- registered_feedbacks++;
- return id;
- }
- feedback_id_t ESPKNXIP::feedback_register_float(String name, float *value, uint8_t precision, char const *prefix, char const *suffix, enable_condition_t cond)
- {
- if (registered_feedbacks >= MAX_FEEDBACKS)
- return -1;
- feedback_id_t id = registered_feedbacks;
- feedbacks[id].type = FEEDBACK_TYPE_FLOAT;
- feedbacks[id].name = name;
- feedbacks[id].cond = cond;
- feedbacks[id].data = (void *)value;
- feedbacks[id].options.float_options.precision = precision;
- feedbacks[id].options.float_options.prefix = prefix ? strdup(prefix) : STRING_DEFAULT_EMPTY;
- feedbacks[id].options.float_options.suffix = suffix ? strdup(suffix) : STRING_DEFAULT_EMPTY;
- registered_feedbacks++;
- return id;
- }
- feedback_id_t ESPKNXIP::feedback_register_bool(String name, bool *value, char const *true_text, char const *false_text, enable_condition_t cond)
- {
- if (registered_feedbacks >= MAX_FEEDBACKS)
- return -1;
- feedback_id_t id = registered_feedbacks;
- feedbacks[id].type = FEEDBACK_TYPE_BOOL;
- feedbacks[id].name = name;
- feedbacks[id].cond = cond;
- feedbacks[id].data = (void *)value;
- feedbacks[id].options.bool_options.true_text = true_text ? strdup(true_text) : STRING_DEFAULT_TRUE;
- feedbacks[id].options.bool_options.false_text = false_text ? strdup(false_text) : STRING_DEFAULT_FALSE;
- registered_feedbacks++;
- return id;
- }
- feedback_id_t ESPKNXIP::feedback_register_action(String name, feedback_action_fptr_t value, const char *btn_text, void *arg, enable_condition_t cond)
- {
- if (registered_feedbacks >= MAX_FEEDBACKS)
- return -1;
- feedback_id_t id = registered_feedbacks;
- feedbacks[id].type = FEEDBACK_TYPE_ACTION;
- feedbacks[id].name = name;
- feedbacks[id].cond = cond;
- feedbacks[id].data = (void *)value;
- feedbacks[id].options.action_options.arg = arg;
- feedbacks[id].options.action_options.btn_text = btn_text ? strdup(btn_text) : STRING_DEFAULT_DO_THIS;
- registered_feedbacks++;
- return id;
- }
- void ESPKNXIP::loop()
- {
- __loop_knx();
- if (server != nullptr)
- {
- __loop_webserver();
- }
- }
- void ESPKNXIP::__loop_webserver()
- {
- server->handleClient();
- }
- void ESPKNXIP::__loop_knx()
- {
- int read = udp.parsePacket();
- if (!read)
- {
- return;
- }
- DEBUG_PRINTLN(F(""));
- DEBUG_PRINT(F("LEN: "));
- DEBUG_PRINTLN(read);
- uint8_t buf[read];
- udp.read(buf, read);
- udp.flush();
- DEBUG_PRINT(F("Got packet:"));
- #ifdef ESP_KNX_DEBUG
- for (int i = 0; i < read; ++i)
- {
- DEBUG_PRINT(F(" 0x"));
- DEBUG_PRINT(buf[i], 16);
- }
- #endif
- DEBUG_PRINTLN(F(""));
- knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf;
- DEBUG_PRINT(F("ST: 0x"));
- DEBUG_PRINTLN(__ntohs(knx_pkt->service_type), 16);
- if (knx_pkt->header_len != 0x06 && knx_pkt->protocol_version != 0x10 && knx_pkt->service_type != KNX_ST_ROUTING_INDICATION)
- return;
- cemi_msg_t *cemi_msg = (cemi_msg_t *)knx_pkt->pkt_data;
- DEBUG_PRINT(F("MT: 0x"));
- DEBUG_PRINTLN(cemi_msg->message_code, 16);
- if (cemi_msg->message_code != KNX_MT_L_DATA_IND)
- return;
- DEBUG_PRINT(F("ADDI: 0x"));
- DEBUG_PRINTLN(cemi_msg->additional_info_len, 16);
- cemi_service_t *cemi_data = &cemi_msg->data.service_information;
- if (cemi_msg->additional_info_len > 0)
- cemi_data = (cemi_service_t *)(((uint8_t *)cemi_data) + cemi_msg->additional_info_len);
- DEBUG_PRINT(F("C1: 0x"));
- DEBUG_PRINTLN(cemi_data->control_1.byte, 16);
- DEBUG_PRINT(F("C2: 0x"));
- DEBUG_PRINTLN(cemi_data->control_2.byte, 16);
- DEBUG_PRINT(F("DT: 0x"));
- DEBUG_PRINTLN(cemi_data->control_2.bits.dest_addr_type, 16);
- if (cemi_data->control_2.bits.dest_addr_type != 0x01)
- return;
- DEBUG_PRINT(F("HC: 0x"));
- DEBUG_PRINTLN(cemi_data->control_2.bits.hop_count, 16);
- DEBUG_PRINT(F("EFF: 0x"));
- DEBUG_PRINTLN(cemi_data->control_2.bits.extended_frame_format, 16);
- DEBUG_PRINT(F("Source: 0x"));
- DEBUG_PRINT(cemi_data->source.bytes.high, 16);
- DEBUG_PRINT(F(" 0x"));
- DEBUG_PRINTLN(cemi_data->source.bytes.low, 16);
- DEBUG_PRINT(F("Dest: 0x"));
- DEBUG_PRINT(cemi_data->destination.bytes.high, 16);
- DEBUG_PRINT(F(" 0x"));
- DEBUG_PRINTLN(cemi_data->destination.bytes.low, 16);
- knx_command_type_t ct = (knx_command_type_t)(((cemi_data->data[0] & 0xC0) >> 6) | ((cemi_data->pci.apci & 0x03) << 2));
- DEBUG_PRINT(F("CT: 0x"));
- DEBUG_PRINTLN(ct, 16);
- #ifdef ESP_KNX_DEBUG
- for (int i = 0; i < cemi_data->data_len; ++i)
- {
- DEBUG_PRINT(F(" 0x"));
- DEBUG_PRINT(cemi_data->data[i], 16);
- }
- #endif
- DEBUG_PRINTLN(F("=="));
- // Call callbacks
- for (int i = 0; i < registered_callback_assignments; ++i)
- {
- DEBUG_PRINT(F("Testing: 0x"));
- DEBUG_PRINT(callback_assignments[i].address.bytes.high, 16);
- DEBUG_PRINT(F(" 0x"));
- DEBUG_PRINTLN(callback_assignments[i].address.bytes.low, 16);
- if (cemi_data->destination.value == callback_assignments[i].address.value)
- {
- DEBUG_PRINTLN(F("Found match"));
- if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond())
- {
- DEBUG_PRINTLN(F("But it's disabled"));
- #if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS
- continue;
- #else
- return;
- #endif
- }
- uint8_t data[cemi_data->data_len];
- memcpy(data, cemi_data->data, cemi_data->data_len);
- data[0] = data[0] & 0x3F;
- message_t msg = {};
- msg.ct = ct;
- msg.received_on = cemi_data->destination;
- msg.data_len = cemi_data->data_len;
- msg.data = data;
- callbacks[callback_assignments[i].callback_id].fkt(msg, callbacks[callback_assignments[i].callback_id].arg);
- #if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS
- continue;
- #else
- return;
- #endif
- }
- }
- return;
- }
- // Global "singleton" object
- ESPKNXIP knx;
|