Пример термостата из wiki некорректен

Здравствуйте. Продолжаю изучать wb-rules, взял термостат из примеров правил термостат
Для меня примеры из вики - это образец, как делать нужно, на что ориентироваться новичкам. Однако в этом примере логика работы термостата сделана, мягко говоря, через седалищный нерв. Термостат там работает только в одном случае:

 whenChanged: temp,

то есть, если я пришел зимой в замерзший до -20 дом, и включил термостат, то пока я не надышу хотя бы до -19,9 - термостат не запустится
если я включил термостат и передвинул желаемую температуру с 0 до +30 градусов - термостат не запустится, пока не изменится температура пола

то есть вся логика начинает работать только после того, как произойдет изменение температуры на датчике. Я вот сейчас поигрался с разными параметрами - иногда приходится ждать по 2-3 минуты, пока температура дрогнет на эти 0,1 градуса и логика термостата возбудится.

Считаю такой пример очень фиговым, ибо он учит как делать не надо изначально. А спросить, как делать правильно, мне не у кого.
Поэтому, прошу дать оценку моему быдло-кодингу.
Я добавил два правила

  1. При изменении статуса термостата ВКЛ проверяется температура датчика и желаемая. Если температура датчика ниже - включается реле. Если выше - реле не включается.
    При изменении статуса термостата ВЫКЛ реле выключается
  2. При изменении бегунка желаемой температуры так же сравнивается температура датчика и новая желаемая. Если она выше - включается реле. Ниже - выключается

и только потом уже идет проверка при изменении температуры пола и работает вся логика из примера.

Буду сильно благодарен за конструктивную критику, потому что образец для подражания в вики - атстой.

defineVirtualDevice("Termostat", {
    title: "Termostat",
    cells: {
      // =============== Прихожая теплый пол
      "R01-TS16-1-mode": {//режим 0-ручной 1-по расписанию
	    type: "switch",
	    value: false,
	    },
      "R01-TS16-1-setpoint": {//уставка
	    type: "range",
	    value: 25,
        max: 35,
        readonly: false
	    },
      "R01-TS16-1-lock": {//блокировка в визуализации 0-снята 1-заблокирована
	    type: "switch",
	    value: false,
	    },
      "R01-TS16-1-onoff": {//активация термостата 0-выкл 1-включен
	    type: "switch",
	    value: false,
	    },
   }
})

var hysteresis = 0.1;
function Termostat(name, temp, setpoint, TS, TS_onoff) {
//правило при включении термостата
  defineRule("On_start",{	//Проверка при включении: температура ниже setpoint - включить реле, выше setpiont- не включать
  							//При выключении - выключить реле 
  whenChanged: TS_onoff,
  then: function () {
	if (dev[TS_onoff]) {
    	if ( dev[temp] < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис
      	dev[TS] = true;
    	}
    	if ( dev[temp] > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис
      	dev[TS] = false;
    	}
    }
    else dev[TS] = false; 
  }
});
//правило при изменении желаемой температуры
  defineRule("On_setpoint_change",{	//Проверка при изменении setpoint: температура ниже setpoint - включить реле, выше setpiont- не включать
  									//При выключении - выключить реле 
  whenChanged: setpoint,
  then: function () {
	if (dev[TS_onoff]) {
    	if ( dev[temp] < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис
      	dev[TS] = true;
    	}
    	if ( dev[temp] > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис
      	dev[TS] = false;
    	}
    }
    else dev[TS] = false; 
  }
});
  // правило при изменении показаний датчика
  defineRule(name, { 
  whenChanged: temp, //при изменении состояния датчика
  then: function (newValue, devName, cellName) { //выполняй следующие действия
    if (dev[TS_onoff]) {
    	if ( newValue < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис
      	dev[TS] = true;
    	}
    	if ( newValue > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис
      	dev[TS] = false;
    	}
    }
    else dev[TS] = false;
  }
});
}

