IOTProject52
Published © GPL3+

Catch Can Monitoring System

Let your engine breathe a little easier.

IntermediateWork in progress12 hours252
Catch Can Monitoring System

Things used in this project

Hardware components

Argon
Particle Argon
×3
Temp&Humi&Barometer Sensor (BME280)
Seeed Studio Temp&Humi&Barometer Sensor (BME280)
×1
Grove - Air quality sensor v1.3
Seeed Studio Grove - Air quality sensor v1.3
×1
Grove - OLED Display 1.12'' V2
Seeed Studio Grove - OLED Display 1.12'' V2
×1
Elegoo Water Level Detection Sensor
×1
NTC 3950 Thermistor
×1
Resistor 10k ohm
Resistor 10k ohm
×1

Software apps and online services

ThingSpeak API
ThingSpeak API

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
10 Pc. Jumper Wire Kit, 5 cm Long
10 Pc. Jumper Wire Kit, 5 cm Long
Breadboard, 170 Pin
Breadboard, 170 Pin
soldered connections would be more practical for real-world application
10AN 90 degree swivels
*Substitutions allowed
10AN hose
*Substitutions allowed
Universal Baffled Catch Can Assembly
*Substitutions allowed

Story

Read more

Schematics

Sensor Specifications

Schematic

Block Diagram

Thing Speak Data

Code

OLED

C#
// This #include statement was automatically added by the Particle IDE.
#include <Grove_OLED_128x64.h>
int wait = 3000;
int temp, lvl, press, humid, qual;
void qualHandler(const char *event, const char *data){
    if (atoi(data) <= 2){
        Particle.publish("Warning", "Low quality", PUBLIC);
    }else{
  Particle.publish("Verify qual", String(data), PUBLIC);
    }
  SeeedOled.clearDisplay(); 
  SeeedOled.setTextXY(0,0);
  SeeedOled.putString("Qual: " + String(data));
  delay(wait);
}

void tempHandler(const char *event, const char *data){
    if (atoi(data) > 300){
        Particle.publish("Warning", "High Temperature", PUBLIC);
    }else{
  Particle.publish("Verify temp", String(data), PUBLIC);
    }
  SeeedOled.clearDisplay(); 
  SeeedOled.setTextXY(1,0);
  SeeedOled.putString("Temp: " + String(data) + "F");
  delay(wait);
  }
  
void lvlHandler(const char *event, const char *data){
    if (atoi(data) > 500){
        Particle.publish("Warning", "Full Can", PUBLIC);
    }else{
  Particle.publish("Verify lvl", String(data), PUBLIC);
    }
  SeeedOled.clearDisplay(); 
  SeeedOled.setTextXY(2,0);
  SeeedOled.putString("Level: " + String(data));
  delay(wait);
  }
  
void thinghHandler(const char *event, const char *data){
    if (atoi(data) > 80){
        Particle.publish("Warning", "High Humidity", PUBLIC);
    }else{
        Particle.publish("Verify humidity", String(data), PUBLIC);
    }
  SeeedOled.clearDisplay(); 
  SeeedOled.setTextXY(3,0);
  SeeedOled.putString("Humidty: " + String(data) + "%");
  delay(wait);
  }
  
void thingpHandler(const char *event, const char *data){
    if (atoi(data) > 3){
        Particle.publish("Warning", "High Pressure", PUBLIC);
    }else{
        Particle.publish("Verify Press", String(data), PUBLIC);
    }
  SeeedOled.clearDisplay(); 
  SeeedOled.setTextXY(4,0);
  SeeedOled.putString("Press: " + String(data) + "atm");
  delay(wait);
}

void setup() {
    Wire.begin();
    
    SeeedOled.init();  //initialze SEEED OLED display
    Particle.publish("Status", "I have connected", PUBLIC);
    int time_now = millis();
    while(millis() <= time_now + wait){
    }
    SeeedOled.clearDisplay();          //clear the screen and set start position to top left corner
    SeeedOled.setNormalDisplay();      //Set display to normal mode (i.e non-inverse mode)
    SeeedOled.setPageMode();           //Set addressing mode to Page Mode
    SeeedOled.setTextXY(0,0);          //Set the cursor to Xth Page, Yth Column
    SeeedOled.putString("Hello World!"); //Print the String
    delay(wait);
    Particle.publish("Status", "Going into the cdoe.", PUBLIC);
    Particle.subscribe("rPress", thingpHandler);
    Particle.subscribe("rLVL", lvlHandler);
    Particle.subscribe("rhumid", thinghHandler);
    Particle.subscribe("rQual", qualHandler);
    Particle.subscribe("rTemp", tempHandler);
}

