Edit IoT profiles

To edit IoT profiles, first go to the device management area by clicking on Devices at the top right in the user menu. Then select the IoT Profiles tab.

The sidebar on the left contains all IoT profiles that you have already set up. To create a new profile, click Add Profile at the top of the sidebar.

You can now configure the IoT profile in the main view.

../../../_images/edit-iot-profiles.png

An IoT profile consists of a list of data point definitions and a decoding algorithm that converts the data from the device into these data points.

Note

If you create or edit an IoT profile, this initially has no effect on existing devices. Only when you assign the profile to a device (see Apply IoT profiles) are the data points created on the device and the decoder registered.

Profile name

Give the IoT profile a meaningful name, for example, the model name of the IoT device or its purpose.

Device type

Select the type of IoT device here for which this profile is intended.

Data points

In this table you can configure the data points that should be generated for every device to which the IoT profile is assigned.

Key

A key that is unique throughout the table and with which the decoder can identify the corresponding data point. The key can be very short as long as it is unique and identifies the data point clearly, for example “temperature” or “meter01”.

Note

To be able to set up a battery watchdog on the device that issues an alarm when the battery level is low, you must mark one of the data points with the key battery. This data point may only assume values from 0 to 100. Do not forget to read the current battery level in your decoder and assign it to this data point. Once you have assigned the profile to a device, you can set the battery alerting on the configuration page of that device in the Alerting section.

Note

ThermoValve devices can have a temperature sensor (e.g. a Wisely or a custom LoRa device) assigned that will be used as the room temperature of the thermostat. If you create an IoT profile for a custom LoRa device that you want to use in conjunction with a ThermoValve device, the key of the temperature data point has to be set to temperature in the profile.

Name

The name of the data point as it is to be displayed later in the data point list, on charts or schematics.

System Name

The system name of the data point to uniquely identify it in the system.

To ensure that data points get a unique system name even if you apply the IoT profile to multiple devices, you can use one of the following placeholders:

  • %DEVID% will automatically be replaced with the device identification (usually the device’s EUI).

  • %DEVNAME% will automatically be replaced with the device name. Note that since the device name is localizable but system names are not, the system will use the device name in the language of the user who is assigning the IoT profile to the data point.

Description

An additional description of the data point, for example its intended use or exact location or its component designation.

Type

The data type of the data point.

Unit

The unit in which the measurement values are recorded on this data point.

Expose to the public web app

If this checkbox is selected, the data point will be displayed in the public web app that can be opened by scanning the QR code on the respective device. Note that the data point and its most recent measurements will be available in the public web app without a login. By disabling this option, the data point and its associated measurements will only be accessible from the regular Avelon user interface with a valid login.

Decoder

In this input field you define the decoding algorithm with which the data stream of the IoT device is converted into the measurements or alarms of the individual data points.

The algorithm must be written as a JavaScript function decode that accepts the following parameters:

function decode(bytes, port, date) {
  var decoded = [];
  // Place your code here and push the results to 'decoded'.
  return decoded;
}
bytes

The data stream as a byte array.

port

The port number on which the data stream is transferred. This can be used on some devices, for example, to identify the message type.

date

A date object of the time at which the message was sent from the IoT device. If this information is not available, the receipt date of the message is assigned to this parameter instead.

Basically, the decode function must return an array of objects in the following form:

Measurements

{
  type: 'measurement',
  key: 'data_point_key_xyz',
  datetime: 1539093694, // milliseconds since Jan 1, 1970 or Date.prototype.toISOString()
  value: 23.5
}
type

Must be set to 'measurement' for normal measurements.

key

The key of the corresponding data point as defined above in the Data Points table in the Key column.

../../../_images/edit-iot-profiles-keys.png
datetime

Date and time of the measurement. Can be set to Date.now() or derived from the function parameter date. Valid values are either the number of milliseconds since the 1st of January 1970 (Date.prototype.getTime()) or a string according to ISO 8601 (Date.prototype.toISOString()).

value

The actually recorded measurement as an integer, floating point or truth value.

