UNPKG

node-red-contrib-mobius-flow-thingsboard

Version:

Node-RED nodes to work with MOBiUSFlow and ThingsBoard.io

610 lines (602 loc) 24.8 kB
"use strict"; /* eslint-disable no-async-promise-executor */ /* Copyright (c) 2018, IAconnects Technology Limited All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ThingsboardHttpClient = void 0; const axios_1 = __importDefault(require("axios")); const events_1 = require("events"); class ThingsboardHttpClient extends events_1.EventEmitter { /** * * @param siteURL The Thingsboard site URL without https:// * @param customerId The customer ID for this connection * @param username The username used for auth with Thingsboard * @param password The password used for auth with Thingsboard */ constructor(siteURL, customerId, username, password) { super(); this.customerId = customerId; this.username = username; this.password = password; this.thingsboardToken = ''; this.axiosInstance = axios_1.default.create({ baseURL: `https://${siteURL}/api`, }); this.Ready = this.getHttpTokenFromTB().catch((err) => { // tslint:disable-next-line: no-console console.log(`Failed to get Thingsboard token - ${err.response.data.message}`); this.emit('connectionError'); }); this.httpTokenRefreshTimer = setInterval(() => { this.refreshHttpTokenFromTB(); }, 5 * 60 * 1000); } /** * Reset the token refresh timer and stop calls to Thingsboard */ closeHttpConnection() { clearInterval(this.httpTokenRefreshTimer); } /** * Get a Thingsboard access token * @returns A void promise */ getHttpTokenFromTB() { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }; const payload = { username: this.username, password: this.password, }; return new Promise((resolve, reject) => { this.axiosInstance .post('/auth/login', payload, axiosConfig) .then((response) => { this.thingsboardToken = response.data.token; resolve(); }) .catch((err) => { reject(err); }); }); } /** * Refresh the Thingsboard access token */ refreshHttpTokenFromTB() { this.Ready = this.getHttpTokenFromTB().catch((err) => { // tslint:disable-next-line: no-console console.log(`Failed to get Thingsboard token - ${err.response.data.message}`); this.emit('connectionError'); }); } /** * Get a Map containing all of the registered devices for a customer * @param detailed Optional property. Set true to get a detailed entry for each device * @returns A promise which resolves with a Map of all the registered devices */ getRegisteredDevices(detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; let page = 0; let hasNext = false; const devices = new Map(); do { const url = `/customer/${this.customerId}/devices?pageSize=20&page=${page}`; await this.axiosInstance .get(url, axiosConfig) .then((response) => { page++; hasNext = response.data.hasNext; response.data.data.forEach((element) => { try { let isGateway = false; if (element.hasOwnProperty('additionalInfo') && typeof element.additionalInfo !== 'string' && element.additionalInfo.hasOwnProperty('gateway')) { isGateway = element.additionalInfo.gateway; } if (element.id.entityType === 'DEVICE' && !isGateway) { if (detailed) { let description = ''; if (element.hasOwnProperty('additionalInfo') && typeof element.additionalInfo !== 'string' && element.additionalInfo.hasOwnProperty('description')) { description = element.additionalInfo.description; } devices.set(element.name, { id: element.id.id, type: element.type, label: element.label, description, }); } else { devices.set(element.name, { id: element.id.id, }); } } } catch (error) { // console.log(`Error: ${error}`); } }); }) .catch((err) => { return reject({ error: 'Failed to get registered devices', reason: err.response.data }); }); } while (hasNext); resolve(devices); }); } /** * Get the access token for a Thingsboard device * @param deviceId The Thingsboard device Id * @returns A promise which resolves with the Thingsboard device access token */ getDeviceAccessToken(deviceId) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/device/${deviceId}/credentials`; await this.axiosInstance .get(url, axiosConfig) .then((response) => { resolve(response.data.credentialsId); }) .catch((err) => { reject({ error: 'Failed to get device access token', reason: err.response.data }); }); }); } /** * Add a new device to Thingsboard * @param name The new device name * @param type The new device type * @param label The new device label * @param description The new device description * @param detailed Optional property. Set true to get a detailed response * @returns A promise which resolves with the device details */ addDevice(name, type, label, description, detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/device`; const payload = { name, type, label, additionalInfo: { description: description || '' }, customerId: { id: this.customerId, entityType: 'CUSTOMER' }, }; await this.axiosInstance .post(url, payload, axiosConfig) .then((response) => { if (detailed) { resolve({ id: response.data.id.id, type: response.data.type, label: response.data.label, description: response.data.additionalInfo.description, }); } else { resolve({ id: response.data.id.id, }); } }) .catch((err) => { reject({ error: 'Failed to add device', reason: err.response.data }); }); }); } /** * Update a Thingsboard device * @param deviceId The Thingsboard device ID of the existing device * @param name The device name * @param type The device type * @param label The device label * @param description new device description * @param detailed Optional property. Set true to get a detailed response * @returns A promise which resolves with the device details */ updateDevice(deviceId, name, type, label, description, detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; let url = `/device/${deviceId}`; let payload = {}; await this.axiosInstance .get(url, axiosConfig) .then((response) => { payload = { ...response.data }; }) .catch((err) => { reject({ error: 'Failed to update device', reason: err.response.data }); }); url = `/device`; payload.name = name; payload.type = type; payload.label = label; try { if (typeof payload.additionalInfo === 'string') { payload.additionalInfo = { description: description || '' }; } else { payload.additionalInfo.description = description || ''; } } catch (_a) { // tslint:disable-next-line: no-console console.log(`Failed to update description of device ${name}`); } await this.axiosInstance .post(url, payload, axiosConfig) .then((response) => { if (detailed) { resolve({ id: response.data.id.id, type: response.data.type, label: response.data.label, description: response.data.additionalInfo.description, }); } else { resolve({ id: response.data.id.id, }); } }) .catch((err) => { reject({ error: 'Failed to update device', reason: err.response.data }); }); }); } /** * Add or update a Thingsboard device's attributes * @param deviceId The Thingsboard device Id * @param attributes The attributes to add / update as key value pairs * @param scope The attribute's scope (Server, Shared or Client) * @returns A void promise */ updateDeviceAttributes(deviceId, attributes, scope) { return new Promise(async (resolve, reject) => { const deviceAccessToken = scope === 'CLIENT_SCOPE' ? await this.getDeviceAccessToken(deviceId).catch((err) => { reject({ error: 'Failed to update device attributes', reason: err.reason }); }) : ''; const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = scope === 'CLIENT_SCOPE' ? `/v1/${deviceAccessToken}/attributes` : `/plugins/telemetry/DEVICE/${deviceId}/attributes/${scope}`; await this.axiosInstance .post(url, attributes, axiosConfig) .then((response) => { resolve(); }) .catch((err) => { reject({ error: 'Failed to update device attributes', reason: { status: err.response.status, message: err.response.data }, }); }); }); } /** * Add or update a Thingsboard device's telemetry * @param deviceId The Thingsboard device Id * @param telemetry The telemetry to add / update as key value pairs * @returns A void promise */ updateDeviceTelemetry(deviceId, telemetry) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/plugins/telemetry/DEVICE/${deviceId}/timeseries/ANY`; await this.axiosInstance .post(url, telemetry, axiosConfig) .then((response) => { resolve(); }) .catch((err) => { reject({ error: 'Failed to update device telemetry', reason: { status: err.response.status, message: err.response.data }, }); }); }); } /** * Get a Map containing all of the registered assets for a customer * @param detailed Optional property. Set true to get a detailed entry for each asset * @returns A promise which resolves with a Map of all the registered assets */ getRegisteredAssets(detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; let page = 0; let hasNext = false; const assets = new Map(); do { const url = `/customer/${this.customerId}/assets?pageSize=20&page=${page}`; await this.axiosInstance .get(url, axiosConfig) .then((response) => { page++; hasNext = response.data.hasNext; response.data.data.forEach((element) => { if (element.id.entityType === 'ASSET') { if (detailed) { let description = ''; if (element.hasOwnProperty('additionalInfo') && typeof element.additionalInfo !== 'string' && element.additionalInfo.hasOwnProperty('description')) { description = element.additionalInfo.description; } assets.set(element.name, { id: element.id.id, type: element.type, label: element.label, description: description, }); } else { assets.set(element.name, { id: element.id.id, }); } } }); }) .catch((err) => { reject({ error: 'Failed to get registered assets', reason: err.response.data }); }); } while (hasNext); resolve(assets); }); } /** * Add a new asset to Thingsboard * @param name The new asset name * @param type The new asset type * @param label The new asset label * @param description The new asset description * @param detailed Optional property. Set true to get a detailed response * @returns A promise which resolves with the asset details */ addAsset(name, type, label, description, detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/asset`; const payload = { name, type, label, additionalInfo: { description: description || '' }, customerId: { id: this.customerId, entityType: 'CUSTOMER' }, }; await this.axiosInstance .post(url, payload, axiosConfig) .then((response) => { if (detailed) { resolve({ id: response.data.id.id, type: response.data.type, label: response.data.label, description: response.data.additionalInfo.description, }); } else { resolve({ id: response.data.id.id, }); } }) .catch((err) => { reject({ error: 'Failed to add asset', reason: err.response.data }); }); }); } /** * Update a Thingsboard asset * @param deviceId The Thingsboard asset ID of the existing asset * @param name The asset name * @param type The asset type * @param label The asset label * @param description The asset description * @param detailed Optional property. Set true to get a detailed response * @returns A promise which resolves with the asset details */ updateAsset(assetId, name, type, label, description, detailed = false) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; let url = `/asset/${assetId}`; let payload = {}; await this.axiosInstance .get(url, axiosConfig) .then((response) => { payload = { ...response.data }; }) .catch((err) => { reject({ error: 'Failed to update asset', reason: err.response.data }); }); url = `/asset`; payload.name = name; payload.type = type; payload.label = label; try { if (typeof payload.additionalInfo === 'string') { payload.additionalInfo = { description: description || '' }; } else { payload.additionalInfo.description = description || ''; } } catch (_a) { // tslint:disable-next-line: no-console console.log(`Failed to update description of asset ${name}`); } await this.axiosInstance .post(url, payload, axiosConfig) .then((response) => { if (detailed) { resolve({ id: response.data.id.id, type: response.data.type, label: response.data.label, description: response.data.additionalInfo.description, }); } else { resolve({ id: response.data.id.id, }); } }) .catch((err) => { reject({ error: 'Failed to update asset', reason: err.response.data }); }); }); } /** * Add or update a Thingsboard assets's attributes * @param assetId The Thingsboard asset Id * @param attributes The attributes to add / update as key value pairs * @returns A void promise */ updateAssetAttributes(assetId, attributes) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/plugins/telemetry/ASSET/${assetId}/attributes/SERVER_SCOPE`; await this.axiosInstance .post(url, attributes, axiosConfig) .then((response) => { resolve(); }) .catch((err) => { reject({ error: 'Failed to update asset attributes', reason: { status: err.response.status, message: err.response.data }, }); }); }); } /** * Add or update a Thingsboard assets's telemetry * @param assetId The Thingsboard asset Id * @param telemetry The telemetry to add / update as key value pairs * @returns A void promise */ updateAssetTelemetry(assetId, telemetry) { return new Promise(async (resolve, reject) => { const axiosConfig = { headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Authorization': `Bearer ${this.thingsboardToken}`, }, }; const url = `/plugins/telemetry/ASSET/${assetId}/timeseries/ANY`; await this.axiosInstance .post(url, telemetry, axiosConfig) .then((response) => { resolve(); }) .catch((err) => { reject({ error: 'Failed to update asset telemetry', reason: { status: err.response.status, message: err.response.data }, }); }); }); } } exports.ThingsboardHttpClient = ThingsboardHttpClient;