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