Maker.io main logo

Digital Nose Milk Freshness Checker

2024-09-27 | By Adafruit Industries

License: See Original Project Qwiic STEMMA

Courtesy of Adafruit

Guide by Carter Nelson

Overview

sensor_1

The classic method for testing for milk freshness is to give it a quick ‎sniff check. If it smells "bad", then it probably is. This is great, as long ‎as your sense of smell is working. But maybe covid, congenital ‎anosmia, a stuffy cold, or something else has reduced your sense of ‎smell. What then?‎

In this guide we investigate the potential for using a gas sensor as a ‎way to test for milk freshness. An SGP30 and a CLUE are used to ‎make a little "freshness checker" device. We then use this in a ‎simple experiment to see if it can detect spoiled milk.‎

sensors_banner3

This project does NOT require any soldering and is a great little ‎science experiment.‎

Hardware

Here is a summary of the hardware needed for this project.‎

This is the battery shown in the guide:‎

but you can use any other similar lithium-ion battery option:‎

Lithium-Ion Batteries

Background Information

In this guide we use the phrase "covid" and/or "coronavirus" to refer ‎to the Coronavirus disease 2019 (COVID-19) as caused by the severe ‎acute respiratory syndrome coronavirus 2 (SARS-CoV-2).‎

Covid Related Loss of Smell

The CDC list "New loss of taste or smell" as one the main symptoms ‎of covid. Sickness related loss of smell is nothing new, even the ‎common cold can cause it to happen. However, due to the global ‎pandemic scope of covid, this issue has received renewed and ‎increased significance. There have been various news stories ‎covering this issue and how it has impacted people's ability to "sniff" ‎for dirty laundry, rotten food, etc.‎

We thought this issue would make for some great science and ‎wanted to see if electrical sensors could be used. Here we share our ‎results on making a milk freshness tester.‎

How Milk Spoils

We use the term "bad" to describe "spoiled" milk, but really, it's just ‎nature doing its thing. There are numerous bacteria that love milk. ‎Unfortunately for us, their consumption of milk produces byproducts ‎that we find distasteful and can even be harmful.‎

Spoilage is also a vague term. As this paper (PDF) puts it:‎

Milk spoilage is an indefinite term and difficult to measure with ‎accuracy.‎

This paper summarizes various techniques that have been ‎investigated as a way to detect spoilage, including:‎

  • pH

  • electrical methods

  • magnetoelastic sensors

  • gas sensor arrays

  • infrared spectroscopy

  • protein count

It is the gas sensor based approach we are interested in here. This is ‎analogous to what we do when we sniff milk to check freshness. Our ‎noses are essentially gas sensors. But if covid (or something else) has ‎knocked out your sense of smell, then your gas sensor is broken. Can ‎we use an electrical gas sensor instead?‎

Electrical Nose

The general of idea of gas sensor-based milk spoilage detection has ‎been investigated by others.‎

There are various gases that are discussed amongst these papers, ‎but a common approach is to use microbial produced volatile ‎organic compounds (VOCs) and carbon dioxide (CO2). Well, ‎the SGP30 from Sensirion , as used on the Adafruit SGP30 STEMMA ‎QT breakout, can detect both of these.‎

So, can the SPG30 be used to create an "electric nose"? Well, let's ‎science this and find out.‎

Build the Checker

Let's start by putting together our CLUE Milk Freshness Checker. The ‎CLUE and SGP-30 are the key parts. For the other items, we used ‎some things that can hopefully be found lying around the house. If ‎not, feel free to substitute for whatever is available to achieve the ‎same general arrangement.‎

  • CLUE board

  • SGP-30 sensor‎

  • Battery

  • Stemma QT cable

  • Popsicle stick

  • Double sided tape

  • Rubber band

things_2

Put a piece of double-sided tape about the same size as the SGP-30 ‎at one end of the popsicle stick.‎

popcicle_3

