生活随笔
收集整理的這篇文章主要介紹了
ThingsBoard接入阿里ALink协议的网关设备探讨
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
目錄
- 前言
- 整體規(guī)劃
- 調(diào)用流程
- 開始
- 總結(jié)
前言
市面多數(shù)設(shè)備支持阿里云物聯(lián)網(wǎng)平臺的ALink協(xié)議,假設(shè)這類網(wǎng)關(guān)無法更改上下行協(xié)議,如何接入tb平臺?
文章是個人調(diào)研過程中的理解和分享,順便通過偏實戰(zhàn)的方式,更全面的實踐tb網(wǎng)關(guān)的mqtt連接器使用。看官網(wǎng)介紹的時候覺得很簡單,真正用起來,細節(jié)和文檔缺失的介紹還是很多的(比如官網(wǎng)只介紹了使用tb網(wǎng)關(guān)時,設(shè)備如何訂閱共享屬性更新,卻沒有介紹設(shè)備還可以主動請求共享屬性)。
由于thingsboard平臺和阿里物聯(lián)網(wǎng)平臺對物聯(lián)網(wǎng)中的相關(guān)定義及功能設(shè)計不一致。以個人淺顯理解,使用下面的對應(yīng)關(guān)系做接入。
ALink協(xié)議文檔
tb-gateway mqtt連接器文檔
ALink協(xié)議ThingsBoard協(xié)議 [tb-gateway]
| 設(shè)備身份注冊 | 自動注冊 |
| 管理拓撲關(guān)系 | 不需要 |
| 子設(shè)備上下線 | 連接/斷開連接請求 |
| 設(shè)備只讀屬性 | 客戶端屬性/遙測 |
| 設(shè)備讀寫屬性 | 上行客戶端屬性 下行RPC |
| 設(shè)備上報事件 | 遙測+規(guī)則鏈/客戶端屬性 ? |
| 設(shè)備服務(wù)(異步同步) | 服務(wù)端RPC(oneway/twoway) |
| 設(shè)備期望屬性值 | 待完善 |
| 設(shè)備禁用/啟用/刪除 | 上行客戶端屬性(屬性名method)下行RPC |
| 設(shè)備標簽 | 上行客戶端屬性 下行RPC |
| ack | 遙測+規(guī)則鏈 |
| TSL模板 | 共享屬性 待完善 |
| OTA | 待完善 |
| 遠程配置 | 共享屬性 待完善 |
| 設(shè)備日志上報 | 待完善 |
| 設(shè)備網(wǎng)絡(luò)狀態(tài) | 待完善 |
| 設(shè)備分發(fā) | 待完善 |
| 設(shè)備任務(wù) | 待完善 |
整體規(guī)劃
ALink網(wǎng)關(guān)及網(wǎng)關(guān)子設(shè)備 用js模擬(硬件沒到,手動調(diào)研吧)
需要依賴 npm install mqtt
使用tb-gateway接入ALink網(wǎng)關(guān)tb web頁面添加ALink網(wǎng)關(guān)
調(diào)用流程
數(shù)據(jù)上傳:(上行)
ALink網(wǎng)關(guān)js 發(fā)送mqtt到EMQX,tb-gateway訂閱EMQX相關(guān)主題,并將數(shù)據(jù)格式及上下行協(xié)議轉(zhuǎn)為tb格式 發(fā)送到tb
數(shù)據(jù)下發(fā):(下行)
tb服務(wù)端下發(fā)指定 到 tb-gateway,tb-gateway轉(zhuǎn)換主題和數(shù)據(jù)格式 發(fā)送到 EMQX,js訂閱EMQX收到消息
下行如果需要回復:js 收到消息后發(fā)送數(shù)據(jù)到EMQX,tb-gateway訂閱回復主題 并轉(zhuǎn)換消息及上行協(xié)議格式 發(fā)送數(shù)據(jù)到tb
開始
tb web添加設(shè)備 阿里云ALink協(xié)議網(wǎng)關(guān) 并勾選 是網(wǎng)關(guān)
點擊 復制訪問令牌將令牌粘貼到 tb-gateway的tb_gateway.yaml中,并設(shè)置tb的地址和端口以及打開mqtt連接器
大體看下js中規(guī)劃的功能,接下來根據(jù)各個功能配置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文件]
模擬演示一個溫度傳感器經(jīng)過ALink網(wǎng)關(guān)上傳數(shù)據(jù):
有temperature屬性作為遙測,通過ALink上行屬性topic上傳。
power屬性作為客戶端屬性,通過ALink上行屬性topic上傳。tb通過服務(wù)改變power的值,控制溫度傳感器的工作與否。
publish_telemetry_interval屬性作為客戶端屬性,通過ALink上行屬性topic上傳。下行屬性控制ALink上行屬性topic上傳間隔。
model型號vendor廠商area地址,作為客戶端屬性,通過ALink上行標簽topic上傳。
status設(shè)備狀態(tài),作為客戶端屬性,通過ALink下行 子設(shè)備禁用/啟用/刪除topic控制。
子設(shè)備上線、子設(shè)備下線
設(shè)置屬性
調(diào)用服務(wù)
下行響應(yīng)
{"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協(xié)議必須要productKey),添加一行,有些bug修復。這里暫時就不一一指出了。
post模擬服務(wù)端RPC
全部使用twoway有響應(yīng)的RPC調(diào)用
獲取token
設(shè)備啟用禁用刪除
tb頁面 客戶端屬性上報也會變
設(shè)置屬性
服務(wù)調(diào)用
總結(jié)
要使用mqtt連接器,要熟悉源碼,方便調(diào)試甚至更改源碼BUG(bug不多)。在調(diào)試過程中可能有一些隱蔽的自身程序的問題,但看起來像tb網(wǎng)關(guān)出問題了,這類問題先找自身程序原因,不要有問題就說網(wǎng)關(guān)有bug,除非能從源碼斷定。
要熟練掌握mqtt.json的配置方式,還有源碼,因為有些值不提供但你一定要用,就需要自己做些修改,比如alink協(xié)議topic上的productKey。以及掌握交互流程,這個比較容易暈頭,因為子設(shè)備到tb,有倆個網(wǎng)關(guān),倆個mqtt服務(wù)器,中間還要做格式轉(zhuǎn)換。
了解倆種平臺的上下行協(xié)議及功能對應(yīng)。
后續(xù)還有些功能沒有來得及實現(xiàn),找機會補上,或者不補了吧。
沒有什么不可能。
總結(jié)
以上是生活随笔為你收集整理的ThingsBoard接入阿里ALink协议的网关设备探讨的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。