Система управление резервом воды и котлом отопления в квартире.

NickVectra
Offline
Зарегистрирован: 09.01.2020
Добрый день!
Решил в квартире сделать систему управления резервом воды и газовым котлом как элемент умного дома.
 
Вот, что из этого получилось:
Два резервных бака с водой суммарным объемом 400 л. расположены в треугольной закрытой нише 1х1 м высотой 1 м.
К выходу баков подключены  гидрофор с гидроаккумулятором на 80 л.
В случае отсутствия необходимого напора воды в водопроводе на входе квартиры включается гидрофор, который поддерживает давление во внутренней системе водопровода.
Рис. 1. упрощенная схема водопровода
 
 
Поступающая вода через обратный клапан 1 и управляемый мотором кран наполняет баки. На входе баков установлен автоматический запорный клапан по уровню (на схеме не показан). Для дополнительной подстраховки в верхней части бака имеется аварийный перелив в канализацию.
Раз в неделю, по воскресеньям, происходит автоматическое обновление воды в баках. При этом кран перекрывает воду наполняющую баки и включается гидрофор. Это позволяет расходовать воду из баков на протяжении дня. В конце дня гидрофор отключается, кран открывается и баки наполняются свежей водой.
Так как давление в водопроводе не превышает 1,5 бар, а гидрофор создает давление от 2,2 до 4,5 бар, при установленных обратных клапанах нет необходимости в перекрывании воды и установке дополнительных кранов. 
 
В зимнее время температура воды на входе может быть ниже 10-15 градусов, что приводит к конденсату на стенках баков. Для предотвращения образования избыточной влаги в нише, где расположены баки, осуществляется ее вентиляция, озонирование и осушение.
 
Первая версия устройства была выполнена на устройствах жесткой логики — реле (см. последние три фото). Уровень воды в баках измерялся  WP-T804. Согласно выставленных уровней происходило срабатывание реле гидрофора, открытие и закрытие  крана наполнения баков. Два независимых датчика показывали текущий расход воды и ее температуру. Управление  вентилятором, озонатором и осушителем было выполнено на реле Sonoff (модели Basic и TH16).
 
 
Знакомство с Arduino состоялось три месяца назад. За это время удалось реализовать данный проект от идеи до готового изделия.
 
Устройство управления собрано на NodeMCU ESP8266, которое по сети WiFi подключено к домашней сети и имеет выход в интернет.  На входе баков установлен расходомер, измерители температуры воды и давления. Аналогичные измерительные датчики установлены на выходе гидрофора.
Учет показаний расхода воды выполнен на основе обработки прерываний вызванных импульсами поступающими от расходомеров, которые поступают на ножки D5 и D6 NodeMCU. 
В датчиках температуры воды используются термосопротивления 50 кОм, напряжение на которых измеряется 16 разрядным АЦП ADS1115 (шина I2C). Дополнительно в каждом цикле опроса датчиков измеряется напряжения питания 5 В, которое участвует как опорное при расчете.
В качестве датчиков температуры и влажности воздуха применены AM2320 работающие по шине I2C. В связи с тем, что в системе имеется два одинаковых датчика, с адресом 0x5C, который не может быть изменен. Поэтому используется мультиплексор шины I2C TCA9548A, который подключен к ножкам D1 и D2 NodeMCU с уровнем 3,3 В. К мультиплексору также подключены два четырех канальных АЦП ADS1115 (0x48), расширитель MCP2317 (0x20), измеритель тока INA226 (0x40) и FRAM (0х50-0х57).
Измерение уровня воды в баках проводится с помощью датчика уровня дифференциального давления (гидростатический датчик уровня). Выходной сигнал датчика  токовая петля 4-20 мА соответствует уровню воды 0-5 м.  Для возможности точного измерения тока на выходе датчика применен модуль INA226, в котором стандартный шунт 0,01 Ом был увеличен в 10000 раз. Питания датчика уровня осуществляется преобразователем DC-DC MT3608 5 в 24 Вольта. Для управления гидрофора используется твердотельное реле на 100 А. Оказалось, что управляющего сигнала 5 В  было недостаточно для коммутации напряжения 220 В. Поэтому управляющее напряжение было повышено до 24 В через MOSFET IRL540NPBF.
Резервный источник питания выполнен на одном аккумуляторе 18650, контроллере заряда литиевого аккумулятора TP4056 и повышающем преобразователе MT3608.
Упрощенная структурная схема измерителя показана на рис.2.
 
