homebridge插件编写

前言

Homebridge is a lightweight NodeJS server that emulates the iOS HomeKit API; 之前在linux上部署过homebridge,最近玩路由在路由器上部署了homebridge,具体怎么部署就不说了,还是来重温一下homebridge插件如何编写,搭建一个Siri物联网吧,小白不会nodejs有点忧桑!

Plugins can publish Accessories and/or Platforms. Accessories are individual devices, like a smart switch or a garage door. Platforms act like a single device but can expose a set of devices, like a house full of smart lightbulbs. 即插件会发布 Accessories 和 Platforms,Accessories是一个独立的设备,而Platforms是这些设备所连接的同一个平台/同一组设备

如何编写插件

homebridge插件编写

上图可知,homebridge的作用就是连接Homekit协议和Device协议,起一个桥的作用。插件包含两个文件pakage.json和index.js,package.json是管理依赖的,index.js是写插件核心逻辑的。

先来看一下HomeKit协议的layout布局:

  • Home:整栋房子的Accessory设备
  • Room:一间屋子的Accessory设备
  • Platform:一组Accessory设备
  • Accessory:独立的Accessory设备
  • Bridge:特殊的Accessory设备,允许与不能直接与HomeKit通信的Accessory设备通信
  • Service:Service对应Accessory设备的功能,如车库门有开门和关门服务
  • Characteristic:每个服务有一系列的Characteristic特性,每个特性有3个权限,read、write和notify,这些Characteristic特性可以在 here 里找到

再来看一下homebridge插件的文件结构

--> mySwitch
	--> config
		--> config.json #插件配置文件
	--> plugin
		--> index.js #核心逻辑,需要自己编写
		-->package.json #插件包管理

第一步:API需求分析

以一个开关插件为例,假设它需要的API有:

  • HTTP协议控制
  • GET请求能够返回一个布尔值表示当前开关的状态(通过读取switch的On characteristic )
  • POST请求包含一个布尔值表示开关的目标状态(通过设置switch的On characteristic)

第二步:注册Accessory设备

将mySwitch插件注入到homebridge,mySwitch是一个javascript对象,包含如下控制逻辑

var Service, Characteristic;
//对外接口 供homebridge引入 
module.exports = function (homebridge) {
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  // registerAccessory' three parameters is plugin-name, accessory-name, constructor-name
  homebridge.registerAccessory("switch-plugin", "MyAwesomeSwitch", mySwitch);
};

第三步:实例化Service服务

支持的service有(可以从HomeKitTypes.js文件中找到)

HomeKitTypes.js:2650: * Service "Accessory Information"
HomeKitTypes.js:2674: * Service "Air Purifier" #空气净化器
HomeKitTypes.js:2697: * Service "Air Quality Sensor" #空气质量传感器
HomeKitTypes.js:2727: * Service "Battery Service"
HomeKitTypes.js:2747: * Service "Camera RTP Stream Management"
HomeKitTypes.js:2770: * Service "Carbon Dioxide Sensor"
HomeKitTypes.js:2794: * Service "Carbon Monoxide Sensor"
HomeKitTypes.js:2818: * Service "Contact Sensor" #接触传感器
HomeKitTypes.js:2840: * Service "Door"
HomeKitTypes.js:2862: * Service "Doorbell"
HomeKitTypes.js:2882: * Service "Fan" #风扇
HomeKitTypes.js:2902: * Service "Fan v2"
HomeKitTypes.js:2926: * Service "Filter Maintenance"
HomeKitTypes.js:2946: * Service "Faucet"
HomeKitTypes.js:2965: * Service "Garage Door Opener"
HomeKitTypes.js:2987: * Service "Heater Cooler"
HomeKitTypes.js:3014: * Service "Humidifier Dehumidifier"
HomeKitTypes.js:3041: * Service "Humidity Sensor" #湿度传感器
HomeKitTypes.js:3063: * Service "Irrigation System"
HomeKitTypes.js:3085: * Service "Leak Sensor"
HomeKitTypes.js:3107: * Service "Light Sensor" #光照传感器
HomeKitTypes.js:3129: * Service "Lightbulb" #灯泡
HomeKitTypes.js:3151: * Service "Lock Management"
HomeKitTypes.js:3177: * Service "Lock Mechanism"
HomeKitTypes.js:3196: * Service "Microphone"
HomeKitTypes.js:3215: * Service "Motion Sensor"
HomeKitTypes.js:3237: * Service "Occupancy Sensor" #人体传感器
HomeKitTypes.js:3259: * Service "Outlet"
HomeKitTypes.js:3278: * Service "Security System" #安全系统
HomeKitTypes.js:3300: * Service "Service Label"
HomeKitTypes.js:3318: * Service "Slat"
HomeKitTypes.js:3340: * Service "Smoke Sensor" #烟雾传感器
HomeKitTypes.js:3362: * Service "Speaker"
HomeKitTypes.js:3381: * Service "Stateless Programmable Switch"
HomeKitTypes.js:3400: * Service "Switch" #开关
HomeKitTypes.js:3418: * Service "Temperature Sensor" #温度传感器
HomeKitTypes.js:3440: * Service "Thermostat" #恒温器
HomeKitTypes.js:3466: * Service "Valve"
HomeKitTypes.js:3491: * Service "Window" #窗户
HomeKitTypes.js:3513: * Service "Window Covering" #窗帘

