Introducing resin.io wifi-connect

Update your device's wifi configuration from your phone

Resin.io gives you an easy way to configure wifi before downloading and provisioning a device. This is great - but this doesn't work when your device is going to be deployed in a customer's network, where you don't know the credentials in advance.

For this reason we built resin-wifi-connect, a simple production-ready tool that allows you to update a device's wifi configuration via a captive portal.

Watch the demo to see it in action!

Wifi-connect Demo from Resin.io Team on Vimeo.

How to use it

  1. Connect your device to resin.io, you can find detailed instructions here.
    Ensure that your device is intially connected via ethernet so it can receive the new wifi-connect code.

  2. Push wifi-connect demo to device:

$ git clone https://github.com/resin-io/resin-wifi-connect
$ cd resin-wifi-connect
$ git remote add <username>@git.resin.io:<username>/<appname>.git
$ git push resin master
  1. Once the device has finished downloading the app, connect to the ResinAP network on your phone. Then open a browser and enter your local network's wifi-credentials.

If you keep the ethernet cable in you can get a live stream of the logs from the resin.io dashboard as the new credentials are saved to the device. If it's successful you'll see the following:

Imgur

Exciting!

How it works

This app interacts with the Connman connection manager in Resin's base OS. It checks whether WiFi is connected, tries to join the favorite networks, and if this fails, it opens an Access Point to which you can connect using a laptop or mobile phone.

The access point's name (SSID) is, by default, "ResinAP". You can change this by changing the "ssid" field in wifi.json. By default, the network is unprotected, but you can add a WPA2 passphrase by setting the "passphrase" field in the same file. Keep in mind that, once you set a passphrase, you can't go back to an unprotected network on an already provisioned device. The server for wifi configuration uses port 8080 by default. This can also be configured in wifi.json, but it will be transparent to the user as all web traffic is redirected when in Access Point mode.

These three configurations can also be set with the environment variables PORTAL_SSID, PORTAL_PASSPHRASE and PORTAL_PORT, but remember that the device has to be online to be able to download the new settings (you can use an ethernet cable or a WiFi network to which you've already connected).

When you connect to the access point, any web page you open will be redirected to our captive portal page, where you can select the SSID and passphrase of the WiFi network to connect to. After this, the app will disable the AP and try to connect. If it fails, it will enable the AP for you to try again. If it succeeds, the network will be remembered by Connman as a favorite.

These features are packed in a small node.js application written in CoffeeScript. The creation of the captive portal involves using the Connman D-Bus API, starting a catch-all DNS server and some iptables magic:

startServer = (wifi) ->  
    console.log('Getting networks list')
    wifi.getNetworksAsync()
    .catch (err) ->
        throw err unless err.message == 'No WiFi networks found'
        return []
    .then (list) ->
        ssidList = list
        wifi.openHotspotAsync(ssid, passphrase)
    .then ->
        console.log('Hotspot enabled')
        dnsServer = spawn('named', ['-f'])
        getIptablesRules (err, iptablesRules) ->
            throw err if err?
            iptables.appendManyAsync(iptablesRules)
            .then ->
                console.log('Captive portal enabled')
                server = app.listen port, (err) ->
                    throw err if err?
                    console.log('Server listening')

Once the device has joined a wifi network, the Node app exits, and your own code can run if you add it to the start script:

#!/bin/bash
npm start

# CODE THAT STARTS YOUR APP GOES HERE

while true  
do  
    echo "App exited but container is still running to allow web terminal access"
    sleep 3600
done  

An extra feature that wifi-connect adds is the ability to remember several connections, as we keep these as an array in a persistent JSON file, which we attempt to connect to before starting the AP:

Promise.each connectionsFromFile, (conn) ->  
        wifi.joinAsync(conn.ssid, conn.passphrase)
        .then ->
            console.log('Joined! Exiting.')
            retryCallback()
        .catch(ignore)
    .finally ->
        startServer(wifi)

Like everything we build, we made wifi-connect because it solves a common problem when shipping a hardware product. We hope it saves you a little time so you can focus on the problems your product was made to solve.

Happy hacking!

Have questions, or just want to say hi? Find us on our community chat.

comments powered by Disqus
Terms of Service | Privacy Statement | Master agreement | Copyright 2019 Balena | All Rights Reserved