homebridge-ge-ac 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.2.1] - 2026-06-21
8
+
9
+ ### Fixed
10
+ - Mode switches could show as generic "Switch 1/2/3" in the Home app after re-pairing. Switched
11
+ to the canonical `ConfiguredName` pattern (declare as optional + set a plain writable value, no
12
+ `onGet` override) so the Home app reliably reads the real names and user renames survive restarts.
13
+
14
+ ### Changed
15
+ - The **Fan** tile now sits right after the thermostat (before the mode switches).
16
+
17
+ ## [1.2.0] - 2026-06-21
18
+
19
+ ### Added
20
+ - **Dedicated Fan accessory** with an **Auto ⇄ Manual** toggle plus a Low/Med/High speed
21
+ slider, mirroring the GE app's Auto/Low/Medium/High fan settings.
22
+
23
+ ### Changed
24
+ - Fan speed moved off the thermostat's rotation-speed slider — Apple's Home app never
25
+ rendered it there — onto its own Fan service on the same accessory (it groups with the AC).
26
+ - Restored-accessory service changes are now persisted via `updatePlatformAccessories`, so
27
+ added services and removed characteristics survive Homebridge restarts.
28
+
29
+ ### Removed
30
+ - The unused `RotationSpeed` characteristic on the HeaterCooler (the Home app never showed it).
31
+
32
+ ## [1.1.0] - 2026-06-19
33
+
34
+ ### Added
35
+ - **Adaptive temperature range** read from the unit (`0x7B06`) instead of a hard-coded
36
+ 64–86 °F, falling back to 64–86 °F when the unit doesn't report one.
37
+ - **Experimental heat/cool support**: when a unit reports a heating setpoint (`0x7002`), the
38
+ plugin also exposes a Heat thermostat mode, a heating setpoint, and a Heat switch.
39
+ ⚠️ Untested on real heat hardware — please [open an issue](https://github.com/fabricore-eng/homebridge-ge-ac/issues) with results.
40
+
41
+ ### Changed
42
+ - Honest compatibility scoping in the README/description: built and tested only on the
43
+ cooling-only GE Profile **AHTT06BC**; other GE Wi-Fi window ACs are likely but unverified.
44
+ - The per-model "available modes" ERD (`0x7B00`) is deliberately ignored — GE window ACs
45
+ report a value that doesn't match the documented bitmask.
46
+
47
+ ## [1.0.0] - 2026-06-19
48
+
49
+ ### Added
50
+ - Initial release: HomeKit control of GE SmartHQ Wi-Fi window air conditioners over GE's
51
+ realtime **WebSocket** channel — setpoint/mode/fan writes the appliance actually honors
52
+ (REST writes are accepted but silently dropped on this AC line).
53
+ - **HeaterCooler** thermostat: on/off, target temperature, current temperature, with the
54
+ slider grid aligned to whole Fahrenheit degrees so both 64 °F and the maximum set cleanly.
55
+ - **Named mode switches**: Cool, Fan Only, Energy Saver, Dry — each sets `ConfiguredName`, so
56
+ HomeKit never shows "Switch 1–5".
57
+ - **Fan speed** control and **live state updates** pushed from the WebSocket subscription, so
58
+ changes made in the GE app or on the unit reflect in HomeKit in real time.
59
+ - Resilience: app-level keepalive, OAuth token refresh ahead of expiry, and
60
+ exponential-backoff auto-reconnect.
61
+
62
+ [1.2.1]: https://github.com/fabricore-eng/homebridge-ge-ac/releases/tag/v1.2.1
63
+ [1.2.0]: https://github.com/fabricore-eng/homebridge-ge-ac/releases/tag/v1.2.0
64
+ [1.1.0]: https://github.com/fabricore-eng/homebridge-ge-ac/releases/tag/v1.1.0
65
+ [1.0.0]: https://github.com/fabricore-eng/homebridge-ge-ac/releases/tag/v1.0.0
package/README.md CHANGED
@@ -103,6 +103,10 @@ The GE SmartHQ / Brillion protocol details (OAuth flow, WebSocket envelopes, ERD
103
103
  encodings) were derived from the excellent [`simbaja/gehome`](https://github.com/simbaja/gehome)
104
104
  Python library. This plugin is an independent JavaScript implementation.
105
105
 
106
+ ## Changelog
107
+
108
+ See [CHANGELOG.md](CHANGELOG.md) for per-release notes.
109
+
106
110
  ## License
107
111
 
108
112
  ISC — see [LICENSE](LICENSE).
package/lib/accessory.js CHANGED
@@ -84,6 +84,27 @@ class ACAccessory {
84
84
  hc.getCharacteristic(this.Ch.TemperatureDisplayUnits)
85
85
  .onGet(() => (this.client.getState(this.mac, C.ERD.TEMP_UNIT) === '01') ? this.Ch.TemperatureDisplayUnits.CELSIUS : this.Ch.TemperatureDisplayUnits.FAHRENHEIT);
86
86
 
87
+ // ---- Fan (Fanv2): Auto/Manual toggle + Low/Med/High slider, bound to fan ERD 0x7A00 ----
88
+ // Built right after the thermostat so its tile sits second, before the mode switches.
89
+ // Apple Home renders this as a proper Fan tile (with an Auto switch), unlike a HeaterCooler's RotationSpeed.
90
+ const fan = accessory.getService(this.S.Fanv2) || accessory.addService(this.S.Fanv2, `${name} Fan`, 'GEAC_FANV2');
91
+ this.fan = fan;
92
+ this._configuredName(fan, 'Fan');
93
+ const TFS = this.Ch.TargetFanState, CFS = this.Ch.CurrentFanState, ACT = this.Ch.Active;
94
+ fan.getCharacteristic(ACT)
95
+ .onGet(() => this._power() ? ACT.ACTIVE : ACT.INACTIVE)
96
+ .onSet(this._wrap(async (v) => { await this.client.setErd(this.mac, C.ERD.POWER, v === ACT.ACTIVE ? C.POWER.ON : C.POWER.OFF); }));
97
+ fan.getCharacteristic(CFS)
98
+ .onGet(() => this._power() ? CFS.BLOWING_AIR : CFS.INACTIVE);
99
+ fan.getCharacteristic(TFS)
100
+ .onGet(() => this._fanIsAuto() ? TFS.AUTO : TFS.MANUAL)
101
+ .onSet(this._wrap(async (v) => {
102
+ await this.client.setErd(this.mac, C.ERD.FAN, v === TFS.AUTO ? C.FAN.AUTO : this._fanFromPct(this._fanSpeedPct()));
103
+ }));
104
+ fan.getCharacteristic(this.Ch.RotationSpeed) // continuous slider; onSet maps to Low(<=34)/Med(<=67)/High
105
+ .onGet(() => this._fanSpeedPct())
106
+ .onSet(this._wrap(async (pct) => { await this.client.setErd(this.mac, C.ERD.FAN, this._fanFromPct(pct)); }));
107
+
87
108
  // ---- mode switches ----
88
109
  const defs = [
89
110
  ['Cool', 'GEAC_COOL', C.MODE.COOL],
@@ -106,27 +127,6 @@ class ACAccessory {
106
127
  return { sw, modeHex };
107
128
  });
108
129
 
109
- // ---- Fan (Fanv2): Auto/Manual toggle + Low/Med/High slider, bound to fan ERD 0x7A00 ----
110
- // Apple Home renders this as a proper Fan tile (with an Auto switch), unlike a HeaterCooler's
111
- // RotationSpeed. Same accessory, so it groups with the thermostat + mode switches.
112
- const fan = accessory.getService(this.S.Fanv2) || accessory.addService(this.S.Fanv2, `${name} Fan`, 'GEAC_FANV2');
113
- this.fan = fan;
114
- this._configuredName(fan, 'Fan');
115
- const TFS = this.Ch.TargetFanState, CFS = this.Ch.CurrentFanState, ACT = this.Ch.Active;
116
- fan.getCharacteristic(ACT)
117
- .onGet(() => this._power() ? ACT.ACTIVE : ACT.INACTIVE)
118
- .onSet(this._wrap(async (v) => { await this.client.setErd(this.mac, C.ERD.POWER, v === ACT.ACTIVE ? C.POWER.ON : C.POWER.OFF); }));
119
- fan.getCharacteristic(CFS)
120
- .onGet(() => this._power() ? CFS.BLOWING_AIR : CFS.INACTIVE);
121
- fan.getCharacteristic(TFS)
122
- .onGet(() => this._fanIsAuto() ? TFS.AUTO : TFS.MANUAL)
123
- .onSet(this._wrap(async (v) => {
124
- await this.client.setErd(this.mac, C.ERD.FAN, v === TFS.AUTO ? C.FAN.AUTO : this._fanFromPct(this._fanSpeedPct()));
125
- }));
126
- fan.getCharacteristic(this.Ch.RotationSpeed) // continuous slider; onSet maps to Low(<=34)/Med(<=67)/High
127
- .onGet(() => this._fanSpeedPct())
128
- .onSet(this._wrap(async (pct) => { await this.client.setErd(this.mac, C.ERD.FAN, this._fanFromPct(pct)); }));
129
-
130
130
  // live updates
131
131
  this._onErd = (m) => { if (m === this.mac) this._pushAll(); };
132
132
  client.on('erd', this._onErd);
@@ -134,9 +134,13 @@ class ACAccessory {
134
134
  }
135
135
 
136
136
  _configuredName(svc, name) {
137
+ // Canonical pattern for naming multiple same-type services so the Home app shows real names
138
+ // (not "Switch 1/2/3"): declare ConfiguredName as optional and set it as a plain writable value.
139
+ // No onGet override — that both confuses some Home clients and would clobber a user rename.
140
+ // Seed our default only when empty so a user's rename survives restarts.
141
+ svc.addOptionalCharacteristic(this.Ch.ConfiguredName);
142
+ if (!svc.getCharacteristic(this.Ch.ConfiguredName).value) svc.setCharacteristic(this.Ch.ConfiguredName, name);
137
143
  svc.setCharacteristic(this.Ch.Name, name);
138
- if (!svc.testCharacteristic(this.Ch.ConfiguredName)) svc.addCharacteristic(this.Ch.ConfiguredName);
139
- svc.getCharacteristic(this.Ch.ConfiguredName).onGet(() => name).updateValue(name);
140
144
  }
141
145
 
142
146
  // ---- state helpers ----
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-ge-ac",
3
3
  "displayName": "GE Profile AC (SmartHQ WebSocket)",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "description": "Homebridge plugin for GE SmartHQ Wi-Fi window air conditioners over GE's realtime WebSocket channel (reliable setpoint/mode/fan control). Built and tested on the cooling-only GE Profile AHTT06BC; likely works on similar GE Wi-Fi window ACs (unverified).",
6
6
  "author": "Fabricore",
7
7
  "license": "ISC",
@@ -30,7 +30,8 @@
30
30
  "files": [
31
31
  "index.js",
32
32
  "lib/",
33
- "config.schema.json"
33
+ "config.schema.json",
34
+ "CHANGELOG.md"
34
35
  ],
35
36
  "dependencies": {
36
37
  "axios": "^1.7.0",