diff --git a/admin/i18n/de.json b/admin/i18n/de.json index 90209c8..f951388 100644 --- a/admin/i18n/de.json +++ b/admin/i18n/de.json @@ -68,5 +68,7 @@ "chars": "Zeichen", "connected": "verbunden", "ms": "ms", - "rejectUnauthorized": "Nicht autorisierte Server ablehnen" + "rejectUnauthorized": "Nicht autorisierte Server ablehnen", + "Parse comma-separated numbers as character codes": "Komma-getrennte Zahlen als Zeichencodes interpretieren", + "Convert comma-separated numbers like '72,101,108,108,111' to text strings like 'Hello'. Only values 0-255 are supported.": "Konvertiere komma-getrennte Zahlen wie '72,101,108,108,111' in Textstrings wie 'Hello'. Nur Werte 0-255 werden unterstützt." } diff --git a/admin/i18n/en.json b/admin/i18n/en.json index 8f0972f..57016d0 100644 --- a/admin/i18n/en.json +++ b/admin/i18n/en.json @@ -68,5 +68,7 @@ "chars": "chars", "connected": "connected", "ms": "ms", - "rejectUnauthorized": "Reject unauthorized servers" + "rejectUnauthorized": "Reject unauthorized servers", + "Parse comma-separated numbers as character codes": "Parse comma-separated numbers as character codes", + "Convert comma-separated numbers like '72,101,108,108,111' to text strings like 'Hello'. Only values 0-255 are supported.": "Convert comma-separated numbers like '72,101,108,108,111' to text strings like 'Hello'. Only values 0-255 are supported." } \ No newline at end of file diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index 4349bd3..5136643 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -479,6 +479,16 @@ "md": 6, "lg": 4, "xl": 4 + }, + "parseCharCodes": { + "type": "checkbox", + "label": "Parse comma-separated numbers as character codes", + "tooltip": "Convert comma-separated numbers like '72,101,108,108,111' to text strings like 'Hello'. Only values 0-255 are supported.", + "xs": 12, + "sm": 12, + "md": 6, + "lg": 4, + "xl": 4 } } } diff --git a/lib/common.js b/lib/common.js index dc6e418..28974a7 100644 --- a/lib/common.js +++ b/lib/common.js @@ -227,14 +227,29 @@ function convertMessage(topic, message, adapter, clientID) { } // try to convert 101,124,444,... To utf8 string - if (type === 'string' && message.match(/^(\d)+,\s?(\d)+,\s?(\d)+/)) { + if (type === 'string' && adapter.config.parseCharCodes && message.match(/^(\d)+,\s?(\d)+,\s?(\d)+/)) { const parts = message.split(','); try { let str = ''; + let allValidCharCodes = true; + + // Build string and validate character codes in one pass for (let p = 0; p < parts.length; p++) { - str += String.fromCharCode(parseInt(parts[p].trim(), 10)); + const charCode = parseInt(parts[p].trim(), 10); + // Only allow values that work reliably with String.fromCharCode (0-255 for extended ASCII) + if (charCode < 0 || charCode > 255) { + allValidCharCodes = false; + break; + } + str += String.fromCharCode(charCode); + } + + // Only use the converted string if all parts were valid character codes + if (allValidCharCodes) { + message = str; + // Character code conversion complete - don't try to parse as number later + return message; } - message = str; } catch { // cannot convert and ignore it } diff --git a/test/testConvert.js b/test/testConvert.js index ea44f9a..4be2e02 100644 --- a/test/testConvert.js +++ b/test/testConvert.js @@ -1,8 +1,57 @@ const expect = require('chai').expect; const convertMessage = require('../lib/common').convertMessage; +const adapterMockWithCharCodes = { config: { parseCharCodes: true } }; +const adapterMockWithoutCharCodes = { config: { parseCharCodes: false } }; + describe('Test convert version', function () { it('Number', async () => { - expect(convertMessage('data', '233.57')).to.be.equal(233.57); + expect(convertMessage('data', '233.57', adapterMockWithoutCharCodes)).to.be.equal(233.57); + }); + + it('Character code conversion - enabled - valid ASCII codes', async () => { + expect(convertMessage('data', '65,66,67', adapterMockWithCharCodes)).to.be.equal('ABC'); + expect(convertMessage('data', '72,101,108,108,111', adapterMockWithCharCodes)).to.be.equal('Hello'); + expect(convertMessage('data', '32,87,111,114,108,100', adapterMockWithCharCodes)).to.be.equal(' World'); + }); + + it('Character code conversion - disabled - no conversion should happen', async () => { + expect(convertMessage('data', '65,66,67', adapterMockWithoutCharCodes)).to.be.equal('65,66,67'); + expect(convertMessage('data', '72,101,108,108,111', adapterMockWithoutCharCodes)).to.be.equal('72,101,108,108,111'); + expect(convertMessage('data', '32,87,111,114,108,100', adapterMockWithoutCharCodes)).to.be.equal('32,87,111,114,108,100'); + }); + + it('Character code conversion - invalid codes should remain unchanged', async () => { + // NUKI 3.0 Pro lock data - should NOT be converted (even when enabled) + expect(convertMessage('data', '3,0,442236930,1,2', adapterMockWithCharCodes)).to.be.equal('3,0,442236930,1,2'); + + // Values above 255 should NOT be converted (even when enabled) + expect(convertMessage('data', '256,512,1024', adapterMockWithCharCodes)).to.be.equal('256,512,1024'); + expect(convertMessage('data', '70000,80000,90000', adapterMockWithCharCodes)).to.be.equal('70000,80000,90000'); + + // Negative numbers should NOT be converted (even when enabled) + expect(convertMessage('data', '-1,2,3', adapterMockWithCharCodes)).to.be.equal('-1,2,3'); + + // Mix of valid and invalid should NOT be converted (even when enabled) + expect(convertMessage('data', '0,65535,1114112', adapterMockWithCharCodes)).to.be.equal('0,65535,1114112'); + }); + + it('Character code conversion - edge cases', async () => { + // Control characters (0-31) are valid when enabled + expect(convertMessage('data', '9,10,13', adapterMockWithCharCodes)).to.be.equal('\t\n\r'); + + // Extended ASCII (128-255) is valid when enabled + expect(convertMessage('data', '128,129,130', adapterMockWithCharCodes)).to.be.equal('\x80\x81\x82'); + + // Single number doesn't match pattern (requires at least 3 numbers) but may be converted to number + expect(convertMessage('data', '65', adapterMockWithCharCodes)).to.be.equal(65); // String '65' becomes number 65 + expect(convertMessage('data', '65,66', adapterMockWithCharCodes)).to.be.equal(65.66); // Two numbers: '65,66' -> '65.66' -> 65.66 + }); + + it('Character code conversion - non-numeric data unchanged', async () => { + expect(convertMessage('data', 'hello,world', adapterMockWithCharCodes)).to.be.equal('hello,world'); + expect(convertMessage('data', 'a,b,c', adapterMockWithCharCodes)).to.be.equal('a,b,c'); + expect(convertMessage('data', '1,2,abc', adapterMockWithCharCodes)).to.be.equal('1,2,abc'); + expect(convertMessage('data', '3.14,2.71,1.41', adapterMockWithCharCodes)).to.be.equal('3.14,2.71,1.41'); }); });