This commit is contained in:
Cristhian Macoh Musada
2020-07-24 21:52:28 +08:00
commit e052f62dd3
17 changed files with 1484 additions and 0 deletions

BIN
AirGradient/.DS_Store vendored Normal file

Binary file not shown.

833
AirGradient/AirGradient.cpp Normal file
View File

@ -0,0 +1,833 @@
/*
Test.h - Test library for Wiring - implementation
Copyright (c) 2006 John Doe. All right reserved.
*/
// include this library's description file
#include "AirGradient.h"
// include description files for other libraries used (if any)
#include <SoftwareSerial.h>
#include "Arduino.h"
#include <Wire.h>
// Constructor /////////////////////////////////////////////////////////////////
// Function that handles the creation and setup of instances
const int MHZ14A = 14;
const int MHZ19B = 19; // this one we use for AQI whatever
const int MHZ14A_PREHEATING_TIME = 3 * 60 * 1000;
const int MHZ19B_PREHEATING_TIME = 3 * 60 * 1000;
const int MHZ14A_RESPONSE_TIME = 60 * 1000;
const int MHZ19B_RESPONSE_TIME = 120 * 1000;
const int STATUS_NO_RESPONSE = -2;
const int STATUS_CHECKSUM_MISMATCH = -3;
const int STATUS_INCOMPLETE = -4;
const int STATUS_NOT_READY = -5;
const int STATUS_PWM_NOT_CONFIGURED = -6;
const int STATUS_serial_MHZ19_NOT_CONFIGURED = -7;
unsigned long lastRequest = 0;
bool SerialConfigured = true;
bool PwmConfigured = true;
AirGradient::AirGradient(bool displayMsg,int baudRate)
{
_debugMsg = displayMsg;
Wire.begin();
Serial.begin(baudRate);
if (_debugMsg) {
Serial.println("AirGradiant Library instantiated successfully.");
}
}
// Public Methods //////////////////////////////////////////////////////////////
// Functions available in Wiring sketches, this library, and other libraries
void AirGradient::PMS_Init(){
if (_debugMsg) {
Serial.println("Initializing PMS...");
}
PMS_Init(5,6);
}
void AirGradient::PMS_Init(int rx_pin,int tx_pin){
PMS_Init(rx_pin,tx_pin,9600);
}
void AirGradient::PMS_Init(int rx_pin,int tx_pin,int baudRate){
_SoftSerial_PMS = new SoftwareSerial(rx_pin,tx_pin);
PMS(*_SoftSerial_PMS);
_SoftSerial_PMS->begin(baudRate);
if(getPM2() <= 0){
if (_debugMsg) {
Serial.println("PMS Sensor Failed to Initialize ");
}
else{
Serial.println("PMS Successfully Initialized. Heating up for 10s");
delay(10000);
}
}
}
int AirGradient::getPM2(){
int pm02;
DATA data;
requestRead();
if (readUntil(data)) {
pm02 = data.PM_AE_UG_2_5;
return pm02;
} else {
//Serial.println("no PMS data");
return NULL;
}
}
// Private Methods /////////////////////////////////////////////////////////////
// Functions only available to other functions in this library
//START PMS FUNCTIONS //
void AirGradient::PMS(Stream& stream)
{
this->_stream = &stream;
}
// Standby mode. For low power consumption and prolong the life of the sensor.
void AirGradient::sleep()
{
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 };
_stream->write(command, sizeof(command));
}
// Operating mode. Stable data should be got at least 30 seconds after the sensor wakeup from the sleep mode because of the fan's performance.
void AirGradient::wakeUp()
{
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 };
_stream->write(command, sizeof(command));
}
// Active mode. Default mode after power up. In this mode sensor would send serial data to the host automatically.
void AirGradient::activeMode()
{
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 };
_stream->write(command, sizeof(command));
_mode = MODE_ACTIVE;
}
// Passive mode. In this mode sensor would send serial data to the host only for request.
void AirGradient::passiveMode()
{
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70 };
_stream->write(command, sizeof(command));
_mode = MODE_PASSIVE;
}
// Request read in Passive Mode.
void AirGradient::requestRead()
{
if (_mode == MODE_PASSIVE)
{
uint8_t command[] = { 0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71 };
_stream->write(command, sizeof(command));
}
}
// Non-blocking function for parse response.
bool AirGradient::read_PMS(DATA& data)
{
_data = &data;
loop();
return _PMSstatus == STATUS_OK;
}
// Blocking function for parse response. Default timeout is 1s.
bool AirGradient::readUntil(DATA& data, uint16_t timeout)
{
_data = &data;
uint32_t start = millis();
do
{
loop();
if (_PMSstatus == STATUS_OK) break;
} while (millis() - start < timeout);
return _PMSstatus == STATUS_OK;
}
void AirGradient::loop()
{
_PMSstatus = STATUS_WAITING;
if (_stream->available())
{
uint8_t ch = _stream->read();
switch (_index)
{
case 0:
if (ch != 0x42)
{
return;
}
_calculatedChecksum = ch;
break;
case 1:
if (ch != 0x4D)
{
_index = 0;
return;
}
_calculatedChecksum += ch;
break;
case 2:
_calculatedChecksum += ch;
_frameLen = ch << 8;
break;
case 3:
_frameLen |= ch;
// Unsupported sensor, different frame length, transmission error e.t.c.
if (_frameLen != 2 * 9 + 2 && _frameLen != 2 * 13 + 2)
{
_index = 0;
return;
}
_calculatedChecksum += ch;
break;
default:
if (_index == _frameLen + 2)
{
_checksum = ch << 8;
}
else if (_index == _frameLen + 2 + 1)
{
_checksum |= ch;
if (_calculatedChecksum == _checksum)
{
_PMSstatus = STATUS_OK;
// Standard Particles, CF=1.
_data->PM_SP_UG_1_0 = makeWord(_payload[0], _payload[1]);
_data->PM_SP_UG_2_5 = makeWord(_payload[2], _payload[3]);
_data->PM_SP_UG_10_0 = makeWord(_payload[4], _payload[5]);
// Atmospheric Environment.
_data->PM_AE_UG_1_0 = makeWord(_payload[6], _payload[7]);
_data->PM_AE_UG_2_5 = makeWord(_payload[8], _payload[9]);
_data->PM_AE_UG_10_0 = makeWord(_payload[10], _payload[11]);
}
_index = 0;
return;
}
else
{
_calculatedChecksum += ch;
uint8_t payloadIndex = _index - 4;
// Payload is common to all sensors (first 2x6 bytes).
if (payloadIndex < sizeof(_payload))
{
_payload[payloadIndex] = ch;
}
}
break;
}
_index++;
}
}
//END PMS FUNCTIONS //
//START TMP_RH FUNCTIONS//
uint32_t AirGradient::getTemp(){
TMP_RH result = periodicFetchData();
return result.t;
}
int AirGradient::getRhum(){
TMP_RH result = periodicFetchData();
return result.rh;
}
TMP_RH_ErrorCode AirGradient::TMP_RH_Init(uint8_t address) {
if (_debugMsg) {
Serial.println("Initializing TMP_RH...");
}
TMP_RH_ErrorCode error = SHT3XD_NO_ERROR;
_address = address;
periodicStart(SHT3XD_REPEATABILITY_HIGH, SHT3XD_FREQUENCY_10HZ);
return error;
}
TMP_RH_ErrorCode AirGradient::reset()
{
return softReset();
}
TMP_RH AirGradient::periodicFetchData() //
{
TMP_RH_ErrorCode error = writeCommand(SHT3XD_CMD_FETCH_DATA);
if (error == SHT3XD_NO_ERROR)
return readTemperatureAndHumidity();
else
returnError(error);
}
TMP_RH_ErrorCode AirGradient::periodicStop() {
return writeCommand(SHT3XD_CMD_STOP_PERIODIC);
}
TMP_RH_ErrorCode AirGradient::periodicStart(TMP_RH_Repeatability repeatability, TMP_RH_Frequency frequency) //
{
TMP_RH_ErrorCode error;
switch (repeatability)
{
case SHT3XD_REPEATABILITY_LOW:
switch (frequency)
{
case SHT3XD_FREQUENCY_HZ5:
error = writeCommand(SHT3XD_CMD_PERIODIC_HALF_L);
break;
case SHT3XD_FREQUENCY_1HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_1_L);
break;
case SHT3XD_FREQUENCY_2HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_2_L);
break;
case SHT3XD_FREQUENCY_4HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_4_L);
break;
case SHT3XD_FREQUENCY_10HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_10_L);
break;
default:
error = SHT3XD_PARAM_WRONG_FREQUENCY;
break;
}
break;
case SHT3XD_REPEATABILITY_MEDIUM:
switch (frequency)
{
case SHT3XD_FREQUENCY_HZ5:
error = writeCommand(SHT3XD_CMD_PERIODIC_HALF_M);
break;
case SHT3XD_FREQUENCY_1HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_1_M);
break;
case SHT3XD_FREQUENCY_2HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_2_M);
break;
case SHT3XD_FREQUENCY_4HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_4_M);
break;
case SHT3XD_FREQUENCY_10HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_10_M);
break;
default:
error = SHT3XD_PARAM_WRONG_FREQUENCY;
break;
}
break;
case SHT3XD_REPEATABILITY_HIGH:
switch (frequency)
{
case SHT3XD_FREQUENCY_HZ5:
error = writeCommand(SHT3XD_CMD_PERIODIC_HALF_H);
break;
case SHT3XD_FREQUENCY_1HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_1_H);
break;
case SHT3XD_FREQUENCY_2HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_2_H);
break;
case SHT3XD_FREQUENCY_4HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_4_H);
break;
case SHT3XD_FREQUENCY_10HZ:
error = writeCommand(SHT3XD_CMD_PERIODIC_10_H);
break;
default:
error = SHT3XD_PARAM_WRONG_FREQUENCY;
break;
}
break;
default:
error = SHT3XD_PARAM_WRONG_REPEATABILITY;
break;
}
delay(100);
return error;
}
TMP_RH_ErrorCode AirGradient::writeCommand(TMP_RH_Commands command)
{
Wire.beginTransmission(_address);
Wire.write(command >> 8);
Wire.write(command & 0xFF);
return (TMP_RH_ErrorCode)(-10 * Wire.endTransmission());
}
TMP_RH_ErrorCode AirGradient::softReset() {
return writeCommand(SHT3XD_CMD_SOFT_RESET);
}
uint32_t AirGradient::readSerialNumber()
{
uint32_t result = SHT3XD_NO_ERROR;
uint16_t buf[2];
if (writeCommand(SHT3XD_CMD_READ_SERIAL_NUMBER) == SHT3XD_NO_ERROR) {
if (read_TMP_RH(buf, 2) == SHT3XD_NO_ERROR) {
result = (buf[0] << 16) | buf[1];
}
}
else if(writeCommand(SHT3XD_CMD_READ_SERIAL_NUMBER) != SHT3XD_NO_ERROR){
if (_debugMsg) {
Serial.println("TMP_RH Failed to Initialize.");
}
}
return result;
}
uint32_t AirGradient::testTMP_RH()
{
uint32_t result = SHT3XD_NO_ERROR;
uint16_t buf[2];
if (writeCommand(SHT3XD_CMD_READ_SERIAL_NUMBER) == SHT3XD_NO_ERROR) {
if (read_TMP_RH(buf, 2) == SHT3XD_NO_ERROR) {
result = (buf[0] << 16) | buf[1];
}
if (_debugMsg) {
Serial.print("TMP_RH successfully initialized with serial number: ");
Serial.println(result);
}
}
else if(writeCommand(SHT3XD_CMD_READ_SERIAL_NUMBER) != SHT3XD_NO_ERROR){
if (_debugMsg) {
Serial.println("TMP_RH Failed to Initialize.");
}
}
return result;
}
TMP_RH_ErrorCode AirGradient::clearAll() {
return writeCommand(SHT3XD_CMD_CLEAR_STATUS);
}
TMP_RH AirGradient::readTemperatureAndHumidity()//
{
TMP_RH result;
result.t = 0;
result.rh = 0;
TMP_RH_ErrorCode error;
uint16_t buf[2];
if (error == SHT3XD_NO_ERROR)
error = read_TMP_RH(buf, 2);
if (error == SHT3XD_NO_ERROR) {
result.t = calculateTemperature(buf[0]);
result.rh = calculateHumidity(buf[1]);
}
result.error = error;
return result;
}
TMP_RH_ErrorCode AirGradient::read_TMP_RH(uint16_t* data, uint8_t numOfPair)//
{
uint8_t buf[2];
uint8_t checksum;
const uint8_t numOfBytes = numOfPair * 3;
Wire.requestFrom(_address, numOfBytes);
int counter = 0;
for (counter = 0; counter < numOfPair; counter++) {
Wire.readBytes(buf, (uint8_t)2);
checksum = Wire.read();
if (checkCrc(buf, checksum) != 0)
return SHT3XD_CRC_ERROR;
data[counter] = (buf[0] << 8) | buf[1];
}
return SHT3XD_NO_ERROR;
}
uint8_t AirGradient::checkCrc(uint8_t data[], uint8_t checksum)//
{
return calculateCrc(data) != checksum;
}
float AirGradient::calculateTemperature(uint16_t rawValue)//
{
return 175.0f * (float)rawValue / 65535.0f - 45.0f;
}
float AirGradient::calculateHumidity(uint16_t rawValue)//
{
return 100.0f * rawValue / 65535.0f;
}
uint8_t AirGradient::calculateCrc(uint8_t data[])
{
uint8_t bit;
uint8_t crc = 0xFF;
uint8_t dataCounter = 0;
for (; dataCounter < 2; dataCounter++)
{
crc ^= (data[dataCounter]);
for (bit = 8; bit > 0; --bit)
{
if (crc & 0x80)
crc = (crc << 1) ^ 0x131;
else
crc = (crc << 1);
}
}
return crc;
}
TMP_RH AirGradient::returnError(TMP_RH_ErrorCode error) {
TMP_RH result;
result.t = NULL;
result.rh = NULL;
result.error = error;
return result;
}
//END TMP_RH FUNCTIONS //
//START C02 FUNCTIONS //
void AirGradient::C02_Init(){
C02_Init(4,3);
}
void AirGradient::C02_Init(int rx_pin,int tx_pin){
C02_Init(rx_pin,tx_pin,9600);
}
void AirGradient::C02_Init(int rx_pin,int tx_pin,int baudRate){
if (_debugMsg) {
Serial.println("Initializing C02...");
}
_SoftSerial_C02 = new SoftwareSerial(rx_pin,tx_pin);
_SoftSerial_C02->begin(baudRate);
if(getC02() == -1){
if (_debugMsg) {
Serial.println("C02 Sensor Failed to Initialize ");
}
}
else{
Serial.println("C02 Successfully Initialized. Heating up for 10s");
delay(10000);
}
}
int AirGradient::getC02(int retryLimit) {
int ctr = 0;
int result_c02 = 0;
while(result_c02 == -1){
result_c02 = get_C02_values();
if(ctr == retryLimit){
return NULL;
}
ctr++;
}
return result_c02;
}
int AirGradient::get_C02_values(){
int retry = 0;
CO2_READ_RESULT result;
const byte C02Command[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
byte C02Response[] = {0,0,0,0,0,0,0};
while(!(_SoftSerial_C02->available())) {
retry++;
// keep sending request until we start to get a response
_SoftSerial_C02->write(C02Command, 7);
delay(50);
if (retry > 10) {
return -1;
}
}
int timeout = 0;
while (_SoftSerial_C02->available() < 7) {
timeout++;
if (timeout > 10) {
while(_SoftSerial_C02->available())
_SoftSerial_C02->read();
break;
}
delay(50);
}
for (int i=0; i < 7; i++) {
int byte = _SoftSerial_C02->read();
if (byte == -1) {
result.success = false;
return -1;
}
C02Response[i] = byte;
}
int valMultiplier = 1;
int high = C02Response[3];
int low = C02Response[4];
unsigned long val = high*256 + low;
return val;
}
//END C02 FUNCTIONS //
//START MHZ19 FUNCTIONS //
void AirGradient::MHZ19_Init(uint8_t type) {
MHZ19_Init(9,10,type);
}
void AirGradient::MHZ19_Init(int rx_pin,int tx_pin, uint8_t type) {
MHZ19_Init(rx_pin,tx_pin,9600,type);
}
void AirGradient::MHZ19_Init(int rx_pin,int tx_pin, int baudRate, uint8_t type) {
if (_debugMsg) {
Serial.println("Initializing MHZ19...");
}
_SoftSerial_MHZ19 = new SoftwareSerial(rx_pin,tx_pin);
_SoftSerial_MHZ19->begin(baudRate);
if(getC02() == -1){
if (_debugMsg) {
Serial.println("MHZ19 Sensor Failed to Initialize ");
}
}
else{
Serial.println("MHZ19 Successfully Initialized. Heating up for 10s");
delay(10000);
}
_type_MHZ19 = type;
PwmConfigured = false;
}
/**
* Enables or disables the debug mode (more logging).
*/
void AirGradient::setDebug_MHZ19(bool enable) {
debug_MHZ19 = enable;
if (debug_MHZ19) {
Serial.println(F("MHZ: debug mode ENABLED"));
} else {
Serial.println(F("MHZ: debug mode DISABLED"));
}
}
bool AirGradient::isPreHeating_MHZ19() {
if (_type_MHZ19 == MHZ14A) {
return millis() < (MHZ14A_PREHEATING_TIME);
} else if (_type_MHZ19 == MHZ19B) {
return millis() < (MHZ19B_PREHEATING_TIME);
} else {
Serial.println(F("MHZ::isPreheating_MHZ19() => UNKNOWN SENSOR"));
return false;
}//
}
bool AirGradient::isReady_MHZ19() {
if (isPreHeating_MHZ19()) return false;
if (_type_MHZ19 == MHZ14A)
return lastRequest < millis() - MHZ14A_RESPONSE_TIME;
else if (_type_MHZ19 == MHZ19B)
return lastRequest < millis() - MHZ19B_RESPONSE_TIME;
else {
Serial.print(F("MHZ::isReady_MHZ19() => UNKNOWN SENSOR \""));
Serial.print(_type_MHZ19);
Serial.println(F("\""));
return true;
}
}
int AirGradient::readMHZ19() {
int firstRead = readInternal_MHZ19();
int secondRead = readInternal_MHZ19();
if (abs(secondRead - firstRead) > 50) {
// we arrive here sometimes when the CO2 sensor is not connected
// could possibly also be fixed with a pull-up resistor on Rx but if we forget this then ...
Serial.println("MHZ::read() inconsistent values");
return -1;
}
Serial.println("MHZ::read(1) " + String(firstRead));
Serial.println("MHZ::read(2) " + String(secondRead));
// TODO: return average?
return secondRead;
}
int AirGradient::readInternal_MHZ19() {
if (!SerialConfigured) {
if (debug_MHZ19) Serial.println(F("-- serial is not configured"));
return STATUS_serial_MHZ19_NOT_CONFIGURED;
}
// if (!isReady_MHZ19()) return STATUS_NOT_READY;
if (debug_MHZ19) Serial.println(F("-- read CO2 uart ---"));
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
unsigned char response[9]; // for answer
if (debug_MHZ19) Serial.print(F(" >> Sending CO2 request"));
_SoftSerial_MHZ19->write(cmd, 9); // request PPM CO2
lastRequest = millis();
// clear the buffer
memset(response, 0, 9);
int waited = 0;
while (_SoftSerial_MHZ19->available() == 0) {
if (debug_MHZ19) Serial.print(".");
delay(100); // wait a short moment to avoid false reading
if (waited++ > 10) {
if (debug_MHZ19) Serial.println(F("No response after 10 seconds"));
_SoftSerial_MHZ19->flush();
return STATUS_NO_RESPONSE;
}
}
if (debug_MHZ19) Serial.println();
// The serial stream can get out of sync. The response starts with 0xff, try
// to resync.
// TODO: I think this might be wrong any only happens during initialization?
boolean skip = false;
while (_SoftSerial_MHZ19->available() > 0 && (unsigned char)_SoftSerial_MHZ19->peek() != 0xFF) {
if (!skip) {
Serial.print(F("MHZ: - skipping unexpected readings:"));
skip = true;
}
Serial.print(" ");
Serial.print(_SoftSerial_MHZ19->peek(), HEX);
_SoftSerial_MHZ19->read();
}
if (skip) Serial.println();
if (_SoftSerial_MHZ19->available() > 0) {
int count = _SoftSerial_MHZ19->readBytes(response, 9);
if (count < 9) {
_SoftSerial_MHZ19->flush();
return STATUS_INCOMPLETE;
}
} else {
_SoftSerial_MHZ19->flush();
return STATUS_INCOMPLETE;
}
if (debug_MHZ19) {
// print out the response in hexa
Serial.print(F(" << "));
for (int i = 0; i < 9; i++) {
Serial.print(response[i], HEX);
Serial.print(F(" "));
}
Serial.println(F(""));
}
// checksum
byte check = getCheckSum_MHZ19(response);
if (response[8] != check) {
Serial.println(F("MHZ: Checksum not OK!"));
Serial.print(F("MHZ: Received: "));
Serial.println(response[8], HEX);
Serial.print(F("MHZ: Should be: "));
Serial.println(check, HEX);
temperature_MHZ19 = STATUS_CHECKSUM_MISMATCH;
_SoftSerial_MHZ19->flush();
return STATUS_CHECKSUM_MISMATCH;
}
int ppm_uart = 256 * (unsigned int)response[2] + (unsigned int)response[3];
temperature_MHZ19 = response[4] - 44; // - 40;
byte status = response[5];
if (debug_MHZ19) {
Serial.print(F(" # PPM UART: "));
Serial.println(ppm_uart);
Serial.print(F(" # temperature_MHZ19? "));
Serial.println(temperature_MHZ19);
}
// Is always 0 for version 14a and 19b
// Version 19a?: status != 0x40
if (debug_MHZ19 && status != 0) {
Serial.print(F(" ! Status maybe not OK ! "));
Serial.println(status, HEX);
} else if (debug_MHZ19) {
Serial.print(F(" Status OK: "));
Serial.println(status, HEX);
}
_SoftSerial_MHZ19->flush();
return ppm_uart;
}
byte AirGradient::getCheckSum_MHZ19(byte* packet) {
if (!SerialConfigured) {
if (debug_MHZ19) Serial.println(F("-- serial is not configured"));
return STATUS_serial_MHZ19_NOT_CONFIGURED;
}
if (debug_MHZ19) Serial.println(F(" getCheckSum_MHZ19()"));
byte i;
unsigned char checksum = 0;
for (i = 1; i < 8; i++) {
checksum += packet[i];
}
checksum = 0xff - checksum;
checksum += 1;
return checksum;
}
//END MHZ19 FUNCTIONS //

300
AirGradient/AirGradient.h Normal file
View File

@ -0,0 +1,300 @@
/*
Test.h - Test library for Wiring - description
Copyright (c) 2006 John Doe. All right reserved.
*/
// ensure this library description is only included once
#ifndef AirGradient_h
#define AirGradient_h
#include <SoftwareSerial.h>
#include <Print.h>
#include "Stream.h"
//MHZ19 CONSTANTS START
// types of sensors.
extern const int MHZ14A;
extern const int MHZ19B;
// status codes
extern const int STATUS_NO_RESPONSE;
extern const int STATUS_CHECKSUM_MISMATCH;
extern const int STATUS_INCOMPLETE;
extern const int STATUS_NOT_READY;
//MHZ19 CONSTANTS END
//ENUMS AND STRUCT FOR TMP_RH START
typedef enum {
SHT3XD_CMD_READ_SERIAL_NUMBER = 0x3780,
SHT3XD_CMD_READ_STATUS = 0xF32D,
SHT3XD_CMD_CLEAR_STATUS = 0x3041,
SHT3XD_CMD_HEATER_ENABLE = 0x306D,
SHT3XD_CMD_HEATER_DISABLE = 0x3066,
SHT3XD_CMD_SOFT_RESET = 0x30A2,
SHT3XD_CMD_CLOCK_STRETCH_H = 0x2C06,
SHT3XD_CMD_CLOCK_STRETCH_M = 0x2C0D,
SHT3XD_CMD_CLOCK_STRETCH_L = 0x2C10,
SHT3XD_CMD_POLLING_H = 0x2400,
SHT3XD_CMD_POLLING_M = 0x240B,
SHT3XD_CMD_POLLING_L = 0x2416,
SHT3XD_CMD_ART = 0x2B32,
SHT3XD_CMD_PERIODIC_HALF_H = 0x2032,
SHT3XD_CMD_PERIODIC_HALF_M = 0x2024,
SHT3XD_CMD_PERIODIC_HALF_L = 0x202F,
SHT3XD_CMD_PERIODIC_1_H = 0x2130,
SHT3XD_CMD_PERIODIC_1_M = 0x2126,
SHT3XD_CMD_PERIODIC_1_L = 0x212D,
SHT3XD_CMD_PERIODIC_2_H = 0x2236,
SHT3XD_CMD_PERIODIC_2_M = 0x2220,
SHT3XD_CMD_PERIODIC_2_L = 0x222B,
SHT3XD_CMD_PERIODIC_4_H = 0x2334,
SHT3XD_CMD_PERIODIC_4_M = 0x2322,
SHT3XD_CMD_PERIODIC_4_L = 0x2329,
SHT3XD_CMD_PERIODIC_10_H = 0x2737,
SHT3XD_CMD_PERIODIC_10_M = 0x2721,
SHT3XD_CMD_PERIODIC_10_L = 0x272A,
SHT3XD_CMD_FETCH_DATA = 0xE000,
SHT3XD_CMD_STOP_PERIODIC = 0x3093,
SHT3XD_CMD_READ_ALR_LIMIT_LS = 0xE102,
SHT3XD_CMD_READ_ALR_LIMIT_LC = 0xE109,
SHT3XD_CMD_READ_ALR_LIMIT_HS = 0xE11F,
SHT3XD_CMD_READ_ALR_LIMIT_HC = 0xE114,
SHT3XD_CMD_WRITE_ALR_LIMIT_HS = 0x611D,
SHT3XD_CMD_WRITE_ALR_LIMIT_HC = 0x6116,
SHT3XD_CMD_WRITE_ALR_LIMIT_LC = 0x610B,
SHT3XD_CMD_WRITE_ALR_LIMIT_LS = 0x6100,
SHT3XD_CMD_NO_SLEEP = 0x303E,
} TMP_RH_Commands;
typedef enum {
SHT3XD_REPEATABILITY_HIGH,
SHT3XD_REPEATABILITY_MEDIUM,
SHT3XD_REPEATABILITY_LOW,
} TMP_RH_Repeatability;
typedef enum {
SHT3XD_MODE_CLOCK_STRETCH,
SHT3XD_MODE_POLLING,
} TMP_RH_Mode;
typedef enum {
SHT3XD_FREQUENCY_HZ5,
SHT3XD_FREQUENCY_1HZ,
SHT3XD_FREQUENCY_2HZ,
SHT3XD_FREQUENCY_4HZ,
SHT3XD_FREQUENCY_10HZ
} TMP_RH_Frequency;
typedef enum {
SHT3XD_NO_ERROR = 0,
SHT3XD_CRC_ERROR = -101,
SHT3XD_TIMEOUT_ERROR = -102,
SHT3XD_PARAM_WRONG_MODE = -501,
SHT3XD_PARAM_WRONG_REPEATABILITY = -502,
SHT3XD_PARAM_WRONG_FREQUENCY = -503,
SHT3XD_PARAM_WRONG_ALERT = -504,
// Wire I2C translated error codes
SHT3XD_WIRE_I2C_DATA_TOO_LOG = -10,
SHT3XD_WIRE_I2C_RECEIVED_NACK_ON_ADDRESS = -20,
SHT3XD_WIRE_I2C_RECEIVED_NACK_ON_DATA = -30,
SHT3XD_WIRE_I2C_UNKNOW_ERROR = -40
} TMP_RH_ErrorCode;
typedef union {
uint16_t rawData;
struct {
uint8_t WriteDataChecksumStatus : 1;
uint8_t CommandStatus : 1;
uint8_t Reserved0 : 2;
uint8_t SystemResetDetected : 1;
uint8_t Reserved1 : 5;
uint8_t T_TrackingAlert : 1;
uint8_t RH_TrackingAlert : 1;
uint8_t Reserved2 : 1;
uint8_t HeaterStatus : 1;
uint8_t Reserved3 : 1;
uint8_t AlertPending : 1;
};
} TMP_RH_RegisterStatus;
struct TMP_RH {
float t;
int rh;
TMP_RH_ErrorCode error;
};
// ENUMS AND STRUCTS FOR TMP_RH END
//ENUMS STRUCTS FOR C02 START
struct CO2_READ_RESULT {
int co2 = -1;
bool success = false;
};
//ENUMS STRUCTS FOR C02 END
// library interface description
class AirGradient
{
// user-accessible "public" interface
public:
AirGradient(bool displayMsg=false,int baudRate=9600);
//void begin(int baudRate=9600);
static void setOutput(Print& debugOut, bool verbose = true);
void beginC02(void);
void beginC02(int,int);
void PMS_Init(void);
void PMS_Init(int,int);
void PMS_Init(int,int,int);
bool _debugMsg;
//PMS VARIABLES PUBLIC_START
static const uint16_t SINGLE_RESPONSE_TIME = 1000;
static const uint16_t TOTAL_RESPONSE_TIME = 1000 * 10;
static const uint16_t STEADY_RESPONSE_TIME = 1000 * 30;
static const uint16_t BAUD_RATE = 9600;
struct DATA {
// Standard Particles, CF=1
uint16_t PM_SP_UG_1_0;
uint16_t PM_SP_UG_2_5;
uint16_t PM_SP_UG_10_0;
// Atmospheric environment
uint16_t PM_AE_UG_1_0;
uint16_t PM_AE_UG_2_5;
uint16_t PM_AE_UG_10_0;
};
void PMS(Stream&);
void sleep();
void wakeUp();
void activeMode();
void passiveMode();
void requestRead();
bool read_PMS(DATA& data);
bool readUntil(DATA& data, uint16_t timeout = SINGLE_RESPONSE_TIME);
int getPM2();
//PMS VARIABLES PUBLIC_END
//TMP_RH VARIABLES PUBLIC START
void ClosedCube_TMP_RH();
uint32_t getTemp();
int getRhum();
TMP_RH_ErrorCode TMP_RH_Init(uint8_t address);
TMP_RH_ErrorCode clearAll();
TMP_RH_ErrorCode softReset();
TMP_RH_ErrorCode reset(); // same as softReset
uint32_t readSerialNumber();
uint32_t testTMP_RH();
TMP_RH_ErrorCode periodicStart(TMP_RH_Repeatability repeatability, TMP_RH_Frequency frequency);
TMP_RH periodicFetchData();
TMP_RH_ErrorCode periodicStop();
//TMP_RH VARIABLES PUBLIC END
//C02 VARIABLES PUBLIC START
void C02_Init();
void C02_Init(int,int);
void C02_Init(int,int,int);
int getC02(int retryLimit = 5);
int get_C02_values();
SoftwareSerial *_SoftSerial_C02;
//CO2 VARIABLES PUBLIC END
//MHZ19 VARIABLES PUBLIC START
void MHZ19_Init(uint8_t);
void MHZ19_Init(int,int,uint8_t);
void MHZ19_Init(int,int,int,uint8_t);
void setDebug_MHZ19(bool enable);
bool isPreHeating_MHZ19();
bool isReady_MHZ19();
int readMHZ19();
//MHZ19 VARIABLES PUBLIC END
// library-accessible "private" interface
private:
int value;
//PMS VARIABLES PRIVATE START
enum STATUS { STATUS_WAITING, STATUS_OK };
enum MODE { MODE_ACTIVE, MODE_PASSIVE };
uint8_t _payload[12];
Stream* _stream;
DATA* _data;
STATUS _PMSstatus;
MODE _mode = MODE_ACTIVE;
uint8_t _index = 0;
uint16_t _frameLen;
uint16_t _checksum;
uint16_t _calculatedChecksum;
SoftwareSerial *_SoftSerial_PMS;
void loop();
//PMS VARIABLES PRIVATE END
//TMP_RH VARIABLES PRIVATE START
uint8_t _address;
TMP_RH_RegisterStatus _status;
TMP_RH_ErrorCode writeCommand(TMP_RH_Commands command);
TMP_RH_ErrorCode writeAlertData(TMP_RH_Commands command, float temperature, float humidity);
uint8_t checkCrc(uint8_t data[], uint8_t checksum);
uint8_t calculateCrc(uint8_t data[]);
float calculateHumidity(uint16_t rawValue);
float calculateTemperature(uint16_t rawValue);
TMP_RH readTemperatureAndHumidity();
TMP_RH_ErrorCode read_TMP_RH(uint16_t* data, uint8_t numOfPair);
TMP_RH returnError(TMP_RH_ErrorCode command);
//TMP_RH VARIABLES PRIVATE END
//MHZ19 VARABLES PUBLIC START
int readInternal_MHZ19();
uint8_t _type_MHZ19, temperature_MHZ19;
boolean debug_MHZ19 = false;
Stream * _serial_MHZ19;
SoftwareSerial *_SoftSerial_MHZ19;
byte getCheckSum_MHZ19(byte *packet);
//MHZ19 VARABLES PUBLIC END
};
#endif

21
AirGradient/LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 AirGradient
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

8
AirGradient/README.md Normal file
View File

@ -0,0 +1,8 @@
AirGradient Arduino Library for ESP8266 (Wemos D1 MINI)
=====================================================================================================
Build your own low cost air quality sensor with optional display measuring PM2.5, CO2, Temperature and Humidity.
This library makes it easy to read the sensor data from the Plantower PMS5003 PM2.5 sensor, the Senseair S8 and the SHT30/31 Temperature and Humidity sensor. Visit our blog for detailed build instructions and PCB layout.
https://www.airgradient.com/blog/

BIN
AirGradient/examples/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
#include <AirGradient.h>
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
ag.PMS_Init();
ag.C02_Init();
ag.TMP_RH_Init(0x44); //check for SHT sensor with address 0x44
}
void loop(){
int PM2 = ag.getPM2();
Serial.print("PM2: ");
Serial.println(PM2);
int CO2 = ag.getC02();
Serial.print("C02: ");
Serial.println(CO2);
TMP_RH result = ag.periodicFetchData();
Serial.print("Humidity: ");
Serial.print(result.rh);
Serial.print(" Temperature: ");
Serial.println(result.t);
delay(5000);
}

