homebridge-samsung-rac 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +172 -0
  2. package/config.schema.json +85 -0
  3. package/dist/cli/probe.d.ts +24 -0
  4. package/dist/cli/probe.js +475 -0
  5. package/dist/cli/probe.js.map +1 -0
  6. package/dist/config.d.ts +25 -0
  7. package/dist/config.js +49 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/deviceAdapter.d.ts +62 -0
  10. package/dist/deviceAdapter.js +186 -0
  11. package/dist/deviceAdapter.js.map +1 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/platform.d.ts +23 -0
  16. package/dist/platform.js +163 -0
  17. package/dist/platform.js.map +1 -0
  18. package/dist/platformAccessory.d.ts +105 -0
  19. package/dist/platformAccessory.js +515 -0
  20. package/dist/platformAccessory.js.map +1 -0
  21. package/dist/racStatus.d.ts +81 -0
  22. package/dist/racStatus.js +61 -0
  23. package/dist/racStatus.js.map +1 -0
  24. package/dist/settings.d.ts +14 -0
  25. package/dist/settings.js +18 -0
  26. package/dist/settings.js.map +1 -0
  27. package/dist/transport/atomic.d.ts +11 -0
  28. package/dist/transport/atomic.js +72 -0
  29. package/dist/transport/atomic.js.map +1 -0
  30. package/dist/transport/certificate.d.ts +35 -0
  31. package/dist/transport/certificate.js +123 -0
  32. package/dist/transport/certificate.js.map +1 -0
  33. package/dist/transport/httpOverTls.d.ts +45 -0
  34. package/dist/transport/httpOverTls.js +176 -0
  35. package/dist/transport/httpOverTls.js.map +1 -0
  36. package/dist/transport/localApi.d.ts +55 -0
  37. package/dist/transport/localApi.js +125 -0
  38. package/dist/transport/localApi.js.map +1 -0
  39. package/dist/transport/pairing.d.ts +51 -0
  40. package/dist/transport/pairing.js +215 -0
  41. package/dist/transport/pairing.js.map +1 -0
  42. package/dist/transport/tls.d.ts +25 -0
  43. package/dist/transport/tls.js +46 -0
  44. package/dist/transport/tls.js.map +1 -0
  45. package/dist/transport/tokenStore.d.ts +27 -0
  46. package/dist/transport/tokenStore.js +90 -0
  47. package/dist/transport/tokenStore.js.map +1 -0
  48. package/homebridge-ui/public/index.html +392 -0
  49. package/homebridge-ui/server.js +198 -0
  50. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # homebridge-samsung-rac
