Eus channelfix (#1583)

Squashed commits included:

Bug fixes and final implementation
- Added Content-Length: 0 to all headers
- Endpoint name checks not using trailing space so cache-busting techniques can be used (i.e., append a nonce to the URL)
- Track when connecting so APList scan doesn't take place during (which changes the channel)
- More debugging output added to assist in tracking down some issues

Added /status.json endpoint for phone apps/XHR to get JSON response

Station Status caching for wifi channel workaround + AJAX/CORS
- During checkstation poll, cache the last station status
- Shut down the station if status = 2,3,4 and channel is different than SoftAP
- Add Access-Control-Allow-Origin: * to endpoint responses used by a service
- Add a /setwifi GET endpoint for phone apps/XHR to use (same parameters as /update endpoint). Returns a JSON response containing chip id and status code.
- Add handler for OPTIONS verb (needed for CORS support)

Wi-Fi Channel Issue Workaround
- Do a site survey upon startup, set SoftAP channel to the strongest rssi's channel
- Compare successful station connect channel to SoftAP's. If different, then defer the Lua success callback to the end. Shut down Station and start the SoftAP back up with original channel.
- After the 10 second shutdown timer fires, check to see if success callback was already called. If not, then call it while starting the Station back up.

HTTP Response and DNS enhancements
- If DNS's UDP buffer fills up, keep going as non-fatal. It's UDP and not guaranteed anyways. I've seen this occur when connecting a PC to the SoftAP and every open program tries to phone home at the same time, overwhelming the EUS DNS server.
- Support for detecting/handling pre-gzipped `enduser_setup.html` (and `http_html_backup`) payload. Nice for keeping the size of the `state->http_payload_data` as small as possible (also makes minimization not as critical)
- Corrected misuse of HTTP 401 response status (changed one occurrence to 400/Bad Request, and changed another to 405/Method Not Allowed)

* Normalized formatting (tabs-to-spaces)
* Added documentation
* Corrected misuse of strlen for binary (gzip) data.
* Added NULL check after malloc
This commit is contained in:
Jason Follas 2016-11-08 15:58:33 -05:00 committed by Marcel Stör
parent a0e2e0ca37
commit 058777ea60
6 changed files with 928 additions and 224 deletions

View File

@ -31,6 +31,7 @@
* @author Robert Foss <dev@robertfoss.se>
*
* Additions & fixes: Johny Mattsson <jmattsson@dius.com.au>
* Jason Follas <jfollas@gmail.com>
*/
#include "module.h"
@ -50,6 +51,8 @@
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define LITLEN(strliteral) (sizeof (strliteral) -1)
#define STRINGIFY(x) #x
#define NUMLEN(x) (sizeof(STRINGIFY(x)) - 1)
#define ENDUSER_SETUP_ERR_FATAL (1 << 0)
#define ENDUSER_SETUP_ERR_NONFATAL (1 << 1)
@ -59,7 +62,7 @@
#define ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND 2
#define ENDUSER_SETUP_ERR_UNKOWN_ERROR 3
#define ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN 4
#define ENDUSER_SETUP_ERR_MAX_NUMBER 5
/**
* DNS Response Packet:
@ -78,159 +81,19 @@ static const char dns_body[] = { 0x00, 0x01, 0x00, 0x01,
0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04 };
static const char http_html_filename[] = "enduser_setup.html";
static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nContent-Type: text/html\r\n"; /* Note single \r\n here! */
static const char http_header_204[] = "HTTP/1.1 204 No Content\r\n\r\n";
static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\n\r\n";
static const char http_header_401[] = "HTTP/1.1 401 Bad request\r\n\r\n";
static const char http_header_404[] = "HTTP/1.1 404 Not found\r\n\r\n";
static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\n\r\n";
static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nConnection:close\r\nContent-Type:text/html\r\n"; /* Note single \r\n here! */
static const char http_header_204[] = "HTTP/1.1 204 No Content\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
static const char http_header_400[] = "HTTP/1.1 400 Bad request\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
static const char http_header_404[] = "HTTP/1.1 404 Not found\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
static const char http_header_405[] = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
/* The below is the un-minified version of the http_html_backup[] string.
* Minified using https://kangax.github.io/html-minifier/
* Note: using method="get" due to iOS not always sending body in same
* packet as the HTTP header, and us thus missing it in that case
*/
#if 0
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=380">
<title>WiFi Login</title>
<style media="screen" type="text/css">
*{margin:0;padding:0}
html{height:100%;background:linear-gradient(rgba(196,102,0,.2),rgba(155,89,182,.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}
body{font-family:arial,verdana}
div{position:absolute;margin:auto;top:-150px;right:0;bottom:0;left:0;width:320px;height:304px}
form{width:320px;text-align:center;position:relative}
form fieldset{background:#fff;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,.4);padding:20px 30px;box-sizing:border-box}
form input{padding:15px;border:1px solid #ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}
form .action-button{border:0 none;border-radius:3px;cursor:pointer;}
#msform .submit:focus,form .action-button:hover{box-shadow:0 0 0 2px #fff,0 0 0 3px #27AE60;}
#formFrame{display: none;}
#aplist{display: block;}
select{width:100%;margin-bottom: 20px;padding: 10px 5px; border:1px solid #ccc;display:none;}
.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}
.fs-subtitle{font-weight:400;font-size:13px;color:#666;margin-bottom:20px}
.fs-status{font-weight:400;font-size:13px;color:#666;margin-bottom:10px;padding-top:20px; border-top:1px solid #ccc}
.submit{width:100px;background: #27AE60; color: #fff;font-weight:700;margin:10px 5px; padding: 10px 5px; }
</style>
</head>
<body>
<div>
<form id="credentialsForm" method="get" action="/update" target="formFrame">
<fieldset>
<iframe id="formFrame" src="" name="formFrame"></iframe> <!-- Used to submit data, needed to prevent re-direction after submission -->
<h2 class="fs-title">WiFi Login</h2>
<h3 class="fs-subtitle">Connect gadget to your WiFi network</h3>
<input id="wifi_ssid" autocorrect="off" autocapitalize="none" name="wifi_ssid" placeholder="WiFi Name">
<select id="aplist" name="aplist" size="1" disabled>
<option>Scanning for networks...</option>
</select>
<input name="wifi_password" placeholder="Password" type="password">
<input type=submit name=save class="action-button submit" value="Save">
<h3 class="fs-status">Status: <span id="status">Updating...</span></h3>
</fieldset>
<h3 id="dbg"></h3>
</form>
</div>
<script>
function fetch(url, method, callback)
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=check_ready;
function check_ready()
{
if (xhr.readyState === 4)
{
callback(xhr.status === 200 ? xhr.responseText : null);
}
}
xhr.open(method, url, true);
xhr.send();
}
function new_status(stat)
{
if (stat)
{
var e = document.getElementById("status");
e.innerHTML = stat;
}
}
function new_status_repeat(stat)
{
new_status(stat);
setTimeout(refresh_status, 750);
}
function new_ap_list(json)
{
if (json)
{
var list = JSON.parse(json);
list.sort(function(a, b){ return b.rssi - a.rssi; });
var ssids = list.map(function(a) { return a.ssid; }).filter(function(item, pos, self) { return self.indexOf(item)==pos; });
var sel = document.getElementById("aplist");
sel.innerHTML = "";
sel.setAttribute("size", Math.max(Math.min(3, list.length), 1));
sel.removeAttribute("disabled");
for (var i = 0; i < ssids.length; ++i)
{
var o = document.createElement("option");
o.innerHTML = ssids[i];
sel.options.add(o);
}
sel.style.display = 'block';
}
}
function new_ap_list_repeat(json)
{
new_ap_list(json);
setTimeout(refresh_ap_list, 3000);
}
function refresh_status()
{
fetch('/status','GET', new_status_repeat);
}
function refresh_ap_list()
{
fetch('/aplist','GET', new_ap_list_repeat);
}
function set_ssid_field() {
var sel = document.getElementById("aplist");
document.getElementById("wifi_ssid").value = sel.value;
}
window.onload = function()
{
refresh_status();
refresh_ap_list();
document.getElementById("aplist").onclick = set_ssid_field;
document.getElementById("aplist").onchange = set_ssid_field;
document.getElementById("credentialsForm").addEventListener("submit", function(){
fetch('/status','GET', new_status);
});
}
</script>
</body>
</html>
#endif
static const char http_html_backup[] =
"<!DOCTYPE html><meta http-equiv=content-type content='text/html; charset=UTF-8'><meta charset=utf-8><meta name=viewport content='width=380'><title>WiFi Login</title><style media=screen type=text/css>*{margin:0;padding:0}html{height:100%;background:linear-gradient(rgba(196,102,0,.2),rgba(155,89,182,.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}body{font-family:arial,verdana}div{position:absolute;margin:auto;top:-150px;right:0;bottom:0;left:0;width:320px;height:304px}form{width:320px;text-align:center;position:relative}form fieldset{background:#fff;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,.4);padding:20px 30px;box-sizing:border-box}form input{padding:15px;border:1px solid #ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}form .action-button{border:0 none;border-radius:3px;cursor:pointer}#msform .submit:focus,form .action-button:hover{box-shadow:0 0 0 2px #fff,0 0 0 3px #27AE60}#formFrame{display:none}#aplist{display:block}select{width:100%;margin-bottom:20px;padding:10px 5px;border:1px solid #ccc;display:none}.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}.fs-subtitle{font-weight:400;font-size:13px;color:#666;margin-bottom:20px}.fs-status{font-weight:400;font-size:13px;color:#666;margin-bottom:10px;padding-top:20px;border-top:1px solid #ccc}.submit{width:100px;background:#27AE60;color:#fff;font-weight:700;margin:10px 5px;padding:10px 5px}</style><div><form id=credentialsForm action=/update target=formFrame><fieldset><iframe id=formFrame src=''name=formFrame></iframe><h2 class=fs-title>WiFi Login</h2><h3 class=fs-subtitle>Connect gadget to your WiFi network</h3><input id=wifi_ssid autocorrect=off autocapitalize=none name=wifi_ssid placeholder='WiFi Name'><select id=aplist name=aplist size=1 disabled><option>Scanning for networks...</select><input name=wifi_password placeholder=Password type=password> <input type=submit name=save class='action-button submit'value=Save><h3 class=fs-status>Status: <span id=status>Updating...</span></h3></fieldset><h3 id=dbg></h3></form></div><script>function fetch(t,e,n){function s(){4===i.readyState&&n(200===i.status?i.responseText:null)}var i=new XMLHttpRequest;i.onreadystatechange=s,i.open(e,t,!0),i.send()}function new_status(t){if(t){var e=document.getElementById('status');e.innerHTML=t}}function new_status_repeat(t){new_status(t),setTimeout(refresh_status,750)}function new_ap_list(t){if(t){var e=JSON.parse(t);e.sort(function(t,e){return e.rssi-t.rssi});var n=e.map(function(t){return t.ssid}).filter(function(t,e,n){return n.indexOf(t)==e}),s=document.getElementById('aplist');s.innerHTML='',s.setAttribute('size',Math.max(Math.min(3,e.length),1)),s.removeAttribute('disabled');for(var i=0;i<n.length;++i){var a=document.createElement('option');a.innerHTML=n[i],s.options.add(a)}s.style.display='block'}}function new_ap_list_repeat(t){new_ap_list(t),setTimeout(refresh_ap_list,3e3)}function refresh_status(){fetch('/status','GET',new_status_repeat)}function refresh_ap_list(){fetch('/aplist','GET',new_ap_list_repeat)}function set_ssid_field(){var t=document.getElementById('aplist');document.getElementById('wifi_ssid').value=t.value}window.onload=function(){refresh_status(),refresh_ap_list(),document.getElementById('aplist').onclick=set_ssid_field,document.getElementById('aplist').onchange=set_ssid_field,document.getElementById('credentialsForm').addEventListener('submit',function(){fetch('/status','GET',new_status)})}</script>";
static const char http_header_content_len_fmt[] = "Content-length:%5d\r\n\r\n";
static const char http_html_gzip_contentencoding[] = "Content-Encoding: gzip\r\n";
// Externally defined: static const char http_html_backup[] = ...
#include "eus/http_html_backup.def"
typedef struct scan_listener
{
@ -250,9 +113,15 @@ typedef struct
int lua_err_cb_ref;
int lua_dbg_cb_ref;
scan_listener_t *scan_listeners;
uint8_t softAPchannel;
uint8_t success;
uint8_t callbackDone;
uint8_t lastStationStatus;
uint8_t connecting;
} enduser_setup_state_t;
static enduser_setup_state_t *state;
static bool manual = false;
static task_handle_t do_station_cfg_handle;
@ -340,7 +209,7 @@ static void enduser_setup_check_station_start(void)
ENDUSER_SETUP_DEBUG("enduser_setup_check_station_start");
os_timer_setfn(&(state->check_station_timer), enduser_setup_check_station, NULL);
os_timer_arm(&(state->check_station_timer), 1*1000, TRUE);
os_timer_arm(&(state->check_station_timer), 3*1000, TRUE);
}
@ -377,13 +246,60 @@ static void enduser_setup_check_station(void *p)
has_ip |= ((char *) &ip)[i];
}
uint8_t currChan = wifi_get_channel();
if (has_ip == 0)
{
return;
// No IP Address yet, so check the reported status
uint8_t curr_status = wifi_station_get_connect_status();
char buf[20];
c_sprintf(buf, "status=%d,chan=%d", curr_status, currChan);
ENDUSER_SETUP_DEBUG(buf);
if (curr_status == 2 || curr_status == 3 || curr_status == 4)
{
state->connecting = 0;
// If the status is an error status and the channel changed, then cache the
// status to state since the Station won't be able to report the same status
// after switching the channel back to the SoftAP's
if (currChan != state->softAPchannel) {
state->lastStationStatus = curr_status;
ENDUSER_SETUP_DEBUG("Turning off Station due to different channel than AP");
wifi_station_disconnect();
wifi_set_opmode(SOFTAP_MODE);
enduser_setup_ap_start();
}
}
return;
}
state->success = 1;
state->lastStationStatus = 5; // We have an IP Address, so the status is 5 (as of SDK 1.5.1)
state->connecting = 0;
#if ENDUSER_SETUP_DEBUG_ENABLE
char debuginfo[100];
c_sprintf(debuginfo, "AP_CHAN: %d, STA_CHAN: %d", state->softAPchannel, currChan);
ENDUSER_SETUP_DEBUG(debuginfo);
#endif
if (currChan == state->softAPchannel)
{
enduser_setup_connected_callback();
state->callbackDone = 1;
}
else
{
ENDUSER_SETUP_DEBUG("Turning off Station due to different channel than AP");
wifi_station_disconnect();
wifi_set_opmode(SOFTAP_MODE);
enduser_setup_ap_start();
}
enduser_setup_check_station_stop();
enduser_setup_connected_callback();
/* Trigger shutdown, but allow time for HTTP client to fetch last status. */
if (!manual)
@ -411,6 +327,8 @@ static void enduser_setup_check_station(void *p)
/* Callback on timeout to hard-close a connection */
static err_t force_abort (void *arg, struct tcp_pcb *pcb)
{
ENDUSER_SETUP_DEBUG("force_abort");
(void)arg;
tcp_poll (pcb, 0, 0);
tcp_abort (pcb);
@ -420,6 +338,8 @@ static err_t force_abort (void *arg, struct tcp_pcb *pcb)
/* Callback to detect a remote-close of a connection */
static err_t handle_remote_close (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
ENDUSER_SETUP_DEBUG("handle_remote_close");
(void)arg; (void)err;
if (p) /* server sent us data, just ACK and move on */
{
@ -438,6 +358,8 @@ static err_t handle_remote_close (void *arg, struct tcp_pcb *pcb, struct pbuf *p
/* Set up a deferred close of a connection, as discussed above. */
static inline void deferred_close (struct tcp_pcb *pcb)
{
ENDUSER_SETUP_DEBUG("deferred_close");
tcp_poll (pcb, force_abort, 15); /* ~3sec from now */
tcp_recv (pcb, handle_remote_close);
tcp_sent (pcb, 0);
@ -446,6 +368,8 @@ static inline void deferred_close (struct tcp_pcb *pcb)
/* Convenience function to queue up a close-after-send. */
static err_t close_once_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
{
ENDUSER_SETUP_DEBUG("close_once_sent");
(void)arg; (void)len;
deferred_close (pcb);
return ERR_OK;
@ -473,7 +397,6 @@ static int enduser_setup_srch_str(const char *str, const char *srch_str)
}
}
/**
* Load HTTP Payload
*
@ -495,14 +418,26 @@ static int enduser_setup_http_load_payload(void)
err2 = vfs_lseek(f, 0, VFS_SEEK_SET);
}
const char cl_hdr[] = "Content-length:%5d\r\n\r\n";
const size_t cl_len = LITLEN(cl_hdr) + 3; /* room to expand %4d */
char cl_hdr[30];
size_t ce_len = 0;
c_sprintf(cl_hdr, http_header_content_len_fmt, file_len);
size_t cl_len = c_strlen(cl_hdr);
if (!f || err != VFS_RES_OK || err2 != VFS_RES_OK)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_load_payload unable to load file enduser_setup.html, loading backup HTML.");
int payload_len = LITLEN(http_header_200) + cl_len + LITLEN(http_html_backup);
c_sprintf(cl_hdr, http_header_content_len_fmt, sizeof(http_html_backup));
cl_len = c_strlen(cl_hdr);
if (http_html_backup[0] == 0x1f && http_html_backup[1] == 0x8b)
{
ce_len = c_strlen(http_html_gzip_contentencoding);
ENDUSER_SETUP_DEBUG("Content is gzipped");
}
int payload_len = LITLEN(http_header_200) + cl_len + ce_len + LITLEN(http_html_backup);
state->http_payload_len = payload_len;
state->http_payload_data = (char *) c_malloc(payload_len);
if (state->http_payload_data == NULL)
@ -513,13 +448,25 @@ static int enduser_setup_http_load_payload(void)
int offset = 0;
c_memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200));
offset += LITLEN(http_header_200);
offset += c_sprintf(state->http_payload_data + offset, cl_hdr, LITLEN(http_html_backup));
c_memcpy(&(state->http_payload_data[offset]), &(http_html_backup), LITLEN(http_html_backup));
return 1;
if (ce_len > 0)
offset += c_sprintf(state->http_payload_data + offset, http_html_gzip_contentencoding, ce_len);
c_memcpy(&(state->http_payload_data[offset]), &(cl_hdr), cl_len);
offset += cl_len;
c_memcpy(&(state->http_payload_data[offset]), &(http_html_backup), sizeof(http_html_backup));
return 1;
}
int payload_len = LITLEN(http_header_200) + cl_len + file_len;
char magic[2];
vfs_read(f, magic, 2);
if (magic[0] == 0x1f && magic[1] == 0x8b)
{
ce_len = c_strlen(http_html_gzip_contentencoding);
ENDUSER_SETUP_DEBUG("Content is gzipped");
}
int payload_len = LITLEN(http_header_200) + cl_len + ce_len + file_len;
state->http_payload_len = payload_len;
state->http_payload_data = (char *) c_malloc(payload_len);
if (state->http_payload_data == NULL)
@ -527,13 +474,19 @@ static int enduser_setup_http_load_payload(void)
return 2;
}
vfs_lseek(f, 0, VFS_SEEK_SET);
int offset = 0;
c_memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200));
offset += LITLEN(http_header_200);
offset += c_sprintf(state->http_payload_data + offset, cl_hdr, file_len);
if (ce_len > 0)
offset += c_sprintf(state->http_payload_data + offset, http_html_gzip_contentencoding, ce_len);
c_memcpy(&(state->http_payload_data[offset]), &(cl_hdr), cl_len);
offset += cl_len;
vfs_read(f, &(state->http_payload_data[offset]), file_len);
vfs_close(f);
vfs_close(f);
return 0;
}
@ -605,6 +558,9 @@ static int enduser_setup_http_urldecode(char *dst, const char *src, int src_len,
*/
static void do_station_cfg (task_param_t param, uint8_t prio)
{
ENDUSER_SETUP_DEBUG("do_station_cfg");
state->connecting = 1;
struct station_config *cnf = (struct station_config *)param;
(void)prio;
@ -635,6 +591,9 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_handle_credentials");
state->success = 0;
state->lastStationStatus = 0;
char *name_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_ssid="));
char *pwd_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_password="));
if (name_str == NULL || pwd_str == NULL)
@ -663,7 +622,7 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data
int err;
err = enduser_setup_http_urldecode(cnf->ssid, name_str_start, name_str_len, sizeof(cnf->ssid));
err |= enduser_setup_http_urldecode(cnf->password, pwd_str_start, pwd_str_len, sizeof(cnf->password));
if (err != 0)
if (err != 0 || c_strlen(cnf->ssid) == 0)
{
ENDUSER_SETUP_DEBUG("Unable to decode HTTP parameter to valid password or SSID");
return 1;
@ -694,7 +653,7 @@ static int enduser_setup_http_handle_credentials(char *data, unsigned short data
static int enduser_setup_http_serve_header(struct tcp_pcb *http_client, const char *header, uint32_t header_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_header");
err_t err = tcp_write (http_client, header, header_len, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
@ -708,8 +667,9 @@ static int enduser_setup_http_serve_header(struct tcp_pcb *http_client, const ch
static err_t streamout_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
{
(void)len;
ENDUSER_SETUP_DEBUG("streamout_sent");
(void)len;
unsigned offs = (unsigned)arg;
if (!state || !state->http_payload_data)
@ -754,6 +714,7 @@ static err_t streamout_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
static int enduser_setup_http_serve_html(struct tcp_pcb *http_client)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_html");
if (state->http_payload_data == NULL)
{
enduser_setup_http_load_payload();
@ -774,7 +735,7 @@ static int enduser_setup_http_serve_html(struct tcp_pcb *http_client)
}
static void serve_status(struct tcp_pcb *conn)
static void enduser_setup_serve_status(struct tcp_pcb *conn)
{
ENDUSER_SETUP_DEBUG("enduser_setup_serve_status");
@ -782,11 +743,12 @@ static void serve_status(struct tcp_pcb *conn)
"HTTP/1.1 200 OK\r\n"
"Cache-control:no-cache\r\n"
"Connection:close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-type:text/plain\r\n"
"Content-length: %d\r\n"
"\r\n"
"%s%s";
const char *state[] =
"%s";
const char *states[] =
{
"Idle.",
"Connecting to \"%s\".",
@ -796,8 +758,8 @@ static void serve_status(struct tcp_pcb *conn)
"Connected to \"%s\" (%s)."
};
const size_t num_states = sizeof(state)/sizeof(state[0]);
uint8_t curr_state = wifi_station_get_connect_status ();
const size_t num_states = sizeof(states)/sizeof(states[0]);
uint8_t curr_state = state->lastStationStatus > 0 ? state->lastStationStatus : wifi_station_get_connect_status ();
if (curr_state < num_states)
{
switch (curr_state)
@ -807,22 +769,21 @@ static void serve_status(struct tcp_pcb *conn)
case STATION_NO_AP_FOUND:
case STATION_GOT_IP:
{
const char *s = state[curr_state];
const char *s = states[curr_state];
struct station_config config;
wifi_station_get_config(&config);
config.ssid[31] = '\0';
struct ip_info ip_info;
struct ip_info ip_info;
wifi_get_ip_info(STATION_IF , &ip_info);
char ip_addr[16];
ip_addr[0] = '\0';
if (curr_state == STATION_GOT_IP)
{
c_sprintf (ip_addr, "%d.%d.%d.%d", IP2STR(&ip_info.ip.addr));
}
wifi_get_ip_info(STATION_IF , &ip_info);
char ip_addr[16];
ip_addr[0] = '\0';
if (curr_state == STATION_GOT_IP)
{
c_sprintf (ip_addr, "%d.%d.%d.%d", IP2STR(&ip_info.ip.addr));
}
int state_len = c_strlen(s);
int ip_len = c_strlen(ip_addr);
@ -844,7 +805,7 @@ static void serve_status(struct tcp_pcb *conn)
/* Handle non-formatted strings */
default:
{
const char *s = state[curr_state];
const char *s = states[curr_state];
int status_len = c_strlen(s);
int buf_len = sizeof(fmt) + status_len + 10; //10 = (9+1), 1 byte is '\0' and 9 are reserved for length field
char buf[buf_len];
@ -862,12 +823,76 @@ static void serve_status(struct tcp_pcb *conn)
}
}
static void enduser_setup_serve_status_as_json (struct tcp_pcb *http_client)
{
ENDUSER_SETUP_DEBUG("enduser_setup_serve_status_as_json");
// If the station is currently shut down because of wi-fi channel issue, use the cached status
uint8_t curr_status = state->lastStationStatus > 0 ? state->lastStationStatus : wifi_station_get_connect_status ();
char json_payload[64];
c_sprintf(json_payload, "{\"deviceid\":\"%06X\", \"status\":%d}", system_get_chip_id(), curr_status);
const char fmt[] =
"HTTP/1.1 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n"
"\r\n"
"%s";
int len = c_strlen(json_payload);
char buf[c_strlen(fmt) + NUMLEN(len) + len - 4];
len = c_sprintf (buf, fmt, len, json_payload);
enduser_setup_http_serve_header (http_client, buf, len);
}
static void enduser_setup_handle_OPTIONS (struct tcp_pcb *http_client, char *data, unsigned short data_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_handle_OPTIONS");
const char json[] =
"HTTP/1.1 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"Content-Length: 0\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: GET\r\n"
"Access-Control-Allow-Age: 300\r\n"
"\r\n";
const char others[] =
"HTTP/1.1 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Content-Length: 0\r\n"
"\r\n";
int type = 0;
if (c_strncmp(data, "GET ", 4) == 0)
{
if (c_strncmp(data + 4, "/aplist", 7) == 0 || c_strncmp(data + 4, "/setwifi?", 9) == 0 || c_strncmp(data + 4, "/status.json", 12) == 0)
{
enduser_setup_http_serve_header (http_client, json, c_strlen(json));
return;
}
}
enduser_setup_http_serve_header (http_client, others, c_strlen(others));
return;
}
/* --- WiFi AP scanning support -------------------------------------------- */
static void free_scan_listeners (void)
{
ENDUSER_SETUP_DEBUG("free_scan_listeners");
if (!state || !state->scan_listeners)
{
return;
@ -886,6 +911,8 @@ static void free_scan_listeners (void)
static void remove_scan_listener (scan_listener_t *l)
{
ENDUSER_SETUP_DEBUG("remove_scan_listener");
if (state)
{
scan_listener_t **sl = &state->scan_listeners;
@ -921,6 +948,8 @@ static char *escape_ssid (char *dst, const char *src)
static void notify_scan_listeners (const char *payload, size_t sz)
{
ENDUSER_SETUP_DEBUG("notify_scan_listeners");
if (!state)
{
return;
@ -944,6 +973,8 @@ static void notify_scan_listeners (const char *payload, size_t sz)
static void on_scan_done (void *arg, STATUS status)
{
ENDUSER_SETUP_DEBUG("on_scan_done");
if (!state || !state->scan_listeners)
{
return;
@ -961,13 +992,14 @@ static void on_scan_done (void *arg, STATUS status)
"HTTP/1.1 200 OK\r\n"
"Connection:close\r\n"
"Cache-control:no-cache\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-type:application/json\r\n"
"Content-length:%4d\r\n"
"\r\n";
const size_t hdr_sz = sizeof (header_fmt) +1 -1; /* +expand %4d, -\0 */
/* To be able to safely escape a pathological SSID, we need 2*32 bytes */
const size_t max_entry_sz = sizeof("{\"ssid\":\"\",\"rssi\":},") + 2*32 + 6;
const size_t max_entry_sz = 27 + 2*32 + 6; // {"ssid":"","rssi":,"chan":}
const size_t alloc_sz = hdr_sz + num_nets * max_entry_sz + 3;
char *http = os_zalloc (alloc_sz);
if (!http)
@ -996,6 +1028,12 @@ static void on_scan_done (void *arg, STATUS status)
p += sizeof (entry_mid) -1;
p += c_sprintf (p, "%d", wn->rssi);
const char entry_chan[] = ",\"chan\":";
strcpy (p, entry_chan);
p += sizeof (entry_chan) -1;
p += c_sprintf (p, "%d", wn->channel);
*p++ = '}';
}
@ -1006,6 +1044,8 @@ static void on_scan_done (void *arg, STATUS status)
http[hdr_sz] = '['; /* Rewrite the \0 with the correct start of body */
notify_scan_listeners (http, hdr_sz + body_sz);
ENDUSER_SETUP_DEBUG(http + hdr_sz);
c_free (http);
return;
}
@ -1048,6 +1088,9 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
pbuf_free (p);
err_t ret = ERR_OK;
ENDUSER_SETUP_DEBUG(data);
if (c_strncmp(data, "GET ", 4) == 0)
{
if (c_strncmp(data + 4, "/ ", 2) == 0)
@ -1061,38 +1104,52 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
goto free_out; /* streaming now in progress */
}
}
else if (c_strncmp(data + 4, "/aplist ", 8) == 0)
else if (c_strncmp(data + 4, "/aplist", 7) == 0)
{
scan_listener_t *l = os_malloc (sizeof (scan_listener_t));
if (!l)
// Don't do an AP Scan while station is trying to connect to Wi-Fi
if (state->connecting == 0)
{
ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL);
}
bool already = (state->scan_listeners != NULL);
tcp_arg (http_client, l);
/* TODO: check if also need a tcp_err() cb, or if recv() is enough */
l->conn = http_client;
l->next = state->scan_listeners;
state->scan_listeners = l;
if (!already)
{
if (!wifi_station_scan(NULL, on_scan_done))
scan_listener_t *l = os_malloc (sizeof (scan_listener_t));
if (!l)
{
enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500));
deferred_close (l->conn);
l->conn = 0;
free_scan_listeners();
ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL);
}
bool already = (state->scan_listeners != NULL);
tcp_arg (http_client, l);
/* TODO: check if also need a tcp_err() cb, or if recv() is enough */
l->conn = http_client;
l->next = state->scan_listeners;
state->scan_listeners = l;
if (!already)
{
if (!wifi_station_scan(NULL, on_scan_done))
{
enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500));
deferred_close (l->conn);
l->conn = 0;
free_scan_listeners();
}
}
goto free_out; /* request queued */
}
else
{
// Return No Content status to the caller
enduser_setup_http_serve_header(http_client, http_header_204, LITLEN(http_header_204));
}
goto free_out; /* request queued */
}
else if (c_strncmp(data + 4, "/status ", 8) == 0)
else if (c_strncmp(data + 4, "/status.json", 12) == 0)
{
serve_status(http_client);
enduser_setup_serve_status_as_json(http_client);
}
else if (c_strncmp(data + 4, "/status", 7) == 0)
{
enduser_setup_serve_status(http_client);
}
else if (c_strncmp(data + 4, "/update?", 8) == 0)
{
switch (enduser_setup_http_handle_credentials(data, data_len))
@ -1101,14 +1158,29 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
enduser_setup_http_serve_header(http_client, http_header_302, LITLEN(http_header_302));
break;
case 1:
enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401));
enduser_setup_http_serve_header(http_client, http_header_400, LITLEN(http_header_400));
break;
default:
ENDUSER_SETUP_ERROR("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
break;
}
}
else if (c_strncmp(data + 4, "/generate_204 ", 14) == 0)
else if (c_strncmp(data + 4, "/setwifi?", 9) == 0)
{
switch (enduser_setup_http_handle_credentials(data, data_len))
{
case 0:
enduser_setup_serve_status_as_json(http_client);
break;
case 1:
enduser_setup_http_serve_header(http_client, http_header_400, LITLEN(http_header_400));
break;
default:
ENDUSER_SETUP_ERROR("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
break;
}
}
else if (c_strncmp(data + 4, "/generate_204", 13) == 0)
{
/* Convince Android devices that they have internet access to avoid pesky dialogues. */
enduser_setup_http_serve_header(http_client, http_header_204, LITLEN(http_header_204));
@ -1116,13 +1188,16 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
else
{
ENDUSER_SETUP_DEBUG("serving 404");
ENDUSER_SETUP_DEBUG(data + 4);
enduser_setup_http_serve_header(http_client, http_header_404, LITLEN(http_header_404));
}
}
else /* not GET */
else if (c_strncmp(data, "OPTIONS ", 8) == 0)
{
enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401));
enduser_setup_handle_OPTIONS(http_client, data, data_len);
}
else /* not GET or OPTIONS */
{
enduser_setup_http_serve_header(http_client, http_header_405, LITLEN(http_header_405));
}
deferred_close (http_client);
@ -1135,6 +1210,7 @@ free_out:
static err_t enduser_setup_http_connectcb(void *arg, struct tcp_pcb *pcb, err_t err)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_connectcb");
if (!state)
{
ENDUSER_SETUP_DEBUG("connect callback but no state?!");
@ -1208,7 +1284,7 @@ static void enduser_setup_http_stop(void)
static void enduser_setup_ap_stop(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_station_stop");
ENDUSER_SETUP_DEBUG("enduser_setup_ap_stop");
wifi_set_opmode(~SOFTAP_MODE & wifi_get_opmode());
}
@ -1234,15 +1310,47 @@ static void enduser_setup_ap_start(void)
cnf.ssid[ssid_name_len] = '_';
c_sprintf(cnf.ssid + ssid_name_len + 1, "%02X%02X%02X", mac[3], mac[4], mac[5]);
cnf.ssid_len = ssid_name_len + 7;
cnf.channel = 1;
cnf.channel = state == NULL? 1 : state->softAPchannel;
cnf.authmode = AUTH_OPEN;
cnf.ssid_hidden = 0;
cnf.max_connection = 5;
cnf.beacon_interval = 100;
wifi_set_opmode(STATIONAP_MODE);
wifi_softap_set_config(&cnf);
#if ENDUSER_SETUP_DEBUG_ENABLE
char debuginfo[100];
c_sprintf(debuginfo, "SSID: %s, CHAN: %d", cnf.ssid, cnf.channel);
ENDUSER_SETUP_DEBUG(debuginfo);
#endif
}
static void on_initial_scan_done (void *arg, STATUS status)
{
ENDUSER_SETUP_DEBUG("on_initial_scan_done");
if (!state)
{
return;
}
int8_t rssi = -100;
if (status == OK)
{
for (struct bss_info *wn = arg; wn; wn = wn->next.stqe_next)
{
if (wn->rssi > rssi)
{
state->softAPchannel = wn->channel;
rssi = wn->rssi;
}
}
}
enduser_setup_ap_start();
enduser_setup_check_station_start();
}
static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned short recv_len)
{
@ -1255,6 +1363,31 @@ static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned
uint32_t dns_reply_static_len = (uint32_t) sizeof(dns_header) + (uint32_t) sizeof(dns_body) + 2 + 4; // dns_id=2bytes, ip=4bytes
uint32_t dns_reply_len = dns_reply_static_len + qname_len;
#if ENDUSER_SETUP_DEBUG_ENABLE
char *qname = c_malloc(qname_len + 12);
if (qname != NULL)
{
c_sprintf(qname, "DNS QUERY = %s", &(recv_data[12]));
uint32_t p;
int i, j;
for(i=12;i<(int)strlen(qname);i++)
{
p=qname[i];
for(j=0;j<(int)p;j++)
{
qname[i]=qname[i+1];
i=i+1;
}
qname[i]='.';
}
qname[i-1]='\0';
ENDUSER_SETUP_DEBUG(qname);
c_free(qname);
}
#endif
uint8_t if_mode = wifi_get_opmode();
if ((if_mode & SOFTAP_MODE) == 0)
{
@ -1305,6 +1438,14 @@ static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Can't execute transmission.", ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND, ENDUSER_SETUP_ERR_FATAL);
}
else if (err == ESPCONN_MAXNUM)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Buffer full. Discarding...", ENDUSER_SETUP_ERR_MAX_NUMBER, ENDUSER_SETUP_ERR_NONFATAL);
}
else if (err == ESPCONN_IF)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Send UDP data failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
}
else if (err != 0)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. espconn_send failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
@ -1408,6 +1549,7 @@ static void enduser_setup_dns_stop(void)
static int enduser_setup_init(lua_State *L)
{
// Note: Normal to not see this debug message on first invocation because debug callback is set below
ENDUSER_SETUP_DEBUG("enduser_setup_init");
if (state != NULL)
@ -1452,12 +1594,19 @@ static int enduser_setup_init(lua_State *L)
{
lua_pushvalue (L, 3);
state->lua_dbg_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
ENDUSER_SETUP_DEBUG("enduser_setup_init: Debug callback has been defined");
}
else
{
state->lua_dbg_cb_ref = LUA_NOREF;
}
state->softAPchannel = 1;
state->success = 0;
state->callbackDone = 0;
state->lastStationStatus = 0;
state->connecting = 0;
return 0;
}
@ -1475,6 +1624,7 @@ static int enduser_setup_manual(lua_State *L)
static int enduser_setup_start(lua_State *L)
{
// Note: The debug callback is set in enduser_setup_init. It's normal to not see this debug message on first invocation.
ENDUSER_SETUP_DEBUG("enduser_setup_start");
if (!do_station_cfg_handle)
@ -1487,10 +1637,14 @@ static int enduser_setup_start(lua_State *L)
goto failed;
}
enduser_setup_check_station_start();
if (!manual)
{
enduser_setup_ap_start();
ENDUSER_SETUP_DEBUG("Performing AP Scan to identify likely AP's channel");
wifi_station_scan(NULL, on_initial_scan_done);
}
else
{
enduser_setup_check_station_start();
}
if(enduser_setup_dns_start())
@ -1529,6 +1683,12 @@ static int enduser_setup_stop(lua_State* L)
{
enduser_setup_ap_stop();
}
if (state->success && !state->callbackDone)
{
wifi_set_opmode(STATION_MODE | wifi_get_opmode());
wifi_station_connect();
enduser_setup_connected_callback();
}
enduser_setup_dns_stop();
enduser_setup_http_stop();
enduser_setup_free();

View File

@ -0,0 +1,315 @@
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>WiFi Login</title>
<style type=text/css>
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
font-family: sans-serif;
text-align: center;
background: #444d44;
}
#content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 320px;
height: 480px;
margin: auto;
}
input,
button,
select {
-webkit-appearance: none;
border-radius: 0;
}
fieldset {
border: 0;
box-shadow: 0 0 15px 1px rgba(0, 0, 0, .4);
box-sizing: border-box;
padding: 20px 30px;
background: #fff;
min-height: 320px;
margin: -1px;
}
input {
border: 1px solid #ccc;
margin-bottom: 10px;
width: 100%;
box-sizing: border-box;
color: #222;
font: 16px monospace;
padding: 15px;
}
select {
font: 16px monospace;
background-color: transparent;
padding: 15px;
}
button {
color: #fff;
border: 0;
border-radius: 3px;
cursor: pointer;
display: block;
font: 16px sans-serif;
text-decoration: none;
padding: 10px 5px;
background: #31b457;
width: 100%;
}
button:focus,
button:hover {
box-shadow: 0 0 0 2px #fff, 0 0 0 3px #31b457;
}
h3 {
font-size: 16px;
color: #666;
margin-bottom: 20px;
}
h4 {
color: #ccc;
padding: 10px;
}
.utility {
float: right;
clear: both;
max-width: 75%;
font-size: 13px;
color: #222;
margin: 10px 0;
padding: 5px 10px;
background: #ccc;
}
.utility:focus,
.utility:hover {
box-shadow: 0 0 0 2px #fff, 0 0 0 3px #ccc;
}
#dropdown,
#f2,
#f3,
#bk2 {
display: none;
}
#dropdown {
position: relative;
width: 100%;
overflow: auto;
height: 51px;
margin-bottom: 10px;
}
#aplist {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
bottom: 0;
border: 1px solid #ccc;
font-family: monospace;
padding: 10px 5px;
}
#arrow {
color: #888;
position: absolute;
right: 8px;
top: 15px;
}
#i {
text-align: justify;
}
</style>
</head>
<body>
<div id=content>
<fieldset>
<div id=deviceId></div>
<div id=f1>
<h3>Connect device to your Wi-Fi</h3>
<button id=networks type=button class=utility></button>
<div id=dropdown>
<span id=arrow>&#x25bc;</span>
<select id=aplist name=aplist></select>
</div>
<input id=ssid type=text autocorrect=off autocapitalize=none placeholder='Wi-Fi Name' />
<input id=wifi_password type=text autocorrect=off autocapitalize=none autocomplete=off placeholder=Password />
<button id=submit type=button>Save</button>
</div>
<div id=f2>
<h1>Success!</h1>
<div id=i>
<h3>Your device has successfully connected to the Wi-Fi network.</h3>
</div>
</div>
<div id=f3>
<h2>Trying...</h2>
<button id=bk2 type=button class='utility'>Go Back to Wi-Fi Setup</button>
</div>
</fieldset>
<h4 id='st'>Updating Status...</h4>
</div>
<script>
var $ = function (selector) { return document.querySelector(selector); };
var ab = $('#networks'), ap = $('#aplist');
var stopAll = false, ra, rs;
function show(f, y) {
if (y == null) y = f;
$(f).style.display = y == f ? 'block' : 'none';
}
function hide(f) {
$(f).style.display = 'none';
}
function to(cb, x) {
return setTimeout(cb, 1000 * x);
}
function refr() {
if (!stopAll)
fetch('/status.json?n=' + Math.random(), 'GET', newSt, 2);
}
function cur(f) {
show('#f1', f);
show('#f2', f);
show('#f3', f);
}
function newSt(s, d) {
clearTimeout(rs);
rs = to(refr, 3);
if (s != 200) {
$('#st').innerText = 'Awaiting Status (' + s + ')';
} else {
if (typeof d === 'string') {
d = JSON.parse(d);
}
$('#deviceId').innerText = d.deviceid;
var c = d.pairing;
var s = [
'Idle',
'Connecting...',
'Failed - wrong password',
'Failed - network not found',
'Failed',
'Wi-Fi successfully connected!'
][d.status];
$('#st').innerText = s;
if (d.status === 5) {
cur('#f2');
stopAll = true;
clearTimeout(ra);
} else if (d.status > 1) {
cur('#f1');
}
}
}
function submit() {
var url = '/setwifi?wifi_ssid=' + encodeURIComponent($('#ssid').value) + '&wifi_password=' + encodeURIComponent($('#wifi_password').value);
clearTimeout(rs);
fetch(url, 'GET', newSt, 2);
cur('#f3');
}
function fetch(url, method, callback, time_out) {
var xhr = new XMLHttpRequest();
xhr.onloadend = function () {
callback(xhr.status, xhr.responseText);
}
xhr.ontimeout = function () {
callback(-1, null);
}
xhr.open(method, url, true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.timeout = (time_out || 10) * 1000;
xhr.send();
}
function gotAp(s, json) {
var list;
if (s === 200 && json != null) {
if (typeof json === 'string' && json.length > 0) {
list = JSON.parse(json);
} else if (typeof json === 'object') {
list = json;
}
list.sort(function (a, b) {
return b.rssi - a.rssi;
});
var ops = '<option>Select a Network...</option>';
for (var i = 0; i < list.length; ++i) {
ops += '<option>' + list[i].ssid + '</option>';
}
ap.innerHTML = ops;
ab.disabled = false;
togAp(null, true);
ab.onclick = togAp;
} else {
ab.innerText = 'No networks found (' + s + ')';
ra = to(refrAp, 5);
}
}
function togAp(ev, force) {
if (!force || ap.style.display == 'block') {
hide('#dropdown');
show('#ssid');
ab.innerText = 'Scan for Networks';
ab.onclick = refrAp;
} else {
show('#dropdown');
hide('#ssid');
ab.innerText = 'Manual Entry';
}
}
function refrAp() {
ab.innerText = 'Searching for networks...';
ab.disabled = true;
ap.innerHTML = '<option disabled>Scanning...</option>';
if (!stopAll)
fetch('/aplist?n=' + Math.random(), 'GET', gotAp, 10);
}
window.onload = function() {
ab.innerText = 'Scan for Networks';
ab.onclick = refrAp;
$('#aplist').onchange = function () {
$('#ssid').value = $('#aplist').value;
};
$('#submit').onclick = submit;
$('#bk2').onclick = function () {
cur('#f1')
}
rs = to(refr, 0.5);
}
</script>
</body>
</html>

Binary file not shown.

View File

@ -0,0 +1,203 @@
static const char http_html_backup[] = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x95, 0x59,
0xfd, 0x72, 0xdb, 0x36, 0x12, 0xff, 0xbb, 0x37, 0x73, 0xef, 0xb0, 0x1e,
0xf7, 0x42, 0xb9, 0xa1, 0x64, 0x7d, 0xd8, 0x69, 0xc6, 0x12, 0xd5, 0xc9,
0xe5, 0x92, 0x26, 0x37, 0x49, 0xda, 0xa9, 0xdd, 0xe9, 0x75, 0x32, 0x99,
0x0e, 0x44, 0x82, 0x26, 0x62, 0x0a, 0xe0, 0x01, 0xa0, 0x65, 0x5d, 0xea,
0x77, 0x3f, 0x2c, 0x01, 0x5a, 0x86, 0x40, 0x87, 0xb5, 0x5a, 0x57, 0x04,
0xb0, 0xdf, 0xfb, 0xc3, 0xee, 0x52, 0x5d, 0x1c, 0xfc, 0xeb, 0xa7, 0x97,
0x17, 0xbf, 0xff, 0xfc, 0x0a, 0x0a, 0xbd, 0x2e, 0x97, 0x7f, 0xff, 0xdb,
0xc2, 0x7d, 0xe3, 0x13, 0x25, 0x99, 0x79, 0xfa, 0x66, 0xb1, 0xa6, 0x9a,
0x00, 0x27, 0x6b, 0x9a, 0x44, 0xd7, 0x8c, 0x6e, 0x2a, 0x21, 0x75, 0x04,
0xa9, 0xe0, 0x9a, 0x72, 0x9d, 0x44, 0x1b, 0x96, 0xe9, 0x22, 0xc9, 0xe8,
0x35, 0x4b, 0xe9, 0xb0, 0x59, 0xc4, 0xc0, 0x38, 0xd3, 0x8c, 0x94, 0x43,
0x95, 0x92, 0x92, 0x26, 0x93, 0xd1, 0x38, 0x6a, 0x04, 0x69, 0xa6, 0x4b,
0xba, 0xfc, 0x8d, 0xbd, 0x66, 0xf0, 0x4e, 0x5c, 0x32, 0xbe, 0x38, 0xb6,
0x3b, 0x78, 0xa6, 0xf4, 0xb6, 0xa4, 0xa0, 0xb7, 0x15, 0x4d, 0x34, 0xbd,
0xd1, 0xc7, 0xa9, 0x52, 0xb8, 0xff, 0xcd, 0x77, 0xf0, 0x05, 0xbf, 0xbe,
0x59, 0x13, 0x69, 0x38, 0xce, 0x60, 0x3c, 0x6f, 0x96, 0x15, 0xc9, 0x32,
0xc6, 0x2f, 0xdb, 0xf5, 0x2d, 0xfe, 0x07, 0xff, 0xd0, 0xfc, 0x18, 0x1f,
0x56, 0x22, 0xdb, 0x3a, 0xd6, 0x82, 0xb2, 0xcb, 0x42, 0x9f, 0xc1, 0x64,
0x3c, 0xfe, 0x87, 0xe5, 0xce, 0x8d, 0xf1, 0xc3, 0x9c, 0xac, 0x59, 0xb9,
0x3d, 0x03, 0x45, 0xb8, 0x1a, 0x2a, 0x2a, 0x59, 0x6e, 0x0f, 0x51, 0xfd,
0x90, 0x94, 0xec, 0xd2, 0x68, 0x4b, 0x29, 0xd7, 0x54, 0xda, 0xfd, 0x15,
0x49, 0xaf, 0x2e, 0xa5, 0xa8, 0x79, 0x76, 0x06, 0x87, 0x27, 0x27, 0x27,
0xd9, 0xc9, 0x89, 0xaf, 0xfb, 0xd0, 0xc5, 0xc4, 0xa9, 0xad, 0x84, 0x62,
0x9a, 0x09, 0x23, 0x85, 0xac, 0x94, 0x28, 0x6b, 0x4d, 0x9d, 0x7c, 0x51,
0xdd, 0xb9, 0x21, 0xad, 0x65, 0x6e, 0xb5, 0x12, 0x5a, 0x8b, 0xf5, 0xdd,
0xb2, 0xa4, 0xf9, 0xee, 0xac, 0x89, 0xec, 0x19, 0xcc, 0xa6, 0xe3, 0xea,
0x66, 0xee, 0xb9, 0x75, 0xf2, 0x1c, 0xb7, 0xbc, 0x20, 0x91, 0x5a, 0x0b,
0xdf, 0x36, 0xc6, 0xab, 0x5a, 0xdb, 0xc0, 0xd4, 0x46, 0x09, 0x6f, 0x1e,
0x15, 0x2d, 0x69, 0xda, 0x9a, 0x3b, 0xdc, 0xd0, 0xd5, 0x15, 0x33, 0x9e,
0x57, 0x15, 0x25, 0x92, 0xf0, 0x94, 0x9e, 0x01, 0x17, 0x9c, 0xb6, 0xa6,
0xc9, 0x8c, 0xca, 0xa1, 0x24, 0x19, 0xab, 0x55, 0x10, 0xf5, 0x9c, 0xd1,
0x32, 0x53, 0xd4, 0x89, 0x72, 0xc4, 0xf7, 0xdc, 0xba, 0x19, 0xaa, 0x82,
0x64, 0x62, 0x63, 0xb6, 0xcc, 0x3f, 0x93, 0xd3, 0xea, 0x06, 0x26, 0xe6,
0x4f, 0x5e, 0xae, 0xc8, 0x60, 0x1c, 0x83, 0xfd, 0x77, 0x74, 0x72, 0x74,
0x8f, 0x9e, 0xfd, 0xaf, 0x49, 0xaf, 0xd3, 0x6b, 0xb6, 0xf6, 0xf2, 0x8e,
0x71, 0x80, 0x19, 0x7a, 0x1e, 0x26, 0x27, 0xcf, 0x73, 0x17, 0x0f, 0xc6,
0x87, 0x36, 0x4c, 0x18, 0xb9, 0x20, 0x4c, 0x43, 0x63, 0x44, 0x18, 0xa6,
0x7d, 0x2f, 0xd0, 0x52, 0x93, 0x3f, 0x96, 0xc1, 0x61, 0x9a, 0xa6, 0xf7,
0x25, 0x0c, 0xdb, 0x84, 0x4d, 0x9c, 0x68, 0x97, 0x26, 0x07, 0xb5, 0x5e,
0x67, 0x52, 0x51, 0x0a, 0x69, 0xec, 0x9d, 0x4e, 0xa7, 0x3b, 0x5c, 0x1a,
0xe6, 0x67, 0x46, 0xe3, 0x5a, 0x70, 0xa1, 0x2a, 0x92, 0x52, 0xdf, 0x6f,
0x0c, 0x9e, 0x6f, 0xb3, 0x9f, 0xc5, 0x6e, 0x09, 0x7e, 0x84, 0x86, 0x4e,
0xaf, 0x36, 0x59, 0x36, 0x04, 0x92, 0x72, 0xdd, 0xa7, 0xc4, 0xa1, 0xc6,
0x29, 0xb1, 0xfc, 0x2e, 0xce, 0x41, 0xc2, 0x03, 0xb0, 0xcc, 0xaa, 0xd6,
0xdf, 0x5a, 0x2a, 0x64, 0xac, 0x04, 0xdb, 0x5d, 0xab, 0x8c, 0xa9, 0xaa,
0x24, 0x5b, 0x13, 0x9d, 0x52, 0xa4, 0x57, 0x41, 0x1c, 0x3a, 0x6f, 0x67,
0x46, 0x53, 0x21, 0x89, 0xbd, 0x5c, 0x0e, 0xa3, 0x9e, 0xf9, 0x88, 0x8d,
0xd3, 0x4e, 0x68, 0xcc, 0x26, 0xab, 0x93, 0xd3, 0xef, 0xc3, 0x5c, 0x75,
0x38, 0x7b, 0x96, 0x8b, 0xb4, 0x56, 0xf1, 0xbd, 0x8d, 0x42, 0x5c, 0x53,
0x09, 0x5f, 0x3a, 0x41, 0x3d, 0x86, 0xa9, 0x51, 0x8a, 0x21, 0x89, 0xed,
0x1a, 0xdd, 0xde, 0xe9, 0xf3, 0x6a, 0xd4, 0xec, 0x5e, 0xb2, 0x10, 0x1c,
0xd4, 0xfa, 0xea, 0x83, 0xe2, 0xd9, 0xb3, 0x67, 0x9d, 0x58, 0xb3, 0x30,
0xf6, 0xe4, 0x9d, 0x78, 0x79, 0x71, 0x30, 0xf5, 0x23, 0x12, 0x30, 0x8d,
0x6a, 0xcd, 0x4a, 0xa6, 0xb7, 0xad, 0x29, 0xa5, 0x20, 0x26, 0xe4, 0x4d,
0x3d, 0x72, 0x76, 0x94, 0x94, 0x48, 0xc4, 0xac, 0x2e, 0x5a, 0x3b, 0x6e,
0x86, 0x2e, 0x64, 0xdf, 0x9f, 0xda, 0x88, 0x79, 0x1e, 0xcc, 0xaa, 0x6e,
0x58, 0xb7, 0x1e, 0x58, 0x2b, 0x82, 0x02, 0xde, 0x14, 0x83, 0xee, 0x7b,
0xec, 0xfc, 0x08, 0x8d, 0x76, 0xa9, 0xf1, 0xb6, 0x1e, 0x9f, 0x9c, 0x50,
0xfe, 0x61, 0x26, 0x45, 0x65, 0xb8, 0x78, 0xdc, 0xac, 0xf2, 0xa9, 0xfb,
0x9e, 0xd9, 0xef, 0xd5, 0xd5, 0xd4, 0x29, 0x70, 0xa0, 0x75, 0xe8, 0xeb,
0x96, 0x11, 0xb4, 0x01, 0x49, 0x4b, 0xa2, 0xd9, 0x35, 0x7d, 0xa0, 0x52,
0xa0, 0xfd, 0x26, 0x0d, 0x1b, 0x57, 0xbe, 0xbd, 0x1a, 0x7f, 0xea, 0x2a,
0x55, 0x77, 0xe5, 0xf1, 0xf5, 0x93, 0xaa, 0x64, 0xaa, 0xa7, 0x09, 0x85,
0xda, 0xc3, 0x26, 0xa9, 0x45, 0x15, 0xb4, 0xa2, 0xa0, 0x4d, 0xf5, 0xd4,
0x49, 0xaf, 0xd1, 0x7a, 0x05, 0x29, 0xb8, 0xae, 0xa1, 0x1f, 0x52, 0x8a,
0xcd, 0x1e, 0xb2, 0x9f, 0x3f, 0x7f, 0x3e, 0xff, 0xaa, 0x5f, 0xae, 0xa1,
0x3e, 0xb7, 0xd2, 0x9c, 0x13, 0x93, 0x50, 0x38, 0x83, 0x2f, 0x41, 0xb3,
0xff, 0x5c, 0x2b, 0xcd, 0xf2, 0xed, 0x1d, 0xe5, 0xe2, 0xb8, 0x19, 0x4a,
0x70, 0x2a, 0x3a, 0x76, 0xc3, 0x90, 0x79, 0xc4, 0xc1, 0x62, 0x89, 0xa7,
0x19, 0xbb, 0x06, 0x96, 0x25, 0xae, 0xe9, 0x2f, 0x91, 0x6b, 0xe1, 0x1a,
0xa1, 0x5d, 0xb5, 0x24, 0x6e, 0x38, 0x7a, 0x9b, 0x2d, 0x17, 0xc7, 0x66,
0xc7, 0x3f, 0xcb, 0x27, 0xb8, 0xc6, 0x8d, 0x62, 0xb6, 0x7c, 0x29, 0x38,
0xc7, 0x5a, 0x6e, 0x19, 0x40, 0x0b, 0xd8, 0x8a, 0x5a, 0xc2, 0x6f, 0x6c,
0xf8, 0x9a, 0x19, 0x1b, 0x66, 0x2d, 0xa9, 0xad, 0x47, 0xc8, 0xce, 0xa9,
0xde, 0x08, 0x79, 0xa5, 0xec, 0xe4, 0xe4, 0xf6, 0xd3, 0x92, 0x28, 0x95,
0xb8, 0x8b, 0x61, 0x94, 0xda, 0x6d, 0xc7, 0xdc, 0x2a, 0x76, 0x30, 0xb5,
0xdb, 0xb8, 0x6f, 0x52, 0xd3, 0x88, 0x6c, 0xe2, 0xbe, 0x7c, 0x72, 0x78,
0x33, 0x3d, 0x5d, 0xa5, 0x73, 0x13, 0x84, 0x8a, 0xec, 0x88, 0x6c, 0xb3,
0x41, 0x32, 0x07, 0x33, 0x9c, 0x0b, 0xdd, 0xb3, 0xd1, 0x64, 0x8f, 0x1d,
0xb5, 0xf3, 0xd6, 0x3e, 0xdb, 0xce, 0x6a, 0xf8, 0x94, 0x62, 0xd9, 0x6e,
0xce, 0x6b, 0xb0, 0x9e, 0x0a, 0x29, 0x0d, 0x5b, 0x22, 0xf2, 0xdc, 0xae,
0x49, 0xc5, 0x34, 0x29, 0x4d, 0x59, 0x49, 0xf0, 0x7e, 0x41, 0x55, 0x92,
0x94, 0x16, 0xa2, 0xcc, 0xa8, 0x4c, 0xa2, 0x26, 0x18, 0xf0, 0xc1, 0xa8,
0x8d, 0xe0, 0x38, 0x90, 0xbe, 0x61, 0x39, 0xfb, 0xa3, 0x32, 0xee, 0x9b,
0xa8, 0x3c, 0x52, 0x8d, 0xa3, 0x59, 0x57, 0x25, 0xd5, 0x14, 0x89, 0x3c,
0xbd, 0x3f, 0xb7, 0x32, 0x8f, 0xc3, 0x24, 0xa8, 0x7a, 0xb5, 0x66, 0xfa,
0x7e, 0x0a, 0x96, 0xe7, 0xe4, 0x9a, 0xfa, 0x81, 0xef, 0xca, 0xfd, 0xf4,
0x2e, 0xf7, 0x93, 0xe5, 0x79, 0x9d, 0xa6, 0x54, 0xa9, 0x03, 0x93, 0xe7,
0xc9, 0x5e, 0xaa, 0x98, 0x5d, 0x5b, 0x8c, 0xfc, 0x8e, 0x88, 0x70, 0x00,
0x29, 0x88, 0x02, 0x65, 0xf9, 0xf2, 0xba, 0x2c, 0xb7, 0x90, 0x5a, 0x00,
0xd1, 0x0c, 0xb1, 0xa3, 0x0b, 0x0a, 0x36, 0x5a, 0x0e, 0x25, 0x23, 0xc4,
0x50, 0x98, 0x9c, 0x2e, 0xcb, 0xee, 0xc8, 0x8a, 0xe9, 0xf2, 0x42, 0x6e,
0xcd, 0x3d, 0x1d, 0x8d, 0x90, 0x7d, 0x1a, 0x7a, 0x8f, 0x55, 0x31, 0x44,
0x5f, 0xe4, 0xe0, 0x17, 0x2d, 0x7f, 0x14, 0xf0, 0x4f, 0x53, 0xd5, 0xd1,
0x22, 0x6b, 0xcd, 0x39, 0xd5, 0x75, 0xd5, 0x1d, 0x1c, 0x7c, 0xf2, 0xee,
0xd0, 0xa2, 0x38, 0x41, 0x1d, 0x91, 0xd2, 0xd1, 0xf2, 0xd7, 0x2a, 0x23,
0xda, 0x58, 0x02, 0xe7, 0x9a, 0xe8, 0x5a, 0x59, 0x83, 0x4e, 0x90, 0xec,
0x8e, 0x7d, 0xa1, 0x52, 0xc9, 0x2a, 0x64, 0x05, 0xf7, 0xb9, 0x26, 0x12,
0xbe, 0x85, 0x04, 0xf2, 0x9a, 0xa7, 0x58, 0x30, 0x60, 0x60, 0x31, 0x2a,
0xe4, 0x11, 0x7c, 0x01, 0x69, 0x4c, 0x91, 0x1c, 0x32, 0x91, 0xd6, 0x6b,
0xca, 0xf5, 0xe8, 0xbf, 0x35, 0x95, 0xdb, 0x73, 0x47, 0xb0, 0xa3, 0x9c,
0xc3, 0xed, 0xdc, 0x17, 0x49, 0x56, 0x46, 0xe6, 0xb7, 0x83, 0xe8, 0xb0,
0xbd, 0x80, 0xd1, 0x51, 0x0c, 0xa4, 0x72, 0x9b, 0xf6, 0x3e, 0x44, 0x47,
0x7b, 0x4c, 0x4a, 0x8b, 0xea, 0x45, 0x59, 0xa2, 0x35, 0xa4, 0x54, 0x34,
0x06, 0x49, 0xcc, 0x9f, 0x32, 0x54, 0x77, 0x74, 0x3b, 0x3b, 0x55, 0x21,
0x36, 0x83, 0x3c, 0x86, 0xad, 0xb1, 0xd3, 0x1d, 0xbb, 0x0f, 0xcb, 0x61,
0xb0, 0x85, 0x24, 0x01, 0x6e, 0x72, 0x7e, 0x04, 0x5b, 0x94, 0x37, 0xf7,
0x48, 0x8c, 0x11, 0xf9, 0xd1, 0xa8, 0xa9, 0x5e, 0x23, 0xdb, 0xaa, 0x90,
0xa8, 0xe1, 0xc9, 0xe1, 0x07, 0x88, 0x9a, 0x51, 0x2b, 0x82, 0x33, 0x88,
0x10, 0xf8, 0xd1, 0x3d, 0xe6, 0xdb, 0x0e, 0x4b, 0x0a, 0x96, 0xd1, 0x41,
0x8e, 0x66, 0xf4, 0xea, 0xf8, 0x6b, 0x02, 0xb5, 0x18, 0xa4, 0xab, 0x18,
0x6e, 0x02, 0x91, 0x2e, 0x1d, 0x8a, 0xea, 0x0b, 0xb6, 0xa6, 0xa2, 0xd6,
0x48, 0x87, 0x2d, 0x69, 0x0c, 0xdf, 0x19, 0xf2, 0x1e, 0xb1, 0x92, 0xe6,
0x72, 0xd0, 0x19, 0xad, 0x03, 0x17, 0xf9, 0x23, 0xef, 0xc8, 0x72, 0x53,
0x9d, 0x16, 0x83, 0xe8, 0x58, 0x59, 0x4c, 0x7d, 0x56, 0x82, 0xff, 0xc0,
0x93, 0x08, 0x9e, 0xc2, 0x7b, 0xa2, 0x8b, 0x91, 0x24, 0x3c, 0x13, 0xeb,
0x81, 0x49, 0x6e, 0xf4, 0xe3, 0xab, 0x8b, 0x28, 0x06, 0x4e, 0x37, 0xe7,
0x3a, 0x86, 0x69, 0x9f, 0x31, 0x69, 0x2d, 0xc3, 0x98, 0xd9, 0xa4, 0x46,
0x87, 0xf9, 0xc4, 0x48, 0xca, 0x9d, 0x88, 0xe0, 0x70, 0xfa, 0xb5, 0xc3,
0x19, 0x1e, 0xf6, 0x28, 0xb7, 0x46, 0x0e, 0x54, 0x0c, 0x59, 0x60, 0x41,
0x33, 0xd6, 0xb5, 0xd1, 0x95, 0x6a, 0x5f, 0x8d, 0x54, 0x90, 0x60, 0x7e,
0x30, 0x96, 0x31, 0xcc, 0xf0, 0x38, 0x0c, 0xa7, 0x82, 0x83, 0xc4, 0xcc,
0xa1, 0xe3, 0x40, 0x38, 0x7e, 0x10, 0xff, 0x88, 0xfd, 0x11, 0xe3, 0x9c,
0xca, 0x0b, 0x7a, 0xa3, 0x11, 0x16, 0x2f, 0x36, 0x84, 0xdd, 0xbb, 0xbb,
0x30, 0xc0, 0x08, 0x2b, 0xf3, 0x17, 0x1d, 0x45, 0x7b, 0x26, 0xdc, 0x02,
0x2d, 0x15, 0x0d, 0x44, 0x3b, 0xdd, 0x58, 0x69, 0x44, 0x0e, 0x99, 0xc1,
0xb2, 0x11, 0xab, 0xb4, 0x34, 0x42, 0xa3, 0xc0, 0x10, 0xf7, 0x41, 0x32,
0xf8, 0xf7, 0xf9, 0x4f, 0x1f, 0x46, 0x15, 0x91, 0x8a, 0x0e, 0xb2, 0x7d,
0x77, 0x5d, 0x00, 0xbb, 0xbd, 0x68, 0xdb, 0xf6, 0x9e, 0x2f, 0xd9, 0xc8,
0x1e, 0xb0, 0x2c, 0x08, 0x4e, 0x7b, 0xd1, 0xd3, 0x86, 0xac, 0x22, 0x0c,
0xad, 0x7b, 0x90, 0x0a, 0x43, 0xfd, 0xb1, 0xdb, 0xee, 0xe8, 0x6d, 0x56,
0xd2, 0x28, 0x7e, 0xe0, 0xd0, 0x8d, 0x09, 0xb6, 0x28, 0x3f, 0x48, 0xf5,
0x9a, 0xb0, 0x92, 0x66, 0x30, 0x84, 0x8d, 0x14, 0x26, 0xf0, 0x6d, 0x53,
0xec, 0xa7, 0x77, 0x25, 0x0d, 0xb8, 0xd0, 0x90, 0x8b, 0x9a, 0xf7, 0xb1,
0x3c, 0x78, 0x6c, 0xcb, 0x7d, 0x77, 0x83, 0x3a, 0x88, 0x42, 0xa6, 0x4f,
0x1f, 0xb3, 0x91, 0xbd, 0x87, 0x9f, 0xda, 0xa0, 0xf5, 0x43, 0x4b, 0x39,
0xd2, 0x10, 0x2a, 0xad, 0xb0, 0x06, 0x2a, 0xa7, 0x0f, 0x61, 0x04, 0x6f,
0xaa, 0xbd, 0x75, 0x01, 0x38, 0xdc, 0x67, 0x57, 0xb6, 0xb5, 0xac, 0x69,
0x40, 0xd4, 0x75, 0xaf, 0x48, 0x87, 0x2c, 0x07, 0x6c, 0xdf, 0xb4, 0x25,
0x4c, 0xfa, 0x0c, 0x9b, 0x44, 0x0f, 0xa0, 0xb6, 0x7b, 0xd9, 0x59, 0x10,
0xec, 0x84, 0x12, 0x16, 0x47, 0x84, 0x61, 0x2d, 0xd1, 0x35, 0x53, 0x01,
0xa9, 0xc6, 0xe9, 0xe9, 0x87, 0x66, 0x84, 0xc2, 0x29, 0xad, 0xa9, 0x82,
0x94, 0xa7, 0x22, 0xa3, 0xbf, 0xfe, 0xf2, 0xf6, 0xa5, 0x58, 0x57, 0x82,
0x53, 0xae, 0x07, 0x98, 0x06, 0x3c, 0x37, 0x89, 0xb8, 0x26, 0x65, 0x4d,
0x8f, 0xf0, 0x1a, 0x3f, 0xf1, 0x26, 0xaf, 0xaf, 0xb1, 0x7a, 0x84, 0x77,
0x32, 0xe6, 0xbd, 0x65, 0x2a, 0xac, 0xda, 0xc6, 0xf2, 0xee, 0xba, 0x1c,
0x06, 0x71, 0x16, 0xf5, 0xd5, 0xcc, 0x7b, 0x22, 0xd7, 0x54, 0x17, 0x22,
0x8b, 0x21, 0x25, 0x65, 0x89, 0x2f, 0xa4, 0x31, 0x68, 0x63, 0xc9, 0x1f,
0xc6, 0x94, 0xce, 0x00, 0xde, 0x14, 0x12, 0x12, 0x34, 0x00, 0xfe, 0xf3,
0xfe, 0xdd, 0x1b, 0xad, 0xab, 0x5f, 0xa8, 0x99, 0x23, 0x94, 0x1e, 0xec,
0xd9, 0x82, 0x84, 0x23, 0xc1, 0x4b, 0x41, 0x32, 0xca, 0x33, 0x6f, 0x26,
0xe9, 0xc4, 0x40, 0xab, 0x7e, 0x80, 0x7c, 0x16, 0x2f, 0x71, 0x23, 0x43,
0x52, 0x65, 0xe2, 0xa9, 0x28, 0xde, 0x80, 0x7d, 0x1d, 0xb7, 0x5d, 0x2a,
0xb5, 0x0d, 0xe4, 0x23, 0x54, 0x0e, 0x27, 0xb1, 0x1d, 0x2f, 0xfe, 0x82,
0xf8, 0x8a, 0xf2, 0x41, 0x1b, 0x32, 0x8c, 0x5f, 0x73, 0x4d, 0xba, 0x7c,
0x57, 0x54, 0xbb, 0xd0, 0xbc, 0xa1, 0x24, 0xa3, 0x26, 0x31, 0x2f, 0xd2,
0x94, 0x56, 0xda, 0xa4, 0x2f, 0x22, 0x55, 0x55, 0xb2, 0x94, 0xa0, 0x6d,
0xc7, 0xd8, 0x84, 0xa3, 0x2e, 0x01, 0x3b, 0x3f, 0x06, 0x6d, 0x46, 0xe0,
0xcf, 0x3f, 0x61, 0x62, 0x3a, 0xd1, 0x77, 0xcd, 0x98, 0xd0, 0xa9, 0x94,
0x67, 0x83, 0xbe, 0xe4, 0x5f, 0x0a, 0xfd, 0xa2, 0xc2, 0x86, 0x89, 0xaa,
0x3b, 0x93, 0x8c, 0x03, 0xdd, 0xbc, 0xab, 0x15, 0x26, 0x49, 0xd3, 0x0b,
0xe1, 0xc9, 0x93, 0x86, 0x19, 0x0e, 0xdc, 0x5c, 0xd6, 0xd3, 0xc0, 0x90,
0xd6, 0xeb, 0x61, 0xad, 0x80, 0x51, 0x49, 0xf9, 0xa5, 0x2e, 0x60, 0x09,
0xe3, 0x07, 0x2b, 0x03, 0x1a, 0xe3, 0x77, 0x36, 0xe4, 0xec, 0xa9, 0x39,
0x81, 0x62, 0xb1, 0xfa, 0x4c, 0x53, 0x1d, 0xf5, 0x69, 0x41, 0x86, 0x9e,
0xb6, 0xe9, 0xb1, 0x8c, 0x94, 0x90, 0x7a, 0xb0, 0x03, 0x1a, 0x89, 0x61,
0xe5, 0xeb, 0x08, 0x27, 0xbe, 0xd5, 0x48, 0x2a, 0xc5, 0x60, 0x08, 0xa4,
0x79, 0xe8, 0x52, 0x17, 0x78, 0xe7, 0x12, 0x23, 0x2a, 0x85, 0xe5, 0x6b,
0x21, 0x2a, 0x54, 0xb7, 0xb4, 0xc3, 0x3b, 0x10, 0xf8, 0x60, 0xbb, 0x58,
0xf3, 0x96, 0xe0, 0xce, 0xa2, 0x0e, 0x11, 0xb9, 0x90, 0x30, 0x40, 0x39,
0xcc, 0x48, 0x19, 0xcf, 0xcd, 0xd7, 0xc2, 0x7a, 0x61, 0xd3, 0x30, 0x87,
0xa7, 0x4f, 0xd9, 0x83, 0xd6, 0xa3, 0xee, 0xa7, 0xf7, 0x94, 0x63, 0xd5,
0x43, 0xe6, 0x8f, 0xec, 0xd3, 0x08, 0x2b, 0x24, 0x16, 0xc6, 0xaf, 0x6a,
0xbf, 0x0d, 0xb7, 0x48, 0x65, 0xdb, 0xdb, 0x9b, 0x8b, 0xf7, 0xef, 0x20,
0x41, 0x15, 0x1d, 0x7c, 0x64, 0x85, 0x33, 0x37, 0x59, 0x95, 0x34, 0x6b,
0xdf, 0x26, 0x3a, 0xa8, 0xb4, 0xb8, 0x34, 0xa8, 0x46, 0x34, 0x76, 0xdf,
0x49, 0x27, 0x49, 0xf0, 0xb4, 0x64, 0xe9, 0x15, 0x24, 0x96, 0xa1, 0x67,
0x1c, 0xf3, 0x58, 0xbd, 0x19, 0xef, 0x83, 0x68, 0x47, 0x07, 0x65, 0xe7,
0x86, 0xae, 0x29, 0xcf, 0x4b, 0x3e, 0xd9, 0x0d, 0x9b, 0x2f, 0xaa, 0xd8,
0xf4, 0xe8, 0xf9, 0x63, 0xba, 0x9a, 0xf3, 0x8f, 0x5e, 0xc7, 0x98, 0xc5,
0x94, 0x76, 0x8f, 0xfe, 0xcd, 0x11, 0x56, 0x09, 0x52, 0xed, 0xbf, 0xad,
0x24, 0xed, 0x9b, 0x50, 0x57, 0x82, 0xed, 0x6b, 0x4f, 0xd4, 0xfe, 0x9c,
0xe7, 0x17, 0x24, 0x7f, 0x2a, 0x77, 0xdd, 0x70, 0xde, 0x1f, 0xa2, 0xf3,
0x94, 0x70, 0x34, 0xb6, 0x45, 0xa7, 0x8a, 0xfa, 0x52, 0x82, 0xb1, 0xe9,
0xc9, 0x49, 0x60, 0x50, 0x60, 0x74, 0xe8, 0xd8, 0x63, 0x8c, 0x7e, 0x4f,
0x78, 0x4d, 0x4a, 0x78, 0xc5, 0xb5, 0xdc, 0x46, 0x8f, 0xca, 0x90, 0xb3,
0x3e, 0xec, 0x35, 0x41, 0x5c, 0x28, 0x91, 0x69, 0x61, 0xca, 0x20, 0x06,
0xe7, 0x0e, 0x45, 0xe6, 0xee, 0x3a, 0x7d, 0xdd, 0xc0, 0x0f, 0xe7, 0xb1,
0xe0, 0xfa, 0xb4, 0x57, 0x13, 0x5a, 0xae, 0x25, 0xa6, 0x80, 0x1b, 0x4d,
0x61, 0x61, 0x78, 0xf4, 0x3b, 0xa3, 0x7d, 0xcd, 0xff, 0xda, 0xeb, 0xa2,
0x6d, 0x2c, 0xf8, 0x0a, 0xfb, 0x40, 0x07, 0xda, 0x30, 0xc3, 0xb0, 0x71,
0x53, 0xc1, 0xbd, 0xfe, 0xec, 0x85, 0xec, 0xf1, 0x40, 0xea, 0x81, 0x90,
0xf7, 0x23, 0x05, 0x52, 0x16, 0x84, 0x5f, 0xd2, 0xbe, 0xf1, 0x60, 0x7f,
0xee, 0xf3, 0x7f, 0xec, 0x70, 0x9b, 0x9e, 0x9a, 0xdb, 0x40, 0xa9, 0x1d,
0x44, 0xad, 0x52, 0x67, 0x9e, 0xdd, 0x0a, 0x28, 0x57, 0x57, 0x53, 0x8f,
0xac, 0xd3, 0xb4, 0x70, 0x54, 0xf6, 0xd4, 0xe3, 0xa2, 0xfb, 0xcd, 0x76,
0x3c, 0x3a, 0x0d, 0x32, 0xb2, 0x38, 0xbe, 0xfb, 0xf5, 0x68, 0x71, 0xec,
0x7e, 0xe6, 0xc5, 0xc7, 0xe6, 0x7f, 0x89, 0xff, 0x1f, 0x01, 0x02, 0x3f,
0xe6, 0x2a, 0x1f, 0x00, 0x00
};
unsigned int http_html_backup_len = 2393;

