Clint WolfBrian Rashap
Published

Safety Warning system

Detects sound and airborne particulates in the air. If unsafe levels of either are present, warning lights will display.

IntermediateShowcase (no instructions)80
Safety Warning system

Things used in this project

Hardware components

Argon
Particle Argon
×1
Grove - Laser PM2.5 Sensor (HM3301)
Seeed Studio Grove - Laser PM2.5 Sensor (HM3301)
×1
Electret Microphone Amplifier MAX4466
×1
NeoPixel Ring - 12 x 5050 RGB LED with Integrated Drivers
×2
Acrylic mounting sheet
×1

Software apps and online services

Adobe Illustrator
Microsoft Windows 11
Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino
Adafruit IO

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Epilog 50W Laser Etching and Cutting Unit

Story

Read more

Custom parts and enclosures

final_artboard_I6kWro99Ef.ai

SafetyWarning system

All holes and curved edges in the 17" by 22" opaque acrylic sheet were cut using an Epilog Laser unit from an Adobe Illustrator file.

Schematics

SafetyWarning system

Safety Warning system as PNG

Code

Safety Warning system

C/C++
Mounted in an industrial fabrication setting, the system detects sound and particulates in the air. If unsafe levels of either are present, warning lights will display so residents in the work area know to wear the appropriate safety gear.

The system will also send data to an Adafruit dashboard to track sound, particulate levels, and warning events.
/*
 * Project SafetyWarning
 * Description: Detects sound and particulates in the air. If unsafe levels of either
 * are present, warning lights will display so residents in the work area know
 * to wear the appropriate safety gear.
 * 
 * The system will also send data to an Adafruit dashboard to track sound / particulate levels and warning events
 * 
 * Author: Clint Wolf
 * Date: April 2022
 */

//===============================================

#include <JsonParserGeneratorRK.h>
#include <Particle.h>
#include <Wire.h>

#include <neopixel.h>

// Adafruit.io Set Up BEGIN
#include <Adafruit_MQTT.h>          
#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT/Adafruit_MQTT.h"

/************************* Adafruit.io Setup ******************************************/
#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883   // use 1883 for SSL
#define AIO_USERNAME    ""
#define AIO_KEY         ""

/************ Global State (no need to change this) ************/
TCPClient TheClient;

// Setup the MQTT client class by passing in the WiFi client, MQTT server, and login details
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY);

/****************************** Feeds ***************************************/ 
// Setup a feed called <object> for publishing. 
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>

//Example: Adafruit_MQTT_Publish theTemperatureObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/Feed2_Temperature");
//Adafruit_MQTT_Publish theSoundObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/SafetyWarning_Sound");
//Note that if the feed exists in a group (fuse) on Adafruit, it must be referenced using a period as in the following:
//Adafruit_MQTT_Publish theSoundObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningsound");
Adafruit_MQTT_Publish theSoundObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningsound");
Adafruit_MQTT_Publish theSoundVoltsObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningmicrophonevolts");
Adafruit_MQTT_Publish theParticulateObjectOne = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningparticulatesone");
Adafruit_MQTT_Publish theParticulateObjectTwoPointFive = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningparticulatestwopointfive");
Adafruit_MQTT_Publish theParticulateObjectTen = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.safetywarningparticulatesten");

// Adafruit.io Set Up END

//-----------------------------------------

const int PIN_Microphone = 3; // Pin 3 (D3) on Argon for output signal to neopixel ring
const int PIN_Particulate = 5; // Pin 5 (D5) on Argon for output signal to neopixel ring
const int NUMPIXELS = 12; // the actual number of neopixels (per ring)
//Adafruit_NeoPixel thePixelObject(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
//supports WS2811/WS2812/WS2813
Adafruit_NeoPixel thePixelObject_Microphone(NUMPIXELS, PIN_Microphone, WS2812B);
Adafruit_NeoPixel thePixelObject_Particulate(NUMPIXELS, PIN_Particulate, WS2812B);
//neopixel thePixelObject(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// Constructor: number of LEDs, pin number, LED type