Датчики температуры DS18В20 OneWire контролируют температуру на твердотельном реле включения гидрофора, контроллере заряда литиевого аккумулятора резервного источника питания и элементе Пельтье в осушителе. Предотвращая перегрев реле гидрофора и контроллера и соответственно образования льда на радиаторе элемента Пельтье.
Датчик протечки следит за протечкой воды и в случае срабатывания отключает гидрофор, перекрывает кран наполнения баков и отправляет  E-mail с предупреждением пользователю.
Для хранения данных программы используется FRAM FM24CL16, которая подключена по шине I2C. Преимуществом использования данного типа памяти, является высокая скорость работы и практически неограниченное число циклов чтения/записи.
Положение крана перекрытия воды наполнения баков отслеживается с помощью аналогового датчика Холла и кругового магнита с диаметральной намагниченностью.
При превышении уровня влажности в нише выше заданного включается осушитель выполненный на элементе Пельтье. Конденсат собранный осушителем накапливается в резервуаре 0,7 л, при наполнении которого мембранный насос по таймеру выкачивает жидкость в канализацию.
Периодически два раза в день, в соответствии с установленными таймерами, включается вентиляция воздуха в нише. Утром в будние дни срабатывает таймер озонирования, вырабатываемый озон дополнительно очищает пространство в нише. После озонирование происходит вентиляция.
В качестве интерфейса взаимодействия с пользователем использовано приложение Virtuino. С целью сокращения объема передаваемой информации обновление данных происходит только на выбранной  странице управления. Планшет Samsung Tab 10.1 2014 Android 5.1.1 SM-P601 использован в качестве панели управления.
Примеры страниц панелей управления представлены на рисунках ниже.
 
 
 
Для обновления программного обеспечения используется режим по воздуху через HTTP Update Server.
Считывание данных выполняется каждую секунду. Контроль наличия связи с планшетом осуществляется каждые 5 секунд. 
В первоначальных версиях программы данные коэффициентов хранились во внутренней EEPROM ESP8266. В библиотеке INA226 также был задействована этот вид памяти. С целью избежания конфликтов использование EEPROM в библиотеке INA226 было исключено для ESP8266.
Применение в качестве внешней памяти для хранения данных AT24C256 приводило к сбоям программы. Это было вызвано тем, что суммарные данные показаний расходомеров записывались в AT24C256 по срабатыванию таймера на основе библиотеки Ticker. Стандартная библиотека AT24C256 имела задержку на 20 мс при записи. Для предотвращения сбоев в работе устройства - запись в память можно было проводить не по прерыванию, а в цикле loop. Позже память AT24C256 была заменена на FM24CL16. 
Для предотвращения ложного срабатывания реле управления исполнительными устройствами (гидрофор, вентилятор, озонатор, кран) введена первоначальная задержка на 60 секунд при старте.
В качестве библиотеки по отправке E-mail была выбрана Gsender, как имеющая наименьшую задержку.
Оперативную коррекцию данных датчиков можно, при необходимости, осуществить через установку нужных коэффициентов в приложении, значения которых будут перезаписаны в памяти.
В программе используются стандартные библиотеки:
ESP8266WiFi.h
ESP8266WebServer.h
ESP8266HTTPUpdateServer.h
VirtuinoCM.h
Adafruit_AM2320.h
Adafruit_MCP23017.h
Adafruit_ADS1015.h
OneWire.h
Wire.h
Ticker.h
EEPROM.h
При компилировании программы в среде Arduino IDE сделаны дополнительные следующие установки: Flash size 4M(3M SPIFFS) и IwIP Variant v1.4 Higher Bandwidth. При этом обеспечивается стабильная работа устройства.
Цикл чтения данных с датчиков DS18B20 (измерение около 750 мс) начинается со считывания, а затем отсылается команда запуска измерения для всех датчиков одновременно. Это позволяет получать данные со всех датчиков практически без задержки с периодичностью 1 сек.
В связи с тем, что применен мультиплексор I2C TCA9548A при обращении к модулям и датчикам, обязательным является предварительная установка нужного порта мультиплексора.
Библиотека FM24CL16 переписана и встроена в проект. Память FM24C16 имеет 8 страниц (адреса 0x50-0x57), в каждой по 256 байт. В библиотеке убрано разбиение на страницы и все пространство памяти доступно сплошной адресацией, а функции read и write позволяет сохранять любые данные и структуры.
Также встроены в проект библиотеки по работе с расходомерами и термосопротивлениями. 
Для дистанционного включения гидрофора, например, из ванной - используется радио-выключатель. Для этого в блоке установлен обучаемый приемник RX480E-4 433 МГц EV1527. 
Для защиты от сбоев по питанию смонтирован ИБП на чипе TP4056 (контроллер заряда), DW01 (схема защиты) и ML8205A (сдвоенный ключ MOSFET), повышающем  преобразователе на MT3608 и аккумуляторе 18650. Информация об уровне заряда передается в приложение.
Фото устройства приведено ниже. На последних трех фотографиях - блок управления первой версии. 
 
