npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

wirone

v1.0.0-1

Published

Wirone is a micro-framework for creating Yandex Smarthome Adapter API

Readme

wirone

Wirone - микро-фреймворк для работы с умным домом Яндекса и реализации Yandex Smarthome Adapter API. Основан на Express.js

Возможности

  • Встроенная реализация сервера OAuth авторизации

  • Поддержка query и action обработчиков для умений и встроенных датчиков устройства, что избавляет от надобности формировать JSON-объект с полной информацией об устройстве:

    const info = {
        name: "Чайник",
        type: "devices.types.cooking.kettle",
    
        capabilities: [
            OnOffCapability({
                // Функция, реализованная как Promise, которая возвращает текущее состояние умения OnOff
                onQuery: powerQuery,
                // Функция, реализованная как Promise, которая обрабатывает изменение состояния умения OnOff
                onAction: powerAction
            }),
            RangeCapability({
                parameters: {
                    instance: "temperature",
                    unit: "unit.temperature.celsius",
                    range: {
                        min: 60,
                        max: 100
                    }
                },
    
                // Функция, реализованная как Promise, которая возвращает текущее состояние умения Range (целевая температура воды)
                onQuery: temperatureQuery,
                // Функция, реализованная как Promise, которая обрабатывает изменение состояния умения Range
                onAction: temperatureAction
            })
        ]
    }
  • Описание устройств в виде независимых модулей:

    const OnOffCapability = require("wirone").capabilities.OnOff;
      
    const ledBulb = () => {
        const powerQuery = () => new Promise((resolve, reject) => {
            // Your awesome code
        });
    
        const powerAction = (state) => new Promise((resolve, reject) => {
            // Your awesome code
        });
    
        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
wirone.init(app, {
    // Обязательные параметры помечены символом *

    // debug - включает отладочные сообщения об OAuth авторизации, запросах к устройствам и т.д.
    // По умолчанию: false
    debug: true,

    // oauth* - конфигурация OAuth авторизации
    oauth: {
        // client* - идентификатор приложения (client identifier) для реализации связки аккаунтов через OAuth
        client: "wirone",

        // secret* - секрет приложения (client password) для реализации связки аккаунтов через OAuth
        secret: "your_secret_here",

        // lifetime - время жизни токена в секундах
        // По умолчанию: 3600
        lifetime: 3600,

        // authorization_page* - объект с описанием типа используемой страницы для авторизации пользователей
        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)
        // Возвращаем устройства для пользователя с ID 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:

// Файл ledBulb.js

const request = require("request");

const OnOffCapability = require("wirone").capabilities.OnOff;

const ledBulb = () => {
    // Обработчик текущего состояния (query) для умения On_off
    const powerQuery = () => new Promise((resolve, reject) => {
        // Запрос к устройству в локальной сети
        request({
            url: "http://192.168.0.110/state",
            method: "get"
        }, (error, response, body) => {
            /*
                Представим, что в ответ устройство возвращает JSON объект вида:
                
                {
                    "power": true,
                    "color": {
                        "r": 255,
                        "g": 42,
                        "b": 14
                    }
                }
                
                Объект содержит информацию о том, включено ли устройство, а также объект с интенсивностью свечения
                оттенков красного, зеленого и синего, от 0 до 255
            */
            
            // Если в процессе запроса произошла ошибка, необходимо вызвать reject с кодом ошибки из документации
            // Подробное описание кодов ошибок находится здесь: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/response-codes.html
            if (error != null)
                return reject("INTERNAL_ERROR");

            // При успешном выполнении запроса записываем полученные данные в объект состояния, который формируется
            // в соответствии с используемым умением или встроенным датчком
      
            // Пример объекта для умения On_off: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html#state__parameters
            resolve({
                instance: "on",
                value: body.power
            });
        });
    });

    // Обработчик изменения состояния (action) для умения On_off
    const powerAction = (state) => new Promise((resolve, reject) => {
        // Параметр state содержит структуру, идентичную объекту state в примере запроса изменения
        // состояния умения On_off: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html#action__example
        
        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 с передачей объекта, который описывает результат изменения состояния умения
            // Пример объекта для умения On_off: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html#action__parameters
            resolve({
                instance: "on",
                action_result: {
                    status: "DONE"
                }
            });
        });
    });

    // Объект с информацией об устройстве
    // Может включать в себя параметры, описанные в документации: https://yandex.ru/dev/dialogs/smart-home/doc/reference/get-devices.html#output-structure
    const info = {
        // Имя устройства
        name: "Лампочка",
        
        // Тип устройства
        type: "devices.types.light",

        // Массив с описанием умений устройства
        capabilities: [
            // Описание умения On_off
            
            // При описании умений и встроенных датчиков используется объект с параметрами, приведенный в документации,
            // например: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html#discovery__parameters
            OnOffCapability({
                // Установка обработчиков для умения
                onQuery: powerQuery,
                onAction: powerAction
            })
        ]
    }

    // Устройство обязательно должно возвращать объект с информацией о нем
    return Object.freeze({
        info
    });
}

module.exports = ledBulb();

Можно также добавить возможность управлять цветом нашей лампочки, используя умение Color_setting:

// Файл ledBulb.js

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"
                }
            });
        });
    });
    
    // Обработчик текущего состояния (query) для умения Color_setting
    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
                }
            });
        });
    });
    
    // Обработчик изменения состояния (action) для умения Color_setting
    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
            }),
            
            // Описание умения Color_setting
            ColorCapability({
                // Используем цветовую модель RGB
                parameters: {
                    color_model: "rgb"
                },

                onQuery: colorQuery,
                onAction: colorAction
            })
        ]
    }

    return Object.freeze({
        info
    });
}

module.exports = ledBulb();

Чтобы не дублировать отправку запросов для получения текущего состояния устройства, можно использовать обработчик globalQuery:

// Файл ledBulb.js

const request = require("request");

const OnOffCapability = require("wirone").capabilities.OnOff;
const ColorCapability = require("wirone").capabilities.ColorSetting;

const ledBulb = () => {
    // Функция globalQuery выполняется при запросе состояния устройства, и позволяет установить глобальное состояние,
    // данные из которого в дальнейшем можно использовать для присвоения значений умений и встроенных датчиков
    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");

            // Устанавливаем полученный от устройства JSON-объект как глобальное состояние
            resolve(body);
        });
    });

    const powerQuery = (globalState) => new Promise((resolve, reject) => {
        // Если была использована функция globalQuery, объект, который был передан в resolve, доступен через параметр globalState
        // в любом обработчике состояния
        
        resolve({
            instance: "on",
            // Установка значения для умения On_off из глобального состояния
            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",
            // Аналогично, устанавливаем значение цвета для умения Color_setting
            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, когда их функционал выйдет из стадии бета-тестирования