生活随笔
收集整理的這篇文章主要介紹了
ThingsBoard接入阿里ALink协议的网关设备探讨
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
目錄
前言
市面多數設備支持阿里云物聯網平臺的ALink協議,假設這類網關無法更改上下行協議,如何接入tb平臺?
文章是個人調研過程中的理解和分享,順便通過偏實戰的方式,更全面的實踐tb網關的mqtt連接器使用。看官網介紹的時候覺得很簡單,真正用起來,細節和文檔缺失的介紹還是很多的(比如官網只介紹了使用tb網關時,設備如何訂閱共享屬性更新,卻沒有介紹設備還可以主動請求共享屬性)。
由于thingsboard平臺和阿里物聯網平臺對物聯網中的相關定義及功能設計不一致。以個人淺顯理解,使用下面的對應關系做接入。
ALink協議文檔
tb-gateway mqtt連接器文檔
ALink協議ThingsBoard協議 [tb-gateway]
| 設備身份注冊 | 自動注冊 |
| 管理拓撲關系 | 不需要 |
| 子設備上下線 | 連接/斷開連接請求 |
| 設備只讀屬性 | 客戶端屬性/遙測 |
| 設備讀寫屬性 | 上行客戶端屬性 下行RPC |
| 設備上報事件 | 遙測+規則鏈/客戶端屬性 ? |
| 設備服務(異步同步) | 服務端RPC(oneway/twoway) |
| 設備期望屬性值 | 待完善 |
| 設備禁用/啟用/刪除 | 上行客戶端屬性(屬性名method)下行RPC |
| 設備標簽 | 上行客戶端屬性 下行RPC |
| ack | 遙測+規則鏈 |
| TSL模板 | 共享屬性 待完善 |
| OTA | 待完善 |
| 遠程配置 | 共享屬性 待完善 |
| 設備日志上報 | 待完善 |
| 設備網絡狀態 | 待完善 |
| 設備分發 | 待完善 |
| 設備任務 | 待完善 |
整體規劃
ALink網關及網關子設備 用js模擬(硬件沒到,手動調研吧)
需要依賴 npm install mqtt
使用tb-gateway接入ALink網關tb web頁面添加ALink網關
調用流程
數據上傳:(上行)
ALink網關js 發送mqtt到EMQX,tb-gateway訂閱EMQX相關主題,并將數據格式及上下行協議轉為tb格式 發送到tb
數據下發:(下行)
tb服務端下發指定 到 tb-gateway,tb-gateway轉換主題和數據格式 發送到 EMQX,js訂閱EMQX收到消息
下行如果需要回復:js 收到消息后發送數據到EMQX,tb-gateway訂閱回復主題 并轉換消息及上行協議格式 發送數據到tb
開始
tb web添加設備 阿里云ALink協議網關 并勾選 是網關
點擊 復制訪問令牌將令牌粘貼到 tb-gateway的tb_gateway.yaml中,并設置tb的地址和端口以及打開mqtt連接器
大體看下js中規劃的功能,接下來根據各個功能配置mqtt.json文件
var power
= 'on';
var publish_telemetry_interval
= 10;
var publish_label_interval
= 30;
var temperature
;
var model
= 'AHT15'
var vendor
= 'jin_shi_da';
var area
= 'beijing';
var status
= 'enable';
var set_attributes_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/property/set';
var set_attributes_topic_reply
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/property/set_reply';
var property_post_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/event/property/post';
var device_login_topic
= '/ext/session/' + productKey
+ '/' + deviceName
+ '/combine/login';
var device_logout_topic
= '/ext/session/' + productKey
+ '/' + deviceName
+ '/combine/logout';
var device_disable_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/disable';
var device_enable_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/enable';
var device_delete_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/delete';
var label_upload_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/update';
var label_upload_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/update_reply';
var label_delete_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/delete';
var label_delete_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/delete_reply';
var service_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/+';
var service_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/+' + '_reply';
配置mqtt.json [json文件不能注釋,下面這個文件加了注釋,文章最后貼出無注釋的mqtt.json文件]
模擬演示一個溫度傳感器經過ALink網關上傳數據:
有temperature屬性作為遙測,通過ALink上行屬性topic上傳。
power屬性作為客戶端屬性,通過ALink上行屬性topic上傳。tb通過服務改變power的值,控制溫度傳感器的工作與否。
publish_telemetry_interval屬性作為客戶端屬性,通過ALink上行屬性topic上傳。下行屬性控制ALink上行屬性topic上傳間隔。
model型號vendor廠商area地址,作為客戶端屬性,通過ALink上行標簽topic上傳。
status設備狀態,作為客戶端屬性,通過ALink下行 子設備禁用/啟用/刪除topic控制。
子設備上線、子設備下線
設置屬性
調用服務
下行響應
{"broker": {"name": "Default Local Broker", "host": "192.168.7.190", "port": 1883, "clientId": "ThingsBoard_gateway", "security": {"type": "basic","username": "admin", "password": "admin" }},"mapping": [{ "topicFilter": "/sys/+/+/thing/event/property/post","subscriptionQos": 0, "converter": {"type": "json","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}","timeout": 60000,"attributes": [{"type": "string","key": "power","value": "${params.power.value}"},{"type": "int","key": "publish_telemetry_interval","value": "${params.publish_telemetry_interval.value}"},{"type": "int","key": "publish_label_interval","value": "${params.publish_telemetry_interval.value}"},{"type": "string","key": "status","value": "${params.status.value}"}],"timeseries": [{"type": "float","key": "temperature","value": "${params.temperature.value}"}]}},{"topicFilter": "/sys/+/+/thing/deviceinfo/update","subscriptionQos": 0,"converter": {"type": "json","deviceNameJsonExpression": "${sys.deviceName}","deviceTypeJsonExpression": "${sys.productKey}","timeout": 60000,"attributes": [{"type": "string","key": "model","value": "${params[0].attrValue}"},{"type": "string","key": "vendor","value": "${params[1].attrValue}"},{"type": "string","key": "area","value": "${params[2].attrValue}"}]}}],"connectRequests": [{"topicFilter": "/ext/session/+/+/combine/login","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}"}],"disconnectRequests": [{"topicFilter": "/ext/session/+/+/combine/logout","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}"}],"attributeUpdates": [],"attributeRequests": [],"serverSideRpc": [{"deviceNameFilter": ".*","methodFilter": "setStatus","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/${params}","responseTopicExpression": "/sys/${deviceType}/${deviceName}/thing/${params}_reply","responseTimeout": 10000,"valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{},\"method\":\"thing.${params}\"}"},{"deviceNameFilter": ".*","methodFilter": "setProperty","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/property/set","responseTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/property/set_reply","responseTimeout": 10000,"valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":${params},\"method\":\"thing.service.property.set\"}"},{"deviceNameFilter": ".*","methodFilter": "replyLabelUpdate","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/deviceinfo/update_reply","valueExpression": "{\"id\":\"123\",\"code\":200,\"data\":{}}"},{"deviceNameFilter": ".*","methodFilter": "service.*","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/${methodName}","valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":${params},\"method\":\"thing.service.${methodName}\"}"}]
}
完整js
var mqtt
= require('mqtt');
var client
= mqtt
.connect('mqtt://localhost:1884', {username
: 'admin',password
: 'admin'
});
var productKey
= "a1CpLSEvPUk";
var deviceName
= "RoomTempSensor";
var version
= "1.0";
var clientId
= 'asdasdasdaedqwe123123123123';
var power
= 'on';
var publish_telemetry_interval
= 10;
var publish_label_interval
= 30;
var temperature
;
var model
= 'AHT15'
var vendor
= 'jin_shi_da';
var area
= 'beijing';
var status
= 'enable';
var set_attributes_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/property/set';
var set_attributes_topic_reply
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/property/set_reply';
var property_post_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/event/property/post';
var device_login_topic
= '/ext/session/' + productKey
+ '/' + deviceName
+ '/combine/login';
var device_logout_topic
= '/ext/session/' + productKey
+ '/' + deviceName
+ '/combine/logout';
var device_disable_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/disable';
var device_enable_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/enable';
var device_delete_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/delete';
var label_upload_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/update';
var label_upload_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/update_reply';
var label_delete_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/delete';
var label_delete_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/deviceinfo/delete_reply';
var service_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/+';
var service_reply_topic
= '/sys/' + productKey
+ '/' + deviceName
+ '/thing/service/+' + '_reply';
var id
= 0;
var pre_interval_property
;
var pre_interval_label
;
var success
= JSON.stringify({"id": "123", "code": 200, "data": {}})
client
.on('connect', function () {console
.log('connected');connect();client
.subscribe(set_attributes_topic
)client
.subscribe(device_disable_topic
)client
.subscribe(device_enable_topic
)client
.subscribe(device_delete_topic
)client
.subscribe(service_topic
)pre_interval_property
= setPublishPropertyInterval(null, publish_telemetry_interval
* 1000);pre_interval_label
= setPublishLabelInterval(null, publish_label_interval
* 1000);
});
client
.on('message', function (topic
, message
) {console
.log('on message:')console
.log(topic
);console
.log(message
.toString());console
.log('--------------------------------------')let data
= JSON.parse(message
);var service_topic_regex
= new RegExp(service_topic
.replace("+", "[^/]+"))switch (topic
) {case set_attributes_topic
:if (data
.params
.hasOwnProperty('publish_telemetry_interval')) {publish_telemetry_interval
= data
.params
.publish_telemetry_interval
;pre_interval_property
= setPublishPropertyInterval(pre_interval_property
, data
.params
.publish_telemetry_interval
* 1000);}if (data
.params
.hasOwnProperty('publish_label_interval')) {publish_label_interval
= data
.params
.publish_label_interval
;pre_interval_label
= setPublishPropertyInterval(pre_interval_label
, data
.params
.publish_label_interval
* 1000);}if (data
.params
.hasOwnProperty('power')) {power
= data
.params
.power
;}publish(set_attributes_topic_reply
, success
)break;case device_disable_topic
:case device_enable_topic
:case device_delete_topic
:if (data
.hasOwnProperty('method')) {status
= data
.method
.split('.')[1];publish('/sys/' + productKey
+ '/' + deviceName
+ '/thing/' + status
+ '_reply', success
)}break;case label_delete_reply_topic
:break;case label_upload_reply_topic
:break;}if (service_topic_regex
.test(topic
)) {if (data
.hasOwnProperty('method')) {var methodName
= data
.method
.split('.')[2]if (methodName
=== "power") {power
= data
.params
.power
;}publish(service_reply_topic
.replace('+', data
.method
), success
);}}
});
client
.on('SIGINT', function () {console
.log('disconnected');disconnect();client
.end();console
.log('Exited!');process
.exit(2);
});
process
.on('uncaughtException', function (e
) {console
.log('Uncaught Exception...');console
.log(e
.stack
);process
.exit(99);
});
function publishTelemetry() {var message
= {"id": ++id
,"version": version
,"sys": {"ack": 0},"params": {"productKey": productKey
,"deviceName": deviceName
,"power": { "value": power
,"time": new Date().getTime()},"publish_telemetry_interval": { "value": publish_telemetry_interval
,"time": new Date().getTime()},"publish_label_interval": { "value": publish_label_interval
,"time": new Date().getTime()},"temperature": { "value": genNextValue(23.6, 0, 45),"time": new Date().getTime()},'status': { 'value': status
,'time': new Date().getTime()},"model": {"value": model
,"time": new Date().getTime()},"vendor": {"value": vendor
,"time": new Date().getTime()},"area": {"value": area
,"time": new Date().getTime()}},"method": "thing.event.property.post"}publish(property_post_topic
, message
);
}
function publishLabel() {var message
= {"id": ++id
,"version": version
,"sys": {"ack": 0,"productKey": productKey
,"deviceName": deviceName
},"params": [{"attrKey": "model","attrValue": model
},{"attrKey": "vendor","attrValue": vendor
},{"attrKey": "area","attrValue": area
}],"method": "thing.deviceinfo.update"}publish(label_upload_topic
, message
);
}
function connect() {var message
= {"id": ++id
,"params": {"productKey": productKey
,"deviceName": deviceName
,"clientId": clientId
,"timestamp": new Date().getTime(),"signMethod": "hmacmd5","sign": "9B9C732412A4F84B981E1AB97CAB****","cleanSession": "true"}}publish(device_login_topic
, message
);
}
function disconnect() {var message
= {"id": ++id
,"params": {"productKey": productKey
,"deviceName": deviceName
}}publish(device_logout_topic
, JSON.stringify(message
));
}
function publish(topic
, message
) {client
.publish(topic
, JSON.stringify(message
));log(topic
, message
);
}
function setPublishPropertyInterval(preInterval
, interval
) {if (status
=== 'enable') {if (preInterval
!= null) {clearInterval(preInterval
);}return setInterval(publishTelemetry
, interval
);}return null;
}
function setPublishLabelInterval(preInterval
, interval
) {if (status
=== 'enable') {if (preInterval
!= null) {clearInterval(preInterval
);}return setInterval(publishLabel
, interval
);}return null;
}
function genNextValue(prevValue
, min
, max
) {var value
= prevValue
+ ((max
- min
) * (Math
.random() - 0.5)) * 0.03;value
= Math
.max(min
, Math
.min(max
, value
));return Math
.round(value
* 10) / 10;
}
function log(topic
, message
) {console
.log('send message:')console
.log(topic
)console
.log(JSON.stringify(message
))console
.log('===================================')
}
完整mqtt.json
{"broker": {"name": "emqx","host": "localhost","port": 1884,"clientId": "ThingsBoard_gateway","security": {"type": "basic","username": "admin","password": "admin"}},"mapping": [{"topicFilter": "/sys/+/+/thing/event/property/post","subscriptionQos": 0,"converter": {"type": "json","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}","timeout": 60000,"attributes": [{"type": "string","key": "power","value": "${params.power.value}"},{"type": "int","key": "publish_telemetry_interval","value": "${params.publish_telemetry_interval.value}"},{"type": "int","key": "publish_label_interval","value": "${params.publish_telemetry_interval.value}"},{"type": "string","key": "status","value": "${params.status.value}"}],"timeseries": [{"type": "float","key": "temperature","value": "${params.temperature.value}"}]}},{"topicFilter": "/sys/+/+/thing/deviceinfo/update","subscriptionQos": 0,"converter": {"type": "json","deviceNameJsonExpression": "${sys.deviceName}","deviceTypeJsonExpression": "${sys.productKey}","timeout": 60000,"attributes": [{"type": "string","key": "model","value": "${params[0].attrValue}"},{"type": "string","key": "vendor","value": "${params[1].attrValue}"},{"type": "string","key": "area","value": "${params[2].attrValue}"}]}}],"connectRequests": [{"topicFilter": "/ext/session/+/+/combine/login","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}"}],"disconnectRequests": [{"topicFilter": "/ext/session/+/+/combine/logout","deviceNameJsonExpression": "${params.deviceName}","deviceTypeJsonExpression": "${params.productKey}"}],"attributeUpdates": [],"attributeRequests": [],"serverSideRpc": [{"deviceNameFilter": ".*","methodFilter": "setStatus","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/${params}","responseTopicExpression": "/sys/${deviceType}/${deviceName}/thing/${params}_reply","responseTimeout": 10000,"valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{},\"method\":\"thing.${params}\"}"},{"deviceNameFilter": ".*","methodFilter": "setProperty","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/property/set","responseTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/property/set_reply","responseTimeout": 10000,"valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":${params},\"method\":\"thing.service.property.set\"}"},{"deviceNameFilter": ".*","methodFilter": "replyLabelUpdate","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/deviceinfo/update_reply","valueExpression": "{\"id\":\"123\",\"code\":200,\"data\":{}}"},{"deviceNameFilter": ".*","methodFilter": "service.*","requestTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/${methodName}","valueExpression": "{\"id\":\"123\",\"version\":\"1.0\",\"params\":${params},\"method\":\"thing.${methodName}\"}","responseTopicExpression": "/sys/${deviceType}/${deviceName}/thing/service/thing.${methodName}_reply","responseTimeout": 10000}]
}
tb-gateway代碼修改。有幾處不能滿足要求(ALink協議必須要productKey),添加一行,有些bug修復。這里暫時就不一一指出了。
post模擬服務端RPC
全部使用twoway有響應的RPC調用
獲取token
設備啟用禁用刪除
tb頁面 客戶端屬性上報也會變
設置屬性
服務調用
總結
要使用mqtt連接器,要熟悉源碼,方便調試甚至更改源碼BUG(bug不多)。在調試過程中可能有一些隱蔽的自身程序的問題,但看起來像tb網關出問題了,這類問題先找自身程序原因,不要有問題就說網關有bug,除非能從源碼斷定。
要熟練掌握mqtt.json的配置方式,還有源碼,因為有些值不提供但你一定要用,就需要自己做些修改,比如alink協議topic上的productKey。以及掌握交互流程,這個比較容易暈頭,因為子設備到tb,有倆個網關,倆個mqtt服務器,中間還要做格式轉換。
了解倆種平臺的上下行協議及功能對應。
后續還有些功能沒有來得及實現,找機會補上,或者不補了吧。
沒有什么不可能。
總結
以上是生活随笔為你收集整理的ThingsBoard接入阿里ALink协议的网关设备探讨的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。