Датчики температуры DS18В20 OneWire контролируют температуру на твердотельном реле включения гидрофора, контроллере заряда литиевого аккумулятора резервного источника питания и элементе Пельтье в осушителе. Предотвращая перегрев реле гидрофора и контроллера и соответственно образования льда на радиаторе элемента Пельтье.
Датчик протечки следит за протечкой воды и в случае срабатывания отключает гидрофор, перекрывает кран наполнения баков и отправляет  E-mail с предупреждением пользователю.
Для хранения данных программы используется FRAM FM24CL16, которая подключена по шине I2C. Преимуществом использования данного типа памяти, является высокая скорость работы и практически неограниченное число циклов чтения/записи.
Положение крана перекрытия воды наполнения баков отслеживается с помощью аналогового датчика Холла и кругового магнита с диаметральной намагниченностью.
 
29/12/2019 Дополнено управлением газового котла.
Упрощенная структурная схема измерителя показана на рис.3.
 
В случае понижения температуры в комнате ниже установленной, которая измеряется датчиком температуры в комнате DS18B20 (контакт D5 - шина OneWire) происходит срабатывание реле (контакт D6), которое переводит котел в режим отопления. Датчик температуры в комнате подключен по двум проводам и запитывается через диод Шотке и конденсатор 10 мкФ. 
Датчик наружной температуры подключен по трех проводной схеме.
Энергометр PZEM-004T производит измерение показателей сети и параметров потребления котлом. Энергометр подключен по UART D7 Rx D8 Tx работающий в режиме программного серийного порта. Для возможности программирования и загрузки выход D8 включен через PNP транзистор.
Дополнительно показания температуры выводятся на LCD1602 (рис. 4), который подключен к процессору по шине I2C (D1 - I2C SDA, D2 - SCL) через преобразователь уровней.
С помощью кнопок + и — можно задать требуемую температуру в комнате. Кнопки подключены к резистивной матрице, выход которой подключено к A0.
 
В случае использования аппаратного порта UART для обмена с энергометром вместо программного - для вывода информации можно использовать D4 выход Serial1 подключаемый через отдельный переходник USB-TTL к компьютеру. Для согласования применен преобразователь логических уровней.
 
Во внутреннем EEPROM сохраняется значение установленной температуры, коррекция датчика температуры в комнате, гистерезис управления котлом по датчику температуры и коррекция датчика температуры на лице.
 
Обмен с сервером Virtuino происходит по каналу WiFi.
 
 
 
 
 
 
 
NickVectra
Offline
Зарегистрирован: 09.01.2020
Внешний вид устройства управления газовым котлом представлен на фото ниже
 
Код основной программы
// Пример https://arduinomania.in.ua/ads1115-i2c-adc-voltmetr

