2015-09-19 21:06:56 +02:00
/*
* Copyright 2015 Robert Foss . All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* - Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* - Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the
* distribution .
* - Neither the name of the copyright holders nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT ,
* INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE .
*
* @ author Robert Foss < dev @ robertfoss . se >
2016-01-28 06:52:09 +01:00
*
* Additions & fixes : Johny Mattsson < jmattsson @ dius . com . au >
2015-09-19 21:06:56 +02:00
*/
2015-12-16 06:04:58 +01:00
# include "module.h"
2015-09-02 14:41:27 +02:00
# include "lauxlib.h"
Initial pass at switching to RTOS SDK.
This compiles, links, and starts the RTOS without crashing and burning.
Lua environment does not yet start due to the different task architecture.
Known pain points:
- task implementation needs to be rewritten for RTOS (next up on my TODO)
- secure espconn does not exist, all secure espconn stuff has been #if 0'd
- lwip now built from within the RTOS SDK, but does not appear to include
MDNS support. Investigation needed.
- there is no access to FRC1 NMI, not sure if we ever actually used that
however. Also #if 0'd out for now.
- new timing constraints introduced by the RTOS, all use of ets_delay_us()
and os_delay_us() needs to be reviewed (the tsl2561 driver in particular).
- even more confusion with ets_ vs os_ vs c_ vs non-prefixed versions.
In the long run everything should be switched to non-prefixed versions.
- system_set_os_print() not available, needs to be reimplemented
- all the RTOS rodata is loaded into RAM, as it apparently uses some
constants while the flash isn't mapped, so our exception handler can't
work its magic. This should be narrowed down to the minimum possible
at some point.
- with each task having its own stack in RTOS, we probably need change
flash-page buffers from the stack to the heap in a bunch of places.
A single, shared, page buffer *might* be possible if we limit ourselves
to running NodeMCU in a single task.
- there's a ton of junk in the sdk-overrides now; over time the core code
should be updated to not need those shims
2016-05-24 07:05:01 +02:00
# include "lwip/tcp.h"
# include "lwip/pbuf.h"
2016-03-08 22:55:53 +01:00
# include "lmem.h"
2015-09-02 14:41:27 +02:00
# include "platform.h"
2016-05-26 10:36:20 +02:00
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
2016-01-20 09:37:03 +01:00
# include "ctype.h"
2015-09-02 14:41:27 +02:00
# include "espconn.h"
# include "flash_fs.h"
2016-03-01 07:54:10 +01:00
# include "task/task.h"
Initial pass at switching to RTOS SDK.
This compiles, links, and starts the RTOS without crashing and burning.
Lua environment does not yet start due to the different task architecture.
Known pain points:
- task implementation needs to be rewritten for RTOS (next up on my TODO)
- secure espconn does not exist, all secure espconn stuff has been #if 0'd
- lwip now built from within the RTOS SDK, but does not appear to include
MDNS support. Investigation needed.
- there is no access to FRC1 NMI, not sure if we ever actually used that
however. Also #if 0'd out for now.
- new timing constraints introduced by the RTOS, all use of ets_delay_us()
and os_delay_us() needs to be reviewed (the tsl2561 driver in particular).
- even more confusion with ets_ vs os_ vs c_ vs non-prefixed versions.
In the long run everything should be switched to non-prefixed versions.
- system_set_os_print() not available, needs to be reimplemented
- all the RTOS rodata is loaded into RAM, as it apparently uses some
constants while the flash isn't mapped, so our exception handler can't
work its magic. This should be narrowed down to the minimum possible
at some point.
- with each task having its own stack in RTOS, we probably need change
flash-page buffers from the stack to the heap in a bunch of places.
A single, shared, page buffer *might* be possible if we limit ourselves
to running NodeMCU in a single task.
- there's a ton of junk in the sdk-overrides now; over time the core code
should be updated to not need those shims
2016-05-24 07:05:01 +02:00
# include "esp_wifi.h"
2016-05-26 10:36:20 +02:00
# include "esp_sta.h"
# include "esp_softap.h"
2015-09-02 14:41:27 +02:00
# define MIN(x, y) (((x) < (y)) ? (x) : (y))
2016-01-28 06:52:09 +01:00
# define LITLEN(strliteral) (sizeof (strliteral) -1)
2015-09-02 14:41:27 +02:00
2015-09-19 21:06:56 +02:00
# define ENDUSER_SETUP_ERR_FATAL (1 << 0)
# define ENDUSER_SETUP_ERR_NONFATAL (1 << 1)
# define ENDUSER_SETUP_ERR_NO_RETURN (1 << 2)
# define ENDUSER_SETUP_ERR_OUT_OF_MEMORY 1
# define ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND 2
# define ENDUSER_SETUP_ERR_UNKOWN_ERROR 3
# define ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN 4
2015-09-02 14:41:27 +02:00
/**
* DNS Response Packet :
*
* | DNS ID - 16 bits |
* | dns_header |
* | QNAME |
* | dns_body |
* | ip - 32 bits |
*
2015-09-16 14:56:44 +02:00
* DNS Header Part | FLAGS | | Q COUNT | | A CNT | | AUTH CNT | | ADD CNT | */
static const char dns_header [ ] = { 0x80 , 0x00 , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 } ;
/* DNS Query Part | Q TYPE | | Q CLASS| */
static const char dns_body [ ] = { 0x00 , 0x01 , 0x00 , 0x01 ,
/* DNS Answer Part |LBL OFFS| | TYPE | | CLASS | | TTL | | RD LEN | */
0xC0 , 0x0C , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 , 0x78 , 0x00 , 0x04 } ;
2016-03-29 17:25:05 +02:00
static const char http_html_filename [ ] = " enduser_setup.html " ;
2016-01-28 06:52:09 +01:00
static const char http_header_200 [ ] = " HTTP/1.1 200 OK \r \n Cache-control:no-cache \r \n Content-Type: text/html \r \n " ; /* Note single \r\n here! */
2016-03-08 22:55:53 +01:00
static const char http_header_204 [ ] = " HTTP/1.1 204 No Content \r \n \r \n " ;
2016-01-28 06:52:09 +01:00
static const char http_header_302 [ ] = " HTTP/1.1 302 Moved \r \n Location: / \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 " ;
/* 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 + 6l rjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq / ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV / TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk + BQrpoP + HLHUrJkuta40C6LP5GvBv + Hqo10ATxxFrTPvNdPr7XwgQud6RvQN / sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4 + HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP + YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst / UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys + 8 OW1pbB7 / e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8 / NggRRanDkBrANn8yHlEQOn / MmwoQfQF7xgmKDnv520bS / pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3 + dW1EIXIu8Qg5Qta4Zlv7drUCoWe8 / 8 MXzaEwux7ESE9h6qnHj3mIO0 / D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC ) }
body { font - family : arial , verdana }
2016-03-08 22:55:53 +01:00
div { position : absolute ; margin : auto ; top : - 150 px ; right : 0 ; bottom : 0 ; left : 0 ; width : 320 px ; height : 304 px }
2016-01-28 06:52:09 +01:00
form { width : 320 px ; text - align : center ; position : relative }
form fieldset { background : # fff ; border : 0 none ; border - radius : 5 px ; box - shadow : 0 0 15 px 1 px rgba ( 0 , 0 , 0 , .4 ) ; padding : 20 px 30 px ; box - sizing : border - box }
form input { padding : 15 px ; border : 1 px solid # ccc ; border - radius : 3 px ; margin - bottom : 10 px ; width : 100 % ; box - sizing : border - box ; font - family : montserrat ; color : # 2 C3E50 ; font - size : 13 px }
form . action - button { border : 0 none ; border - radius : 3 px ; cursor : pointer ; }
# msform .submit:focus,form .action-button:hover{box-shadow:0 0 0 2px #fff,0 0 0 3px #27AE60;}
2016-03-08 22:55:53 +01:00
# formFrame{display: none;}
# aplist{display: block;}
2016-01-28 06:52:09 +01:00
select { width : 100 % ; margin - bottom : 20 px ; padding : 10 px 5 px ; border : 1 px solid # ccc ; display : none ; }
. fs - title { font - size : 15 px ; text - transform : uppercase ; color : # 2 C3E50 ; margin - bottom : 10 px }
. fs - subtitle { font - weight : 400 ; font - size : 13 px ; color : # 666 ; margin - bottom : 20 px }
. fs - status { font - weight : 400 ; font - size : 13 px ; color : # 666 ; margin - bottom : 10 px ; padding - top : 20 px ; border - top : 1 px solid # ccc }
. submit { width : 100 px ; background : # 27 AE60 ; color : # fff ; font - weight : 700 ; margin : 10 px 5 px ; padding : 10 px 5 px ; }
< / style >
< / head >
< body >
< div >
2016-03-08 22:55:53 +01:00
< form id = " credentialsForm " method = " get " action = " /update " target = " formFrame " >
2016-01-28 06:52:09 +01:00
< fieldset >
2016-03-08 22:55:53 +01:00
< iframe id = " formFrame " src = " " name = " formFrame " > < / iframe > < ! - - Used to submit data , needed to prevent re - direction after submission - - >
2016-01-28 06:52:09 +01:00
< h2 class = " fs-title " > WiFi Login < / h2 >
< h3 class = " fs-subtitle " > Connect gadget to your WiFi network < / h3 >
2016-03-08 22:55:53 +01:00
< 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 >
2016-01-28 06:52:09 +01:00
< / select >
< input name = " wifi_password " placeholder = " Password " type = " password " >
2016-03-08 22:55:53 +01:00
< input type = submit name = save class = " action-button submit " value = " Save " >
2016-01-28 06:52:09 +01:00
< h3 class = " fs-status " > Status : < span id = " status " > Updating . . . < / span > < / h3 >
< / fieldset >
2016-03-08 22:55:53 +01:00
< h3 id = " dbg " > < / h3 >
2016-01-28 06:52:09 +01:00
< / form >
< / div >
< script >
function fetch ( url , method , callback )
{
var xhr = new XMLHttpRequest ( ) ;
xhr . onreadystatechange = check_ready ;
function check_ready ( )
{
if ( xhr . readyState = = = 4 )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
callback ( xhr . status = = = 200 ? xhr . responseText : null ) ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
}
xhr . open ( method , url , true ) ;
xhr . send ( ) ;
}
2016-03-08 22:55:53 +01:00
2016-01-28 06:52:09 +01:00
function new_status ( stat )
{
if ( stat )
{
var e = document . getElementById ( " status " ) ;
e . innerHTML = stat ;
}
}
2015-09-02 14:41:27 +02:00
2016-01-28 06:52:09 +01:00
2016-03-08 22:55:53 +01:00
function new_status_repeat ( stat )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
new_status ( stat ) ;
setTimeout ( refresh_status , 750 ) ;
2016-01-28 06:52:09 +01:00
}
2016-03-08 22:55:53 +01:00
function new_ap_list ( json )
2016-01-28 06:52:09 +01:00
{
if ( json )
{
var list = JSON . parse ( json ) ;
2016-03-08 22:55:53 +01:00
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 " ) ;
2016-01-28 06:52:09 +01:00
sel . innerHTML = " " ;
2016-03-08 22:55:53 +01:00
sel . setAttribute ( " size " , Math . max ( Math . min ( 3 , list . length ) , 1 ) ) ;
sel . removeAttribute ( " disabled " ) ;
2016-01-28 06:52:09 +01:00
for ( var i = 0 ; i < ssids . length ; + + i )
{
var o = document . createElement ( " option " ) ;
o . innerHTML = ssids [ i ] ;
2016-03-08 22:55:53 +01:00
sel . options . add ( o ) ;
2016-01-28 06:52:09 +01:00
}
2016-03-08 22:55:53 +01:00
sel . style . display = ' block ' ;
2016-01-28 06:52:09 +01:00
}
}
2016-03-08 22:55:53 +01:00
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 ) ;
}
2016-03-30 16:57:30 +02:00
function set_ssid_field ( ) {
var sel = document . getElementById ( " aplist " ) ;
document . getElementById ( " wifi_ssid " ) . value = sel . value ;
}
2016-03-08 22:55:53 +01:00
window . onload = function ( )
2016-01-28 06:52:09 +01:00
{
refresh_status ( ) ;
refresh_ap_list ( ) ;
2016-03-30 16:57:30 +02:00
document . getElementById ( " aplist " ) . onclick = set_ssid_field ;
2016-04-01 03:26:22 +02:00
document . getElementById ( " aplist " ) . onchange = set_ssid_field ;
2016-03-08 22:55:53 +01:00
document . getElementById ( " credentialsForm " ) . addEventListener ( " submit " , function ( ) {
fetch ( ' / status ' , ' GET ' , new_status ) ;
} ) ;
2016-01-28 06:52:09 +01:00
}
< / script >
< / body >
< / html >
# endif
static const char http_html_backup [ ] =
2016-04-01 03:26:22 +02:00
" <!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.in
2016-01-28 06:52:09 +01:00
2016-03-24 09:25:49 +01:00
2016-01-28 06:52:09 +01:00
typedef struct scan_listener
{
2016-03-24 09:25:49 +01:00
struct tcp_pcb * conn ;
2016-01-28 06:52:09 +01:00
struct scan_listener * next ;
} scan_listener_t ;
2015-09-19 21:06:56 +02:00
typedef struct
{
struct espconn * espconn_dns_udp ;
2016-03-24 09:25:49 +01:00
struct tcp_pcb * http_pcb ;
2015-09-19 21:06:56 +02:00
char * http_payload_data ;
uint32_t http_payload_len ;
os_timer_t check_station_timer ;
2016-03-08 22:55:53 +01:00
os_timer_t shutdown_timer ;
2015-09-19 21:06:56 +02:00
int lua_connected_cb_ref ;
int lua_err_cb_ref ;
int lua_dbg_cb_ref ;
2016-01-28 06:52:09 +01:00
scan_listener_t * scan_listeners ;
2015-09-19 21:06:56 +02:00
} enduser_setup_state_t ;
static enduser_setup_state_t * state ;
2016-01-28 06:52:09 +01:00
static bool manual = false ;
2016-03-01 07:54:10 +01:00
static task_handle_t do_station_cfg_handle ;
2015-09-02 14:41:27 +02:00
2016-03-08 22:55:53 +01:00
static int enduser_setup_manual ( lua_State * L ) ;
2015-09-12 18:07:53 +02:00
static int enduser_setup_start ( lua_State * L ) ;
static int enduser_setup_stop ( lua_State * L ) ;
2016-03-08 22:55:53 +01:00
static void enduser_setup_stop_callback ( void * ptr ) ;
2015-09-19 21:06:56 +02:00
static void enduser_setup_station_start ( void ) ;
2015-09-12 18:07:53 +02:00
static void enduser_setup_ap_start ( void ) ;
static void enduser_setup_ap_stop ( void ) ;
2016-01-20 09:37:03 +01:00
static void enduser_setup_check_station ( void * p ) ;
2016-03-08 22:55:53 +01:00
static void enduser_setup_debug ( int line , const char * str ) ;
2015-09-19 21:06:56 +02:00
# define ENDUSER_SETUP_DEBUG_ENABLE 0
# if ENDUSER_SETUP_DEBUG_ENABLE
2016-03-08 22:55:53 +01:00
# define ENDUSER_SETUP_DEBUG(str) enduser_setup_debug(__LINE__, str)
2015-09-19 21:06:56 +02:00
# else
2016-03-08 22:55:53 +01:00
# define ENDUSER_SETUP_DEBUG(str) do {} while(0)
2015-09-19 21:06:56 +02:00
# endif
2016-01-28 06:52:09 +01:00
# define ENDUSER_SETUP_ERROR(str, err, err_severity) \
do { \
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( str ) ; \
2016-01-28 06:52:09 +01:00
if ( err_severity & ENDUSER_SETUP_ERR_FATAL ) enduser_setup_stop ( lua_getstate ( ) ) ; \
2016-03-08 22:55:53 +01:00
enduser_setup_error ( __LINE__ , str , err ) ; \
2016-01-28 06:52:09 +01:00
if ( ! ( err_severity & ENDUSER_SETUP_ERR_NO_RETURN ) ) \
return err ; \
} while ( 0 )
2015-09-19 21:06:56 +02:00
2016-03-08 22:55:53 +01:00
2016-01-28 06:52:09 +01:00
# define ENDUSER_SETUP_ERROR_VOID(str, err, err_severity) \
do { \
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( str ) ; \
2016-01-28 06:52:09 +01:00
if ( err_severity & ENDUSER_SETUP_ERR_FATAL ) enduser_setup_stop ( lua_getstate ( ) ) ; \
2016-03-08 22:55:53 +01:00
enduser_setup_error ( __LINE__ , str , err ) ; \
2016-01-28 06:52:09 +01:00
if ( ! ( err_severity & ENDUSER_SETUP_ERR_NO_RETURN ) ) \
return ; \
} while ( 0 )
2015-09-19 21:06:56 +02:00
2016-03-08 22:55:53 +01:00
static void enduser_setup_debug ( int line , const char * str )
2015-09-19 21:06:56 +02:00
{
2016-03-08 22:55:53 +01:00
lua_State * L = lua_getstate ( ) ;
if ( state ! = NULL & & state - > lua_dbg_cb_ref ! = LUA_NOREF )
{
lua_rawgeti ( L , LUA_REGISTRYINDEX , state - > lua_dbg_cb_ref ) ;
lua_pushfstring ( L , " %d: \t %s " , line , str ) ;
lua_call ( L , 1 , 0 ) ;
}
}
static void enduser_setup_error ( int line , const char * str , int err )
{
ENDUSER_SETUP_DEBUG ( " enduser_setup_error " ) ;
2015-09-19 21:06:56 +02:00
2016-03-08 22:55:53 +01:00
lua_State * L = lua_getstate ( ) ;
if ( state ! = NULL & & state - > lua_err_cb_ref ! = LUA_NOREF )
2015-09-19 21:06:56 +02:00
{
lua_rawgeti ( L , LUA_REGISTRYINDEX , state - > lua_err_cb_ref ) ;
lua_pushnumber ( L , err ) ;
2016-03-08 22:55:53 +01:00
lua_pushfstring ( L , " %d: \t %s " , line , str ) ;
2015-09-19 21:06:56 +02:00
lua_call ( L , 2 , 0 ) ;
}
}
2016-03-08 22:55:53 +01:00
static void enduser_setup_connected_callback ( )
2015-09-19 21:06:56 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_connected_callback " ) ;
2015-09-19 21:06:56 +02:00
2016-03-08 22:55:53 +01:00
lua_State * L = lua_getstate ( ) ;
if ( state ! = NULL & & state - > lua_connected_cb_ref ! = LUA_NOREF )
2015-09-19 21:06:56 +02:00
{
lua_rawgeti ( L , LUA_REGISTRYINDEX , state - > lua_connected_cb_ref ) ;
lua_call ( L , 0 , 0 ) ;
}
}
2015-09-02 14:41:27 +02:00
2015-09-12 18:07:53 +02:00
static void enduser_setup_check_station_start ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_check_station_start " ) ;
2015-09-19 21:06:56 +02:00
os_timer_setfn ( & ( state - > check_station_timer ) , enduser_setup_check_station , NULL ) ;
os_timer_arm ( & ( state - > check_station_timer ) , 1 * 1000 , TRUE ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_check_station_stop ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_check_station_stop " ) ;
2015-09-02 14:41:27 +02:00
2016-03-01 08:33:11 +01:00
if ( state ! = NULL )
2016-03-08 22:55:53 +01:00
{
2016-03-01 08:33:11 +01:00
os_timer_disarm ( & ( state - > check_station_timer ) ) ;
2016-03-08 22:55:53 +01:00
}
2015-09-02 14:41:27 +02:00
}
/**
* Check Station
*
* Check that we ' ve successfully entered station mode .
*/
2016-01-20 09:37:03 +01:00
static void enduser_setup_check_station ( void * p )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_check_station " ) ;
2016-01-20 09:37:03 +01:00
( void ) p ;
2015-09-02 14:41:27 +02:00
struct ip_info ip ;
2016-05-26 10:36:20 +02:00
memset ( & ip , 0 , sizeof ( struct ip_info ) ) ;
2015-09-02 14:41:27 +02:00
wifi_get_ip_info ( STATION_IF , & ip ) ;
int i ;
char has_ip = 0 ;
for ( i = 0 ; i < sizeof ( struct ip_info ) ; + + i )
{
has_ip | = ( ( char * ) & ip ) [ i ] ;
}
2015-09-16 14:56:44 +02:00
2015-09-02 14:41:27 +02:00
if ( has_ip = = 0 )
{
return ;
}
2015-09-16 14:56:44 +02:00
2016-03-08 22:55:53 +01:00
enduser_setup_check_station_stop ( ) ;
enduser_setup_connected_callback ( ) ;
/* Trigger shutdown, but allow time for HTTP client to fetch last status. */
if ( ! manual )
{
os_timer_setfn ( & ( state - > shutdown_timer ) , enduser_setup_stop_callback , NULL ) ;
os_timer_arm ( & ( state - > shutdown_timer ) , 10 * 1000 , FALSE ) ;
}
2015-09-02 14:41:27 +02:00
}
2016-03-29 08:27:55 +02:00
/* --- Connection closing handling ----------------------------------------- */
/* It is far more memory efficient to let the other end close the connection
* first and respond to that , than us initiating the closing . The latter
* seems to leave the pcb in a fin_wait state for a long time , which can
* starve us of memory over time .
*
* By instead using the poll function to schedule a hard abort a few seconds
* from now we achieve a deadline close . The downside is a ( very ) slight
* risk of dropping the connection early , but in this application that ' s
* hidden by the retries on the JavaScript side anyway .
*/
/* Callback on timeout to hard-close a connection */
static err_t force_abort ( void * arg , struct tcp_pcb * pcb )
{
( void ) arg ;
tcp_poll ( pcb , 0 , 0 ) ;
tcp_abort ( pcb ) ;
return ERR_ABRT ;
}
/* 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 )
{
( void ) arg ; ( void ) err ;
if ( p ) /* server sent us data, just ACK and move on */
{
tcp_recved ( pcb , p - > tot_len ) ;
pbuf_free ( p ) ;
}
else /* hey, remote end closed, we can do a soft close safely, yay! */
{
tcp_recv ( pcb , 0 ) ;
tcp_poll ( pcb , 0 , 0 ) ;
tcp_close ( pcb ) ;
}
return ERR_OK ;
}
/* Set up a deferred close of a connection, as discussed above. */
static inline void deferred_close ( struct tcp_pcb * pcb )
{
tcp_poll ( pcb , force_abort , 15 ) ; /* ~3sec from now */
tcp_recv ( pcb , handle_remote_close ) ;
tcp_sent ( pcb , 0 ) ;
}
/* Convenience function to queue up a close-after-send. */
static err_t close_once_sent ( void * arg , struct tcp_pcb * pcb , u16_t len )
{
( void ) arg ; ( void ) len ;
deferred_close ( pcb ) ;
return ERR_OK ;
}
/* ------------------------------------------------------------------------- */
2015-09-02 14:41:27 +02:00
/**
* Search String
*
* Search string for first occurance of any char in srch_str .
*
* @ return - 1 iff no occurance of char was found .
*/
2015-09-12 18:07:53 +02:00
static int enduser_setup_srch_str ( const char * str , const char * srch_str )
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
char * found = strpbrk ( str , srch_str ) ;
if ( ! found )
2016-03-08 22:55:53 +01:00
{
2015-09-02 14:41:27 +02:00
return - 1 ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
else
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
return found - str ;
2016-03-08 22:55:53 +01:00
}
2015-09-02 14:41:27 +02:00
}
/**
* Load HTTP Payload
*
* @ return - 0 iff payload loaded successfully
* 1 iff backup html was loaded
* 2 iff out of memory
*/
2015-09-12 18:07:53 +02:00
static int enduser_setup_http_load_payload ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_load_payload " ) ;
2015-09-02 14:41:27 +02:00
int f = fs_open ( http_html_filename , fs_mode2flag ( " r " ) ) ;
int err = fs_seek ( f , 0 , FS_SEEK_END ) ;
int file_len = ( int ) fs_tell ( f ) ;
int err2 = fs_seek ( f , 0 , FS_SEEK_SET ) ;
2015-09-19 21:06:56 +02:00
2016-01-28 06:52:09 +01:00
const char cl_hdr [ ] = " Content-length:%5d \r \n \r \n " ;
const size_t cl_len = LITLEN ( cl_hdr ) + 3 ; /* room to expand %4d */
2015-09-02 14:41:27 +02:00
if ( f = = 0 | | err = = - 1 | | err2 = = - 1 )
{
2016-03-29 17:25:05 +02:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_load_payload unable to load file enduser_setup.html, loading backup HTML. " ) ;
2015-09-02 14:41:27 +02:00
2016-01-28 06:52:09 +01:00
int payload_len = LITLEN ( http_header_200 ) + cl_len + LITLEN ( http_html_backup ) ;
2015-09-19 21:06:56 +02:00
state - > http_payload_len = payload_len ;
2016-05-26 10:36:20 +02:00
state - > http_payload_data = ( char * ) malloc ( payload_len ) ;
2015-09-19 21:06:56 +02:00
if ( state - > http_payload_data = = NULL )
2015-09-02 14:41:27 +02:00
{
return 2 ;
}
2015-09-19 21:06:56 +02:00
2015-09-02 14:41:27 +02:00
int offset = 0 ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( state - > http_payload_data [ offset ] ) , & ( http_header_200 ) , LITLEN ( http_header_200 ) ) ;
2016-01-28 06:52:09 +01:00
offset + = LITLEN ( http_header_200 ) ;
2016-05-26 10:36:20 +02:00
offset + = sprintf ( state - > http_payload_data + offset , cl_hdr , LITLEN ( http_html_backup ) ) ;
memcpy ( & ( state - > http_payload_data [ offset ] ) , & ( http_html_backup ) , LITLEN ( http_html_backup ) ) ;
2015-09-02 14:41:27 +02:00
return 1 ;
}
2015-09-19 21:06:56 +02:00
2016-01-28 06:52:09 +01:00
int payload_len = LITLEN ( http_header_200 ) + cl_len + file_len ;
2015-09-19 21:06:56 +02:00
state - > http_payload_len = payload_len ;
2016-05-26 10:36:20 +02:00
state - > http_payload_data = ( char * ) malloc ( payload_len ) ;
2015-09-19 21:06:56 +02:00
if ( state - > http_payload_data = = NULL )
2015-09-02 14:41:27 +02:00
{
return 2 ;
}
2015-09-19 21:06:56 +02:00
2015-09-02 14:41:27 +02:00
int offset = 0 ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( state - > http_payload_data [ offset ] ) , & ( http_header_200 ) , LITLEN ( http_header_200 ) ) ;
2016-01-28 06:52:09 +01:00
offset + = LITLEN ( http_header_200 ) ;
2016-05-26 10:36:20 +02:00
offset + = sprintf ( state - > http_payload_data + offset , cl_hdr , file_len ) ;
2015-09-19 21:06:56 +02:00
fs_read ( f , & ( state - > http_payload_data [ offset ] ) , file_len ) ;
2015-09-02 14:41:27 +02:00
return 0 ;
}
/**
* De - escape URL data
*
* Parse escaped and form encoded data of request .
2016-03-08 22:55:53 +01:00
*
* @ return - return 0 iff the HTTP parameter is decoded into a valid string .
2015-09-02 14:41:27 +02:00
*/
2016-03-08 22:55:53 +01:00
static int enduser_setup_http_urldecode ( char * dst , const char * src , int src_len , int dst_len )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_urldecode " ) ;
char * dst_start = dst ;
2016-03-31 08:46:03 +02:00
char * dst_last = dst + dst_len - 1 ; /* -1 to reserve space for last \0 */
2015-09-02 14:41:27 +02:00
char a , b ;
int i ;
2016-03-31 08:46:03 +02:00
for ( i = 0 ; i < src_len & & * src & & dst < dst_last ; + + i )
2015-09-02 14:41:27 +02:00
{
if ( ( * src = = ' % ' ) & & ( ( a = src [ 1 ] ) & & ( b = src [ 2 ] ) ) & & ( isxdigit ( a ) & & isxdigit ( b ) ) )
{
if ( a > = ' a ' )
{
a - = ' a ' - ' A ' ;
}
if ( a > = ' A ' )
{
a - = ( ' A ' - 10 ) ;
}
else
{
a - = ' 0 ' ;
}
if ( b > = ' a ' )
{
b - = ' a ' - ' A ' ;
}
if ( b > = ' A ' )
{
b - = ( ' A ' - 10 ) ;
}
else
{
b - = ' 0 ' ;
}
* dst + + = 16 * a + b ;
src + = 3 ;
i + = 2 ;
} else {
char c = * src + + ;
if ( c = = ' + ' )
{
c = ' ' ;
}
* dst + + = c ;
}
}
* dst + + = ' \0 ' ;
2016-03-31 08:46:03 +02:00
return ( i < src_len ) ; /* did we fail to process all the input? */
2015-09-02 14:41:27 +02:00
}
2016-03-01 07:54:10 +01:00
/**
* Task to do the actual station configuration .
* This config * cannot * be done in the network receive callback or serious
* issues like memory corruption occur .
*/
2016-05-24 10:07:27 +02:00
static void do_station_cfg ( task_param_t param , task_prio_t prio )
2016-03-01 07:54:10 +01:00
{
struct station_config * cnf = ( struct station_config * ) param ;
( void ) prio ;
/* Best-effort disconnect-reconfig-reconnect. If the device is currently
* connected , the disconnect will work but the connect will report failure
* ( though it will actually start connecting ) . If the devices is not
* connected , the disconnect may fail but the connect will succeed . A
* solid head - in - the - sand approach seems to be the best tradeoff on
* functionality - vs - code - size .
* TODO : maybe use an error callback to at least report if the set config
* call fails .
*/
wifi_station_disconnect ( ) ;
wifi_station_set_config ( cnf ) ;
wifi_station_connect ( ) ;
2016-03-08 22:55:53 +01:00
luaM_free ( lua_getstate ( ) , cnf ) ;
2016-03-01 07:54:10 +01:00
}
2015-09-02 14:41:27 +02:00
/**
* Handle HTTP Credentials
*
* @ return - return 0 iff credentials are found and handled successfully
* return 1 iff credentials aren ' t found
* return 2 iff an error occured
*/
2015-09-12 18:07:53 +02:00
static int enduser_setup_http_handle_credentials ( char * data , unsigned short data_len )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_handle_credentials " ) ;
2015-09-02 14:41:27 +02:00
2015-10-01 20:22:45 +02:00
char * name_str = ( char * ) ( ( uint32_t ) strstr ( & ( data [ 6 ] ) , " wifi_ssid= " ) ) ;
char * pwd_str = ( char * ) ( ( uint32_t ) strstr ( & ( data [ 6 ] ) , " wifi_password= " ) ) ;
2015-09-02 14:41:27 +02:00
if ( name_str = = NULL | | pwd_str = = NULL )
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " Password or SSID string not found " ) ;
2015-09-02 14:41:27 +02:00
return 1 ;
}
2016-01-28 06:52:09 +01:00
int name_field_len = LITLEN ( " wifi_ssid= " ) ;
int pwd_field_len = LITLEN ( " wifi_password= " ) ;
2015-09-02 14:41:27 +02:00
char * name_str_start = name_str + name_field_len ;
char * pwd_str_start = pwd_str + pwd_field_len ;
int name_str_len = enduser_setup_srch_str ( name_str_start , " & " ) ;
int pwd_str_len = enduser_setup_srch_str ( pwd_str_start , " & " ) ;
2016-03-08 22:55:53 +01:00
if ( name_str_len = = - 1 | | pwd_str_len = = - 1 )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " Password or SSID HTTP paramter divider not found " ) ;
2015-09-02 14:41:27 +02:00
return 1 ;
}
2015-09-19 21:06:56 +02:00
2015-09-02 14:41:27 +02:00
2016-03-08 22:55:53 +01:00
struct station_config * cnf = luaM_malloc ( lua_getstate ( ) , sizeof ( struct station_config ) ) ;
2016-05-26 10:36:20 +02:00
memset ( cnf , 0 , sizeof ( struct station_config ) ) ;
2015-09-02 14:41:27 +02:00
2016-03-08 22:55:53 +01:00
int err ;
2016-03-31 08:46:03 +02:00
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 ) ) ;
2016-03-08 22:55:53 +01:00
if ( err ! = 0 )
{
ENDUSER_SETUP_DEBUG ( " Unable to decode HTTP parameter to valid password or SSID " ) ;
return 1 ;
}
ENDUSER_SETUP_DEBUG ( " " ) ;
ENDUSER_SETUP_DEBUG ( " WiFi Credentials Stored " ) ;
ENDUSER_SETUP_DEBUG ( " ----------------------- " ) ;
ENDUSER_SETUP_DEBUG ( " name: " ) ;
ENDUSER_SETUP_DEBUG ( cnf - > ssid ) ;
ENDUSER_SETUP_DEBUG ( " pass: " ) ;
ENDUSER_SETUP_DEBUG ( cnf - > password ) ;
ENDUSER_SETUP_DEBUG ( " ----------------------- " ) ;
ENDUSER_SETUP_DEBUG ( " " ) ;
task_post_medium ( do_station_cfg_handle , ( task_param_t ) cnf ) ;
2015-09-02 14:41:27 +02:00
return 0 ;
}
/**
* Serve HTML
2015-09-19 21:06:56 +02:00
*
2015-09-02 14:41:27 +02:00
* @ return - return 0 iff html was served successfully
*/
2016-03-24 09:25:49 +01:00
static int enduser_setup_http_serve_header ( struct tcp_pcb * http_client , const char * header , uint32_t header_len )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_serve_header " ) ;
2015-09-19 21:06:56 +02:00
2016-03-24 09:25:49 +01:00
err_t err = tcp_write ( http_client , header , header_len , TCP_WRITE_FLAG_COPY ) ;
if ( err ! = ERR_OK )
2015-09-02 14:41:27 +02:00
{
2016-03-29 08:27:55 +02:00
deferred_close ( http_client ) ;
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_serve_header failed on tcp_write " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_NONFATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
return 0 ;
}
static err_t streamout_sent ( void * arg , struct tcp_pcb * pcb , u16_t len )
{
( void ) len ;
unsigned offs = ( unsigned ) arg ;
if ( ! state | | ! state - > http_payload_data )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
tcp_abort ( pcb ) ;
return ERR_ABRT ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
unsigned wanted_len = state - > http_payload_len - offs ;
unsigned buf_free = tcp_sndbuf ( pcb ) ;
if ( buf_free < wanted_len )
wanted_len = buf_free ;
/* no-copy write */
err_t err = tcp_write ( pcb , state - > http_payload_data + offs , wanted_len , 0 ) ;
if ( err ! = ERR_OK )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_DEBUG ( " streaming out html failed " ) ;
tcp_abort ( pcb ) ;
return ERR_ABRT ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
2016-03-24 09:25:49 +01:00
offs + = wanted_len ;
if ( offs > = state - > http_payload_len )
{
tcp_sent ( pcb , 0 ) ;
2016-03-29 08:27:55 +02:00
deferred_close ( pcb ) ;
2016-03-24 09:25:49 +01:00
}
else
tcp_arg ( pcb , ( void * ) offs ) ;
return ERR_OK ;
2015-09-02 14:41:27 +02:00
}
/**
* Serve HTML
2015-09-16 14:56:44 +02:00
*
2015-09-02 14:41:27 +02:00
* @ return - return 0 iff html was served successfully
*/
2016-03-24 09:25:49 +01:00
static int enduser_setup_http_serve_html ( struct tcp_pcb * http_client )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_serve_html " ) ;
2015-09-19 21:06:56 +02:00
if ( state - > http_payload_data = = NULL )
2015-09-02 14:41:27 +02:00
{
enduser_setup_http_load_payload ( ) ;
}
2015-09-16 14:56:44 +02:00
2016-03-24 09:25:49 +01:00
unsigned chunklen = tcp_sndbuf ( http_client ) ;
tcp_arg ( http_client , ( void * ) chunklen ) ;
tcp_recv ( http_client , 0 ) ; /* avoid confusion about the tcp_arg */
tcp_sent ( http_client , streamout_sent ) ;
/* Begin the no-copy stream-out here */
err_t err = tcp_write ( http_client , state - > http_payload_data , chunklen , 0 ) ;
if ( err ! = 0 )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_serve_html failed. tcp_write failed " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_NONFATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-16 14:56:44 +02:00
2015-09-02 14:41:27 +02:00
return 0 ;
}
2016-03-24 09:25:49 +01:00
static void serve_status ( struct tcp_pcb * conn )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_serve_status " ) ;
2016-01-28 06:52:09 +01:00
const char fmt [ ] =
" HTTP/1.1 200 OK \r \n "
2016-03-29 08:27:55 +02:00
" Cache-control:no-cache \r \n "
" Connection:close \r \n "
" Content-type:text/plain \r \n "
2016-01-28 06:52:09 +01:00
" Content-length: %d \r \n "
" \r \n "
2016-03-01 07:55:45 +01:00
" %s%s " ;
2016-01-28 06:52:09 +01:00
const char * state [ ] =
{
2016-03-08 22:55:53 +01:00
" Idle. " ,
" Connecting to \" %s \" . " ,
" Failed to connect to \" %s \" - Wrong password. " ,
" Failed to connect to \" %s \" - Network not found. " ,
" Failed to connect. " ,
" Connected to \" %s \" . "
2016-01-28 06:52:09 +01:00
} ;
2016-03-08 22:55:53 +01:00
const size_t num_states = sizeof ( state ) / sizeof ( state [ 0 ] ) ;
uint8_t curr_state = wifi_station_get_connect_status ( ) ;
if ( curr_state < num_states )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
switch ( curr_state )
{
case STATION_CONNECTING :
case STATION_WRONG_PASSWORD :
case STATION_NO_AP_FOUND :
case STATION_GOT_IP :
{
const char * s = state [ curr_state ] ;
struct station_config config ;
wifi_station_get_config ( & config ) ;
config . ssid [ 31 ] = ' \0 ' ;
2016-05-26 10:36:20 +02:00
int state_len = strlen ( s ) ;
int ssid_len = strlen ( config . ssid ) ;
2016-03-08 22:55:53 +01:00
int status_len = state_len + ssid_len + 1 ;
char status_buf [ status_len ] ;
memset ( status_buf , 0 , status_len ) ;
2016-05-26 10:36:20 +02:00
status_len = sprintf ( status_buf , s , config . ssid ) ;
2016-03-08 22:55:53 +01:00
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 ] ;
memset ( buf , 0 , buf_len ) ;
2016-05-26 10:36:20 +02:00
int output_len = sprintf ( buf , fmt , status_len , status_buf ) ;
2016-03-08 22:55:53 +01:00
enduser_setup_http_serve_header ( conn , buf , output_len ) ;
}
break ;
/* Handle non-formatted strings */
default :
{
const char * s = state [ curr_state ] ;
2016-05-26 10:36:20 +02:00
int status_len = strlen ( s ) ;
2016-03-08 22:55:53 +01:00
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 ] ;
memset ( buf , 0 , buf_len ) ;
2016-05-26 10:36:20 +02:00
int output_len = sprintf ( buf , fmt , status_len , s ) ;
2016-03-08 22:55:53 +01:00
enduser_setup_http_serve_header ( conn , buf , output_len ) ;
}
break ;
}
2016-03-01 07:55:45 +01:00
}
2016-03-08 22:55:53 +01:00
else
2016-03-01 07:55:45 +01:00
{
2016-03-08 22:55:53 +01:00
enduser_setup_http_serve_header ( conn , http_header_500 , LITLEN ( http_header_500 ) ) ;
2016-01-28 06:52:09 +01:00
}
}
2015-09-02 14:41:27 +02:00
2016-01-28 06:52:09 +01:00
/* --- WiFi AP scanning support -------------------------------------------- */
static void free_scan_listeners ( void )
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
if ( ! state | | ! state - > scan_listeners )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
return ;
2016-03-08 22:55:53 +01:00
}
2015-09-02 14:41:27 +02:00
2016-01-28 06:52:09 +01:00
scan_listener_t * l = state - > scan_listeners , * next = 0 ;
while ( l )
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
next = l - > next ;
2016-05-26 10:36:20 +02:00
free ( l ) ;
2016-01-28 06:52:09 +01:00
l = next ;
}
state - > scan_listeners = 0 ;
}
2016-03-24 09:25:49 +01:00
static void remove_scan_listener ( scan_listener_t * l )
2016-01-28 06:52:09 +01:00
{
2016-03-24 09:25:49 +01:00
if ( state )
2016-01-28 06:52:09 +01:00
{
scan_listener_t * * sl = & state - > scan_listeners ;
while ( * sl )
2015-10-01 20:22:45 +02:00
{
2016-01-28 06:52:09 +01:00
/* Remove any and all references to the closed conn from the scan list */
2016-03-24 09:25:49 +01:00
if ( * sl = = l )
2016-01-28 06:52:09 +01:00
{
* sl = l - > next ;
2016-05-26 10:36:20 +02:00
free ( l ) ;
2016-01-28 06:52:09 +01:00
/* No early exit to guard against multi-entry on list */
}
else
sl = & ( * sl ) - > next ;
2015-10-01 20:22:45 +02:00
}
2016-01-28 06:52:09 +01:00
}
}
2016-03-08 22:55:53 +01:00
2016-01-28 06:52:09 +01:00
static char * escape_ssid ( char * dst , const char * src )
{
for ( int i = 0 ; i < 32 & & src [ i ] ; + + i )
{
if ( src [ i ] = = ' \\ ' | | src [ i ] = = ' " ' )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
* dst + + = ' \\ ' ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
* dst + + = src [ i ] ;
}
return dst ;
}
static void notify_scan_listeners ( const char * payload , size_t sz )
{
if ( ! state )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
return ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
for ( scan_listener_t * l = state - > scan_listeners ; l ; l = l - > next )
{
2016-03-24 09:25:49 +01:00
if ( tcp_write ( l - > conn , payload , sz , TCP_WRITE_FLAG_COPY ) ! = ERR_OK )
2016-03-08 22:55:53 +01:00
{
ENDUSER_SETUP_DEBUG ( " failed to send wifi list " ) ;
2016-03-29 08:27:55 +02:00
tcp_abort ( l - > conn ) ;
2016-03-08 22:55:53 +01:00
}
2016-03-29 08:27:55 +02:00
else
tcp_sent ( l - > conn , close_once_sent ) ; /* TODO: time-out sends? */
2016-03-24 09:25:49 +01:00
l - > conn = 0 ;
2016-01-28 06:52:09 +01:00
}
free_scan_listeners ( ) ;
}
2015-09-16 14:56:44 +02:00
2016-01-28 06:52:09 +01:00
static void on_scan_done ( void * arg , STATUS status )
{
if ( ! state | | ! state - > scan_listeners )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
return ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
if ( status = = OK )
{
unsigned num_nets = 0 ;
for ( struct bss_info * wn = arg ; wn ; wn = wn - > next . stqe_next )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
+ + num_nets ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
const char header_fmt [ ] =
" HTTP/1.1 200 OK \r \n "
" Connection:close \r \n "
2016-03-29 08:27:55 +02:00
" Cache-control:no-cache \r \n "
2016-01-28 06:52:09 +01:00
" 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 alloc_sz = hdr_sz + num_nets * max_entry_sz + 3 ;
2016-05-26 10:36:20 +02:00
char * http = zalloc ( alloc_sz ) ;
2016-01-28 06:52:09 +01:00
if ( ! http )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
goto serve_500 ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
char * p = http + hdr_sz ; /* start body where we know it will be */
/* p[0] will be clobbered when we print the header, so fill it in last */
+ + p ;
for ( struct bss_info * wn = arg ; wn ; wn = wn - > next . stqe_next )
2015-10-01 20:22:45 +02:00
{
2016-01-28 06:52:09 +01:00
if ( wn ! = arg )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
* p + + = ' , ' ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
const char entry_start [ ] = " { \" ssid \" : \" " ;
strcpy ( p , entry_start ) ;
p + = sizeof ( entry_start ) - 1 ;
p = escape_ssid ( p , wn - > ssid ) ;
const char entry_mid [ ] = " \" , \" rssi \" : " ;
strcpy ( p , entry_mid ) ;
p + = sizeof ( entry_mid ) - 1 ;
2016-05-26 10:36:20 +02:00
p + = sprintf ( p , " %d " , wn - > rssi ) ;
2016-01-28 06:52:09 +01:00
* p + + = ' } ' ;
2015-10-01 20:22:45 +02:00
}
2016-01-28 06:52:09 +01:00
* p + + = ' ] ' ;
size_t body_sz = ( p - http ) - hdr_sz ;
2016-05-26 10:36:20 +02:00
sprintf ( http , header_fmt , body_sz ) ;
2016-01-28 06:52:09 +01:00
http [ hdr_sz ] = ' [ ' ; /* Rewrite the \0 with the correct start of body */
notify_scan_listeners ( http , hdr_sz + body_sz ) ;
2016-05-26 10:36:20 +02:00
free ( http ) ;
2016-01-28 06:52:09 +01:00
return ;
}
serve_500 :
notify_scan_listeners ( http_header_500 , LITLEN ( http_header_500 ) ) ;
}
/* ---- end WiFi AP scan support ------------------------------------------- */
2016-03-24 09:25:49 +01:00
static err_t enduser_setup_http_recvcb ( void * arg , struct tcp_pcb * http_client , struct pbuf * p , err_t err )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_recvcb " ) ;
2016-03-29 08:27:55 +02:00
2016-03-24 09:25:49 +01:00
if ( ! state | | err ! = ERR_OK )
2016-01-28 06:52:09 +01:00
{
2016-03-24 09:25:49 +01:00
if ( ! state )
ENDUSER_SETUP_DEBUG ( " ignoring received data while stopped " ) ;
tcp_abort ( http_client ) ;
return ERR_ABRT ;
}
if ( ! p ) /* remote side closed, close our end too */
{
ENDUSER_SETUP_DEBUG ( " connection closed " ) ;
scan_listener_t * l = arg ; /* if it's waiting for scan, we have a ptr here */
if ( l )
remove_scan_listener ( l ) ;
2016-03-29 08:27:55 +02:00
deferred_close ( http_client ) ;
2016-03-24 09:25:49 +01:00
return ERR_OK ;
2015-09-02 14:41:27 +02:00
}
2016-01-28 06:52:09 +01:00
2016-05-26 10:36:20 +02:00
char * data = zalloc ( p - > tot_len + 1 ) ;
2016-03-24 09:25:49 +01:00
if ( ! data )
return ERR_MEM ;
unsigned data_len = pbuf_copy_partial ( p , data , p - > tot_len , 0 ) ;
tcp_recved ( http_client , p - > tot_len ) ;
pbuf_free ( p ) ;
2016-03-29 08:27:55 +02:00
err_t ret = ERR_OK ;
2016-05-26 10:36:20 +02:00
if ( strncmp ( data , " GET " , 4 ) = = 0 )
2015-09-02 14:41:27 +02:00
{
2016-05-26 10:36:20 +02:00
if ( strncmp ( data + 4 , " / " , 2 ) = = 0 )
2015-10-01 20:22:45 +02:00
{
2016-01-28 06:52:09 +01:00
if ( enduser_setup_http_serve_html ( http_client ) ! = 0 )
2016-03-08 22:55:53 +01:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_recvcb failed. Unable to send HTML. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_NONFATAL ) ;
}
else
{
2016-03-29 08:27:55 +02:00
goto free_out ; /* streaming now in progress */
2016-03-08 22:55:53 +01:00
}
2015-10-01 20:22:45 +02:00
}
2016-05-26 10:36:20 +02:00
else if ( strncmp ( data + 4 , " /aplist " , 8 ) = = 0 )
2016-01-28 06:52:09 +01:00
{
2016-05-26 10:36:20 +02:00
scan_listener_t * l = malloc ( sizeof ( scan_listener_t ) ) ;
2016-01-28 06:52:09 +01:00
if ( ! l )
2016-03-08 22:55:53 +01:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " out of memory " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_NONFATAL ) ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
bool already = ( state - > scan_listeners ! = NULL ) ;
2016-03-24 09:25:49 +01:00
tcp_arg ( http_client , l ) ;
/* TODO: check if also need a tcp_err() cb, or if recv() is enough */
l - > conn = http_client ;
2016-01-28 06:52:09 +01:00
l - > next = state - > scan_listeners ;
state - > scan_listeners = l ;
2015-09-16 14:56:44 +02:00
2016-01-28 06:52:09 +01:00
if ( ! already )
{
2016-03-08 22:55:53 +01:00
if ( ! wifi_station_scan ( NULL , on_scan_done ) )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
enduser_setup_http_serve_header ( http_client , http_header_500 , LITLEN ( http_header_500 ) ) ;
2016-03-29 08:27:55 +02:00
deferred_close ( l - > conn ) ;
2016-03-24 09:25:49 +01:00
l - > conn = 0 ;
2016-03-08 22:55:53 +01:00
free_scan_listeners ( ) ;
2016-01-28 06:52:09 +01:00
}
}
2016-03-29 08:27:55 +02:00
goto free_out ; /* request queued */
2016-01-28 06:52:09 +01:00
}
2016-05-26 10:36:20 +02:00
else if ( strncmp ( data + 4 , " /status " , 8 ) = = 0 )
2016-01-28 06:52:09 +01:00
{
2016-03-08 22:55:53 +01:00
serve_status ( http_client ) ;
2016-01-28 06:52:09 +01:00
}
2016-05-26 10:36:20 +02:00
else if ( strncmp ( data + 4 , " /update? " , 8 ) = = 0 )
2016-01-28 06:52:09 +01:00
{
switch ( enduser_setup_http_handle_credentials ( data , data_len ) )
{
case 0 :
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 ) ) ;
break ;
default :
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_recvcb failed. Failed to handle wifi credentials. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_NONFATAL ) ;
2016-01-28 06:52:09 +01:00
break ;
}
}
2016-05-26 10:36:20 +02:00
else if ( strncmp ( data + 4 , " /generate_204 " , 14 ) = = 0 )
2016-03-08 22:55:53 +01:00
{
2016-03-29 17:25:05 +02:00
/* Convince Android devices that they have internet access to avoid pesky dialogues. */
2016-03-08 22:55:53 +01:00
enduser_setup_http_serve_header ( http_client , http_header_204 , LITLEN ( http_header_204 ) ) ;
}
2016-01-28 06:52:09 +01:00
else
2015-10-01 20:22:45 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " serving 404 " ) ;
ENDUSER_SETUP_DEBUG ( data + 4 ) ;
2016-01-28 06:52:09 +01:00
enduser_setup_http_serve_header ( http_client , http_header_404 , LITLEN ( http_header_404 ) ) ;
2015-10-01 20:22:45 +02:00
}
2015-09-02 14:41:27 +02:00
}
2016-01-28 06:52:09 +01:00
else /* not GET */
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
enduser_setup_http_serve_header ( http_client , http_header_401 , LITLEN ( http_header_401 ) ) ;
2015-09-02 14:41:27 +02:00
}
2016-01-28 06:52:09 +01:00
2016-03-29 08:27:55 +02:00
deferred_close ( http_client ) ;
free_out :
2016-05-26 10:36:20 +02:00
free ( data ) ;
2016-03-29 08:27:55 +02:00
return ret ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
static err_t enduser_setup_http_connectcb ( void * arg , struct tcp_pcb * pcb , err_t err )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_connectcb " ) ;
2016-03-24 09:25:49 +01:00
if ( ! state )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_DEBUG ( " connect callback but no state?! " ) ;
tcp_abort ( pcb ) ;
return ERR_ABRT ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
tcp_accepted ( state - > http_pcb ) ;
tcp_recv ( pcb , enduser_setup_http_recvcb ) ;
2016-03-29 08:27:55 +02:00
tcp_nagle_disable ( pcb ) ;
2016-03-24 09:25:49 +01:00
return ERR_OK ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
static int enduser_setup_http_start ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_start " ) ;
2016-03-24 09:25:49 +01:00
struct tcp_pcb * pcb = tcp_new ( ) ;
if ( pcb = = NULL )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_start failed. Memory allocation failed (http_pcb == NULL). " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
if ( tcp_bind ( pcb , IP_ADDR_ANY , 80 ) ! = ERR_OK )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
ENDUSER_SETUP_ERROR ( " http_start bind failed " , ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
state - > http_pcb = tcp_listen ( pcb ) ;
if ( ! state - > http_pcb )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
tcp_abort ( pcb ) ; /* original wasn't freed for us */
ENDUSER_SETUP_ERROR ( " http_start listen failed " , ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-16 14:56:44 +02:00
2016-03-24 09:25:49 +01:00
tcp_accept ( state - > http_pcb , enduser_setup_http_connectcb ) ;
/* TODO: check lwip tcp timeouts */
#if 0
2015-09-19 21:06:56 +02:00
err = espconn_regist_time ( state - > espconn_http_tcp , 2 , 0 ) ;
2015-09-02 14:41:27 +02:00
if ( err = = ESPCONN_ARG )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " http_start failed. Unable to set TCP timeout. " , ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND , ENDUSER_SETUP_ERR_NONFATAL | ENDUSER_SETUP_ERR_NO_RETURN ) ;
2015-09-02 14:41:27 +02:00
}
2016-03-24 09:25:49 +01:00
# endif
2015-09-02 14:41:27 +02:00
2016-03-24 09:25:49 +01:00
int err = enduser_setup_http_load_payload ( ) ;
2015-09-02 14:41:27 +02:00
if ( err = = 1 )
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_start info. Loaded backup HTML. " ) ;
2015-09-02 14:41:27 +02:00
}
else if ( err = = 2 )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " http_start failed. Unable to allocate memory for HTTP payload. " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
return 0 ;
2015-09-02 14:41:27 +02:00
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_http_stop ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_http_stop " ) ;
2015-09-02 14:41:27 +02:00
2016-03-24 09:25:49 +01:00
if ( state & & state - > http_pcb )
2015-09-02 14:41:27 +02:00
{
2016-03-24 09:25:49 +01:00
tcp_close ( state - > http_pcb ) ; /* cannot fail for listening sockets */
state - > http_pcb = 0 ;
2015-09-02 14:41:27 +02:00
}
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_ap_stop ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_station_stop " ) ;
2015-09-02 14:41:27 +02:00
wifi_set_opmode ( ~ SOFTAP_MODE & wifi_get_opmode ( ) ) ;
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_ap_start ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_ap_start " ) ;
2015-09-02 14:41:27 +02:00
struct softap_config cnf ;
2016-05-26 10:36:20 +02:00
memset ( & ( cnf ) , 0 , sizeof ( struct softap_config ) ) ;
2015-09-27 20:02:25 +02:00
2015-12-21 04:50:36 +01:00
# ifndef ENDUSER_SETUP_AP_SSID
# define ENDUSER_SETUP_AP_SSID "SetupGadget"
# endif
2015-09-27 20:02:25 +02:00
char ssid [ ] = ENDUSER_SETUP_AP_SSID ;
2016-05-26 10:36:20 +02:00
int ssid_name_len = strlen ( ssid ) ;
memcpy ( & ( cnf . ssid ) , ssid , ssid_name_len ) ;
2015-09-27 20:02:25 +02:00
uint8_t mac [ 6 ] ;
wifi_get_macaddr ( SOFTAP_IF , mac ) ;
cnf . ssid [ ssid_name_len ] = ' _ ' ;
2016-05-26 10:36:20 +02:00
sprintf ( cnf . ssid + ssid_name_len + 1 , " %02X%02X%02X " , mac [ 3 ] , mac [ 4 ] , mac [ 5 ] ) ;
2016-01-28 06:52:09 +01:00
cnf . ssid_len = ssid_name_len + 7 ;
2015-09-02 14:41:27 +02:00
cnf . channel = 1 ;
cnf . authmode = AUTH_OPEN ;
cnf . ssid_hidden = 0 ;
cnf . max_connection = 5 ;
cnf . beacon_interval = 100 ;
2016-01-28 06:52:09 +01:00
wifi_set_opmode ( STATIONAP_MODE ) ;
2015-09-02 14:41:27 +02:00
wifi_softap_set_config ( & cnf ) ;
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_dns_recv_callback ( void * arg , char * recv_data , unsigned short recv_len )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_dns_recv_callback. " ) ;
2015-09-02 14:41:27 +02:00
struct espconn * callback_espconn = arg ;
struct ip_info ip_info ;
2016-05-26 10:36:20 +02:00
uint32_t qname_len = strlen ( & ( recv_data [ 12 ] ) ) + 1 ; // \0=1byte
2015-09-19 21:06:56 +02:00
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 ;
2015-09-02 14:41:27 +02:00
uint8_t if_mode = wifi_get_opmode ( ) ;
if ( ( if_mode & SOFTAP_MODE ) = = 0 )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Interface mode not supported. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
2015-09-02 14:41:27 +02:00
uint8_t if_index = ( if_mode = = STATION_MODE ? STATION_IF : SOFTAP_IF ) ;
if ( wifi_get_ip_info ( if_index , & ip_info ) = = false )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Unable to get interface IP. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
2016-05-26 10:36:20 +02:00
char * dns_reply = ( char * ) malloc ( dns_reply_len ) ;
2015-09-02 14:41:27 +02:00
if ( dns_reply = = NULL )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Failed to allocate memory. " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_NONFATAL ) ;
2015-09-02 14:41:27 +02:00
}
uint32_t insert_byte = 0 ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( dns_reply [ insert_byte ] ) , recv_data , 2 ) ;
2015-09-02 14:41:27 +02:00
insert_byte + = 2 ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( dns_reply [ insert_byte ] ) , dns_header , sizeof ( dns_header ) ) ;
2015-09-02 14:41:27 +02:00
insert_byte + = ( uint32_t ) sizeof ( dns_header ) ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( dns_reply [ insert_byte ] ) , & ( recv_data [ 12 ] ) , qname_len ) ;
2015-09-02 14:41:27 +02:00
insert_byte + = qname_len ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( dns_reply [ insert_byte ] ) , dns_body , sizeof ( dns_body ) ) ;
2015-09-02 14:41:27 +02:00
insert_byte + = ( uint32_t ) sizeof ( dns_body ) ;
2016-05-26 10:36:20 +02:00
memcpy ( & ( dns_reply [ insert_byte ] ) , & ( ip_info . ip ) , 4 ) ;
2015-09-19 21:06:56 +02:00
2015-11-12 05:46:26 +01:00
// SDK 1.4.0 changed behaviour, for UDP server need to look up remote ip/port
remot_info * pr = 0 ;
if ( espconn_get_connection_info ( callback_espconn , & pr , 0 ) ! = ESPCONN_OK )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Unable to get IP of UDP sender. " , ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND , ENDUSER_SETUP_ERR_FATAL ) ;
2015-11-12 05:46:26 +01:00
}
callback_espconn - > proto . udp - > remote_port = pr - > remote_port ;
2016-05-26 10:36:20 +02:00
memmove ( callback_espconn - > proto . udp - > remote_ip , pr - > remote_ip , 4 ) ;
2015-11-12 05:46:26 +01:00
2015-09-02 14:41:27 +02:00
int8_t err ;
2016-01-28 06:52:09 +01:00
err = espconn_send ( callback_espconn , dns_reply , dns_reply_len ) ;
2016-05-26 10:36:20 +02:00
free ( dns_reply ) ;
2015-09-02 14:41:27 +02:00
if ( err = = ESPCONN_MEM )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Failed to allocate memory for send. " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
else if ( err = = ESPCONN_ARG )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. Can't execute transmission. " , ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
else if ( err ! = 0 )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR_VOID ( " dns_recv_callback failed. espconn_send failed " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
}
2015-09-19 21:06:56 +02:00
static void enduser_setup_free ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_free " ) ;
2015-09-19 21:06:56 +02:00
if ( state = = NULL )
{
return ;
}
2016-03-08 22:55:53 +01:00
/* Make sure no running timers are left. */
os_timer_disarm ( & ( state - > check_station_timer ) ) ;
os_timer_disarm ( & ( state - > shutdown_timer ) ) ;
2015-09-19 21:06:56 +02:00
if ( state - > espconn_dns_udp ! = NULL )
{
if ( state - > espconn_dns_udp - > proto . udp ! = NULL )
{
2016-05-26 10:36:20 +02:00
free ( state - > espconn_dns_udp - > proto . udp ) ;
2015-09-19 21:06:56 +02:00
}
2016-05-26 10:36:20 +02:00
free ( state - > espconn_dns_udp ) ;
2015-09-19 21:06:56 +02:00
}
2015-09-02 14:41:27 +02:00
2016-05-26 10:36:20 +02:00
free ( state - > http_payload_data ) ;
2016-01-28 06:52:09 +01:00
free_scan_listeners ( ) ;
2016-05-26 10:36:20 +02:00
free ( state ) ;
2015-09-19 21:06:56 +02:00
state = NULL ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
static int enduser_setup_dns_start ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_dns_start " ) ;
2015-09-02 14:41:27 +02:00
2015-09-19 21:06:56 +02:00
if ( state - > espconn_dns_udp ! = NULL )
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Appears to already be started (espconn_dns_udp != NULL). " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-05-26 10:36:20 +02:00
state - > espconn_dns_udp = ( struct espconn * ) malloc ( sizeof ( struct espconn ) ) ;
2015-09-19 21:06:56 +02:00
if ( state - > espconn_dns_udp = = NULL )
2015-09-02 14:41:27 +02:00
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Memory allocation failed (espconn_dns_udp == NULL). " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-05-26 10:36:20 +02:00
esp_udp * esp_udp_data = ( esp_udp * ) malloc ( sizeof ( esp_udp ) ) ;
2015-09-02 14:41:27 +02:00
if ( esp_udp_data = = NULL )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Memory allocation failed (esp_udp == NULL). " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2016-05-26 10:36:20 +02:00
memset ( state - > espconn_dns_udp , 0 , sizeof ( struct espconn ) ) ;
memset ( esp_udp_data , 0 , sizeof ( esp_udp ) ) ;
2015-09-19 21:06:56 +02:00
state - > espconn_dns_udp - > proto . udp = esp_udp_data ;
state - > espconn_dns_udp - > type = ESPCONN_UDP ;
state - > espconn_dns_udp - > state = ESPCONN_NONE ;
2015-09-02 14:41:27 +02:00
esp_udp_data - > local_port = 53 ;
int8_t err ;
2015-09-19 21:06:56 +02:00
err = espconn_regist_recvcb ( state - > espconn_dns_udp , enduser_setup_dns_recv_callback ) ;
2015-09-02 14:41:27 +02:00
if ( err ! = 0 )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Couldn't add receive callback, unknown error. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
err = espconn_create ( state - > espconn_dns_udp ) ;
2015-09-02 14:41:27 +02:00
if ( err = = ESPCONN_ISCONN )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Couldn't create connection, already listening for that connection. " , ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
else if ( err = = ESPCONN_MEM )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Couldn't create connection, out of memory. " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
else if ( err ! = 0 )
{
2016-01-28 06:52:09 +01:00
ENDUSER_SETUP_ERROR ( " dns_start failed. Couldn't create connection, unknown error. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
return 0 ;
2015-09-02 14:41:27 +02:00
}
2015-09-12 18:07:53 +02:00
static void enduser_setup_dns_stop ( void )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_dns_stop " ) ;
2015-09-02 14:41:27 +02:00
2016-03-01 08:33:11 +01:00
if ( state ! = NULL & & state - > espconn_dns_udp ! = NULL )
2015-09-02 14:41:27 +02:00
{
2015-09-19 21:06:56 +02:00
espconn_delete ( state - > espconn_dns_udp ) ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
}
2015-09-02 14:41:27 +02:00
2015-09-19 21:06:56 +02:00
static int enduser_setup_init ( lua_State * L )
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_init " ) ;
2015-09-19 21:06:56 +02:00
if ( state ! = NULL )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_ERROR ( " init failed. Appears to already be started. " , ENDUSER_SETUP_ERR_UNKOWN_ERROR , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-19 21:06:56 +02:00
return ENDUSER_SETUP_ERR_UNKOWN_ERROR ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
2016-05-26 10:36:20 +02:00
state = ( enduser_setup_state_t * ) zalloc ( sizeof ( enduser_setup_state_t ) ) ;
2015-09-19 21:06:56 +02:00
if ( state = = NULL )
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_ERROR ( " init failed. Unable to allocate memory. " , ENDUSER_SETUP_ERR_OUT_OF_MEMORY , ENDUSER_SETUP_ERR_FATAL ) ;
2015-09-19 21:06:56 +02:00
return ENDUSER_SETUP_ERR_OUT_OF_MEMORY ;
}
2016-05-26 10:36:20 +02:00
memset ( state , 0 , sizeof ( enduser_setup_state_t ) ) ;
2015-09-19 21:06:56 +02:00
state - > lua_connected_cb_ref = LUA_NOREF ;
state - > lua_err_cb_ref = LUA_NOREF ;
state - > lua_dbg_cb_ref = LUA_NOREF ;
if ( ! lua_isnoneornil ( L , 1 ) )
{
lua_pushvalue ( L , 1 ) ;
state - > lua_connected_cb_ref = luaL_ref ( L , LUA_REGISTRYINDEX ) ;
}
else
{
state - > lua_connected_cb_ref = LUA_NOREF ;
}
if ( ! lua_isnoneornil ( L , 2 ) )
{
lua_pushvalue ( L , 2 ) ;
state - > lua_err_cb_ref = luaL_ref ( L , LUA_REGISTRYINDEX ) ;
}
else
{
state - > lua_err_cb_ref = LUA_NOREF ;
}
if ( ! lua_isnoneornil ( L , 3 ) )
2015-09-02 14:41:27 +02:00
{
2015-09-19 21:06:56 +02:00
lua_pushvalue ( L , 3 ) ;
state - > lua_dbg_cb_ref = luaL_ref ( L , LUA_REGISTRYINDEX ) ;
}
else
{
state - > lua_dbg_cb_ref = LUA_NOREF ;
2015-09-02 14:41:27 +02:00
}
2015-09-19 21:06:56 +02:00
return 0 ;
2015-09-02 14:41:27 +02:00
}
2016-03-08 22:55:53 +01:00
static int enduser_setup_manual ( lua_State * L )
2016-01-28 06:52:09 +01:00
{
if ( ! lua_isnoneornil ( L , 1 ) )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
manual = lua_toboolean ( L , 1 ) ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
lua_pushboolean ( L , manual ) ;
return 1 ;
}
2015-09-19 21:06:56 +02:00
static int enduser_setup_start ( lua_State * L )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_start " ) ;
2015-09-19 21:06:56 +02:00
2016-03-01 07:54:10 +01:00
if ( ! do_station_cfg_handle )
2016-03-08 22:55:53 +01:00
{
2016-03-01 07:54:10 +01:00
do_station_cfg_handle = task_get_id ( do_station_cfg ) ;
2016-03-08 22:55:53 +01:00
}
2016-03-01 07:54:10 +01:00
2015-09-19 21:06:56 +02:00
if ( enduser_setup_init ( L ) )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
goto failed ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
2016-03-08 22:55:53 +01:00
enduser_setup_check_station_start ( ) ;
2016-01-28 06:52:09 +01:00
if ( ! manual )
2015-09-19 21:06:56 +02:00
{
2016-01-28 06:52:09 +01:00
enduser_setup_ap_start ( ) ;
2015-09-19 21:06:56 +02:00
}
if ( enduser_setup_dns_start ( ) )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
goto failed ;
2016-03-08 22:55:53 +01:00
}
2015-09-19 21:06:56 +02:00
if ( enduser_setup_http_start ( ) )
2016-03-08 22:55:53 +01:00
{
2016-01-28 06:52:09 +01:00
goto failed ;
2016-03-08 22:55:53 +01:00
}
2016-01-28 06:52:09 +01:00
goto out ;
2015-09-16 14:56:44 +02:00
2016-01-28 06:52:09 +01:00
failed :
2016-03-08 22:55:53 +01:00
enduser_setup_stop ( lua_getstate ( ) ) ;
2016-01-28 06:52:09 +01:00
out :
2015-09-02 14:41:27 +02:00
return 0 ;
}
2016-03-08 22:55:53 +01:00
/**
* A wrapper needed for type - reasons strictness reasons .
*/
static void enduser_setup_stop_callback ( void * ptr )
{
enduser_setup_stop ( lua_getstate ( ) ) ;
}
2015-09-12 18:07:53 +02:00
static int enduser_setup_stop ( lua_State * L )
2015-09-02 14:41:27 +02:00
{
2016-03-08 22:55:53 +01:00
ENDUSER_SETUP_DEBUG ( " enduser_setup_stop " ) ;
2016-01-28 06:52:09 +01:00
if ( ! manual )
{
enduser_setup_ap_stop ( ) ;
}
2015-09-02 14:41:27 +02:00
enduser_setup_dns_stop ( ) ;
enduser_setup_http_stop ( ) ;
2015-09-19 21:06:56 +02:00
enduser_setup_free ( ) ;
2015-09-02 14:41:27 +02:00
return 0 ;
}
2015-12-16 06:04:58 +01:00
static const LUA_REG_TYPE enduser_setup_map [ ] = {
2016-01-28 06:52:09 +01:00
{ LSTRKEY ( " manual " ) , LFUNCVAL ( enduser_setup_manual ) } ,
2015-09-02 14:41:27 +02:00
{ LSTRKEY ( " start " ) , LFUNCVAL ( enduser_setup_start ) } ,
{ LSTRKEY ( " stop " ) , LFUNCVAL ( enduser_setup_stop ) } ,
{ LNILKEY , LNILVAL }
} ;
2015-12-16 06:04:58 +01:00
NODEMCU_MODULE ( ENDUSER_SETUP , " enduser_setup " , enduser_setup_map , NULL ) ;