SMS Swimming Lap Counter in bash with Twilio

Kate’s participating in a semester long 100 mile swim challenge at CSUF through the Student Rec Center (SRC).  They’ve got a small pool you can swim laps in, and there’s a prize if you swim a certain number of miles.  To help with tracking and motivation, I wrote a super simple set of scripts for her to keep track of her miles and send her daily stats reminders.

The application is just written in bash, because bash is easy, and can be enabled for CGI in apache.  Let’s get to it!  I wanted this to be text message based, so I started with Twilio.  I purchased a new number for $1/month and set it up to handle incoming text messages with a Webhook:

So incoming messages will hit laps.sh with a query string FULL of information.  I only care about the incoming number and the message.  Let’s take a look at laps.sh:

#!/bin/bash

echo "Content-type: text/html"
echo ""

MESSAGE=$(echo $QUERY_STRING | tr '&' '\n' | egrep '^From=|^Body=' | tr '=' ' ' | awk '{ print $4 $2 }')
MSGARRAY=($MESSAGE)

## make sure it's kate
[ ${MSGARRAY[1]} == "%2B1626xxxxxxx" ] || exit 1

## make sure it's a number greater than zero
[ ${MSGARRAY[0]} -ge 1 ] || exit 1

## record value
echo -n ${MSGARRAY[0]} >> records.csv
echo -n " " >> records.csv
date +%Y-%m-%dT%H:%M:%SZ >> records.csv

## get number of days left
NOW=$(date +%s)
END="1576886400"
DAYS=$((($END-$NOW)/86400))

## get total laps swam
SWAM=$(awk '{ sum += $1 } END { print sum }' records.csv)

## lap math
TOTALLAPS="7000"
LAPSLEFT=$(($TOTALLAPS-$SWAM))
if [ $DAYS -ge 1 ]
then
  LAPSDAY=$(($LAPSLEFT/$DAYS))
else
  LAPSDAY=$LAPSLEFT
fi

RESPONSE="Added ${MSGARRAY[0]} laps to the counter!%0aThat is $LAPSLEFT laps to go!%0a$DAYS days until the end...%0aThat is $LAPSDAY laps per day if you swam every day!"

KATE="+1626xxxxxxx"
curl --silent -XPOST -d "Body=$RESPONSE" -d "From=+1657yyyyyyy" -d "To=$KATE" "https://api.twilio.com/2010-04-01/Accounts/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Messages" -u "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

The script is pretty self explanatory, for the most part.  The content type stuff is required or else apache gives out 500’s.  The MESSAGE variable is just breaking apart the query string into multiple lines, grepping for the two lines I actually care about, and the following line puts them into a string array.

The “From” field needs to match Kate’s phone number, or else I just exit.  The first word of the body needs to actually be a number greater than zero, or else we exit.  Once that’s validated we store the number of laps and the date in a local csv file.

There’s 70 laps in a mile in this pool.  The ending date is in unix time (Dec 21 2019 in this case).  The rest is just a little math to build a message, and then a curl call to send a response message.

I also call a dailyreminder.sh file from a cron job.  It’s pretty much the same thing but without the query string handling and a slightly different message.  I hope she makes it to 100 miles!

Christmas Tree water level sensor and text alerting

Dry Christmas trees are a major fire hazard. I don’t have to link the videos, but they go up in flames super fast. My wife and I are pretty forgetful sometimes so I wanted to find some way of making sure that our tree didn’t go too long without a drink!

Last year I used some soil moisture sensors (but purchased in a 5-pack on ebay for cheap). I ended up using the digital outputs, suspending it in the water between the bolts on the tree stand, and hooking it up to a raspberry pi. It looked like this:
soil sensor in tree water
Well, it sort of worked.. when the water level dropped enough, it changed signals. The problem is that it wasn’t designed to be in tree-sap infested waters. So eventually it just stopped working entirely.

This year I’d like to do better. So instead of using a soil moisture sensor, I purchased a pack of DP5200 float sensors.

With a little bit of corrugated plastic (thank you election season signs) and hot glue, it was pretty easy to suspend the float at an appropriate level in the christmas tree stand:

With my new dupont connector kit it was a breeze to connect this to my Raspberry Pi.

I’m connecting the sensor to the I2C pin and ground on the Raspberry pi as it already has an internal pull-up resistor and I don’t need to add an external one for the switch.

The code consists of two new files and one modified rc.local for startup. I set up an account on Twilio to send the text messages.

First, the initialization file at /root/tree/init.sh — this simply initializes the GPIO as an input.

#!/bin/bash

echo 3 > /sys/class/gpio/export
echo in >  /sys/class/gpio/gpio3/direction

Second, the actual monitoring code at /root/tree/monitor.sh — This sends an initial state to the numbers, and then watches the state for a consistent seconds of 0 or 1, and will only send an alert if the state actually changed and was consistent for 15 full seconds. So, in the actual event of a state change from empty to full or full to empty, it will take between 16 and 30 seconds to receive an alert.

#!/bin/bash

function text {
  ## $1 = number
  ## $2 = status.  0=needs water, 1=plenty of water
  [ $2 -eq 0 ] && MESSAGE="🎄 I'M SO THIRSTY!!! 🎄"
  [ $2 -eq 1 ] && MESSAGE="🎄 I am a happy tree 🎄"
  curl -X POST -F "Body=$MESSAGE" -F "From=+1657wwwxxxx" -F "To=$1" "https://api.twilio.com/2010-04-01/Accounts/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Messages" -u "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}

KATE="+1626yyyzzzz"
RAY="+1626xxxyyyy"
WAITS=$(seq 1 15)

OLDSTATE=$(cat /sys/class/gpio/gpio3/value)

text $RAY $OLDSTATE
text $KATE $OLDSTATE

while true
do

  COUNT=0
  for i in $WAITS
  do
    # 0 needs water, 1 has enough water
    STATE=$(cat /sys/class/gpio/gpio3/value)
    let "COUNT = COUNT + STATE"
    sleep 1
    echo $COUNT
  done

  # only message on consistent change
  if ! ((COUNT % 15))
  then
    if [ $STATE -ne $OLDSTATE ]
    then
      OLDSTATE=$STATE
      text $RAY $STATE
      text $KATE $STATE
    fi
  fi

sleep 1
done

Finally, a simple modification to /etc/rc.local, add these two lines before “exit 0”

/root/tree/init.sh
/root/tree/monitor/sh &

That’s it! Super easy and fun, and no tree fires!