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.

3031 lines
75 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. constexpr float txPower() {
  41. return WIFI_OUTPUT_POWER_DBM;
  42. }
  43. constexpr WiFiSleepType_t sleep() {
  44. return WIFI_SLEEP_MODE;
  45. }
  46. } // namespace build
  47. namespace ap {
  48. namespace settings {
  49. namespace options {
  50. PROGMEM_STRING(Disabled, "off");
  51. PROGMEM_STRING(Enabled, "on");
  52. PROGMEM_STRING(Fallback, "fallback");
  53. static constexpr espurna::settings::options::Enumeration<wifi::ApMode> ApModeOptions[] PROGMEM {
  54. {wifi::ApMode::Disabled, Disabled},
  55. {wifi::ApMode::Enabled, Enabled},
  56. {wifi::ApMode::Fallback, Fallback},
  57. };
  58. } // namespace options
  59. } // namespace settings
  60. } // namespace ap
  61. namespace settings {
  62. namespace options {
  63. PROGMEM_STRING(None, "none");
  64. PROGMEM_STRING(Modem, "modem");
  65. PROGMEM_STRING(Light, "light");
  66. static constexpr espurna::settings::options::Enumeration<WiFiSleepType_t> WiFiSleepTypeOptions[] PROGMEM {
  67. {WIFI_NONE_SLEEP, None},
  68. {WIFI_MODEM_SLEEP, Modem},
  69. {WIFI_LIGHT_SLEEP, Light},
  70. };
  71. } // namespace options
  72. } // namespace settings
  73. } // namespace
  74. } // namespace wifi
  75. namespace settings {
  76. namespace internal {
  77. template<>
  78. wifi::StaMode convert(const String& value) {
  79. return convert<bool>(value)
  80. ? wifi::StaMode::Enabled
  81. : wifi::StaMode::Disabled;
  82. }
  83. String serialize(wifi::StaMode mode) {
  84. return serialize(mode == wifi::StaMode::Enabled);
  85. }
  86. template<>
  87. wifi::ApMode convert(const String& value) {
  88. return convert(wifi::ap::settings::options::ApModeOptions, value, wifi::ApMode::Fallback);
  89. }
  90. String serialize(wifi::ApMode mode) {
  91. return serialize(wifi::ap::settings::options::ApModeOptions, mode);
  92. }
  93. template <>
  94. WiFiSleepType_t convert(const String& value) {
  95. return convert(wifi::settings::options::WiFiSleepTypeOptions, value, wifi::build::sleep());
  96. }
  97. String serialize(WiFiSleepType_t sleep) {
  98. return serialize(wifi::settings::options::WiFiSleepTypeOptions, sleep);
  99. }
  100. template <>
  101. IPAddress convert(const String& value) {
  102. IPAddress out;
  103. out.fromString(value);
  104. return out;
  105. }
  106. template <>
  107. wifi::Mac convert(const String& value) {
  108. wifi::Mac out{};
  109. static constexpr size_t Min { 12 };
  110. static constexpr size_t Max { 17 };
  111. switch (value.length()) {
  112. // xxxxxxxxxx
  113. case Min:
  114. hexDecode(value.c_str(), value.length(), out.data(), out.size());
  115. break;
  116. // xx:xx:xx:xx:xx:xx
  117. case Max: {
  118. String buffer;
  119. buffer.reserve(value.length());
  120. for (auto it = value.begin(); it != value.end(); ++it) {
  121. if ((*it) != ':') {
  122. buffer += *it;
  123. }
  124. }
  125. if (buffer.length() == Min) {
  126. hexDecode(buffer.c_str(), buffer.length(), out.data(), out.size());
  127. }
  128. break;
  129. }
  130. }
  131. return out;
  132. }
  133. // XXX: "(IP unset)" when not set, no point saving these :/
  134. // XXX: both 0.0.0.0 and 255.255.255.255 will be saved as empty string
  135. String serialize(const IPAddress& ip) {
  136. return ip.isSet() ? ip.toString() : emptyString;
  137. }
  138. String serialize(wifi::Mac mac) {
  139. String out;
  140. out.reserve(18);
  141. bool delim { false };
  142. char buffer[3] = {0};
  143. for (auto& byte : mac) {
  144. hexEncode(&byte, 1, buffer, sizeof(buffer));
  145. if (delim) {
  146. out += ':';
  147. }
  148. out += buffer;
  149. delim = true;
  150. }
  151. return out;
  152. }
  153. } // namespace internal
  154. } // namespace settings
  155. namespace wifi {
  156. namespace {
  157. // Use SDK constants directly. Provide a constexpr version of the Core enum, since the code never
  158. // actually uses `WiFi::mode(...)` directly, *but* opmode is retrieved using the SDK function.
  159. static constexpr uint8_t OpmodeNull { NULL_MODE };
  160. static constexpr uint8_t OpmodeSta { STATION_MODE };
  161. static constexpr uint8_t OpmodeAp { SOFTAP_MODE };
  162. static constexpr uint8_t OpmodeApSta { OpmodeSta | OpmodeAp };
  163. enum class ScanError {
  164. None,
  165. AlreadyScanning,
  166. System,
  167. NoNetworks
  168. };
  169. enum class Action {
  170. StationConnect,
  171. StationContinueConnect,
  172. StationTryConnectBetter,
  173. StationDisconnect,
  174. AccessPointFallback,
  175. AccessPointFallbackCheck,
  176. AccessPointStart,
  177. AccessPointStop,
  178. TurnOff,
  179. TurnOn,
  180. };
  181. using Actions = std::list<Action>;
  182. using ActionsQueue = std::queue<Action, Actions>;
  183. enum class State {
  184. Boot,
  185. Connect,
  186. TryConnectBetter,
  187. Connected,
  188. Idle,
  189. Init,
  190. Timeout,
  191. Fallback,
  192. WaitScan,
  193. WaitScanWithoutCurrent,
  194. WaitConnected
  195. };
  196. namespace internal {
  197. // Module actions are controled in a serialzed manner, when internal loop is done with the
  198. // current task and is free to take up another one. Allow to toggle OFF for the whole module,
  199. // discarding any actions involving an active WiFi. Default is ON
  200. bool enabled { false };
  201. ActionsQueue actions;
  202. } // namespace internal
  203. uint8_t opmode() {
  204. return wifi_get_opmode();
  205. }
  206. void ensure_opmode(uint8_t mode) {
  207. const auto is_set = [&]() {
  208. return (opmode() == mode);
  209. };
  210. // `std::abort()` calls are the to ensure the mode actually changes, but it should be extremely rare
  211. // it may be also wise to add these for when the mode is already the expected one,
  212. // since we should enforce mode changes to happen *only* through the configuration loop
  213. if (!is_set()) {
  214. wifi_set_opmode_current(mode);
  215. espurna::time::blockingDelay(
  216. espurna::duration::Seconds(1),
  217. espurna::duration::Milliseconds(10),
  218. [&]() {
  219. return !is_set();
  220. });
  221. if (!is_set()) {
  222. abort();
  223. }
  224. }
  225. }
  226. bool enabled() {
  227. return internal::enabled;
  228. }
  229. void enable() {
  230. internal::enabled = true;
  231. }
  232. void disable() {
  233. internal::enabled = false;
  234. }
  235. void action(Action value) {
  236. switch (value) {
  237. case Action::StationConnect:
  238. case Action::StationTryConnectBetter:
  239. case Action::StationContinueConnect:
  240. case Action::StationDisconnect:
  241. case Action::AccessPointFallback:
  242. case Action::AccessPointFallbackCheck:
  243. case Action::AccessPointStart:
  244. case Action::AccessPointStop:
  245. if (!enabled()) {
  246. return;
  247. }
  248. break;
  249. case Action::TurnOff:
  250. case Action::TurnOn:
  251. break;
  252. }
  253. internal::actions.push(value);
  254. }
  255. ActionsQueue& actions() {
  256. return internal::actions;
  257. }
  258. namespace debug {
  259. String error(wifi::ScanError error) {
  260. const __FlashStringHelper* ptr { nullptr };
  261. switch (error) {
  262. case wifi::ScanError::AlreadyScanning:
  263. ptr = F("Scan already in progress");
  264. break;
  265. case wifi::ScanError::System:
  266. ptr = F("Could not start the scan");
  267. break;
  268. case wifi::ScanError::NoNetworks:
  269. ptr = F("No networks");
  270. break;
  271. case wifi::ScanError::None:
  272. ptr = F("OK");
  273. break;
  274. }
  275. return ptr;
  276. }
  277. String mac(Mac mac) {
  278. return espurna::settings::internal::serialize(mac);
  279. }
  280. String ip(const IPAddress& addr) {
  281. return addr.toString();
  282. }
  283. String ip(ip4_addr_t addr) {
  284. String out;
  285. out.reserve(16);
  286. bool delim { false };
  287. for (int byte = 0; byte < 4; ++byte) {
  288. if (delim) {
  289. out += '.';
  290. }
  291. out += ip4_addr_get_byte_val(addr, byte);
  292. delim = true;
  293. }
  294. return out;
  295. }
  296. String authmode(AUTH_MODE mode) {
  297. const __FlashStringHelper* ptr { F("UNKNOWN") };
  298. switch (mode) {
  299. case AUTH_OPEN:
  300. ptr = F("OPEN");
  301. break;
  302. case AUTH_WEP:
  303. ptr = F("WEP");
  304. break;
  305. case AUTH_WPA_PSK:
  306. ptr = F("WPAPSK");
  307. break;
  308. case AUTH_WPA2_PSK:
  309. ptr = F("WPA2PSK");
  310. break;
  311. case AUTH_WPA_WPA2_PSK:
  312. ptr = F("WPAWPA2-PSK");
  313. break;
  314. case AUTH_MAX:
  315. break;
  316. }
  317. return ptr;
  318. }
  319. String opmode(uint8_t mode) {
  320. const __FlashStringHelper* ptr { nullptr };
  321. switch (mode) {
  322. case OpmodeApSta:
  323. ptr = F("AP+STA");
  324. break;
  325. case OpmodeSta:
  326. ptr = F("STA");
  327. break;
  328. case OpmodeAp:
  329. ptr = F("AP");
  330. break;
  331. case OpmodeNull:
  332. ptr = F("NULL");
  333. break;
  334. }
  335. return ptr;
  336. }
  337. } // namespace debug
  338. namespace settings {
  339. namespace keys {
  340. PROGMEM_STRING(TxPower, "wifiTxPwr");
  341. PROGMEM_STRING(Sleep, "wifiSleep");
  342. } // namespace keys
  343. float txPower() {
  344. return getSetting(keys::TxPower, wifi::build::txPower());
  345. }
  346. WiFiSleepType_t sleep() {
  347. return getSetting(keys::Sleep, wifi::build::sleep());
  348. }
  349. namespace query {
  350. namespace internal {
  351. #define EXACT_VALUE(NAME, FUNC)\
  352. String NAME () {\
  353. return espurna::settings::internal::serialize(FUNC());\
  354. }
  355. #define ID_VALUE(NAME, FUNC)\
  356. String NAME (size_t id) {\
  357. return espurna::settings::internal::serialize(FUNC(id));\
  358. }
  359. EXACT_VALUE(sleep, settings::sleep)
  360. EXACT_VALUE(txPower, settings::txPower)
  361. } // namespace internal
  362. } // namespace query
  363. } // namespace settings
  364. // We are guaranteed to have '\0' when <32 b/c the SDK zeroes out the data
  365. // But, these are byte arrays, not C strings. When ssid_len is available, use it.
  366. // When not, we are still expecting the <32 arrays to have '\0' at the end and we manually
  367. // set the 32'nd char to '\0' to prevent conversion issues
  368. String convertSsid(const softap_config& config) {
  369. String ssid;
  370. ssid.concat(reinterpret_cast<const char*>(config.ssid), config.ssid_len);
  371. return ssid;
  372. }
  373. String convertSsid(const bss_info& info) {
  374. String ssid;
  375. ssid.concat(reinterpret_cast<const char*>(info.ssid), info.ssid_len);
  376. return ssid;
  377. }
  378. template <typename T, size_t SsidSize = sizeof(T::ssid)>
  379. String convertSsid(const T& config) {
  380. static_assert(SsidSize == 32, "");
  381. const char* ptr { reinterpret_cast<const char*>(config.ssid) };
  382. char ssid[SsidSize + 1];
  383. std::copy(ptr, ptr + SsidSize, ssid);
  384. ssid[SsidSize] = '\0';
  385. return ssid;
  386. }
  387. template <typename T, size_t PassphraseSize = sizeof(T::password)>
  388. String convertPassphrase(const T& config) {
  389. static_assert(PassphraseSize == 64, "");
  390. const char* ptr { reinterpret_cast<const char*>(config.password) };
  391. char passphrase[PassphraseSize + 1];
  392. std::copy(ptr, ptr + PassphraseSize, passphrase);
  393. passphrase[PassphraseSize] = '\0';
  394. return passphrase;
  395. }
  396. template <typename T, size_t MacSize = sizeof(T::bssid)>
  397. wifi::Mac convertBssid(const T& info) {
  398. static_assert(MacSize == 6, "");
  399. wifi::Mac mac;
  400. std::copy(info.bssid, info.bssid + MacSize, mac.begin());
  401. return mac;
  402. }
  403. struct Info {
  404. Info() = default;
  405. Info(const Info&) = default;
  406. Info(Info&&) = default;
  407. Info(wifi::Mac&& bssid, AUTH_MODE authmode, int8_t rssi, uint8_t channel) :
  408. _bssid(std::move(bssid)),
  409. _authmode(authmode),
  410. _rssi(rssi),
  411. _channel(channel)
  412. {}
  413. explicit Info(const bss_info& info) :
  414. _bssid(convertBssid(info)),
  415. _authmode(info.authmode),
  416. _rssi(info.rssi),
  417. _channel(info.channel)
  418. {}
  419. Info& operator=(const Info&) = default;
  420. Info& operator=(Info&&) = default;
  421. Info& operator=(const bss_info& info) {
  422. _bssid = convertBssid(info);
  423. _authmode = info.authmode;
  424. _channel = info.channel;
  425. _rssi = info.rssi;
  426. return *this;
  427. }
  428. explicit operator bool() const {
  429. return _rssi != 0 && _channel != 0;
  430. }
  431. bool operator<(const Info& rhs) const {
  432. return _rssi < rhs._rssi;
  433. }
  434. bool operator>(const Info& rhs) const {
  435. return _rssi > rhs._rssi;
  436. }
  437. const wifi::Mac& bssid() const {
  438. return _bssid;
  439. }
  440. AUTH_MODE authmode() const {
  441. return _authmode;
  442. }
  443. int8_t rssi() const {
  444. return _rssi;
  445. }
  446. uint8_t channel() const {
  447. return _channel;
  448. }
  449. private:
  450. Mac _bssid{};
  451. AUTH_MODE _authmode { AUTH_OPEN };
  452. int8_t _rssi { 0 };
  453. uint8_t _channel { 0u };
  454. };
  455. struct SsidInfo {
  456. SsidInfo() = delete;
  457. explicit SsidInfo(const bss_info& info) :
  458. _ssid(convertSsid(info)),
  459. _info(info)
  460. {}
  461. SsidInfo(String&& ssid, wifi::Info&& info) :
  462. _ssid(std::move(ssid)),
  463. _info(std::move(info))
  464. {}
  465. const String& ssid() const {
  466. return _ssid;
  467. }
  468. const wifi::Info& info() const {
  469. return _info;
  470. }
  471. // decreasing order by rssi (default sort() order is increasing)
  472. bool operator<(const SsidInfo& rhs) const {
  473. if (!_info.rssi()) {
  474. return false;
  475. }
  476. return info() > rhs.info();
  477. }
  478. private:
  479. String _ssid;
  480. wifi::Info _info;
  481. };
  482. using SsidInfos = std::forward_list<SsidInfo>;
  483. // Note that lwip config allows up to 3 DNS servers. But, most of the time we use DHCP.
  484. // TODO: ::dns(size_t index)? how'd that look with settings?
  485. struct IpSettings {
  486. IpSettings() = default;
  487. IpSettings(const IpSettings&) = default;
  488. IpSettings(IpSettings&&) = default;
  489. IpSettings& operator=(const IpSettings&) = default;
  490. IpSettings& operator=(IpSettings&&) = default;
  491. template <typename Ip, typename Netmask, typename Gateway, typename Dns>
  492. IpSettings(Ip&& ip, Netmask&& netmask, Gateway&& gateway, Dns&& dns) :
  493. _ip(std::forward<Ip>(ip)),
  494. _netmask(std::forward<Netmask>(netmask)),
  495. _gateway(std::forward<Gateway>(gateway)),
  496. _dns(std::forward<Dns>(dns))
  497. {}
  498. const IPAddress& ip() const {
  499. return _ip;
  500. }
  501. const IPAddress& netmask() const {
  502. return _netmask;
  503. }
  504. const IPAddress& gateway() const {
  505. return _gateway;
  506. }
  507. const IPAddress& dns() const {
  508. return _dns;
  509. }
  510. explicit operator bool() const {
  511. return _ip.isSet()
  512. && _netmask.isSet()
  513. && _gateway.isSet()
  514. && _dns.isSet();
  515. }
  516. ip_info toIpInfo() const {
  517. ip_info info{};
  518. info.ip.addr = _ip.v4();
  519. info.netmask.addr = _netmask.v4();
  520. info.gw.addr = _gateway.v4();
  521. return info;
  522. }
  523. private:
  524. IPAddress _ip;
  525. IPAddress _netmask;
  526. IPAddress _gateway;
  527. IPAddress _dns;
  528. };
  529. struct StaNetwork {
  530. Mac bssid;
  531. String ssid;
  532. String passphrase;
  533. int8_t rssi;
  534. uint8_t channel;
  535. };
  536. struct SoftApNetwork {
  537. Mac bssid;
  538. String ssid;
  539. String passphrase;
  540. uint8_t channel;
  541. AUTH_MODE authmode;
  542. };
  543. struct Network {
  544. Network() = delete;
  545. Network(const Network&) = default;
  546. Network(Network&&) = default;
  547. Network& operator=(Network&&) = default;
  548. explicit Network(String&& ssid) :
  549. _ssid(std::move(ssid))
  550. {}
  551. Network(String&& ssid, String&& passphrase) :
  552. _ssid(std::move(ssid)),
  553. _passphrase(std::move(passphrase))
  554. {}
  555. Network(String&& ssid, String&& passphrase, IpSettings&& settings) :
  556. _ssid(std::move(ssid)),
  557. _passphrase(std::move(passphrase)),
  558. _ipSettings(std::move(settings))
  559. {}
  560. // TODO(?): in case SDK API is used directly, this also could use an authmode field
  561. // Arduino wrapper sets WPAPSK minimum by default, so one use-case is to set it to WPA2PSK
  562. Network(Network other, wifi::Mac bssid, uint8_t channel) :
  563. _ssid(std::move(other._ssid)),
  564. _passphrase(std::move(other._passphrase)),
  565. _ipSettings(std::move(other._ipSettings)),
  566. _bssid(bssid),
  567. _channel(channel)
  568. {}
  569. bool dhcp() const {
  570. return !_ipSettings;
  571. }
  572. const String& ssid() const {
  573. return _ssid;
  574. }
  575. const String& passphrase() const {
  576. return _passphrase;
  577. }
  578. const IpSettings& ipSettings() const {
  579. return _ipSettings;
  580. }
  581. const wifi::Mac& bssid() const {
  582. return _bssid;
  583. }
  584. uint8_t channel() const {
  585. return _channel;
  586. }
  587. private:
  588. String _ssid;
  589. String _passphrase;
  590. IpSettings _ipSettings;
  591. Mac _bssid {};
  592. uint8_t _channel { 0u };
  593. };
  594. using Networks = std::list<Network>;
  595. // -----------------------------------------------------------------------------
  596. // STATION
  597. // -----------------------------------------------------------------------------
  598. namespace sta {
  599. namespace build {
  600. static constexpr size_t NetworksMax { WIFI_MAX_NETWORKS };
  601. // aka short interval
  602. static constexpr auto ConnectionInterval = espurna::duration::Milliseconds { WIFI_CONNECT_INTERVAL };
  603. // aka long interval
  604. static constexpr auto ReconnectionInterval = espurna::duration::Milliseconds { WIFI_RECONNECT_INTERVAL };
  605. static constexpr int ConnectionRetries { WIFI_CONNECT_RETRIES };
  606. static constexpr auto RecoveryInterval = ConnectionInterval * ConnectionRetries;
  607. constexpr StaMode mode() {
  608. return WIFI_STA_MODE;
  609. }
  610. const __FlashStringHelper* ssid(size_t index) {
  611. return (
  612. (index == 0) ? F(WIFI1_SSID) :
  613. (index == 1) ? F(WIFI2_SSID) :
  614. (index == 2) ? F(WIFI3_SSID) :
  615. (index == 3) ? F(WIFI4_SSID) :
  616. (index == 4) ? F(WIFI5_SSID) : nullptr
  617. );
  618. }
  619. const __FlashStringHelper* passphrase(size_t index) {
  620. return (
  621. (index == 0) ? F(WIFI1_PASS) :
  622. (index == 1) ? F(WIFI2_PASS) :
  623. (index == 2) ? F(WIFI3_PASS) :
  624. (index == 3) ? F(WIFI4_PASS) :
  625. (index == 4) ? F(WIFI5_PASS) : nullptr
  626. );
  627. }
  628. const __FlashStringHelper* ip(size_t index) {
  629. return (
  630. (index == 0) ? F(WIFI1_IP) :
  631. (index == 1) ? F(WIFI2_IP) :
  632. (index == 2) ? F(WIFI3_IP) :
  633. (index == 3) ? F(WIFI4_IP) :
  634. (index == 4) ? F(WIFI5_IP) : nullptr
  635. );
  636. }
  637. const __FlashStringHelper* gateway(size_t index) {
  638. return (
  639. (index == 0) ? F(WIFI1_GW) :
  640. (index == 1) ? F(WIFI2_GW) :
  641. (index == 2) ? F(WIFI3_GW) :
  642. (index == 3) ? F(WIFI4_GW) :
  643. (index == 4) ? F(WIFI5_GW) : nullptr
  644. );
  645. }
  646. const __FlashStringHelper* netmask(size_t index) {
  647. return (
  648. (index == 0) ? F(WIFI1_MASK) :
  649. (index == 1) ? F(WIFI2_MASK) :
  650. (index == 2) ? F(WIFI3_MASK) :
  651. (index == 3) ? F(WIFI4_MASK) :
  652. (index == 4) ? F(WIFI5_MASK) : nullptr
  653. );
  654. }
  655. const __FlashStringHelper* dns(size_t index) {
  656. return (
  657. (index == 0) ? F(WIFI1_DNS) :
  658. (index == 1) ? F(WIFI2_DNS) :
  659. (index == 2) ? F(WIFI3_DNS) :
  660. (index == 3) ? F(WIFI4_DNS) :
  661. (index == 4) ? F(WIFI5_DNS) : nullptr
  662. );
  663. }
  664. const __FlashStringHelper* bssid(size_t index) {
  665. return (
  666. (index == 0) ? F(WIFI1_BSSID) :
  667. (index == 1) ? F(WIFI2_BSSID) :
  668. (index == 2) ? F(WIFI3_BSSID) :
  669. (index == 3) ? F(WIFI4_BSSID) :
  670. (index == 4) ? F(WIFI5_BSSID) : nullptr
  671. );
  672. }
  673. constexpr uint8_t channel(size_t index) {
  674. return (
  675. (index == 0) ? WIFI1_CHANNEL :
  676. (index == 1) ? WIFI2_CHANNEL :
  677. (index == 2) ? WIFI3_CHANNEL :
  678. (index == 3) ? WIFI4_CHANNEL :
  679. (index == 4) ? WIFI5_CHANNEL : 0
  680. );
  681. }
  682. } // namespace build
  683. namespace settings {
  684. namespace keys {
  685. PROGMEM_STRING(Mode, "wifiStaMode");
  686. PROGMEM_STRING(Ssid, "ssid");
  687. PROGMEM_STRING(Passphrase, "pass");
  688. PROGMEM_STRING(Ip, "ip");
  689. PROGMEM_STRING(Gateway, "gw");
  690. PROGMEM_STRING(Netmask, "mask");
  691. PROGMEM_STRING(Dns, "dns");
  692. PROGMEM_STRING(Bssid, "bssid");
  693. PROGMEM_STRING(Channel, "chan");
  694. } // namespace keys
  695. wifi::StaMode mode() {
  696. return getSetting(keys::Mode, build::mode());
  697. }
  698. String ssid(size_t index) {
  699. return getSetting({keys::Ssid, index}, build::ssid(index));
  700. }
  701. String passphrase(size_t index) {
  702. return getSetting({keys::Passphrase, index}, build::passphrase(index));
  703. }
  704. IPAddress ip(size_t index) {
  705. return espurna::settings::internal::convert<IPAddress>(
  706. getSetting({keys::Ip, index}, build::ip(index)));
  707. }
  708. IPAddress gateway(size_t index) {
  709. return espurna::settings::internal::convert<IPAddress>(
  710. getSetting({keys::Gateway, index}, build::gateway(index)));
  711. }
  712. IPAddress netmask(size_t index) {
  713. return espurna::settings::internal::convert<IPAddress>(
  714. getSetting({keys::Netmask, index}, build::netmask(index)));
  715. }
  716. IPAddress dns(size_t index) {
  717. return espurna::settings::internal::convert<IPAddress>(
  718. getSetting({keys::Dns, index}, build::dns(index)));
  719. }
  720. wifi::Mac bssid(size_t index) {
  721. return espurna::settings::internal::convert<wifi::Mac>(
  722. getSetting({keys::Bssid, index}, build::bssid(index)));
  723. }
  724. int8_t channel(size_t index) {
  725. return getSetting({keys::Channel, index}, build::channel(index));
  726. }
  727. namespace query {
  728. namespace internal {
  729. ID_VALUE(ip, settings::ip)
  730. ID_VALUE(gateway, settings::gateway)
  731. ID_VALUE(netmask, settings::netmask)
  732. ID_VALUE(dns, settings::dns)
  733. ID_VALUE(bssid, settings::bssid)
  734. ID_VALUE(channel, settings::channel)
  735. EXACT_VALUE(mode, settings::mode)
  736. } // namespace internal
  737. static constexpr std::array<espurna::settings::query::IndexedSetting, 8> Settings PROGMEM {
  738. {{keys::Ssid, settings::ssid},
  739. {keys::Passphrase, settings::passphrase},
  740. {keys::Ip, internal::ip},
  741. {keys::Gateway, internal::gateway},
  742. {keys::Netmask, internal::netmask},
  743. {keys::Dns, internal::dns},
  744. {keys::Bssid, internal::bssid},
  745. {keys::Channel, internal::channel}}
  746. };
  747. } // namespace query
  748. } // namespace settings
  749. IPAddress ip() {
  750. ip_info info;
  751. wifi_get_ip_info(STATION_IF, &info);
  752. return info.ip;
  753. }
  754. uint8_t channel() {
  755. return wifi_get_channel();
  756. }
  757. int8_t rssi() {
  758. return wifi_station_get_rssi();
  759. }
  760. wifi::Networks networks() {
  761. wifi::Networks out;
  762. for (size_t id = 0; id < build::NetworksMax; ++id) {
  763. auto ssid = settings::ssid(id);
  764. if (!ssid.length()) {
  765. break;
  766. }
  767. auto pass = settings::passphrase(id);
  768. auto ip = settings::ip(id);
  769. auto ipSettings = ip.isSet()
  770. ? wifi::IpSettings{
  771. std::move(ip),
  772. settings::netmask(id),
  773. settings::gateway(id),
  774. settings::dns(id)}
  775. : wifi::IpSettings{};
  776. Network network(std::move(ssid), settings::passphrase(id), std::move(ipSettings));
  777. auto channel = settings::channel(id);
  778. if (channel) {
  779. out.emplace_back(std::move(network), settings::bssid(id), channel);
  780. } else {
  781. out.push_back(std::move(network));
  782. }
  783. }
  784. return out;
  785. }
  786. size_t countNetworks() {
  787. size_t networks { 0 };
  788. for (size_t id = 0; id < build::NetworksMax; ++id) {
  789. auto ssid = settings::ssid(id);
  790. if (!ssid.length()) {
  791. break;
  792. }
  793. ++networks;
  794. }
  795. return networks;
  796. }
  797. // Note that authmode field is a our threshold, not the one selected by an AP
  798. wifi::Info info(const station_config& config) {
  799. return wifi::Info{
  800. convertBssid(config),
  801. config.threshold.authmode,
  802. rssi(),
  803. channel()};
  804. }
  805. wifi::Info info() {
  806. station_config config{};
  807. wifi_station_get_config(&config);
  808. return info(config);
  809. }
  810. wifi::StaNetwork current(const station_config& config) {
  811. return {
  812. convertBssid(config),
  813. convertSsid(config),
  814. convertPassphrase(config),
  815. rssi(),
  816. channel()};
  817. }
  818. wifi::StaNetwork current() {
  819. station_config config{};
  820. wifi_station_get_config(&config);
  821. return current(config);
  822. }
  823. #if WIFI_GRATUITOUS_ARP_SUPPORT
  824. namespace garp {
  825. namespace build {
  826. static constexpr auto IntervalMin = espurna::duration::Milliseconds { WIFI_GRATUITOUS_ARP_INTERVAL_MIN };
  827. static constexpr auto IntervalMax = espurna::duration::Milliseconds { WIFI_GRATUITOUS_ARP_INTERVAL_MAX };
  828. } // namespace build
  829. namespace settings {
  830. namespace internal {
  831. template <typename T>
  832. T randomInterval(T minimum, T maximum) {
  833. return T(::randomNumber(minimum.count(), maximum.count()));
  834. }
  835. espurna::duration::Milliseconds randomInterval() {
  836. return randomInterval(build::IntervalMin, build::IntervalMax);
  837. }
  838. } // namespace internal
  839. espurna::duration::Milliseconds interval() {
  840. static const auto defaultInterval = internal::randomInterval();
  841. return getSetting("wifiGarpIntvl", defaultInterval);
  842. }
  843. } // namespace settings
  844. namespace internal {
  845. timer::SystemTimer timer;
  846. bool wait { false };
  847. } // namespace internal
  848. bool send() {
  849. bool result { false };
  850. for (netif* interface = netif_list; interface != nullptr; interface = interface->next) {
  851. if (
  852. (interface->flags & NETIF_FLAG_ETHARP)
  853. && (interface->hwaddr_len == ETHARP_HWADDR_LEN)
  854. && (!ip4_addr_isany_val(*netif_ip4_addr(interface)))
  855. && (interface->flags & NETIF_FLAG_LINK_UP)
  856. && (interface->flags & NETIF_FLAG_UP)
  857. ) {
  858. etharp_gratuitous(interface);
  859. result = true;
  860. }
  861. }
  862. return result;
  863. }
  864. bool wait() {
  865. if (internal::wait) {
  866. return true;
  867. }
  868. internal::wait = true;
  869. return false;
  870. }
  871. void stop() {
  872. internal::timer.stop();
  873. }
  874. void reset() {
  875. internal::wait = false;
  876. }
  877. void start(espurna::duration::Milliseconds next) {
  878. internal::timer.repeat(next, reset);
  879. }
  880. } // namespace garp
  881. #endif
  882. namespace scan {
  883. namespace settings {
  884. namespace keys {
  885. PROGMEM_STRING(Enabled, "wifiScan");
  886. } // namespace keys
  887. } // namespace settings
  888. using SsidInfosPtr = std::shared_ptr<wifi::SsidInfos>;
  889. using Success = std::function<void(bss_info*)>;
  890. using Error = std::function<void(wifi::ScanError)>;
  891. struct Task {
  892. Task() = delete;
  893. Task(Success&& success, Error&& error) :
  894. _success(std::move(success)),
  895. _error(std::move(error))
  896. {}
  897. void success(bss_info* info) {
  898. _success(info);
  899. }
  900. void error(wifi::ScanError error) {
  901. _error(error);
  902. }
  903. private:
  904. Success _success;
  905. Error _error;
  906. };
  907. using TaskPtr = std::unique_ptr<Task>;
  908. namespace internal {
  909. TaskPtr task;
  910. void stop() {
  911. task = nullptr;
  912. }
  913. // STATUS comes from c_types.h, and it seems this is the only place that uses it
  914. // instead of some ESP-specific type.
  915. void complete(void* result, STATUS status) {
  916. if (status) { // aka anything but OK / 0
  917. task->error(wifi::ScanError::System);
  918. stop();
  919. return;
  920. }
  921. size_t networks { 0ul };
  922. bss_info* head = reinterpret_cast<bss_info*>(result);
  923. for (bss_info* it = head; it; it = STAILQ_NEXT(it, next), ++networks) {
  924. task->success(it);
  925. }
  926. if (!networks) {
  927. task->error(wifi::ScanError::NoNetworks);
  928. }
  929. stop();
  930. }
  931. } // namespace internal
  932. bool start(Success&& success, Error&& error) {
  933. if (internal::task) {
  934. error(wifi::ScanError::AlreadyScanning);
  935. return false;
  936. }
  937. // Note that esp8266 callback only reports the resulting status and will (always?) timeout all by itself
  938. // Default values are an active scan with some unspecified channel times.
  939. // (zeroed out scan_config struct or simply nullptr)
  940. // For example, c/p config from the current esp32 Arduino Core wrapper which are close to the values mentioned here:
  941. // https://github.com/espressif/ESP8266_NONOS_SDK/issues/103#issuecomment-383440370
  942. // Which could be useful if scanning needs to be more aggressive or switched into PASSIVE scan type
  943. //scan_config config{};
  944. //config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
  945. //config.scan_time.active.min = 100;
  946. //config.scan_time.active.max = 300;
  947. if (wifi_station_scan(nullptr, &internal::complete)) {
  948. internal::task = std::make_unique<Task>(std::move(success), std::move(error));
  949. return true;
  950. }
  951. error(wifi::ScanError::System);
  952. return false;
  953. }
  954. // Alternative to the stock WiFi method, where we wait for the task to finish before returning
  955. bool wait(Success&& success, Error&& error) {
  956. auto result = start(std::move(success), std::move(error));
  957. while (internal::task) {
  958. delay(100);
  959. }
  960. return result;
  961. }
  962. // Another alternative to the stock WiFi method, return a shared Info list
  963. // Caller is expected to wait for the scan to complete before using the contents
  964. SsidInfosPtr ssidinfos() {
  965. auto infos = std::make_shared<wifi::SsidInfos>();
  966. start(
  967. [infos](bss_info* found) {
  968. infos->emplace_front(*found);
  969. },
  970. [infos](wifi::ScanError) {
  971. infos->clear();
  972. });
  973. return infos;
  974. }
  975. } // namespace scan
  976. bool enabled() {
  977. return wifi::opmode() & wifi::OpmodeSta;
  978. }
  979. // XXX: WiFi.disconnect() also implicitly disables STA mode *and* erases the current STA config
  980. void disconnect() {
  981. if (enabled()) {
  982. wifi_station_disconnect();
  983. }
  984. }
  985. // Some workarounds for built-in WiFi management:
  986. // - don't *intentionally* perist current SSID & PASS even when persistance is disabled from the Arduino Core side.
  987. // while this seems like a good idea in theory, we end up with a bunch of async actions coming our way.
  988. // - station disconnect events are linked with the connection routine as well, single WiFi::begin() may trigger up to
  989. // 3 events (as observed with `WiFi::waitForConnectResult()`) before the connection loop stops further attempts
  990. // - explicit OPMODE changes to both notify the userspace when the change actually happens (alternative is SDK event, but it is SYS context),
  991. // since *all* STA & AP start-up methods will implicitly change the mode (`WiFi.begin()`, `WiFi.softAP()`, `WiFi.config()`)
  992. void enable() {
  993. ensure_opmode(opmode() | OpmodeSta);
  994. wifi_station_disconnect();
  995. delay(10);
  996. if (wifi_station_get_reconnect_policy()) {
  997. wifi_station_set_reconnect_policy(false);
  998. }
  999. if (wifi_station_get_auto_connect()) {
  1000. wifi_station_set_auto_connect(false);
  1001. }
  1002. }
  1003. void disable() {
  1004. ensure_opmode(opmode() & ~OpmodeSta);
  1005. }
  1006. namespace connection {
  1007. namespace internal {
  1008. struct Task {
  1009. static constexpr size_t SsidMax { sizeof(station_config::ssid) };
  1010. static constexpr size_t PassphraseMin { 8ul };
  1011. static constexpr size_t PassphraseMax { sizeof(station_config::password) };
  1012. static constexpr int8_t RssiThreshold { -127 };
  1013. using Iterator = wifi::Networks::iterator;
  1014. Task() = delete;
  1015. Task(const Task&) = delete;
  1016. Task(Task&&) = delete;
  1017. explicit Task(String&& hostname, Networks&& networks, int retries) :
  1018. _hostname(std::move(hostname)),
  1019. _networks(std::move(networks)),
  1020. _begin(_networks.begin()),
  1021. _end(_networks.end()),
  1022. _current(_begin),
  1023. _retries(retries),
  1024. _retry(_retries)
  1025. {}
  1026. bool empty() const {
  1027. return _networks.empty();
  1028. }
  1029. size_t count() const {
  1030. return _networks.size();
  1031. }
  1032. bool done() const {
  1033. return _current == _end;
  1034. }
  1035. bool next() {
  1036. if (!done()) {
  1037. if (--_retry < 0) {
  1038. _retry = _retries;
  1039. _current = std::next(_current);
  1040. }
  1041. return !done();
  1042. }
  1043. return false;
  1044. }
  1045. bool connect() const {
  1046. if (!done() && wifi::sta::enabled()) {
  1047. // Need to call this to cancel SDK tasks (previous scan, connection, etc.)
  1048. // Otherwise, it will fail the initial attempt and force a retry.
  1049. wifi::sta::disconnect();
  1050. // SDK sends EVENT_STAMODE_DISCONNECTED right after the disconnect() call, which is likely to happen
  1051. // after being connected and disconnecting for the first time. Not doing this will cause the connection loop
  1052. // to cancel the `wait` lock too early, forcing the Timeout state despite the EVENT_STAMODE_GOTIP coming in later.
  1053. // Allow the event to come in right now to allow `wifi_station_connect()` down below trigger a real one.
  1054. yield();
  1055. auto& network = *_current;
  1056. if (!network.dhcp()) {
  1057. auto& ipsettings = network.ipSettings();
  1058. wifi_station_dhcpc_stop();
  1059. auto current = ip();
  1060. auto info = ipsettings.toIpInfo();
  1061. if (!wifi_set_ip_info(STATION_IF, &info)) {
  1062. return false;
  1063. }
  1064. dns_setserver(0, ipsettings.dns());
  1065. if (current.isSet() && (current != info.ip)) {
  1066. #undef netif_set_addr
  1067. netif_set_addr(eagle_lwip_getif(STATION_IF), &info.ip, &info.netmask, &info.gw);
  1068. }
  1069. }
  1070. // Only the STA cares about the hostname setting
  1071. // esp8266 specific Arduino-specific - this sets lwip internal structs related to the DHCPc
  1072. WiFi.hostname(_hostname);
  1073. // The rest is related to the connection routine
  1074. // SSID & Passphrase are u8 arrays, with 0 at the end when the string is less than it's size
  1075. // Perform checks earlier, before calling SDK config functions, since it would not reflect in the connection
  1076. // state correctly, and we would need to use the Event API once again.
  1077. station_config config{};
  1078. auto& ssid = network.ssid();
  1079. if (!ssid.length() || (ssid.length() > SsidMax)) {
  1080. return false;
  1081. }
  1082. std::copy(ssid.c_str(), ssid.c_str() + ssid.length(),
  1083. reinterpret_cast<char*>(config.ssid));
  1084. if (ssid.length() < SsidMax) {
  1085. config.ssid[ssid.length()] = 0;
  1086. }
  1087. auto& pass = network.passphrase();
  1088. if (pass.length()) {
  1089. if ((pass.length() < PassphraseMin) || (pass.length() > PassphraseMax)) {
  1090. return false;
  1091. }
  1092. config.threshold.authmode = AUTH_WPA_PSK;
  1093. std::copy(pass.c_str(), pass.c_str() + pass.length(),
  1094. reinterpret_cast<char*>(config.password));
  1095. if (pass.length() < PassphraseMax) {
  1096. config.password[pass.length()] = 0;
  1097. }
  1098. } else {
  1099. config.threshold.authmode = AUTH_OPEN;
  1100. config.password[0] = 0;
  1101. }
  1102. config.threshold.rssi = RssiThreshold;
  1103. if (network.channel()) {
  1104. auto& bssid = network.bssid();
  1105. std::copy(bssid.begin(), bssid.end(), config.bssid);
  1106. config.bssid_set = 1;
  1107. }
  1108. // TODO: check every return value?
  1109. // TODO: is it sufficient for the event to fire? otherwise,
  1110. // there needs to be a manual timeout code after this returns true
  1111. wifi_station_set_config_current(&config);
  1112. if (!wifi_station_connect()) {
  1113. return false;
  1114. }
  1115. if (network.channel()) {
  1116. wifi_set_channel(network.channel());
  1117. }
  1118. if (network.dhcp() && (wifi_station_dhcpc_status() != DHCP_STARTED)) {
  1119. wifi_station_dhcpc_start();
  1120. }
  1121. return true;
  1122. }
  1123. return false;
  1124. }
  1125. Networks& networks() {
  1126. return _networks;
  1127. }
  1128. private:
  1129. String _hostname;
  1130. Networks _networks;
  1131. Iterator _begin;
  1132. Iterator _end;
  1133. Iterator _current;
  1134. const int _retries;
  1135. int _retry;
  1136. };
  1137. using ActionPtr = void(*)();
  1138. void action_next() {
  1139. wifi::action(wifi::Action::StationContinueConnect);
  1140. }
  1141. void action_new() {
  1142. wifi::action(wifi::Action::StationConnect);
  1143. }
  1144. wifi::sta::scan::SsidInfosPtr scanResults;
  1145. wifi::Networks preparedNetworks;
  1146. bool connected { false };
  1147. bool wait { false };
  1148. timer::SystemTimer timer;
  1149. bool persist { false };
  1150. using TaskPtr = std::unique_ptr<Task>;
  1151. TaskPtr task;
  1152. } // namespace internal
  1153. void persist(bool value) {
  1154. internal::persist = value;
  1155. }
  1156. bool persist() {
  1157. return internal::persist;
  1158. }
  1159. void stop() {
  1160. internal::task.reset();
  1161. internal::timer.stop();
  1162. }
  1163. bool start(String&& hostname) {
  1164. if (!internal::task) {
  1165. internal::task = std::make_unique<internal::Task>(
  1166. std::move(hostname),
  1167. std::move(internal::preparedNetworks),
  1168. build::ConnectionRetries);
  1169. internal::timer.stop();
  1170. return true;
  1171. }
  1172. internal::preparedNetworks.clear();
  1173. return false;
  1174. }
  1175. void schedule(espurna::duration::Milliseconds next, internal::ActionPtr ptr) {
  1176. internal::timer.once(next, ptr);
  1177. DEBUG_MSG_P(PSTR("[WIFI] Next connection attempt in %u (ms)\n"), next.count());
  1178. }
  1179. void schedule_next() {
  1180. schedule(build::ConnectionInterval, internal::action_next);
  1181. }
  1182. void schedule_new(espurna::duration::Milliseconds next) {
  1183. schedule(next, internal::action_new);
  1184. }
  1185. void schedule_new() {
  1186. schedule_new(build::ReconnectionInterval);
  1187. }
  1188. bool next() {
  1189. return internal::task->next();
  1190. }
  1191. bool connect() {
  1192. if (internal::task->connect()) {
  1193. internal::wait = true;
  1194. return true;
  1195. }
  1196. return false;
  1197. }
  1198. // Note that `wifi_station_get_connect_status()` may never actually change the state from CONNECTING when AP is not available.
  1199. // Wait for the WiFi stack event instead (handled on setup with a static object) and continue after it is either connected or disconnected
  1200. bool wait() {
  1201. if (internal::wait) {
  1202. return true;
  1203. }
  1204. return false;
  1205. }
  1206. // TODO(Core 2.7.4): `WiFi.isConnected()` is a simple `wifi_station_get_connect_status() == STATION_GOT_IP`,
  1207. // Meaning, it will never detect link up / down updates when AP silently kills the connection or something else unexpected happens.
  1208. // Running JustWiFi with autoconnect + reconnect enabled, it silently avoided the issue b/c the SDK reconnect routine disconnected the STA,
  1209. // causing our state machine to immediately cancel it (since `WL_CONNECTED != WiFi.status()`) and then try to connect again using it's own loop.
  1210. // We could either (* is used currently):
  1211. // - (*) listen for the SDK event through the `WiFi.onStationModeDisconnected()`
  1212. // - ( ) poll NETIF_FLAG_LINK_UP for the lwip's netif, since the SDK will bring the link down on disconnection
  1213. // find the `interface` in the `netif_list`, where `interface->num == STATION_IF`
  1214. // - ( ) use lwip's netif event system from the recent Core, track UP and DOWN for a specific interface number
  1215. // this one is probably only used internally, thus should be treated as a private API
  1216. // - ( ) poll whether `wifi_get_ip_info(STATION_IF, &ip);` is set to something valid
  1217. // (tuple of ip, gw and mask)
  1218. // - ( ) poll `WiFi.localIP().isSet()`
  1219. // (will be unset when the link is down)
  1220. // placing status into a simple bool to avoid extracting ip info every time someone needs to check the connection
  1221. bool connected() {
  1222. return internal::connected;
  1223. }
  1224. bool connecting() {
  1225. return static_cast<bool>(internal::task);
  1226. }
  1227. bool lost() {
  1228. static bool last { internal::connected };
  1229. if (internal::connected != last) {
  1230. last = internal::connected;
  1231. return !last;
  1232. }
  1233. return false;
  1234. }
  1235. void prepare(Networks&& networks) {
  1236. internal::preparedNetworks = std::move(networks);
  1237. }
  1238. bool prepared() {
  1239. return internal::preparedNetworks.size();
  1240. }
  1241. } // namespace connection
  1242. bool connected() {
  1243. return connection::connected();
  1244. }
  1245. bool connecting() {
  1246. return connection::connecting();
  1247. }
  1248. bool scanning() {
  1249. return static_cast<bool>(scan::internal::task);
  1250. }
  1251. // TODO: generic onEvent is deprecated on esp8266 in favour of the event-specific
  1252. // methods returning 'cancelation' token. Right now it is a basic shared_ptr with an std function inside of it.
  1253. // esp32 only has a generic onEvent, but event names are not compatible with the esp8266 version.
  1254. //
  1255. // 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`
  1256. // (i.e. 'disarmed', 'armed-for', 'received-success', 'received-failure'. where 'armed-for' only reacts on a specific assignment, and the consumer
  1257. // checks whether 'received-success' had happend, and also handles 'received-failure'. when 'disarmed', value status does not change)
  1258. // 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
  1259. // (or at all, for anything), it might be nice to have some safeguards.
  1260. void init() {
  1261. static auto disconnected = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected&) {
  1262. connection::internal::wait = false;
  1263. connection::internal::connected = false;
  1264. });
  1265. static auto connected = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP&) {
  1266. connection::internal::wait = false;
  1267. connection::internal::connected = true;
  1268. });
  1269. disconnect();
  1270. disable();
  1271. yield();
  1272. }
  1273. void toggle() {
  1274. auto current = enabled();
  1275. connection::persist(!current);
  1276. wifi::action(current
  1277. ? wifi::Action::StationDisconnect
  1278. : wifi::Action::StationConnect);
  1279. }
  1280. namespace scan {
  1281. namespace build {
  1282. constexpr bool enabled() {
  1283. return 1 == WIFI_SCAN_NETWORKS;
  1284. }
  1285. } // namespace build
  1286. namespace settings {
  1287. bool enabled() {
  1288. return getSetting(keys::Enabled, build::enabled());
  1289. }
  1290. namespace query {
  1291. EXACT_VALUE(enabled, settings::enabled)
  1292. } // namespace query
  1293. } // namespace settings
  1294. namespace periodic {
  1295. namespace build {
  1296. static constexpr auto Interval = espurna::duration::Milliseconds { WIFI_SCAN_RSSI_CHECK_INTERVAL };
  1297. static constexpr int8_t Checks { WIFI_SCAN_RSSI_CHECKS };
  1298. constexpr int8_t threshold() {
  1299. return WIFI_SCAN_RSSI_THRESHOLD;
  1300. }
  1301. } // namespace build
  1302. namespace settings {
  1303. namespace keys {
  1304. PROGMEM_STRING(Threshold, "wifiScanRssi");
  1305. } // namespace keys
  1306. int8_t threshold() {
  1307. return getSetting(FPSTR(keys::Threshold), build::threshold());
  1308. }
  1309. namespace query {
  1310. EXACT_VALUE(threshold, settings::threshold)
  1311. } // namespace query
  1312. } // namespace settings
  1313. namespace internal {
  1314. int8_t threshold { build::threshold() };
  1315. int8_t counter { build::Checks };
  1316. timer::SystemTimer timer;
  1317. void task() {
  1318. if (!wifi::sta::connected()) {
  1319. counter = build::Checks;
  1320. return;
  1321. }
  1322. auto rssi = wifi::sta::rssi();
  1323. if (rssi > threshold) {
  1324. counter = build::Checks;
  1325. } else if (rssi < threshold) {
  1326. if (counter < 0) {
  1327. return;
  1328. }
  1329. if (!--counter) {
  1330. wifi::action(wifi::Action::StationTryConnectBetter);
  1331. }
  1332. }
  1333. }
  1334. void start() {
  1335. counter = build::Checks;
  1336. timer.repeat(build::Interval, task);
  1337. }
  1338. void stop() {
  1339. counter = build::Checks;
  1340. timer.stop();
  1341. }
  1342. } // namespace internal
  1343. void threshold(int8_t value) {
  1344. internal::threshold = value;
  1345. }
  1346. void stop() {
  1347. internal::stop();
  1348. }
  1349. void start() {
  1350. internal::start();
  1351. }
  1352. bool check() {
  1353. if (internal::counter <= 0) {
  1354. internal::counter = build::Checks;
  1355. return true;
  1356. }
  1357. return false;
  1358. }
  1359. } // namespace periodic
  1360. } // namespace scan
  1361. namespace connection {
  1362. // After scan attempt, generate a new networks list based on the results sorted by the rssi value.
  1363. // For the initial connection, add every matching network with the scan result bssid and channel info.
  1364. // For the attempt to find a better network, filter out every network with worse than the current network's rssi
  1365. void scanNetworks() {
  1366. internal::scanResults = wifi::sta::scan::ssidinfos();
  1367. }
  1368. bool suitableNetwork(const Network& network, const SsidInfo& ssidInfo) {
  1369. return (ssidInfo.ssid() == network.ssid())
  1370. && ((ssidInfo.info().authmode() != AUTH_OPEN)
  1371. ? network.passphrase().length()
  1372. : !network.passphrase().length());
  1373. }
  1374. bool scanProcessResults(int8_t threshold) {
  1375. if (internal::scanResults) {
  1376. auto results = std::move(internal::scanResults);
  1377. results->sort();
  1378. if (threshold < 0) {
  1379. results->remove_if([threshold](const wifi::SsidInfo& result) {
  1380. return result.info().rssi() < threshold;
  1381. });
  1382. }
  1383. Networks networks(std::move(internal::preparedNetworks));
  1384. Networks sortedNetworks;
  1385. for (auto& result : *results) {
  1386. for (auto& network : networks) {
  1387. if (suitableNetwork(network, result)) {
  1388. sortedNetworks.emplace_back(network, result.info().bssid(), result.info().channel());
  1389. break;
  1390. }
  1391. }
  1392. }
  1393. internal::preparedNetworks = std::move(sortedNetworks);
  1394. internal::scanResults.reset();
  1395. }
  1396. return internal::preparedNetworks.size();
  1397. }
  1398. bool scanProcessResults(const wifi::Info& info) {
  1399. return scanProcessResults(info.rssi());
  1400. }
  1401. bool scanProcessResults() {
  1402. return scanProcessResults(0);
  1403. }
  1404. } // namespace connection
  1405. void configure() {
  1406. auto enabled = (wifi::StaMode::Enabled == wifi::sta::settings::mode());
  1407. connection::persist(enabled);
  1408. wifi::action(enabled
  1409. ? wifi::Action::StationConnect
  1410. : wifi::Action::StationDisconnect);
  1411. scan::periodic::threshold(
  1412. scan::periodic::settings::threshold());
  1413. #if WIFI_GRATUITOUS_ARP_SUPPORT
  1414. auto interval = garp::settings::interval();
  1415. if (interval.count()) {
  1416. garp::start(interval);
  1417. } else {
  1418. garp::stop();
  1419. }
  1420. #endif
  1421. }
  1422. } // namespace sta
  1423. // -----------------------------------------------------------------------------
  1424. // ACCESS POINT
  1425. // -----------------------------------------------------------------------------
  1426. namespace ap {
  1427. namespace build {
  1428. static constexpr size_t SsidMax { sizeof(softap_config::ssid) };
  1429. static constexpr size_t PassphraseMin { 8u };
  1430. static constexpr size_t PassphraseMax { sizeof(softap_config::password) };
  1431. static constexpr int Hidden { 0 };
  1432. static constexpr uint8_t ConnectionsMax { 4u };
  1433. constexpr bool hasSsid() {
  1434. return __builtin_strlen(WIFI_AP_SSID);
  1435. }
  1436. const __FlashStringHelper* ssid() {
  1437. return F(WIFI_AP_SSID);
  1438. }
  1439. constexpr bool hasPassphrase() {
  1440. return __builtin_strlen(WIFI_AP_PASS);
  1441. }
  1442. const __FlashStringHelper* passphrase() {
  1443. return F(WIFI_AP_PASS);
  1444. }
  1445. constexpr bool captive() {
  1446. return 1 == WIFI_AP_CAPTIVE_ENABLED;
  1447. }
  1448. constexpr ApMode mode() {
  1449. return WIFI_AP_MODE;
  1450. }
  1451. constexpr uint8_t channel() {
  1452. return WIFI_AP_CHANNEL;
  1453. }
  1454. } // namespace build
  1455. namespace settings {
  1456. namespace keys {
  1457. PROGMEM_STRING(Mode, "wifiApMode");
  1458. PROGMEM_STRING(Ssid, "wifiApSsid");
  1459. PROGMEM_STRING(Passphrase, "wifiApPass");
  1460. PROGMEM_STRING(Channel, "wifiApChan");
  1461. [[gnu::unused]] PROGMEM_STRING(Captive, "wifiApCaptive");
  1462. } // namespace keys
  1463. wifi::ApMode mode() {
  1464. return getSetting(FPSTR(keys::Mode), build::mode());
  1465. }
  1466. String defaultSsid() {
  1467. return String(systemIdentifier());
  1468. }
  1469. String ssid() {
  1470. return getSetting(FPSTR(keys::Ssid), build::hasSsid()
  1471. ? build::ssid()
  1472. : systemHostname());
  1473. }
  1474. String passphrase() {
  1475. return getSetting(FPSTR(keys::Passphrase), build::hasPassphrase()
  1476. ? build::passphrase()
  1477. : systemPassword());
  1478. }
  1479. int8_t channel() {
  1480. return getSetting(FPSTR(keys::Channel), build::channel());
  1481. }
  1482. [[gnu::unused]]
  1483. bool captive() {
  1484. return getSetting(FPSTR(keys::Captive), build::captive());
  1485. }
  1486. namespace query {
  1487. namespace internal {
  1488. EXACT_VALUE(captive, wifi::ap::settings::captive)
  1489. EXACT_VALUE(channel, wifi::ap::settings::channel)
  1490. EXACT_VALUE(mode, wifi::ap::settings::mode)
  1491. #undef ID_VALUE
  1492. #undef EXACT_VALUE
  1493. } // namespace internal
  1494. } // namespace query
  1495. } // namespace settings
  1496. namespace internal {
  1497. #if WIFI_AP_CAPTIVE_SUPPORT
  1498. bool captive { build::captive() };
  1499. DNSServer dns;
  1500. #endif
  1501. void start(String&& defaultSsid, String&& ssid, String&& passphrase, uint8_t channel) {
  1502. // Always generate valid AP config, even when user-provided credentials fail to comply with the requirements
  1503. // TODO: configuration routine depends on a lwip dhcpserver, which is a custom module made specifically for the ESP.
  1504. // while it's possible to hijack this and control the process manually, right now it's easier to delegate this to the Core helpers
  1505. // (plus, it makes it not compatible with the esp-idf stack anyway, since wifi_softap_dhcps_... calls don't do anything here)
  1506. const char* apSsid {
  1507. (ssid.length() && (ssid.length() < build::SsidMax))
  1508. ? ssid.c_str() : defaultSsid.c_str() };
  1509. const char* apPass {
  1510. (passphrase.length() \
  1511. && (passphrase.length() >= build::PassphraseMin) \
  1512. && (passphrase.length() < build::PassphraseMax))
  1513. ? passphrase.c_str() : nullptr };
  1514. // TODO: when using `softap_config`, can also tweak the beacon intvl
  1515. // static constexpr uint16_t BeaconInterval { 100u };
  1516. WiFi.softAP(apSsid, apPass, channel, build::Hidden, build::ConnectionsMax);
  1517. }
  1518. } // namespace internal
  1519. #if WIFI_AP_CAPTIVE_SUPPORT
  1520. void captive(bool value) {
  1521. internal::captive = value;
  1522. }
  1523. bool captive() {
  1524. return internal::captive;
  1525. }
  1526. void dnsLoop() {
  1527. internal::dns.processNextRequest();
  1528. }
  1529. #endif
  1530. IPAddress ip() {
  1531. ip_info info;
  1532. wifi_get_ip_info(SOFTAP_IF, &info);
  1533. return info.ip;
  1534. }
  1535. void enable() {
  1536. ensure_opmode(opmode() | OpmodeAp);
  1537. }
  1538. void disable() {
  1539. ensure_opmode(opmode() & ~OpmodeAp);
  1540. }
  1541. bool enabled() {
  1542. return wifi::opmode() & OpmodeAp;
  1543. }
  1544. void toggle() {
  1545. wifi::action(wifi::ap::enabled()
  1546. ? wifi::Action::AccessPointStop
  1547. : wifi::Action::AccessPointStart);
  1548. }
  1549. void stop() {
  1550. #if WIFI_AP_CAPTIVE_SUPPORT
  1551. internal::dns.stop();
  1552. #endif
  1553. WiFi.softAPdisconnect();
  1554. }
  1555. void start(String&& defaultSsid, String&& ssid, String&& passphrase, uint8_t channel) {
  1556. internal::start(std::move(defaultSsid), std::move(ssid),
  1557. std::move(passphrase), channel);
  1558. #if WIFI_AP_CAPTIVE_SUPPORT
  1559. if (internal::captive) {
  1560. internal::dns.setErrorReplyCode(DNSReplyCode::NoError);
  1561. internal::dns.start(53, "*", ip());
  1562. } else {
  1563. internal::dns.stop();
  1564. }
  1565. #endif
  1566. }
  1567. wifi::SoftApNetwork current() {
  1568. softap_config config{};
  1569. wifi_softap_get_config(&config);
  1570. wifi::Mac mac;
  1571. WiFi.softAPmacAddress(mac.data());
  1572. return {
  1573. mac,
  1574. convertSsid(config),
  1575. convertPassphrase(config),
  1576. config.channel,
  1577. config.authmode};
  1578. }
  1579. void init() {
  1580. disable();
  1581. }
  1582. size_t stations() {
  1583. return WiFi.softAPgetStationNum();
  1584. }
  1585. namespace fallback {
  1586. namespace build {
  1587. constexpr auto Timeout = espurna::duration::Milliseconds { WIFI_FALLBACK_TIMEOUT };
  1588. } // namespace build
  1589. namespace internal {
  1590. auto timeout = build::Timeout;
  1591. bool enabled { false };
  1592. timer::SystemTimer timer;
  1593. } // namespace internal
  1594. void enable() {
  1595. internal::enabled = true;
  1596. }
  1597. void disable() {
  1598. internal::enabled = false;
  1599. }
  1600. bool enabled() {
  1601. return internal::enabled;
  1602. }
  1603. void remove() {
  1604. internal::timer.stop();
  1605. }
  1606. void check();
  1607. void schedule() {
  1608. internal::timer.repeat(
  1609. internal::timeout,
  1610. []() {
  1611. wifi::action(wifi::Action::AccessPointFallbackCheck);
  1612. });
  1613. }
  1614. void check() {
  1615. if (wifi::ap::enabled()
  1616. && wifi::sta::connected()
  1617. && !wifi::ap::stations())
  1618. {
  1619. wifi::action(wifi::Action::AccessPointStop);
  1620. return;
  1621. }
  1622. }
  1623. } // namespace fallback
  1624. void configure() {
  1625. auto current = settings::mode();
  1626. if (wifi::ApMode::Fallback == current) {
  1627. fallback::enable();
  1628. } else {
  1629. fallback::disable();
  1630. fallback::remove();
  1631. wifi::action((wifi::ApMode::Enabled == current)
  1632. ? wifi::Action::AccessPointStart
  1633. : wifi::Action::AccessPointStop);
  1634. }
  1635. #if WIFI_AP_CAPTIVE_SUPPORT
  1636. captive(settings::captive());
  1637. #endif
  1638. }
  1639. } // namespace ap
  1640. // -----------------------------------------------------------------------------
  1641. // SETTINGS
  1642. // -----------------------------------------------------------------------------
  1643. namespace settings {
  1644. namespace query {
  1645. static constexpr std::array<espurna::settings::query::Setting, 10> Settings PROGMEM {
  1646. {{wifi::ap::settings::keys::Ssid, wifi::ap::settings::ssid},
  1647. {wifi::ap::settings::keys::Passphrase, wifi::ap::settings::passphrase},
  1648. {wifi::ap::settings::keys::Captive, wifi::ap::settings::query::internal::captive},
  1649. {wifi::ap::settings::keys::Channel, wifi::ap::settings::query::internal::channel},
  1650. {wifi::ap::settings::keys::Mode, wifi::ap::settings::query::internal::mode},
  1651. {wifi::sta::settings::keys::Mode, wifi::sta::settings::query::internal::mode},
  1652. {wifi::sta::scan::settings::keys::Enabled, wifi::sta::scan::settings::query::enabled},
  1653. {wifi::sta::scan::periodic::settings::keys::Threshold, wifi::sta::scan::periodic::settings::query::threshold},
  1654. {wifi::settings::keys::TxPower, espurna::wifi::settings::query::internal::txPower},
  1655. {wifi::settings::keys::Sleep, espurna::wifi::settings::query::internal::sleep}}
  1656. };
  1657. // indexed settings for 'sta' connections
  1658. bool checkIndexedPrefix(StringView key) {
  1659. return espurna::settings::query::IndexedSetting::findSamePrefix(
  1660. wifi::sta::settings::query::Settings, key);
  1661. }
  1662. // generic 'ap' and 'modem' configuration
  1663. bool checkExactPrefix(StringView key) {
  1664. PROGMEM_STRING(Prefix, "wifi");
  1665. if (espurna::settings::query::samePrefix(key, Prefix)) {
  1666. return true;
  1667. }
  1668. return false;
  1669. }
  1670. String findIndexedValueFrom(StringView key) {
  1671. using espurna::settings::query::IndexedSetting;
  1672. return IndexedSetting::findValueFrom(
  1673. wifi::sta::countNetworks(),
  1674. wifi::sta::settings::query::Settings, key);
  1675. }
  1676. String findValueFrom(StringView key) {
  1677. using espurna::settings::query::Setting;
  1678. return Setting::findValueFrom(Settings, key);
  1679. }
  1680. void setup() {
  1681. // TODO: small implementation detail - when searching, these
  1682. // should be registered like this so the 'exact' is processed first
  1683. settingsRegisterQueryHandler({
  1684. .check = checkIndexedPrefix,
  1685. .get = findIndexedValueFrom,
  1686. });
  1687. settingsRegisterQueryHandler({
  1688. .check = checkExactPrefix,
  1689. .get = findValueFrom,
  1690. });
  1691. }
  1692. } // namespace query
  1693. void configure() {
  1694. wifi::ap::configure();
  1695. wifi::sta::configure();
  1696. WiFi.setSleepMode(wifi::settings::sleep());
  1697. WiFi.setOutputPower(wifi::settings::txPower());
  1698. }
  1699. } // namespace settings
  1700. // -----------------------------------------------------------------------------
  1701. // TERMINAL
  1702. // -----------------------------------------------------------------------------
  1703. #if TERMINAL_SUPPORT
  1704. namespace terminal {
  1705. namespace commands {
  1706. PROGMEM_STRING(Stations, "WIFI.STATIONS");
  1707. void stations(::terminal::CommandContext&& ctx) {
  1708. size_t stations { 0ul };
  1709. for (auto* it = wifi_softap_get_station_info(); it; it = STAILQ_NEXT(it, next), ++stations) {
  1710. ctx.output.printf_P(PSTR("%s %s\n"),
  1711. wifi::debug::mac(convertBssid(*it)).c_str(),
  1712. wifi::debug::ip(it->ip).c_str());
  1713. }
  1714. wifi_softap_free_station_info();
  1715. if (!stations) {
  1716. terminalError(ctx, F("No stations connected"));
  1717. return;
  1718. }
  1719. terminalOK(ctx);
  1720. }
  1721. PROGMEM_STRING(Network, "NETWORK");
  1722. void network(::terminal::CommandContext&& ctx) {
  1723. for (auto& addr : addrList) {
  1724. ctx.output.printf_P(PSTR("%s%d %4s %6s "),
  1725. addr.ifname().c_str(),
  1726. addr.ifnumber(),
  1727. addr.ifUp() ? "up" : "down",
  1728. addr.isLocal() ? "local" : "global");
  1729. #if LWIP_IPV6
  1730. if (addr.isV4()) {
  1731. #endif
  1732. ctx.output.printf_P(PSTR("ip %s gateway %s mask %s\n"),
  1733. wifi::debug::ip(addr.ipv4()).c_str(),
  1734. wifi::debug::ip(addr.gw()).c_str(),
  1735. wifi::debug::ip(addr.netmask()).c_str());
  1736. #if LWIP_IPV6
  1737. } else {
  1738. // TODO: ip6_addr[...] array is included in the list
  1739. // we'll just see another entry
  1740. // TODO: routing info is not attached to the netif :/
  1741. // ref. nd6.h (and figure out what it does)
  1742. ctx.output.printf_P(PSTR("ip %s\n"),
  1743. wifi::debug::ip(netif->ip6_addr[i]).c_str());
  1744. }
  1745. #endif
  1746. }
  1747. for (int n = 0; n < DNS_MAX_SERVERS; ++n) {
  1748. auto ip = IPAddress(dns_getserver(n));
  1749. if (!ip.isSet()) {
  1750. break;
  1751. }
  1752. ctx.output.printf_P(PSTR("dns %s\n"), wifi::debug::ip(ip).c_str());
  1753. }
  1754. }
  1755. PROGMEM_STRING(Wifi, "WIFI");
  1756. void wifi(::terminal::CommandContext&& ctx) {
  1757. if (ctx.argv.size() == 2) {
  1758. auto id = espurna::settings::internal::convert<size_t>(ctx.argv[1]);
  1759. if (id < wifi::sta::build::NetworksMax) {
  1760. settingsDump(ctx, wifi::sta::settings::query::Settings, id);
  1761. return;
  1762. }
  1763. terminalError(ctx, F("Network ID out of configurable range"));
  1764. return;
  1765. }
  1766. const auto mode = wifi::opmode();
  1767. ctx.output.printf_P(PSTR("OPMODE: %s\n"), wifi::debug::opmode(mode).c_str());
  1768. if (mode & OpmodeAp) {
  1769. auto current = wifi::ap::current();
  1770. ctx.output.printf_P(PSTR("SoftAP: bssid %s channel %hhu auth %s\n"),
  1771. wifi::debug::mac(current.bssid).c_str(),
  1772. current.channel,
  1773. wifi::debug::authmode(current.authmode).c_str(),
  1774. current.ssid.c_str(),
  1775. current.passphrase.c_str());
  1776. if (wifi::ap::fallback::enabled() && wifi::ap::fallback::internal::timer) {
  1777. ctx.output.printf_P(PSTR("fallback check every %u ms\n"),
  1778. wifi::ap::fallback::build::Timeout.count());
  1779. }
  1780. }
  1781. if (mode & OpmodeSta) {
  1782. if (wifi::sta::connected()) {
  1783. station_config config{};
  1784. wifi_station_get_config(&config);
  1785. auto network = wifi::sta::current(config);
  1786. ctx.output.printf_P(PSTR("STA: bssid %s rssi %hhd channel %hhu ssid \"%s\"\n"),
  1787. wifi::debug::mac(network.bssid).c_str(),
  1788. network.rssi, network.channel, network.ssid.c_str());
  1789. } else {
  1790. ctx.output.printf_P(PSTR("STA: %s\n"),
  1791. wifi::sta::connecting() ? "connecting" : "disconnected");
  1792. }
  1793. }
  1794. settingsDump(ctx, wifi::settings::query::Settings);
  1795. terminalOK(ctx);
  1796. }
  1797. PROGMEM_STRING(Reset, "WIFI.RESET");
  1798. void reset(::terminal::CommandContext&& ctx) {
  1799. wifi::sta::disconnect();
  1800. wifi::settings::configure();
  1801. terminalOK(ctx);
  1802. }
  1803. PROGMEM_STRING(Station, "WIFI.STA");
  1804. void station(::terminal::CommandContext&& ctx) {
  1805. wifi::sta::toggle();
  1806. terminalOK(ctx);
  1807. }
  1808. PROGMEM_STRING(AccessPoint, "WIFI.AP");
  1809. void access_point(::terminal::CommandContext&& ctx) {
  1810. wifi::ap::toggle();
  1811. terminalOK(ctx);
  1812. }
  1813. PROGMEM_STRING(Off, "WIFI.OFF");
  1814. void off(::terminal::CommandContext&& ctx) {
  1815. wifi::action(Action::TurnOff);
  1816. terminalOK(ctx);
  1817. }
  1818. PROGMEM_STRING(On, "WIFI.ON");
  1819. void on(::terminal::CommandContext&& ctx) {
  1820. wifi::action(Action::TurnOn);
  1821. terminalOK(ctx);
  1822. }
  1823. PROGMEM_STRING(Scan, "WIFI.SCAN");
  1824. void scan(::terminal::CommandContext&& ctx) {
  1825. wifi::sta::scan::wait(
  1826. [&](bss_info* info) {
  1827. ctx.output.printf_P(PSTR("BSSID: %s AUTH: %11s RSSI: %3hhd CH: %2hhu SSID: %s\n"),
  1828. wifi::debug::mac(convertBssid(*info)).c_str(),
  1829. wifi::debug::authmode(info->authmode).c_str(),
  1830. info->rssi,
  1831. info->channel,
  1832. convertSsid(*info).c_str()
  1833. );
  1834. },
  1835. [&](wifi::ScanError error) {
  1836. terminalError(ctx, wifi::debug::error(error));
  1837. }
  1838. );
  1839. }
  1840. static constexpr ::terminal::Command List[] PROGMEM {
  1841. {Stations, commands::stations},
  1842. {Network, commands::network},
  1843. {Wifi, commands::wifi},
  1844. {Reset, commands::reset},
  1845. {Station, commands::station},
  1846. {AccessPoint, commands::access_point},
  1847. {Scan, commands::scan},
  1848. {Off, commands::off},
  1849. {On, commands::on},
  1850. };
  1851. } // namespace commands
  1852. void init() {
  1853. espurna::terminal::add(commands::List);
  1854. }
  1855. } // namespace terminal
  1856. #endif
  1857. // -----------------------------------------------------------------------------
  1858. // WEB
  1859. // -----------------------------------------------------------------------------
  1860. #if WEB_SUPPORT
  1861. namespace web {
  1862. void onConnected(JsonObject& root) {
  1863. for (const auto& setting : wifi::settings::query::Settings) {
  1864. root[FPSTR(setting.key().c_str())] = setting.value();
  1865. }
  1866. espurna::web::ws::EnumerableConfig config{root, STRING_VIEW("wifiConfig")};
  1867. config(STRING_VIEW("networks"), wifi::sta::countNetworks(), wifi::sta::settings::query::Settings);
  1868. auto& container = config.root();
  1869. container[F("max")] = wifi::sta::build::NetworksMax;
  1870. }
  1871. bool onKeyCheck(StringView key, const JsonVariant&) {
  1872. return wifi::settings::query::checkExactPrefix(key)
  1873. || wifi::settings::query::checkIndexedPrefix(key);
  1874. }
  1875. void onScan(uint32_t client_id) {
  1876. if (wifi::sta::scanning()) {
  1877. return;
  1878. }
  1879. wifi::sta::scan::start([client_id](bss_info* found) {
  1880. wifi::SsidInfo result(*found);
  1881. wsPost(client_id, [result](JsonObject& root) {
  1882. JsonArray& scan = root.createNestedArray("scanResult");
  1883. auto& info = result.info();
  1884. scan.add(wifi::debug::mac(info.bssid()));
  1885. scan.add(wifi::debug::authmode(info.authmode()));
  1886. scan.add(info.rssi());
  1887. scan.add(info.channel());
  1888. scan.add(result.ssid());
  1889. });
  1890. },
  1891. [client_id](wifi::ScanError error) {
  1892. wsPost(client_id, [error](JsonObject& root) {
  1893. root["scanError"] = wifi::debug::error(error);
  1894. });
  1895. });
  1896. }
  1897. void onAction(uint32_t client_id, const char* action, JsonObject&) {
  1898. if (STRING_VIEW("scan") == action) {
  1899. onScan(client_id);
  1900. }
  1901. }
  1902. } // namespace web
  1903. #endif
  1904. // -----------------------------------------------------------------------------
  1905. // INITIALIZATION
  1906. // -----------------------------------------------------------------------------
  1907. namespace settings {
  1908. void migrate(int version) {
  1909. if (version < 5) {
  1910. moveSetting(F("apmode"), ap::settings::keys::Mode);
  1911. }
  1912. }
  1913. } // namespace settings
  1914. namespace debug {
  1915. [[gnu::unused]]
  1916. String event(wifi::Event value) {
  1917. String out;
  1918. switch (value) {
  1919. case wifi::Event::Initial:
  1920. out = F("Initial");
  1921. break;
  1922. case wifi::Event::Mode: {
  1923. const auto mode = wifi::opmode();
  1924. out = F("Mode changed to ");
  1925. out += wifi::debug::opmode(mode);
  1926. break;
  1927. }
  1928. case wifi::Event::StationInit:
  1929. out = F("Station init");
  1930. break;
  1931. case wifi::Event::StationScan:
  1932. out = F("Scanning");
  1933. break;
  1934. case wifi::Event::StationConnecting:
  1935. out = F("Connecting");
  1936. break;
  1937. case wifi::Event::StationConnected: {
  1938. auto current = wifi::sta::current();
  1939. out += F("Connected to BSSID ");
  1940. out += wifi::debug::mac(current.bssid);
  1941. out += F(" SSID ");
  1942. out += current.ssid;
  1943. break;
  1944. }
  1945. case wifi::Event::StationTimeout:
  1946. out = F("Connection timeout");
  1947. break;
  1948. case wifi::Event::StationDisconnected: {
  1949. auto current = wifi::sta::current();
  1950. out += F("Disconnected from ");
  1951. out += current.ssid;
  1952. break;
  1953. }
  1954. case wifi::Event::StationReconnect:
  1955. out = F("Reconnecting");
  1956. break;
  1957. }
  1958. return out;
  1959. }
  1960. [[gnu::unused]]
  1961. const char* state(wifi::State value) {
  1962. const char* out = "?";
  1963. switch (value) {
  1964. case wifi::State::Boot:
  1965. out = PSTR("Boot");
  1966. break;
  1967. case wifi::State::Connect:
  1968. out = PSTR("Connect");
  1969. break;
  1970. case wifi::State::TryConnectBetter:
  1971. out = PSTR("TryConnectBetter");
  1972. break;
  1973. case wifi::State::Fallback:
  1974. out = PSTR("Fallback");
  1975. break;
  1976. case wifi::State::Connected:
  1977. out = PSTR("Connected");
  1978. break;
  1979. case wifi::State::Idle:
  1980. out = PSTR("Idle");
  1981. break;
  1982. case wifi::State::Init:
  1983. out = PSTR("Init");
  1984. break;
  1985. case wifi::State::Timeout:
  1986. out = PSTR("Timeout");
  1987. break;
  1988. case wifi::State::WaitScan:
  1989. out = PSTR("WaitScan");
  1990. break;
  1991. case wifi::State::WaitScanWithoutCurrent:
  1992. out = PSTR("WaitScanWithoutCurrent");
  1993. break;
  1994. case wifi::State::WaitConnected:
  1995. out = PSTR("WaitConnected");
  1996. break;
  1997. }
  1998. return out;
  1999. }
  2000. } // namespace debug
  2001. namespace internal {
  2002. // STA + AP FALLBACK:
  2003. // - try connection
  2004. // - if ok, stop existing AP
  2005. // - if not, keep / start AP
  2006. //
  2007. // STA:
  2008. // - try connection
  2009. // - don't do anything on completion
  2010. //
  2011. // TODO? WPS / SMARTCONFIG + STA + AP FALLBACK
  2012. // - same as above
  2013. // - when requested, make sure there are no active connections
  2014. // abort when sta connected or ap is connected
  2015. // - run autoconf, receive credentials and store in a free settings slot
  2016. // TODO: provide a clearer 'unroll' of the current state?
  2017. using EventCallbacks = std::forward_list<wifi::EventCallback>;
  2018. EventCallbacks callbacks;
  2019. void publish(wifi::Event event) {
  2020. for (auto& callback : callbacks) {
  2021. callback(event);
  2022. }
  2023. }
  2024. void subscribe(wifi::EventCallback callback) {
  2025. callbacks.push_front(callback);
  2026. }
  2027. State handleAction(State& state, Action action) {
  2028. switch (action) {
  2029. case Action::StationConnect:
  2030. if (!wifi::sta::enabled()) {
  2031. wifi::sta::enable();
  2032. publish(wifi::Event::Mode);
  2033. }
  2034. if (!wifi::sta::connected()) {
  2035. if (wifi::sta::connecting()) {
  2036. wifi::sta::connection::schedule_next();
  2037. } else {
  2038. state = State::Init;
  2039. }
  2040. }
  2041. break;
  2042. case Action::StationContinueConnect:
  2043. if (wifi::sta::connecting()) {
  2044. state = State::Connect;
  2045. }
  2046. break;
  2047. case Action::StationDisconnect:
  2048. if (wifi::sta::connected()) {
  2049. wifi::ap::fallback::remove();
  2050. wifi::sta::disconnect();
  2051. }
  2052. wifi::sta::connection::stop();
  2053. if (wifi::sta::enabled()) {
  2054. wifi::sta::disable();
  2055. publish(wifi::Event::Mode);
  2056. }
  2057. break;
  2058. case Action::StationTryConnectBetter:
  2059. if (!wifi::sta::connected() || wifi::sta::connecting()) {
  2060. wifi::sta::scan::periodic::stop();
  2061. break;
  2062. }
  2063. if (wifi::sta::scan::periodic::check()) {
  2064. state = State::TryConnectBetter;
  2065. }
  2066. break;
  2067. case Action::AccessPointFallback:
  2068. case Action::AccessPointStart:
  2069. if (!wifi::ap::enabled()) {
  2070. wifi::ap::enable();
  2071. wifi::ap::start(
  2072. wifi::ap::settings::defaultSsid(),
  2073. wifi::ap::settings::ssid(),
  2074. wifi::ap::settings::passphrase(),
  2075. wifi::ap::settings::channel());
  2076. publish(wifi::Event::Mode);
  2077. if ((Action::AccessPointFallback == action)
  2078. && wifi::ap::fallback::enabled()) {
  2079. wifi::ap::fallback::schedule();
  2080. }
  2081. }
  2082. break;
  2083. case Action::AccessPointFallbackCheck:
  2084. if (wifi::ap::fallback::enabled()) {
  2085. wifi::ap::fallback::check();
  2086. }
  2087. break;
  2088. case Action::AccessPointStop:
  2089. if (wifi::ap::enabled()) {
  2090. wifi::ap::fallback::remove();
  2091. wifi::ap::stop();
  2092. wifi::ap::disable();
  2093. publish(wifi::Event::Mode);
  2094. }
  2095. break;
  2096. case Action::TurnOff:
  2097. if (wifi::enabled()) {
  2098. wifi::ap::fallback::remove();
  2099. wifi::ap::stop();
  2100. wifi::ap::disable();
  2101. wifi::sta::scan::periodic::stop();
  2102. wifi::sta::connection::stop();
  2103. wifi::sta::disconnect();
  2104. wifi::sta::disable();
  2105. wifi::disable();
  2106. publish(wifi::Event::Mode);
  2107. break;
  2108. }
  2109. break;
  2110. case Action::TurnOn:
  2111. if (!wifi::enabled()) {
  2112. wifi::enable();
  2113. wifi::settings::configure();
  2114. }
  2115. break;
  2116. }
  2117. return state;
  2118. }
  2119. bool prepareConnection() {
  2120. if (wifi::sta::enabled()) {
  2121. wifi::sta::connection::prepare(wifi::sta::networks());
  2122. return wifi::sta::connection::prepared();
  2123. }
  2124. return false;
  2125. }
  2126. void loop() {
  2127. static State state { State::Boot };
  2128. static State last_state { state };
  2129. if (last_state != state) {
  2130. DEBUG_MSG_P(PSTR("[WIFI] State %s -> %s\n"),
  2131. debug::state(last_state),
  2132. debug::state(state));
  2133. last_state = state;
  2134. }
  2135. switch (state) {
  2136. case State::Boot:
  2137. state = State::Idle;
  2138. publish(wifi::Event::Initial);
  2139. break;
  2140. case State::Init: {
  2141. if (!prepareConnection()) {
  2142. state = State::Fallback;
  2143. break;
  2144. }
  2145. wifi::sta::scan::periodic::stop();
  2146. if (wifi::sta::scan::settings::enabled()) {
  2147. if (wifi::sta::scanning()) {
  2148. break;
  2149. }
  2150. wifi::sta::connection::scanNetworks();
  2151. state = State::WaitScan;
  2152. break;
  2153. }
  2154. state = State::Connect;
  2155. break;
  2156. }
  2157. case State::TryConnectBetter:
  2158. if (wifi::sta::scan::settings::enabled()) {
  2159. if (wifi::sta::scanning()) {
  2160. break;
  2161. }
  2162. if (!prepareConnection()) {
  2163. state = State::Idle;
  2164. break;
  2165. }
  2166. wifi::sta::scan::periodic::stop();
  2167. wifi::sta::connection::scanNetworks();
  2168. state = State::WaitScanWithoutCurrent;
  2169. break;
  2170. }
  2171. state = State::Idle;
  2172. break;
  2173. case State::Fallback:
  2174. state = State::Idle;
  2175. wifi::sta::connection::schedule_new();
  2176. if (wifi::ApMode::Fallback == wifi::ap::settings::mode()) {
  2177. wifi::action(wifi::Action::AccessPointFallback);
  2178. }
  2179. publish(wifi::Event::StationReconnect);
  2180. break;
  2181. case State::WaitScan:
  2182. if (wifi::sta::scanning()) {
  2183. break;
  2184. }
  2185. wifi::sta::connection::scanProcessResults();
  2186. state = State::Connect;
  2187. break;
  2188. case State::WaitScanWithoutCurrent:
  2189. if (wifi::sta::scanning()) {
  2190. break;
  2191. }
  2192. if (wifi::sta::connection::scanProcessResults(wifi::sta::info())) {
  2193. wifi::sta::disconnect();
  2194. state = State::Connect;
  2195. break;
  2196. }
  2197. state = State::Idle;
  2198. break;
  2199. case State::Connect: {
  2200. if (!wifi::sta::connecting()) {
  2201. if (!wifi::sta::connection::start(systemHostname())) {
  2202. state = State::Timeout;
  2203. break;
  2204. }
  2205. }
  2206. if (wifi::sta::connection::connect()) {
  2207. state = State::WaitConnected;
  2208. publish(wifi::Event::StationConnecting);
  2209. } else {
  2210. state = State::Timeout;
  2211. }
  2212. break;
  2213. }
  2214. case State::WaitConnected:
  2215. if (wifi::sta::connection::wait()) {
  2216. break;
  2217. }
  2218. if (wifi::sta::connected()) {
  2219. state = State::Connected;
  2220. break;
  2221. }
  2222. state = State::Timeout;
  2223. break;
  2224. // Current logic closely follows the SDK connection routine with reconnect enabled,
  2225. // and will retry the same network multiple times before giving up.
  2226. case State::Timeout:
  2227. if (wifi::sta::connecting() && wifi::sta::connection::next()) {
  2228. state = State::Idle;
  2229. wifi::sta::connection::schedule_next();
  2230. publish(wifi::Event::StationTimeout);
  2231. } else {
  2232. wifi::sta::connection::stop();
  2233. state = State::Fallback;
  2234. }
  2235. break;
  2236. case State::Connected:
  2237. wifi::sta::connection::stop();
  2238. if (wifi::sta::scan::settings::enabled()) {
  2239. wifi::sta::scan::periodic::start();
  2240. }
  2241. state = State::Idle;
  2242. publish(wifi::Event::StationConnected);
  2243. break;
  2244. case State::Idle: {
  2245. auto& actions = wifi::actions();
  2246. if (!actions.empty()) {
  2247. state = handleAction(state, actions.front());
  2248. actions.pop();
  2249. }
  2250. break;
  2251. }
  2252. }
  2253. // SDK disconnection event is specific to the phy layer. i.e. it will happen all the same
  2254. // when trying to connect and being unable to find the AP, being forced out by the AP with bad credentials
  2255. // or being disconnected when the wireless signal is lost.
  2256. // Thus, provide a specific connected -> disconnected event specific to the IP network availability.
  2257. if (wifi::sta::connection::lost()) {
  2258. wifi::sta::scan::periodic::stop();
  2259. if (wifi::sta::connection::persist()) {
  2260. wifi::sta::connection::schedule_new(wifi::sta::build::RecoveryInterval);
  2261. }
  2262. publish(wifi::Event::StationDisconnected);
  2263. }
  2264. #if WIFI_AP_CAPTIVE_SUPPORT
  2265. // Captive portal only queues packets and those need to be processed asap
  2266. if (wifi::ap::enabled() && wifi::ap::captive()) {
  2267. wifi::ap::dnsLoop();
  2268. }
  2269. #endif
  2270. #if WIFI_GRATUITOUS_ARP_SUPPORT
  2271. // ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546
  2272. // Periodically send out ARP, even if no one asked
  2273. if (wifi::sta::connected() && !wifi::sta::garp::wait()) {
  2274. wifi::sta::garp::send();
  2275. }
  2276. #endif
  2277. }
  2278. // XXX: With Arduino Core 3.0.0, WiFi is asleep on boot
  2279. // It will wake up when calling WiFi::mode(...):
  2280. // - WiFi.begin(...)
  2281. // - WiFi.softAP(...)
  2282. // - WiFi.enableSTA(...)
  2283. // - WiFi.enableAP(...)
  2284. // ref. https://github.com/esp8266/Arduino/pull/7902
  2285. void init() {
  2286. WiFi.persistent(false);
  2287. wifi::ap::init();
  2288. wifi::sta::init();
  2289. }
  2290. } // namespace internal
  2291. void setup() {
  2292. internal::init();
  2293. migrateVersion(settings::migrate);
  2294. settings::query::setup();
  2295. action(wifi::Action::TurnOn);
  2296. #if SYSTEM_CHECK_ENABLED
  2297. if (!systemCheck()) {
  2298. actions() = wifi::ActionsQueue{};
  2299. action(wifi::Action::TurnOn);
  2300. action(wifi::Action::AccessPointStart);
  2301. }
  2302. #endif
  2303. #if DEBUG_SUPPORT
  2304. wifiRegister([](Event event) {
  2305. DEBUG_MSG_P(PSTR("[WIFI] %s\n"), debug::event(event).c_str());
  2306. });
  2307. #endif
  2308. #if WEB_SUPPORT
  2309. wsRegister()
  2310. .onAction(web::onAction)
  2311. .onConnected(web::onConnected)
  2312. .onKeyCheck(web::onKeyCheck);
  2313. #endif
  2314. #if TERMINAL_SUPPORT
  2315. terminal::init();
  2316. #endif
  2317. espurnaRegisterLoop(internal::loop);
  2318. espurnaRegisterReload(settings::configure);
  2319. }
  2320. } // namespace
  2321. } // namespace wifi
  2322. } // namespace espurna
  2323. // -----------------------------------------------------------------------------
  2324. // API
  2325. // -----------------------------------------------------------------------------
  2326. void wifiRegister(espurna::wifi::EventCallback callback) {
  2327. espurna::wifi::internal::subscribe(callback);
  2328. }
  2329. bool wifiConnectable() {
  2330. return espurna::wifi::ap::enabled();
  2331. }
  2332. bool wifiConnected() {
  2333. return espurna::wifi::sta::connected();
  2334. }
  2335. IPAddress wifiStaIp() {
  2336. if (espurna::wifi::opmode() & espurna::wifi::OpmodeSta) {
  2337. return espurna::wifi::sta::ip();
  2338. }
  2339. return {};
  2340. }
  2341. String wifiStaSsid() {
  2342. if (espurna::wifi::opmode() & espurna::wifi::OpmodeSta) {
  2343. auto current = espurna::wifi::sta::current();
  2344. return current.ssid;
  2345. }
  2346. return emptyString;
  2347. }
  2348. void wifiDisconnect() {
  2349. espurna::wifi::sta::disconnect();
  2350. }
  2351. void wifiToggleAp() {
  2352. espurna::wifi::ap::toggle();
  2353. }
  2354. void wifiToggleSta() {
  2355. espurna::wifi::sta::toggle();
  2356. }
  2357. void wifiStartAp() {
  2358. espurna::wifi::action(
  2359. espurna::wifi::Action::AccessPointStart);
  2360. }
  2361. bool wifiDisabled() {
  2362. return espurna::wifi::opmode()
  2363. == espurna::wifi::OpmodeNull;
  2364. }
  2365. void wifiDisable() {
  2366. espurna::wifi::ap::fallback::remove();
  2367. espurna::wifi::sta::scan::periodic::stop();
  2368. espurna::wifi::ensure_opmode(
  2369. espurna::wifi::OpmodeNull);
  2370. }
  2371. void wifiTurnOff() {
  2372. espurna::wifi::action(
  2373. espurna::wifi::Action::TurnOff);
  2374. }
  2375. void wifiTurnOn() {
  2376. espurna::wifi::action(
  2377. espurna::wifi::Action::TurnOn);
  2378. }
  2379. void wifiApCheck() {
  2380. espurna::wifi::action(
  2381. espurna::wifi::Action::AccessPointFallbackCheck);
  2382. }
  2383. size_t wifiApStations() {
  2384. if (espurna::wifi::ap::enabled()) {
  2385. return espurna::wifi::ap::stations();
  2386. }
  2387. return 0;
  2388. }
  2389. IPAddress wifiApIp() {
  2390. return espurna::wifi::ap::ip();
  2391. }
  2392. void wifiSetup() {
  2393. espurna::wifi::setup();
  2394. }