New uses for old Dash buttons

I recently acquired a few Amazon Dash buttons and wondered if they might be repurposed to serve some useful purpose, other than ordering groceries.

I found this article by Ted Benson in which he describes how Dash buttons send an ARP probe after joining the WiFi network. By listening for these ARP probes (and their unique MAC addresses) you can trigger IFTTT Webhooks workflows, which turns the humble Amazon ordering tool into a customisable IoT button.

Bob Steinbeiser replied to Ted’s post with a clever Python script which sniffs ARP packets using a raw socket. Having played with Bob’s script I set about trying to make a few improvements. I’m a novice when it comes to Python though, so forgive my amateur code.

import socket
import struct
import binascii
import urllib2
import time

# Based on an original script by Bob Steinbeiser (https://medium.com/@xtalker)
# Adapted to ignore duplicate presses and added support for multiple IFTTT triggers

# Use your own IFTTT Webhooks key here - see https://ifttt.com/maker_webhooks
ifttt_key = 'abc123'

# MAC addresses and their corresponding IFTTT Webhooks triggers
things = {
    'aabbccddeeff' : ['lights_off', 'sockets_off'],
    'a0b1c2d3e4f5' : ['test_1', 'test_2', 'test_3']
}

last_success={}

for macaddr in things:
    last_success[macaddr] = 0

rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))

while True:
    packet = rawSocket.recvfrom(2048)

    ethernet_header = packet[0][0:14]
    ethernet_detailed = struct.unpack('!6s6s2s', ethernet_header)

    arp_header = packet[0][14:42]
    arp_detailed = struct.unpack('2s2s1s1s2s6s4s6s4s', arp_header)

    # Skip non-ARP packets
    ethertype = ethernet_detailed[2]
    if ethertype != '\x08\x06':
        continue

    source_mac = binascii.hexlify(arp_detailed[5])

    time_now = int(time.time())

    # Is this a known 'thing' ?
    if source_mac in things:

        trigger_list = things[source_mac]

        # Prevent duplicate presses from being actioned (within 10 secs)
        if time_now > last_success[source_mac] + 10:
 
            # Supports multiple IFTTT Webhook triggers
            for trigger in trigger_list:

                print "Device " + source_mac + " has triggered " + trigger
                last_success[source_mac] = int(time.time())
                data = '{ "value1" : "' + source_mac + '", "value2" : "' + trigger + '" }'
                req = urllib2.Request('https://maker.ifttt.com/trigger/' + trigger + '/with/key/' + ifttt_key, data, {'Content-Type': 'application/json'})
                f = urllib2.urlopen(req)
                response = f.read()
                f.close()
                print response

         else:
             print "Ignoring repeated event for " + source_mac

    else:
        print "Ignoring unknown device " + source_mac

Note that this Python script doesn’t work on Mac OS, due to the lack of support for AF_PACKET sockets.

Advertisements

Configure exim4 to use an O2 mail relay

To configure exim4 to use O2’s SMTP server for outbound mail:

(As root) edit /etc/exim4/passwd.client and add your O2 portal authentication credentials.

See ‘man exim4_passwd_client’ for how exim4 parses this file.

Note that smtp.o2.co.uk resolves to the IP address 82.132.141.69, but the reverse DNS for that IP address resolves to mail.o2.co.uk, so you will need to use that name in passwd.client.

The line in passwd.client should look something like this:

mail.o2.co.uk:yourname@o2.co.uk:your_password

O2’s mail servers don’t support TLS so you will need to add the following line to /etc/exim4/exim4.conf.localmacros (just create the file if it doesn’t exist already)

AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS = true

This allows passwords to be sent over an insecure connection. It’s far from ideal but nothing can be done until O2 supports TLS.

Finally run ‘dpkg-reconfigure exim4-config’ to update the mail server configuration.

Select ‘mail sent by smarthost; received via SMTP or fetchmail‘ and when it asks for the smarthost address use smtp.o2.co.uk.

You should now be sending email using O2’s smarthost.

Check /var/log/exim4/mainlog when sending mail to confirm that everything is OK.

