so

Vibration detection

Volume 1, Issue 17; 18 Nov 2017

My first “hardware” project in a long time combines a sensor and a Raspberry Pi and a web service. As one does.

You say Rube Goldberg like it’s a bad thing.

The problem: although my apartment is small, it does have a washer/dryer. The washer/dryer is, thankfully, about as far from my office as you can get (i.e. not far). Combine that with the fact that I’ve almost always got my Sonos playing, and I close my office door if I can still hear the washing machine after that, and you can probably see where this is going: throw in a load of laundry at 8:00a, plan to check it at 8:45a. Finally notice it, cold, wet, and permanently wrinkled at 2:30p. Curse loudly.

At this point, a sensible person would set an alarm on their phone or something and be done with the problem. Except between the time I put the laundry in, planning to set an alarm, and ten seconds later when I reach my office, I’ve usually forgotten about the alarm. And if I remember to set the alarm, there’s still a good chance that I’ll dismiss it (“right, I’ll check that in one minute”) without ever actually checking the laundry.

A while back, I decided what would be fun to build would be a little IoT gadget that would detect the vibration of the machine and notify me when it stopped. Persistently notify me.

I bought an Arduino for this purpose, and a package of sensors, but I confess I have never quite figured out how to program the Arduino. So the project got shelved and for many months the laundry continued to be cold, wet, and wrinkled. Or in the case of the dryer: cold, dry, wrinkled, and shrunk.

Somewhere along the way, I picked up a Raspberry Pi, which has also mostly been gathering dust. (I didn’t realize until a couple of weeks ago that one of my monitors actually does have an HDMI input.) Last weekend, I setup Pi Hole on the Pi. Oh, man, is that useful and fun! Almost immediately, I wondered, could I use that to make my little gadget? A quick web search confirmed that absolutely, I could!

I ordered another couple of Pis (gonna set up Pi Hole at a friend’s house this weekend), a few “SW-420 Vibration Sensor Modules”, and a wifi USB stick.

Et voilà!

[Image]
Vibration detector

Except…there’s still the question of monitoring the sensor and sending notifications. Let’s dispatch with the notifications part first. I cobbled together a simple web server to run on my laptop. If you post a notification to it, it’ll use notify-send to make a notification bubble on my desktop. (And by “you” I mean “me” because I’m not making this service available to the general public, thank you very much.)

If you’re curious, here it is (if you’re not curious, just skip over it):

#!/usr/bin/python3

"""
notify-server.py is a simple web notification service.

The incoming message is displayed using "notify-send". That works on
modern Linux desktops. Don't know about other platforms. Something
else, I suppose.
"""

from subprocess import call
from bottle import route, run, request, response

@route('/hello')
def hello():
    """ Say hello. A simple way to see if the service is running. """
    return {"status": "ok", "message": "hello"}

@route('/notify', method="POST")
def notify():
    """ Notify the user. """
    message = request.forms.getunicode("message") # The curl way
    title = request.forms.getunicode("title")
    icon = request.forms.getunicode("icon")
    if message is None:
        message = request.query.getunicode("message") # The URI param way
        title = request.query.getunicode("title")
        icon = request.query.getunicode("icon")

    if message is None:
        response.status = 400
        return {"status": "error", "message": "No message parameter provided"}
    else:
        cmd = ["/usr/bin/notify-send", "-u", "normal"]
        if icon is not None:
            cmd.append("-i")
            cmd.append(icon)
        if title is not None:
            cmd.append(title)
        else:
            cmd.append("Notice")
        cmd.append(message)
        print(cmd)
        call(cmd)
        return {"status": "ok"}

run(host='0.0.0.0', port=7788)

With that out of the way, it’s just a question of generating the notifications from the Pi. I really don’t know anything about how the Raspberry Pi GPIO system works. I do, however, know how to cut-and-paste. In this case, from an Instructable about how to do exactly this.

Here’s my first crude attempt. Not, I expect, a shining example of good Python style. Bit of a hack job, really. I’ll see about cleaning it up when I add some error checking ☺.

#!/usr/bin/python3

import sys
import RPi.GPIO as GPIO
import time
import requests

ONE_SECOND = 1
TEN_SECONDS = 10
THIRTY_SECONDS = 30
ONE_MINUTE = 60

HOSTS = ["wpad530", "wpad530w"]
PORTS = [7788]

# GPIO setup
channel = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(channel, GPIO.IN)

lastMovement = None

def millis():
    return int(round(time.time() * 1000.0))

def callback(channel):
    # We don't care about GPIO.input(channel)
    # If we got called, there was motion
    global lastMovement
    lastMovement = millis()

def check_server(host, port):
    server = "http://{0}:{1}".format(host,port)
    uri = server + "/hello"
    try:
        resp = requests.get(uri)
        if resp.status_code == 200:
            return server
    except:
        pass

    return None

def find_server():
    for host in HOSTS:
        for port in PORTS:
            server = check_server(host,port)
            if server is not None:
                return server + "/notify"
    return None

def notify(server, title, message):
    requests.post("{0}?icon=dialog-warning&title={1}&message={2}" \
                  .format(server, title, message))

GPIO.add_event_detect(channel, GPIO.BOTH, bouncetime=300)
GPIO.add_event_callback(channel, callback)

message = " ".join(sys.argv[1:])
if message != "":
    message = ": " + message

# Can we talk to the notify server
notify_server = find_server()
if notify_server is None:
    print("Error: cannot find notification server!")
    sys.exit(1)

interval = ONE_SECOND
startTime = millis()
lastMovement = startTime
while True:
    if lastMovement:
        duration = (millis() - lastMovement) / 1000.0
        if duration > TEN_SECONDS:
            notify(notify_server,
                   "Vibration alert",
                   "No activity for {0}s{1}" \
                   .format(int(round(duration)), message))
            # Wait at least 30 seconds before sending another notice
            interval = THIRTY_SECONDS
        else:
            interval = ONE_SECOND
    time.sleep(interval)

Basically: check for vibration every second. If there hasn’t been any for 10 seconds, send a notification and change the wait interval to 30 seconds. Every thirty seconds, if there’s been no new vibration, resend the notification. If there has been vibration, go back to checking every second.

So does it work? Of course it does!

[Image]
 Pi Notification

Uh. At least, it tests well. I got this finished a couple of hours ago, too late to test on a real laundry run. Maybe tomorrow.

Update 20 Nov 2017

It turns out the SW-420 isn't really sensitive enough for this application. It works, but not reliably. I have a couple of ideas. I think I might get an MPU-6050 accelerometer and try that. Another possibility is building a rig that amplifies the vibration. A third possibility is to investigate a sensor that detects the noise of the machine.

I'm not really that surprised, and it was still a fun build.


  • Remember, the “s” in “IoT” stands for “security”.

Please provide your name and email address. Your email address will not be displayed and I won’t spam you, I promise. Your name and a link to your web address, if you provide one, will be displayed.

Your name:

Your email:

Web address:

Do you comprehend the words on this page? (Please demonstrate that you aren't a mindless, screen-scraping robot.)

What is seven plus three?  (e.g. six plus two is 8)

Enter your comment in the box below. You may style your comment with the CommonMark flavor of Markdown.

All comments are moderated. I don’t promise to preserve all of your formatting and I reserve the right to remove comments for any reason.