void loop() {
    

}

Air Quality and Thermistor

C#
// This #include statement was automatically added by the Particle IDE.
#include <photon-thermistor.h>

// This #include statement was automatically added by the Particle IDE.
#include <ThingSpeak.h>

// This #include statement was automatically added by the Particle IDE.
#include <Grove_Air_quality_Sensor.h>

AirQualitySensor sensor(A0);
Thermistor *thermistor;
TCPClient client;
int myChannel = 1902499;  // ThingSpeak channel id
int wait = 2000;
int period = 10000;
const String readKey = "RWBU3O76REKRMVO1"; //readKey if the channel is public you only need the channel id if not the key is required
const String writeKey = "VG4CSRB16GDOPROQ"; //writeKey allows you to populate fields in ThingSpeak
particle::Future<bool> publishFuture; //This will be used later to trigger an event in a special case

void setup(void) {
    ThingSpeak.begin(client);
    thermistor = new Thermistor(A2, 9990, 100000, 25, 3950, 5, 20);
    Particle.publish("Status", "Waiting sensor to init...", PUBLIC);
    delay(wait);
  
  if (sensor.init()) {
    Particle.publish("Status", "Sensor ready.", PUBLIC);
  }
  else {
    Particle.publish("Status", "Sensor ERROR!", PUBLIC);
  }
}

  
void loop(void) {
  
    int quality = sensor.slope();
    float tempF = thermistor->readTempF();
    float tempC = thermistor->readTempC();

    ThingSpeak.setField(1, tempC);
    ThingSpeak.setField(2, tempF);
    ThingSpeak.setField(6, quality);
    ThingSpeak.writeFields(myChannel, writeKey);
  
  if (quality == AirQualitySensor::FORCE_SIGNAL) {
    Particle.publish("Sensor value", "High pollution! Force signal active.");
  }
  else if (quality == AirQualitySensor::HIGH_POLLUTION) {
   publishFuture = Particle.publish("Sensor value", "High pollution!");
   Particle.publish("Sensor value", "High pollution! Force signal active.");
     if (publishFuture.isDone()){
     if(publishFuture.isSucceeded()){
         Particle.publish("Quality", "Check Filter", PUBLIC); //If the quality is very poor this will notify IFTTT
         }
         }
  }
  else if (quality == AirQualitySensor::LOW_POLLUTION) {
    Particle.publish("Sensor value", "Low pollution!");
  }
  else if (quality == AirQualitySensor::FRESH_AIR) {
    Particle.publish("Sensor value", "Fresh air.");
  }
    
    float rhumid = ThingSpeak.readFloatField(myChannel, 3);
    float rpress = ThingSpeak.readFloatField(myChannel, 4);
    float rlvl = ThingSpeak.readFloatField(myChannel, 5);
    
    Particle.publish("rhumidity", String::format("%2.0f",rhumid),60,PUBLIC);
    delay(wait);
    Particle.publish("rPressure", String::format("%.1f",rpress),60,PUBLIC);
    delay(wait);
    Particle.publish("rLVL", String::format("%3.0f",rlvl),60,PUBLIC);
    delay(wait);
    
    //morphing
} 

BME280 & Water Level Detector

C#
// This #include statement was automatically added by the Particle IDE.
#include <ThingSpeak.h>

#pragma once

#ifndef _SEEED_BME280_H_
#define _SEEED_BME280_H_

#include <Arduino.h>
#include <Wire.h>

#define BME280_ADDRESS   0x76

#define BME280_REG_DIG_T1    0x88
#define BME280_REG_DIG_T2    0x8A
#define BME280_REG_DIG_T3    0x8C

