homebridge-aiseg-awning-window-command 0.2.4 → 0.2.5
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 +45 -0
- package/README.md +73 -40
- package/config.schema.json +0 -36
- package/index.js +43 -4
- package/package.json +13 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.5
|
|
4
|
+
|
|
5
|
+
- Improved the snap behavior of the HomeKit tilt slider for AiSEG2 awning windows.
|
|
6
|
+
- Tilt values now snap more firmly to 0°, 15°, 30°, 45°, and 60°.
|
|
7
|
+
- Updated the README and CHANGELOG documentation.
|
|
8
|
+
- Hid internal AiSEG request parameters to simplify the Homebridge UI settings.
|
|
9
|
+
|
|
10
|
+
### Notes
|
|
11
|
+
|
|
12
|
+
This plugin is designed and tested for Sankyo Aluminum awning windows connected to AiSEG2.
|
|
13
|
+
|
|
14
|
+
Normal users only need:
|
|
15
|
+
|
|
16
|
+
- AiSEG2 IP address or host name
|
|
17
|
+
- AiSEG web password
|
|
18
|
+
- Node ID for each window
|
|
19
|
+
|
|
20
|
+
Each window's Node ID can be checked on the AiSEG2 web page under **Device Control > Window Sash**.
|
|
21
|
+
Open the browser developer tools with **F12**, go to **Network > Fetch/XHR**, press an action button, open the `operation` request, and check the 9-digit `nodeId` value in the request payload.
|
|
22
|
+
|
|
23
|
+
## 0.2.4
|
|
24
|
+
|
|
25
|
+
- Refactored Python scripts to use a shared `aiseg_device.py` core.
|
|
26
|
+
- Kept `aiseg_window.py` as a compatibility wrapper.
|
|
27
|
+
- Continued to bundle package-local Python scripts.
|
|
28
|
+
- Continued to avoid Python `requests` dependency.
|
|
29
|
+
|
|
30
|
+
## 0.2.3
|
|
31
|
+
|
|
32
|
+
- Bundled the AiSEG awning Python script inside the npm package.
|
|
33
|
+
- Removed fixed private IP, password, and node ID values from the packaged script.
|
|
34
|
+
- Passed user-specific values from Homebridge config to Python via environment variables.
|
|
35
|
+
- Removed dependency on the external Python `requests` package.
|
|
36
|
+
|
|
37
|
+
## 0.2.2
|
|
38
|
+
|
|
39
|
+
- Improved HomeKit awning window behavior.
|
|
40
|
+
- Added better motion guard handling for open, close, and tilt operations.
|
|
41
|
+
- Improved HomeKit display consistency during AiSEG state polling.
|
|
42
|
+
|
|
43
|
+
## 0.2.1 and earlier
|
|
44
|
+
|
|
45
|
+
- Early development releases for AiSEG2 awning window control.
|
package/README.md
CHANGED
|
@@ -1,62 +1,95 @@
|
|
|
1
1
|
# homebridge-aiseg-awning-window-command
|
|
2
2
|
|
|
3
|
-
Homebridge plugin for AiSEG2 awning windows
|
|
3
|
+
Homebridge plugin for AiSEG2-connected awning windows.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This plugin exposes AiSEG2 awning windows as HomeKit `WindowCovering` accessories.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Features
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
- Open / close control from HomeKit
|
|
10
|
+
- HomeKit horizontal tilt angle control
|
|
11
|
+
- Stop switch accessory
|
|
12
|
+
- Status polling from AiSEG2
|
|
13
|
+
- Motion guard to reduce stale-state jumps
|
|
14
|
+
- Strong tilt snapping for HomeKit
|
|
15
|
+
- Package-local Python scripts included
|
|
16
|
+
- No external Python `requests` dependency
|
|
17
|
+
- Shared AiSEG Python core used internally
|
|
11
18
|
|
|
12
|
-
##
|
|
19
|
+
## Supported HomeKit tilt values
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
- Node.js 22 or later
|
|
16
|
-
- Working command or script for device operation
|
|
17
|
-
- Optional status command for state polling
|
|
21
|
+
The HomeKit tilt slider is snapped to the following values:
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
| HomeKit tilt | AiSEG command |
|
|
24
|
+
|---:|---|
|
|
25
|
+
| 0° | close |
|
|
26
|
+
| 15° | angle1 |
|
|
27
|
+
| 30° | angle2 |
|
|
28
|
+
| 45° | angle3 |
|
|
29
|
+
| 60° | open |
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
31
|
+
Starting from v0.2.5, the plugin more strongly echoes the snapped tilt value back to HomeKit.
|
|
32
|
+
This helps prevent the Home app slider from visually stopping around intermediate values such as 25°.
|
|
24
33
|
|
|
25
|
-
##
|
|
34
|
+
## Notes
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
This plugin is designed and tested for Sankyo Aluminum awning windows connected to AiSEG2.
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
Normal users only need to configure:
|
|
30
39
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- status command
|
|
35
|
-
- polling interval
|
|
36
|
-
- movement guard / transition timing where supported
|
|
40
|
+
- AiSEG2 IP address or host name
|
|
41
|
+
- AiSEG web password
|
|
42
|
+
- Node ID for each window
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
Internal AiSEG request parameters are hidden from the Homebridge UI because they normally do not need to be changed.
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
Prepare and test the command scripts before configuring Homebridge.
|
|
46
|
+
## How to find the Node ID
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
You can check each window's Node ID from the AiSEG2 web page:
|
|
49
|
+
|
|
50
|
+
1. Open the AiSEG2 web interface.
|
|
51
|
+
2. Go to **Device Control > Window Sash**.
|
|
52
|
+
3. Open the browser developer tools with **F12**.
|
|
53
|
+
4. Go to the **Network** tab and filter by **Fetch/XHR**.
|
|
54
|
+
5. Press an action button for the target window.
|
|
55
|
+
6. Open the `operation` request.
|
|
56
|
+
7. Check the request payload. The window Node ID is the 9-digit `nodeId` value.
|
|
57
|
+
|
|
58
|
+
## Tested environment
|
|
59
|
+
|
|
60
|
+
- AiSEG2
|
|
61
|
+
- Sankyo Aluminum awning windows
|
|
62
|
+
- AiSEG adapter / related Panasonic equipment: MKN7751K
|
|
63
|
+
- Homebridge 2.x
|
|
64
|
+
- Node.js 24.x
|
|
44
65
|
|
|
45
|
-
|
|
46
|
-
python3 /path/to/script.py target status
|
|
47
|
-
python3 /path/to/script.py target open
|
|
48
|
-
python3 /path/to/script.py target close
|
|
49
|
-
```
|
|
66
|
+
## Hidden internal settings
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
The following AiSEG request parameters are intentionally hidden from the Homebridge UI:
|
|
69
|
+
|
|
70
|
+
- device type
|
|
71
|
+
- page
|
|
72
|
+
- page326
|
|
73
|
+
- track
|
|
74
|
+
- request_by_form
|
|
75
|
+
- acceptId
|
|
76
|
+
|
|
77
|
+
The plugin still uses internal defaults for these values.
|
|
78
|
+
|
|
79
|
+
## Related plugin
|
|
80
|
+
|
|
81
|
+
For AiSEG3 blind shutters, use:
|
|
82
|
+
|
|
83
|
+
- `homebridge-aiseg-shutter-command`
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
52
86
|
|
|
53
|
-
|
|
87
|
+
- Homebridge
|
|
88
|
+
- Node.js
|
|
89
|
+
- Python 3 available in the Homebridge environment
|
|
54
90
|
|
|
55
|
-
|
|
56
|
-
2. Confirm the command path is correct inside the Homebridge environment.
|
|
57
|
-
3. Confirm the Homebridge container has permission to execute the command.
|
|
58
|
-
4. Check Homebridge logs for command timeout or stderr output.
|
|
91
|
+
No additional Python package installation is required.
|
|
59
92
|
|
|
60
|
-
##
|
|
93
|
+
## Version history
|
|
61
94
|
|
|
62
|
-
|
|
95
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
package/config.schema.json
CHANGED
|
@@ -115,42 +115,6 @@
|
|
|
115
115
|
"aisegId"
|
|
116
116
|
]
|
|
117
117
|
}
|
|
118
|
-
},
|
|
119
|
-
"deviceType": {
|
|
120
|
-
"title": "種別",
|
|
121
|
-
"description": "AiSEG2オーニング窓の種別です。通常は 0x0f です。",
|
|
122
|
-
"type": "string",
|
|
123
|
-
"default": "0x0f"
|
|
124
|
-
},
|
|
125
|
-
"page": {
|
|
126
|
-
"title": "Page",
|
|
127
|
-
"description": "詳細設定です。通常は 1 です。",
|
|
128
|
-
"type": "string",
|
|
129
|
-
"default": "1"
|
|
130
|
-
},
|
|
131
|
-
"page326": {
|
|
132
|
-
"title": "Page326",
|
|
133
|
-
"description": "詳細設定です。通常は 1 です。",
|
|
134
|
-
"type": "string",
|
|
135
|
-
"default": "1"
|
|
136
|
-
},
|
|
137
|
-
"track": {
|
|
138
|
-
"title": "Track",
|
|
139
|
-
"description": "詳細設定です。通常は 326 です。",
|
|
140
|
-
"type": "string",
|
|
141
|
-
"default": "326"
|
|
142
|
-
},
|
|
143
|
-
"requestByForm": {
|
|
144
|
-
"title": "Request By Form",
|
|
145
|
-
"description": "詳細設定です。通常は 1 です。",
|
|
146
|
-
"type": "string",
|
|
147
|
-
"default": "1"
|
|
148
|
-
},
|
|
149
|
-
"acceptId": {
|
|
150
|
-
"title": "Accept ID",
|
|
151
|
-
"description": "詳細設定です。通常は 65550 です。",
|
|
152
|
-
"type": "string",
|
|
153
|
-
"default": "65550"
|
|
154
118
|
}
|
|
155
119
|
},
|
|
156
120
|
"required": [
|
package/index.js
CHANGED
|
@@ -162,17 +162,52 @@ class AwningWindow {
|
|
|
162
162
|
.onGet(() => this.positionState);
|
|
163
163
|
|
|
164
164
|
this.service.getCharacteristic(this.Characteristic.CurrentHorizontalTiltAngle)
|
|
165
|
-
.setProps({ minValue: 0, maxValue: 60, minStep: 15 })
|
|
165
|
+
.setProps({ minValue: 0, maxValue: 60, minStep: 15, validValues: [0, 15, 30, 45, 60] })
|
|
166
166
|
.onGet(() => this.currentTilt);
|
|
167
167
|
|
|
168
168
|
this.service.getCharacteristic(this.Characteristic.TargetHorizontalTiltAngle)
|
|
169
|
-
.setProps({ minValue: 0, maxValue: 60, minStep: 15 })
|
|
169
|
+
.setProps({ minValue: 0, maxValue: 60, minStep: 15, validValues: [0, 15, 30, 45, 60] })
|
|
170
170
|
.onGet(() => this.targetTilt)
|
|
171
171
|
.onSet(async (value) => this.setTargetTilt(value));
|
|
172
172
|
|
|
173
173
|
this.updateHomeKit();
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
|
|
177
|
+
forceTiltSnap(snapped, source, mirrorCurrent) {
|
|
178
|
+
const v = clamp(Number(snapped), 0, 60);
|
|
179
|
+
|
|
180
|
+
this.targetTilt = v;
|
|
181
|
+
|
|
182
|
+
const targetChar = this.service.getCharacteristic(this.Characteristic.TargetHorizontalTiltAngle);
|
|
183
|
+
const currentChar = this.service.getCharacteristic(this.Characteristic.CurrentHorizontalTiltAngle);
|
|
184
|
+
|
|
185
|
+
targetChar.updateValue(v);
|
|
186
|
+
|
|
187
|
+
if (mirrorCurrent) {
|
|
188
|
+
this.currentTilt = v;
|
|
189
|
+
currentChar.updateValue(v);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// HomeKit sometimes keeps the raw slider value briefly.
|
|
193
|
+
// Echo the snapped value several times so 25° etc. returns to 15/30/45/60 cleanly.
|
|
194
|
+
for (const delay of [100, 350, 900]) {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
if (this.targetTilt === v || Date.now() < this.motionUntil) {
|
|
197
|
+
targetChar.updateValue(v);
|
|
198
|
+
|
|
199
|
+
if (mirrorCurrent) {
|
|
200
|
+
currentChar.updateValue(v);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (source && this.lastUnknownStatus === "__never__") {
|
|
204
|
+
// no-op: keep linter-free structure without extra log noise
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}, delay);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
176
211
|
async setTargetPosition(value) {
|
|
177
212
|
const raw = clamp(Number(value), 0, 100);
|
|
178
213
|
const normalized = raw <= 50 ? 0 : 100;
|
|
@@ -201,14 +236,14 @@ class AwningWindow {
|
|
|
201
236
|
|
|
202
237
|
this.log.info(`[${this.name}] TargetHorizontalTiltAngle ${raw} -> ${snapped}`);
|
|
203
238
|
|
|
204
|
-
this.
|
|
205
|
-
this.service.getCharacteristic(this.Characteristic.TargetHorizontalTiltAngle).updateValue(snapped);
|
|
239
|
+
this.forceTiltSnap(snapped, "setTargetTilt-start", false);
|
|
206
240
|
|
|
207
241
|
if (snapped === 0) {
|
|
208
242
|
this.targetPosition = 0;
|
|
209
243
|
this.service.getCharacteristic(this.Characteristic.TargetPosition).updateValue(0);
|
|
210
244
|
this.startMotion("closed", this.motionGuardSeconds, 0);
|
|
211
245
|
await this.runAction("close", "tilt 0 / close");
|
|
246
|
+
this.forceTiltSnap(0, "tilt-close-done", true);
|
|
212
247
|
return;
|
|
213
248
|
}
|
|
214
249
|
|
|
@@ -217,6 +252,7 @@ class AwningWindow {
|
|
|
217
252
|
this.service.getCharacteristic(this.Characteristic.TargetPosition).updateValue(100);
|
|
218
253
|
this.startMotion("open", Math.max(this.motionGuardSeconds, this.openAcceptDelaySeconds), 60);
|
|
219
254
|
await this.runAction("open", "tilt 60 / open");
|
|
255
|
+
this.forceTiltSnap(60, "tilt-open-done", true);
|
|
220
256
|
return;
|
|
221
257
|
}
|
|
222
258
|
|
|
@@ -227,12 +263,15 @@ class AwningWindow {
|
|
|
227
263
|
if (snapped === 15) {
|
|
228
264
|
this.startMotion("angle1", this.tiltMotionGuardSeconds, 15);
|
|
229
265
|
await this.runAction("angle1", "angle1");
|
|
266
|
+
this.forceTiltSnap(15, "angle1-done", true);
|
|
230
267
|
} else if (snapped === 30) {
|
|
231
268
|
this.startMotion("angle2", this.tiltMotionGuardSeconds, 30);
|
|
232
269
|
await this.runAction("angle2", "angle2");
|
|
270
|
+
this.forceTiltSnap(30, "angle2-done", true);
|
|
233
271
|
} else {
|
|
234
272
|
this.startMotion("angle3", this.tiltMotionGuardSeconds, 45);
|
|
235
273
|
await this.runAction("angle3", "angle3");
|
|
274
|
+
this.forceTiltSnap(45, "angle3-done", true);
|
|
236
275
|
}
|
|
237
276
|
}
|
|
238
277
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-aiseg-awning-window-command",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Homebridge plugin for AiSEG2 awning windows",
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Homebridge plugin for AiSEG2 awning windows with HomeKit tilt snapping",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"homebridge-plugin",
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
"window",
|
|
11
11
|
"homekit",
|
|
12
12
|
"homebridge",
|
|
13
|
-
"command"
|
|
13
|
+
"command",
|
|
14
|
+
"aiseg2",
|
|
15
|
+
"window-covering",
|
|
16
|
+
"tilt",
|
|
17
|
+
"sankyo",
|
|
18
|
+
"sankyo-aluminum",
|
|
19
|
+
"panasonic"
|
|
14
20
|
],
|
|
15
21
|
"engines": {
|
|
16
22
|
"homebridge": ">=1.8.0",
|
|
@@ -23,6 +29,8 @@
|
|
|
23
29
|
"README.md",
|
|
24
30
|
"LICENSE",
|
|
25
31
|
"scripts/aiseg_window.py",
|
|
26
|
-
"scripts/aiseg_device.py"
|
|
27
|
-
|
|
32
|
+
"scripts/aiseg_device.py",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"displayName": "Homebridge AiSEG Awning Window Command"
|
|
28
36
|
}
|