Introduction: Coughing Planter

About: its my birfday

What's this then?

A breathing, coughing, air-quality-sensing, Google-Sheets-logging, real-live-plant-having planter!

Using an air-quality sensor and a hidden Raspberry Pi Zero W, this little planter personifies a plant to sense indoor air quality.

Code and miscellaneous resources (laser cutter PDF and Fritzing model) are in the project's Github repository.

Bill of Materials

  • Raspberry Pi Zero W (other models should be fine, with small changes to the code and potentially wiring. The wifi is used to log to Google Sheets, but is otherwise optional.)
  • An SGP30 air-quality sensor (we used the breakout board from Adafruit, but any similar one should work.)
  • A 2.5W mono speaker amp and small speaker (we used this amp and this speaker)
  • A Pimoroni Blinkt! LED strip (available here; you could also use a strip of 8 NeoPixels, with some small changes to the code)
  • A power adapter for the Pi (perhaps this one)


We're going to document the high-level process in this Instructable.

  1. Configure and test the SGP30 sensor
  2. Configure and test the speakers
  3. Configure and test the lights
  4. Google Sheets configuration
  5. General software configuration
  6. Assemble the case

Let's begin!

Step 1: Sensor Chip - RPi Configuration

We'll be using an SGP30 CO2 and VOC (Volatile Organic Compound) sensor for this project. It speaks I2C, which is relatively easy to use on the Raspberry Pi. There are pre-existing libraries for the Arduino and CircuitPython boards from Adafruit, but I could't find one for the Pi, so I wrote my own.

Connecting the sensor to the Pi

First things first, let's get the Pi set up for I2C.

Throughout this guide, I am referencing the physical Pi pin numbers, not the Broadcom or GPIO numbers. In this PDF, I am using the middle ones (white background, labeled "Position" in the key).

  1. On your Pi, run sudo raspi-config. Under "Interfacing Options," find and enable "I2C".
  2. On recent versions of Raspbian, the software we need is already installed, but just in case, use apt-get to install python-smbus and i2c-tools:
    $ sudo apt-get update
    $ sudo apt-get install python-smbus i2c-tools
  3. Turn off your pi and connect the pins on the SGP30 breakout as follows:
    SCL → Pi pin 5
    SDA → Pi pin 3
    Vin → Pi 5v (any will do)
    Gnd → Pi ground (any will do)
    1v8 not connected
  4. Power up the Pi and check that the SGP30 is detected. Run i2cdetect -y 1 and look for a device with address 0x58. (Note that on some other Pis, you want i2cdetect -y 0, apparently).

That's it! Let's try talking to the thing.

Step 2: Sensor Chip - Scripting

Now that the sensor chip is connected and detected, let's test it.

  1. Pull down the SGP30 RPi library from Github:
    $ git clone
  2. Change directory into the cloned repo:
    $ cd sgp30
  3. Run the demo script:
    $ python

The SGP30 takes about 15 seconds to warm up, so expect to see "warming up..." for a while. Eventually you should see output like the image above.


  • IOError: check the connections with the breakout board. This usually means something is wrong with the I2C connection.
  • ImportError with SMBus: SMBus is not installed. Run apt-get install python-smbus.

Step 3: Speakers

The Raspberry Pi Zero doesn't have dedicated audio output, so we'll be using a PWM pin to simulate it. We'll follow bits from this guide from Adafruit, which is unfortunately partially out of date. Also there are some small tweaks, mainly since using mono audio.

Raspberry Pi setup

  1. Enable the device tree overlay for PWM audio, which is documented here. Open /boot/config.txt as root in your favorite editor, and add this line at the end:
  2. You can skip the step from the Adafruit guide of setting the PWM pin to Alt-5 mode; this seems to be unnecessary with the device tree overlay.
  3. You can also skip the low-pass filter from the Adafruit guide, unless you're feeling adventurous. We got decent audio out of the Pi without it.
  4. Run `sudo raspi-config` and, under "Audio," force the output over the 3.5mm (headphones) jack. This will make sure the speaker still works even if you connect an HDMI cable.
  5. Reboot the Pi.

Wiring up the amp

