bobaoskit-附件,dnssd和WebSocket


因此,我描述了托管软件附件系统的结构。


简化的模型包括主要流程( bobaoskit.worker )和附件脚本(使用bobaoskit.sdkbobaoskit.accessory )。 在主要过程中,要求使用附件来控制某些字段。 反过来,从附件请求主要对象更新状态。


以普通继电器为例。


使用输入命令,继电器有时可能由于各种原因(设备冻结等)而无法更改其位置。 因此,我们将不派遣多少团队,状态也不会改变。 并且,在另一种情况下,当由第三方系统命令时,继电器可以更改其状态。 在这种情况下,其状态将更改,附件脚本可以响应有关状态更改的传入事件,并向主进程发送请求。


动机


在多个对象上实现了Apple HomeKit之后,我开始寻找与Android类似的东西,因为 我本人只有从iOS设备运行的iPad。 主要标准是能够在没有云服务的情况下在本地网络上工作的能力。 而且,HomeKit中缺少的是有限的信息。 例如,您可以使用恒温器。 它的所有控制都简化为操作模式(关闭,加热,冷却和自动)和设定温度的选择。 简单越好,但我认为并非总是如此。 没有足够的诊断信息。 例如,空调,换流器,什么通风参数是否正常工作。 可能由于内部错误导致空调无法工作。 鉴于可以考虑此信息,因此决定编写其实现。


可以看看ioBroker,OpenHAB,家庭助理等选项。
但是在列出的node.js中,只有ioBroker(在我撰写文章时,注意到redis也参与了该过程)。 到那时,他已经发现了如何组织进程间通信,并且处理redis变得很有趣,最近有人听说过。


您还可以注意以下规格:


Web Thing API


装置



Redis帮助进程间通信,并且还充当附件的数据库。


bobaoskit.worker模块发生请求队列(使用bee-queueredis之上),执行请求,从数据库中写入/读取。


在用户脚本中, bobaoskit.accessory对象侦听此特定附件的单独的bee-queue ,执行规定的操作,并通过bobaoskit.sdk对象将请求发送到主进程队列。


协议书


所有请求和发布的消息都是JSON格式的字符串,其中包含methodpayload字段。 即使payload = null ,也需要字段。


要求bobaoskit.worker


  • 方法: ping ,有效载荷: null
  • 方法: get general info ,有效载荷: null
  • 方法: clear accessories ,有效载荷: null
  • 方法: add accessory
    有效载荷:

 { id: "accessoryId", type: "switch/sensor/etc", name: "Accessory Display Name", control: [<array of control fields>], status: [<array of status fields>] } 

  • 方法: remove accessory ,有效载荷: accessoryId/[acc1id, acc2id, ...]
  • 方法: get accessory info ,有效载荷: null/accId/[acc1id, acc2id...]
    payload字段中,您可以发送附件的null /质量id 。 如果发送null ,则将返回有关所有现有附件的信息。
  • 方法: get status value ,有效载荷: {id: accessoryId, status: fieldId}
    payload字段中,您可以发送{id: accessoryId, status: fieldId}形式的对象(其中status字段可以是字段数组),或者payload可以是此类对象数组。
  • 方法: update status value ,有效负载: {id: accessoryId, status: {field: fieldId, value: value}
    payload字段中,您可以发送以下形式的对象{id: accessoryId, status: {field: fieldId, value: value}} ,(其中status字段可以是数组{field: fieldId, value: value} ),或者payload可以是此类对象的数组有点。
  • 方法: control accessory value ,有效载荷: {id: accessoryId, control: {field: fieldId, value: value}}
    payload字段中,您可以发送以下形式的对象{id: accessoryId, control: {field: fieldId, value: value}} (其中control字段可以是数组{field: fieldId, value: value} ),或者payload可以是这样的对象数组有点。

响应任何请求(如果成功),将显示以下形式的消息:


{ method: "success", payload: <...> }


如果失败:


{ method: "error", payload: "Error description" }


在以下情况下,消息还会在redis PUB/SUB通道(在config.json中定义)中发布:所有附件都被清除( clear accessories ); add accessoryadd accessory ); 附件已移除( remove accessory ); 附件更新状态( update status value )。


广播消息还包含两个字段: methodpayload


客户端SDK


内容描述


客户端SDK( bobaoskit.accessory )允许您从js脚本调用上述方法。


模块内部有两个构造函数对象。 第一个创建一个Sdk对象来访问上述方法,第二个创建一个附件-这些函数之上的包装器。


 const BobaosKit = require("bobaoskit.accessory"); //   sdk. //  , //     , //     sdk, const sdk = BobaosKit.Sdk({ redis: redisClient // optional job_channel: "bobaoskit_job", // optional. default: bobaoskit_job broadcast_channel: "bobaoskit_bcast" // optional. default: bobaoskit_bcast }); //   const dummySwitchAcc = BobaosKit.Accessory({ id: "dummySwitch", // required name: "Dummy Switch", // required type: "switch", // required control: ["state"], // requried. ,   . status: ["state"], // required.   . sdk: sdk, // optional. //   ,   sdk   //     redis: undefined, job_channel: "bobaoskit_job", broadcast_channel: "bobaoskit_bcast" }); 