//Не превышать входное напряжение более чем на 0,3 В!!!

  // The ADC input range (or gain) can be changed via the following
  // functions, but be careful never to exceed VDD +0.3V max, or to
  // exceed the upper and lower limits if you adjust the input range!
  // Setting these values incorrectly may destroy your ADC!
  //                                                                 ADS1115
  //                                                                 -------
  // ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 0.1875mV (default)
  // ads.setGain(GAIN_ONE);        // 1x gain   +/- 4.096V  1 bit = 0.125mV
  // ads.setGain(GAIN_TWO);        // 2x gain   +/- 2.048V  1 bit = 0.0625mV
  // ads.setGain(GAIN_FOUR);       // 4x gain   +/- 1.024V  1 bit = 0.03125mV
  // ads.setGain(GAIN_EIGHT);      // 8x gain   +/- 0.512V  1 bit = 0.015625mV
  // ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.0078125mV

//АЦП 1
//0 канал 0 - pressure_input  V11 давление воды на входе ножки разъема №3 1-общий 2-выход 3-+5В
//1 канал 1 - temp_water_input V12 - температура воды на входе - аналоговый датчик ножки разъема №3 4-общий 5-выход
//2 канал 2 - pressure_output  V13 давление воды на выходе ножки разъема №3 1-общий 2-выход 3-+5В
//3 канал 3 - temp_water_output V14 - температура воды на выходе - аналоговый датчик ножки разъема №5 4-общий 5-выход

//АЦП 2
//4 канал 0 - V_5_supply_ADC V15 - напряжение питания +5В датчиков температуры NTC 
//5 канал 1 -  V_battery_ADC  V16 - батарея
//6 канал 2 - Holl_sensor_ADC V17 - датчик Холла
//7 канал 3 - V_Alarm_leak_Analog V18 - аналоговый выход датчика протечки

#ifndef _ADS1115_H
#define _ADS1115_H
 
#include <Adafruit_ADS1015.h>

#include "TCA9548.h"  //мультиплексор I2C

// Analog pin used to read ADC
#define pressure_input_ADC    00  //0 канал 0 АЦП1 - pressure_input  V11 давление воды на входе 
#define temp_water_input_ADC  01  //1 канал 1 АЦП1 - temp_water_input V12 - температура воды на входе 
#define pressure_output_ADC   02  //2 канал 0 АЦП1 - pressure_input  V11 давление воды на входе 
#define temp_water_output_ADC 03  //3 канал 1 АЦП1 - temp_water_input V12 - температура воды на входе 

#define V_5_supply_ADC        04 //4 канал 1 АЦП2 - V_sup V16 - напряжение питания +5В датчиков температуры NTC
#define V_battery_ADC         05 //5 канал 2 АЦП2 - батарея
#define Holl_sensor_ADC       06 //6 канал 2 АЦП2 - Holl_sensor_ADC V17 - датчик Холла
#define Alarm_ADC             07 //7 канал 3 АЦП2 - аналоговый выход датчика протечки 5 В - сухо

Adafruit_ADS1115 ads(0x48); //Здесь указываем адрес устройства

//void TCA_select(uint8_t); //external - предопределенее функции - мультиплексор I2C

float Read_ADC(uint8_t i, bool _show)
//i - номер канала АЦП 0-3 АЦП1 4-7 АЦП2
//show_in_detail - вывести значения на экран 
{  
  if(i < 4) TCA_select(Mux_ADS1115_1); //подготовка чтения через мультиплексор шины I2C первого АЦП
  else TCA_select(Mux_ADS1115_2); //подготовка чтения через мультиплексор шины I2C второго АЦП
  if(_show)
  {
    Serial.print("Канал "+(String)i+"\t");
  }
  i &= 3; //преобразуем вход, т.к. не более трех
  
  int16_t adc = ads.readADC_SingleEnded(i);// на выходе преобразования АЦП мы получаем 16-разрядное знаковое целое
  float Voltage = (adc * 0.1875)/1000.0;   //пересчитываем в привычные вольты
  if(_show)
  {
    Serial.println((String)adc+ "\t\t"+(String)Voltage+" V");
  }  
  return Voltage;
}
#endif //_ADS1115_H

 