Place the SGP-30 firmly onto the double-sided tape and attach the ‎Stemma QT cable.‎

attach_4

Use the rubber band to secure the CLUE and battery to the other ‎end of the popsicle stick.‎

Plug in the Stemma QT cable and battery.‎

secure_5

secure_6

Do NOT let the SGP-30 touch the milk. It is not a moisture proof ‎sensor.‎

Now move on to setting up the CLUE for CircuitPython usage and ‎loading the code. It comes with some preset values we determined ‎in our testing. We'll cover how you can alter these later.‎

CircuitPython on CLUE

CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It ‎makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY flash drive to iterate.‎

The following instructions will show you how to install CircuitPython. ‎If you've already installed CircuitPython but are looking to update it ‎or reinstall it, the same steps work for that as well!‎

Set up CircuitPython Quick Start!‎

Follow this quick step-by-step for super-fast Python power :)‎

Download the latest version of CircuitPython for CLUE from ‎circuitpython.org

Click the link above to download the latest version of ‎CircuitPython for the CLUE.

Download and save it to your desktop (or wherever is handy).‎

click_7

Plug your CLUE into your computer using a known-good USB cable.‎

A lot of people end up using charge-only USB cables and it is very ‎frustrating! So make sure you have a USB cable you know is good ‎for data sync.‎

Double-click the Reset button on the top (magenta arrow) on your ‎board, and you will see the NeoPixel RGB LED (green arrow) turn ‎green. If it turns red, check the USB cable, try another USB port, ‎etc. Note: The little red LED next to the USB connector will pulse red. ‎That's ok!‎

If double-clicking doesn't work the first time, try again. Sometimes it ‎can take a few tries to get the rhythm right!‎

board_8

You will see a new disk drive appear called CLUEBOOT.‎

Drag the adafruit-circuitpython-clue-etc.uf2 file to CLUEBOOT.‎

drag_9

drag_10

The LED will flash. Then, the CLUEBOOT drive will disappear, and a ‎new disk drive called CIRCUITPY will appear.‎

If this is the first time, you're installing CircuitPython or you're doing ‎a completely fresh install after erasing the filesystem, you will have ‎two files - boot_out.txt, and code.py, and one folder - lib on ‎your CIRCUITPY drive.‎

If CircuitPython was already installed, the files present before ‎reloading CircuitPython should still be present on ‎your CIRCUITPY drive. Loading CircuitPython will not create new ‎files if there was already a CircuitPython filesystem present.‎

That's it, you're done! :)‎

drive_11

CLUE CircuitPython Libraries

The CLUE is packed full of features like a display and a ton of sensors. ‎Now that you have CircuitPython installed on your CLUE, you'll need ‎to install a base set of CircuitPython libraries to use the features of ‎the board with CircuitPython.‎

Follow these steps to get the necessary libraries installed.‎

Installing CircuitPython Libraries ‎on your CLUE

If you do not already have a lib folder on your CIRCUITPY drive, ‎create one now.‎

Then, download the CircuitPython library bundle that matches your ‎version of CircuitPython from CircuitPython.org.‎

Download the latest library bundle from circuitpython.org

The bundle downloads as a .zip file. Extract the file. Open the ‎resulting folder.‎

bundle_12

Open the lib folder found within.‎

lib_13

Once inside, you'll find a lengthy list of folders and .mpy files. To ‎install a CircuitPython library, you drag the file or folder from ‎the bundle lib folder to the lib folder on your CIRCUITPY drive.

list_14

Copy the following folders and files from the bundle lib ‎folder to the lib folder on your CIRCUITPY drive:‎

  • adafruit_apds9960

  • adafruit_bmp280.mpy

  • adafruit_bus_device

  • adafruit_clue.mpy

  • adafruit_display_shapes

  • adafruit_display_text

  • adafruit_lis3mdl.mpy

  • adafruit_lsm6ds

  • adafruit_register

  • adafruit_sht31d.mpy

  • adafruit_slideshow.mpy

  • neopixel.mpy