const int pixelDelay=250; // just a variable I created to delay a change in pixel properties
const int startPixel=0; // the first pixel is 0 (zero based)
int var_PixelBrightness = 200; // the brightness value can go from 1 to 255
// thePixelObject.Color() takes RGB values from 0,0,0, to 255,255,255 - or a defined color from an included COLORS file

//-----------------------------------------

int var_LastMicrophoneRead = 0; // Number of milliseconds since last read
float var_MicrophoneReadValue = 0.0; //value read from sensor
const int var_MicrophoneSampleWindow = 50; // a "read" of the microphone will last 50 milliseconds
int var_VoltsPublishCounter = 0; // Used to publish microphone volts data 1/4 as much as the decibel data

int var_LastParticulateRead = 0; // Number of milliseconds since last read
float var_ParticulateReadValue = 0.0; //value read from sensor
int var_ParticulateAccumulator = 0; // used to "remember" if any of the 3 particulate readings are in the warning zone

int var_LastServerPing = 0;

//-----------------------------------------

// Start of Particulate sensor items

uint8_t buf[30];

unsigned int lastTime, last, lastSound; // used to determine the time elapsed between sensor reads

const char* arr_ParticulateString[] = 
  {
    "Sensor ID: ", 
    "PM 1.0 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
    "PM 2.5 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
    "PM 10.0 concentration(CF=1,Standard particulate matter,unit:ug/m3): ",
    // "PM1.0 concentration(Atmospheric environment,unit:ug/m3): ",
    // "PM2.5 concentration(Atmospheric environment,unit:ug/m3): ",
    // "PM10 concentration(Atmospheric environment,unit:ug/m3): ",
  };

// End of Particulate Sensor itmes

//-----------------------------------------


// Use the following to adjust the "bad" threshhold values
// Values above the values listed below will trigger the warning mechanism

// PARTICULATE WARNING THRESHHOLDS
//uint16_t var_WarningThreshhold_PM1_0 = 50;
//uint16_t var_WarningThreshhold_PM2_5 = 50;
//uint16_t var_WarningThreshhold_PM10_0 = 50;
// The format for the array below is:
//       {Sensor ID (just a placeholder in this array), PM1.0, PM2.5, PM10.0}
//       For example, the second element in the array will be the threshhold value for PM1.0
uint16_t arr_WarningThreshhold[4] = {0, 7, 11, 13}; // used only for the particulate sensor

// SOUND WARNING THRESHHOLD
float var_WarningThreshhold_Microphone = 2.00; // not used since we've converted to decibels
//float var_ParticulateDangerThreshhold = 50.0;

const unsigned int var_SensorReadInterval = 30000; // in milliseconds
float var_SoundDB;

SYSTEM_THREAD(ENABLED);
//===========================================================

void setup() 
{
  // Put initialization like pinMode and begin functions here.
  
  // For the Microphone:
  // Pin A1 to do an Analog read from the microphone
  // Pin D3 and D5 to send a signal to the appropriate neopixel ring
  
  // For the Particulate Sensor:
  // Use SCL and SDA

  Serial.begin(9600);
  //while(!Serial);
  waitFor(Serial.isConnected, 15000);
  delay(1);

  Wire.begin();
  Wire.beginTransmission(0x40);
  Wire.write(0x88);
  Wire.endTransmission(false);

  Serial.println(); // just giving myself a line break to more easily see the message in the mext line on the terminal
  Serial.println("The SafetyWarning program has activated the Serial port.");

  pinMode(3,OUTPUT); // used for signal for neopixel ring
  pinMode(5,OUTPUT); // used for signal for neopixel ring

  thePixelObject_Microphone.begin();
  ManageWarningPixelRing_Microphone(0); // sets pixel ring to an initial color of GREEN 
  //thePixelObject_Microphone.clear(); // sets all pixels' colors to 'off' - to initialize ALL pixels

  thePixelObject_Particulate.begin();
  ManageWarningPixelRing_Particulates(0); // sets pixel ring to an initial color of GREEN
  //thePixelObject_Particulate.clear(); // sets all pixels' colors to 'off' - to initialize ALL pixels

  //Connect to WiFi but not Particle Cloud
  WiFi.connect();
  while(WiFi.connecting()) 
     {
      Serial.printf(".");
       delay(250);
     }
  Serial.printf("\n");
  delay(5);
}