Now that the Pi is set up for PWM audio, we need to connect the amp. The spec sheet says that if we don't have differential output (which we don't), we should ground Audio-. So the wiring is like this:

  1. Amp A+ → Pi pin 12 (this is the PWM audio pin that the pwm overlay uses by default)
  2. Amp A- → Pi ground (any will do)
  3. Amp Vin → Pi 5v (any will do)
  4. Amp Gnd → Pi ground (any will do)
  5. Amp SD not connected (this is the shutdown pin – could be fun to play with, but not necessary)

Connect the amp output terminals to a speaker, and fire the Pi back up.

Testing audio & minimizing static

Since we're faking audio output with PWM, the waveform is really noisy. Here is how we got decent audio out of the Pi:

  1. Run alsamixer and turn the volume all the way up.
  2. Play a sound with aplay . You should hear the sound and, potentially, a lot of static afterwards.
  3. Use a tiny screwdriver to adjust the potentiometer on the amp board. If you're hearing static, turn it down just until you don't any more. If you're hearing nothing, turn it up until you hear static, then back down.
  4. Run aplay again and see how it sounds. Even below the static limit, we had staticky sounds on the loud points of an audio file. Tweak the potentiometer as needed.

Keep in mind that the speaker will ultimately be inside a box, so you probably want it as loud as possible.


If you can't get the speaker to play anything, check your wiring, and make sure you rebooted after adjusting config.txt. Also check the output of gpio readall: you should see that physical pin 12 is in Mode ALT5. If not, try running gpio -g mode 18 alt5. This should be the default with the device overlay, but it's worth a shot.

Step 4: Lights

For breathing and coughing animations, we need some lights! I happened to have a Pimoroni Blinkt! handy, but you could easily use a strip of NeoPixels.

The Blinkt is designed as a shield, but that's no good for us; we want a lot of those pins. It turns out, from inspecting the pinout, that the Blinkt actually only uses 4 pins. So we'll just wire those straight through to the Pi so we can use the Python library without changes.

Wiring the Blinkt

Connect the following pins:

  1. Blinkt pin 2 → Pi 5v (any will do)
  2. Blinkt pin 4 → Pi ground (any will do)
  3. Blinkt pin 16 → Pi pin 16
  4. Blinkt pin 18 → Pi pin 18
  5. All other Blinkt pins can be left unconnected

Testing it out

  1. Fire up the Pi and install the Blinkt Python library with sudo apt-get install python-blinkt
  2. Run python and try a simple demo of the lights, perhaps like this:
    import blinkt
    blinkt.set_all(128, 50, 50)
    # admire for a few seconds
    # hit ctrl+d to exit


Man, that Blinkt pinout is really confusing. Make sure you have it oriented correctly (mind the round corners). In our model, if you put the Pimoroni pirate logo on the top, you want to put pins on the left side of the headers (see photo).

Step 5: Software - Google Sheets Setup