folder_15

Your lib folder should look like the image on the left. These libraries ‎will let you run the demos in the CLUE guide.‎

Code

Here's how to load the code and assets as well as some additional ‎libraries you'll need.‎

Additional Libraries

In addition to the main libraries needed for CLUE support covered in ‎the previous section, you'll also need to install the libraries listed here.‎

You can download the latest library bundle from the CircuitPython ‎webpage.‎

Make sure you have a copy of these in your CIRCUITPY/lib folder.‎

  • adafruit_bitmap_font

  • adafruit_imageload

  • adafruit_sgp30.py

Here's a summary:‎

summary_16

Freshness Indication

The code below comes with preset values for TVOC_LEVELS used to ‎determine milk freshness. In the next section we discuss the ‎experiment we ran to determine these values as well as how you ‎can alter them. Once set, the CLUE display will indicate milk ‎‎"freshness" as follows:‎

  • GOOD + smiling cow = the TVOC levels are very low, so milk ‎should be good.‎

good_17

  • SUS? + confused cow = the TVOC levels are high enough that ‎the milk may be starting to turn.‎

sus_18

  • BAD! + frowning cow = the TVOC level are high enough the ‎milk should be considered bad.‎

bad_19

Code

Use the Project ZIP link below to download the code as well as the ‎bitmaps and font files used in a single zip file.‎

Drag the entire bmps and fonts folders to your CIRCUITPY folder. ‎These assets will live in those subfolders.‎

Save the code listing as code.py into your CIRCUITPY folder so it will ‎run automatically when powered up.‎

‎Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2021 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import board
import displayio
import adafruit_sgp30
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import adafruit_imageload
from adafruit_clue import clue

# --| User Config |-------------------------
TVOC_LEVELS = (80, 120)  # set two TVOC levels
MESSAGES = ("GOOD", "SUS?", "BAD!")  # set three messages (4 char max)
# ------------------------------------------

# setup UI
cow_bmp, cow_pal = adafruit_imageload.load("bmps/milk_bg.bmp")
background = displayio.TileGrid(cow_bmp, pixel_shader=cow_pal)

mouth_bmp, mouth_pal = adafruit_imageload.load("bmps/mouth_sheet.bmp")
mouth = displayio.TileGrid(
    mouth_bmp,
    pixel_shader=mouth_pal,
    tile_width=40,
    tile_height=20,
    width=1,
    height=1,
    x=35,
    y=110,
)

msg_font = bitmap_font.load_font("fonts/Alphakind_28.bdf")
msg_font.load_glyphs("".join(MESSAGES))
message = label.Label(msg_font, text="WAIT", color=0x000000)
message.anchor_point = (0.5, 0.5)
message.anchored_position = (172, 38)

data_font = bitmap_font.load_font("fonts/F25_Bank_Printer_Bold_12.bdf")
data_font.load_glyphs("eTVOC=12345?")
tvoc = label.Label(data_font, text="TVOC=?????", color=0x000000)
tvoc.anchor_point = (0, 1)
tvoc.anchored_position = (5, 235)

eco2 = label.Label(data_font, text="eCO2=?????", color=0x000000)
eco2.anchor_point = (0, 1)
eco2.anchored_position = (130, 235)

splash = displayio.Group()
splash.append(background)
splash.append(mouth)
splash.append(message)
splash.append(tvoc)
splash.append(eco2)
clue.display.root_group = splash

# setup SGP30 and wait for initial warm up
i2c = board.I2C()  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
sgp30 = adafruit_sgp30.Adafruit_SGP30(i2c)
time.sleep(15)