Код программы управления газовым котлом
#include <ESP8266WiFi.h>

#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>

#include <PZEM004Tv30.h>

#include <LCD_1602_RUS.h> //Бибилиотека с русским шрифтом для шины I2C
#include <Wire.h>

LCD_1602_RUS lcd(0x3f, 16, 2); // set the LCD address to 0x3f for a 16 chars and 2 line display

ESP8266WebServer HttpServer(8080);      // Порт для входа, стандартный 80 занят для virtuino
ESP8266HTTPUpdateServer httpUpdater;

const char* ssid;
const char* password;

char Message[512]; //Массив для отправки сообщений серверу Virtuino

#include "ROM.h" //внутрення EEPORM для хранения данных коэффициентов

#include "VirtuinoCM_command.h"

#include <OneWire.h>
#define ONE_WIRE_BUS D5 // Ножка подключения датчика температуры

// https://habr.com/ru/post/470217/ - питание внутреннего датчика

OneWire  ds(ONE_WIRE_BUS); //Датчик температуры DS18B20

byte addr_ext[8] = {0x28, 0xE4, 0xF1, 0x79, 0x97, 0x11, 0x03, 0x23}; //external
byte addr_int[8] = {0x28, 0xB3, 0x87, 0x79, 0xA2, 0x00, 0x03, 0xBA}; //internal

/*
byte addr_ext[8] = {0x10, 0xB7, 0xA0, 0x59, 0x01, 0x08, 0x00, 0xDC}; //external
byte addr_int[8] = {0x28, 0x6E, 0x57, 0x79, 0xA2, 0x00, 0x03, 0x6A}; //internal
*/

#define PIN_RELAY D6 // Реле управления котлом
//#define PIN_CALIBRATE D6 // Реле калибровки - нет необходимости

int flag_P = 0; //флага начала учета мощности потребления

//HardwareSerial hwserial(UART0);     // Use hwserial UART0 at pins GPIO1 (TX) and GPIO3 (RX) занято под USB
//PZEM004Tv30 pzem(&hwserial);
PZEM004Tv30 pzem(D7, D8); //Sowtware 
/* Use software serial for the PZEM
   Pin D7 Rx (Connects to the Tx pin on the PZEM)
   Pin D8 Tx (Connects to the Rx pin on the PZEM)
*/

//описание https://www.innovatorsguru.com/pzem-004t-v3/
//https://mysku.ru/blog/china-stores/43331.html транзистор
//https://github.com/mandulaj/PZEM-004T-v30 библиотека