Termostat("R01-TS16-1", "wb-mai6_92/IN 3 N Temperature", "Termostat/R01-TS16-1-setpoint", "wb-mr6cu_99/K5", "Termostat/R01-TS16-1-onoff"); // Прихожая теплый пол

Евгений, вы очень строги и не совсем справедливы. Вы смотрите на учебный пример, который иллюстрирует бизнес-логику термостата. А в учебном примере не должно ничего отвлекать от главного (говорю уверенно, потому что бывший учитель), поэтому многого необходимого нет.

По вашему коду. У вас во всех трех правилах алгоритм один и тот-же. Проще написать одно правило:

  defineRule("termostatController",{ 
  whenChanged: [TS_onoff, setpoint, temp], 
  then: function () {
	if (dev[TS_onoff]) {
    	if ( dev[temp] < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис
      	dev[TS] = true;
    	}
    	if ( dev[temp] > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис
      	dev[TS] = false;
    	}
    }
    else dev[TS] = false; 
  }
});

В данном примере TS_onoff, setpoint и temp должны быть строками вида “имя устройства/имя параметра”. Т.е. и уставку надо хранить в виртуальном устройстве.

И это тоже учебный пример, можно придумать другие варианты. Пишите так, как вам удобно и понятно.

1 лайк

Посмотрите еще статью:

Там я как раз описываю не реально воплощенную, но законченную конструкцию. Если будут замечания к коду - пишите прямо в комментах к статье, буду вам очень благодарен за замечания.

Дмитрий, по поводу строгости и справедливости :slight_smile:
Я начинающий вайренбордист, и столкнулся с тем, что вьехать в ВБ для начинающего очень сложно. Одна из причин - как раз такие образцы из вики. Для совсем новичков они дают неправильные примеры, а для людей которые уже освоились - бесполезны, потому что эти люди сами смогут писать такие или даже лучше варианты.

Смотрите, я изучаю ВБ. Захожу в раздел вики, посвященный примерам правил для ВБ. Естественно, я ожидаю там эталонные образцы кода - как делать правильно, чтобы на их основе, и с дополнительными знаниями после обучения делать правильные проекты. И как минимум тот же термостат в виде примера кода должен работать
а) идеально, чтобы не спалить теплый пол
б) должен обладать простой и понятной логикой - это как купить обычный механический термостат в мерлене. Вот кнопка вкл-выкл, вот крутилка температуры, вот датчик, вот логика его работы
в) должен быть не выдернутым куском кода другого приложения, а законченным образцом - как делать хорошо и правильно.
И тогда люди, которые учатся работать на вашем оборудовании по вашим обучалкам и примерам из вики, будут создавать новые решения правильно и красиво.

Возвращаясь к куску кода про термостат - получается, что просто из топика поддержки вырезали кусок кода какого-то гражданина, и положили его как образец. То, что кусок кода может вообще не выполнить свой функционал - это прям провал. То, что кусок кода не может корректно инициализировать устройство (то есть понять свой статус и запустить логику, а не ждать у моря погоды, которая сменит температуру датчика) - это тоже провал.

Именно это поразило меня, конечно. Естественно, что претензий к проекту, откуда взялся этот код, у меня нет.

Но я прям 100500% уверен, что вы согласитесь - если изменить раздел про термостат, добавив в него предложенный вами вариант инициализации вместо моего корявого, получится корректно работающий, правильный образец термостата, который можно уже обвешивать функционалом или взять как есть, просто скопипастив, и сэкономить кому-то вечер при запуске своего первого проекта :slight_smile:

А вам большое человеческое спасибо за предложенный вариант, он красивше моего. Пошел читать вашу статью :slight_smile:

Вот, отловил первый баг. Если на датчике 29,1 градуса, а бегунок термостата сдвинули с 30 до 29, то термостат не выключится, хотя должен.
Значит, придется все таки разбивать эту конструкцию

