Автоматизация квартиры с HomePod, Raspberry Pi и Node.js
Перевели для вас статью Криса Хокинса, в которой он рассказывает о превращении своей квартиры в умный дом. В качестве базы используется HomePod от Apple, но, конечно, можно применять и другие системы.
У меня дома работает Apple HomePod, который помогает контролировать определенные системы в доме (к примеру, умные лампы) при помощи обычного запроса к Siri. Работает система как из дома, так и вне его (умный помощник есть на телефоне).
Изначально я скептически относился к управлению домом при помощи голосовых команд, ведь далеко не все распознается ассистентами (не только Siri) корректно. Но затем это вошло в привычку. Поскольку у ламп Hue нет физического выключателя, а в приложении нужно совершить несколько действий для управления освещением, я привлек к делу Siri.
Затем мне захотелось начать при помощи голосового помощника управлять и другими системами в доме, например, телевизором или консолью. В случае с ТВ я, например, обнаружил Simple IP Control — метод управления моим Sony Bravia путем отправки команд по TCP.
Кастомизируем Siri
Во второй половине 2018 года Apple открыла приложение Shortcuts для всех пользователей iOS. Оно позволяет автоматизировать работу с телефоном (или умным домом) без необходимости писать код.
У приложения много встроенных команд. Что ему недостает, так это возможности использовать TCP-команды, хотя там есть механизм работы с URL.
Плюс ко всему, можно писать собственные модули на Objective-C или Swift. Этого решил не делать, поскольку в будущем я могу сменить мой HomePod на другого помощника. Вместо этого мне захотелось написать веб-приложение, которое сможет отвечать на команды Siri.
Управление Sony Bravia TV
Вооружившись мануалом с командами для моего ТВ, я написал приложение на Node.js Express (Github), которое научил отвечать на некоторые из общих команд. Начал я с включения и громкости.
Команда setPowerStatus делает все, что нам нужно.
Заголовок состоит из символов * и s, которые статичны и используются для всех команд. Затем третий байт (с) используется для Command. Есть четыре значения, которые могут занимать эту позицию. C для Command (отправка команды на ТВ), E — для Enquiry (проверка текущего значения определенного параметра, например, громкости), А — Answer (отправляется в ответ на Commands и Enquiries) и N для Notify (оповещение о событиях, вроде отключение громкости).
Для достижения своей цели мне пришлось изучить документацию Sony’s JSON-RPC. Как оказалось, природа JSON-RPC over HTTP позволила упростить задачу и сократить количество кода.
Работа с JSON-RPC API была простой. Возьмем, например, сервис (system), команду (getPowerStatus) плюс параметры (true или false) и сформируем HTTP-запрос, который затем отправляем на ТВ.
let body = JSON.stringify({ method: command, id: ++this.id, params: params, version: "1.0", }); return new Promise((resolve, reject) => { fetch('http://' + this.ip + ':' + this.port + '/sony/' + service, { method: 'post', headers: { 'X-Auth-PSK': this.psk }, body: body, }).then(response => { return response.json(); }).then(response => { if (response.error && (!response.result || response.result.length === 0)) { reject({ code: response.error[0] }); } else { resolve(response.result[0]); } }).catch(error => { reject(error); }); });
По умолчанию аутентификация осуществляется при помощи предварительного ключа, отправляемого в заголовке HTTP-запроса. Но есть более безопасный и удобный способ сделать это при помощи accessControl. В этом случае мы можем отправлять команду на телевизор и осуществлять безопасный обмен кода при базовой HTTP-аутентификации. После того, как аутентификация выполнена один раз, дальнейшая авторизация выполняется при помощи cookie.
Но мой ТВ защищен файерволом, настроенным на роутере, так что я использовал предварительный ключ.
Включение Xbox One
Xbox, конечно, требовал иной настройки. Microsoft, похоже, приняла решение не использовать REST API, поэтому работа была выполнена при помощи UDP-пакетов.
К счастью, у Node.js есть модуль dgram, который «из коробки» работает со всеми возможностями USP. Вот, что у меня получилось в итоге.
turnOn() { let socket = dgram.createSocket('udp4'); let powerPayload = new Buffer('\x00' + String.fromCharCode(this.liveId.length) + this.liveId.toUpperCase() + '\x00'); let powerHeader = Buffer.concat([new Buffer('dd0200', 'hex'), new Buffer(String.fromCharCode(powerPayload.length)), new Buffer('\x00\x00')]); let powerPacket = Buffer.concat([powerHeader, powerPayload]); return this._sendPacket(socket, powerPacket); } _sendPacket(socket, buffer) { return new Promise((resolve, reject) => { socket.send(buffer, 0, buffer.length, Constants.xboxPort, this.ip, function(err) { socket.close(); if (err) { return reject(err); } resolve(); }); }); }
Для настрйоки я использовал список ID-устройств, который можно найти здесь. Если вы хотите просто взять код из моего репозитория, то вам нужно заменить ID в файле config.json.
Настройка Shortcuts для Siri
Для того, чтобы Siri могла выполнять команды, которые я только что создал, ей нужен помощник. Его я создал из Raspberry Pi, поскольку «малинка» подходит по всем параметрам. Для этого я купил Pi 3 Model B+, поддерживающий Wi-Fi.
У Raspbian есть GUI для настройки. Я подключился к Wi-Fi, затем отключил дисплей и продолжил работу по SSH. Для того, чтобы убедиться в постоянной активности веб-приложения, я настроил сокет активации сервиса в systemd, так что если бы процесс Node.js упал, система автоматически могла его перезапустить.
Собственно, Shortcuts для Siri были самым простым этапом работы. Это интуитивное приложение с нативной поддержкой голосовых команд. Оно по умолчанию уже умело работать с HomePod, дополнительно настраивать ничего не понадобилось.
Собираем все вместе
Поскольку мой ТВ работает на Android, он поддерживает приложения вроде Netflix и YouTube. Помня об этом, я создал команды для запуска этих сервисов. Кроме того, я добавил команды для контроля громкости, режима работы ТВ, паузы и проигрывания контента.
Вот примеры всего, что я создал. Я также постарался сделать проект модульным, так что добавлять другие модели SmartTV — не проблема.
Вот пример модуля, который включает Xbox, ТВ и активизирует первый порт HDMI.
router.post('/turnOnXboxAndTV', function(req, res, next) { Promise.all([ xbox.turnOn(), tv.turnOn() .then(() => new Promise(resolve => setTimeout(resolve, 2000))) .then(() => tv.setInput(config.scripts.xboxInput)), ]).then(() => { res.sendStatus(200); }).catch((error) => { res.status(500).send(error); }); });
А вот как это все работает на практике.
К сожалению, функциональность Siri не слишком хороша. У той же Alexa от Amazon гораздо более обширный спектр возможностей и весьма мощный API. Думаю, на основе Alexa можно создать куда более серьезные проекты.
Источник: Хабр