You're on our GB site browsing in US. Switch over to browse in your local language.

Project

Pantry Monitor A Deep Dive Into Rapid Prototyping

Scroll down

In an effort to live frugally and put money towards savings, I eat a lot of beans and rice. My electric pressure cooker is a dear friend. The problem: I don’t like running out of something and I can’t always be bothered to go to the store in the middle of the week. I like to buy things in batches. Therefore: my goal for this project was to make something that will tell me when I’ll run out of each staple.

Initially, I considered using multiple scales to measure different staples. It soon made more sense to use a single scale (cheap for one, expensive for multiple) and select the relevant staple to log with a button press. This meant, also, that I got to play with light-up arcade-style buttons and reduced the project’s overall complexity. As I didn’t have access to a laser cutter or 3D printer, I used a cardboard shipping box (the kind that goes together without tape). If you invert them, hiding any shipping labels and printed logos on the inside, you get a shockingly clean looking project box for basically nothing. As cardboard is extremely forgiving, you also have the flexibility to make small cutouts, curved cuts and alterations with just a hobby knife.
A major piece I should mention: for talking to the USB scale, it’s probably easier to start with someone else’s working solution than do what I did and muck around with manual byte manipulation. I’ve a little bit of a masochist streak so I did it anyway, but it’s not recommended. I’ll give a bird’s-eye view of how I’m talking to the scale but not dive too deep into the implementation. It’s a little… ugly.
From the start, I chose to track 5 staples. That means, a button for each staple and an LED for each button. The pi needs to listen the states of 5 buttons and control 5 LEDs. This number could be scaled up to 10 buttons and 10 different staples (or hundreds if you want to get creative), it’s really just a matter of patience (more soldering and wire routing) and choosing new GPIO pins.

If this project was to live in an active kitchen instead of a lower-traffic pantry, I think I’d want to put it in a proper case with some decent waterproofing. These buttons aren’t meant for harsh environments– waterproofing and dust resistance are a whole different animal that I haven’t tried to tackle here.

As I began working on this project, I didn’t start with a fined-grained picture of how the parts would interact. I find that it’s faster and more informative to start with small, almost trivial parts and iteratively build towards something useful. Get one component to talk to another, package that into a nice black box and move onto a bigger problem. If we make a big, elaborate plan from the start, it’s hard to see edge cases and notice places where the design has holes. Before the scale arrived, I tried this very thing and started guessing how I might plug an unknown component into the system. As I figured out how I’d talk with the scale, I ended up scrapping almost all of that mock-up.

Setup Your Pi

SD CARD SETUP:

