matterbridge-daikin-local 0.4.0 → 0.6.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/CHANGELOG.md +27 -0
- package/README.md +76 -41
- package/apps/frontend/build/app.js +1 -1
- package/apps/frontend/build/app.js.map +3 -3
- package/dist/accessory/daikinAccessory.js +28 -11
- package/dist/accessory/endpoints.js +21 -15
- package/dist/accessory/layout.js +12 -1
- package/dist/accessory/matterState.js +15 -8
- package/dist/config.js +14 -7
- package/dist/controls.js +9 -4
- package/dist/mapping.js +12 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.0] - 2026-10-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- [devices]: Add the option to make the auto fan speed the first or the last step of the fan speed, for Matter apps that show no auto fan mode. First is step 1 or the bottom of the slider, last is the top step or 100 %, and the speeds of the unit share the rest.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- [devices]: A Matter app turning the fan to off or 0 sets the first step of the fan speed, the slowest speed or auto when auto is the first step, instead of being ignored. Every Matter fan control has an off position below its first step, and an air conditioner has no fan-off.
|
|
16
|
+
- [setup]: Choose where auto is in Matter in one place in the setup page: the native Auto fan mode, a switch beside it, or the first or the last step of the fan speed in place of it. The Apple Home and Google Home presets pick the switch.
|
|
17
|
+
- [setup]: Choose how the dry and fan modes appear in one place in the setup page: the native Matter modes of the air conditioner only, a mode select, or a switch for each picked mode.
|
|
18
|
+
- [config]: **Breaking:** the `hasDryModeSwitch`, `hasFanModeSwitch` and `hasModeSelect` settings of a stored device are replaced by `modeControl`, which is `native`, `select` or `switches`, and `modeSwitches`, which lists `dehumidify`, `fan` or both while `modeControl` is `switches`. A device stored with a mode switch or a mode select comes back with the native modes only: choose them again in the setup page.
|
|
19
|
+
- [config]: **Breaking:** the `hasFanAutoSwitch` setting of a stored device is replaced by `fanAutoControl`, which is `native`, `switch`, `firstStep` or `lastStep`. A device stored with the auto fan switch comes back with the native Auto fan mode instead: choose the switch again in the setup page.
|
|
20
|
+
- [setup]: Make the parts of the air conditioner a choice of the presets. The Full Matter support preset makes the switches, a fan of its own and a humidity sensor of its own parts of one composed device, and the Apple Home and Google Home presets make each of them a device of its own, since neither app is known to show the parts of a composed device well. A preset now shows as chosen only while that choice matches too.
|
|
21
|
+
- [setup]: Replace the Full and Minimal presets with a Full Matter support preset, for apps that show every Matter control: fan speed as steps with its auto mode, the swing as a mode select, a switch for each special mode and the outdoor temperature sensor, all as one composed device. The Apple Home and Google Home presets no longer add the dry and fan mode switches, and the Google Home preset adds the humidity sensor of its own, as the Apple Home preset does. Both leave the swing out of Matter and as it is, instead of adding a switch for each axis.
|
|
22
|
+
|
|
23
|
+
## [0.5.0] - 2026-10-14
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- [devices]: Add the option of a mode select for the mode instead of the dry and fan mode switches, which picks Off or any mode of the unit as the thermostat mode does, on a unit with a dry or a fan mode. The swing can be a mode select instead of the switches too. No preset adds them: few Matter apps show a mode select. Home Assistant does, Apple Home does not.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- [sensors]: Keep the humidity on the air conditioner when it is also on a humidity sensor of its own, as the room temperature is, so that the sensor is an addition for the Matter apps that show no humidity on an air conditioner.
|
|
32
|
+
- [sensors]: Make the outdoor temperature sensor a device of its own even when the air conditioner is a composed device, so that it can be put in another room of a Matter app instead of reporting the outdoor temperature for the room of the air conditioner. On an air conditioner that had the sensor as a part, the sensor comes back as a new device.
|
|
33
|
+
|
|
7
34
|
## [0.4.0] - 2026-10-14
|
|
8
35
|
|
|
9
36
|
### Added
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Afterwards the same page lists your air conditioners with their status. From the
|
|
|
80
80
|
</tr>
|
|
81
81
|
<tr>
|
|
82
82
|
<td valign="top">
|
|
83
|
-
<img src="screenshots/options-presets-fan.png" alt="The Options step with the
|
|
83
|
+
<img src="screenshots/options-presets-fan.png" alt="The Options step with the presets and the three ways to show the fan speed">
|
|
84
84
|
<br><b>Options.</b> Start from a preset, then choose how the fan speed appears.
|
|
85
85
|
</td>
|
|
86
86
|
<td valign="top">
|
|
@@ -102,7 +102,7 @@ The plugin presents itself to every adapter with a **Terminal ID**, which it gen
|
|
|
102
102
|
| Thermostat mode | Off, Cool, Heat, Auto, Dry (dehumidify) and Fan only, for the modes the unit has. Not every Matter app offers Dry and Fan only |
|
|
103
103
|
| Target temperature | The setpoint of the current mode, within the limits and the step the unit reports. In Auto the unit has one setpoint, which both the heating and the cooling setpoint show |
|
|
104
104
|
| Current temperature | The indoor temperature the unit measures |
|
|
105
|
-
| Humidity | The indoor humidity, on the air conditioner
|
|
105
|
+
| Humidity | The indoor humidity, on the air conditioner and, when you add one, on a sensor of its own, for a unit that gives it: see below |
|
|
106
106
|
| Outdoor temperature | A temperature sensor named `<name> Outdoor Temperature`, when you turn it on, for a unit that gives it: see below |
|
|
107
107
|
| Fan speed | Your choice: see below |
|
|
108
108
|
| Swing | Your choice: see below |
|
|
@@ -111,26 +111,28 @@ The adapter does not report whether the compressor is running, so a unit that is
|
|
|
111
111
|
|
|
112
112
|
### How it is built in Matter
|
|
113
113
|
|
|
114
|
-
Matter describes a device as endpoints. Each endpoint has one or more device types, which say what it is, and clusters, which hold its attributes and commands. Each air conditioner is one device: an endpoint for the air conditioner, with every control or sensor that has no place on it as a part of it, on an endpoint of its own:
|
|
114
|
+
Matter describes a device as endpoints. Each endpoint has one or more device types, which say what it is, and clusters, which hold its attributes and commands. Each air conditioner is one device: an endpoint for the air conditioner, with every control or sensor that has no place on it as a part of it, on an endpoint of its own. The outdoor temperature sensor is the exception, and is always a device of its own:
|
|
115
115
|
|
|
116
116
|
```mermaid
|
|
117
117
|
flowchart TD
|
|
118
118
|
bridge["Matterbridge<br/>Aggregator"]
|
|
119
119
|
subgraph device["One device in a Matter app"]
|
|
120
|
-
ac["<b>Living Room</b><br/>Room Air Conditioner, Bridged Node, Power Source<br/>On/Off, Thermostat<br/>
|
|
120
|
+
ac["<b>Living Room</b><br/>Room Air Conditioner, Bridged Node, Power Source<br/>On/Off, Thermostat<br/>Fan Control when it is here, and Relative Humidity once the unit gives it"]
|
|
121
121
|
fan["Fan<br/>Fan Control"]
|
|
122
122
|
switches["On/Off Plug-in Unit, one for each switch<br/>tagged 1 Vertical Swing, 2 Horizontal Swing and so on<br/>On/Off"]
|
|
123
|
+
selects["Mode Select, one for the mode and one for the swing<br/>tagged 1 Mode, 2 Swing<br/>Mode Select"]
|
|
123
124
|
humidity["Humidity Sensor<br/>Relative Humidity"]
|
|
124
|
-
outdoor["Temperature Sensor, tagged Outdoor<br/>Temperature Measurement"]
|
|
125
125
|
end
|
|
126
|
+
outdoor["<b>Living Room Outdoor Temperature</b><br/>Temperature Sensor, Bridged Node, Power Source<br/>Temperature Measurement"]
|
|
126
127
|
bridge --> ac
|
|
127
128
|
ac -.-> fan
|
|
128
129
|
ac -.-> switches
|
|
130
|
+
ac -.-> selects
|
|
129
131
|
ac -.-> humidity
|
|
130
|
-
|
|
132
|
+
bridge -.-> outdoor
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
A solid line is always there. A dashed one is there when you choose it in the setup, and for a sensor once the unit gives its reading. The air conditioner also has the Bridged Node and Power Source device types, with the Identify, Bridged Device Basic Information and Power Source clusters: the name and the serial number of the device, whether it is reachable, and that it runs on mains power. Its parts have none of these, because in Matter they belong to the device.
|
|
135
|
+
A solid line is always there. A dashed one is there when you choose it in the setup, and for a sensor once the unit gives its reading. The air conditioner also has the Bridged Node and Power Source device types, with the Identify, Bridged Device Basic Information and Power Source clusters: the name and the serial number of the device, whether it is reachable, and that it runs on mains power. Its parts have none of these, because in Matter they belong to the device. The outdoor temperature sensor has them all, with a name and a serial number of its own, so that you can put it in another room of your Matter app, such as outside, and it does not report the outdoor temperature for the room of the air conditioner.
|
|
134
136
|
|
|
135
137
|
| Device type | ID | Stands for |
|
|
136
138
|
| -------------------- | ------ | ----------------------------------------------------- |
|
|
@@ -140,24 +142,25 @@ A solid line is always there. A dashed one is there when you choose it in the se
|
|
|
140
142
|
| Room Air Conditioner | 0x0072 | The air conditioner |
|
|
141
143
|
| Fan | 0x002B | A fan of its own |
|
|
142
144
|
| On/Off Plug-in Unit | 0x010A | Each switch |
|
|
145
|
+
| Mode Select | 0x0027 | The mode select of the mode, and that of the swing |
|
|
143
146
|
| Humidity Sensor | 0x0307 | A humidity sensor of its own |
|
|
144
147
|
| Temperature Sensor | 0x0302 | The outdoor temperature |
|
|
145
148
|
|
|
146
|
-
The setup can also make each part a device of its own: see [One device or several](#one-device-or-several).
|
|
149
|
+
The setup can also make each part a device of its own: see [One device or several](#one-device-or-several). The names below, such as `<name> Fan`, are those of the parts as devices of their own. As parts they have no name in Matter: a switch or a mode select is tagged with what it is, such as `Vertical Swing`, and a Matter app shows each part in its own way.
|
|
147
150
|
|
|
148
151
|
### Room temperature, humidity and outdoor temperature
|
|
149
152
|
|
|
150
|
-
The room temperature
|
|
153
|
+
The room temperature and the humidity are always on the air conditioner. You choose whether the humidity also gets a sensor of its own, and whether the outdoor temperature gets a sensor, and each appears once the unit gives it.
|
|
151
154
|
|
|
152
|
-
| Reading | In Matter
|
|
153
|
-
| ------------------- |
|
|
154
|
-
| Humidity | On the air conditioner, beside the room temperature
|
|
155
|
-
| Outdoor temperature | None, unless you turn on a temperature sensor named `<name> Outdoor Temperature`. It is
|
|
155
|
+
| Reading | In Matter |
|
|
156
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| Humidity | On the air conditioner, beside the room temperature. You can add a humidity sensor of its own, named `<name> Humidity`, for Matter apps that show no humidity on an air conditioner. It shows the same humidity, which stays on the air conditioner for the apps that do |
|
|
158
|
+
| Outdoor temperature | None, unless you turn on a temperature sensor named `<name> Outdoor Temperature`. It is a device of its own, never on the air conditioner or a part of it, so that you can put it in another room, such as outside. It is off by default because a Matter app puts a new device in a room of its choosing, which may show the outdoor temperature as its own until you move the sensor |
|
|
156
159
|
|
|
157
160
|
- **No adapter says which readings its unit has**, so the plugin goes by what the unit reads. The humidity and the outdoor temperature get their place in Matter the first time the unit gives them: at once for a unit that gave them during setup, and otherwise the air conditioner is built again in Matter when the reading first comes. An outdoor temperature with its sensor turned off builds nothing.
|
|
158
161
|
- **A reading keeps its place from then on.** Some units give no outdoor temperature while they are idle, and a sensor that came and went would be a device that comes and goes in your Matter apps.
|
|
159
162
|
- **A reading the unit does not give right now shows no value**, instead of the last one it gave. This holds for all three, the room temperature included.
|
|
160
|
-
- **Apple Home is reported to show a humidity only on a sensor of its own**, and not on an air conditioner: see the [Home Assistant Matter Hub notes](https://riddix.github.io/home-assistant-matter-hub/devices/temperature-humidity-sensor). The Apple Home preset
|
|
163
|
+
- **Apple Home is reported to show a humidity only on a sensor of its own**, and not on an air conditioner: see the [Home Assistant Matter Hub notes](https://riddix.github.io/home-assistant-matter-hub/devices/temperature-humidity-sensor). The Apple Home preset adds that sensor for that reason. It has not been tested with this plugin.
|
|
161
164
|
|
|
162
165
|
### Fan speed
|
|
163
166
|
|
|
@@ -167,24 +170,44 @@ The room temperature is always on the air conditioner. You choose where the othe
|
|
|
167
170
|
| Percentage | A slider from 0 to 100. The plugin rounds it to the nearest speed of the unit and shows the percentage that speed stands for |
|
|
168
171
|
| Not shown | No fan control. The fan speed either stays as it is, or is set to the speed you pick each time the plugin sends a command |
|
|
169
172
|
|
|
170
|
-
An air conditioner has no fan-off of its own, so a Matter app turning the fan to off or 0
|
|
173
|
+
Every Matter fan control has an off position below its first step: speed 0, 0 %, or the fan mode Off. An air conditioner has no fan-off of its own, so a Matter app turning the fan to off or 0 sets the first step instead: the slowest speed, or auto when auto is the first step. To let the unit pick the speed, choose auto, wherever the setup puts it: see [Auto fan speed](#auto-fan-speed).
|
|
174
|
+
|
|
175
|
+
The fan speed is a control of the air conditioner itself. For Matter apps that show no fan speed on an air conditioner, or do not show it well, the setup can move it to a fan of its own instead, named `<name> Fan`, as steps or as a percentage. The air conditioner then has no fan speed, so an app does not show two. No preset does this. The fan behaves the same in either place: it shows the speed the unit holds, also while the unit is off, and turning it off sets the first step.
|
|
176
|
+
|
|
177
|
+
#### Auto fan speed
|
|
171
178
|
|
|
172
|
-
|
|
179
|
+
On a unit that has an auto fan speed, the setup puts auto in one of these places in Matter:
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
| Option | In Matter |
|
|
182
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
183
|
+
| Native fan mode only | The Auto fan mode of the fan speed, as Matter has it. Not every app shows it |
|
|
184
|
+
| A switch | The Auto fan mode, and a switch named `<name> Auto Fan` beside it. On sets the fan to auto. Off puts it back at the speed it last ran at, or at a middle speed when the plugin never saw it at one |
|
|
185
|
+
| The first step | Below the slowest speed of the unit: step 1, or the bottom of the slider |
|
|
186
|
+
| The last step | Above the fastest speed of the unit: the top step, or 100 % |
|
|
187
|
+
|
|
188
|
+
A switch or a step is for Matter apps that show no Auto fan mode. A step takes the place of the Auto fan mode, because the two cannot show at once: in the Auto fan mode, Matter holds no percent. As a step, auto shares the scale with the speeds of the unit, so a unit with six speeds gets seven steps. Off and 0 set the first step, as above, so with auto first, turning the fan off sets it to auto. While the fan speed is hidden, only the switch is offered, and not even that with a fixed speed, because that speed is sent with every command.
|
|
175
189
|
|
|
176
190
|
### Swing
|
|
177
191
|
|
|
178
|
-
| Option
|
|
179
|
-
|
|
|
180
|
-
| Switches
|
|
181
|
-
|
|
|
192
|
+
| Option | In Matter |
|
|
193
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| Switches | One on/off switch for each axis the unit swings on, named `<name> Vertical Swing` and `<name> Horizontal Swing`. A unit that swings on one axis gets one switch. They appear as outlets, the on/off device type every Matter app supports |
|
|
195
|
+
| Mode select | One mode select named `<name> Swing`, with Off, Vertical, Horizontal and Both for the swing modes the unit has. Few Matter apps show it: see [Mode selects](#mode-selects) |
|
|
196
|
+
| Not shown | No swing control. The swing either stays as it is, or is set to the mode you pick each time the plugin sends a command |
|
|
182
197
|
|
|
183
198
|
Fixed louver positions are not controlled from Matter.
|
|
184
199
|
|
|
185
200
|
### Dry and fan modes
|
|
186
201
|
|
|
187
|
-
Dry and Fan only are always among the
|
|
202
|
+
Dry and Fan only are always among the native Matter modes of the air conditioner, but some Matter apps, such as Apple Home, only offer Off, Cool, Heat and Auto. For those apps the setup can add one of these:
|
|
203
|
+
|
|
204
|
+
| Option | In Matter |
|
|
205
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| Native modes only | Nothing more: Dry and Fan only are among the modes of the air conditioner. The default |
|
|
207
|
+
| A mode select | A mode select named `<name> Mode`, with every mode of the unit: see [Mode selects](#mode-selects) |
|
|
208
|
+
| Switches | A switch named `<name> Dry Mode`, `<name> Fan Mode` or both, for whichever of the two modes the unit has that you pick |
|
|
209
|
+
|
|
210
|
+
A switch does this:
|
|
188
211
|
|
|
189
212
|
| Switch | Does |
|
|
190
213
|
| ---------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -193,6 +216,19 @@ Dry and Fan only are always among the thermostat modes of the air conditioner, b
|
|
|
193
216
|
|
|
194
217
|
A switch is on while the unit is on and in its mode, whichever way it got there.
|
|
195
218
|
|
|
219
|
+
### Mode selects
|
|
220
|
+
|
|
221
|
+
A mode select is the Matter device type that picks one value from a list, as a drop-down does. The setup can add one for the mode, named `<name> Mode`, on a unit with a dry or a fan mode, and one for the swing, named `<name> Swing`, as a choice under [Swing](#swing). Each lists only what the unit has, and is chosen instead of the switches: the mode select under [Dry and fan modes](#dry-and-fan-modes), the swing select under [Swing](#swing). The Full Matter support preset adds the swing select, and no preset adds the mode select.
|
|
222
|
+
|
|
223
|
+
| Mode select | Lists | Picking a value |
|
|
224
|
+
| ----------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| Mode | Off, Auto, Cool, Heat, Dry and Fan Only, as the thermostat mode has them | Off turns the unit off, and any other mode turns it on in that mode. The select shows Off while the unit is off, as the thermostat mode does |
|
|
226
|
+
| Swing | Off, Vertical, Horizontal and Both | Sets the swing |
|
|
227
|
+
|
|
228
|
+
**Few Matter apps show a mode select.** Home Assistant shows each one as a select among the settings of the device. Apple Home is reported not to show them, and Google Home, Amazon Alexa and SmartThings do not list them among the devices they support. This has not been tested with this plugin. In the other apps, the switches are the way to reach these controls.
|
|
229
|
+
|
|
230
|
+
The thermostat mode of the air conditioner follows the unit, whichever control changed it: picking Dry or Fan Only on the mode select shows Dry or Fan Only there, and Auto on a unit that only cools or only heats shows Cool or Heat, because Matter has Auto only with both. How an app without Dry or Fan Only, such as Apple Home, shows the air conditioner in those modes is up to the app, as it is with the switches.
|
|
231
|
+
|
|
196
232
|
### Special modes
|
|
197
233
|
|
|
198
234
|
Matter has no control for Powerful, Econo and Streamer, so the setup can add a switch for each one the unit reports, named `<name> Powerful`, `<name> Econo` and `<name> Streamer`. None is added unless you ask for it.
|
|
@@ -204,44 +240,43 @@ Matter has no control for Powerful, Econo and Streamer, so the setup can add a s
|
|
|
204
240
|
|
|
205
241
|
In the Options step, a preset fills in every choice above for what the unit can do. You can change any of them afterwards.
|
|
206
242
|
|
|
207
|
-
| Preset
|
|
208
|
-
|
|
|
209
|
-
| Apple Home
|
|
210
|
-
| Google Home
|
|
211
|
-
| Full
|
|
212
|
-
| Minimal | Not shown | Not shown | No | No | No | None | On the air conditioner | No |
|
|
213
|
-
|
|
214
|
-
The Apple Home and Google Home presets are starting points, not tested recommendations. They add switches and sensors for what each app is not known to show: Home's climate controls have Off, Heat, Cool and Auto only and are reported to show no humidity, and Google Home is reported to show Fan only but not Dry.
|
|
243
|
+
| Preset | Fan speed | Swing | Dry and fan modes | Auto fan | Special mode switches | Humidity | Outdoor sensor | Parts of the air conditioner |
|
|
244
|
+
| ------------------- | ---------- | ------------- | ----------------- | -------- | --------------------- | --------------------------- | -------------- | ---------------------------- |
|
|
245
|
+
| Apple Home | Percentage | Not shown | Native modes only | Switch | None | Also on a sensor of its own | No | No |
|
|
246
|
+
| Google Home | Steps | Not shown | Native modes only | Switch | None | Also on a sensor of its own | No | No |
|
|
247
|
+
| Full Matter support | Steps | A mode select | Native modes only | Native | All | On the air conditioner | Yes | Yes |
|
|
215
248
|
|
|
216
249
|
### One device or several
|
|
217
250
|
|
|
218
|
-
The switches, a fan of its own and
|
|
251
|
+
The switches, the mode selects, a fan of its own and a humidity sensor of its own are parts of the air conditioner, which Matter calls a composed device, so they move between rooms with it. The outdoor temperature sensor is a device of its own either way, so that you can put it in another room than the air conditioner. The setup can make each of them a device of its own instead, beside the air conditioner. A Matter app then does not know that they belong together, so each is put in a room by itself: moving the air conditioner to another room leaves them where they were. A new device starts as one composed device. The Full Matter support preset keeps it one, and the Apple Home and Google Home presets make each part a device of its own: see [Presets](#presets).
|
|
219
252
|
|
|
220
253
|
```mermaid
|
|
221
254
|
flowchart TD
|
|
222
255
|
bridge["Matterbridge<br/>Aggregator"]
|
|
223
|
-
ac["<b>Living Room</b><br/>Room Air Conditioner<br/>On/Off, Thermostat<br/>
|
|
256
|
+
ac["<b>Living Room</b><br/>Room Air Conditioner<br/>On/Off, Thermostat<br/>Fan Control when it is here, and Relative Humidity once the unit gives it"]
|
|
224
257
|
fan["<b>Living Room Fan</b><br/>Fan<br/>Fan Control"]
|
|
225
258
|
switches["<b>Living Room Vertical Swing</b>, and the other switches<br/>On/Off Plug-in Unit, one for each switch<br/>On/Off"]
|
|
259
|
+
selects["<b>Living Room Mode</b> and <b>Living Room Swing</b><br/>Mode Select, one for each<br/>Mode Select"]
|
|
226
260
|
humidity["<b>Living Room Humidity</b><br/>Humidity Sensor<br/>Relative Humidity"]
|
|
227
261
|
outdoor["<b>Living Room Outdoor Temperature</b><br/>Temperature Sensor<br/>Temperature Measurement"]
|
|
228
262
|
bridge --> ac
|
|
229
263
|
bridge -.-> fan
|
|
230
264
|
bridge -.-> switches
|
|
265
|
+
bridge -.-> selects
|
|
231
266
|
bridge -.-> humidity
|
|
232
267
|
bridge -.-> outdoor
|
|
233
268
|
```
|
|
234
269
|
|
|
235
|
-
| | Parts of the air conditioner
|
|
236
|
-
| --------------- |
|
|
237
|
-
| In a Matter app | One device
|
|
238
|
-
| Rooms | The parts move with the air conditioner
|
|
239
|
-
| Names | The air conditioner alone has one. Each switch is tagged with a number and with what it is, such as `Vertical Swing
|
|
240
|
-
| Unreachable | The air conditioner says so for all of them
|
|
270
|
+
| | Parts of the air conditioner | Devices of their own |
|
|
271
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
272
|
+
| In a Matter app | One device | One device for the air conditioner, and one for each of the others |
|
|
273
|
+
| Rooms | The parts move with the air conditioner | Each is put in a room by itself |
|
|
274
|
+
| Names | The air conditioner alone has one. Each switch and each mode select is tagged with a number and with what it is, such as `Vertical Swing` | Each has its own, such as `<name> Vertical Swing` |
|
|
275
|
+
| Unreachable | The air conditioner says so for all of them | Each says so itself |
|
|
241
276
|
|
|
242
277
|
Matter names a device, not its parts, so what a part is called is up to the Matter app. One that reads the tags can show `Vertical Swing`, one that does not shows numbered outlets to rename yourself, and one that shows no parts at all shows the air conditioner alone. This has not been tested in each Matter app: try it in yours, and turn it off if the parts do not show as you want.
|
|
243
278
|
|
|
244
|
-
Changing the choice builds the air conditioner again in Matter. The switches, the fan and the
|
|
279
|
+
Changing the choice builds the air conditioner again in Matter. The switches, the mode selects, the fan and the humidity sensor are removed and added in their new place, so a Matter app takes them for new devices: they lose the rooms, the names and the automations they had there.
|
|
245
280
|
|
|
246
281
|
## Compared with other Daikin plugins
|
|
247
282
|
|
|
@@ -256,9 +291,9 @@ This plugin is the only one of the four that finds the adapters itself, that spe
|
|
|
256
291
|
| Built on | [daikin-local-control](https://github.com/cheziyi/daikin-local-control), a library by the same author that works on its own | [daikin-controller](https://github.com/Apollon77/daikin-controller) | Its own DSIOT code | [daikin-controller-cloud](https://github.com/Apollon77/daikin-controller-cloud) |
|
|
257
292
|
| Adding a unit | A search of the network and a guided setup page, or by address. No restart | IP addresses in the plugin settings | IP addresses in the plugin settings, then a restart | Every unit of the account, with a white list and a black list |
|
|
258
293
|
| KEY and child lock code | Asked for in the setup page, by the adapters that need them | Not supported: it cannot connect to an adapter that wants its KEY or a child lock code | Not supported: it cannot connect to an adapter with a child lock code. DSIOT adapters have no KEY | Neither applies: it signs in to the Daikin cloud |
|
|
259
|
-
| Thermostat modes | Off, Cool, Heat, Auto, Dry and Fan only, and an optional switch for each of Dry and Fan only
|
|
294
|
+
| Thermostat modes | Off, Cool, Heat, Auto, Dry and Fan only, and an optional switch for each of Dry and Fan only or a mode select | Off, Cool, Heat, Auto, Dry and Fan only | Off, Cool, Heat, Auto and Fan only. Dry and humidify through a mode select | Off, Cool, Heat, Auto, Dry and Fan only |
|
|
260
295
|
| Fan speed | Steps, a percentage or not shown, on the air conditioner or on a fan of its own, and an optional switch for the auto speed | No | Low, Medium, High and Auto, a percentage, or a mode select | A fan device |
|
|
261
|
-
| Swing | A switch for each axis, or not shown with the mode you fix
|
|
296
|
+
| Swing | A switch for each axis, a mode select, or not shown with the mode you fix | No | The airflow direction of each axis, through a mode select | No |
|
|
262
297
|
| Powerful, Econo and Streamer | An optional switch for each | No | No | A switch for Powerful |
|
|
263
298
|
| Humidity | Shown on the air conditioner, when the unit gives it | No | Target humidity and ventilation as fans, and a humidity sensor | A humidity sensor, on units that report it |
|
|
264
299
|
| Outdoor temperature | A sensor you turn on, when the unit gives it | No | Yes | Yes |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
async function w(e,n,o){try{let[a,r]=e.split(" "),i={method:a};n!==void 0&&(i.headers={"Content-Type":"application/json"},i.body=JSON.stringify(n));let d=await fetch(new URL(`api/${r}${o===void 0?"":`?${o}`}`,document.baseURI),i);if(d.status===204)return{ok:!0};if(d.status===503)return{ok:!1,error:"The plugin is not running. Start it in Matterbridge, then reload this page."};let p=await d.json();return"ok"in p?p:{ok:!1,error:p.error??`The plugin answered with status ${d.status}`}}catch{return{ok:!1,error:"Matterbridge did not answer. Check that it is running, then try again."}}}function x(e){let{fanControl:n,fanSpeedWhenHidden:o,swingControl:a,swingModeWhenHidden:r,hasDryModeSwitch:i,hasFanModeSwitch:d,hasFanAutoSwitch:p,hasSeparateFan:h,specialModeSwitches:S,hasOutdoorTemperatureSensor:b,hasSeparateHumiditySensor:te,isComposed:ne}=e;return{fanControl:n,fanSpeedWhenHidden:o,swingControl:a,swingModeWhenHidden:r,hasDryModeSwitch:i,hasFanModeSwitch:d,hasFanAutoSwitch:p,hasSeparateFan:h,specialModeSwitches:[...S],hasOutdoorTemperatureSensor:b,hasSeparateHumiditySensor:te,isComposed:ne}}var oe={fanControl:"steps",fanSpeedWhenHidden:"keep",swingControl:"switches",swingModeWhenHidden:"keep",hasDryModeSwitch:!1,hasFanModeSwitch:!1,hasFanAutoSwitch:!1,hasSeparateFan:!1,specialModeSwitches:[],hasOutdoorTemperatureSensor:!1,hasSeparateHumiditySensor:!1,isComposed:!0};function I(e){return{adapter:e,name:e.name??"",isNew:!0,...x(oe)}}function q(e){let{capabilities:n,controls:o,name:a,macAddress:r,host:i,protocol:d,requiresRegistration:p,usesHttps:h,requiresLocalPassword:S}=e;return{adapter:{host:i,protocol:d,name:a,macAddress:r,requiresRegistration:p,usesHttps:h,requiresLocalPassword:S},name:a,isNew:!1,capabilities:n,controls:o,...x(e)}}var W={fanSteps:[],hasFanAuto:!1,hasSwing:!1,hasDryMode:!1,hasFanMode:!1,specialModes:[]};var s={screen:"loading",setup:void 0,found:[],selected:new Set,queue:[],saved:[],handled:[],isBusy:!1,busyText:"",error:"",confirmingRemoval:""};function f(){return s.queue[0]}function v(e){return e.name===""?e.adapter.host:e.name}function N(e){return(s.setup?.devices??[]).some(n=>n.macAddress===e.macAddress)}var P;function B(e){P=e}function l(){let e=document.getElementById("app");if(e===null||P===void 0)return;let n=`${s.screen}:${f()?.adapter.host??""}`,o=e.dataset.form===n,a=document.activeElement instanceof HTMLElement?document.activeElement.id:"",r=new Map;for(let i of e.querySelectorAll("input[type=text], input[type=password]"))i.id!==""&&r.set(i.id,i.value);if(e.replaceChildren(...P[s.screen]().filter(i=>i!==!1&&i!==null&&i!==void 0)),e.dataset.form=n,!!o){for(let[i,d]of r){let p=document.getElementById(i);p instanceof HTMLInputElement&&(p.value=d)}a!==""&&document.getElementById(a)?.focus()}}async function M(e,n){s.isBusy=!0,s.busyText=e,s.error="",l();try{return await n()}finally{s.isBusy=!1,l()}}async function E(){let e=await w("GET state");if(!e.ok){s.error=e.error,s.screen="home",l();return}if(s.setup=e.state,e.state.devices.length===0){await C();return}s.screen="home",l()}async function H(){let e=await w("GET state");e.ok&&(s.setup=e.state)}function g(){s.queue=[],s.saved=[],s.handled=[],s.error="",s.screen="home",H().then(l),l()}async function C(){s.screen="find";let e=await M("Searching your network. This takes about three seconds\u2026",async()=>w("POST discover"));if(!e.ok){s.error=`The search did not run: ${e.error}`,l();return}await H();let n=s.found.filter(o=>!e.devices.some(a=>a.host===o.host)&&s.selected.has(o.host)).map(o=>({...o,isConfigured:N(o)}));s.found=[...e.devices,...n];for(let o of e.devices)!o.isConfigured&&!o.hasDuplicateName&&s.selected.add(o.host);l()}async function K(e){let n=await M(`Looking for an adapter at ${e}\u2026`,async()=>w("POST identify",{host:e}));if(!n.ok){s.error=n.error,l();return}let o=N(n.device),a=n.device.requiresRegistration===!0;s.found=[...s.found.filter(r=>r.host!==n.device.host),{...n.device,isConfigured:o,hasDuplicateName:!1,needsRegistration:a}],o||s.selected.add(n.device.host),l()}function R(e){s.queue=e,s.saved=[],s.handled=[],O()}async function O(){let e=f();if(e===void 0){s.screen=s.saved.length>0?"done":"home",await H(),l();return}s.screen="connect",s.error="",e.probe=void 0,l(),await D(e)}async function D(e,n){let a={device:{...e.adapter,name:e.name===""?void 0:e.name},localPassword:e.localPassword,key:n},r=n===void 0?`Checking ${v(e)}\u2026`:`Registering with ${v(e)}\u2026`,i=await M(r,async()=>w(n===void 0?"POST probe":"POST register",a));if(!i.ok){s.error=i.error,l();return}if(e.probe=i.result,e.adapter=i.result.device,i.result.status==="ready"){e.capabilities=i.result.capabilities,e.controls=i.result.controls;let d=i.result.device.name??"";d!==""&&(e.name=d),e.name!==""&&(s.screen="options")}l()}function U(){let e=s.queue.shift();e!==void 0&&s.handled.push({label:v(e),isSaved:!1}),O()}async function Y(e){let{adapter:n,name:o,localPassword:a}=e,r={...n,name:o,localPassword:a,...x(e)},i=await M(`Saving ${o}\u2026`,async()=>w("POST device",{device:r}));if(!i.ok){s.error=i.error,l();return}s.setup=i.state,s.saved.push(o),s.handled.push({label:o,isSaved:!0}),s.queue.shift(),await O()}async function G(e){let n=await M(`Removing ${e.name}\u2026`,async()=>w("DELETE device",void 0,`macAddress=${encodeURIComponent(e.macAddress)}`));s.confirmingRemoval="",n.ok||(s.error=n.error),await H(),l()}function t(e,n={},...o){let a=document.createElement(e);for(let[r,i]of Object.entries(n))i===void 0||i===!1||(r==="class"?a.className=String(i):r==="html"?a.innerHTML=String(i):r.startsWith("on")&&typeof i=="function"?a.addEventListener(r.slice(2),i):r in a?a[r]=i:a.setAttribute(r,String(i)));for(let r of o)r!==!1&&r!==null&&r!==void 0&&a.append(r);return a}function c(e,n,o="quiet",a=!1){return t("button",{type:"button",class:`button ${o}`,onclick:n,disabled:a||s.isBusy},e)}function m(){return s.isBusy?t("p",{class:"notice working",role:"status"},t("span",{class:"spinner"}),s.busyText):s.error!==""?t("p",{class:"notice problem",role:"alert"},s.error):null}function L(){let e=f();if(e===void 0||!e.isNew||s.handled.length+s.queue.length<2)return null;let n=s.screen==="connect"?"Connecting":"Choosing options";return t("ol",{class:"units","aria-label":"Air conditioners in this setup"},...s.handled.map(o=>t("li",{class:o.isSaved?"done":"skipped"},t("strong",{},o.label),t("span",{class:"meta"},o.isSaved?"Added":"Skipped"))),...s.queue.map((o,a)=>t("li",{class:a===0?"current":"ahead","aria-current":a===0?"true":void 0},t("strong",{},v(o)),t("span",{class:"meta"},a===0?n:"Waiting"))))}function $(e,n){return t("header",{class:"unit-header"},t("h1",{},v(e)),t("p",{class:"meta"},n))}function k(){if(s.queue.length>0&&!s.queue.some(a=>a.isNew))return null;let e=["Find","Connect","Options"],o=["find","connect","options"].indexOf(s.screen);return t("ol",{class:"steps"},...e.map((a,r)=>{let i="ahead";return r<o?i="done":r===o&&(i="current"),t("li",{class:i,"aria-current":r===o?"step":void 0},a)}))}function se(e){let n=t("input",{type:"text",id:"device-name",maxLength:32,autocomplete:"off"}),o=()=>{let a=n.value.trim();if(a!==""){if(s.setup?.devices.some(r=>r.name===a)){s.error=`"${a}" is already the name of another air conditioner. Pick a different one.`,l();return}e.name=a,s.error="",s.screen="options",l()}};return t("div",{class:"field"},t("label",{for:"device-name"},"Name this air conditioner"),t("p",{class:"help"},"Its adapter has no name of its own. This is the name it gets in Matter, up to 32 characters. Name the adapter in its Daikin app later and the plugin takes that name instead."),t("div",{class:"inline-form"},n,c("Continue",o,"primary")))}function ie(e,n){let o=t("input",{type:"password",id:"lock-code",inputMode:"numeric",autocomplete:"off",maxLength:64});return t("div",{class:"field"},t("label",{for:"lock-code"},"Child lock code"),t("p",{class:"help"},"The 4-digit code set under Child lock in the Daikin app the adapter was set up with. It is not printed on the adapter."),n?t("p",{class:"problem-text"},"The adapter did not accept that code."):null,t("div",{class:"inline-form"},o,c("Check the code",()=>{o.value!==""&&(e.localPassword=o.value,D(e))},"primary")))}function ae(e,n){let o=t("input",{type:"text",id:"adapter-key",autocomplete:"off",spellcheck:!1,maxLength:64});return t("div",{class:"field"},t("label",{for:"adapter-key"},"KEY on the adapter"),t("p",{class:"help"},"This adapter only answers apps it has registered. The KEY is printed on its label, next to the SSID, and in the operation manual. It is used once and not stored."),n?t("p",{class:"problem-text"},"The adapter did not accept that KEY."):null,t("div",{class:"inline-form"},o,c("Register",()=>{o.value.trim()!==""&&D(e,o.value.trim())},"primary")))}function re(e){let n=e.probe?.status;return n==="ready"&&e.name===""?se(e):n==="localPasswordRequired"||n==="localPasswordRejected"?ie(e,n==="localPasswordRejected"):n==="registrationRequired"||n==="registrationRejected"?ae(e,n==="registrationRejected"):n==="unreachable"?t("p",{class:"notice problem",role:"alert"},`${e.name} did not answer at ${e.adapter.host}. Check that it has power and is on the network, then try again.`):n==="failed"?t("p",{class:"notice problem",role:"alert"},`${e.name} answered with an error: ${e.probe?.message??"unknown"}`):null}function V(){let e=f();if(e===void 0)return[m()];let n=e.probe?.status==="unreachable"||e.probe?.status==="failed"||s.error!==""&&e.name!=="";return[k(),L(),$(e,`Connecting to its adapter at ${e.adapter.host}.`),m(),s.isBusy?null:re(e),t("div",{class:"footer"},n?c("Try again",()=>{D(e)}):null,c(e.isNew&&s.queue.length>1?"Skip this one":"Cancel",e.isNew&&s.queue.length>1?U:g))]}function z(){let e=s.saved;return[t("header",{},t("h1",{},e.length===1?`${e[0]} is set up`:`${e.length} air conditioners are set up`)),t("p",{class:"lead"},"They now appear in the Matter apps paired with Matterbridge. If Matterbridge is not paired yet, pair it from its home page."),t("div",{class:"footer"},c("Done",g,"primary"))]}function le(e){let n=!e.isConfigured&&!e.hasDuplicateName,o=[];e.isConfigured&&o.push("Already set up."),e.hasDuplicateName&&o.push("Another adapter has the same name. Give each its own name in its Daikin app, then search again."),e.name===void 0&&o.push("It has not told its name yet. If it has none, you will name it after connecting."),n&&e.needsRegistration?o.push("Asks for the KEY printed on its label."):n&&e.requiresRegistration&&o.push("Already registered with this plugin, so it will not ask for its KEY."),n&&e.requiresLocalPassword&&o.push("Asks for its child lock code.");let a=t("input",{type:"checkbox",id:`found-${e.host}`,checked:n&&s.selected.has(e.host),disabled:!n,onchange:()=>{a.checked?s.selected.add(e.host):s.selected.delete(e.host),l()}});return t("li",{class:n?"found":"found unavailable"},t("label",{for:`found-${e.host}`},a,t("span",{},t("strong",{},e.name??"Unnamed adapter"),t("span",{class:"meta"},` ${e.host}`),...o.map(r=>t("span",{class:"meta note"},r)))))}function J(){let e=s.found.filter(r=>s.selected.has(r.host)&&!r.isConfigured&&!r.hasDuplicateName),n=t("input",{type:"text",id:"host",placeholder:"192.168.1.50",spellcheck:!1,autocomplete:"off"}),o=()=>{n.value.trim()!==""&&K(n.value.trim())};n.addEventListener("keydown",r=>{r.key==="Enter"&&o()});let a=e.length===1?"Set up 1 air conditioner":`Set up ${e.length} air conditioners`;return[k(),t("header",{},t("h1",{},"Find your air conditioners")),m(),s.found.length>0?t("ul",{class:"found-list"},...s.found.map(le)):null,!s.isBusy&&s.found.length===0?t("p",{class:"empty"},"No adapter answered. A search only reaches adapters on the same network segment as Matterbridge: add the others by address below."):null,t("details",{class:"manual",open:!s.isBusy&&s.found.length===0},t("summary",{},"Can't see your air conditioner?"),t("p",{class:"help"},"Enter the address of its adapter. The plugin reads the name of the adapter there and follows it to a new address like any other, as long as a search can reach it. An adapter on another subnet or VLAN keeps this address."),t("div",{class:"inline-form"},t("label",{for:"host",class:"visually-hidden"},"Adapter address"),n,c("Look it up",o))),t("div",{class:"footer"},c("Search again",()=>{C()}),c("Cancel",g),c(a,()=>R(e.map(I)),"primary",e.length===0))]}function ce(e){let n=e.controls===void 0||e.controls.fanSteps.length>0,o=e.controls===void 0||e.controls.hasSwing,a=e.hasSeparateFan?" on a fan of its own":"",r=e.fanControl==="hidden"?"fan speed not shown":`fan speed as ${e.fanControl==="steps"?"steps":"a percentage"}${a}`,i=e.swingControl==="hidden"?"swing not shown":"swing as switches",d=[n?r:"",o?i:""].filter(b=>b!=="").join(", "),p=[e.hasDryModeSwitch?"dry mode":"",e.hasFanModeSwitch?"fan mode":"",e.hasFanAutoSwitch?"auto fan":"",...e.specialModeSwitches].filter(b=>b!==""),h=p.length===0?"":`a switch for ${p.join(", ")}`,S=e.isComposed?", all as one device":"";return d===""?`${h===""?"power, mode and temperature":h}${S}`:`${h===""?d:`${d}, and ${h}`}${S}`}function de(e){let n={online:"Online",offline:"Not answering",stopped:"Not started"}[e.status],o=s.confirmingRemoval===e.macAddress;return t("li",{class:"device"},t("div",{class:"device-main"},t("h3",{},e.name),t("p",{class:"meta"},t("span",{class:`status ${e.status}`},n),` at ${e.host}. In Matter: ${ce(e)}.`),e.status!=="online"&&e.error?t("p",{class:"meta problem-text"},e.error):null),o?t("div",{class:"actions"},t("span",{class:"meta"},"Remove it from Matter too?"),c("Remove",()=>{G(e)},"danger"),c("Keep",()=>{s.confirmingRemoval="",l()})):t("div",{class:"actions"},c(e.status==="online"?"Options":"Reconnect",()=>R([q(e)])),c("Remove",()=>{s.confirmingRemoval=e.macAddress,l()})))}function Q(){let e=s.setup?.devices??[];return[t("header",{},t("h1",{},"Daikin air conditioners"),s.setup?c("Add air conditioners",()=>{C()},"primary"):null),m(),e.length===0&&s.setup?t("p",{class:"empty"},"No air conditioner is set up yet. Add one to control it from Matter."):t("ul",{class:"devices"},...e.map(de)),s.setup?null:c("Try again",()=>{E()},"primary")]}var F={apple:{title:"Apple Home",text:"Fan speed as a percentage, the humidity on a sensor of its own, and a switch for everything Home has no control for: swing, dry mode, fan mode and auto fan."},google:{title:"Google Home",text:"Fan speed as steps, swing switches and a dry mode switch."},full:{title:"Full",text:"Every control, switch and sensor this unit can have, special modes and the outdoor temperature included."},minimal:{title:"Minimal",text:"No fan control, no swing control and no switches. The fan and the swing stay as they are."}};function j(e,n){let o=e==="minimal";return{fanControl:o||n.fanSteps.length===0?"hidden":e==="apple"?"percent":"steps",fanSpeedWhenHidden:"keep",swingControl:o||!n.hasSwing?"hidden":"switches",swingModeWhenHidden:"keep",hasDryModeSwitch:!o&&n.hasDryMode,hasFanModeSwitch:(e==="apple"||e==="full")&&n.hasFanMode,hasFanAutoSwitch:(e==="apple"||e==="full")&&n.hasFanAuto,hasSeparateFan:!1,specialModeSwitches:e==="full"?[...n.specialModes]:[],hasOutdoorTemperatureSensor:e==="full",hasSeparateHumiditySensor:e==="apple"}}function Z(e,n,o){let a=x(e);return o.fanSteps.length===0&&Object.assign(a,{fanControl:"hidden",fanSpeedWhenHidden:"keep"}),a.fanControl==="hidden"&&(a.hasSeparateFan=!1),o.hasSwing||Object.assign(a,{swingControl:"hidden",swingModeWhenHidden:"keep"}),a.specialModeSwitches=o.specialModes.filter(r=>a.specialModeSwitches.includes(r)),JSON.stringify(a)===JSON.stringify({...j(n,o),isComposed:a.isComposed})}var pe={auto:"Auto",silent:"Silent",level1:"Level 1",level2:"Level 2",level3:"Level 3",level4:"Level 4",level5:"Level 5"},ue={off:"No swing",vertical:"Up and down",horizontal:"Left and right",both:"Up, down, left and right"},X={powerful:{title:"Powerful",text:"Runs the unit at full power for a while."},econo:{title:"Econo",text:"Limits how much power the unit draws."},streamer:{title:"Streamer",text:"Runs the air purifying of the unit."}},A={steps:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect x="2" y="20" width="8" height="6"/><rect x="14" y="14" width="8" height="12"/><rect x="26" y="8" width="8" height="18"/><rect x="38" y="2" width="8" height="24"/></svg>',percent:'<svg viewBox="0 0 48 28" aria-hidden="true"><path d="M2 26 L46 2 L46 26 Z"/></svg>',hidden:'<svg viewBox="0 0 48 28" aria-hidden="true"><path class="stroke" d="M6 14 H42" stroke-dasharray="3 5"/></svg>',switches:'<svg viewBox="0 0 48 28" aria-hidden="true"><path class="stroke" d="M12 4 V24 M7 9 L12 4 L17 9 M7 19 L12 24 L17 19"/><path class="stroke" d="M26 14 H46 M31 9 L26 14 L31 19 M41 9 L46 14 L41 19"/></svg>'};function u(e,n){Object.assign(e,n),l()}function T(e,n,o,a,r,i,d){let p=`${e}-${n}`,h=t("input",{type:"radio",name:e,id:p,checked:n===o,onchange:()=>d(n)});return t("label",{class:"choice pictured",for:p},h,t("span",{class:"glyph",html:i}),t("span",{},t("strong",{},a),t("span",{class:"meta"},r)))}function y(e,n,o,a,r){let i=t("input",{type:"checkbox",id:e,checked:n,onchange:()=>r(i.checked)});return t("label",{class:"choice",for:e},i,t("span",{},t("strong",{},o),t("span",{class:"meta"},a)))}function he(e,n){return t("section",{class:"presets"},t("h2",{},"Start from a preset"),t("div",{class:"preset-list"},...Object.keys(F).map(o=>t("button",{type:"button",class:"preset","aria-pressed":String(Z(e,o,n)),onclick:()=>u(e,j(o,n))},t("strong",{},F[o].title),t("span",{class:"meta"},F[o].text)))),t("p",{class:"help"},"A preset fills in the choices below for what this unit can do. Change any of them afterwards."))}function fe(e,n){let o=e.capabilities?.fanSpeeds??[],a=n.fanSteps.length;if(a===0)return t("section",{},t("h2",{},"Fan speed"),t("p",{class:"help"},"This unit does not let apps set its fan speed, so there is nothing to choose."));let r=n.hasFanAuto&&!(e.fanControl==="hidden"&&e.fanSpeedWhenHidden!=="keep");return t("section",{},t("h2",{},"Fan speed"),t("div",{class:"choices"},T("fan","steps",e.fanControl,"Steps",`${a} speeds, one for each speed of the unit${o.includes("auto")?", and auto":""}.`,A.steps,i=>u(e,{fanControl:i})),T("fan","percent",e.fanControl,"Percentage","A slider from 0 to 100, which the plugin rounds to the nearest speed of the unit.",A.percent,i=>u(e,{fanControl:i})),T("fan","hidden",e.fanControl,"Not shown in Matter","No fan control. Pick below what the plugin does with the fan speed instead.",A.hidden,i=>u(e,{fanControl:i}))),e.fanControl==="hidden"?t("div",{class:"field"},t("label",{for:"fan-fixed"},"When the plugin sends a command, the fan speed"),t("select",{id:"fan-fixed",onchange:i=>u(e,{fanSpeedWhenHidden:i.target.value})},t("option",{value:"keep",selected:e.fanSpeedWhenHidden==="keep"},"stays as it is"),...o.map(i=>t("option",{value:i,selected:e.fanSpeedWhenHidden===i},`is set to ${pe[i]}`)))):null,e.fanControl==="hidden"?null:y("separate-fan",e.hasSeparateFan,"Show the fan as a device of its own",`It appears as "${e.name} Fan", and the air conditioner has no fan speed of its own. For apps that show no fan speed on an air conditioner. It always shows the speed the unit holds, also while the unit is off.`,i=>u(e,{hasSeparateFan:i})),r?y("fan-auto-switch",e.hasFanAutoSwitch,"Add a switch for auto fan speed",`It appears as "${e.name} Auto Fan". On sets the fan to auto, off puts it back at the speed it last ran at. For apps that show no auto fan mode.`,i=>u(e,{hasFanAutoSwitch:i})):null)}function me(e){let n=e.capabilities?.swingModes??[];if(n.length===0)return t("section",{},t("h2",{},"Swing"),t("p",{class:"help"},"This unit does not let apps move its louvers, so there is nothing to choose."));let o=n.includes("vertical"),a=n.includes("horizontal"),r="One switch, for the up and down swing.";return o&&a?r="Two switches: one for the up and down swing, one for left and right.":a&&(r="One switch, for the left and right swing."),t("section",{},t("h2",{},"Swing"),t("div",{class:"choices"},T("swing","switches",e.swingControl,"Switches",r,A.switches,i=>u(e,{swingControl:i})),T("swing","hidden",e.swingControl,"Not shown in Matter","No swing control. Pick below what the plugin does with the swing instead.",A.hidden,i=>u(e,{swingControl:i}))),e.swingControl==="hidden"?t("div",{class:"field"},t("label",{for:"swing-fixed"},"When the plugin sends a command, the swing"),t("select",{id:"swing-fixed",onchange:i=>u(e,{swingModeWhenHidden:i.target.value})},t("option",{value:"keep",selected:e.swingModeWhenHidden==="keep"},"stays as it is"),...n.map(i=>t("option",{value:i,selected:e.swingModeWhenHidden===i},`is set to: ${ue[i]}`)))):null)}function we(e,n){if(!n.hasDryMode&&!n.hasFanMode)return null;let o=e.capabilities?.modes??[];return t("section",{},t("h2",{},"Dry and fan modes"),t("p",{class:"help"},`These are always among the modes of the air conditioner in Matter, but some apps, such as Apple Home, only offer cool, heat and auto. A switch makes the mode reachable there: on puts the unit in that mode, off puts it back in ${o.includes("cool")?"cool":"heat"}.`),t("div",{class:"choices"},n.hasDryMode?y("dry-mode-switch",e.hasDryModeSwitch,"Add a switch for dry mode",`It appears as "${e.name} Dry Mode".`,a=>u(e,{hasDryModeSwitch:a})):null,n.hasFanMode?y("fan-mode-switch",e.hasFanModeSwitch,"Add a switch for fan mode",`It appears as "${e.name} Fan Mode".`,a=>u(e,{hasFanModeSwitch:a})):null))}function ge(e,n){return n.specialModes.length===0?null:t("section",{},t("h2",{},"Special modes"),t("p",{class:"help"},"Matter has no control for these, so each can get a switch of its own. A switch only takes effect in the modes this unit offers that special mode in."),t("div",{class:"choices"},...n.specialModes.map(o=>y(`special-${o}`,e.specialModeSwitches.includes(o),`Add a switch for ${X[o].title}`,X[o].text,a=>u(e,{specialModeSwitches:n.specialModes.filter(r=>r===o?a:e.specialModeSwitches.includes(r))})))))}function ye(e){return t("section",{},t("h2",{},"Humidity and outdoor temperature"),t("p",{class:"help"},"No adapter says which readings its unit has, so each of these takes effect once the unit gives that reading. The room temperature is always on the air conditioner."),t("div",{class:"choices"},y("separate-humidity",e.hasSeparateHumiditySensor,"Show the humidity on a sensor of its own",`It appears as "${e.name} Humidity", and the air conditioner has no humidity of its own. For apps that show no humidity on an air conditioner, which Apple Home is reported to be.`,n=>u(e,{hasSeparateHumiditySensor:n})),y("outdoor-sensor",e.hasOutdoorTemperatureSensor,"Add a sensor for the outdoor temperature",`It appears as "${e.name} Outdoor Temperature". A Matter app may take it for the temperature of the room, so it is off unless you turn it on. Some units give no outdoor temperature while they are idle: the sensor then shows no value.`,n=>u(e,{hasOutdoorTemperatureSensor:n}))))}function ve(e){return t("section",{},t("h2",{},"One device or several"),t("p",{class:"help"},"The switches, a fan of its own and the sensors can be parts of the air conditioner in Matter, or devices of their own beside it."),t("div",{class:"choices"},y("composed",e.isComposed,"Make them parts of the air conditioner",`Your Matter app has one device, "${e.name}", and they move between rooms with it. A part has no name of its own in Matter, so an app may show the switches as numbered outlets, and an app that shows no parts shows the air conditioner alone. Turned off, each is a device of its own with its own name, which your app puts in a room by itself.`,n=>u(e,{isComposed:n}))))}function _(){let e=f();if(e===void 0)return[m()];let n=e.controls??W;return[k(),L(),$(e,`Options for the air conditioner at ${e.adapter.host}. Power, mode and temperature always appear in Matter: choose how the rest appears.`),he(e,n),fe(e,n),me(e),we(e,n),ge(e,n),ye(e),ve(e),e.isNew?null:t("p",{class:"help"},"Changing any of these rebuilds this air conditioner in Matter. Your Matter app may take a moment to show the change."),m(),t("div",{class:"footer"},c("Cancel",g),c(e.isNew?`Add ${e.name}`:"Save options",()=>{Y(e)},"primary"))]}function ee(){try{if(window.parent===window)return;let e=getComputedStyle(window.parent.document.body);for(let n of["--primary-color","--div-bg-color","--div-text-color","--main-bg-color","--main-label-color","--div-border-color","--main-light-color"]){let o=e.getPropertyValue(n).trim();o!==""&&document.documentElement.style.setProperty(`--host${n.slice(1)}`,o)}document.documentElement.style.setProperty("--host-font",e.fontFamily)}catch{}}B({loading:()=>[t("p",{class:"notice working",role:"status"},t("span",{class:"spinner"}),"Loading\u2026")],home:Q,find:J,connect:V,options:_,done:z});ee();l();E();
|
|
1
|
+
async function b(e,t,n){try{let[i,r]=e.split(" "),a={method:i};t!==void 0&&(a.headers={"Content-Type":"application/json"},a.body=JSON.stringify(t));let c=await fetch(new URL(`api/${r}${n===void 0?"":`?${n}`}`,document.baseURI),a);if(c.status===204)return{ok:!0};if(c.status===503)return{ok:!1,error:"The plugin is not running. Start it in Matterbridge, then reload this page."};let l=await c.json();return"ok"in l?l:{ok:!1,error:l.error??`The plugin answered with status ${c.status}`}}catch{return{ok:!1,error:"Matterbridge did not answer. Check that it is running, then try again."}}}function k(e){let{fanControl:t,fanSpeedWhenHidden:n,swingControl:i,swingModeWhenHidden:r,modeControl:a,modeSwitches:c,fanAutoControl:l,hasSeparateFan:m,specialModeSwitches:f,hasOutdoorTemperatureSensor:w,hasSeparateHumiditySensor:h,isComposed:v}=e;return{fanControl:t,fanSpeedWhenHidden:n,swingControl:i,swingModeWhenHidden:r,modeControl:a,modeSwitches:[...c],fanAutoControl:l,hasSeparateFan:m,specialModeSwitches:[...f],hasOutdoorTemperatureSensor:w,hasSeparateHumiditySensor:h,isComposed:v}}var le={fanControl:"steps",fanSpeedWhenHidden:"keep",swingControl:"switches",swingModeWhenHidden:"keep",modeControl:"native",modeSwitches:[],fanAutoControl:"native",hasSeparateFan:!1,specialModeSwitches:[],hasOutdoorTemperatureSensor:!1,hasSeparateHumiditySensor:!1,isComposed:!0};function K(e){return{adapter:e,name:e.name??"",isNew:!0,...k(le)}}function Y(e){let{capabilities:t,controls:n,name:i,macAddress:r,host:a,protocol:c,requiresRegistration:l,usesHttps:m,requiresLocalPassword:f}=e;return{adapter:{host:a,protocol:c,name:i,macAddress:r,requiresRegistration:l,usesHttps:m,requiresLocalPassword:f},name:i,isNew:!1,capabilities:t,controls:n,...k(e)}}var G={fanSteps:[],hasFanAuto:!1,hasSwing:!1,hasDryMode:!1,hasFanMode:!1,specialModes:[]};var s={screen:"loading",setup:void 0,found:[],selected:new Set,queue:[],saved:[],handled:[],isBusy:!1,busyText:"",error:"",confirmingRemoval:""};function g(){return s.queue[0]}function x(e){return e.name===""?e.adapter.host:e.name}function W(e){return(s.setup?.devices??[]).some(t=>t.macAddress===e.macAddress)}var q;function V(e){q=e}function d(){let e=document.getElementById("app");if(e===null||q===void 0)return;let t=`${s.screen}:${g()?.adapter.host??""}`,n=e.dataset.form===t,i=document.activeElement instanceof HTMLElement?document.activeElement.id:"",r=new Map;for(let a of e.querySelectorAll("input[type=text], input[type=password]"))a.id!==""&&r.set(a.id,a.value);if(e.replaceChildren(...q[s.screen]().filter(a=>a!==!1&&a!==null&&a!==void 0)),e.dataset.form=t,!!n){for(let[a,c]of r){let l=document.getElementById(a);l instanceof HTMLInputElement&&(l.value=c)}i!==""&&document.getElementById(i)?.focus()}}async function E(e,t){s.isBusy=!0,s.busyText=e,s.error="",d();try{return await t()}finally{s.isBusy=!1,d()}}async function R(){let e=await b("GET state");if(!e.ok){s.error=e.error,s.screen="home",d();return}if(s.setup=e.state,e.state.devices.length===0){await H();return}s.screen="home",d()}async function F(){let e=await b("GET state");e.ok&&(s.setup=e.state)}function C(){s.queue=[],s.saved=[],s.handled=[],s.error="",s.screen="home",F().then(d),d()}async function H(){s.screen="find";let e=await E("Searching your network. This takes about three seconds\u2026",async()=>b("POST discover"));if(!e.ok){s.error=`The search did not run: ${e.error}`,d();return}await F();let t=s.found.filter(n=>!e.devices.some(i=>i.host===n.host)&&s.selected.has(n.host)).map(n=>({...n,isConfigured:W(n)}));s.found=[...e.devices,...t];for(let n of e.devices)!n.isConfigured&&!n.hasDuplicateName&&s.selected.add(n.host);d()}async function z(e){let t=await E(`Looking for an adapter at ${e}\u2026`,async()=>b("POST identify",{host:e}));if(!t.ok){s.error=t.error,d();return}let n=W(t.device),i=t.device.requiresRegistration===!0;s.found=[...s.found.filter(r=>r.host!==t.device.host),{...t.device,isConfigured:n,hasDuplicateName:!1,needsRegistration:i}],n||s.selected.add(t.device.host),d()}function N(e){s.queue=e,s.saved=[],s.handled=[],B()}async function B(){let e=g();if(e===void 0){s.screen=s.saved.length>0?"done":"home",await F(),d();return}s.screen="connect",s.error="",e.probe=void 0,d(),await L(e)}async function L(e,t){let i={device:{...e.adapter,name:e.name===""?void 0:e.name},localPassword:e.localPassword,key:t},r=t===void 0?`Checking ${x(e)}\u2026`:`Registering with ${x(e)}\u2026`,a=await E(r,async()=>b(t===void 0?"POST probe":"POST register",i));if(!a.ok){s.error=a.error,d();return}if(e.probe=a.result,e.adapter=a.result.device,a.result.status==="ready"){e.capabilities=a.result.capabilities,e.controls=a.result.controls;let c=a.result.device.name??"";c!==""&&(e.name=c),e.name!==""&&(s.screen="options")}d()}function J(){let e=s.queue.shift();e!==void 0&&s.handled.push({label:x(e),isSaved:!1}),B()}async function Q(e){let{adapter:t,name:n,localPassword:i}=e,r={...t,name:n,localPassword:i,...k(e)},a=await E(`Saving ${n}\u2026`,async()=>b("POST device",{device:r}));if(!a.ok){s.error=a.error,d();return}s.setup=a.state,s.saved.push(n),s.handled.push({label:n,isSaved:!0}),s.queue.shift(),await B()}async function Z(e){let t=await E(`Removing ${e.name}\u2026`,async()=>b("DELETE device",void 0,`macAddress=${encodeURIComponent(e.macAddress)}`));s.confirmingRemoval="",t.ok||(s.error=t.error),await F(),d()}function o(e,t={},...n){let i=document.createElement(e);for(let[r,a]of Object.entries(t))a===void 0||a===!1||(r==="class"?i.className=String(a):r==="html"?i.innerHTML=String(a):r.startsWith("on")&&typeof a=="function"?i.addEventListener(r.slice(2),a):r in i?i[r]=a:i.setAttribute(r,String(a)));for(let r of n)r!==!1&&r!==null&&r!==void 0&&i.append(r);return i}function p(e,t,n="quiet",i=!1){return o("button",{type:"button",class:`button ${n}`,onclick:t,disabled:i||s.isBusy},e)}function y(){return s.isBusy?o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),s.busyText):s.error!==""?o("p",{class:"notice problem",role:"alert"},s.error):null}function P(){let e=g();if(e===void 0||!e.isNew||s.handled.length+s.queue.length<2)return null;let t=s.screen==="connect"?"Connecting":"Choosing options";return o("ol",{class:"units","aria-label":"Air conditioners in this setup"},...s.handled.map(n=>o("li",{class:n.isSaved?"done":"skipped"},o("strong",{},n.label),o("span",{class:"meta"},n.isSaved?"Added":"Skipped"))),...s.queue.map((n,i)=>o("li",{class:i===0?"current":"ahead","aria-current":i===0?"true":void 0},o("strong",{},x(n)),o("span",{class:"meta"},i===0?t:"Waiting"))))}function j(e,t){return o("header",{class:"unit-header"},o("h1",{},x(e)),o("p",{class:"meta"},t))}function M(){if(s.queue.length>0&&!s.queue.some(i=>i.isNew))return null;let e=["Find","Connect","Options"],n=["find","connect","options"].indexOf(s.screen);return o("ol",{class:"steps"},...e.map((i,r)=>{let a="ahead";return r<n?a="done":r===n&&(a="current"),o("li",{class:a,"aria-current":r===n?"step":void 0},i)}))}function ce(e){let t=o("input",{type:"text",id:"device-name",maxLength:32,autocomplete:"off"}),n=()=>{let i=t.value.trim();if(i!==""){if(s.setup?.devices.some(r=>r.name===i)){s.error=`"${i}" is already the name of another air conditioner. Pick a different one.`,d();return}e.name=i,s.error="",s.screen="options",d()}};return o("div",{class:"field"},o("label",{for:"device-name"},"Name this air conditioner"),o("p",{class:"help"},"Its adapter has no name of its own. This is the name it gets in Matter, up to 32 characters. Name the adapter in its Daikin app later and the plugin takes that name instead."),o("div",{class:"inline-form"},t,p("Continue",n,"primary")))}function de(e,t){let n=o("input",{type:"password",id:"lock-code",inputMode:"numeric",autocomplete:"off",maxLength:64});return o("div",{class:"field"},o("label",{for:"lock-code"},"Child lock code"),o("p",{class:"help"},"The 4-digit code set under Child lock in the Daikin app the adapter was set up with. It is not printed on the adapter."),t?o("p",{class:"problem-text"},"The adapter did not accept that code."):null,o("div",{class:"inline-form"},n,p("Check the code",()=>{n.value!==""&&(e.localPassword=n.value,L(e))},"primary")))}function pe(e,t){let n=o("input",{type:"text",id:"adapter-key",autocomplete:"off",spellcheck:!1,maxLength:64});return o("div",{class:"field"},o("label",{for:"adapter-key"},"KEY on the adapter"),o("p",{class:"help"},"This adapter only answers apps it has registered. The KEY is printed on its label, next to the SSID, and in the operation manual. It is used once and not stored."),t?o("p",{class:"problem-text"},"The adapter did not accept that KEY."):null,o("div",{class:"inline-form"},n,p("Register",()=>{n.value.trim()!==""&&L(e,n.value.trim())},"primary")))}function he(e){let t=e.probe?.status;return t==="ready"&&e.name===""?ce(e):t==="localPasswordRequired"||t==="localPasswordRejected"?de(e,t==="localPasswordRejected"):t==="registrationRequired"||t==="registrationRejected"?pe(e,t==="registrationRejected"):t==="unreachable"?o("p",{class:"notice problem",role:"alert"},`${e.name} did not answer at ${e.adapter.host}. Check that it has power and is on the network, then try again.`):t==="failed"?o("p",{class:"notice problem",role:"alert"},`${e.name} answered with an error: ${e.probe?.message??"unknown"}`):null}function X(){let e=g();if(e===void 0)return[y()];let t=e.probe?.status==="unreachable"||e.probe?.status==="failed"||s.error!==""&&e.name!=="";return[M(),P(),j(e,`Connecting to its adapter at ${e.adapter.host}.`),y(),s.isBusy?null:he(e),o("div",{class:"footer"},t?p("Try again",()=>{L(e)}):null,p(e.isNew&&s.queue.length>1?"Skip this one":"Cancel",e.isNew&&s.queue.length>1?J:C))]}function _(){let e=s.saved;return[o("header",{},o("h1",{},e.length===1?`${e[0]} is set up`:`${e.length} air conditioners are set up`)),o("p",{class:"lead"},"They now appear in the Matter apps paired with Matterbridge. If Matterbridge is not paired yet, pair it from its home page."),o("div",{class:"footer"},p("Done",C,"primary"))]}function ue(e){let t=!e.isConfigured&&!e.hasDuplicateName,n=[];e.isConfigured&&n.push("Already set up."),e.hasDuplicateName&&n.push("Another adapter has the same name. Give each its own name in its Daikin app, then search again."),e.name===void 0&&n.push("It has not told its name yet. If it has none, you will name it after connecting."),t&&e.needsRegistration?n.push("Asks for the KEY printed on its label."):t&&e.requiresRegistration&&n.push("Already registered with this plugin, so it will not ask for its KEY."),t&&e.requiresLocalPassword&&n.push("Asks for its child lock code.");let i=o("input",{type:"checkbox",id:`found-${e.host}`,checked:t&&s.selected.has(e.host),disabled:!t,onchange:()=>{i.checked?s.selected.add(e.host):s.selected.delete(e.host),d()}});return o("li",{class:t?"found":"found unavailable"},o("label",{for:`found-${e.host}`},i,o("span",{},o("strong",{},e.name??"Unnamed adapter"),o("span",{class:"meta"},` ${e.host}`),...n.map(r=>o("span",{class:"meta note"},r)))))}function ee(){let e=s.found.filter(r=>s.selected.has(r.host)&&!r.isConfigured&&!r.hasDuplicateName),t=o("input",{type:"text",id:"host",placeholder:"192.168.1.50",spellcheck:!1,autocomplete:"off"}),n=()=>{t.value.trim()!==""&&z(t.value.trim())};t.addEventListener("keydown",r=>{r.key==="Enter"&&n()});let i=e.length===1?"Set up 1 air conditioner":`Set up ${e.length} air conditioners`;return[M(),o("header",{},o("h1",{},"Find your air conditioners")),y(),s.found.length>0?o("ul",{class:"found-list"},...s.found.map(ue)):null,!s.isBusy&&s.found.length===0?o("p",{class:"empty"},"No adapter answered. A search only reaches adapters on the same network segment as Matterbridge: add the others by address below."):null,o("details",{class:"manual",open:!s.isBusy&&s.found.length===0},o("summary",{},"Can't see your air conditioner?"),o("p",{class:"help"},"Enter the address of its adapter. The plugin reads the name of the adapter there and follows it to a new address like any other, as long as a search can reach it. An adapter on another subnet or VLAN keeps this address."),o("div",{class:"inline-form"},o("label",{for:"host",class:"visually-hidden"},"Adapter address"),t,p("Look it up",n))),o("div",{class:"footer"},p("Search again",()=>{H()}),p("Cancel",C),p(i,()=>N(e.map(K)),"primary",e.length===0))]}function fe(e){let t=e.controls===void 0||e.controls.fanSteps.length>0,n=e.controls===void 0||e.controls.hasSwing,i=e.hasSeparateFan?" on a fan of its own":"",r={native:"",switch:"",firstStep:" with auto first",lastStep:" with auto last"}[e.fanAutoControl],a=e.fanControl==="hidden"?"fan speed not shown":`fan speed as ${e.fanControl==="steps"?"steps":"a percentage"}${r}${i}`,c={switches:"swing as switches",select:"swing as a mode select",hidden:"swing not shown"}[e.swingControl],l=[t?a:"",n?c:""].filter(v=>v!=="").join(", "),f=[...e.modeControl==="switches"?e.modeSwitches.map(v=>v==="dehumidify"?"dry mode":"fan mode"):[],e.fanAutoControl==="switch"?"auto fan":"",...e.specialModeSwitches].filter(v=>v!==""),w=[f.length===0?"":`a switch for ${f.join(", ")}`,e.modeControl==="select"?"a mode select":""].filter(v=>v!=="").join(" and "),h=e.isComposed?", all as one device":"";return l===""?`${w===""?"power, mode and temperature":w}${h}`:`${w===""?l:`${l}, and ${w}`}${h}`}function me(e){let t={online:"Online",offline:"Not answering",stopped:"Not started"}[e.status],n=s.confirmingRemoval===e.macAddress;return o("li",{class:"device"},o("div",{class:"device-main"},o("h3",{},e.name),o("p",{class:"meta"},o("span",{class:`status ${e.status}`},t),` at ${e.host}. In Matter: ${fe(e)}.`),e.status!=="online"&&e.error?o("p",{class:"meta problem-text"},e.error):null),n?o("div",{class:"actions"},o("span",{class:"meta"},"Remove it from Matter too?"),p("Remove",()=>{Z(e)},"danger"),p("Keep",()=>{s.confirmingRemoval="",d()})):o("div",{class:"actions"},p(e.status==="online"?"Options":"Reconnect",()=>N([Y(e)])),p("Remove",()=>{s.confirmingRemoval=e.macAddress,d()})))}function te(){let e=s.setup?.devices??[];return[o("header",{},o("h1",{},"Daikin air conditioners"),s.setup?p("Add air conditioners",()=>{H()},"primary"):null),y(),e.length===0&&s.setup?o("p",{class:"empty"},"No air conditioner is set up yet. Add one to control it from Matter."):o("ul",{class:"devices"},...e.map(me)),s.setup?null:p("Try again",()=>{R()},"primary")]}var O={apple:{title:"Apple Home",text:"Fan speed as a percentage, the humidity on a sensor of its own too, and a switch for auto fan, each a device of its own. The swing is not shown and stays as it is."},google:{title:"Google Home",text:"Fan speed as steps, the humidity on a sensor of its own too, and a switch for auto fan, each a device of its own. The swing is not shown and stays as it is."},matter:{title:"Full Matter support",text:"For apps that show every Matter control: fan speed as steps with its auto mode, the swing as a mode select, a switch for each special mode and the outdoor temperature, all as one device."}};function U(e,t){let n=e==="matter",i=n&&t.hasSwing?"select":"hidden";return{fanControl:t.fanSteps.length===0?"hidden":e==="apple"?"percent":"steps",fanSpeedWhenHidden:"keep",swingControl:i,swingModeWhenHidden:"keep",modeControl:"native",modeSwitches:[],fanAutoControl:!n&&t.hasFanAuto?"switch":"native",hasSeparateFan:!1,specialModeSwitches:n?[...t.specialModes]:[],hasOutdoorTemperatureSensor:n,hasSeparateHumiditySensor:!n,isComposed:n}}function oe(e,t,n){let i=k(e);n.fanSteps.length===0&&Object.assign(i,{fanControl:"hidden",fanSpeedWhenHidden:"keep"}),i.fanControl==="hidden"&&(i.hasSeparateFan=!1);let r=i.fanAutoControl==="firstStep"||i.fanAutoControl==="lastStep",a=i.fanControl==="hidden"&&i.fanSpeedWhenHidden!=="keep";return(!n.hasFanAuto||r&&i.fanControl==="hidden"||a)&&(i.fanAutoControl="native"),n.hasSwing||Object.assign(i,{swingControl:"hidden",swingModeWhenHidden:"keep"}),i.modeControl!=="switches"&&(i.modeSwitches=[]),!n.hasDryMode&&!n.hasFanMode&&Object.assign(i,{modeControl:"native",modeSwitches:[]}),i.specialModeSwitches=n.specialModes.filter(c=>i.specialModeSwitches.includes(c)),JSON.stringify(i)===JSON.stringify(U(t,n))}var I={auto:"Auto",silent:"Silent",level1:"Level 1",level2:"Level 2",level3:"Level 3",level4:"Level 4",level5:"Level 5"},ne={off:"No swing",vertical:"Up and down",horizontal:"Left and right",both:"Up, down, left and right"},se={powerful:{title:"Powerful",text:"Runs the unit at full power for a while."},econo:{title:"Econo",text:"Limits how much power the unit draws."},streamer:{title:"Streamer",text:"Runs the air purifying of the unit."}},A={steps:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect x="2" y="20" width="8" height="6"/><rect x="14" y="14" width="8" height="12"/><rect x="26" y="8" width="8" height="18"/><rect x="38" y="2" width="8" height="24"/></svg>',percent:'<svg viewBox="0 0 48 28" aria-hidden="true"><path d="M2 26 L46 2 L46 26 Z"/></svg>',hidden:'<svg viewBox="0 0 48 28" aria-hidden="true"><path class="stroke" d="M6 14 H42" stroke-dasharray="3 5"/></svg>',switches:'<svg viewBox="0 0 48 28" aria-hidden="true"><path class="stroke" d="M12 4 V24 M7 9 L12 4 L17 9 M7 19 L12 24 L17 19"/><path class="stroke" d="M26 14 H46 M31 9 L26 14 L31 19 M41 9 L46 14 L41 19"/></svg>',select:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect class="stroke" x="3" y="5" width="42" height="18" rx="4"/><path class="stroke" d="M31 12 L35 16 L39 12"/></svg>'},ie="Few apps show a mode select: Home Assistant does, Apple Home does not.";function T(e,t,n){return e.isComposed?n?`It appears as a part of "${e.name}", tagged "${t}"`:`It appears as a part of "${e.name}"`:`It appears as "${e.name} ${t}"`}function u(e,t){Object.assign(e,t),d()}function D(e,t,n,i,r,a,c){let l=`${e}-${t}`,m=o("input",{type:"radio",name:e,id:l,checked:t===n,onchange:()=>c(t)});return o("label",{class:"choice pictured",for:l},m,o("span",{class:"glyph",html:a}),o("span",{},o("strong",{},i),o("span",{class:"meta"},r)))}function S(e,t,n,i,r,a){let c=`${e}-${t}`,l=o("input",{type:"radio",name:e,id:c,checked:t===n,onchange:()=>a(t)});return o("label",{class:"choice",for:c},l,o("span",{},o("strong",{},i),o("span",{class:"meta"},r)))}function $(e,t,n,i,r){let a=o("input",{type:"checkbox",id:e,checked:t,onchange:()=>r(a.checked)});return o("label",{class:"choice",for:e},a,o("span",{},o("strong",{},n),o("span",{class:"meta"},i)))}function we(e,t){return o("section",{class:"presets"},o("h2",{},"Start from a preset"),o("div",{class:"preset-list"},...Object.keys(O).map(n=>o("button",{type:"button",class:"preset","aria-pressed":String(oe(e,n,t)),onclick:()=>u(e,U(n,t))},o("strong",{},O[n].title),o("span",{class:"meta"},O[n].text)))),o("p",{class:"help"},"A preset fills in the choices below for what this unit can do. Change any of them afterwards."))}function ge(e){let t=e.map(n=>I[n]);return t.length===1?t[0]:`${t.slice(0,-1).join(", ")} and ${t[t.length-1]}`}function ye(e,t,n){if(!t.hasFanAuto||e.fanControl==="hidden"&&e.fanSpeedWhenHidden!=="keep")return null;let i=f=>u(e,{fanAutoControl:f}),r=e.fanControl==="hidden",a=`${T(e,"Auto Fan",!0)}. On sets the fan to auto, off puts it back at the speed it last ran at.`,c=I[t.fanSteps[0]],l=I[t.fanSteps[t.fanSteps.length-1]],m=r?[S("fan-auto","native",n,"Not shown in Matter","Without a fan speed in Matter, auto has no place there either.",i),S("fan-auto","switch",n,"A switch",a,i)]:[S("fan-auto","native",n,"Native fan mode only","Auto is a mode of the fan speed, beside its speeds, as Matter has it.",i),S("fan-auto","switch",n,"A switch",`${a} Auto stays a fan mode as well.`,i),S("fan-auto","firstStep",n,"The first step",`Auto comes below ${c}: step 1, or the bottom of the slider.`,i),S("fan-auto","lastStep",n,"The last step",`Auto comes above ${l}: the top step, or 100 %.`,i)];return o("div",{class:"field"},o("h3",{id:"fan-auto-title"},"Auto fan speed"),r?null:o("p",{class:"help"},"Matter has auto as a fan mode, which not every app shows. A switch makes it reachable there too, and a step takes its place."),o("div",{class:"choices",role:"radiogroup","aria-labelledby":"fan-auto-title"},...m))}function ve(e,t){let n=e.capabilities?.fanSpeeds??[];if(t.fanSteps.length===0)return o("section",{},o("h2",{},"Fan speed"),o("p",{class:"help"},"This unit does not let apps set its fan speed, so there is nothing to choose."));let r=e.fanControl==="hidden",a=t.hasFanAuto&&!r&&(e.fanAutoControl==="firstStep"||e.fanAutoControl==="lastStep"),c=t.hasFanAuto&&e.fanAutoControl==="switch",l=t.fanSteps;a&&(l=e.fanAutoControl==="firstStep"?["auto",...t.fanSteps]:[...t.fanSteps,"auto"]);let f=t.hasFanAuto&&!a?" Auto is not among them: it is a fan mode of its own.":"",w=`${l.length} ${l.length===1?"speed":"speeds"}, ${a?"in order":"slowest first"}: ${ge(l)}.${f}`;return o("section",{},o("h2",{},"Fan speed"),o("div",{class:"choices"},D("fan","steps",e.fanControl,"Steps",w,A.steps,h=>u(e,{fanControl:h})),D("fan","percent",e.fanControl,"Percentage",`A slider from 0 to 100, which the plugin rounds to the nearest of ${w}`,A.percent,h=>u(e,{fanControl:h})),D("fan","hidden",e.fanControl,"Not shown in Matter","No fan control. Pick below what the plugin does with the fan speed instead.",A.hidden,h=>u(e,{fanControl:h}))),e.fanControl==="hidden"?o("div",{class:"field"},o("label",{for:"fan-fixed"},"When the plugin sends a command, the fan speed"),o("select",{id:"fan-fixed",onchange:h=>u(e,{fanSpeedWhenHidden:h.target.value})},o("option",{value:"keep",selected:e.fanSpeedWhenHidden==="keep"},"stays as it is"),...n.map(h=>o("option",{value:h,selected:e.fanSpeedWhenHidden===h},`is set to ${I[h]}`)))):null,e.fanControl==="hidden"?null:$("separate-fan",e.hasSeparateFan,"Show the fan as a device of its own",`${T(e,"Fan",!1)}, and the air conditioner has no fan speed of its own. For apps that show no fan speed on an air conditioner, or do not show it well. It always shows the speed the unit holds, also while the unit is off.`,h=>u(e,{hasSeparateFan:h})),ye(e,t,r&&!c?"native":e.fanAutoControl))}function Se(e){let t=e.capabilities?.swingModes??[];if(t.length===0)return o("section",{},o("h2",{},"Swing"),o("p",{class:"help"},"This unit does not let apps move its louvers, so there is nothing to choose."));let n=t.includes("vertical"),i=t.includes("horizontal"),r="One switch, for the up and down swing.";return n&&i?r="Two switches: one for the up and down swing, one for left and right.":i&&(r="One switch, for the left and right swing."),o("section",{},o("h2",{},"Swing"),o("div",{class:"choices"},D("swing","switches",e.swingControl,"Switches",r,A.switches,a=>u(e,{swingControl:a})),D("swing","select",e.swingControl,"A mode select",`${T(e,"Swing",!0)}, and picks one of: ${t.map(a=>ne[a].toLowerCase()).join(", ")}. ${ie}`,A.select,a=>u(e,{swingControl:a})),D("swing","hidden",e.swingControl,"Not shown in Matter","No swing control. Pick below what the plugin does with the swing instead.",A.hidden,a=>u(e,{swingControl:a}))),e.swingControl==="hidden"?o("div",{class:"field"},o("label",{for:"swing-fixed"},"When the plugin sends a command, the swing"),o("select",{id:"swing-fixed",onchange:a=>u(e,{swingModeWhenHidden:a.target.value})},o("option",{value:"keep",selected:e.swingModeWhenHidden==="keep"},"stays as it is"),...t.map(a=>o("option",{value:a,selected:e.swingModeWhenHidden===a},`is set to: ${ne[a]}`)))):null)}function be(e,t){if(!t.hasDryMode&&!t.hasFanMode)return null;let n=e.capabilities?.modes??[],i=[...t.hasDryMode?["dehumidify"]:[],...t.hasFanMode?["fan"]:[]],r={dehumidify:"Dry Mode",fan:"Fan Mode"},a=i.filter(l=>e.modeSwitches.includes(l)),c=l=>u(e,l==="switches"&&a.length===0?{modeControl:l,modeSwitches:i}:{modeControl:l});return o("section",{},o("h2",{},"Dry and fan modes"),o("p",{class:"help"},"These are among the modes of the air conditioner in Matter, but some apps, such as Apple Home, only offer cool, heat and auto."),o("div",{class:"choices",role:"radiogroup","aria-label":"Dry and fan modes"},S("mode-control","native",e.modeControl,"Native modes only","Dry and fan are modes of the air conditioner, as Matter has them, and nothing more is added.",c),S("mode-control","select",e.modeControl,"A mode select",`${T(e,"Mode",!0)}, with Off and every mode of the unit, as the air conditioner has them. Picking a mode turns the unit on in it. ${ie}`,c),S("mode-control","switches",e.modeControl,"Switches",`A switch for each mode picked below. On puts the unit in that mode, off puts it back in ${n.includes("cool")?"cool":"heat"}.`,c)),e.modeControl==="switches"?o("div",{class:"choices"},...i.map(l=>$(`mode-switch-${l}`,a.includes(l),`Add a switch for ${r[l].toLowerCase()}`,`${T(e,r[l],!0)}.`,m=>u(e,{modeSwitches:i.filter(f=>f===l?m:a.includes(f))})))):null)}function Ce(e,t){return t.specialModes.length===0?null:o("section",{},o("h2",{},"Special modes"),o("p",{class:"help"},"Matter has no control for these, so each can get a switch of its own. A switch only takes effect in the modes this unit offers that special mode in."),o("div",{class:"choices"},...t.specialModes.map(n=>$(`special-${n}`,e.specialModeSwitches.includes(n),`Add a switch for ${se[n].title}`,se[n].text,i=>u(e,{specialModeSwitches:t.specialModes.filter(r=>r===n?i:e.specialModeSwitches.includes(r))})))))}function xe(e){return o("section",{},o("h2",{},"Humidity and outdoor temperature"),o("p",{class:"help"},"No adapter says which readings its unit has, so each of these takes effect once the unit gives that reading. The room temperature and the humidity are always on the air conditioner."),o("div",{class:"choices"},$("separate-humidity",e.hasSeparateHumiditySensor,"Also show the humidity on a sensor of its own",`${T(e,"Humidity",!1)}, and the air conditioner keeps its humidity. For apps that show no humidity on an air conditioner, which Apple Home is reported to be.`,t=>u(e,{hasSeparateHumiditySensor:t})),$("outdoor-sensor",e.hasOutdoorTemperatureSensor,"Add a sensor for the outdoor temperature",`It appears as "${e.name} Outdoor Temperature". It is a device of its own, which you can put in another room, such as outside. Until you do, a Matter app may show the outdoor temperature as that of the room it put the sensor in. Some units give no outdoor temperature while they are idle: the sensor then shows no value.`,t=>u(e,{hasOutdoorTemperatureSensor:t}))))}function ke(e){return o("section",{},o("h2",{},"One device or several"),o("p",{class:"help"},"The switches, a fan of its own and a humidity sensor of its own can be parts of the air conditioner in Matter, or devices of their own beside it. The outdoor temperature sensor is always a device of its own, so that you can put it in another room than the air conditioner."),o("div",{class:"choices"},$("composed",e.isComposed,"Make them parts of the air conditioner",`Your Matter app has one device, "${e.name}", and they move between rooms with it. A part has no name of its own in Matter, so an app may show the switches as numbered outlets, and an app that shows no parts shows the air conditioner alone. Turned off, each is a device of its own with its own name, which your app puts in a room by itself.`,t=>u(e,{isComposed:t}))))}function ae(){let e=g();if(e===void 0)return[y()];let t=e.controls??G;return[M(),P(),j(e,`Options for the air conditioner at ${e.adapter.host}. Power, mode and temperature always appear in Matter: choose how the rest appears.`),we(e,t),ve(e,t),Se(e),be(e,t),Ce(e,t),xe(e),ke(e),e.isNew?null:o("p",{class:"help"},"Changing any of these rebuilds this air conditioner in Matter. Your Matter app may take a moment to show the change."),y(),o("div",{class:"footer"},p("Cancel",C),p(e.isNew?`Add ${e.name}`:"Save options",()=>{Q(e)},"primary"))]}function re(){try{if(window.parent===window)return;let e=getComputedStyle(window.parent.document.body);for(let t of["--primary-color","--div-bg-color","--div-text-color","--main-bg-color","--main-label-color","--div-border-color","--main-light-color"]){let n=e.getPropertyValue(t).trim();n!==""&&document.documentElement.style.setProperty(`--host${t.slice(1)}`,n)}document.documentElement.style.setProperty("--host-font",e.fontFamily)}catch{}}V({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:te,find:ee,connect:X,options:ae,done:_});re();d();R();
|
|
2
2
|
/**
|
|
3
3
|
* @file apps/frontend/src/api.ts
|
|
4
4
|
* @description What the plugin's API sends and takes, and the call to it: see src/setup/setupApi.ts in the plugin.
|