├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── esp32-xbee-board.step ├── main ├── CMakeLists.txt ├── Kconfig.projbuild ├── component.mk ├── config.c ├── core_dump.c ├── include │ ├── config.h │ ├── core_dump.h │ ├── interface │ │ ├── ntrip.h │ │ ├── socket_client.h │ │ └── socket_server.h │ ├── log.h │ ├── protocol │ │ └── nmea.h │ ├── retry.h │ ├── status_led.h │ ├── stream_stats.h │ ├── tasks.h │ ├── uart.h │ ├── util.h │ ├── web_server.h │ └── wifi.h ├── interface │ ├── ntrip2_client.c │ ├── ntrip2_server.c │ ├── ntrip_caster.c │ ├── ntrip_client.c │ ├── ntrip_server.c │ ├── ntrip_util.c │ ├── socket_client.c │ └── socket_server.c ├── log.c ├── main.c ├── protocol │ └── nmea.c ├── retry.c ├── status_led.c ├── stream_stats.c ├── uart.c ├── util.c ├── web_server.c └── wifi.c ├── partitions.csv ├── sdkconfig ├── wipe_config.bin └── www ├── bs-4.3.1.bundle.min.js ├── bs-4.3.1.min.css ├── favicon.ico ├── index.html ├── jquery-3.4.1.min.js └── log.html /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | cmake-build-debug 13 | cmake-build-debug-xtensa-esp32-elf 14 | 15 | build 16 | sdkconfig.old 17 | .idea/ 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/button"] 2 | path = components/button 3 | url = https://github.com/nebkat/esp32-button.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | # Include additional local components 6 | set(EXTRA_COMPONENT_DIRS "./components/button") 7 | 8 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 9 | project(esp32-xbee) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 XBee 2 | ESP32 XBee is the official firmware for the Ardusimple [WiFi NTRIP Master](https://www.ardusimple.com/product/wifi-ntrip-master/) ESP32 XBee device [[3D model]](https://github.com/nebkat/esp32-xbee/blob/master/esp32-xbee-board.step), made with [ESP-IDF](https://github.com/espressif/esp-idf). Its main function is to forward the UART of the ESP32 to a variety of protocols over WiFi. 3 | 4 | Although it is primarly intended for the WiFi NTRIP Master, this software can run on any ESP32 with minimal modifications to the GPIO configuration. 5 | 6 | ## Features 7 | - WiFi Station 8 | - WiFi Hotspot 9 | - Web Interface 10 | - UART configuration 11 | - NTRIP Client/Server/Caster 12 | - TCP/UDP Socket Server/Client 13 | 14 | ## Help 15 | If you've just received your ESP32 XBee, check out the [Getting Started](https://github.com/nebkat/esp32-xbee/wiki/Getting-Started) page. 16 | 17 | To install the latest firmware check out the [Firmware Update](https://github.com/nebkat/esp32-xbee/wiki/Firmware-Update) page. 18 | 19 | If you are experiencing problems with the firmware, please see the [Reporting Crashes](https://github.com/nebkat/esp32-xbee/wiki/Reporting-Crashes) page. 20 | 21 | ## Pinout 22 | [![Pinout diagram](https://i.imgur.com/mjqNXxb.png)](https://github.com/nebkat/esp32-xbee/wiki/Pinout) 23 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.c" 2 | "config.c" 3 | "core_dump.c" 4 | "log.c" 5 | "interface/ntrip_util.c" 6 | "retry.c" 7 | "status_led.c" 8 | "stream_stats.c" 9 | "uart.c" 10 | "util.c" 11 | "web_server.c" 12 | "wifi.c" 13 | "interface/ntrip_caster.c" 14 | "interface/ntrip_client.c" 15 | "interface/ntrip_server.c" 16 | "interface/socket_client.c" 17 | "interface/socket_server.c" 18 | "protocol/nmea.c" 19 | INCLUDE_DIRS "include") 20 | 21 | spiffs_create_partition_image(www ../www FLASH_IN_PROJECT) 22 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | # put here your custom config value -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /main/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "config.h" 28 | 29 | static const char *TAG = "CONFIG"; 30 | static const char *STORAGE = "config"; 31 | 32 | nvs_handle_t config_handle; 33 | 34 | const config_item_t CONFIG_ITEMS[] = { 35 | // Admin 36 | { 37 | .key = KEY_CONFIG_ADMIN_AUTH, 38 | .type = CONFIG_ITEM_TYPE_INT8, 39 | .def.int8 = 0 40 | }, 41 | { 42 | .key = KEY_CONFIG_ADMIN_USERNAME, 43 | .type = CONFIG_ITEM_TYPE_STRING, 44 | .def.str = "" 45 | }, { 46 | .key = KEY_CONFIG_ADMIN_PASSWORD, 47 | .type = CONFIG_ITEM_TYPE_STRING, 48 | .secret = true, 49 | .def.str = "" 50 | }, 51 | 52 | // Bluetooth 53 | { 54 | .key = KEY_CONFIG_BLUETOOTH_ACTIVE, 55 | .type = CONFIG_ITEM_TYPE_BOOL, 56 | .def.bool1 = false 57 | }, { 58 | .key = KEY_CONFIG_BLUETOOTH_DEVICE_NAME, 59 | .type = CONFIG_ITEM_TYPE_STRING, 60 | .def.str = "" 61 | }, { 62 | .key = KEY_CONFIG_BLUETOOTH_DEVICE_DISCOVERABLE, 63 | .type = CONFIG_ITEM_TYPE_BOOL, 64 | .def.bool1 = true 65 | }, { 66 | .key = KEY_CONFIG_BLUETOOTH_PIN_CODE, 67 | .type = CONFIG_ITEM_TYPE_UINT16, 68 | .secret = true, 69 | .def.uint16 = 1234 70 | }, 71 | 72 | // NTRIP 73 | { 74 | .key = KEY_CONFIG_NTRIP_SERVER_ACTIVE, 75 | .type = CONFIG_ITEM_TYPE_BOOL, 76 | .def.bool1 = false 77 | }, { 78 | .key = KEY_CONFIG_NTRIP_SERVER_COLOR, 79 | .type = CONFIG_ITEM_TYPE_COLOR, 80 | .def.color.rgba = 0x00000055u 81 | }, { 82 | .key = KEY_CONFIG_NTRIP_SERVER_HOST, 83 | .type = CONFIG_ITEM_TYPE_STRING, 84 | .def.str = "" 85 | }, { 86 | .key = KEY_CONFIG_NTRIP_SERVER_PORT, 87 | .type = CONFIG_ITEM_TYPE_UINT16, 88 | .def.uint16 = 2101 89 | }, { 90 | .key = KEY_CONFIG_NTRIP_SERVER_MOUNTPOINT, 91 | .type = CONFIG_ITEM_TYPE_STRING, 92 | .def.str = "" 93 | }, { 94 | .key = KEY_CONFIG_NTRIP_SERVER_USERNAME, 95 | .type = CONFIG_ITEM_TYPE_STRING, 96 | .def.str = "" 97 | }, { 98 | .key = KEY_CONFIG_NTRIP_SERVER_PASSWORD, 99 | .type = CONFIG_ITEM_TYPE_STRING, 100 | .secret = true, 101 | .def.str = "" 102 | }, 103 | 104 | { 105 | .key = KEY_CONFIG_NTRIP_CLIENT_ACTIVE, 106 | .type = CONFIG_ITEM_TYPE_BOOL, 107 | .def.bool1 = false 108 | }, { 109 | .key = KEY_CONFIG_NTRIP_CLIENT_COLOR, 110 | .type = CONFIG_ITEM_TYPE_COLOR, 111 | .def.color.rgba = 0x00000055u 112 | }, { 113 | .key = KEY_CONFIG_NTRIP_CLIENT_HOST, 114 | .type = CONFIG_ITEM_TYPE_STRING, 115 | .def.str = "" 116 | }, { 117 | .key = KEY_CONFIG_NTRIP_CLIENT_PORT, 118 | .type = CONFIG_ITEM_TYPE_UINT16, 119 | .def.uint16 = 2101 120 | }, { 121 | .key = KEY_CONFIG_NTRIP_CLIENT_MOUNTPOINT, 122 | .type = CONFIG_ITEM_TYPE_STRING, 123 | .def.str = "" 124 | }, { 125 | .key = KEY_CONFIG_NTRIP_CLIENT_USERNAME, 126 | .type = CONFIG_ITEM_TYPE_STRING, 127 | .def.str = "" 128 | }, { 129 | .key = KEY_CONFIG_NTRIP_CLIENT_PASSWORD, 130 | .type = CONFIG_ITEM_TYPE_STRING, 131 | .secret = true, 132 | .def.str = "" 133 | }, 134 | 135 | { 136 | .key = KEY_CONFIG_NTRIP_CASTER_ACTIVE, 137 | .type = CONFIG_ITEM_TYPE_BOOL, 138 | .def.bool1 = false 139 | }, { 140 | .key = KEY_CONFIG_NTRIP_CASTER_COLOR, 141 | .type = CONFIG_ITEM_TYPE_COLOR, 142 | .def.color.rgba = 0x00000055u 143 | }, { 144 | .key = KEY_CONFIG_NTRIP_CASTER_PORT, 145 | .type = CONFIG_ITEM_TYPE_UINT16, 146 | .def.uint16 = 2101 147 | }, { 148 | .key = KEY_CONFIG_NTRIP_CASTER_MOUNTPOINT, 149 | .type = CONFIG_ITEM_TYPE_STRING, 150 | .def.str = "" 151 | }, { 152 | .key = KEY_CONFIG_NTRIP_CASTER_USERNAME, 153 | .type = CONFIG_ITEM_TYPE_STRING, 154 | .def.str = "" 155 | }, { 156 | .key = KEY_CONFIG_NTRIP_CASTER_PASSWORD, 157 | .type = CONFIG_ITEM_TYPE_STRING, 158 | .secret = true, 159 | .def.str = "" 160 | }, 161 | 162 | // Socket 163 | { 164 | .key = KEY_CONFIG_SOCKET_SERVER_ACTIVE, 165 | .type = CONFIG_ITEM_TYPE_BOOL, 166 | .def.bool1 = false 167 | }, { 168 | .key = KEY_CONFIG_SOCKET_SERVER_COLOR, 169 | .type = CONFIG_ITEM_TYPE_COLOR, 170 | .def.color.rgba = 0x00000055u 171 | }, { 172 | .key = KEY_CONFIG_SOCKET_SERVER_TCP_PORT, 173 | .type = CONFIG_ITEM_TYPE_UINT16, 174 | .def.uint16 = 23 175 | }, { 176 | .key = KEY_CONFIG_SOCKET_SERVER_UDP_PORT, 177 | .type = CONFIG_ITEM_TYPE_UINT16, 178 | .def.uint16 = 23 179 | }, 180 | 181 | { 182 | .key = KEY_CONFIG_SOCKET_CLIENT_ACTIVE, 183 | .type = CONFIG_ITEM_TYPE_BOOL, 184 | .def.bool1 = false 185 | }, { 186 | .key = KEY_CONFIG_SOCKET_CLIENT_COLOR, 187 | .type = CONFIG_ITEM_TYPE_COLOR, 188 | .def.color.rgba = 0x00000055u 189 | }, { 190 | .key = KEY_CONFIG_SOCKET_CLIENT_HOST, 191 | .type = CONFIG_ITEM_TYPE_STRING, 192 | .def.str = "" 193 | }, { 194 | .key = KEY_CONFIG_SOCKET_CLIENT_PORT, 195 | .type = CONFIG_ITEM_TYPE_UINT16, 196 | .def.uint16 = 23 197 | }, { 198 | .key = KEY_CONFIG_SOCKET_CLIENT_TYPE_TCP_UDP, 199 | .type = CONFIG_ITEM_TYPE_BOOL, 200 | .def.bool1 = true 201 | }, { 202 | .key = KEY_CONFIG_SOCKET_CLIENT_CONNECT_MESSAGE, 203 | .type = CONFIG_ITEM_TYPE_STRING, 204 | .def.str = "\n" 205 | }, 206 | 207 | // UART 208 | { 209 | .key = KEY_CONFIG_UART_NUM, 210 | .type = CONFIG_ITEM_TYPE_UINT8, 211 | .def.uint8 = UART_NUM_0 212 | }, { 213 | .key = KEY_CONFIG_UART_TX_PIN, 214 | .type = CONFIG_ITEM_TYPE_UINT8, 215 | .def.uint8 = GPIO_NUM_1 216 | }, { 217 | .key = KEY_CONFIG_UART_RX_PIN, 218 | .type = CONFIG_ITEM_TYPE_UINT8, 219 | .def.uint8 = GPIO_NUM_3 220 | }, { 221 | .key = KEY_CONFIG_UART_RTS_PIN, 222 | .type = CONFIG_ITEM_TYPE_UINT8, 223 | .def.uint8 = GPIO_NUM_14 224 | }, { 225 | .key = KEY_CONFIG_UART_CTS_PIN, 226 | .type = CONFIG_ITEM_TYPE_UINT8, 227 | .def.uint8 = GPIO_NUM_33 228 | }, { 229 | .key = KEY_CONFIG_UART_BAUD_RATE, 230 | .type = CONFIG_ITEM_TYPE_UINT32, 231 | .def.uint32 = 115200 232 | }, { 233 | .key = KEY_CONFIG_UART_DATA_BITS, 234 | .type = CONFIG_ITEM_TYPE_INT8, 235 | .def.int8 = UART_DATA_8_BITS 236 | }, { 237 | .key = KEY_CONFIG_UART_STOP_BITS, 238 | .type = CONFIG_ITEM_TYPE_INT8, 239 | .def.int8 = UART_STOP_BITS_1 240 | }, { 241 | .key = KEY_CONFIG_UART_PARITY, 242 | .type = CONFIG_ITEM_TYPE_INT8, 243 | .def.int8 = UART_PARITY_DISABLE 244 | }, { 245 | .key = KEY_CONFIG_UART_FLOW_CTRL_RTS, 246 | .type = CONFIG_ITEM_TYPE_BOOL, 247 | .def.bool1 = false 248 | }, { 249 | .key = KEY_CONFIG_UART_FLOW_CTRL_CTS, 250 | .type = CONFIG_ITEM_TYPE_BOOL, 251 | .def.bool1 = false 252 | }, { 253 | .key = KEY_CONFIG_UART_LOG_FORWARD, 254 | .type = CONFIG_ITEM_TYPE_BOOL, 255 | .def.bool1 = false 256 | }, 257 | 258 | // WiFi 259 | { 260 | .key = KEY_CONFIG_WIFI_AP_ACTIVE, 261 | .type = CONFIG_ITEM_TYPE_BOOL, 262 | .def.bool1 = true 263 | }, { 264 | .key = KEY_CONFIG_WIFI_AP_COLOR, 265 | .type = CONFIG_ITEM_TYPE_COLOR, 266 | .def.color.rgba = 0x00000055u 267 | }, { 268 | .key = KEY_CONFIG_WIFI_AP_SSID, 269 | .type = CONFIG_ITEM_TYPE_STRING, 270 | .def.str = "" 271 | }, { 272 | .key = KEY_CONFIG_WIFI_AP_SSID_HIDDEN, 273 | .type = CONFIG_ITEM_TYPE_BOOL, 274 | .def.bool1 = false 275 | }, { 276 | .key = KEY_CONFIG_WIFI_AP_AUTH_MODE, 277 | .type = CONFIG_ITEM_TYPE_UINT8, 278 | .def.uint8 = WIFI_AUTH_OPEN 279 | }, { 280 | .key = KEY_CONFIG_WIFI_AP_PASSWORD, 281 | .type = CONFIG_ITEM_TYPE_STRING, 282 | .secret = true, 283 | .def.str = "" 284 | }, { 285 | .key = KEY_CONFIG_WIFI_AP_GATEWAY, 286 | .type = CONFIG_ITEM_TYPE_IP, 287 | .def.uint32 = esp_netif_htonl(esp_netif_ip4_makeu32(192, 168, 4, 1)) 288 | }, { 289 | .key = KEY_CONFIG_WIFI_AP_SUBNET, 290 | .type = CONFIG_ITEM_TYPE_UINT8, 291 | .def.uint8 = 24 292 | }, { 293 | .key = KEY_CONFIG_WIFI_STA_ACTIVE, 294 | .type = CONFIG_ITEM_TYPE_BOOL, 295 | .def.bool1 = false 296 | }, { 297 | .key = KEY_CONFIG_WIFI_STA_COLOR, 298 | .type = CONFIG_ITEM_TYPE_COLOR, 299 | .def.color.rgba = 0x0044ff55u 300 | }, { 301 | .key = KEY_CONFIG_WIFI_STA_SSID, 302 | .type = CONFIG_ITEM_TYPE_STRING, 303 | .def.str = "" 304 | }, { 305 | .key = KEY_CONFIG_WIFI_STA_PASSWORD, 306 | .type = CONFIG_ITEM_TYPE_STRING, 307 | .secret = true, 308 | .def.str = "" 309 | }, { 310 | .key = KEY_CONFIG_WIFI_STA_SCAN_MODE_ALL, 311 | .type = CONFIG_ITEM_TYPE_BOOL, 312 | .def.bool1 = false 313 | }, { 314 | .key = KEY_CONFIG_WIFI_STA_AP_FORWARD, 315 | .type = CONFIG_ITEM_TYPE_BOOL, 316 | .def.bool1 = false 317 | }, { 318 | .key = KEY_CONFIG_WIFI_STA_STATIC, 319 | .type = CONFIG_ITEM_TYPE_BOOL, 320 | .def.bool1 = false 321 | }, { 322 | .key = KEY_CONFIG_WIFI_STA_IP, 323 | .type = CONFIG_ITEM_TYPE_IP, 324 | .def.uint32 = esp_netif_htonl(esp_netif_ip4_makeu32(192, 168, 0, 100)) 325 | }, { 326 | .key = KEY_CONFIG_WIFI_STA_GATEWAY, 327 | .type = CONFIG_ITEM_TYPE_IP, 328 | .def.uint32 = esp_netif_htonl(esp_netif_ip4_makeu32(192, 168, 0, 1)) 329 | }, { 330 | .key = KEY_CONFIG_WIFI_STA_SUBNET, 331 | .type = CONFIG_ITEM_TYPE_UINT8, 332 | .def.uint8 = 24 333 | }, { 334 | .key = KEY_CONFIG_WIFI_STA_DNS_A, 335 | .type = CONFIG_ITEM_TYPE_IP, 336 | .def.uint32 = esp_netif_htonl(esp_netif_ip4_makeu32(1, 1, 1, 1)) 337 | }, { 338 | .key = KEY_CONFIG_WIFI_STA_DNS_B, 339 | .type = CONFIG_ITEM_TYPE_IP, 340 | .def.uint32 = esp_netif_htonl(esp_netif_ip4_makeu32(1, 0, 0, 1)) 341 | } 342 | }; 343 | 344 | const config_item_t *config_items_get(int *count) { 345 | *count = sizeof(CONFIG_ITEMS) / sizeof(config_item_t); 346 | return &CONFIG_ITEMS[0]; 347 | } 348 | 349 | esp_err_t config_set(const config_item_t *item, void *value) { 350 | switch (item->type) { 351 | case CONFIG_ITEM_TYPE_BOOL: 352 | return config_set_bool1(item->key, *((bool *) value)); 353 | case CONFIG_ITEM_TYPE_INT8: 354 | return config_set_i8(item->key, *((int8_t *)value)); 355 | case CONFIG_ITEM_TYPE_INT16: 356 | return config_set_i16(item->key, *((int16_t *)value)); 357 | case CONFIG_ITEM_TYPE_INT32: 358 | return config_set_i32(item->key, *((int32_t *)value)); 359 | case CONFIG_ITEM_TYPE_INT64: 360 | return config_set_i64(item->key, *((int64_t *)value)); 361 | case CONFIG_ITEM_TYPE_UINT8: 362 | return config_set_u8(item->key, *((uint8_t *)value)); 363 | case CONFIG_ITEM_TYPE_UINT16: 364 | return config_set_u16(item->key, *((uint16_t *)value)); 365 | case CONFIG_ITEM_TYPE_UINT32: 366 | return config_set_u32(item->key, *((uint32_t *)value)); 367 | case CONFIG_ITEM_TYPE_UINT64: 368 | return config_set_u64(item->key, *((uint64_t *)value)); 369 | case CONFIG_ITEM_TYPE_STRING: 370 | return config_set_str(item->key, (char *) value); 371 | default: 372 | return ESP_ERR_INVALID_ARG; 373 | } 374 | } 375 | 376 | esp_err_t config_set_i8(const char *key, int8_t value) { 377 | return nvs_set_i8(config_handle, key, value); 378 | } 379 | 380 | esp_err_t config_set_i16(const char *key, int16_t value) { 381 | return nvs_set_i16(config_handle, key, value); 382 | } 383 | 384 | esp_err_t config_set_i32(const char *key, int32_t value) { 385 | return nvs_set_i32(config_handle, key, value); 386 | } 387 | 388 | esp_err_t config_set_i64(const char *key, int64_t value) { 389 | return nvs_set_i64(config_handle, key, value); 390 | } 391 | 392 | esp_err_t config_set_u8(const char *key, uint8_t value) { 393 | return nvs_set_u8(config_handle, key, value); 394 | } 395 | 396 | esp_err_t config_set_u16(const char *key, uint16_t value) { 397 | return nvs_set_u16(config_handle, key, value); 398 | } 399 | 400 | esp_err_t config_set_u32(const char *key, uint32_t value) { 401 | return nvs_set_u32(config_handle, key, value); 402 | } 403 | 404 | esp_err_t config_set_u64(const char *key, uint64_t value) { 405 | return nvs_set_u64(config_handle, key, value); 406 | } 407 | 408 | esp_err_t config_set_color(const char *key, config_color_t value) { 409 | return nvs_set_u32(config_handle, key, value.rgba); 410 | } 411 | 412 | esp_err_t config_set_bool1(const char *key, bool value) { 413 | return nvs_set_i8(config_handle, key, value); 414 | } 415 | 416 | esp_err_t config_set_str(const char *key, char *value) { 417 | return nvs_set_str(config_handle, key, value); 418 | } 419 | 420 | esp_err_t config_set_blob(const char *key, char *value, size_t length) { 421 | return nvs_set_blob(config_handle, key, value, length); 422 | } 423 | 424 | esp_err_t config_init() { 425 | esp_err_t err = nvs_flash_init(); 426 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 427 | // NVS partition was truncated and needs to be erased 428 | // Retry nvs_flash_init 429 | ESP_ERROR_CHECK(nvs_flash_erase()); 430 | err = nvs_flash_init(); 431 | } 432 | ESP_ERROR_CHECK(err); 433 | 434 | ESP_LOGD(TAG, "Opening Non-Volatile Storage (NVS) handle '%s'... ", STORAGE); 435 | return nvs_open(STORAGE, NVS_READWRITE, &config_handle); 436 | } 437 | 438 | esp_err_t config_reset() { 439 | uart_nmea("$PESP,CFG,RESET"); 440 | 441 | return nvs_erase_all(config_handle); 442 | } 443 | 444 | int8_t config_get_i8(const config_item_t *item) { 445 | int8_t value = item->def.int8; 446 | nvs_get_i8(config_handle, item->key, &value); 447 | return value; 448 | } 449 | 450 | int16_t config_get_i16(const config_item_t *item) { 451 | int16_t value = item->def.int16; 452 | nvs_get_i16(config_handle, item->key, &value); 453 | return value; 454 | } 455 | 456 | int32_t config_get_i32(const config_item_t *item) { 457 | int32_t value = item->def.int32; 458 | nvs_get_i32(config_handle, item->key, &value); 459 | return value; 460 | } 461 | 462 | int64_t config_get_i64(const config_item_t *item) { 463 | int64_t value = item->def.int64; 464 | nvs_get_i64(config_handle, item->key, &value); 465 | return value; 466 | } 467 | 468 | uint8_t config_get_u8(const config_item_t *item) { 469 | uint8_t value = item->def.uint8; 470 | nvs_get_u8(config_handle, item->key, &value); 471 | return value; 472 | } 473 | 474 | uint16_t config_get_u16(const config_item_t *item) { 475 | uint16_t value = item->def.uint16; 476 | nvs_get_u16(config_handle, item->key, &value); 477 | return value; 478 | } 479 | 480 | uint32_t config_get_u32(const config_item_t *item) { 481 | uint32_t value = item->def.uint32; 482 | nvs_get_u32(config_handle, item->key, &value); 483 | return value; 484 | } 485 | 486 | uint64_t config_get_u64(const config_item_t *item) { 487 | uint64_t value = item->def.uint64; 488 | nvs_get_u64(config_handle, item->key, &value); 489 | return value; 490 | } 491 | 492 | config_color_t config_get_color(const config_item_t *item) { 493 | config_color_t value = item->def.color; 494 | nvs_get_u32(config_handle, item->key, &value.rgba); 495 | return value; 496 | } 497 | 498 | bool config_get_bool1(const config_item_t *item) { 499 | int8_t value = item->def.bool1; 500 | nvs_get_i8(config_handle, item->key, &value); 501 | return value > 0; 502 | } 503 | 504 | const config_item_t * config_get_item(const char *key) { 505 | for (unsigned int i = 0; i < sizeof(CONFIG_ITEMS) / sizeof(config_item_t); i++) { 506 | const config_item_t *item = &CONFIG_ITEMS[i]; 507 | if (strcmp(item->key, key) == 0) { 508 | return item; 509 | } 510 | } 511 | 512 | // Fatal error 513 | ESP_ERROR_CHECK(ESP_FAIL); 514 | 515 | return NULL; 516 | } 517 | 518 | esp_err_t config_get_primitive(const config_item_t *item, void *out_value) { 519 | esp_err_t ret; 520 | switch (item->type) { 521 | case CONFIG_ITEM_TYPE_BOOL: 522 | *((bool *) out_value) = item->def.bool1; 523 | ret = nvs_get_i8(config_handle, item->key, out_value); 524 | break; 525 | case CONFIG_ITEM_TYPE_INT8: 526 | *((int8_t *) out_value) = item->def.int8; 527 | ret = nvs_get_i8(config_handle, item->key, out_value); 528 | break; 529 | case CONFIG_ITEM_TYPE_INT16: 530 | *((int16_t *) out_value) = item->def.int16; 531 | ret = nvs_get_i16(config_handle, item->key, out_value); 532 | break; 533 | case CONFIG_ITEM_TYPE_INT32: 534 | *((int32_t *) out_value) = item->def.int32; 535 | ret = nvs_get_i32(config_handle, item->key, out_value); 536 | break; 537 | case CONFIG_ITEM_TYPE_INT64: 538 | *((int64_t *) out_value) = item->def.int64; 539 | ret = nvs_get_i64(config_handle, item->key, out_value); 540 | break; 541 | case CONFIG_ITEM_TYPE_UINT8: 542 | *((uint8_t *) out_value) = item->def.uint8; 543 | ret = nvs_get_u8(config_handle, item->key, out_value); 544 | break; 545 | case CONFIG_ITEM_TYPE_UINT16: 546 | *((uint16_t *) out_value) = item->def.uint16; 547 | ret = nvs_get_u16(config_handle, item->key, out_value); 548 | break; 549 | case CONFIG_ITEM_TYPE_UINT32: 550 | case CONFIG_ITEM_TYPE_IP: 551 | *((uint32_t *) out_value) = item->def.uint32; 552 | ret = nvs_get_u32(config_handle, item->key, out_value); 553 | break; 554 | case CONFIG_ITEM_TYPE_UINT64: 555 | *((uint64_t *) out_value) = item->def.uint64; 556 | ret = nvs_get_u64(config_handle, item->key, out_value); 557 | break; 558 | case CONFIG_ITEM_TYPE_COLOR: 559 | *((config_color_t *) out_value) = item->def.color; 560 | ret = nvs_get_u32(config_handle, item->key, out_value); 561 | break; 562 | default: 563 | return ESP_ERR_INVALID_ARG; 564 | } 565 | 566 | return (ret == ESP_OK || ret == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : ret; 567 | } 568 | 569 | esp_err_t config_get_str_blob_alloc(const config_item_t *item, void **out_value) { 570 | size_t length; 571 | esp_err_t ret = config_get_str_blob(item, NULL, &length); 572 | if (ret != ESP_OK) return ret; 573 | *out_value = malloc(length); 574 | return config_get_str_blob(item, *out_value, &length); 575 | } 576 | 577 | esp_err_t config_get_str_blob(const config_item_t *item, void *out_value, size_t *length) { 578 | esp_err_t ret; 579 | 580 | switch (item->type) { 581 | case CONFIG_ITEM_TYPE_STRING: 582 | ret = nvs_get_str(config_handle, item->key, out_value, length); 583 | if (ret == ESP_ERR_NVS_NOT_FOUND) { 584 | if (length != NULL) *length = strlen(item->def.str) + 1; 585 | if (out_value != NULL) strcpy(out_value, item->def.str); 586 | } 587 | break; 588 | case CONFIG_ITEM_TYPE_BLOB: 589 | ret = nvs_get_blob(config_handle, item->key, out_value, length); 590 | if (ret == ESP_ERR_NVS_NOT_FOUND) { 591 | if (length != NULL) *length = item->def.blob.length; 592 | if (out_value != NULL) memcpy(out_value, item->def.blob.data, item->def.blob.length); 593 | } 594 | break; 595 | default: 596 | return ESP_ERR_INVALID_ARG; 597 | } 598 | 599 | return (ret == ESP_OK || ret == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : ret; 600 | } 601 | 602 | esp_err_t config_commit() { 603 | uart_nmea("$PESP,CFG,UPDATED"); 604 | 605 | return nvs_commit(config_handle); 606 | } 607 | 608 | static void config_restart_task() { 609 | vTaskDelay(pdMS_TO_TICKS(1000)); 610 | esp_restart(); 611 | } 612 | 613 | void config_restart() { 614 | uart_nmea("$PESP,CFG,RESTARTING"); 615 | 616 | xTaskCreate(config_restart_task, "config_restart_task", 4096, NULL, TASK_PRIORITY_MAX, NULL); 617 | } 618 | -------------------------------------------------------------------------------- /main/core_dump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include "core_dump.h" 22 | 23 | static const esp_partition_t *core_dump_partition; 24 | static size_t core_dump_size = 0; 25 | 26 | void core_dump_check() { 27 | size_t core_dump_addr = 0; 28 | if (esp_core_dump_image_get(&core_dump_addr, &core_dump_size) != ESP_OK || core_dump_size == 0) { 29 | return; 30 | } 31 | 32 | core_dump_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, 33 | ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL); 34 | } 35 | 36 | size_t core_dump_available() { 37 | return core_dump_size; 38 | } 39 | 40 | esp_err_t core_dump_read(size_t offset, void *buffer, size_t len) { 41 | return esp_partition_read(core_dump_partition, offset, buffer, len); 42 | } -------------------------------------------------------------------------------- /main/include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_CONFIG_H 2 | #define ESP32_XBEE_CONFIG_H 3 | 4 | #include 5 | 6 | typedef enum { 7 | CONFIG_ITEM_TYPE_BOOL = 0, 8 | CONFIG_ITEM_TYPE_INT8, 9 | CONFIG_ITEM_TYPE_INT16, 10 | CONFIG_ITEM_TYPE_INT32, 11 | CONFIG_ITEM_TYPE_INT64, 12 | CONFIG_ITEM_TYPE_UINT8, 13 | CONFIG_ITEM_TYPE_UINT16, 14 | CONFIG_ITEM_TYPE_UINT32, 15 | CONFIG_ITEM_TYPE_UINT64, 16 | CONFIG_ITEM_TYPE_STRING, 17 | CONFIG_ITEM_TYPE_BLOB, 18 | CONFIG_ITEM_TYPE_COLOR, 19 | CONFIG_ITEM_TYPE_IP, 20 | CONFIG_ITEM_TYPE_MAX 21 | } config_item_type_t; 22 | 23 | typedef union { 24 | struct values { 25 | uint8_t alpha; 26 | uint8_t blue; 27 | uint8_t green; 28 | uint8_t red; 29 | } values; 30 | uint32_t rgba; 31 | } config_color_t; 32 | 33 | typedef union { 34 | bool bool1; 35 | int8_t int8; 36 | int16_t int16; 37 | int32_t int32; 38 | int64_t int64; 39 | uint8_t uint8; 40 | uint16_t uint16; 41 | uint32_t uint32; 42 | uint64_t uint64; 43 | config_color_t color; 44 | char *str; 45 | struct blob { 46 | uint8_t *data; 47 | size_t length; 48 | } blob; 49 | 50 | } config_item_value_t; 51 | 52 | typedef struct config_item { 53 | char *key; 54 | config_item_type_t type; 55 | bool secret; 56 | config_item_value_t def; 57 | } config_item_t; 58 | 59 | #define CONFIG_VALUE_UNCHANGED "\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a" 60 | 61 | // Admin 62 | #define KEY_CONFIG_ADMIN_AUTH "adm_auth" 63 | #define KEY_CONFIG_ADMIN_USERNAME "adm_user" 64 | #define KEY_CONFIG_ADMIN_PASSWORD "adm_pass" 65 | 66 | // Bluetooth 67 | #define KEY_CONFIG_BLUETOOTH_ACTIVE "bt_active" 68 | #define KEY_CONFIG_BLUETOOTH_DEVICE_NAME "bt_dev_name" 69 | #define KEY_CONFIG_BLUETOOTH_DEVICE_DISCOVERABLE "bt_dev_vis" 70 | #define KEY_CONFIG_BLUETOOTH_PIN_CODE "bt_pin_code" 71 | 72 | // NTRIP 73 | #define KEY_CONFIG_NTRIP_SERVER_ACTIVE "ntr_srv_active" 74 | #define KEY_CONFIG_NTRIP_SERVER_COLOR "ntr_srv_color" 75 | #define KEY_CONFIG_NTRIP_SERVER_HOST "ntr_srv_host" 76 | #define KEY_CONFIG_NTRIP_SERVER_PORT "ntr_srv_port" 77 | #define KEY_CONFIG_NTRIP_SERVER_MOUNTPOINT "ntr_srv_mp" 78 | #define KEY_CONFIG_NTRIP_SERVER_USERNAME "ntr_srv_user" 79 | #define KEY_CONFIG_NTRIP_SERVER_PASSWORD "ntr_srv_pass" 80 | 81 | #define KEY_CONFIG_NTRIP_CLIENT_ACTIVE "ntr_cli_active" 82 | #define KEY_CONFIG_NTRIP_CLIENT_COLOR "ntr_cli_color" 83 | #define KEY_CONFIG_NTRIP_CLIENT_HOST "ntr_cli_host" 84 | #define KEY_CONFIG_NTRIP_CLIENT_PORT "ntr_cli_port" 85 | #define KEY_CONFIG_NTRIP_CLIENT_MOUNTPOINT "ntr_cli_mp" 86 | #define KEY_CONFIG_NTRIP_CLIENT_USERNAME "ntr_cli_user" 87 | #define KEY_CONFIG_NTRIP_CLIENT_PASSWORD "ntr_cli_pass" 88 | 89 | #define KEY_CONFIG_NTRIP_CASTER_ACTIVE "ntr_cst_active" 90 | #define KEY_CONFIG_NTRIP_CASTER_COLOR "ntr_cst_color" 91 | #define KEY_CONFIG_NTRIP_CASTER_PORT "ntr_cst_port" 92 | #define KEY_CONFIG_NTRIP_CASTER_MOUNTPOINT "ntr_cst_mp" 93 | #define KEY_CONFIG_NTRIP_CASTER_USERNAME "ntr_cst_user" 94 | #define KEY_CONFIG_NTRIP_CASTER_PASSWORD "ntr_cst_pass" 95 | 96 | // Socket 97 | #define KEY_CONFIG_SOCKET_SERVER_ACTIVE "sck_srv_active" 98 | #define KEY_CONFIG_SOCKET_SERVER_COLOR "sck_srv_color" 99 | #define KEY_CONFIG_SOCKET_SERVER_TCP_PORT "sck_srv_t_port" 100 | #define KEY_CONFIG_SOCKET_SERVER_UDP_PORT "sck_srv_u_port" 101 | 102 | #define KEY_CONFIG_SOCKET_CLIENT_ACTIVE "sck_cli_active" 103 | #define KEY_CONFIG_SOCKET_CLIENT_COLOR "sck_cli_color" 104 | #define KEY_CONFIG_SOCKET_CLIENT_HOST "sck_cli_host" 105 | #define KEY_CONFIG_SOCKET_CLIENT_PORT "sck_cli_port" 106 | #define KEY_CONFIG_SOCKET_CLIENT_TYPE_TCP_UDP "sck_cli_type" 107 | #define KEY_CONFIG_SOCKET_CLIENT_CONNECT_MESSAGE "sck_cli_msg" 108 | 109 | // UART 110 | #define KEY_CONFIG_UART_NUM "uart_num" 111 | #define KEY_CONFIG_UART_TX_PIN "uart_tx_pin" 112 | #define KEY_CONFIG_UART_RX_PIN "uart_rx_pin" 113 | #define KEY_CONFIG_UART_RTS_PIN "uart_rts_pin" 114 | #define KEY_CONFIG_UART_CTS_PIN "uart_cts_pin" 115 | #define KEY_CONFIG_UART_BAUD_RATE "uart_baud_rate" 116 | #define KEY_CONFIG_UART_DATA_BITS "uart_data_bits" 117 | #define KEY_CONFIG_UART_STOP_BITS "uart_stop_bits" 118 | #define KEY_CONFIG_UART_PARITY "uart_parity" 119 | #define KEY_CONFIG_UART_FLOW_CTRL_RTS "uart_fc_rts" 120 | #define KEY_CONFIG_UART_FLOW_CTRL_CTS "uart_fc_cts" 121 | #define KEY_CONFIG_UART_LOG_FORWARD "uart_log_fwd" 122 | 123 | // WiFi 124 | #define KEY_CONFIG_WIFI_AP_ACTIVE "w_ap_active" 125 | #define KEY_CONFIG_WIFI_AP_COLOR "w_ap_color" 126 | #define KEY_CONFIG_WIFI_AP_SSID "w_ap_ssid" 127 | #define KEY_CONFIG_WIFI_AP_SSID_HIDDEN "w_ap_ssid_hid" 128 | #define KEY_CONFIG_WIFI_AP_AUTH_MODE "w_ap_auth_mode" 129 | #define KEY_CONFIG_WIFI_AP_PASSWORD "w_ap_pass" 130 | #define KEY_CONFIG_WIFI_AP_GATEWAY "w_ap_gw" 131 | #define KEY_CONFIG_WIFI_AP_SUBNET "w_ap_subnet" 132 | 133 | #define KEY_CONFIG_WIFI_STA_ACTIVE "w_sta_active" 134 | #define KEY_CONFIG_WIFI_STA_COLOR "w_sta_color" 135 | #define KEY_CONFIG_WIFI_STA_SSID "w_sta_ssid" 136 | #define KEY_CONFIG_WIFI_STA_PASSWORD "w_sta_pass" 137 | #define KEY_CONFIG_WIFI_STA_SCAN_MODE_ALL "w_sta_scan_mode" 138 | #define KEY_CONFIG_WIFI_STA_AP_FORWARD "w_sta_ap_fwd" 139 | #define KEY_CONFIG_WIFI_STA_STATIC "w_sta_static" 140 | #define KEY_CONFIG_WIFI_STA_IP "w_sta_ip" 141 | #define KEY_CONFIG_WIFI_STA_GATEWAY "w_sta_gw" 142 | #define KEY_CONFIG_WIFI_STA_SUBNET "w_sta_subnet" 143 | #define KEY_CONFIG_WIFI_STA_DNS_A "w_sta_dns_a" 144 | #define KEY_CONFIG_WIFI_STA_DNS_B "w_sta_dns_b" 145 | 146 | esp_err_t config_init(); 147 | esp_err_t config_reset(); 148 | 149 | const config_item_t *config_items_get(int *count); 150 | const config_item_t * config_get_item(const char *key); 151 | 152 | #define CONF_ITEM( key ) config_get_item(key) 153 | 154 | bool config_get_bool1(const config_item_t *item); 155 | int8_t config_get_i8(const config_item_t *item); 156 | int16_t config_get_i16(const config_item_t *item); 157 | int32_t config_get_i32(const config_item_t *item); 158 | int64_t config_get_i64(const config_item_t *item); 159 | uint8_t config_get_u8(const config_item_t *item); 160 | uint16_t config_get_u16(const config_item_t *item); 161 | uint32_t config_get_u32(const config_item_t *item); 162 | uint64_t config_get_u64(const config_item_t *item); 163 | config_color_t config_get_color(const config_item_t *item); 164 | 165 | esp_err_t config_set(const config_item_t *item, void *value); 166 | esp_err_t config_set_bool1(const char *key, bool value); 167 | esp_err_t config_set_i8(const char *key, int8_t value); 168 | esp_err_t config_set_i16(const char *key, int16_t value); 169 | esp_err_t config_set_i32(const char *key, int32_t value); 170 | esp_err_t config_set_i64(const char *key, int64_t value); 171 | esp_err_t config_set_u8(const char *key, uint8_t value); 172 | esp_err_t config_set_u16(const char *key, uint16_t value); 173 | esp_err_t config_set_u32(const char *key, uint32_t value); 174 | esp_err_t config_set_u64(const char *key, uint64_t value); 175 | esp_err_t config_set_color(const char *key, config_color_t value); 176 | esp_err_t config_set_str(const char *key, char *value); 177 | esp_err_t config_set_blob(const char *key, char *value, size_t length); 178 | 179 | esp_err_t config_get_str_blob_alloc(const config_item_t *item, void **out_value); 180 | esp_err_t config_get_str_blob(const config_item_t *item, void *out_value, size_t *length); 181 | esp_err_t config_get_primitive(const config_item_t *item, void *out_value); 182 | 183 | esp_err_t config_commit(); 184 | void config_restart(); 185 | 186 | #endif //ESP32_XBEE_CONFIG_H 187 | -------------------------------------------------------------------------------- /main/include/core_dump.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef ESP32_XBEE_CORE_DUMP_H 19 | #define ESP32_XBEE_CORE_DUMP_H 20 | 21 | void core_dump_check(); 22 | size_t core_dump_available(); 23 | esp_err_t core_dump_read(size_t offset, void *buffer, size_t len); 24 | 25 | #endif //ESP32_XBEE_CORE_DUMP_H 26 | -------------------------------------------------------------------------------- /main/include/interface/ntrip.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_NTRIP_H 2 | #define ESP32_XBEE_NTRIP_H 3 | 4 | #define NTRIP_GENERIC_NAME "ESP32-XBee" 5 | #define NTRIP_CLIENT_NAME NTRIP_GENERIC_NAME "_Client" 6 | #define NTRIP_SERVER_NAME NTRIP_GENERIC_NAME "_Server" 7 | #define NTRIP_CASTER_NAME NTRIP_GENERIC_NAME "_Caster" 8 | 9 | #define NTRIP_PORT_DEFAULT 2101 10 | #define NTRIP_MOUNTPOINT_DEFAULT "DEFAULT" 11 | #define NTRIP_KEEP_ALIVE_THRESHOLD 10000 12 | 13 | #define NEWLINE "\r\n" 14 | #define NEWLINE_LENGTH 2 15 | 16 | void ntrip_server_init(); 17 | void ntrip_client_init(); 18 | void ntrip_caster_init(); 19 | 20 | bool ntrip_response_ok(void *response); 21 | bool ntrip_response_sourcetable_ok(void *response); 22 | 23 | #endif //ESP32_XBEE_NTRIP_H 24 | -------------------------------------------------------------------------------- /main/include/interface/socket_client.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_SOCKET_CLIENT_H 2 | #define ESP32_XBEE_SOCKET_CLIENT_H 3 | 4 | #include 5 | #include 6 | 7 | void socket_client_init(); 8 | 9 | #endif //ESP32_XBEE_SOCKET_CLIENT_H 10 | -------------------------------------------------------------------------------- /main/include/interface/socket_server.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_SOCKET_SERVER_H 2 | #define ESP32_XBEE_SOCKET_SERVER_H 3 | 4 | #include 5 | 6 | void socket_server_init(); 7 | 8 | #endif //ESP32_XBEE_SOCKET_SERVER_H 9 | -------------------------------------------------------------------------------- /main/include/log.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_LOG_H 2 | #define ESP32_XBEE_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | esp_err_t log_init(); 9 | int log_vprintf(const char * format, va_list arg); 10 | void *log_receive(size_t *length, TickType_t ticksToWait); 11 | void log_return(void *item); 12 | 13 | #endif //ESP32_XBEE_LOG_H 14 | -------------------------------------------------------------------------------- /main/include/protocol/nmea.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_NMEA_H 2 | #define ESP32_XBEE_NMEA_H 3 | 4 | int nmea_asprintf(char **strp, const char *fmt, ...); 5 | int nmea_vasprintf(char **strp, const char *fmt, va_list args); 6 | 7 | #endif //ESP32_XBEE_NMEA_H 8 | -------------------------------------------------------------------------------- /main/include/retry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef ESP32_XBEE_RETRY_H 19 | #define ESP32_XBEE_RETRY_H 20 | 21 | #include 22 | 23 | typedef struct retry_delay *retry_delay_handle_t; 24 | 25 | retry_delay_handle_t retry_init(bool first_instant, uint8_t short_count, int short_delay, int max_delay); 26 | int retry_delay(retry_delay_handle_t handle); 27 | void retry_reset(retry_delay_handle_t handle); 28 | 29 | #endif //ESP32_XBEE_RETRY_H 30 | -------------------------------------------------------------------------------- /main/include/status_led.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_STATUS_LED_H 2 | #define ESP32_XBEE_STATUS_LED_H 3 | 4 | #include 5 | 6 | typedef enum { 7 | STATUS_LED_STATIC = 0, 8 | STATUS_LED_FADE, 9 | STATUS_LED_BLINK 10 | } status_led_flashing_mode_t; 11 | 12 | typedef struct status_led_color_t *status_led_handle_t; 13 | struct status_led_color_t { 14 | uint8_t red; 15 | uint8_t green; 16 | uint8_t blue; 17 | 18 | status_led_flashing_mode_t flashing_mode; 19 | uint32_t interval; 20 | uint32_t duration; 21 | uint8_t expire; 22 | 23 | bool remove; 24 | bool active; 25 | 26 | SLIST_ENTRY(status_led_color_t) next; 27 | }; 28 | 29 | status_led_handle_t status_led_add(uint32_t rgba, status_led_flashing_mode_t flashing_mode, uint32_t interval, uint32_t duration, uint8_t expire); 30 | void status_led_remove(status_led_handle_t color); 31 | void status_led_init(); 32 | 33 | void rssi_led_set(uint8_t value); 34 | void rssi_led_fade(uint8_t value, int max_fade_time_ms); 35 | void assoc_led_set(uint8_t value); 36 | void assoc_led_fade(uint8_t value, int max_fade_time_ms); 37 | void sleep_led_set(uint8_t value); 38 | void sleep_led_fade(uint8_t value, int max_fade_time_ms); 39 | 40 | #endif //ESP32_XBEE_STATUS_LED_H 41 | -------------------------------------------------------------------------------- /main/include/stream_stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2020 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef ESP32_XBEE_STREAM_STATS_H 19 | #define ESP32_XBEE_STREAM_STATS_H 20 | 21 | #include 22 | 23 | typedef struct stream_stats_values { 24 | const char *name; 25 | 26 | uint32_t total_in; 27 | uint32_t total_out; 28 | 29 | uint32_t rate_in; 30 | uint32_t rate_out; 31 | } stream_stats_values_t; 32 | 33 | typedef struct stream_stats *stream_stats_handle_t; 34 | 35 | void stream_stats_init(); 36 | stream_stats_handle_t stream_stats_new(const char *name); 37 | 38 | void stream_stats_increment(stream_stats_handle_t stats, uint32_t in, uint32_t out); 39 | void stream_stats_values(stream_stats_handle_t stats, stream_stats_values_t *values); 40 | 41 | stream_stats_handle_t stream_stats_first(); 42 | stream_stats_handle_t stream_stats_next(stream_stats_handle_t stats); 43 | 44 | #endif //ESP32_XBEE_STREAM_STATS_H 45 | -------------------------------------------------------------------------------- /main/include/tasks.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_TASKS_H 2 | #define ESP32_XBEE_TASKS_H 3 | 4 | #define TASK_PRIORITY_STATUS_LED 0 5 | #define TASK_PRIORITY_RESET_BUTTON 0 6 | #define TASK_PRIORITY_WIFI_STATUS 0 7 | #define TASK_PRIORITY_STATS 0 8 | #define TASK_PRIORITY_INTERFACE 5 9 | #define TASK_PRIORITY_UART 10 10 | #define TASK_PRIORITY_MAX 100 11 | 12 | #endif //ESP32_XBEE_TASKS_H 13 | -------------------------------------------------------------------------------- /main/include/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_UART_H 2 | #define ESP32_XBEE_UART_H 3 | 4 | #include 5 | 6 | ESP_EVENT_DECLARE_BASE(UART_EVENT_READ); 7 | ESP_EVENT_DECLARE_BASE(UART_EVENT_WRITE); 8 | 9 | #define UART_BUFFER_SIZE 4096 10 | 11 | void uart_init(); 12 | 13 | void uart_inject(void *data, size_t len); 14 | int uart_log(char *buffer, size_t len); 15 | int uart_nmea(const char *fmt, ...); 16 | int uart_write(char *buffer, size_t len); 17 | 18 | void uart_register_read_handler(esp_event_handler_t event_handler); 19 | void uart_register_write_handler(esp_event_handler_t event_handler); 20 | 21 | #endif //ESP32_XBEE_UART_H 22 | -------------------------------------------------------------------------------- /main/include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_UTIL_H 2 | #define ESP32_XBEE_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define PRINT_LINE printf("%s:%d %s\n", __FILE__, __LINE__, __func__) 10 | #define UART_PRINT_LINE uart_nmea("$PESP,DBG,%s,%d,%s", __FILE__, __LINE__, __func__) 11 | 12 | #define ERROR_ACTION(TAG, condition, action, format, ... ) if ((condition)) { \ 13 | ESP_LOGE(TAG, "%s:%d (%s): " format, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \ 14 | action; \ 15 | } 16 | 17 | #define SOCKTYPE_NAME(socktype) (socktype == SOCK_STREAM ? "TCP" : (socktype == SOCK_DGRAM ? "UDP" : (socktype == SOCK_RAW ? "RAW" : "???"))) 18 | 19 | #define CONNECT_SOCKET_ERROR_OPTS -3 20 | #define CONNECT_SOCKET_ERROR_RESOLVE -2 21 | #define CONNECT_SOCKET_ERROR_CONNECT -1 22 | 23 | void destroy_socket(int *socket); 24 | char *sockaddrtostr(struct sockaddr *a); 25 | 26 | char *extract_http_header(const char *buffer, const char *key); 27 | 28 | int connect_socket(char *host, int port, int socktype); 29 | char *http_auth_basic_header(const char *username, const char *password); 30 | 31 | #endif //ESP32_XBEE_UTIL_H 32 | -------------------------------------------------------------------------------- /main/include/web_server.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_WEB_SERVER_H 2 | #define ESP32_XBEE_WEB_SERVER_H 3 | 4 | void web_server_init(); 5 | 6 | #endif //ESP32_XBEE_WEB_SERVER_H 7 | -------------------------------------------------------------------------------- /main/include/wifi.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_XBEE_WIFI_H 2 | #define ESP32_XBEE_WIFI_H 3 | 4 | #include 5 | 6 | typedef struct wifi_ap_status { 7 | bool active; 8 | 9 | char ssid[33]; 10 | wifi_auth_mode_t authmode; 11 | uint8_t devices; 12 | 13 | esp_ip4_addr_t ip4_addr; 14 | esp_ip6_addr_t ip6_addr; 15 | } wifi_ap_status_t; 16 | 17 | typedef struct wifi_sta_status { 18 | bool active; 19 | bool connected; 20 | 21 | char ssid[33]; 22 | wifi_auth_mode_t authmode; 23 | int8_t rssi; 24 | 25 | esp_ip4_addr_t ip4_addr; 26 | esp_ip6_addr_t ip6_addr; 27 | } wifi_sta_status_t; 28 | 29 | void net_init(); 30 | void wifi_init(); 31 | 32 | wifi_ap_record_t * wifi_scan(uint16_t *number); 33 | 34 | wifi_sta_list_t *wifi_ap_sta_list(); 35 | 36 | void wifi_ap_status(wifi_ap_status_t *status); 37 | void wifi_sta_status(wifi_sta_status_t *status); 38 | 39 | void wait_for_ip(); 40 | void wait_for_network(); 41 | 42 | const char *esp_netif_name(esp_netif_t *esp_netif); 43 | const char * wifi_auth_mode_name(wifi_auth_mode_t auth_mode); 44 | 45 | #endif //ESP32_XBEE_WIFI_H 46 | -------------------------------------------------------------------------------- /main/interface/ntrip2_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "interface/ntrip.h" 27 | #include "config.h" 28 | #include "util.h" 29 | #include "uart.h" 30 | 31 | static const char *TAG = "NTRIP_CLIENT"; 32 | 33 | #define BUFFER_SIZE 512 34 | 35 | esp_http_client_handle_t http = NULL; 36 | 37 | static void ntrip2_client_uart_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) { 38 | if (http == NULL) return; 39 | 40 | uart_data_t *data = event_data; 41 | int sent = esp_http_client_write(http, (char *) data->buffer, data->len); 42 | if (sent < 0) { 43 | esp_http_client_close(http); 44 | esp_http_client_cleanup(http); 45 | http = NULL; 46 | } 47 | } 48 | 49 | void ntrip2_client_task(void *ctx) { 50 | uart_register_handler(ntrip2_client_uart_handler); 51 | 52 | while (true) { 53 | wait_for_ip(); 54 | 55 | char *host, *mountpoint, *username, *password; 56 | uint16_t port = config_get_u16(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_PORT)); 57 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_HOST), (void **) &host); 58 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_USERNAME), (void **) &username); 59 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_PASSWORD), (void **) &password); 60 | 61 | // Prepend '/' to mountpoint path 62 | size_t length; 63 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_MOUNTPOINT), NULL, &length); 64 | mountpoint = malloc(length + 1); 65 | mountpoint[0] = '/'; 66 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_MOUNTPOINT), mountpoint + 1, &length); 67 | 68 | // Configure host URL 69 | esp_http_client_config_t config = { 70 | .host = host, 71 | .port = port, 72 | .method = HTTP_METHOD_GET, 73 | .path = mountpoint, 74 | .auth_type = HTTP_AUTH_TYPE_BASIC, 75 | .username = username, 76 | .password = password 77 | }; 78 | 79 | // Initialize client 80 | http = esp_http_client_init(&config); 81 | esp_http_client_set_header(http, "Ntrip-Version", "Ntrip/2.0"); 82 | esp_http_client_set_header(http, "User-Agent", "NTRIP " NTRIP_CLIENT_NAME "/2.0"); 83 | esp_http_client_set_header(http, "Connection", "close"); 84 | 85 | esp_err_t err = esp_http_client_open(http, 0); 86 | ERROR_ACTION(TAG, err != ESP_OK, goto _error, "Could not open HTTP connection: %d %s", err, esp_err_to_name(err)); 87 | 88 | int content_length = esp_http_client_fetch_headers(http); 89 | ERROR_ACTION(TAG, content_length < 0, goto _error, "Could not connect to caster: %d %s", errno, strerror(errno)); 90 | int status_code = esp_http_client_get_status_code(http); 91 | ERROR_ACTION(TAG, status_code != 200, goto _error, "Could not access mountpoint: %d", status_code); 92 | 93 | ERROR_ACTION(TAG, !esp_http_client_is_chunked_response(http), goto _error, "Caster did not respond with chunked transfer encoding: content_length %d", content_length); 94 | 95 | ESP_LOGI(TAG, "Successfully connected to %s:%d/%s", host, port, mountpoint); 96 | uart_nmea("$PESP,NTRIP,SRV,CONNECTED,%s:%d,%s", host, port, mountpoint); 97 | 98 | char *buffer = malloc(BUFFER_SIZE); 99 | 100 | int len; 101 | while ((len = esp_http_client_read(http, buffer, BUFFER_SIZE)) >= 0) { 102 | uart_write(buffer, len); 103 | } 104 | 105 | free(buffer); 106 | 107 | ESP_LOGW(TAG, "Disconnected from %s:%d/%s", host, port, mountpoint); 108 | uart_nmea("$PESP,NTRIP,SRV,DISCONNECTED,%s:%d,%s", host, port, mountpoint); 109 | 110 | _error: 111 | esp_http_client_close(http); 112 | esp_http_client_cleanup(http); 113 | http = NULL; 114 | 115 | free(host); 116 | free(mountpoint); 117 | free(username); 118 | free(password); 119 | } 120 | 121 | vTaskDelete(NULL); 122 | } 123 | 124 | void ntrip2_client_init() { 125 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_ACTIVE))) return; 126 | 127 | xTaskCreate(ntrip2_client_task, "ntrip2_client_task", 16384, NULL, TASK_PRIORITY_NTRIP, NULL); 128 | } -------------------------------------------------------------------------------- /main/interface/ntrip2_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "interface/ntrip.h" 27 | #include "config.h" 28 | #include "util.h" 29 | #include "uart.h" 30 | 31 | static const char *TAG = "NTRIP_SERVER"; 32 | 33 | #define BUFFER_SIZE 512 34 | 35 | esp_http_client_handle_t http = NULL; 36 | static int server_keep_alive; 37 | 38 | static void ntrip2_server_uart_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) { 39 | if (http == NULL) return; 40 | 41 | uart_data_t *data = event_data; 42 | int sent = esp_http_client_write(http, (char *) data->buffer, data->len); 43 | if (sent < 0) { 44 | esp_http_client_close(http); 45 | esp_http_client_cleanup(http); 46 | http = NULL; 47 | } 48 | 49 | server_keep_alive = 0; 50 | } 51 | 52 | static void ntrip2_server_task(void *ctx) { 53 | uart_register_handler(ntrip2_server_uart_handler); 54 | 55 | while (true) { 56 | wait_for_ip(); 57 | 58 | char *host, *mountpoint, *username, *password; 59 | uint16_t port = config_get_u16(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_PORT)); 60 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_HOST), (void **) &host); 61 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_USERNAME), (void **) &username); 62 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_PASSWORD), (void **) &password); 63 | 64 | // Prepend '/' to mountpoint path 65 | size_t length; 66 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_MOUNTPOINT), NULL, &length); 67 | mountpoint = malloc(length + 1); 68 | mountpoint[0] = '/'; 69 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_MOUNTPOINT), mountpoint + 1, &length); 70 | 71 | // Configure host URL 72 | esp_http_client_config_t config = { 73 | .host = host, 74 | .port = port, 75 | .method = HTTP_METHOD_POST, 76 | .path = mountpoint, 77 | .auth_type = HTTP_AUTH_TYPE_BASIC, 78 | .username = username, 79 | .password = password 80 | }; 81 | 82 | // Initialize server 83 | http = esp_http_client_init(&config); 84 | esp_http_client_set_header(http, "Ntrip-Version", "Ntrip/2.0"); 85 | esp_http_client_set_header(http, "User-Agent", "NTRIP " NTRIP_SERVER_NAME "/2.0"); 86 | esp_http_client_set_header(http, "Connection", "close"); 87 | 88 | esp_err_t err = esp_http_client_open(http, 0); 89 | ERROR_ACTION(TAG, err != ESP_OK, goto _error, "Could not open HTTP connection: %d %s", err, esp_err_to_name(err)); 90 | 91 | err = esp_http_client_fetch_headers(http); 92 | ERROR_ACTION(TAG, err < 0, goto _error, "Could not connect to caster: %d %s", errno, strerror(errno)); 93 | int status_code = esp_http_client_get_status_code(http); 94 | ERROR_ACTION(TAG, status_code != 200, goto _error, "Could not access mountpoint: %d", status_code); 95 | 96 | ESP_LOGI(TAG, "Successfully connected to %s:%d/%s", host, port, mountpoint); 97 | uart_nmea("$PESP,NTRIP,SRV,CONNECTED,%s:%d,%s", host, port, mountpoint); 98 | 99 | // Keep alive 100 | server_keep_alive = NTRIP_KEEP_ALIVE_THRESHOLD; 101 | while (true) { 102 | if (server_keep_alive >= NTRIP_KEEP_ALIVE_THRESHOLD) { 103 | int sent = esp_http_client_write(http, NEWLINE, NEWLINE_LENGTH); 104 | if (sent < 0) break; 105 | 106 | server_keep_alive = 0; 107 | } 108 | 109 | server_keep_alive += NTRIP_KEEP_ALIVE_THRESHOLD / 10; 110 | vTaskDelay(pdMS_TO_TICKS(NTRIP_KEEP_ALIVE_THRESHOLD / 10)); 111 | } 112 | 113 | ESP_LOGW(TAG, "Disconnected from %s:%d/%s", host, port, mountpoint); 114 | uart_nmea("$PESP,NTRIP,SRV,DISCONNECTED,%s:%d,%s", host, port, mountpoint); 115 | 116 | _error: 117 | esp_http_client_close(http); 118 | esp_http_client_cleanup(http); 119 | http = NULL; 120 | 121 | free(host); 122 | free(mountpoint); 123 | free(username); 124 | free(password); 125 | } 126 | 127 | vTaskDelete(NULL); 128 | } 129 | 130 | void ntrip2_server_init() { 131 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_ACTIVE))) return; 132 | 133 | xTaskCreate(ntrip2_server_task, "ntrip2_server_task", 16384, NULL, TASK_PRIORITY_NTRIP, NULL); 134 | } -------------------------------------------------------------------------------- /main/interface/ntrip_caster.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "interface/ntrip.h" 28 | #include "config.h" 29 | #include "util.h" 30 | #include "uart.h" 31 | 32 | static const char *TAG = "NTRIP_CASTER"; 33 | 34 | #define BUFFER_SIZE 512 35 | 36 | static int sock = -1; 37 | 38 | static status_led_handle_t status_led = NULL; 39 | static stream_stats_handle_t stream_stats = NULL; 40 | 41 | typedef struct ntrip_caster_client_t { 42 | int socket; 43 | SLIST_ENTRY(ntrip_caster_client_t) next; 44 | } ntrip_caster_client_t; 45 | 46 | static SLIST_HEAD(caster_clients_list_t, ntrip_caster_client_t) caster_clients_list; 47 | 48 | static void ntrip_caster_client_remove(ntrip_caster_client_t *caster_client) { 49 | struct sockaddr_in6 client_addr; 50 | socklen_t socklen = sizeof(client_addr); 51 | int err = getpeername(caster_client->socket, (struct sockaddr *) &client_addr, &socklen); 52 | char *addr_str = err != 0 ? "UNKNOWN" : sockaddrtostr((struct sockaddr *) &client_addr); 53 | 54 | uart_nmea("$PESP,NTRIP,CST,CLIENT,DISCONNECTED,%s", addr_str); 55 | 56 | destroy_socket(&caster_client->socket); 57 | 58 | SLIST_REMOVE(&caster_clients_list, caster_client, ntrip_caster_client_t, next); 59 | free(caster_client); 60 | 61 | if (status_led != NULL && SLIST_EMPTY(&caster_clients_list)) status_led->flashing_mode = STATUS_LED_STATIC; 62 | } 63 | 64 | static void ntrip_caster_uart_handler(void* handler_args, esp_event_base_t base, int32_t length, void* buffer) { 65 | ntrip_caster_client_t *client, *client_tmp; 66 | SLIST_FOREACH_SAFE(client, &caster_clients_list, next, client_tmp) { 67 | int sent = write(client->socket, buffer, length); 68 | if (sent < 0) { 69 | ntrip_caster_client_remove(client); 70 | } else { 71 | stream_stats_increment(stream_stats, 0, sent); 72 | } 73 | } 74 | } 75 | 76 | static int ntrip_caster_socket_init() { 77 | int port = config_get_u16(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_PORT)); 78 | 79 | sock = socket(PF_INET6, SOCK_STREAM, 0); 80 | ERROR_ACTION(TAG, sock < 0, return sock, "Could not create TCP socket: %d %s", errno, strerror(errno)) 81 | 82 | int reuse = 1; 83 | int err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 84 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not set TCP socket options: %d %s", errno, strerror(errno)) 85 | 86 | struct sockaddr_in6 srv_addr = { 87 | .sin6_family = PF_INET6, 88 | .sin6_addr = IN6ADDR_ANY_INIT, 89 | .sin6_port = htons(port) 90 | }; 91 | 92 | err = bind(sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); 93 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not bind TCP socket: %d %s", errno, strerror(errno)) 94 | 95 | err = listen(sock, 1); 96 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not listen on TCP socket: %d %s", errno, strerror(errno)) 97 | 98 | ESP_LOGI(TAG, "Listening on port %d", port); 99 | uart_nmea("$PESP,NTRIP,CST,BIND,%d", port); 100 | 101 | return 0; 102 | } 103 | 104 | static void ntrip_caster_task(void *ctx) { 105 | uart_register_read_handler(ntrip_caster_uart_handler); 106 | 107 | config_color_t status_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_COLOR)); 108 | if (status_led_color.rgba != 0) status_led = status_led_add(status_led_color.rgba, STATUS_LED_STATIC, 500, 2000, 0); 109 | 110 | stream_stats = stream_stats_new("ntrip_caster"); 111 | 112 | while (true) { 113 | ntrip_caster_socket_init(); 114 | 115 | char *mountpoint, *username, *password; 116 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_USERNAME), (void **) &username); 117 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_PASSWORD), (void **) &password); 118 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_MOUNTPOINT), (void **) &mountpoint); 119 | 120 | // Wait for client connections 121 | int sock_client = -1; 122 | char *buffer = malloc(BUFFER_SIZE); 123 | while (true) { 124 | destroy_socket(&sock_client); 125 | 126 | struct sockaddr_in6 source_addr; 127 | size_t addr_len = sizeof(source_addr); 128 | sock_client = accept(sock, (struct sockaddr *)&source_addr, &addr_len); 129 | ERROR_ACTION(TAG, sock_client < 0, goto _error, "Could not accept connection: %d %s", errno, strerror(errno)) 130 | 131 | int len = read(sock_client, buffer, BUFFER_SIZE - 1); 132 | ERROR_ACTION(TAG, len <= 0, continue, "Could not receive from client: %d %s", errno, strerror(errno)) 133 | buffer[len] = '\0'; 134 | 135 | // Find mountpoint requested by looking for GET /(%s)? 136 | char *mountpoint_path = extract_http_header(buffer, "GET "); 137 | ERROR_ACTION(TAG, mountpoint_path == NULL, { 138 | char *response = "HTTP/1.1 405 Method Not Allowed" NEWLINE \ 139 | "Allow: GET" NEWLINE \ 140 | NEWLINE; 141 | 142 | int err = write(sock_client, response, strlen(response)); 143 | if (err < 0) ESP_LOGE(TAG, "Could not send response to client: %d %s", errno, strerror(errno)); 144 | 145 | continue; 146 | }, "Client did not send GET request") 147 | 148 | // Move pointer to name of mountpoint, or empty string if sourcetable request 149 | char *mountpoint_name = mountpoint_path; 150 | 151 | // Treat GET /mountpoint and GET mountpoint the same 152 | if (mountpoint_name[0] == '/') mountpoint_name++; 153 | 154 | // Move to space or end of string (removing HTTP/1.1 from line) 155 | char *space = strstr(mountpoint_name, " "); 156 | if (space != NULL) *space = '\0'; 157 | 158 | // Print sourcetable if exact mountpoint was not requested 159 | bool print_sourcetable = strcasecmp(mountpoint, mountpoint_name) != 0; 160 | free(mountpoint_path); 161 | 162 | // Ensure authenticated 163 | char *basic_authentication = strlen(username) == 0 ? NULL : http_auth_basic_header(username, password); 164 | char *authorization_header = extract_http_header(buffer, "Authorization:"); 165 | bool authenticated = basic_authentication == NULL || 166 | (authorization_header != NULL && strcasecmp(basic_authentication, authorization_header) == 0); 167 | free(basic_authentication); 168 | free(authorization_header); 169 | 170 | // Use HTTP response if not an NTRIP client 171 | char *user_agent_header = extract_http_header(buffer, "User-Agent:"); 172 | bool ntrip_agent = user_agent_header == NULL || strcasestr(user_agent_header, "NTRIP") != NULL; 173 | free(user_agent_header); 174 | 175 | // Unknown mountpoint or sourcetable requested 176 | if (print_sourcetable) { 177 | char stream[256] = ""; 178 | snprintf(stream, sizeof(stream), "STR;%s;;;;;;;;0.00;0.00;0;0;;none;%c;N;0;" NEWLINE "ENDSOURCETABLE", 179 | mountpoint, strlen(username) == 0 ? 'N' : 'B'); 180 | 181 | snprintf(buffer, BUFFER_SIZE, "%s 200 OK" NEWLINE \ 182 | "Server: NTRIP %s/%s" NEWLINE \ 183 | "Content-Type: text/plain" NEWLINE \ 184 | "Content-Length: %d" NEWLINE \ 185 | "Connection: close" NEWLINE \ 186 | NEWLINE \ 187 | "%s", 188 | ntrip_agent ? "SOURCETABLE" : "HTTP/1.0", 189 | NTRIP_CASTER_NAME, &esp_ota_get_app_description()->version[1], 190 | strlen(stream), stream); 191 | 192 | int err = write(sock_client, buffer, strlen(buffer)); 193 | if (err < 0) ESP_LOGE(TAG, "Could not send response to client: %d %s", errno, strerror(errno)); 194 | 195 | continue; 196 | } 197 | 198 | // Request basic authentication header 199 | if (!authenticated) { 200 | char *message = "Authorization Required"; 201 | snprintf(buffer, BUFFER_SIZE, "HTTP/1.0 401 Unauthorized" NEWLINE \ 202 | "Server: %s/1.0" NEWLINE \ 203 | "WWW-Authenticate: Basic realm=\"/%s\"" NEWLINE 204 | "Content-Type: text/plain" NEWLINE \ 205 | "Content-Length: %d" NEWLINE \ 206 | "Connection: close" NEWLINE \ 207 | NEWLINE \ 208 | "%s", 209 | NTRIP_CASTER_NAME, mountpoint, strlen(message), message); 210 | 211 | int err = write(sock_client, buffer, strlen(buffer)); 212 | if (err < 0) ESP_LOGE(TAG, "Could not send response to client: %d %s", errno, strerror(errno)); 213 | 214 | continue; 215 | } 216 | 217 | char response[] = "ICY 200 OK" NEWLINE NEWLINE; 218 | int err = write(sock_client, response, sizeof(response)); 219 | ERROR_ACTION(TAG, err < 0, continue, "Could not send response to client: %d %s", errno, strerror(errno)) 220 | 221 | ntrip_caster_client_t *client = malloc(sizeof(ntrip_caster_client_t)); 222 | client->socket = sock_client; 223 | SLIST_INSERT_HEAD(&caster_clients_list, client, next); 224 | 225 | // Socket will now be dealt with by ntrip_caster_uart_handler, set to -1 so it doesn't get destroyed 226 | sock_client = -1; 227 | 228 | if (status_led != NULL) status_led->flashing_mode = STATUS_LED_FADE; 229 | 230 | char *addr_str = sockaddrtostr((struct sockaddr *) &source_addr); 231 | uart_nmea("$PESP,NTRIP,CST,CLIENT,CONNECTED,%s", addr_str); 232 | } 233 | 234 | _error: 235 | destroy_socket(&sock); 236 | 237 | free(buffer); 238 | } 239 | } 240 | 241 | void ntrip_caster_init() { 242 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_NTRIP_CASTER_ACTIVE))) return; 243 | 244 | xTaskCreate(ntrip_caster_task, "ntrip_caster_task", 4096, NULL, TASK_PRIORITY_INTERFACE, NULL); 245 | } -------------------------------------------------------------------------------- /main/interface/ntrip_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "interface/ntrip.h" 30 | #include "config.h" 31 | #include "util.h" 32 | #include "uart.h" 33 | 34 | static const char *TAG = "NTRIP_CLIENT"; 35 | 36 | #define BUFFER_SIZE 512 37 | 38 | #define GPGGA_HEADER "$GPGGA" 39 | #define GNGGA_HEADER "$GNGGA" 40 | #define GGA_END "\r\n" 41 | 42 | static const int CASTER_READY_BIT = BIT0; 43 | 44 | static int sock = -1; 45 | 46 | static TaskHandle_t nmea_gga_send_task = NULL; 47 | 48 | static EventGroupHandle_t client_event_group; 49 | 50 | static status_led_handle_t status_led = NULL; 51 | static stream_stats_handle_t stream_stats = NULL; 52 | 53 | static char nmea_gga_latest[128] = ""; 54 | 55 | static void nmea_gga_extract(int32_t length, void *buffer) { 56 | void *start = memmem(buffer, length, GPGGA_HEADER, strlen(GPGGA_HEADER)); 57 | if (start == NULL) start = memmem(buffer, length, GNGGA_HEADER, strlen(GNGGA_HEADER)); 58 | if (start == NULL) return; 59 | void *zero = memmem(start, length - (start - buffer), "\0", 1); 60 | void *end = memmem(start, length - (start - buffer), GGA_END, strlen(GGA_END)); 61 | 62 | if (end == NULL || (zero != NULL && zero < end)) return; 63 | 64 | unsigned int size = (end - start) + strlen(GGA_END); 65 | if (size > (sizeof(nmea_gga_latest) - 1)) return; 66 | 67 | memcpy(nmea_gga_latest, start, size); 68 | nmea_gga_latest[size] = '\0'; 69 | } 70 | 71 | static void ntrip_client_nmea_gga_send_task(void *ctx) { 72 | vTaskDelay(pdMS_TO_TICKS(1000)); 73 | 74 | while (true) { 75 | int sent = send(sock, nmea_gga_latest, strlen(nmea_gga_latest), 0); 76 | if (sent < 0) { 77 | destroy_socket(&sock); 78 | } else { 79 | stream_stats_increment(stream_stats, 0, sent); 80 | } 81 | 82 | vTaskDelay(pdMS_TO_TICKS(15000)); 83 | } 84 | } 85 | 86 | static void ntrip_client_uart_handler(void* handler_args, esp_event_base_t base, int32_t length, void* buffer) { 87 | // Caster connected and ready for data 88 | if ((xEventGroupGetBits(client_event_group) & CASTER_READY_BIT) == 0) return; 89 | 90 | nmea_gga_extract(length, buffer); 91 | 92 | /*int sent = send(sock, buffer, length, 0); 93 | if (sent < 0) { 94 | destroy_socket(&sock); 95 | } else { 96 | stream_stats_increment(stream_stats, 0, sent); 97 | }*/ 98 | } 99 | 100 | static void ntrip_client_task(void *ctx) { 101 | client_event_group = xEventGroupCreate(); 102 | uart_register_read_handler(ntrip_client_uart_handler); 103 | 104 | config_color_t status_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_COLOR)); 105 | if (status_led_color.rgba != 0) status_led = status_led_add(status_led_color.rgba, STATUS_LED_FADE, 500, 2000, 0); 106 | if (status_led != NULL) status_led->active = false; 107 | 108 | stream_stats = stream_stats_new("ntrip_client"); 109 | 110 | retry_delay_handle_t delay_handle = retry_init(true, 5, 2000, 0); 111 | 112 | while (true) { 113 | retry_delay(delay_handle); 114 | 115 | wait_for_ip(); 116 | 117 | char *buffer = NULL; 118 | 119 | char *host, *mountpoint, *username, *password; 120 | uint16_t port = config_get_u16(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_PORT)); 121 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_HOST), (void **) &host); 122 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_USERNAME), (void **) &username); 123 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_PASSWORD), (void **) &password); 124 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_MOUNTPOINT), (void **) &mountpoint); 125 | 126 | ESP_LOGI(TAG, "Connecting to %s:%d/%s", host, port, mountpoint); 127 | uart_nmea("$PESP,NTRIP,CLI,CONNECTING,%s:%d,%s", host, port, mountpoint); 128 | sock = connect_socket(host, port, SOCK_STREAM); 129 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_RESOLVE, goto _error, "Could not resolve host"); 130 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_CONNECT, goto _error, "Could not connect to host"); 131 | 132 | buffer = malloc(BUFFER_SIZE); 133 | 134 | char *authorization = http_auth_basic_header(username, password); 135 | snprintf(buffer, BUFFER_SIZE, "GET /%s HTTP/1.1" NEWLINE \ 136 | "User-Agent: NTRIP %s/%s" NEWLINE \ 137 | "Authorization: %s" NEWLINE 138 | NEWLINE 139 | , mountpoint, NTRIP_CLIENT_NAME, &esp_ota_get_app_description()->version[1], authorization); 140 | free(authorization); 141 | 142 | int err = write(sock, buffer, strlen(buffer)); 143 | ERROR_ACTION(TAG, err < 0, goto _error, "Could not send request to caster: %d %s", errno, strerror(errno)); 144 | 145 | int len = read(sock, buffer, BUFFER_SIZE - 1); 146 | ERROR_ACTION(TAG, len <= 0, goto _error, "Could not receive response from caster: %d %s", errno, strerror(errno)); 147 | buffer[len] = '\0'; 148 | 149 | char *status = extract_http_header(buffer, ""); 150 | ERROR_ACTION(TAG, status == NULL || !ntrip_response_ok(status), free(status); goto _error, 151 | "Could not connect to mountpoint: %s", 152 | status == NULL ? "HTTP response malformed" : 153 | (ntrip_response_sourcetable_ok(status) ? "Mountpoint not found" : status)) 154 | free(status); 155 | 156 | ESP_LOGI(TAG, "Successfully connected to %s:%d/%s", host, port, mountpoint); 157 | uart_nmea("$PESP,NTRIP,CLI,CONNECTED,%s:%d,%s", host, port, mountpoint); 158 | 159 | retry_reset(delay_handle); 160 | 161 | if (status_led != NULL) status_led->active = true; 162 | 163 | // Start sending GGA to caster 164 | xTaskCreate(ntrip_client_nmea_gga_send_task, "ntrip_client_gga", 2048, NULL, TASK_PRIORITY_INTERFACE, &nmea_gga_send_task); 165 | 166 | // Connected 167 | xEventGroupSetBits(client_event_group, CASTER_READY_BIT); 168 | 169 | // Read from socket until disconnected 170 | while (sock != -1 && (len = read(sock, buffer, BUFFER_SIZE)) >= 0) { 171 | uart_write(buffer, len); 172 | 173 | stream_stats_increment(stream_stats, len, 0); 174 | } 175 | 176 | // Disconnected 177 | xEventGroupSetBits(client_event_group, CASTER_READY_BIT); 178 | 179 | // Stop sending GGA to caster 180 | vTaskDelete(nmea_gga_send_task); 181 | 182 | if (status_led != NULL) status_led->active = false; 183 | 184 | ESP_LOGW(TAG, "Disconnected from %s:%d/%s", host, port, mountpoint); 185 | uart_nmea("$PESP,NTRIP,CLI,DISCONNECTED,%s:%d,%s", host, port, mountpoint); 186 | 187 | _error: 188 | destroy_socket(&sock); 189 | 190 | free(buffer); 191 | free(host); 192 | free(mountpoint); 193 | free(username); 194 | free(password); 195 | } 196 | 197 | vTaskDelete(NULL); 198 | } 199 | 200 | void ntrip_client_init() { 201 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_NTRIP_CLIENT_ACTIVE))) return; 202 | 203 | xTaskCreate(ntrip_client_task, "ntrip_client_task", 4096, NULL, TASK_PRIORITY_INTERFACE, NULL); 204 | } -------------------------------------------------------------------------------- /main/interface/ntrip_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "interface/ntrip.h" 30 | #include "config.h" 31 | #include "util.h" 32 | #include "uart.h" 33 | 34 | static const char *TAG = "NTRIP_SERVER"; 35 | 36 | #define BUFFER_SIZE 512 37 | 38 | static const int CASTER_READY_BIT = BIT0; 39 | static const int DATA_READY_BIT = BIT1; 40 | static const int DATA_SENT_BIT = BIT2; 41 | 42 | static int sock = -1; 43 | 44 | static int data_keep_alive; 45 | static EventGroupHandle_t server_event_group; 46 | 47 | static status_led_handle_t status_led = NULL; 48 | static stream_stats_handle_t stream_stats = NULL; 49 | 50 | static TaskHandle_t server_task = NULL; 51 | static TaskHandle_t sleep_task = NULL; 52 | 53 | static void ntrip_server_uart_handler(void* handler_args, esp_event_base_t base, int32_t length, void* buffer) { 54 | EventBits_t event_bits = xEventGroupGetBits(server_event_group); 55 | 56 | // Reset data availability bit 57 | if ((event_bits & DATA_READY_BIT) == 0) { 58 | xEventGroupSetBits(server_event_group, DATA_READY_BIT); 59 | 60 | if (event_bits & DATA_SENT_BIT) 61 | ESP_LOGI(TAG, "Data received by UART, will now reconnect to caster if disconnected"); 62 | } 63 | data_keep_alive = 0; 64 | 65 | // Ignore if caster is not connected and ready for data 66 | if ((event_bits & CASTER_READY_BIT) == 0) return; 67 | 68 | // Caster is connected and some data will be sent 69 | if ((event_bits & DATA_SENT_BIT) == 0) xEventGroupSetBits(server_event_group, DATA_SENT_BIT); 70 | 71 | int sent = write(sock, buffer, length); 72 | if (sent < 0) { 73 | destroy_socket(&sock); 74 | vTaskResume(server_task); 75 | } else { 76 | stream_stats_increment(stream_stats, 0, sent); 77 | } 78 | } 79 | 80 | static void ntrip_server_sleep_task(void *ctx) { 81 | vTaskSuspend(NULL); 82 | 83 | while (true) { 84 | // If wait time exceeded, clear data ready bit 85 | if (data_keep_alive == NTRIP_KEEP_ALIVE_THRESHOLD) { 86 | xEventGroupClearBits(server_event_group, DATA_READY_BIT); 87 | ESP_LOGW(TAG, "No data received by UART in %d seconds, will not reconnect to caster if disconnected", NTRIP_KEEP_ALIVE_THRESHOLD / 1000); 88 | } 89 | data_keep_alive += NTRIP_KEEP_ALIVE_THRESHOLD / 10; 90 | vTaskDelay(pdMS_TO_TICKS(NTRIP_KEEP_ALIVE_THRESHOLD / 10)); 91 | } 92 | } 93 | 94 | static void ntrip_server_task(void *ctx) { 95 | server_event_group = xEventGroupCreate(); 96 | uart_register_read_handler(ntrip_server_uart_handler); 97 | xTaskCreate(ntrip_server_sleep_task, "ntrip_server_sleep_task", 2048, NULL, TASK_PRIORITY_INTERFACE, &sleep_task); 98 | 99 | config_color_t status_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_COLOR)); 100 | if (status_led_color.rgba != 0) status_led = status_led_add(status_led_color.rgba, STATUS_LED_FADE, 500, 2000, 0); 101 | if (status_led != NULL) status_led->active = false; 102 | 103 | stream_stats = stream_stats_new("ntrip_server"); 104 | 105 | retry_delay_handle_t delay_handle = retry_init(true, 5, 2000, 0); 106 | 107 | while (true) { 108 | retry_delay(delay_handle); 109 | 110 | // Wait for data to be available 111 | if ((xEventGroupGetBits(server_event_group) & DATA_READY_BIT) == 0) { 112 | ESP_LOGI(TAG, "Waiting for UART input to connect to caster"); 113 | uart_nmea("$PESP,NTRIP,SRV,WAITING"); 114 | xEventGroupWaitBits(server_event_group, DATA_READY_BIT, true, false, portMAX_DELAY); 115 | } 116 | 117 | vTaskResume(sleep_task); 118 | 119 | wait_for_ip(); 120 | 121 | char *buffer = NULL; 122 | 123 | char *host, *mountpoint, *password; 124 | uint16_t port = config_get_u16(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_PORT)); 125 | config_get_primitive(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_PORT), &port); 126 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_HOST), (void **) &host); 127 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_PASSWORD), (void **) &password); 128 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_MOUNTPOINT), (void **) &mountpoint); 129 | 130 | ESP_LOGI(TAG, "Connecting to %s:%d/%s", host, port, mountpoint); 131 | uart_nmea("$PESP,NTRIP,SRV,CONNECTING,%s:%d,%s", host, port, mountpoint); 132 | sock = connect_socket(host, port, SOCK_STREAM); 133 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_RESOLVE, goto _error, "Could not resolve host"); 134 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_CONNECT, goto _error, "Could not connect to host"); 135 | 136 | buffer = malloc(BUFFER_SIZE); 137 | 138 | snprintf(buffer, BUFFER_SIZE, "SOURCE %s /%s" NEWLINE \ 139 | "Source-Agent: NTRIP %s/%s" NEWLINE \ 140 | NEWLINE, password, mountpoint, NTRIP_SERVER_NAME, &esp_ota_get_app_description()->version[1]); 141 | 142 | int err = write(sock, buffer, strlen(buffer)); 143 | ERROR_ACTION(TAG, err < 0, goto _error, "Could not send request to caster: %d %s", errno, strerror(errno)); 144 | 145 | int len = read(sock, buffer, BUFFER_SIZE - 1); 146 | ERROR_ACTION(TAG, len <= 0, goto _error, "Could not receive response from caster: %d %s", errno, strerror(errno)); 147 | buffer[len] = '\0'; 148 | 149 | char *status = extract_http_header(buffer, ""); 150 | ERROR_ACTION(TAG, status == NULL || !ntrip_response_ok(status), free(status); goto _error, 151 | "Could not connect to mountpoint: %s", status == NULL ? "HTTP response malformed" : status); 152 | free(status); 153 | 154 | ESP_LOGI(TAG, "Successfully connected to %s:%d/%s", host, port, mountpoint); 155 | uart_nmea("$PESP,NTRIP,SRV,CONNECTED,%s:%d,%s", host, port, mountpoint); 156 | 157 | retry_reset(delay_handle); 158 | 159 | if (status_led != NULL) status_led->active = true; 160 | 161 | // Connected 162 | xEventGroupSetBits(server_event_group, CASTER_READY_BIT); 163 | 164 | // Await disconnect from UART handler 165 | vTaskSuspend(NULL); 166 | 167 | // Disconnected 168 | xEventGroupClearBits(server_event_group, CASTER_READY_BIT | DATA_SENT_BIT); 169 | 170 | if (status_led != NULL) status_led->active = false; 171 | 172 | ESP_LOGW(TAG, "Disconnected from %s:%d/%s", host, port, mountpoint); 173 | uart_nmea("$PESP,NTRIP,SRV,DISCONNECTED,%s:%d,%s", host, port, mountpoint); 174 | 175 | _error: 176 | vTaskSuspend(sleep_task); 177 | 178 | destroy_socket(&sock); 179 | 180 | free(buffer); 181 | free(host); 182 | free(mountpoint); 183 | free(password); 184 | } 185 | } 186 | 187 | void ntrip_server_init() { 188 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_NTRIP_SERVER_ACTIVE))) return; 189 | 190 | xTaskCreate(ntrip_server_task, "ntrip_server_task", 4096, NULL, TASK_PRIORITY_INTERFACE, &server_task); 191 | } 192 | -------------------------------------------------------------------------------- /main/interface/ntrip_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | static bool str_starts_with(const char *a, const char *b) { 22 | return strncmp(a, b, strlen(b)) == 0; 23 | } 24 | 25 | bool ntrip_response_ok(void *response) { 26 | return str_starts_with(response, "OK") || str_starts_with(response, "ICY 200 OK") || 27 | str_starts_with(response, "HTTP/1.1 200 OK"); 28 | } 29 | 30 | bool ntrip_response_sourcetable_ok(void *response) { 31 | return str_starts_with(response, "HTTP/1.1 200 OK") || str_starts_with(response, "SOURCETABLE 200 OK"); 32 | } -------------------------------------------------------------------------------- /main/interface/socket_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "interface/socket_client.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | static const char *TAG = "SOCKET_CLIENT"; 35 | 36 | #define BUFFER_SIZE 1024 37 | 38 | static int sock = -1; 39 | 40 | static status_led_handle_t status_led = NULL; 41 | static stream_stats_handle_t stream_stats = NULL; 42 | 43 | static void socket_client_uart_handler(void* handler_args, esp_event_base_t base, int32_t length, void* buffer) { 44 | if (sock == -1) return; 45 | 46 | stream_stats_increment(stream_stats, 0, length); 47 | 48 | int err = write(sock, buffer, length); 49 | if (err < 0) destroy_socket(&sock); 50 | } 51 | 52 | static void socket_client_task(void *ctx) { 53 | uart_register_read_handler(socket_client_uart_handler); 54 | 55 | config_color_t status_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_COLOR)); 56 | if (status_led_color.rgba != 0) status_led = status_led_add(status_led_color.rgba, STATUS_LED_FADE, 500, 2000, 0); 57 | if (status_led != NULL) status_led->active = false; 58 | 59 | stream_stats = stream_stats_new("socket_client"); 60 | 61 | retry_delay_handle_t delay_handle = retry_init(true, 5, 2000, 0); 62 | 63 | while (true) { 64 | retry_delay(delay_handle); 65 | 66 | wait_for_ip(); 67 | 68 | char *host, *connect_message; 69 | uint16_t port = config_get_u16(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_PORT)); 70 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_HOST), (void **) &host); 71 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_CONNECT_MESSAGE), (void **) &connect_message); 72 | int socktype = config_get_bool1(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_TYPE_TCP_UDP)) ? SOCK_STREAM : SOCK_DGRAM; 73 | 74 | ESP_LOGI(TAG, "Connecting to %s host %s:%d", SOCKTYPE_NAME(socktype), host, port); 75 | uart_nmea("$PESP,SOCK,CLI,%s,CONNECTING,%s:%d", SOCKTYPE_NAME(socktype), host, port); 76 | sock = connect_socket(host, port, socktype); 77 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_RESOLVE, goto _error, "Could not resolve host"); 78 | ERROR_ACTION(TAG, sock == CONNECT_SOCKET_ERROR_CONNECT, goto _error, "Could not connect to host"); 79 | 80 | int err = write(sock, connect_message, strlen(connect_message)); 81 | free(connect_message); 82 | ERROR_ACTION(TAG, err < 0, goto _error, "Could not send connection message: %d %s", errno, strerror(errno)); 83 | 84 | ESP_LOGI(TAG, "Successfully connected to %s:%d", host, port); 85 | uart_nmea("$PESP,SOCK,CLI,%s,CONNECTED,%s:%d", SOCKTYPE_NAME(socktype), host, port); 86 | 87 | retry_reset(delay_handle); 88 | 89 | if (status_led != NULL) status_led->active = true; 90 | 91 | char *buffer = malloc(BUFFER_SIZE); 92 | 93 | int len; 94 | while ((len = read(sock, buffer, BUFFER_SIZE)) >= 0) { 95 | uart_write(buffer, len); 96 | 97 | stream_stats_increment(stream_stats, len, 0); 98 | } 99 | 100 | free(buffer); 101 | 102 | if (status_led != NULL) status_led->active = false; 103 | 104 | ESP_LOGW(TAG, "Disconnected from %s:%d: %d %s", host, port, errno, strerror(errno)); 105 | uart_nmea("$PESP,SOCK,CLI,%s,DISCONNECTED,%s:%d", SOCKTYPE_NAME(socktype), host, port); 106 | 107 | _error: 108 | destroy_socket(&sock); 109 | 110 | free(host); 111 | free(connect_message); 112 | } 113 | 114 | vTaskDelete(NULL); 115 | } 116 | 117 | void socket_client_init() { 118 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_SOCKET_CLIENT_ACTIVE))) return; 119 | 120 | xTaskCreate(socket_client_task, "socket_client_task", 4096, NULL, TASK_PRIORITY_INTERFACE, NULL); 121 | } -------------------------------------------------------------------------------- /main/interface/socket_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "config.h" 27 | #include "interface/socket_server.h" 28 | #include "status_led.h" 29 | #include "stream_stats.h" 30 | #include "uart.h" 31 | #include "util.h" 32 | 33 | static const char *TAG = "SOCKET_SERVER"; 34 | 35 | #define BUFFER_SIZE 1024 36 | 37 | static int sock_tcp, sock_udp; 38 | static char *buffer; 39 | 40 | static status_led_handle_t status_led = NULL; 41 | static stream_stats_handle_t stream_stats = NULL; 42 | 43 | typedef struct socket_client_t { 44 | int socket; 45 | struct sockaddr_in6 addr; 46 | int type; 47 | SLIST_ENTRY(socket_client_t) next; 48 | } socket_client_t; 49 | 50 | static SLIST_HEAD(socket_client_list_t, socket_client_t) socket_client_list; 51 | 52 | static bool socket_address_equal(struct sockaddr_in6 *a, struct sockaddr_in6 *b) { 53 | if (a->sin6_family != b->sin6_family) return false; 54 | 55 | if (a->sin6_family == PF_INET) { 56 | struct sockaddr_in *a4 = (struct sockaddr_in *) a; 57 | struct sockaddr_in *b4 = (struct sockaddr_in *) b; 58 | 59 | return a4->sin_addr.s_addr == b4->sin_addr.s_addr && a4->sin_port == b4->sin_port; 60 | } else if (a->sin6_family == PF_INET6) { 61 | return memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(a->sin6_addr)) == 0 && a->sin6_port == b->sin6_port; 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | static socket_client_t * socket_client_add(int sock, struct sockaddr_in6 addr, int socktype) { 68 | socket_client_t *client = malloc(sizeof(socket_client_t)); 69 | *client = (socket_client_t) { 70 | .socket = sock, 71 | .addr = addr, 72 | .type = socktype 73 | }; 74 | 75 | SLIST_INSERT_HEAD(&socket_client_list, client, next); 76 | 77 | char *addr_str = sockaddrtostr((struct sockaddr *) &addr); 78 | ESP_LOGI(TAG, "Accepted %s client %s", SOCKTYPE_NAME(socktype), addr_str); 79 | uart_nmea("$PESP,SOCK,SRV,%s,CONNECTED,%s", SOCKTYPE_NAME(socktype), addr_str); 80 | 81 | if (status_led != NULL) status_led->flashing_mode = STATUS_LED_FADE; 82 | 83 | return client; 84 | } 85 | 86 | static void socket_client_remove(socket_client_t *socket_client) { 87 | char *addr_str = sockaddrtostr((struct sockaddr *) &socket_client->addr); 88 | ESP_LOGI(TAG, "Disconnected %s client %s", SOCKTYPE_NAME(socket_client->type), addr_str); 89 | uart_nmea("$PESP,SOCK,SRV,%s,DISCONNECTED,%s", SOCKTYPE_NAME(socket_client->type), addr_str); 90 | 91 | destroy_socket(&socket_client->socket); 92 | 93 | SLIST_REMOVE(&socket_client_list, socket_client, socket_client_t, next); 94 | free(socket_client); 95 | 96 | if (status_led != NULL && SLIST_EMPTY(&socket_client_list)) status_led->flashing_mode = STATUS_LED_STATIC; 97 | } 98 | 99 | static void socket_server_uart_handler(void* handler_args, esp_event_base_t base, int32_t length, void* buf) { 100 | socket_client_t *client, *client_tmp; 101 | SLIST_FOREACH_SAFE(client, &socket_client_list, next, client_tmp) { 102 | int sent = write(client->socket, buf, length); 103 | if (sent < 0) { 104 | ESP_LOGE(TAG, "Could not write to %s socket: %d %s", SOCKTYPE_NAME(client->type), errno, strerror(errno)); 105 | socket_client_remove(client); 106 | } else { 107 | stream_stats_increment(stream_stats, 0, sent); 108 | } 109 | } 110 | } 111 | 112 | static int socket_init(int socktype, int port) { 113 | int sock = socket(PF_INET6, socktype, 0); 114 | ERROR_ACTION(TAG, sock < 0, return -1, "Could not create %s socket: %d %s", SOCKTYPE_NAME(socktype), errno, strerror(errno)) 115 | 116 | int reuse = 1; 117 | int err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 118 | ERROR_ACTION(TAG, err != 0, close(sock); return -1, "Could not set %s socket options: %d %s", SOCKTYPE_NAME(socktype), errno, strerror(errno)) 119 | 120 | struct sockaddr_in6 srv_addr = { 121 | .sin6_family = PF_INET6, 122 | .sin6_addr = IN6ADDR_ANY_INIT, 123 | .sin6_port = htons(port) 124 | }; 125 | 126 | err = bind(sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); 127 | ERROR_ACTION(TAG, err != 0, close(sock); return -1, "Could not bind %s socket: %d %s", SOCKTYPE_NAME(socktype), errno, strerror(errno)) 128 | 129 | ESP_LOGI(TAG, "%s socket listening on port %d", SOCKTYPE_NAME(socktype), port); 130 | uart_nmea("$PESP,SOCK,SRV,%s,BIND,%d", SOCKTYPE_NAME(socktype), port); 131 | 132 | return sock; 133 | } 134 | 135 | static esp_err_t socket_tcp_init() { 136 | int port = config_get_u16(CONF_ITEM(KEY_CONFIG_SOCKET_SERVER_TCP_PORT)); 137 | 138 | sock_tcp = socket_init(SOCK_STREAM, port); 139 | if (sock_tcp < 0) return ESP_FAIL; 140 | 141 | int err = listen(sock_tcp, 1); 142 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock_tcp); return ESP_FAIL, "Could not listen on TCP socket: %d %s", errno, strerror(errno)) 143 | 144 | return ESP_OK; 145 | } 146 | 147 | static esp_err_t socket_tcp_accept() { 148 | struct sockaddr_in6 source_addr; 149 | uint addr_len = sizeof(source_addr); 150 | int sock = accept(sock_tcp, (struct sockaddr *)&source_addr, &addr_len); 151 | ERROR_ACTION(TAG, sock < 0, return ESP_FAIL, "Could not accept new TCP connection: %d %s", errno, strerror(errno)) 152 | 153 | socket_client_add(sock, source_addr, SOCK_STREAM); 154 | return ESP_OK; 155 | } 156 | 157 | static esp_err_t socket_udp_init() { 158 | int port = config_get_u16(CONF_ITEM(KEY_CONFIG_SOCKET_SERVER_UDP_PORT)); 159 | 160 | sock_udp = socket_init(SOCK_DGRAM, port); 161 | return sock_udp < 0 ? ESP_FAIL : ESP_OK; 162 | } 163 | 164 | static bool socket_udp_has_client(struct sockaddr_in6 *source_addr) { 165 | socket_client_t *client; 166 | SLIST_FOREACH(client, &socket_client_list, next) { 167 | if (client->type != SOCK_DGRAM) continue; 168 | 169 | struct sockaddr_in6 *client_addr = ((struct sockaddr_in6 *) &client->addr); 170 | 171 | if (socket_address_equal(source_addr, client_addr)) return true; 172 | } 173 | 174 | return false; 175 | } 176 | 177 | static esp_err_t socket_udp_client_accept(struct sockaddr_in6 source_addr) { 178 | if (socket_udp_has_client(&source_addr)) return ESP_OK; 179 | 180 | int sock = socket(PF_INET6, SOCK_DGRAM, 0); 181 | ERROR_ACTION(TAG, sock < 0, return sock, "Could not create client UDP socket: %d %s", errno, strerror(errno)) 182 | 183 | int reuse = 1; 184 | int err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 185 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not set client UDP socket options: %d %s", errno, strerror(errno)) 186 | 187 | struct sockaddr_in6 server_addr; 188 | socklen_t socklen = sizeof(server_addr); 189 | getsockname(sock_udp, (struct sockaddr *)&server_addr, &socklen); 190 | err = bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)); 191 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not bind client UDP socket: %d %s", errno, strerror(errno)) 192 | 193 | err = connect(sock, (struct sockaddr *)&source_addr, sizeof(source_addr)); 194 | ERROR_ACTION(TAG, err != 0, destroy_socket(&sock); return err, "Could not connect client UDP socket: %d %s", errno, strerror(errno)) 195 | 196 | socket_client_add(sock, source_addr, SOCK_DGRAM); 197 | return ESP_OK; 198 | } 199 | 200 | static esp_err_t socket_udp_accept() { 201 | struct sockaddr_in6 source_addr; 202 | socklen_t socklen = sizeof(source_addr); 203 | 204 | // Receive until nothing left to receive 205 | int len; 206 | while ((len = recvfrom(sock_udp, buffer, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&source_addr, &socklen)) > 0) { 207 | // Multiple connections could have been made at once, so accept for every receive just in case 208 | socket_udp_client_accept(source_addr); 209 | 210 | stream_stats_increment(stream_stats, len, 0); 211 | 212 | uart_write(buffer, len); 213 | } 214 | 215 | // Error occurred during receiving 216 | if (len < 0 && errno != EWOULDBLOCK) { 217 | ESP_LOGE(TAG, "Unable to receive UDP connection: %d %s", errno, strerror(errno)); 218 | return ESP_FAIL; 219 | } 220 | 221 | return ESP_OK; 222 | } 223 | 224 | static void socket_clients_receive(fd_set *socket_set) { 225 | socket_client_t *client, *client_tmp; 226 | SLIST_FOREACH_SAFE(client, &socket_client_list, next, client_tmp) { 227 | if (!FD_ISSET(client->socket, socket_set)) continue; 228 | 229 | // Receive until nothing left to receive 230 | int len; 231 | while ((len = recv(client->socket, buffer, BUFFER_SIZE, MSG_DONTWAIT)) > 0) { 232 | stream_stats_increment(stream_stats, len, 0); 233 | 234 | uart_write(buffer, len); 235 | } 236 | 237 | // Remove on error 238 | if (len < 0 && errno != EWOULDBLOCK) { 239 | socket_client_remove(client); 240 | } 241 | } 242 | } 243 | 244 | static void socket_server_task(void *ctx) { 245 | uart_register_read_handler(socket_server_uart_handler); 246 | 247 | config_color_t status_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_SOCKET_SERVER_COLOR)); 248 | if (status_led_color.rgba != 0) status_led = status_led_add(status_led_color.rgba, STATUS_LED_STATIC, 500, 2000, 0); 249 | 250 | stream_stats = stream_stats_new("socket_server"); 251 | 252 | while (true) { 253 | SLIST_INIT(&socket_client_list); 254 | 255 | socket_tcp_init(); 256 | socket_udp_init(); 257 | 258 | // Accept/receive loop 259 | buffer = malloc(BUFFER_SIZE); 260 | fd_set socket_set; 261 | while (true) { 262 | // Reset all selected 263 | FD_ZERO(&socket_set); 264 | 265 | // New TCP/UDP connections 266 | FD_SET(sock_tcp, &socket_set); 267 | FD_SET(sock_udp, &socket_set); 268 | 269 | int maxfd = MAX(sock_tcp, sock_udp); 270 | 271 | // Existing connections 272 | socket_client_t *client; 273 | SLIST_FOREACH(client, &socket_client_list, next) { 274 | FD_SET(client->socket, &socket_set); 275 | maxfd = MAX(maxfd, client->socket); 276 | } 277 | 278 | // Wait for activity on one of selected 279 | int err = select(maxfd + 1, &socket_set, NULL, NULL, NULL); 280 | ERROR_ACTION(TAG, err < 0, goto _error, "Could not select socket to receive from: %d %s", errno, strerror(errno)) 281 | 282 | // Accept new connections 283 | if (FD_ISSET(sock_tcp, &socket_set)) socket_tcp_accept(); 284 | if (FD_ISSET(sock_udp, &socket_set)) socket_udp_accept(); 285 | 286 | // Receive from existing connections 287 | socket_clients_receive(&socket_set); 288 | } 289 | 290 | _error: 291 | destroy_socket(&sock_tcp); 292 | destroy_socket(&sock_udp); 293 | socket_client_t *client, *client_tmp; 294 | SLIST_FOREACH_SAFE(client, &socket_client_list, next, client_tmp) { 295 | destroy_socket(&client->socket); 296 | SLIST_REMOVE(&socket_client_list, client, socket_client_t, next); 297 | free(client); 298 | } 299 | 300 | free(buffer); 301 | } 302 | } 303 | 304 | void socket_server_init() { 305 | if (!config_get_bool1(CONF_ITEM(KEY_CONFIG_SOCKET_SERVER_ACTIVE))) return; 306 | 307 | xTaskCreate(socket_server_task, "socket_server_task", 4096, NULL, TASK_PRIORITY_INTERFACE, NULL); 308 | } -------------------------------------------------------------------------------- /main/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "log.h" 25 | 26 | #define INITIAL_MAGIC "@@@@\n" 27 | 28 | static const char *TAG = "LOG"; 29 | 30 | static RingbufHandle_t ringbuf_handle; 31 | 32 | esp_err_t log_init() { 33 | ringbuf_handle = xRingbufferCreate(4096, RINGBUF_TYPE_BYTEBUF); 34 | if (ringbuf_handle == NULL) { 35 | ESP_LOGE(TAG, "Could not create log ring buffer"); 36 | return ESP_FAIL; 37 | } 38 | 39 | // Magic string to let web log know that ESP32 has restart (to reset line counter) 40 | xRingbufferSend(ringbuf_handle, INITIAL_MAGIC, strlen(INITIAL_MAGIC), 0); 41 | 42 | return ESP_OK; 43 | } 44 | 45 | int log_vprintf(const char * format, va_list arg) { 46 | char buffer[512]; 47 | int n = vsnprintf(buffer, 512, format, arg); 48 | 49 | if (n > 512) { 50 | n = 512; 51 | } 52 | 53 | // Remove log colors for web log buffer 54 | xRingbufferSend(ringbuf_handle, buffer + strlen(LOG_COLOR_E), 55 | n - strlen(LOG_COLOR_E) - strlen(LOG_RESET_COLOR) - 1, 0); 56 | xRingbufferSend(ringbuf_handle, "\n", 1, 0); 57 | 58 | uart_log(buffer, n); 59 | 60 | return n; 61 | } 62 | 63 | void *log_receive(size_t *length, TickType_t ticksToWait) { 64 | return xRingbufferReceive(ringbuf_handle, length, ticksToWait); 65 | } 66 | 67 | void log_return(void *item) { 68 | vRingbufferReturnItem(ringbuf_handle, item); 69 | } 70 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "freertos/FreeRTOS.h" 27 | #include "freertos/task.h" 28 | #include "esp_system.h" 29 | #include "esp_log.h" 30 | #include "driver/uart.h" 31 | #include "driver/ledc.h" 32 | #include "button.h" 33 | 34 | #include "config.h" 35 | #include "wifi.h" 36 | #include "interface/socket_server.h" 37 | #include "uart.h" 38 | #include "interface/ntrip.h" 39 | #include "tasks.h" 40 | 41 | static const char *TAG = "MAIN"; 42 | 43 | static char *reset_reason_name(esp_reset_reason_t reason); 44 | 45 | static void reset_button_task() { 46 | QueueHandle_t button_queue = button_init(PIN_BIT(GPIO_NUM_0)); 47 | gpio_set_pull_mode(GPIO_NUM_0, GPIO_PULLUP_ONLY); 48 | while (true) { 49 | button_event_t button_ev; 50 | if (xQueueReceive(button_queue, &button_ev, 1000 / portTICK_PERIOD_MS)) { 51 | if (button_ev.event == BUTTON_DOWN && button_ev.duration > 5000) { 52 | config_reset(); 53 | vTaskDelay(2000 / portTICK_PERIOD_MS); 54 | esp_restart(); 55 | } 56 | } 57 | } 58 | } 59 | 60 | static void sntp_time_set_handler(struct timeval *tv) { 61 | ESP_LOGI(TAG, "Synced time from SNTP"); 62 | } 63 | 64 | void app_main() 65 | { 66 | status_led_init(); 67 | status_led_handle_t status_led = status_led_add(0xFFFFFF33, STATUS_LED_FADE, 250, 2500, 0); 68 | 69 | log_init(); 70 | esp_log_set_vprintf(log_vprintf); 71 | esp_log_level_set("gpio", ESP_LOG_WARN); 72 | esp_log_level_set("system_api", ESP_LOG_WARN); 73 | esp_log_level_set("wifi", ESP_LOG_WARN); 74 | esp_log_level_set("esp_netif_handlers", ESP_LOG_WARN); 75 | 76 | core_dump_check(); 77 | 78 | xTaskCreate(reset_button_task, "reset_button", 4096, NULL, TASK_PRIORITY_RESET_BUTTON, NULL); 79 | 80 | stream_stats_init(); 81 | 82 | config_init(); 83 | uart_init(); 84 | 85 | esp_reset_reason_t reset_reason = esp_reset_reason(); 86 | 87 | const esp_app_desc_t *app_desc = esp_ota_get_app_description(); 88 | char elf_buffer[17]; 89 | esp_ota_get_app_elf_sha256(elf_buffer, sizeof(elf_buffer)); 90 | 91 | uart_nmea("$PESP,INIT,START,%s,%s", app_desc->version, reset_reason_name(reset_reason)); 92 | 93 | ESP_LOGI(TAG, "╔══════════════════════════════════════════════╗"); 94 | ESP_LOGI(TAG, "║ ESP32 XBee %-33s " "║", app_desc->version); 95 | ESP_LOGI(TAG, "╠══════════════════════════════════════════════╣"); 96 | ESP_LOGI(TAG, "║ Compiled: %8s %-25s " "║", app_desc->time, app_desc->date); 97 | ESP_LOGI(TAG, "║ ELF SHA256: %-32s " "║", elf_buffer); 98 | ESP_LOGI(TAG, "║ ESP-IDF: %-35s " "║", app_desc->idf_ver); 99 | ESP_LOGI(TAG, "╟──────────────────────────────────────────────╢"); 100 | ESP_LOGI(TAG, "║ Reset reason: %-30s " "║", reset_reason_name(reset_reason)); 101 | ESP_LOGI(TAG, "╟──────────────────────────────────────────────╢"); 102 | ESP_LOGI(TAG, "║ Author: Nebojša Cvetković ║"); 103 | ESP_LOGI(TAG, "║ Source: https://github.com/nebkat/esp32-xbee ║"); 104 | ESP_LOGI(TAG, "╚══════════════════════════════════════════════╝"); 105 | 106 | esp_event_loop_create_default(); 107 | 108 | vTaskDelay(pdMS_TO_TICKS(2500)); 109 | status_led->interval = 100; 110 | status_led->duration = 1000; 111 | status_led->flashing_mode = STATUS_LED_BLINK; 112 | 113 | if (reset_reason != ESP_RST_POWERON && reset_reason != ESP_RST_SW && reset_reason != ESP_RST_WDT) { 114 | status_led->active = false; 115 | status_led_handle_t error_led = status_led_add(0xFF000033, STATUS_LED_BLINK, 50, 10000, 0); 116 | 117 | vTaskDelay(pdMS_TO_TICKS(10000)); 118 | 119 | status_led_remove(error_led); 120 | status_led->active = true; 121 | } 122 | 123 | 124 | net_init(); 125 | wifi_init(); 126 | 127 | web_server_init(); 128 | 129 | ntrip_caster_init(); 130 | ntrip_server_init(); 131 | ntrip_client_init(); 132 | 133 | socket_server_init(); 134 | socket_client_init(); 135 | 136 | uart_nmea("$PESP,INIT,COMPLETE"); 137 | 138 | wait_for_ip(); 139 | 140 | sntp_setoperatingmode(SNTP_OPMODE_POLL); 141 | sntp_setservername(0, "pool.ntp.org"); 142 | sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH); 143 | sntp_set_time_sync_notification_cb(sntp_time_set_handler); 144 | sntp_init(); 145 | 146 | #ifdef DEBUG_HEAP 147 | while (true) { 148 | vTaskDelay(pdMS_TO_TICKS(2000)); 149 | 150 | multi_heap_info_t info; 151 | heap_caps_get_info(&info, MALLOC_CAP_DEFAULT); 152 | 153 | uart_nmea("$PESP,HEAP,FREE,%d/%d,%d%%", info.total_free_bytes, 154 | info.total_allocated_bytes + info.total_free_bytes, 155 | 100 * info.total_free_bytes / (info.total_allocated_bytes + info.total_free_bytes)); 156 | } 157 | #endif 158 | } 159 | 160 | static char *reset_reason_name(esp_reset_reason_t reason) { 161 | switch (reason) { 162 | default: 163 | case ESP_RST_UNKNOWN: 164 | return "UNKNOWN"; 165 | case ESP_RST_POWERON: 166 | return "POWERON"; 167 | case ESP_RST_EXT: 168 | return "EXTERNAL"; 169 | case ESP_RST_SW: 170 | return "SOFTWARE"; 171 | case ESP_RST_PANIC: 172 | return "PANIC"; 173 | case ESP_RST_INT_WDT: 174 | return "INTERRUPT_WATCHDOG"; 175 | case ESP_RST_TASK_WDT: 176 | return "TASK_WATCHDOG"; 177 | case ESP_RST_WDT: 178 | return "OTHER_WATCHDOG"; 179 | case ESP_RST_DEEPSLEEP: 180 | return "DEEPSLEEP"; 181 | case ESP_RST_BROWNOUT: 182 | return "BROWNOUT"; 183 | case ESP_RST_SDIO: 184 | return "SDIO"; 185 | } 186 | } -------------------------------------------------------------------------------- /main/protocol/nmea.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "protocol/nmea.h" 25 | 26 | uint8_t nmea_calculate_checksum(char *sentence) { 27 | uint8_t checksum = 0; 28 | unsigned int length = strlen(sentence); 29 | for (unsigned int i = 1; i < length; i++) { 30 | checksum ^= (uint8_t) sentence[i]; 31 | } 32 | 33 | return checksum; 34 | } 35 | 36 | int nmea_asprintf(char **strp, const char *fmt, ...) { 37 | va_list args; 38 | va_start(args, fmt); 39 | 40 | int l = nmea_vasprintf(strp, fmt, args); 41 | 42 | va_end(args); 43 | 44 | return l; 45 | } 46 | 47 | int nmea_vasprintf(char **strp, const char *fmt, va_list args) { 48 | char *sentence; 49 | vasprintf(&sentence, fmt, args); 50 | uint8_t checksum = nmea_calculate_checksum(sentence); 51 | int l = asprintf(strp, "%s*%02X\r\n", sentence, checksum); 52 | free(sentence); 53 | 54 | return l; 55 | } 56 | -------------------------------------------------------------------------------- /main/retry.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include "retry.h" 22 | 23 | struct retry_delay { 24 | uint16_t attempts; 25 | 26 | bool first_instant; 27 | 28 | uint8_t short_count; 29 | int short_delay; 30 | 31 | int max_delay; 32 | 33 | uint8_t delays_offset; 34 | }; 35 | 36 | static const int delays[] = {1000, 2000, 5000, 10000, 15000, 30000, 45000, 60000, 90000, 37 | 120000, 300000, 600000, 900000, 1800000, 2700000, 3600000}; 38 | static const int delays_count = sizeof(delays) / sizeof(int); 39 | 40 | retry_delay_handle_t retry_init(bool first_instant, uint8_t short_count, int short_delay, int max_delay) { 41 | retry_delay_handle_t handle = malloc(sizeof(struct retry_delay)); 42 | *handle = (struct retry_delay) { 43 | .attempts = 0, 44 | 45 | .first_instant = first_instant, 46 | 47 | .short_count = short_count, 48 | .short_delay = short_delay, 49 | 50 | .max_delay = max_delay, 51 | 52 | .delays_offset = 0 53 | }; 54 | 55 | while (handle->delays_offset < delays_count && delays[handle->delays_offset] < short_delay) { 56 | handle->delays_offset++; 57 | } 58 | 59 | return handle; 60 | } 61 | 62 | int retry_delay(retry_delay_handle_t handle) { 63 | int attempts = handle->attempts; 64 | int delay; 65 | if (attempts == 0 && handle->first_instant) { 66 | delay = 0; 67 | } else if (attempts < handle->short_count) { 68 | delay = handle->short_delay; 69 | } else { 70 | attempts -= handle->short_count; 71 | attempts += handle->delays_offset; 72 | 73 | if (attempts < delays_count) { 74 | delay = delays[attempts]; 75 | } else { 76 | delay = delays[delays_count - 1]; 77 | } 78 | 79 | if (handle->max_delay > 0 && delay > handle->max_delay) delay = handle->max_delay; 80 | } 81 | 82 | handle->attempts++; 83 | 84 | if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay)); 85 | 86 | return handle->attempts; 87 | } 88 | 89 | void retry_reset(retry_delay_handle_t handle) { 90 | handle->attempts = 0; 91 | } -------------------------------------------------------------------------------- /main/status_led.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include "freertos/FreeRTOS.h" 21 | #include "freertos/task.h" 22 | #include "freertos/queue.h" 23 | #include "freertos/semphr.h" 24 | #include "freertos/xtensa_api.h" 25 | #include "freertos/portmacro.h" 26 | #include "status_led.h" 27 | #include 28 | 29 | #define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE 30 | 31 | #define STATUS_LED_RED_GPIO GPIO_NUM_21 32 | #define STATUS_LED_GREEN_GPIO GPIO_NUM_22 33 | #define STATUS_LED_BLUE_GPIO GPIO_NUM_23 34 | #define STATUS_LED_RED_CHANNEL LEDC_CHANNEL_0 35 | #define STATUS_LED_GREEN_CHANNEL LEDC_CHANNEL_1 36 | #define STATUS_LED_BLUE_CHANNEL LEDC_CHANNEL_2 37 | 38 | #define STATUS_LED_RSSI_GPIO GPIO_NUM_18 39 | #define STATUS_LED_SLEEP_GPIO GPIO_NUM_27 40 | #define STATUS_LED_ASSOC_GPIO GPIO_NUM_25 41 | #define STATUS_LED_RSSI_CHANNEL LEDC_CHANNEL_3 42 | #define STATUS_LED_SLEEP_CHANNEL LEDC_CHANNEL_4 43 | #define STATUS_LED_ASSOC_CHANNEL LEDC_CHANNEL_5 44 | 45 | #define STATUS_LED_FREQ 1000 46 | 47 | static SLIST_HEAD(status_led_color_list_t, status_led_color_t) status_led_colors_list; 48 | 49 | static TaskHandle_t led_task; 50 | 51 | void status_led_clear() { 52 | 53 | } 54 | 55 | status_led_handle_t status_led_add(uint32_t rgba, status_led_flashing_mode_t flashing_mode, uint32_t interval, uint32_t duration, uint8_t expire) { 56 | uint8_t red = (rgba >> 24u) & 0xFFu; 57 | uint8_t green = (rgba >> 16u) & 0xFFu; 58 | uint8_t blue = (rgba >> 8u) & 0xFFu; 59 | uint8_t alpha = rgba & 0xFFu; 60 | 61 | status_led_handle_t color = calloc(1, sizeof(struct status_led_color_t)); 62 | color->red = (red * alpha) / 0xFF; 63 | color->green = (green * alpha) / 0xFF; 64 | color->blue = (blue * alpha) / 0xFF; 65 | 66 | color->flashing_mode = flashing_mode; 67 | color->interval = interval; 68 | color->duration = duration; 69 | color->expire = expire; 70 | 71 | color->active = true; 72 | 73 | // Insert at tail 74 | if (SLIST_EMPTY(&status_led_colors_list)) { 75 | SLIST_INSERT_HEAD(&status_led_colors_list, color, next); 76 | } else { 77 | status_led_handle_t current, next; 78 | SLIST_FOREACH_SAFE(current, &status_led_colors_list, next, next) { 79 | if (next == NULL) { 80 | SLIST_INSERT_AFTER(current, color, next); 81 | } 82 | } 83 | } 84 | 85 | vTaskResume(led_task); 86 | 87 | return color; 88 | } 89 | 90 | void status_led_remove(status_led_handle_t color) { 91 | if (color == NULL) return; 92 | color->remove = true; 93 | } 94 | 95 | static void status_led_channel_set(ledc_channel_t channel, uint8_t value) { 96 | ledc_set_duty(LEDC_SPEED_MODE, channel, value); 97 | ledc_update_duty(LEDC_SPEED_MODE, channel); 98 | } 99 | 100 | static void status_led_set(uint8_t red, uint8_t green, uint8_t blue) { 101 | status_led_channel_set(STATUS_LED_RED_CHANNEL, 0xFF - red); 102 | status_led_channel_set(STATUS_LED_GREEN_CHANNEL, 0xFF - green); 103 | status_led_channel_set(STATUS_LED_BLUE_CHANNEL, 0xFF - blue); 104 | } 105 | 106 | static void status_led_channel_fade(ledc_channel_t channel, uint8_t value, int max_fade_time_ms) { 107 | ledc_set_fade_with_time(LEDC_SPEED_MODE, channel, value, max_fade_time_ms); 108 | ledc_fade_start(LEDC_SPEED_MODE, channel, LEDC_FADE_NO_WAIT); 109 | } 110 | 111 | static void status_led_fade(uint8_t red, uint8_t green, uint8_t blue, int max_fade_time_ms) { 112 | status_led_channel_fade(STATUS_LED_RED_CHANNEL, 0xFF - red, max_fade_time_ms); 113 | status_led_channel_fade(STATUS_LED_GREEN_CHANNEL, 0xFF - green, max_fade_time_ms); 114 | status_led_channel_fade(STATUS_LED_BLUE_CHANNEL, 0xFF - blue, max_fade_time_ms); 115 | } 116 | 117 | static void status_led_show(status_led_handle_t color) { 118 | if (color->flashing_mode == STATUS_LED_STATIC) { 119 | status_led_set(color->red, color->green, color->blue); 120 | 121 | vTaskDelay(pdMS_TO_TICKS(color->duration)); 122 | } else { 123 | bool fade = color->flashing_mode == STATUS_LED_FADE; 124 | bool active = true; 125 | for (unsigned int i = 0; i < color->duration / color->interval; i++, active = !active) { 126 | uint8_t red = active ? color->red : 0; 127 | uint8_t green = active ? color->green : 0; 128 | uint8_t blue = active ? color->blue : 0; 129 | if (fade) { 130 | status_led_fade(red, green, blue, color->interval / 2); 131 | } else { 132 | status_led_set(red, green, blue); 133 | } 134 | 135 | vTaskDelay(pdMS_TO_TICKS(color->interval)); 136 | } 137 | } 138 | 139 | // Turn off all LEDs 140 | status_led_set(0, 0, 0); 141 | } 142 | 143 | static void status_led_task() { 144 | while (true) { 145 | // Wait for a color 146 | if (SLIST_EMPTY(&status_led_colors_list)) vTaskSuspend(NULL); 147 | 148 | status_led_handle_t color, color_tmp; 149 | SLIST_FOREACH_SAFE(color, &status_led_colors_list, next, color_tmp) { 150 | // Marked for removal 151 | if (color->remove) { 152 | SLIST_REMOVE(&status_led_colors_list, color, status_led_color_t, next); 153 | free(color); 154 | continue; 155 | } 156 | 157 | // Show color 158 | if (color->active) status_led_show(color); 159 | } 160 | } 161 | } 162 | 163 | void status_led_init() { 164 | ledc_timer_config_t ledc_timer = { 165 | .duty_resolution = LEDC_TIMER_8_BIT, 166 | .freq_hz = STATUS_LED_FREQ, 167 | .speed_mode = LEDC_SPEED_MODE, 168 | .timer_num = LEDC_TIMER_0, 169 | .clk_cfg = LEDC_AUTO_CLK, 170 | }; 171 | 172 | ledc_timer_config(&ledc_timer); 173 | 174 | ledc_channel_config_t ledc_config = { 175 | .duty = 255, 176 | .speed_mode = LEDC_SPEED_MODE, 177 | .hpoint = 0, 178 | .timer_sel = LEDC_TIMER_0 179 | }; 180 | 181 | ledc_config.channel = STATUS_LED_RED_CHANNEL; 182 | ledc_config.gpio_num = STATUS_LED_RED_GPIO; 183 | ledc_channel_config(&ledc_config); 184 | 185 | ledc_config.channel = STATUS_LED_GREEN_CHANNEL; 186 | ledc_config.gpio_num = STATUS_LED_GREEN_GPIO; 187 | ledc_channel_config(&ledc_config); 188 | 189 | ledc_config.channel = STATUS_LED_BLUE_CHANNEL; 190 | ledc_config.gpio_num = STATUS_LED_BLUE_GPIO; 191 | ledc_channel_config(&ledc_config); 192 | 193 | ledc_config.channel = STATUS_LED_SLEEP_CHANNEL; 194 | ledc_config.gpio_num = STATUS_LED_SLEEP_GPIO; 195 | ledc_channel_config(&ledc_config); 196 | 197 | ledc_config.duty = 0; 198 | ledc_config.channel = STATUS_LED_RSSI_CHANNEL; 199 | ledc_config.gpio_num = STATUS_LED_RSSI_GPIO; 200 | ledc_channel_config(&ledc_config); 201 | 202 | ledc_config.channel = STATUS_LED_ASSOC_CHANNEL; 203 | ledc_config.gpio_num = STATUS_LED_ASSOC_GPIO; 204 | ledc_channel_config(&ledc_config); 205 | 206 | ledc_fade_func_install(0); 207 | 208 | xTaskCreate(status_led_task, "status_led", 2048, NULL, TASK_PRIORITY_STATUS_LED, &led_task); 209 | } 210 | 211 | void rssi_led_set(uint8_t value) { 212 | status_led_channel_set(STATUS_LED_RSSI_CHANNEL, value); 213 | } 214 | 215 | void rssi_led_fade(uint8_t value, int max_fade_time_ms) { 216 | status_led_channel_fade(STATUS_LED_RSSI_CHANNEL, value, max_fade_time_ms); 217 | } 218 | 219 | void assoc_led_set(uint8_t value) { 220 | status_led_channel_set(STATUS_LED_ASSOC_CHANNEL, value); 221 | } 222 | 223 | void assoc_led_fade(uint8_t value, int max_fade_time_ms) { 224 | status_led_channel_fade(STATUS_LED_ASSOC_CHANNEL, value, max_fade_time_ms); 225 | } 226 | 227 | void sleep_led_set(uint8_t value) { 228 | status_led_channel_set(STATUS_LED_SLEEP_CHANNEL, 0xFF - value); 229 | } 230 | 231 | void sleep_led_fade(uint8_t value, int max_fade_time_ms) { 232 | status_led_channel_fade(STATUS_LED_SLEEP_CHANNEL, 0xFF - value, max_fade_time_ms); 233 | } -------------------------------------------------------------------------------- /main/stream_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2020 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "include/stream_stats.h" 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #define RUNNING_AVERAGE_PERIOD 1000 27 | #define RUNNING_AVERAGE_ALPHA 0.8 28 | #define RUNNING_AVERAGE_PERIOD_CORRECTION (1000.0 / RUNNING_AVERAGE_PERIOD) 29 | 30 | struct stream_stats { 31 | const char *name; 32 | 33 | uint32_t total_in; 34 | uint32_t total_out; 35 | 36 | double rate_in; 37 | double rate_out; 38 | 39 | uint32_t rate_in_period_count; 40 | uint32_t rate_out_period_count; 41 | 42 | SLIST_ENTRY(stream_stats) next; 43 | }; 44 | 45 | static SLIST_HEAD(stream_stats_list_t, stream_stats) stream_stats_list; 46 | 47 | static void stream_stats_task(void *ctx) { 48 | while (true) { 49 | vTaskDelay(pdMS_TO_TICKS(RUNNING_AVERAGE_PERIOD)); 50 | stream_stats_handle_t stats; 51 | SLIST_FOREACH(stats, &stream_stats_list, next) { 52 | stats->rate_in = stats->rate_in * RUNNING_AVERAGE_ALPHA + 53 | (double) stats->rate_in_period_count * (1.0 - RUNNING_AVERAGE_ALPHA) * RUNNING_AVERAGE_PERIOD_CORRECTION; 54 | stats->rate_out = stats->rate_out * RUNNING_AVERAGE_ALPHA + 55 | (double) stats->rate_out_period_count * (1.0 - RUNNING_AVERAGE_ALPHA) * RUNNING_AVERAGE_PERIOD_CORRECTION; 56 | 57 | stats->rate_in_period_count = 0; 58 | stats->rate_out_period_count = 0; 59 | } 60 | } 61 | } 62 | 63 | void stream_stats_init() { 64 | SLIST_INIT(&stream_stats_list); 65 | xTaskCreate(stream_stats_task, "stream_stats_task", 2048, NULL, TASK_PRIORITY_STATS, NULL); 66 | } 67 | 68 | stream_stats_handle_t stream_stats_new(const char *name) { 69 | stream_stats_handle_t new = calloc(1, sizeof(struct stream_stats)); 70 | new->name = name; 71 | SLIST_INSERT_HEAD(&stream_stats_list, new, next); 72 | 73 | return new; 74 | } 75 | 76 | void stream_stats_increment(stream_stats_handle_t stats, uint32_t in, uint32_t out) { 77 | stats->total_in += in; 78 | stats->total_out += out; 79 | stats->rate_in_period_count += in; 80 | stats->rate_out_period_count += out; 81 | } 82 | 83 | void stream_stats_values(stream_stats_handle_t stats, stream_stats_values_t *values) { 84 | *values = (stream_stats_values_t) { 85 | .name = stats->name, 86 | .total_in = stats->total_in, 87 | .total_out = stats->total_out, 88 | .rate_in = stats->rate_in, 89 | .rate_out = stats->rate_out 90 | }; 91 | } 92 | 93 | stream_stats_handle_t stream_stats_first() { 94 | return SLIST_FIRST(&stream_stats_list); 95 | } 96 | 97 | stream_stats_handle_t stream_stats_next(stream_stats_handle_t stats) { 98 | return SLIST_NEXT(stats, next); 99 | } 100 | 101 | stream_stats_handle_t stream_stats_get(const char *name) { 102 | stream_stats_handle_t stats; 103 | SLIST_FOREACH(stats, &stream_stats_list, next) { 104 | if (stats->name == name) { 105 | return stats; 106 | } 107 | } 108 | 109 | return NULL; 110 | } -------------------------------------------------------------------------------- /main/uart.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "uart.h" 27 | #include "config.h" 28 | #include "interface/socket_server.h" 29 | #include "tasks.h" 30 | 31 | static const char *TAG = "UART"; 32 | 33 | ESP_EVENT_DEFINE_BASE(UART_EVENT_READ); 34 | ESP_EVENT_DEFINE_BASE(UART_EVENT_WRITE); 35 | 36 | void uart_register_read_handler(esp_event_handler_t event_handler) { 37 | ESP_ERROR_CHECK(esp_event_handler_register(UART_EVENT_READ, ESP_EVENT_ANY_ID, event_handler, NULL)); 38 | } 39 | 40 | void uart_unregister_read_handler(esp_event_handler_t event_handler) { 41 | ESP_ERROR_CHECK(esp_event_handler_unregister(UART_EVENT_READ, ESP_EVENT_ANY_ID, event_handler)); 42 | } 43 | 44 | void uart_register_write_handler(esp_event_handler_t event_handler) { 45 | ESP_ERROR_CHECK(esp_event_handler_register(UART_EVENT_WRITE, ESP_EVENT_ANY_ID, event_handler, NULL)); 46 | } 47 | 48 | void uart_unregister_write_handler(esp_event_handler_t event_handler) { 49 | ESP_ERROR_CHECK(esp_event_handler_unregister(UART_EVENT_WRITE, ESP_EVENT_ANY_ID, event_handler)); 50 | } 51 | 52 | static int uart_port = -1; 53 | static bool uart_log_forward = false; 54 | 55 | static stream_stats_handle_t stream_stats; 56 | 57 | static void uart_task(void *ctx); 58 | 59 | void uart_init() { 60 | uart_log_forward = config_get_bool1(CONF_ITEM(KEY_CONFIG_UART_LOG_FORWARD)); 61 | 62 | uart_port = config_get_u8(CONF_ITEM(KEY_CONFIG_UART_NUM)); 63 | 64 | uart_hw_flowcontrol_t flow_ctrl; 65 | bool flow_ctrl_rts = config_get_bool1(CONF_ITEM(KEY_CONFIG_UART_FLOW_CTRL_RTS)); 66 | bool flow_ctrl_cts = config_get_bool1(CONF_ITEM(KEY_CONFIG_UART_FLOW_CTRL_CTS)); 67 | if (flow_ctrl_rts && flow_ctrl_cts) { 68 | flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS; 69 | } else if (flow_ctrl_rts) { 70 | flow_ctrl = UART_HW_FLOWCTRL_RTS; 71 | } else if (flow_ctrl_cts) { 72 | flow_ctrl = UART_HW_FLOWCTRL_CTS; 73 | } else { 74 | flow_ctrl = UART_HW_FLOWCTRL_DISABLE; 75 | } 76 | 77 | uart_config_t uart_config = { 78 | .baud_rate = config_get_u32(CONF_ITEM(KEY_CONFIG_UART_BAUD_RATE)), 79 | .data_bits = config_get_u8(CONF_ITEM(KEY_CONFIG_UART_DATA_BITS)), 80 | .parity = config_get_u8(CONF_ITEM(KEY_CONFIG_UART_PARITY)), 81 | .stop_bits = config_get_u8(CONF_ITEM(KEY_CONFIG_UART_STOP_BITS)), 82 | .flow_ctrl = flow_ctrl 83 | }; 84 | ESP_ERROR_CHECK(uart_param_config(uart_port, &uart_config)); 85 | ESP_ERROR_CHECK(uart_set_pin( 86 | uart_port, 87 | config_get_i8(CONF_ITEM(KEY_CONFIG_UART_TX_PIN)), 88 | config_get_i8(CONF_ITEM(KEY_CONFIG_UART_RX_PIN)), 89 | config_get_i8(CONF_ITEM(KEY_CONFIG_UART_RTS_PIN)), 90 | config_get_i8(CONF_ITEM(KEY_CONFIG_UART_CTS_PIN)) 91 | )); 92 | ESP_ERROR_CHECK(uart_driver_install(uart_port, UART_BUFFER_SIZE, UART_BUFFER_SIZE, 0, NULL, 0)); 93 | 94 | stream_stats = stream_stats_new("uart"); 95 | 96 | xTaskCreate(uart_task, "uart_task", 8192, NULL, TASK_PRIORITY_UART, NULL); 97 | } 98 | 99 | static void uart_task(void *ctx) { 100 | uint8_t buffer[UART_BUFFER_SIZE]; 101 | 102 | while (true) { 103 | int32_t len = uart_read_bytes(uart_port, buffer, sizeof(buffer), pdMS_TO_TICKS(50)); 104 | if (len < 0) { 105 | ESP_LOGE(TAG, "Error reading from UART"); 106 | } else if (len == 0) { 107 | continue; 108 | } 109 | 110 | stream_stats_increment(stream_stats, len, 0); 111 | 112 | esp_event_post(UART_EVENT_READ, len, &buffer, len, portMAX_DELAY); 113 | } 114 | } 115 | 116 | void uart_inject(void *buf, size_t len) { 117 | esp_event_post(UART_EVENT_READ, len, buf, len, portMAX_DELAY); 118 | } 119 | 120 | int uart_log(char *buf, size_t len) { 121 | if (!uart_log_forward) return 0; 122 | return uart_write(buf, len); 123 | } 124 | 125 | int uart_nmea(const char *fmt, ...) { 126 | va_list args; 127 | va_start(args, fmt); 128 | 129 | char *nmea; 130 | nmea_vasprintf(&nmea, fmt, args); 131 | int l = uart_write(nmea, strlen(nmea)); 132 | free(nmea); 133 | 134 | va_end(args); 135 | 136 | return l; 137 | } 138 | 139 | int uart_write(char *buf, size_t len) { 140 | if (uart_port < 0) return 0; 141 | if (len == 0) return 0; 142 | 143 | int written = uart_write_bytes(uart_port, buf, len); 144 | if (written < 0) return written; 145 | 146 | stream_stats_increment(stream_stats, 0, len); 147 | 148 | esp_event_post(UART_EVENT_WRITE, len, buf, len, portMAX_DELAY); 149 | 150 | return written; 151 | } -------------------------------------------------------------------------------- /main/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "util.h" 26 | 27 | void destroy_socket(int *socket) { 28 | if (*socket < 0) return; 29 | shutdown(*socket, SHUT_RDWR); 30 | close(*socket); 31 | *socket = -1; 32 | } 33 | 34 | // Include space for port 35 | static char addr_str[INET6_ADDRSTRLEN + 2 + 6 + 1]; 36 | 37 | char *sockaddrtostr(struct sockaddr *a) { 38 | struct sockaddr_in *a4 = (struct sockaddr_in *) a; 39 | struct sockaddr_in6 *a6 = (struct sockaddr_in6 *) a; 40 | 41 | sa_family_t family = a->sa_family; 42 | int port = 0; 43 | ip4_addr_t *addr4 = NULL; 44 | ip6_addr_t *addr6 = NULL; 45 | if (family == PF_INET) { 46 | addr4 = (ip4_addr_t *) &a4->sin_addr.s_addr; 47 | port = a4->sin_port; 48 | } else if (family == PF_INET6) { 49 | addr6 = (ip6_addr_t *) &a6->sin6_addr; 50 | 51 | if (ip6_addr_isipv4mappedipv6(addr6)) { 52 | family = PF_INET; 53 | addr4 = (ip4_addr_t *) &addr6->addr[3]; 54 | } 55 | 56 | port = a6->sin6_port; 57 | } 58 | 59 | // Get address string 60 | if (family == PF_INET) { 61 | inet_ntop(AF_INET, addr4, addr_str, INET_ADDRSTRLEN); 62 | } else if (family == PF_INET6) { 63 | addr_str[0] = '['; 64 | inet_ntop(AF_INET6, addr6, addr_str + 1, INET6_ADDRSTRLEN); 65 | int ip_len = strlen(addr_str); 66 | addr_str[ip_len] = ']'; 67 | addr_str[ip_len + 1] = '\0'; 68 | } else { 69 | return "UNKNOWN"; 70 | } 71 | 72 | // Append port number 73 | sprintf(addr_str + strlen(addr_str), ":%d", ntohs(port)); 74 | 75 | return addr_str; 76 | } 77 | 78 | char *extract_http_header(const char *buffer, const char *key) { 79 | // Need space for key, at least 1 character, and newline 80 | if (strlen(key) + 2 > strlen(buffer)) return NULL; 81 | 82 | // Cheap search ignores potential problems where searched key is suffix of another longer key 83 | char *start = strcasestr(buffer, key); 84 | if (!start) return NULL; 85 | start += strlen(key); 86 | 87 | char *end = strstr(start, "\r\n"); 88 | if (!end) return NULL; 89 | 90 | // Trim whitespace at start and end 91 | while (isspace((unsigned char) *start) && start < end) start++; 92 | while (isspace((unsigned char) *(end - 1)) && start < end) end--; 93 | 94 | int len = (int) (end - start); 95 | if (len == 0) return NULL; 96 | 97 | char *header_value = malloc(len + 1); 98 | if (header_value == NULL) return NULL; 99 | 100 | memcpy(header_value, start, len); 101 | header_value[len] = '\0'; 102 | return header_value; 103 | } 104 | 105 | int connect_socket(char *host, int port, int socktype) { 106 | int err; 107 | struct addrinfo addr_hints; 108 | struct addrinfo *addr_results; 109 | 110 | // Obtain address(es) matching host/port 111 | memset(&addr_hints, 0, sizeof(struct addrinfo)); 112 | addr_hints.ai_family = AF_UNSPEC; 113 | addr_hints.ai_socktype = socktype; 114 | addr_hints.ai_flags = AI_NUMERICSERV; 115 | addr_hints.ai_protocol = 0; 116 | 117 | char port_string[6]; 118 | sprintf(port_string, "%u", port); 119 | err = getaddrinfo(host, port_string, &addr_hints, &addr_results); 120 | if (err < 0) return CONNECT_SOCKET_ERROR_RESOLVE; 121 | 122 | int sock = -1; 123 | 124 | // Try all resolved hosts 125 | for (struct addrinfo *addr_result = addr_results; addr_result != NULL; addr_result = addr_result->ai_next) { 126 | sock = socket(addr_result->ai_family, addr_result->ai_socktype, addr_result->ai_protocol); 127 | if (sock < 0) continue; 128 | 129 | if (connect(sock, addr_result->ai_addr, addr_result->ai_addrlen) == 0) break; 130 | 131 | close(sock); 132 | 133 | sock = -1; 134 | } 135 | 136 | freeaddrinfo(addr_results); 137 | 138 | if (sock < 0) return CONNECT_SOCKET_ERROR_CONNECT; 139 | 140 | // Read/write timeouts 141 | struct timeval timeout; 142 | timeout.tv_sec = 10; 143 | timeout.tv_usec = 0; 144 | err = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); 145 | if (err != 0) goto _opts_error; 146 | err = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); 147 | if (err != 0) goto _opts_error; 148 | 149 | // Reuse address 150 | int reuse = 1; 151 | err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 152 | if (err != 0) goto _opts_error; 153 | 154 | return sock; 155 | 156 | _opts_error: 157 | close(sock); 158 | return CONNECT_SOCKET_ERROR_OPTS; 159 | } 160 | 161 | char *http_auth_basic_header(const char *username, const char *password) { 162 | int out; 163 | char *user_info = NULL; 164 | char *digest = NULL; 165 | size_t n = 0; 166 | asprintf(&user_info, "%s:%s", username, password); 167 | mbedtls_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info)); 168 | digest = calloc(1, 6 + n + 1); 169 | strcpy(digest, "Basic "); 170 | mbedtls_base64_encode((unsigned char *)digest + 6, n, (size_t *)&out, (const unsigned char *)user_info, strlen(user_info)); 171 | free(user_info); 172 | return digest; 173 | } 174 | 175 | esp_err_t write_all(int fd, char *buf, size_t buf_len) { 176 | int ret; 177 | while (buf_len > 0) { 178 | ret = write(fd, buf, buf_len); 179 | if (ret < 0) return ESP_FAIL; 180 | 181 | buf += ret; 182 | buf_len -= ret; 183 | } 184 | return ESP_OK; 185 | } -------------------------------------------------------------------------------- /main/web_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "web_server.h" 37 | 38 | // Max length a file path can have on storage 39 | #define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) 40 | #define FILE_HASH_SUFFIX ".crc" 41 | 42 | #define WWW_PARTITION_PATH "/www" 43 | #define WWW_PARTITION_LABEL "www" 44 | #define BUFFER_SIZE 2048 45 | 46 | static const char *TAG = "WEB"; 47 | 48 | static char *buffer; 49 | 50 | enum auth_method { 51 | AUTH_METHOD_OPEN = 0, 52 | AUTH_METHOD_HOTSPOT = 1, 53 | AUTH_METHOD_BASIC = 2 54 | }; 55 | 56 | static char *basic_authentication; 57 | static enum auth_method auth_method; 58 | 59 | #define IS_FILE_EXT(filename, ext) \ 60 | (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0) 61 | 62 | static esp_err_t www_spiffs_init() { 63 | ESP_LOGD(TAG, "Initializing SPIFFS"); 64 | 65 | esp_vfs_spiffs_conf_t conf = { 66 | .base_path = WWW_PARTITION_PATH, 67 | .partition_label = WWW_PARTITION_LABEL, 68 | .max_files = 10, 69 | .format_if_mount_failed = false 70 | }; 71 | 72 | esp_err_t ret = esp_vfs_spiffs_register(&conf); 73 | if (ret != ESP_OK) { 74 | if (ret == ESP_FAIL) { 75 | ESP_LOGE(TAG, "Failed to mount or format filesystem"); 76 | } else if (ret == ESP_ERR_NOT_FOUND) { 77 | ESP_LOGE(TAG, "Failed to find SPIFFS partition"); 78 | } else { 79 | ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); 80 | } 81 | return ESP_FAIL; 82 | } 83 | 84 | size_t total = 0, used = 0; 85 | ret = esp_spiffs_info(WWW_PARTITION_LABEL, &total, &used); 86 | if (ret != ESP_OK) { 87 | ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); 88 | return ESP_FAIL; 89 | } 90 | 91 | ESP_LOGD(TAG, "Partition size: total: %d, used: %d", total, used); 92 | return ESP_OK; 93 | } 94 | 95 | // Set HTTP response content type according to file extension 96 | static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename) 97 | { 98 | if (IS_FILE_EXT(filename, ".html")) { 99 | return httpd_resp_set_type(req, "text/html"); 100 | } else if (IS_FILE_EXT(filename, ".js")) { 101 | return httpd_resp_set_type(req, "application/javascript"); 102 | } else if (IS_FILE_EXT(filename, ".css")) { 103 | return httpd_resp_set_type(req, "text/css"); 104 | } else if (IS_FILE_EXT(filename, ".ico")) { 105 | return httpd_resp_set_type(req, "image/x-icon"); 106 | } 107 | /* This is a limited set only */ 108 | /* For any other type always set as plain text */ 109 | return httpd_resp_set_type(req, "text/plain"); 110 | } 111 | 112 | /* Copies the full path into destination buffer and returns 113 | * pointer to path (skipping the preceding base path) */ 114 | static char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize) 115 | { 116 | const size_t base_pathlen = strlen(base_path); 117 | size_t pathlen = strlen(uri); 118 | 119 | const char *quest = strchr(uri, '?'); 120 | if (quest) { 121 | pathlen = MIN(pathlen, quest - uri); 122 | } 123 | const char *hash = strchr(uri, '#'); 124 | if (hash) { 125 | pathlen = MIN(pathlen, hash - uri); 126 | } 127 | 128 | if (base_pathlen + pathlen + 1 > destsize) { 129 | // Full path string won't fit into destination buffer 130 | return NULL; 131 | } 132 | 133 | // Construct full path (base + path) 134 | strcpy(dest, base_path); 135 | strlcpy(dest + base_pathlen, uri, pathlen + 1); 136 | 137 | // Return pointer to path, skipping the base 138 | return dest + base_pathlen; 139 | } 140 | 141 | static esp_err_t json_response(httpd_req_t *req, cJSON *root) { 142 | // Set mime type 143 | esp_err_t err = httpd_resp_set_type(req, "application/json"); 144 | if (err != ESP_OK) return err; 145 | 146 | // Convert to string 147 | bool success = cJSON_PrintPreallocated(root, buffer, BUFFER_SIZE, false); 148 | cJSON_Delete(root); 149 | if (!success) { 150 | ESP_LOGE(TAG, "Not enough space in buffer to output JSON"); 151 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough space in buffer to output JSON"); 152 | return ESP_FAIL; 153 | } 154 | 155 | // Send as response 156 | err = httpd_resp_send(req, buffer, strlen(buffer)); 157 | if (err != ESP_OK) return err; 158 | 159 | return ESP_OK; 160 | } 161 | 162 | static esp_err_t basic_auth(httpd_req_t *req) { 163 | int authorization_length = httpd_req_get_hdr_value_len(req, "Authorization") + 1; 164 | if (authorization_length == 0) goto _auth_required; 165 | 166 | char *authorization_header = malloc(authorization_length); 167 | httpd_req_get_hdr_value_str(req, "Authorization", authorization_header, authorization_length); 168 | 169 | bool authenticated = strcasecmp(basic_authentication, authorization_header) == 0; 170 | free(authorization_header); 171 | 172 | if (authenticated) return ESP_OK; 173 | 174 | _auth_required: 175 | httpd_resp_set_hdr(req, "WWW-Authenticate", "Basic realm=\"ESP32 XBee Config\""); 176 | httpd_resp_set_status(req, "401"); // Unauthorized 177 | char *unauthorized = "401 Unauthorized - Incorrect or no password provided"; 178 | httpd_resp_send(req, unauthorized, strlen(unauthorized)); 179 | return ESP_FAIL; 180 | } 181 | 182 | static esp_err_t hotspot_auth(httpd_req_t *req) { 183 | int sock = httpd_req_to_sockfd(req); 184 | 185 | struct sockaddr_in6 client_addr; 186 | socklen_t socklen = sizeof(client_addr); 187 | getpeername(sock, (struct sockaddr *)&client_addr, &socklen); 188 | 189 | // TODO: Correctly read IPv4? 190 | // ERROR_ACTION(TAG, client_addr.sin6_family != AF_INET, goto _auth_error, "IPv6 connections not supported, IP family %d", client_addr.sin6_family); 191 | 192 | wifi_sta_list_t *ap_sta_list = wifi_ap_sta_list(); 193 | esp_netif_sta_list_t esp_netif_ap_sta_list; 194 | esp_netif_get_sta_list(ap_sta_list, &esp_netif_ap_sta_list); 195 | 196 | // TODO: Correctly read IPv4? 197 | for (int i = 0; i < esp_netif_ap_sta_list.num; i++) { 198 | if (esp_netif_ap_sta_list.sta[i].ip.addr == client_addr.sin6_addr.un.u32_addr[3]) return ESP_OK; 199 | } 200 | 201 | //_auth_error: 202 | httpd_resp_set_status(req, "401"); // Unauthorized 203 | char *unauthorized = "401 Unauthorized - Configured to only accept connections from hotspot devices"; 204 | httpd_resp_send(req, unauthorized, strlen(unauthorized)); 205 | return ESP_FAIL; 206 | } 207 | 208 | static esp_err_t check_auth(httpd_req_t *req) { 209 | if (auth_method == AUTH_METHOD_HOTSPOT) return hotspot_auth(req); 210 | if (auth_method == AUTH_METHOD_BASIC) return basic_auth(req); 211 | return ESP_OK; 212 | } 213 | 214 | static esp_err_t log_get_handler(httpd_req_t *req) { 215 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 216 | 217 | httpd_resp_set_type(req, "text/plain"); 218 | 219 | size_t length; 220 | void *log_data = log_receive(&length, 1); 221 | if (log_data == NULL) { 222 | httpd_resp_sendstr(req, ""); 223 | 224 | return ESP_OK; 225 | } 226 | 227 | httpd_resp_send(req, log_data, length); 228 | 229 | log_return(log_data); 230 | 231 | return ESP_OK; 232 | } 233 | 234 | static esp_err_t core_dump_get_handler(httpd_req_t *req) { 235 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 236 | 237 | size_t core_dump_size = core_dump_available(); 238 | if (core_dump_size == 0) { 239 | httpd_resp_sendstr(req, "No core dump available"); 240 | return ESP_OK; 241 | } 242 | 243 | httpd_resp_set_type(req, "application/octet-stream"); 244 | 245 | const esp_app_desc_t *app_desc = esp_ota_get_app_description(); 246 | 247 | char elf_sha256[7]; 248 | esp_ota_get_app_elf_sha256(elf_sha256, sizeof(elf_sha256)); 249 | 250 | time_t t = time(NULL); 251 | char date[20] = "\0"; 252 | if (t > 315360000l) strftime(date, sizeof(date), "_%F_%T", localtime(&t)); 253 | 254 | char content_disposition[128]; 255 | snprintf(content_disposition, sizeof(content_disposition), 256 | "attachment; filename=\"esp32_xbee_%s_core_dump_%s%s.bin\"", app_desc->version, elf_sha256, date); 257 | httpd_resp_set_hdr(req, "Content-Disposition", content_disposition); 258 | 259 | for (int offset = 0; offset < core_dump_size; offset += BUFFER_SIZE) { 260 | size_t read = core_dump_size - offset; 261 | if (read > BUFFER_SIZE) read = BUFFER_SIZE; 262 | 263 | core_dump_read(offset, buffer, read); 264 | httpd_resp_send_chunk(req, buffer, read); 265 | } 266 | 267 | httpd_resp_send_chunk(req, NULL, 0); 268 | 269 | return ESP_OK; 270 | } 271 | 272 | static esp_err_t heap_info_get_handler(httpd_req_t *req) { 273 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 274 | 275 | multi_heap_info_t info; 276 | heap_caps_get_info(&info, MALLOC_CAP_DEFAULT); 277 | 278 | cJSON *root = cJSON_CreateObject(); 279 | 280 | cJSON_AddNumberToObject(root, "total_free_bytes", info.total_free_bytes); 281 | cJSON_AddNumberToObject(root, "total_allocated_bytes", info.total_allocated_bytes); 282 | cJSON_AddNumberToObject(root, "largest_free_block", info.largest_free_block); 283 | cJSON_AddNumberToObject(root, "minimum_free_bytes", info.minimum_free_bytes); 284 | cJSON_AddNumberToObject(root, "allocated_blocks", info.allocated_blocks); 285 | cJSON_AddNumberToObject(root, "free_blocks", info.free_blocks); 286 | cJSON_AddNumberToObject(root, "total_blocks", info.total_blocks); 287 | 288 | return json_response(req, root); 289 | } 290 | 291 | static esp_err_t file_check_etag_hash(httpd_req_t *req, char *file_hash_path, char *etag, size_t etag_size) { 292 | struct stat file_hash_stat; 293 | if (stat(file_hash_path, &file_hash_stat) == -1) { 294 | // Hash file not created yet 295 | return ESP_ERR_NOT_FOUND; 296 | } 297 | 298 | FILE *fd_hash = fopen(file_hash_path, "r+"); 299 | 300 | // Ensure hash file was opened 301 | ERROR_ACTION(TAG, fd_hash == NULL, return ESP_FAIL, 302 | "Could not open hash file %s (%lu bytes) for reading/updating: %d %s", file_hash_path, 303 | file_hash_stat.st_size, errno, strerror(errno)); 304 | 305 | // Read existing hash 306 | uint32_t crc; 307 | int read = fread(&crc, sizeof(crc), 1, fd_hash); 308 | fclose(fd_hash); 309 | ERROR_ACTION(TAG, read != 1, return ESP_FAIL, 310 | "Could not read hash file %s: %d %s", file_hash_path, 311 | errno, strerror(errno)); 312 | 313 | snprintf(etag, etag_size, "\"%08X\"", crc); 314 | 315 | // Compare to header sent by client 316 | size_t if_none_match_length = httpd_req_get_hdr_value_len(req, "If-None-Match") + 1; 317 | if (if_none_match_length > 1) { 318 | char *if_none_match = malloc(if_none_match_length); 319 | httpd_req_get_hdr_value_str(req, "If-None-Match", if_none_match, if_none_match_length); 320 | 321 | bool header_match = strcmp(etag, if_none_match) == 0; 322 | free(if_none_match); 323 | 324 | // Matching ETag, return not modified 325 | if (header_match) { 326 | return ESP_OK; 327 | } else { 328 | ESP_LOGW(TAG, "ETag for file %s sent by client does not match (%s != %s)", file_hash_path, etag, if_none_match); 329 | return ESP_ERR_INVALID_CRC; 330 | } 331 | } 332 | 333 | return ESP_ERR_INVALID_ARG; 334 | } 335 | 336 | static esp_err_t file_get_handler(httpd_req_t *req) { 337 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 338 | 339 | char file_path[FILE_PATH_MAX - strlen(FILE_HASH_SUFFIX)]; 340 | char file_hash_path[FILE_PATH_MAX]; 341 | FILE *fd = NULL, *fd_hash = NULL; 342 | struct stat file_stat; 343 | 344 | // Extract filename from URL 345 | char *file_name = get_path_from_uri(file_path, WWW_PARTITION_PATH, req->uri, sizeof(file_path)); 346 | ERROR_ACTION(TAG, file_name == NULL, { 347 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); 348 | return ESP_FAIL; 349 | }, "Filename too long") 350 | 351 | // If name has trailing '/', respond with index page 352 | if (file_name[strlen(file_name) - 1] == '/' && strlen(file_name) + strlen("index.html") < FILE_PATH_MAX) { 353 | strcpy(&file_name[strlen(file_name)], "index.html"); 354 | 355 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 356 | } 357 | 358 | set_content_type_from_file(req, file_name); 359 | 360 | // Check if file exists 361 | ERROR_ACTION(TAG, stat(file_path, &file_stat) == -1, { 362 | httpd_resp_send_404(req); 363 | return ESP_FAIL; 364 | }, "Could not stat file %s", file_path) 365 | 366 | // Check file hash (if matches request, file is not modified) 367 | strcpy(file_hash_path, file_path); 368 | strcpy(&file_hash_path[strlen(file_hash_path)], FILE_HASH_SUFFIX); 369 | char etag[8 + 2 + 1] = ""; // Store CRC32, quotes and \0 370 | if (file_check_etag_hash(req, file_hash_path, etag, sizeof(etag)) == ESP_OK) { 371 | httpd_resp_set_status(req, "304 Not Modified"); 372 | httpd_resp_send(req, NULL, 0); 373 | return ESP_OK; 374 | } 375 | 376 | if (strlen(etag) > 0) httpd_resp_set_hdr(req, "ETag", etag); 377 | 378 | fd = fopen(file_path, "r"); 379 | ERROR_ACTION(TAG, fd == NULL, { 380 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not read file"); 381 | return ESP_FAIL; 382 | }, "Could not read file %s", file_path) 383 | 384 | ESP_LOGI(TAG, "Sending file %s (%ld bytes)...", file_name, file_stat.st_size); 385 | 386 | // Retrieve the pointer to scratch buffer for temporary storage 387 | size_t length; 388 | uint32_t crc = 0; 389 | do { 390 | // Read file in chunks into the scratch buffer 391 | length = fread(buffer, 1, BUFFER_SIZE, fd); 392 | 393 | // Send the buffer contents as HTTP response chunk 394 | if (httpd_resp_send_chunk(req, buffer, length) != ESP_OK) { 395 | ESP_LOGE(TAG, "Failed sending file %s", file_name); 396 | httpd_resp_sendstr_chunk(req, NULL); 397 | 398 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); 399 | 400 | fclose(fd); 401 | return ESP_FAIL; 402 | } 403 | 404 | // Update checksum 405 | crc = crc32_le(crc, (const uint8_t *)buffer, length); 406 | } while (length != 0); 407 | 408 | // Close file after sending complete 409 | fclose(fd); 410 | 411 | // Store CRC hash 412 | fd_hash = fopen(file_hash_path, "w"); 413 | if (fd_hash != NULL) { 414 | fwrite(&crc, sizeof(crc), 1, fd_hash); 415 | fclose(fd_hash); 416 | } else { 417 | ESP_LOGW(TAG, "Could not open hash file %s for writing: %d %s", file_hash_path, errno, strerror(errno)); 418 | } 419 | 420 | return ESP_OK; 421 | } 422 | 423 | static esp_err_t config_get_handler(httpd_req_t *req) { 424 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 425 | 426 | cJSON *root = cJSON_CreateObject(); 427 | 428 | const esp_app_desc_t *app_desc = esp_ota_get_app_description(); 429 | cJSON_AddStringToObject(root, "version", app_desc->version); 430 | 431 | int config_item_count; 432 | const config_item_t *config_items = config_items_get(&config_item_count); 433 | for (int i = 0; i < config_item_count; i++) { 434 | const config_item_t *item = &config_items[i]; 435 | 436 | int64_t int64 = 0; 437 | uint64_t uint64 = 0; 438 | 439 | size_t length = 0; 440 | char *string = NULL; 441 | 442 | config_color_t color; 443 | esp_ip4_addr_t ip; 444 | 445 | switch (item->type) { 446 | case CONFIG_ITEM_TYPE_STRING: 447 | case CONFIG_ITEM_TYPE_BLOB: 448 | // Get length 449 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_str_blob(item, NULL, &length)); 450 | string = calloc(1, length + 1); 451 | 452 | // Get value 453 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_str_blob(item, string, &length)); 454 | string[length] = '\0'; 455 | break; 456 | case CONFIG_ITEM_TYPE_COLOR: 457 | // Convert to hex 458 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_primitive(item, &color)); 459 | asprintf(&string, "#%02x%02x%02x", color.values.red, color.values.green, color.values.blue); 460 | break; 461 | case CONFIG_ITEM_TYPE_IP: 462 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_primitive(item, &ip)); 463 | cJSON *ip_parts = cJSON_AddArrayToObject(root, item->key); 464 | for (int b = 0; b < 4; b++) { 465 | cJSON_AddItemToArray(ip_parts, cJSON_CreateNumber(esp_ip4_addr_get_byte(&ip, b))); 466 | } 467 | 468 | break; 469 | case CONFIG_ITEM_TYPE_UINT8: 470 | case CONFIG_ITEM_TYPE_UINT16: 471 | case CONFIG_ITEM_TYPE_UINT32: 472 | case CONFIG_ITEM_TYPE_UINT64: 473 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_primitive(item, &uint64)); 474 | asprintf(&string, "%llu", uint64); 475 | break; 476 | case CONFIG_ITEM_TYPE_BOOL: 477 | case CONFIG_ITEM_TYPE_INT8: 478 | case CONFIG_ITEM_TYPE_INT16: 479 | case CONFIG_ITEM_TYPE_INT32: 480 | case CONFIG_ITEM_TYPE_INT64: 481 | ESP_ERROR_CHECK_WITHOUT_ABORT(config_get_primitive(item, &int64)); 482 | asprintf(&string, "%lld", int64); 483 | break; 484 | default: 485 | string = calloc(1, 1); 486 | break; 487 | } 488 | 489 | if (string != NULL) { 490 | // Hide secret values that aren't empty 491 | char *value = item->secret && strlen(string) > 0 ? CONFIG_VALUE_UNCHANGED : string; 492 | cJSON_AddStringToObject(root, item->key, value); 493 | 494 | free(string); 495 | } 496 | } 497 | 498 | return json_response(req, root); 499 | } 500 | 501 | static esp_err_t config_post_handler(httpd_req_t *req) { 502 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 503 | 504 | int ret = httpd_req_recv(req, buffer, BUFFER_SIZE - 1); 505 | if (ret <= 0) { 506 | if (ret == HTTPD_SOCK_ERR_TIMEOUT) { 507 | httpd_resp_send_408(req); 508 | } 509 | 510 | return ESP_FAIL; 511 | } 512 | 513 | buffer[ret] = '\0'; 514 | 515 | cJSON *root = cJSON_Parse(buffer); 516 | 517 | int config_item_count; 518 | const config_item_t *config_items = config_items_get(&config_item_count); 519 | for (int i = 0; i < config_item_count; i++) { 520 | config_item_t item = config_items[i]; 521 | 522 | if (cJSON_HasObjectItem(root, item.key)) { 523 | cJSON *entry = cJSON_GetObjectItem(root, item.key); 524 | 525 | size_t length = 0; 526 | if (cJSON_IsString(entry)) { 527 | length = strlen(entry->valuestring); 528 | 529 | // Ignore empty primitives 530 | if (length == 0 && item.type != CONFIG_ITEM_TYPE_BLOB && item.type != CONFIG_ITEM_TYPE_STRING) continue; 531 | 532 | // Ignore unchanged values 533 | if (strcmp(entry->valuestring, CONFIG_VALUE_UNCHANGED) == 0) continue; 534 | } 535 | 536 | // TODO: Cleanup 537 | esp_err_t err; 538 | if (item.type > CONFIG_ITEM_TYPE_MAX) { 539 | err = ESP_ERR_INVALID_ARG; 540 | } else if (item.type == CONFIG_ITEM_TYPE_STRING) { 541 | err = config_set_str(item.key, entry->valuestring); 542 | } else if (item.type == CONFIG_ITEM_TYPE_BLOB) { 543 | err = config_set_blob(item.key, entry->valuestring, length); 544 | } else if (item.type == CONFIG_ITEM_TYPE_COLOR) { 545 | bool is_black = strcmp(entry->valuestring, "#000000") == 0; 546 | config_color_t color; 547 | color.rgba = strtoul(entry->valuestring + 1, NULL, 16) << 8u; 548 | 549 | if (!is_black && color.rgba == 0) { 550 | err = ESP_ERR_INVALID_ARG; 551 | } else { 552 | // Set alpha to default 553 | if (!is_black) color.values.alpha = item.def.color.values.alpha; 554 | 555 | err = config_set_color(item.key, color); 556 | } 557 | } else if (item.type == CONFIG_ITEM_TYPE_IP) { 558 | uint8_t a[4]; 559 | 560 | if (!cJSON_IsArray(entry) || cJSON_GetArraySize(entry) != 4) { 561 | err = ESP_ERR_INVALID_ARG; 562 | } else { 563 | for (int b = 0; b < 4; b++) { 564 | a[b] = (uint8_t) strtoul(cJSON_GetArrayItem(entry, b)->valuestring, NULL, 10); 565 | } 566 | ; 567 | uint32_t ip = esp_netif_htonl(esp_netif_ip4_makeu32(a[0], a[1], a[2], a[3])); 568 | err = config_set_u32(item.key, ip); 569 | } 570 | } else { 571 | bool is_zero = strcmp(entry->valuestring, "0") == 0 || strcmp(entry->valuestring, "0.0") == 0; 572 | int64_t int64 = strtol(entry->valuestring, NULL, 10); 573 | uint64_t uint64 = strtoul(entry->valuestring, NULL, 10); 574 | 575 | if (!is_zero && (int64 == 0 || uint64 == 0)) { 576 | err = ESP_ERR_INVALID_ARG; 577 | } else { 578 | switch (item.type) { 579 | case CONFIG_ITEM_TYPE_BOOL: 580 | case CONFIG_ITEM_TYPE_INT8: 581 | case CONFIG_ITEM_TYPE_INT16: 582 | case CONFIG_ITEM_TYPE_INT32: 583 | case CONFIG_ITEM_TYPE_INT64: 584 | err = config_set(&item, &int64); 585 | break; 586 | case CONFIG_ITEM_TYPE_UINT8: 587 | case CONFIG_ITEM_TYPE_UINT16: 588 | case CONFIG_ITEM_TYPE_UINT32: 589 | case CONFIG_ITEM_TYPE_UINT64: 590 | err = config_set(&item, &uint64); 591 | break; 592 | default: 593 | err = ESP_FAIL; 594 | break; 595 | } 596 | } 597 | } 598 | 599 | if (err != ESP_OK) { 600 | ESP_LOGE(TAG, "Error setting %s = %s: %d - %s", item.key, entry->valuestring, err, esp_err_to_name(err)); 601 | } 602 | } 603 | } 604 | 605 | cJSON_Delete(root); 606 | 607 | config_commit(); 608 | config_restart(); 609 | 610 | root = cJSON_CreateObject(); 611 | cJSON_AddBoolToObject(root, "success", true); 612 | 613 | return json_response(req, root); 614 | } 615 | 616 | static esp_err_t status_get_handler(httpd_req_t *req) { 617 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 618 | 619 | cJSON *root = cJSON_CreateObject(); 620 | 621 | // Uptime 622 | cJSON_AddNumberToObject(root, "uptime", (int) ((double) esp_timer_get_time() / 1000000)); 623 | 624 | // Heap 625 | cJSON *heap = cJSON_AddObjectToObject(root, "heap"); 626 | cJSON_AddNumberToObject(heap, "total", heap_caps_get_total_size(MALLOC_CAP_8BIT)); 627 | cJSON_AddNumberToObject(heap, "free", heap_caps_get_free_size(MALLOC_CAP_8BIT)); 628 | 629 | // Streams 630 | cJSON *streams = cJSON_AddObjectToObject(root, "streams"); 631 | stream_stats_values_t values; 632 | for (stream_stats_handle_t stats = stream_stats_first(); stats != NULL; stats = stream_stats_next(stats)) { 633 | stream_stats_values(stats, &values); 634 | 635 | cJSON *stream = cJSON_AddObjectToObject(streams, values.name); 636 | cJSON *total = cJSON_AddObjectToObject(stream, "total"); 637 | cJSON_AddNumberToObject(total, "in", values.total_in); 638 | cJSON_AddNumberToObject(total, "out", values.total_out); 639 | cJSON *rate = cJSON_AddObjectToObject(stream, "rate"); 640 | cJSON_AddNumberToObject(rate, "in", values.rate_in); 641 | cJSON_AddNumberToObject(rate, "out", values.rate_out); 642 | } 643 | 644 | // Sockets 645 | cJSON *sockets = cJSON_AddArrayToObject(root, "sockets"); 646 | for (int s = LWIP_SOCKET_OFFSET; s < LWIP_SOCKET_OFFSET + CONFIG_LWIP_MAX_SOCKETS; s++) { 647 | int err; 648 | 649 | int socktype; 650 | socklen_t socktype_len = sizeof(socktype); 651 | err = getsockopt(s, SOL_SOCKET, SO_TYPE, &socktype, &socktype_len); 652 | if (err < 0) continue; 653 | 654 | cJSON *socket = cJSON_CreateObject(); 655 | 656 | cJSON_AddStringToObject(socket, "type", SOCKTYPE_NAME(socktype)); 657 | 658 | struct sockaddr_in6 addr; 659 | socklen_t socklen = sizeof(addr); 660 | 661 | err = getsockname(s, (struct sockaddr *)&addr, &socklen); 662 | if (err == 0) cJSON_AddStringToObject(socket, "local", sockaddrtostr((struct sockaddr *) &addr)); 663 | 664 | err = getpeername(s, (struct sockaddr *)&addr, &socklen); 665 | if (err == 0) cJSON_AddStringToObject(socket, "peer", sockaddrtostr((struct sockaddr *) &addr)); 666 | 667 | cJSON_AddItemToArray(sockets, socket); 668 | } 669 | 670 | // WiFi 671 | wifi_ap_status_t ap_status; 672 | wifi_sta_status_t sta_status; 673 | 674 | wifi_ap_status(&ap_status); 675 | wifi_sta_status(&sta_status); 676 | 677 | cJSON *wifi = cJSON_AddObjectToObject(root, "wifi"); 678 | 679 | cJSON *ap = cJSON_AddObjectToObject(wifi, "ap"); 680 | cJSON_AddBoolToObject(ap, "active", ap_status.active); 681 | if (ap_status.active) { 682 | cJSON_AddStringToObject(ap, "ssid", (char *) ap_status.ssid); 683 | cJSON_AddStringToObject(ap, "authmode", wifi_auth_mode_name(ap_status.authmode)); 684 | cJSON_AddNumberToObject(ap, "devices", ap_status.devices); 685 | 686 | char ip[40]; 687 | snprintf(ip, sizeof(ip), IPSTR, IP2STR(&ap_status.ip4_addr)); 688 | cJSON_AddStringToObject(ap, "ip4", ip); 689 | snprintf(ip, sizeof(ip), IPV6STR, IPV62STR(ap_status.ip6_addr)); 690 | cJSON_AddStringToObject(ap, "ip6", ip); 691 | } 692 | 693 | cJSON *sta = cJSON_AddObjectToObject(wifi, "sta"); 694 | cJSON_AddBoolToObject(sta, "active", ap_status.active); 695 | if (sta_status.active) { 696 | cJSON_AddBoolToObject(sta, "connected", sta_status.connected); 697 | if (sta_status.connected) { 698 | cJSON_AddStringToObject(sta, "ssid", (char *) sta_status.ssid); 699 | cJSON_AddStringToObject(sta, "authmode", wifi_auth_mode_name(sta_status.authmode)); 700 | cJSON_AddNumberToObject(sta, "rssi", sta_status.rssi); 701 | 702 | char ip[40]; 703 | snprintf(ip, sizeof(ip), IPSTR, IP2STR(&sta_status.ip4_addr)); 704 | cJSON_AddStringToObject(sta, "ip4", ip); 705 | snprintf(ip, sizeof(ip), IPV6STR, IPV62STR(sta_status.ip6_addr)); 706 | cJSON_AddStringToObject(sta, "ip6", ip); 707 | } 708 | } 709 | 710 | return json_response(req, root); 711 | } 712 | 713 | static esp_err_t wifi_scan_get_handler(httpd_req_t *req) { 714 | if (check_auth(req) == ESP_FAIL) return ESP_FAIL; 715 | 716 | uint16_t ap_count; 717 | wifi_ap_record_t *ap_records = wifi_scan(&ap_count); 718 | 719 | cJSON *root = cJSON_CreateArray(); 720 | for (int i = 0; i < ap_count; i++) { 721 | wifi_ap_record_t *ap_record = &ap_records[i]; 722 | cJSON *ap = cJSON_CreateObject(); 723 | cJSON_AddItemToArray(root, ap); 724 | cJSON_AddStringToObject(ap, "ssid", (char *) ap_record->ssid); 725 | cJSON_AddNumberToObject(ap, "rssi", ap_record->rssi); 726 | cJSON_AddStringToObject(ap, "authmode", wifi_auth_mode_name(ap_record->authmode)); 727 | } 728 | 729 | free(ap_records); 730 | 731 | return json_response(req, root); 732 | } 733 | 734 | static esp_err_t register_uri_handler(httpd_handle_t server, const char *path, httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r)) { 735 | httpd_uri_t uri_config_get = { 736 | .uri = path, 737 | .method = method, 738 | .handler = handler 739 | }; 740 | return httpd_register_uri_handler(server, &uri_config_get); 741 | } 742 | 743 | static httpd_handle_t web_server_start(void) 744 | { 745 | config_get_primitive(CONF_ITEM(KEY_CONFIG_ADMIN_AUTH), &auth_method); 746 | if (auth_method == AUTH_METHOD_BASIC) { 747 | char *username, *password; 748 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_ADMIN_USERNAME), (void **) &username); 749 | config_get_str_blob_alloc(CONF_ITEM(KEY_CONFIG_ADMIN_PASSWORD), (void **) &password); 750 | basic_authentication = http_auth_basic_header(username, password); 751 | free(username); 752 | free(password); 753 | } 754 | 755 | httpd_handle_t server = NULL; 756 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 757 | config.uri_match_fn = httpd_uri_match_wildcard; 758 | 759 | // Start the httpd server 760 | ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); 761 | if (httpd_start(&server, &config) == ESP_OK) { 762 | register_uri_handler(server, "/config", HTTP_GET, config_get_handler); 763 | register_uri_handler(server, "/config", HTTP_POST, config_post_handler); 764 | register_uri_handler(server, "/status", HTTP_GET, status_get_handler); 765 | 766 | register_uri_handler(server, "/log", HTTP_GET, log_get_handler); 767 | register_uri_handler(server, "/core_dump", HTTP_GET, core_dump_get_handler); 768 | register_uri_handler(server, "/heap_info", HTTP_GET, heap_info_get_handler); 769 | 770 | register_uri_handler(server, "/wifi/scan", HTTP_GET, wifi_scan_get_handler); 771 | 772 | register_uri_handler(server, "/*", HTTP_GET, file_get_handler); 773 | } 774 | 775 | if (server == NULL) { 776 | ESP_LOGE(TAG, "Could not start server"); 777 | return NULL; 778 | } 779 | 780 | buffer = malloc(BUFFER_SIZE); 781 | 782 | return server; 783 | } 784 | 785 | void web_server_init() { 786 | www_spiffs_init(); 787 | web_server_start(); 788 | } -------------------------------------------------------------------------------- /main/wifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-XBee distribution (https://github.com/nebkat/esp32-xbee). 3 | * Copyright (c) 2019 Nebojsa Cvetkovic. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "wifi.h" 34 | #include "config.h" 35 | 36 | static const char *TAG = "WIFI"; 37 | 38 | static EventGroupHandle_t wifi_event_group; 39 | const int WIFI_STA_GOT_IPV4_BIT = BIT0; 40 | const int WIFI_STA_GOT_IPV6_BIT = BIT1; 41 | const int WIFI_AP_STA_CONNECTED_BIT = BIT2; 42 | 43 | static TaskHandle_t sta_status_task = NULL; 44 | static TaskHandle_t sta_reconnect_task = NULL; 45 | 46 | static status_led_handle_t status_led_ap; 47 | static status_led_handle_t status_led_sta; 48 | 49 | static wifi_config_t config_ap; 50 | static wifi_config_t config_sta; 51 | 52 | static retry_delay_handle_t delay_handle; 53 | 54 | static bool ap_active = false; 55 | static bool sta_active = false; 56 | 57 | static bool sta_connected; 58 | static wifi_ap_record_t sta_ap_info; 59 | static wifi_sta_list_t ap_sta_list; 60 | 61 | static esp_netif_t *esp_netif_ap; 62 | static esp_netif_t *esp_netif_sta; 63 | 64 | static void wifi_sta_status_task(void *ctx) { 65 | uint8_t rssi_duty = 0; 66 | while (true) { 67 | sta_connected = esp_wifi_sta_get_ap_info(&sta_ap_info) == ESP_OK; 68 | 69 | if (sta_connected) { 70 | float rssi_percentage = ((float) sta_ap_info.rssi + 90.0f) / 60.0f; 71 | rssi_percentage = MAX(0, MIN(1, rssi_percentage)); 72 | rssi_duty = powf(rssi_percentage, 3) * 255; 73 | } else { 74 | rssi_duty = 0; 75 | } 76 | 77 | rssi_led_fade(rssi_duty, 100); 78 | 79 | vTaskDelay(pdMS_TO_TICKS(1000)); 80 | } 81 | } 82 | 83 | static void wifi_sta_reconnect_task(void *ctx) { 84 | while (true) { 85 | int attempts = retry_delay(delay_handle); 86 | 87 | ESP_LOGI(TAG, "Station Reconnecting: %s, attempts: %d", config_sta.sta.ssid, attempts); 88 | uart_nmea("$PESP,WIFI,STA,RECONNECTING,%s,%d", config_sta.sta.ssid, attempts); 89 | 90 | esp_wifi_connect(); 91 | 92 | // Wait for next disconnect event 93 | vTaskSuspend(NULL); 94 | } 95 | } 96 | 97 | static void handle_sta_start(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 98 | ESP_LOGI(TAG, "WIFI_EVENT_STA_START"); 99 | 100 | sta_active = true; 101 | 102 | esp_wifi_connect(); 103 | } 104 | 105 | static void handle_sta_stop(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 106 | ESP_LOGI(TAG, "WIFI_EVENT_STA_STOP"); 107 | 108 | sta_active = false; 109 | } 110 | 111 | static void handle_sta_connected(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 112 | const wifi_event_sta_connected_t *event = (const wifi_event_sta_connected_t *) event_data; 113 | 114 | ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED: ssid: %.*s", event->ssid_len, event->ssid); 115 | uart_nmea("$PESP,WIFI,STA,CONNECTED,%.*s", event->ssid_len, event->ssid); 116 | 117 | sta_connected = true; 118 | 119 | retry_reset(delay_handle); 120 | 121 | // Tracking status 122 | if (sta_status_task != NULL) vTaskResume(sta_status_task); 123 | 124 | // No longer attempting to reconnect 125 | if (sta_reconnect_task != NULL) vTaskSuspend(sta_reconnect_task); 126 | 127 | if (status_led_sta != NULL) status_led_sta->flashing_mode = STATUS_LED_FADE; 128 | } 129 | 130 | static void handle_sta_disconnected(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 131 | const wifi_event_sta_disconnected_t *event = (const wifi_event_sta_disconnected_t *) event_data; 132 | char *reason; 133 | switch (event->reason) { 134 | case WIFI_REASON_AUTH_EXPIRE: 135 | case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: 136 | case WIFI_REASON_AUTH_FAIL: 137 | case WIFI_REASON_ASSOC_EXPIRE: 138 | case WIFI_REASON_HANDSHAKE_TIMEOUT: 139 | reason = "AUTH"; 140 | break; 141 | case WIFI_REASON_NO_AP_FOUND: 142 | reason = "NOT_FOUND"; 143 | break; 144 | default: 145 | reason = "UNKNOWN"; 146 | } 147 | 148 | ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED: ssid: %.*s, reason: %d (%s)", event->ssid_len, event->ssid, event->reason, reason); 149 | uart_nmea("$PESP,WIFI,STA,DISCONNECTED,%.*s,%d,%s", event->ssid_len, event->ssid, event->reason, reason); 150 | 151 | sta_connected = false; 152 | 153 | // No longer tracking status 154 | if (sta_status_task != NULL) vTaskSuspend(sta_status_task); 155 | 156 | // Attempting to reconnect 157 | if (sta_reconnect_task != NULL) vTaskResume(sta_reconnect_task); 158 | 159 | // Disable RSSI led 160 | rssi_led_set(0); 161 | 162 | xEventGroupClearBits(wifi_event_group, WIFI_STA_GOT_IPV4_BIT); 163 | xEventGroupClearBits(wifi_event_group, WIFI_STA_GOT_IPV6_BIT); 164 | 165 | if (status_led_sta != NULL) status_led_sta->flashing_mode = STATUS_LED_STATIC; 166 | } 167 | 168 | static void handle_sta_auth_mode_change(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 169 | const wifi_event_sta_authmode_change_t *event = (const wifi_event_sta_authmode_change_t *) event_data; 170 | const char *old_auth_mode = wifi_auth_mode_name(event->old_mode); 171 | const char *new_auth_mode = wifi_auth_mode_name(event->new_mode); 172 | 173 | ESP_LOGI(TAG, "WIFI_EVENT_STA_AUTHMODE_CHANGE: old: %s, new: %s", old_auth_mode, new_auth_mode); 174 | uart_nmea("$PESP,WIFI,STA,AUTH_MODE_CHANGED,%s,%s", old_auth_mode, new_auth_mode); 175 | } 176 | 177 | static void handle_ap_start(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 178 | ESP_LOGI(TAG, "WIFI_EVENT_AP_START"); 179 | 180 | // IP forwarding/NATP 181 | if (config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_AP_FORWARD))) { 182 | esp_netif_ip_info_t ip_info_ap; 183 | esp_netif_get_ip_info(esp_netif_ap, &ip_info_ap); 184 | ip_napt_enable(ip_info_ap.ip.addr, 1); 185 | } 186 | 187 | ap_active = true; 188 | } 189 | 190 | static void handle_ap_stop(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 191 | ESP_LOGI(TAG, "WIFI_EVENT_AP_STOP"); 192 | 193 | ap_active = false; 194 | } 195 | 196 | static void handle_ap_sta_connected(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 197 | const wifi_event_ap_staconnected_t *event = (const wifi_event_ap_staconnected_t *) event_data; 198 | 199 | ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED: mac: " MACSTR, MAC2STR(event->mac)); 200 | uart_nmea("$PESP,WIFI,AP,STA_CONNECTED," MACSTR, MAC2STR(event->mac)); 201 | 202 | xEventGroupSetBits(wifi_event_group, WIFI_AP_STA_CONNECTED_BIT); 203 | 204 | if (status_led_ap != NULL) status_led_ap->flashing_mode = STATUS_LED_FADE; 205 | } 206 | 207 | static void handle_ap_sta_disconnected(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 208 | const wifi_event_ap_stadisconnected_t *event = (const wifi_event_ap_stadisconnected_t *) event_data; 209 | 210 | ESP_LOGI(TAG, "WIFI_EVENT_AP_STADISCONNECTED: mac: " MACSTR, MAC2STR(event->mac)); 211 | uart_nmea("$PESP,WIFI,AP,STA_DISCONNECTED," MACSTR, MAC2STR(event->mac)); 212 | 213 | wifi_ap_sta_list(); 214 | if (ap_sta_list.num == 0) { 215 | xEventGroupClearBits(wifi_event_group, WIFI_AP_STA_CONNECTED_BIT); 216 | 217 | if (status_led_ap != NULL) status_led_ap->flashing_mode = STATUS_LED_STATIC; 218 | } 219 | } 220 | 221 | static void handle_sta_got_ip(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 222 | const ip_event_got_ip_t *event = (const ip_event_got_ip_t *) event_data; 223 | 224 | // IP forwarding/NATP update AP DHCPS DNS info 225 | if (ap_active & config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_AP_FORWARD))) { 226 | esp_netif_dns_info_t dns_info_sta; 227 | ESP_ERROR_CHECK(esp_netif_get_dns_info(esp_netif_sta, ESP_NETIF_DNS_MAIN, &dns_info_sta)); 228 | 229 | ESP_ERROR_CHECK(esp_netif_dhcps_stop(esp_netif_ap)); 230 | ESP_ERROR_CHECK(esp_netif_set_dns_info(esp_netif_ap, ESP_NETIF_DNS_MAIN, &dns_info_sta)); 231 | ESP_ERROR_CHECK(esp_netif_dhcps_start(esp_netif_ap)); 232 | } 233 | 234 | ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP: ip: " IPSTR "/%d, gw: " IPSTR, 235 | IP2STR(&event->ip_info.ip), 236 | ffs(~event->ip_info.netmask.addr) - 1, 237 | IP2STR(&event->ip_info.gw)); 238 | uart_nmea("$PESP,WIFI,STA,IP," IPSTR "/%d," IPSTR, 239 | IP2STR(&event->ip_info.ip), 240 | ffs(~event->ip_info.netmask.addr) - 1, 241 | IP2STR(&event->ip_info.gw)); 242 | 243 | xEventGroupSetBits(wifi_event_group, WIFI_STA_GOT_IPV4_BIT); 244 | } 245 | 246 | static void handle_sta_lost_ip(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 247 | ESP_LOGI(TAG, "IP_EVENT_STA_LOST_IP"); 248 | uart_nmea("$PESP,WIFI,STA,IP_LOST"); 249 | 250 | xEventGroupClearBits(wifi_event_group, WIFI_STA_GOT_IPV4_BIT); 251 | } 252 | 253 | static void handle_ap_sta_ip_assigned(void *esp_netif, esp_event_base_t base, int32_t event_id, void *event_data) { 254 | const ip_event_ap_staipassigned_t *event = (const ip_event_ap_staipassigned_t *) event_data; 255 | 256 | ESP_LOGI(TAG, "IP_EVENT_AP_STAIPASSIGNED: ip: " IPSTR, IP2STR(&event->ip)); 257 | uart_nmea("$PESP,WIFI,AP,STA_IP_ASSIGNED," IPSTR, IP2STR(&event->ip)); 258 | } 259 | 260 | void wait_for_ip() { 261 | xEventGroupWaitBits(wifi_event_group, WIFI_STA_GOT_IPV4_BIT, false, false, portMAX_DELAY); 262 | } 263 | 264 | void wait_for_network() { 265 | xEventGroupWaitBits(wifi_event_group, WIFI_STA_GOT_IPV4_BIT | WIFI_AP_STA_CONNECTED_BIT, false, false, portMAX_DELAY); 266 | } 267 | 268 | void net_init() { 269 | esp_netif_init(); 270 | 271 | // SoftAP 272 | bool ap_enable = config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_AP_ACTIVE)); 273 | if (ap_enable) { 274 | esp_netif_ap = esp_netif_create_default_wifi_ap(); 275 | 276 | // IP configuration 277 | esp_netif_ip_info_t ip_info_ap; 278 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_AP_GATEWAY), &ip_info_ap.ip); 279 | ip_info_ap.gw = ip_info_ap.ip; 280 | uint8_t subnet = config_get_u8(CONF_ITEM(KEY_CONFIG_WIFI_STA_SUBNET)); 281 | ip_info_ap.netmask.addr = esp_netif_htonl(0xffffffffu << (32u - subnet)); 282 | 283 | // IP forwarding/NATP 284 | if (config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_AP_FORWARD))) { 285 | uint8_t dhcps_offer = true; 286 | ESP_ERROR_CHECK(esp_netif_dhcps_option(esp_netif_ap, ESP_NETIF_OP_SET, ESP_NETIF_DOMAIN_NAME_SERVER, &dhcps_offer, 1)); 287 | } 288 | 289 | ESP_ERROR_CHECK(esp_netif_dhcps_stop(esp_netif_ap)); 290 | ESP_ERROR_CHECK(esp_netif_set_ip_info(esp_netif_ap, &ip_info_ap)); 291 | ESP_ERROR_CHECK(esp_netif_dhcps_start(esp_netif_ap)); 292 | } 293 | 294 | // STA 295 | bool sta_enable = config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_ACTIVE)); 296 | if (sta_enable) { 297 | esp_netif_ip_info_t ip_info_sta; 298 | esp_netif_sta = esp_netif_create_default_wifi_sta(); 299 | 300 | // Static IP configuration 301 | if (config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_STATIC))) { 302 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_STA_IP), &ip_info_sta.ip); 303 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_STA_GATEWAY), &ip_info_sta.gw); 304 | uint8_t subnet = config_get_u8(CONF_ITEM(KEY_CONFIG_WIFI_STA_SUBNET)); 305 | ip_info_sta.netmask.addr = esp_netif_htonl(0xffffffffu << (32u - subnet)); 306 | 307 | esp_netif_dns_info_t dns_info_sta_main, dns_info_sta_backup; 308 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_STA_DNS_A), &dns_info_sta_main.ip.u_addr.ip4.addr); 309 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_STA_DNS_B), &dns_info_sta_backup.ip.u_addr.ip4.addr); 310 | 311 | ESP_ERROR_CHECK(esp_netif_dhcpc_stop(esp_netif_sta)); 312 | ESP_ERROR_CHECK(esp_netif_set_ip_info(esp_netif_sta, &ip_info_sta)); 313 | ESP_ERROR_CHECK(esp_netif_set_dns_info(esp_netif_sta, ESP_NETIF_DNS_MAIN, &dns_info_sta_main)); 314 | ESP_ERROR_CHECK(esp_netif_set_dns_info(esp_netif_sta, ESP_NETIF_DNS_BACKUP, &dns_info_sta_backup)); 315 | ESP_ERROR_CHECK(esp_netif_dhcpc_start(esp_netif_sta)); 316 | } 317 | } 318 | } 319 | 320 | void wifi_init() { 321 | wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); 322 | ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); 323 | 324 | wifi_event_group = xEventGroupCreate(); 325 | 326 | // Listen for WiFi and IP events 327 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, &handle_sta_start, NULL)); 328 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_STOP, &handle_sta_stop, NULL)); 329 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &handle_sta_connected, NULL)); 330 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &handle_sta_disconnected, NULL)); 331 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_AUTHMODE_CHANGE, &handle_sta_auth_mode_change, NULL)); 332 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_START, &handle_ap_start, NULL)); 333 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STOP, &handle_ap_stop, NULL)); 334 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STACONNECTED, &handle_ap_sta_connected, NULL)); 335 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, &handle_ap_sta_disconnected, NULL)); 336 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &handle_sta_got_ip, NULL)); 337 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, &handle_sta_lost_ip, NULL)); 338 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, &handle_ap_sta_ip_assigned, NULL)); 339 | 340 | // Reconnect delay timer 341 | delay_handle = retry_init(true, 5, 2000, 60000); 342 | 343 | bool sta_enable = config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_ACTIVE)); 344 | bool ap_enable = config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_AP_ACTIVE)); 345 | 346 | // Configure and connect 347 | wifi_mode_t wifi_mode; 348 | if (sta_enable && ap_enable) { 349 | wifi_mode = WIFI_MODE_APSTA; 350 | } else if (ap_enable) { 351 | wifi_mode = WIFI_MODE_AP; 352 | } else if (sta_enable) { 353 | wifi_mode = WIFI_MODE_STA; 354 | } else { 355 | return; 356 | } 357 | 358 | ESP_ERROR_CHECK(esp_wifi_set_mode(wifi_mode)); 359 | ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); 360 | 361 | // SoftAP 362 | if (ap_enable) { 363 | esp_netif_ip_info_t ip_info_ap; 364 | esp_netif_get_ip_info(esp_netif_ap, &ip_info_ap); 365 | 366 | config_ap.ap.max_connection = 4; 367 | size_t ap_ssid_len = sizeof(config_ap.ap.ssid); 368 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_WIFI_AP_SSID), &config_ap.ap.ssid, &ap_ssid_len); 369 | ap_ssid_len--; // Remove null terminator from length 370 | config_ap.ap.ssid_len = ap_ssid_len; 371 | if (ap_ssid_len == 0) { 372 | // Generate a default AP SSID based on MAC address and store 373 | uint8_t mac[6]; 374 | esp_wifi_get_mac(WIFI_IF_AP, mac); 375 | snprintf((char *) config_ap.ap.ssid, sizeof(config_ap.ap.ssid), "ESP_XBee_%02X%02X%02X", 376 | mac[3], mac[4], mac[5]); 377 | config_ap.ap.ssid_len = strlen((char *) config_ap.ap.ssid); 378 | 379 | config_set_str(KEY_CONFIG_WIFI_AP_SSID, (char *) config_ap.ap.ssid); 380 | } 381 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_AP_SSID_HIDDEN), &config_ap.ap.ssid_hidden); 382 | size_t ap_password_len = sizeof(config_ap.ap.password); 383 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_WIFI_AP_PASSWORD), &config_ap.ap.password, &ap_password_len); 384 | ap_password_len--; // Remove null terminator from length 385 | config_get_primitive(CONF_ITEM(KEY_CONFIG_WIFI_AP_AUTH_MODE), &config_ap.ap.authmode); 386 | 387 | ESP_LOGI(TAG, "WIFI_AP_SSID: %s %s(%s)", config_ap.ap.ssid, 388 | config_ap.ap.ssid_hidden ? "(hidden) " : "", 389 | ap_password_len == 0 ? "open" : "with password"); 390 | uart_nmea("$PESP,WIFI,AP,SSID,%s,%c,%c", config_ap.ap.ssid, 391 | config_ap.ap.ssid_hidden ? 'H' : 'V', 392 | ap_password_len == 0 ? 'O' : 'P'); 393 | 394 | ESP_LOGI(TAG, "WIFI_AP_IP: ip: " IPSTR "/%d, gw: " IPSTR, 395 | IP2STR(&ip_info_ap.ip), 396 | ffs(~ip_info_ap.netmask.addr) - 1, 397 | IP2STR(&ip_info_ap.gw)); 398 | uart_nmea("$PESP,WIFI,AP,IP," IPSTR "/%d", 399 | IP2STR(&ip_info_ap.ip), 400 | ffs(~ip_info_ap.netmask.addr) - 1); 401 | 402 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &config_ap)); 403 | ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_AP, WIFI_BW_HT20)); 404 | 405 | config_color_t ap_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_WIFI_AP_COLOR)); 406 | if (ap_led_color.rgba != 0) status_led_ap = status_led_add(ap_led_color.rgba, STATUS_LED_STATIC, 500, 2000, 0); 407 | } 408 | 409 | // STA 410 | if (sta_enable) { 411 | size_t sta_ssid_len = sizeof(config_sta.sta.ssid); 412 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_WIFI_STA_SSID), &config_sta.sta.ssid, &sta_ssid_len); 413 | sta_ssid_len--; // Remove null terminator from length 414 | if (sta_ssid_len == 0) sta_enable = false; 415 | size_t sta_password_len = sizeof(config_sta.sta.password); 416 | config_get_str_blob(CONF_ITEM(KEY_CONFIG_WIFI_STA_PASSWORD), &config_sta.sta.password, &sta_password_len); 417 | sta_password_len--; // Remove null terminator from length 418 | config_sta.sta.scan_method = config_get_bool1(CONF_ITEM(KEY_CONFIG_WIFI_STA_SCAN_MODE_ALL)) 419 | ? WIFI_ALL_CHANNEL_SCAN : WIFI_FAST_SCAN; 420 | 421 | ESP_LOGI(TAG, "WIFI_STA_CONNECTING: %s (%s), %s scan", config_sta.sta.ssid, 422 | sta_password_len == 0 ? "open" : "with password", 423 | config_sta.sta.scan_method == WIFI_ALL_CHANNEL_SCAN ? "all channel" : "fast"); 424 | uart_nmea("$PESP,WIFI,STA,CONNECTING,%s,%c,%c", config_sta.sta.ssid, 425 | sta_password_len == 0 ? 'O' : 'P', 426 | config_sta.sta.scan_method == WIFI_ALL_CHANNEL_SCAN ? 'A' : 'F'); 427 | 428 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &config_sta)); 429 | ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20)); 430 | 431 | // Keep track of connection for RSSI indicator, but suspend until connected 432 | xTaskCreate(wifi_sta_status_task, "wifi_sta_status", 2048, NULL, TASK_PRIORITY_WIFI_STATUS, &sta_status_task); 433 | vTaskSuspend(sta_status_task); 434 | 435 | // Reconnect when disconnected 436 | xTaskCreate(wifi_sta_reconnect_task, "wifi_sta_reconnect", 4096, NULL, TASK_PRIORITY_WIFI_STATUS, &sta_reconnect_task); 437 | vTaskSuspend(sta_reconnect_task); 438 | 439 | config_color_t sta_led_color = config_get_color(CONF_ITEM(KEY_CONFIG_WIFI_STA_COLOR)); 440 | if (sta_led_color.rgba != 0) status_led_sta = status_led_add(sta_led_color.rgba, STATUS_LED_STATIC, 500, 2000, 0); 441 | } 442 | 443 | ESP_ERROR_CHECK(esp_wifi_start()); 444 | } 445 | 446 | wifi_sta_list_t *wifi_ap_sta_list() { 447 | esp_wifi_ap_get_sta_list(&ap_sta_list); 448 | return &ap_sta_list; 449 | } 450 | 451 | void wifi_ap_status(wifi_ap_status_t *status) { 452 | status->active = ap_active; 453 | if (!ap_active) return; 454 | 455 | memcpy(status->ssid, config_ap.ap.ssid, sizeof(config_ap.ap.ssid)); 456 | status->authmode = config_ap.ap.authmode; 457 | 458 | wifi_ap_sta_list(); 459 | status->devices = ap_sta_list.num; 460 | 461 | 462 | esp_netif_ip_info_t ip_info; 463 | esp_netif_get_ip_info(esp_netif_ap, &ip_info); 464 | status->ip4_addr = ip_info.ip; 465 | 466 | esp_netif_get_ip6_linklocal(esp_netif_ap, &status->ip6_addr); 467 | } 468 | 469 | void wifi_sta_status(wifi_sta_status_t *status) { 470 | status->active = sta_active; 471 | status->connected = sta_connected; 472 | if (!sta_connected) { 473 | memcpy(status->ssid, config_sta.sta.ssid, sizeof(config_sta.sta.ssid)); 474 | return; 475 | } 476 | 477 | memcpy(status->ssid, sta_ap_info.ssid, sizeof(sta_ap_info.ssid)); 478 | status->rssi = sta_ap_info.rssi; 479 | status->authmode = sta_ap_info.authmode; 480 | 481 | esp_netif_ip_info_t ip_info; 482 | esp_netif_get_ip_info(esp_netif_sta, &ip_info); 483 | status->ip4_addr = ip_info.ip; 484 | 485 | esp_netif_get_ip6_linklocal(esp_netif_sta, &status->ip6_addr); 486 | } 487 | 488 | wifi_ap_record_t *wifi_scan(uint16_t *number) { 489 | wifi_mode_t wifi_mode; 490 | esp_wifi_get_mode(&wifi_mode); 491 | 492 | // Ensure STA is enabled 493 | if (wifi_mode != WIFI_MODE_APSTA && wifi_mode != WIFI_MODE_STA) { 494 | esp_wifi_set_mode(wifi_mode == WIFI_MODE_AP ? WIFI_MODE_APSTA : WIFI_MODE_STA); 495 | } 496 | 497 | wifi_scan_config_t wifi_scan_config = { 498 | .ssid = NULL, 499 | .bssid = NULL, 500 | .channel = 0, 501 | .show_hidden = 0 502 | }; 503 | 504 | esp_wifi_scan_start(&wifi_scan_config, true); 505 | 506 | esp_wifi_scan_get_ap_num(number); 507 | if (*number <= 0) { 508 | return NULL; 509 | } 510 | 511 | wifi_ap_record_t *ap_records = (wifi_ap_record_t *) malloc(*number * sizeof(wifi_ap_record_t)); 512 | esp_wifi_scan_get_ap_records(number, ap_records); 513 | 514 | return ap_records; 515 | } 516 | 517 | const char *wifi_auth_mode_name(wifi_auth_mode_t auth_mode) { 518 | switch (auth_mode) { 519 | case WIFI_AUTH_OPEN: 520 | return "OPEN"; 521 | case WIFI_AUTH_WEP: 522 | return "WEP"; 523 | case WIFI_AUTH_WPA_PSK: 524 | return "WPA_PSK"; 525 | case WIFI_AUTH_WPA2_PSK: 526 | return "WPA2_PSK"; 527 | case WIFI_AUTH_WPA_WPA2_PSK: 528 | return "WPA/2_PSK"; 529 | case WIFI_AUTH_WPA2_ENTERPRISE: 530 | return "WPA2_ENTERPRISE"; 531 | default: 532 | return "Unknown"; 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild 3 | nvs, data, nvs, , 0x6000, 4 | phy_init, data, phy, , 0x1000, 5 | factory, app, factory, , 2M, 6 | www, data, spiffs, , 1M, 7 | coredump, data, coredump,, 192k, 8 | -------------------------------------------------------------------------------- /wipe_config.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nebkat/esp32-xbee/8fc008ad0f8b68bea17e6f8638c720525121f6a3/www/favicon.ico -------------------------------------------------------------------------------- /www/log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ESP32 XBee Log 4 | 5 | 6 | 7 | 8 | 14 | 102 | 103 | 104 |
105 |
106 |

ESP32 XBee Log

107 |

The device log will be loaded automatically below. Make sure that you have at most one log page open at a time.

108 |
109 |
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
#TimeLevelTagComment
124 |
125 |
126 | 138 | 139 | --------------------------------------------------------------------------------