Пробрасываем свет из HomeAssistant в HomeKit через Node-Red

Преамбула: по неким необъяснимым причинам возникло желание пробрасывать свет из HomeAssistant в HomeKit без участия HA. Потому что родная интеграция НА для HomeKit хоть и хорошая, но почему-то хромает, например, может пересоздать устройство или задублировать его. И если со всеми выключателями, которые живут своей жизнью, через MQTT все было просто, то вот со светом (а у меня большинство ламп и лент от Yeelight) – пришлось посидеть, подумать. Зачем пробрасывать лампы не напрямую, а из НА? Ну потому что они все равно там есть, и потому что в результате получается некий универсальный интерфейс к лампам разных производителей. Не претендую на идеал, но, по крайней мере, flow не занимает всю страницу, и при дублировании нужно поправить всего две ноды для тех, кто дочитает до середины, или одну – для тех, кто дочитает до конца 🙂

В общем, поехали.

Вот так вот выглядит целиком (так и тянет сказать flow, но flow – это вроде как целиком лист) мой набор нод для проброса лампочек из НА в HomeKit и обратно. Думаю, тут большинству все понятно даже по названиям, но пройдемся по каждой, для начинающих.

Самая первая нода – это Events: state, в свойствах которой мы указываем, события от какого устройства мы хотим получать. В данном случае, это light.bed_strip. Обратите внимание, если галка Output only… установлена, ее нужно снять, иначе вы будете получать события только при включении/выключении лампы, а нам нужны все.
Вторая нода – это function. В ней я подготавливаю сообщение для передачи в НК и провожу дополнительные манипуляции со значениями яркости и цвета. Чтобы не срисовывать с картинки, ниже привожу ее код целиком. Обратите внимание, привожу значения яркости к целому числу, предварительно разделив на 2.55. Если получившееся значение 0 – ставлю значение по умолчанию в 10%. В этой же ноде я записываю все значения во flow переменные, они нам пригодятся потом.
var characteristic = {};

if(msg.data.new_state.state === 'on') {
    characteristic.On = true;

    if(msg.data.new_state.attributes.hasOwnProperty("brightness") && msg.data.new_state.attributes.brightness !== undefined) {
        characteristic.Brightness = parseInt((msg.data.new_state.attributes.brightness / 2.55).toFixed()) || 10;
    } else {
        characteristic.Brightness = 100;
    }
    
    if(msg.data.new_state.attributes.hasOwnProperty("hs_color") && msg.data.new_state.attributes.hs_color[0] !== undefined) {
        characteristic.Hue = msg.data.new_state.attributes.hs_color[0];
    } else {
        characteristic.Hue = 0;
    }
    
    if(msg.data.new_state.attributes.hasOwnProperty("hs_color") && msg.data.new_state.attributes.hs_color[1] !== undefined) {
        characteristic.Saturation = msg.data.new_state.attributes.hs_color[1];
    } else {
        characteristic.Saturation = 0;
    }
    

    var brg_name = msg.topic   '.brightness';
    var sat_name = msg.topic   '.saturation';
    var hue_name = msg.topic   '.hue'
    
    flow.set(brg_name, characteristic.Brightness);
    flow.set(sat_name, characteristic.Saturation);
    flow.set(hue_name, characteristic.Hue);

} else {
    characteristic.On = false;
}


msg.payload = characteristic;
return msg;

Кстати, я очень не люблю использовать “постоянные” переменные, например, если скопировать ноду, в которой пишется что-то в переменную test, у нас будет две ноды, работающие с одной переменной. А значит, в какой-то момент результаты могут быть неожиданными. Поскольку я понял, что без flow переменных все-таки не обойтись, то я решил сделать так, чтобы имена переменных были сами переменными, в результате, для каждой лампы имена будут уникальными. 

Имя переменной, в которой хранится значение яркости в данном случае, например, будет light.bed_strip.brightness. При другом имени лампочки в НА – будет другое имя. Но этим можно голову не забивать, к ним обращаться не требуется.

