homematic-manager 3.0.0-beta.7 → 3.0.0-beta.9
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/node_modules/@homematic-manager/backend/dist/api/backend.js +25 -0
- package/node_modules/@homematic-manager/backend/dist/interfaces/manager.d.ts +11 -1
- package/node_modules/@homematic-manager/backend/dist/interfaces/manager.js +15 -2
- package/node_modules/@homematic-manager/backend/package.json +1 -1
- package/node_modules/@homematic-manager/core/dist/api/types.d.ts +12 -0
- package/node_modules/@homematic-manager/core/dist/rssi/index.d.ts +53 -0
- package/node_modules/@homematic-manager/core/dist/rssi/index.js +59 -0
- package/node_modules/@homematic-manager/core/package.json +1 -1
- package/package.json +3 -3
- package/ui/assets/index-B7Elf04r.js +14 -0
- package/ui/assets/index-Cf5ZWmKX.css +1 -0
- package/ui/index.html +2 -2
- package/ui/assets/index-CiQd11uV.js +0 -14
- package/ui/assets/index-CkUznuX2.css +0 -1
|
@@ -416,6 +416,8 @@ export class Backend {
|
|
|
416
416
|
return this.#resetUnreach(p[0], p[1]);
|
|
417
417
|
case 'serviceMessages.list':
|
|
418
418
|
return this.#serviceMessages(p[0]);
|
|
419
|
+
case 'serviceMessages.refresh':
|
|
420
|
+
return this.#refreshAllServiceMessages(p[0]);
|
|
419
421
|
case 'serviceMessages.ack':
|
|
420
422
|
return this.#acknowledge(p[0], p[1], p[2]);
|
|
421
423
|
case 'events.recent':
|
|
@@ -1022,6 +1024,28 @@ export class Backend {
|
|
|
1022
1024
|
#serviceMessages(interfaceName) {
|
|
1023
1025
|
return this.#caches.listServiceMessages(interfaceName);
|
|
1024
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Issue #146: what the refresh button of the service-message tab asks for.
|
|
1029
|
+
*
|
|
1030
|
+
* The list itself comes out of the cache, which the events and a five-minute poll keep up to
|
|
1031
|
+
* date; pressing refresh re-read that cache and therefore changed nothing at all - "Refresh
|
|
1032
|
+
* Button ohne Funktion". This makes the round trip the button promises: `getServiceMessages`
|
|
1033
|
+
* on every connected interface that has the method, and the `:0` sweep on HmIP, which is where
|
|
1034
|
+
* its messages come from. Failures are notices, as everywhere else here - an interface that
|
|
1035
|
+
* does not answer must not turn the button into an error dialog.
|
|
1036
|
+
*/
|
|
1037
|
+
async #refreshAllServiceMessages(interfaceName) {
|
|
1038
|
+
const names = (this.#manager?.names() ?? []).filter((name) => (interfaceName === undefined || name === interfaceName) && this.#manager?.isConnected(name) === true);
|
|
1039
|
+
for (const name of names) {
|
|
1040
|
+
if (name === 'HmIP-RF') {
|
|
1041
|
+
await this.sweepHmip(name);
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
await this.#refreshServiceMessages(name);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return this.#serviceMessages(interfaceName);
|
|
1048
|
+
}
|
|
1025
1049
|
/**
|
|
1026
1050
|
* Does it make sense to ask this interface for its service messages?
|
|
1027
1051
|
*
|
|
@@ -1390,6 +1414,7 @@ export const API_METHOD_NAMES = [
|
|
|
1390
1414
|
'unreach.list',
|
|
1391
1415
|
'unreach.reset',
|
|
1392
1416
|
'serviceMessages.list',
|
|
1417
|
+
'serviceMessages.refresh',
|
|
1393
1418
|
'serviceMessages.ack',
|
|
1394
1419
|
'events.recent',
|
|
1395
1420
|
'events.clear',
|
|
@@ -25,6 +25,8 @@ import { RpcClient, type RpcCallRecord, type RpcClientOptions } from '../rpc/cli
|
|
|
25
25
|
import { type CallbackHandler, type CallbackServerSet } from '../rpc/server.js';
|
|
26
26
|
/** How often the watchdog looks at every interface. 2.x used the same 15 s. */
|
|
27
27
|
export declare const WATCHDOG_INTERVAL_MS = 15000;
|
|
28
|
+
/** Where an interface process on this very box calls back (#144). */
|
|
29
|
+
export declare const LOOPBACK_IP = "127.0.0.1";
|
|
28
30
|
/** How long `stop()` waits for the de-registering `init(url, '')` calls. */
|
|
29
31
|
export declare const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
30
32
|
/**
|
|
@@ -97,7 +99,15 @@ export declare function firstBidcosInterfaceAddress(result: unknown): string | u
|
|
|
97
99
|
export declare class InterfaceManager {
|
|
98
100
|
#private;
|
|
99
101
|
constructor(options: InterfaceManagerOptions);
|
|
100
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* The address the interface processes are told to call back on.
|
|
104
|
+
*
|
|
105
|
+
* Issue #144: on the CCU itself (`local`, which is what the addon sets) that is `127.0.0.1`.
|
|
106
|
+
* The first LAN address of the box worked, but it is the wrong answer twice over: it is the
|
|
107
|
+
* one address that changes - a new lease, another network - while `init` registrations survive
|
|
108
|
+
* such a change in the interface process's handler list, and every other local subscriber on a
|
|
109
|
+
* CCU registers on the loopback, which is what a look at that list expects to see.
|
|
110
|
+
*/
|
|
101
111
|
get callbackIp(): string;
|
|
102
112
|
/** D-31: are the subscriptions currently dropped because nobody is looking? */
|
|
103
113
|
get idle(): boolean;
|
|
@@ -27,6 +27,8 @@ import { CallbackServers } from '../rpc/server.js';
|
|
|
27
27
|
import { localIPv4Addresses, probePort, withTimeout } from '../util/net.js';
|
|
28
28
|
/** How often the watchdog looks at every interface. 2.x used the same 15 s. */
|
|
29
29
|
export const WATCHDOG_INTERVAL_MS = 15_000;
|
|
30
|
+
/** Where an interface process on this very box calls back (#144). */
|
|
31
|
+
export const LOOPBACK_IP = '127.0.0.1';
|
|
30
32
|
/** How long `stop()` waits for the de-registering `init(url, '')` calls. */
|
|
31
33
|
export const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
32
34
|
/**
|
|
@@ -86,14 +88,25 @@ export class InterfaceManager {
|
|
|
86
88
|
},
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* The address the interface processes are told to call back on.
|
|
93
|
+
*
|
|
94
|
+
* Issue #144: on the CCU itself (`local`, which is what the addon sets) that is `127.0.0.1`.
|
|
95
|
+
* The first LAN address of the box worked, but it is the wrong answer twice over: it is the
|
|
96
|
+
* one address that changes - a new lease, another network - while `init` registrations survive
|
|
97
|
+
* such a change in the interface process's handler list, and every other local subscriber on a
|
|
98
|
+
* CCU registers on the loopback, which is what a look at that list expects to see.
|
|
99
|
+
*/
|
|
90
100
|
get callbackIp() {
|
|
91
101
|
const configured = this.#options.connection.callback.ip;
|
|
92
102
|
if (configured !== '') {
|
|
93
103
|
return configured;
|
|
94
104
|
}
|
|
105
|
+
if (this.#options.connection.local === true) {
|
|
106
|
+
return LOOPBACK_IP;
|
|
107
|
+
}
|
|
95
108
|
const addresses = (this.#options.localAddresses ?? (() => localIPv4Addresses()))();
|
|
96
|
-
return addresses[0] ??
|
|
109
|
+
return addresses[0] ?? LOOPBACK_IP;
|
|
97
110
|
}
|
|
98
111
|
/** D-31: are the subscriptions currently dropped because nobody is looking? */
|
|
99
112
|
get idle() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homematic-manager/backend",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.9",
|
|
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",
|
|
@@ -811,6 +811,18 @@ export interface ApiMethods {
|
|
|
811
811
|
params: [interfaceName?: string];
|
|
812
812
|
result: ServiceMessage[];
|
|
813
813
|
};
|
|
814
|
+
/**
|
|
815
|
+
* Issue #146: asks the interface processes again and answers with the fresh list.
|
|
816
|
+
*
|
|
817
|
+
* {@link 'serviceMessages.list'} reads the backend's cache, which is filled by the events and
|
|
818
|
+
* by a poll every five minutes - so the refresh button of the tab returned the same list it
|
|
819
|
+
* already showed and looked like a button without a function. This is the call that makes the
|
|
820
|
+
* round trip: `getServiceMessages` per BidCos interface, the `:0` sweep on HmIP.
|
|
821
|
+
*/
|
|
822
|
+
'serviceMessages.refresh': {
|
|
823
|
+
params: [interfaceName?: string];
|
|
824
|
+
result: ServiceMessage[];
|
|
825
|
+
};
|
|
814
826
|
/** Acknowledge by writing the datapoint (STICKY_UNREACH etc.). */
|
|
815
827
|
'serviceMessages.ack': {
|
|
816
828
|
params: [interfaceName: string, address: string, datapoint: string];
|
|
@@ -107,4 +107,57 @@ export declare class RssiStore {
|
|
|
107
107
|
readonly tx?: number;
|
|
108
108
|
} | undefined;
|
|
109
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[];
|
|
110
163
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -196,4 +196,63 @@ function channel(value) {
|
|
|
196
196
|
function hex(value) {
|
|
197
197
|
return `0${value.toString(16)}`.slice(-2);
|
|
198
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
|
+
}
|
|
199
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.9",
|
|
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.9",
|
|
71
|
+
"@homematic-manager/core": "3.0.0-beta.9",
|
|
72
72
|
"binrpc": "^4.2.0",
|
|
73
73
|
"homematic-rega": "^2.0.0",
|
|
74
74
|
"homematic-xmlrpc": "^2.0.0",
|