int errorLCD; //ошибка поска дисплея
//================================================================= setup
void setup() {
  pinMode(PIN_RELAY, OUTPUT); // Объявляем пин реле как выход
  digitalWrite(PIN_RELAY, OFF); // Выключаем реле - посылаем высокий

//  hwserial.swap();           // (optionally) swap hw_serial pins to gpio13(rx),15(tx)
  
  Serial.begin(115200); //далее используем порт Serial1 выход D4 (Tx) и отдельный переходник USB-TTL
  Serial.println();

  Wire.begin(SDA, SCL); //подготовили шину I2C D1 D2
  Wire.beginTransmission(0x3f); //Поиск дисплея
  errorLCD = Wire.endTransmission(); 

  if (errorLCD == 0) {
    lcd.init();
    lcd.setBacklight(255);
    lcd.home();
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("Загрузка");
  } 
  else {
    Serial.print(F("Error : LCD not found."));
    Serial.println(errorLCD);
  }

  EEPROM.begin(64);//Установить размер внутренней памяти для хранения первоначальных значений

  WiFi_Connect(); //найти и подсоединиться к сети WiFi
  Serial.printf("\nWiFi connected to %s IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());

  httpUpdater.setup(&HttpServer, "admin", "admin");
  HttpServer.onNotFound(handleNotFound);
  HttpServer.begin();
  Serial.print(F("Start server IP adress:"));
  Serial.print(WiFi.localIP());
  Serial.println(F(":8080/update"));

  init_variables(); //инициализация переменных Virtuino
 
  if (errorLCD == 0) { //подготовить дисплей для отображения информации
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(F("Troom= ??"));
    lcd.setCursor(0, 1);
    lcd.print(F("Tset = ??"));
  }
}
//================================================================= setup

//================================================================= handleNotFound
/* Выводить надпись, если такой страницы ненайдено */
void handleNotFound() {
  HttpServer.send(404, "text/plain", "404: Not found");
}
//================================================================= handleNotFound

#define server_cycle 5000 //период опроса датчиков, передачи и приема данных от сервера Virtuino
#define key_cycle 500 //период опроса кнопок на устройстве
//================================================================= loop
void loop() {
  static uint32_t switchTime = millis(); //таймер обращения к серверу Virtuino
  static uint32_t keyTime = millis(); //таймер опроса кнопок
  static uint32_t termoTime = millis(); //таймер опроса кнопок
  static int positionCounter = 12;  

  HttpServer.handleClient();       // Прослушивание HTTP-запросов от клиентов

  if ((unsigned long)(millis() - switchTime) > server_cycle) //запрос Virtuino каждые 5 секунд
  {
 //  Serial.println(ESP.getFreeHeap());
   read_pzem(); //запрос данных от энергометра
   dallRead(); //опрос датчиков температуры
 
    V[V_communication]++; //увеличить счетчик каждые 5 секунд
    
    //  Serial.println("Send request to Virtuino server 172.20.0.34 from loop");
    sprintf(Message, "1234!V%d=%.1f$!V%d=%.3f$!V%d=%.1f$\
                          !V%d=%.3f$!V%d=%.2f$!V%d=%.2f$\
                          !V%d=%.0f$!V%d=%.0f$!V%d=%.1f$\
                          !V%d=%.2f$!V%d=%.0f$!V%d=%.2f$\
                          !V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$",
            
            V_Voltage, V[V_Voltage], V_Current, V[V_Current], V_Power, V[V_Power],
            V_Energy, V[V_Energy], V_temp_room_heat, V[V_temp_room_heat], V_communication, V[V_communication],
            V_heat_on_off, V[V_heat_on_off], V_connection_failed, V[V_connection_failed], V_Frequency, V[V_Frequency], 
            V_PF, V[V_PF], V_error_pzem, V[V_error_pzem],V_external_temp, V[V_external_temp],
            //запрос данных
            V_Energy_reset, V_cor_temp_room_heat, V_temp_heat_delta, V_temp_heat_Virtuino, V_flag_timer, V_cor_external_temp, V_timer_temp);

    if (debug) Serial.println(Message);

    send_request("172.20.0.34", 80, Message, 800); //500 - при большем значении затруднение в обновлении по воздуху. При меньшем потери связи

//сбросить показания накопленной энергии
    if (V[V_Energy_reset] == 1) { 
      pzem.resetEnergy();
      V[V_error_pzem] = 0; //сбросить счетчик ошибок
    }  
    
//проверка режима работы таймера - отлавиливаем начало и конец
    if(V[V_flag_timer] == 1 && flag_timer == 0) { //включился таймер первый раз
      flag_timer = 1;
      old_room_temp = V[V_temp_heat_Virtuino]; //запомнить старую температуру 
      V[V_temp_heat_Virtuino] = V[V_timer_temp]; //установить новую температуру
      //передать температуру приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug) Serial.println(Message);
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
    }
    
    if(V[V_flag_timer] == 0 && flag_timer == 1) { //выключился таймер первый раз
      flag_timer = 0;
      V[V_temp_heat_Virtuino] = old_room_temp; //восстановить старую температуру 
      //передать температуру приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug){
        Serial.print("восстановить старую температуру ");
        Serial.println(V[V_temp_heat_Virtuino]);
        Serial.println(Message);
      }
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
    }

//сравнение температуры в комнате и установленной
    if (V[V_temp_heat_Virtuino] - V[V_temp_heat_delta] > V[V_temp_room_heat]) { //включить котел
      V[V_heat_on_off] = ON;
    }
    if (V[V_temp_heat_Virtuino] + V[V_temp_heat_delta] < V[V_temp_room_heat]) { //выключить котел
      V[V_heat_on_off] = OFF;
    }
    digitalWrite(PIN_RELAY, V[V_heat_on_off]);//выключить/включить котел

    change_variables(false); //изменить значение из приложения

    //вывод информации на LCD
    if (errorLCD == 0) {
      lcd.setCursor(7, 0);
      lcd.print(V[V_temp_room_heat], 2);
      lcd.setCursor(7, 1);
      lcd.print(V[V_temp_heat_Virtuino], 2);
      
      lcd.setCursor(12, 0); //индикатор связи
      if(V[V_connection_failed] != 0) lcd.print("   \\");
        else lcd.print("   Y");
    }
    switchTime = millis();
  }

  //бегущая строка индиктора отопления
  if ((unsigned long)(millis() - termoTime) > 400) 
  {
      if (positionCounter++ > 14) positionCounter = 12;
      lcd.setCursor(positionCounter, 1); //индикатор включения отопления
      if (V[V_heat_on_off] == ON) lcd.print(" > ");
      else lcd.print("    ");
      termoTime = millis();
  }
  
  //обработка нажатия клавиш на устройстве
  if ((unsigned long)(millis() - keyTime) > key_cycle) //запрос каждую 0.5 секунды
  {
    float keyValue = key();
    //     Serial.println(keyValue);
    if (keyValue != 0) //опрос кнопки
    {
      V[V_temp_heat_Virtuino] +=  keyValue; //изменить значение и передать приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug) Serial.println(Message);
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
      lcd.setCursor(7, 1);
      lcd.print(V[V_temp_heat_Virtuino], 2);
    }
    keyTime = millis();
  }

}
//================================================================= loop