Alarms

{
  type: 'alarm',
  key: 'data_point_key_xyz',
  datetime: 1539093694, // milliseconds since Jan 1, 1970 or Date.prototype.toISOString()
  value: 23.5,
  toState: 'offNormal', // or 'normal' if alarm is gone
  message: 'The alarm message'
}
type

Must be set to 'alarm' for alarms.

key

The key of the corresponding data point as defined above in the Data Points table in the Key column.

../../../_images/edit-iot-profiles-keys.png
datetime

Date and time of the alarm. Can be set to Date.now() or derived from the function parameter date. Valid values are either the number of milliseconds since the 1st of January 1970 (Date.prototype.getTime()) or a string according to ISO 8601 (Date.prototype.toISOString()).

value

The pending alarm value.

toState

'offNormal' In the event of an alarm or 'normal' in the event of an acknowledgement. If the alarm does not refer to the current measurement, but to another problem (e.g. that the value could not be read out correctly), you can also use the status 'fault'.

message

The alarm text as it should be displayed in an alarm ticket and in any notifications.

Auxiliary functions

In this section you will find some auxiliary functions that can simplify the manipulation of byte arrays and the creation of output objects.

Create output object for measurements

// Helper function to generate a measurement object.
function createMeasurement(key, datetime, value) {
  return {
    type: 'measurement',
    key: key,
    datetime: datetime,
    value: value
  };
}

Create measurement object:

decoded.push(createMeasurement('data_point_key_xyz', 1539093694, 24.5));

Create output object for alarms

// Helper function to generate an alarm object.
function createAlarm(key, datetime, value, isGone, message) {
  return {
    type: 'alarm',
    key: key,
    datetime: datetime,
    value: value,
    toState: isGone ? 'normal' : 'offNormal',
    message: message
  };
}

Create alarm object:

decoded.push(createAlarm('data_point_key_xyz', 1539093694, 24.5, false, 'Alarm'));

Parse Float32

// Helper function to generate a float32 from an array of bytes at the given offset.
function getFloat32(bytes, offset, littleEndian) {
  var buffer = new ArrayBuffer(4);
  var view = new DataView(buffer);
  for (var i = 0; i < 4; i++) {
    view.setUint8(i, bytes[offset + i]);
  }
  return Number(view.getFloat32(0, littleEndian));
}

Parse Int16

// Helper function to generate an int16 from an array of bytes at the given offset.
function getInt16(bytes, offset, littleEndian) {
  var buffer = new ArrayBuffer(2);
  var view = new DataView(buffer);
  for (var i = 0; i < 2; i++) {
    view.setUint8(i, bytes[offset + i]);
  }
  return Number(view.getInt16(0, littleEndian));
}

Parse Uint16

// Helper function to generate a uint16 from an array of bytes at the given offset.
function getUint16(bytes, offset, littleEndian) {
  var buffer = new ArrayBuffer(2);
  var view = new DataView(buffer);
  for (var i = 0; i < 2; i++) {
    view.setUint8(i, bytes[offset + i]);
    }
  return Number(view.getUint16(0, littleEndian));
}

Parse Uint8

// Helper function to generate a uint8 from an array of bytes at the given offset.
function getUint8(bytes, offset, littleEndian) {
  var buffer = new ArrayBuffer(1);
  var view = new DataView(buffer);
  view.setUint8(0, bytes[offset]);
  return Number(view.getUint8(0));
}

Parse single bit

// Helper function to extract a single bit from a byte in an array of bytes.
// Bit index 0 is the least significant bit, 7 is the most significant bit.
function getBit(bytes, offset, bitIndex) {
  return (bytes[offset] & (1 << bitIndex)) >> bitIndex;
}

Define number of decimal places of a number

// Helper function to set the number of decimals of a number.
function getFixed(number, decimals) {
  return Number(number.toFixed(decimals));
}

Test Payload

To test the decoding algorithm, enter the expected payload in hexadecimal format (e.g. 007B42F600003039) in the Payload field and then click on the Test button.

