homebridge-tekmar 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.
- package/CLAUDE.md +50 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/config.schema.json +66 -0
- package/dist/api/types.d.ts +144 -0
- package/dist/api/types.js +2 -0
- package/dist/api/wattsApi.d.ts +83 -0
- package/dist/api/wattsApi.js +437 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/platform.d.ts +18 -0
- package/dist/platform.js +103 -0
- package/dist/platformAccessory.d.ts +27 -0
- package/dist/platformAccessory.js +191 -0
- package/dist/settings.d.ts +16 -0
- package/dist/settings.js +25 -0
- package/package.json +64 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
Homebridge dynamic platform plugin for Tekmar tN4 WiFi thermostats (models 561-564) controlled via the Watts Home cloud API.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
- **TypeScript** dynamic platform plugin (ES2022, nodenext modules)
|
|
10
|
+
- **No runtime dependencies** — uses Node 20+ native `fetch`
|
|
11
|
+
- **Polling architecture** — platform polls API on interval, pushes updates via `updateCharacteristic()`. GET handlers return cached data instantly to prevent HomeKit "Not Responding" errors.
|
|
12
|
+
- **Heat-only** — Tekmar 561 is heating-only. TargetHeatingCoolingState: OFF, HEAT, AUTO (schedule).
|
|
13
|
+
|
|
14
|
+
## Key Files
|
|
15
|
+
|
|
16
|
+
- `src/index.ts` — Entry point, registers platform
|
|
17
|
+
- `src/settings.ts` — API constants (Azure B2C URLs, client IDs)
|
|
18
|
+
- `src/platform.ts` — DynamicPlatformPlugin: discovery, polling, accessory lifecycle
|
|
19
|
+
- `src/platformAccessory.ts` — HomeKit Thermostat service characteristics
|
|
20
|
+
- `src/api/types.ts` — TypeScript interfaces for API responses
|
|
21
|
+
- `src/api/wattsApi.ts` — API client: Azure B2C auth, REST calls, temp conversion
|
|
22
|
+
|
|
23
|
+
## API Details
|
|
24
|
+
|
|
25
|
+
- **API Base**: `https://home.watts.com/api`
|
|
26
|
+
- **Auth**: Azure AD B2C PKCE OAuth2 at `login.watts.io`
|
|
27
|
+
- **Temperatures**: Plain integers in Fahrenheit from the API; HomeKit uses Celsius internally
|
|
28
|
+
- **Modes**: String values `"Heat"` / `"Off"` (not integer codes)
|
|
29
|
+
- **Control**: `PATCH /api/Device/{id}` with `{"Settings":{...}}`
|
|
30
|
+
|
|
31
|
+
## Build & Test
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install # Install dev dependencies
|
|
35
|
+
npm run build # Compile TypeScript to dist/
|
|
36
|
+
npm run watch # Compile with file watcher
|
|
37
|
+
npm run dev # nodemon auto-restart
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Standalone auth test:
|
|
41
|
+
```bash
|
|
42
|
+
WATTS_USERNAME="you@email.com" WATTS_PASSWORD="yourpass" npx tsx test-auth.ts
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Important Notes
|
|
46
|
+
|
|
47
|
+
- Azure B2C `tx` query parameter must have literal `StateProperties=` (not URL-encoded `%3D`)
|
|
48
|
+
- B2C SelfAsserted endpoint returns HTTP 200 with error in JSON body `{"status":"400",...}`
|
|
49
|
+
- Auth flow requires manual redirect following with cookie jar accumulation
|
|
50
|
+
- `URLSearchParams` over-encodes some characters for B2C — query strings for SelfAsserted/confirmed are built manually
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Schmelkin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# homebridge-tekmar
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/homebridge-tekmar)
|
|
4
|
+
[](https://www.npmjs.com/package/homebridge-tekmar)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A [Homebridge](https://homebridge.io) plugin for [Tekmar](https://www.watts.com/products/heating-cooling-comfort/smart-home/thermostats) tN4 WiFi thermostats (561/562/563/564). Control your Tekmar radiant floor heating thermostat through Apple HomeKit via the Watts Home cloud API.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Current Temperature** — Room, floor, and outdoor sensor readings
|
|
12
|
+
- **Target Temperature** — Set your heating setpoint from the Home app or Siri
|
|
13
|
+
- **Mode Control** — Off, Heat, and Auto (scheduled program) modes
|
|
14
|
+
- **Automatic Polling** — Keeps HomeKit in sync with your thermostat state
|
|
15
|
+
- **Easy Configuration** — Set up through Homebridge Config UI X
|
|
16
|
+
- **No Runtime Dependencies** — Uses Node.js native `fetch`, no extra packages needed
|
|
17
|
+
- **Child Bridge Support** — Run as an isolated child bridge for stability
|
|
18
|
+
|
|
19
|
+
## Supported Hardware
|
|
20
|
+
|
|
21
|
+
| Model | Name | Type |
|
|
22
|
+
|-------|------|------|
|
|
23
|
+
| 561 | tN4 Smart Thermostat | WiFi radiant floor heating |
|
|
24
|
+
| 562 | tN4 Smart Thermostat | WiFi radiant floor heating |
|
|
25
|
+
| 563 | tN4 Smart Thermostat | WiFi radiant floor heating |
|
|
26
|
+
| 564 | tN4 Smart Thermostat | WiFi radiant floor heating |
|
|
27
|
+
|
|
28
|
+
These thermostats are controlled via the **Watts Home** mobile app ([iOS](https://apps.apple.com/app/watts-home/id1452498498) / [Android](https://play.google.com/store/apps/details?id=com.watts.mobileapp)). You must have a Watts Home account with your thermostat(s) configured before using this plugin.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### Option 1: Via Homebridge Config UI X (Recommended)
|
|
33
|
+
|
|
34
|
+
1. Search for `homebridge-tekmar` in the Homebridge Config UI X plugins tab
|
|
35
|
+
2. Click **Install**
|
|
36
|
+
3. Configure with your Watts Home account credentials
|
|
37
|
+
|
|
38
|
+
### Option 2: Via npm
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g homebridge-tekmar
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Configure the plugin through the Homebridge Config UI X interface, or manually edit your `config.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"platforms": [
|
|
51
|
+
{
|
|
52
|
+
"platform": "TekmarThermostat",
|
|
53
|
+
"name": "Tekmar Thermostat",
|
|
54
|
+
"username": "your@email.com",
|
|
55
|
+
"password": "your-password"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Configuration Options
|
|
62
|
+
|
|
63
|
+
| Option | Default | Description |
|
|
64
|
+
|--------|---------|-------------|
|
|
65
|
+
| `username` | *required* | Your Watts Home app email address |
|
|
66
|
+
| `password` | *required* | Your Watts Home app password |
|
|
67
|
+
| `pollingInterval` | `60` | Polling interval in seconds (10-300) |
|
|
68
|
+
| `temperatureUnit` | `fahrenheit` | Display unit: `fahrenheit` or `celsius` |
|
|
69
|
+
|
|
70
|
+
## How It Works
|
|
71
|
+
|
|
72
|
+
1. Authenticates with the Watts Home cloud API (Azure AD B2C OAuth2)
|
|
73
|
+
2. Discovers all Tekmar thermostats across your locations
|
|
74
|
+
3. Exposes each thermostat as a HomeKit Thermostat accessory
|
|
75
|
+
4. Polls the API at a configurable interval to keep state in sync
|
|
76
|
+
5. SET commands (temperature, mode) are sent immediately to the API
|
|
77
|
+
|
|
78
|
+
### HomeKit Mode Mapping
|
|
79
|
+
|
|
80
|
+
The Tekmar 561 is a **heat-only** thermostat. HomeKit modes are mapped as:
|
|
81
|
+
|
|
82
|
+
| HomeKit Mode | Tekmar Behavior |
|
|
83
|
+
|-------------|-----------------|
|
|
84
|
+
| **Off** | Thermostat off |
|
|
85
|
+
| **Heat** | Manual heating to target temperature |
|
|
86
|
+
| **Auto** | Scheduled program mode enabled |
|
|
87
|
+
|
|
88
|
+
> **Note:** Cool mode is not available — the Tekmar tN4 series does not support cooling.
|
|
89
|
+
|
|
90
|
+
## Supported Features
|
|
91
|
+
|
|
92
|
+
- Set and read target temperature
|
|
93
|
+
- Read current room temperature
|
|
94
|
+
- Switch between Off / Heat / Auto modes
|
|
95
|
+
- Current heating state (actively heating or idle)
|
|
96
|
+
- Temperature display unit preference
|
|
97
|
+
- Multiple thermostats across multiple locations
|
|
98
|
+
|
|
99
|
+
## Known Limitations
|
|
100
|
+
|
|
101
|
+
- **Cloud API only** — Requires internet connectivity (no local control)
|
|
102
|
+
- **Heat only** — No cooling support (hardware limitation)
|
|
103
|
+
- **No humidity sensor** — The thermostat does not report humidity
|
|
104
|
+
- **Floor/outdoor temps** — Floor and outdoor sensor readings are fetched but not yet exposed as separate HomeKit sensors
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
### Local Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Clone the repository
|
|
112
|
+
git clone https://github.com/apumapho/homebridge-tekmar.git
|
|
113
|
+
cd homebridge-tekmar
|
|
114
|
+
|
|
115
|
+
# Install dependencies
|
|
116
|
+
npm install
|
|
117
|
+
|
|
118
|
+
# Build
|
|
119
|
+
npm run build
|
|
120
|
+
|
|
121
|
+
# Link for local testing
|
|
122
|
+
npm link
|
|
123
|
+
|
|
124
|
+
# Run Homebridge in debug mode
|
|
125
|
+
homebridge -D
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Standalone API Test
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
WATTS_USERNAME="you@email.com" WATTS_PASSWORD="yourpass" npx tsx test-auth.ts
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Troubleshooting
|
|
135
|
+
|
|
136
|
+
### Thermostat not appearing in HomeKit
|
|
137
|
+
|
|
138
|
+
1. Check Homebridge logs for authentication errors
|
|
139
|
+
2. Verify your Watts Home credentials are correct
|
|
140
|
+
3. Ensure your thermostat is set up in the Watts Home app
|
|
141
|
+
4. Try restarting Homebridge
|
|
142
|
+
|
|
143
|
+
### "Not Responding" in Home app
|
|
144
|
+
|
|
145
|
+
- Check your internet connection
|
|
146
|
+
- Verify the Watts Home cloud service is operational
|
|
147
|
+
- Increase `pollingInterval` if you're hitting rate limits
|
|
148
|
+
|
|
149
|
+
### Authentication failures
|
|
150
|
+
|
|
151
|
+
- Ensure your email and password match what you use in the Watts Home app
|
|
152
|
+
- Check if your account has been locked (too many failed attempts)
|
|
153
|
+
- The plugin uses the same authentication flow as the mobile app
|
|
154
|
+
|
|
155
|
+
## About Tekmar / Watts
|
|
156
|
+
|
|
157
|
+
[Tekmar](https://www.watts.com/brands/tekmar) is a Watts brand specializing in radiant heating controls. The tN4 WiFi thermostat series connects to the Watts Home cloud platform for remote control. This plugin uses the Watts Home cloud API to integrate with HomeKit.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
166
|
+
|
|
167
|
+
## Acknowledgments
|
|
168
|
+
|
|
169
|
+
- Thanks to the [Homebridge](https://homebridge.io) team for the excellent platform
|
|
170
|
+
- Built with [Claude Code](https://claude.com/claude-code)
|
|
171
|
+
|
|
172
|
+
## Support
|
|
173
|
+
|
|
174
|
+
- [Report Issues](https://github.com/apumapho/homebridge-tekmar/issues)
|
|
175
|
+
- [Homebridge Discord](https://discord.gg/homebridge)
|
|
176
|
+
- [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "TekmarThermostat",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"headerDisplay": "Homebridge plugin for [Tekmar tN4 WiFi thermostats](https://www.watts.com/products/heating-cooling-comfort/smart-home/thermostats) (561/562/563/564) via the Watts Home cloud API.",
|
|
6
|
+
"footerDisplay": "For help, visit the [GitHub repository](https://github.com/apumapho/homebridge-tekmar).",
|
|
7
|
+
"schema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"title": "Name",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "Tekmar Thermostat",
|
|
14
|
+
"required": true
|
|
15
|
+
},
|
|
16
|
+
"username": {
|
|
17
|
+
"title": "Email",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"format": "email",
|
|
20
|
+
"description": "Your Watts Home app email address.",
|
|
21
|
+
"required": true
|
|
22
|
+
},
|
|
23
|
+
"password": {
|
|
24
|
+
"title": "Password",
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Your Watts Home app password.",
|
|
27
|
+
"required": true
|
|
28
|
+
},
|
|
29
|
+
"pollingInterval": {
|
|
30
|
+
"title": "Polling Interval (seconds)",
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"default": 60,
|
|
33
|
+
"minimum": 10,
|
|
34
|
+
"maximum": 300,
|
|
35
|
+
"description": "How often to poll the Watts Home API for thermostat state updates."
|
|
36
|
+
},
|
|
37
|
+
"temperatureUnit": {
|
|
38
|
+
"title": "Temperature Display Unit",
|
|
39
|
+
"type": "string",
|
|
40
|
+
"default": "fahrenheit",
|
|
41
|
+
"oneOf": [
|
|
42
|
+
{ "title": "Fahrenheit", "enum": ["fahrenheit"] },
|
|
43
|
+
{ "title": "Celsius", "enum": ["celsius"] }
|
|
44
|
+
],
|
|
45
|
+
"description": "Preferred temperature display unit in the Home app."
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"layout": [
|
|
50
|
+
"name",
|
|
51
|
+
"username",
|
|
52
|
+
{
|
|
53
|
+
"key": "password",
|
|
54
|
+
"type": "password"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "fieldset",
|
|
58
|
+
"title": "Advanced Settings",
|
|
59
|
+
"expandable": true,
|
|
60
|
+
"items": [
|
|
61
|
+
"pollingInterval",
|
|
62
|
+
"temperatureUnit"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { PlatformConfig } from 'homebridge';
|
|
2
|
+
export interface TekmarPlatformConfig extends PlatformConfig {
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
pollingInterval?: number;
|
|
6
|
+
temperatureUnit?: 'fahrenheit' | 'celsius';
|
|
7
|
+
}
|
|
8
|
+
export interface WattsApiResponse<T> {
|
|
9
|
+
errorNumber: number;
|
|
10
|
+
errorMessage: string | null;
|
|
11
|
+
body: T;
|
|
12
|
+
}
|
|
13
|
+
export interface WattsLocation {
|
|
14
|
+
locationId: string;
|
|
15
|
+
ownerId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
address: {
|
|
18
|
+
address: string;
|
|
19
|
+
address2: string;
|
|
20
|
+
city: string;
|
|
21
|
+
state_province: string;
|
|
22
|
+
zipcode: string;
|
|
23
|
+
country: string;
|
|
24
|
+
};
|
|
25
|
+
awayState: number;
|
|
26
|
+
isDefault: boolean;
|
|
27
|
+
isShared: boolean;
|
|
28
|
+
userType: number;
|
|
29
|
+
supportsAway: boolean;
|
|
30
|
+
usersCount: number;
|
|
31
|
+
devicesCount: number;
|
|
32
|
+
}
|
|
33
|
+
export interface WattsDeviceData {
|
|
34
|
+
Sensors: {
|
|
35
|
+
Room: {
|
|
36
|
+
Val: number;
|
|
37
|
+
Status: string;
|
|
38
|
+
};
|
|
39
|
+
Floor: {
|
|
40
|
+
Val: number;
|
|
41
|
+
Status: string;
|
|
42
|
+
};
|
|
43
|
+
Outdoor: {
|
|
44
|
+
Val: number;
|
|
45
|
+
Status: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
State: {
|
|
49
|
+
Op: string;
|
|
50
|
+
Sub: string;
|
|
51
|
+
};
|
|
52
|
+
Mode: {
|
|
53
|
+
Active: number;
|
|
54
|
+
Val: string;
|
|
55
|
+
Enum: string[];
|
|
56
|
+
};
|
|
57
|
+
Target: {
|
|
58
|
+
Active: number;
|
|
59
|
+
Sensor: string;
|
|
60
|
+
Hold: number;
|
|
61
|
+
Heat: number;
|
|
62
|
+
Cool: number | null;
|
|
63
|
+
Min: number;
|
|
64
|
+
Max: number;
|
|
65
|
+
Steps: number;
|
|
66
|
+
};
|
|
67
|
+
TempUnits: {
|
|
68
|
+
Active: number;
|
|
69
|
+
Val: string;
|
|
70
|
+
Enum: string[];
|
|
71
|
+
};
|
|
72
|
+
Units: string;
|
|
73
|
+
SchedEnable: {
|
|
74
|
+
Active: number;
|
|
75
|
+
Val: string;
|
|
76
|
+
Enum: string[];
|
|
77
|
+
};
|
|
78
|
+
Schedule: Record<string, unknown>;
|
|
79
|
+
Energy: Record<string, unknown>;
|
|
80
|
+
DateTime: string;
|
|
81
|
+
TZOffset: number;
|
|
82
|
+
}
|
|
83
|
+
export interface WattsDevice {
|
|
84
|
+
data: WattsDeviceData;
|
|
85
|
+
isConnected: boolean;
|
|
86
|
+
requestingUser: string;
|
|
87
|
+
deviceId: string;
|
|
88
|
+
name: string;
|
|
89
|
+
modelId: number;
|
|
90
|
+
modelNumber: string;
|
|
91
|
+
deviceType: string;
|
|
92
|
+
deviceTypeId: number;
|
|
93
|
+
location: {
|
|
94
|
+
locationId: string;
|
|
95
|
+
name: string;
|
|
96
|
+
address: unknown;
|
|
97
|
+
awayState: number;
|
|
98
|
+
userType: number;
|
|
99
|
+
};
|
|
100
|
+
imageUrl: string | null;
|
|
101
|
+
isShared: boolean;
|
|
102
|
+
}
|
|
103
|
+
export interface ThermostatDevice {
|
|
104
|
+
id: string;
|
|
105
|
+
name: string;
|
|
106
|
+
/** Current room temperature in °F */
|
|
107
|
+
currentTemperature: number;
|
|
108
|
+
/** Current floor temperature in °F */
|
|
109
|
+
floorTemperature: number;
|
|
110
|
+
/** Current outdoor temperature in °F */
|
|
111
|
+
outdoorTemperature: number;
|
|
112
|
+
/** Target/setpoint temperature in °F */
|
|
113
|
+
targetTemperature: number;
|
|
114
|
+
/** Operating mode: "Heat" | "Off" */
|
|
115
|
+
mode: string;
|
|
116
|
+
/** Whether the thermostat is currently calling for heat */
|
|
117
|
+
isHeating: boolean;
|
|
118
|
+
/** Whether the location is in Away mode */
|
|
119
|
+
isAway: boolean;
|
|
120
|
+
/** Whether schedule is enabled */
|
|
121
|
+
scheduleEnabled: boolean;
|
|
122
|
+
/** Whether the device is connected */
|
|
123
|
+
isConnected: boolean;
|
|
124
|
+
/** Model number */
|
|
125
|
+
modelNumber: string;
|
|
126
|
+
/** Min/Max setpoint range in °F */
|
|
127
|
+
minSetpoint: number;
|
|
128
|
+
maxSetpoint: number;
|
|
129
|
+
/** Location ID (needed for away mode) */
|
|
130
|
+
locationId: string;
|
|
131
|
+
}
|
|
132
|
+
export interface B2CTokenResponse {
|
|
133
|
+
access_token: string;
|
|
134
|
+
id_token: string;
|
|
135
|
+
token_type: string;
|
|
136
|
+
not_before: number;
|
|
137
|
+
expires_in: number;
|
|
138
|
+
expires_on: number;
|
|
139
|
+
resource: string;
|
|
140
|
+
id_token_expires_in: number;
|
|
141
|
+
profile_info: string;
|
|
142
|
+
refresh_token: string;
|
|
143
|
+
refresh_token_expires_in: number;
|
|
144
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Logger } from 'homebridge';
|
|
2
|
+
import type { TekmarPlatformConfig, ThermostatDevice, WattsDevice, WattsLocation } from './types.js';
|
|
3
|
+
/** Convert Fahrenheit to Celsius (HomeKit uses Celsius internally). */
|
|
4
|
+
export declare function fahrenheitToCelsius(f: number): number;
|
|
5
|
+
/** Convert Celsius (from HomeKit) to Fahrenheit for the API. */
|
|
6
|
+
export declare function celsiusToFahrenheit(c: number): number;
|
|
7
|
+
export declare class WattsApi {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly log;
|
|
10
|
+
private accessToken;
|
|
11
|
+
private refreshToken;
|
|
12
|
+
private tokenExpiresAt;
|
|
13
|
+
/** Cookie jar — accumulated across all auth requests. */
|
|
14
|
+
private cookieJar;
|
|
15
|
+
constructor(config: TekmarPlatformConfig, log: Logger);
|
|
16
|
+
/**
|
|
17
|
+
* Full Azure AD B2C authentication flow:
|
|
18
|
+
* 1. GET /authorize → follow redirects, load login page, extract CSRF & cookies
|
|
19
|
+
* 2. POST /SelfAsserted → submit credentials
|
|
20
|
+
* 3. GET /confirmed → get redirect with auth code
|
|
21
|
+
* 4. POST /token → exchange code for tokens
|
|
22
|
+
*/
|
|
23
|
+
authenticate(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Refresh the access token using the refresh token.
|
|
26
|
+
*/
|
|
27
|
+
refreshAccessToken(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Ensure we have a valid access token before making API calls.
|
|
30
|
+
*/
|
|
31
|
+
ensureAuthenticated(): Promise<void>;
|
|
32
|
+
apiRequest<T>(url: string, options?: RequestInit): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Get all locations for the user.
|
|
35
|
+
*/
|
|
36
|
+
getLocations(): Promise<WattsLocation[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Get all devices for a location.
|
|
39
|
+
*/
|
|
40
|
+
getLocationDevices(locationId: string): Promise<WattsDevice[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Get a single device with fresh data.
|
|
43
|
+
*/
|
|
44
|
+
getDevice(deviceId: string): Promise<WattsDevice>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetch all thermostat devices across all locations.
|
|
47
|
+
* Returns normalized ThermostatDevice[].
|
|
48
|
+
*/
|
|
49
|
+
getDevices(): Promise<ThermostatDevice[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a WattsDevice into our simplified ThermostatDevice.
|
|
52
|
+
*/
|
|
53
|
+
normalizeDevice(device: WattsDevice): ThermostatDevice;
|
|
54
|
+
/**
|
|
55
|
+
* Set the target temperature for a thermostat.
|
|
56
|
+
* @param deviceId - The device ID
|
|
57
|
+
* @param celsius - Target temperature in Celsius (from HomeKit)
|
|
58
|
+
*/
|
|
59
|
+
setTargetTemperature(deviceId: string, celsius: number): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Set the operating mode for a thermostat.
|
|
62
|
+
* @param deviceId - The device ID
|
|
63
|
+
* @param mode - "Heat" or "Off"
|
|
64
|
+
*/
|
|
65
|
+
setMode(deviceId: string, mode: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Set the schedule enable/disable.
|
|
68
|
+
* @param deviceId - The device ID
|
|
69
|
+
* @param enabled - Whether to enable the schedule
|
|
70
|
+
*/
|
|
71
|
+
setScheduleEnabled(deviceId: string, enabled: boolean): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Set the away state for a location.
|
|
74
|
+
* @param locationId - The location ID
|
|
75
|
+
* @param away - true for Away, false for Home
|
|
76
|
+
*/
|
|
77
|
+
setAwayState(locationId: string, away: boolean): Promise<void>;
|
|
78
|
+
private setTokens;
|
|
79
|
+
/** Collect Set-Cookie headers from a response into the cookie jar. */
|
|
80
|
+
private collectCookies;
|
|
81
|
+
/** Format the cookie jar as a Cookie header string. */
|
|
82
|
+
private getCookieHeader;
|
|
83
|
+
}
|