Sygaldry
Loading...
Searching...
No Matches
sygbe-wifi.hpp
1/*
2Copyright 2022 Edu Meneses https://www.edumeneses.com, Metalab - Société des
3Arts Technologiques (SAT), Input Devices and Music Interaction Laboratory
4(IDMIL), McGill University
5
6Copyright 2023 Travis J. West, https://traviswest.ca, Input Devices and Music
7Interaction Laboratory (IDMIL), Centre for Interdisciplinary Research in Music
8Media and Technology (CIRMMT), McGill University, Montréal, Canada, and Univ.
9Lille, Inria, CNRS, Centrale Lille, UMR 9189 CRIStAL, F-59000 Lille, France
10
11SPDX-License-Identifier: MIT
12*/
13
14#pragma once
15#include <charconv>
16#include <cstring>
17#include <freertos/FreeRTOS.h>
18#include <freertos/task.h>
19#include <freertos/event_groups.h>
20#include <esp_err.h>
21#include <esp_wifi.h>
22#include <nvs_flash.h>
23#include <sygah-metadata.hpp>
24#include <sygah-endpoints.hpp>
25#include <sygup-cstdio_logger.hpp>
26
27namespace sygaldry { namespace sygbe {
32
33struct WiFi
34: name_<"WiFi Manager">
35, author_<"Edu Meneses (2022) and Travis J. West (2023)">
36, copyright_<"Copyright 2023 Travis J. West">
37, license_<"SPDX-License-Identifier: MIT">
38, version_<"0.0.0">
39, description_<"An ESP-IDF WiFi API wrapper originally adapted from the Puara Module Manager">
40{
41 struct inputs_t {
42 text< "hostname"
43 , "Name of this device on the network."
44 "Must be less than 31 bytes long."
45 "Requires reboot to take effect."
47 > hostname;
48 text< "access point SSID"
49 , "Name of the device-hosted network."
50 "Must be less than 31 bytes long."
51 "Requires reboot to take effect."
53 > ap_ssid;
54 text< "access point password"
55 , "Password of the device-hosted network."
56 "Must be greater than 8 and less than 63 bytes long."
57 "Requires reboot to take effect."
59 > ap_password;
60 text< "WiFi SSID"
61 , "Name of the WiFi network to connect to."
62 "Must be less than 31 bytes long."
63 "Requires reboot to take effect."
65 > wifi_ssid;
66 text< "WiFi password"
67 , "Password of the WiFi network to connect to."
68 "Must be greater than 8 and less than 63 bytes long."
69 "Requires reboot to take effect."
71 > wifi_password;
72 toggle< "enable access point"
73 , "Indicate whether to persistently enable the device-hosted network."
74 "When this toggle is disabled, the access point is only enabled if"
75 "the device fails to connect to WiFi in station mode."
76 , 0
78 > enable_ap;
79 } inputs;
80
81 struct outputs_t {
82 toggle<"WiFi connected", "Indicates when WiFi is successfully connected"> wifi_connected;
83 toggle<"AP running", "Indicates when the device-hosted network is running"> ap_running;
84 text<"WiFi MAC", "MAC address of the device as a WiFi station on the main network"> wifi_mac;
85 text<"AP MAC", "MAC address of the device as a WiFi access point on the device-hosted network"> ap_mac;
86 text<"IP address", "IP address of the device as a WiFi station on the main network."
87 "Use this address to send network messages to the device when WiFi is connected."> ip_address;
88 } outputs;
89
91 EventGroupHandle_t event_group;
92 char connection_attempts;
93 sygup::CstdioLogger* log;
94 static constexpr int connected_bit = BIT0;
95 static constexpr int fail_bit = BIT1;
96 static constexpr int maximum_connection_attempts = 2;
97 };
98
99 void set_wifi_mode(wifi_mode_t mode)
100 {
101 log.print("wifi: setting WiFi mode to ");
102 switch(mode)
103 {
104 case WIFI_MODE_STA: log.println("station"); break;
105 case WIFI_MODE_AP: log.println("access point"); break;
106 case WIFI_MODE_APSTA: log.println("access point / station"); break;
107 default: log.println("unsupported mode..?"); break;
108 }
109
110 ESP_ERROR_CHECK(esp_wifi_set_mode(mode));
111
112 if (mode == WIFI_MODE_STA || mode == WIFI_MODE_APSTA)
113 {
114 wifi_config_t sta_config{};
115 std::memcpy(sta_config.sta.ssid, inputs.wifi_ssid.value.c_str(), inputs.wifi_ssid.value.length()+1);
116 std::memcpy(sta_config.sta.password, inputs.wifi_password.value.c_str(), inputs.wifi_password.value.length()+1);
117 log.println("wifi: Enabling station");
118 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
119 }
120
121 if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA)
122 {
123 wifi_config_t ap_config{};
124 std::memcpy(ap_config.ap.ssid, inputs.ap_ssid.value.c_str(), inputs.ap_ssid.value.length()+1);
125 std::memcpy(ap_config.ap.password, inputs.ap_password.value.c_str(), inputs.ap_password.value.length()+1);
126 ap_config.ap.ssid_len = inputs.ap_ssid.value.length();
127 ap_config.ap.channel = 5;
128 ap_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
129 ap_config.ap.ssid_hidden = 0;
130 ap_config.ap.max_connection = 5;
131 // TODO: add channel, authmode, hidden, and max connections as inputs
132 log.println("wifi: Enabling access point");
133 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
134 }
135 }
136
137 [[no_unique_address]] sygup::CstdioLogger log;
138
139 void init()
140 {
141 if (inputs.hostname.value.empty() || inputs.hostname.value.length() > 31)
142 {
143 inputs.hostname = "sygaldry_instrument";
144 log.println("wifi warning: initialized hostname................ '", inputs.hostname.value, "'");
145 }
146
147 if (inputs.ap_ssid.value.empty() || inputs.ap_ssid.value.length() > 31)
148 {
149 inputs.ap_ssid = "sygaldry_admin";
150 log.println("wifi warning: initialized access point SSID....... '", inputs.ap_ssid.value, "'");
151 }
152
153 if ( inputs.ap_password.value.empty() || inputs.ap_password.value.length() < 8 || inputs.ap_password.value.length() > 63)
154 {
155 inputs.ap_password = "sygaldry_admin";
156 log.println("wifi warning: initialized access point password... '", inputs.ap_password.value, "'");
157 }
158
159 // TODO: just don't bother trying to connect to wifi in this case
160 if ( inputs.wifi_ssid.value.empty() || inputs.wifi_ssid.value.length() > 31)
161 {
162 inputs.wifi_ssid = "sygaldry_wifi";
163 log.println("wifi warning: initialized WiFi SSID............... '", inputs.wifi_ssid.value, "'");
164 }
165
166 if ( inputs.wifi_password.value.empty() || inputs.wifi_password.value.length() < 8 || inputs.wifi_password.value.length() > 63)
167 {
168 inputs.wifi_password = "sygaldry_admin";
169 log.println("wifi warning: initialized WiFi password........... '", inputs.wifi_password.value, "'");
170 }
171 esp_err_t ret = nvs_flash_init();
172 if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
173 ESP_ERROR_CHECK(nvs_flash_erase());
174 ret = nvs_flash_init();
175 }
176 ESP_ERROR_CHECK(ret);
177 log.println("wifi: Initialized NVS");
178
179 ESP_ERROR_CHECK(esp_netif_init());
180 log.println("wifi: Initialized network interface");
181
182 ESP_ERROR_CHECK(esp_event_loop_create_default());
183 log.println("wifi: Created default event loop");
184
185 // TODO: do we need to store these handles?
186 esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
187 esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
188 ESP_ERROR_CHECK(esp_netif_set_hostname( sta_netif
189 , inputs.hostname.value.c_str()
190 )
191 );
192 ESP_ERROR_CHECK(esp_netif_set_hostname( ap_netif
193 , inputs.hostname.value.c_str()
194 )
195 );
196 log.println("wifi: Set hostnames");
197
198 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
199 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
200 log.println("wifi: Initialized WiFi with default configuration");
201 if (inputs.enable_ap) set_wifi_mode(WIFI_MODE_APSTA);
202 else set_wifi_mode(WIFI_MODE_STA);
203 auto sta_event_handler = +[](void * arg, esp_event_base_t event_base, long int event_id, void * event_data)
204 {
205 handler_state_t& handler_state = *(handler_state_t*)arg;
206 auto& log = *handler_state.log;
207 if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
208 {
209 log.println("wifi: WiFi station started. Connecting to network...");
210 esp_wifi_connect();
211 }
212 else if (event_base == WIFI_EVENT &&
213 event_id == WIFI_EVENT_STA_DISCONNECTED)
214 {
215 log.print("wifi: Station disconnected. ");
216 if (handler_state.connection_attempts < handler_state.maximum_connection_attempts)
217 {
218 log.println("Attempting to reconnect...");
219 esp_wifi_connect();
220 handler_state.connection_attempts++;
221 } else {
222 log.println("Connection failed.");
223 xEventGroupSetBits(handler_state.event_group, handler_state.fail_bit);
224 }
225 } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
226 log.println("Connection succeeded.");
227 handler_state.connection_attempts = 0;
228 xEventGroupSetBits(handler_state.event_group, handler_state.connected_bit);
229 }
230 };
231 handler_state_t handler_state{};
232 handler_state.event_group = xEventGroupCreate();
233 handler_state.connection_attempts = 0;
234 handler_state.log = &log;
235
236 esp_event_handler_instance_t instance_any_id;
237 esp_event_handler_instance_t instance_got_ip;
238 ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
239 ESP_EVENT_ANY_ID,
240 sta_event_handler,
241 (void*)&handler_state,
242 &instance_any_id)
243 );
244 ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
245 IP_EVENT_STA_GOT_IP,
246 sta_event_handler,
247 (void*)&handler_state,
248 &instance_got_ip)
249 );
250 log.println("wifi: Registered WiFi station event handler");
251
252 log.println("wifi: Starting WiFi...");
253 ESP_ERROR_CHECK(esp_wifi_start());
254
255 EventBits_t bits = xEventGroupWaitBits( handler_state.event_group
256 , handler_state.connected_bit | handler_state.fail_bit
257 , pdFALSE, pdFALSE, portMAX_DELAY
258 );
259 log.println("wifi: Finished waiting...");
260 if (bits & handler_state.connected_bit)
261 {
262 outputs.wifi_connected = 1;
263 outputs.ap_running = inputs.enable_ap;
264 }
265 else // fail_bit; TODO: shouldn't we only enable AP if it isn't already?
266 {
267 set_wifi_mode(WIFI_MODE_AP);
268 outputs.ap_running = 1;
269 outputs.wifi_connected = 0;
270 }
271 //ESP_ERROR_CHECK(esp_event_handler_instance_unregister( IP_EVENT
272 // , IP_EVENT_STA_GOT_IP
273 // , instance_got_ip
274 // )
275 // );
276 //ESP_ERROR_CHECK(esp_event_handler_instance_unregister( WIFI_EVENT
277 // , ESP_EVENT_ANY_ID
278 // , instance_any_id
279 // )
280 // );
281 //vEventGroupDelete(handler_state.event_group);
282 //log.println("Cleaned up event handler.");
283 // TODO: clean up or hide this ugly mess that converts mac and ip addresses to nicely formatted strings
284 char mac_string[18] = {0};
285 unsigned char mac[6] = {0};
286 char * ptr;
287 auto mac_to_string = [&]()
288 {
289 ptr = mac_string;
290 for (int i = 0; i < 6; ++i)
291 {
292 auto [one_past_written, ec] = std::to_chars(ptr, ptr+2, mac[i], 16);
293 if (one_past_written != ptr+2) // if to_chars only wrote one character
294 {
295 ptr[1] = ptr[0]; // copy written character to second position
296 ptr[0] = '0'; // pad first position with 0
297 }
298 ptr = ptr+2;
299 *ptr++ = ':';
300 }
301 *--ptr = 0;
302 };
303
304 esp_wifi_get_mac(WIFI_IF_STA, mac);
305 mac_to_string();
306 outputs.wifi_mac.value = mac_string;
307
308 esp_wifi_get_mac(WIFI_IF_AP, mac);
309 mac_to_string();
310 outputs.ap_mac.value = mac_string;
311
312 char ip_string[16];
313 esp_netif_ip_info_t ip_info;
314 esp_netif_get_ip_info(sta_netif, &ip_info);
315 ptr = ip_string;
316 auto [a,b] = std::to_chars(ptr, ptr+3, esp_ip4_addr1_16(&ip_info.ip), 10);
317 ptr = a;
318 *ptr++ = '.';
319 auto [c,d] = std::to_chars(ptr, ptr+3, esp_ip4_addr2_16(&ip_info.ip), 10);
320 ptr = c;
321 *ptr++ = '.';
322 auto [e,f] = std::to_chars(ptr, ptr+3, esp_ip4_addr3_16(&ip_info.ip), 10);
323 ptr = e;
324 *ptr++ = '.';
325 auto [g,h] = std::to_chars(ptr, ptr+3, esp_ip4_addr4_16(&ip_info.ip), 10);
326 ptr = g;
327 *ptr = 0;
328 outputs.ip_address = ip_string;
329 }
330
331 void main() { return; }
332};
333
336} }
Document the author of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:39
Document the copyright statement of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:47
Document a textual description of an entity, e.g. an endpoint, component or binding.
Definition sygah-metadata.hpp:35
Document the copyright license of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:45
Document the name of an entity, e.g. an endpoint, component, or binding.
Definition sygah-metadata.hpp:33
Definition sygbe-wifi.hpp:90
Definition sygbe-wifi.hpp:41
Definition sygbe-wifi.hpp:81
Definition sygbe-wifi.hpp:40
Session data tag helper.
Definition sygah-endpoints.hpp:252
Write only tag helper.
Definition sygah-endpoints.hpp:245
A text string endpoint with persistent value semantics.
Definition sygah-endpoints.hpp:304
A two-state integer endpoint with persistent value semantics.
Definition sygah-endpoints.hpp:289
Document a textual description of the version number of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:51