homebridge-ac-freedom 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0 — Breaking Change
4
+
5
+ - **Config structure flattened** — nested `cloud`, `local`, `presets` objects replaced with flat properties (`cloudEmail`, `cloudPassword`, `cloudRegion`, `cloudDeviceId`, `localIp`, `localMac`, `sleep`, `health`, `eco`, `clean`, `comfwind`, `display`)
6
+ - **Config UI completely rewritten** — no custom layout, no `oneOf` dropdowns, fixes all floating popup bugs
7
+ - **Conditional fields** — cloud/local fields show/hide based on connection mode
8
+ - Existing configs need to be re-entered via Homebridge UI after upgrading
9
+
10
+ ## 1.2.3
11
+
12
+ - Attempt fix for floating popup — replace `oneOf` with `enum` + `titleMap`
13
+
3
14
  ## 1.2.2
4
15
 
5
- - Remove unused `changelog` field from config.schema.json (HB reads CHANGELOG.md directly from GitHub)
16
+ - Remove unused `changelog` field from config.schema.json
6
17
 
7
18
  ## 1.2.1
8
19
 
package/README.md CHANGED
@@ -87,11 +87,9 @@ Add the following to your Homebridge `config.json`:
87
87
  {
88
88
  "name": "Living Room AC",
89
89
  "connection": "cloud",
90
- "cloud": {
91
- "email": "your-email@example.com",
92
- "password": "your-password",
93
- "region": "eu"
94
- }
90
+ "cloudEmail": "your-email@example.com",
91
+ "cloudPassword": "your-password",
92
+ "cloudRegion": "eu"
95
93
  }
96
94
  ]
97
95
  }
@@ -111,10 +109,8 @@ Add the following to your Homebridge `config.json`:
111
109
  {
112
110
  "name": "Bedroom AC",
113
111
  "connection": "local",
114
- "local": {
115
- "ip": "192.168.1.100",
116
- "mac": "AA:BB:CC:DD:EE:FF"
117
- }
112
+ "localIp": "192.168.1.100",
113
+ "localMac": "AA:BB:CC:DD:EE:FF"
118
114
  }
119
115
  ]
120
116
  }
@@ -133,32 +129,32 @@ Add the following to your Homebridge `config.json`:
133
129
  | `pollInterval` | integer | `30` | State polling interval in seconds (5--300) |
134
130
  | `tempStep` | number | `0.5` | Temperature adjustment step: `0.5` or `1` |
135
131
 
136
- #### Cloud Options
132
+ #### Cloud Options (when `connection` is `"cloud"`)
137
133
 
138
134
  | Option | Type | Required | Description |
139
135
  |--------|------|----------|-------------|
140
- | `email` | string | Yes | AC Freedom app email |
141
- | `password` | string | Yes | AC Freedom app password |
142
- | `region` | string | No | Server region: `"eu"`, `"usa"`, `"cn"`, `"rus"` (default: `"eu"`) |
143
- | `deviceId` | string | No | Specific device endpoint ID (leave empty for auto-detect) |
136
+ | `cloudEmail` | string | Yes | AC Freedom app email |
137
+ | `cloudPassword` | string | Yes | AC Freedom app password |
138
+ | `cloudRegion` | string | No | Server region: `"eu"`, `"usa"`, `"cn"`, `"rus"` (default: `"eu"`) |
139
+ | `cloudDeviceId` | string | No | Specific device endpoint ID (leave empty for auto-detect) |
144
140
 
145
- #### Local Options
141
+ #### Local Options (when `connection` is `"local"`)
146
142
 
147
143
  | Option | Type | Required | Description |
148
144
  |--------|------|----------|-------------|
149
- | `ip` | string | Yes | AC unit's IP address on your local network |
150
- | `mac` | string | Yes | AC unit's MAC address (`AA:BB:CC:DD:EE:FF` format) |
145
+ | `localIp` | string | Yes | AC unit's IP address on your local network |
146
+ | `localMac` | string | Yes | AC unit's MAC address (`AA:BB:CC:DD:EE:FF` format) |
151
147
 
152
148
  #### Feature Switches
153
149
 
154
150
  | Option | Type | Default | Description |
155
151
  |--------|------|---------|-------------|
