Контроль изменения температуры

На уроке мы проходили пример, на базе которого будем знакомиться с возможностями языка С++. Приведенная ниже программа включает в себя повторение пройденного материала + использует контейнеры, которыми мы не пользовались до сих пор.

В примере используется плата Arduino Uno, датчик DS19B20 и RGB светодиод.

Задача программы:

  • Измерять температуру
  • Отслеживать резкое изменение температуры
  • Контролировать превышение пороговых значений
  • Контролировать состояние датчика температуры
  • Формировать и выдавать по запросу предупреждающее сообщение и код ошибки.

Текущая версия обрабатывает одну ошибку в течении заданного периода, выдавая сообщение о наиболее приоритетной. В дальнейшем мы познакомимся с тем, как сформировать очередь ошибок для последовательной обработки каждой.

Список возможных ошибок:
enum class Errors {
  FREE,
  CONNECT,
  DT_UP,    //резкое повышение температуры
  DT_DOWN,  //резкое понижение температуры
  MAXVAL,   //превышение верхнего порога (включаем кондей)
  MINVAL,   //холоднее нижнего порога (включаем обогреватель)
  OK_TEMPER //температура в норме
};

OK_TEMPER не совсем в теме, но пока оставим как диагностическое сообщение не являющееся ошибкой.

using errorCode_t = Errors;
using errorMsg_t = std::pair<const char *, errorCode_t>;

Диагностическая информация будет передаваться в виде структуры содержащей текстовое сообщение и код ошибки из enum Errors. Для упрощения чтения программы назначим псевдонимы для списка ошибок (errorCode_t) и самого сообщения (errorMsg_t), т.к. понять что есть errorMsg_t значительно проще, чем std::pair<const char *, errorCode_t>

Работать с датчиком температуры будет класс DSB20. Задача класса – через заданные промежутки времени измерять температуру, рассчитывать минимальное, максимальное и среднее значение. Для подавления помех (случайных, сбойных показаний датчика) используем экспоненциальное сглаживание. Коэффициент сглаживания ALFA = 0,3.

Ниже показано, как сохраняются в массиве значения температуры:

double tempC = dS.getTempC(sA);                //получение данных с датчика температуры
for(double &temp: arTemp)sum += temp;          //сумма всех значений массива
double mean = sum/arSize;                      //вычисляем среднее значение температуры
arTemp[nAr] = ALFA * tempC + (1- ALFA) * mean; //сохраняем отфильтрованное значение

В жилом помещении температура воздуха не должна резко изменяться. Наш класс будет собирать значения температуры в массив размером AR_TEMPER_SIZE с периодичностью DT_TEMPER миллисекунд. Разница между минимальным и максимальным значением температуры в массиве, после циклического наполнения, даст данные о динамике. Резкое изменение температуры создаст сообщение об ошибке, которое будет обрабатывать другой блок программы.

resMin = std::min_element(arTemp.begin(), arTemp.end());
resMax = std::max_element(arTemp.begin(), arTemp.end());
if(abs(*resMax - *resMin) > maxStep){
  String error1 = "Резкое " + static_cast<String>(resMin < resMax? "повышение":"понижение") + " температуры на " + static_cast<String>(*resMax - *resMin) + " гр.";
  errorMsg = {error1, resMin < resMax? Errors::DT_UP: Errors::DT_DOWN};
}

Обратите внимание на переменную errorMsg, эта переменная является членом базового класса BasePin.

class BasePin {
  protected:
    const char* name;    //имя устройства для диагностики
    errorMsg_t errorMsg; 
  public:
    BasePin(const char* name):name(name), errorMsg({"", Errors::FREE}){}
    bool diagError(){return errorMsg.second != Errors::FREE;}
    errorMsg_t& getEr(){return errorMsg;}
    void clearError(){errorMsg = {"", Errors::FREE};}
};

