Hardware components | ||||||
![]() |
| × | 3 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
![]() |
| |||||
![]() |
| |||||
| ||||||
| ||||||
|
For direct injection engines, the positive crankcase ventilation (PCV) removes pressure (in the form of air and oil droplets) from the engine's crankcase and reverts it through the intake system. The problem is this pressure relief hose allows oil and other nasty stuff from the crankcase to get into the intake manifold, where it doesn't belong. These contaminants can coat everything within the intercooler, boost hoses, and inside the intake and solidify due to the high combustion temperatures; when left unchecked, this buildup can hurt fuel economy and horsepower, create knocking, preignition, and can even cause misfires.
Catch cans are commonly used to separate oil droplets from the air circulating through the engine and intake systems of a car. By disconnecting the air intake from the positive crankcase ventilation valve (PCV) and connecting the PCV hose to the intake, a catch can is easily be integrated into an engine. The air/oil mixture flows into the can, where the oil separates from the air via condensation of the oil at the bottom of the can while the air is allowed to ventilate through the filter and out of a hose to the air intake port.
The catch can is equipped with a drain plug to empty the oil during normal engine servicing. However, an excess of oil, toxic gases, varying temperatures, humidity, or pressure between servicing can signal a much bigger problem for the vehicle. By integrating a catch can with a variety of sensors and an OLED display for active monitoring, your car will be breathing easier than ever, providing more power and costing you less money in gas in no time.
// 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() {
}
// 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
}
// 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
}
Comments
Please log in or sign up to comment.