#define BME280_REG_DIG_P1    0x8E
#define BME280_REG_DIG_P2    0x90
#define BME280_REG_DIG_P3    0x92
#define BME280_REG_DIG_P4    0x94
#define BME280_REG_DIG_P5    0x96
#define BME280_REG_DIG_P6    0x98
#define BME280_REG_DIG_P7    0x9A
#define BME280_REG_DIG_P8    0x9C
#define BME280_REG_DIG_P9    0x9E

#define BME280_REG_DIG_H1    0xA1
#define BME280_REG_DIG_H2    0xE1
#define BME280_REG_DIG_H3    0xE3
#define BME280_REG_DIG_H4    0xE4
#define BME280_REG_DIG_H5    0xE5
#define BME280_REG_DIG_H6    0xE7

#define BME280_REG_CHIPID          0xD0
#define BME280_REG_VERSION         0xD1
#define BME280_REG_SOFTRESET       0xE0

#define BME280_REG_CAL26           0xE1

#define BME280_REG_CONTROLHUMID    0xF2
#define BME280_REG_CONTROL         0xF4
#define BME280_REG_CONFIG          0xF5
#define BME280_REG_PRESSUREDATA    0xF7
#define BME280_REG_TEMPDATA        0xFA
#define BME280_REG_HUMIDITYDATA    0xFD

class BME280 {
  public:
    bool init(int i2c_addr = BME280_ADDRESS);
    float getTemperature(void);
    float getPressure(void);
    float getHumidity(void);
    float calcAltitude(float pressure);
  private:
    int _devAddr;
    bool isTransport_OK;

    // Calibration data
    uint16_t dig_T1;
    int16_t dig_T2;
    int16_t dig_T3;
    uint16_t dig_P1;
    int16_t dig_P2;
    int16_t dig_P3;
    int16_t dig_P4;
    int16_t dig_P5;
    int16_t dig_P6;
    int16_t dig_P7;
    int16_t dig_P8;
    int16_t dig_P9;
    uint8_t dig_H1;
    int16_t dig_H2;
    uint8_t dig_H3;
    int16_t dig_H4;
    int16_t dig_H5;
    int8_t  dig_H6;
    int32_t t_fine;

    // private functions
    uint8_t BME280Read8(uint8_t reg);
    uint16_t BME280Read16(uint8_t reg);
    uint16_t BME280Read16LE(uint8_t reg);
    int16_t BME280ReadS16(uint8_t reg);
    int16_t BME280ReadS16LE(uint8_t reg);
    uint32_t BME280Read24(uint8_t reg);
    void writeRegister(uint8_t reg, uint8_t val);
};

bool BME280::init(int i2c_addr) {
    uint8_t retry = 0;
    uint8_t chip_id = 0;


    _devAddr = i2c_addr;
    Wire.begin();

    while ((retry++ < 5) && (chip_id != 0x60)) {
        chip_id = BME280Read8(BME280_REG_CHIPID);
        #ifdef BMP280_DEBUG_PRINT
        Serial.print("Read chip ID: ");
        Serial.println(chip_id);
        #endif
        delay(100);
    }
    if (chip_id != 0x60){
        Serial.println("Read Chip ID fail!");
        return false;
    }

    dig_T1 = BME280Read16LE(BME280_REG_DIG_T1);
    dig_T2 = BME280ReadS16LE(BME280_REG_DIG_T2);
    dig_T3 = BME280ReadS16LE(BME280_REG_DIG_T3);

    dig_P1 = BME280Read16LE(BME280_REG_DIG_P1);
    dig_P2 = BME280ReadS16LE(BME280_REG_DIG_P2);
    dig_P3 = BME280ReadS16LE(BME280_REG_DIG_P3);
    dig_P4 = BME280ReadS16LE(BME280_REG_DIG_P4);
    dig_P5 = BME280ReadS16LE(BME280_REG_DIG_P5);
    dig_P6 = BME280ReadS16LE(BME280_REG_DIG_P6);
    dig_P7 = BME280ReadS16LE(BME280_REG_DIG_P7);
    dig_P8 = BME280ReadS16LE(BME280_REG_DIG_P8);
    dig_P9 = BME280ReadS16LE(BME280_REG_DIG_P9);

    dig_H1 = BME280Read8(BME280_REG_DIG_H1);
    dig_H2 = BME280Read16LE(BME280_REG_DIG_H2);
    dig_H3 = BME280Read8(BME280_REG_DIG_H3);
    dig_H4 = (BME280Read8(BME280_REG_DIG_H4) << 4) | (0x0F & BME280Read8(BME280_REG_DIG_H4 + 1));
    dig_H5 = (BME280Read8(BME280_REG_DIG_H5 + 1) << 4) | (0x0F & BME280Read8(BME280_REG_DIG_H5) >> 4);
    dig_H6 = (int8_t)BME280Read8(BME280_REG_DIG_H6);

    writeRegister(BME280_REG_CONTROLHUMID, 0x05);  //Choose 16X oversampling
    writeRegister(BME280_REG_CONTROL, 0xB7);  //Choose 16X oversampling

    return true;
}