Класс BasePin хранит имя датчика (к примеру – “кухня” или “гараж”) и структуру содержащую код и описание ошибки.

Для работы класса DSB20 нужно подключить класс таймера, который отсчитывает интервалы времени между измерениями, и класс управления индикацией Vled. Класс Vled мы уже использовали в наших примерах. Каждый экземпляр класса управляет одним светодиодом, пин которого передается в конструктор класса при инициализации. Включение индикатора осуществляется вызовом функции члена класса turnOnVLed(int nF). nF – число миганий светодиода.

Ниже привожу содержание файлов программы. Стоит выделить новые классы в отдельные файлы, но они у нас пока не доделаны. Оставлю так, как есть.

#include <DallasTemperature.h>
#include <ArduinoSTL.h>
#include <vector>
#include <utility>
#include "timer.h"
#include "vLed.h"

#define PIN_DS18B20 8
#define MIN_TEMPER_VAL 15
#define MAX_TEMPER_VAL 28
#define AR_TEMPER_SIZE 10
#define MAX_TEMPER_STEP 0.8
#define DT_TEMPER 2000

const uint8_t LED_PIN_BLUE = 9;
const uint8_t LED_PIN_GREEN = 10;
const uint8_t LED_PIN_RED = 11;
const double ALFA = 0.3;

typedef std::vector <double>::iterator temrIt_t;
//using temrIt_t = std::vector <double>::iterator;

//список возможных ошибок
enum class Errors {
  FREE,
  CONNECT,
  DT_UP,
  DT_DOWN,
  MAXVAL,
  MINVAL,
  OK_TEMPER
};
using errorCode_t = Errors;
//using errorMsg_t = std::pair<const char *, errorCode_t>;
using errorMsg_t = std::pair<String, errorCode_t>;
// Создаем объект OneWire
OneWire oneWire(PIN_DS18B20);

// Создаем объект DallasTemperature для работы с сенсорами, передавая ему ссылку на объект для работы с 1-Wire.
DallasTemperature dallasSensors(&oneWire);

