
因此,我描述了托管软件附件系统的结构。
简化的模型包括主要流程( bobaoskit.worker
)和附件脚本(使用bobaoskit.sdk
和bobaoskit.accessory
)。 在主要过程中,要求使用附件来控制某些字段。 反过来,从附件请求主要对象更新状态。
以普通继电器为例。
使用输入命令,继电器有时可能由于各种原因(设备冻结等)而无法更改其位置。 因此,我们将不派遣多少团队,状态也不会改变。 并且,在另一种情况下,当由第三方系统命令时,继电器可以更改其状态。 在这种情况下,其状态将更改,附件脚本可以响应有关状态更改的传入事件,并向主进程发送请求。
动机
在多个对象上实现了Apple HomeKit之后,我开始寻找与Android类似的东西,因为 我本人只有从iOS设备运行的iPad。 主要标准是能够在没有云服务的情况下在本地网络上工作的能力。 而且,HomeKit中缺少的是有限的信息。 例如,您可以使用恒温器。 它的所有控制都简化为操作模式(关闭,加热,冷却和自动)和设定温度的选择。 简单越好,但我认为并非总是如此。 没有足够的诊断信息。 例如,空调,换流器,什么通风参数是否正常工作。 可能由于内部错误导致空调无法工作。 鉴于可以考虑此信息,因此决定编写其实现。
可以看看ioBroker,OpenHAB,家庭助理等选项。
但是在列出的node.js中,只有ioBroker(在我撰写文章时,注意到redis也参与了该过程)。 到那时,他已经发现了如何组织进程间通信,并且处理redis变得很有趣,最近有人听说过。
您还可以注意以下规格:
→ Web Thing API
装置

Redis
帮助进程间通信,并且还充当附件的数据库。
bobaoskit.worker
模块发生请求队列(使用bee-queue
在redis
之上),执行请求,从数据库中写入/读取。
在用户脚本中, bobaoskit.accessory
对象侦听此特定附件的单独的bee-queue
,执行规定的操作,并通过bobaoskit.sdk
对象将请求发送到主进程队列。
协议书
所有请求和发布的消息都是JSON
格式的字符串,其中包含method
和payload
字段。 即使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 accessory
( add accessory
); 附件已移除( remove accessory
); 附件更新状态( update status value
)。
广播消息还包含两个字段: method
和payload
。
客户端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
调用功能用于事件ready
, error
, control 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_id
, method
和payload
。
该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 value
, control 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
模型的任何附件。
什么可以做得更好:
- 二进制协议将允许发送较少的数据。 另一方面,
JSON
开发和理解速度更快。 二进制协议也需要标准化。
仅此而已,我将很高兴收到任何反馈。