fix(gateway): stop offering a link a SoftAP sketch can never open
The serial monitor turns any 192.168.4.x it sees into an "Open IoT Gateway" link, because that is the subnet QEMU's slirp leases to a connected board. The ESP32's SoftAP default address is the same 192.168.4.1, so a captive-portal sketch got the same link. It cannot work: an access point never receives a DHCP lease, so it never reaches 'got_ip', so it never registers the bridge the proxy looks up. The click landed on a 404 whose text then told the user to make sure the sketch connected to WiFi, which is precisely what that sketch had chosen not to do. The user is doing everything right and the product is lying twice. Gate the link on the status every family already reports: QEMU through wifi_status_parser, the in-browser JS engines, and the Pico W's cyw43 peripheral all emit 'got_ip'. Without it the address stays plain text. Measured on a XIAO ESP32S3 Sense, one sketch per mode: softAP 192.168.4.1 gateway links 1 -> 0 station 10.13.37.42 gateway links 1 -> 1 (unchanged) The 404 body is rewritten to name softAP as the likely cause and point at a network that exists in the simulator, instead of accusing the sketch. Covered by a test that fails if the accusatory sentence comes back.
This commit is contained in:
parent
45410df6af
commit
0251bd25a0
|
|
@ -87,8 +87,26 @@ async def gateway_proxy(client_id: str, path: str, request: Request) -> Response
|
|||
if overlay_resp is not None:
|
||||
return overlay_resp
|
||||
|
||||
# Wording matters here. The old text said "make sure your sketch connected
|
||||
# to WiFi", which reads as an accusation to the one sketch that most often
|
||||
# lands on this branch: a captive-portal / provisioning sketch that called
|
||||
# WiFi.softAP() and deliberately did NOT join a network. Such a board is
|
||||
# the access point, so it never gets a DHCP lease, never reaches 'got_ip',
|
||||
# and never registers the bridge this proxy looks up — there is nothing to
|
||||
# reach and nothing the user did wrong. Name that case first.
|
||||
return Response(
|
||||
content='{"error":"No WiFi-enabled board found for this client. Make sure your sketch connected to WiFi and started a server on port 80."}',
|
||||
content=json.dumps({
|
||||
'error': 'no_reachable_board',
|
||||
'message': (
|
||||
'Nothing is registered for this client, so there is no server '
|
||||
'to reach. If your sketch calls WiFi.softAP(), that is why: '
|
||||
'the simulator can reach a board that JOINED a network, not a '
|
||||
'board that created one. Join a simulated network instead '
|
||||
'(WiFi.begin("Velxio-GUEST") — open, no password) and start '
|
||||
'your server on port 80. If the sketch does join a network, '
|
||||
'wait for it to print its IP before opening the gateway.'
|
||||
),
|
||||
}),
|
||||
status_code=404,
|
||||
media_type='application/json',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -293,7 +293,20 @@ export const SerialMonitor: React.FC = () => {
|
|||
// the emulated network. The trailing dot-check keeps a longer
|
||||
// address like 192.168.4.15.99 from half-matching.
|
||||
const ipRegex = /(?:http:\/\/)?(?:192\.168\.4|10\.13\.37)\.(\d{1,3})(?!\d|\.\d)(\/[^\s]*)?/g;
|
||||
const matches = [...text.matchAll(ipRegex)];
|
||||
// ...but ONLY once the board actually joined a network. A sketch
|
||||
// that calls WiFi.softAP() becomes the access point instead of a
|
||||
// client, and the ESP32's SoftAP default address is 192.168.4.1
|
||||
// — the very subnet above. The address alone cannot tell the two
|
||||
// apart, so a captive-portal sketch used to get a gateway link
|
||||
// that could never resolve: the bridge the proxy looks up is
|
||||
// registered on 'got_ip', which only a STATION ever reaches, so
|
||||
// the click landed on a 404 telling the user to connect to WiFi
|
||||
// when the sketch had deliberately chosen not to. Gate on the
|
||||
// status every family reports: QEMU (wifi_status_parser),
|
||||
// the in-browser JS engines, and the Pico W's cyw43 peripheral
|
||||
// all emit 'got_ip'. Without it the IP stays plain text.
|
||||
const reachable = activeBoard.wifiStatus?.status === 'got_ip';
|
||||
const matches = reachable ? [...text.matchAll(ipRegex)] : [];
|
||||
|
||||
if (matches.length > 0) {
|
||||
const parts: (string | React.ReactNode)[] = [];
|
||||
|
|
|
|||
|
|
@ -246,6 +246,26 @@ class TestHttpServerDetection(unittest.TestCase):
|
|||
class TestIoTGatewayUrl(unittest.TestCase):
|
||||
"""Test IoT Gateway URL construction for the WebServer sketch."""
|
||||
|
||||
def test_unreachable_message_names_softap_instead_of_blaming_the_sketch(self):
|
||||
"""The 404 a captive-portal sketch hits must explain WHY, not accuse.
|
||||
|
||||
A sketch that calls WiFi.softAP() is the access point: it never gets a
|
||||
DHCP lease, never reaches 'got_ip', and so never registers the bridge
|
||||
the proxy looks up. The old copy said "make sure your sketch connected
|
||||
to WiFi", which is exactly what such a sketch deliberately did not do.
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
route = Path(__file__).resolve().parents[3] / 'backend/app/api/routes/iot_gateway.py'
|
||||
src = route.read_text(encoding='utf-8')
|
||||
# The accusatory sentence must be gone...
|
||||
self.assertNotIn('Make sure your sketch connected to WiFi and started', src)
|
||||
# ...and the replacement must name softAP and point at a real network.
|
||||
tail = src[src.index('no_reachable_board'):]
|
||||
self.assertIn('WiFi.softAP()', tail)
|
||||
self.assertIn('Velxio-GUEST', tail)
|
||||
|
||||
def test_root_gateway_url(self):
|
||||
client_id = 'board-1'
|
||||
url = f'http://localhost:8001/api/gateway/{client_id}/'
|
||||
|
|
|
|||
Loading…
Reference in New Issue