├── .babelrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── .vscode
└── settings.json
├── CLA.md
├── LICENSE
├── README.md
├── docs
└── index.html
├── package.json
├── samples
├── BatteryLife
│ ├── README.md
│ └── src
│ │ ├── BatteryLife.js
│ │ └── Config_BatteryLife.yaml
└── webpack
│ └── statusconsole
│ ├── .babelrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── index.html
│ ├── index.js
│ └── style.css
│ └── webpack.config.js
├── src
├── BaseClient.js
├── BaseConfig.js
├── api
│ ├── ApiClient.js
│ ├── ApiErrors.js
│ ├── DscClient.js
│ ├── LecClient.js
│ ├── MgmtClient.js
│ ├── RegistryClient.js
│ ├── RulesClient.js
│ └── StateClient.js
├── application
│ ├── ApplicationClient.js
│ ├── ApplicationConfig.js
│ └── index.js
├── device
│ ├── DeviceClient.js
│ ├── DeviceConfig.js
│ └── index.js
├── gateway
│ ├── GatewayClient.js
│ ├── GatewayConfig.js
│ └── index.js
├── index.js
└── util.js
└── test
├── ApplicationClient.spec.js
├── ApplicationConfig.spec.js
├── ApplicationConfigFile.spec.yaml
├── DeviceClient.spec.js
├── DeviceConfig.spec.js
├── DeviceConfigFile.spec.yaml
├── DscClient.spec.js
├── GatewayClient.spec.js
├── GatewayConfig.spec.js
├── StateClient.spec.js
└── incorrectLogLevelTest.spec.yaml
/.babelrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "presets": [ "@babel/preset-env" ]
4 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .settings
2 | node_modules
3 | coverage
4 | .idea
5 | .DS_Store
6 | dist/iotf-client-bundle.js
7 | dist/iotf-client-bundle.min.js
8 | package-lock.json
9 | IoTFoundation.pem
10 | src/util/IoTFoundation.pem
11 | dist
12 | .nyc_output
13 | instrumented-src
14 | npm-debug.log
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .git
2 | .idea
3 | .nyc_output
4 | .settings
5 | .travis.yml
6 | .vscode
7 | coverage
8 | docs
9 | node_modules
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | matrix:
4 | include:
5 | - node_js: "12"
6 | env: RUN_COVERAGE=true
7 | - node_js: "11"
8 |
9 | before_install:
10 | # Upgrade NPM, without this a number of modules will fail to install
11 | - npm i -g npm
12 |
13 | install:
14 | - npm install
15 |
16 | script:
17 | # - npm ls
18 | # Run test, optionally with coverage enabled. Return non-0 if tests fail
19 | - |
20 | if [ -n "$RUN_COVERAGE" ]; then
21 | if npm run test-cov; then
22 | cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
23 | exit 0
24 | fi
25 | else
26 | if npm run test; then
27 | exit 0
28 | fi
29 | fi
30 | exit 1
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "wcaForGP.enable": true
3 | }
--------------------------------------------------------------------------------
/CLA.md:
--------------------------------------------------------------------------------
1 | IBM Contributor License Agreement
2 | =================================
3 |
4 | Version 1.0.0 January 14, 2014
5 |
6 | In order for You (as defined below) to make intellectual property Contributions (as defined below) now or in the future to IBM GitHub repositories,
7 | You must agree to this Contributor License Agreement ("CLA").
8 |
9 | Please read this CLA carefully before accepting its terms. By accepting the CLA, You are agreeing to be bound by its terms.
10 | If You submit a Pull Request against an IBM repository on GitHub You must include in the Pull Request a statement of Your acceptance of this CLA.
11 |
12 | As used in this CLA:
13 | (i) "You" (or "Your") shall mean the entity that is making this Agreement with IBM;
14 | (ii)"Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is submitted by You to IBM for inclusion in,
15 | or documentation of, any of the IBM GitHub repositories;
16 | (iii) "Submit" (or "Submitted") means any form of communication sent to IBM (e.g. the content You post in a GitHub Issue or submit as part of a GitHub Pull Request).
17 |
18 | This agreement applies to all Contributions You Submit.
19 |
20 | This CLA, and the license(s) associated with the particular IBM GitHub repositories You are contributing to, provides a license to Your Contributions to IBM and downstream consumers,
21 | but You still own Your Contributions, and except for the licenses provided for in this CLA, You reserve all right, title and interest in Your Contributions.
22 |
23 | IBM requires that each Contribution You Submit now or in the future comply with the following four commitments.
24 |
25 | 1) You will only Submit Contributions where You have authored 100% of the content.
26 | 2) You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the
27 | Contributions.
28 | 3) Whatever content You Contribute will be provided under the license(s) associated with the particular IBM GitHub repository You are contributing to.
29 | 4) You understand and agree that IBM GitHub repositories and Your contributions are public, and that a record of the contribution (including all personal information You submit with it)
30 | is maintained indefinitely and may be redistributed consistent with the license(s) involved.
31 | You will promptly notify the Eclipse Foundation if You become aware of any facts or circumstances that would make these commitments inaccurate in any way.
32 | To do so, please create an Issue in the appropriate GitHub repository.
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License, Version 1.0 (EPL-1.0)
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IBM Watson IoT Platform Javascript SDK
2 |
3 | [](https://travis-ci.org/ibm-watson-iot/iot-nodejs)
4 | [](https://coveralls.io/github/ibm-watson-iot/iot-nodejs?branch=master)
5 | [](https://github.com/ibm-watson-iot/iot-nodejs/issues)
6 | [](https://github.com/ibm-watson-iot/iot-nodejs/blob/master/LICENSE)
7 |
8 |
9 |
10 | ## Installation
11 |
12 | ```
13 | npm install @wiotp/sdk --save
14 | ```
15 |
16 |
17 | ## Usage
18 |
19 | ### Application
20 |
21 | ```javascript
22 | import {ApplicationClient, ApplicationConfig} from '@wiotp/sdk';
23 |
24 | let appConfig = ApplicationConfig.parseEnvVars();
25 | let appClient = new ApplicationClient(appConfig);
26 | appClient.connect();
27 | // Do stuff
28 | appClient.disconnect();
29 | ```
30 |
31 | ### Device
32 |
33 | ```javascript
34 | import {DeviceClient, DeviceConfig} from '@wiotp/sdk';
35 |
36 | let deviceConfig = DeviceConfig.parseEnvVars();
37 | let deviceClient = new DeviceClient(deviceConfig);
38 | deviceClient.connect();
39 | // Do stuff
40 | deviceClient.disconnect();
41 | ```
42 |
43 | ### Gateway
44 |
45 | ```javascript
46 | import {GatewayClient, GatewayConfig} from '@wiotp/sdk';
47 |
48 | let gwConfig = GatewayConfig.parseEnvVars();
49 | let gwClient = new GatewayClient(gwConfig);
50 | gwClient.connect();
51 | // Do stuff
52 | gwClient.disconnect();
53 | ```
54 |
55 | ## Development
56 |
57 | ### Build
58 | ```
59 | npm i
60 | npm run build
61 | ```
62 |
63 | ## Publish
64 |
65 | ```
66 | npm login
67 | npm publish .
68 | ```
69 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | placeholder
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wiotp/sdk",
3 | "version": "0.8.2",
4 | "description": "SDK for developing device, gateway, and application clients for IBM Watson IoT Platform",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist",
8 | "src",
9 | "package.json",
10 | "README.md",
11 | "LICENCE"
12 | ],
13 | "publishConfig": {
14 | "access": "public"
15 | },
16 | "dependencies": {
17 | "axios": "^1.7.9",
18 | "bluebird": "^3.7.2",
19 | "btoa": "^1.2.1",
20 | "concat-stream": "^2.0.0",
21 | "esm": "^3.2.25",
22 | "events": "^3.3.0",
23 | "form-data": "^4.0.1",
24 | "format": "^0.2.2",
25 | "loglevel": "^1.9.2",
26 | "mqtt": "^4.3.8",
27 | "systeminformation": "^5.25.11",
28 | "tinycache": "^1.1.2",
29 | "uuid": "^9.0.1",
30 | "yaml": "2.6.1",
31 | "yargs": "^17.7.2"
32 | },
33 | "devDependencies": {
34 | "@babel/cli": "^7.26.4",
35 | "@babel/core": "^7.26.0",
36 | "@babel/preset-env": "^7.26.0",
37 | "@babel/register": "^7.25.9",
38 | "@ibm-cloud/cloudant": "^0.8.3",
39 | "@istanbuljs/nyc-config-babel": "^3.0.0",
40 | "babel-plugin-istanbul": "^6.1.1",
41 | "babelify": "^10.0.0",
42 | "browserify": "^17.0.1",
43 | "chai": "^4.5.0",
44 | "chai-as-promised": "^7.1.2",
45 | "coveralls": "3.1.1",
46 | "mocha": "^10.8.2",
47 | "mocha-steps": "^1.3.0",
48 | "nyc": "15.1.0",
49 | "rimraf": "^5.0.10",
50 | "terser": "^5.37.0"
51 | },
52 | "scripts": {
53 | "clean": "rimraf dist/*",
54 | "build": "npm run clean && npm run build:commonjs && npm run build:bundle && npm run build:min",
55 | "build:commonjs": "babel src --out-dir dist",
56 | "build:bundle": "browserify -t [ babelify --presets [ @babel/preset-env ] --global ] src --outfile dist/bundled/wiotp-bundle.js",
57 | "build:min": "terser dist/bundled/wiotp-bundle.js -o dist/bundled/wiotp-bundle.min.js",
58 | "test": "mocha --require @babel/register --require mocha-steps",
59 | "test-cov": "nyc --reporter=lcov --reporter=text-summary mocha --require mocha-steps",
60 | "test:watch": "mocha --require @babel/register --require mocha-steps --watch"
61 | },
62 | "author": {
63 | "name": "David Parker",
64 | "email": "parkerda@uk.ibm.com"
65 | },
66 | "contributors": [
67 | {
68 | "name": "Tom Klapiscak",
69 | "email": "klapitom@uk.ibm.com"
70 | },
71 | {
72 | "name": "Bryan Boyd",
73 | "email": "bboyd@us.ibm.com"
74 | },
75 | {
76 | "name": "Amritha Raj Herle",
77 | "email": "herleraja@gmail.com"
78 | }
79 | ],
80 | "repository": {
81 | "type": "git",
82 | "url": "git+https://github.com/ibm-watson-iot/iot-nodejs.git"
83 | },
84 | "license": "EPL-1.0",
85 | "nyc": {
86 | "require": [
87 | "@babel/register"
88 | ],
89 | "all": true,
90 | "include": [
91 | "src/**/*.js"
92 | ],
93 | "reporter": [
94 | "lcov",
95 | "text"
96 | ],
97 | "sourceMap": false,
98 | "instrument": true
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/samples/BatteryLife/README.md:
--------------------------------------------------------------------------------
1 | # IBM Watson IoT Platform BatteryLife Device Client
2 |
3 | Device code for sending system utilization data to IBM Watson IoT Platform, powered by [sebhildebrandt/systeminformation](https://github.com/sebhildebrandt/systeminformation).
4 |
5 | > Systeminformation is a library for ightweight collection of 40+ functions to retrieve detailed hardware, system and OS information.
6 |
7 | The following data points are collected by the BatteryLife sample:
8 | * CPU speed (GHz)
9 | * Manufacturer (string)
10 | * Total Memory (bytes)
11 | * Free Memory (bytes)
12 | * Active Memory (bytes)
13 | * Battery Remaining (%)
14 | * Battery Charging Status (boolean)
15 |
16 | A tutorial guiding you through the process of setting up this sample on a Raspberry Pi is published on [IBM Developer developerWorks Recipes](https://developer.ibm.com/recipes/tutorials/raspberry-pi-4/)
17 |
18 | ## Event Format
19 |
20 | - `cpuSpeed` obtained from `Systeminformation.CpuData.speed`
21 | - `manufacturer` obtained from `Systeminformation.SystemData.manufacturer`
22 | - `memory.total` obtained from `Systeminformation.MemData.total`
23 | - `memory.free` calculated using `Systeminformation.MemData.free`
24 | - `memory.active` calculated using `Systeminformation.MemData.active`
25 | - `battery.percent` calculated using `Systeminformation.BatteryData.percent`
26 | - `battery.charging` calculated using `Systeminformation.BatteryData.ischarging`
27 |
28 | ## Before you Begin
29 |
30 | Register a device with IBM Watson IoT Platform.
31 |
32 | For information on how to register devices, see the [Connecting Devices](https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/platform/iotplatform_task.html) topic in the IBM Watson IoT Platform documentation.
33 |
34 | At the end of the registration process, make a note of the following parameters:
35 | - Organization ID
36 | - Type ID
37 | - Device ID
38 | - Authentication Token
--------------------------------------------------------------------------------
/samples/BatteryLife/src/BatteryLife.js:
--------------------------------------------------------------------------------
1 | // *****************************************************************************
2 | // Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
3 | //
4 | // All rights reserved. This program and the accompanying materials
5 | // are made available under the terms of the Eclipse Public License v1.0
6 | // which accompanies this distribution, and is available at
7 | // http://www.eclipse.org/legal/epl-v10.html
8 | // *****************************************************************************
9 |
10 | import {DeviceClient, DeviceConfig} from '@wiotp/sdk';
11 | // or you can use import {DeviceClient, DeviceConfig} from '/Users/*InsertYourPathHere/GitHub/iot-nodejs/src/index.js';
12 | import { v4 as uuidv4 } from 'uuid';
13 | const si = require('systeminformation')
14 | const argv = require('yargs')
15 | .option('quickstart', {
16 | alias: 'q',
17 | description: 'Connects the sample device to quickstart',
18 | type: 'boolean',
19 | })
20 | .option('configFile', {
21 | alias: 'cfg',
22 | description: 'Connects the sample device using the Config_Sample.yaml file',
23 | type: 'config',
24 | })
25 | .help()
26 | .alias('help', 'h')
27 | .epilogue("If neither the quickstart or configFile parameter is provided the device will attempt to parse the configuration from environment variables.")
28 | .argv;
29 |
30 | let deviceConfig = null;
31 | let deviceClient = null;
32 | let data = {
33 | "cpuSpeed": null,
34 | "manufacturer": null,
35 | "memory": {
36 | "total":null,
37 | "free": null,
38 | "active": null,
39 | },
40 | "battery": {
41 | "percent":null,
42 | "charging": null,
43 | }
44 | };
45 |
46 |
47 | if (argv.quickstart) {
48 | let identity = {orgId:"quickstart", typeId:"nodejsSample", deviceId:uuidv4()};
49 | let options = {
50 | logLevel: "info",
51 | };
52 | let auth = null; //As quickstart does not support authentication
53 | deviceConfig = new DeviceConfig(identity, auth, options)
54 | startClient()
55 | console.log("\x1b[35m"); //Text formatting
56 | console.log("Welcome to IBM Watson IoT Platform Quickstart, view a vizualization of live data from this device at the URL below:");
57 | console.log("https://quickstart.internetofthings.ibmcloud.com/#/device/"+(identity["deviceId"])+"/sensor/");
58 | console.log("\x1b[0m"); //Text formatting
59 | }
60 | else if(argv.configFile)
61 | {
62 | deviceConfig = DeviceConfig.parseConfigFile("Config_Sample.yaml");
63 | startClient();
64 | }
65 | else
66 | {
67 | // Intialize using:
68 | // export WIOTP_IDENTITY_ORGID=myOrg
69 | // export WIOTP_IDENTITY_TYPEID=myType
70 | // export WIOTP_IDENTITY_DEVICEID=myDevice
71 | // export WIOTP_AUTH_TOKEN=myToken
72 | deviceConfig = DeviceConfig.parseEnvVars();
73 | startClient();
74 | }
75 |
76 | function startClient(){
77 | deviceClient = new DeviceClient(deviceConfig);
78 | deviceClient.connect();
79 | console.log("Press {ctrl + c} to disconnect at any time.")
80 | var interval = 2
81 | setInterval(sendInformation, interval*1000)
82 | }
83 |
84 | function sendInformation() {
85 | si.cpu().then(cpuData => {
86 | data['cpuSpeed'] = cpuData.speed
87 | })
88 | si.system().then(system =>{
89 | data['manufacturer'] = system.manufacturer
90 | })
91 | si.mem().then(memData => {
92 | var memoryRef = data['memory']
93 | memoryRef['total'] = memData.total
94 | memoryRef['free'] = memData.free
95 | memoryRef['active'] = memData.active
96 | })
97 | si.battery().then(batteryData => {
98 | var batteryRef = data['battery']
99 | batteryRef['percent'] = batteryData.percent
100 | batteryRef['charging'] = batteryData.ischarging
101 | })
102 | console.log(data)
103 | //console.log(data) ------ Use to visualise date in console
104 | deviceClient.publishEvent("BatteryLife","json",data);
105 | }
--------------------------------------------------------------------------------
/samples/BatteryLife/src/Config_BatteryLife.yaml:
--------------------------------------------------------------------------------
1 |
2 | identity:
3 | orgId: myOrg
4 | typeId: myType
5 | deviceId: myDevice
6 | auth:
7 | token: myToken
8 | options:
9 | domain: internetofthings.ibmcloud.com
10 | logLevel: notAValidLogLevel
11 | mqtt:
12 | port: 8883
13 | transport: tcp
14 | cleanStart: true
15 | sessionExpiry: 3600
16 | keepAlive: 60
17 | caFile: myPath
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ]
5 | }
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/README.md:
--------------------------------------------------------------------------------
1 | # IBM Watson IoT Platform Status Console Demo Application
2 |
3 |
4 | ## Building the application
5 |
6 | ```
7 | npm i
8 | npm run build-prod
9 | ```
10 |
11 | ## Running the application
12 |
13 | Open `src/index.html` in your browser of choice
14 |
15 |
16 | ## Publishing the application
17 |
18 | ```
19 | npm login
20 | npm publish .
21 | ```
22 |
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wiotp/statusconsole",
3 | "version": "1.0.0",
4 | "description": "IBM Watson IoT Platform Status Console Demo Application",
5 | "private": true,
6 | "main": "index.js",
7 | "scripts": {
8 | "build-dev": "webpack -d --mode development",
9 | "build-prod": "webpack -p --mode production",
10 | "test": "mocha --require @babel/register"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@wiotp/sdk": "0.6.2"
17 | },
18 | "devDependencies": {
19 | "@babel/cli": "^7.7.7",
20 | "@babel/register": "^7.7.7",
21 | "@babel/core": "^7.7.7",
22 | "@babel/preset-env": "^7.7.7",
23 | "babel-loader": "^8.0.6",
24 | "chai": "^4.2.0",
25 | "mocha": "^6.2.2",
26 | "webpack": "^4.41.4",
27 | "webpack-cli": "^3.3.10"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IBM Watson IoT Platform Starter Application
6 |
7 |
8 |
9 | DISCONNECTED
10 |
11 |
20 |
21 |
Connectivity:
22 |
Connect Application |
23 |
Disconnect Application
24 |
25 |
26 |
Actions:
27 |
Subscribe to all Events |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/src/index.js:
--------------------------------------------------------------------------------
1 | import { ApplicationClient, ApplicationConfig } from '@wiotp/sdk'
2 |
3 |
4 | let appClient = null;
5 |
6 | window.initialize = function() {
7 | if (appClient != null) {
8 | console.log("Client is already initialized");
9 | }
10 | else {
11 | let appId = document.getElementById("appId").value;
12 | let apikey = document.getElementById("apikey").value;
13 | let token = document.getElementById("token").value;
14 | let portNumber = document.getElementById("port").value;
15 | let logLevel = document.getElementById("logLevel").value;
16 |
17 | let identity = null;
18 |
19 | if (appId != null || appId != "") {
20 | identity = {appId: appId}
21 | }
22 |
23 | let auth = {
24 | key: apikey,
25 | token: token
26 | };
27 | let options = {
28 | logLevel:logLevel,
29 | mqtt: {
30 | transport:"websockets",
31 | port: parseInt(portNumber)
32 | }
33 | };
34 | let appConfig = new ApplicationConfig(identity, auth, options);
35 | appClient = new ApplicationClient(appConfig);
36 |
37 | // Event callbacks
38 | appClient.on("deviceEvent", function (typeId, deviceId, eventId, format, payload) {
39 | console.log("Device Event from :: " + typeId + " : " + deviceId + " of event " + eventId + " with format " + format + " - payload = " + payload);
40 | });
41 |
42 | // Connectivity callbacks
43 | appClient.on("connect", function () {
44 | document.getElementById("status").innerHTML = "CONNECTED";
45 | });
46 | appClient.on("reconnect", function () {
47 | document.getElementById("status").innerHTML = "RECONNECTING";
48 | });
49 | appClient.on("close", function () {
50 | document.getElementById("status").innerHTML = "DISCONNECTED";
51 | });
52 | appClient.on("offline", function () {
53 | document.getElementById("status").innerHTML = "OFFLINE";
54 | });
55 |
56 | // Error callback
57 | appClient.on("error", function (err) {
58 | document.getElementById("lastError").innerHTML = err;
59 | });
60 | }
61 | }
62 |
63 | window.connect = function() {
64 | if (appClient == null) {
65 | document.getElementById("lastError").innerHTML = "Need to initialize client before you can connect!";
66 | return;
67 | }
68 | appClient.connect();
69 | }
70 |
71 | window.disconnect = function() {
72 | if (appClient == null) {
73 | document.getElementById("lastError").innerHTML = "Need to initialize client before you can disconnect!";
74 | return;
75 | }
76 | appClient.disconnect();
77 | }
78 |
79 | window.subscribeToEvents = function() {
80 | if (appClient == null) {
81 | document.getElementById("lastError").innerHTML = "Need to initialize client before you can subscribe to events!";
82 | return;
83 | }
84 | appClient.subscribeToEvents("+", "+", "+", "+", 0);
85 | }
86 |
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/src/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: tahoma;
3 | font-size: 12pt;
4 | }
5 |
6 | div#status {
7 | font-weight: bold;
8 | border:1px solid #000;
9 | padding: 5px;
10 | margin: 5px;
11 | }
12 |
13 | div#lastError {
14 | font-family: consolas;
15 | background-color: red;
16 | color: #fff;
17 | border:1px solid #000;
18 | padding: 5px;
19 | margin: 5px;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/samples/webpack/statusconsole/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | filename: 'main.js',
7 | path: path.resolve(__dirname, 'dist')
8 | },
9 | module: {
10 | rules: [
11 | {
12 | test: /\.js$/,
13 | use: 'babel-loader',
14 | exclude: /node_modules/
15 | }
16 | ]
17 | },
18 | externals: {
19 | fs: "commonjs fs"
20 | //resolves webpack not calling FS as a module but as an environmental object
21 | },
22 | };
--------------------------------------------------------------------------------
/src/BaseClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import events from 'events';
12 | import mqtt from 'mqtt';
13 | import log from 'loglevel';
14 |
15 | import TinyCache from 'tinycache';
16 |
17 | const { v4: uuidv4 } = require('uuid');
18 |
19 | export default class BaseClient extends events.EventEmitter {
20 | constructor(config){
21 | super();
22 | this.log = log;
23 | this.log.setDefaultLevel(config.options.logLevel);
24 |
25 | this.config = config;
26 |
27 | this.reconnectLog = 0;
28 | this.mqtt = null;
29 |
30 | this.lostConnectionLog = new TinyCache();
31 |
32 | }
33 |
34 |
35 | isConnected() {
36 | if (this.mqtt == null) {
37 | return false;
38 | }
39 | return this.mqtt.connected;
40 | }
41 |
42 |
43 | connect(){
44 | if(this.mqtt != null) {
45 | this.log.info("[BaseClient:connect] Reconnecting to " + this.config.getMqttHost() + " as " + this.config.getClientId());
46 | this.mqtt.reconnect();
47 | return;
48 | }
49 |
50 | this.log.info("[BaseClient:connect] Connecting to " + this.config.getMqttHost() + " as " + this.config.getClientId());
51 |
52 | this.mqtt = mqtt.connect(this.config.getMqttHost(), this.config.getMqttConfig());
53 |
54 | /* Events coming from mqtt
55 | * Event 'connect' - Emitted on successful (re)connection (i.e. connack rc=0).
56 | * Event 'reconnect' - Emitted when a reconnect starts.
57 | * Event 'close' - Emitted after a disconnection.
58 | * Event 'offline' - Emitted when the client goes offline.
59 | * Event 'error' - Emitted when the client cannot connect (i.e. connack rc != 0) or when a parsing error occurs.
60 | * Event 'end' - Emitted when mqtt.Client#end() is called. If a callback was passed to mqtt.Client#end(), this event is emitted once the callback returns.
61 | * Event 'message' - Emitted when the client receives a publish packet
62 | * Event 'packetsend' - Emitted when the client sends any packet. This includes .published() packets as well as packets used by MQTT for managing subscriptions and connections
63 | * Event 'packetreceive' - Emitted when the client receives any packet. This includes packets from subscribed topics as well as packets used by MQTT for managing subscriptions and connections
64 | */
65 |
66 | this.mqtt.on('connect', () => {
67 | this.log.info("[BaseClient:onConnect] MQTT client is connected.");
68 | this.emit('connect');
69 |
70 | // less than 3 connect attempts you get put to a connect delay of 1 second
71 | // after 3 connect attempts you get put to a connect delay of 2 seconds (3 seconds elapsed - 3 attempts @ 1 second intervals)
72 | // after 6 connect attempts you get put to a connect delay of 5 seconds (3 + 6 seconds elapsed - 3 attempts @ 2 second intervals)
73 | // after 9 connect attempts you get put to a connect delay of 20 seconds (3 + 6 + 15 seconds elapsed - 3 attempts @ 5 second intervals)
74 | let connectionLostCount = this.lostConnectionLog.size;
75 |
76 | // Default is 1 second reconnect period
77 | let reconnectPeriod = 1000;
78 | if (connectionLostCount >= 9) {
79 | reconnectPeriod = 20000;
80 |
81 | // Log this and raise the error EVERY time we reconnect under these conditions.
82 | this.log.warn("[BaseClient:onOffline] This client is likely suffering from clientId stealing (where two connections try to use the same client Id).");
83 | this.emit("error", "Exceeded 9 connection losses in a 5 minute period. Check for clientId conflict with another connection.")
84 | }
85 | else if (connectionLostCount >= 6) {
86 | reconnectPeriod = 5000;
87 | }
88 | else if (connectionLostCount >= 3) {
89 | reconnectPeriod = 2000;
90 | }
91 |
92 | if (reconnectPeriod != this.mqtt.options.reconnectPeriod) {
93 | this.log.info("[BaseClient:onOffline] Client has lost connection " + connectionLostCount + " times during the last 5 minutes, reconnect delay adjusted to " + reconnectPeriod + " ms");
94 | this.mqtt.options.reconnectPeriod = reconnectPeriod;
95 | }
96 |
97 | });
98 |
99 | this.mqtt.on('reconnect', () => {
100 | this.log.info("[BaseClient:onReconnect] MQTT client is reconnecting.");
101 | // this.log.debug("[BaseClient:onReconnect] Resubscribe topics:");
102 | // this.log.debug(this.mqtt._resubscribeTopics);
103 | this.emit('reconnect');
104 | });
105 |
106 | this.mqtt.on('close', () => {
107 | this.log.info("[BaseClient:onClose] MQTT client connection was closed.");
108 | this.emit('close');
109 | });
110 |
111 | this.mqtt.on('offline', () => {
112 | let newId = uuidv4();
113 | this.log.info("[BaseClient:onOffline] MQTT client connection is offline. [" + newId + "]");
114 | this.emit('offline');
115 | // Record the disconnect event for 5 minutes
116 | this.lostConnectionLog.put( newId, '1', 300000 );
117 |
118 | let connectionLostCount = this.lostConnectionLog.size;
119 | this.log.info("[BaseClient:onOffline] Connection losses in the last 5 minutes: " + connectionLostCount);
120 |
121 | });
122 |
123 | this.mqtt.on('error', (error) => {
124 | this.log.error("[BaseClient:onError] " + error);
125 |
126 | let errorMsg = '' + error;
127 | if (errorMsg.indexOf('Not authorized') > -1) {
128 | this.log.error("[BaseClient:onError] One or more configuration parameters are wrong. Modify the configuration before trying to reconnect.");
129 | this.mqtt.end(false, () => {
130 | this.log.info("[BaseClient:onError] Closed the MQTT connection due to client misconfiguration");
131 | });
132 | }
133 | this.emit('error', error);
134 | });
135 | }
136 |
137 |
138 | disconnect(){
139 | if(this.mqtt == null) {
140 | this.log.info("[BaseClient:disconnect] Client was never connected");
141 | return;
142 | }
143 |
144 | this.mqtt.end(false, () => {
145 | this.log.info("[BaseClient:disconnect] Closed the MQTT connection due to disconnect() call");
146 | });
147 | }
148 |
149 |
150 | _subscribe(topic, QoS, callback) {
151 | if (this.mqtt == null) {
152 | this.emit('error', "[BaseClient:_subscribe] MQTT Client is not initialized - call connect() first");
153 | return;
154 | }
155 | if (!this.mqtt.connected) {
156 | this.emit('error', "[BaseClient:_subscribe] MQTT Client is not connected - call connect() first");
157 | return;
158 | }
159 |
160 | QoS = QoS || 0;
161 | callback = callback || function (err, granted) {
162 | if (err == null) {
163 | for (var index in granted) {
164 | let grant = granted[index];
165 | this.log.debug("[BaseClient:_subscribe] Subscribed to " + grant.topic + " at QoS " + grant.qos);
166 | }
167 | } else {
168 | this.log.error("[BaseClient:_subscribe] " + err);
169 | this.emit("error", err);
170 | }
171 | }.bind(this);
172 |
173 | this.log.debug("[BaseClient:_subscribe] Subscribing to topic " + topic + " with QoS " + QoS);
174 | this.mqtt.subscribe(topic, { qos: parseInt(QoS) }, callback);
175 | }
176 |
177 |
178 | _unsubscribe(topic, callback) {
179 | if (this.mqtt == null) {
180 | this.emit('error', "[BaseClient:_unsubscribe] MQTT Client is not initialized - call connect() first");
181 | return;
182 | }
183 | if (!this.mqtt.connected) {
184 | this.emit('error', "[BaseClient:_unsubscribe] MQTT Client is not connected - call connect() first");
185 | return;
186 | }
187 |
188 | callback = callback || function (err) {
189 | if (err == null) {
190 | this.log.debug("[BaseClient:_unsubscribe] Unsubscribed from: " + topic);
191 | } else {
192 | this.log.error("[BaseClient:_unsubscribe] " + err);
193 | this.emit("error", err);
194 | }
195 | }.bind(this);
196 |
197 | this.log.debug("[BaseClient:_unsubscribe] Unsubscribe: " + topic);
198 | this.mqtt.unsubscribe(topic, callback);
199 | }
200 |
201 |
202 | _publish(topic, msg, QoS, callback) {
203 | QoS = QoS || 0;
204 |
205 | if (this.mqtt == null) {
206 | this.emit('error', "[BaseClient:_unsubscribe] MQTT Client is not initialized - call connect() first");
207 | return;
208 | }
209 | if (!this.mqtt.connected) {
210 | this.emit('error', "[BaseClient:_unsubscribe] MQTT Client is not connected - call connect() first");
211 | return;
212 | }
213 |
214 | if ((typeof msg === 'object' || typeof msg === 'boolean' || typeof msg === 'number') && !Buffer.isBuffer(msg)) {
215 | // mqtt library does not support sending JSON/Boolean/Number data. So stringifying it.
216 | // All JSON object, array will be encoded.
217 | msg = JSON.stringify(msg);
218 | }
219 |
220 | this.log.debug("[BaseClient:_publish] Publish: " + topic + ", " + msg + ", QoS : " + QoS);
221 | this.mqtt.publish(topic, msg, { qos: parseInt(QoS) }, callback);
222 | }
223 |
224 | }
225 |
--------------------------------------------------------------------------------
/src/BaseConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export default class BaseConfig{
13 | constructor(identity, auth, options) {
14 | this.identity = identity;
15 | this.auth = auth;
16 | this.options = options;
17 |
18 | // Validation for options common to all confiugration
19 |
20 | if (this.options != null && "mqtt" in this.options) {
21 | // validate port
22 | if ("port" in this.options.mqtt && this.options.mqtt.port != null) {
23 | if (isNaN(this.options.mqtt.port)) {
24 | throw new Error("Optional setting options.mqtt.port must be a number if provided");
25 | }
26 | }
27 | // Validate cleanStart
28 | if ("cleanStart" in this.options.mqtt && typeof(this.options.mqtt.cleanStart) != "boolean") {
29 | throw new Error("Optional setting options.mqtt.cleanStart must be a boolean if provided");
30 | }
31 | }
32 |
33 | // Set defaults for optional configuration
34 | if (this.options == null) {
35 | this.options = {};
36 | }
37 | if (!("domain" in this.options) || this.options.domain == null) {
38 | this.options.domain = "internetofthings.ibmcloud.com";
39 | }
40 |
41 | if (!("logLevel" in this.options) || this.options.logLevel == null) {
42 | this.options.logLevel = "info";
43 | }
44 |
45 | if (!("mqtt" in this.options)) {
46 | this.options.mqtt = {};
47 | }
48 |
49 | if (!("port" in this.options.mqtt) || this.options.mqtt.port == null) {
50 | this.options.mqtt.port = 8883;
51 | }
52 | if (!("transport" in this.options.mqtt) || this.options.mqtt.transport == null) {
53 | this.options.mqtt.transport = "tcp";
54 | }
55 |
56 | if (!("cleanStart" in this.options.mqtt)) {
57 | this.options.mqtt.cleanStart = true;
58 | }
59 | if (!("sessionExpiry" in this.options.mqtt)) {
60 | this.options.mqtt.sessionExpiry = 3600;
61 | }
62 | if (!("keepAlive" in this.options.mqtt)) {
63 | this.options.mqtt.keepAlive = 60;
64 | }
65 |
66 | if (!("caFile" in this.options.mqtt)) {
67 | this.options.mqtt.caFile = null;
68 | }
69 | }
70 |
71 | getOrgId() {
72 | throw new Error("Sub class must implement getOrgId()");
73 | }
74 |
75 | isQuickstart() {
76 | return this.getOrgId() == "quickstart";
77 | }
78 |
79 | getClientId() {
80 | throw new Error("Sub class must implement getClientId()");
81 | }
82 |
83 | getMqttUsername() {
84 | throw new Error("Sub class must implement getMqttUsername()");
85 | }
86 |
87 | getMqttPassword() {
88 | throw new Error("Sub class must implement getMqttPassowrd()");
89 | }
90 |
91 | getMqttConfig() {
92 | // See: https://www.npmjs.com/package/mqtt#mqttclientstreambuilder-options
93 | let mqttConfig = {
94 | // Identity
95 | clientId: this.getClientId(),
96 |
97 | // Basic Connectivity
98 | keepalive: this.options.mqtt.keepAlive, // in seconds
99 | connectTimeout: 90*1000, // milliseconds, time to wait before a CONNACK is received
100 | reconnectPeriod: 1000, // milliseconds, interval between two reconnections
101 | queueQoSZero: true, // if connection is broken, queue outgoing QoS zero messages
102 | resubscribe: true, // if connection is broken and reconnects, subscribed topics are automatically subscribed again
103 |
104 | clean: this.options.mqtt.cleanStart, // set to false to receive QoS 1 and 2 messages while offline
105 |
106 | // Authentication
107 | username: this.getMqttUsername(),
108 | password: this.getMqttPassword(),
109 |
110 | // Security
111 | // If you are using a self-signed certificate, pass the rejectUnauthorized: false option. Beware
112 | // that you are exposing yourself to man in the middle attacks, so it is a configuration that
113 | // is not recommended for production environments.
114 | rejectUnauthorized: true,
115 |
116 | // MQTTv5 support doesn't work with Watson IoT Platform, so stick to default for now
117 | // protocolId: "MQTT",
118 | // protocolVersion: 5
119 | }
120 | return mqttConfig;
121 | }
122 |
123 | getMqttHost() {
124 | let server = this.getOrgId() + ".messaging." + this.options.domain + ":" + this.options.mqtt.port;
125 |
126 | // For unencrpyted ports
127 | if (this.options.mqtt.port == 80 || this.options.mqtt.port == 1883) {
128 | if (this.options.mqtt.transport == "tcp") {
129 | return "tcp://" + server;
130 | }
131 | if (this.options.mqtt.transport == "websockets") {
132 | return "ws://" + server;
133 | }
134 | }
135 |
136 | // For encrypted ports
137 | if (this.options.mqtt.port == 443 || this.options.mqtt.port == 8883) {
138 | if (this.options.mqtt.transport == "tcp") {
139 | return "ssl://" + server;
140 | }
141 | if (this.options.mqtt.transport == "websockets") {
142 | return "wss://" + server;
143 | }
144 | }
145 |
146 | // Default to something, but really shouldn't hit this scenario unless misconfigured
147 | return "ssl://" + server;
148 | }
149 |
150 | static parseEnvVars() {
151 | throw new Error("Sub class must implement parseEnvVars()");
152 | }
153 |
154 | static parseConfigFile() {
155 | throw new Error("Sub class must implement parseConfigFile()");
156 | }
157 |
158 | };
159 |
--------------------------------------------------------------------------------
/src/api/ApiClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import xhr from 'axios';
12 | import Promise from 'bluebird';
13 | import btoa from 'btoa';
14 | import FormData from 'form-data';
15 | import log from 'loglevel';
16 |
17 | import { isBrowser } from '../util';
18 |
19 | export default class ApiClient {
20 | constructor(config) {
21 | this.log = log;
22 | this.log.setDefaultLevel(config.options.logLevel);
23 |
24 | this.config = config;
25 | this.useLtpa = this.config.auth && this.config.auth.useLtpa;
26 |
27 | this.log.debug("[ApiClient:constructor] ApiClient initialized for " + this.config.getApiBaseUri());
28 | }
29 |
30 | // e.g. [{name: true}, {description: false}] => -name,description
31 | parseSortSpec(sortSpec) {
32 | return sortSpec
33 | ? sortSpec.map(s=>{
34 | const e = Object.entries(s)[0];
35 | return `${e[1] ? '-' : ''}${e[0]}`
36 | }).join(',')
37 | : undefined;
38 | };
39 |
40 | callApi(method, expectedHttpCode, expectJsonContent, paths, body, params) {
41 | return new Promise((resolve, reject) => {
42 | let uri = this.config.getApiBaseUri();
43 |
44 | if (Array.isArray(paths)) {
45 | for (var i = 0, l = paths.length; i < l; i++) {
46 | uri += '/' + paths[i];
47 | }
48 | }
49 |
50 | let xhrConfig = {
51 | url: uri,
52 | method: method,
53 | headers: {
54 | 'Content-Type': 'application/json'
55 | },
56 | validateStatus: (status) => {
57 | if(Array.isArray(expectedHttpCode)) {
58 | return expectedHttpCode.includes(status);
59 | } else {
60 | return status === expectedHttpCode;
61 | }
62 | }
63 | };
64 |
65 | if (this.useLtpa) {
66 | xhrConfig.withCredentials = true;
67 | }
68 | else {
69 | xhrConfig.headers['Authorization'] = 'Basic ' + btoa(this.config.auth.key + ':' + this.config.auth.token);
70 | }
71 |
72 | if (body) {
73 | xhrConfig.data = body;
74 | }
75 |
76 | if (params) {
77 | xhrConfig.params = params;
78 | }
79 |
80 | if (this.config.getAdditionalHeaders()) {
81 | xhrConfig.headers = {...xhrConfig.headers, ...this.config.getAdditionalHeaders()};
82 | }
83 |
84 | function transformResponse(response) {
85 | if (expectJsonContent && !(typeof response.data === 'object')) {
86 | try {
87 | resolve(JSON.parse(response.data));
88 | } catch (e) {
89 | reject(e);
90 | }
91 | } else {
92 | resolve(response.data);
93 | }
94 | }
95 | this.log.debug("[ApiClient:transformResponse] " + xhrConfig);
96 | xhr(xhrConfig).then(transformResponse, reject);
97 | });
98 | }
99 |
100 | getOrganizationDetails() {
101 | this.log.debug("[ApiClient] getOrganizationDetails()");
102 | return this.callApi('GET', 200, true, null, null);
103 | }
104 |
105 |
106 | getServiceStatus() {
107 | this.log.debug("[ApiClient] getServiceStatus()");
108 | return this.callApi('GET', 200, true, ['service-status'], null);
109 | }
110 |
111 | //Usage Management
112 | getActiveDevices(start, end, detail) {
113 | this.log.debug("[ApiClient] getActiveDevices(" + start + ", " + end + ")");
114 | detail = detail | false;
115 | let params = {
116 | start: start,
117 | end: end,
118 | detail: detail
119 | };
120 | return this.callApi('GET', 200, true, ['usage', 'active-devices'], null, params);
121 | }
122 |
123 | getHistoricalDataUsage(start, end, detail) {
124 | this.log.debug("[ApiClient] getHistoricalDataUsage(" + start + ", " + end + ")");
125 | detail = detail | false;
126 | let params = {
127 | start: start,
128 | end: end,
129 | detail: detail
130 | };
131 | return this.callApi('GET', 200, true, ['usage', 'historical-data'], null, params);
132 | }
133 |
134 | getDataUsage(start, end, detail) {
135 | this.log.debug("[ApiClient] getDataUsage(" + start + ", " + end + ")");
136 | detail = detail | false;
137 | let params = {
138 | start: start,
139 | end: end,
140 | detail: detail
141 | };
142 | return this.callApi('GET', 200, true, ['usage', 'data-traffic'], null, params);
143 | }
144 |
145 |
146 | //client connectivity status
147 | getConnectionStates(){
148 | this.log.debug("[ApiClient] getConnectionStates() - client connectivity");
149 | return this.callApi('GET', 200, true, ["clientconnectionstates"], null);
150 | }
151 |
152 | getConnectionState(id){
153 | this.log.debug("[ApiClient] getConnectionState() - client connectivity");
154 | return this.callApi('GET', 200, true, ["clientconnectionstates/" + id], null);
155 | }
156 |
157 | getConnectedClientsConnectionStates(){
158 | this.log.debug("[ApiClient] getConnectedClientsConnectionStates() - client connectivity");
159 | return this.callApi('GET', 200, true, ["clientconnectionstates?connectionStatus=connected"], null);
160 | }
161 |
162 | getRecentConnectionStates(date){
163 | this.log.debug("[ApiClient] getRecentConnectionStates() - client connectivity");
164 | return this.callApi('GET', 200, true, ["clientconnectionstates?connectedAfter=" + date], null);
165 | }
166 |
167 | getCustomConnectionState(query){
168 | this.log.debug("[ApiClient] getCustomConnectionStates() - client connectivity");
169 | return this.callApi('GET', 200, true, ["clientconnectionstates" + query], null);
170 | }
171 |
172 | //bulk apis
173 | getAllDevices(params) {
174 | this.log.debug("[ApiClient] getAllDevices() - BULK");
175 | return this.callApi('GET', 200, true, ["bulk", "devices"], null, params);
176 | }
177 |
178 | /**
179 | * Gateway Access Control (Beta)
180 | * The methods in this section follow the documentation listed under the link:
181 | * https://console.ng.bluemix.net/docs/services/IoT/gateways/gateway-access-control.html#gateway-access-control-beta-
182 | * Involves the following sections from the above mentioned link:
183 | * Assigning a role to a gateway
184 | * Adding devices to and removing devices from a resource group
185 | * Finding a resource group
186 | * Querying a resource group
187 | * Creating and deleting a resource group
188 | * Updating group properties
189 | * Retrieving and updating device properties
190 | *
191 | */
192 |
193 | //getGatewayGroup(gatewayId)
194 | //updateDeviceRoles(deviceId, roles[])
195 | //getAllDevicesInGropu(groupId)
196 | //addDevicesToGroup(groupId, deviceList[])
197 | //removeDevicesFromGroup(groupId, deviceList[])
198 |
199 | getGroupIdsForDevice(deviceId){
200 | this.log.debug("[ApiClient] getGroupIdsForDevice("+deviceId+")");
201 | return this.callApi('GET', 200, true, ['authorization', 'devices' , deviceId], null);
202 | }
203 |
204 | updateDeviceRoles(deviceId, roles){
205 | this.log.debug("[ApiClient] updateDeviceRoles("+deviceId+","+roles+")");
206 | return this.callApi('PUT', 200, false, ['authorization', 'devices', deviceId, 'roles'], roles);
207 | }
208 |
209 | getAllDevicesInGroup(groupId){
210 | this.log.debug("[ApiClient] getAllDevicesInGropu("+groupId+")");
211 | return this.callApi('GET', 200, true, ['bulk', 'devices' , groupId], null);
212 | }
213 |
214 | addDevicesToGroup(groupId, deviceList){
215 | this.log.debug("[ApiClient] addDevicesToGroup("+groupId+","+deviceList+")");
216 | return this.callApi('PUT', 200, false, ['bulk', 'devices' , groupId, "add"], deviceList);
217 | }
218 |
219 | removeDevicesFromGroup(groupId, deviceList){
220 | this.log.debug("[ApiClient] removeDevicesFromGroup("+groupId+","+deviceList+")");
221 | return this.callApi('PUT', 200, false, ['bulk', 'devices' , groupId, "remove"], deviceList);
222 | }
223 |
224 | // https://console.ng.bluemix.net/docs/services/IoT/gateways/gateway-access-control.html
225 |
226 | // Finding a Resource Group
227 | // getGatewayGroups()
228 | // Querying a resource group
229 | // getUniqueDevicesInGroup(groupId)
230 | // getUniqueGatewayGroup(groupId)
231 | // Creating and deleting a resource group
232 | // createGatewayGroup(groupName)
233 | // deleteGatewayGroup(groupId)
234 | // Retrieving and updating device properties
235 | // getGatewayGroupProperties()
236 | // getDeviceRoles(deviceId)
237 | // updateGatewayProperties(gatewayId,control_props)
238 | // updateDeviceControlProperties(deviceId, withroles)
239 |
240 | // Finding a Resource Group
241 | getAllGroups(){
242 | this.log.debug("[ApiClient] getAllGroups()");
243 | return this.callApi('GET', 200, true, ['groups'], null);
244 | }
245 |
246 | // Querying a resource group
247 |
248 | // Get unique identifiers of the members of the resource group
249 | getAllDeviceIdsInGroup(groupId){
250 | this.log.debug("[ApiClient] getAllDeviceIdsInGroup("+groupId+")");
251 | return this.callApi('GET', 200, true, ['bulk', 'devices' , groupId, "ids"], null);
252 | }
253 |
254 | // properties of the resource group
255 | getGroup(groupId){
256 | this.log.debug("[ApiClient] getGroup("+groupId+")");
257 | return this.callApi('GET', 200, true, ['groups', groupId], null);
258 | }
259 |
260 | // Creating and deleting a resource group
261 |
262 | // Create a Resource Group
263 | createGroup(groupInfo){
264 | this.log.debug("[ApiClient] createGroup()");
265 | return this.callApi('POST', 201, true, ['groups'], groupInfo);
266 | }
267 |
268 | // Delete a Resource Group
269 | deleteGroup(groupId){
270 | this.log.debug("[ApiClient] deleteGroup("+groupId+")");
271 | return this.callApi('DELETE', 200, false, ['groups', groupId], null);
272 | }
273 |
274 | // Retrieving and updating device properties
275 |
276 | // Get the ID of the devices group of a gateway
277 | getAllDeviceAccessControlProperties(){
278 | this.log.debug("[ApiClient] getAllDeviceAccessControlProperties()");
279 | return this.callApi('GET', 200, true, ['authorization', 'devices' ], null);
280 | }
281 |
282 | // Get standard role of a gateway
283 | getDeviceAccessControlProperties(deviceId){
284 | this.log.debug("[ApiClient] getDeviceAccessControlProperties("+deviceId+")");
285 | return this.callApi('GET', 200, true, ['authorization', 'devices', deviceId, 'roles'], null);
286 | }
287 |
288 | // Update device properties without affecting the access control properties
289 | updateDeviceAccessControlProperties(deviceId,deviceProps){
290 | this.log.debug("[ApiClient] updateDeviceAccessControlProperties("+deviceId+")");
291 | return this.callApi('PUT', 200, true, ['authorization', 'devices' , deviceId], deviceProps);
292 | }
293 |
294 | // Assign a standard role to a gateway
295 | updateDeviceAccessControlPropertiesWithRoles(deviceId, devicePropsWithRoles){
296 | this.log.debug("[ApiClient] updateDeviceAccessControlPropertiesWithRoles("+deviceId+","+devicePropsWithRoles+")");
297 | return this.callApi('PUT', 200, true, ['authorization', 'devices', deviceId, 'withroles'], devicePropsWithRoles);
298 | }
299 |
300 | // Duplicating updateDeviceRoles(deviceId, roles) for Gateway Roles
301 | updateGatewayRoles(gatewayId, roles){
302 | this.log.debug("[ApiClient] updateGatewayRoles("+gatewayId+","+roles+")");
303 | return this.callApi('PUT', 200, false, ['authorization', 'devices', gatewayId, 'roles'], roles);
304 | }
305 |
306 | // Extending getAllGroups() to fetch individual Groups
307 | getGroups(groupId){
308 | this.log.debug("[ApiClient] getGroups("+groupId+")");
309 | return this.callApi('GET', 200, true, ['groups', groupId], null);
310 | }
311 |
312 |
313 | callFormDataApi(method, expectedHttpCode, expectJsonContent, paths, body, params) {
314 | return new Promise((resolve, reject) => {
315 | let uri = this.config.getApiBaseUri();
316 |
317 | if (Array.isArray(paths)) {
318 | for (var i = 0, l = paths.length; i < l; i++) {
319 | uri += '/' + paths[i];
320 | }
321 | }
322 |
323 | let xhrConfig = {
324 | url: uri,
325 | method: method,
326 | headers: {
327 | 'Content-Type': 'multipart/form-data'
328 | }
329 | };
330 |
331 | if (this.useLtpa) {
332 | xhrConfig.withCredentials = true;
333 | }
334 | else {
335 | xhrConfig.headers['Authorization'] = 'Basic ' + btoa(this.apiKey + ':' + this.apiToken);
336 | }
337 |
338 | if (body) {
339 | xhrConfig.data = body;
340 | if(isBrowser()) {
341 | xhrConfig.transformRequest = [function (data) {
342 | var formData = new FormData()
343 |
344 | if(xhrConfig.method == "POST") {
345 | if(data.schemaFile) {
346 | var blob = new Blob([data.schemaFile], { type: "application/json" })
347 | var schemaFileName = `${data.name || 'schema'}.json`;
348 | formData.append('schemaFile', blob, schemaFileName);
349 | }
350 |
351 | if(data.name) {
352 | formData.append('name', data.name)
353 | }
354 |
355 | if (data.schemaType) {
356 | formData.append('schemaType', 'json-schema')
357 | }
358 | if (data.description) {
359 | formData.append('description', data.description)
360 | }
361 | } else if(xhrConfig.method == "PUT") {
362 | if(data.schemaFile) {
363 | var blob = new Blob([data.schemaFile], { type: "application/json", name: data.name })
364 | var schemaFileName = `${data.name || 'schema'}.json`;
365 | formData.append('schemaFile', blob, schemaFileName);
366 | }
367 | }
368 |
369 | return formData;
370 | }]
371 | }
372 | }
373 |
374 | if (params) {
375 | xhrConfig.params = params;
376 | }
377 |
378 | if (this.config.getAdditionalHeaders()) {
379 | xhrConfig.headers = {...xhrConfig.headers, ...this.config.getAdditionalHeaders()};
380 | }
381 |
382 | function transformResponse(response) {
383 | if (response.status === expectedHttpCode) {
384 | if (expectJsonContent && !(typeof response.data === 'object')) {
385 | try {
386 | resolve(JSON.parse(response.data));
387 | } catch (e) {
388 | reject(e);
389 | }
390 | } else {
391 | resolve(response.data);
392 | }
393 | } else {
394 | reject(new Error(method + " " + uri + ": Expected HTTP " + expectedHttpCode + " from server but got HTTP " + response.status + ". Error Body: " + JSON.stringify(response.data)));
395 | }
396 | }
397 | this.log.debug("[ApiClient:transformResponse] " + xhrConfig);
398 |
399 | if(isBrowser()) {
400 | xhr(xhrConfig).then(transformResponse, reject);
401 | } else {
402 | var formData = null
403 | var config = {
404 | url: uri,
405 | method: method,
406 | headers: {'Content-Type': 'multipart/form-data'},
407 | auth : {
408 | user : this.apiKey,
409 | pass : this.apiToken
410 | },
411 | formData: {},
412 | rejectUnauthorized: false
413 | }
414 |
415 | if(xhrConfig.method == "POST") {
416 | formData = {
417 | 'schemaFile': {
418 | 'value': body.schemaFile,
419 | 'options': {
420 | 'contentType': 'application/json',
421 | 'filename': body.name
422 | }
423 | },
424 | 'schemaType': 'json-schema',
425 | 'name': body.name
426 | }
427 | config.formData = formData
428 | } else if(xhrConfig.method == "PUT") {
429 | formData = {
430 | 'schemaFile': {
431 | 'value': body.schemaFile,
432 | 'options': {
433 | 'contentType': 'application/json',
434 | 'filename': body.name
435 | }
436 | }
437 | }
438 | config.formData = formData
439 | }
440 | request(config, function optionalCallback(err, response, body) {
441 | if (response.statusCode === expectedHttpCode) {
442 | if (expectJsonContent && !(typeof response.data === 'object')) {
443 | try {
444 | resolve(JSON.parse(body));
445 | } catch (e) {
446 | reject(e);
447 | }
448 | } else {
449 | resolve(body);
450 | }
451 | } else {
452 | reject(new Error(method + " " + uri + ": Expected HTTP " + expectedHttpCode + " from server but got HTTP " + response.statusCode + ". Error Body: " + err));
453 | }
454 | });
455 | }
456 | });
457 | }
458 |
459 | invalidOperation(message) {
460 | return new Promise((resolve, reject) => {
461 | resolve(message)
462 | })
463 | }
464 | }
465 |
--------------------------------------------------------------------------------
/src/api/ApiErrors.js:
--------------------------------------------------------------------------------
1 |
2 | export class WiotpError extends Error {
3 | constructor(message, cause) {
4 | super(message);
5 | this.cause = cause;
6 | this.name = this.constructor.name;
7 | }
8 | }
9 |
10 | export class InvalidServiceCredentials extends WiotpError {}
11 |
12 | export class DestinationAlreadyExists extends WiotpError {}
13 |
14 | export class ServiceNotFound extends WiotpError {}
15 |
16 |
17 | export const handleError = (err, errorMappings) => {
18 | if(err && err.response && err.response.data && err.response.data.exception && err.response.data.exception.id) {
19 | if(errorMappings && errorMappings[err.response.data.exception.id]) {
20 | throw new errorMappings[err.response.data.exception.id](err.response.data.message, err);
21 | } else {
22 | throw new WiotpError(err.response.data.message, err);
23 | }
24 | } else {
25 | throw err;
26 | }
27 | }
28 |
29 | export default {
30 | WiotpError,
31 | InvalidServiceCredentials,
32 | DestinationAlreadyExists,
33 | ServiceNotFound,
34 | }
--------------------------------------------------------------------------------
/src/api/DscClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import log from 'loglevel';
12 |
13 | import * as errors from './ApiErrors';
14 |
15 |
16 | export default class DscClient {
17 | constructor(apiClient) {
18 | this.log = log;
19 |
20 | // callApi(method, expectedHttpCode, expectJsonContent, paths, body, params) {
21 | this.apiClient = apiClient;
22 | }
23 |
24 |
25 |
26 | /**************************************
27 | ** Services
28 | **************************************/
29 |
30 | // {name, description, type, credentials}
31 | createService(service) {
32 | return this.apiClient.callApi('POST', 201, true, ['s2s', 'services'], service)
33 | .catch(err => errors.handleError(err, {CUDSS0026E: errors.InvalidServiceCredentials}));
34 | }
35 |
36 |
37 | createCloudantService({
38 | name, description,
39 | username, password, host=`${username}.cloudant.com`, port=443, url=`https://${username}:${password}@${host}`,
40 | apikey, iam_apikey_name, iam_apikey_description, iam_role_crn, iam_serviceid_crn
41 | }) {
42 | return this.createService({name, description, type: 'cloudant', credentials: {
43 | username, password, host, port, url,
44 | apikey, iam_apikey_name, iam_apikey_description, iam_role_crn, iam_serviceid_crn
45 | }})
46 | }
47 |
48 | createEventstreamsService({name, description,
49 | api_key, kafka_admin_url, kafka_brokers_sasl, user, password,
50 | apikey, iam_apikey_name, iam_apikey_description, iam_role_crn, iam_serviceid_crn
51 | }) {
52 | return this.createService({name, description, type: 'eventstreams', credentials: {
53 | api_key, kafka_admin_url, kafka_brokers_sasl, user, password,
54 | apikey, iam_apikey_name, iam_apikey_description, iam_role_crn, iam_serviceid_crn
55 | }})
56 | }
57 |
58 | getService(serviceId) {
59 | return this.apiClient.callApi('GET', 200, true, ['s2s', 'services', serviceId])
60 | .catch(err => errors.handleError(err, {CUDSS0019E: errors.ServiceNotFound}));
61 | }
62 |
63 | getServices(serviceType) {
64 | return this.apiClient.callApi('GET', 200, true, ['s2s', 'services'], null, { bindingMode:'manual', serviceType })
65 | .catch(err => errors.handleError(err, {}));
66 | }
67 |
68 |
69 | deleteService(serviceId) {
70 | return this.apiClient.callApi('DELETE', 204, false, ['s2s', 'services', serviceId])
71 | .catch(err => errors.handleError(err, {}));
72 | }
73 |
74 |
75 | /**************************************
76 | ** Historian Connectors
77 | **************************************/
78 |
79 | // {name, description, serviceId, timezone, enabled}
80 | createConnector({name, type, description=undefined, serviceId, timezone='UTC', enabled=true}) {
81 | return this.apiClient.callApi('POST', 201, true, ['historianconnectors'], {name, description, type, serviceId, timezone, enabled})
82 | .catch(err => errors.handleError(err, {}));
83 | }
84 |
85 |
86 | updateConnector({id, name, description, serviceId, type, enabled, timezone}) {
87 | return this.apiClient.callApi('PUT', 200, true, ['historianconnectors', id], {id, name, description, serviceId, type, enabled, timezone})
88 | .catch(err => errors.handleError(err, {}));
89 | }
90 |
91 | getConnectors({name, serviceType, enabled, serviceId}) {
92 | return this.apiClient.callApi('GET', 200, true, ['historianconnectors'], null, {
93 | name: name ? name : undefined,
94 | type: serviceType ? serviceType : undefined,
95 | enabled: enabled === undefined ? undefined : enabled,
96 | serviceId: serviceId ? serviceId : undefined,
97 | })
98 | .catch(err => errors.handleError(err, {}));
99 | }
100 |
101 | deleteConnector(connectorId) {
102 | return this.apiClient.callApi('DELETE', 204, false, ['historianconnectors', connectorId])
103 | .catch(err => errors.handleError(err, {}));
104 | }
105 |
106 |
107 | /**************************************
108 | ** Destinations
109 | **************************************/
110 | // {name, type, configuration}
111 | createDestination(connectorId, destination) {
112 | return this.apiClient.callApi('POST', 201, true, ['historianconnectors', connectorId, 'destinations'], destination)
113 | .catch(err => errors.handleError(err, {CUDDSC0103E: errors.DestinationAlreadyExists}));
114 | }
115 |
116 | createCloudantDestination(connectorId, {name, bucketInterval}) {
117 | return this.createDestination(connectorId, {name, type: 'cloudant', configuration: { bucketInterval }});
118 | }
119 |
120 | createEventstreamsDestination(connectorId, {name, partitions=1}) {
121 | return this.createDestination(connectorId, {name, type: 'eventstreams', configuration: { partitions }});
122 | }
123 |
124 | getDestinations(connectorId, params={name:undefined}) {
125 | const {name} = params;
126 | return this.apiClient.callApi('GET', 200, true, ['historianconnectors', connectorId, 'destinations'], null, {
127 | name: name ? name : undefined,
128 | })
129 | .catch(err => errors.handleError(err, {}));
130 | }
131 |
132 | deleteDestination(connectorId, destinationName) {
133 | return this.apiClient.callApi('DELETE', [200, 204], false, ['historianconnectors', connectorId, 'destinations', destinationName])
134 | .catch(err => errors.handleError(err, {}));
135 | }
136 |
137 |
138 | /**************************************
139 | ** Forwarding Rules
140 | **************************************/
141 |
142 | // {name, destinationName, type:event, selector: {deviceType, eventId}}
143 | // {name, destinationName, type:state, selector: {logicalInterfaceId}}
144 | createForwardingRule(connectorId, forwardingrule) {
145 | return this.apiClient.callApi('POST', 201, true, ['historianconnectors', connectorId, 'forwardingrules'], forwardingrule)
146 | .catch(err => errors.handleError(err, {}));
147 | }
148 |
149 | createEventForwardingRule(connectorId, {name, destinationName, deviceType='*', eventId='*'}) {
150 | return this.createForwardingRule(connectorId, {name, destinationName, type: 'event', selector: {deviceType, eventId}})
151 | }
152 |
153 | getForwardingRules(connectorId) {
154 | // TODO: QS params
155 | return this.apiClient.callApi('GET', 200, true, ['historianconnectors', connectorId, 'forwardingrules'])
156 | .catch(err => errors.handleError(err, {}));
157 | }
158 |
159 | deleteForwardingRule(connectorId, forwardingRuleId) {
160 | return this.apiClient.callApi('DELETE', 204, false, ['historianconnectors', connectorId, 'forwardingrules', forwardingRuleId])
161 | .catch(err => errors.handleError(err, {}));
162 | }
163 |
164 |
165 | }
--------------------------------------------------------------------------------
/src/api/LecClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import log from 'loglevel';
12 |
13 | export default class LecClient {
14 | constructor(apiClient) {
15 | this.log = log;
16 |
17 | this.apiClient = apiClient;
18 |
19 | // Create an alias to the apiClient's callApi
20 | this.callApi = this.apiClient.callApi.bind(this.apiClient);
21 | }
22 |
23 | getLastEvents(type, id) {
24 | this.log.debug("[ApiClient] getLastEvents() - event cache");
25 | return this.callApi('GET', 200, true, ["device", "types", type, "devices", id, "events"], null);
26 | }
27 |
28 | getLastEventsByEventType(type, id, eventType) {
29 | this.log.debug("[ApiClient] getLastEventsByEventType() - event cache");
30 | return this.callApi('GET', 200, true, ["device", "types", type, "devices", id, "events", eventType], null);
31 | }
32 |
33 | };
--------------------------------------------------------------------------------
/src/api/MgmtClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import log from 'loglevel';
12 |
13 | export default class MgmtClient {
14 | constructor(apiClient) {
15 | this.log = log;
16 |
17 | this.apiClient = apiClient;
18 |
19 | // Create an alias to the apiClient's callApi
20 | this.callApi = this.apiClient.callApi.bind(this.apiClient);
21 | }
22 |
23 | getAllDeviceManagementRequests() {
24 | this.log.debug("[ApiClient] getAllDeviceManagementRequests()");
25 | return this.callApi('GET', 200, true, ['mgmt', 'requests'], null);
26 | }
27 |
28 | initiateDeviceManagementRequest(action, parameters, devices) {
29 | this.log.debug("[ApiClient] initiateDeviceManagementRequest(" + action + ", " + parameters + ", " + devices + ")");
30 | let body = {
31 | action: action,
32 | parameters: parameters,
33 | devices: devices
34 | };
35 | return this.callApi('POST', 202, true, ['mgmt', 'requests'], JSON.stringify(body));
36 | }
37 |
38 | getDeviceManagementRequest(requestId) {
39 | this.log.debug("[ApiClient] getDeviceManagementRequest(" + requestId + ")");
40 | return this.callApi('GET', 200, true, ['mgmt', 'requests', requestId], null);
41 | }
42 |
43 | deleteDeviceManagementRequest(requestId) {
44 | this.log.debug("[ApiClient] deleteDeviceManagementRequest(" + requestId + ")");
45 | return this.callApi('DELETE', 204, false, ['mgmt', 'requests', requestId], null);
46 | }
47 |
48 | getDeviceManagementRequestStatus(requestId) {
49 | this.log.debug("[ApiClient] getDeviceManagementRequestStatus(" + requestId + ")");
50 | return this.callApi('GET', 200, true, ['mgmt', 'requests', requestId, 'deviceStatus'], null);
51 | }
52 |
53 | getDeviceManagementRequestStatusByDevice(requestId, typeId, deviceId) {
54 | this.log.debug("[ApiClient] getDeviceManagementRequestStatusByDevice(" + requestId + ", " + typeId + ", " + deviceId + ")");
55 | return this.callApi('GET', 200, true, ['mgmt', 'requests', requestId, 'deviceStatus', typeId, deviceId], null);
56 | }
57 |
58 | };
--------------------------------------------------------------------------------
/src/api/RegistryClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export default class RegistryClient {
13 | constructor(apiClient) {
14 | this.apiClient = apiClient;
15 | }
16 |
17 | listAllDevicesOfType(type) {
18 | this.apiClient.log.debug("[ApiClient] listAllDevicesOfType(" + type + ")");
19 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices'], null);
20 | }
21 |
22 | deleteDeviceType(type) {
23 | this.apiClient.log.debug("[ApiClient] deleteDeviceType(" + type + ")");
24 | return this.apiClient.callApi('DELETE', 204, false, ['device', 'types', type], null);
25 | }
26 |
27 | getDeviceType(type) {
28 | this.apiClient.log.debug("[ApiClient] getDeviceType(" + type + ")");
29 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type], null);
30 | }
31 |
32 | getAllDeviceTypes() {
33 | this.apiClient.log.debug("[ApiClient] getAllDeviceTypes()");
34 | return this.apiClient.callApi('GET', 200, true, ['device', 'types'], null);
35 | }
36 |
37 | updateDeviceType(type, description, deviceInfo, metadata) {
38 | this.apiClient.log.debug("[ApiClient] updateDeviceType(" + type + ", " + description + ", " + deviceInfo + ", " + metadata + ")");
39 | let body = {
40 | deviceInfo: deviceInfo,
41 | description: description,
42 | metadata: metadata
43 | };
44 |
45 | return this.apiClient.callApi('PUT', 200, true, ['device', 'types', type], JSON.stringify(body));
46 | }
47 |
48 | registerDeviceType(typeId, description, deviceInfo, metadata, classId) {
49 | this.apiClient.log.debug("[ApiClient] registerDeviceType(" + typeId + ", " + description + ", " + deviceInfo + ", " + metadata + ", " + classId + ")");
50 | // TODO: field validation
51 | classId = classId || "Device";
52 | let body = {
53 | id: typeId,
54 | classId: classId,
55 | deviceInfo: deviceInfo,
56 | description: description,
57 | metadata: metadata
58 | };
59 |
60 | return this.apiClient.callApi('POST', 201, true, ['device', 'types'], JSON.stringify(body));
61 | }
62 |
63 | registerDevice(type, deviceId, authToken, deviceInfo, location, metadata) {
64 | this.apiClient.log.debug("[ApiClient] registerDevice(" + type + ", " + deviceId + ", " + deviceInfo + ", " + location + ", " + metadata + ")");
65 | // TODO: field validation
66 | let body = {
67 | deviceId: deviceId,
68 | authToken: authToken,
69 | deviceInfo: deviceInfo,
70 | location: location,
71 | metadata: metadata
72 | };
73 |
74 | return this.apiClient.callApi('POST', 201, true, ['device', 'types', type, 'devices'], JSON.stringify(body));
75 | }
76 |
77 | unregisterDevice(type, deviceId) {
78 | this.apiClient.log.debug("[ApiClient] unregisterDevice(" + type + ", " + deviceId + ")");
79 | return this.apiClient.callApi('DELETE', 204, false, ['device', 'types', type, 'devices', deviceId], null);
80 | }
81 |
82 | updateDevice(type, deviceId, deviceInfo, status, metadata, extensions) {
83 | this.apiClient.log.debug("[ApiClient] updateDevice(" + type + ", " + deviceId + ", " + deviceInfo + ", " + status + ", " + metadata + ")");
84 | let body = {
85 | deviceInfo: deviceInfo,
86 | status: status,
87 | metadata: metadata,
88 | extensions: extensions
89 | };
90 |
91 | return this.apiClient.callApi('PUT', 200, true, ['device', 'types', type, 'devices', deviceId], JSON.stringify(body));
92 | }
93 |
94 | getDevice(type, deviceId) {
95 | this.apiClient.log.debug("[ApiClient] getDevice(" + type + ", " + deviceId + ")");
96 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId], null);
97 | }
98 |
99 |
100 | /**
101 | * Register multiple new devices, each request can contain a maximum of 512KB.
102 | * The response body will contain the generated authentication tokens for all devices.
103 | * The caller of the method must make sure to record these tokens when processing
104 | * the response. The IBM Watson IoT Platform will not be able to retrieve lost authentication tokens
105 | *
106 | * @param arryOfDevicesToBeAdded Array of JSON devices to be added. Refer to
107 | * link
108 | * for more information about the schema to be used
109 | */
110 | registerMultipleDevices(arryOfDevicesToBeAdded) {
111 | this.apiClient.log.debug("[ApiClient] arryOfDevicesToBeAdded() - BULK");
112 | return this.apiClient.callApi('POST', 201, true, ["bulk", "devices", "add"], JSON.stringify(arryOfDevicesToBeAdded));
113 | }
114 |
115 | /**
116 | * Delete multiple devices, each request can contain a maximum of 512Kb
117 | *
118 | * @param arryOfDevicesToBeDeleted Array of JSON devices to be deleted. Refer to
119 | * link
120 | * for more information about the schema to be used.
121 | */
122 | deleteMultipleDevices(arryOfDevicesToBeDeleted) {
123 |
124 | this.apiClient.log.debug("[ApiClient] deleteMultipleDevices() - BULK");
125 | return this.apiClient.callApi('POST', 201, true, ["bulk", "devices", "remove"], JSON.stringify(arryOfDevicesToBeDeleted));
126 | }
127 |
128 | getDeviceLocation(type, deviceId) {
129 | this.apiClient.log.debug("[ApiClient] getDeviceLocation(" + type + ", " + deviceId + ")");
130 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId, 'location'], null);
131 | }
132 |
133 | updateDeviceLocation(type, deviceId, location) {
134 | this.apiClient.log.debug("[ApiClient] updateDeviceLocation(" + type + ", " + deviceId + ", " + location + ")");
135 |
136 | return this.apiClient.callApi('PUT', 200, true, ['device', 'types', type, 'devices', deviceId, 'location'], JSON.stringify(location));
137 | }
138 |
139 |
140 | getDeviceManagementInformation(type, deviceId) {
141 | this.apiClient.log.debug("[ApiClient] getDeviceManagementInformation(" + type + ", " + deviceId + ")");
142 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId, 'mgmt'], null);
143 | }
144 |
145 | getAllDiagnosticLogs(type, deviceId) {
146 | this.apiClient.log.debug("[ApiClient] getAllDiagnosticLogs(" + type + ", " + deviceId + ")");
147 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId, 'diag', 'logs'], null);
148 | }
149 |
150 | clearAllDiagnosticLogs(type, deviceId) {
151 | this.apiClient.log.debug("[ApiClient] clearAllDiagnosticLogs(" + type + ", " + deviceId + ")");
152 | return this.apiClient.callApi('DELETE', 204, false, ['device', 'types', type, 'devices', deviceId, 'diag', 'logs'], null);
153 | }
154 |
155 | addDeviceDiagLogs(type, deviceId, log) {
156 | this.apiClient.log.debug("[ApiClient] addDeviceDiagLogs(" + type + ", " + deviceId + ", " + log + ")");
157 | return this.apiClient.callApi('POST', 201, false, ['device', 'types', type, 'devices', deviceId, 'diag', 'logs'], JSON.stringify(log));
158 | }
159 |
160 | getDiagnosticLog(type, deviceId, logId) {
161 | this.apiClient.log.debug("[ApiClient] getAllDiagnosticLogs(" + type + ", " + deviceId + ", " + logId + ")");
162 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId, 'diag', 'logs', logId], null);
163 | }
164 |
165 | deleteDiagnosticLog(type, deviceId, logId) {
166 | this.apiClient.log.debug("[ApiClient] deleteDiagnosticLog(" + type + ", " + deviceId + ", " + logId + ")");
167 | return this.apiClient.callApi('DELETE', 204, false, ['device', 'types', type, 'devices', deviceId, 'diag', 'logs', logId], null);
168 | }
169 |
170 | getDeviceErrorCodes(type, deviceId) {
171 | this.apiClient.log.debug("[ApiClient] getDeviceErrorCodes(" + type + ", " + deviceId + ")");
172 | return this.apiClient.callApi('GET', 200, true, ['device', 'types', type, 'devices', deviceId, 'diag', 'errorCodes'], null);
173 | }
174 |
175 | clearDeviceErrorCodes(type, deviceId) {
176 | this.apiClient.log.debug("[ApiClient] clearDeviceErrorCodes(" + type + ", " + deviceId + ")");
177 | return this.apiClient.callApi('DELETE', 204, false, ['device', 'types', type, 'devices', deviceId, 'diag', 'errorCodes'], null);
178 | }
179 |
180 | addErrorCode(type, deviceId, log) {
181 | this.apiClient.log.debug("[ApiClient] addErrorCode(" + type + ", " + deviceId + ", " + log + ")");
182 | return this.apiClient.callApi('POST', 201, false, ['device', 'types', type, 'devices', deviceId, 'diag', 'errorCodes'], JSON.stringify(log));
183 | }
184 |
185 | getDeviceConnectionLogs(typeId, deviceId) {
186 | this.apiClient.log.debug("[ApiClient] getDeviceConnectionLogs(" + typeId + ", " + deviceId + ")");
187 | let params = {
188 | typeId: typeId,
189 | deviceId: deviceId
190 | };
191 | return this.apiClient.callApi('GET', 200, true, ['logs', 'connection'], null, params);
192 | }
193 |
194 |
195 | };
--------------------------------------------------------------------------------
/src/api/RulesClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import log from 'loglevel';
12 |
13 | export default class RulesClient {
14 | constructor(apiClient) {
15 | this.log = log;
16 |
17 | this.apiClient = apiClient;
18 |
19 | // Create an alias to the apiClient's callApi
20 | this.callApi = this.apiClient.callApi.bind(this.apiClient);
21 | }
22 |
23 |
24 | getRulesForLogicalInterface(logicalInterfaceId) {
25 | if (this.draftMode) {
26 | return this.getRulesForLogicalInterface(logicalInterfaceId);
27 | } else {
28 | return this.getActiveRulesForLogicalInterface(logicalInterfaceId);
29 | }
30 | }
31 |
32 |
33 | getDraftRulesForLogicalInterface(logicalInterfaceId) {
34 | return this.callApi('GET', 200, true, ['draft', 'logicalinterfaces', logicalInterfaceId, 'rules']);
35 | }
36 |
37 |
38 | getActiveRulesForLogicalInterface(logicalInterfaceId) {
39 | return this.callApi('GET', 200, true, ['logicalinterfaces', logicalInterfaceId, 'rules']);
40 | }
41 |
42 |
43 | createRule(logicalInterfaceId, name, condition, description=undefined, notificationStrategy=RulesClient.RuleNotificationStrategy.EVERY_TIME()) {
44 | var body = {
45 | name,
46 | condition,
47 | notificationStrategy
48 | }
49 | if (description) body['description'] = description;
50 | var base = this.draftMode ? ['draft', 'logicalinterfaces', logicalInterfaceId, 'rules'] : ['logicalinterfaces', logicalInterfaceId, 'rules'];
51 | return this.callApi('POST', 201, true, base, JSON.stringify(body));
52 | }
53 |
54 |
55 | updateRule(rule) {
56 | var base = this.draftMode ? ['draft', 'logicalinterfaces', rule.logicalInterfaceId, 'rules', rule.id] : ['logicalinterfaces', rule.logicalInterfaceId, 'rules', rule.id];
57 | return this.callApi('PUT', 200, true, base, JSON.stringify(rule));
58 | }
59 |
60 |
61 | deleteRule(logicalInterfaceId, ruleId) {
62 | var base = this.draftMode ? ['draft', 'logicalinterfaces', logicalInterfaceId, 'rules', ruleId] : ['logicalinterfaces', logicalInterfaceId, 'rules', ruleId];
63 | return this.callApi('DELETE', 204, false, base);
64 | }
65 |
66 | }
67 |
68 | RulesClient.RuleNotificationStrategy = {
69 | EVERY_TIME: () => ({
70 | when: 'every-time'
71 | }),
72 | BECOMES_TRUE: () => ({
73 | when: 'becomes-true',
74 | }),
75 | X_IN_Y: (count) => ({
76 | when: 'x-in-y',
77 | count,
78 | }),
79 | PERSISTS: (count, timePeriod) => ({
80 | when: 'persists',
81 | count,
82 | timePeriod
83 | }),
84 |
85 | };
--------------------------------------------------------------------------------
/src/api/StateClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import log from 'loglevel';
12 |
13 | export default class StateClient {
14 | constructor(apiClient) {
15 | this.log = log;
16 |
17 | this.apiClient = apiClient;
18 | this.draftMode = true;
19 |
20 | // Create an alias to the apiClient's methods that we use
21 | this.callApi = this.apiClient.callApi.bind(this.apiClient);
22 | this.parseSortSpec = this.apiClient.parseSortSpec.bind(this.apiClient);
23 | this.callFormDataApi = this.apiClient.callFormDataApi.bind(this.apiClient);
24 | this.invalidOperation = this.apiClient.invalidOperation.bind(this.apiClient);
25 | }
26 |
27 | workWithActive() {
28 | this.draftMode = false;
29 | }
30 |
31 | workWithDraft() {
32 | this.draftMode = true;
33 | }
34 |
35 | // IM Device state API
36 | createSchema(schemaContents, name, description) {
37 | var body = {
38 | 'schemaFile': schemaContents,
39 | 'schemaType': 'json-schema',
40 | 'name': name,
41 | }
42 |
43 | if (description) {
44 | body.description = description
45 | }
46 |
47 | var base = this.draftMode ? ["draft", "schemas"] : ["schemas"]
48 | return this.callFormDataApi('POST', 201, true, base, body, null);
49 | }
50 |
51 | getSchema(schemaId) {
52 | var base = this.draftMode ? ["draft", "schemas", schemaId] : ["schemas", schemaId]
53 | return this.callApi('GET', 200, true, base);
54 | }
55 |
56 | getActiveSchema(schemaId) {
57 | return this.callApi('GET', 200, true, ["schemas", schemaId]);
58 | }
59 |
60 | getSchemas() {
61 | var base = this.draftMode ? ["draft", "schemas"] : ["schemas"]
62 | return this.callApi('GET', 200, true, base);
63 | }
64 |
65 | getActiveSchemas() {
66 | return this.callApi('GET', 200, true, ["schemas"]);
67 | }
68 |
69 | updateSchema(schemaId, name, description) {
70 | var body = {
71 | "id": schemaId,
72 | "name": name,
73 | "description": description
74 | }
75 |
76 | var base = this.draftMode ? ["draft", "schemas", schemaId] : ["schemas", schemaId]
77 | return this.callApi('PUT', 200, true, base, body);
78 | }
79 |
80 | updateSchemaContent(schemaId, schemaContents, filename) {
81 | var body = {
82 | 'schemaFile': schemaContents,
83 | 'name': filename
84 | }
85 |
86 | var base = this.draftMode ? ["draft", "schemas", schemaId, "content"] : ["schemas", schemaId, "content"]
87 | return this.callFormDataApi('PUT', 204, false, base, body, null);
88 | }
89 |
90 | getSchemaContent(schemaId) {
91 | var base = this.draftMode ? ["draft", "schemas", schemaId, "content"] : ["schemas", schemaId, "content"]
92 | return this.callApi('GET', 200, true, base);
93 | }
94 |
95 | getActiveSchemaContent(schemaId) {
96 | return this.callApi('GET', 200, true, ["schemas", schemaId, "content"]);
97 | }
98 |
99 | deleteSchema(schemaId) {
100 | var base = this.draftMode ? ["draft", "schemas", schemaId] : ["schemas", schemaId]
101 | return this.callApi('DELETE', 204, false, base, null);
102 | }
103 |
104 |
105 | createEventType(name, description, schemaId) {
106 | var body = {
107 | 'name': name,
108 | 'description': description,
109 | 'schemaId': schemaId,
110 | }
111 | var base = this.draftMode ? ["draft", "event", "types"] : ["event", "types"]
112 | return this.callApi('POST', 201, true, base, JSON.stringify(body));
113 | }
114 |
115 | getEventType(eventTypeId) {
116 | var base = this.draftMode ? ["draft", "event", "types", eventTypeId] : ["event", "types", eventTypeId]
117 | return this.callApi('GET', 200, true, base);
118 | }
119 |
120 | getActiveEventType(eventTypeId) {
121 | return this.callApi('GET', 200, true, ["event", "types", eventTypeId]);
122 | }
123 |
124 | deleteEventType(eventTypeId) {
125 | var base = this.draftMode ? ["draft", "event", "types", eventTypeId] : ["event", "types", eventTypeId]
126 | return this.callApi('DELETE', 204, false, base);
127 | }
128 |
129 | updateEventType(eventTypeId, name, description, schemaId) {
130 | var body = {
131 | "id": eventTypeId,
132 | "name": name,
133 | "description": description,
134 | "schemaId": schemaId
135 | }
136 |
137 | var base = this.draftMode ? ["draft", "event", "types", eventTypeId] : ["event", "types", eventTypeId]
138 | return this.callApi('PUT', 200, true, base, body);
139 | }
140 |
141 | getEventTypes() {
142 | var base = this.draftMode ? ["draft", "event", "types"] : ["event", "types"]
143 | return this.callApi('GET', 200, true, base);
144 | }
145 |
146 | getActiveEventTypes() {
147 | return this.callApi('GET', 200, true, ["event", "types"]);
148 | }
149 |
150 | createPhysicalInterface(name, description) {
151 | var body = {
152 | 'name': name,
153 | 'description': description
154 | }
155 |
156 | var base = this.draftMode ? ["draft", "physicalinterfaces"] : ["physicalinterfaces"]
157 | return this.callApi('POST', 201, true, base, body);
158 | }
159 |
160 | getPhysicalInterface(physicalInterfaceId) {
161 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId] : ["physicalinterfaces", physicalInterfaceId]
162 | return this.callApi('GET', 200, true, base);
163 | }
164 |
165 | getActivePhysicalInterface(physicalInterfaceId) {
166 | return this.callApi('GET', 200, true, ["physicalinterfaces", physicalInterfaceId]);
167 | }
168 |
169 | deletePhysicalInterface(physicalInterfaceId) {
170 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId] : ["physicalinterfaces", physicalInterfaceId]
171 | return this.callApi('DELETE', 204, false, base);
172 | }
173 |
174 | updatePhysicalInterface(physicalInterfaceId, name, description) {
175 | var body = {
176 | 'id': physicalInterfaceId,
177 | 'name': name,
178 | 'description': description
179 | }
180 |
181 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId] : ["physicalinterfaces", physicalInterfaceId]
182 | return this.callApi('PUT', 200, true, base, body);
183 | }
184 |
185 | getPhysicalInterfaces() {
186 | var base = this.draftMode ? ["draft", "physicalinterfaces"] : ["physicalinterfaces"]
187 | return this.callApi('GET', 200, true, base);
188 | }
189 |
190 | getActivePhysicalInterfaces() {
191 | return this.callApi('GET', 200, true, ["physicalinterfaces"]);
192 | }
193 |
194 | createPhysicalInterfaceEventMapping(physicalInterfaceId, eventId, eventTypeId) {
195 | var body = {
196 | "eventId": eventId,
197 | "eventTypeId": eventTypeId
198 | }
199 |
200 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId, "events"] : ["physicalinterfaces", physicalInterfaceId, "events"]
201 | return this.callApi('POST', 201, true, base, body);
202 | }
203 |
204 | getPhysicalInterfaceEventMappings(physicalInterfaceId) {
205 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId, "events"] : ["physicalinterfaces", physicalInterfaceId, "events"]
206 | return this.callApi('GET', 200, true, base);
207 | }
208 |
209 | getActivePhysicalInterfaceEventMappings(physicalInterfaceId) {
210 | return this.callApi('GET', 200, true, ["physicalinterfaces", physicalInterfaceId, "events"]);
211 | }
212 |
213 | deletePhysicalInterfaceEventMapping(physicalInterfaceId, eventId) {
214 | var base = this.draftMode ? ["draft", "physicalinterfaces", physicalInterfaceId, "events", eventId] : ["physicalinterfaces", physicalInterfaceId, "events", eventId]
215 | return this.callApi('DELETE', 204, false, base);
216 | }
217 |
218 | createLogicalInterface(name, description, schemaId, alias) {
219 | var body = {
220 | 'name': name,
221 | 'description': description,
222 | 'schemaId': schemaId,
223 | }
224 | if (alias !== undefined) {
225 | body.alias = alias;
226 | }
227 |
228 | var base = this.draftMode ? ["draft", "logicalinterfaces"] : ["applicationinterfaces"]
229 | return this.callApi('POST', 201, true, base, body);
230 | }
231 |
232 | getLogicalInterface(logicalInterfaceId) {
233 | var base = this.draftMode ? ["draft", "logicalinterfaces", logicalInterfaceId] : ["applicationinterfaces", logicalInterfaceId]
234 | return this.callApi('GET', 200, true, base);
235 | }
236 |
237 | getActiveLogicalInterface(logicalInterfaceId) {
238 | return this.callApi('GET', 200, true, ["logicalinterfaces", logicalInterfaceId]);
239 | }
240 |
241 | deleteLogicalInterface(logicalInterfaceId) {
242 | var base = this.draftMode ? ["draft", "logicalinterfaces", logicalInterfaceId] : ["applicationinterfaces", logicalInterfaceId]
243 | return this.callApi('DELETE', 204, false, base);
244 | }
245 |
246 | updateLogicalInterface(logicalInterfaceId, name, description, schemaId, alias) {
247 | var body = {
248 | "id": logicalInterfaceId,
249 | "name": name,
250 | "description": description,
251 | "schemaId": schemaId
252 | }
253 | if (alias !== undefined) {
254 | body.alias = alias;
255 | }
256 |
257 | var base = this.draftMode ? ["draft", "logicalinterfaces", logicalInterfaceId] : ["applicationinterfaces", logicalInterfaceId]
258 | return this.callApi('PUT', 200, true, base, body);
259 | }
260 |
261 |
262 | getLogicalInterfaces(bookmark, limit, sort, name) {
263 | var base = this.draftMode ? ["draft", "logicalinterfaces"] : ["logicalinterfaces"]
264 |
265 | const _sort = this.parseSortSpec(sort);
266 |
267 | return this.callApi('GET', 200, true, base, undefined, {_bookmark: bookmark,_limit: limit, _sort, name : name ? name : undefined});
268 | }
269 |
270 | getActiveLogicalInterfaces() {
271 | return this.callApi('GET', 200, true, ["logicalinterfaces"]);
272 | }
273 |
274 | // Application interface patch operation on draft version
275 | // Acceptable operation id - validate-configuration, activate-configuration, list-differences
276 | patchOperationLogicalInterface(logicalInterfaceId, operationId) {
277 | var body = {
278 | "operation": operationId
279 | }
280 |
281 | if(this.draftMode) {
282 | switch(operationId) {
283 | case 'validate-configuration':
284 | return this.callApi('PATCH', 200, true, ["draft", "logicalinterfaces", logicalInterfaceId], body);
285 | case 'activate-configuration':
286 | return this.callApi('PATCH', 202, true, ["draft", "logicalinterfaces", logicalInterfaceId], body);
287 | case 'deactivate-configuration':
288 | return this.callApi('PATCH', 202, true, ["draft", "logicalinterfaces", logicalInterfaceId], body);
289 | case 'list-differences':
290 | return this.callApi('PATCH', 200, true, ["draft", "logicalinterfaces", logicalInterfaceId], body);
291 | default:
292 | return this.callApi('PATCH', 200, true, ["draft", "logicalinterfaces", logicalInterfaceId], body);
293 | }
294 | } else {
295 | return this.invalidOperation("PATCH operation not allowed on active logical interface");
296 | }
297 | }
298 |
299 | // Application interface patch operation on active version
300 | // Acceptable operation id - deactivate-configuration
301 | patchOperationActiveLogicalInterface(logicalInterfaceId, operationId) {
302 | var body = {
303 | "operation": operationId
304 | }
305 |
306 | if(this.draftMode) {
307 | return this.callApi('PATCH', 202, true, ["logicalinterfaces", logicalInterfaceId], body)
308 | }
309 | else {
310 | return this.invalidOperation("PATCH operation 'deactivate-configuration' not allowed on logical interface");
311 | }
312 | }
313 |
314 | // Application interface patch operation on draft version
315 | // Acceptable operation id - validate-configuration, activate-configuration, list-differences
316 | patchOperationDeviceType(deviceTypeId, operationId) {
317 | var body = {
318 | "operation": operationId
319 | }
320 |
321 | if(this.draftMode) {
322 | switch(operationId) {
323 | case 'validate-configuration':
324 | return this.callApi('PATCH', 200, true, ["draft", "device", "types", deviceTypeId], body);
325 | case 'activate-configuration':
326 | return this.callApi('PATCH', 202, true, ["draft", "device", "types", deviceTypeId], body);
327 | case 'deactivate-configuration':
328 | return this.callApi('PATCH', 202, true, ["draft", "device", "types", deviceTypeId], body);
329 | case 'list-differences':
330 | return this.callApi('PATCH', 200, true, ["draft", "device", "types", deviceTypeId], body);
331 | default:
332 | return this.callApi('PATCH', 200, true, ["draft", "device", "types", deviceTypeId], body);
333 | }
334 | } else {
335 | return this.invalidOperation("this method cannot be called when draftMode=false");
336 | }
337 | }
338 |
339 | // Create device type with physical Interface Id
340 | createDeviceType(typeId, description, deviceInfo, metadata, classId, physicalInterfaceId) {
341 | this.log.debug("[ApiClient] registerDeviceType(" + typeId + ", " + description + ", " + deviceInfo + ", " + metadata + ", " + classId + ", " + physicalInterfaceId + ")");
342 | classId = classId || "Device";
343 | let body = {
344 | id: typeId,
345 | classId: classId,
346 | deviceInfo: deviceInfo,
347 | description: description,
348 | metadata: metadata,
349 | physicalInterfaceId: physicalInterfaceId
350 | };
351 |
352 | return this.callApi('POST', 201, true, ['device', 'types'], JSON.stringify(body));
353 | }
354 |
355 |
356 | getDeviceTypesByLogicalInterfaceId(logicalInterfaceId, bookmark=0, limit=10) {
357 | if(this.draftMode) {
358 | return this.callApi('GET', 200, true, ['draft', 'device', 'types'], null, {logicalInterfaceId, '_bookmark': bookmark, '_limit': limit});
359 | } else {
360 | return this.callApi('GET', 200, true, ['device', 'types'], null, {'_bookmark': bookmark, '_limit': limit});
361 | }
362 | }
363 |
364 | createDeviceTypePhysicalInterfaceAssociation(typeId, physicalInterfaceId) {
365 | let body = {
366 | id: physicalInterfaceId
367 | };
368 |
369 | if(this.draftMode) {
370 | return this.callApi('POST', 201, true, ['draft', 'device', 'types', typeId, 'physicalinterface'], JSON.stringify(body));
371 | } else {
372 | return this.callApi('PUT', 200, true, ['device', 'types', typeId], JSON.stringify({physicalInterfaceId : physicalInterfaceId}));
373 | }
374 |
375 | }
376 |
377 | getDeviceTypePhysicalInterfaces(typeId) {
378 | if(this.draftMode) {
379 | return this.callApi('GET', 200, true, ['draft', 'device', 'types', typeId, 'physicalinterface']);
380 | } else {
381 | return this.invalidOperation("GET Device type's physical interface is not allowed");
382 | }
383 | }
384 |
385 | getActiveDeviceTypePhysicalInterfaces(typeId) {
386 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'physicalinterface']);
387 | }
388 |
389 |
390 | deleteDeviceTypePhysicalInterfaceAssociation(typeId) {
391 | if(this.draftMode) {
392 | return this.callApi('DELETE', 204, false, ['draft', 'device', 'types', typeId, 'physicalinterface']);
393 | } else {
394 | return this.invalidOperation("DELETE Device type's physical interface is not allowed");
395 | }
396 | }
397 |
398 | createDeviceTypeLogicalInterfaceAssociation(typeId, logicalInterfaceId) {
399 | var body = {
400 | 'id': logicalInterfaceId
401 | }
402 |
403 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'logicalinterfaces'] : ['device', 'types', typeId, 'applicationinterfaces']
404 | return this.callApi('POST', 201, true, base, body);
405 | }
406 |
407 | getDeviceTypeLogicalInterfaces(typeId) {
408 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'logicalinterfaces'] : ['device', 'types', typeId, 'applicationinterfaces']
409 | return this.callApi('GET', 200, true, base);
410 | }
411 |
412 | getActiveDeviceTypeLogicalInterfaces(typeId) {
413 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'logicalinterfaces']);
414 | }
415 |
416 | createDeviceTypeLogicalInterfacePropertyMappings(typeId, logicalInterfaceId, mappings, notificationStrategy) {
417 | var body = null, base = null
418 | if(this.draftMode) {
419 | body = {
420 | "logicalInterfaceId": logicalInterfaceId,
421 | "propertyMappings": mappings,
422 | "notificationStrategy": "never"
423 | }
424 |
425 | if(notificationStrategy) {
426 | body.notificationStrategy = notificationStrategy
427 | }
428 |
429 | base = ['draft', 'device', 'types', typeId, 'mappings']
430 | } else {
431 | body = {
432 | "applicationInterfaceId": logicalInterfaceId,
433 | "propertyMappings": mappings
434 | }
435 | base = ['device', 'types', typeId, 'mappings']
436 | }
437 |
438 | return this.callApi('POST', 201, true, base, body);
439 | }
440 |
441 | getDeviceTypePropertyMappings(typeId) {
442 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'mappings'] : ['device', 'types', typeId, 'mappings']
443 | return this.callApi('GET', 200, true, base);
444 | }
445 |
446 | getActiveDeviceTypePropertyMappings(typeId) {
447 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'mappings']);
448 | }
449 |
450 | getDeviceTypeLogicalInterfacePropertyMappings(typeId, logicalInterfaceId) {
451 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'mappings', logicalInterfaceId] : ['device', 'types', typeId, 'mappings', logicalInterfaceId]
452 | return this.callApi('GET', 200, true, base);
453 | }
454 |
455 | getActiveDeviceTypeLogicalInterfacePropertyMappings(typeId, logicalInterfaceId) {
456 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'mappings', logicalInterfaceId]);
457 | }
458 |
459 | updateDeviceTypeLogicalInterfacePropertyMappings(typeId, logicalInterfaceId, mappings, notificationStrategy) {
460 | var body = null, base = null
461 | if(this.draftMode) {
462 | body = {
463 | "logicalInterfaceId": logicalInterfaceId,
464 | "propertyMappings": mappings,
465 | "notificationStrategy": "never"
466 | }
467 |
468 | if(notificationStrategy) {
469 | body.notificationStrategy = notificationStrategy
470 | }
471 |
472 | base = ['draft', 'device', 'types', typeId, 'mappings', logicalInterfaceId]
473 | } else {
474 | body = {
475 | "applicationInterfaceId": logicalInterfaceId,
476 | "propertyMappings": mappings
477 | }
478 | base = ['device', 'types', typeId, 'mappings', logicalInterfaceId]
479 | }
480 | return this.callApi('PUT', 200, false, base, body);
481 | }
482 |
483 | deleteDeviceTypeLogicalInterfacePropertyMappings(typeId, logicalInterfaceId) {
484 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'mappings', logicalInterfaceId] : ['device', 'types', typeId, 'mappings', logicalInterfaceId]
485 | return this.callApi('DELETE', 204, false, base);
486 | }
487 |
488 | deleteDeviceTypeLogicalInterfaceAssociation(typeId, logicalInterfaceId) {
489 | var base = this.draftMode ? ['draft', 'device', 'types', typeId, 'logicalinterfaces', logicalInterfaceId] : ['device', 'types', typeId, 'applicationinterfaces', logicalInterfaceId]
490 | return this.callApi('DELETE', 204, false, base);
491 | }
492 |
493 | // Device Type patch operation on draft version
494 | // Acceptable operation id - validate-configuration, activate-configuration, list-differences
495 | patchOperationDeviceType(typeId, operationId) {
496 | if(!operationId) {
497 | return invalidOperation("PATCH operation is not allowed. Operation id is expected")
498 | }
499 |
500 | var body = {
501 | "operation": operationId
502 | }
503 |
504 | var base = this.draftMode ? ['draft', 'device', 'types', typeId]: ['device', 'types', typeId]
505 |
506 | if(this.draftMode) {
507 | switch(operationId) {
508 | case 'validate-configuration':
509 | return this.callApi('PATCH', 200, true, base, body);
510 | break
511 | case 'activate-configuration':
512 | return this.callApi('PATCH', 202, true, base, body);
513 | break
514 | case 'deactivate-configuration':
515 | return this.callApi('PATCH', 202, true, base, body);
516 | break
517 | case 'list-differences':
518 | return this.callApi('PATCH', 200, true, base, body);
519 | break
520 | default:
521 | return this.invalidOperation("PATCH operation is not allowed. Invalid operation id")
522 | }
523 | } else {
524 | switch(operationId) {
525 | case 'validate-configuration':
526 | return this.callApi('PATCH', 200, true, base, body);
527 | break
528 | case 'deploy-configuration':
529 | return this.callApi('PATCH', 202, true, base, body);
530 | break
531 | case 'remove-deployed-configuration':
532 | return this.callApi('PATCH', 202, true, base, body);
533 | break
534 | case 'list-differences':
535 | return this.invalidOperation("PATCH operation 'list-differences' is not allowed")
536 | break
537 | default:
538 | return this.invalidOperation("PATCH operation is not allowed. Invalid operation id")
539 | }
540 | }
541 | }
542 |
543 |
544 | // Device Type patch operation on active version
545 | // Acceptable operation id - deactivate-configuration
546 | patchOperationActiveDeviceType(typeId, operationId) {
547 | var body = {
548 | "operation": operationId
549 | }
550 |
551 | if(this.draftMode) {
552 | return this.callApi('PATCH', 202, true, ['device', 'types', typeId], body);
553 | }
554 | else {
555 | return this.invalidOperation("PATCH operation 'deactivate-configuration' is not allowed");
556 | }
557 | }
558 |
559 | getDeviceTypeDeployedConfiguration(typeId) {
560 | if(this.draftMode) {
561 | return this.invalidOperation("GET deployed configuration is not allowed");
562 | } else {
563 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'deployedconfiguration']);
564 | }
565 | }
566 |
567 | getDeviceState(typeId, deviceId, logicalInterfaceId) {
568 | return this.callApi('GET', 200, true, ['device', 'types', typeId, 'devices', deviceId, 'state', logicalInterfaceId]);
569 | }
570 |
571 | createSchemaAndEventType(schemaContents, schemaFileName, eventTypeName, eventDescription) {
572 | var body = {
573 | 'schemaFile': schemaContents,
574 | 'schemaType': 'json-schema',
575 | 'name': schemaFileName
576 | }
577 |
578 | var createSchema = new Promise((resolve, reject) => {
579 | var base = this.draftMode ? ["draft", "schemas"] : ["schemas"]
580 | this.callFormDataApi('POST', 201, true, base, body, null).then(result => {
581 | resolve(result)
582 | }, error => {
583 | reject(error)
584 | })
585 | })
586 |
587 | return createSchema.then(value => {
588 | var schemaId = value["id"]
589 | return this.createEventType(eventTypeName, eventDescription, schemaId)
590 | })
591 | }
592 |
593 | createSchemaAndLogicalInterface(schemaContents, schemaFileName, appInterfaceName, appInterfaceDescription, appInterfaceAlias) {
594 | var body = {
595 | 'schemaFile': schemaContents,
596 | 'schemaType': 'json-schema',
597 | 'name': schemaFileName
598 | }
599 |
600 | var createSchema = new Promise((resolve, reject) => {
601 | var base = this.draftMode ? ["draft", "schemas"] : ["schemas"]
602 | this.callFormDataApi('POST', 201, true, base, body, null).then(result => {
603 | resolve(result)
604 | }, error => {
605 | reject(error)
606 | })
607 | })
608 |
609 | return createSchema.then(value => {
610 | var schemaId = value.id
611 | return this.createLogicalInterface(appInterfaceName, appInterfaceDescription, schemaId, appInterfaceAlias)
612 | })
613 | }
614 |
615 | createPhysicalInterfaceWithEventMapping(physicalInterfaceName, description, eventId, eventTypeId) {
616 | var createPhysicalInterface = new Promise((resolve, reject) => {
617 | this.createPhysicalInterface(physicalInterfaceName, description).then(result => {
618 | resolve(result)
619 | }, error => {
620 | reject(error)
621 | })
622 | })
623 |
624 | return createPhysicalInterface.then(value => {
625 | var physicalInterface = value
626 |
627 | var PhysicalInterfaceEventMapping = new Promise((resolve, reject) => {
628 | this.createPhysicalInterfaceEventMapping(physicalInterface.id, eventId, eventTypeId).then(result => {
629 | resolve([physicalInterface, result])
630 | }, error => {
631 | reject(error)
632 | })
633 | })
634 |
635 | return PhysicalInterfaceEventMapping.then(result => {
636 | return result
637 | })
638 | })
639 | }
640 |
641 | createDeviceTypeLogicalInterfaceEventMapping(deviceTypeName, description, logicalInterfaceId, eventMapping, notificationStrategy) {
642 | var createDeviceType = new Promise((resolve, reject) => {
643 | this.createDeviceType(deviceTypeName, description).then(result => {
644 | resolve(result)
645 | }, error => {
646 | reject(error)
647 | })
648 | })
649 |
650 | return createDeviceType.then(result => {
651 | var deviceObject = result
652 | var deviceTypeLogicalInterface = null
653 | var deviceTypeLogicalInterface = new Promise((resolve, reject) => {
654 | this.createDeviceTypeLogicalInterfaceAssociation(deviceObject.id, logicalInterfaceId).then(result => {
655 | resolve(result)
656 | }, error => {
657 | reject(error)
658 | })
659 | })
660 |
661 | return deviceTypeLogicalInterface.then(result => {
662 | deviceTypeLogicalInterface = result
663 | var deviceTypeLogicalInterfacePropertyMappings = new Promise((resolve, reject) => {
664 | var notificationstrategy = "never"
665 | if(notificationStrategy) {
666 | notificationstrategy = notificationStrategy
667 | }
668 |
669 | this.createDeviceTypeLogicalInterfacePropertyMappings(deviceObject.id, logicalInterfaceId, eventMapping, notificationstrategy).then(result => {
670 | var arr = [deviceObject, deviceTypeLogicalInterface, result]
671 | resolve(arr)
672 | }, error => {
673 | reject(error)
674 | })
675 | })
676 |
677 | return deviceTypeLogicalInterfacePropertyMappings.then(result => {
678 | return result
679 | })
680 | })
681 | })
682 | }
683 |
684 | };
--------------------------------------------------------------------------------
/src/application/ApplicationClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { isDefined } from '../util';
12 | import { default as BaseClient } from '../BaseClient';
13 | import { default as ApiClient } from '../api/ApiClient';
14 | import { default as RegistryClient } from '../api/RegistryClient';
15 | import { default as MgmtClient } from '../api/MgmtClient';
16 | import { default as LecClient } from '../api/LecClient';
17 | import { default as DscClient } from '../api/DscClient';
18 | import { default as RulesClient } from '../api/RulesClient';
19 | import { default as StateClient } from '../api/StateClient';
20 |
21 | import { default as ApplicationConfig } from './ApplicationConfig';
22 |
23 | const DEVICE_EVT_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/evt\/(.+)\/fmt\/(.+)$/;
24 | const DEVICE_CMD_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/cmd\/(.+)\/fmt\/(.+)$/;
25 | const DEVICE_STATE_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/intf\/(.+)\/evt\/state$/;
26 | const DEVICE_STATE_ERROR_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/err\/data$/;
27 | const RULE_TRIGGER_RE = /^iot-2\/intf\/(.+)\/rule\/(.+)\/evt\/trigger$/;
28 | const RULE_ERROR_RE = /^iot-2\/intf\/(.+)\/rule\/(.+)\/err\/data$/;
29 | const DEVICE_MON_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/mon$/;
30 | const APP_MON_RE = /^iot-2\/app\/(.+)\/mon$/;
31 |
32 | export default class ApplicationClient extends BaseClient {
33 | constructor(config) {
34 | if (!config instanceof ApplicationConfig) {
35 | throw new Error("Config must be an instance of ApplicationConfig");
36 | }
37 | super(config);
38 | this.useLtpa = config.auth && config.auth.useLtpa;
39 |
40 | if ((config.auth && config.auth.useLtpa) || config.getOrgId() != "quickstart") {
41 | this._apiClient = new ApiClient(this.config);
42 |
43 | this.dsc = new DscClient(this._apiClient);
44 | this.lec = new LecClient(this._apiClient);
45 | this.mgmt = new MgmtClient(this._apiClient);
46 | this.registry = new RegistryClient(this._apiClient);
47 | this.rules = new RulesClient(this._apiClient);
48 | this.state = new StateClient(this._apiClient);
49 | }
50 |
51 | this.log.debug("[ApplicationClient:constructor] ApplicationClient initialized for organization : " + config.getOrgId());
52 | }
53 |
54 |
55 | connect() {
56 | super.connect();
57 |
58 | this.mqtt.on('message', (topic, payload) => {
59 | this.log.trace("[ApplicationClient:onMessage] mqtt: ", topic, payload.toString());
60 |
61 | // For each type of registered callback, check the incoming topic against a Regexp.
62 | // If matches, forward the payload and various fields from the topic (extracted using groups in the regexp)
63 |
64 | var match = DEVICE_EVT_RE.exec(topic);
65 | if (match) {
66 | this.emit('deviceEvent', match[1], match[2], match[3], match[4], payload, topic);
67 | return;
68 | }
69 |
70 |
71 | var match = DEVICE_CMD_RE.exec(topic);
72 | if (match) {
73 | this.emit('deviceCommand', match[1], match[2], match[3], match[4], payload, topic);
74 | return;
75 | }
76 |
77 | var match = DEVICE_STATE_RE.exec(topic);
78 | if(match){
79 | this.emit('deviceState', match[1], match[2], match[3], payload, topic);
80 | return;
81 | }
82 |
83 | var match = DEVICE_STATE_ERROR_RE.exec(topic);
84 | if(match){
85 | this.emit('deviceStateError', match[1], match[2], payload, topic);
86 | return;
87 | }
88 |
89 | var match = RULE_TRIGGER_RE.exec(topic);
90 | if(match){
91 | this.emit('ruleTrigger', match[1], match[2], payload, topic);
92 | return;
93 | }
94 |
95 | var match = RULE_ERROR_RE.exec(topic);
96 | if(match){
97 | this.emit('ruleError', match[1], match[2], payload, topic);
98 | return;
99 | }
100 |
101 | var match = DEVICE_MON_RE.exec(topic);
102 | if (match) {
103 | this.emit('deviceStatus', match[1], match[2], payload, topic);
104 | return;
105 | }
106 |
107 | var match = APP_MON_RE.exec(topic);
108 | if (match) {
109 | this.emit('appStatus', match[1], payload, topic);
110 | return;
111 | }
112 |
113 | // catch all which logs the receipt of an unexpected message
114 | this.log.warn("[ApplicationClient:onMessage] Message received on unexpected topic" + ", " + topic + ", " + payload);
115 | });
116 | }
117 |
118 |
119 | // ==========================================================================
120 | // Device Events
121 | // ==========================================================================
122 |
123 | publishEvent(typeId, deviceId, eventId, format, data, qos, callback) {
124 | qos = qos || 0;
125 | if (!isDefined(typeId) || !isDefined(deviceId) || !isDefined(eventId) || !isDefined(format)) {
126 | this.log.error("[ApplicationClient:publishDeviceEvent] Required params for publishDeviceEvent not present");
127 | this.emit('error', "[ApplicationClient:publishDeviceEvent] Required params for publishDeviceEvent not present");
128 | return;
129 | }
130 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/evt/" + eventId + "/fmt/" + format;
131 | this._publish(topic, data, qos, callback);
132 | return this;
133 | }
134 |
135 | subscribeToEvents(typeId, deviceId, eventId, format, qos, callback) {
136 | typeId = typeId || '+';
137 | deviceId = deviceId || '+';
138 | eventId = eventId || '+';
139 | format = format || '+';
140 | qos = qos || 0;
141 |
142 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/evt/" + eventId + "/fmt/" + format;
143 | this._subscribe(topic, qos, callback);
144 | return this;
145 | }
146 |
147 | unsubscribeFromEvents(typeId, deviceId, eventId, format, callback) {
148 | typeId = typeId || '+';
149 | deviceId = deviceId || '+';
150 | eventId = eventId || '+';
151 | format = format || '+';
152 |
153 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/evt/" + eventId + "/fmt/" + format;
154 | this._unsubscribe(topic, callback);
155 | return this;
156 | }
157 |
158 |
159 | // ==========================================================================
160 | // Device Commands
161 | // ==========================================================================
162 |
163 | publishCommand(typeId, deviceId, commandId, format, data, qos, callback) {
164 | qos = qos || 0;
165 | if (!isDefined(typeId) || !isDefined(deviceId) || !isDefined(commandId) || !isDefined(format)) {
166 | this.log.error("[ApplicationClient:publishDeviceCommand] Required params for publishDeviceCommand not present");
167 | this.emit('error', "[ApplicationClient:publishDeviceCommand] Required params for publishDeviceCommand not present");
168 | return;
169 | }
170 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/cmd/" + commandId + "/fmt/" + format;
171 | this._publish(topic, data, qos, callback);
172 | return this;
173 | }
174 |
175 | subscribeToCommands(typeId, deviceId, commandId, format, qos, callback){
176 | typeId = typeId || '+';
177 | deviceId = deviceId || '+';
178 | commandId = commandId || '+';
179 | format = format || '+';
180 | qos = qos || 0;
181 |
182 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/cmd/" + commandId + "/fmt/" + format;
183 | this.log.debug("[ApplicationClient:subscribeToDeviceCommands] Calling subscribe with QoS " + qos);
184 | this._subscribe(topic, qos, callback);
185 | return this;
186 | }
187 |
188 | unsubscribeFromCommands(typeId, deviceId, commandId, format, callback) {
189 | typeId = typeId || '+';
190 | deviceId = deviceId || '+';
191 | commandId = commandId || '+';
192 | format = format || '+';
193 |
194 | var topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/cmd/" + commandId + "/fmt/" + format;
195 | this._unsubscribe(topic, callback);
196 | return this;
197 | }
198 |
199 |
200 | // ==========================================================================
201 | // Device State Events
202 | // ==========================================================================
203 |
204 | subscribeToDeviceState(type, id, interfaceId, qos){
205 | type = type || '+';
206 | id = id || '+';
207 | interfaceId = interfaceId || '+';
208 | qos = qos || 0;
209 |
210 | var topic = "iot-2/type/" + type + "/id/" + id + "/intf/"+ interfaceId + "/evt/state";
211 | this.log.debug("[ApplicationClient:subscribeToDeviceState] Calling subscribe with QoS "+qos);
212 | this._subscribe(topic, qos);
213 | return this;
214 | }
215 |
216 | unsubscribeToDeviceState(type, id, interfaceId){
217 | type = type || '+';
218 | id = id || '+';
219 | interfaceId = interfaceId || '+';
220 |
221 | var topic = "iot-2/type/" + type + "/id/" + id + "/intf/"+ interfaceId + "/evt/state";
222 | this._unsubscribe(topic);
223 | return this;
224 | }
225 |
226 |
227 | // ==========================================================================
228 | // Device State Errors
229 | // ==========================================================================
230 |
231 | subscribeToDeviceErrors(type, id, qos){
232 | type = type || '+';
233 | id = id || '+';
234 | qos = qos || 0;
235 |
236 | var topic = "iot-2/type/" + type + "/id/" + id + "/err/data";
237 | this.log.debug("[ApplicationClient:subscribeToDeviceErrors] Calling subscribe with QoS "+qos);
238 | this._subscribe(topic, qos);
239 | return this;
240 | }
241 |
242 | unsubscribeToDeviceErrors(type, id){
243 | type = type || '+';
244 | id = id || '+';
245 |
246 | var topic = "iot-2/type/" + type + "/id/" + id + "/err/data";
247 | this._unsubscribe(topic);
248 | return this;
249 | }
250 |
251 |
252 | // ==========================================================================
253 | // Rule Triggers
254 | // ==========================================================================
255 |
256 | subscribeToRuleTriggers(interfaceId, ruleId, qos){
257 | interfaceId = interfaceId || '+';
258 | ruleId = ruleId || '+';
259 | qos = qos || 0;
260 |
261 | var topic = "iot-2/intf/" + interfaceId + "/rule/" + ruleId + "/evt/trigger";
262 | this.log.debug("[ApplicationClient:subscribeToRuleTriggers] Calling subscribe with QoS "+qos);
263 | this._subscribe(topic, qos);
264 | return this;
265 | }
266 |
267 | unsubscribeToRuleTriggers(interfaceId, ruleId){
268 | interfaceId = interfaceId || '+';
269 | ruleId = ruleId || '+';
270 |
271 | var topic = "iot-2/intf/" + interfaceId + "/rule/" + ruleId + "/evt/trigger";
272 | this._unsubscribe(topic);
273 | return this;
274 | }
275 |
276 |
277 | // ==========================================================================
278 | // Rule Trigger Errors
279 | // ==========================================================================
280 |
281 | subscribeToRuleErrors(interfaceId, ruleId, qos){
282 | interfaceId = interfaceId || '+';
283 | ruleId = ruleId || '+';
284 | qos = qos || 0;
285 |
286 | var topic = "iot-2/intf/" + interfaceId + "/rule/" + ruleId + "/err/data";
287 | this.log.debug("[ApplicationClient:subscribeToRuleErrors] Calling subscribe with QoS "+qos);
288 | this._subscribe(topic, qos);
289 | return this;
290 | }
291 |
292 | unsubscribeToRuleErrors(interfaceId, ruleId){
293 | interfaceId = interfaceId || '+';
294 | ruleId = ruleId || '+';
295 |
296 | var topic = "iot-2/intf/" + interfaceId + "/rule/" + ruleId + "/err/data";
297 | this._unsubscribe(topic);
298 | return this;
299 | }
300 |
301 |
302 | // ==========================================================================
303 | // Device Status
304 | // ==========================================================================
305 |
306 | subscribeToDeviceStatus(type, id, qos) {
307 | type = type || '+';
308 | id = id || '+';
309 | qos = qos || 0;
310 |
311 | var topic = "iot-2/type/" + type + "/id/" + id + "/mon";
312 | this.log.debug("[ApplicationClient:subscribeToDeviceStatus] Calling subscribe with QoS " + qos);
313 | this._subscribe(topic, qos);
314 | return this;
315 | }
316 |
317 | unsubscribeToDeviceStatus(type, id) {
318 | type = type || '+';
319 | id = id || '+';
320 |
321 | var topic = "iot-2/type/" + type + "/id/" + id + "/mon";
322 | this._unsubscribe(topic);
323 | return this;
324 | }
325 |
326 | // ==========================================================================
327 | // Application Status
328 | // ==========================================================================
329 |
330 | subscribeToAppStatus(id, qos) {
331 | id = id || '+';
332 | qos = qos || 0;
333 |
334 | var topic = "iot-2/app/" + id + "/mon";
335 | this.log.debug("[ApplicationClient:subscribeToAppStatus] Calling subscribe with QoS " + qos);
336 | this._subscribe(topic, qos);
337 | return this;
338 | }
339 |
340 | unsubscribeToAppStatus(id) {
341 | id = id || '+';
342 |
343 | var topic = "iot-2/app/" + id + "/mon";
344 | this._unsubscribe(topic);
345 |
346 | return this;
347 | }
348 |
349 | };
--------------------------------------------------------------------------------
/src/application/ApplicationConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { default as BaseConfig } from '../BaseConfig';
12 | import log from 'loglevel';
13 | const YAML = require('yaml');
14 | const fs = require('fs');
15 |
16 | const { v4: uuidv4 } = require('uuid');
17 |
18 | export default class ApplicationConfig extends BaseConfig{
19 | constructor(identity, auth, options) {
20 | super(identity, auth, options);
21 |
22 | // Authentication is not supported for quickstart
23 | // and not required when auth.useLtpa is set
24 | if (this.auth != null && !this.auth.useLtpa) {
25 | if (!("key" in this.auth) || this.auth.key == null) {
26 | throw new Error("Missing auth.key from configuration");
27 | }
28 | if (!("token" in this.auth) || this.auth.token == null) {
29 | throw new Error("Missing auth.token from configuration");
30 | }
31 | }
32 |
33 | // Set defaults for optional configuration
34 | if (this.identity == null) {
35 | this.identity = {};
36 | }
37 | if (!("appId" in this.identity)) {
38 | this.identity.appId = uuidv4();
39 | }
40 |
41 | if (!("sharedSubscription" in this.options.mqtt)) {
42 | this.options.mqtt.sharedSubscription = false;
43 | }
44 | }
45 |
46 | getOrgId() {
47 | if (this.auth == null) {
48 | return "quickstart";
49 | } else if (this.auth.key) {
50 | return this.auth.key.split("-")[1];
51 | } else {
52 | return null;
53 | }
54 | }
55 |
56 | // Returns options.apiRoot if set, 'api/v0002' otherwiss
57 | getApiRoot() {
58 | return this.options.apiRoot || 'api/v0002';
59 | }
60 |
61 | getApiBaseUri() {
62 | return this.auth && this.auth.useLtpa
63 | ? `/${this.getApiRoot()}`
64 | : `https://${this.getOrgId()}.${this.options.domain}/${this.getApiRoot()}`;
65 | }
66 |
67 | getClientId() {
68 | let clientIdPrefix = "a";
69 | if (this.options.mqtt.sharedSubscription == true) {
70 | clientIdPrefix = "A";
71 | }
72 | return clientIdPrefix + ":" + this.getOrgId() + ":" + this.identity.appId;
73 | }
74 |
75 | getMqttUsername() {
76 | return this.auth.key;
77 | }
78 |
79 | getMqttPassword() {
80 | return this.auth.token;
81 | }
82 |
83 | getAdditionalHeaders() {
84 | return this.options && this.options.http
85 | ? this.options.http.additionalHeaders
86 | : [];
87 | }
88 |
89 | setAdditionalHeader(key, value) {
90 | if(!this.options.http) this.options.http = {};
91 | if(!this.options.http.additionalHeaders) this.options.http.additionalHeaders = {}
92 | this.options.http.additionalHeaders[key] = value;
93 | }
94 |
95 | static parseEnvVars() {
96 | // Auth
97 | let authKey = process.env.WIOTP_AUTH_KEY || null;
98 | let authToken = process.env.WIOTP_AUTH_TOKEN || null;
99 |
100 | // Also support WIOTP_API_KEY / WIOTP_API_TOKEN usage
101 | if (authKey == null && authToken == null) {
102 | authKey = process.env.WIOTP_API_KEY || null;
103 | authToken = process.env.WIOTP_API_TOKEN || null;
104 | }
105 |
106 | // Identity
107 | let appId = process.env.WIOTP_IDENTITY_APPID || uuidv4();
108 |
109 | // Options
110 | let domain = process.env.WIOTP_OPTIONS_DOMAIN || null;
111 | let apiRoot = process.env.WIOTP_OPTIONS_API_ROOT || null;
112 | let logLevel = process.env.WIOTP_OPTIONS_LOGLEVEL || "info";
113 | let port = process.env.WIOTP_OPTIONS_MQTT_PORT || null;
114 | let transport = process.env.WIOTP_OPTIONS_MQTT_TRANSPORT || null;
115 | let caFile = process.env.WIOTP_OPTIONS_MQTT_CAFILE || null;
116 | let cleanStart = process.env.WIOTP_OPTIONS_MQTT_CLEANSTART || "true";
117 | let sessionExpiry = process.env.WIOTP_OPTIONS_MQTT_SESSIONEXPIRY || 3600;
118 | let keepAlive = process.env.WIOTP_OPTIONS_MQTT_KEEPALIVE || 60;
119 | let sharedSubs = process.env.WIOTP_OPTIONS_MQTT_SHAREDSUBSCRIPTION || "false";
120 | let verifyCert = process.env.WIOTP_OPTIONS_HTTP_VERIFY || "true";
121 |
122 | // TODO: add environment variable parsing for options.http.additionalHeaders
123 |
124 | // String to int conversions
125 | if (port != null) {
126 | port = parseInt(port);
127 | }
128 | sessionExpiry = parseInt(sessionExpiry);
129 | keepAlive = parseInt(keepAlive)
130 |
131 | let identity = {appId: appId};
132 | let options = {
133 | domain: domain,
134 | apiRoot: apiRoot,
135 | logLevel: logLevel,
136 | mqtt: {
137 | port: port,
138 | transport: transport,
139 | cleanStart: (["True", "true", "1"].includes(cleanStart)),
140 | sessionExpiry: sessionExpiry,
141 | keepAlive: keepAlive,
142 | sharedSubscription: (["True", "true", "1"].includes(sharedSubs)),
143 | caFile: caFile,
144 | },
145 | http: {
146 | verify: (["True", "true", "1"].includes(verifyCert))
147 | },
148 | };
149 | let auth = null;
150 | // Quickstart doesn't support auth, so ensure we only add this if it's defined
151 | if (authToken != null) {
152 | auth = {key: authKey, token: authToken};
153 | }
154 |
155 | return new ApplicationConfig(identity, auth, options);
156 | }
157 |
158 | static parseConfigFile(configFilePath) {
159 |
160 | //Example Application Configuration File:
161 |
162 | /*
163 | identity:
164 | appId: myApp
165 | auth:
166 | key: a-23gh56-sdsdajhjnee
167 | token: Ab$76s)asj8_s5
168 | options:
169 | domain: internetofthings.ibmcloud.com
170 | apiRoot: 'api/platform'
171 | logLevel: error|warning|info|debug
172 | mqtt:
173 | instanceId: myInstance
174 | port: 8883
175 | transport: tcp
176 | cleanStart: false
177 | sessionExpiry: 3600
178 | keepAlive: 60
179 | caFile: /path/to/certificateAuthorityFile.pem
180 | http:
181 | verify: true
182 | additionalHeaders:
183 | X-Csrf-Token: sdfsdfsdf
184 | X-myheader: hello
185 | */
186 |
187 | const configFile = fs.readFileSync(configFilePath, 'utf8');
188 | var data = YAML.parse(configFile);
189 |
190 | if(!fs.existsSync(configFilePath)) {
191 | throw new Error("File not found");
192 | }else
193 | {try {
194 | const configFile = fs.readFileSync(configFilePath, 'utf8');
195 | var data = YAML.parse(configFile);
196 | } catch (err) {
197 | throw new Error("Error reading device configuration file: " + err.code);
198 | }
199 | }
200 |
201 |
202 | if (("options" in data) & ("logLevel" in data["options"]))
203 | {
204 | var validLevels = ["error", "warning", "info", "debug"];
205 | if (!(validLevels.includes(data["options"]["logLevel"])))
206 | {
207 | throw new Error("Optional setting options.logLevel (Currently: " + data["options"]["logLevel"] + ") must be one of error, warning, info, debug")
208 | }
209 | }
210 | else
211 | {
212 | data["options"]["logLevel"] = log.GetLogger(data["options"]["logLevel"].toUpperCase());
213 | }
214 |
215 | return new ApplicationConfig(data['identity'],data['auth'],data['options'])
216 | }
217 | }
--------------------------------------------------------------------------------
/src/application/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export { default as ApplicationClient } from './ApplicationClient';
13 | export { default as ApplicationConfig } from './ApplicationConfig';
--------------------------------------------------------------------------------
/src/device/DeviceClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { isDefined } from '../util';
12 | import { default as BaseClient } from '../BaseClient';
13 | import { default as DeviceConfig } from './DeviceConfig';
14 |
15 | const WILDCARD_TOPIC = 'iot-2/cmd/+/fmt/+';
16 | const CMD_RE = /^iot-2\/cmd\/(.+)\/fmt\/(.+)$/;
17 |
18 | const util = require('util');
19 |
20 | export default class DeviceClient extends BaseClient {
21 |
22 | constructor(config){
23 | if (!config instanceof DeviceConfig) {
24 | throw new Error("Config must be an instance of DeviceConfig");
25 | }
26 | super(config);
27 |
28 | this.log.debug("[DeviceClient:constructor] DeviceClient initialized for " + config.getClientId());
29 | }
30 |
31 | _commandSubscriptionCallback(err, granted) {
32 | if (err == null) {
33 | for (var index in granted) {
34 | let grant = granted[index];
35 | this.log.debug("[DeviceClient:connect] Subscribed to device commands on " + grant.topic + " at QoS " + grant.qos);
36 | }
37 | } else {
38 | this.log.error("[DeviceClient:connect] Unable to establish subscription for device commands: " + err);
39 | this.emit("error", new Error("Unable to establish subscription for device commands: " + err));
40 | }
41 | }
42 |
43 | connect(){
44 | super.connect();
45 |
46 | this.mqtt.on('connect', () => {
47 | // On connect establish a subscription for commands sent to this device (but not if connecting to quickstart)
48 | if(!this.config.isQuickstart()){
49 | // You need to bind a particular this context to the method before you can use it as a callback
50 | this.mqtt.subscribe(WILDCARD_TOPIC, { qos: 1 }, this._commandSubscriptionCallback.bind(this));
51 | }
52 | });
53 |
54 | this.mqtt.on('message', (topic, payload) => {
55 | this.log.debug("[DeviceClient:onMessage] Message received on topic : "+ topic + " with payload : "+ payload);
56 |
57 | let match = CMD_RE.exec(topic);
58 |
59 | if (match) {
60 | this.emit('command', match[1], match[2], payload, topic);
61 | }
62 | });
63 | }
64 |
65 | publishEvent(eventId, format, data, qos, callback){
66 | qos = qos || 0;
67 |
68 | if (!isDefined(eventId) || !isDefined(format)) {
69 | this.log.error("[DeviceClient:publishEvent] Required params for publishEvent not present");
70 | this.emit('error', "[DeviceClient:publishEvent] Required params for publishEvent not present");
71 | return;
72 | }
73 |
74 | let topic = util.format("iot-2/evt/%s/fmt/%s", eventId, format);
75 | this._publish(topic, data, qos, callback);
76 | return this;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/device/DeviceConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { default as BaseConfig } from '../BaseConfig';
12 | import log from 'loglevel';
13 | const YAML = require('yaml');
14 | const fs = require('fs');
15 |
16 |
17 | export default class DeviceConfig extends BaseConfig{
18 | constructor(identity, auth, options) {
19 | super(identity, auth, options);
20 |
21 | //Validate the arguments
22 | if (this.identity == null) {
23 | throw new Error("Missing identity from configuration");
24 | }
25 | if (!("orgId" in this.identity) || this.identity.orgId == null) {
26 | throw new Error("Missing identity.orgId from configuration");
27 | }
28 | if (!("typeId" in this.identity) || this.identity.typeId == null) {
29 | throw new Error("Missing identity.typeId from configuration");
30 | }
31 | if (!("deviceId" in this.identity) || this.identity.deviceId == null) {
32 | throw new Error("Missing identity.deviceId from configuration");
33 | }
34 |
35 | // Authentication is not supported for quickstart
36 | if (this.identity.orgId == "quickstart") {
37 | if (this.auth != null) {
38 | throw new Error("Quickstart service does not support device authentication");
39 | }
40 | } else {
41 | if (this.auth == null) {
42 | throw new Error("Missing auth from configuration");
43 | }
44 | if (!("token" in this.auth) || this.auth.token == null) {
45 | throw new Error("Missing auth.token from configuration");
46 | }
47 | }
48 |
49 | }
50 |
51 | getOrgId() {
52 | return this.identity.orgId;
53 | }
54 |
55 | getClientId() {
56 | return "d:" + this.identity.orgId + ":" + this.identity.typeId + ":" + this.identity.deviceId;
57 | }
58 |
59 | getMqttUsername() {
60 | return "use-token-auth";
61 | }
62 | getMqttPassword() {
63 | if (this.identity.orgId != "quickstart")
64 | {
65 | return this.auth.token;
66 | }
67 | else{
68 | return null;
69 | }
70 | }
71 |
72 | static parseEnvVars() {
73 |
74 | //Identity
75 | let orgId = process.env.WIOTP_IDENTITY_ORGID || null;
76 | let typeId = process.env.WIOTP_IDENTITY_TYPEID || null;
77 | let deviceId = process.env.WIOTP_IDENTITY_DEVICEID || null;
78 |
79 | // Auth
80 | let authToken = process.env.WIOTP_AUTH_TOKEN || null;
81 |
82 | // Also support WIOTP_API_TOKEN usage
83 | if (authToken == null) {
84 | authToken = process.env.WIOTP_API_TOKEN || null;
85 | }
86 |
87 | // Options
88 | let domain = process.env.WIOTP_OPTIONS_DOMAIN || null;
89 | let logLevel = process.env.WIOTP_OPTIONS_LOGLEVEL || "info";
90 | let port = process.env.WIOTP_OPTIONS_MQTT_PORT || null;
91 | let transport = process.env.WIOTP_OPTIONS_MQTT_TRANSPORT || null;
92 | let caFile = process.env.WIOTP_OPTIONS_MQTT_CAFILE || null;
93 | let cleanStart = process.env.WIOTP_OPTIONS_MQTT_CLEANSTART || "true";
94 | let sessionExpiry = process.env.WIOTP_OPTIONS_MQTT_SESSIONEXPIRY || 3600;
95 | let keepAlive = process.env.WIOTP_OPTIONS_MQTT_KEEPALIVE || 60;
96 | let sharedSubs = process.env.WIOTP_OPTIONS_MQTT_SHAREDSUBSCRIPTION || "false";
97 |
98 | // String to int conversions
99 | if (port != null) {
100 | port = parseInt(port);
101 | }
102 | sessionExpiry = parseInt(sessionExpiry);
103 | keepAlive = parseInt(keepAlive)
104 |
105 | let identity = {orgId:orgId, typeId: typeId, deviceId:deviceId};
106 | let options = {
107 | domain: domain,
108 | logLevel: logLevel,
109 | mqtt: {
110 | port: port,
111 | transport: transport,
112 | cleanStart: (["True", "true", "1"].includes(cleanStart)),
113 | sessionExpiry: sessionExpiry,
114 | keepAlive: keepAlive,
115 | sharedSubscription: (["True", "true", "1"].includes(sharedSubs)),
116 | caFile: caFile,
117 | },
118 | };
119 | let auth = null;
120 | // Quickstart doesn't support auth, so ensure we only add this if it's defined
121 | if (authToken != null) {
122 | auth = {token: authToken};
123 | }
124 |
125 | return new DeviceConfig(identity, auth, options);
126 | }
127 |
128 | static parseConfigFile(configFilePath) {
129 |
130 | //Example Device Configuration File:
131 |
132 | /*
133 | identity:
134 | orgId: org1id
135 | typeId: raspberry-pi-3
136 | deviceId: 00ef08ac05
137 | auth:
138 | token: Ab$76s)asj8_s5
139 | options:
140 | domain: internetofthings.ibmcloud.com
141 | logLevel: error|warning|info|debug
142 | mqtt:
143 | port: 8883
144 | transport: tcp
145 | cleanStart: true
146 | sessionExpiry: 3600
147 | keepAlive: 60
148 | caFile: /path/to/certificateAuthorityFile.pem
149 | */
150 |
151 | const configFile = fs.readFileSync(configFilePath, 'utf8');
152 | var data = YAML.parse(configFile);
153 |
154 |
155 | if(!fs.existsSync(configFilePath)) {
156 | throw new Error("File not found");
157 | }else
158 | {try {
159 | const configFile = fs.readFileSync(configFilePath, 'utf8');
160 | var data = YAML.parse(configFile);
161 | } catch (err) {
162 | throw new Error("Error reading device configuration file: " + err.code);
163 | }
164 | }
165 |
166 |
167 | if (("options" in data) & ("logLevel" in data["options"]))
168 | {
169 | var validLevels = ["error", "warning", "info", "debug"];
170 | if (!(validLevels.includes(data["options"]["logLevel"])))
171 | {
172 | throw new Error("Optional setting options.logLevel (Currently: " + data["options"]["logLevel"] + ") must be one of error, warning, info, debug")
173 | }
174 | }
175 | else
176 | {
177 | data["options"]["logLevel"] = log.GetLogger(data["options"]["logLevel"].toUpperCase());
178 | }
179 |
180 | return new DeviceConfig(data['identity'],data['auth'],data['options'])
181 | }
182 | }
--------------------------------------------------------------------------------
/src/device/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export { default as DeviceClient } from './DeviceClient';
13 | export { default as DeviceConfig } from './DeviceConfig';
14 |
15 |
--------------------------------------------------------------------------------
/src/gateway/GatewayClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { default as BaseClient } from '../BaseClient';
12 | import { default as GatewayConfig } from './GatewayConfig';
13 |
14 | const CMD_RE = /^iot-2\/type\/(.+)\/id\/(.+)\/cmd\/(.+)\/fmt\/(.+)$/;
15 |
16 | const util = require('util');
17 |
18 | export default class GatewayClient extends BaseClient {
19 |
20 | constructor(config){
21 | if (!config instanceof GatewayConfig) {
22 | throw new Error("Config must be an instance of GatewayConfig");
23 | }
24 | if (config.isQuickstart()) {
25 | throw new Error('[GatewayClient:constructor] Quickstart not supported in Gateways');
26 | }
27 | super(config);
28 |
29 | this.log.debug("[GatewayClient:constructor] GatewayClient initialized for " + config.getClientId());
30 | }
31 |
32 |
33 | connect(){
34 | super.connect();
35 |
36 | this.mqtt.on('connect', () => {
37 | // This gateway client implemention does not automatically subscribe to any commands!?
38 | });
39 |
40 | this.mqtt.on('message', (topic, payload) => {
41 | this.log.debug("[GatewayClient:onMessage] Message received on topic : "+ topic + " with payload : "+ payload);
42 |
43 | let match = CMD_RE.exec(topic);
44 | if(match){
45 | this.emit('command', match[1], match[2], match[3], match[4], payload, topic);
46 | }
47 | });
48 | }
49 |
50 |
51 | publishEvent(eventId, format, payload, qos, callback){
52 | return this._publishEvent(this.config.identity.typeId, this.config.identity.deviceId, eventId, format, payload, qos, callback);
53 | }
54 |
55 |
56 | publishDeviceEvent(typeid, deviceId, eventid, format, payload, qos, callback){
57 | return this._publishEvent(typeId, deviceId, eventid, format, payload, qos, callback);
58 | }
59 |
60 |
61 | _publishEvent(typeId, deviceId, eventId, format, data, qos, callback){
62 | let topic = util.format("iot-2/type/%s/id/%s/evt/%s/fmt/%s", typeId, deviceId, eventId, format);
63 | qos = qos || 0;
64 | this._publish(topic, data, qos, callback);
65 | return this;
66 | }
67 |
68 |
69 | subscribeToDeviceCommands(typeId, deviceId, commandId, format, qos, callback){
70 | typeId = typeId || '+';
71 | deviceId = deviceId || '+';
72 | commandId = commandid || '+';
73 | format = format || '+';
74 | qos = qos || 0;
75 |
76 | let topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/cmd/"+ commandId + "/fmt/" + format;
77 | this._subscribe(topic,qos, callback);
78 | return this;
79 | }
80 |
81 |
82 | unsubscribeFromDeviceCommands(typeId, deviceId, commandId, format, callback){
83 | typeId = typeId || '+';
84 | deviceId = deviceId || '+';
85 | commandId = commandId || '+';
86 | format = format || '+';
87 |
88 | let topic = "iot-2/type/" + typeId + "/id/" + deviceId + "/cmd/"+ commandId + "/fmt/" + format;
89 | this._unsubscribe(topic, callback);
90 | return this;
91 | }
92 |
93 |
94 | subscribeToCommands(commandId, format, qos, callback){
95 | commandId = commandId || '+';
96 | format = format || '+';
97 | qos = qos || 0;
98 |
99 | let topic = "iot-2/type/" + this.config.identity.typeId + "/id/" + this.config.identity.deviceId + "/cmd/"+ commandId + "/fmt/" + format;
100 | this._subscribe(topic, qos, callback);
101 | return this;
102 | }
103 |
104 |
105 | unsubscribeFromCommands(commandId, format, callback){
106 | commandId = commandId || '+';
107 | format = format || '+';
108 |
109 | let topic = "iot-2/type/" + this.config.identity.typeId + "/id/" + this.config.identity.deviceId + "/cmd/"+ commandId + "/fmt/" + format;
110 | this._unsubscribe(topic, callback);
111 | return this;
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/gateway/GatewayConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { default as DeviceConfig } from '../device/DeviceConfig';
12 |
13 | export default class GatewayConfig extends DeviceConfig{
14 | constructor(identity, auth, options) {
15 | super(identity, auth, options);
16 | }
17 |
18 | getClientId() {
19 | return "g:" + this.identity.orgId + ":" + this.identity.typeId + ":" + this.identity.deviceId;
20 | }
21 |
22 | };
--------------------------------------------------------------------------------
/src/gateway/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export { default as GatewayClient } from './GatewayClient';
13 | export { default as GatewayConfig } from './GatewayConfig';
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export { default as ApplicationClient } from './application/ApplicationClient';
13 | export { default as DeviceClient } from './device/DeviceClient';
14 | export { default as GatewayClient } from './gateway/GatewayClient';
15 |
16 | export { default as ApplicationConfig } from './application/ApplicationConfig';
17 | export { default as DeviceConfig } from './device/DeviceConfig';
18 |
19 | export { default as ApiClient} from './api/ApiClient';
20 | export { default as ApiErrors} from './api/ApiErrors';
21 | export { default as DscClient} from './api/DscClient';
22 | export { default as LecClient} from './api/LecClient';
23 | export { default as MgmtClient} from './api/MgmtClient';
24 | export { default as RegistryClient} from './api/RegistryClient';
25 | export { default as RulesClient} from './api/RulesClient';
26 | export { default as StateClient} from './api/StateClient';
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | export function isString(value){
13 | return typeof value === 'string';
14 | }
15 |
16 | export function isNumber(value){
17 | return typeof value === 'number';
18 | }
19 |
20 | export function isBoolean(value){
21 | return typeof value === 'boolean';
22 | }
23 |
24 | export function isDefined(value){
25 | return value !== undefined && value !== null;
26 | }
27 |
28 | export const isBrowser = new Function("try {return this===window;}catch(e){ return false;}");
29 |
30 | export const isNode = new Function("try {return this===global;}catch(e){return false;}");
31 |
32 | export function generateUUID() {
33 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
34 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
35 | return v.toString(16);
36 | });
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/test/ApplicationClient.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { ApplicationConfig, ApplicationClient } from '../src/application';
12 | import { assert } from 'chai';
13 |
14 | // Turn off console output
15 | console.info = () => {};
16 |
17 |
18 | describe('WIoTP Application Capabilities', function() {
19 |
20 | let appConfig = ApplicationConfig.parseEnvVars();
21 |
22 | describe("Event Publication and Subscription", function() {
23 | let testTypeId = "foo";
24 | let testDeviceId = "bar";
25 | let testEventId = "testEvent";
26 | let testEventFormat = "json";
27 | let testEventData = "{'foo': 'bar'}";
28 | let testQos = 1;
29 |
30 | let appClient = null;
31 |
32 | before("Initialize the application", function(){
33 | appClient = new ApplicationClient(appConfig);
34 | })
35 |
36 | afterEach("Remove error listener(s)", function() {
37 | appClient.removeAllListeners("error");
38 | });
39 |
40 | step("Connect within 5 seconds", function(done){
41 | this.timeout(5000);
42 | appClient.on("connect", done);
43 | appClient.on("error", done);
44 | appClient.connect();
45 | });
46 |
47 | step("Subscribe to device events", function(done){
48 | let onSubscribe = function(err, granted) {
49 | if (err != null) {
50 | done(err);
51 | }
52 | done();
53 | }
54 | appClient.on("error", done);
55 | appClient.subscribeToEvents(testTypeId, testDeviceId, testEventId, testEventFormat, testQos, onSubscribe);
56 | });
57 |
58 | step("Publish & recieve device event", function(done){
59 | let onEventSent = function(err) {
60 | if (err != null) {
61 | done(err);
62 | }
63 | }
64 |
65 | let onEventReceived = function(typeId, deviceId, eventId, format, payload) {
66 | assert(typeId == testTypeId, "Type ID does not match");
67 | assert(deviceId == testDeviceId, "Device ID does not match");
68 | assert(eventId == testEventId, "Event ID does not match");
69 | assert(format == testEventFormat, "Format does not match");
70 | assert(payload == testEventData, "Payload does not match");
71 | done();
72 | }
73 |
74 | appClient.on("error", done);
75 | appClient.on("deviceEvent", onEventReceived);
76 |
77 | appClient.publishEvent(testTypeId, testDeviceId, testEventId, testEventFormat, testEventData, testQos, onEventSent);
78 | });
79 |
80 | step("Disconnect the MQTT client", function(done) {
81 | appClient.on("error", done);
82 | appClient.on("close", function() {
83 | assert(appClient.isConnected() == false, "Application is still connected");
84 | done();
85 | });
86 | appClient.disconnect();
87 | });
88 |
89 | after("Ensure the the MQTT client is disconnected", function() {
90 | if (appClient != null && appClient.isConnected()) {
91 | appClient.disconnect();
92 | }
93 | });
94 | });
95 |
96 | describe("Command Publication and Subscription", function() {
97 | let testTypeId = "foo";
98 | let testDeviceId = "bar";
99 | let testCommandId = "testCommand";
100 | let testCommandFormat = "json";
101 | let testCommandData = "{'foo': 'bar'}";
102 | let testQos = 1;
103 |
104 | let appClient = null;
105 |
106 | before("Initialize the application", function(){
107 | appClient = new ApplicationClient(appConfig);
108 | })
109 |
110 | afterEach("Remove error listener(s)", function() {
111 | appClient.removeAllListeners("error");
112 | });
113 |
114 | step("Connect within 5 seconds", function(done){
115 | this.timeout(5000);
116 | appClient.on("connect", done);
117 | appClient.on("error", done);
118 | appClient.connect();
119 | });
120 |
121 | step("Subscribe to device command", function(done){
122 | let onSubscribe = function(err, granted) {
123 | if (err != null) {
124 | done(err);
125 | }
126 | done();
127 | }
128 | appClient.on("error", done);
129 | appClient.subscribeToCommands(testTypeId, testDeviceId, testCommandId, testCommandFormat, testQos, onSubscribe);
130 | });
131 |
132 | step("Publish & recieve device command", function(done){
133 | let onCommandSent = function(err) {
134 | if (err != null) {
135 | done(err);
136 | }
137 | }
138 |
139 | let onCommandReceived = function(typeId, deviceId, commandId, format, payload) {
140 | assert(typeId == testTypeId, "Type ID does not match");
141 | assert(deviceId == testDeviceId, "Device ID does not match");
142 | assert(commandId == testCommandId, "Command ID does not match");
143 | assert(format == testCommandFormat, "Format does not match");
144 | assert(payload == testCommandData, "Payload does not match");
145 | done();
146 | }
147 |
148 | appClient.on("error", done);
149 | appClient.on("deviceCommand", onCommandReceived);
150 |
151 | appClient.publishCommand(testTypeId, testDeviceId, testCommandId, testCommandFormat, testCommandData, testQos, onCommandSent);
152 | });
153 |
154 | step("Disconnect the MQTT client", function(done) {
155 | appClient.on("error", done);
156 | appClient.on("close", function() {
157 | assert(appClient.isConnected() == false, "Application is still connected");
158 | done();
159 | });
160 | appClient.disconnect();
161 | });
162 |
163 | after("Ensure the the MQTT client is disconnected", function() {
164 | if (appClient != null && appClient.isConnected()) {
165 | appClient.disconnect();
166 | }
167 | });
168 | });
169 |
170 | // TODO: Add test coverage
171 | describe("Subscribe & receive device status updates", function() {});
172 | describe("Subscribe & receive app status updates", function() {});
173 |
174 | describe("Subscribe & receive device state events", function() {});
175 | describe("Subscribe & receive device state errors", function() {});
176 |
177 | describe("Subscribe & receive rule trigger events", function() {});
178 | describe("Subscribe & receive rule trigger errors", function() {});
179 |
180 | });
181 |
--------------------------------------------------------------------------------
/test/ApplicationConfig.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { ApplicationConfig } from '../src/application';
12 | import { expect } from 'chai';
13 |
14 | // Turn off console output
15 | console.info = () => {};
16 |
17 | describe('WIoTP Application Configuration', () => {
18 |
19 | it('Initialise with the minimum required configuration', () => {
20 | let identity = null;
21 | let auth = {key: "a-orgid-sssssss", token: "test"};
22 | let options = null;
23 |
24 | let config = new ApplicationConfig(identity, auth, options);
25 | expect(config.getOrgId()).to.equal("orgid");
26 | expect(config.identity.appId).to.not.be.null; // Should be a generated UUID
27 | expect(config.options.logLevel).to.equal("info");
28 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
29 | expect(config.options.mqtt.transport).to.equal("tcp");
30 | expect(config.options.mqtt.port).to.equal(8883);
31 | });
32 |
33 | it('Load mimimal configuration from environment variables', () => {
34 | let config = ApplicationConfig.parseEnvVars();
35 | expect(config.getOrgId()).to.equal(process.env.WIOTP_API_KEY.split("-")[1]);
36 | expect(config.identity.appId).to.not.be.null; // Should be a generated UUID
37 | expect(config.options.logLevel).to.equal("info");
38 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
39 | expect(config.options.mqtt.transport).to.equal("tcp");
40 | expect(config.options.mqtt.port).to.equal(8883);
41 | });
42 |
43 | it('Load configuration from yaml config file', () => {
44 | let config = ApplicationConfig.parseConfigFile("./test/ApplicationConfigFile.spec.yaml");
45 | expect(config.identity.appId).to.equal("myApp")
46 | expect(config.auth.key).to.equal("myKey")
47 | expect(config.auth.token).to.equal("myToken")
48 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
49 | expect(config.options.logLevel).to.equal("info");
50 | expect(config.options.apiRoot).equal('api/v0002');
51 | expect(config.options.mqtt.port).to.equal(8883);
52 | expect(config.options.mqtt.transport).to.equal("tcp");
53 | expect(config.options.mqtt.cleanStart).to.equal(false);
54 | expect(config.options.mqtt.sessionExpiry).to.equal(3600);
55 | expect(config.options.mqtt.keepAlive).to.equal(60);
56 | expect(config.options.mqtt.caFile).to.equal("myPath");
57 | expect(config.options.http.verify).to.equal(true);
58 | expect(config.options.http.additionalHeaders).to.deep.equal({hello: 'world', name: 'tom'});
59 | });
60 |
61 | it('Missing auth.key throws error', () => {
62 | let identity = null;
63 | let auth = {key: null};
64 | let options = null;
65 | var applicationConfigTest = function(){new ApplicationConfig(identity, auth, options)};
66 | expect(applicationConfigTest).to.throw('Missing auth.key from configuration');
67 | });
68 |
69 | it('Missing auth.token throws error', () => {
70 | let identity = null;
71 | let auth = {key: "MyKey", token: null};
72 | let options = null;
73 | var applicationConfigTest = function(){new ApplicationConfig(identity, auth, options)};
74 | expect(applicationConfigTest).to.throw('Missing auth.token from configuration');
75 | });
76 |
77 | it('Initialise without auth to induce quickstart', () => {
78 | let identity = null;
79 | let auth = null;
80 | let options = null;
81 |
82 | let config = new ApplicationConfig(identity, auth, options);
83 | expect(config.getOrgId()).to.equal("quickstart");
84 | });
85 |
86 | it('Load port as a string with environment variables', () => {
87 | process.env['WIOTP_OPTIONS_MQTT_PORT'] = '8883';
88 | let config = ApplicationConfig.parseEnvVars();
89 | expect(config.options.mqtt.port).to.equal(8883);
90 | });
91 |
92 | it('Incorrect logLevel in config file throws error', () => {
93 | var applicationConfigTest = function(){new ApplicationConfig.parseConfigFile("./test/incorrectLogLevelTest.spec.yaml")};
94 | expect(applicationConfigTest).to.throw('Optional setting options.logLevel (Currently: notALogLevel) must be one of error, warning, info, debug');
95 | });
96 |
97 | });
98 |
--------------------------------------------------------------------------------
/test/ApplicationConfigFile.spec.yaml:
--------------------------------------------------------------------------------
1 |
2 | identity:
3 | appId: myApp
4 | auth:
5 | key: myKey
6 | token: myToken
7 | options:
8 | apiRoot: api/v0002
9 | domain: internetofthings.ibmcloud.com
10 | logLevel: info
11 | mqtt:
12 | instanceId: myInstance
13 | port: 8883
14 | transport: tcp
15 | cleanStart: false
16 | sessionExpiry: 3600
17 | keepAlive: 60
18 | caFile: myPath
19 | http:
20 | verify: true
21 | additionalHeaders:
22 | hello: world
23 | name: tom
--------------------------------------------------------------------------------
/test/DeviceClient.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { ApplicationConfig, ApplicationClient } from '../src/application';
12 | import { DeviceConfig, DeviceClient } from '../src/device';
13 | import { assert } from 'chai';
14 | import { step } from 'mocha-steps';
15 |
16 | const { v4: uuidv4 } = require('uuid');
17 |
18 | // Turn off console output
19 | console.info = () => {};
20 |
21 | describe('WIoTP Device Capabilities', function() {
22 |
23 | let appConfig = ApplicationConfig.parseEnvVars();
24 |
25 | describe("Event Publication", function() {
26 | let testTypeId = "iotnodejs-test";
27 | let testDeviceId = uuidv4();
28 | let testEventId = "testEvent";
29 | let testEventFormat = "json";
30 | let testEventData = "{'foo': 'bar'}";
31 | let testQos = 1;
32 |
33 | let appClient = null;
34 | let deviceConfig = null;
35 | let deviceClient = null;
36 |
37 | before("Register the test device", function(done){
38 | this.timeout(10000);
39 | appClient = new ApplicationClient(appConfig);
40 |
41 | // Register the device type
42 | appClient.registry.registerDeviceType(testTypeId)
43 | .catch(function(err){
44 | if (err.response.status != 409) {
45 | // Anything other than conflict is bad!
46 | throw err;
47 | }
48 | console.info("device type already exists, carry on!")
49 | })
50 | .then(function(result) {
51 | // Register the device
52 | return appClient.registry.registerDevice(testTypeId, testDeviceId);
53 | })
54 | .then(function(deviceDetails) {
55 | console.info(deviceDetails);
56 | let identity = {orgId: appConfig.getOrgId(), typeId: deviceDetails.typeId, deviceId: deviceDetails.deviceId};
57 | let auth = {token: deviceDetails.authToken}
58 | let options = {logLevel: "info"};
59 | deviceConfig = new DeviceConfig(identity, auth, options);
60 | deviceClient = new DeviceClient(deviceConfig);
61 | done();
62 | })
63 | .catch(function(err){
64 | done(err);
65 | });
66 | })
67 |
68 | afterEach("Remove error listener(s)", function() {
69 | appClient.removeAllListeners("error");
70 | deviceClient.removeAllListeners("error");
71 | });
72 |
73 | step("Connect application within 5 seconds", function(done){
74 | this.timeout(5000);
75 | appClient.on("connect", done);
76 | appClient.on("error", done);
77 | appClient.connect();
78 | });
79 |
80 | step("Connect device within 5 seconds", function(done){
81 | this.timeout(5000);
82 | deviceClient.on("connect", done);
83 | deviceClient.on("error", done);
84 | deviceClient.connect();
85 | });
86 |
87 | step("Subscribe to device events", function(done){
88 | let onSubscribe = function(err, granted) {
89 | if (err != null) {
90 | done(err);
91 | }
92 | done();
93 | }
94 | appClient.on("error", done);
95 | appClient.subscribeToEvents(testTypeId, testDeviceId, testEventId, testEventFormat, testQos, onSubscribe);
96 | });
97 |
98 | step("Publish & recieve device event within 10 seconds", function(done){
99 | this.timeout(10000);
100 |
101 | let onEventSent = function(err) {
102 | if (err != null) {
103 | done(err);
104 | }
105 | }
106 |
107 | let onEventReceived = function(typeId, deviceId, eventId, format, payload) {
108 | assert(typeId == testTypeId, "Type ID does not match");
109 | assert(deviceId == testDeviceId, "Device ID does not match");
110 | assert(eventId == testEventId, "Event ID does not match");
111 | assert(format == testEventFormat, "Format does not match");
112 | assert(payload == testEventData, "Payload does not match");
113 | done();
114 | }
115 |
116 | deviceClient.on("error", done);
117 | appClient.on("error", done);
118 | appClient.on("deviceEvent", onEventReceived);
119 |
120 | deviceClient.publishEvent(testEventId, testEventFormat, testEventData, testQos, onEventSent);
121 | });
122 |
123 | step("Disconnect the device MQTT client", function(done) {
124 | deviceClient.on("error", done);
125 | deviceClient.on("close", function() {
126 | assert(deviceClient.isConnected() == false, "Device is still connected");
127 | done();
128 | });
129 | deviceClient.disconnect();
130 | });
131 |
132 | step("Disconnect the application MQTT client", function(done) {
133 | appClient.on("error", done);
134 | appClient.on("close", function() {
135 | assert(appClient.isConnected() == false, "Application is still connected");
136 | done();
137 | });
138 | appClient.disconnect();
139 | });
140 |
141 | after("Delete the test device & ensure the the MQTT clients are disconnected", function() {
142 | if (appClient != null && appClient.isConnected()) {
143 | appClient.disconnect();
144 | }
145 | if (deviceClient != null && deviceClient.isConnected()) {
146 | deviceClient.disconnect();
147 | }
148 |
149 | appClient.registry.unregisterDevice(testTypeId, testDeviceId);
150 | });
151 | });
152 |
153 | describe("Command Subscription", function() {
154 | let testTypeId = "iotnodejs-test";
155 | let testDeviceId = uuidv4();
156 | let testCommandId = "testCommand";
157 | let testCommandFormat = "json";
158 | let testCommandData = "{'foo': 'bar'}";
159 | let testQos = 1;
160 |
161 | let appClient = null;
162 | let deviceConfig = null;
163 | let deviceClient = null;
164 |
165 | before("Register the test device", function(done){
166 | this.timeout(10000);
167 | appClient = new ApplicationClient(appConfig);
168 |
169 | // Register the device type
170 | appClient.registry.registerDeviceType(testTypeId)
171 | .catch(function(err){
172 | if (err.response.status != 409) {
173 | // Anything other than conflict is bad!
174 | throw err;
175 | }
176 | console.info("device type already exists, carry on!")
177 | })
178 | .then(function(result) {
179 | // Register the device
180 | return appClient.registry.registerDevice(testTypeId, testDeviceId);
181 | })
182 | .then(function(deviceDetails) {
183 | console.info(deviceDetails);
184 | let identity = {orgId: appConfig.getOrgId(), typeId: deviceDetails.typeId, deviceId: deviceDetails.deviceId};
185 | let auth = {token: deviceDetails.authToken}
186 | let options = {logLevel: "info"};
187 | deviceConfig = new DeviceConfig(identity, auth, options);
188 | deviceClient = new DeviceClient(deviceConfig);
189 | done();
190 | })
191 | .catch(function(err){
192 | done(err);
193 | });
194 | })
195 |
196 | afterEach("Remove error listener(s)", function() {
197 | appClient.removeAllListeners("error");
198 | deviceClient.removeAllListeners("error");
199 | });
200 |
201 | step("Connect application within 5 seconds", function(done){
202 | this.timeout(5000);
203 | appClient.on("connect", done);
204 | appClient.on("error", done);
205 | appClient.connect();
206 | });
207 |
208 | step("Connect device within 5 seconds", function(done){
209 | this.timeout(5000);
210 | deviceClient.on("connect", done);
211 | deviceClient.on("error", done);
212 | deviceClient.connect();
213 | });
214 |
215 | step("Publish & recieve device command within 10 seconds", function(done){
216 | this.timeout(10000);
217 | let onCommandSent = function(err) {
218 | if (err != null) {
219 | done(err);
220 | }
221 | }
222 |
223 | let onCommandReceived = function(commandId, format, payload) {
224 | assert(commandId == testCommandId, "Command ID does not match");
225 | assert(format == testCommandFormat, "Format does not match");
226 | assert(payload == testCommandData, "Payload does not match");
227 | done();
228 | }
229 |
230 | deviceClient.on("error", done);
231 | appClient.on("error", done);
232 | deviceClient.on("command", onCommandReceived);
233 |
234 | appClient.publishCommand(testTypeId, testDeviceId, testCommandId, testCommandFormat, testCommandData, testQos, onCommandSent);
235 | });
236 |
237 | step("Disconnect the device MQTT client", function(done) {
238 | deviceClient.on("error", done);
239 | deviceClient.on("close", function() {
240 | assert(deviceClient.isConnected() == false, "Device is still connected");
241 | done();
242 | });
243 | deviceClient.disconnect();
244 | });
245 |
246 | step("Disconnect the application MQTT client", function(done) {
247 | appClient.on("error", done);
248 | appClient.on("close", function() {
249 | assert(appClient.isConnected() == false, "Application is still connected");
250 | done();
251 | });
252 | appClient.disconnect();
253 | });
254 |
255 | after("Delete the test device & ensure the the MQTT clients are disconnected", function() {
256 | if (appClient != null && appClient.isConnected()) {
257 | appClient.disconnect();
258 | }
259 | if (deviceClient != null && deviceClient.isConnected()) {
260 | deviceClient.disconnect();
261 | }
262 |
263 | appClient.registry.unregisterDevice(testTypeId, testDeviceId);
264 | });
265 | });
266 |
267 | });
268 |
--------------------------------------------------------------------------------
/test/DeviceConfig.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | import { DeviceConfig } from '../src/device';
13 | import { expect } from 'chai';
14 |
15 | // Turn off console output
16 | console.info = () => {};
17 |
18 | describe('WIoTP Device Configuration', () => {
19 |
20 | it('Initialise with the minimum required configuration', () => {
21 | let identity = {orgId: "myOrg", typeId: "myType", deviceId: "myDevice"};
22 | let auth = {token: "myToken"};
23 | let options = null;
24 |
25 | let config = new DeviceConfig(identity, auth, options);
26 | expect(config.getOrgId()).to.equal("myOrg");
27 | expect(config.options.logLevel).to.equal("info");
28 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
29 | expect(config.options.mqtt.transport).to.equal("tcp");
30 | expect(config.options.mqtt.port).to.equal(8883);
31 | });
32 |
33 | it('Load mimimal configuration from environment variables', () => {
34 | process.env['WIOTP_IDENTITY_ORGID'] = 'myOrg';
35 | process.env['WIOTP_IDENTITY_DEVICEID'] = 'MyDevice';
36 | process.env['WIOTP_IDENTITY_TYPEID'] = 'myType';
37 | let config = DeviceConfig.parseEnvVars();
38 | expect(config.options.logLevel).to.equal("info");
39 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
40 | expect(config.options.mqtt.transport).to.equal("tcp");
41 | expect(config.options.mqtt.port).to.equal(8883);
42 | delete process.env['WIOTP_IDENTITY_ORGID'];
43 | delete process.env['WIOTP_IDENTITY_DEVICEID'];
44 | delete process.env['WIOTP_IDENTITY_TYPEID'];
45 |
46 | });
47 |
48 | it('Load configuration from yaml config file', () => {
49 | let config = DeviceConfig.parseConfigFile("./test/DeviceConfigFile.spec.yaml");
50 | expect(config.identity.orgId).to.equal("myOrg")
51 | expect(config.identity.typeId).to.equal("myType")
52 | expect(config.identity.deviceId).to.equal("myDevice")
53 | expect(config.auth.token).to.equal("myToken")
54 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
55 | expect(config.options.logLevel).to.equal("info");
56 | expect(config.options.mqtt.port).to.equal(8883);
57 | expect(config.options.mqtt.transport).to.equal("tcp");
58 | expect(config.options.mqtt.cleanStart).to.equal(true);
59 | expect(config.options.mqtt.sessionExpiry).to.equal(3600);
60 | expect(config.options.mqtt.keepAlive).to.equal(60);
61 | expect(config.options.mqtt.caFile).to.equal("myPath");
62 | });
63 |
64 | it('Missing identity throws error', () => {
65 | let identity = null;
66 | let auth = null;
67 | let options = null;
68 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
69 | expect(deviceConfigTest).to.throw('Missing identity from configuration');
70 | });
71 |
72 | it('Missing orgID throws error', () => {
73 | let identity = {orgId: null};
74 | let auth = null;
75 | let options = null;
76 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
77 | expect(deviceConfigTest).to.throw('Missing identity.orgId from configuration');
78 | });
79 |
80 | it('Missing typeId throws error', () => {
81 | let identity = {orgId: "myOrg", typeId: null};
82 | let auth = null;
83 | let options = null;
84 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
85 | expect(deviceConfigTest).to.throw('Missing identity.typeId from configuration');
86 | });
87 |
88 | it('Missing deviceId throws error', () => {
89 | let identity = {orgId: "myOrg", typeId: "myType", deviceId: null};
90 | let auth = null;
91 | let options = null;
92 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
93 | expect(deviceConfigTest).to.throw('Missing identity.deviceId from configuration');
94 | });
95 |
96 | it('Quickstart with an auth throws error', () => {
97 | let identity = {orgId: "quickstart", typeId: "myType", deviceId: "MyDevice"};
98 | let auth = {token: "myToken"};
99 | let options = null;
100 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
101 | expect(deviceConfigTest).to.throw('Quickstart service does not support device authentication');
102 | });
103 |
104 | it('Missing auth throws error', () => {
105 | let identity = {orgId: "myOrg", typeId: "myType", deviceId: "MyDevice"};
106 | let auth = null;
107 | let options = null;
108 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
109 | expect(deviceConfigTest).to.throw('Missing auth from configuration');
110 | });
111 |
112 | it('Missing auth token throws error', () => {
113 | let identity = {orgId: "myOrg", typeId: "myType", deviceId: "MyDevice"};
114 | let auth = {token: null};
115 | let options = null;
116 | var deviceConfigTest = function(){new DeviceConfig(identity, auth, options)};
117 | expect(deviceConfigTest).to.throw('Missing auth.token from configuration');
118 | });
119 |
120 | it('Incorrect logLevel in config file throws error', () => {
121 | var deviceConfigTest = function(){new DeviceConfig.parseConfigFile("./test/incorrectLogLevelTest.spec.yaml")};
122 | expect(deviceConfigTest).to.throw('Optional setting options.logLevel (Currently: notALogLevel) must be one of error, warning, info, debug');
123 | });
124 |
125 |
126 | });
--------------------------------------------------------------------------------
/test/DeviceConfigFile.spec.yaml:
--------------------------------------------------------------------------------
1 |
2 | identity:
3 | orgId: myOrg
4 | typeId: myType
5 | deviceId: myDevice
6 | auth:
7 | token: myToken
8 | options:
9 | domain: internetofthings.ibmcloud.com
10 | logLevel: info
11 | mqtt:
12 | port: 8883
13 | transport: tcp
14 | cleanStart: true
15 | sessionExpiry: 3600
16 | keepAlive: 60
17 | caFile: myPath
18 |
--------------------------------------------------------------------------------
/test/DscClient.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | const { v4: uuidv4 } = require('uuid');
12 |
13 | import { expect, use } from 'chai';
14 | const chaiAsPromised = require('chai-as-promised');
15 | use(chaiAsPromised);
16 |
17 | const { CloudantV1: Cloudant } = require('@ibm-cloud/cloudant');
18 |
19 | import { ApplicationConfig, ApplicationClient } from '../src/application';
20 | import * as errors from '../src/api/ApiErrors';
21 |
22 | // Turn off console output
23 | console.info = () => {};
24 |
25 |
26 |
27 | const generateCloudantServiceConfigDbName = (orgId, serviceId) => {
28 | return `iotp_${orgId}_${serviceId}_configuration`;
29 | }
30 |
31 | const generateCloudantDestinationConfigDbName = (orgId, destinationName) => {
32 | return `iotp_${orgId}_${destinationName}_configuration`;
33 | }
34 |
35 | const generateCloudantDestinationBucketDbNamePattern = (orgId, destinationName, bucketInterval) => {
36 | let bucketPattern;
37 | switch(bucketInterval.toUpperCase()) {
38 | case 'DAY': bucketPattern = '\\d{4}-\\d{2}-\\d{1,2}'; break;
39 | case 'WEEK': bucketPattern = '\\d{4}-w\\d{1,2}'; break;
40 | case 'MONTH': bucketPattern = '\\d{4}-\\d{2}'; break;
41 | default: throw new Error(`Unknown bucketInterval: ${bucketInterval}`);
42 | }
43 | return new RegExp(`iotp_${orgId}_${destinationName}_${bucketPattern}`);
44 | }
45 |
46 |
47 | /*
48 | * Wrap cloudant:2.1.0 (OSS pre-approved) to callbacks to promises
49 | */
50 |
51 |
52 | const cloudantDestroyDb = (cloudant, dbName) => {
53 | return new Promise(
54 | (resolve, reject) => {
55 | cloudant.db.destroy(
56 | dbName,
57 | function(err, data) {
58 | if(err){
59 | console.warn(`Failed to delete cloudant DB ${dbName}: ${err}`);
60 | reject(err);
61 | } else {
62 | resolve(data);
63 | }
64 | }
65 | );
66 | }
67 | );
68 | }
69 |
70 |
71 | const cloudantListDbs = (cloudant) => {
72 | return new Promise(
73 | (resolve, reject) => cloudant.db.list(function(err, data) {
74 | if(err) {
75 | console.warn(`Failed to list cloudant DBs`);
76 | reject(err);
77 | } else {
78 | resolve(data);
79 | }
80 | })
81 | )
82 | }
83 |
84 | describe('WIoTP DSC Client Capabilities', function() {
85 |
86 | let orgId = null;
87 | let dscClient = null;
88 |
89 | before("Initialize the application", function(){
90 | const appConfig = ApplicationConfig.parseEnvVars();
91 | orgId = appConfig.getOrgId();
92 | const appClient = new ApplicationClient(appConfig);
93 | dscClient = appClient.dsc;
94 | });
95 |
96 |
97 |
98 | // TODO: Example scenarios - these should both:
99 | // 1. provide coverage of client function
100 | // 2. serve as useful, self-documenting reference material
101 | // e.g.
102 | describe('Create, retrieve, connect and delete a Cloudant service binding', function() {
103 |
104 | this.timeout(10000);
105 |
106 | const description = 'WIoTP DSC Client Capabilities / Create, retrieve, connect and delete a Cloudant service binding';
107 |
108 | let cloudantUsername;
109 | let cloudantPassword;
110 | let cloudant;
111 |
112 | let createdService;
113 | let createdConnector;
114 | let createdDestination;
115 | let createdForwardingRule;
116 |
117 |
118 | before('Init Cloudant Client', function() {
119 | cloudantUsername = process.env.CLOUDANT_USERNAME || null;
120 | cloudantPassword = process.env.CLOUDANT_PASSWORD || null;
121 | if( cloudantUsername === null) expect.fail('CLOUDANT_USERNAME env variable is required for this test');
122 | if( cloudantPassword === null) expect.fail('CLOUDANT_PASSWORD env variable is required for this test');
123 | cloudant = Cloudant({account: cloudantUsername, password: cloudantPassword})
124 | })
125 |
126 | step('Create service', () => {
127 | const name = uuidv4();
128 | return dscClient.createCloudantService({name, description, username: cloudantUsername, password: cloudantPassword})
129 | .then(_createdService => {
130 | createdService = _createdService;
131 | expect(createdService.type).to.equal('cloudant');
132 | expect(createdService.name).to.equal(name);
133 | expect(createdService.description).to.equal(description);
134 | });
135 | })
136 |
137 | step('Retrieve services', () => {
138 | return dscClient.getServices()
139 | .then(services => {
140 | expect(services).to.have.property('results');
141 | const filtered = services.results.filter(service => service.id === createdService.id);
142 | expect(filtered).to.have.length(1);
143 | const service = filtered[0];
144 | expect(service).to.deep.equal(createdService);
145 | });
146 | });
147 |
148 |
149 | step('Retrieve service', function() {
150 | return dscClient.getService(createdService.id)
151 | .then(service => {
152 | expect(service).to.deep.equal(createdService);
153 | })
154 | });
155 |
156 |
157 | step('Create connector for service', function() {
158 | const name = uuidv4();
159 | const timezone = "Africa/Casablanca";
160 | return dscClient.createConnector({name, type:'cloudant', description, serviceId: createdService.id, timezone})
161 | .then(connector => {
162 | createdConnector = connector;
163 | expect(connector).to.have.property('id');
164 | expect(connector).to.have.property('name', name);
165 | expect(connector).to.have.property('serviceId', createdService.id);
166 | expect(connector).to.have.property('type', 'cloudant');
167 | expect(connector).to.have.property('timezone', timezone);
168 | expect(connector).to.have.property('enabled', true);
169 | expect(connector).to.have.property('created');
170 | expect(connector).to.have.property('createdBy');
171 | expect(connector).to.have.property('updated');
172 | expect(connector).to.have.property('updatedBy');
173 | expect(connector).to.have.property('refs');
174 | })
175 | });
176 |
177 | step('Update connector', function() {
178 | const newName = uuidv4();
179 | return dscClient.updateConnector({...createdConnector, name: newName})
180 | .then(updatedConnector => {
181 | expect(updatedConnector).to.have.property('id', createdConnector.id);
182 | expect(updatedConnector).to.have.property('name', newName);
183 | expect(updatedConnector).to.have.property('serviceId', createdService.id);
184 | expect(updatedConnector).to.have.property('type', createdConnector.type);
185 | expect(updatedConnector).to.have.property('timezone', createdConnector.timezone);
186 | expect(updatedConnector).to.have.property('enabled', true);
187 | expect(updatedConnector).to.have.property('created', createdConnector.created);
188 | expect(updatedConnector).to.have.property('createdBy', createdConnector.createdBy);
189 | expect(updatedConnector).to.have.property('updated');
190 | expect(updatedConnector).to.have.property('updatedBy', createdConnector.updatedBy);
191 | expect(updatedConnector).to.have.property('refs');
192 | })
193 | });
194 |
195 | // TODO: retrieve connector
196 | // TODO: retrieve connectors
197 |
198 |
199 | step('Create connector destination', function() {
200 | const name = uuidv4();
201 | const bucketInterval = 'MONTH';
202 | return dscClient.createCloudantDestination(createdConnector.id, {name, bucketInterval})
203 | .then(destination => {
204 | createdDestination = destination;
205 | expect(destination).to.have.property('name', name);
206 | expect(destination).to.have.property('type', 'cloudant');
207 | expect(destination).to.have.property('configuration');
208 | expect(destination.configuration).to.have.property('bucketInterval', bucketInterval);
209 | })
210 | });
211 |
212 | step('Attempting to create duplicate destination should throw DestinationAlreadyExistss error', function() {
213 | const bucketInterval = 'MONTH';
214 | return expect(
215 | dscClient.createCloudantDestination(createdConnector.id, {name: createdDestination.name, bucketInterval})
216 | ).to.be.rejectedWith(errors.DestinationAlreadyExistss);
217 | });
218 |
219 | // TODO: retrieve connector destination
220 | // TODO: retrieve connector destinations
221 |
222 | step('Create connector forwarding rule', function() {
223 | const name = uuidv4();
224 | const deviceType='*';
225 | const eventId='*';
226 | return dscClient.createEventForwardingRule(createdConnector.id, {name, destinationName: createdDestination.name, deviceType, eventId})
227 | .then(forwardingRule => {
228 | createdForwardingRule = forwardingRule;
229 | expect(forwardingRule).to.have.property('name', name);
230 | expect(forwardingRule).to.have.property('type', 'event');
231 | expect(forwardingRule).to.have.property('destinationName', createdDestination.name);
232 | expect(forwardingRule).to.have.property('selector');
233 | expect(forwardingRule.selector).to.have.property('deviceType', deviceType);
234 | expect(forwardingRule.selector).to.have.property('eventId', eventId);
235 | })
236 | });
237 |
238 | // TODO: retrieve connector forwarding rule
239 | // TODO: retrieve connector forwarding rules
240 |
241 | // TODO: send an event, check it makes it into cloudant
242 |
243 | step('Delete forwarding rule', () => {
244 | return dscClient.deleteForwardingRule(createdConnector.id, createdForwardingRule.id)
245 | });
246 |
247 | step('Delete destination', () => {
248 | return dscClient.deleteDestination(createdConnector.id, createdDestination.name)
249 | });
250 |
251 |
252 | step('Delete connector', () => {
253 | return dscClient.deleteConnector(createdConnector.id)
254 | });
255 |
256 | step('Delete Service', () => {
257 | return dscClient.deleteService(createdService.id)
258 | });
259 |
260 |
261 |
262 | after('Cleanup forwarding rule', () => {
263 | if(createdConnector && createdForwardingRule) return dscClient.deleteForwardingRule(createdConnector.id, createdForwardingRule.id).catch(err=>{})
264 | });
265 |
266 | after('Cleanup destination', () => {
267 | if(createdConnector && createdDestination) return dscClient.deleteDestination(createdConnector.id, createdDestination.name).catch(err=>{})
268 | });
269 |
270 |
271 | after('Cleanup connector', () => {
272 | if (createdConnector) return dscClient.deleteConnector(createdConnector.id).catch(err=>{});
273 | });
274 |
275 | after('Cleanup service', () => {
276 | if (createdService) return dscClient.deleteService(createdService.id).catch(err=>{});
277 | });
278 |
279 |
280 |
281 | // deleting a cloudant destination via WIoTP does not delete the databases it created in cloudant (by design)
282 | // to avoid clogging up our test cloudant instance, we delete all databases that will have been created by WIoTP
283 | // NOTE: this must occur after we've deleted the destination from WIoTP otherwise it may be recreated by WIoTP
284 |
285 |
286 | after('Cleanup service config DB from cloudant', () => {
287 | if(createdService)
288 | return cloudantDestroyDb(cloudant, generateCloudantServiceConfigDbName(orgId, createdService.id)).catch(err=>{});
289 | });
290 |
291 | after('Cleanup destination config DB from cloudant', () => {
292 | if(createdDestination)
293 | return cloudantDestroyDb(cloudant, generateCloudantDestinationConfigDbName(orgId, createdDestination.name)).catch(err=>{});
294 | });
295 |
296 | after('Cleanup destination bucket databases from cloudant', () => {
297 | if(createdDestination) {
298 | return cloudantListDbs(cloudant)
299 | .then((body) => {
300 | return Promise.all(
301 | body
302 | .filter((db) => {
303 | const pattern = generateCloudantDestinationBucketDbNamePattern(orgId, createdDestination.name, createdDestination.configuration.bucketInterval);
304 | return pattern.test(db);
305 | })
306 | .map(db => {
307 | return cloudantDestroyDb(cloudant, db).catch(err=>{})
308 | })
309 | );
310 | })
311 | .catch(err => {})
312 | }
313 | });
314 |
315 |
316 |
317 | // ensure created resources:
318 | // 1. have globally-unique IDs (that will not conflict with those used by concurrently-running tests)
319 | // 2. are cleaned up, even in the event of test failure
320 | // 3. have some info associated with them to assist debugging - e.g. in name (if poss) or metadata
321 | });
322 |
323 |
324 |
325 | /* Self-contained simple unit tests */
326 | describe('createCloudantService', () => {
327 | it('should throw InvalidServiceCredentials when credentials are invalid', () => {
328 | return expect(dscClient.createCloudantService({name: 'asd', username:'user', password: 'pass'}))
329 | .to.be.rejectedWith(errors.InvalidServiceCredentials);
330 | });
331 | });
332 |
333 | describe('getService', () => {
334 | it('should throw ServiceNotFound when service does not exist', function() {
335 | this.timeout(10000);
336 | return expect(dscClient.getService(uuidv4()))
337 | .to.be.rejectedWith(errors.ServiceNotFound);
338 | });
339 | });
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 | describe('Create, retrieve, connect and delete an EventStreams service binding', function() {
362 |
363 | this.timeout(10000);
364 |
365 | const description = 'WIoTP DSC Client Capabilities / Create, retrieve, connect and delete a EventStreams service binding';
366 |
367 | let eventstreamsApiKey;
368 | let eventstreamsAdminUrl;
369 | let eventstreamsBrokers;
370 | let eventstreamsUser;
371 | let eventstreamsPassword;
372 |
373 | let createdService;
374 | let createdConnector;
375 | let createdDestination;
376 | let createdForwardingRule;
377 |
378 |
379 | before('Get Eventstreams credentials from environment', function() {
380 | eventstreamsApiKey = process.env.EVENTSTREAMS_API_KEY || expect.fail('EVENTSTREAMS_API_KEY env variable is required for this test');
381 | eventstreamsAdminUrl = process.env.EVENTSTREAMS_ADMIN_URL || expect.fail('EVENTSTREAMS_ADMIN_URL env variable is required for this test');
382 | eventstreamsBrokers = process.env.EVENTSTREAMS_BROKER1 ? [process.env.EVENTSTREAMS_BROKER1] : expect.fail('EVENTSTREAMS_BROKER1 env variable is required for this test');
383 | eventstreamsUser = process.env.EVENTSTREAMS_USER || expect.fail('EVENTSTREAMS_USER env variable is required for this test');
384 | eventstreamsPassword = process.env.EVENTSTREAMS_PASSWORD || expect.fail('EVENTSTREAMS_PASSWORD env variable is required for this test');
385 | })
386 |
387 | step('Create service', () => {
388 | const name = uuidv4();
389 | return dscClient.createEventstreamsService({name, description,
390 | api_key: eventstreamsApiKey, kafka_admin_url: eventstreamsAdminUrl, kafka_brokers_sasl: eventstreamsBrokers,
391 | user: eventstreamsUser, password: eventstreamsPassword})
392 | .then(_createdService => {
393 | createdService = _createdService;
394 | expect(createdService.type).to.equal('eventstreams');
395 | expect(createdService.name).to.equal(name);
396 | expect(createdService.description).to.equal(description);
397 | });
398 | })
399 |
400 | step('Retrieve services', () => {
401 | return dscClient.getServices()
402 | .then(services => {
403 | expect(services).to.have.property('results');
404 | const filtered = services.results.filter(service => service.id === createdService.id);
405 | expect(filtered).to.have.length(1);
406 | const service = filtered[0];
407 | expect(service).to.deep.equal(createdService);
408 | });
409 | });
410 |
411 |
412 | step('Retrieve service', function() {
413 | return dscClient.getService(createdService.id)
414 | .then(service => {
415 | expect(service).to.deep.equal(createdService);
416 | })
417 | });
418 |
419 |
420 | step('Create connector for service', function() {
421 | const name = uuidv4();
422 | const timezone = "Africa/Casablanca";
423 | return dscClient.createConnector({name, type: 'eventstreams', description, serviceId: createdService.id, timezone})
424 | .then(connector => {
425 | createdConnector = connector;
426 | expect(connector).to.have.property('id');
427 | expect(connector).to.have.property('name', name);
428 | expect(connector).to.have.property('serviceId', createdService.id);
429 | expect(connector).to.have.property('type', 'eventstreams');
430 | expect(connector).to.have.property('timezone', timezone);
431 | expect(connector).to.have.property('enabled', true);
432 | expect(connector).to.have.property('created');
433 | expect(connector).to.have.property('createdBy');
434 | expect(connector).to.have.property('updated');
435 | expect(connector).to.have.property('updatedBy');
436 | expect(connector).to.have.property('refs');
437 | })
438 | });
439 |
440 |
441 |
442 | step('Update connector', function() {
443 | const newName = uuidv4();
444 | return dscClient.updateConnector({...createdConnector, name: newName})
445 | .then(updatedConnector => {
446 | expect(updatedConnector).to.have.property('id', createdConnector.id);
447 | expect(updatedConnector).to.have.property('name', newName);
448 | expect(updatedConnector).to.have.property('serviceId', createdService.id);
449 | expect(updatedConnector).to.have.property('type', createdConnector.type);
450 | expect(updatedConnector).to.have.property('timezone', createdConnector.timezone);
451 | expect(updatedConnector).to.have.property('enabled', true);
452 | expect(updatedConnector).to.have.property('created', createdConnector.created);
453 | expect(updatedConnector).to.have.property('createdBy', createdConnector.createdBy);
454 | expect(updatedConnector).to.have.property('updated');
455 | expect(updatedConnector).to.have.property('updatedBy', createdConnector.updatedBy);
456 | expect(updatedConnector).to.have.property('refs');
457 | })
458 | });
459 |
460 | step('Create connector destination', function() {
461 | const name = uuidv4();
462 | const partitions = 2;
463 | return dscClient.createEventstreamsDestination(createdConnector.id, {name, partitions})
464 | .then(destination => {
465 | createdDestination = destination;
466 | expect(destination).to.have.property('name', name);
467 | expect(destination).to.have.property('type', 'eventstreams');
468 | expect(destination).to.have.property('configuration');
469 | expect(destination.configuration).to.have.property('partitions', partitions);
470 | })
471 | });
472 |
473 |
474 | step('Create connector forwarding rule', function() {
475 | const name = uuidv4();
476 | const deviceType='*';
477 | const eventId='*';
478 | return dscClient.createEventForwardingRule(createdConnector.id, {name, destinationName: createdDestination.name, deviceType, eventId})
479 | .then(forwardingRule => {
480 | createdForwardingRule = forwardingRule;
481 | expect(forwardingRule).to.have.property('name', name);
482 | expect(forwardingRule).to.have.property('type', 'event');
483 | expect(forwardingRule).to.have.property('destinationName', createdDestination.name);
484 | expect(forwardingRule).to.have.property('selector');
485 | expect(forwardingRule.selector).to.have.property('deviceType', deviceType);
486 | expect(forwardingRule.selector).to.have.property('eventId', eventId);
487 | })
488 | });
489 |
490 | step('Delete forwarding rule', () => {
491 | return dscClient.deleteForwardingRule(createdConnector.id, createdForwardingRule.id)
492 | });
493 |
494 |
495 | step('Delete destination', () => {
496 | return dscClient.deleteDestination(createdConnector.id, createdDestination.name)
497 | });
498 |
499 |
500 | step('Delete connector', () => {
501 | return dscClient.deleteConnector(createdConnector.id)
502 | });
503 |
504 |
505 | step('Delete Service', () => {
506 | return dscClient.deleteService(createdService.id)
507 | });
508 |
509 | after('Cleanup forwarding rule', () => {
510 | if(createdConnector && createdForwardingRule) return dscClient.deleteForwardingRule(createdConnector.id, createdForwardingRule.id).catch(err=>{})
511 | });
512 |
513 |
514 | after('Cleanup destination', () => {
515 | if(createdConnector && createdDestination) return dscClient.deleteDestination(createdConnector.id, createdDestination.name).catch(err=>{})
516 | });
517 |
518 | after('Cleanup connector', () => {
519 | if (createdConnector) return dscClient.deleteConnector(createdConnector.id).catch(err=>{});
520 | });
521 |
522 | after('Cleanup service', () => {
523 | if (createdService) return dscClient.deleteService(createdService.id).catch(err=>{});
524 | });
525 |
526 |
527 | // NOTE: no need to cleanup ES config. Unlike for Cloudant destinations,
528 | // WIoTP will delete the ES topic when the associated destination is deleted
529 |
530 | });
531 |
532 |
533 |
534 | });
--------------------------------------------------------------------------------
/test/GatewayClient.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { ApplicationConfig, ApplicationClient } from '../src/application';
12 | import { GatewayConfig, GatewayClient } from '../src/gateway';
13 | import { assert } from 'chai';
14 | import { step } from 'mocha-steps';
15 |
16 | const { v4: uuidv4 } = require('uuid');
17 |
18 | // Turn off console output
19 | console.info = () => {};
20 |
21 | describe('WIoTP Gateway Capabilities', function() {
22 |
23 | let testTypeId = "iotnodejs-testgw";
24 | let testQos = 1;
25 | let appConfig = ApplicationConfig.parseEnvVars();
26 |
27 | describe("Event Publication", function() {
28 | let testDeviceId = uuidv4();
29 | let testEventId = "testEvent";
30 | let testEventFormat = "json";
31 | let testEventData = "{'foo': 'bar'}";
32 |
33 | let appClient = null;
34 | let deviceConfig = null;
35 | let deviceClient = null;
36 |
37 | before("Register the test gateway", function(done){
38 | this.timeout(10000);
39 | appClient = new ApplicationClient(appConfig);
40 |
41 | // Register the device type
42 | appClient.registry.registerDeviceType(testTypeId, null, null, null, "Gateway")
43 | .catch(function(err){
44 | if (err.response.status != 409) {
45 | // Anything other than conflict is bad!
46 | throw err;
47 | }
48 | console.info("device type already exists, carry on!")
49 | })
50 | .then(function(result) {
51 | // Register the device
52 | return appClient.registry.registerDevice(testTypeId, testDeviceId);
53 | })
54 | .then(function(deviceDetails) {
55 | console.info(deviceDetails);
56 | let identity = {orgId: appConfig.getOrgId(), typeId: deviceDetails.typeId, deviceId: deviceDetails.deviceId};
57 | let auth = {token: deviceDetails.authToken}
58 | let options = {logLevel: "info"};
59 | deviceConfig = new GatewayConfig(identity, auth, options);
60 | deviceClient = new GatewayClient(deviceConfig);
61 | done();
62 | })
63 | .catch(function(err){
64 | done(err);
65 | });
66 | })
67 |
68 | afterEach("Remove error listener(s)", function() {
69 | appClient.removeAllListeners("error");
70 | deviceClient.removeAllListeners("error");
71 | });
72 |
73 | step("Connect application within 5 seconds", function(done){
74 | this.timeout(5000);
75 | appClient.on("connect", done);
76 | appClient.on("error", done);
77 | appClient.connect();
78 | });
79 |
80 | step("Connect gateway within 5 seconds", function(done){
81 | this.timeout(5000);
82 | deviceClient.on("connect", done);
83 | deviceClient.on("error", done);
84 | deviceClient.connect();
85 | });
86 |
87 | step("Subscribe to gateway events", function(done){
88 | let onSubscribe = function(err, granted) {
89 | if (err != null) {
90 | done(err);
91 | }
92 | done();
93 | }
94 | appClient.on("error", done);
95 | appClient.subscribeToEvents(testTypeId, testDeviceId, testEventId, testEventFormat, testQos, onSubscribe);
96 | });
97 |
98 | step("Publish & recieve gateway event within 10 seconds", function(done){
99 | this.timeout(10000);
100 |
101 | let onEventSent = function(err) {
102 | if (err != null) {
103 | done(err);
104 | }
105 | }
106 |
107 | let onEventReceived = function(typeId, deviceId, eventId, format, payload) {
108 | assert(typeId == testTypeId, "Type ID does not match");
109 | assert(deviceId == testDeviceId, "Device ID does not match");
110 | assert(eventId == testEventId, "Event ID does not match");
111 | assert(format == testEventFormat, "Format does not match");
112 | assert(payload == testEventData, "Payload does not match");
113 | done();
114 | }
115 |
116 | deviceClient.on("error", done);
117 | appClient.on("error", done);
118 | appClient.on("deviceEvent", onEventReceived);
119 |
120 | deviceClient.publishEvent(testEventId, testEventFormat, testEventData, testQos, onEventSent);
121 | });
122 |
123 | step("Disconnect the gateway MQTT client", function(done) {
124 | deviceClient.on("error", done);
125 | deviceClient.on("close", function() {
126 | assert(deviceClient.isConnected() == false, "Device is still connected");
127 | done();
128 | });
129 | deviceClient.disconnect();
130 | });
131 |
132 | step("Disconnect the application MQTT client", function(done) {
133 | appClient.on("error", done);
134 | appClient.on("close", function() {
135 | assert(appClient.isConnected() == false, "Application is still connected");
136 | done();
137 | });
138 | appClient.disconnect();
139 | });
140 |
141 | after("Delete the test gateway & ensure the the MQTT clients are disconnected", function() {
142 | if (appClient != null && appClient.isConnected()) {
143 | appClient.disconnect();
144 | }
145 | if (deviceClient != null && deviceClient.isConnected()) {
146 | deviceClient.disconnect();
147 | }
148 |
149 | appClient.registry.unregisterDevice(testTypeId, testDeviceId);
150 | });
151 | });
152 |
153 | describe("Command Subscription", function() {
154 | let testDeviceId = uuidv4();
155 | let testCommandId = "testCommand";
156 | let testCommandFormat = "json";
157 | let testCommandData = "{'foo': 'bar'}";
158 |
159 | let appClient = null;
160 | let deviceConfig = null;
161 | let deviceClient = null;
162 |
163 | before("Register the test device", function(done){
164 | this.timeout(10000);
165 | appClient = new ApplicationClient(appConfig);
166 |
167 | // Register the device type
168 | appClient.registry.registerDeviceType(testTypeId, null, null, null, "Gateway")
169 | .catch(function(err){
170 | if (err.response.status != 409) {
171 | // Anything other than conflict is bad!
172 | throw err;
173 | }
174 | console.info("device type already exists, carry on!")
175 | })
176 | .then(function(result) {
177 | // Register the device
178 | return appClient.registry.registerDevice(testTypeId, testDeviceId);
179 | })
180 | .then(function(deviceDetails) {
181 | console.info(deviceDetails);
182 | let identity = {orgId: appConfig.getOrgId(), typeId: deviceDetails.typeId, deviceId: deviceDetails.deviceId};
183 | let auth = {token: deviceDetails.authToken}
184 | let options = {logLevel: "info"};
185 | deviceConfig = new GatewayConfig(identity, auth, options);
186 | deviceClient = new GatewayClient(deviceConfig);
187 | done();
188 | })
189 | .catch(function(err){
190 | done(err);
191 | });
192 | })
193 |
194 | afterEach("Remove error listener(s)", function() {
195 | appClient.removeAllListeners("error");
196 | deviceClient.removeAllListeners("error");
197 | });
198 |
199 | step("Connect application within 5 seconds", function(done){
200 | this.timeout(5000);
201 | appClient.on("connect", done);
202 | appClient.on("error", done);
203 | appClient.connect();
204 | });
205 |
206 | step("Connect gateway within 5 seconds", function(done){
207 | this.timeout(5000);
208 | deviceClient.on("connect", done);
209 | deviceClient.on("error", done);
210 | deviceClient.connect();
211 | });
212 |
213 | step("Subscribe to gateway command", function(done){
214 | let onSubscribe = function(err, granted) {
215 | if (err != null) {
216 | done(err);
217 | }
218 | done();
219 | }
220 | deviceClient.on("error", done);
221 | deviceClient.subscribeToCommands(testCommandId, testCommandFormat, testQos, onSubscribe);
222 | });
223 |
224 |
225 | step("Publish & recieve gateway command within 10 seconds", function(done){
226 | this.timeout(10000);
227 | let onCommandSent = function(err) {
228 | if (err != null) {
229 | done(err);
230 | }
231 | }
232 |
233 | let onCommandReceived = function(typeId, deviceId, commandId, format, payload) {
234 | assert(typeId == testTypeId, "Type ID does not match");
235 | assert(deviceId == testDeviceId, "Device ID does not match");
236 | assert(commandId == testCommandId, "Command ID does not match");
237 | assert(format == testCommandFormat, "Format does not match");
238 | assert(payload == testCommandData, "Payload does not match");
239 | done();
240 | }
241 |
242 | deviceClient.on("error", done);
243 | appClient.on("error", done);
244 | deviceClient.on("command", onCommandReceived);
245 |
246 | appClient.publishCommand(testTypeId, testDeviceId, testCommandId, testCommandFormat, testCommandData, testQos, onCommandSent);
247 | });
248 |
249 | step("Disconnect the gateway MQTT client", function(done) {
250 | deviceClient.on("error", done);
251 | deviceClient.on("close", function() {
252 | assert(deviceClient.isConnected() == false, "Device is still connected");
253 | done();
254 | });
255 | deviceClient.disconnect();
256 | });
257 |
258 | step("Disconnect the application MQTT client", function(done) {
259 | appClient.on("error", done);
260 | appClient.on("close", function() {
261 | assert(appClient.isConnected() == false, "Application is still connected");
262 | done();
263 | });
264 | appClient.disconnect();
265 | });
266 |
267 | after("Delete the test gateway & ensure the the MQTT clients are disconnected", function() {
268 | if (appClient != null && appClient.isConnected()) {
269 | appClient.disconnect();
270 | }
271 | if (deviceClient != null && deviceClient.isConnected()) {
272 | deviceClient.disconnect();
273 | }
274 |
275 | appClient.registry.unregisterDevice(testTypeId, testDeviceId);
276 | });
277 | });
278 |
279 | });
280 |
--------------------------------------------------------------------------------
/test/GatewayConfig.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2014, 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 |
12 | import { GatewayConfig } from '../src/gateway';
13 | import { expect } from 'chai';
14 |
15 | // Turn off console output
16 | console.info = () => {};
17 |
18 | describe('WIoTP Gateway Configuration', () => {
19 |
20 | it('Initialise with the minimum required configuration', () => {
21 | let identity = {orgId: "orgid", typeId: "mytypeid", deviceId: "mydeviceid"};
22 | let auth = {token: "test"};
23 | let options = null;
24 |
25 | let config = new GatewayConfig(identity, auth, options);
26 | expect(config.getOrgId()).to.equal("orgid");
27 | expect(config.getClientId()).to.contain("g:");
28 | expect(config.identity.appId).to.not.be.null; // Should be a generated UUID
29 | expect(config.options.logLevel).to.equal("info");
30 | expect(config.options.domain).to.equal("internetofthings.ibmcloud.com");
31 | expect(config.options.mqtt.transport).to.equal("tcp");
32 | expect(config.options.mqtt.port).to.equal(8883);
33 | });
34 |
35 | });
36 |
--------------------------------------------------------------------------------
/test/StateClient.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *****************************************************************************
3 | Copyright (c) 2019 IBM Corporation and other Contributors.
4 | All rights reserved. This program and the accompanying materials
5 | are made available under the terms of the Eclipse Public License v1.0
6 | which accompanies this distribution, and is available at
7 | http://www.eclipse.org/legal/epl-v10.html
8 | *****************************************************************************
9 | *
10 | */
11 | import { ApplicationConfig, ApplicationClient } from '../src/application';
12 | import { expect } from 'chai';
13 |
14 | // Turn off console output
15 | console.info = () => {};
16 |
17 |
18 | describe('WIoTP State Client Capabilities', function() {
19 |
20 | let stateClient = null;
21 |
22 | before("Initialize the application", function(){
23 | const appConfig = ApplicationConfig.parseEnvVars();
24 | const appClient = new ApplicationClient(appConfig);
25 | stateClient = appClient.state;
26 | stateClient.workWithDraft();
27 | });
28 |
29 |
30 | // TODO: Example scenarios - these should both:
31 | // 1. provide coverage of client function
32 | // 2. serve as useful, self-documenting reference material
33 | // e.g.
34 | //describe('Create and activate a simple model and verify resulting state', () => {
35 | //step('Create a device type', () => {
36 | //})
37 | // create pi, ets, li, mappings, activate
38 | // send device event
39 | // verify state over HTTP
40 | // verify state notifications over MQTT
41 | // ... etc
42 |
43 | // ensure created resources:
44 | // 1. have globally-unique IDs (that will not conflict with those used by concurrently-running tests)
45 | // 2. are cleaned up, even in the event of test failure
46 | // 3. have some info associated with them to assist debugging - e.g. in name (if poss) or metadata
47 | //});
48 |
49 |
50 | /* Self-contained simple unit tests */
51 | describe('getDraftLogicalInterfaces', () => {
52 |
53 | it('should return a different page for different values of bookmark parameter', () => {
54 | return Promise.all([
55 | stateClient.getLogicalInterfaces(0, 1),
56 | stateClient.getLogicalInterfaces(1, 1),
57 | ])
58 | .then( (pages) => {
59 | const [page1, page2] = pages;
60 |
61 | // structural assertions common to both pages
62 | pages.forEach(page => {
63 | expect(page).to.have.property('bookmark');
64 | expect(page).to.have.property('results');
65 | expect(page).to.have.property('meta');
66 | expect(page.meta).to.have.property('total_rows');
67 | expect(page.meta.total_rows).to.be.at.least(2);
68 | expect(page.results).to.have.length(1);
69 | });
70 |
71 | // and check the pages returned are actually different
72 | expect(page1.bookmark).to.equal('1');
73 | expect(page2.bookmark).to.equal('2');
74 |
75 | expect(page1.results[0].id).to.not.equal(page2.results[0].id);
76 | })
77 | });
78 |
79 | it(`should correctly honour name parameter`, () => {
80 | let theName;
81 | return stateClient.getLogicalInterfaces(0, 1)
82 | .then((page) => {
83 | expect(page).to.have.property('results');
84 | expect(page.results).to.not.be.empty;
85 | theName = page.results[0].name;
86 | return stateClient.getLogicalInterfaces(0, 10, undefined, theName)
87 | })
88 | .then(page => {
89 | expect(page).to.have.property('results');
90 | expect(page.results).to.not.be.empty;
91 | expect(page.results.every(r => r.name.includes(theName))).to.be.true;
92 | })
93 |
94 | });
95 |
96 |
97 | // TODO: test other sortable fields (NOTE: some of which are not strings)
98 | [
99 | [{name: false}],
100 | [{name: true}],
101 |
102 | [{description: false}],
103 | [{description: true}],
104 |
105 | [{name: false}, {description: false}],
106 | [{name: false}, {description: true}],
107 |
108 | [{description: false}, {name: true}],
109 | [{description: false}, {name: false}],
110 |
111 | ].forEach((sort) => {
112 | it(`should correctly honour sort parameters: ${JSON.stringify(sort)}`, () => {
113 | return stateClient.getLogicalInterfaces(0, 10, sort)
114 | .then((page) => {
115 | expect(page).to.have.property('results');
116 | const actual = page.results;
117 |
118 | // sort results by name programatically here and check it aligns with expected results
119 | const expected = [...actual];
120 | expected.sort( (a,b) => {
121 |
122 | for(let i=0; i `${a}, '${c.description}'(${c.id})`, ''))
148 | console.log(actual.reduce((a,c) => `${a}, '${c.description}'(${c.id})`, ''))
149 | */
150 |
151 | expect(actual).to.deep.equal(expected);
152 | }); // then
153 | }); // it
154 |
155 | }); // forEach
156 |
157 | }); // describe('getDraftLogicalInterfaces'
158 |
159 | });
--------------------------------------------------------------------------------
/test/incorrectLogLevelTest.spec.yaml:
--------------------------------------------------------------------------------
1 | options:
2 | logLevel: notALogLevel
3 |
--------------------------------------------------------------------------------