View File

@ -0,0 +1,7 @@
#!/bin/sh
# Uses zopfli for better gzip compression
# sudo apt-get install zopfli
zopfli --gzip ./enduser_setup.html
xxd -i ./enduser_setup.html.gz | sed 's/unsigned char/static const char/; s/__enduser_setup_html_gz/http_html_backup/' > http_html_backup.def

View File

@ -7,10 +7,29 @@ This module provides a simple way of configuring ESP8266 chips without using a s
![enduser setup config dialog](../../img/enduser-setup.jpg "enduser setup config dialog")
After running [`enduser_setup.start()`](#enduser_setupstart) a portal like the above can be accessed through a wireless network called SetupGadget_XXXXXX. The portal is used to submit the credentials for the WiFi of the enduser.
After an IP address has been successfully obtained this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called.
After running [`enduser_setup.start()`](#enduser_setupstart), a wireless network named "SetupGadget_XXXXXX" will start. Connect to that SSID and then navigate to the root
of any website (e.g., `http://example.com/` will work, but do not use `.local` domains because it will fail on iOS). A web page similar to the picture above will load, allowing the
end user to provide their Wi-Fi information.
After an IP address has been successfully obtained, then this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called. There is a 10-second delay before
teardown to allow connected clients to obtain a last status message while the SoftAP is still active.
Alternative HTML can be served by placing a file called `enduser_setup.html` on the filesystem. Everything needed by the web page must be included in this one file. This file will be kept
in RAM, so keep it as small as possible. The file can be gzip'd ahead of time to reduce the size (i.e., using `gzip -n` or `zopfli`), and when served, the End User Setup module will add
the appropriate `Content-Encoding` header to the response. *Note: Even if gzipped, the file still needs to be named `enduser_setup.html`.*
The following HTTP endpoints exist:
|Endpoint|Description|
|--------|-----------|
|/|Returns HTML for the web page. Will return the contents of `enduser_setup.html` if it exists on the filesystem, otherwise will return a page embedded into the firmware image.|
|/aplist|Forces the ESP8266 to perform a site survey across all channels, reporting access points that it can find. Return payload is a JSON array: `[{"ssid":"foobar","rssi":-36,"chan":3}]`|
|/generate_204|Returns a HTTP 204 status (expected by certain Android clients during Wi-Fi connectivity checks)|
|/status|Returns plaintext status description, used by the web page|
|/status.json|Returns a JSON payload containing the ESP8266's chip id in hexadecimal format and the status code: 0=Idle, 1=Connecting, 2=Wrong Password, 3=Network not Found, 4=Failed, 5=Success|
|/setwifi|Endpoint intended for services to use for setting the wifi credentials. Identical to `/update` except returns the same payload as `/status.json` instead of redirecting to `/`.|
|/update|Form submission target. Example: `http://example.com/update?wifi_ssid=foobar&wifi_password=CorrectHorseBatteryStaple`. Must be a GET request. Will redirect to `/` when complete. |
Alternative HTML can be served by placing a file called `enduser_setup.html` in the filesystem. This file will be kept in RAM, so keep it as small as possible.
## enduser_setup.manual()
@ -53,7 +72,7 @@ Starts the captive portal.
#### Parameters
- `onConnected()` callback will be fired when an IP-address has been obtained, just before the enduser_setup module will terminate itself
- `onError()` callback will be fired if an error is encountered. `err_num` is a number describing the error, and `string` contains a description of the error.
- `onDebug()` callback is disabled by default. It is intended to be used to find internal issues in the module. `string` contains a description of what is going on.
- `onDebug()` callback is disabled by default (controlled by `#define ENDUSER_SETUP_DEBUG_ENABLE` in `enduser_setup.c`). It is intended to be used to find internal issues in the module. `string` contains a description of what is going on.
#### Returns
`nil`