whenChanged: [TS_onoff, setpoint, temp]

хотя бы на две части. В первой отрабатывать TS_onoff и temp, а во второй при обработке setpoint убрать учет hysteresis

Почему? При изменении dev[setpoint] вызовется правило, и сработает триггер

if ( dev[temp] > dev[setpoint] + hysteresis)

если hysteresis < 0.1 гр. А если больше, то и срабатывать не должен.

Ну все верно. Только если у меня на термометре 29,1 гр, гистерисис 0,1, а сетпоинт сменился с 30 на 29, то термостат продолжить работать. А это неправильно - я сменил бегунок на 29, значит, при значении 29,1 термостат должен выключиться, а он не выключается - гист 0,1 не дает. Поэтому при изменении контрола сетпоинт нужно значение гистера не учитывать

Почему? (29.1 > 29 + 0.1) == false

потому что когда пользователь прибора крутит ручку, а у него при установленной температуре 29 и фактической 29,1 термостат работает, пользователь видит некорректную работу термостата. То что у него там значение может плавать ±0,1 пользователя не волнует. Да, с точки зрения термостата он все делает по правилу (29.1 > 29 + 0.1) == false.
Только правило кривое, ошибка логики того, кто это правило писал.
При работе термостата он действительно поддерживает температуру t±0,1, это нужно в том числе для экономии циклов реле
А при взаимодействии с пользователем (крутим ручку) он должен четко отрабатывать логику без всяких допусков. Человеку видит, что при установленной 29 прибор продолжает работать при 29,1. Значит, прибор сломан

Глубоко копаете. Если погружаться в глубины психики, то соглашусь. Интересно посмотреть на финальный результат вашего скрипта :grinning: Поделитесь?
Можете в это же правило добавить проверку на причину вызова сработки правила

