From 27fedd5bfc2de0cf932addb63290e32cdd00f195 Mon Sep 17 00:00:00 2001
From: foxthefox <16841643+foxthefox@users.noreply.github.com>
Date: Sat, 11 Feb 2023 22:40:58 +0100
Subject: [PATCH 1/3] 1.0.2
---
README.md | 4 +-
lib/data/getriggerlistinfos.xml | 5 +
lib/data/test_api_response.xml | 33 ++++-
lib/fritz_ahaapi.js | 52 +++++++-
lib/fritz_mockserver.js | 12 +-
lib/xml2json.js | 208 ++++++++++++++++++++++++++++++++
package-lock.json | 5 -
package.json | 9 +-
8 files changed, 308 insertions(+), 20 deletions(-)
create mode 100644 lib/data/getriggerlistinfos.xml
create mode 100644 lib/xml2json.js
diff --git a/README.md b/README.md
index 913e735..75747da 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ const logout = await fritz.logout_SID();
see the example.js.
## API Calls
-* todo for 1.0.2
+* todo for 1.0.3
## Changelog
### 1.0.2
@@ -51,7 +51,7 @@ see the example.js.
* (foxthefox) implementation of new commands from API version 1.57
### 1.0.1
-* skipping usage of chalk, figlet
+* (foxthefox) skipping usage of chalk, figlet
### 1.0.0
* (foxthefox) common js module with 2 named exports Fritz and FritzEmu
diff --git a/lib/data/getriggerlistinfos.xml b/lib/data/getriggerlistinfos.xml
new file mode 100644
index 0000000..fdd48b2
--- /dev/null
+++ b/lib/data/getriggerlistinfos.xml
@@ -0,0 +1,5 @@
+
+
+ Trigger AlertOn
+
+
\ No newline at end of file
diff --git a/lib/data/test_api_response.xml b/lib/data/test_api_response.xml
index 107f229..66bafd4 100644
--- a/lib/data/test_api_response.xml
+++ b/lib/data/test_api_response.xml
@@ -660,4 +660,35 @@
1617368283
-
+
+ 11934 0318081
+ 407
+ 1
+ 31.20
+ 0x0feb
+ HAN-FUN
+ 1
+ 0
+ HAN-FUN #2
+
+
+ 11934 0318081-1
+ 2001
+ 8208
+ 0.0
+ 0x0feb
+ HAN-FUN
+ 1
+ 0
+ HAN-FUN #2
+
+ 407
+ 514
+ 256
+
+
+ 0
+ 1670596133
+
+
+
\ No newline at end of file
diff --git a/lib/fritz_ahaapi.js b/lib/fritz_ahaapi.js
index 24f1bcd..b1df123 100644
--- a/lib/fritz_ahaapi.js
+++ b/lib/fritz_ahaapi.js
@@ -67,6 +67,16 @@ class Fritz {
}
}
+ // get trigger information (XML)
+ async getTriggerListInfos() {
+ try {
+ const body = this.executeCommand2('gettriggerlistinfos', '', 1);
+ return Promise.resolve(body);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ }
+
// get basic device stats (XML)
/**
* other generic functions
@@ -250,11 +260,12 @@ class Fritz {
}
}
- // set color hue or saturation
+ // set color hue or saturation, limited to the colordefaults
/**
* @param {string} ain
* @param {string | number | boolean | null} saturation
* @param {string | number | boolean | null} hue
+ * duration is not supportet
*/
async setColor(ain, saturation, hue) {
try {
@@ -269,6 +280,40 @@ class Fritz {
}
}
+ // set color hue or saturation, free in the HSV-color schema
+ /**
+ * @param {string} ain
+ * @param {string | number | boolean | null} saturation
+ * @param {string | number | boolean | null} hue
+ * duration is not supportet
+ */
+ async setUnmappedColor(ain, saturation, hue) {
+ try {
+ const body = await this.executeCommand2(
+ 'setunmappedcolor&saturation=' + saturation + '&hue=' + hue + '&duration=0',
+ ain,
+ 1
+ );
+ return Promise.resolve('OK');
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ }
+
+ // set trigger active
+ /**
+ * @param {string} ain
+ * @param {string | number | boolean} active
+ */
+ async setTriggerActive(ain, active) {
+ try {
+ const val = active === 1 || active === '1' || active === true ? '1' : '0';
+ const body = await this.executeCommand2('settriggeractive¶m=' + val, ain, 1);
+ return Promise.resolve(active);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ }
// apply template
/**
* @param {string} ain
@@ -496,7 +541,8 @@ class Fritz {
path: LOGIN_SID_ROUTE,
method: 'GET',
headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'User-Agent': 'fritzdect_aha_nodejs/1.0.0'
},
rejectUnauthorized: false
};
@@ -521,6 +567,7 @@ class Fritz {
// resolve on end
res.on('end', () => {
if (responseBody) {
+ // login probleme abfangen issue#377 fritzdect
const challenge = responseBody.match('(.*?)')[1];
const blocktime = Math.floor(responseBody.match('(.*?)')[1]);
const pbkf2 = challenge.startsWith('2$') ? true : false;
@@ -634,6 +681,7 @@ class Fritz {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
+ 'User-Agent': 'fritzdect_aha_nodejs/1.0.0',
'Content-Length': Buffer.byteLength(xFormBody)
},
rejectUnauthorized: false
diff --git a/lib/fritz_mockserver.js b/lib/fritz_mockserver.js
index d4788a4..d93734f 100644
--- a/lib/fritz_mockserver.js
+++ b/lib/fritz_mockserver.js
@@ -3,7 +3,7 @@
const http = require('http');
const fs = require('fs');
const { parse } = require('querystring');
-const parser = require('xml2json-light');
+const parser = require('./xml2json.js');
const crypto = require('crypto');
const path = require('path');
@@ -11,8 +11,9 @@ console.log('PATH ist ' + path.join(__dirname, './data/'));
const xmlDevicesGroups = fs.readFileSync(path.join(__dirname, './data/') + 'test_api_response.xml');
//var xmlDevicesGroups = fs.readFileSync('./test.xml');
-const xmlTemplate = fs.readFileSync(path.join(__dirname, './data/') + 'template_answer.xml');
+const xmlTemplate = fs.readFileSync(path.join(__dirname, './data/') + 'template_new_fail.xml');
const xmlTempStat = fs.readFileSync(path.join(__dirname, './data/') + 'devicestat_temp_answer.xml');
+const xmlTriggerlist = fs.readFileSync(path.join(__dirname, './data/') + 'getriggerlistinfos.xml');
const xmlPowerStats = fs.readFileSync(path.join(__dirname, './data/') + 'devicestat_power_answer.xml');
const xmlColorDefaults = fs.readFileSync(path.join(__dirname, './data/') + 'color_defaults.xml');
const hkr_batt = fs.readFileSync(path.join(__dirname, './data/') + 'hkr_response.xml');
@@ -706,6 +707,11 @@ function homeautoswitchAnswer(
case 'setmetadata':
break;
case 'gettriggerlistinfo':
+ // todo empty templates
+ response.writeHead(200, { 'xmlDevicesGroups-Type': 'application/json' });
+ response.write(String(xmlTriggerlist));
+ response.end();
+ return response;
break;
case 'settriggeractive':
break;
@@ -756,7 +762,6 @@ class FritzEmu {
this.debugmode = debugmode;
this.deviceresponse = null;
}
- // S7WebAPI
setupHttpServer(callback) {
console.log('\x1b[33m', ' _____ ____ _ _ _ _ ');
console.log('\x1b[33m', ' | ___| __ ) / \\ | | | | / \\ ');
@@ -876,6 +881,7 @@ class FritzEmu {
break;
}
}
+ console.log('\x1b[0m');
if (reqstring[0] == '/login_sid.lua') {
if (version == 2) {
diff --git a/lib/xml2json.js b/lib/xml2json.js
new file mode 100644
index 0000000..852ea4f
--- /dev/null
+++ b/lib/xml2json.js
@@ -0,0 +1,208 @@
+/*
+copy of https://github.com/enkidoo-ai/xml2json
+The MIT License (MIT)
+Copyright (c) 2016 Société Enkidoo Technologies Inc.
+
+extended by corrections for
+- tag names also in subhierarchy causing maximum stack trace fault
+- tags with 2digit length
+- only one clean
+
+all part of PR #8
+*/
+
+'use strict';
+
+module.exports = {
+ xml2json: xml2json
+};
+
+//***********************************************************************
+// Main function. Clears the given xml and then starts the recursion
+//***********************************************************************
+function xml2json(xmlStr) {
+ xmlStr = cleanXML(xmlStr);
+ return xml2jsonRecurse(xmlStr);
+}
+
+//***********************************************************************
+// Recursive function that creates a JSON object with a given XML string.
+//***********************************************************************
+function xml2jsonRecurse(xmlStr) {
+ var obj = {},
+ tagName,
+ indexClosingTag,
+ inner_substring,
+ tempVal,
+ openingTag;
+
+ while (xmlStr.match(/<[^\/][^>]*>/)) {
+ openingTag = xmlStr.match(/<[^\/][^>]*>/)[0];
+ tagName = openingTag.substring(1, openingTag.length - 1);
+ indexClosingTag = xmlStr.indexOf(openingTag.replace('<', ''));
+
+ // indexClosingTag is the first occurance of the closing tag, if there are same tags in other hierarchy, then this is the wrong catch
+ // search for next openingTag is needed
+ // if the next openingTag has smaller index than the next closingIndex then this portion must be part of the string
+ let tmpString = xmlStr.substring(openingTag.length, xmlStr.length);
+ let nextOpeningIndex = tmpString.indexOf(openingTag);
+ let nextClosingIndex = tmpString.indexOf(openingTag.replace('<', ''));
+ let cutLength = openingTag.length + nextClosingIndex;
+
+ // indexClosingTag to be replaced when not beeing itself and there is deeper level with same tagName && tempClosingIndex < nextOpeningIndex
+ // repeat the search until only closing tag exists
+ let j = 1;
+ while (indexClosingTag != -1 && nextOpeningIndex != -1 && nextOpeningIndex < nextClosingIndex) {
+ //console.log(' while ', j);
+ tmpString = xmlStr.substring(cutLength + (openingTag.length + 1) * j, xmlStr.length);
+ nextOpeningIndex = tmpString.indexOf(openingTag);
+ nextClosingIndex = tmpString.indexOf(openingTag.replace('<', ''));
+ cutLength = cutLength + nextClosingIndex;
+ //console.log(openingTag, ' nextClose ', nextClosingIndex);
+ //shifting the index of closing tag to the position where no other opening detected tag with same name is found
+ indexClosingTag = cutLength + (openingTag.length + 1) * j;
+ j++;
+ }
+
+ // account for case where additional information in the openning tag
+ if (indexClosingTag == -1) {
+ tagName = openingTag.match(/[^<][\w+$]*/)[0];
+ indexClosingTag = xmlStr.indexOf('' + tagName);
+ if (indexClosingTag == -1) {
+ indexClosingTag = xmlStr.indexOf('<\\/' + tagName);
+ }
+ }
+ inner_substring = xmlStr.substring(openingTag.length, indexClosingTag);
+ if (inner_substring.match(/<[^\/][^>]*>/)) {
+ //no need for cleanXML again
+ //tempVal = xml2json(inner_substring);
+ tempVal = xml2jsonRecurse(inner_substring);
+ } else {
+ tempVal = inner_substring;
+ }
+ // account for array or obj //
+ if (obj[tagName] === undefined) {
+ obj[tagName] = tempVal;
+ } else if (Array.isArray(obj[tagName])) {
+ obj[tagName].push(tempVal);
+ } else {
+ obj[tagName] = [ obj[tagName], tempVal ];
+ }
+
+ xmlStr = xmlStr.substring(openingTag.length * 2 + 1 + inner_substring.length);
+ }
+
+ return obj;
+}
+
+//*****************************************************************
+// Removes some characters that would break the recursive function.
+//*****************************************************************
+function cleanXML(xmlStr) {
+ xmlStr = xmlStr.replace(//g, ''); //remove commented lines
+ xmlStr = xmlStr.replace(/\n|\t|\r/g, ''); //replace special characters
+ xmlStr = xmlStr.replace(/ {1,}<|\t{1,} {1,}|>\t{1,}/g, '>'); //replace trailing spaces and tabs
+ xmlStr = xmlStr.replace(/<\?[^>]*\?>/g, ''); //delete docType tags
+ xmlStr = replaceSelfClosingTags(xmlStr); //replace self closing tags
+ xmlStr = replaceAloneValues(xmlStr); //replace the alone tags values
+ xmlStr = replaceAttributes(xmlStr); //replace attributes
+ return xmlStr;
+}
+
+//************************************************************************************************************
+// Replaces all the self closing tags with attributes with another tag containing its attribute as a property.
+// The function works if the tag contains multiple attributes.
+//
+// Example : '' becomes
+// 'attrValue'
+//************************************************************************************************************
+function replaceSelfClosingTags(xmlStr) {
+ var selfClosingTags = xmlStr.match(/<[^/][^>]*\/>/g);
+ if (selfClosingTags) {
+ for (var i = 0; i < selfClosingTags.length; i++) {
+ var oldTag = selfClosingTags[i];
+ var tempTag = oldTag.substring(0, oldTag.length - 2);
+ tempTag += '>';
+
+ var tagName = oldTag.match(/[^<][\w+$]*/)[0];
+ var closingTag = '' + tagName + '>';
+ var newTag = '<' + tagName + '>';
+
+ //beobachten was mit einstelligen Attributwerten passiert
+ var attrs = tempTag.match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/g);
+
+ if (attrs) {
+ for (var j = 0; j < attrs.length; j++) {
+ var attr = attrs[j];
+ var attrName = attr.substring(0, attr.indexOf('='));
+ var attrValue = attr.substring(attr.indexOf('"') + 1, attr.lastIndexOf('"'));
+
+ newTag += '<' + attrName + '>' + attrValue + '' + attrName + '>';
+ }
+ }
+
+ newTag += closingTag;
+ xmlStr = xmlStr.replace(oldTag, newTag);
+ }
+ }
+
+ return xmlStr;
+}
+
+//*************************************************************************************************
+// Replaces all the tags with attributes and a value with a new tag.
+//
+// Example : 'tagValue' becomes
+// 'attrValue<_@attribute>tagValue'
+//*************************************************************************************************
+function replaceAloneValues(xmlStr) {
+ var tagsWithAttributesAndValue = xmlStr.match(/<[^\/][^>][^<]+\s+.[^<]+[=][^<]+>{1}([^<]+)/g);
+
+ if (tagsWithAttributesAndValue) {
+ for (var i = 0; i < tagsWithAttributesAndValue.length; i++) {
+ var oldTag = tagsWithAttributesAndValue[i];
+ var oldTagName = oldTag.substring(0, oldTag.indexOf('>') + 1);
+ var oldTagValue = oldTag.substring(oldTag.indexOf('>') + 1);
+
+ var newTag = oldTagName + '<_@attribute>' + oldTagValue + '';
+ xmlStr = xmlStr.replace(oldTag, newTag);
+ }
+ }
+
+ return xmlStr;
+}
+
+//*****************************************************************************************************************
+// Replaces all the tags with attributes with another tag containing its attribute as a property.
+// The function works if the tag contains multiple attributes.
+//
+// Example : '' becomes 'attrValue'
+//*****************************************************************************************************************
+function replaceAttributes(xmlStr) {
+ // the following line doesnt catch 2 digit tags
+ //var tagsWithAttributes = xmlStr.match(/<[^\/][^>][^<]+\s+.[^<]+[=][^<]+>/g);
+ // 2 digits tags are catched
+ var tagsWithAttributes = xmlStr.match(/<[^>][^<]+\s+.[^<]+[=][^<]+>/g);
+ if (tagsWithAttributes) {
+ for (var i = 0; i < tagsWithAttributes.length; i++) {
+ var oldTag = tagsWithAttributes[i];
+ var tagName = oldTag.match(/[^<][\w+$]*/)[0];
+ var newTag = '<' + tagName + '>';
+ var attrs = oldTag.match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/g);
+
+ if (attrs) {
+ for (var j = 0; j < attrs.length; j++) {
+ var attr = attrs[j];
+ var attrName = attr.substring(0, attr.indexOf('='));
+ var attrValue = attr.substring(attr.indexOf('"') + 1, attr.lastIndexOf('"'));
+
+ newTag += '<' + attrName + '>' + attrValue + '' + attrName + '>';
+ }
+ }
+ xmlStr = xmlStr.replace(oldTag, newTag);
+ }
+ }
+
+ return xmlStr;
+}
diff --git a/package-lock.json b/package-lock.json
index aae3ca2..d1bdce9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1377,11 +1377,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "xml2json-light": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/xml2json-light/-/xml2json-light-1.0.6.tgz",
- "integrity": "sha512-6CSibpteBS4B8/fzJaj6TDtWatIlonSFfVVK3TLM23mlTOxkMgVA4b2FaGeTIrrhOMdDZ8X1/dvo4mfBtsU4yw=="
- },
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index 01e34d1..2447205 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,9 @@
{
"name": "fritzdect-aha-nodejs",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "NodeJS library using the AHA api of Fritzbox to control DECT smarthome devices.",
"main": "index.js",
"dependencies": {
- "xml2json-light": "^1.0.6",
"command-line-args": "^5.2.0",
"command-line-usage": "^6.1.1"
},
@@ -21,11 +20,7 @@
"type": "git",
"url": "git+https://github.com/foxthefox/fritzdect-aha-nodejs.git"
},
- "keywords": [
- "DECT",
- "Fritzbox",
- "AHA-api"
- ],
+ "keywords": [ "DECT", "Fritzbox", "AHA-api" ],
"author": "foxthefox@wysiwis.net",
"license": "MIT",
"bugs": {
From 49cdc771b95d8f3f34d5fd19a8f45f29a4ab7a8e Mon Sep 17 00:00:00 2001
From: foxthefox <16841643+foxthefox@users.noreply.github.com>
Date: Sat, 11 Feb 2023 22:42:19 +0100
Subject: [PATCH 2/3] 1.0.2
---
lib/data/template_new_fail.xml | 49 ++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 lib/data/template_new_fail.xml
diff --git a/lib/data/template_new_fail.xml b/lib/data/template_new_fail.xml
new file mode 100644
index 0000000..63f16e1
--- /dev/null
+++ b/lib/data/template_new_fail.xml
@@ -0,0 +1,49 @@
+
+
+ alles aus
+
+
+
+
+
+
+
+
+
+ testSzenario
+ {"type":"generic","icon":3}
+
+
+
+
+
+
+
+
+
+
+ vorlage210200
+
+
+
+
+
+
+
+
+
+
+
+
+ vorlage_dect200
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From ba289a0fd8c8b1cfc608ce92b365b85cd71b4482 Mon Sep 17 00:00:00 2001
From: foxthefox <16841643+foxthefox@users.noreply.github.com>
Date: Sat, 11 Feb 2023 22:44:57 +0100
Subject: [PATCH 3/3] 1.0.2
---
test/integration.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/integration.js b/test/integration.js
index 2a0c670..f6d73b1 100644
--- a/test/integration.js
+++ b/test/integration.js
@@ -6,7 +6,7 @@ const FritzEmu = require('../index.js').FritzEmu;
const http = require('http');
const fs = require('fs');
//const { parse } = require('querystring');
-const parser = require('xml2json-light');
+const parser = require('../lib/xml2json.js');
const crypto = require('crypto');