mirror of
https://github.com/lasthead0/yandex2mqtt.git
synced 2025-08-10 01:49:14 +03:00
Merge branch 'beta'
This commit is contained in:
332
README.md
332
README.md
@@ -7,16 +7,26 @@
|
|||||||
Доступна бета-версия на ветке `beta`. Для установки выполнить команду `git clone -b beta https://github.com/lasthead0/yandex2mqtt.git`. Подробности изменений и документация в `README` ветки `beta`.
|
Доступна бета-версия на ветке `beta`. Для установки выполнить команду `git clone -b beta https://github.com/lasthead0/yandex2mqtt.git`. Подробности изменений и документация в `README` ветки `beta`.
|
||||||
|
|
||||||
## Важно
|
## Важно
|
||||||
Те, кто пользуется оригинальным проектом (или его форками), обратите внимание на то, что немного изменились настройки устройств (блок **devices** в файле конфигурации).
|
Те, кто пользуется оригинальным проектом (или его форками), обратите внимание на то, что немного изменились настройки устройств (блок `devices` в файле конфигурации).
|
||||||
|
|
||||||
На данный момент проверено получение температуры и влажности с датчиков (датчики дверей и движения пока в бета-тесте), и включение/выключение света (вкл./выкл. других устройств по аналогии тоже должно работать).
|
На данный момент проверено получение температуры и влажности с датчиков (датчики дверей и движения пока в бета-тесте), и включение/выключение света (вкл./выкл. других устройств по аналогии тоже должно работать).
|
||||||
|
|
||||||
Прочий функционал (изменение громкости, каналов, отключение звука), поидее, так же должны работать.
|
Прочий функционал (изменение громкости, каналов, отключение звука), поидее, так же должны работать.
|
||||||
|
|
||||||
## ChangeLog
|
## ChangeLog
|
||||||
|
###### 16.05.2021
|
||||||
|
Добавлено логирование некоторых событий.
|
||||||
|
|
||||||
|
###### 13.05.2021
|
||||||
|
Добавлена поддержка API уведомлений об изменении состояний устройств.
|
||||||
|
|
||||||
|
###### 31.03.2021
|
||||||
|
Добавлена поддрежка разделения доступа пользователей к устройствам.
|
||||||
|
|
||||||
|
###### Release
|
||||||
Проведён рефакторинг кода и, местами, внесены значительные правки.
|
Проведён рефакторинг кода и, местами, внесены значительные правки.
|
||||||
|
|
||||||
Добавлена поддержка датчиков (устройств **devices.types.sensor**)
|
Добавлена поддержка датчиков (устройств `devices.types.sensor`)
|
||||||
|
|
||||||
## Требования
|
## Требования
|
||||||
- **"Белый" IP адрес и домен**. Если нет своего домена и белого IP адреса можно воспользоваться Dynamic DNS сервисами (например, noip.com).
|
- **"Белый" IP адрес и домен**. Если нет своего домена и белого IP адреса можно воспользоваться Dynamic DNS сервисами (например, noip.com).
|
||||||
@@ -55,14 +65,20 @@ npm start
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Настройка yandex2mqtt
|
## Настройка yandex2mqtt
|
||||||
Все основные настройки моста прописываются в файл config.js. Перед запуском обязательно отредактируйте его.
|
Все основные настройки моста прописываются в файл `config.js`. Перед запуском обязательно отредактируйте его.
|
||||||
```
|
```
|
||||||
mv config.orig.js config.js
|
mv config.orig.js config.js
|
||||||
```
|
```
|
||||||
|
|
||||||
**Файл конфигурации**
|
#### Файл конфигурации
|
||||||
```
|
```
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
notification: [
|
||||||
|
{
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
mqtt: {
|
mqtt: {
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
@@ -75,34 +91,37 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
|
...
|
||||||
],
|
],
|
||||||
|
|
||||||
users: [
|
users: [
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
|
...
|
||||||
],
|
],
|
||||||
|
|
||||||
devices: [
|
devices: [
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
|
...
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Блок настройки mqtt клиента**
|
###### Блок настройки mqtt клиента
|
||||||
Указать данные Вашего MQTT сервера
|
Указать данные Вашего MQTT сервера
|
||||||
```
|
```
|
||||||
mqtt: {
|
mqtt: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 1883,
|
port: 1883,
|
||||||
user: 'user',
|
user: 'user',
|
||||||
password: 'password'
|
password: 'password',
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
**Блок настройки https сервера**
|
###### Блок настройки https сервера
|
||||||
Указать порт, на котором будет работать мост, а так же пути к сертификату ssl.
|
Указать порт, на котором будет работать мост, а так же пути к сертификату ssl.
|
||||||
```
|
```
|
||||||
https: {
|
https: {
|
||||||
@@ -112,125 +131,204 @@ https: {
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
**Блок настройки клиентов**
|
###### Блок настройки клиентов
|
||||||
Здесь используются произвольные данные, далее они понадобятся для подключения к УД Yandex.
|
Здесь используются произвольные данные, далее они понадобятся для подключения к УД Yandex.
|
||||||
```
|
```
|
||||||
clients: [
|
clients: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Yandex',
|
name: 'Yandex',
|
||||||
clientId: 'client',
|
clientId: 'client',
|
||||||
clientSecret: 'secret',
|
clientSecret: 'secret',
|
||||||
isTrusted: false
|
isTrusted: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
```
|
```
|
||||||
|
|
||||||
**Блок настройки пользователей**
|
###### Блок настройки пользователей
|
||||||
```
|
```
|
||||||
users: [
|
users: [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: '1',
|
||||||
username: "admin",
|
username: 'admin',
|
||||||
password: "admin",
|
password: 'admin',
|
||||||
name: "Administrator"
|
name: 'Administrator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2",
|
id: '2',
|
||||||
username: "user1",
|
username: 'user1',
|
||||||
password: "user1",
|
password: 'user1',
|
||||||
name: "User"
|
name: 'User',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
```
|
```
|
||||||
|
|
||||||
**Блок настройки устройств**
|
###### Блок настройки устройств
|
||||||
```
|
```
|
||||||
devices: [
|
devices: [
|
||||||
{
|
{
|
||||||
id: "lvr-003-switch",
|
id: 'haw-002-switch',
|
||||||
name: "Основной свет",
|
name: 'Свет в коридоре',
|
||||||
room: "Гостиная",
|
room: 'Коридор',
|
||||||
type: "devices.types.light",
|
type: 'devices.types.light',
|
||||||
mqtt: [
|
allowedUsers: ['2'],
|
||||||
{
|
mqtt: [
|
||||||
instance: "on",
|
{
|
||||||
set: "/yandex/controls/light_LvR_003/state/on",
|
instance: 'on',
|
||||||
state: "/yandex/controls/light_LvR_003/state"
|
set: '/yandex/controls/light_HaW_002/on',
|
||||||
},
|
state: '/yandex/controls/light_HaW_002/on/state',
|
||||||
],
|
},
|
||||||
/* mapping значений между yandex и УД */
|
],
|
||||||
valueMapping: [
|
capabilities: [
|
||||||
{
|
{
|
||||||
type: "on_off",
|
type: 'devices.capabilities.on_off',
|
||||||
mapping: [[false, true], [0, 1]] // [yandex, mqtt]
|
retrievable: true,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
capabilities: [
|
},
|
||||||
{
|
|
||||||
type: "devices.capabilities.on_off",
|
|
||||||
retrievable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "lvr-001-weather",
|
id: 'lvr-003-switch',
|
||||||
name: "В гостиной",
|
name: 'Основной свет',
|
||||||
room: "Гостиная",
|
room: 'Гостиная',
|
||||||
type: "devices.types.sensor",
|
type: 'devices.types.light',
|
||||||
mqtt: [
|
allowedUsers: ['2'],
|
||||||
{
|
mqtt: [
|
||||||
instance: "temperature",
|
{
|
||||||
state: "/yandex/sensors/LvR_001_Weather/temperature"
|
instance: 'on',
|
||||||
},
|
set: '/yandex/controls/light_LvR_003/on',
|
||||||
{
|
state: '/yandex/controls/light_LvR_003/on/state',
|
||||||
instance: "humidity",
|
},
|
||||||
state: "/yandex/sensors/LvR_001_Weather/humidity"
|
],
|
||||||
}
|
valueMapping: [
|
||||||
],
|
{
|
||||||
properties: [
|
type: 'on_off',
|
||||||
{
|
mapping: [[false, true], [0, 1]], // [yandex, mqtt]
|
||||||
type: "devices.properties.float",
|
},
|
||||||
retrievable: true,
|
],
|
||||||
parameters: {
|
capabilities: [
|
||||||
instance: "temperature",
|
{
|
||||||
unit: "unit.temperature.celsius"
|
type: 'devices.capabilities.on_off',
|
||||||
},
|
retrievable: true,
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
type: "devices.properties.float",
|
},
|
||||||
retrievable: true,
|
|
||||||
parameters: {
|
{
|
||||||
instance: "humidity",
|
id: 'lvr-001-weather',
|
||||||
unit: "unit.percent"
|
name: 'В гостиной',
|
||||||
},
|
room: 'Гостиная',
|
||||||
/* Блок state указывать не обязательно */
|
type: 'devices.types.sensor',
|
||||||
state: {
|
allowedUsers: ['2'],
|
||||||
instance: "humidity",
|
mqtt: [
|
||||||
value: 0
|
{
|
||||||
}
|
instance: 'temperature',
|
||||||
}
|
state: '/yandex/sensors/LvR_001_Weather/temperature',
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
/* --- end */
|
instance: 'humidity',
|
||||||
]
|
state: '/yandex/sensors/LvR_001_Weather/humidity',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
type: 'devices.properties.float',
|
||||||
|
retrievable: true,
|
||||||
|
parameters: {
|
||||||
|
instance: 'temperature',
|
||||||
|
unit: 'unit.temperature.celsius',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'devices.properties.float',
|
||||||
|
retrievable: true,
|
||||||
|
parameters: {
|
||||||
|
instance: 'humidity',
|
||||||
|
unit: 'unit.percent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'plug-001-flower',
|
||||||
|
name: 'Розетка для цветка',
|
||||||
|
room: 'Гостиная',
|
||||||
|
type: 'devices.types.socket',
|
||||||
|
allowedUsers: ['2'],
|
||||||
|
mqtt: [
|
||||||
|
{
|
||||||
|
instance: 'on',
|
||||||
|
set: '/yandex/controls/socket_LvR_002/on',
|
||||||
|
state: '/yandex/controls/socket_LvR_002/on/state',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instance: 'power',
|
||||||
|
state: '/yandex/controls/socket_LvR_002/power',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
capabilities: [
|
||||||
|
{
|
||||||
|
type: 'devices.capabilities.on_off',
|
||||||
|
retrievable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
type: 'devices.properties.float',
|
||||||
|
retrievable: true,
|
||||||
|
parameters: {
|
||||||
|
instance: 'power',
|
||||||
|
unit: 'unit.watt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/* --- end */
|
||||||
|
],
|
||||||
```
|
```
|
||||||
*Рекомендую указывать id в конфиге, чтобы исключить "наложение" новых устройств на "старые", которые уже добавлены в yandex.*
|
*Рекомендую указывать id в конфиге, чтобы исключить "наложение" новых устройств на "старые", которые уже добавлены в навык.*
|
||||||
|
|
||||||
*В случае отсутсвия id в конфиге, он будет назначен автоматически по индексу в массиве.*
|
*В случае отсутсвия id в конфиге, он будет назначен автоматически по индексу в массиве.*
|
||||||
|
|
||||||
###### Mapping значений
|
#### Уведомление об изменении состояний устройств
|
||||||
Блок valueMapping позволяет настроить конвертацию значений между yandex api и MQTT. Это может быть актуально для умений типа **devices.capabilities.on_off** и **devices.capabilities.toggle**.
|
Платформа УД Яндекс предоставляет сервис уведомлений об изменении состояний устройств. При изменении состояния устройства (например, изменение влажности) yandex2mqtt будет отправлять запрос с новым состоянием.
|
||||||
|
|
||||||
|
В настройках предусмотрен блок `notification`.
|
||||||
|
|
||||||
|
```
|
||||||
|
notification: [
|
||||||
|
{
|
||||||
|
skill_id: '6fca0a54-a505-4420-b774-f01da95e5c31',
|
||||||
|
oauth_token: 'AQA11AAPv-V2BAT7o_ps6gEtrtNNjlE2ENYt96w',
|
||||||
|
user_id: '2'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Если к yandex2mqtt "подключено" несколько навыков УД, то в массиве необходимо указать настройки для каждого навыка УД, который должен получать уведомления.
|
||||||
|
|
||||||
|
`skill_id` (идентификатор вызываемого навыка, присвоенный при создании) и `oauth_token` (авторизационный токен владельца навыка) можно узнать из документации на [Уведомление об изменении состояний устройств](https://yandex.ru/dev/dialogs/smart-home/doc/reference-alerts/post-skill_id-callback-state.html), а `user_id` - id пользователя в файле конфигурации yandex2mqtt.
|
||||||
|
|
||||||
|
*Важно. Уведомления будут отправляться при изменнии mqtt топика хранящего состояние устройства. Соответственно, если для устройства не задан топик state, то уведомление для устройтва отправляться не будет.*
|
||||||
|
|
||||||
|
|
||||||
|
#### Разрешенные пользователи для устройств (allowedUsers)
|
||||||
|
В блоке конфигурации можно указать пользователей (id пользователей), для которых будет доступно устройство.
|
||||||
|
|
||||||
|
В опции `allowedUsers` указыватся массив (строковых значений) id. Если данная опция не указана, то для неё будет назначено значение ['1'];
|
||||||
|
|
||||||
|
#### Mapping значений
|
||||||
|
Блок valueMapping позволяет настроить конвертацию значений между yandex api и MQTT. Это может быть актуально для умений типа `devices.capabilities.on_off` и `devices.capabilities.toggle`.
|
||||||
|
|
||||||
*Например, если в УД состояние влючено/выключено соответствует значениям 1/0, то Вам понадобиться их конвертировать, т.к. в навыках Yandex значения true/false.*
|
*Например, если в УД состояние влючено/выключено соответствует значениям 1/0, то Вам понадобиться их конвертировать, т.к. в навыках Yandex значения true/false.*
|
||||||
```
|
```
|
||||||
valueMapping: [
|
valueMapping: [
|
||||||
{
|
{
|
||||||
type: "on_off",
|
type: 'on_off',
|
||||||
mapping: [[false, true], [0, 1]] // [yandex, mqtt]
|
mapping: [[false, true], [0, 1]], // [yandex, mqtt]
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
```
|
```
|
||||||
В mapping указывается миссив массивов. Первый массив - значения в yandex, второй - в MQTT.
|
В mapping указывается миссив массивов. Первый массив - значения в yandex, второй - в MQTT.
|
||||||
|
|
||||||
@@ -239,15 +337,19 @@ valueMapping: [
|
|||||||
- [Типы умений устройства](https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/capability-types.html)
|
- [Типы умений устройства](https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/capability-types.html)
|
||||||
- [Типы встроенных датчиков](https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/properties-types.html)
|
- [Типы встроенных датчиков](https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/properties-types.html)
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
Добавлено две "стратегии" логирования: сообщений об ошибках в файл `log/error.log` (аргумент запуска `--log-error`) и всех сообщений в консоль (`--log-info`).
|
||||||
|
Для запуска y2m с логирование необходимо добавить аргумент запуска в команду запуска в файле настройки служба (**раздел ниже**) или запустить из консоли.
|
||||||
|
|
||||||
## Создание службы
|
## Создание службы
|
||||||
В папке /etc/systemd/system/ создать файл yandex2mqtt.service со следующим содержанем:
|
В папке `/etc/systemd/system/` создать файл `yandex2mqtt.service` со следующим содержанем:
|
||||||
```
|
```
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=yandex2mqtt
|
Description=yandex2mqtt
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/npm start
|
ExecStart=/usr/bin/node app.js --log-error
|
||||||
WorkingDirectory=/opt/yandex2mqtt
|
WorkingDirectory=/opt/yandex2mqtt
|
||||||
StandardOutput=inherit
|
StandardOutput=inherit
|
||||||
StandardError=inherit
|
StandardError=inherit
|
||||||
@@ -275,25 +377,24 @@ service yandex2mqtt restart
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Создание навыка (в Яндекс Диалоги)
|
## Создание навыка (в Яндекс Диалоги)
|
||||||
|
|
||||||
Заходим в [Яндекс Диалоги](https://dialogs.yandex.ru/developer) => Создать диалог => Умный дом
|
Заходим в [Яндекс Диалоги](https://dialogs.yandex.ru/developer) => Создать диалог => Умный дом
|
||||||
|
|
||||||
###### Основные настройки
|
#### Основные настройки
|
||||||
- **Название** *Любое*
|
- **Название** *Любое*
|
||||||
- **Backend** *Endpoint URL* и указываем https://your.domain.ru:port/provider
|
- **Backend** *Endpoint URL* и указываем https://your.domain.ru:port/provider
|
||||||
- **Тип доступа** *Приватный*
|
- **Тип доступа** *Приватный*
|
||||||
|
|
||||||
###### Публикация в каталоге
|
#### Публикация в каталоге
|
||||||
- **Подзаголовок** *Любой текст*
|
- **Подзаголовок** *Любой текст*
|
||||||
- **Имя разработчика** *Ваше имя*
|
- **Имя разработчика** *Ваше имя*
|
||||||
- **Официальный навык** *Нет*
|
- **Официальный навык** *Нет*
|
||||||
- **Описание** *Любой текст*
|
- **Описание** *Любой текст*
|
||||||
- **Иконка** *Своя иконка*
|
- **Иконка** *Своя иконка*
|
||||||
|
|
||||||
###### Связка аккаунтов
|
#### Связка аккаунтов
|
||||||
- **Авторизация** _Кнопка **"Создать"**_
|
- **Авторизация** _Кнопка **"Создать"**_
|
||||||
|
|
||||||
###### Создание связки аккаунтов
|
#### Создание связки аккаунтов
|
||||||
- **Идентификатор приложения** *Файл конфигурации clients.clientId*
|
- **Идентификатор приложения** *Файл конфигурации clients.clientId*
|
||||||
- **Секрет приложения** *Файл конфигурации clients.clientSecret*
|
- **Секрет приложения** *Файл конфигурации clients.clientSecret*
|
||||||
- **URL авторизации** *https://your.domain.ru:port/dialog/authorize*
|
- **URL авторизации** *https://your.domain.ru:port/dialog/authorize*
|
||||||
@@ -306,7 +407,7 @@ service yandex2mqtt restart
|
|||||||
|
|
||||||
## Известные "особенности поведения" ioBroker (iob)
|
## Известные "особенности поведения" ioBroker (iob)
|
||||||
|
|
||||||
###### Не изменяются и не читаются топики MQTT
|
#### Не изменяются и не читаются топики MQTT
|
||||||
|
|
||||||
Если случается такое, что Алиса получает голосовую команду и не сообщает об ошибке, но при этом топик не меняет своего значения или, при изменении стейта (объекта iob) MQTT топик не публикуется (Алиса не получает нового значения, а сообщает старое) **необходимо перезапустить адаптер mqtt**.
|
Если случается такое, что Алиса получает голосовую команду и не сообщает об ошибке, но при этом топик не меняет своего значения или, при изменении стейта (объекта iob) MQTT топик не публикуется (Алиса не получает нового значения, а сообщает старое) **необходимо перезапустить адаптер mqtt**.
|
||||||
|
|
||||||
@@ -317,4 +418,3 @@ service yandex2mqtt restart
|
|||||||
"topic": "/yandex/controls/light_BdR_002/state"
|
"topic": "/yandex/controls/light_BdR_002/state"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
**Перезапустить адаптер mqtt**
|
|
70
app.js
70
app.js
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
/* */
|
||||||
|
const {createLogger, format, transports} = require('winston');
|
||||||
/* express and https */
|
/* express and https */
|
||||||
const ejs = require('ejs');
|
const ejs = require('ejs');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
@@ -20,6 +22,29 @@ const mqtt = require('mqtt');
|
|||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const Device = require('./device');
|
const Device = require('./device');
|
||||||
|
|
||||||
|
/* */
|
||||||
|
const clArgv = process.argv.slice(2);
|
||||||
|
|
||||||
|
/* Logging */
|
||||||
|
global.logger = createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: format.combine(
|
||||||
|
format.errors({stack: true}),
|
||||||
|
format.timestamp(),
|
||||||
|
format.printf(({level, message, timestamp, stack}) => {
|
||||||
|
return `${timestamp} ${level}: ${stack != undefined ? stack : message}`;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new transports.Console({
|
||||||
|
silent: clArgv.indexOf('--log-info') == -1
|
||||||
|
})
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clArgv.indexOf('--log-error') > -1) global.logger.add(new transports.File({filename: 'log/error.log', level: 'error'}));
|
||||||
|
|
||||||
|
/* */
|
||||||
app.engine('ejs', ejs.__express);
|
app.engine('ejs', ejs.__express);
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('views', path.join(__dirname, './views'));
|
app.set('views', path.join(__dirname, './views'));
|
||||||
@@ -110,6 +135,51 @@ global.mqttClient = mqtt.connect(`mqtt://${config.mqtt.host}`, {
|
|||||||
const {deviceId, instance} = subscription;
|
const {deviceId, instance} = subscription;
|
||||||
const ldevice = global.devices.find(d => d.data.id == deviceId);
|
const ldevice = global.devices.find(d => d.data.id == deviceId);
|
||||||
ldevice.updateState(`${message}`, instance);
|
ldevice.updateState(`${message}`, instance);
|
||||||
|
|
||||||
|
/* Make Request to Yandex Dialog notification API */
|
||||||
|
Promise.all(config.notification.map(el => {
|
||||||
|
let {skill_id, oauth_token, user_id} = el;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let req = https.request({
|
||||||
|
hostname: 'dialogs.yandex.net',
|
||||||
|
port: 443,
|
||||||
|
path: `/api/v1/skills/${skill_id}/callback/state`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': `application/json`,
|
||||||
|
'Authorization': `OAuth ${oauth_token}`
|
||||||
|
}
|
||||||
|
}, res => {
|
||||||
|
res.on('data', d => {
|
||||||
|
global.logger.log('info', {message: `${d}`});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', error => {
|
||||||
|
global.logger.log('error', {message: `${error}`});
|
||||||
|
});
|
||||||
|
|
||||||
|
let {id, capabilities, properties} = ldevice.getState();
|
||||||
|
req.write(JSON.stringify({
|
||||||
|
"ts": Math.floor(Date.now() / 1000),
|
||||||
|
"payload": {
|
||||||
|
"user_id": `${user_id}`,
|
||||||
|
"devices": [{
|
||||||
|
id,
|
||||||
|
capabilities: capabilities.filter(c => c.state.instance == instance),
|
||||||
|
properties: properties.filter(p => p.state.instance == instance)
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* */
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
@@ -43,11 +43,12 @@ module.exports = {
|
|||||||
name: 'Свет в коридоре',
|
name: 'Свет в коридоре',
|
||||||
room: 'Коридор',
|
room: 'Коридор',
|
||||||
type: 'devices.types.light',
|
type: 'devices.types.light',
|
||||||
|
allowedUsers: ['2'],
|
||||||
mqtt: [
|
mqtt: [
|
||||||
{
|
{
|
||||||
instance: 'on',
|
instance: 'on',
|
||||||
set: '/yandex/controls/light_HaW_002/state/on',
|
set: '/yandex/controls/light_HaW_002/on',
|
||||||
state: '/yandex/controls/light_HaW_002/state',
|
state: '/yandex/controls/light_HaW_002/on/state',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
capabilities: [
|
capabilities: [
|
||||||
@@ -63,11 +64,12 @@ module.exports = {
|
|||||||
name: 'Основной свет',
|
name: 'Основной свет',
|
||||||
room: 'Гостиная',
|
room: 'Гостиная',
|
||||||
type: 'devices.types.light',
|
type: 'devices.types.light',
|
||||||
|
allowedUsers: ['2'],
|
||||||
mqtt: [
|
mqtt: [
|
||||||
{
|
{
|
||||||
instance: 'on',
|
instance: 'on',
|
||||||
set: '/yandex/controls/light_LvR_003/state/on',
|
set: '/yandex/controls/light_LvR_003/on',
|
||||||
state: '/yandex/controls/light_LvR_003/state',
|
state: '/yandex/controls/light_LvR_003/on/state',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
valueMapping: [
|
valueMapping: [
|
||||||
@@ -89,6 +91,7 @@ module.exports = {
|
|||||||
name: 'В гостиной',
|
name: 'В гостиной',
|
||||||
room: 'Гостиная',
|
room: 'Гостиная',
|
||||||
type: 'devices.types.sensor',
|
type: 'devices.types.sensor',
|
||||||
|
allowedUsers: ['2'],
|
||||||
mqtt: [
|
mqtt: [
|
||||||
{
|
{
|
||||||
instance: 'temperature',
|
instance: 'temperature',
|
||||||
@@ -124,11 +127,12 @@ module.exports = {
|
|||||||
name: 'Розетка для цветка',
|
name: 'Розетка для цветка',
|
||||||
room: 'Гостиная',
|
room: 'Гостиная',
|
||||||
type: 'devices.types.socket',
|
type: 'devices.types.socket',
|
||||||
|
allowedUsers: ['2'],
|
||||||
mqtt: [
|
mqtt: [
|
||||||
{
|
{
|
||||||
instance: 'on',
|
instance: 'on',
|
||||||
set: '/yandex/controls/socket_LvR_002/state/on',
|
set: '/yandex/controls/socket_LvR_002/on',
|
||||||
state: '/yandex/controls/socket_LvR_002/state/on',
|
state: '/yandex/controls/socket_LvR_002/on/state',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
instance: 'power',
|
instance: 'power',
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const {logger} = global;
|
||||||
const loki = require('lokijs');
|
const loki = require('lokijs');
|
||||||
|
|
||||||
global.dbl = new loki('./loki.json', {
|
global.dbl = new loki('./loki.json', {
|
||||||
@@ -16,36 +17,40 @@ global.dbl = new loki('./loki.json', {
|
|||||||
|
|
||||||
module.exports.find = (key, done) => {
|
module.exports.find = (key, done) => {
|
||||||
const ltoken = global.authl.findOne({'token': key});
|
const ltoken = global.authl.findOne({'token': key});
|
||||||
if (ltoken){
|
if (ltoken) {
|
||||||
console.log('Token found');
|
|
||||||
const {userId, clientId} = ltoken;
|
const {userId, clientId} = ltoken;
|
||||||
return done(null, {userId, clientId})
|
return done(null, {userId, clientId})
|
||||||
} else {
|
} else {
|
||||||
return done(new Error('Token Not Found'));
|
logger.log('error', new Error('Token Not Found'));
|
||||||
|
return done();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.findByUserIdAndClientId = (userId, clientId, done) => {
|
module.exports.findByUserIdAndClientId = (userId, clientId, done) => {
|
||||||
const ltoken = global.authl.findOne({'userId': userId});
|
const ltoken = global.authl.findOne({'userId': userId});
|
||||||
if (ltoken){
|
if (ltoken){
|
||||||
console.log('Load token by userId: User found');
|
logger.log('info', {message: `Load token by userId (${userId}): User found`});
|
||||||
const {token, userId: uid, clientId: cid} = ltoken;
|
const {token, userId: uid, clientId: cid} = ltoken;
|
||||||
if (uid === userId && cid === clientId) return done(null, token);
|
if (uid === userId && cid === clientId) {
|
||||||
else return done(new Error('Token Not Found'));
|
return done(null, token);
|
||||||
|
} else {
|
||||||
|
logger.log('error', new Error('Token Not Found'));
|
||||||
|
return done();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('User not found');
|
logger.log('error', new Error('User Not Found'));
|
||||||
return done(new Error('User Not Found'));
|
return done();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.save = (token, userId, clientId, done) => {
|
module.exports.save = (token, userId, clientId, done) => {
|
||||||
console.log('Start saving token');
|
logger.log('info', {message: `Start saving token`});
|
||||||
const ltoken = global.authl.findOne({'userId': userId});
|
const ltoken = global.authl.findOne({'userId': userId});
|
||||||
if (ltoken){
|
if (ltoken){
|
||||||
console.log('User Updated');
|
logger.log('info', {message: `User Updated`});
|
||||||
global.authl.update(Object.assign({}, ltoken, {token, userId, clientId}));
|
global.authl.update(Object.assign({}, ltoken, {token, userId, clientId}));
|
||||||
} else {
|
} else {
|
||||||
console.log('User not Found. Create new...');
|
logger.log('info', {message: `User not Found. Create new...`});
|
||||||
global.authl.insert({'type': 'token', token, userId, clientId});
|
global.authl.insert({'type': 'token', token, userId, clientId});
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const {logger} = global;
|
||||||
const codes = {};
|
const codes = {};
|
||||||
|
|
||||||
module.exports.find = (key, done) => {
|
module.exports.find = (key, done) => {
|
||||||
if (codes[key]) return done(null, codes[key]);
|
if (codes[key]) {
|
||||||
return done(new Error('Code Not Found'));
|
return done(null, codes[key]);
|
||||||
|
} else {
|
||||||
|
logger.log('error', new Error('Code Not Found'));
|
||||||
|
return done();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.save = (code, clientId, redirectUri, userId, userName, done) => {
|
module.exports.save = (code, clientId, redirectUri, userId, userName, done) => {
|
||||||
|
@@ -1,17 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const {logger} = global;
|
||||||
const {clients} = require('../config');
|
const {clients} = require('../config');
|
||||||
|
|
||||||
module.exports.findById = (id, done) => {
|
module.exports.findById = (id, done) => {
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
if (client.id === id) return done(null, client);
|
if (client.id === id) return done(null, client);
|
||||||
}
|
}
|
||||||
return done(new Error('Client Not Found'));
|
logger.log('error', new Error('Client Not Found'));
|
||||||
|
return done();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.findByClientId = (clientId, done) => {
|
module.exports.findByClientId = (clientId, done) => {
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
if (client.clientId === clientId) return done(null, client);
|
if (client.clientId === clientId) return done(null, client);
|
||||||
}
|
}
|
||||||
return done(new Error('Client Not Found'));
|
logger.log('error', new Error('Client Not Found'));
|
||||||
|
return done();
|
||||||
};
|
};
|
||||||
|
@@ -1,17 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const {logger} = global;
|
||||||
const {users} = require('../config');
|
const {users} = require('../config');
|
||||||
|
|
||||||
module.exports.findById = (id, done) => {
|
module.exports.findById = (id, done) => {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
if (user.id === id) return done(null, user);
|
if (user.id === id) return done(null, user);
|
||||||
}
|
}
|
||||||
return done(new Error('User Not Found'));
|
logger.log('error', new Error('User Not Found'));
|
||||||
|
return done();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.findByUsername = (username, done) => {
|
module.exports.findByUsername = (username, done) => {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
if (user.username === username) return done(null, user);
|
if (user.username === username) return done(null, user);
|
||||||
}
|
}
|
||||||
return done(new Error('User Not Found'));
|
logger.log('error', new Error('User Not Found'));
|
||||||
|
return done();
|
||||||
};
|
};
|
||||||
|
69
device.js
69
device.js
@@ -1,3 +1,5 @@
|
|||||||
|
const {logger} = global;
|
||||||
|
|
||||||
/* function for convert system values to Yandex (depends of capability or property type) */
|
/* function for convert system values to Yandex (depends of capability or property type) */
|
||||||
function convertToYandexValue(val, actType) {
|
function convertToYandexValue(val, actType) {
|
||||||
switch(actType) {
|
switch(actType) {
|
||||||
@@ -8,7 +10,7 @@ function convertToYandexValue(val, actType) {
|
|||||||
const value = parseFloat(val);
|
const value = parseFloat(val);
|
||||||
return isNaN(value) ? 0.0 : value;
|
return isNaN(value) ? 0.0 : value;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(`Can't parse to float: ${val}`);
|
logger.log('error', {message: `Can't parse to float: ${val}`});
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,11 +40,14 @@ class Device {
|
|||||||
valueMapping: options.valueMapping || [],
|
valueMapping: options.valueMapping || [],
|
||||||
},
|
},
|
||||||
capabilities: (options.capabilities || []).map(c => Object.assign({}, c, {state: (c.state == undefined) ? this.initState(c) : c.state})),
|
capabilities: (options.capabilities || []).map(c => Object.assign({}, c, {state: (c.state == undefined) ? this.initState(c) : c.state})),
|
||||||
properties: (options.properties || []).map(p => Object.assign({}, p, {state: (p.state == undefined) ? this.initState(p) : p.state}))
|
properties: (options.properties || []).map(p => Object.assign({}, p, {state: (p.state == undefined) ? this.initState(p) : p.state})),
|
||||||
}
|
};
|
||||||
|
this.meta = {
|
||||||
|
allowedUsers: options.allowedUsers || ['1'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create init state on device object create */
|
/* Create init state (for capabilities and properties) on device object create */
|
||||||
initState(cp) {
|
initState(cp) {
|
||||||
const {type, parameters} = cp;
|
const {type, parameters} = cp;
|
||||||
const actType = String(type).split('.')[2];
|
const actType = String(type).split('.')[2];
|
||||||
@@ -60,10 +65,10 @@ class Device {
|
|||||||
value: false
|
value: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'toggle': {
|
case 'mode': {
|
||||||
return {
|
return {
|
||||||
instance: parameters.instance,
|
instance: parameters.instance,
|
||||||
value: false
|
value: parameters.modes[0].value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'range': {
|
case 'range': {
|
||||||
@@ -72,20 +77,45 @@ class Device {
|
|||||||
value: parameters.range.min
|
value: parameters.range.min
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'mode': {
|
case 'toggle': {
|
||||||
return {
|
return {
|
||||||
instance: parameters.instance,
|
instance: parameters.instance,
|
||||||
value: parameters.modes[0].value
|
value: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
console.error(`Unsupported capability type: ${type}`)
|
logger.log('error', {message: `Unsupported capability type: ${type}`});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find capability by type (and instance) */
|
||||||
|
findCapability(type, instance) {
|
||||||
|
const {capabilities} = this.data;
|
||||||
|
if (instance != undefined) {
|
||||||
|
return capabilities.find(c => c.type === type && c.state.instance === instance);
|
||||||
|
} else {
|
||||||
|
return capabilities.find(c => c.type === type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find property by type (and instance) */
|
||||||
|
findProperty(type, instance) {
|
||||||
|
const {properties} = this.data;
|
||||||
|
if (instance != undefined) {
|
||||||
|
return properties.find(p => p.type === type && p.state.instance === instance);
|
||||||
|
} else {
|
||||||
|
return properties.find(p => p.type === type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find 'set' topic by instance*/
|
||||||
|
findTopicByInstance(instance) {
|
||||||
|
return this.data.custom_data.mqtt.find(i => i.instance === instance).set;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get mapped value (if exist) for capability type */
|
/* Get mapped value (if exist) for capability type */
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -105,21 +135,6 @@ class Device {
|
|||||||
return (mappedValue != undefined) ? mappedValue : val;
|
return (mappedValue != undefined) ? mappedValue : val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find capability by type */
|
|
||||||
findCapability(type) {
|
|
||||||
return this.data.capabilities.find(c => c.type === type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unused for now */
|
|
||||||
findProperty(type) {
|
|
||||||
return this.data.properties.find(p => p.type === type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find 'set' topic by instance*/
|
|
||||||
findTopicByInstance(instance) {
|
|
||||||
return this.data.custom_data.mqtt.find(i => i.instance === instance).set;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo() {
|
getInfo() {
|
||||||
const {id, name, description, room, type, capabilities, properties} = this.data;
|
const {id, name, description, room, type, capabilities, properties} = this.data;
|
||||||
return {id, name, description, room, type, capabilities, properties};
|
return {id, name, description, room, type, capabilities, properties};
|
||||||
@@ -160,7 +175,7 @@ class Device {
|
|||||||
let message;
|
let message;
|
||||||
let topic;
|
let topic;
|
||||||
try {
|
try {
|
||||||
const capability = this.findCapability(type);
|
const capability = this.findCapability(type, instance);
|
||||||
if (capability == undefined) throw new Error(`Can't find capability '${type}' in device '${id}'`);
|
if (capability == undefined) throw new Error(`Can't find capability '${type}' in device '${id}'`);
|
||||||
capability.state.value = value;
|
capability.state.value = value;
|
||||||
topic = this.findTopicByInstance(instance);
|
topic = this.findTopicByInstance(instance);
|
||||||
@@ -168,7 +183,7 @@ class Device {
|
|||||||
message = `${value}`;
|
message = `${value}`;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
topic = false;
|
topic = false;
|
||||||
console.log(e);
|
logger.log('error', {message: `${e}`});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic) {
|
if (topic) {
|
||||||
@@ -198,7 +213,7 @@ class Device {
|
|||||||
const value = this.getMappedValue(val, actType, false);
|
const value = this.getMappedValue(val, actType, false);
|
||||||
cp.state = {instance, value: convertToYandexValue(value, actType)};
|
cp.state = {instance, value: convertToYandexValue(value, actType)};
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
logger.log('error', {message: `${e}`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,8 @@
|
|||||||
"passport-http": "^0.3.0",
|
"passport-http": "^0.3.0",
|
||||||
"passport-http-bearer": "^1.0.1",
|
"passport-http-bearer": "^1.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-oauth2-client-password": "^0.1.2"
|
"passport-oauth2-client-password": "^0.1.2",
|
||||||
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.21.0",
|
"eslint": "^7.21.0",
|
||||||
|
@@ -20,20 +20,28 @@ module.exports.ping = [
|
|||||||
module.exports.devices = [
|
module.exports.devices = [
|
||||||
passport.authenticate('bearer', {session: true}),
|
passport.authenticate('bearer', {session: true}),
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
const reqId = req.get('X-Request-Id');
|
const [reqId, authToken] = [req.get('X-Request-Id'), req.get('Authorization').split(' ')[1]];
|
||||||
const r = {
|
|
||||||
request_id: reqId,
|
|
||||||
payload: {
|
|
||||||
user_id: "1",
|
|
||||||
devices: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const d of global.devices) {
|
try {
|
||||||
r.payload.devices.push(d.getInfo());
|
const {userId} = global.authl.findOne({'token': authToken});
|
||||||
};
|
|
||||||
|
const r = {
|
||||||
res.status(200).send(r);
|
request_id: reqId,
|
||||||
|
payload: {
|
||||||
|
user_id: userId,
|
||||||
|
devices: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const d of global.devices.filter(d => Array.isArray(d.meta.allowedUsers) && d.meta.allowedUsers.indexOf(userId) > -1)) {
|
||||||
|
r.payload.devices.push(d.getInfo());
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).send(r);
|
||||||
|
} catch (e) {
|
||||||
|
global.logger.log('error', {message: `${e}`});
|
||||||
|
res.status(404).send(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user