//===========================================================

void loop()  {
  MQTT_connect();
  KeepConnectionAlive();

  if((millis()-lastSound)> var_SensorReadInterval/4) {
    func_ReadMicrophone();
    lastSound = millis();
  }

  if((millis()-lastTime) > var_SensorReadInterval) {    
    func_ReadParticulateSensorValue(buf,29);
    func_ParseParticulateResultValue(buf);
    func_ParseResult(buf);

    // Print a couple of blank lines for readability
    Serial.printf("\n\n------------- \n\n");

    lastTime = millis();
  }
      
  delay(1); // allow some time to complete all system tasks
}

//===========================================================

void func_ReadMicrophone()
{
  unsigned long var_StartMillis = millis(); // start of sample window
  unsigned int var_PeakToPeak = 0;
  unsigned int var_SignalMax = 0;
  unsigned int var_SignalMin = 4096;   // BRIAN - changed to match Argon
  float var_MicrophoneVolts;

  // collect data for 50 milliseconds
  while (millis() - var_StartMillis < var_MicrophoneSampleWindow)
    {
      var_MicrophoneReadValue = analogRead(A1);
      if (var_MicrophoneReadValue < 4096)          // BRIAN - changed to match Argon
        {
          if (var_MicrophoneReadValue > var_SignalMax)
            {
              var_SignalMax = var_MicrophoneReadValue;
            }
          else if (var_MicrophoneReadValue < var_SignalMin)
            {
              var_SignalMin = var_MicrophoneReadValue;
            }
        }
    }

    var_PeakToPeak = var_SignalMax - var_SignalMin; // max - min = peak to peak value (amplitude)
    var_MicrophoneVolts = (var_PeakToPeak * 3.3) / 4096; // convert to volts  // BRIAN - changed to match Argon
    
  if (mqtt.Update())
    {
      Serial.print ("Microphone value: ");
      // Values over 2000 are considered misreads and are converted to zero
      // Now either print/publish the calculated value or zero if no value exists during this read
      if (var_MicrophoneVolts < 2000.0)
        {
          Serial.print (var_MicrophoneVolts);
        }
      else
        {
          var_MicrophoneVolts = 0;
          Serial.print (var_MicrophoneVolts);
        }
      Serial.print(" (threshhold warning limit: 80");
      //Serial.print(var_WarningThreshhold_Microphone);
      Serial.print(")\n");
      
      if(var_MicrophoneVolts < 1.11) {                       //convert voltage to dB - two regions
        var_SoundDB = 350*log10(var_MicrophoneVolts)+50;
      }
      else {
        var_SoundDB = 32*log10(var_MicrophoneVolts) + 64.6; 
      }



      // Determine color of ring lights based on decibel values for Microphone neopixel ring

      if (var_SoundDB < 70)
        {
          ManageWarningPixelRing_Microphone(0); // turn the warning light GREEN
        }
      else
        if (var_SoundDB >= 70 && var_SoundDB <= 80)
          {
            ManageWarningPixelRing_Microphone(1); // turn the warning light YELLOW
          }
        else
          {
            ManageWarningPixelRing_Microphone(2); // turn the warning light RED
            Serial.print("  This warning threshhold has been exceeded\n");
          }
       
      PublishSoundValueToDashboard(var_SoundDB);
      
      //if (var_VoltsPublishSwitch == 0)
      //  {
      //    PublishSoundVoltsValueToDashboard(var_MicrophoneVolts);
      //   var_VoltsPublishSwitch = 1;
      //  }
      //else
      //  {
      //    var_VoltsPublishSwitch = 0;
      //  }


      if (var_VoltsPublishCounter == 3)
        {
          PublishSoundVoltsValueToDashboard(var_MicrophoneVolts);
          var_VoltsPublishCounter = 0;
        }
      else
        {
          var_VoltsPublishCounter = var_VoltsPublishCounter + 1;
        }
        

    }
}

//===========================================================
// Convert Voltage to dB and publish to Adafruit.io via MQTT

