UNPKG

iobroker.lorawan

Version:

converts the desired lora gateway data to a ioBroker structure

862 lines (809 loc) 89.5 kB
'use strict'; const mqtt = require('mqtt'); const crypto = require('crypto'); /* * Created with @iobroker/create-adapter v2.6.0 */ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const utils = require('@iobroker/adapter-core'); const bridgeClass = require('./lib/modules/bridge'); const mqttClientClass = require('./lib/modules/mqttclient'); const messagehandlerClass = require('./lib/modules/messagehandler'); const downlinkConfighandlerClass = require('./lib/modules/downlinkConfighandler'); class Lorawan extends utils.Adapter { /** * @param [options] options of the adapter */ constructor(options) { super({ ...options, name: 'lorawan', }); this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); this.on('objectChange', this.onObjectChange.bind(this)); this.on('message', this.onMessage.bind(this)); this.on('unload', this.onUnload.bind(this)); this.on('fileChange', this.onFileChange.bind(this)); this.origin = { ttn: 'ttn', chirpstack: 'chirpstack', }; this.NextSendLocks = new Map(); // key -> Promise-chain // Simulation variables this.simulation = {}; this.mySystemConfig; this.language; // Adapter Version this.version; this.secret = { hash: 'f3988f71e0d6248fbf690c414bcb46b0500c3a8b3ec9adb9c66be2774ec12291', salt: 'LoRaWANBeScJoFr', }; } onFileChange(_id, _fileName, _size) { // restart adapter after upload //this.restart(); } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { const activeFunction = 'onReady'; try { // read system translation out of i18n translation this.i18nTranslation = await this.geti18nTranslation(); // get systemconfig and configued language this.mySystemConfig = await this.getForeignObjectAsync('system.config'); this.language = this.mySystemConfig?.common.language || 'en'; // Read aktual Adapterversion const adapterinfos = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); this.version = adapterinfos?.common.version; // create downlinkConfigs this.downlinkConfighandler = new downlinkConfighandlerClass(this); // Merge the configed and standard profile of downlinks await this.downlinkConfighandler.addAndMergeDownlinkConfigs(); // create new messagehandler this.messagehandler = new messagehandlerClass(this); // generate new configed downlinkstates on allready existing devices at adapter startup if (this.config.origin !== 'off') { await this.messagehandler.generateDownlinksAndRemoveStatesAtStatup(); } // generate deviceinfo of all devices in info folder await this.messagehandler.generateDeviceinfosAtStartup(); // get history instances at Startup await this.messagehandler.setCustomObjectAtStartup(); // Set mqtt client => just declare, if a url is set if (this.config.origin !== 'off') { this.mqttClient = new mqttClientClass(this, this.config); } // declare bridge if configed if (this.config.BridgeType !== 'off') { this.bridge = new bridgeClass(this); } //Subscribe all configuration and control states await this.subscribeStatesAsync('*'); //this.subscribeObjectsAsync('*.uplink.decoded.*'); //this.subscribeObjectsAsync('*.downlink.control.*'); this.log.debug(`the adapter starts with downlinkconfigs: ${JSON.stringify(this.config.downlinkConfig)}.`); this.log.debug( `the active downlinkconfigs are: ${JSON.stringify(this.downlinkConfighandler.activeDownlinkConfigs)}`, ); /* setTimeout(async () => { this.log.debug('vor Simulation'); await this.startSimulation(); this.log.debug('nach Simulation'); }, 5000); */ /*this.simulation.timeout = setTimeout(async () => { const topic = "application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/f1c0ae0e-b4a2-4547-b360-7cfa15e85734/command/down"; const message = {devEui:"f1c0ae0e-b4a2-4547-b360-7cfa15e85734",confirmed:false,fPort:1,data:"AAA"}; await this.mqttClient?.publish(topic,JSON.stringify(message)); }, 5000);*/ } catch (error) { this.log.error(`error at ${activeFunction}: ${error}`); } } createHash(plainText, salt) { return crypto .createHash('sha256') .update(plainText + salt) .digest('hex'); } async geti18nTranslation() { const systemConfig = await this.getForeignObjectAsync('system.config'); if (systemConfig) { let lang = systemConfig.common.language; if (!lang) { lang = 'en'; } const translationsPath = `./admin/i18n/${lang}/translations.json`; return require(translationsPath); } return {}; } async startSimulation() { // TTN //const topic ="v3/hafi-ttn-lorawan@ttn/devices/Meins/up"; //const message = {"end_device_ids":{"device_id":"eui-lobaro-modbus","application_ids":{"application_id":"hafi-ttn-lorawan"},"dev_eui":"70B3D5E050013950","join_eui":"D55B58C0DDC074DE","dev_addr":"260B5972"},"correlation_ids":["gs:uplink:01HMQZVSCX4D7JRDNFA7GJ9D4W"],"received_at":"2024-01-22T07:06:25.260676101Z","uplink_message":{"session_key_id":"AY0v/ZirzRkpNW0Cgjdhig==","f_port":20,"f_cnt":2,"frm_payload":"AA5BAf0AxwIAAQ==","decoded_payload":{"airhumidity":50.9,"airtemperature":19.9,"port":20,"relais1":0,"relais2":1,"relais3":null,"relais5":null,"volt":3.649,"zisternenpegel":2},"rx_metadata":[{"gateway_ids":{"gateway_id":"hafenmeister-port2ttn-ng","eui":"50313953530A4750"},"time":"2024-01-22T07:06:25.013878Z","timestamp":995696116,"rssi":-37,"channel_rssi":-37,"snr":8.5,"location":{"latitude":53.5548443059465,"longitude":9.92155426743724,"altitude":10,"source":"SOURCE_REGISTRY"},"uplink_token":"CiYKJAoYaGFmZW5tZWlzdGVyLXBvcnQydHRuLW5nEghQMTlTUwpHUBD0u+TaAxoLCPGnuK0GEM3uvhkgoIL0oP24Sg==","channel_index":5,"received_at":"2024-01-22T07:06:25.032492359Z"}],"settings":{"data_rate":{"lora":{"bandwidth":125000,"spreading_factor":9,"coding_rate":"4/5"}},"frequency":"867500000","timestamp":995696116,"time":"2024-01-22T07:06:25.013878Z"},"received_at":"2024-01-22T07:06:25.054442349Z","consumed_airtime":"0.205824s","network_ids":{"net_id":"000013","ns_id":"EC656E0000000181","tenant_id":"ttn","cluster_id":"eu1","cluster_address":"eu1.cloud.thethings.network"}}}; /* const topic = 'v3/hafi-ttn-lorawan@ttn/devices/eui-00137A1000044DF5/up'; const message = { end_device_ids: { device_id: 'shlm-luxsensor-001', application_ids: { application_id: 'shlm-clima' }, dev_eui: '00137A1000044DF5', join_eui: '00137A1000000002', dev_addr: '260B60D9', }, correlation_ids: ['gs:uplink:01JDM7QFYGPF09F2NK2YW5GW78'], received_at: '2024-11-26T12:39:20.993583474Z', uplink_message: { session_key_id: 'AZNi5dvTa96t7M6UDHLcnw==', f_port: 6, f_cnt: 107, frm_payload: 'AR4BJAAAAQIAAAA=', decoded_payload: { BatV: 3.6, Illuminance: 258 }, rx_metadata: [ { gateway_ids: { gateway_id: 'shlmgw03', eui: 'A84041FDFE291238' }, time: '2024-11-26T12:39:20.769507Z', timestamp: 184317371, rssi: -29, channel_rssi: -29, snr: 14, frequency_offset: '9922', location: { latitude: 54.51076410677215, longitude: 9.540317058563234, source: 'SOURCE_REGISTRY', }, uplink_token: 'ChYKFAoIc2hsbWd3MDMSCKhAQf3+KRI4ELvr8VcaDAj4gpe6BhCWuPL1AiD4xK/RrrNE', channel_index: 1, received_at: '2024-11-26T12:39:20.784112662Z', }, { gateway_ids: { gateway_id: 'shlmgw01', eui: 'A84041FDFE276220' }, time: '2024-11-26T12:39:20.764831Z', timestamp: 304678548, rssi: -105, channel_rssi: -105, snr: -1.8, frequency_offset: '10121', location: { latitude: 54.5128326197047, longitude: 9.54245877441114, source: 'SOURCE_REGISTRY', }, uplink_token: 'ChYKFAoIc2hsbWd3MDESCKhAQf3+J2IgEJSNpJEBGgwI+IKXugYQ9bnS+QIgoOSGgu/qhgE=', channel_index: 1, received_at: '2024-11-26T12:39:20.791977205Z', }, { gateway_ids: { gateway_id: 'eui-7076ff0056070bc0', eui: '7076FF0056070BC0' }, time: '2024-11-26T12:39:20.756Z', timestamp: 2848353700, rssi: -120, channel_rssi: -120, snr: -8.2, uplink_token: 'CiIKIAoUZXVpLTcwNzZmZjAwNTYwNzBiYzASCHB2/wBWBwvAEKTbmc4KGgwI+IKXugYQiY/egwMgoJHx+PLsngEqDAj4gpe6BhCAyr7oAg==', channel_index: 6, gps_time: '2024-11-26T12:39:20.756Z', received_at: '2024-11-26T12:39:20.813139849Z', }, ], settings: { data_rate: { lora: { bandwidth: 125000, spreading_factor: 7, coding_rate: '4/5' } }, frequency: '868300000', timestamp: 184317371, time: '2024-11-26T12:39:20.769507Z', }, received_at: '2024-11-26T12:39:20.785204683Z', consumed_airtime: '0.061696s', version_ids: { brand_id: 'netvox', model_id: 'r718g', hardware_version: '2', firmware_version: '10', band_id: 'EU_863_870', }, network_ids: { net_id: '000013', ns_id: 'EC656E0000000181', tenant_id: 'ttn', cluster_id: 'eu1', cluster_address: 'eu1.cloud.thethings.network', }, }, }; */ // ACK //const topic = "v3/hafi-ttn-lorawan@ttn/devices/eui-a84041162183f8fb/down/ack"; //const message = {"end_device_ids":{"device_id":"eui-a84041162183f8fb","application_ids":{"application_id":"hafi-ttn-lorawan"},"dev_eui":"A84041162183F8FB","join_eui":"A840410000000101","dev_addr":"260B141A"},"correlation_ids":["as:downlink:01HP6D18MQXJN90J5B07DC11HY","gs:uplink:01HP6D1A9X4WAA3SFMXH4ESSMV"],"received_at":"2024-02-09T07:41:41.776887672Z","downlink_ack":{"session_key_id":"AY2MUrmnuovS8DCZAfYmsA==","f_port":1,"f_cnt":21,"frm_payload":"AQAAeA==","confirmed":true,"priority":"NORMAL","correlation_ids":["as:downlink:01HP6D18MQXJN90J5B07DC11HY"],"confirmed_retry":{"attempt":1}}}; /* // Chipstack const topic = 'application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/a84041f621857cd2/event/up'; const message = { deduplicationId: '46fecb8d-d7f1-481e-8b1b-bcaad7e5d1e0', time: '2025-02-18T14:49:19.475813+00:00', deviceInfo: { tenantId: '52f14cd4-c6f1-4fbd-8f87-4025e1d49242', tenantName: 'ChirpStack', applicationId: 'bac5ba56-f9c6-4d98-a609-8366e048495d', applicationName: 'Pool', deviceProfileId: 'e847fd4b-a87e-452c-91b8-a4fbaa51acfa', deviceProfileName: 'Dragino Feuchtesenor', deviceName: 'Skimmer', devEui: 'a84041f621857cd2', deviceClassEnabled: 'CLASS_A', tags: {}, }, devAddr: '01cdf20a', adr: true, dr: 5, fCnt: 170, fPort: 2, confirmed: false, data: 'DPYBAaL//AA9', object: { Soilmoisture: 41.8, Soilconductivity: 61, devicetype: 'Dragino', Volt: 3.318, Soiltemperature: -0.4, }, rxInfo: [ { gatewayId: '503035416e314750', uplinkId: 33537, gwTime: '2025-02-18T14:49:19.475813+00:00', rssi: -108, snr: 2, channel: 4, location: { latitude: 50.693467319817266, longitude: 8.47676753997803 }, context: 'mRVIdA==', crcStatus: 'CRC_OK', }, ], txInfo: { frequency: 867300000, modulation: { lora: { bandwidth: 125000, spreadingFactor: 7, codeRate: 'CR_4_5' } }, }, regionConfigId: 'eu868', };*/ // Chipstack const topic = 'application/bbea74d6-1fc5-4238-af20-d2aecdbb4f8e/device/70b3d52dd301b3cc/event/up'; const message = { deduplicationId: '1d94e6d8-25cc-4099-ab45-4a7f7faa1634', time: '2025-05-15T13:22:08.891648+00:00', deviceInfo: { tenantId: '52f14cd4-c6f1-4fbd-8f87-4025e1d49242', tenantName: 'ChirpStack', applicationId: 'bbea74d6-1fc5-4238-af20-d2aecdbb4f8e', applicationName: 'Heizen', deviceProfileId: 'aedb4f09-8644-430b-a088-47519f01530b', deviceProfileName: 'MClimate Vicki', deviceName: 'ThermostatKinderzimmerRika', devEui: '70b3d52dd301b3cc', deviceClassEnabled: 'CLASS_A', tags: {}, }, devAddr: '01b6c24b', adr: true, dr: 5, fCnt: 38547, fPort: 2, confirmed: false, data: 'RADjUgC5gRKfTt7eEfAw', object: { CalibrationFailed: false, ValveOpenness: 0, MotorRange: 478, ChildLock: false, Reason: 81, RelativeHumidity: 30.47, BatteryVoltage: 3.5, AttachedBackplate: true, extSensorTemperature: 22.7, TargetTemperature: 18, PerceiveAsOnline: true, BrokenSensor: false, AntiFreezeProtection: false, HighMotorConsumption: false, Device: 'Vicki', SensorTemperature: 23.06, targetTemperatureFloat: 18.5, MotorPosition: 478, OpenWindow: false, LowMotorConsumption: false, }, rxInfo: [ { gatewayId: '503035416e314750', uplinkId: 55809, gwTime: '2025-05-15T13:22:08.891648+00:00', nsTime: '2025-05-15T13:22:08.927071564+00:00', rssi: -81, snr: 7.5, channel: 2, rfChain: 1, location: { latitude: 50.693467319817266, longitude: 8.47676753997803 }, context: 'YOhWvA==', crcStatus: 'CRC_OK', }, ], txInfo: { frequency: 868500000, modulation: { lora: { bandwidth: 125000, spreadingFactor: 7, codeRate: 'CR_4_5' } }, }, regionConfigId: 'eu868', }; //const topic = "application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/a84041f621857cd2/command/down"; //const message = {"devEui":"a84041f621857cd2","confirmed":false,"fPort":1,"data":"AQAqMA=="}; // Chirpstack LT222222 //const topic = "application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/a8404127a188d826/event/up"; //const message = {"deduplicationId":"bd3fdb3b-af86-4617-b9f2-da07075d2bc5","time":"2024-01-24T16:47:01.573381+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"d63c10b6-9263-4ab3-9299-4308fa19a2ad","applicationName":"Benjamin Schmidt","deviceProfileId":"f1c0ae0e-b4a2-4547-b360-7cfa15e85734","deviceProfileName":"Dragino LT22222","deviceName":"Relaistestgerät","devEui":"a8404127a188d826","deviceClassEnabled":"CLASS_C","tags":{}},"devAddr":"01dfbaf2","adr":true,"dr":5,"fCnt":12,"fPort":2,"confirmed":false,"data":"AAAAAAAAAAA8/0E=","object":{"RO1_status":"OFF","DO2_status":"H","ACI2_mA":0.0,"DO1_status":"H","Hardware_mode":"LT22222","RO2_status":"OFF","AVI2_V":0.0,"ACI1_mA":0.0,"DI1_status":"H","DI2_status":"H","Work_mode":"2ACI+2AVI","AVI1_V":0.0},"rxInfo":[{"gatewayId":"50303541b0344750","uplinkId":57857,"gwTime":"2024-01-24T16:47:01.573381+00:00","nsTime":"2024-01-24T16:47:02.370171527+00:00","rssi":-54,"snr":8.5,"channel":6,"location":{"latitude":50.69344693065449,"longitude":8.476783633232118},"context":"2tr9BA==","metadata":{"region_config_id":"eu868","region_common_name":"EU868"},"crcStatus":"CRC_OK"}],"txInfo":{"frequency":867700000,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5"}}}}; //const topic = "application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/a8404127a188d826/command/down"; //const message = {"devEui":"a8404127a188d826","confirmed":false,"fPort":1,"data":"AQACWA=="}; // ACK //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/ack"; //const message = {"deduplicationId":"b080c0d8-6151-4675-84b8-74ecf9e33bae","time":"2023-08-15T13:22:27.969901+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"queueItemId":"3434298f-2b89-49f8-885e-9fdd9f0892e6","acknowledged":true,"fCntDown":262}; // TXACK //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/txack"; //const message = {"downlinkId":2478630510,"time":"2024-01-27T11:50:04.736655452+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"queueItemId":"efc2bacf-d5da-48d3-a6ef-2a77fda41bd0","fCntDown":4940,"gatewayId":"50313953530a4750","txInfo":{"frequency":868300000,"power":16,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5","polarizationInversion":true}},"timing":{"delay":{"delay":"1s"}},"context":"eqFuiw=="}}; // STATUS //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/status"; //const message = {"deduplicationId":"4a91b00d-b5e1-4955-b085-ba21b9318213","time":"2024-01-26T20:18:45.299871+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"margin":7,"externalPowerSource":false,"batteryLevelUnavailable":false,"batteryLevel":85.826775}; // UP //const topic = "application/e91e66ba-1aa7-4bdf-af88-f1246e0b8d75/device/a84041263188b787/event/up"; //const message = {"deduplicationId":"ce1ca35d-35c7-4f60-844c-c2b2810fd74b","time":"2024-08-25T07:10:47.758298+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"e91e66ba-1aa7-4bdf-af88-f1246e0b8d75","applicationName":"Türen","deviceProfileId":"431c5895-68e2-478d-945f-f0e9a6f5f9f5","deviceProfileName":"Dragino Türsensoren / Fenstersensoren","deviceName":"Flurtüre","devEui":"a84041263188b787","deviceClassEnabled":"CLASS_A","tags":{}},"devAddr":"0061ebd4","adr":true,"dr":5,"fCnt":8264,"fPort":10,"confirmed":false,"data":"DAYBAA+IAAAAAA==","object":{"ALARM":0.0,"BAT_V":3.078,"CONTACT":true,"OPEN_TIMES":3976.0,"MOD":1.0,"LAST_OPEN_DURATION":0.0,"OPEN":false,"devicetype":"Dragino"},"rxInfo":[{"gatewayId":"503035416e314750","uplinkId":64001,"gwTime":"2024-08-25T07:10:47.758298+00:00","nsTime":"2024-08-25T07:11:29.787667701+00:00","rssi":-68,"snr":9.25,"channel":6,"location":{"latitude":50.69350130173554,"longitude":8.476821184158327},"context":"fp1WbA==","metadata":{"region_common_name":"EU868","region_config_id":"eu868"},"crcStatus":"CRC_OK"}],"txInfo":{"frequency":867700000,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5"}}}}; // LOG //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/up"; //const message = {"time":"2024-01-27T10:29:58.221817559+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"level":"ERROR","code":"UPLINK_CODEC","description":"Exception generated by quickjs","context":{"deduplication_id":"c44e7e25-09ce-4c95-b96f-5a298c5c6440"}}; // JOIN //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/join"; //const message = {"deduplicationId":"44cef56d-1b8d-45fc-a762-03b98b620db2","time":"2023-12-12T03:13:21.551178+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"devAddr":"01009400"}; // DOWN //const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/command/down"; //const message = {"devEui": "70b3d52dd300ed31", "confirmed": false,"fPort": 1,"data": "DQEYDQEY"}; this.log.debug(`incomming topic: ${topic}`); this.log.debug(`incomming message: ${JSON.stringify(message)}`); await this.messagehandler?.handleMessage(topic, message); } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * * @param callback function wich is called after shutdown adapter */ async onUnload(callback) { try { // Ausgabe der Nachrichtg, dass der Adapter beendet wird const notificationId = `${this.namespace}.${this.bridge?.Words.notification}${this.bridge?.GeneralId}`; await this.bridge?.publishNotification( notificationId, this.i18nTranslation['Adapter will be stoped'], this.bridge?.Notificationlevel.bridgeConnection, ); // clear timeout (for simulation) if (this.simulation.timeout) { this.clearTimeout(this.simulation.timeout); delete this.simulation.timeout; } // Clear Schedules in directoriehandler this.messagehandler?.clearAllSchedules(); // Clear Schedules in Bridge this.bridge?.clearAllSchedules(); // Destroy Bridged mqtt client this.bridge?.bridgeMqttClient?.destroy(); // Destroy mqtt client this.mqttClient?.destroy(); callback(); } catch (e) { this.log.error(e); callback(); } } /** * Is called if a subscribed object changes * * @param id id of the changed object * @param obj value and ack of the changed object */ async onObjectChange(id, obj) { this.log.debug(`${id} is changed into ${JSON.stringify(obj.common)}`); const activeFunction = 'main.js - onObjectChange'; this.log.debug(`Function ${activeFunction} started.`); try { // Only work, if bridge is activ if (this.bridge) { // Erzeugen der HA Bridged für Control // check for new Entry const members = obj.common.members; for (const member of members) { if (!this.bridge.ForeignBridgeMembers[member]) { if (!member.startsWith(this.namespace)) { await this.bridge?.discoverForeignRange(member); } else { this.log.warn( `The bridge enum is set within adapternamespace. please remove form id: ${member}`, ); } return; } } // check for Entry removed for (const member of Object.values(this.bridge.ForeignBridgeMembers)) { if (!members.includes(member)) { await this.bridge.discoverForeignRange(member, true); return; } } } } catch (error) { this.log.error(`error at ${activeFunction}: ${error}`); } } /** * Is called if a subscribed state changes * * @param id id of the changed state * @param state value and ack of the changed state */ async onStateChange(id, state) { const activeFunction = 'onStateChange'; try { if (state) { //this.log.debug(`state ${id} changed: val: ${state.val} - ack: ${state.ack}`); // The state was changed => only states with ack = false will be processed, others will be ignored if (id.startsWith(`${this.namespace}.info.`)) { this.log.silly( `the state ${id} has changed to ${state.val !== '' ? state.val : '""'} with ack = ${state.ack}.`, ); } else { this.log.debug( `the state ${id} has changed to ${state.val !== '' ? state.val : '""'} with ack = ${state.ack}.`, ); } if (!state.ack) { if (id.startsWith(this.namespace)) { // Check for downlink in id if (id.indexOf('.downlink.control.') !== -1) { // get information of the changing state const changeInfo = await this.getChangeInfo(id, { withBestMatch: true }); const suffix = this.downlinkConfighandler?.getDownlinkTopicSuffix(changeInfo?.changedState); if (changeInfo?.changedState === 'push' || changeInfo?.changedState === 'replace') { const downlinkTopic = this.downlinkConfighandler?.getDownlinkTopic(changeInfo, suffix); try { if (JSON.parse(state.val)) { await this.sendDownlink(downlinkTopic, state.val, changeInfo); await this.bridge?.publishId(id, state.val, {}); await this.setState(id, state.val, true); } } catch (error) { this.log.warn(`Cant send invalid downlinks. Error: ${error}`); } } else if (changeInfo?.changedState === 'CustomSend') { if (state.val !== '') { const downlinkTopic = this.downlinkConfighandler?.getDownlinkTopic( changeInfo, suffix, ); const downlinkConfig = this.downlinkConfighandler?.activeDownlinkConfigs[ changeInfo.bestMatchForDeviceType ]; /* Alte Zuweisung const Statevalues = state.val.split(','); const StateElements = { payloadInHex: Statevalues[0].toUpperCase(), port: downlinkConfig.port, confirmed: downlinkConfig.confirmed, priority: downlinkConfig.priority, push: false, }; // Assign writen values for (const element in Statevalues) { if (Statevalues[element] === 'push') { StateElements.push = true; break; } if (element === '1') { StateElements.port = Number(Statevalues[element]); } else if (element === '2') { StateElements.confirmed = Statevalues[element] === 'true' ? true : false; } else if (element === '3') { StateElements.priority = Statevalues[element]; } } */ // Eingefügt am 09.12.2026 const Statevalues = state.val.split(','); const StateElements = { payloadInHex: null, port: downlinkConfig.port, confirmed: downlinkConfig.confirmed, priority: downlinkConfig.priority, push: false, }; for (const raw of Statevalues) { const element = raw.trim(); // --- push --- if (element.toLowerCase() === 'push') { StateElements.push = true; continue; } // --- confirmed (boolean) --- if (element === 'true' || element === 'false') { StateElements.confirmed = element === 'true'; continue; } // --- port (number) --- if (/^\d+$/.test(element)) { StateElements.port = Number(element); continue; } // --- payloadInHex: reiner Hex-String --- if (/^[0-9A-Fa-f]+$/.test(element)) { StateElements.payloadInHex = element.toUpperCase(); continue; } // --- priority: alles, was kein reiner Hex-String ist --- if (/[^0-9A-Fa-f]/.test(element)) { StateElements.priority = element; continue; } } // Query about th correct type this.log.debug( 'The following values are detected / used at input of custom send state', ); for (const element in StateElements) { this.log.debug( `${element}: Type: ${typeof StateElements[element]} - Value: ${StateElements[element]}`, ); } // write into NextSend, or push directly if (!StateElements.push) { // Write into nextSend await this.writeNextSend(changeInfo, StateElements.payloadInHex); } if ( !changeInfo?.bestMatchForDeviceType || this.downlinkConfighandler?.activeDownlinkConfigs[ changeInfo.bestMatchForDeviceType ].sendWithUplink === 'disabled' || StateElements.push ) { const downlink = this.downlinkConfighandler?.getDownlink( { port: StateElements.port, confirmed: StateElements.confirmed, priority: StateElements.priority, }, StateElements.payloadInHex, changeInfo, ); if (downlink !== undefined) { await this.sendDownlink( downlinkTopic, JSON.stringify(downlink), changeInfo, ); } } } await this.bridge?.publishId(id, state.val, {}); await this.setState(id, state.val, true); } else { const downlinkTopic = this.downlinkConfighandler?.getDownlinkTopic(changeInfo, suffix); const downlinkParameter = this.downlinkConfighandler?.getDownlinkParameter( changeInfo, {}, ); if (downlinkParameter !== undefined) { const payloadInHex = this.downlinkConfighandler?.calculatePayloadInHex( downlinkParameter, state, ); await this.writeNextSend(changeInfo, payloadInHex); if ( !changeInfo?.bestMatchForDeviceType || this.downlinkConfighandler?.activeDownlinkConfigs[ changeInfo.bestMatchForDeviceType ].sendWithUplink === 'disabled' ) { const downlink = this.downlinkConfighandler?.getDownlink( downlinkParameter, payloadInHex, changeInfo, ); if (downlink !== undefined) { await this.sendDownlink( downlinkTopic, JSON.stringify(downlink), changeInfo, ); } } await this.bridge?.publishId(id, state.val, {}); await this.setState(id, state.val, true); } } } else if (id.includes('.nextSend.push')) { if (state.val) { await this.checkSendDownlinkWithUplink(id, { pushNextSend: true }); await this.setState(id, false, true); } } else if (id.indexOf('.configuration.') !== -1) { // State is from configuration path const changeInfo = await this.getChangeInfo(id, { withBestMatch: true }); this.messagehandler?.fillWithDownlinkConfig(changeInfo?.objectStartDirectory, {}); // remove not configed states const adapterObjects = await this.getAdapterObjectsAsync(); for (const adapterObject of Object.values(adapterObjects)) { if ( adapterObject.type === 'state' && adapterObject._id.indexOf( `${changeInfo?.objectStartDirectory}.downlink.control`, ) !== -1 ) { const changeInfo = await this.getChangeInfo(adapterObject._id); const downlinkParameter = this.downlinkConfighandler?.getDownlinkParameter( changeInfo, { startupCheck: true, }, ); if (!downlinkParameter) { await this.delObjectAsync(this.removeNamespace(adapterObject._id)); } } } await this.setState(id, state.val, true); } else if (id.endsWith('.bridge.send')) { const topic = await this.getStateAsync(`${this.namespace}.bridge.topic`); const payload = await this.getStateAsync(`${this.namespace}.bridge.payload`); if (topic && payload) { await this.bridge?.bridgeMqttClient.publish(topic.val, payload.val, {}); await this.setState(`${this.namespace}.bridge.topic`, topic.val, true); await this.setState(`${this.namespace}.bridge.payload`, payload.val, true); } await this.setState(id, state.val, true); } else if (id.endsWith('.bridge.notification')) { const words = state.val.split(' '); const hash = this.createHash(words[0], this.secret.salt); if (hash === this.secret.hash) { if (words[1] === 'mqtt') { this.extendObject('bridge.debug', { type: 'folder', common: { name: 'Debugfunctions of bridge' }, native: {}, }); this.extendObject('bridge.debug.topic', { type: 'state', common: { name: 'topic of mqtt message', type: 'string', read: true, write: true, def: '', }, native: {}, }); this.extendObject('bridge.debug.payload', { type: 'state', common: { name: 'payload of mqtt message', type: 'string', role: 'json', read: true, write: true, def: '', }, native: {}, }); this.extendObject('bridge.debug.send', { type: 'state', common: { name: 'payload of mqtt message', type: 'boolean', role: 'button', read: false, write: true, def: false, }, native: {}, }); // Incomming this.extendObject('bridge.debug.incommingTopic', { type: 'state', common: { name: 'topic of mqtt message', type: 'string', read: true, write: false, def: '', }, native: {}, }); this.extendObject('bridge.debug.incommingTopicFilter', { type: 'state', common: { name: 'filter for topic of mqtt message', type: 'string', read: true, write: true, def: '', }, native: {}, }); this.extendObject('bridge.debug.incommingPayload', { type: 'state', common: { name: 'payload of mqtt message', type: 'string', role: 'json', read: true, write: false, def: '', }, native: {}, }); // Outgoing this.extendObject('bridge.debug.outgoingTopic', { type: 'state', common: { name: 'topic of mqtt message', type: 'string', read: true, write: false, def: '', }, native: {}, }); this.extendObject('bridge.debug.outgoingTopicFilter', { type: 'state', common: { name: 'filter for topic of mqtt message', type: 'string', read: true, write: true, def: '', }, native: {}, }); this.extendObject('bridge.debug.outgoingPayload', { type: 'state', common: { name: 'payload of mqtt message', type: 'string', role: 'json', read: true, write: false, def: '', }, native: {}, }); await this.setState(id, '', true); } } else { let notificationId = `${this.namespace}.${this.bridge?.Words.notification}${this.bridge?.GeneralId}`; await this.bridge?.publishNotification( notificationId, state.val, this.config.BridgenotificationActivation, ); await this.setState(id, state.val, true); } } else if (id.endsWith('bridge.debug.incommingTopicFilter')) { this.bridge?.bridgeMqttClient.setFilter('incomming', state.val); await this.setState(id, state.val, true); } else if (id.endsWith('bridge.debug.outgoingTopicFilter')) { this.bridge?.bridgeMqttClient.setFilter('outgoing', state.val); await this.setState(id, state.val, true); } else if (id.endsWith('.bridge.debug.send')) { const topic = await this.getStateAsync('bridge.debug.topic'); const payload = await this.getStateAsync('bridge.debug