├── src ├── decoders │ ├── GlassBreak.ts │ ├── DoorWindow.ts │ ├── TankLevelSensor.ts │ ├── AccelerationMovementSensor.ts │ ├── ContactSensor.ts │ ├── SinglePushButton.ts │ ├── WaterSensor.ts │ ├── AmbientLightSensor.ts │ ├── Temperature_Event.ts │ ├── TiltSensor.ts │ ├── CompassSensor.ts │ ├── HoneywellFiveEightZeroZero.ts │ ├── UltraSonicSensor.ts │ ├── CurrentLoopSensor.ts │ ├── VoltageSensor.ts │ ├── MagnetometerSensor.ts │ ├── GpsDevice.ts │ ├── HighPrecisionTiltSensor.ts │ ├── ThermocoupleTemperatureSensor.ts │ ├── HighFreqVibrationSensor │ │ ├── CondensedFftEnergy.ts │ │ ├── CondensedFftCommons.ts │ │ └── CondensedFFt.ts │ ├── HBVibrationSensor.ts │ ├── AirTemperatureAndHumiditySensor.ts │ ├── Common_Events.ts │ └── WeatherStationSensor.ts ├── types │ ├── index.ts │ └── EventTypes.ts ├── lib │ ├── utils.ts │ ├── CommonDecodings.ts │ ├── HexConvertor.ts │ └── IdentifyEventType.ts ├── index.ts └── parser.ts ├── docs ├── .nojekyll └── README.md ├── .babelrc.json ├── typedoc.json ├── .prettierignore ├── .eslintignore ├── .editorconfig ├── .gitattributes ├── prettier.config.js ├── tsconfig.bundle.json ├── tests ├── ContactSensor.test.ts ├── TankLevelSensor.test.ts ├── DoorWindow.test.ts ├── AmbientLightSensor.test.ts ├── AccelerationMovementSensor.test.ts ├── SinglePushButton.test.ts ├── VoltageSensor.test.ts ├── TiltSensor.test.ts ├── CurrentLoopSensor.test.ts ├── HoneywellFiveEightZeroZero.test.ts ├── UltraSonicSensor.test.ts ├── MinorTests.test.ts ├── CompassSensor.test.ts ├── GpsDevice.test.ts ├── ThermocoupleTemperatureSensor.test.ts ├── WaterSensor.test.ts ├── HighPrecisionTiltSensor.test.ts ├── MagnetometerSensor.test.ts ├── HBVibrationSensor.test.ts ├── CondensedFFt.test.ts ├── AirTemperatureAndHumiditySensor.test.ts ├── CommonEvents.test.ts └── WeatherStationSensor.test.ts ├── .eslintrc.js ├── DecoderDocs ├── ContactSensor.md ├── DoorWindow.md ├── TankLevelSensor.md ├── GlassBreak.md ├── AmbientLightSensor.md ├── AccelerationMovementSensor.md ├── WaterSensor.md ├── VoltageSensor.md ├── TiltSensor.md ├── UltraSonicSensor.md ├── CurrentLoopSensor.md ├── HighPrecisionTiltSensor.md ├── CompassSensor.md ├── GpsDevice.md ├── ThermocoupleTemperatureSensor.md ├── HoneywellFiveEightZeroZero.md ├── Readme.md ├── MagnetometerSensor.md ├── HBVibrationSensor.md ├── AirTemperatureAndHumiditySensor.md ├── CondensedFFt.md └── CommonEvents.md ├── jest.config.js ├── .gitignore ├── LICENSE ├── tsconfig.json ├── README.md ├── text.txt ├── rollup.config.mjs ├── package.json └── setup.js /src/decoders/GlassBreak.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default function () { 3 | return { event: 'Glass Break' }; 4 | } 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": ["external-helpers"] 11 | } 12 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "./docs", 3 | "name": "Radio Bridge Packet Decoder", 4 | "plugin": ["typedoc-plugin-markdown"], 5 | "readme": "none", 6 | "tsconfig": "./tsconfig.bundle.json" 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Unlinted files and folders. 2 | # References at https://prettier.io/docs/en/ignore.html#ignoring-files. 3 | 4 | # Generated docs, bundles and type definitions. 5 | docs/ 6 | dist/ 7 | types/ 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Unlinted files and folders. 2 | # References at https://eslint.org/docs/user-guide/configuring#eslintignore 3 | 4 | # Generated docs, bundles and type definitions. 5 | docs/ 6 | dist/ 7 | types/ 8 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface DecodedPayload { 2 | [key: string]: never; 3 | } 4 | 5 | export type HexDecimal = { 6 | hex: string; 7 | decimal: number; 8 | binary: string; 9 | ascii: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | export const base64decode = (str: string): string => 4 | Buffer.from(str, 'base64').toString('binary'); 5 | export const base64encode = (str: string): string => 6 | Buffer.from(str, 'binary').toString('base64'); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Settings for editors and IDEs. 2 | # References at https://editorconfig.org/. 3 | 4 | root = true 5 | 6 | # Settings for any file. 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # File attributes for Git repository. 2 | # References at https://git-scm.com/docs/gitattributes. 3 | 4 | # Handle files as text and ensure Unix line endings. 5 | * text=auto eol=lf 6 | 7 | # Ignore differences on NPM's lockfile. 8 | # Since version 5.7.0, NPM automatically handles merge conflicts. 9 | package-lock.json -diff 10 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /* eslint-env node */ 3 | 4 | /** 5 | * An object with Prettier.js options. 6 | * @type {import('prettier').Options} 7 | */ 8 | const options = { 9 | bracketSameLine: true, 10 | quoteProps: 'consistent', 11 | singleQuote: true, 12 | trailingComma: 'all', 13 | }; 14 | 15 | module.exports = options; 16 | -------------------------------------------------------------------------------- /src/lib/CommonDecodings.ts: -------------------------------------------------------------------------------- 1 | import { hexToBinaryMessageDecoder } from './HexConvertor'; 2 | import { HexDecimal } from '../types'; 3 | 4 | export function binaryStateDecode( 5 | hexData: HexDecimal, 6 | bitMsgs: { [key: number]: string | undefined; nobit?: string }, 7 | prefix: string = '', 8 | ) { 9 | const dataMessagePayload = hexToBinaryMessageDecoder(hexData['hex'], bitMsgs); 10 | 11 | return prefix + dataMessagePayload; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | // Transpilation settings. 5 | "module": "ES2022", 6 | "target": "ES5", 7 | 8 | // Output settings. 9 | "newLine": "LF", 10 | "sourceMap": true, 11 | 12 | // Type declaration settings. 13 | "declaration": true, 14 | "declarationDir": "types/", 15 | "declarationMap": true 16 | }, 17 | "exclude": ["./src/**/*.test.ts", "./src/**/*.test.tsx"] 18 | } 19 | -------------------------------------------------------------------------------- /src/decoders/DoorWindow.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { binaryStateDecode } from '../lib/CommonDecodings'; 5 | export default function (hexDecimal: HexDecimal[]) { 6 | const dataMessage = {}; 7 | if (hexDecimal.length >= 1) { 8 | const byteZeroHex = hexDecimal[1]; 9 | const bitMsgs = { 10 | 0: 'Opened', 11 | nobit: 'Closed', 12 | }; 13 | dataMessage['event'] = binaryStateDecode(byteZeroHex, bitMsgs); 14 | } 15 | 16 | return dataMessage; 17 | } 18 | -------------------------------------------------------------------------------- /src/decoders/TankLevelSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length >= 1) { 9 | const byteZeroHex = hexDecimal[1]; 10 | const bitMsgs = ['Tank full', 'Tank empty']; 11 | dataMessage['event'] = hexToDecimalMessageDecoder( 12 | byteZeroHex['decimal'], 13 | bitMsgs, 14 | ); 15 | } 16 | 17 | return dataMessage; 18 | } 19 | -------------------------------------------------------------------------------- /src/decoders/AccelerationMovementSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length >= 1) { 9 | const byteZeroHex = hexDecimal[1]; 10 | const bitMsgs = ['Acceleration started', 'Acceleration stopped']; 11 | dataMessage['event'] = hexToDecimalMessageDecoder( 12 | byteZeroHex['decimal'], 13 | bitMsgs, 14 | ); 15 | } 16 | 17 | return dataMessage; 18 | } 19 | -------------------------------------------------------------------------------- /src/decoders/ContactSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage: { [key: string]: string | object } = {}; 8 | if (hexDecimal.length >= 1) { 9 | const byteZeroHex = hexDecimal[1]; 10 | const bitMsgs = ['Shorted', 'Opened', 'Shorted', 'Opened']; 11 | dataMessage['event'] = hexToDecimalMessageDecoder( 12 | byteZeroHex['decimal'], 13 | bitMsgs, 14 | ); 15 | } 16 | 17 | return dataMessage; 18 | } 19 | -------------------------------------------------------------------------------- /tests/ContactSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { CONTACT_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | ContactSensorEvent', () => { 5 | it.each([ 6 | ['opened', '180701', 'Opened'], 7 | ['shorted', '170700', 'Shorted'], 8 | ])('decodes a contact %s event', (description, payload, expectedState) => { 9 | const decodedData = decode(payload); 10 | const expectedOutput = {}; 11 | expectedOutput[CONTACT_SENSOR] = { 12 | event: expectedState, 13 | }; 14 | expect(decodedData).toMatchObject(expectedOutput); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/TankLevelSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { TANK_LEVEL_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | TankLevelSensor', () => { 5 | it.each([ 6 | ['opened', '180C01', 'Tank empty'], 7 | ['shorted', '170C00', 'Tank full'], 8 | ])('decodes a contact %s event', (description, payload, expectedState) => { 9 | const decodedData = decode(payload); 10 | const expectedOutput = {}; 11 | expectedOutput[TANK_LEVEL_SENSOR] = { 12 | event: expectedState, 13 | }; 14 | expect(decodedData).toMatchObject(expectedOutput); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/decoders/SinglePushButton.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length >= 2) { 9 | const pushByte = hexDecimal[2]['decimal']; 10 | const bitMsgs = { 11 | 0: 'Button Pressed', 12 | 1: 'Button Released', 13 | 2: 'Button Held', 14 | 3: 'Button Released After Hold', 15 | }; 16 | dataMessage['event'] = hexToDecimalMessageDecoder(pushByte, bitMsgs); 17 | } 18 | 19 | return dataMessage; 20 | } 21 | -------------------------------------------------------------------------------- /tests/DoorWindow.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { DOOR_WINDOW_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | DoorWindowEvent', () => { 5 | it.each([ 6 | ['close', '170300', 'Closed'], 7 | ['open', '160301', 'Opened'], 8 | ])( 9 | 'decodes a door window %s event', 10 | (description, payload: string, expectedState) => { 11 | const decodedData = decode(payload); 12 | const expectedOutput = {}; 13 | expectedOutput[DOOR_WINDOW_SENSOR] = { 14 | event: expectedState, 15 | }; 16 | expect(decodedData).toMatchObject(expectedOutput); 17 | }, 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /* eslint-env node */ 3 | 4 | 'use strict'; 5 | 6 | /** 7 | * An object with ESLint options. 8 | * @type {import('eslint').Linter.Config} 9 | */ 10 | const options = { 11 | root: true, 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | ecmaVersion: 2021, 18 | }, 19 | plugins: ['@typescript-eslint'], 20 | extends: [ 21 | 'eslint:recommended', 22 | 'plugin:prettier/recommended', 23 | 'plugin:@typescript-eslint/eslint-recommended', 24 | 'plugin:@typescript-eslint/recommended', 25 | ], 26 | }; 27 | 28 | module.exports = options; 29 | -------------------------------------------------------------------------------- /DecoderDocs/ContactSensor.md: -------------------------------------------------------------------------------- 1 | # Contact Sensors 2 | 3 | The payload decoder function is applicable to contact sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the last received state(shorted, opened) of contact | 11 | 12 | ## Example 13 | 14 | ```json 15 | // 170700 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "CONTACT_SENSOR", 20 | CONTACT_SENSOR: { 21 | event: 'Shorted', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /DecoderDocs/DoorWindow.md: -------------------------------------------------------------------------------- 1 | # Door/Window Sensors 2 | 3 | The payload decoder function is applicable to door/window sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Indicates the current door state (either opened/closed) | 11 | 12 | ## Example 13 | 14 | ```json 15 | // 160301 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "DOOR_WINDOW_SENSOR", 20 | DOOR_WINDOW_SENSOR: { 21 | event: 'Opened', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /tests/AmbientLightSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { AMBIENT_LIGHT_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | AmbientLightSensor', () => { 5 | it.each([['3', '160b060301', null, 3]])( 6 | 'decodes a ambient light measurement: %s', 7 | (description, payload: string, expectedState, expectedMeasurement) => { 8 | const decodedData = decode(payload); 9 | const expectedOutput = {}; 10 | expectedOutput[AMBIENT_LIGHT_SENSOR] = { 11 | event: expectedState, 12 | lightMeasurement: expectedMeasurement, 13 | }; 14 | expect(decodedData).toMatchObject(expectedOutput); 15 | }, 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /DecoderDocs/TankLevelSensor.md: -------------------------------------------------------------------------------- 1 | # Tank Level Sensors 2 | 3 | The payload decoder function is applicable to tank level sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Indicates the tank state (either full or empty) | 11 | 12 | ## Example 13 | 14 | ```json 15 | // 180C01 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "TANK_LEVEL_SENSOR", 20 | TANK_LEVEL_SENSOR: { 21 | event: 'Tank empty', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /DecoderDocs/GlassBreak.md: -------------------------------------------------------------------------------- 1 | # Glass Break Sensors 2 | 3 | The payload decoder function is applicable to glass break sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the last received description of event | 11 | 12 | ## Example 13 | 14 | ```json 15 | // Glass Break 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "GLASS_BREAK_EVENT", 20 | GLASS_BREAK_EVENT: { 21 | event: 'Glass Break', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /src/decoders/WaterSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length >= 1) { 9 | const byteZeroHex = hexDecimal[1]; 10 | const byteOneHex = hexDecimal[2]; 11 | const bitMsgs = ['Water or liquid present', 'Water or liquid not present']; 12 | dataMessage['event'] = hexToDecimalMessageDecoder( 13 | byteZeroHex['decimal'], 14 | bitMsgs, 15 | ); 16 | 17 | dataMessage['relativeMeasurement'] = byteOneHex['decimal']; 18 | } 19 | 20 | return dataMessage; 21 | } 22 | -------------------------------------------------------------------------------- /DecoderDocs/AmbientLightSensor.md: -------------------------------------------------------------------------------- 1 | # Ambient Light Sensors 2 | 3 | The payload decoder function is applicable to ambient light sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | lightMeasurement | String | Holds the last received relative temperature value | 11 | 12 | ## Example 13 | 14 | ```json 15 | // 160b060301 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "AMBIENT_LIGHT_SENSOR", 20 | AMBIENT_LIGHT_SENSOR: { 21 | lightMeasurement: '3', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /tests/AccelerationMovementSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { ACCELERATION_MOVEMENT_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | AccelerationMovementSensor', () => { 5 | it.each([ 6 | ['stopped', '110e01', 'Acceleration stopped'], 7 | ['started', '100e00', 'Acceleration started'], 8 | ])( 9 | 'decodes an Acceleration %s event', 10 | (description, payload, expectedState) => { 11 | const decodedData = decode(payload); 12 | const expectedOutput = {}; 13 | expectedOutput[ACCELERATION_MOVEMENT_SENSOR] = { 14 | event: expectedState, 15 | }; 16 | expect(decodedData).toMatchObject(expectedOutput); 17 | }, 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/SinglePushButton.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { PUSH_BUTTON } from '../src/types/EventTypes'; 3 | 4 | describe('unit | SinglePushButton', () => { 5 | it.each([ 6 | ['released', '14060301', 'Button Released'], 7 | ['held', '13060302', 'Button Held'], 8 | ['pressed', '13060300', 'Button Pressed'], 9 | ])( 10 | 'decodes a single push button %s event', 11 | (description, payload: string, expectedState) => { 12 | const decodedData = decode(payload); 13 | const expectedOutput = {}; 14 | expectedOutput[PUSH_BUTTON] = { 15 | event: expectedState, 16 | }; 17 | expect(decodedData).toMatchObject(expectedOutput); 18 | }, 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /* eslint-env node */ 3 | 4 | /** 5 | * An object with Jest options. 6 | * @type {import('ts-jest').JestConfigWithTsJest} 7 | */ 8 | const options = { 9 | roots: ['/tests'], 10 | testMatch: [ 11 | '**/__tests__/**/*.+(ts|tsx|js)', 12 | '**/?(*.)+(spec|test).+(ts|tsx|js)', 13 | ], 14 | transform: { 15 | '^.+\\.tsx?$': [ 16 | 'ts-jest', 17 | { 18 | isolatedModules: true, 19 | useESM: true, 20 | }, 21 | ], 22 | }, 23 | resolver: 'ts-jest-resolver', 24 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], 25 | moduleNameMapper: { 26 | '^~(.*)$': '/src/$1', 27 | }, 28 | }; 29 | 30 | module.exports = options; 31 | -------------------------------------------------------------------------------- /DecoderDocs/AccelerationMovementSensor.md: -------------------------------------------------------------------------------- 1 | # Acceleration Movement Sensors 2 | 3 | The payload decoder function is applicable to acceleration movement sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the last received description of event | 11 | 12 | ## Example 13 | 14 | ```json 15 | // 110e01 16 | { 17 | protocol: ProtocolVersion, 18 | counter: PacketCounter, 19 | type: "ACCELERATION_MOVEMENT_SENSOR", 20 | ACCELERATION_MOVEMENT_SENSOR: { 21 | event: 'Acceleration stopped', 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Unversioned files and folders. 2 | # References at https://git-scm.com/docs/gitignore. 3 | 4 | # Finder's configuration files (Mac). 5 | .DS_Store 6 | 7 | # Node.js modules. 8 | node_modules/ 9 | 10 | # NPM's shrinkwrap. 11 | # We're using `package-lock.json` instead. 12 | npm-shrinkwrap.json 13 | 14 | # Yarn's lockfile, Plug'n'Play, folder and settings. 15 | # We're using npm, and it provides its lockfile and settings. 16 | yarn.lock 17 | .pnp.* 18 | .yarn/ 19 | .yarnrc 20 | .yarnrc.yaml 21 | 22 | # pnpm's lockfile, workspace definitions and hooks. 23 | # We're using npm, and it provides its lockfile and settings. 24 | pnpm-lock.yaml 25 | pnpm-workspace.yaml 26 | pnpmfile.js 27 | 28 | # Log files. 29 | *.log 30 | *.log.* 31 | /.github/ 32 | /.idea/ 33 | -------------------------------------------------------------------------------- /DecoderDocs/WaterSensor.md: -------------------------------------------------------------------------------- 1 | # Water Sensors 2 | 3 | The payload decoder function is applicable to water sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | -------------------- | ------- | ----------------------------------------------------------- | 10 | | event | String | Holds the last received description of event | 11 | | relativeMeasurement | String | Holds the last received temperature value with unit | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 16080115 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "WATER_SENSOR", 21 | WATER_SENSOR: { 22 | event: 'Water or liquid not present', 23 | relativeMeasurement: 21, 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /DecoderDocs/VoltageSensor.md: -------------------------------------------------------------------------------- 1 | # Voltage Sensors 2 | 3 | The payload decoder function is applicable to voltage sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | -------------------- | ------- | ----------------------------------------------------------- | 10 | | event | String | Holds the last received description of event | 11 | | voltage | String | Holds the last received voltage measurement value with unit | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 191404059e 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "VOLTAGE_SENSOR", 21 | VOLTAGE_SENSOR: { 22 | event: 'Report on change decrease', 23 | voltage: '14.38 V', 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /src/decoders/AmbientLightSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length >= 2) { 9 | const byteZero = hexDecimal[1]['decimal']; 10 | const byteOne = hexDecimal[2]['decimal']; 11 | const bitMsgs = [ 12 | 'Periodic Report', 13 | 'Ambient light has risen above upper threshold', 14 | 'Ambient light has fallen below lower threshold', 15 | 'Report on change increase', 16 | 'Report on change decrease', 17 | ]; 18 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZero, bitMsgs); 19 | dataMessage['lightMeasurement'] = byteOne; 20 | } 21 | 22 | return dataMessage; 23 | } 24 | -------------------------------------------------------------------------------- /DecoderDocs/TiltSensor.md: -------------------------------------------------------------------------------- 1 | # Tilt Sensors 2 | 3 | The payload decoder function is applicable to tilt sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | ------------------------ | ------- | --------------------------------------------------------- | 10 | | event | String | -- | 11 | | tilt_angle_vertical_axis | String | Holds the Tilt angle from vertical axis | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 150a0219 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "TILT_EVENT", 21 | TILT_EVENT: { 22 | event: 'Report on change toward vertical', 23 | tilt_angle_vertical_axis: '25 Degrees', 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /DecoderDocs/UltraSonicSensor.md: -------------------------------------------------------------------------------- 1 | # Ultrasonic Level Sensor 2 | 3 | The payload decoder function is applicable to ultrasonic level sensor. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | -------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the last received description of event | 11 | | sensor_state | String | Holds the last received temperature value with unit | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 121001270f 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "ULTRASONIC_LEVEL_SENSOR", 21 | ULTRASONIC_LEVEL_SENSOR: { 22 | event: 'Distance has risen above upper threshold', 23 | sensor_state: 9999, 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /src/decoders/Temperature_Event.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { HexDecimal } from '../types'; 3 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 4 | 5 | export default function (hexDecimal: HexDecimal[]) { 6 | const dataMessage: string | never[] = []; 7 | if (hexDecimal.length > 3) { 8 | const byteZeroHex = hexDecimal[1]['decimal']; 9 | 10 | // Current temperature in degrees Celsius 11 | const bitMsgs = [ 12 | 'Periodic Report', 13 | 'Temperature has risen above upper threshold', 14 | 'Temperature has fallen below lower threshold', 15 | 'Report on change increase', 16 | 'Report on change decrease', 17 | ]; 18 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroHex, bitMsgs); 19 | dataMessage['current_temperature'] = { value: dataMessage, unit: 'C' }; 20 | } 21 | 22 | return dataMessage; 23 | } 24 | -------------------------------------------------------------------------------- /tests/VoltageSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { VOLTAGE_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | VoltageSensor', () => { 5 | it.each([ 6 | [ 7 | 'VOLTAGE_SENSOR', 8 | '1214020006', 9 | 'Analog measurement has fallen below lower threshold', 10 | 0.06, 11 | ], 12 | ['VOLTAGE_SENSOR', '191404059e', 'Report on change decrease', 14.38], 13 | ])( 14 | 'decodes a voltageSensor %s event', 15 | (description, payload: string, expectedState, sensorState) => { 16 | const decodedData = decode(payload); 17 | const expectedOutput = {}; 18 | expectedOutput[VOLTAGE_SENSOR] = { 19 | event: expectedState, 20 | voltage: { value: sensorState, unit: 'V' }, 21 | }; 22 | expect(decodedData).toMatchObject(expectedOutput); 23 | }, 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /DecoderDocs/CurrentLoopSensor.md: -------------------------------------------------------------------------------- 1 | # 4-20mA Current Loop Sensors 2 | 3 | The payload decoder function is applicable to 4-20mA current loop sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the Current measurement value with unit | 11 | | sensor_state | Float | Indicates the latest sensor state | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 181100ffff 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "WIRELESS_420MA_CURRENT_LOOP_SENSOR", 21 | WIRELESS_420MA_CURRENT_LOOP_SENSOR: { 22 | event: 'Periodic Report', 23 | sensor_state: 655.35, 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /tests/TiltSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { TILT_EVENT } from '../src/types/EventTypes'; 3 | 4 | describe('unit | TiltSensor', () => { 5 | it.each([ 6 | ['1b0a0238', 'Report on change toward vertical', 56], 7 | ['160a034a', 'Report on change toward horizontal', 74], 8 | ['150a0219', 'Report on change toward vertical', 25], 9 | ])( 10 | 'decodes tilt sensor payload %s, event: %s, Degrees', 11 | (payload: string, eventState, tiltAngleVerticalAxis) => { 12 | const decodedData = decode(payload); 13 | const expectedOutput = {}; 14 | expectedOutput[TILT_EVENT] = { 15 | event: eventState, 16 | tilt_angle_vertical_axis: { 17 | value: tiltAngleVerticalAxis, 18 | unit: 'Degrees', 19 | }, 20 | }; 21 | expect(decodedData).toMatchObject(expectedOutput); 22 | }, 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /DecoderDocs/HighPrecisionTiltSensor.md: -------------------------------------------------------------------------------- 1 | # High Precision Tilt Sensor 2 | 3 | The payload decoder function is applicable to high precision tilt sensor. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | ------------------------ | ------- | --------------------------------------------------------- | 10 | | temperature | String | Holds the last received temperature value with unit | 11 | | tilt_angle_vertical_axis | String | Holds the Tilt angle from vertical axis with unit | 12 | 13 | ## Example 14 | 15 | ```json 16 | // 1a0f0059030c 17 | { 18 | protocol: ProtocolVersion, 19 | counter: PacketCounter, 20 | type: "HIGH_PRECISION_TILT_SENSOR", 21 | HIGH_PRECISION_TILT_SENSOR: { 22 | temperature: '12 C', 23 | tilt_angle_vertical_axis: '89.3 Degrees', 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /tests/CurrentLoopSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { WIRELESS_420MA_CURRENT_LOOP_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | CurrentLoopSensor', () => { 5 | it.each([ 6 | [ 7 | 'with 0.01 mA', 8 | '1211020001', 9 | 'Analog measurement has fallen below lower threshold', 10 | 0.01, 11 | ], 12 | ['with 0.01 mA', '181100ffff', 'Periodic Report', 655.35], 13 | ])( 14 | 'decodes current loop sensor: %s', 15 | (description, payload: string, eventState, sensorState) => { 16 | const decodedData = decode(payload); 17 | const expectedOutput = {}; 18 | expectedOutput[WIRELESS_420MA_CURRENT_LOOP_SENSOR] = { 19 | event: eventState, 20 | sensor_state: { unit: 'mA', value: sensorState }, 21 | }; 22 | expect(decodedData).toMatchObject(expectedOutput); 23 | }, 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /DecoderDocs/CompassSensor.md: -------------------------------------------------------------------------------- 1 | # Compass Sensors 2 | 3 | The payload decoder function is applicable to compass sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | Holds the last received temperature value with unit | 11 | | angle | String | Holds the last received angle value with unit | 12 | | temperature | String | Holds the last received temperature value with unit | 13 | 14 | ## Example 15 | 16 | ```json 17 | // 171A00200ABA 18 | { 19 | protocol: ProtocolVersion, 20 | counter: PacketCounter, 21 | type: "COMPASS_SENSOR", 22 | COMPASS_SENSOR: { 23 | event: 'Periodic report', 24 | angle: '8202', 25 | temperature: '186', 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /src/decoders/TiltSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage: { [key: string]: string | object } = {}; 8 | if (hexDecimal.length >= 2 && 1 in hexDecimal) { 9 | const byteHex = hexDecimal[1].decimal; 10 | const bitMsgs: string[] = [ 11 | 'Sensor transitioned to vertical', 12 | 'Sensor transitioned to horizontal', 13 | 'Report on change toward vertical', 14 | 'Report on change toward horizontal', 15 | ]; 16 | dataMessage['event'] = hexToDecimalMessageDecoder(byteHex, bitMsgs); 17 | } 18 | 19 | if (hexDecimal.length >= 3 && 2 in hexDecimal) { 20 | dataMessage['tilt_angle_vertical_axis'] = { 21 | value: hexDecimal[2].decimal, 22 | unit: 'Degrees', 23 | }; 24 | } 25 | 26 | return dataMessage; 27 | } 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A Branded Type for values parseable to number. 3 | */ 4 | import { decode } from './parser'; 5 | // import { DecodedPayload } from './types'; 6 | import { base64decode } from './lib/utils'; 7 | import { binaryToHex } from './lib/HexConvertor'; 8 | 9 | export const decodePayload = (...args: string[]) => { 10 | return decode(...args); 11 | }; 12 | 13 | export const decodeTtnPayload = (receivedPayload: string) => { 14 | return decodePayload(binaryToHex(base64decode(receivedPayload))); 15 | }; 16 | 17 | export const decodeChirpstackPayload = (receivedPayload: string) => { 18 | return decodePayload(binaryToHex(base64decode(receivedPayload))); 19 | }; 20 | 21 | export const decodeAwsIotPayload = (receivedPayload: string) => { 22 | return decodePayload(binaryToHex(base64decode(receivedPayload))); 23 | }; 24 | 25 | export const decodeKerlinkPayload = (receivedPayload: string) => { 26 | return decodePayload(binaryToHex(base64decode(receivedPayload))); 27 | }; 28 | -------------------------------------------------------------------------------- /DecoderDocs/GpsDevice.md: -------------------------------------------------------------------------------- 1 | # Gps Device Sensor - GPS Config 2 | 3 | The payload decoder function is applicable to gps device sensor - gps config. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | -- | 11 | | latitude | String | Holds the last received latitude value | 12 | | longitude | String | Holds the last received longitude value | 13 | 14 | ## Example 15 | 16 | ```json 17 | // f01671653e3d963d1512cc 18 | { 19 | protocol: ProtocolVersion, 20 | counter: PacketCounter, 21 | type: "GPS_SENSOR", 22 | GPS_SENSOR: { 23 | event: 'Valid GPS Fix', 24 | latitude: '169.8577814000', 25 | longitude: '102.47912440000', 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /tests/HoneywellFiveEightZeroZero.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { HONEYWELL_5800 } from '../src/types/EventTypes'; 3 | 4 | describe('unit | HoneywellFiveEightZeroZero', () => { 5 | it.each([ 6 | [ 7 | 'HONEYWELL_5800', 8 | '6417ff331004b4034fea53', 9 | 3346436, 10 | '0x331004', 11 | 46083, 12 | '0xb403', 13 | ], 14 | ])( 15 | 'decodes a honeywell5800 %s event', 16 | ( 17 | description, 18 | payload: string, 19 | decimalId, 20 | hexId, 21 | sensorPayload, 22 | sensorPayloadHex, 23 | ) => { 24 | const decodedData = decode(payload); 25 | const expectedOutput = {}; 26 | expectedOutput[HONEYWELL_5800] = { 27 | decimal_id: decimalId, 28 | hex_id: hexId, 29 | sensor_payload: sensorPayload, 30 | sensor_payload_hex: sensorPayloadHex, 31 | }; 32 | expect(decodedData).toMatchObject(expectedOutput); 33 | }, 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /DecoderDocs/ThermocoupleTemperatureSensor.md: -------------------------------------------------------------------------------- 1 | # Thermocouple Temperature Sensors 2 | 3 | The payload decoder function is applicable to thermocouple temperature sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | event | String | -- | 11 | | faults | String | Holds the last received faults value | 12 | | current_temp | String | Holds the last received temperature value with unit | 13 | 14 | ## Example 15 | 16 | ```json 17 | // 111300015f00 18 | { 19 | protocol: ProtocolVersion, 20 | counter: PacketCounter, 21 | type: "THERMOCOUPLE_TEMP", 22 | THERMOCOUPLE_TEMP: { 23 | event: 'Periodic Report', 24 | faults: 'No Faults', 25 | current_temp: 21.94, 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /tests/UltraSonicSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { ULTRASONIC_LEVEL_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | UltraSonicLevelSensor', () => { 5 | it.each([ 6 | ['13100401f4', 'Report on change decrease', 500], 7 | ['14100306e7', 'Report on change increase', 1767], 8 | ['1510040411', 'Report on change decrease', 1041], 9 | ['1210020247', 'Distance has fallen below lower threshold', 583], 10 | ['121001270f', 'Distance has risen above upper threshold', 9999], 11 | ])( 12 | 'decodes ultra sonic sensor: %s, event: %s, distance: %s', 13 | (payload: string, eventState, expectedState) => { 14 | const decodedData = decode(payload); 15 | const expectedOutput = {}; 16 | expectedOutput[ULTRASONIC_LEVEL_SENSOR] = { 17 | event: eventState, 18 | sensor_state: { value: expectedState, unit: 'mm' }, 19 | }; 20 | expect(decodedData).toMatchObject(expectedOutput); 21 | }, 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /src/decoders/CompassSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { HexDecimal } from '../types'; 3 | import { hexToDecimal, hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 4 | 5 | export default function (hexDecimal: HexDecimal[]) { 6 | const dataMessage = {}; 7 | if (hexDecimal.length >= 1) { 8 | const byteZeroHex = hexDecimal[1]; 9 | const bitMsgs = [ 10 | 'Periodic report', 11 | 'Angle rose above upper threshold', 12 | 'Angle fell below lower threshold', 13 | 'Angle report-on-change angle increase', 14 | 'Angle report-on-change angle decrease', 15 | ]; 16 | dataMessage['event'] = hexToDecimalMessageDecoder( 17 | byteZeroHex['decimal'], 18 | bitMsgs, 19 | ); 20 | } 21 | 22 | if (2 in hexDecimal && 3 in hexDecimal) { 23 | const byteHex = hexDecimal[2]['hex'] + hexDecimal[3]['hex']; 24 | dataMessage['angle'] = hexToDecimal(byteHex); 25 | } 26 | 27 | if (4 in hexDecimal) { 28 | dataMessage['temperature'] = hexDecimal[4]['decimal']; 29 | } 30 | 31 | return dataMessage; 32 | } 33 | -------------------------------------------------------------------------------- /tests/MinorTests.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { GLASS_BREAK_EVENT } from '../src/types/EventTypes'; 3 | import { baseConvert } from '../src/lib/HexConvertor'; 4 | 5 | describe('unit | GlassBreak', () => { 6 | it('decodes a glass break event', () => { 7 | const payload = '980571653056983d1512cc'; 8 | const decodedData = decode(payload); 9 | const expectedOutput = {}; 10 | expectedOutput[GLASS_BREAK_EVENT] = { 11 | event: 'Glass Break', 12 | }; 13 | expect(decodedData).toMatchObject(expectedOutput); 14 | }); 15 | }); 16 | 17 | describe('unit | convertorTest', () => { 18 | it.each([ 19 | ['0C', '12', '1100'], 20 | ['0A', '10', '1010'], 21 | ['AA', '170', '10101010'], 22 | ])('convert hex %s to decimal %s and binary %s', (hex, decimal, binary) => { 23 | const decimalConversion = baseConvert(hex, 16, 10); 24 | const binaryConversion = baseConvert(hex, 16, 2); 25 | expect(decimalConversion).toEqual(decimal); 26 | expect(binaryConversion).toEqual(binary); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/decoders/HoneywellFiveEightZeroZero.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimal } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const data_message = {}; 8 | if (2 in hexDecimal && 3 in hexDecimal && 4 in hexDecimal) { 9 | data_message['hex_id'] = 10 | '0x' + 11 | hexDecimal[2]['hex'] + 12 | '' + 13 | hexDecimal[3]['hex'] + 14 | '' + 15 | hexDecimal[4]['hex']; 16 | data_message['decimal_id'] = hexToDecimal( 17 | hexDecimal[2]['hex'] + 18 | '' + 19 | hexDecimal[3]['hex'] + 20 | '' + 21 | hexDecimal[4]['hex'], 22 | ); 23 | } 24 | 25 | if (5 in hexDecimal) { 26 | const sixthPayload = 6 in hexDecimal ? hexDecimal[6]['hex'] : ''; 27 | data_message['sensor_payload'] = hexToDecimal( 28 | hexDecimal[5]['hex'] + '' + sixthPayload, 29 | ); 30 | data_message['sensor_payload_hex'] = 31 | '0x' + hexDecimal[5]['hex'] + '' + sixthPayload; 32 | } 33 | return data_message; 34 | } 35 | -------------------------------------------------------------------------------- /tests/CompassSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { COMPASS_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | CompassSensorEvent', () => { 5 | it.each([ 6 | [ 7 | 'Angle rose, angle: 524, temp: 8', 8 | '181A01020C08', 9 | 'Angle rose above upper threshold', 10 | 524, 11 | 8, 12 | ], 13 | [ 14 | 'Periodic report, angle: 8202, temp: 186', 15 | '171A00200ABA', 16 | 'Periodic report', 17 | 8202, 18 | 186, 19 | ], 20 | ])( 21 | 'decodes a compass sensor %s event', 22 | ( 23 | description, 24 | payload: string, 25 | expectedState, 26 | expectedAngle, 27 | expectedTemperature, 28 | ) => { 29 | const decodedData = decode(payload); 30 | const expectedOutput = {}; 31 | expectedOutput[COMPASS_SENSOR] = { 32 | event: expectedState, 33 | angle: expectedAngle, 34 | temperature: expectedTemperature, 35 | }; 36 | expect(decodedData).toMatchObject(expectedOutput); 37 | }, 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/GpsDevice.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { GPS_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | GpsDevice', () => { 5 | it.each([ 6 | [ 7 | '00167165bea3973d1512cc', 8 | 'Valid GPS Fix', 9 | '170.6992535000', 10 | '102.47912440000', 11 | ], 12 | [ 13 | '78167165445b963d1512cc', 14 | 'Valid GPS Fix', 15 | '169.8978710000', 16 | '102.47912440000', 17 | ], 18 | [ 19 | 'f01671653e3d963d1512cc', 20 | 'Valid GPS Fix', 21 | '169.8577814000', 22 | '102.47912440000', 23 | ], 24 | ])( 25 | 'decodes a payload: %s, gps status: %s with latitude: %s, longitude: %s event', 26 | (payload: string, gpsStatus, latitude, longitude) => { 27 | const decodedData = decode(payload); 28 | const expectedOutput = {}; 29 | expectedOutput[GPS_SENSOR] = { 30 | event: gpsStatus, 31 | latitude: latitude, 32 | longitude: longitude, 33 | }; 34 | expect(decodedData).toMatchObject(expectedOutput); 35 | }, 36 | ); 37 | }); 38 | -------------------------------------------------------------------------------- /src/decoders/UltraSonicSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { hexToDecimal, hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 4 | import type { HexDecimal } from '../types'; 5 | 6 | // Assuming sensorState is a boolean variable 7 | export default function (hexDecimal: HexDecimal[]) { 8 | const dataMessage: { [key: string]: string | object } = {}; 9 | if (hexDecimal.length > 3) { 10 | const byteZeroHex = hexDecimal[1]['decimal']; 11 | const distanceUpperByteHex = hexDecimal[2]['hex']; 12 | const distanceLowerByteHex = hexDecimal[3]['hex']; 13 | 14 | const currentDistance = hexToDecimal( 15 | distanceUpperByteHex + '' + distanceLowerByteHex, 16 | ); 17 | const bitMsgs = [ 18 | 'Periodic Report', 19 | 'Distance has risen above upper threshold', 20 | 'Distance has fallen below lower threshold', 21 | 'Report on change increase', 22 | 'Report on change decrease', 23 | ]; 24 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroHex, bitMsgs); 25 | dataMessage['sensor_state'] = { value: currentDistance, unit: 'mm' }; 26 | } 27 | return dataMessage; 28 | } 29 | -------------------------------------------------------------------------------- /DecoderDocs/HoneywellFiveEightZeroZero.md: -------------------------------------------------------------------------------- 1 | # Honeywell Five Eight Zero Zero 2 | 3 | The payload decoder function is applicable to honeywell five eight zero zero. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | ------------------------ | ------- | --------------------------------------------------------- | 10 | | decimal_id | String | -- | 11 | | hex_id | String | -- | 12 | | sensor_payload | String | -- | 13 | | sensor_payload_hex | String | -- | 14 | 15 | ## Example 16 | 17 | ```json 18 | // 6417ff331004b4034fea53 19 | { 20 | protocol: ProtocolVersion, 21 | counter: PacketCounter, 22 | type: "HONEYWELL_5800", 23 | HONEYWELL_5800: { 24 | decimal_id: '3346436', 25 | hex_id: '0x331004', 26 | sensor_payload: '46083', 27 | sensor_payload_hex: '0xb403', 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Radio Bridge Inc.] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/decoders/CurrentLoopSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { hexToDecimal, hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 4 | import type { HexDecimal } from '../types'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage: { [key: string]: string | object } = {}; 8 | if (hexDecimal.length > 3) { 9 | const byteZeroHex = hexDecimal[1].decimal; 10 | const distanceUpperByteHex = hexDecimal[2].hex; 11 | const distanceLowerByteHex = hexDecimal[3].hex; 12 | 13 | let currentMeasurement = hexToDecimal( 14 | distanceUpperByteHex + '' + distanceLowerByteHex, 15 | ); 16 | 17 | currentMeasurement = currentMeasurement / 100; 18 | const bitMsgs = [ 19 | 'Periodic Report', 20 | 'Analog measurement has risen above upper threshold', 21 | 'Analog measurement has fallen below lower threshold', 22 | 'Report on change increase', 23 | 'Report on change decrease', 24 | ]; 25 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroHex, bitMsgs); 26 | dataMessage['sensor_state'] = { value: currentMeasurement, unit: 'mA' }; 27 | } 28 | return dataMessage; 29 | } 30 | -------------------------------------------------------------------------------- /tests/ThermocoupleTemperatureSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { THERMOCOUPLE_TEMP } from '../src/types/EventTypes'; 3 | 4 | describe('unit | ThermocoupleTemperatureSensor', () => { 5 | it.each([ 6 | [ 7 | 'with 141300015d00', 8 | '141300015d00', 9 | 'Periodic Report', 10 | 21.81, 11 | 'No Faults', 12 | ], 13 | [ 14 | 'with 121300015e00', 15 | '121300015e00', 16 | 'Periodic Report', 17 | 21.88, 18 | 'No Faults', 19 | ], 20 | [ 21 | 'with 111300015f00', 22 | '111300015f00', 23 | 'Periodic Report', 24 | 21.94, 25 | 'No Faults', 26 | ], 27 | ])( 28 | 'decodes thermocouple temperature sensor: %s', 29 | (description, payload: string, eventState, currentTemp, faults) => { 30 | const decodedData = decode(payload); 31 | const expectedOutput = {}; 32 | expectedOutput[THERMOCOUPLE_TEMP] = { 33 | event: eventState, 34 | faults: faults, 35 | current_temp: { value: currentTemp, unit: 'C' }, 36 | }; 37 | expect(decodedData).toMatchObject(expectedOutput); 38 | }, 39 | ); 40 | }); 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // React.js JSX settings. 4 | "jsx": "react", 5 | "jsxFactory": "React.createElement", 6 | "jsxFragmentFactory": "React.Fragment", 7 | 8 | // Module settings. 9 | "allowSyntheticDefaultImports": true, 10 | "esModuleInterop": true, 11 | "module": "ES2022", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | 15 | // Strictness and quality settings. 16 | "alwaysStrict": true, 17 | "exactOptionalPropertyTypes": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noImplicitOverride": true, 21 | "noImplicitReturns": true, 22 | "noPropertyAccessFromIndexSignature": true, 23 | "noUncheckedIndexedAccess": true, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | "strict": true, 27 | "verbatimModuleSyntax": false, 28 | 29 | // Type-checking settings. 30 | "lib": ["ES2020", "DOM"], 31 | "skipDefaultLibCheck": true, 32 | "skipLibCheck": true, 33 | "types": ["jest"], 34 | "checkJs": false 35 | }, 36 | "include": ["./src/**/*.ts", "./src/**/*.tsx"] 37 | } 38 | -------------------------------------------------------------------------------- /DecoderDocs/Readme.md: -------------------------------------------------------------------------------- 1 | ## Sensor Decode Output Examples 2 | 1. [Common Events - Supervisory, Reset, Tamper etc](./CommonEvents.md) 3 | 2. [Acceleration Movement Sensor](./AccelerationMovementSensor.md) 4 | 3. [Air Temperature & Humidity Sensor](./AirTemperatureAndHumiditySensor.md) 5 | 4. [Ambient Light Sensor](./AmbientLightSensor.md) 6 | 5. [Compass](./CompassSensor.md) 7 | 6. [Contact Sensor](./ContactSensor.md) 8 | 7. [Current Loop Sensor](./CurrentLoopSensor.md) 9 | 8. [Door Window](./DoorWindow.md) 10 | 9. [Glass Break](./GlassBreak.md) 11 | 10. [GPS Device](./GpsDevice.md) 12 | 11. [High Frequency Vibration Sensor](./HBVibrationSensor.md) 13 | 12. [High Frequency Vibration Sensor - Condensed FFT](./CondensedFFt.md) 14 | 13. [High Precision Tilt Sensor](./HighPrecisionTiltSensor.md) 15 | 14. [Honeywell 5800](./HoneywellFiveEightZeroZero.md) 16 | 15. [Magnetometer](./MagnetometerSensor.md) 17 | 16. [Tank Level Sensor](./TankLevelSensor.md) 18 | 17. [Thermocouple Temperature Sensor](./ThermocoupleTemperatureSensor.md) 19 | 18. [Tilt Sensor](./TiltSensor.md) 20 | 19. [UltraSonic Sensor](./UltraSonicSensor.md) 21 | 20. [Voltage Sensor](./VoltageSensor.md) 22 | 21. [Water Sensor](./WaterSensor.md) 23 | -------------------------------------------------------------------------------- /DecoderDocs/MagnetometerSensor.md: -------------------------------------------------------------------------------- 1 | # Magnetometer Sensor 2 | 3 | The payload decoder function is applicable to magnetometer sensor. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | messageType | String | -- | 11 | | xaxis | String | Holds the last received x axis value in milliguss | 12 | | yaxis | String | Holds the last received y axis value in milliguss | 13 | | zaxis | String | Holds the last received z axis value in milliguss | 14 | | temperature | String | Holds the last received temperature value with unit | 15 | 16 | ## Example 17 | 18 | ```json 19 | // 6418feae100000022536 20 | { 21 | protocol: ProtocolVersion, 22 | counter: PacketCounter, 23 | type: "MAGNETOMETER_SENSOR", 24 | MAGNETOMETER_SENSOR: { 25 | messageType: null, 26 | xaxis: '-20976', 27 | yaxis: '0', 28 | zaxis: '549', 29 | temperature: '54', 30 | } 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /tests/WaterSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { WATER_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | WaterSensorEvent', () => { 5 | it.each([ 6 | [ 7 | 'water not present, measure: 5', 8 | '1a080105', 9 | 5, 10 | 'Water or liquid not present', 11 | ], 12 | ['water present, measure: 253', '190800fd', 253, 'Water or liquid present'], 13 | [ 14 | 'water not present, measure: 16', 15 | '18080110', 16 | 16, 17 | 'Water or liquid not present', 18 | ], 19 | [ 20 | 'water not present, measure: 21', 21 | '16080115', 22 | 21, 23 | 'Water or liquid not present', 24 | ], 25 | ])( 26 | 'decodes a water rope sensor: %s ', 27 | ( 28 | description, 29 | payload: string, 30 | expectedRelativeMeasurement, 31 | expectedWaterPresent, 32 | ) => { 33 | const decodedData = decode(payload); 34 | const expectedOutput = {}; 35 | expectedOutput[WATER_SENSOR] = { 36 | event: expectedWaterPresent, 37 | relativeMeasurement: expectedRelativeMeasurement, 38 | }; 39 | expect(decodedData).toMatchObject(expectedOutput); 40 | }, 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/HighPrecisionTiltSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { HIGH_PRECISION_TILT_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | HighPrecisionTiltSensor', () => { 5 | it.each([ 6 | ['with 120f00580814', '120f00580814', null, 20, 88.8], 7 | ['with 1c0f0059030c', '1c0f0059030c', null, 12, 89.3], 8 | ['with 180f00590109', '180f00590109', null, 9, 89.1], 9 | ['with 180f00590109', '1a0f0059030c', null, 12, 89.3], 10 | ])( 11 | 'decodes high precision tilt sensor: %s', 12 | ( 13 | description, 14 | payload: string, 15 | eventState, 16 | temperature, 17 | tiltAngleVerticalAxis, 18 | ) => { 19 | const decodedData = decode(payload); 20 | const expectedOutput = {}; 21 | expectedOutput[HIGH_PRECISION_TILT_SENSOR] = { 22 | temperature: { value: temperature, unit: 'C' }, 23 | tilt_angle_vertical_axis: { 24 | value: tiltAngleVerticalAxis, 25 | unit: 'Degrees', 26 | }, 27 | }; 28 | if ('event' in decodedData) { 29 | expectedOutput[HIGH_PRECISION_TILT_SENSOR].push('event', eventState); 30 | } 31 | expect(decodedData).toMatchObject(expectedOutput); 32 | }, 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/MagnetometerSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { MAGNETOMETER_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | MagnetometerSensor', () => { 5 | it.each([ 6 | [ 7 | 'with temperature 54', 8 | '6418feae100000022536', 9 | null, 10 | '54', 11 | -20976, 12 | '0', 13 | '549', 14 | ], 15 | ])( 16 | 'decodes magnetometer sensor: %s', 17 | (description, payload: string, messageType, temp, xaxis, yaxis, zaxis) => { 18 | const decodedData = decode(payload); 19 | const expectedOutput = {}; 20 | if ('messageType' in decodedData) { 21 | expectedOutput[MAGNETOMETER_SENSOR].messageType = messageType; 22 | } 23 | if ('xaxis' in decodedData) { 24 | expectedOutput[MAGNETOMETER_SENSOR].xaxis = xaxis; 25 | } 26 | if ('yaxis' in decodedData) { 27 | expectedOutput[MAGNETOMETER_SENSOR].yaxis = yaxis; 28 | } 29 | if ('zaxis' in decodedData) { 30 | expectedOutput[MAGNETOMETER_SENSOR].zaxis = zaxis; 31 | } 32 | if ('temperature' in decodedData) { 33 | expectedOutput[MAGNETOMETER_SENSOR].temperature = temp; 34 | } 35 | expect(decodedData).toMatchObject(expectedOutput); 36 | }, 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/decoders/VoltageSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { hexToDecimal, hexToDecimalMessageDecoder } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (hexDecimal.length > 3) { 9 | const byteZeroHex = hexDecimal[1]['decimal']; 10 | const distanceUpperByteHex = hexDecimal[2]['hex']; 11 | const distanceLowerByteHex = hexDecimal[3]['hex']; 12 | 13 | let currentMeasurement = hexToDecimal( 14 | distanceUpperByteHex + '' + distanceLowerByteHex, 15 | ); 16 | 17 | currentMeasurement = currentMeasurement / 100; 18 | const sensorState = false; 19 | if (sensorState) { 20 | dataMessage['voltage'] = { value: currentMeasurement, unit: 'V' }; 21 | } else { 22 | const bitMsgs = [ 23 | 'Periodic Report', 24 | 'Analog measurement has risen above upper threshold', 25 | 'Analog measurement has fallen below lower threshold', 26 | 'Report on change increase', 27 | 'Report on change decrease', 28 | ]; 29 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroHex, bitMsgs); 30 | dataMessage['voltage'] = { value: currentMeasurement, unit: 'V' }; 31 | } 32 | } 33 | return dataMessage; 34 | } 35 | -------------------------------------------------------------------------------- /DecoderDocs/HBVibrationSensor.md: -------------------------------------------------------------------------------- 1 | # High Bandwidth Vibration Sensors 2 | 3 | The payload decoder function is applicable to high bandwidth vibration sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | | Key | Type | Description | 9 | | --------------------- | ------- | --------------------------------------------------------- | 10 | | axis | Integer | Holds the last received axis value | 11 | | event | String | Holds the last received description of event | 12 | | vibration_velocity | String | Holds the last received low frequency peak velocity | 13 | | vibration_gforce | String | Holds the last received high frequency peak velocity | 14 | | accelerator_temp | String | Holds the last received state of sensor | 15 | | bias_voltage | Decimal | Holds the last received bias voltage value | 16 | 17 | ## Example 18 | 19 | ```json 20 | // 1f1c001c2c1ba8 21 | { 22 | protocol: ProtocolVersion, 23 | counter: PacketCounter, 24 | type: "HB_VIBRATION_SENSOR", 25 | HB_VIBRATION_SENSOR: { 26 | axis: 'Channel 1', 27 | event: 'Periodic Report', 28 | vibration_velocity: '0.28000', 29 | vibration_gforce: 11, 30 | accelerator_temp: 27, 31 | bias_voltage: 1.68, 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /src/decoders/MagnetometerSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { 4 | hexToDecimalMessageDecoder, 5 | signedHexToDecimal, 6 | } from '../lib/HexConvertor'; 7 | import type { HexDecimal } from '../types'; 8 | 9 | export default function (hexDecimal: HexDecimal[]) { 10 | const dataMessage: { [key: string]: string | number } = {}; 11 | if (1 in hexDecimal) { 12 | const byteOneDecimal = hexDecimal[1]['decimal']; 13 | const bitsMsgs: string[] = []; 14 | bitsMsgs[0] = []; 15 | bitsMsgs[0][0] = 'Periodic Report'; 16 | dataMessage['messageType'] = hexToDecimalMessageDecoder( 17 | byteOneDecimal, 18 | bitsMsgs, 19 | ); 20 | } 21 | 22 | if (2 in hexDecimal && 3 in hexDecimal) { 23 | const xaxisHex = hexDecimal[2]['hex'] + hexDecimal[3]['hex']; 24 | dataMessage['xaxis'] = signedHexToDecimal(xaxisHex, 16); 25 | } 26 | 27 | if (4 in hexDecimal && 5 in hexDecimal) { 28 | const yaxisHex = hexDecimal[4]['hex'] + hexDecimal[5]['hex']; 29 | dataMessage['yaxis'] = signedHexToDecimal(yaxisHex, 16); 30 | } 31 | 32 | if (6 in hexDecimal && 7 in hexDecimal) { 33 | const zaxisHex = hexDecimal[6].hex + hexDecimal[7].hex; 34 | dataMessage['zaxis'] = signedHexToDecimal(zaxisHex, 16); 35 | } 36 | 37 | if (8 in hexDecimal) { 38 | const tempHex = hexDecimal[8].hex; 39 | dataMessage['temperature'] = signedHexToDecimal(tempHex); 40 | } 41 | return dataMessage; 42 | } 43 | -------------------------------------------------------------------------------- /tests/HBVibrationSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { HB_VIBRATION_SENSOR } from '../src/types/EventTypes'; 3 | 4 | describe('unit | HBVibrationSensor', () => { 5 | it.each([ 6 | ['1f1c001c2c1ba8', 'Channel 1', 'Periodic Report', '0.28000', 11, 27, 1.68], 7 | ['1b1c000100149d', 'Channel 1', 'Periodic Report', '0.01000', 0, 20, 1.57], 8 | [ 9 | '1b1c002d0929a0', 10 | 'Channel 1', 11 | 'Periodic Report', 12 | '0.45000', 13 | 2.25, 14 | 41, 15 | 1.6, 16 | ], 17 | ['1f1c00010010a0', 'Channel 1', 'Periodic Report', '0.01000', 0, 16, 1.6], 18 | ])( 19 | 'decodes a HBVibration %s event', 20 | ( 21 | payload: string, 22 | axis, 23 | expectedEvent, 24 | lowFreqPeakVelocity, 25 | highFreqPeakGforce, 26 | temperature, 27 | biasVoltage, 28 | ) => { 29 | const decodedData = decode(payload); 30 | const expectedOutput = {}; 31 | expectedOutput[HB_VIBRATION_SENSOR] = { 32 | axis: axis, 33 | event: expectedEvent, 34 | lowFreqPeakVelocity: { value: lowFreqPeakVelocity, unit: 'inches/sec' }, 35 | highFreqPeakGforce: { value: highFreqPeakGforce, unit: 'g' }, 36 | accelerator_temp: { value: temperature, unit: 'Celsius' }, 37 | biasVoltage: { value: biasVoltage, unit: 'V' }, 38 | }; 39 | expect(decodedData).toMatchObject(expectedOutput); 40 | }, 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /src/decoders/GpsDevice.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { signedHexToDecimal } from '../lib/HexConvertor'; 5 | 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = { latitude: '', longitude: '' }; 8 | 9 | if (1 in hexDecimal) { 10 | let statusMsg = {}; 11 | const binary = hexDecimal[1]['binary']; 12 | statusMsg = 1 == binary[7] ? 'Valid GPS Fix' : 'No GPS Fix'; 13 | if (1 == binary[6]) { 14 | statusMsg = 'Event: Movement Started'; 15 | } 16 | if (1 == binary[5]) { 17 | statusMsg = 'Event: Movement Stopped'; 18 | } 19 | if (1 == binary[4]) { 20 | statusMsg = 'Checksum failure'; 21 | } 22 | 23 | dataMessage['event'] = statusMsg; 24 | } 25 | 26 | if (9 in hexDecimal) { 27 | const lat_hex = 28 | hexDecimal[2]['hex'] + 29 | '' + 30 | hexDecimal[3]['hex'] + 31 | '' + 32 | hexDecimal[4]['hex'] + 33 | '' + 34 | hexDecimal[5]['hex']; 35 | const long_hex = 36 | hexDecimal[6]['hex'] + 37 | '' + 38 | hexDecimal[7]['hex'] + 39 | '' + 40 | hexDecimal[8]['hex'] + 41 | '' + 42 | hexDecimal[9]['hex']; 43 | 44 | dataMessage['latitude'] = Number( 45 | signedHexToDecimal(lat_hex, 32) / 10000000, 46 | ).toFixed(10); 47 | dataMessage['longitude'] = Number( 48 | signedHexToDecimal(long_hex, 32) / 10000000, 49 | ).toFixed(11); 50 | } 51 | return dataMessage; 52 | } 53 | -------------------------------------------------------------------------------- /src/decoders/HighPrecisionTiltSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import type { HexDecimal } from '../types'; 4 | import { 5 | hexToDecimalMessageDecoder, 6 | signedHexToDecimal, 7 | } from '../lib/HexConvertor'; 8 | 9 | export default function (hexDecimal: HexDecimal[]) { 10 | let wholeNum: number = 0; 11 | let fraction: number = 0; 12 | if (2 in hexDecimal) { 13 | wholeNum = hexDecimal[2]['decimal']; 14 | } 15 | 16 | if (3 in hexDecimal) { 17 | fraction = hexDecimal[3]['decimal'] / 10; 18 | } 19 | const dataMessage: { [key: string]: string | object } = {}; 20 | // The 8-bit event for message 0x06 is a two-byte ASCII 21 | if (1 in hexDecimal) { 22 | const byteHex: number = hexDecimal[1]['decimal']; 23 | const bitMsgs: string[] = [ 24 | 'Periodic report', 25 | 'Sensor transitioned toward the vertical orientation', 26 | 'Sensor transitioned away from the vertical orientation', 27 | 'Report on change toward the vertical orientation', 28 | 'Report on change away from the vertical orientation', 29 | ]; 30 | dataMessage['event'] = hexToDecimalMessageDecoder(byteHex, bitMsgs); 31 | } 32 | 33 | if (2 in hexDecimal) { 34 | dataMessage['tilt_angle_vertical_axis'] = { 35 | value: wholeNum + fraction, 36 | unit: 'Degrees', 37 | }; 38 | } 39 | 40 | if (4 in hexDecimal) { 41 | const temp: number = signedHexToDecimal(hexDecimal[4]['hex']); 42 | dataMessage['temperature'] = { unit: 'C', value: temp }; 43 | } 44 | return dataMessage; 45 | } 46 | -------------------------------------------------------------------------------- /tests/CondensedFFt.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { CONDENSED_FFT_ENERGY } from '../src/types/EventTypes'; 3 | 4 | describe('unit | CondensedFFt', () => { 5 | it.each([ 6 | [ 7 | 'with part payload', 8 | '112000001e0022000c000e', 9 | '101c000100139f', 10 | '12201000130006001a0005', 11 | '1320200009000400010001', 12 | '1420300001000100000000', 13 | { 14 | condensedFft: [ 15 | { 16 | axis: '01', 17 | condensedFft: { 18 | type: 'Total Energy Values', 19 | values: { 20 | '10-20Hz': '0.00205', 21 | '21-40Hz': '0.00233', 22 | '41-55Hz': '0.00082', 23 | '56-70Hz': '0.00096', 24 | }, 25 | }, 26 | }, 27 | { 28 | axis: '01', 29 | condensedFft: { 30 | type: 'Total Energy Values', 31 | values: { 32 | '71-110Hz': '0.00130', 33 | '111-130Hz': '0.00041', 34 | '131-230Hz': '0.00178', 35 | '231Hz+': '0.00034', 36 | }, 37 | }, 38 | }, 39 | ], 40 | condensedFftTotal: 146, 41 | }, 42 | ], 43 | ])( 44 | 'decodes an condensedfft %s (%s) event', 45 | ( 46 | description, 47 | condensedFft: string, 48 | hbPayload: string, 49 | condensedFftPart: string, 50 | condensedFftEnergy: string, 51 | condensedFftEnergyPart: string, 52 | result, 53 | ) => { 54 | const decodedData = decode( 55 | condensedFft, 56 | hbPayload, 57 | condensedFftPart, 58 | condensedFftEnergy, 59 | condensedFftEnergyPart, 60 | ); 61 | const expectedOutput = {}; 62 | expectedOutput[CONDENSED_FFT_ENERGY] = result; 63 | expect(decodedData).toMatchObject(expectedOutput); 64 | }, 65 | ); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/AirTemperatureAndHumiditySensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { 3 | AIR_TEMP_HUMIDITY_SENSOR, 4 | INTERNAL_TEMPERATURE, 5 | } from '../src/types/EventTypes'; 6 | 7 | describe('unit | AirTemperatureAndHumidity', () => { 8 | it.each([ 9 | ['with 1219009630', '1219009630', 'Periodic Report', '-22.3'], 10 | ['with 1e19008d60', '1e19008d60', 'Periodic Report', '-13.6'], 11 | ['with 1619009170', '1619009170', 'Periodic Report', '-17.7'], 12 | ])( 13 | 'decodes internal temperature: %s', 14 | (description, payload: string, eventDescription, temp) => { 15 | const decodedData = decode(payload); 16 | const expectedOutput = {}; 17 | expectedOutput[INTERNAL_TEMPERATURE] = { 18 | event: eventDescription, 19 | type: INTERNAL_TEMPERATURE, 20 | current_temperature: temp, 21 | }; 22 | expect(decodedData).toMatchObject(expectedOutput); 23 | }, 24 | ); 25 | it.each([ 26 | [ 27 | 'with 140d0096004450', 28 | '140d0096004450', 29 | 'Periodic Report', 30 | '68.0', 31 | '-22.0', 32 | ], 33 | [ 34 | 'with 120d058f303c00', 35 | '120d058f303c00', 36 | 'Humidity has risen above upper threshold', 37 | '60.0', 38 | '-15.3', 39 | ], 40 | [ 41 | 'with 130d058f603c00', 42 | '130d058f603c00', 43 | 'Humidity has risen above upper threshold', 44 | '60.0', 45 | '-15.6', 46 | ], 47 | ])( 48 | 'decodes air temperature and humidity: %s', 49 | (description, payload: string, eventDescription, humidity, temp) => { 50 | const decodedData = decode(payload); 51 | const expectedOutput = {}; 52 | expectedOutput[AIR_TEMP_HUMIDITY_SENSOR] = { 53 | event: eventDescription, 54 | type: AIR_TEMP_HUMIDITY_SENSOR, 55 | current_temperature: temp, 56 | humidity: humidity, 57 | }; 58 | expect(decodedData).toMatchObject(expectedOutput); 59 | }, 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /DecoderDocs/AirTemperatureAndHumiditySensor.md: -------------------------------------------------------------------------------- 1 | # Air Temperature & Humidity Sensors 2 | 3 | The payload decoder function is applicable to air temperature and humidity sensors. 4 | 5 | 6 | ## Decoder Format 7 | 8 | 9 | 10 | ## AIR_TEMP_HUMIDITY_SENSOR 11 | 12 | | Key | Type | Description | 13 | | --------------------- | ------- | ------------------------------------------------------------ | 14 | | current_temperature | String | Holds the last received temperature value in degrees celcius | 15 | | humidity | String | Holds the last received humidity value with unit | 16 | | event | String | -- | 17 | | type | String | -- | 18 | 19 | ## Example 20 | 21 | ```json 22 | // 130d058f603c00 23 | { 24 | protocol: ProtocolVersion, 25 | counter: PacketCounter, 26 | type: "AIR_TEMP_HUMIDITY_SENSOR", 27 | AIR_TEMP_HUMIDITY_SENSOR: { 28 | type: 'AIR_TEMP_HUMIDITY_SENSOR', 29 | event: 'Humidity has risen above upper threshold', 30 | current_temperature: '-15.6', 31 | humidity: '60.0' 32 | } 33 | } 34 | ``` 35 | 36 | 37 | 38 | ## INTERNAL_TEMPERATURE 39 | 40 | | Key | Type | Description | 41 | | --------------------- | ------- | ------------------------------------------------------------ | 42 | | current_temperature | String | Holds the last received temperature value with unit | 43 | | event | String | | 44 | | type | String | -- | 45 | ## Example 46 | 47 | ```json 48 | // 1219009630 49 | { 50 | protocol: ProtocolVersion, 51 | counter: PacketCounter, 52 | type: "INTERNAL_TEMPERATURE", 53 | INTERNAL_TEMPERATURE: { 54 | type: 'INTERNAL_TEMPERATURE', 55 | event: 'Periodic Report', 56 | current_temperature: '-22.3' 57 | } 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /src/decoders/ThermocoupleTemperatureSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { 4 | hexToBinaryMessageDecoder, 5 | hexToDecimalMessageDecoder, 6 | signedHexToDecimal, 7 | } from '../lib/HexConvertor'; 8 | import type { HexDecimal } from '../types'; 9 | 10 | export default function (hexDecimal: HexDecimal[]) { 11 | const dataMessage: { [key: string]: string | object } = {}; 12 | if (hexDecimal.length > 3) { 13 | const byteZeroHex = hexDecimal[1]['decimal']; 14 | const byteOneHex = signedHexToDecimal( 15 | hexDecimal[2]['hex'] + '' + hexDecimal[3]['hex'], 16 | 16, 17 | ); 18 | const temperature = Number((byteOneHex / 16).toFixed(2)); 19 | const byteThreeHex = hexDecimal[4]['decimal']; //Faults (see fault definitions) 20 | 21 | if (0 == byteThreeHex) { 22 | dataMessage['faults'] = 'No Faults'; 23 | } else { 24 | const bitData = [ 25 | 'An open circuit such as broken thermocouple wires has been detected', 26 | 'The input voltage is negative or greater than VDD', 27 | 'Thermocouple temperature is too low', 28 | 'The thermocouple temperature is too high', 29 | 'The Cold-Junction temperature is lower than the cold-junction' + 30 | ' temperature low threshold', 31 | 'The cold-Junction temperature is at or lower than the cold-junction' + 32 | ' temperature high threshold', 33 | 'The hot junction temperature is outside of the normal operating range', 34 | 'The cold-Junction temperature is outside of the normal operating range', 35 | ]; 36 | dataMessage['faults'] = hexToBinaryMessageDecoder( 37 | hexDecimal[4]['hex'], 38 | bitData, 39 | ); 40 | } 41 | const bitMsgs = [ 42 | 'Periodic Report', 43 | 'Temperature has risen above upper threshold', 44 | 'Temperature has fallen below lower threshold', 45 | 'Report on change increase', 46 | 'Report on change decrease', 47 | 'Fault', 48 | ]; 49 | //Relative temperature measurement: $byte_two_hex\n 50 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroHex, bitMsgs); 51 | dataMessage['current_temp'] = { value: temperature, unit: 'C' }; 52 | } 53 | return dataMessage; 54 | } 55 | -------------------------------------------------------------------------------- /src/types/EventTypes.ts: -------------------------------------------------------------------------------- 1 | export const RESET = 'RESET'; 2 | export const SUPERVISORY = 'SUPERVISOR'; 3 | export const TAMPER = 'TAMPER'; 4 | export const DOOR_WINDOW_SENSOR = 'DOOR_SENSOR'; 5 | export const ACCELEROMETER = 'ACCELEROMETER'; 6 | export const VIBRATION_EVENT = 'VIBRATION'; 7 | export const DOWNLINK = 'DOWNLINK'; 8 | export const CONTACT_SENSOR = 'CONTACT_SENSOR'; 9 | export const WATER_SENSOR = 'WATER_SENSOR'; 10 | export const TANK_LEVEL_SENSOR = 'TANK_LEVEL_SENSOR'; 11 | export const TEMPERATURE_EVENT = 'TEMPERATURE_EVENT'; 12 | export const TILT_EVENT = 'TILT_EVENT'; 13 | export const GLASS_BREAK_EVENT = 'GLASS_BREAK_EVENT'; 14 | export const PUSH_BUTTON = 'PUSH_BUTTON'; 15 | export const AMBIENT_LIGHT_SENSOR = 'AMBIENT_LIGHT_SENSOR'; 16 | export const TEST_MODE_EXIT = 'TEST_MODE_EXIT'; 17 | export const RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED'; 18 | export const CURRENT_SENSOR_STATE = 'CURRENT_SENSOR_STATE'; 19 | export const ACCELERATION_MOVEMENT_SENSOR = 'ACCELERATION_MOVEMENT_SENSOR'; 20 | export const AIR_TEMP_HUMIDITY_SENSOR = 'AIR_TEMP_HUMIDITY_SENSOR'; 21 | export const HIGH_PRECISION_TILT_SENSOR = 'HIGH_PRECISION_TILT_SENSOR'; 22 | export const ULTRASONIC_LEVEL_SENSOR = 'WIRELESS_ULTRASONIC_LEVEL_SENSOR'; 23 | export const WIRELESS_420MA_CURRENT_LOOP_SENSOR = 'WIRELESS_420MA_CURRENT_LOOP_SENSOR'; 24 | export const THERMOCOUPLE_TEMP = 'THERMOCOUPLE_TEMP_SENSOR'; 25 | export const HB_VIBRATION_SENSOR = 'HIGH_BANDWIDTH_VIBRATION_SENSOR'; 26 | export const CONDENSED_FFT_ENERGY_PART = 'VIBRATION_CONDENSED_FFT_ENERGY_PART'; 27 | export const CONDENSED_FFT_ENERGY = 'VIBRATION_CONDENSED_FFT_ENERGY'; 28 | export const CONDENSED_FFT_PART = 'VIBRATION_CONDENSED_FFT_PART'; 29 | export const CONDENSED_FFT = 'VIBRATION_CONDENSED_FFT'; 30 | export const WEATHER_STATION = 'WEATHER_STATION_SENSOR'; 31 | export const VOLTAGE_SENSOR = 'VOLTAGE_SENSOR'; 32 | export const EVENT_UNKNOWN = 'UNKNOWN'; 33 | export const TEST_MESSAGE = 'TEST_MESSAGE'; 34 | export const LINK_QUALITY = 'LINK_QUALITY'; 35 | export const GPS_SENSOR = 'GPS_SENSOR'; 36 | export const HONEYWELL_5800 = 'HONEYWELL_5800'; 37 | export const MAGNETOMETER_SENSOR = 'MAGNETOMETER_SENSOR'; 38 | export const DEVICE_INFO = 'DEVICE_INFO'; 39 | export const COMPASS_SENSOR = 'COMPASS_SENSOR'; 40 | export const INTERNAL_TEMPERATURE = 'INTERNAL_TEMPERATURE'; 41 | -------------------------------------------------------------------------------- /src/decoders/HighFreqVibrationSensor/CondensedFftEnergy.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../../types'; 4 | import { decode } from '../../parser'; 5 | import { HB_VIBRATION_SENSOR } from '../../types/EventTypes'; 6 | import { combineCfftValues } from './CondensedFftCommons'; 7 | import { hexToBinaryDecimal } from '../../lib/HexConvertor'; 8 | 9 | export default function ( 10 | condensedFftEnergy: HexDecimal[], 11 | hexPayloads: string[], 12 | ) { 13 | const hbPayload = hexPayloads[1]; 14 | const condensedFftEnergyPart = hexPayloads[2]; 15 | const condensedFftEnergyPartHexDecimal = hexToBinaryDecimal( 16 | condensedFftEnergyPart, 17 | ); 18 | condensedFftEnergyPartHexDecimal.splice(0, 1); 19 | 20 | let lowFreqPeakVelocity: number | undefined; 21 | 22 | if (hbPayload) { 23 | const hbPayloadDecoded = decode(hbPayload); 24 | lowFreqPeakVelocity = 25 | hbPayloadDecoded[HB_VIBRATION_SENSOR]['lowFreqPeakVelocity']['value']; 26 | } 27 | 28 | const finalValues = combineCfftValues( 29 | condensedFftEnergy, 30 | condensedFftEnergyPartHexDecimal, 31 | ); 32 | 33 | let cfftTotal: number = 0; 34 | 35 | finalValues.forEach((finalValue) => { 36 | if ( 37 | finalValue && 38 | finalValue['condensedFft'] && 39 | finalValue['condensedFft'].values 40 | ) { 41 | const allValues = Object.values(finalValue['condensedFft'].values); 42 | allValues.forEach((value) => { 43 | const numericValue = Number(value); 44 | if (!isNaN(numericValue)) { 45 | cfftTotal += numericValue; 46 | } 47 | }); 48 | } 49 | }); 50 | 51 | cfftTotal = 0 == cfftTotal ? 1 : cfftTotal; 52 | 53 | finalValues.forEach((additionalConversion, index) => { 54 | for (const [key, singleBandVal] of Object.entries( 55 | additionalConversion['condensedFft'].values, 56 | )) { 57 | const finalValue = 58 | lowFreqPeakVelocity !== undefined 59 | ? lowFreqPeakVelocity * 60 | ((typeof singleBandVal == 'string' 61 | ? parseInt(singleBandVal) 62 | : singleBandVal) / 63 | cfftTotal) 64 | : 0; 65 | 66 | finalValues[index]['condensedFft'].values[key] = finalValue.toFixed(5); 67 | } 68 | }); 69 | 70 | return { 71 | condensedFftTotal: cfftTotal, 72 | condensedFft: finalValues, 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RadioBridge Decoder 2 | 3 | This library helps de-coding the Radio Bridge sensor payload as a JSON Object, that can be used in various applications to display the proper sensor states. 4 | 5 | ## Installation 6 | 7 | This library can be installed using any compatible package manager. 8 | 9 | ```sh 10 | npm install @radiobridge/packet-decoder --save 11 | 12 | # For Yarn, use the command below. 13 | yarn add @radiobridge/packet-decoder 14 | ``` 15 | 16 | ## Usage 17 | 18 | You can use the decoder package using NPM or use the minified JS version to decode the data in browsers. 19 | 20 | ### Node.js 21 | ```ts 22 | const decoder = require('@radiobridge/packet-decoder') 23 | 24 | console.log(decoder.decodePayload('1901030126000000000000')) 25 | ``` 26 | 27 | ### JavaScript - Browser compatible 28 | 29 | Download the JS file from [here](https://github.com/RadioBridge/Packet-Decoder/blob/master/dist/radiobridge-decoder.min.js) 30 | ```js 31 | 32 | 33 | 36 | ``` 37 | 38 | ### Additional Helper functions 39 | Following functions are also available to decode the raw payloads received through different LNS networks, You can pass the raw data to the appropriate function to decode the payload. 40 | 41 | ```js 42 | let result = decodeTtnPayload('GAYDAQ=='); 43 | 44 | let result = decodeChirpstackPayload('FwYDAA=='); 45 | 46 | let result = decodeAwsIotPayload('FwYDAA=='); 47 | 48 | let result = decodeKerlinkPayload('FwYDAA=='); 49 | ``` 50 | 51 | ## Output Format 52 | 53 | decodePayload function accepts a Hexadecimal payload and returns the decoded values as an JSON Object. 54 | 55 | ## Example Output 56 | 57 | ```json 58 | // Input: 1901030126000000000000 59 | // Output: 60 | { 61 | "protocol": "1", 62 | "counter": 25, 63 | "type": "SUPERVISOR", 64 | "SUPERVISOR": { 65 | "BatteryLow": true, 66 | "ErrorWithLastDownlink": true, 67 | "TamperState": false, 68 | "TamperSinceLastReset": false, 69 | "threshold": "X-axis over threshold", 70 | "battery": "2.6V", 71 | "accumulationCount": 0 72 | } 73 | } 74 | ``` 75 | 76 | ## Sensor Decode Examples 77 | 78 | Please refer to the [GitHub README](https://github.com/RadioBridge/Packet-Decoder/tree/master/DecoderDocs#readme) to view the sensors output in detail. 79 | 80 | 81 | -------------------------------------------------------------------------------- /DecoderDocs/CondensedFFt.md: -------------------------------------------------------------------------------- 1 | ## CondensedFFT & CondensedFFt Part 2 | 3 | ### Input 4 | 5 | Condensed FFT requires 5 payloads to decode the final values. Here is an example of parameters required to pass. 6 | 7 | ```js 8 | radiobridgeDecoder.decodePayload('', '', '', '', ''); 9 | ``` 10 | 11 | ```js 12 | radiobridgeDecoder.decodePayload('112000001e0022000c000e', '101c000100139f', '12201000130006001a0005', '1320200009000400010001', '1420300001000100000000'); 13 | ``` 14 | 15 | 16 | ## CondensedFFT Array 17 | | Key | Type | Description | 18 | | --------------------- | ------- | ------------------------------------------------------------ | 19 | | condensedFftTotal | String | Holds the last condensed fft total value | 20 | | condensedFft | Array | Holds the last value of axis, type and band values | 21 | | axis | String | Holds the last condensed fft total value | 22 | | type | Array | Holds the last value of axis, type and band values | 23 | | values | Oject | Holds bands frequency range values as object | 24 | | data | String | hold the payload value | 25 | 26 | ## Example Output 27 | 28 | 29 | ```json 30 | { 31 | "protocol": "1", 32 | "counter": 17, 33 | "type": "VIBRATION_CONDENSED_FFT_ENERGY", 34 | "VIBRATION_CONDENSED_FFT_ENERGY": { 35 | "condensedFftTotal": 146, 36 | "condensedFft": [ 37 | { 38 | "data": "00", 39 | "axis": "01", 40 | "condensedFft": { 41 | "type": "Total Energy Values", 42 | "values": { 43 | "10-20Hz": "0.00205", 44 | "21-40Hz": "0.00233", 45 | "41-55Hz": "0.00082", 46 | "56-70Hz": "0.00096" 47 | } 48 | } 49 | }, 50 | { 51 | "data": "10", 52 | "axis": "01", 53 | "condensedFft": { 54 | "type": "Total Energy Values", 55 | "values": { 56 | "71-110Hz": "0.00130", 57 | "111-130Hz": "0.00041", 58 | "131-230Hz": "0.00178", 59 | "231Hz+": "0.00034" 60 | } 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /text.txt: -------------------------------------------------------------------------------- 1 | Package Current Wanted Latest Location Depended by 2 | @types/eslint 8.4.10 8.44.3 8.44.3 node_modules/@types/eslint typescript-library-boilerplate 3 | @types/jest 29.2.4 29.5.5 29.5.5 node_modules/@types/jest typescript-library-boilerplate 4 | @types/prettier 2.7.2 2.7.3 3.0.0 node_modules/@types/prettier typescript-library-boilerplate 5 | @typescript-eslint/eslint-plugin 5.47.0 5.62.0 6.7.4 node_modules/@typescript-eslint/eslint-plugin typescript-library-boilerplate 6 | @typescript-eslint/parser 5.47.0 5.62.0 6.7.4 node_modules/@typescript-eslint/parser typescript-library-boilerplate 7 | eslint 8.30.0 8.50.0 8.50.0 node_modules/eslint typescript-library-boilerplate 8 | eslint-config-prettier 8.5.0 8.10.0 9.0.0 node_modules/eslint-config-prettier typescript-library-boilerplate 9 | eslint-plugin-prettier 4.2.1 4.2.1 5.0.0 node_modules/eslint-plugin-prettier typescript-library-boilerplate 10 | jest 29.3.1 29.7.0 29.7.0 node_modules/jest typescript-library-boilerplate 11 | prettier 2.8.1 2.8.8 3.0.3 node_modules/prettier typescript-library-boilerplate 12 | rollup 2.79.1 2.79.1 3.29.4 node_modules/rollup typescript-library-boilerplate 13 | rollup-plugin-typescript2 0.34.1 0.34.1 0.36.0 node_modules/rollup-plugin-typescript2 typescript-library-boilerplate 14 | ts-jest 29.0.3 29.1.1 29.1.1 node_modules/ts-jest typescript-library-boilerplate 15 | ts-jest-resolver 2.0.0 2.0.1 2.0.1 node_modules/ts-jest-resolver typescript-library-boilerplate 16 | typedoc 0.23.23 0.23.28 0.25.1 node_modules/typedoc typescript-library-boilerplate 17 | typedoc-plugin-markdown 3.14.0 3.16.0 3.16.0 node_modules/typedoc-plugin-markdown typescript-library-boilerplate 18 | typescript 4.9.4 4.9.5 5.2.2 node_modules/typescript typescript-library-boilerplate 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Radio Bridge Packet Decoder 2 | 3 | # Radio Bridge Packet Decoder 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [decodeAwsIotPayload](README.md#decodeawsiotpayload) 10 | - [decodeChirpstackPayload](README.md#decodechirpstackpayload) 11 | - [decodeKerlinkPayload](README.md#decodekerlinkpayload) 12 | - [decodePayload](README.md#decodepayload) 13 | - [decodeTtnPayload](README.md#decodettnpayload) 14 | 15 | ## Functions 16 | 17 | ### decodeAwsIotPayload 18 | 19 | ▸ **decodeAwsIotPayload**(`receivedPayload`): `Object` 20 | 21 | #### Parameters 22 | 23 | | Name | Type | 24 | | :------ | :------ | 25 | | `receivedPayload` | `string` | 26 | 27 | #### Returns 28 | 29 | `Object` 30 | 31 | #### Defined in 32 | 33 | [index.ts:21](https://github.com/RadioBridge/Packet-Decoder/blob/61c3f88/src/index.ts#L21) 34 | 35 | ___ 36 | 37 | ### decodeChirpstackPayload 38 | 39 | ▸ **decodeChirpstackPayload**(`receivedPayload`): `Object` 40 | 41 | #### Parameters 42 | 43 | | Name | Type | 44 | | :------ | :------ | 45 | | `receivedPayload` | `string` | 46 | 47 | #### Returns 48 | 49 | `Object` 50 | 51 | #### Defined in 52 | 53 | [index.ts:17](https://github.com/RadioBridge/Packet-Decoder/blob/61c3f88/src/index.ts#L17) 54 | 55 | ___ 56 | 57 | ### decodeKerlinkPayload 58 | 59 | ▸ **decodeKerlinkPayload**(`receivedPayload`): `Object` 60 | 61 | #### Parameters 62 | 63 | | Name | Type | 64 | | :------ | :------ | 65 | | `receivedPayload` | `string` | 66 | 67 | #### Returns 68 | 69 | `Object` 70 | 71 | #### Defined in 72 | 73 | [index.ts:25](https://github.com/RadioBridge/Packet-Decoder/blob/61c3f88/src/index.ts#L25) 74 | 75 | ___ 76 | 77 | ### decodePayload 78 | 79 | ▸ **decodePayload**(`hexPayload`): `Object` 80 | 81 | #### Parameters 82 | 83 | | Name | Type | 84 | | :------ | :------ | 85 | | `hexPayload` | `string` | 86 | 87 | #### Returns 88 | 89 | `Object` 90 | 91 | #### Defined in 92 | 93 | [index.ts:9](https://github.com/RadioBridge/Packet-Decoder/blob/61c3f88/src/index.ts#L9) 94 | 95 | ___ 96 | 97 | ### decodeTtnPayload 98 | 99 | ▸ **decodeTtnPayload**(`receivedPayload`): `Object` 100 | 101 | #### Parameters 102 | 103 | | Name | Type | 104 | | :------ | :------ | 105 | | `receivedPayload` | `string` | 106 | 107 | #### Returns 108 | 109 | `Object` 110 | 111 | #### Defined in 112 | 113 | [index.ts:13](https://github.com/RadioBridge/Packet-Decoder/blob/61c3f88/src/index.ts#L13) 114 | -------------------------------------------------------------------------------- /src/decoders/HighFreqVibrationSensor/CondensedFftCommons.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../../types'; 4 | import { binaryToDecimal } from '../../lib/HexConvertor'; 5 | 6 | export function combineCfftValues( 7 | hexDataOne: HexDecimal[], 8 | hexDataTwo: HexDecimal[] | null = null, 9 | ) { 10 | const curEventConversion = {}; 11 | curEventConversion['data'] = hexDataOne[1]['hex']; 12 | curEventConversion['axis'] = 13 | binaryToDecimal(hexDataOne[1]['binary'].slice(-4)) + 1; 14 | curEventConversion['condensedFft'] = condensedFFTConversion(hexDataOne); 15 | 16 | const additionalConversion = {}; 17 | if (hexDataTwo) { 18 | additionalConversion['data'] = hexDataTwo[1]['hex']; 19 | additionalConversion['axis'] = 20 | binaryToDecimal(hexDataTwo[1]['binary'].slice(-4)) + 1; 21 | additionalConversion['condensedFft'] = condensedFFTConversion(hexDataTwo); 22 | } 23 | 24 | return [curEventConversion, additionalConversion]; 25 | } 26 | 27 | function condensedFFTConversion(hexDecimal: HexDecimal[]) { 28 | const payloadDefByte = binaryToDecimal(hexDecimal[1].binary.substring(0, 4)); 29 | const byteOneTwo = binaryToDecimal( 30 | hexDecimal[2]['binary'] + hexDecimal[3]['binary'], 31 | ); 32 | const byteThreeFour = binaryToDecimal( 33 | hexDecimal[4]['binary'] + hexDecimal[5]['binary'], 34 | ); 35 | const byteFiveSix = binaryToDecimal( 36 | hexDecimal[6]['binary'] + hexDecimal[7]['binary'], 37 | ); 38 | const byteSevenEight = binaryToDecimal( 39 | hexDecimal[8]['binary'] + hexDecimal[9]['binary'], 40 | ); 41 | const payloadDefMap: Record = { 42 | '0': { type: 'Total Energy Values', freqRange: '0-3' }, 43 | '1': { type: 'Total Energy Values', freqRange: '4-7' }, 44 | '2': { type: 'Peak Energy Values', freqRange: '0-3' }, 45 | '3': { type: 'Peak Energy Values', freqRange: '4-7' }, 46 | }; 47 | 48 | const { type, freqRange } = payloadDefMap[payloadDefByte] || { 49 | type: '', 50 | freqRange: '', 51 | }; 52 | 53 | const values: { [key: string]: string } = {}; 54 | 55 | if (freqRange === '0-3') { 56 | values['10-20Hz'] = byteOneTwo; 57 | values['21-40Hz'] = byteThreeFour; 58 | values['41-55Hz'] = byteFiveSix; 59 | values['56-70Hz'] = byteSevenEight; 60 | } else { 61 | values['71-110Hz'] = byteOneTwo; 62 | values['111-130Hz'] = byteThreeFour; 63 | values['131-230Hz'] = byteFiveSix; 64 | values['231Hz+'] = byteSevenEight; 65 | } 66 | return { type, values }; 67 | } 68 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import terser from '@rollup/plugin-terser'; 4 | import typescript2 from 'rollup-plugin-typescript2'; 5 | import packageJSON from './package.json' assert { type: 'json' }; 6 | import resolve from "@rollup/plugin-node-resolve"; 7 | import babel from "@rollup/plugin-babel"; 8 | import commonjs from "@rollup/plugin-commonjs"; 9 | import json from "@rollup/plugin-json"; 10 | 11 | /** 12 | * Comment with library information to be appended in the generated bundles. 13 | */ 14 | const banner = `/*! 15 | * ${packageJSON.name} v${packageJSON.version} 16 | * (c) ${packageJSON.author.name} 17 | * Released under the ${packageJSON.license} License. 18 | */ 19 | `; 20 | 21 | /** 22 | * Creates an output options object for Rollup.js. 23 | * @param {import('rollup').OutputOptions} options 24 | * @returns {import('rollup').OutputOptions} 25 | */ 26 | function createOutputOptions(options) { 27 | return { 28 | banner, 29 | name: 'radiobridgeDecoder', 30 | exports: 'named', 31 | sourcemap: true, 32 | ...options, 33 | }; 34 | } 35 | 36 | /** 37 | * @type {import('rollup').RollupOptions} 38 | */ 39 | const options = { 40 | input: './src/index.ts', 41 | output: [ 42 | createOutputOptions({ 43 | file: './dist/index.js', 44 | format: 'commonjs', 45 | }), 46 | createOutputOptions({ 47 | file: './dist/index.cjs', 48 | format: 'commonjs', 49 | }), 50 | createOutputOptions({ 51 | file: './dist/index.mjs', 52 | format: 'esm', 53 | }), 54 | createOutputOptions({ 55 | file: './dist/index.esm.js', 56 | format: 'esm', 57 | }), 58 | createOutputOptions({ 59 | file: './dist/index.umd.js', 60 | format: 'umd', 61 | }), 62 | createOutputOptions({ 63 | file: './dist/index.umd.min.js', 64 | format: 'umd', 65 | plugins: [terser()], 66 | }), 67 | createOutputOptions({ 68 | file: `./dist/radiobridge-decoder.min.js`, 69 | format: "iife", 70 | sourceMap: 'inline', 71 | }), 72 | ], 73 | plugins: [ 74 | typescript2({ 75 | clean: true, 76 | useTsconfigDeclarationDir: true, 77 | tsconfig: './tsconfig.bundle.json', 78 | }), 79 | json(), 80 | resolve({ 81 | jsnext: true, 82 | main: true, 83 | browser: true, 84 | preferBuiltins: false 85 | }), 86 | commonjs({ 87 | ignore: ['fs', 'path', 'utils'], 88 | }), 89 | babel({ 90 | babelHelpers: "bundled", 91 | //exclude: 'node_modules/**', 92 | }), 93 | terser(), 94 | 95 | ], 96 | }; 97 | 98 | export default options; 99 | -------------------------------------------------------------------------------- /src/decoders/HighFreqVibrationSensor/CondensedFFt.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { hexToBinaryDecimal } from '../../lib/HexConvertor'; 4 | import type { HexDecimal } from '../../types'; 5 | import { decode } from '../../parser'; 6 | import { 7 | CONDENSED_FFT_ENERGY, 8 | HB_VIBRATION_SENSOR, 9 | } from '../../types/EventTypes'; 10 | import { combineCfftValues } from './CondensedFftCommons'; 11 | 12 | type CondensedFftType = { 13 | condensedFft: { 14 | data: string; 15 | axis: string; 16 | type: string; 17 | values: Record; 18 | }; 19 | condensedFftTotal: number; 20 | }; 21 | 22 | export default function (condensedFft: HexDecimal[], hexPayloads: string[]) { 23 | const hbPayload = hexPayloads[1]; 24 | const condensedFftPart = hexPayloads[2]; 25 | const condensedFftEnergy = hexPayloads[3]; 26 | const condensedFftEnergyPart = hexPayloads[4]; 27 | 28 | const dataMessage: CondensedFftType = { 29 | condensedFft: { 30 | data: '', 31 | axis: '', 32 | type: '', 33 | values: {}, 34 | }, 35 | condensedFftTotal: 0, 36 | }; 37 | 38 | let lowFreqPeakVelocity: number | undefined; 39 | 40 | if (hbPayload) { 41 | const hbPayloadDecoded = decode(hbPayload); 42 | lowFreqPeakVelocity = 43 | hbPayloadDecoded[HB_VIBRATION_SENSOR]['lowFreqPeakVelocity']['value']; 44 | } 45 | let condensedFftPartHexDecimal = null; 46 | if (condensedFftPart) { 47 | condensedFftPartHexDecimal = hexToBinaryDecimal(condensedFftPart); 48 | condensedFftPartHexDecimal.splice(0, 1); 49 | } 50 | const finalValues = combineCfftValues( 51 | condensedFft, 52 | condensedFftPartHexDecimal, 53 | ); 54 | let cfftTotal: number = 0; 55 | 56 | const condensedFftEnergyDecoded = decode( 57 | condensedFftEnergy, 58 | hbPayload, 59 | condensedFftEnergyPart, 60 | ); 61 | 62 | cfftTotal = 63 | condensedFftEnergyDecoded[CONDENSED_FFT_ENERGY]['condensedFftTotal']; 64 | cfftTotal = 0 == cfftTotal ? 1 : cfftTotal; 65 | 66 | finalValues.forEach((additionalConversion, index) => { 67 | for (const [key, singleBandVal] of Object.entries( 68 | additionalConversion['condensedFft'].values, 69 | )) { 70 | const finalValue = 71 | lowFreqPeakVelocity !== undefined 72 | ? lowFreqPeakVelocity * 73 | ((typeof singleBandVal == 'string' 74 | ? parseInt(singleBandVal) 75 | : singleBandVal) / 76 | cfftTotal) 77 | : 0; 78 | 79 | finalValues[index]['condensedFft'].values[key] = finalValue.toFixed(5); 80 | } 81 | }); 82 | return { 83 | condensedFftTotal: cfftTotal, 84 | condensedFft: finalValues, 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@radiobridge/packet-decoder", 3 | "version": "1.0.3", 4 | "description": "Decodes the RadioBridge sensor packets", 5 | "cdn": "dist/index.umd.js", 6 | "main": "dist/index.js", 7 | "types": "types/index.d.ts", 8 | "unpkg": "dist/index.umd.js", 9 | "module": "dist/index.esm.js", 10 | "jsdelivr": "dist/index.umd.js", 11 | "umd:main": "dist/index.umd.js", 12 | "files": [ 13 | "dist/", 14 | "types/" 15 | ], 16 | "exports": { 17 | ".": [ 18 | { 19 | "import": "./dist/index.mjs", 20 | "require": "./dist/index.cjs", 21 | "default": "./dist/index.js" 22 | }, 23 | "./dist/index.js" 24 | ] 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.24.0", 28 | "@babel/preset-env": "^7.24.0", 29 | "@rollup/plugin-babel": "^6.0.4", 30 | "@rollup/plugin-json": "^6.1.0", 31 | "@rollup/plugin-node-resolve": "^15.2.3", 32 | "@rollup/plugin-terser": "^0.4.4", 33 | "@types/eslint": "^8.44.3", 34 | "@types/jest": "^29.5.5", 35 | "@types/locutus": "^0.0.8", 36 | "@types/prettier": "^3.0.0", 37 | "@typescript-eslint/eslint-plugin": "^6.7.4", 38 | "@typescript-eslint/parser": "^6.7.4", 39 | "eslint": "^8.50.0", 40 | "eslint-config-prettier": "^9.0.0", 41 | "eslint-plugin-prettier": "^5.0.0", 42 | "jest": "^29.7.0", 43 | "prettier": "^3.0.3", 44 | "rollup": "^3.29.4", 45 | "rollup-plugin-commonjs": "^10.1.0", 46 | "rollup-plugin-internal": "^1.0.4", 47 | "rollup-plugin-node-builtins": "^2.1.2", 48 | "rollup-plugin-node-polyfills": "^0.2.1", 49 | "rollup-plugin-typescript2": "^0.36.0", 50 | "ts-jest": "^29.1.1", 51 | "ts-jest-resolver": "^2.0.1", 52 | "typedoc": "^0.25.1", 53 | "typedoc-plugin-markdown": "^3.16.0", 54 | "typescript": "^5.2.2" 55 | }, 56 | "scripts": { 57 | "doc": "typedoc src/index.ts", 58 | "test": "jest", 59 | "lint": "eslint \"*/**/*.{ts,js,json}\"", 60 | "lint:fix": "eslint \"*/**/*.{ts,js,json}\" --fix", 61 | "build": "rollup --config ./rollup.config.mjs", 62 | "prepublishOnly": "npm run build" 63 | }, 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/RadioBridge/Packet-Decoder.git" 67 | }, 68 | "keywords": [ 69 | "radiobridge", 70 | "packet-decoder" 71 | ], 72 | "author": { 73 | "name": "Deepak Maurya" 74 | }, 75 | "license": "MIT", 76 | "bugs": { 77 | "url": "https://github.com/RadioBridge/Packet-Decoder/issues" 78 | }, 79 | "homepage": "https://github.com/RadioBridge/Packet-Decoder#readme", 80 | "dependencies": { 81 | "@rollup/plugin-commonjs": "^25.0.7", 82 | "buffer": "^6.0.3", 83 | "locutus": "^2.0.16", 84 | "rollup-plugin-auto-external": "^2.0.0", 85 | "rollup-plugin-node-globals": "^1.4.0" 86 | }, 87 | "publishConfig": { 88 | "access": "public", 89 | "registry": "https://registry.npmjs.org/" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/decoders/HBVibrationSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { HexDecimal } from '../types'; 4 | import { 5 | hexToDecimalMessageDecoder, 6 | hexToDecimal, 7 | binaryToDecimal, 8 | signedHexToDecimal, 9 | } from '../lib/HexConvertor'; 10 | 11 | type HBVibrationDecodeType = { 12 | axis: string; 13 | event: string | null; 14 | lowFreqPeakVelocity: object; 15 | highFreqPeakGforce: object; 16 | accelerator_temp: object; 17 | biasVoltage: object; 18 | }; 19 | 20 | export default function (hexDecimal: HexDecimal[]) { 21 | let axisReport = ''; 22 | const dataMessage: HBVibrationDecodeType = { 23 | axis: '', 24 | event: null, 25 | lowFreqPeakVelocity: {}, 26 | highFreqPeakGforce: {}, 27 | accelerator_temp: {}, 28 | biasVoltage: {}, 29 | }; 30 | switch (hexDecimal[0]['decimal']) { 31 | case 28: 32 | axisReport = 'Channel 1'; 33 | break; 34 | case 29: 35 | axisReport = 'Channel 2'; 36 | break; 37 | case 30: 38 | axisReport = 'Channel 3'; 39 | break; 40 | case 31: 41 | axisReport = 'Channel 4'; 42 | break; 43 | } 44 | 45 | dataMessage['axis'] = axisReport; 46 | if (1 in hexDecimal) { 47 | const byteOneHexData = binaryToDecimal(hexDecimal[1]['binary'].slice(-4)); 48 | const vibrationEventPayload = { 49 | 0: 'Periodic Report', 50 | 1: 'High frequency vibration has risen above upper threshold', 51 | 2: 'High frequency vibration has fallen below lower threshold', 52 | 3: 'Low frequency velocity has risen above upper threshold', 53 | 4: 'Low frequency velocity has fallen below lower threshold', 54 | 5: 'Accelerometer exceeded g-force range', 55 | }; 56 | let decodedValue = hexToDecimalMessageDecoder( 57 | byteOneHexData, 58 | vibrationEventPayload, 59 | ); 60 | if (5 == byteOneHexData) { 61 | const bitFourToSix = hexDecimal[1]['binary'].substr(1, 3); 62 | const gforceRange = { 63 | 0: 'x1', 64 | 1: 'x2', 65 | 2: 'x4', 66 | 3: 'x5', 67 | 4: 'x8', 68 | 5: 'x10', 69 | 6: 'x16', 70 | 7: 'x32', 71 | }; 72 | const bitfourSixDecimal = binaryToDecimal(bitFourToSix); 73 | decodedValue += 74 | ': ' + hexToDecimalMessageDecoder(bitfourSixDecimal, gforceRange); 75 | } 76 | 77 | dataMessage['event'] = decodedValue; 78 | } 79 | 80 | if (2 in hexDecimal) { 81 | const byteOnetwoHexData = hexDecimal[2]['hex']; 82 | const decimalVal = hexToDecimal(byteOnetwoHexData); 83 | const inchesPerSec = decimalVal > 0 ? decimalVal / 100 : 0; 84 | dataMessage['lowFreqPeakVelocity'] = { 85 | value: inchesPerSec.toFixed(5), 86 | unit: 'inches/sec', 87 | }; 88 | } 89 | 90 | if (3 in hexDecimal) { 91 | dataMessage['highFreqPeakGforce'] = { 92 | value: hexDecimal[3]['decimal'] / 4, 93 | unit: 'g', 94 | }; 95 | } 96 | 97 | if (4 in hexDecimal) { 98 | dataMessage['accelerator_temp'] = { 99 | value: signedHexToDecimal(hexDecimal[4]['hex']), 100 | unit: 'Celsius', 101 | }; 102 | } 103 | 104 | if (5 in hexDecimal) { 105 | const voltage = hexToDecimal(hexDecimal[5]['hex']); 106 | dataMessage['biasVoltage'] = { value: voltage / 100, unit: 'V' }; 107 | } 108 | return dataMessage; 109 | } 110 | -------------------------------------------------------------------------------- /src/decoders/AirTemperatureAndHumiditySensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import type { HexDecimal } from '../types'; 4 | import { identifyEventType } from '../lib/IdentifyEventType'; 5 | import { 6 | binaryToDecimal, 7 | hexToDecimalMessageDecoder, 8 | } from '../lib/HexConvertor'; 9 | 10 | export default function (hexDecimal: HexDecimal[]) { 11 | const eventType = identifyEventType(hexDecimal[0]['decimal']); 12 | let dataMessage = {}; 13 | if (hexDecimal.length >= 2) { 14 | const byteZeroHex = hexDecimal[1]['decimal']; 15 | const byte4 = 4 in hexDecimal ? hexDecimal[4]['binary'] : null; 16 | const byte5 = 5 in hexDecimal ? hexDecimal[5]['binary'] : null; 17 | const currentState = getSensorState( 18 | hexDecimal[2]['binary'], 19 | hexDecimal[3]['binary'], 20 | byte4, 21 | byte5, 22 | ); 23 | 24 | const bitMsgs: string[] = [ 25 | 'Periodic Report', 26 | 'Temperature has risen above upper threshold', 27 | 'Temperature has fallen below lower threshold', 28 | 'Temperature report on change increase', 29 | 'Temperature report on change decrease', 30 | eventType === 'INTERNAL_TEMPERATURE' 31 | ? 'Shake Message' 32 | : 'Humidity has risen above upper threshold', 33 | 'Humidity has fallen below lower threshold', 34 | 'Humidity report on change increase', 35 | 'Humidity report on change decrease', 36 | ]; 37 | 38 | const dataMessages: string[] = hexToDecimalMessageDecoder( 39 | byteZeroHex, 40 | bitMsgs, 41 | ); 42 | 43 | // Relative temperature measurement: $byteTwoHex\n 44 | dataMessage = { 45 | type: eventType, 46 | event: dataMessages, 47 | ...currentState, 48 | }; 49 | } 50 | return dataMessage; 51 | } 52 | 53 | function getSensorState( 54 | byteOneBin: string, 55 | byteTwoBin: string, 56 | byteThreeBin: string | null, 57 | byteFourBin: string | null, 58 | ) { 59 | const finalArray: Record = {}; 60 | let byteOneHex: string = byteOneBin; // Current temperature in degrees Celsius 61 | 62 | // suppose we recv byte one as 0x9D = 10011101 = remove 1st MSB then we get 0011101=0x1D 63 | // and if 1st MSB = 1 then negative and if it's 0 then positive 64 | if ('1' === byteOneHex.substring(0, 1)) { 65 | byteOneHex = binaryToDecimal(byteOneHex.substring(1)); 66 | byteOneHex = '-' + byteOneHex; 67 | } else { 68 | // positive number 69 | byteOneHex = binaryToDecimal(byteOneHex.substring(1)); 70 | } 71 | // First decimal of current temperature (0-9) in the most significant 4-bits. 72 | let byteTwoHex = '' + byteTwoBin.match(/.{1,4}/g); 73 | byteTwoHex = binaryToDecimal(byteTwoHex).toString(); 74 | finalArray['current_temperature'] = `${byteOneHex}.${byteTwoHex}`; 75 | if (byteThreeBin !== null && byteFourBin !== null) { 76 | let byteThreeHex: string = byteThreeBin; // Humidity in % relative humidity (0-100%) 77 | if (byteThreeHex.startsWith('1')) { 78 | byteThreeHex = binaryToDecimal(byteThreeHex.substring(1)).toString(); 79 | byteThreeHex = '-' + byteThreeHex; 80 | } else { 81 | // positive number 82 | byteThreeHex = binaryToDecimal(byteThreeHex.substring(1)).toString(); 83 | } 84 | // First decimal of relative humidity (0-9) in the most significant 4-bits. 85 | let byteFourHex: string = byteFourBin.split('', 4)[0] || '0'; 86 | byteFourHex = binaryToDecimal(byteFourHex).toString(); 87 | 88 | finalArray['humidity'] = `${byteThreeHex}.${byteFourHex}`; 89 | } 90 | 91 | return finalArray; 92 | } 93 | -------------------------------------------------------------------------------- /setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | const fs = require('fs/promises'); 5 | const path = require('path'); 6 | const readline = require('readline'); 7 | 8 | const resolveToFiles = (directory) => async (dirent) => { 9 | if ( 10 | dirent.name === '.git' || 11 | dirent.name === 'setup.js' || 12 | dirent.name === 'node_modules' 13 | ) 14 | return []; 15 | if (dirent.isFile()) return [path.resolve(directory, dirent.name)]; 16 | if (dirent.isDirectory()) 17 | return await getFiles(path.resolve(directory, dirent.name)); 18 | return []; 19 | }; 20 | 21 | const getFiles = async (directory) => { 22 | const filesWithTypes = await fs.readdir(directory, { 23 | encoding: 'utf-8', 24 | withFileTypes: true, 25 | }); 26 | 27 | const resolutions = filesWithTypes.map(resolveToFiles(directory)); 28 | 29 | const files = await Promise.all(resolutions); 30 | 31 | return files.flat(1); 32 | }; 33 | 34 | const gettingFiles = getFiles(path.resolve(__dirname)); 35 | 36 | const replaceInFile = (matcher, replacer) => async (path) => { 37 | const contents = await fs.readFile(path, { encoding: 'utf-8' }); 38 | await fs.writeFile(path, contents.replace(matcher, replacer)); 39 | }; 40 | 41 | const rl = readline.createInterface({ 42 | input: process.stdin, 43 | output: process.stdout, 44 | }); 45 | 46 | const replaceVariable = async (variable, value) => { 47 | const files = await gettingFiles; 48 | 49 | const matcher = new RegExp(`\\[${variable}\\]`, 'g'); 50 | 51 | await Promise.all(files.map(replaceInFile(matcher, value))); 52 | }; 53 | 54 | const fillTemplateVariable = (variable, question, defaultValue = '') => 55 | new Promise((resolve) => { 56 | rl.question(question, async (receivedValue) => { 57 | const value = receivedValue?.trim() || defaultValue; 58 | 59 | replaceVariable(variable, value); 60 | 61 | rl.write(`Replaced ${variable} to "${value}".\n`); 62 | 63 | resolve(); 64 | }); 65 | }); 66 | 67 | replaceVariable('year', new Date().getFullYear()) 68 | .then(() => 69 | fillTemplateVariable( 70 | 'libraryNameWithSpacesAndUpperCases', 71 | ` 72 | Library name (can have upper/lower case letters, spaces, numbers and special characters) 73 | Ex. React.js, Vue.js, Fetch Interceptors, GraphQL Loader for Webpack etc. 74 | > `, 75 | ), 76 | ) 77 | .then(() => 78 | fillTemplateVariable( 79 | 'libraryName', 80 | ` 81 | Module name (only lower case letters, "." or "-") and optionally with scope. 82 | Ex. react, vue, fetch-interceptors, @webpack/graphql-loader etc. 83 | > `, 84 | ), 85 | ) 86 | .then(() => 87 | fillTemplateVariable( 88 | 'libraryCamelCaseName', 89 | ` 90 | What is you library name in camel-case for UMD bundles? 91 | Ex. React, Vue, fetchInterceptors, graphqlLoaderForWebpack etc. 92 | > `, 93 | ), 94 | ) 95 | .then(() => 96 | fillTemplateVariable( 97 | 'repositoryOwner', 98 | ` 99 | What is your nickname on GitHub? 100 | > `, 101 | '', 102 | ), 103 | ) 104 | .then(() => 105 | fillTemplateVariable( 106 | 'repositoryName', 107 | ` 108 | What will be this repository name on GitHub? 109 | > `, 110 | ), 111 | ) 112 | .then(() => 113 | fillTemplateVariable( 114 | 'authorFullName', 115 | ` 116 | What is your full name? (used in MIT License) 117 | > `, 118 | '', 119 | ), 120 | ) 121 | .then(() => { 122 | rl.close(); 123 | fs.unlink(path.resolve(__filename)); 124 | }) 125 | .catch((error) => { 126 | console.error(error?.message ?? error ?? 'Unknown error.'); 127 | rl.close(); 128 | }); 129 | -------------------------------------------------------------------------------- /DecoderDocs/CommonEvents.md: -------------------------------------------------------------------------------- 1 | # Common Events - Applicable to all Radio Bridge Sensors 2 | 3 | The payload decoder function is applicable to all the sensors. 4 | 5 | For more detailed information, please visit [Radio Bridge official website](https://www.radiobridge.com). 6 | 7 | ## Decoder Format 8 | 9 | 10 | 11 | ## Supervisory 12 | 13 | | Key | Type | Description | 14 | | --------------------- | ------- | --------------------------------------------------------- | 15 | | BatteryLow | Boolean | Indicates if the battery is low | 16 | | ErrorWithLastDownlink | Boolean | Indicates if the last downlink was failed | 17 | | TamperState | Boolean | Tamper state | 18 | | TamperSinceLastReset | Boolean | Indicates if the sensor was opened after last reset event | 19 | | battery | Float | Displays the current battery voltage | 20 | | accumulationCount | Int | -- | 21 | 22 | ## Example 23 | 24 | > Payload: 1901030126000000000000 25 | ```json 26 | { 27 | protocol: ProtocolVersion, 28 | counter: PacketCounter, 29 | type: "SUPERVISOR", 30 | SUPERVISOR: { 31 | BatteryLow: true, 32 | ErrorWithLastDownlink: true, 33 | TamperState: false, 34 | TamperSinceLastReset: false, 35 | threshold: 'X-axis over threshold', 36 | battery: '2.6V', 37 | accumulationCount: '0' 38 | } 39 | } 40 | ``` 41 | 42 | 43 | 44 | ## Tamper 45 | 46 | | Key | Type | Description | 47 | | --------------------- | ------- | -----------------------------------------------------------------------------| 48 | | event | String | Yes - When device enclosure is open and when closed this will hold No value. | 49 | 50 | ## Example 51 | 52 | > Payload: 190201 53 | ```json 54 | { 55 | protocol: ProtocolVersion, 56 | counter: PacketCounter, 57 | type: "TAMPER", 58 | TAMPER: { 59 | event: 'Open' 60 | } 61 | } 62 | ``` 63 | 64 | 65 | 66 | ## Reset 67 | 68 | | Key | Type | Description | 69 | | --------------------- | ------- | --------------------------------------------------------- | 70 | | hardware_version | String | Hardware version from reset message Example: “2.0” | 71 | | firmware_version | String | Firmware version from reset message Example: “1.6” | 72 | 73 | ## Example 74 | > Payload: 10000a2288ad703c 75 | ```json 76 | { 77 | protocol: ProtocolVersion, 78 | counter: PacketCounter, 79 | type: "RESET", 80 | RESET: { 81 | hardware_version: '2.2', 82 | firmware_version: '2.5.13', 83 | } 84 | } 85 | ``` 86 | 87 | 88 | 89 | ## Downlink 90 | 91 | | Key | Type | Description | 92 | | --------------------- | ------- | --------------------------------------------------------- | 93 | | extendedBytes | String | -- | 94 | | event | String | -- | 95 | 96 | ## Example 97 | 98 | > Payload: 16ff0311019e0000000000 99 | 100 | ```json 101 | { 102 | protocol: ProtocolVersion, 103 | counter: PacketCounter, 104 | type: "DOWNLINK", 105 | DOWNLINK: { 106 | extendedBytes: '11 01 9E 00 00 00 00 00', 107 | event: 'Msg Valid - No Errors', 108 | } 109 | } 110 | ``` 111 | 112 | 113 | 114 | ## Link Quality 115 | 116 | | Key | Type | Description | 117 | | --------------------- | ------- | --------------------------------------------------------- | 118 | | rssi | Int | -- | 119 | | snr | Int | -- | 120 | 121 | ## Example 122 | 123 | > Payload: 1bfb01b906 124 | 125 | ```json 126 | { 127 | protocol: ProtocolVersion, 128 | counter: PacketCounter, 129 | type: "LINK_QUALITY", 130 | LINK_QUALITY: { 131 | rssi: -71, 132 | snr: 6, 133 | } 134 | } 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /src/lib/HexConvertor.ts: -------------------------------------------------------------------------------- 1 | import * as locutusModule from 'locutus'; 2 | import * as math from 'locutus/php/math'; 3 | import { HexDecimal } from '../types'; 4 | 5 | export function hexToBinaryDecimal(data: string): HexDecimal[] { 6 | if (typeof data === 'undefined') { 7 | console.trace(data); 8 | console.dir(data); 9 | } 10 | const hexData = data.match(new RegExp('.{1,2}', 'g')) || []; 11 | const hexDecimalBinary: Array = []; 12 | hexData.forEach((singleHexByte) => { 13 | const decimalVal = baseConvert(singleHexByte, 16, 2); 14 | const binaryValue = decimalVal.padStart(8, '0'); 15 | const newHexDecimalBinary = { 16 | hex: singleHexByte, 17 | decimal: parseInt(baseConvert(singleHexByte, 16, 10)), 18 | binary: binaryValue, 19 | ascii: hexToStr(singleHexByte), 20 | }; 21 | hexDecimalBinary.push(newHexDecimalBinary); 22 | }); 23 | return hexDecimalBinary; 24 | } 25 | 26 | export function baseConvert( 27 | data: string | number, 28 | fromBase: number, 29 | toBase: number, 30 | ) { 31 | let num = data; 32 | if (isNaN(data)) { 33 | num = parseInt(data as string, fromBase); 34 | fromBase = 10; 35 | } 36 | return parseInt(num + '', fromBase | 0).toString(toBase | 0); 37 | } 38 | 39 | export function hexToDecimalMessageDecoder( 40 | decimalData: number, 41 | bitsMessages: { [key: string | number]: string } | string[], 42 | ) { 43 | if (decimalData in bitsMessages) { 44 | return bitsMessages[decimalData]; 45 | } else { 46 | return null; 47 | } 48 | } 49 | 50 | function hexToStr(str1: string) { 51 | const hex = str1.toString(); 52 | let str = ''; 53 | for (let n = 0; n < hex.length; n += 2) { 54 | str += String.fromCharCode(parseInt(hex.substring(n, 3), 16)); 55 | } 56 | return str; 57 | } 58 | 59 | export function signedHexToDecimal(hexNumber: string, binaryDigits = 8) { 60 | //first convert into binary 61 | const binary = hexToBinary(hexNumber, binaryDigits); 62 | const newBinary = binary.split(''); 63 | let decimal: number; 64 | if ('1' == binary[0]) { 65 | //Now convert binary into 1's complement 66 | //console.log(`binary length: ${binary.length}`); 67 | for (let i = 0; i < binary.length; i++) { 68 | if (1 == parseInt(binary[i])) { 69 | newBinary[i] = 0; 70 | } else { 71 | newBinary[i] = 1; 72 | } 73 | } 74 | 75 | //now add 1 to convert into 2's complement 76 | decimal = binaryToDecimal(newBinary.join('')); 77 | decimal++; 78 | decimal = -decimal; 79 | } else { 80 | decimal = binaryToDecimal(binary); 81 | } 82 | 83 | return Number(decimal); 84 | } 85 | 86 | function hexToBinary(hex: string, binaryDigits: number) { 87 | const HexNumber = locutusModule.php.math.base_convert(hex, 16, 2); 88 | return HexNumber.padStart(binaryDigits, '0'); 89 | } 90 | 91 | export function binaryToDecimal(binary: string) { 92 | return locutusModule.php.math.base_convert(binary, 2, 10); 93 | } 94 | 95 | export function binaryToHex(binary: string) { 96 | return locutusModule.php.math.base_convert(binary, 2, 16); 97 | } 98 | 99 | export function hexToDecimal(hex: string) { 100 | return parseInt(math.base_convert(hex, 16, 10)); 101 | } 102 | 103 | export function hexToBinaryMessageDecoder( 104 | hexData: string, 105 | bitsMessages: { [key: number]: string | undefined; nobit?: string }, 106 | returnType: 'string' | 'array' = 'string', 107 | ): string | string[] { 108 | const decVal: string = math.base_convert(hexData, 16, 2); 109 | const binaryValue: string = decVal.padEnd(8, '0'); 110 | let dataMessage: string = ''; 111 | const diffMsgs: string[] = []; 112 | 113 | // We will traverse in reverse to check the bits set and set messages sent by the device 114 | const binaryValueArr: string[] = binaryValue.match(/.{1,1}/g) || []; 115 | 116 | for (let bitNo = 0; bitNo < binaryValueArr.length; bitNo++) { 117 | const value: number = parseInt('' + binaryValueArr[bitNo]); 118 | if (value === 1) { 119 | if (bitNo in bitsMessages) { 120 | diffMsgs[bitNo] = bitsMessages[bitNo] || ''; 121 | } 122 | } 123 | } 124 | 125 | if ('nobit' in bitsMessages && diffMsgs.length === 0) { 126 | diffMsgs.push(bitsMessages['nobit'] || ''); 127 | } 128 | 129 | if (returnType === 'array') { 130 | return diffMsgs; 131 | } else { 132 | if (diffMsgs.length) { 133 | dataMessage += diffMsgs.toString(); 134 | } 135 | 136 | return dataMessage; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/CommonEvents.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { 3 | DEVICE_INFO, 4 | DOWNLINK, 5 | LINK_QUALITY, 6 | RESET, 7 | SUPERVISORY, 8 | TAMPER, 9 | } from '../src/types/EventTypes'; 10 | 11 | describe('unit | supervisoryEvent', () => { 12 | it.each([ 13 | ['with 2.6v', '1901030126000000000000', '2.6V'], 14 | ['with 2.7v', '1c01030127000000000000', '2.7V'], 15 | ])( 16 | 'decodes a supervisory event %s', 17 | (description, payload, batteryVoltage) => { 18 | const decodedData = decode(payload); 19 | const expectedOutput = {}; 20 | expectedOutput[SUPERVISORY] = { 21 | BatteryLow: true, 22 | ErrorWithLastDownlink: true, 23 | TamperState: false, 24 | TamperSinceLastReset: false, 25 | threshold: 'X-axis over threshold', 26 | battery: batteryVoltage, 27 | accumulationCount: 0, 28 | }; 29 | expect(decodedData).toMatchObject(expectedOutput); 30 | }, 31 | ); 32 | 33 | /** 34 | * Tamper events 35 | */ 36 | it.each([ 37 | ['Open', '190201', 'Open'], 38 | ['Closed', '180200', 'Closed'], 39 | ])('decodes a tamper %s event', (description, payload, expectedState) => { 40 | const decodedData = decode(payload); 41 | const expectedOutput = {}; 42 | expectedOutput[TAMPER] = { 43 | event: expectedState, 44 | }; 45 | expect(decodedData).toMatchObject(expectedOutput); 46 | }); 47 | 48 | /** 49 | * Reset events 50 | */ 51 | it.each([ 52 | ['HW: 2.2, FM: 2.5.13', '10000a2288ad703c', '2.2', '2.5.13'], 53 | ['HW: 1.0, FM: 1.6', '100006100106181e', '1.0', '1.6'], 54 | ['HW: 2.7, FM: 2.2.16', '100011278850703c', '2.7', '2.2.16'], 55 | ])( 56 | 'decodes a RESET: %s event', 57 | (description, payload, hardwareVersion, firmwareVersion) => { 58 | const decodedData = decode(payload); 59 | const expectedOutput = {}; 60 | expectedOutput[RESET] = { 61 | hardwareVersion, 62 | firmwareVersion, 63 | }; 64 | expect(decodedData).toMatchObject(expectedOutput); 65 | }, 66 | ); 67 | 68 | /** 69 | * Downlink events 70 | */ 71 | it.each([ 72 | ['DW: Invalid', '17ff01', 'Invalid', null], 73 | [ 74 | 'Misc error in DOWNLINK', 75 | '18ff0d0600080708070800', 76 | 'Misc error in DOWNLINK', 77 | '06 00 08 07 08 07 08 00', 78 | ], 79 | ['DW: Msg Valid', '15ff02', 'Msg Valid', null], 80 | [ 81 | 'Msg Valid - No Errors', 82 | '16ff0311019e0000000000', 83 | 'Msg Valid - No Errors', 84 | '11 01 9E 00 00 00 00 00', 85 | ], 86 | ])( 87 | 'decodes a Downlink: %s event', 88 | (description, payload, expectedEvent, extendedBytes) => { 89 | const decodedData = decode(payload); 90 | const expectedOutput = {}; 91 | expectedOutput[DOWNLINK] = { 92 | extendedBytes, 93 | event: expectedEvent, 94 | }; 95 | expect(decodedData).toMatchObject(expectedOutput); 96 | }, 97 | ); 98 | 99 | /** 100 | * linkQuality events 101 | */ 102 | it.each([ 103 | ['RSSI: -71, SNR: 6', '1bfb01b906', -71, 6], 104 | ['RSSI: -45, SNR: 7', '12fb00d307', -45, 7], 105 | ['RSSI: -67, SNR: 8', '14fb00bd08', -67, 8], 106 | ])( 107 | 'decodes a linkQuality event %s', 108 | (description, payload: string, rssi, snr) => { 109 | const decodedData = decode(payload); 110 | const expectedOutput = {}; 111 | expectedOutput[LINK_QUALITY] = { 112 | rssi: rssi, 113 | snr: snr, 114 | }; 115 | expect(decodedData).toMatchObject(expectedOutput); 116 | }, 117 | ); 118 | 119 | /** 120 | * Device info events 121 | */ 122 | it.each([ 123 | [ 124 | 'Device Info Message 1 of 1', 125 | '13fa11fc00010100000000', 126 | 'Device Info Message 1 of 1', 127 | 'DOWNLINK_ADVANCED', 128 | '00 01 01 00 00 00 00', 129 | ], 130 | [ 131 | 'Device Info Message 2 of 2', 132 | '12fa22fc00010100000000', 133 | 'Device Info Message 2 of 2', 134 | 'DOWNLINK_ADVANCED', 135 | '00 01 01 00 00 00 00', 136 | ], 137 | ])( 138 | 'decodes a deviceInfo event %s', 139 | ( 140 | description, 141 | payload: string, 142 | expectedEvent, 143 | packetType, 144 | downlinkBytes, 145 | ) => { 146 | const decodedData = decode(payload); 147 | const expectedOutput = {}; 148 | expectedOutput[DEVICE_INFO] = { 149 | event: expectedEvent, 150 | packetType, 151 | downlinkBytes, 152 | }; 153 | expect(decodedData).toMatchObject(expectedOutput); 154 | }, 155 | ); 156 | }); 157 | -------------------------------------------------------------------------------- /src/lib/IdentifyEventType.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ACCELERATION_MOVEMENT_SENSOR, 3 | AIR_TEMP_HUMIDITY_SENSOR, 4 | AMBIENT_LIGHT_SENSOR, 5 | COMPASS_SENSOR, 6 | CONDENSED_FFT, 7 | CONDENSED_FFT_ENERGY, 8 | CONDENSED_FFT_ENERGY_PART, 9 | CONDENSED_FFT_PART, 10 | CONTACT_SENSOR, 11 | CURRENT_SENSOR_STATE, 12 | DEVICE_INFO, 13 | DOOR_WINDOW_SENSOR, 14 | DOWNLINK, 15 | EVENT_UNKNOWN, 16 | GLASS_BREAK_EVENT, 17 | GPS_SENSOR, 18 | HB_VIBRATION_SENSOR, 19 | HIGH_PRECISION_TILT_SENSOR, 20 | HONEYWELL_5800, 21 | INTERNAL_TEMPERATURE, 22 | LINK_QUALITY, 23 | MAGNETOMETER_SENSOR, 24 | PUSH_BUTTON, 25 | RATE_LIMIT_EXCEEDED, 26 | RESET, 27 | SUPERVISORY, 28 | TAMPER, 29 | TANK_LEVEL_SENSOR, 30 | TEMPERATURE_EVENT, 31 | TEST_MODE_EXIT, 32 | THERMOCOUPLE_TEMP, 33 | TILT_EVENT, 34 | ULTRASONIC_LEVEL_SENSOR, 35 | VOLTAGE_SENSOR, 36 | WATER_SENSOR, 37 | WEATHER_STATION, 38 | WIRELESS_420MA_CURRENT_LOOP_SENSOR, 39 | } from '../types/EventTypes'; 40 | import { binaryToDecimal, hexToBinaryDecimal } from './HexConvertor'; 41 | 42 | export function identifyEventType( 43 | firstByteInDecimal: number, 44 | apiHexData: string | null = null, 45 | ) { 46 | let command = 'UNKNOWN'; 47 | 48 | switch (firstByteInDecimal) { 49 | case 0: 50 | command = RESET; 51 | break; 52 | case 1: 53 | command = SUPERVISORY; 54 | break; 55 | case 2: 56 | command = TAMPER; 57 | break; 58 | case 3: 59 | command = DOOR_WINDOW_SENSOR; 60 | break; 61 | //case 4: 62 | // command = 'ACCELEROMETER'; break; 63 | case 5: 64 | command = GLASS_BREAK_EVENT; 65 | break; 66 | case 6: 67 | command = PUSH_BUTTON; 68 | break; 69 | case 7: 70 | command = CONTACT_SENSOR; 71 | break; 72 | case 8: 73 | command = WATER_SENSOR; 74 | break; 75 | case 9: 76 | command = TEMPERATURE_EVENT; 77 | break; 78 | case 10: 79 | command = TILT_EVENT; 80 | break; 81 | case 11: 82 | command = AMBIENT_LIGHT_SENSOR; 83 | break; 84 | case 12: 85 | command = TANK_LEVEL_SENSOR; 86 | break; 87 | case 13: 88 | command = AIR_TEMP_HUMIDITY_SENSOR; 89 | break; 90 | case 14: 91 | command = ACCELERATION_MOVEMENT_SENSOR; 92 | break; 93 | case 15: 94 | command = HIGH_PRECISION_TILT_SENSOR; 95 | break; 96 | case 16: 97 | command = ULTRASONIC_LEVEL_SENSOR; 98 | break; 99 | case 17: 100 | command = WIRELESS_420MA_CURRENT_LOOP_SENSOR; 101 | break; 102 | case 19: 103 | command = THERMOCOUPLE_TEMP; 104 | break; 105 | case 20: 106 | command = VOLTAGE_SENSOR; 107 | break; 108 | case 22: 109 | command = GPS_SENSOR; 110 | break; 111 | case 23: 112 | command = HONEYWELL_5800; 113 | break; 114 | case 24: 115 | command = MAGNETOMETER_SENSOR; 116 | break; 117 | case 25: 118 | command = INTERNAL_TEMPERATURE; 119 | break; 120 | case 26: 121 | command = COMPASS_SENSOR; 122 | break; 123 | case 28: 124 | case 29: 125 | case 30: 126 | case 31: 127 | command = HB_VIBRATION_SENSOR; 128 | break; 129 | case 32: 130 | if (apiHexData == null) { 131 | command = EVENT_UNKNOWN; 132 | } else { 133 | const hexDecimal = hexToBinaryDecimal(apiHexData); 134 | if (hexDecimal.length > 2) { 135 | hexDecimal.shift(); 136 | const binaryVal = '' + hexDecimal![1]!['binary']; 137 | const payloadDefByte = binaryToDecimal(binaryVal.slice(0, 4)); 138 | 139 | if (payloadDefByte == 0) { 140 | command = CONDENSED_FFT_ENERGY; 141 | } else if (payloadDefByte == 1) { 142 | command = CONDENSED_FFT_ENERGY_PART; 143 | } else if (payloadDefByte == 2) { 144 | command = CONDENSED_FFT; 145 | } else if (payloadDefByte == 3) { 146 | command = CONDENSED_FFT_PART; 147 | } 148 | } 149 | } 150 | break; 151 | case 27: 152 | command = WEATHER_STATION; 153 | break; 154 | case 250: 155 | command = DEVICE_INFO; 156 | break; 157 | case 251: 158 | command = LINK_QUALITY; 159 | break; 160 | case 252: 161 | command = RATE_LIMIT_EXCEEDED; 162 | break; 163 | case 253: 164 | command = CURRENT_SENSOR_STATE; 165 | break; 166 | case 254: 167 | command = TEST_MODE_EXIT; 168 | break; 169 | case 255: 170 | command = DOWNLINK; 171 | break; 172 | default: 173 | command = EVENT_UNKNOWN; 174 | } 175 | 176 | return command; 177 | } 178 | -------------------------------------------------------------------------------- /tests/WeatherStationSensor.test.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '../src/parser'; 2 | import { WEATHER_STATION } from '../src/types/EventTypes'; 3 | 4 | describe('unit | WeatherStationSensor', () => { 5 | it.each([ 6 | [ 7 | 'with wind direction', 8 | '181b010027004b00b8', 9 | { value: 39, unit: 'Degrees', directionFrom: 'North' }, 10 | { value: 184, unit: 'Degrees', directionFrom: 'North' }, 11 | { value: 75, unit: 'Degrees', directionFrom: 'North' }, 12 | 'wind_direction', 13 | ], 14 | [ 15 | 'with rain', 16 | '141b04000000000000', 17 | { value: 0, unit: 'Seconds' }, 18 | { value: 0, unit: 'mm' }, 19 | { value: 0, unit: 'mm/h' }, 20 | 'rain', 21 | ], 22 | [ 23 | 'with wind speed', 24 | '121b02000003e70000', 25 | { value: 0, unit: 'm/s' }, 26 | { value: 0, unit: 'm/s' }, 27 | { value: 0.999, unit: 'm/s' }, 28 | 'wind_speed', 29 | ], 30 | ])( 31 | 'decodes weather station sensor: %s', 32 | (description, payload: string, d1, d2, d3, type) => { 33 | const decodedData = decode(payload); 34 | const expectedOutput = {}; 35 | if (type == 'wind_direction') { 36 | expectedOutput[WEATHER_STATION] = { 37 | wind_direction: { 38 | min: d1, 39 | max: d2, 40 | avg: d3, 41 | }, 42 | }; 43 | } 44 | if (type == 'rain') { 45 | expectedOutput[WEATHER_STATION] = { 46 | rain: { 47 | duration: d1, 48 | accumulation: d2, 49 | intensity: d3, 50 | }, 51 | }; 52 | } 53 | if (type == 'wind_speed') { 54 | expectedOutput[WEATHER_STATION] = { 55 | wind_speed: { 56 | min: d1, 57 | max: d2, 58 | avg: d3, 59 | }, 60 | }; 61 | } 62 | expect(decodedData).toMatchObject(expectedOutput); 63 | }, 64 | ); 65 | it.each([ 66 | [ 67 | 'with station stattus', 68 | '161b0600000000007a0e37', 69 | { value: 0, unit: 'C' }, 70 | { value: 0, unit: 'V' }, 71 | { value: 12.2, unit: 'V' }, 72 | { value: 3.639 }, 73 | ], 74 | ])( 75 | 'decodes weather station sensor: %s', 76 | (description, payload: string, d1, d2, d3, d4) => { 77 | const decodedData = decode(payload); 78 | const expectedOutput = {}; 79 | expectedOutput[WEATHER_STATION] = { 80 | station_status: { 81 | temperature: d1, 82 | voltage: d2, 83 | supply: d3, 84 | reference: d4, 85 | }, 86 | }; 87 | expect(decodedData).toMatchObject(expectedOutput); 88 | }, 89 | ); 90 | it.each([ 91 | [ 92 | 'with station status', 93 | '161b0600000000007a0e37', 94 | { value: 0, unit: 'C' }, 95 | { value: 0, unit: 'V' }, 96 | { value: 12.2, unit: 'V' }, 97 | { value: 3.639 }, 98 | ], 99 | ])( 100 | 'decodes weather station sensor: %s', 101 | (description, payload: string, d1, d2, d3, d4) => { 102 | const decodedData = decode(payload); 103 | const expectedOutput = {}; 104 | expectedOutput[WEATHER_STATION] = { 105 | station_status: { 106 | temperature: d1, 107 | voltage: d2, 108 | supply: d3, 109 | reference: d4, 110 | }, 111 | }; 112 | expect(decodedData).toMatchObject(expectedOutput); 113 | }, 114 | ); 115 | it.each([ 116 | [ 117 | 'with pressure_temp_humidity', 118 | '1a1b03002e20cc0187', 119 | { value: 4.6, unit: 'C' }, 120 | { value: 83960, unit: 'Pa' }, 121 | { value: 39.1, unit: '%' }, 122 | ], 123 | ])( 124 | 'decodes weather station sensor: %s', 125 | (description, payload: string, d1, d2, d3) => { 126 | const decodedData = decode(payload); 127 | const expectedOutput = {}; 128 | expectedOutput[WEATHER_STATION] = { 129 | pressure_temp_humidity: { 130 | temperature: d1, 131 | pressure: d2, 132 | humidity: d3, 133 | }, 134 | }; 135 | expect(decodedData).toMatchObject(expectedOutput); 136 | }, 137 | ); 138 | it.each([ 139 | [ 140 | 'with hail', 141 | '151b05000000000000', 142 | { value: 0, unit: 'Seconds' }, 143 | { value: 0, unit: 'mm' }, 144 | { value: 0, unit: 'mm/h' }, 145 | ], 146 | ])( 147 | 'decodes weather station sensor: %s', 148 | (description, payload: string, d1, d2, d3) => { 149 | const decodedData = decode(payload); 150 | const expectedOutput = {}; 151 | expectedOutput[WEATHER_STATION] = { 152 | hail: { 153 | duration: d1, 154 | accumulation: d2, 155 | intensity: d3, 156 | }, 157 | }; 158 | expect(decodedData).toMatchObject(expectedOutput); 159 | }, 160 | ); 161 | it.each([ 162 | ['with invalid data', '171b0004', ['Some data is marked as invalid']], 163 | ])( 164 | 'decodes weather station sensor: %s', 165 | (description, payload: string, error) => { 166 | const decodedData = decode(payload); 167 | const expectedOutput = {}; 168 | expectedOutput[WEATHER_STATION] = { 169 | error_event: error, 170 | }; 171 | expect(decodedData).toMatchObject(expectedOutput); 172 | }, 173 | ); 174 | }); 175 | -------------------------------------------------------------------------------- /src/decoders/Common_Events.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { 3 | binaryToDecimal, 4 | hexToBinaryMessageDecoder, 5 | hexToDecimal, 6 | hexToDecimalMessageDecoder, 7 | signedHexToDecimal, 8 | } from '../lib/HexConvertor'; 9 | import { HexDecimal } from '../types'; 10 | import { binaryStateDecode } from '../lib/CommonDecodings'; 11 | 12 | export function supervisory(hexDecimal: HexDecimal[]) { 13 | // Below array defines what each bit represents in message if it is set to 1 14 | const supervisoryDecode = {}; 15 | // removing zero byte because this represents sensor message type 16 | hexDecimal.splice(0, 1); 17 | 18 | if (0 in hexDecimal) { 19 | const bitsMsgs = [ 20 | 'BatteryLow', 21 | 'ErrorWithLastDownlink', 22 | 'TamperState', 23 | 'TamperSinceLastReset', 24 | ]; 25 | const commonDecoded = hexToBinaryMessageDecoder( 26 | hexDecimal[0]['hex'], 27 | bitsMsgs, 28 | 'array', 29 | ); 30 | if (typeof commonDecoded !== 'string') { 31 | bitsMsgs.forEach((item) => { 32 | supervisoryDecode[item] = commonDecoded.indexOf(item) > -1; 33 | }); 34 | } 35 | } 36 | 37 | if (1 in hexDecimal) { 38 | const byteOne = hexDecimal[1]; 39 | const bitMsgs = [ 40 | 'X-axis over threshold', 41 | 'Y-axis over threshold', 42 | 'Z-axis over threshold', 43 | 'Settling window time expired', 44 | ]; 45 | supervisoryDecode['threshold'] = hexToBinaryMessageDecoder( 46 | byteOne['hex'], 47 | bitMsgs, 48 | 'string', 49 | ); 50 | } 51 | 52 | const batteryLevelIndex = 2; 53 | if (batteryLevelIndex in hexDecimal) { 54 | supervisoryDecode['battery'] = 55 | Number(parseInt(hexDecimal[batteryLevelIndex]['hex']) / 10).toFixed(1) + 56 | 'V'; 57 | } 58 | 59 | /* 60 | * Event accumulation count, Byte 7-8 61 | */ 62 | if (7 in hexDecimal && 8 in hexDecimal) { 63 | supervisoryDecode['accumulationCount'] = hexToDecimal( 64 | hexDecimal[7]['hex'] + hexDecimal[8]['hex'], 65 | ); 66 | } 67 | 68 | return supervisoryDecode; 69 | } 70 | 71 | export function tamperDetect(hexDecimal: HexDecimal[]) { 72 | const byteZeroHex = hexDecimal[1]; 73 | const dataMessage = {}; 74 | const bitMsgs = { 75 | 0: 'Open', 76 | nobit: 'Closed', 77 | }; 78 | dataMessage['event'] = binaryStateDecode(byteZeroHex, bitMsgs); 79 | return dataMessage; 80 | } 81 | 82 | /** 83 | * Decodes reset events 84 | * @param hexDecimal 85 | */ 86 | export function reset(hexDecimal: HexDecimal[]) { 87 | let hardwareVersion, firmwareVersion; 88 | if (hexDecimal.length >= 5) { 89 | const hardwareVersionByte = hexDecimal[2]; 90 | const hardwareVersionMajorVer = hardwareVersionByte['hex'][0]; 91 | const hardwareVersionMinorVer = hardwareVersionByte['hex'][1]; 92 | 93 | const firmwareByteOne: string = hexDecimal[3]['binary']; 94 | const firmwareByteTwo: string = hexDecimal[4]['binary']; 95 | if (firmwareByteOne[0] == 1) { 96 | const finalString = firmwareByteOne.slice(1) + firmwareByteTwo; 97 | const decodedVersions = finalString.split(/(.{5})/).filter((O) => O); 98 | firmwareVersion = 99 | binaryToDecimal(decodedVersions[0]) + 100 | '.' + 101 | binaryToDecimal(decodedVersions[1]) + 102 | '.' + 103 | binaryToDecimal(decodedVersions[2]); 104 | } else { 105 | firmwareVersion = 106 | 255 == hexDecimal[3]['decimal'] || 0 == hexDecimal[3]['decimal'] 107 | ? '-' 108 | : hexDecimal[3]['decimal'] + '.' + hexDecimal[4]['decimal']; 109 | } 110 | 111 | hardwareVersion = 112 | 255 == hardwareVersionByte['decimal'] || 113 | 0 == hardwareVersionByte['decimal'] 114 | ? '-' 115 | : hexToDecimal(hardwareVersionMajorVer) + 116 | '.' + 117 | hexToDecimal(hardwareVersionMinorVer); 118 | } 119 | return { 120 | hardwareVersion: hardwareVersion, 121 | firmwareVersion: firmwareVersion, 122 | }; 123 | } 124 | 125 | /** 126 | * Decodes downlink events 127 | * @param hexDecimal 128 | */ 129 | export function downlink(hexDecimal: HexDecimal[]) { 130 | const byteZeroDecimal = hexDecimal[1]['decimal']; 131 | const dataMessage = {}; 132 | const bitMsgs = { 133 | nobit: 'No Message Received', 134 | 1: 'Invalid', 135 | 2: 'Msg Valid', 136 | 3: 'Msg Valid - No Errors', 137 | 4: 'Invalid - Command not supported', 138 | 5: 'Invalid - reserved bits and bytes of downlink must be zero', 139 | 6: 'Invalid - byte value of downlink is not in range', 140 | 7: 'Invalid - unsupported DOWNLINK option byte', 141 | 8: "Invalid - this lora zone doesn't support confirmed uplink messages", 142 | 9: 'Invalid - for confirmed messages, no more than 8 retries are allowed', 143 | 10: 'Invalid - for unconfirmed messages, no more than 1 retry is allowed', 144 | 11: 'valid port range for lora is 1 - 223, 0 means leave it default', 145 | 12: 'The DOWNLINK is for a Sensor type not supported on this Device', 146 | 13: 'Misc error in DOWNLINK', 147 | 14: 'Invalid - Link Quality period should be greater than 60 Minutes', 148 | }; 149 | dataMessage['event'] = hexToDecimalMessageDecoder(byteZeroDecimal, bitMsgs); 150 | dataMessage['extendedBytes'] = null; 151 | if (hexDecimal[1]['decimal'] > 2) { 152 | // Extended downlink of 11 bytes 153 | const downlinkMsg = []; 154 | for (let i = 2; i < hexDecimal.length; i++) { 155 | downlinkMsg.push(hexDecimal[i]['hex'].toUpperCase()); 156 | } 157 | dataMessage['extendedBytes'] = downlinkMsg.join(' '); 158 | } 159 | return dataMessage; 160 | } 161 | 162 | /** 163 | * Test event 164 | */ 165 | export function testEvent() { 166 | return { TEST_EVENT: 'Test Event' }; 167 | } 168 | 169 | /** 170 | * Link quality 171 | * @param hexDecimal 172 | */ 173 | export function linkQuality(hexDecimal: HexDecimal[]) { 174 | const rssiValue = signedHexToDecimal(hexDecimal[2]['hex']); 175 | const snrValue = signedHexToDecimal(hexDecimal[3]['hex']); 176 | return { rssi: rssiValue, snr: snrValue }; 177 | } 178 | 179 | /** 180 | * Rate limit exceeded 181 | */ 182 | export function rateLimitExceeded() { 183 | return { event: 'Rate limit exceeded' }; 184 | } 185 | 186 | /** 187 | * Manufacturing test message 188 | */ 189 | export function manufacturingTestMessage() { 190 | return { event: 'Manufacturing test message' }; 191 | } 192 | 193 | /** 194 | * Device info 195 | * @param hexDecimal 196 | */ 197 | export function deviceInfo(hexDecimal: HexDecimal[]) { 198 | let subDataMessage = ''; 199 | 200 | let checkIfAllFF = false; 201 | let packetType = null; 202 | const downlinkBytes = []; 203 | for (let i = 1; i < hexDecimal.length; i++) { 204 | if (255 != hexDecimal[i]['decimal']) { 205 | checkIfAllFF = false; 206 | break; 207 | } 208 | } 209 | 210 | if (!checkIfAllFF) { 211 | const totalPackets = binaryToDecimal(hexDecimal[1]['binary'].slice(-4)); 212 | const curPacketCount = binaryToDecimal(hexDecimal[1]['binary'].slice(0, 4)); 213 | 214 | if (curPacketCount > totalPackets) { 215 | switch (hexDecimal[1]['decimal']) { 216 | case 16: 217 | packetType = 'CONFIG'; 218 | break; 219 | case 32: 220 | packetType = 'MFG_LOT'; 221 | break; 222 | } 223 | } else { 224 | subDataMessage = curPacketCount + ' of ' + totalPackets; 225 | switch (hexDecimal[2]['decimal']) { 226 | case 252: 227 | packetType = 'DOWNLINK_ADVANCED'; 228 | break; 229 | case 1: 230 | packetType = 'DOWNLINK_GENERAL'; 231 | break; 232 | } 233 | } 234 | 235 | for (let i = 3; i < hexDecimal.length; i++) { 236 | downlinkBytes.push(hexDecimal[i]['hex'].toUpperCase()); 237 | } 238 | } else { 239 | subDataMessage = '\nDownlink Not Initialized Yet'; 240 | } 241 | 242 | return { 243 | event: 'Device Info Message ' + subDataMessage, 244 | packetType, 245 | downlinkBytes: downlinkBytes.join(' '), 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import { HexDecimal } from './types'; 2 | import { identifyEventType } from './lib/IdentifyEventType'; 3 | import { binaryToDecimal, hexToBinaryDecimal } from './lib/HexConvertor'; 4 | import { 5 | ACCELERATION_MOVEMENT_SENSOR, 6 | AIR_TEMP_HUMIDITY_SENSOR, 7 | AMBIENT_LIGHT_SENSOR, 8 | COMPASS_SENSOR, 9 | CONDENSED_FFT, 10 | CONDENSED_FFT_ENERGY, 11 | CONTACT_SENSOR, 12 | DEVICE_INFO, 13 | DOOR_WINDOW_SENSOR, 14 | DOWNLINK, 15 | GLASS_BREAK_EVENT, 16 | GPS_SENSOR, 17 | HB_VIBRATION_SENSOR, 18 | HIGH_PRECISION_TILT_SENSOR, 19 | HONEYWELL_5800, 20 | INTERNAL_TEMPERATURE, 21 | LINK_QUALITY, 22 | MAGNETOMETER_SENSOR, 23 | PUSH_BUTTON, 24 | RATE_LIMIT_EXCEEDED, 25 | RESET, 26 | SUPERVISORY, 27 | TAMPER, 28 | TANK_LEVEL_SENSOR, 29 | TEMPERATURE_EVENT, 30 | TEST_MESSAGE, 31 | TEST_MODE_EXIT, 32 | THERMOCOUPLE_TEMP, 33 | TILT_EVENT, 34 | ULTRASONIC_LEVEL_SENSOR, 35 | VOLTAGE_SENSOR, 36 | WATER_SENSOR, 37 | WEATHER_STATION, 38 | WIRELESS_420MA_CURRENT_LOOP_SENSOR, 39 | } from './types/EventTypes'; 40 | import Temperature_Event from './decoders/Temperature_Event'; 41 | import { 42 | deviceInfo, 43 | downlink, 44 | linkQuality, 45 | manufacturingTestMessage, 46 | rateLimitExceeded, 47 | reset, 48 | supervisory, 49 | tamperDetect, 50 | testEvent, 51 | } from './decoders/Common_Events'; 52 | import DoorWindow from './decoders/DoorWindow'; 53 | import GlassBreak from './decoders/GlassBreak'; 54 | import ContactSensor from './decoders/ContactSensor'; 55 | import SinglePushButton from './decoders/SinglePushButton'; 56 | import TankLevelSensor from './decoders/TankLevelSensor'; 57 | import AmbientLightSensor from './decoders/AmbientLightSensor'; 58 | import WaterSensor from './decoders/WaterSensor'; 59 | import CompassSensor from './decoders/CompassSensor'; 60 | import GpsDevice from './decoders/GpsDevice'; 61 | import AccelerationMovementSensor from './decoders/AccelerationMovementSensor'; 62 | import TiltSensor from './decoders/TiltSensor'; 63 | import VoltageSensor from './decoders/VoltageSensor'; 64 | import AirTemperatureAndHumiditySensor from './decoders/AirTemperatureAndHumiditySensor'; 65 | import HoneywellFiveEightZeroZero from './decoders/HoneywellFiveEightZeroZero'; 66 | import HighPrecisionTiltSensor from './decoders/HighPrecisionTiltSensor'; 67 | import UltraSonicSensor from './decoders/UltraSonicSensor'; 68 | import HBVibrationSensor from './decoders/HBVibrationSensor'; 69 | import CurrentLoopSensor from './decoders/CurrentLoopSensor'; 70 | import ThermocoupleTemperatureSensor from './decoders/ThermocoupleTemperatureSensor'; 71 | import WeatherStationSensor from './decoders/WeatherStationSensor'; 72 | import MagnetometerSensor from './decoders/MagnetometerSensor'; 73 | import CondensedFFt from './decoders/HighFreqVibrationSensor/CondensedFFt'; 74 | import CondensedFftEnergy from './decoders/HighFreqVibrationSensor/CondensedFftEnergy'; 75 | 76 | class RadioBridgeDecoder { 77 | private hexPayloads: string[] = []; 78 | 79 | constructor(...args: string[]) { 80 | if (args.length < 1) { 81 | throw new Error('Atleast one payload is required to decode.'); 82 | } 83 | for (let i = 0, arg; (arg = args[i]); i++) { 84 | this.hexPayloads.push(arg); 85 | } 86 | } 87 | 88 | convert() { 89 | const hexDecimal = hexToBinaryDecimal(this.hexPayloads[0]); 90 | const packetCounter = hexDecimal![0]!['decimal']; 91 | const protocolVersion = hexDecimal![0]!['binary'].slice(0, 4); 92 | 93 | hexDecimal.splice(0, 1); 94 | 95 | if (!(0 in hexDecimal)) { 96 | return {}; 97 | } 98 | 99 | const eventType = identifyEventType( 100 | hexDecimal[0]['decimal'], 101 | this.hexPayloads[0], 102 | ); 103 | const eventDecoded = this.mapConversion(eventType, hexDecimal); 104 | return { 105 | protocol: binaryToDecimal(protocolVersion), 106 | counter: packetCounter, 107 | type: eventType, 108 | ...eventDecoded, 109 | }; 110 | } 111 | 112 | mapConversion(eventType: string, hexDecimal: Array) { 113 | const data: Record = {}; 114 | switch (eventType) { 115 | case RESET: 116 | data[RESET] = reset(hexDecimal); 117 | break; 118 | case DOWNLINK: 119 | data[DOWNLINK] = downlink(hexDecimal); 120 | break; 121 | case TAMPER: 122 | data[TAMPER] = tamperDetect(hexDecimal); 123 | break; 124 | case SUPERVISORY: 125 | data[SUPERVISORY] = supervisory(hexDecimal); 126 | break; 127 | case TEST_MESSAGE: 128 | data[TEST_MESSAGE] = testEvent(); 129 | break; 130 | case LINK_QUALITY: 131 | data[LINK_QUALITY] = linkQuality(hexDecimal); 132 | break; 133 | case RATE_LIMIT_EXCEEDED: 134 | data[RATE_LIMIT_EXCEEDED] = rateLimitExceeded(); 135 | break; 136 | case TEST_MODE_EXIT: 137 | data[TEST_MODE_EXIT] = manufacturingTestMessage(); 138 | break; 139 | case DEVICE_INFO: 140 | data[DEVICE_INFO] = deviceInfo(hexDecimal); 141 | break; 142 | case DOOR_WINDOW_SENSOR: 143 | data[DOOR_WINDOW_SENSOR] = DoorWindow(hexDecimal); 144 | break; 145 | case GLASS_BREAK_EVENT: 146 | data[GLASS_BREAK_EVENT] = GlassBreak(); 147 | break; 148 | case CONTACT_SENSOR: 149 | data[CONTACT_SENSOR] = ContactSensor(hexDecimal); 150 | break; 151 | case TEMPERATURE_EVENT: 152 | data[TEMPERATURE_EVENT] = Temperature_Event(hexDecimal); 153 | break; 154 | case PUSH_BUTTON: 155 | data[PUSH_BUTTON] = SinglePushButton(hexDecimal); 156 | break; 157 | case TANK_LEVEL_SENSOR: 158 | data[TANK_LEVEL_SENSOR] = TankLevelSensor(hexDecimal); 159 | break; 160 | case AMBIENT_LIGHT_SENSOR: 161 | data[AMBIENT_LIGHT_SENSOR] = AmbientLightSensor(hexDecimal); 162 | break; 163 | case WATER_SENSOR: 164 | data[WATER_SENSOR] = WaterSensor(hexDecimal); 165 | break; 166 | case COMPASS_SENSOR: 167 | data[COMPASS_SENSOR] = CompassSensor(hexDecimal); 168 | break; 169 | case GPS_SENSOR: 170 | data[GPS_SENSOR] = GpsDevice(hexDecimal); 171 | break; 172 | case ACCELERATION_MOVEMENT_SENSOR: 173 | data[ACCELERATION_MOVEMENT_SENSOR] = 174 | AccelerationMovementSensor(hexDecimal); 175 | break; 176 | case TILT_EVENT: 177 | data[TILT_EVENT] = TiltSensor(hexDecimal); 178 | break; 179 | case VOLTAGE_SENSOR: 180 | data[VOLTAGE_SENSOR] = VoltageSensor(hexDecimal); 181 | break; 182 | case HONEYWELL_5800: 183 | data[HONEYWELL_5800] = HoneywellFiveEightZeroZero(hexDecimal); 184 | break; 185 | case HIGH_PRECISION_TILT_SENSOR: 186 | data[HIGH_PRECISION_TILT_SENSOR] = HighPrecisionTiltSensor(hexDecimal); 187 | break; 188 | case ULTRASONIC_LEVEL_SENSOR: 189 | data[ULTRASONIC_LEVEL_SENSOR] = UltraSonicSensor(hexDecimal); 190 | break; 191 | case WIRELESS_420MA_CURRENT_LOOP_SENSOR: 192 | data[WIRELESS_420MA_CURRENT_LOOP_SENSOR] = 193 | CurrentLoopSensor(hexDecimal); 194 | break; 195 | case THERMOCOUPLE_TEMP: 196 | data[THERMOCOUPLE_TEMP] = ThermocoupleTemperatureSensor(hexDecimal); 197 | break; 198 | case WEATHER_STATION: 199 | data[WEATHER_STATION] = WeatherStationSensor(hexDecimal); 200 | break; 201 | case MAGNETOMETER_SENSOR: 202 | data[MAGNETOMETER_SENSOR] = MagnetometerSensor(hexDecimal); 203 | break; 204 | case HB_VIBRATION_SENSOR: 205 | data[HB_VIBRATION_SENSOR] = HBVibrationSensor(hexDecimal); 206 | break; 207 | case AIR_TEMP_HUMIDITY_SENSOR: 208 | data[AIR_TEMP_HUMIDITY_SENSOR] = 209 | AirTemperatureAndHumiditySensor(hexDecimal); 210 | break; 211 | case INTERNAL_TEMPERATURE: 212 | data[INTERNAL_TEMPERATURE] = 213 | AirTemperatureAndHumiditySensor(hexDecimal); 214 | break; 215 | case CONDENSED_FFT: 216 | data[CONDENSED_FFT] = CondensedFFt(hexDecimal, this.hexPayloads); 217 | break; 218 | case CONDENSED_FFT_ENERGY: 219 | data[CONDENSED_FFT_ENERGY] = CondensedFftEnergy( 220 | hexDecimal, 221 | this.hexPayloads, 222 | ); 223 | break; 224 | } 225 | 226 | return data; 227 | } 228 | } 229 | 230 | export function decode(...args: string[]) { 231 | return new RadioBridgeDecoder(...args).convert(); 232 | } 233 | -------------------------------------------------------------------------------- /src/decoders/WeatherStationSensor.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { hexToDecimal, signedHexToDecimal } from '../lib/HexConvertor'; 4 | import type { HexDecimal } from '../types'; 5 | // Assuming sensorState is a boolean variable 6 | export default function (hexDecimal: HexDecimal[]) { 7 | const dataMessage = {}; 8 | if (1 in hexDecimal) { 9 | const byteOneHexData = hexDecimal[1]['decimal']; 10 | switch (byteOneHexData) { 11 | case 0: 12 | if (2 in hexDecimal) { 13 | dataMessage['error_event'] = errorEventDecode(hexDecimal); 14 | } 15 | break; 16 | case 1: 17 | if (7 in hexDecimal) { 18 | dataMessage['wind_direction'] = windDirectionDecode(hexDecimal); 19 | } 20 | break; 21 | case 2: 22 | if (7 in hexDecimal) { 23 | dataMessage['wind_speed'] = windSpeedDecode(hexDecimal); 24 | } 25 | break; 26 | case 3: 27 | if (7 in hexDecimal) { 28 | dataMessage['pressure_temp_humidity'] = 29 | pressureTempHumidity(hexDecimal); 30 | } 31 | break; 32 | case 4: 33 | if (7 in hexDecimal) { 34 | dataMessage['rain'] = rainDecode(hexDecimal); 35 | } 36 | break; 37 | case 5: 38 | if (7 in hexDecimal) { 39 | dataMessage['hail'] = hailDecode(hexDecimal); 40 | } 41 | break; 42 | case 6: 43 | if (7 in hexDecimal) { 44 | dataMessage['station_status'] = stationStatusDecode(hexDecimal); 45 | } 46 | break; 47 | } 48 | } 49 | return dataMessage; 50 | } 51 | 52 | function errorEventDecode(hexDecimal: [HexDecimal]) { 53 | const errorMsgs: string[] = []; 54 | const byteTwoHexData = hexDecimal[2]?.binary; 55 | 56 | if (byteTwoHexData) { 57 | if (1 == byteTwoHexData[7]) { 58 | errorMsgs.push('Timeout waiting for response from weather station'); 59 | } 60 | if (1 == byteTwoHexData[6]) { 61 | errorMsgs.push('CRC Error'); 62 | } 63 | if (1 == byteTwoHexData[5]) { 64 | errorMsgs.push('Some data is marked as invalid'); 65 | } 66 | } 67 | 68 | return errorMsgs; 69 | } 70 | 71 | function windDirectionDecode(hexDecimal: [HexDecimal]) { 72 | const byteTwoHexData = hexDecimal[2]?.hex || '00'; 73 | const byteThreeHexData = hexDecimal[3]?.hex || '00'; 74 | const windDirectionMin = hexToDecimal(byteTwoHexData + byteThreeHexData); 75 | 76 | const byteFourHexData = hexDecimal[4]?.hex || '00'; 77 | const byteFiveHexData = hexDecimal[5]?.hex || '00'; 78 | const windDirectionAvg = hexToDecimal(byteFourHexData + byteFiveHexData); 79 | 80 | const byteSixHexData = hexDecimal[6]?.hex || '00'; 81 | const byteSevenHexData = hexDecimal[7]?.hex || '00'; 82 | const windDirectionMax = hexToDecimal(byteSixHexData + byteSevenHexData); 83 | 84 | const res: Record = {}; 85 | res['min'] = { 86 | value: windDirectionMin, 87 | unit: 'Degrees', 88 | directionFrom: 'North', 89 | }; 90 | res['max'] = { 91 | value: windDirectionMax, 92 | unit: 'Degrees', 93 | directionFrom: 'North', 94 | }; 95 | res['avg'] = { 96 | value: windDirectionAvg, 97 | unit: 'Degrees', 98 | directionFrom: 'North', 99 | }; 100 | return res; 101 | } 102 | 103 | function windSpeedDecode(hexDecimal: [HexDecimal]) { 104 | const byteTwoHexData = hexDecimal[2]?.hex || '0000'; 105 | const byteThreeHexData = hexDecimal[3]?.hex || '0000'; 106 | let windSpeedMin = hexToDecimal(byteTwoHexData + byteThreeHexData); 107 | windSpeedMin = windSpeedMin / 1000; 108 | 109 | const byteFourHexData = hexDecimal[4]?.hex || '0000'; 110 | const byteFiveHexData = hexDecimal[5]?.hex || '0000'; 111 | let windSpeedAvg = hexToDecimal(byteFourHexData + byteFiveHexData); 112 | windSpeedAvg = windSpeedAvg / 1000; 113 | 114 | const byteSixHexData = hexDecimal[6]?.hex || '0000'; 115 | const byteSevenHexData = hexDecimal[7]?.hex || '0000'; 116 | let windSpeedMax = hexToDecimal(byteSixHexData + byteSevenHexData); 117 | windSpeedMax = windSpeedMax / 1000; 118 | 119 | const res: Record = {}; 120 | res['min'] = { value: windSpeedMin, unit: 'm/s' }; 121 | res['max'] = { value: windSpeedMax, unit: 'm/s' }; 122 | res['avg'] = { value: windSpeedAvg, unit: 'm/s' }; 123 | return res; 124 | } 125 | 126 | function pressureTempHumidity(hexDecimal: [HexDecimal]) { 127 | const byteTwoHexData = hexDecimal[2]?.hex || '0000'; 128 | const byteThreeHexData = hexDecimal[3]?.hex || '0000'; 129 | let temperature = signedHexToDecimal(byteTwoHexData + byteThreeHexData, 16); 130 | temperature = temperature / 10; 131 | 132 | const byteFourHexData = hexDecimal[4]?.hex || '0000'; 133 | const byteFiveHexData = hexDecimal[5]?.hex || '0000'; 134 | let pressure = hexToDecimal(byteFourHexData + byteFiveHexData); 135 | pressure = pressure * 10; 136 | 137 | const byteSixHexData = hexDecimal[6]?.hex || '0000'; 138 | const byteSevenHexData = hexDecimal[7]?.hex || '0000'; 139 | let humidity = hexToDecimal(byteSixHexData + byteSevenHexData); 140 | humidity = humidity / 10; 141 | 142 | const res: Record = {}; 143 | res['temperature'] = { value: temperature, unit: 'C' }; 144 | res['pressure'] = { value: Number(pressure.toFixed(0)), unit: 'Pa' }; 145 | res['humidity'] = { value: humidity, unit: '%' }; 146 | return res; 147 | } 148 | 149 | function rainDecode(hexDecimal: [HexDecimal]) { 150 | const byteTwoHexData = hexDecimal[2]?.hex || '0000'; 151 | const byteThreeHexData = hexDecimal[3]?.hex || '0000'; 152 | const rainDuration = hexToDecimal(byteTwoHexData + byteThreeHexData); 153 | 154 | const byteFourHexData = hexDecimal[4]?.hex || '0000'; 155 | const byteFiveHexData = hexDecimal[5]?.hex || '0000'; 156 | let rainAccumulation = hexToDecimal(byteFourHexData + byteFiveHexData); 157 | rainAccumulation = rainAccumulation / 100; 158 | 159 | const byteSixHexData = hexDecimal[6]?.hex || '0000'; 160 | const byteSevenHexData = hexDecimal[7]?.hex || '0000'; 161 | let rainIntensity = hexToDecimal(byteSixHexData + byteSevenHexData); 162 | rainIntensity = rainIntensity / 10; 163 | 164 | const res: Record = {}; 165 | res['duration'] = { value: rainDuration, unit: 'Seconds' }; 166 | res['accumulation'] = { value: rainAccumulation, unit: 'mm' }; 167 | res['intensity'] = { value: rainIntensity, unit: 'mm/h' }; 168 | return res; 169 | } 170 | 171 | function hailDecode(hexDecimal: [HexDecimal]) { 172 | const byteTwoHexData = hexDecimal[2]?.hex || '0000'; 173 | const byteThreeHexData = hexDecimal[3]?.hex || '0000'; 174 | const hailDuration = hexToDecimal(byteTwoHexData + byteThreeHexData); 175 | 176 | const byteFourHexData = hexDecimal[4]?.hex || '0000'; 177 | const byteFiveHexData = hexDecimal[5]?.hex || '0000'; 178 | let hailAccumulation = hexToDecimal(byteFourHexData + byteFiveHexData); 179 | hailAccumulation = hailAccumulation / 10; 180 | 181 | const byteSixHexData = hexDecimal[6]?.hex || '0000'; 182 | const byteSevenHexData = hexDecimal[7]?.hex || '0000'; 183 | let hailIntensity = hexToDecimal(byteSixHexData + byteSevenHexData); 184 | hailIntensity = hailIntensity / 10; 185 | 186 | const res: Record = {}; 187 | res['duration'] = { value: hailDuration, unit: 'Seconds' }; 188 | res['accumulation'] = { value: hailAccumulation, unit: 'mm' }; 189 | res['intensity'] = { value: hailIntensity, unit: 'mm/h' }; 190 | 191 | return res; 192 | } 193 | function stationStatusDecode(hexDecimal: [HexDecimal]) { 194 | const byteTwoHexData = hexDecimal[2]?.hex || '0000'; 195 | const byteThreeHexData = hexDecimal[3]?.hex || '0000'; 196 | let heatingTemperature = signedHexToDecimal( 197 | byteTwoHexData + byteThreeHexData, 198 | 16, 199 | ); 200 | heatingTemperature = heatingTemperature / 10; 201 | 202 | const byteFourHexData = hexDecimal[4]?.hex || '0000'; 203 | const byteFiveHexData = hexDecimal[5]?.hex || '0000'; 204 | let heatingVoltage = hexToDecimal(byteFourHexData + byteFiveHexData); 205 | heatingVoltage = heatingVoltage / 10; 206 | 207 | const byteSixHexData = hexDecimal[6]?.hex || '0000'; 208 | const byteSevenHexData = hexDecimal[7]?.hex || '0000'; 209 | let supplyVoltage = hexToDecimal(byteSixHexData + byteSevenHexData); 210 | supplyVoltage = supplyVoltage / 10; 211 | 212 | let referenceVoltage: string | number = 'N/A'; 213 | 214 | if (9 in hexDecimal) { 215 | const byteEightHexData = hexDecimal[8]?.hex || '0000'; 216 | const byteNineHexData = hexDecimal[9]?.hex || '0000'; 217 | referenceVoltage = hexToDecimal(byteEightHexData + byteNineHexData); 218 | referenceVoltage = referenceVoltage / 1000; 219 | } 220 | 221 | const res: Record = { 222 | temperature: { value: heatingTemperature, unit: 'C' }, 223 | voltage: { value: heatingVoltage, unit: 'V' }, 224 | supply: { value: supplyVoltage, unit: 'V' }, 225 | reference: { value: referenceVoltage }, 226 | }; 227 | 228 | return res; 229 | } 230 | --------------------------------------------------------------------------------