void PublishSoundValueToDashboard(float theSoundValue) 
{
//  var_soundDB = 53*log10(theSoundValue) + 59;   //convert voltage to dB
  theSoundObject.publish(theSoundValue);
  //Serial.print(theSoundValue);
  //Serial.print("\n");
}

//===========================================================

void PublishSoundVoltsValueToDashboard(float theSoundValue) 
{
  theSoundVoltsObject.publish(theSoundValue);
  //Serial.print(theSoundValue);
  //Serial.print("\n");
}

//===========================================================

void func_ReadParticulateSensorValue(uint8_t* data, int data_len) {
  int i;
  
  Wire.requestFrom(0x40, 29);
  for (i = 0; i < data_len; i++) {
    data[i] = Wire.read();
  }
}

//===========================================================

void func_ParseParticulateResultValue(uint8_t* data)
{
//  for (int i = 0; i < 28; i++)
//  {
//    Serial.print("From ParseResultValue (these are HEX values): ");
//    Serial.println(data[i], HEX);
//  }
  uint8_t sum = 0;
    for (int i = 0; i < 28; i++)
    {
        sum += data[i];
    }
    if (sum != data[28])
    {
        Serial.printf("wrong checkSum!! \n");
    }
}

//===========================================================

void func_ParseResult(uint8_t* data) 
{
  //var_ParticulateAccumulator = 0;
  uint16_t var_ParticulateValue = 0;
  for (int i = 1; i < 5; i++) 
    {
     var_ParticulateValue = (uint16_t) data[i * 2] << 5 | data[i * 2 + 1];
     // The line below is the main PRINT call for the Particulate Sensor
     func_PrintResult(arr_ParticulateString[i - 1], var_ParticulateValue, i-1); // passing "i" too for array reference - CW
     //if (i>1) // skipping the first data value as it is the sensor ID number
     // {
     //   Serial.printf("Value: ");
     //   Serial.println(var_ParticulateValue);
     // }

     // NOTE: Only the PM10.0 value will be used when determining what color the Particulate Warning light will be
     if ((i-1) == 3) // referencing the PM10.0 value cell in the array
      {
        //check the value for color range
        if (var_ParticulateValue < 16) // then green
          {
            ManageWarningPixelRing_Particulates(0);
          }
        else
          {
            if (var_ParticulateValue >= 16 && var_ParticulateValue < 22) // then yellow
              {
                ManageWarningPixelRing_Particulates(1);
              }
            else // then red
              {
                ManageWarningPixelRing_Particulates(2);
              }
          }
      }
    }

    //if (var_ParticulateAccumulator > 0) 
    //{
    //    ManageWarningPixelRing_Particulates(1); // turn the warning light RED
    //  }
    //else 
    //  {
    //    ManageWarningPixelRing_Particulates(0); // turn the warning light GREEN
    //  }
}

//===========================================================

void func_PrintResult(const char* theParticulateString, uint16_t theParticulateValue, int theArrayIndex1) // added theArrayIndex - CW
{
    //Serial.printf(theParticulateString);
    //Serial.print(theParticulateValue);

    if (theArrayIndex1 > 0)
      {
        //Serial.print("\n");
        Serial.printf(theParticulateString);
        Serial.print(theParticulateValue);
        Serial.print(" (threshhold warning limit: ");
        Serial.print(arr_WarningThreshhold[theArrayIndex1]);
        Serial.print(")\n");
        PublishParticulateValueToDashboard(theArrayIndex1, theParticulateValue);
      }
    else
      {
        Serial.print("\n");
      }

    if (theArrayIndex1 > 0) // when "i" is 0, it is referencing the Sensor ID, so it does not need to be checked against a threshhold
      {
        if (theParticulateValue > arr_WarningThreshhold[theArrayIndex1])
          {
           Serial.print("  This warning threshhold has been exceeded");
           //Serial.print(theParticulateString);
           //Serial.print(" has been exceeded");
           //Serial.print(arr_WarningThreshhold[theArrayIndex]);
           Serial.print("\n");
           //var_ParticulateAccumulator = var_ParticulateAccumulator + 1;
          }
        //else
        //  {
        //   Serial.print("\n");
        // }
        Serial.print("\n");
      }
}

//===========================================================