View File

@ -0,0 +1,41 @@
#include <AirGradient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
AirGradient ag = AirGradient();
#define OLED_RESET 0
Adafruit_SSD1306 display(OLED_RESET);
void setup(){
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
ag.PMS_Init();
ag.C02_Init();
ag.TMP_RH_Init(0x44); //check for SHT sensor with address 0x44
showTextRectangle("Init", String(ESP.getChipId(),HEX),"AirGradient");
delay(2000);
}
void loop(){
int PM2 = ag.getPM2();
int CO2 = ag.getC02();
TMP_RH result = ag.periodicFetchData();
showTextRectangle(String(result.t)+"c "+String(result.rh)+"%", "PM2: "+ String(PM2), "CO2: "+String(CO2)+"");
delay(5000);
}
// DISPLAY
void showTextRectangle(String ln1, String ln2, String ln3) {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(32,8);
display.println(ln1);
display.setTextSize(1);
display.setCursor(32,16);
display.println(ln2);
display.setTextSize(1);
display.setCursor(32,24);
display.println(ln3);
display.display();
}

View File

@ -0,0 +1,77 @@
#include <AirGradient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
AirGradient ag = AirGradient();
#define OLED_RESET 0
Adafruit_SSD1306 display(OLED_RESET);
String APIROOT = "http://hw.airgradient.com/";
void setup(){
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
ag.PMS_Init();
ag.C02_Init();
ag.TMP_RH_Init(0x44); //check for SHT sensor with address 0x44
showTextRectangle("Init", String(ESP.getChipId(),HEX),"AirGradient");
connectToWifi();
delay(2000);
}
void loop(){
int PM2 = ag.getPM2();
int CO2 = ag.getC02();
TMP_RH result = ag.periodicFetchData();
showTextRectangle(String(result.t)+"c "+String(result.rh)+"%", "PM2: "+ String(PM2), "CO2: "+String(CO2)+"");
// send payload
String payload = "{\"pm02\":" + String(PM2) + ",\"wifi\":" + String(WiFi.RSSI()) + ",\"rco2\":" + String(CO2) + ",\"atmp\":" + String(result.t) + ",\"rhum\":" + String(result.rh) + "}";
Serial.println(payload);
String POSTURL = APIROOT + "sensors/airgradient:" + String(ESP.getChipId(),HEX) + "/measures";
Serial.println(POSTURL);
HTTPClient http;
http.begin(POSTURL);
http.addHeader("content-type", "application/json");
int httpCode = http.POST(payload);
String response = http.getString();
Serial.println(httpCode);
Serial.println(response);
http.end();
delay(15000);
}
// DISPLAY
void showTextRectangle(String ln1, String ln2, String ln3) {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(32,8);
display.println(ln1);
display.setTextSize(1);
display.setCursor(32,16);
display.println(ln2);
display.setTextSize(1);
display.setCursor(32,24);
display.println(ln3);
display.display();
}
// Wifi Manager
void connectToWifi(){
WiFiManager wifiManager;
//chWiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AIRGRADIENT-"+String(ESP.getChipId(),HEX);
wifiManager.setTimeout(120);
if(!wifiManager.autoConnect((const char*)HOTSPOT.c_str())) {
//Serial.println("failed to connect and hit timeout");
delay(3000);
ESP.restart();
delay(5000);
}
}