PXE boot from a MacBook

In the course of installing Debian Linux on an ALIX.2D3 system I needed to setup a Preboot eXecution Environment (PXE) and all I had to hand was a MacBook. This is how I did it.

There are two elements of the PXE boot, the DHCP server and the TFTP server. DHCP tells the client where to find the network bootstrap program and it then retrieves it using TFTP.

To configure the Mac OS DHCP server you will need to edit /etc/bootpd.plist

The options I used are below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Subnets</key>
  <array>
    <dict>
      <key>allocate</key>
      <true/>
      <key>dhcp_option_128</key>
      <string>192.168.0.1</string>
      <key>dhcp_option_66</key>
      <string>192.168.0.1</string>
      <key>dhcp_option_67</key>
      <string>pxelinux.0</string>
      <key>name</key>
      <string>My subnet</string>
      <key>net_address</key>
      <string>192.168.0.0</string>
      <key>net_mask</key>
      <string>255.255.255.0</string>
      <key>net_range</key>
      <array>
        <string>192.168.0.100</string>
        <string>192.168.0.199</string>
      </array>
    </dict>
  </array>
  <key>bootp_enabled</key>
  <true/>
  <key>dhcp_enabled</key>
  <true/>
  <key>netboot_enable</key>
  <true/>
</dict>
</plist>

dhcp_option_66 is the TFTP Server Name
dhcp_option_67 is the Boot File Name
dhcp_option_128 is TFTP Server IP address

I’m not sure which of dhcp_option_66 or dhcp_option_128 is actually being used here, but since they are both the same it doesn’t harm to leave them both in there.

This DHCP server configuration will allocate an IP address to the connected client in the range 192.168.0.100 – 192.168.0.199.

For this to work you will also need to configure your ethernet interface (usually en0) with the static IP address 192.168.0.1.

To start the DHCP server I used ‘/usr/libexec/bootpd -d’ which tells bootp to stay in the foreground and output additional debug information.

Next we will need to configure and start the TFTP server.

For simplicity I used a neat Mac OS application called TftpServer, which is written by Fabrizio La Rosa.

The application lets you configure the TFTP server path as well as checking path permissions and managing the startup and shutdown of the daemon.

With the DHCP and TFTP servers running and the client connected I copied my Debian network bootstrap files to the TFTP path (in my case /private/tftpboot) and then created a few symbolic links:

cd /private/tftpboot
ln -s ./debian-installer/i386/pxelinux.0 pxelinux.
ln -s ./debian-installer/i386/pxelinux.0 pxelinux.0
ln -s ./debian-installer/i386/pxelinux.cfg pxelinux.cfg

The first symlink is not a typo, it is deliberate. After much trial and error (and ethereal packet sniffing) I discovered that for some reason the requested file kept dropping the trailing zero and so by simply adding the symlink everything worked.

With all this in place I was able to power-on the ALIX.2D3, watch it configure via DHCP and then retrieve and execute the installer.

Please leave a comment if you’ve found any of this useful, that way I won’t feel like I’ve wasted my time writing all this up 🙂

Installing Linux on an ALIX.2D3

I was lucky enough to come by a PC Engines ALIX.2D3 low-power server and set about getting a decent operating system onto it, to hopefully replace my existing over-powered Fedora home server.

This project was far from simple, so for the benefit of anyone else embarking on this endeavour, below is a brain dump of what was required. This is not intended as a complete step-by-step guide as I am doing this from memory, but I believe I have captured most of the pertinent information to get you through.

First things first. The 2D3 does not have a video adaptor and so you will require a null-modem serial console cable. I also needed a serial to USB adapter so I could connect from a MacBook.

The second ‘issue’ is that even though the 2D3 has USB ports, it will only boot from a Compact Flash card. I ordered a Kingston 40x 4GB CD card for under a tenner.

After the CF card arrived I spent a frustrating few days trying to install Debian Lenny.

For simplicity I opted to perform a netboot install. I gleaned a lot of useful information about Debian’s serial console support from Philip Tricca’s Installing Lenny on Alix 2D3 over serial console blog post.

