Fork of the espurna firmware for `mhsw` switches
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.

2437 lines
61 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 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
6 years ago
8 years ago
8 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 "wifi_config.h"
  10. #include "telnet.h"
  11. #include "ws.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 <queue>
  19. #include <vector>
  20. // -----------------------------------------------------------------------------
  21. // SETTINGS
  22. // -----------------------------------------------------------------------------
  23. namespace settings {
  24. namespace internal {
  25. template<>
  26. wifi::StaMode convert(const String& value) {
  27. return convert<bool>(value)
  28. ? wifi::StaMode::Enabled
  29. : wifi::StaMode::Disabled;
  30. }
  31. template<>
  32. wifi::ApMode convert(const String& value) {
  33. switch (value.toInt()) {
  34. case 0:
  35. return wifi::ApMode::Disabled;
  36. case 1:
  37. return wifi::ApMode::Enabled;
  38. case 2:
  39. return wifi::ApMode::Fallback;
  40. }
  41. return wifi::build::softApMode();
  42. }
  43. template <>
  44. WiFiSleepType_t convert(const String& value) {
  45. switch (value.toInt()) {
  46. case 2:
  47. return WIFI_MODEM_SLEEP;
  48. case 1:
  49. return WIFI_LIGHT_SLEEP;
  50. case 0:
  51. return WIFI_NONE_SLEEP;
  52. }
  53. return wifi::build::sleep();
  54. }
  55. template <>
  56. IPAddress convert(const String& value) {
  57. IPAddress out;
  58. out.fromString(value);
  59. return out;
  60. }
  61. // XXX: "(IP unset)" when not set, no point saving these :/
  62. String serialize(const IPAddress& ip) {
  63. return ip.isSet() ? ip.toString() : emptyString;
  64. }
  65. } // namespace internal
  66. } // namespace settings
  67. // -----------------------------------------------------------------------------
  68. // INTERNAL
  69. // -----------------------------------------------------------------------------
  70. namespace wifi {
  71. // XXX: esp8266 Arduino API inclues pseudo-modes and is not directly convertible
  72. // into the SDK constants. Provide a constexpr version of the enum, since the code never
  73. // actually uses `WiFi::mode(...)` directly, *but* opmode is retrieved using the SDK function.
  74. constexpr uint8_t OpmodeNull { NULL_MODE };
  75. constexpr uint8_t OpmodeSta { STATION_MODE };
  76. constexpr uint8_t OpmodeAp { SOFTAP_MODE };
  77. constexpr uint8_t OpmodeApSta { OpmodeSta | OpmodeAp };
  78. using Mac = std::array<uint8_t, 6>;
  79. using Macs = std::vector<Mac>;
  80. enum class ScanError {
  81. None,
  82. AlreadyScanning,
  83. System,
  84. NoNetworks
  85. };
  86. enum class Action {
  87. StationConnect,
  88. StationContinueConnect,
  89. StationTryConnectBetter,
  90. StationDisconnect,
  91. AccessPointFallback,
  92. AccessPointFallbackCheck,
  93. AccessPointStart,
  94. AccessPointStop,
  95. TurnOff,
  96. TurnOn
  97. };
  98. using Actions = std::list<Action>;
  99. using ActionsQueue = std::queue<Action, Actions>;
  100. enum class State {
  101. Boot,
  102. Connect,
  103. TryConnectBetter,
  104. Connected,
  105. Idle,
  106. Init,
  107. Timeout,
  108. Fallback,
  109. WaitScan,
  110. WaitScanWithoutCurrent,
  111. WaitConnected
  112. };
  113. namespace internal {
  114. // Module actions are controled in a serialzed manner, when internal loop is done with the
  115. // current task and is free to take up another one. Allow to toggle OFF for the whole module,
  116. // discarding any actions involving an active WiFi. Default is ON
  117. bool enabled { true };
  118. ActionsQueue actions;
  119. } // namespace internal
  120. uint8_t opmode() {
  121. return wifi_get_opmode();
  122. }
  123. bool enabled() {
  124. return internal::enabled;
  125. }
  126. void enable() {
  127. internal::enabled = true;
  128. }
  129. void disable() {
  130. internal::enabled = false;
  131. }
  132. void action(Action value) {
  133. switch (value) {
  134. case Action::StationConnect:
  135. case Action::StationTryConnectBetter:
  136. case Action::StationContinueConnect:
  137. case Action::StationDisconnect:
  138. case Action::AccessPointFallback:
  139. case Action::AccessPointFallbackCheck:
  140. case Action::AccessPointStart:
  141. case Action::AccessPointStop:
  142. if (!enabled()) {
  143. return;
  144. }
  145. break;
  146. case Action::TurnOff:
  147. case Action::TurnOn:
  148. break;
  149. }
  150. internal::actions.push(value);
  151. }
  152. ActionsQueue& actions() {
  153. return internal::actions;
  154. }
  155. // ::forceSleepBegin() remembers the previous mode and ::forceSleepWake() calls station connect when it has STA in it :/
  156. // while we *do* set opmode to 0 to avoid this uncertainty, preper to call wake through SDK instead of the Arduino wrapper
  157. //
  158. // 0xFFFFFFF is a magic number per the NONOS API reference, 3.7.5 wifi_fpm_do_sleep:
  159. // > If sleep_time_in_us is 0xFFFFFFF, the ESP8266 will sleep till be woke up as below:
  160. // > • If wifi_fpm_set_sleep_type is set to be LIGHT_SLEEP_T, ESP8266 can wake up by GPIO.
  161. // > • If wifi_fpm_set_sleep_type is set to be MODEM_SLEEP_T, ESP8266 can wake up by wifi_fpm_do_wakeup.
  162. //
  163. // In our case, wake-up is software driven, so the MODEM sleep is the only choice available.
  164. // This version can *only* work from CONT context, since the only consumer atm is wifi::Action handler
  165. // TODO(esp32): Null mode turns off radio, no need for these
  166. bool sleep() {
  167. if ((opmode() == ::wifi::OpmodeNull) && (wifi_fpm_get_sleep_type() == NONE_SLEEP_T)) {
  168. wifi_fpm_set_sleep_type(MODEM_SLEEP_T);
  169. yield();
  170. wifi_fpm_open();
  171. yield();
  172. if (0 == wifi_fpm_do_sleep(0xFFFFFFF)) {
  173. delay(10);
  174. return true;
  175. }
  176. }
  177. return false;
  178. }
  179. bool wakeup() {
  180. if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) {
  181. wifi_fpm_do_wakeup();
  182. wifi_fpm_close();
  183. delay(10);
  184. return true;
  185. }
  186. return false;
  187. }
  188. namespace debug {
  189. String error(wifi::ScanError error) {
  190. const __FlashStringHelper* ptr { nullptr };
  191. switch (error) {
  192. case wifi::ScanError::AlreadyScanning:
  193. ptr = F("Scan already in progress");
  194. break;
  195. case wifi::ScanError::System:
  196. ptr = F("Could not start the scan");
  197. break;
  198. case wifi::ScanError::NoNetworks:
  199. ptr = F("No networks");
  200. break;
  201. case wifi::ScanError::None:
  202. ptr = F("OK");
  203. break;
  204. }
  205. return ptr;
  206. }
  207. String ip(const IPAddress& addr) {
  208. return addr.toString();
  209. }
  210. String ip(ip4_addr_t addr) {
  211. String out;
  212. out.reserve(16);
  213. bool delim { false };
  214. for (int byte = 0; byte < 4; ++byte) {
  215. if (delim) {
  216. out += '.';
  217. }
  218. out += ip4_addr_get_byte_val(addr, byte);
  219. delim = true;
  220. }
  221. return out;
  222. }
  223. String mac(const wifi::Mac& mac) {
  224. String out;
  225. out.reserve(18);
  226. bool delim { false };
  227. char buffer[3] = {0};
  228. for (auto& byte : mac) {
  229. hexEncode(&byte, 1, buffer, sizeof(buffer));
  230. if (delim) {
  231. out += ':';
  232. }
  233. out += buffer;
  234. delim = true;
  235. }
  236. return out;
  237. }
  238. String authmode(AUTH_MODE mode) {
  239. const __FlashStringHelper* ptr { F("UNKNOWN") };
  240. switch (mode) {
  241. case AUTH_OPEN:
  242. ptr = F("OPEN");
  243. break;
  244. case AUTH_WEP:
  245. ptr = F("WEP");
  246. break;
  247. case AUTH_WPA_PSK:
  248. ptr = F("WPAPSK");
  249. break;
  250. case AUTH_WPA2_PSK:
  251. ptr = F("WPA2PSK");
  252. break;
  253. case AUTH_WPA_WPA2_PSK:
  254. ptr = F("WPAWPA2-PSK");
  255. break;
  256. case AUTH_MAX:
  257. break;
  258. }
  259. return ptr;
  260. }
  261. String opmode(uint8_t mode) {
  262. const __FlashStringHelper* ptr { nullptr };
  263. switch (mode) {
  264. case ::wifi::OpmodeApSta:
  265. ptr = F("AP+STA");
  266. break;
  267. case ::wifi::OpmodeSta:
  268. ptr = F("STA");
  269. break;
  270. case ::wifi::OpmodeAp:
  271. ptr = F("AP");
  272. break;
  273. case ::wifi::OpmodeNull:
  274. ptr = F("NULL");
  275. break;
  276. }
  277. return ptr;
  278. }
  279. } // namespace debug
  280. namespace settings {
  281. void migrate(int version) {
  282. if (version && (version < 5)) {
  283. moveSetting("apmode", "wifiApMode");
  284. }
  285. }
  286. decltype(millis()) garpInterval() {
  287. return getSetting("wifiGarpIntvl", secureRandom(wifi::build::garpIntervalMin(), wifi::build::garpIntervalMax()));
  288. }
  289. float txPower() {
  290. return getSetting("wifiTxPwr", wifi::build::outputDbm());
  291. }
  292. WiFiSleepType_t sleep() {
  293. return getSetting("wifiSleep", wifi::build::sleep());
  294. }
  295. bool scanNetworks() {
  296. return getSetting("wifiScan", wifi::build::scanNetworks());
  297. }
  298. int8_t scanRssiThreshold() {
  299. return getSetting("wifiScanRssi", wifi::build::scanRssiThreshold());
  300. }
  301. String hostname() {
  302. return getSetting("hostname", getIdentifier());
  303. }
  304. wifi::StaMode staMode() {
  305. return getSetting("wifiStaMode", wifi::build::staMode());
  306. }
  307. IPAddress staIp(size_t index) {
  308. return ::settings::internal::convert<IPAddress>(
  309. getSetting({"ip", index}, wifi::build::ip(index)));
  310. }
  311. String staSsid(size_t index) {
  312. return getSetting({"ssid", index}, wifi::build::ssid(index));
  313. }
  314. String staPassphrase(size_t index) {
  315. return getSetting({"pass", index}, wifi::build::passphrase(index));
  316. }
  317. IPAddress staGateway(size_t index) {
  318. return ::settings::internal::convert<IPAddress>(
  319. getSetting({"gw", index}, wifi::build::gateway(index)));
  320. }
  321. IPAddress staMask(size_t index) {
  322. return ::settings::internal::convert<IPAddress>(
  323. getSetting({"mask", index}, wifi::build::mask(index)));
  324. }
  325. IPAddress staDns(size_t index) {
  326. return ::settings::internal::convert<IPAddress>(
  327. getSetting({"dns", index}, wifi::build::dns(index)));
  328. }
  329. bool softApCaptive() {
  330. return getSetting("wifiApCaptive", wifi::build::softApCaptive());
  331. }
  332. wifi::ApMode softApMode() {
  333. return getSetting("wifiApMode", wifi::build::softApMode());
  334. }
  335. String softApSsid() {
  336. return getSetting("wifiApSsid", wifi::build::hasSoftApSsid()
  337. ? wifi::build::softApSsid()
  338. : hostname());
  339. }
  340. String softApPassphrase() {
  341. return getSetting("wifiApPass", wifi::build::hasSoftApPassphrase()
  342. ? wifi::build::softApPassphrase()
  343. : getAdminPass());
  344. }
  345. int8_t softApChannel() {
  346. return getSetting("wifiApChannel", wifi::build::softApChannel());
  347. }
  348. wifi::Mac softApLease(size_t index) {
  349. wifi::Mac lease { 0u, 0u, 0u, 0u, 0u, 0u };
  350. auto value = getSetting({"wifiApLease", index});
  351. if (12 == value.length()) {
  352. hexDecode(value.c_str(), value.length(), lease.data(), lease.size());
  353. }
  354. return lease;
  355. }
  356. } // namespace settings
  357. // We are guaranteed to have '\0' when <32 b/c the SDK zeroes out the data
  358. // But, these are byte arrays, not C strings. When ssid_len is available, use it.
  359. // When not, we are still expecting the <32 arrays to have '\0' at the end and we manually
  360. // set the 32'nd char to '\0' to prevent conversion issues
  361. namespace {
  362. String convertSsid(const softap_config& config) {
  363. String ssid;
  364. ssid.concat(reinterpret_cast<const char*>(config.ssid), config.ssid_len);
  365. return ssid;
  366. }
  367. String convertSsid(const bss_info& info) {
  368. String ssid;
  369. ssid.concat(reinterpret_cast<const char*>(info.ssid), info.ssid_len);
  370. return ssid;
  371. }
  372. String convertSsid(const station_config& config) {
  373. constexpr size_t SsidSize { sizeof(softap_config::ssid) };
  374. const char* ptr { reinterpret_cast<const char*>(config.ssid) };
  375. char ssid[SsidSize + 1];
  376. std::copy(ptr, ptr + SsidSize, ssid);
  377. ssid[SsidSize] = '\0';
  378. return ssid;
  379. }
  380. template <typename T, size_t PassphraseSize = sizeof(T::password)>
  381. String convertPassphrase(const T& config) {
  382. const char* ptr { reinterpret_cast<const char*>(config.password) };
  383. char passphrase[PassphraseSize + 1];
  384. std::copy(ptr, ptr + PassphraseSize, passphrase);
  385. passphrase[PassphraseSize] = '\0';
  386. return passphrase;
  387. }
  388. template <typename T>
  389. wifi::Mac convertBssid(const T& info) {
  390. wifi::Mac mac;
  391. std::copy(info.bssid, info.bssid + 6, mac.begin());
  392. return mac;
  393. }
  394. } // namespace
  395. struct Info {
  396. Info() = default;
  397. Info(const Info&) = default;
  398. Info(Info&&) = default;
  399. Info(wifi::Mac&& bssid, AUTH_MODE authmode, int8_t rssi, uint8_t channel) :
  400. _bssid(std::move(bssid)),
  401. _authmode(authmode),
  402. _rssi(rssi),
  403. _channel(channel)
  404. {}
  405. explicit Info(const bss_info& info) :
  406. _bssid(convertBssid(info)),
  407. _authmode(info.authmode),
  408. _rssi(info.rssi),
  409. _channel(info.channel)
  410. {}
  411. Info& operator=(const Info&) = default;
  412. Info& operator=(Info&&) = default;
  413. Info& operator=(const bss_info& info) {
  414. _bssid = convertBssid(info);
  415. _authmode = info.authmode;
  416. _channel = info.channel;
  417. _rssi = info.rssi;
  418. return *this;
  419. }
  420. explicit operator bool() const {
  421. return _rssi != 0 && _channel != 0;
  422. }
  423. bool operator<(const Info& rhs) const {
  424. return _rssi < rhs._rssi;
  425. }
  426. bool operator>(const Info& rhs) const {
  427. return _rssi > rhs._rssi;
  428. }
  429. const wifi::Mac& bssid() const {
  430. return _bssid;
  431. }
  432. AUTH_MODE authmode() const {
  433. return _authmode;
  434. }
  435. int8_t rssi() const {
  436. return _rssi;
  437. }
  438. uint8_t channel() const {
  439. return _channel;
  440. }
  441. private:
  442. //Mac _bssid {{ 0u, 0u, 0u, 0u, 0u, 0u }}; // TODO: gcc4 can't figure out basic aggregate, replace when using gcc10 builds
  443. Mac _bssid {};
  444. AUTH_MODE _authmode { AUTH_OPEN };
  445. int8_t _rssi { 0 };
  446. uint8_t _channel { 0u };
  447. };
  448. struct SsidInfo {
  449. SsidInfo() = delete;
  450. explicit SsidInfo(const bss_info& info) :
  451. _ssid(convertSsid(info)),
  452. _info(info)
  453. {}
  454. SsidInfo(String&& ssid, wifi::Info&& info) :
  455. _ssid(std::move(ssid)),
  456. _info(std::move(info))
  457. {}
  458. const String& ssid() const {
  459. return _ssid;
  460. }
  461. const wifi::Info& info() const {
  462. return _info;
  463. }
  464. // decreasing order by rssi (default sort() order is increasing)
  465. bool operator<(const SsidInfo& rhs) const {
  466. if (!_info.rssi()) {
  467. return false;
  468. }
  469. return info() > rhs.info();
  470. }
  471. private:
  472. String _ssid;
  473. wifi::Info _info;
  474. };
  475. using SsidInfos = std::forward_list<SsidInfo>;
  476. // Note that lwip config allows up to 3 DNS servers. But, most of the time we use DHCP.
  477. // TODO: ::dns(size_t index)? how'd that look with settings?
  478. struct IpSettings {
  479. IpSettings() = default;
  480. IpSettings(const IpSettings&) = default;
  481. IpSettings(IpSettings&&) = default;
  482. IpSettings& operator=(const IpSettings&) = default;
  483. IpSettings& operator=(IpSettings&&) = default;
  484. template <typename Ip, typename Gateway, typename Netmask, typename Dns>
  485. IpSettings(Ip&& ip, Gateway&& gateway, Netmask&& netmask, Dns&& dns) :
  486. _ip(std::forward<Ip>(ip)),
  487. _gateway(std::forward<Gateway>(gateway)),
  488. _netmask(std::forward<Netmask>(netmask)),
  489. _dns(std::forward<Dns>(dns))
  490. {}
  491. const IPAddress& ip() const {
  492. return _ip;
  493. }
  494. const IPAddress& gateway() const {
  495. return _gateway;
  496. }
  497. const IPAddress& netmask() const {
  498. return _netmask;
  499. }
  500. const IPAddress& dns() const {
  501. return _dns;
  502. }
  503. explicit operator bool() const {
  504. return _ip.isSet()
  505. && _gateway.isSet()
  506. && _netmask.isSet()
  507. && _dns.isSet();
  508. }
  509. private:
  510. IPAddress _ip;
  511. IPAddress _gateway;
  512. IPAddress _netmask;
  513. IPAddress _dns;
  514. };
  515. struct StaNetwork {
  516. Mac bssid;
  517. String ssid;
  518. String passphrase;
  519. int8_t rssi;
  520. uint8_t channel;
  521. };
  522. struct SoftApNetwork {
  523. Mac bssid;
  524. String ssid;
  525. String passphrase;
  526. uint8_t channel;
  527. AUTH_MODE authmode;
  528. };
  529. struct Network {
  530. Network() = delete;
  531. Network(const Network&) = default;
  532. Network(Network&&) = default;
  533. Network& operator=(Network&&) = default;
  534. template <typename Ssid>
  535. explicit Network(Ssid&& ssid) :
  536. _ssid(std::forward<Ssid>(ssid))
  537. {}
  538. template <typename Ssid, typename Passphrase>
  539. Network(Ssid&& ssid, Passphrase&& passphrase) :
  540. _ssid(std::forward<Ssid>(ssid)),
  541. _passphrase(std::forward<Passphrase>(passphrase))
  542. {}
  543. template <typename Ssid, typename Passphrase, typename Settings>
  544. Network(Ssid&& ssid, Passphrase&& passphrase, Settings&& settings) :
  545. _ssid(std::forward<Ssid>(ssid)),
  546. _passphrase(std::forward<Passphrase>(passphrase)),
  547. _ipSettings(std::forward<Settings>(settings))
  548. {}
  549. // TODO(?): in case SDK API is used directly, this also could use an authmode field
  550. // Arduino wrapper sets WPAPSK minimum by default, so one use-case is to set it to WPA2PSK
  551. Network(Network&& other, wifi::Mac bssid, uint8_t channel) :
  552. _ssid(std::move(other._ssid)),
  553. _passphrase(std::move(other._passphrase)),
  554. _ipSettings(std::move(other._ipSettings)),
  555. _bssid(bssid),
  556. _channel(channel)
  557. {}
  558. bool dhcp() const {
  559. return !_ipSettings;
  560. }
  561. const String& ssid() const {
  562. return _ssid;
  563. }
  564. const String& passphrase() const {
  565. return _passphrase;
  566. }
  567. const IpSettings& ipSettings() const {
  568. return _ipSettings;
  569. }
  570. const wifi::Mac& bssid() const {
  571. return _bssid;
  572. }
  573. uint8_t channel() const {
  574. return _channel;
  575. }
  576. private:
  577. String _ssid;
  578. String _passphrase;
  579. IpSettings _ipSettings;
  580. Mac _bssid {};
  581. uint8_t _channel { 0u };
  582. };
  583. using Networks = std::list<Network>;
  584. // -----------------------------------------------------------------------------
  585. // STATION
  586. // -----------------------------------------------------------------------------
  587. namespace sta {
  588. constexpr auto ConnectionInterval = wifi::build::staConnectionInterval();
  589. constexpr auto ConnectionRetries = wifi::build::staConnectionRetries();
  590. constexpr auto ReconnectionInterval = wifi::build::staReconnectionInterval();
  591. uint8_t channel() {
  592. return wifi_get_channel();
  593. }
  594. int8_t rssi() {
  595. return wifi_station_get_rssi();
  596. }
  597. // Note that authmode is a spefific threshold selected by the Arduino WiFi.begin()
  598. // (ref. Arduino ESP8266WiFi default, which is AUTH_WPA_WPA2_PSK in the current 3.0.0)
  599. // Also, it is not really clear whether `wifi_get_channel()` will work correctly in the future versions,
  600. // since the API seems to be related to the promiscuous WiFi (aka sniffer), but it does return the correct values.
  601. wifi::Info info(const station_config& config) {
  602. return wifi::Info{
  603. convertBssid(config),
  604. config.threshold.authmode,
  605. rssi(),
  606. channel()};
  607. }
  608. wifi::Info info() {
  609. station_config config{};
  610. wifi_station_get_config(&config);
  611. return info(config);
  612. }
  613. wifi::IpSettings ipsettings() {
  614. return {
  615. WiFi.localIP(),
  616. WiFi.gatewayIP(),
  617. WiFi.subnetMask(),
  618. WiFi.dnsIP()};
  619. }
  620. wifi::Mac bssid() {
  621. station_config config{};
  622. wifi_station_get_config(&config);
  623. return convertBssid(config);
  624. }
  625. wifi::StaNetwork current(const station_config& config) {
  626. return {
  627. convertBssid(config),
  628. convertSsid(config),
  629. convertPassphrase(config),
  630. rssi(),
  631. channel()};
  632. }
  633. wifi::StaNetwork current() {
  634. station_config config{};
  635. wifi_station_get_config(&config);
  636. return current(config);
  637. }
  638. #if WIFI_GRATUITOUS_ARP_SUPPORT
  639. namespace garp {
  640. namespace internal {
  641. Ticker timer;
  642. bool wait { false };
  643. decltype(millis()) interval { wifi::build::garpIntervalMin() };
  644. } // namespace internal
  645. bool send() {
  646. bool result { false };
  647. for (netif* interface = netif_list; interface != nullptr; interface = interface->next) {
  648. if (
  649. (interface->flags & NETIF_FLAG_ETHARP)
  650. && (interface->hwaddr_len == ETHARP_HWADDR_LEN)
  651. && (!ip4_addr_isany_val(*netif_ip4_addr(interface)))
  652. && (interface->flags & NETIF_FLAG_LINK_UP)
  653. && (interface->flags & NETIF_FLAG_UP)
  654. ) {
  655. etharp_gratuitous(interface);
  656. result = true;
  657. }
  658. }
  659. return result;
  660. }
  661. bool wait() {
  662. if (internal::wait) {
  663. return true;
  664. }
  665. internal::wait = true;
  666. return false;
  667. }
  668. void stop() {
  669. internal::timer.detach();
  670. }
  671. void start(decltype(millis()) ms) {
  672. internal::timer.attach_ms(ms, []() {
  673. internal::wait = false;
  674. });
  675. }
  676. } // namespace garp
  677. #endif
  678. namespace scan {
  679. using SsidInfosPtr = std::shared_ptr<wifi::SsidInfos>;
  680. using Success = std::function<void(bss_info*)>;
  681. using Error = std::function<void(wifi::ScanError)>;
  682. struct Task {
  683. Task() = delete;
  684. template <typename S, typename E>
  685. Task(S&& success, E&& error) :
  686. _success(std::forward<S>(success)),
  687. _error(std::forward<E>(error))
  688. {}
  689. void success(bss_info* info) {
  690. _success(info);
  691. }
  692. void error(wifi::ScanError error) {
  693. _error(error);
  694. }
  695. private:
  696. Success _success;
  697. Error _error;
  698. };
  699. using TaskPtr = std::unique_ptr<Task>;
  700. namespace internal {
  701. TaskPtr task;
  702. void stop() {
  703. task = nullptr;
  704. }
  705. // STATUS comes from c_types.h, and it seems this is the only place that uses it
  706. // instead of some ESP-specific type.
  707. void complete(void* result, STATUS status) {
  708. if (status) { // aka anything but OK / 0
  709. task->error(wifi::ScanError::System);
  710. stop();
  711. return;
  712. }
  713. size_t networks { 0ul };
  714. bss_info* head = reinterpret_cast<bss_info*>(result);
  715. for (bss_info* it = head; it; it = STAILQ_NEXT(it, next), ++networks) {
  716. task->success(it);
  717. }
  718. if (!networks) {
  719. task->error(wifi::ScanError::NoNetworks);
  720. }
  721. stop();
  722. }
  723. } // namespace internal
  724. bool start(Success&& success, Error&& error) {
  725. if (internal::task) {
  726. error(wifi::ScanError::AlreadyScanning);
  727. return false;
  728. }
  729. // Note that esp8266 callback only reports the resulting status and will (always?) timeout all by itself
  730. // Default values are an active scan with some unspecified channel times.
  731. // (zeroed out scan_config struct or simply nullptr)
  732. // For example, c/p config from the current esp32 Arduino Core wrapper which are close to the values mentioned here:
  733. // https://github.com/espressif/ESP8266_NONOS_SDK/issues/103#issuecomment-383440370
  734. // Which could be useful if scanning needs to be more aggressive or switched into PASSIVE scan type
  735. //scan_config config{};
  736. //config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
  737. //config.scan_time.active.min = 100;
  738. //config.scan_time.active.max = 300;
  739. if (wifi_station_scan(nullptr, &internal::complete)) {
  740. internal::task = std::make_unique<Task>(std::move(success), std::move(error));
  741. return true;
  742. }
  743. error(wifi::ScanError::System);
  744. return false;
  745. }
  746. // Alternative to the stock WiFi method, where we wait for the task to finish before returning
  747. bool wait(Success&& success, Error&& error) {
  748. auto result = start(std::move(success), std::move(error));
  749. while (internal::task) {
  750. delay(100);
  751. }
  752. return result;
  753. }
  754. // Another alternative to the stock WiFi method, return a shared Info list
  755. // Caller is expected to wait for the scan to complete before using the contents
  756. SsidInfosPtr ssidinfos() {
  757. auto infos = std::make_shared<wifi::SsidInfos>();
  758. start(
  759. [infos](bss_info* found) {
  760. wifi::SsidInfo pair(*found);
  761. infos->remove_if([&](const wifi::SsidInfo& current) {
  762. return (current.ssid() == pair.ssid()) && (current.info() < pair.info());
  763. });
  764. infos->emplace_front(std::move(pair));
  765. },
  766. [infos](wifi::ScanError) {
  767. infos->clear();
  768. });
  769. return infos;
  770. }
  771. } // namespace scan
  772. bool enabled() {
  773. return wifi::opmode() & wifi::OpmodeSta;
  774. }
  775. // XXX: WiFi.disconnect() also implicitly disables STA mode *and* erases the current STA config
  776. void disconnect() {
  777. if (enabled()) {
  778. wifi_station_disconnect();
  779. }
  780. }
  781. // Some workarounds for built-in WiFi management:
  782. // - don't *intentionally* perist current SSID & PASS even when persistance is disabled from the Arduino Core side.
  783. // while this seems like a good idea in theory, we end up with a bunch of async actions coming our way.
  784. // - station disconnect events are linked with the connection routine as well, single WiFi::begin() may trigger up to
  785. // 3 events (as observed with `WiFi::waitForConnectResult()`) before the connection loop stops further attempts
  786. // - explicit OPMODE changes to both notify the userspace when the change actually happens (alternative is SDK event, but it is SYS context),
  787. // since *all* STA & AP start-up methods will implicitly change the mode (`WiFi.begin()`, `WiFi.softAP()`, `WiFi.config()`)
  788. void enable() {
  789. if (WiFi.enableSTA(true)) {
  790. disconnect();
  791. ETS_UART_INTR_DISABLE();
  792. wifi_station_set_reconnect_policy(false);
  793. if (wifi_station_get_auto_connect()) {
  794. wifi_station_set_auto_connect(false);
  795. }
  796. ETS_UART_INTR_ENABLE();
  797. return;
  798. }
  799. // `std::abort()` calls are the to ensure the mode actually changes, but it should be extremely rare
  800. // it may be also wise to add these for when the mode is already the expected one,
  801. // since we should enforce mode changes to happen *only* through the configuration loop
  802. abort();
  803. }
  804. void disable() {
  805. if (!WiFi.enableSTA(false)) {
  806. abort();
  807. }
  808. }
  809. namespace connection {
  810. namespace internal {
  811. struct Task {
  812. using Iterator = wifi::Networks::iterator;
  813. Task() = delete;
  814. Task(const Task&) = delete;
  815. Task(Task&&) = delete;
  816. explicit Task(String&& hostname, Networks&& networks, int retries) :
  817. _hostname(std::move(hostname)),
  818. _networks(std::move(networks)),
  819. _begin(_networks.begin()),
  820. _end(_networks.end()),
  821. _current(_begin),
  822. _retries(retries),
  823. _retry(_retries)
  824. {}
  825. bool empty() const {
  826. return _networks.empty();
  827. }
  828. size_t count() const {
  829. return _networks.size();
  830. }
  831. bool done() const {
  832. return _current == _end;
  833. }
  834. bool next() {
  835. if (!done()) {
  836. if (_retry-- < 0) {
  837. _retry = _retries;
  838. _current = std::next(_current);
  839. }
  840. return !done();
  841. }
  842. return false;
  843. }
  844. // Sanity checks for SSID & PASSPHRASE lengths are performed by the WiFi.begin()
  845. // (or, failing connection, if we ever use raw SDK API)
  846. bool connect() const {
  847. if (!done() && wifi::sta::enabled()) {
  848. wifi::sta::disconnect();
  849. auto& network = *_current;
  850. if (!network.dhcp()) {
  851. auto& ipsettings = network.ipSettings();
  852. if (!WiFi.config(ipsettings.ip(), ipsettings.gateway(), ipsettings.netmask(), ipsettings.dns())) {
  853. return false;
  854. }
  855. }
  856. // Only the STA cares about the hostname setting
  857. // esp8266 specific Arduino-specific - this sets lwip internal structs related to the DHCPc
  858. WiFi.hostname(_hostname);
  859. if (network.channel()) {
  860. WiFi.begin(network.ssid(), network.passphrase(),
  861. network.channel(), network.bssid().data());
  862. } else {
  863. WiFi.begin(network.ssid(), network.passphrase());
  864. }
  865. return true;
  866. }
  867. return false;
  868. }
  869. Networks& networks() {
  870. return _networks;
  871. }
  872. void reset() {
  873. _begin = _networks.begin();
  874. _end = _networks.end();
  875. _current = _begin;
  876. _retry = _retries;
  877. }
  878. // Since after sort() the ssid<->info pairs will be in a proper order, look up the known network and move it to the front aka 'head'
  879. // Continue after shifting the 'head' element one element further, b/c we also a guaranteed that ssid<->info pairs are unique
  880. // Authmode comparison is pretty lenient, so only requirement is availability of the passphrase text.
  881. // Does not invalidate iterators, since the elements are swapped in-place, but we still need to reset to initial state.
  882. void sort(scan::SsidInfosPtr&& ptr) {
  883. auto& pairs = *ptr;
  884. pairs.sort();
  885. auto begin = _networks.begin();
  886. auto end = _networks.end();
  887. auto head = begin;
  888. for (auto& pair : pairs) {
  889. for (auto network = head; (head != end) && (network != end); ++network) {
  890. if (pair.ssid() != (*network).ssid()) {
  891. continue;
  892. }
  893. auto& info = pair.info();
  894. if ((*network).passphrase().length()
  895. && (info.authmode() == AUTH_OPEN)) {
  896. continue;
  897. }
  898. *network = wifi::Network(std::move(*network), info.bssid(), info.channel());
  899. if (network != head) {
  900. std::swap(*network, *head);
  901. }
  902. ++head;
  903. break;
  904. }
  905. }
  906. reset();
  907. }
  908. // Allow to remove the currently used network right from the scan routine
  909. // Only makes sense when wifi::Network's bssid exist, either after sort() or if loaded from settings
  910. bool filter(const wifi::Info& info) {
  911. _networks.remove_if([&](const wifi::Network& network) {
  912. return network.bssid() == info.bssid();
  913. });
  914. reset();
  915. return !done();
  916. }
  917. private:
  918. String _hostname;
  919. Networks _networks;
  920. Iterator _begin;
  921. Iterator _end;
  922. Iterator _current;
  923. const int _retries;
  924. int _retry;
  925. };
  926. station_status_t last { STATION_IDLE };
  927. bool connected { false };
  928. Ticker timer;
  929. bool persist { false };
  930. bool lock { false };
  931. using TaskPtr = std::unique_ptr<Task>;
  932. TaskPtr task;
  933. } // namespace internal
  934. bool locked() {
  935. return internal::lock;
  936. }
  937. void unlock() {
  938. internal::lock = false;
  939. }
  940. void lock() {
  941. internal::lock = true;
  942. }
  943. void persist(bool value) {
  944. internal::persist = value;
  945. }
  946. bool persist() {
  947. return internal::persist;
  948. }
  949. void stop() {
  950. if (!locked()) {
  951. internal::task.reset();
  952. internal::timer.detach();
  953. }
  954. }
  955. bool started() {
  956. return static_cast<bool>(internal::task);
  957. }
  958. void start(String&& hostname, Networks&& networks, int retries) {
  959. if (!locked()) {
  960. internal::task = std::make_unique<internal::Task>(
  961. std::move(hostname),
  962. std::move(networks),
  963. retries);
  964. internal::timer.detach();
  965. }
  966. }
  967. void schedule(decltype(millis()) ms, wifi::Action next) {
  968. internal::timer.once_ms(ms, [next]() {
  969. wifi::action(next);
  970. unlock();
  971. });
  972. lock();
  973. }
  974. void continued() {
  975. schedule(wifi::sta::ConnectionInterval, wifi::Action::StationContinueConnect);
  976. }
  977. void initial() {
  978. schedule(wifi::sta::ReconnectionInterval, wifi::Action::StationConnect);
  979. }
  980. bool next() {
  981. return internal::task->next();
  982. }
  983. bool connect() {
  984. return internal::task->connect();
  985. }
  986. bool filter(const wifi::Info& info) {
  987. return internal::task->filter(info);
  988. }
  989. void sort(scan::SsidInfosPtr&& infos) {
  990. internal::task->sort(std::move(infos));
  991. }
  992. station_status_t last() {
  993. return internal::last;
  994. }
  995. // Note that `wifi_station_get_connect_status()` only makes sence when something is setting `wifi_set_event_handler_cb(...)`
  996. // *and*, it should only be expected to work when STA is not yet connected. After a successful connection, we should track the network interface and / or SDK events.
  997. // Events are already enabled in the Arduino Core (and heavily wired through-out it, so we can't override b/c only one handler is allowed).
  998. bool wait() {
  999. internal::last = wifi_station_get_connect_status();
  1000. bool out { false };
  1001. switch (internal::last) {
  1002. case STATION_CONNECTING:
  1003. out = true;
  1004. break;
  1005. case STATION_GOT_IP:
  1006. internal::connected = true;
  1007. break;
  1008. case STATION_IDLE:
  1009. case STATION_NO_AP_FOUND:
  1010. case STATION_CONNECT_FAIL:
  1011. case STATION_WRONG_PASSWORD:
  1012. break;
  1013. }
  1014. return out;
  1015. }
  1016. // TODO(Core 2.7.4): `WiFi.isConnected()` is a simple `wifi_station_get_connect_status() == STATION_GOT_IP`,
  1017. // Meaning, it will never detect link up / down updates when AP silently kills the connection or something else unexpected happens.
  1018. // Running JustWiFi with autoconnect + reconnect enabled, it silently avoided the issue b/c the SDK reconnect routine disconnected the STA,
  1019. // causing our state machine to immediatly cancel it (since `WL_CONNECTED != WiFi.status()`) and then try to connect again using it's own loop.
  1020. // We could either (* is used currently):
  1021. // - (*) listen for the SDK event through the `WiFi.onStationModeDisconnected()`
  1022. // - ( ) poll NETIF_FLAG_LINK_UP for the lwip's netif, since the SDK will bring the link down on disconnection
  1023. // find the `interface` in the `netif_list`, where `interface->num == STATION_IF`
  1024. // - ( ) use lwip's netif event system from the recent Core, track UP and DOWN for a specific interface number
  1025. // this one is probably only used internally, thus should be treated as a private API
  1026. // - ( ) poll whether `wifi_get_ip_info(STATION_IF, &ip);` is set to something valid
  1027. // (tuple of ip, gw and mask)
  1028. // - ( ) poll `WiFi.localIP().isSet()`
  1029. // (will be unset when the link is down)
  1030. // placing status into a simple bool to avoid extracting ip info every time someone needs to check the connection
  1031. bool connected() {
  1032. return internal::connected;
  1033. }
  1034. bool connecting() {
  1035. return static_cast<bool>(internal::task);
  1036. }
  1037. bool lost() {
  1038. static bool last { internal::connected };
  1039. bool out { false };
  1040. if (internal::connected != last) {
  1041. last = internal::connected;
  1042. if (!last) {
  1043. if (persist() && !connecting()) {
  1044. schedule(wifi::sta::ConnectionInterval * wifi::sta::ConnectionRetries, wifi::Action::StationConnect);
  1045. }
  1046. out = true;
  1047. }
  1048. }
  1049. return out;
  1050. }
  1051. } // namespace connection
  1052. bool connected() {
  1053. return connection::connected();
  1054. }
  1055. bool connecting() {
  1056. return connection::connecting();
  1057. }
  1058. bool scanning() {
  1059. return static_cast<bool>(scan::internal::task);
  1060. }
  1061. // TODO: generic onEvent is deprecated on esp8266 in favour of the event-specific
  1062. // methods returning 'cancelation' token. Right now it is a basic shared_ptr with an std function inside of it.
  1063. // esp32 only has a generic onEvent, but event names are not compatible with the esp8266 version.
  1064. void init() {
  1065. static auto status = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& src) { // aka const auto&
  1066. connection::internal::connected = false;
  1067. });
  1068. disconnect();
  1069. disable();
  1070. }
  1071. void toggle() {
  1072. auto current = enabled();
  1073. connection::persist(!current);
  1074. wifi::action(current
  1075. ? wifi::Action::StationDisconnect
  1076. : wifi::Action::StationConnect);
  1077. }
  1078. namespace scan {
  1079. namespace periodic {
  1080. namespace internal {
  1081. constexpr int8_t Checks { wifi::build::scanRssiChecks() };
  1082. constexpr decltype(millis()) CheckInterval { wifi::build::scanRssiCheckInterval() };
  1083. int8_t threshold { wifi::build::scanRssiThreshold() };
  1084. int8_t counter { Checks };
  1085. Ticker timer;
  1086. void task() {
  1087. if (!wifi::sta::connected()) {
  1088. counter = Checks;
  1089. return;
  1090. }
  1091. auto rssi = wifi::sta::rssi();
  1092. if (rssi > threshold) {
  1093. counter = Checks;
  1094. } else if (rssi < threshold) {
  1095. if (counter < 0) {
  1096. return;
  1097. }
  1098. if (!--counter) {
  1099. wifi::action(wifi::Action::StationTryConnectBetter);
  1100. }
  1101. }
  1102. }
  1103. void start() {
  1104. counter = Checks;
  1105. timer.attach_ms(CheckInterval, task);
  1106. }
  1107. void stop() {
  1108. counter = Checks;
  1109. timer.detach();
  1110. }
  1111. } // namespace internal
  1112. void threshold(int8_t value) {
  1113. internal::threshold = value;
  1114. }
  1115. int8_t threshold() {
  1116. return internal::threshold;
  1117. }
  1118. void stop() {
  1119. internal::stop();
  1120. }
  1121. void start() {
  1122. internal::start();
  1123. }
  1124. bool check() {
  1125. if (internal::counter <= 0) {
  1126. internal::counter = internal::Checks;
  1127. return true;
  1128. }
  1129. return false;
  1130. }
  1131. bool enabled() {
  1132. return internal::timer.active();
  1133. }
  1134. } // namespace periodic
  1135. } // namespace scan
  1136. } // namespace sta
  1137. // -----------------------------------------------------------------------------
  1138. // ACCESS POINT
  1139. // -----------------------------------------------------------------------------
  1140. namespace ap {
  1141. static constexpr size_t LeasesMax { 4u };
  1142. namespace internal {
  1143. #if WIFI_AP_CAPTIVE_SUPPORT
  1144. bool captive { wifi::build::softApCaptive() };
  1145. DNSServer dns;
  1146. #endif
  1147. #if WIFI_AP_LEASES_SUPPORT
  1148. wifi::Macs leases;
  1149. #endif
  1150. } // namespace internal
  1151. #if WIFI_AP_CAPTIVE_SUPPORT
  1152. void captive(bool value) {
  1153. internal::captive = value;
  1154. }
  1155. bool captive() {
  1156. return internal::captive;
  1157. }
  1158. void dnsLoop() {
  1159. internal::dns.processNextRequest();
  1160. }
  1161. #endif
  1162. void enable() {
  1163. if (!WiFi.enableAP(true)) {
  1164. abort();
  1165. }
  1166. }
  1167. void disable() {
  1168. if (!WiFi.enableAP(false)) {
  1169. abort();
  1170. }
  1171. }
  1172. bool enabled() {
  1173. return wifi::opmode() & WIFI_AP;
  1174. }
  1175. void toggle() {
  1176. wifi::action(wifi::ap::enabled()
  1177. ? wifi::Action::AccessPointStop
  1178. : wifi::Action::AccessPointStart);
  1179. }
  1180. #if WIFI_AP_LEASES_SUPPORT
  1181. void setupLeases() {
  1182. for (auto& lease : internal::leases) {
  1183. wifi_softap_add_dhcps_lease(lease.data());
  1184. }
  1185. }
  1186. void clearLeases() {
  1187. internal::leases.clear();
  1188. }
  1189. template <typename T>
  1190. void lease(T&& mac) {
  1191. if (internal::leases.size() < LeasesMax) {
  1192. internal::leases.push_back(std::forward<T>(mac));
  1193. }
  1194. }
  1195. #endif
  1196. void stop() {
  1197. #if WIFI_AP_CAPTIVE_SUPPORT
  1198. internal::dns.stop();
  1199. #endif
  1200. WiFi.softAPdisconnect();
  1201. }
  1202. void start(String&& ssid, String&& passphrase, uint8_t channel) {
  1203. if (!enabled()) {
  1204. return;
  1205. }
  1206. if (!ssid.length()) {
  1207. disable();
  1208. return;
  1209. }
  1210. #if WIFI_AP_LEASES_SUPPORT
  1211. // Default amount of stations is 4, which we use here b/c softAp is called without arguments.
  1212. // When chaging the number below, update LeasesMax / use it as the 5th param
  1213. // (4th is `hidden` SSID)
  1214. setupLeases();
  1215. #endif
  1216. // TODO: softAP() implicitly enables AP mode
  1217. enable();
  1218. WiFi.softAP(ssid, passphrase, channel);
  1219. #if WIFI_AP_CAPTIVE_SUPPORT
  1220. if (internal::captive) {
  1221. internal::dns.setErrorReplyCode(DNSReplyCode::NoError);
  1222. internal::dns.start(53, "*", WiFi.softAPIP());
  1223. } else {
  1224. internal::dns.stop();
  1225. }
  1226. #endif
  1227. }
  1228. wifi::SoftApNetwork current() {
  1229. softap_config config{};
  1230. wifi_softap_get_config(&config);
  1231. wifi::Mac mac;
  1232. WiFi.softAPmacAddress(mac.data());
  1233. return {
  1234. mac,
  1235. convertSsid(config),
  1236. convertPassphrase(config),
  1237. config.channel,
  1238. config.authmode};
  1239. }
  1240. void init() {
  1241. disable();
  1242. }
  1243. uint8_t stations() {
  1244. return WiFi.softAPgetStationNum();
  1245. }
  1246. namespace fallback {
  1247. namespace internal {
  1248. bool enabled { false };
  1249. decltype(millis()) timeout { wifi::build::softApFallbackTimeout() };
  1250. Ticker timer;
  1251. } // namespace internal
  1252. void enable() {
  1253. internal::enabled = true;
  1254. }
  1255. void disable() {
  1256. internal::enabled = false;
  1257. }
  1258. bool enabled() {
  1259. return internal::enabled;
  1260. }
  1261. void remove() {
  1262. internal::timer.detach();
  1263. }
  1264. bool scheduled() {
  1265. return internal::timer.active();
  1266. }
  1267. void check();
  1268. void schedule() {
  1269. internal::timer.once_ms(internal::timeout, check);
  1270. }
  1271. void check() {
  1272. if (wifi::ap::enabled()
  1273. && wifi::sta::connected()
  1274. && !wifi::ap::stations())
  1275. {
  1276. remove();
  1277. wifi::action(wifi::Action::AccessPointStop);
  1278. return;
  1279. }
  1280. schedule();
  1281. }
  1282. } // namespace fallback
  1283. } // namespace ap
  1284. // -----------------------------------------------------------------------------
  1285. // SETTINGS
  1286. // -----------------------------------------------------------------------------
  1287. namespace settings {
  1288. wifi::Networks networks() {
  1289. wifi::Networks out;
  1290. for (size_t id = 0; id < wifi::build::NetworksMax; ++id) {
  1291. auto ssid = wifi::settings::staSsid(id);
  1292. if (!ssid.length()) {
  1293. break;
  1294. }
  1295. auto pass = wifi::settings::staPassphrase(id);
  1296. auto ip = staIp(id);
  1297. if (ip.isSet()) {
  1298. out.emplace_back(std::move(ssid), std::move(pass),
  1299. wifi::IpSettings{std::move(ip), staGateway(id), staMask(id), staDns(id)});
  1300. } else {
  1301. out.emplace_back(std::move(ssid), std::move(pass));
  1302. }
  1303. }
  1304. auto leftover = std::unique(out.begin(), out.end(), [](const wifi::Network& lhs, const wifi::Network& rhs) {
  1305. return lhs.ssid() == rhs.ssid();
  1306. });
  1307. out.erase(leftover, out.end());
  1308. return out;
  1309. }
  1310. void configure() {
  1311. auto ap_mode = wifi::settings::softApMode();
  1312. if (wifi::ApMode::Fallback == ap_mode) {
  1313. wifi::ap::fallback::enable();
  1314. } else {
  1315. wifi::ap::fallback::disable();
  1316. wifi::ap::fallback::remove();
  1317. wifi::action((ap_mode == wifi::ApMode::Enabled)
  1318. ? wifi::Action::AccessPointStart
  1319. : wifi::Action::AccessPointStop);
  1320. }
  1321. #if WIFI_AP_CAPTIVE_SUPPORT
  1322. wifi::ap::captive(wifi::settings::softApCaptive());
  1323. #endif
  1324. #if WIFI_AP_LEASES_SUPPORT
  1325. wifi::ap::clearLeases();
  1326. for (size_t index = 0; index < wifi::ap::LeasesMax; ++index) {
  1327. wifi::ap::lease(wifi::settings::softApLease(index));
  1328. }
  1329. #endif
  1330. auto sta_enabled = (wifi::StaMode::Enabled == wifi::settings::staMode());
  1331. wifi::sta::connection::persist(sta_enabled);
  1332. wifi::action(sta_enabled
  1333. ? wifi::Action::StationConnect
  1334. : wifi::Action::StationDisconnect);
  1335. wifi::sta::scan::periodic::threshold(wifi::settings::scanRssiThreshold());
  1336. #if WIFI_GRATUITOUS_ARP_SUPPORT
  1337. wifi::sta::garp::start(wifi::settings::garpInterval());
  1338. #endif
  1339. WiFi.setSleepMode(wifi::settings::sleep());
  1340. WiFi.setOutputPower(wifi::settings::txPower());
  1341. }
  1342. } // namespace settings
  1343. // -----------------------------------------------------------------------------
  1344. // TERMINAL
  1345. // -----------------------------------------------------------------------------
  1346. namespace terminal {
  1347. #if TERMINAL_SUPPORT
  1348. void init() {
  1349. terminalRegisterCommand(F("WIFI.STATIONS"), [](const ::terminal::CommandContext& ctx) {
  1350. size_t stations { 0ul };
  1351. for (auto* it = wifi_softap_get_station_info(); it; it = STAILQ_NEXT(it, next), ++stations) {
  1352. ctx.output.printf_P(PSTR("%s %s\n"),
  1353. wifi::debug::mac(convertBssid(*it)).c_str(),
  1354. wifi::debug::ip(it->ip).c_str());
  1355. }
  1356. wifi_softap_free_station_info();
  1357. if (!stations) {
  1358. terminalError(ctx, F("No stations connected"));
  1359. return;
  1360. }
  1361. terminalOK(ctx);
  1362. });
  1363. terminalRegisterCommand(F("NETWORK"), [](const ::terminal::CommandContext& ctx) {
  1364. for (auto& addr : addrList) {
  1365. ctx.output.printf_P(PSTR("%s%d %4s %6s "),
  1366. addr.ifname().c_str(),
  1367. addr.ifnumber(),
  1368. addr.ifUp() ? "up" : "down",
  1369. addr.isLocal() ? "local" : "global");
  1370. #if LWIP_IPV6
  1371. if (addr.isV4()) {
  1372. #endif
  1373. ctx.output.printf_P(PSTR("ip %s gateway %s mask %s\n"),
  1374. wifi::debug::ip(addr.ipv4()).c_str(),
  1375. wifi::debug::ip(addr.gw()).c_str(),
  1376. wifi::debug::ip(addr.netmask()).c_str());
  1377. #if LWIP_IPV6
  1378. } else {
  1379. // TODO: ip6_addr[...] array is included in the list
  1380. // we'll just see another entry
  1381. // TODO: routing info is not attached to the netif :/
  1382. // ref. nd6.h (and figure out what it does)
  1383. ctx.output.printf_P(PSTR("ip %s\n"),
  1384. wifi::debug::ip(netif->ip6_addr[i]).c_str());
  1385. }
  1386. #endif
  1387. }
  1388. for (int n = 0; n < DNS_MAX_SERVERS; ++n) {
  1389. auto ip = IPAddress(dns_getserver(n));
  1390. if (!ip.isSet()) {
  1391. break;
  1392. }
  1393. ctx.output.printf_P(PSTR("dns %s\n"), wifi::debug::ip(ip).c_str());
  1394. }
  1395. });
  1396. terminalRegisterCommand(F("WIFI"), [](const ::terminal::CommandContext& ctx) {
  1397. const auto mode = wifi::opmode();
  1398. ctx.output.printf_P(PSTR("OPMODE: %s\n"), wifi::debug::opmode(mode).c_str());
  1399. if (mode & OpmodeAp) {
  1400. auto current = wifi::ap::current();
  1401. ctx.output.printf_P(PSTR("SoftAP: bssid %s channel %hhu auth %s ssid \"%s\" passphrase \"%s\"\n"),
  1402. wifi::debug::mac(current.bssid).c_str(),
  1403. current.channel,
  1404. wifi::debug::authmode(current.authmode).c_str(),
  1405. current.ssid.c_str(),
  1406. current.passphrase.c_str());
  1407. }
  1408. if (mode & OpmodeSta) {
  1409. if (wifi::sta::connected()) {
  1410. station_config config{};
  1411. wifi_station_get_config(&config);
  1412. auto network = wifi::sta::current(config);
  1413. ctx.output.printf_P(PSTR("STA: bssid %s rssi %hhd channel %hhu ssid \"%s\"\n"),
  1414. wifi::debug::mac(network.bssid).c_str(),
  1415. network.rssi, network.channel, network.ssid.c_str());
  1416. } else {
  1417. ctx.output.println(F("STA: disconnected"));
  1418. }
  1419. }
  1420. terminalOK(ctx);
  1421. });
  1422. terminalRegisterCommand(F("WIFI.RESET"), [](const ::terminal::CommandContext& ctx) {
  1423. wifiDisconnect();
  1424. wifi::settings::configure();
  1425. terminalOK(ctx);
  1426. });
  1427. terminalRegisterCommand(F("WIFI.STA"), [](const ::terminal::CommandContext& ctx) {
  1428. wifi::sta::toggle();
  1429. terminalOK(ctx);
  1430. });
  1431. terminalRegisterCommand(F("WIFI.AP"), [](const ::terminal::CommandContext& ctx) {
  1432. wifi::ap::toggle();
  1433. terminalOK(ctx);
  1434. });
  1435. terminalRegisterCommand(F("WIFI.SCAN"), [](const ::terminal::CommandContext& ctx) {
  1436. wifi::sta::scan::wait(
  1437. [&](bss_info* info) {
  1438. ctx.output.printf_P(PSTR("BSSID: %s AUTH: %11s RSSI: %3hhd CH: %2hhu SSID: %s\n"),
  1439. wifi::debug::mac(convertBssid(*info)).c_str(),
  1440. wifi::debug::authmode(info->authmode).c_str(),
  1441. info->rssi,
  1442. info->channel,
  1443. convertSsid(*info).c_str()
  1444. );
  1445. },
  1446. [&](wifi::ScanError error) {
  1447. terminalError(ctx, wifi::debug::error(error));
  1448. }
  1449. );
  1450. });
  1451. }
  1452. } // namespace terminal
  1453. #endif
  1454. // -----------------------------------------------------------------------------
  1455. // WEB
  1456. // -----------------------------------------------------------------------------
  1457. namespace web {
  1458. #if WEB_SUPPORT
  1459. bool onKeyCheck(const char * key, JsonVariant& value) {
  1460. if (strncmp(key, "wifi", 4) == 0) return true;
  1461. if (strncmp(key, "ssid", 4) == 0) return true;
  1462. if (strncmp(key, "pass", 4) == 0) return true;
  1463. if (strncmp(key, "ip", 2) == 0) return true;
  1464. if (strncmp(key, "gw", 2) == 0) return true;
  1465. if (strncmp(key, "mask", 4) == 0) return true;
  1466. if (strncmp(key, "dns", 3) == 0) return true;
  1467. return false;
  1468. }
  1469. void onConnected(JsonObject& root) {
  1470. root["wifiScan"] = wifi::settings::scanNetworks();
  1471. JsonObject& wifi = root.createNestedObject("wifiConfig");
  1472. root["max"] = wifi::build::NetworksMax;
  1473. {
  1474. const char* schema_keys[] = {
  1475. "ssid",
  1476. "pass",
  1477. "ip",
  1478. "gw",
  1479. "mask",
  1480. "dns"
  1481. };
  1482. JsonArray& schema = wifi.createNestedArray("schema");
  1483. schema.copyFrom(schema_keys, sizeof(schema_keys) / sizeof(*schema_keys));
  1484. }
  1485. JsonArray& networks = wifi.createNestedArray("networks");
  1486. // TODO: send build flags as 'original' replacements?
  1487. // with the current model, removing network from the UI is
  1488. // equivalent to the factory reset and will silently use the build default
  1489. auto entries = wifi::settings::networks();
  1490. for (auto& entry : entries) {
  1491. JsonArray& network = networks.createNestedArray();
  1492. network.add(entry.ssid());
  1493. network.add(entry.passphrase());
  1494. auto& ipsettings = entry.ipSettings();
  1495. network.add(::settings::internal::serialize(ipsettings.ip()));
  1496. network.add(::settings::internal::serialize(ipsettings.gateway()));
  1497. network.add(::settings::internal::serialize(ipsettings.netmask()));
  1498. network.add(::settings::internal::serialize(ipsettings.dns()));
  1499. }
  1500. }
  1501. void onScan(uint32_t client_id) {
  1502. if (wifi::sta::scanning()) {
  1503. return;
  1504. }
  1505. wifi::sta::scan::start([client_id](bss_info* found) {
  1506. wifi::SsidInfo result(*found);
  1507. wsPost(client_id, [result](JsonObject& root) {
  1508. JsonArray& scan = root.createNestedArray("scanResult");
  1509. scan.add(result.ssid());
  1510. auto& info = result.info();
  1511. scan.add(info.rssi());
  1512. scan.add(info.authmode());
  1513. scan.add(info.channel());
  1514. scan.add(wifi::debug::mac(info.bssid()));
  1515. });
  1516. },
  1517. [client_id](wifi::ScanError error) {
  1518. wsPost(client_id, [error](JsonObject& root) {
  1519. root["scanError"] = wifi::debug::error(error);
  1520. });
  1521. });
  1522. }
  1523. void onAction(uint32_t client_id, const char* action, JsonObject&) {
  1524. if (strcmp(action, "scan") == 0) {
  1525. onScan(client_id);
  1526. }
  1527. }
  1528. #endif
  1529. } // namespace web
  1530. // -----------------------------------------------------------------------------
  1531. // INITIALIZATION
  1532. // -----------------------------------------------------------------------------
  1533. namespace debug {
  1534. String event(wifi::Event value) {
  1535. String out;
  1536. switch (value) {
  1537. case wifi::Event::Initial:
  1538. out = F("Initial");
  1539. break;
  1540. case wifi::Event::Mode: {
  1541. const auto mode = wifi::opmode();
  1542. out = F("Mode changed to ");
  1543. out += wifi::debug::opmode(mode);
  1544. break;
  1545. }
  1546. case wifi::Event::StationInit:
  1547. out = F("Station init");
  1548. break;
  1549. case wifi::Event::StationScan:
  1550. out = F("Scanning");
  1551. break;
  1552. case wifi::Event::StationConnecting:
  1553. out = F("Connecting");
  1554. break;
  1555. case wifi::Event::StationConnected: {
  1556. auto current = wifi::sta::current();
  1557. out += F("Connected to BSSID ");
  1558. out += wifi::debug::mac(current.bssid);
  1559. out += F(" SSID ");
  1560. out += current.ssid;
  1561. break;
  1562. }
  1563. case wifi::Event::StationTimeout:
  1564. out = F("Connection timeout");
  1565. break;
  1566. case wifi::Event::StationDisconnected: {
  1567. auto current = wifi::sta::current();
  1568. out += F("Disconnected from ");
  1569. out += current.ssid;
  1570. break;
  1571. }
  1572. case wifi::Event::StationReconnect:
  1573. out = F("Reconnecting");
  1574. break;
  1575. }
  1576. return out;
  1577. }
  1578. const char* state(wifi::State value) {
  1579. switch (value) {
  1580. case wifi::State::Boot:
  1581. return "Boot";
  1582. case wifi::State::Connect:
  1583. return "Connect";
  1584. case wifi::State::TryConnectBetter:
  1585. return "TryConnectBetter";
  1586. case wifi::State::Fallback:
  1587. return "Fallback";
  1588. case wifi::State::Connected:
  1589. return "Connected";
  1590. case wifi::State::Idle:
  1591. return "Idle";
  1592. case wifi::State::Init:
  1593. return "Init";
  1594. case wifi::State::Timeout:
  1595. return "Timeout";
  1596. case wifi::State::WaitScan:
  1597. return "WaitScan";
  1598. case wifi::State::WaitScanWithoutCurrent:
  1599. return "WaitScanWithoutCurrent";
  1600. case wifi::State::WaitConnected:
  1601. return "WaitConnected";
  1602. }
  1603. return "";
  1604. }
  1605. } // namespace debug
  1606. namespace internal {
  1607. // STA + AP FALLBACK:
  1608. // - try connection
  1609. // - if ok, stop existing AP
  1610. // - if not, keep / start AP
  1611. //
  1612. // STA:
  1613. // - try connection
  1614. // - don't do anything on completion
  1615. //
  1616. // TODO? WPS / SMARTCONFIG + STA + AP FALLBACK
  1617. // - same as above
  1618. // - when requested, make sure there are no active connections
  1619. // abort when sta connected or ap is connected
  1620. // - run autoconf, receive credentials and store in a free settings slot
  1621. // TODO: provide a clearer 'unroll' of the current state?
  1622. using EventCallbacks = std::forward_list<wifi::EventCallback>;
  1623. EventCallbacks callbacks;
  1624. void publish(wifi::Event event) {
  1625. for (auto& callback : callbacks) {
  1626. callback(event);
  1627. }
  1628. }
  1629. void subscribe(wifi::EventCallback callback) {
  1630. callbacks.push_front(callback);
  1631. }
  1632. namespace {
  1633. } // namespace
  1634. State handleAction(State& state, Action action) {
  1635. switch (action) {
  1636. case Action::StationConnect:
  1637. if (!wifi::sta::connecting() && !wifi::sta::connected()) {
  1638. if (!wifi::sta::enabled()) {
  1639. wifi::sta::enable();
  1640. publish(wifi::Event::Mode);
  1641. }
  1642. if (!wifi::sta::connecting()) {
  1643. state = State::Init;
  1644. }
  1645. }
  1646. break;
  1647. case Action::StationContinueConnect:
  1648. if (wifi::sta::connecting() && !wifi::sta::connection::locked()) {
  1649. state = State::Connect;
  1650. }
  1651. break;
  1652. case Action::StationDisconnect:
  1653. if (wifi::sta::connected()) {
  1654. wifi::ap::fallback::remove();
  1655. wifi::sta::disconnect();
  1656. }
  1657. if (wifi::sta::connecting()) {
  1658. wifi::sta::connection::unlock();
  1659. wifi::sta::connection::stop();
  1660. }
  1661. if (wifi::sta::enabled()) {
  1662. wifi::sta::disable();
  1663. publish(wifi::Event::Mode);
  1664. }
  1665. break;
  1666. case Action::StationTryConnectBetter:
  1667. if (!wifi::sta::connected() || wifi::sta::connecting()) {
  1668. wifi::sta::scan::periodic::stop();
  1669. break;
  1670. }
  1671. if (wifi::sta::scan::periodic::check()) {
  1672. state = State::TryConnectBetter;
  1673. }
  1674. break;
  1675. case Action::AccessPointFallback:
  1676. case Action::AccessPointStart:
  1677. if (!wifi::ap::enabled()) {
  1678. wifi::ap::enable();
  1679. wifi::ap::start(
  1680. wifi::settings::softApSsid(),
  1681. wifi::settings::softApPassphrase(),
  1682. wifi::settings::softApChannel());
  1683. if ((Action::AccessPointFallback == action)
  1684. && wifi::ap::fallback::enabled()) {
  1685. wifi::ap::fallback::schedule();
  1686. }
  1687. }
  1688. break;
  1689. case Action::AccessPointFallbackCheck:
  1690. if (wifi::ap::fallback::enabled()) {
  1691. wifi::ap::fallback::check();
  1692. }
  1693. break;
  1694. case Action::AccessPointStop:
  1695. if (wifi::ap::enabled()) {
  1696. wifi::ap::fallback::remove();
  1697. wifi::ap::stop();
  1698. wifi::ap::disable();
  1699. publish(wifi::Event::Mode);
  1700. }
  1701. break;
  1702. case Action::TurnOff:
  1703. if (wifi::enabled()) {
  1704. wifi::ap::fallback::remove();
  1705. wifi::ap::stop();
  1706. wifi::ap::disable();
  1707. wifi::sta::scan::periodic::stop();
  1708. wifi::sta::connection::stop();
  1709. wifi::sta::disconnect();
  1710. wifi::sta::disable();
  1711. wifi::disable();
  1712. if (!wifi::sleep()) {
  1713. wifi::action(wifi::Action::TurnOn);
  1714. break;
  1715. }
  1716. }
  1717. break;
  1718. case Action::TurnOn:
  1719. if (!wifi::enabled()) {
  1720. wifi::enable();
  1721. wifi::wakeup();
  1722. wifi::settings::configure();
  1723. }
  1724. break;
  1725. }
  1726. return state;
  1727. }
  1728. bool prepareConnection() {
  1729. if (wifi::sta::enabled()) {
  1730. auto networks = wifi::settings::networks();
  1731. if (networks.size()) {
  1732. wifi::sta::connection::start(
  1733. wifi::settings::hostname(), std::move(networks), wifi::sta::ConnectionRetries);
  1734. return true;
  1735. }
  1736. }
  1737. return false;
  1738. }
  1739. void loop() {
  1740. static decltype(wifi::sta::scan::ssidinfos()) infos;
  1741. static State state { State::Boot };
  1742. static State last_state { state };
  1743. if (last_state != state) {
  1744. DEBUG_MSG_P(PSTR("[WIFI] State %s -> %s\n"),
  1745. debug::state(last_state),
  1746. debug::state(state));
  1747. last_state = state;
  1748. }
  1749. switch (state) {
  1750. case State::Boot:
  1751. publish(wifi::Event::Initial);
  1752. state = State::Idle;
  1753. break;
  1754. case State::Init: {
  1755. if (!prepareConnection()) {
  1756. state = State::Fallback;
  1757. break;
  1758. }
  1759. wifi::sta::scan::periodic::stop();
  1760. if (wifi::settings::scanNetworks()) {
  1761. infos = wifi::sta::scan::ssidinfos();
  1762. state = State::WaitScan;
  1763. break;
  1764. }
  1765. state = State::Connect;
  1766. break;
  1767. }
  1768. case State::TryConnectBetter:
  1769. if (wifi::settings::scanNetworks() && prepareConnection()) {
  1770. wifi::sta::scan::periodic::stop();
  1771. infos = wifi::sta::scan::ssidinfos();
  1772. state = State::WaitScanWithoutCurrent;
  1773. break;
  1774. }
  1775. state = State::Idle;
  1776. break;
  1777. case State::Fallback:
  1778. publish(wifi::Event::StationReconnect);
  1779. wifi::sta::connection::initial();
  1780. wifi::action(wifi::Action::AccessPointFallback);
  1781. state = State::Idle;
  1782. break;
  1783. case State::WaitScan:
  1784. if (wifi::sta::scanning()) {
  1785. break;
  1786. }
  1787. wifi::sta::connection::sort(std::move(infos));
  1788. state = State::Connect;
  1789. break;
  1790. case State::WaitScanWithoutCurrent:
  1791. if (wifi::sta::scanning()) {
  1792. break;
  1793. }
  1794. wifi::sta::connection::sort(std::move(infos));
  1795. if (wifi::sta::connection::filter(wifi::sta::info())) {
  1796. wifi::sta::disconnect();
  1797. state = State::Connect;
  1798. break;
  1799. }
  1800. state = State::Idle;
  1801. break;
  1802. case State::Connect: {
  1803. if (wifi::sta::connection::connect()) {
  1804. state = State::WaitConnected;
  1805. publish(wifi::Event::StationConnecting);
  1806. } else {
  1807. state = State::Timeout;
  1808. }
  1809. break;
  1810. }
  1811. case State::WaitConnected:
  1812. if (wifi::sta::connection::wait()) {
  1813. break;
  1814. }
  1815. if (wifi::sta::connected()) {
  1816. state = State::Connected;
  1817. break;
  1818. }
  1819. state = State::Timeout;
  1820. break;
  1821. // Current logic closely follows the SDK connection routine with reconnect enabled,
  1822. // and will retry the same network multiple times before giving up.
  1823. case State::Timeout:
  1824. wifi::sta::connection::unlock();
  1825. if (wifi::sta::connecting() && wifi::sta::connection::next()) {
  1826. wifi::sta::connection::continued();
  1827. state = State::Idle;
  1828. publish(wifi::Event::StationTimeout);
  1829. } else {
  1830. wifi::sta::connection::stop();
  1831. state = State::Fallback;
  1832. }
  1833. break;
  1834. case State::Connected:
  1835. infos.reset();
  1836. wifi::sta::connection::unlock();
  1837. wifi::sta::connection::stop();
  1838. if (wifi::settings::scanNetworks()) {
  1839. wifi::sta::scan::periodic::start();
  1840. }
  1841. state = State::Idle;
  1842. publish(wifi::Event::StationConnected);
  1843. break;
  1844. case State::Idle: {
  1845. auto& actions = wifi::actions();
  1846. if (!actions.empty()) {
  1847. state = handleAction(state, actions.front());
  1848. actions.pop();
  1849. }
  1850. break;
  1851. }
  1852. }
  1853. // SDK disconnection event is specific to the phy layer. i.e. it will happen all the same
  1854. // when trying to connect and being unable to find the AP, being forced out by the AP with bad credentials
  1855. // or being disconnected when the wireless signal is lost.
  1856. // Thus, provide a specific connected -> disconnected event specific to the IP network availability.
  1857. if (wifi::sta::connection::lost()) {
  1858. publish(wifi::Event::StationDisconnected);
  1859. }
  1860. #if WIFI_AP_CAPTIVE_SUPPORT
  1861. // Captive portal only queues packets and those need to be processed asap
  1862. if (wifi::ap::enabled() && wifi::ap::captive()) {
  1863. wifi::ap::dnsLoop();
  1864. }
  1865. #endif
  1866. #if WIFI_GRATUITOUS_ARP_SUPPORT
  1867. // ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546
  1868. // Periodically send out ARP, even if no one asked
  1869. if (wifi::sta::connected() && !wifi::sta::garp::wait()) {
  1870. wifi::sta::garp::send();
  1871. }
  1872. #endif
  1873. }
  1874. // XXX: With Arduino Core 3.0.0, WiFi is asleep on boot
  1875. // It will wake up when calling WiFi::mode(...):
  1876. // - WiFi.begin(...)
  1877. // - WiFi.softAP(...)
  1878. // - WiFi.enableSTA(...)
  1879. // - WiFi.enableAP(...)
  1880. // ref. https://github.com/esp8266/Arduino/pull/7902
  1881. void init() {
  1882. WiFi.persistent(false);
  1883. wifi::ap::init();
  1884. wifi::sta::init();
  1885. }
  1886. } // namespace internal
  1887. } // namespace wifi
  1888. // -----------------------------------------------------------------------------
  1889. // API
  1890. // -----------------------------------------------------------------------------
  1891. void wifiRegister(wifi::EventCallback callback) {
  1892. wifi::internal::subscribe(callback);
  1893. }
  1894. bool wifiConnectable() {
  1895. return wifi::ap::enabled();
  1896. }
  1897. bool wifiConnected() {
  1898. return wifi::sta::connected();
  1899. }
  1900. IPAddress wifiStaIp() {
  1901. if (wifi::opmode() & wifi::OpmodeSta) {
  1902. return WiFi.localIP();
  1903. }
  1904. return {};
  1905. }
  1906. String wifiStaSsid() {
  1907. if (wifi::opmode() & wifi::OpmodeSta) {
  1908. auto current = wifi::sta::current();
  1909. return current.ssid;
  1910. }
  1911. return emptyString;
  1912. }
  1913. void wifiDisconnect() {
  1914. wifi::sta::disconnect();
  1915. }
  1916. void wifiToggleAp() {
  1917. wifi::ap::toggle();
  1918. }
  1919. void wifiToggleSta() {
  1920. wifi::sta::toggle();
  1921. }
  1922. void wifiStartAp() {
  1923. wifi::action(wifi::Action::AccessPointStart);
  1924. }
  1925. void wifiTurnOff() {
  1926. wifi::action(wifi::Action::TurnOff);
  1927. }
  1928. void wifiTurnOn() {
  1929. wifi::action(wifi::Action::TurnOn);
  1930. }
  1931. void wifiApCheck() {
  1932. wifi::action(wifi::Action::AccessPointFallbackCheck);
  1933. }
  1934. void wifiSetup() {
  1935. wifi::internal::init();
  1936. wifi::settings::migrate(migrateVersion());
  1937. wifi::settings::configure();
  1938. #if SYSTEM_CHECK_ENABLED
  1939. if (!systemCheck()) {
  1940. wifi::actions() = wifi::ActionsQueue{};
  1941. wifi::action(wifi::Action::AccessPointStart);
  1942. }
  1943. #endif
  1944. #if DEBUG_SUPPORT
  1945. wifiRegister([](wifi::Event event) {
  1946. DEBUG_MSG_P(PSTR("[WIFI] %s\n"), wifi::debug::event(event).c_str());
  1947. });
  1948. #endif
  1949. #if WEB_SUPPORT
  1950. wsRegister()
  1951. .onAction(wifi::web::onAction)
  1952. .onConnected(wifi::web::onConnected)
  1953. .onKeyCheck(wifi::web::onKeyCheck);
  1954. #endif
  1955. #if TERMINAL_SUPPORT
  1956. wifi::terminal::init();
  1957. #endif
  1958. espurnaRegisterLoop(wifi::internal::loop);
  1959. espurnaRegisterReload(wifi::settings::configure);
  1960. }