homematic-manager 3.0.0-beta.6 → 3.0.0-beta.8
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/dist/testSupport.js +9 -2
- package/node_modules/@homematic-manager/backend/package.json +1 -1
- package/node_modules/@homematic-manager/core/dist/devices/index.d.ts +7 -0
- package/node_modules/@homematic-manager/core/dist/devices/index.js +7 -0
- package/node_modules/@homematic-manager/core/dist/rssi/index.d.ts +72 -0
- package/node_modules/@homematic-manager/core/dist/rssi/index.js +83 -0
- package/node_modules/@homematic-manager/core/package.json +1 -1
- package/package.json +3 -3
- package/ui/assets/index-B8KS6KqA.js +14 -0
- package/ui/assets/{index-C99NI3-M.css → index-Dx9muUH_.css} +1 -1
- package/ui/index.html +2 -2
- package/ui/assets/index-Mfj6d7fY.js +0 -14
package/dist/testSupport.js
CHANGED
|
@@ -183,7 +183,14 @@ export async function startForTest(options = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
const ports = simulator
|
|
186
|
-
? {
|
|
186
|
+
? {
|
|
187
|
+
'BidCos-RF': simulator.ports.rfd,
|
|
188
|
+
'HmIP-RF': simulator.ports.hmip,
|
|
189
|
+
// only when the simulator was given a `virtualListenPort` (the CCU's group process)
|
|
190
|
+
...(typeof simulator.ports.virtual === 'number'
|
|
191
|
+
? { VirtualDevices: simulator.ports.virtual }
|
|
192
|
+
: {}),
|
|
193
|
+
}
|
|
187
194
|
: {};
|
|
188
195
|
const host = await createWebHost({
|
|
189
196
|
port: 0,
|
|
@@ -206,7 +213,7 @@ export async function startForTest(options = {}) {
|
|
|
206
213
|
if (simulator && host.backend) {
|
|
207
214
|
const connection = {
|
|
208
215
|
host: '127.0.0.1',
|
|
209
|
-
interfaces: ['BidCos-RF', 'HmIP-RF'],
|
|
216
|
+
interfaces: ['BidCos-RF', 'HmIP-RF', ...(ports['VirtualDevices'] === undefined ? [] : ['VirtualDevices'])],
|
|
210
217
|
autoDetect: false,
|
|
211
218
|
extraInterfaces: [],
|
|
212
219
|
tls: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homematic-manager/backend",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Homematic Manager backend: XML-RPC/BIN-RPC clients and callback servers, caches, optional ReGa",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -72,6 +72,13 @@ export declare const RX_MODES: {
|
|
|
72
72
|
* by itself and one that does not (task 6).
|
|
73
73
|
*/
|
|
74
74
|
export declare function decodeRxMode(rxMode: number | undefined): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Is roaming on for this device? rfd reports `ROAMING` as `1`/`0` over BIN-RPC and as a boolean
|
|
77
|
+
* over XML-RPC, so both spellings mean yes.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isRoaming(device: {
|
|
80
|
+
readonly ROAMING?: boolean | number | undefined;
|
|
81
|
+
}): boolean;
|
|
75
82
|
/** `DIRECTION` of a channel. */
|
|
76
83
|
export declare const DIRECTIONS: readonly ["NONE", "SENDER", "RECEIVER"];
|
|
77
84
|
/** Decodes `DIRECTION`; anything unexpected is `NONE`, as the 2.x grid did. */
|
|
@@ -55,6 +55,13 @@ export function decodeRxMode(rxMode) {
|
|
|
55
55
|
}
|
|
56
56
|
return names;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Is roaming on for this device? rfd reports `ROAMING` as `1`/`0` over BIN-RPC and as a boolean
|
|
60
|
+
* over XML-RPC, so both spellings mean yes.
|
|
61
|
+
*/
|
|
62
|
+
export function isRoaming(device) {
|
|
63
|
+
return device.ROAMING === true || device.ROAMING === 1;
|
|
64
|
+
}
|
|
58
65
|
/** `DIRECTION` of a channel. */
|
|
59
66
|
export const DIRECTIONS = ['NONE', 'SENDER', 'RECEIVER'];
|
|
60
67
|
/** Decodes `DIRECTION`; anything unexpected is `NONE`, as the 2.x grid did. */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* as events and sit in every device's `<device>:0` VALUES paramset - the same thing 2.x does in
|
|
8
8
|
* main.js (:404-432 for the events, :800-822 for the paramset read).
|
|
9
9
|
*/
|
|
10
|
+
import type { BidcosInterfaceInfo } from '../api/types.js';
|
|
10
11
|
import type { Paramset, ParamsetValue } from '../rpc/values.js';
|
|
11
12
|
/** What an interface process sends instead of a value it does not have. */
|
|
12
13
|
export declare const RSSI_UNKNOWN = 65536;
|
|
@@ -41,6 +42,24 @@ export declare function rssiClass(dbm: number | undefined): RssiClass;
|
|
|
41
42
|
* `undefined` for a value there is none for - the cell stays empty, as it did.
|
|
42
43
|
*/
|
|
43
44
|
export declare function rssiColor(dbm: number | undefined): string | undefined;
|
|
45
|
+
/** The two fields of a `listBidcosInterfaces` entry that name it. */
|
|
46
|
+
export type BidcosInterfaceName = Pick<BidcosInterfaceInfo, 'ADDRESS' | 'DESCRIPTION'>;
|
|
47
|
+
/**
|
|
48
|
+
* How a BidCos interface is named for a person: the `DESCRIPTION` the CCU carries for it - the
|
|
49
|
+
* name a LAN gateway was given in the WebUI, `CCU2-Coprocessor` for the built-in module - and the
|
|
50
|
+
* serial when there is none. The 2.x Funk grid put the serial over the interface's columns with
|
|
51
|
+
* the description in small print under it; a gateway without a description shows its serial alone.
|
|
52
|
+
*/
|
|
53
|
+
export declare function bidcosInterfaceLabel(gateway: BidcosInterfaceName): string;
|
|
54
|
+
/**
|
|
55
|
+
* The receiver a BidCos-RF device is routed through, for the device grid (BUGS.md B-2): the
|
|
56
|
+
* label of the gateway whose serial the description's `INTERFACE` names, the serial itself when
|
|
57
|
+
* `listBidcosInterfaces` does not know it or has not been read yet, and nothing for a device
|
|
58
|
+
* without one - HmIP and Wired have no receivers.
|
|
59
|
+
*/
|
|
60
|
+
export declare function receiverLabel(device: {
|
|
61
|
+
readonly INTERFACE?: string | undefined;
|
|
62
|
+
}, gateways: readonly BidcosInterfaceName[]): string;
|
|
44
63
|
/** The HmIP datapoints the matrix is built from. */
|
|
45
64
|
export declare const RSSI_DATAPOINTS: readonly string[];
|
|
46
65
|
export interface RssiStoreOptions {
|
|
@@ -88,4 +107,57 @@ export declare class RssiStore {
|
|
|
88
107
|
readonly tx?: number;
|
|
89
108
|
} | undefined;
|
|
90
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Why a device is, or is not, proposed for another receiver (#69).
|
|
112
|
+
*
|
|
113
|
+
* - `switch`: another interface receives the device better than the configured one by at least
|
|
114
|
+
* the margin.
|
|
115
|
+
* - `marginal`: another interface receives it better, but by less than the margin - one sample
|
|
116
|
+
* apart, which `rssiInfo` values are between two reads.
|
|
117
|
+
* - `unheard`: the configured receiver has no measurement of the device at all while another
|
|
118
|
+
* interface has one; that can mean the device never reaches its receiver, or that rfd has not
|
|
119
|
+
* heard it since a restart.
|
|
120
|
+
* - `keep`: the configured receiver hears it best, or at least as well as any other.
|
|
121
|
+
* - `unmeasured`: no interface has a measurement.
|
|
122
|
+
* - `roaming`: the device roams, so the CCU picks its receiver itself; nothing to assign.
|
|
123
|
+
*/
|
|
124
|
+
export type ReceiverVerdict = 'switch' | 'marginal' | 'unheard' | 'keep' | 'unmeasured' | 'roaming';
|
|
125
|
+
/** One row of the "assign the best receiver" proposal (#69). */
|
|
126
|
+
export interface ReceiverProposal {
|
|
127
|
+
readonly address: string;
|
|
128
|
+
/** The serial of the receiver the device is configured for. */
|
|
129
|
+
readonly configured: string;
|
|
130
|
+
/** What the configured receiver receives from the device, in dBm. */
|
|
131
|
+
readonly configuredTx: number | undefined;
|
|
132
|
+
/** The interface that receives the device best; `undefined` without a measurement. */
|
|
133
|
+
readonly best: string | undefined;
|
|
134
|
+
readonly bestTx: number | undefined;
|
|
135
|
+
/** `bestTx - configuredTx` where both are known. */
|
|
136
|
+
readonly gain: number | undefined;
|
|
137
|
+
readonly verdict: ReceiverVerdict;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The margin a better interface has to clear before a switch is proposed, in dB. The values of
|
|
141
|
+
* `rssiInfo` are the last measurement rfd holds, and two reads of the same link differ by a few dB
|
|
142
|
+
* without anything having moved; 6 dB is roughly a quartering of the received power and clears
|
|
143
|
+
* that noise.
|
|
144
|
+
*/
|
|
145
|
+
export declare const DEFAULT_RECEIVER_MARGIN_DB = 6;
|
|
146
|
+
/** The fields of a device description the proposal reads. */
|
|
147
|
+
export interface ReceiverCandidate {
|
|
148
|
+
readonly ADDRESS: string;
|
|
149
|
+
readonly PARENT?: string | undefined;
|
|
150
|
+
readonly INTERFACE?: string | undefined;
|
|
151
|
+
readonly ROAMING?: boolean | number | undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The dry run behind issue #69: for every BidCos-RF device with a receiver, which interface hears
|
|
155
|
+
* it best and whether that is worth a `setBidcosInterface`. Nothing is written here - the list
|
|
156
|
+
* is what the user confirms, device by device, before anything is. Channels and devices without
|
|
157
|
+
* an `INTERFACE` (HmIP, Wired, groups) are not in the answer at all. Sorted by verdict in the
|
|
158
|
+
* order of {@link ReceiverVerdict}, then by gain, then by address.
|
|
159
|
+
*/
|
|
160
|
+
export declare function proposeReceivers(devices: readonly ReceiverCandidate[], interfaceAddresses: readonly string[], store: Pick<RssiStore, 'get' | 'bestInterfaceFor'>, options?: {
|
|
161
|
+
readonly marginDb?: number | undefined;
|
|
162
|
+
}): ReceiverProposal[];
|
|
91
163
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -59,6 +59,30 @@ export function rssiColor(dbm) {
|
|
|
59
59
|
const green = channel((256 * (value - RSSI_BAD)) / (RSSI_MEDIUM - RSSI_BAD));
|
|
60
60
|
return `#${hex(red)}${hex(green)}00`;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* How a BidCos interface is named for a person: the `DESCRIPTION` the CCU carries for it - the
|
|
64
|
+
* name a LAN gateway was given in the WebUI, `CCU2-Coprocessor` for the built-in module - and the
|
|
65
|
+
* serial when there is none. The 2.x Funk grid put the serial over the interface's columns with
|
|
66
|
+
* the description in small print under it; a gateway without a description shows its serial alone.
|
|
67
|
+
*/
|
|
68
|
+
export function bidcosInterfaceLabel(gateway) {
|
|
69
|
+
const description = gateway.DESCRIPTION?.trim() ?? '';
|
|
70
|
+
return description === '' ? gateway.ADDRESS : description;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The receiver a BidCos-RF device is routed through, for the device grid (BUGS.md B-2): the
|
|
74
|
+
* label of the gateway whose serial the description's `INTERFACE` names, the serial itself when
|
|
75
|
+
* `listBidcosInterfaces` does not know it or has not been read yet, and nothing for a device
|
|
76
|
+
* without one - HmIP and Wired have no receivers.
|
|
77
|
+
*/
|
|
78
|
+
export function receiverLabel(device, gateways) {
|
|
79
|
+
const serial = device.INTERFACE ?? '';
|
|
80
|
+
if (serial === '') {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
const gateway = gateways.find((candidate) => candidate.ADDRESS === serial);
|
|
84
|
+
return gateway ? bidcosInterfaceLabel(gateway) : serial;
|
|
85
|
+
}
|
|
62
86
|
/** The HmIP datapoints the matrix is built from. */
|
|
63
87
|
export const RSSI_DATAPOINTS = ['RSSI_DEVICE', 'RSSI_PEER'];
|
|
64
88
|
/** The RSSI matrix of one interface. */
|
|
@@ -172,4 +196,63 @@ function channel(value) {
|
|
|
172
196
|
function hex(value) {
|
|
173
197
|
return `0${value.toString(16)}`.slice(-2);
|
|
174
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* The margin a better interface has to clear before a switch is proposed, in dB. The values of
|
|
201
|
+
* `rssiInfo` are the last measurement rfd holds, and two reads of the same link differ by a few dB
|
|
202
|
+
* without anything having moved; 6 dB is roughly a quartering of the received power and clears
|
|
203
|
+
* that noise.
|
|
204
|
+
*/
|
|
205
|
+
export const DEFAULT_RECEIVER_MARGIN_DB = 6;
|
|
206
|
+
const VERDICT_ORDER = ['switch', 'marginal', 'unheard', 'keep', 'unmeasured', 'roaming'];
|
|
207
|
+
/**
|
|
208
|
+
* The dry run behind issue #69: for every BidCos-RF device with a receiver, which interface hears
|
|
209
|
+
* it best and whether that is worth a `setBidcosInterface`. Nothing is written here - the list
|
|
210
|
+
* is what the user confirms, device by device, before anything is. Channels and devices without
|
|
211
|
+
* an `INTERFACE` (HmIP, Wired, groups) are not in the answer at all. Sorted by verdict in the
|
|
212
|
+
* order of {@link ReceiverVerdict}, then by gain, then by address.
|
|
213
|
+
*/
|
|
214
|
+
export function proposeReceivers(devices, interfaceAddresses, store, options = {}) {
|
|
215
|
+
const margin = Math.max(0, options.marginDb ?? DEFAULT_RECEIVER_MARGIN_DB);
|
|
216
|
+
const proposals = [];
|
|
217
|
+
for (const device of devices) {
|
|
218
|
+
const configured = device.INTERFACE ?? '';
|
|
219
|
+
if (configured === '' || (device.PARENT ?? '') !== '') {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const configuredTx = store.get(device.ADDRESS, configured)?.tx;
|
|
223
|
+
const best = store.bestInterfaceFor(device.ADDRESS, interfaceAddresses);
|
|
224
|
+
const gain = best?.tx !== undefined && configuredTx !== undefined ? best.tx - configuredTx : undefined;
|
|
225
|
+
let verdict;
|
|
226
|
+
if (device.ROAMING === true || device.ROAMING === 1) {
|
|
227
|
+
verdict = 'roaming';
|
|
228
|
+
}
|
|
229
|
+
else if (best === undefined) {
|
|
230
|
+
verdict = 'unmeasured';
|
|
231
|
+
}
|
|
232
|
+
else if (best.address === configured) {
|
|
233
|
+
verdict = 'keep';
|
|
234
|
+
}
|
|
235
|
+
else if (gain === undefined) {
|
|
236
|
+
verdict = 'unheard';
|
|
237
|
+
}
|
|
238
|
+
else if (gain <= 0) {
|
|
239
|
+
verdict = 'keep';
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
verdict = gain >= margin ? 'switch' : 'marginal';
|
|
243
|
+
}
|
|
244
|
+
proposals.push({
|
|
245
|
+
address: device.ADDRESS,
|
|
246
|
+
configured,
|
|
247
|
+
configuredTx,
|
|
248
|
+
best: best?.address,
|
|
249
|
+
bestTx: best?.tx,
|
|
250
|
+
gain,
|
|
251
|
+
verdict,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return proposals.sort((a, b) => VERDICT_ORDER.indexOf(a.verdict) - VERDICT_ORDER.indexOf(b.verdict) ||
|
|
255
|
+
(b.gain ?? Number.NEGATIVE_INFINITY) - (a.gain ?? Number.NEGATIVE_INFINITY) ||
|
|
256
|
+
a.address.localeCompare(b.address));
|
|
257
|
+
}
|
|
175
258
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homematic-manager",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Configure and administer Homematic and HomematicIP devices - devices and channels, direct links, paramsets, RSSI, service messages, events and an RPC console - from a local HTTP/WebSocket server that also serves the web UI",
|
|
6
6
|
"keywords": [
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"hm-simulator": "^1.0.0"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@homematic-manager/backend": "3.0.0-beta.
|
|
71
|
-
"@homematic-manager/core": "3.0.0-beta.
|
|
70
|
+
"@homematic-manager/backend": "3.0.0-beta.8",
|
|
71
|
+
"@homematic-manager/core": "3.0.0-beta.8",
|
|
72
72
|
"binrpc": "^4.2.0",
|
|
73
73
|
"homematic-rega": "^2.0.0",
|
|
74
74
|
"homematic-xmlrpc": "^2.0.0",
|