# loop forever
while True:
    eCO2, TVOC = sgp30.iaq_measure()

    tvoc.text = "TVOC={:5d}".format(TVOC)
    eco2.text = "eCO2={:5d}".format(eCO2)

    level = 0
    for thresh in TVOC_LEVELS:
        if TVOC <= thresh:
            break
        level += 1

    if level <= len(TVOC_LEVELS):
        message.text = MESSAGES[level]
        mouth[0] = level
    else:
        message.text = "????"

    time.sleep(1)

 

View on GitHub

Experiment

Here is the simple experiment we ran to determine the specific SPG-‎‎30 sensor values used in the code.‎

The experiment was carried out during February 2021 with the milk ‎shown.‎

Data was collected February 28, 2021.‎

date_20

Each day, a small amount of milk was poured into a glass and ‎labeled with the current date. This was done for 5 days to produce a ‎series of milk of differing ages.‎

glass_21

We used the code to provide a readout of the SGP-30's TVOC and ‎eCO2 - the values shown in the grass at the bottom of the display.‎

At this point, don't worry about what the cow says.‎

glass_22

Do NOT let the SGP-30 touch the milk. It is not a moisture proof ‎sensor.‎

For each sample, we lowered the sensor into the glass without ‎touching the milk, slowly moved it around, and watched the ‎readings for about 1 minute.‎

We then wrote down the observed range for each gas reading.‎

observe_23

Here is a summary of the readings we obtained:‎

summary_24

Now we have values we can use in our code to determine milk ‎freshness. We considered there to be three levels:‎

  • GOOD - low readings very close to fresh milk

  • SUS? - the readings are slightly elevated, milk is sus

  • BAD! - milk that has obviously gone bad

Based on the values we observed, we decided on TVOC <= 80 as the ‎cutoff for GOOD, TVOC <= 120 as the cutoff for SUS, and anything ‎above that as BAD!‎

However, different milk may age in different ways. Different ‎environmental conditions, like temperature and humidity, may also ‎affect the readings. Further, your personal level of comfort on what ‎constitutes "bad" milk, may be different than others. Therefore, we ‎suggest you repeat the above experiment.‎

We suggest you repeat this experiment!‎

Adjusting the Code

You can easily adjust the code to change the cutoff TVOC levels used ‎for freshness detection. You can even customize what the cow says. ‎Just look for these lines at the top of the code:‎

‎Download File‎

Copy Code
# --| User Config |-------------------------
TVOC_LEVELS = (80, 120)  # set two TVOC levels
MESSAGES = ("GOOD", "SUS?", "BAD!")  # set three messages (4 char max)
# ------------------------------------------

and change as needed. Note there are only two TVOC levels to ‎specify for a total of three freshness levels.‎

Other Sensor Values

We decided to keep things simple and just use the SGP-30's TVOC ‎reading. But note in the data above that eCO2 also increased with ‎milk age. This production of CO2 is mentioned in the research papers ‎previously cited. Perhaps a more sophisticated approach could be ‎used that take both readings into account? We leave that up to any ‎intrepid scientist out there that wishes to investigate further.‎

Other Sensors

We tried a few other sensors as well, but they did not seem to have ‎any useful "milk freshness" related signal. At least nothing with as ‎‎000good a signal-to-noise ratio as the SGP-30.‎

Other Uses

We also thought this idea could be used for detecting dirty laundry. ‎However, that did not seem to work - at least not as easy as the ‎approach above for milk. This may be due to the various dyes used ‎to color clothing producing VOCs that mask any similar signal from ‎‎"dirty" smells.‎

制造商零件编号 4500
CLUE NRF52840 EXPRESS
Adafruit Industries LLC
制造商零件编号 3709
SGP30 AIR QUALITY SENSOR BREAKOU
Adafruit Industries LLC
制造商零件编号 4210
JST SH 4-PIN CABLE - QWIIC COMPA
Adafruit Industries LLC
制造商零件编号 4829
ADAFRUIT SGP40 AIR QUALITY SENSO
Adafruit Industries LLC
制造商零件编号 3660
STEMMA QT BME680 SENSOR BOARD
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum