物联网语音云云接入
物联网语音云云对接
云云对接,即在设备物联网体系中,厂商自建设备云平台服务与各三方平台云服务的对接。
厂商在三方平台创建自身的技能平台后,适配各厂商提供的云服务对接协议,即可实现与厂商自建体系的间接连接;
以天猫精灵的简介图为例子:
通讯协议几乎只包含HTTP接口调用的方式,因此云云对接的流程看起来就很简单且清澈:
- 用户语音触发三方平台,以下以发起
打开空调
为例 - 三方平台根据
打开空调
找到对应设备,将设备id与打开空调
动作通过HTTP接口调用的方式,转递给产商云 - 产商云根据
控制设备
指令协议进行接口对接,将其消耗至内部云服务中,进行实施的设备控制
除了HTTP接口调用的形式,还存在少量支持局域网下WIFI/蓝牙形式的协议对接,不过整体流程与上述无异;
其后,本篇将介绍云云对接的一般流程、抽象架构以及奇妙的开发方案
一般流程
本篇略过用户授权这一技能授权前置,因此一个标准的 三方云->产商云
一定是这样的过程:
- 发现设备
- 控制设备
- 查询/同步设备
- 主动上报
其中,发现设备
一定是首要的动作,其余三个都是设备语音技能的赋能项;
发现设备
根据 小度
小爱
Tmall
...
等平台的发现设备协议,由于暂未发现其他平台的异处,发现设备一定由以下部分组成:
- 设备id、设备名、设备类型[三方云平台]
- 技能列表、属性列表
- 用户/客户端id
因此这一流程的关键点是:将产商云设备的技能、属性与三方云文档中所列一一对应;
根据各平台的特性,一般分为两类对应模式:
- 三方平台内创建产品,定义其属性、枚举、技能,如 天猫
- 三方平台自规定产品,如 小度、小爱
所以此流程的执行动作为:
- 接受三方平台发现设备请求,请求中一定带有用户id信息
- 根据用户id查询厂商云中,该用户所属设备信息
- 将厂商云设备于三方云设备进行 上述两种模式类型的值映射
- 返回结果集
具体的抽象架构后续补充
控制、查询设备
控制与查询一样,都是由三方平台主动发起的技能动作;
在目前已知的平台中,查询亦或控制,都是由其平台报文中的一值区分;
比如小度的查询空气指令:
由namespace
定位查询动作,再由 name
区分查询技能
天猫精灵的设置属性指令:
也由namespace
定位控制动作,再由 name
区分控制动作;
并且由上图可知,控制指令的参数会有三方平台定义,因此本流程的重点由两个:
- 定位操作动作,操作属性
- 拆箱包装属性,转化为厂商云结构属性
主动上报
各平台的主动上报机制大体上是暴露一个HTTP接口,由厂商云自行调用,其中上报内容分为三类:
- 属性上报
- 设备上下先上报
- 事件上报
因为是由产商方主动发起的动作,流程细节将由各方的业务细节决定,此篇略过
总结
流程进行图大致为:
抽象架构
云云接入方的产商平台,与数据中台的设计有点相似:同样是针对向内向外的数据进行统一管理和利用。
不过由于两方的云服务差异可能甚大,因此云云接入平台除了对数据进行存储、转手外,还需要做最重要的一步: 映射转化
云云接入的核心也将围绕着将对方模型转换为我方模型这一理念进行设计,因此大致架构图将为:
一个应用在尽量不修改代码的前提下,与多家不同规则的云产商平台进行接入。一定需要使用大量的策略+工厂
模式,将相同的逻辑块抽取,业务代码细致到各个协议的模型处理;
于是有了中心执行器,因为不管是哪个平台的协议接入,无疑是三步:
- 构建产商云内部设备协议的参数
- 发起设备控制\产商云应用的接口
- 封装设备响应或接口返回的结果集,对标对接协议
其中构建与封装则围绕着云云接入的核心:模型转化器 处理:
模型转化器
打个比方:
小度,打开设备
{
"header": {
"namespace": "DuerOS.ConnectedHome.Control",
"name": "TurnOnRequest",
"messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
"payloadVersion": "1"
},
"payload": {
"accessToken": "[OAuth token here]",
"appliance": {
"additionalApplianceDetails": {},
"applianceId": "[Device ID for Light]"
}
}
}
该协议值中,我们只能通过 header:name=TurnOnRequest
判断出这个协议是打开控制
将 TurnOnRequest 看作小度云中的技能控制码,根据协议也可知,发现设备的技能表中:
打开的请求就是 TurnOn (code码) + Request 的组合方式。
假设我方云中,对于设备的打开操作并非该码,或者并非一个code码所能决定的,我方对于设备的控制属性为 status = 0
时为打开 status = 1
为关闭;
所以此模型映射的关系将会如此:
turnOn = status,0
turnOff = status,1
除了属性值的映射关系外,还有设备类型;
因为三方云各自支持的设备类型列表的迥异,以及我方云设备不在支持类型设备列表中,所以需要做设备类型的映射关系;
并且在某些云产商的要求中,对于设备名、类型名、英文名、品牌、组名....都推荐使用官方列出的字典值
还有,对于设备所拥有的技能,也有两种创建方式:
- 我方云进行配置
- 云产商处创建产品时进行配置
后者只需在运营商平台配置好设备类型下的技能即可,而前者则需要如上同一样,创建一张技能表;
并且多余带着属性的技能,比方说,小度:设置风速技能
{
"header": {
"namespace": "DuerOS.ConnectedHome.Control",
"name": "SetFanSpeedRequest",
"messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
"payloadVersion": "1"
},
"payload": {
"fanSpeed": {
"level": "high"
},
"accessToken": "[OAuth token here]",
"appliance": {
"additionalApplianceDetails": {},
"applianceId": "[Device ID]"
}
}
}
fanSpeed:level:high
需要做与我方云风速值的映射关系;
最终模型转换器与组装器将有如下的功能:
- 属性映射
- 设备映射
- 技能表映射
变更消息
上报动作简单的分为:设备-用户-平台:用户可属于多个平台,并且在各个平台上注册同一台设备;
那么可以预见,一个设备发生变更后,一定需要进行如下步骤:
- 找到拥有这台设备的用户
- 找到这个用户注册的三方云平台
- 发起http请求,主动上报
这时则设计两个抽象模块:
- 进行上报的整体动作整理
- 进行上报触发前的鉴权/冷却处理
整体动作分为:包装变更属性 、定位上报配置 、上报
前置,可用与同一冷却判断,阻止旧消息覆盖,鉴权等增强方法上;
有趣的细节
在以上点到为止的描述中,每个步骤都可以设计处好用的策略;
模型转化器
对于普通的code = code , code = code.value 的映射关系上,我们已无需优化,但是在某些平台中协议的入参、响应结果集的封装,都会出现与标准模块格格不入的结构;
比方说百度的标准属性的入参与出参:
"attributes": [
{
"name": "volume",
"value": 50,
"scale": "",
"timestampOfSample": 1496741861,
"uncertaintyInMilliseconds": 10,
"legalValue": "[0, 100]"
}
]
特殊属性的入参与出参:
"deltaValue": {
"value": 3
}
需要一种自定义的模板配置在不影响标准结构解析的前提下,进行特殊化的解析与封装
因此这里的奇妙方案就是 :运行时,编译字符串中的代码
简单言之,就是通过在数据库的映射表中配置伪代码的方式,在程序运行时动态赋值与执行,达到定制化的解析入参与封装结果集;
函数
所有人对于一个事物都有不同看法,各方云亦如此;
比如对于RGB灯的控制,我说将灯设置为红色:
- 小度给我:"hue": 350.5,"saturation": 0.7138,"brightness": 0.6524`
- 小爱给我:
1110
- 天猫给我:
(255,255,255)
- ....
各个产商给我的东西都不一样,假设我方云使用的是(255,255,255),需要将小度的HSB,小爱的十进制数转换为rgb的单位;
除了颜色,像色温、CO、温度...等等,会涉及到单位转换,值变更等等逻辑的属性,都会出现以上操作。
那么在映射表中,除了做code=code值的匹对外,还需要一种自定义公式,支持值一致转换;
因此就可以使用到 rgbConvert=com.xx.xx.xx
方法名 = 类名的方式配置在映射关系中,使用JDK反射类方法执行配置完全的函数。
并发
因为多平台对接的原因,在设备变更频繁的场景下,会出现热点设备频繁的与用户客户端进行HTTP交互的损坏;
一是频繁的http会很容易发生第一条请求因为网络问题阻塞,第二条被消费时,出现三方云侧的旧请求覆盖情况;
虽然大部分平台在上报时,会通过上报的时间戳参数自行解决旧消息覆盖问题。
但是除了新旧消息覆盖问题,还有二:占cpu内存
三:设备与云属性不一致
四:....
等等等等5,6,7,8只要能想到,频繁进行http,超时,冗余...等等问题就可能一股脑过来;
所以对设备上报这一动作,一定需要进行并发限流控制;
因此处于消息接收器 - 分流器 这两个阶段中,抛开一般应用中对于接口、请求的限流设计。
我推荐使用Lua
脚本:在对设备id这一key值进行缓存的同时,还可以自定义设置平台阈值,请求总数阈值...
比如以下,在seconds秒内,执行limit次
local key = KEYS[1];
local value = ARGV[1];
local delValue = ARGV[2];
local limit = ARGV[3];
local seconds = ARGV[4];
redis.call('ZREMRANGEBYSCORE', key, '-inf', delValue)
local count = redis.call('ZCARD', key)
if tonumber(limit) and count >= tonumber(limit) then
return 0
else
redis.call('ZADD', key, value, value)
redis.call('EXPIRE', key, seconds)
return 1
end
总结
云云接入的业务,转义个词:你的应用重构为我的应用;
由两个应用的碰撞,除了需使用大量 策略 + 工厂 进行冗余代码抽象的同时,还需要考虑到对方应用后续升级、变化的可能;
所以云云接入平台的设计我认为重点是前期对后期的预演以及架构全能的程度;