Download image of raspbian via torrent (https://www.raspberrypi.org/downloads/raspbian/)

For a decidedly less scary approach, we can use a simple, easy to use tool like Etcher (http://www.balena.io/etcher/). If you’re feeling adventurous, we can also write the image directly to a disk using dd.

A warning: dd doesn’t care about your data. Without the slightest hesitation, it will devour and replace your data with random noise.

 Make VERY sure you’re pointing at the right device before proceeding. Do NOT write to /dev/sda. That’s where the OS lives, and, you want to have a functional computer, right? For this slightly harrowing process, follow a very thorough, well-written tutorial here. The relevant device will only appear when your SD card is mounted. By default dd will not display any output. There are other tools like pv that will help display your progress, see this person’s question for more details.

HEADLESS MODE (optional):

In this section, I’ve outlined how to make your Pi boot up and connect to your wifi without a monitor or any peripherals. Because sometimes, like me when I wrote this, you don’t have an HDMI-screen or even a USB keyboard. To be clear, things are a whole lot easier if we connect a screen and a keyboard: if you’re working locally, debugging and editing on the pi will be easier, there will be fewer slowdowns and you’ll get immediate feedback when things aren’t working.

Once we’ve burned the Raspbian image to the SD card (see previous section for details), plug in the SD card to another computer.

NOTE: If you’ve used a tool like NOOBS, it may expect you to run through some setup and configuration with a monitor and keyboard and you won’t yet have an accessible /boot directory, it’s just a blank slate for installing the OS from scratch, it’s not even there yet.

Assuming your SD card appears to be the root directory of a linux system (it should have directories like /etc/, /bin/l and /usr), navigate to the boot directory and initialize the file:

cd /Volumes/boot; touch ssh

This allows the user to login over ssh. otherwise any attempts are blocked. To automatically connect to your local Wifi network, we need to configure WPA_supplicant (given SSID and password), we’ll need to create a WPA_supplicant config file. Follow the steps here. Next, we need to find the Pi on your local network. To find the pi, first we can try pinging it:

ping -c 3 pi@raspi.local

If that works, you’ll see something like this:

From there, we have the IP address and we can try logging in over SSH:

ssh pi@192.168.0.11

We should see something like this:

If that doesn’t work, and we don’t find the pi by pinging it, you can sometimes look at the config page for your router and check for connected devices. Type ‘192.168.1.1’ into your browser’s search bar and find the relevant page. It’s going to be different for every router so you’re on your own there. Barring that, we can follow stackoverflow’s advice and install then run nmap. Once you have a list of IPs, try pinging each one then logging in over ssh. It’ll reject you if there’s no user named pi. (default username: pi, default password: raspberry)

Setup Your Dev Environment

Once successfully logged into the pi, make sure we change the default password and give ourselves some semblance of security. Enter the following and follow the onscreen prompts:

passwd

To run the scripts I’ve written, we’ll need to install the relevant libraries. apt-get installs multiple packages at once if you separate them by spaces after the install argument. Remember, when you’re on Linux, case matters (‘A’ ≠ ‘a’). From the pi’s console, type:

sudo apt-get install python-dev python3-rpi.gpio

pip3 install notify-run

Then, install your favorite editor and get yourself some version control:  

sudo apt-get install vim git

And, because it’s nice to work in a comfortable setting, change the pi’s default text editor to your one of choice:    

sudo update-alternatives --config editor

You may find it’s easier to do some deal of the trial and error (especially the stuff that doesn’t involve the GPIO pins) locally on a fast, comfortable machine and then move things over when it works nicely. Over an SSH connection, I find that the pi is sometimes sluggish to accept rapid inputs. If I’m editing things on the pi itself, I like to open two terminals, one for vim and the other for a prompt.

Shameless plug for vim: nano is okay, but vi (installed by default) and vim (the modern version) are vastly more powerful. If you’re curious (and not afraid of keyboard shortcuts) type vimtutor into your shell prompt and follow along.

Git Some Version Control

If you care about not losing your work, use version control. Prototyping a particular behavior can be tricky. You might make changes and realize that you had something that worked better a few hours ago and you’ve since saved and rebooted your computer, twice. If you’re not on git or similar, you’re out of luck. Oh, catastrophic hard drive failure? You’re covered too, it’s saved off-site.

Each time I switch between machines (laptop to pi, pi to laptop), I like to push my changes to github, (git commit -a, git push origin master) then immediately git pull origin master on the other machine. If say, one version gets changes that the other does not, there are ways of merging changes: that’s the whole idea with version control. This, though (alternating between computers and saving each change to github) is an easy and predictable enough workflow for my purposes and I’ve stuck with it.

I’m not going to go into setting up a new repository here, something like this should do the trick. Once git is installed, change your name and email in git so that you can commit new changes to your repository (and have those changes logged to your commit history):    

git config --global user.name "<your name>"

git config --global user.email "<your email>

If you’re creating a new project, initialize your repository from somewhere else (say, a laptop), push those changes to github (you’ll need an account) and then clone your newly minted repository to your pi. If you want to play around with  my repository, create a new branch and clone that to your machine.

git clone https://github.com/aaevan/pantry-monitor

From then on, it’s a matter of making changes to the files, adding any new files to what git knows about (git add -a), and pushing those changes to a remote server (git push origin master)

Scale Setup

When you first get your scale, test it out with batteries. If it powers on, good. When the scale is connected over USB, you don’t need to use batteries– you can save those for something else. When you first try to talk to the scale (i.e. ‘cat /dev/hidraw0’ into a prompt to see the raw output), you’ll probably get an error like this:

usb.core.USBError: [Errno 13] Access denied (insufficient permissions)

By default, we as a normal user can’t go reading raw values from arbitrary devices. This is a good thing. To make it so we don’t have to run all our commands after sudo or as root, do the following:

#make a new file, replacing vim with your editor of choice:

sudo vim /etc/udev/rules.d/99-hidraw-permissions.rules

#with the contents:

"KERNEL==""hidraw*"", SUBSYSTEM==""hidraw"", MODE=""0664"", GROUP=""plugdev""

Then, restart the pi:

sudo shutdown -r now

To find the name of the device, use lsusb to see your connected devices (capitals matter!) :

lsusb | grep Dymo

To find the system’s name of a device (its file representation in /dev), I will sometimes use diff to find the difference between two different outputs of a command:

ls /dev > /tmp/1

#remove the device

ls /dev > /tmp/2; diff /tmp/1 /tmp/2.

The same sequence can be used replacing ls with your command of choice for anything that changes when you plug in or connect a device.

Once we have the device permissions worked out, we can see the changing state of the scale over USB by looking at the raw values:

 

If we want to do something more useful than look at unicode garbage, we can try something like:

cat /dev/hidraw0 | xxd -c 6

and display the raw byte values:

Now that we know how long the message it sends over and over is, we can start doing interesting things with the output. To save you the trouble, I’ve already gone through the contortions to get the byte values into a usable format. The full manipulation happens in read_scale.py but I’ve taken a screenshot of the important parts:

If you’re in the habit of unplugging the scale multiple times, my script might not see the change in device name. It assumes a device name of /dev/hidraw0, but that number can be changed on line 6 of readline (by default, its number is 0) to match the hidraw device number. If your device shows up with a different device name, it should be simple enough to find and replace the device name inside read_scale.py.

The Ins and Outs

To interface with the LEDs and switches, we’ll be using the Raspberry Pi GPIO python library (imported as RPi.GPIO). With this, we are able to define pins as either input or output,  read the state of input pins or set I/O pins high (5v) or low (0v or ground). By using python, we get to interact in real-time with these pins and can rapidly prototype some interesting behaviors.

When I’m making a new object, I always like to start with simple pieces and work up from there. In our case, I recommend trying to get the simplest moving pieces working independently from inside the interpreter. Since someone else has already conclusively solved this problem I’m going to point you towards a great tutorial: link. Once we know the basic moving pieces, we can slowly stitch them together into larger and more complex pieces. If you try to tackle too much at once, you won’t have a clear indication of what is going wrong and where it’s happening. You’ll get stuck. From a shell (lxterminal on raspbian) type:

$ python3

#import relevant libraries:
>>> import RPi.GPIO as GPIO

>>> from time import sleep

When prototyping some behavior, we can use our tiny failures at the interpreter to rapidly trial and error the way to something that works.

When I was first assembling things, I tried to drive the switch LEDs from the GPIO pins. I was wrong. Strange hardware problems start happening if you’re drawing too much current from the GPIO pins. Save yourself the trouble and drive your LEDs with a transistor and power everything off of the 5v rail. Trying to directly drive LEDs from GPIO pins will cause more problems than it’s worth. Save yourself the headache and drive the LEDs with transistors.

A few different times, I’ve made the mistake of touching the internals of the pi while it’s running and it just… shuts off.  It’s a robust little machine but it’s not immortal. I’m guessing that behavior is some sort of ESD protection kicking in. If you find the Pi shuts down unexpectedly, it’s probably not fried. Unplug everything from it, and let it sit for several minutes and things will probably work again. Try it first with just power, and any peripherals. If it boots fine without any GPIO pins connected, you’re probably fine to try again.  This probably happened to me a dozen times before I started being more careful. Double check your wiring and try again. It’s easy to accidentally jump a GPIO pin to ground and prevent the Pi from starting at all. The numbering of the GPIO pins can be tricky and they’ve thrown a few GND pins in the middle of otherwise linear runs of digits. Try to avoid hot-swapping jumpers to the I/O pins. It doesn’t like that so much.

Instead of directly driving the LEDs with GPIO pins, we’ll use transistors instead. Transistors aren’t scary. In this case, I’m using the common 2N3904 NPN transistor in a TO-92 package (the most common form factor). In this configuration, it acts as an almost instantaneous switch with no moving parts. We’ll use a resistor of value somewhere between 1k and 10k to limit the amount of current that we draw from the GPIO pin. If we can imagine electricity as a conversation between the different components: you whisper at the middle pin (the base) through a high value resistor. When the middle pin (base) hears a whisper, it tells the pin on the power side (the collector) to shout at the pin connected to ground (the emitter). We’re greatly amplifying a weak signal (the current-limited output of a GPIO pin) into something that can drive something more power hungry

Assembling The Hardware

Let’s unpack our arcade switches and get them soldered up. They come in a ridiculous, messy format. Don’t be alarmed:

The switches go together in a fairly straightforward way:

To each switch body, I’ve soldered halved female-female jumper wires to the common pin and the normally closed pin of each switch (abbreviated NO, NC and COM on the switch body). Cut each jumper wire in half, strip about 1/4″ of insulation off of the end, twist so that the strands don’t come free and hook it through the hole in the spade connector. It should then be easy to solder each connection. If you have the variety, choose a different color jumper for each switch, you’ll thank me later. Perform the same stripping, soldering and heat-shrinking routine on each LED contact.

As the LEDs are wired to expect 12v instead of 5v, we need to swap out the resistors on each leg of the leds in the little plastic casings with new ones.

I used a heavy piece of metal and some packing tape to hold the ends close together for ease of soldering. This is why third hand tools exist. They’re great.

Another way to accomplish this would be to unbend the legs and swap in LEDs without included resistors. This is easier and faster, it just requires that we have a few LEDs on hand already. If you bought a raspberry pi starter kit, there should be a few included. This just means we need to place the current limiting resistors somewhere else in the circuit. For that, I’ve used 220 ohm through-hole resistors but we could just have easily used 330 As to the difference between 220 and 330 (if we’re using a 5mm LED) you’re not going to burn them out and you’re not going to make them dim enough to notice the difference. For the transistor middle pin, I’ve used 2k but we could just have easily used anything from 1k to 10k.

Aside: For managing a whole tangle of wires that make up the circuit, I’ve found that the time I’ve spent learning to use KiCAD has been more than repaid in frustration saved when dealing with breadboards. The one thing I will say, is try to not fall prey to magical thinking. It’s all too easy when designing and fabricating circuit boards to make an informed guess and hope for the best, but any error is made especially unpleasant by the 2+ week turnaround between trial and error. Try your best to double check your work and not fool yourself. I’ve made the same error maybe a half dozen times now where I assume that the pinout of one transistor (in KiCAD’s parts library) will be the same as another, when really, two or more of their pins are swapped. Really: use the footprint and schematic symbol for the component you’re actually going to use (i.e. 2N3904). Take a look at the finished circuit and trace out some of the paths: does the signal pin of the transistor correspond to the 10k resistor and the GPIO output pin? If not, you’ve messed up somewhere.

You can just as easily (read: much more easily) fabricate the circuit on protoboard or plug it into a breadboard, this is just my preference.On the board I ordered, I ended up not populating the whole thing. Because of internal pull down resistors (switched on during setup) we can use the input pins as is. The board I ordered is really just 5 copies of the following crammed into a tiny little space along with 5 copies of some unneeded switch protection circuitry:

Each transistor is triggered by a GPIO pin. Each LED pin spans the gap between a 220 ohm resistor and the 5v rail. When the GPIO pin is brought HIGH, the transistor opens, and current from the 5v rail can pass through the LED into ground. See above schematic for details.

By default, the script uses pins 8, 10, 12, 16, 18 for the LEDs outputs and pins 29, 31, 33, 35, 37 for the switches. These numbers can be changed by editing lines 26 and 27 of gpio_control.py. Each switch is tied to a GPIO pin with the other end to 5v. When a switch is closed, it opens a path from the GPIO pin to 5v and the GPIO pin is able to read HIGH. Otherwise, it is internally (microscopically inside the microcontroller) tied to GND with a high value resistor. To simplify things (in a very roundabout way), I’ve done most of the wiring on the PCB. I actually planned for more circuitry than I needed and ended up not using half the board (the side I’d planned to use for the switches). The LEDs are wired to the board (jumpers 4, 6, 8, 10 and 12) and the switches have one leg tied to ground. The signal lines from the PCB (the pins of jumper 2), are wired to 8, 10, 12, 16 and 18. One leg of each switch is wired to pins 29, 31, 33, 35 and 37. It’s easy to put the LED in backwards and there’s no downside (other than it not lighting up), so it may require some trial and error to get the polarity of the LEDs correct.

To fit all of this colorful wiring (pretty but not so professional), I’ve used the clean but un-glamorous reversed cardboard box. Using one of the included plastic spacer rings, I was able to trace the locations for each switch onto the box. Then, with a sharp knife or hobby blade, cut each hole tighten one half of the switch onto the box.

The nice thing about these switches is that they cleanly separate into the plastic body of the switch and the electronics. This makes it easy to do all of the soldering and heat shrink separately and attach the fully wired switches after the fact. Because of the placement of the switches in the box I chose, I had to stagger the last two switches (see green and blue switch bodies).

To double check my wiring, I’ve programmed a cheap arduino clone with a blink sketch. This setup doesn’t care at all about hot swapping and we can quickly figure out which lead goes to ground for each LED and whether the leds need to switch orientation in their sockets. A similar jig could be assembled to test the switches. From here, we can try to arrange everything in the box. If you end up making something similar to what I’ve done here, do yourself a favor, and use a bigger box. I’ve cut tiny rectangles from the corners of the box to allow the USB and power cables to pass through.

To further clean up wiring, I’ve also made two different 5:1 male-to-female jumper adapters. Given the limited amount of space and the 10 different things all trying to vie for the same GND pins, these came in handy:

Script Setup And Customization

There’s several moving pieces to the guts of this project:

read_scale.py: reading the raw byte values off of the usb scale (byte manipulation in python)

notify_script: handles reading off of staple_log and putting the information it gathers in a human readable format.

gpio_control.py: the top level script. Sets up the GPIO pins to activate different listening and output states. Imports read_scale and notify_script.

staple_log.txt: a python dictionary in plain text, one per line. Stores the keys and values of each staple quantity and the date that the record was made. This is turned back into something useful using ast.literal_eval().

If you’d like to customize the names of the staples tracked, edit lines 19, 20 and 22 of gpio_control.py and delete the contents of staple_log.txt.

Once you’ve gotten the scripts you’ve written (or used the files from my repository) to do something properly interesting, stable and predictable, we’re ready to add the script to the pi’s startup routine. There are several ways to run things at startup but it was only crontab that I could get to work. When I tried to add things to startup by editing my .bashrc, I found it only ran things once I’d already ssh’d into the pi. To add the main script to your crontab, open up your most easily usable editor by running crontab -e (I use vim, you can use nano) and enter the following:

@reboot echo 'rebooted at' `date` >> /home/pi/log.txt

@reboot /home/pi/pantry-monitor/gpio_control.py &

Save, quit and reboot the pi.

sudo shutdown -r now

I’ve added a logging command so I can see and verify that things from my crontab are running. An & at the end of a command means run the command in the background where we don’t have to see its output. Though, If you’re printing to the screen, it may still spam you with unwanted text.

Caution: if you have a lot of print statements and debug messages in your main script, when you go to log into your pi, your console will be spammed with all that output and it will be difficult if not impossible input commands and read output.

I like to stop this when it happens by typing kill -9 `pgrep <part of process name>` On my pi, for the script in question, the following works to stop any unwanted spam:

kill -9 `pgrep gpio`


NOTE: When you include a command in `backticks`, it will run first, then the outer command is run with the inner command replaced with its output. This is useful for stuff like displaying the current date (date) and fetching a process ids by name (pgrep) without running a separate command.

Notifications

To get useful information to the phone, I’ve used notify.run for push notifications. Thankfully, it’s easily installable over pip (pip3 because we’re using python 3).

pip3 install notify-run

To get notifications working, you will have to manually step through the registration process as described at the bottom of the page here. notify.run works by allowing push notifications to be sent from your phone’s browser. It’s free and simple to set up.

Note:If you are already subscribed to one notification source, you may have to clear the existing settings by going into chrome settings–>Site settings–>Notifications and clearing or resetting existing values.  scroll down to the blue “subscribe on this device button and allow notifications for this site.

Do a few test notifications, just to make sure it’s working. I had my phone on silent and no vibration and thought it wasn’t working. Don’t be me. Again, simple pieces first, build up to complexity from predictable parts.

How It Works

If everything goes as planned, given a USB power source, when you power on the scale, the buttons will scroll through an idle animation. By pressing one of the buttons, a weighing routine will be triggered and any non-zero value logged for several consecutive seconds will be taken as the most recent value for that staple. Given a new line to the log, a notification for the most recent values will be sent to the user’s phone via notify-run. The tracker will then return to an idling animation and wait for further input. If the scale is turned off, the lights will go off and it will appear to sleep.


For a more fine-grained description, I’ve written the following:

  • Turn on the scale. When the scale is off, the button lights are unlit.
  • When the scale is detected to be on (having a live USB connection), the main unit will start cycling through each LED:
    • 1 on, 1 off,
    • 2 on, 2 off,
    • 3 on, 3 off,
    • 4 on, 4 off,
    • 5 on, 5 off,
  • When one of the buttons is detected as pressed, a single LED will blink.
  • When a given LED is blinking, the chosen staple (set to buttons/LEDs 1 through 5 accordingly) can be logged.
  • During the weighing routine, if the value of the scale does not change and stays at zero, the weighing routine will time out.
  • Given a non-zero value that does change from read to read, additional time is added to the timeout value, giving more time to position the staple on the scale.
  • If a zero scale measurement is measured and the timeout runs out, the system returns to the idling animation.
  • When the scale turns off to save power, the USB connection will be lost, and the LEDs will stop cycling. Button presses will no longer be registered until the scale is turned on again.  
  • When a non-zero value is measured on the scale, and that value does not change for 4 consecutive reads, a new line in the log-file is written. That entry has the last known values for the other staples along with the updated value for the chosen staple
  • Upon a successful read and log-file write, a notification (to the given registered URL stored on the pi) will be sent to the user’s phone or any subscribed device via notify-run.
  • The notification will reflect the changing values of the staples over time and provide an estimate of the remaining days of stock and current gram amounts remaining.

This project was a lot of fun. I rarely get to throw so much time at something and then turn around to see something that actually works. I see a few areas for improvement but isn’t that what you get (vicious criticism) for putting it out into the world? For any questions, I’ll be monitoring the thread on the forum and will try to provide a reasonable semblance of tech support.

Created by Aaron Evan-Browning

Like what you read? Why not show your appreciation by giving some love.

From a quick tap to smashing that love button and show how much you enjoyed this project.