Система управление резервом воды и котлом отопления в квартире.
- Войдите на сайт для отправки комментариев
Чт, 09/01/2020 - 18:49
Добрый день!
Решил в квартире сделать систему управления резервом воды и газовым котлом как элемент умного дома.
Вот, что из этого получилось:
Два резервных бака с водой суммарным объемом 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.










01
// Пример <a href="https://arduinomania.in.ua/ads1115-i2c-adc-voltmetr" title="https://arduinomania.in.ua/ads1115-i2c-adc-voltmetr" rel="nofollow">https://arduinomania.in.ua/ads1115-i2c-adc-voltmetr</a>
02
03
//Не превышать входное напряжение более чем на 0,3 В!!!
04
05
// The ADC input range (or gain) can be changed via the following
06
// functions, but be careful never to exceed VDD +0.3V max, or to
07
// exceed the upper and lower limits if you adjust the input range!
08
// Setting these values incorrectly may destroy your ADC!
09
// ADS1115
10
// -------
11
// ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 0.1875mV (default)
12
// ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 0.125mV
13
// ads.setGain(GAIN_TWO); // 2x gain +/- 2.048V 1 bit = 0.0625mV
14
// ads.setGain(GAIN_FOUR); // 4x gain +/- 1.024V 1 bit = 0.03125mV
15
// ads.setGain(GAIN_EIGHT); // 8x gain +/- 0.512V 1 bit = 0.015625mV
16
// ads.setGain(GAIN_SIXTEEN); // 16x gain +/- 0.256V 1 bit = 0.0078125mV
17
18
//АЦП 1
19
//0 канал 0 - pressure_input V11 давление воды на входе ножки разъема №3 1-общий 2-выход 3-+5В
20
//1 канал 1 - temp_water_input V12 - температура воды на входе - аналоговый датчик ножки разъема №3 4-общий 5-выход
21
//2 канал 2 - pressure_output V13 давление воды на выходе ножки разъема №3 1-общий 2-выход 3-+5В
22
//3 канал 3 - temp_water_output V14 - температура воды на выходе - аналоговый датчик ножки разъема №5 4-общий 5-выход
23
24
//АЦП 2
25
//4 канал 0 - V_5_supply_ADC V15 - напряжение питания +5В датчиков температуры NTC
26
//5 канал 1 - V_battery_ADC V16 - батарея
27
//6 канал 2 - Holl_sensor_ADC V17 - датчик Холла
28
//7 канал 3 - V_Alarm_leak_Analog V18 - аналоговый выход датчика протечки
29
30
#ifndef _ADS1115_H
31
#define _ADS1115_H
32
33
#include <Adafruit_ADS1015.h>
34
35
#include "TCA9548.h" //мультиплексор I2C
36
37
// Analog pin used to read ADC
38
#define pressure_input_ADC 00 //0 канал 0 АЦП1 - pressure_input V11 давление воды на входе
39
#define temp_water_input_ADC 01 //1 канал 1 АЦП1 - temp_water_input V12 - температура воды на входе
40
#define pressure_output_ADC 02 //2 канал 0 АЦП1 - pressure_input V11 давление воды на входе
41
#define temp_water_output_ADC 03 //3 канал 1 АЦП1 - temp_water_input V12 - температура воды на входе
42
43
#define V_5_supply_ADC 04 //4 канал 1 АЦП2 - V_sup V16 - напряжение питания +5В датчиков температуры NTC
44
#define V_battery_ADC 05 //5 канал 2 АЦП2 - батарея
45
#define Holl_sensor_ADC 06 //6 канал 2 АЦП2 - Holl_sensor_ADC V17 - датчик Холла
46
#define Alarm_ADC 07 //7 канал 3 АЦП2 - аналоговый выход датчика протечки 5 В - сухо
47
48
Adafruit_ADS1115 ads(0x48);
//Здесь указываем адрес устройства
49
50
//void TCA_select(uint8_t); //external - предопределенее функции - мультиплексор I2C
51
52
float
Read_ADC(uint8_t i,
bool
_show)
53
//i - номер канала АЦП 0-3 АЦП1 4-7 АЦП2
54
//show_in_detail - вывести значения на экран
55
{
56
if
(i < 4) TCA_select(Mux_ADS1115_1);
//подготовка чтения через мультиплексор шины I2C первого АЦП
57
else
TCA_select(Mux_ADS1115_2);
//подготовка чтения через мультиплексор шины I2C второго АЦП
58
if
(_show)
59
{
60
Serial
.print(
"Канал "
+(String)i+
"\t"
);
61
}
62
i &= 3;
//преобразуем вход, т.к. не более трех
63
64
int16_t adc = ads.readADC_SingleEnded(i);
// на выходе преобразования АЦП мы получаем 16-разрядное знаковое целое
65
float
Voltage = (adc * 0.1875)/1000.0;
//пересчитываем в привычные вольты
66
if
(_show)
67
{
68
Serial
.println((String)adc+
"\t\t"
+(String)Voltage+
" V"
);
69
}
70
return
Voltage;
71
}
72
#endif //_ADS1115_H
001
#include <ESP8266WiFi.h>
002
003
#include <ESP8266WebServer.h>
004
#include <ESP8266HTTPUpdateServer.h>
005
006
#include <PZEM004Tv30.h>
007
008
#include <LCD_1602_RUS.h> //Бибилиотека с русским шрифтом для шины I2C
009
#include <Wire.h>
010
011
LCD_1602_RUS lcd(0x3f, 16, 2);
// set the LCD address to 0x3f for a 16 chars and 2 line display
012
013
ESP8266WebServer HttpServer(8080);
// Порт для входа, стандартный 80 занят для virtuino
014
ESP8266HTTPUpdateServer httpUpdater;
015
016
const
char
* ssid;
017
const
char
* password;
018
019
char
Message[512];
//Массив для отправки сообщений серверу Virtuino
020
021
#include "ROM.h" //внутрення EEPORM для хранения данных коэффициентов
022
023
#include "VirtuinoCM_command.h"
024
025
#include <OneWire.h>
026
#define ONE_WIRE_BUS D5 // Ножка подключения датчика температуры
027
028
// <a href="https://habr.com/ru/post/470217/" title="https://habr.com/ru/post/470217/" rel="nofollow">https://habr.com/ru/post/470217/</a> - питание внутреннего датчика
029
030
OneWire ds(ONE_WIRE_BUS);
//Датчик температуры DS18B20
031
032
byte
addr_ext[8] = {0x28, 0xE4, 0xF1, 0x79, 0x97, 0x11, 0x03, 0x23};
//external
033
byte
addr_int[8] = {0x28, 0xB3, 0x87, 0x79, 0xA2, 0x00, 0x03, 0xBA};
//internal
034
035
/*
036
byte addr_ext[8] = {0x10, 0xB7, 0xA0, 0x59, 0x01, 0x08, 0x00, 0xDC}; //external
037
byte addr_int[8] = {0x28, 0x6E, 0x57, 0x79, 0xA2, 0x00, 0x03, 0x6A}; //internal
038
*/
039
040
#define PIN_RELAY D6 // Реле управления котлом
041
//#define PIN_CALIBRATE D6 // Реле калибровки - нет необходимости
042
043
int
flag_P = 0;
//флага начала учета мощности потребления
044
045
//HardwareSerial hwserial(UART0); // Use hwserial UART0 at pins GPIO1 (TX) and GPIO3 (RX) занято под USB
046
//PZEM004Tv30 pzem(&hwserial);
047
PZEM004Tv30 pzem(D7, D8);
//Sowtware
048
/* Use software serial for the PZEM
049
Pin D7 Rx (Connects to the Tx pin on the PZEM)
050
Pin D8 Tx (Connects to the Rx pin on the PZEM)
051
*/
052
053
//описание <a href="https://www.innovatorsguru.com/pzem-004t-v3/" title="https://www.innovatorsguru.com/pzem-004t-v3/" rel="nofollow">https://www.innovatorsguru.com/pzem-004t-v3/</a>
054
//<a href="https://mysku.ru/blog/china-stores/43331.html" rel="nofollow">https://mysku.ru/blog/china-stores/43331.html</a> транзистор
055
//<a href="https://github.com/mandulaj/PZEM-004T-v30" rel="nofollow">https://github.com/mandulaj/PZEM-004T-v30</a> библиотека
056
057
int
errorLCD;
//ошибка поска дисплея
058
//================================================================= setup
059
void
setup
() {
060
pinMode(PIN_RELAY, OUTPUT);
// Объявляем пин реле как выход
061
digitalWrite(PIN_RELAY, OFF);
// Выключаем реле - посылаем высокий
062
063
// hwserial.swap(); // (optionally) swap hw_serial pins to gpio13(rx),15(tx)
064
065
Serial
.begin(115200);
//далее используем порт Serial1 выход D4 (Tx) и отдельный переходник USB-TTL
066
Serial
.println();
067
068
Wire.begin(SDA, SCL);
//подготовили шину I2C D1 D2
069
Wire.beginTransmission(0x3f);
//Поиск дисплея
070
errorLCD = Wire.endTransmission();
071
072
if
(errorLCD == 0) {
073
lcd.init();
074
lcd.setBacklight(255);
075
lcd.home();
076
lcd.clear();
077
lcd.setCursor(4, 0);
078
lcd.print(
"Загрузка"
);
079
}
080
else
{
081
Serial
.print(F(
"Error : LCD not found."
));
082
Serial
.println(errorLCD);
083
}
084
085
EEPROM.begin(64);
//Установить размер внутренней памяти для хранения первоначальных значений
086
087
WiFi_Connect();
//найти и подсоединиться к сети WiFi
088
Serial
.printf(
"\nWiFi connected to %s IP address: %s\n"
, WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
089
090
httpUpdater.
setup
(&HttpServer,
"admin"
,
"admin"
);
091
HttpServer.onNotFound(handleNotFound);
092
HttpServer.begin();
093
Serial
.print(F(
"Start server IP adress:"
));
094
Serial
.print(WiFi.localIP());
095
Serial
.println(F(
":8080/update"
));
096
097
init_variables();
//инициализация переменных Virtuino
098
099
if
(errorLCD == 0) {
//подготовить дисплей для отображения информации
100
lcd.clear();
101
lcd.setCursor(0, 0);
102
lcd.print(F(
"Troom= ??"
));
103
lcd.setCursor(0, 1);
104
lcd.print(F(
"Tset = ??"
));
105
}
106
}
107
//================================================================= setup
108
109
//================================================================= handleNotFound
110
/* Выводить надпись, если такой страницы ненайдено */
111
void
handleNotFound() {
112
HttpServer.send(404,
"text/plain"
,
"404: Not found"
);
113
}
114
//================================================================= handleNotFound
115
116
#define server_cycle 5000 //период опроса датчиков, передачи и приема данных от сервера Virtuino
117
#define key_cycle 500 //период опроса кнопок на устройстве
118
//================================================================= loop
119
void
loop
() {
120
static
uint32_t switchTime = millis();
//таймер обращения к серверу Virtuino
121
static
uint32_t keyTime = millis();
//таймер опроса кнопок
122
static
uint32_t termoTime = millis();
//таймер опроса кнопок
123
static
int
positionCounter = 12;
124
125
HttpServer.handleClient();
// Прослушивание HTTP-запросов от клиентов
126
127
if
((unsigned
long
)(millis() - switchTime) > server_cycle)
//запрос Virtuino каждые 5 секунд
128
{
129
// Serial.println(ESP.getFreeHeap());
130
read_pzem();
//запрос данных от энергометра
131
dallRead();
//опрос датчиков температуры
132
133
V[V_communication]++;
//увеличить счетчик каждые 5 секунд
134
135
// Serial.println("Send request to Virtuino server 172.20.0.34 from loop");
136
sprintf(Message, "1234!V%d=%.1f$!V%d=%.3f$!V%d=%.1f$\
137
!V%d=%.3f$!V%d=%.2f$!V%d=%.2f$\
138
!V%d=%.0f$!V%d=%.0f$!V%d=%.1f$\
139
!V%d=%.2f$!V%d=%.0f$!V%d=%.2f$\
140
!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$",
141
142
V_Voltage, V[V_Voltage], V_Current, V[V_Current], V_Power, V[V_Power],
143
V_Energy, V[V_Energy], V_temp_room_heat, V[V_temp_room_heat], V_communication, V[V_communication],
144
V_heat_on_off, V[V_heat_on_off], V_connection_failed, V[V_connection_failed], V_Frequency, V[V_Frequency],
145
V_PF, V[V_PF], V_error_pzem, V[V_error_pzem],V_external_temp, V[V_external_temp],
146
//запрос данных
147
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);
148
149
if
(debug)
Serial
.println(Message);
150
151
send_request(
"172.20.0.34"
, 80, Message, 800);
//500 - при большем значении затруднение в обновлении по воздуху. При меньшем потери связи
152
153
//сбросить показания накопленной энергии
154
if
(V[V_Energy_reset] == 1) {
155
pzem.resetEnergy();
156
V[V_error_pzem] = 0;
//сбросить счетчик ошибок
157
}
158
159
//проверка режима работы таймера - отлавиливаем начало и конец
160
if
(V[V_flag_timer] == 1 && flag_timer == 0) {
//включился таймер первый раз
161
flag_timer = 1;
162
old_room_temp = V[V_temp_heat_Virtuino];
//запомнить старую температуру
163
V[V_temp_heat_Virtuino] = V[V_timer_temp];
//установить новую температуру
164
//передать температуру приложению
165
sprintf(Message,
"1234!V%d=%.1f$"
, V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
166
if
(debug)
Serial
.println(Message);
167
send_request(
"172.20.0.34"
, 80, Message, 200);
//200 только передача
168
}
169
170
if
(V[V_flag_timer] == 0 && flag_timer == 1) {
//выключился таймер первый раз
171
flag_timer = 0;
172
V[V_temp_heat_Virtuino] = old_room_temp;
//восстановить старую температуру
173
//передать температуру приложению
174
sprintf(Message,
"1234!V%d=%.1f$"
, V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
175
if
(debug){
176
Serial
.print(
"восстановить старую температуру "
);
177
Serial
.println(V[V_temp_heat_Virtuino]);
178
Serial
.println(Message);
179
}
180
send_request(
"172.20.0.34"
, 80, Message, 200);
//200 только передача
181
}
182
183
//сравнение температуры в комнате и установленной
184
if
(V[V_temp_heat_Virtuino] - V[V_temp_heat_delta] > V[V_temp_room_heat]) {
//включить котел
185
V[V_heat_on_off] = ON;
186
}
187
if
(V[V_temp_heat_Virtuino] + V[V_temp_heat_delta] < V[V_temp_room_heat]) {
//выключить котел
188
V[V_heat_on_off] = OFF;
189
}
190
digitalWrite(PIN_RELAY, V[V_heat_on_off]);
//выключить/включить котел
191
192
change_variables(
false
);
//изменить значение из приложения
193
194
//вывод информации на LCD
195
if
(errorLCD == 0) {
196
lcd.setCursor(7, 0);
197
lcd.print(V[V_temp_room_heat], 2);
198
lcd.setCursor(7, 1);
199
lcd.print(V[V_temp_heat_Virtuino], 2);
200
201
lcd.setCursor(12, 0);
//индикатор связи
202
if
(V[V_connection_failed] != 0) lcd.print(
" \\"
);
203
else
lcd.print(
" Y"
);
204
}
205
switchTime = millis();
206
}
207
208
//бегущая строка индиктора отопления
209
if
((unsigned
long
)(millis() - termoTime) > 400)
210
{
211
if
(positionCounter++ > 14) positionCounter = 12;
212
lcd.setCursor(positionCounter, 1);
//индикатор включения отопления
213
if
(V[V_heat_on_off] == ON) lcd.print(
" > "
);
214
else
lcd.print(
" "
);
215
termoTime = millis();
216
}
217
218
//обработка нажатия клавиш на устройстве
219
if
((unsigned
long
)(millis() - keyTime) > key_cycle)
//запрос каждую 0.5 секунды
220
{
221
float
keyValue = key();
222
// Serial.println(keyValue);
223
if
(keyValue != 0)
//опрос кнопки
224
{
225
V[V_temp_heat_Virtuino] += keyValue;
//изменить значение и передать приложению
226
sprintf(Message,
"1234!V%d=%.1f$"
, V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
227
if
(debug)
Serial
.println(Message);
228
send_request(
"172.20.0.34"
, 80, Message, 200);
//200 только передача
229
lcd.setCursor(7, 1);
230
lcd.print(V[V_temp_heat_Virtuino], 2);
231
}
232
keyTime = millis();
233
}
234
235
}
236
//================================================================= loop
237
238
//================================================================= key
239
float
key() {
240
int
val = analogRead(0);
// считываем значение с аналогового входа и записываем в переменную val
241
if
(val < 50)
return
-0.1;
// сверяем переменную, если val меньше 50 возвращаем -1 (левая кнопка)
242
else
if
(val > 450)
return
0.1;
// если val больше 600 правая кнопка)возвращаем 1
243
else
return
0;
244
}
245
//================================================================= key
246
247
//================================================================= read_pzem
248
void
read_pzem()
249
{
250
float
voltage = pzem.voltage();
251
if
( !isnan(voltage) ) {
252
// Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
253
V[V_Voltage] = voltage;
254
}
else
{
255
Serial
.println(
"Error reading voltage"
);
256
V[V_error_pzem]++;
257
}
258
259
float
current = pzem.current();
260
if
( !isnan(current) ) {
261
// Serial.print("Current: "); Serial.print(current); Serial.println("A");
262
V[V_Current] = current;
263
}
else
{
264
Serial
.println(
"Error reading current"
);
265
V[V_error_pzem]++;
266
}
267
268
float
power = pzem.power();
269
if
( !isnan(power) ) {
270
// Serial.print("Power: "); Serial.print(power); Serial.println("W");
271
V[V_Power] = power;
272
}
else
{
273
Serial
.println(
"Error reading power"
);
274
V[V_error_pzem]++;
275
}
276
277
float
energy = pzem.energy();
278
if
( !isnan(energy) ) {
279
// Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
280
V[V_Energy] = energy;
281
}
else
{
282
Serial
.println(
"Error reading energy"
);
283
V[V_error_pzem]++;
284
}
285
286
float
frequency = pzem.frequency();
287
if
( !isnan(frequency) ) {
288
// Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
289
V[V_Frequency] = frequency;
290
}
else
{
291
Serial
.println(
"Error reading frequency"
);
292
V[V_error_pzem]++;
293
}
294
295
float
pf = pzem.pf();
296
if
( !isnan(pf) ) {
297
// Serial.print("PF: "); Serial.println(pf);
298
V[V_PF] = pf;
299
}
else
{
300
Serial
.println(
"Error reading power factor"
);
301
V[V_error_pzem]++;
302
}
303
304
// Serial.println();
305
}
306
//================================================================= read_pzem
307
308
//================================================================= dallRead
309
void
dallRead()
310
{
311
ds.reset();
312
ds.select(addr_int);
313
ds.write(0xBE);
//Считывание значения с датчика
314
int16_t temp = (ds.read() | ds.read() << 8);
//Принимаем два байта температуры 16-разрядное целое число со знаком
315
V[V_temp_room_heat] = (
float
)temp * 0.0625 + V[V_cor_temp_room_heat];
316
317
ds.reset();
318
ds.select(addr_ext);
319
ds.write(0xBE);
//Считывание значения с датчика
320
temp = (ds.read() | ds.read() << 8);
//Принимаем два байта температуры
321
322
//int16_t raw = (data[1] << 8) | data[0];
323
//float ft = (int16_t) ((MSB << 8) | LSB);
324
/*
325
if (temp & 0x8000) // Знак значения если значение меньше нуля
326
{
327
temp = (temp ^ 0xffff) + 1; // Инвертируем биты, если знак отрицательный и прибавляем единицу
328
temp = 0 - temp; // Make negative
329
}
330
*/
331
V[V_external_temp] = temp* 0.0625+V[V_cor_external_temp];
//Умножаем на 0.0625
332
333
ds.reset();
334
ds.write(0xCC);
//Обращение ко всем датчикам
335
ds.write(0x44, 1);
//Команда на конвертацию
336
}
337
//================================================================= dallRead
338
339
340
//================================================================= find_wifi
341
bool
find_wifi()
342
{
343
//поиск известной сети. Если не найден продолжить поиск
344
345
uint8_t n = WiFi.scanNetworks();
346
347
Serial
.println();
348
if
(n == 0)
349
{
350
Serial
.println(F(
"No networks found"
));
351
return
false
;
352
}
353
else
354
{
355
Serial
.print(n);
356
Serial
.println(F(
" networks found :"
));
357
for
(uint8_t i = 0; i < n; ++i)
358
{
359
Serial
.println(WiFi.SSID(i));
360
if
(WiFi.SSID(i) ==
"******1"
)
361
{
362
ssid =
"******1"
;
363
password =
"******"
;
364
return
true
;
365
}
366
if
(WiFi.SSID(i) ==
"******2"
)
367
{
368
ssid =
"******2"
;
369
password =
"87654321"
;
370
IPAddress ip(10, 0, 1, 208);
371
IPAddress gateway(10, 0, 1, 2);
372
IPAddress subnet(255, 255, 255, 0);
373
IPAddress dns(8, 8, 8, 8);
374
WiFi.config(ip, dns, gateway, subnet);
// If you don't want to config IP manually disable this line
375
return
true
;
376
}
377
}
378
}
379
return
false
;
380
}
381
//================================================================= find_wifi
382
383
384
//================================================================= WiFi_Connect
385
void
WiFi_Connect()
386
{
387
//просканируем на изветные сети, чтобы подключить автоматически
388
WiFi.mode(WIFI_STA);
389
WiFi.disconnect();
390
delay(100);
391
while
(!find_wifi())
392
{
//поиск известной сети. Если не найден продолжить поиск
393
Serial
.println(F(
"\nTrying to find known WiFi network"
));
394
}
395
396
//подключимся к знакомой сети
397
Serial
.println(
"Connecting to "
+ String(ssid));
398
// WiFi.mode(WIFI_STA); // Конфигурация как клиет WiFi
399
WiFi.begin(ssid, password);
400
401
while
(WiFi.status() != WL_CONNECTED) {
402
delay(500);
403
Serial
.print(
"."
);
404
}
405
// Serial.print(F("\nWiFi connected. Local IP "));
406
// Serial.println(WiFi.localIP());
407
}
408
//================================================================= WiFi_Connect
Ссылки на остальные тексты программ
https://drive.google.com/open?id=13i1B7 … nIwDIfkgXI
https://drive.google.com/open?id=1jvYK4 … dc-yMX0mTX
Решил опубликовать - может кто-то найдет что-то для себя полезное.
Сильно не ругайте - если случайно нарушил правила форума.
Всем удачи
Тоже хочу :(
Монтаж - жесть(опять рука за молотком тянется :)
Нодэмцу не подвисает?
Я выше писал
При компилировании программы в среде Arduino IDE сделаны дополнительные следующие установки: Flash size 4M(3M SPIFFS) и IwIP Variant v1.4 Higher Bandwidth. Без этого иногда вылетала с ошибкой extension.
Для контроля перегрузки каждый раз сохраняю номер запуска и передаю его по e-mail. После последнего моего перезапуска работает 14 дней.
Считаю это стабильная работа
монтаж конечно жесть, но идея и реализация супер!
Ещё одна идея по считывания данных со счетчика представлена в теме "Очередная система считывания данных счетчика". Монтаж чуть аккуратнее кажется. Несколько фото