//================================================================= key
float key() {
  int val = analogRead(0); // считываем значение с аналогового входа и записываем в переменную val
  if (val < 50) return -0.1; // сверяем переменную, если val меньше 50 возвращаем -1 (левая кнопка)
  else if (val > 450) return 0.1; // если val больше 600 правая кнопка)возвращаем 1
  else return 0;
}
//================================================================= key

//================================================================= read_pzem
void read_pzem()
{
  float voltage = pzem.voltage();
  if ( !isnan(voltage) ) {
    //        Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
    V[V_Voltage] = voltage;
  } else {
    Serial.println("Error reading voltage");
    V[V_error_pzem]++;
  }

  float current = pzem.current();
  if ( !isnan(current) ) {
    //        Serial.print("Current: "); Serial.print(current); Serial.println("A");
    V[V_Current] = current;
  } else {
    Serial.println("Error reading current");
    V[V_error_pzem]++;
  }

  float power = pzem.power();
  if ( !isnan(power) ) {
    //        Serial.print("Power: "); Serial.print(power); Serial.println("W");
    V[V_Power] = power;
  } else {
    Serial.println("Error reading power");
    V[V_error_pzem]++;
  }

  float energy = pzem.energy();
  if ( !isnan(energy) ) {
    //        Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
    V[V_Energy] = energy;
  } else {
    Serial.println("Error reading energy");
    V[V_error_pzem]++;
  }

  float frequency = pzem.frequency();
  if ( !isnan(frequency) ) {
    //        Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
    V[V_Frequency] = frequency;
  } else {
    Serial.println("Error reading frequency");
    V[V_error_pzem]++;
  }

  float pf = pzem.pf();
  if ( !isnan(pf) ) {
    //        Serial.print("PF: "); Serial.println(pf);
    V[V_PF] = pf;
  } else {
    Serial.println("Error reading power factor");
    V[V_error_pzem]++;
  }

  //    Serial.println();
}
//================================================================= read_pzem

