-
-
Notifications
You must be signed in to change notification settings - Fork 749
Generic WiFi Design
THIS WIKI IS OUT OF DATE. check out the README.md on GitHub for docs on Espruino internals, or see www.espruino.com for searchable, up to date docs on using Espruino.
Here we are capturing the design of a generic WiFi interface that will be implemented by the likes of ESP8266. The name of the interface has not been finalized but in this document, we will assume it will be called wifi
.
This page will be deleted and/or used for the basis of proper documentation when work has been completed. There is a forum thread discussing this work item. There is also an explicit work item tracking the detailed implementation and design (#589).
Before we go deep into design thoughts, it is always good to see what others have done. There is absolutely no reason that we don't just do exactly what has been done before. Here are some variants:
- Espruino's existing network connectivity libraries for ESP8266 (see bottom of page), CC3000, WIZnet Ethernet and GSM/GPRS
- particle.io
- smart.js
- Windows WiFIDirectDevice
- Android WiFiManager
- NodeMCU firmware
- Others???
Also, please bear in mind that WiFi/Ethernet/GSM aren't the only ways to connect to the net. I'm pretty sure Thread/6LoWPAN will be getting extremely popular soon, and it'd be good to have an API for that which behaves in a similar way.
Something important to me (@tve) is wireless operation of Espruino, meaning that I should just have to apply power and then be able to point the IDE at it and start using it, without hooking up any wires (usb/serial). To me what this implies is:
- unless otherwise configured, the wifi comes up automatically at power-up
- unless otherwise configured, the wifi has self-configuration fall-backs (see below) so the user can regain control when the Wifi connection fails
- unless otherwise configured, the interactive loop has a listener on wifi
In terms of fall-back, what I've implemented in esp-link, which seems to work pretty well, is that the device tries to connect to the last AP at boot, and if that does not succeed in 20 seconds, it starts an AP to which the user can connect in order to scan wifi and find an AP. Once connected to an AP, the device drops its own AP after 20 seconds. Starting the AP after 20 seconds only happens on power-up for security and power consumption reasons (i.e. it doesn't happen if the AP just disappears).
The above type of logic could be implemented in JS, but only if there is a way to lock some start-up code into the device that does not go away when new apps are loaded via the IDE, etc.
###setPower
wifi.setPower(onOrOff, function(err) { ... })
Initialise WiFi, or turn it off. That could be handy?
-
onOrOff
[Boolean
] - Switch on or off the WiFi interface. Useful to switch off WiFi to reduce power consumption.
-
gw
:setMode
was suggested here - I think something like that makes more sense? -
rakeshpai
: I'd suggest not exposing this, maybe? I think espruino knows enough about whether the WiFi radio needs to be powered or not (using.createAP
or.disconnect
or similar methods), and should decide on the user's behalf. This would make it easier to use, and potentially result in power savings. Just thinking out loud. -
tve
: I definitely need to be able to control the wifi power state, and it should have more than just an on/off state. Wifi can be put into low power state using the DTIM intervals, but that reduces bandwidth and increases latency. Sensors that communicate sporadically need to be able to turn wifi off for power conservation. This may also be a good place to configure whether wifi should come on at boot time (default) or not. -
Kolban
- 2015-10-24 - On an ESP8266, what is the underlying SDK API that might be used to affect power?
###connect
wifi.connect(ssid, key, [options], function(err) { ... });
Connect to the given access point. The options
could be an object extra stuff like the security type.
The callback is called with err==null on success. Could maybe also return the IP?
####Implementation
An option called autoConnect
has been added to the options
.
-
autoConnect
- A boolean value. Whentrue
, a reconnect to the supplied access point will be performed each time the ESP8266 boots. Whenfalse
, reconnection to the access point will not be attempted when the device boots. When not set, the currentautoConnect
settings will be maintained. -
dnsServers
- An array of dotted decimal IP addresses. These servers will be used as the servers for DNS resolution (if supplied).
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
####Comments
-
Kolban
: When should the callback occur? Choices include when connected or when connected AND ready for use? -
Sameh Hady
: I guess callback should occur once connected and ready. ( My vote ) -
Kolban
: My vote would be an object of properties as opposed to positional parameters. -
Kolban
: What should be in the options like data? For example static IP address, hostname etc etc? -
Sameh Hady
: Maybe an option to set auto connect true or false -
rakeshpai
: I think the callback should be called only when it's ready. If we want intermediate states, we could make.connect
return an event emitter, to which we can add listeners. Eg:wifi.connect(...).on('...', function() { ... })
-
gw
: My vote is for connected only too. Maybewifi
itself should emit DHCP/etc events. Seerakeshpai
's comments onsetIP
- maybe that could be removed and merged in here? Does that work nicely when we also consider connecting via Ethernet? -
Kolban
- 2015-10-23 - Callback occurs when ready for work (i.e. connected and IP allocated).
###disconnect
Disconnect from an access point (assumes a previous connect()
).
wifi.disconnect(function(err) { ... });
####Implementation
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
###scan
wifi.scan(function(err, aps) { ... });
Call the callback with a list of discovered access points, of the form aps = [ { ssid, enc, signal_strength, mac_address } ].
The callback is called with err==null on success.
-
err
- Error indication. -
aps
- Array of objects, one per found access point.
####Implementation
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
####Comments
-
tve
: Should this be calledscan
as opposed togetAPs
? -
Sameh Hady
: Vote forscan
-
rakeshpai
: +1 forscan
. We could consider.rssi
instead ofsignal_strength
, if it won't be confusing. Also, maybe.mac
instead of.mac_address
? -
gw
: ok, scan sounds good. The existing JS module usesssid/enc/signal/mac
and has done for a while. Issignal
less confusing and shorter? -
kolban
- 2015-10-23 - Function namedscan
.
###getConnectedAP
wifi.getConnectedAP(function(err, ap) { ... });
Call the callback with the name of the currently connected access point. The callback is called with err==null on success.
-
err
- Error indication. -
ap
- Details of the currently connected access point.
####Comments
-
Kolban
: What should be included in the response data? -
gw
: Unsure. Maybe it makes sense to merge this andgetConnectedDevices
? -
tve
: I think this should return as much info as there is, and in particular, give good information about the connectivity. Here are some use-cases, which could also be resolved using a wifi-state-change callback: am I connected, i.e. have an IP and everything I need? How can I tell that I'm connected but am not getting an IP? How can I tell that I got disconnected and maybe either need to scan for a different AP or start my own AP so the user can reconfigure me? This call could also return signal strength and eliminate the need for a separate rssi call.
###createAP
wifi.createAP(ssid, key, channel, enc, function(err) { ... })
Create an access point with the given ssid, key, channel, and encoding. Encoding can be 0, undefined, "open", "wep", "wpa_psk", "wpa2_psk" or "wpa_wpa2_psk".
Example: wifi.createAP("ESP123","HelloWorld",5,"wpa2_psk",print)
####Implementation
The implementation is:
wifi.createAP(ssid [,password] [,options] [,callback])
The options currently defined are:
-
authMode
- The authentication mode to use. Can be one ofopen
,wpa2
,wpa
orwpa_wpa2
.
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
####Comments
-
Kolban
: I had thought that a good name would have beenbecomeAP
but I think I am now likingcreateAP
better. -
Kolban
: My vote would be an object of properties as opposed to positional parameters. -
gw
: My preference is an object too - with sensible defaults (is there a channel that can be used legally in all regions? 5?) -
rakeshpai
: We'd need a corresponding method to stop the AP as well. Symmetric sounding names might be a good idea, alastartAP
andstopAP
. Also, +1 for object. There are other properties we can set when creating an AP (example), and having them as positional arguments would make it difficult to use. -
Kolban
- 2015-10-23 - Function namedcreateAP
. -
tve
documentation for this needs to indicate that the channel is only used when in AP-only mode, when running in AP+STA mode the STA side dictates the channel -
tve
: this is missing some args about the IP addresses. If we want to keep it simple (my vote) I'd add an options hash (to make it expandable) and only support asubnet_cidr
field, this would allow the user to set a subnet, have the device pick the first address for itself, and run dhcp to assign the rest to clients that connect. If nothing is specified, the device picks the default subnet192.168.4.0/24
(random suggestion based on esp8266 default).
###stopAP
Stop being an access point.
wifi.stopAP(function(err) { ... })
Stops an access point.
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
####Comments
-
gw
: Does it make sense to just havedisconnect
- which stops the AP and/or disconnects from whatever it was connected to? Seems strange having to have 2 functions that do almost the same thing but in different circumstances. -
bluebie
: the ESP8266 can be connected to an AP while also producing it's own AP, so it does make sense to be able to disconnect the client function and the server function individually. -
tve
being able to control STA and AP separately is important: it allows the AP to brought up to configure the STA side, connect, and then drop the AP once the STA side is happy
###getConnectedDevices
wifi.getConnectedDevices(function(err, devices) { ... });
If in AP mode (with wifi.createAP), call the callback with the second argument as an array of { ip, mac } objects - one for each connected device.
####Comments
-
Kolban
: I am not a fan of the name of the function. I would suggest something likegetConnectedStations
as it aren't devices which connect to access points but rather stations. -
tve
: I agree with Kolban: either getConnectedStations, getConnectedClients, or getClients -
Kolban
- 2015-10-24 - Examining this item in more detail, my vote is not to have a callback but simply to return the values. However, before we go down that route, was there a reason that we had a callback? In the ESP8266, we can get this data without a callback but that may not be true for other devices and since we want this interface to be generic ...
###getIP
wifi.getIP(function(err, ip) { ... });
Call the callback with the current address details: {ip:string, mac:string, ...?}
The callback is called with err==null on success.
####Implementation
Date | Note |
---|---|
2015-10-23 | First pass implementation. |
####Comments
-
Sameh Hady
: I prefer to make this function generic, something likewifi.info(function(err, wifi) { ... })
that returns an object ( wifi.ip, wifi.mac, etc ) -
gw
: The suggestion is that this is generic (in all but name), returning all that info. Choice of name is because that's what is currently implemented, but it could be changed. If it were changed I think there must be something more informative thaninfo
though. Probably needs to begetX
to mirror asetX
too. -
rakeshpai
: Node uses[os.networkInterfaces](https://nodejs.org/api/os.html#os_os_networkinterfaces)
, which could be interesting. We'd need to use a plural, since the hardware can be on multiple networks simultaneously, eg. acting as both a station and an AP. Also, would this need to be an asynchronous call? (It might be best to keep it async for compatibility though.) -
gw
: yep, it'd need to be async to also work on other devices (like ESP8266 via AT commands). Interesting about multiple interfaces, but IMO in terms of name,networkInterfaces
isn't really very obvious, and what would thesetter
version be? I'd say 95% of the time people will just use this to get the IP, so it'd be nice to make it easy to use for that case. -
Kolban
- 2015-10-24 - Is there a reason that this function is defined to have a callback function? This appears to be a function that could simply return the result but I want to validate that there isn't some good reason a callback was chosen (for example because a callback might be necessary for other boards).
###setIP
wifi.setIP(data, function(err) { ... });
Call the function with address details - items left out are not changed: {ip:string, mac:string, ...?}
Current behaviour on CC3000/WIZnet is to do DHCP if called without arguments.
The callback is called with err==null on success.
####Comments
-
rakeshpai
: I'd vote against having this method, and rolling the configuration into the arguments accepted by.createAP
or.connect
methods instead. -
gw
: interesting. It'd still be nice to be able to force DHCP, but I guess a connect to the same AP without a disconnect first could do that. We also need to think about Ethernet/GSM, but I guessconnect
could work in those cases too. -
tve
: something missing from the above is the ability to set the hostname used in the DHCP registration -
Kolban
- 2015-10-24 - Can anyone think of a reaspm where we might want to set an IP at a later time than we create an Access Point connection or when we become an access point ourselves? If not, then I think I'm seeing that this should be an option on theconnect
andcreateAP
functions.
###setDNS
wifi.setDNS(dnsServers)
Sets the DNS servers that should be used.
Example: `wifi.setDNS(["8.8.8.8", "8.8.4.4"]);
####Comments
-
rakeshpai
: Not entirely sure about where this method should exist. Iswifi
even the right place? Also, should this method exist at all, or should this just be part of the config required to start a connection? -
gw
: I'd vote for sticking it as an option inconnect
- or if we're keepingsetIP
to put it in there - after all, chances are if you're setting DNS you want it set alongside IP/Gateway/etc. -
Kolban
- 2015-10-24 - I'm also thinking that this seems to apply to theconnect
function.
###rssi
wifi.rssi()
or wifi.rssi('access point')
Gets the signal strength of the router when connected to it, or of an access point if specified.
####Comments
-
gw
: I'd be in favour of scrapping this? I wonder how often it'd be used, and.scan
should do this for you (although it would obviously return other access points too). IfgetIP
is being renamed to something more generic likenetworkInterfaces
, the data could go in there too. -
tve
: I'm very keen on this for troubleshooting reasons, it's very painful to place or troubleshoot sensors when you have no idea how well they're able to communicate -
Kolban
- 2015-10-24 - It appears that if we ask for a list of access points usingscan
we get the rssi value. Would this cover a need to get it for the one currently being used?
#Design Questions
We are now looking towards low-level design. The goal here is to make a new library called wifi
that can then be created with require("wifi")
. However, is this the right thing? Should wifi
not be a class that we can create with new WiFi()
so that we can save state within it?
Here is my thinking. For many of the methods, we will be changing the state of our WiFi instance. For example, when we call connect(myFunc)
... we need to save the reference to the myFunc
function so that when we do connect, the function can be called. The way we had been saving this was as C language globals that hold JsVar references. However I'm thinking that is wrong. Given that we now have a variable (I think) that is an instance of a WiFi
... can't we hang these callbacks off this variable as hidden properties?
This is where we need gw
to assist.
gw
I just posted this response to what I think is the same question on the issue tracker? Stick it in a variable in hiddenRoot
, and make a esp8266GetWiFiVar()
function to get it - then it can always be optimised later.
Definitely you should avoid keeping JsVar pointers wherever possible as it's just asking for bugs to creep in. It's also using our precious RAM :)