if (setpoint == devName + '/' + cellName) { ...

да, маньяк я в некоторых вещах. Просто если я в машине отпускаю педаль газа я жду немедленной реакции, а не джиттера ±5кмч :slight_smile:
Пока вместо проверки разбил правило на два. Первое учитывает дребезг температуры и работает на изменение выключателя и датчика. Второе работает на изменение сетпоинт крутилки и не использует гистерисис. Так просто нагляднее для меня как для новичка.
Щас вдохновлюсь ваше статьей и попробую подружить ПВУ электролюкс ервх600инв и реле клапанов в комнаты. А уже если удастся - тогда буду извращаться с приточкой аэролайф б600 и вытяжкой из запчастей франкенштейна. Вот там ваша статья как нельзя кстати будет как образец.

1 лайк

Я просто добавил условия для срабатывания правил:

Спойлер

 //Temperature sensors: "xxxxxxxx" - replace with the address of the temperature sensor, specify the correct id of the relay module  
var TempSensor1 = "wb-w1/28-000000084ffc"; // Master SU
var TempSensor2 = "wb-w1/28-0621b4b9c8ac"; // Guest SU
var TempSensor3 = "wb-w1/28-3c4df64859e8"; // Entrance
var TempSensor4 = "wb-w1/28-3c2cf648db25"; // Bathroom
var TempSensor5 = "wb-w1/28-3cf5f648aa1b"; // SU
  
//Chanels of relay module for heating controlling
var CircuitControl1 = "wb-mr6c_64/K3"; // Master SU
var CircuitControl2 = "wb-mr6c_64/K2"; // Guest SU
var CircuitControl3 = "wb-mr6c_64/K1"; // Entrance
var CircuitControl4 = "wb-mr6c_64/K4"; // Bathroom
var CircuitControl5 = "wb-mr6c_64/K5"; // SU
  
  
//Settings
var Tsafety = 10;   //Upper temperature limit of the heating element,
var Thyst = 1;

//Virtual device for floor heating control
var Floor = { 
    OnOff1: {             //Button for turning on circuit
        type : "switch",
        title: "Zone 1 control",
        value: false,
        readonly: false,
        order: 1
       },
    OnOff2: {             //Button for turning on circuit
        type : "switch",
        title: "Zone 2 control",
        value : false,
        readonly: false,
        order: 2
      },  
    OnOff3: {             //Button for turning on circuit
        type : "switch",
        title: "Zone 3 control",
        value: false,
        readonly: false,
        order: 3
       },
    OnOff4: {             //Button for turning on circuit
        type : "switch",
        title: "Zone 4 control",
        value : false,
        readonly: false,
        order: 4
      },  
    OnOff5: {             //Button for turning on circuit
        type : "switch",
        title: "Zone 5 control",
        value: false,
        readonly: false,
        order: 5
       },
    IsOn1: {              //Circuit in heating mode
        type : "text",
        title: "Zone 1 action",
        value : "unknown",
        readonly: true,
        order: 6
      },
    IsOn2: {              //Circuit in heating mode
        type : "text",
        title: "Zone 2 action",
        value : "unknown",
        readonly: true,
        order: 7
      },
    IsOn3: {              //Circuit in heating mode
        type : "text",
        title: "Zone 3 action",
        value : "unknown",
        readonly: true,
        order: 8
      },
    IsOn4: {              //Circuit in heating mode
        type : "text",
        title: "Zone 4 action",
        value : "unknown",
        readonly: true,
        order: 9
      },
    IsOn5: {              //Circuit in heating mode
        type : "text",
        title: "Zone 5 action",
        value : "unknown",
        readonly: true,
        order: 10
      },
    Temp1: {              //Primary heating element temperature
        type : "temperature",
        title: "Zone 1 current temperature",
        value: 0,
        order: 11
      },
    Temp2: {              //Second heating element temperature
        type : "temperature",
        title: "Zone 2 current temperature",
        value: 0,
        order: 12
      },
    Temp3: {              //Primary heating element temperature
        type : "temperature",
        title: "Zone 3 current temperature",
        value: 0,
        order: 13
      },
    Temp4: {              //Second heating element temperature
        type : "temperature",
        title: "Zone 4 current temperature",
        value: 0,
        order: 14
      },
    Temp5: {              //Primary heating element temperature
        type : "temperature",
        title: "Zone 5 current temperature",
        value: 0,
        order: 15
      },
    TempSet1: {              //Temperature set point
        type : "temperature",
        title: "Zone 1 set temperature",
        value: Tsafety,
        order: 16,
        readonly: false    
      },
    TempSet2: {              //Temperature set point
        type : "temperature",
        title: "Zone 2 set temperature",
        value: Tsafety,
        order: 17,
        readonly: false    
      },
    TempSet3: {              //Temperature set point
        type : "temperature",
        title: "Zone 3 set temperature",
        value: Tsafety,
        order: 18,
        readonly: false    
      },
    TempSet4: {              //Temperature set point
        type : "temperature",
        title: "Zone 4 set temperature",
        value: Tsafety,
        order: 19,
        readonly: false    
      },
    TempSet5: {              //Temperature set point
        type : "temperature",
        title: "Zone 5 set temperature",
        value: Tsafety,
        order: 20,
        readonly: false    
      },
    };
    
      defineVirtualDevice("Heating", {
        title:"Floor heating",         
        cells: Floor
      });
  
    //Circuit control
    defineRule("Heating1",  {                
      whenChanged: [TempSensor1, "Heating/OnOff1", "Heating/TempSet1", "system/Current uptime"],
      then: function () {
        if((dev[TempSensor1] < (dev["Heating/TempSet1"] - Thyst)) && dev["Heating/OnOff1"] == true ) { 
        dev[CircuitControl1] = true;
        dev["Heating/IsOn1"] = "heating"
        };
        if((dev[TempSensor1] > (dev["Heating/TempSet1"] + Thyst)) || dev["Heating/OnOff1"] == false) { 
        dev[CircuitControl1] = false;
        dev["Heating/IsOn1"] = "idle"
        };
        dev["Heating/Temp1"] = dev[TempSensor1];
      }
    });
    
    //Circuit control
    defineRule("Heating2",  {                
      whenChanged: [TempSensor2, "Heating/OnOff2", "Heating/TempSet2", "system/Current uptime"],
      then: function () {
        if((dev[TempSensor2] < (dev["Heating/TempSet2"] - Thyst)) && dev["Heating/OnOff2"] == true ) { 
        dev[CircuitControl2] = true;
        dev["Heating/IsOn2"] = "heating"
        };
        if((dev[TempSensor2] > (dev["Heating/TempSet2"] + Thyst)) || dev["Heating/OnOff2"] == false) { 
        dev[CircuitControl2] = false;
        dev["Heating/IsOn2"] = "idle"
        };
        dev["Heating/Temp2"] = dev[TempSensor2];
      }
    });

    //Circuit control
    defineRule("Heating3",  {                
      whenChanged: [TempSensor3, "Heating/OnOff3", "Heating/TempSet3", "system/Current uptime"],
      then: function () {
        if((dev[TempSensor3] < (dev["Heating/TempSet3"] - Thyst)) && dev["Heating/OnOff3"] == true ) { 
        dev[CircuitControl3] = true;
        dev["Heating/IsOn3"] = "heating"
        };
        if((dev[TempSensor3] > (dev["Heating/TempSet3"] + Thyst)) || dev["Heating/OnOff3"] == false) { 
        dev[CircuitControl3] = false;
        dev["Heating/IsOn3"] = "idle"
        };
        dev["Heating/Temp3"] = dev[TempSensor3];
      }
    });
    
    //Circuit control
    defineRule("Heating4",  {                
      whenChanged: [TempSensor4, "Heating/OnOff4", "Heating/TempSet4", "system/Current uptime"],
      then: function () {
        if((dev[TempSensor4] < (dev["Heating/TempSet4"] - Thyst)) && dev["Heating/OnOff4"] == true ) { 
        dev[CircuitControl4] = true;
        dev["Heating/IsOn4"] = "heating"
        };
        if((dev[TempSensor4] > (dev["Heating/TempSet4"] + Thyst)) || dev["Heating/OnOff4"] == false) { 
        dev[CircuitControl4] = false;
        dev["Heating/IsOn4"] = "idle"
        };
        dev["Heating/Temp4"] = dev[TempSensor4];
      }
    });

    //Circuit control
    defineRule("Heating5",  {                
      whenChanged: [TempSensor5, "Heating/OnOff5", "Heating/TempSet5", "system/Current uptime"],
      then: function () {
        if((dev[TempSensor5] < (dev["Heating/TempSet5"] - Thyst)) && dev["Heating/OnOff5"] == true ) { 
        dev[CircuitControl5] = true;
        dev["Heating/IsOn5"] = "heating"
        };
        if((dev[TempSensor5] > (dev["Heating/TempSet5"] + Thyst)) || dev["Heating/OnOff5"] == false) { 
        dev[CircuitControl5] = false;
        dev["Heating/IsOn5"] = "idle"
        };
        dev["Heating/Temp5"] = dev[TempSensor5];
      }
    });

я блин с букварем ВБ изучаю, а тут вы со своими && и || :slight_smile: Не пугайте новичков :slight_smile:
Я правильно понял, что у вас жестко зашита верхняя граница температуры в +10 градусов переменной tsafety и в дальнейшем она является ограничителем всех tempset?

Я этот скрипт отсюда стащил и доработал: Автоматизация электрического тёплого пола - Wiren Board

Это мой второй в жизни скрипт на WB Rules, так что тоже не эксперт. tsafety там осталась от примера, в скрипте не используется

спасиб, пойду почитаю, а потом сяду пилить виртуальный девайс для реле защиты от протечек, хочется чтобы он нормально показывал зону протечки и уведомление в телегу присылал