Skip to content

Commit 78b4159

Browse files
committed
Allow for dynamic reconfigure including port
1 parent 4e6fa55 commit 78b4159

File tree

6 files changed

+79
-33
lines changed

6 files changed

+79
-33
lines changed

docs/environment.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ CIRCUITPY_WEB_API_PASSWORD
3939
~~~~~~~~~~~~~~~~~~~~~~~~~~
4040
Password required to make modifications to the board from the Web Workflow.
4141

42+
CIRCUITPY_WEB_API_PORT
43+
~~~~~~~~~~~~~~~~~~~~~~
44+
TCP port number used for the web HTTP API. Defaults to 80 when omitted.
45+
4246
CIRCUITPY_WIFI_PASSWORD
4347
~~~~~~~~~~~~~~~~~~~~~~~
4448
Wi-Fi password used to auto connect to CIRCUITPY_WIFI_SSID.

docs/workflows.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Read-only characteristic that returns the UTF-8 encoded version string.
7272
The web workflow is depends on adding Wi-Fi credentials into the `/.env` file. The keys are
7373
`CIRCUITPY_WIFI_SSID` and `CIRCUITPY_WIFI_PASSWORD`. Once these are defined, CircuitPython will
7474
automatically connect to the network and start the webserver used for the workflow. The webserver
75-
is on port 80. It also enables MDNS.
75+
is on port 80 unless overridden by `CIRCUITPY_WEB_API_PORT`. It also enables MDNS.
7676

7777
Here is an example `/.env`:
7878

@@ -83,6 +83,8 @@ CIRCUITPY_WIFI_PASSWORD='secretpassword'
8383