Дальше идет стандартная нода НК, в которой нужно прописать имя устройства, под которым лампочка появится в НК и какие характеристики она поддерживает. Для лампочек из Икея, например, которые только белые, стоит убрать Hue и Saturation, в этом случае нода не будет посылать эти параметры, хоть обтыкайся в цветовой круг в HK App.

Нода типа switch для проверки hap.context. Нужна для того, чтобы быть уверенным, что идущее далее сообщение было порождено самой нодой НК, а не пришло к ней ранее по цепочке. Сам автор интеграции с НК в своих примерах использует проверку на hap.context внутри функций, но мне кажется поставить такую ноду-фильтр гораздо проще.

Теперь мы подходим еще к одному свичу, который называется On/Off, но при этом имеет три выхода. Даже более, в свойствах у него идет булева проверка ложь/истина и все остальное. Такая вот неформальная логика получается 🙂 Обратите внимание, что проверяю не просто msg.payload, а его свойство On – msg.payload.On. Логика его работы проста: если msg.payload.On установлен в false – то пойдем в первый выход, который приведет нас к выключению света, если true – то идем к включению света, а если что-то еще или не установлен – то пойдем в третий выход, предполагая, что данное сообщение предназначено для регулировки яркости или цвета.

Ноды выключения и включения света – это ноды call service и выглядят идентично, за исключеним вызываемого сервиса. На всякий случай все равно их здесь приведу, хотя думаю, они всем известны.

Основной интерес представляют оставшиеся две с половиной ноды, а именно, function, join и call service. 

Нода function выглядит вот так и с ней пришлось повозиться. Ее задача – сформировать полное сообщение для отправки в ноду сервиса, а именно, установить параметры яркости, цвета и насыщенности. Берет она один из сообщения (а нода НК шлет по одному параметру в сообщении, что для меня было весьма огорчительно), а два остальных – из записанных ранее во flow переменных. В случае, если лампа включается впервые (например, только добавлена), то параметров hue и saturation не будет, НА еще не записал ничего в переменные flow, читать нам нечего. Но при этом, iOS при включении новой лампы, в некоторые моменты, шлет два сообщения подряд, On=true (что логично) и второе brightness=100 (что лишнее, из-за чего мы в свиче On/Off уходим по третьему пути и как раз попадаем в ситуацию отсутствия значений Hue и Saturation). Чтобы не возникала ошибка в такой ситуации, добавлена проверка наличия этих значений и если их нет, то мы в НА отправим только параметр яркости. Читабельный код ниже.

var brg_name = msg.topic   '.brightness';
var sat_name = msg.topic   '.saturation';
var hue_name = msg.topic   '.hue'

var new_val = {};

if(msg.payload.Brightness >= 0) {
    new_val.brightness = msg.payload.Brightness;
    flow.set(brg_name, msg.payload.Brightness);
    new_val.saturation = flow.get(sat_name);
    new_val.hue = flow.get(hue_name);
}

if(msg.payload.Hue >= 0) {
    new_val.brightness = flow.get(brg_name);
    new_val.saturation = flow.get(sat_name);
    new_val.hue = msg.payload.Hue;
    flow.set(hue_name, msg.payload.Hue);
}

if(msg.payload.Saturation >= 0) {
    new_val.brightness = flow.get(brg_name);
    new_val.saturation = msg.payload.Saturation;
    flow.set(sat_name, msg.payload.Saturation);
    new_val.hue = flow.get(hue_name);  
}

new_val.data = {
   "brightness_pct" : new_val.brightness
}

if(new_val.hue !== undefined && new_val.saturation !== undefined) {
        new_val.data.hs_color = [new_val.hue, new_val.saturation];
}

msg.payload = new_val;
return msg;

