[IMP] point_of_sale: support Wi-Fi on the posbox

This allows people to connect the posbox to networks using a wireless
network adapter.

When plugging in a USB Wi-Fi adapter and no network cable, the posbox
will boot and host its own Access Point called "Posbox". Users can
connect their device to this network and can then connect to the posbox
the usual way.

An interface for has been provided that also allows users to instruct
the posbox to connect to a different Wi-Fi network. This is useful when
the client is not running Odoo locally.

It is also possible to make this configuration persistent. With a
persistent Wi-Fi network configuration, the posbox will always try to
connect to the specified network after a reboot.

Attempts have been made to make the Wi-Fi connection as robust as
possible. Upon connection loss, the posbox will automatically attempt to
reconnect.
This commit is contained in:
Joren Van Onder 2015-10-07 14:49:28 +02:00
parent a0a4ec6374
commit 546a1d4a32
14 changed files with 447 additions and 4 deletions

View File

@ -193,6 +193,8 @@ class EscposDriver(Thread):
def print_status(self,eprint):
localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
hosting_ap = os.system('pgrep hostapd') == 0
ssid = subprocess.check_output('iwconfig 2>&1 | grep \'ESSID:"\' | sed \'s/.*"\\(.*\\)"/\\1/\'', shell=True).rstrip()
ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
ips = [ ip for ip in ips if ip not in localips ]
eprint.text('\n\n')
@ -201,6 +203,11 @@ class EscposDriver(Thread):
eprint.text('\n')
eprint.set(align='center')
if hosting_ap:
eprint.text('Wireless network:\nPosbox\n\n')
elif ssid:
eprint.text('Wireless network:\n' + ssid + '\n\n')
if len(ips) == 0:
eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
elif len(ips) == 1:

View File