float BME280::getTemperature(void) {
    int32_t var1, var2;

    int32_t adc_T = BME280Read24(BME280_REG_TEMPDATA);
    // Check if the last transport successed
    if (!isTransport_OK) {
        return 0;
    }
    adc_T >>= 4;
    var1 = (((adc_T >> 3) - ((int32_t)(dig_T1 << 1))) *
            ((int32_t)dig_T2)) >> 11;

    var2 = (((((adc_T >> 4) - ((int32_t)dig_T1)) *
              ((adc_T >> 4) - ((int32_t)dig_T1))) >> 12) *
            ((int32_t)dig_T3)) >> 14;

    t_fine = var1 + var2;
    float T = (t_fine * 5 + 128) >> 8;
    return T / 100;
}

float BME280::getPressure(void) {
  int64_t var1, var2, p;
  // Call getTemperature to get t_fine
  getTemperature();
  // Check if the last transport successed
  if (!isTransport_OK) {
    return 0;
  }
  int32_t adc_P = BME280Read24(BME280_REG_PRESSUREDATA);
  adc_P >>= 4;
  var1 = ((int64_t)t_fine) - 128000;
  var2 = var1 * var1 * (int64_t)dig_P6;
  var2 = var2 + ((var1 * (int64_t)dig_P5) << 17);
  var2 = var2 + (((int64_t)dig_P4) << 35);
  var1 = ((var1 * var1 * (int64_t)dig_P3) >> 8) + ((var1 * (int64_t)dig_P2) << 12);
  var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)dig_P1) >> 33;
  if (var1 == 0) {
    return 0; // avoid exception caused by division by zero
  }
  p = 1048576 - adc_P;
  p = (((p << 31) - var2) * 3125) / var1;
  var1 = (((int64_t)dig_P9) * (p >> 13) * (p >> 13)) >> 25;
  var2 = (((int64_t)dig_P8) * p) >> 19;
  p = ((p + var1 + var2) >> 8) + (((int64_t)dig_P7) << 4);
  return (float)(p / 256.0);
}

float BME280::getHumidity(void) {
  int32_t v_x1_u32r, adc_H;
  // Call getTemperature to get t_fine
  getTemperature();
  // Check if the last transport successed
  if (!isTransport_OK) {
    return 0;
  }
  adc_H = BME280Read16(BME280_REG_HUMIDITYDATA);
  v_x1_u32r = (t_fine - ((int32_t)76800));
  v_x1_u32r = (
                ((((adc_H << 14) - (((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * v_x1_u32r)) +
                ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)dig_H6)) >> 10) *
                (((v_x1_u32r * ((int32_t)dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) *
                ((int32_t)dig_H2) + 8192) >> 14));
  v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4));
  v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
  v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
  v_x1_u32r = v_x1_u32r >> 12;
  float h = v_x1_u32r / 1024.0;
  return h;
}

float BME280::calcAltitude(float pressure) {
    if (!isTransport_OK) {
        return 0;
    }

    float A = pressure / 101325;
    float B = 1 / 5.25588;
    float C = pow(A, B);
    C = 1.0 - C;
    C = C / 0.0000225577;
    return C;
}

uint8_t BME280::BME280Read8(uint8_t reg) {
    Wire.beginTransmission(_devAddr);
    Wire.write(reg);
    Wire.endTransmission();

    Wire.requestFrom(_devAddr, 1);
    // return 0 if slave didn't response
    if (Wire.available() < 1) {
        isTransport_OK = false;
        return 0;
    } else {
        isTransport_OK = true;
    }

    return Wire.read();
}