Нода join. Спорная нода и ее присутствие обусловлено лишь тем, что yeelight очень не любит спам на порту и если послать ему некое количество сообщений за единицу времени, он временно отрубит возможность управлять лампой по сети. А при регулировке яркости или задумчивом выборе цвета на цветовой палитре, нода НК просто сыпет сообщениями. Чтобы их чуть-чуть замедлить, я и добавил эту ноду. Ее негативный импакт – некоторые операции начинают отрабатывать с задержкой в 1 секунду, лампа становится слегка задумчивой, например, если менять только цвет и не менять его насыщенность. В общем ее можно смело убрать, но помнить про реакции yeelightов на спам.

Ну и последняя нода – тоже call service с типом сервиса turn_on. Обратите внимание, что поле Data у этой ноды не заполнено никак, необходимые данные мы передаем напрямую в msg.payload.data. Нода получается полностью идентичной ноде включения и выделена отдельно только для наглядности.

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

Но если у вас еще есть время и желание, давайте порассуждаем над такой чисто гипотетической ситуацией, когда у кого-то лампочек не одна и даже не две? Я понимаю, конечно, что это ситуация чисто гипотетическая, ну у кого может быть три лампы, тем более умных, не говоря уже о большем количестве? Но вдруг? 

В таком случае, flow у этого человека может выглядеть как-то вот так, например.

Ну а что тут такого? Предполагаю, что у кого-то это и правда выглядит вот так и не вызывает никаких вопросов. Но мою тонкую душевную организацию от этого просто рвет на тысячу маленьких медвежат. Но даже если забить на тонкую душевную организацию, то если вдруг по прошествии времени где-то найдется ошибка в функции? Придется править n x [количество ламп] раз. Ну прям совсем не вариант, согласны? А значит, приступаем к операции “оптимизация

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

На самом деле, отличий от первого варианта тут минимум, именно поэтому он и был так подробно расписан. 

Что изменилось? Первая нода теперь state changes с фильтром light., чтобы не загромождать flow соединениями, используются ноды link in и link out и немного поменялись ноды НК и вызовов сервиса. А теперь  подробнее.

Первая нода – events state с фильтром по подстроке, получает все сообщения от НА для домена light.

Нода функции “To HomeKit” не изменилась совсем.

Как пользоваться коннекторами, даже если не знаете, разобраться очень просто. Давайте им понятные имена и помните, что out – это исходящее сообщение, in – входящее. Но неправильно вы их даже прицепить не сможете, так что с ними все.

Ноды homekit. Единственное, что требует правки при добавлении нового устройства. Теперь тут нужно править два поля: имя устройства, под которым оно будет видно в НК, и Topic – то, сообщения с каким топиком (заголовком) будут приниматься этой нодой. Не забудьте только поставить галку Filter on Topic. Для примера на рисунке – эта нода будет ловить и обрабатывать сообщения только в том случае, если его заголовок содержит light.wall_strip_1 – это entity_id из НА.

У ноды свича On/Off пропал третий выход и ее выходы теперь настроены на false и остальное. Под остальным у нас проходит включение света или регулировка яркости/цвета.

Полностью пропала спорная нода join, потому что в этом случае она начинает мешать прохождению сообщений, когда вы просите Сири включить или выключить все лампы сразу. Ну да и не беда, просто помним про возможное поведение yeelight. Кстати, если в самом НА дергать ползунки яркости или цветности – лампы точно также благополучно отключают на какое-то время возможность управления по сети, так что ничего мы не теряем.

Ноды call service on и off – меняем Entity Id на {{ topic }}. Поле Data по-прежнему оставляем пустым, вся нужная информация идет в payload.data.

И вот на этом уже действительно все. Теперь, когда нужно будет что-то поправить, достаточно будет открыть одну ноду, а добавление новой лампы сводится к копированию ноды НК с обоими линками и внесением правок в два поля внутри нее.

Спасибо что дочитали, надеюсь все у вас получится.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *