Merge branch 'beta'

This commit is contained in:
Eugene Abramov
2021-09-07 19:04:10 +03:00
10 changed files with 394 additions and 180 deletions

332
README.md
View File

@@ -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
View File

@@ -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;

View File

@@ -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',

View File

@@ -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();

View File

@@ -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) => {

View File

@@ -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();
}; };

View File

@@ -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();
}; };

View File

@@ -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}`});
} }
} }
} }

View File

@@ -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",

View File

@@ -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);
}
} }
]; ];