@ -2,6 +2,8 @@
import logging
import os
import time
import werkzeug
import subprocess
from os import listdir
import openerp
@ -43,6 +45,9 @@ index_template = """
to the <a href='/hw_proxy/status'>hardware status page</a>.
</p>
<p>
Wi-Fi can be configured by visiting the <a href='/wifi'>Wi-Fi configuration page</a>.
</p>
<p>
The PosBox software installed on this posbox is <b>version 13</b>,
the posbox version number is independent from Odoo. You can upgrade
the software on the <a href='/hw_proxy/upgrade/'>upgrade page</a>.
@ -60,4 +65,100 @@ class PosboxHomepage(openerp.addons.web.controllers.main.Home):
def index(self):
#return request.render('hw_posbox_homepage.index',mimetype='text/html')
return index_template
@http.route('/wifi', type='http', auth='none', website=True)
def wifi(self):
wifi_template = """
<!DOCTYPE HTML>
<html>
<head>
<title>Wifi configuration</title>
<style>
body {
width: 480px;
margin: 60px auto;
font-family: sans-serif;
text-align: justify;
color: #6B6B6B;
}
</style>
</head>
<body>
<h1>Configure wifi</h1>
<p>
Here you can configure how the posbox should connect to wireless networks.
Currently only Open and WPA networks are supported. When enabling the persistent checkbox,
the chosen network will be saved and the posbox will attempt to connect to it every time it boots.
</p>
<form action='/wifi_connect' method='POST'>
<table>
<tr>
<td>
ESSID:
</td>
<td>
<select name="essid">
"""
try:
f = open('/tmp/scanned_networks.txt', 'r')
for line in f:
line = line.rstrip()
line = werkzeug.utils.escape(line)
wifi_template += '<option value="' + line + '">' + line + '</option>\n'
f.close()
except IOError:
_logger.warning("No /tmp/scanned_networks.txt")
wifi_template += """
</select>
</td>
</tr>
<tr>
<td>
Password:
</td>
<td>
<input type="password" name="password" placeholder="optional"/>
</td>
</tr>
<tr>
<td>
Persistent:
</td>
<td>
<input type="checkbox" name="persistent"/>
</td>
</tr>
<tr>
<td/>
<td>
<input type="submit" value="connect"/>
</td>
</tr>
</table>
</form>
<p>
You can clear the persistent configuration by clicking below:
<form action='/wifi_clear'>
<input type="submit" value="Clear persistent network configuration"/>
</form>
</p>
<form>
</body>
</html>
"""
return wifi_template
@http.route('/wifi_connect', type='http', auth='none', cors='*')
def connect_to_wifi(self, essid, password, persistent=False):
if persistent:
persistent = "1"
else:
persistent = ""
subprocess.call(['/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh', essid, password, persistent])
return "connecting to " + essid
@http.route('/wifi_clear', type='http', auth='none', cors='*')
def clear_wifi_configuration(self):
os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/clear_wifi_configuration.sh')
return "configuration cleared"

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
logger -t posbox_clear_wifi_configuration "Clearing the wifi configuration"
sudo mount -o remount,rw /
sudo rm -f /home/pi/wifi_network.txt
sudo mount -o remount,ro /

View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
# call with ESSID and optionally a password
# when called without an ESSID, it will attempt
# to reconnect to a previously chosen network
function connect () {
WPA_PASS_FILE="/tmp/wpa_pass.txt"
PERSISTENT_WIFI_NETWORK_FILE="/home/pi/wifi_network.txt"
CURRENT_WIFI_NETWORK_FILE="/tmp/current_wifi_network.txt" # used to repair connection when we lose it
ESSID="${1}"
PASSWORD="${2}"
PERSIST="${3}"
sleep 3
sudo pkill -f keep_wifi_alive.sh
# make network choice persistent
if [ -n "${ESSID}" ] ; then
if [ -n "${PERSIST}" ] ; then
logger -t posbox_connect_to_wifi "Making network selection permanent"
sudo mount -o remount,rw /
echo "${ESSID}" > ${PERSISTENT_WIFI_NETWORK_FILE}
echo "${PASSWORD}" >> ${PERSISTENT_WIFI_NETWORK_FILE}
sudo mount -o remount,ro /
fi
else
logger -t posbox_connect_to_wifi "Reading configuration from ${PERSISTENT_WIFI_NETWORK_FILE}"
ESSID=$(head -n 1 "${PERSISTENT_WIFI_NETWORK_FILE}" | tr -d '\n')
PASSWORD=$(tail -n 1 "${PERSISTENT_WIFI_NETWORK_FILE}" | tr -d '\n')
fi
echo "${ESSID}" > ${CURRENT_WIFI_NETWORK_FILE}
echo "${PASSWORD}" >> ${CURRENT_WIFI_NETWORK_FILE}
logger -t posbox_connect_to_wifi "Connecting to ${ESSID}"
sudo service hostapd stop
sudo service isc-dhcp-server stop
sudo pkill wpa_supplicant
sudo ifconfig wlan0 down
sudo ifconfig wlan0 0.0.0.0 # this is how you clear the interface's configuration
sudo ifconfig wlan0 up
if [ -z "${PASSWORD}" ] ; then
sudo iwconfig wlan0 essid "${ESSID}"
else
sudo wpa_passphrase "${ESSID}" "${PASSWORD}" > "${WPA_PASS_FILE}"
sudo wpa_supplicant -B -i wlan0 -c "${WPA_PASS_FILE}"
fi
sudo service dhcpcd restart
# give dhcp some time
timeout 30 sh -c 'until ifconfig wlan0 | grep "inet addr:" ; do sleep 0.1 ; done'
if [ $? -eq 124 ] ; then
logger -t posbox_connect_to_wifi "Failed to connect, forcing Posbox AP"
sudo /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh "force" &
else
logger -t posbox_connect_to_wifi "Restarting odoo"
sudo service odoo restart
logger -t posbox_connect_to_wifi "Starting wifi keep alive script"
/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/keep_wifi_alive.sh &
fi
}
connect "${1}" "${2}" "${3}" &

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
CURRENT_WIFI_NETWORK_FILE="/tmp/current_wifi_network.txt"
while true ; do
if [ -z "$(cat <(ifconfig eth0) <(ifconfig wlan0) | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}';)" ] ; then
ESSID=$(head -n 1 "${CURRENT_WIFI_NETWORK_FILE}" | tr -d '\n')
PASSWORD=$(tail -n 1 "${CURRENT_WIFI_NETWORK_FILE}" | tr -d '\n')
logger -t posbox_keep_wifi_alive "Lost wifi, trying to reconnect to ${ESSID}"
sudo /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh "${ESSID}" "${PASSWORD}"
sleep 30
fi
sleep 2
done

View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
FORCE_HOST_AP="${1}"
WIRED_IP=$(ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}';)
WIFI_NETWORK_FILE="/home/pi/wifi_network.txt"
# if there is no wired ip, attempt to start an AP through wireless interface
if [ -z "${WIRED_IP}" ] ; then
logger -t posbox_wireless_ap "No wired IP"
ifconfig wlan0 down
ifconfig wlan0 up
# wait for wlan0 to come up
sleep 5
# we cannot scan for networks while in Master mode
# so first scan and save the networks to a list
iwlist wlan0 scan | grep 'ESSID:' | sed 's/.*ESSID:"\(.*\)"/\1/' > /tmp/scanned_networks.txt
# only do it when there is a wireless interface
if [ -n "$(iw list)" ] ; then
if [ -f "${WIFI_NETWORK_FILE}" ] && [ -z "${FORCE_HOST_AP}" ] ; then
logger -t posbox_wireless_ap "Loading persistently saved setting"
/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh &
else
logger -t posbox_wireless_ap "Starting AP"
service hostapd restart
ip addr add 10.10.0.1/24 dev wlan0
service isc-dhcp-server restart
service odoo restart
fi
# no wired, no wireless
else
service odoo restart
fi
# wired
else
service odoo restart
fi

View File

@ -0,0 +1,20 @@
# Defaults for hostapd initscript
#
# See /usr/share/doc/hostapd/README.Debian for information about alternative
# methods of managing hostapd.
#
# Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration
# file and hostapd will be started during system boot. An example configuration
# file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
#
DAEMON_CONF="/etc/hostapd/hostapd.conf"
# Additional daemon options to be appended to hostapd command:-
# -d show more debug messages (-dd for even more)
# -K include key data in debug messages
# -t include timestamps in some debug messages
#
# Note that -B (daemon mode) and -P (pidfile) options are automatically
# configured by the init.d script and must not be added to DAEMON_OPTS.
#
DAEMON_OPTS="-d"

View File

@ -0,0 +1,17 @@
# This file may be changed either manually or by running dpkg-reconfigure.
#
# N.B.: dpkg-reconfigure deletes everything from this file except for
# the assignments to variables INTERFACES, HOTPLUG_INTERFACES, ARGS and
# SUSPEND_ACTION. When run it uses the current values of those variables
# as their default values, thus preserving the administrator's changes.
#
# This file is sourced by both the init script /etc/init.d/ifplugd and
# the udev script /lib/udev/ifplugd.agent to give default values.
# The init script starts ifplugd for all interfaces listed in
# INTERFACES, and the udev script starts ifplugd for all interfaces
# listed in HOTPLUG_INTERFACES. The special value all starts one
# ifplugd for all interfaces being present.
INTERFACES="eth0" # auto
HOTPLUG_INTERFACES="eth0" # all
ARGS="-q -f -u0 -d10 -w -I"
SUSPEND_ACTION="stop"

View File

@ -0,0 +1,113 @@
#
# Sample configuration file for ISC dhcpd for Debian
#
#
# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed. We default to the
# behavior of the version 2 packages ('none', since DHCP v2 didn't
# have support for DDNS.)
ddns-update-style none;
# option definitions common to all supported networks...
#option domain-name "example.org";
#option domain-name-servers ns1.example.org, ns2.example.org;
#default-lease-time 600;
#max-lease-time 7200;
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
#authoritative;
# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
# log-facility local7;
# No service will be given on this subnet, but declaring it helps the
# DHCP server to understand the network topology.
#subnet 10.152.187.0 netmask 255.255.255.0 {
#}
# This is a very basic subnet declaration.
#subnet 10.254.239.0 netmask 255.255.255.224 {
# range 10.254.239.10 10.254.239.20;
# option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
#}
subnet 10.10.0.0 netmask 255.255.255.0 {
range 10.10.0.2 10.10.0.254;
option domain-name-servers 8.8.8.8, 208.67.222.222;
option routers 10.10.0.1;
}
# This declaration allows BOOTP clients to get dynamic addresses,
# which we don't really recommend.
#subnet 10.254.239.32 netmask 255.255.255.224 {
# range dynamic-bootp 10.254.239.40 10.254.239.60;
# option broadcast-address 10.254.239.31;
# option routers rtr-239-32-1.example.org;
#}
# A slightly different configuration for an internal subnet.
#subnet 10.5.5.0 netmask 255.255.255.224 {
# range 10.5.5.26 10.5.5.30;
# option domain-name-servers ns1.internal.example.org;
# option domain-name "internal.example.org";
# option routers 10.5.5.1;
# option broadcast-address 10.5.5.31;
# default-lease-time 600;
# max-lease-time 7200;
#}
# Hosts which require special configuration options can be listed in
# host statements. If no address is specified, the address will be
# allocated dynamically (if possible), but the host-specific information
# will still come from the host declaration.
#host passacaglia {
# hardware ethernet 0:0:c0:5d:bd:95;
# filename "vmunix.passacaglia";
# server-name "toccata.fugue.com";
#}
# Fixed IP addresses can also be specified for hosts. These addresses
# should not also be listed as being available for dynamic assignment.
# Hosts for which fixed IP addresses have been specified can boot using
# BOOTP or DHCP. Hosts for which no fixed address is specified can only
# be booted with DHCP, unless there is an address range on the subnet
# to which a BOOTP client is connected which has the dynamic-bootp flag
# set.
#host fantasia {
# hardware ethernet 08:00:07:26:c0:a5;
# fixed-address fantasia.fugue.com;
#}
# You can declare a class of clients and then do address allocation
# based on that. The example below shows a case where all clients
# in a certain class get addresses on the 10.17.224/24 subnet, and all
# other clients get addresses on the 10.0.29/24 subnet.
#class "foo" {
# match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
#}
#shared-network 224-29 {
# subnet 10.17.224.0 netmask 255.255.255.0 {
# option routers rtr-224.example.org;
# }
# subnet 10.0.29.0 netmask 255.255.255.0 {
# option routers rtr-29.example.org;
# }
# pool {
# allow members of "foo";
# range 10.17.224.10 10.17.224.250;
# }
# pool {
# deny members of "foo";
# range 10.0.29.10 10.0.29.230;
# }
#}

View File

@ -0,0 +1,45 @@
# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.
# Allow users of this group to interact with dhcpcd via the control socket.
#controlgroup wheel
# Inform the DHCP server of our hostname for DDNS.
hostname
# Use the hardware address of the interface for the Client ID.
#clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
duid
# Persist interface configuration when dhcpcd exits.
persistent
# Rapid commit support.
# Safe to enable by default because it requires the equivalent option set
# on the server to actually work.
option rapid_commit
# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes
# Most distributions have NTP support.
option ntp_servers
# Respect the network MTU.
# Some interface drivers reset when changing the MTU so disabled by default.
#option interface_mtu
# A ServerID is required by RFC2131.
require dhcp_server_identifier
# Generate Stable Private IPv6 Addresses instead of hardware based ones
slaac private
# A hook script is provided to lookup the hostname if not set by the DHCP
# server, but it should not be run by default.
nohook lookup-hostname
# dhcpcd will assign zeroconf 169.254.*.* addresses when
# it can't connect, which we don't want
noipv4ll

View File

@ -0,0 +1,3 @@
interface=wlan0
ssid=Posbox
channel=1

View File

@ -21,5 +21,6 @@ mkdir -p /var/run/odoo
chown pi:pi /var/run/odoo
/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/led_status.sh &
/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh &
exit 0

View File

@ -31,7 +31,7 @@ apt-get -y autoremove
apt-get update
apt-get -y dist-upgrade
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-simplejson python-tz python-unittest2 python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen"
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-simplejson python-tz python-unittest2 python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server"
apt-get -y install ${PKGS_TO_INSTALL}
@ -64,7 +64,8 @@ chmod 644 /etc/logrotate.conf
echo "* * * * * rm /var/run/odoo/sessions/*" | crontab -
update-rc.d odoo defaults
update-rc.d -f hostapd remove
update-rc.d -f isc-dhcp-server remove
# https://www.raspberrypi.org/forums/viewtopic.php?p=79249
# to not have "setting up console font and keymap" during boot take ages

View File

@ -41,7 +41,6 @@ odoo.py" | tee --append .git/info/sparse-checkout > /dev/null
git read-tree -mu HEAD
cd "${__dir}"
# rc.local
LOOP_MAPPER_PATH=$(kpartx -av posbox.img | tail -n 1 | cut -d ' ' -f 3)
LOOP_MAPPER_PATH="/dev/mapper/${LOOP_MAPPER_PATH}"
mkdir "${MOUNT_POINT}"