8484
# To enable modifying files from the web. Change this too!
8585
CIRCUITPY_WEB_API_PASSWORD='passw0rd'
86+
87+
CIRCUITPY_WEB_API_PORT=80
8688
```
8789

8890
MDNS is used to resolve [`circuitpython.local`](http://circuitpython.local) to a device specific

ports/espressif/common-hal/mdns/Server.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,9 @@ mp_obj_t common_hal_mdns_server_find(mdns_server_obj_t *self, const char *servic
204204
}
205205

206206
void common_hal_mdns_server_advertise_service(mdns_server_obj_t *self, const char *service_type, const char *protocol, mp_int_t port) {
207-
mdns_service_add(NULL, service_type, protocol, port, NULL, 0);
207+
if (mdns_service_exists(service_type, protocol, NULL)) {
208+
mdns_service_port_set(service_type, protocol, port);
209+
} else {
210+
mdns_service_add(NULL, service_type, protocol, port, NULL, 0);
211+
}
208212
}

shared-bindings/mdns/Server.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mdns_server_find_obj, 1, _mdns_server_find);
161161
//| def advertise_service(self, *, service_type: str, protocol: str, port: int) -> None:
162162
//| """Respond to queries for the given service with the given port.
163163
//|
164+
//| service_type and protocol can only occur on one port. Any call after the first will
165+
//| update the entry's port.
166+
//|
164167
//| :param str service_type: The service type such as "_http"
165168
//| :param str protocol: The service protocol such as "_tcp"
166169
//| :param int port: The port used by the service"""

supervisor/shared/web_workflow/static/welcome.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async function find_devices() {
4444
li.appendChild(a);
4545
var port = "";
4646
if (device.port != 80) {
47-
port = ":" + version_info.port;
47+
port = ":" + device.port;
4848
}
4949
var server;
5050
if (mdns_works) {

supervisor/shared/web_workflow/web_workflow.c

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ typedef struct {
9898
static wifi_radio_error_t wifi_status = WIFI_RADIO_ERROR_NONE;
9999

100100
static mdns_server_obj_t mdns;
101+
static uint32_t web_api_port = 80;
101102

102103
static socketpool_socketpool_obj_t pool;
103104
static socketpool_socket_obj_t listening;
@@ -189,6 +190,9 @@ void supervisor_web_workflow_status(void) {
189190
}
190191

191192
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+
}
192196
// TODO: Use these unicode to show signal strength: ▂▄▆█
193197
}
194198
} else {
@@ -199,11 +203,6 @@ void supervisor_web_workflow_status(void) {
199203
void supervisor_start_web_workflow(void) {
200204
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI
201205

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-
}
207206

208207
char ssid[33];
209208
char password[64];
@@ -218,15 +217,20 @@ void supervisor_start_web_workflow(void) {
218217
password_len <= 0 || (size_t)password_len >= sizeof(password)) {
219218
return;
220219
}
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+
}
223224

224225
// TODO: Do our own scan so that we can find the channel we want before calling connect.
225226
// Otherwise, connect will do a full slow scan to pick the best AP.
226227

227228
// NUL terminate the strings because dotenv doesn't.
228229
ssid[ssid_len] = '\0';
229230
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.
230234
wifi_status = common_hal_wifi_radio_connect(
231235
&common_hal_wifi_radio_obj, (uint8_t *)ssid, ssid_len, (uint8_t *)password, password_len,
232236
0, 0.1, NULL, 0);
@@ -236,21 +240,46 @@ void supervisor_start_web_workflow(void) {
236240
return;
237241
}
238242

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+
new_port = strtoul(port_encoded, NULL, 10);
251+
}
252+
253+
bool first_start = mdns.base.type != &mdns_server_type;
254+
bool port_changed = new_port != web_api_port;
242255

243-
pool.base.type = &socketpool_socketpool_type;
244-
common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj);
256+
if (first_start) {
257+
ESP_LOGI(TAG, "Starting web workflow");
258+
mdns_server_construct(&mdns, true);
259+
mdns.base.type = &mdns_server_type;
260+
common_hal_mdns_server_set_instance_name(&mdns, MICROPY_HW_BOARD_NAME);
261+
pool.base.type = &socketpool_socketpool_type;
262+
common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj);
245263

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);
264+
listening.base.type = &socketpool_socket_type;
265+
active.base.type = &socketpool_socket_type;
266+
active.num = -1;
267+
active.connected = false;
268+
269+
websocket_init();
270+
}
271+
if (port_changed) {
272+
common_hal_socketpool_socket_close(&listening);
273+
}
274+
if (first_start || port_changed) {
275+
web_api_port = new_port;
276+
common_hal_mdns_server_advertise_service(&mdns, "_circuitpython", "_tcp", web_api_port);
277+
socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening);
278+
common_hal_socketpool_socket_settimeout(&listening, 0);
279+
// Bind to any ip.
280+
common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port);
281+
common_hal_socketpool_socket_listen(&listening, 1);
282+
}
254283

255284
mp_int_t api_password_len = dotenv_get_key("/.env", "CIRCUITPY_WEB_API_PASSWORD", _api_password + 1, sizeof(_api_password) - 2);
256285
if (api_password_len > 0) {
@@ -259,12 +288,6 @@ void supervisor_start_web_workflow(void) {
259288
_base64_in_place(_api_password, api_password_len + 1, sizeof(_api_password));
260289
}
261290

262-
active.base.type = &socketpool_socket_type;
263-
active.num = -1;
264-
active.connected = false;
265-
266-
websocket_init();
267-
268291
// TODO:
269292
// GET /cp/serial.txt
270293
// - Most recent 1k of serial output.
@@ -529,7 +552,13 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request,
529552
_send_str(socket, "http");
530553
}
531554

532-
_send_strs(socket, "://", hostname, ".local", path, "\r\n", NULL);
555+
_send_strs(socket, "://", hostname, ".local", NULL);
556+
if (web_api_port != 80) {
557+
char encoded_port[6];
558+
snprintf(encoded_port, sizeof(encoded_port), "%d", web_api_port);
559+
_send_strs(socket, ":", encoded_port, NULL);
560+
}
561+
_send_strs(socket, path, "\r\n", NULL);
533562
_cors_header(socket, request);
534563
_send_str(socket, "\r\n");
535564
}
@@ -647,7 +676,7 @@ static void _reply_with_devices_json(socketpool_socket_obj_t *socket, _request *
647676
}
648677
const char *hostname = common_hal_mdns_remoteservice_get_hostname(&found_devices[i]);
649678
const char *instance_name = common_hal_mdns_remoteservice_get_instance_name(&found_devices[i]);
650-
char port_encoded[4];
679+
char port_encoded[6];
651680
int port = common_hal_mdns_remoteservice_get_port(&found_devices[i]);
652681
snprintf(port_encoded, sizeof(port_encoded), "%d", port);
653682
char ip_encoded[4 * 4];
@@ -675,6 +704,8 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
675704
char encoded_creation_id[11]; // 2 ** 32 is 10 decimal digits plus one for \0
676705
snprintf(encoded_creation_id, sizeof(encoded_creation_id), "%u", CIRCUITPY_CREATION_ID);
677706
const char *hostname = common_hal_mdns_server_get_hostname(&mdns);
707+
char encoded_port[6];
708+
snprintf(encoded_port, sizeof(encoded_port), "%d", web_api_port);
678709
_send_chunks(socket,
679710
"{\"web_api_version\": 1, ",
680711
"\"version\": \"", MICROPY_GIT_TAG, "\", ",
@@ -685,7 +716,7 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
685716
"\"creator_id\": ", encoded_creator_id, ", ",
686717
"\"creation_id\": ", encoded_creation_id, ", ",
687718
"\"hostname\": \"", hostname, "\", ",
688-
"\"port\": 80, ",
719+
"\"port\": ", encoded_port, ", ",
689720
"\"ip\": \"", _our_ip_encoded,
690721
"\"}", NULL);
691722
// Empty chunk signals the end of the response.
@@ -1173,7 +1204,9 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
11731204
request->authenticated = memcmp(request->header_value, prefix, strlen(prefix)) == 0 &&
11741205
strcmp(_api_password, request->header_value + strlen(prefix)) == 0;
11751206
} else if (strcmp(request->header_key, "Host") == 0) {
1176-
request->redirect = strcmp(request->header_value, "circuitpython.local") == 0;
1207+
// Do a prefix check so that port is ignored.
1208+
const char *cp_local = "circuitpython.local";
1209+
request->redirect = memcmp(request->header_value, cp_local, strlen(cp_local)) == 0;
11771210
} else if (strcmp(request->header_key, "Content-Length") == 0) {
11781211
request->content_length = strtoul(request->header_value, NULL, 10);
11791212
} else if (strcmp(request->header_key, "Expect") == 0) {

0 commit comments

Comments
 (0)