/*
* 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 "IoTLabs"
#define AIO_KEY "aio_REBO83agD50Mqh9VohnAzJcOK5ky"
/************ 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
}
Comments
Please log in or sign up to comment.