diff --git a/.gitignore b/.gitignore index d9228bd..e83e955 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ package-lock.json .idea /node_modules/ /cli/homeymatic-cli + + +# Added by Homey CLI +/.homeybuild/ \ No newline at end of file diff --git a/.homeycompose/app.json b/.homeycompose/app.json index c2f6f61..514fa6c 100644 --- a/.homeycompose/app.json +++ b/.homeycompose/app.json @@ -48,5 +48,8 @@ "path": "/bridges/delete/", "public": false } - } + }, + "platforms": [ + "local" + ] } \ No newline at end of file diff --git a/app.js b/app.js index 538db42..a988dca 100644 --- a/app.js +++ b/app.js @@ -6,30 +6,46 @@ const HomeMaticCCUMQTT = require('./lib/HomeMaticCCUMQTT'); const HomeMaticCCURPC = require('./lib/HomeMaticCCURPC'); const HomeMaticCCUJack = require('./lib/HomeMaticCCUJack'); const Constants = require('./lib/constants'); -const Logger = require('./lib/logger') +const Logger = require('./lib/logger'); -const connTypeRPC = 'use_rpc' -const connTypeMQTT = 'use_mqtt' -const connTypeCCUJack = 'use_ccu_jack' +const connTypeRPC = 'use_rpc'; +const connTypeMQTT = 'use_mqtt'; +const connTypeCCUJack = 'use_ccu_jack'; class Homematic extends Homey.App { async onInit() { this.logger = new Logger(this.homey); this.logger.log('info', 'Started homematic...'); - var self = this; - let address = await this.homey.cloud.getLocalAddress() - self.homeyIP = address.split(':')[0] - self.settings = self.getSettings(); - self.discovery = new HomeMaticDiscovery(this.logger, this.homey); - self.bridges = {}; - if (this.homey.app.settings.use_stored_bridges) { - self.initializeStoredBridges(); - } else { - await self.discovery.discover() + + try { + const address = await this.homey.cloud.getLocalAddress(); + this.homeyIP = address.split(':')[0]; + this.settings = this.getSettings(); + this.discovery = new HomeMaticDiscovery(this.logger, this.homey); + this.bridges = {}; + + const storedBridges = this.getStoredBridges(); + if (Object.keys(storedBridges).length > 0) { + this.logger.log('info', 'Initializing stored bridges...'); + this.initializeStoredBridges(storedBridges); + } else { + await this.discovery.discover(); + } + } catch (err) { + this.logger.log('error', 'Initialization failed:', err); } } + async onUninit() { + this.logger.log('info', 'Unloading homematic app...'); + Object.keys(this.bridges).forEach(serial => { + if (this.bridges[serial].cleanup) { + this.bridges[serial].cleanup(); + } + }); + } + getSettings() { return { "use_mqtt": this.homey.settings.get('use_mqtt'), @@ -38,91 +54,91 @@ class Homematic extends Homey.App { "ccu_jack_user": this.homey.settings.get('ccu_jack_user'), "ccu_jack_password": this.homey.settings.get('ccu_jack_password'), "use_stored_bridges": this.homey.settings.get('use_stored_bridges'), - } + }; } getStoredBridges() { - var self = this; - var bridges = {}; + const bridges = {}; this.homey.settings.getKeys().forEach((key) => { if (key.startsWith(Constants.SETTINGS_PREFIX_BRIDGE)) { let bridge = this.homey.settings.get(key); - bridges[bridge.serial] = bridge + bridges[bridge.serial] = bridge; } - }) - - return bridges + }); + return bridges; } - initializeStoredBridges() { - var self = this; - var bridges = this.getStoredBridges(); + initializeStoredBridges(bridges) { Object.keys(bridges).forEach((serial) => { let bridge = bridges[serial]; - self.logger.log('info', "Initializing stored ccu:", "Type", bridge.type, "Serial", bridge.serial, "IP", bridge.address); - self.initializeBridge(bridge) + this.logger.log('info', "Initializing stored CCU:", "Type", bridge.type, "Serial", bridge.serial, "IP", bridge.address); + this.initializeBridge(bridge); }); } getConnectionType() { - if (this.homey.app.settings.connection_type) { - return this.homey.app.settings.connection_type + if (this.settings.connection_type) { + return this.settings.connection_type; } - if (this.homey.app.settings.use_mqtt === true) { - return connTypeMQTT + if (this.settings.use_mqtt) { + return connTypeMQTT; } - return connTypeRPC + return connTypeRPC; } initializeBridge(bridge) { - let self = this; - let connType = self.getConnectionType() - self.logger.log('info', 'Connection type:', connType) - switch (connType) { - case connTypeRPC: - self.logger.log('info', "Initializing RPC CCU"); - self.bridges[bridge.serial] = new HomeMaticCCURPC(self.logger, self.homey, bridge.type, bridge.serial, bridge.address); - break; - case connTypeMQTT: - self.logger.log('info', "Initializing MQTT CCU "); - self.bridges[bridge.serial] = new HomeMaticCCUMQTT(self.logger, self.homey, bridge.type, bridge.serial, bridge.address); - break; - case connTypeCCUJack: - self.logger.log('info', "Initializing CCU Jack"); - self.bridges[bridge.serial] = new HomeMaticCCUJack( - self.logger, - self.homey, - bridge.type, - bridge.serial, - bridge.address, - this.homey.app.settings.ccu_jack_mqtt_port, - this.homey.app.settings.ccu_jack_user, - this.homey.app.settings.ccu_jack_password, - ); - break; + try { + const connType = this.getConnectionType(); + this.logger.log('info', 'Connection type:', connType); + switch (connType) { + case connTypeRPC: + this.logger.log('info', "Initializing RPC CCU"); + this.bridges[bridge.serial] = new HomeMaticCCURPC(this.logger, this.homey, bridge.type, bridge.serial, bridge.address); + break; + case connTypeMQTT: + this.logger.log('info', "Initializing MQTT CCU "); + this.bridges[bridge.serial] = new HomeMaticCCUMQTT(this.logger, this.homey, bridge.type, bridge.serial, bridge.address); + break; + case connTypeCCUJack: + this.logger.log('info', "Initializing CCU Jack"); + this.bridges[bridge.serial] = new HomeMaticCCUJack( + this.logger, + this.homey, + bridge.type, + bridge.serial, + bridge.address, + this.settings.ccu_jack_mqtt_port, + this.settings.ccu_jack_user, + this.settings.ccu_jack_password, + ); + break; + default: + throw new Error(`Unsupported connection type: ${connType}`); + } + return this.bridges[bridge.serial]; + } catch (err) { + this.logger.log('error', `Failed to initialize bridge ${bridge.serial}:`, err); } - - return self.bridges[bridge.serial] } setBridgeAddress(serial, address) { - var self = this; - self.bridges[serial].address = address; + if (this.bridges[serial]) { + this.bridges[serial].address = address; + } else { + this.logger.log('error', `No bridge found for serial: ${serial}`); + } } deleteStoredBridges() { - var self = this; - var bridges = this.getStoredBridges() + const bridges = this.getStoredBridges(); Object.keys(bridges).forEach((serial) => { - this.homey.settings.unset(Constants.SETTINGS_PREFIX_BRIDGE + serial) - }) + this.homey.settings.unset(Constants.SETTINGS_PREFIX_BRIDGE + serial); + }); } getLogLines() { return this.logger.getLogLines(); } - } - module.exports = Homematic; diff --git a/app.json b/app.json index 2bb2ab3..3bbfc23 100644 --- a/app.json +++ b/app.json @@ -50,6 +50,9 @@ "public": false } }, + "platforms": [ + "local" + ], "drivers": [ { "id": "HM-CC-RT-DN", @@ -9147,6 +9150,94 @@ } ] }, + { + "id": "HmIP-SWO-B", + "name": { + "en": "HmIP-SWO-B" + }, + "class": "sensor", + "capabilities": [], + "images": { + "large": "/drivers/HmIP-SWO-B/assets/images/large.png", + "small": "/drivers/HmIP-SWO-B/assets/images/small.png" + }, + "pair": [ + { + "id": "list_bridges", + "template": "list_devices", + "options": { + "singular": true + }, + "navigation": { + "next": "list_devices" + } + }, + { + "id": "list_devices", + "template": "list_devices", + "navigation": { + "next": "add_devices" + } + }, + { + "id": "add_devices", + "template": "add_devices" + } + ], + "settings": [ + { + "type": "group", + "label": { + "en": "Device data" + }, + "children": [ + { + "id": "app", + "type": "label", + "label": { + "en": "App" + }, + "value": "Homematic" + }, + { + "id": "driver", + "type": "label", + "label": { + "en": "Driver" + }, + "value": "", + "hint": { + "en": "In most cases the driver name is the same as the device type, but some drivers support multiple device types. In those cases they might not match." + } + }, + { + "id": "address", + "type": "label", + "label": { + "en": "Device Address" + }, + "value": "" + }, + { + "id": "ccuIP", + "type": "label", + "label": { + "en": "CCU IP" + }, + "value": "" + }, + { + "id": "ccuSerial", + "type": "text", + "label": { + "en": "CCU Serial" + }, + "value": "" + } + ] + } + ] + }, { "id": "HmIP-SWO-PR", "name": { diff --git a/cli/cmd/bindata.go b/cli/cmd/bindata.go index 16e6b61..c073bc6 100644 --- a/cli/cmd/bindata.go +++ b/cli/cmd/bindata.go @@ -1,12 +1,10 @@ -// Code generated by go-bindata. +// Code generated for package cmd by go-bindata DO NOT EDIT. (@generated) // sources: // data/driver/assets/icon.svg // data/driver/device.js // data/driver/driver.compose.json // data/driver/driver.flow.compose.json // data/driver/driver.js -// DO NOT EDIT! - package cmd import ( @@ -53,26 +51,37 @@ type bindataFileInfo struct { modTime time.Time } +// Name return file name func (fi bindataFileInfo) Name() string { return fi.name } + +// Size return file size func (fi bindataFileInfo) Size() int64 { return fi.size } + +// Mode return file mode func (fi bindataFileInfo) Mode() os.FileMode { return fi.mode } + +// Mode return file modify time func (fi bindataFileInfo) ModTime() time.Time { return fi.modTime } + +// IsDir return file whether a directory func (fi bindataFileInfo) IsDir() bool { - return false + return fi.mode&os.ModeDir != 0 } + +// Sys return file is sys mode func (fi bindataFileInfo) Sys() interface{} { return nil } -var _dataDriverAssetsIconSvg = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x56\x4f\x73\xeb\xc6\x0d\xbf\xfb\x53\x6c\x99\x4b\x32\x25\xa1\x05\xf6\x1f\xe0\x5a\xce\x4c\xfb\x9a\x49\x0e\xb9\xb4\x69\x3a\x93\x1b\x1f\x49\x4b\x9c\x27\x89\x1a\x92\x96\xec\xf7\xe9\x3b\xbb\x24\x25\xd9\x7e\xed\x94\x17\x62\x7f\xf8\xb3\xc0\x6f\x81\x25\x1f\x7e\x7c\xd9\xef\xd4\xa9\xe9\x87\xb6\x3b\xac\x33\x04\x9d\xa9\xe6\x50\x75\x75\x7b\xd8\xac\xb3\x7f\xfd\xf6\x53\xc1\x99\x1a\xc6\xf2\x50\x97\xbb\xee\xd0\xac\xb3\x43\x97\xfd\xf8\x78\xf7\xf0\xa7\xa2\x50\x7f\xeb\x9b\x72\x6c\x6a\x75\x6e\xc7\xad\xfa\xe5\xf0\x65\xa8\xca\x63\xa3\xbe\xdf\x8e\xe3\xf1\x7e\xb5\x3a\x9f\xcf\xd0\xce\x20\x74\xfd\x66\xf5\x83\x2a\x8a\xc7\xbb\xbb\x87\xe1\xb4\xb9\x53\x4a\xbd\xec\x77\x87\xe1\xbe\xae\xd6\xd9\xec\x70\x7c\xee\x77\xc9\xb0\xae\x56\xcd\xae\xd9\x37\x87\x71\x58\x21\xe0\x2a\xbb\x9a\x57\x57\xf3\x2a\xee\xde\x9e\x9a\xaa\xdb\xef\xbb\xc3\x90\x3c\x0f\xc3\x77\x37\xc6\x7d\xfd\x74\xb1\x8e\xd9\x9c\x4d\x32\x42\x11\x59\x69\x5a\x11\x15\x7d\xfd\x54\x0c\xaf\x87\xb1\x7c\x29\xde\xba\x0e\xa7\xcd\xb7\x5c\x49\x6b\xbd\x1a\x4e\x9b\xab\xe5\xff\x67\x75\x3f\x74\x75\x7b\xec\xea\xf6\x62\xbe\x00\x30\x74\xcf\x7d\xd5\x3c\x75\xfd\xa6\x81\x43\x33\xae\x3e\xfd\xf6\xe9\xa2\x2c\x34\xd4\x63\x7d\x13\x66\xe1\xf3\xcd\xae\x6f\x48\x3e\x94\xfb\x66\x38\x96\x55\x33\xac\x16\x3c\xf9\x9f\xdb\x7a\xdc\xae\x33\xa7\x75\x5a\x6e\x9b\x76\xb3\x1d\xaf\xeb\x53\xdb\x9c\xff\xda\xbd\xac\x33\xad\xb4\x42\x43\x40\x82\xde\x5f\xa4\x30\x19\x5d\xbb\x04\x13\xd0\xd6\xeb\x6c\x38\x6d\x78\x5a\xcc\xdb\xdd\x5f\xcc\x34\x08\x81\x55\xdf\xbb\xba\xf4\x2c\x95\x41\x93\x2b\xd2\x28\x85\xc6\x02\xed\x0f\xc9\x6b\x29\xf5\xbe\xee\xaa\x98\xfb\x3a\x6b\xab\xee\x00\x91\xbd\xc7\x3b\xa5\x1e\xea\xe6\x69\x88\x76\xd3\x66\x71\x45\x99\x5a\x25\xd5\xc5\x35\xfa\xd5\xb1\x82\xab\xe1\xe7\x72\x98\xea\x56\xea\x58\x6e\x9a\xaa\xdb\x75\xfd\x3a\xfb\xee\x29\x3d\xb3\xe2\x73\xd7\xd7\x4d\xbf\xa8\x7c\x7a\xde\xa8\xba\x63\x59\xb5\xe3\xeb\x34\x15\x73\xec\xa5\xc8\x18\xf5\xa2\xd7\xdf\xd6\x0f\xdb\xb2\xee\xce\xeb\x8c\xde\x2b\xbf\x76\xdd\x3e\x46\x35\x2c\xec\x49\xde\xab\xab\x97\x75\x16\x2c\x88\x21\xf2\xf6\x83\xf2\x75\x9d\x11\x23\xb0\xb5\x1c\xde\x2b\xeb\xae\x7a\x8e\x73\x53\x3c\x1f\xda\x71\x58\x67\xfb\xfd\x07\xf7\xe7\xbe\x8f\x06\xbb\xf2\xb5\xe9\xd7\x59\x7a\xe1\x6c\x34\x6c\xbb\xf3\xa6\x8f\xf4\x3d\x95\xbb\x0b\x7f\x73\xa8\xe3\xcb\xfb\x50\xe7\xf6\x50\x77\xe7\x62\xee\x2c\x14\xfa\x40\xc2\x6c\xb1\x34\x1b\x6a\xfc\x90\xf1\x6c\xf2\xb2\xce\x0a\xfe\x2f\xba\xd7\xff\xa1\xdb\x97\x2f\xed\xbe\xfd\xda\xd4\xeb\x0c\x97\xbe\xd8\x37\x63\x59\x97\x63\x79\xed\x86\x05\x71\xa9\xa7\x94\x7a\xe8\xeb\xa7\xfb\x7f\x7c\xfa\x69\x5a\x29\xf5\x50\x55\xf7\xff\xee\xfa\x2f\xf3\x52\x29\x15\x0d\xca\xcf\xdd\xf3\xb8\xce\xb2\xc7\x0b\xfc\x50\x57\xf7\x4f\x5d\xbf\x2f\xc7\xc7\x76\x5f\x6e\x9a\x38\xe4\x7f\x7e\xd9\xef\x1e\x56\x57\xc5\x1b\xe3\xf1\xf5\xd8\x5c\x83\x4e\x61\xfb\x66\x1a\xf9\x6f\xde\x7b\x75\xb5\x6f\xa3\xd3\xea\x9f\x63\xbb\xdb\xfd\x12\x37\x99\xcb\xba\x09\xda\x8e\xbb\xe6\x31\xed\x39\x89\x4b\x15\xab\xb9\x8c\xb9\xc8\xd5\x4d\x95\x0f\xab\x85\x83\xb4\xda\xbc\x63\x73\x57\x7e\x6e\x76\xeb\xec\xef\x9f\x9b\x43\xa3\xf0\x3d\xd7\x9b\xbe\x7b\x3e\xee\xbb\xba\x99\xfb\x25\xbb\x32\xfb\xa6\x7f\xc6\xbe\x3c\x0c\x91\x86\x75\x96\xc4\x5d\x39\x36\xdf\xeb\xbc\x40\x6f\x21\x68\x36\xf4\xc3\xc2\xff\xb1\x1c\xb7\x4b\x4d\xc3\xf8\xba\x6b\xd6\xd9\x53\xbb\xdb\xdd\x7f\xa7\xd3\xf3\x97\x61\xec\xbb\x2f\xcd\xd4\x5a\xf7\x1a\x5c\x30\x3a\x38\x5c\x9a\x40\xa9\x78\xa6\x0a\x2d\xa0\x38\x6f\x42\x4e\xc6\x80\x71\xce\x92\x3a\xa9\xc2\x69\x70\x12\xd8\xa8\x9d\x22\xb0\x5e\x5b\xa2\xbc\x20\xb0\x68\x03\x2d\x08\x5e\x11\x87\x40\xde\x19\xce\x35\xa0\xf5\x26\x7c\x04\xd2\xdb\x52\xee\x34\x58\xed\x3e\xac\x51\xcd\xb1\x38\x9f\xa2\x87\x05\x08\x33\xc0\xea\x67\xe5\x1d\x78\x67\x34\xdb\x6b\xda\xea\x0f\xf5\xab\x42\x74\x20\xe8\x2d\xe7\xc4\x04\xac\x03\xaa\x4a\x15\x1a\xc8\x60\xa0\xbc\xd0\x60\x82\x15\xab\x0a\x0c\xe0\x58\x93\x33\x11\x8b\x7c\x70\xaa\x14\x43\x60\xe1\x2b\xf6\xb3\x4a\xf1\x2c\x7b\x13\xb9\xb0\x0c\xe2\x6d\x0c\xa9\xf3\xc2\x10\x68\xb2\x2e\xc4\xf0\x28\x4c\x3e\x2f\xac\x80\x66\xef\x8d\x9a\x03\x84\x09\x33\xc8\xc4\x11\x13\xd2\xe2\x52\x74\x2f\x98\x90\x10\xd8\x04\x9f\x4b\x00\x47\xd6\x25\x2e\x0c\x79\xc9\x85\xc1\x05\x34\xa8\x34\x78\xcd\x28\x98\x6b\xf0\xa2\x03\x2b\x06\x0a\xda\xb9\x5c\x03\xa3\x17\x52\x4e\x43\x70\x5a\x42\x24\x98\x4d\x3c\x34\x63\x80\xd0\xe7\x1a\x34\x5a\xa7\xac\x80\xa0\x73\x31\x95\x58\x9f\x66\x13\x21\xcf\xda\xda\x04\x39\x87\x14\xd4\x57\xb5\x57\xf1\xa0\x5d\x70\x79\x21\x0c\x22\x1c\xcc\x4c\x1d\xba\x89\x3a\xe7\x9d\xf3\xaa\x40\x0d\xec\xbc\x49\x75\x04\xf4\xc6\x4e\x3d\x62\x45\xe8\x06\x32\x06\x98\x8d\x71\x94\xeb\xa4\xd7\x1c\x82\x93\xd8\x04\xcc\x82\x2a\xd2\xe2\x25\x10\x9b\x3c\x52\x25\x46\x94\x06\xc3\x86\x7d\x4c\x3c\x0a\x41\x61\x00\x12\x6f\x6c\xac\xdd\x51\xf4\x8a\x27\x84\xce\x52\xc8\x35\x58\x12\x46\xb5\x8b\xc5\x38\xe3\x35\x4d\xf5\x59\x71\xa2\x26\x21\xd8\xdc\x32\x58\xb2\x96\xa7\x42\x34\x93\xcd\xc9\x83\x37\xc6\x7a\xa5\x41\x5b\xef\xa3\x09\x23\xda\xd8\x86\xc4\xde\xd9\xdc\x0a\x10\xdb\x64\x60\xbd\xb5\x36\xd7\x91\x3f\x1b\xf3\x13\x42\x91\xbc\x90\x18\x82\x29\x9e\x96\x75\x1c\x0f\x54\x02\xb0\x23\xc2\x44\xa3\x01\xb2\x88\x2e\x17\x0f\xcc\x2e\x38\x85\xe0\x75\x30\x94\x17\x08\xce\x88\x60\xea\x24\x01\xf6\x14\xec\x45\x8a\xf3\x55\x10\x68\xed\x42\xc8\x11\xc4\x5a\xed\x17\xc0\x5f\x80\x53\x2c\xd7\x3a\x76\x9c\x7a\x90\x02\x90\x26\xa2\xd8\x37\x41\x0c\xe6\x17\xad\x06\x23\x6c\xdd\x2d\x40\x28\xe8\x72\xad\x10\x90\x30\xd8\x48\x97\x17\x12\xaf\x96\x4d\xe6\xfc\x52\x11\x85\x05\x2f\xde\x61\x5e\xd8\x00\xc1\x5a\xe7\xd5\xef\x0a\xd9\x46\xb2\xc8\xc5\x01\xf4\xa0\x1d\x8b\xa5\x78\x4c\xda\xa3\xc6\x38\x21\x96\x01\x0d\x1b\xbc\x0a\xdb\x28\x8a\x04\x21\xb9\x48\x3c\x6d\xc1\x0e\xc4\x23\xa5\xce\x4d\xdc\x47\x62\x8c\x07\x23\x9a\x82\xda\x2a\xe3\x41\xbc\x31\x6e\x11\x2c\xab\x93\xba\xe8\x2f\xc2\x6d\x2e\x24\xa0\xc5\x39\x21\xf5\x87\xda\xab\x10\xaf\x31\xe3\x89\x73\x1d\xb3\x97\x00\x16\x59\xde\x3b\x78\xcf\xde\x87\x18\xda\x01\x63\xa0\xf9\x8d\x32\xa5\x90\x36\x71\x17\x69\xce\xdd\x73\xbc\x75\xc4\x61\xae\x53\xd6\x08\x64\xc8\x47\x0f\x04\xd6\xce\x07\x73\x23\x9d\xd4\xa2\x9f\xde\xee\x6d\x0e\xc6\x02\xb9\x38\xf5\x29\x69\x1b\x00\x8d\xb3\xc6\xe6\x26\x5e\x5b\x3a\x5d\x60\x0c\x8e\x59\x7c\x3a\x34\x9d\xe6\x87\x08\xbc\x35\xde\x98\x2b\xb4\x04\x8f\xe5\x5e\x3d\x16\x2d\x82\xf3\xe8\x29\x8d\xa0\x27\xcd\xf1\x66\x4b\xc0\x0d\x12\xe2\x05\x28\xd1\x46\x91\x05\xd2\x8e\xe6\x2d\x03\xc9\x8d\x7d\xb1\x38\x7c\x55\xbf\x2a\xf1\x10\x90\xad\x77\xe9\xb3\x41\xc6\xa3\xfa\x5d\x91\x36\x10\x28\x76\xf5\x9b\x42\x1d\x18\x11\x8b\xa2\x4e\x91\x78\x87\xc6\x84\xab\xb0\x55\x46\x83\x77\xe2\xc8\xdc\x48\x91\x6d\x22\xd0\xde\x98\x38\x69\x8e\x20\x56\xcd\x71\xa8\x6c\xc0\x34\x54\x01\x85\x28\x6e\x24\x10\x10\x99\xbc\xc2\xd8\x68\xd6\x4f\x43\x35\x5b\x52\xbe\x58\x2e\x88\x99\x11\xa3\xb6\xe9\x16\xc5\x80\xce\xdf\x48\x5f\x2f\xdf\xc7\xf8\x69\x8e\xdf\x58\x23\xc6\x5e\xc1\xcb\x3f\x60\x77\x38\x34\xd5\xd8\xf5\x45\xf5\xdc\x9f\xca\xf1\xb9\x6f\xd6\x99\x5e\x7e\x9f\x56\x9b\xc7\xbb\x87\xf8\x67\xf3\x78\xf7\x9f\x00\x00\x00\xff\xff\x35\x32\xd3\xa6\x1d\x0e\x00\x00") +var _dataDriverAssetsIconSvg = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x57\x4d\x8f\xe3\xc8\xcd\xbe\x0f\x30\xff\xa1\x5e\xcd\x65\x06\xaf\x44\x17\x59\x5f\x64\xa7\xdd\x0b\x24\x93\xc5\xee\x61\x2f\xc9\x66\x03\xec\x4d\x23\xa9\x6d\x61\x6c\xa9\x21\xa9\xed\xee\xf9\xf5\x41\xe9\xcb\xee\xf1\x6e\x10\x5f\xc4\x7a\xf8\x14\x8b\x7c\x8a\x55\x92\xef\x7f\x78\x39\x1e\xd4\xa9\xea\xfa\xba\x6d\xb6\x09\x82\x4e\x54\xd5\x14\x6d\x59\x37\xbb\x6d\xf2\xaf\x5f\x7f\xcc\x38\x51\xfd\x90\x37\x65\x7e\x68\x9b\x6a\x9b\x34\x6d\xf2\xc3\xc3\xfb\x77\xf7\xff\x97\x65\xea\x6f\x5d\x95\x0f\x55\xa9\xce\xf5\xb0\x57\x3f\x37\x5f\xfb\x22\x7f\xaa\xd4\xc7\xfd\x30\x3c\xdd\x6d\x36\xe7\xf3\x19\xea\x19\x84\xb6\xdb\x6d\x3e\xa9\x2c\x7b\x78\xff\xee\xfd\xbb\xfb\xfe\xb4\x7b\xff\x4e\x29\xf5\x72\x3c\x34\xfd\x5d\x59\x6c\x93\x79\xce\xd3\x73\x77\x18\xb9\x65\xb1\xa9\x0e\xd5\xb1\x6a\x86\x7e\x83\x80\x9b\xe4\x8a\x5f\x5c\xf8\x45\xcc\xa0\x3e\x55\x45\x7b\x3c\xb6\x4d\x3f\x4e\x6d\xfa\x0f\xd7\xec\xae\x7c\x5c\xe9\x31\xa5\xb3\x19\x59\x28\x22\x1b\x4d\x1b\xa2\xac\x2b\x1f\xb3\xfe\xb5\x19\xf2\x97\xec\xbb\xb9\xfd\x69\xf7\x47\x73\x49\x6b\xbd\xe9\x4f\xbb\x2b\xea\xff\x48\xbb\xeb\xdb\xb2\x7e\x6a\xcb\x7a\xe5\x2f\x00\xf4\xed\x73\x57\x54\x8f\x6d\xb7\xab\xa0\xa9\x86\xcd\xe7\x5f\x3f\xaf\xce\x4c\x43\x39\x94\xd7\x71\x16\x61\xdf\xac\xfb\x46\xed\x26\x3f\x56\xfd\x53\x5e\x54\xfd\x66\xc1\xa7\x00\xe7\xba\x1c\xf6\xdb\xc4\x69\x3d\x8d\xf7\x55\xbd\xdb\x0f\x57\xc0\xa9\xae\xce\x7f\x6d\x5f\xb6\x89\x56\x5a\xa1\x21\x20\x41\xef\x57\x2b\xcc\xac\x4b\xcf\xe0\x84\xd4\xe5\x36\xe9\x4f\x3b\x9e\x47\xf3\xa2\x77\x2b\x51\x83\x10\x58\xf5\xd1\x95\xb9\x67\x29\x0c\x9a\x54\x91\x46\xc9\x34\x66\x68\x3f\x4d\xd3\x96\x92\xef\xca\xb6\x88\x25\x6c\x93\xba\x68\x1b\x88\x32\x3e\x44\xc2\x7d\x59\x3d\xf6\x23\x73\x5a\x30\x0e\x29\x51\x9b\xc9\xb9\xce\x8e\x53\xcb\x58\xc8\x15\xf5\x4b\xde\xcf\x12\x28\xf5\x94\xef\xaa\xa2\x3d\xb4\xdd\x36\xf9\xf0\x38\xfe\x16\xcf\x97\xb6\x2b\xab\x6e\xf1\xf9\xf1\xf7\xd6\xd7\x3e\xe5\x45\x3d\xbc\x4e\xa7\x65\x89\xbf\x54\x1b\x03\xaf\x04\xfd\x27\x84\x7e\x9f\x97\xed\x79\x9b\xd0\x8d\xf7\x5b\xdb\x1e\x63\x60\xc3\xc2\x9e\xe4\xc6\x5f\xbc\x6c\x93\x60\x41\x0c\x91\xb7\xb7\xde\xd7\x6d\x42\x8c\xc0\xd6\x72\xb8\xf1\x96\x6d\xf1\x1c\x4f\x54\xf6\xdc\xd4\x43\xbf\x4d\x8e\xc7\xdb\x00\xcf\x5d\x17\x19\x87\xfc\xb5\xea\xb6\xc9\xf8\xc0\x85\xd5\xef\xdb\xf3\xae\x8b\x4a\x3e\xe6\x87\x8b\x94\x73\xb4\xa7\x97\x9b\x68\xe7\xba\x29\xdb\x73\x36\x37\x1c\x0a\xdd\xaa\x31\x53\x96\x1e\x44\x8d\xb7\x79\xcf\x9c\x97\x6d\x92\xf1\x9f\x39\x5f\xff\x9b\xf3\x98\xbf\xd4\xc7\xfa\x5b\x55\x6e\x13\x5c\x5b\xe5\x58\x0d\x79\x99\x0f\xf9\x55\x83\x2c\x90\x9b\x5a\x4d\xa9\xfb\xae\x7c\xbc\xfb\xc7\xe7\x1f\xe7\xa1\x52\xf7\x45\x71\xf7\xef\xb6\xfb\xba\x8c\x95\x52\x91\x92\x7f\x69\x9f\x87\x6d\x92\x3c\x5c\xf0\xfb\xb2\xb8\x7b\x6c\xbb\x63\x3e\x3c\xd4\xc7\x7c\x57\xc5\x8b\xe0\xff\x5f\x8e\x87\xfb\xcd\xc5\xf1\x96\x3d\xbc\x3e\x55\x57\x71\xa7\xc8\x5d\x35\xdd\x0b\x7f\x78\x43\x96\xc5\xb1\x8e\xb3\x36\xff\x1c\xea\xc3\xe1\xe7\xb8\xcc\x52\xde\x55\xd8\x7a\x38\x54\x0f\xe3\xb2\x93\xb9\xd6\xb2\x99\x8b\x59\x8a\xdd\x5c\x57\x7b\xbf\x59\xd4\x98\x86\xbb\xef\xb5\x3d\xe4\x5f\xaa\xc3\x36\xf9\xfb\x97\xaa\xa9\x14\xde\x48\xbf\xeb\xda\xe7\xa7\x63\x5b\x56\x73\x17\x25\x57\x3a\xbf\x6d\xab\xa1\xcb\x9b\x3e\x2a\xb2\x4d\x46\xf3\x90\x0f\xd5\x47\x9d\x66\xe8\x2d\x04\xcd\x86\x3e\xad\xdb\xf1\x94\x0f\xfb\xb5\xba\x7e\x78\x3d\x54\xdb\xe4\xb1\x3e\x1c\xee\x3e\xe8\xf1\xf7\x97\x7e\xe8\xda\xaf\xd5\xd4\x71\x77\x1a\x5c\x30\x3a\x38\x5c\x1b\x43\xa9\xb8\xcb\x0a\x2d\xa0\x38\x6f\x42\x4a\xc6\x80\x71\xce\x92\x3a\xa9\xcc\x69\x70\x12\xd8\xa8\x83\x22\xb0\x5e\x5b\xa2\x34\x23\xb0\x68\x03\x2d\x08\x5e\x10\x87\x40\xde\x19\x4e\x35\xa0\xf5\x26\xdc\x02\xe3\xd3\x52\xea\x34\x58\xed\x6e\xc6\xa8\xe6\x58\x9c\x4e\xd1\xc3\x02\x84\x19\x60\xf5\x93\xf2\x0e\xbc\x33\x9a\xed\x25\x6d\xf5\xbb\xfa\x45\x21\x3a\x10\xf4\x96\x53\x62\x02\xd6\x01\x55\xa1\x32\x0d\x64\x30\x50\x9a\x69\x30\xc1\x8a\x55\x19\x06\x70\xac\xc9\x99\x88\x45\x41\x78\xac\x14\x43\x60\xe1\x0b\xf6\x93\x1a\xe3\x59\xf6\x26\x6a\x61\x19\xc4\xdb\x18\x52\xa7\x99\x21\xd0\x64\x5d\x88\xe1\x51\x98\x7c\x9a\x59\x01\xcd\xde\x1b\x35\x07\x08\x13\x66\x90\x89\x23\x26\xa4\xc5\x8d\xd1\xbd\xe0\x88\x84\xc0\x26\xf8\x54\x02\x38\xb2\x6e\xd4\xc2\x90\x97\x54\x18\x5c\x40\x83\x4a\x83\xd7\x8c\x82\xa9\x06\x2f\x3a\xb0\x62\xa0\xa0\x9d\x4b\x35\x30\x7a\x21\xe5\x34\x04\xa7\x25\x44\x81\xd9\xc4\x4d\x33\x06\x08\x7d\xaa\x41\xa3\x75\xca\x0a\x08\x3a\x17\x53\x89\xf5\x69\x36\x11\xf2\xac\xad\x1d\x21\xe7\x90\x82\xfa\xa6\x8e\x2a\x6e\xb4\x0b\x2e\xcd\x84\x41\x84\x83\x99\xa5\x43\x37\x49\xe7\xbc\x73\x5e\x65\xa8\x81\x9d\x37\x63\x1d\x01\xbd\xb1\x53\x8f\x58\x11\xba\x82\x8c\x01\x66\x63\x1c\xa5\x7a\xf4\x6b\x0e\xc1\x49\x6c\x02\x66\x41\x15\x65\xf1\x12\x88\x4d\x1a\xa5\x12\x23\x4a\x83\x61\xc3\x3e\x26\x1e\x8d\xa0\x30\x00\x89\x37\x36\xd6\xee\x28\xce\x8a\x3b\x84\xce\x52\x48\x35\x58\x12\x46\x75\x88\xc5\x38\xe3\x35\x4d\xf5\x59\x71\xa2\x26\x23\xd8\xd4\x32\x58\xb2\x96\xa7\x42\x34\x93\x4d\xc9\x83\x37\xc6\x7a\xa5\x41\x5b\xef\x23\x85\x11\x6d\x6c\x43\x62\xef\x6c\x6a\x05\x88\xed\x48\xb0\xde\x5a\x9b\xea\xa8\x9f\x8d\xf9\x09\xa1\x48\x9a\x49\x0c\xc1\x14\x77\xcb\x3a\x8e\x1b\x2a\x01\xd8\x11\xe1\x28\xa3\x01\xb2\x88\x2e\x15\x0f\xcc\x2e\x38\x85\xe0\x75\x30\x94\x66\x08\xce\x88\xe0\xd8\x49\x02\xec\x29\xd8\xd5\x8a\xe7\x2b\x23\xd0\xda\x85\x90\x22\x88\xb5\xda\x2f\x80\x5f\x81\x53\x2c\xd7\x3a\x76\x3c\xf6\x20\x05\x20\x4d\x44\xb1\x6f\x82\x18\x4c\x57\xaf\x06\x23\x6c\xdd\x35\x40\x28\xe8\x52\xad\x10\x90\x30\xd8\x28\x97\x17\x12\xaf\x96\x45\xe6\xfc\xc6\x22\x32\x0b\x5e\xbc\xc3\x34\xb3\x01\x82\xb5\xce\xab\xdf\x14\xb2\x8d\x62\x91\x8b\x07\xd0\x83\x76\x2c\x96\xe2\x36\x69\x8f\x1a\xe3\x09\xb1\x0c\x68\xd8\xe0\xc5\xd8\x47\x53\x24\x08\xc9\x6a\xf1\xb4\x04\x3b\x10\x8f\x34\x76\xee\xa8\x7d\x14\xc6\x78\x30\xa2\x29\xa8\xbd\x32\x1e\xc4\x1b\xe3\x16\xc3\xb2\x3a\xa9\xd5\xbf\x1a\xd7\xb9\x90\x80\x16\xe7\x84\xd4\xef\xea\xa8\x42\xbc\xc6\x8c\x27\x4e\x75\xcc\x5e\x02\x58\x64\xf9\x7e\x82\xf7\xec\x7d\x88\xa1\x1d\x30\x06\x9a\x9f\x28\x53\x0a\xe3\x22\x6e\xb5\xe6\xdc\x3d\xc7\x5b\x47\x1c\xa6\x7a\xcc\x1a\x81\x0c\xf9\x38\x03\x81\xb5\xf3\xc1\x5c\x59\x27\xb5\xf8\xa7\xa7\x7b\x9b\x83\xb1\x40\x2e\x9e\xfa\x31\x69\x1b\x00\x8d\xb3\xc6\xa6\x26\x5e\x5b\x7a\xbc\xc0\x18\x1c\xb3\xf8\x71\xd3\xf4\x78\x7e\x88\xc0\x5b\xe3\x8d\xb9\x40\x4b\xf0\x58\xee\x65\xc6\xe2\x45\x70\x1e\x3d\x8d\x47\xd0\x93\xe6\x78\xb3\x8d\xc0\x15\x12\xe2\x05\x28\x91\xa3\xc8\x02\x69\x47\xf3\x92\x81\xe4\x8a\x9f\x2d\x13\xbe\xa9\x5f\x94\x78\x08\xc8\xd6\xbb\xf1\xb5\x41\xc6\xa3\xfa\x4d\x91\x36\x10\x28\x76\xf5\x9b\x42\x1d\x18\x11\x8b\xa2\x4e\x51\x78\x87\xc6\x84\x8b\xb1\x57\x46\x83\x77\xe2\xc8\x5c\x59\x51\x6d\x22\xd0\xde\x98\x78\xd2\x1c\x41\xac\x9a\xe3\xa1\xb2\x01\xc7\x43\x15\x50\x88\xe2\x42\x02\x01\x91\xc9\x2b\x8c\x8d\x66\xfd\x74\xa8\x66\x26\xa5\x0b\x73\x41\xcc\x8c\x18\xb5\x1f\x6f\x51\x0c\xe8\xfc\x95\xf5\xed\xf2\x82\x8c\xef\xe7\xf8\x9a\x35\x62\xec\x15\xba\x7e\x20\xb6\x4d\x53\x15\x43\xdb\x65\xc5\x73\x77\xca\x87\xe7\xae\xda\x26\x7a\xfd\xa8\xda\xec\xe2\x7f\xc3\xf8\xad\xf3\xf0\xfe\xdd\x7f\x02\x00\x00\xff\xff\x70\x54\x97\xe5\x5c\x0e\x00\x00") func dataDriverAssetsIconSvgBytes() ([]byte, error) { return bindataRead( @@ -87,12 +96,12 @@ func dataDriverAssetsIconSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/driver/assets/icon.svg", size: 3613, mode: os.FileMode(420), modTime: time.Unix(1604862634, 0)} + info := bindataFileInfo{name: "data/driver/assets/icon.svg", size: 3676, mode: os.FileMode(438), modTime: time.Unix(1715717688, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _dataDriverDeviceJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x54\x4d\x6f\xdb\x30\x0c\xbd\xfb\x57\x10\xbe\x38\x46\x52\x75\xbd\xc6\xf3\x80\x6e\xcd\xd6\x02\xfd\x42\x93\xfb\x20\xdb\x4c\xa2\x4d\x91\x32\x89\xce\x92\x19\xfa\xef\x83\x1c\x3b\x75\x3e\xd0\xf6\xb2\x0f\x9f\x04\x3e\x52\x7c\xe2\x7b\x66\x54\x5a\x04\x4b\x46\xe4\x14\x25\x41\x90\x6b\x65\x09\xae\xf5\x02\x37\x90\x82\xc1\x1f\xa5\x30\xd8\x8b\xe6\x3e\x10\xc5\x49\x83\x5f\xe1\x4a\xe4\xd8\x4d\x60\xec\x9c\xb1\x73\x29\xb2\xf3\xa2\xc6\xd8\x37\xeb\xd3\x83\xaa\x02\x31\x05\xa5\x09\xd8\x5d\x29\x49\x34\x95\x67\xce\x05\x55\x75\xe6\x31\xf6\x89\x2f\x79\x26\xa4\x20\x81\xb6\x06\xb6\x3d\xf2\x36\xbc\xb9\xe3\x4b\x48\xa1\x0a\x00\x00\x7c\x91\xe1\x6a\x86\x20\x94\x20\xc1\xe5\x41\xbd\x73\x75\x5a\x58\x55\xcc\xb9\x70\xd8\x54\xd5\xa1\x7c\xce\x95\x42\x19\x0e\xe1\x62\xf0\x1c\xfd\x8e\x9b\x70\x08\xe1\x78\x72\x39\x19\x85\x9d\xf8\x8a\xcb\x12\x27\x9b\x25\x7a\x34\xd3\x5a\x22\x57\x61\x0d\xbb\xc1\x8e\x09\xaa\xa2\xd3\x51\x72\x4b\xfb\x74\xfe\x18\x85\xc0\xd5\xa3\x45\x69\xf1\xa5\x91\x35\x59\xaa\xd8\x0d\xbc\x3d\xe7\x92\x5b\x5b\xeb\xbc\xe0\x24\xf2\x46\x16\x5c\x13\xaa\xc2\xb6\xfa\x56\x41\xdd\x4d\xab\x1b\x25\xa8\x17\x77\x5e\xb2\xe2\x06\x44\xb1\x86\x14\x68\x2e\x2c\x9b\x21\x5d\x71\xe2\xbd\x98\x71\x22\x23\xb2\x92\xd0\xb2\x1b\x55\xe0\x3a\xd9\x95\xb4\x72\x77\x6d\xd0\xcc\x6e\x0b\x9f\x36\x43\xb7\xe3\x69\x4b\x74\x1b\xbc\xc1\x1a\xbb\xf1\x1e\x5b\xe4\x65\x9d\x5e\xd3\xeb\x0d\xba\xb5\x9f\x1b\x1c\x31\xef\x58\xa9\xc3\xf0\x55\x4b\xfd\x3d\xca\xc1\xf1\xa9\xe6\xed\x2d\xf8\x9a\x4e\x07\x25\xfb\x4f\x3d\x11\xb2\xe5\x12\x0d\x6b\x7c\xb7\x77\x5b\x9c\x34\x3f\x40\xbb\x3e\x14\x02\xbb\x2f\x17\x19\x9a\x8f\x25\x91\x56\x16\xde\xf9\x9b\xea\xac\xc6\x07\xe2\x17\x8e\xd6\x64\xf8\x68\x85\x8a\x6e\x85\x25\x54\x68\xec\x91\x9f\x2d\xca\x69\x63\xe8\x67\xd7\x4e\xb5\x81\x9e\x44\x82\xac\xbe\x1d\x52\xb8\x48\xda\xf3\xfb\xd4\xbb\xf6\xa0\xbb\x73\x2d\xde\xef\xc7\x07\x42\xf9\x0e\x2c\x33\xa2\x98\x21\xd3\xaa\x17\xa1\x27\x74\x16\x41\x7f\x8b\xd4\x5b\xf7\x46\x11\x9a\x29\xcf\xf1\x9e\x2f\x10\xfa\x10\x3d\xe3\xdb\xcd\x7a\x59\x14\x06\xad\xf5\xd0\xd0\x43\x0d\x19\x9f\xf9\xf8\x34\x1a\x8f\xbf\x8e\xaf\x1f\x9e\x26\xd1\x00\x7a\xb5\xa8\x31\xa4\x1f\x4e\xf8\x65\x7b\xa1\x11\x2b\x34\x8c\x8c\x98\xcd\xda\x17\x3c\xfa\xcb\xb1\xf8\x2c\xf5\xcf\x9e\x4f\x1a\x40\x05\xe1\xb6\x47\x38\x6c\x9b\xb9\x53\xd1\x01\x84\x4b\x5f\xdc\xda\xc8\xce\xb5\xa1\x10\x5c\xbc\xef\xa3\x38\xf9\x07\x33\xb9\x7d\xb8\xff\xf2\x1f\x8c\x44\x6a\x35\x7b\x71\x22\xae\x71\x77\xe7\x9f\xf0\xab\x7e\xa1\x8b\x52\x22\xc3\xf5\x52\x1b\xb2\x90\x1e\x2e\xee\x24\xf8\x1d\x00\x00\xff\xff\x5c\xf8\xbf\xae\xc7\x07\x00\x00") +var _dataDriverDeviceJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x94\x5d\x6f\xda\x3c\x14\xc7\xef\x91\xf8\x0e\x47\xb9\x09\x11\xe0\x3e\xbd\x25\x4f\x26\x75\x2b\x5b\x2b\xf5\x4d\x85\xfb\xc9\x49\x4e\xc1\x9b\xb1\x99\x7d\xc2\x60\x51\xbe\xfb\xe4\xbc\x40\x9a\xd2\xd1\x9b\x4e\xeb\x55\x73\xfe\xe7\xcd\xfe\xff\xb0\x9f\x59\x04\x4b\x46\x24\xe4\x87\xfd\x5e\xbf\x97\x68\x65\x09\xae\xf4\x0a\x77\x10\x81\xc1\x1f\x99\x30\x38\xf0\x97\x2e\xe0\x07\x61\x93\x70\x89\x1b\x91\x60\x3b\x83\xb1\x33\xc6\xce\xa4\x88\xcf\xd2\x52\x63\xdf\x6c\x99\xdf\xef\xe5\x39\x88\x27\x50\x9a\x80\xdd\x66\x92\x44\x5d\x3b\x2e\x0a\xa7\x8d\x9d\xc8\x3e\xf1\x35\x8f\x85\x14\x24\xd0\x56\x4a\x35\x27\x69\xe2\xbb\x5b\xbe\x86\x08\xf2\x7e\x0f\x00\xc0\x95\x19\xae\x16\x08\x42\x09\x12\x5c\x76\x3a\xb8\x06\x2e\xcf\xcb\x73\x56\x14\xde\xa4\xa9\x2b\x63\xc9\x92\x2b\x85\xd2\x9b\xc0\xf9\xa8\x15\xfe\x8e\x3b\x6f\x02\xde\x6c\x7e\x31\x9f\x7a\x6d\x61\xc3\x65\x86\xf3\xdd\x1a\x9d\x1c\x6b\x2d\x91\x2b\xaf\xd2\x8b\xd1\x61\x1f\x54\x69\x7b\xae\xe4\x96\x9e\x6f\xf5\xae\x8b\xf4\x7b\x45\x7d\xd7\x28\x2d\xfe\xf1\x0a\xf7\x89\x2a\x3d\x98\xb0\xff\x48\x24\xb7\xb6\x04\x60\xc5\x49\x24\xb5\x59\xb8\x25\x54\xa9\x6d\x7c\xcf\x5d\x0b\x37\x57\xab\x6b\x25\x68\x10\xb4\xcf\xb5\xe1\x06\x44\xba\x85\x08\x68\x29\x2c\x5b\x20\x5d\x72\xe2\x83\x80\x71\x22\x23\xe2\x8c\xd0\xb2\x6b\x95\xe2\x36\x3c\xd4\x34\x1c\xb4\x01\x69\x2e\xb3\xd2\x5f\xc1\xa4\x3d\xf4\x15\x58\xda\x33\xde\x02\xcd\xfe\xbe\x8f\xc0\x73\xc2\xbb\x93\x1e\xbe\xc5\xcb\xe6\xaf\x18\xbd\xdc\xbf\x0d\x59\x6b\xcf\xd3\xb0\xfd\xd5\xc5\x0f\x9f\xc5\x73\x8b\x4b\x36\x4f\xda\xd6\x2d\xea\x9c\xf9\x58\xcc\x66\x6b\x34\xac\xa6\xf1\x59\xc3\x20\xdc\xff\x42\xf6\xcf\x8d\x42\x60\x77\xd9\x2a\x46\xf3\x31\x23\xd2\xca\xc2\x7f\x65\xb7\x2a\xb3\xa6\x43\xfc\xc2\xe9\x96\x0c\x9f\x6e\x50\xd1\x8d\xb0\x84\x0a\x8d\x7d\x89\xba\x45\xf9\x54\xb3\xde\x02\xfa\x49\x1b\x18\x48\x24\x88\xcb\x09\x10\xc1\x79\xd8\xfc\xff\x7f\xe4\x78\xee\x6c\x50\x14\x8d\x3e\x1c\x06\x5d\xe7\xdc\x0c\x16\x1b\x91\x2e\x90\x69\x35\xf0\xd1\xed\x34\xf6\x61\x58\x29\xd5\x8b\x7b\x91\xa6\x06\xad\x85\x21\xf8\x13\x27\xd5\xd3\x86\xe0\x8f\x1f\x1e\xa7\xb3\xd9\xd7\xd9\xd5\xfd\xe3\xdc\x1f\xc1\xa0\xf4\x30\x80\xe8\xc3\x31\x42\xaa\x8e\x46\x6c\xd0\x30\x32\x62\xb1\x68\x76\x7c\x70\xdd\x31\xfd\x2c\xf5\xcf\x81\x4b\x1a\x41\x0e\x5e\x35\xc4\x9b\x34\xd3\x8a\x63\xd1\x11\x78\x6b\x57\xdc\x60\x63\x97\xda\x90\x07\x45\xd0\xe1\x26\x08\xdf\xe3\xd8\x37\xf7\x77\x5f\xfe\x85\x53\x4b\xad\x16\x27\x0e\x5d\xec\x59\x6d\x43\x5e\xbf\xed\x2b\x9d\x66\x12\x19\x6e\xd7\xda\x90\x85\xa8\xfb\x48\x87\xfd\xde\xef\x00\x00\x00\xff\xff\xe9\x6e\x77\xbb\xcf\x07\x00\x00") func dataDriverDeviceJsBytes() ([]byte, error) { return bindataRead( @@ -107,12 +116,12 @@ func dataDriverDeviceJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/driver/device.js", size: 1991, mode: os.FileMode(420), modTime: time.Unix(1604862634, 0)} + info := bindataFileInfo{name: "data/driver/device.js", size: 1999, mode: os.FileMode(438), modTime: time.Unix(1715717688, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _dataDriverDriverComposeJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x95\x4f\x6b\xe3\x30\x10\xc5\xef\xf9\x14\x83\xcf\x21\xb9\xf7\x56\xd2\xc3\xe6\xb2\x14\x96\x3d\x95\xb2\x4c\x6c\x61\x0f\x48\xb2\xd0\x8c\xc3\x96\xe0\xef\xbe\x48\x72\xfe\xf8\x5f\xc8\x36\xa1\xbd\x05\xe5\xcd\x7b\xef\x27\xd9\xf2\x61\x01\x00\x90\x51\x91\x3d\x41\x76\x38\xc0\xea\xc5\xd3\x5e\xf9\x9f\x68\x14\xb4\x6d\xb6\x4c\x7f\x5b\x34\x2a\x7b\x82\x24\x8e\x2b\xca\x4e\x0f\x44\x45\xdb\x8d\xe5\x1a\x99\xfb\xba\x4d\x58\xba\x70\xce\xd1\xe1\x8e\x34\x09\xa9\xa0\x7c\x7b\xef\xd6\xc9\x60\x19\x57\x2e\x32\x35\xfa\x32\xd4\xc8\xd6\x45\xf4\xe2\xf5\x28\x7f\x8d\xcc\x4a\x78\x9d\xc6\xd7\x71\x62\xe5\x6c\xd9\xc5\x45\x1b\x36\xa8\xf5\x7f\xd9\xc4\x89\x68\xd3\xc3\x73\x48\x3e\x74\x3e\x59\x9f\xbb\x5e\x6c\xaa\x26\x96\x3f\x3b\x4f\x45\xe0\x59\xf6\x15\xa2\x8c\xd3\x28\xea\xa4\x2b\xd4\x9e\xf2\xb1\xae\x76\x42\xb5\xed\x6f\xc7\x99\x87\x6c\xd9\x68\x0c\x55\xc4\x37\xaa\x27\x68\x07\x46\x16\xf7\x54\x62\x30\x9b\xf6\xb2\xea\xaf\x8c\xca\xf4\x1d\x17\x13\xde\xf3\xe0\x33\x40\xb7\x82\xdf\xda\x17\x8b\xe2\xde\xba\x97\x16\x57\xda\x4e\x26\xa5\x94\xe3\xb3\xcb\x4a\x84\x6c\xc9\xd7\x9e\x0d\xf9\x70\xd1\xae\xf4\x75\xe3\x86\x79\x1a\x77\x4a\x4f\x03\xa7\xf7\xee\x25\x16\x80\x02\x05\xb3\xab\xe7\x9d\x57\xa4\x0b\x1f\x87\xde\x46\x66\x63\xfb\xcb\xed\x70\xc3\x5a\xa3\xee\xa9\xe6\x8c\x68\x9e\x61\xc0\xf2\xec\x5c\x36\xa9\x69\x67\x9c\xf7\xa8\x9b\x98\xff\xa3\x36\xca\xa0\x50\x3e\x9e\x9f\x98\xbd\x4a\x9b\x6e\x82\xaf\x01\x4e\x37\xcd\x27\x99\xe7\xd2\x2b\xb2\x72\x4b\xf8\xd6\x82\xa9\x59\x20\x47\x56\x0c\x52\x29\x48\xe8\x10\xae\x78\xa0\xb4\xc4\xe1\x37\x76\x7f\xa7\x47\x2d\x6c\xc1\x12\x76\x8d\x00\xd7\xe6\x38\xc4\xc0\x8d\x73\xb5\x17\x30\x8d\x16\x72\xba\x27\xe7\x15\x6c\x2d\x48\x55\xb3\x3a\xc7\x7d\x80\xa1\xb2\x12\xb0\xb5\x80\x41\xc9\xab\xd5\xcc\x46\xdc\x7d\xa4\x58\x14\x5e\xf1\xf0\x5d\x3e\x89\x1e\x7c\xa6\x09\xfb\xb9\xcb\xfc\xe4\xd9\xde\xcd\x9c\xe7\xcd\xf6\xf5\x6b\x88\x37\x9b\xdf\xb0\x7d\xfd\x4e\xd2\x5f\xca\x13\xce\x82\x1c\x69\x25\x7c\x21\x1e\x01\xdb\xc5\x3d\x0c\xb8\xb7\xf2\x3e\xfc\x94\x2c\xda\xc5\xbf\x00\x00\x00\xff\xff\x19\xa1\x48\x70\x9b\x09\x00\x00") +var _dataDriverDriverComposeJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x95\x4f\x6b\xe3\x30\x10\xc5\xef\x81\x7c\x87\xc1\xe7\x90\xdc\x7b\x2b\xe9\x61\x73\x59\x0a\xcb\x9e\x4a\x59\x26\xb6\xb0\x07\x24\x59\x68\xc6\x61\x4b\xc8\x77\x5f\x24\x39\x89\xe3\x3f\x21\x5b\x4c\x7b\x4b\xe4\x37\x6f\xde\x6f\x24\x5b\xc7\xe5\x02\x00\x20\xa3\x22\x7b\x82\xec\x78\x84\xf5\x8b\xa7\x83\xf2\x3f\xd1\x28\x38\x9d\xb2\x55\xfb\xdc\xa2\x51\xd9\x13\xb4\xf2\xb8\xa4\xec\x78\x49\x92\x9c\xce\x95\xb9\x46\xe6\x5b\xe5\x36\x2c\x75\xdd\x73\x74\xb8\x27\x4d\x42\x2a\x48\xdf\xde\xcf\x0f\xc8\x60\x19\x97\xba\x8d\x35\xfa\x32\x84\xc9\x36\x45\xb4\xe3\xcd\x20\xc4\x06\x99\x95\xf0\x26\xd5\x6f\x62\xc5\xda\xd9\xf2\xdc\x31\xfa\xb0\x41\xad\xff\xcb\x27\x56\x44\x9f\x1e\xa4\x43\xf2\x21\xf8\xd5\xbd\x13\xb8\x33\x60\x4d\x2c\x7f\xf6\x9e\x8a\x40\xb5\xea\x49\x44\x19\xa7\x51\xd4\x45\x58\xa8\x03\xe5\x23\xc2\xda\x09\xd5\xb6\x37\x96\x2b\x16\xd9\xb2\xd1\x18\xf2\x88\x6f\xd4\xad\xe2\xd4\xf7\xb2\x78\xa0\x12\x83\xdf\x84\x9d\x55\x7f\x65\x90\xa8\x67\x7a\xfd\xdb\xf5\xbf\x33\x82\x29\xb2\x87\x47\xf0\x70\x6c\x2c\x8a\x39\x52\x77\x6d\xee\x85\x1e\x6f\xd7\xb6\xba\x1c\x6b\x56\x22\x64\x4b\xbe\x7f\x64\xe4\xc3\x45\xcf\xd2\xd7\x8d\x1b\x74\xd5\xb8\x57\x7a\x02\x3e\xbd\x9a\x2f\x31\x07\x14\x28\xd8\x47\xef\x9b\xe5\x15\xe9\xc2\xc7\xb2\xb7\xa1\xdf\x48\x8b\xee\x68\xdc\x20\xdc\x00\x21\x85\x9d\x52\xdd\x41\xe9\x21\x3d\x3b\x97\x8d\x8b\xfa\x48\x97\xc2\x03\xea\x26\x66\xf8\x51\x1b\x65\x50\x28\x1f\x71\x18\xab\xbe\x4f\x9d\x3e\x19\x5f\x06\x9e\xbe\x4a\x9f\x66\x9f\x4c\x50\x91\x95\x87\x02\xec\x2c\x98\x9a\x05\x72\x64\xc5\x20\x95\x82\x34\x01\x08\x37\x03\x50\x5a\xe2\xf0\x1b\xdb\xc7\xe9\xf4\x85\x41\xac\x60\xdf\x08\x70\x6d\xce\x45\x0c\xdc\x38\x57\x7b\x01\xd3\x68\x21\xa7\x6f\xe4\xbc\x86\x9d\x05\xa9\x6a\x56\xd7\x76\x1f\x60\xa8\xac\x04\x6c\x2d\x60\x50\xf2\x6a\x3d\x35\x8c\x39\x76\x17\x8b\xc2\x2b\x1e\xbc\xea\x17\xd5\xec\xdb\x9b\xe8\x9f\xdb\xb6\x9f\xde\xe6\x39\xd8\xf3\xbc\xd9\xbd\x7e\x19\xf9\x76\xfb\x1b\x76\xaf\xdf\x4e\xfc\x4b\x79\xc2\x69\x9e\x33\xb5\x84\x5b\x65\x1e\xe8\xb6\xe3\x9c\xe0\xb7\x4b\xef\xc3\xfb\x67\xb9\x38\x2d\x17\xff\x02\x00\x00\xff\xff\xb1\x5f\x77\x10\xf3\x09\x00\x00") func dataDriverDriverComposeJsonBytes() ([]byte, error) { return bindataRead( @@ -127,12 +136,12 @@ func dataDriverDriverComposeJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/driver/driver.compose.json", size: 2459, mode: os.FileMode(420), modTime: time.Unix(1604867793, 0)} + info := bindataFileInfo{name: "data/driver/driver.compose.json", size: 2547, mode: os.FileMode(438), modTime: time.Unix(1715717688, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _dataDriverDriverFlowComposeJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x53\xcd\x6a\xf3\x30\x10\xbc\xfb\x29\x16\x91\x63\x92\x07\x30\x7c\x97\xf0\xf5\x50\x28\xa1\xd0\xde\x4a\x0e\x0a\xde\xba\xa6\xb2\x64\xd6\xb2\x4b\x31\x7a\xf7\x22\xd9\x75\xfc\x27\xdb\x04\x37\xa7\xb0\x9a\x1d\xcd\x8c\xc6\x55\x00\x00\xc0\x34\x25\x71\x8c\x94\xb3\x10\xde\xdc\xc4\xfe\xaa\xf6\x9f\xc3\x24\x11\x0b\x81\x55\x15\x1c\xff\x53\x52\x22\x9d\x79\x8a\x60\xcc\x21\x23\xcc\x73\xb6\xef\x83\x75\xa2\x05\xb2\x70\xc0\xe1\x8e\x50\x5a\x9e\x53\xa1\xb5\x92\xf0\x50\xa2\xd4\xac\x07\x32\x43\x2a\xf5\x89\xb2\xaf\x6c\x5a\x61\xbb\x21\x79\x6a\xef\x66\x57\x77\xc7\x40\xda\x8d\xf7\x3b\x73\x28\x59\xa4\x57\x24\x2f\xca\x6b\x64\xd2\x10\x9b\x84\x99\xd1\xb4\x3f\xb9\x0c\x2c\x73\x8a\xff\xc2\x70\x63\xe5\x57\xea\x42\x2e\x11\xa9\x2c\x52\x5f\x5e\x5c\xc9\x45\x81\xd3\x32\x5b\xb9\xd5\x01\x76\xb5\xa8\x1c\xc2\x7f\x70\x3c\xbb\xac\x4f\xcd\xc4\x8c\x73\xe9\x6e\x12\x97\x31\xc2\xae\xe4\xc2\xee\x3e\x6a\x24\xae\xf1\xc6\x37\xb7\xed\x3d\x81\x5e\x93\x1d\xb7\x31\x1e\x87\x2d\x5e\xf0\x2b\x8a\xd9\x0e\xb4\xd0\xba\x0b\x1d\xea\xd9\x15\xbf\x03\x63\x03\x48\xde\x41\x36\x01\x74\x5c\xef\xed\x11\xca\x68\x29\xbe\x19\xc8\x65\xdc\xc7\x71\x04\x0b\x5d\x73\x9f\xfd\xab\xed\xca\x52\xdd\x9e\x2d\x12\x66\xa1\x1b\x56\x6e\xcd\xd3\xe7\x1f\x8a\xf4\xe6\xaf\xfe\xe2\x58\xef\x7d\x70\xbf\x9a\x55\x96\x84\x92\xf1\xe6\x8e\x9e\x2c\xe9\xbd\x86\xd6\x36\x2f\x98\x3e\xaf\xe7\x97\xc0\x04\x3f\x01\x00\x00\xff\xff\x08\xab\x54\xd2\xa4\x06\x00\x00") +var _dataDriverDriverFlowComposeJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x54\xc1\x6a\xeb\x30\x10\xbc\x07\xf2\x0f\x8b\xc8\x31\xc9\x07\x18\xde\x25\xbc\x1e\x0a\x25\x14\xda\x5b\xc9\x41\xc6\x5b\xd7\x54\x96\x8c\x2c\xbb\x14\xa3\x7f\x2f\x92\x5d\xdb\x51\x2c\xd9\x84\x3a\xa7\x64\x77\x76\x32\x33\x1e\xdc\x6c\x37\x00\x00\x44\xc9\x2c\x4d\x51\x96\x24\x82\xb7\x76\x64\x3e\xcd\xf0\xd5\xa2\xb2\x84\x44\x40\x9a\x06\x8e\xff\x65\x56\xa3\x3c\xd3\x1c\x41\xeb\x43\x21\xb1\x2c\xc9\xde\x41\xab\x4c\x31\x24\x91\xcb\x62\x77\xc8\x0d\xd3\xa9\x52\x4a\x70\x78\xa8\x91\x2b\x72\x8d\xd2\x37\x6c\xe2\x13\xb9\xa3\xcf\xa3\xb3\xbf\xe1\x34\x37\x02\x48\x6c\xff\xc7\x15\x38\x50\x7f\x17\x16\xc6\xab\x3c\x46\xe9\x87\xf9\xfd\x4c\xfa\x22\xd3\x38\x7d\x3b\x76\x46\x17\xd7\x3b\x95\xe9\x4a\xce\x3b\x4b\xbf\x8a\xe7\x12\x4a\xa4\x28\x12\xf1\xe5\x07\xd6\x94\x55\xe8\xd1\xda\x6b\x6e\x0e\xb0\x6b\x85\x95\x10\xfd\x83\xe3\xd9\xe6\x7e\xea\x26\x7a\x22\xa0\xf1\xa9\xa4\x3c\x45\xd8\xd5\x94\x99\xe3\x47\x85\x92\x2a\x1c\x08\x83\xe7\xfe\x15\x5c\xf5\xdb\xb2\x6b\xed\xb3\xd9\x1f\x30\x1a\x23\x0b\x57\xa2\xc7\xb6\xd5\x18\x91\x87\x6f\x02\x36\xb4\x89\x21\x7b\x07\xde\xc5\x30\xf2\xbe\x37\x2b\xe4\xc9\x6c\x8a\x21\xcc\x65\xa2\xa1\x13\x49\xcc\x75\xcf\xbe\x15\x5e\x4d\x73\x66\xeb\xf7\x6c\xa0\x10\xc6\xfe\x69\x05\x17\x15\xa1\xfc\x10\x52\xad\xd0\x81\x17\xcb\x7b\xff\xe3\x0f\x28\x5a\x66\x8c\x09\x9e\xae\xe0\xeb\xc9\xd0\xde\x6f\x6b\x79\x19\x9d\xd7\xe5\xf0\xb3\xdb\x5c\xb6\x1b\xbd\xdd\xfc\x04\x00\x00\xff\xff\x91\x7e\x98\x31\xdc\x06\x00\x00") func dataDriverDriverFlowComposeJsonBytes() ([]byte, error) { return bindataRead( @@ -147,12 +156,12 @@ func dataDriverDriverFlowComposeJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/driver/driver.flow.compose.json", size: 1700, mode: os.FileMode(420), modTime: time.Unix(1604862634, 0)} + info := bindataFileInfo{name: "data/driver/driver.flow.compose.json", size: 1756, mode: os.FileMode(438), modTime: time.Unix(1715717688, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _dataDriverDriverJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x54\x41\x6f\xe2\x3c\x10\xbd\xe7\x57\xcc\xa9\x4e\x24\x30\xdf\x1d\xe5\x3b\x6c\xab\xd5\xae\xb4\xaa\xaa\x55\x6f\xab\x6a\x65\x92\x21\x98\x75\x6c\x76\x3c\x81\x56\x51\xfe\xfb\xca\x0e\x81\x50\xa0\xd4\x27\x3c\xf3\xe6\x79\x66\xde\x23\xa2\xf1\x08\x9e\x49\x17\x2c\xe6\x49\x52\x38\xeb\x19\xbe\xb9\x1a\xdf\x20\x07\xc2\xbf\x8d\x26\x4c\xc5\x2a\x04\x44\x36\xdf\xe7\x1f\x48\x6f\x91\xc6\x00\x29\x67\x52\xce\x8c\x5e\xcc\xca\x98\x93\x6b\x1f\xe0\x49\x61\x94\xf7\x91\xaf\x56\xac\x8b\x7d\x21\xbe\x32\xda\xd2\x0f\x3c\x6d\x92\x00\x00\x38\xfb\xdd\x6a\x4e\x33\x68\xe3\x35\x1c\xdf\x6c\x90\xe4\x90\x98\x1f\xe2\x6d\x3b\x05\xbd\x04\x79\xaf\x36\x6a\xa1\x8d\x66\x8d\x1e\xba\xee\x90\xe7\x95\xf6\xb2\x18\x27\x73\xf8\x75\xc8\x0e\x0c\xa4\x6c\x85\xa0\xad\x66\xad\xcc\x75\xb2\x70\x44\xdb\x82\x84\xae\x13\x93\x33\x16\xb4\xe5\x18\xbd\x6f\xcd\x28\xcf\xb7\x29\x2f\xa2\x44\xf2\x01\xfd\xcb\xe9\x12\xd0\x78\xbc\x39\xf8\xcb\x47\x84\xb1\x62\x35\x08\xf4\xfc\xb6\xe9\x6b\xe2\xc0\xbd\x3e\x8f\xaa\x0e\x6f\x88\xd1\xd3\xb1\xc8\xb8\x2a\xbd\x50\x2d\xd7\x4e\xdb\x54\x4c\x44\x36\x01\xb1\x52\x1e\x16\x88\x36\x6e\x19\xcb\xe8\x89\x63\x33\x61\x51\x16\x41\x3e\x36\xf5\x02\xe9\x4b\xc3\xec\xac\x87\xff\x60\xfa\xbe\xbf\xdf\x4b\xe3\x76\xcf\xa4\xab\x6a\x80\x3d\x11\x7a\x8f\x25\xe4\x60\x71\xd7\x1b\x56\x7e\x35\x6e\x77\xaf\xa8\xdc\x03\x1f\x70\xab\x0b\x4c\xcf\x27\x99\x6e\x42\xb1\xc8\x4e\xe4\x90\x84\x95\xf6\x8c\x94\x5e\x89\xff\x6c\xec\x8f\xf0\xc3\x22\xa5\xa9\xa2\xca\x4f\xc0\xb3\x62\xcc\x20\xff\x7f\x64\xd9\xe1\xe8\x25\x44\x94\x5c\xc4\x7e\x21\xcf\x7b\xf8\x70\xbf\xbb\x83\x98\x8e\xbd\x84\xc5\x1d\x11\x87\x50\x76\x81\x37\x1c\x42\x6e\xc8\xc2\x13\xb9\x5a\x7b\x94\x84\xde\x99\x2d\xa6\x4c\x0d\x66\x67\x05\x5d\x6f\x92\x4f\x32\xad\xb1\xe0\x74\xa9\x8c\xbf\xc4\x74\x12\xe9\xb2\x6b\xb6\xea\x7a\x8d\x6f\xeb\xcb\x17\x14\x0d\x22\xa6\x65\x94\x6e\x02\xec\xfe\xa0\x3d\x2e\xba\xfd\xa4\x2b\x4e\x05\xdc\xbf\x72\x85\xf4\x14\x5b\x28\x2e\x56\xbd\xab\x91\xc8\x51\x96\x1c\x07\x1f\x4d\xd9\x25\x49\xed\xca\xc6\xa0\xc4\xd7\x8d\x23\x0e\x7f\x99\x77\x1f\xb9\x79\xf2\x2f\x00\x00\xff\xff\x65\xe6\x70\xfd\x5b\x05\x00\x00") +var _dataDriverDriverJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x54\x4d\x6f\xdb\x30\x0c\xbd\x07\xc8\x7f\xe0\xa9\x92\x81\x44\xd9\x3d\xf0\x0e\x6b\x31\x6c\xc0\x50\x14\x43\x6f\x43\x31\x28\x36\xe3\x28\x93\xa5\x8c\x92\x93\x16\x86\xff\xfb\x20\xd9\xae\x13\x27\xe9\xc2\x93\x4d\x3e\x3e\x7e\x3c\xda\xac\x72\x08\xce\x93\xca\x3c\x5b\x4e\x27\xd3\x49\x66\x8d\xf3\xf0\xcd\x96\xf8\x06\x29\x10\xfe\xad\x14\x21\x67\x9b\xe0\x60\xc9\xb2\x07\x3c\x90\xda\x23\x1d\x23\x84\x58\x08\xb1\xd0\x6a\xb5\xc8\x63\x4c\x6c\x5d\xc4\x4f\x27\x99\x96\xce\x45\xca\x52\x7a\x95\x75\xa9\xf8\xea\xd1\xe4\xae\x67\xaa\x03\x12\x00\xc0\x9a\xef\x46\x79\x9e\x04\x0f\x74\xe6\xaa\x1d\x92\xe8\x23\xcb\x21\x50\xd7\x73\x50\x6b\x10\xf7\x72\x27\x57\x4a\x2b\xaf\xd0\x41\xd3\x0c\x00\xbf\x51\x4e\x64\xc7\xd1\x14\x7e\x0d\xe1\x9e\x83\xa4\x29\x10\x94\x51\x5e\x49\xfd\x01\x5d\x30\x56\xd7\x20\xa0\x69\xd8\xec\x9c\x07\x4d\x7e\x82\xef\xfa\xd3\xd2\xf9\x1b\x58\x2f\xc2\xd8\x29\xdb\xb8\xc2\xcb\x68\x1b\xa8\x1d\xfe\x7f\x03\x2f\x1f\x93\xc6\x9c\x4d\x2f\xd8\xf3\xdb\xae\xcd\x8a\x93\xb7\x7a\x3d\xca\x32\x94\x61\xc7\xe5\x63\x96\xb6\x05\xbf\x90\x2e\xb6\x56\x19\xce\x66\x2c\x99\x01\xdb\x48\x07\x2b\x44\x13\x17\x8e\x79\x77\x26\x43\x43\x61\x65\x06\x41\x3c\x56\xe5\x0a\xe9\x4b\xe5\xbd\x35\x0e\x3e\xc1\xfc\xac\xc7\xdf\x6b\x6d\x0f\xcf\xa4\x8a\xa2\xc7\x3d\x11\x3a\x87\x39\xa4\x60\xf0\xd0\xde\xb1\xf8\xaa\xed\xe1\x5e\x52\xde\x01\x1f\x70\xaf\x32\xe4\xe7\xd3\xcc\x77\x21\x99\x25\xa7\xca\x08\xc2\x42\x39\x8f\xc4\xaf\x05\x7e\x56\xe6\x47\x78\x30\x48\x9c\x4b\x2a\xdc\x0c\x9c\x97\x1e\x13\x48\x3f\x1f\x9f\x71\x6f\x6a\x0d\x11\x26\x56\xb1\x65\x48\xd3\x16\xdf\xbf\xdf\xdd\x41\x0c\xc7\x76\xc2\xfa\x06\xc4\xbb\x2b\xb9\x44\x1c\x8c\xd0\x57\x64\xe0\x89\x6c\xa9\x1c\x0a\x42\x67\xf5\x1e\xb9\xa7\x0a\x93\xf3\x8c\xa6\x3d\x98\x5b\xb9\xb6\x98\x79\xbe\x96\xda\x5d\xe4\x3a\x75\x35\xc9\xf5\x23\x6b\x7a\xc1\x6f\x10\xdb\x5f\x90\x37\x28\xca\xf3\xa8\xe3\x0c\xbc\xfd\x83\x66\xd8\x79\x7d\xeb\x8d\x8c\xd4\xec\xea\x5c\xa1\x1d\x81\x33\xe9\xb3\x4d\x7b\xe8\x48\x64\x29\x79\x9f\x6b\x3c\x6e\x1c\xb5\xb4\x79\xa5\x51\xe0\xeb\xce\x92\x0f\xdf\xd2\xe8\x6f\xb8\x9c\x4e\xfe\x05\x00\x00\xff\xff\xf3\x3e\x47\x31\x8a\x05\x00\x00") func dataDriverDriverJsBytes() ([]byte, error) { return bindataRead( @@ -167,7 +176,7 @@ func dataDriverDriverJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "data/driver/driver.js", size: 1371, mode: os.FileMode(420), modTime: time.Unix(1604862634, 0)} + info := bindataFileInfo{name: "data/driver/driver.js", size: 1418, mode: os.FileMode(438), modTime: time.Unix(1715717688, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -224,11 +233,11 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "data/driver/assets/icon.svg": dataDriverAssetsIconSvg, - "data/driver/device.js": dataDriverDeviceJs, - "data/driver/driver.compose.json": dataDriverDriverComposeJson, + "data/driver/assets/icon.svg": dataDriverAssetsIconSvg, + "data/driver/device.js": dataDriverDeviceJs, + "data/driver/driver.compose.json": dataDriverDriverComposeJson, "data/driver/driver.flow.compose.json": dataDriverDriverFlowComposeJson, - "data/driver/driver.js": dataDriverDriverJs, + "data/driver/driver.js": dataDriverDriverJs, } // AssetDir returns the file names below a certain @@ -270,16 +279,17 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "data": &bintree{nil, map[string]*bintree{ "driver": &bintree{nil, map[string]*bintree{ "assets": &bintree{nil, map[string]*bintree{ "icon.svg": &bintree{dataDriverAssetsIconSvg, map[string]*bintree{}}, }}, - "device.js": &bintree{dataDriverDeviceJs, map[string]*bintree{}}, - "driver.compose.json": &bintree{dataDriverDriverComposeJson, map[string]*bintree{}}, + "device.js": &bintree{dataDriverDeviceJs, map[string]*bintree{}}, + "driver.compose.json": &bintree{dataDriverDriverComposeJson, map[string]*bintree{}}, "driver.flow.compose.json": &bintree{dataDriverDriverFlowComposeJson, map[string]*bintree{}}, - "driver.js": &bintree{dataDriverDriverJs, map[string]*bintree{}}, + "driver.js": &bintree{dataDriverDriverJs, map[string]*bintree{}}, }}, }}, }} @@ -330,4 +340,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/drivers/HmIP-SWO-B/assets/icon.svg b/drivers/HmIP-SWO-B/assets/icon.svg new file mode 100644 index 0000000..10d0296 --- /dev/null +++ b/drivers/HmIP-SWO-B/assets/icon.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + diff --git a/drivers/HmIP-SWO-B/assets/images/large.png b/drivers/HmIP-SWO-B/assets/images/large.png new file mode 100644 index 0000000..fe71200 Binary files /dev/null and b/drivers/HmIP-SWO-B/assets/images/large.png differ diff --git a/drivers/HmIP-SWO-B/assets/images/small.png b/drivers/HmIP-SWO-B/assets/images/small.png new file mode 100644 index 0000000..7d75235 Binary files /dev/null and b/drivers/HmIP-SWO-B/assets/images/small.png differ diff --git a/drivers/HmIP-SWO-B/device.js b/drivers/HmIP-SWO-B/device.js new file mode 100644 index 0000000..7b3a67a --- /dev/null +++ b/drivers/HmIP-SWO-B/device.js @@ -0,0 +1,41 @@ +'use strict'; + +const Homey = require('homey'); +const Device = require('../../lib/device.js') +const Convert = require('../../lib/convert.js') + +const capabilityMap = { + "measure_temperature": { + "channel": 1, + "key": "ACTUAL_TEMPERATURE" + }, + "measure_humidity": { + "channel": 1, + "key": "HUMIDITY", + "convert": Convert.toFloat + }, + "measure_wind_strength": { + "channel": 1, + "key": "WIND_SPEED", + "convert": Convert.toFloat + }, + "measure_luminance": { + "channel": 1, + "key": "ILLUMINATION", + "convert": Convert.toInt + }, + "measure_homematic_sunshineduration": { + "channel": 1, + "key": "SUNSHINEDURATION", + "convert": Convert.toInt + } +} + +class HomematicDevice extends Device { + + onInit() { + super.onInit(capabilityMap); + } +} + +module.exports = HomematicDevice; diff --git a/drivers/HmIP-SWO-B/driver.compose.json b/drivers/HmIP-SWO-B/driver.compose.json new file mode 100644 index 0000000..64890fa --- /dev/null +++ b/drivers/HmIP-SWO-B/driver.compose.json @@ -0,0 +1,88 @@ +{ + "id": "HmIP-SWO-B", + "name": { + "en": "HmIP-SWO-B" + }, + "class": "sensor", + "capabilities": [], + "images": { + "large": "/drivers/HmIP-SWO-B/assets/images/large.png", + "small": "/drivers/HmIP-SWO-B/assets/images/small.png" + }, + "pair": [ + { + "id": "list_bridges", + "template": "list_devices", + "options": { + "singular": true + }, + "navigation": { + "next": "list_devices" + } + }, + { + "id": "list_devices", + "template": "list_devices", + "navigation": { + "next": "add_devices" + } + }, + { + "id": "add_devices", + "template": "add_devices" + } + ], + "settings": [ + { + "type": "group", + "label": { + "en": "Device data" + }, + "children": [ + { + "id": "app", + "type": "label", + "label": { + "en": "App" + }, + "value": "Homematic" + }, + { + "id": "driver", + "type": "label", + "label": { + "en": "Driver" + }, + "value": "", + "hint": { + "en": "In most cases the driver name is the same as the device type, but some drivers support multiple device types. In those cases they might not match." + } + }, + { + "id": "address", + "type": "label", + "label": { + "en": "Device Address" + }, + "value": "" + }, + { + "id": "ccuIP", + "type": "label", + "label": { + "en": "CCU IP" + }, + "value": "" + }, + { + "id": "ccuSerial", + "type": "text", + "label": { + "en": "CCU Serial" + }, + "value": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/drivers/HmIP-SWO-B/driver.js b/drivers/HmIP-SWO-B/driver.js new file mode 100644 index 0000000..8571384 --- /dev/null +++ b/drivers/HmIP-SWO-B/driver.js @@ -0,0 +1,24 @@ +'use strict'; + +const Homey = require('homey'); +const Driver = require('../../lib/driver.js'); + +class HomematicDriver extends Driver { + + onInit() { + super.onInit(); + this.capabilities = [ + 'measure_temperature', + 'measure_humidity', + 'measure_wind_strength', + 'measure_luminance', + 'measure_homematic_sunshineduration' + ] + this.homematicTypes = ['HmIP-SWO-B']; + this.log(this.homematicTypes.join(','), 'has been inited'); + } + + +} + +module.exports = HomematicDriver; \ No newline at end of file diff --git a/lib/CapabilityManager.js b/lib/CapabilityManager.js new file mode 100644 index 0000000..0243d51 --- /dev/null +++ b/lib/CapabilityManager.js @@ -0,0 +1,131 @@ +'use strict'; + +const Constants = require('./constants'); + +class CapabilityManager { + constructor(device, capabilityMap) { + this.device = device; + this.capabilityMap = capabilityMap; + this.deviceAddress = device.deviceAddress; + this.HomeyInterfaceName = device.HomeyInterfaceName; + } + + registerCapabilityListeners() { + Object.keys(this.capabilityMap).forEach(capabilityName => { + const capability = this.capabilityMap[capabilityName]; + this.device.logger.log('info', `Registering capability listener for ${capabilityName}`); + + if (capability.set) { + this.device.registerCapabilityListener(capabilityName, async (value, opts) => { + let setValue = value; + if (capability.set.convertMQTT && this.device.bridge.transport === Constants.TRANSPORT_MQTT) { + setValue = capability.set.convertMQTT(value); + } else if (capability.set.convert) { + setValue = capability.set.convert(value); + } else { + setValue = this.convertValue(capability.set.valueType, value); + } + + let key = capability.set.key; + if (capability.set.convertKey) { + key = capability.set.convertKey(key, value); + } + + let channel = capability.set.channel; + if (capability.set.convertChannel) { + channel = capability.set.convertChannel(channel, value); + } + + this.device.setValue(channel, key, setValue); + }); + } + }); + } + + initializeCapabilities() { + Object.keys(this.capabilityMap).forEach(name => { + const capability = this.capabilityMap[name]; + if (capability.channel && capability.key) { + this.getCapabilityValue(name); + this.initializeEventListener(name); + } + }); + this.initializeExtraEventListeners(); + } + + getCapabilityValue(capabilityName) { + const capability = this.capabilityMap[capabilityName]; + this.device.bridge.getValue(this.HomeyInterfaceName, `${this.deviceAddress}:${capability.channel}`, capability.key) + .then(value => { + value = this.convertCapabilityValue(value, capabilityName); + return this.device.setCapabilityValue(capabilityName, value); + }) + .catch(err => { + this.device.logger.log('error', `Failed to get capability ${capabilityName} for device ${this.device.getName()} (${this.deviceAddress})`, err); + }); + } + + initializeEventListener(capabilityName) { + const capability = this.capabilityMap[capabilityName]; + const eventName = `event-${this.deviceAddress}:${capability.channel}-${capability.key}`; + + this.device.logger.log('info', `Initializing event listener for ${capabilityName} with event name ${eventName}`); + this.device.bridge.on(eventName, async value => { + value = this.convertCapabilityValue(value, capabilityName); + if (value !== undefined) { + try { + await this.device.setCapabilityValue(capabilityName, value); + } catch (err) { + this.device.logger.log('error', `Failed to set capability ${capabilityName} for device ${this.device.getName()} (${this.deviceAddress})`, err); + } + } + }); + this.device.addedEvents.push(eventName); + } + + initializeExtraEventListeners() { + // Implement additional event listeners if needed + } + + convertCapabilityValue(value, capabilityName) { + const { convert, convertMQTT, valueType } = this.capabilityMap[capabilityName]; + if (convertMQTT && this.device.bridge.transport === Constants.TRANSPORT_MQTT) { + return convertMQTT(value); + } else if (convert) { + return convert(value); + } else { + return this.convertValue(valueType, value); + } + } + + convertValue(valueType, value) { + switch (valueType) { + case 'string': + return value.toString(); + case 'int': + return parseInt(value); + case 'float': + return parseFloat(value); + case 'boolean': + return value === 1; + case 'onOffDimGet': + return value > 0; + case 'keymatic': + return true; + case 'keymatic_swap': + return !value; + case 'onOffDimSet': + return value ? 0.99 : "0.0"; + case 'Wh': + return parseFloat(value) / 1000; + case 'floatPercent': + return parseFloat(value) * 100; + case 'mA': + return parseFloat(value) / 1000; + default: + return value; + } + } +} + +module.exports = CapabilityManager; diff --git a/lib/DeviceLister.js b/lib/DeviceLister.js new file mode 100644 index 0000000..c22b2e6 --- /dev/null +++ b/lib/DeviceLister.js @@ -0,0 +1,70 @@ +'use strict'; + +class DeviceLister { + constructor(driver) { + this.driver = driver; + } + + async onListDevices(data, currentBridge) { + if (!currentBridge) { + return this.onListDevicesBridges(); + } + return this.onListDevicesDevices(currentBridge); + } + + async onListDevicesBridges() { + const self = this.driver; + try { + await self.homey.app.discovery.discover(); + return Object.values(self.homey.app.bridges).map(bridge => ({ + name: `CCU(${bridge.address})`, + data: { + serial: bridge.serial, + } + })); + } catch (err) { + throw new Error('Discovery failed'); + } + } + + async onListDevicesDevices(currentBridge) { + const self = this.driver; + if (!currentBridge) { + throw new Error('Missing Bridge'); + } + + let devices = []; + try { + const bridgeDevices = await currentBridge.listDevices(); + Object.keys(bridgeDevices).forEach(interfaceName => { + bridgeDevices[interfaceName].forEach(device => { + if (self.homematicTypes.includes(device.TYPE)) { + for (let idx = 0; idx < self.numDevices; idx++) { + const deviceObj = { + name: self.getDeviceName(device.ADDRESS, idx), + capabilities: self.capabilities, + data: { + id: device.ADDRESS, + attributes: { + HomeyInterfaceName: interfaceName, + bridgeSerial: currentBridge.serial + } + } + }; + if (self.multiDevice) { + deviceObj.data.attributes.Index = idx; + } + devices.push(deviceObj); + } + } + }); + }); + } catch (err) { + throw new Error('Failed to list devices: ' + err); + } + + return devices; + } +} + +module.exports = DeviceLister; diff --git a/lib/HomeMaticCCUJack.js b/lib/HomeMaticCCUJack.js index e37044d..f341a40 100644 --- a/lib/HomeMaticCCUJack.js +++ b/lib/HomeMaticCCUJack.js @@ -1,221 +1,243 @@ -/*global module:true, require:false*/ +'use strict'; const EventEmitter = require('events'); -const axios = require('axios').default; +const axios = require('axios'); const mqtt = require('mqtt'); const Constants = require('./constants.js'); -const mqttStatusPrefix = 'device/status/' +const mqttStatusPrefix = 'device/status/'; class HomeMaticCCUJack extends EventEmitter { - constructor(logger, homey, type, serial, ccuIP, mqttPort, user, password) { - super(); - this.homey = homey; - this.logger = logger; - this.ccuIP = ccuIP; - this.mqttPort = mqttPort; - this.type = type; - this.transport = Constants.TRANSPORT_MQTT; - this.serial = serial; - this.homeyIP = this.homey.app.homeyIP; - this.subscribedTopics = []; - this.connected = false; - this.setupMqtt(user, password); - let clientOptions = { - baseURL: 'http://' + ccuIP + ':2121/', - timeout: 10000 - } - if (user && password) { - clientOptions.auth = { - username: user, - password: password - } - } - this.jackClient = axios.create(clientOptions) - } - - subscribeTopic(name) { - let self = this; - self.MQTTClient.subscribe(name, function(err) { - if (err) { - self.logger.log('error', err); - } else { - // self.logger.log('info', "Subscribed to topic", name); - } - }) - } - setupMqtt(user, password) { - let self = this; - let options = {}; - if (user && password) { - options.username = user; - options.password = password; - } - this.MQTTClient = mqtt.connect('mqtt://'+self.ccuIP+':'+self.mqttPort, options); - this.MQTTClient.on('connect', function() { - self.logger.log('info', 'Connected to CCU Jack broker at: ', self.ccuIP + ':'+self.mqttPort ) - self.connected = true; - self.subscribedTopics.forEach(topic => { - self.subscribeTopic(topic); - }) - }) - - this.MQTTClient.on('message', function(topic, message) { - if (topic.startsWith(mqttStatusPrefix)) { - let devTopic = topic.replace(mqttStatusPrefix, ''); - let parts = devTopic.split('/'); - if (parts.length != 3 ) { - // console.log("Wrong message received: ", topic, devTopic, parts); - return - } - let address, channel, datapoint; - [address, channel, datapoint] = parts; - let msg = JSON.parse(message.toString()); - let eventName = self.getEventName(address, channel, datapoint); - //self.logger.log("info", "Emitting event", eventName, "with value", msg.v) - self.emit(eventName, msg.v); - } - }); - - this.MQTTClient.on('close', function() { - if (self.connected) { - self.logger.log('info', 'MQTT disconnected') - } - self.connected = false; - }) - - this.MQTTClient.on('offline', function() { - if (self.connected) { - self.logger.log('info', 'MQTT disconnected') - } - self.connected = false; - }) - - this.MQTTClient.on('error', function(err) { - self.logger.log('error', 'MQTT error connecting:', err) - self.connected = false; - }) - + constructor(logger, homey, type, serial, ccuIP, mqttPort, user, password) { + super(); + this.homey = homey; + this.logger = logger; + this.ccuIP = ccuIP; + this.mqttPort = mqttPort; + this.type = type; + this.transport = Constants.TRANSPORT_MQTT; + this.serial = serial; + this.homeyIP = this.homey.app.homeyIP; + this.subscribedTopics = []; + this.connected = false; + this.setupMqtt(user, password); + this.mqttReconnectInterval = 5000; + + this.jackClient = this.createJackClient(ccuIP, user, password); + this.requestQueue = []; + this.isProcessingQueue = false; + + this.homey.on('unload', () => this.cleanup()); + } + + createJackClient(ccuIP, user, password) { + let clientOptions = { + baseURL: 'http://' + ccuIP + ':2121/', + timeout: 10000 + }; + + if (user && password) { + clientOptions.auth = { + username: user, + password: password + }; } + return axios.create(clientOptions); + } - getEventName(address, channel, datapoint) { - return "event-" + address + ":" +channel + "-" + datapoint - } + enqueueRequest(request) { + this.requestQueue.push(request); + this.processQueue(); + } - on(event, callback) { - let self = this; - super.on(event, callback); - - const prefix = "event-"; - if (event.startsWith(prefix)) { - let tmp = event.replace(prefix, ""); - let channel, datapoint; - [channel, datapoint] = tmp.split("-"); - channel = channel.replace(":", "/") - let topic = mqttStatusPrefix+channel+'/'+datapoint; - if (!self.subscribedTopics.includes(topic)) { - self.subscribedTopics.push(topic); - } - if (self.connected) { - //self.logger.log("info", "Subscribing topic", topic) - self.subscribeTopic(topic); - } - } + async processQueue() { + if (this.isProcessingQueue || this.requestQueue.length === 0) { + return; } - async listDevices() { - let self = this; - let allDevices = []; - - return new Promise((resolve, reject) => { - self.jackClient.get('device') - .then(resp => { - let deviceLinks = resp.data['~links'] - let devPromises = [] - deviceLinks.forEach(link => { - let p = self.jackClient.get('device/' + link['href']) - devPromises.push(p) - }) - - Promise.all(devPromises) - .then(results => { - let devices = {}; - for (var i = 0; i < results.length; i++) { - let dev = results[i].data - if (devices[dev.interfaceType] === undefined) { - devices[dev.interfaceType] = [] - } - devices[dev.interfaceType].push({ - TYPE: dev.type, - ADDRESS: dev.address - }) - } - resolve(devices) - }) - .catch(err => { - self.logger.log("error", "Failed to process devices:", err) - reject(err) - }) - }) - .catch(err => { - console.log("error", "Failed to get devices:", err) - reject(err) - }) - }) + this.isProcessingQueue = true; + while (this.requestQueue.length > 0) { + const requestBatch = this.requestQueue.splice(0, 10); // Batch of 10 requests + try { + await Promise.all(requestBatch.map(request => request())); + } catch (error) { + this.logger.log('error', 'Failed to process request:', error); + } } - - getInterfaceName(dev) { - if (dev.address.startsWith('CUX')) { - return 'CUxD' - } - - if (dev.type.toLowerCase().startsWith('hm-')) { - return 'BidCos-RF' + this.isProcessingQueue = false; + } + + subscribeTopic(name) { + this.enqueueRequest(() => new Promise((resolve, reject) => { + this.MQTTClient.subscribe(name, (err) => { + if (err) { + this.logger.log('error', 'Failed to subscribe to topic', name, err); + return reject(err); } - - if (dev.type.toLowerCase().startsWith('hmip-')) { - return 'HmIP-RF' + this.logger.log('info', 'Subscribed to topic', name); + resolve(); + }); + })); + } + + setupMqtt(user, password) { + let options = {}; + if (user && password) { + options.username = user; + options.password = password; + } + this.MQTTClient = mqtt.connect('mqtt://' + this.ccuIP + ':' + this.mqttPort, options); + this.MQTTClient.on('connect', () => { + this.logger.log('info', 'Connected to CCU Jack broker at:', this.ccuIP + ':' + this.mqttPort); + this.connected = true; + this.subscribedTopics.forEach(topic => { + this.subscribeTopic(topic); + }); + }); + + this.MQTTClient.on('message', (topic, message) => { + if (topic.startsWith(mqttStatusPrefix)) { + let devTopic = topic.replace(mqttStatusPrefix, ''); + let parts = devTopic.split('/'); + if (parts.length !== 3) { + this.logger.log('error', 'Received malformed message', topic, devTopic, parts); + return; } - - return '' + let [address, channel, datapoint] = parts; + let msg = JSON.parse(message.toString()); + let eventName = this.getEventName(address, channel, datapoint); + this.emit(eventName, msg.v); + } + }); + + this.MQTTClient.on('close', () => { + if (this.connected) { + this.logger.log('info', 'MQTT disconnected'); + } + this.connected = false; + setTimeout(() => this.setupMqtt(user, password), this.mqttReconnectInterval); + }); + + this.MQTTClient.on('offline', () => { + if (this.connected) { + this.logger.log('info', 'MQTT went offline'); + } + this.connected = false; + setTimeout(() => this.setupMqtt(user, password), this.mqttReconnectInterval); + }); + + this.MQTTClient.on('error', (err) => { + this.logger.log('error', 'MQTT connection error:', err); + this.connected = false; + setTimeout(() => this.setupMqtt(user, password), this.mqttReconnectInterval); + }); + } + + getEventName(address, channel, datapoint) { + return "event-" + address + ":" + channel + "-" + datapoint; + } + + on(event, callback) { + super.on(event, callback); + + const prefix = "event-"; + if (event.startsWith(prefix)) { + let tmp = event.replace(prefix, ""); + let [channel, datapoint] = tmp.split("-"); + channel = channel.replace(":", "/"); + let topic = mqttStatusPrefix + channel + '/' + datapoint; + if (!this.subscribedTopics.includes(topic)) { + this.subscribedTopics.push(topic); + } + if (this.connected) { + this.subscribeTopic(topic); + } } - - getValue(interfaceName, address, key) { - let self = this; - return new Promise(function(resolve, reject) { - let valueURL = 'device/'+address.replace(':', '/') + '/' + key + '/~pv'; - self.jackClient.get(valueURL) - .then(resp =>{ - resolve(resp.data.v) - }) - .catch(err => { - self.logger.log("error", "Failed to get device value:", address, key, err) - reject(err) - }) - }) + } + + async listDevices() { + return new Promise((resolve, reject) => { + this.enqueueRequest(() => { + return this.jackClient.get('device') + .then(resp => { + let deviceLinks = resp.data['~links']; + let devPromises = deviceLinks.map(link => this.jackClient.get('device/' + link['href'])); + return Promise.all(devPromises) + .then(results => { + let devices = {}; + results.forEach(dev => { + let device = dev.data; + if (!devices[device.interfaceType]) { + devices[device.interfaceType] = []; + } + devices[device.interfaceType].push({ + TYPE: device.type, + ADDRESS: device.address + }); + }); + resolve(devices); + }) + .catch(err => { + this.logger.log("error", "Failed to process devices:", err); + reject(err); + }); + }) + .catch(err => { + this.logger.log("error", "Failed to get devices:", err); + reject(err); + }); + }); + }); + } + + async getValue(interfaceName, address, key) { + return new Promise((resolve, reject) => { + let valueURL = 'device/' + address.replace(':', '/') + '/' + key + '/~pv'; + this.enqueueRequest(() => { + return this.jackClient.get(valueURL) + .then(resp => { + resolve(resp.data.v); + }) + .catch(err => { + this.logger.log("error", "Failed to get device value:", address, key, err); + reject(new Error('Failed to get value')); + }); + }); + }); + } + + async setValue(interfaceName, address, key, value) { + let myValue = value; + if (myValue === "1.0") { + myValue = 1; + } else if (myValue === "0.0") { + myValue = 0; } - - setValue(interfaceName, address, key, value) { - let self = this; - let myValue = value; - if (myValue === "1.0") { - myValue = 1 - } else if (myValue === "0.0") { - myValue = 0 - } - return new Promise(function(resolve, reject) { - self.MQTTClient.publish('device/set/'+address.replace(':', '/')+'/'+key, JSON.stringify(myValue), function(err) { - if (err) { - self.logger.log('error', 'Failed to publish message: ', err); - reject(err); - } else { - // console.log("Message published") - } - }); - resolve(true) - }) + return new Promise((resolve, reject) => { + this.enqueueRequest(() => { + return new Promise((resolveInner, rejectInner) => { + this.MQTTClient.publish('device/set/' + address.replace(':', '/') + '/' + key, JSON.stringify(myValue), (err) => { + if (err) { + this.logger.log('error', 'Failed to publish message:', err); + rejectInner(new Error('Failed to set value')); + } else { + resolveInner(true); + } + }); + }).then(resolve).catch(reject); + }); + }); + } + + cleanup() { + if (this.MQTTClient) { + this.MQTTClient.end(true, () => { + this.logger.log('info', 'MQTT client disconnected'); + }); } - + this.requestQueue = []; + this.isProcessingQueue = false; + } } -module.exports = HomeMaticCCUJack; \ No newline at end of file +module.exports = HomeMaticCCUJack; diff --git a/lib/HomeMaticDiscovery.js b/lib/HomeMaticDiscovery.js index f006ac6..6c22552 100644 --- a/lib/HomeMaticDiscovery.js +++ b/lib/HomeMaticDiscovery.js @@ -1,72 +1,115 @@ 'use strict'; const dgram = require('dgram'); -const Constants = require('./constants') +const Constants = require('./constants'); const DISCOVER_MESSAGE = Buffer.from([0x02, 0x8F, 0x91, 0xC0, 0x01, 'e', 'Q', '3', 0x2D, 0x2A, 0x00, 0x2A, 0x00, 0x49]); -const DISCOVER_TIMEOUT = 2000; +const DISCOVER_TIMEOUT = 5000; const CCU_PORT = 43439; const DGRAM_PORT = 48724; -module.exports = class HomeMaticDiscovery { - +class HomeMaticDiscovery { constructor(logger, homey) { this.logger = logger; this.homey = homey; this.devices = {}; + this.discoveryInProgress = false; + } + + async getClient() { + if (!this._client) { + this._client = this.createClient(); + } + return this._client; + } + + createClient() { + return new Promise((resolve, reject) => { + const client = dgram.createSocket('udp4'); + client.on('message', this._onClientMessage.bind(this)); + client.on('error', (err) => { + this.logger.log('error', 'UDP client error:', err); + }); + client.on('listening', () => { + const address = client.address(); + this.logger.log('info', `UDP client listening on ${address.address}:${address.port}`); + }); + client.bind(DGRAM_PORT, (err) => { + if (err) { + this.logger.log('error', 'Failed to bind UDP client:', err); + return reject(err); + } + client.setBroadcast(true); + resolve(client); + }); + }); } _onClientMessage(message, remote) { - var self = this; - const { - address, - } = remote; + const { address, port } = remote; + this.logger.log('info', `Received message from ${address}:${port}`); + this.logger.log('info', `Raw message: ${message.toString('hex')}`); + + try { + const { type, serial } = this.parseMessage(message); + this.logger.log('info', `Discovered device - Type: ${type}, Serial: ${serial}, Address: ${address}`); + + if (this.homey.app.bridges[serial]) { + this.logger.log('info', `Updating existing bridge: ${serial}`); + this.homey.app.setBridgeAddress(serial, address); + } else { + this.logger.log('info', `Initializing new bridge: ${serial}`); + this.homey.app.initializeBridge({ serial: serial, type: type, address: address }); + } + this.homey.settings.set(Constants.SETTINGS_PREFIX_BRIDGE + serial, { serial: serial, type: type, address: address }); + } catch (err) { + this.logger.log('error', `Error processing message from ${address}:`, err); + } + } - const headerStart = 0; + parseMessage(message) { const headerEnd = 5; - const header = message.slice(headerStart, headerEnd); + const typeEnd = message.indexOf(0x00, headerEnd); + if (typeEnd === -1) throw new Error('Invalid message format: missing type'); - const typeStart = headerEnd; - const typeEnd = message.indexOf(0x00); - const type = message.slice(typeStart, typeEnd).toString(); + const type = message.slice(headerEnd, typeEnd).toString(); const serialStart = typeEnd + 1; - const serialEnd = serialStart + message.slice(serialStart).indexOf(0x00); + const serialEnd = message.indexOf(0x00, serialStart); + if (serialEnd === -1) throw new Error('Invalid message format: missing serial'); + const serial = message.slice(serialStart, serialEnd).toString(); - //if (this.devices[serial]) { - if (this.homey.app.bridges[serial]) { - //this.devices[serial].address = address; - this.homey.app.setBridgeAddress(serial, address) - } else { - this.homey.app.initializeBridge({serial: serial, type: type, address: address}) - } - this.homey.settings.set(Constants.SETTINGS_PREFIX_BRIDGE + serial, {serial: serial, type: type, address: address}) + return { type, serial }; } - async getClient() { - if (!this._client) { - this._client = new Promise((resolve, reject) => { - const client = dgram.createSocket('udp4'); - client.on('message', this._onClientMessage.bind(this)); - client.bind(48724, err => { - if (err) return reject(err); - client.setBroadcast(true); - resolve(client); - }); - }); + async discover({ timeout = DISCOVER_TIMEOUT } = {}) { + if (this.discoveryInProgress) { + this.logger.log('info', 'Discovery process already in progress...'); + return; } - return this._client; - } - - async discover({ timeout = DISCOVER_TIMEOUT } = {}) { - const client = await this.getClient(); - client.send(DISCOVER_MESSAGE, 0, DISCOVER_MESSAGE.length, CCU_PORT, '255.255.255.255'); - await new Promise(resolve => { - setTimeout(resolve, timeout); - }); - //return this.devices; + this.discoveryInProgress = true; + this.logger.log('info', 'Starting discovery process...'); + + try { + const client = await this.getClient(); + client.send(DISCOVER_MESSAGE, 0, DISCOVER_MESSAGE.length, CCU_PORT, '255.255.255.255', (err) => { + if (err) { + this.logger.log('error', 'Failed to send discovery message:', err); + } else { + this.logger.log('info', 'Discovery message sent'); + } + }); + await new Promise(resolve => setTimeout(resolve, timeout)); + this.logger.log('info', 'Discovery process completed'); + } catch (err) { + this.logger.log('error', 'Discovery failed:', err); + } finally { + this.discoveryInProgress = false; + this.logger.log('info', 'Discovery process finished.'); + } } +} -} \ No newline at end of file +module.exports = HomeMaticDiscovery; diff --git a/lib/connection.js b/lib/connection.js index ab24635..36e6406 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -6,201 +6,202 @@ const Homey = require('homey'); class InterfaceConnection extends EventEmitter { - constructor(logger, interfaceName, config) { - super(); - this.logger = logger; - this.interfaceName = interfaceName; - this._settings = config; - this.failureCount = 0; - this.maxFailureCount = 10; - this.deactivated = false; - this.connected = false; - this.wasConnected = false; - this.reconnectInterval = 10000; - this.setMaxListeners.retrying = false; - freeport((err, port) => { - if (err) throw err; - this.port = port; - this.initRpcServerAndClient(port) - }); - + constructor(logger, interfaceName, config) { + super(); + this.logger = logger; + this.interfaceName = interfaceName; + this._settings = config; + this.failureCount = 0; + this.maxFailureCount = 10; + this.deactivated = false; + this.connected = false; + this.wasConnected = false; + this.reconnectInterval = 10000; + this.retrying = false; + + freeport((err, port) => { + if (err) throw err; + this.port = port; + this.initRpcServerAndClient(port); + }); + + Homey.app.on('unload', () => this.cleanup()); + } + + getInitUrl() { + return `${this._settings.protocol}://${this._settings.homeyIP}:${this.port}`; + } + + retryConnect() { + this.logger.log('info', "Check retry: ", this.interfaceName); + if (this.failureCount > this.maxFailureCount && !this.wasConnected) { + this.logger.log('info', "Giving up on ", this.interfaceName); + clearInterval(this.retryConnectTimer); + this.retrying = false; + return; } - - getInitUrl() { - return this._settings.protocol + "://" + this._settings.homeyIP + ":" + this.port; + if (!this.connected) { + this.logger.log('info', "Reconnecting to ", this.interfaceName); + this.failureCount += 1; + let backoffTime = Math.min(this.failureCount * this.reconnectInterval, 60000); // Cap at 60 seconds + setTimeout(() => this.initInterface(), backoffTime); } - - retryConnect(self) { - self.logger.log('info', "Check retry: ", self.interfaceName) - if (self.failureCount > self.maxFailureCount && self.wasConnected == false) { - self.logger.log('info', "Giving up on ", self.interfaceName) - clearInterval(self.retryConnectTimer); - self.retrying = false; - return + } + + initRpcServerAndClient(port) { + this.createRpcServer(port); + this.rpcClient = this.createRpcClient(); + this.initInterface(); + } + + createRpcServer(port) { + this.rpcServer = this._settings.rpc.createServer({ host: this._settings.homeyIP, port }); + + this.rpcServer.on('system.listMethods', (err, params, callback) => { + callback(null, ['system.listMethods', 'system.multicall', 'event', 'listDevices']); + }); + + this.rpcServer.on('listDevices', (err, params, callback) => { + callback(null, []); + }); + + this.rpcServer.on('event', (err, params, callback) => { + this.emitEvent(params); + callback(null, ''); + }); + + this.rpcServer.on('system.multicall', (err, params, callback) => { + params[0].forEach(call => { + if (call.methodName === 'event') { + this.emitEvent(call.params); } - if (self.connected == false) { - self.logger.log('info', "Reconecting to ", self.interfaceName) - self.failureCount += 1; - self.initInterface(self.retryConnectTimer); - return - } - } - - initRpcServerAndClient(port) { - this.createRpcServer(port); - this.rpcClient = this.createRpcClient(); - this.initInterface(); - } - - createRpcServer(port) { - var self = this - this.rpcServer = this._settings.rpc.createServer({ host: this._settings.homeyIP, port: port }); - - this.rpcServer.on('system.listMethods', function (err, params, callback) { - callback(null, ['system.listMethods', 'system.multicall', 'event', 'listDevices']); - }); - - this.rpcServer.on('listDevices', function (err, params, callback) { - callback(null, []); - }); - - this.rpcServer.on('event', function (err, params, callback) { - self.emitEvent(params) - callback(null, ''); - }); - - this.rpcServer.on('system.multicall', function (err, params, callback) { - params[0].forEach(function (call) { - if (call.methodName === 'event') { - self.emitEvent(call.params) - } - }); - callback(null, ''); - }); - + }); + callback(null, ''); + }); + } + + createRpcClient() { + return this._settings.rpc.createClient({ host: this._settings.ccuIP, port: this._settings.port }); + } + + emitEvent(event) { + if (event && event.length === 4) { + const eventName = `event-${event[1]}-${event[2]}`; + this.emit('event', { + name: eventName, + value: event[3] + }); } + this.lastEvent = now(); + } - createRpcClient() { - return this._settings.rpc.createClient({ host: this._settings.ccuIP, port: this._settings.port }); - } - - emitEvent(event) { - if (event && event.length === 4) { - let eventName = "event-" + event[1] + "-" + event[2] - this.emit('event', { - 'name': eventName, - 'value': event[3] - }) + initInterface() { + this.rpcClient.methodCall('init', [this.getInitUrl(), `homey_${this.interfaceName}`], (err, res) => { + if (err) { + this.logger.log('info', "Failed to connect:", this.interfaceName, err); + this.connected = false; + if (!this.retrying) { + this.retrying = true; + this.retryConnectTimer = setInterval(() => this.retryConnect(), this.reconnectInterval); } + } else { + this.logger.log('info', "Connected to", this.interfaceName); + this.failureCount = 0; + this.connected = true; + this.wasConnected = true; + this.retrying = false; + clearInterval(this.retryConnectTimer); this.lastEvent = now(); + this.checkConnection(); + } + }); + } + + checkConnection() { + clearTimeout(this.rpcPingTimer); + const pingTimeout = this._settings.pingTimeout; + const elapsed = Math.round((now() - this.lastEvent) / 1000); + + if (elapsed > pingTimeout) { + this.logger.log('info', 'ping timeout', this.interfaceName, elapsed); + this.initInterface(); + return; } - - /** - * Tell the CCU that we want to receive events - */ - initInterface() { - var self = this; - this.rpcClient.methodCall('init', [self.getInitUrl(), 'homey_' + self.interfaceName], function (err, res) { - if (err) { - self.logger.log('info', "Failed to connect:", self.interfaceName, err) - self.connected = false; - if (!self.retrying) { - self.retrying = true; - self.retryConnectTimer = setInterval(self.retryConnect, self.reconnectInterval, self); - } - } else { - self.logger.log('info', "Connected to", self.interfaceName) - self.failureCount = 0; - self.connected = true; - self.wasConnected = true; - self.retrying = false; - clearInterval(self.retryConnectTimer); - self.lastEvent = now(); - self.checkConnection(); - } - }); + if (elapsed >= (pingTimeout / 2)) { + this.logger.log('info', "Sending ping to ", this.interfaceName); + this.rpcClient.methodCall('ping', ['homey'], (err, res) => { }); } - - checkConnection() { - var self = this; - clearTimeout(this.rpcPingTimer); - const pingTimeout = this._settings.pingTimeout; - const elapsed = Math.round((now() - this.lastEvent) / 1000); - if (elapsed > pingTimeout) { - self.logger.log('info', 'ping timeout', this.interfaceName, elapsed); - self.initInterface(); - return; - } - if (elapsed >= (pingTimeout / 2)) { - self.logger.log('info', "Sending ping to ", this.interfaceName) - self.rpcClient.methodCall('ping', ['homey'], (err, res) => { - + this.rpcPingTimer = setTimeout(() => this.checkConnection(), pingTimeout * 250); + } + + unsubscribe() { + this.rpcClient.methodCall('init', [this.getInitUrl(), ''], (err, res) => { }); + } + + listDevices() { + return new Promise((resolve, reject) => { + if (!this.connected) { + resolve({ 'interfaceName': this.interfaceName, 'devices': [] }); + } else { + this.rpcClient.methodCall('listDevices', [], (err, res) => { + if (err) { + reject(err); + } else { + const devices = res.map(device => { + device.HomeyInterfaceName = this.interfaceName; + return device; }); - } - this.rpcPingTimer = setTimeout(() => { - this.checkConnection(); - }, pingTimeout * 250); - } - - /** - * Tell the CCU that we no longer want to receive events - */ - unsubscribe() { - this.rpcClient.methodCall('init', [this.getInitUrl(), ''], function (err, res) { + resolve({ 'interfaceName': this.interfaceName, 'devices': devices }); + } }); + } + }); + } + + getValue(address, key) { + return new Promise((resolve, reject) => { + this.rpcClient.methodCall('getValue', [address, key], (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + } + + setValue(address, key, value) { + return new Promise((resolve, reject) => { + this.rpcClient.methodCall('setValue', [address, key, value], (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + } + + cleanup() { + if (this.rpcClient) { + this.rpcClient.methodCall('init', [this.getInitUrl(), ''], (err, res) => { + if (err) { + this.logger.log('error', 'Failed to cleanup RPC client:', err); + } + }); } - - listDevices() { - var self = this; - return new Promise(function (resolve, reject) { - if (!self.connected) { - resolve({ 'interfaceName': self.interfaceName, 'devices': [] }) - } else { - self.rpcClient.methodCall('listDevices', [], function (err, res) { - if (err) { - reject(err) - } else { - let devices = [] - for (var i = 0; i < res.length; i++) { - res[i].HomeyInterfaceName = self.interfaceName - devices.push(res[i]) - } - resolve({ 'interfaceName': self.interfaceName, 'devices': devices }) - } - }); - } - }) - } - - getValue(address, key) { - var self = this; - return new Promise(function (resolve, reject) { - self.rpcClient.methodCall('getValue', [address, key], function (err, res) { - if (err) { - reject(err) - } else { - resolve(res) - } - }) - }) - } - - setValue(address, key, value) { - var self = this; - return new Promise(function (resolve, reject) { - self.rpcClient.methodCall('setValue', [address, key, value], function (err, res) { - if (err) { - reject(err) - } else { - resolve(res) - } - }) - }) + if (this.rpcServer) { + this.rpcServer.close(() => { + this.logger.log('info', 'RPC server closed'); + }); } + clearTimeout(this.rpcPingTimer); + clearInterval(this.retryConnectTimer); + } } function now() { - return (new Date()).getTime(); + return (new Date()).getTime(); } -module.exports = InterfaceConnection; \ No newline at end of file +module.exports = InterfaceConnection; diff --git a/lib/convert.js b/lib/convert.js index e820639..6897226 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -1,57 +1,41 @@ module.exports = { - toFloat: function (value) { + toFloat(value) { return parseFloat(value); }, - toString: function (value) { + toString(value) { return value.toString(); }, - toInt: function (value) { + toInt(value) { return parseInt(value); }, - toBoolean: function (value) { - if (value === 0) { - return false - } - return true - }, - levelToOnOff: function (value) { - if (value > 0) { - return true - } - return value = false - }, - toggleBoolean: function (value) { - if (value === true) { - return false - } - return true - }, - onOffToLevel: function (value) { - if (value === true) { - return "0.99" - } else { - return "0.0" - } - }, - WhToKWh: function (value) { - return parseFloat(value) / 1000 - }, - floatToPercent: function (value) { - return Math.floor(parseFloat(value) * 100) - }, - mAToA: function (value) { - return parseFloat(value) / 1000 - }, - toTrue: function (value) { - return true - }, - tofix: function (value) { - return value.toFixed(2) - }, - faultLowbatToBoolean: function(value) { - if (value === 6) { - return true - } - return false + toBoolean(value) { + return value !== 0; + }, + levelToOnOff(value) { + return value > 0; + }, + toggleBoolean(value) { + return !value; + }, + onOffToLevel(value) { + return value ? "0.99" : "0.0"; + }, + WhToKWh(value) { + return parseFloat(value) / 1000; + }, + floatToPercent(value) { + return Math.floor(parseFloat(value) * 100); + }, + mAToA(value) { + return parseFloat(value) / 1000; + }, + toTrue() { + return true; + }, + tofix(value) { + return parseFloat(value).toFixed(2); + }, + faultLowbatToBoolean(value) { + return value === 6; } -} +}; diff --git a/lib/device.js b/lib/device.js index 4b8fe50..86745e5 100644 --- a/lib/device.js +++ b/lib/device.js @@ -1,176 +1,50 @@ 'use strict'; const Homey = require('homey'); -const Constants = require('./constants.js'); +const CapabilityManager = require('./CapabilityManager'); class Device extends Homey.Device { - onInit(capabilityMap) { + async onInit(capabilityMap) { this.logger = this.homey.app.logger; + this.logger.log('info', `Initializing device with capability map: ${JSON.stringify(capabilityMap)}`); this.capabilityMap = capabilityMap; - this.deviceAddress = this.getData().id - this.HomeyInterfaceName = this.getData().attributes.HomeyInterfaceName - this.bridgeSerial = this.getSetting('ccuSerial'); - if (this.bridgeSerial === null || this.bridgeSerial === "") { - this.bridgeSerial = this.getData().attributes.bridgeSerial; - } - var self = this; + this.deviceAddress = this.getData().id; + this.HomeyInterfaceName = this.getData().attributes.HomeyInterfaceName; + this.bridgeSerial = this.getSetting('ccuSerial') || this.getData().attributes.bridgeSerial; this.addedEvents = []; - this.driver.getBridge({serial: this.bridgeSerial}) - .then(bridge => { - self.bridge = bridge; - self.initilizeCapabilities(); - self.registerCapabilityListeners(); - self.setSettings({ - address: self.deviceAddress, - ccuIP: self.bridge.ccuIP, - ccuSerial: self.bridge.serial, - driver: self.driver.manifest.id - }) - }) - .catch(err => { - this.error(err); - }); + + try { + this.bridge = await this.driver.getBridge({ serial: this.bridgeSerial }); + this.logger.log('info', `Bridge found: ${this.bridgeSerial}`); + this.capabilityManager = new CapabilityManager(this, this.capabilityMap); // Moved this line here + this.capabilityManager.initializeCapabilities(); + this.capabilityManager.registerCapabilityListeners(); + + await this.setSettings({ + address: this.deviceAddress, + ccuIP: this.bridge.ccuIP, + ccuSerial: this.bridge.serial, + driver: this.driver.manifest.id + }); + } catch (err) { + this.error('Failed to initialize device:', err); + } } onDeleted() { - this.addedEvents.forEach((eventName) => { + this.addedEvents.forEach(eventName => { this.bridge.removeAllListeners(eventName); - }) - } - - setValue(channel, key, value) { - var self = this; - this.bridge.setValue(this.HomeyInterfaceName, this.deviceAddress + ':' + channel, key, value).then((res) => { - }).then(() => { - return - }).catch((err) => { - self.logger.log('info', 'Set', key, 'failed for device - Value ' + value, this.deviceAddress) - throw new Error('Failed to set value', null); - }) - } - - registerCapabilityListeners() { - var self = this; - Object.keys(this.capabilityMap).forEach((capabilityName) => { - if (self.capabilityMap[capabilityName].set) { - this.registerCapabilityListener(capabilityName, async (value, opts) => { - let setValue = value; - if (self.capabilityMap[capabilityName].set.convertMQTT && self.bridge.transport === Constants.TRANSPORT_MQTT) { - setValue = self.capabilityMap[capabilityName].set.convertMQTT(value) - } else if (self.capabilityMap[capabilityName].set.convert) { - setValue = self.capabilityMap[capabilityName].set.convert(value) - } else { - setValue = this.convertValue(self.capabilityMap[capabilityName].set.valueType, value); - } - let key = self.capabilityMap[capabilityName].set.key - // console.log(self.capabilityMap[capabilityName].set.convertKey) - if (self.capabilityMap[capabilityName].set.convertKey) { - key = self.capabilityMap[capabilityName].set.convertKey(key, value) - } - let channel = self.capabilityMap[capabilityName].set.channel - if (self.capabilityMap[capabilityName].set.convertChannel) { - key = self.capabilityMap[capabilityName].set.convertChannel(channel, value) - } - this.setValue(channel, key, setValue) - }) - } - }) - } - - initilizeCapabilities() { - var self = this; - Object.keys(this.capabilityMap).forEach((name) => { - // Setting initial values - if (self.capabilityMap[name].channel && self.capabilityMap[name].key) { - self.getCapabilityValue(name); - self.initializeEventListener(name); - } - }) - self.initializeExtraEventListeners() - } - - getCapabilityValue(capabilityName) { - var self = this; - this.bridge.getValue(self.HomeyInterfaceName, self.deviceAddress + ':' + self.capabilityMap[capabilityName].channel, self.capabilityMap[capabilityName].key).then((value) => { - if (self.capabilityMap[capabilityName].convertMQTT && self.bridge.transport === Constants.TRANSPORT_MQTT) { - value = self.capabilityMap[capabilityName].convertMQTT(value) - } else if (self.capabilityMap[capabilityName].convert) { - value = self.capabilityMap[capabilityName].convert(value) - } else { - value = this.convertValue(self.capabilityMap[capabilityName].valueType, value); - } - self.setCapabilityValue(capabilityName, value).catch((err) => { - self.logger.log('error', 'Failed to set capability ', capabilityName, 'for device ', self.getName(), '(', self.deviceAddress, ')', err); - }) - }).catch((err) => { - }) - } - - initializeEventListener(capabilityName) { - var self = this; - var eventName = 'event-' + self.deviceAddress + ':' + self.capabilityMap[capabilityName].channel + '-' + self.capabilityMap[capabilityName].key; - this.bridge.on(eventName, (value) => { - if (self.capabilityMap[capabilityName].convert) { - value = self.capabilityMap[capabilityName].convert(value) - } else { - value = this.convertValue(self.capabilityMap[capabilityName].valueType, value); - } - if (value !== undefined) { - self.setCapabilityValue(capabilityName, value).catch((err) => { - self.logger.log('error', 'Failed to set capability ', capabilityName, 'for device ', self.getName(), '(', self.deviceAddress, ')', err); - }) - } }); - self.addedEvents.push(eventName); } - initializeExtraEventListeners() { - } - - convertValue(valueType, value) { - if (valueType === 'string') { - value = value.toString(); - } else if (valueType === 'int') { - value = parseInt(value) - } else if (valueType === 'float') { - value = parseFloat(value) - } else if (valueType === 'boolean') { - if (value === 0) { - value = false - } else if (value === 1) { - value = true - } - } else if (valueType === 'onOffDimGet') { - if (value > 0) { - value = true - } else { - value = false - } - } else if (valueType === 'keymatic') { - value = true; - } else if (valueType === 'keymatic_swap') { - if (value === true) { - value = false - } else { - value = true - } - } else if (valueType === 'onOffDimSet') { - if (value === true) { - value = 0.99 - } else { - value = "0.0" - } - } else if (valueType === 'Wh') { - value = parseFloat(value) / 1000 - } else if (valueType === 'floatPercent') { - value = parseFloat(value) * 100 - } else if (valueType === 'mA') { - value = parseFloat(value) / 1000 - } - return value; + setValue(channel, key, value) { + this.bridge.setValue(this.HomeyInterfaceName, `${this.deviceAddress}:${channel}`, key, value) + .catch(err => { + this.logger.log('info', 'Set', key, 'failed for device - Value', value, this.deviceAddress); + throw new Error('Failed to set value'); + }); } } - module.exports = Device; diff --git a/lib/driver.js b/lib/driver.js index 2a01fae..4e2b157 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -1,125 +1,56 @@ 'use strict'; const Homey = require('homey'); +const DeviceLister = require('./DeviceLister'); class Driver extends Homey.Driver { - onInit() { - this.multiDevice = false - this.numDevices = 1 + onInit() { + this.multiDevice = false; + this.numDevices = 1; + this.deviceLister = new DeviceLister(this); + } + + getDeviceName(address, idx) { + if (this.multiDevice === true) { + return `${address}-${idx + 1}`; + } else { + return address; } + } - async getBridge({serial}) { - let self = this; - //backwards compatibility - if (serial === undefined && Object.keys(this.homey.app.bridges).length > 0) { - return this.homey.app.bridges[Object.keys(this.homey.app.bridges)[0]] - } - - if (this.homey.app.bridges[serial]) - return this.homey.app.bridges[serial]; - - //self.homey.app.bridges = await self.homey.app.discovery.discover(); - await this.homey.app.discovery.discover(); - //backwards compatibility - if (serial === undefined && Object.keys(this.homey.app.bridges).length > 0) { - return self.homey.app.bridges[Object.keys(this.homey.app.bridges)[0]] - } - - if (this.homey.app.bridges[serial]) - return this.homey.app.bridges[serial]; - - throw new Error('Bridge not found'); + async getBridge({ serial }) { + const bridges = this.homey.app.bridges; + if (!serial && Object.keys(bridges).length > 0) { + return bridges[Object.keys(bridges)[0]]; } - async onPair(session) { - let currentBridge; - let self = this; - - const onListDevices = (data) => { - if (!currentBridge) - return onListDevicesBridges(data); - - return onListDevicesDevices(data); - } - - const onListDevicesBridges = async (data) => { - try { - await self.homey.app.discovery.discover() - - const result = Object.values(self.homey.app.bridges).map(bridge => { - return { - name: "CCU(" + bridge.address + ")", - data: { - serial: bridge.serial, - } - } - }); - - return result; - - } catch (err) { - throw new Error('Discovery failed') - } - } - - const onListDevicesDevices = async (data) => { - if (!currentBridge) - throw new Error('Missing Bridge'); - - let devices = []; - var self = this; - - try { - let bridgeDevices = await currentBridge.listDevices() - - Object.keys(bridgeDevices).forEach((interfaceName) => { - for (var i = 0; i < bridgeDevices[interfaceName].length; i++) { - if (self.homematicTypes.includes(bridgeDevices[interfaceName][i].TYPE)) { - for (let idx = 0; idx < this.numDevices; idx++) { - let device = { - "name": this.getDeviceName(bridgeDevices[interfaceName][i].ADDRESS, idx), - "capabilities": self.capabilities, - "data": { - "id": bridgeDevices[interfaceName][i].ADDRESS, - "attributes": { - "HomeyInterfaceName": interfaceName, - "bridgeSerial": currentBridge.serial - } - } - } - if (this.multiDevice) { - device.data.attributes.Index = idx - } - devices.push(device); - } - } - } - }) - } catch (err) { - throw new Error('Failed to list devices: ' + err) - } + if (bridges[serial]) { + return bridges[serial]; + } - return devices; + await this.homey.app.discovery.discover(); - } + if (!serial && Object.keys(bridges).length > 0) { + return bridges[Object.keys(bridges)[0]]; + } - const onListBridgesSelection = async (data) => { - currentBridge = self.homey.app.bridges[data[0].data.serial]; - } + if (bridges[serial]) { + return bridges[serial]; + } - session.setHandler('list_devices', onListDevices); - session.setHandler('list_bridges_selection', onListBridgesSelection); + throw new Error('Bridge not found'); + } - } + async onPair(session) { + let currentBridge; + const self = this; - getDeviceName(address, idx) { - if (this.multiDevice == true) { - return address + "-" + (idx + 1) - } else { - return address - } - } + session.setHandler('list_devices', (data) => self.deviceLister.onListDevices(data, currentBridge)); + session.setHandler('list_bridges_selection', (data) => { + currentBridge = self.homey.app.bridges[data[0].data.serial]; + }); + } } module.exports = Driver; diff --git a/package.json b/package.json index 3f8b20f..7daac0b 100644 --- a/package.json +++ b/package.json @@ -5,18 +5,13 @@ "main": "app.js", "dependencies": { "@twendt/binrpc": "3.3.2", - "axios": "^0.21.1", - "binary": "^0.3.0", + "axios": "^1.7.2", "freeport": "1.0.5", "homematic-xmlrpc": "^1.0.2", - "mqtt": "^3.0.0", - "node-fetch": "^2.6.1", - "put": "0.0.6", - "which": "^2.0.2" + "mqtt": "^3.0.0" }, "devDependencies": { - "eslint": "^7.13.0", - "homey": "^2.14.2" + "homey": "^3.0.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1"