From 17f53c083bc912df40113ca1a9481e0daba0cb8a Mon Sep 17 00:00:00 2001 From: user2684 <you@example.com> Date: Tue, 4 Apr 2017 18:39:03 +0200 Subject: [PATCH] Add support for MQ gas sensor #36 --- NodeManager.cpp | 225 +++++++++++++++++++++++++++++++++++++++++++++--- NodeManager.h | 76 +++++++++++++++- NodeManager.ino | 2 +- README.md | 33 ++++++- config.h | 8 +- 5 files changed, 323 insertions(+), 21 deletions(-) diff --git a/NodeManager.cpp b/NodeManager.cpp index 4d218df..32a1225 100644 --- a/NodeManager.cpp +++ b/NodeManager.cpp @@ -154,6 +154,12 @@ void Sensor::before() { onBefore(); } +// call the sensor-specific implementation of setup +void Sensor::setup() { + if (_pin == -1) return; + onSetup(); +} + // call the sensor-specific implementation of loop void Sensor::loop(const MyMessage & message) { if (_pin == -1) return; @@ -278,12 +284,16 @@ void SensorAnalogInput::setRangeMax(int value) { _range_max = value; } -// what do to during setup +// what do to during before void SensorAnalogInput::onBefore() { // prepare the pin for input pinMode(_pin, INPUT); } +// what do to during setup +void SensorAnalogInput::onSetup() { +} + // what do to during loop void SensorAnalogInput::onLoop() { // read the input @@ -375,12 +385,16 @@ void SensorThermistor::setOffset(float value) { _offset = value; } -// what do to during setup +// what do to during before void SensorThermistor::onBefore() { // set the pin as input pinMode(_pin, INPUT); } +// what do to during setup +void SensorThermistor::onSetup() { +} + // what do to during loop void SensorThermistor::onLoop() { // read the voltage across the thermistor @@ -423,12 +437,16 @@ void SensorThermistor::onReceive(const MyMessage & message) { SensorDigitalInput::SensorDigitalInput(int child_id, int pin): Sensor(child_id, pin) { } -// what do to during setup +// what do to during before void SensorDigitalInput::onBefore() { // set the pin for input pinMode(_pin, INPUT); } +// what do to during setup +void SensorDigitalInput::onSetup() { +} + // what do to during loop void SensorDigitalInput::onLoop() { // read the value @@ -459,6 +477,7 @@ void SensorDigitalInput::onReceive(const MyMessage & message) { SensorDigitalOutput::SensorDigitalOutput(int child_id, int pin): Sensor(child_id, pin) { } +// what do to during before void SensorDigitalOutput::onBefore() { // set the pin as output and initialize it accordingly pinMode(_pin, OUTPUT); @@ -467,6 +486,10 @@ void SensorDigitalOutput::onBefore() { _value_int = _initial_value; } +// what do to during setup +void SensorDigitalOutput::onSetup() { +} + // setter/getter void SensorDigitalOutput::setInitialValue(int value) { _initial_value = value; @@ -560,12 +583,16 @@ SensorDHT::SensorDHT(int child_id, int pin, DHT* dht, int sensor_type, int dht_t } } -// what do to during setup +// what do to during before void SensorDHT::onBefore() { // initialize the dht library _dht->begin(); } +// what do to during setup +void SensorDHT::onSetup() { +} + // what do to during loop void SensorDHT::onLoop() { // temperature sensor @@ -627,12 +654,16 @@ SensorSHT21::SensorSHT21(int child_id, int sensor_type): Sensor(child_id,A2) { } } -// what do to during setup +// what do to during before void SensorSHT21::onBefore() { // initialize the library Wire.begin(); } +// what do to during setup +void SensorSHT21::onSetup() { +} + // what do to during loop void SensorSHT21::onLoop() { // temperature sensor @@ -708,13 +739,17 @@ int SensorSwitch::getInitial() { return _initial; } -// what do to during setup +// what do to during before void SensorSwitch::onBefore() { // initialize the value if (_mode == RISING) _value_int = LOW; else if (_mode == FALLING) _value_int = HIGH; } +// what do to during setup +void SensorSwitch::onSetup() { +} + // what do to during loop void SensorSwitch::onLoop() { // wait to ensure the the input is not floating @@ -775,10 +810,14 @@ SensorDs18b20::SensorDs18b20(int child_id, int pin, DallasTemperature* sensors, _sensors = sensors; } -// what do to during setup +// what do to during before void SensorDs18b20::onBefore() { } +// what do to during setup +void SensorDs18b20::onSetup() { +} + // what do to during loop void SensorDs18b20::onLoop() { // request the temperature @@ -814,11 +853,15 @@ SensorBH1750::SensorBH1750(int child_id): Sensor(child_id,A4) { _lightSensor = new BH1750(); } -// what do to during setup +// what do to during before void SensorBH1750::onBefore() { _lightSensor->begin(); } +// what do to during setup +void SensorBH1750::onSetup() { +} + // what do to during loop void SensorBH1750::onLoop() { // request the light level @@ -852,12 +895,16 @@ SensorMLX90614::SensorMLX90614(int child_id, Adafruit_MLX90614* mlx, int sensor_ setValueType(TYPE_FLOAT); } -// what do to during setup +// what do to during before void SensorMLX90614::onBefore() { // initialize the library _mlx->begin(); } +// what do to during setup +void SensorMLX90614::onSetup() { +} + // what do to during loop void SensorMLX90614::onLoop() { float temperature = _sensor_type == 0 ? _mlx->readAmbientTempC() : _mlx->readObjectTempC(); @@ -907,9 +954,12 @@ SensorBME280::SensorBME280(int child_id, Adafruit_BME280* bme, int sensor_type): } } -// what do to during setup +// what do to during before void SensorBME280::onBefore() { - // initialize the library +} + +// what do to during setup +void SensorBME280::onSetup() { } // what do to during loop @@ -965,6 +1015,152 @@ void SensorBME280::onReceive(const MyMessage & message) { } #endif +/* + * SensorMQ + */ +SensorMQ::SensorMQ(int child_id, int pin): Sensor(child_id,pin) { + setPresentation(S_AIR_QUALITY); + setType(V_LEVEL); +} + +//setter/getter +void SensorMQ::setRlValue(float value) { + _rl_value = value; +} +void SensorMQ::setRoValue(float value) { + _ro = value; +} +void SensorMQ::setCleanAirFactor(float value) { + _ro_clean_air_factor = value; +} +void SensorMQ::setCalibrationSampleTimes(int value) { + _calibration_sample_times = value; +} +void SensorMQ::setCalibrationSampleInterval(int value){ + _calibration_sample_interval = value; +} +void SensorMQ::setReadSampleTimes(int value) { + _read_sample_times = value; +} +void SensorMQ::setReadSampleInterval(int value) { + _read_sample_interval = value; +} +void SensorMQ::setLPGCurve(float *value) { + _LPGCurve[0] = value[0]; + _LPGCurve[2] = value[1]; + _LPGCurve[2] = value[2]; +} +void SensorMQ::setCOCurve(float *value) { + _COCurve[0] = value[0]; + _COCurve[2] = value[1]; + _COCurve[2] = value[2]; +} +void SensorMQ::setSmokeCurve(float *value) { + _SmokeCurve[0] = value[0]; + _SmokeCurve[2] = value[1]; + _SmokeCurve[2] = value[2]; +} + +// what do to during before +void SensorMQ::onBefore() { + // prepare the pin for input + pinMode(_pin, INPUT); +} + +// what do to during setup +void SensorMQ::onSetup() { + _ro = _MQCalibration(); + onLoop(); +} + +// what do to during loop +void SensorMQ::onLoop() { + if (_pin == -1) return; + // calculate rs/ro + float mq = _MQRead()/_ro; + // calculate the ppm + float lpg = _MQGetGasPercentage(mq,_gas_lpg); + float co = _MQGetGasPercentage(mq,_gas_co); + float smoke = _MQGetGasPercentage(mq,_gas_smoke); + // assign to the value the requested gas + uint16_t value; + if (_target_gas == _gas_lpg) value = lpg; + if (_target_gas == _gas_co) value = co; + if (_target_gas == _gas_smoke) value = smoke; + #if DEBUG == 1 + Serial.print(F("MQ I=")); + Serial.print(_child_id); + Serial.print(F(" V=")); + Serial.print(value); + Serial.print(F(" LPG=")); + Serial.print(lpg); + Serial.print(F(" CO=")); + Serial.print(co); + Serial.print(F(" SMOKE=")); + Serial.println(smoke); + #endif + // store the value + _value_int = (int16_t)ceil(value); +} + +// what do to as the main task when receiving a message +void SensorMQ::onReceive(const MyMessage & message) { + onLoop(); +} + +// returns the calculated sensor resistance +float SensorMQ::_MQResistanceCalculation(int raw_adc) { + return ( ((float)_rl_value*(1023-raw_adc)/raw_adc)); +} + +// This function assumes that the sensor is in clean air +float SensorMQ::_MQCalibration() { + int i; + float val=0; + //take multiple samples + for (i=0; i< _calibration_sample_times; i++) { + val += _MQResistanceCalculation(analogRead(_pin)); + delay(_calibration_sample_interval); + } + //calculate the average value + val = val/_calibration_sample_times; + //divided by RO_CLEAN_AIR_FACTOR yields the Ro + val = val/_ro_clean_air_factor; + //according to the chart in the datasheet + return val; +} + +// This function use MQResistanceCalculation to caculate the sensor resistenc (Rs). +float SensorMQ::_MQRead() { + int i; + float rs=0; + for (i=0; i<_read_sample_times; i++) { + rs += _MQResistanceCalculation(analogRead(_pin)); + delay(_read_sample_interval); + } + rs = rs/_read_sample_times; + return rs; +} + + +// This function passes different curves to the MQGetPercentage function which calculates the ppm (parts per million) of the target gas. +int SensorMQ::_MQGetGasPercentage(float rs_ro_ratio, int gas_id) { + if ( gas_id == _gas_lpg ) { + return _MQGetPercentage(rs_ro_ratio,_LPGCurve); + } else if ( gas_id == _gas_co) { + return _MQGetPercentage(rs_ro_ratio,_COCurve); + } else if ( gas_id == _gas_smoke) { + return _MQGetPercentage(rs_ro_ratio,_SmokeCurve); + } + return 0; +} + +// returns ppm of the target gas +int SensorMQ::_MQGetPercentage(float rs_ro_ratio, float *pcurve) { + return (pow(10,( ((log10(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0]))); +} + + /******************************************* NodeManager */ @@ -1059,6 +1255,7 @@ int NodeManager::registerSensor(int sensor_type, int pin, int child_id) { else if (sensor_type == SENSOR_ANALOG_INPUT) return registerSensor(new SensorAnalogInput(child_id, pin)); else if (sensor_type == SENSOR_LDR) return registerSensor(new SensorLDR(child_id, pin)); else if (sensor_type == SENSOR_THERMISTOR) return registerSensor(new SensorThermistor(child_id, pin)); + else if (sensor_type == SENSOR_MQ) return registerSensor(new SensorMQ(child_id, pin)); #endif #if MODULE_DIGITAL_INPUT == 1 else if (sensor_type == SENSOR_DIGITAL_INPUT) return registerSensor(new SensorDigitalInput(child_id, pin)); @@ -1305,6 +1502,12 @@ void NodeManager::setup() { #if SERVICE_MESSAGES == 1 _send(_msg.set("STARTED")); #endif + // run setup for all the registered sensors + for (int i = 0; i < 255; i++) { + if (_sensors[i] == 0) continue; + // call each sensor's setup() + _sensors[i]->setup(); + } } // run the main function for all the register sensors diff --git a/NodeManager.h b/NodeManager.h index da3ca44..fff34de 100644 --- a/NodeManager.h +++ b/NodeManager.h @@ -41,7 +41,7 @@ #define EEPROM_SLEEP_UNIT 4 // define NodeManager version -#define VERSION 1.3 +#define VERSION 1.4 /************************************ * Include user defined configuration settings @@ -97,7 +97,7 @@ #define BATTERY_CHILD_ID 201 #endif -// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR +// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ #ifndef MODULE_ANALOG_INPUT #define MODULE_ANALOG_INPUT 0 #endif @@ -147,6 +147,8 @@ #define SENSOR_LDR 2 // Thermistor sensor, return the temperature based on the attached thermistor #define SENSOR_THERMISTOR 3 + // MQ2 air quality sensor + #define SENSOR_MQ 19 #endif #if MODULE_DIGITAL_INPUT == 1 // Generic digital sensor, return a pin's digital value @@ -194,7 +196,7 @@ // MLX90614 sensor, contactless temperature sensor #define SENSOR_BME280 18 #endif -// last Id: 18 +// last Id: 19 /*********************************** Libraries */ @@ -301,10 +303,12 @@ class Sensor { // define what to do at each stage of the sketch virtual void before(); virtual void presentation(); + virtual void setup(); virtual void loop(const MyMessage & message); virtual void receive(const MyMessage & message); // abstract functions, subclasses need to implement virtual void onBefore() = 0; + virtual void onSetup() = 0; virtual void onLoop() = 0; virtual void onReceive(const MyMessage & message) = 0; protected: @@ -353,6 +357,7 @@ class SensorAnalogInput: public Sensor { void setRangeMax(int value); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -391,6 +396,7 @@ class SensorThermistor: public Sensor { void setOffset(float value); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -409,6 +415,7 @@ class SensorDigitalInput: public Sensor { SensorDigitalInput(int child_id, int pin); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); }; @@ -425,6 +432,7 @@ class SensorDigitalOutput: public Sensor { void setPulseWidth(int value); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -460,6 +468,7 @@ class SensorDHT: public Sensor { SensorDHT(int child_id, int pin, DHT* dht, int sensor_type, int dht_type); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -479,6 +488,7 @@ class SensorSHT21: public Sensor { SensorSHT21(int child_id, int sensor_type); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -514,6 +524,7 @@ class SensorSwitch: public Sensor { int getInitial(); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -548,6 +559,7 @@ class SensorDs18b20: public Sensor { SensorDs18b20(int child_id, int pin, DallasTemperature* sensors, int index); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -566,6 +578,7 @@ class SensorBH1750: public Sensor { SensorBH1750(int child_id); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -582,6 +595,7 @@ class SensorMLX90614: public Sensor { SensorMLX90614(int child_id, Adafruit_MLX90614* mlx, int sensor_type); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -599,6 +613,7 @@ class SensorBME280: public Sensor { SensorBME280(int child_id, Adafruit_BME280* bme, int sensor_type); // define what to do at each stage of the sketch void onBefore(); + void onSetup(); void onLoop(); void onReceive(const MyMessage & message); protected: @@ -607,6 +622,61 @@ class SensorBME280: public Sensor { }; #endif +/* + SensorMQ + */ +class SensorMQ: public Sensor { + public: + SensorMQ(int child_id, int pin); + // define the target gas whose ppm has to be returned. 0: LPG, 1: CO, 2: Smoke (default: 1); + void setTargetGas(int value); + // define the load resistance on the board, in kilo ohms (default: 1); + void setRlValue(float value); + // define the Ro resistance on the board (default: 10000); + void setRoValue(float value); + // Sensor resistance in clean air (default: 9.83); + void setCleanAirFactor(float value); + // define how many samples you are going to take in the calibration phase (default: 50); + void setCalibrationSampleTimes(int value); + // define the time interal(in milisecond) between each samples in the cablibration phase (default: 500); + void setCalibrationSampleInterval(int value); + // define how many samples you are going to take in normal operation (default: 50); + void setReadSampleTimes(int value); + // define the time interal(in milisecond) between each samples in the normal operations (default: 5); + void setReadSampleInterval(int value); + // set the LPGCurve array (default: {2.3,0.21,-0.47}) + void setLPGCurve(float *value); + // set the COCurve array (default: {2.3,0.72,-0.34}) + void setCOCurve(float *value); + // set the SmokeCurve array (default: {2.3,0.53,-0.44}) + void setSmokeCurve(float *value); + // define what to do at each stage of the sketch + void onBefore(); + void onSetup(); + void onLoop(); + void onReceive(const MyMessage & message); + protected: + float _rl_value = 1.0; + float _ro_clean_air_factor = 9.83; + int _calibration_sample_times = 50; + int _calibration_sample_interval = 500; + int _read_sample_interval = 50; + int _read_sample_times = 5; + float _ro = 10000.0; + float _LPGCurve[3] = {2.3,0.21,-0.47}; + float _COCurve[3] = {2.3,0.72,-0.34}; + float _SmokeCurve[3] = {2.3,0.53,-0.44}; + float _MQResistanceCalculation(int raw_adc); + float _MQCalibration(); + float _MQRead(); + int _MQGetGasPercentage(float rs_ro_ratio, int gas_id); + int _MQGetPercentage(float rs_ro_ratio, float *pcurve); + int _gas_lpg = 0; + int _gas_co = 1; + int _gas_smoke = 2; + int _target_gas = _gas_co; +}; + /*************************************** NodeManager: manages all the aspects of the node */ diff --git a/NodeManager.ino b/NodeManager.ino index fead15d..4d224b4 100644 --- a/NodeManager.ino +++ b/NodeManager.ino @@ -29,7 +29,7 @@ void before() { /* * Register below your sensors */ - + nodeManager.registerSensor(SENSOR_MQ,A1); /* * Register above your sensors diff --git a/README.md b/README.md index d5a7892..0a92d2e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Those NodeManager's directives in the `config.h` file control which module/libra // if enabled, a battery sensor will be created at BATTERY_CHILD_ID and will report vcc voltage together with the battery level percentage #define BATTERY_SENSOR 1 -// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR +// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ #define MODULE_ANALOG_INPUT 1 // Enable this module to use one of the following sensors: SENSOR_DIGITAL_INPUT #define MODULE_DIGITAL_INPUT 1 @@ -196,6 +196,7 @@ SENSOR_HTU21D | HTU21D sensor, return temperature/humidity based on the attached SENSOR_BH1750 | BH1750 sensor, return light level in lux SENSOR_MLX90614 | MLX90614 contactless temperature sensor, return ambient and object temperature SENSOR_BME280 | BME280 sensor, return temperature/humidity/pressure based on the attached BME280 sensor +SENSOR_MQ | MQ sensor, return ppm of the target gas To register a sensor simply call the NodeManager instance with the sensory type and the pin the sensor is conncted to. For example: ~~~c @@ -213,6 +214,8 @@ If you want to create a custom sensor and register it with NodeManager so it can ~~~c // define what to do during before() to setup the sensor void onBefore(); + // define what to do during setup() by executing the sensor's main task + void onSetup(); // define what to do during loop() by executing the sensor's main task void onLoop(); // define what to do during receive() when the sensor receives a message @@ -258,7 +261,7 @@ The following methods are available for all the sensors: void setForceUpdate(int value); // the value type of this sensor (default: TYPE_INTEGER) void setValueType(int value); -// for float values, set the float precision (default: 2) + // for float values, set the float precision (default: 2) void setFloatPrecision(int value); // optionally sleep interval in milliseconds before sending each message to the radio network (default: 0) void setSleepBetweenSend(int value); @@ -306,6 +309,32 @@ Each sensor class can expose additional methods. void setOffset(float value); ~~~ +#### SensorMQ +~~~c + // define the target gas whose ppm has to be returned. 0: LPG, 1: CO, 2: Smoke (default: 1); + void setTargetGas(int value); + // define the load resistance on the board, in kilo ohms (default: 1); + void setRlValue(float value); + // define the Ro resistance on the board (default: 10000); + void setRoValue(float value); + // Sensor resistance in clean air (default: 9.83); + void setCleanAirFactor(float value); + // define how many samples you are going to take in the calibration phase (default: 50); + void setCalibrationSampleTimes(int value); + // define the time interal(in milisecond) between each samples in the cablibration phase (default: 500); + void setCalibrationSampleInterval(int value); + // define how many samples you are going to take in normal operation (default: 50); + void setReadSampleTimes(int value); + // define the time interal(in milisecond) between each samples in the normal operations (default: 5); + void setReadSampleInterval(int value); + // set the LPGCurve array (default: {2.3,0.21,-0.47}) + void setLPGCurve(float *value); + // set the COCurve array (default: {2.3,0.72,-0.34}) + void setCOCurve(float *value); + // set the SmokeCurve array (default: {2.3,0.53,-0.44}) + void setSmokeCurve(float *value); +~~~ + #### SensorDigitalOutput / SensorRelay / SensorLatchingRelay ~~~c // set how to initialize the output (default: LOW) diff --git a/config.h b/config.h index e160b1c..70ce7ad 100644 --- a/config.h +++ b/config.h @@ -5,8 +5,8 @@ * Sketch configuration */ -#define SKETCH_NAME "NodeManagerTemplate" -#define SKETCH_VERSION "1.3" +#define SKETCH_NAME "NodeManager" +#define SKETCH_VERSION "1.4" /********************************** * MySensors configuration @@ -16,7 +16,7 @@ //#define MY_NODE_ID 100 #define MY_RADIO_NRF24 -#define MY_RF24_ENABLE_ENCRYPTION +//#define MY_RF24_ENABLE_ENCRYPTION //#define MY_RF24_CHANNEL 76 //#define MY_RADIO_RFM69 @@ -48,7 +48,7 @@ // if enabled, a battery sensor will be created at BATTERY_CHILD_ID and will report vcc voltage together with the battery level percentage #define BATTERY_SENSOR 1 -// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR +// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ #define MODULE_ANALOG_INPUT 1 // Enable this module to use one of the following sensors: SENSOR_DIGITAL_INPUT #define MODULE_DIGITAL_INPUT 1 -- GitLab