node-vscp
Version:
Utility and constant package for VSCP
1,926 lines (1,558 loc) • 61.7 kB
JavaScript
// VSCP common javascript library
//
// Copyright © 2012-2024 Ake Hedman, Grodans Paradis AB
// <akhe@grodansparadis.com>
// Copyright © 2015-2020 Andreas Merkle
// <vscp@blue-andi.de>
//
// Licence:
// The MIT License (MIT)
// [OSI Approved License]
//
// The MIT License (MIT)
//
// Copyright © 2015-2024 Åke Hedman, Grodans Paradis AB (Paradise of the Frog)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Alternative licenses for VSCP & Friends may be arranged by contacting
// Grodans Paradis AB at info@grodansparadis.com, http://www.grodansparadis.com
//
// This code requires node.js > 10.4 as bigint support is needed.
//
'use strict';
const vscp_class = require('node-vscp-class');
/**
* VSCP core javascript library version
* @property {number} major - Major version number
* @property {number} minor - Minor version number
* @property {number} release - Sub-minor version number
*/
const version = {
major: 1,
minor: 1,
release: 20
};
/* !!! VSCP classes and types see in the autogenerated files vscp_class.js and
* vscp_type.js !!! */
/**
* VSCP class priorities
* @enum {number}
* @const
*/
const priority = {
PRIORITY_0: 0,
PRIORITY_HIGH: 0,
PRIORITY_1: 1,
PRIORITY_2: 2,
PRIORITY_3: 3,
PRIORITY_NORMAL: 3,
PRIORITY_4: 4,
PRIORITY_5: 5,
PRIORITY_6: 6,
PRIORITY_7: 7,
PRIORITY_LOW: 7
};
const guidtype = {
GUIDTYPE_0: 0, // Standard GUID
GUIDTYPE_STANDARD: 0,
GUIDTYPE_1: 1, // GUID is IP.v6 address
GUIDTYPE_IPV6: 1,
GUIDTYPE_2: 2, // GUID is RFC 4122 Version 1
GUIDTYPE_RFC4122_1: 2,
GUIDTYPE_3: 3, // GUID is RFC 4122 Version 4
GUIDTYPE_RFC4122_4: 3
}
/**
* VSCP host capabilities (wcyd - What Can You Do)
*
* Due to Javascripts incapability to handle 64-bit numbers
* values are bit positions instead of proper constants. That is
* the constant is gotten with 2^bit. The capabilitues 64-bit
* integer can them be divided into two 32-bit integers and with
* that be handle also in Javascript
*
* @enum {number}
* @const
*/
const hostCapability = {
REMOTE_VARIABLE: 63,
DECISION_MATRIX: 62,
INTERFACE: 61,
TCPIP: 15,
UDP: 14,
MULTICAST_ANNOUNCE: 13,
RAWETH: 12,
WEB: 11,
WEBSOCKET: 10,
REST: 9,
MULTICAST_CHANNEL: 8,
IP6: 6,
IP4: 5,
SSL: 4,
TWO_CONNECTIONS: 3,
AES256: 2,
AES192: 1,
AES128: 0
};
/*
Measurement data format masks
*/
const measurementDataCodingMask = {
MASK_DATACODING_TYPE: 0xE0, /* Bits 5,6,7 */
MASK_DATACODING_UNIT: 0x18, /* Bits 3,4 */
MASK_DATACODING_INDEX: 0x07 /* Bits 0,1,2 */
};
/*
These bits are coded in the three MSB bits of the first data byte
of measurement data and tells the type of the data that follows.
*/
const measurementDataCoding = {
DATACODING_BIT: 0x00,
DATACODING_BYTE: 0x20,
DATACODING_STRING: 0x40,
DATACODING_INTEGER: 0x60,
DATACODING_NORMALIZED: 0x80,
DATACODING_SINGLE: 0xA0, /* single precision float */
DATACODING_DOUBLE: 0xC0, /* double precision float */
DATACODING_RESERVED2: 0xE0
};
/* ---------------------------------------------------------------------- */
/**
* VSCP event.
* @class
*
* @param {object} options - Options
* @param {number} options.vscpHead - Event head
* @param {boolean} options.guidIsIpV6Addr - GUID is a IPv6 address
* @param {boolean} options.dumbNode - Node is a dumb node
* @param {number} options.vscpPriority - Priority
* @param {number} options.vscpGuidType - GUID Type
* @param {boolean} options.vscpHardCoded - Hard coded node id
* @param {boolean} options.vscpCalcCRC - Calculate CRC
* @param {number} options.vscpClass - VSCP class
* @param {number} options.vscpType - VSCP type
* @param {number} options.vscpObId - Object id
* @param {string} options.vscpDateTime - ISO UTC Date + time
* @param {number} options.vscpTimeStamp - Timestamp
* @param {string} options.vscpGuid - GUID string
* @param {(number[]|string)} options.vscpData - Event data
* @param {string} options.text - Event on text form
*/
class Event {
constructor(options) {
/**
* VSCP event head
* @member {number}
*/
this.vscpHead = 0;
/**
* VSCP class
* @member {number}
*/
this.vscpClass = 0;
/**
* VSCP type
* @member {number}
*/
this.vscpType = 0;
/**
* VSCP object id used by driver for channel info and etc.
* @member {number}
*/
this.vscpObId = 0;
/**
* Relative timestamp for package in us
* @member {number}
*/
this.vscpTimeStamp = 0;
/**
* Date/Time for package
* @member {date}
*/
this.vscpDateTime = new Date();
this.vscpDateTime = Date.UTC(
this.vscpDateTime.getUTCFullYear(), this.vscpDateTime.getUTCMonth(), this.vscpDateTime.getUTCDate(),
this.vscpDateTime.getUTCHours(), this.vscpDateTime.getUTCMinutes(), this.vscpDateTime.getUTCSeconds());
this.vscpDateTime = new Date(this.vscpDateTime);
/**
* Node global unique id LSB(15) -> MSB(0)
* @member {string}
*/
this.vscpGuid = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
/**
* Data array or string
* @member {(number[]|string)}
*/
this.vscpData = [];
if ('undefined' !== typeof options) {
if ('string' === typeof options) {
this.setFromString(options);
}
else {
if ('number' === typeof options.vscpHead) {
this.vscpHead = options.vscpHead;
} else if ('string' === typeof options.vscpHead) {
this.vscpHead = parseInt(options.vscpHead);
}
if ('boolean' === typeof options.guidIsIpV6Addr) {
if (false === options.guidIsIpV6Addr) {
this.vscpHead &= 0xefff;
} else {
this.setIPV6Addr();
}
}
if ('boolean' === typeof options.dumbNode) {
if (false === options.dumbNode) {
this.vscpHead &= 0x7fff;
} else {
this.vscpHead |= 0x8000;
}
}
// 0 - 7
if ('number' === typeof options.vscpPriority) {
if ((0 <= options.vscpPriority) && (7 >= options.vscpPriority)) {
this.vscpHead &= 0xff1f;
this.vscpHead |= (options.vscpPriority << 5);
}
} else if ('string' === typeof options.vscpPriority) {
let n = parseInt(options.vscpPriority);
this.vscpHead &= 0xff1f;
this.vscpHead |= (n << 5);
}
// 0 - 7
if ('number' === typeof options.vscpGuidType) {
if ((0 <= options.vscpGuidType) && (7 >= options.vscpGuidType)) {
this.vscpHead &= 0x8fff;
this.vscpHead |= (options.vscpGuidType << 12);
}
} else if ('string' === typeof options.vscpGuidType) {
let n = parseInt(options.vscpGuidType);
this.vscpHead &= 0xff1f;
this.vscpHead |= (n << 5);
}
if ('boolean' === typeof options.vscpHardCoded) {
if (false === options.vscpHardCoded) {
this.vscpHead &= 0xffef;
} else {
this.vscpHead |= 0x0010;
}
}
if ('boolean' === typeof options.vscpCalcCRC) {
if (false === options.vscpCalcCRC) {
this.vscpHead &= 0xfff7;
} else {
this.vscpHead |= 0x0008;
}
}
if ('number' === typeof options.vscpClass) {
this.vscpClass = options.vscpClass;
} else if ('string' === typeof options.vscpClass) {
this.vscpClass = parseInt(options.vscpClass);
}
if ('number' === typeof options.vscpType) {
this.vscpType = options.vscpType;
} else if ('string' === typeof options.vscpType) {
this.vscpType = parseInt(options.vscpType);
}
if ('number' === typeof options.vscpObId) {
this.vscpObId = options.vscpObId;
} else if ('string' === typeof options.vscpObId) {
this.vscpObId = parseInt(options.vscpObId);
}
if ('number' === typeof options.vscpTimeStamp) {
this.vscpTimeStamp = options.vscpTimeStamp;
} else if ('string' === typeof options.vscpTimeStamp) {
this.vscpTimeStamp = parseInt(options.vscpTimeStamp);
}
if ('string' === typeof options.vscpDateTime) {
// Time in UTC for events but conversion
// is done in send routine
this.vscpDateTime = new Date(options.vscpDateTime);
} else if (true === (options.vscpDateTime instanceof Date)) {
// Time should be GMT
this.vscpDateTime = options.vscpDateTime;
}
// GUID
if ('string' === typeof options.vscpGuid) {
this.vscpGuid = options.vscpGuid;
}
// VSCP data
if (Array.isArray(options.vscpData)) {
this.vscpData = options.vscpData;
} else if (('string' === typeof options.vscpData) ) {
this.vscpData = options.vscpData.split(',');
// Make data numeric
for ( var n in this.vscpData ) {
this.vscpData[n] = readValue(this.vscpData[n]);
}
}
// 'text' to init from string form
if ('string' === typeof options.text) {
this.setFromString(options.text);
}
}
}
}
/**
* Set bit in header that mark GUID as IP v6 address
*/
setIPV6Addr() {
this.vscpHead &= 0x8FFF;
this.vscpHead |= 0x1000;
}
/**
* Check if GUID for this event is a IP v6 address or not?
*
* @return {boolean} If the GUID is a IP v6 address, it will return true,
* otherwise false.
*/
isIPV6Addr() {
var result = false;
if ( 0x1000 === (this.vscpHead & 0x7000)) {
result = true;
}
return result;
}
/**
* Set bit that mark this event as coming from a dumb node (No MDF, registers, nothing).
*/
setDumbNode() {
this.vscpHead |= 0x8000;
}
/**
* Check if this event is marked as coming from a dumb node.
* Dumb node means no MDF, registers, nothing.
*
* @return {boolean} If the node is a dumb node, it will return true, otherwise
* false.
*/
isDumbNode() {
var result = false;
if (0 < (this.vscpHead & 0x8000)) {
result = true;
}
return result;
}
/**
* Set the VSCP event priority (0-7). Lower value is higher priority.
*
* @param {number} priority - Priority
*/
setPriority(priority) {
if ((0 <= priority) && (7 >= priority)) {
this.vscpHead &= 0xff1f;
this.vscpHead |= (priority << 5);
}
}
/**
* Get the VSCP event priority (0-7). Lower value is higher priority.
*
* @return {number} Priority of the event.
*/
getPriority() {
return (this.vscpHead >> 5) & 0x0007;
}
/**
* Set the VSCP GUID type (0-7).
*
* @param {number} type - Priority
*/
setGuidType(type) {
if ((0 <= type) && (7 >= type)) {
this.vscpHead &= 0x8fff;
this.vscpHead |= (type << 12);
}
}
/**
* Get the VSCP event GUID type (0-7).
*
* @return {number} Priority of the event.
*/
getGuidType() {
return (this.vscpHead >> 12) & 0x0007;
}
/**
* Set the node id of the event sender as hard coded?
*/
setHardCodedAddr() {
this.vscpHead |= 0x0010;
}
/**
* Is the node id of the event sender hard coded or not?
*
* @return {boolean} If the node id is hard coded, it will return true,
* otherwise false.
*/
isHardCodedAddr() {
var result = false;
if (0 < (this.vscpHead & 0x0010)) {
result = true;
}
return result;
}
/**
* Set flag for no CRC calculation?
*/
setDoNotCalcCRC() {
this.vscpHead |= 0x0008;
}
/**
* Is CRC calculated or not?
*
* @return {boolean} If nor CRC should be calculated true is returned.
*/
isDoNotCalcCRC() {
var result = false;
if (0 < (this.vscpHead & 0x0008)) {
result = true;
}
return result;
}
/*!
getRollingIndex
Some nodes keep a rolling index of there frames (typically
wireless nodes). This function get the index.
@return {number} Rolling index 0-7.
*/
getRollingIndex() {
return (this.vscpHead & 7);
}
/*!
setRollingIndex
Set rolling index (0-7)
@param rindex Rolling index to set (0-7)
*/
setRollingIndex(rindex) {
rindex &= 7;
this.vscpHead &= 0xfff8;
this.vscpHead += rindex;
}
// ---------------------------------------------------
/**
* Get event as string.
* @return {string} Event as string with the following format
* vscpHead,vscpClass,vscpType,vscpObId,vscpDateTime,vscpTimeStamp,vscpGuid,vspData
*/
getAsString() {
var index = 0;
var str = '';
str += this.vscpHead.toString() + ',';
str += this.vscpClass.toString() + ',';
str += this.vscpType.toString() + ',';
str += this.vscpObId.toString() + ',';
str += this.vscpDateTime.toISOString() + ',';
str += this.vscpTimeStamp.toString() + ',';
str += this.vscpGuid;
if ( Array.isArray(this.vscpData)) {
if (0 < this.vscpData.length) {
str += ',';
}
for (index = 0; index < this.vscpData.length; ++index) {
str += this.vscpData[index].toString();
if ((this.vscpData.length - 1) > index) {
str += ',';
}
}
} else if ('string' === typeof this.vscpData) {
if (0 < this.vscpData.length) {
str += ',';
}
str += this.vscpData;
} else {
console.error(getTime() + ' Invalid VSCP event data.');
}
return str;
}
/**
* Get event as string.
* @return {string} Event as string with the following format
* vscpHead,vscpClass,vscpType,vscpObId,vscpDateTime,vscpTimeStamp,vscpGuid,vspData
*/
toString() {
return this.getAsString();
}
/**
* Set event from string.
* @return {string} Event as string
*/
setFromString(str) {
if ('string' !== typeof str) {
console.error('VSCP event is not in string form.');
throw('VSCP event is not in string form.');
}
var ea = str.split(',');
// Get head
if (ea.length) {
this.vscpHead = readValue(ea[0]);
}
// Get VSCP class
if (ea.length > 1) {
this.vscpClass = readValue(ea[1]);
}
// Get VSCP type
if (ea.length > 2) {
this.vscpType = readValue(ea[2]);
}
// Get VSCP obid
if (ea.length > 3) {
// If left empty set default
if (0 == ea[3]) {
ea[3] = '0';
}
this.vscpObId = readValue(ea[3]);
}
// Get VSCP datetime
// If left empty set default
if (0 == ea[4].length) {
this.vscpDateTime = new Date();
this.vscpDateTime = Date.UTC(
this.vscpDateTime.getUTCFullYear(), this.vscpDateTime.getUTCMonth(), this.vscpDateTime.getUTCDate(),
this.vscpDateTime.getUTCHours(), this.vscpDateTime.getUTCMinutes(), this.vscpDateTime.getUTCSeconds());
this.vscpDateTime = new Date(this.vscpDateTime);
} else if ((ea.length > 4) && (0 !== ea[4].length)) {
this.vscpDateTime = new Date(ea[4]);
}
// Timestamp
this.vscpTimeStamp = 0;
// If left empty set default
if (0 == ea[5]) {
ea[5] = '0';
}
if (ea.length > 5) {
this.vscpTimeStamp = parseInt(ea[5]);
}
// Get VSCP GUID
// If left empty set default
if (0 == ea[6]) {
ea[6] = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
}
this.vscpGuid = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
if (ea.length > 6) {
this.vscpGuid = ea[6];
}
// Get VSCP data
this.vscpData = [];
if (ea.length > 7) {
for (let i = 7; i < ea.length; i++) {
this.vscpData.push(readValue(ea[i]));
}
}
}
/**
* return JSON object representation of event
* @return {object} Event as JSON object
*/
toJSONObj() {
var ev = {};
ev.vscpHead = this.vscpHead & 0xffff;
ev.vscpClass = this.vscpClass & 0xffff;
ev.vscpType = this.vscpType & 0xffff;
ev.vscpGuid = this.vscpGuid;
ev.vscpObId = this.vscpObId;
ev.vscpTimeStamp = this.vscpTimeStamp;
ev.vscpDateTime = this.vscpDateTime;
ev.vscpData = this.vscpData;
return ev;
}
} // Event
/* ---------------------------------------------------------------------- */
/**
* Read a hex, binary, octal or decimal value and return as
* an integer.
* @param {string} input - Hex or decimal value as string
* @return {number} Value
*/
var readValue = function(input) {
var txtvalue = input.toLowerCase();
var poshex = txtvalue.indexOf('0x');
var posbin = txtvalue.indexOf('0b');
var posoct = txtvalue.indexOf('0o');
if ((-1 == poshex) && (-1 == posbin) && (-1 == posoct)) {
return parseInt(txtvalue);
} else if (-1 != poshex) {
txtvalue = txtvalue.substring(poshex + 2);
return parseInt(txtvalue, 16);
} else if (-1 != posbin) {
txtvalue = txtvalue.substring(posbin + 2);
return parseInt(txtvalue, 2);
} else if (-1 != posoct) {
txtvalue = txtvalue.substring(posoct + 2);
return parseInt(txtvalue, 8);
} else {
return NaN;
}
};
/**
* Utility function which returns the current time in the following format:
* hh:mm:ss.us
*
* @return {string} Current time in the format
* hh:mm:ss.us
*/
var getTime = function() {
var now = new Date();
var paddingHead = function(num, size) {
var str = num + '';
while (str.length < size) {
str = '0' + str;
}
return str;
};
var paddingTail = function(num, size) {
var str = num + '';
while (str.length < size) {
str = str + '0';
}
return str;
};
return '' + paddingHead(now.getHours(), 2) + ':' +
paddingHead(now.getMinutes(), 2) + ':' +
paddingHead(now.getSeconds(), 2) + '.' +
paddingTail(now.getMilliseconds(), 3);
};
/**
* Converts a GUID number array to a GUID string.
*
* @param {number[]} guid - GUID number array
* @return {string} GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
*/
var guidToStr = function(guid) {
var guidStr = '';
var index = 0;
var hexValue = '';
// If buffer . convert to array
if ( Buffer.isBuffer(guid) ) {
var arr = Array.prototype.slice.call(guid, 0);
guid = arr;
}
if ( !Array.isArray(guid) ) {
throw(new Error("Argument must be array or buffer"));
}
for (index = 0; index < guid.length; ++index) {
hexValue = guid[index].toString(16).toUpperCase();
if (2 > hexValue.length) {
hexValue = '0' + hexValue;
}
guidStr += hexValue;
if (index < (guid.length - 1)) {
guidStr += ':';
}
}
return guidStr;
};
/**
* Converts a GUID string to a GUID number array.
*
* @param {string} guid - GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
* @return {number[]} GUID number array and array with length != 16 for invalid
* GUID
*/
var strToGuid = function(str) {
var guid = [];
var items = [];
var index = 0;
if ('undefined' === typeof str) {
throw(new Error("Parameter error: Missing argument"));
}
if ('string' !== typeof str) {
throw(new Error("Parameter error: Argument should be string"));
}
// If GUID is "-" use interface GUID
if ('-' === str.trim()) {
str = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
}
items = str.split(':');
if (16 !== items.length) {
throw 'Parameter error: A VSCP GUID consist of 16 items';
}
for (index = 0; index < items.length; ++index) {
guid.push(parseInt(items[index], 16));
}
return guid;
};
/**
* Check for all null GUID
*
* @param {string|array} guid - GUID string/array
* @return {boolean} True if guid is all nills
*/
var isGuidZero = function(guid) {
var guidArray = [];
if ('undefined' === typeof guid) {
throw(new Error("Parameter error: Missing argument"));
}
if ('string' === typeof guid) {
guidArray = strToGuid(guid);
}
else if ( Array.isArray(guid) ) {
guidArray = guid;
}
// If buffer . convert to array
else if ( Buffer.isBuffer(guid) ) {
guidArray = Array.prototype.slice.call(guid, 0);
}
else {
throw(new Error("Parameter error: Argument must be of type string, array or buffer"));
}
for (let i = 0; i < 16; i++) {
if (guidArray[i]) return false;
}
return true;
};
/**
* getNodeId
*
* Get node id from a node GUID string.
*
* @param {string|array|buffer} guid - GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
* @return {number} Node id
*/
var getNodeId = function(guid) {
if ('undefined' === typeof guid) {
throw new Error("Parameter error: GUID is undefined.");
}
if ('string' === typeof guid) {
// Short for all nulls?
if (('-' === guid) || ('' === guid) ) {
return 0;
}
return ( (parseInt(guid.split(':')[14], 16) << 8) +
parseInt(guid.split(':')[15], 16));
}
else if ( Array.isArray(guid) ) {
return ((guid[14] << 8) + guid[15]);
}
// If buffer . convert to array
else if ( Buffer.isBuffer(guid) ) {
var guidArray = Array.prototype.slice.call(guid, 0);
return ((guidArray[14] << 8) + guidArray[15]);
}
else {
throw("Parameter error: Argument must be of type string, array or buffer");
}
};
/**
* getNickName
*
* Get node id from a node GUID string.
*
* @param {string|array|buffer} guid - GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
* @return {number} Node id
*/
var getNickName = function(guid) {
return getNodeId(guid);
};
/**
* setNodeId
*
* Set node to a node GUID string. TODO should be 16-bit!
*
* @param {string} guid - GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
* @param {number} nodeid - Node is to set (16-bit).
* @return {string} guid with LSB set to node id, or null
* on error.
*/
var setNodeId = function(guid, nodeid) {
var guidArray = [];
var rtype = 0; // Return type 0=string,1=array,2=buffer
if ( ('undefined' === typeof guid) ||
('undefined' === typeof nodeid) ) {
throw(new Error("Missing argument"));
}
if ('number' !== typeof nodeid) {
throw("nodeid argument should be a 16-bit number.");
}
if ('string' === typeof guid) {
rtype = 0; // Return string
guidArray = guid.split(':');
}
else if ( Array.isArray(guid) ) {
rtype = 1; // Return array
guidArray = guid;
}
// If buffer . convert to array
else if ( Buffer.isBuffer(guid) ) {
rtype = 2; // Return buffer
guidArray = Array.prototype.slice.call(guid, 0);
}
else {
throw("guid argument should be a string,array or buffer");
}
guidArray[14] = ((nodeid >> 8) & 0xff);
guidArray[15] = nodeid & 0xff;
switch (rtype) {
case 0: // String
return guidToStr(guidArray);
break;
case 1: // Array
return guidArray;
break;
case 2: // Buffer
return Buffer.from(guidArray);
break;
}
};
/**
* setNickName
*
* Set node to a node GUID string. TODO should be 16-bit!
*
* @param {string} guid - GUID string, e.g.
* 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
* @param {number} nodeid - Node is to set (16-bit).
* @return {string} guid with LSB set to node id, or null
* on error.
*/
var setNickName = function(guid, nodeid) {
return setNodeId(guid, nodeid);
};
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
// Since DOMStrings are 16-bit-encoded strings, in most browsers
// calling window.btoa on a Unicode string will cause a Character
// Out Of Range exception if a character exceeds the range of a
// 8-bit ASCII-encoded character.
/**
* Encode base64 unicode safe.
* https://stackabuse.com/encoding-and-decoding-base64-strings-in-node-js/
*
* @param {string} str - Unicode string
* @return {string} Base64
*/
var b64EncodeUnicode = function(str) {
var rv = Buffer.from(str, 'utf8');
return rv.toString('base64');
};
/**
* Decode base64 unicode safe.
* @param {string} str - Base64
* @return {string} Unicode string
* Note: prior to Node v4, use new Buffer rather than Buffer.from.
*/
var b64DecodeUnicode = function(str) {
return Buffer.from(str, 'base64').toString('utf8');
};
// ----------------------------------------------------------------------------
// Header helpers
/*!
isIPV6Addr
A node that use an IPv6 address can use this address as its's
GUID and then should set this bit to indicate this.
@param {number} head VSCP head (16-bit)
@return {boolean} true if this is a Ipv6 GUID.
*/
var isIPV6Addr = function(head) {
var result = false;
if ( 'number' !== typeof head ) {
throw(new Error("Parameter error: 'head' should be a number."))
}
if ( 0x1000 === (head & 0x7000)) {
result = true;
}
return result;
};
/*!
isDumbNode
A Dumb node have no registers etc and can only send
events. This function check if it is.
@param {number} head VSCP head (16-bit)
@return {boolean} true if this is a dumb node.
*/
var isDumbNode = function(head) {
if ( 'number' !== typeof head ) {
throw(new Error("Parameter error: 'head' should be a number."))
}
return (head & (1 << 15) ? true : false );
};
/*!
getPriority
@param {number} head VSCP head (16-bit or 8-bit)
@return {number} VSCP priority 0-7 where 0 is highest
priority.
*/
var getPriority = function(head) {
if ( 'number' !== typeof head ) {
throw(new Error("Parameter error: 'head' should be a number."))
}
head = (head & 0xff); // In case 16-bit head
return ((head >> 5) & 7);
};
/*!
Get the VSCP event GUID type (0-7).
@return {number} Priority of the event.
*/
var getGuidType = function(vscpHead) {
return (vscpHead >> 12) & 0x0007;
};
/*!
isHardCodedAddr
A hardcoded node is a node where the address is
set and can not be changed. This is important for
CAN4VSCP and RS-485 systems where the nickname id
is dynamic but the GUID for the node is not.
@param {number} head VSCP head (16-bit or 8-bit)
@return {boolean} true if this is a hardcoded address node.
*/
var isHardCodedAddr = function(head) {
var result = false;
if ( 'number' !== typeof head ) {
throw("Parameter error: 'head' should be a number.")
}
if (0 < (head & 0x0010)) {
result = true;
}
return result;
};
/*!
isDoNotCalcCRC
Check if the don't calculate CRC bit is set. This is
present for wireless devices and similar.
@param {number} head VSCP head (16-bit or 8-bit)
@return {boolean} true if CRC should noe be calculated
*/
var isDoNotCalcCRC = function(head) {
var result = false;
if ( 'number' !== typeof head ) {
throw(new Error("Parameter error: 'head' should be a number."))
}
if (0 < (head & 0x0008)) {
result = true;
}
return result;
};
/*!
getRollingIndex
Some nodes keep a rolling index of there frames (typically
wireless nodes). This function get the index.
@param {number} head VSCP head (16-bit or 8-bit)
@return {number} Rolling index 0-7.
*/
var getRollingIndex = function(head) {
if ( 'number' !== typeof head ) {
throw(new Error("Parameter error: 'head' should be a number."))
}
return (head & 7);
};
/* ---------------------------------------------------------------------- */
/*!
toFixed
Round value to a fixed precision.
@param {number} value - Value
@param {number} precision - Precision
@return {string} Rounded value
*/
var toFixed = function(value, precision) {
if ( ('number' !== typeof value) ||
('number' !== typeof precision) ) {
throw(new Error("Parameter error: 'value' and precision' should be numbers."))
}
var power = Math.pow(10, precision || 0);
return String((Math.round(value * power) / power).toFixed(precision));
};
/*!
varInt2BigInt
Convert VSCP data to a BigInt value.
The byte that make up the BigInt is stored in a byte array
with MSB to LSB storage order.
@param {array[]|buffer[]} data - Byte array/buffer
@return {bigint} BigInt value
*/
var varInt2BigInt = function(data) {
var rval = 0.0;
var work = 0n;
var bNegative = false;
var i = 0;
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."));
}
if (0 !== (data[0] & 0x80)) {
bNegative = true;
for (i = 0; i < data.length; i++) {
data[i] = ~data[i] & 0xff;
}
}
for (i = 0; i < data.length; i++) {
work = work << 8n;
work += BigInt(data[i]);
}
if (true === bNegative) {
work = -1n * (work + 1n);
}
return work;
};
/*!
Get the data coding for all measurements even for
measurements where the data coding is just implied
Note! unit and sensor index is not valid for level II measurement
events.For level II they are both frull bytes and must be read from
the data.
{
datacoding:
unit:
sensorindex:
}
@param vscpClass {number} One of the valid measurement classes
@param vscpData {array | buffer } Event data.
@return Data coding byte. See measurementDataCoding above.
*/
var getMeasurementDataCoding = function(vscpClass,vscpData) {
// -1 means not defined.
var rvobj = {
datacoding: 0,
unit: 0,
sensorindex: 0,
index: -1,
zone: -1,
subzone: -1
};
// Check parameters
if ( vscpClass !== number ) {
throw(new Error("Parameter error: 'vscpClass' should be a numeric."));
}
if ( !Array.isArray(vscpData) && !Buffer.isBuffer(vscpData)) {
throw(new Error("Parameter error: 'vscpData' should be a numeric array or buffer."));
}
if ( ( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENTX4 ) ) ) {
rvobj.datacoding = getDataCoding(vscpData[0]);
rvobj.unit = getUnit(vscpData[0]);
rvobj.sensorindex = getSensorIndex(vscpData[0]);
} else if ( vscpClass == vscp_class.VSCP_CLASS1_DATA ) {
rvobj.datacoding = getDataCoding(vscpData[0]);
rvobj.unit = getUnit(vscpData[0]);
rvobj.sensorindex = getSensorIndex(vscpData[0]);
} else if ( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT64 ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT64X4 ) ) {
// Always double, unit=0,sensorindex=0
rvobj.datacoding = measurementDataCoding.DATACODING_DOUBLE;
rvobj.unit = 0;
rvobj.sensorindex = 0;
} else if ( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREZONE ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREZONEX4 ) ) {
rvobj.datacoding = getDataCoding(vscpData[3]);
rvobj.unit = getUnit(vscpData[3]);
rvobj.sensorindex = getSensorIndex(vscpData[3]);
} else if ( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT32 ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT32X4 ) ) {
// Always single, unit=0,sensorindex=0
rvobj.datacoding = measurementDataCoding.DATACODING_SINGLE;
rvobj.unit = 0;
rvobj.sensorindex = 0;
} else if ( (vscpClass >= vscp_class.VSCP_CLASS1_SETVALUEZONE ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_SETVALUEZONEX4 ) ) {
rvobj.datacoding = getDataCoding(vscpData[3]);
rvobj.unit = getUnit(vscpData[3]);
rvobj.sensorindex = getSensorIndex(vscpData[3]);
rvobj.index = vscpData[0];
rvobj.zone = vscpData[1];
rvobj.subzone = vscpData[2];
} else if ( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENTX4) ) ) {
// At offset 16
rvobj.datacoding = getDataCoding(vscpData[16]);
rvobj.unit = getUnit(vscpData[16]);
rvobj.sensorindex = getSensorIndex(vscpData[16]);
} else if ( vscpClass == (512 + vscp_class.VSCP_CLASS1_DATA ) ) {
// At offset 16
rvobj.datacoding = getDataCoding(vscpData[16]);
rvobj.unit = getUnit(vscpData[16]);
rvobj.sensorindex = getSensorIndex(vscpData[16]);
} else if ( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT64) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT64X4) ) ) {
// Offset 16, Always double, unit=0,sensorindex=0
rvobj.datacoding = measurementDataCoding.DATACODING_DOUBLE;
rvobj.unit = 0;
rvobj.sensorindex = 0;
} else if ( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREZONE) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREZONEX4) ) ) {
// At offset 16
rvobj.datacoding = getDataCoding(vscpData[16+3]);
rvobj.unit = getUnit(vscpData[16+3]);
rvobj.sensorindex = getSensorIndex(vscpData[16+3]);
rvobj.index = vscpData[0];
rvobj.zone = vscpData[1];
rvobj.subzone = vscpData[2];
} else if ( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT32) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT32X4) ) ) {
// Offset 16, Always double, unit=0,sensorindex=0
rvobj.datacoding = measurementDataCoding.DATACODING_SINGLE;
rvobj.unit = 0;
rvobj.sensorindex = 0;
} else if ( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_SETVALUEZONE) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_SETVALUEZONEX4) ) ) {
rvobj.datacoding = vscpData[16+3];
rvobj.unit = getUnit(vscpData[16+3]);
rvobj.sensorindex = getSensorIndex(vscpData[16+3]);
rvobj.index = vscpData[16];
rvobj.zone = vscpData[16+1];
rvobj.subzone = vscpData[16+2];
} else if ( (vscp_class.VSCP_CLASS2_MEASUREMENT_STR == vscpClass) ) {
rv = measurementDataCoding.DATACODING_STRING;
// Always string, index=0
rvobj.datacoding = measurementDataCoding.DATACODING_STRING;
rvobj.sensorindex = vscpData[0];
rvobj.index = 0;
rvobj.zone = vscpData[1];
rvobj.subzone = vscpData[2];
rvobj.unit = vscpData[3];
} else if ( (vscp_class.VSCP_CLASS2_MEASUREMENT_FLOAT == vscpClass) ) {
// Always double, index=0
rvobj.datacoding = measurementDataCoding.DATACODING_DOUBLE;
rvobj.sensorindex = vscpData[0];
rvobj.index = 0;
rvobj.zone = vscpData[1];
rvobj.subzone = vscpData[2];
rvobj.unit = vscpData[3];
}
return rvobj;
}
/*!
getDataCoding
Get data coding.
@param {number} data - Data
@return {number} Coding
*/
var getDataCoding = function(datacoding) {
if ( 'number' !== typeof datacoding ) {
throw("Parameter error: 'datacoding' should be a number.")
}
return (datacoding & measurementDataCodingMask.MASK_DATACODING_TYPE);
};
/*!
getDataCodingStr
Get unit descriptive string from data coding.
@param {number} data - Data coding
@return {string} Unit string
*/
var getDataCodingStr = function(datacoding) {
var datacodingtxt = "";
if ( 'number' !== typeof datacoding ) {
throw("Parameter error: 'datacoding' should be a number.")
}
switch (datacoding) {
case measurementDataCoding.DATACODING_BIT:
datacodingtxt = "Bits";
break;
case measurementDataCoding.DATACODING_BYTE:
datacodingtxt = "Bytes";
break;
case measurementDataCoding.DATACODING_INTEGER:
datacodingtxt = "Integer";
break;
case measurementDataCoding.DATACODING_NORMALIZED:
datacodingtxt = "Normalized integer";
break;
case measurementDataCoding.DATACODING_STRING:
datacodingtxt = "String";
break;
case measurementDataCoding.DATACODING_SINGLE:
datacodingtxt = "Floating point (single)";
break;
default:
datacodingtxt = "Unknown data coding";
break;
}
return datacodingtxt;
}
/*!
getUnit
Get unit from data coding.
@param {number} data - Data coding
@return {number} Unit
*/
var getUnit = function(datacoding) {
if ( 'number' !== typeof datacoding ) {
throw("Parameter error: 'datacoding' should be a number.")
}
return ((datacoding & measurementDataCodingMask.MASK_DATACODING_UNIT) >> 3);
};
/*!
getSensorIndex
Get sensor index from data coding.
@param {number} data - Data coding
@return {number} Sensor index
*/
var getSensorIndex = function(datacoding) {
if ( 'number' !== typeof datacoding ) {
throw("Parameter error: 'datacoding' should be a number.")
}
return (datacoding & measurementDataCodingMask.MASK_DATACODING_INDEX);
};
/*!
isMeasurement
Returns true if vscpClass is a measurement class
@param {number} vscpClass - VSCP class to check
@return {boolean True if vscpClass is a measurement class, false otherwise
*/
var isMeasurement = function(vscpClass) {
let rv = false;
// Allow for event object
// if ( typeof vscpClass !== 'object') {
// vscpClass = vscpClass.vscpClass;
// }
if (( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENTX4 ) ) ||
(vscpClass == vscp_class.VSCP_CLASS1_DATA ) ||
( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT64 ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT64X4 ) ) ||
( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREZONE ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREZONEX4 ) ) ||
( (vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT32 ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT32X4 ) ) ||
( (vscpClass >= vscp_class.VSCP_CLASS1_SETVALUEZONE ) &&
(vscpClass <= vscp_class.VSCP_CLASS1_SETVALUEZONEX4 ) ) ||
( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENTX4) ) ) ||
(vscpClass == (512 + vscp_class.VSCP_CLASS1_DATA ) ) ||
( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT64) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT64X4) ) ) ||
( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREZONE) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREZONEX4) ) ) ||
( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT32) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_MEASUREMENT32X4) ) ) ||
( (vscpClass >= (512 + vscp_class.VSCP_CLASS1_SETVALUEZONE) ) &&
(vscpClass <= (512 + vscp_class.VSCP_CLASS1_SETVALUEZONEX4) ) ) ||
(vscp_class.VSCP_CLASS2_MEASUREMENT_STR == vscpClass) ||
(vscp_class.VSCP_CLASS2_MEASUREMENT_FLOAT == vscpClass)) {
rv = true;
}
return rv;
};
/*!
decodeMeasurementClass10
Decode a class 10 measurement.
CLASS1.MEASUREMENT
@param {number[]} data - Data (event data array/buffer
where first data byte is the VSCP data coding)
@return bits - {logical[]} Array of bits
bytes - {number[]} Array of bytes
integer . {bigint} Integer as bigint
string - {number} String value as number.
float - {number} Floating point value as number.
*/
var decodeMeasurementClass10 = function(data) {
var rval;
var newData = [];
var sign = 0;
var exp = 0;
var mantissa = 0;
var str = '';
var i = 0;
var j = 0;
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 2 ) {
throw(new Error("Parameter error: 'data' should have a length >= 2."))
}
switch (getDataCoding( data[0] & measurementDataCodingMask.MASK_DATACODING_TYPE ) ) {
case measurementDataCoding.DATACODING_BIT: // Bits
rval = [];
for (i=1; i<data.length; i++) {
for (j=0;j<8;j++) {
rval.push((data[i] & (1<<(7-j))) ? true : false);
}
}
break;
case measurementDataCoding.DATACODING_BYTE: // Bytes
rval = [];
for (i=1; i<data.length; i++) {
rval.push(data[i]);
}
break;
case measurementDataCoding.DATACODING_INTEGER: // Integer
rval = varInt2BigInt(data.slice(1));
break;
case measurementDataCoding.DATACODING_STRING: // String
for (i = 1; i < data.length; i++) {
str += String.fromCharCode(data[i]);
}
rval = parseFloat(str);
break;
case measurementDataCoding.DATACODING_NORMALIZED: // Normalized integer
exp = data[1];
rval = Number(varInt2BigInt(data.slice(2)));
// Handle mantissa
if (0 !== (exp & 0x80)) {
exp &= 0x7f;
rval = rval / Math.pow(10, exp);
}
else {
exp &= 0x7f;
rval = rval * Math.pow(10, exp);
}
break;
case measurementDataCoding.DATACODING_SINGLE: // Floating point
if (5 === data.length) {
rval = data.readFloatBE(1);
}
break;
case measurementDataCoding.DATACODING_DOUBLE:
if (8 === data.length) {
rval = data.readDoubleBE(1);
}
break;
case measurementDataCoding.DATACODING_RESERVED2: // Reserved
break;
default:
break;
}
return rval;
};
/*!
decodeMeasurementClass60
CLASS1.MEASUREMENT64
Decode a class 60 measurement. Data is a
64-bit double floating point number.
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass60 = function(data) {
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 8 ) {
throw(new Error("Parameter error: 'data' should have a length >= 8."))
}
return data.readDoubleBE(0);
};
/*!
decodeMeasurementClass65
Decode a class 65 measurement.
CLASS1.MEASUREZONE
0 - Index (Not sensor index)
1 - Zone
2 - subzone
3 - data coding
4-7 - Data with format defined by data
coding byte.
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass65 = function(data) {
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 5 ) {
throw(new Error("Parameter error: 'data' should have a length >= 5."))
}
var b = data.slice(3);
return decodeMeasurementClass10(b);
};
/*!
decodeMeasurementClass70
CLASS1.MEASUREMENT32
Decode a class 70 measurement.
Data is a 32-bit floating
point value.
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass70 = function(data) {
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 4 ) {
throw(new Error("Parameter error: 'data' should have a length >= 4."))
}
return data.readFloatBE(0);
};
/*!
decodeMeasurementClass85
CLASS1.SETVALUEZONE
Decode a class 85 measurement (setvalue)
Data is
0 - Sensor index
1 - Zone
2 - Subzone
3 - Data coding
4-7 - Data Value
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass85 = function(data) {
return decodeMeasurementClass65(data);
};
/*!
decodeMeasurementClass1040
Decode a class 1040 measurement
CLASS2.MEASUREMENT_STR
Data is measurement in string form.
0 - Sensor index
1 - Zone
2 - Subzone
3 - Unit
4.. - String up to the maximum data size of
483 digits including a possible decimal
point. The decimal point should always be
a "." independent of locale.
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass1040 = function(data) {
var str = "";
var i = 0;
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 4 ) {
throw(new Error("Parameter error: 'data' should have a length >= 4."))
}
for (i = 4; i < data.length; i++) {
str += String.fromCharCode(data[i]);
}
return parseFloat(str);
};
/*!
decodeMeasurementClass1060
Decode a class 1060 measurement
CLASS2.MEASUREMENT_FLOAT
Data is measurement in floating point
double form.
0 - Sensor index
1 - Zone
2 - Subzone
3 - Unit
4-11 - 64-bit double precision floating point
value stored MSB first.
@param {number[]} data - Data array/buffer
@return {number} Value as float
*/
var decodeMeasurementClass1060 = function(data) {
// If argument is array convert to buffer
if ( Array.isArray(data) ) {
data = Buffer.from(data);
}
// We must have a buffer
if ( !Buffer.isBuffer(data) ) {
throw(new Error("Parameter error: 'data' should be a numeric array or buffer."))
}
// We must have size that fit the expected data
if ( data.length < 12 ) {
throw(new Error("Parameter error: 'data' should have a length >= 12."))
}
return data.readDoubleBE(4);
}
/*!
getMeasurementData
Return measurement information including value for a
measurement event.
@param {object} e Measurement event object
@return a measurement object on the following form
{
unit: {number}
sensorindex: {number}
datacoding: {number}
index: {number}
zone: {number}
subzone: {number}
value: {number}
}
Items that are not defined for a particular event is not returned
and will be left undefined. This is typical for zone/subzone that
is only available for a few of the measurement events.
If the event is not a measurement event an empty object
is returned.
*/
var getMeasurementData = function(e) {
var rvobj = {};
// unit: {number},
// sensorindex: {number},
// datacoding: {number},
// index: {number},
// zone: {number},
// subzone: {number},
// value: {number}
// Check parameters
if ( typeof e !== 'object' ) {
throw(new Error("Parameter error: 'e' should be a VSCP event object."));
}
if ( !isMeasurement(e.vscpClass) ) {
throw(new Error("Parameter error: 'e' should be a VSCP measurement event."));
}
if ( ( (e.vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT ) &&
(e.vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENTX4 ) ) ) {
rvobj.datacoding = getDataCoding(e.vscpData[0]);
rvobj.unit = getUnit(e.vscpData[0]);
rvobj.sensorindex = getSensorIndex(e.vscpData[0]);
rvobj.value = decodeMeasurementClass10(e.vscpData);
} else if ( e.vscpClass == vscp_class.VSCP_CLASS1_DATA ) {
rvobj.datacoding = getDataCoding(e.vscpData[0]);
rvobj.unit = getUnit(e.vscpData[0]);
rvobj.sensorindex = getSensorIndex(e.vscpData[0]);
rvobj.value = decodeMeasurementClass10(e.vscpData);
} else if ( (e.vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT64 ) &&
(e.vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT64X4 ) ) {
// Always double, unit=0,sensorindex=0
rvobj.datacoding = measurementDataCoding.DATACODING_DOUBLE;
rvobj.unit = 0;
rvobj.sensorindex = 0;
rvobj.value = decodeMeasurementClass60(e.vscpData);
} else if ( (e.vscpClass >= vscp_class.VSCP_CLASS1_MEASUREZONE ) &&
(e.vscpClass <= vscp_class.VSCP_CLASS1_MEASUREZONEX4 ) ) {
rvobj.datacoding = getDataCoding(e.vscpData[3]);
rvobj.unit = getUnit(e.vscpData[3]);
rvobj.sensorindex = getSensorIndex(e.vscpData[3]);
rvobj.index = e.vscpData[0];
rvobj.zone = e.vscpData[1];
rvobj.subzone = e.vscpData[2];
rvobj.value = decodeMeasurementClass65(e.vscpData);
} else if ( (e.vscpClass >= vscp_class.VSCP_CLASS1_MEASUREMENT32 ) &&
(e.vscpClass <= vscp_class.VSCP_CLASS1_MEASUREMENT32X4 ) ) {
// A