Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for SmartMeasure SM300D2-VO2 air quality multi-sensor #2447

Merged
merged 3 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smar
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.

[![version](https://img.shields.io/badge/version-1.15.0--dev-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
[![branch](https://img.shields.io/badge/branch-sm300d2-orange.svg)](https://github.com/xoseperez/espurna/tree/sm300d2/)
mcspr marked this conversation as resolved.
Show resolved Hide resolved
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
[![ci build](https://github.com/xoseperez/espurna/workflows/ESPurna%20build/badge.svg?branch=dev)](https://github.com/xoseperez/espurna/actions)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
Expand Down
3 changes: 3 additions & 0 deletions code/espurna/board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ const char* getEspurnaSensors() {
#if SI7021_SUPPORT
"SI7021 "
#endif
#if SM300D2_SUPPORT
"SM300D2 "
#endif
#if SONAR_SUPPORT
"SONAR "
#endif
Expand Down
1 change: 1 addition & 0 deletions code/espurna/config/arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
//#define SENSEAIR_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1
//#define SM300D2_SUPPORT 1
//#define SONAR_SUPPORT 1
//#define T6613_SUPPORT 1
//#define THERMOSTAT_SUPPORT 1
Expand Down
18 changes: 18 additions & 0 deletions code/espurna/config/sensors.h
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,23 @@
#define SI7021_ADDRESS 0x00 // 0x00 means auto
#endif

//------------------------------------------------------------------------------
// SM300D2 sensor
// Enable support by passing SM300D2_SUPPORT=1 build flag
//------------------------------------------------------------------------------

#ifndef SM300D2_SUPPORT
#define SM300D2_SUPPORT 0
#endif

#ifndef SM300D2_RX_PIN
#define SM300D2_RX_PIN 13
#endif

#ifndef SM300D2_BAUDRATE
#define SM300D2_BAUDRATE 9600
#endif

//------------------------------------------------------------------------------
// HDC1080 / 831R temperature & humidity sensor
// Enable support by passing HDC1080_SUPPORT=1 build flag
Expand Down Expand Up @@ -1397,6 +1414,7 @@
SHT3X_I2C_SUPPORT || \
SI1145_SUPPORT || \
SI7021_SUPPORT || \
SM300D2_SUPPORT || \
SONAR_SUPPORT || \
T6613_SUPPORT || \
THERMOSTAT_SUPPORT || \
Expand Down
5 changes: 4 additions & 1 deletion code/espurna/config/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@
#define SENSOR_HDC1080_ID 40
#define SENSOR_PZEM004TV30_ID 41
#define SENSOR_BME680_ID 42
#define SENSOR_SM300D2_ID 43

//--------------------------------------------------------------------------------
// Magnitudes
Expand Down Expand Up @@ -369,8 +370,10 @@
#define MAGNITUDE_IAQ_ACCURACY 34
#define MAGNITUDE_IAQ_STATIC 35
#define MAGNITUDE_VOC 36
#define MAGNITUDE_TVOC 37
#define MAGNITUDE_CH2O 38

#define MAGNITUDE_MAX 38
#define MAGNITUDE_MAX 39

#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
Expand Down
26 changes: 26 additions & 0 deletions code/espurna/sensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "sensors/SI7021Sensor.h"
#endif

#if SM300D2_SUPPORT
#include "sensors/SM300D2Sensor.h"
#endif

#if SONAR_SUPPORT
#include "sensors/SonarSensor.h"
#endif
Expand Down Expand Up @@ -753,6 +757,12 @@ String magnitudeTopic(unsigned char type) {
case MAGNITUDE_FREQUENCY:
result = F("frequency");
break;
case MAGNITUDE_TVOC:
result = F("tvoc");
break;
case MAGNITUDE_CH2O:
result = F("ch2o");
break;
case MAGNITUDE_NONE:
default:
result = F("unknown");
Expand Down Expand Up @@ -1023,6 +1033,8 @@ const char * const _magnitudeSettingsPrefix(unsigned char type) {
case MAGNITUDE_RESISTANCE: return "res";
case MAGNITUDE_PH: return "ph";
case MAGNITUDE_FREQUENCY: return "freq";
case MAGNITUDE_TVOC: return "tvoc";
case MAGNITUDE_CH2O: return "ch2o";
default: return nullptr;
}
}
Expand Down Expand Up @@ -1289,6 +1301,12 @@ String magnitudeName(unsigned char type) {
case MAGNITUDE_FREQUENCY:
result = F("Frequency");
break;
case MAGNITUDE_TVOC:
result = F("TVOC");
break;
case MAGNITUDE_CH2O:
result = F("CH2O");
break;
case MAGNITUDE_NONE:
default:
break;
Expand Down Expand Up @@ -2120,6 +2138,14 @@ void _sensorLoad() {
}
#endif

#if SM300D2_SUPPORT
{
SM300D2Sensor * sensor = new SM300D2Sensor();
sensor->setRX(SM300D2_RX_PIN);
_sensors.push_back(sensor);
}
#endif

#if T6613_SUPPORT
{
T6613Sensor * sensor = new T6613Sensor();
Expand Down
3 changes: 3 additions & 0 deletions code/espurna/sensors/BaseSensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ class BaseSensor {
return sensor::Unit::KilowattHour;
case MAGNITUDE_PM1dot0:
case MAGNITUDE_PM2dot5:
case MAGNITUDE_PM10:
case MAGNITUDE_TVOC:
case MAGNITUDE_CH2O:
return sensor::Unit::MicrogrammPerCubicMeter;
case MAGNITUDE_CO:
case MAGNITUDE_CO2:
Expand Down
249 changes: 249 additions & 0 deletions code/espurna/sensors/SM300D2Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// -----------------------------------------------------------------------------
// SmartMeasure SM300D2-VO2
// https://es.aliexpress.com/item/32984571140.html
// Uses SoftwareSerial library
// Copyright (C) 2021 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && SM300D2_SUPPORT

#pragma once

#include <SoftwareSerial.h>

#include "BaseSensor.h"

class SM300D2Sensor : public BaseSensor {

public:

// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------

SM300D2Sensor() {
_count = 7;
_sensor_id = SENSOR_SM300D2_ID;
}

~SM300D2Sensor() {
if (_serial) delete _serial;
}

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

void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}

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

unsigned char getRX() {
return _pin_rx;
}

// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------

// Initialization method, must be idempotent
void begin() {

if (!_dirty) return;

if (_serial) delete _serial;

if (3 == _pin_rx) {
Serial.begin(SM300D2_BAUDRATE);
} else if (13 == _pin_rx) {
Serial.begin(SM300D2_BAUDRATE);
Serial.flush();
Serial.swap();
} else {
_serial = new SoftwareSerial(_pin_rx, -1, false);
_serial->enableIntTx(false);
_serial->begin(SM300D2_BAUDRATE);
}

_ready = true;
_dirty = false;

}

// Descriptive name of the sensor
String description() {
char buffer[28];
if (_serial_is_hardware()) {
snprintf(buffer, sizeof(buffer), "SM300D2 @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "SM300D2 @ SwSerial(%u,NULL)", _pin_rx);
}
return String(buffer);
}

// Descriptive name of the slot # index
String description(unsigned char index) {
return description();
};

// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u", _pin_rx);
return String(buffer);
}

// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CO2;
if (index == 1) return MAGNITUDE_CH2O;
if (index == 2) return MAGNITUDE_TVOC;
if (index == 3) return MAGNITUDE_PM2dot5;
if (index == 4) return MAGNITUDE_PM10;
if (index == 5) return MAGNITUDE_TEMPERATURE;
if (index == 6) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}

// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _co2;
if (index == 1) return _ch2o;
if (index == 2) return _tvoc;
if (index == 3) return _pm25;
if (index == 4) return _pm100;
if (index == 5) return _temperature;
if (index == 6) return _humidity;
return 0;
}

// Process sensor UART
void tick() {
_read();
}

protected:

// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------

bool _serial_is_hardware() {
return (3 == _pin_rx) || (13 == _pin_rx);
}

bool _serial_available() {
if (_serial_is_hardware()) {
return Serial.available();
} else {
return _serial->available();
}
}

void _serial_flush() {
if (_serial_is_hardware()) {
return Serial.flush();
} else {
return _serial->flush();
}
}

uint8_t _serial_read() {
if (_serial_is_hardware()) {
return Serial.read();
} else {
return _serial->read();
}
}

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

void _parse() {

#if SENSOR_DEBUG
char tmp[50];
for (unsigned char i=0; i<17; i++) sprintf(&tmp[2*i], "%02X", _buffer[i]);
mcspr marked this conversation as resolved.
Show resolved Hide resolved
DEBUG_MSG("[SENSOR] SM300D2: %s\n", tmp);
#endif

// check second header byte
if (_buffer[1] != 0x02) {
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] SM300D2: Wrong header\n");
#endif
return;
}

// check crc
uint8_t crc = 0;
for (unsigned char i=0; i<16; i++) crc += _buffer[i];
if (crc != _buffer[16]) {
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] SM300D2: Wrong CRC\n");
#endif
return;
}

// CO2
_co2 = 256 * _buffer[2] + _buffer[3];

// CH2O
_ch2o = 256 * _buffer[4] + _buffer[5];

// TVOC
_tvoc = 256 * _buffer[6] + _buffer[7];

// PM 2.5
_pm25 = 256 * _buffer[8] + _buffer[9];

// PM 10
_pm100 = 256 * _buffer[10] + _buffer[11];

// Temperature
_temperature = _buffer[12] + (float) _buffer[13] / 10.0;
mcspr marked this conversation as resolved.
Show resolved Hide resolved

// Humidity
_humidity = _buffer[14] + (float) _buffer[15] / 10.0;

}

void _read() {

while(_serial_available()) {

unsigned char ch = _serial_read();
if ((_position > 0) || (ch == 0x3C)) {
_buffer[_position] = ch;
_position++;
if (_position == 17) {
_position = 0;
_parse();
}
}
yield();

}

}

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

unsigned char _buffer[17] = {0};
unsigned char _position = 0;

double _co2 = 0;
double _ch2o = 0;
double _tvoc = 0;
double _pm25 = 0;
double _pm100 = 0;
double _temperature = 0;
double _humidity = 0;

unsigned int _pin_rx;
SoftwareSerial * _serial = NULL;

};

#endif // SENSOR_SUPPORT && SM300D2_SUPPORT
Loading