mirror of
https://github.com/anklimov/lighthub
synced 2026-05-02 04:05:10 +00:00
Compare commits
11 Commits
b888f1a521
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 47c2fd8a74 | |||
| 04d709a1e8 | |||
| e42053ab1b | |||
| 84b685c564 | |||
| bbceb044c1 | |||
| b5ef7ff221 | |||
| 00969f88a4 | |||
| efbda54c01 | |||
| 56cb09ee7c | |||
| e2dded5eb3 | |||
|
|
9ac145a454 |
@@ -1,5 +1,5 @@
|
|||||||
# LightHub
|
# LightHub
|
||||||
is Flexible, Arduino-Mega/Arduino DUE/ESP8266/ESP32 open-software and open-hardware SmartHome controller.
|
LightHub is a Flexible open-software and open-hardware Smart Home controller for Arduino-Mega, Arduino Due, ESP8266, and ESP32.
|
||||||
Useful links:
|
Useful links:
|
||||||
|
|
||||||
* [Article/RU](https://geektimes.ru/post/295109/)
|
* [Article/RU](https://geektimes.ru/post/295109/)
|
||||||
@@ -8,7 +8,7 @@ Useful links:
|
|||||||
|
|
||||||
* [WIKI/RU](https://www.lazyhome.ru/dokuwiki/doku.php?id=start)
|
* [WIKI/RU](https://www.lazyhome.ru/dokuwiki/doku.php?id=start)
|
||||||
|
|
||||||
* [Doxigen autodocumentation for developers](https://anklimov.github.io/lighthub/docs/html/index.html) (litle bit outdated)
|
* [Doxygen autodocumentation for developers](https://anklimov.github.io/lighthub/docs/html/index.html) (litle bit outdated)
|
||||||
|
|
||||||
It may operate both:
|
It may operate both:
|
||||||
* On [especially designed hardware board](http://www.lazyhome.ru/index.php/featurerequest) with 16 optocoupled digital inputs, 16 ESD protected digital/analog Inputs/outputs, 8 open-collector outputs (up to 0.5A/50V), DMX IN/OUT, MODBUS RTU and hardware 1-wire support circuit.
|
* On [especially designed hardware board](http://www.lazyhome.ru/index.php/featurerequest) with 16 optocoupled digital inputs, 16 ESD protected digital/analog Inputs/outputs, 8 open-collector outputs (up to 0.5A/50V), DMX IN/OUT, MODBUS RTU and hardware 1-wire support circuit.
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
-DRESTART_LAN_ON_MQTT_ERRORS
|
-DRESTART_LAN_ON_MQTT_ERRORS
|
||||||
-D CORS=\"*\"
|
-D CORS=\"*\"
|
||||||
-D REDIRECTION_URL=\"http://lazyhome.ru/pwa\"
|
#-D REDIRECTION_URL=\"http://lazyhome.ru/pwa\"
|
||||||
|
-D REDIRECTION_URL=lazyhome.ru/pwa
|
||||||
-DOTA_PORT=80
|
-DOTA_PORT=80
|
||||||
|
|
||||||
#oct22 - violation in Publish/OnMQTTConnect while publish homie info
|
#oct22 - violation in Publish/OnMQTTConnect while publish homie info
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
-DSYSLOG_ENABLE
|
-DSYSLOG_ENABLE
|
||||||
-DSTATUSLED
|
-DSTATUSLED
|
||||||
-DMCP23017
|
-DMCP23017
|
||||||
#-DPID_DISABLE
|
|
||||||
-DARDUINO_OTA_MDNS_DISABLE
|
-DARDUINO_OTA_MDNS_DISABLE
|
||||||
-DMDNS_ENABLE
|
-DMDNS_ENABLE
|
||||||
-DTIMER_INT
|
-DTIMER_INT
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
#-D CORS=\"http://lazyhome.ru\"
|
#-D CORS=\"http://lazyhome.ru\"
|
||||||
-DOTA_PORT=80
|
-DOTA_PORT=80
|
||||||
-D CORS=\"*\"
|
-D CORS=\"*\"
|
||||||
-D REDIRECTION_URL=\"http://lazyhome.ru/pwa\"
|
-D REDIRECTION_URL=lazyhome.ru/pwa
|
||||||
-D MERCURY_ENABLE
|
-D MERCURY_ENABLE
|
||||||
#-D IPMODBUS
|
#-D IPMODBUS
|
||||||
-D CONFIG_CLEAN_PIN=2
|
-D CONFIG_CLEAN_PIN=2
|
||||||
|
|||||||
Binary file not shown.
181
documentation/Sprinkler_module.md
Normal file
181
documentation/Sprinkler_module.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Данный модуль реализует многозональную систему полива
|
||||||
|
|
||||||
|
## Система состоит из следующих компонент:
|
||||||
|
|
||||||
|
* Накопительный водяной бак. Снабжен двумя поплавками. Максимум воды и минимум воды. Заведены на входы wMax и wMin
|
||||||
|
* Насос полива высокого давления. Запитан из бака. Включается реле, подключенным к выходу rPump. Датчик тока для контроля того, что насос включен, заведен на вход fbPump
|
||||||
|
* Набор клапанов зон полива. Подключены через оптореле к выходам, заданным в параметре pin соответствующей зоны полива.
|
||||||
|
* Клапан налива из водопровода. Подключен через оптореле к выходу vIn
|
||||||
|
* Насос дренажного колодца. Приоритетный источник для наполнения бака полива. Когда система полива находится в ждущем или активном режиме, бак пытается максимально наполнится из дренажного колодца. Насос дренажа имеет поплавковый выключатель, отключающий насос при осушении дренажного колодца. Насос включается реле, которое подключено к выходу rDren. Для контроля того, что насос включен и момента осушения колодца, используется датчик тока, который подключен в входу fbDren
|
||||||
|
* Опциональный водосчетчик. Контакты подключены к входу wCtr
|
||||||
|
|
||||||
|
## Система налива воды реализована при помощи конечного автомата со следующими состояниями:
|
||||||
|
|
||||||
|
* SP\_UNKNOWN
|
||||||
|
* SP\_OFF
|
||||||
|
* SP\_DREN\_ON - дренажный насос включен
|
||||||
|
* SP\_DREN\_OPERATE - дренажный насос работает
|
||||||
|
* SP\_DREN\_EMPTY - дренажный насос выключился встроенным поплавком - колодец пуст
|
||||||
|
* SP\_VIN - включено наполнение из водопровода
|
||||||
|
* SP\_FULL - бак наполнен
|
||||||
|
|
||||||
|
граф переходов конечного автомата системы налива воды
|
||||||
|
|
||||||
|
Состояние SP\_* | Условие перехода | перейти в состояние | выполнить при переходе |
|
||||||
|
|------|--------|-------|---------|
|
||||||
|
INIT | true | OFF | выключить клапана и насосы |
|
||||||
|
OFF | vMax && !FREEZE | FULL | выключить vIN, rDren|
|
||||||
|
OFF | ! vMax && !FREEZE | DREN\_ON|включить rDren|
|
||||||
|
DREN\_ON|fbDren (насос дренажа реально работает)|DREN\_OPERATE|
|
||||||
|
DREN\_ON|таймаут 10 сек|DREN\_EMPTY|
|
||||||
|
DREN\_EMPTY|включен цикл полива и бак не полон|VIN|включить клапан vIN для набора бака из водопровода|
|
||||||
|
DREN\_OPERATE|fbDren (насос дренажа более не работает)|DREN\_EMPTY||
|
||||||
|
VIN|fbDren|DREN\_OPERATE|вылючить клапан vIN для набора бака из водопровода|
|
||||||
|
VIN, DREN\_OPERATE|vMax|FULL | выключить vIN, rDren|
|
||||||
|
VIN|таймаут 1200 сек | FAULT\_VIN| выключить vIN|
|
||||||
|
DREN\_OPERATE|таймаут 1200 сек |FAULT_DREN||
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Конфигурирование:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
"items":
|
||||||
|
{
|
||||||
|
"sprinkler":[23,
|
||||||
|
|
||||||
|
{
|
||||||
|
"":{
|
||||||
|
"vIn:7,
|
||||||
|
"wMax":15,
|
||||||
|
"wMin:17,
|
||||||
|
"rDren":6,
|
||||||
|
"fbDren":12,
|
||||||
|
"rPump":5,
|
||||||
|
"fbPump":11,
|
||||||
|
"wCtr":19
|
||||||
|
},
|
||||||
|
"garden":{"pin":13,"set":60,"val":15,"cmd":2},
|
||||||
|
"backyard":{"pin":14,"set":60,"val":15,"cmd":2},
|
||||||
|
"trees":{"pin":15,"set":60,"val":15,"cmd":2}
|
||||||
|
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Алгоритм работы
|
||||||
|
в настройки зон полива задаем интенсивность для каждой зоны.
|
||||||
|
|
||||||
|
Это можно сделать как в конфиге так динамически, (стандартными механизмами управления по MQTT, HTTP, CAN)
|
||||||
|
|
||||||
|
Рассмотрим на примере MQTT:
|
||||||
|
|
||||||
|
**топик** ```root/name/sprinkler/garden/set -> 60```
|
||||||
|
|
||||||
|
Задаем обьем полива 60 отсчетов счетчика воды (если счетчик не сконфигурирован - 60 секунд)
|
||||||
|
|
||||||
|
Контроллер должен передать это значение в выходной топик ```root/name/s_out/sprinkrer/garden/set``` и оно будет восстановлено при перезагрузке контроллера
|
||||||
|
|
||||||
|
отработанный обьем воды или время будет сохраняться в параметре "val" каждой зоны (параметр будет автоматически увеличиваться при работе зоны, передаваться в соответствующий зоне топик для мониторинга и восстановления в случае перезагрузки контроллера)
|
||||||
|
|
||||||
|
**Пример топика:** ```root/s_out/sprinkler/garden/val```
|
||||||
|
|
||||||
|
Когда данный параметр достигнет значения, заданного в параметре "set" контроллер завершит полив данной зоны и перейдет к следующей.
|
||||||
|
|
||||||
|
Для сброса счетчиков можно использовать как непосредственную установку значения параметра "val" для каждой зоны так и команду RESET, отправленную в нужную зону или в объект sprinkler через суффикс /cmd.
|
||||||
|
|
||||||
|
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
|
||||||
|
|
||||||
|
**Пример:** ```root/name/sprinkler/cmd -> RESET```
|
||||||
|
|
||||||
|
|
||||||
|
## Управление
|
||||||
|
|
||||||
|
### Включение/выключение полива конкретной зоны:
|
||||||
|
|
||||||
|
**Включить** ```root/name/sprinkler/garden/cmd -> ON```
|
||||||
|
|
||||||
|
**Выключить** ```root/name/sprinkler/garden/cmd -> OFF```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Включение/выключение цикла полива:
|
||||||
|
|
||||||
|
**Включить** ```root/name/sprinkler/cmd -> ON```
|
||||||
|
Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler перейдет в состояние OFF
|
||||||
|
|
||||||
|
Перед включением полива, система убедится что бак наполнен или до-наполнит его до максимума из водопровода.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Выключить** ```root/name/sprinkler/cmd -> OFF```
|
||||||
|
Система немедленно остановит текущий цикл полива (закроет клапаны зон, выключит насос полива)
|
||||||
|
|
||||||
|
|
||||||
|
Аналогично, будут работать команды XON и XOFF, с одним исключением, что команда XON может быть запрещена и игнорироваться если активирован режим DISABLE. Это базовая функция контроллера и не относится к функционалу данного модуля. Но может быть использована, например, для запрета полива на определенное время после выпадения осадков
|
||||||
|
|
||||||
|
**Пример**
|
||||||
|
|
||||||
|
```
|
||||||
|
root/name/sprinkler/ctrl -> DISABLE
|
||||||
|
root/name/sprinkler/cmd -> XON //Будет проигнорировано
|
||||||
|
|
||||||
|
root/name/sprinkler/ctrl -> ENABLE
|
||||||
|
root/name/sprinkler/cmd -> XON //А вот теперь сработает
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Даже в выключенном состоянии (OFF) , система полива работает в дежурном режиме, поддерживая максимальный уровень воды в баке за счет немедленной перекачки из дренажного колодца
|
||||||
|
|
||||||
|
При попытке включения системы после завершения дневного задания по поливу всех зон (параметр val для всех зон достиг параметра set), система сразу перейдет в состояние OFF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Полная блокировка системы полива (в зимнее время)
|
||||||
|
Ддя перевода канала полива в полностью заблокированное состояние и обратно импользуется системная команда FREEZE/UNFREEZE соответственно
|
||||||
|
|
||||||
|
В режиме FREEZE полностью заблокирована обработка всех команд, кроме UNFREEZE, заблокирован автомат пополнения бака из дренажного насоса и выключены насосы и все клапана
|
||||||
|
|
||||||
|
Рекомендуется задать флаг FREEZE в конфигурации канала (см документ ...) , чтобы избежать разблокировки при утере значений топика /clrl и перезагрузки системы
|
||||||
|
|
||||||
|
Также, на вход /val обЪекта sprinkler можно подать значение уличной температуры. И если значения будут ниже нуля, система автоматически перейдет в режим FREEZE
|
||||||
|
|
||||||
|
**Пример**
|
||||||
|
|
||||||
|
```
|
||||||
|
root/name/sprinkler/ctrl -> FREEZE
|
||||||
|
root/name/sprinkrer/cmd -> ON //Будет проигнорировано
|
||||||
|
|
||||||
|
root/name/sprinkler/ctrl -> UNFREEZE
|
||||||
|
root/name/sprinkrer/cmd -> ON //А вот теперь сработает
|
||||||
|
|
||||||
|
root/name/sprinkler/val -> -1 //система перейдет в режим FREEZE
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Передача статусных значений
|
||||||
|
|
||||||
|
|
||||||
|
**Примеры выдачи в топики:**
|
||||||
|
|
||||||
|
```
|
||||||
|
root/s_out/sprinkler/$fbPump - ON/OFF признак того, что включен основной насос (от датчика тока)
|
||||||
|
root/s_out/sprinkler/$fbDren - ON/OFF признак того, что включен дренажный насос (от датчика тока)
|
||||||
|
root/s_out/sprinkler/$state - состояние конечного автомата Системы налива воды
|
||||||
|
root/s_out/sprinkler/$wMax - ON/OFF достигнут максимум воды в баке (от поплавкового датчика)
|
||||||
|
root/s_out/sprinkler/$wMin - ON/OFF достигнут минимум воды в баке (от поплавкового датчика)
|
||||||
|
root/s_out/sprinkler/$rDren - ON/OFF включено реле дренажного насоса
|
||||||
|
root/s_out/sprinkler/$rPump - ON/OFF включено реле основного насоса
|
||||||
|
root/s_out/sprinkler/set - значение счетчика воды (восстанавливается при перезагрузке из данного топика)
|
||||||
|
root/s_out/sprinkler/$vIN - ON/OFF - признак открытия клапана налива бака из водопровода
|
||||||
|
|
||||||
|
root/s_out/sprinkler/garden/set - требуемый обьем (или время) полива зоны
|
||||||
|
root/s_out/sprinkler/garden/cmd - ON или OFF - признак включения полива зоны
|
||||||
|
root/s_out/sprinkler/garden/$state - ON или OFF - признак того что зона поливается в настоящее время
|
||||||
|
root/s_out/sprinkler/garden/val - текущее время или обьем полива данной зоны
|
||||||
|
|
||||||
|
```
|
||||||
@@ -68,6 +68,8 @@ e-mail anklimov@gmail.com
|
|||||||
#include "modules/out_humidifier.h"
|
#include "modules/out_humidifier.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "modules/out_sprinkler.h"
|
||||||
|
|
||||||
#ifdef CANDRV
|
#ifdef CANDRV
|
||||||
#include <candriver.h>
|
#include <candriver.h>
|
||||||
extern canDriver LHCAN;
|
extern canDriver LHCAN;
|
||||||
@@ -2886,6 +2888,12 @@ switch (itemType)
|
|||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SPRINKLER_ENABLE
|
||||||
|
case CH_SPRINKLER:
|
||||||
|
driver = new out_sprinkler ;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef COUNTER_DISABLE
|
#ifndef COUNTER_DISABLE
|
||||||
case CH_COUNTER:
|
case CH_COUNTER:
|
||||||
driver = new out_counter ;
|
driver = new out_counter ;
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ const suffixstr suffix_P[] PROGMEM =
|
|||||||
#define CH_COUNTER 20
|
#define CH_COUNTER 20
|
||||||
#define CH_HUMIDIFIER 21
|
#define CH_HUMIDIFIER 21
|
||||||
#define CH_MERCURY 22
|
#define CH_MERCURY 22
|
||||||
#define CH_MAX 22
|
#define CH_SPRINKLER 23
|
||||||
|
#define CH_MAX 23
|
||||||
|
|
||||||
#define POLLING_SLOW 1
|
#define POLLING_SLOW 1
|
||||||
#define POLLING_FAST 2
|
#define POLLING_FAST 2
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ const ch_type ch_type_P[] PROGMEM =
|
|||||||
"ELEVATOR", // 19 //
|
"ELEVATOR", // 19 //
|
||||||
"COUNTER", // 20 //Generic counter
|
"COUNTER", // 20 //Generic counter
|
||||||
"HUM", // 21 //Humidifier
|
"HUM", // 21 //Humidifier
|
||||||
"MERCURY" // 22 //Mercury energy meter/RS485 interface
|
"MERCURY", // 22 //Mercury energy meter/RS485 interface
|
||||||
|
"SPRINKLR" // 23 //Sprinkler controller
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ch_typeNum sizeof(ch_type_P)/sizeof(ch_type)
|
#define ch_typeNum sizeof(ch_type_P)/sizeof(ch_type)
|
||||||
|
|||||||
@@ -2717,6 +2717,12 @@ infoSerial<<F("\n(+)MERCURY");
|
|||||||
infoSerial<<F("\n(-)MERCURY");
|
infoSerial<<F("\n(-)MERCURY");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SPRINKLER_ENABLE
|
||||||
|
infoSerial<<F("\n(+)SPRINKLER");
|
||||||
|
#else
|
||||||
|
infoSerial<<F("\n(-)SPRINKLER");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CANDRV
|
#ifdef CANDRV
|
||||||
infoSerial<<F("\n(+)CAN");
|
infoSerial<<F("\n(+)CAN");
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -52,23 +52,24 @@ if (gatesObj)
|
|||||||
{
|
{
|
||||||
aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0);
|
aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0);
|
||||||
convert2float(setObj);
|
convert2float(setObj);
|
||||||
aJsonObject * valObj = getCreateObject(i,"val",(float) 20.0);
|
aJsonObject * valObj = getCreateObject(i,"val",(float) NAN);
|
||||||
convert2float(valObj);
|
convert2float(valObj);
|
||||||
aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0);
|
aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0);
|
||||||
convert2float(poObj);
|
convert2float(poObj);
|
||||||
|
|
||||||
int direction = DIRECT;
|
int direction = DIRECT;
|
||||||
float kP=getFloatFromJson(pidObj,0,1.0);
|
float kP=getFloatFromJson(pidObj,"kP",0,1.0);
|
||||||
if (kP<0)
|
if (kP<0)
|
||||||
{
|
{
|
||||||
kP=-kP;
|
kP=-kP;
|
||||||
direction=REVERSE;
|
direction=REVERSE;
|
||||||
}
|
}
|
||||||
float kI=getFloatFromJson(pidObj,1);
|
float kI=getFloatFromJson(pidObj,"kI",1,0.0);
|
||||||
float kD=getFloatFromJson(pidObj,2);
|
float kD=getFloatFromJson(pidObj,"kD",2,0.0);
|
||||||
float dT=getFloatFromJson(pidObj,3,5.0);
|
float dT=getFloatFromJson(pidObj,"dT",3,5.0);
|
||||||
|
|
||||||
pidObj->valueint = (long int) new PID (&valObj->valuefloat, &poObj->valuefloat, &setObj->valuefloat, kP, kI, kD, direction);
|
pidObj->valueint = (long int) new PID (&valObj->valuefloat, &poObj->valuefloat, &setObj->valuefloat, kP, kI, kD, P_ON_E,direction,
|
||||||
|
getFloatFromJson(i,"AlarmTime",4,120.0)*1000UL, getFloatFromJson(i,"AlarmValue",5,0.0));
|
||||||
|
|
||||||
//((PID*) pidObj->valueint)->SetMode (AUTOMATIC);
|
//((PID*) pidObj->valueint)->SetMode (AUTOMATIC);
|
||||||
((PID*) pidObj->valueint)->SetSampleTime(dT*1000.0);
|
((PID*) pidObj->valueint)->SetSampleTime(dT*1000.0);
|
||||||
@@ -114,7 +115,7 @@ return 1;
|
|||||||
|
|
||||||
int out_Multivent::isActive()
|
int out_Multivent::isActive()
|
||||||
{
|
{
|
||||||
debugSerial<<"VENT:active: ";
|
//debugSerial<<"VENT:active: ";
|
||||||
if (gatesObj)
|
if (gatesObj)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -153,6 +154,56 @@ int out_Multivent::isActive()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void out_Multivent::stopAllzones(){
|
||||||
|
if (!gatesObj) return;
|
||||||
|
debugSerial << F("VENT: Stop all zones. ")<<endl;
|
||||||
|
aJsonObject * i = gatesObj->child;
|
||||||
|
while (i)
|
||||||
|
{
|
||||||
|
if (i->name && *i->name)
|
||||||
|
{
|
||||||
|
int cmd = getIntFromJson(i,"cmd");
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case CMD_ON:
|
||||||
|
case CMD_HEATCOOL:
|
||||||
|
case CMD_FAN:
|
||||||
|
case CMD_AUTO:
|
||||||
|
case CMD_COOL:
|
||||||
|
case CMD_HEAT:
|
||||||
|
case CMD_DRY:
|
||||||
|
//case CMD_OFF:
|
||||||
|
setValToJson(i,"@preHaltcmd",cmd);
|
||||||
|
// setPassiveMode(i, true);
|
||||||
|
fanCtrl(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD),i->name,true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i=i->next;
|
||||||
|
}//while
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void out_Multivent::restoreAllzones(){
|
||||||
|
if (!gatesObj) return;
|
||||||
|
debugSerial << F("VENT: Restore all zones. ")<<endl;
|
||||||
|
aJsonObject * i = gatesObj->child;
|
||||||
|
while (i)
|
||||||
|
{
|
||||||
|
if (i->name && *i->name)
|
||||||
|
{
|
||||||
|
int preHaltcmd = getIntFromJson(i,"@preHaltcmd",CMD_OFF);
|
||||||
|
if (preHaltcmd) //setPassiveMode(i, false);
|
||||||
|
fanCtrl(itemCmd().Cmd(preHaltcmd).setSuffix(S_CMD),i->name,true);
|
||||||
|
setValToJson(i,"@preHaltcmd",0); //reset preHaltcmd in any case
|
||||||
|
}
|
||||||
|
i=i->next;
|
||||||
|
}//while
|
||||||
|
};
|
||||||
|
|
||||||
|
#define assign_if_positive(var, source) {int x=source; if (x>=0) {var=x;}}
|
||||||
|
|
||||||
int out_Multivent::Poll(short cause)
|
int out_Multivent::Poll(short cause)
|
||||||
{
|
{
|
||||||
if (!acObj || !gatesObj) return 0;
|
if (!acObj || !gatesObj) return 0;
|
||||||
@@ -166,14 +217,15 @@ int out_Multivent::Poll(short cause)
|
|||||||
|
|
||||||
// metrics, collected from AC
|
// metrics, collected from AC
|
||||||
float acTemp = getFloatFromJson(acObj,"val",NAN);
|
float acTemp = getFloatFromJson(acObj,"val",NAN);
|
||||||
int actualCmd = getIntFromJson (acObj,"mode");
|
int acCmd = getIntFromJson (acObj,"mode");
|
||||||
|
|
||||||
// global params
|
// global params
|
||||||
int boostTreshold = getIntFromJson (acObj,"boost");
|
int boostTreshold = getIntFromJson (acObj,"boost",AC_BOOST_TRESHOLD);
|
||||||
int actualMode = CMD_FAN;
|
int actualMode = CMD_FAN;
|
||||||
if (acTemp>30.0) actualMode = CMD_HEAT;
|
if (acTemp>30.0) actualMode = CMD_HEAT;
|
||||||
else if (acTemp<15.0) actualMode = CMD_COOL;
|
else if (acTemp<15.0) actualMode = CMD_COOL;
|
||||||
|
|
||||||
|
if (isnan(acTemp) && acCmd) actualMode = acCmd;
|
||||||
|
|
||||||
aJsonObject * i = gatesObj->child;
|
aJsonObject * i = gatesObj->child;
|
||||||
int balance = 0;
|
int balance = 0;
|
||||||
@@ -181,91 +233,115 @@ int out_Multivent::Poll(short cause)
|
|||||||
bool autoRequested = false; //At least 1 ch requested AUTO mode
|
bool autoRequested = false; //At least 1 ch requested AUTO mode
|
||||||
bool pidActive = false;
|
bool pidActive = false;
|
||||||
bool pidComputed = false;
|
bool pidComputed = false;
|
||||||
|
int lastACfan = -1;
|
||||||
while (i)
|
while (i)
|
||||||
{
|
{
|
||||||
if (i->name && *i->name)
|
if (i->name && *i->name)
|
||||||
{
|
{
|
||||||
int cmd = getIntFromJson(i,"cmd");
|
int cmd = getIntFromJson (i,"cmd");
|
||||||
int set = getIntFromJson(i,"set");
|
float set = getFloatFromJson(i,"set");
|
||||||
int val = getIntFromJson(i,"val");
|
float val = getFloatFromJson(i,"val");
|
||||||
|
int fan = getIntFromJson(i,"fan");
|
||||||
|
|
||||||
int execCmd = 0;
|
int execCmd = 0;
|
||||||
|
bool weakMode=false; // kind of modes when we activating PID only if AC in active mode
|
||||||
switch (cmd)
|
switch (cmd)
|
||||||
{
|
{
|
||||||
case CMD_HEATCOOL:
|
case CMD_HEATCOOL:
|
||||||
{
|
{
|
||||||
|
switch (acCmd) //TODO - release heat/cool then PID released it, not just set <> val
|
||||||
|
{
|
||||||
|
case CMD_HEAT:
|
||||||
if (set>val) execCmd = CMD_HEAT;
|
if (set>val) execCmd = CMD_HEAT;
|
||||||
|
if (set<val-AC_HEATCOOL_DEADBAND) execCmd = CMD_COOL;
|
||||||
|
break;
|
||||||
|
case CMD_COOL:
|
||||||
if (set<val) execCmd = CMD_COOL;
|
if (set<val) execCmd = CMD_COOL;
|
||||||
|
if (set>val+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (set>val+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT;
|
||||||
|
if (set<val-AC_HEATCOOL_DEADBAND) execCmd = CMD_COOL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_FAN:
|
case CMD_FAN:
|
||||||
ventRequested = true;
|
if (fan>0) ventRequested = true;
|
||||||
|
weakMode = true;
|
||||||
execCmd = cmd;
|
execCmd = cmd;
|
||||||
break;
|
break;
|
||||||
case CMD_AUTO:
|
case CMD_AUTO:
|
||||||
autoRequested = true;
|
if (fan>0) autoRequested = true;
|
||||||
|
weakMode = true;
|
||||||
execCmd = cmd;
|
execCmd = cmd;
|
||||||
break;
|
break;
|
||||||
case CMD_COOL:
|
case CMD_COOL:
|
||||||
case CMD_HEAT:
|
case CMD_HEAT:
|
||||||
case CMD_OFF:
|
case CMD_OFF:
|
||||||
//setValToJson(i,"@C",cmd);
|
|
||||||
execCmd = cmd;
|
execCmd = cmd;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
bool passiveMode = getIntFromJson(i,"@pasv",0);
|
bool passiveMode = getIntFromJson(i,"@pasv",0);
|
||||||
|
|
||||||
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
|
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
|
||||||
if (pidObj && pidObj->valueint)
|
aJsonObject * poObj = aJson.getObjectItem(i, "po");
|
||||||
|
aJsonObject * valObj = aJson.getObjectItem(i, "val");
|
||||||
|
|
||||||
|
if (pidObj && pidObj->valueint && poObj && poObj->type == aJson_Float && valObj && valObj->type == aJson_Float)
|
||||||
{
|
{
|
||||||
PID * p = (PID *) pidObj->valueint;
|
PID * p = (PID *) pidObj->valueint;
|
||||||
if ((execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true;
|
|
||||||
|
|
||||||
|
/// Setup PID mode and direction based on real AC mode
|
||||||
switch (actualMode)
|
switch (actualMode)
|
||||||
{ //if air hot or cold - uses temp PID and block control by /fan
|
{ //if air hot or cold - uses temp PID and block control by /fan
|
||||||
case CMD_HEAT:
|
case CMD_HEAT:
|
||||||
p->SetMode(AUTOMATIC);
|
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
|
||||||
p->SetControllerDirection(DIRECT);
|
p->SetControllerDirection(DIRECT);
|
||||||
break;
|
break;
|
||||||
case CMD_COOL:
|
case CMD_COOL:
|
||||||
p->SetMode(AUTOMATIC);
|
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
|
||||||
p->SetControllerDirection(REVERSE);
|
p->SetControllerDirection(REVERSE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (passiveMode || execCmd == CMD_AUTO || execCmd ==CMD_OFF) p->SetMode(MANUAL);
|
if ((passiveMode || weakMode || execCmd ==CMD_OFF) && p->GetMode() == AUTOMATIC)
|
||||||
|
{
|
||||||
|
p->SetMode(MANUAL);
|
||||||
|
debugSerial<<F("VENT: PID set to MANUAL due no HEAT/COOL. zone:")<<i->name<<endl;
|
||||||
|
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(getIntFromJson(i,"fan_in",0)).setSuffix(S_FAN),i->name,true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->Compute())
|
if (!p->isOutdated() && (execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true;
|
||||||
{
|
|
||||||
|
if (p->Compute())
|
||||||
|
|
||||||
aJsonObject * poObj = aJson.getObjectItem(i,"po");
|
|
||||||
if (poObj && poObj->type == aJson_Float)
|
|
||||||
{
|
{
|
||||||
debugSerial<<F("VENT: ")
|
debugSerial<<F("VENT: ")
|
||||||
<<item->itemArr->name<<"/"<<i->name
|
<<item->itemArr->name<<"/"<<i->name
|
||||||
<<F(" in:")<<p->GetIn()<<F(" set:")<<p->GetSet()<<F(" out:")<<p->GetOut()
|
<<F(" in:")<<p->GetIn()<<F(" set:")<<p->GetSet()<<F(" out:")<<p->GetOut()
|
||||||
<<" P:"<<p->GetKp()<<" I:"<<p->GetKi()<<" D:"<<p->GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M");
|
<<" P:"<<p->GetKp()<<" I:"<<p->GetKi()<<" D:"<<p->GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M");
|
||||||
|
if (p->isOutdated()) debugSerial << F(" <ALM>");
|
||||||
debugSerial<<endl;
|
debugSerial<<endl;
|
||||||
|
|
||||||
|
if (!isnan(poObj->valuefloat))
|
||||||
switch (execCmd)
|
switch (execCmd)
|
||||||
{
|
{
|
||||||
case CMD_HEAT:
|
case CMD_HEAT:
|
||||||
|
|
||||||
if (actualCmd==CMD_COOL) //close
|
if (actualMode==CMD_COOL) //close
|
||||||
fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true);
|
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
|
||||||
else
|
else
|
||||||
fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true);
|
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true))
|
||||||
|
|
||||||
balance+=poObj->valuefloat;
|
balance+=poObj->valuefloat;
|
||||||
pidComputed = true;
|
pidComputed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CMD_COOL:
|
case CMD_COOL:
|
||||||
if (actualCmd==CMD_HEAT) //close
|
if (actualMode==CMD_HEAT) //close
|
||||||
fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true);
|
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
|
||||||
else
|
else
|
||||||
fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true);
|
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true));
|
||||||
|
|
||||||
balance-=poObj->valuefloat;
|
balance-=poObj->valuefloat;
|
||||||
pidComputed = true;
|
pidComputed = true;
|
||||||
@@ -277,18 +353,25 @@ int out_Multivent::Poll(short cause)
|
|||||||
case CMD_HEAT:
|
case CMD_HEAT:
|
||||||
debugSerial<<F("VENT: HEAT PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
|
debugSerial<<F("VENT: HEAT PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
|
||||||
|
|
||||||
if (actualCmd!=CMD_OFF || passiveMode) fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true);
|
if (cmd !=CMD_OFF || passiveMode) assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true));
|
||||||
break;
|
break;
|
||||||
case CMD_COOL:
|
case CMD_COOL:
|
||||||
debugSerial<<F("VENT: COOL PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
|
debugSerial<<F("VENT: COOL PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
|
||||||
|
|
||||||
if (actualCmd!=CMD_OFF || passiveMode) fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true);
|
if (cmd !=CMD_OFF || passiveMode) assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true));
|
||||||
break;
|
break;
|
||||||
case CMD_FAN: //no more hot or cold air
|
case CMD_FAN: //no more hot or cold air
|
||||||
((PID *) pidObj->valueint)->SetMode(MANUAL);
|
((PID *) pidObj->valueint)->SetMode(MANUAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else //PID not computed - maybe not in time, but we can use PID output as indicator of balance and boost if needed
|
||||||
|
{
|
||||||
|
if (p->GetMode() == AUTOMATIC && !isnan(poObj->valuefloat))
|
||||||
|
{
|
||||||
|
balance+=(p->GetDirection() == DIRECT) ? poObj->valuefloat : -poObj->valuefloat;
|
||||||
|
//if (execCmd == CMD_HEAT) balance+=poObj->valuefloat;
|
||||||
|
//else if (execCmd == CMD_COOL) balance-=poObj->valuefloat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,26 +379,32 @@ int out_Multivent::Poll(short cause)
|
|||||||
}
|
}
|
||||||
i=i->next;
|
i=i->next;
|
||||||
}//while
|
}//while
|
||||||
if (pidComputed)
|
|
||||||
|
if (pidComputed) //Active mode. PIDs in pasive mode do not triggered this flag
|
||||||
{
|
{
|
||||||
debugSerial<<F("VENT: Chan balance=")<<balance<<F(" treshold:")<<boostTreshold<<endl;
|
debugSerial<<F("VENT: Chan balance=")<<balance<<F(" treshold:")<<boostTreshold<<endl;
|
||||||
|
|
||||||
if (balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_HEAT).Int(30).setSuffix(S_SET));
|
if (balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_HEAT).Int(AC_BOOST_HIGH_TEMP).setSuffix(S_SET));
|
||||||
else if (-balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(18).setSuffix(S_SET));
|
else if (-balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(AC_BOOST_LOW_TEMP).setSuffix(S_SET));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pidActive = false;
|
if (abs(balance)<AC_BOOST_DEADBAND) pidActive = false; //No request from active PID = no active PID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool noFurtherModes = false;
|
||||||
|
bool activeMode = (actualMode==CMD_HEAT || actualMode==CMD_COOL);
|
||||||
|
|
||||||
if (!pidActive)
|
if (!pidActive) // No active mode PID - no boost needed, but maybe vent still requested
|
||||||
|
|
||||||
{
|
{
|
||||||
resetBoost();
|
resetBoost();
|
||||||
if (autoRequested) sendACcmd(itemCmd().Cmd(CMD_AUTO));
|
if (autoRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_AUTO));
|
||||||
else if (ventRequested) sendACcmd(itemCmd().Cmd(CMD_FAN));
|
else if (ventRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_FAN));
|
||||||
|
else noFurtherModes = true; //No AUTO or FAN mode requested - so we can skip sending command to AC at all and save some energy on it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastACfan>0 || (lastACfan == 0 && (!activeMode || noFurtherModes))) sendACcmd(itemCmd().Percents255(lastACfan).setSuffix(S_FAN)); //if PID already set some fan value - send it to AC
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,7 +412,7 @@ return 1;
|
|||||||
|
|
||||||
int out_Multivent::getChanType()
|
int out_Multivent::getChanType()
|
||||||
{
|
{
|
||||||
return CH_THERMO; /////PWM
|
return CH_THERMO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -362,6 +451,7 @@ void out_Multivent::setPassiveMode(aJsonObject* zone, bool mode)
|
|||||||
item->SendStatusImmediate(itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),FLAG_COMMAND,zone->name); //Send /fan->AUTO
|
item->SendStatusImmediate(itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),FLAG_COMMAND,zone->name); //Send /fan->AUTO
|
||||||
|
|
||||||
SubmitParameters(cascadeObj,"fan",itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),false);
|
SubmitParameters(cascadeObj,"fan",itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),false);
|
||||||
|
setValToJson(zone,"fan",0); //reset fan level on passive mode ON
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -409,69 +499,32 @@ void out_Multivent::clearFlag (aJsonObject* zone, uint32_t flag)
|
|||||||
|
|
||||||
int out_Multivent::Ctrl(itemCmd cmd, char* subItem , bool toExecute, bool authorized)
|
int out_Multivent::Ctrl(itemCmd cmd, char* subItem , bool toExecute, bool authorized)
|
||||||
{
|
{
|
||||||
return fanCtrl(cmd,subItem, toExecute, false);
|
int result = fanCtrl(cmd,subItem, false);
|
||||||
|
|
||||||
|
if (result>=0)
|
||||||
|
{
|
||||||
|
debugSerial << F("VENT: CTRL fan level: ")<<result<<endl;
|
||||||
|
sendACcmd(itemCmd().Percents255(result).setSuffix(S_FAN));
|
||||||
|
}
|
||||||
|
else debugSerial << F("VENT: CTRL result: ")<<(result==-1? F("OK") : F("not OK"))<<endl;
|
||||||
|
// else debugSerial << F("VENT: Skip turning OFF by fan")<<endl;
|
||||||
|
return (result == -2)? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int out_Multivent::fanCtrl(itemCmd cmd, char* subItem , bool toExecute, bool force)
|
// Return:
|
||||||
|
// Main AC fan value (0..255) or
|
||||||
|
// -1 unknown fan + success
|
||||||
|
// -2 unknown fan + error (forbidden by PID or frozen by freeze flag)
|
||||||
|
int out_Multivent::fanCtrl(itemCmd cmd, char* subItem , bool force)
|
||||||
{
|
{
|
||||||
if (!gatesObj || !acObj) return 0;
|
if (!gatesObj || !acObj) return -2;
|
||||||
if (cmd.getCmd()==CMD_DISABLE || cmd.getCmd()==CMD_ENABLE) return 0;
|
if (cmd.getCmd()==CMD_DISABLE || cmd.getCmd()==CMD_ENABLE) return -2;
|
||||||
int suffixCode = cmd.getSuffix();
|
int suffixCode = cmd.getSuffix();
|
||||||
debugSerial << " VENT: CTRL " << subItem << " "; cmd.debugOut();
|
debugSerial << "VENT: CTRL " << subItem << " "; cmd.debugOut();
|
||||||
|
|
||||||
if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it
|
if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it
|
||||||
|
|
||||||
bool turnbyfan = getIntFromJson(acObj,"turnbyfan",0);
|
int turnbyfan = getIntFromJson(acObj,"turnbyfan",0);
|
||||||
|
|
||||||
if (!subItem) // feedback from shared AC
|
|
||||||
{
|
|
||||||
switch (suffixCode)
|
|
||||||
{
|
|
||||||
case S_VAL:
|
|
||||||
if (cmd.isValue())
|
|
||||||
{
|
|
||||||
debugSerial << F("VENT:")<<F("AC air temp: ")<< cmd.getFloat()<<endl;
|
|
||||||
item->setExt(millisNZ()); //setup validity interval
|
|
||||||
setValToJson(acObj,"val",cmd.getFloat());
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case S_FAN:
|
|
||||||
if (cmd.isValue())
|
|
||||||
{
|
|
||||||
debugSerial << F("VENT:")<<F("AC fan: ")<< cmd.getCmd()<<endl;
|
|
||||||
setValToJson(acObj,"fan",cmd.getInt());
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case S_SET:
|
|
||||||
if (cmd.isValue())
|
|
||||||
{
|
|
||||||
debugSerial << F("VENT:")<<F("AC set: ")<< cmd.getCmd()<<endl;
|
|
||||||
setValToJson(acObj,"set",cmd.getFloat());
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case S_MODE:
|
|
||||||
if (cmd.isCommand())
|
|
||||||
{
|
|
||||||
debugSerial << F("VENT:")<<F("AC mode: ")<< cmd.getCmd()<<endl;
|
|
||||||
setValToJson(acObj,"mode",cmd.getCmd());
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case S_CMD:
|
|
||||||
debugSerial<<"VENT: Todo - handle cmd/HALT. cmd="<<cmd.getCmd()<<endl;
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
//case S_TEMP: - temp corrupted by core
|
|
||||||
// debugSerial << F("VENT:")<<F("AC air roomtemp: ")<< cmd.getFloat()<<endl;
|
|
||||||
// setValToJson(acObj,"roomtemp",cmd.getFloat());
|
|
||||||
//return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aJsonObject * i = NULL;
|
|
||||||
|
|
||||||
if (cmd.getSuffix()==S_FAN)
|
if (cmd.getSuffix()==S_FAN)
|
||||||
{
|
{
|
||||||
@@ -488,17 +541,91 @@ if (cmd.getSuffix()==S_FAN)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_LOW:
|
case CMD_LOW:
|
||||||
cmd.Percents255(10);
|
cmd.Percents255(64);
|
||||||
cmd.Cmd(0);
|
cmd.Cmd(0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_OFF:
|
// case CMD_OFF:
|
||||||
cmd.Percents255(0);
|
// cmd.Percents255(0);
|
||||||
cmd.Cmd(0);
|
// cmd.Cmd(0);
|
||||||
break;
|
// break;
|
||||||
|
default:
|
||||||
|
if (cmd.isValue()) cmd.Percents255(cmd.getInt()); // convert to integer
|
||||||
} //switch cmd
|
} //switch cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!subItem) // feedback from shared AC
|
||||||
|
{
|
||||||
|
switch (suffixCode)
|
||||||
|
{
|
||||||
|
case S_VAL:
|
||||||
|
if (cmd.isValue())
|
||||||
|
{
|
||||||
|
debugSerial << F("VENT: ")<<F("AC air temp: ")<< cmd.getFloat()<<endl;
|
||||||
|
item->setExt(millisNZ()); //setup validity interval
|
||||||
|
setValToJson(acObj,"val",cmd.getFloat());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case S_FAN:
|
||||||
|
if (cmd.isValue())
|
||||||
|
{
|
||||||
|
debugSerial << F("VENT: ")<<F("AC fan: ")<< cmd.getInt()<<endl;
|
||||||
|
checkACfan(cmd.getInt());
|
||||||
|
setValToJson(acObj,"fan",cmd.getInt());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case S_SET:
|
||||||
|
if (cmd.isValue())
|
||||||
|
{
|
||||||
|
debugSerial << F("VENT: ")<<F("AC set: ")<< cmd.getFloat()<<endl;
|
||||||
|
checkACset(cmd.getFloat());
|
||||||
|
setValToJson(acObj,"set",cmd.getFloat());
|
||||||
|
if (cmd.getFloat() == AC_BOOST_HIGH_TEMP || cmd.getFloat() == AC_BOOST_LOW_TEMP)
|
||||||
|
{
|
||||||
|
setFlag(acObj, FLAG_ACTION_NEEDED);
|
||||||
|
debugSerial<<F("VENT: boost mode detected")<<endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case S_MODE:
|
||||||
|
if (cmd.isCommand())
|
||||||
|
{
|
||||||
|
debugSerial << F("VENT: ")<<F("AC mode: ")<< cmd.getCmd()<<endl;
|
||||||
|
checkACcmd(cmd.getCmd());
|
||||||
|
setValToJson(acObj,"mode",cmd.getCmd());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case S_CMD:
|
||||||
|
|
||||||
|
switch (cmd.getCmd())
|
||||||
|
{
|
||||||
|
case CMD_OFF:
|
||||||
|
stopAllzones();
|
||||||
|
break;
|
||||||
|
case CMD_ON:
|
||||||
|
case CMD_HEAT:
|
||||||
|
case CMD_COOL:
|
||||||
|
case CMD_HEATCOOL:
|
||||||
|
case CMD_DRY:
|
||||||
|
case CMD_FAN:
|
||||||
|
case CMD_AUTO:
|
||||||
|
restoreAllzones();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
//case S_TEMP: - temp corrupted by core
|
||||||
|
// debugSerial << F("VENT:")<<F("AC air roomtemp: ")<< cmd.getFloat()<<endl;
|
||||||
|
// setValToJson(acObj,"roomtemp",cmd.getFloat());
|
||||||
|
//return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aJsonObject * i = NULL;
|
||||||
|
|
||||||
if (gatesObj) i = gatesObj->child; // Pass 1 - calculate summ air value, max value etc
|
if (gatesObj) i = gatesObj->child; // Pass 1 - calculate summ air value, max value etc
|
||||||
|
|
||||||
int activeV = 0;
|
int activeV = 0;
|
||||||
@@ -506,6 +633,7 @@ int totalV = 0;
|
|||||||
int maxV=0;
|
int maxV=0;
|
||||||
int maxRequestedV=0;
|
int maxRequestedV=0;
|
||||||
int maxPercent=0;
|
int maxPercent=0;
|
||||||
|
bool exitAfterSendingStatus = false;
|
||||||
|
|
||||||
|
|
||||||
while (i)
|
while (i)
|
||||||
@@ -530,31 +658,28 @@ while (i)
|
|||||||
{
|
{
|
||||||
|
|
||||||
case S_FAN:
|
case S_FAN:
|
||||||
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -1;}
|
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
|
||||||
if (cmd.isValue())
|
if (cmd.isValue())
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!force && pidEnabled(pidObj))
|
if (!force)
|
||||||
|
{
|
||||||
|
setValToJson(i,"fan_in",cmd.getInt()); //fan controlled by external source, save input value to restore after exit from passive mode
|
||||||
|
debugSerial<<"VENT: external fan level stored: "<<cmd.getInt()<<endl;
|
||||||
|
if (pidEnabled(pidObj))
|
||||||
{
|
{
|
||||||
debugSerial<<F("VENT: FAN control disabled by PID")<<endl;
|
debugSerial<<F("VENT: FAN control disabled by PID")<<endl;
|
||||||
return 1;
|
return -2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// on boost something requested - remove
|
|
||||||
//if (!force && getFlag(acObj,FLAG_ACTION_NEEDED))
|
|
||||||
// {
|
|
||||||
// debugSerial<<F("VENT: FAN control disabled in boost mode")<<endl;
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (cmd.getInt())
|
if (cmd.getInt())
|
||||||
{
|
{
|
||||||
if (cmdObj->valueint == CMD_OFF && turnbyfan)
|
if (cmdObj->valueint == CMD_OFF && turnbyfan)
|
||||||
{
|
{
|
||||||
cmd.Cmd(CMD_ON);
|
cmd.Cmd(turnbyfan);
|
||||||
debugSerial<<"VENT: generating ON by fan"<<endl;
|
debugSerial<<"VENT: generating cmd by fan: "<<turnbyfan<<endl;
|
||||||
}
|
}
|
||||||
//fanObj->valueint = cmd.getInt();
|
|
||||||
//sendFlags |= FLAG_PARAMETERS;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -562,12 +687,7 @@ while (i)
|
|||||||
{
|
{
|
||||||
cmd.Cmd(CMD_OFF);
|
cmd.Cmd(CMD_OFF);
|
||||||
debugSerial<<"VENT: Turning OFF by fan"<<endl;
|
debugSerial<<"VENT: Turning OFF by fan"<<endl;
|
||||||
//setValToJson(i,"@precmd",cmdObj->valueint);
|
|
||||||
//cmdObj->valueint = CMD_OFF;
|
|
||||||
//sendFlags |= FLAG_COMMAND;
|
|
||||||
}
|
}
|
||||||
//fanObj->valueint = 0;
|
|
||||||
//sendFlags |= FLAG_PARAMETERS;
|
|
||||||
}
|
}
|
||||||
fanObj->valueint = cmd.getInt(); //
|
fanObj->valueint = cmd.getInt(); //
|
||||||
if (!passiveMode) sendFlags |= FLAG_PARAMETERS; //
|
if (!passiveMode) sendFlags |= FLAG_PARAMETERS; //
|
||||||
@@ -576,18 +696,25 @@ while (i)
|
|||||||
else if (cmd.getCmd() == CMD_AUTO)
|
else if (cmd.getCmd() == CMD_AUTO)
|
||||||
{
|
{
|
||||||
setPassiveMode(i,true); //Setup flag
|
setPassiveMode(i,true); //Setup flag
|
||||||
|
passiveMode = true;
|
||||||
cmd.Cmd(CMD_OFF);
|
cmd.Cmd(CMD_OFF);
|
||||||
cmd.setSuffix(S_CMD);
|
cmd.setSuffix(S_CMD);
|
||||||
}
|
}
|
||||||
|
else if (cmd.getCmd() == CMD_OFF)
|
||||||
|
{
|
||||||
|
setPassiveMode(i,false);
|
||||||
|
passiveMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!cmd.isCommand()) break; // if have command in FAN suffix - continue processing
|
if (!cmd.isCommand()) break; // if have command in FAN suffix - continue processing
|
||||||
|
debugSerial<<"VENT: cmd in FAN suffix, process as command. cmd="<<cmd.getCmd()<<endl;
|
||||||
case S_CMD:
|
case S_CMD:
|
||||||
if (cmd.isCommand())
|
if (cmd.isCommand())
|
||||||
{
|
{
|
||||||
|
|
||||||
if (cmd.getCmd() == CMD_ON)
|
if (cmd.getCmd() == CMD_ON)
|
||||||
{
|
{
|
||||||
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -1;}
|
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
|
||||||
if (cmdObj->valueint != CMD_OFF && cmdObj->valueint != CMD_HALT) break;
|
if (cmdObj->valueint != CMD_OFF && cmdObj->valueint != CMD_HALT) break;
|
||||||
cmd.Percents255(fanObj->valueint);
|
cmd.Percents255(fanObj->valueint);
|
||||||
cmd.setSuffix(S_FAN);
|
cmd.setSuffix(S_FAN);
|
||||||
@@ -604,13 +731,17 @@ while (i)
|
|||||||
case CMD_ON:
|
case CMD_ON:
|
||||||
break;
|
break;
|
||||||
case CMD_OFF:
|
case CMD_OFF:
|
||||||
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -1;}
|
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
|
||||||
if (cmdObj->valueint != CMD_OFF) setValToJson(i,"@precmd",cmdObj->valueint); //saving previous mode
|
if (cmdObj->valueint != CMD_OFF) setValToJson(i,"@precmd",cmdObj->valueint); //saving previous mode
|
||||||
cmd.Percents255(0);
|
cmd.Percents255(0);
|
||||||
cmd.setSuffix(S_FAN);
|
cmd.setSuffix(S_FAN);
|
||||||
|
debugSerial<<"VENT: Turning OFF. saving cmd:"<<cmdObj->valueint<<endl;
|
||||||
sendFlags |= FLAG_COMMAND;
|
sendFlags |= FLAG_COMMAND;
|
||||||
//if (!passiveMode) sendFlags |= FLAG_PARAMETERS;
|
//sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
|
||||||
//else cmd.Cmd(CMD_AUTO);
|
//
|
||||||
|
if (!passiveMode) sendFlags |= FLAG_PARAMETERS;
|
||||||
|
// else cmd.Cmd(CMD_AUTO);
|
||||||
|
|
||||||
cmdObj->valueint = CMD_OFF;
|
cmdObj->valueint = CMD_OFF;
|
||||||
enablePid(pidObj,false);
|
enablePid(pidObj,false);
|
||||||
break;
|
break;
|
||||||
@@ -630,11 +761,11 @@ while (i)
|
|||||||
|
|
||||||
case CMD_FREEZE:
|
case CMD_FREEZE:
|
||||||
setFlag(i,FLAG_FREEZED);
|
setFlag(i,FLAG_FREEZED);
|
||||||
return 1;
|
return -1;
|
||||||
|
|
||||||
case CMD_UNFREEZE:
|
case CMD_UNFREEZE:
|
||||||
clearFlag(i,FLAG_FREEZED);
|
clearFlag(i,FLAG_FREEZED);
|
||||||
return 1;
|
return -1;
|
||||||
|
|
||||||
case CMD_COOL:
|
case CMD_COOL:
|
||||||
case CMD_DRY:
|
case CMD_DRY:
|
||||||
@@ -655,6 +786,10 @@ while (i)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_FAN:
|
case CMD_FAN:
|
||||||
|
sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
|
||||||
|
cmd.Percents255(fanObj->valueint);
|
||||||
|
cmd.setSuffix(S_FAN);
|
||||||
|
// continue
|
||||||
case CMD_AUTO:
|
case CMD_AUTO:
|
||||||
enablePid(pidObj,false);
|
enablePid(pidObj,false);
|
||||||
sendFlags |= FLAG_COMMAND;
|
sendFlags |= FLAG_COMMAND;
|
||||||
@@ -676,16 +811,22 @@ while (i)
|
|||||||
{
|
{
|
||||||
setValToJson(i,"set",cmd.getFloat());
|
setValToJson(i,"set",cmd.getFloat());
|
||||||
sendFlags |= FLAG_PARAMETERS;
|
sendFlags |= FLAG_PARAMETERS;
|
||||||
|
exitAfterSendingStatus = true; //if setpoint updated - send status immediately to update PID and sending status
|
||||||
}
|
}
|
||||||
|
else return -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case S_VAL:
|
case S_VAL:
|
||||||
if (cmd.isValue())
|
if (cmd.isValue())
|
||||||
{
|
{
|
||||||
debugSerial<<F("VENT: value ")<<cmd.getFloat()<<endl;
|
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
|
||||||
setValToJson(i,"val",cmd.getFloat());
|
if (pidObj && pidObj->valueint)
|
||||||
|
{
|
||||||
|
((PID *) pidObj->valueint)->SetVal(cmd.getFloat());
|
||||||
|
debugSerial<<F("VENT: PID input: ")<<i->name<<F(" updated by temp: ")<< cmd.getFloat()<<endl;
|
||||||
}
|
}
|
||||||
return 1;
|
}
|
||||||
|
return -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -713,6 +854,7 @@ while (i)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // subitem
|
} // subitem
|
||||||
|
if (exitAfterSendingStatus) return -1; //if setpoint updated - send status immediately to update PID and sending status
|
||||||
|
|
||||||
if ((cmdObj->valueint != CMD_OFF && cmdObj->valueint != -1) || passiveMode)
|
if ((cmdObj->valueint != CMD_OFF && cmdObj->valueint != -1) || passiveMode)
|
||||||
{
|
{
|
||||||
@@ -737,16 +879,14 @@ if (!totalV) return 0;
|
|||||||
int fanV=activeV/totalV;
|
int fanV=activeV/totalV;
|
||||||
debugSerial << F("VENT: Total V:")<<totalV<<F(" active V:")<<activeV/255<< F(" fan%:")<<fanV<< F(" Max req:")<<maxRequestedV/255 <<F(" from ")<<maxV<<F(" m3")<< endl;
|
debugSerial << F("VENT: Total V:")<<totalV<<F(" active V:")<<activeV/255<< F(" fan%:")<<fanV<< F(" Max req:")<<maxRequestedV/255 <<F(" from ")<<maxV<<F(" m3")<< endl;
|
||||||
|
|
||||||
//executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd((fanV)?CMD_ON:CMD_OFF));
|
|
||||||
executeCommand(acObj,-1,itemCmd().Percents255(fanV).setSuffix(S_FAN));
|
|
||||||
/*
|
//if (fanV || allowToTurnOffByFan) sendACcmd(itemCmd().Percents255(fanV).setSuffix(S_FAN));
|
||||||
if (fanV)
|
// else debugSerial << F("VENT: Skip turning OFF by fan")<<endl;
|
||||||
executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd(CMD_ON));
|
|
||||||
else
|
|
||||||
executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV)); */
|
|
||||||
|
|
||||||
//Move gates only if fan is actually on
|
//Move gates only if fan is actually on
|
||||||
if (!fanV) return 1;
|
if (!fanV) return 0;
|
||||||
|
|
||||||
i=NULL;
|
i=NULL;
|
||||||
if (gatesObj) i = gatesObj->child; //Pass 2: re-distribute airflow
|
if (gatesObj) i = gatesObj->child; //Pass 2: re-distribute airflow
|
||||||
@@ -784,7 +924,7 @@ while (i)
|
|||||||
i=i->next;
|
i=i->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return fanV;
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_Multivent::enablePid(aJsonObject* pidObj, int enable, int direction )
|
void out_Multivent::enablePid(aJsonObject* pidObj, int enable, int direction )
|
||||||
@@ -802,11 +942,82 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj)
|
|||||||
return ((pidObj && pidObj->valueint) && (((PID *) pidObj->valueint)->GetMode() ==AUTOMATIC));
|
return ((pidObj && pidObj->valueint) && (((PID *) pidObj->valueint)->GetMode() ==AUTOMATIC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void out_Multivent::checkACcmd (int acCmd)
|
||||||
|
{
|
||||||
|
if (!acObj) return;
|
||||||
|
int lastCmd = getIntFromJson(acObj,"@lastCmd");
|
||||||
|
int prevACcmd = getIntFromJson(acObj,"mode");
|
||||||
|
int lastFan = getIntFromJson(acObj,"@lastFan",-1);
|
||||||
|
|
||||||
|
if (!prevACcmd) {
|
||||||
|
debugSerial<<"VENT: AC MODE received initially "<<acCmd<<endl;
|
||||||
|
setValToJson(acObj,"@lastCmd",acCmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCmd && (acCmd != lastCmd) ) {
|
||||||
|
|
||||||
|
if (!(acCmd == CMD_OFF && lastFan == 0))
|
||||||
|
{
|
||||||
|
debugSerial<<"VENT: AC MODE changed manually from "<<lastCmd<<" to "<<acCmd<<endl;
|
||||||
|
//TODO!
|
||||||
|
switch (acCmd)
|
||||||
|
{
|
||||||
|
case CMD_OFF:
|
||||||
|
stopAllzones();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
restoreAllzones();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValToJson(acObj,"@lastCmd",acCmd);
|
||||||
|
setValToJson(acObj,"@lastFan",-1);
|
||||||
|
return;}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_Multivent::checkACfan (int acFan)
|
||||||
|
{
|
||||||
|
if (!acObj) return;
|
||||||
|
int prevACfan = getIntFromJson(acObj,"fan");
|
||||||
|
if (acFan != prevACfan)
|
||||||
|
{
|
||||||
|
debugSerial<<"VENT: AC FAN changed, delete lastfan"<<endl;
|
||||||
|
setValToJson(acObj,"@lastFan",-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_Multivent::checkACset (int acSet)
|
||||||
|
{
|
||||||
|
if (!acObj) return;
|
||||||
|
int lastSet = getIntFromJson(acObj,"@lastSet",-1);
|
||||||
|
//int prevACset = getIntFromJson(acObj,"set");
|
||||||
|
|
||||||
|
if (lastSet == -1) {
|
||||||
|
debugSerial<<"VENT: AC SET received initially "<<acSet<<endl;
|
||||||
|
setValToJson(acObj,"@lastSet",acSet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSet && (acSet != lastSet)) {
|
||||||
|
debugSerial<<"VENT: AC SET changed manually from "<<lastSet<<" to "<<acSet<<endl;
|
||||||
|
setValToJson(acObj,"@lastSet",acSet);
|
||||||
|
return;}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int out_Multivent::sendACcmd (itemCmd cmd)
|
int out_Multivent::sendACcmd (itemCmd cmd)
|
||||||
{
|
{
|
||||||
if (!acObj) return 0;
|
if (!acObj) return 0;
|
||||||
int lastCmd = getIntFromJson(acObj,"@lastCmd");
|
int supress = getIntFromJson(acObj,"supress",0xF);
|
||||||
int acCmd = getIntFromJson(acObj,"mode");
|
|
||||||
|
|
||||||
|
//int acCmd = getIntFromJson(acObj,"mode");
|
||||||
|
|
||||||
//if (lastCmd && (acCmd != lastCmd)) {
|
//if (lastCmd && (acCmd != lastCmd)) {
|
||||||
// //debugSerial<<"VENT: AC MODE changed manually to "<<item->getCmd()<<endl;
|
// //debugSerial<<"VENT: AC MODE changed manually to "<<item->getCmd()<<endl;
|
||||||
@@ -814,28 +1025,61 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj)
|
|||||||
|
|
||||||
if (cmd.isCommand())
|
if (cmd.isCommand())
|
||||||
{
|
{
|
||||||
if (cmd.getCmd() == lastCmd ) {
|
int lastCmd = getIntFromJson(acObj,"@lastCmd");
|
||||||
|
int overrideCmd = getIntFromJson(acObj,"overridecmd");
|
||||||
|
if (overrideCmd && cmd.getCmd() != CMD_OFF) cmd.Cmd(overrideCmd); //if override cmd exist - use it instead of requested. But allow to turn off by original cmd
|
||||||
|
|
||||||
|
if (cmd.getCmd() == lastCmd && (supress & AC_SUPPRESS_CMD))
|
||||||
|
{
|
||||||
//debugSerial<<"VENT: AC MODE already "<<lastCmd<<endl;
|
//debugSerial<<"VENT: AC MODE already "<<lastCmd<<endl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
debugSerial<<"VENT: sendACcmd. cmd="; cmd.debugOut(); debugSerial<<endl;
|
||||||
executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0));
|
executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0));
|
||||||
setValToJson(acObj,"@lastCmd",cmd.getCmd());
|
setValToJson(acObj,"@lastCmd",cmd.getCmd());
|
||||||
|
setValToJson(acObj,"@lastFan",-1); //reset last fan to avoid suppression after mode change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd.isValue())
|
if (cmd.isValue())
|
||||||
{
|
{
|
||||||
executeCommand(acObj,-1,cmd.Cmd(0));
|
int lastFan = getIntFromJson(acObj,"@lastFan",-1);
|
||||||
|
int lastSet = getFloatFromJson(acObj,"@lastSet");
|
||||||
|
int prevACcmd = getIntFromJson(acObj,"mode",-1);
|
||||||
|
bool isActiveNow = (prevACcmd != CMD_OFF && prevACcmd != -1) || (lastFan>0);
|
||||||
|
|
||||||
switch (cmd.getSuffix())
|
switch (cmd.getSuffix())
|
||||||
{
|
{
|
||||||
case S_FAN:
|
case S_FAN:
|
||||||
setValToJson(acObj,"@lastFan",cmd.getCmd());
|
if (!(supress & AC_SUPPRESS_FAN) || lastFan != cmd.getInt()) {
|
||||||
|
debugSerial<<"VENT: sendACcmd. fan="; cmd.debugOut(); debugSerial<<endl;
|
||||||
|
executeCommand(acObj,-1,cmd.Cmd(0));
|
||||||
|
}
|
||||||
|
setValToJson(acObj,"@lastFan",cmd.getInt());
|
||||||
|
|
||||||
|
if (cmd.getInt() == 0)
|
||||||
|
{
|
||||||
|
/////setValToJson(acObj,"@lastCmd",CMD_OFF);
|
||||||
|
if (isActiveNow) setValToJson(acObj,"@preCmd",prevACcmd);
|
||||||
|
}
|
||||||
|
else if (cmd.getInt() > 0 && !isActiveNow)
|
||||||
|
{
|
||||||
|
int preCmd = getIntFromJson(acObj,"@preCmd",CMD_FAN);
|
||||||
|
setValToJson(acObj,"@lastCmd",preCmd);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case S_SET:
|
case S_SET:
|
||||||
setValToJson(acObj,"@lastSet",cmd.getCmd());
|
if (!(supress & AC_SUPPRESS_SET) || lastSet != cmd.getFloat()) {
|
||||||
|
debugSerial<<"VENT: sendACcmd. set="; cmd.debugOut(); debugSerial<<endl;
|
||||||
|
executeCommand(acObj,-1,cmd.Cmd(0));
|
||||||
}
|
}
|
||||||
|
setValToJson(acObj,"@lastSet",cmd.getFloat());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
executeCommand(acObj,-1,cmd.Cmd(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -844,22 +1088,22 @@ void out_Multivent::setBoost(itemCmd cmd)
|
|||||||
{
|
{
|
||||||
if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return;
|
if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return;
|
||||||
debugSerial<<"VENT: boost on"<<endl;
|
debugSerial<<"VENT: boost on"<<endl;
|
||||||
int acTemp = getIntFromJson(acObj,"set",21);
|
int acSetTemp = getIntFromJson(acObj,"set",AC_PRESET_TEMP);
|
||||||
setValToJson(acObj,"@preset",acTemp);
|
setValToJson(acObj,"@preset",acSetTemp);
|
||||||
//executeCommand(acObj,-1,cmd.setArgType(0).setSuffix(S_CMD));
|
|
||||||
sendACcmd(cmd);
|
sendACcmd(cmd);
|
||||||
//executeCommand(acObj,-1,cmd.Cmd(0).setSuffix(S_SET));
|
|
||||||
setFlag(acObj,FLAG_ACTION_NEEDED);
|
setFlag(acObj,FLAG_ACTION_NEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_Multivent::resetBoost()
|
void out_Multivent::resetBoost()
|
||||||
{
|
{
|
||||||
if (!acObj || !getFlag(acObj,FLAG_ACTION_NEEDED)) return;
|
if (!acObj) return;
|
||||||
|
//int acSetTemp = getIntFromJson(acObj,"set");
|
||||||
|
if (!getFlag(acObj,FLAG_ACTION_NEEDED)) return;
|
||||||
debugSerial<<"VENT: boost off"<<endl;
|
debugSerial<<"VENT: boost off"<<endl;
|
||||||
int preTemp = getIntFromJson(acObj,"@preset",21);
|
int preTemp = getIntFromJson(acObj,"@preset",AC_PRESET_TEMP);
|
||||||
if (preTemp<17) preTemp = 21;
|
if (preTemp<=AC_BOOST_LOW_TEMP) preTemp = AC_PRESET_TEMP;
|
||||||
|
if (preTemp>=AC_BOOST_HIGH_TEMP) preTemp = AC_PRESET_TEMP;
|
||||||
sendACcmd(itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
|
sendACcmd(itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
|
||||||
//executeCommand(acObj,-1,itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
|
|
||||||
clearFlag(acObj,FLAG_ACTION_NEEDED);
|
clearFlag(acObj,FLAG_ACTION_NEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,18 @@
|
|||||||
#include "itemCmd.h"
|
#include "itemCmd.h"
|
||||||
#include <PID_v1.h>
|
#include <PID_v1.h>
|
||||||
|
|
||||||
|
#define AC_BOOST_HIGH_TEMP 30
|
||||||
|
#define AC_PRESET_TEMP 21
|
||||||
|
#define AC_BOOST_LOW_TEMP 17
|
||||||
|
#define AC_BOOST_TRESHOLD 230
|
||||||
|
#define AC_BOOST_DEADBAND 10
|
||||||
|
#define AC_HEATCOOL_DEADBAND 1.0
|
||||||
|
|
||||||
|
#define AC_SUPPRESS_FAN 1
|
||||||
|
#define AC_SUPPRESS_SET 2
|
||||||
|
#define AC_SUPPRESS_CMD 4
|
||||||
|
#define AC_SUPPRESS_ALL 7
|
||||||
|
|
||||||
//static int8_t motorQuote = 0;
|
|
||||||
|
|
||||||
class out_Multivent : public abstractOut {
|
class out_Multivent : public abstractOut {
|
||||||
public:
|
public:
|
||||||
@@ -22,10 +32,13 @@ public:
|
|||||||
int getChanType() override;
|
int getChanType() override;
|
||||||
//int getDefaultStorageType(){return ST_PERCENTS255;};
|
//int getDefaultStorageType(){return ST_PERCENTS255;};
|
||||||
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
|
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
|
||||||
int fanCtrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool force = false);
|
int fanCtrl(itemCmd cmd, char* subItem=NULL, bool force = false);
|
||||||
protected:
|
protected:
|
||||||
void getConfig();
|
void getConfig();
|
||||||
int sendACcmd (itemCmd cmd);
|
int sendACcmd (itemCmd cmd);
|
||||||
|
void checkACcmd (int acCmd);
|
||||||
|
void checkACfan (int acFan);
|
||||||
|
void checkACset (int acSet);
|
||||||
void setPassiveMode(aJsonObject* zone, bool mode);
|
void setPassiveMode(aJsonObject* zone, bool mode);
|
||||||
uint32_t getFlag (aJsonObject* zone, uint32_t flag);
|
uint32_t getFlag (aJsonObject* zone, uint32_t flag);
|
||||||
void setFlag (aJsonObject* zone, uint32_t flag);
|
void setFlag (aJsonObject* zone, uint32_t flag);
|
||||||
@@ -35,6 +48,8 @@ protected:
|
|||||||
void setBoost(itemCmd);
|
void setBoost(itemCmd);
|
||||||
void resetBoost();
|
void resetBoost();
|
||||||
void notifyState(itemCmd state);
|
void notifyState(itemCmd state);
|
||||||
|
void stopAllzones();
|
||||||
|
void restoreAllzones();
|
||||||
|
|
||||||
|
|
||||||
aJsonObject * gatesObj;
|
aJsonObject * gatesObj;
|
||||||
|
|||||||
@@ -5,9 +5,21 @@
|
|||||||
#include <item.h>
|
#include <item.h>
|
||||||
#include <PID_v1.h>
|
#include <PID_v1.h>
|
||||||
#include "itemCmd.h"
|
#include "itemCmd.h"
|
||||||
|
#include "aJson.h"
|
||||||
|
|
||||||
#define OUTPUT_TRESHOLD 1.0
|
#define OUTPUT_TRESHOLD 1.0
|
||||||
|
|
||||||
|
class pidJson {
|
||||||
|
public:
|
||||||
|
pidJson(aJsonObject * _obj){obj=_obj;};
|
||||||
|
|
||||||
|
aJsonObject * getValObj(){return NULL;};
|
||||||
|
aJsonObject * getSetObj(){return NULL;};
|
||||||
|
aJsonObject * getPoObj(){return NULL;};
|
||||||
|
PID * getPID(){return (obj) ? (PID*) obj->valueint: NULL;};
|
||||||
|
aJsonObject * obj;
|
||||||
|
};
|
||||||
|
|
||||||
class pidPersistent : public chPersistent {
|
class pidPersistent : public chPersistent {
|
||||||
public:
|
public:
|
||||||
PID * pid;
|
PID * pid;
|
||||||
|
|||||||
744
lighthub/modules/out_sprinkler.cpp
Normal file
744
lighthub/modules/out_sprinkler.cpp
Normal file
@@ -0,0 +1,744 @@
|
|||||||
|
#ifdef SPRINKLER_ENABLE
|
||||||
|
|
||||||
|
#include "modules/out_sprinkler.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "options.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
|
||||||
|
#include "item.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
bool out_sprinkler::getConfig()
|
||||||
|
{
|
||||||
|
gatesObj = NULL;
|
||||||
|
vinPin = drenPin = pumpPin = -1;
|
||||||
|
wMaxPin = wMinPin = fbDrenPin = fbPumpPin = wCtrPin = -1;
|
||||||
|
|
||||||
|
if (!item || !item->itemArg) return false;
|
||||||
|
|
||||||
|
aJsonObject * arg = item->itemArg;
|
||||||
|
if (arg->type == aJson_Array && aJson.getArraySize(arg) > 1)
|
||||||
|
{
|
||||||
|
aJsonObject * second = aJson.getArrayItem(arg, 1);
|
||||||
|
if (second && second->type == aJson_Object) gatesObj = second;
|
||||||
|
}
|
||||||
|
else if (arg->type == aJson_Object)
|
||||||
|
{
|
||||||
|
gatesObj = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gatesObj) return false;
|
||||||
|
|
||||||
|
aJsonObject * rootCfg = aJson.getObjectItem(gatesObj, "");
|
||||||
|
if (!rootCfg) rootCfg = gatesObj;
|
||||||
|
|
||||||
|
vinPin = getIntFromJson(rootCfg, "vIn", -1);
|
||||||
|
drenPin = getIntFromJson(rootCfg, "rDren", -1);
|
||||||
|
pumpPin = getIntFromJson(rootCfg, "rPump", -1);
|
||||||
|
wMaxPin = getIntFromJson(rootCfg, "wMax", -1);
|
||||||
|
wMinPin = getIntFromJson(rootCfg, "wMin", -1);
|
||||||
|
fbDrenPin = getIntFromJson(rootCfg, "fbDren", -1);
|
||||||
|
fbPumpPin = getIntFromJson(rootCfg, "fbPump", -1);
|
||||||
|
wCtrPin = getIntFromJson(rootCfg, "wCtr", -1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isValidControlPin(short pin)
|
||||||
|
{
|
||||||
|
return (pin >= 0 && pin < PINS_COUNT && !isProtectedPin(pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::publishBooleanStateIfChanged(const char * subItem, bool state, uint32_t flag, uint32_t & lastState)
|
||||||
|
{
|
||||||
|
if (state == (bool) (lastState & flag)) return;
|
||||||
|
if (state) lastState |= flag ; else lastState &= ~flag;
|
||||||
|
publishBooleanState(subItem, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::setOutput(short pin, bool value)
|
||||||
|
{
|
||||||
|
if (!isValidControlPin(pin)) return;
|
||||||
|
digitalWrite(pin, value ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::Setup()
|
||||||
|
{
|
||||||
|
abstractOut::Setup();
|
||||||
|
|
||||||
|
if (!getConfig())
|
||||||
|
{
|
||||||
|
debugSerial << F("SPRINKLER: config failed") << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidControlPin(vinPin)) { pinMode(vinPin, OUTPUT); digitalWrite(vinPin, LOW); }
|
||||||
|
if (isValidControlPin(drenPin)) { pinMode(drenPin, OUTPUT); digitalWrite(drenPin, LOW); }
|
||||||
|
if (isValidControlPin(pumpPin)) { pinMode(pumpPin, OUTPUT); digitalWrite(pumpPin, LOW); }
|
||||||
|
|
||||||
|
aJsonObject * zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
short pin = getIntFromJson(zone, "pin", -1);
|
||||||
|
if (isValidControlPin(pin)) { pinMode(pin, OUTPUT); digitalWrite(pin, LOW); }
|
||||||
|
getCreateObject(zone, "cmd", (long)CMD_OFF);
|
||||||
|
getCreateObject(zone, "val", (long)0);
|
||||||
|
getCreateObject(zone, "set", (long)0);
|
||||||
|
getCreateObject(zone, "@active", (long)0);
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateObject(gatesObj, "@state", (long)SP_UNKNOWN);
|
||||||
|
getCreateObject(gatesObj, "@timer", (long)0);
|
||||||
|
getCreateObject(gatesObj, "@flowTimer", (long)0);
|
||||||
|
|
||||||
|
uint16_t lastVals = 0;
|
||||||
|
if (wCtrPin >= 0 && wCtrPin < PINS_COUNT)
|
||||||
|
{
|
||||||
|
pinMode(wCtrPin, INPUT);
|
||||||
|
lastVals |= (getPinVal(wCtrPin) ? LASTWCTRLSTATE : 0);
|
||||||
|
lastVals |= (getPinVal(wCtrPin) ? LASTWCTRLSTATE_ALL : 0);
|
||||||
|
}
|
||||||
|
if (wMaxPin >= 0 && wMaxPin < PINS_COUNT) {
|
||||||
|
pinMode(wMaxPin, INPUT);
|
||||||
|
lastVals |= (publishBooleanState("$wMax", getPinVal(wMaxPin)) ? LASTWMAXSTATE : 0);
|
||||||
|
}
|
||||||
|
if (wMinPin >= 0 && wMinPin < PINS_COUNT) {
|
||||||
|
pinMode(wMinPin, INPUT);
|
||||||
|
lastVals |= (publishBooleanState("$wMin", getPinVal(wMinPin)) ? LASTWMINSTATE : 0);
|
||||||
|
}
|
||||||
|
if (fbDrenPin >= 0 && fbDrenPin < PINS_COUNT) {
|
||||||
|
pinMode(fbDrenPin, INPUT);
|
||||||
|
lastVals |= (publishBooleanState("$fbDren", getPinVal(fbDrenPin)) ? LASTFBDRENSTATE : 0);
|
||||||
|
}
|
||||||
|
if (fbPumpPin >= 0 && fbPumpPin < PINS_COUNT) {
|
||||||
|
pinMode(fbPumpPin, INPUT);
|
||||||
|
lastVals |= (publishBooleanState("$fbPump", getPinVal(fbPumpPin)) ? LASTFBPUMPSTATE : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValToJson(gatesObj, "@lastVals", (long)lastVals);
|
||||||
|
|
||||||
|
//item->setExt(millisNZ());
|
||||||
|
setStatus(CST_INITIALIZED);
|
||||||
|
notifyState(SP_UNKNOWN);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::Stop()
|
||||||
|
{
|
||||||
|
debugSerial << F("SPRINKLER: stop") << endl;
|
||||||
|
turnOffAllZones();
|
||||||
|
pump(false);
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
setOutput(pumpPin, false);
|
||||||
|
setStatus(CST_UNKNOWN);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
int out_sprinkler::Status()
|
||||||
|
{
|
||||||
|
if (!item || !gatesObj) return 0;
|
||||||
|
|
||||||
|
bool wMax = (wMaxPin >= 0) ? getPinVal(wMaxPin) : false;
|
||||||
|
bool wMin = (wMinPin >= 0) ? getPinVal(wMinPin) : false;
|
||||||
|
bool fbDren = (fbDrenPin >= 0) ? getPinVal(fbDrenPin) : false;
|
||||||
|
bool fbPump = (fbPumpPin >= 0) ? getPinVal(fbPumpPin) : false;
|
||||||
|
|
||||||
|
publishBooleanStateIfChanged("$wMax", wMax, lastWMaxState);
|
||||||
|
publishBooleanStateIfChanged("$wMin", wMin, lastWMinState);
|
||||||
|
publishBooleanStateIfChanged("$fbDren", fbDren, lastFbDrenState);
|
||||||
|
publishBooleanStateIfChanged("$fbPump", fbPump, lastFbPumpState);
|
||||||
|
|
||||||
|
int state = getIntFromJson(gatesObj, "@state", SP_UNKNOWN);
|
||||||
|
publishNumericState("$state", state);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool out_sprinkler::isFreeze()
|
||||||
|
{
|
||||||
|
if (!item) return false;
|
||||||
|
return ((item->getFlag(FLAG_FREEZED) & FLAG_FREEZED) == FLAG_FREEZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool out_sprinkler::isNeedPump(bool steelNeed)
|
||||||
|
{
|
||||||
|
if (!gatesObj) return false;
|
||||||
|
if (!steelNeed && (!item || item->getCmd() != CMD_ON)) return false;
|
||||||
|
|
||||||
|
aJsonObject * zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
if (getIntFromJson(zone, "@active", 0)) return true;
|
||||||
|
int cmd = getIntFromJson(zone, "cmd", CMD_OFF);
|
||||||
|
if (cmd == CMD_ON)
|
||||||
|
{
|
||||||
|
long setVal = getIntFromJson(zone, "set", 0);
|
||||||
|
long valVal = getIntFromJson(zone, "val", 0);
|
||||||
|
if (valVal < setVal) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::pump(bool state)
|
||||||
|
{
|
||||||
|
if (!isValidControlPin(pumpPin)) return;
|
||||||
|
setOutput(pumpPin, state);
|
||||||
|
publishBooleanState("$rPump", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::turnOffAllZones()
|
||||||
|
{
|
||||||
|
if (!gatesObj) return;
|
||||||
|
aJsonObject * zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
short pin = getIntFromJson(zone, "pin", -1);
|
||||||
|
setOutput(pin, false);
|
||||||
|
if (getIntFromJson(zone, "@active", 0))
|
||||||
|
{
|
||||||
|
setZoneActive(zone, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::turnOffValves()
|
||||||
|
{
|
||||||
|
turnOffAllZones();
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::notifyState(short state)
|
||||||
|
{
|
||||||
|
if (!gatesObj) return;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
publishNumericState("$state", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::shutdown(sprinklerState nextState)
|
||||||
|
{
|
||||||
|
if (!gatesObj) return 0;
|
||||||
|
|
||||||
|
switch (nextState)
|
||||||
|
{
|
||||||
|
case SP_OFF:
|
||||||
|
case SP_FULL:
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
break;
|
||||||
|
case SP_DREN_ON:
|
||||||
|
case SP_DREN_OPERATE:
|
||||||
|
setOutput(drenPin, true);
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
break;
|
||||||
|
case SP_VIN:
|
||||||
|
setOutput(vinPin, true);
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
break;
|
||||||
|
case SP_DREN_EMPTY:
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
break;
|
||||||
|
case SP_FAULT_VIN:
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
break;
|
||||||
|
case SP_FAULT_DREN:
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
break;
|
||||||
|
case SP_UNKNOWN:
|
||||||
|
setOutput(drenPin, false);
|
||||||
|
setOutput(vinPin, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishBooleanState("$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE);
|
||||||
|
publishBooleanState("$vIN", nextState == SP_VIN);
|
||||||
|
notifyState(nextState);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline aJsonObject * out_sprinkler::getZone(const char * name)
|
||||||
|
{
|
||||||
|
if (!gatesObj || !name || !*name) return NULL;
|
||||||
|
aJsonObject * zone = aJson.getObjectItem(gatesObj, name);
|
||||||
|
if (zone && zone->type == aJson_Object) return zone;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline aJsonObject * out_sprinkler::findNextZone()
|
||||||
|
{
|
||||||
|
if (!gatesObj) return NULL;
|
||||||
|
|
||||||
|
aJsonObject * zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
if (getIntFromJson(zone, "@active", 0)) return zone;
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
int cmd = getIntFromJson(zone, "cmd", CMD_OFF);
|
||||||
|
long setVal = getIntFromJson(zone, "set", 0);
|
||||||
|
long valVal = getIntFromJson(zone, "val", 0);
|
||||||
|
if (cmd == CMD_ON && valVal < setVal) return zone;
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::setZoneActive(aJsonObject * zone, bool active)
|
||||||
|
{
|
||||||
|
if (!zone) return;
|
||||||
|
setValToJson(zone, "@active", (long)(active ? 1 : 0));
|
||||||
|
|
||||||
|
char subItem[48];
|
||||||
|
snprintf(subItem, sizeof(subItem), "%s/$state", zone->name);
|
||||||
|
item->SendStatusImmediate(itemCmd().Cmd(active ? CMD_ON : CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::updateZoneValue(aJsonObject * zone, long value)
|
||||||
|
{
|
||||||
|
if (!zone) return;
|
||||||
|
long current = getIntFromJson(zone, "val", 0);
|
||||||
|
current += value;
|
||||||
|
setValToJson(zone, "val", current);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(current).setSuffix(S_VAL), FLAG_PARAMETERS, zone->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::updateCounterValue()
|
||||||
|
{
|
||||||
|
int value = 1;
|
||||||
|
if (!gatesObj) return;
|
||||||
|
long current = getIntFromJson(gatesObj, "@wCtr", 0);
|
||||||
|
current += value;
|
||||||
|
setValToJson(gatesObj, "wCtr", current);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(current).setSuffix(S_SET), FLAG_PARAMETERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool out_sprinkler::publishBooleanState(const char * subItem, bool state)
|
||||||
|
{
|
||||||
|
if (!item) return state;
|
||||||
|
item->SendStatusImmediate(itemCmd().Cmd(state ? CMD_ON : CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, (char *)subItem);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_sprinkler::publishNumericState(const char * subItem, long value)
|
||||||
|
{
|
||||||
|
if (!item) return;
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_SET), FLAG_PARAMETERS, (char *)subItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::Poll(short cause)
|
||||||
|
{
|
||||||
|
if (!item || !gatesObj) return 0;
|
||||||
|
|
||||||
|
|
||||||
|
bool freeze = isFreeze();
|
||||||
|
bool wMax = (wMaxPin >= 0) ? getPinVal(wMaxPin) : false;
|
||||||
|
bool wMin = (wMinPin >= 0) ? getPinVal(wMinPin) : false;
|
||||||
|
bool fbDren = (fbDrenPin >= 0) ? getPinVal(fbDrenPin) : false;
|
||||||
|
bool fbPump = (fbPumpPin >= 0) ? getPinVal(fbPumpPin) : false;
|
||||||
|
|
||||||
|
uint32_t lastVals = getIntFromJson(gatesObj, "@lastVals", 0);
|
||||||
|
|
||||||
|
publishBooleanStateIfChanged("$wMax", wMax, LASTWMAXSTATE, lastVals);
|
||||||
|
publishBooleanStateIfChanged("$wMin", wMin, LASTWMINSTATE, lastVals);
|
||||||
|
publishBooleanStateIfChanged("$fbDren", fbDren, LASTFBDRENSTATE, lastVals);
|
||||||
|
publishBooleanStateIfChanged("$fbPump", fbPump, LASTFBPUMPSTATE, lastVals);
|
||||||
|
|
||||||
|
uint32_t now = millisNZ();
|
||||||
|
int state = getIntFromJson(gatesObj, "@state", SP_UNKNOWN);
|
||||||
|
uint32_t timer = (uint32_t)getIntFromJson(gatesObj, "@timer", 0);
|
||||||
|
bool lastWctrlState = lastVals & LASTWCTRLSTATE;
|
||||||
|
bool lastWctrlStateAll = lastVals & LASTWCTRLSTATE_ALL;
|
||||||
|
|
||||||
|
if (wCtrPin >= 0)
|
||||||
|
{
|
||||||
|
bool curr = getPinVal(wCtrPin);
|
||||||
|
if (curr && !lastWctrlStateAll)
|
||||||
|
{
|
||||||
|
updateCounterValue();
|
||||||
|
}
|
||||||
|
if (curr) lastVals |= LASTWCTRLSTATE_ALL; else lastVals &= ~LASTWCTRLSTATE_ALL;
|
||||||
|
}
|
||||||
|
setValToJson(gatesObj, "@lastVals", (long)lastVals);
|
||||||
|
|
||||||
|
if (freeze)
|
||||||
|
{
|
||||||
|
shutdown(SP_OFF);
|
||||||
|
turnOffValves();
|
||||||
|
pump(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case SP_UNKNOWN:
|
||||||
|
case SP_OFF:
|
||||||
|
if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
notifyState(state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = SP_DREN_ON;
|
||||||
|
setValToJson(gatesObj, "@timer", (long)now);
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_ON);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_DREN_ON:
|
||||||
|
if (fbDren)
|
||||||
|
{
|
||||||
|
state = SP_DREN_OPERATE;
|
||||||
|
setValToJson(gatesObj, "@timer", (long)now);
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_OPERATE);
|
||||||
|
}
|
||||||
|
else if (isTimeOver(timer, now, DRENAGE_ON_TIME))
|
||||||
|
{
|
||||||
|
state = SP_DREN_EMPTY;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_EMPTY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_DREN_OPERATE:
|
||||||
|
if (!fbDren)
|
||||||
|
{
|
||||||
|
state = SP_DREN_EMPTY;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_EMPTY);
|
||||||
|
}
|
||||||
|
else if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FULL);
|
||||||
|
}
|
||||||
|
else if (isTimeOver(timer, now, 1200000UL))
|
||||||
|
{
|
||||||
|
state = SP_FAULT_DREN;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FAULT_DREN);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_DREN_EMPTY:
|
||||||
|
if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FULL);
|
||||||
|
}
|
||||||
|
else if (item->getCmd() == CMD_ON)
|
||||||
|
{
|
||||||
|
state = SP_VIN;
|
||||||
|
setValToJson(gatesObj, "@timer", (long)now);
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_VIN);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_VIN:
|
||||||
|
if (fbDren)
|
||||||
|
{
|
||||||
|
state = SP_DREN_OPERATE;
|
||||||
|
setValToJson(gatesObj, "@timer", (long)now);
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_OPERATE);
|
||||||
|
}
|
||||||
|
else if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FULL);
|
||||||
|
}
|
||||||
|
else if (isTimeOver(timer, now, VIN_MAX_TIME))
|
||||||
|
{
|
||||||
|
state = SP_FAULT_VIN;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FAULT_VIN);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_FULL:
|
||||||
|
if (!wMax)
|
||||||
|
{
|
||||||
|
state = SP_DREN_ON;
|
||||||
|
setValToJson(gatesObj, "@timer", (long)now);
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_DREN_ON);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_FAULT_VIN:
|
||||||
|
if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SP_FAULT_DREN:
|
||||||
|
if (wMax)
|
||||||
|
{
|
||||||
|
state = SP_FULL;
|
||||||
|
setValToJson(gatesObj, "@state", (long)state);
|
||||||
|
shutdown(SP_FULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tankReady = (state == SP_FULL || wMax);
|
||||||
|
bool needPump = false;
|
||||||
|
aJsonObject * currentZone = NULL;
|
||||||
|
|
||||||
|
if (item->getCmd() == CMD_ON && !tankReady)
|
||||||
|
{
|
||||||
|
turnOffAllZones();
|
||||||
|
pump(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->getCmd() == CMD_ON && tankReady)
|
||||||
|
{
|
||||||
|
currentZone = findNextZone();
|
||||||
|
if (currentZone)
|
||||||
|
{
|
||||||
|
long setVal = getIntFromJson(currentZone, "set", 0);
|
||||||
|
long valVal = getIntFromJson(currentZone, "val", 0);
|
||||||
|
|
||||||
|
if (!getIntFromJson(currentZone, "@active", 0))
|
||||||
|
{
|
||||||
|
turnOffAllZones();
|
||||||
|
setZoneActive(currentZone, true);
|
||||||
|
short zonePin = getIntFromJson(currentZone, "pin", -1);
|
||||||
|
setOutput(zonePin, true);
|
||||||
|
setValToJson(gatesObj, "@flowTimer", (long)now);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wCtrPin >= 0)
|
||||||
|
{
|
||||||
|
bool curr = getPinVal(wCtrPin);
|
||||||
|
if (curr && !lastWctrlState)
|
||||||
|
{
|
||||||
|
updateZoneValue(currentZone, 1);
|
||||||
|
}
|
||||||
|
if (curr) lastVals |= LASTWCTRLSTATE; else lastVals &= ~LASTWCTRLSTATE;
|
||||||
|
setValToJson(gatesObj, "@lastVals", (long)lastVals);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint32_t flowTimer = (uint32_t)getIntFromJson(gatesObj, "@flowTimer", now);
|
||||||
|
if (isTimeOver(flowTimer, now, 1000UL))
|
||||||
|
{
|
||||||
|
updateZoneValue(currentZone, 1);
|
||||||
|
setValToJson(gatesObj, "@flowTimer", (long)now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (setVal > 0 && valVal >= setVal)
|
||||||
|
{
|
||||||
|
setOutput(getIntFromJson(currentZone, "pin", -1), false);
|
||||||
|
setZoneActive(currentZone, false);
|
||||||
|
//////setValToJson(currentZone, "cmd", (long)CMD_OFF);
|
||||||
|
item->SendStatusImmediate(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, currentZone->name);
|
||||||
|
currentZone = findNextZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentZone)
|
||||||
|
{
|
||||||
|
needPump = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needPump)
|
||||||
|
{
|
||||||
|
pump(false);
|
||||||
|
if (item->getCmd() == CMD_ON)
|
||||||
|
{
|
||||||
|
aJsonObject * resultZone = findNextZone();
|
||||||
|
if (!resultZone)
|
||||||
|
{
|
||||||
|
item->setCmd(CMD_OFF);
|
||||||
|
item->SendStatus(FLAG_COMMAND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pump(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authorized)
|
||||||
|
{
|
||||||
|
if (!item || !gatesObj) return 0;
|
||||||
|
int suffixCode = cmd.isCommand() ? S_CMD : cmd.getSuffix();
|
||||||
|
|
||||||
|
if (subItem && *subItem)
|
||||||
|
{
|
||||||
|
aJsonObject * zone = getZone(subItem);
|
||||||
|
if (!zone) return 0;
|
||||||
|
|
||||||
|
switch (suffixCode)
|
||||||
|
{
|
||||||
|
case S_SET:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
long value = cmd.getInt();
|
||||||
|
setValToJson(zone, "set", value);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_SET), FLAG_PARAMETERS, subItem);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case S_VAL:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
long value = cmd.getInt();
|
||||||
|
setValToJson(zone, "val", value);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_VAL), FLAG_PARAMETERS, subItem);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case S_CMD:
|
||||||
|
default:
|
||||||
|
switch (cmd.getCmd())
|
||||||
|
{
|
||||||
|
case CMD_ON:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
setValToJson(zone, "cmd", (long)CMD_ON);
|
||||||
|
item->SendStatusImmediate(itemCmd().Cmd(CMD_ON).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case CMD_OFF:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
setValToJson(zone, "cmd", (long)CMD_OFF);
|
||||||
|
setZoneActive(zone, false);
|
||||||
|
setOutput(getIntFromJson(zone, "pin", -1), false);
|
||||||
|
item->SendStatusImmediate(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case CMD_RESET:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
setValToJson(zone, "val", (long)0);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(0).setSuffix(S_VAL), FLAG_PARAMETERS, subItem);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (suffixCode)
|
||||||
|
{
|
||||||
|
case S_CMD:
|
||||||
|
switch (cmd.getCmd())
|
||||||
|
{
|
||||||
|
case CMD_ON:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case CMD_OFF:
|
||||||
|
turnOffAllZones();
|
||||||
|
pump(false);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case CMD_RESET:
|
||||||
|
{
|
||||||
|
aJsonObject * zone = gatesObj->child;
|
||||||
|
while (zone)
|
||||||
|
{
|
||||||
|
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||||
|
{
|
||||||
|
setValToJson(zone, "val", (long)0);
|
||||||
|
item->SendStatusImmediate(itemCmd().Int(0).setSuffix(S_VAL), FLAG_PARAMETERS, zone->name);
|
||||||
|
}
|
||||||
|
zone = zone->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_SET:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
long value = cmd.getInt();
|
||||||
|
setValToJson(gatesObj, "@wCtr", value);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case S_VAL:
|
||||||
|
if (toExecute)
|
||||||
|
{
|
||||||
|
long value = cmd.getInt();
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
item->setFlag(FLAG_FREEZED);
|
||||||
|
item->SendStatus(FLAG_FLAGS);
|
||||||
|
}
|
||||||
|
else if (isFreeze())
|
||||||
|
{
|
||||||
|
item->clearFlag(FLAG_FREEZED);
|
||||||
|
item->SendStatus(FLAG_FLAGS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int out_sprinkler::getChanType()
|
||||||
|
{
|
||||||
|
return CH_RELAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SPRINKLER_ENABLE
|
||||||
67
lighthub/modules/out_sprinkler.h
Normal file
67
lighthub/modules/out_sprinkler.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "options.h"
|
||||||
|
#ifdef SPRINKLER_ENABLE
|
||||||
|
|
||||||
|
#include <abstractout.h>
|
||||||
|
#include <item.h>
|
||||||
|
|
||||||
|
#define DRENAGE_ON_TIME 10000
|
||||||
|
#define VIN_MAX_TIME 1200000UL
|
||||||
|
|
||||||
|
#define LASTWCTRLSTATE 1
|
||||||
|
#define LASTWCTRLSTATE_ALL 2
|
||||||
|
#define LASTWMAXSTATE 4
|
||||||
|
#define LASTWMINSTATE 8
|
||||||
|
#define LASTFBDRENSTATE 16
|
||||||
|
#define LASTFBPUMPSTATE 32
|
||||||
|
|
||||||
|
enum sprinklerState {
|
||||||
|
SP_UNKNOWN = 0,
|
||||||
|
SP_OFF = 1,
|
||||||
|
SP_DREN_ON = 2,
|
||||||
|
SP_DREN_OPERATE = 3,
|
||||||
|
SP_DREN_EMPTY = 4,
|
||||||
|
SP_VIN = 5,
|
||||||
|
SP_FULL = 6,
|
||||||
|
SP_FAULT_VIN = -1,
|
||||||
|
SP_FAULT_DREN = -2
|
||||||
|
};
|
||||||
|
|
||||||
|
class out_sprinkler : public abstractOut {
|
||||||
|
public:
|
||||||
|
|
||||||
|
//out_sprinkler(){ /*NO getConfig() here due Poll() optimization*/ };
|
||||||
|
bool getConfig();
|
||||||
|
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
|
||||||
|
int Setup() override;
|
||||||
|
int Poll(short cause) override;
|
||||||
|
int Stop() override;
|
||||||
|
//int Status() override;
|
||||||
|
int getChanType() override;
|
||||||
|
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
aJsonObject * gatesObj;
|
||||||
|
short vinPin, drenPin, pumpPin;
|
||||||
|
short wMaxPin, wMinPin, fbDrenPin, fbPumpPin, wCtrPin;
|
||||||
|
|
||||||
|
|
||||||
|
void pump(bool state);
|
||||||
|
void setOutput(short pin, bool value);
|
||||||
|
bool isNeedPump(bool steelNeed=false);
|
||||||
|
void turnOffValves();
|
||||||
|
void turnOffAllZones();
|
||||||
|
aJsonObject * getZone(const char * name);
|
||||||
|
aJsonObject * findNextZone();
|
||||||
|
void setZoneActive(aJsonObject * zone, bool active);
|
||||||
|
void updateZoneValue(aJsonObject * zone, long value);
|
||||||
|
bool publishBooleanState(const char * subItem, bool state);
|
||||||
|
void publishNumericState(const char * subItem, long value);
|
||||||
|
void publishBooleanStateIfChanged(const char * subItem, bool state, uint32_t flag, uint32_t & lastState);
|
||||||
|
bool isFreeze();
|
||||||
|
void notifyState(short state);
|
||||||
|
int shutdown(sprinklerState nextState);
|
||||||
|
void updateCounterValue();
|
||||||
|
};
|
||||||
|
#endif
|
||||||
@@ -1061,6 +1061,14 @@ if (element && element->type == aJson_Boolean) return element->valuebool;
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long getIntFromJson(aJsonObject * a, const char * name, int i,long def)
|
||||||
|
{
|
||||||
|
if (!a) return def;
|
||||||
|
if (a->type == aJson_Object) return getIntFromJson(a,name,def);
|
||||||
|
else if (a->type == aJson_Array) return getIntFromJson(a,i,def);
|
||||||
|
else return def;
|
||||||
|
}
|
||||||
|
|
||||||
float getFloatFromJson(aJsonObject * a, int i, float def)
|
float getFloatFromJson(aJsonObject * a, int i, float def)
|
||||||
{
|
{
|
||||||
aJsonObject * element = NULL;
|
aJsonObject * element = NULL;
|
||||||
@@ -1086,6 +1094,15 @@ if (element && element->type == aJson_Int) return element->valueint;
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float getFloatFromJson(aJsonObject * a, const char * name, int i, float def)
|
||||||
|
{
|
||||||
|
aJsonObject * element = NULL;
|
||||||
|
if (!a) return def;
|
||||||
|
if (a->type == aJson_Object) return getFloatFromJson(a,name,def);
|
||||||
|
else if (a->type == aJson_Array) return getFloatFromJson(a,i,def);
|
||||||
|
else return def;
|
||||||
|
}
|
||||||
|
|
||||||
aJsonObject * getCreateObject(aJsonObject * a, int n)
|
aJsonObject * getCreateObject(aJsonObject * a, int n)
|
||||||
{
|
{
|
||||||
if (!a) return NULL;
|
if (!a) return NULL;
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ bool checkToken(char * token, char * data);
|
|||||||
long getIntFromJson(aJsonObject * a, const char * name, long def = 0);
|
long getIntFromJson(aJsonObject * a, const char * name, long def = 0);
|
||||||
float getFloatFromJson(aJsonObject * a, int i, float def = 0.0);
|
float getFloatFromJson(aJsonObject * a, int i, float def = 0.0);
|
||||||
float getFloatFromJson(aJsonObject * a, const char * name, float def = 0.0);
|
float getFloatFromJson(aJsonObject * a, const char * name, float def = 0.0);
|
||||||
|
long getIntFromJson(aJsonObject * a, const char * name, int i,long def);
|
||||||
|
float getFloatFromJson(aJsonObject * a, const char * name, int i, float def);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -147,7 +149,14 @@ if (a->type == aJson_Object)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
aJsonObject * getCreateObject(aJsonObject * a, const char * name, int n, Type def)
|
||||||
|
{
|
||||||
|
if (!a) return NULL;
|
||||||
|
if (a->type == aJson_Object) return getCreateObject(a,name,def);
|
||||||
|
else if (a->type == aJson_Array) return getCreateObject(a,n,def);
|
||||||
|
else return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
aJsonObject * setValToJson(aJsonObject * a, const char * name, Type val)
|
aJsonObject * setValToJson(aJsonObject * a, const char * name, Type val)
|
||||||
|
|||||||
@@ -637,6 +637,8 @@ extra_scripts = extra_script.py
|
|||||||
|
|
||||||
;;;; WEMOS D1 ;;;;;
|
;;;; WEMOS D1 ;;;;;
|
||||||
board = d1_mini
|
board = d1_mini
|
||||||
|
board_build.ldscript = eagle.flash.4m1m.ld
|
||||||
|
|
||||||
; change microcontroller
|
; change microcontroller
|
||||||
board_build.mcu = esp8266
|
board_build.mcu = esp8266
|
||||||
; change MCU frequency
|
; change MCU frequency
|
||||||
|
|||||||
Reference in New Issue
Block a user