Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3150 lines
76 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
Remove wifi info from File: dir, Node: Top, This is the top of the INFO tree. This is the Info main menu (aka directory node). A few useful Info commands: 'q' quits; '?' lists all Info commands; 'h' starts the Info tutorial; 'mTexinfo RET' visits the Texinfo manual, etc. * Menu: Basics * Common options: (coreutils)Common options. * Coreutils: (coreutils). Core GNU (file, text, shell) utilities. * Date input formats: (coreutils)Date input formats. * Ed: (ed). The GNU line editor * File permissions: (coreutils)File permissions. Access modes. * Finding files: (find). Operating on files matching certain criteria. C++ libraries * autosprintf: (autosprintf). Support for printf format strings in C++. Compression * Gzip: (gzip). General (de)compression of files (lzw). Development * SSIP: (ssip). Speech Synthesis Interface Protocol. * Speech Dispatcher: (speech-dispatcher). Speech Dispatcher. * libffi: (libffi). Portable foreign-function interface library. DOS * Mtools: (mtools). Mtools: utilities to access DOS disks in Unix. Editors * nano: (nano). Small and friendly text editor. General Commands * Screen: (screen). Full-screen window manager. GNU Gettext Utilities * autopoint: (gettext)autopoint Invocation. Copy gettext infrastructure. * envsubst: (gettext)envsubst Invocation. Expand environment variables. * gettextize: (gettext)gettextize Invocation. Prepare a package for gettext. * gettext: (gettext). GNU gettext utilities. * ISO3166: (gettext)Country Codes. ISO 3166 country codes. * ISO639: (gettext)Language Codes. ISO 639 language codes. * msgattrib: (gettext)msgattrib Invocation. Select part of a PO file. * msgcat: (gettext)msgcat Invocation. Combine several PO files. * msgcmp: (gettext)msgcmp Invocation. Compare a PO file and template. * msgcomm: (gettext)msgcomm Invocation. Match two PO files. * msgconv: (gettext)msgconv Invocation. Convert PO file to encoding. * msgen: (gettext)msgen Invocation. Create an English PO file. * msgexec: (gettext)msgexec Invocation. Process a PO file. * msgfilter: (gettext)msgfilter Invocation. Pipe a PO file through a filter. * msgfmt: (gettext)msgfmt Invocation. Make MO files out of PO files. * msggrep: (gettext)msggrep Invocation. Select part of a PO file. * msginit: (gettext)msginit Invocation. Create a fresh PO file. * msgmerge: (gettext)msgmerge Invocation. Update a PO file from template. * msgunfmt: (gettext)msgunfmt Invocation. Uncompile MO file into PO file. * msguniq: (gettext)msguniq Invocation. Unify duplicates for PO file. * ngettext: (gettext)ngettext Invocation. Translate a message with plural. * xgettext: (gettext)xgettext Invocation. Extract strings into a PO file. GNU organization * Maintaining Findutils: (find-maint). Maintaining GNU findutils GNU Utilities * dirmngr-client: (gnupg). X.509 CRL and OCSP client. * dirmngr: (gnupg). X.509 CRL and OCSP server. * gpg: (gnupg1). OpenPGP encryption and signing tool (v1). * gpg-agent: (gnupg). The secret key daemon. * gpg2: (gnupg). OpenPGP encryption and signing tool. * gpgsm: (gnupg). S/MIME encryption and signing tool. Individual utilities * aclocal-invocation: (automake-1.15)aclocal Invocation. Generating aclocal.m4. * arch: (coreutils)arch invocation. Print machine hardware name. * automake-invocation: (automake-1.15)automake Invocation. Generating Makefile.in. * base32: (coreutils)base32 invocation. Base32 encode/decode data. * base64: (coreutils)base64 invocation. Base64 encode/decode data. * basename: (coreutils)basename invocation. Strip directory and suffix. * bibtex: (web2c)bibtex invocation. Maintaining bibliographies. * cat: (coreutils)cat invocation. Concatenate and write files. * chcon: (coreutils)chcon invocation. Change SELinux CTX of files. * chgrp: (coreutils)chgrp invocation. Change file groups. * chmod: (coreutils)chmod invocation. Change access permissions. * chown: (coreutils)chown invocation. Change file owners and groups. * chroot: (coreutils)chroot invocation. Specify the root directory. * cksum: (coreutils)cksum invocation. Print POSIX CRC checksum. * cmp: (diffutils)Invoking cmp. Compare 2 files byte by byte. * comm: (coreutils)comm invocation. Compare sorted files by line. * cp: (coreutils)cp invocation. Copy files. * csplit: (coreutils)csplit invocation. Split by context. * cut: (coreutils)cut invocation. Print selected parts of lines. * date: (coreutils)date invocation. Print/set system date and time. * dd: (coreutils)dd invocation. Copy and convert a file. * df: (coreutils)df invocation. Report file system disk usage. * diff: (diffutils)Invoking diff. Compare 2 files line by line. * diff3: (diffutils)Invoking diff3. Compare 3 files line by line. * dir: (coreutils)dir invocation. List directories briefly. * dircolors: (coreutils)dircolors invocation. Color setup for ls. * dirname: (coreutils)dirname invocation. Strip last file name component. * du: (coreutils)du invocation. Report on disk usage. * dvicopy: (web2c)dvicopy invocation. Virtual font expansion * dvitomp: (web2c)dvitomp invocation. DVI to MPX (MetaPost pictures). * dvitype: (web2c)dvitype invocation. DVI to human-readable text. * echo: (coreutils)echo invocation. Print a line of text. * env: (coreutils)env invocation. Modify the environment. * expand: (coreutils)expand invocation. Convert tabs to spaces. * expr: (coreutils)expr invocation. Evaluate expressions. * factor: (coreutils)factor invocation. Print prime factors * false: (coreutils)false invocation. Do nothing, unsuccessfully. * find: (find)Invoking find. Finding and acting on files. * fmt: (coreutils)fmt invocation. Reformat paragraph text. * fold: (coreutils)fold invocation. Wrap long input lines. * gftodvi: (web2c)gftodvi invocation. Generic font proofsheets. * gftopk: (web2c)gftopk invocation. Generic to packed fonts. * gftype: (web2c)gftype invocation. GF to human-readable text. * groups: (coreutils)groups invocation. Print group names a user is in. * gunzip: (gzip)Overview. Decompression. * gzexe: (gzip)Overview. Compress executables. * head: (coreutils)head invocation. Output the first part of files. * hostid: (coreutils)hostid invocation. Print numeric host identifier. * hostname: (coreutils)hostname invocation. Print or set system name. * id: (coreutils)id invocation. Print user identity. * install: (coreutils)install invocation. Copy files and set attributes. * join: (coreutils)join invocation. Join lines on a common field. * kill: (coreutils)kill invocation. Send a signal to processes. * link: (coreutils)link invocation. Make hard links between files. * ln: (coreutils)ln invocation. Make links between files. * locate: (find)Invoking locate. Finding files in a database. * logname: (coreutils)logname invocation. Print current login name. * ls: (coreutils)ls invocation. List directory contents. * md5sum: (coreutils)md5sum invocation. Print or check MD5 digests. * mf: (web2c)mf invocation. Creating typeface families. * mft: (web2c)mft invocation. Prettyprinting Metafont source. * mkdir: (coreutils)mkdir invocation. Create directories. * mkfifo: (coreutils)mkfifo invocation. Create FIFOs (named pipes). * mknod: (coreutils)mknod invocation. Create special files. * mktemp: (coreutils)mktemp invocation. Create temporary files. * mltex: (web2c)MLTeX. Multi-lingual TeX. * mpost: (web2c)mpost invocation. Creating technical diagrams. * mv: (coreutils)mv invocation. Rename files. * nice: (coreutils)nice invocation. Modify niceness. * nl: (coreutils)nl invocation. Number lines and write files. * nohup: (coreutils)nohup invocation. Immunize to hangups. * nproc: (coreutils)nproc invocation. Print the number of processors. * numfmt: (coreutils)numfmt invocation. Reformat numbers. * od: (coreutils)od invocation. Dump files in octal, etc. * paste: (coreutils)paste invocation. Merge lines of files. * patch: (diffutils)Invoking patch. Apply a patch to a file. * patgen: (web2c)patgen invocation. Creating hyphenation patterns. * pathchk: (coreutils)pathchk invocation. Check file name portability. * pktogf: (web2c)pktogf invocation. Packed to generic fonts. * pktype: (web2c)pktype invocation. PK to human-readable text. * pltotf: (web2c)pltotf invocation. Property list to TFM. * pooltype: (web2c)pooltype invocation. Display WEB pool files. * pr: (coreutils)pr invocation. Paginate or columnate files. * printenv: (coreutils)printenv invocation. Print environment variables. * printf: (coreutils)printf invocation. Format and print data. * ptx: (coreutils)ptx invocation. Produce permuted indexes. * pwd: (coreutils)pwd invocation. Print working directory. * readlink: (coreutils)readlink invocation. Print referent of a symlink. * realpath: (coreutils)realpath invocation. Print resolved file names. * rm: (coreutils)rm invocation. Remove files. * rmdir: (coreutils)rmdir invocation. Remove empty directories. * runcon: (coreutils)runcon invocation. Run in specified SELinux CTX. * sdiff: (diffutils)Invoking sdiff. Merge 2 files side-by-side. * seq: (coreutils)seq invocation. Print numeric sequences * sha1sum: (coreutils)sha1sum invocation. Print or check SHA-1 digests. * sha2: (coreutils)sha2 utilities. Print or check SHA-2 digests. * shred: (coreutils)shred invocation. Remove files more securely. * shuf: (coreutils)shuf invocation. Shuffling text files. * sleep: (coreutils)sleep invocation. Delay for a specified time. * sort: (coreutils)sort invocation. Sort text files. * split: (coreutils)split invocation. Split into pieces. * stat: (coreutils)stat invocation. Report file(system) status. * stdbuf: (coreutils)stdbuf invocation. Modify stdio buffering. * stty: (coreutils)stty invocation. Print/change terminal settings. * sum: (coreutils)sum invocation. Print traditional checksum. * sync: (coreutils)sync invocation. Synchronize memory to disk. * tac: (coreutils)tac invocation. Reverse files. * tail: (coreutils)tail invocation. Output the last part of files. * tangle: (web2c)tangle invocation. WEB to Pascal. * tee: (coreutils)tee invocation. Redirect to multiple files. * test: (coreutils)test invocation. File/string tests. * tex: (web2c)tex invocation. Typesetting. * tftopl: (web2c)tftopl invocation. TFM -> property list. * time: (time). Run programs and summarize system resource usage. * timeout: (coreutils)timeout invocation. Run with time limit. * touch: (coreutils)touch invocation. Change file timestamps. * tr: (coreutils)tr invocation. Translate characters. * true: (coreutils)true invocation. Do nothing, successfully. * truncate: (coreutils)truncate invocation. Shrink/extend size of a file. * tsort: (coreutils)tsort invocation. Topological sort. * tty: (coreutils)tty invocation. Print terminal name. * uname: (coreutils)uname invocation. Print system information. * unexpand: (coreutils)unexpand invocation. Convert spaces to tabs. * uniq: (coreutils)uniq invocation. Uniquify files. * unlink: (coreutils)unlink invocation. Removal via unlink(2). * updatedb: (find)Invoking updatedb. Building the locate database. * uptime: (coreutils)uptime invocation. Print uptime and load. * users: (coreutils)users invocation. Print current user names. * vdir: (coreutils)vdir invocation. List directories verbosely. * vftovp: (web2c)vftovp invocation. Virtual font -> virtual pl. * vptovf: (web2c)vptovf invocation. Virtual pl -> virtual font. * wc: (coreutils)wc invocation. Line, word, and byte counts. * weave: (web2c)weave invocation. WEB to TeX. * who: (coreutils)who invocation. Print who is logged in. * whoami: (coreutils)whoami invocation. Print effective user ID. * xargs: (find)Invoking xargs. Operating on many files. * yes: (coreutils)yes invocation. Print a string indefinitely. * zcat: (gzip)Overview. Decompression to stdout. * zdiff: (gzip)Overview. Compare compressed files. * zforce: (gzip)Overview. Force .gz extension on files. * zgrep: (gzip)Overview. Search compressed files. * zmore: (gzip)Overview. Decompression output by pages. Kernel * GRUB: (grub). The GRand Unified Bootloader * grub-dev: (grub-dev). The GRand Unified Bootloader Dev * grub-install: (grub)Invoking grub-install. Install GRUB on your drive * grub-mkconfig: (grub)Invoking grub-mkconfig. Generate GRUB configuration * grub-mkpasswd-pbkdf2: (grub)Invoking grub-mkpasswd-pbkdf2. * grub-mkrelpath: (grub)Invoking grub-mkrelpath. * grub-mkrescue: (grub)Invoking grub-mkrescue. Make a GRUB rescue image * grub-mount: (grub)Invoking grub-mount. Mount a file system using GRUB * grub-probe: (grub)Invoking grub-probe. Probe device information * grub-script-check: (grub)Invoking grub-script-check. Libraries * RLuserman: (rluserman). The GNU readline library User's Manual. Man-pages * Latex2man: (latex2man). Its Man-Page Math * bc: (bc). An arbitrary precision calculator language. * dc: (dc). Arbitrary precision RPN "Desktop Calculator". Network applications * Wget: (wget). Non-interactive network downloader. Programming * flex: (flex). Fast lexical analyzer generator (lex replacement). * gnucash: (gnucash-design). Design of the GnuCash program Programming Tools * Gperf: (gperf). Perfect Hash Function Generator. Software development * Autoconf Archive: (autoconf-archive). A collection of re-usable Autoconf macros. * Automake: (automake-1.15). Making GNU standards-compliant Makefiles. Sound * SSIP: (ssip). Speech Synthesis Interface Protocol. * Say for Speech Dispatcher: (spd-say). Say. * Speech Dispatcher: (speech-dispatcher). Speech Dispatcher. TeX * DVI-to-PNG: (dvipng). Translating TeX DVI files to Portable Network Graphics (PNG). * DVI-to-Postscript: (dvips). Translating TeX DVI files to PostScript. * Eplain: (eplain). Expanding on plain Tex. * EpsPDF: (epspdf). Portable GUI- and command-line EPS/PS/PDF conversion * Kpathsea: (kpathsea). File lookup along search paths. * LaTeX2e: (latex2e). Unofficial LaTeX reference manual. * LaTeX2e-es: (latex2e-es). Manual de extraoficial de LaTeX. * Naming TeX fonts: (fontname). Filenames for TeX fonts. * TL-build: (tlbuild). TeX Live configuration and development. * TeX Directories: (tds). A directory structure for TeX files. * TeXdraw: (texdraw). A system for producing PostScript drawings from TeX. * Web2c: (web2c). TeX, Metafont, and companion programs. * afm2tfm: (dvips)Invoking afm2tfm. Making Type 1 fonts available to TeX. * dvipng: (dvipng). A DVI-to-PNG translator. * dvips: (dvips)Invoking Dvips. DVI-to-PostScript translator. * kpsewhich: (kpathsea)Invoking kpsewhich. TeX file searching. * mf2pt1: (mf2pt1). PostScript Type 1 fonts from Metafont source. * mktexfmt: (kpathsea)mktex scripts. Format (fmt/base/mem) generation. * mktexlsr: (kpathsea)Filename database. Update ls-R. * mktexmf: (kpathsea)mktex scripts. MF source generation. * mktexpk: (kpathsea)mktex scripts. PK bitmap generation. * mktextex: (kpathsea)mktex scripts. TeX source generation. * mktextfm: (kpathsea)mktex scripts. TeX font metric generation. Text creation and manipulation * Diffutils: (diffutils). Comparing and merging files. * M4: (m4). A powerful macro processor. * grep: (grep). Print lines matching a pattern. * sed: (sed). Stream EDitor. command. Use * SSID MODE CHAN RATE SIGNAL BARS SECURITY to get current connections
5 years ago
7 years ago
7 years ago
6 years ago
  1. /*
  2. WIFI MODULE
  3. Original code based on JustWifi, Wifi Manager for ESP8266 (GPLv3+)
  4. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  5. Modified for ESPurna
  6. Copyright (C) 2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  7. */
  8. #include "wifi.h"
  9. #include "telnet.h"
  10. #include "ws.h"
  11. #include <IPAddress.h>
  12. #include <AddrList.h>
  13. #if WIFI_AP_CAPTIVE_SUPPORT
  14. #include <DNSServer.h>
  15. #endif
  16. #include <algorithm>
  17. #include <array>
  18. #include <list>
  19. #include <queue>
  20. #include <vector>
  21. // ref.
  22. // https://github.com/d-a-v/esp82xx-nonos-linklayer/blob/master/README.md#how-it-works
  23. //
  24. // Current esp8266 Arduino Core is based on the NONOS SDK using the lwip1.4 APIs
  25. // To handle static IPs, these need to be called when current IP differs from the one set via the setting.
  26. //
  27. // Can't include the original headers, since they refer to the ip_addr_t and IPAddress depends on a specific overload to extract v4 addresses
  28. // (SDK layer *only* works with ipv4 addresses)
  29. #undef netif_set_addr
  30. extern "C" netif* eagle_lwip_getif(int);
  31. extern "C" void netif_set_addr(netif* netif, ip4_addr_t*, ip4_addr_t*, ip4_addr_t*);
  32. // -----------------------------------------------------------------------------
  33. // INTERNAL
  34. // -----------------------------------------------------------------------------
  35. namespace espurna {
  36. namespace wifi {
  37. using Mac = std::array<uint8_t, 6>;
  38. namespace {
  39. namespace build {
  40. namespace compat {
  41. [[gnu::unused, gnu::deprecated("WIFI_MODEM_SLEEP_{NONE, MODEM, LIGHT} should be used instead, see config/general.h")]]
  42. constexpr sleep_type_t arduino_sleep(WiFiSleepType type) {
  43. return static_cast<sleep_type_t>(type);
  44. }
  45. [[gnu::unused]]
  46. constexpr sleep_type_t arduino_sleep(sleep_type_t type) {
  47. return type;
  48. }
  49. } // namespace compat
  50. constexpr float txPower() {
  51. return WIFI_OUTPUT_POWER_DBM;
  52. }
  53. constexpr sleep_type_t sleep() {
  54. return compat::arduino_sleep(WIFI_SLEEP_MODE);
  55. }
  56. constexpr BootMode bootMode() {
  57. return WIFI_BOOT_MODE;
  58. }
  59. } // namespace build
  60. namespace settings {
  61. namespace options {
  62. PROGMEM_STRING(Disabled, "off");
  63. PROGMEM_STRING(Enabled, "on");
  64. } // namespace options
  65. } // namespace settings
  66. namespace ap {
  67. namespace settings {
  68. namespace options {
  69. PROGMEM_STRING(Fallback, "fallback");
  70. static constexpr espurna::settings::options::Enumeration<ApMode> ApModeOptions[] PROGMEM {
  71. {ApMode::Disabled, wifi::settings::options::Disabled},
  72. {ApMode::Enabled, wifi::settings::options::Enabled},
  73. {ApMode::Fallback, Fallback},
  74. };
  75. } // namespace options
  76. } // namespace settings
  77. } // namespace ap
  78. namespace settings {
  79. namespace options {
  80. PROGMEM_STRING(None, "none");
  81. PROGMEM_STRING(Modem, "modem");
  82. PROGMEM_STRING(Light, "light");
  83. static constexpr espurna::settings::options::Enumeration<sleep_type_t> SleepTypeOptions[] PROGMEM {
  84. {NONE_SLEEP_T, None},
  85. {MODEM_SLEEP_T, Modem},
  86. {LIGHT_SLEEP_T, Light},
  87. };
  88. } // namespace options
  89. } // namespace settings
  90. } // namespace
  91. } // namespace wifi
  92. namespace settings {
  93. namespace internal {
  94. template<>
  95. wifi::BootMode convert(const String& value) {
  96. return convert<bool>(value)
  97. ? wifi::BootMode::Enabled
  98. : wifi::BootMode::Disabled;
  99. }
  100. String serialize(wifi::BootMode mode) {
  101. return serialize(mode == wifi::BootMode::Enabled);
  102. }
  103. template<>
  104. wifi::StaMode convert(const String& value) {
  105. return convert<bool>(value)
  106. ? wifi::StaMode::Enabled
  107. : wifi::StaMode::Disabled;
  108. }
  109. String serialize(wifi::StaMode mode) {
  110. return serialize(mode == wifi::StaMode::Enabled);
  111. }
  112. template<>
  113. wifi::ApMode convert(const String& value) {
  114. return convert(wifi::ap::settings::options::ApModeOptions, value, wifi::ApMode::Fallback);
  115. }
  116. String serialize(wifi::ApMode mode) {
  117. return serialize(wifi::ap::settings::options::ApModeOptions, mode);
  118. }
  119. template <>
  120. sleep_type_t convert(const String& value) {
  121. return convert(wifi::settings::options::SleepTypeOptions, value, wifi::build::sleep());
  122. }
  123. String serialize(sleep_type_t sleep) {
  124. return serialize(wifi::settings::options::SleepTypeOptions, sleep);
  125. }
  126. template <>
  127. IPAddress convert(const String& value) {
  128. IPAddress out;
  129. out.fromString(value);
  130. return out;
  131. }
  132. template <>
  133. wifi::Mac convert(const String& value) {
  134. wifi::Mac out{};
  135. static constexpr size_t Min { 12 };
  136. static constexpr size_t Max { 17 };
  137. switch (value.length()) {
  138. // xxxxxxxxxx
  139. case Min:
  140. hexDecode(value.c_str(), value.length(), out.data(), out.size());
  141. break;
  142. // xx:xx:xx:xx:xx:xx
  143. case Max: {
  144. String buffer;
  145. buffer.reserve(value.length());
  146. for (auto it = value.begin(); it != value.end(); ++it) {
  147. if ((*it) != ':') {
  148. buffer += *it;
  149. }
  150. }
  151. if (buffer.length() == Min) {
  152. hexDecode(buffer.c_str(), buffer.length(), out.data(), out.size());
  153. }
  154. break;
  155. }
  156. }
  157. return out;
  158. }
  159. // XXX: "(IP unset)" when not set, no point saving these :/
  160. // XXX: both 0.0.0.0 and 255.255.255.255 will be saved as empty string
  161. String serialize(const IPAddress& ip) {
  162. return ip.isSet() ? ip.toString() : emptyString;
  163. }
  164. String serialize(wifi::Mac mac) {
  165. String out;
  166. out.reserve(18);
  167. bool delim { false };
  168. char buffer[3] = {0};
  169. for (auto& byte : mac) {
  170. hexEncode(&byte, 1, buffer, sizeof(buffer));
  171. if (delim) {
  172. out += ':';
  173. }
  174. out += buffer;
  175. delim = true;
  176. }
  177. return out;
  178. }
  179. } // namespace internal
  180. } // namespace settings
  181. namespace wifi {
  182. namespace {
  183. // Use SDK constants directly. Provide a constexpr version of the Core enum, since the code never
  184. // actually uses `WiFi::mode(...)` directly, *but* opmode is retrieved using the SDK function.
  185. static constexpr uint8_t OpmodeNull { NULL_MODE };
  186. static constexpr uint8_t OpmodeSta { STATION_MODE };
  187. static constexpr uint8_t OpmodeAp { SOFTAP_MODE };
  188. static constexpr uint8_t OpmodeApSta { OpmodeSta | OpmodeAp };
  189. enum class ScanError {
  190. None,
  191. AlreadyScanning,
  192. Busy,
  193. NoNetworks,
  194. System,
  195. };
  196. enum class Action {
  197. AccessPointFallback,
  198. AccessPointFallbackCheck,
  199. AccessPointStart,
  200. AccessPointStop,
  201. Boot,
  202. StationConnect,
  203. StationContinueConnect,
  204. StationDisconnect,
  205. StationTryConnectBetter,
  206. TurnOff,
  207. TurnOn,
  208. };
  209. using Actions = std::list<Action>;
  210. using ActionsQueue = std::queue<Action, Actions>;
  211. enum class State {
  212. Boot,
  213. Connect,
  214. TryConnectBetter,
  215. Connected,
  216. Idle,
  217. Init,
  218. Timeout,
  219. Fallback,
  220. WaitScan,
  221. WaitScanWithoutCurrent,
  222. WaitConnected
  223. };
  224. namespace internal {
  225. // Module actions are controled in a serialzed manner, when internal loop is done with the
  226. // current task and is free to take up another one. Allow to toggle OFF for the whole module,
  227. // discarding any actions involving an active WiFi. Default is ON
  228. bool enabled { false };
  229. ActionsQueue actions;
  230. State state { State::Boot };
  231. State last_state { state };
  232. } // namespace internal
  233. void tx_power(float dbm) {
  234. if (std::isinf(dbm) || std::isnan(dbm)) {
  235. return;
  236. }
  237. // system_phy_set_max_tpw() unit is .25dBm
  238. constexpr auto Min = float{ 0.0f };
  239. constexpr auto Max = float{ 20.5f };
  240. dbm = std::clamp(dbm, Min, Max);
  241. dbm *= 4.0f;
  242. system_phy_set_max_tpw(dbm);
  243. }
  244. sleep_type_t sleep_type() {
  245. return wifi_get_sleep_type();
  246. }
  247. bool sleep_type(sleep_type_t type) {
  248. return wifi_set_sleep_type(type);
  249. }
  250. uint8_t opmode() {
  251. return wifi_get_opmode();
  252. }
  253. void ensure_opmode(uint8_t mode) {
  254. const auto is_set = [&]() {
  255. return (opmode() == mode);
  256. };
  257. // `std::abort()` calls are the to ensure the mode actually changes, but it should be extremely rare
  258. // it may be also wise to add these for when the mode is already the expected one,
  259. // since we should enforce mode changes to happen *only* through the configuration loop
  260. if (!is_set()) {
  261. const auto current = wifi_get_opmode();
  262. wifi_set_opmode_current(mode);
  263. const auto result = time::blockingDelay(
  264. duration::Seconds(1),
  265. duration::Milliseconds(10),
  266. [&]() {
  267. return !is_set();
  268. });
  269. if (result) {
  270. abort();
  271. }
  272. if (current == OpmodeNull) {
  273. wakeupModemForcedSleep();
  274. }
  275. }
  276. }
  277. bool enabled() {
  278. return internal::enabled;
  279. }
  280. void enable() {
  281. internal::enabled = true;
  282. }
  283. void disable() {
  284. internal::enabled = false;
  285. }
  286. void action(Action value) {
  287. switch (value) {
  288. case Action::StationConnect:
  289. case Action::StationTryConnectBetter:
  290. case Action::StationContinueConnect:
  291. case Action::StationDisconnect:
  292. case Action::AccessPointFallback:
  293. case Action::AccessPointFallbackCheck:
  294. case Action::AccessPointStart:
  295. case Action::AccessPointStop:
  296. if (!enabled()) {
  297. return;
  298. }
  299. break;
  300. case Action::TurnOff:
  301. case Action::TurnOn:
  302. case Action::Boot:
  303. break;
  304. }
  305. internal::actions.push(value);
  306. }
  307. template <typename T>
  308. State handle_action(State state, T&& handler) {
  309. if (!internal::actions.empty()) {
  310. state = handler(state, internal::actions.front());
  311. internal::actions.pop();
  312. }
  313. return state;
  314. }
  315. namespace debug {
  316. String error(ScanError error) {
  317. StringView out;
  318. switch (error) {
  319. case ScanError::None:
  320. out = STRING_VIEW("OK");
  321. break;
  322. case ScanError::AlreadyScanning:
  323. out = STRING_VIEW("Scan already in progress");
  324. break;
  325. case ScanError::Busy:
  326. out = STRING_VIEW("State machine is busy");
  327. break;
  328. case ScanError::NoNetworks:
  329. out = STRING_VIEW("No networks");
  330. break;
  331. case ScanError::System:
  332. out = STRING_VIEW("System unable to start the scan");
  333. break;
  334. }
  335. return out.toString();
  336. }
  337. String mac(Mac mac) {
  338. return espurna::settings::internal::serialize(mac);
  339. }
  340. String ip(const IPAddress& addr) {
  341. return addr.toString();
  342. }
  343. String ip(ip4_addr_t addr) {
  344. String out;
  345. out.reserve(16);
  346. bool delim { false };
  347. for (int byte = 0; byte < 4; ++byte) {
  348. if (delim) {
  349. out += '.';
  350. }
  351. out += ip4_addr_get_byte_val(addr, byte);
  352. delim = true;
  353. }
  354. return out;
  355. }
  356. String authmode(AUTH_MODE mode) {
  357. StringView out;
  358. switch (mode) {
  359. case AUTH_OPEN:
  360. out = STRING_VIEW("OPEN");
  361. break;
  362. case AUTH_WEP:
  363. out = STRING_VIEW("WEP");
  364. break;
  365. case AUTH_WPA_PSK:
  366. out = STRING_VIEW("WPAPSK");
  367. break;
  368. case AUTH_WPA2_PSK:
  369. out = STRING_VIEW("WPA2PSK");
  370. break;
  371. case AUTH_WPA_WPA2_PSK:
  372. out = STRING_VIEW("WPAWPA2-PSK");
  373. break;
  374. case AUTH_MAX:
  375. default:
  376. out = STRING_VIEW("UNKNOWN");
  377. break;
  378. }
  379. return out.toString();
  380. }
  381. String opmode(uint8_t mode) {
  382. StringView out;
  383. switch (mode) {
  384. case OpmodeApSta:
  385. out = STRING_VIEW("AP+STA");
  386. break;
  387. case OpmodeSta:
  388. out = STRING_VIEW("STA");
  389. break;
  390. case OpmodeAp:
  391. out = STRING_VIEW("AP");
  392. break;
  393. case OpmodeNull:
  394. out = STRING_VIEW("NULL");
  395. break;
  396. }
  397. return out.toString();
  398. }
  399. String sleep_type(sleep_type_t type) {
  400. return espurna::settings::internal::serialize(type);
  401. }
  402. } // namespace debug
  403. namespace settings {
  404. namespace keys {
  405. PROGMEM_STRING(TxPower, "wifiTxPwr");
  406. PROGMEM_STRING(Sleep, "wifiSleep");
  407. PROGMEM_STRING(Boot, "wifiBoot");
  408. } // namespace keys
  409. float txPower() {
  410. return getSetting(keys::TxPower, build::txPower());
  411. }
  412. sleep_type_t sleep() {
  413. return getSetting(keys::Sleep, build::sleep());
  414. }
  415. BootMode bootMode() {
  416. return getSetting(keys::Boot, build::bootMode());
  417. }
  418. namespace query {
  419. namespace internal {
  420. #define EXACT_VALUE(NAME, FUNC)\
  421. String NAME () {\
  422. return espurna::settings::internal::serialize(FUNC());\
  423. }
  424. #define ID_VALUE(NAME, FUNC)\
  425. String NAME (size_t id) {\
  426. return espurna::settings::internal::serialize(FUNC(id));\
  427. }
  428. EXACT_VALUE(sleep, settings::sleep)
  429. EXACT_VALUE(txPower, settings::txPower)
  430. EXACT_VALUE(bootMode, settings::bootMode)
  431. } // namespace internal
  432. } // namespace query
  433. } // namespace settings
  434. // We are guaranteed to have '\0' when <32 b/c the SDK zeroes out the data
  435. // But, these are byte arrays, not C strings. When ssid_len is available, use it.
  436. // When not, we are still expecting the <32 arrays to have '\0' at the end and we manually
  437. // set the 32'nd char to '\0' to prevent conversion issues
  438. String convertSsid(const softap_config& config) {
  439. String ssid;
  440. ssid.concat(reinterpret_cast<const char*>(config.ssid), config.ssid_len);
  441. return ssid;
  442. }
  443. String convertSsid(const bss_info& info) {
  444. String ssid;
  445. ssid.concat(reinterpret_cast<const char*>(info.ssid), info.ssid_len);
  446. return ssid;
  447. }
  448. template <typename T, size_t SsidSize = sizeof(T::ssid)>
  449. String convertSsid(const T& config) {
  450. static_assert(SsidSize == 32, "");
  451. const char* ptr { reinterpret_cast<const char*>(config.ssid) };
  452. char ssid[SsidSize + 1];
  453. std::copy(ptr, ptr + SsidSize, ssid);
  454. ssid[SsidSize] = '\0';
  455. return ssid;
  456. }
  457. template <typename T, size_t PassphraseSize = sizeof(T::password)>
  458. String convertPassphrase(const T& config) {
  459. static_assert(PassphraseSize == 64, "");
  460. const char* ptr { reinterpret_cast<const char*>(config.password) };
  461. char passphrase[PassphraseSize + 1];
  462. std::copy(ptr, ptr + PassphraseSize, passphrase);
  463. passphrase[PassphraseSize] = '\0';
  464. return passphrase;
  465. }
  466. template <typename T, size_t MacSize = sizeof(T::bssid)>
  467. Mac convertBssid(const T& info) {
  468. static_assert(MacSize == 6, "");
  469. Mac mac;
  470. std::copy(info.bssid, info.bssid + MacSize, mac.begin());
  471. return mac;
  472. }
  473. struct Info {
  474. Info() = default;
  475. Info(const Info&) = default;
  476. Info(Info&&) = default;
  477. Info(Mac&& bssid, AUTH_MODE authmode, int8_t rssi, uint8_t channel) :
  478. _bssid(std::move(bssid)),
  479. _authmode(authmode),
  480. _rssi(rssi),
  481. _channel(channel)
  482. {}
  483. explicit Info(const bss_info& info) :
  484. _bssid(convertBssid(info)),
  485. _authmode(info.authmode),
  486. _rssi(info.rssi),
  487. _channel(info.channel)
  488. {}
  489. Info& operator=(const Info&) = default;
  490. Info& operator=(Info&&) = default;
  491. Info& operator=(const bss_info& info) {
  492. _bssid = convertBssid(info);
  493. _authmode = info.authmode;
  494. _channel = info.channel;
  495. _rssi = info.rssi;
  496. return *this;
  497. }
  498. explicit operator bool() const {
  499. return _rssi != 0 && _channel != 0;
  500. }
  501. bool operator<(const Info& rhs) const {
  502. return _rssi < rhs._rssi;
  503. }
  504. bool operator>(const Info& rhs) const {
  505. return _rssi > rhs._rssi;
  506. }
  507. const Mac& bssid() const {
  508. return _bssid;
  509. }
  510. AUTH_MODE authmode() const {
  511. return _authmode;
  512. }
  513. int8_t rssi() const {
  514. return _rssi;
  515. }
  516. uint8_t channel() const {
  517. return _channel;
  518. }
  519. private:
  520. Mac _bssid{};
  521. AUTH_MODE _authmode { AUTH_OPEN };
  522. int8_t _rssi { 0 };
  523. uint8_t _channel { 0u };
  524. };
  525. struct SsidInfo {
  526. SsidInfo() = delete;
  527. explicit SsidInfo(const bss_info& info) :
  528. _ssid(convertSsid(info)),
  529. _info(info)
  530. {}
  531. SsidInfo(String&& ssid, Info&& info) :
  532. _ssid(std::move(ssid)),
  533. _info(std::move(info))
  534. {}
  535. const String& ssid() const {
  536. return _ssid;
  537. }
  538. const Info& info() const {
  539. return _info;
  540. }
  541. // decreasing order by rssi (default sort() order is increasing)
  542. bool operator<(const SsidInfo& rhs) const {
  543. if (!_info.rssi()) {
  544. return false;
  545. }
  546. return info() > rhs.info();
  547. }
  548. private:
  549. String _ssid;
  550. Info _info;
  551. };
  552. using SsidInfos = std::forward_list<SsidInfo>;
  553. // Note that lwip config allows up to 3 DNS servers. But, most of the time we use DHCP.
  554. // TODO: ::dns(size_t index)? how'd that look with settings?
  555. struct IpSettings {
  556. IpSettings() = default;
  557. IpSettings(const IpSettings&) = default;
  558. IpSettings(IpSettings&&) = default;
  559. IpSettings& operator=(const IpSettings&) = default;
  560. IpSettings& operator=(IpSettings&&) = default;
  561. template <typename Ip, typename Netmask, typename Gateway, typename Dns>
  562. IpSettings(Ip&& ip, Netmask&& netmask, Gateway&& gateway, Dns&& dns) :
  563. _ip(std::forward<Ip>(ip)),
  564. _netmask(std::forward<Netmask>(netmask)),
  565. _gateway(std::forward<Gateway>(gateway)),
  566. _dns(std::forward<Dns>(dns))
  567. {}
  568. const IPAddress& ip() const {
  569. return _ip;
  570. }
  571. const IPAddress& netmask() const {
  572. return _netmask;
  573. }
  574. const IPAddress& gateway() const {
  575. return _gateway;
  576. }
  577. const IPAddress& dns() const {
  578. return _dns;
  579. }
  580. explicit operator bool() const {
  581. return _ip.isSet()
  582. && _netmask.isSet()
  583. && _gateway.isSet();
  584. }
  585. ip_info toIpInfo() const {
  586. ip_info info{};
  587. info.ip.addr = _ip.v4();
  588. info.netmask.addr = _netmask.v4();
  589. info.gw.addr = _gateway.v4();
  590. return info;
  591. }
  592. private:
  593. IPAddress _ip;
  594. IPAddress _netmask;
  595. IPAddress _gateway;
  596. IPAddress _dns;
  597. };
  598. struct StaNetwork {
  599. Mac bssid;
  600. String ssid;
  601. String passphrase;
  602. int8_t rssi;
  603. uint8_t channel;
  604. };
  605. struct SoftApNetwork {
  606. Mac bssid;
  607. String ssid;
  608. String passphrase;
  609. uint8_t channel;
  610. AUTH_MODE authmode;
  611. };
  612. struct Network {
  613. Network() = delete;
  614. Network(const Network&) = default;
  615. Network(Network&&) = default;
  616. Network& operator=(Network&&) = default;
  617. explicit Network(String&& ssid) :
  618. _ssid(std::move(ssid))
  619. {}
  620. Network(String&& ssid, String&& passphrase) :
  621. _ssid(std::move(ssid)),
  622. _passphrase(std::move(passphrase))
  623. {}
  624. Network(String&& ssid, String&& passphrase, IpSettings&& settings) :
  625. _ssid(std::move(ssid)),
  626. _passphrase(std::move(passphrase)),
  627. _ipSettings(std::move(settings))
  628. {}
  629. // TODO(?): in case SDK API is used directly, this also could use an authmode field
  630. // Arduino wrapper sets WPAPSK minimum by default, so one use-case is to set it to WPA2PSK
  631. Network(Network other, Mac bssid, uint8_t channel) :
  632. _ssid(std::move(other._ssid)),
  633. _passphrase(std::move(other._passphrase)),
  634. _ipSettings(std::move(other._ipSettings)),
  635. _bssid(bssid),
  636. _channel(channel)
  637. {}
  638. bool dhcp() const {
  639. return !_ipSettings;
  640. }
  641. const String& ssid() const {
  642. return _ssid;
  643. }
  644. const String& passphrase() const {
  645. return _passphrase;
  646. }
  647. const IpSettings& ipSettings() const {
  648. return _ipSettings;
  649. }
  650. const Mac& bssid() const {
  651. return _bssid;
  652. }
  653. uint8_t channel() const {
  654. return _channel;
  655. }
  656. private:
  657. String _ssid;
  658. String _passphrase;
  659. IpSettings _ipSettings;
  660. Mac _bssid {};
  661. uint8_t _channel { 0u };
  662. };
  663. using Networks = std::list<Network>;
  664. // -----------------------------------------------------------------------------
  665. // STATION
  666. // -----------------------------------------------------------------------------
  667. namespace sta {
  668. namespace build {
  669. static constexpr size_t NetworksMax { WIFI_MAX_NETWORKS };
  670. // aka short interval
  671. static constexpr auto ConnectionInterval = duration::Milliseconds{ WIFI_CONNECT_INTERVAL };
  672. // aka long interval
  673. static constexpr auto ReconnectionInterval = duration::Milliseconds{ WIFI_RECONNECT_INTERVAL };
  674. static constexpr int ConnectionRetries { WIFI_CONNECT_RETRIES };
  675. static constexpr auto RecoveryInterval = ConnectionInterval * ConnectionRetries;
  676. constexpr StaMode mode() {
  677. return WIFI_STA_MODE;
  678. }
  679. #define WIFI_SETTING_STRING_RESULT(FIRST, SECOND, THIRD, FOURTH, FIFTH)\
  680. (index == 0) ? STRING_VIEW_SETTING(FIRST) :\
  681. (index == 1) ? STRING_VIEW_SETTING(SECOND) :\
  682. (index == 2) ? STRING_VIEW_SETTING(THIRD) :\
  683. (index == 3) ? STRING_VIEW_SETTING(FOURTH) :\
  684. (index == 4) ? STRING_VIEW_SETTING(FIFTH) : StringView()
  685. StringView ssid(size_t index) {
  686. return WIFI_SETTING_STRING_RESULT(
  687. WIFI1_SSID,
  688. WIFI2_SSID,
  689. WIFI3_SSID,
  690. WIFI4_SSID,
  691. WIFI5_SSID
  692. );
  693. }
  694. StringView passphrase(size_t index) {
  695. return WIFI_SETTING_STRING_RESULT(
  696. WIFI1_PASS,
  697. WIFI2_PASS,
  698. WIFI3_PASS,
  699. WIFI4_PASS,
  700. WIFI5_PASS
  701. );
  702. }
  703. StringView ip(size_t index) {
  704. return WIFI_SETTING_STRING_RESULT(
  705. WIFI1_IP,
  706. WIFI2_IP,
  707. WIFI3_IP,
  708. WIFI4_IP,
  709. WIFI5_IP
  710. );
  711. }
  712. StringView gateway(size_t index) {
  713. return WIFI_SETTING_STRING_RESULT(
  714. WIFI1_GW,
  715. WIFI2_GW,
  716. WIFI3_GW,
  717. WIFI4_GW,
  718. WIFI5_GW
  719. );
  720. }
  721. StringView netmask(size_t index) {
  722. return WIFI_SETTING_STRING_RESULT(
  723. WIFI1_MASK,
  724. WIFI2_MASK,
  725. WIFI3_MASK,
  726. WIFI4_MASK,
  727. WIFI5_MASK
  728. );
  729. }
  730. StringView dns(size_t index) {
  731. return WIFI_SETTING_STRING_RESULT(
  732. WIFI1_DNS,
  733. WIFI2_DNS,
  734. WIFI3_DNS,
  735. WIFI4_DNS,
  736. WIFI5_DNS
  737. );
  738. }
  739. StringView bssid(size_t index) {
  740. return WIFI_SETTING_STRING_RESULT(
  741. WIFI1_BSSID,
  742. WIFI2_BSSID,
  743. WIFI3_BSSID,
  744. WIFI4_BSSID,
  745. WIFI5_BSSID
  746. );
  747. }
  748. #undef WIFI_SETTING_STRING_RESULT
  749. constexpr uint8_t channel(size_t index) {
  750. return (
  751. (index == 0) ? WIFI1_CHANNEL :
  752. (index == 1) ? WIFI2_CHANNEL :
  753. (index == 2) ? WIFI3_CHANNEL :
  754. (index == 3) ? WIFI4_CHANNEL :
  755. (index == 4) ? WIFI5_CHANNEL : 0
  756. );
  757. }
  758. } // namespace build
  759. namespace settings {
  760. namespace keys {
  761. PROGMEM_STRING(Mode, "wifiStaMode");
  762. PROGMEM_STRING(Ssid, "ssid");
  763. PROGMEM_STRING(Passphrase, "pass");
  764. PROGMEM_STRING(Ip, "ip");
  765. PROGMEM_STRING(Gateway, "gw");
  766. PROGMEM_STRING(Netmask, "mask");
  767. PROGMEM_STRING(Dns, "dns");
  768. PROGMEM_STRING(Bssid, "bssid");
  769. PROGMEM_STRING(Channel, "chan");
  770. } // namespace keys
  771. String from_string(espurna::settings::Key key, StringView defaultValue) {
  772. return getSetting(key, defaultValue);
  773. }
  774. IPAddress from_ipaddress(espurna::settings::Key key, StringView defaultValue) {
  775. return espurna::settings::internal::convert<IPAddress>(
  776. getSetting(key, defaultValue));
  777. }
  778. StaMode mode() {
  779. return getSetting(keys::Mode, build::mode());
  780. }
  781. String ssid(size_t index) {
  782. return from_string({keys::Ssid, index}, build::ssid(index));
  783. }
  784. String passphrase(size_t index) {
  785. return from_string({keys::Passphrase, index}, build::passphrase(index));
  786. }
  787. IPAddress ip(size_t index) {
  788. return from_ipaddress({keys::Ip, index}, build::ip(index));
  789. }
  790. IPAddress gateway(size_t index) {
  791. return from_ipaddress({keys::Gateway, index}, build::gateway(index));
  792. }
  793. IPAddress netmask(size_t index) {
  794. return from_ipaddress({keys::Netmask, index}, build::netmask(index));
  795. }
  796. IPAddress dns(size_t index) {
  797. return from_ipaddress({keys::Dns, index}, build::dns(index));
  798. }
  799. Mac bssid(size_t index) {
  800. return espurna::settings::internal::convert<Mac>(
  801. getSetting({keys::Bssid, index}, build::bssid(index)));
  802. }
  803. int8_t channel(size_t index) {
  804. return getSetting({keys::Channel, index}, build::channel(index));
  805. }
  806. namespace query {
  807. namespace internal {
  808. ID_VALUE(ip, settings::ip)
  809. ID_VALUE(gateway, settings::gateway)
  810. ID_VALUE(netmask, settings::netmask)
  811. ID_VALUE(dns, settings::dns)
  812. ID_VALUE(bssid, settings::bssid)
  813. ID_VALUE(channel, settings::channel)
  814. EXACT_VALUE(mode, settings::mode)
  815. } // namespace internal
  816. static constexpr std::array<espurna::settings::query::IndexedSetting, 8> Settings PROGMEM {
  817. {{keys::Ssid, settings::ssid},
  818. {keys::Passphrase, settings::passphrase},
  819. {keys::Ip, internal::ip},
  820. {keys::Gateway, internal::gateway},
  821. {keys::Netmask, internal::netmask},
  822. {keys::Dns, internal::dns},
  823. {keys::Bssid, internal::bssid},
  824. {keys::Channel, internal::channel}}
  825. };
  826. } // namespace query
  827. } // namespace settings
  828. IPAddress ip() {
  829. ip_info info;
  830. wifi_get_ip_info(STATION_IF, &info);
  831. return info.ip;
  832. }
  833. uint8_t channel() {
  834. return wifi_get_channel();
  835. }
  836. int8_t rssi() {
  837. return wifi_station_get_rssi();
  838. }
  839. Networks networks() {
  840. Networks out;
  841. for (size_t id = 0; id < build::NetworksMax; ++id) {
  842. auto ssid = settings::ssid(id);
  843. if (!ssid.length()) {
  844. break;
  845. }
  846. auto pass = settings::passphrase(id);
  847. auto ip = settings::ip(id);
  848. auto ipSettings = ip.isSet()
  849. ? IpSettings{
  850. std::move(ip),
  851. settings::netmask(id),
  852. settings::gateway(id),
  853. settings::dns(id)}
  854. : IpSettings{};
  855. Network network(std::move(ssid), settings::passphrase(id), std::move(ipSettings));
  856. auto channel = settings::channel(id);
  857. if (channel) {
  858. out.emplace_back(std::move(network), settings::bssid(id), channel);
  859. } else {
  860. out.push_back(std::move(network));
  861. }
  862. }
  863. return out;
  864. }
  865. size_t countNetworks() {
  866. size_t networks { 0 };
  867. for (size_t id = 0; id < build::NetworksMax; ++id) {
  868. auto ssid = settings::ssid(id);
  869. if (!ssid.length()) {
  870. break;
  871. }
  872. ++networks;
  873. }
  874. return networks;
  875. }
  876. // Note that authmode field is a our threshold, not the one selected by an AP
  877. Info info(const station_config& config) {
  878. return Info{
  879. convertBssid(config),
  880. config.threshold.authmode,
  881. rssi(),
  882. channel()};
  883. }
  884. Info info() {
  885. station_config config{};
  886. wifi_station_get_config(&config);
  887. return info(config);
  888. }
  889. StaNetwork current(const station_config& config) {
  890. return {
  891. convertBssid(config),
  892. convertSsid(config),
  893. convertPassphrase(config),
  894. rssi(),
  895. channel()};
  896. }
  897. StaNetwork current() {
  898. station_config config{};
  899. wifi_station_get_config(&config);
  900. return current(config);
  901. }
  902. #if WIFI_GRATUITOUS_ARP_SUPPORT
  903. namespace garp {
  904. namespace build {
  905. static constexpr auto IntervalMin = duration::Milliseconds{ WIFI_GRATUITOUS_ARP_INTERVAL_MIN };
  906. static constexpr auto IntervalMax = duration::Milliseconds{ WIFI_GRATUITOUS_ARP_INTERVAL_MAX };
  907. } // namespace build
  908. namespace settings {
  909. namespace internal {
  910. template <typename T>
  911. T randomInterval(T minimum, T maximum) {
  912. return T(::randomNumber(minimum.count(), maximum.count()));
  913. }
  914. duration::Milliseconds randomInterval() {
  915. return randomInterval(build::IntervalMin, build::IntervalMax);
  916. }
  917. } // namespace internal
  918. duration::Milliseconds interval() {
  919. static const auto defaultInterval = internal::randomInterval();
  920. return getSetting("wifiGarpIntvl", defaultInterval);
  921. }
  922. } // namespace settings
  923. namespace internal {
  924. timer::SystemTimer timer;
  925. bool wait { false };
  926. } // namespace internal
  927. bool send() {
  928. bool result { false };
  929. for (netif* interface = netif_list; interface != nullptr; interface = interface->next) {
  930. if (
  931. (interface->flags & NETIF_FLAG_ETHARP)
  932. && (interface->hwaddr_len == ETHARP_HWADDR_LEN)
  933. && (!ip4_addr_isany_val(*netif_ip4_addr(interface)))
  934. && (interface->flags & NETIF_FLAG_LINK_UP)
  935. && (interface->flags & NETIF_FLAG_UP)
  936. ) {
  937. etharp_gratuitous(interface);
  938. result = true;
  939. }
  940. }
  941. return result;
  942. }
  943. bool wait() {
  944. if (internal::wait) {
  945. return true;
  946. }
  947. internal::wait = true;
  948. return false;
  949. }
  950. void stop() {
  951. internal::timer.stop();
  952. }
  953. void reset() {
  954. internal::wait = false;
  955. }
  956. void start(duration::Milliseconds next) {
  957. internal::timer.repeat(next, reset);
  958. }
  959. } // namespace garp
  960. #endif
  961. namespace scan {
  962. namespace settings {
  963. namespace keys {
  964. PROGMEM_STRING(Enabled, "wifiScan");
  965. } // namespace keys
  966. } // namespace settings
  967. using SsidInfosPtr = std::shared_ptr<SsidInfos>;
  968. using Success = std::function<void(bss_info*)>;
  969. using Error = std::function<void(ScanError)>;
  970. struct Task {
  971. Task() = delete;
  972. Task(Success&& success, Error&& error) :
  973. _success(std::move(success)),
  974. _error(std::move(error))
  975. {}
  976. void success(bss_info* info) {
  977. _success(info);
  978. }
  979. void error(ScanError error) {
  980. _error(error);
  981. }
  982. private:
  983. Success _success;
  984. Error _error;
  985. };
  986. using TaskPtr = std::unique_ptr<Task>;
  987. namespace internal {
  988. bool flag { false };
  989. TaskPtr task;
  990. void stop() {
  991. flag = false;
  992. task = nullptr;
  993. }
  994. // STATUS comes from c_types.h, and it seems this is the only place that uses it
  995. // instead of some ESP-specific type.
  996. void complete(void* result, STATUS status) {
  997. if (status) { // aka anything but OK / 0
  998. task->error(ScanError::System);
  999. stop();
  1000. return;
  1001. }
  1002. size_t networks { 0ul };
  1003. bss_info* head = reinterpret_cast<bss_info*>(result);
  1004. for (bss_info* it = head; it; it = STAILQ_NEXT(it, next), ++networks) {
  1005. task->success(it);
  1006. }
  1007. if (!networks) {
  1008. task->error(ScanError::NoNetworks);
  1009. }
  1010. stop();
  1011. }
  1012. } // namespace internal
  1013. bool start(Success&& success, Error&& error) {
  1014. if (internal::flag) {
  1015. error(ScanError::Busy);
  1016. return false;
  1017. }
  1018. if (internal::task) {
  1019. error(ScanError::AlreadyScanning);
  1020. return false;
  1021. }
  1022. // Note that esp8266 callback only reports the resulting status and will (always?) timeout all by itself
  1023. // Default values are an active scan with some unspecified channel times.
  1024. // (zeroed out scan_config struct or simply nullptr)
  1025. // For example, c/p config from the current esp32 Arduino Core wrapper which are close to the values mentioned here:
  1026. // https://github.com/espressif/ESP8266_NONOS_SDK/issues/103#issuecomment-383440370
  1027. // Which could be useful if scanning needs to be more aggressive or switched into PASSIVE scan type
  1028. //scan_config config{};
  1029. //config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
  1030. //config.scan_time.active.min = 100;
  1031. //config.scan_time.active.max = 300;
  1032. if (wifi_station_scan(nullptr, &internal::complete)) {
  1033. internal::task = std::make_unique<Task>(std::move(success), std::move(error));
  1034. internal::flag = true;
  1035. return true;
  1036. }
  1037. error(ScanError::System);
  1038. return false;
  1039. }
  1040. // Alternative to the stock WiFi method, where we wait for the task to finish before returning
  1041. bool wait(Success&& success, Error&& error) {
  1042. auto result = start(std::move(success), std::move(error));
  1043. while (internal::task) {
  1044. delay(100);
  1045. }
  1046. return result;
  1047. }
  1048. // Another alternative to the stock WiFi method, return a shared Info list
  1049. // Caller is expected to wait for the scan to complete before using the contents
  1050. SsidInfosPtr ssidinfos() {
  1051. auto infos = std::make_shared<SsidInfos>();
  1052. start(
  1053. [infos](bss_info* found) {
  1054. infos->emplace_front(*found);
  1055. },
  1056. [infos](ScanError) {
  1057. infos->clear();
  1058. });
  1059. return infos;
  1060. }
  1061. } // namespace scan
  1062. bool enabled() {
  1063. return wifi::opmode() & wifi::OpmodeSta;
  1064. }
  1065. // XXX: WiFi.disconnect() also implicitly disables STA mode *and* erases the current STA config
  1066. void disconnect() {
  1067. if (enabled()) {
  1068. wifi_station_disconnect();
  1069. }
  1070. }
  1071. // Some workarounds for built-in WiFi management:
  1072. // - don't *intentionally* perist current SSID & PASS even when persistance is disabled from the Arduino Core side.
  1073. // while this seems like a good idea in theory, we end up with a bunch of async actions coming our way.
  1074. // - station disconnect events are linked with the connection routine as well, single WiFi::begin() may trigger up to
  1075. // 3 events (as observed with `WiFi::waitForConnectResult()`) before the connection loop stops further attempts
  1076. // - explicit OPMODE changes to both notify the userspace when the change actually happens (alternative is SDK event, but it is SYS context),
  1077. // since *all* STA & AP start-up methods will implicitly change the mode (`WiFi.begin()`, `WiFi.softAP()`, `WiFi.config()`)
  1078. void enable() {
  1079. ensure_opmode(opmode() | OpmodeSta);
  1080. wifi_station_disconnect();
  1081. delay(10);
  1082. if (wifi_station_get_reconnect_policy()) {
  1083. wifi_station_set_reconnect_policy(false);
  1084. }
  1085. if (wifi_station_get_auto_connect()) {
  1086. wifi_station_set_auto_connect(false);
  1087. }
  1088. }
  1089. void disable() {
  1090. ensure_opmode(opmode() & ~OpmodeSta);
  1091. }
  1092. namespace connection {
  1093. namespace internal {
  1094. struct Task {
  1095. static constexpr size_t SsidMax { sizeof(station_config::ssid) };
  1096. static constexpr size_t PassphraseMin { 8ul };
  1097. static constexpr size_t PassphraseMax { sizeof(station_config::password) };
  1098. static constexpr int8_t RssiThreshold { -127 };
  1099. using Iterator = Networks::iterator;
  1100. Task() = delete;
  1101. Task(const Task&) = delete;
  1102. Task(Task&&) = delete;
  1103. Task(String hostname, Networks networks, int retries) :
  1104. _hostname(std::move(hostname)),
  1105. _networks(std::move(networks)),
  1106. _begin(_networks.begin()),
  1107. _end(_networks.end()),
  1108. _current(_begin),
  1109. _retries(retries),
  1110. _retry(_retries)
  1111. {}
  1112. bool empty() const {
  1113. return _networks.empty();
  1114. }
  1115. size_t count() const {
  1116. return _networks.size();
  1117. }
  1118. bool done() const {
  1119. return _current == _end;
  1120. }
  1121. bool next() {
  1122. if (!done()) {
  1123. if (--_retry < 0) {
  1124. _retry = _retries;
  1125. _current = std::next(_current);
  1126. }
  1127. return !done();
  1128. }
  1129. return false;
  1130. }
  1131. bool connect() const {
  1132. if (!done() && sta::enabled()) {
  1133. // Need to call this to cancel SDK tasks (previous scan, connection, etc.)
  1134. // Otherwise, it will fail the initial attempt and force a retry.
  1135. sta::disconnect();
  1136. // SDK sends EVENT_STAMODE_DISCONNECTED right after the disconnect() call, which is likely to happen
  1137. // after being connected and disconnecting for the first time. Not doing this will cause the connection loop
  1138. // to cancel the `wait` lock too early, forcing the Timeout state despite the EVENT_STAMODE_GOTIP coming in later.
  1139. // Allow the event to come in right now to allow `wifi_station_connect()` down below trigger a real one.
  1140. yield();
  1141. auto& network = *_current;
  1142. if (!network.dhcp()) {
  1143. auto& ipsettings = network.ipSettings();
  1144. wifi_station_dhcpc_stop();
  1145. auto current = ip();
  1146. auto info = ipsettings.toIpInfo();
  1147. if (!wifi_set_ip_info(STATION_IF, &info)) {
  1148. return false;
  1149. }
  1150. dns_setserver(0, ipsettings.dns());
  1151. if (current.isSet() && (current != info.ip)) {
  1152. #undef netif_set_addr
  1153. netif_set_addr(eagle_lwip_getif(STATION_IF), &info.ip, &info.netmask, &info.gw);
  1154. }
  1155. }
  1156. // Only the STA cares about the hostname setting
  1157. // esp8266 specific Arduino-specific - this sets lwip internal structs related to the DHCPc
  1158. WiFi.hostname(_hostname);
  1159. // The rest is related to the connection routine
  1160. // SSID & Passphrase are u8 arrays, with 0 at the end when the string is less than it's size
  1161. // Perform checks earlier, before calling SDK config functions, since it would not reflect in the connection
  1162. // state correctly, and we would need to use the Event API once again.
  1163. station_config config{};
  1164. auto& ssid = network.ssid();
  1165. if (!ssid.length() || (ssid.length() > SsidMax)) {
  1166. return false;
  1167. }
  1168. std::copy(ssid.c_str(), ssid.c_str() + ssid.length(),
  1169. reinterpret_cast<char*>(config.ssid));
  1170. if (ssid.length() < SsidMax) {
  1171. config.ssid[ssid.length()] = 0;
  1172. }
  1173. auto& pass = network.passphrase();
  1174. if (pass.length()) {
  1175. if ((pass.length() < PassphraseMin) || (pass.length() > PassphraseMax)) {
  1176. return false;
  1177. }
  1178. config.threshold.authmode = AUTH_WPA_PSK;
  1179. std::copy(pass.c_str(), pass.c_str() + pass.length(),
  1180. reinterpret_cast<char*>(config.password));
  1181. if (pass.length() < PassphraseMax) {
  1182. config.password[pass.length()] = 0;
  1183. }
  1184. } else {
  1185. config.threshold.authmode = AUTH_OPEN;
  1186. config.password[0] = 0;
  1187. }
  1188. config.threshold.rssi = RssiThreshold;
  1189. if (network.channel()) {
  1190. auto& bssid = network.bssid();
  1191. std::copy(bssid.begin(), bssid.end(), config.bssid);
  1192. config.bssid_set = 1;
  1193. }
  1194. // TODO: check every return value?
  1195. // TODO: is it sufficient for the event to fire? otherwise,
  1196. // there needs to be a manual timeout code after this returns true
  1197. wifi_station_set_config_current(&config);
  1198. if (!wifi_station_connect()) {
  1199. return false;
  1200. }
  1201. if (network.channel()) {
  1202. wifi_set_channel(network.channel());
  1203. }
  1204. if (network.dhcp() && (wifi_station_dhcpc_status() != DHCP_STARTED)) {
  1205. wifi_station_dhcpc_start();
  1206. }
  1207. return true;
  1208. }
  1209. return false;
  1210. }
  1211. Networks& networks() {
  1212. return _networks;
  1213. }
  1214. private:
  1215. String _hostname;
  1216. Networks _networks;
  1217. Iterator _begin;
  1218. Iterator _end;
  1219. Iterator _current;
  1220. const int _retries;
  1221. int _retry;
  1222. };
  1223. using ActionPtr = void(*)();
  1224. void action_next() {
  1225. action(Action::StationContinueConnect);
  1226. }
  1227. void action_new() {
  1228. action(Action::StationConnect);
  1229. }
  1230. sta::scan::SsidInfosPtr scanResults;
  1231. Networks preparedNetworks;
  1232. bool connected { false };
  1233. bool wait { false };
  1234. timer::SystemTimer timer;
  1235. bool persist { false };
  1236. using TaskPtr = std::unique_ptr<Task>;
  1237. TaskPtr task;
  1238. } // namespace internal
  1239. void persist(bool value) {
  1240. internal::persist = value;
  1241. }
  1242. bool persist() {
  1243. return internal::persist;
  1244. }
  1245. void stop() {
  1246. scan::internal::flag = false;
  1247. internal::scanResults = nullptr;
  1248. internal::preparedNetworks.clear();
  1249. internal::timer.stop();
  1250. internal::task.reset();
  1251. }
  1252. bool start(String&& hostname) {
  1253. if (!internal::task) {
  1254. internal::task = std::make_unique<internal::Task>(
  1255. std::move(hostname),
  1256. std::move(internal::preparedNetworks),
  1257. build::ConnectionRetries);
  1258. internal::timer.stop();
  1259. return true;
  1260. }
  1261. internal::preparedNetworks.clear();
  1262. return false;
  1263. }
  1264. void schedule(duration::Milliseconds next, internal::ActionPtr ptr) {
  1265. internal::timer.once(next, ptr);
  1266. DEBUG_MSG_P(PSTR("[WIFI] Next connection attempt in %u (ms)\n"), next.count());
  1267. }
  1268. void schedule_next() {
  1269. schedule(build::ConnectionInterval, internal::action_next);
  1270. }
  1271. void schedule_new(duration::Milliseconds next) {
  1272. schedule(next, internal::action_new);
  1273. }
  1274. void schedule_new() {
  1275. schedule_new(build::ReconnectionInterval);
  1276. }
  1277. bool next() {
  1278. return internal::task->next();
  1279. }
  1280. bool connect() {
  1281. scan::internal::flag = true;
  1282. if (internal::task->connect()) {
  1283. internal::wait = true;
  1284. return true;
  1285. }
  1286. scan::internal::flag = false;
  1287. return false;
  1288. }
  1289. // Note that `wifi_station_get_connect_status()` may never actually change the state from CONNECTING when AP is not available.
  1290. // Wait for the WiFi stack event instead (handled on setup with a static object) and continue after it is either connected or disconnected
  1291. bool wait() {
  1292. return internal::wait;
  1293. }
  1294. // TODO(Core 2.7.4): `WiFi.isConnected()` is a simple `wifi_station_get_connect_status() == STATION_GOT_IP`,
  1295. // Meaning, it will never detect link up / down updates when AP silently kills the connection or something else unexpected happens.
  1296. // Running JustWiFi with autoconnect + reconnect enabled, it silently avoided the issue b/c the SDK reconnect routine disconnected the STA,
  1297. // causing our state machine to immediately cancel it (since `WL_CONNECTED != WiFi.status()`) and then try to connect again using it's own loop.
  1298. // We could either (* is used currently):
  1299. // - (*) listen for the SDK event through the `WiFi.onStationModeDisconnected()`
  1300. // - ( ) poll NETIF_FLAG_LINK_UP for the lwip's netif, since the SDK will bring the link down on disconnection
  1301. // find the `interface` in the `netif_list`, where `interface->num == STATION_IF`
  1302. // - ( ) use lwip's netif event system from the recent Core, track UP and DOWN for a specific interface number
  1303. // this one is probably only used internally, thus should be treated as a private API
  1304. // - ( ) poll whether `wifi_get_ip_info(STATION_IF, &ip);` is set to something valid
  1305. // (tuple of ip, gw and mask)
  1306. // - ( ) poll `WiFi.localIP().isSet()`
  1307. // (will be unset when the link is down)
  1308. // placing status into a simple bool to avoid extracting ip info every time someone needs to check the connection
  1309. bool connected() {
  1310. return internal::connected;
  1311. }
  1312. bool connecting() {
  1313. return static_cast<bool>(internal::task);
  1314. }
  1315. bool lost() {
  1316. static bool last { internal::connected };
  1317. if (internal::connected != last) {
  1318. last = internal::connected;
  1319. return !last;
  1320. }
  1321. return false;
  1322. }
  1323. void prepare(Networks&& networks) {
  1324. std::swap(internal::preparedNetworks, networks);
  1325. }
  1326. bool prepared() {
  1327. return internal::preparedNetworks.size() > 0;
  1328. }
  1329. } // namespace connection
  1330. bool connected() {
  1331. return connection::connected();
  1332. }
  1333. bool connecting() {
  1334. return connection::connecting();
  1335. }
  1336. bool scanning() {
  1337. return static_cast<bool>(scan::internal::task);
  1338. }
  1339. // TODO: generic onEvent is deprecated on esp8266 in favour of the event-specific
  1340. // methods returning 'cancelation' token. Right now it is a basic shared_ptr with an std function inside of it.
  1341. // esp32 only has a generic onEvent, but event names are not compatible with the esp8266 version.
  1342. //
  1343. // TODO: instead of bool, do a state object that is 'armed' before use and it is possible to make sure there's an expected value swap between `true` and `false`
  1344. // (i.e. 'disarmed', 'armed-for', 'received-success', 'received-failure'. where 'armed-for' only reacts on a specific assignment, and the consumer
  1345. // checks whether 'received-success' had happend, and also handles 'received-failure'. when 'disarmed', value status does not change)
  1346. // TODO: ...and a timeout? most of the time, these happen right after switch into the system task. but, since the sdk funcs don't block until success
  1347. // (or at all, for anything), it might be nice to have some safeguards.
  1348. void init() {
  1349. static auto disconnected = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected&) {
  1350. connection::internal::wait = false;
  1351. connection::internal::connected = false;
  1352. });
  1353. static auto connected = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP&) {
  1354. connection::internal::wait = false;
  1355. connection::internal::connected = true;
  1356. });
  1357. disconnect();
  1358. disable();
  1359. yield();
  1360. }
  1361. void toggle() {
  1362. auto current = enabled();
  1363. connection::persist(!current);
  1364. action(current
  1365. ? Action::StationDisconnect
  1366. : Action::StationConnect);
  1367. }
  1368. namespace scan {
  1369. namespace build {
  1370. constexpr bool enabled() {
  1371. return 1 == WIFI_SCAN_NETWORKS;
  1372. }
  1373. } // namespace build
  1374. namespace settings {
  1375. bool enabled() {
  1376. return getSetting(keys::Enabled, build::enabled());
  1377. }
  1378. namespace query {
  1379. EXACT_VALUE(enabled, settings::enabled)
  1380. } // namespace query
  1381. } // namespace settings
  1382. namespace periodic {
  1383. namespace build {
  1384. static constexpr auto Interval = duration::Milliseconds{ WIFI_SCAN_RSSI_CHECK_INTERVAL };
  1385. static constexpr auto Checks = int8_t{ WIFI_SCAN_RSSI_CHECKS };
  1386. constexpr int8_t threshold() {
  1387. return WIFI_SCAN_RSSI_THRESHOLD;
  1388. }
  1389. } // namespace build
  1390. namespace settings {
  1391. namespace keys {
  1392. PROGMEM_STRING(Threshold, "wifiScanRssi");
  1393. } // namespace keys
  1394. int8_t threshold() {
  1395. return getSetting(FPSTR(keys::Threshold), build::threshold());
  1396. }
  1397. namespace query {
  1398. EXACT_VALUE(threshold, settings::threshold)
  1399. } // namespace query
  1400. } // namespace settings
  1401. namespace internal {
  1402. int8_t threshold { build::threshold() };
  1403. int8_t counter { build::Checks };
  1404. timer::SystemTimer timer;
  1405. void task() {
  1406. if (!sta::connected()) {
  1407. counter = build::Checks;
  1408. return;
  1409. }
  1410. auto rssi = sta::rssi();
  1411. if (rssi > threshold) {
  1412. counter = build::Checks;
  1413. } else if (rssi < threshold) {
  1414. if (counter < 0) {
  1415. return;
  1416. }
  1417. if (!--counter) {
  1418. action(Action::StationTryConnectBetter);
  1419. }
  1420. }
  1421. }
  1422. void start() {
  1423. counter = build::Checks;
  1424. timer.repeat(build::Interval, task);
  1425. }
  1426. void stop() {
  1427. counter = build::Checks;
  1428. timer.stop();
  1429. }
  1430. } // namespace internal
  1431. void threshold(int8_t value) {
  1432. internal::threshold = value;
  1433. }
  1434. void stop() {
  1435. internal::stop();
  1436. }
  1437. void start() {
  1438. internal::start();
  1439. }
  1440. bool check() {
  1441. if (internal::counter <= 0) {
  1442. internal::counter = build::Checks;
  1443. return true;
  1444. }
  1445. return false;
  1446. }
  1447. } // namespace periodic
  1448. } // namespace scan
  1449. namespace connection {
  1450. // After scan attempt, generate a new networks list based on the results sorted by the rssi value.
  1451. // For the initial connection, add every matching network with the scan result bssid and channel info.
  1452. // For the attempt to find a better network, filter out every network with worse than the current network's rssi
  1453. void scanNetworks() {
  1454. internal::scanResults = sta::scan::ssidinfos();
  1455. }
  1456. bool suitableNetwork(const Network& network, const SsidInfo& ssidInfo) {
  1457. return (ssidInfo.ssid() == network.ssid())
  1458. && ((ssidInfo.info().authmode() != AUTH_OPEN)
  1459. ? network.passphrase().length()
  1460. : !network.passphrase().length());
  1461. }
  1462. bool scanProcessResults(int8_t threshold) {
  1463. if (internal::scanResults) {
  1464. decltype(internal::scanResults) results;
  1465. std::swap(results, internal::scanResults);
  1466. results->sort();
  1467. if (threshold < 0) {
  1468. results->remove_if(
  1469. [threshold](const SsidInfo& result) {
  1470. return result.info().rssi() < threshold;
  1471. });
  1472. }
  1473. decltype(internal::preparedNetworks) networks;
  1474. std::swap(networks, internal::preparedNetworks);
  1475. decltype(internal::preparedNetworks) sortedNetworks;
  1476. for (auto& result : *results) {
  1477. for (auto& network : networks) {
  1478. if (suitableNetwork(network, result)) {
  1479. sortedNetworks.emplace_back(network, result.info().bssid(), result.info().channel());
  1480. break;
  1481. }
  1482. }
  1483. }
  1484. std::swap(sortedNetworks, internal::preparedNetworks);
  1485. internal::scanResults.reset();
  1486. }
  1487. return internal::preparedNetworks.size() > 0;
  1488. }
  1489. bool scanProcessResults(const Info& info) {
  1490. return scanProcessResults(info.rssi());
  1491. }
  1492. bool scanProcessResults() {
  1493. return scanProcessResults(0);
  1494. }
  1495. } // namespace connection
  1496. void configure() {
  1497. auto enabled = (StaMode::Enabled == sta::settings::mode());
  1498. connection::persist(enabled);
  1499. action(enabled
  1500. ? Action::StationConnect
  1501. : Action::StationDisconnect);
  1502. scan::periodic::threshold(
  1503. scan::periodic::settings::threshold());
  1504. #if WIFI_GRATUITOUS_ARP_SUPPORT
  1505. auto interval = garp::settings::interval();
  1506. if (interval.count()) {
  1507. garp::start(interval);
  1508. } else {
  1509. garp::stop();
  1510. }
  1511. #endif
  1512. }
  1513. } // namespace sta
  1514. // -----------------------------------------------------------------------------
  1515. // ACCESS POINT
  1516. // -----------------------------------------------------------------------------
  1517. namespace ap {
  1518. namespace build {
  1519. static constexpr size_t SsidMax { sizeof(softap_config::ssid) };
  1520. static constexpr size_t PassphraseMin { 8u };
  1521. static constexpr size_t PassphraseMax { sizeof(softap_config::password) };
  1522. static constexpr int Hidden { 0 };
  1523. static constexpr uint8_t ConnectionsMax { 4u };
  1524. PROGMEM_STRING(ApSsid, WIFI_AP_SSID);
  1525. constexpr StringView ssid() {
  1526. return ApSsid;
  1527. }
  1528. constexpr bool hasSsid() {
  1529. return ssid().length() > 0;
  1530. }
  1531. PROGMEM_STRING(ApPass, WIFI_AP_PASS);
  1532. constexpr StringView passphrase() {
  1533. return ApPass;
  1534. }
  1535. constexpr bool hasPassphrase() {
  1536. return passphrase().length() > 0;
  1537. }
  1538. constexpr bool captive() {
  1539. return 1 == WIFI_AP_CAPTIVE_ENABLED;
  1540. }
  1541. constexpr ApMode mode() {
  1542. return WIFI_AP_MODE;
  1543. }
  1544. constexpr uint8_t channel() {
  1545. return WIFI_AP_CHANNEL;
  1546. }
  1547. } // namespace build
  1548. namespace settings {
  1549. namespace keys {
  1550. PROGMEM_STRING(Mode, "wifiApMode");
  1551. PROGMEM_STRING(Ssid, "wifiApSsid");
  1552. PROGMEM_STRING(Passphrase, "wifiApPass");
  1553. PROGMEM_STRING(Channel, "wifiApChan");
  1554. [[gnu::unused]] PROGMEM_STRING(Captive, "wifiApCaptive");
  1555. } // namespace keys
  1556. ApMode mode() {
  1557. return getSetting(FPSTR(keys::Mode), build::mode());
  1558. }
  1559. String defaultSsid() {
  1560. return String(systemIdentifier());
  1561. }
  1562. String ssid() {
  1563. return getSetting(FPSTR(keys::Ssid), build::hasSsid()
  1564. ? build::ssid()
  1565. : systemHostname());
  1566. }
  1567. String passphrase() {
  1568. return getSetting(FPSTR(keys::Passphrase), build::hasPassphrase()
  1569. ? build::passphrase()
  1570. : systemPassword());
  1571. }
  1572. int8_t channel() {
  1573. return getSetting(FPSTR(keys::Channel), build::channel());
  1574. }
  1575. [[gnu::unused]]
  1576. bool captive() {
  1577. return getSetting(FPSTR(keys::Captive), build::captive());
  1578. }
  1579. namespace query {
  1580. namespace internal {
  1581. EXACT_VALUE(captive, ap::settings::captive)
  1582. EXACT_VALUE(channel, ap::settings::channel)
  1583. EXACT_VALUE(mode, ap::settings::mode)
  1584. #undef ID_VALUE
  1585. #undef EXACT_VALUE
  1586. } // namespace internal
  1587. } // namespace query
  1588. } // namespace settings
  1589. namespace internal {
  1590. #if WIFI_AP_CAPTIVE_SUPPORT
  1591. bool captive { build::captive() };
  1592. DNSServer dns;
  1593. #endif
  1594. void start(String&& defaultSsid, String&& ssid, String&& passphrase, uint8_t channel) {
  1595. // Always generate valid AP config, even when user-provided credentials fail to comply with the requirements
  1596. // TODO: configuration routine depends on a lwip dhcpserver, which is a custom module made specifically for the ESP.
  1597. // while it's possible to hijack this and control the process manually, right now it's easier to delegate this to the Core helpers
  1598. // (plus, it makes it not compatible with the esp-idf stack anyway, since wifi_softap_dhcps_... calls don't do anything here)
  1599. const char* apSsid {
  1600. (ssid.length() && (ssid.length() < build::SsidMax))
  1601. ? ssid.c_str() : defaultSsid.c_str() };
  1602. const char* apPass {
  1603. (passphrase.length() \
  1604. && (passphrase.length() >= build::PassphraseMin) \
  1605. && (passphrase.length() < build::PassphraseMax))
  1606. ? passphrase.c_str() : nullptr };
  1607. // TODO: when using `softap_config`, can also tweak the beacon intvl
  1608. // static constexpr uint16_t BeaconInterval { 100u };
  1609. WiFi.softAP(apSsid, apPass, channel, build::Hidden, build::ConnectionsMax);
  1610. }
  1611. } // namespace internal
  1612. #if WIFI_AP_CAPTIVE_SUPPORT
  1613. void captive(bool value) {
  1614. internal::captive = value;
  1615. }
  1616. bool captive() {
  1617. return internal::captive;
  1618. }
  1619. void dnsLoop() {
  1620. internal::dns.processNextRequest();
  1621. }
  1622. #endif
  1623. IPAddress ip() {
  1624. ip_info info;
  1625. wifi_get_ip_info(SOFTAP_IF, &info);
  1626. return info.ip;
  1627. }
  1628. void enable() {
  1629. ensure_opmode(opmode() | OpmodeAp);
  1630. }
  1631. void disable() {
  1632. ensure_opmode(opmode() & ~OpmodeAp);
  1633. }
  1634. bool enabled() {
  1635. return opmode() & OpmodeAp;
  1636. }
  1637. void toggle() {
  1638. action(ap::enabled()
  1639. ? Action::AccessPointStop
  1640. : Action::AccessPointStart);
  1641. }
  1642. void stop() {
  1643. #if WIFI_AP_CAPTIVE_SUPPORT
  1644. internal::dns.stop();
  1645. #endif
  1646. WiFi.softAPdisconnect();
  1647. }
  1648. void start(String&& defaultSsid, String&& ssid, String&& passphrase, uint8_t channel) {
  1649. internal::start(std::move(defaultSsid), std::move(ssid),
  1650. std::move(passphrase), channel);
  1651. #if WIFI_AP_CAPTIVE_SUPPORT
  1652. if (internal::captive) {
  1653. internal::dns.setErrorReplyCode(DNSReplyCode::NoError);
  1654. internal::dns.start(53, "*", ip());
  1655. } else {
  1656. internal::dns.stop();
  1657. }
  1658. #endif
  1659. }
  1660. SoftApNetwork current() {
  1661. softap_config config{};
  1662. wifi_softap_get_config(&config);
  1663. Mac mac;
  1664. WiFi.softAPmacAddress(mac.data());
  1665. return {
  1666. mac,
  1667. convertSsid(config),
  1668. convertPassphrase(config),
  1669. config.channel,
  1670. config.authmode};
  1671. }
  1672. void init() {
  1673. disable();
  1674. }
  1675. size_t stations() {
  1676. return WiFi.softAPgetStationNum();
  1677. }
  1678. namespace fallback {
  1679. namespace build {
  1680. constexpr auto Timeout = duration::Milliseconds{ WIFI_FALLBACK_TIMEOUT };
  1681. } // namespace build
  1682. namespace internal {
  1683. auto timeout = build::Timeout;
  1684. bool enabled { false };
  1685. timer::SystemTimer timer;
  1686. } // namespace internal
  1687. void enable() {
  1688. internal::enabled = true;
  1689. }
  1690. void disable() {
  1691. internal::enabled = false;
  1692. }
  1693. bool enabled() {
  1694. return internal::enabled;
  1695. }
  1696. void remove() {
  1697. internal::timer.stop();
  1698. }
  1699. void check();
  1700. void schedule() {
  1701. internal::timer.repeat(
  1702. internal::timeout,
  1703. []() {
  1704. action(Action::AccessPointFallbackCheck);
  1705. });
  1706. }
  1707. void check() {
  1708. if (ap::enabled()
  1709. && sta::connected()
  1710. && !ap::stations())
  1711. {
  1712. action(Action::AccessPointStop);
  1713. return;
  1714. }
  1715. }
  1716. } // namespace fallback
  1717. void configure() {
  1718. auto current = settings::mode();
  1719. if (ApMode::Fallback == current) {
  1720. fallback::enable();
  1721. } else {
  1722. fallback::disable();
  1723. fallback::remove();
  1724. action((ApMode::Enabled == current)
  1725. ? Action::AccessPointStart
  1726. : Action::AccessPointStop);
  1727. }
  1728. #if WIFI_AP_CAPTIVE_SUPPORT
  1729. captive(settings::captive());
  1730. #endif
  1731. }
  1732. } // namespace ap
  1733. // -----------------------------------------------------------------------------
  1734. // SETTINGS
  1735. // -----------------------------------------------------------------------------
  1736. namespace settings {
  1737. namespace query {
  1738. static constexpr std::array<espurna::settings::query::Setting, 11> Settings PROGMEM {
  1739. {{ap::settings::keys::Ssid, ap::settings::ssid},
  1740. {ap::settings::keys::Passphrase, ap::settings::passphrase},
  1741. {ap::settings::keys::Captive, ap::settings::query::internal::captive},
  1742. {ap::settings::keys::Channel, ap::settings::query::internal::channel},
  1743. {ap::settings::keys::Mode, ap::settings::query::internal::mode},
  1744. {sta::settings::keys::Mode, sta::settings::query::internal::mode},
  1745. {sta::scan::settings::keys::Enabled, sta::scan::settings::query::enabled},
  1746. {sta::scan::periodic::settings::keys::Threshold, sta::scan::periodic::settings::query::threshold},
  1747. {settings::keys::TxPower, query::internal::txPower},
  1748. {settings::keys::Sleep, query::internal::sleep},
  1749. {settings::keys::Boot, query::internal::bootMode},
  1750. }
  1751. };
  1752. // indexed settings for 'sta' connections
  1753. bool checkIndexedPrefix(StringView key) {
  1754. return espurna::settings::query::IndexedSetting::findSamePrefix(
  1755. sta::settings::query::Settings, key);
  1756. }
  1757. // generic 'ap' and 'modem' configuration
  1758. bool checkExactPrefix(StringView key) {
  1759. PROGMEM_STRING(Prefix, "wifi");
  1760. if (espurna::settings::query::samePrefix(key, Prefix)) {
  1761. return true;
  1762. }
  1763. return false;
  1764. }
  1765. String findIndexedValueFrom(StringView key) {
  1766. using espurna::settings::query::IndexedSetting;
  1767. return IndexedSetting::findValueFrom(
  1768. sta::countNetworks(),
  1769. sta::settings::query::Settings, key);
  1770. }
  1771. String findValueFrom(StringView key) {
  1772. using espurna::settings::query::Setting;
  1773. return Setting::findValueFrom(Settings, key);
  1774. }
  1775. void setup() {
  1776. // TODO: small implementation detail - when searching, these
  1777. // should be registered like this so the 'exact' is processed first
  1778. settingsRegisterQueryHandler({
  1779. .check = checkIndexedPrefix,
  1780. .get = findIndexedValueFrom,
  1781. });
  1782. settingsRegisterQueryHandler({
  1783. .check = checkExactPrefix,
  1784. .get = findValueFrom,
  1785. });
  1786. }
  1787. } // namespace query
  1788. void configure() {
  1789. ap::configure();
  1790. sta::configure();
  1791. sleep_type(settings::sleep());
  1792. tx_power(settings::txPower());
  1793. }
  1794. } // namespace settings
  1795. // -----------------------------------------------------------------------------
  1796. // TERMINAL
  1797. // -----------------------------------------------------------------------------
  1798. #if TERMINAL_SUPPORT
  1799. namespace terminal {
  1800. namespace commands {
  1801. PROGMEM_STRING(Stations, "WIFI.STATIONS");
  1802. void stations(::terminal::CommandContext&& ctx) {
  1803. size_t stations { 0ul };
  1804. for (auto* it = wifi_softap_get_station_info(); it; it = STAILQ_NEXT(it, next), ++stations) {
  1805. ctx.output.printf_P(PSTR("%s %s\n"),
  1806. debug::mac(convertBssid(*it)).c_str(),
  1807. debug::ip(it->ip).c_str());
  1808. }
  1809. wifi_softap_free_station_info();
  1810. if (!stations) {
  1811. terminalError(ctx, F("No stations connected"));
  1812. return;
  1813. }
  1814. terminalOK(ctx);
  1815. }
  1816. PROGMEM_STRING(Network, "NETWORK");
  1817. void network(::terminal::CommandContext&& ctx) {
  1818. for (auto& addr : addrList) {
  1819. ctx.output.printf_P(PSTR("%s%d %4s %6s "),
  1820. addr.ifname().c_str(),
  1821. addr.ifnumber(),
  1822. addr.ifUp() ? "up" : "down",
  1823. addr.isLocal() ? "local" : "global");
  1824. #if LWIP_IPV6
  1825. if (addr.isV4()) {
  1826. #endif
  1827. ctx.output.printf_P(PSTR("ip %s gateway %s mask %s\n"),
  1828. debug::ip(addr.ipv4()).c_str(),
  1829. debug::ip(addr.gw()).c_str(),
  1830. debug::ip(addr.netmask()).c_str());
  1831. #if LWIP_IPV6
  1832. } else {
  1833. // TODO: ip6_addr[...] array is included in the list
  1834. // we'll just see another entry
  1835. // TODO: routing info is not attached to the netif :/
  1836. // ref. nd6.h (and figure out what it does)
  1837. ctx.output.printf_P(PSTR("ip %s\n"),
  1838. debug::ip(netif->ip6_addr[i]).c_str());
  1839. }
  1840. #endif
  1841. }
  1842. for (int n = 0; n < DNS_MAX_SERVERS; ++n) {
  1843. auto ip = IPAddress(dns_getserver(n));
  1844. if (!ip.isSet()) {
  1845. break;
  1846. }
  1847. ctx.output.printf_P(PSTR("dns %s\n"), debug::ip(ip).c_str());
  1848. }
  1849. }
  1850. PROGMEM_STRING(Wifi, "WIFI");
  1851. void wifi(::terminal::CommandContext&& ctx) {
  1852. if (ctx.argv.size() == 2) {
  1853. auto id = espurna::settings::internal::convert<size_t>(ctx.argv[1]);
  1854. if (id < sta::build::NetworksMax) {
  1855. settingsDump(ctx, sta::settings::query::Settings, id);
  1856. return;
  1857. }
  1858. terminalError(ctx, F("Network ID out of configurable range"));
  1859. return;
  1860. }
  1861. const auto mode = wifi::opmode();
  1862. ctx.output.printf_P(PSTR("OPMODE: %s\n"),
  1863. debug::opmode(mode).c_str());
  1864. const auto sleep = wifi::sleep_type();
  1865. if (sleep != NONE_SLEEP_T) {
  1866. ctx.output.printf_P(PSTR("SLEEP: %s\n"),
  1867. debug::sleep_type(sleep).c_str());
  1868. }
  1869. if (mode & OpmodeAp) {
  1870. auto current = ap::current();
  1871. ctx.output.printf_P(PSTR("SoftAP: bssid %s channel %hhu auth %s\n"),
  1872. debug::mac(current.bssid).c_str(),
  1873. current.channel,
  1874. debug::authmode(current.authmode).c_str(),
  1875. current.ssid.c_str(),
  1876. current.passphrase.c_str());
  1877. if (ap::fallback::enabled() && ap::fallback::internal::timer) {
  1878. ctx.output.printf_P(PSTR("fallback check every %u ms\n"),
  1879. ap::fallback::build::Timeout.count());
  1880. }
  1881. }
  1882. if (mode & OpmodeSta) {
  1883. if (sta::connected()) {
  1884. station_config config{};
  1885. wifi_station_get_config(&config);
  1886. auto network = sta::current(config);
  1887. ctx.output.printf_P(PSTR("STA: bssid %s rssi %hhd channel %hhu ssid \"%s\"\n"),
  1888. debug::mac(network.bssid).c_str(),
  1889. network.rssi, network.channel, network.ssid.c_str());
  1890. } else {
  1891. ctx.output.printf_P(PSTR("STA: %s\n"),
  1892. sta::connecting() ? "connecting" : "disconnected");
  1893. }
  1894. }
  1895. settingsDump(ctx, settings::query::Settings);
  1896. terminalOK(ctx);
  1897. }
  1898. PROGMEM_STRING(Reset, "WIFI.RESET");
  1899. void reset(::terminal::CommandContext&& ctx) {
  1900. sta::disconnect();
  1901. settings::configure();
  1902. terminalOK(ctx);
  1903. }
  1904. PROGMEM_STRING(Station, "WIFI.STA");
  1905. void station(::terminal::CommandContext&& ctx) {
  1906. sta::toggle();
  1907. terminalOK(ctx);
  1908. }
  1909. PROGMEM_STRING(AccessPoint, "WIFI.AP");
  1910. void access_point(::terminal::CommandContext&& ctx) {
  1911. ap::toggle();
  1912. terminalOK(ctx);
  1913. }
  1914. PROGMEM_STRING(Off, "WIFI.OFF");
  1915. void off(::terminal::CommandContext&& ctx) {
  1916. action(Action::TurnOff);
  1917. terminalOK(ctx);
  1918. }
  1919. PROGMEM_STRING(On, "WIFI.ON");
  1920. void on(::terminal::CommandContext&& ctx) {
  1921. action(Action::TurnOn);
  1922. terminalOK(ctx);
  1923. }
  1924. PROGMEM_STRING(Scan, "WIFI.SCAN");
  1925. void scan(::terminal::CommandContext&& ctx) {
  1926. sta::scan::wait(
  1927. [&](bss_info* info) {
  1928. ctx.output.printf_P(PSTR("BSSID: %s AUTH: %11s RSSI: %3hhd CH: %2hhu SSID: %s\n"),
  1929. debug::mac(convertBssid(*info)).c_str(),
  1930. debug::authmode(info->authmode).c_str(),
  1931. info->rssi,
  1932. info->channel,
  1933. convertSsid(*info).c_str()
  1934. );
  1935. },
  1936. [&](ScanError error) {
  1937. terminalError(ctx, debug::error(error));
  1938. }
  1939. );
  1940. }
  1941. static constexpr ::terminal::Command List[] PROGMEM {
  1942. {Stations, commands::stations},
  1943. {Network, commands::network},
  1944. {Wifi, commands::wifi},
  1945. {Reset, commands::reset},
  1946. {Station, commands::station},
  1947. {AccessPoint, commands::access_point},
  1948. {Scan, commands::scan},
  1949. {Off, commands::off},
  1950. {On, commands::on},
  1951. };
  1952. } // namespace commands
  1953. void init() {
  1954. espurna::terminal::add(commands::List);
  1955. }
  1956. } // namespace terminal
  1957. #endif
  1958. // -----------------------------------------------------------------------------
  1959. // WEB
  1960. // -----------------------------------------------------------------------------
  1961. #if WEB_SUPPORT
  1962. namespace web {
  1963. void onConnected(JsonObject& root) {
  1964. for (const auto& setting : settings::query::Settings) {
  1965. root[FPSTR(setting.key().c_str())] = setting.value();
  1966. }
  1967. espurna::web::ws::EnumerableConfig config{root, STRING_VIEW("wifiConfig")};
  1968. config(STRING_VIEW("networks"), sta::countNetworks(), sta::settings::query::Settings);
  1969. auto& container = config.root();
  1970. container[F("max")] = sta::build::NetworksMax;
  1971. }
  1972. bool onKeyCheck(StringView key, const JsonVariant&) {
  1973. return settings::query::checkExactPrefix(key)
  1974. || settings::query::checkIndexedPrefix(key);
  1975. }
  1976. void onScan(uint32_t client_id) {
  1977. sta::scan::start([client_id](bss_info* found) {
  1978. SsidInfo result(*found);
  1979. wsPost(client_id, [result](JsonObject& root) {
  1980. JsonArray& scan = root.createNestedArray("scanResult");
  1981. auto& info = result.info();
  1982. scan.add(debug::mac(info.bssid()));
  1983. scan.add(debug::authmode(info.authmode()));
  1984. scan.add(info.rssi());
  1985. scan.add(info.channel());
  1986. scan.add(result.ssid());
  1987. });
  1988. },
  1989. [client_id](ScanError error) {
  1990. wsPost(client_id, [error](JsonObject& root) {
  1991. root["scanError"] = debug::error(error);
  1992. });
  1993. });
  1994. }
  1995. void onAction(uint32_t client_id, const char* action, JsonObject&) {
  1996. if (STRING_VIEW("scan") == action) {
  1997. onScan(client_id);
  1998. }
  1999. }
  2000. } // namespace web
  2001. #endif
  2002. // -----------------------------------------------------------------------------
  2003. // INITIALIZATION
  2004. // -----------------------------------------------------------------------------
  2005. namespace settings {
  2006. void migrate(int version) {
  2007. if (version < 5) {
  2008. moveSetting(F("apmode"), ap::settings::keys::Mode);
  2009. }
  2010. }
  2011. } // namespace settings
  2012. namespace debug {
  2013. [[gnu::unused]]
  2014. String event(Event value) {
  2015. String out;
  2016. switch (value) {
  2017. case Event::Initial:
  2018. out = F("Initial");
  2019. break;
  2020. case Event::Mode: {
  2021. const auto mode = wifi::opmode();
  2022. out = F("Mode changed to ");
  2023. out += debug::opmode(mode);
  2024. break;
  2025. }
  2026. case Event::StationInit:
  2027. out = F("Station init");
  2028. break;
  2029. case Event::StationScan:
  2030. out = F("Scanning");
  2031. break;
  2032. case Event::StationConnecting:
  2033. out = F("Connecting");
  2034. break;
  2035. case Event::StationConnected: {
  2036. auto current = sta::current();
  2037. out += F("Connected to BSSID ");
  2038. out += debug::mac(current.bssid);
  2039. out += F(" SSID ");
  2040. out += current.ssid;
  2041. break;
  2042. }
  2043. case Event::StationTimeout:
  2044. out = F("Connection timeout");
  2045. break;
  2046. case Event::StationDisconnected: {
  2047. auto current = sta::current();
  2048. out += F("Disconnected from ");
  2049. out += current.ssid;
  2050. break;
  2051. }
  2052. case Event::StationReconnect:
  2053. out = F("Reconnecting");
  2054. break;
  2055. }
  2056. return out;
  2057. }
  2058. [[gnu::unused]]
  2059. const char* state(State value) {
  2060. const char* out = "?";
  2061. switch (value) {
  2062. case State::Boot:
  2063. out = PSTR("Boot");
  2064. break;
  2065. case State::Connect:
  2066. out = PSTR("Connect");
  2067. break;
  2068. case State::TryConnectBetter:
  2069. out = PSTR("TryConnectBetter");
  2070. break;
  2071. case State::Fallback:
  2072. out = PSTR("Fallback");
  2073. break;
  2074. case State::Connected:
  2075. out = PSTR("Connected");
  2076. break;
  2077. case State::Idle:
  2078. out = PSTR("Idle");
  2079. break;
  2080. case State::Init:
  2081. out = PSTR("Init");
  2082. break;
  2083. case State::Timeout:
  2084. out = PSTR("Timeout");
  2085. break;
  2086. case State::WaitScan:
  2087. out = PSTR("WaitScan");
  2088. break;
  2089. case State::WaitScanWithoutCurrent:
  2090. out = PSTR("WaitScanWithoutCurrent");
  2091. break;
  2092. case State::WaitConnected:
  2093. out = PSTR("WaitConnected");
  2094. break;
  2095. }
  2096. return out;
  2097. }
  2098. } // namespace debug
  2099. namespace internal {
  2100. // STA + AP FALLBACK:
  2101. // - try connection
  2102. // - if ok, stop existing AP
  2103. // - if not, keep / start AP
  2104. //
  2105. // STA:
  2106. // - try connection
  2107. // - don't do anything on completion
  2108. //
  2109. // TODO? WPS / SMARTCONFIG + STA + AP FALLBACK
  2110. // - same as above
  2111. // - when requested, make sure there are no active connections
  2112. // abort when sta connected or ap is connected
  2113. // - run autoconf, receive credentials and store in a free settings slot
  2114. // TODO: provide a clearer 'unroll' of the current state?
  2115. using EventCallbacks = std::forward_list<EventCallback>;
  2116. EventCallbacks callbacks;
  2117. void publish(Event event) {
  2118. for (auto& callback : callbacks) {
  2119. callback(event);
  2120. }
  2121. }
  2122. void subscribe(EventCallback callback) {
  2123. callbacks.push_front(callback);
  2124. }
  2125. State handle_action(State state, Action action) {
  2126. switch (action) {
  2127. case Action::StationConnect:
  2128. if (!sta::enabled()) {
  2129. sta::enable();
  2130. publish(Event::Mode);
  2131. }
  2132. if (!sta::connected()) {
  2133. if (sta::connecting()) {
  2134. sta::connection::schedule_next();
  2135. } else {
  2136. state = State::Init;
  2137. }
  2138. }
  2139. break;
  2140. case Action::StationContinueConnect:
  2141. if (sta::connecting()) {
  2142. state = State::Connect;
  2143. }
  2144. break;
  2145. case Action::StationDisconnect:
  2146. if (sta::connected()) {
  2147. ap::fallback::remove();
  2148. sta::disconnect();
  2149. }
  2150. sta::connection::stop();
  2151. if (sta::enabled()) {
  2152. sta::disable();
  2153. publish(Event::Mode);
  2154. }
  2155. break;
  2156. case Action::StationTryConnectBetter:
  2157. if (!sta::connected() || sta::connecting()) {
  2158. sta::scan::periodic::stop();
  2159. break;
  2160. }
  2161. if (sta::scan::periodic::check()) {
  2162. state = State::TryConnectBetter;
  2163. }
  2164. break;
  2165. case Action::AccessPointFallback:
  2166. case Action::AccessPointStart:
  2167. if (!ap::enabled()) {
  2168. ap::enable();
  2169. ap::start(
  2170. ap::settings::defaultSsid(),
  2171. ap::settings::ssid(),
  2172. ap::settings::passphrase(),
  2173. ap::settings::channel());
  2174. publish(Event::Mode);
  2175. if ((Action::AccessPointFallback == action)
  2176. && ap::fallback::enabled()) {
  2177. ap::fallback::schedule();
  2178. }
  2179. }
  2180. break;
  2181. case Action::AccessPointFallbackCheck:
  2182. if (ap::fallback::enabled()) {
  2183. ap::fallback::check();
  2184. }
  2185. break;
  2186. case Action::AccessPointStop:
  2187. if (ap::enabled()) {
  2188. ap::fallback::remove();
  2189. ap::stop();
  2190. ap::disable();
  2191. publish(Event::Mode);
  2192. }
  2193. break;
  2194. case Action::TurnOff:
  2195. if (wifi::enabled()) {
  2196. ap::fallback::remove();
  2197. ap::stop();
  2198. ap::disable();
  2199. sta::scan::periodic::stop();
  2200. sta::connection::stop();
  2201. sta::disconnect();
  2202. sta::disable();
  2203. wifi::disable();
  2204. publish(Event::Mode);
  2205. break;
  2206. }
  2207. break;
  2208. case Action::Boot:
  2209. case Action::TurnOn:
  2210. if (!wifi::enabled()) {
  2211. wifi::enable();
  2212. #if SYSTEM_CHECK_ENABLED
  2213. if ((action == Action::Boot) && !systemCheck()) {
  2214. wifi::action(Action::AccessPointStart);
  2215. break;
  2216. }
  2217. #endif
  2218. settings::configure();
  2219. }
  2220. break;
  2221. }
  2222. return state;
  2223. }
  2224. bool prepareConnection() {
  2225. if (sta::enabled()) {
  2226. sta::connection::prepare(sta::networks());
  2227. return sta::connection::prepared();
  2228. }
  2229. return false;
  2230. }
  2231. void loop() {
  2232. if (last_state != state) {
  2233. DEBUG_MSG_P(PSTR("[WIFI] State %s -> %s\n"),
  2234. debug::state(last_state),
  2235. debug::state(state));
  2236. last_state = state;
  2237. }
  2238. switch (state) {
  2239. case State::Boot:
  2240. state = State::Idle;
  2241. publish(Event::Initial);
  2242. break;
  2243. case State::Init: {
  2244. if (!prepareConnection()) {
  2245. state = State::Fallback;
  2246. break;
  2247. }
  2248. sta::scan::periodic::stop();
  2249. if (sta::scan::settings::enabled()) {
  2250. if (sta::scanning()) {
  2251. break;
  2252. }
  2253. sta::connection::scanNetworks();
  2254. state = State::WaitScan;
  2255. break;
  2256. }
  2257. state = State::Connect;
  2258. break;
  2259. }
  2260. case State::TryConnectBetter:
  2261. if (sta::scan::settings::enabled()) {
  2262. if (sta::scanning()) {
  2263. break;
  2264. }
  2265. if (!prepareConnection()) {
  2266. state = State::Idle;
  2267. break;
  2268. }
  2269. sta::scan::periodic::stop();
  2270. sta::connection::scanNetworks();
  2271. state = State::WaitScanWithoutCurrent;
  2272. break;
  2273. }
  2274. state = State::Idle;
  2275. break;
  2276. case State::Fallback:
  2277. state = State::Idle;
  2278. sta::connection::schedule_new();
  2279. if (ApMode::Fallback == ap::settings::mode()) {
  2280. action(Action::AccessPointFallback);
  2281. }
  2282. publish(Event::StationReconnect);
  2283. break;
  2284. case State::WaitScan:
  2285. if (sta::scanning()) {
  2286. break;
  2287. }
  2288. sta::connection::scanProcessResults();
  2289. state = State::Connect;
  2290. break;
  2291. case State::WaitScanWithoutCurrent:
  2292. if (sta::scanning()) {
  2293. break;
  2294. }
  2295. if (sta::connection::scanProcessResults(sta::info())) {
  2296. sta::disconnect();
  2297. state = State::Connect;
  2298. break;
  2299. }
  2300. state = State::Idle;
  2301. break;
  2302. case State::Connect: {
  2303. if (!sta::connecting()) {
  2304. if (!sta::connection::start(systemHostname())) {
  2305. state = State::Timeout;
  2306. break;
  2307. }
  2308. }
  2309. if (sta::connection::connect()) {
  2310. state = State::WaitConnected;
  2311. publish(Event::StationConnecting);
  2312. } else {
  2313. state = State::Timeout;
  2314. }
  2315. break;
  2316. }
  2317. case State::WaitConnected:
  2318. if (sta::connection::wait()) {
  2319. break;
  2320. }
  2321. if (sta::connected()) {
  2322. state = State::Connected;
  2323. break;
  2324. }
  2325. state = State::Timeout;
  2326. break;
  2327. // Current logic closely follows the SDK connection routine with reconnect enabled,
  2328. // and will retry the same network multiple times before giving up.
  2329. case State::Timeout:
  2330. if (sta::connecting() && sta::connection::next()) {
  2331. state = State::Idle;
  2332. sta::connection::schedule_next();
  2333. publish(Event::StationTimeout);
  2334. } else {
  2335. sta::connection::stop();
  2336. state = State::Fallback;
  2337. }
  2338. break;
  2339. case State::Connected:
  2340. sta::connection::stop();
  2341. if (sta::scan::settings::enabled()) {
  2342. sta::scan::periodic::start();
  2343. }
  2344. state = State::Idle;
  2345. publish(Event::StationConnected);
  2346. break;
  2347. case State::Idle: {
  2348. state = wifi::handle_action(
  2349. state, internal::handle_action);
  2350. break;
  2351. }
  2352. }
  2353. // SDK disconnection event is specific to the phy layer. i.e. it will happen all the same
  2354. // when trying to connect and being unable to find the AP, being forced out by the AP with bad credentials
  2355. // or being disconnected when the wireless signal is lost.
  2356. // Thus, provide a specific connected -> disconnected event specific to the IP network availability.
  2357. if (sta::connection::lost()) {
  2358. sta::scan::periodic::stop();
  2359. if (sta::connection::persist()) {
  2360. sta::connection::schedule_new(sta::build::RecoveryInterval);
  2361. }
  2362. publish(Event::StationDisconnected);
  2363. }
  2364. #if WIFI_AP_CAPTIVE_SUPPORT
  2365. // Captive portal only queues packets and those need to be processed asap
  2366. if (ap::enabled() && ap::captive()) {
  2367. ap::dnsLoop();
  2368. }
  2369. #endif
  2370. #if WIFI_GRATUITOUS_ARP_SUPPORT
  2371. // ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546
  2372. // Periodically send out ARP, even if no one asked
  2373. if (sta::connected() && !sta::garp::wait()) {
  2374. sta::garp::send();
  2375. }
  2376. #endif
  2377. }
  2378. // XXX: With Arduino Core 3.0.0, WiFi is asleep on boot
  2379. // It will wake up when calling WiFi::mode(...):
  2380. // - WiFi.begin(...)
  2381. // - WiFi.softAP(...)
  2382. // - WiFi.enableSTA(...)
  2383. // - WiFi.enableAP(...)
  2384. // ref. https://github.com/esp8266/Arduino/pull/7902
  2385. void init() {
  2386. WiFi.persistent(false);
  2387. ap::init();
  2388. sta::init();
  2389. }
  2390. } // namespace internal
  2391. void setup() {
  2392. internal::init();
  2393. migrateVersion(settings::migrate);
  2394. settings::query::setup();
  2395. if (BootMode::Enabled == settings::bootMode()) {
  2396. action(Action::Boot);
  2397. }
  2398. #if DEBUG_SUPPORT
  2399. wifiRegister([](Event event) {
  2400. DEBUG_MSG_P(PSTR("[WIFI] %s\n"), debug::event(event).c_str());
  2401. });
  2402. #endif
  2403. #if WEB_SUPPORT
  2404. wsRegister()
  2405. .onAction(web::onAction)
  2406. .onConnected(web::onConnected)
  2407. .onKeyCheck(web::onKeyCheck);
  2408. #endif
  2409. #if TERMINAL_SUPPORT
  2410. terminal::init();
  2411. #endif
  2412. espurnaRegisterLoop(internal::loop);
  2413. espurnaRegisterReload(settings::configure);
  2414. }
  2415. } // namespace
  2416. } // namespace wifi
  2417. } // namespace espurna
  2418. // -----------------------------------------------------------------------------
  2419. // API
  2420. // -----------------------------------------------------------------------------
  2421. void wifiRegister(espurna::wifi::EventCallback callback) {
  2422. espurna::wifi::internal::subscribe(callback);
  2423. }
  2424. bool wifiConnectable() {
  2425. return espurna::wifi::ap::enabled();
  2426. }
  2427. bool wifiConnected() {
  2428. return espurna::wifi::sta::connected();
  2429. }
  2430. IPAddress wifiStaIp() {
  2431. if (espurna::wifi::opmode() & espurna::wifi::OpmodeSta) {
  2432. return espurna::wifi::sta::ip();
  2433. }
  2434. return {};
  2435. }
  2436. String wifiStaSsid() {
  2437. if (espurna::wifi::opmode() & espurna::wifi::OpmodeSta) {
  2438. auto current = espurna::wifi::sta::current();
  2439. return current.ssid;
  2440. }
  2441. return emptyString;
  2442. }
  2443. void wifiDisconnect() {
  2444. espurna::wifi::sta::disconnect();
  2445. }
  2446. void wifiToggleAp() {
  2447. espurna::wifi::ap::toggle();
  2448. }
  2449. void wifiToggleSta() {
  2450. espurna::wifi::sta::toggle();
  2451. }
  2452. void wifiStartAp() {
  2453. espurna::wifi::action(
  2454. espurna::wifi::Action::AccessPointStart);
  2455. }
  2456. bool wifiDisabled() {
  2457. return espurna::wifi::opmode()
  2458. == espurna::wifi::OpmodeNull;
  2459. }
  2460. void wifiDisable() {
  2461. espurna::wifi::ap::fallback::remove();
  2462. espurna::wifi::sta::scan::periodic::stop();
  2463. espurna::wifi::ensure_opmode(
  2464. espurna::wifi::OpmodeNull);
  2465. }
  2466. void wifiTurnOff() {
  2467. espurna::wifi::action(
  2468. espurna::wifi::Action::TurnOff);
  2469. }
  2470. void wifiTurnOn() {
  2471. espurna::wifi::action(
  2472. espurna::wifi::Action::TurnOn);
  2473. }
  2474. void wifiApCheck() {
  2475. espurna::wifi::action(
  2476. espurna::wifi::Action::AccessPointFallbackCheck);
  2477. }
  2478. size_t wifiApStations() {
  2479. if (espurna::wifi::ap::enabled()) {
  2480. return espurna::wifi::ap::stations();
  2481. }
  2482. return 0;
  2483. }
  2484. IPAddress wifiApIp() {
  2485. return espurna::wifi::ap::ip();
  2486. }
  2487. void wifiSetup() {
  2488. espurna::wifi::setup();
  2489. }