2
+
3
+ Control a Samsung room air conditioner from HomeKit over its **local network API**
4
+ (`https://<ip>:8888`, mutual TLS) instead of the SmartThings cloud.
5
+
6
+ The local API is richer than the cloud one. Most importantly it exposes the vane
7
+ position — `Wind.direction` — which the SmartThings API does not expose at all,
8
+ and which is the reason this plugin exists.
9
+
10
+ Developed against a **TP6X_RAC_16K** (SmartThings vendor id `DA-AC-RAC-100001`).
11
+ Other units of the same generation should work; older port-2878 models will not.
12
+
13
+ ## What it exposes
14
+
15
+ A single `HeaterCooler` accessory per unit:
16
+
17
+ | HomeKit | Comes from |
18
+ |---|---|
19
+ | On / off | `Operation.power` |
20
+ | Current temperature | `Temperatures[0].current` |
21
+ | Target temperature | `Temperatures[0].desired`, with the range the unit reports |
22
+ | Mode | `Mode.modes`, limited to the modes the unit says it supports |
23
+ | Fan speed | `Wind.speedLevel` / `Wind.maxSpeedLevel` |
24
+ | Swing | `Wind.direction` |
25
+ | Filter indicator | the `FilterAlarm` entry in `Alarms` |
26
+
27
+ Dry and fan-only modes have no HomeKit equivalent. The plugin reports them as
28
+ Auto/Idle and leaves them alone rather than overwriting a mode you chose in the
29
+ Samsung app.
30
+
31
+ A service only appears once the unit has actually published a reading for it, and
32
+ is never withdrawn afterwards — some units publish nothing until they are running.
33
+
34
+ ## Setup
35
+
36
+ 1. Install the plugin and open its settings in the Homebridge UI.
37
+ 2. **Fetch the certificate.** The local API requires a client certificate that
38
+ bundles a private key. It is not shipped with this plugin: it is downloaded
39
+ once and stored next to your Homebridge config, on your machine only.
40
+ 3. **Add your air conditioner's IP address** and save. Give it a fixed DHCP lease
41
+ on your router — the plugin cannot follow a unit that moves. You can add
42
+ several units.
43
+ 4. **Pair.** This is the awkward part, and the order matters:
44
+ - power the air conditioner **off**
45
+ - press *Start pairing*
46
+ - power it back **on** when the UI asks
47
+
48
+ The unit does not hand out a token on request. It calls back to Homebridge on
49
+ port 8889 the moment it powers on, and that callback carries the token.
50
+
51
+ The token is stored alongside the certificate, outside `config.json`.
52
+
53
+ ### If pairing never completes
54
+
55
+ The air conditioner connects back to whatever address asked it for a token, so
56
+ the callback has to be able to reach Homebridge:
57
+
58
+ - **Homebridge in Docker without host networking** — the callback cannot arrive.
59
+ Pair from the host with the probe CLI below and paste the token into the
60
+ *Paste token* box:
61
+
62
+ ```bash
63
+ npx homebridge-samsung-rac-probe pair --host 10.0.0.9
64
+ ```
65
+ - **A firewall on the Homebridge machine** — allow inbound TCP on port 8889.
66
+ - **A listener left over from an earlier attempt** — it keeps the port and the
67
+ next run fails in a way that looks identical to the unit never calling back.
68
+
69
+ ## Probe CLI
70
+
71
+ A command-line driver over the plugin's own transport code, for setting a unit up
72
+ without the UI and for working out what a misbehaving one is doing.
73
+
74
+ ```bash
75
+ npx homebridge-samsung-rac-probe cert # download the certificate
76
+ npx homebridge-samsung-rac-probe pair --host 10.0.0.9 # run the pairing ritual
77
+ npx homebridge-samsung-rac-probe dump --host 10.0.0.9 # print the device state
78
+ npx homebridge-samsung-rac-probe writes --host 10.0.0.9 --power-on
79
+ ```
80
+
81
+ It ships with the plugin, so it works from an ordinary install. From a checkout,
82
+ `npm run probe -- dump --host 10.0.0.9` runs the same thing through ts-node.
83
+
84
+ Progress goes to stderr and the payload to stdout, so either can be redirected
85
+ without the running commentary landing in the file:
86
+
87
+ ```bash
88
+ npx homebridge-samsung-rac-probe dump --host 10.0.0.9 > unit.json
89
+ npx homebridge-samsung-rac-probe writes --host 10.0.0.9 --power-on > report.md
90
+ ```
91
+
92
+ `dump` is the first thing to ask for in a bug report about an unfamiliar model:
93
+ it separates a parsing bug from a genuine hardware difference, and its stdout is
94
+ the device's own JSON.
95
+
96
+ `writes` matters more than it sounds. **This hardware answers HTTP 200 to commands
97
+ it silently discards.** The probe writes a value, reads it back, and counts only a
98
+ *changed read-back* as success. It restores everything it touched and leaves a
99
+ report, printed to stdout (`--out <path>` saves it to a file instead). The
100
+ findings for the reference unit are summarised below.
101
+
102
+ Always pass `--power-on`. On the reference unit **every write except power is
103
+ silently discarded while it is off** — 200, no error, no effect — so a run without
104
+ it says nothing about whether a field is writable.
105
+
106
+ The plugin applies the same rule at runtime: every write is confirmed by a
107
+ read-back, and a value the unit quietly refuses is corrected in the Home app
108
+ rather than left showing something that never happened.
109
+
110
+ ### What the reference unit accepts
111
+
112
+ Measured on a TP6X_RAC_16K, 2026-09-15:
113
+
114
+ | | |
115
+ |---|---|
116
+ | Method | `PUT` only — `POST` returns 405 |
117
+ | Power, fan speed, setpoint, mode | all applied |
118
+ | Vane | `Fix` and `Up_And_Low` applied; `Vertical` and `SwingUD` rejected with 400; `All` accepted and then ignored |
119
+
120
+ Consequence worth knowing: **changing anything in the Home app while the unit is
121
+ off does nothing**, and the tile will snap back after a second. Turn it on first.
122
+ That is the hardware's behaviour, not the plugin's — the plugin reports it
123
+ honestly rather than pretending the change stuck.
124
+
125
+ ## Configuration
126
+
127
+ Everything is editable in the Homebridge UI. The equivalent `config.json`:
128
+
129
+ ```json
130
+ {
131
+ "platform": "SamsungRacLocal",
132
+ "devices": [
133
+ { "name": "Lounge", "host": "10.0.0.9" }
134
+ ],
135
+ "updateInterval": 10,
136
+ "swingDirection": "Up_And_Low"
137
+ }
138
+ ```
139
+
140
+ | Key | Default | |
141
+ |---|---|---|
142
+ | `devices[].host` | — | The unit's IP address. Required. |
143
+ | `devices[].name` | the unit's own name | Shown in the Home app. |
144
+ | `devices[].token` | — | Only if you paired outside this plugin. |
145
+ | `updateInterval` | `10` | Seconds between polls; minimum 5. |
146
+ | `swingDirection` | `Up_And_Low` | What to set the vane to for "swing on". Units differ; the log says if yours ignores it. |
147
+ | `requestTimeout` | `5` | Seconds. |
148
+ | `certificateUrl` | community URL | Only if you mirror the certificate yourself. |
149
+
150
+ ## Security notes
151
+
152
+ - The client certificate contains a private key and is **not** in this
153
+ repository. It is fetched at setup time and written `0600` under your
154
+ Homebridge storage path.
155
+ - Device tokens grant full local control of the air conditioner. They are stored
156
+ `0600` in the same directory, not in `config.json`.
157
+ - The connection to the unit uses TLS 1.0 with OpenSSL security level 0, because
158
+ that is all its firmware speaks. It never leaves your network.
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ npm install
164
+ npm test
165
+ npm run lint
166
+ npm run build
167
+ npm run watch # rebuild and restart Homebridge on change
168
+ ```
169
+
170
+ Unit tests never touch the network: `nock` is locked down and `fetch` is replaced
171
+ outright. The fixture in `tests/fixtures/devices.json` is a real capture from the
172
+ reference unit.
@@ -0,0 +1,85 @@
1
+ {
2
+ "pluginAlias": "SamsungRacLocal",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "customUi": true,
6
+ "headerDisplay": "Controls a Samsung room air conditioner over its local network API. Add each unit's IP address below, then pair it from the card above — pairing needs the unit powered off and back on, so it cannot be done from this form alone.",
7
+ "schema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "devices": {
11
+ "title": "Air conditioners",
12
+ "type": "array",
13
+ "items": {
14
+ "type": "object",
15
+ "properties": {
16
+ "name": {
17
+ "title": "Name",
18
+ "type": "string",
19
+ "description": "Shown in the Home app. Leave empty to use the name the unit reports."
20
+ },
21
+ "host": {
22
+ "title": "IP address",
23
+ "type": "string",
24
+ "required": true,
25
+ "format": "ipv4",
26
+ "description": "The unit's address on your network. Give it a fixed DHCP lease on your router — the plugin cannot follow it if it moves."
27
+ },
28
+ "token": {
29
+ "title": "Device token (optional)",
30
+ "type": "string",
31
+ "description": "Only needed if you paired outside this plugin. Leave empty and use Pair above; tokens paired there are stored outside config.json."
32
+ }
33
+ }
34
+ }
35
+ },
36
+ "updateInterval": {
37
+ "title": "Status poll interval (seconds)",
38
+ "type": "integer",
39
+ "default": 10,
40
+ "minimum": 5,
41
+ "description": "How often to read the unit. This is a local network request, so it can be frequent."
42
+ },
43
+ "swingDirection": {
44
+ "title": "Swing direction",
45
+ "type": "string",
46
+ "default": "Up_And_Low",
47
+ "oneOf": [
48
+ { "title": "Up and down (confirmed working)", "enum": ["Up_And_Low"] },
49
+ { "title": "All directions", "enum": ["All"] },
50
+ { "title": "Vertical", "enum": ["Vertical"] },
51
+ { "title": "Swing up/down", "enum": ["SwingUD"] }
52
+ ],
53
+ "description": "What to set the vane to when swing is switched on in HomeKit. On a TP6X_RAC_16K only Up_And_Low works: Vertical and SwingUD are rejected outright, and All is accepted and then ignored. The others are kept for units that differ; the log says if yours refuses the one you pick."
54
+ },
55
+ "requestTimeout": {
56
+ "title": "Request timeout (seconds)",
57
+ "type": "integer",
58
+ "default": 5,
59
+ "minimum": 1
60
+ },
61
+ "certificateUrl": {
62
+ "title": "Client certificate URL",
63
+ "type": "string",
64
+ "description": "Advanced. Where to download the Samsung client certificate from. Change this only if you mirror it yourself."
65
+ }
66
+ }
67
+ },
68
+ "layout": [
69
+ {
70
+ "key": "devices",
71
+ "type": "array",
72
+ "buttonText": "Add an air conditioner",
73
+ "items": ["devices[].name", "devices[].host", "devices[].token"]
74
+ },
75
+ "updateInterval",
76
+ "swingDirection",
77
+ {
78
+ "type": "fieldset",
79
+ "title": "Advanced",
80
+ "expandable": true,
81
+ "expanded": false,
82
+ "items": ["requestTimeout", "certificateUrl"]
83
+ }
84
+ ]
85
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Command-line driver over the plugin's own transport modules.
4
+ *
5
+ * It answered the question this plugin was built around — does the hardware
6
+ * apply writes, or accept and discard them the way the SmartThings cloud does? —
7
+ * and stays as the tool to reach for when the plugin meets real hardware:
8
+ *
9
+ * - `dump` tells a parsing bug apart from a hardware difference when someone
10
+ * reports a model that behaves unlike the reference unit;
11
+ * - `writes` establishes what a different model accepts, which matters because
12
+ * the accepted values ARE model-specific (this unit rejects `Vertical`
13
+ * outright while accepting `Up_And_Low`);
14
+ * - `pair` is the way through when Homebridge runs in a container that the air
15
+ * conditioner's callback cannot reach.
16
+ *
17
+ * Shipped as a bin, so it is reachable from an ordinary install rather than only
18
+ * from a checkout:
19
+ *
20
+ * npx homebridge-samsung-rac-probe dump --host 172.24.0.125
21
+ *
22
+ * From a checkout, `npm run probe -- dump --host ...` runs it through ts-node.
23
+ */
24
+ export {};