uint16_t BME280::BME280Read16(uint8_t reg) {
    uint8_t msb, lsb;

    Wire.beginTransmission(_devAddr);
    Wire.write(reg);
    Wire.endTransmission();

    Wire.requestFrom(_devAddr, 2);
    // return 0 if slave didn't response
    if (Wire.available() < 2) {
        isTransport_OK = false;
        return 0;
    } else {
        isTransport_OK = true;
    }
    msb = Wire.read();
    lsb = Wire.read();

    return (uint16_t) msb << 8 | lsb;
}

uint16_t BME280::BME280Read16LE(uint8_t reg) {
    uint16_t data = BME280Read16(reg);
    return (data >> 8) | (data << 8);
}

int16_t BME280::BME280ReadS16(uint8_t reg) {
    return (int16_t)BME280Read16(reg);
}

int16_t BME280::BME280ReadS16LE(uint8_t reg) {
    return (int16_t)BME280Read16LE(reg);
}

uint32_t BME280::BME280Read24(uint8_t reg) {
    uint32_t data;

    Wire.beginTransmission(_devAddr);
    Wire.write(reg);
    Wire.endTransmission();

    Wire.requestFrom(_devAddr, 3);
    // return 0 if slave didn't response
    if (Wire.available() < 3) {
        isTransport_OK = false;
        return 0;
    } else if (isTransport_OK == false) {
        isTransport_OK = true;
        if (!init(_devAddr)) {
            #ifdef BMP280_DEBUG_PRINT
            Serial.println("Device not connected or broken!");
            #endif
        }
    }
    data = Wire.read();
    data <<= 8;
    data |= Wire.read();
    data <<= 8;
    data |= Wire.read();

    return data;
}

void BME280::writeRegister(uint8_t reg, uint8_t val) {
    Wire.beginTransmission(_devAddr); // start transmission to device
    Wire.write(reg);       // send register address
    Wire.write(val);         // send value to write
    Wire.endTransmission();     // end transmission
}

#endif

TCPClient client;
int myChannel = 1902499;  // ThingSpeak channel id
int wait = 5000;
int readPin = A0;
int period = 103000;
unsigned long time_now = 0;
const String readKey = "RWBU3O76REKRMVO1"; //readKey if the channel is public you only need the channel id if not the key is required
const String writeKey = "VG4CSRB16GDOPROQ"; //writeKey allows you to populate fields in ThingSpeak
const String warning = "Water Level To High. Empty Catch Can.";
particle::Future<bool> publishFuture; //This will be used later to trigger an event in a special case

BME280 bme280;

void setup() {
    ThingSpeak.begin(client);
    pinMode(readPin, INPUT);
    if(!bme280.init()){
        Particle.publish("Status", "Device error", PUBLIC);
    }
    Particle.publish("Status", "Sensor Connected", PUBLIC);
    if(millis() <= time_now + period){
        time_now += period;
    }
    
}

void loop() {
    float pressure;
    
    float humid = bme280.getHumidity();
    float press = bme280.getPressure()/101325;
    float alt = bme280.calcAltitude(pressure);
    int lvl = analogRead(readPin);
    //Particle.publish("Water Level", String(lvl), PUBLIC);

    delay(wait);
    ThingSpeak.setField(3, humid);
    ThingSpeak.setField(4, press);
    ThingSpeak.setField(5, lvl);
    ThingSpeak.writeFields(myChannel, writeKey);
    
    float rqual = ThingSpeak.readFloatField(myChannel, 6);
    Particle.publish("rQuality", String::format("%1.0f",rqual),60,PUBLIC);
    delay(wait);
    
    float rtempC = ThingSpeak.readFloatField(myChannel, 1);
    Particle.publish("rTemperature C", String(rtempC), PUBLIC);
    delay(wait);
    
    float rtempF = ThingSpeak.readFloatField(myChannel, 2);
    Particle.publish("Temp", String::format("%3.0f",rtempF), PUBLIC);
    delay(period);
    
    // origiami
}

Credits

IOTProject52
1 project • 1 follower
Contact

Comments

Please log in or sign up to comment.