├── .npmignore ├── examples ├── quickstart │ ├── README.md │ ├── start.sh │ ├── device_id_password.json │ ├── start.bat │ ├── package.json │ └── index.js ├── event.js ├── one_model_one_secret.js ├── remote_confit_get.js ├── remote_confit_sub.js ├── shadow.js ├── props.js ├── origin.js ├── ipv6.js ├── tag.js ├── service_async.js ├── service_sync.js ├── forward.js ├── gateway.js └── broswer │ └── index.html ├── test ├── README.md ├── manual │ ├── originGateway.js │ ├── unsubscribe.js │ ├── dev.js │ ├── devGateway.js │ ├── devDevices.js │ └── subdevice.js ├── auto │ ├── utils.test.js │ ├── gateway.test.js │ ├── base.test.js │ ├── register.test.js │ ├── subdevice.test.js │ └── senior.test.js └── fixtures.js ├── .babelrc ├── .eslintrc ├── .editorconfig ├── src ├── subdevice.js ├── device.js ├── index.js ├── const.js ├── utils.js ├── gateway.js ├── model.js └── thing.js ├── .vscode └── launch.json ├── LICENSE ├── .gitignore ├── package.unstabitily.json ├── docs ├── weixin-min-program-usage..md └── alipay-min-program-usage.md ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | src/ 3 | rap/ 4 | examples/Ruff 5 | *.log 6 | *.out 7 | -------------------------------------------------------------------------------- /examples/quickstart/README.md: -------------------------------------------------------------------------------- 1 | ## 如何启动 2 | 3 | 1:修改device_id_password.json,把注册的设备三元组信息填写进去 4 | 2:命令行执行 ` sh ./start.sh ` 5 | 6 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 目录说明 3 | 4 | - auto:自动测试用例 5 | - manual:手动发布分支 6 | - fixtrues:开发者测试设备三元组信息(声明:三元组信息仅限测试脚本使用,其他情况请使用自己注册的三元组) 7 | -------------------------------------------------------------------------------- /examples/quickstart/start.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | if [ ! -d ./node_modules ]; then 4 | printf "Installing alibabacloud Iot Device SDK...\n" 5 | npm install 6 | fi 7 | 8 | node index.js -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "0.10" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint-config-ali/react", 4 | "rules": { 5 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] 6 | } 7 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /examples/quickstart/device_id_password.json: -------------------------------------------------------------------------------- 1 | { 2 | "productKey": "a1FVHNuAmvq", 3 | "deviceName": "testdevice1", 4 | "deviceSecret": "QNNcinhgbY5VgpnQT0DtIUXAlV4jYthO", 5 | "region":"cn-shanghai" 6 | } -------------------------------------------------------------------------------- /examples/quickstart/start.bat: -------------------------------------------------------------------------------- 1 | 2 | if not exist node_modules ( 3 | rem Installing alibabacloud Iot Device SDK... 4 | npm install 5 | node index.js 6 | ) else ( 7 | node index.js 8 | ) 9 | -------------------------------------------------------------------------------- /src/subdevice.js: -------------------------------------------------------------------------------- 1 | const Thing = require('./thing'); 2 | 3 | class SubDevice extends Thing { 4 | constructor(config = {},gateway) { 5 | super(config); 6 | this.gateway = gateway; 7 | this._mqttClient = gateway._mqttClient; 8 | } 9 | } 10 | module.exports = SubDevice; -------------------------------------------------------------------------------- /examples/quickstart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "license": "ISC", 10 | "dependencies": { 11 | "alibabacloud-iot-device-sdk": "1.2.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/device.js: -------------------------------------------------------------------------------- 1 | const Thing = require('./thing'); 2 | 3 | class Device extends Thing { 4 | constructor(config = {}) { 5 | super(config); 6 | // create mqttclient 7 | this._createClient(this.model); 8 | // subcribe client event and preset topic 9 | this._subscribeClientEvent(); 10 | } 11 | } 12 | module.exports = Device; -------------------------------------------------------------------------------- /examples/event.js: -------------------------------------------------------------------------------- 1 | 2 | const iot = require('../'); 3 | 4 | const device = iot.device({ 5 | "ProductKey": "a1ouyopKiEU", 6 | "DeviceName": "device6", 7 | "DeviceSecret": "yoyXdmII3xcT9udR1DLQRzMGjkRRtkgc" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>connect'); 12 | device.postEvent("error", { 13 | power: 10, 14 | }, (res) => { 15 | console.log(`postEvent:`,res); 16 | }) 17 | }); 18 | -------------------------------------------------------------------------------- /examples/one_model_one_secret.js: -------------------------------------------------------------------------------- 1 | const iot = require('../lib'); 2 | 3 | const registerDeviceInfo = { 4 | productKey:"a15YDgQGhU0", 5 | productSecret:"AP4HnuqhNqqArIkH", 6 | deviceName:"device1" 7 | } 8 | 9 | iot.register(registerDeviceInfo,(res)=>{ 10 | if(res.code != 200){ 11 | console.log("register faild",res); 12 | return; 13 | } 14 | console.log("register succeed"); 15 | console.log("res is:",res); 16 | }) 17 | 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('mqtt'); 2 | const { register } = require('./utils'); 3 | import packageJson from '../package.json' 4 | 5 | const iot = { 6 | device: config => { 7 | const Device = require('./device');; 8 | return new Device(config); 9 | }, 10 | gateway: config => { 11 | const Gateway = require('./gateway'); 12 | return new Gateway(config); 13 | }, 14 | register, 15 | mqtt, 16 | sdkver:packageJson.version 17 | }; 18 | 19 | module.exports = iot; -------------------------------------------------------------------------------- /examples/remote_confit_get.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | // init device and connect linkplatform 4 | const device = iot.device({ 5 | "PRODUCTKEY": "a1ouyopKiEU", 6 | "DeviceName": "device1", 7 | "DeviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>device connect succeed'); 12 | // get remote Configuration 13 | device.getConfig((res) => { 14 | console.log(`remote Configuration is:`,res); 15 | }); 16 | }); -------------------------------------------------------------------------------- /examples/remote_confit_sub.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | 4 | // init device and connect linkplatform 5 | const device = iot.device({ 6 | "PRODUCTKEY": "a1ouyopKiEU", 7 | "DeviceName": "device1", 8 | "DeviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 9 | }); 10 | 11 | device.on('connect', () => { 12 | console.log('>>>>>device connect succeed'); 13 | // subscribe remote Configuration change 14 | device.onConfig((res) => { 15 | console.log("remote Configuration change:",res); 16 | }); 17 | }); -------------------------------------------------------------------------------- /examples/shadow.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | // init device and connect linkplatform 4 | const device = iot.device({ 5 | "productKey": "a1ouyopKiEU", 6 | "deviceName": "device1", 7 | "deviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>device connect succeed'); 12 | device.getShadow(); 13 | device.postShadow({ 14 | a: "avalue11" 15 | }); 16 | setTimeout(()=>{ 17 | device.deleteShadow("a"); 18 | },5000) 19 | }); 20 | //subscribe platform response 21 | device.onShadow((res) => { 22 | console.log('获取最新设备影子,%o', res); 23 | }) 24 | -------------------------------------------------------------------------------- /test/manual/originGateway.js: -------------------------------------------------------------------------------- 1 | // origin usage 2 | const iot = require('../../'); 3 | const fixtures = require('../fixtures'); 4 | 5 | const gateway = iot.gateway(fixtures.sdk_gateway2); 6 | 7 | gateway.subscribe('/a1BOOa9HG6Z/sdk_gateway2/user/get'); 8 | 9 | gateway.on('message', (topic, payload) => { 10 | console.log('topic:',topic); 11 | if(payload){ 12 | console.log('payload',payload.toString()); 13 | } 14 | }); 15 | 16 | gateway.on('connect', () => { 17 | console.log('>>>>>connect'); 18 | // setInterval(()=>{ 19 | // device.publish('/a1YPDpMvd5t/base_sdk_device2/update', "hello"); 20 | // },2000) 21 | }); 22 | -------------------------------------------------------------------------------- /test/manual/unsubscribe.js: -------------------------------------------------------------------------------- 1 | const iot = require('../../lib'); 2 | 3 | const device = iot.device({ 4 | "productKey": "a1M2kvfkVYF", 5 | "deviceName": "network1", 6 | "deviceSecret": "3gA9Lwkn7v8r8gxTd1uhHVb131J4LuIG", 7 | keepalive: 60, 8 | clean: false 9 | }); 10 | 11 | device.publish('/a1MquU83rKN/gateway/usr/update', 'Hello'); 12 | 13 | 14 | device.on('connect', () => { 15 | console.log('connected successfully!'); 16 | // device.subscribe('/a1M2kvfkVYF/network1/user/get'); 17 | device.unsubscribe('/a1M2kvfkVYF/network1/user/get'); 18 | }); 19 | 20 | device.on('message', function(topic, payload){ 21 | console.log(topic.toString(),payload.toString()); 22 | }); -------------------------------------------------------------------------------- /examples/props.js: -------------------------------------------------------------------------------- 1 | const iot = require('../lib'); 2 | 3 | let count = 0; 4 | const device = iot.device({ 5 | "ProductKey": "a1ouyopKiEU", 6 | "DeviceName": "device6", 7 | "DeviceSecret": "yoyXdmII3xcT9udR1DLQRzMGjkRRtkgc" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>connect'); 12 | // setInterval(() => { 13 | // device.postProps({ 14 | // state: count++%2 15 | // }, (res) => { 16 | // console.log(`postProps:`,res); 17 | // }); 18 | // }, 10000); 19 | device.postProps({ 20 | state: count++%2 21 | }, (res) => { 22 | console.log(`postProps:>>>>>>>>>>`,res); 23 | }); 24 | }); 25 | 26 | device.onProps((res)=>{ 27 | console.log('>>>onProps',res); 28 | }) -------------------------------------------------------------------------------- /examples/origin.js: -------------------------------------------------------------------------------- 1 | // origin usage 2 | const iot = require('../'); 3 | 4 | // init device and connect linkplatform 5 | const device = iot.device({ 6 | "ProductKey": "a1YPDpMvd5t", 7 | "DeviceName": "base_sdk_device4", 8 | "DeviceSecret": "2Df8xOQC1yfBeAZhRkYHJV4YuuXF4H08" 9 | }); 10 | 11 | 12 | 13 | 14 | device.on('message', (topic, payload) => { 15 | console.log('topic:',topic); 16 | if(payload){ 17 | console.log('payload',payload.toString()); 18 | } 19 | }); 20 | 21 | device.on('connect', () => { 22 | console.log('>>>>>connect'); 23 | device.subscribe('/a1YPDpMvd5t/base_sdk_device2/get'); 24 | setInterval(()=>{ 25 | device.publish('/a1YPDpMvd5t/base_sdk_device2/update', "hello"); 26 | },2000) 27 | }); 28 | -------------------------------------------------------------------------------- /examples/quickstart/index.js: -------------------------------------------------------------------------------- 1 | const iot = require('alibabacloud-iot-device-sdk'); 2 | const deviceConfig = require('./device_id_password.json'); 3 | 4 | const device = iot.device(deviceConfig); 5 | 6 | device.on('connect', () => { 7 | console.log('Connect successfully!'); 8 | console.log('Post properties every 5 seconds...'); 9 | setInterval(() => { 10 | const params = { 11 | Status: 1, 12 | Data: 'Hello, world!' 13 | }; 14 | console.log(`Post properties: ${JSON.stringify(params)}`); 15 | device.postProps(params); 16 | }, 5000); 17 | 18 | device.serve('property/set', (data) => { 19 | console.log('Received a message: ', JSON.stringify(data)); 20 | }); 21 | }); 22 | 23 | device.on('error', err => { 24 | console.error(err); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/ipv6.js: -------------------------------------------------------------------------------- 1 | // origin usage 2 | const iot = require('../lib'); 3 | 4 | // init device and connect linkplatform 5 | // ipv6链接 ipv6 server,需要链接的全链路都支持ipv6服务,才能成功连接 6 | // 测试链接成功环境:alibaba-inc-ipv6test 7 | const device = iot.device({ 8 | "ProductKey": "a1ouyopKiEU", 9 | "DeviceName": "device6", 10 | "DeviceSecret": "yoyXdmII3xcT9udR1DLQRzMGjkRRtkgc", 11 | "brokerUrl":"mqtt://ipv6.itls.cn-shanghai.aliyuncs.com:1883" 12 | }); 13 | 14 | device.on('message', (topic, payload) => { 15 | console.log('topic:',topic); 16 | if(payload){ 17 | console.log('payload',payload.toString()); 18 | } 19 | }); 20 | 21 | 22 | device.on('connect', () => { 23 | console.log('>>>>>connect'); 24 | }); 25 | 26 | // 当出现错误时回调 27 | device.on('error', (err) => { 28 | console.log('error:', err); 29 | }); -------------------------------------------------------------------------------- /examples/tag.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | // init device and connect linkplatform 4 | const device = iot.device({ 5 | "PRODUCTKEY": "a1ouyopKiEU", 6 | "DeviceName": "device1", 7 | "DeviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>device connect succeed'); 12 | // post device tag 13 | const tagsInfo = [ 14 | {"attrKey": "Temperature","attrValue": "36.8"}, 15 | {"attrKey": "Room","attrValue": "avalu301e"}]; 16 | device.postTags( 17 | tagsInfo, 18 | (res) => { 19 | console.log("post tags result:",res); 20 | }); 21 | // delete tag after ten seconds 22 | setTimeout(()=>{ 23 | device.deleteTags(['Temperature','Room'], (res) => { 24 | console.log(`tag delete succeed`); 25 | }); 26 | },10000) 27 | }); -------------------------------------------------------------------------------- /examples/service_async.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | // init device and connect linkplatform 4 | const device = iot.device({ 5 | "PRODUCTKEY": "a1ouyopKiEU", 6 | "DeviceName": "device1", 7 | "DeviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>device connect succeed'); 12 | }); 13 | 14 | // subscribe add_async service 15 | device.onService('add_async', function (res,reply) { 16 | console.log('add_async called,res:',res); 17 | const { params:{x,y}={}} = res; 18 | const result = addFunc(x,y); 19 | console.log('result',result); 20 | reply(result); 21 | }); 22 | 23 | function addFunc(x,y){ 24 | let err; 25 | if(x==undefined || y==undefined){ 26 | err = 'x or y invail value'; 27 | return {err,code:10001} 28 | } 29 | 30 | return { 31 | data:{ 32 | z:x+y 33 | }, 34 | code:200 35 | } 36 | } -------------------------------------------------------------------------------- /examples/service_sync.js: -------------------------------------------------------------------------------- 1 | const iot = require('../'); 2 | 3 | // init device and connect linkplatform 4 | const device = iot.device({ 5 | "PRODUCTKEY": "a1ouyopKiEU", 6 | "DeviceName": "device1", 7 | "DeviceSecret": "mi9FfuIN28blO1n4oSytBi2kvcWoJzTj" 8 | }); 9 | 10 | device.on('connect', () => { 11 | console.log('>>>>>device connect succeed'); 12 | }); 13 | // subscribe wakeup_async service 14 | device.onService('add_sync', function (res,reply) { 15 | console.log('add_sync called,res:',res); 16 | const { params:{x,y}={}} = res; 17 | const result = addFunc(x,y); 18 | console.log('result',result); 19 | reply(result,'sync'); 20 | }); 21 | 22 | function addFunc(x,y){ 23 | let err; 24 | if(x==undefined || y==undefined){ 25 | err = 'x or y invail value'; 26 | return {err,code:10001} 27 | } 28 | 29 | return { 30 | data:{ 31 | z:x+y 32 | }, 33 | code:200 34 | } 35 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | 9 | { 10 | "type": "node", 11 | "request": "launch", 12 | "name": "dev", 13 | "program": "${workspaceFolder}/test/manual/dev.js", 14 | "env":{ 15 | "DEBUG":"TRUE", 16 | } 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "devDevices", 22 | "program": "${workspaceFolder}/test/manual/devDevices.js", 23 | "env":{ 24 | "DEBUG":"TRUE", 25 | }, 26 | { 27 | "type": "node", 28 | "request": "launch", 29 | "name": "subdevice", 30 | "program": "${workspaceFolder}/test/manual/subdevice.js", 31 | "env":{ 32 | "DEBUG":"TRUE", 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alibaba Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/auto/utils.test.js: -------------------------------------------------------------------------------- 1 | 2 | const {isJsonString,mqttMatch} = require('../../lib/utils'); 3 | 4 | describe('util test', () => { 5 | test('test isJsonString funciton with string should be pass ', done => { 6 | console.log("isJsonString('aaa')",isJsonString("aaa")); 7 | expect(isJsonString("aaa")).toBeFalsy(); 8 | done(); 9 | }); 10 | test('test isJsonString funciton with jsonstring should be pass ', done => { 11 | const jsonObj = {'a':'aaa'}; 12 | const jsonStr = JSON.stringify(jsonObj); 13 | console.log("jsonStr:%s,result:",jsonStr,isJsonString(jsonStr)); 14 | expect(isJsonString(jsonStr)).toBeTruthy(); 15 | done() 16 | }); 17 | test('test mqttMatch funciton should be pass ', done => { 18 | const filter1 = '/sys/a1ouyopKiEU/device1/thing/service/#' 19 | const filter2 = '/sys/a1ouyopKiEU/device1/thing/service/+' 20 | const filter3 = '/sys/a1ouyopKiEU/device1/thing/#' 21 | const topic = '/sys/a1ouyopKiEU/device1/thing/service/add_async'; 22 | expect(mqttMatch(filter1,topic)).toBeTruthy(); 23 | expect(mqttMatch(filter2,topic)).toBeTruthy(); 24 | expect(mqttMatch(filter3,topic)).toBeTruthy(); 25 | done() 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | rap/ 15 | lib/ 16 | unstabitily/ 17 | coverage/ 18 | # dist/ 19 | # examples/ 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # next.js build output 65 | .next 66 | -------------------------------------------------------------------------------- /examples/forward.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 操作步骤: 4 | * 1:首先进入产品详情,在对应设备上创建2个自定义topic 5 | * 1.1: 一个是用户转发的topic, 如 /a1aq9sQk2JE/${deviceName}/user/forward 6 | * 1.2: 另一个用户接受数据上传,通过规则引擎扭转到forward的topic,示例:/a1aq9sQk2JE/sdk_device3/user/userpost 7 | * 2:进入设备详情的topic,查看设备topic,如: /a1aq9sQk2JE/sdk_device3/user/forward 8 | * 3:使用规则引擎,创建一个新的规则引擎,将用户topic转发到这个自定义topic中 9 | * 处理数据的sql:SELECT * FROM "/a1aq9sQk2JE/sdk_device3/user/userpost" 10 | * 转发数据-数据目的地:该方法将数据发到另一个Topic中:/a1aq9sQk2JE/sdk_device3/user/forward 11 | * 结果:执行下面代码段,通过订阅forward的topic,就可以接收到发送到userpost topic的消息 12 | */ 13 | 14 | 15 | // origin usage 16 | const iot = require('../'); 17 | 18 | // init device and connect linkplatform 19 | const device = iot.device({ 20 | productKey: 'a1aq9sQk2JE', 21 | deviceName: 'sdk_device3', 22 | deviceSecret: 'ZXdtUJ1nlGxy1v2ZHjq5cTTt3RTVicoX', 23 | }); 24 | 25 | 26 | device.on('message', (topic, payload) => { 27 | console.log('topic:',topic); 28 | if(payload){ 29 | console.log('payload',payload.toString()); 30 | } 31 | }); 32 | 33 | device.on('connect', () => { 34 | console.log('>>>>>connect'); 35 | device.subscribe('/a1aq9sQk2JE/sdk_device3/user/forward'); 36 | setInterval(()=>{ 37 | let jsonString = JSON.stringify({ "a":"hello"}); 38 | device.publish('/a1aq9sQk2JE/sdk_device3/user/userpost',jsonString ); 39 | },2000) 40 | }); 41 | -------------------------------------------------------------------------------- /package.unstabitily.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alife/alibabacloud-iot-device-sdk-unstabitily", 3 | "version": "0.9999.8", 4 | "publishConfig": { 5 | "registry": "https://registry.npm.alibaba-inc.com" 6 | }, 7 | "registry": "https://registry.npm.alibaba-inc.com", 8 | "description": "阿里云IoT测试sdk,会包含测试中的新功能。注意:此版本不稳定,稳定版本版本请使用 npm install alibabacloud-iot-device-sdk", 9 | "main": "./lib/index.js", 10 | "scripts": { 11 | "broker": "node broker.js", 12 | "lib": "babel src -d lib", 13 | "dev": "babel src -w -d lib", 14 | "test": "jest --coverage ./test/", 15 | "test:dev": "jest --coverage --watch ./test/", 16 | "rap": "npm run lib && rm -rf rap && mkdir rap && cp -R lib rap && cp package.json rap && sed 's/npm/rap/g' README.md > rap/README.md && cd rap && rap publish", 17 | "dist": "npm run lib && rm -rf dist/ && mkdir dist && browserify lib/index.js | uglifyjs -c > dist/alibabacloud-iot-device-sdk.min.js" 18 | }, 19 | "author": "玄彦", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "aedes": "^0.35.3", 23 | "babel-cli": "^6.26.0", 24 | "babel-preset-env": "^1.6.1", 25 | "browserify": "^16.2.2", 26 | "jest": "^22.0.4", 27 | "mosca": "^2.8.3", 28 | "uglify-js": "^3.4.5" 29 | }, 30 | "dependencies": { 31 | "axios": "^0.18.0", 32 | "mqtt": "3.0.0", 33 | "qs": "^6.6.0" 34 | }, 35 | "ruff": { 36 | "dependencies": { 37 | "mqtt": "^0.1.3" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/gateway.js: -------------------------------------------------------------------------------- 1 | const iot = require('../lib'); 2 | 3 | // init device and geteway for linkplatform 4 | const sub1Info = { 5 | "ProductKey": "a1ouyopKiEU", 6 | "DeviceName": "sub1", 7 | "DeviceSecret": "SpT5OQsX4LZi5CsQIb6OS8hVt8qFRw7o" 8 | } 9 | const sub2Info = { 10 | "ProductKey": "a1ouyopKiEU", 11 | "DeviceName": "sub2", 12 | "DeviceSecret": "9Nh3fE6xZiMrMKSaRiiIpYCf6uPpHkav" 13 | } 14 | 15 | const gateway = iot.gateway({ 16 | "ProductKey": "a1NuvAIZXv7", 17 | "DeviceName": "gateway1", 18 | "DeviceSecret": "S6X7MBsy5zjP07lwzoq1VNgAT81EYw2n" 19 | }); 20 | 21 | gateway.on('connect', () => { 22 | console.log('>>>>>gateway connect succeed'); 23 | 24 | //subdevice login 25 | const sub1 = gateway.login( 26 | sub1Info, 27 | (res) => { 28 | console.log('>>>>>sub1 login', res); 29 | } 30 | ); 31 | // subdevice on connected 32 | sub1.on('connect', () => { 33 | console.log(">>>>sub1 connected!"); 34 | sub1.postProps({ 35 | state: 1 36 | },(res)=>{ 37 | console.log('sub1 postProps:',res); 38 | }) 39 | }); 40 | 41 | // 云端设置属性时触发 42 | sub1.onProps((res)=>{ 43 | console.log(">>>>sub1 onProps:",res); 44 | }) 45 | 46 | //subdevice login 47 | // const sub2= gateway.login( 48 | // sub2Info, 49 | // (res) => { 50 | // console.log('>>>>>sub2 login', res); 51 | // } 52 | // ); 53 | // // subdevice on connected 54 | // sub2.on('connect', () => { 55 | // console.log(">>>>sub2 connected!"); 56 | // // set device props 57 | // sub2.postProps({ 58 | // state: 1 59 | // }) 60 | // }); 61 | }); -------------------------------------------------------------------------------- /test/auto/gateway.test.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const iot = require('../../lib'); 3 | const fixtures = require('../fixtures'); 4 | 5 | let gateway; 6 | const sub_device1 = fixtures.sub_device1; 7 | const sub_device2 = fixtures.sub_device2; 8 | 9 | beforeAll(()=> { 10 | return new Promise((resolve, reject)=>{ 11 | gateway = iot.gateway({...fixtures.sdk_gateway1}); 12 | gateway.on('connect', () => { 13 | resolve(); 14 | }); 15 | }) 16 | },3000) 17 | 18 | afterAll(() => { 19 | gateway.end(); 20 | }); 21 | 22 | describe('device test', () => { 23 | test('gateway connect linkPlatform should be ok', done => { 24 | if(gateway.connected){ 25 | done(); 26 | } 27 | }); 28 | 29 | test('get topo should not be ok', done => { 30 | try { 31 | //网关获子设备 ok, iot.gateway#getTopo() 32 | gateway.getTopo( 33 | (res)=>{ 34 | console.log('>>>>>getTopo',res) 35 | done(); 36 | } 37 | ); 38 | } catch (e) { console.error(e)} 39 | }); 40 | 41 | test('add topo should not be ok', done => { 42 | try { 43 | // 添加topo ok 44 | gateway.addTopo( 45 | [sub_device1,sub_device2], 46 | (res)=>{ 47 | console.log('>>>>>addTopo',res.message,res.data); 48 | done(); 49 | } 50 | ); 51 | } catch (e) { console.error(e)} 52 | }); 53 | 54 | test('delete topo should not be ok', done => { 55 | try { 56 | // 删除设备ok 57 | gateway.removeTopo( 58 | [sub_device2], 59 | (res)=>{ 60 | console.log('>>>>>removeTopo') 61 | console.log(res) 62 | done(); 63 | } 64 | ); 65 | } catch (e) { console.error(e)} 66 | }); 67 | 68 | 69 | 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /test/auto/base.test.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const iot = require('../../lib'); 3 | const fixtures = require('../fixtures'); 4 | 5 | let device; 6 | beforeAll(()=> { 7 | return new Promise((resolve, reject)=>{ 8 | device = iot.device({ 9 | ...fixtures.sdk_device1 10 | }); 11 | device.on('connect', () => { 12 | resolve(); 13 | }); 14 | }) 15 | },3000) 16 | 17 | afterAll(() => { 18 | device.end(); 19 | }); 20 | 21 | describe('device test', () => { 22 | test('device connect linkPlatform should be ok', done => { 23 | if(device.connected){ 24 | done(); 25 | } 26 | }); 27 | 28 | test('productKey should not be empty', done => { 29 | try { 30 | const device = iot.device(); 31 | } catch (e) { 32 | done(); 33 | } 34 | }); 35 | 36 | test('deviceName should not be empty', done => { 37 | try { 38 | const device = iot.device({ 39 | productKey: '1' 40 | }); 41 | } catch (e) { 42 | done(); 43 | } 44 | }); 45 | 46 | test('deviceSecret should not be empty', done => { 47 | try { 48 | const device = iot.device({ 49 | productKey: '1', 50 | deviceName: '2' 51 | }); 52 | } catch (e) { 53 | done(); 54 | } 55 | }); 56 | 57 | test('postProps should not be ok', done => { 58 | try { 59 | // 上报设备属性 60 | device.postProps({ 61 | LightSwitch: 0 62 | }, (res) => { 63 | done(); 64 | }); 65 | } catch (e) {} 66 | }); 67 | 68 | test('postEvent should not be ok', done => { 69 | try { 70 | // 上报设备事件ok 71 | device.postEvent("lowpower", { 72 | power: 20, 73 | }, (res) => { 74 | console.log(`postEvent:${res}`); 75 | done(); 76 | }) 77 | } catch (e) {} 78 | }); 79 | 80 | }); 81 | 82 | -------------------------------------------------------------------------------- /docs/weixin-min-program-usage..md: -------------------------------------------------------------------------------- 1 | ## 微信小程序中使用 alibabacloud-iot-device-sdk 2 | 3 | 注意: alibabacloud-iot-device-sdk 1.2.4版本以上才支持 4 | 5 | ## 小程序开发环境 6 | 7 | 详细参考:https://developers.weixin.qq.com/miniprogram/dev/framework/ 8 | 9 | ## 如何集成 10 | 11 | ##### lib下载 12 | 下载 alibabacloud-iot-device-sdk代码 [连接地址](https://github.com/aliyun/alibabacloud-iot-device-sdk) 13 | 在dist文件夹中有支持浏览器和微信或支付宝运行环境编译完成的js文件, 14 | - alibabacloud-iot-device-sdk.js 源码版 15 | - alibabacloud-iot-device-sdk.min.js 压缩版 16 | 17 | 18 | #### 项目中集成 19 | 1:将alibabacloud-iot-device-sdk.js或alibabacloud-iot-device-sdk.min.js导入支付宝小程序工程目录 例如 放在工程目录下的dist文件夹,那么js的文件地址为 /dist/alibabacloud-iot-device-sdk.js 20 | 21 | 2:在需要使用到sdk地方 22 | ````js 23 | // 引入包 24 | var iot = require('/dist/alibabacloud-iot-device-sdk.js'); 25 | // 定义云端创建的设备三元组信息,并使用协议声明,使用 "protocol": 'alis://' 26 | const sdk_device = { 27 | "productKey": "", 28 | "deviceName": "", 29 | "deviceSecret": "", 30 | "protocol": 'wxs://', 31 | } 32 | // 连接云平台 33 | let device = iot.device(sdk_device); 34 | // 当连接成功进入回调 35 | device.on('connect', () => { 36 | console.log('连接成功....'); 37 | }); 38 | // 当收到云端消息下发 39 | device.on('message', (topic, payload) => { 40 | console.log('topic:', topic); 41 | if (payload) { 42 | console.log('payload', payload); 43 | console.log('payload.toString()', payload.toString()); 44 | } 45 | }); 46 | // 当出现错误时回调 47 | device.on('error', (err) => { 48 | console.log('error:', err); 49 | }); 50 | 51 | // 其他更多功能参考api说明,地址:https://github.com/aliyun/alibabacloud-iot-device-sdk 52 | ```` 53 | 54 | 55 | ## 特别注意,必读 56 | 57 | 1:alibabacloud-iot-device-sdk 1.2.4版本以上才支持 58 | 2: 模拟器和真机调试都可以成功连接 59 | 3:配置信任服务器地址 60 | 日常环境可以点击ide右上角详情,勾选 “不校验合法域名、webview(业务域名)、tls版本及HTTPS证书” 选项 61 | 线上环境必须配置信任地址, 加上productKey为 aaaaaaaabbbbbbbcccccc, cn-shanghai 那配置可信服务器地址为 aaaaaaaabbbbbbbcccccc.iot-as-mqtt.cn-shanghai.aliyuncs.com 62 | -------------------------------------------------------------------------------- /test/auto/register.test.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const iot = require('../../lib'); 3 | const Device = require('../../lib/device'); 4 | const fixtures = require('../fixtures'); 5 | 6 | let device; 7 | let gateway; 8 | beforeAll(()=> { 9 | return new Promise((resolve, reject)=>{ 10 | device = iot.device({ 11 | ...fixtures.sdk_device2 12 | }); 13 | device.on('connect', () => { 14 | resolve(); 15 | }); 16 | }) 17 | },3000) 18 | 19 | afterAll(() => { 20 | device.end(); 21 | gateway.end(); 22 | }); 23 | 24 | const registerDeviceInfo = { 25 | productKey:"a15YDgQGhU0", 26 | productSecret:"AP4HnuqhNqqArIkH", 27 | deviceName:"device1" 28 | } 29 | 30 | describe('device dynamic register test', () => { 31 | 32 | test('direct devices register should be ok', done => { 33 | // 动态注册ok 34 | iot.register(registerDeviceInfo,(res)=>{ 35 | console.log("direct devices register should be ok",res) 36 | if(res.code == '200'){ 37 | done(); 38 | } 39 | }) 40 | }); 41 | 42 | test('direct devices register use wrong info shold be error', done => { 43 | // 动态注册ok 44 | iot.register({ 45 | productKey:"xxxxx", 46 | productSecret:"xxx", 47 | deviceName:"xxx" 48 | },(res)=>{ 49 | console.log("direct devices register should be wrong",res) 50 | if(res.code != '200'){ 51 | done(); 52 | } 53 | }) 54 | }); 55 | 56 | 57 | test('gateway subdevice register should be ok', done => { 58 | // 测试网关动态注册子设备 ok 59 | gateway = iot.gateway({...fixtures.sdk_gateway1}); 60 | gateway.on('connect', () => { 61 | gateway.regiestSubDevice([{ 62 | "deviceName": "device3", 63 | "productKey": "a15YDgQGhU0" 64 | }],(res)=>{ 65 | if(res.message == 'success'){ 66 | done(); 67 | } 68 | }); 69 | }); 70 | }); 71 | 72 | }); 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/manual/dev.js: -------------------------------------------------------------------------------- 1 | const iot = require('../../lib'); 2 | const fixtures = require('../../test/fixtures'); 3 | 4 | // const device = iot.device(fixtures.sub_device3); 5 | 6 | const device = iot.device({ 7 | "ProductKey": "a1YPDpMvd5t", 8 | "DeviceName": "base_sdk_device2", 9 | "DeviceSecret": "WnuQaINuj8oJva7R2vJGwds5w9PqF9VT" 10 | }); 11 | 12 | 13 | device.subscribe('/a1YPDpMvd5t/base_sdk_device2/get'); 14 | 15 | device.on('message', (topic, payload) => { 16 | console.log('topic:',topic); 17 | if(payload){ 18 | console.log('payload',payload.toString()); 19 | } 20 | }); 21 | 22 | device.on('connect', () => { 23 | // devicebeok() //测试动态注册 24 | console.log('>>>>>connect'); 25 | // device.publish('/a1YPDpMvd5t/base_sdk_device2/update', "hello"); 26 | 27 | }); 28 | 29 | 30 | // device.publish('/a1YPDpMvd5t/base_sdk_device1/update', 'hello world!'); 31 | 32 | // device.subscribe('/a1YPDpMvd5t/base_sdk_device1/get'); 33 | 34 | // device.on('message', (topic, payload) => { 35 | // console.log(topic, payload.toString()); 36 | // }); 37 | 38 | // device.subscribe('/a1aq9sQk2JE/sdk_device1/user/get'); 39 | // device.on('connect', () => { 40 | // // devicebeok() //测试动态注册 41 | // console.log('>>>>>connect'); 42 | // // 上报设备属性 43 | // device.postProps({ 44 | // LightSwitch: 0 45 | // }, (res) => { 46 | // console.log('1postProps'); 47 | // }); 48 | 49 | // // 获取远程配置功能ok 50 | // device.getConfig((res) => { 51 | // console.log(`2getConfig:${res.data.toString()}`); 52 | // }); 53 | // //订阅影子设备返回值 54 | // device.onShadow((res) => { 55 | // console.log('3onShadow,%o', res); 56 | // }) 57 | // // 设备主动获取影子 ok 58 | // device.getShadow(); 59 | 60 | // }); 61 | 62 | 63 | 64 | // device.postProps({ 65 | // color: {red:1,green:2,blue:3} 66 | // }, (res) => { 67 | // console.log(res); 68 | // }); 69 | 70 | // device.on('error', (error) => { 71 | // console.log("error",error); 72 | // }); -------------------------------------------------------------------------------- /docs/alipay-min-program-usage.md: -------------------------------------------------------------------------------- 1 | ## 支付宝小程序中使用 alibabacloud-iot-device-sdk 2 | 3 | 注意: alibabacloud-iot-device-sdk 1.2.4版本以上才支持 4 | 5 | ## 支付宝小程序开发环境 6 | 7 | 详细参考:https://docs.alipay.com/mini/developer/getting-started 8 | 9 | ## 如何集成 10 | 11 | ##### lib下载 12 | 下载 alibabacloud-iot-device-sdk代码 [连接地址](https://github.com/aliyun/alibabacloud-iot-device-sdk) 13 | 在dist文件夹中有支持浏览器和微信或支付宝运行环境编译完成的js文件, 14 | - alibabacloud-iot-device-sdk.js 源码版 15 | - alibabacloud-iot-device-sdk.min.js 压缩版 16 | 17 | 18 | #### 项目中集成 19 | 1:将alibabacloud-iot-device-sdk.js或alibabacloud-iot-device-sdk.min.js导入支付宝小程序工程目录 例如 放在工程目录下的dist文件夹,那么js的文件地址为 /dist/alibabacloud-iot-device-sdk.js 20 | 21 | 2:在需要使用到sdk地方 22 | ````js 23 | // 引入包 24 | var iot = require('/dist/alibabacloud-iot-device-sdk.js'); 25 | // 定义云端创建的设备三元组信息,并使用协议声明,使用 "protocol": 'alis://' 26 | const sdk_device = { 27 | "productKey": "", 28 | "deviceName": "", 29 | "deviceSecret": "", 30 | "protocol": 'alis://', 31 | } 32 | // 连接云平台 33 | let device = iot.device(sdk_device); 34 | // 当连接成功进入回调 35 | device.on('connect', () => { 36 | console.log('连接成功....'); 37 | }); 38 | // 当收到云端消息下发 39 | device.on('message', (topic, payload) => { 40 | console.log('topic:', topic); 41 | if (payload) { 42 | console.log('payload', payload); 43 | console.log('payload.toString()', payload.toString()); 44 | } 45 | }); 46 | // 当出现错误时回调 47 | device.on('error', (err) => { 48 | console.log('error:', err); 49 | }); 50 | 51 | // 其他更多功能参考api说明,地址:https://github.com/aliyun/alibabacloud-iot-device-sdk 52 | ```` 53 | 54 | 55 | ## 特别注意,必读 56 | 57 | 1:alibabacloud-iot-device-sdk 1.2.4版本以上才支持 58 | 2: 支付宝模拟器无法成功连接,需要使用真机调试(编辑器右上角调试按钮,生成二维码,支付宝app扫描进入真机模式) 59 | 3:配置信任服务器地址 60 | 日常环境可以点击小程序ide右上角详情,勾选 “忽略 httpRequest 域名合法性检查(仅限调试时,且支付宝 10.1.35 版本以上)” 选项 61 | 线上环境必须配置信任地址, 加上productKey为 aaaaaaaabbbbbbbcccccc, cn-shanghai 那配置可信服务器地址为 aaaaaaaabbbbbbbcccccc.iot-as-mqtt.cn-shanghai.aliyuncs.com 62 | 4:支付宝小程序的数据都会被处理为Uint8Array格式,这部分需要使用者自行处理 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alibabacloud-iot-device-sdk", 3 | "version": "1.2.8", 4 | "description": "alibabacloud iot device sdk", 5 | "keywords": [ 6 | "iot", 7 | "mqtt", 8 | "linkPlatform", 9 | "linkKit", 10 | "linkEdge", 11 | "aliyun-iot-mqtt", 12 | "aliyun", 13 | "alibabacloud-iot-mqtt", 14 | "alibabacloud", 15 | "iot", 16 | "iothub", 17 | "linkdevelop", 18 | "alink", 19 | "device", 20 | "sdk", 21 | "iot-sdk" 22 | ], 23 | "main": "lib/index.js", 24 | "homepage": "https://github.com/aliyun/alibabacloud-iot-device-sdk", 25 | "scripts": { 26 | "lib": "babel src -d lib", 27 | "dev": "babel src -w -d lib", 28 | "test": "jest --coverage ./test/", 29 | "test:dev": "jest --coverage --watch ./test/", 30 | "rap": "npm run lib && rm -rf rap && mkdir rap && cp -R lib rap && cp package.json rap && sed 's/npm/rap/g' README.md > rap/README.md && cd rap && rap publish", 31 | "build": "npm run lib && rimraf dist/ && mkdirp dist/ && browserify lib/index.js -s iot > dist/alibabacloud-iot-device-sdk.js && uglifyjs < dist/alibabacloud-iot-device-sdk.js > dist/alibabacloud-iot-device-sdk.min.js", 32 | "pubus": "rm -rf unstabitily && mkdir unstabitily && cp package.unstabitily.json unstabitily/ && mv ./unstabitily/package.unstabitily.json ./unstabitily/package.json && cp README.md ./unstabitily && cp -r lib/ ./unstabitily && tnpm publish ./unstabitily/" 33 | }, 34 | "jest": { 35 | "verbose": true, 36 | "testEnvironment": "node", 37 | "moduleDirectories": [ 38 | "node_modules" 39 | ] 40 | }, 41 | "author": "玄彦", 42 | "email": "xuanyan.lyw@alibaba-inc.com", 43 | "url": "https://github.com/aliyun/alibabacloud-iot-device-sdk/issues", 44 | "license": "MIT", 45 | "files": [ 46 | "dist/", 47 | "lib" 48 | ], 49 | "devDependencies": { 50 | "babel-cli": "^6.26.0", 51 | "babel-preset-env": "^1.6.1", 52 | "browserify": "^16.2.2", 53 | "jest": "^22.0.4", 54 | "mkdirp": "^0.5.1", 55 | "rimraf": "^2.6.3", 56 | "uglify-js": "^3.4.5" 57 | }, 58 | "dependencies": { 59 | "axios": "^0.18.0", 60 | "crypto-js": "^3.1.9-1", 61 | "mqtt": "3.0.0", 62 | "qs": "^6.6.0" 63 | }, 64 | "ruff": { 65 | "dependencies": { 66 | "mqtt": "^0.1.3" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/manual/devGateway.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const util = require('util'); 3 | const iot = require('../../lib'); 4 | const fixtures = require('../../test/fixtures'); 5 | 6 | const sub_device1 = fixtures.sub_device1; 7 | const sub_device2 = fixtures.sub_device2; 8 | 9 | 10 | // sdk_gateway1 11 | const gateway = iot.gateway({ 12 | ...fixtures.sdk_gateway1 13 | }); 14 | // 测试上报一条设备标签数据 15 | gateway.on('connect', () => { 16 | 17 | }); 18 | 19 | // 测试ok的 20 | function getewaybeok() { 21 | 22 | //网关获子设备 ok, iot.gateway#getTopo() 23 | gateway.getTopo( 24 | (res) => { 25 | console.log('>>>>>getTopo') 26 | console.log(res.message) 27 | console.log(res.data) 28 | } 29 | ); 30 | // 添加topo ok iot.gateway#addTopo() 31 | gateway.addTopo( 32 | [sub_device1, sub_device2], 33 | (res) => { 34 | console.log('>>>>>getTopo', res.message, res.data); 35 | } 36 | ); 37 | gateway.addTopo( 38 | [sub_device1, sub_device2], 39 | (res) => { 40 | console.log('>>>>>getTopo', res.message, res.data); 41 | } 42 | ); 43 | //删除设备ok 44 | gateway.removeTopo( 45 | [sub_device1, sub_device2], 46 | (res) => { 47 | console.log('>>>>>getTopo') 48 | console.log(res.message) 49 | console.log(res.data) 50 | } 51 | ); 52 | 53 | //子设备登录ok 54 | gateway.login( 55 | sub_device1, 56 | (res) => { 57 | console.log('>>>>>login', res); 58 | } 59 | ); 60 | 61 | // 子设备连接状态 62 | sub.on('connect', () => { 63 | console.log(">>>>sub connected!"); 64 | // doSomething 65 | }); 66 | 67 | //登录后登出ok 68 | const sub = gateway.login( 69 | sub_device1, 70 | (res) => { 71 | console.log('>>>>>login', res); 72 | } 73 | ); 74 | 75 | // logout beok 76 | setTimeout(() => { 77 | gateway.logout( 78 | sub_device1, 79 | (res) => { 80 | console.log('>>>>>logout', res); 81 | } 82 | ); 83 | }, 5000); 84 | 85 | // 子设备属性上报 86 | sub.postProps({ 87 | LightSwitch: 0 88 | }, (res) => { 89 | console.log(">>>>sub postProps!"); 90 | console.log(res); 91 | }); 92 | 93 | // 测试网关动态注册子设备 ok 94 | gateway.on('connect', () => { 95 | gateway.regiestSubDevice([{ 96 | "deviceName": "device3", 97 | "productKey": "a15YDgQGhU0" 98 | }], (res) => { 99 | console.log("regiestSubDevice res:", res); 100 | }); 101 | }); 102 | 103 | } 104 | -------------------------------------------------------------------------------- /test/auto/subdevice.test.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const iot = require('../../lib'); 3 | const fixtures = require('../fixtures'); 4 | 5 | const sub_device3 = fixtures.sub_device3; 6 | let gateway; 7 | let sub1; 8 | let postPropsSucceedStat = false; 9 | 10 | beforeAll(() => { 11 | return new Promise((resolve, reject) => { 12 | gateway = iot.gateway({ 13 | ...fixtures.sdk_gateway2 14 | }); 15 | gateway.on('message', function(topic, payload){ 16 | console.log(">>>>gateway message:",topic.toString(),payload.toString()); 17 | }); 18 | gateway.on('connect', () => { 19 | //子设备登录ok 20 | sub1 = gateway.login( 21 | sub_device3, 22 | (res) => { 23 | console.log('>>>>>login', res); 24 | } 25 | ); 26 | // 子设备连接状态 27 | sub1.on('connect', () => { 28 | console.log(">>>>sub connected!"); 29 | sub1.postProps({ 30 | stat:0 31 | }, (res) => { 32 | console.log(">>>>sub device postProps!"); 33 | console.log(res); 34 | postPropsSucceedStat = true; 35 | }); 36 | resolve(); 37 | }); 38 | sub1.on('message', function(topic, payload){ 39 | console.log(">>>>sub1 message:",topic.toString(),payload.toString()); 40 | }); 41 | }); 42 | }) 43 | }, 5000) 44 | 45 | afterAll(() => { 46 | setTimeout(()=>{ 47 | gateway.end(); 48 | sub1.end(); 49 | },5000); 50 | }); 51 | 52 | describe('device test', () => { 53 | test('gateway connect linkPlatform should be ok', done => { 54 | if (gateway.connected) { 55 | done(); 56 | } 57 | }); 58 | 59 | test('sub device connect linkPlatform should be ok', done => { 60 | if (sub1.connected) { 61 | done(); 62 | } 63 | }); 64 | 65 | test('sub device post props should be ok', done => { 66 | 67 | setTimeout(()=>{ 68 | console.log("postPropsSucceedStat",postPropsSucceedStat); 69 | if(postPropsSucceedStat){ 70 | done(); 71 | } 72 | },3000) 73 | 74 | }); 75 | 76 | // test('sub device logout should be ok', done => { 77 | // try { 78 | // //登出ok 79 | // gateway.logout( 80 | // sub_device1, 81 | // (res) => { 82 | // console.log('>>>>>logout', res); 83 | // console.log('>>>>>logout .code', res.code); 84 | // // 速度太快有时会相应520 subDevice is already in offline status, subDevice must online first 85 | // // expect('res.code').toBe('200'); 86 | // done(); 87 | // } 88 | // ); 89 | // } catch (e) { 90 | // console.error(e) 91 | // } 92 | // }) 93 | }); 94 | -------------------------------------------------------------------------------- /test/auto/senior.test.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const util = require('util'); 3 | const iot = require('../../lib'); 4 | const Device = require('../../lib/device'); 5 | const fixtures = require('../fixtures'); 6 | 7 | let device; 8 | beforeAll(()=> { 9 | return new Promise((resolve, reject)=>{ 10 | device = iot.device({ 11 | ...fixtures.sdk_device3 12 | }); 13 | device.on('connect', () => { 14 | resolve(); 15 | }); 16 | }) 17 | },3000) 18 | 19 | afterAll(() => { 20 | device.end(); 21 | }); 22 | 23 | describe('device tags ability test', () => { 24 | let tags = [ 25 | {"attrKey": "Temperature", "attrValue": "311"}, 26 | {"attrKey": "a","attrValue": "avalue"}, 27 | {"attrKey": "b","attrValue": "bvalue"}, 28 | {"attrKey": "c","attrValue": "bvalue"} 29 | ]; 30 | 31 | test('add tags should be ok', done => { 32 | try { 33 | device.postTags( 34 | tags, 35 | (res) => { 36 | console.log(`add tag ok res:${res.id}`); 37 | done() 38 | } 39 | ); 40 | } 41 | catch (e) {console.error(e);} 42 | }); 43 | 44 | test('delete tags should be ok', done => { 45 | try { 46 | device.deleteTags( 47 | ['a'], 48 | (res) => { 49 | console.log(`tagid:${res.id}^ delete succeed`); 50 | done() 51 | } 52 | ); 53 | } 54 | catch (e) {console.error(e);} 55 | }); 56 | 57 | }); 58 | 59 | // 远程配置功能 60 | // remote configuration 61 | describe('device remote configuration ability test', () => { 62 | test('get remote configuration should be ok', done => { 63 | try { 64 | // 获取远程配置功能 65 | device.getConfig((res) => { 66 | console.log("getConfig:%o,",res.data); 67 | done() 68 | }); 69 | } 70 | catch (e) {console.error(e);} 71 | }); 72 | }); 73 | 74 | 75 | // 远程配置功能 76 | // remote configuration 77 | describe('device shadow ability test', () => { 78 | const random = Math.random().toString(36).substr(2); 79 | test('shadow get should be ok', done => { 80 | try { 81 | device.onShadow((res) => { 82 | // { method: 'reply', 83 | // payload:{ status: 'success', 84 | // state: { reported: { a: 'avalue11' } },} 85 | console.log('@@getShadow:,%o', res); 86 | done() 87 | }) 88 | device.getShadow(); 89 | } 90 | catch (e) {console.error(e);} 91 | }); 92 | test('shadow post should be ok', done => { 93 | try { 94 | device.onShadow((res) => { 95 | console.log('@@postShadow,%o', res); 96 | done() 97 | }) 98 | device.postShadow({ 99 | a: "random" 100 | }); 101 | } 102 | catch (e) {console.error(e);} 103 | }); 104 | test('shadow delete should be ok', done => { 105 | try { 106 | device.onShadow((res) => { 107 | console.log('@@deleteShadow,%o', res); 108 | done() 109 | }) 110 | device.deleteShadow("a"); 111 | } 112 | catch (e) {console.error(e);} 113 | }); 114 | },5000); -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | const { hmacSign } = require('../lib/utils'); 2 | 3 | exports.lightDevice = { 4 | productKey: 'a', 5 | deviceName: 'b', 6 | deviceSecret: 'c', 7 | brokerUrl: 'tcp://127.0.0.1:1883/' 8 | }; 9 | 10 | // pn:高级版_sdk开发测试设备 11 | exports.sdk_device1 = { 12 | productKey: 'a1aq9sQk2JE', 13 | deviceName: 'sdk_device1', 14 | deviceSecret: 'shvvZGkq4mM641WUhpfJpyInVeJhAH2y', 15 | }; 16 | exports.sdk_device2 = { 17 | productKey: 'a1aq9sQk2JE', 18 | deviceName: 'sdk_device2', 19 | deviceSecret: '9MjBATSaS9RVditKYamlTtIGwqhuiQeB', 20 | }; 21 | exports.sdk_device3 = { 22 | productKey: 'a1aq9sQk2JE', 23 | deviceName: 'sdk_device3', 24 | deviceSecret: 'ZXdtUJ1nlGxy1v2ZHjq5cTTt3RTVicoX', 25 | }; 26 | exports.sdk_device4 = { 27 | "ProductKey": "a1aq9sQk2JE", 28 | "DeviceName": "sdk_device4", 29 | "DeviceSecret": "oHnUgLkqzv4jlEcqMkukjedXxRWE51Gg" 30 | }; 31 | 32 | // 高级版sdk网关高级版 33 | exports.sdk_gateway1 = { 34 | productKey: 'a1BOOa9HG6Z', 35 | deviceName: 'sdk_gateway1', 36 | deviceSecret: 'foepBtCT0Jl6x640KKQCPeYi9Kv8NdSV' 37 | }; 38 | exports.sdk_gateway2 = { 39 | productKey: 'a1BOOa9HG6Z', 40 | deviceName: 'sdk_gateway2', 41 | deviceSecret: 'w6EXjFLT44VH0T5BUn0xplqW47Ye9Uze' 42 | }; 43 | 44 | // 网关测试子设备 45 | exports.sub_device1 = { 46 | productKey: 'a1aq9sQk2JE', 47 | deviceName: 'sub_device1', 48 | deviceSecret: 'ZlpbIRlTJQSzt8LegT5ALBvHbgEZK6Av' 49 | }; 50 | exports.sub_device2 = { 51 | productKey: 'a1aq9sQk2JE', 52 | deviceName: 'sub_device2', 53 | deviceSecret: 'qp5ZAHBffAjKgqjDQ2bgiReNWE5uBKDW' 54 | }; 55 | exports.sub_device3 = { 56 | "ProductKey": "a1aq9sQk2JE", 57 | "DeviceName": "sub_device3", 58 | "DeviceSecret": "yl2qWGfww43eXrePsHCDhKH5y8URJyJK" 59 | }; 60 | exports.sub_device4 = { 61 | "ProductKey": "a1aq9sQk2JE", 62 | "DeviceName": "sub_device4", 63 | "DeviceSecret": "EIq7cCbjblm1Lb13Z2tXhmw04y1Lx0P6" 64 | }; 65 | exports.sub_device5 = { 66 | "ProductKey": "a1aq9sQk2JE", 67 | "DeviceName": "sub_device5", 68 | "DeviceSecret": "fe7pLZGwNm4uy8Vm7Mu5EqAoR8cO52na" 69 | }; 70 | 71 | 72 | 73 | // 基础版sdk开发 74 | exports.base_sdk_device1 = { 75 | productKey: 'a1YPDpMvd5t', 76 | deviceName: 'base_sdk_device1', 77 | deviceSecret: 'ePvXtVsndoeocd8zDSLC1BDDsCtPLBos' 78 | }; 79 | 80 | exports.base_sdk_device2 = { 81 | productKey: 'a1YPDpMvd5t', 82 | deviceName: 'base_sdk_device2', 83 | deviceSecret: 'WnuQaINuj8oJva7R2vJGwds5w9PqF9VT' 84 | }; 85 | 86 | // 不存在或错误的三元组 87 | exports.wrong_device1 = { 88 | productKey: 'xxxxx', 89 | deviceName: 'xxxxx_base_sdk_device1', 90 | deviceSecret: 'xxxxxx' 91 | }; 92 | 93 | // 其他模拟设备 94 | exports.airBox = { 95 | productKey: 'a', 96 | deviceName: 'b', 97 | deviceSecret: 'c', 98 | brokerUrl: 'tcp://127.0.0.1:1883/' 99 | }; 100 | 101 | exports.gateway = { 102 | productKey: 'a', 103 | deviceName: 'b', 104 | deviceSecret: 'c', 105 | brokerUrl: 'tcp://127.0.0.1:1883/' 106 | }; 107 | 108 | exports.gateway2 = { 109 | productKey: 'a', 110 | deviceName: 'b', 111 | deviceSecret: 'c', 112 | brokerUrl: 'tcp://127.0.0.1:1883/' 113 | }; 114 | 115 | exports.testDevices = [ 116 | { 117 | productKey: 'a', 118 | deviceName: 'b', 119 | deviceSecret: 'c', 120 | brokerUrl: 'tcp://127.0.0.1:1883/' 121 | } 122 | ]; 123 | 124 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | // 阿里云LinkPlatform平台功能topic方法参数模块 2 | export const ALIYUN_BROKER_METHODS_TEMPLATE = { 3 | /* device methods */ 4 | POST_PROPERY: 'thing.event.property.post', 5 | POST_EVENT: 'thing.event.%s.post', 6 | POST_TAGS: 'thing.deviceinfo.update', 7 | DELETE_TAGS: 'thing.deviceinfo.delete', 8 | GET_CONFIG: 'thing.config.get', 9 | /* gateway methods */ 10 | ADD_TOPO: 'thing.topo.add', 11 | DELETE_TOPO: 'thing.topo.delete', 12 | GET_TOPO: 'thing.topo.get', 13 | SUBDEVICE_REGISTER: 'thing.sub.register' 14 | }; 15 | 16 | export const ALIYUN_BROKER_TOPICS = { 17 | /* device topic */ 18 | SERVICE_TOPIC: '/sys/%s/%s/thing/service/%s', 19 | SERVICE_REPLY_TOPIC: '/sys/%s/%s/thing/service/%s_reply', 20 | PROPERTY_POST_TOPIC: '/sys/%s/%s/thing/event/property/post', 21 | PROPERTY_POST_REPLY_TOPIC: '/sys/%s/%s/thing/event/property/post_reply', 22 | ONSET_PROPS_TOPIC :'/sys/%s/%s/thing/service/property/set', 23 | // EVENT_WILDCARD_TOPIC:'/sys/%s/%s/thing/event/#', 24 | EVENT_WILDCARD_TOPIC:'/sys/%s/%s/thing/event/+/post_reply', 25 | EVENT_POST_TOPIC: '/sys/%s/%s/thing/event/%s/post', 26 | EVENT_POST_REPLY_TOPIC: '/sys/%s/%s/thing/event/%s/post_reply', 27 | REPORT_SDK_INFO_TOPIC: '/sys/%s/%s/thing/deviceinfo/update', 28 | //设备标签上报topic。参数:{productKey}/{deviceName} 29 | TAG_TOPIC: '/sys/%s/%s/thing/deviceinfo/update', 30 | //设备标签上报reply的topic。参数:{productKey}/{deviceName} 31 | TAG_REPLY_TOPIC: '/sys/%s/%s/thing/deviceinfo/update_reply', 32 | //删除设备标签topic。参数:{productKey}/{deviceName} 33 | TAG_DELETE_TOPIC: '/sys/%s/%s/thing/deviceinfo/delete', 34 | //删除设备标签reply的topic。参数:{productKey}/{deviceName} 35 | TAG_DELETE_REPLY_TOPIC: '/sys/%s/%s/thing/deviceinfo/delete_reply', 36 | // 远程配置通用topic 37 | CONFIG_WILDCARD_TOPIC: '/sys/%s/%s/thing/config/#', 38 | // 远程配置topic 参数:{productKey}/{deviceName} 39 | CONFIG_TOPIC: '/sys/%s/%s/thing/config/get', 40 | // 远程配置reply topic 参数:{productKey}/{deviceName} 41 | CONFIG_REPLY_TOPIC: '/sys/%s/%s/thing/config/get_reply', 42 | // 远程配置订阅变化topic 参数:{productKey}/{deviceName} 43 | CONFIG_SUBSCRIBE_TOPIC: '/sys/%s/%s/thing/config/push', 44 | // 远程配置reply topic 参数:{productKey}/{deviceName} 45 | CONFIG_SUBSCRIBE_RESP_TOPIC: '/sys/%s/%s/thing/config/push_reply', 46 | // 设备影子订阅topic 47 | SHADOW_SUBSCRIBE_TOPIC: '/shadow/get/%s/%s', 48 | // 设备影子获取topic 49 | SHADOW_GET_TOPIC: '/shadow/update/%s/%s', 50 | // 设备影子更新topic 51 | SHADOW_POST_TOPIC: '/shadow/update/%s/%s', 52 | // RRPC云端请求消息通用Topic 53 | RRPC_REQ_TOPIC:'/sys/%s/%s/rrpc/request/+', 54 | // RRPC响应消息Topic 55 | RRPC_RESP_TOPIC:'/sys/%s/%s/rrpc/response/%s', 56 | 57 | /* gateway topic */ 58 | ADD_TOPO_TOPIC: '/sys/%s/%s/thing/topo/add', 59 | ADD_TOPO_REPLY_TOPIC: '/sys/%s/%s/thing/topo/add_reply', 60 | DELETE_TOPO_TOPIC: '/sys/%s/%s/thing/topo/delete', 61 | DELETE_TOPO_REPLY_TOPIC: '/sys/%s/%s/thing/topo/delete_reply', 62 | GET_TOPO_TOPIC: '/sys/%s/%s/thing/topo/get', 63 | GET_TOPO_REPLY_TOPIC: '/sys/%s/%s/thing/topo/get_reply', 64 | LOGIN_TOPIC: '/ext/session/%s/%s/combine/login', 65 | LOGIN_REPLY_TOPIC: '/ext/session/%s/%s/combine/login_reply', 66 | LOGOUT_TOPIC: '/ext/session/%s/%s/combine/logout', 67 | LOGOUT_REPLY_TOPIC: '/ext/session/%s/%s/combine/logout_reply', 68 | SUBDEVICE_REGISTER_TOPIC: '/sys/%s/%s/thing/sub/register', 69 | SUBDEVICE_REGISTER_REPLY_TOPIC: '/sys/%s/%s/thing/sub/register_reply', 70 | }; 71 | -------------------------------------------------------------------------------- /examples/broswer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 97 | 98 |
99 |
100 | 101 | 102 | 103 | 104 |
105 | 106 | 107 | alibabacloud-iot-device-sdk 108 | 109 | 110 | -------------------------------------------------------------------------------- /test/manual/devDevices.js: -------------------------------------------------------------------------------- 1 | const Buffer = require('buffer').Buffer; 2 | const util = require('util'); 3 | const iot = require('../../lib'); 4 | const fixtures = require('../fixtures'); 5 | 6 | // const device = iot.device({...fixtures.sdk_device1}); 7 | 8 | const device = iot.device({ 9 | "ProductKey": "a1ouyopKiEU", 10 | // "ProductKey": "a1ouyopKiEU123", 11 | "DeviceName": "sub1", 12 | "DeviceSecret": "SpT5OQsX4LZi5CsQIb6OS8hVt8qFRw7o" 13 | }) 14 | 15 | // const device = iot.device({ 16 | // productKey: 'a1BOOa9HG6Z', 17 | // deviceName: 'sdk_network1', 18 | // deviceSecret: 'SgNxAqaav9URfVuHpnxmq9vDFZbwmZ7H' 19 | // }); 20 | 21 | // base_sdk_device2 22 | // const device = iot.device({ ...fixtures.base_sdk_device2}) 23 | 24 | // const deviceConfig = { 25 | // "productKey": "a1FVHNuAmvq", 26 | // "deviceName": "testdevice1", 27 | // "deviceSecret": "QNNcinhgbY5VgpnQT0DtIUXAlV4jYthO", 28 | // "region":"cn-shanghai" 29 | // } 30 | // const device = iot.device(deviceConfig); 31 | 32 | // device.subscribe(`${device.productKey}/${device.deviceName}/user/get`); 33 | // device.on('message', (topic, payload) => { 34 | // console.log(topic, payload); 35 | // console.log("^:message",topic); 36 | // device.end(); 37 | // }); 38 | 39 | // 测试上报一条设备标签数据 40 | device.on('connect', () => { 41 | // devicebeok() //测试动态注册 42 | console.log('>>>>>connect'); 43 | // 上报设备属性 44 | device.postProps({ 45 | state: 0 46 | }, (res) => { 47 | console.log('1postProps'); 48 | }); 49 | 50 | // 获取远程配置功能ok 51 | device.getConfig((res) => { 52 | console.log(`2getConfig:${res.data.toString()}`); 53 | }); 54 | //订阅影子设备返回值 55 | device.onShadow((res) => { 56 | console.log('3onShadow,%o', res); 57 | }) 58 | // 设备主动获取影子 ok 59 | device.getShadow(); 60 | 61 | }); 62 | 63 | 64 | 65 | // device.on('message', (topic,message) => { 66 | // console.log('topic:',topic); 67 | // console.log('message:',message.toString()); 68 | // }); 69 | 70 | // 测试直连设备动态注册功能 ok 71 | function testDirectRegiest(){ 72 | const registerDeviceInfo = { 73 | productKey:"a15YDgQGhU0", 74 | productSecret:"AP4HnuqhNqqArIkH", 75 | deviceName:"device1" 76 | } 77 | // 动态注册ok 78 | iot.register(registerDeviceInfo,(error,res)=>{ 79 | if(error){console.log("register faild",error);return} 80 | console.log("register succeed,data:",res); 81 | }) 82 | } 83 | 84 | 85 | // 测试直连设备动态注册功能 ok 86 | function testSubDeivceRegiest(){ 87 | const registerDeviceInfo = { 88 | productKey:"a15YDgQGhU0", 89 | productSecret:"AP4HnuqhNqqArIkH", 90 | deviceName:"device1" 91 | } 92 | // 动态注册ok 93 | iot.register(registerDeviceInfo,(error,res)=>{ 94 | if(error){console.log("register faild",error);return} 95 | console.log("register succeed,data:",res); 96 | }) 97 | } 98 | 99 | 100 | // 测试ok的 101 | function devicebeok() { 102 | //连接ok 103 | console.log('>>>>>connect'); 104 | 105 | // 上报设备属性 106 | device.postProps({ 107 | LightSwitch: 0 108 | }, (res) => { 109 | console.log(res); 110 | }); 111 | //订阅可以收到服务调用下发 112 | device.onService('wakeup_async', function (res) { 113 | // 处理服务参数 114 | console.log('1^:device.serve res',res); 115 | }); 116 | //重复订阅不会多次执行回调函数 117 | device.onService('wakeup_async', function (res) { 118 | // 处理服务参数 119 | console.log('1^:device.serve'); 120 | }); 121 | 122 | // 异步方式回复 123 | device.onService('wakeup_async', function (res,reply) { 124 | // 处理服务参数 125 | console.log('^onService',res); 126 | 127 | reply({ 128 | "code": 200, 129 | "data": {out:1} 130 | }); 131 | }) 132 | 133 | // 同步方式回复 134 | device.onService('wakeup_sync', function (res,reply) { 135 | // 处理服务参数 136 | console.log('^onService',res); 137 | reply({ 138 | "code": 200, 139 | "data": {out:1} 140 | },'sync'); 141 | }) 142 | 143 | // postevent 事件上班 ok 144 | device.postEvent("lowpower", { 145 | power: 10, 146 | }, (res) => { 147 | console.log(`1postEvent:${res}`); 148 | }) 149 | //回调函数不重复 150 | device.postEvent("lowpower", { 151 | power: 20, 152 | }, (res) => { 153 | console.log(`2postEvent:${res}`); 154 | }) 155 | 156 | //删除标签ok 157 | device.deleteTags(['a'], (res) => { 158 | console.log(`tagid:${res.id}^ delete succeed`); 159 | }); 160 | //删除多个标签回调函数不冲突 161 | device.deleteTags(['b'], (res) => { 162 | console.log(`tagid:${res.id}^ delete succeed`); 163 | }); 164 | // 添加标签ok 165 | device.postTags( 166 | [{ 167 | "attrKey": "Temperature", 168 | "attrValue": "311" 169 | }, 170 | { 171 | "attrKey": "a", 172 | "attrValue": "avalue" 173 | }, 174 | { 175 | "attrKey": "b", 176 | "attrValue": "bvalue" 177 | } 178 | ], 179 | (res) => { 180 | console.log(res); 181 | } 182 | ); 183 | 184 | // 获取远程配置功能ok 185 | device.getConfig((res) => { 186 | console.log(`getConfig:${res.data.toString()}`); 187 | }); 188 | // 订阅远程配置 189 | device.onConfig((res) => { 190 | console.log("onConfig,res:",res); 191 | }); 192 | 193 | // 获取远程接收云端下发 194 | device.onConfig((res) => { 195 | console.log(`onConfig:${res.data.toString()}`); 196 | }); 197 | 198 | 199 | //订阅影子设备返回值 200 | device.onShadow((res) => { 201 | console.log('获取最新设备影子,%o', res); 202 | }) 203 | // 设备主动获取影子 ok 204 | device.getShadow(); 205 | // 设备上报实际值 206 | device.postShadow({ 207 | a: "avalue11" 208 | }); 209 | 210 | // 删除影子属性 ok 211 | // 参数 属性key,若为空则删除全部 212 | device.deleteShadow("a"); 213 | device.deleteShadow(["a","b"]); 214 | device.deleteShadow() 215 | 216 | } 217 | -------------------------------------------------------------------------------- /test/manual/subdevice.js: -------------------------------------------------------------------------------- 1 | const iot = require('../../'); 2 | const fixtures = require('../fixtures'); 3 | 4 | 5 | const gateway = iot.gateway(fixtures.sdk_gateway2); 6 | // const dsad = iot.device(fixtures.sub_device3); 7 | let sub1; 8 | gateway.on('message', function(topic, payload){ 9 | console.log("message>>>" , topic.toString(),payload.toString()); 10 | }); 11 | 12 | gateway.on('error', (err)=>{ 13 | console.log('gateway error:',err); 14 | }); 15 | 16 | // setInterval(() => { 17 | // console.log("login") 18 | // gateway.login( 19 | // fixtures.sub_device5, 20 | // (res) => { 21 | // console.log('>>>>> sub1 login ...'); 22 | // }); 23 | // }, 5000); 24 | 25 | gateway.on('connect', () => { 26 | //子设备1上线 27 | sub1 = gateway.login( 28 | fixtures.sub_device4, 29 | (res) => { 30 | console.log('>>>>> sub1 login ...'); 31 | }); 32 | 33 | 34 | sub1.on('error', (resp) => { 35 | console.log(">>>>sub1 error!!!",resp); 36 | }); 37 | 38 | //订阅可以收到服务调用下发 39 | sub1.onService('add_sync', function (res,reply) { 40 | console.log('add_sync called,res:',res); 41 | const { params:{x,y}={}} = res; 42 | const result = addFunc(x,y); 43 | console.log('result',result); 44 | reply(result,'sync'); 45 | }); 46 | 47 | function addFunc(x,y){ 48 | let err; 49 | if(x==undefined || y==undefined){ 50 | err = 'x or y invail value'; 51 | return {err,code:10001} 52 | } 53 | 54 | return { 55 | data:{ 56 | z:x+y 57 | }, 58 | code:200 59 | } 60 | } 61 | 62 | sub1.on('connect', () => { 63 | console.log(">>>>sub1 connected!"); 64 | 65 | 66 | 67 | 68 | // sub1.postProps({ 69 | // state: 0 70 | // },(res)=>{ 71 | // console.log(">>>>sub1 postProps!",res); 72 | // }); 73 | // sub1.postProps({ 74 | // state: 0 75 | // },(res)=>{ 76 | // console.log(">>>>sub1 postProps!",res); 77 | // }); 78 | // sub1.postEvent("lowpower", { 79 | // power: 10, 80 | // }, (res) => { 81 | // console.log('1postEvent:',res); 82 | // }) 83 | 84 | // 添加标签ok 85 | // sub1.postTags( 86 | // [{ 87 | // "attrKey": "Temperature", 88 | // "attrValue": "311" 89 | // }, 90 | // { 91 | // "attrKey": "a", 92 | // "attrValue": "avalue" 93 | // }, 94 | // { 95 | // "attrKey": "b", 96 | // "attrValue": "bvalue" 97 | // } 98 | // ], 99 | // (res) => { 100 | // console.log(res); 101 | // } 102 | // ); 103 | 104 | // sub1.getConfig((res) => { 105 | // console.log('>>>> getConfig:',res.data); 106 | // }); 107 | // // 订阅远程配置 108 | // sub1.onConfig((res) => { 109 | // console.log("onConfig,res:",res); 110 | // }); 111 | 112 | //订阅影子设备返回值 113 | sub1.onShadow((res) => { 114 | console.log('获取最新设备影子,%o', res); 115 | }) 116 | // 设备主动获取影子 ok 117 | sub1.getShadow(); 118 | }); 119 | 120 | 121 | //订阅可以收到服务调用下发 122 | // sub1.onService('wakeup_async', function (res,reply) { 123 | // // 处理服务参数 124 | // console.log('1^:sub1 wakeup_async',res); 125 | // reply({ 126 | // "code": 200, 127 | // "data": {out:0} 128 | // }); 129 | // }); 130 | 131 | // sub1.onService('wakeup_sync', function (res,reply) { 132 | // // 处理服务参数 133 | // console.log('1^:sub1 wakeup_sync',res); 134 | // reply({ 135 | // "code": 200, 136 | // "data": {out:1} 137 | // },'sync'); 138 | // }); 139 | 140 | 141 | 142 | //删除标签ok 143 | // sub1.deleteTags(['a'], (res) => { 144 | // console.log(`tagid:${res.id}^ delete succeed`); 145 | // }); 146 | // //删除多个标签回调函数不冲突 147 | // sub1.deleteTags(['b'], (res) => { 148 | // console.log(`tagid:${res.id}^ delete succeed`); 149 | // }); 150 | // 添加标签ok 151 | // sub1.postTags( 152 | // [{ 153 | // "attrKey": "Temperature", 154 | // "attrValue": "311" 155 | // }, 156 | // { 157 | // "attrKey": "a", 158 | // "attrValue": "avalue" 159 | // }, 160 | // { 161 | // "attrKey": "b", 162 | // "attrValue": "bvalue" 163 | // } 164 | // ], 165 | // (res) => { 166 | // console.log(res); 167 | // } 168 | // ); 169 | 170 | //子设备2上线 171 | // const sub2 = gateway.login( 172 | // fixtures.sub_device2, 173 | // (res) => { 174 | // console.log('>>>>> sub2 login ...'); 175 | // }); 176 | 177 | // sub2.on('error', (resp) => { 178 | // console.log(">>>>sub2 error!!!",resp); 179 | // }); 180 | 181 | // sub2.on('connect', () => { 182 | // console.log(">>>>sub2 connected!"); 183 | 184 | // }); 185 | 186 | 187 | 188 | // setTimeout(() => { 189 | // gateway.logout( 190 | // {"productKey": "a1FTDuODs4g","deviceName": "subdevice1"}, 191 | // (res) => { 192 | // //sub1.end(); 193 | // console.log('>>>>>sub logout', res); 194 | // sub1.postProps({ 195 | // LightSwitch: 0 196 | // },(res)=>{ 197 | // console.log(">>>>sub1 postProps!"); 198 | // console.log(res); 199 | // }); 200 | // }); 201 | // }, 10000); 202 | 203 | 204 | //子设备连接状态 205 | // sub1.on('connect', () => { 206 | // console.log(">>>>sub1 connected!"); 207 | // sub1.postProps({ 208 | // LightSwitch: 0 209 | // },(res)=>{ 210 | // console.log(">>>>sub1 postProps!"); 211 | // console.log(res); 212 | // }); 213 | // }); 214 | 215 | // sub1.on('message', function(topic, payload){ 216 | // console.log('SUB1 message:',topic.toString(),payload.toString()); 217 | // }); 218 | 219 | // sub1.on('error', (err)=>{ 220 | // console.log('SUB error:',err); 221 | // }); 222 | 223 | //设置延迟使主网关离线 224 | // setTimeout(() => { 225 | // gateway.end(); 226 | // console.log('gateway END!'); 227 | // }, 5000); 228 | 229 | // 子设备能否正常登出 230 | // setTimeout(() => { 231 | // gateway.logout( 232 | // fixtures.sub_device2, 233 | // (res) => { 234 | // console.log('>>>>>sub2 logout', res); 235 | // }); 236 | // // gateway.logout(sub1); 237 | // }, 5000); 238 | }); 239 | 240 | // // 退出登录 241 | // setTimeout(() => { 242 | // // sub1._unsubscribePresetTopic(); 243 | // // gateway._unsubscribePresetTopic(); 244 | // gateway.logout(fixtures.sub_device5) 245 | // }, 5000); -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const cryptojs = require('crypto-js'); 2 | const os = require('os'); 3 | const axios = require('axios'); 4 | const qs = require('qs'); 5 | 6 | function hmacSign(type, secret, content) { 7 | return cryptojs.HmacSHA1(content,secret).toString(); 8 | } 9 | 10 | function mqttMatch(filter, topic) { 11 | const filterArray = filter.split('/') 12 | const length = filterArray.length 13 | const topicArray = topic.split('/') 14 | for (var i = 0; i < length; ++i) { 15 | var left = filterArray[i] 16 | var right = topicArray[i] 17 | if (left === '#') return topicArray.length >= length - 1 18 | if (left !== '+' && left !== right) return false 19 | } 20 | return length === topicArray.length 21 | } 22 | 23 | function mqttNotMatch(filter, topic){ 24 | return !mqttMatch(filter, topic) 25 | } 26 | 27 | function createGuid() { 28 | let id = 1; 29 | return function () { 30 | return String(id++); 31 | }; 32 | } 33 | 34 | function isJsonString(str) { 35 | try { 36 | if (typeof JSON.parse(str) == "object") { 37 | return true; 38 | } 39 | } catch (e) { 40 | 41 | } 42 | return false; 43 | } 44 | 45 | function signUtil(deviceConfig, signMethod = 'sha1') { 46 | const timestamp = Date.now(); 47 | // 忽略三元组大小写 48 | tripleIgnoreCase(deviceConfig); 49 | const device = { 50 | productKey: deviceConfig.productKey, 51 | deviceName: deviceConfig.deviceName, 52 | clientId: `${deviceConfig.productKey}&${deviceConfig.deviceName}`, 53 | timestamp 54 | }; 55 | device.signMethod = `hmac${signMethod}`; 56 | const signcontent = `clientId${device.clientId}deviceName${ 57 | device.deviceName 58 | }productKey${device.productKey}timestamp${timestamp}`; 59 | device.sign = hmacSign(signMethod, deviceConfig.deviceSecret, signcontent); 60 | return device; 61 | } 62 | 63 | function deviceRegisterSign(productKey, productSecret, deviceName, random, signMethod = 'sha1') { 64 | // 忽略三元组大小写 65 | const config = { productKey,productSecret,deviceName}; 66 | tripleIgnoreCase(config); 67 | // signMethod = `hmac${signMethod}`; 68 | const signcontent = `deviceName${config.deviceName}productKey${config.productKey}random${random}`; 69 | // console.log("deviceRegisterSign",signMethod,signcontent) 70 | const sign = hmacSign("sha1", config.productSecret, signcontent); 71 | 72 | return sign; 73 | } 74 | 75 | function createDebug(mod) { 76 | return (...args) => { 77 | const debugMod = process.env.DEBUG; 78 | if (debugMod && (debugMod === '*' || mod.indexOf(debugMod) > -1)) { 79 | const _args = [`${mod}:`, ...args]; 80 | console.log(..._args); 81 | } 82 | }; 83 | } 84 | 85 | function getIP() { 86 | var ifaces = os.networkInterfaces(); 87 | let ip = ""; 88 | for (var dev in ifaces) { 89 | ifaces[dev].forEach(function (details) { 90 | if (details.family == 'IPv4' && dev === 'en0') { 91 | ip = details.address; 92 | } 93 | }); 94 | } 95 | return ip; 96 | } 97 | 98 | /* 99 | * 设备动态注册 100 | * 子设备注册+直连设备使用一型一密动态注册 101 | */ 102 | function register({ 103 | productKey, 104 | productSecret, 105 | deviceName, 106 | random, 107 | signMethod, 108 | registerURL 109 | }, cb) { 110 | const rd = random || Math.random().toString(36).substr(2); 111 | const sm = signMethod || "hmacsha1"; 112 | const url = registerURL || "https://iot-auth.cn-shanghai.aliyuncs.com/auth/register/device"; 113 | const sign = deviceRegisterSign(productKey, productSecret, deviceName, rd, sm); 114 | const data = qs.stringify({ 115 | productKey, 116 | deviceName, 117 | random: rd, 118 | sign, 119 | signMethod: sm 120 | }); 121 | axios.post(url, data).then(function (res) { 122 | const data = res.data; 123 | if (data.code != 200) { 124 | throw data 125 | } 126 | cb(data); 127 | }).catch(function (error) { 128 | cb(error); 129 | }) 130 | } 131 | // 三元组不为空 132 | function tripleExpectNotNull(triple) { 133 | if (typeof triple.productKey === 'undefined') { 134 | throw new Error('productKey should not be empty'); 135 | } 136 | if (typeof triple.deviceName === 'undefined') { 137 | throw new Error('deviceName should not be empty'); 138 | } 139 | if (typeof triple.deviceSecret === 'undefined') { 140 | throw new Error('deviceSecret should not be empty'); 141 | } 142 | } 143 | 144 | // 三元组忽略大小写 triple ignore case 145 | function tripleIgnoreCase(config){ 146 | Object.keys(config).forEach((originKey) => { 147 | let key = originKey.toLowerCase(); 148 | switch (key) { 149 | case "productkey": 150 | config.productKey = config[originKey]; 151 | break; 152 | case "devicename": 153 | config.deviceName = config[originKey]; 154 | break; 155 | case "devicesecret": 156 | config.deviceSecret = config[originKey]; 157 | break; 158 | } 159 | }); 160 | return config; 161 | } 162 | 163 | // 获取当前sdk版本 164 | // 支付宝小程序:JS|Ali 165 | // 微信小程序:JS|WX 166 | // 浏览器:JS|Broswer 167 | // node环境:NodeJS 168 | // 命令行:JS|CLI 169 | // 未知:JS|UNKNOW 170 | let LANG = 'JS|UNKNOW'; 171 | 172 | function getSDKLanguage(){ 173 | // 支付宝小程序运行环境 不完全可靠 174 | if(typeof my !== 'undefined' && (my.navigateToMiniProgram || my.navigateBackMiniProgram)){ 175 | return "JS|Ali"; 176 | } 177 | // 微信小程序判断 不完全可靠 178 | if(typeof wx !== 'undefined' && (wx.navigateToMiniProgram || wx.navigateBackMiniProgram)){ 179 | return "JS|WX"; 180 | } 181 | // 浏览器环境判断 182 | if( 183 | (typeof window !== 'undefined' && typeof window.document !== 'undefined') || 184 | (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) 185 | ) 186 | { 187 | return "JS|Broswer "; 188 | } 189 | // node环境 190 | if(typeof module !== 'undefined' && module.exports){ 191 | return "NodeJS"; 192 | } 193 | 194 | // 返回主动设置的环境值 195 | return LANG; 196 | 197 | } 198 | function setSDKLanguage(lang){ 199 | LANG = lang; 200 | } 201 | 202 | 203 | exports.tripleIgnoreCase = tripleIgnoreCase; 204 | exports.tripleExpectNotNull = tripleExpectNotNull; 205 | exports.getIP = getIP; 206 | exports.hmacSign = hmacSign; 207 | exports.mqttMatch = mqttMatch; 208 | exports.mqttNotMatch = mqttNotMatch; 209 | exports.createGuid = createGuid; 210 | exports.signUtil = signUtil; 211 | exports.createDebug = createDebug; 212 | exports.register = register; 213 | exports.isJsonString = isJsonString; 214 | exports.getSDKLanguage = getSDKLanguage; 215 | exports.setSDKLanguage = setSDKLanguage; 216 | -------------------------------------------------------------------------------- /src/gateway.js: -------------------------------------------------------------------------------- 1 | 2 | // const Device = require('./device'); 3 | const Thing = require('./thing'); 4 | const SubDevice = require('./subdevice'); 5 | 6 | const { 7 | createDebug, 8 | signUtil, 9 | tripleExpectNotNull, 10 | tripleIgnoreCase, 11 | mqttMatch, 12 | mqttNotMatch, 13 | isJsonString 14 | } = require('./utils'); 15 | const debug = createDebug('gateway'); 16 | 17 | class Gateway extends Thing { 18 | constructor(config) { 19 | super(config); 20 | // create mqttclient 21 | this._createClient(this.model); 22 | // subcribe client event and preset topic 23 | this._subscribeClientEvent(); 24 | // 子设备管理 25 | this.subDevices = []; 26 | // 调试模式标识 27 | debug('debugger mode'); 28 | } 29 | // 入参,设备三元组组成的数组 30 | addTopo(devices, cb) { 31 | const signed = devices.map((device) => signUtil(device)); 32 | this._publishAlinkMessage({ 33 | method: this.model.ADD_TOPO_METHOD, 34 | pubTopic: this.model.ADD_TOPO_TOPIC, 35 | params: signed, 36 | }, 37 | cb 38 | ); 39 | } 40 | 41 | getTopo(cb) { 42 | this._publishAlinkMessage({ 43 | method: this.model.GET_TOPO_METHOD, 44 | pubTopic: this.model.GET_TOPO_TOPIC, 45 | params: {}, 46 | }, 47 | cb 48 | ); 49 | } 50 | /* 51 | {"id" : "123", 52 | "version":"1.0", 53 | "params" : [{ 54 | "deviceName" : "deviceName1234", 55 | "productKey" : "1234556554" 56 | }], 57 | "method":"thing.topo.delete" } 58 | */ 59 | removeTopo(devices, cb) { 60 | this._publishAlinkMessage({ 61 | method: this.model.DELETE_TOPO_METHOD, 62 | pubTopic: this.model.DELETE_TOPO_TOPIC, 63 | params: devices, 64 | }, 65 | cb 66 | ); 67 | } 68 | login(device, cb) { 69 | // 忽略三元组大小写 70 | tripleIgnoreCase(device); 71 | // 校验三元组非空 72 | tripleExpectNotNull(device); 73 | // 创建subdevice 74 | const subDevice = new SubDevice(device,this); 75 | 76 | this._addSubDevices(subDevice); 77 | // 通过网关登录 78 | this._publishAlinkMessage({ 79 | params: signUtil(device), 80 | pubTopic: this.model.LOGIN_TOPIC, 81 | },(resp)=>{ 82 | cb(resp); 83 | if (resp.code === 200) { 84 | // gateway subscribe subDevice Topic 85 | // subdevice subscribe topic must until subdevice login succeed! 86 | this._subscribePresetTopic(subDevice); 87 | subDevice.emit("connect"); 88 | } else { 89 | subDevice.emit("error",resp); 90 | } 91 | } 92 | ); 93 | 94 | return subDevice; 95 | } 96 | logout(params, cb) { 97 | this._publishAlinkMessage({ 98 | params: params, 99 | pubTopic: this.model.LOGOUT_TOPIC, 100 | }, 101 | cb 102 | ); 103 | } 104 | 105 | /* 106 | * 网关动态注册子设备 107 | "params": [ 108 | { 109 | "deviceName": "deviceName1234", 110 | "productKey": "1234556554" 111 | } 112 | ] 113 | */ 114 | regiestSubDevice(data, cb) { 115 | let params; 116 | if (data instanceof Array) { 117 | params = data; 118 | } else if (data instanceof Object) { 119 | params = [data]; 120 | } 121 | this._publishAlinkMessage({ 122 | method: this.model.SUBDEVICE_REGISTER_METHOD, 123 | params, 124 | pubTopic: this.model.SUBDEVICE_REGISTER_TOPIC, 125 | }, 126 | cb 127 | ); 128 | } 129 | 130 | /* 131 | * 重写device message方法,因为消息只发送到网关,所以要通过网关转发到子设备 132 | */ 133 | _mqttCallbackHandler(topic,message) { 134 | 135 | // 情况1:返回值为非结构化数据(非结构化可能是:基础版产品或是用户自定义topic) 136 | if(isJsonString(message.toString())==false){ 137 | return; 138 | } 139 | 140 | // 开始处理返回值 141 | try { 142 | let res = JSON.parse(message.toString()); 143 | let subDevice; 144 | // console.log('gateway _mqttCallbackHandler',topic,res); 145 | 146 | //处理On Props Set回调 147 | // topic /sys///thing/service/property/set 148 | subDevice = this._searchMqttMatchOnSetPropsTopicWithSubDevice(topic); 149 | if(subDevice){ 150 | subDevice._onPropsCB(res); 151 | return; 152 | } 153 | 154 | //处理子设备服务返回数据,同步或者异步方式 155 | subDevice = this._searchMqttMatchServiceTopicWithSubDevice(topic); 156 | if(subDevice){ 157 | // console.log("gateway _searchMqttMatchServiceTopicWithSubDevice",topic); 158 | subDevice._onReceiveService(topic,res); 159 | return; 160 | } 161 | // 处理子设备影子服务回调 162 | subDevice = this._searchMqttMatchShadowTopicWithSubDevice(topic); 163 | if(subDevice){ 164 | // console.log("_searchMqttMatchShadowTopicWithSubDevice"); 165 | subDevice._onShadowCB(res); 166 | return; 167 | } 168 | 169 | // 远程配置回调 170 | subDevice = this._searchMqttMatchConfigTopicWithSubDevice(topic); 171 | if(subDevice){ 172 | // console.log("_searchMqttMatchConfigTopicWithSubDevice"); 173 | subDevice._onConfigCB(res); 174 | return; 175 | } 176 | 177 | //其他通用回调 178 | const {id: cbID} = res; 179 | const callback = this._getAllSubDevicesCallback(cbID,topic); 180 | // console.log("gateway通用回调",topic,callback,message); 181 | if(callback){callback(res);} 182 | 183 | } catch (e) { 184 | console.log('_mqttCallbackHandler error',e) 185 | } 186 | 187 | // device 、gateway message handler 188 | super._mqttCallbackHandler(topic,message); 189 | } 190 | 191 | // 子设备On Set Porps topic 192 | _searchMqttMatchOnSetPropsTopicWithSubDevice(topic){ 193 | return this._getSubDevices().find( subDevice => 194 | mqttMatch(subDevice.model.ONSET_PROPS_TOPIC,topic) 195 | ); 196 | } 197 | // 子设备的服务topic 198 | _searchMqttMatchServiceTopicWithSubDevice(topic){ 199 | return this._getSubDevices().find(subDevice => 200 | mqttMatch(subDevice.model.getWildcardServiceTopic(),topic) || mqttMatch(subDevice.model.RRPC_REQ_TOPIC,topic) 201 | ); 202 | } 203 | // 子设备影子设备topic 204 | _searchMqttMatchShadowTopicWithSubDevice(topic){ 205 | return this._getSubDevices().find(subDevice => 206 | mqttMatch(subDevice.model.SHADOW_SUBSCRIBE_TOPIC,topic) 207 | ); 208 | } 209 | // 子设备的远程配置topic 210 | _searchMqttMatchConfigTopicWithSubDevice(topic){ 211 | return this._getSubDevices().find(subDevice => { 212 | return mqttMatch(subDevice.model.getWildcardConfigTopic(),topic) && 213 | (mqttNotMatch(subDevice.model.CONFIG_REPLY_TOPIC,topic) === true) 214 | }); 215 | } 216 | 217 | _getSubDevices(){ 218 | if(!this.subDevices) { 219 | this.subDevices = []; 220 | } 221 | return this.subDevices; 222 | } 223 | _addSubDevices(subDevice){ 224 | this._getSubDevices().push(subDevice); 225 | } 226 | 227 | _getAllSubDevicesCallback(cbID,topic) { 228 | let callback; 229 | this._getSubDevices().forEach(subDevice => { 230 | // console.log('>>>subDevice',subDevice); 231 | let cb = subDevice._findCallback(cbID,topic); 232 | if(cb) { 233 | callback = cb; 234 | } 235 | return; 236 | }); 237 | return callback; 238 | } 239 | } 240 | 241 | module.exports = Gateway; 242 | -------------------------------------------------------------------------------- /src/model.js: -------------------------------------------------------------------------------- 1 | import { 2 | ALIYUN_BROKER_METHODS_TEMPLATE as METHODS_TEMPLATE, 3 | ALIYUN_BROKER_TOPICS as BROKER_TOPICS 4 | } from './const' 5 | import packageJson from '../package.json' 6 | 7 | const { 8 | hmacSign, 9 | getSDKLanguage 10 | } = require('./utils'); 11 | const util = require('util'); 12 | 13 | const isBrowser = 14 | (typeof window !== 'undefined' && typeof window.document !== 'undefined') || 15 | (typeof WorkerGlobalScope !== 'undefined' && 16 | self instanceof WorkerGlobalScope); 17 | const DEFAULT_REGION = 'cn-shanghai'; 18 | const BROKER_URL = '%s%s.iot-as-mqtt.%s.aliyuncs.com:%s/'; 19 | const tlsPrefix = ['tls://', 'mqtts://', 'wss://','wxs://','alis://']; 20 | 21 | export default class Model { 22 | 23 | constructor(config) { 24 | //初始化参数 25 | this.init(config) 26 | //参数校验合法 27 | this.configchecking(); 28 | } 29 | 30 | // 获取通配订阅topic 31 | getWildcardTopic() { 32 | return util.format("/sys/%s/%s/thing", this.productKey, this.deviceName); 33 | } 34 | 35 | // 获取三元组 36 | triple() { 37 | const { 38 | productKey, 39 | deviceName, 40 | deviceSecret 41 | } = this; 42 | return { 43 | productKey, 44 | deviceName, 45 | deviceSecret 46 | } 47 | } 48 | 49 | getPostEventMethod(eventName) { 50 | return util.format(METHODS_TEMPLATE.POST_EVENT, eventName); 51 | } 52 | getPostEvenTopic(eventName) { 53 | return util.format(BROKER_TOPICS.EVENT_POST_TOPIC, this.productKey, this.deviceName, eventName); 54 | } 55 | getWildcardEvenTopic() { 56 | return util.format(BROKER_TOPICS.EVENT_WILDCARD_TOPIC, this.productKey, this.deviceName); 57 | } 58 | getPostEvenReplyTopic(eventName) { 59 | return util.format(BROKER_TOPICS.EVENT_POST_REPLY_TOPIC, this.productKey, this.deviceName, eventName); 60 | } 61 | getServiceTopic(serviceName) { 62 | let serviceNameReplace = serviceName; 63 | if (serviceName.indexOf('/') != -1) { 64 | serviceNameReplace = serviceName.replace('.', '/') 65 | } 66 | return util.format(BROKER_TOPICS.SERVICE_TOPIC, this.productKey, this.deviceName, serviceNameReplace) 67 | } 68 | getWildcardServiceTopic(){ 69 | return util.format(BROKER_TOPICS.SERVICE_TOPIC, this.productKey, this.deviceName, '#') 70 | } 71 | getWildcardConfigTopic(){ 72 | return util.format(BROKER_TOPICS.CONFIG_WILDCARD_TOPIC, this.productKey, this.deviceName) 73 | } 74 | 75 | getServiceRespTopic(serviceName) { 76 | let serviceNameReplace = serviceName; 77 | if (serviceName.indexOf('/') != -1) { 78 | serviceNameReplace = serviceName.replace('.', '/') 79 | } 80 | return util.format(BROKER_TOPICS.SERVICE_REPLY_TOPIC, this.productKey, this.deviceName, serviceNameReplace) 81 | } 82 | 83 | getRRPCRespTopic(msgId){ 84 | return util.format(BROKER_TOPICS.RRPC_RESP_TOPIC, this.productKey, this.deviceName, msgId) 85 | } 86 | 87 | //生成alink协议内容 88 | genAlinkContent(method, params, id="", version = '1.0') { 89 | const msg = { 90 | id, 91 | version, 92 | params, 93 | method 94 | }; 95 | return msg; 96 | } 97 | 98 | /** 99 | * 连接参数生成规则 100 | * broker:${YourProductKey}.iot-as-mqtt.${Region}.aliyuncs.com:1883 101 | * Region:https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.11186623.2.14.6cba65fekp352N 102 | * ClientId: 表示客户端ID,建议使用设备的MAC地址或SN码,64字符内 ,clientId+"|securemode=3,signmethod=hmacsha1,timestamp=132323232|" 103 | * Username: deviceName+"&"+productKey 104 | * Password: sign_hmac(deviceSecret,content) 105 | * securemode:表示目前安全模式,可选值有2 (TLS直连模式)和3(TCP直连模式) 106 | * signmethod:签名算法,支持hmacmd5,hmacsha1,hmacsha256和 sha256,默认为 hmacsha1。 107 | * timestamp: 可选 108 | */ 109 | genConnectPrarms() { 110 | const lang = getSDKLanguage(); 111 | const extra = `lan=${lang},_v=${packageJson.version}|` 112 | // console.log('extra',extra); 113 | // const extra ='' 114 | let params = { 115 | clientId:`${this.clientId}|securemode=${this.securemode },signmethod=hmac${this.signAlgorithm},timestamp=${this.timestamp},${extra}`, 116 | username: `${this.deviceName}&${this.productKey}`, 117 | password: hmacSign( 118 | this.signAlgorithm, 119 | this.deviceSecret, 120 | `clientId${this.clientId}deviceName${this.deviceName}productKey${ 121 | this.productKey 122 | }timestamp${this.timestamp}` 123 | ), 124 | keepalive: this.keepalive, 125 | clean: this.clean, 126 | } 127 | 128 | // 支付宝小程序api全局对象my 129 | if(lang==='JS|Ali'){ 130 | params.my = my; 131 | } 132 | return params; 133 | } 134 | 135 | // 初始化连接参数 136 | init(config) { 137 | // 判断是否使用加密模式 138 | if ( 139 | (config.brokerUrl && tlsPrefix.some(prefix => config.brokerUrl.startsWith(prefix))) || 140 | (config.protocol && tlsPrefix.some(prefix => config.protocol.startsWith(prefix))) || 141 | config.tls 142 | ) { 143 | this.securemode = 2; 144 | this.tls = true; 145 | } else { 146 | this.securemode = 3; 147 | } 148 | // 浏览器使用433端口,非浏览器使用1883端口 149 | if (isBrowser) { 150 | if (this.tls) { 151 | this.brokerProtocol = 'wss://'; 152 | } else { 153 | this.brokerProtocol = 'ws://'; 154 | } 155 | this.brokerPort = 443; 156 | } else { 157 | if (this.tls) { 158 | this.brokerProtocol = 'mqtts://'; 159 | } else { 160 | this.brokerProtocol = 'mqtt://'; 161 | } 162 | this.brokerPort = 1883; 163 | } 164 | 165 | // 补充自定义protocol,支持微信小程序wxs://和阿里小程序alis:// 166 | if (config.protocol && (config.protocol.startsWith('wxs') || config.protocol.startsWith('alis'))) { 167 | // 支持wxs://和wxs两种传入模式 168 | this.brokerProtocol = config.protocol.indexOf('://')!=-1?config.protocol:config.protocol+"://"; 169 | this.brokerPort = 443; 170 | } 171 | 172 | //三元组忽略大小写 173 | Object.keys(config).forEach((originKey) => { 174 | let key = originKey.toLowerCase(); 175 | switch (key) { 176 | case "productkey": 177 | this.productKey = config[originKey]; 178 | break; 179 | case "devicename": 180 | this.deviceName = config[originKey]; 181 | break; 182 | case "devicesecret": 183 | this.deviceSecret = config[originKey]; 184 | break; 185 | } 186 | }); 187 | this.region = config.region || config.regionId; 188 | this.keepalive = config.keepalive || 60; //keepalive,默认60 189 | this.clean = config.clean || false; //cleanSession配置选项 190 | this.signAlgorithm = config.signAlgorithm || 'sha1'; 191 | this.config = config || {}; 192 | if (config.brokerUrl) { 193 | this.brokerUrl = config.brokerUrl; 194 | } else { 195 | this.brokerUrl = util.format( 196 | BROKER_URL, 197 | this.brokerProtocol, 198 | this.productKey, 199 | this.region || DEFAULT_REGION, 200 | this.brokerPort 201 | ); 202 | } 203 | this.timestamp = Date.now(); 204 | this.clientId = config.clientId ? 205 | `${config.clientId}` : 206 | `${this.productKey}&${this.deviceName}`; 207 | 208 | /* 初始化topic */ 209 | //methods 210 | this.POST_PROPERY_METHOD = METHODS_TEMPLATE.POST_PROPERY 211 | // this.POST_EVENT = util.format(METHODS_TEMPLATE.POST_EVENT, eventName); 212 | this.POST_TAGS_METHOD = METHODS_TEMPLATE.POST_TAGS; 213 | this.DELETE_TAGS_METHOD = METHODS_TEMPLATE.DELETE_TAGS; 214 | this.GET_CONFIG_METHOD = METHODS_TEMPLATE.GET_CONFIG; 215 | this.ADD_TOPO_METHOD = METHODS_TEMPLATE.ADD_TOPO; 216 | this.DELETE_TOPO_METHOD = METHODS_TEMPLATE.DELETE_TOPO; 217 | this.GET_TOPO_METHOD = METHODS_TEMPLATE.GET_TOPO; 218 | this.SUBDEVICE_REGISTER_METHOD = METHODS_TEMPLATE.SUBDEVICE_REGISTER; 219 | 220 | //topic 221 | const _formatWithPKDN = template => util.format(template, this.productKey, this.deviceName); 222 | this.POST_PROPS_TOPIC = _formatWithPKDN(BROKER_TOPICS.PROPERTY_POST_TOPIC) 223 | this.POST_PROPS_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.PROPERTY_POST_REPLY_TOPIC) 224 | this.ONSET_PROPS_TOPIC = _formatWithPKDN(BROKER_TOPICS.ONSET_PROPS_TOPIC) 225 | 226 | // this.POST_EVENT_TOPIC = _formatWithPKDN(BROKER_TOPICS.EVENT_POST_TOPIC) 227 | // this.POST_EVENT_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.EVENT_POST_REPLY_TOPIC) 228 | this.SERVICE_TOPIC = _formatWithPKDN(BROKER_TOPICS.SERVICE_TOPIC) 229 | this.TAG_TOPIC = _formatWithPKDN(BROKER_TOPICS.TAG_TOPIC) 230 | this.TAG_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.TAG_REPLY_TOPIC) 231 | this.TAG_DELETE_TOPIC = _formatWithPKDN(BROKER_TOPICS.TAG_DELETE_TOPIC) 232 | this.TAG_DELETE_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.TAG_DELETE_REPLY_TOPIC) 233 | this.CONFIG_TOPIC = _formatWithPKDN(BROKER_TOPICS.CONFIG_TOPIC) 234 | this.CONFIG_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.CONFIG_REPLY_TOPIC) 235 | this.CONFIG_SUBSCRIBE_TOPIC = _formatWithPKDN(BROKER_TOPICS.CONFIG_SUBSCRIBE_TOPIC) 236 | this.CONFIG_SUBSCRIBE_RESP_TOPIC = _formatWithPKDN(BROKER_TOPICS.CONFIG_SUBSCRIBE_RESP_TOPIC) 237 | this.SHADOW_SUBSCRIBE_TOPIC = _formatWithPKDN(BROKER_TOPICS.SHADOW_SUBSCRIBE_TOPIC) 238 | this.SHADOW_GET_TOPIC = _formatWithPKDN(BROKER_TOPICS.SHADOW_GET_TOPIC) 239 | this.SHADOW_POST_TOPIC = _formatWithPKDN(BROKER_TOPICS.SHADOW_POST_TOPIC) 240 | this.RRPC_REQ_TOPIC = _formatWithPKDN(BROKER_TOPICS.RRPC_REQ_TOPIC) 241 | 242 | // gateway 243 | this.ADD_TOPO_TOPIC = _formatWithPKDN(BROKER_TOPICS.ADD_TOPO_TOPIC) 244 | this.ADD_TOPO_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.ADD_TOPO_REPLY_TOPIC) 245 | this.DELETE_TOPO_TOPIC = _formatWithPKDN(BROKER_TOPICS.DELETE_TOPO_TOPIC) 246 | this.DELETE_TOPO_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.DELETE_TOPO_REPLY_TOPIC) 247 | this.GET_TOPO_TOPIC = _formatWithPKDN(BROKER_TOPICS.GET_TOPO_TOPIC) 248 | this.GET_TOPO_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.GET_TOPO_REPLY_TOPIC) 249 | this.LOGIN_TOPIC = _formatWithPKDN(BROKER_TOPICS.LOGIN_TOPIC) 250 | this.LOGIN_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.LOGIN_REPLY_TOPIC) 251 | this.LOGOUT_TOPIC = _formatWithPKDN(BROKER_TOPICS.LOGOUT_TOPIC) 252 | this.LOGOUT_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.LOGOUT_REPLY_TOPIC) 253 | this.SUBDEVICE_REGISTER_TOPIC = _formatWithPKDN(BROKER_TOPICS.SUBDEVICE_REGISTER_TOPIC) 254 | this.SUBDEVICE_REGISTER_REPLY_TOPIC = _formatWithPKDN(BROKER_TOPICS.SUBDEVICE_REGISTER_REPLY_TOPIC) 255 | } 256 | 257 | configchecking() { 258 | if (typeof this.productKey === 'undefined') { 259 | throw new Error('productKey should not be empty'); 260 | } 261 | if (typeof this.deviceName === 'undefined') { 262 | throw new Error('deviceName should not be empty'); 263 | } 264 | if (typeof this.deviceSecret === 'undefined') { 265 | throw new Error('deviceSecret should not be empty'); 266 | } 267 | } 268 | } 269 | 270 | module.exports = Model; 271 | -------------------------------------------------------------------------------- /src/thing.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('mqtt'); 2 | const EventEmitter = require('events'); 3 | const util = require('util'); 4 | 5 | const { 6 | createGuid, 7 | createDebug, 8 | getIP, 9 | isJsonString, 10 | mqttMatch, 11 | mqttNotMatch 12 | } = require('./utils'); 13 | const debug = createDebug('device'); 14 | const guid = createGuid(); 15 | const packagejson = require('../package.json'); 16 | const Model = require('./model'); 17 | const nilFn = ()=>{}; 18 | 19 | class Thing extends EventEmitter { 20 | 21 | constructor(config = {},mqttClient,type) { 22 | super(); 23 | // 设备类型:device,subdevice,gateway 24 | this._type = this.constructor.name || "UNKNOW"; 25 | // console.log("thing type:",this._type); 26 | //init model 27 | this.model = new Model(config); 28 | this.serveCB = []; 29 | this._onShadowCB = nilFn; 30 | this._onConfigCB = nilFn; 31 | // props set callback 32 | this._onPropsCB = nilFn; 33 | //兼容旧版本 34 | this._compatibleoverdue(); 35 | } 36 | 37 | // 发布消息到topic 38 | publish(...args) { 39 | this._mqttClient.publish(...args); 40 | } 41 | // 订阅消息 42 | subscribe(...args) { 43 | this._mqttClient.subscribe(...args); 44 | } 45 | // 取消订阅消息 46 | unsubscribe(...args) { 47 | this._mqttClient.unsubscribe(...args); 48 | } 49 | end(...args) { 50 | this._mqttClient.end(...args); 51 | } 52 | 53 | // 属性快捷获取 54 | get connected() { 55 | return this._mqttClient.connected; 56 | } 57 | get mqttClient() { 58 | return this._mqttClient; 59 | } 60 | 61 | 62 | /* 63 | 高级版设备属性上报:详细文档地址:https://help.aliyun.com/document_detail/89301.html?spm=a2c4g.11186623.6.660.3ad223dcF1jjSU 64 | "params": {"key": "key","value": "value"} 65 | */ 66 | postProps(props = {}, cb) { 67 | this._publishAlinkMessage({ 68 | method: this.model.POST_PROPERY_METHOD, 69 | pubTopic: this.model.POST_PROPS_TOPIC, 70 | expectReplyTopic:this.model.POST_PROPS_REPLY_TOPIC, 71 | params: props 72 | }, cb); 73 | } 74 | 75 | // 当云端属性设置时触发 76 | onProps(cb) { 77 | this._onPropsCB = cb; 78 | } 79 | 80 | /* 81 | 高级版设备事件上报:详细文档地址:https://help.aliyun.com/document_detail/89301.html?spm=a2c4g.11186623.6.660.3ad223dcF1jjSU 82 | "params": {"key": "key","value": "value"} 83 | */ 84 | postEvent(eventName, params = {}, cb = () => {}) { 85 | this._publishAlinkMessage({ 86 | method: this.model.getPostEventMethod(eventName), 87 | pubTopic: this.model.getPostEvenTopic(eventName), 88 | expectReplyTopic:this.model.getPostEvenReplyTopic(eventName), 89 | params 90 | }, cb); 91 | } 92 | 93 | /* 94 | 高级版设备服务监听:详细文档地址:https://help.aliyun.com/document_detail/89301.html?spm=a2c4g.11186623.6.660.3ad223dcF1jjSU 95 | "params": 96 | serviceName,cb 97 | */ 98 | onService(serviceName, cb) { 99 | if (this._addServiceListenerFn == undefined) { 100 | this._addServiceListenerFn = this._wrapServiceSubscribe(serviceName, cb); 101 | } 102 | const reply = this._wrapReplyServiceFn(serviceName); 103 | const newCb = (res)=>{ 104 | cb(res,reply) 105 | } 106 | this._addServiceListenerFn(); 107 | this._pushReceiveServiceCallback(serviceName, newCb); 108 | } 109 | 110 | // 封装服务响应的reply函数 111 | _wrapReplyServiceFn(serviceName){ 112 | /* 113 | 回应云端调用服务的接口 114 | params的数据结构:{ 115 | id:xxx 116 | "code": 200, 117 | "data": { 118 | output:xxx 119 | } 120 | */ 121 | const fn = (params,type='async') => { 122 | if(params.id == undefined) { params.id = guid() } 123 | let topic; 124 | // 同步调用和异步调用 125 | if(type=='async'){ 126 | topic = this.model.getServiceRespTopic(serviceName); 127 | } else { 128 | const rrpcid = this._getServiceRRPCID(serviceName); 129 | topic = this.model.getRRPCRespTopic(rrpcid); 130 | } 131 | // console.log('topic:',topic); 132 | // console.log('params',params); 133 | this._publishMessage(topic,params); 134 | } 135 | return fn; 136 | } 137 | 138 | 139 | /* 140 | 设备标签上报:详细文档地址:https://help.aliyun.com/document_detail/89304.html?spm=a2c4g.11174283.6.662.7e9d16685aIS1k 141 | "params": [{"attrKey": "key","attrValue": "value"},{},{}] 142 | 每次上报不会清除上一次的tag 143 | */ 144 | postTags(tags = [], cb = () => {}) { 145 | this._publishAlinkMessage({ 146 | method: this.model.POST_TAGS_METHOD, 147 | pubTopic: this.model.TAG_TOPIC, 148 | params: tags 149 | }, cb); 150 | } 151 | // 删除设备标签 152 | deleteTags(tags = [], cb) { 153 | let params = tags.map((tag) => { 154 | return { 155 | attrKey: tag 156 | } 157 | }) 158 | this._publishAlinkMessage({ 159 | method: this.model.DELETE_TAGS_METHOD, 160 | pubTopic: this.model.TAG_DELETE_TOPIC, 161 | params: params 162 | }, cb); 163 | } 164 | 165 | /* 166 | * 获取远程配置 167 | * 技术文档地址:https://help.aliyun.com/document_detail/89308.html?spm=a2c4g.11186623.6.666.53e168d0joDPCn 168 | * 返回数据格式 example 169 | { 170 | code: 200, 171 | data: 172 | { configId: '3cfda5091d5b4f53b51621cf4bbf86ec', 173 | configSize: 16, 174 | getType: 'file', 175 | sign: 'xxx', 176 | signMethod: 'Sha256', 177 | url: 'https://otx-devicecenter-thing-config-cn-shanghai-online.oss-cn-shanghai.aliyuncs.com/xxxxxx' }, 178 | id: '3', 179 | method: 'thing.config.get', 180 | version: '1.0' 181 | } 182 | */ 183 | getConfig(cb = () => {}) { 184 | // 目前只支持产品维度和文件类型 185 | const params = { 186 | "configScope": "product", 187 | "getType": "file" 188 | } 189 | this._publishAlinkMessage({ 190 | method: this.model.GET_CONFIG_METHOD, 191 | pubTopic: this.model.CONFIG_TOPIC, 192 | params: params 193 | }, cb); 194 | } 195 | 196 | //当远程配置下发到设备触发 197 | onConfig(cb) { 198 | this._onConfigCB = cb; 199 | } 200 | 201 | // 获取设备影子数据 202 | getShadow() { 203 | const msg = { 204 | "method": "get" 205 | } 206 | this._publishMessage(this.model.SHADOW_GET_TOPIC, msg); 207 | } 208 | 209 | // 设备上报实际值 210 | postShadow(reported) { 211 | const msg = { 212 | "method": "update", 213 | "state": { 214 | "reported": reported 215 | }, 216 | "version": Date.now() 217 | } 218 | this._publishMessage(this.model.SHADOW_POST_TOPIC, msg) 219 | } 220 | 221 | // 删除影子设备 222 | deleteShadow(keys) { 223 | let reported = {}; 224 | if (typeof keys == 'string') { 225 | const key = keys; 226 | reported[key] = "null" 227 | } 228 | if (typeof keys == "object") { 229 | keys.forEach((key) => { 230 | reported[key] = "null" 231 | }); 232 | 233 | } 234 | if (keys == undefined) { 235 | reported = "null" 236 | } 237 | const msg = { 238 | "method": "delete", 239 | "state": { 240 | reported 241 | }, 242 | "version": Date.now() 243 | } 244 | this._publishMessage(this.model.SHADOW_POST_TOPIC, msg) 245 | 246 | } 247 | // 注册影子设备监听 248 | onShadow(cb) { 249 | this._onShadowCB = cb; 250 | } 251 | 252 | // 发送普通消息 253 | _publishMessage(pubTopic, msg = {}, qos = 0) { 254 | const payload = JSON.stringify(msg); 255 | this.publish(pubTopic, payload, { 256 | qos 257 | }, (err, res) => { 258 | if (err) { 259 | // console.log('publish error', pubTopic, msg.id, err, res) 260 | } 261 | }); 262 | } 263 | 264 | // 发送alink协议消息 265 | _publishAlinkMessage({ 266 | method = "", 267 | pubTopic, 268 | params, 269 | expectReplyTopic, 270 | timeout, 271 | msgId 272 | }, cb) { 273 | 274 | const id = this._genAlinkMessageId(msgId,expectReplyTopic); 275 | //暂存回调函数 276 | this._pushCallback(id, cb); 277 | const msg = this.model.genAlinkContent(method, params, id); 278 | const payload = JSON.stringify(msg); 279 | // console.log("_publishAlinkMessage:",payload,pubTopic); 280 | this.publish(pubTopic, payload, (err, res) => { 281 | debug('pub callback', pubTopic, msg.id, err, res); 282 | // console.log('pub callback', pubTopic, msg.id, err, res); 283 | if (err) { 284 | debug('publish error', pubTopic, msg.id, err, res) 285 | } 286 | }); 287 | } 288 | 289 | _genAlinkMessageId(originID,expect){ 290 | let msgId; 291 | const separator = '|exp-topic|'; 292 | if(!originID){ 293 | msgId = guid(); 294 | } 295 | // 解决部分topic响应多次的问题 296 | if(expect){ 297 | msgId = msgId + separator + expect; 298 | } 299 | return msgId; 300 | } 301 | 302 | _subscribeClientEvent(client = this._mqttClient){ 303 | ['connect', 'error', 'close', 'reconnect', 'offline', 'message'].forEach(evtName => { 304 | this._mqttClient.on(evtName, (...args) => { 305 | debug(`mqtt client ${evtName}`); 306 | if (evtName === 'connect') { 307 | debug('mqtt connected'); 308 | this._subscribePresetTopic(); 309 | } 310 | // 事件流到设备端开发者lib中的方式有2中,通过subscribe和通过callback 311 | if (evtName === 'message') { 312 | // 1:处理subscribe通知 313 | this.emit(evtName,...args); 314 | // 2:处理callback通知 315 | this._mqttCallbackHandler(...args); 316 | return; 317 | } 318 | if (evtName === 'close') { 319 | // console.log("on close"); 320 | } 321 | // 其他事件 'connect', 'error', 'close', 'reconnect', 'offline'处理 322 | this.emit(evtName,args); 323 | }); 324 | }); 325 | } 326 | 327 | _createClient() { 328 | this._mqttClient = mqtt.connect(this.model.brokerUrl, this.model.genConnectPrarms()); 329 | } 330 | 331 | _subscribePresetTopic(thing=this) { 332 | //初始化只需要订阅 属性返回的topic和标签删除返回的topic,事件topic需要跟进event动态订阅,所以初始化不需要订阅 333 | [ 334 | // "/sys/#", 335 | // "/shadow/#", 336 | // "/ext/#" 337 | // devices 338 | thing.model.POST_PROPS_REPLY_TOPIC, 339 | thing.model.ONSET_PROPS_TOPIC, 340 | thing.model.getWildcardEvenTopic(), 341 | thing.model.TAG_REPLY_TOPIC, 342 | thing.model.TAG_DELETE_REPLY_TOPIC, 343 | thing.model.CONFIG_REPLY_TOPIC, 344 | thing.model.SHADOW_SUBSCRIBE_TOPIC, 345 | thing.model.CONFIG_SUBSCRIBE_TOPIC, 346 | thing.model.CONFIG_SUBSCRIBE_RESP_TOPIC, 347 | // gateway 348 | thing.model.ADD_TOPO_REPLY_TOPIC, 349 | thing.model.DELETE_TOPO_REPLY_TOPIC, 350 | thing.model.GET_TOPO_REPLY_TOPIC, 351 | thing.model.LOGIN_REPLY_TOPIC, 352 | thing.model.LOGOUT_REPLY_TOPIC, 353 | thing.model.SUBDEVICE_REGISTER_REPLY_TOPIC, 354 | thing.model.RRPC_REQ_TOPIC 355 | ].forEach((replyTopic) => { 356 | // console.log("subscribe topic>>>>>>", replyTopic); 357 | this.subscribe(replyTopic, { 358 | "qos": 1 359 | }, (error, res) => { 360 | // console.log(">>>>>> subscribe topic resp",error,res); 361 | if (error) { 362 | debug('sub error:', error.toString); 363 | } 364 | }) 365 | }) 366 | } 367 | // 处理内部message以及各种方法的回调 368 | _mqttCallbackHandler(topic,message) { 369 | // console.log('device _mqttCallbackHandler',topic,message); 370 | // console.log('message',JSON.parse(message.toString())); 371 | // console.log('topic',topic); 372 | 373 | // 几种不处理的情况 374 | // 情况1:回调函数为空 375 | if (this._cb==[] && this._serviceCB==[] && this._onShadowCB == nilFn && this._onConfigCB == nilFn ){ 376 | return; 377 | } 378 | // 情况2:返回值为非结构化数据(非结构化可能是:基础版产品或是用户自定义topic) 379 | if(isJsonString(message.toString())==false){ 380 | return; 381 | } 382 | // 开始处理返回值 383 | try { 384 | let res = JSON.parse(message.toString()); 385 | 386 | //处理On Props Set回调 387 | // topic /sys///thing/service/property/set 388 | if((mqttMatch(this.model.ONSET_PROPS_TOPIC,topic))){ 389 | this._onPropsCB(res); 390 | return; 391 | } 392 | 393 | //处理物模型服务订阅返回数据,同步或者异步方式 394 | if((mqttMatch(this.model.getWildcardServiceTopic(),topic) || mqttMatch(this.model.RRPC_REQ_TOPIC,topic)) && this._onReceiveService!=undefined){ 395 | // console.log("device mqttMatch(this.model.getWildcardServiceTopic"); 396 | this._onReceiveService(topic,res); 397 | return; 398 | } 399 | // 影子设备reply和云端或应用下发影子配置通知,很久之前cmp定义的方法名称,所以格式与其他名称不太相同 400 | if(mqttMatch(this.model.SHADOW_SUBSCRIBE_TOPIC,topic) && this._onShadowCB!=nilFn){ 401 | this._onShadowCB(res); 402 | return; 403 | } 404 | // 远程配置回调 405 | if( 406 | mqttMatch(this.model.getWildcardConfigTopic(),topic) && 407 | mqttNotMatch(this.model.CONFIG_REPLY_TOPIC,topic) && 408 | this._onConfigCB!=undefined 409 | ){ 410 | this._onConfigCB(res); 411 | return; 412 | } 413 | //其他通用回调 414 | let {id: cbID} = res; 415 | const callback = this._findCallback(cbID,topic); 416 | if(callback){callback(res);} 417 | 418 | } catch (e) { 419 | // console.log('_mqttCallbackHandler error',e) 420 | } 421 | } 422 | 423 | _findCallback(cbID,topic) { 424 | const separator = '|exp-topic|'; 425 | const msgTopic = cbID.split(separator)[1]; 426 | if(msgTopic && (msgTopic!=topic)){ 427 | return; 428 | } 429 | // 查找回调函数,找到后删除 430 | const cb = this._getCallbackById(cbID); 431 | delete this._cb[cbID]; 432 | return cb; 433 | 434 | // if(cbID.indexOf(separator)>0 ){ 435 | // console.log("cbID>>>>:",cbID); 436 | // console.log("cbID.split",cbID.split(separator)[1]); 437 | // if(cbID.split(separator)[1] != topic){ 438 | // return; 439 | // } 440 | // } 441 | } 442 | // // 查找回调函数,找到后使 443 | // _popCallback(cbID) { 444 | // const cb = this._getCallbackById(cbID); 445 | // delete this._cb[cbID]; 446 | // return cb; 447 | // } 448 | 449 | _wrapServiceSubscribe(serviceName, cb) { 450 | let subscription; 451 | const fn = () => { 452 | //初始化 453 | if (subscription == undefined) { 454 | subscription = {} 455 | }; 456 | // 查找是否存在 457 | if (subscription.serviceName == undefined) { 458 | this.subscribe(this.model.getServiceTopic(serviceName), (error, res) => { 459 | if (error) { 460 | debug('sub error:', res); 461 | } 462 | }); 463 | subscription.serviceName = true; 464 | } 465 | } 466 | return fn; 467 | } 468 | 469 | //处理接收云端下发服务调用 470 | _onReceiveService(topic,res) { 471 | // console.log("_onReceiveService",this.model.deviceName) 472 | const { 473 | method 474 | } = res; 475 | let serviceName = method.split('.').pop(); 476 | let cb = this._serviceCB[serviceName] || (() => {}); 477 | // 如果是rrpc的方式产生的服务同步调用,需要记录服务的id 478 | if(mqttMatch(this.model.RRPC_REQ_TOPIC,topic)){ 479 | const rrpcid = topic.split('/').pop(); 480 | this._pushReceiveServiceRRPCID(serviceName,rrpcid); 481 | } 482 | cb(res); 483 | } 484 | _pushCallback(msgid, fn) { 485 | // 初始化回调函数数组 486 | if (this._cb == undefined) { 487 | this._cb = [] 488 | }; 489 | this._cb[msgid] = fn; 490 | } 491 | _getCallbackById(msgid) { 492 | // 初始化回调函数数组 493 | if (this._cb == undefined) { 494 | this._cb = [] 495 | }; 496 | return this._cb[msgid]; 497 | } 498 | _pushReceiveServiceCallback(serviceName, fn) { 499 | // 初始化回调函数数组 500 | if (this._serviceCB == undefined) { 501 | this._serviceCB = [] 502 | }; 503 | this._serviceCB[serviceName] = fn; 504 | } 505 | _pushReceiveServiceRRPCID(serviceName, rrpdid) { 506 | // 初始化回调函数数组 507 | if (this._serviceRRPCID == undefined) { 508 | this._serviceRRPCID = [] 509 | }; 510 | this._serviceRRPCID[serviceName] = rrpdid; 511 | } 512 | _getServiceRRPCID(serviceName) { 513 | if(this._serviceRRPCID && this._serviceRRPCID[serviceName]){ 514 | return this._serviceRRPCID[serviceName] 515 | } 516 | return undefined; 517 | } 518 | 519 | _compatibleoverdue() { 520 | 521 | } 522 | } 523 | 524 | 525 | 526 | module.exports = Thing; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alibabacloud IoT Device SDK for Javascript 2 | > 阿里云IoT官方版本 3 | 4 | alibabacloud IoT Device SDK提供设备接入阿里云IoT物联网平台(LinkPlatform)的JavaScript版本的sdk,可以运行在node,broswer,微信小程序,支付宝小程序环境,封装LinkPlatform物联网平台的设备端能力,如设备连接云平台,数据pub,sub的上下行通讯。还有许多高级功能,如影子设备,远程配置,基于设备物模型(属性、服务、事件)的开发模式,网关和子设备的能力等,基于SDK的设备端开发或设备应用开发,可以极大简化开发门槛。 5 | 6 | 如果有使用问题可以反馈到xuanyan.lyw@alibaba-inc.com,关于IoT物联网平台更多功能和功能详细说明,参考官网文档 [https://help.aliyun.com/product/30520.html](https://help.aliyun.com/product/30520.html) 7 | 8 | 9 | ## 优点 10 | 11 | - 阿里云物联网平台js版本官方sdk 12 | - 支持node、broswer、微信小程序、支付宝小程序环境运行 13 | - 体积小,压缩版本仅有418k 14 | - 已为数十万设备或小程序提供链接服务 15 | 16 | ## 安装 17 | 18 | > 安装 Node.js 运行环境,版本 `>=4.0.0` 。 19 | 20 | 通过 npm 包管理工具安装: 21 | 22 | ```bash 23 | npm install alibabacloud-iot-device-sdk --save 24 | ``` 25 | 26 | 当前稳定版本 1.2.4 27 | 当前最新版本 1.2.8 28 | 29 | ## 特别注意 30 | 31 | 支付宝小程序使用注意: 32 | 支付宝最新版本容器升级后导致支付宝小程序无法使用 'alibabacloud-iot-device-sdk.js' ,请使用支付宝定制版本 'alibabacloud-iot-device-sdk-1.2.8-alimin-compatible.js',请在dist目录下下载 33 | 34 | ## 快速开始 35 | 36 | ```javascript 37 | // node引入包名 38 | const iot = require('alibabacloud-iot-device-sdk'); 39 | // 浏览器、微信小程序,支付宝小程序引入./dist编译的js文件 40 | // const iot = require('./dist/alibabacloud-iot-device-sdk.js'); 41 | // 特别注意:支付宝最新版本容器升级后导致支付宝小程序无法使用 'alibabacloud-iot-device-sdk.js' ,请使用支付宝定制版本 'alibabacloud-iot-device-sdk-1.2.8-alimin-compatible.js',请在dist目录下下载 42 | // js版本下载地址: 43 | // https://github.com/aliyun/alibabacloud-iot-device-sdk/tree/master/dist 或 44 | // alibabacloud-iot-device-sdk.js 下载地址 https://unpkg.com/alibabacloud-iot-device-sdk@1.2.4/dist/alibabacloud-iot-device-sdk.js 或 45 | // alibabacloud-iot-device-sdk.min.js 下载地址 https://unpkg.com/alibabacloud-iot-device-sdk@1.2.4/dist/alibabacloud-iot-device-sdk.min.js 46 | // 47 | 48 | const device = iot.device({ 49 | productKey: '', 50 | deviceName: '', 51 | deviceSecret: '', 52 | // 支付宝小程序和微信小程序额外需要配置协议参数 53 | // "protocol": 'alis://', "protocol": 'wxs://', 54 | }); 55 | device.subscribe('///get'); 56 | device.on('connect', () => { 57 | console.log('connect successfully!'); 58 | device.publish('///update', 'hello world!'); 59 | }); 60 | device.on('message', (topic, payload) => { 61 | console.log(topic, payload.toString()); 62 | }); 63 | ``` 64 | 65 | ## 物模型使用 66 | 67 | LinkPlatform 封装了物模型定义与 Alink 异步协议,SDK 封装使得设备与云端通信时不需要关心 MQTT topic,只需要调用属性上报(iot.device#postProps())、服务监听(iot.device#onService())、事件上报(iot.device#postEvent())等相关 API。 68 | 69 | ### 设备属性上报 70 | 71 | ```javascript 72 | device.on('connect', () => { 73 | device.postProps({ 74 | LightSwitch: 0 75 | }, (res) => { 76 | console.log(`postProps:`,res); 77 | }); 78 | }); 79 | 80 | ``` 81 | 82 | 调用 `device.postProps()` 等同于执行以下代码: 83 | ```javascript 84 | // 发布属性上报 topic 85 | device.publish('/sys///thing/event/property/post', JSON.stringify({ 86 | { 87 | id: msgId, 88 | version: '1.0', 89 | params: { 90 | 'LightSwitch': 25, 91 | }, 92 | method: 'thing.event.property.post' 93 | } 94 | })); 95 | 96 | // 监听属性上报响应 topic 97 | device.subscribe('/sys///thing/event/property/post_reply'); 98 | device.on('message', function(topic, message){ 99 | const res = message.toString(); 100 | if (res.id === msgId) { 101 | // 在这里处理服务端响应 102 | console.log(res.data); 103 | } 104 | }); 105 | ``` 106 | 107 | ### 监听云端下发的服务调用消息 108 | 109 | ```javascript 110 | // 监听云端设置属性wakeup_async服务消息 111 | device.onService('wakeup_async', function (res,reply) { 112 | // 处理服务参数 113 | console.log('wakeup_async',res); 114 | // 返回云端异步服务处理消息 115 | reply({ 116 | "code": 200, 117 | "data": {out:1} 118 | }); 119 | }); 120 | ``` 121 | 122 | 其他更多功能,请查api列表,值得注意的是,更多示例,见源码example文件夹 123 | - device继承自mqtt.js的EventEmitter,可以使用其全部方法 124 | - gateway可以使用device的全部api 125 | - subDevice也可以使用device的全部api 126 | 127 | 128 | ## API 129 | 130 | 设备相关 131 | * iot.device() 132 | * iot.device#publish() 133 | * iot.device#subscribe() 134 | * iot.device#unsubscribe() 135 | * iot.device#postProps() 136 | * iot.device#onProps() 137 | * iot.device#postEvent() 138 | * iot.device#onService() 139 | * iot.device#end() 140 | * iot.postTags#postTags() 141 | * iot.deleteTags#deleteTags() 142 | * iot.getConfig#getConfig() 143 | * iot.onConfig#onConfig 144 | * iot.onShadow#onShadow() 145 | 146 | * iot.getShadow#getShadow() 147 | * iot.postShadow#postShadow() 148 | * iot.deleteShadow#deleteShadow() 149 | 150 | 网关相关(网关也可以使用设备相关的api) 151 | * iot.gateway() 152 | * iot.gateway#addTopo() 153 | * iot.gateway#getTopo() 154 | * iot.gateway#removeTopo() 155 | * iot.gateway#login() 156 | * iot.gateway#logout() 157 | 158 | 159 | 子设备 160 | * 与设备api相同,通过网关的login()方法返回子设备实例 161 | 162 | 动态注册 163 | * iot(register() 164 | * iot.gateway#regiestSubDevice() 165 | 166 | 167 | 168 | 169 | ### iot.device(options) 170 | 171 | 和云端建立连接,返回一个 `Device` 连接实例,入参: 172 | 173 | * `options` 174 | * `productKey` (`String`) 175 | * `deviceName` (`String`) 176 | * `deviceSecret` (`String`) 177 | * `region` (`String`) 阿里云 region,默认值:cn-shanghai 178 | * `tls` (`Bool`) 是否开启 TLS 加密,Node.js 中如果开启将使用 TLS 协议进行连接,浏览器如果开启将上使用 WSS 协议 179 | * `keepalive` (`int`) 心跳报文时间间隔,默认值60秒 180 | * `clean` (`bool`) cleansession,是否清除连接session设置,默认值false 181 | 182 | ````js 183 | const device = iot.device({ 184 | productKey: '', 185 | deviceName: '', 186 | deviceSecret: '' 187 | }); 188 | ```` 189 | 190 | ### Event `'connect'` 191 | 192 | `function(connack) {}` 193 | 194 | 当连接到云端成功时触发。 195 | 196 | ````javascript 197 | const iot = require('alibabacloud-iot-device-sdk'); 198 | 199 | const device = iot.device({ 200 | productKey: '', 201 | deviceName: '', 202 | deviceSecret: '' 203 | }); 204 | 205 | device.on('connect', () => { 206 | console.log('connected!'); 207 | }); 208 | ```` 209 | 210 | ### Event `'message'` 211 | 212 | `function(topic, message) {}` 213 | 214 | 当接受到云端消息时触发,回调函数参数: 215 | 216 | * `topic` 消息主题 217 | * `message` 消息 payload 218 | 219 | 220 | ````js 221 | device.on('message', (res) => { 222 | console.log('msg:',res); 223 | }); 224 | ```` 225 | 226 | ### Event `'error'` 227 | 228 | 注意:由于nodejs的event机制,如果未监听error事件,当出现错误时会throw一个error,未try catch会导致程序终止,建议error事件需要监听
229 | 230 | node对于这个error处理的解释: 231 | When an error occurs within an EventEmitter instance, the typical action is for an 'error' event to be emitted. These are treated as special cases within Node.js. If an EventEmitter does not have at least one listener registered for the 'error' event, and an 'error' event is emitted, the error is thrown, a stack trace is printed, and the Node.js process exits. 原文地址:https://nodejs.org/api/events.html 232 | 233 | ````js 234 | device.on('error', (err) => { 235 | console.log('error:',err); 236 | }); 237 | ```` 238 | 239 | 当连接出错触发 240 | 241 | 242 | 243 | ### iot.device#publish(topic, message, [options], [callback]) 244 | 245 | * `topic` :topic值 `String` 类型 246 | * `message` 需要发送的消息, 数据格式为`Buffer or String` 类型 247 | * `options` 248 | * `qos` qos级别 `number` 类型 ,默认值0 249 | * `retain` 250 | * `dup` 251 | * `callback` 252 | 253 | ````js 254 | device.publish('///update', 'hello world!'); 255 | device.publish('///update', 'hello world!',{ 256 | qos:1 // default 0 257 | }); 258 | ```` 259 | 260 | 261 | 262 | ### iot.device#subscribe(topic, [callback]) 263 | 264 | 订阅消息,等同于 [mqtt.Client#subscribe()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#subscribe) 方法。 265 | 266 | 267 | 268 | ### iot.device#unsubscribe(topic, [callback]) 269 | 270 | 取消订阅消息等同于 [mqtt.Client#unsubscribe()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#unsubscribe) 方法。 271 | 272 | 273 | 274 | ### iot.device#postProps(params, [callback]) 275 | 276 | 上报物模型属性: 277 | 278 | * `params` 属性参数,`Object` 类型 279 | * `callback` 280 | * `res` 服务端 reply 消息内容 281 | 282 | 283 | 284 | ````js 285 | // 上报设备属性 286 | device.postProps({ 287 | LightSwitch: 0 288 | }, (res) => { 289 | console.log(res); 290 | }); 291 | ```` 292 | 293 | 294 | 295 | ### iot.device#onProps(cb) 296 | 297 | 上报物模型属性: 298 | 299 | * `refuns` 300 | * `cb` 回调函数 301 | 302 | 303 | 304 | ````js 305 | device.onProps((res)=>{ 306 | console.log('>>>onProps',res); 307 | }) 308 | ```` 309 | 310 | 311 | ### iot.device#postEvent(eventIdentifier, params, [callback]) 312 | 313 | 上报物模型事件: 314 | 315 | * `eventName` 事件名称 `String` 类型 316 | * `params` 事件参数,`Object` 类型 317 | * `callback` 318 | * `err` 错误,比如超时 319 | * `res` 服务端 reply 消息内容 320 | 321 | ````js 322 | // 上报设备事件 323 | device.postEvent("lowpower", { 324 | power: 10, 325 | }, (res) => { 326 | console.log(`postEvent:${res}`); 327 | }) 328 | ```` 329 | 330 | 331 | 332 | 333 | ### iot.device#onService(seviceIdentifier, [callback]) 334 | 335 | 监听物模型服务: 336 | 337 | * `seviceName` 服务名称,`String` 类型 338 | * `callback` 339 | * `res` 服务端返回参数 340 | * `reply` 响应服务的函数,可以使用同步可以异步方式响应 341 | 342 | relpy(params,[async or sync]) 343 | * `params` 响应返回的数据 344 | * `id` 返回服务端的消息id,可以不填会自动生成 345 | * `code` 响应服务端的code,200为成功 346 | * `data` 响应服务端的数据 347 | * `type` 响应服务的类型,根据选择物模型服务的类型,同步或异步,选填 'async' or 'sync' 348 | 349 | 350 | ```javascript 351 | // 假设物模型中有 wakeup_async的异步服务和wakeup_sync的同步服务,输出值都为out 352 | // 异步方式回复 353 | device.onService('wakeup_async', function (res,reply) { 354 | // 处理服务参数 355 | console.log('^onService',res); 356 | reply({ 357 | "code": 200, 358 | "data": {out:1} 359 | }); 360 | }) 361 | 362 | // 同步方式回复 363 | device.onService('wakeup_sync', function (res,reply) { 364 | // 处理服务参数 365 | console.log('^onService',res); 366 | reply({ 367 | "code": 200, 368 | "data": {out:1} 369 | },'sync'); 370 | }) 371 | ``` 372 | 373 | 374 | 375 | 376 | ### iot.device#end([force], [options], [callback]) 377 | 378 | 设备或网关断开连接,等同于 [mqtt.Client#end()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#end) 方法。 379 | 380 | 381 | 382 | ### iot.device#postTags(params, [callback]) 383 | 384 | 上报设备标签: 385 | 386 | * `params` 属性对象数组,`array` 类型,内容格式示例 [ {attrKey:'xxx',attrValue:'xxx'},{}...] 387 | * `attrKey` 错误,比如超时或者 `res.code !== 200` 388 | * `attrValue` 服务端 reply 消息内容 389 | * `callback` 390 | * `res` 服务端 reply 消息内容 391 | 392 | - 每次重新上报设备标签相同key会覆盖内容,不同key会增加标签 393 | - params 示例: 394 | 395 | ````js 396 | const tags = [ 397 | { 398 | "attrKey": "Temperature", 399 | "attrValue": "36.8" 400 | } 401 | ] 402 | device.postTags( 403 | tags, 404 | (res) => { 405 | console.log(`add tag ok res:${res.id}`); 406 | done() 407 | } 408 | ); 409 | ```` 410 | 411 | 412 | 413 | ### iot.device#deleteTags(tags) 414 | 415 | 删除设备标签: 416 | 417 | * `tags` 属性参数,`array` 类型,内容格式 [ 'string','string',....] 418 | * `string` 内外为tag的标签名称 419 | 420 | - 示例: 421 | ```javascript 422 | device.deleteTags(['tagA','tagB']); 423 | ``` 424 | 425 | 426 | 427 | ### iot.device#getConfig(callback) 428 | 429 | 获取设备远程配置: 430 | 431 | * `callback` 回调函数 432 | * res:当前设备的远程配置 433 | 434 | - 示例: 435 | ````javascript 436 | device.getConfig((res) => { 437 | console.log("getConfig:",res); 438 | }); 439 | ```` 440 | 441 | 442 | 443 | ### iot.device#onConfig(callback) 444 | 445 | 订阅设备远程配置,当云端修改远程配置时,设备端会收到消息: 446 | 447 | * `callback` 回调函数 448 | * res:当前设备的远程配置 449 | 450 | - 示例: 451 | ````javascript 452 | device.onConfig((res) => { 453 | console.log("onConfig,res:",res); 454 | }); 455 | ```` 456 | 457 | 458 | 459 | ### iot.device#onShadow(callback) 460 | 461 | 订阅设备影子回调函数方法: 462 | 463 | * `callback` 回调函数 464 | * res当影子设备变化或获取影子设备消息,上报影子消息时回调 465 | 466 | - 示例: 467 | ````javascript 468 | device.onShadow((res) => { 469 | console.log('获取最新设备影子,%o', res); 470 | }) 471 | ```` 472 | 473 | 474 | 475 | ### iot.device#getShadow() 476 | 477 | 获取设备影子最新: 478 | 479 | 480 | - 示例: 481 | ````javascript 482 | // 设备主动获取影子,回调函数会触发onShadow方法,返回设备影子信息 483 | device.getShadow(); 484 | ```` 485 | 486 | 487 | 488 | ### iot.device#postShadow(params) 489 | 490 | 上报设备影子数据 491 | 492 | * `params` 上报影子设备的实际值 493 | 494 | - 示例: 495 | ````javascript 496 | // 上报成功或错误都会触发onShadow方法,返回设备影子信息 497 | device.postShadow({ 498 | a: "avalue" 499 | }); 500 | ```` 501 | 502 | 503 | 504 | ### iot.device#deleteShadow(keys) 505 | 506 | 删除影子设备的属性值 507 | 508 | * `keys` 需要删除设备影子的属性的key数组 509 | * 除了数组外,如果传入单个key,可以删除单个属性,传入空会删除全部属性 510 | 511 | - 示例: 512 | 513 | ````javascript 514 | // 删除单个影子设备属性 515 | device.deleteShadow("a"); 516 | // 删除多个影子设备属性 517 | device.deleteShadow(["a","b"]); 518 | // 删除所有影子设备属性 519 | device.deleteShadow() 520 | ```` 521 | 522 | 523 | 524 | ### iot.gateway(options) 525 | 526 | 和云端建立连接,返回一个网关 `Gateway` 类连接实例,继承自 `Device`  类。网关可以使用设备的所有方法 527 | 528 | * `options` 529 | * `productKey` (`String`) 530 | * `deviceName` (`String`) 531 | * `deviceSecret` (`String`) 532 | * `region` (`String`) 阿里云 region,默认值:cn-shanghai 533 | * `tls` (`Bool`) 是否开启 TLS 加密,Node.js 中如果开启将使用 TLS 协议进行连接,浏览器如果开启将上使用 WSS 协议 534 | * `keepalive` (`int`) 心跳报文时间间隔,默认值60秒 535 | * `clean` (`bool`) cleansession,是否清除连接session设置,默认值false 536 | 537 | 538 | ````js 539 | const device = iot.gateway({ 540 | productKey: '', 541 | deviceName: '', 542 | deviceSecret: '' 543 | }); 544 | ```` 545 | 546 | 547 | 548 | ### iot.gateway#addTopo(deviceSign, [callback]) 549 | 550 | 添加子设备到拓扑 551 | 552 | * `params` 子设备三元组数组,[{productKey,deviceName,deviceSecret},{productKey,deviceName,deviceSecret},] 553 | * `callback` 554 | * `res` 服务端 reply 消息内容 555 | 556 | ````js 557 | gateway.addTopo( 558 | [sub_device1,sub_device2], 559 | (res)=>{console.log('>>>>>getTopo',res.message,res.data);} 560 | ); 561 | ```` 562 | 563 | 564 | 565 | ### iot.gateway#getTopo(callback) 566 | 567 | 添加子设备到拓扑关系 568 | 569 | * `callback` 570 | * `res` 服务端 reply 消息内容 571 | 572 | ````js 573 | gateway.getTopo( 574 | (res)=>{ 575 | console.log(res) 576 | } 577 | ); 578 | ```` 579 | 580 | 581 | 582 | ### iot.gateway#removeTopo(params, [callback]) 583 | 584 | 从拓扑关系里移除子设备 585 | 586 | * `params` 移除设备参数的数组,示例:[{"productKey": "xx","deviceName": "xx"},{"productKey": "xx","deviceName": "xx"},....] 587 | * `productKey` 588 | * `deviceName` 589 | * `callback` 590 | * `res` 服务端 reply 消息内容 591 | 592 | ````js 593 | gateway.removeTopo( 594 | [{"productKey": "xx","deviceName": "xx"},{"productKey": "xx","deviceName": "xx"}], 595 | (res)=>{ 596 | console.log('>>>>>getTopo') 597 | console.log(res.message) 598 | console.log(res.data) 599 | } 600 | ); 601 | ```` 602 | 603 | 604 | 605 | 606 | ### iot.gateway#login(params, [callback]) 607 | 608 | 子设备上线 609 | 610 | * `params` 登录的设备信息示例:{"productKey": "xx","deviceName": "xx",} 611 | * `productKey` (`String`) 612 | * `deviceName` (`String`) 613 | * `deviceSecret` (`String`) 614 | * `callback` 615 | * `res` 服务端 reply 消息内容 616 | * `返回值` 返回一个子设备,子设备可以使用设备的api,做相关的操作 617 | 618 | ````js 619 | 620 | gateway.on('connect', () => { 621 | //子设备登录ok 622 | sub1 = gateway.login( 623 | {"productKey":"xx","deviceName":"xx","deviceSecret":"xxx"}, 624 | (res) => { 625 | console.log('>>>>>login', res); 626 | } 627 | ); 628 | // 子设备连接状态 629 | sub1.on('connect', () => { 630 | console.log(">>>>sub connected!"); 631 | // do something 632 | sub1.postProps({ 633 | LightSwitch: 0 634 | },(res)=>{ 635 | console.log(">>>>sub postProps!"); 636 | console.log(res); 637 | }); 638 | }); 639 | }); 640 | ```` 641 | 642 | 643 | 644 | ### iot.gateway#logout(params, [callback]) 645 | 646 | 子设备下线 647 | 648 | * `params` 子设备身份 649 | * `productKey` (`String`) 650 | * `deviceName` (`String`) 651 | * `callback` 652 | * `res` 服务端 reply 消息内容 653 | 654 | ````js 655 | gateway.logout( 656 | {"productKey": "xxxxx","deviceName": "xxxxx"}, 657 | (res) => { 658 | console.log('>>>>>logout', res); 659 | } 660 | ```` 661 | 662 | 663 | 664 | ### iot#register(params, [callback]) 665 | 666 | 直连设备动态注册(小程序环境不支持此方法) 667 | 668 | * `params` 子设备身份 object 实例 productKey:"a15YDgQGhU0", 669 | * `productKey` 670 | * `productSecret` 671 | * `deviceName` 672 | * `callback` 673 | * `res` 服务端 reply 消息内容,包含设备三元组 674 | 675 | ````js 676 | const params = { 677 | productKey:"xxxxxx", 678 | productSecret:"xxxxxx", 679 | deviceName:"xxxxxx" 680 | } 681 | iot.register(params,(res)=>{ 682 | console.log("register:",res); 683 | if(res.code == '200'){ 684 | // 注册成功请保存设备三元组,只会返回一次 685 | } 686 | }) 687 | ```` 688 | 689 | 690 | 691 | ### iot.gateway#regiestSubDevice(params, [callback]) 692 | 693 | 通过网关注册子设备(小程序环境不支持此方法) 694 | 695 | * `params` 子设备身份信息,可以是单个`{productKey,deviceName}`或者是一组`[{productKey,deviceName},{productKey2,deviceName2}]`进行批量注册 696 | * `productKey` 697 | * `deviceName` 698 | * `callback` 699 | * `res` 服务端 reply 消息内容 700 | 701 | ````js 702 | const gateway = iot.gateway({ 703 | productKey: '', 704 | deviceName: '', 705 | deviceSecret: ''} 706 | ); 707 | gateway.on('connect', () => { 708 | gateway.regiestSubDevice([{"deviceName": "xxx","productKey": "xxx"}],(res)=>{ 709 | if(res.code == '200'){ 710 | // 注册成功请保存设备三元组,只会返回一次 711 | } 712 | }); 713 | }); 714 | ```` 715 | 716 | ## 平台返回res统一格式 717 | * `id` 发送请求的消息id,sdk中会自动生成 718 | * `message` 返回的消息 719 | * `data` 返回的数据 720 | * `code` 服务端返回消息的状态码,常见code如下 721 | * `200` 成功 722 | * `400` 内部服务错误, 处理时发生内部错误 723 | * `429` 请求过于频繁,设备端处理不过来时可以使用 724 | * `460` 请求参数错误 725 | * `520` 子设备会话不存在 726 | 727 | 728 | ## example说明 729 | > 注意: example中的iot引用,const iot = require('../lib');,如果在github工程以外使用,需要换成 const iot = require('alibabacloud-iot-device-sdk'); 730 | 731 | example见github开源工程中example目录 https://github.com/aliyun/alibabacloud-iot-device-sdk 732 | 733 | - broswer 浏览器中使用demo 路径 /example/broswer/index.html 734 | - quickstart 快速开始demo 路径 /example/quickstart 735 | - props 物模型-属性demo 路径 /example/props.js 736 | - event 物模型-事件demo 路径 /example/event.js 737 | - service_async 物模型-异步服务demo 路径 /example/service_async.js 738 | - service_sync 物模型-同步服务demo 路径 /example/service_sync.js 739 | - origin 基础mqtt pub,sub使用 路径 /example/origin.js 740 | - remote_confit_get 远程配置-主动获取 路径 /example/remote_confit_get.js 741 | - remote_confit_sub 远程配置-订阅获取 路径 /example/remote_confit_sub.js 742 | - tag 设备标签获取 路径 /example/tag.js 743 | - shadow 设备影子 路径 /example/shadow.js 744 | - one_model_one_secret 一型一密使用demo 路径 /example/one_model_one_secret.js 745 | - forward 通过规则引擎转发后订阅数据的示例 746 | 747 | 748 | 749 | 750 | 751 | 752 | ## 版本更新说明 753 | 754 | #### 1.2.8 755 | - 使用crypto-js替代原生的crypto做设备签名加密功能,浏览器和微信支付宝小程序的编译大小,min压缩版从661k减少到418k,体积减少37% 756 | 757 | 758 | #### 1.2.7 759 | - 增加onProps方法,用于监听云端对物模型属性设置的监听 760 | - 解决postEvent和postProps回调中内容不正确的问题 761 | 762 | #### 1.2.5 763 | - 修复网关使用mqtt pub和sub时字符转译报错的警告 764 | 765 | #### 1.2.4 稳定版本 766 | 767 | - 包名的修改,从 aliyun-iot-device-sdk 正式改名为 alibabacloud-iot-device-sdk 768 | - 增加对微信小程序,支付宝小程序的支持,浏览器的支持 [教程连接 ./docs](./docs) 769 | - 增加onService中reply函数,并支持同步和异步调用 770 | ```javascript 771 | // 假设物模型中有 wakeup_async的异步服务和wakeup_sync的同步服务,输出值都为out 772 | // 异步方式回复 773 | device.onService('wakeup_async', function (res,reply) { 774 | // 处理服务参数 775 | console.log('^onService',res); 776 | reply({ 777 | "code": 200, 778 | "data": {out:1} 779 | }); 780 | }) 781 | 782 | // 同步方式回复 783 | device.onService('wakeup_sync', function (res,reply) { 784 | // 处理服务参数 785 | console.log('^onService',res); 786 | reply({ 787 | "code": 200, 788 | "data": {out:1} 789 | },'sync'); 790 | }) 791 | ``` 792 | 793 | - 增加onConfig方法用于订阅云端远程配置更新 794 | - 增加部分功能的example 795 | - 重写了网关子设备subdevice的实现 796 | 797 | #### 1.0.1版本更新 798 | 799 | - 对核心代码进行了重构 800 | - 增加设备标签上报功能 801 | - 增加删除标签功能 802 | - 增加了设备动态注册功能 803 | - 增加设备影子相关功能 804 | - 增加获取设备配置功能 805 | - [非兼容性升级]网关设备之前的删除topo方法名和文档不一致,去掉了deleteTopo方法,统一使用removeTopo 806 | - [非兼容性升级]网关类方法,入参需要signUtil的逻辑去除,直接传入设备信息 807 | - [非兼容性升级]去掉signUtil方法导出 808 | - [非兼容性升级]初始化device和gateway时候签名方法参数从signAlgorithm改为signmethod 809 | - [非兼容性升级]设备的serve方法改成onService 810 | - [非兼容性升级]修改返回函数的res格式,取消err作为第一个参数,code,mssage都会放在res里面 811 | - [非兼容性升级]去掉网关设备的方法 postSubDeviceProps,postSubDeviceEvent,serveSubDeviceService,改成使用子设备调用 812 | ```` 813 | // iot.gateway#postSubDeviceProps() 814 | // iot.gateway#postSubDeviceEvent() 815 | // iot.gateway#serveSubDeviceService() 816 | 817 | const sub = gateway.login( 818 | sub_device1, 819 | (res)=>{console.log('>>>>>login',res);} 820 | ); 821 | 822 | sub.on('connect', () => { 823 | console.log(">>>>sub connected!"); 824 | sub.postProps({ 825 | LightSwitch: 0 826 | },(res)=>{ 827 | console.log(">>>>sub postProps!"); 828 | console.log(res); 829 | }); 830 | }); 831 | setTimeout(()=>{ 832 | gateway.logout( 833 | sub_device1, 834 | (res)=>{console.log('>>>>>logout',res);} 835 | ); 836 | },5000) 837 | ```` 838 | 839 | #### 0.3.1版本更新 840 | - productKey,deviceName,deviceSecret大小写经常容易忽略,新版本支持忽略大小写,也可以连接上 841 | 842 | #### 0.3.0版本更新 843 | 1:初始化连接option选择增加支持keepalive和clean(cleansession)配置 844 | 2:修改regionId为region,并兼容之前的regionId参数 845 | 846 | 847 | #### 研发中常见的问题 848 | 849 | 1:onMessage多次回调,示例如下 850 | 851 | ````js 852 | // 错误示例 853 | const device = iot.device({...,...}); 854 | device.on('connect', (res) => { 855 | device.on('message',(topic,payload) => { 856 | // you biz logic at here 857 | }); 858 | }); 859 | 860 | // 错误原因 device.on('message')委托放在 device.on('connecnt')中会导致多次绑定,产生message中的函数多次重复触发 861 | 862 | // 正确示例 863 | const device = iot.device({...,...}); 864 | device.on('connect', (res) => { 865 | 866 | }); 867 | device.on('message',(topic,payload) => { 868 | // you biz logic at here 869 | }); 870 | ```` 871 | 872 | --------------------------------------------------------------------------------