11 Commits

Author SHA1 Message Date
47c2fd8a74 1-st post-AI review
Co-authored-by: Copilot <copilot@github.com>
2026-05-01 23:28:13 +03:00
04d709a1e8 MultiAC small fix + SPRINKLER chanell type (initial, unverivied) 2026-05-01 19:57:51 +03:00
e42053ab1b Multvent/MultiAC finally working as designed. pre-relese deep testing
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 11:55:51 +03:00
84b685c564 bin for due 2026-04-23 22:50:31 +03:00
bbceb044c1 MultiAC pre-releas 3 2026-04-11 23:58:25 +03:00
b5ef7ff221 Multi AC pre-release 2 2026-04-05 20:57:16 +03:00
00969f88a4 MultiAC pre-release 2026-04-05 00:53:44 +03:00
efbda54c01 Merge branch 'master' of https://github.com/anklimov/lighthub 2026-04-01 00:17:45 +03:00
56cb09ee7c MultiAC backward compatible with Multivent, overridecmd and turnbyfan (with val) attributes 2026-04-01 00:14:21 +03:00
e2dded5eb3 Merge pull request #67 from arto-b/arto-b-patch-1
Fix wording in README and correct Doxygen spelling
2026-03-14 23:08:53 +03:00
Arto Baltayan
9ac145a454 Fix wording in README and correct Doxygen spelling
Improve the wording of the first sentence in the README and correct the spelling of Doxygen.
2026-03-11 21:13:50 -04:00
17 changed files with 1506 additions and 199 deletions

View File