With the 2D3 connected via serial I powered it on and hit ‘S’ during the memory test to enter the system BIOS settings. I then changed the baud rate to 9600 (9), enabled PXE boot (E) and enabled HDD wait (W).

I set up my MacBook as a PXE boot server and the instructions are in my blog post – PXE boot from a MacBook

I also had to slightly modify some of the Debian netboot files to add serial console support. More information and the diffs can be found in this Debian bug report – serial console boot not possible

For compatibility sake I wanted to install ‘Lenny’ and not the latest ‘Squeeze’, however the install menu was not obliging. To choose Lenny I had to switch to an advanced install.

After booting from the network (which was attached to my MacBook), I swapped to an open Internet connection and configured an IP address. This allowed me to carry on with the installation and downloads without having to proxy via the MacBook.

When the installation got as far as detecting the hard disk, which in my case was a Compact Flash card, no matter what I did it would not detect the storage. This foxed me for a few days until by chance I had a USB hard disk connected during the installation and this somehow caused the flash disk to be discovered too.

After selecting the CF card (/dev/hda) I was able to partition the disk and finish the Debian installation.

To protect my CF card from way too many read/writes I opted to permanently add a conventional USB hard disk for the more dynamic data partitions – swap, /tmp, /var and /home.

I simply created appropriate partitions using fdisk on the USB drive (/dev/sda), ext3 formatted the partitions using mkfs.ext3 (mkfs.swap in the case of the swap partition) and set about copying the data from the CF card to the USB disk. Don’t forget to mark the swap partition as type 82 (Linux Swap). I used a 525MB swap partition for this 256MB board.

I arranged my partitions as follows:

/dev/sda1 /tmp (Linux ext3) 518.20MB

/dev/sda2 swap (Linux swap) 526.42MB

/dev/sda3 /var (Linux ext3) 3010.46MB

/dev/sda4 /home (Linux ext3) 496050.19MB

Format the partitions:

mkfs.ext3 /dev/sda1

mkfs.ext3 /dev/sda3

mkfs.ext3 /dev/sda4

mkswap /dev/sda2

Create mount points:

mkdir -p /mnt/tmp /mnt/var /mnt/home

Mount the partitions:

mount /dev/sda1 /mnt/tmp

mount /dev/sda3 /mnt/var

mount /dev/sda4 /mnt/home

Set the ‘sticky’ bit on the new /tmp:

chmod 1777 /mnt/tmp

Copy the data from /dev/hda to /dev/sda:

