├── DPT.h ├── LICENSE ├── README.md ├── esp-knx-ip-config.cpp ├── esp-knx-ip-conversion.cpp ├── esp-knx-ip-send.cpp ├── esp-knx-ip-webserver.cpp ├── esp-knx-ip.cpp ├── esp-knx-ip.h ├── examples ├── environment-sensor │ └── environment-sensor.ino ├── sonoff │ └── sonoff.ino └── static-config │ └── static-config.ino ├── keywords.txt └── library.properties /DPT.h: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | typedef enum __dpt_1_001 8 | { 9 | DPT_1_001_OFF = 0x00, 10 | DPT_1_001_ON = 0x01, 11 | } dpt_1_001_t; 12 | 13 | typedef enum __dpt_2_001 14 | { 15 | DPT_2_001_NO_OFF = 0b00, 16 | DPT_2_001_NO_ON = 0b01, 17 | DPT_2_001_YES_OFF = 0b10, 18 | DPT_2_001_YES_ON = 0b11, 19 | } dpt_2_001_t; 20 | 21 | typedef enum __dpt_3_007 22 | { 23 | DPT_3_007_DECREASE_STOP = 0x00, 24 | DPT_3_007_DECREASE_100 = 0x01, 25 | DPT_3_007_DECREASE_50 = 0x02, 26 | DPT_3_007_DECREASE_25 = 0x03, 27 | DPT_3_007_DECREASE_12 = 0x04, 28 | DPT_3_007_DECREASE_6 = 0x05, 29 | DPT_3_007_DECREASE_3 = 0x06, 30 | DPT_3_007_DECREASE_1 = 0x07, 31 | DPT_3_007_INCREASE_STOP = 0x08, 32 | DPT_3_007_INCREASE_100 = 0x09, 33 | DPT_3_007_INCREASE_50 = 0x0A, 34 | DPT_3_007_INCREASE_25 = 0x0B, 35 | DPT_3_007_INCREASE_12 = 0x0C, 36 | DPT_3_007_INCREASE_6 = 0x0D, 37 | DPT_3_007_INCREASE_3 = 0x0E, 38 | DPT_3_007_INCREASE_1 = 0x0F, 39 | } dpt_3_007_t; 40 | 41 | typedef enum __weekday 42 | { 43 | DPT_10_001_WEEKDAY_NODAY = 0, 44 | DPT_10_001_WEEKDAY_MONDAY = 1, 45 | DPT_10_001_WEEKDAY_TUESDAY = 2, 46 | DPT_10_001_WEEKDAY_WEDNESDAY = 3, 47 | DPT_10_001_WEEKDAY_THURSDAY = 4, 48 | DPT_10_001_WEEKDAY_FRIDAY = 5, 49 | DPT_10_001_WEEKDAY_SATURDAY = 6, 50 | DPT_10_001_WEEKDAY_SUNDAY = 7, 51 | } weekday_t; 52 | 53 | typedef struct __time_of_day 54 | { 55 | weekday_t weekday; 56 | uint8_t hours; 57 | uint8_t minutes; 58 | uint8_t seconds; 59 | } time_of_day_t; 60 | 61 | typedef struct __date 62 | { 63 | uint8_t day; 64 | uint8_t month; 65 | uint8_t year; 66 | } date_t; 67 | 68 | typedef struct __color 69 | { 70 | uint8_t red; 71 | uint8_t green; 72 | uint8_t blue; 73 | } color_t; 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nico Weichbrodt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP-KNX-IP # 2 | 3 | This is a library for the ESP8266 to enable KNXnet/IP communication. It uses UDP multicast on 224.0.23.12:3671. 4 | It is intended to be used with the Arduino platform for the ESP8266. 5 | 6 | ## Prerequisities ## 7 | 8 | I recommend version 2.3.0 of the esp8266 board libraries. With 2.4.0 I experienced unstable receivement of packets. 9 | 10 | ## How to use ## 11 | 12 | The library is under development. API may change multiple times in the future. 13 | 14 | API documentation is available [here](https://github.com/envy/esp-knx-ip/wiki/API) 15 | 16 | A simple example: 17 | 18 | ```c++ 19 | #include 20 | 21 | const char* ssid = "my-ssid"; // your network SSID (name) 22 | const char* pass = "my-pw"; // your network password 23 | 24 | config_id_t my_GA; 25 | config_id_t param_id; 26 | 27 | int8_t some_var = 0; 28 | 29 | void setup() 30 | { 31 | // Register a callback that is called when a configurable group address is receiving a telegram 32 | knx.register_callback("Set/Get callback", my_callback); 33 | knx.register_callback("Write callback", my_other_callback); 34 | 35 | int default_val = 21; 36 | param_id = knx.config_register_int("My Parameter", default_val); 37 | 38 | // Register a configurable group address for sending out answers 39 | my_GA = knx.config_register_ga("Answer GA"); 40 | 41 | knx.load(); // Try to load a config from EEPROM 42 | 43 | WiFi.begin(ssid, pass); 44 | while (WiFi.status() != WL_CONNECTED) { 45 | delay(500); 46 | } 47 | 48 | knx.start(); // Start everything. Must be called after WiFi connection has been established 49 | } 50 | 51 | void loop() 52 | { 53 | knx.loop(); 54 | } 55 | 56 | 57 | void my_callback(message_t const &msg, void *arg) 58 | { 59 | switch (msg.ct) 60 | { 61 | case KNX_CT_WRITE: 62 | // Save received data 63 | some_var = knx.data_to_1byte_int(msg.data); 64 | break; 65 | case KNX_CT_READ: 66 | // Answer with saved data 67 | knx.answer1ByteInt(msg.received_on, some_var); 68 | break; 69 | } 70 | } 71 | 72 | void my_other_callback(message_t const &msg, void *arg) 73 | { 74 | switch (msg.ct) 75 | { 76 | case KNX_CT_WRITE: 77 | // Write an answer somewhere else 78 | int value = knx.config_get_int(param_id); 79 | address_t ga = knx.config_get_ga(my_GA); 80 | knx.answer1ByteInt(ga, (int8_t)value); 81 | break; 82 | } 83 | } 84 | 85 | ``` 86 | 87 | ## How to configure (buildtime) ## 88 | 89 | Open the `esp-knx-ip.h` and take a look at the config options at the top inside the block marked `CONFIG` 90 | 91 | ## How to configure (runtime) ## 92 | 93 | Simply visit the IP of your ESP with a webbrowser. You can configure the following: 94 | * KNX physical address 95 | * Which group address should trigger which callback 96 | * Which group address are to be used by the program (e.g. for status replies) 97 | 98 | The configuration is dynamically generated from the code. -------------------------------------------------------------------------------- /esp-knx-ip-config.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #include "esp-knx-ip.h" 8 | 9 | /** 10 | * Physical address functions 11 | */ 12 | 13 | void ESPKNXIP::physical_address_set(address_t const &addr) 14 | { 15 | physaddr = addr; 16 | } 17 | 18 | address_t ESPKNXIP::physical_address_get() 19 | { 20 | return physaddr; 21 | } 22 | 23 | /** 24 | * Configuration functions start here 25 | */ 26 | config_id_t ESPKNXIP::config_register_string(String name, uint8_t len, String _default, enable_condition_t cond) 27 | { 28 | if (registered_configs >= MAX_CONFIGS) 29 | return -1; 30 | 31 | if (_default.length() >= len) 32 | return -1; 33 | 34 | config_id_t id = registered_configs; 35 | 36 | custom_configs[id].name = name; 37 | custom_configs[id].type = CONFIG_TYPE_STRING; 38 | custom_configs[id].len = sizeof(uint8_t) + len; 39 | custom_configs[id].cond = cond; 40 | if (id == 0) 41 | custom_configs[id].offset = 0; 42 | else 43 | custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len; 44 | 45 | __config_set_string(id, _default); 46 | 47 | registered_configs++; 48 | 49 | DEBUG_PRINT("Registered config >"); 50 | DEBUG_PRINT(name); 51 | DEBUG_PRINT("< @ "); 52 | DEBUG_PRINT(id); 53 | DEBUG_PRINT("/string["); 54 | DEBUG_PRINT(custom_configs[id].offset); 55 | DEBUG_PRINT("+"); 56 | DEBUG_PRINT(custom_configs[id].len); 57 | DEBUG_PRINTLN("]"); 58 | 59 | return id; 60 | } 61 | 62 | config_id_t ESPKNXIP::config_register_int(String name, int32_t _default, enable_condition_t cond) 63 | { 64 | if (registered_configs >= MAX_CONFIGS) 65 | return -1; 66 | 67 | config_id_t id = registered_configs; 68 | 69 | custom_configs[id].name = name; 70 | custom_configs[id].type = CONFIG_TYPE_INT; 71 | custom_configs[id].len = sizeof(uint8_t) + sizeof(int32_t); 72 | custom_configs[id].cond = cond; 73 | if (id == 0) 74 | custom_configs[id].offset = 0; 75 | else 76 | custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len; 77 | 78 | __config_set_int(id, _default); 79 | 80 | registered_configs++; 81 | 82 | DEBUG_PRINT("Registered config >"); 83 | DEBUG_PRINT(name); 84 | DEBUG_PRINT("< @ "); 85 | DEBUG_PRINT(id); 86 | DEBUG_PRINT("/int["); 87 | DEBUG_PRINT(custom_configs[id].offset); 88 | DEBUG_PRINT("+"); 89 | DEBUG_PRINT(custom_configs[id].len); 90 | DEBUG_PRINTLN("]"); 91 | 92 | return id; 93 | } 94 | 95 | config_id_t ESPKNXIP::config_register_bool(String name, bool _default, enable_condition_t cond) 96 | { 97 | if (registered_configs >= MAX_CONFIGS) 98 | return -1; 99 | 100 | config_id_t id = registered_configs; 101 | 102 | custom_configs[id].name = name; 103 | custom_configs[id].type = CONFIG_TYPE_BOOL; 104 | custom_configs[id].len = sizeof(uint8_t) + sizeof(uint8_t); 105 | custom_configs[id].cond = cond; 106 | if (id == 0) 107 | custom_configs[id].offset = 0; 108 | else 109 | custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len; 110 | 111 | __config_set_bool(id, _default); 112 | 113 | registered_configs++; 114 | 115 | DEBUG_PRINT("Registered config >"); 116 | DEBUG_PRINT(name); 117 | DEBUG_PRINT("< @ "); 118 | DEBUG_PRINT(id); 119 | DEBUG_PRINT("/bool["); 120 | DEBUG_PRINT(custom_configs[id].offset); 121 | DEBUG_PRINT("+"); 122 | DEBUG_PRINT(custom_configs[id].len); 123 | DEBUG_PRINTLN("]"); 124 | 125 | return id; 126 | } 127 | 128 | config_id_t ESPKNXIP::config_register_options(String name, option_entry_t *options, uint8_t _default, enable_condition_t cond) 129 | { 130 | if (registered_configs >= MAX_CONFIGS) 131 | return -1; 132 | 133 | if (options == nullptr || options->name == nullptr) 134 | return -1; 135 | 136 | config_id_t id = registered_configs; 137 | 138 | custom_configs[id].name = name; 139 | custom_configs[id].type = CONFIG_TYPE_OPTIONS; 140 | custom_configs[id].len = sizeof(uint8_t) + sizeof(uint8_t); 141 | custom_configs[id].cond = cond; 142 | if (id == 0) 143 | custom_configs[id].offset = 0; 144 | else 145 | custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len; 146 | 147 | custom_configs[id].data.options = options; 148 | 149 | __config_set_options(id, _default); 150 | 151 | registered_configs++; 152 | 153 | DEBUG_PRINT("Registered config >"); 154 | DEBUG_PRINT(name); 155 | DEBUG_PRINT("< @ "); 156 | DEBUG_PRINT(id); 157 | DEBUG_PRINT("/opt["); 158 | DEBUG_PRINT(custom_configs[id].offset); 159 | DEBUG_PRINT("+"); 160 | DEBUG_PRINT(custom_configs[id].len); 161 | DEBUG_PRINTLN("]"); 162 | 163 | return id; 164 | } 165 | 166 | config_id_t ESPKNXIP::config_register_ga(String name, enable_condition_t cond) 167 | { 168 | if (registered_configs >= MAX_CONFIGS) 169 | return -1; 170 | 171 | config_id_t id = registered_configs; 172 | 173 | custom_configs[id].name = name; 174 | custom_configs[id].type = CONFIG_TYPE_GA; 175 | custom_configs[id].len = sizeof(uint8_t) + sizeof(address_t); 176 | custom_configs[id].cond = cond; 177 | if (id == 0) 178 | custom_configs[id].offset = 0; 179 | else 180 | custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len; 181 | 182 | address_t t; 183 | t.value = 0; 184 | __config_set_ga(id, t); 185 | 186 | registered_configs++; 187 | 188 | DEBUG_PRINT("Registered config >"); 189 | DEBUG_PRINT(name); 190 | DEBUG_PRINT("< @ "); 191 | DEBUG_PRINT(id); 192 | DEBUG_PRINT("/ga["); 193 | DEBUG_PRINT(custom_configs[id].offset); 194 | DEBUG_PRINT("+"); 195 | DEBUG_PRINT(custom_configs[id].len); 196 | DEBUG_PRINTLN("]"); 197 | 198 | return id; 199 | } 200 | 201 | void ESPKNXIP::__config_set_flags(config_id_t id, config_flags_t flags) 202 | { 203 | DEBUG_PRINT("Setting flag @ "); 204 | DEBUG_PRINT(custom_configs[id].offset); 205 | DEBUG_PRINT(" to "); 206 | DEBUG_PRINT(custom_config_data[custom_configs[id].offset], BIN); 207 | DEBUG_PRINT(" | "); 208 | DEBUG_PRINT(flags, BIN); 209 | custom_config_data[custom_configs[id].offset] |= (uint8_t)flags; 210 | DEBUG_PRINT(" = "); 211 | DEBUG_PRINTLN(custom_config_data[custom_configs[id].offset], BIN); 212 | } 213 | 214 | void ESPKNXIP::config_set_string(config_id_t id, String val) 215 | { 216 | if (id >= registered_configs) 217 | return; 218 | if (custom_configs[id].type != CONFIG_TYPE_STRING) 219 | return; 220 | if (val.length() >= custom_configs[id].len) 221 | return; 222 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 223 | __config_set_string(id, val); 224 | } 225 | 226 | void ESPKNXIP::__config_set_string(config_id_t id, String &val) 227 | { 228 | memcpy(&custom_config_data[custom_configs[id].offset + sizeof(uint8_t)], val.c_str(), val.length()+1); 229 | } 230 | 231 | void ESPKNXIP::config_set_int(config_id_t id, int32_t val) 232 | { 233 | if (id >= registered_configs) 234 | return; 235 | if (custom_configs[id].type != CONFIG_TYPE_INT) 236 | return; 237 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 238 | __config_set_int(id, val); 239 | } 240 | 241 | void ESPKNXIP::__config_set_int(config_id_t id, int32_t val) 242 | { 243 | // This does not work for some reason: 244 | // Could be due to pointer alignment 245 | //int32_t *v = (int32_t *)(custom_config_data + custom_configs[id].offset); 246 | //*v = val; 247 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] = (uint8_t)((val & 0xFF000000) >> 24); 248 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] = (uint8_t)((val & 0x00FF0000) >> 16); 249 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 2] = (uint8_t)((val & 0x0000FF00) >> 8); 250 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 3] = (uint8_t)((val & 0x000000FF) >> 0); 251 | } 252 | 253 | void ESPKNXIP::config_set_bool(config_id_t id, bool val) 254 | { 255 | if (id >= registered_configs) 256 | return; 257 | if (custom_configs[id].type != CONFIG_TYPE_BOOL) 258 | return; 259 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 260 | __config_set_bool(id, val); 261 | } 262 | 263 | void ESPKNXIP::__config_set_bool(config_id_t id, bool val) 264 | { 265 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] = val ? 1 : 0; 266 | } 267 | 268 | void ESPKNXIP::config_set_options(config_id_t id, uint8_t val) 269 | { 270 | if (id >= registered_configs) 271 | return; 272 | if (custom_configs[id].type != CONFIG_TYPE_OPTIONS) 273 | return; 274 | 275 | option_entry_t *cur = custom_configs[id].data.options; 276 | while (cur->name != nullptr) 277 | { 278 | if (cur->value == val) 279 | { 280 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 281 | __config_set_options(id, val); 282 | break; 283 | } 284 | cur++; 285 | } 286 | } 287 | 288 | void ESPKNXIP::__config_set_options(config_id_t id, uint8_t val) 289 | { 290 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] = val; 291 | } 292 | 293 | void ESPKNXIP::config_set_ga(config_id_t id, address_t const &val) 294 | { 295 | if (id >= registered_configs) 296 | return; 297 | if (custom_configs[id].type != CONFIG_TYPE_GA) 298 | return; 299 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 300 | __config_set_ga(id, val); 301 | } 302 | 303 | void ESPKNXIP::__config_set_ga(config_id_t id, address_t const &val) 304 | { 305 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] = val.bytes.high; 306 | custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] = val.bytes.low; 307 | } 308 | 309 | String ESPKNXIP::config_get_string(config_id_t id) 310 | { 311 | if (id >= registered_configs) 312 | return String(""); 313 | 314 | return String((char *)&custom_config_data[custom_configs[id].offset + sizeof(uint8_t)]); 315 | } 316 | 317 | int32_t ESPKNXIP::config_get_int(config_id_t id) 318 | { 319 | if (id >= registered_configs) 320 | return 0; 321 | 322 | int32_t v = (custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] << 24) + 323 | (custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] << 16) + 324 | (custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 2] << 8) + 325 | (custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 3] << 0); 326 | return v; 327 | } 328 | 329 | bool ESPKNXIP::config_get_bool(config_id_t id) 330 | { 331 | if (id >= registered_configs) 332 | return false; 333 | 334 | return custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] != 0; 335 | } 336 | 337 | uint8_t ESPKNXIP::config_get_options(config_id_t id) 338 | { 339 | if (id >= registered_configs) 340 | return false; 341 | 342 | return custom_config_data[custom_configs[id].offset + sizeof(uint8_t)]; 343 | } 344 | 345 | address_t ESPKNXIP::config_get_ga(config_id_t id) 346 | { 347 | address_t t; 348 | if (id >= registered_configs) 349 | { 350 | t.value = 0; 351 | return t; 352 | } 353 | 354 | t.bytes.high = custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0]; 355 | t.bytes.low = custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1]; 356 | 357 | return t; 358 | } 359 | -------------------------------------------------------------------------------- /esp-knx-ip-conversion.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #include "esp-knx-ip.h" 8 | 9 | /** 10 | * Conversion functions 11 | */ 12 | 13 | bool ESPKNXIP::data_to_bool(uint8_t *data) 14 | { 15 | return (data[0] & 0x01) == 1 ? true : false; 16 | } 17 | 18 | int8_t ESPKNXIP::data_to_1byte_int(uint8_t *data) 19 | { 20 | return (int8_t)data[1]; 21 | } 22 | 23 | uint8_t ESPKNXIP::data_to_1byte_uint(uint8_t *data) 24 | { 25 | return data[1]; 26 | } 27 | 28 | int16_t ESPKNXIP::data_to_2byte_int(uint8_t *data) 29 | { 30 | return (int16_t)((data[1] << 8) | data[2]); 31 | } 32 | 33 | uint16_t ESPKNXIP::data_to_2byte_uint(uint8_t *data) 34 | { 35 | return (uint16_t)((data[1] << 8) | data[2]); 36 | } 37 | 38 | float ESPKNXIP::data_to_2byte_float(uint8_t *data) 39 | { 40 | //uint8_t sign = (data[1] & 0b10000000) >> 7; 41 | uint8_t expo = (data[1] & 0b01111000) >> 3; 42 | int16_t mant = ((data[1] & 0b10000111) << 8) | data[2]; 43 | return 0.01f * mant * pow(2, expo); 44 | } 45 | 46 | time_of_day_t ESPKNXIP::data_to_3byte_time(uint8_t *data) 47 | { 48 | time_of_day_t time; 49 | time.weekday = (weekday_t)((data[1] & 0b11100000) >> 5); 50 | time.hours = (data[1] & 0b00011111); 51 | time.minutes = (data[2] & 0b00111111); 52 | time.seconds = (data[3] & 0b00111111); 53 | return time; 54 | } 55 | 56 | date_t ESPKNXIP::data_to_3byte_data(uint8_t *data) 57 | { 58 | date_t date; 59 | date.day = (data[1] & 0b00011111); 60 | date.month = (data[2] & 0b00001111); 61 | date.year = (data[3] & 0b01111111); 62 | return date; 63 | } 64 | 65 | color_t ESPKNXIP::data_to_3byte_color(uint8_t *data) 66 | { 67 | color_t color; 68 | color.red = data[1]; 69 | color.green = data[2]; 70 | color.blue = data[3]; 71 | return color; 72 | } 73 | 74 | int32_t ESPKNXIP::data_to_4byte_int(uint8_t *data) 75 | { 76 | return (int32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4] << 0)); 77 | } 78 | 79 | uint32_t ESPKNXIP::data_to_4byte_uint(uint8_t *data) 80 | { 81 | return (uint32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4] << 0)); 82 | } 83 | 84 | float ESPKNXIP::data_to_4byte_float(uint8_t *data) 85 | { 86 | return (float)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) |data[4]); 87 | } -------------------------------------------------------------------------------- /esp-knx-ip-send.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #include "esp-knx-ip.h" 8 | 9 | /** 10 | * Send functions 11 | */ 12 | 13 | void ESPKNXIP::send(address_t const &receiver, knx_command_type_t ct, uint8_t data_len, uint8_t *data) 14 | { 15 | if (receiver.value == 0) 16 | return; 17 | #if SEND_CHECKSUM 18 | uint32_t len = 6 + 2 + 8 + data_len + 1; // knx_pkt + cemi_msg + cemi_service + data + checksum 19 | #else 20 | uint32_t len = 6 + 2 + 8 + data_len; // knx_pkt + cemi_msg + cemi_service + data 21 | #endif 22 | DEBUG_PRINT(F("Creating packet with len ")); 23 | DEBUG_PRINTLN(len) 24 | uint8_t buf[len]; 25 | knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf; 26 | knx_pkt->header_len = 0x06; 27 | knx_pkt->protocol_version = 0x10; 28 | knx_pkt->service_type = __ntohs(KNX_ST_ROUTING_INDICATION); 29 | knx_pkt->total_len.len = __ntohs(len); 30 | cemi_msg_t *cemi_msg = (cemi_msg_t *)knx_pkt->pkt_data; 31 | cemi_msg->message_code = KNX_MT_L_DATA_IND; 32 | cemi_msg->additional_info_len = 0; 33 | cemi_service_t *cemi_data = &cemi_msg->data.service_information; 34 | cemi_data->control_1.bits.confirm = 0; 35 | cemi_data->control_1.bits.ack = 0; 36 | cemi_data->control_1.bits.priority = B11; 37 | cemi_data->control_1.bits.system_broadcast = 0x01; 38 | cemi_data->control_1.bits.repeat = 0x01; 39 | cemi_data->control_1.bits.reserved = 0; 40 | cemi_data->control_1.bits.frame_type = 0x01; 41 | cemi_data->control_2.bits.extended_frame_format = 0x00; 42 | cemi_data->control_2.bits.hop_count = 0x06; 43 | cemi_data->control_2.bits.dest_addr_type = 0x01; 44 | cemi_data->source = physaddr; 45 | cemi_data->destination = receiver; 46 | //cemi_data->destination.bytes.high = (area << 3) | line; 47 | //cemi_data->destination.bytes.low = member; 48 | cemi_data->data_len = data_len; 49 | cemi_data->pci.apci = (ct & 0x0C) >> 2; 50 | cemi_data->pci.tpci_seq_number = 0x00; // ??? 51 | cemi_data->pci.tpci_comm_type = KNX_COT_UDP; // ??? 52 | memcpy(cemi_data->data, data, data_len); 53 | cemi_data->data[0] = (cemi_data->data[0] & 0x3F) | ((ct & 0x03) << 6); 54 | 55 | #if SEND_CHECKSUM 56 | // Calculate checksum, which is just XOR of all bytes 57 | uint8_t cs = buf[0] ^ buf[1]; 58 | for (uint32_t i = 2; i < len - 1; ++i) 59 | { 60 | cs ^= buf[i]; 61 | } 62 | buf[len - 1] = cs; 63 | #endif 64 | 65 | DEBUG_PRINT(F("Sending packet:")); 66 | for (int i = 0; i < len; ++i) 67 | { 68 | DEBUG_PRINT(F(" 0x")); 69 | DEBUG_PRINT(buf[i], 16); 70 | } 71 | DEBUG_PRINTLN(F("")); 72 | 73 | udp.beginPacketMulticast(MULTICAST_IP, MULTICAST_PORT, WiFi.localIP()); 74 | udp.write(buf, len); 75 | udp.endPacket(); 76 | } 77 | 78 | void ESPKNXIP::send_1bit(address_t const &receiver, knx_command_type_t ct, uint8_t bit) 79 | { 80 | uint8_t buf[] = {(uint8_t)(bit & 0b00000001)}; 81 | send(receiver, ct, 1, buf); 82 | } 83 | 84 | void ESPKNXIP::send_2bit(address_t const &receiver, knx_command_type_t ct, uint8_t twobit) 85 | { 86 | uint8_t buf[] = {(uint8_t)(twobit & 0b00000011)}; 87 | send(receiver, ct, 1, buf); 88 | } 89 | 90 | void ESPKNXIP::send_4bit(address_t const &receiver, knx_command_type_t ct, uint8_t fourbit) 91 | { 92 | uint8_t buf[] = {(uint8_t)(fourbit & 0b00001111)}; 93 | send(receiver, ct, 1, buf); 94 | } 95 | 96 | void ESPKNXIP::send_1byte_int(address_t const &receiver, knx_command_type_t ct, int8_t val) 97 | { 98 | uint8_t buf[] = {0x00, (uint8_t)val}; 99 | send(receiver, ct, 2, buf); 100 | } 101 | 102 | void ESPKNXIP::send_1byte_uint(address_t const &receiver, knx_command_type_t ct, uint8_t val) 103 | { 104 | uint8_t buf[] = {0x00, val}; 105 | send(receiver, ct, 2, buf); 106 | } 107 | 108 | void ESPKNXIP::send_2byte_int(address_t const &receiver, knx_command_type_t ct, int16_t val) 109 | { 110 | uint8_t buf[] = {0x00, (uint8_t)(val >> 8), (uint8_t)(val & 0x00FF)}; 111 | send(receiver, ct, 3, buf); 112 | } 113 | 114 | void ESPKNXIP::send_2byte_uint(address_t const &receiver, knx_command_type_t ct, uint16_t val) 115 | { 116 | uint8_t buf[] = {0x00, (uint8_t)(val >> 8), (uint8_t)(val & 0x00FF)}; 117 | send(receiver, ct, 3, buf); 118 | } 119 | 120 | void ESPKNXIP::send_2byte_float(address_t const &receiver, knx_command_type_t ct, float val) 121 | { 122 | float v = val * 100.0f; 123 | int e = 0; 124 | for (; v < -2048.0f; v /= 2) 125 | ++e; 126 | for (; v > 2047.0f; v /= 2) 127 | ++e; 128 | long m = (long)round(v) & 0x7FF; 129 | short msb = (short) (e << 3 | m >> 8); 130 | if (val < 0.0f) 131 | msb |= 0x80; 132 | uint8_t buf[] = {0x00, (uint8_t)msb, (uint8_t)m}; 133 | send(receiver, ct, 3, buf); 134 | } 135 | 136 | void ESPKNXIP::send_3byte_time(address_t const &receiver, knx_command_type_t ct, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds) 137 | { 138 | weekday <<= 5; 139 | uint8_t buf[] = {0x00, (uint8_t)(((weekday << 5) & 0xE0) | (hours & 0x1F)), (uint8_t)(minutes & 0x3F), (uint8_t)(seconds & 0x3F)}; 140 | send(receiver, ct, 4, buf); 141 | } 142 | 143 | void ESPKNXIP::send_3byte_date(address_t const &receiver, knx_command_type_t ct, uint8_t day, uint8_t month, uint8_t year) 144 | { 145 | uint8_t buf[] = {0x00, (uint8_t)(day & 0x1F), (uint8_t)(month & 0x0F), year}; 146 | send(receiver, ct, 4, buf); 147 | } 148 | 149 | void ESPKNXIP::send_3byte_color(address_t const &receiver, knx_command_type_t ct, uint8_t red, uint8_t green, uint8_t blue) 150 | { 151 | uint8_t buf[] = {0x00, red, green, blue}; 152 | send(receiver, ct, 4, buf); 153 | } 154 | 155 | void ESPKNXIP::send_4byte_int(address_t const &receiver, knx_command_type_t ct, int32_t val) 156 | { 157 | uint8_t buf[] = {0x00, 158 | (uint8_t)((val & 0xFF000000) >> 24), 159 | (uint8_t)((val & 0x00FF0000) >> 16), 160 | (uint8_t)((val & 0x0000FF00) >> 8), 161 | (uint8_t)((val & 0x000000FF) >> 0)}; 162 | send(receiver, ct, 5, buf); 163 | } 164 | 165 | void ESPKNXIP::send_4byte_uint(address_t const &receiver, knx_command_type_t ct, uint32_t val) 166 | { 167 | uint8_t buf[] = {0x00, 168 | (uint8_t)((val & 0xFF000000) >> 24), 169 | (uint8_t)((val & 0x00FF0000) >> 16), 170 | (uint8_t)((val & 0x0000FF00) >> 8), 171 | (uint8_t)((val & 0x000000FF) >> 0)}; 172 | send(receiver, ct, 5, buf); 173 | } 174 | 175 | void ESPKNXIP::send_4byte_float(address_t const &receiver, knx_command_type_t ct, float val) 176 | { 177 | uint8_t buf[] = {0x00, ((uint8_t *)&val)[3], ((uint8_t *)&val)[2], ((uint8_t *)&val)[1], ((uint8_t *)&val)[0]}; 178 | send(receiver, ct, 5, buf); 179 | } 180 | 181 | void ESPKNXIP::send_14byte_string(address_t const &receiver, knx_command_type_t ct, const char *val) 182 | { 183 | // DPT16 strings are always 14 bytes long, however the data array is one larger due to the telegram structure. 184 | // The first byte needs to be zero, string start after that. 185 | uint8_t buf[15] = {0x00}; 186 | int len = strlen(val); 187 | if (len > 14) 188 | { 189 | len = 14; 190 | } 191 | memcpy(buf+1, val, len); 192 | send(receiver, ct, 15, buf); 193 | } 194 | -------------------------------------------------------------------------------- /esp-knx-ip-webserver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #include "esp-knx-ip.h" 8 | 9 | void ESPKNXIP::__handle_root() 10 | { 11 | String m = F(""); 12 | #if USE_BOOTSTRAP 13 | m += F(""); 14 | m += F(""); 15 | #endif 16 | m += F("
"); 17 | m += F("

