Автопреобразование boolean в wb-rules

Привет! Столкнулся с ситуацие когда часть логики не отрабатывает.
Причина в WARNING: converting value ‘false’ to boolean ‘true’
что в таком виде:

defineRule("thermostat_mode_switch", {
    whenChanged: "floor_thermostat/mode",
    then: function(newValue) {
        thermostatState.mode = newValue ? "auto" : "manual";
        
        // Изменение доступности ползунка ручной температуры
        dev["floor_thermostat/manual_setpoint#readonly"] = dev["floor_thermostat/mode"];

что в таком:

// Изменение доступности ползунка ручной температуры
        dev["floor_thermostat/manual_setpoint#readonly"] = newValue;

Подскажите, пожалуйста, правильный путь.

Добрый день!

Рекомендую проверить, какие именно данные приходят и какого они типа.

Какое/какие значения возвращает ваше устройство в переменную newValue: (“текст”, “число”, логическое значение true/false )?
Если неизвестно, то узнать это можно, например вот так:

then: function(newValue) {
        log( "правило thermostat_mode_switch получило значение newValue = ", newValue, " типа: ", typeof(newValue) );
        thermostatState.mode = newValue ? "auto" : "manual";

Т.е. вывести в отладочный лог полученное функцией значение и тип этого значения.

typeof newValue - вернет тип переменной newValue что-то из вариантов
“number” I “string” I “boolean” I “undefined” I “object” I “function”

Далее, исходя из того, какой тип переменной у вас есть - производите нужные вам логические действия согласно этому типу (т.е. в операторе if “сравнивайте” с соответствующим типом)

1 Like

Судя по описанию ошибки (предупреждения) , ваша функция получает ТЕКСТ “false” и вы пытаетесь использовать его как булево значение (истина/ложь). JS преобразовал “не пустой текст” как логическое значение “истина”.

const value1 = "hello";
const bool1 = Boolean(value1); // true

const value2 = 0;
const bool2 = Boolean(value2); // false

const value3 = null;
const bool3 = Boolean(value3); // false

const value4 = "false"; // String "false" is truthy
const bool4 = Boolean(value4); // true

Если это так (т.е. функция на вход получает текст “false” или иное “слово”) и вам нужно “ветвиться”, то преобразуйте это явным образом/

Например (не явный if() ),

var state =  (newValue === "false" ) ? false : true;

Если newValue есть ТЕКСТ “false”, то в переменную state верни булево значение false, иначе верни булево значение true

После этого можете использовать переменную state как логическую.
Например

dev["floor_thermostat/manual_setpoint#readonly"] = state; 

ну или без промежуточных переменных, если других “ветвлений” не предвидится

dev["floor_thermostat/manual_setpoint#readonly"] =  (newValue === "false" ) ? false : true ; 
2 Likes

Спасибо за совет! И вот что получил:

23-10-2025 16:02:43.972 [wb-rules]	WARNING: converting value 'false' to boolean 'true'
23-10-2025 16:02:43.965 [wb-rules]	INFO: [rule info] правило thermostat_mode_switch получило значение newValue =  false  типа:  boolean

23-10-2025 16:02:36.848 [wb-rules]	WARNING: converting value 'true' to boolean 'true'
23-10-2025 16:02:36.838 [wb-rules]	INFO: [rule info] правило thermostat_mode_switch получило значение newValue =  true  типа:  boolean

мало того, даже с явным приведем типа:

dev["floor_thermostat/manual_setpoint#readonly"] = Boolean(newValue);

по -прежнему WARNING: converting value ‘false’ to boolean ‘true’

Приведите пожалуйста весь текст вашей функции - может быть там есть еще проблемы…

Попробуйте “закоментировать” все лишние строки кода в этой функции,
оставив только вывод в лог (без передачи данных в вирт. устройство). Сохранится ли подобное предупреждение после этого?

без передачи в виртуальное устройство нет предупреждения:

23-10-2025 16:28:29.940 [wb-rules]	INFO: [rule info] правило thermostat_mode_switch получило значение newValue =  false  типа:  boolean
23-10-2025 16:28:29.199 [wb-rules]	INFO: [rule info] правило thermostat_mode_switch получило значение newValue =  true  типа:  boolean

весь скрипт:

Код
// =====================================================
// Виртуальный термостат для управления теплым полом
// Версия: 1.0
// =====================================================

// Конфигурация устройств
var CONFIG = {
    // Датчик температуры
    temperatureSensor: "wb-m1w2_228/External Sensor 1",
    // Реле управления нагревом
    heatingRelay: "wb-mrwm2_197/K1",
    // Гистерезис температуры (допустимое отклонение)
    hysteresis: 0.5,
    // Период обновления в секундах
    updatePeriod: 10,
    // Температура по умолчанию
    defaultTemp: 22.0,
    // Минимальная и максимальная температура
    minTemp: 15.0,
    maxTemp: 30.0
};

// Расписание для автоматического режима
// Формат: { день_недели: [ [час, минута, температура], ... ] }
var SCHEDULE = {
    // Понедельник - Пятница (рабочие дни)
    weekday: [
        [6, 30, 23.0],   // 06:30 - утренний подогрев
        [8, 30, 20.0],   // 08:30 - все ушли
        [17, 00, 22.0],  // 17:00 - вечерний подогрев
        [22, 30, 19.0]   // 22:30 - спим
    ],
    // Суббота - Воскресенье (выходные)
    weekend: [
        [8, 00, 23.0],   // 08:00 - утренний подогрев
        [23, 00, 19.0]   // 23:00 - спим
    ]
};

// Глобальные переменные для хранения состояния
var thermostatState = {
    mode: "manual",              // "manual" или "auto"
    manualSetpoint: CONFIG.defaultTemp,
    currentSetpoint: CONFIG.defaultTemp,
    currentTemp: null,
    isHeating: false,
    nextEvent: null,
    nextEventTemp: null,
    lastUpdateTime: 0
};

// =====================================================
// Описание виртуального устройства 
// =====================================================
defineVirtualDevice("floor_thermostat", {
    title: "Термостат теплого пола",
    cells: {
        // Переключатель режима работы
        mode: {
            type: "switch",
            value: false,  // false = ручной, true = автоматический
            title: "Автоматический режим"
        },
        
        // Ползунок для установки температуры в ручном режиме
        manual_setpoint: {
            type: "range",
            value: CONFIG.defaultTemp,
            min: CONFIG.minTemp,
            max: CONFIG.maxTemp,
            title: "Заданная температура (°C)",
            readonly: false
        },
        
        // Отображение текущей температуры
        current_temperature: {
            type: "value",
            value: 0,
            units: "°C",
            title: "Текущая температура",
            readonly: true
        },
        
        // Отображение активной заданной температуры
        active_setpoint: {
            type: "value",
            value: CONFIG.defaultTemp,
            units: "°C",
            title: "Активная уставка",
            readonly: true
        },
        
        // Индикатор работы нагрева
        heating_state: {
            type: "switch",
            value: false,
            title: "Нагрев",
            readonly: true
        },
        
        // Следующее событие расписания
        next_event: {
            type: "text",
            value: "---",
            title: "Следующее событие",
            readonly: true
        },
        
        // Температура следующего события
        next_event_temp: {
            type: "value",
            value: 0,
            units: "°C",
            title: "Температура события",
            readonly: true
        },
        
        // Статус системы
        status: {
            type: "text",
            value: "Инициализация...",
            title: "Статус",
            readonly: true
        }
    }
});

// =====================================================
// Вспомогательные функции
// =====================================================

// Получение текущего дня недели (0 = воскресенье, 6 = суббота)
function getCurrentDayType() {
    var day = new Date().getDay();
    return (day === 0 || day === 6) ? "weekend" : "weekday";
}

// Форматирование времени
function formatTime(hours, minutes) {
    var h = hours < 10 ? "0" + hours : hours;
    var m = minutes < 10 ? "0" + minutes : minutes;
    return h + ":" + m;
}

// Поиск следующего события в расписании
function findNextScheduleEvent() {
    var now = new Date();
    var currentHour = now.getHours();
    var currentMinute = now.getMinutes();
    var dayType = getCurrentDayType();
    var schedule = SCHEDULE[dayType];
    
    // Поиск следующего события сегодня
    for (var i = 0; i < schedule.length; i++) {
        var event = schedule[i];
        if (event[0] > currentHour || (event[0] === currentHour && event[1] > currentMinute)) {
            return {
                time: formatTime(event[0], event[1]),
                temp: event[2],
                today: true
            };
        }
    }
    
    // Если сегодня событий больше нет, берем первое событие завтра
	var tomorrowDay = (now.getDay() + 1) % 7;
    var tomorrowDayType = (tomorrowDay === 0 || tomorrowDay === 6) ? "weekend" : "weekday";
//    var tomorrowDayType = (now.getDay() === 5) ? "weekend" : 
//         (now.getDay() === 6) ? "weekday" : dayType;
    var tomorrowSchedule = SCHEDULE[tomorrowDayType];
    if (tomorrowSchedule.length > 0) {
        return {
            time: formatTime(tomorrowSchedule[0][0], tomorrowSchedule[0][1]) + " (завтра)",
            temp: tomorrowSchedule[0][2],
            today: false
        };
    }
    
    return null;
}

// Получение температуры для текущего времени из расписания
function getScheduleTemperature() {
    var now = new Date();
    var currentHour = now.getHours();
    var currentMinute = now.getMinutes();
    var dayType = getCurrentDayType();
    var schedule = SCHEDULE[dayType];
    
    var activeTemp = CONFIG.defaultTemp;
    
    // Находим последнее прошедшее событие
    for (var i = schedule.length - 1; i >= 0; i--) {
        var event = schedule[i];
        if (event[0] < currentHour || (event[0] === currentHour && event[1] <= currentMinute)) {
            activeTemp = event[2];
            break;
        }
    }
    
    // Если все события в будущем, берем последнее событие вчерашнего дня
    if (i < 0) {
		var yesterdayDay = (now.getDay() - 1 + 7) % 7;
        var yesterdayDayType = (yesterdayDay === 0 || yesterdayDay === 6) ? "weekend" : "weekday";
//        var yesterdayDayType = (now.getDay() === 1) ? "weekend" : 
//                              (now.getDay() === 0) ? "weekday" : dayType;
        var yesterdaySchedule = SCHEDULE[yesterdayDayType];
        if (yesterdaySchedule.length > 0) {
            activeTemp = yesterdaySchedule[yesterdaySchedule.length - 1][2];
        }
    }
    
    return activeTemp;
}

// Управление реле нагрева с гистерезисом
function controlHeating(currentTemp, setpoint) {
    if (currentTemp === null || currentTemp === undefined) {
        // Если нет данных о температуре, выключаем нагрев для безопасности
        dev[CONFIG.heatingRelay] = false;
        thermostatState.isHeating = false;
        return false;
    }
    
    var shouldHeat = false;
    
    if (thermostatState.isHeating) {
        // Если нагрев включен, выключаем при достижении уставки + половина гистерезиса
        shouldHeat = currentTemp < (setpoint + CONFIG.hysteresis / 2);
    } else {
        // Если нагрев выключен, включаем при падении ниже уставки - половина гистерезиса
        shouldHeat = currentTemp < (setpoint - CONFIG.hysteresis / 2);
    }
    
    thermostatState.isHeating = shouldHeat;
    dev[CONFIG.heatingRelay] = shouldHeat;
    
    return shouldHeat;
}

// Обновление статуса системы
function updateStatus() {
    var mode = thermostatState.mode;
    var heating = thermostatState.isHeating ? "ВКЛ" : "ВЫКЛ";
    var temp = thermostatState.currentTemp !== null ? 
               thermostatState.currentTemp.toFixed(1) + "°C" : "---";
    
    var status = "Режим: " + (mode === "auto" ? "Авто" : "Ручной") + 
                 " | Нагрев: " + heating + 
                 " | T: " + temp;
    
    dev["floor_thermostat/status"] = status;
}

// =====================================================
// Правила управления
// =====================================================

// Правило: Переключение режима работы
defineRule("thermostat_mode_switch", {
    whenChanged: "floor_thermostat/mode",
    then: function(newValue) {
        thermostatState.mode = newValue ? "auto" : "manual";
        
        // Изменение доступности ползунка ручной температуры
        dev["floor_thermostat/manual_setpoint#readonly"] = Boolean(newValue);
        log( "правило thermostat_mode_switch получило значение newValue = ", Boolean(newValue), " типа: ", typeof(Boolean(newValue)) );
        
        // Обновление уставки в зависимости от режима
        if (thermostatState.mode === "auto") {
            thermostatState.currentSetpoint = getScheduleTemperature();
            log.info("Термостат: переключен в автоматический режим");
        } else {
            thermostatState.currentSetpoint = thermostatState.manualSetpoint;
            log.info("Термостат: переключен в ручной режим");
        }
        
        dev["floor_thermostat/active_setpoint"] = thermostatState.currentSetpoint;
        updateStatus();
    }
});

// Правило: Изменение ручной уставки
defineRule("thermostat_manual_setpoint", {
    whenChanged: "floor_thermostat/manual_setpoint",
    then: function(newValue) {
        thermostatState.manualSetpoint = newValue;
        
        if (thermostatState.mode === "manual") {
            thermostatState.currentSetpoint = newValue;
            dev["floor_thermostat/active_setpoint"] = newValue;
            log.debug("Термостат: установлена температура " + newValue + "°C");
        }
    }
});

// Правило: Чтение температуры с датчика
defineRule("thermostat_read_temperature", {
    whenChanged: CONFIG.temperatureSensor,
    then: function(newValue) {
        if (newValue !== null && newValue !== undefined) {
            thermostatState.currentTemp = parseFloat(newValue);
            dev["floor_thermostat/current_temperature"] = thermostatState.currentTemp;
            
            // Управление нагревом
            var heating = controlHeating(thermostatState.currentTemp, thermostatState.currentSetpoint);
            dev["floor_thermostat/heating_state"] = heating;
            
            updateStatus();
        }
    }
});

// =====================================================
// Периодические задачи
// =====================================================

// Обновление расписания и состояния каждые 10 секунд
setInterval(function() {
    // Обновление температуры по расписанию в автоматическом режиме
    if (thermostatState.mode === "auto") {
        var scheduleTemp = getScheduleTemperature();
        if (Math.abs(scheduleTemp - thermostatState.currentSetpoint) > 0.1) {
            thermostatState.currentSetpoint = scheduleTemp;
            dev["floor_thermostat/active_setpoint"] = scheduleTemp;
            log.info("Термостат: температура по расписанию изменена на " + scheduleTemp + "°C");
        }
    }
    
    // Обновление информации о следующем событии
    var nextEvent = findNextScheduleEvent();
    if (nextEvent) {
        dev["floor_thermostat/next_event"] = nextEvent.time;
        dev["floor_thermostat/next_event_temp"] = nextEvent.temp;
    } else {
        dev["floor_thermostat/next_event"] = "---";
        dev["floor_thermostat/next_event_temp"] = 0;
    }
    
    // Проверка состояния нагрева
    if (thermostatState.currentTemp !== null) {
        var heating = controlHeating(thermostatState.currentTemp, thermostatState.currentSetpoint);
        dev["floor_thermostat/heating_state"] = heating;
    }
    
    updateStatus();
    
}, CONFIG.updatePeriod * 1000);

// =====================================================
// Инициализация при запуске
// =====================================================
setTimeout(function() {
    log.info("Термостат теплого пола: инициализация");
    
    // Установка начальных значений
    thermostatState.mode = dev["floor_thermostat/mode"] ? "auto" : "manual";
    thermostatState.manualSetpoint = dev["floor_thermostat/manual_setpoint"];
    
    if (thermostatState.mode === "auto") {
        thermostatState.currentSetpoint = getScheduleTemperature();
        dev["floor_thermostat/manual_setpoint#readonly"] = true;
    } else {
        thermostatState.currentSetpoint = thermostatState.manualSetpoint;
        dev["floor_thermostat/manual_setpoint#readonly"] = false;
    }
    
    dev["floor_thermostat/active_setpoint"] = thermostatState.currentSetpoint;
    
    // Попытка прочитать текущую температуру
    if (dev[CONFIG.temperatureSensor] !== undefined) {
        thermostatState.currentTemp = parseFloat(dev[CONFIG.temperatureSensor]);
        dev["floor_thermostat/current_temperature"] = thermostatState.currentTemp;
    }
    
    updateStatus();
    log.info("Термостат теплого пола: инициализация завершена");
    
}, 1000);
// Правило: Переключение режима работы
defineRule("thermostat_mode_switch", {
    whenChanged: "floor_thermostat/mode",
    then: function(newValue) {
        thermostatState.mode = newValue ? "auto" : "manual";
        
        // Изменение доступности ползунка ручной температуры
        dev["floor_thermostat/manual_setpoint#readonly"] = Boolean(newValue);
        log( "правило thermostat_mode_switch получило значение newValue = ", Boolean(newValue), " типа: ", typeof(Boolean(newValue)) );
        
        // Обновление уставки в зависимости от режима
        if (thermostatState.mode === "auto") {
            thermostatState.currentSetpoint = getScheduleTemperature();
            log.info("Термостат: переключен в автоматический режим");
        } else {
            thermostatState.currentSetpoint = thermostatState.manualSetpoint;
            log.info("Термостат: переключен в ручной режим");
        }
        
        dev["floor_thermostat/active_setpoint"] = thermostatState.currentSetpoint;
        updateStatus();
    }
});

Я бы предложил “отключить” весь код в этом правиле (если это именно оно вызывает ошибку/предупреждение) и постепенно подключал бы строки кода с контролем момента возникновения ошибки (возможно с промежуточным выводом в лог значения тех или иных переменных).
Т.е. необходимо найти конкретную строку (действие) которая вызывает данное поведение (предупреждение).

Вплоть до проверки исполнения путем явной подстановки значений.
Например
dev["floor_thermostat/manual_setpoint#readonly"] = true;
вместо
dev["floor_thermostat/manual_setpoint#readonly"] = Boolean(newValue);

или даже “глупого” прямого присвоения

    then: function(newValue) {
     newValue = true;
     и далее текст функции

PS а на этом этапе
thermostatState.mode = newValue ? "auto" : "manual";
Ошибки не возникает? Здесь как минимум идет первая попытка использовать newValue как булевую переменную (значение)…

У меня ваш скрипт работает без подобной ошибки.

Единственное, статус ридонли у меня стал переключаться на от false/true от 0/1

Вот в таком виде

        // Изменение доступности ползунка ручной температуры
        dev["floor_thermostat/manual_setpoint#readonly"] =  Boolean(newValue) ? 0:1;

и тоже самое в функции setTimeout()

...
    if (thermostatState.mode === "auto") {
        thermostatState.currentSetpoint = getScheduleTemperature();
        dev["floor_thermostat/manual_setpoint#readonly"] = 1;
    } else {
        thermostatState.currentSetpoint = thermostatState.manualSetpoint;
        dev["floor_thermostat/manual_setpoint#readonly"] = 0;
    }
...

При этом ни в одном из вариантов предупреждения не появляются.

Ошибки не возникает?

Нет, все нормально:

INFO: [rule info]  значение thermostatState.mode =  manual  типа:  string
INFO: [rule info]  значение thermostatState.mode =  auto  типа:  string

Да и вот тут даже с прямым присваиванием особенность воспроизводится

WARNING: converting value 'false' to boolean 'true'

Вариант с 0/1 тоже пробовал и да, так действительно работает.

Видимо правда где-то рядом…
Документация нам говорит о setReadonly(string), getReadonly() => boolean
а еще вот тут похожая ситуация

Добрый день,
Удалось ли решить вопрос?

Здравствуйте! Ну как сказать. Есть опереденная путанница с инициализацией в true|false и управлением в 0|1 Если бы явно описали в документации или привели к одному стилю, было бы проше, конечно )

Здравствуйте.
Это связано с тем что в MQTT-топиках исторически много где используются 0/1 (как «уровень сигнала», digital state), а движке правил — true/false, потому что это JavaScript и там логика булевая.
Сложность в том что приведение к единому стилю процесс очень длительный.

Здравствуйте! Если явно опишете в документации, это будет плюсом, понятнее и прозрачнее. Спасибо!

Добрый день!

Передал ваше пожелание в отдел документации.
Могу ли я ещё чем-то помочь?

1 Like