Getting weather data from an Acu-Rite TXxxxx Bridge

Introduction

The Acu-Rite 5n1 sensor and TX9xxx bridge make a neat little home weather station. However, it has one problem; the data is sent only to their servers. This makes it hard to use the data locally. One solution is to set up My Backyard Weather to forward data to a Weather Underground Personal weather station. This also has a couple of drawbacks. All data is not currently sent (wind gusts for example) and it only sends the data at 15 minute intervals.

There are a couple of ways to work around this.

Hi-jacking the bridge data stream

The bridge sends data to www.acu-link.com using a simple HTTP POST. If you can redirect www.acu-link.com to your own server, you can have a script that reads the data, does what you like with it and then forward it on to the real www.acu-link.com (or not). I run my own local DNS server and web server so this was pretty easy to do.

Advantages. Easy to set up and free if you have the network infrastructure.

Disadvantages. In my case, if my DNS server is down, name resolution falls back to a public DNS server. When this happens, the bridge starts sending directly to the Acu-Rite servers, bypassing my data collection. A reboot of the bridge is required to redirect it back to my server.

A Raspberry PI network bridge

Inserting a Raspberry PI configured as a network bridge between the Acu-Rite bridge and your router also makes it easy to intercept the data. I had a Raspberry PI with Raspbian "wheezy" installed. I plugged in an USB network adaptor that I had laying about. Below are the steps I took to make this work.

  1. Installed bridge-utils, tcpdump, tcpflow, php5, php5-mysql, php5-curl
    • apt-get bridge-utils
    • apt-get tcpdump
    • apt-get tcpflow
    • and so on.
  2. Configured the network interfaces in bridge mode

    To configure the network interfaces in bridge mode you need to modify the /etc/network/interfaces file. Mine looks like this:

    auto lo
    iface lo inet loopback
    auto eth0
    iface eth0 inet manual
    auto eth1
    iface eth1 inet manual
    auto br0
    iface br0 inet dhcp
    bridge_ports eth0 eth1
    

  3. Installed an init script to start monitoring the network and process the data. I did this in two parts. I created a script in /usr/sbin called weather. It looks like:
    	#!/bin/sh
    
    	(/usr/bin/tcpflow -c -i eth1 -s tcp dst port 80 | /home/pi/acu-link/acu-link) 2>> /var/log/weather.log &
    	

    What this does is start the tcpflow program and set it to look for the packets from the bridge. The output of this is sent to my weather processing program (acu-link). I re-direct any error output to /var/log/weather.log so I can monitor that for errors.

    I created an init script and placed it in /etc/init.d It looks like: (this should probably be a link instead of inline)

    	#! /bin/sh
    	### BEGIN INIT INFO
    	# Provides:          weather
    	# Required-Start:    $remote_fs $syslog
    	# Required-Stop:     $remote_fs $syslog
    	# Default-Start:     2 3 4 5
    	# Default-Stop:      0 1 6
    	# Short-Description: Intercept weather data from the network interface.
    	# Description:       Lookd for weather data getting sent by the Acu-rite
    	#                    bridge, decode it, save it in a database, and send
    	#                    it to on-line weather services (WUnderground,
    	#                    WeatherBug)
    	### END INIT INFO
    
    	# Author: Bob Paauwe 
    
    	# Do NOT "set -e"
    
    	# PATH should only include /usr/* if it runs after the mountnfs.sh script
    	PATH=/sbin:/usr/sbin:/bin:/usr/bin
    	DESC="09150TRX Internet Bridge"
    	NAME=weather
    	DAEMON=/usr/sbin/$NAME
    	DAEMON_ARGS=""
    	PIDFILE=/var/run/$NAME.pid
    	SCRIPTNAME=/etc/init.d/$NAME
    
    	# Exit if the package is not installed
    	[ -x "$DAEMON" ] || exit 0
    
    	# Read configuration variable file if it is present
    	[ -r /etc/default/$NAME ] && . /etc/default/$NAME
    
    	# Load the VERBOSE setting and other rcS variables
    	. /lib/init/vars.sh
    
    	# Define LSB log_* functions.
    	# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
    	# and status_of_proc is working.
    	. /lib/lsb/init-functions
    
    	#
    	# Function that starts the daemon/service
    	#
    	do_start()
    	{
    	        # Return
    			#   0 if daemon has been started
    			#   1 if daemon was already running
    			#   2 if daemon could not be started
    			start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
    			|| return 1
    			start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
    			$DAEMON_ARGS \
    			|| return 2
    			# Add code here, if necessary, that waits for the process to be ready
    			# to handle requests from services started subsequently which depend
    			# on this one.  As a last resort, sleep for some time.
    			}
    
    	#
    	# Function that stops the daemon/service
    	#
    	do_stop()
    	{
    		# Return
    		#   0 if daemon has been stopped
    		#   1 if daemon was already stopped
    		#   2 if daemon could not be stopped
    		#   other if a failure occurred
    		start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
    		RETVAL="$?"
    		[ "$RETVAL" = 2 ] && return 2
    		# Wait for children to finish too if this is a daemon that forks
    		# and if the daemon is only ever run from this initscript.
    		# If the above conditions are not satisfied then add some other code
    		# that waits for the process to drop all resources that could be
    		# needed by services started subsequently.  A last resort is to
    		# sleep for some time.
    		start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
    		[ "$?" = 2 ] && return 2
    		# Many daemons don't delete their pidfiles when they exit.
    		rm -f $PIDFILE
    		return "$RETVAL"
    	}
    
    	#
    	# Function that sends a SIGHUP to the daemon/service
    	#
    	do_reload() {
    		#
    		# If the daemon can reload its configuration without
    		# restarting (for example, when it is sent a SIGHUP),
    		# then implement that here.
    		#
    		start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
    		return 0
    	}
    
    	case "$1" in
    	start)
    		[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    		do_start
    		case "$?" in
    		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    		esac
    		;;
    	stop)
    		[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    		do_stop
    		case "$?" in
    		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    		esac
    		;;
    	status)
    		status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
    		;;
    	#reload|force-reload)
    		#
    		# If do_reload() is not implemented then leave this commented out
    		# and leave 'force-reload' as an alias for 'restart'.
    		#
    		#log_daemon_msg "Reloading $DESC" "$NAME"
    		#do_reload
    		#log_end_msg $?
    		#;;
    	restart|force-reload)
    		#
    		# If the "reload" option is implemented then remove the
    		# 'force-reload' alias
    		#
    		log_daemon_msg "Restarting $DESC" "$NAME"
    		do_stop
    		case "$?" in
    		0|1)
    			do_start
    			case "$?" in
    			0) log_end_msg 0 ;;
    			1) log_end_msg 1 ;; # Old process is still running
    			*) log_end_msg 1 ;; # Failed to start
    			esac
    			;;
    		*)
    			# Failed to stop
    			log_end_msg 1
    			;;
    			esac
    		;;
    	*)
    		#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    		echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    		exit 3
    		;;
    	esac
    
    	:
    	
    This is based on the skeleton script with the NAME set to the name of my script in /usr/sbin -- weather