View File

@ -0,0 +1,16 @@
#include <AirGradient.h>
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
ag.C02_Init();
}
void loop(){
int CO2 = ag.getC02();
Serial.print("C02: ");
Serial.println(CO2);
delay(5000);
}

View File

@ -0,0 +1,16 @@
#include <AirGradient.h>
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
ag.MHZ19_Init(MHZ19B);
}
void loop(){
int MHZ19_C02 = ag.readMHZ19();
Serial.print("C02: ");
Serial.println(MHZ19_C02);
delay(5000);
}

View File

@ -0,0 +1,16 @@
#include <AirGradient.h>
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
ag.PMS_Init();
}
void loop(){
int PM2 = ag.getPM2();
Serial.print("PM2: ");
Serial.println(PM2);
delay(5000);
}

View File

@ -0,0 +1,16 @@
#include <AirGradient.h>
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
ag.TMP_RH_Init(0x44); //check for SHT sensor with address 0x44
}
void loop(){
TMP_RH result = ag.periodicFetchData();
Serial.print("Humidity: ");
Serial.print(result.rh);
Serial.print(" Temperature: ");
Serial.println(result.t);
delay(5000);
}

65
AirGradient/keywords.txt Normal file
View File

@ -0,0 +1,65 @@
#######################################
# Syntax Coloring Map For AirGradient
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
AirGradient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
setOutput KEYWORD2
beginC02 KEYWORD2
PMS_Init KEYWORD2
PMS KEYWORD2
sleep KEYWORD2
wakeUp KEYWORD2
activeMode KEYWORD2
passiveMode KEYWORD2
requestRead KEYWORD2
read_PMS KEYWORD2
readUntil KEYWORD2
getPM2 KEYWORD2
ClosedCube_TMP_RH KEYWORD2
getTemp KEYWORD2
getRhum KEYWORD2
TMP_RH_Init KEYWORD2
clearAll KEYWORD2
softReset KEYWORD2
reset KEYWORD2
readSerialNumber KEYWORD2
testTMP_RH KEYWORD2
periodicStart KEYWORD2
periodicFetchData KEYWORD2
periodicStop KEYWORD2
C02_Init KEYWORD2
getC02 KEYWORD2
get_C02_values KEYWORD2
MHZ19_Init KEYWORD2
setDebug_MHZ19 KEYWORD2
isPreHeating_MHZ19 KEYWORD2
readMHZ19 KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################
MHZ14A LITERAL1
MHZ19B LITERAL1