cp -a /var/* /mnt/var

cp -a /home/* /mnt/home

After the copying has completed I added the USB disk partitions to /etc/fstab so that they would mount at boot time. First we need the device IDs:

# blkid /dev/sda*

/dev/sda1: UUID=”8525ccf6-f4ed-4d40-8e51-54251517fe45″ TYPE=”ext3″ SEC_TYPE=”ext2″

/dev/sda2: TYPE=”swap” UUID=”4975cddf-64fd-4961-9b3a-aa092d3b7e07″

/dev/sda3: UUID=”c236f962-633e-42be-9ca5-428aab9bcbc5″ TYPE=”ext3″ SEC_TYPE=”ext2″

/dev/sda4: UUID=”b853c32f-9786-41e1-b006-e5177ffdc74b” TYPE=”ext3″ SEC_TYPE=”ext2″

I made the required changes to /etc/fstab which now looks like this:

# /dev/hda1 / (4GB Compact Flash)

UUID=e8efa91d-65cd-4525-89e8-0e3e46c5ae3e /     ext3 errors=remount-ro,noatime 0 1

# /dev/sda1 /tmp

UUID=8525ccf6-f4ed-4d40-8e51-54251517fe45 /tmp  ext3 defaults,noatime 0 0

# /dev/sda2 swap

UUID=4975cddf-64fd-4961-9b3a-aa092d3b7e07 none  swap sw 0 0

# /dev/sda3 /var

UUID=c236f962-633e-42be-9ca5-428aab9bcbc5 /var  ext3 defaults,noatime 0 0

# /dev/sda4 /home

UUID=b853c32f-9786-41e1-b006-e5177ffdc74b /home ext3 defaults,noatime 0 0

Note that I added the ‘noatime’ option to prevent inode access times from being updated.

Make sure that the previous (now unused) mount points are commented out (including swap).

I also wanted to move root’s home directory to the USB disk and so created a new /mnt/home/root directory and added the following mountcommand to the end of /etc/rc.local:

mount --bind /home/root /root

Finally, before the reboot you’ll need to add a forced delay into the boot sequence to allow the USB drive to be detected before the mountall script starts. More details of this can be found in my previous blog post – Wait for USB.

Now it’s time to be brave and issue a reboot! Don’t forget to disable PXE boot in the BIOS settings as you shouldn’t be needing that again.

After the reboot:

Use ‘mount’ to check that all the partitions are present and correct:

# mount

/dev/hda1 on / type ext3 (rw,noatime,errors=remount-ro)

tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)

proc on /proc type proc (rw,noexec,nosuid,nodev)

sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)

udev on /dev type tmpfs (rw,mode=0755)

tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)

devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)

/dev/sda1 on /tmp type ext3 (rw,noatime)

/dev/sda3 on /var type ext3 (rw,noatime)

/dev/sda4 on /home type ext3 (rw,noatime)

Use ‘swapon -s’ to check that swap is mounted on the correct partition (should be /dev/sda2):

# swapon -s

Filename                                Type            Size    Used    Priority

/dev/sda2                               partition       514072  564     -1

You can now start to add packages and configure your server as you want it. As the 2D3 does not come with a RTC battery one of the first things I did was install ntpd to keep the time accurate.

Wait for USB

Having installed Debian GNU/Linux 5.0 (“lenny”) on a PC Engines Alix 2D3 system I ran into problems with the external USB disk drive not being mounted at boot-up.

Mounting local filesystems…mount: special device UUID=… does not exist

These error messages were coming when /etc/rcS.d/S35mountall.sh was attempting to mount all the local filesystems from /etc/fstab. In my case the USB subsystem had not yet initialised and so the entries in /dev had not been created yet.

I could simply wait until booting has finished and mount the partitions manually, or add them to /etc/rc.local, but I was after a more elegant solution.

The answer is to introduce a deliberate pause into the boot sequence to allow the USB subsystem to initialise before the mounts are started.

#!/bin/sh
# Script to force boot sequence to wait for USB disk detection
# Copy to /etc/init.d/waitforusb
# ln -s /etc/init.d/waitforusb /etc/rcS.d/S25waitforusb
# Disk drive mount we're waiting on
usbdrive="/dev/sda"
# Max time to wait (in seconds)
maxwait=30
case "$1" in
start)
  echo "Waiting for USB disk drive $usbdrive"
  for (( i = 0; i <= $maxwait; i++ ))
  do
    [ -b $usbdrive ] && exit 0
    echo -n "."
    sleep 1
  done
  exit 1
  ;;
stop)
  ;;
esac

Copy this startup file to /etc/init.d and then create a symbolic link in /etc/rcS.d just before the filesystem checks:

ln -s /etc/init.d/waitforusb /etc/rcS.d/S25waitforusb

The script will check for the disk drive entries in /dev every second and allow boot execution to continue when it’s there. There is a safety timeout which allows execution to continue regardless after 30 seconds.

Squeezebox Server on a budget

Looking for an inexpensive, quiet and low-power Squeezebox Server?

The O2 Joggler is a rebadged version of a OpenPeak OpenFrame 7″ touch-screen device. It has an Intel Atom Z520 CPU running at 1.3Ghz, 512Mb of RAM and 1GB of internal flash storage. You can also run it as a fully functioning Squeezebox Server!

Here’s how:

  • First check that your Joggler is running the latest 26635.S3 (Jun 25 2010) software, if it’s not then update
  • You will need telnet access, so download this Joggler telnet hack
  • Unpack the Zip archive to the root of a USB stick formatted in FAT16 or FAT32
  • Unplug the Joggler, insert the USB stick, then power on again and wait!
  • When the Joggler reboots it will have telnet enabled 🙂
  • Telnet to the IP address of your Joggler and login with the username ‘letmein’
  • Download Logitech’s Squeezebox Server v7.5.1 and save to your PC desktop

Now use these commands to download and install flipflip’s Squeezebox server wrapper:

cd /media
mkdir /media/ssods4 /opt
ln -s /media/ssods4 /opt/ssods4
cd /opt/ssods4
wget http://oinkzwurgl.org/downloads/ssods/ssods-4.9.1-i686.tar.gz
tar -xzvpf ssods-4.9.1-i686.tar.gz
echo "ssods:*:1000:1000:ssods:/opt/ssods4:" >> /etc/passwd
echo "ssods:*:1000:" >> /etc/group
LC_ALL= /opt/ssods4/etc/init.d/rc.ssods start

Assuming all the above has gone ok, you can now open a browser and finish the Squeezebox server installation:

  • Open a web browser on your PC and enter the URL of your Joggler, e.g. http://192.168.1.65:9099/
  • Follow the instructions in SSOXX to upload the squeezeboxserver-7.5.1.tgz file you downloaded earlier and then install the SqueezeCenter tar ball
  • When it’s finished you should see Success messages like the screenshot below
  • Now just click on ‘Start SqueezeboxServer’ to fire it up

If you are going to use your Joggler as a dedicated Squeezebox Server then you might want to make the following tweaks to ensure that it all starts up automatically and unnecessary processes are disabled:

  • Edit  /etc/init.d/boot.d/S99boot.hacks and add the line ‘LC_ALL= /opt/ssods4/etc/init.d/rc.ssods start’ in the starthacks() function, just after the telnetd line should be fine (my S99boot.hacks additions are at the end of this post)
  • Go into the SSOXX settings tab and make sure that autostart is enabled
  • Stop the X11 server and O2 GUI from loading by commenting out the following two lines from the end of /etc/init.d/rcS
cd /openpeak/tango
./run &
  • Disable automatic software updating to ensure that all your good work is not undone in the future:
echo "127.0.0.1 localhost applog.openpeak.net o2.openpeak.com o2.openpeak.co.uk" > /etc/hosts

I want to keep my Joggler in a cupboard and forget all about it, so I have no use for the display.
To save power I switch the screen off entirely, to do this I use Starter’s driver patches:

cd /media
wget http://get.intanet.com/dl/brightness.sh
wget http://get.intanet.com/dl/bp
./brightness.sh allowscreenoff 1
./brightness.sh negativevalues 1

Add the following line to /etc/init.d/boot.d/S99boot.hacks:

/bin/echo "-3">/proc/blctrl"

If you want to mount a Samba/CIFS share (like a NAS mount) you will need to download the cifs.ko kernel module and add that to the startup file.

The starthacks() function in my /etc/init.d/boot.d/S99boot.hacks startup file now looks like this:

starthacks()
{
  # enable telnet
  /usr/sbin/telnetd
  # load CIFS kernel module
  /sbin/insmod /media/cifs.ko
  /bin/sleep 2
  # mount network share
  /bin/mount -t cifs //192.168.1.2/musicshare /mnt/music -o user=music,password=secret
  /bin/sleep 2
  # disable screen
  /bin/echo "-3">/proc/blctrl
  # start SSOXX and Squeezebox Server
  LC_ALL= /opt/ssods4/etc/init.d/rc.ssods start
}

Reboot your Joggler for the display driver patches to load.

Prevent SSH session timeout

When using mobile broadband I found that a SSH shell session to a remote server would terminate unless I used it every few minutes. To overcome this use the ServerAliveInterval option to send a regular keepalive message to the remote server, which should be enough for the ISP firewalls to maintain the TCP session indefinitely.

Either add the SSH option via the command line, e.g.

$ ssh -o ServerAliveInterval=60 user@host

Or add the following line to ~/.ssh/config:

ServerAliveInterval 60  # Send a keepalive every 60 seconds