156
- | `presets.sleep` | boolean | `true` | Show Sleep Mode switch |
157
- | `presets.health` | boolean | `true` | Show Health / Ionizer switch |
158
- | `presets.eco` | boolean | `true` | Show Eco / Mildew Prevention switch |
159
- | `presets.clean` | boolean | `true` | Show Self Clean switch |
160
- | `showComfWind` | boolean | `true` | Show Comfortable Wind switch |
161
- | `showDisplay` | boolean | `true` | Show Display (LED) switch |
152
+ | `sleep` | boolean | `true` | Show Sleep Mode switch |
153
+ | `health` | boolean | `true` | Show Health / Ionizer switch |
154
+ | `eco` | boolean | `true` | Show Eco / Mildew Prevention switch |
155
+ | `clean` | boolean | `true` | Show Self Clean switch |
156
+ | `comfwind` | boolean | `true` | Show Comfortable Wind switch |
157
+ | `display` | boolean | `true` | Show Display (LED) switch |
162
158
 
163
159
  ## HomeKit Climate Card
164
160
 
@@ -5,11 +5,11 @@
5
5
  "headerDisplay": "AUX Air Conditioner control via Broadlink (local) or AUX Cloud API.",
6
6
  "schema": {
7
7
  "type": "object",
8
- "required": ["name"],
9
8
  "properties": {
10
9
  "name": {
11
10
  "title": "Platform Name",
12
11
  "type": "string",
12
+ "required": true,
13
13
  "default": "AC Freedom"
14
14
  },
15
15
  "devices": {
@@ -17,78 +17,81 @@
17
17
  "type": "array",
18
18
  "items": {
19
19
  "type": "object",
20
- "required": ["name", "connection"],
21
20
  "properties": {
22
21
  "name": {
23
22
  "title": "Device Name",
24
23
  "type": "string",
24
+ "required": true,
25
25
  "default": "AC Freedom"
26
26
  },
27
27
  "connection": {
28
28
  "title": "Connection Mode",
29
29
  "type": "string",
30
+ "required": true,
30
31
  "default": "cloud",
31
- "oneOf": [
32
- { "title": "Cloud (AUX Cloud API)", "enum": ["cloud"] },
33
- { "title": "Local (Broadlink UDP)", "enum": ["local"] }
34
- ]
32
+ "enum": ["cloud", "local"]
35
33
  },
36
- "cloud": {
37
- "type": "object",
38
- "properties": {
39
- "email": {
40
- "title": "Email",
41
- "type": "string"
42
- },
43
- "password": {
44
- "title": "Password",
45
- "type": "string"
46
- },
47
- "region": {
48
- "title": "Region",
49
- "type": "string",
50
- "default": "eu",
51
- "oneOf": [
52
- { "title": "Europe", "enum": ["eu"] },
53
- { "title": "USA", "enum": ["usa"] },
54
- { "title": "China", "enum": ["cn"] },
55
- { "title": "Russia", "enum": ["rus"] }
56
- ]
57
- },
58
- "deviceId": {
59
- "title": "Device ID (endpointId, leave empty for auto-detect)",
60
- "type": "string"
61
- }
62
- }
34
+ "cloudEmail": {
35
+ "title": "Cloud Email",
36
+ "type": "string",
37
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'cloud';" }
38
+ },
39
+ "cloudPassword": {
40
+ "title": "Cloud Password",
41
+ "type": "string",
42
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'cloud';" }
43
+ },
44
+ "cloudRegion": {
45
+ "title": "Cloud Region",
46
+ "type": "string",
47
+ "default": "eu",
48
+ "enum": ["eu", "usa", "cn", "rus"],
49
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'cloud';" }
50
+ },
51
+ "cloudDeviceId": {
52
+ "title": "Cloud Device ID (leave empty for auto-detect)",
53
+ "type": "string",
54
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'cloud';" }
55
+ },
56
+ "localIp": {
57
+ "title": "IP Address",
58
+ "type": "string",
59
+ "format": "ipv4",
60
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'local';" }
61
+ },
62
+ "localMac": {
63
+ "title": "MAC Address",
64
+ "type": "string",
65
+ "pattern": "^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$",
66
+ "placeholder": "AA:BB:CC:DD:EE:FF",
67
+ "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'local';" }
63
68
  },
64
- "local": {
65
- "type": "object",
66
- "properties": {
67
- "ip": {
68
- "title": "IP Address",
69
- "type": "string"
70
- },
71
- "mac": {
72
- "title": "MAC Address (AA:BB:CC:DD:EE:FF)",
73
- "type": "string"
74
- }
75
- }
69
+ "sleep": {
70
+ "title": "Sleep Mode",
71
+ "type": "boolean",
72
+ "default": true
76
73
  },
77
- "presets": {
78
- "type": "object",
79
- "properties": {
80
- "sleep": { "title": "Sleep Mode", "type": "boolean", "default": true },
81
- "health": { "title": "Health / Ionizer", "type": "boolean", "default": true },
82
- "eco": { "title": "Eco / Mildew Prevention", "type": "boolean", "default": true },
83
- "clean": { "title": "Self Clean", "type": "boolean", "default": true }
84
- }
74
+ "health": {
75
+ "title": "Health / Ionizer",
76
+ "type": "boolean",
77
+ "default": true
85
78
  },
86
- "showComfWind": {
79
+ "eco": {
80
+ "title": "Eco / Mildew Prevention",
81
+ "type": "boolean",
82
+ "default": true
83
+ },
84
+ "clean": {
85
+ "title": "Self Clean",
86
+ "type": "boolean",
87
+ "default": true
88
+ },
89
+ "comfwind": {
87
90
  "title": "Comfortable Wind",
88
91
  "type": "boolean",
89
92
  "default": true
90
93
  },
91
- "showDisplay": {
94
+ "display": {
92
95
  "title": "Display",
93
96
  "type": "boolean",
94
97
  "default": true
@@ -104,64 +107,14 @@
104
107
  "title": "Temperature Step",
105
108
  "type": "number",
106
109
  "default": 0.5,
107
- "oneOf": [
108
- { "title": "0.5 °C", "enum": [0.5] },
109
- { "title": "1 °C", "enum": [1] }
110
- ]
110
+ "minimum": 0.5,
111
+ "maximum": 1
111
112
  }
112
113
  }
113
114
  }
114
115
  }
115
116
  }
116
117
  },
117
- "layout": [
118
- { "key": "name" },
119
- {
120
- "key": "devices",
121
- "type": "tabarray",
122
- "title": "{{ value.name || 'New Device' }}",
123
- "buttonText": "Add Device",
124
- "items": [
125
- { "key": "devices[].name" },
126
- { "key": "devices[].connection" },
127
- {
128
- "key": "devices[].cloud",
129
- "type": "fieldset",
130
- "title": "Cloud Settings",
131
- "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'cloud';" },
132
- "items": [
133
- { "key": "devices[].cloud.email" },
134
- { "key": "devices[].cloud.password" },
135
- { "key": "devices[].cloud.region" },
136
- { "key": "devices[].cloud.deviceId" }
137
- ]
138
- },
139
- {
140
- "key": "devices[].local",
141
- "type": "fieldset",
142
- "title": "Local Settings",
143
- "condition": { "functionBody": "return model.devices[arrayIndices[0]].connection === 'local';" },
144
- "items": [
145
- { "key": "devices[].local.ip" },
146
- { "key": "devices[].local.mac" }
147
- ]
148
- },
149
- {
150
- "key": "devices[].presets",
151
- "type": "fieldset",
152
- "title": "Preset Modes",
153
- "items": [
154
- { "key": "devices[].presets.sleep" },
155
- { "key": "devices[].presets.health" },
156
- { "key": "devices[].presets.eco" },
157
- { "key": "devices[].presets.clean" }
158
- ]
159
- },
160
- { "key": "devices[].showComfWind" },
161
- { "key": "devices[].showDisplay" },
162
- { "key": "devices[].pollInterval" },
163
- { "key": "devices[].tempStep" }
164
- ]
165
- }
166
- ]
118
+ "form": null,
119
+ "display": null
167
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-ac-freedom",
3
- "version": "1.2.2",
3
+ "version": "2.0.0",
4
4
  "displayName": "AC Freedom",
5
5
  "description": "Homebridge plugin for AUX air conditioners via Broadlink (local UDP) and AUX Cloud API",
6
6
  "main": "index.js",
@@ -210,7 +210,12 @@ class AcFreedomAccessory {
210
210
 
211
211
  // ── Preset Switches (linked to HeaterCooler) ──────────────────
212
212
  setupPresetSwitches() {
213
- const presets = this.config.presets || { sleep: true, health: true, eco: true, clean: true };
213
+ const presets = {
214
+ sleep: this.config.sleep !== false,
215
+ health: this.config.health !== false,
216
+ eco: this.config.eco !== false,
217
+ clean: this.config.clean !== false,
218
+ };
214
219
 
215
220
  this.presetSwitches = {};
216
221
 
@@ -254,7 +259,7 @@ class AcFreedomAccessory {
254
259
 
255
260
  // ── Comfortable Wind Switch (linked to HeaterCooler) ────────────
256
261
  setupComfWindSwitch() {
257
- if (this.config.showComfWind === false) {
262
+ if (this.config.comfwind === false) {
258
263
  const existing = this.accessory.getServiceById(this.Service.Switch, 'comfwind');
259
264
  if (existing) this.accessory.removeService(existing);
260
265
  return;
@@ -277,7 +282,7 @@ class AcFreedomAccessory {
277
282
 
278
283
  // ── Display Switch (linked to HeaterCooler) ────────────────────
279
284
  setupDisplaySwitch() {
280
- if (this.config.showDisplay === false) {
285
+ if (this.config.display === false) {
281
286
  const existing = this.accessory.getServiceById(this.Service.Switch, 'display');
282
287
  if (existing) this.accessory.removeService(existing);
283
288
  return;
@@ -351,8 +356,7 @@ class AcFreedomAccessory {
351
356
  if (err.message && err.message.includes('server busy')) return;
352
357
  // Re-login on token expiry
353
358
  if (err.message && err.message.includes('token')) {
354
- const cloud = this.config.cloud;
355
- await api.login(cloud.email, cloud.password);
359
+ await api.login(this.config.cloudEmail, this.config.cloudPassword);
356
360
  }
357
361
  throw err;
358
362
  }
package/src/platform.js CHANGED
@@ -45,8 +45,8 @@ class AcFreedomPlatform {
45
45
  async setupDevice(deviceConfig) {
46
46
  const uuid = this.api.hap.uuid.generate(
47
47
  deviceConfig.connection === 'cloud'
48
- ? `ac-freedom-cloud-${deviceConfig.cloud?.email}-${deviceConfig.cloud?.deviceId || 'auto'}`
49
- : `ac-freedom-local-${deviceConfig.local?.ip}-${deviceConfig.local?.mac}`,
48
+ ? `ac-freedom-cloud-${deviceConfig.cloudEmail}-${deviceConfig.cloudDeviceId || 'auto'}`
49
+ : `ac-freedom-local-${deviceConfig.localIp}-${deviceConfig.localMac}`,
50
50
  );
51
51
 
52
52
  const existingAccessory = this.accessories.get(uuid);
@@ -74,16 +74,15 @@ class AcFreedomPlatform {
74
74
  }
75
75
 
76
76
  async setupCloudDevice(config) {
77
- const cloud = config.cloud;
78
- if (!cloud || !cloud.email || !cloud.password) {
77
+ if (!config.cloudEmail || !config.cloudPassword) {
79
78
  this.log.error('Cloud config missing email/password');
80
79
  return null;
81
80
  }
82
81
 
83
- const api = new AuxCloudAPI(cloud.region || 'eu');
82
+ const api = new AuxCloudAPI(config.cloudRegion || 'eu');
84
83
  try {
85
- await api.login(cloud.email, cloud.password);
86
- this.log.info('Cloud login successful: %s', cloud.email);
84
+ await api.login(config.cloudEmail, config.cloudPassword);
85
+ this.log.info('Cloud login successful: %s', config.cloudEmail);
87
86
 
88
87
  const families = await api.getFamilies();
89
88
  let devices = [];
@@ -92,8 +91,8 @@ class AcFreedomPlatform {
92
91
  devices.push(...devs);
93
92
  }
94
93
 
95
- if (cloud.deviceId) {
96
- devices = devices.filter(d => d.endpointId === cloud.deviceId);
94
+ if (config.cloudDeviceId) {
95
+ devices = devices.filter(d => d.endpointId === config.cloudDeviceId);
97
96
  }
98
97
 
99
98
  if (devices.length === 0) {
@@ -111,20 +110,19 @@ class AcFreedomPlatform {
111
110
  }
112
111
 
113
112
  async setupLocalDevice(config) {
114
- const local = config.local;
115
- if (!local || !local.ip || !local.mac) {
113
+ if (!config.localIp || !config.localMac) {
116
114
  this.log.error('Local config missing ip/mac');
117
115
  return null;
118
116
  }
119
117
 
120
- const api = new BroadlinkAcApi(local.ip, local.mac);
118
+ const api = new BroadlinkAcApi(config.localIp, config.localMac);
121
119
  try {
122
120
  const connected = await api.connect();
123
121
  if (!connected) {
124
122
  this.log.error('Failed to connect to local device at %s', local.ip);
125
123
  return null;
126
124
  }
127
- this.log.info('Local device connected: %s', local.ip);
125
+ this.log.info('Local device connected: %s', config.localIp);
128
126
  return { type: 'local', api };
129
127
  } catch (err) {
130
128
  this.log.error('Local connection failed: %s', err.message);