<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sodnpoo.xsl"?>
<xml>
<post>
  <tag value="doorbell"/>
  <title>doorbell to real time android alert</title>
  <date>7 Jul 2014</date>
  <p>
With the <a href="/posts.xml/simple_ac_doorbell_to_gpio_interface.xml">doorbell physical hardware interface</a> working reliably, I just needed to connect up some software to send an alert to my phone. Initially I thought I'd avoid having to write any notification code by leaning on something in the cloud; I found <a href="http://www.realtime-security.de/">Server Push Notifications</a> that has a free/lite android app and has simple URL based API - this is important as although we have a full OS on the DG834v4, it has only 16MB RAM; with only about 800K free. The hotplugd system on openwrt can run arbitrary scripts on button presses and was configured to call wget with the appropriate URL to generate the notification. This worked well in testing but in real use, the latency of the notification going out to the internet, being processed somewhere and then coming back again meant I still sometimes missed the door.
  </p>
  <p>
To reduce the latency I decided all network traffic should be local-only. A few months ago, at work I wrote some UDP broadcast P2P-discovery code; broadcast traffic is link-local and UDP is very lightweight - this seemed ideal: broadcast packets could be generated on the DG384v4 using socat and some sl4a+python code running on my phone could simply listen for them. Again early testing indicated success, but as soon as the phone's screen switched off and it went into sleep, the network stack just stopped passing the broadcast traffic to the python code. It seems obvious now that this would happen, keeping the networking running on the off chance of receiving a broadcast is pretty wasteful from a power/battery point-of-view.
  </p>
  <p>
Making the phone poll is a much better idea; the phone can wake up, check the doorbell and go back to sleep again. Polling once every few seconds shouldn't have a measurable impact on battery life, the phone will likely be waking up at least that often to service one of the many long running processes it already has to deal with.
  </p>
  <p>
If the phone is now polling, rather than listening for the doorbell notification, the state will need to be remembered for a short time to ensure that the phone has seen it. Implemented in a simple UDP server, is a latch-like mechanism where normally, when polled it'll return a false response unless it has been triggered, where it'll return a true for a short time instead. 
  </p>
  <p>
I verified that the phone continued to poll while asleep and although it does slow the rate down to around once per minute when doing nothing, most importantly it polls at the correct rate when playing music - which is the time I need it most :)
  </p>
  <p>
The UDP server has been coded in C to keep the memory footprint small, so it can be run directly on the memory-squeezed DG834v4. The original proof-of-concept was written in python, running on my desktop machine; it's memory usage weighed in at around 6MB, the C version is around 400K. Neither code base has been optimised at all however. 
  </p>
  <p>
To compile, you'll need to download and build the openwrt toolchain. First, clone the git repo:  
  </p>
  <pre>
git clone git://git.openwrt.org/openwrt.git
  </pre>
  <p>
Then use the menu system to select your architecture:
  </p>
  <pre>
make menuconfig
  </pre>
  <p>
And to just build the toolchain:
  </p>
  <pre>
make prepare
  </pre>
  <p>
Next we need to set the 'STAGING_DIR' environment variable - the actual directory will vary depending on the architecture, gcc version etc:
  </p>
  <pre>
STAGING_DIR=/full/path/to/openwrt/staging_dir/toolchain-mips_mips32_gcc-4.8-linaro_uClibc-0.9.33.2/
export STAGING_DIR
  </pre>
  <p>
And finally to actually do the compile (again - will vary on arch/versions):
  </p>
  <pre>
/full/path/to/openwrt/staging_dir/toolchain-mips_mips32_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-gcc udp-doorbell-server.c -o udp-doorbell-server
  </pre>
  <p>
Here's the C source (udp-doorbell-server.c):  
  </p>
  <pre>
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;stdio.h&gt;
#include &lt;signal.h&gt;

time_t last = 0;

void sig_handler(int signo){
  if (signo == SIGUSR1) {
    printf("received SIGUSR1\n");
    last = time(NULL);
  }
}

int main(int argc, char**argv){
  if (signal(SIGUSR1, sig_handler) == SIG_ERR){
    printf("\ncan't catch SIGUSR1\n");
  }

  int sockfd,n;
  struct sockaddr_in servaddr,cliaddr;
  socklen_t len;
  char mesg[1000];

  sockfd=socket(AF_INET,SOCK_DGRAM,0);

  bzero(&amp;servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
  servaddr.sin_port=htons(32000);
  bind(sockfd,(struct sockaddr *)&amp;servaddr,sizeof(servaddr));

  for (;;){
    len = sizeof(cliaddr);

    n = recvfrom(sockfd, mesg, 1, 0, (struct sockaddr *)&amp;cliaddr,&amp;len);
    
    char result = '0';
    time_t now = time(NULL);
    if((int)now-30 &lt; (int)last){
      result = '1';
    }
    
    printf("now: %d,\tlast: %d\n", (int)now, (int)last);
    printf("result: %c\n", result);

    sendto(sockfd, &amp;result, 1, 0, (struct sockaddr *)&amp;cliaddr,sizeof(cliaddr));
  }
}
  </pre>
  <p>
It listens on port 32000 and is triggered by a SIGUSR1 signal - it'll remain in the triggered state for 30 seconds; in the hotplugd button config is this:  
  </p>
  <pre>
killall -USR1 udp-doorbell-server
  </pre>
  <p>
Last but not least is the SL4A/Python code that runs on the phone:
  </p>
  <pre>
import android
import time
import socket


default_soundfile = '/system/media/audio/notifications/S_Good_News.ogg'
doorbell_net = '"WIRELESS"'
doorbell_server = ('192.168.1.250', 32000)

device = None # running locally or via adb

def alert(msg, title='ALERT!', soundfile=default_soundfile, silent=False):
  droid.vibrate(2000)
  droid.makeToast('%s %s' % (title,msg))
  droid.notify(title, msg)
  
  if silent is False:
    droid.mediaPlay(soundfile, soundfile)
    while droid.mediaIsPlaying(soundfile) is True:
      time.sleep(0.1)
  
droid = android.Android(device)
alert("starting", title="startup", silent=True)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(1)
c = doorbell_server
while True:

  try:
  
    network = droid.wifiGetConnectionInfo()[1]['ssid']
    if network == doorbell_net:
      
      s.connect(c)
      s.sendto('0', c)
      data = s.recv(1)
      print "d:", data
      
      d = int(data)
      if d == 1:
        alert("Door bell")

  except socket.error as e:
    print "e: %s" % e
  
  time.sleep(5)
  </pre>
  <p>
It'll only try and poll when the phone is connected to the 'WIRELESS' network; if it is, and it detects the trigger it'll make the phone run a of bunch alerts (vibrate/toast/notify/play media) - you'll likely need to change default_soundfile, doorbell_net and doorbell_server to match your environment.
  </p>
  <p>
Hopefully I won't miss any more parcels now :)  
  </p>
</post>
</xml>

