diff --git a/config.json b/config.json index 8bf2238..1f204c8 100644 --- a/config.json +++ b/config.json @@ -26,7 +26,7 @@ }, "emPuppettering": { "enabled": true, - "duplexTimeout": 360 + "duplexTimeout": "250" }, "emessage": { "enabled": true, @@ -50,6 +50,10 @@ "username": "test", "password": "testsucks", "duplexTimeout": 300 + }, + "pnet": { + "enabled": true, + "duplexTimeout": 10 } }, "pagers": { diff --git a/html_config/index.html b/html_config/index.html index 7e16a9f..a490f99 100644 --- a/html_config/index.html +++ b/html_config/index.html @@ -325,6 +325,8 @@ deviceType: [ { k: 'Generic', v: 'generic' }, { k: 'Birdy Slim (IoT)', v: 'birdyslim' }, + { k: 'MXP600', v: 'mxp600' }, + { k: 'Generic Tetra', v: 'tetra-generic' }, ], connectorTypes: [ { k: 'Dummy', v: 'dummy' }, @@ -339,6 +341,8 @@ { k: 'e*2wayS alarmManager', v: 'em-a-twoways' }, { k: 'LoRaWAN TTNv3', v: 'lorawan' }, + + { k: 'pNET TETRA', v: 'pnet' }, ], configData: { boskrypt: { diff --git a/html_main/index.html b/html_main/index.html index c0f52be..abc4e93 100644 --- a/html_main/index.html +++ b/html_main/index.html @@ -217,6 +217,8 @@ Last LoRaWAN Packet: deviceType: [ {k: 'Generic', v: 'generic'}, {k: 'Birdy Slim (IoT)', v: 'birdyslim'}, + { k: 'MXP600', v: 'mxp600' }, + { k: 'Generic Tetra', v: 'tetra-generic' }, ], connectorTypes: [ {k: 'Dummy', v: 'dummy'}, @@ -231,6 +233,8 @@ Last LoRaWAN Packet: {k: 'e*2wayS alarmManager', v: 'em-a-twoways'}, {k: 'LoRaWAN TTNv3', v: 'lorawan'}, + + { k: 'pNET TETRA', v: 'pnet' }, ], newMSGData: { diff --git a/index.js b/index.js index 58de35b..7510ca0 100644 --- a/index.js +++ b/index.js @@ -36,6 +36,9 @@ if (!!config.connectors.emPuppettering && config.connectors.emPuppettering.enabl if (!!config.connectors.ecityruf && config.connectors.pagernetzAT.enabled === true) { types.ConnectorRegistry.register(new types.Connectors.PagernetzConnetorAT(connection)) } +if (!!config.connectors.ecityruf && config.connectors.pnet.enabled === true) { + types.ConnectorRegistry.register(new types.Connectors.pNetConnector()) +} types.ConnectorRegistry.register(new types.Connectors.DummyConnector()) @@ -43,6 +46,9 @@ types.DeviceRegistry.register(new types.devices.GenericPager()) types.DeviceRegistry.register(new types.devices.BirdySlim()) types.DeviceRegistry.register(new types.devices.EMessage2Ways()) types.DeviceRegistry.register(new types.devices.Skyper()) +//new tetra devices +types.DeviceRegistry.register(new types.devices.TETRA_Generic()) +types.DeviceRegistry.register(new types.devices.TETRA_MXP600()) const express = require('express') const { MessageManager } = require('./types') diff --git a/ttnv3.uplink.birdyslim.js b/ttnv3.uplink.birdyslim.js index 96b4e2e..1e8578d 100644 --- a/ttnv3.uplink.birdyslim.js +++ b/ttnv3.uplink.birdyslim.js @@ -1,125 +1,125 @@ function Decoder(bytes, port) { - var data = {} - function bytesToString(a) { - return a.map(function(x){ return String.fromCharCode(x) }).join('') // because very OLD js parser - } - switch (port) { - case 1: // Technical Received Acknowledgment '|5||C|' - // 31 32 33 34 35 20 - data.type = 'ack' - data.ack = 'recv' - data.msgid = bytesToString(bytes.splice(0,5)) - data.rssi = bytes[0] - break; - case 2: // Read and Operational Acknowledgment 'AA|5|' and Operational '33|5|' - // AA 31 32 33 34 35 - // operational ack with Payload "test"(maybe you should use a single byte) - // 33 31 32 33 34 35 54 65 73 74 - data.type = 'ack' - data.ack = bytes.length == 5 ? 'read' : 'operational' - data.msgid = bytesToString(bytes.splice(0,5)) - if (data.ack === 'operational') { - data.operationalData = bytes + var data = {} + function bytesToString(a) { + return a.map(function(x){ return String.fromCharCode(x) }).join('') // because very OLD js parser + } + switch (port) { + case 1: // Technical Received Acknowledgment '|5||C|' + // 31 32 33 34 35 20 + data.type = 'ack' + data.ack = 'recv' + data.msgid = bytesToString(bytes.splice(0,5)) + data.rssi = -bytes[0] / 2 + break; + case 2: // Read and Operational Acknowledgment 'AA|5|' and Operational '33|5|' + // AA 31 32 33 34 35 + // operational ack with Payload "test"(maybe you should use a single byte) + // 33 31 32 33 34 35 54 65 73 74 + data.type = 'ack' + data.ack = bytes.length == 5 ? 'read' : 'operational' + data.msgid = bytesToString(bytes.splice(0,5)) + if (data.ack === 'operational') { + data.operationalData = bytes + } + break; + case 3: // Status & Canned Messages + // Canned Message '01' --- '05', single Hex Number, incrementing + // Status '|Z|' or if no CenterSelection '00' + + if (bytes.length == 1) { + data.type = 'cannedMessage' + data.cannedMessage = bytes[0] * 1 + } else { + data.type = 'status' + data.status = bytes[0] * 1 + data.selection = bytes[1] * 1 + } + break; + case 4: // Battery and Power Notifications + // Startup 'FF' + // Shutdown 'EE' + // Low Battery 'FD' + // ChargingOn '|V|CC' + // ChargingOff'|V|BB' + if (bytes.length == 1) { + data.type = 'power' + if (bytes[0] >= 0xEE) { + data.poweredOn = bytes[0]== 0xFF } - break; - case 3: // Status & Canned Messages - // Canned Message '01' --- '05', single Hex Number, incrementing - // Status '|Z|' or if no CenterSelection '00' - - if (bytes.length == 1) { - data.type = 'cannedMessage' - data.cannedMessage = bytes[0] * 1 - } else { - data.type = 'status' - data.status = bytes[0] * 1 - data.selection = bytes[1] * 1 + if (bytes[0] == 0xFD) { + data.type = 'low_battery' } - break; - case 4: // Battery and Power Notifications - // Startup 'FF' - // Shutdown 'EE' - // Low Battery 'FD' - // ChargingOn '|V|CC' - // ChargingOff'|V|BB' - if (bytes.length == 1) { - data.type = 'power' - if (bytes[0] >= 0xEE) { - data.poweredOn = bytes[0]== 0xFF - } - if (bytes[0] == 0xFD) { - data.type = 'low_battery' - } - if (bytes[0] <= 0xAA) { - data.type = 'battery' - data.battery = bytes[0] * 1 - data.isCharging = false - } - } else { + if (bytes[0] <= 0xAA) { data.type = 'battery' data.battery = bytes[0] * 1 - data.isCharging = bytes[1] == 0xCC + data.isCharging = false } - break; - case 5: // OTAStatus + LoneWorker/SOS Trigger + GPS Tracking Port '|G|' - // GPS Tracking '|G|' - //// 00 1E 33 D7 00 4A 2C 31 10 - // SOS Start 'FF|G|' - //// FF 00 1E 33 D7 00 4A 2C 31 10 - // SOS End 'FE|G|' - //// FE 00 1E 33 D7 00 4A 2C 31 10 - // LoneWorker Lack of movement 'FD|G|' - //// FD 00 1E 33 D7 00 4A 2C 31 10 - // LoneWorker Falldetect 'FC|G|' - //// FC 00 1E 33 D7 00 4A 2C 31 10 - // LoneWorker End 'FB|G|' - //// FB 00 1E 33 D7 00 4A 2C 31 10 - // OTA/PagerStatus 'FA|G||V|' - //// FA 00 1E 33 D7 00 4A 2C 31 10 28 - if (bytes.length !== 9) { // we have more than just the |G| block - if (bytes.length === 10) { - switch(bytes[0]) { - case 0xFF: // SOS Start - data.type = 'sos' - data.sos = true - break; - case 0xFE: // SOS End - data.type = 'sos' - data.sos = false - break; - case 0xFD: // LoneWorker Lack of movement - data.type = 'sos' - data.sos = false - data.loneworker = 'lackofmovement' - break; - case 0xFC: // LoneWorker Falldetect - data.type = 'sos' - data.sos = false - data.loneworker = 'falldetect' - break; - case 0xFB: // LoneWorker End - data.type = 'sos' - data.sos = false - data.loneworker = false - break; - } - bytes.splice(0,1) + } else { + data.type = 'battery' + data.battery = bytes[0] * 1 + data.isCharging = bytes[1] == 0xCC + } + break; + case 5: // OTAStatus + LoneWorker/SOS Trigger + GPS Tracking Port '|G|' + // GPS Tracking '|G|' + //// 00 1E 33 D7 00 4A 2C 31 10 + // SOS Start 'FF|G|' + //// FF 00 1E 33 D7 00 4A 2C 31 10 + // SOS End 'FE|G|' + //// FE 00 1E 33 D7 00 4A 2C 31 10 + // LoneWorker Lack of movement 'FD|G|' + //// FD 00 1E 33 D7 00 4A 2C 31 10 + // LoneWorker Falldetect 'FC|G|' + //// FC 00 1E 33 D7 00 4A 2C 31 10 + // LoneWorker End 'FB|G|' + //// FB 00 1E 33 D7 00 4A 2C 31 10 + // OTA/PagerStatus 'FA|G||V|' + //// FA 00 1E 33 D7 00 4A 2C 31 10 28 + if (bytes.length !== 9) { // we have more than just the |G| block + if (bytes.length === 10) { + switch(bytes[0]) { + case 0xFF: // SOS Start + data.type = 'sos' + data.sos = true + break; + case 0xFE: // SOS End + data.type = 'sos' + data.sos = false + break; + case 0xFD: // LoneWorker Lack of movement + data.type = 'sos' + data.sos = false + data.loneworker = 'lackofmovement' + break; + case 0xFC: // LoneWorker Falldetect + data.type = 'sos' + data.sos = false + data.loneworker = 'falldetect' + break; + case 0xFB: // LoneWorker End + data.type = 'sos' + data.sos = false + data.loneworker = false + break; } - if (bytes.length === 11) { // OTA PagerStatus - data.type = 'ota' - data.ota = 'status' - data.battery = bytes[10] * 1 - bytes.splice(0,1) - } - } else { // we are having a normal gps block only - data.type = 'gps' + bytes.splice(0,1) } - var gpsBlock = bytes.splice(0,9) - data.longitude = ((gpsBlock[0]<<24)>>>0) + ((gpsBlock[1]<<16)>>>0) + ((gpsBlock[2]<<8)>>>0) + gpsBlock[3] - data.longitude /= 10e4 - data.latitude = ((gpsBlock[4]<<24)>>>0) + ((gpsBlock[5]<<16)>>>0) + ((gpsBlock[6]<<8)>>>0) + gpsBlock[7] - data.latitude /= 10e4 - data.lastGPSAcquisition = gpsBlock[8] - break; - } - return data; - } \ No newline at end of file + if (bytes.length === 11) { // OTA PagerStatus + data.type = 'ota' + data.ota = 'status' + data.battery = bytes[10] * 1 + bytes.splice(0,1) + } + } else { // we are having a normal gps block only + data.type = 'gps' + } + var gpsBlock = bytes.splice(0,9) + data.longitude = ((gpsBlock[0]<<24)>>>0) + ((gpsBlock[1]<<16)>>>0) + ((gpsBlock[2]<<8)>>>0) + gpsBlock[3] + data.longitude /= 10e4 + data.latitude = ((gpsBlock[4]<<24)>>>0) + ((gpsBlock[5]<<16)>>>0) + ((gpsBlock[6]<<8)>>>0) + gpsBlock[7] + data.latitude /= 10e4 + data.lastGPSAcquisition = gpsBlock[8] + break; + } + return data; +} \ No newline at end of file diff --git a/types/connectors/index.js b/types/connectors/index.js index 6f29b74..a8df0a6 100644 --- a/types/connectors/index.js +++ b/types/connectors/index.js @@ -22,6 +22,7 @@ module.exports = { eMessagePuppeteerConnectorTwoWays, PagernetzConnetorAT: require('./PagernetzConnetorAT'), + pNetConnector: require('./pNetConnector'), LoRaWANConnector: require("./LoRaWANConnector"), POCSAGConnector: require("./POCSAGConnector"), diff --git a/types/connectors/pNetConnector.js b/types/connectors/pNetConnector.js new file mode 100644 index 0000000..8286881 --- /dev/null +++ b/types/connectors/pNetConnector.js @@ -0,0 +1,59 @@ +const Connector = require("./Connector") +const config = require('../../config.json') +const md5 = require('md5') +const axios = require('axios') +const { io } = require("socket.io-client") + + +class pNetConnector extends Connector { + constructor (amqpConnMngr) { + super(amqpConnMngr) + this.name = "pnet" + this.duplexCapable = true + this.managementSocket = false + } + async transmitMessage(msg, params) { + console.log('pnet socket=',this.managementSocket) + if (!this.managementSocket) { + console.log('starting pnet socket') + this.managementSocket = io("wss://") + this.managementSocket.on('smartpager:ingress:event', (evt) => { + console.log(evt) + //this.connectorRegistry.reportState(msg, evt.UUID, evt.state) + if (evt.delivered) this.connectorRegistry.reportDelivered({ id: evt.msgid }, `tetra:${ evt.device_id }`) + //require('../MessageManager').markMessageRead(data.msgid) + if (evt.response) require('../MessageManager').respondToMessage(evt.msgid, evt.response) + }) + await this.managementSocket.connect() + } + const UUID = this.name+':'+md5(JSON.stringify([this.name,...params])) + if (params.length < 1) return false + const target = params[0] + if (target.split('#').length !== 2) throw 'No valid pNet Parameter ' + const contact = target.split('#')[ 0 ], options = target.split('#')[ 1 ] + /*const extraParameters = { + auth: { + username: config.connectors.dapnet.username, + password: config.connectors.dapnet.password + } + }*/ + this.managementSocket.emit('smartpager:ingress', { + UUID, + id: msg.id, + text: msg.payload, + contact, + options, + }) + return true + /*return axios.post("https://pnet.cheetah.cat/", dapnetRequest, extraParameters) + .then(() => { + return true + }) + .catch((err) => { + console.error(err.response.data) + this.connectorRegistry.reportFail(msg, UUID) + return false + })*/ + } +} +module.exports = pNetConnector \ No newline at end of file diff --git a/types/devices/GenericTetra.js b/types/devices/GenericTetra.js new file mode 100644 index 0000000..f9ee810 --- /dev/null +++ b/types/devices/GenericTetra.js @@ -0,0 +1,20 @@ +const MessageManager = require("../MessageManager") +const PagerDevice = require("./Device") +const Str = require('@supercharge/strings') + +class GenericTetra extends PagerDevice { + constructor () { + super() + this.duplex = true + this.name = "tetra-generic" + } + RandID() { + return `TETRA${ Str.random(8) }` + } + async formatTX(msg) { + msg.id = this.RandID() + await MessageManager.BindMsg(msg) + // return msg + } +} +module.exports = GenericTetra \ No newline at end of file diff --git a/types/devices/MXP600.js b/types/devices/MXP600.js new file mode 100644 index 0000000..9d91d4b --- /dev/null +++ b/types/devices/MXP600.js @@ -0,0 +1,20 @@ +const MessageManager = require("../MessageManager") +const PagerDevice = require("./Device") +const Str = require('@supercharge/strings') + +class TETRA_MXP600 extends PagerDevice { + constructor () { + super() + this.duplex = true + this.name = "mxp600" + } + RandID() { + return `MXP${ Str.random(8) }` + } + async formatTX(msg) { + msg.id = this.RandID() + await MessageManager.BindMsg(msg) + // return msg + } +} +module.exports = TETRA_MXP600 \ No newline at end of file diff --git a/types/devices/index.js b/types/devices/index.js index 3fb93b4..8ee0a9c 100644 --- a/types/devices/index.js +++ b/types/devices/index.js @@ -3,5 +3,8 @@ module.exports = { BirdySlim: require("./BirdySlim"), EMessage2Ways: require("./EMessage2Ways"), Skyper: require("./Skyper"), + TETRA_MXP600: require("./MXP600"), + TETRA_Generic: require("./GenericTetra"), + Device: require("./Device"), } \ No newline at end of file