View File

@ -0,0 +1,9 @@
name=AirGradient Air Quality Sensor
version=1.0.0
author=AirGradient <support@airgradient.com>
maintainer=AirGradient <support@airgradient.com>
sentence=ESP8266 library for an air quality sensor featuring PM2.5, CO2, Temperature and Humidity with OLED display.
paragraph=The library is very robust and works with the Plantower PMS5003 particle sensor, the Senseair S8 CO2 sensor and the SHT30/31 sensor for humidity and temperature. You can also connect an OLED display or send the air quality data to the AirGradient platform or any other backend.
category=Sensors
url=https://github.com/airgradienthq/arduino
architectures=*

39
AirGradient/readme.txt Normal file
View File

@ -0,0 +1,39 @@
This is an example C++ library for Arduino 0004+, based on one created by
Nicholas Zambetti for Wiring 0006+
Installation
--------------------------------------------------------------------------------
To install this library, just place this entire folder as a subfolder in your
Arduino/lib/targets/libraries folder.
When installed, this library should look like:
Arduino/lib/targets/libraries/Test (this library's folder)
Arduino/lib/targets/libraries/Test/Test.cpp (the library implementation file)
Arduino/lib/targets/libraries/Test/Test.h (the library description file)
Arduino/lib/targets/libraries/Test/keywords.txt (the syntax coloring file)
Arduino/lib/targets/libraries/Test/examples (the examples in the "open" menu)
Arduino/lib/targets/libraries/Test/readme.txt (this file)
Building
--------------------------------------------------------------------------------
After this library is installed, you just have to start the Arduino application.
You may see a few warning messages as it's built.
To use this library in a sketch, go to the Sketch | Import Library menu and
select Test. This will add a corresponding line to the top of your sketch:
#include <Test.h>
To stop using this library, delete that line from your sketch.
Geeky information:
After a successful build of this library, a new file named "Test.o" will appear
in "Arduino/lib/targets/libraries/Test". This file is the built/compiled library
code.
If you choose to modify the code for this library (i.e. "Test.cpp" or "Test.h"),
then you must first 'unbuild' this library by deleting the "Test.o" file. The
new "Test.o" with your code will appear after the next press of "verify"