Files
IoTManager/src/modules/sensors/MQgas/MQgas.cpp
2023-11-24 21:14:46 +01:00

553 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Global.h"
#include "classes/IoTItem.h"
#include "NTP.h"
extern IoTGpio IoTgpio;
#ifdef ESP8266
#define ADC_BIT 10
#endif
#ifdef ESP32
#define ADC_BIT 12
// #define analogWrite ledcWrite
#endif
#define ADC_VALUE_MAX pow(2, ADC_BIT)
// Это файл сенсора, в нем осуществляется чтение сенсора.
// для добавления сенсора вам нужно скопировать этот файл и заменить в нем текст AnalogAdc на название вашего сенсора
// Название должно быть уникальным, коротким и отражать суть сенсора.
// ребенок - родитель
class MQgas : public IoTItem
{
private:
//=======================================================================================================
// Секция переменных.
// Это секция где Вы можете объявлять переменные и объекты arduino библиотек, что бы
// впоследствии использовать их в loop и setup
byte _pin;
int currentSensor;
float _ro = 0; // расчетное сопротивления датчика. переменная для текущих расчетов
float ro_TH = 0;
float rlBoard = 0;
float R0CleanAir; // расчетное сопротивления датчика, полученное из настроек элемента
float R0CleanAirDefault;
float RlR0CleanAir;
float RlR0CleanAirDefault;
float ppmCleanAir;
float ppmCleanAirDefault;
double aLimit;
double bLimit;
byte sampleTimes = 10;
byte sampleInterval = 20;
byte intensity = 5;
bool autoCalibrationEnable = true;
unsigned int autoCalibPeriod = 24; // часы
unsigned long autoCalibTimer = 0; // мс
unsigned int warmUpTime = 60; // сек
bool _stateCalibrate = false;
bool _stateCalibrateTH = false;
String lastCalibration;
float highRs = 0;
bool enableTempHumCorrection = false;
float k1;
float k2;
float b1;
float b2;
double Hum;
double Temp;
double tempHumCorrection;
String _idTempSensor;
String _idHumSensor;
float operatingVoltage = 3.3;
double ppmResult = 0;
bool debug = true;
public:
//=======================================================================================================
// setup()
// это аналог setup из arduino. Здесь вы можете выполнять методы инициализации сенсора.
// Такие как ...begin и подставлять в них параметры полученные из web интерфейса.
// Все параметры хранятся в перемененной parameters, вы можете прочитать любой параметр используя jsonRead функции:
// jsonReadStr, jsonReadBool, jsonReadInt
MQgas(String parameters) : IoTItem(parameters)
{
#ifdef ESP8266
_pin = 0;
#endif
#ifdef ESP32
_pin = jsonReadInt(parameters, "pin-Esp32");
// adcAttachPin(_pin);
#endif
currentSensor = jsonReadInt(parameters, "Series"); // пока не используется
jsonRead(parameters, F("Rl on board"), rlBoard, false);
jsonRead(parameters, F("aLimit"), aLimit, false);
jsonRead(parameters, F("bLimit"), bLimit, false);
jsonRead(parameters, F("Ro in clean air"), R0CleanAir, false); //
R0CleanAirDefault = R0CleanAir;
jsonRead(parameters, F("Rl/Ro in clean air"), RlR0CleanAir, false); // соотношение
RlR0CleanAirDefault = RlR0CleanAir;
jsonRead(parameters, F("PPM in clean air"), ppmCleanAir, false); //
ppmCleanAirDefault = ppmCleanAir;
warmUpTime = jsonReadInt(parameters, "Warm up time");
sampleInterval = jsonReadInt(parameters, "Sample interval");
sampleTimes = jsonReadInt(parameters, "Sample times");
intensity = jsonReadInt(parameters, "Calibtation intensity");
autoCalibrationEnable = jsonReadBool(parameters, "autoCalibration");
autoCalibPeriod = jsonReadInt(parameters, "autoCalib.Period");
enableTempHumCorrection = jsonReadBool(parameters, "TempHum correction");
jsonRead(parameters, F("k1"), k1, false);
jsonRead(parameters, F("k2"), k2, false);
jsonRead(parameters, F("b1"), b1, false);
jsonRead(parameters, F("b2"), b2, false);
jsonRead(parameters, F("temperature"), Temp, false);
jsonRead(parameters, F("humidity"), Hum, false);
jsonRead(parameters, "idTempSensor", _idTempSensor);
jsonRead(parameters, "idHumSensor", _idHumSensor);
jsonRead(parameters, F("operating voltage"), operatingVoltage, false);
debug = jsonReadBool(parameters, "Debug");
if (debug)
{
Serial.print("ADC_VALUE_MAX =");
Serial.println(ADC_VALUE_MAX);
Serial.print("R0CleanAir =");
Serial.println(R0CleanAir);
Serial.print("Rl/R0CleanAir =");
Serial.println(RlR0CleanAir);
Serial.print("ppmCleanAir =");
Serial.println(ppmCleanAir);
}
calibrate(); // быстрая калибровка, по холодному
warmUpTime = millis() / 1000 + warmUpTime;
_stateCalibrate = false; // чтобы сделать еще одну калибровку через warmUpTime
lastCalibration = NAN;
}
//=======================================================================================================
// это аналог loop из arduino, но вызываемый каждые int секунд, заданные в настройках. Здесь вы должны выполнить чтение вашего сенсора
void doByInterval()
{
if (!_stateCalibrate && millis() > warmUpTime * 1000) // повторная калибровка после минимального прогрева
{
calibrate();
}
if (((millis() - autoCalibTimer) > autoCalibPeriod * 3600 * 1000) && autoCalibrationEnable) // автомастическа калибровка
{
autoCalibration();
}
ppmResult = readSensor();
if (_stateCalibrate) // без калибровки не выводим ничего
{
value.valD = ppmResult;
regEvent(value.valD, "MQgas");
}
else
{
value.valD = NAN;
regEvent(value.valD, "MQgas");
}
}
// получаем Temp и Hum из других элементов
void onRegEvent(IoTItem *eventItem)
{
if (eventItem)
{
if (_idTempSensor != "")
{
if (_idTempSensor == eventItem->getID())
{
String _idTempSensorString = eventItem->getValue();
Temp = _idTempSensorString.toFloat();
if (debug)
{
String output = " got via eventItem: Temp = " + String(Temp);
SerialPrint("I", "MQgas", output, _id);
}
}
}
if (_idHumSensor != "")
{
if (_idHumSensor == eventItem->getID())
{
String _idHumSensorSting = eventItem->getValue();
Hum = _idHumSensorSting.toFloat();
if (debug)
{
String output = " got via eventItem: Hum = " + String(Hum);
SerialPrint("I", "MQgas", output, _id);
}
}
}
}
}
// получаем вызовы из сценария
IoTValue execute(String command, std::vector<IoTValue> &param)
{
if (command == "calibrate") // калибровка значениями по умолчанию (из настроек)
{
R0CleanAir = R0CleanAirDefault;
RlR0CleanAir = RlR0CleanAirDefault;
ppmCleanAir = ppmCleanAirDefault;
calibrate();
SerialPrint("I", "MQgas", "calibrate() with default values", _id);
}
else if (command == "calibrateR0")
{
R0CleanAir = param[0].valD;
calibrate();
String output = "calibrateR0(), R0CleanAir = " + String(R0CleanAir);
SerialPrint("I", "MQgas", output, _id);
}
else if (command == "calibrateRlRo")
{
R0CleanAir = 0;
ppmCleanAir = 0;
RlR0CleanAir = param[0].valD;
calibrate();
String output = "calibrateRlRo(), RlR0CleanAir = " + String(RlR0CleanAir);
SerialPrint("I", "MQgas", output, _id);
}
else if (command == "calibratePPM")
{
R0CleanAir = 0;
RlR0CleanAir = 0;
ppmCleanAir = param[0].valD;
calibrate();
String output = "calibratePPM(), ppmCleanAir = " + String(ppmCleanAir);
SerialPrint("I", "MQgas", output, _id);
}
else if (command == "setAutoCalibration")
{
if (param[0].isDecimal)
{
autoCalibrationEnable = param[0].valD;
}
String output = "setAutoCalibration = " + String(autoCalibrationEnable);
SerialPrint("I", "MQgas", output, _id);
}
else if (command == "runAutoCalibration")
{
autoCalibration();
}
else if (command == "lastCalibration") // оправляем время послежней калибровки в сценарий
{
IoTValue valTmp;
valTmp.isDecimal = false;
valTmp.valS = lastCalibration;
String output = "By request: lastCalibration = " + String(valTmp.valS);
SerialPrint("I", "MQgas", output, _id);
return valTmp;
}
/*
else if (command == "enabledAutoCalibration") // оправляем время послежней калибровки в сценарий
{
IoTValue valTmp;
valTmp.isDecimal = true;
valTmp.valD = autoCalibrationEnable;
String output = "By request: enabledAutoCalibration = " + String(valTmp.valD);
SerialPrint("I", "MQgas", output, _id);
return valTmp;
}
*/
else if (command == "TempHumCorrection") // получаем Temp и Hum из сценария
{
if (param[0].isDecimal)
Temp = param[0].valD;
if (param[1].isDecimal)
Hum = param[1].valD;
String output = "TempHumCorrection() temperature = " + String(Temp) + " humidity = " + String(Hum);
SerialPrint("I", "MQgas", output, _id);
}
return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки
}
// получиение соотношения Rl/Ro на чистом воздухе
float getRlRoInCleanAir()
{
float RlR0CleanAirCalc;
if (ppmCleanAir) // или расчитываем по ppm
{
RlR0CleanAirCalc = exp((log(ppmCleanAir) * aLimit) + bLimit);
}
else // или берем готовое их настроек
{
RlR0CleanAirCalc = RlR0CleanAir;
}
return RlR0CleanAirCalc;
}
// калибровка датчика
void calibrate()
{
float ro = 0;
if (R0CleanAir) // при знании сопративления датчика на чистом воздухe
{ // фиксированая калибровка датчика
ro = R0CleanAir;
RlR0CleanAir = rlBoard / R0CleanAir; // просто пересчитываем для вывода в дебаг
}
else // расчет ro через RlRoInCleanAir
{
float rs = readRs(sampleTimes * intensity); // считывания показаний сопративление датчика на чистом воздухе!!
ro = rs / getRlRoInCleanAir();
}
if (ro && ro == ro) // проверка что не NAN
{
_ro = ro; // значение сопративления сенсора на воздухе
_stateCalibrate = true;
lastCalibration = getDateTimeDotFormated();
String output = "Calibration successful! Ro in Clean Air = " + String(_ro);
Serial.println();
SerialPrint("I", "MQgas", output, _id);
calibrationTH();
}
else
{
Serial.println();
SerialPrint("E", "MQgas", " Calibration failed! Fill one of Setting 'in clean air', check wiring ", _id);
}
}
// выполнение автоматической калибровки
void autoCalibration()
{
_ro = highRs / getRlRoInCleanAir(); // значение сопративления сенсора на воздухе
if (_ro && _ro == _ro)
{
_stateCalibrate = true;
lastCalibration = getDateTimeDotFormated();
String output = "autoCalibration successful!, R0 = " + String(_ro);
Serial.println();
SerialPrint("I", "MQgas", output, _id);
calibrationTH();
autoCalibTimer = millis();
highRs = 0;
}
else
{
Serial.println();
SerialPrint("E", "MQgas", " autoCalibration failed! Fill one of Setting 'in clean air', check wiring ", _id);
}
}
// калибровка с учетом температуры и влажности
void calibrationTH()
{
if (enableTempHumCorrection && goodReadTH()) // нормализуем к текущей температуре и влажности
{
tempHumCorrection = TempHumCorrection();
ro_TH = _ro * tempHumCorrection;
_stateCalibrateTH = true;
String output = "TH Calibration successful! Ro_TH in Clean Air = " + String(ro_TH);
Serial.println();
SerialPrint("I", "MQgas", output, _id);
}
else
{
Serial.println();
SerialPrint("E", "MQgas", "TH Calibration failed! Fill Temp and Hum at Settings or add Temp and Hum sensors!", _id);
}
}
// расчет сопротивление датчика
float CalculateResistance(int sensorADC)
{
float sensorVoltage = sensorADC * (operatingVoltage / ADC_VALUE_MAX);
float sensorResistance = (operatingVoltage - sensorVoltage) / sensorVoltage * rlBoard;
return sensorResistance;
}
// циклическое считывание сопративления датчика
float readRs(byte sampleTimes)
{
float rs = 0;
int sensorADC;
byte error0 = 0;
byte errorMax = 0;
for (int i = 0; i < sampleTimes; i++)
{
sensorADC = analogRead(_pin);
if (sensorADC >= ADC_VALUE_MAX - 1)
{
errorMax = 1;
if (debug)
{
String output = "Check wiring, analogRead(_pin) = " + String(sensorADC);
SerialPrint("E", "MQgas", output, _id);
}
}
else if (sensorADC == 0)
{
error0 = 1;
if (debug)
SerialPrint("E", "MQgas", "Check sensor, analogRead(_pin) = 0", _id);
}
else
{
rs += CalculateResistance(sensorADC);
delay(sampleInterval);
}
}
if (!error0 && !errorMax)
{
rs = rs / (sampleTimes);
if (rs > highRs && millis() > warmUpTime * 1000) // запоминаем максимальное значение сопротивления сенсора для автокалибровки
highRs = rs;
}
else
{
rs = NAN;
if (errorMax)
{
String output = "Check wiring, analogRead(_pin) = " + String(sensorADC);
SerialPrint("E", "MQgas", output, _id);
}
if (error0)
SerialPrint("E", "MQgas", "Check sensor, analogRead(_pin) = 0", _id);
}
if (debug)
{
Serial.print("Analog: ");
Serial.print(analogRead(_pin));
Serial.print("\tRo: ");
Serial.print(_ro);
Serial.print("\treadRs: ");
Serial.print(rs);
Serial.print("\thighRs: ");
Serial.print(highRs);
}
return rs;
}
// расчет соотношения Rs/Ro
float readRatio()
{
float readRatio = NAN;
if (_ro)
{
float rs = readRs(sampleTimes);
if (rs)
{
readRatio = rs / _ro; // getRo();
}
}
if (debug)
{
Serial.print(" \tRs/RoRatio: ");
Serial.print(readRatio);
}
return readRatio;
}
// расчет значения концентрации газа
double readSensor()
{
double readSensor = NAN;
double ratio = readRatio();
if (ratio != 0 && ratio == ratio) // also not NAN
{
readSensor = exp((log(ratio) - bLimit) / aLimit);
if (debug)
{
Serial.print("\tGas: ");
Serial.println(readSensor, 10);
}
if (enableTempHumCorrection && _stateCalibrateTH && goodReadTH()) // поправка на температуру и влажность
{
tempHumCorrection = TempHumCorrection();
ratio = ratio * tempHumCorrection;
readSensor = exp((log(ratio * _ro / ro_TH) - bLimit) / aLimit);
if (debug)
{
Serial.print("\tTHCor: ");
Serial.print(tempHumCorrection, 10);
Serial.print("\tGasTHCorrected: ");
Serial.println(readSensor, 10);
}
}
}
return readSensor;
}
// расчет поправки на температуру и влажность
double TempHumCorrection()
{
double correction = 1;
double k_hum = k1 * Hum / 100 + k2;
double b_hum = b1 * Hum / 100 + b2;
correction = k_hum * Temp + b_hum;
if (debug)
{
Serial.print("\tk_hum: ");
Serial.print(k_hum, 10);
Serial.print("\tb_hum: ");
Serial.print(b_hum, 10);
Serial.print("\tcor: ");
Serial.print(correction, 10);
}
return correction;
}
// проверка значний температуры и влажности
bool goodReadTH()
{
if (Hum > 0 && Hum < 95 && Temp > 0 && Temp < 70)
{
return true;
}
else
{
if (debug)
{
Serial.println();
SerialPrint("E", "MQgas", "Wrong data from Temperature and Humidity sensor or from Settings", _id);
}
return false;
}
}
~MQgas(){};
};
void *getAPI_MQgas(String subtype, String param)
{
if (subtype == F("MQgas"))
{
return new MQgas(param);
}
else
{
return nullptr;
}
}