This library contains message encoders and decoders for Jooby LoRaWAN devices.
Install required dependencies:
npm install jooby-codec
This will provide codecs for:
usWaterMeterCommand)PLC is implemented in source but is not part of the public package exports.
There is a low-level documentation available in a separate repository.
It's also possible to send MTX commands via Lora.
Add to the project:
import {commands, message} from 'jooby-codec/analog';
// output all available downlink and uplink commands tree
console.log(commands);
// all uplink commands
console.log(commands.uplink);
// one particular command
console.log(commands.uplink.correctTime2000);
// output main namespace for work with messages
console.log(message);
But it's better to add only necessary commands to the project:
// to get only downlink commands
import {downlink} from 'jooby-codec/analog/commands';
// or slightly more efficient
import * as downlink from 'jooby-codec/analog/commands/downlink';
// to get one particular command
import * as setTime2000 from 'jooby-codec/analog/commands/downlink/setTime2000.js';
The last approach is preferred as it is more efficient and will initialize only the necessary commands.
Prepare and parse downlink message:
import * as message from 'jooby-codec/analog/message/downlink';
import * as downlinkCommands from 'jooby-codec/analog/commands/downlink';
import getHexFromBytes from 'jooby-codec/utils/getHexFromBytes.js';
const commands = [
{
id: downlinkCommands.correctTime2000.id,
parameters: {sequenceNumber: 45, seconds: -120}
}
];
const bytes = message.toBytes(commands);
console.log('message encoded:', JSON.stringify(bytes));
// output:
[12,2,45,136,254]
console.log('message encoded in HEX:', getHexFromBytes(bytes));
// output:
'0c 02 2d 88 fe'
// decode message back from bytes
const parsedMessage = message.fromBytes(bytes);
console.log('parsedMessage:', parsedMessage);
// output:
{
commands: [
{
id: 12,
name: 'correctTime2000',
headerSize: 2,
bytes: [Array],
parameters: [Object]
}
],
bytes: [ 12, 2, 45, 136, 254 ],
lrc: { received: 254, calculated: 254 }
}
Parse uplink message:
import * as message from 'jooby-codec/analog/message/uplink';
// binary data received from a device
const bytes = [0x0c, 0x01, 0x00, 0x58];
// decode it
const payload = message.fromBytes(bytes);
if ( 'error' in payload ) {
console.log('decode failure:', payload.error, payload.message);
} else {
console.log('message decoded:', payload.commands);
// output:
[
{
id: 12,
name: 'correctTime2000',
headerSize: 2,
bytes: [ 12, 1, 0 ],
parameters: { status: 0 }
}
]
}
There may be necessary to pass an additional config with hardware type:
import * as message from 'jooby-codec/analog/message/uplink';
import * as hardwareTypes from 'jooby-codec/analog/constants/hardwareTypes.js';
// binary data received from a device
const bytes = [0x62, 0x20, 0x09, 0x1e];
const config = {
hardwareType: hardwareTypes.GASI3
};
// decode it
const payload = message.fromBytes(bytes, config);
if ( 'error' in payload ) {
console.log('decode failure:', payload.error, payload.message);
} else {
console.log('message decoded:', payload.commands[0]);
// output:
{
id: 96,
name: 'lastEvent',
headerSize: 1,
bytes: [98, 32, 9],
config: {hardwareType: 3},
parameters: {
sequenceNumber: 32,
status: {
isBatteryLow: true,
isMagneticInfluence: false,
isButtonReleased: false,
isConnectionLost: true
}
}
}
}
Analog ultrasound commands are carried inside Analog usWaterMeterCommand. Encode the inner payload first, then wrap it:
import * as ultrasoundMessage from 'jooby-codec/analog-ultrasound/message/downlink';
import * as ultrasoundCommands from 'jooby-codec/analog-ultrasound/commands/downlink';
import * as analogMessage from 'jooby-codec/analog/message/downlink';
import * as analogCommands from 'jooby-codec/analog/commands/downlink';
const innerBytes = ultrasoundMessage.toBytes([
{
id: ultrasoundCommands.setDepassivationConfig.id,
parameters: {resistanceStartThreshold: 24000, resistanceStopThreshold: 20000}
}
]);
const bytes = analogMessage.toBytes([
{
id: analogCommands.usWaterMeterCommand.id,
parameters: {data: innerBytes}
}
]);
Decode the reverse way: Analog uplink usWaterMeterCommand.parameters.data is an Analog ultrasound message (analog-ultrasound/message/uplink.fromBytes).
Add to the project:
import {commands, message} from 'jooby-codec/mtx1';
// output all available downlink and uplink commands tree
console.log(commands);
// all uplink commands
console.log(commands.uplink);
// one particular command
console.log(commands.uplink.setDateTime);
// output main namespace for work with messages
console.log(message);
But it's better to add only necessary commands to the project:
// to get only downlink commands
import {downlink} from 'jooby-codec/mtx1/commands';
// or slightly more efficient
import * as downlink from 'jooby-codec/mtx1/commands/downlink';
// to get one particular command
import * as setDateTime from 'jooby-codec/mtx1/commands/downlink/setDateTime.js';
The last approach is preferred as it is more efficient and will initialize only the necessary commands.
Prepare and parse downlink message and frame:
import * as message from 'jooby-codec/mtx1/message/downlink';
import * as frame from 'jooby-codec/mtx1/utils/frame.js';
import * as downlinkCommands from 'jooby-codec/mtx1/commands/downlink';
import getHexFromBytes from 'jooby-codec/utils/getHexFromBytes.js';
import * as frameTypes from 'jooby-codec/mtx1/constants/frameTypes.js';
const aesKey = [...Array(16).keys()];
const messageId = 10;
const commands = [
{
id: downlinkCommands.setDateTime.id,
parameters: {
isSummerTime: false,
seconds: 55,
minutes: 31,
hours: 18,
day: 2,
date: 19,
month: 2,
year: 24
}
}
];
const messageBytes = message.toBytes(
commands,
{
messageId,
accessLevel: downlinkCommands.setDateTime.accessLevel,
aesKey
}
);
console.log('message encoded:', JSON.stringify(messageBytes));
// output:
[10,19,237,116,10,174,74,186,200,66,196,27,231,245,13,60,40,132]
console.log('message encoded in HEX:', getHexFromBytes(messageBytes));
// output:
'0a 13 ed 74 0a ae 4a ba c8 42 c4 1b e7 f5 0d 3c 28 84'
const frameBytes = frame.toBytes(
messageBytes,
{
type: frameTypes.DATA_REQUEST,
source: 0xffff,
destination: 0xaaaa
}
);
console.log('frame encoded:', frameBytes);
// output:
[126,80,170,170,255,255,10,125,51,237,116,10,174,74,186,200,66,196,27,231,245,13,60,40,132,97,187,126]
console.log('frame encoded in HEX:', getHexFromBytes(frameBytes));
// output:
'7e 50 aa aa ff ff 0a 7d 33 ed 74 0a ae 4a ba c8 42 c4 1b e7 f5 0d 3c 28 84 61 bb 7e'
// decode message back from bytes
const parsedMessage = message.fromBytes(messageBytes, {aesKey});
console.log('parsed message:', parsedMessage);
// output:
/* {
messageId: 10,
accessLevel: 3,
commands: [
{
id: 8,
name: 'setDateTime',
headerSize: 2,
bytes: [Array],
parameters: [Object]
}
],
bytes: [237, 116, 10, 174, 74, 186, 200, 66, 196, 27, 231, 245, 13, 60, 40, 132],
lrc: {received: 119, calculated: 119}
} */
// decode message back from frame
const parsedFrame = frame.fromBytes(frameBytes);
console.log('parsedFrame:', parsedFrame);
// output:
/* {
bytes: [126, 80, 170, 170, 255, 255, 10, 125, 51, 237, 116, 10, 174, 74, 186, 200, 66, 196, 27, 231, 245, 13, 60, 40, 132, 97, 187, 126],
payload: [10, 19, 237, 116, 10, 174, 74, 186, 200, 66, 196, 27, 231, 245, 13, 60, 40, 132],
crc: {calculated: 47969, received: 47969},
header: {type: 80, typeName: 'DATA_REQUEST', destination: 43690, source: 65535}
} */
if ( 'bytes' in parsedFrame ) {
const parsedMessage2 = message.fromBytes(parsedFrame.payload, {aesKey});
console.log('parsedMessage2:', parsedMessage2);
// output:
/* {
messageId: 10,
accessLevel: 3,
commands: [
{
id: 8,
name: 'setDateTime',
headerSize: 2,
bytes: [Array],
parameters: [Object]
}
],
bytes: [237, 116, 10, 174, 74, 186, 200, 66, 196, 27, 231, 245, 13, 60, 40, 132],
lrc: {received: 119, calculated: 119}
} */
}
Parse uplink message:
import * as message from 'jooby-codec/mtx1/message/uplink';
import * as frame from 'jooby-codec/mtx1/utils/frame.js';
import getBytesFromHex from 'jooby-codec/utils/getBytesFromHex.js';
const aesKey = [...Array(16).keys()];
// a message with one getDeviceId command
const messageBytes = getBytesFromHex('0a13cf5fa5a836724fc97a0735f817d49651');
// the same message as a frame
const frameBytes = getBytesFromHex('7e51aaaaffff0a7d33cf5fa5a836724fc97a0735f817d49651fe547e');
const parsedMessage = message.fromBytes(messageBytes, {aesKey});
console.log('parsed message:', parsedMessage);
/* output:
{
messageId: 10,
accessLevel: 3,
commands: [
{
id: 5,
name: 'getDeviceId',
headerSize: 2,
bytes: [Array],
parameters: [Object]
}
],
bytes: [
207, 95, 165, 168, 54, 114, 79, 201,
122, 7, 53, 248, 23, 212, 150, 81
],
lrc: { received: 71, calculated: 71 }
} */
const parsedFrame = frame.fromBytes(frameBytes);
console.log('parsed frame:', parsedFrame);
/* output:
{
bytes: [
126, 81, 170, 170, 255, 255, 10, 125,
51, 207, 95, 165, 168, 54, 114, 79,
201, 122, 7, 53, 248, 23, 212, 150,
81, 254, 84, 126
],
payload: [
10, 19, 207, 95, 165, 168, 54, 114,
79, 201, 122, 7, 53, 248, 23, 212,
150, 81
],
crc: { calculated: 21758, received: 21758 },
header: {
type: 81,
typeName: 'DATA_RESPONSE',
destination: 43690,
source: 65535
}
} */
// parsed successfully
if ( 'bytes' in parsedFrame ) {
const parsedMessage2 = message.fromBytes(parsedFrame.payload, {aesKey});
if ( JSON.stringify(parsedMessage) === JSON.stringify(parsedMessage2) ) {
console.log('correct message');
} else {
throw new Error('parsedMessage and parsedMessage2 should be identical!');
}
}
Add to the project:
import {commands, message} from 'jooby-codec/obis-observer';
// output all available downlink and uplink commands tree
console.log(commands);
// all uplink commands
console.log(commands.uplink);
// one particular command
console.log(commands.uplink.getMeterId);
// output main namespace for work with messages
console.log(message);
But it's better to add only necessary commands to the project:
// to get only downlink commands
import {downlink} from 'jooby-codec/obis-observer/commands';
// or slightly more efficient
import * as downlink from 'jooby-codec/obis-observer/commands/downlink';
// to get one particular command
import * as getMeterInfo from 'jooby-codec/obis-observer/commands/downlink/getMeterInfo.js';
The last approach is preferred as it is more efficient and will initialize only the necessary commands.
Prepare and parse downlink message:
import * as message from 'jooby-codec/obis-observer/message/downlink';
import * as downlinkCommands from 'jooby-codec/obis-observer/commands/downlink';
import getHexFromBytes from 'jooby-codec/utils/getHexFromBytes.js';
const commands = [
{
id: downlinkCommands.getMeterProfile.id,
parameters: {requestId: 3, meterProfileId: 4}
}
];
const bytes = message.toBytes(commands);
console.log('message encoded:', JSON.stringify(bytes));
// output:
[102,2,3,4]
console.log('message encoded in HEX:', getHexFromBytes(bytes));
// output:
'66 02 03 04'
// decode message back from bytes
const parsedMessage = message.fromBytes(bytes);
console.log('parsedMessage:', parsedMessage);
// output:
{
commands: [
{
id: 102,
name: 'getMeterProfile',
headerSize: 2,
bytes: [Array],
parameters: [Object]
}
],
bytes: [102, 2, 3, 4]
}
Parse uplink message:
import * as message from 'jooby-codec/obis-observer/message/uplink';
// binary data received from a device
const bytes = [0x10, 0x0d, 0x02, 0x00, 0x00, 0x00, 0x51, 0x2c, 0x2d, 0xea, 0xae, 0x2c, 0x2f, 0x0a, 0xf6];
// decode it
const payload = message.fromBytes(bytes);
if ( 'error' in payload ) {
console.log('decode failure:', payload.error, payload.message);
} else {
console.log('message decoded:', payload.commands);
// output:
[
{
id: 16,
name: 'getArchiveState',
headerSize: 2,
bytes: [
16, 13, 2, 0, 0, 0, 81, 44, 45, 234, 174, 44, 47, 10, 246
],
parameters: {
requestId: 2,
archiveRecordsNumber: 81,
eldestTime2000: 741206702,
newestTime2000: 741280502
}
}
]
}