sdk对象支持Promise方法:


 sdk.ping(); sdk.getGeneralInfo(); sdk.clearAccessories(); sdk.addAccessory(payload); sdk.removeAccessory(payload); sdk.getAccessoryInfo(payload); sdk.getStatusValue(payload); sdk.updateStatusValue(payload); sdk.controlAccessoryValue(payload); 

BobaosKit.Accessory({..})对象BobaosKit.Accessory({..})BobaosKit.Sdk(...)对象顶部的包装。


接下来,我将说明如何解决:


 //     self.getAccessoryInfo = _ => { return _sdk.getAccessoryInfo(id); }; self.getStatusValue = payload => { return _sdk.getStatusValue({ id: id, status: payload }); }; self.updateStatusValue = payload => { return _sdk.updateStatusValue({ id: id, status: payload }); }; 

这两个对象也是EventEmitter
Sdk针对已ready和已broadcasted event事件调用功能。
Accessory调用功能用于事件readyerrorcontrol accessory value


例子


 const BobaosKit = require("bobaoskit.accessory"); const Bobaos = require("bobaos.sub"); // init bobaos with default params const bobaos = Bobaos(); // init sdk with default params const accessorySdk = BobaosKit.Sdk(); const SwitchAccessory = params => { let { id, name, controlDatapoint, stateDatapoint } = params; // init accessory const swAcc = BobaosKit.Accessory({ id: id, name: name, type: "switch", control: ["state"], status: ["state"], sdk: accessorySdk }); //       state //     KNX  bobaos swAcc.on("control accessory value", async (payload, cb) => { const processOneAccessoryValue = async payload => { let { field, value } = payload; if (field === "state") { await bobaos.setValue({ id: controlDatapoint, value: value }); } }; if (Array.isArray(payload)) { await Promise.all(payload.map(processOneAccessoryValue)); return; } await processOneAccessoryValue(payload); }); const processOneBaosValue = async payload => { let { id, value } = payload; if (id === stateDatapoint) { await swAcc.updateStatusValue({ field: "state", value: value }); } }; //      KNX //   state  bobaos.on("datapoint value", payload => { if (Array.isArray(payload)) { return payload.forEach(processOneBaosValue); } return processOneBaosValue(payload); }); return swAcc; }; const switches = [ { id: "sw651", name: "", controlDatapoint: 651, stateDatapoint: 652 }, { id: "sw653", name: " 1", controlDatapoint: 653, stateDatapoint: 653 }, { id: "sw655", name: " 2", controlDatapoint: 655, stateDatapoint: 656 }, { id: "sw657", name: " 1", controlDatapoint: 657, stateDatapoint: 658 }, { id: "sw659", name: "", controlDatapoint: 659, stateDatapoint: 660 } ]; switches.forEach(SwitchAccessory); 

WebSocket API


bobaoskit.worker监听./config.json定义的WebSocket端口。


传入请求是JSON字符串,必须具有以下字段: request_idmethodpayload


该API仅限于以下请求:


  • 方法: ping ,有效载荷: null
  • 方法: get general info ,有效载荷: null
  • 方法: get accessory info ,有效载荷: null/accId/[acc1Id, ...]
  • 方法: get status value ,有效载荷: {id: accId, status: field1/[field1, ...]}/[{id: ...}...]
  • 方法: control accessory value ,有效负载: {id: accId, control: {field: field1, value: value}/[{field: .. value: ..}]}/[{id: ...}, ...]

get status valuecontrol accessory value方法将payload字段接受为单个对象或数组。 payload内的control/status字段也可以是单个对象或数组。


来自服务器的以下事件消息也发送到所有客户端:


  • 方法: clear accessories ,有效载荷:空
  • 方法: remove accessory ,有效载荷:附件ID
  • 方法: add accessory, payload :{id:...}
  • 方法: update status value, payload :{id:...}

dnssd


由于npm dnssd模块,该应用程序将本地网络上的WebSocket端口作为_bobaoskit._tcp服务dnssd


演示版



将另外撰写一篇文章,介绍如何使用视频和flutter印象编写应用程序。


后记


因此,获得了用于管理软件附件的简单系统。
配件可以与现实世界中的物体形成对比:按钮,传感器,开关,恒温器,收音机。 由于没有标准化,因此可以实现适合control < == > update模型的任何附件。


什么可以做得更好:


  1. 二进制协议将允许发送较少的数据。 另一方面, JSON开发和理解速度更快。 二进制协议也需要标准化。

仅此而已,我将很高兴收到任何反馈。

Source: https://habr.com/ru/post/zh-CN437846/


All Articles