From 9627e1e3b50f64f9fbf7623fb080cc66984efc57 Mon Sep 17 00:00:00 2001 From: David Flanagan Date: Sat, 2 Jul 2016 07:18:47 +0000 Subject: [PATCH] working version --- .gitignore | 1 + README.md | 44 ++++++++++++++++- config/hostapd.conf | 6 +++ config/rc.local | 22 +++++++++ config/udhcpd.conf | 118 ++++++++++++++++++++++++++++++++++++++++++++ index.js | 72 +++++++++++++++------------ start.sh | 5 +- wait.js | 5 ++ wifi.js | 7 +-- 9 files changed, 242 insertions(+), 38 deletions(-) create mode 100644 config/hostapd.conf create mode 100755 config/rc.local create mode 100644 config/udhcpd.conf create mode 100644 wait.js diff --git a/.gitignore b/.gitignore index a3a1868..353bd59 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ oauthToken.json # Ignore emacs backup files *~ +*# # Logs logs diff --git a/README.md b/README.md index 50b49cc..85a5155 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,47 @@ handles the first-time setup required to get the device working: The code is Linux-specific and has so far only been tested on a Raspberry Pi 3. It also requires hostapd and udhcpd to be installed -and properly configured. +and properly configured. Here are the steps I followed to get my +Raspberry Pi set up to run this server: + +## Step 1 +Install software we need to host an access point, but +make sure it does not run by default each time we boot + +``` +$ sudo apt-get install hostapd +$ sudo apt-get install udhcpd +$ sudo systemctl disable hostapd +$ sudo systemctl disable udhcpd +``` +## Step 2 +Next, configure the software: + +- Edit /etc/default/hostapd to add the line: + +``` +DAEMON_CONF="/etc/hostapd/hostapd.conf" +``` + +- Copy `config/hostapd.conf` to `/etc/hostapd/hostapd.conf` + +- Edit the file `/etc/default/udhcpd` and comment out the line: + +``` +DHCPD_ENABLED="no" +``` + +- Copy `config/udhcpd.conf` to `/etc/udhcp.conf` + +## Step 3 + +Finally, set up your system to run this server each time it boots up +Do this by editing `/etc/rc.local` to add this line, editing the path +as appropriate to refer to the start.sh script in this repo. + +``` +/home/pi/vaani.setup/start.sh & +``` + +See `config/rc.local` for a startup script that work for me. diff --git a/config/hostapd.conf b/config/hostapd.conf new file mode 100644 index 0000000..0e4c4dc --- /dev/null +++ b/config/hostapd.conf @@ -0,0 +1,6 @@ +interface=wlan0 +driver=nl80211 +ssid=VaaniSetup +hw_mode=g +# or 1 or 11 +channel=6 diff --git a/config/rc.local b/config/rc.local new file mode 100755 index 0000000..18abeb0 --- /dev/null +++ b/config/rc.local @@ -0,0 +1,22 @@ +#!/bin/sh -e +# +# rc.local +# +# This script is executed at the end of each multiuser runlevel. +# Make sure that the script will "exit 0" on success or any other +# value on error. +# +# In order to enable or disable this script just change the execution +# bits. +# +# By default this script does nothing. + +# Print the IP address +#_IP=$(hostname -I) || true +#if [ "$_IP" ]; then +# printf "My IP address is %s\n" "$_IP" +#fi + +/home/pi/vaani.setup/start.sh & + +exit 0 diff --git a/config/udhcpd.conf b/config/udhcpd.conf new file mode 100644 index 0000000..2794726 --- /dev/null +++ b/config/udhcpd.conf @@ -0,0 +1,118 @@ +# The start and end of the IP lease block +start 10.0.0.2 #default: 192.168.0.20 +end 10.0.0.99 #default: 192.168.0.254 + +# The interface that udhcpd will use +interface wlan0 #default: eth0 +opt subnet 255.255.255.0 +opt router 10.0.0.1 + +# I wonder if I need this. The only time we'll be running is when +# there is no internet connection, so we won't be able to reach this server +# opt dns 8.8.8.8 + + +# I think this might interfere with normal mDNS .local +#opt domain local + + +# The maximim number of leases (includes addressesd reserved +# by OFFER's, DECLINE's, and ARP conficts + +#max_leases 254 #default: 254 + + +# If remaining is true (default), udhcpd will store the time +# remaining for each lease in the udhcpd leases file. This is +# for embedded systems that cannot keep time between reboots. +# If you set remaining to no, the absolute time that the lease +# expires at will be stored in the dhcpd.leases file. + +#remaining yes #default: yes + + +# The time period at which udhcpd will write out a dhcpd.leases +# file. If this is 0, udhcpd will never automatically write a +# lease file. (specified in seconds) + +#auto_time 7200 #default: 7200 (2 hours) + + +# The amount of time that an IP will be reserved (leased) for if a +# DHCP decline message is received (seconds). + +#decline_time 3600 #default: 3600 (1 hour) + + +# The amount of time that an IP will be reserved (leased) for if an +# ARP conflct occurs. (seconds + +#conflict_time 3600 #default: 3600 (1 hour) + + +# How long an offered address is reserved (leased) in seconds + +#offer_time 60 #default: 60 (1 minute) + +# If a lease to be given is below this value, the full lease time is +# instead used (seconds). + +#min_lease 60 #defult: 60 + + +# The location of the leases file + +#lease_file /var/lib/misc/udhcpd.leases #defualt: /var/lib/misc/udhcpd.leases + +# The location of the pid file +#pidfile /var/run/udhcpd.pid #default: /var/run/udhcpd.pid + +# Everytime udhcpd writes a leases file, the below script will be called. +# Useful for writing the lease file to flash every few hours. + +#notify_file #default: (no script) + +#notify_file dumpleases # <--- useful for debugging + +# The following are bootp specific options, setable by udhcpd. + +#siaddr 192.168.0.22 #default: 0.0.0.0 + +#sname zorak #default: (none) + +#boot_file /var/nfs_root #default: (none) + +# The remainer of options are DHCP options and can be specifed with the +# keyword 'opt' or 'option'. If an option can take multiple items, such +# as the dns option, they can be listed on the same line, or multiple +# lines. The only option with a default is 'lease'. + +# Currently supported options, for more info, see options.c +#opt subnet +#opt timezone +#opt router +#opt timesrv +#opt namesrv +#opt dns +#opt logsrv +#opt cookiesrv +#opt lprsrv +#opt bootsize +#opt domain +#opt swapsrv +#opt rootpath +#opt ipttl +#opt mtu +#opt broadcast +#opt wins +#opt lease +#opt ntpsrv +#opt tftp +#opt bootfile +#opt wpad + +# Static leases map +#static_lease 00:60:08:11:CE:4E 192.168.0.54 +#static_lease 00:60:08:11:CE:3E 192.168.0.44 + + diff --git a/index.js b/index.js index 19bb31b..84b936b 100644 --- a/index.js +++ b/index.js @@ -1,27 +1,31 @@ -var wifi = require('./wifi.js'); -var express = require('express'); -var bodyParser = require('body-parser'); -var fs = require('fs'); +var Express = require('express'); var Handlebars = require('handlebars'); var Evernote = require('evernote').Evernote; +var bodyParser = require('body-parser'); +var fs = require('fs'); +var wifi = require('./wifi.js'); +var wait = require('./wait.js'); var evernoteConfig = require('./evernoteConfig.json'); -// Start running the server, then determine whether we need to -// to start a private AP so the user can connect +// Start running the server, then, if we don't have a wifi connection after +// 15 seconds, start a private access point that the user can connect to. startServer(); -wifi.getStatus().then(status => { - // If we don't have a wifi connection, broadcast our own wifi network. - // If we don't do that, no one will be able to connect to the server! - console.log('wifi status:', status); - if (status !== 'COMPLETED') { - wifi.startAP(); - console.log('Started private wifi network VaaniSetup'); - } -}) + +wait(15000) + .then(() => wifi.getStatus()) + .then(status => { + // If we don't have a wifi connection, broadcast our own wifi network. + // If we don't do that, no one will be able to connect to the server! + console.log('wifi status:', status); + if (status !== 'COMPLETED') { + wifi.startAP(); + console.log('Started private wifi network VaaniSetup'); + } + }); function startServer(wifiStatus) { // Now start up the express server - var server = express(); + var server = Express(); // When we get POSTs, handle the body like this server.use(bodyParser.urlencoded({extended:false})); @@ -56,7 +60,6 @@ var statusTemplate = getTemplate('./templates/status.hbs'); // This function handles requests for the root URL '/'. // We display a different page depending on what stage of setup we're at function handleRoot(request, response) { - console.log('handleRoot'); wifi.getStatus().then(status => { console.log("wifi status", status); @@ -102,24 +105,24 @@ function handleConnecting(request, response) { var password = request.body.password.trim(); response.send(connectingTemplate({ssid: ssid})); - // XXX: another problem. I think I need to wait a bit after sending the - // response to be sure it gets through. Now that I'm calling stopAP first - // the network went down before I received the response in my browser. + // Wait before switching networks to make sure the response gets through. + // And also wait to be sure that the access point is fully down before + // defining the new network. + wait(2000) + .then(() => wifi.stopAP()) + .then(() => wait(2000)) + .then(() => wifi.defineNetwork(ssid, password)); - // XXX I've got a problem here. This brings up the new network, and - // we can connect to the device over local wifi. But the device - // still thinks it is 10.0.0.1 instead of using the IP address - // assigned by dhcp and somehow that means that it can't get out to - // the internet. (At least when I'm testing from home where - // 10.0.0.1 is my router) Rebooting fixes things, but I somehow need - // stopAP() to relinquish 10.0.0.1 and get the right address. XXX - // Maybe calling stopAP first before defining the network would - // help? No, that doesn't seem to help with the ip address problem - wifi.stopAP().then(out => wifi.defineNetwork(ssid, password)); + // XXX: it would be cool to monitor the network connection and + // beep (or blink leds) when the network has switched over and the + // user can click the continue button. + // Whether or not I should do that, I should at least modify the + // template so it has a JS-based countdown that makes the user wait + // 20 seconds or something before enabling the continue button. } function handleOauthSetup(request, response) { - response.send(oauthSetupTemplate({})); + response.send(oauthSetupTemplate()); } // We hold our oauth state here. If this was a server that ever had @@ -186,5 +189,12 @@ function handleOauthCallback(request, response) { } function handleStatus(request, response) { + // XXX + // I want to expand the status template so that it actually displays + // the current wifi and oauth status and displays buttons that take + // the user back to the /wifiSetup and /oauthSetup pages. In order to + // do that, this function will need to determine the current network + // and oauth status (e.g. the expiration date of the token) and pass + // those to the template. response.send(statusTemplate()) } diff --git a/start.sh b/start.sh index 861bb61..52edbd4 100755 --- a/start.sh +++ b/start.sh @@ -1,3 +1,6 @@ #!/bin/sh +# This script starts the server and is suitable for use from /etc/rc.local +# The script assumes it is run with root privileges cd `dirname $0` -/usr/local/bin/node index.js > stdout.log 2> stderr.log +/usr/local/bin/node index.js &> `date +%F-%H-%M-%S`.log + diff --git a/wait.js b/wait.js new file mode 100644 index 0000000..b132364 --- /dev/null +++ b/wait.js @@ -0,0 +1,5 @@ +module.exports = function wait(milliseconds) { + return new Promise(function (resolve, reject) { + setTimeout(resolve, milliseconds); + }); +}; diff --git a/wifi.js b/wifi.js index 368a2c4..c6f765d 100644 --- a/wifi.js +++ b/wifi.js @@ -156,15 +156,12 @@ function startAP() { * this point, the AP should be in the process of stopping but may not * yet be completely down. * - * Note that this function does not change the local IP address from 10.0.0.1 - * back to whatever it was before startAP() was called. As far as I can tell - * this does not actually cause any problems. - * * Note that this function requires root privileges to work */ function stopAP() { return run('systemctl stop udhcpd') - .then(output => run('systemctl stop hostapd')); + .then(output => run('systemctl stop hostapd')) + .then(output => run('ifconfig wlan0 0.0.0.0')); } /*