注册switch时涉及到的两个service:

  • AccessoryInformation service:每个Accessory都需要广播与设备本身相关的信息,无论其类型如何;它包含了ManufacturerModel SerialNumber等特性

    /**
     * Service "Accessory Information"
     */
    
    Service.AccessoryInformation = function(displayName, subtype) {
      Service.call(this, displayName, '0000003E-0000-1000-8000-0026BB765291', subtype);
    
      // Required Characteristics
      this.addCharacteristic(Characteristic.Identify);
      this.addCharacteristic(Characteristic.Manufacturer);
      this.addCharacteristic(Characteristic.Model);
      this.addCharacteristic(Characteristic.Name);
      this.addCharacteristic(Characteristic.SerialNumber);
      this.addCharacteristic(Characteristic.FirmwareRevision);
    
      // Optional Characteristics
      this.addOptionalCharacteristic(Characteristic.HardwareRevision);
      this.addOptionalCharacteristic(Characteristic.AccessoryFlags);
    };
    
  • Swith service:真正的开关service,switch设备具有布尔特性On

    /**
     * Service "Switch"
     */
    
    Service.Switch = function(displayName, subtype) {
      Service.call(this, displayName, '00000049-0000-1000-8000-0026BB765291', subtype);
    
      // Required Characteristics
      this.addCharacteristic(Characteristic.On);
    
      // Optional Characteristics
      this.addOptionalCharacteristic(Characteristic.Name);
    };
    
  • 每种设备的特性都可以在HomeKitTypes.js源码中查找到

mySwitch对象的getServices原型函数中,需要实例化以上两个service。每个service的每个characteristic 的getter和setter方法,将调用来自Homekit每个requests

mySwitch.prototype = {
  getServices: function () {
    let informationService = new Service.AccessoryInformation();
    informationService
      .setCharacteristic(Characteristic.Manufacturer, "My switch manufacturer") //setter
      .setCharacteristic(Characteristic.Model, "My switch model")
      .setCharacteristic(Characteristic.SerialNumber, "123-456-789");
 
    let switchService = new Service.Switch("My switch");
    switchService
      .getCharacteristic(Characteristic.On)
        .on('get', this.getSwitchOnCharacteristic.bind(this)) //getter
        .on('set', this.setSwitchOnCharacteristic.bind(this)); //setter
 
    this.informationService = informationService;
    this.switchService = switchService;
    return [informationService, switchService];
  }
};

不同于AccessoryInformation服务的特性(它是可读的,但只能在插件初始化时设置),On特性既可以写亦可以响应getter和setter

第四步:编写characteristic 的getter和setter方法

mySwitch对象的专用原型函数中编写On特性的getter和setter的逻辑。假设switch的控制逻辑如下

使用requesturl模块执行HTTP请求,需要将这两个请求定义为全局变量

const request = require('request');
const url = require('url');
 
function mySwitch(log, config) {
  this.log = log;
  this.getUrl = url.parse(config['getUrl']);
  this.postUrl = url.parse(config['postUrl']);
}
 
mySwitch.prototype = {
  getSwitchOnCharacteristic: function (next) { //getter
    const me = this;
    request({
        url: me.getUrl,
        method: 'GET',
    }, 
    function (error, response, body) {
      if (error) {
        me.log('STATUS: ' + response.statusCode);
        me.log(error.message);
        return next(error);
      }
      return next(null, body.currentState);
    });
  },
   
  setSwitchOnCharacteristic: function (on, next) { //setter
    const me = this;
    request({
      url: me.postUrl,
      body: {'targetState': on},
      method: 'POST',
      headers: {'Content-type': 'application/json'}
    },
    function (error, response) {
      if (error) {
        me.log('STATUS: ' + response.statusCode);
        me.log(error.message);
        return next(error);
      }
      return next();
    });
  }
};

第五步:安装插件

npm install -g switch-plugin

打开config.json,在accessory 部分添加

{
  "accessory": "MyAwesomeSwitch",
  "getUrl": "http://192.168.0.10/api/status",
  "postUrl": "http://192.168.0.10/api/order"
}

重启homebridge即可!

参考链接

Github homebridge

npm中已发布的插件

How To Make Siri your Perfect Home Companion With Devices not Supported by Apple Homekit