Привет, читатель! В этой публикации хочу рассказать, как я запилил себе интерфейс управления кондиционером в Home Assistant.
Недавно приобрёл себе кондиционер Gree Lomo Inverter R32, сразу заказал под него модуль от ONOKOM для интеграции с Wirenboard.
А вот как выглядит модуль
Нюансы подключения модуля
Модуль подключил с помощью видеоинструкции вместо Wi-Fi модуля.
У меня на шине скорость 115200, модуль изначально был на скорости 9600, но производитель любезно предоставил мне инструкцию по перепрошивке модуля для поддержки регистра, в который можно прописать другую скорость. Возможно у вас модуль будет уже прошит и не понадобится это делать.
Процесс перепрошивки не сложный, правда софт там под Windows, но, несмотря на то, что у меня OS X, перепрошить можно через VirtualBox.
У меня при первой попытке прокинуть устройство возникла ошибка Can't attach USB device
Это лечится при помощи запуска VirtualBox через sudo, чтобы появилась возможность прокинуть устройство модуля в виртуалку. Да, и там важно, чтобы USB Type-C кабель был с поддержкой передачи данных, мой штатный зарядный кабель не обнаружил устройство.
Вот скриншот, где прокидывается устройство.
после успешной перепрошивки будет выглядеть примерно так
И еще один нюанс, до подключения модуля к шине, позаботьтесь о смене Modbus Slave ID (при необходимости), так как по умолчанию у модуля Slave ID === 1. Сделать это можно при помощи утилиты modbus_client (внимательно читаем документацию!)
После подключения модуля к контроллеру и установки шаблона в Wirenboard (процесс описан в видеоинструкции от ONOKOM, ссылка выше. Мы получаем вот такой базовый интерфейс
Создание виртуального устройства
В штатном функционале есть некоторые неудобства:
- Основной режим работы задаётся в виде integer-значения, это не очень удобно, т.к. надо запоминать соответствие цифры режиму.
- То же самое касается режимов работы горизонтальных и вертикальных шторок.
В таком виде интегрироваться с Home Assistant неудобно, поэтому я решил воспользоваться виртуальным устройством Wirenboard, где поведение можно поправить "под себя".
Для удобства пользования в скрипте wb-rules все значения хранятся в переменных. Логику работы функций описывать не буду, чтобы не засорять эфис, она там не сложная. Оставил все необходимые комментарии. Код скрипта разместил в конце публикации.
Экспорт виртуального устройства
Для экспорта виртуального устройства и всех его контролов в HA использовал wb-engine. Там ставится deb-пакет, а дальше заходим в настройки
Прокликиваем все галки виртуального устройства.
После сохранения у вас должно появиться виртуальное устройство.
Убеждаемся что оно прокинулось в Home Assistant.
Настройка Home Assistant
Я у себя на дашбордах не использую какие-то дополнительные интерфейсы, один раз поставил Lovelace и у меня после ребута всё отвалилось, с тех пор решил пользоваться только штатным функционалом.
Изначально я обдумывал вариант с управлением через mqtt.climate, там термостат, в котором можно настроить управление температурой, отображать температуру с датчика, выставлять разные режимы, управлять скоростями вентилятора, пресетами, и даже шторками, но есть существенные (по моему мнению) ограничения:но интерфейс ограничен, чтобы настроить режим
- для управления скоростями вентилятора нужно провалиться в настройки карточки
- для управления пресетами надо провалиться в настройки карточки
- для управления шторками предусмотрено всего два режима - выключено и автоматически (нет возможности управлять вручную)
- нет управления отдельно горизонтальными/вертикальными шторками, не знаю насколько это важно для вас, но я хотел реализовать весь функционал
В принципе, можно было рассмотреть вариант с гибридной карточкой, чтобы в верхней части был термостат для управления температурой и режимами, а ниже разместить пресеты, вентилятор и шторки, и я даже в конфиге создал устройство, и оно даже заработало, выглядело это вот так
Но у меня возникли какие-то проблемы с сохранением уставок, почему-то после релоада конфигурации в панели разработчика значения сбрасывались + не значение не доходило до реального устройства, я не стал долго тратить время и решил пойти по пути создания собственной карточки. В конце публикации на всякий случай выложил кусок конфига с этим термостатом.
У меня есть панель для отображения всего, что касается климата. На панели размещены 4 (по количеству помещений) карточки типа "вертикальный стек". Одним из элементов карточки, относящейся к помещению, где установлен кондиционер, является карточка типа "сетка", количество столбцов - 1, отключил опцию "квадратные карточки". Каждый элемент сетки - горизонтальный стек, элементами горизонтального стека являются контролы. Выглядит это вот так:
В принципе, получилось удобно, хотелось бы, конечно, крутилку, как в термостате, но уже привык к такому варианту. Желаю всем удачи! Отвечу на вопросы в комментариях.
Код скрипта
Выкладываю код скрипта на wb-rules
var realDeviceName = "ONOKOM-AIR-GR-1-MB-B_10";
var virtDeviceName = "virtual-conditioner-livingroom"
var targetTempMin = 16
var targetTempMax = 30
var targetTempDefault = 20
var fanSpeedMin = 0
var fanSpeedMax = 3
var fanSpeedAuto = 0
var fanSpeed1 = 1
var fanSpeed2 = 2
var fanSpeed3 = 3
var fanSpeedDefault = fanSpeedAuto
var modeAuto = 3
var modeCool = 2
var modeDry = 4
var modeFan = 5
var modeHeat = 1
var modeMaxVal = 5
var modeMinVal = 1
var modeDefault = modeCool
var horizontalVanesMin = 0
var horizontalVanesMax = 6
var horizontalVanesDisable = 0
var horizontalVanesAuto = 1
var horizontalVanesExtremeDown = 2
var horizontalVanesExtremeUp = 6
var horizontalVanesMiddle = 4
var horizontalVanesDefault = horizontalVanesMiddle
var verticalVanesMin = 0
var verticalVanesMax = 6
var verticalVanesDisable = 0
var verticalVanesAuto = 1
var verticalVanesExtremeLeft = 2
var verticalVanesExtremeRight = 6
var verticalVanesMiddle = 4
var verticalVanesDefault = verticalVanesMiddle
defineVirtualDevice(virtDeviceName, {
title: virtDeviceName,
cells: {
//зеркалирование топика Active
enabled: {
type: "switch",
value: false,
},
//зеркалирование топика Screen light
light: {
type: "switch",
value: false,
},
//зеркалирование топика Quiet mode
quietMode: {
type: "switch",
value: false,
},
//зеркалирование топика Turbo mode
turboMode: {
type: "switch",
value: false,
},
//зеркалирование топика Eco mode
ecoMode: {
type: "switch",
value: false,
},
//зеркалирование топика Sleep mode
sleepMode: {
type: "switch",
value: false,
},
//зеркалирование топика Ionization
ionization: {
type: "switch",
value: false,
},
//зеркалирование топика Target temperature
targetTemp: {
type : "range",
value : targetTempDefault,
max : targetTempMax,
min : targetTempMin,
},
//устанавливает температуру на 16
targetTemp16: {
type: "pushbutton"
},
//устанавливает температуру на 18
targetTemp18: {
type: "pushbutton"
},
//устанавливает температуру на 20
targetTemp20: {
type: "pushbutton"
},
//устанавливает температуру на 21
targetTemp21: {
type: "pushbutton"
},
//устанавливает температуру на 22
targetTemp22: {
type: "pushbutton"
},
//зеркалирует топик Fan speed
fanSpeed: {
type : "range",
value : fanSpeedDefault,
max : fanSpeedMax,
min : fanSpeedMin,
},
//устанавливает скорость вентилятора в авто-режим
fanSpeedAuto: {
type: "switch",
value: false,
},
//устанавливает скорость вентилятора на 1
fanSpeed1: {
type: "switch",
value: false,
},
//устанавливает скорость вентилятора на 2
fanSpeed2: {
type: "switch",
value: false,
},
//устанавливает скорость вентилятора на 3
fanSpeed3: {
type: "switch",
value: false,
},
//зеркалирует топик Mode
mode: {
type : "range",
value : modeDefault,
max : modeMaxVal,
min : modeMinVal,
},
//устанавливает режим кондиционера на авто
modeAuto: {
type: "switch",
value: false,
},
//устанавливает режим кондиционера на охлаждение
modeCool: {
type: "switch",
value: false,
},
//устанавливает режим кондиционера на осушение
modeDry: {
type: "switch",
value: false,
},
//устанавливает режим кондиционера на вентилирование
modeFan: {
type: "switch",
value: false,
},
//устанавливает режим кондиционера на нагрев
modeHeat: {
type: "switch",
value: false,
},
//зеркалирование топика Horizontal vanes
horizontalVanes: {
type : "range",
value : horizontalVanesDefault,
max : horizontalVanesMax,
min : horizontalVanesMin,
},
//устанавливает режим горизонтальных шторок в режим свинга
horizontalAuto: {
type: "switch",
value: false,
},
//выключаем режим свинга на горизонтальных шторках
horizontalDisable: {
type: "switch",
value: false,
},
//подвинуть горизонтальные шторки вверх на 1 деление, если был включен режим свинга или выключены - подвинет в крайнее верхнее положение
horizontalMoveUp: {
type: "pushbutton",
},
//подвинуть горизонтальные шторки вниз на 1 деление, если был включен режим свинга или выключены - подвинет в крайнее нижнее положение
horizontalMoveDown: {
type: "pushbutton",
},
//зеркалирование топика Vertical vanes
verticalVanes: {
type : "range",
value : verticalVanesDefault,
max : verticalVanesMax,
min : verticalVanesMin,
},
//устанавливает режим вертикальных шторок в режим свинга
verticalAuto: {
type: "switch",
value: false,
},
//выключаем режим свинга на вертикальных шторках
verticalDisable: {
type: "switch",
value: false,
},
//подвинуть вертикальных шторки вправо на 1 деление, если был включен режим свинга или выключены - подвинет в крайнее правое положение
verticalMoveRight: {
type: "pushbutton",
},
//подвинуть вертикальных шторки влево на 1 деление, если был включен режим свинга или выключены - подвинет в крайнее левое положение
verticalMoveLeft: {
type: "pushbutton",
},
}
})
/*
ВКЛЮЧЕНИЕ/ВЫКЛЮЧЕНИЕ + ПОДСВЕТКА
*/
defineRule("{}/enabled".format(virtDeviceName), {
whenChanged: "{}/enabled".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Active"] = newValue
}
})
defineRule("{}/light".format(virtDeviceName), {
whenChanged: "{}/light".format(virtDeviceName),
then: function (newValue, devName, cellName) {
//конвертируем булевое значение свитча в 1/0
if(newValue){
dev[realDeviceName]["Screen light"] = 1
} else {
dev[realDeviceName]["Screen light"] = 0
}
}
})
/*
УПРАВЛЕНИЕ ТЕМПЕРАТУРОЙ
*/
defineRule("{}/targetTemp".format(virtDeviceName), {
whenChanged: "{}/targetTemp".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Target temperature"] = newValue
}
})
defineRule("{}/targetTemp16".format(virtDeviceName), {
whenChanged: "{}/targetTemp16".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[virtDeviceName]['targetTemp'] = 16
}
})
defineRule("{}/targetTemp18".format(virtDeviceName), {
whenChanged: "{}/targetTemp18".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[virtDeviceName]['targetTemp'] = 18
}
})
defineRule("{}/targetTemp20".format(virtDeviceName), {
whenChanged: "{}/targetTemp20".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[virtDeviceName]['targetTemp'] = 20
}
})
defineRule("{}/targetTemp21".format(virtDeviceName), {
whenChanged: "{}/targetTemp21".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[virtDeviceName]['targetTemp'] = 21
}
})
defineRule("{}/targetTemp22".format(virtDeviceName), {
whenChanged: "{}/targetTemp22".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[virtDeviceName]['targetTemp'] = 22
}
})
/*
ОСНОВНЫЕ РЕЖИМЫ (взаимоисключаемые)
*/
defineRule("{}/mode".format(virtDeviceName), {
whenChanged: "{}/mode".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Mode"] = newValue
if(newValue === modeAuto && !dev[virtDeviceName]["modeAuto"]) {
dev[virtDeviceName]["modeAuto"] = true
}
if(newValue === modeCool && !dev[virtDeviceName]["modeCool"]) {
dev[virtDeviceName]["modeCool"] = true
}
if(newValue === modeDry && !dev[virtDeviceName]["modeDry"]) {
dev[virtDeviceName]["modeDry"] = true
}
if(newValue === modeFan && !dev[virtDeviceName]["modeFan"]) {
dev[virtDeviceName]["modeFan"] = true
}
if(newValue === modeHeat && !dev[virtDeviceName]["modeHeat"]) {
dev[virtDeviceName]["modeHeat"] = true
}
}
})
var checkModes = function() {
if(!dev[virtDeviceName]["modeAuto"] && !dev[virtDeviceName]["modeCool"] && !dev[virtDeviceName]["modeDry"] && !dev[virtDeviceName]["modeFan"] && !dev[virtDeviceName]["modeHeat"]) {
dev[virtDeviceName]["enabled"] = false
} if(dev[virtDeviceName]["modeAuto"] || dev[virtDeviceName]["modeCool"] || dev[virtDeviceName]["modeDry"] || dev[virtDeviceName]["modeFan"] || dev[virtDeviceName]["modeHeat"]) {
dev[virtDeviceName]["enabled"] = true
}
}
defineRule("{}/modeAuto".format(virtDeviceName), {
whenChanged: "{}/modeAuto".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]["mode"] = modeAuto
dev[virtDeviceName]["modeCool"] = false
dev[virtDeviceName]["modeDry"] = false
dev[virtDeviceName]["modeFan"] = false
dev[virtDeviceName]["modeHeat"] = false
}
checkModes()
}
})
defineRule("{}/modeCool".format(virtDeviceName), {
whenChanged: "{}/modeCool".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]["mode"] = modeCool
dev[virtDeviceName]["modeAuto"] = false
dev[virtDeviceName]["modeDry"] = false
dev[virtDeviceName]["modeFan"] = false
dev[virtDeviceName]["modeHeat"] = false
}
checkModes()
}
})
defineRule("{}/modeDry".format(virtDeviceName), {
whenChanged: "{}/modeDry".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]["mode"] = modeDry
dev[virtDeviceName]["modeAuto"] = false
dev[virtDeviceName]["modeCool"] = false
dev[virtDeviceName]["modeFan"] = false
dev[virtDeviceName]["modeHeat"] = false
}
checkModes()
}
})
defineRule("{}/modeFan".format(virtDeviceName), {
whenChanged: "{}/modeFan".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]["mode"] = modeFan
dev[virtDeviceName]["modeAuto"] = false
dev[virtDeviceName]["modeCool"] = false
dev[virtDeviceName]["modeDry"] = false
dev[virtDeviceName]["modeHeat"] = false
}
checkModes()
}
})
defineRule("{}/modeHeat".format(virtDeviceName), {
whenChanged: "{}/modeHeat".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]["mode"] = modeHeat
dev[virtDeviceName]["modeAuto"] = false
dev[virtDeviceName]["modeCool"] = false
dev[virtDeviceName]["modeDry"] = false
dev[virtDeviceName]["modeFan"] = false
}
checkModes()
}
})
/*
ПРЕСЕТЫ
*/
defineRule("{}/quietMode".format(virtDeviceName), {
whenChanged: "{}/quietMode".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Quiet mode"] = newValue
//взаимоисключаемость (повторяем реализацию из шаблона)
if(newValue){
dev[virtDeviceName]['turboMode'] = false
}
}
})
defineRule("{}/turboMode".format(virtDeviceName), {
whenChanged: "{}/turboMode".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Turbo mode"] = newValue
//взаимоисключаемость (повторяем реализацию из шаблона)
if(newValue){
dev[virtDeviceName]['quietMode'] = false
}
}
})
defineRule("{}/ecoMode".format(virtDeviceName), {
whenChanged: "{}/ecoMode".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Eco mode"] = newValue
}
})
defineRule("{}/sleepMode".format(virtDeviceName), {
whenChanged: "{}/sleepMode".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Sleep mode"] = newValue
}
})
defineRule("{}/ionization".format(virtDeviceName), {
whenChanged: "{}/ionization".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Ionization"] = newValue
}
})
/*
УПРАВЛЕНИЕ ВЕНТИЛЯТОРОМ
*/
defineRule("{}/fanSpeed".format(virtDeviceName), {
whenChanged: "{}/fanSpeed".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]["Fan speed"] = newValue
if(newValue === fanSpeedAuto && !dev[virtDeviceName]['fanSpeedAuto']){
dev[virtDeviceName]['fanSpeedAuto'] = true
}
if(newValue === fanSpeed1 && !dev[virtDeviceName]['fanSpeed1']){
dev[virtDeviceName]['fanSpeed1'] = true
}
if(newValue === fanSpeed2 && !dev[virtDeviceName]['fanSpeed2']){
dev[virtDeviceName]['fanSpeed2'] = true
}
if(newValue === fanSpeed3 && !dev[virtDeviceName]['fanSpeed3']){
dev[virtDeviceName]['fanSpeed3'] = true
}
}
})
var checkFan = function() {
if(!dev[virtDeviceName]["fanSpeed1"] && !dev[virtDeviceName]["fanSpeed2"] && !dev[virtDeviceName]["fanSpeed3"]) {
dev[virtDeviceName]["fanSpeedAuto"] = true
}
}
defineRule("{}/fanSpeedAuto".format(virtDeviceName), {
whenChanged: "{}/fanSpeedAuto".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
if(dev[virtDeviceName]["fanSpeed"] !== fanSpeedAuto ){
dev[virtDeviceName]["fanSpeed"] = fanSpeedAuto
}
dev[virtDeviceName]["fanSpeed1"] = false
dev[virtDeviceName]["fanSpeed2"] = false
dev[virtDeviceName]["fanSpeed3"] = false
}
checkFan()
}
})
defineRule("{}/fanSpeed1".format(virtDeviceName), {
whenChanged: "{}/fanSpeed1".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
if(dev[virtDeviceName]["fanSpeed"] !== fanSpeed1 ){
dev[virtDeviceName]["fanSpeed"] = fanSpeed1
}
dev[virtDeviceName]["fanSpeedAuto"] = false
dev[virtDeviceName]["fanSpeed2"] = false
dev[virtDeviceName]["fanSpeed3"] = false
}
checkFan()
}
})
defineRule("{}/fanSpeed2".format(virtDeviceName), {
whenChanged: "{}/fanSpeed2".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
if(dev[virtDeviceName]["fanSpeed"] !== fanSpeed2 ){
dev[virtDeviceName]["fanSpeed"] = fanSpeed2
}
dev[virtDeviceName]["fanSpeedAuto"] = false
dev[virtDeviceName]["fanSpeed1"] = false
dev[virtDeviceName]["fanSpeed3"] = false
}
checkFan()
}
})
defineRule("{}/fanSpeed3".format(virtDeviceName), {
whenChanged: "{}/fanSpeed3".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
if(dev[virtDeviceName]["fanSpeed"] !== fanSpeed3 ){
dev[virtDeviceName]["fanSpeed"] = fanSpeed3
}
dev[virtDeviceName]["fanSpeedAuto"] = false
dev[virtDeviceName]["fanSpeed1"] = false
dev[virtDeviceName]["fanSpeed2"] = false
}
checkFan()
}
})
/*
ГОРИЗОНТАЛЬНЫЕ ШТОРКИ (вверх-вниз)
*/
var checkHorizontalVanes = function() {
if(!dev[virtDeviceName]['horizontalVanesDisable'] && !dev[virtDeviceName]['horizontalVanesAuto'] && dev[virtDeviceName]['horizontalVanes'] < horizontalVanesExtremeDown){
dev[virtDeviceName]['horizontalVanes'] = horizontalVanesMiddle
}
}
defineRule("{}/horizontalVanes".format(virtDeviceName), {
whenChanged: "{}/horizontalVanes".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]['Horizontal vanes'] = newValue
if(newValue === horizontalVanesDisable) {
dev[virtDeviceName]['horizontalVanesDisable'] = true
}
if(newValue === horizontalVanesAuto) {
dev[virtDeviceName]['horizontalVanesAuto'] = true
}
}
})
defineRule("{}/horizontalDisable".format(virtDeviceName), {
whenChanged: "{}/horizontalDisable".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue) {
dev[virtDeviceName]['horizontalAuto'] = false
dev[virtDeviceName]['horizontalVanes'] = horizontalVanesDisable
} else {
checkHorizontalVanes()
}
}
})
defineRule("{}/horizontalAuto".format(virtDeviceName), {
whenChanged: "{}/horizontalAuto".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue) {
dev[virtDeviceName]['horizontalDisable'] = false
dev[virtDeviceName]['horizontalVanes'] = horizontalVanesAuto
} else {
checkHorizontalVanes()
}
}
})
defineRule("{}/horizontalMoveUp".format(virtDeviceName), {
whenChanged: "{}/horizontalMoveUp".format(virtDeviceName),
then: function (newValue, devName, cellName) {
//если режим шторок "выключен" или авто
if(dev[virtDeviceName]['horizontalVanes'] === horizontalVanesDisable || dev[virtDeviceName]['horizontalVanes'] === horizontalVanesAuto) {
//тогда ставим в крайнее верхнее положение
dev[virtDeviceName]['horizontalVanes'] = horizontalVanesExtremeUp
//иначе проверяем что мы не вышли за границу
} else if(dev[virtDeviceName]['horizontalVanes'] < horizontalVanesMax) {
//и тогда двигаем шторки вверх
dev[virtDeviceName]['horizontalVanes']++
}
dev[virtDeviceName]['horizontalAuto'] = false
dev[virtDeviceName]['horizontalDisable'] = false
}
})
defineRule("{}/horizontalMoveDown".format(virtDeviceName), {
whenChanged: "{}/horizontalMoveDown".format(virtDeviceName),
then: function (newValue, devName, cellName) {
//если режим шторок "выключен" или авто
if(dev[virtDeviceName]['horizontalVanes'] === verticalVanesDisable || dev[virtDeviceName]['horizontalVanes'] === verticalVanesAuto) {
//тогда ставим в крайнее нижнее положение
dev[virtDeviceName]['horizontalVanes'] = horizontalVanesExtremeDown
//иначе проверяем что мы не вышли за границу
} else if(dev[virtDeviceName]['horizontalVanes'] > horizontalVanesExtremeDown) {
//и тогда двигаем шторки вниз
dev[virtDeviceName]['horizontalVanes']--
}
}
})
/*
ВЕРТИКАЛЬНЫЕ ШТОРКИ (вправо-влево)
*/
var checkVerticalVanes = function() {
if(!dev[virtDeviceName]['verticalVanesDisable'] && !dev[virtDeviceName]['verticalVanesAuto'] && dev[virtDeviceName]['verticalVanes'] < verticalVanesExtremeLeft){
dev[virtDeviceName]['verticalVanes'] = verticalVanesMiddle
}
}
defineRule("{}/verticalVanes".format(virtDeviceName), {
whenChanged: "{}/verticalVanes".format(virtDeviceName),
then: function (newValue, devName, cellName) {
dev[realDeviceName]['Vertical vanes'] = newValue
if(newValue === verticalVanesDisable) {
dev[virtDeviceName]['verticalVanesDisable'] = true
}
if(newValue === verticalVanesAuto) {
dev[virtDeviceName]['verticalVanesAuto'] = true
}
}
})
defineRule("{}/verticalDisable".format(virtDeviceName), {
whenChanged: "{}/verticalDisable".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue){
dev[virtDeviceName]['verticalAuto'] = false
dev[virtDeviceName]['verticalVanes'] = verticalVanesDisable
} else {
checkVerticalVanes()
}
}
})
defineRule("{}/verticalAuto".format(virtDeviceName), {
whenChanged: "{}/verticalAuto".format(virtDeviceName),
then: function (newValue, devName, cellName) {
if(newValue) {
dev[virtDeviceName]['verticalDisable'] = false
dev[virtDeviceName]['verticalVanes'] = verticalVanesAuto
} else {
checkVerticalVanes()
}
}
})
defineRule("{}/verticalMoveRight".format(virtDeviceName), {
whenChanged: "{}/verticalMoveRight".format(virtDeviceName),
then: function (newValue, devName, cellName) {
//если режим шторок "выключен" или авто
if(dev[virtDeviceName]['verticalVanes'] === verticalVanesDisable || dev[virtDeviceName]['verticalVanes'] === verticalVanesAuto) {
//тогда ставим в крайнее правое положение
dev[virtDeviceName]['verticalVanes'] = verticalVanesExtremeRight
//иначе проверяем что мы не вышли за границу
} else if(dev[virtDeviceName]['verticalVanes'] < verticalVanesMax) {
//и тогда двигаем шторки вправо
dev[virtDeviceName]['verticalVanes']++
}
}
})
defineRule("{}/verticalMoveLeft".format(virtDeviceName), {
whenChanged: "{}/verticalMoveLeft".format(virtDeviceName),
then: function (newValue, devName, cellName) {
//если режим шторок "выключен" или авто
if(dev[virtDeviceName]['verticalVanes'] === verticalVanesDisable || dev[virtDeviceName]['verticalVanes'] === verticalVanesAuto) {
//тогда ставим в крайнее левое положение
dev[virtDeviceName]['verticalVanes'] = verticalVanesExtremeLeft
} else if(dev[virtDeviceName]['verticalVanes'] > verticalVanesExtremeLeft) {
dev[virtDeviceName]['verticalVanes']--
}
dev[virtDeviceName]['verticalAuto'] = false
dev[virtDeviceName]['verticalDisable'] = false
}
})
Конфиг карточки
Выкладываю конфиг карточки home assistant.
type: vertical-stack
cards:
- square: false
type: grid
cards:
- type: horizontal-stack
cards:
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_enabled
icon: mdi:air-conditioner
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_light
icon: mdi:lightbulb-on
- type: horizontal-stack
cards:
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_modeauto
name: авто
icon: mdi:auto-mode
show_state: false
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_modecool
name: охлаждение
icon: mdi:snowflake
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_modedry
icon: mdi:water
name: осушение
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_modefan
icon: mdi:fan
name: вентиляция
show_state: false
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_modeheat
name: обогрев
icon: mdi:weather-sunny
show_state: false
- type: horizontal-stack
cards:
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_fanspeedauto
icon: mdi:fan-auto
show_state: false
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_fanspeed1
icon: mdi:fan-speed-1
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_fanspeed2
icon: mdi:fan-speed-2
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_fanspeed3
icon: mdi:fan-speed-3
- type: horizontal-stack
cards:
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_quietmode
icon: mdi:ear-hearing-off
name: тихо
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_ecomode
icon: mdi:charity
name: эко
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_turbomode
icon: mdi:wind-power
name: турбо
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_sleepmode
icon: mdi:power-sleep
name: ' сон'
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_ionization
icon: mdi:home-heart
name: ион
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_targettemp16
name: '16'
icon: mdi:temperature-celsius
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_targettemp18
name: '18'
icon: mdi:temperature-celsius
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_targettemp20
name: '20'
icon: mdi:temperature-celsius
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_targettemp21
name: '21'
icon: mdi:temperature-celsius
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_targettemp22
name: '22'
icon: mdi:temperature-celsius
- type: entities
entities:
- entity: number.virtual_conditioner_livingroom_targettemp
icon: mdi:temperature-celsius
secondary_info: none
name: температура
state_color: false
show_header_toggle: false
- type: horizontal-stack
cards:
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_horizontaldisable
icon: mdi:close-box
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_horizontalauto
icon: mdi:arrow-up-down-bold
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_horizontalmoveup
icon: mdi:arrow-up-bold
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_horizontalmovedown
icon: mdi:arrow-down-bold
- type: horizontal-stack
cards:
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_verticaldisable
icon: mdi:close-box
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: switch.virtual_conditioner_livingroom_verticalauto
icon: mdi:arrow-left-right-bold
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_verticalmoveleft
icon: mdi:arrow-left-bold
- show_name: false
show_icon: true
type: button
tap_action:
action: toggle
entity: button.virtual_conditioner_livingroom_verticalmoveright
icon: mdi:arrow-right-bold
columns: 1
title: кондиционер
Конфиг термостата
Как и обещал, выкладываю конфиг для термостата, я отказался от этой идеи, но вдруг кто-то решил докрутить эту тему под себя.
mqtt:
climate:
- name: кондиционер в гостинной
unique_id: virtual_conditioner_livingroom
modes:
- "auto"
- "cool"
- "dry"
- "fan_only"
- "heat"
- "off"
max_temp: 30
min_temp: 16
fan_modes:
- "auto"
- "high"
- "medium"
- "low"
fan_mode_command_topic: "/devices/virtual-conditioner-livingroom/controls/fanSpeed"
fan_mode_command_template: >-
{% if value == 'auto' %}
0
{% elif value == 'low' %}
1
{% elif value == 'medium' %}
2
{% else %}
3
{% endif %}
fan_mode_state_topic: "/devices/virtual-conditioner-livingroom/controls/fanSpeed"
fan_mode_state_template: >-
{% if value == '0' %}
auto
{% elif value == '1' %}
low
{% elif value == '2' %}
medium
{% else %}
high
{% endif %}
mode_command_topic: '/devices/virtual-conditioner-livingroom/controls/mode'
mode_command_template: >-
{% if value == 'off' %}
0
{% elif value == 'cool' %}
1
{% elif value == 'auto' %}
2
{% elif value == 'dry' %}
3
{% elif value == 'fan_only' %}
4
{% else %}
5
{% endif %}
mode_state_topic: '/devices/virtual-conditioner-livingroom/controls/mode'
mode_state_template: >-
{% if value == '0' %}
off
{% elif value == '1' %}
cool
{% elif value == '2' %}
auto
{% elif value == '3' %}
dry
{% elif value == '4' %}
fan_only
{% else %}
heat
{% endif %}
temperature_command_topic: '/devices/virtual-conditioner-livingroom/controls/targetTemp'
temperature_state_topic: '/devices/virtual-conditioner-livingroom/controls/targetTemp'
current_temperature_topic: '/devices/ONOKOM-AIR-GR-1-MB-B_10/controls/Indoor air temperature'
temp_step: 1
temperature_unit: C
"Я у себя на дашбордах не использую какие-то дополнительные интерфейсы, один раз поставил Lovelace и у меня после ребута всё отвалилось, с тех пор решил пользоваться только штатным функционалом."
Lovelace и есть штатный функционал. Его не надо ставить. Хотя, вроде его не так давно переименовали просто в Dashboard.
У меня потом после перезагрузки малины у меня не стартанул докер-контейнер с HA, пришлось вручную выпиливать этот ui, решил больше судьбу не испытывать 😌