Skip to content

Commit

Permalink
sensor: add support for SmartMeasure SM300D2-VO2 air quality multi-se…
Browse files Browse the repository at this point in the history
…nsor (#2447)

* Support for SmartMeasure SM300D2-VO2 air quality multi-sensor

* Fix temperature sign, using hexDecode

* Update README.md
  • Loading branch information
xoseperez authored May 12, 2021
1 parent 4612948 commit bb7ffe9
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 1 deletion.
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
253 changes: 253 additions & 0 deletions code/espurna/sensors/SM300D2Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// -----------------------------------------------------------------------------
// 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 hex[(sizeof(_buffer)*2)+1] = {0};
if (hexEncode(_buffer, sizeof(_buffer), hex, sizeof(hex))) {
DEBUG_MSG("[SENSOR] SM300D2: %s\n", hex);
}
#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] & 0x7F) + (float) _buffer[13] / 10.0;
if ((_buffer[12] & 0x80) == 0x80) {
_temperature = -_temperature;
}

// 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

0 comments on commit bb7ffe9

Please sign in to comment.