ESP KNX

"); 18 | 19 | // Feedback 20 | 21 | if (registered_feedbacks > 0) 22 | { 23 | m += F("

Feedback

"); 24 | for (feedback_id_t i = 0; i < registered_feedbacks; ++i) 25 | { 26 | if (feedbacks[i].cond && !feedbacks[i].cond()) 27 | { 28 | continue; 29 | } 30 | m += F("
"); 31 | m += F("
"); 32 | m += F("
"); 33 | m += feedbacks[i].name; 34 | m += F("
"); 35 | switch (feedbacks[i].type) 36 | { 37 | case FEEDBACK_TYPE_INT: 38 | m += F(""); 39 | m += String(*(int32_t *)feedbacks[i].data); 40 | m += F(""); 41 | break; 42 | case FEEDBACK_TYPE_FLOAT: 43 | m += F(""); 44 | m += String(*(float *)feedbacks[i].data, feedbacks[i].options.float_options.precision); 45 | m += F(""); 46 | break; 47 | case FEEDBACK_TYPE_BOOL: 48 | m += F(""); 49 | m += (*(bool *)feedbacks[i].data) ? F("True") : F("False"); 50 | m += F(""); 51 | break; 52 | case FEEDBACK_TYPE_ACTION: 53 | m += F("
"); 56 | break; 57 | } 58 | m += F("
"); 59 | m += F("
"); 60 | } 61 | } 62 | 63 | if (registered_callbacks > 0) 64 | m += F("

