mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-27 14:42:18 +03:00
553 lines
20 KiB
C++
553 lines
20 KiB
C++
#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> ¶m)
|
||
{
|
||
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;
|
||
}
|
||
}
|