@@ -1,5 +1,5 @@
# 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:
* [Article/RU](https://geektimes.ru/post/295109/)
@@ -8,7 +8,7 @@ Useful links:
* [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:
* 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.

View File

@@ -17,7 +17,8 @@
-DRESTART_LAN_ON_MQTT_ERRORS
-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
#oct22 - violation in Publish/OnMQTTConnect while publish homie info

View File

@@ -6,7 +6,6 @@
-DSYSLOG_ENABLE
-DSTATUSLED
-DMCP23017
#-DPID_DISABLE
-DARDUINO_OTA_MDNS_DISABLE
-DMDNS_ENABLE
-DTIMER_INT
@@ -37,7 +36,7 @@
#-D CORS=\"http://lazyhome.ru\"
-DOTA_PORT=80
-D CORS=\"*\"
-D REDIRECTION_URL=\"http://lazyhome.ru/pwa\"
-D REDIRECTION_URL=lazyhome.ru/pwa
-D MERCURY_ENABLE
#-D IPMODBUS
-D CONFIG_CLEAN_PIN=2

Binary file not shown.

View 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 - текущее время или обьем полива данной зоны
```

View File

@@ -68,6 +68,8 @@ e-mail anklimov@gmail.com
#include "modules/out_humidifier.h"
#endif
#include "modules/out_sprinkler.h"
#ifdef CANDRV
#include <candriver.h>
extern canDriver LHCAN;
@@ -2886,6 +2888,12 @@ switch (itemType)
break;
#endif
#ifdef SPRINKLER_ENABLE
case CH_SPRINKLER:
driver = new out_sprinkler ;
break;
#endif
#ifndef COUNTER_DISABLE
case CH_COUNTER:
driver = new out_counter ;

View File

@@ -68,7 +68,8 @@ const suffixstr suffix_P[] PROGMEM =
#define CH_COUNTER 20
#define CH_HUMIDIFIER 21
#define CH_MERCURY 22
#define CH_MAX 22
#define CH_SPRINKLER 23
#define CH_MAX 23
#define POLLING_SLOW 1
#define POLLING_FAST 2

View File

@@ -59,7 +59,8 @@ const ch_type ch_type_P[] PROGMEM =
"ELEVATOR", // 19 //
"COUNTER", // 20 //Generic counter
"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)

View File

@@ -2717,6 +2717,12 @@ infoSerial<<F("\n(+)MERCURY");
infoSerial<<F("\n(-)MERCURY");
#endif
#ifdef SPRINKLER_ENABLE
infoSerial<<F("\n(+)SPRINKLER");
#else
infoSerial<<F("\n(-)SPRINKLER");
#endif
#ifdef CANDRV
infoSerial<<F("\n(+)CAN");
#else

View File

@@ -52,23 +52,24 @@ if (gatesObj)
{
aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0);
convert2float(setObj);
aJsonObject * valObj = getCreateObject(i,"val",(float) 20.0);
aJsonObject * valObj = getCreateObject(i,"val",(float) NAN);
convert2float(valObj);
aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0);
convert2float(poObj);
int direction = DIRECT;
float kP=getFloatFromJson(pidObj,0,1.0);
float kP=getFloatFromJson(pidObj,"kP",0,1.0);
if (kP<0)
{
kP=-kP;
direction=REVERSE;
}
float kI=getFloatFromJson(pidObj,1);
float kD=getFloatFromJson(pidObj,2);
float dT=getFloatFromJson(pidObj,3,5.0);
float kI=getFloatFromJson(pidObj,"kI",1,0.0);
float kD=getFloatFromJson(pidObj,"kD",2,0.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)->SetSampleTime(dT*1000.0);
@@ -114,7 +115,7 @@ return 1;
int out_Multivent::isActive()
{
debugSerial<<"VENT:active: ";
//debugSerial<<"VENT:active: ";
if (gatesObj)
{
/*
@@ -153,6 +154,56 @@ int out_Multivent::isActive()
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)
{
if (!acObj || !gatesObj) return 0;
@@ -166,14 +217,15 @@ int out_Multivent::Poll(short cause)
// metrics, collected from AC
float acTemp = getFloatFromJson(acObj,"val",NAN);
int actualCmd = getIntFromJson (acObj,"mode");
int acCmd = getIntFromJson (acObj,"mode");
// global params
int boostTreshold = getIntFromJson (acObj,"boost");
int boostTreshold = getIntFromJson (acObj,"boost",AC_BOOST_TRESHOLD);
int actualMode = CMD_FAN;
if (acTemp>30.0) actualMode = CMD_HEAT;
else if (acTemp<15.0) actualMode = CMD_COOL;
if (isnan(acTemp) && acCmd) actualMode = acCmd;
aJsonObject * i = gatesObj->child;
int balance = 0;
@@ -181,91 +233,115 @@ int out_Multivent::Poll(short cause)
bool autoRequested = false; //At least 1 ch requested AUTO mode
bool pidActive = false;
bool pidComputed = false;
int lastACfan = -1;
while (i)
{
if (i->name && *i->name)
{
int cmd = getIntFromJson(i,"cmd");
int set = getIntFromJson(i,"set");
int val = getIntFromJson(i,"val");
int cmd = getIntFromJson (i,"cmd");
float set = getFloatFromJson(i,"set");
float val = getFloatFromJson(i,"val");
int fan = getIntFromJson(i,"fan");
int execCmd = 0;
bool weakMode=false; // kind of modes when we activating PID only if AC in active mode
switch (cmd)
{
case CMD_HEATCOOL:
{
if (set>val) execCmd = CMD_HEAT;
if (set<val) execCmd = CMD_COOL;
}
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-AC_HEATCOOL_DEADBAND) execCmd = CMD_COOL;
break;
case 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;
case CMD_FAN:
ventRequested = true;
if (fan>0) ventRequested = true;
weakMode = true;
execCmd = cmd;
break;
case CMD_AUTO:
autoRequested = true;
if (fan>0) autoRequested = true;
weakMode = true;
execCmd = cmd;
break;
case CMD_COOL:
case CMD_HEAT:
case CMD_OFF:
//setValToJson(i,"@C",cmd);
execCmd = cmd;
break;
}
bool passiveMode = getIntFromJson(i,"@pasv",0);
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;
if ((execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true;
switch (actualMode)
{ //if air hot or cold - uses temp PID and block control by /fan
case CMD_HEAT:
p->SetMode(AUTOMATIC);
p->SetControllerDirection(DIRECT);
break;
case CMD_COOL:
p->SetMode(AUTOMATIC);
p->SetControllerDirection(REVERSE);
break;
default:
if (passiveMode || execCmd == CMD_AUTO || execCmd ==CMD_OFF) p->SetMode(MANUAL);
}
/// Setup PID mode and direction based on real AC mode
switch (actualMode)
{ //if air hot or cold - uses temp PID and block control by /fan
case CMD_HEAT:
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
p->SetControllerDirection(DIRECT);
break;
case CMD_COOL:
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
p->SetControllerDirection(REVERSE);
break;
default:
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->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: ")
<<item->itemArr->name<<"/"<<i->name
<<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");
if (p->isOutdated()) debugSerial << F(" <ALM>");
debugSerial<<endl;
if (!isnan(poObj->valuefloat))
switch (execCmd)
{
case CMD_HEAT:
if (actualCmd==CMD_COOL) //close
fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true);
if (actualMode==CMD_COOL) //close
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
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;
pidComputed = true;
break;
case CMD_COOL:
if (actualCmd==CMD_HEAT) //close
fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true);
if (actualMode==CMD_HEAT) //close
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
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;
pidComputed = true;
@@ -277,45 +353,58 @@ int out_Multivent::Poll(short cause)
case CMD_HEAT:
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;
case CMD_COOL:
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;
case CMD_FAN: //no more hot or cold air
((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;
}
}
}
}
i=i->next;
}//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;
if (balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_HEAT).Int(30).setSuffix(S_SET));
else if (-balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(18).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(AC_BOOST_LOW_TEMP).setSuffix(S_SET));
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();
if (autoRequested) sendACcmd(itemCmd().Cmd(CMD_AUTO));
else if (ventRequested) sendACcmd(itemCmd().Cmd(CMD_FAN));
if (autoRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_AUTO));
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;
};
@@ -323,7 +412,7 @@ return 1;
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
SubmitParameters(cascadeObj,"fan",itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),false);
setValToJson(zone,"fan",0); //reset fan level on passive mode ON
}
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)
{
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 (cmd.getCmd()==CMD_DISABLE || cmd.getCmd()==CMD_ENABLE) return 0;
if (!gatesObj || !acObj) return -2;
if (cmd.getCmd()==CMD_DISABLE || cmd.getCmd()==CMD_ENABLE) return -2;
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
bool 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;
int turnbyfan = getIntFromJson(acObj,"turnbyfan",0);
if (cmd.getSuffix()==S_FAN)
{
@@ -488,17 +541,91 @@ if (cmd.getSuffix()==S_FAN)
break;
case CMD_LOW:
cmd.Percents255(10);
cmd.Percents255(64);
cmd.Cmd(0);
break;
case CMD_OFF:
cmd.Percents255(0);
cmd.Cmd(0);
break;
// case CMD_OFF:
// cmd.Percents255(0);
// cmd.Cmd(0);
// break;
default:
if (cmd.isValue()) cmd.Percents255(cmd.getInt()); // convert to integer
} //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
int activeV = 0;
@@ -506,6 +633,7 @@ int totalV = 0;
int maxV=0;
int maxRequestedV=0;
int maxPercent=0;
bool exitAfterSendingStatus = false;
while (i)
@@ -530,31 +658,28 @@ while (i)
{
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 (!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;
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 (cmdObj->valueint == CMD_OFF && turnbyfan)
{
cmd.Cmd(CMD_ON);
debugSerial<<"VENT: generating ON by fan"<<endl;
cmd.Cmd(turnbyfan);
debugSerial<<"VENT: generating cmd by fan: "<<turnbyfan<<endl;
}
//fanObj->valueint = cmd.getInt();
//sendFlags |= FLAG_PARAMETERS;
}
else
{
@@ -562,12 +687,7 @@ while (i)
{
cmd.Cmd(CMD_OFF);
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(); //
if (!passiveMode) sendFlags |= FLAG_PARAMETERS; //
@@ -576,18 +696,25 @@ while (i)
else if (cmd.getCmd() == CMD_AUTO)
{
setPassiveMode(i,true); //Setup flag
passiveMode = true;
cmd.Cmd(CMD_OFF);
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
debugSerial<<"VENT: cmd in FAN suffix, process as command. cmd="<<cmd.getCmd()<<endl;
case S_CMD:
if (cmd.isCommand())
{
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;
cmd.Percents255(fanObj->valueint);
cmd.setSuffix(S_FAN);
@@ -604,13 +731,17 @@ while (i)
case CMD_ON:
break;
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
cmd.Percents255(0);
cmd.setSuffix(S_FAN);
debugSerial<<"VENT: Turning OFF. saving cmd:"<<cmdObj->valueint<<endl;
sendFlags |= FLAG_COMMAND;
//if (!passiveMode) sendFlags |= FLAG_PARAMETERS;
//else cmd.Cmd(CMD_AUTO);
//sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
//
if (!passiveMode) sendFlags |= FLAG_PARAMETERS;
// else cmd.Cmd(CMD_AUTO);
cmdObj->valueint = CMD_OFF;
enablePid(pidObj,false);
break;
@@ -630,11 +761,11 @@ while (i)
case CMD_FREEZE:
setFlag(i,FLAG_FREEZED);
return 1;
return -1;
case CMD_UNFREEZE:
clearFlag(i,FLAG_FREEZED);
return 1;
return -1;
case CMD_COOL:
case CMD_DRY:
@@ -655,6 +786,10 @@ while (i)
break;
case CMD_FAN:
sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
cmd.Percents255(fanObj->valueint);
cmd.setSuffix(S_FAN);
// continue
case CMD_AUTO:
enablePid(pidObj,false);
sendFlags |= FLAG_COMMAND;
@@ -676,16 +811,22 @@ while (i)
{
setValToJson(i,"set",cmd.getFloat());
sendFlags |= FLAG_PARAMETERS;
exitAfterSendingStatus = true; //if setpoint updated - send status immediately to update PID and sending status
}
else return -1;
break;
case S_VAL:
if (cmd.isValue())
{
debugSerial<<F("VENT: value ")<<cmd.getFloat()<<endl;
setValToJson(i,"val",cmd.getFloat());
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
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;
default:
@@ -703,16 +844,17 @@ while (i)
if (sendFlags & FLAG_COMMAND) SubmitParameters(cascadeObj,"cmd",itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0),true);
if (sendFlags & FLAG_PARAMETERS)
switch (cmd.getSuffix())
{
case S_SET:
SubmitParameters(cascadeObj,"set",cmd,true);
break;
case S_FAN:
SubmitParameters(cascadeObj,"fan",cmd,true);
break;
}
{
case S_SET:
SubmitParameters(cascadeObj,"set",cmd,true);
break;
case S_FAN:
SubmitParameters(cascadeObj,"fan",cmd,true);
break;
}
}
} // 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)
{
@@ -737,16 +879,14 @@ if (!totalV) return 0;
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;
//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)
executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd(CMD_ON));
else
executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV)); */
//if (fanV || allowToTurnOffByFan) sendACcmd(itemCmd().Percents255(fanV).setSuffix(S_FAN));
// else debugSerial << F("VENT: Skip turning OFF by fan")<<endl;
//Move gates only if fan is actually on
if (!fanV) return 1;
if (!fanV) return 0;
i=NULL;
if (gatesObj) i = gatesObj->child; //Pass 2: re-distribute airflow
@@ -784,7 +924,7 @@ while (i)
i=i->next;
}
return 1;
return fanV;
}
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));
}
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)
{
if (!acObj) return 0;
int lastCmd = getIntFromJson(acObj,"@lastCmd");
int acCmd = getIntFromJson(acObj,"mode");
int supress = getIntFromJson(acObj,"supress",0xF);
//int acCmd = getIntFromJson(acObj,"mode");
//if (lastCmd && (acCmd != lastCmd)) {
// //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.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;
}
else
{
debugSerial<<"VENT: sendACcmd. cmd="; cmd.debugOut(); debugSerial<<endl;
executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0));
setValToJson(acObj,"@lastCmd",cmd.getCmd());
setValToJson(acObj,"@lastFan",-1); //reset last fan to avoid suppression after mode change
}
}
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())
{
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;
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;
}
@@ -844,22 +1088,22 @@ void out_Multivent::setBoost(itemCmd cmd)
{
if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return;
debugSerial<<"VENT: boost on"<<endl;
int acTemp = getIntFromJson(acObj,"set",21);
setValToJson(acObj,"@preset",acTemp);
//executeCommand(acObj,-1,cmd.setArgType(0).setSuffix(S_CMD));
int acSetTemp = getIntFromJson(acObj,"set",AC_PRESET_TEMP);
setValToJson(acObj,"@preset",acSetTemp);
sendACcmd(cmd);
//executeCommand(acObj,-1,cmd.Cmd(0).setSuffix(S_SET));
setFlag(acObj,FLAG_ACTION_NEEDED);
}
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;
int preTemp = getIntFromJson(acObj,"@preset",21);
if (preTemp<17) preTemp = 21;
int preTemp = getIntFromJson(acObj,"@preset",AC_PRESET_TEMP);
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));
//executeCommand(acObj,-1,itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
clearFlag(acObj,FLAG_ACTION_NEEDED);
}

View File

@@ -6,8 +6,18 @@
#include "itemCmd.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 {
public:
@@ -22,10 +32,13 @@ public:
int getChanType() override;
//int getDefaultStorageType(){return ST_PERCENTS255;};
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:
void getConfig();
int sendACcmd (itemCmd cmd);
void checkACcmd (int acCmd);
void checkACfan (int acFan);
void checkACset (int acSet);
void setPassiveMode(aJsonObject* zone, bool mode);
uint32_t getFlag (aJsonObject* zone, uint32_t flag);
void setFlag (aJsonObject* zone, uint32_t flag);
@@ -35,6 +48,8 @@ protected:
void setBoost(itemCmd);
void resetBoost();
void notifyState(itemCmd state);
void stopAllzones();
void restoreAllzones();
aJsonObject * gatesObj;

View File

@@ -5,9 +5,21 @@
#include <item.h>
#include <PID_v1.h>
#include "itemCmd.h"
#include "aJson.h"
#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 {
public:
PID * pid;

View 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

View 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

View File

@@ -1061,6 +1061,14 @@ if (element && element->type == aJson_Boolean) return element->valuebool;
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)
{
aJsonObject * element = NULL;
@@ -1086,6 +1094,15 @@ if (element && element->type == aJson_Int) return element->valueint;
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)
{
if (!a) return NULL;

View File

@@ -88,6 +88,8 @@ bool checkToken(char * token, char * data);
long getIntFromJson(aJsonObject * a, const char * name, long def = 0);
float getFloatFromJson(aJsonObject * a, int i, 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;
}
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>
aJsonObject * setValToJson(aJsonObject * a, const char * name, Type val)

View File

@@ -637,6 +637,8 @@ extra_scripts = extra_script.py
;;;; WEMOS D1 ;;;;;
board = d1_mini
board_build.ldscript = eagle.flash.4m1m.ld
; change microcontroller
board_build.mcu = esp8266
; change MCU frequency