//================================================================= dallRead
void dallRead()
{
  ds.reset();
  ds.select(addr_int);
  ds.write(0xBE); //Считывание значения с датчика
  int16_t temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры 16-разрядное целое число со знаком
  V[V_temp_room_heat] = (float)temp * 0.0625 + V[V_cor_temp_room_heat];

  ds.reset();
  ds.select(addr_ext);
  ds.write(0xBE); //Считывание значения с датчика
  temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры

//int16_t raw = (data[1] << 8) | data[0];
//float ft = (int16_t) ((MSB << 8) | LSB);
/*
  if (temp & 0x8000) // Знак значения если значение меньше нуля
  {
    temp = (temp ^ 0xffff) + 1;  // Инвертируем биты, если знак отрицательный и прибавляем единицу
    temp = 0 - temp; // Make negative
  }
*/  
  V[V_external_temp] = temp* 0.0625+V[V_cor_external_temp]; //Умножаем на 0.0625

  ds.reset();
  ds.write(0xCC); //Обращение ко всем датчикам
  ds.write(0x44, 1); //Команда на конвертацию
}
//================================================================= dallRead


//================================================================= find_wifi
bool find_wifi()
{
  //поиск известной сети. Если не найден продолжить поиск

  uint8_t n = WiFi.scanNetworks();

  Serial.println();
  if (n == 0)
  {
    Serial.println(F("No networks found"));
    return false;
  }
  else
  {
    Serial.print(n);
    Serial.println(F(" networks found :"));
    for (uint8_t i = 0; i < n; ++i)
    {
      Serial.println(WiFi.SSID(i));
      if (WiFi.SSID(i) == "******1")
      {
        ssid = "******1";
        password = "******";
        return true;
      }
      if (WiFi.SSID(i) == "******2")
      {
        ssid = "******2";
        password = "87654321";
        IPAddress ip(10, 0, 1, 208);
        IPAddress gateway(10, 0, 1, 2);
        IPAddress subnet(255, 255, 255, 0);
        IPAddress dns(8, 8, 8, 8);
        WiFi.config(ip, dns, gateway, subnet);          // If you don't want to config IP manually disable this line
        return true;
      }
    }
  }
  return false;
}
//=================================================================  find_wifi


//================================================================= WiFi_Connect
void WiFi_Connect()
{
  //просканируем на изветные сети, чтобы подключить автоматически
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  while (!find_wifi())
  { //поиск известной сети. Если не найден продолжить поиск
    Serial.println(F("\nTrying to find known WiFi network"));
  }

  //подключимся к знакомой сети
  Serial.println("Connecting to " + String(ssid));
  //  WiFi.mode(WIFI_STA);                       // Конфигурация как клиет WiFi
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
//  Serial.print(F("\nWiFi connected. Local IP "));
//  Serial.println(WiFi.localIP());
}
//================================================================= WiFi_Connect

 

 

Ссылки на остальные тексты программ
https://drive.google.com/open?id=13i1B7 … nIwDIfkgXI

https://drive.google.com/open?id=1jvYK4 … dc-yMX0mTX

Решил опубликовать - может кто-то найдет что-то для себя полезное.
Сильно не ругайте - если случайно нарушил правила форума.
Всем удачи

 

 
 
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Тоже хочу :(

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

 Монтаж - жесть(опять рука за молотком тянется :)

NickVectra
Offline
Зарегистрирован: 09.01.2020
Учитывая то, что устройство управления газовым котлом выполнено на NodeMCU (процессор имеет достаточно мощности) и  эксплуатация в течении двух недель показали целесообразность построить отдельный сервер Virtuino на этом процессоре. 
Благодаря этому скорость реакции на нажатие клавиш в приложении увеличилась, что сделало систему более информативной и дружественной. Также это было возможным так, как приложение Virtuino может одновременно быть подключенным к нескольким серверам (один виртуальный и два реальных). 
Новый скетч для устройства управления газовым котлом приведен ниже.
inspiritus
Offline
Зарегистрирован: 17.12.2012

Нодэмцу не подвисает?

NickVectra
Offline
Зарегистрирован: 09.01.2020

Я выше писал

При компилировании программы в среде Arduino IDE сделаны дополнительные следующие установки: Flash size 4M(3M SPIFFS) и IwIP Variant v1.4 Higher Bandwidth. Без этого иногда вылетала с ошибкой extension.

Для контроля  перегрузки каждый раз сохраняю номер запуска и передаю его по e-mail. После последнего моего перезапуска работает 14 дней.

Считаю это стабильная работа