Deal With WiFi Loss And Planned Disconnections
Internet of Things devices based on imps are usually intended to remain connected to the Internet at all times, but this is not always possible — or even, in some cases, desirable.
Users’ broadband connections go down. WiFi routers crash. Businesses may have redundancy on these resources, but consumers rarely do. In these instances, the developer will need to decide how their imp-based product will respond to such circumstances: should it continue to operate, if only to inform the user that the connection is down, or sleep until it can connect once again?
In fact, the developer may even decide that it is advantageous if the device disconnects itself, perhaps in order to minimize its power consumption in order to preserve battery charge, or because it is relaying sensor data that doesn’t need to be updated continuously.
A new imp-enabled device will not be able to connect to the Internet until it has been configured using BlinkUp™. Yet a developer may want their product to be able to work as soon as the end-user first powers it up. Again, the imp will need to work offline, if only to prompt the user for WiFi credentials.
Additionally, there are cases where a developer may choose to use the imp as the microcontroller within a product for which Internet connectivity is optional. Here the imp will not only need to be able to operate without a connection, but also it may never connect to the Internet and simply run the firmware installed in the factory.
In each of these situations, the imp API provides the developer with tools to manage how the imp operates offline.
You specify how the imp reacts to outages by calling the imp API method server.setsendtimeoutpolicy(). This is always called implicitly with default values, but by including the call in your device code you can control how the imp deals with the loss of its Internet connection.
The imp’s default behavior upon an unintended disconnection is to attempt to reconnect immediately. It will try to do so for 60 seconds. If it fails, it suspends operation, disables its WiFi radio and goes into a low-power state: an imp001 card will draw around 6µA, the imp002 and imp003 modules 4µA. Under normal operation, all three draw between 60mA and 80mA.
An imp remains in this ‘snooze’ state for nine minutes. At the end of this period, the imp will wake and perform a warm start. It will begin running its Squirrel code and remain offline until that code makes an attempt to contact the server, such as server.log() or agent.send(). At this point, impOS™ powers up the imp’s WiFi radio and then spends 60 seconds trying to connect. If it succeeds, Squirrel continues to run, and you will see the message [Exit Code] imp restarted, reason: wifi outage
in the log. If reconnection fails, impOS puts the device back into the snooze state.
It’s important to note that waking from snooze amounts to a reboot. Upon disconnection, the execution of your code is paused and will only be resumed if the imp reconnects within the initial 60-second period. If this doesn’t happen, the execution of your code ends. When the imp eventually reconnects, your program is restarted. State data and the contents of variables are not retained, though some data can be preserved in the non-volatile nv table if it has been set up by the Squirrel code.
The method server.setsendtimeoutpolicy() takes three parameters, only the first of which is of interest here; for details of the others, see the method’s documentation. The method’s key parameter is a constant, onError, which can take one of two possible values: SUSPEND_ON_ERROR or RETURN_ON_ERROR. The former is the default and specifies the mode of operation outlined above. RETURN_ON_ERROR, however, tells the imp to continue running as normal when the WiFi connection is lost.
Which of these behaviors you choose will depend on your application. A device that simply relays data from sensors to its agent — and from there to a web page or a mobile app — will not need to continue processing and relaying sensor data if there is nowhere for that data to be sent; the device should suspend operation until it is once again able to contact its agent. Reconnection will take place automatically.
However, a device that isn’t reliant on an Internet link may need to continue operating if the WiFi connection is lost. An imp-powered digital clock, for instance, needs to continue updating its display even if it is temporarily out of reach of the app that adjusts its settings.
At the end of the initial 60-second re-connection period, an imp set to RETURN_ON_ERROR will automatically call any function that may have been previously registered using the API method server.onunexpecteddisconnect(). This callback function provides a way to prepare the imp for a period of operating without an Internet connection: perhaps to present a ‘disconnected’ icon on an attached LCD screen, for example. The nominated function must provide a single parameter: in integer into which a code representing the reason for the disconnection, if known, is placed.
The two methods, server.setsendtimeoutpolicy() and server.onunexpecteddisconnect(), are typically called together, at the start of the device code. Remember, the SUSPEND_ON_ERROR policy remains in force until it is changed using server.setsendtimeoutpolicy() — before then a disconnection event can’t be trapped using the function registered using server.onunexpecteddisconnect(). Your policy choice is not retained after a power-cycle or warm restart.
Any imp set to RETURN_ON_ERROR will no longer automatically attempt to reconnect to the Internet periodically. Getting back in touch with the agent becomes the responsibility of the device code, which sooner or later must call the API method server.connect() to do so.
This method has two parameters: a function that will be called whatever the outcome of the attempt to connect, and a float value specifying the length of time in seconds that the imp will persevere in that attempt. The bid to connect occurs asynchronously; it takes place alongside whatever other code the imp is running.
The callback function specified in server.connect() should take a single parameter: a variable into which an integer code will be passed. The code will be SERVER_CONNECTED if the imp is back online, or one of four other values — NO_WIFI, NO_LINK, NO_IP_ADDRESS, NO_SERVER, NOT_RESOLVED, NO_PROXY or NOT_AUTHORISED — if the attempt to connect failed in some way. These are the codes issued when an unexpected disconnection takes place, and for this reason, it is common practice to use the disconnection handler listed in the code above as the server.connect() callback, though this is not mandatory.
If the reason code indicates failure, the device software will typically act on this by scheduling another attempt to connect at some point in the future. Here, the code simply resets a counter, and leaves the handler function to set the disconnected flag if the device has not successfully reconnected. A list of disconnection/connection failure reason codes can be found here.
If imps appear unresponsive when they are disconnected from WiFi, it is often because they haven’t been set to override the device’s default unexpected disconnection behavior, or have been set to do so too far into the Squirrel code — a common error. If this is unwanted behavior, it can easily be overridden by including a server.setsendtimeoutpolicy() call as soon as possible in the device code, with the onError parameter set to RETURN_ON_ERROR as the code above shows.
Some developers who do include server.setsendtimeoutpolicy() to set the policy they require nonetheless neglect to schedule a call to server.connect() in order to try and re-establish the agent connection. Failing to attempt to reconnect will leave the device operational, as confirmed by visual checks, but not able to be contacted by its agent.
Just as a suddenly disconnected imp can attempt to re-establish an Internet link by calling server.connect(), so too a connected imp may deliberately break its connection by calling another API method, server.disconnect(). This also has the effect of turning off the imp’s networking hardware. Whether the program continues to run will depend, again, on the parameters passed to server.setsendtimeoutpolicy().
The default mode — onError is set to SUSPEND_ON_ERROR — will see the imp enter the ten-minute snooze-reconnection cycle discussed above. If the imp is set to RETURN_ON_ERROR, however, it will continue to run its code. Because disconnection was deliberate, any callback function registered using server.onunexpecteddisconnect() will not be called.
The following code shows how this might be used:
Here, the imp001-based device powers down its WiFi as soon as the program starts but continues to operate in order to take data samples from connected sensors every two seconds. Every hour, however, it attempts to connect to its agent; if it is successful, it uploads the data to the agent. It then disconnects for another hour while it continues to take samples.
Important You must allow impOS an opportunity to perform any necessary housekeeping — listen for an impOS and/or Squirrel update, server redirect, or any application messages to the device — before disconnection takes place. The best — and strongly recommended — way to achieve this is to embed the call in a function that will executed when the imp next goes idle, which happens when there is no further Squirrel code in queue and impOS has performed all the tasks it needs to perform. Register this function using imp.onidle().
This is demonstrated in the code above. The onidle function registered in line 53 ensure the disconnection will take place once Squirrel execution comes to an end, ie. after line 60, which registers a function call to take place in two seconds' time, has been executed.
The scenarios considered above show how to keep an imp running despite an unexpected disconnection from the network and how to deliberately disconnect it. But what about devices which have not yet connected, and perhaps have not yet been configured to connect? All of the tools discussed so far apply in this case too, allowing the imp-enabled device to operate right out of the box.
The developer has extra tools to help in this situation. First, the API method imp.enableblinkup() can be used to disable the BlinkUp feature until the user has been prompted to begin the process of configuring the device to connect to the Internet. BlinkUp functionality is always enabled for the first 60 seconds after an imp has been powered on. However, including:
imp.enableblinkup(false);
at the start of your code will ensure the imp can’t be configured by BlinkUp after that initial 60-second period has elapsed. You may choose to disable BlinkUp initially if, for instance, the device has its own screen and you plan to use this to issue prompts to the user. Later, the code can call
imp.enableblinkup(true);
to re-enable the BlinkUp facility. It’s unlikely you would know the credentials of the end-user’s WiFi network, though if you did, you could use the method imp.setwificonfiguration() to configure the device to talk to that hotspot. This method takes two parameters, both strings: the first is the network’s SSID, the second its password (or an empty string, ""
, if there is no password because it is an open network).
Out of the box, new imp-powered products should have no stored WiFi credentials — manufacturers should clear each unit’s saved credentials in the factory. In this case imp.getssid() will return a zero-length string, ""
, allowing your code to determine whether the device is being run out of the box before BlinkUp.
The following code snippet shows how you might apply these in a device designed to boot (warm or cold) into an offline state:
You’ll notice that the second line of non-comment program code is the server.setsendtimeoutpolicy() call, setting the device to RETURN_ON_ERROR. Once again, this is used to ensure the imp code continues to run after impOS detects that the device is not connected to a wireless network.
So far, we’ve covered situations in which the imp-enabled device is kept running and powered while it is disconnected from the network. It is also possible to disconnect and power down the device to a very low level, awaking only when necessary — perhaps to take a sensor reading, or to very briefly go online and transmit collated sensor data. This provides the best power conservation, though there are consequences that you need to be aware of when considering whether to implement this behavior in your device code.
The imp API provides the convenience method server.sleepfor() to put the device into deep sleep for a fixed duration of time.
If you wish to specify the time at which the imp will wake rather than the length of the sleep period, use server.sleepuntil() which take integers that specify the hour and the minute at which the imp will wake; seconds and day-of-the-week values may also be provided for greater precision.
At the specified time, the imp will automatically wake. Alternatively, it can be woken earlier by sending a trigger pulse on the imp’s wake-up pin — pin 1 (imp001 and imp002) or pin W (imp003 and imp004m); the imp005 currently has no deep sleep capability — if this has been set using:
hardware.pin1.configure(DIGITAL_IN_WAKEUP);
See the pin.configure() documentation for additional details.
Putting the imp into deep sleep clearly saves energy, but it has another consequence: the CPU stops running your device code. When the imp wakes, it performs a warm boot: impOS is started up, it reloads your code from Flash storage and runs it from the start. No state information, such as the contents of variables, will be retained unless you have explicitly saved them to persistent storage.
When the imp wakes, it will not connect to the Internet immediately. If you’ve called server.setsendtimeoutpolicy() with SUSPEND_ON_ERROR (or left it as the default), the first time your code does something that requires Internet access, such as a call to server.log() or agent.send(), the imp will suspend execution of Squirrel code and try to connect. If you’ve chosen to use the RETURN_ON_ERROR policy, however, the imp will not connect until specifically instructed to do so using server.connect().
Remember, when the device restarts and begins running its code, the default policy SUSPEND_ON_ERROR is in force until Squirrel encounters a server.setsendtimeoutpolicy() call specifying RETURN_ON_ERROR. Again, this is why it is important to set your desired policy in your code as soon as possible.
Please take a look at our WiFi state diagram, which presents the program flow arising from various connected and disconnected scenarios.
Data can be preserved during deep sleeps, either by storing it in the imp’s own nv table (note that not all imps support this) or by sending it to the agent before WiFi is disabled. The latter, which requires that data be packaged into a single Squirrel table of 64KB or less, has the advantage that the data is preserved even if the imp’s power is cut; data in the nv table does not persist when the imp loses power completely.
To make use of the nv table, we might add an extra function to write data to the nv table, and modify the function safeSleepDuration() in the code above:
Finally, because deep sleep is triggered as soon as server.sleepfor() or server.sleepuntil() are called, it is important to allow impOS an opportunity to perform any necessary housekeeping before this happens. The best and recommended way to achieve this is to embed the calls listed above in a function that is executed when the imp goes idle, which happens when there is no further Squirrel code in queue and impOS has performed all the tasks it needs to perform. Register this function using imp.onidle() as the code above shows.