If no errors occur, the decoded output object appears in the Result field. In the event of an error, toast notifications appear with a short description of the problem and, if necessary, the incorrect line in the source code.

Complete example

function decode(bytes, port, date) {
  // Decode an uplink message from a buffer (array) of bytes to an object.

  // Helper function to generate a measurement object.
  function createMeasurement(key, datetime, value) {
    return {
      type: 'measurement',
      key: key,
      datetime: datetime,
      value: value
    };
  }

  // Helper function to generate an alarm object.
  function createAlarm(key, datetime, value, isGone, message) {
    return {
      type: 'alarm',
      key: key,
      datetime: datetime,
      value: value,
      toState: isGone ? 'normal' : 'offNormal',
      message: message
    };
  }

  // Helper function to generate a float32 from an array of bytes at the given offset.
  function getFloat32(bytes, offset, littleEndian) {
    var buffer = new ArrayBuffer(4);
    var view = new DataView(buffer);
    for (var i = 0; i < 4; i++) {
      view.setUint8(i, bytes[offset + i]);
    }
    return Number(view.getFloat32(0, littleEndian));
  }

  // Helper function to generate an int16 from an array of bytes at the given offset.
  function getInt16(bytes, offset, littleEndian) {
    var buffer = new ArrayBuffer(2);
    var view = new DataView(buffer);
    for (var i = 0; i < 2; i++) {
      view.setUint8(i, bytes[offset + i]);
    }
    return Number(view.getInt16(0, littleEndian));
  }

  // Helper function to generate a uint16 from an array of bytes at the given offset.
  function getUint16(bytes, offset, littleEndian) {
    var buffer = new ArrayBuffer(2);
    var view = new DataView(buffer);
    for (var i = 0; i < 2; i++) {
      view.setUint8(i, bytes[offset + i]);
    }
    return Number(view.getUint16(0, littleEndian));
  }

  // Helper function to generate a uint8 from an array of bytes at the given offset.
  function getUint8(bytes, offset) {
    var buffer = new ArrayBuffer(1);
    var view = new DataView(buffer);
    view.setUint8(0, bytes[offset]);
    return Number(view.getUint8(0));
  }

  // Helper function to extract a single bit from a byte in an array of bytes.
  // Bit index 0 is the least significant bit, 7 is the most significant bit.
  function getBit(bytes, offset, bitIndex) {
    return (bytes[offset] & (1 << bitIndex)) >> bitIndex;
  }

  // Helper function to set the number of decimals of a number.
  function getFixed(number, decimals) {
    return Number(number.toFixed(decimals));
  }

  var decoded = [];
  var now = Date.now();

  // Add the decoded values to an array.
  // Example:

  // Decode byte 1 (at index 0) as a battery capacity with a value between 0 and 100.
  decoded.push(createMeasurement('battery', now, getFixed(getUint8(bytes, 0) * 100 / 255, 2)));

  // Decode bytes 2 to 3 (starting at index 1) as an unsigned 16-bit integer
  // for the data point with key 'key_1'.
  decoded.push(createMeasurement('key_1', now, getUint16(bytes, 1)));

  // Decode bytes 4 to 7 (starting at index 3) as a 32-bit float
  // for the data point with key 'key_2'.
  decoded.push(createMeasurement('key_2', now, getFloat32(bytes, 3)));

  // Decode bytes 8 to 9 (starting at index 7) as a signed 16-bit integer
  // for the data point with key 'key_3'.
  decoded.push(createMeasurement('key_3', now, getInt16(bytes, 7)));

  // Decode the least significant bit in byte 10 as a binary value (0 or 1).
  // for the data point with key 'key_4'.
  decoded.push(createMeasurement('key_4', now, getBit(bytes, 9, 0)));

  // Decode byte 11 (at index 10) as a binary alarm value (0 or 1)
  // for the data point with key 'key_1' and provide a specific alarm message.
  decoded.push(createAlarm('key_1', now, getBit(bytes, 10, 0), false, 'Add alarm text here'));

  return decoded;
}