@@ -98,6 +98,7 @@ typedef struct {
98
98
static wifi_radio_error_t wifi_status = WIFI_RADIO_ERROR_NONE ;
99
99
100
100
static mdns_server_obj_t mdns ;
101
+ static uint32_t web_api_port = 80 ;
101
102
102
103
static socketpool_socketpool_obj_t pool ;
103
104
static socketpool_socket_obj_t listening ;
@@ -189,6 +190,9 @@ void supervisor_web_workflow_status(void) {
189
190
}
190
191
191
192
mp_printf (& mp_plat_print , "%s" , _our_ip_encoded );
193
+ if (web_api_port != 80 ) {
194
+ mp_printf (& mp_plat_print , ":%d" , web_api_port );
195
+ }
192
196
// TODO: Use these unicode to show signal strength: ▂▄▆█
193
197
}
194
198
} else {
@@ -199,11 +203,6 @@ void supervisor_web_workflow_status(void) {
199
203
void supervisor_start_web_workflow (void ) {
200
204
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI
201
205
202
- if (common_hal_wifi_radio_get_enabled (& common_hal_wifi_radio_obj ) &&
203
- wifi_radio_get_ipv4_address (& common_hal_wifi_radio_obj ) != 0 ) {
204
- // Already started.
205
- return ;
206
- }
207
206
208
207
char ssid [33 ];
209
208
char password [64 ];
@@ -218,15 +217,20 @@ void supervisor_start_web_workflow(void) {
218
217
password_len <= 0 || (size_t )password_len >= sizeof (password )) {
219
218
return ;
220
219
}
221
- common_hal_wifi_init (false);
222
- common_hal_wifi_radio_set_enabled (& common_hal_wifi_radio_obj , true);
220
+ if (!common_hal_wifi_radio_get_enabled (& common_hal_wifi_radio_obj )) {
221
+ common_hal_wifi_init (false);
222
+ common_hal_wifi_radio_set_enabled (& common_hal_wifi_radio_obj , true);
223
+ }
223
224
224
225
// TODO: Do our own scan so that we can find the channel we want before calling connect.
225
226
// Otherwise, connect will do a full slow scan to pick the best AP.
226
227
227
228
// NUL terminate the strings because dotenv doesn't.
228
229
ssid [ssid_len ] = '\0' ;
229
230
password [password_len ] = '\0' ;
231
+ // We can all connect again because it will return early if we're already connected to the
232
+ // network. If we are connected to a different network, then it will disconnect before
233
+ // attempting to connect to the given network.
230
234
wifi_status = common_hal_wifi_radio_connect (
231
235
& common_hal_wifi_radio_obj , (uint8_t * )ssid , ssid_len , (uint8_t * )password , password_len ,
232
236
0 , 0.1 , NULL , 0 );
@@ -236,21 +240,47 @@ void supervisor_start_web_workflow(void) {
236
240
return ;
237
241
}
238
242
239
- mdns_server_construct (& mdns , true);
240
- common_hal_mdns_server_set_instance_name (& mdns , MICROPY_HW_BOARD_NAME );
241
- common_hal_mdns_server_advertise_service (& mdns , "_circuitpython" , "_tcp" , 80 );
243
+ char port_encoded [6 ];
244
+ size_t port_len = 0 ;
245
+ size_t new_port = web_api_port ;
246
+ #if CIRCUITPY_DOTENV
247
+ port_len = dotenv_get_key ("/.env" , "CIRCUITPY_WEB_API_PORT" , port_encoded , sizeof (port_encoded ) - 1 );
248
+ #endif
249
+ if (0 < port_len && port_len < sizeof (port_encoded )) {
250
+ port_encoded [port_len ] = '\0' ;
251
+ new_port = strtoul (port_encoded , NULL , 10 );
252
+ }
253
+
254
+ bool first_start = mdns .base .type != & mdns_server_type ;
255
+ bool port_changed = new_port != web_api_port ;
242
256
243
- pool .base .type = & socketpool_socketpool_type ;
244
- common_hal_socketpool_socketpool_construct (& pool , & common_hal_wifi_radio_obj );
257
+ if (first_start ) {
258
+ ESP_LOGI (TAG , "Starting web workflow" );
259
+ mdns_server_construct (& mdns , true);
260
+ mdns .base .type = & mdns_server_type ;
261
+ common_hal_mdns_server_set_instance_name (& mdns , MICROPY_HW_BOARD_NAME );
262
+ pool .base .type = & socketpool_socketpool_type ;
263
+ common_hal_socketpool_socketpool_construct (& pool , & common_hal_wifi_radio_obj );
245
264
246
- ESP_LOGI (TAG , "Starting web workflow" );
247
- listening .base .type = & socketpool_socket_type ;
248
- socketpool_socket (& pool , SOCKETPOOL_AF_INET , SOCKETPOOL_SOCK_STREAM , & listening );
249
- common_hal_socketpool_socket_settimeout (& listening , 0 );
250
- // Bind to any ip.
251
- // TODO: Make this port .env configurable.
252
- common_hal_socketpool_socket_bind (& listening , "" , 0 , 80 );
253
- common_hal_socketpool_socket_listen (& listening , 1 );
265
+ listening .base .type = & socketpool_socket_type ;
266
+ active .base .type = & socketpool_socket_type ;
267
+ active .num = -1 ;
268
+ active .connected = false;
269
+
270
+ websocket_init ();
271
+ }
272
+ if (port_changed ) {
273
+ common_hal_socketpool_socket_close (& listening );
274
+ }
275
+ if (first_start || port_changed ) {
276
+ web_api_port = new_port ;
277
+ common_hal_mdns_server_advertise_service (& mdns , "_circuitpython" , "_tcp" , web_api_port );
278
+ socketpool_socket (& pool , SOCKETPOOL_AF_INET , SOCKETPOOL_SOCK_STREAM , & listening );
279
+ common_hal_socketpool_socket_settimeout (& listening , 0 );
280
+ // Bind to any ip.
281
+ common_hal_socketpool_socket_bind (& listening , "" , 0 , web_api_port );
282
+ common_hal_socketpool_socket_listen (& listening , 1 );
283
+ }
254
284
255
285
mp_int_t api_password_len = dotenv_get_key ("/.env" , "CIRCUITPY_WEB_API_PASSWORD" , _api_password + 1 , sizeof (_api_password ) - 2 );
256
286
if (api_password_len > 0 ) {
@@ -259,12 +289,6 @@ void supervisor_start_web_workflow(void) {
259
289
_base64_in_place (_api_password , api_password_len + 1 , sizeof (_api_password ));
260
290
}
261
291
262
- active .base .type = & socketpool_socket_type ;
263
- active .num = -1 ;
264
- active .connected = false;
265
-
266
- websocket_init ();
267
-
268
292
// TODO:
269
293
// GET /cp/serial.txt
270
294
// - Most recent 1k of serial output.
@@ -283,6 +307,10 @@ static void _send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int l
283
307
}
284
308
}
285
309
310
+ STATIC void _print_raw (void * env , const char * str , size_t len ) {
311
+ _send_raw ((socketpool_socket_obj_t * )env , (const uint8_t * )str , (size_t )len );
312
+ }
313
+
286
314
static void _send_str (socketpool_socket_obj_t * socket , const char * str ) {
287
315
_send_raw (socket , (const uint8_t * )str , strlen (str ));
288
316
}
@@ -301,14 +329,19 @@ static void _send_strs(socketpool_socket_obj_t *socket, ...) {
301
329
}
302
330
303
331
static void _send_chunk (socketpool_socket_obj_t * socket , const char * chunk ) {
304
- char encoded_len [sizeof (size_t ) * 2 + 1 ];
305
- int len = snprintf (encoded_len , sizeof (encoded_len ), "%X" , strlen (chunk ));
306
- _send_raw (socket , (const uint8_t * )encoded_len , len );
307
- _send_raw (socket , (const uint8_t * )"\r\n" , 2 );
332
+ mp_print_t _socket_print = {socket , _print_raw };
333
+ mp_printf (& _socket_print , "%X\r\n" , strlen (chunk ));
308
334
_send_raw (socket , (const uint8_t * )chunk , strlen (chunk ));
309
335
_send_raw (socket , (const uint8_t * )"\r\n" , 2 );
310
336
}
311
337
338
+ STATIC void _print_chunk (void * env , const char * str , size_t len ) {
339
+ mp_print_t _socket_print = {env , _print_raw };
340
+ mp_printf (& _socket_print , "%X\r\n" , len );
341
+ _send_raw ((socketpool_socket_obj_t * )env , (const uint8_t * )str , len );
342
+ _send_raw ((socketpool_socket_obj_t * )env , (const uint8_t * )"\r\n" , 2 );
343
+ }
344
+
312
345
// A bit of a misnomer because it sends all arguments as one chunk.
313
346
// The last argument must be NULL! Otherwise, it won't stop.
314
347
static void _send_chunks (socketpool_socket_obj_t * socket , ...) {
@@ -326,9 +359,9 @@ static void _send_chunks(socketpool_socket_obj_t *socket, ...) {
326
359
}
327
360
va_end (strs_to_count );
328
361
329
- char encoded_len [ sizeof ( size_t ) * 2 + 1 ];
330
- snprintf ( encoded_len , sizeof ( encoded_len ), "%X" , chunk_len ) ;
331
- _send_strs ( socket , encoded_len , " \r\n" , NULL );
362
+
363
+ mp_print_t _socket_print = { socket , _print_raw } ;
364
+ mp_printf ( & _socket_print , "%X \r\n" , chunk_len );
332
365
333
366
str = va_arg (strs_to_send , const char * );
334
367
while (str != NULL ) {
@@ -531,7 +564,12 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request,
531
564
_send_str (socket , "http" );
532
565
}
533
566
534
- _send_strs (socket , "://" , hostname , ".local" , path , "\r\n" , NULL );
567
+ _send_strs (socket , "://" , hostname , ".local" , NULL );
568
+ if (web_api_port != 80 ) {
569
+ mp_print_t _socket_print = {socket , _print_raw };
570
+ mp_printf (& _socket_print , ":%d" , web_api_port );
571
+ }
572
+ _send_strs (socket , path , "\r\n" , NULL );
535
573
_cors_header (socket , request );
536
574
_send_str (socket , "\r\n" );
537
575
}
@@ -540,6 +578,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
540
578
socketpool_socket_send (socket , (const uint8_t * )OK_JSON , strlen (OK_JSON ));
541
579
_cors_header (socket , request );
542
580
_send_str (socket , "\r\n" );
581
+ mp_print_t _socket_print = {socket , _print_chunk };
543
582
_send_chunk (socket , "[" );
544
583
bool first = true;
545
584
@@ -560,7 +599,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
560
599
}
561
600
// We use nanoseconds past Jan 1, 1970 for consistency with BLE API and
562
601
// LittleFS.
563
- _send_chunk (socket , ", \"modified_ns\": " );
602
+ _send_chunk (socket , ", " );
564
603
565
604
uint64_t truncated_time = timeutils_mktime (1980 + (file_info .fdate >> 9 ),
566
605
(file_info .fdate >> 5 ) & 0xf ,
@@ -569,15 +608,17 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
569
608
(file_info .ftime >> 5 ) & 0x1f ,
570
609
(file_info .ftime & 0x1f ) * 2 ) * 1000000000ULL ;
571
610
572
- char encoded_number [32 ];
573
- snprintf (encoded_number , sizeof (encoded_number ), "%lld" , truncated_time );
574
- _send_chunks (socket , encoded_number , ", \"file_size\": " , NULL );
611
+ // Use snprintf because mp_printf doesn't support 64 bit numbers by
612
+ // default.
613
+ char encoded_time [32 ];
614
+ snprintf (encoded_time , sizeof (encoded_time ), "%llu" , truncated_time );
615
+ mp_printf (& _socket_print , "\"modified_ns\": %s, " , encoded_time );
575
616
size_t file_size = 0 ;
576
617
if ((file_info .fattrib & AM_DIR ) == 0 ) {
577
618
file_size = file_info .fsize ;
578
619
}
579
- snprintf ( encoded_number , sizeof ( encoded_number ), "%d " , file_size );
580
- _send_chunks ( socket , encoded_number , "}" , NULL );
620
+ mp_printf ( & _socket_print , "\"file_size\": %d } " , file_size );
621
+
581
622
first = false;
582
623
res = f_readdir (dir , & file_info );
583
624
}
@@ -587,12 +628,10 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
587
628
588
629
static void _reply_with_file (socketpool_socket_obj_t * socket , _request * request , const char * filename , FIL * active_file ) {
589
630
uint32_t total_length = f_size (active_file );
590
- char encoded_len [10 ];
591
- snprintf (encoded_len , sizeof (encoded_len ), "%d" , total_length );
592
631
593
- _send_strs (socket ,
594
- "HTTP/1.1 200 OK\r\n" ,
595
- "Content-Length: " , encoded_len , " \r\n" , NULL );
632
+ _send_str (socket , "HTTP/1.1 200 OK\r\n" );
633
+ mp_print_t _socket_print = { socket , _print_raw };
634
+ mp_printf ( & _socket_print , "Content-Length: %d \r\n" , total_length );
596
635
// TODO: Make this a table to save space.
597
636
if (_endswith (filename , ".txt" ) || _endswith (filename , ".py" )) {
598
637
_send_str (socket , "Content-Type: text/plain\r\n" );
@@ -640,27 +679,23 @@ static void _reply_with_devices_json(socketpool_socket_obj_t *socket, _request *
640
679
socketpool_socket_send (socket , (const uint8_t * )OK_JSON , strlen (OK_JSON ));
641
680
_cors_header (socket , request );
642
681
_send_str (socket , "\r\n" );
643
- char total_encoded [ 4 ] ;
644
- snprintf ( total_encoded , sizeof ( total_encoded ), "%d" , total_results );
645
- _send_chunks ( socket , "{\"total\": " , total_encoded , ", \"devices\": [" , NULL );
682
+ mp_print_t _socket_print = { socket , _print_chunk } ;
683
+
684
+ mp_printf ( & _socket_print , "{\"total\": %d, \"devices\": [" , total_results );
646
685
for (size_t i = 0 ; i < count ; i ++ ) {
647
686
if (i > 0 ) {
648
687
_send_chunk (socket , "," );
649
688
}
650
689
const char * hostname = common_hal_mdns_remoteservice_get_hostname (& found_devices [i ]);
651
690
const char * instance_name = common_hal_mdns_remoteservice_get_instance_name (& found_devices [i ]);
652
- char port_encoded [4 ];
653
691
int port = common_hal_mdns_remoteservice_get_port (& found_devices [i ]);
654
- snprintf (port_encoded , sizeof (port_encoded ), "%d" , port );
655
- char ip_encoded [4 * 4 ];
656
692
uint32_t ipv4_address = mdns_remoteservice_get_ipv4_address (& found_devices [i ]);
657
693
uint8_t * octets = (uint8_t * )& ipv4_address ;
658
- snprintf (ip_encoded , sizeof (ip_encoded ), "%d.%d.%d.%d" , octets [0 ], octets [1 ], octets [2 ], octets [3 ]);
659
- _send_chunks (socket ,
660
- "{\"hostname\": \"" , hostname , "\", " ,
661
- "\"instance_name\": \"" , instance_name , "\", " ,
662
- "\"port\": " , port_encoded , ", " ,
663
- "\"ip\": \"" , ip_encoded , "\"}" , NULL );
694
+ mp_printf (& _socket_print ,
695
+ "{\"hostname\": \"%s\", "
696
+ "\"instance_name\": \"%s\", "
697
+ "\"port\": %d, "
698
+ "\"ip\": \"%d.%d.%d.%d\"}" , hostname , instance_name , port , octets [0 ], octets [1 ], octets [2 ], octets [3 ]);
664
699
common_hal_mdns_remoteservice_deinit (& found_devices [i ]);
665
700
}
666
701
_send_chunk (socket , "]}" );
@@ -672,24 +707,22 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
672
707
_send_str (socket , OK_JSON );
673
708
_cors_header (socket , request );
674
709
_send_str (socket , "\r\n" );
675
- char encoded_creator_id [11 ]; // 2 ** 32 is 10 decimal digits plus one for \0
676
- snprintf (encoded_creator_id , sizeof (encoded_creator_id ), "%u" , CIRCUITPY_CREATOR_ID );
677
- char encoded_creation_id [11 ]; // 2 ** 32 is 10 decimal digits plus one for \0
678
- snprintf (encoded_creation_id , sizeof (encoded_creation_id ), "%u" , CIRCUITPY_CREATION_ID );
710
+ mp_print_t _socket_print = {socket , _print_chunk };
711
+
679
712
const char * hostname = common_hal_mdns_server_get_hostname (& mdns );
680
- _send_chunks ( socket ,
681
- "{\"web_api_version\": 1, " ,
682
- "\"version \": \"" , MICROPY_GIT_TAG , "\", " ,
683
- "\"build_date \": \"" , MICROPY_BUILD_DATE , "\", " ,
684
- "\"board_name \": \"" , MICROPY_HW_BOARD_NAME , "\", " ,
685
- "\"mcu_name \": \"" , MICROPY_HW_MCU_NAME , "\", " ,
686
- "\"board_id \": \"" , CIRCUITPY_BOARD_ID , "\", " ,
687
- "\"creator_id \": " , encoded_creator_id , " , ",
688
- "\"creation_id \": " , encoded_creation_id , ", " ,
689
- "\"hostname \": \"" , hostname , "\", " ,
690
- "\"port \": 80 , " ,
691
- "\"ip \": \"" , _our_ip_encoded ,
692
- "\"}" , NULL );
713
+ // Note: this leverages the fact that C concats consecutive string literals together.
714
+ mp_printf ( & _socket_print ,
715
+ "{\"web_api_version \": 1, "
716
+ "\"version \": \"" MICROPY_GIT_TAG "\", "
717
+ "\"build_date \": \"" MICROPY_BUILD_DATE "\", "
718
+ "\"board_name \": \"" MICROPY_HW_BOARD_NAME "\", "
719
+ "\"mcu_name \": \"" MICROPY_HW_MCU_NAME "\", "
720
+ "\"board_id \": \"" CIRCUITPY_BOARD_ID "\" , "
721
+ "\"creator_id \": %u, "
722
+ "\"creation_id \": %u, "
723
+ "\"hostname \": \"%s\" , "
724
+ "\"port \": %d, "
725
+ "\"ip\": \"%s\" }" , CIRCUITPY_CREATOR_ID , CIRCUITPY_CREATION_ID , hostname , web_api_port , _our_ip_encoded );
693
726
// Empty chunk signals the end of the response.
694
727
_send_chunk (socket , "" );
695
728
}
@@ -1179,7 +1212,12 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
1179
1212
request -> authenticated = strncmp (request -> header_value , prefix , strlen (prefix )) == 0 &&
1180
1213
strcmp (_api_password , request -> header_value + strlen (prefix )) == 0 ;
1181
1214
} else if (strcasecmp (request -> header_key , "Host" ) == 0 ) {
1182
- request -> redirect = strcmp (request -> header_value , "circuitpython.local" ) == 0 ;
1215
+ // Do a prefix check so that port is ignored. Length must be the same or the
1216
+ // header ends in :.
1217
+ const char * cp_local = "circuitpython.local" ;
1218
+ request -> redirect = strncmp (request -> header_value , cp_local , strlen (cp_local )) == 0 &&
1219
+ (strlen (request -> header_value ) == strlen (cp_local ) ||
1220
+ request -> header_value [strlen (cp_local )] == ':' );
1183
1221
} else if (strcasecmp (request -> header_key , "Content-Length" ) == 0 ) {
1184
1222
request -> content_length = strtoul (request -> header_value , NULL , 10 );
1185
1223
} else if (strcasecmp (request -> header_key , "Expect" ) == 0 ) {
0 commit comments