wirone
Wirone - микро-фреймворк для работы с умным домом Яндекса и реализации Yandex Smarthome Adapter API. Основан на Express.js
Возможности
-
Встроенная реализация сервера OAuth авторизации
-
Поддержка query и action обработчиков для умений и встроенных датчиков устройства, что избавляет от надобности формировать JSON-объект с полной информацией об устройстве:
const info = {
name: "Чайник",
type: "devices.types.cooking.kettle",
capabilities: [
OnOffCapability({
onQuery: powerQuery,
onAction: powerAction
}),
RangeCapability({
parameters: {
instance: "temperature",
unit: "unit.temperature.celsius",
range: {
min: 60,
max: 100
}
},
onQuery: temperatureQuery,
onAction: temperatureAction
})
]
}
-
Описание устройств в виде независимых модулей:
const OnOffCapability = require("wirone").capabilities.OnOff;
const ledBulb = () => {
const powerQuery = () => new Promise((resolve, reject) => {
});
const powerAction = (state) => new Promise((resolve, reject) => {
});
const info = {
name: "Лампочка",
type: "devices.types.light",
capabilities: [
OnOffCapability({
onQuery: powerQuery,
onAction: powerAction
})
]
}
return Object.freeze({
info
});
}
module.exports = ledBulb();
Быстрый старт
Для демонстрации возможностей и примеров кода был создан репозиторий с шаблоном приложения.
Шаблон содержит базовую реализацию сервера, включая готовые обработчики OAuth авторизации, связь с базой данных MySQL, и описание трех устройств для умного дома.
Инициализация Wirone
Чтобы использовать Wirone в вашем приложении, вам необходимо установить Express.js, а также инициализировать Wirone с определенной конфигурацией.
Пример кода для инициализации Wirone:
const fs = require("fs");
const https = require("https");
const express = require("express");
const app = express();
const wirone = require("wirone");
const devices = {
ledBulb: require("./src/devices/ledBulb.js"),
switch: require("./src/devices/switch.js")
};
app.use(express.json());
app.use(express.urlencoded({extended: true}));
wirone.init(app, {
debug: true,
oauth: {
client: "wirone",
secret: "your_secret_here",
lifetime: 3600,
authorization_page: {
type: "static_page",
path: path.join(__dirname + "/static/oauth/index.html")
},
onAuthorize: oauth.generateCode,
onGranted: oauth.saveAccessToken,
onRefresh: oauth.refreshAccessToken,
onVerify: oauth.verifyAccessToken
}
});
wirone.query((userID) => new Promise((resolve, reject) => {
if (userID == 1)
resolve([
devices.ledBulb,
devices.switch
]);
else
reject("Access denied for user with ID '" + userID + "'");
}));
https.createServer({
key: fs.readFileSync("./static/ssl/privkey.pem"),
ca: fs.readFileSync("./static/ssl/chain.pem"),
cert: fs.readFileSync("./static/ssl/cert.pem")
}, app).listen(443, "0.0.0.0", 10, () => {
console.log("> Server ready");
});
Реализация обработчиков OAuth авторизации:
Для понимания работы обработчиков OAuth авторизации рекомендуется посмотреть их реализацию в репозитории с шаблоном приложения.
Описание пользовательских устройств
Устройства в wirone представляют собой независимые модули (функциональные объекты), которые, как правило, содержат объект с информацией об устройстве и описание обработчиков, для умений и встроенных датчиков.
Объект с информацией об устройстве описывается используя параметры из документации, а также используя объекты умений (capabilities) и встроенных датчиков (properties).
Рассмотрим пример описания умной лампочки, которая находится в локальной сети, и поддерживает управление через REST-подобное API:
const request = require("request");
const OnOffCapability = require("wirone").capabilities.OnOff;
const ledBulb = () => {
const powerQuery = () => new Promise((resolve, reject) => {
request({
url: "http://192.168.0.110/state",
method: "get"
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "on",
value: body.power
});
});
});
const powerAction = (state) => new Promise((resolve, reject) => {
let newPowerState = state.value;
request({
url: "http://192.168.0.110/state",
method: "post",
json: {
power: newPowerState
}
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "on",
action_result: {
status: "DONE"
}
});
});
});
const info = {
name: "Лампочка",
type: "devices.types.light",
capabilities: [
OnOffCapability({
onQuery: powerQuery,
onAction: powerAction
})
]
}
return Object.freeze({
info
});
}
module.exports = ledBulb();
Можно также добавить возможность управлять цветом нашей лампочки, используя умение Color_setting:
const request = require("request");
const OnOffCapability = require("wirone").capabilities.OnOff;
const ColorCapability = require("wirone").capabilities.ColorSetting;
const ledBulb = () => {
const powerQuery = () => new Promise((resolve, reject) => {
request({
url: "http://192.168.0.110/state",
method: "get"
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "on",
value: body.power
});
});
});
const powerAction = (state) => new Promise((resolve, reject) => {
let newPowerState = state.value;
request({
url: "http://192.168.0.110/state",
method: "post",
json: {
power: newPowerState
}
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "on",
action_result: {
status: "DONE"
}
});
});
});
const colorQuery = () => new Promise((resolve, reject) => {
request({
url: "http://192.168.0.110/state",
method: "get"
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "rgb",
value: {
r: body.color.r,
g: body.color.g,
b: body.color.b
}
});
});
});
const colorAction = (state) => new Promise((resolve, reject) => {
let newColorState = state.value;
request({
url: "http://192.168.0.110/state",
method: "post",
json: {
color: newColorState
}
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "rgb",
action_result: {
status: "DONE"
}
});
});
});
const info = {
name: "Лампочка",
type: "devices.types.light",
capabilities: [
OnOffCapability({
onQuery: powerQuery,
onAction: powerAction
}),
ColorCapability({
parameters: {
color_model: "rgb"
},
onQuery: colorQuery,
onAction: colorAction
})
]
}
return Object.freeze({
info
});
}
module.exports = ledBulb();
Чтобы не дублировать отправку запросов для получения текущего состояния устройства, можно использовать обработчик globalQuery:
const request = require("request");
const OnOffCapability = require("wirone").capabilities.OnOff;
const ColorCapability = require("wirone").capabilities.ColorSetting;
const ledBulb = () => {
const globalQuery = () => new Promise((resolve, reject) => {
request({
url: "http://192.168.0.110/state",
method: "get"
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve(body);
});
});
const powerQuery = (globalState) => new Promise((resolve, reject) => {
resolve({
instance: "on",
value: globalState.power
});
});
const powerAction = (state) => new Promise((resolve, reject) => {
let newPowerState = state.value;
request({
url: "http://192.168.0.110/state",
method: "post",
json: {
power: newPowerState
}
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "on",
action_result: {
status: "DONE"
}
});
});
});
const colorQuery = (globalState) => new Promise((resolve, reject) => {
resolve({
instance: "rgb",
value: {
r: globalState.color.r,
g: globalState.color.g,
b: globalState.color.b
}
});
});
const colorAction = (state) => new Promise((resolve, reject) => {
let newColorState = state.value;
request({
url: "http://192.168.0.110/state",
method: "post",
json: {
color: newColorState
}
}, (error, response, body) => {
if (error != null)
return reject("INTERNAL_ERROR");
resolve({
instance: "rgb",
action_result: {
status: "DONE"
}
});
});
});
const info = {
name: "Лампочка",
type: "devices.types.light",
globalQuery: globalQuery,
capabilities: [
OnOffCapability({
onQuery: powerQuery,
onAction: powerAction
}),
ColorCapability({
parameters: {
color_model: "rgb"
},
onQuery: colorQuery,
onAction: colorAction
})
]
}
return Object.freeze({
info
});
}
module.exports = ledBulb();
Другой пример описание устройства вы можете посмотреть в репозитории шаблона для Wirone.
Планы развития на будущее
Планируется реализация:
- Инструментов для удобного взаимодействия с API сервиса уведомлений
- Поддержки встроенных датчиков с типом Event, когда их функционал выйдет из стадии бета-тестирования