Теперь давайте разбираться, что делают эти методы, да и вообще что нужно рассчитать для полного управления системой отопления.
Для начала нужно периодически рассчитывать среднюю (или минимальную, тут кому как интересно) температуру всех комнат для общих отопителей. Например, если в системе котел работает на несколько комнат, то для расчета температуры подачи нам нужно знать целевую температуру воздуха. Если зонного регулирования в комнатах нет, то лучше рассчитать среднюю температуру комнат, если есть, то нужно обязательно прогреть самую холодную, а остальные ограничат потребление тепла сами. Тек же для работы дневного термостата, нужно периодически пересчитывать целевую температуру воздуха. Я добавил в класс Timer отдельный объект ClockChimeClimate. В нем каждые 5 минут происходит вызов сценария tempHomeChanges. Использование сценария оправдано ещё и вызовом его из объектов класса Rooms при изменении текущей температуры в любой комнате.
Код: Выделить всё
$m=date('i',time());
// Расчет средней температуры жилых комнат
if (!($m%5)) {
runScript('tempHomeChanges');
}
// Расчет целевой температуры воздуха всех объектов класса
if (!($m%5)) {
// запуск методов airCalculate
$objects=getObjectsByClass('Thermostat');
foreach($objects as $obj) {
$obj=getObject($obj['TITLE']);
$obj->callMethod('airCalculate');
}
}
Код: Выделить всё
/* вычисляет среднюю температуру жилых комнат
для передачи её объектам термостатов котлов
которые работают на всю систему отопления.
*/
$check=array('OfficeRoom','Bedroom','Livingroom','HallArea','KitchenArea');
$t_avg=0; //средняя температура
$r_count=0; //количество принятых комнат
foreach($check as $r) {
$prop_id=getValueIdByName($r, 'Temperature');
$arr_s = SQLSelectOne("SELECT UNIX_TIMESTAMP(UPDATED) as UPDATED, VALUE FROM `pvalues` WHERE ID=$prop_id");
if (time()-$arr_s['UPDATED'] < 6*60*60) {
echo 'температура '.gg("$r.inRoomText").' '.$arr_s['VALUE'].'<br>';
$r_count++;
$t_avg=$t_avg+$arr_s['VALUE'];
} else {
echo 'данные о температуре '.gg("$r.inRoomText").' не актуальны <br>';
}
}
if (!$r_count) {
echo 'Данные не получены. Взять среднее по последним измерениям <br>';
foreach($check as $r) {
$r_count++;
$t_avg=$t_avg+gg("$r.Temperature");
}
}
$t_avg=round($t_avg/$r_count, 1);
echo "Средняя температура в жилых комнатах $t_avg <br>";
// передать данные в объект термостат
// температуру следует передавать и в объекты других термостатов, котлы которых работают на всю систему
// sg('ts_avghome.status',$t_avg);
sg('thrm_alpha.airCurrentTemp', $t_avg);
sg('thrm_beta.airCurrentTemp', $t_avg);
Далее идет перебор всех объектов класса Thermostat для выполнение методов airCalculate. В свойстве airRequiredTemp будет текущая целевая температура. Но любая система имеет инерцию. Это время, которое нам требуется на прогрев, и время на остывание. Допустим мы хотим понизить температуру ночью на 1 градус, а утром опять её увеличить. После того, как мы убавим, или даже полностью отключим отопитель, пройдет какое то время, прежде чем комната начнет остывать. Так же и при нагреве. Это и есть инерционность системы. Для каждого объекта это свойство индивидуально, и подбирается опытным путём по графикам. Метод airCalculate запишет свойство airShiftTemp в котором будет целевая температура комнаты с учетом инерции системы, чтобы успеть ее достичь.
Код: Выделить всё
$m=date('i',time());
/* Расчет целевой температуры воздуха в зависимости от времени суток
В результате записывается свойство с историей airRequiredTemp
для построения графика и контроля достижения заданной температуры;
и свойство без истории airShiftTemp для расчета температуры подачи
с учетом инерции системы.
*/
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.airCalculate','thrm'); }
$h=(int)date('G',time());
$m=date('i',time());
$airFunc=$this->getProperty('airFunc');
// свойство airFunc может содержать массив из 24 цифр разделенных пробелом, либо только одну цифру.
if (is_numeric($airFunc)) {
$t_desire = $airFunc;
// логи
if ($log_level>1) { DebMes("$ot Свойство airFunc задано числом $t_desire",'thrm'); }
} else {
// логи
if ($log_level>1) { DebMes("$ot Свойство airFunc задано массивом",'thrm'); }
// Преобразовать строку в массив
$arrTemp=explode(' ', $airFunc);
// Проверить корректность массива температур
if (count($arrTemp)<>24) {
if ($log_level>0) { DebMes("$ot ИСКЛЮЧЕНИЕ в методе airCalculate объекта ".$this->object_title.' не корректный массив в свойстве airFunc','thrm'); }
say("$ot Исключение в методе airCalculate объекта ".$this->object_title);
return;
}
// Получить значение температуры текущего часа
$t_desire=$arrTemp[$h];
// следующий элемент массива
$hn=$h+1;
if ($hn>23) {$hn-=24;}
// пропорционально изменить на разницу значения следующего часа
$t_desire+=($arrTemp[$hn]-$arrTemp[$h])*$m/60;
$t_desire=round($t_desire, 1);
// логи
if ($log_level>1) {
DebMes("$ot Установленная температура текущего часа $arrTemp[$h]",'thrm');
DebMes("$ot Установленная температура следующего часа $arrTemp[$hn]",'thrm');
}
}
// логи
if ($log_level>0) {
DebMes("$ot Требуемая температура $t_desire",'thrm');
}
// airRequiredTemp используется в основном для построения графиков
// и показа желаемого значения температуры на элементах термостатов
$this->setProperty('airRequiredTemp', $t_desire);
// пересчитать со сдвигом значение $t_desire если $airFunc не число
// иначе $t_desire останется равным числу в $airFunc
if (!is_numeric($airFunc)) {
// Смотреть в будущее чтобы успеть по причине инерционности системы
$h+=$this->getProperty('timeShift');
if ($h>23) {$h-=24;}
// Получить значение температуры текущего часа
$t_desire=$arrTemp[$h];
// следующий элемент массива
$hn=$h+1;
if ($hn>23) {$hn-=24;}
// пропорционально изменить на разницу значения следующего часа
$t_desire+=($arrTemp[$hn]-$arrTemp[$h])*$m/60;
$t_desire=round($t_desire, 1);
// логи
if ($log_level>1) {
DebMes("$ot Установленная температура будущего часа $arrTemp[$h]",'thrm');
DebMes("$ot Установленная температура следующего часа $arrTemp[$hn]",'thrm');
}
if ($log_level>0) {
DebMes("$ot Требуемая будущая температура $t_desire",'thrm');
}
}
// сохраненная будущая температура не равна расчетной будущей
// перезапись новой температуры, вызов метода расчета t подачи воды
if ($this->getProperty('airShiftTemp') <> $t_desire) {
$this->setProperty('airShiftTemp', $t_desire);
$this->callMethod('waterCalculate', array('airShiftTemp'=>$t_desire));
}
Если целевая температура комнаты изменилась, будет вызван метод waterCalculate для расчета температуры подачи. Тут мы вычислим свойство waterRequiredTemp - требуемая температура подачи котла. От чего она зависит? Факторов довольно много. Самый главный это температура на улице. Чем холоднее, тем больше нужна температура подачи, чтобы компенсировать потери тепла. Я использую классический расчет по температурной кривой с коэффициентами наклона и сдвига, как в большинстве котлов. Далее очень весомый фактор будет момент приближения нашей текущей температуры к целевой. И чтобы не перегреть комнату, нужно понизить подачу. Ещё сильное влияние окажет ветер. Он просто выдувает тепло из дома, и мы должны это компенсировать. Ну и под финал - интенсивность освещённости. Когда солнце светит ярко, энергии тепловой требуется меньше, чем в очень пасмурные дни. Всем этим занимается метод pzaCalculate, результаты вычисления которого использует waterCalculate. Но одной погодозависимой автоматикой сыт не будешь, и если помещение холодное, то правильнее будет разогнать отопитель на полную мощность для начального прогрева. В итоге waterCalculate возьмет максимально допустимую температуру по ПЗА если мы опаздываем на три градуса, далее будет работать по ПЗА, а при приближении целевой на 1 градус начнет уменьшать подачу.
Код: Выделить всё
/* Расчет температуры подачи
если фактическая температура воздуха +3 <= заблаговременной расчетной
т.е. запаздываем на 3 градуса, то берем максимальную температуру подачи из ПЗА.
иначе если температура воздуха +1 <= заблаговременной расчетной
т.е. меньше заданной на 1 градус, то берем температуру по ПЗА
иначе начинаем приближаться к заблаговременной температуре воздуха +5
*/
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.waterCalculate','thrm'); }
//$pzat = $this->getProperty('pzaTemp'); // температура по ПАЗ
if (isset($params['pzaTemp'])) { $pzat=$params['pzaTemp']; } else { $pzat=$this->getProperty('pzaTemp'); }
$act = $this->getProperty('airCurrentTemp'); // текущая температура воздуха (возможно выцепить без обращения к db)
//$ast = $this->getProperty('airShiftTemp'); // заблаговременный расчет требуемой температуры воздуха
if (isset($params['airShiftTemp'])) { $ast=$params['airShiftTemp']; } else { $ast=$this->getProperty('airShiftTemp'); }
if ($act+3<=$ast) {
$t='максимальная t подачи из ПЗА';
$wrt=$this->callMethod('pzaCalculate', array('t'=>-30)); // Получить максимальную температуру по ПЗА;
}
elseif ($act+1<=$ast) {
$t='t подачи по ПЗА';
$wrt=$pzat;
}
else {
$t='коррекция t при приближении';
$wrt=$pzat;
$wrt-=$pzat*17/100 * min(max((1-$ast+$act),0),1);
$wrt=round($wrt);
}
if ($log_level>0) { DebMes("$ot $t $wrt",'thrm'); }
if ($this->getProperty('waterRequiredTemp') <> $wrt) {
$this->setProperty('waterRequiredTemp', $wrt);
// Запуск исполняющих методов
$this->callMethod('setTemperature', array('t'=>$wrt));
$this->callMethod('positionServo');
}
// подумать когда и при каких условиях вызывать чтобы сократить выполнение кода
$this->callMethod('workDecision');
Теперь о самом методе pzaCalculate. В нем как раз и происходит расчет температуры по ПЗА. Метод вызывается при изменении уличной температуры. Рассчитывает ПЗА и сразу вызывает waterCalculate для перерасчета подачи. Тут как раз и происходит расчет по температуре улицы, скорости ветра и освещенности. Не все коэффициенты выведены в свойства объектов. Многие из них прописаны в коде. Для точной настройки нужно подгонять их под свой дом индивидуально. В этом очень помогают графики. О них мы тоже попозже поговорим.
Код: Выделить всё
/* Расчет требуемой температуры подачи по ПЗА
Учитывается уличная температура, скорость ветра, яркость солнца
Коэффициенты влияния этих факторов указаны в коде
*/
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.pzaCalculate','thrm'); }
// температура улица. ВНИМАНИЕ данные берутся с объекта улицы
if (isset($params['t'])) { $t =$params['t']; } else { $t = gg('DrivewayArea.Temperature'); }
// коэффициент выбора кривой. Поднимает хвост графика. Увеличить для + в морозы
if (isset($params['k1'])) { $k1 =$params['k1']; } else { $k1 = $this->getProperty('K1'); }
// коэффициент правки всей кривой. Перемещает вверх/вниз весь график
if (isset($params['k2'])) { $k2 =$params['k2']; } else { $k2 = $this->getProperty('K2'); }
// значение по умолчанию
if (!$k1) { $k1=1.5; }
if (!$k2) { $k2=20; }
// Рассчитать изгиб кривой
$Tp = sqrt((20-$t)*$k1)*10;
if (!isset($params['t']) && $log_level>1) { DebMes("$ot Изгиб кривой $Tp",'thrm'); }
// При увеличении желаемой комнатной температуры на 1°C кривая традиционно смещается на 5°C:
$Tp = $Tp + ($k2-20)*5;
if (!isset($params['t']) && $log_level>1) { DebMes("$ot Смещение кривой $Tp",'thrm'); }
// Правка на ветер
// <3 Слабый <6 Сильный <9 Очень сильный Иначе Шквальный
// поправку вносим от 0 до 10 м*с. Корректировка на 25%
$Tp+=$Tp*25/100 * min(max(gg('weather_now.windSpeed')/10,0),1);
if (!isset($params['t']) && $log_level>1) { DebMes("$ot Поправка на ветер $Tp",'thrm'); }
// Правка на солнце
// Освещенность наблюдается от 0 до 600 пунктов. Понижать t будет от 200 до 600
// Корректировка на 10%
$Tp-=$Tp*10/100 * min(max((gg('DrivewayArea.Luminiscence')-200)/400,0),1);
if (!isset($params['t']) && $log_level>1) { DebMes("$ot Поправка на солнце $Tp",'thrm'); }
// округлить, ограничить и записать
$Tp = round($Tp);
$Tp = min($Tp,85);
$Tp = max($Tp,20);
if (!isset($params['t']) && $log_level>0) { DebMes("$ot Итоговая t по ПЗА $Tp",'thrm'); }
// при передаче параметра t вернуть ответ для графиков. Иначе запуск метода
if (isset($params['t'])) {
if ($log_level>0) { DebMes("$ot На запрос уличной $t отправлен ответ $Tp",'thrm'); }
return($Tp);
} else {
$this->setProperty('pzaTemp', $Tp);
$this->callMethod('waterCalculate', array('pzaTemp'=>$Tp));
}
Далее по цепочке waterCalculate вызывает методы setTemperature и positionServo. В общем коде этих методов нет ничего интересного. Тут мы прописывает код в методе объектов. Например для объекта котла, подключенного через контроллер MyHeat в методе setTemperature будет отправка температуры подачи котлу, по средствам API контроллера. Код я выложу в самом конце. Сейчас мы продолжим обсуждать работу класса. Метод positionServo используется для объектов смесительных узлов. В нем мы поворачиваем трехходовой кран в нужную сторону для получения нужной t подачи. То есть у котла мы управляем температурой подачи, а у смесительного узла крутим сервопривод.
waterCalculate вызывает ещё один метод. Это WorkDecision. Он примет решение о полном включении или отключении отопителя, или циркуляционного насоса смесительного узла. Будут вызваны методы turnOn и turnOff. их коде устанавливаются таймеры минимальной работы и паузы. Без них, в ситуации нахождения на грани равенства температур, котел может постоянно включаться и выключаться. Тут работает правило, что если включились, то должны работать какое то время. А если выключились, то несколько минут ждем. Это убирает тактование. Очень полезно для электрических котлов, да и для всех в целом.
Код: Выделить всё
/* Принимает решение о работе котла или насоса, на основе текущей и требуемой температуры воздуха
Запускается из метода waterCalculate, а так же может быть вызван при необходимости проверки решения
о работе котла из других элементов системы
*/
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.workDecision','thrm'); }
// Не выполнять логику если установлен ручной режим
if (!$this->getProperty('auto')) {
if ($log_level>0) { DebMes("$ot Выполнение запрещено. auto режим не выбран",'thrm'); }
return;
}
// Сбор данных
$s = $this->getProperty('status');
$t = $this->getProperty('Title');
$n = $this->object_title;
$t_desire = $this->getProperty('airShiftTemp');
// гистерезис и значение по умолчанию
$hyst = $this->getProperty('hysteresis');
if (!is_numeric($hyst)) {$hyst = 0.2;}
// предельные температуры подачи и значения по умолчанию
$waterMaxTemp = $this->getProperty('waterMaxTemp');
$waterMinTemp = $this->getProperty('waterMinTemp');
if (!is_numeric($waterMaxTemp)) {$waterMaxTemp = 75;}
if (!is_numeric($waterMinTemp)) {$waterMinTemp = 10;}
// Текущая температура подачи. Если данных нет, то принять за 40
$waterCurrentTemp = $this->getProperty('waterCurrentTemp');
if (!is_numeric($waterCurrentTemp)) {$waterCurrentTemp = 40;}
// Получить значение метода, запрещающего работу
$mayBanOn = intval($this->callmethod('mayBanOn'));
// При запрете метод вернет 2. Преобразовать ответ в true / false
if ($log_level>1) { DebMes("$ot метод mayBanOn вернул $mayBanOn",'thrm'); }
if ($mayBanOn == 2) {$mayBanOn = true;} else {$mayBanOn = false;}
if ($s) { // *** котел сейчас работает, проверка решения на Отключение
// текущая t воздуха >= заблаговременной требуемой + гистерезис
if ($this->getProperty('airCurrentTemp') >= $t_desire + $hyst || $mayBanOn || $waterCurrentTemp > $waterMaxTemp) {
// Если существует таймер
if (timeOutExists($n.'_work') && !$mayBanOn && $waterCurrentTemp < $waterMaxTemp) {
if ($log_level>0) { DebMes("$ot Обеспечение минимального времени работы котла $t",'thrm'); }
} else {
$this->callMethod('turnOff');
}
}
} else { // *** котел сейчас НЕ работает, проверка решения на Включение
// текущая t воздуха <= заблаговременной требуемой - гистерезис
if (($this->getProperty('airCurrentTemp') <= $t_desire - $hyst || $waterCurrentTemp < $waterMinTemp) && !$mayBanOn) {
// Если существует таймер паузы
if (timeOutExists($n.'_nowork')) {
if ($log_level>0) { DebMes("$ot Обеспечение минимальной паузы перед включением котла $t",'thrm'); }
} else {
$this->callMethod('turnOn');
}
}
}
Код: Выделить всё
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.turnOff','thrm'); }
$this->setProperty('status',0);
$n = $this->object_title;
// Время минимальной работы
$mintimeoff = $this->getProperty('mintimeoff');
if ( !is_numeric($mintimeoff) ) {$mintimeoff = 20*60;}
// установить таймер паузы
setTimeOut($n.'_nowork', "debmes('Tаймер $n закончен'); cm('$n.workDecision');", $mintimeoff);
// Удалить таймер работы
clearTimeOut($n.'_work');
Код: Выделить всё
if (defined('THRM_LOG_LEVEL') && THRM_LOG_LEVEL != '') { $log_level = THRM_LOG_LEVEL; } else { $log_level = 0; }
if ($log_level>0) { $ot=$this->object_title; }
if ($log_level>1) { DebMes($ot.'.turnOn','thrm'); }
$this->setProperty('status',1);
$n = $this->object_title;
// Время минимальной работы
$mintimeon = $this->getProperty('mintimeon');
if ( !is_numeric($mintimeon) ) {$mintimeon = 15*60;}
//установить таймер работы
setTimeOut($n.'_work', "debmes('Tаймер $n закончен'); cm('$n.workDecision');", $mintimeon);
// Удалить таймер паузы
clearTimeOut($n.'_nowork');
Для управления объектами класса можно вывести их в меню или на сцены. Код шаблона отображения покажет все необходимые элементы.
![Шаблон.png](./download/file.php?id=19711&sid=08cb8c5f7f48da48209c44ed4710e6ed)
- Шаблон.png (12.39 КБ) 5993 просмотра
Код: Выделить всё
<svg width="100%" height="155px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="fg%.object_id%">
<feGaussianBlur in="SourceGraphic" stdDeviation="1" />
</filter>
<clipPath id="cut%.object_id%" >
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" />
</clipPath>
</defs>
<! -- Название термостата по описанию объекта -->
<text x="0" y="14" fill="rgb(48, 48, 48)" stroke="none" font-size="16px">%.object_description%</text>
<! -- Текущая температура воздуха -->
<g transform="translate(194,25)">
<title>Текущая температура воздуха</title>
<rect x="0" y="1" rx="4" ry="4" width="60" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="7" y="11" fill="rgb(150, 150, 150)" font-size="10px">Текущая</text>
<text x="7" y="30" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17px">%.airCurrentTemp%<tspan font-size="12px">°C</tspan></text>
</g>
<! -- Индикатор работы и таймеров -->
<g transform="translate(1,70)">
<title>Индикатор работы и таймеров</title>
<rect x="0" y="1" rx="4" ry="4" width="39" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<g style='display:%.status|"none;inline"%' onClick='callMethod("%.turnOff");'>
<rect x="0" y="0" rx="4" ry="4" width="39" height="35" fill="white" stroke="#dddddd" stroke-width="1"/>
<! -- Значок пламени -->
<g fill="none" stroke="black" stroke-width="1.5">
<path d="m 13,6 c 6,10 -6,10 0,20" />
<path d="m 20,6 c 6,10 -6,10 0,20" />
<path d="m 27,6 c 6,10 -6,10 0,20" />
<path d="m 7,28 25,0" />
</g>
</g>
<g style='display:%.status|"inline;none"%' onClick='callMethod("%.turnOn");'>
<rect x="0" y="0" rx="4" ry="4" width="39" height="35" fill="white" stroke="#dddddd" stroke-width="1"/>
</g>
<! -- Рамки таймеров -->
<rect id="greentimer%.object_id%" x="3" y="3" rx="1" ry="1" width="33" height="29" fill="none" stroke="gold" stroke-width="4" style='display:none'/>
<rect id="redtimer%.object_id%" x="3" y="3" rx="1" ry="1" width="33" height="29" fill="none" stroke="red" stroke-width="4" style='display:none'/>
</g>
<! -- ПЗА температура воды -->
<g transform="translate(52,70)">
<title>температура по ПЗА</title>
<rect x="0" y="1" rx="4" ry="4" width="60" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="7" y="11" fill="rgb(150, 150, 150)" font-size="10px">ПЗА вода</text>
<text x="6" y="30" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17px">%.pzaTemp%<tspan font-size="13px">°C</tspan></text>
</g>
<! -- Расчетная температура воды -->
<g transform="translate(123,70)">
<title>Расчетная температура теплоносителя</title>
<rect x="0" y="1" rx="4" ry="4" width="60" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="7" y="11" fill="rgb(150, 150, 150)" font-size="10px">Расчетная</text>
<text x="6" y="30" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17px">%.waterRequiredTemp%<tspan font-size="13px">°C</tspan></text>
</g>
<! -- Текущая температура воды -->
<g transform="translate(194,70)">
<title>Текущая температура теплоносителя</title>
<rect x="0" y="1" rx="4" ry="4" width="60" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="7" y="11" fill="rgb(150, 150, 150)" font-size="10px">Текущая</text>
<text x="6" y="30" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17px">%.waterCurrentTemp%<tspan font-size="13px">°C</tspan></text>
</g>
<! -- Сообщения -->
<g transform="translate(78,115)">
<title>Сообщения</title>
<rect x="0" y="1" rx="4" ry="4" width="105" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="105" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="4" y="14" fill="rgb(150, 150, 150)" font-size="10px">вкл по температуре</text>
<text x="4" y="25" fill="rgb(150, 150, 150)" font-size="10px">уст. таймер работы</text>
</g>
<! -- Гистерезис -->
<g transform="translate(194,115)">
<title>Отклонение вверх и вниз</title>
<g onClick='callMethod("%.workDecision");' >
<rect x="0" y="1" rx="4" ry="4" width="60" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="60" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="4" y="11" fill="rgb(150, 150, 150)" font-size="10px">Гистерезис</text>
<text x="6" y="30" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17">%.hysteresis%<tspan font-size="13px">°C</tspan></text>
</g>
</g>
<! -- Авто -->
<g transform="translate(1,115)">
<title>Автоматическое или ручное управление</title>
<g style='display:%.auto|"none;block"%' onClick='ajaxSetGlobal("%.auto", 0); callMethod("%.workDecision");'>
<rect x="0" y="1" rx="4" ry="4" width="66" height="35" fill="#2f89cd" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="66" height="35" fill="none" stroke="#dddddd" stroke-width="1"/> <! -- тонкий контур рамки по середине -->
<rect x="44" y="2" rx="3" ry="3" width="20" height="31" fill="#f6f6f6" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)"/> <! -- размытая рамка наружу и во внутрь -->
<rect x="44" y="2" rx="3" ry="3" width="20" height="31" fill="#f6f6f6" stroke="#dddddd" stroke-width="1"/> <! -- контур с заливкой внутри -->
<text x="4" y="23" fill="white" font-weight="bold" font-size="17">auto</text>
</g>
<g style='display:%.auto|"block;none"%' onClick='ajaxSetGlobal("%.auto", 1); callMethod("%.workDecision");'>
<rect x="0" y="1" rx="4" ry="4" width="66" height="35" fill="#e9e9e9" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="0" rx="4" ry="4" width="66" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<rect x="2" y="2" rx="3" ry="3" width="20" height="31" fill="#f6f6f6" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)"/>
<rect x="2" y="2" rx="3" ry="3" width="20" height="31" fill="#f6f6f6" stroke="#dddddd" stroke-width="1"/>
<text x="25" y="21" fill="rgb(48, 48, 48)" font-weight="bold" font-size="11">manual</text>
</g>
<!-- размытая и обрезанная общая рамка -->
</g>
<! -- График расчетной температуры воздуха -->
<g transform="translate(1,59)" >
<title>Расчет температуры воздуха по суточному термостату</title>
<rect x="0" y="-33" rx="4" ry="4" width="182" height="35" fill="none" stroke="#999999" stroke-width="1" filter="url(#fg%.object_id%)" clip-path="inset(-0.2px 0px 0.2px 0px round 1.2px)"/>
<rect x="0" y="-34" rx="4" ry="4" width="182" height="35" fill="none" stroke="#dddddd" stroke-width="1"/>
<text x="7" y="-23" fill="rgb(150, 150, 150)" font-size="10px">Расчетная температура воздуха</text>
<text x="129" y="-4" fill="rgb(48, 48, 48)" font-weight="bold" font-size="17px">%.airRequiredTemp%<tspan font-size="13px">°C</tspan></text>
<g transform="rotate(270)" fill="rgb(90, 90, 90)" >
<rect id="gr%.object_id%0" width="0" height="4" y="2" />
<rect id="gr%.object_id%1" width="0" height="4" y="7" />
<rect id="gr%.object_id%2" width="0" height="4" y="12" />
<rect id="gr%.object_id%3" width="0" height="4" y="17" />
<rect id="gr%.object_id%4" width="0" height="4" y="22" />
<rect id="gr%.object_id%5" width="0" height="4" y="27" />
<rect id="gr%.object_id%6" width="0" height="4" y="33" />
<rect id="gr%.object_id%7" width="0" height="4" y="38" />
<rect id="gr%.object_id%8" width="0" height="4" y="43" />
<rect id="gr%.object_id%9" width="0" height="4" y="48" />
<rect id="gr%.object_id%10" width="0" height="4" y="53" />
<rect id="gr%.object_id%11" width="0" height="4" y="58" />
<rect id="gr%.object_id%12" width="0" height="4" y="64" />
<rect id="gr%.object_id%13" width="0" height="4" y="69" />
<rect id="gr%.object_id%14" width="0" height="4" y="74" />
<rect id="gr%.object_id%15" width="0" height="4" y="79" />
<rect id="gr%.object_id%16" width="0" height="4" y="84" />
<rect id="gr%.object_id%17" width="0" height="4" y="89" />
<rect id="gr%.object_id%18" width="0" height="4" y="95" />
<rect id="gr%.object_id%19" width="0" height="4" y="100" />
<rect id="gr%.object_id%20" width="0" height="4" y="105" />
<rect id="gr%.object_id%21" width="0" height="4" y="110" />
<rect id="gr%.object_id%22" width="0" height="4" y="115" />
<rect id="gr%.object_id%23" width="0" height="4" y="120" />
</g>
</g>
</svg>
<script type="text/javascript">
var url=ROOTHTML;
url+='objects/?script='+encodeURIComponent('scriptTimeOutExists')+'&timername=%.object_title%_nowork';
$.ajax({
url: url
}).done(function(data) {
if (data==0) {
document.getElementById('redtimer%.object_id%').setAttribute('style', 'display:none');
} else {
document.getElementById('redtimer%.object_id%').setAttribute('style', 'display:block');
}
});
url+='objects/?script='+encodeURIComponent('scriptTimeOutExists')+'&timername=%.object_title%_work';
$.ajax({
url: url
}).done(function(data) {
if (data==0) {
document.getElementById('greentimer%.object_id%').setAttribute('style', 'display:none');
} else {
document.getElementById('greentimer%.object_id%').setAttribute('style', 'display:block');
}
});
// Получить значение аир функции и разбить на массив
//var tstring = '23 23 23 23 23 23.5 24 24 24 24 23.5 23.5 23.5 23.5 23.5 23.5 23.7 24 24 24 24 24 23.5 23';
var tstring = '%.airFunc%';
var tarr = tstring.split(' ');
// Дополнить массив до 24 последним элементов
var i = tarr.length + 1;
while (i <= 24) {
tarr.push(tarr[tarr.length-1]);
i++;
}
// Перебрать массив для установки графика
var h = new Date();
tarr.forEach(function(item, i, temparr) {
document.getElementById('gr%.object_id%' + i).setAttribute('width', (item - Math.max.apply(null, tarr) + 2) * 9);
if (h.getHours() == i) {
document.getElementById('gr%.object_id%' + i).setAttribute('fill', "gold");
}
});
</script>
Теперь о том, как установить, и обеспечить работу.
Убедимся, что в системе нет одноименного класса Thermostat. Импортируем класс, можно вместе с объектами моих отопителей. Будет проще разобраться. Потом удалите объекты и создадите свои. После импорта можно сразу добавить один из объектов в меню, чтобы убедиться что всё работает. Затем нужно настроить вызов методов класса. Всего будет три точки от куда запускаются методы.
1. Из метода onNewMinute каждые 5 минут происходит вызов методов airCalculate. Пример кода в самом начале. Нужно для суточного термостата. Далее методы класса будут вызываются по цепочке при необходимости.
2. При изменении температуры комнатных датчиков запускается сценарий tempHomeChanges. Он установит свойство airCurrentTemp что приведет к вызову метода waterCalculate.
Если у вас только один датчик температуры, то можно сразу писать его значение в свойство airCurrentTemp. Тогда сценарий tempHomeChanges не нужен. Сценарий только рассчитывает среднюю температуру комнат.
3. При изменении уличной температуры нужно вызвать метод pzaCalculate. Тут есть момент. В коде метода pzaCalculate есть получение свойства уличной температуры weather_now.temperature, скорости ветра weather_now.windSpeed и интенсивности освещённости DrivewayArea.Luminiscence. Замените их на свои. Если в вашей системе нет этих данных, тогда закомментируйте в методе pzaCalculate код поправки температуры подачи на эти факторы. Но без уличной температуры никак не обойтись.
Для ведения логов можно добавить в файл config.php строку Define('THRM_LOG_LEVEL', 2); Тогда все комментарии будут писаться в файл ...\cms\debmes\ГГГГ-ММ-ДД_thrm.log
0 - не писать, 1 - только основные логи, 2 - вся отладочная информация.
Ожидайте продолжение в ближайшее время! Скоро выложу класс целиком для скачивания.
А пока коротенькое резюме. Класс Thermostat может управлять отопителями по средствам их включения и выключения. Суточный термостат. Рассчитывает температуру подачи на основе ПЗА и целевой температуры дома. При подключении контроллера MyHeat может управлять отопителем по цифровой шине для поддержания целевой температуры. Управление сервоприводом смесительного узла.