Callbacks

"); 65 | 66 | if (registered_callback_assignments > 0) 67 | { 68 | for (uint8_t i = 0; i < registered_callback_assignments; ++i) 69 | { 70 | if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond()) 71 | { 72 | continue; 73 | } 74 | address_t &addr = callback_assignments[i].address; 75 | m += F("
"); 76 | m += F("
"); 77 | m += F("
"); 78 | m += addr.ga.area; 79 | m += F("/"); 80 | m += addr.ga.line; 81 | m += F("/"); 82 | m += addr.ga.member; 83 | m += F(""); 84 | m += F(""); 85 | m += callbacks[callback_assignments[i].callback_id].name; 86 | m += F("
"); 87 | m += F("
"); 90 | m += F("
"); 91 | m += F("
"); 92 | } 93 | } 94 | 95 | if (registered_callbacks > 0) 96 | { 97 | m += F("
"); 98 | m += F("
"); 99 | m += F(""); 100 | m += F("
/
"); 101 | m += F(""); 102 | m += F("
/
"); 103 | m += F(""); 104 | m += F("
->
"); 105 | m += F(""); 119 | m += F("
"); 120 | m += F("
"); 121 | m += F("
"); 122 | } 123 | 124 | m += F("

Configuration

"); 125 | 126 | // Physical address 127 | m += F("
"); 128 | m += F("
"); 129 | m += F("
Physical address
"); 130 | m += F(""); 133 | m += F("
.
"); 134 | m += F(""); 137 | m += F("
.
"); 138 | m += F(""); 141 | m += F("
"); 142 | m += F("
"); 143 | m += F("
"); 144 | 145 | if (registered_configs > 0) 146 | { 147 | for (config_id_t i = 0; i < registered_configs; ++i) 148 | { 149 | // Check if this config option has a enable condition and if so check that condition 150 | if (custom_configs[i].cond && !custom_configs[i].cond()) 151 | continue; 152 | 153 | m += F("
"); 154 | m += F("
"); 155 | m += F("
"); 156 | m += custom_configs[i].name; 157 | m += F("
"); 158 | 159 | switch (custom_configs[i].type) 160 | { 161 | case CONFIG_TYPE_STRING: 162 | m += F(""); 167 | break; 168 | case CONFIG_TYPE_INT: 169 | m += F(""); 172 | break; 173 | case CONFIG_TYPE_BOOL: 174 | m += F("
"); 175 | m += F(""); 179 | m += F("
"); 180 | break; 181 | case CONFIG_TYPE_OPTIONS: 182 | { 183 | m += F(""); 203 | break; 204 | } 205 | case CONFIG_TYPE_GA: 206 | address_t a = config_get_ga(i); 207 | m += F(""); 210 | m += F("
/
"); 211 | m += F(""); 214 | m += F("
/
"); 215 | m += F(""); 218 | break; 219 | } 220 | m += F(""); 223 | m += F("
"); 224 | m += F("
"); 225 | m += F("
"); 226 | } 227 | } 228 | 229 | #if !(DISABLE_EEPROM_BUTTONS && DISABLE_RESTORE_BUTTON && DISABLE_REBOOT_BUTTON) 230 | // EEPROM save and restore 231 | m += F("
"); 232 | // Save to EEPROM 233 | #if !DISABLE_EEPROM_BUTTONS 234 | m += F("
"); 235 | m += F("
"); 236 | m += F(""); 237 | m += F(""); 238 | m += F("
"); 239 | m += F("
"); 240 | // Restore from EEPROM 241 | m += F("
"); 242 | m += F("
"); 243 | m += F(""); 244 | m += F(""); 245 | m += F("
"); 246 | m += F("
"); 247 | #endif 248 | #if !DISABLE_RESTORE_BUTTON 249 | // Load Defaults 250 | m += F("
"); 251 | m += F("
"); 252 | m += F(""); 253 | m += F("
"); 254 | m += F("
"); 255 | #endif 256 | #if !DISABLE_REBOOT_BUTTON 257 | // Reboot 258 | m += F("
"); 259 | m += F("
"); 260 | m += F(""); 261 | m += F("
"); 262 | m += F("
"); 263 | #endif 264 | m += F("
"); // row 265 | #endif 266 | 267 | // End of page 268 | m += F("
"); 269 | server->send(200, F("text/html"), m); 270 | } 271 | 272 | void ESPKNXIP::__handle_register() 273 | { 274 | DEBUG_PRINTLN(F("Register called")); 275 | if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")) && server->hasArg(F("cb"))) 276 | { 277 | uint8_t area = server->arg(F("area")).toInt(); 278 | uint8_t line = server->arg(F("line")).toInt(); 279 | uint8_t member = server->arg(F("member")).toInt(); 280 | callback_id_t cb = (callback_id_t)server->arg(F("cb")).toInt(); 281 | 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 | 292 | if (area > 31 || line > 7) 293 | { 294 | DEBUG_PRINTLN(F("Area or Line wrong")); 295 | goto end; 296 | } 297 | 298 | if (cb >= registered_callbacks) 299 | { 300 | DEBUG_PRINTLN(F("Invalid callback id")); 301 | goto end; 302 | } 303 | address_t ga = {.ga={line, area, member}}; 304 | __callback_register_assignment(ga, cb); 305 | } 306 | end: 307 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 308 | server->send(302); 309 | } 310 | 311 | void ESPKNXIP::__handle_delete() 312 | { 313 | DEBUG_PRINTLN(F("Delete called")); 314 | if (server->hasArg(F("id"))) 315 | { 316 | callback_assignment_id_t id = (callback_assignment_id_t)server->arg(F("id")).toInt(); 317 | 318 | DEBUG_PRINT(F("Got args: ")); 319 | DEBUG_PRINT(id); 320 | DEBUG_PRINTLN(F("")); 321 | 322 | if (id >= registered_callback_assignments) 323 | { 324 | DEBUG_PRINTLN(F("ID wrong")); 325 | goto end; 326 | } 327 | 328 | __callback_delete_assignment(id); 329 | } 330 | end: 331 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 332 | server->send(302); 333 | } 334 | 335 | void ESPKNXIP::__handle_set() 336 | { 337 | DEBUG_PRINTLN(F("Set called")); 338 | if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member"))) 339 | { 340 | uint8_t area = server->arg(F("area")).toInt(); 341 | uint8_t line = server->arg(F("line")).toInt(); 342 | uint8_t member = server->arg(F("member")).toInt(); 343 | 344 | DEBUG_PRINT(F("Got args: ")); 345 | DEBUG_PRINT(area); 346 | DEBUG_PRINT(F(".")); 347 | DEBUG_PRINT(line); 348 | DEBUG_PRINT(F(".")); 349 | DEBUG_PRINT(member); 350 | DEBUG_PRINTLN(F("")); 351 | 352 | if (area > 31 || line > 7) 353 | { 354 | DEBUG_PRINTLN(F("Area or Line wrong")); 355 | goto end; 356 | } 357 | 358 | physaddr.bytes.high = (area << 4) | line; 359 | physaddr.bytes.low = member; 360 | } 361 | end: 362 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 363 | server->send(302); 364 | } 365 | 366 | void ESPKNXIP::__handle_config() 367 | { 368 | DEBUG_PRINTLN(F("Config called")); 369 | if (server->hasArg(F("id"))) 370 | { 371 | config_id_t id = server->arg(F("id")).toInt(); 372 | 373 | DEBUG_PRINT(F("Got args: ")); 374 | DEBUG_PRINT(id); 375 | DEBUG_PRINTLN(F("")); 376 | 377 | if (id < 0 || id >= registered_configs) 378 | { 379 | DEBUG_PRINTLN(F("ID wrong")); 380 | goto end; 381 | } 382 | 383 | switch (custom_configs[id].type) 384 | { 385 | case CONFIG_TYPE_STRING: 386 | { 387 | String v = server->arg(F("value")); 388 | if (v.length() >= custom_configs[id].len) 389 | goto end; 390 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 391 | __config_set_string(id, v); 392 | break; 393 | } 394 | case CONFIG_TYPE_INT: 395 | { 396 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 397 | __config_set_int(id, server->arg(F("value")).toInt()); 398 | break; 399 | } 400 | case CONFIG_TYPE_BOOL: 401 | { 402 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 403 | __config_set_bool(id, server->arg(F("value")).compareTo(F("on")) == 0); 404 | break; 405 | } 406 | case CONFIG_TYPE_OPTIONS: 407 | { 408 | uint8_t val = (uint8_t)server->arg(F("value")).toInt(); 409 | DEBUG_PRINT(F("Value: ")); 410 | DEBUG_PRINTLN(val); 411 | config_set_options(id, val); 412 | break; 413 | } 414 | case CONFIG_TYPE_GA: 415 | { 416 | uint8_t area = server->arg(F("area")).toInt(); 417 | uint8_t line = server->arg(F("line")).toInt(); 418 | uint8_t member = server->arg(F("member")).toInt(); 419 | if (area > 31 || line > 7) 420 | { 421 | DEBUG_PRINTLN(F("Area or Line wrong")); 422 | goto end; 423 | } 424 | address_t tmp; 425 | tmp.bytes.high = (area << 3) | line; 426 | tmp.bytes.low = member; 427 | __config_set_flags(id, CONFIG_FLAGS_VALUE_SET); 428 | __config_set_ga(id, tmp); 429 | break; 430 | } 431 | } 432 | } 433 | end: 434 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 435 | server->send(302); 436 | } 437 | 438 | void ESPKNXIP::__handle_feedback() 439 | { 440 | DEBUG_PRINTLN(F("Feedback called")); 441 | if (server->hasArg(F("id"))) 442 | { 443 | config_id_t id = server->arg(F("id")).toInt(); 444 | 445 | DEBUG_PRINT(F("Got args: ")); 446 | DEBUG_PRINT(id); 447 | DEBUG_PRINTLN(F("")); 448 | 449 | if (id < 0 || id >= registered_feedbacks) 450 | { 451 | DEBUG_PRINTLN(F("ID wrong")); 452 | goto end; 453 | } 454 | 455 | switch (feedbacks[id].type) 456 | { 457 | case FEEDBACK_TYPE_ACTION: 458 | { 459 | feedback_action_fptr_t func = (feedback_action_fptr_t)feedbacks[id].data; 460 | void *arg = feedbacks[id].options.action_options.arg; 461 | func(arg); 462 | break; 463 | } 464 | default: 465 | DEBUG_PRINTLN(F("Feedback has no action")); 466 | break; 467 | } 468 | } 469 | end: 470 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 471 | server->send(302); 472 | } 473 | 474 | #if !DISABLE_RESTORE_BUTTONS 475 | void ESPKNXIP::__handle_restore() 476 | { 477 | DEBUG_PRINTLN(F("Restore called")); 478 | memcpy(custom_config_data, custom_config_default_data, MAX_CONFIG_SPACE); 479 | end: 480 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 481 | server->send(302); 482 | } 483 | #endif 484 | 485 | #if !DISABLE_REBOOT_BUTTONS 486 | void ESPKNXIP::__handle_reboot() 487 | { 488 | DEBUG_PRINTLN(F("Rebooting!")); 489 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 490 | server->send(302); 491 | delay(1000); 492 | ESP.restart(); 493 | //while(1); 494 | } 495 | #endif 496 | 497 | #if !DISABLE_EEPROM_BUTTONS 498 | void ESPKNXIP::__handle_eeprom() 499 | { 500 | DEBUG_PRINTLN(F("EEPROM called")); 501 | if (server->hasArg(F("mode"))) 502 | { 503 | uint8_t mode = server->arg(F("mode")).toInt(); 504 | 505 | DEBUG_PRINT(F("Got args: ")); 506 | DEBUG_PRINT(mode); 507 | DEBUG_PRINTLN(F("")); 508 | 509 | if (mode == 1) 510 | { 511 | // save 512 | save_to_eeprom(); 513 | } 514 | else if (mode == 2) 515 | { 516 | // restore 517 | restore_from_eeprom(); 518 | } 519 | } 520 | end: 521 | server->sendHeader(F("Location"),F(__ROOT_PATH)); 522 | server->send(302); 523 | } 524 | #endif 525 | -------------------------------------------------------------------------------- /esp-knx-ip.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #include "esp-knx-ip.h" 8 | 9 | ESPKNXIP::ESPKNXIP() : server(nullptr), registered_callback_assignments(0), registered_callbacks(0), registered_configs(0), registered_feedbacks(0) 10 | { 11 | DEBUG_PRINTLN(); 12 | DEBUG_PRINTLN("ESPKNXIP starting up"); 13 | // Default physical address is 1.1.0 14 | physaddr.bytes.high = (/*area*/1 << 4) | /*line*/1; 15 | physaddr.bytes.low = /*member*/0; 16 | memset(callback_assignments, 0, MAX_CALLBACK_ASSIGNMENTS * sizeof(callback_assignment_t)); 17 | memset(callbacks, 0, MAX_CALLBACKS * sizeof(callback_fptr_t)); 18 | memset(custom_config_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t)); 19 | memset(custom_config_default_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t)); 20 | memset(custom_configs, 0, MAX_CONFIGS * sizeof(config_t)); 21 | } 22 | 23 | void ESPKNXIP::load() 24 | { 25 | memcpy(custom_config_default_data, custom_config_data, MAX_CONFIG_SPACE); 26 | EEPROM.begin(EEPROM_SIZE); 27 | restore_from_eeprom(); 28 | } 29 | 30 | void ESPKNXIP::start(ESP8266WebServer *srv) 31 | { 32 | server = srv; 33 | __start(); 34 | } 35 | 36 | void ESPKNXIP::start() 37 | { 38 | server = new ESP8266WebServer(80); 39 | __start(); 40 | } 41 | 42 | void ESPKNXIP::__start() 43 | { 44 | if (server != nullptr) 45 | { 46 | server->on(ROOT_PREFIX, [this](){ 47 | __handle_root(); 48 | }); 49 | server->on(__ROOT_PATH, [this](){ 50 | __handle_root(); 51 | }); 52 | server->on(__REGISTER_PATH, [this](){ 53 | __handle_register(); 54 | }); 55 | server->on(__DELETE_PATH, [this](){ 56 | __handle_delete(); 57 | }); 58 | server->on(__PHYS_PATH, [this](){ 59 | __handle_set(); 60 | }); 61 | #if !DISABLE_EEPROM_BUTTONS 62 | server->on(__EEPROM_PATH, [this](){ 63 | __handle_eeprom(); 64 | }); 65 | #endif 66 | server->on(__CONFIG_PATH, [this](){ 67 | __handle_config(); 68 | }); 69 | server->on(__FEEDBACK_PATH, [this](){ 70 | __handle_feedback(); 71 | }); 72 | #if !DISABLE_RESTORE_BUTTON 73 | server->on(__RESTORE_PATH, [this](){ 74 | __handle_restore(); 75 | }); 76 | #endif 77 | #if !DISABLE_REBOOT_BUTTON 78 | server->on(__REBOOT_PATH, [this](){ 79 | __handle_reboot(); 80 | }); 81 | #endif 82 | server->begin(); 83 | } 84 | 85 | udp.beginMulticast(WiFi.localIP(), MULTICAST_IP, MULTICAST_PORT); 86 | } 87 | 88 | void ESPKNXIP::save_to_eeprom() 89 | { 90 | uint32_t address = 0; 91 | uint64_t magic = EEPROM_MAGIC; 92 | EEPROM.put(address, magic); 93 | address += sizeof(uint64_t); 94 | EEPROM.put(address++, registered_callback_assignments); 95 | for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i) 96 | { 97 | EEPROM.put(address, callback_assignments[i].address); 98 | address += sizeof(address_t); 99 | } 100 | for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i) 101 | { 102 | EEPROM.put(address, callback_assignments[i].callback_id); 103 | address += sizeof(callback_id_t); 104 | } 105 | EEPROM.put(address, physaddr); 106 | address += sizeof(address_t); 107 | 108 | EEPROM.put(address, custom_config_data); 109 | address += sizeof(custom_config_data); 110 | 111 | EEPROM.commit(); 112 | DEBUG_PRINT("Wrote to EEPROM: 0x"); 113 | DEBUG_PRINTLN(address, HEX); 114 | } 115 | 116 | void ESPKNXIP::restore_from_eeprom() 117 | { 118 | uint32_t address = 0; 119 | uint64_t magic = 0; 120 | EEPROM.get(address, magic); 121 | if (magic != EEPROM_MAGIC) 122 | { 123 | DEBUG_PRINTLN("No valid magic in EEPROM, aborting restore."); 124 | DEBUG_PRINT("Expected 0x"); 125 | DEBUG_PRINT((unsigned long)(EEPROM_MAGIC >> 32), HEX); 126 | DEBUG_PRINT(" 0x"); 127 | DEBUG_PRINT((unsigned long)(EEPROM_MAGIC), HEX); 128 | DEBUG_PRINT(" got 0x"); 129 | DEBUG_PRINT((unsigned long)(magic >> 32), HEX); 130 | DEBUG_PRINT(" 0x"); 131 | DEBUG_PRINTLN((unsigned long)magic, HEX); 132 | return; 133 | } 134 | address += sizeof(uint64_t); 135 | EEPROM.get(address++, registered_callback_assignments); 136 | for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i) 137 | { 138 | EEPROM.get(address, callback_assignments[i].address); 139 | address += sizeof(address_t); 140 | } 141 | for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i) 142 | { 143 | EEPROM.get(address, callback_assignments[i].callback_id); 144 | address += sizeof(callback_id_t); 145 | } 146 | EEPROM.get(address, physaddr); 147 | address += sizeof(address_t); 148 | 149 | //EEPROM.get(address, custom_config_data); 150 | //address += sizeof(custom_config_data); 151 | uint32_t conf_offset = address; 152 | for (uint8_t i = 0; i < registered_configs; ++i) 153 | { 154 | // First byte is flags. 155 | config_flags_t flags = CONFIG_FLAGS_NO_FLAGS; 156 | flags = (config_flags_t)EEPROM.read(address); 157 | DEBUG_PRINT("Flag in EEPROM @ "); 158 | DEBUG_PRINT(address - conf_offset); 159 | DEBUG_PRINT(": "); 160 | DEBUG_PRINTLN(flags, BIN); 161 | custom_config_data[custom_configs[i].offset] = flags; 162 | if (flags & CONFIG_FLAGS_VALUE_SET) 163 | { 164 | DEBUG_PRINTLN("Non-default value"); 165 | for (int j = 0; j < custom_configs[i].len - sizeof(uint8_t); ++j) 166 | { 167 | custom_config_data[custom_configs[i].offset + sizeof(uint8_t) + j] = EEPROM.read(address + sizeof(uint8_t) + j); 168 | } 169 | } 170 | 171 | address += custom_configs[i].len; 172 | } 173 | 174 | DEBUG_PRINT("Restored from EEPROM: 0x"); 175 | DEBUG_PRINTLN(address, HEX); 176 | } 177 | 178 | uint16_t ESPKNXIP::__ntohs(uint16_t n) 179 | { 180 | return (uint16_t)((((uint8_t*)&n)[0] << 8) | (((uint8_t*)&n)[1])); 181 | } 182 | 183 | callback_assignment_id_t ESPKNXIP::__callback_register_assignment(address_t address, callback_id_t id) 184 | { 185 | if (registered_callback_assignments >= MAX_CALLBACK_ASSIGNMENTS) 186 | return -1; 187 | 188 | callback_assignment_id_t aid = registered_callback_assignments; 189 | 190 | callback_assignments[aid].address = address; 191 | callback_assignments[aid].callback_id = id; 192 | registered_callback_assignments++; 193 | return aid; 194 | } 195 | 196 | void ESPKNXIP::__callback_delete_assignment(callback_assignment_id_t id) 197 | { 198 | if (id >= registered_callback_assignments) 199 | return; 200 | 201 | uint32_t dest_offset = 0; 202 | uint32_t src_offset = 0; 203 | uint32_t len = 0; 204 | if (id == 0) 205 | { 206 | // start of array, so delete first entry 207 | src_offset = 1; 208 | // registered_ga_callbacks will be 1 in case of only one entry 209 | // registered_ga_callbacks will be 2 in case of two entries, etc.. 210 | // so only copy anything, if there is it at least more then one element 211 | len = (registered_callback_assignments - 1); 212 | } 213 | else if (id == registered_callback_assignments - 1) 214 | { 215 | // last element, don't do anything, simply decrement counter 216 | } 217 | else 218 | { 219 | // somewhere in the middle 220 | // need to calc offsets 221 | 222 | // skip all prev elements 223 | dest_offset = id; // id is equal to how many element are in front of it 224 | src_offset = dest_offset + 1; // start after the current element 225 | len = (registered_callback_assignments - 1 - id); 226 | } 227 | 228 | if (len > 0) 229 | { 230 | memmove(callback_assignments + dest_offset, callback_assignments + src_offset, len * sizeof(callback_assignment_t)); 231 | } 232 | 233 | registered_callback_assignments--; 234 | } 235 | 236 | callback_id_t ESPKNXIP::callback_register(String name, callback_fptr_t cb, void *arg, enable_condition_t cond) 237 | { 238 | if (registered_callbacks >= MAX_CALLBACKS) 239 | return -1; 240 | 241 | callback_id_t id = registered_callbacks; 242 | 243 | callbacks[id].name = name; 244 | callbacks[id].fkt = cb; 245 | callbacks[id].cond = cond; 246 | callbacks[id].arg = arg; 247 | registered_callbacks++; 248 | return id; 249 | } 250 | 251 | void ESPKNXIP::callback_assign(callback_id_t id, address_t val) 252 | { 253 | if (id >= registered_callbacks) 254 | return; 255 | 256 | __callback_register_assignment(val, id); 257 | } 258 | 259 | /** 260 | * Feedback functions start here 261 | */ 262 | 263 | feedback_id_t ESPKNXIP::feedback_register_int(String name, int32_t *value, enable_condition_t cond) 264 | { 265 | if (registered_feedbacks >= MAX_FEEDBACKS) 266 | return -1; 267 | 268 | feedback_id_t id = registered_feedbacks; 269 | 270 | feedbacks[id].type = FEEDBACK_TYPE_INT; 271 | feedbacks[id].name = name; 272 | feedbacks[id].cond = cond; 273 | feedbacks[id].data = (void *)value; 274 | 275 | registered_feedbacks++; 276 | 277 | return id; 278 | } 279 | 280 | feedback_id_t ESPKNXIP::feedback_register_float(String name, float *value, uint8_t precision, enable_condition_t cond) 281 | { 282 | if (registered_feedbacks >= MAX_FEEDBACKS) 283 | return -1; 284 | 285 | feedback_id_t id = registered_feedbacks; 286 | 287 | feedbacks[id].type = FEEDBACK_TYPE_FLOAT; 288 | feedbacks[id].name = name; 289 | feedbacks[id].cond = cond; 290 | feedbacks[id].data = (void *)value; 291 | feedbacks[id].options.float_options.precision = precision; 292 | 293 | registered_feedbacks++; 294 | 295 | return id; 296 | } 297 | 298 | feedback_id_t ESPKNXIP::feedback_register_bool(String name, bool *value, enable_condition_t cond) 299 | { 300 | if (registered_feedbacks >= MAX_FEEDBACKS) 301 | return -1; 302 | 303 | feedback_id_t id = registered_feedbacks; 304 | 305 | feedbacks[id].type = FEEDBACK_TYPE_BOOL; 306 | feedbacks[id].name = name; 307 | feedbacks[id].cond = cond; 308 | feedbacks[id].data = (void *)value; 309 | 310 | registered_feedbacks++; 311 | 312 | return id; 313 | } 314 | 315 | feedback_id_t ESPKNXIP::feedback_register_action(String name, feedback_action_fptr_t value, void *arg, enable_condition_t cond) 316 | { 317 | if (registered_feedbacks >= MAX_FEEDBACKS) 318 | return -1; 319 | 320 | feedback_id_t id = registered_feedbacks; 321 | 322 | feedbacks[id].type = FEEDBACK_TYPE_ACTION; 323 | feedbacks[id].name = name; 324 | feedbacks[id].cond = cond; 325 | feedbacks[id].data = (void *)value; 326 | feedbacks[id].options.action_options.arg = arg; 327 | 328 | registered_feedbacks++; 329 | 330 | return id; 331 | } 332 | 333 | void ESPKNXIP::loop() 334 | { 335 | __loop_knx(); 336 | if (server != nullptr) 337 | { 338 | __loop_webserver(); 339 | } 340 | } 341 | 342 | void ESPKNXIP::__loop_webserver() 343 | { 344 | server->handleClient(); 345 | } 346 | 347 | void ESPKNXIP::__loop_knx() 348 | { 349 | int read = udp.parsePacket(); 350 | if (!read) 351 | { 352 | return; 353 | } 354 | DEBUG_PRINTLN(F("")); 355 | DEBUG_PRINT(F("LEN: ")); 356 | DEBUG_PRINTLN(read); 357 | 358 | uint8_t buf[read]; 359 | 360 | udp.read(buf, read); 361 | udp.flush(); 362 | 363 | DEBUG_PRINT(F("Got packet:")); 364 | for (int i = 0; i < read; ++i) 365 | { 366 | DEBUG_PRINT(F(" 0x")); 367 | DEBUG_PRINT(buf[i], 16); 368 | } 369 | DEBUG_PRINTLN(F("")); 370 | 371 | knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf; 372 | 373 | DEBUG_PRINT(F("ST: 0x")); 374 | DEBUG_PRINTLN(__ntohs(knx_pkt->service_type), 16); 375 | 376 | if (knx_pkt->header_len != 0x06 && knx_pkt->protocol_version != 0x10 && knx_pkt->service_type != KNX_ST_ROUTING_INDICATION) 377 | return; 378 | 379 | cemi_msg_t *cemi_msg = (cemi_msg_t *)knx_pkt->pkt_data; 380 | 381 | DEBUG_PRINT(F("MT: 0x")); 382 | DEBUG_PRINTLN(cemi_msg->message_code, 16); 383 | 384 | if (cemi_msg->message_code != KNX_MT_L_DATA_IND) 385 | return; 386 | 387 | DEBUG_PRINT(F("ADDI: 0x")); 388 | DEBUG_PRINTLN(cemi_msg->additional_info_len, 16); 389 | 390 | cemi_service_t *cemi_data = &cemi_msg->data.service_information; 391 | 392 | if (cemi_msg->additional_info_len > 0) 393 | cemi_data = (cemi_service_t *)(((uint8_t *)cemi_data) + cemi_msg->additional_info_len); 394 | 395 | DEBUG_PRINT(F("C1: 0x")); 396 | DEBUG_PRINTLN(cemi_data->control_1.byte, 16); 397 | 398 | DEBUG_PRINT(F("C2: 0x")); 399 | DEBUG_PRINTLN(cemi_data->control_2.byte, 16); 400 | 401 | DEBUG_PRINT(F("DT: 0x")); 402 | DEBUG_PRINTLN(cemi_data->control_2.bits.dest_addr_type, 16); 403 | 404 | if (cemi_data->control_2.bits.dest_addr_type != 0x01) 405 | return; 406 | 407 | DEBUG_PRINT(F("HC: 0x")); 408 | DEBUG_PRINTLN(cemi_data->control_2.bits.hop_count, 16); 409 | 410 | DEBUG_PRINT(F("EFF: 0x")); 411 | DEBUG_PRINTLN(cemi_data->control_2.bits.extended_frame_format, 16); 412 | 413 | DEBUG_PRINT(F("Source: 0x")); 414 | DEBUG_PRINT(cemi_data->source.bytes.high, 16); 415 | DEBUG_PRINT(F(" 0x")); 416 | DEBUG_PRINTLN(cemi_data->source.bytes.low, 16); 417 | 418 | DEBUG_PRINT(F("Dest: 0x")); 419 | DEBUG_PRINT(cemi_data->destination.bytes.high, 16); 420 | DEBUG_PRINT(F(" 0x")); 421 | DEBUG_PRINTLN(cemi_data->destination.bytes.low, 16); 422 | 423 | knx_command_type_t ct = (knx_command_type_t)(((cemi_data->data[0] & 0xC0) >> 6) | ((cemi_data->pci.apci & 0x03) << 2)); 424 | 425 | DEBUG_PRINT(F("CT: 0x")); 426 | DEBUG_PRINTLN(ct, 16); 427 | 428 | for (int i = 0; i < cemi_data->data_len; ++i) 429 | { 430 | DEBUG_PRINT(F(" 0x")); 431 | DEBUG_PRINT(cemi_data->data[i], 16); 432 | } 433 | 434 | DEBUG_PRINTLN(F("==")); 435 | 436 | // Call callbacks 437 | for (int i = 0; i < registered_callback_assignments; ++i) 438 | { 439 | DEBUG_PRINT(F("Testing: 0x")); 440 | DEBUG_PRINT(callback_assignments[i].address.bytes.high, 16); 441 | DEBUG_PRINT(F(" 0x")); 442 | DEBUG_PRINTLN(callback_assignments[i].address.bytes.low, 16); 443 | if (cemi_data->destination.value == callback_assignments[i].address.value) 444 | { 445 | DEBUG_PRINTLN(F("Found match")); 446 | if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond()) 447 | { 448 | DEBUG_PRINTLN(F("But it's disabled")); 449 | #if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS 450 | continue; 451 | #else 452 | return; 453 | #endif 454 | } 455 | uint8_t data[cemi_data->data_len]; 456 | memcpy(data, cemi_data->data, cemi_data->data_len); 457 | data[0] = data[0] & 0x3F; 458 | message_t msg = {}; 459 | msg.ct = ct; 460 | msg.received_on = cemi_data->destination; 461 | msg.data_len = cemi_data->data_len; 462 | msg.data = data; 463 | callbacks[callback_assignments[i].callback_id].fkt(msg, callbacks[callback_assignments[i].callback_id].arg); 464 | #if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS 465 | continue; 466 | #else 467 | return; 468 | #endif 469 | } 470 | } 471 | 472 | return; 473 | } 474 | 475 | // Global "singleton" object 476 | ESPKNXIP knx; 477 | -------------------------------------------------------------------------------- /esp-knx-ip.h: -------------------------------------------------------------------------------- 1 | /** 2 | * esp-knx-ip library for KNX/IP communication on an ESP8266 3 | * Author: Nico Weichbrodt 4 | * License: MIT 5 | */ 6 | 7 | #ifndef ESP_KNX_IP_H 8 | #define ESP_KNX_IP_H 9 | 10 | /** 11 | * CONFIG 12 | * All MAX_ values must not exceed 255 (1 byte, except MAC_CONFIG_SPACE which can go up to 2 bytes, so 0xffff in theory) and must not be negative! 13 | * Config space is restriced by EEPROM_SIZE (default 1024). 14 | * Required EEPROM size is 8 + MAX_GA_CALLBACKS * 3 + 2 + MAX_CONFIG_SPACE which is 552 by default 15 | */ 16 | #define EEPROM_SIZE 1024 // [Default 1024] 17 | #define MAX_CALLBACK_ASSIGNMENTS 10 // [Default 10] Maximum number of group address callbacks that can be stored 18 | #define MAX_CALLBACKS 10 // [Default 10] Maximum number of callbacks that can be stored 19 | #define MAX_CONFIGS 20 // [Default 20] Maximum number of config items that can be stored 20 | #define MAX_CONFIG_SPACE 0x0200 // [Default 0x0200] Maximum number of bytes that can be stored for custom config 21 | 22 | #define MAX_FEEDBACKS 20 // [Default 20] Maximum number of feedbacks that can be shown 23 | 24 | // Callbacks 25 | #define ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS 0 // [Default 0] Set to 1 to always test all assigned callbacks. This allows for multiple callbacks being assigned to the same address. If disabled, only the first assigned will be called. 26 | 27 | // Webserver related 28 | #define USE_BOOTSTRAP 1 // [Default 1] Set to 1 to enable use of bootstrap CSS for nicer webconfig. CSS is loaded from bootstrapcdn.com. Set to 0 to disable 29 | #define ROOT_PREFIX "" // [Default ""] This gets prepended to all webserver paths, default is empty string "". Set this to "/knx" if you want the config to be available on http:///knx 30 | #define DISABLE_EEPROM_BUTTONS 0 // [Default 0] Set to 1 to disable the EEPROM buttons in the web ui. 31 | #define DISABLE_REBOOT_BUTTON 0 // [Default 0] Set to 1 to disable the reboot button in the web ui. 32 | #define DISABLE_RESTORE_BUTTON 0 // [Default 0] Set to 1 to disable the "restore defaults" button in the web ui. 33 | 34 | // These values normally don't need adjustment 35 | #ifndef MULTICAST_PORT 36 | #define MULTICAST_PORT 3671 // [Default 3671] 37 | #endif 38 | #ifndef MULTICAST_IP 39 | #define MULTICAST_IP IPAddress(224, 0, 23, 12) // [Default IPAddress(224, 0, 23, 12)] 40 | #endif 41 | #define SEND_CHECKSUM 0 42 | 43 | // Uncomment to enable printing out debug messages. 44 | #define ESP_KNX_DEBUG 45 | /** 46 | * END CONFIG 47 | */ 48 | 49 | #include "Arduino.h" 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include "DPT.h" 56 | 57 | #define EEPROM_MAGIC (0xDEADBEEF00000000 + (MAX_CONFIG_SPACE) + (MAX_CALLBACK_ASSIGNMENTS << 16) + (MAX_CALLBACKS << 8)) 58 | 59 | // Define where debug output will be printed. 60 | #ifndef DEBUG_PRINTER 61 | #define DEBUG_PRINTER Serial 62 | #endif 63 | 64 | // Setup debug printing macros. 65 | #ifdef ESP_KNX_DEBUG 66 | #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); } 67 | #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); } 68 | #else 69 | #define DEBUG_PRINT(...) {} 70 | #define DEBUG_PRINTLN(...) {} 71 | #endif 72 | 73 | #define __ROOT_PATH ROOT_PREFIX"/" 74 | #define __REGISTER_PATH ROOT_PREFIX"/register" 75 | #define __DELETE_PATH ROOT_PREFIX"/delete" 76 | #define __PHYS_PATH ROOT_PREFIX"/phys" 77 | #define __EEPROM_PATH ROOT_PREFIX"/eeprom" 78 | #define __CONFIG_PATH ROOT_PREFIX"/config" 79 | #define __FEEDBACK_PATH ROOT_PREFIX"/feedback" 80 | #define __RESTORE_PATH ROOT_PREFIX"/restore" 81 | #define __REBOOT_PATH ROOT_PREFIX"/reboot" 82 | 83 | /** 84 | * Different service types, we are mainly interested in KNX_ST_ROUTING_INDICATION 85 | */ 86 | typedef enum __knx_service_type 87 | { 88 | KNX_ST_SEARCH_REQUEST = 0x0201, 89 | KNX_ST_SEARCH_RESPONSE = 0x0202, 90 | KNX_ST_DESCRIPTION_REQUEST = 0x0203, 91 | KNX_ST_DESCRIPTION_RESPONSE = 0x0204, 92 | KNX_ST_CONNECT_REQUEST = 0x0205, 93 | KNX_ST_CONNECT_RESPONSE = 0x0206, 94 | KNX_ST_CONNECTIONSTATE_REQUEST = 0x0207, 95 | KNX_ST_CONNECTIONSTATE_RESPONSE = 0x0208, 96 | KNX_ST_DISCONNECT_REQUEST = 0x0209, 97 | KNX_ST_DISCONNECT_RESPONSE = 0x020A, 98 | 99 | KNX_ST_DEVICE_CONFIGURATION_REQUEST = 0x0310, 100 | KNX_ST_DEVICE_CONFIGURATION_ACK = 0x0311, 101 | 102 | KNX_ST_TUNNELING_REQUEST = 0x0420, 103 | KNX_ST_TUNNELING_ACK = 0x0421, 104 | 105 | KNX_ST_ROUTING_INDICATION = 0x0530, 106 | KNX_ST_ROUTING_LOST_MESSAGE = 0x0531, 107 | KNX_ST_ROUTING_BUSY = 0x0532, 108 | 109 | // KNX_ST_RLOG_START = 0x0600, 110 | // KNX_ST_RLOG_END = 0x06FF, 111 | 112 | KNX_ST_REMOTE_DIAGNOSTIC_REQUEST = 0x0740, 113 | KNX_ST_REMOTE_DIAGNOSTIC_RESPONSE = 0x0741, 114 | KNX_ST_REMOTE_BASIC_CONFIGURATION_REQUEST = 0x0742, 115 | KNX_ST_REMOTE_RESET_REQUEST = 0x0743, 116 | 117 | // KNX_ST_OBJSRV_START = 0x0800, 118 | // KNX_ST_OBJSRV_END = 0x08FF, 119 | } knx_service_type_t; 120 | 121 | /** 122 | * Differnt command types, first three are of main interest 123 | */ 124 | typedef enum __knx_command_type 125 | { 126 | KNX_CT_READ = 0x00, 127 | KNX_CT_ANSWER = 0x01, 128 | KNX_CT_WRITE = 0x02, 129 | KNX_CT_INDIVIDUAL_ADDR_WRITE = 0x03, 130 | KNX_CT_INDIVIDUAL_ADDR_REQUEST = 0x04, 131 | KNX_CT_INDIVIDUAL_ADDR_RESPONSE = 0x05, 132 | KNX_CT_ADC_READ = 0x06, 133 | KNX_CT_ADC_ANSWER = 0x07, 134 | KNX_CT_MEM_READ = 0x08, 135 | KNX_CT_MEM_ANSWER = 0x09, 136 | KNX_CT_MEM_WRITE = 0x0A, 137 | //KNX_CT_UNKNOWN = 0x0B, 138 | KNX_CT_MASK_VERSION_READ = 0x0C, 139 | KNX_CT_MASK_VERSION_RESPONSE = 0x0D, 140 | KNX_CT_RESTART = 0x0E, 141 | KNX_CT_ESCAPE = 0x0F, 142 | } knx_command_type_t; 143 | 144 | /** 145 | * cEMI message types, mainly KNX_MT_L_DATA_IND is interesting 146 | */ 147 | typedef enum __knx_cemi_msg_type 148 | { 149 | KNX_MT_L_DATA_REQ = 0x11, 150 | KNX_MT_L_DATA_IND = 0x29, 151 | KNX_MT_L_DATA_CON = 0x2E, 152 | } knx_cemi_msg_type_t; 153 | 154 | /** 155 | * TCPI communication type 156 | */ 157 | typedef enum __knx_communication_type { 158 | KNX_COT_UDP = 0x00, // Unnumbered Data Packet 159 | KNX_COT_NDP = 0x01, // Numbered Data Packet 160 | KNX_COT_UCD = 0x02, // Unnumbered Control Data 161 | KNX_COT_NCD = 0x03, // Numbered Control Data 162 | } knx_communication_type_t; 163 | 164 | /** 165 | * KNX/IP header 166 | */ 167 | typedef struct __knx_ip_pkt 168 | { 169 | uint8_t header_len; // Should always be 0x06 170 | uint8_t protocol_version; // Should be version 1.0, transmitted as 0x10 171 | uint16_t service_type; // See knx_service_type_t 172 | union 173 | { 174 | struct { 175 | uint8_t first_byte; 176 | uint8_t second_byte; 177 | } bytes; 178 | uint16_t len; 179 | } total_len; // header_len + rest of pkt. This is a bit weird as the spec says this: If the total number of bytes transmitted is greater than 252 bytes, the first “Total Length” byte is set to FF (255). Only in this case the second byte includes additional length information 180 | uint8_t pkt_data[]; // This is of type cemi_msg_t 181 | } knx_ip_pkt_t; 182 | 183 | typedef struct __cemi_addi 184 | { 185 | uint8_t type_id; 186 | uint8_t len; 187 | uint8_t data[]; 188 | } cemi_addi_t; 189 | 190 | typedef union __address 191 | { 192 | uint16_t value; 193 | struct 194 | { 195 | uint8_t high; 196 | uint8_t low; 197 | } bytes; 198 | struct __attribute__((packed)) 199 | { 200 | uint8_t line:3; 201 | uint8_t area:5; 202 | uint8_t member; 203 | } ga; 204 | struct __attribute__((packed)) 205 | { 206 | uint8_t line:4; 207 | uint8_t area:4; 208 | uint8_t member; 209 | } pa; 210 | uint8_t array[2]; 211 | } address_t; 212 | 213 | typedef struct __cemi_service 214 | { 215 | union 216 | { 217 | struct 218 | { 219 | // Struct is reversed due to bit order 220 | uint8_t confirm:1; // 0 = no error, 1 = error 221 | uint8_t ack:1; // 0 = no ack, 1 = ack 222 | uint8_t priority:2; // 0 = system, 1 = high, 2 = urgent/alarm, 3 = normal 223 | uint8_t system_broadcast:1; // 0 = system broadcast, 1 = broadcast 224 | uint8_t repeat:1; // 0 = repeat on error, 1 = do not repeat 225 | uint8_t reserved:1; // always zero 226 | uint8_t frame_type:1; // 0 = extended, 1 = standard 227 | } bits; 228 | uint8_t byte; 229 | } control_1; 230 | union 231 | { 232 | struct 233 | { 234 | // Struct is reversed due to bit order 235 | uint8_t extended_frame_format:4; 236 | uint8_t hop_count:3; 237 | uint8_t dest_addr_type:1; // 0 = individual, 1 = group 238 | } bits; 239 | uint8_t byte; 240 | } control_2; 241 | address_t source; 242 | address_t destination; 243 | uint8_t data_len; // length of data, excluding the tpci byte 244 | struct 245 | { 246 | uint8_t apci:2; // If tpci.comm_type == KNX_COT_UCD or KNX_COT_NCD, then this is apparently control data? 247 | uint8_t tpci_seq_number:4; 248 | uint8_t tpci_comm_type:2; // See knx_communication_type_t 249 | } pci; 250 | uint8_t data[]; 251 | } cemi_service_t; 252 | 253 | typedef struct __cemi_msg 254 | { 255 | uint8_t message_code; 256 | uint8_t additional_info_len; 257 | union 258 | { 259 | cemi_addi_t additional_info[]; 260 | cemi_service_t service_information; 261 | } data; 262 | } cemi_msg_t; 263 | 264 | typedef enum __config_type 265 | { 266 | CONFIG_TYPE_UNKNOWN, 267 | CONFIG_TYPE_INT, 268 | CONFIG_TYPE_BOOL, 269 | CONFIG_TYPE_STRING, 270 | CONFIG_TYPE_OPTIONS, 271 | CONFIG_TYPE_GA, 272 | } config_type_t; 273 | 274 | typedef enum __feedback_type 275 | { 276 | FEEDBACK_TYPE_UNKNOWN, 277 | FEEDBACK_TYPE_INT, 278 | FEEDBACK_TYPE_FLOAT, 279 | FEEDBACK_TYPE_BOOL, 280 | FEEDBACK_TYPE_ACTION, 281 | } feedback_type_t; 282 | 283 | typedef enum __config_flags 284 | { 285 | CONFIG_FLAGS_NO_FLAGS = 0, 286 | CONFIG_FLAGS_VALUE_SET = 1, 287 | } config_flags_t; 288 | 289 | typedef struct __message 290 | { 291 | knx_command_type_t ct; 292 | address_t received_on; 293 | uint8_t data_len; 294 | uint8_t *data; 295 | } message_t; 296 | 297 | typedef bool (*enable_condition_t)(void); 298 | typedef void (*callback_fptr_t)(message_t const &msg, void *arg); 299 | typedef void (*feedback_action_fptr_t)(void *arg); 300 | 301 | typedef uint8_t callback_id_t; 302 | typedef uint8_t callback_assignment_id_t; 303 | typedef uint8_t config_id_t; 304 | typedef uint8_t feedback_id_t; 305 | 306 | typedef struct __option_entry 307 | { 308 | char *name; 309 | uint8_t value; 310 | } option_entry_t; 311 | 312 | typedef struct __config 313 | { 314 | config_type_t type; 315 | String name; 316 | uint8_t offset; 317 | uint8_t len; 318 | enable_condition_t cond; 319 | union { 320 | option_entry_t *options; 321 | } data; 322 | } config_t; 323 | 324 | typedef struct __feedback_float_options 325 | { 326 | uint8_t precision; 327 | } feedback_float_options_t; 328 | 329 | typedef struct __feedback_action_options 330 | { 331 | void * arg; 332 | } feedback_action_options_t; 333 | 334 | typedef struct __feedback 335 | { 336 | feedback_type_t type; 337 | String name; 338 | enable_condition_t cond; 339 | void *data; 340 | union { 341 | feedback_float_options_t float_options; 342 | feedback_action_options_t action_options; 343 | } options; 344 | } feedback_t; 345 | 346 | typedef struct __callback 347 | { 348 | callback_fptr_t fkt; 349 | enable_condition_t cond; 350 | void *arg; 351 | String name; 352 | } callback_t; 353 | 354 | typedef struct __callback_assignment 355 | { 356 | address_t address; 357 | callback_id_t callback_id; 358 | } callback_assignment_t; 359 | 360 | class ESPKNXIP { 361 | public: 362 | ESPKNXIP(); 363 | void load(); 364 | void start(); 365 | void start(ESP8266WebServer *srv); 366 | void loop(); 367 | 368 | void save_to_eeprom(); 369 | void restore_from_eeprom(); 370 | 371 | callback_id_t callback_register(String name, callback_fptr_t cb, void *arg = nullptr, enable_condition_t cond = nullptr); 372 | void callback_assign(callback_id_t id, address_t val); 373 | 374 | void physical_address_set(address_t const &addr); 375 | address_t physical_address_get(); 376 | 377 | // Configuration functions 378 | config_id_t config_register_string(String name, uint8_t len, String _default, enable_condition_t cond = nullptr); 379 | config_id_t config_register_int(String name, int32_t _default, enable_condition_t cond = nullptr); 380 | config_id_t config_register_bool(String name, bool _default, enable_condition_t cond = nullptr); 381 | config_id_t config_register_options(String name, option_entry_t *options, uint8_t _default, enable_condition_t cond = nullptr); 382 | config_id_t config_register_ga(String name, enable_condition_t cond = nullptr); 383 | 384 | String config_get_string(config_id_t id); 385 | int32_t config_get_int(config_id_t id); 386 | bool config_get_bool(config_id_t id); 387 | uint8_t config_get_options(config_id_t id); 388 | address_t config_get_ga(config_id_t id); 389 | 390 | void config_set_string(config_id_t id, String val); 391 | void config_set_int(config_id_t id, int32_t val); 392 | void config_set_bool(config_id_t, bool val); 393 | void config_set_options(config_id_t id, uint8_t val); 394 | void config_set_ga(config_id_t id, address_t const &val); 395 | 396 | // Feedback functions 397 | feedback_id_t feedback_register_int(String name, int32_t *value, enable_condition_t cond = nullptr); 398 | feedback_id_t feedback_register_float(String name, float *value, uint8_t precision = 2, enable_condition_t cond = nullptr); 399 | feedback_id_t feedback_register_bool(String name, bool *value, enable_condition_t cond = nullptr); 400 | feedback_id_t feedback_register_action(String name, feedback_action_fptr_t value, void *arg = nullptr, enable_condition_t = nullptr); 401 | 402 | // Send functions 403 | void send(address_t const &receiver, knx_command_type_t ct, uint8_t data_len, uint8_t *data); 404 | 405 | void send_1bit(address_t const &receiver, knx_command_type_t ct, uint8_t bit); 406 | void send_2bit(address_t const &receiver, knx_command_type_t ct, uint8_t twobit); 407 | void send_4bit(address_t const &receiver, knx_command_type_t ct, uint8_t fourbit); 408 | void send_1byte_int(address_t const &receiver, knx_command_type_t ct, int8_t val); 409 | void send_1byte_uint(address_t const &receiver, knx_command_type_t ct, uint8_t val); 410 | void send_2byte_int(address_t const &receiver, knx_command_type_t ct, int16_t val); 411 | void send_2byte_uint(address_t const &receiver, knx_command_type_t ct, uint16_t val); 412 | void send_2byte_float(address_t const &receiver, knx_command_type_t ct, float val); 413 | void send_3byte_time(address_t const &receiver, knx_command_type_t ct, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds); 414 | void send_3byte_time(address_t const &receiver, knx_command_type_t ct, time_of_day_t const &time) { send_3byte_time(receiver, ct, time.weekday, time.hours, time.minutes, time.seconds); } 415 | void send_3byte_date(address_t const &receiver, knx_command_type_t ct, uint8_t day, uint8_t month, uint8_t year); 416 | void send_3byte_date(address_t const &receiver, knx_command_type_t ct, date_t const &date) { send_3byte_date(receiver, ct, date.day, date.month, date.year); } 417 | void send_3byte_color(address_t const &receiver, knx_command_type_t ct, uint8_t red, uint8_t green, uint8_t blue); 418 | void send_3byte_color(address_t const &receiver, knx_command_type_t ct, color_t const &color) { send_3byte_color(receiver, ct, color.red, color.green, color.blue); } 419 | void send_4byte_int(address_t const &receiver, knx_command_type_t ct, int32_t val); 420 | void send_4byte_uint(address_t const &receiver, knx_command_type_t ct, uint32_t val); 421 | void send_4byte_float(address_t const &receiver, knx_command_type_t ct, float val); 422 | void send_14byte_string(address_t const &receiver, knx_command_type_t ct, const char *val); 423 | 424 | void write_1bit(address_t const &receiver, uint8_t bit) { send_1bit(receiver, KNX_CT_WRITE, bit); } 425 | void write_2bit(address_t const &receiver, uint8_t twobit) { send_2bit(receiver, KNX_CT_WRITE, twobit); } 426 | void write_4bit(address_t const &receiver, uint8_t fourbit) { send_4bit(receiver, KNX_CT_WRITE, fourbit); } 427 | void write_1byte_int(address_t const &receiver, int8_t val) { send_1byte_int(receiver, KNX_CT_WRITE, val); } 428 | void write_1byte_uint(address_t const &receiver, uint8_t val) { send_1byte_uint(receiver, KNX_CT_WRITE, val); } 429 | void write_2byte_int(address_t const &receiver, int16_t val) { send_2byte_int(receiver, KNX_CT_WRITE, val); } 430 | void write_2byte_uint(address_t const &receiver, uint16_t val) { send_2byte_uint(receiver, KNX_CT_WRITE, val); } 431 | void write_2byte_float(address_t const &receiver, float val) { send_2byte_float(receiver, KNX_CT_WRITE, val); } 432 | void write_3byte_time(address_t const &receiver, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds) { send_3byte_time(receiver, KNX_CT_WRITE, weekday, hours, minutes, seconds); } 433 | void write_3byte_time(address_t const &receiver, time_of_day_t const &time) { send_3byte_time(receiver, KNX_CT_WRITE, time.weekday, time.hours, time.minutes, time.seconds); } 434 | void write_3byte_date(address_t const &receiver, uint8_t day, uint8_t month, uint8_t year) { send_3byte_date(receiver, KNX_CT_WRITE, day, month, year); } 435 | void write_3byte_date(address_t const &receiver, date_t const &date) { send_3byte_date(receiver, KNX_CT_WRITE, date.day, date.month, date.year); } 436 | void write_3byte_color(address_t const &receiver, uint8_t red, uint8_t green, uint8_t blue) { send_3byte_color(receiver, KNX_CT_WRITE, red, green, blue); } 437 | void write_3byte_color(address_t const &receiver, color_t const &color) { send_3byte_color(receiver, KNX_CT_WRITE, color); } 438 | void write_4byte_int(address_t const &receiver, int32_t val) { send_4byte_int(receiver, KNX_CT_WRITE, val); } 439 | void write_4byte_uint(address_t const &receiver, uint32_t val) { send_4byte_uint(receiver, KNX_CT_WRITE, val); } 440 | void write_4byte_float(address_t const &receiver, float val) { send_4byte_float(receiver, KNX_CT_WRITE, val); } 441 | void write_14byte_string(address_t const &receiver, const char *val) { send_14byte_string(receiver, KNX_CT_WRITE, val); } 442 | 443 | void answer_1bit(address_t const &receiver, uint8_t bit) { send_1bit(receiver, KNX_CT_ANSWER, bit); } 444 | void answer_2bit(address_t const &receiver, uint8_t twobit) { send_2bit(receiver, KNX_CT_ANSWER, twobit); } 445 | void answer_4bit(address_t const &receiver, uint8_t fourbit) { send_4bit(receiver, KNX_CT_ANSWER, fourbit); } 446 | void answer_1byte_int(address_t const &receiver, int8_t val) { send_1byte_int(receiver, KNX_CT_ANSWER, val); } 447 | void answer_1byte_uint(address_t const &receiver, uint8_t val) { send_1byte_uint(receiver, KNX_CT_ANSWER, val); } 448 | void answer_2byte_int(address_t const &receiver, int16_t val) { send_2byte_int(receiver, KNX_CT_ANSWER, val); } 449 | void answer_2byte_uint(address_t const &receiver, uint16_t val) { send_2byte_uint(receiver, KNX_CT_ANSWER, val); } 450 | void answer_2byte_float(address_t const &receiver, float val) { send_2byte_float(receiver, KNX_CT_ANSWER, val); } 451 | void answer_3byte_time(address_t const &receiver, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds) { send_3byte_time(receiver, KNX_CT_ANSWER, weekday, hours, minutes, seconds); } 452 | void answer_3byte_time(address_t const &receiver, time_of_day_t const &time) { send_3byte_time(receiver, KNX_CT_ANSWER, time.weekday, time.hours, time.minutes, time.seconds); } 453 | void answer_3byte_date(address_t const &receiver, uint8_t day, uint8_t month, uint8_t year) { send_3byte_date(receiver, KNX_CT_ANSWER, day, month, year); } 454 | void answer_3byte_date(address_t const &receiver, date_t const &date) { send_3byte_date(receiver, KNX_CT_ANSWER, date.day, date.month, date.year); } 455 | void answer_3byte_color(address_t const &receiver, uint8_t red, uint8_t green, uint8_t blue) { send_3byte_color(receiver, KNX_CT_ANSWER, red, green, blue); } 456 | void answer_3byte_color(address_t const &receiver, color_t const &color) { send_3byte_color(receiver, KNX_CT_ANSWER, color); } 457 | void answer_4byte_int(address_t const &receiver, int32_t val) { send_4byte_int(receiver, KNX_CT_ANSWER, val); } 458 | void answer_4byte_uint(address_t const &receiver, uint32_t val) { send_4byte_uint(receiver, KNX_CT_ANSWER, val); } 459 | void answer_4byte_float(address_t const &receiver, float val) { send_4byte_float(receiver, KNX_CT_ANSWER, val);} 460 | void answer_14byte_string(address_t const &receiver, const char *val) { send_14byte_string(receiver, KNX_CT_ANSWER, val); } 461 | 462 | bool data_to_bool(uint8_t *data); 463 | int8_t data_to_1byte_int(uint8_t *data); 464 | uint8_t data_to_1byte_uint(uint8_t *data); 465 | int16_t data_to_2byte_int(uint8_t *data); 466 | uint16_t data_to_2byte_uint(uint8_t *data); 467 | float data_to_2byte_float(uint8_t *data); 468 | color_t data_to_3byte_color(uint8_t *data); 469 | time_of_day_t data_to_3byte_time(uint8_t *data); 470 | date_t data_to_3byte_data(uint8_t *data); 471 | int32_t data_to_4byte_int(uint8_t *data); 472 | uint32_t data_to_4byte_uint(uint8_t *data); 473 | float data_to_4byte_float(uint8_t *data); 474 | 475 | static address_t GA_to_address(uint8_t area, uint8_t line, uint8_t member) 476 | { 477 | // Yes, the order is correct, see the struct definition above 478 | address_t tmp = {.ga={line, area, member}}; 479 | return tmp; 480 | } 481 | 482 | static address_t PA_to_address(uint8_t area, uint8_t line, uint8_t member) 483 | { 484 | // Yes, the order is correct, see the struct definition above 485 | address_t tmp = {.pa={line, area, member}}; 486 | return tmp; 487 | } 488 | 489 | private: 490 | void __start(); 491 | void __loop_knx(); 492 | 493 | // Webserver functions 494 | void __loop_webserver(); 495 | void __handle_root(); 496 | void __handle_register(); 497 | void __handle_delete(); 498 | void __handle_set(); 499 | #if !DISABLE_EEPROM_BUTTONS 500 | void __handle_eeprom(); 501 | #endif 502 | void __handle_config(); 503 | void __handle_feedback(); 504 | #if !DISABLE_RESTORE_BUTTONS 505 | void __handle_restore(); 506 | #endif 507 | #if !DISABLE_REBOOT_BUTTONS 508 | void __handle_reboot(); 509 | #endif 510 | 511 | void __config_set_flags(config_id_t id, config_flags_t flags); 512 | 513 | void __config_set_string(config_id_t id, String &val); 514 | void __config_set_int(config_id_t id, int32_t val); 515 | void __config_set_bool(config_id_t id, bool val); 516 | void __config_set_options(config_id_t id, uint8_t val); 517 | void __config_set_ga(config_id_t id, address_t const &val); 518 | 519 | callback_assignment_id_t __callback_register_assignment(address_t address, callback_id_t id); 520 | void __callback_delete_assignment(callback_assignment_id_t id); 521 | 522 | ESP8266WebServer *server; 523 | address_t physaddr; 524 | WiFiUDP udp; 525 | 526 | callback_assignment_id_t registered_callback_assignments; 527 | callback_assignment_t callback_assignments[MAX_CALLBACK_ASSIGNMENTS]; 528 | 529 | callback_id_t registered_callbacks; 530 | callback_t callbacks[MAX_CALLBACKS]; 531 | 532 | config_id_t registered_configs; 533 | uint8_t custom_config_data[MAX_CONFIG_SPACE]; 534 | uint8_t custom_config_default_data[MAX_CONFIG_SPACE]; 535 | config_t custom_configs[MAX_CONFIGS]; 536 | 537 | feedback_id_t registered_feedbacks; 538 | feedback_t feedbacks[MAX_FEEDBACKS]; 539 | 540 | uint16_t __ntohs(uint16_t); 541 | }; 542 | 543 | // Global "singleton" object 544 | extern ESPKNXIP knx; 545 | 546 | #endif 547 | -------------------------------------------------------------------------------- /examples/environment-sensor/environment-sensor.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an example showing a simple environment sensor based on a BME280 attached via I2C. 3 | * This sketch was tested on a WeMos D1 mini 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | // WiFi config here 10 | const char* ssid = "myssid"; 11 | const char* pass = "mypassword"; 12 | 13 | #define LED_PIN D4 14 | #define UPDATE_INTERVAL 10000 15 | 16 | unsigned long next_change = 0; 17 | 18 | float last_temp = 0.0; 19 | float last_hum = 0.0; 20 | float last_pres = 0.0; 21 | 22 | config_id_t temp_ga, hum_ga, pres_ga; 23 | config_id_t hostname_id; 24 | config_id_t update_rate_id, send_rate_id; 25 | config_id_t enable_sending_id; 26 | config_id_t enable_reading_id; 27 | 28 | Adafruit_BME280 bme; 29 | 30 | void setup() { 31 | pinMode(LED_PIN, OUTPUT); 32 | Serial.begin(115200); 33 | 34 | hostname_id = knx.config_register_string("Hostname", 20, String("env")); 35 | enable_sending_id = knx.config_register_bool("Send on update", true); 36 | update_rate_id = knx.config_register_int("Update rate (ms)", UPDATE_INTERVAL); 37 | temp_ga = knx.config_register_ga("Temperature", show_periodic_options); 38 | hum_ga = knx.config_register_ga("Humidity", show_periodic_options); 39 | pres_ga = knx.config_register_ga("Pressure", show_periodic_options); 40 | 41 | knx.callback_register("Read Temperature", temp_cb); 42 | knx.callback_register("Read Humidity", hum_cb); 43 | knx.callback_register("Read Pressure", pres_cb); 44 | 45 | knx.feedback_register_float("Temperature (°C)", &last_temp); 46 | knx.feedback_register_float("Humidity (%)", &last_hum); 47 | knx.feedback_register_float("Pressure (hPa)", &last_pres, 0); 48 | 49 | // Load previous config from EEPROM 50 | knx.load(); 51 | 52 | // Init sensor 53 | if (!bme.begin(0x76)) { 54 | Serial.println("Could not find a valid BME280 sensor, check wiring!"); 55 | } 56 | 57 | // Init WiFi 58 | WiFi.hostname(knx.config_get_string(hostname_id)); 59 | WiFi.begin(ssid, pass); 60 | 61 | Serial.println(""); 62 | Serial.print("[Connecting]"); 63 | Serial.print(ssid); 64 | 65 | digitalWrite(LED_PIN, LOW); 66 | while (WiFi.status() != WL_CONNECTED) { 67 | digitalWrite(LED_PIN, HIGH); 68 | delay(250); 69 | Serial.print("."); 70 | digitalWrite(LED_PIN, LOW); 71 | delay(250); 72 | } 73 | digitalWrite(LED_PIN, HIGH); 74 | 75 | // Start knx 76 | knx.start(); 77 | 78 | Serial.println(); 79 | Serial.println("Connected to wifi"); 80 | Serial.println(WiFi.localIP()); 81 | } 82 | 83 | void loop() { 84 | knx.loop(); 85 | 86 | unsigned long now = millis(); 87 | 88 | if (next_change < now) 89 | { 90 | next_change = now + knx.config_get_int(update_rate_id); 91 | 92 | last_temp = bme.readTemperature(); 93 | last_hum = bme.readHumidity(); 94 | last_pres = bme.readPressure()/100.0f; 95 | 96 | Serial.print("T: "); 97 | Serial.print(last_temp); 98 | Serial.print("°C H: "); 99 | Serial.print(last_hum); 100 | Serial.print("% P: "); 101 | Serial.print(last_pres); 102 | Serial.println("hPa"); 103 | 104 | if (knx.config_get_bool(enable_sending_id)) 105 | { 106 | knx.write_2byte_float(knx.config_get_ga(temp_ga), last_temp); 107 | knx.write_2byte_float(knx.config_get_ga(hum_ga), last_hum); 108 | knx.write_2byte_float(knx.config_get_ga(pres_ga), last_pres); 109 | } 110 | } 111 | 112 | delay(50); 113 | } 114 | 115 | bool show_periodic_options() 116 | { 117 | return knx.config_get_bool(enable_sending_id); 118 | } 119 | 120 | bool enable_reading_callback() 121 | { 122 | return knx.config_get_bool(enable_reading_id); 123 | } 124 | 125 | void temp_cb(message_t const &msg, void *arg) 126 | { 127 | switch (msg.ct) 128 | { 129 | case KNX_CT_READ: 130 | { 131 | knx.answer_2byte_float(msg.received_on, last_temp); 132 | break; 133 | } 134 | } 135 | } 136 | 137 | void hum_cb(message_t const &msg, void *arg) 138 | { 139 | switch (msg.ct) 140 | { 141 | case KNX_CT_READ: 142 | { 143 | knx.answer_2byte_float(msg.received_on, last_hum); 144 | break; 145 | } 146 | } 147 | } 148 | 149 | void pres_cb(message_t const &msg, void *arg) 150 | { 151 | switch (msg.ct) 152 | { 153 | case KNX_CT_READ: 154 | { 155 | knx.answer_2byte_float(msg.received_on, last_pres); 156 | break; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /examples/sonoff/sonoff.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // WiFi config here 4 | const char* ssid = "ssid"; 5 | const char* pass = "pass"; 6 | 7 | // Common 8 | #define LED_PIN 13 9 | 10 | // For Basic and S20 11 | #define BTN1_PIN 0 12 | #define CH1_PIN 12 13 | 14 | // For 4CH 15 | #define BTN2_PIN 9 16 | #define CH2_PIN 5 17 | #define BTN3_PIN 10 18 | #define CH3_PIN 4 19 | #define BTN4_PIN 14 20 | #define CH4_PIN 15 21 | 22 | typedef enum __type_option 23 | { 24 | SONOFF_TYPE_NONE = 0, 25 | SONOFF_TYPE_BASIC = 1, 26 | SONOFF_TYPE_S20 = 2, 27 | SONOFF_TYPE_4CH = 3, 28 | SONOFF_TYPE_4CH_PRO = 4, 29 | } type_option_t; 30 | 31 | option_entry_t type_options[] = { 32 | {"Sonoff Basic", SONOFF_TYPE_BASIC}, 33 | {"Sonoff S20", SONOFF_TYPE_S20}, 34 | {"Sonoff 4CH", SONOFF_TYPE_4CH}, 35 | {"Sonoff 4CH Pro", SONOFF_TYPE_4CH_PRO}, 36 | {nullptr, 0} 37 | }; 38 | 39 | config_id_t hostname_id; 40 | config_id_t type_id; 41 | 42 | typedef struct __sonoff_channel 43 | { 44 | int pin; 45 | int btn_pin; 46 | config_id_t status_ga_id; 47 | bool state; 48 | bool last_btn_state; 49 | } sonoff_channel_t; 50 | 51 | sonoff_channel_t channels[] = { 52 | {CH1_PIN, BTN1_PIN, 0, false, false}, 53 | {CH2_PIN, BTN2_PIN, 0, false, false}, 54 | {CH3_PIN, BTN3_PIN, 0, false, false}, 55 | {CH4_PIN, BTN4_PIN, 0, false, false}, 56 | }; 57 | 58 | void setup() 59 | { 60 | pinMode(LED_PIN, OUTPUT); 61 | pinMode(BTN1_PIN, INPUT_PULLUP); 62 | pinMode(BTN2_PIN, INPUT_PULLUP); 63 | pinMode(BTN3_PIN, INPUT_PULLUP); 64 | pinMode(BTN4_PIN, INPUT_PULLUP); 65 | pinMode(CH1_PIN, OUTPUT); 66 | pinMode(CH2_PIN, OUTPUT); 67 | pinMode(CH3_PIN, OUTPUT); 68 | pinMode(CH4_PIN, OUTPUT); 69 | Serial.begin(115200); 70 | 71 | // Register the config options 72 | hostname_id = knx.config_register_string("Hostname", 20, String("sonoff")); 73 | type_id = knx.config_register_options("Type", type_options, SONOFF_TYPE_BASIC); 74 | 75 | channels[0].status_ga_id = knx.config_register_ga("Channel 1 Status GA"); 76 | channels[1].status_ga_id = knx.config_register_ga("Channel 2 Status GA", is_4ch_or_4ch_pro); 77 | channels[2].status_ga_id = knx.config_register_ga("Channel 3 Status GA", is_4ch_or_4ch_pro); 78 | channels[3].status_ga_id = knx.config_register_ga("Channel 4 Status GA", is_4ch_or_4ch_pro); 79 | 80 | knx.callback_register("Channel 1", channel_cb, &channels[0]); 81 | knx.callback_register("Channel 2", channel_cb, &channels[1], is_4ch_or_4ch_pro); 82 | knx.callback_register("Channel 3", channel_cb, &channels[2], is_4ch_or_4ch_pro); 83 | knx.callback_register("Channel 4", channel_cb, &channels[3], is_4ch_or_4ch_pro); 84 | 85 | knx.feedback_register_bool("Channel 1 is on", &(channels[0].state)); 86 | knx.feedback_register_action("Toogle channel 1", toggle_chan, &channels[0]); 87 | knx.feedback_register_bool("Channel 2 is on", &(channels[1].state), is_4ch_or_4ch_pro); 88 | knx.feedback_register_action("Toogle channel 2", toggle_chan, &channels[1], is_4ch_or_4ch_pro); 89 | knx.feedback_register_bool("Channel 3 is on", &(channels[2].state), is_4ch_or_4ch_pro); 90 | knx.feedback_register_action("Toogle channel 3", toggle_chan, &channels[2], is_4ch_or_4ch_pro); 91 | knx.feedback_register_bool("Channel 4 is on", &(channels[3].state), is_4ch_or_4ch_pro); 92 | knx.feedback_register_action("Toogle channel 4", toggle_chan, &channels[3], is_4ch_or_4ch_pro); 93 | 94 | knx.load(); 95 | 96 | // Init WiFi 97 | WiFi.hostname(knx.config_get_string(hostname_id)); 98 | WiFi.begin(ssid, pass); 99 | 100 | Serial.println(""); 101 | Serial.print("[Connecting]"); 102 | Serial.print(ssid); 103 | 104 | digitalWrite(LED_PIN, LOW); 105 | while (WiFi.status() != WL_CONNECTED) { 106 | digitalWrite(LED_PIN, HIGH); 107 | delay(500); 108 | Serial.print("."); 109 | digitalWrite(LED_PIN, LOW); 110 | } 111 | digitalWrite(LED_PIN, HIGH); 112 | 113 | // Start knx 114 | knx.start(); 115 | 116 | Serial.println(); 117 | Serial.println("Connected to wifi"); 118 | Serial.println(WiFi.localIP()); 119 | } 120 | 121 | void loop() 122 | { 123 | knx.loop(); 124 | 125 | // Check local buttons 126 | check_button(&channels[0]); 127 | if (is_4ch_or_4ch_pro()) 128 | { 129 | check_button(&channels[1]); 130 | check_button(&channels[2]); 131 | check_button(&channels[3]); 132 | } 133 | 134 | delay(50); 135 | } 136 | 137 | bool is_basic_or_s20() 138 | { 139 | uint8_t type = knx.config_get_options(type_id); 140 | return type == SONOFF_TYPE_BASIC || type == SONOFF_TYPE_S20; 141 | } 142 | 143 | bool is_4ch_or_4ch_pro() 144 | { 145 | uint8_t type = knx.config_get_options(type_id); 146 | return type == SONOFF_TYPE_4CH ||type == SONOFF_TYPE_4CH_PRO; 147 | } 148 | 149 | void check_button(sonoff_channel_t *chan) 150 | { 151 | bool state_now = digitalRead(chan->btn_pin) == HIGH ? true : false; 152 | if (state_now != chan->last_btn_state && state_now == LOW) 153 | { 154 | chan->state = !chan->state; 155 | digitalWrite(chan->pin, chan->state ? HIGH : LOW); 156 | knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state); 157 | } 158 | chan->last_btn_state = state_now; 159 | } 160 | 161 | void toggle_chan(void *arg) 162 | { 163 | sonoff_channel_t *chan = (sonoff_channel_t *)arg; 164 | chan->state = !chan->state; 165 | digitalWrite(chan->pin, chan->state ? HIGH : LOW); 166 | knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state); 167 | } 168 | 169 | void channel_cb(message_t const &msg, void *arg) 170 | { 171 | sonoff_channel_t *chan = (sonoff_channel_t *)arg; 172 | switch (msg.ct) 173 | { 174 | case KNX_CT_WRITE: 175 | chan->state = msg.data[0]; 176 | Serial.println(chan->state ? "Toggle on" : "Toggle off"); 177 | digitalWrite(chan->pin, chan->state ? HIGH : LOW); 178 | knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state); 179 | break; 180 | case KNX_CT_READ: 181 | knx.answer_1bit(msg.received_on, chan->state); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /examples/static-config/static-config.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an example showing a simple environment sensor based on a BME280 attached via I2C. 3 | * It shows, how the library can used to statically configure a device without a webserver for config. 4 | * This sketch was tested on a WeMos D1 mini 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | // WiFi config here 11 | const char* ssid = "myssid"; 12 | const char* pass = "mypassword"; 13 | 14 | #define LED_PIN D4 15 | #define UPDATE_INTERVAL 10000 16 | 17 | unsigned long next_change = 0; 18 | 19 | float last_temp = 0.0; 20 | float last_hum = 0.0; 21 | float last_pres = 0.0; 22 | 23 | Adafruit_BME280 bme; 24 | 25 | // Group addresses to send to (1/1/1, 1/1/2 and 1/1/3) 26 | address_t temp_ga = knx.GA_to_address(1, 1, 1); 27 | address_t hum_ga = knx.GA_to_address(1, 1, 2); 28 | address_t pres_ga = knx.GA_to_address(1, 1, 3); 29 | 30 | void setup() { 31 | pinMode(LED_PIN, OUTPUT); 32 | Serial.begin(115200); 33 | 34 | callback_id_t temp_cb_id = knx.callback_register("Read Temperature", temp_cb); 35 | callback_id_t hum_cb_id =knx.callback_register("Read Humidity", hum_cb); 36 | callback_id_t pres_cb_id =knx.callback_register("Read Pressure", pres_cb); 37 | 38 | // Assign callbacks to group addresses (2/1/1, 2/1/2, 2/1/3) 39 | knx.callback_assign(temp_cb_id, knx.GA_to_address(2, 1, 1)); 40 | knx.callback_assign(hum_cb_id, knx.GA_to_address(2, 1, 2)); 41 | knx.callback_assign(pres_cb_id, knx.GA_to_address(2, 1, 3)); 42 | 43 | // Set physical address (1.1.1) 44 | knx.physical_address_set(knx.PA_to_address(1, 1, 1)); 45 | 46 | // Do not call knx.load() for static config, it will try to load config from EEPROM which we don't have here 47 | 48 | // Init sensor 49 | if (!bme.begin(0x76)) { 50 | Serial.println("Could not find a valid BME280 sensor, check wiring!"); 51 | } 52 | 53 | // Init WiFi 54 | WiFi.hostname("env"); 55 | WiFi.begin(ssid, pass); 56 | 57 | Serial.println(""); 58 | Serial.print("[Connecting]"); 59 | Serial.print(ssid); 60 | 61 | digitalWrite(LED_PIN, LOW); 62 | while (WiFi.status() != WL_CONNECTED) { 63 | digitalWrite(LED_PIN, HIGH); 64 | delay(250); 65 | Serial.print("."); 66 | digitalWrite(LED_PIN, LOW); 67 | delay(250); 68 | } 69 | digitalWrite(LED_PIN, HIGH); 70 | 71 | // Start knx, disable webserver by passing nullptr 72 | knx.start(nullptr); 73 | 74 | Serial.println(); 75 | Serial.println("Connected to wifi"); 76 | Serial.println(WiFi.localIP()); 77 | } 78 | 79 | void loop() { 80 | knx.loop(); 81 | 82 | unsigned long now = millis(); 83 | 84 | if (next_change < now) 85 | { 86 | next_change = now + UPDATE_INTERVAL; 87 | 88 | last_temp = bme.readTemperature(); 89 | last_hum = bme.readHumidity(); 90 | last_pres = bme.readPressure()/100.0f; 91 | 92 | Serial.print("T: "); 93 | Serial.print(last_temp); 94 | Serial.print("°C H: "); 95 | Serial.print(last_hum); 96 | Serial.print("% P: "); 97 | Serial.print(last_pres); 98 | Serial.println("hPa"); 99 | 100 | knx.write_2byte_float(temp_ga, last_temp); 101 | knx.write_2byte_float(hum_ga, last_hum); 102 | knx.write_2byte_float(pres_ga, last_pres); 103 | } 104 | 105 | delay(50); 106 | } 107 | 108 | void temp_cb(message_t const &msg, void *arg) 109 | { 110 | switch (msg.ct) 111 | { 112 | case KNX_CT_READ: 113 | { 114 | knx.answer_2byte_float(msg.received_on, last_temp); 115 | break; 116 | } 117 | } 118 | } 119 | 120 | void hum_cb(message_t const &msg, void *arg) 121 | { 122 | switch (msg.ct) 123 | { 124 | case KNX_CT_READ: 125 | { 126 | knx.answer_2byte_float(msg.received_on, last_hum); 127 | break; 128 | } 129 | } 130 | } 131 | 132 | void pres_cb(message_t const &msg, void *arg) 133 | { 134 | switch (msg.ct) 135 | { 136 | case KNX_CT_READ: 137 | { 138 | knx.answer_2byte_float(msg.received_on, last_pres); 139 | break; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # datatypes 2 | address_t KEYWORD1 DATA_TYPE 3 | callback_id_t KEYWORD1 DATA_TYPE 4 | callback_assignment_id_t KEYWORD1 DATA_TYPE 5 | config_id_t KEYWORD1 DATA_TYPE 6 | enable_condition_t KEYWORD1 DATA_TYPE 7 | callback_fptr_t KEYWORD1 DATA_TYPE 8 | knx_command_type_t KEYWORD1 DATA_TYPE 9 | 10 | # methods 11 | setup KEYWORD2 12 | loop KEYWORD2 13 | GA_to_address KEYWORD2 14 | PA_to_address KEYWORD2 15 | callback_register KEYWORD2 16 | callback_assign KEYWORD2 17 | config_register_string KEYWORD2 18 | config_register_int KEYWORD2 19 | config_register_ga KEYWORD2 20 | config_register_bool KEYWORD2 21 | config_get_string KEYWORD2 22 | config_get_int KEYWORD2 23 | config_get_ga KEYWORD2 24 | config_get_bool KEYWORD2 25 | config_set_string KEYWORD2 26 | config_set_int KEYWORD2 27 | config_set_ga KEYWORD2 28 | config_set_bool KEYWORD2 29 | feedback_register_int KEYWORD2 30 | feedback_register_float KEYWORD2 31 | feedback_register_bool KEYWORD2 32 | feedback_register_action KEYWORD2 33 | send_1bit KEYWORD2 34 | send_2bit KEYWORD2 35 | send_4bit KEYWORD2 36 | send_1byte_int KEYWORD2 37 | send_1byte_uint KEYWORD2 38 | send_2byte_int KEYWORD2 39 | send_2byte_uint KEYWORD2 40 | send_2byte_float KEYWORD2 41 | send_3byte_time KEYWORD2 42 | send_3byte_time KEYWORD2 43 | send_3byte_date KEYWORD2 44 | send_3byte_date KEYWORD2 45 | send_3byte_color KEYWORD2 46 | send_3byte_color KEYWORD2 47 | send_4byte_int KEYWORD2 48 | send_4byte_uint KEYWORD2 49 | send_4byte_float KEYWORD2 50 | write_1bit KEYWORD2 51 | write_2bit KEYWORD2 52 | write_4bit KEYWORD2 53 | write_1byte_int KEYWORD2 54 | write_1byte_uint KEYWORD2 55 | write_2byte_int KEYWORD2 56 | write_2byte_uint KEYWORD2 57 | write_2byte_float KEYWORD2 58 | write_3byte_time KEYWORD2 59 | write_3byte_time KEYWORD2 60 | write_3byte_date KEYWORD2 61 | write_3byte_date KEYWORD2 62 | write_3byte_color KEYWORD2 63 | write_3byte_color KEYWORD2 64 | write_4byte_int KEYWORD2 65 | write_4byte_uint KEYWORD2 66 | write_4byte_float KEYWORD2 67 | answer_1bit KEYWORD2 68 | answer_2bit KEYWORD2 69 | answer_4bit KEYWORD2 70 | answer_1byte_int KEYWORD2 71 | answer_1byte_uint KEYWORD2 72 | answer_2byte_int KEYWORD2 73 | answer_2byte_uint KEYWORD2 74 | answer_2byte_float KEYWORD2 75 | answer_3byte_time KEYWORD2 76 | answer_3byte_time KEYWORD2 77 | answer_3byte_date KEYWORD2 78 | answer_3byte_date KEYWORD2 79 | answer_3byte_color KEYWORD2 80 | answer_3byte_color KEYWORD2 81 | answer_4byte_int KEYWORD2 82 | answer_4byte_uint KEYWORD2 83 | answer_4byte_float KEYWORD2 84 | 85 | data_to_1byte_int KEYWORD2 86 | data_to_2byte_int KEYWORD2 87 | data_to_2byte_float KEYWORD2 88 | data_to_4byte_float KEYWORD2 89 | data_to_3byte_color KEYWORD2 90 | data_to_3byte_time KEYWORD2 91 | data_to_3byte_data KEYWORD2 92 | 93 | # constants 94 | knx LITERAL1 95 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP KNX IP Library 2 | version=0.4 3 | author=Nico Weichbrodt 4 | maintainer=Nico Weichbrodt 5 | sentence=ESP8266 library for KNX/IP communication. 6 | paragraph=Build your own IoT devices with KNX/IP connectivity! 7 | category=Communication 8 | url=https://github.com/envy/esp-knx-ip 9 | architectures=esp8266 10 | includes=esp-knx-ip.h --------------------------------------------------------------------------------