hm-simulator 1.3.0 → 1.3.1
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/CHANGELOG.md +15 -0
- package/README.md +14 -1
- package/lib/sim.js +32 -8
- package/package.json +1 -1
- package/sim.d.ts +39 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes of hm-simulator. The release workflow takes each version's section from here for the
|
|
4
4
|
GitHub release notes.
|
|
5
5
|
|
|
6
|
+
## 1.3.1
|
|
7
|
+
|
|
8
|
+
- `getLinks` without an address reports `FLAGS: 1` (`SENDER_BROKEN`) on every device-internal link of
|
|
9
|
+
`rfd` - a relay's own button on the relay, a dimmer's channel on its virtual channels - as rfd
|
|
10
|
+
3.89.11 does although such a link works; filtered by an address the link carries its stored flags,
|
|
11
|
+
0, as rfd answers there too. A test that shows a client how it handles rfd's flag can now make the
|
|
12
|
+
link with `addLink` instead of seeding it (B-3, found through Homematic Manager's links list).
|
|
13
|
+
- `replaceDevice` answers `true`, as the specification's `bool` says, where it answered `''` (a
|
|
14
|
+
client that takes the answer as the success saw a replacement that did happen as failed).
|
|
15
|
+
- rfd's `listReplaceableDevices` names only the devices of the same type that are unreachable
|
|
16
|
+
(`setReachable(iface, address, false)`); a device that still answers is not replaced.
|
|
17
|
+
- The declarations gain `clients` (the registered logic layers per interface, read-only, `Client`),
|
|
18
|
+
`dispatch` with its `DispatchContext` (the servers' entry, which a test may wrap to record calls),
|
|
19
|
+
and `replaceDevice`'s `true` (B-2, found through Homematic Manager's tests on 1.3.0).
|
|
20
|
+
|
|
6
21
|
## 1.3.0
|
|
7
22
|
|
|
8
23
|
- The calls Homematic Manager makes, measured on rfd and hmipserver of firmware 3.89.11:
|
package/README.md
CHANGED
|
@@ -487,6 +487,18 @@ Where the simulator imitates an interface process, this says whether the shape w
|
|
|
487
487
|
CCU or is the simulator's model. The measurements were made for Homematic Manager (tasks 6 and 58)
|
|
488
488
|
on CCUs with firmware 3.89.8 and 3.89.x.
|
|
489
489
|
|
|
490
|
+
### Direct links
|
|
491
|
+
|
|
492
|
+
rfd (firmware 3.89.11, 2026-09-30) answers `getLinks("", flags)` - the list of all links - with
|
|
493
|
+
`FLAGS: 1` (`SENDER_BROKEN`) on every **device-internal** link: a relay's own button on the relay
|
|
494
|
+
(`:1` → `:1`), a dimmer's channel on its virtual channels. The link works; the bit carries no
|
|
495
|
+
information there. Asked for one address, `getLinks("ABC0000001:1", 0)` and `getLinks("ABC0000001",
|
|
496
|
+
0)` answer the same link with `FLAGS: 0`, and `getLinkPeers` names the own channel once. The simulator
|
|
497
|
+
does the same on `rfd`: `addLink` stores `FLAGS: 0`, the unfiltered list adds bit 1 to internal links
|
|
498
|
+
(seeded flags are kept and or-ed), the filtered one reports what is stored. hmipserver is not
|
|
499
|
+
measured and reports the stored flags. rfd also refuses `getLinkPeers` of a device address (`-1
|
|
500
|
+
Failure`); the simulator answers the peers of all its channels.
|
|
501
|
+
|
|
490
502
|
### CONFIG_PENDING
|
|
491
503
|
|
|
492
504
|
What a `putParamset MASTER` does, per interface (`interfaces.<iface>.configPendingMode`):
|
|
@@ -609,7 +621,8 @@ lifted again):
|
|
|
609
621
|
|
|
610
622
|
The simulator answers so, with the fault table in force (`-1 Generic error` under hmipserver's
|
|
611
623
|
default table where rfd says `-1 Failure`) and `''` for hmipserver's empty body. Its models:
|
|
612
|
-
`listReplaceableDevices` on rfd lists the other devices of the same `TYPE
|
|
624
|
+
`listReplaceableDevices` on rfd lists the other devices of the same `TYPE` that are unreachable
|
|
625
|
+
(`setReachable(iface, address, false)`; a device that still answers is not replaced); `setMetadata` on rfd
|
|
613
626
|
knows only its own addresses; `setInterfaceClock` is recorded, not measured (it sets the clock the
|
|
614
627
|
devices get). `addDevice(serial)` pairs only the device of `scriptKeyMismatch`, with the right
|
|
615
628
|
temporary key; any other serial is `unknownInstance`, the mismatch a `notSupported` fault.
|
package/lib/sim.js
CHANGED
|
@@ -45,6 +45,15 @@ const CALLBACK_LOG_SIZE = 10000;
|
|
|
45
45
|
/** the interfaces that speak the BidCos protocol, and therefore have setTempKey */
|
|
46
46
|
const BIDCOS_INTERFACES = new Set(['rfd', 'wired']);
|
|
47
47
|
|
|
48
|
+
/** FLAGS bit of a link whose sender rfd counts as broken (bit 2 is the receiver) */
|
|
49
|
+
const LINK_SENDER_BROKEN = 1;
|
|
50
|
+
|
|
51
|
+
/** The device part of a channel address: `ABC0000001:1` -> `ABC0000001`. */
|
|
52
|
+
function deviceOf(address) {
|
|
53
|
+
const colon = String(address).indexOf(':');
|
|
54
|
+
return colon === -1 ? String(address) : String(address).slice(0, colon);
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
/** device types of the HmIP radio modules, which carry DUTY_CYCLE_LEVEL and CARRIER_SENSE_LEVEL */
|
|
49
58
|
const RADIO_MODULE_TYPES = new Set(['RPI-RF-MOD', 'HmIP-RFUSB', 'HmIP-RFUSB-TK', 'HMIP-CCU3', 'HmIP-CCU3']);
|
|
50
59
|
|
|
@@ -897,14 +906,24 @@ class HmSim {
|
|
|
897
906
|
links = links.filter((link) => addresses.has(link.SENDER) || addresses.has(link.RECEIVER));
|
|
898
907
|
}
|
|
899
908
|
|
|
900
|
-
|
|
909
|
+
// rfd (measured, firmware 3.89.11): the list of *all* links carries FLAGS bit 1 (SENDER_BROKEN)
|
|
910
|
+
// on every device-internal link - a relay's own button on the relay, a dimmer's channel on its
|
|
911
|
+
// virtual channels - although the link works; asked for one address, the same link comes with
|
|
912
|
+
// its stored flags, 0. The simulator does the same on rfd, whatever `flags` asks for.
|
|
913
|
+
const internalBroken = iface === 'rfd' && !address;
|
|
914
|
+
return links.map((link) => this.linkStruct(iface, link, flags, internalBroken));
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/** Sender and receiver on one device: `ABC0000001:1` and `ABC0000001:2`, or the same channel. */
|
|
918
|
+
isInternalLink(link) {
|
|
919
|
+
return deviceOf(link.SENDER) === deviceOf(link.RECEIVER);
|
|
901
920
|
}
|
|
902
921
|
|
|
903
|
-
linkStruct(iface, link, flags) {
|
|
922
|
+
linkStruct(iface, link, flags, internalBroken = false) {
|
|
904
923
|
const result = {
|
|
905
924
|
SENDER: link.SENDER,
|
|
906
925
|
RECEIVER: link.RECEIVER,
|
|
907
|
-
FLAGS: link.FLAGS || 0,
|
|
926
|
+
FLAGS: (link.FLAGS || 0) | (internalBroken && this.isInternalLink(link) ? LINK_SENDER_BROKEN : 0),
|
|
908
927
|
NAME: link.NAME || '',
|
|
909
928
|
DESCRIPTION: link.DESCRIPTION || '',
|
|
910
929
|
};
|
|
@@ -1373,9 +1392,11 @@ class HmSim {
|
|
|
1373
1392
|
|
|
1374
1393
|
/**
|
|
1375
1394
|
* `listReplaceableDevices(address)` - the devices a device can take the place of. rfd: the other
|
|
1376
|
-
* devices of the same `TYPE`
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1395
|
+
* devices of the same `TYPE` that are unreachable (`setReachable(iface, address, false)`) - a
|
|
1396
|
+
* device that still answers is not replaced (the simulator's model, after what the CCU's WebUI
|
|
1397
|
+
* offers; measured: `[]` for rfd's own `BidCoS-RF`, `-2 Unknown instance` for an address it does
|
|
1398
|
+
* not know). hmipserver answers with an empty HTTP body whatever it is asked (measured, firmware
|
|
1399
|
+
* 3.89.11), which the simulator answers as `''`.
|
|
1379
1400
|
*/
|
|
1380
1401
|
listReplaceableDevices(iface, params) {
|
|
1381
1402
|
const [address] = params || [];
|
|
@@ -1389,7 +1410,10 @@ class HmSim {
|
|
|
1389
1410
|
if (device.PARENT) {
|
|
1390
1411
|
return [];
|
|
1391
1412
|
}
|
|
1392
|
-
|
|
1413
|
+
const unreachable = this.unreachable[iface] || new Set();
|
|
1414
|
+
return this.deviceList(iface).filter(
|
|
1415
|
+
(entry) => entry.TYPE === device.TYPE && entry.ADDRESS !== address && unreachable.has(entry.ADDRESS),
|
|
1416
|
+
);
|
|
1393
1417
|
}
|
|
1394
1418
|
|
|
1395
1419
|
/**
|
|
@@ -1736,7 +1760,7 @@ class HmSim {
|
|
|
1736
1760
|
}
|
|
1737
1761
|
|
|
1738
1762
|
this.deleteDevice(iface, oldAddress);
|
|
1739
|
-
return
|
|
1763
|
+
return true;
|
|
1740
1764
|
}
|
|
1741
1765
|
|
|
1742
1766
|
reportValueUsage(iface, params) {
|
package/package.json
CHANGED
package/sim.d.ts
CHANGED
|
@@ -257,6 +257,31 @@ declare namespace HmSim {
|
|
|
257
257
|
/** `method -> handler(iface, params)`, as the servers dispatch it. */
|
|
258
258
|
type RpcMethodTable = Record<string, (iface: string, params: unknown[]) => unknown>;
|
|
259
259
|
|
|
260
|
+
/** A logic layer registered with `init`: what the simulator calls back. */
|
|
261
|
+
interface Client {
|
|
262
|
+
/** the interface id the client gave `init` */
|
|
263
|
+
id: string;
|
|
264
|
+
/** its callback url, as given */
|
|
265
|
+
url: string;
|
|
266
|
+
/** the key in `clients[iface]` (the url, exactly) */
|
|
267
|
+
key: string;
|
|
268
|
+
/** the underlying binrpc or xmlrpc client */
|
|
269
|
+
client: unknown;
|
|
270
|
+
/** one callback to the client; logged in `getCallbackLog()` */
|
|
271
|
+
methodCall(
|
|
272
|
+
method: string,
|
|
273
|
+
params: unknown[],
|
|
274
|
+
callback: (error: Error | null | undefined, result?: unknown) => void,
|
|
275
|
+
options?: {timeout?: number},
|
|
276
|
+
): void;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** What the servers hand `dispatch` besides the call. */
|
|
280
|
+
interface DispatchContext {
|
|
281
|
+
/** the connection the call came in on, for an injected `closeSocket` */
|
|
282
|
+
socket?: {destroy(): void};
|
|
283
|
+
}
|
|
284
|
+
|
|
260
285
|
/** The ports once `whenReady()` resolved. */
|
|
261
286
|
type Ports = Partial<Record<InterfaceName, number>>;
|
|
262
287
|
|
|
@@ -305,6 +330,8 @@ declare class HmSim {
|
|
|
305
330
|
/** stored state per interface and address: `{VALUES, MASTER, LINKS, ...}` */
|
|
306
331
|
values: Record<string, Record<string, Record<string, Record<string, unknown>>>>;
|
|
307
332
|
links: Record<string, HmSim.Link[]>;
|
|
333
|
+
/** the registered logic layers per interface, by callback url: the subscriptions `init(url, '')` removes */
|
|
334
|
+
readonly clients: Record<string, Record<string, HmSim.Client>>;
|
|
308
335
|
serviceMessages: Record<string, HmSim.ServiceMessage[]>;
|
|
309
336
|
/** every reportValueUsage */
|
|
310
337
|
valueUsage: Array<{iface: string; address: string; valueId: string; refCounter: number; ts: number}>;
|
|
@@ -421,10 +448,21 @@ declare class HmSim {
|
|
|
421
448
|
|
|
422
449
|
/** One RPC method as a client call would run it; throws the fault. */
|
|
423
450
|
callMethod(iface: string, method: string, params?: unknown[]): unknown;
|
|
451
|
+
/**
|
|
452
|
+
* The servers' entry for every incoming call: injected faults and the lifecycle gates apply, then
|
|
453
|
+
* `callMethod`, and `callback(fault)` or `callback(null, result)`. A test wraps it to record calls.
|
|
454
|
+
*/
|
|
455
|
+
dispatch(
|
|
456
|
+
iface: string,
|
|
457
|
+
method: string,
|
|
458
|
+
params: unknown[],
|
|
459
|
+
callback: (fault: HmSim.RpcFault | null | undefined, result?: unknown) => void,
|
|
460
|
+
context?: HmSim.DispatchContext,
|
|
461
|
+
): void;
|
|
424
462
|
getLinks(iface: string, params?: unknown[]): Array<HmSim.Link & Record<string, unknown>>;
|
|
425
463
|
getLinkPeers(iface: string, params: unknown[]): string[];
|
|
426
464
|
deleteDevice(iface: string, address: string, flags?: number): '';
|
|
427
|
-
replaceDevice(iface: string, oldAddress: string, newAddress: string):
|
|
465
|
+
replaceDevice(iface: string, oldAddress: string, newAddress: string): true;
|
|
428
466
|
getKeyMismatchDevice(iface: string, params?: [reset?: boolean]): string;
|
|
429
467
|
suppressServiceMessages(
|
|
430
468
|
iface: string,
|