diff --git a/README.md b/README.md index 286b82f..7e68b75 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ As I currently only have the H6051 lamp, this is the only device I can test with - make search intervals configurable - clear all timeouts, when adapter is stopped +- replace forbidden characters in names - update translations - update dependecies diff --git a/build/main.js b/build/main.js index 077c4bb..c7759c6 100644 --- a/build/main.js +++ b/build/main.js @@ -76,7 +76,8 @@ class GoveeLocal extends utils.Adapter { case "scan": for (const key of Object.keys(messageObject.msg.data)) { if (key != "device") { - this.setObjectNotExists(messageObject.msg.data.device, { + const deviceName = messageObject.msg.data.device.replace(this.FORBIDDEN_CHARS, "_"); + this.setObjectNotExists(deviceName, { type: "device", common: { name: messageObject.msg.data.sku, @@ -84,7 +85,7 @@ class GoveeLocal extends utils.Adapter { }, native: {} }); - this.setObjectNotExists(`${messageObject.msg.data.device}.deviceInfo.${key}`, { + this.setObjectNotExists(`${deviceName}.deviceInfo.${key}`, { type: "state", common: { name: getDatapointDescription(key), @@ -95,7 +96,7 @@ class GoveeLocal extends utils.Adapter { }, native: {} }); - this.setState(`${messageObject.msg.data.device}.deviceInfo.${key}`, { + this.setState(`${deviceName}.deviceInfo.${key}`, { val: messageObject.msg.data[key], ack: true }); @@ -115,7 +116,7 @@ class GoveeLocal extends utils.Adapter { const devices = await this.getStatesAsync(this.name + "." + this.instance + ".*.deviceInfo.ip"); for (const key in devices) { if (devices[key].val == remote.address) { - const sendingDevice = key.split(".")[2]; + const sendingDevice = key.split(".")[2].replace(this.FORBIDDEN_CHARS, "_"); const devStatusMessageObject = JSON.parse(message.toString()); this.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, { type: "state", @@ -261,4 +262,7 @@ function componentToHex(c) { const hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } +function name2id(pName) { + return (pName || "").replace(this.FORBIDDEN_CHARS, "_"); +} //# sourceMappingURL=main.js.map diff --git a/build/main.js.map b/build/main.js.map index 13060cb..2eeb66f 100644 --- a/build/main.js.map +++ b/build/main.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/main.ts"], - "sourcesContent": ["/*\r\n * Created with @iobroker/create-adapter v2.3.0\r\n */\r\n\r\n// The adapter-core module gives you access to the core ioBroker functions\r\n// you need to create an adapter\r\nimport * as utils from '@iobroker/adapter-core';\r\nimport * as dgram from 'node:dgram';\r\n\r\n// Load your modules here, e.g.:\r\n// import * as fs from \"fs\";\r\n\r\nconst LOCAL_PORT = 4002;\r\nconst SEND_SCAN_PORT = 4001;\r\nconst CONTROL_PORT = 4003;\r\nconst M_CAST = '239.255.255.250';\r\n\r\nconst server = dgram.createSocket('udp4');\r\nconst client = dgram.createSocket('udp4');\r\n\r\nconst scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\r\nconst requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\r\n\r\nlet searchInterval: NodeJS.Timeout;\r\n\r\nconst intervals: { [device: string]: NodeJS.Timeout } = {};\r\n\r\nclass GoveeLocal extends utils.Adapter {\r\n\tpublic constructor(options: Partial = {}) {\r\n\t\tsuper({\r\n\t\t\t...options,\r\n\t\t\tname: 'govee-local',\r\n\t\t});\r\n\t\tthis.on('ready', this.onReady.bind(this));\r\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\r\n\t\t// this.on('objectChange', this.onObjectChange.bind(this));\r\n\t\t// this.on('message', this.onMessage.bind(this));\r\n\t\tthis.on('unload', this.onUnload.bind(this));\r\n\t}\r\n\r\n\t/**\r\n\t * Is called when databases are connected and adapter received configuration.\r\n\t */\r\n\tprivate async onReady(): Promise {\r\n\t\tthis.setObjectNotExists('info.connection', {\r\n\t\t\ttype: 'state',\r\n\t\t\tcommon: {\r\n\t\t\t\tname: 'Device discovery running',\r\n\t\t\t\ttype: 'boolean',\r\n\t\t\t\trole: 'indicator.connected',\r\n\t\t\t\tread: true,\r\n\t\t\t\twrite: false,\r\n\t\t\t},\r\n\t\t\tnative: {},\r\n\t\t});\r\n\t\tserver.on('message', this.onUdpMessage.bind(this));\r\n\t\tserver.on('error', (error) => {\r\n\t\t\tthis.log.error('server bind error : ' + error.message);\r\n\t\t\tthis.setState('info.connection', { val: false, ack: true });\r\n\t\t});\r\n\r\n\t\tserver.bind(LOCAL_PORT, this.serverBound.bind(this));\r\n\r\n\t\tthis.subscribeStates('*.devStatus.*');\r\n\t}\r\n\r\n\t/**\r\n\t * handles when udp socket is up\r\n\t * configure multicast membership\r\n\t * start periodic scan for devices\r\n\t */\r\n\tprivate async serverBound(): Promise {\r\n\t\tserver.setBroadcast(true);\r\n\t\tserver.setMulticastTTL(128);\r\n\t\tserver.addMembership(M_CAST);\r\n\t\tthis.setState('info.connection', { val: true, ack: true });\r\n\t\tthis.log.info('UDP listening on ' + server.address().address + ':' + server.address().port);\r\n\r\n\t\tconst result = this.setInterval(this.sendScan.bind(this), this.config.searchInterval * 1000);\r\n\t\tif (result !== void 0) {\r\n\t\t\tsearchInterval = result;\r\n\t\t}\r\n\t\t// this.sendScan();\r\n\t}\r\n\r\n\t/**\r\n\t * handle icoming messages on the udp socket\r\n\t * @param message the message itself\r\n\t * @param remote the sender of the message\r\n\t */\r\n\tprivate async onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): Promise {\r\n\t\tconst messageObject = JSON.parse(message.toString());\r\n\t\tswitch (messageObject.msg.cmd) {\r\n\t\t\tcase 'scan':\r\n\t\t\t\tfor (const key of Object.keys(messageObject.msg.data)) {\r\n\t\t\t\t\tif (key != 'device') {\r\n\t\t\t\t\t\tthis.setObjectNotExists(messageObject.msg.data.device, {\r\n\t\t\t\t\t\t\ttype: 'device',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: messageObject.msg.data.sku,\r\n\t\t\t\t\t\t\t\trole: 'group',\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${messageObject.msg.data.device}.deviceInfo.${key}`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: getDatapointDescription(key),\r\n\t\t\t\t\t\t\t\ttype: 'string',\r\n\t\t\t\t\t\t\t\trole: 'state',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: false,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${messageObject.msg.data.device}.deviceInfo.${key}`, {\r\n\t\t\t\t\t\t\tval: messageObject.msg.data[key],\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (!(messageObject.msg.data.device in intervals)) {\r\n\t\t\t\t\tconst result = this.setInterval(\r\n\t\t\t\t\t\t() => this.requestDeviceStatus(messageObject.msg.data.ip),\r\n\t\t\t\t\t\tthis.config.deviceStatusRefreshInterval * 1000,\r\n\t\t\t\t\t);\r\n\t\t\t\t\tif (result !== void 0) {\r\n\t\t\t\t\t\tintervals[messageObject.msg.data.device] = result;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'devStatus':\r\n\t\t\t\tconst devices = await this.getStatesAsync(this.name + '.' + this.instance + '.*.deviceInfo.ip');\r\n\t\t\t\tfor (const key in devices) {\r\n\t\t\t\t\tif (devices[key as keyof typeof devices].val == remote.address) {\r\n\t\t\t\t\t\tconst sendingDevice = key.split('.')[2];\r\n\t\t\t\t\t\tconst devStatusMessageObject = JSON.parse(message.toString());\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'On / Off state of the lamp',\r\n\t\t\t\t\t\t\t\ttype: 'boolean',\r\n\t\t\t\t\t\t\t\trole: 'switch',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.onOff`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.onOff == 1,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.brightness`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'Brightness of the light',\r\n\t\t\t\t\t\t\t\ttype: 'number',\r\n\t\t\t\t\t\t\t\trole: 'level.dimmer',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.brightness`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.brightness,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.color`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'Current showing color of the lamp',\r\n\t\t\t\t\t\t\t\ttype: 'string',\r\n\t\t\t\t\t\t\t\trole: 'level.color.rgb',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.color`, {\r\n\t\t\t\t\t\t\tval:\r\n\t\t\t\t\t\t\t\t'#' +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.r) +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.g) +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.b),\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.colorTemInKelvin`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'If staying in white light, the color temperature',\r\n\t\t\t\t\t\t\t\ttype: 'number',\r\n\t\t\t\t\t\t\t\trole: 'level.color.temperature',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.colorTemInKelvin`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.colorTemInKelvin,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tthis.log.info('message from: ' + remote.address + ':' + remote.port + ' - ' + message);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * sends the device status request to one specific device\r\n\t * @param receiver the ip ( / hsotname ) of the device that should be queried\r\n\t */\r\n\tprivate requestDeviceStatus(receiver: string): void {\r\n\t\tconsole.log('request device status ' + receiver);\r\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(requestStatusMessage));\r\n\t\tclient.send(requestDeviceStatusBuffer, 0, requestDeviceStatusBuffer.length, CONTROL_PORT, receiver);\r\n\t}\r\n\r\n\t/**\r\n\t * send the scan message to the udp multicast address\r\n\t */\r\n\tprivate async sendScan(): Promise {\r\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(scanMessage));\r\n\t\tclient.send(scanMessageBuffer, 0, scanMessageBuffer.length, SEND_SCAN_PORT, M_CAST);\r\n\t\t//this.log.info('send message ' + scanMessageBuffer);\r\n\t}\r\n\r\n\t/**\r\n\t * Is called when adapter shuts down - callback has to be called under any circumstances!\r\n\t */\r\n\tprivate onUnload(callback: () => void): void {\r\n\t\ttry {\r\n\t\t\tthis.clearInterval(searchInterval);\r\n\t\t\tObject.entries(intervals).forEach(([_, interval]) => this.clearInterval(interval));\r\n\t\t\tclient.close();\r\n\t\t\tserver.close();\r\n\t\t\tthis.setState('info.connection', { val: false, ack: true });\r\n\t\t\tcallback();\r\n\t\t} catch (e) {\r\n\t\t\tcallback();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Is called if a subscribed state changes\r\n\t */\r\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise {\r\n\t\tif (state && !state.ack) {\r\n\t\t\tconst ipOfDevice = await this.getStateAsync(id.split('.')[2] + '.deviceInfo.ip');\r\n\t\t\tif (ipOfDevice) {\r\n\t\t\t\tthis.log.info('should send to ip : ' + ipOfDevice.val);\r\n\t\t\t\tconst receiver = ipOfDevice.val?.toString();\r\n\t\t\t\tswitch (id.split('.')[4]) {\r\n\t\t\t\t\tcase 'onOff':\r\n\t\t\t\t\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: state.val ? 1 : 0 } } };\r\n\t\t\t\t\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\r\n\t\t\t\t\t\tclient.send(turnMessageBuffer, 0, turnMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'brightness':\r\n\t\t\t\t\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value: state.val } } };\r\n\t\t\t\t\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\r\n\t\t\t\t\t\tclient.send(brightnessMessageBuffer, 0, brightnessMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'colorTemInKelvin':\r\n\t\t\t\t\t\tconst colorTempMessage = { msg: { cmd: 'colorTemInKelvin', data: { value: state.val } } };\r\n\t\t\t\t\t\tconst colorTempMessageBuffer = Buffer.from(JSON.stringify(colorTempMessage));\r\n\t\t\t\t\t\tclient.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.log.error('device not found');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nif (require.main !== module) {\r\n\t// Export the constructor in compact mode\r\n\tmodule.exports = (options: Partial | undefined) => new GoveeLocal(options);\r\n} else {\r\n\t// otherwise start the instance directly\r\n\t(() => new GoveeLocal())();\r\n}\r\n\r\n/**\r\n * This method returns the description for device information datapoints\r\n * to unbloat the upper methods.\r\n * tanslations would be great here\r\n * @param name the name of the parameter retrieved from the device\r\n * @returns the description, that should be set to the datapoint\r\n */\r\nfunction getDatapointDescription(name: string): string {\r\n\tswitch (name) {\r\n\t\tcase 'model':\r\n\t\t\treturn 'Specific model of the Lamp';\r\n\t\tcase 'ip':\r\n\t\t\treturn 'IP address of the Lamp';\r\n\t\tcase 'bleVersionHard':\r\n\t\t\treturn 'Bluetooth Low Energy Hardware Version';\r\n\t\tcase 'bleVersionSoft':\r\n\t\t\treturn 'Bluetooth Low Energy Software Version';\r\n\t\tcase 'wifiVersionHard':\r\n\t\t\treturn 'WiFi Hardware Version';\r\n\t\tcase 'wifiVersionSoft':\r\n\t\t\treturn 'WiFi Software Version';\r\n\t\tdefault:\r\n\t\t\treturn '';\r\n\t}\r\n}\r\n\r\n/**\r\n * Convert number (<255) to two digit hex for colorcode\r\n * @param the int value, should me < 255\r\n * @returns the hex value as string\r\n */\r\nfunction componentToHex(c: number): string {\r\n\tconst hex = c.toString(16);\r\n\treturn hex.length == 1 ? '0' + hex : hex;\r\n}\r\n"], - "mappings": "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", + "sourcesContent": ["/*\r\n * Created with @iobroker/create-adapter v2.3.0\r\n */\r\n\r\n// The adapter-core module gives you access to the core ioBroker functions\r\n// you need to create an adapter\r\nimport * as utils from '@iobroker/adapter-core';\r\nimport * as dgram from 'node:dgram';\r\n\r\n// Load your modules here, e.g.:\r\n// import * as fs from \"fs\";\r\n\r\nconst LOCAL_PORT = 4002;\r\nconst SEND_SCAN_PORT = 4001;\r\nconst CONTROL_PORT = 4003;\r\nconst M_CAST = '239.255.255.250';\r\n\r\nconst server = dgram.createSocket('udp4');\r\nconst client = dgram.createSocket('udp4');\r\n\r\nconst scanMessage = { msg: { cmd: 'scan', data: { account_topic: 'reserved' } } };\r\nconst requestStatusMessage = { msg: { cmd: 'devStatus', data: {} } };\r\n\r\nlet searchInterval: NodeJS.Timeout;\r\n\r\nconst intervals: { [device: string]: NodeJS.Timeout } = {};\r\n\r\nclass GoveeLocal extends utils.Adapter {\r\n\tpublic constructor(options: Partial = {}) {\r\n\t\tsuper({\r\n\t\t\t...options,\r\n\t\t\tname: 'govee-local',\r\n\t\t});\r\n\t\tthis.on('ready', this.onReady.bind(this));\r\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\r\n\t\t// this.on('objectChange', this.onObjectChange.bind(this));\r\n\t\t// this.on('message', this.onMessage.bind(this));\r\n\t\tthis.on('unload', this.onUnload.bind(this));\r\n\t}\r\n\r\n\t/**\r\n\t * Is called when databases are connected and adapter received configuration.\r\n\t */\r\n\tprivate async onReady(): Promise {\r\n\t\tthis.setObjectNotExists('info.connection', {\r\n\t\t\ttype: 'state',\r\n\t\t\tcommon: {\r\n\t\t\t\tname: 'Device discovery running',\r\n\t\t\t\ttype: 'boolean',\r\n\t\t\t\trole: 'indicator.connected',\r\n\t\t\t\tread: true,\r\n\t\t\t\twrite: false,\r\n\t\t\t},\r\n\t\t\tnative: {},\r\n\t\t});\r\n\t\tserver.on('message', this.onUdpMessage.bind(this));\r\n\t\tserver.on('error', (error) => {\r\n\t\t\tthis.log.error('server bind error : ' + error.message);\r\n\t\t\tthis.setState('info.connection', { val: false, ack: true });\r\n\t\t});\r\n\r\n\t\tserver.bind(LOCAL_PORT, this.serverBound.bind(this));\r\n\r\n\t\tthis.subscribeStates('*.devStatus.*');\r\n\t}\r\n\r\n\t/**\r\n\t * handles when udp socket is up\r\n\t * configure multicast membership\r\n\t * start periodic scan for devices\r\n\t */\r\n\tprivate async serverBound(): Promise {\r\n\t\tserver.setBroadcast(true);\r\n\t\tserver.setMulticastTTL(128);\r\n\t\tserver.addMembership(M_CAST);\r\n\t\tthis.setState('info.connection', { val: true, ack: true });\r\n\t\tthis.log.info('UDP listening on ' + server.address().address + ':' + server.address().port);\r\n\r\n\t\tconst result = this.setInterval(this.sendScan.bind(this), this.config.searchInterval * 1000);\r\n\t\tif (result !== void 0) {\r\n\t\t\tsearchInterval = result;\r\n\t\t}\r\n\t\t// this.sendScan();\r\n\t}\r\n\r\n\t/**\r\n\t * handle icoming messages on the udp socket\r\n\t * @param message the message itself\r\n\t * @param remote the sender of the message\r\n\t */\r\n\tprivate async onUdpMessage(message: Buffer, remote: dgram.RemoteInfo): Promise {\r\n\t\tconst messageObject = JSON.parse(message.toString());\r\n\t\tswitch (messageObject.msg.cmd) {\r\n\t\t\tcase 'scan':\r\n\t\t\t\tfor (const key of Object.keys(messageObject.msg.data)) {\r\n\t\t\t\t\tif (key != 'device') {\r\n\t\t\t\t\t\tconst deviceName = messageObject.msg.data.device.replace(this.FORBIDDEN_CHARS, '_');\r\n\t\t\t\t\t\tthis.setObjectNotExists(deviceName, {\r\n\t\t\t\t\t\t\ttype: 'device',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: messageObject.msg.data.sku,\r\n\t\t\t\t\t\t\t\trole: 'group',\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${deviceName}.deviceInfo.${key}`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: getDatapointDescription(key),\r\n\t\t\t\t\t\t\t\ttype: 'string',\r\n\t\t\t\t\t\t\t\trole: 'state',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: false,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${deviceName}.deviceInfo.${key}`, {\r\n\t\t\t\t\t\t\tval: messageObject.msg.data[key],\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (!(messageObject.msg.data.device in intervals)) {\r\n\t\t\t\t\tconst result = this.setInterval(\r\n\t\t\t\t\t\t() => this.requestDeviceStatus(messageObject.msg.data.ip),\r\n\t\t\t\t\t\tthis.config.deviceStatusRefreshInterval * 1000,\r\n\t\t\t\t\t);\r\n\t\t\t\t\tif (result !== void 0) {\r\n\t\t\t\t\t\tintervals[messageObject.msg.data.device] = result;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'devStatus':\r\n\t\t\t\tconst devices = await this.getStatesAsync(this.name + '.' + this.instance + '.*.deviceInfo.ip');\r\n\t\t\t\tfor (const key in devices) {\r\n\t\t\t\t\tif (devices[key as keyof typeof devices].val == remote.address) {\r\n\t\t\t\t\t\tconst sendingDevice = key.split('.')[2].replace(this.FORBIDDEN_CHARS, '_');\r\n\t\t\t\t\t\tconst devStatusMessageObject = JSON.parse(message.toString());\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'On / Off state of the lamp',\r\n\t\t\t\t\t\t\t\ttype: 'boolean',\r\n\t\t\t\t\t\t\t\trole: 'switch',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.onOff`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.onOff == 1,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.brightness`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'Brightness of the light',\r\n\t\t\t\t\t\t\t\ttype: 'number',\r\n\t\t\t\t\t\t\t\trole: 'level.dimmer',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.brightness`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.brightness,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.color`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'Current showing color of the lamp',\r\n\t\t\t\t\t\t\t\ttype: 'string',\r\n\t\t\t\t\t\t\t\trole: 'level.color.rgb',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.color`, {\r\n\t\t\t\t\t\t\tval:\r\n\t\t\t\t\t\t\t\t'#' +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.r) +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.g) +\r\n\t\t\t\t\t\t\t\tcomponentToHex(devStatusMessageObject.msg.data.color.b),\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setObjectNotExists(`${sendingDevice}.devStatus.colorTemInKelvin`, {\r\n\t\t\t\t\t\t\ttype: 'state',\r\n\t\t\t\t\t\t\tcommon: {\r\n\t\t\t\t\t\t\t\tname: 'If staying in white light, the color temperature',\r\n\t\t\t\t\t\t\t\ttype: 'number',\r\n\t\t\t\t\t\t\t\trole: 'level.color.temperature',\r\n\t\t\t\t\t\t\t\tread: true,\r\n\t\t\t\t\t\t\t\twrite: true,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnative: {},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis.setState(`${sendingDevice}.devStatus.colorTemInKelvin`, {\r\n\t\t\t\t\t\t\tval: devStatusMessageObject.msg.data.colorTemInKelvin,\r\n\t\t\t\t\t\t\tack: true,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tthis.log.info('message from: ' + remote.address + ':' + remote.port + ' - ' + message);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * sends the device status request to one specific device\r\n\t * @param receiver the ip ( / hsotname ) of the device that should be queried\r\n\t */\r\n\tprivate requestDeviceStatus(receiver: string): void {\r\n\t\tconsole.log('request device status ' + receiver);\r\n\t\tconst requestDeviceStatusBuffer = Buffer.from(JSON.stringify(requestStatusMessage));\r\n\t\tclient.send(requestDeviceStatusBuffer, 0, requestDeviceStatusBuffer.length, CONTROL_PORT, receiver);\r\n\t}\r\n\r\n\t/**\r\n\t * send the scan message to the udp multicast address\r\n\t */\r\n\tprivate async sendScan(): Promise {\r\n\t\tconst scanMessageBuffer = Buffer.from(JSON.stringify(scanMessage));\r\n\t\tclient.send(scanMessageBuffer, 0, scanMessageBuffer.length, SEND_SCAN_PORT, M_CAST);\r\n\t\t//this.log.info('send message ' + scanMessageBuffer);\r\n\t}\r\n\r\n\t/**\r\n\t * Is called when adapter shuts down - callback has to be called under any circumstances!\r\n\t */\r\n\tprivate onUnload(callback: () => void): void {\r\n\t\ttry {\r\n\t\t\tthis.clearInterval(searchInterval);\r\n\t\t\tObject.entries(intervals).forEach(([_, interval]) => this.clearInterval(interval));\r\n\t\t\tclient.close();\r\n\t\t\tserver.close();\r\n\t\t\tthis.setState('info.connection', { val: false, ack: true });\r\n\t\t\tcallback();\r\n\t\t} catch (e) {\r\n\t\t\tcallback();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Is called if a subscribed state changes\r\n\t */\r\n\tprivate async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise {\r\n\t\tif (state && !state.ack) {\r\n\t\t\tconst ipOfDevice = await this.getStateAsync(id.split('.')[2] + '.deviceInfo.ip');\r\n\t\t\tif (ipOfDevice) {\r\n\t\t\t\tthis.log.info('should send to ip : ' + ipOfDevice.val);\r\n\t\t\t\tconst receiver = ipOfDevice.val?.toString();\r\n\t\t\t\tswitch (id.split('.')[4]) {\r\n\t\t\t\t\tcase 'onOff':\r\n\t\t\t\t\t\tconst turnMessage = { msg: { cmd: 'turn', data: { value: state.val ? 1 : 0 } } };\r\n\t\t\t\t\t\tconst turnMessageBuffer = Buffer.from(JSON.stringify(turnMessage));\r\n\t\t\t\t\t\tclient.send(turnMessageBuffer, 0, turnMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'brightness':\r\n\t\t\t\t\t\tconst brightnessMessage = { msg: { cmd: 'brightness', data: { value: state.val } } };\r\n\t\t\t\t\t\tconst brightnessMessageBuffer = Buffer.from(JSON.stringify(brightnessMessage));\r\n\t\t\t\t\t\tclient.send(brightnessMessageBuffer, 0, brightnessMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'colorTemInKelvin':\r\n\t\t\t\t\t\tconst colorTempMessage = { msg: { cmd: 'colorTemInKelvin', data: { value: state.val } } };\r\n\t\t\t\t\t\tconst colorTempMessageBuffer = Buffer.from(JSON.stringify(colorTempMessage));\r\n\t\t\t\t\t\tclient.send(colorTempMessageBuffer, 0, colorTempMessageBuffer.length, CONTROL_PORT, receiver);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.log.error('device not found');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nif (require.main !== module) {\r\n\t// Export the constructor in compact mode\r\n\tmodule.exports = (options: Partial | undefined) => new GoveeLocal(options);\r\n} else {\r\n\t// otherwise start the instance directly\r\n\t(() => new GoveeLocal())();\r\n}\r\n\r\n/**\r\n * This method returns the description for device information datapoints\r\n * to unbloat the upper methods.\r\n * tanslations would be great here\r\n * @param name the name of the parameter retrieved from the device\r\n * @returns the description, that should be set to the datapoint\r\n */\r\nfunction getDatapointDescription(name: string): string {\r\n\tswitch (name) {\r\n\t\tcase 'model':\r\n\t\t\treturn 'Specific model of the Lamp';\r\n\t\tcase 'ip':\r\n\t\t\treturn 'IP address of the Lamp';\r\n\t\tcase 'bleVersionHard':\r\n\t\t\treturn 'Bluetooth Low Energy Hardware Version';\r\n\t\tcase 'bleVersionSoft':\r\n\t\t\treturn 'Bluetooth Low Energy Software Version';\r\n\t\tcase 'wifiVersionHard':\r\n\t\t\treturn 'WiFi Hardware Version';\r\n\t\tcase 'wifiVersionSoft':\r\n\t\t\treturn 'WiFi Software Version';\r\n\t\tdefault:\r\n\t\t\treturn '';\r\n\t}\r\n}\r\n\r\n/**\r\n * Convert number (<255) to two digit hex for colorcode\r\n * @param the int value, should me < 255\r\n * @returns the hex value as string\r\n */\r\nfunction componentToHex(c: number): string {\r\n\tconst hex = c.toString(16);\r\n\treturn hex.length == 1 ? '0' + hex : hex;\r\n}\r\nfunction name2id(pName): string {\r\n\treturn (pName || '').replace(this.FORBIDDEN_CHARS, '_');\r\n}\r\n"], + "mappings": "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", "names": [] } diff --git a/src/main.ts b/src/main.ts index 68bb9fc..c57c0d5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -94,7 +94,8 @@ class GoveeLocal extends utils.Adapter { case 'scan': for (const key of Object.keys(messageObject.msg.data)) { if (key != 'device') { - this.setObjectNotExists(messageObject.msg.data.device, { + const deviceName = messageObject.msg.data.device.replace(this.FORBIDDEN_CHARS, '_'); + this.setObjectNotExists(deviceName, { type: 'device', common: { name: messageObject.msg.data.sku, @@ -102,7 +103,7 @@ class GoveeLocal extends utils.Adapter { }, native: {}, }); - this.setObjectNotExists(`${messageObject.msg.data.device}.deviceInfo.${key}`, { + this.setObjectNotExists(`${deviceName}.deviceInfo.${key}`, { type: 'state', common: { name: getDatapointDescription(key), @@ -113,7 +114,7 @@ class GoveeLocal extends utils.Adapter { }, native: {}, }); - this.setState(`${messageObject.msg.data.device}.deviceInfo.${key}`, { + this.setState(`${deviceName}.deviceInfo.${key}`, { val: messageObject.msg.data[key], ack: true, }); @@ -133,7 +134,7 @@ class GoveeLocal extends utils.Adapter { const devices = await this.getStatesAsync(this.name + '.' + this.instance + '.*.deviceInfo.ip'); for (const key in devices) { if (devices[key as keyof typeof devices].val == remote.address) { - const sendingDevice = key.split('.')[2]; + const sendingDevice = key.split('.')[2].replace(this.FORBIDDEN_CHARS, '_'); const devStatusMessageObject = JSON.parse(message.toString()); this.setObjectNotExists(`${sendingDevice}.devStatus.onOff`, { type: 'state', @@ -317,3 +318,6 @@ function componentToHex(c: number): string { const hex = c.toString(16); return hex.length == 1 ? '0' + hex : hex; } +function name2id(pName): string { + return (pName || '').replace(this.FORBIDDEN_CHARS, '_'); +}