homematic-manager 3.0.0-beta.7 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homematic-manager/backend",
3
- "version": "3.0.0-beta.7",
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",
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homematic-manager/core",
3
- "version": "3.0.0-beta.7",
3
+ "version": "3.0.0-beta.8",
4
4
  "type": "module",
5
5
  "description": "Pure domain logic of the Homematic Manager: no I/O, no DOM",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homematic-manager",
3
- "version": "3.0.0-beta.7",
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.7",
71
- "@homematic-manager/core": "3.0.0-beta.7",
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",