// Специальный объект для хранения адреса устройства
DeviceAddress sensorAddress={0x28, 0x14, 0xE6, 0x7F, 0x6, 0x0, 0x0, 0x3C};
//********************************************************************************
class BasePin {
  protected:
    const char* name;    //имя устройства для диагностики
    errorMsg_t errorMsg; 
  public:
    BasePin(const char* name):name(name), errorMsg({"", Errors::FREE}){}
    bool diagError(){return errorMsg.second != Errors::FREE;}
    errorMsg_t& getEr(){return errorMsg;}
    void clearError(){errorMsg = {"", Errors::FREE};}
};
//********************************************************************************
class  DSB20: public BasePin{
    DallasTemperature& dS;
    DeviceAddress& sA;  //адрес датчика температуры
    std::vector <double> arTemp;
    uint8_t arSize;
    uint8_t nAr;
    double maxStep;
    Timer* timer;
    uint32_t dTime; //интервал между запосами температуры
  protected:
    double getTemp();
    void hotCool(double);
    double expTemp(double); //фильтрация значения получаемого от датчика температуры
  public:
    DSB20(const char* name, DallasTemperature& dS, DeviceAddress &sA);
    ~DSB20();
    double& getMean();  //запрос текущего значения температуры
    void minmaxTemp();  //контроль динамики
    bool cycle();
};
//-------------------------
DSB20::DSB20(const char* name, DallasTemperature& dS, DeviceAddress &sA) : BasePin(name),dS(dS), sA(sA){
  arSize = AR_TEMPER_SIZE;
  nAr = 0;
  maxStep = MAX_TEMPER_STEP; //максимально допустимое изменение значения температуры в 10 сек.
  timer = new Timer();
  dTime = DT_TEMPER; //сделать сеттер
  arTemp.reserve(arSize);
  arTemp.assign(arSize, 22);
}
//-------------------------
DSB20::~DSB20(){
  delete timer;
}
//-------------------------
double& DSB20::getMean(){
  return arTemp[nAr];      //последнее усредненное значение
}
//-------------------------
void DSB20::hotCool(double mean){
    if(mean < MIN_TEMPER_VAL)errorMsg = {"Cool", Errors::MINVAL}; //контроль крайних значений
      else if(mean < MAX_TEMPER_VAL)  errorMsg = {"Norm", Errors::OK_TEMPER};
        else errorMsg = {"Hot", Errors::MAXVAL};
}
//-------------------------
double DSB20::expTemp(double tC){ //экспоненциальное сглаживание
  double sum = 0.;
  for(double &temp: arTemp)sum += temp; 
  double mean = sum/arSize; //вычисляем среднее значение температуры
  hotCool(mean);  //контроль превышения порогов
  return ALFA * tC + (1- ALFA) * mean;
}
//-------------------------
void DSB20::minmaxTemp(){  //контроль динамики изменения температуры
  temrIt_t resMin;  //    std::vector <double>::iterator resMin;
  temrIt_t resMax;  //    std::vector <double>::iterator resMax;
  resMin = std::min_element(arTemp.begin(), arTemp.end());
  resMax = std::max_element(arTemp.begin(), arTemp.end());
  if(abs(*resMax - *resMin) > maxStep){ //если разница 
    String error = "Резкое " + static_cast<String>(resMin < resMax? "повышение":"понижение") + " температуры на " + static_cast<String>(*resMax - *resMin) + " гр.";
    errorMsg = {error, resMin < resMax? Errors::DT_UP: Errors::DT_DOWN};
  }
}
//-------------------------
double DSB20::getTemp(){
  double tempC = dS.getTempC(sA);
  if(tempC == -127.0){
      errorMsg = {"Not connected", Errors::CONNECT};
  } else {
    arTemp[nAr] = expTemp(tempC); //заполнение массива
    Serial.println(static_cast<String>(nAr) + " => " + arTemp[nAr]);
    if(++nAr == arSize){
      minmaxTemp(); //контроль динамики изменения температуры
      nAr = 0;
    }
  }
  return tempC;
}
//--------------------------
bool DSB20::cycle(){
  if(!timer->getTimer()){
    timer->setTimer(dTime);
     getTemp();
  }
  return true;
}
//********************************************************************************
// Вспомогательная функция печати значения температуры для устрйоства
void printTemperature(DeviceAddress deviceAddress){
  double tempC = dallasSensors.getTempC(deviceAddress);
  Serial.print("Temp C: ");
  Serial.println(tempC);
}
//********************************************************************************
// Вспомогательная функция для отображения адреса датчика ds18b20
void printAddress(DeviceAddress deviceAddress){
  for (uint8_t i = 0; i < 8; i++)
  {
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
}
//************************************* !!!
DSB20 *dsb20;
Vled vledBlue(LED_PIN_BLUE);
Vled vledGreen(LED_PIN_GREEN);
Vled vledRed(LED_PIN_RED);
//*************************************
void setup(void){
  Serial.begin(9600);  
  dsb20 = new DSB20("MySensor", dallasSensors, sensorAddress);

}
//************************************
void loop(void){
  // Запрос на измерения датчиком температуры
  dallasSensors.requestTemperatures(); // Просим ds18b20 собрать данные
  if(dsb20->cycle())
    if(dsb20->diagError()){
      if(dsb20->getEr().second != Errors::OK_TEMPER)Serial.println(dsb20->getEr().first);
      switch(dsb20->getEr().second){
        case Errors::CONNECT:vledRed.turnOnVLed(20);vledGreen.turnOnVLed(20);vledBlue.turnOnVLed(20);break;
        case Errors::DT_UP:vledRed.turnOnVLed(8);break;
        case Errors::MAXVAL:vledRed.turnOnVLed();break;
        case Errors::DT_DOWN:vledBlue.turnOnVLed(8);break;
        case Errors::MINVAL:vledBlue.turnOnVLed();break;
        default:break;
      }
      dsb20->clearError();
    }
  vledBlue.cycleVLed();
  vledGreen.cycleVLed();
  vledRed.cycleVLed();
}

Далее Timer и Vled. Там комментировать нечего.

//vLed.h
#ifndef VLED_H
#define VLED_H
#include "timer.h"

class Vled {           //аппаратно - виртуальный индикатор
  private:
    int pin;              //номер пина, к которому подключен светодиод
    int nFlash;           //число миганий светодиода этого индикатора
    unsigned long time_x; //длительность включения светодиода
  protected:
    int statTimer;        //состояние индикатора, текущий полупериод
    Timer vTimer;         //внутренний таймер виртуального индикатора
  public:
    bool turnOnVLed();       //включение индикатора
    bool turnOnVLed(int);       //включение индикатора
    bool cycleVLed();     //обеспечение работы индикатора
    Vled(int, int nFlash = 2, unsigned long time_x = 180);
};

#endif
//vLed.cpp
#include "Arduino.h"
#include "vLed.h"
#include "timer.h"
//++++++++++++++++++++++++++++++++++++++++++++++++++++
Vled::Vled(int pin, int nFlash= 2, unsigned long time_x = 180){
    pinMode(pin, OUTPUT);
    this->pin = pin;
    this->nFlash = nFlash;    //фиксированное число миганий этого индикатора
    this->time_x = time_x;    //длительность свечения
    this->statTimer = 0;      
}
//****************************************************
bool Vled::turnOnVLed(){   //включение индикатора c фиксированным числом миганий
  return turnOnVLed(this -> nFlash);
}
//****************************************************
bool Vled::turnOnVLed(int nF){//включение индикатора с заданным числом миганий
  if(this->statTimer == 0){   //если индикатор свободен
    this->statTimer = nF * 2; //переводим в полупериоды
    this->vTimer.setTimer(this->time_x);
    return true;              //индикатор включен
  } else return false;        //индикатор занят
}
//************************************
bool Vled::cycleVLed(){               //обеспечение работы индикатора
  if(this->statTimer > 0 && !vTimer.getTimer()){ //определяем состояние таймера и если выключился,
      this->vTimer.setTimer(this->time_x);           //запускаем заново
      int st = (this->statTimer & 1) == 0;            //состояние светодиода определяется младшим разрядом счетчика индикатора
      digitalWrite(this->pin, st);
      this->statTimer--;             //уменьшаем счетчик индикатора
  }
  return this->statTimer > 0;
}
//timer.h
#ifndef TIMER_H
#define TIMER_H

//class Timer;
class Timer {
  private:
     unsigned long ulTimer;
  public:
     Timer(unsigned long dT);
     Timer();
     ~Timer(){};
     //*****************************       установка времени задержки таймера
     bool setTimer(unsigned long dT);
     //***************************** запрос состояния таймера. Возвращает true, если включен.
     bool getTimer();

};
#endif
//timer.cpp
#include <Arduino.h>
#include "timer.h"

Timer::Timer(unsigned long dT){ulTimer = dT;}
Timer::Timer(){ulTimer = 0;}
//***********************************************************************
bool Timer::setTimer(unsigned long dT){
bool bActive = false;
  if(ulTimer == 0){           //если таймер не включен
      bActive = true;
      ulTimer = millis() + dT;  //задаем значение - сумма текущего времени
  }                               //и заданного времни ожидания
  return bActive;
}
//***************************** запрос состояния таймера. Возвращает true, если включен.
bool Timer::getTimer(){
  if(ulTimer <= millis()){   //проверяем состояние таймера
      ulTimer = 0;            //если время истекло, сбрасываем заданное значение, выключаем таймер.
  }
  return ulTimer != 0;       //возвращаем логическое состояние таймера, если включен - true
}

Пишите вопросы.