hm-simulator 0.1.1 → 1.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Sebastian Raff
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,85 +1,363 @@
1
1
  # hm-simulator
2
2
 
3
3
  [![NPM version](https://badge.fury.io/js/hm-simulator.svg)](http://badge.fury.io/js/hm-simulator)
4
- [![Dependency Status](https://img.shields.io/gemnasium/hobbyquaker/hm-simulator.svg?maxAge=2592000)](https://gemnasium.com/github.com/hobbyquaker/hm-simulator)
5
- [![Build Status](https://travis-ci.org/hobbyquaker/hm-simulator.svg?branch=master)](https://travis-ci.org/hobbyquaker/hm-simulator)
6
- [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
4
+ [![CI](https://github.com/hobbyquaker/hm-simulator/actions/workflows/ci.yml/badge.svg)](https://github.com/hobbyquaker/hm-simulator/actions/workflows/ci.yml)
7
5
  [![License][mit-badge]][mit-url]
8
6
 
9
- > Simulates (partly) a Homematic CCU
7
+ > Simulates a Homematic CCU for automated tests
10
8
 
9
+ hm-simulator answers the RPC calls a CCU's interface processes answer, keeps the state behind them
10
+ and calls back into the logic layer that registered itself, so that an application that talks to a
11
+ CCU can be tested without one. It is used by
12
+ [node-red-contrib-ccu](https://github.com/rdmtc/node-red-contrib-ccu),
13
+ [hm2mqtt.js](https://github.com/hobbyquaker/hm2mqtt.js) and
14
+ [Homematic Manager](https://github.com/hobbyquaker/homematic-manager).
11
15
 
12
16
  ## Installation
13
17
 
14
- Prerequisites: [Node.js](https://nodejs.org) >= 6.0
15
-
16
- `npm install -g hm-simulator`
17
-
18
-
19
- ## What for?
20
-
21
- This can be used for automated tests of software connecting to a Homematic CCU
22
-
23
-
24
- ## What does it do?
25
-
26
- **Warning: This is far away from a complete simulation and still work in progress** Until now I'm only implementing what
27
- I needed for basic tests of [hm2mqtt.js](https://github.com/hobbyquaker/hm2mqtt.js).
28
-
29
- * Simulated rfd, [binrpc](https://github.com/hobbyquaker/binrpc) only, interface on port 2001
30
- * Simulated hmipserver, xmlrpc only, interface on port 2010
31
-
32
-
33
- ### RPC methods implemented until now
34
-
35
- **incoming:**
36
-
37
- * init
38
- * ping
39
- * system.listMethods
40
- * getParamsetDescription
41
-
42
-
43
- **outgoing:**
44
-
45
- * listDevices
46
- * newDevices
47
- * deleteDevices
48
- * event
49
- * system.multicall
50
-
51
-
52
- ### Scripting
53
-
54
- In the behaviors directory are two example scripts:
55
-
56
- * `virtual-button-press.js` triggers a `PRESS_SHORT` on `BidCoS-RF:1` every 5 seconds.
57
- * `window-open-close.js` opens and closes `0000D3C98C9233:1` every 30 seconds.
58
-
59
-
60
- ## Todo
61
-
62
- * bugfixes...
63
- * extend script api
64
- * more behavior examples
65
- * more rfd devices
66
- * correct error responses to invalid methodCalls
67
- * incoming getParamset
68
- * MASTER, MAINTENANCE and LINK paramsets
69
- * service messages, incoming getServiceMessages
70
- * incoming putParamset
71
- * rega listener on port 8181, rega script mocking
72
- * ... many more... ;)
73
-
18
+ Prerequisites: [Node.js](https://nodejs.org) >= 20.19
19
+
20
+ ```
21
+ npm install --save-dev hm-simulator
22
+ ```
23
+
24
+ Or, as a standalone process, `npm install -g hm-simulator` and then `hm-simulator --help`.
25
+
26
+ ## What is simulated
27
+
28
+ **Interfaces.** rfd (binrpc), hmipserver (xmlrpc), BidCos-Wired (binrpc), VirtualDevices (xmlrpc,
29
+ path `/groups`) and CUxD (binrpc). Each has its own devices, values, paramsets, links and service
30
+ messages. rfd and hmipserver start by default, the others when their port is configured.
31
+
32
+ **Incoming RPC methods.**
33
+
34
+ | | |
35
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
36
+ | session | `init` (register and de-register a logic layer), `ping`, `system.listMethods`, `system.methodHelp`, `system.multicall` |
37
+ | devices | `listDevices`, `getDeviceDescription`, `deleteDevice`, `replaceDevice`, `setInstallMode`, `getInstallMode` |
38
+ | paramsets | `getParamsetDescription`, `getParamset`, `putParamset`, `getValue`, `setValue`, `determineParameter`, `reportValueUsage` |
39
+ | links | `getLinks`, `getLinkPeers`, `getLinkInfo`, `setLinkInfo`, `addLink`, `removeLink`, `activateLinkParamset` |
40
+ | interface | `rssiInfo`, `listBidcosInterfaces`, `setBidcosInterface`, `getServiceMessages`, `setTempKey` (BidCos only) |
41
+ | teams | `listTeams`, `setTeam` (BidCos only): the smoke detector teams as rfd keeps them, pseudo devices `*<serial>` |
42
+ | maintenance | `clearConfigCache`, `restoreConfigToDevice`, `updateFirmware`, `installFirmware` (the last two are stubs that record the call) |
43
+
44
+ **Outgoing RPC calls** to every registered logic layer: `listDevices`, `newDevices`,
45
+ `deleteDevices`, `updateDevice` (after `setTeam`), `event`, `system.multicall`.
46
+
47
+ **ReGa** (`rega.exe` on port 8181): the scripts of the
48
+ [homematic-rega](https://github.com/hobbyquaker/homematic-rega) client - `getChannels`,
49
+ `getVariables`, `getPrograms`, `getRooms`, `getFunctions`, `getValues` - plus setting a variable,
50
+ renaming an object, activating and executing a program. Every script the mock receives is recorded
51
+ (`sim.regaSim.scripts`), renames additionally in `sim.regaSim.renames`.
52
+
53
+ **Faults.** Unknown method, unknown address, unknown paramset, unknown parameter, read-only
54
+ parameter, wrong type, out of range, unknown link. Over xmlrpc as an XML-RPC fault, over binrpc as
55
+ a message of type `0xff` with a `faultCode`/`faultString` struct - see
56
+ [Fault codes](#fault-codes-measured) below.
57
+
58
+ What is **not** simulated: the actual radio protocol, firmware behaviour, duty cycle, the CCU's web
59
+ UI and JSON-API, and anything a device does on its own beyond what a behaviour script or the
60
+ scenario API makes it do.
61
+
62
+ ## Usage
63
+
64
+ ```js
65
+ const HmSim = require('hm-simulator/sim.js'); // ESM: import HmSim from 'hm-simulator/sim.mjs'
66
+
67
+ const sim = new HmSim({
68
+ devices: {
69
+ rfd: require('hm-simulator/data/devices-rfd.json'),
70
+ hmip: require('hm-simulator/data/devices-hmip.json'),
71
+ },
72
+ config: {listenAddress: '127.0.0.1', binrpcListenPort: 2001, xmlrpcListenPort: 2010},
73
+ });
74
+
75
+ await sim.whenReady(); // every server is accepting connections
76
+ // ... run the code under test ...
77
+ sim.close();
78
+ ```
79
+
80
+ With a port of `0` the operating system picks one and `sim.ports` holds what it picked
81
+ (`sim.ports.rfd`, `sim.ports.hmip`, ...) once `whenReady()` resolved. That is the reliable way to
82
+ run tests in parallel.
83
+
84
+ ### Options
85
+
86
+ | option | default | |
87
+ | -------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------- |
88
+ | `log` | silent | object with `debug`, `info`, `warn`, `error` |
89
+ | `devices` | `{rfd: {devices: []}, hmip: {devices: []}}` | device descriptions per interface |
90
+ | `paramsetDescriptions` | the bundled 8.4 MB `data/paramset-descriptions.json` | replaces the bundled descriptions |
91
+ | `config.listenAddress` | all interfaces | |
92
+ | `config.binrpcListenPort` | – | rfd |
93
+ | `config.xmlrpcListenPort` | – | hmipserver |
94
+ | `config.wiredListenPort` | – | BidCos-Wired, started when `devices.wired` exists |
95
+ | `config.virtualListenPort` | off | VirtualDevices |
96
+ | `config.cuxdListenPort` | off | CUxD |
97
+ | `config.virtualPath` | `/groups` | path the VirtualDevices server answers on |
98
+ | `behaviorPath` | `behaviors/` of the package | directory with behaviour scripts |
99
+ | `rega` | off | ReGa mock, see below |
100
+ | `links` | `{}` | links per interface, `[{SENDER, RECEIVER, NAME, DESCRIPTION}]` |
101
+ | `serviceMessages` | `{}` | service messages per interface, `[[address, datapoint, value]]` |
102
+ | `newDevices` | `{}` | devices that appear when the install mode is switched on, `{<iface>: {devices, delay}}` |
103
+ | `bidcosInterfaces` | one per interface | what `listBidcosInterfaces` answers |
104
+ | `defaultRssi` | `-65` | what `rssiInfo` answers where a device has no RSSI datapoint |
105
+ | `interfaces` | see [CONFIG_PENDING](#config_pending) | per interface behaviour |
106
+ | `faults` | see [Fault codes](#fault-codes-measured) | overrides for the fault table |
107
+ | `tls` | off | `true` generates a self signed certificate, or pass `{key, cert}` |
108
+ | `auth` | off | `{username, password}`, HTTP basic auth for the xmlrpc servers and ReGa |
109
+
110
+ `rega` takes `{port, listenAddress, channels, variables, programs, rooms, functions, values, tls,
111
+ auth}`. `channels` is what `getChannels()` answers: `[{id, address, name}]`.
112
+
113
+ ### Scenario API
114
+
115
+ ```js
116
+ sim.addDevice('rfd', deviceDescription, channel0, channel1); // sends newDevices
117
+ sim.removeDevice('rfd', 'ABC0000001'); // sends deleteDevices
118
+ sim.fireEvent('rfd', 'ABC0000001:1', 'STATE', true); // one event, no validation
119
+ sim.setServiceMessage('rfd', 'ABC0000001:0', 'STICKY_UNREACH', true);
120
+ sim.scriptNewDevices('rfd', [devices], 500); // appear after setInstallMode
121
+ await sim.dropConnection('rfd'); // interface process restart
122
+ sim.getWriteLog(); // every accepted putParamset: {iface, address, paramset, values, rejected, ts}
123
+ sim.getConfigPending('rfd'); // [{address, sticky}]
124
+ sim.getPoisonedChannels('hmip'); // channels whose stored MASTER has a parameter they do not have
125
+ sim.api.emit('setValue', 'rfd', 'ABC0000001:1', 'STATE', true); // as a behaviour script would
126
+ ```
127
+
128
+ Behaviour scripts are plain modules in `behaviorPath` that export `api => { ... }` and use
129
+ `api.emit('setValue', iface, address, datapoint, value)`; the two examples in `behaviors/` press a
130
+ virtual button and open a window periodically. Point `behaviorPath` at an empty directory to have
131
+ none.
132
+
133
+ ## CONFIG_PENDING
134
+
135
+ Devices that end up stuck in `CONFIG_PENDING` after a paramset write are the reason this part
136
+ exists (Homematic Manager issue #98). Until 2026-09-05 there were two competing explanations and
137
+ nobody had measured which one the interface processes actually implement. Homematic Manager's
138
+ roadmap task 6 measured it, on two lab CCUs on firmware 3.89.8; the write-up with the raw answers
139
+ is `docs/config-pending.md` in that repository, and the answer is that neither hypothesis was
140
+ right:
141
+
142
+ - **hmipserver stores what it rejects.** Everything in the struct is written into its own
143
+ configuration for the channel first; the fault comes afterwards, when the result cannot be
144
+ transferred to the device. A parameter the channel does not have is stored **for ever** - it
145
+ survives a restart, no RPC method removes it, and from then on every `putParamset` on that
146
+ channel faults, including one with an empty struct. A value of the wrong type raises a sticky
147
+ `CONFIG_PENDING`, which a valid full MASTER write clears. A number outside `MIN`..`MAX` is
148
+ accepted without a word: hmipserver does not range-check.
149
+ - **rfd never faults on a value.** It drops a parameter the device does not have, ignores what it
150
+ cannot use, clamps numbers into `MIN`..`MAX` and coerces strings - and answers `ok` to all of
151
+ it. `CONFIG_PENDING` there means "a configuration is queued for the device" and clears when the
152
+ device takes it, which on a battery device is when it next wakes up.
153
+
154
+ The simulator implements both, plus the two original hypotheses, per interface:
155
+
156
+ ```js
157
+ new HmSim({
158
+ interfaces: {
159
+ hmip: {configPendingMode: 'hmip'}, // the default for hmip
160
+ rfd: {configPendingMode: 'bidcos', configPendingDelay: 3000}, // the default for rfd and wired
161
+ },
162
+ });
163
+ ```
164
+
165
+ | option | default | |
166
+ | ------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | `configPendingMode` | `'hmip'` for `hmip`, `'bidcos'` for `rfd` and `wired`, `'strict'` for the rest | see the table below |
168
+ | `configPendingOnWrite` | `false` | raise `CONFIG_PENDING` for _every_ accepted MASTER write. Implied by `'bidcos'`, which raises it for every write that changes something |
169
+ | `configPendingDelay` | `0` | milliseconds after which a non-sticky `CONFIG_PENDING` clears itself - the stand-in for the device taking the configuration. The lab measured 160-180 s on a thermostat that transmits regularly, and "until someone opens the door" on a contact |
170
+ | `serviceMessagesEmptyAsString` | `false` | answer `getServiceMessages` with `''` instead of `[]` when nothing is pending, which is what rfd really does |
171
+
172
+ | `configPendingMode` | what a `putParamset MASTER` does |
173
+ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `'hmip'` | measured hmipserver: stores everything, faults when the result is not transferable, poisons the channel on an unknown parameter, sticky `CONFIG_PENDING` on a wrong type, no range check |
175
+ | `'bidcos'` | measured rfd: no fault, unknown parameters dropped, values clamped or ignored, `CONFIG_PENDING` while the change is queued |
176
+ | `'strict'` | the first hypothesis: an invalid write is answered with a fault and nothing is written |
177
+ | `'pending'` | the second hypothesis: the write is accepted, the valid parameters are stored, the rejected ones are recorded, and a sticky `CONFIG_PENDING` is raised |
178
+
179
+ In `'strict'` and `'pending'` a sticky `CONFIG_PENDING` clears on a valid full MASTER write, on
180
+ `clearConfigCache(deviceAddress)` or on `restoreConfigToDevice(deviceAddress)`. In `'hmip'` only
181
+ the valid full MASTER write does: `clearConfigCache`, `restoreConfigToDevice` and
182
+ `determineParameter` answer `-1 Generic error` there, exactly as hmipserver does - they are BidCos
183
+ methods that hmipserver lists but does not implement.
184
+
185
+ `sim.getPoisonedChannels(iface)` lists the channels whose stored MASTER carries a parameter their
186
+ description does not have. Deleting the device (`sim.removeDevice`) is the only thing that clears
187
+ one, which is also true of the hardware: there, it means pairing the device again.
188
+
189
+ ## Fault codes, measured
190
+
191
+ The interface processes' fault codes are not publicly specified, and the table in `lib/faults.js`
192
+ used to be an educated guess. It is a measurement now (Homematic Manager task 6, firmware 3.89.8).
193
+ The default table is **hmipserver's**, because that is the one an application has to survive:
194
+
195
+ | key | code | string | when |
196
+ | ------------------ | ---- | ------------------------------- | ---------------------------------------------------------------------------------- |
197
+ | `unknownMethod` | -1 | Invalid XML-RPC message | no handler for the method name (hmipserver answers this without a faultCode) |
198
+ | `unknownInstance` | -2 | Invalid device | address not known to this interface |
199
+ | `unknownParamset` | -2 | Invalid device | the device has no such paramset |
200
+ | `unknownLink` | -2 | Invalid device | the two channels are not linked |
201
+ | `unknownParameter` | -5 | Unknown Parameter for value key | the paramset has no such parameter |
202
+ | `readOnly` | -5 | Invalid parameter or value | `OPERATIONS & 2` is not set |
203
+ | `typeError` | -5 | Invalid parameter or value | value does not fit the parameter's `TYPE` |
204
+ | `outOfRange` | -5 | Invalid parameter or value | ENUM value not in `VALUE_LIST` |
205
+ | `invalidValue` | -5 | Invalid parameter or value | the stored channel configuration cannot be transferred |
206
+ | `notSupported` | -1 | Generic error | a method this interface does not implement |
207
+ | `notReachable` | -1 | Generic error (UNREACH) | a sleeping battery device |
208
+ | `invalidArguments` | -321 | Invalid arguments | wrong number of arguments; hmipserver really answers a Java exception message here |
209
+
210
+ rfd's table is exported as `BIDCOS_FAULTS` and differs in more than wording - it answers **no
211
+ fault at all** for an unknown paramset name (it takes the name as a peer address), for a missing
212
+ argument, and for a `setValue` on a read-only datapoint:
213
+
214
+ ```js
215
+ const {FAULT_TABLES} = require('hm-simulator/lib/faults.js');
216
+ new HmSim({faults: FAULT_TABLES.bidcos});
217
+ ```
218
+
219
+ Every entry can still be replaced individually:
220
+
221
+ ```js
222
+ new HmSim({faults: {unknownParameter: {faultCode: -4, faultString: 'Unknown parameter'}}});
223
+ ```
224
+
225
+ ## Fixtures
226
+
227
+ `data/devices-rfd.json` and `data/devices-hmip.json` (the historical device lists) and
228
+ `data/paramset-descriptions.json` (1855 descriptions) ship with the package and are unchanged.
229
+
230
+ `data/fixtures/devices.json` is generated from node-red-contrib-ccu's `paramsets.json` and contains
231
+ real descriptions for HmIP-PDT, HmIPW-DRS8, HmIPW-DRI16, HmIPW-DRAP, HM-LC-Sw1-Pl and HM-CC-RT-DN:
232
+
233
+ ```js
234
+ const fixture = require('hm-simulator/data/fixtures/devices.json');
235
+ const sim = new HmSim({devices: fixture.devices, paramsetDescriptions: fixture.paramsetDescriptions});
236
+ ```
237
+
238
+ Regenerate or extend it with
239
+
240
+ ```
241
+ node tools/fixtures-from-paramsets.js --source ../node-red-contrib-ccu/paramsets.json \
242
+ --types HmIP-PDT,HM-LC-Sw1-Pl --out data/fixtures/devices.json
243
+ node tools/fixtures-from-paramsets.js --source ../node-red-contrib-ccu/paramsets.json --list
244
+ ```
245
+
246
+ A paramset description dump knows the channel _types_ of a device but never their _indexes_, so
247
+ the channel layout comes from `tools/device-layouts.json`. An entry with a `channels` list is the
248
+ real layout of the hardware, read from a `listDevices` dump; the seven device types of the
249
+ Homematic Manager lab (HmIP-PDT, HmIP-WRC2, HmIPW-DRS8, HmIPW-DRI16, HmIPW-DRAP, HM-CC-TC,
250
+ HM-Sec-SC) have one since 2026-09-05. Everything else is synthesised from `order` and `counts`:
251
+ consistent within a fixture (`CHILDREN`, `PARENT` and `INDEX` always agree), not guaranteed to
252
+ match the hardware. Correcting an entry from a `listDevices` dump is a one line change there.
253
+
254
+ `data/fixtures/lab-devices.json` is such a dump: `listDevices` as two CCUs on firmware 3.89.8
255
+ answered it, with the serials anonymised and nothing else touched. It has no paramset
256
+ descriptions - pass the ones from `devices.json` or the bundled set alongside:
257
+
258
+ ```js
259
+ const lab = require('hm-simulator/data/fixtures/lab-devices.json');
260
+ const fixture = require('hm-simulator/data/fixtures/devices.json');
261
+ const sim = new HmSim({devices: lab.devices, paramsetDescriptions: fixture.paramsetDescriptions});
262
+ ```
263
+
264
+ It is also where the oddities live that only real hardware produces - the CCU's own `HmIP-RCV-50`
265
+ sends a trailing **empty string** in `CHILDREN`, for instance.
266
+
267
+ ## Command line
268
+
269
+ ```
270
+ hm-simulator [options]
271
+
272
+ --listen-address <ip> default 127.0.0.1
273
+ --binrpc-port <port> rfd, default 2001
274
+ --xmlrpc-port <port> hmipserver, default 2010
275
+ --virtual-port <port> VirtualDevices, off unless given
276
+ --cuxd-port <port> CUxD, off unless given
277
+ --rega-port <port> ReGa mock, default 8181
278
+ --no-rega do not start the ReGa mock
279
+ -v, --verbosity <level> error|warn|info|debug, default info
280
+ ```
281
+
282
+ ## Compatibility
283
+
284
+ - `require('hm-simulator/sim')` and `require('hm-simulator/sim.js')` keep returning the `HmSim`
285
+ class; `sim.mjs` is the ESM entry point.
286
+ - The constructor options `config.listenAddress`, `config.binrpcListenPort`,
287
+ `config.xmlrpcListenPort`, `devices`, `log`, `behaviorPath` and `rega` are unchanged, and so are
288
+ `sim.api`, `sim.regaSim`, `sim.values`, `sim.rfdServer`, `sim.hmipServer` and `sim.close()`.
289
+ - Everything new is off by default: no additional server starts, no TLS, no basic auth.
290
+ - `configPendingMode` **is** on by default, though: since the lab measurement of 2026-09-05 the
291
+ `hmip` interface defaults to `'hmip'` and `rfd`/`wired` to `'bidcos'`, so an application is
292
+ tested against what the interface processes really do rather than against a guess. Pass
293
+ `interfaces: {rfd: {configPendingMode: 'strict'}}` for the old behaviour.
294
+ - The fault codes changed with the same measurement (`-3` -> `-2`, `-4`/`-6`/`-7` -> `-5`, and the
295
+ strings with them). A test that asserts a code has to be updated; `FAULT_TABLES` and the
296
+ `faults` option restore any other table.
297
+ - Calls that used to be answered with an empty string because they had no handler now do something:
298
+ `getParamset`, `putParamset`, `getLinks`, `getServiceMessages`, `listBidcosInterfaces`, `rssiInfo`
299
+ and the rest of the table above. An unknown method now answers a fault instead of an empty string.
300
+ - Node >= 20.19. `binrpc` 4 and `homematic-xmlrpc` 2 are the only runtime dependencies.
301
+
302
+ ## Changelog
303
+
304
+ ### 1.1.0
305
+
306
+ - `setTempKey` on the BidCos interfaces: the passphrase a device is taught in with, applied to
307
+ the pairings that follow; `sim.getTempKey(iface)` reads back what was set, an empty string
308
+ clears it. No cryptography. hmipserver has no such method, so it faults with unknown-method
309
+ there (Homematic Manager issue #20).
310
+ - Smoke detector teams on the BidCos interfaces: `listTeams` and `setTeam`. A channel with a
311
+ `TEAM_TAG` is in the team its `TEAM` names, a pseudo device `*<serial>` whose team channel lists
312
+ the members in `TEAM_CHANNELS`; `setTeam(channel, team)` moves it and deletes a team nobody is
313
+ left in, `setTeam(channel, '')` puts it back into a team of its own, created like the one it
314
+ left (`newDevices`, `deleteDevices`, `updateDevice` to the logic layers). The shape is rfd's
315
+ as Homematic Manager task 58 read it from a CCU3; when rfd deletes and creates the team devices
316
+ is the simulator's model, not a measurement.
317
+
318
+ ### 1.0.0
319
+
320
+ - Node >= 20.19, `binrpc` ^4.2, `homematic-xmlrpc` ^2.0; `express`, `body-parser`, `request`,
321
+ `async` and `yalm` removed. The ReGa mock runs on `node:http`, the CLI logs through `console`.
322
+ - Paramset state per device and channel for MASTER, VALUES, SERVICE and the link paramsets, with
323
+ type, range, `VALUE_LIST` and `OPERATIONS` checks on every write.
324
+ - `CONFIG_PENDING` semantics, configurable per interface: `'hmip'` and `'bidcos'` are what the
325
+ two interface processes of a CCU on firmware 3.89.8 were measured to do (Homematic Manager task
326
+ 6, 2026-09-05), `'strict'` and `'pending'` are the two hypotheses that measurement replaced.
327
+ `getPoisonedChannels()` for the channel an unknown parameter destroyed.
328
+ - Fault codes and strings measured on the same firmware, hmipserver's as the default table and
329
+ rfd's as `BIDCOS_FAULTS`.
330
+ - `data/fixtures/lab-devices.json`: an anonymised real `listDevices` of two CCUs, and the channel
331
+ layouts in `tools/device-layouts.json` corrected from it.
332
+ - Links: `getLinks`, `getLinkPeers`, `getLinkInfo`, `setLinkInfo`, `addLink`, `removeLink`,
333
+ `activateLinkParamset`, and link paramsets addressed by the peer's address.
334
+ - Interface and service methods: `rssiInfo`, `listBidcosInterfaces`, `setBidcosInterface`,
335
+ `getServiceMessages`, `setInstallMode`/`getInstallMode` with scripted `newDevices`,
336
+ `deleteDevice`, `replaceDevice`, `reportValueUsage`, `updateFirmware`, `installFirmware`,
337
+ `clearConfigCache`, `restoreConfigToDevice`, `determineParameter`, `getDeviceDescription`,
338
+ `getValue`, `system.methodHelp`, `system.multicall`.
339
+ - Fault responses on both transports, with an overridable fault table.
340
+ - VirtualDevices and CUxD servers, BidCos-Wired, optional TLS with a generated certificate and
341
+ optional HTTP basic auth.
342
+ - ReGa mock with `getChannels`, `getValues`, rename/program scripts and a script log.
343
+ - Scenario API: `addDevice`, `removeDevice`, `fireEvent`, `setServiceMessage`, `scriptNewDevices`,
344
+ `dropConnection`, `getWriteLog`, `getConfigPending`.
345
+ - `whenReady()`, port `0` support and `sim.ports`.
346
+ - Fixture generator `tools/fixtures-from-paramsets.js` and generated real device fixtures.
347
+ - ESLint 9 + Prettier, `node --test`, GitHub Actions on Node 20/22/24.
348
+
349
+ ### 0.1.1 and earlier
350
+
351
+ `init`, `ping`, `system.listMethods`, `getParamsetDescription`, `listDevices`, `setValue`, events
352
+ through behaviour scripts, ReGa mock.
74
353
 
75
354
  ## Contributing
76
355
 
77
- Pull Request welcome! :-)
78
-
356
+ Pull requests welcome. `npm run lint` and `npm test` have to pass.
79
357
 
80
358
  ## License
81
359
 
82
- MIT
360
+ MIT
83
361
  Copyright (c) 2017 Sebastian Raff
84
362
 
85
363
  [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat
@@ -1,4 +1,4 @@
1
- module.exports = api => {
1
+ module.exports = (api) => {
2
2
  setInterval(() => {
3
3
  api.emit('setValue', 'rfd', 'BidCoS-RF:1', 'PRESS_SHORT', true);
4
4
  }, 5000);
@@ -1,4 +1,4 @@
1
- module.exports = api => {
1
+ module.exports = (api) => {
2
2
  setInterval(() => {
3
3
  api.emit('setValue', 'hmip', '0000D3C98C9233:1', 'STATE', true);
4
4
  setTimeout(() => {