capture details

To capture the data from the ethernet interface I'm using the command

tcpflow -c -i eth1 -s tcp dst port 80

This looks at all network traffic on the second network interface and filters it to only packets going to port 80. Port 80 is the HTTP port. I filter this output in my program that process the raw data from the bridge. Previously, I would do a first level of filtering using strings | grep and then send this filtered output to a php script.

'C' program info

I'm currently using a 'C' program that I created to process the raw data from the bridge and forward it on to Weather Underground and WeatherBug. The main reason I switched from using the PHP script to a 'C' program was so I could make use of pthreads to parallelize sending the data to both weather sites. I was seeing issues where the data would get delayed or lost because one of the weather sites would sometimes take minutes to accept the data.

I'm still seeing cases where one of the sites will timout and refuse my connection. But now this won't effect the data getting sent to the other site or effect it getting inserted into my local database.

What does the program do?

  1. It takes the raw data stream and parses the raw data.
  2. It converts the raw data to usable information
  3. About once a minute, it uploads the data to:
    • A Weather Underground personal station
    • A WeatherBug backyard station
    • A local relational database
    • A simple text log file

acu-link-0.1 source package download

PHP Script info (old)

Why a php script? The script is the same one I ran on my web server that handled the redirected data push from the bridge. It only requried a small change to take the data via STDIN instead of getting it from the $_POST variable.

Command used to capture and process data:
sudo tcpflow -c -i eth1 -s tcp dst port 80 | stdbuf -oL strings | stdbuf -oL fgrep "id=" | php weather.php

Script that processes the data from the bridge. Does three or four things:

  1. Stores data in a local mysql database. Just because I can.
  2. Uploads data to WeatherUndergound PWS (every minute).
  3. Uploads data to WeatherBug backyard station (every minute).
  4. Tracks pressure to provide auto-calibration of barometric pressure

Weather sites info

Weatherbug: http://data.backyard2.weatherbug.com/data/ecv.aspx?ID=p17648&NUM=29244 Weatherbug: http://weather.weatherbug.com/CA/El%20Dorado%20Hills-weather.html?zcode=z6286&stat=p17648 WeatherUnderground: http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KCAELDOR15

Comparison between MBW reporting and local reporting