iobroker.openknx 0.1.9 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ const util = require('util');
9
9
  // DPT11.*: date
10
10
  //
11
11
  exports.formatAPDU = (value) => {
12
- if (!value) return log.error('cannot write null value for DPT11');
12
+ if (value == null) return log.error('cannot write null value for DPT11');
13
13
  switch (typeof value) {
14
14
  case 'string':
15
15
  case 'number':
@@ -14,7 +14,7 @@ const log = require('log-driver').logger;
14
14
  */
15
15
 
16
16
  exports.formatAPDU = (value) => {
17
- if (!value || typeof value != 'number')
17
+ if (value == null || typeof value != 'number')
18
18
  log.error('DPT14: Must supply a number value');
19
19
  const apdu_data = Buffer.alloc(4);
20
20
  apdu_data.writeFloatBE(value, 0);
@@ -25,7 +25,7 @@
25
25
  const log = require('log-driver').logger;
26
26
 
27
27
  exports.formatAPDU = function (value) {
28
- if (!value) log.warn("DPT18: cannot write null value");
28
+ if (value == null) log.warn("DPT18: cannot write null value");
29
29
  else {
30
30
  var apdu_data = new Buffer(1);
31
31
  if (typeof value == 'object' &&
@@ -8,18 +8,20 @@ const log = require('log-driver').logger;
8
8
  // DPT2 frame description.
9
9
  // Always 8-bit aligned.
10
10
  exports.formatAPDU = (value) => {
11
- if (!value) return log.error('DPT2: cannot write null value');
11
+ if (value == null) return log.error('DPT2: cannot write null value');
12
12
 
13
13
  if (
14
14
  typeof value === 'object' &&
15
15
  value.hasOwnProperty('priority') &&
16
16
  value.hasOwnProperty('data')
17
17
  )
18
- return Buffer.from([(value.priority << 1) + (value.data & 0b00000001)]);
18
+ return Buffer.from([((value.priority << 1) & 0b00000010) + (value.data & 0b00000001)]);
19
19
 
20
20
  log.error('DPT2: Must supply an value {priority:<bool>, data:<bool>}');
21
21
  // FIXME: should this return zero buffer when error? Or nothing?
22
- return Buffer.from([0]);
22
+ //a malformed object shall not result in transmission
23
+ //return Buffer.from([0]);
24
+ return null;
23
25
  };
24
26
 
25
27
  exports.fromBuffer = (buf) => {
@@ -17,7 +17,7 @@ const log = require('log-driver').logger;
17
17
 
18
18
  // FIXME: help needed
19
19
  exports.formatAPDU = function(value) {
20
- if (!value) return log.error('DPT21: cannot write null value');
20
+ if (value == null) return log.error('DPT21: cannot write null value');
21
21
  log.debug('./knx/src/dpt21.js : input value = ' + value);
22
22
 
23
23
  //var apdu_data = new Buffer(1);
@@ -31,7 +31,9 @@ exports.formatAPDU = function(value) {
31
31
 
32
32
  log.error('DPT21: Must supply a value which is an object');
33
33
  //return apdu_data;
34
- return Buffer.from([0]);
34
+ //a malformed object shall not result in transmission
35
+ //return Buffer.from([0]);
36
+ return null;
35
37
  }
36
38
 
37
39
  exports.fromBuffer = function(buf) {
@@ -10,7 +10,7 @@ const log = require('log-driver').logger;
10
10
  // MSB: Red, Green, LSB: Blue
11
11
  //
12
12
  exports.formatAPDU = (value) => {
13
- if (!value) return log.error('DPT232: cannot write null value');
13
+ if (value == null) return log.error('DPT232: cannot write null value');
14
14
 
15
15
  if (typeof value === 'object') {
16
16
  const { red, green, blue } = value;
@@ -9,7 +9,7 @@ const log = require('log-driver').logger;
9
9
  // DPT237: 2-byte unsigned value
10
10
  //
11
11
  exports.formatAPDU = function(value) {
12
- if (!value) return log.error('DPT237: cannot write null value');
12
+ if (value == null) return log.error('DPT237: cannot write null value');
13
13
 
14
14
  log.trace('dpt278.js : input value = ' + value);
15
15
 
@@ -9,7 +9,7 @@ const log = require('log-driver').logger;
9
9
  // DPT3.*: 4-bit dimming/blinds control
10
10
  //
11
11
  exports.formatAPDU = (value) => {
12
- if (!value) return log.warn('DPT3: cannot write null value');
12
+ if (value == null) return log.warn('DPT3: cannot write null value');
13
13
 
14
14
  if (
15
15
  typeof value == 'object' &&
@@ -9,7 +9,7 @@ const log = require('log-driver').logger;
9
9
  // DPT4: 8-bit character
10
10
  //
11
11
  exports.formatAPDU = (value) => {
12
- if (!value) return log.warn('DPT4: cannot write null value');
12
+ if (value == null) return log.warn('DPT4: cannot write null value');
13
13
 
14
14
  if (typeof value !== 'string')
15
15
  return log.warn('DPT4: Must supply a character or string');
@@ -90,5 +90,12 @@ exports.subtypes = {
90
90
  "name" : "DPT_Brightness",
91
91
  "desc" : "interior brightness",
92
92
  "unit" : "lux"
93
- }
93
+ },
94
+ // 7.600
95
+ "600" : { "use" : "FB",
96
+ "name" : "DPT_Absolute_Colour_Temperature",
97
+ "desc" : "Absolute colour temperature",
98
+ "unit" : "K"
99
+ }
100
+
94
101
  }
@@ -1,19 +1,16 @@
1
- /* eslint-disable quotes */
2
1
  "use strict";
3
2
 
4
3
  const DPTLib = require(__dirname + "/knx/src/dptlib"); //todo copy for the moment
5
4
  const select = require("xpath");
6
- const _ = require("underscore");
7
5
  const util = require("util");
8
6
  const dom = require("xmldom").DOMParser;
9
7
  const tools = require("./tools.js");
10
-
11
- let groupAddresses = {};
8
+ const similarity = require("similarity");
12
9
 
13
10
  module.exports = {
14
11
  // get xml Files from index.html via adapter.on 'message' and put it into file Obj
15
12
  parseInput: function (adapter, xml, callback) {
16
- let fileObjectList = {};
13
+ const fileObjectList = {};
17
14
 
18
15
  if (xml) {
19
16
  fileObjectList["ga.xml"] = new dom().parseFromString(xml);
@@ -27,45 +24,50 @@ module.exports = {
27
24
 
28
25
  // generate the Groupaddress structure
29
26
  generateIoBObjects: function (adapter, fileObjectList, callback) {
30
- groupAddresses = {};
31
- let iobrokerObjects = [];
27
+ const groupAddresses = {};
28
+ const iobrokerObjects = [];
32
29
 
33
30
  function processFile(adapter, err, objectList) {
34
31
  //from xml export
35
32
  const doc = objectList["ga.xml"];
36
33
  if (doc)
37
- _.each(select.select("//*[local-name(.)='GroupAddress' and string(@Address)]", doc), function (ga) {
34
+ select.select("//*[local-name(.)='GroupAddress' and string(@Address)]", doc).forEach(ga => {
38
35
  const address = ga.getAttribute("Address");
39
36
  groupAddresses[address] = ga;
40
37
  });
41
38
 
42
39
  //format and put each found group address into the object tree
43
- _.each(groupAddresses, function (groupAddress) {
40
+ Object.entries(groupAddresses).forEach(([key, groupAddress]) => {
44
41
  const gaName = groupAddress.getAttribute("Name");
45
42
  let autoread = true;
46
- const dpt = tools.convertDPTtype(groupAddress.getAttribute("DatapointType") || groupAddress.getAttribute("DPTs")); //DPT or DPTs
47
43
  let dptObj = {};
48
- let fullPath = tools.formatGa(gaName);
49
- let range = [,];
44
+ let fullPath = tools.formatGaForIob(gaName);
45
+ let range = [];
50
46
  let role = "state";
51
47
  let type;
52
48
 
53
49
  //add name of Mittelgruppe and Hauptgruppe to fullPath if exist
54
50
  if (groupAddress.parentNode.nodeName === "GroupRange") {
55
- fullPath = tools.formatGa(groupAddress.parentNode.getAttribute("Name")) + "." + fullPath;
51
+ fullPath = tools.formatGaForIob(groupAddress.parentNode.getAttribute("Name")) + "." + fullPath;
56
52
  if (groupAddress.parentNode.parentNode.nodeName === "GroupRange") {
57
- fullPath = tools.formatGa(groupAddress.parentNode.parentNode.getAttribute("Name")) + "." + fullPath;
53
+ fullPath = tools.formatGaForIob(groupAddress.parentNode.parentNode.getAttribute("Name")) + "." + fullPath;
58
54
  }
59
55
  }
60
56
 
57
+ const dpt = tools.formatDpt(groupAddress.getAttribute("DatapointType") || groupAddress.getAttribute("DPTs")); //DPT or DPTs
61
58
  if (dpt == "") {
62
- const text = "no import of " + fullPath + " address " + groupAddress.getAttribute("Address") + " with no DPT set";
59
+ const text = `Not adding ${fullPath} because no DPT is assigned to ${groupAddress.getAttribute("Address")}. The GA is not connected to a KO in ETS`;
63
60
  adapter.log.warn(text);
64
61
  if (!err) err = text;
65
- else err += " ," + text;
62
+ else err += "<br/>\n" + text;
66
63
  return;
67
64
  }
68
-
65
+ if (dpt.indexOf(".") === -1) {
66
+ const text = `${fullPath} ${groupAddress.getAttribute("Address")} has only set a base DPT in ETS. No additional information (e.g. unit) for this GA available.`;
67
+ adapter.log.info(text);
68
+ if (!err) err = text;
69
+ else err += "<br/>\n" + text;
70
+ }
69
71
  try {
70
72
  dptObj = DPTLib.resolve(dpt);
71
73
  } catch (e) {
@@ -73,14 +75,14 @@ module.exports = {
73
75
  }
74
76
 
75
77
  // is there a scalar range? eg. DPT5.003 angle degrees (0=0, ff=360)
76
- if (!!dptObj && dptObj.hasOwnProperty("subtype") && dptObj.subtype && dptObj.subtype.hasOwnProperty("scalar_range")) {
78
+ if (!!dptObj && dptObj.subtype && dptObj.subtype.scalar_range) {
77
79
  range = dptObj.subtype.scalar_range;
78
- } else if (!!dptObj && dptObj.hasOwnProperty("scalar_range")) {
80
+ } else if (!!dptObj && dptObj.scalar_range) {
79
81
  range = dptObj.scalar_range;
80
- } else if (!!dptObj && dptObj.hasOwnProperty("subtype") && dptObj.subtype && dptObj.subtype.hasOwnProperty("range")) {
82
+ } else if (!!dptObj && dptObj.subtype && dptObj.subtype.range) {
81
83
  // just a plain numeric value, only check if within bounds
82
84
  range = dptObj.subtype.range;
83
- } else if (!!dptObj && dptObj.basetype && dptObj.basetype.hasOwnProperty("range")) {
85
+ } else if (!!dptObj && dptObj.basetype && dptObj.basetype.range) {
84
86
  // just a plain numeric value, only check if within bounds
85
87
  range = dptObj.basetype.range;
86
88
  } else if (!!dptObj && !tools.isEmptyObject(dptObj)) {
@@ -116,79 +118,175 @@ module.exports = {
116
118
  range[1] = undefined;
117
119
  type = "number";
118
120
  } else if (tools.isUnknownDPT(dpt)) {
121
+ //raw interface
119
122
  range[0] = undefined;
120
123
  range[1] = undefined;
121
- type = "string"; //raw
124
+ type = "string";
122
125
  } else {
123
126
  type = "number";
124
127
  }
125
128
 
126
129
  //if dpt is for sure not a status, remove from autoread
127
- if (tools.isStriggerDPT(dpt)) {
130
+ if (tools.isTriggerDPT(dpt)) {
128
131
  autoread = false;
129
132
  }
130
133
 
134
+ //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/objectsschema.md#objects
131
135
  const iobrokerObject = {
132
136
  _id: fullPath,
133
137
  type: "state",
134
138
  common: {
135
- desc:
136
- "Basetype: " +
137
- (!dptObj || tools.isEmptyObject(dptObj)
138
- ? "raw value"
139
- : dptObj.basetype.desc +
140
- (Object.prototype.hasOwnProperty.call(dptObj, "subtype") && Object.prototype.hasOwnProperty.call(dptObj, "desc") ? ", Subtype: " + dptObj.subtype.desc : "")),
139
+ desc: "Basetype: " +
140
+ (!dptObj || tools.isEmptyObject(dptObj) ?
141
+ "raw value" :
142
+ dptObj.basetype.desc +
143
+ (Object.prototype.hasOwnProperty.call(dptObj, "subtype") && Object.prototype.hasOwnProperty.call(dptObj, "desc") ? ", Subtype: " + dptObj.subtype.desc : "")),
141
144
  min: range[0],
142
145
  max: range[1],
143
146
  name: gaName,
144
147
  read: true,
145
148
  role: role, //description: https://github.com/ioBroker/ioBroker/blob/master/doc/STATE_ROLES.md#state-roles
146
- type: type, //default is mixed==any type) (possible values: number, string, boolean, array, object, mixed, file). As exception the objects with type meta could have common.type=meta.user or meta.folder. It is important to note that array, object, mixed and file must be serialized using JSON.stringify().
147
- unit: !!dptObj && Object.prototype.hasOwnProperty.call(dptObj, "subtype") && Object.prototype.hasOwnProperty.call(dptObj, "unit") ? dptObj.subtype.unit : undefined,
149
+ type: type, //possible values: number, string, boolean, array, object, mixed, file. It is important to note that array, object, mixed and file must be serialized using JSON.stringify().
150
+ unit: !!dptObj && dptObj.subtype && dptObj.subtype.unit ? dptObj.subtype.unit : undefined,
148
151
  write: true,
152
+ states: undefined, //todo can be extracted from some dpts
149
153
  },
150
154
  native: {
151
- address: adr2ga(groupAddress.getAttribute("Address")),
155
+ address: convertToGa(groupAddress.getAttribute("Address")),
152
156
  answer_groupValueResponse: false, //overwrite manually
153
157
  autoread: autoread,
154
158
  bitlength: !dptObj || tools.isEmptyObject(dptObj) ? undefined : dptObj.basetype.bitlength, //informative
155
159
  dpt: dpt,
156
160
  encoding:
157
- !!dptObj && Object.prototype.hasOwnProperty.call(dptObj, "subtype")
158
- ? Object.prototype.hasOwnProperty.call(dptObj, "enc")
159
- ? dptObj.subtype.enc
160
- : dptObj.basetype.enc
161
- : undefined, //informative ,todo matcht zu common.states`? hat basetype enc?
161
+ !!dptObj && Object.prototype.hasOwnProperty.call(dptObj, "subtype") ?
162
+ Object.prototype.hasOwnProperty.call(dptObj, "enc") ?
163
+ dptObj.subtype.enc :
164
+ dptObj.basetype.enc : undefined, //informative
162
165
  force_encoding:
163
- !!dptObj && Object.prototype.hasOwnProperty.call(dptObj, "subtype") && Object.prototype.hasOwnProperty.call(dptObj, "subtype") && dptObj.subtype
164
- ? dptObj.subtype.force_encoding
165
- : undefined, //DPT16
166
+ !!dptObj && Object.prototype.hasOwnProperty.call(dptObj, "subtype") && Object.prototype.hasOwnProperty.call(dptObj, "subtype") && dptObj.subtype ?
167
+ dptObj.subtype.force_encoding : undefined, //DPT16
166
168
  signedness:
167
- !!dptObj && !tools.isEmptyObject(dptObj)
168
- ? Object.prototype.hasOwnProperty.call(dptObj, "signedness") && dptObj.basetype
169
- ? dptObj.basetype.signedness
170
- : undefined
171
- : undefined, //signed or unsigned or empty
169
+ !!dptObj && !tools.isEmptyObject(dptObj) ?
170
+ Object.prototype.hasOwnProperty.call(dptObj, "signedness") && dptObj.basetype ?
171
+ dptObj.basetype.signedness :
172
+ undefined : undefined, //signed or unsigned or empty
172
173
  valuetype: !dptObj || tools.isEmptyObject(dptObj) ? undefined : dptObj.basetype.valuetype, //informative: composite or basic
173
174
  },
174
175
  };
175
176
 
176
177
  iobrokerObjects.push(iobrokerObject);
177
178
  });
178
- if (typeof callback === "function") callback(err, iobrokerObjects);
179
+
180
+ if (typeof callback === "function") {
181
+ callback(err, iobrokerObjects);
182
+ }
179
183
  }
180
184
 
181
185
  // check if fileObjectList is typeof Object
182
- if (!fileObjectList.hasOwnProperty("push")) {
186
+ if (!Object.prototype.hasOwnProperty.call(fileObjectList, "push")) {
183
187
  processFile(adapter, null, fileObjectList);
184
188
  }
185
189
  },
190
+
191
+ //find statusGAs based on regexp
192
+ findStatusGAs(adapter, gaList, callback) {
193
+ let count = 0;
194
+ const regex = RegExp(adapter.config.statusRegex, "gi");
195
+ for (const statusElement of gaList) {
196
+ if (regex.test(statusElement)) {
197
+ //for all status gas
198
+ const reducedStatusElement = statusElement.replace((regex), "");
199
+ const nonStatusElement = Array.from(gaList).reduce((previousValue, currentValue) => {
200
+ //compare with all others
201
+ if (!previousValue) {
202
+ return currentValue;
203
+ }
204
+ if (currentValue === statusElement) {
205
+ //found itself, skip
206
+ return previousValue;
207
+ }
208
+ if (regex.test(currentValue)) {
209
+ //found another status, skip
210
+ return previousValue;
211
+ }
212
+ //reduce the list to the best match
213
+ if (similarity(unify(currentValue,adapter), unify(reducedStatusElement,adapter)) > similarity(unify(previousValue,adapter), unify(reducedStatusElement, adapter))) {
214
+ return currentValue;
215
+ } else {
216
+ return previousValue;
217
+ }
218
+ });
219
+ const a = unify(reducedStatusElement, adapter);
220
+ const b = unify(nonStatusElement, adapter);
221
+ if (similarity(a, b) > adapter.config.regexSimil) {
222
+ //found a match that is not and status and good enough, generate alias object
223
+ createAlias(adapter, nonStatusElement, statusElement);
224
+ count++;
225
+ adapter.log.info("create aliases: found match " + nonStatusElement + " and " + statusElement);
226
+ }
227
+ }
228
+ }
229
+ callback(count);
230
+ }
186
231
  };
187
232
 
233
+ /* make strings comparable*/
234
+ function unify(element, adapter) {
235
+ element = element.toLowerCase().replace(/_/g,"");
236
+ if (!(adapter.config.aliasRange)) {
237
+ //exclude group names from search if user sets
238
+ const n = element.lastIndexOf(".");
239
+ element = element.substring(n + 1);
240
+ }
241
+ return element;
242
+ }
243
+
244
+
188
245
  //possibly convert 2byte value into ././. form
189
- function adr2ga(adr) {
246
+ function convertToGa(adr) {
190
247
  //check if already in good format
191
248
  if (adr.includes("/")) return adr;
192
249
  //Bereiche: Hauptgruppe = 0..31, Mittelgruppe = 0..7, Untergruppe = 0..255
193
250
  return util.format("%d/%d/%d", (adr >> 11) & 0x1f, (adr >> 8) & 0x7, adr & 0xff);
194
251
  }
252
+
253
+ function createAlias(adapter, idDst, idSrc) {
254
+ adapter.getObject(idDst, null, (err, obj) => {
255
+ if (obj != null) {
256
+ const dstObj = obj;
257
+ adapter.getObject(idSrc, null, (err, srcObj) => {
258
+ if (srcObj != null) {
259
+ const aliasId = adapter.config.aliasPath + dstObj._id.replace(adapter.namespace, "");
260
+ const aliasObject = {
261
+ "_id": aliasId,
262
+ "type": "state",
263
+ "common": {
264
+ "name": dstObj.common.name,
265
+ "desc": dstObj.common.desc,
266
+ "role": dstObj.common.role,
267
+ "type": dstObj.common.type,
268
+ "read": dstObj.common.read,
269
+ "write": dstObj.common.write,
270
+ "unit": dstObj.common.unit,
271
+ "min": dstObj.common.min,
272
+ "max": dstObj.common.max,
273
+ "def": "",
274
+ "alias": {
275
+ "id": {
276
+ "read": srcObj._id,
277
+ "write": dstObj._id
278
+ }
279
+ }
280
+ },
281
+ };
282
+
283
+ adapter.setForeignObject(aliasId, aliasObject, (err, obj) => {});
284
+ } else {
285
+ adapter.log.debug("createAlias: source object not found: " + idSrc);
286
+ }
287
+ });
288
+ } else {
289
+ adapter.log.debug("createAlias: destination object not found: " + idDst);
290
+ }
291
+ });
292
+ }