From c3a1ba8de28e6d16e2b68d5341a018f712cd45dd Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 1 Aug 2021 17:48:19 +0200 Subject: [PATCH] Initial wifi range extender (#12784) --- CHANGELOG.md | 1 + RELEASENOTES.md | 1 + tasmota/xdrv_58_range_extender.ino | 352 +++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 tasmota/xdrv_58_range_extender.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abfae0ea..5e839b4ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [9.5.0.4] ### Added - Support for second DNS server +- Optional IP filter to command ``TCPStart`` (#12806) ### Changed - ESP8266Audio library from v1.5.0 to v1.9.2 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1417e723d..69bfc7056 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -113,6 +113,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo - Support for Technoline WS2300-15 Anemometer [#12573](https://github.com/arendst/Tasmota/issues/12573) - Support for Telaire T6700 Series CO2 sensor by Alexander Savchenko [#12618](https://github.com/arendst/Tasmota/issues/12618) - Support for CAN bus and Freedom Won Battery Management System by Marius Bezuidenhout [#12651](https://github.com/arendst/Tasmota/issues/12651) +- Optional IP filter to command ``TCPStart`` [#12806](https://github.com/arendst/Tasmota/issues/12806) ### Changed - ESP32 core library from v1.0.6 to v1.0.7.3 diff --git a/tasmota/xdrv_58_range_extender.ino b/tasmota/xdrv_58_range_extender.ino new file mode 100644 index 000000000..627d4a31d --- /dev/null +++ b/tasmota/xdrv_58_range_extender.ino @@ -0,0 +1,352 @@ +/* + xdrv_58_range_extender.ino - WiFi Range Extender for Tasmota + + Copyright (C) 2021 sillyfrog and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_WIFI_RANGE_EXTENDER +/********************************************************************************************* +To use this, add the following to your user_config_override.h +#define USE_WIFI_RANGE_EXTENDER + +Additionally PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH must be set in your build options. +For example, in your platfromio_tasmota_cenv.ini, you will need an entry such as: +[env:tasmota-rangeextender] +build_flags = ${common.build_flags} + -D FIRMWARE_RANGE_EXTENDER + -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH + +If you want to support NAPT (removing the need for routes on a core router): +#define USE_WIFI_RANGE_EXTENDER_NAPT + +An example full static configuration: +#define USE_WIFI_RANGE_EXTENDER +#define USE_WIFI_RANGE_EXTENDER_NAPT +#define WIFI_RGX_SSID "rangeextender" +#define WIFI_RGX_PASSWORD "securepassword" +#define WIFI_RGX_IP_ADDRESS "10.99.1.1" +#define WIFI_RGX_SUBNETMASK "255.255.255.0" + +\*********************************************************************************************/ + +#define XDRV_58 58 + +// Memory usage at 512: Heap from 30136 to 17632: 12504 +// Memory usage at 128: Heap from 30136 to 26848: 3288 +#define NAPT 128 // IP_NAPT_MAX: 512 +#define NAPT_PORT 10 // IP_PORTMAP_MAX: 32 + +#warning **** USE_WIFI_RANGE_EXTENDER is enabled **** + +#ifdef ESP8266 +#ifdef LWIP_FEATURES +// All good +#else +#error LWIP_FEATURES required, add "-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH" to build_flags +#endif // LWIP_FEATURES +#endif // ESP8266 + +#ifdef ESP32 +#ifdef CONFIG_LWIP_IP_FORWARD +// All good +#else +#error CONFIG_LWIP_IP_FORWARD not set, arduino-esp32 v2 or later required with CONFIG_LWIP_IP_FORWARD support +#endif // CONFIG_LWIP_IP_FORWARD +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT +#ifdef CONFIG_LWIP_IPV4_NAPT +// All good +#else +#error CONFIG_LWIP_IPV4_NAPT not set, arduino-esp32 v2 or later required with CONFIG_LWIP_IPV4_NAPT support +#endif // IP_NAPT +#endif // CONFIG_LWIP_IPV4_NAPT +#endif // ESP32 + +const char kDrvRgxCommands[] PROGMEM = "Rgx|" // Prefix + "State" + "|" + D_CMND_SSID + "|" + D_CMND_PASSWORD +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT + "|" + "NAPT" +#endif // USE_WIFI_RANGE_EXTENDER_NAPT + "|" + "Address" + "|" + "Subnet"; + +void (*const DrvRgxCommand[])(void) PROGMEM = { + &CmndRgxState, + &CmndRgxSSID, + &CmndRgxPassword, +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT + &CmndRgxNAPT, +#endif // USE_WIFI_RANGE_EXTENDER_NAPT + &CmndRgxAddresses, + &CmndRgxAddresses, +}; + +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT +#ifdef ESP8266 +#include +#endif // ESP8266 +#endif // USE_WIFI_RANGE_EXTENDER_NAPT + +#include +#include +#ifdef ESP8266 +#include +#endif // ESP8266 +#ifdef ESP32 +#include "lwip/lwip_napt.h" +#include +#endif // ESP32 + +#define RGX_NOT_CONFIGURED 0 +#define RGX_FORCE_CONFIGURE 1 +#define RGX_CONFIGURED 2 +#define RGX_CONFIG_INCOMPLETE 3 +#define RGX_SETUP_NAPT 4 + +typedef struct { + uint8_t status = RGX_NOT_CONFIGURED; +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT + bool napt_enabled = false; +#ifdef ESP8266 + bool napt_inited = false; +#endif // ESP8266 +#endif // USE_WIFI_RANGE_EXTENDER_NAPT +} TRgxSettings; + +TRgxSettings RgxSettings; + +// Check the current configuration is complete, updating RgxSettings.status +void RgxCheckConfig(void) +{ + if ( + strlen(SettingsText(SET_RGX_SSID)) > 0 && + strlen(SettingsText(SET_RGX_PASSWORD)) >= 8 && + Settings->ipv4_rgx_address && + Settings->ipv4_rgx_subnetmask) + { + if (RgxSettings.status != RGX_FORCE_CONFIGURE) + { + RgxSettings.status = RGX_NOT_CONFIGURED; + } + } + else + { + RgxSettings.status = RGX_CONFIG_INCOMPLETE; + } +} + +void CmndRgxState(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (Settings->sbflag1.range_extender != XdrvMailbox.payload) { + Settings->sbflag1.range_extender = XdrvMailbox.payload; + if (0 == XdrvMailbox.payload) { // Turn off + TasmotaGlobal.restart_flag = 2; + } + } + } + ResponseCmndStateText(Settings->sbflag1.range_extender); +} + +void CmndRgxAddresses(void) { + char network_address[22]; + ext_snprintf_P(network_address, sizeof(network_address), PSTR(" (%_I)"), (uint32_t)NetworkAddress()); + uint32_t ipv4_address; + if (ParseIPv4(&ipv4_address, XdrvMailbox.data)) { + if (XdrvMailbox.command[3] == 'S') { // Subnet + Settings->ipv4_rgx_subnetmask = ipv4_address; + } else { + Settings->ipv4_rgx_address = ipv4_address; + } + RgxSettings.status = RGX_FORCE_CONFIGURE; + } + ResponseRgxConfig(); +} + +void CmndRgxSSID(void) { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_RGX_SSID, (SC_CLEAR == Shortcut()) ? "" : XdrvMailbox.data); + } + RgxSettings.status = RGX_FORCE_CONFIGURE; + ResponseRgxConfig(); +} + +void CmndRgxPassword(void) { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_RGX_PASSWORD, (SC_CLEAR == Shortcut()) ? "" : XdrvMailbox.data); + } + RgxSettings.status = RGX_FORCE_CONFIGURE; + ResponseRgxConfig(); +} + +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT +void CmndRgxNAPT(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (Settings->sbflag1.range_extender_napt != XdrvMailbox.payload) { + Settings->sbflag1.range_extender_napt = XdrvMailbox.payload; + RgxSettings.status = RGX_FORCE_CONFIGURE; + } + } + ResponseCmndStateText(Settings->sbflag1.range_extender_napt); +}; +#endif // USE_WIFI_RANGE_EXTENDER_NAPT + +void ResponseRgxConfig(void) +{ + RgxCheckConfig(); + Response_P(PSTR("{\"Rgx\":{\"Valid\":\"%s\",\"" D_CMND_SSID "\":\"%s\",\"" D_CMND_PASSWORD "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\"}"), + (RgxSettings.status == RGX_CONFIG_INCOMPLETE) ? "false" : "true", + EscapeJSONString(SettingsText(SET_RGX_SSID)).c_str(), + EscapeJSONString(SettingsText(SET_RGX_PASSWORD)).c_str(), + Settings->ipv4_rgx_address, + Settings->ipv4_rgx_subnetmask); +} + +void rngxSetup() +{ + // Check we have a complete config first + RgxCheckConfig(); + if (RgxSettings.status == RGX_CONFIG_INCOMPLETE) + { + AddLog(LOG_LEVEL_DEBUG, PSTR("RGX: Range Extender config incomplete")); + return; + } +#ifdef ESP8266 + dhcps_set_dns(0, WiFi.dnsIP(0)); + dhcps_set_dns(1, WiFi.dnsIP(1)); +#endif // ESP8266 +#ifdef ESP32 + esp_err_t err; + tcpip_adapter_dns_info_t ip_dns; + + err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + err = tcpip_adapter_get_dns_info(TCPIP_ADAPTER_IF_STA, ESP_NETIF_DNS_MAIN, &ip_dns); + err = tcpip_adapter_set_dns_info(TCPIP_ADAPTER_IF_AP, ESP_NETIF_DNS_MAIN, &ip_dns); + dhcps_offer_t opt_val = OFFER_DNS; // supply a dns server via dhcps + tcpip_adapter_dhcps_option(ESP_NETIF_OP_SET, ESP_NETIF_DOMAIN_NAME_SERVER, &opt_val, 1); + err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); +#endif // ESP32 + // WiFi.softAPConfig(EXTENDER_LOCAL_IP, EXTENDER_GATEWAY_IP, EXTENDER_SUBNET); + WiFi.softAPConfig(Settings->ipv4_rgx_address, Settings->ipv4_rgx_address, Settings->ipv4_rgx_subnetmask); + WiFi.softAP(SettingsText(SET_RGX_SSID), SettingsText(SET_RGX_PASSWORD)); + AddLog(LOG_LEVEL_INFO, PSTR("RGX: WiFi Extender AP Enabled")); + RgxSettings.status = RGX_SETUP_NAPT; +} + +void rngxSetupNAPT(void) { + // A short delay is required for enabling NAPT to work on the ESP32 +#ifdef USE_WIFI_RANGE_EXTENDER_NAPT + if (Settings->sbflag1.range_extender_napt && !RgxSettings.napt_enabled) { +#ifdef ESP8266 + if (!RgxSettings.napt_inited) { + err_t ret = ip_napt_init(NAPT, NAPT_PORT); + if (ret == ERR_OK) { + AddLog(LOG_LEVEL_INFO, PSTR("RGX: NAPT initialization complete")); + RgxSettings.napt_inited = true; + } else { + AddLog(LOG_LEVEL_ERROR, PSTR("RGX: NAPT initialization failed! (%d)"), ret); + } + } + if (RgxSettings.napt_inited) { + err_t ret = ip_napt_enable_no(SOFTAP_IF, 1); + if (ret == ERR_OK) { + AddLog(LOG_LEVEL_INFO, PSTR("RGX: NAPT Enabled")); + RgxSettings.napt_enabled = true; + } + } +#endif // ESP8266 +#ifdef ESP32 + ip_napt_enable(WiFi.softAPIP(), 1); + AddLog(LOG_LEVEL_INFO, PSTR("RGX: NAPT Enabled")); + RgxSettings.napt_enabled = true; +#endif // ESP32 + } + else if (!Settings->sbflag1.range_extender_napt && RgxSettings.napt_enabled) { +#ifdef ESP8266 + err_t ret = ip_napt_enable_no(SOFTAP_IF, 0); + if (ret == ERR_OK) { + AddLog(LOG_LEVEL_INFO, "RGX: NAPT Disabled"); + RgxSettings.napt_enabled = false; + } +#endif // ESP8266 +#ifdef ESP32 + ip_napt_enable(WiFi.softAPIP(), 0); + AddLog(LOG_LEVEL_INFO, "RGX: NAPT Disabled, reboot maybe required"); +#endif // ESP32 + } +#endif // USE_WIFI_RANGE_EXTENDER_NAPT + RgxSettings.status = RGX_CONFIGURED; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv58(uint8_t function) { + bool result = false; + + if (FUNC_COMMAND == function) { + result = DecodeCommand(kDrvRgxCommands, DrvRgxCommand); + } + else if (Settings->sbflag1.range_extender && !TasmotaGlobal.restart_flag) { + switch (function) + { + case FUNC_PRE_INIT: + break; + case FUNC_EVERY_SECOND: + // AddLog(LOG_LEVEL_INFO, PSTR("RGX: XXX DEBUG INFO: Wifi.status: %d, WiFi.getMode(): %d, RgxSettings.status: %d"), Wifi.status, WiFi.getMode(), RgxSettings.status); + if (RgxSettings.status == RGX_NOT_CONFIGURED && Wifi.status == WL_CONNECTED) + { + // Setup only if WiFi in STA only mode + if (WiFi.getMode() == WIFI_STA) + { + // Connecting for the first time, setup WiFi + rngxSetup(); + } + else + { + RgxSettings.status = RGX_CONFIGURED; + } + } + else if (RgxSettings.status == RGX_FORCE_CONFIGURE && Wifi.status == WL_CONNECTED) + { + rngxSetup(); + } + else if (RgxSettings.status == RGX_SETUP_NAPT) + { + rngxSetupNAPT(); + } + else if (RgxSettings.status == RGX_CONFIGURED) + { + if (Wifi.status != WL_CONNECTED) + { + // No longer connected, need to setup again + AddLog(LOG_LEVEL_INFO, PSTR("RGX: No longer connected, prepare to reconnect WiFi AP...")); + RgxSettings.status = RGX_NOT_CONFIGURED; + } + } + break; + } + } + return result; +} + +#endif // USE_WIFI_RANGE_EXTENDER \ No newline at end of file