1+ /* !
2+ * @file ws_networking_pico_v2.h
3+ *
4+ * This is a driver for using the Raspberry Pi Pico (RP2040)
5+ * network interface with Adafruit IO Wippersnapper.
6+ *
7+ * Adafruit invests time and resources providing this open source code,
8+ * please support Adafruit and open-source hardware by purchasing
9+ * products from Adafruit!
10+ *
11+ * Copyright (c) Brent Rubell 2023 for Adafruit Industries.
12+ *
13+ * MIT license, all text here must be included in any redistribution.
14+ *
15+ */
16+
17+ #ifndef WS_NETWORKING_PICO_V2_H
18+ #define WS_NETWORKING_PICO_V2_H
19+
20+ #ifdef ARDUINO_ARCH_RP2040
21+
22+ #define PICO_CONNECT_TIMEOUT_MS 20000 /* !< Connection timeout (in ms) */
23+ #define PICO_CONNECT_RETRY_DELAY_MS 200 /* !< delay time between retries. */
24+
25+ #include " Wippersnapper_V2.h"
26+
27+ #include " Adafruit_MQTT.h"
28+ #include " Adafruit_MQTT_Client.h"
29+ #include " Arduino.h"
30+ #include < WiFiClient.h>
31+ #include < WiFiClientSecure.h>
32+ extern Wippersnapper_V2 WsV2;
33+
34+ /* ***************************************************************************/
35+ /* !
36+ @brief Class for using the Raspberry Pi Pico network interface.
37+ */
38+ /* ***************************************************************************/
39+ class ws_networking_pico_v2 : public Wippersnapper_V2 {
40+
41+ public:
42+ /* *************************************************************************/
43+ /* !
44+ @brief Initializes the WipperSnapper class for RPi Pico.
45+ */
46+ /* *************************************************************************/
47+ ws_networking_pico_v2 () : Wippersnapper_V2() {
48+ _ssid = 0 ;
49+ _pass = 0 ;
50+ }
51+
52+ /* *************************************************************************/
53+ /* !
54+ @brief Destructor
55+ */
56+ /* *************************************************************************/
57+ ~ws_networking_pico_v2 () {
58+ if (_mqtt_client_secure)
59+ delete _mqtt_client_secure;
60+ if (_mqtt_client_secure)
61+ delete _mqtt_client_secure;
62+ }
63+
64+ /* *******************************************************/
65+ /* !
66+ @brief Sets the WiFi client's ssid and password.
67+ @param ssid
68+ WiFi network's SSID.
69+ @param ssidPassword
70+ WiFi network's password.
71+ */
72+ /* *******************************************************/
73+ void set_ssid_pass (const char *ssid, const char *ssidPassword) {
74+ _ssid = ssid;
75+
76+ // set the AP password
77+ // check if ssidPassword was "" in secrets.json
78+ if ((ssidPassword != NULL ) && (strlen (ssidPassword) == 0 )) {
79+ _pass = NULL ; // Set as NULL for open networks
80+ } else {
81+ _pass = ssidPassword;
82+ }
83+ }
84+
85+ /* *********************************************************/
86+ /* !
87+ @brief Sets the WiFi client's ssid and password.
88+ */
89+ /* *********************************************************/
90+ void set_ssid_pass () {
91+ _ssid = WsV2._configV2 .network .ssid ;
92+ _pass = WsV2._configV2 .network .pass ;
93+ }
94+
95+ /* **********************************************************/
96+ /* !
97+ @brief Performs a scan of local WiFi networks.
98+ @returns True if `_network_ssid` is found, False otherwise.
99+ */
100+ /* **********************************************************/
101+ bool check_valid_ssid () {
102+ // Set WiFi to station mode and disconnect from an AP if it was previously
103+ // connected
104+ WiFi.mode (WIFI_STA);
105+ WiFi.disconnect ();
106+ delay (100 );
107+
108+ // Perform a network scan
109+ int n = WiFi.scanNetworks ();
110+ if (n == 0 ) {
111+ WS_DEBUG_PRINTLN (" ERROR: No WiFi networks found!" );
112+ return false ;
113+ }
114+
115+ // Was the network within secrets.json found?
116+ for (int i = 0 ; i < n; ++i) {
117+ if (strcmp (_ssid, WiFi.SSID (i)) == 0 ) {
118+ WS_DEBUG_PRINT (" SSID (" );
119+ WS_DEBUG_PRINT (_ssid);
120+ WS_DEBUG_PRINT (" ) found! RSSI: " );
121+ WS_DEBUG_PRINTLN (WiFi.RSSI (i));
122+ return true ;
123+ }
124+ if (WsV2._isWiFiMultiV2 ) {
125+ // multi network mode
126+ for (int j = 0 ; j < WS_MAX_ALT_WIFI_NETWORKS; j++) {
127+ if (strcmp (WsV2._multiNetworksV2 [j].ssid , WiFi.SSID (i)) == 0 ) {
128+ WS_DEBUG_PRINT (" SSID (" );
129+ WS_DEBUG_PRINT (WsV2._multiNetworksV2 [j].ssid );
130+ WS_DEBUG_PRINT (" ) found! RSSI: " );
131+ WS_DEBUG_PRINTLN (WiFi.RSSI (i));
132+ return true ;
133+ }
134+ }
135+ }
136+ }
137+
138+ // User-set network not found, print scan results to serial console
139+ WS_DEBUG_PRINTLN (" ERROR: Your requested WiFi network was not found!" );
140+ WS_DEBUG_PRINTLN (" WipperSnapper found these WiFi networks: " );
141+ for (int i = 0 ; i < n; ++i) {
142+ WS_DEBUG_PRINT (WiFi.SSID (i));
143+ WS_DEBUG_PRINT (" " );
144+ WS_DEBUG_PRINT (WiFi.RSSI (i));
145+ WS_DEBUG_PRINTLN (" dB" );
146+ }
147+
148+ return false ;
149+ }
150+
151+ /* *******************************************************/
152+ /* !
153+ @brief Sets the RPi Pico's unique client identifier
154+ @note On RPi Pico, the UID is the MAC address.
155+ */
156+ /* *******************************************************/
157+ void getMacAddr () {
158+ uint8_t mac[6 ] = {0 };
159+ WiFi.macAddress (mac);
160+ memcpy (WsV2._macAddrV2 , mac, sizeof (mac));
161+ }
162+
163+ /* *******************************************************/
164+ /* !
165+ @brief Gets the current network RSSI value
166+ @return int32_t RSSI value
167+ */
168+ /* *******************************************************/
169+ int32_t getRSSI () { return WiFi.RSSI (); }
170+
171+ /* *******************************************************/
172+ /* !
173+ @brief Initializes the MQTT client
174+ @param clientID
175+ MQTT client identifier
176+ */
177+ /* *******************************************************/
178+ void setupMQTTClient (const char *clientID) {
179+ if (strcmp (WsV2._configV2 .aio_url , " io.adafruit.com" ) == 0 ||
180+ strcmp (WsV2._configV2 .aio_url , " io.adafruit.us" ) == 0 ) {
181+ _mqtt_client_secure = new WiFiClientSecure ();
182+ _mqtt_client_secure->setCACert (
183+ strcmp (WsV2._configV2 .aio_url , " io.adafruit.com" ) == 0
184+ ? _aio_root_ca_prod
185+ : _aio_root_ca_staging);
186+ WsV2._mqttV2 = new Adafruit_MQTT_Client (
187+ _mqtt_client_secure, WsV2._configV2 .aio_url , WsV2._configV2 .io_port , clientID,
188+ WsV2._configV2 .aio_user , WsV2._configV2 .aio_key );
189+ } else {
190+ _mqtt_client_insecure = new WiFiClient ();
191+ WsV2._mqttV2 = new Adafruit_MQTT_Client (
192+ _mqtt_client_insecure, WsV2._configV2 .aio_url , WsV2._configV2 .io_port ,
193+ clientID, WsV2._configV2 .aio_user , WsV2._configV2 .aio_key );
194+ }
195+ }
196+
197+ /* *******************************************************/
198+ /* !
199+ @brief Returns the network status of an RPi Pico.
200+ @return ws_status_t
201+ */
202+ /* *******************************************************/
203+ ws_status_t networkStatus () {
204+ switch (WiFi.status ()) {
205+ case WL_CONNECTED:
206+ return WS_NET_CONNECTED;
207+ case WL_CONNECT_FAILED:
208+ return WS_NET_CONNECT_FAILED;
209+ case WL_IDLE_STATUS:
210+ return WS_IDLE;
211+ default :
212+ return WS_NET_DISCONNECTED;
213+ }
214+ }
215+
216+ /* ******************************************************************/
217+ /* !
218+ @brief Returns the type of network connection used by Wippersnapper
219+ @return Pico
220+ */
221+ /* ******************************************************************/
222+ const char *connectionType () { return " Pico" ; }
223+
224+ protected:
225+ const char *_ssid; // /< WiFi SSID
226+ const char *_pass; // /< WiFi password
227+ WiFiClient
228+ *_mqtt_client_insecure; // /< Pointer to an insecure WiFi client object
229+ WiFiClientSecure
230+ *_mqtt_client_secure; // /< Pointer to a secure WiFi client object
231+ WiFiMulti _wifiMulti; // /< WiFiMulti object for multi-network mode
232+
233+ const char *_aio_root_ca_staging =
234+ " -----BEGIN CERTIFICATE-----\n "
235+ " MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw\n "
236+ " TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n "
237+ " cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\n "
238+ " WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n "
239+ " RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G\n "
240+ " h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV\n "
241+ " 6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw\n "
242+ " gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\n "
243+ " ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj\n "
244+ " v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB\n "
245+ " AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g\n "
246+ " BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu\n "
247+ " Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc\n "
248+ " MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL\n "
249+ " pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp\n "
250+ " eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH\n "
251+ " pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7\n "
252+ " s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu\n "
253+ " h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv\n "
254+ " YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8\n "
255+ " ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0\n "
256+ " LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+\n "
257+ " EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY\n "
258+ " Ig46v9mFmBvyH04=\n "
259+ " -----END CERTIFICATE-----\n " ; // /< Root certificate for io.adafruit.us
260+
261+ const char *_aio_root_ca_prod =
262+ " -----BEGIN CERTIFICATE-----\n "
263+ " MIIEjTCCA3WgAwIBAgIQDQd4KhM/xvmlcpbhMf/ReTANBgkqhkiG9w0BAQsFADBh\n "
264+ " MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n "
265+ " d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n "
266+ " MjAeFw0xNzExMDIxMjIzMzdaFw0yNzExMDIxMjIzMzdaMGAxCzAJBgNVBAYTAlVT\n "
267+ " MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n "
268+ " b20xHzAdBgNVBAMTFkdlb1RydXN0IFRMUyBSU0EgQ0EgRzEwggEiMA0GCSqGSIb3\n "
269+ " DQEBAQUAA4IBDwAwggEKAoIBAQC+F+jsvikKy/65LWEx/TMkCDIuWegh1Ngwvm4Q\n "
270+ " yISgP7oU5d79eoySG3vOhC3w/3jEMuipoH1fBtp7m0tTpsYbAhch4XA7rfuD6whU\n "
271+ " gajeErLVxoiWMPkC/DnUvbgi74BJmdBiuGHQSd7LwsuXpTEGG9fYXcbTVN5SATYq\n "
272+ " DfbexbYxTMwVJWoVb6lrBEgM3gBBqiiAiy800xu1Nq07JdCIQkBsNpFtZbIZhsDS\n "
273+ " fzlGWP4wEmBQ3O67c+ZXkFr2DcrXBEtHam80Gp2SNhou2U5U7UesDL/xgLK6/0d7\n "
274+ " 6TnEVMSUVJkZ8VeZr+IUIlvoLrtjLbqugb0T3OYXW+CQU0kBAgMBAAGjggFAMIIB\n "
275+ " PDAdBgNVHQ4EFgQUlE/UXYvkpOKmgP792PkA76O+AlcwHwYDVR0jBBgwFoAUTiJU\n "
276+ " IBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG\n "
277+ " AQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMDQGCCsGAQUFBwEB\n "
278+ " BCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGA1Ud\n "
279+ " HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEds\n "
280+ " b2JhbFJvb3RHMi5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEW\n "
281+ " HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDQYJKoZIhvcNAQELBQADggEB\n "
282+ " AIIcBDqC6cWpyGUSXAjjAcYwsK4iiGF7KweG97i1RJz1kwZhRoo6orU1JtBYnjzB\n "
283+ " c4+/sXmnHJk3mlPyL1xuIAt9sMeC7+vreRIF5wFBC0MCN5sbHwhNN1JzKbifNeP5\n "
284+ " ozpZdQFmkCo+neBiKR6HqIA+LMTMCMMuv2khGGuPHmtDze4GmEGZtYLyF8EQpa5Y\n "
285+ " jPuV6k2Cr/N3XxFpT3hRpt/3usU/Zb9wfKPtWpoznZ4/44c1p9rzFcZYrWkj3A+7\n "
286+ " TNBJE0GmP2fhXhP1D/XVfIW/h0yCJGEiV9Glm/uGOa3DXHlmbAcxSyCRraG+ZBkA\n "
287+ " 7h4SeM6Y8l/7MBRpPCz6l8Y=\n "
288+ " -----END CERTIFICATE-----\n " ; // /< Root certificate for io.adafruit.com
289+
290+ /* *************************************************************************/
291+ /* !
292+ @brief Establishes a connection with the wireless network.
293+ */
294+ /* *************************************************************************/
295+ void _connect () {
296+
297+ if (WiFi.status () == WL_CONNECTED)
298+ return ;
299+
300+ WiFi.mode (WIFI_STA);
301+ WsV2.feedWDTV2 ();
302+ WiFi.setTimeout (20000 );
303+ WsV2.feedWDTV2 ();
304+
305+ if (strlen (_ssid) == 0 ) {
306+ _statusV2 = WS_SSID_INVALID;
307+ } else {
308+ _disconnect ();
309+ delay (5000 );
310+ WsV2.feedWDTV2 ();
311+ if (WsV2._isWiFiMultiV2 ) {
312+ // multi network mode
313+ _wifiMulti.clearAPList ();
314+ // add default network
315+ _wifiMulti.addAP (_ssid, _pass);
316+ // add array of alternative networks
317+ for (int i = 0 ; i < WS_MAX_ALT_WIFI_NETWORKS; i++) {
318+ _wifiMulti.addAP (WsV2._multiNetworksV2 [i].ssid ,
319+ WsV2._multiNetworksV2 [i].pass );
320+ }
321+ WsV2.feedWDTV2 ();
322+ if (_wifiMulti.run (10000 ) == WL_CONNECTED) {
323+ WsV2.feedWDTV2 ();
324+ _statusV2 = WS_NET_CONNECTED;
325+ return ;
326+ }
327+ WsV2.feedWDTV2 ();
328+ } else {
329+ WiFi.begin (_ssid, _pass);
330+
331+ // Use the macro to retry the status check until connected / timed out
332+ int lastResult;
333+ /* RETRY_FUNCTION_UNTIL_TIMEOUT(
334+ []() -> int { return WiFi.status(); }, // Function call each cycle
335+ int, // return type
336+ lastResult, // return variable (unused here)
337+ [](int status) { return status == WL_CONNECTED; }, // check
338+ PICO_CONNECT_TIMEOUT_MS, // timeout interval (ms)
339+ PICO_CONNECT_RETRY_DELAY_MS); // interval between retries */
340+
341+ if (lastResult == WL_CONNECTED) {
342+ _statusV2 = WS_NET_CONNECTED;
343+ // wait 2seconds for connection to stabilize
344+ // WS_DELAY_WITH_WDT(2000);
345+ return ;
346+ }
347+ }
348+ _statusV2 = WS_NET_DISCONNECTED;
349+ }
350+ }
351+
352+ /* *************************************************************************/
353+ /* !
354+ @brief Disconnects from the wireless network.
355+ */
356+ /* *************************************************************************/
357+ void _disconnect () {
358+ WsV2.feedWDTV2 ();
359+ WiFi.disconnect ();
360+ delay (5000 );
361+ WsV2.feedWDTV2 ();
362+ }
363+ };
364+
365+ #endif // ARDUINO_ARCH_RP2040
366+ #endif // WS_NETWORKING_PICO_V2_H
0 commit comments