S&B Volcano vaporizer remote control with Pi Pico W
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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * wifi.c
  3. *
  4. * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * See <http://www.gnu.org/licenses/>.
  17. */
  18. #include "pico/cyw43_arch.h"
  19. #include "lwip/netif.h"
  20. #include "lwip/ip4_addr.h"
  21. #include "dhcpserver.h"
  22. #include "config.h"
  23. #include "log.h"
  24. #include "mem.h"
  25. #include "usb_descriptors.h"
  26. #include "wifi.h"
  27. #define CONNECT_TIMEOUT_MS (8UL * 1000UL)
  28. #define SCAN_AP_TIMEOUT_MS (20UL * 1000UL)
  29. #define WIFI_AP_SSID_PREFIX "Volcano-"
  30. #define WIFI_AP_SSID_LEN 4
  31. #define WIFI_AP_PASS_LEN 8
  32. static_assert((WIFI_AP_SSID_LEN + WIFI_AP_PASS_LEN) <= (2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES),
  33. "SSID and Password parts for AP need to fit Pico serial number");
  34. enum wifi_state {
  35. WS_IDLE = 0,
  36. WS_SCAN,
  37. WS_CONNECT,
  38. WS_WAIT_FOR_IP,
  39. WS_READY,
  40. };
  41. static enum wifi_state state = WS_IDLE;
  42. static uint32_t start_scan_time = 0;
  43. static uint32_t start_connect_time = 0;
  44. static uint32_t start_ip_time = 0;
  45. static dhcp_server_t dhcp_server;
  46. static bool enabled_ap = false;
  47. static char curr_ssid[WIFI_MAX_NAME_LEN + 1] = {0};
  48. static char curr_pass[WIFI_MAX_PASS_LEN + 1] = {0};
  49. static void wifi_ap(void) {
  50. cyw43_thread_enter();
  51. // last N chars of serial for ssid and password
  52. const size_t prefix_len = strlen(WIFI_AP_SSID_PREFIX);
  53. char wifi_ssid[prefix_len + WIFI_AP_SSID_LEN + 1];
  54. memcpy(wifi_ssid, WIFI_AP_SSID_PREFIX, prefix_len);
  55. memcpy(wifi_ssid + prefix_len,
  56. string_pico_serial + (2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES) - WIFI_AP_SSID_LEN,
  57. WIFI_AP_SSID_LEN);
  58. wifi_ssid[prefix_len + WIFI_AP_SSID_LEN] = '\0';
  59. strncpy(curr_ssid, wifi_ssid, WIFI_MAX_NAME_LEN);
  60. char wifi_pass[WIFI_AP_PASS_LEN + 1] = {0};
  61. memcpy(wifi_pass,
  62. string_pico_serial + (2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES) - WIFI_AP_SSID_LEN - WIFI_AP_PASS_LEN,
  63. WIFI_AP_PASS_LEN);
  64. strncpy(curr_pass, wifi_pass, WIFI_MAX_PASS_LEN);
  65. debug("disable sta");
  66. cyw43_arch_disable_sta_mode();
  67. debug("enable ap '%s' '%s'", wifi_ssid, wifi_pass);
  68. cyw43_arch_enable_ap_mode(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK);
  69. ip4_addr_t gw, mask;
  70. IP4_ADDR(&gw, 192, 168, 4, 1);
  71. IP4_ADDR(&mask, 255, 255, 255, 0);
  72. debug("enable dhcp");
  73. dhcp_server_init(&dhcp_server, &gw, &mask);
  74. state = WS_READY;
  75. enabled_ap = true;
  76. cyw43_thread_exit();
  77. }
  78. static void wifi_connect(const char *ssid, const char *pw, uint32_t auth) {
  79. cyw43_thread_enter();
  80. debug("connecting to '%s'", ssid);
  81. strncpy(curr_ssid, ssid, WIFI_MAX_NAME_LEN);
  82. strncpy(curr_pass, pw, WIFI_MAX_PASS_LEN);
  83. // https://github.com/raspberrypi/pico-sdk/issues/1413
  84. uint32_t a = 0;
  85. if (auth & 4) {
  86. a = CYW43_AUTH_WPA2_AES_PSK;
  87. } else if (auth & 2) {
  88. a = CYW43_AUTH_WPA_TKIP_PSK;
  89. }
  90. int r = cyw43_arch_wifi_connect_async(ssid, pw, a);
  91. if (r != 0) {
  92. debug("failed to connect %d", r);
  93. state = WS_SCAN;
  94. } else {
  95. start_connect_time = to_ms_since_boot(get_absolute_time());
  96. state = WS_CONNECT;
  97. }
  98. cyw43_thread_exit();
  99. }
  100. static int scan_result(void *env, const cyw43_ev_scan_result_t *result) {
  101. (void)env;
  102. cyw43_thread_enter();
  103. if (result && (state == WS_SCAN)) {
  104. for (int i = 0; i < mem_data()->net_count; i++) {
  105. if ((strlen(mem_data()->net[i].name) == result->ssid_len)
  106. && (memcmp(mem_data()->net[i].name, result->ssid, result->ssid_len) == 0)) {
  107. wifi_connect(mem_data()->net[i].name,
  108. mem_data()->net[i].pass,
  109. result->auth_mode);
  110. break;
  111. }
  112. }
  113. }
  114. cyw43_thread_exit();
  115. return 0;
  116. }
  117. static void wifi_scan(void) {
  118. debug("starting scan");
  119. state = WS_SCAN;
  120. cyw43_wifi_scan_options_t scan_options = {0};
  121. int err = cyw43_wifi_scan(&cyw43_state, &scan_options, NULL, scan_result);
  122. if (err != 0) {
  123. debug("error %d", err);
  124. }
  125. }
  126. void wifi_init(void) {
  127. if (state != WS_IDLE) {
  128. debug("invalid state %d", state);
  129. return;
  130. }
  131. cyw43_thread_enter();
  132. cyw43_arch_enable_sta_mode();
  133. wifi_scan();
  134. start_scan_time = to_ms_since_boot(get_absolute_time());
  135. cyw43_thread_exit();
  136. }
  137. void wifi_deinit(void) {
  138. cyw43_thread_enter();
  139. cyw43_arch_disable_sta_mode();
  140. cyw43_arch_disable_ap_mode();
  141. state = WS_IDLE;
  142. if (enabled_ap) {
  143. enabled_ap = false;
  144. dhcp_server_deinit(&dhcp_server);
  145. }
  146. cyw43_thread_exit();
  147. }
  148. bool wifi_initialized(void) {
  149. return (state != WS_IDLE);
  150. }
  151. bool wifi_ready(void) {
  152. return (state == WS_READY);
  153. }
  154. const char *wifi_state(void) {
  155. switch (state) {
  156. case WS_IDLE:
  157. return "Disabled";
  158. case WS_SCAN:
  159. return "Scanning";
  160. case WS_CONNECT:
  161. return "Connecting";
  162. case WS_WAIT_FOR_IP:
  163. return "Waiting for IP";
  164. case WS_READY: {
  165. uint32_t now = to_ms_since_boot(get_absolute_time()) % (enabled_ap ? 9000 : 6000);
  166. if (now < 3000) {
  167. // show SSID
  168. return curr_ssid;
  169. } else if (now < 6000) {
  170. // show IP
  171. cyw43_arch_lwip_begin();
  172. const ip4_addr_t *ip = netif_ip4_addr(netif_default);
  173. cyw43_arch_lwip_end();
  174. return ip4addr_ntoa(ip);
  175. } else {
  176. // show Pass (only AP)
  177. return curr_pass;
  178. }
  179. }
  180. }
  181. return NULL;
  182. }
  183. void wifi_run(void) {
  184. cyw43_thread_enter();
  185. if (state == WS_SCAN) {
  186. if (!cyw43_wifi_scan_active(&cyw43_state)) {
  187. debug("restarting scan");
  188. wifi_scan();
  189. }
  190. uint32_t now = to_ms_since_boot(get_absolute_time());
  191. if ((now - start_scan_time) >= SCAN_AP_TIMEOUT_MS) {
  192. debug("wifi sta timeout. opening ap.");
  193. wifi_ap();
  194. }
  195. } else if (state == WS_CONNECT) {
  196. int link = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA);
  197. static int prev_link = 0xFF;
  198. if (prev_link != link) {
  199. prev_link = link;
  200. debug("net link status: %d", link);
  201. }
  202. if (link == CYW43_LINK_JOIN) {
  203. debug("joined network");
  204. start_ip_time = to_ms_since_boot(get_absolute_time());
  205. state = WS_WAIT_FOR_IP;
  206. } else if (link < CYW43_LINK_DOWN) {
  207. debug("net connection failed. retry.");
  208. wifi_scan();
  209. }
  210. uint32_t now = to_ms_since_boot(get_absolute_time());
  211. if ((now - start_connect_time) >= CONNECT_TIMEOUT_MS) {
  212. debug("net connection timeout. retry.");
  213. wifi_scan();
  214. }
  215. } else if (state == WS_WAIT_FOR_IP) {
  216. cyw43_arch_lwip_begin();
  217. const ip4_addr_t *ip = netif_ip4_addr(netif_default);
  218. cyw43_arch_lwip_end();
  219. if (ip4_addr_get_u32(ip) != 0) {
  220. state = WS_READY;
  221. debug("got IP '%s'", ip4addr_ntoa(ip));
  222. }
  223. uint32_t now = to_ms_since_boot(get_absolute_time());
  224. if ((now - start_ip_time) >= CONNECT_TIMEOUT_MS) {
  225. debug("net dhcp timeout. retry.");
  226. //cyw43_arch_lwip_begin();
  227. //dhcp_renew(netif_default);
  228. //cyw43_arch_lwip_end();
  229. // DHCP renew does not seem to help, only a full reconnect
  230. wifi_scan();
  231. }
  232. }
  233. cyw43_thread_exit();
  234. }