void PublishParticulateValueToDashboard(int theArrayIndex2, float theParticulateValue)
{
  switch(theArrayIndex2)
    {
      case 1:
        theParticulateObjectOne.publish(theParticulateValue);
        break;
      case 2:
        theParticulateObjectTwoPointFive.publish(theParticulateValue);
        break;
      case 3:
        theParticulateObjectTen.publish(theParticulateValue);
        break;
    }
}

//===========================================================

void ManageWarningPixelRing_Microphone(int theOnOffValue)
  {
    if (theOnOffValue == 0) // turn the warning light GREEN
      {
        for (int i=0; i<12; i++) // runs through each neopixel address
          {
           thePixelObject_Microphone.setPixelColor(i,0x00FF00);
           thePixelObject_Microphone.setBrightness(var_PixelBrightness);
          }
      }
    else
      {
        if (theOnOffValue == 1) // turn the warning light YELLOW
          {
           for (int i=0; i<12; i++) // runs through each neopixel address
             {
              thePixelObject_Microphone.setPixelColor(i,0xFFFF00);
              thePixelObject_Microphone.setBrightness(var_PixelBrightness);
             }
          }
        else // turn the warning light RED
          {
           for (int i=0; i<12; i++) // runs through each neopixel address
              {
               thePixelObject_Microphone.setPixelColor(i,0xFF0000);
               thePixelObject_Microphone.setBrightness(var_PixelBrightness);
              }
          }
      }
    
    thePixelObject_Microphone.show(); // sends the updated neopixel values out to the neopixels
  } 

//===========================================================

void ManageWarningPixelRing_Particulates(int theColorValue)
  {
    if (theColorValue == 0) // turn the warning lights GREEN
      {
        for (int i=0; i<12; i++) // runs through each neopixel address
          {
           thePixelObject_Particulate.setPixelColor(i,0x00FF00);
           thePixelObject_Particulate.setBrightness(var_PixelBrightness);
          }
      }
    else
      {
        if (theColorValue == 1) // turn the warning lights YELLOW
          {
            for (int i=0; i<12; i++) // runs through each neopixel address
              {
                 thePixelObject_Particulate.setPixelColor(i,0xFFFF00);
                 thePixelObject_Particulate.setBrightness(var_PixelBrightness);
              }
          }
        else // turn the warning lights RED
          {
            for (int i=0; i<12; i++) // runs through each neopixel address
              {
                thePixelObject_Particulate.setPixelColor(i,0xFF0000);
                thePixelObject_Particulate.setBrightness(var_PixelBrightness);
              }
          }
      }

    thePixelObject_Particulate.show(); // sends the updated neopixel values out to the neopixels
  }

//===========================================================

// Function to connect and reconnect as necessary to the MQTT server.

void MQTT_connect() 
{
  int8_t ret;
 
  // Stop if already connected.
  if (mqtt.connected()) 
  {
    return;
  }
 
  Serial.print("Connecting to MQTT... ");
 
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.println(mqtt.connectErrorString(ret));
       Serial.println("Retrying MQTT connection in 5 seconds...");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
  }
  Serial.println("MQTT Connected!");
}

//===========================================================

void KeepConnectionAlive()
{
  // The default "keep-alive" timeframe is 5 minutes, or 300 seconds.
  // I will check for a publish event and if none has occured in the last 2
  // minutes, I will ping the server.
  if ((millis()-var_LastServerPing) > 120000) // (120 seconds)
    {
      Serial.println("Pinging the MQTT server");
      // ping the server to keep the mqtt connection alive
      if(! mqtt.ping()) // if I cannot ping the server, force a disconnect
         {
           Serial.println("Forcing an MQTT disconnect");
           mqtt.disconnect(); // forcing a disconnect will force a reconnection
           Serial.println("Attempting to reestablish MQTT connection");
         }
      var_LastServerPing = millis();
    }
  delay(5);  // just enough of a delay to allow completion of other tasks
}

Credits

Clint Wolf

Clint Wolf

3 projects • 1 follower
Brian Rashap

Brian Rashap

9 projects • 46 followers
Former General Manager of US Facilities Operations at Intel Corporation. Currently loving my encore career as a teacher focused on IoT.

Comments

Add projectSign up / Login