iobroker.openknx 0.0.15 → 0.0.16
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.
- package/README.md +37 -6
- package/admin/openknx.png +0 -0
- package/io-package.json +1 -1
- package/lib/generateGAS.js +29 -16
- package/lib/tools.js +28 -2
- package/main.js +140 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,12 +18,12 @@ Please do not test in critical environments
|
|
|
18
18
|
|
|
19
19
|
install in shell
|
|
20
20
|
cd /opt/iobroker/node_modules
|
|
21
|
-
npm i iobroker.openknx
|
|
21
|
+
npm i iobroker.openknx
|
|
22
22
|
iobroker add openknx
|
|
23
23
|
npm i knx
|
|
24
24
|
|
|
25
25
|
updates:
|
|
26
|
-
npm i iobroker.openknx
|
|
26
|
+
npm i iobroker.openknx
|
|
27
27
|
iobroker upload openknx
|
|
28
28
|
|
|
29
29
|
# GA import
|
|
@@ -68,7 +68,7 @@ Leave setting empty to use the adapters own namespace.
|
|
|
68
68
|
},
|
|
69
69
|
"force_encoding": "",
|
|
70
70
|
"signedness": "",
|
|
71
|
-
"valuetype": "basic"
|
|
71
|
+
"valuetype": "basic" //composite means set via a specific object
|
|
72
72
|
},
|
|
73
73
|
"from": "system.adapter.openknx.0",
|
|
74
74
|
"user": "system.user.admin",
|
|
@@ -79,12 +79,40 @@ Leave setting empty to use the adapters own namespace.
|
|
|
79
79
|
# Adapter communication Interface Description
|
|
80
80
|
handeled DPTs 1-21,232,237,238
|
|
81
81
|
|
|
82
|
-
Unhandeled DPTs are written as raw buffers, the iterface is a hexadecimal number.
|
|
82
|
+
Unhandeled DPTs are written as raw buffers, the iterface is a sequencial string of hexadecimal number. For example write '0102feff' to send values 0x01 0x02 0xfe 0xff on the bus.
|
|
83
83
|
Where number, see scaling.
|
|
84
84
|
|
|
85
85
|
Description of handeled DPTs
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
javascript datatype special values range
|
|
88
|
+
DPT-1 boolean false, true
|
|
89
|
+
DPT-2 object {"priority":1 bit,"data":1 bit} -
|
|
90
|
+
DPT-3 object {"decr_incr":1 bit,"data":2 bit} -
|
|
91
|
+
DPT-18 object {"save_recall":0,"scenenumber":0}
|
|
92
|
+
DPT-21 object {"outofservice":0,"fault":0,"overridden":0,"inalarm":0,"alarmunack":0} -
|
|
93
|
+
DPT-232 object {red:0..255, green:0.255, blue:0.255} -
|
|
94
|
+
DPT-237 object {"address":0,"addresstype":0,"readresponse":0,"lampfailure":0,"ballastfailure":0,"convertorerror":0} -
|
|
95
|
+
DPT-4 string one character sent as 8-bit character
|
|
96
|
+
DPT-16 string one character sent as 16-character string
|
|
97
|
+
DPT-5 number 8-bit unsigned value
|
|
98
|
+
DPT-5.001 number 0..100 [%] scaled to 1-byte
|
|
99
|
+
DPT-5.003 number 0..360 [°] scaled to 1-byte
|
|
100
|
+
DPT-6 number 8-bit signed -128..127
|
|
101
|
+
DPT-7 number 16-bit unsigned value
|
|
102
|
+
DPT-8 number 2-byte signed value -32768..32767
|
|
103
|
+
DPT-9 number 2-byte floating point value
|
|
104
|
+
DPT-14 number 4-byte floating point value
|
|
105
|
+
DPT-12 number 4-byte unsigned value
|
|
106
|
+
DPT-13 number 4-byte signed value
|
|
107
|
+
DPT-15 number 4-byte
|
|
108
|
+
DPT-17 number 1-byte
|
|
109
|
+
DPT-20 number 1-byte
|
|
110
|
+
DPT-238 number 1-byte
|
|
111
|
+
DPT-10 number for Date Object -
|
|
112
|
+
DPT-11 number for Date Object -
|
|
113
|
+
DPT-19 number for Date Object -
|
|
114
|
+
rest object {0 .. } -
|
|
115
|
+
|
|
88
116
|
|
|
89
117
|
Only time and date information is exchanged with KNX time based datatypes, e.g. DPT-19 has unsupported fields for signal quality
|
|
90
118
|
|
|
@@ -130,6 +158,7 @@ Select XML Output Format
|
|
|
130
158
|
After the successful import a message shows how much objects where recognized.
|
|
131
159
|
Press "save & close" or "save" to restart the adapter and take over the changes.
|
|
132
160
|
When starting, the adapter tries to read all GroupAdresses with have the autoread flag (default setting). This could take a while and can produce a higher load on your KNX-bus. This ensures that the adapter operates with up-to-date values from the start.
|
|
161
|
+
Autoread is done on the first connection with the knx bus after an adapter start or restart, not on every knx reconnection.
|
|
133
162
|
|
|
134
163
|
## Objects
|
|
135
164
|
In Objects the group adress tree like in your ETS project.
|
|
@@ -155,8 +184,10 @@ Wide DPT (datapoint type) support (DPT1 - DPT21, DPT232, DPT237, DPT238 supporte
|
|
|
155
184
|
- only three level group address are supported
|
|
156
185
|
|
|
157
186
|
## Changelog
|
|
187
|
+
### 0.0.16
|
|
188
|
+
* raw value handling
|
|
189
|
+
|
|
158
190
|
### 0.0.14
|
|
159
|
-
* initial raw value handling added
|
|
160
191
|
* import ga xml
|
|
161
192
|
|
|
162
193
|
### 0.0.12
|
package/admin/openknx.png
CHANGED
|
Binary file
|
package/io-package.json
CHANGED
package/lib/generateGAS.js
CHANGED
|
@@ -130,17 +130,17 @@ module.exports = {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// is there a scalar range? eg. DPT5.003 angle degrees (0=0, ff=360)
|
|
133
|
-
if (dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('scalar_range')) {
|
|
133
|
+
if (!!(dptObj) && dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('scalar_range')) {
|
|
134
134
|
range = dptObj.subtype.scalar_range;
|
|
135
|
-
} else if (dptObj.hasOwnProperty('scalar_range')) {
|
|
135
|
+
} else if (!!(dptObj) && dptObj.hasOwnProperty('scalar_range')) {
|
|
136
136
|
range = dptObj.scalar_range;
|
|
137
|
-
} else if (dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('range')) {
|
|
137
|
+
} else if (!!(dptObj) && dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('range')) {
|
|
138
138
|
// just a plain numeric value, only check if within bounds
|
|
139
139
|
range = dptObj.subtype.range;
|
|
140
|
-
} else if (
|
|
140
|
+
} else if (!!(dptObj) && dptObj.basetype.hasOwnProperty('range')) {
|
|
141
141
|
// just a plain numeric value, only check if within bounds
|
|
142
142
|
range = dptObj.basetype.range;
|
|
143
|
-
} else if (!tools.isEmptyObject(dptObj)) {
|
|
143
|
+
} else if (!!(dptObj) && !tools.isEmptyObject(dptObj)) {
|
|
144
144
|
//extracted from basetype
|
|
145
145
|
range = [0, Math.pow(2, dptObj.basetype.bitlength) - 1];
|
|
146
146
|
} else {
|
|
@@ -148,21 +148,28 @@ module.exports = {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (tools.isDateDPT(dpt)) {
|
|
151
|
+
// we convert knx date types in javascript Date with different size
|
|
152
|
+
range[0] = undefined;
|
|
153
|
+
range[1] = undefined;
|
|
151
154
|
type = 'number';
|
|
152
|
-
} else if (!tools.isEmptyObject(dptObj) && dptObj.basetype.valuetype == 'composite') {
|
|
155
|
+
} else if (!!(dptObj) && !tools.isEmptyObject(dptObj) && dptObj.basetype.valuetype == 'composite') {
|
|
153
156
|
range[0] = undefined;
|
|
154
157
|
range[1] = undefined;
|
|
155
158
|
type = 'object';
|
|
156
|
-
} else if (!tools.isEmptyObject(dptObj) && dptObj.basetype.bitlength == 1) {
|
|
159
|
+
} else if (!!(dptObj) && !tools.isEmptyObject(dptObj) && dptObj.basetype.bitlength == 1) {
|
|
157
160
|
type = 'boolean';
|
|
158
161
|
} else if (tools.isStringDPT(dpt)) {
|
|
159
162
|
range[0] = undefined;
|
|
160
163
|
range[1] = undefined;
|
|
161
164
|
type = 'string';
|
|
162
|
-
} else if (tools.
|
|
165
|
+
} else if (tools.isFloatDPT(dpt)) {
|
|
163
166
|
range[0] = undefined;
|
|
164
167
|
range[1] = undefined;
|
|
165
168
|
type = 'number';
|
|
169
|
+
} else if (tools.isUnknownDPT(dpt)) {
|
|
170
|
+
range[0] = undefined;
|
|
171
|
+
range[1] = undefined;
|
|
172
|
+
type = 'string'; //raw
|
|
166
173
|
} else {
|
|
167
174
|
type = 'number';
|
|
168
175
|
}
|
|
@@ -171,26 +178,32 @@ module.exports = {
|
|
|
171
178
|
_id: fullPath,
|
|
172
179
|
type: 'state',
|
|
173
180
|
common: {
|
|
174
|
-
desc: 'Basetype: ' +
|
|
175
|
-
((dptObj
|
|
181
|
+
desc: 'Basetype: ' +
|
|
182
|
+
(!(dptObj) || tools.isEmptyObject(dptObj) ? 'raw value' :
|
|
183
|
+
(
|
|
184
|
+
dptObj.basetype.desc +
|
|
185
|
+
((dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('desc')) ? (', Subtype: ' + dptObj.subtype.desc) : '')
|
|
186
|
+
)
|
|
187
|
+
),
|
|
176
188
|
min: range[0],
|
|
177
189
|
max: range[1],
|
|
178
190
|
name: gaName,
|
|
179
191
|
read: true,
|
|
180
192
|
role: '', // todo add default role
|
|
181
193
|
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().
|
|
182
|
-
unit: (dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('unit')) ? dptObj.subtype.unit :
|
|
194
|
+
unit: (!!(dptObj) && dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('unit')) ? dptObj.subtype.unit : undefined,
|
|
183
195
|
write: true,
|
|
184
196
|
},
|
|
185
197
|
native: {
|
|
186
198
|
address: adr2ga(groupAddress.getAttribute('Address')),
|
|
199
|
+
answer_groupValueResponse: false, //overwrite manually
|
|
187
200
|
autoread: true,
|
|
188
|
-
bitlength: tools.isEmptyObject(dptObj) ?
|
|
201
|
+
bitlength: !(dptObj) || tools.isEmptyObject(dptObj) ? undefined : dptObj.basetype.bitlength, //informative
|
|
189
202
|
dpt: dpt,
|
|
190
|
-
encoding: dptObj.hasOwnProperty('subtype') ? (dptObj.subtype.hasOwnProperty('enc') ? dptObj.subtype.enc : dptObj.basetype.enc) :
|
|
191
|
-
force_encoding: (dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('force_encoding')) ? dptObj.subtype.force_encoding :
|
|
192
|
-
signedness: !tools.isEmptyObject(dptObj) ? (dptObj.basetype.hasOwnProperty('signedness') ? dptObj.basetype.signedness :
|
|
193
|
-
valuetype: tools.isEmptyObject(dptObj) ?
|
|
203
|
+
encoding: !!(dptObj) && dptObj.hasOwnProperty('subtype') ? (dptObj.subtype.hasOwnProperty('enc') ? dptObj.subtype.enc : dptObj.basetype.enc) : undefined, //informative ,todo matcht zu common.states`? hat basetype enc?
|
|
204
|
+
force_encoding: (!!(dptObj) && dptObj.hasOwnProperty('subtype') && dptObj.subtype.hasOwnProperty('force_encoding')) ? dptObj.subtype.force_encoding : undefined, //DPT16
|
|
205
|
+
signedness: !!(dptObj) && !tools.isEmptyObject(dptObj) ? (dptObj.basetype.hasOwnProperty('signedness') ? dptObj.basetype.signedness : undefined) : undefined, //signed or unsigned or empty
|
|
206
|
+
valuetype: !(dptObj) || tools.isEmptyObject(dptObj) ? undefined : dptObj.basetype.valuetype, //informative: composite or basic
|
|
194
207
|
}
|
|
195
208
|
};
|
|
196
209
|
|
package/lib/tools.js
CHANGED
|
@@ -119,6 +119,21 @@ function convertDPTtype(dpt) {
|
|
|
119
119
|
return dpt;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/* for testing, forward msg from one to another test address
|
|
123
|
+
better approach: send all test values via ets, send received value back from iobroker, compare in ets
|
|
124
|
+
*/
|
|
125
|
+
function interfaceTest(id, state) {
|
|
126
|
+
const inpath = this.mynamespace + '.test.testin';
|
|
127
|
+
const outpath = this.mynamespace + '.test.testout';
|
|
128
|
+
if (id.startsWith(inpath)) {
|
|
129
|
+
var out = outpath + id.replace(inpath, '');
|
|
130
|
+
this.setForeignState(out, {
|
|
131
|
+
val: state.val,
|
|
132
|
+
ack: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
function isEmptyObject(obj) {
|
|
123
138
|
return (obj
|
|
124
139
|
&& Object.keys(obj).length === 0
|
|
@@ -138,7 +153,15 @@ function isStringDPT(dpt) {
|
|
|
138
153
|
}
|
|
139
154
|
|
|
140
155
|
function isDateDPT(dpt) {
|
|
141
|
-
return (dpt == 'DPT19' || dpt
|
|
156
|
+
return (dpt == 'DPT19' || dpt.startsWith('DPT19.')
|
|
157
|
+
|| dpt == 'DPT10' || dpt.startsWith('DPT10.')
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isFloatDPT(dpt) {
|
|
162
|
+
return (dpt == 'DPT9' || dpt.startsWith('DPT9.')
|
|
163
|
+
|| dpt == 'DPT14' || dpt.startsWith('DPT14.')
|
|
164
|
+
);
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
function isUnknownDPT(dpt) {
|
|
@@ -147,7 +170,7 @@ function isUnknownDPT(dpt) {
|
|
|
147
170
|
} catch (e) {
|
|
148
171
|
return true;
|
|
149
172
|
}
|
|
150
|
-
return
|
|
173
|
+
return dptObj == null;
|
|
151
174
|
}
|
|
152
175
|
|
|
153
176
|
module.exports = {
|
|
@@ -159,5 +182,8 @@ module.exports = {
|
|
|
159
182
|
isStringDPT,
|
|
160
183
|
isDateDPT,
|
|
161
184
|
isUnknownDPT,
|
|
185
|
+
isFloatDPT,
|
|
162
186
|
isEmptyObject,
|
|
187
|
+
interfaceTest,
|
|
188
|
+
|
|
163
189
|
};
|
package/main.js
CHANGED
|
@@ -19,10 +19,8 @@ const tools = require('./lib/tools.js');
|
|
|
19
19
|
|
|
20
20
|
class openknx extends utils.Adapter {
|
|
21
21
|
|
|
22
|
-
mapping = {}; //todo join with states
|
|
23
|
-
states = {};
|
|
24
|
-
knxDatapoints = {}; //key array of knx.Datapoint known from mapping todo join
|
|
25
22
|
gaList = new DoubleKeyedMap();
|
|
23
|
+
autoreaddone = false;
|
|
26
24
|
|
|
27
25
|
knxConnection;
|
|
28
26
|
mynamespace;
|
|
@@ -44,7 +42,6 @@ class openknx extends utils.Adapter {
|
|
|
44
42
|
this.mynamespace = this.namespace;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
|
|
48
45
|
/**
|
|
49
46
|
* Is called when databases are connected and adapter received configuration.
|
|
50
47
|
*/
|
|
@@ -83,72 +80,6 @@ class openknx extends utils.Adapter {
|
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
/**
|
|
87
|
-
* Is called if a subscribed state changes
|
|
88
|
-
* state.ack is coming in false if set by user (nodered, script...), here we set it.
|
|
89
|
-
* https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/adapterdev.md
|
|
90
|
-
* @param {string} id
|
|
91
|
-
* @param {ioBroker.State | null | undefined} state
|
|
92
|
-
*/
|
|
93
|
-
onStateChange(id, state) {
|
|
94
|
-
|
|
95
|
-
var isRaw = false;
|
|
96
|
-
|
|
97
|
-
if (!id) return;
|
|
98
|
-
if (!state /*obj deleted*/ || typeof state !== 'object') return;
|
|
99
|
-
//not a KNX object
|
|
100
|
-
if (!this.states[id] || !this.states[id].native || this.states[id].native.address === undefined) return;
|
|
101
|
-
if (!this.knxConnection) { //!knxConnection.connected != 'connected' todo check connection knxConncetion.initialState == initialized??
|
|
102
|
-
this.log.warn('stateChange: not ready');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (state.c == 'self') {
|
|
107
|
-
//called by self, avoid loop
|
|
108
|
-
//console.log('state change self id: ' + id);
|
|
109
|
-
//this.interfaceTest(id, state);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
var dpt = this.states[id].native.dpt;
|
|
114
|
-
var ga = this.states[id].native.address;
|
|
115
|
-
var val = state.val;
|
|
116
|
-
|
|
117
|
-
//convert val into object for certain dpts
|
|
118
|
-
if (tools.isDateDPT(dpt)) {
|
|
119
|
-
val = new Date(val);
|
|
120
|
-
} else if (!tools.isStringDPT(dpt)) {
|
|
121
|
-
val = this.convertType(val);
|
|
122
|
-
} else if (tools.isUnknownDPT(dpt)) {
|
|
123
|
-
//write raw buffers for unknown dpts, iterface is a hex value
|
|
124
|
-
//bitlength is the buffers bytelength * 8.
|
|
125
|
-
val = Buffer.from(val, 'hex');
|
|
126
|
-
isRaw = true;
|
|
127
|
-
} else {
|
|
128
|
-
this.log.warn('Missing implementation for unhandeled DPT, assuming number');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (state.c == 'GroupValue_Read') {
|
|
132
|
-
//interface to trigger GrouValue_Read is this comment
|
|
133
|
-
this.log.debug('Outgoing GroupValue_Read to ' + ga + ' value ' + val);
|
|
134
|
-
this.knxConnection.read(ga);
|
|
135
|
-
if (!state.ack) this.setForeignState(id, {
|
|
136
|
-
ack: true,
|
|
137
|
-
c: 'self'
|
|
138
|
-
});
|
|
139
|
-
} else if (this.states[id].common.write) {
|
|
140
|
-
this.log.debug('Outgoing GroupValue_Write to ' + ga + ' value ' + val + ' from ' + id);
|
|
141
|
-
if (isRaw) ; //connection.writeRaw('1/0/0', val); //todo TEST
|
|
142
|
-
else this.knxConnection.write(ga, val, dpt);
|
|
143
|
-
if (!state.ack) this.setForeignState(id, {
|
|
144
|
-
ack: true,
|
|
145
|
-
c: 'self'
|
|
146
|
-
});
|
|
147
|
-
} else {
|
|
148
|
-
this.log.warn('not configured write to ga: ' + val);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
83
|
// New message arrived. obj is array with current messages
|
|
153
84
|
// triggered from admin page read in knx project
|
|
154
85
|
onMessage(obj) {
|
|
@@ -217,7 +148,7 @@ class openknx extends utils.Adapter {
|
|
|
217
148
|
* IOBroker Object tree cannot store 2 objects of same name, warn
|
|
218
149
|
*/
|
|
219
150
|
warnDuplicates(objects) {
|
|
220
|
-
//todo remove unallowed
|
|
151
|
+
//todo remove unallowed character, lib function
|
|
221
152
|
let arr = [];
|
|
222
153
|
let duplicates = [];
|
|
223
154
|
for (const object of objects) {
|
|
@@ -242,14 +173,13 @@ class openknx extends utils.Adapter {
|
|
|
242
173
|
if (val instanceof Date) {
|
|
243
174
|
//convert Date to number
|
|
244
175
|
ret = Number(new Date(val));
|
|
176
|
+
} else if (Buffer.isBuffer(val)) {
|
|
177
|
+
//before object check
|
|
178
|
+
ret = val.toString('hex');
|
|
245
179
|
} else if (typeof val === 'object') {
|
|
246
180
|
ret = JSON.stringify(val);
|
|
247
181
|
} else if (typeof val === 'string') {
|
|
248
|
-
|
|
249
|
-
ret = JSON.parse(val); //string to object, the string types have to be filtered out before call
|
|
250
|
-
} catch (e) {
|
|
251
|
-
this.log.warn('write Object error, wrong format for dpt type ' + val);
|
|
252
|
-
}
|
|
182
|
+
//use as is
|
|
253
183
|
} else {
|
|
254
184
|
//both can handle number and boolean
|
|
255
185
|
ret = val;
|
|
@@ -257,47 +187,126 @@ class openknx extends utils.Adapter {
|
|
|
257
187
|
return ret;
|
|
258
188
|
}
|
|
259
189
|
|
|
260
|
-
|
|
261
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Is called if a subscribed state changes
|
|
192
|
+
* state.ack is coming in false if set by user (nodered, script...), here we set it.
|
|
193
|
+
* https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/adapterdev.md
|
|
194
|
+
* @param {string} id
|
|
195
|
+
* @param {ioBroker.State | null | undefined} state
|
|
196
|
+
*/
|
|
197
|
+
onStateChange(id, state) {
|
|
198
|
+
var isRaw = false;
|
|
199
|
+
|
|
200
|
+
if (!id) return;
|
|
201
|
+
if (!state /*obj deleted*/ || typeof state !== 'object') return;
|
|
202
|
+
//not a KNX object
|
|
203
|
+
if (!this.gaList.getDataById(id) || !this.gaList.getDataById(id).native || !this.gaList.getDataById(id).native.address) return;
|
|
204
|
+
if (!this.knxConnection) { //!knxConnection.connected != 'connected' todo check connection knxConncetion.initialState == initialized??
|
|
205
|
+
this.log.warn('stateChange: not ready');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (state.c == 'self') {
|
|
210
|
+
//called by self, avoid loop
|
|
211
|
+
//console.log('state change self id: ' + id);
|
|
212
|
+
//tools.interfaceTest(id, state);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var dpt = this.gaList.getDataById(id).native.dpt;
|
|
217
|
+
var ga = this.gaList.getDataById(id).native.address;
|
|
218
|
+
var val = state.val;
|
|
262
219
|
|
|
263
|
-
|
|
264
|
-
|
|
220
|
+
//convert val into object for certain dpts
|
|
221
|
+
if (tools.isDateDPT(dpt)) {
|
|
222
|
+
//before composite check, date is also composite
|
|
223
|
+
val = new Date(val);
|
|
224
|
+
} else if (this.gaList.getDataById(id).native.valuetype == 'composite') {
|
|
225
|
+
try {
|
|
226
|
+
val = JSON.parse(val);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
this.log.warn('stateChange: unsupported value format ' + val + ' for ' + ga);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
} else if (tools.isStringDPT(dpt)) {
|
|
232
|
+
; //val = this.convertType(val);
|
|
233
|
+
} else if (tools.isUnknownDPT(dpt)) {
|
|
234
|
+
//write raw buffers for unknown dpts, iterface is a hex value
|
|
235
|
+
//bitlength is the buffers bytelength * 8.
|
|
236
|
+
val = Buffer.from(val, 'hex');
|
|
237
|
+
isRaw = true;
|
|
238
|
+
this.log.warn('Missing implementation for unhandeled DPT ' + dpt + ', assuming raw values');
|
|
239
|
+
} else {}
|
|
240
|
+
|
|
241
|
+
if (state.c == 'GroupValue_Read') {
|
|
242
|
+
//interface to trigger GrouValue_Read is this comment
|
|
243
|
+
this.log.debug('Outgoing GroupValue_Read to ' + ga + ' value ' + val);
|
|
244
|
+
this.knxConnection.read(ga);
|
|
245
|
+
if (!state.ack) this.setForeignState(id, {
|
|
246
|
+
ack: true,
|
|
247
|
+
c: 'self'
|
|
248
|
+
});
|
|
249
|
+
} else if (this.gaList.getDataById(id).common.write) {
|
|
250
|
+
this.log.debug('Outgoing GroupValue_Write to ' + ga + ' value ' + val + ' from ' + id);
|
|
251
|
+
if (isRaw) this.knxConnection.writeRaw(ga, val);
|
|
252
|
+
else this.knxConnection.write(ga, val, dpt);
|
|
253
|
+
if (!state.ack) this.setForeignState(id, {
|
|
254
|
+
ack: true,
|
|
255
|
+
c: 'self'
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
this.log.warn('not configured write to ga: ' + val);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
265
261
|
|
|
262
|
+
//interface to knx stack
|
|
263
|
+
startKnxServer() {
|
|
266
264
|
this.knxConnection = knx.Connection({
|
|
267
265
|
ipAddr: this.config.gwip,
|
|
268
266
|
ipPort: this.config.gwipport,
|
|
269
267
|
physAddr: this.config.eibadr,
|
|
270
268
|
minimumDelay: this.config.frameInterval,
|
|
271
269
|
//map set the log level for messsages printed on the console. This can be 'error', 'warn', 'info' (default), 'debug', or 'trace'.
|
|
272
|
-
|
|
270
|
+
loglevel: this.log.level == 'silly' ? 'trace' : this.log.level,
|
|
273
271
|
//debug:
|
|
274
272
|
handlers: {
|
|
275
273
|
connected: () => {
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
//create new knx datapoint and bind to connection
|
|
275
|
+
//in connected in order to have autoread work
|
|
276
|
+
var cnt_complete = 0;
|
|
277
|
+
var cnt_withDPT = 0;
|
|
278
|
+
if (!this.autoreaddone) {
|
|
279
|
+
//do autoread on start of adapter and not every connection
|
|
278
280
|
for (const key of this.gaList) {
|
|
279
|
-
if (this.gaList.
|
|
281
|
+
if (this.gaList.getDataById(key).native.address.match(/\d*\/\d*\/\d*/) && this.gaList.getDataById(key).native.dpt) {
|
|
280
282
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
autoread: this.gaList.getById(key).native.autoread // issue a GroupValue_Read request to try to get the initial state from the bus (if any)
|
|
283
|
+
var dp = new knx.Datapoint({
|
|
284
|
+
ga: this.gaList.getDataById(key).native.address,
|
|
285
|
+
dpt: this.gaList.getDataById(key).native.dpt,
|
|
286
|
+
autoread: this.gaList.getDataById(key).native.autoread // issue a GroupValue_Read request to try to get the initial state from the bus (if any)
|
|
286
287
|
}, this.knxConnection);
|
|
288
|
+
this.gaList.setDpById(key, dp);
|
|
287
289
|
cnt_withDPT++;
|
|
288
|
-
this.log.debug('Datapoint
|
|
290
|
+
this.log.debug('Datapoint ' + (this.gaList.getDataById(key).native.autoread ? 'autoread ' : '') +
|
|
291
|
+
'created and GroupValueWrite sent: ' + this.gaList.getDataById(key).native.address + ' ' + key);
|
|
289
292
|
} catch (e) {
|
|
290
293
|
this.log.warn('could not create KNX Datapoint for ' + key + ' with error: ' + e);
|
|
291
294
|
}
|
|
292
295
|
} else {
|
|
293
|
-
this.log.
|
|
296
|
+
this.log.warn('no match for ' + key);
|
|
294
297
|
}
|
|
295
298
|
cnt_complete++;
|
|
296
299
|
}
|
|
300
|
+
this.autoreaddone = true;
|
|
301
|
+
this.log.info('Registered with ' + cnt_withDPT + ' KNX datapoints of ' + cnt_complete + ' datapoints in adapter.');
|
|
297
302
|
}
|
|
298
|
-
|
|
299
303
|
this.setState('info.connection', true, true);
|
|
300
|
-
this.log.info('Connected!
|
|
304
|
+
this.log.info('Connected!');
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
disconnected: () => {
|
|
308
|
+
this.setState('info.connection', false, true);
|
|
309
|
+
this.log.info('Connection lost');
|
|
301
310
|
},
|
|
302
311
|
|
|
303
312
|
event: (evt, src, dest, val) => {
|
|
@@ -308,43 +317,45 @@ class openknx extends utils.Adapter {
|
|
|
308
317
|
}
|
|
309
318
|
|
|
310
319
|
/* some checks */
|
|
311
|
-
if (!this.
|
|
320
|
+
if (!this.gaList.getDpByAddress(dest)) {
|
|
312
321
|
this.log.warn('Ignoring ' + evt + ' received on unknown GA: ' + dest);
|
|
313
322
|
return;
|
|
314
323
|
}
|
|
315
324
|
|
|
316
|
-
var val = tools.isStringDPT(this.
|
|
325
|
+
var val = tools.isStringDPT(this.gaList.getDataByAddress(dest).native.dpt) ?
|
|
326
|
+
this.gaList.getDpByAddress(dest).current_value :
|
|
327
|
+
this.convertType(this.gaList.getDpByAddress(dest).current_value);
|
|
317
328
|
|
|
318
329
|
switch (evt) {
|
|
319
330
|
case 'GroupValue_Read':
|
|
320
331
|
//fetch val from addressed object and write on bus if configured to answer
|
|
321
|
-
this.getForeignState(this.
|
|
322
|
-
this.log.debug('Incoming GroupValue_Read from ' + src + ' to ' + '(' + dest + ') ' + this.
|
|
323
|
-
if (this.
|
|
332
|
+
this.getForeignState(this.gaList.getIdByAddress(dest), (err, state) => {
|
|
333
|
+
this.log.debug('Incoming GroupValue_Read from ' + src + ' to ' + '(' + dest + ') ' + this.gaList.getDataByAddress(dest).common.name);
|
|
334
|
+
if (this.gaList.getDataByAddress(dest).native.answer_groupValueResponse) {
|
|
324
335
|
//https://bitbucket.org/ekarak/knx.js/issues/83/send-groupvalue_response
|
|
325
336
|
//workaround, send out a write instead response
|
|
326
|
-
this.knxConnection.write(dest, state.val, this.
|
|
337
|
+
this.knxConnection.write(dest, state.val, this.gaList.getDataByAddress(dest).native.dpt);
|
|
327
338
|
this.log.debug('responding with value ' + state.val);
|
|
328
339
|
}
|
|
329
340
|
});
|
|
330
341
|
break;
|
|
331
342
|
|
|
332
343
|
case 'GroupValue_Response':
|
|
333
|
-
this.setForeignState(
|
|
344
|
+
this.setForeignState(this.gaList.getIdByAddress(dest), {
|
|
334
345
|
val: val,
|
|
335
346
|
ack: true,
|
|
336
347
|
c: 'self'
|
|
337
348
|
});
|
|
338
|
-
this.log.debug('Incoming GroupValue_Response from ' + src + ' to ' + '(' + dest + ') ' + this.
|
|
349
|
+
this.log.debug('Incoming GroupValue_Response from ' + src + ' to ' + '(' + dest + ') ' + this.gaList.getDataByAddress(dest).common.name + ': ' + val);
|
|
339
350
|
break;
|
|
340
351
|
|
|
341
352
|
case 'GroupValue_Write':
|
|
342
|
-
this.setForeignState(this.
|
|
353
|
+
this.setForeignState(this.gaList.getIdByAddress(dest), {
|
|
343
354
|
val: val,
|
|
344
355
|
ack: true,
|
|
345
356
|
c: 'self'
|
|
346
357
|
});
|
|
347
|
-
this.log.debug('Incoming GroupValue_Write ga: ' + dest + ' val: ' +
|
|
358
|
+
this.log.debug('Incoming GroupValue_Write ga: ' + dest + ' val: ' + val + ' dpt: ' + this.gaList.getDataByAddress(dest).native.dpt + ' to Object: ' + this.gaList.getIdByAddress(dest));
|
|
348
359
|
break;
|
|
349
360
|
|
|
350
361
|
default:
|
|
@@ -355,24 +366,13 @@ class openknx extends utils.Adapter {
|
|
|
355
366
|
});
|
|
356
367
|
}
|
|
357
368
|
|
|
358
|
-
/* for testing, forward msg from one to another test address*/
|
|
359
|
-
interfaceTest(id, state) {
|
|
360
|
-
const inpath = this.mynamespace + '.test.testin';
|
|
361
|
-
const outpath = this.mynamespace + '.test.testout';
|
|
362
|
-
if (id.startsWith(inpath)) {
|
|
363
|
-
var out = outpath + id.replace(inpath, '');
|
|
364
|
-
this.setForeignState(out, {
|
|
365
|
-
val: state.val,
|
|
366
|
-
ack: true,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
369
|
//todo: after override object path settings change load states again
|
|
372
370
|
main() {
|
|
373
371
|
this.log.info('Connecting to knx gateway: ' + this.config.gwip + ":" + this.config.gwipport + ' with phy. Adr: ' + this.config.eibadr + ' minimum send delay: ' + this.config.frameInterval);
|
|
374
372
|
this.log.info(utils.controllerDir);
|
|
375
373
|
this.setState('info.connection', false, true);
|
|
374
|
+
|
|
375
|
+
//fill gaList object from iobroker objects
|
|
376
376
|
this.getObjectView('system', 'state', {
|
|
377
377
|
startkey: this.mynamespace + '.',
|
|
378
378
|
endkey: this.mynamespace + '.\u9999',
|
|
@@ -381,42 +381,58 @@ class openknx extends utils.Adapter {
|
|
|
381
381
|
if (err) {
|
|
382
382
|
this.log.error('Cannot get objects: ' + err);
|
|
383
383
|
} else {
|
|
384
|
-
this.states = {};
|
|
385
384
|
for (var i = res.rows.length - 1; i >= 0; i--) {
|
|
386
385
|
var id = res.rows[i].id;
|
|
387
|
-
this.states[id] = res.rows[i].value; //list by object name todo remove
|
|
388
|
-
this.mapping[res.rows[i].value.native.address] = res.rows[i].value; //list by ga todo remove
|
|
389
386
|
this.gaList.set(id, res.rows[i].value.native.address, res.rows[i].value);
|
|
390
387
|
}
|
|
391
388
|
this.startKnxServer();
|
|
392
389
|
}
|
|
393
390
|
});
|
|
391
|
+
|
|
394
392
|
}
|
|
395
393
|
}
|
|
396
394
|
|
|
397
|
-
|
|
398
395
|
class DoubleKeyedMap {
|
|
399
396
|
constructor() {
|
|
397
|
+
//id, ga
|
|
400
398
|
this.keymap = new Map();
|
|
399
|
+
//id, iobroker object
|
|
401
400
|
this.data = new Map();
|
|
401
|
+
//id, knx dp
|
|
402
|
+
this.dp = new Map();
|
|
402
403
|
}
|
|
404
|
+
//update or add
|
|
403
405
|
set(id, address, data) {
|
|
404
|
-
this.keymap.set(
|
|
406
|
+
this.keymap.set(address, id);
|
|
405
407
|
this.data.set(id, data);
|
|
406
408
|
}
|
|
407
|
-
|
|
409
|
+
//only dp returns transformed value, hold a reference to it
|
|
410
|
+
setDpById(id, dp) {
|
|
411
|
+
this.dp.set(id, dp);
|
|
412
|
+
}
|
|
413
|
+
getDpById(id) {
|
|
414
|
+
return this.dp.get(id);
|
|
415
|
+
}
|
|
416
|
+
getDpByAddress(address) {
|
|
417
|
+
return this.dp.get(this.keymap.get(address));
|
|
418
|
+
}
|
|
419
|
+
getDataById(id) {
|
|
408
420
|
return this.data.get(id);
|
|
409
421
|
}
|
|
410
|
-
|
|
422
|
+
getDataByAddress(address) {
|
|
411
423
|
return this.data.get(this.keymap.get(address));
|
|
412
424
|
}
|
|
425
|
+
getIdByAddress(address) {
|
|
426
|
+
return this.keymap.get(address);
|
|
427
|
+
}
|
|
413
428
|
|
|
429
|
+
//key value is id
|
|
414
430
|
[Symbol.iterator] = function () {
|
|
415
431
|
return {
|
|
416
432
|
index: -1,
|
|
417
433
|
data: this.data,
|
|
418
434
|
next() {
|
|
419
|
-
return (this.index
|
|
435
|
+
return (++this.index < this.data.size) ? {
|
|
420
436
|
done: false,
|
|
421
437
|
value: Array.from(this.data.keys())[this.index]
|
|
422
438
|
} : {
|