Every few seconds, the code logs an air quality reading to a Google spreadsheet. We are using the gspread library for this. Granting gspread access to Google Sheets is a little complicated, and their guide is a little out of date. Follow these steps:

  1. Go to the Google Cloud Resource Manager. You may have to enroll in the Google developer program to gain access to this.
  2. Hit the "Create Project" button at the top, and fill in the details with whatever you please.
  3. On the next page, in the "APIs" panel, select "Go to APIs overview."
  4. On that page, hit "Enable APIs and Services" at the top.
  5. Search for and enable the Google Drive API and Google Sheets API. Don't worry about the warning about credentials; we'll handle that next.
  6. Go back to the APIs Dashboard and select "Credentials" from the list on the left.
  7. Drop down the "Create Credentials" button and select "Service Account Key." This will create a "user" that our script will act as, when it needs to make changes to a Sheet.
  8. On the following page, give the account a name (any will do), and give it the "Service Account User" role from the "Service Accounts" section in the Roles dropdown.
  9. Make sure "JSON" is selected in the "Key type" section and hit "Create." The JSON credentials file will automatically download. Keep this safe, and don't publish it anywhere! It's tantamount to a password (though it will only have access to what you grant it access to, it's still best to be safe).
  10. Finally, we'll need to find the service account user's full username. Go to the "IAM" dashboard by selecting the hamburger menu in the upper left, then "IAM & admin," then "IAM". Make a note of the service user account's email address. It should be something of the form "". (Note that this is also in the JSON file we downloaded earlier, if you just want to grab it from there.)

Alright, that was exhausting. Last step is to make a spreadsheet for the data logging and grant access to the service user. We'll do that like this:

  1. Make a new Google Sheet. Doesn't need to be anything special. You might want to make a column header (there will be three: timestamp, CO2 (ppm), and VOCs (ppb)).
  2. Grant edit access to the service account user we made earlier. In the top-right, select "Sharing," paste in the service user's email address, drop down the pencil button and select "Can Edit". Hit "Send" and you should be good to go!
  3. Make a note of the really long ID part of the URL of the spreadsheet (the part between /spreadsheets/d/ and /edit). We'll need that to configure gspread in the next step.

Step 6: Software - All Together Now

Alright, let's pull down and configure the software that makes it all go!

  1. Install some dependencies:
    $ sudo apt-get install python-pip python-pyaudio
    $ pip install pydub oauth2client gspread
  2. Clone the repository:
    $ git clone
  3. Change into the new planter directory:
    $ cd planter
  4. Copy the SGP30 driver into the planter directory. If you previously cloned it, this should be something like...
    $ cp ../sgp30/ .
  5. Copy the Google Sheets JSON credentials file from the previous step into the planter directory, and name it "gsheets-creds.json".
  6. Find or record a cough sound you like (try!) and put it in the planter directory.
  7. In your favorite editor, open the "" file.
  8. Around line 22, where the GoogleSheetsLogger is initialized, replace the long string of numbers and letters with the ID of the sheet you created in the previous step.
  9. Around line 143, change the filename in the line that sets cough_filename to match the filename of the cough you've selected in step 6.

That's it! Run ./ to get the whole thing going. If the air quality is good, every few seconds the front LEDs will "breathe" in a relaxing blue color. If it's bad, the speaker will play the cough sound and the lights will pulse red in sync.

Poke around in the code and change some things - timings, "good" and "bad" air quality thresholds, colors, etc.

Step 7: Case

We used a laser cutter to make the housing for the planter. We designed two pieces: the wooden outer box, with a hidden compartment for the electronics and a hole for the sensor to poke out, and a waterproof acrylic sleeve for the plant.

The PDF for both pieces can be found here. The skeleton of the box was built using the laser-cutter Ruby Gem (if you open the PDF with Illustrator you can see the settings we used outside of the artboards). We used 1/8" thick wood; you will need to adjust the box if you use a different thickness, or if your laser cutter's kerf is significantly different than ours. The PDF includes 3 options for a faceplate — two with a grille of very small holes for the lights, and a third with larger circles, one per LED on the Blinkt.

For waterproofing the acrylic, we sealed it with silicone adhesive, which we let cure overnight. Thoroughly test the seal by filling the sleeve with water, letting it sit for a while, and looking for drops of water on the outside. Recaulk as needed until you're satisfied.

After cutting, we stained the wood and applied a Carnauba-based wax for light waterproofing. The box should fit together in a fairly obvious manner. The face with four holes is the back, and the lower hole is for the SGP30 chip to fit through (upside down). The two small pieces on the right of the PDF are for a small cover for the topside of the chip. The piece with no holes is the "shelf" that fits into the slots on three sides of the box, and provides support for the planter sleeve and separation from the electronics.

Step 8: Cram It All Together & Next Steps

Now that everything is hopefully running, it's time to cram it all in the case. We don't have any great suggestions here. We did use the power rail from a half-size breadboard to have one source of power and ground for all the components, but otherwise we just shoved things in and secured with a little hot glue and tape.

Some thoughts on where to go from here:

  1. Add a soil moisture sensor, or other sensor of plant health (root capacitance, leaf color, etc.)
  2. Tweet at your boss or city or the EPA whenever the air quality is bad
  3. Add a temperature and humidity sensor to increase the accuracy of the SGP30
  4. Add wheels and a photosensor to make the plant stay in the sunlight as much as possible.