homebridge-legrand-radiant 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,185 @@
1
+ # Homebridge Legrand Radiant
2
+
3
+ [![npm](https://img.shields.io/npm/v/homebridge-legrand-radiant.svg)](https://www.npmjs.com/package/homebridge-legrand-radiant)
4
+ [![License](https://img.shields.io/npm/l/homebridge-legrand-radiant.svg)](https://github.com/yourusername/homebridge-legrand-radiant/blob/main/LICENSE)
5
+
6
+ Homebridge plugin to control **Legrand Radiant WiFi smart switches and dimmers** via the Legrand cloud API.
7
+
8
+ ## Features
9
+
10
+ - 🔌 Control Legrand Radiant WiFi switches and dimmers via HomeKit
11
+ - 🔄 Automatic device discovery - no manual configuration needed
12
+ - 🔐 Secure OAuth2 authentication with automatic token refresh
13
+ - 📡 Real-time status updates
14
+ - 💡 Support for both switches and dimmers
15
+
16
+ ## Supported Devices
17
+
18
+ This plugin works with Legrand Radiant WiFi smart devices that use the **Legrand Smart Lights** app, including:
19
+
20
+ - Radiant Smart WiFi Switch
21
+ - Radiant Smart WiFi Dimmer
22
+ - Other WiFi-enabled devices using the Legrand Smart Lights app
23
+
24
+ > **Note:** This plugin is for WiFi-based switches that use the cloud API. It does **not** support RF-based switches that require the LC7001 hub.
25
+
26
+ ## Requirements
27
+
28
+ - [Homebridge](https://homebridge.io/) v1.6.0 or later
29
+ - Node.js v18 or later
30
+ - A Legrand Smart Lights account with configured devices
31
+
32
+ ## Installation
33
+
34
+ ### Via Homebridge UI (Recommended)
35
+
36
+ 1. Open the Homebridge UI
37
+ 2. Go to **Plugins**
38
+ 3. Search for `homebridge-legrand-radiant`
39
+ 4. Click **Install**
40
+
41
+ ### Via npm
42
+
43
+ ```bash
44
+ npm install -g homebridge-legrand-radiant
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ Add the platform to your Homebridge `config.json`:
50
+
51
+ ```json
52
+ {
53
+ "platforms": [
54
+ {
55
+ "platform": "LegrandRadiant",
56
+ "name": "Legrand",
57
+ "email": "your-email@example.com",
58
+ "password": "your-password"
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ### Configuration Options
65
+
66
+ | Option | Required | Default | Description |
67
+ |--------|----------|---------|-------------|
68
+ | `platform` | Yes | - | Must be `LegrandRadiant` |
69
+ | `name` | Yes | - | Display name in Homebridge logs |
70
+ | `email` | Yes | - | Your Legrand Smart Lights account email |
71
+ | `password` | Yes | - | Your Legrand Smart Lights account password |
72
+ | `debug` | No | `false` | Enable verbose debug logging |
73
+
74
+ ### Advanced Configuration
75
+
76
+ If you need to manually specify devices (instead of auto-discovery):
77
+
78
+ ```json
79
+ {
80
+ "platforms": [
81
+ {
82
+ "platform": "LegrandRadiant",
83
+ "name": "Legrand",
84
+ "email": "your-email@example.com",
85
+ "password": "your-password",
86
+ "devices": [
87
+ {
88
+ "id": "e182f1e0-ae64-41e6-a892-43fd7c5b2bad",
89
+ "name": "Kitchen Light",
90
+ "type": "dimmer"
91
+ }
92
+ ],
93
+ "debug": true
94
+ }
95
+ ]
96
+ }
97
+ ```
98
+
99
+ | Device Option | Required | Description |
100
+ |---------------|----------|-------------|
101
+ | `id` | Yes | Device UUID from the Legrand API |
102
+ | `name` | Yes | Display name in HomeKit |
103
+ | `type` | No | `switch` or `dimmer` (auto-detected if not specified) |
104
+
105
+ ## How It Works
106
+
107
+ This plugin connects to Legrand's cloud API to control your WiFi smart switches:
108
+
109
+ 1. **Authentication** - Securely logs in using OAuth2 with your Legrand account
110
+ 2. **Discovery** - Automatically finds all your configured devices
111
+ 3. **Control** - Sends commands through the cloud API
112
+ 4. **Status** - Retrieves current device state
113
+
114
+ ```
115
+ HomeKit → Homebridge → Legrand Cloud API → Your WiFi Switch
116
+ ```
117
+
118
+ ## Development
119
+
120
+ ### Building
121
+
122
+ ```bash
123
+ npm install
124
+ npm run build
125
+ ```
126
+
127
+ ### Testing
128
+
129
+ ```bash
130
+ # Test the API directly
131
+ npx ts-node tools/testCloudApi.ts <deviceId> interactive <token>
132
+ ```
133
+
134
+ ### Development Mode
135
+
136
+ ```bash
137
+ npm run watch
138
+ ```
139
+
140
+ ## Troubleshooting
141
+
142
+ ### Devices not appearing
143
+
144
+ 1. Make sure your devices are set up in the Legrand Smart Lights app
145
+ 2. Verify your email and password are correct
146
+ 3. Enable `debug: true` in the config and check Homebridge logs
147
+
148
+ ### Authentication errors
149
+
150
+ 1. Verify your credentials are correct
151
+ 2. Try logging out and back in to the Legrand Smart Lights app
152
+ 3. Check if your account has 2FA enabled (not currently supported)
153
+
154
+ ### Commands not working
155
+
156
+ 1. Check that the device is online in the Legrand app
157
+ 2. Verify your internet connection
158
+ 3. Enable debug mode to see API responses
159
+
160
+ ## API Documentation
161
+
162
+ This plugin uses the Legrand Developer API:
163
+
164
+ | Endpoint | Purpose |
165
+ |----------|---------|
166
+ | `GET /servicecatalog/api/v3.0/plants` | List homes |
167
+ | `GET /servicecatalog/api/v3.0/plants/{id}/modules` | List devices |
168
+ | `POST /devicemanagement/api/v2.0/modules/{id}/commands/setState` | Control device |
169
+
170
+ ## Contributing
171
+
172
+ Contributions are welcome! Please feel free to submit issues and pull requests.
173
+
174
+ ## Credits
175
+
176
+ - Inspired by [homebridge-lc7001](https://github.com/sbozarth/homebridge-lc7001)
177
+ - Uses the Legrand cloud API (reverse engineered)
178
+
179
+ ## License
180
+
181
+ MIT License - see [LICENSE](LICENSE) for details.
182
+
183
+ ## Disclaimer
184
+
185
+ This plugin is not affiliated with or endorsed by Legrand. Use at your own risk.
@@ -0,0 +1,127 @@
1
+ {
2
+ "pluginAlias": "LegrandRadiant",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "headerDisplay": "Control your Legrand Radiant WiFi smart switches and dimmers through HomeKit. Enter your Legrand Smart Lights app credentials below.",
6
+ "footerDisplay": "Devices are automatically discovered from your Legrand account. For issues, check the [GitHub repository](https://github.com/yourusername/homebridge-legrand-radiant).",
7
+ "schema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "name": {
11
+ "title": "Platform Name",
12
+ "type": "string",
13
+ "default": "Legrand",
14
+ "required": true,
15
+ "description": "Name shown in Homebridge logs"
16
+ },
17
+ "email": {
18
+ "title": "Email",
19
+ "type": "string",
20
+ "format": "email",
21
+ "required": true,
22
+ "description": "Your Legrand Smart Lights account email"
23
+ },
24
+ "password": {
25
+ "title": "Password",
26
+ "type": "string",
27
+ "required": true,
28
+ "description": "Your Legrand Smart Lights account password"
29
+ },
30
+ "pollingInterval": {
31
+ "title": "Polling Interval (seconds)",
32
+ "type": "integer",
33
+ "default": 30,
34
+ "minimum": 10,
35
+ "maximum": 300,
36
+ "description": "How often to check for state changes (10-300 seconds)"
37
+ },
38
+ "debug": {
39
+ "title": "Debug Mode",
40
+ "type": "boolean",
41
+ "default": false,
42
+ "description": "Enable verbose logging for troubleshooting"
43
+ },
44
+ "accessToken": {
45
+ "title": "Manual Access Token",
46
+ "type": "string",
47
+ "description": "Optional: Only use if automatic login fails"
48
+ },
49
+ "devices": {
50
+ "title": "Manual Device List",
51
+ "type": "array",
52
+ "items": {
53
+ "type": "object",
54
+ "properties": {
55
+ "id": {
56
+ "title": "Device ID",
57
+ "type": "string",
58
+ "required": true
59
+ },
60
+ "name": {
61
+ "title": "Device Name",
62
+ "type": "string",
63
+ "required": true
64
+ },
65
+ "type": {
66
+ "title": "Device Type",
67
+ "type": "string",
68
+ "oneOf": [
69
+ { "title": "Switch", "enum": ["switch"] },
70
+ { "title": "Dimmer", "enum": ["dimmer"] }
71
+ ],
72
+ "default": "switch"
73
+ }
74
+ }
75
+ },
76
+ "description": "Optional: Only needed if auto-discovery doesn't work"
77
+ }
78
+ },
79
+ "required": ["name", "email", "password"]
80
+ },
81
+ "layout": [
82
+ {
83
+ "type": "fieldset",
84
+ "title": "Legrand Account",
85
+ "expandable": false,
86
+ "items": [
87
+ "email",
88
+ {
89
+ "key": "password",
90
+ "type": "password"
91
+ }
92
+ ]
93
+ },
94
+ {
95
+ "type": "fieldset",
96
+ "title": "Settings",
97
+ "expandable": true,
98
+ "expanded": false,
99
+ "items": [
100
+ "name",
101
+ "pollingInterval",
102
+ "debug"
103
+ ]
104
+ },
105
+ {
106
+ "type": "fieldset",
107
+ "title": "Advanced",
108
+ "expandable": true,
109
+ "expanded": false,
110
+ "items": [
111
+ "accessToken",
112
+ {
113
+ "key": "devices",
114
+ "type": "array",
115
+ "title": "Manual Devices",
116
+ "expandable": true,
117
+ "expanded": false,
118
+ "items": [
119
+ "devices[].id",
120
+ "devices[].name",
121
+ "devices[].type"
122
+ ]
123
+ }
124
+ ]
125
+ }
126
+ ]
127
+ }
@@ -0,0 +1,35 @@
1
+ import { PlatformAccessory } from 'homebridge';
2
+ import { LegrandCloudPlatform } from './cloudPlatform';
3
+ /**
4
+ * Accessory handler for a Legrand Cloud-controlled switch
5
+ */
6
+ export declare class LegrandCloudAccessory {
7
+ private readonly platform;
8
+ private readonly accessory;
9
+ private readonly deviceId;
10
+ private readonly deviceName;
11
+ private service;
12
+ private state;
13
+ private readonly isDimmer;
14
+ constructor(platform: LegrandCloudPlatform, accessory: PlatformAccessory, deviceId: string, deviceName: string, deviceType: string);
15
+ /**
16
+ * Handle SET On
17
+ */
18
+ private setOn;
19
+ /**
20
+ * Handle GET On
21
+ */
22
+ private getOn;
23
+ /**
24
+ * Handle SET Brightness (for dimmers)
25
+ */
26
+ private setBrightness;
27
+ /**
28
+ * Handle GET Brightness (for dimmers)
29
+ */
30
+ private getBrightness;
31
+ /**
32
+ * Update state from external source (polling)
33
+ */
34
+ updateState(isOn: boolean, brightness?: number): void;
35
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LegrandCloudAccessory = void 0;
4
+ /**
5
+ * Accessory handler for a Legrand Cloud-controlled switch
6
+ */
7
+ class LegrandCloudAccessory {
8
+ platform;
9
+ accessory;
10
+ deviceId;
11
+ deviceName;
12
+ service;
13
+ state = {
14
+ on: false,
15
+ brightness: 100,
16
+ };
17
+ isDimmer;
18
+ constructor(platform, accessory, deviceId, deviceName, deviceType) {
19
+ this.platform = platform;
20
+ this.accessory = accessory;
21
+ this.deviceId = deviceId;
22
+ this.deviceName = deviceName;
23
+ this.isDimmer = deviceType === 'dimmer';
24
+ // Set accessory information
25
+ this.accessory.getService(this.platform.Service.AccessoryInformation)
26
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Legrand')
27
+ .setCharacteristic(this.platform.Characteristic.Model, `Radiant WiFi ${this.isDimmer ? 'Dimmer' : 'Switch'}`)
28
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, deviceId.substring(0, 8))
29
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, '1.0.0');
30
+ // Remove any conflicting services
31
+ const existingLightbulb = this.accessory.getService(this.platform.Service.Lightbulb);
32
+ const existingSwitch = this.accessory.getService(this.platform.Service.Switch);
33
+ if (this.isDimmer) {
34
+ // Remove switch service if exists (in case device type changed)
35
+ if (existingSwitch) {
36
+ this.accessory.removeService(existingSwitch);
37
+ }
38
+ // Get or create lightbulb service
39
+ this.service = existingLightbulb || this.accessory.addService(this.platform.Service.Lightbulb, deviceName);
40
+ // Set up brightness characteristic
41
+ this.service.getCharacteristic(this.platform.Characteristic.Brightness)
42
+ .onSet(this.setBrightness.bind(this))
43
+ .onGet(this.getBrightness.bind(this));
44
+ }
45
+ else {
46
+ // Remove lightbulb service if exists
47
+ if (existingLightbulb) {
48
+ this.accessory.removeService(existingLightbulb);
49
+ }
50
+ // Get or create switch service
51
+ this.service = existingSwitch || this.accessory.addService(this.platform.Service.Switch, deviceName);
52
+ }
53
+ // Set the display name
54
+ this.service.setCharacteristic(this.platform.Characteristic.Name, deviceName);
55
+ // Register handlers for on/off
56
+ this.service.getCharacteristic(this.platform.Characteristic.On)
57
+ .onSet(this.setOn.bind(this))
58
+ .onGet(this.getOn.bind(this));
59
+ }
60
+ /**
61
+ * Handle SET On
62
+ */
63
+ async setOn(value) {
64
+ const isOn = value;
65
+ this.platform.log.debug(`Setting ${this.deviceName} to ${isOn ? 'ON' : 'OFF'}`);
66
+ try {
67
+ const success = isOn
68
+ ? await this.platform.cloudApi.turnOn(this.deviceId)
69
+ : await this.platform.cloudApi.turnOff(this.deviceId);
70
+ if (success) {
71
+ this.state.on = isOn;
72
+ this.platform.log.info(`${this.deviceName} is now ${isOn ? 'ON' : 'OFF'}`);
73
+ }
74
+ else {
75
+ throw new Error('Failed to set power state');
76
+ }
77
+ }
78
+ catch (error) {
79
+ this.platform.log.error(`Error setting power: ${error}`);
80
+ throw error;
81
+ }
82
+ }
83
+ /**
84
+ * Handle GET On
85
+ */
86
+ async getOn() {
87
+ return this.state.on;
88
+ }
89
+ /**
90
+ * Handle SET Brightness (for dimmers)
91
+ */
92
+ async setBrightness(value) {
93
+ const brightness = value;
94
+ this.platform.log.debug(`Setting ${this.deviceName} brightness to ${brightness}%`);
95
+ try {
96
+ const success = await this.platform.cloudApi.setBrightness(this.deviceId, brightness);
97
+ if (success) {
98
+ this.state.brightness = brightness;
99
+ this.state.on = brightness > 0;
100
+ }
101
+ else {
102
+ throw new Error('Failed to set brightness');
103
+ }
104
+ }
105
+ catch (error) {
106
+ this.platform.log.error(`Error setting brightness: ${error}`);
107
+ throw error;
108
+ }
109
+ }
110
+ /**
111
+ * Handle GET Brightness (for dimmers)
112
+ */
113
+ async getBrightness() {
114
+ return this.state.brightness;
115
+ }
116
+ /**
117
+ * Update state from external source (polling)
118
+ */
119
+ updateState(isOn, brightness) {
120
+ // Only update if state has changed
121
+ if (this.state.on !== isOn) {
122
+ this.state.on = isOn;
123
+ this.service.updateCharacteristic(this.platform.Characteristic.On, isOn);
124
+ this.platform.log.debug(`${this.deviceName} state updated: ${isOn ? 'ON' : 'OFF'}`);
125
+ }
126
+ if (this.isDimmer && brightness !== undefined && this.state.brightness !== brightness) {
127
+ this.state.brightness = brightness;
128
+ this.service.updateCharacteristic(this.platform.Characteristic.Brightness, brightness);
129
+ this.platform.log.debug(`${this.deviceName} brightness updated: ${brightness}%`);
130
+ }
131
+ }
132
+ }
133
+ exports.LegrandCloudAccessory = LegrandCloudAccessory;
@@ -0,0 +1,50 @@
1
+ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2
+ import { LegrandCloudApi } from './legrandCloudApi';
3
+ /**
4
+ * Legrand Radiant Cloud Platform Plugin
5
+ * Controls Legrand Radiant WiFi smart switches via the cloud API
6
+ */
7
+ export declare class LegrandCloudPlatform implements DynamicPlatformPlugin {
8
+ readonly log: Logger;
9
+ readonly config: PlatformConfig;
10
+ readonly api: API;
11
+ readonly Service: typeof Service;
12
+ readonly Characteristic: typeof Characteristic;
13
+ readonly accessories: PlatformAccessory[];
14
+ private readonly accessoryHandlers;
15
+ cloudApi: LegrandCloudApi;
16
+ private pollingInterval;
17
+ constructor(log: Logger, config: PlatformConfig, api: API);
18
+ /**
19
+ * Start polling for device status updates
20
+ */
21
+ private startPolling;
22
+ /**
23
+ * Stop polling
24
+ */
25
+ private stopPolling;
26
+ /**
27
+ * Poll all devices for their current status using real-time getState command
28
+ */
29
+ private pollDeviceStatus;
30
+ /**
31
+ * Called when homebridge restores cached accessories from disk
32
+ */
33
+ configureAccessory(accessory: PlatformAccessory): void;
34
+ /**
35
+ * Discover and register devices
36
+ */
37
+ private discoverDevices;
38
+ /**
39
+ * Remove cached accessories that are no longer present
40
+ */
41
+ private cleanupOldAccessories;
42
+ /**
43
+ * Add a device from an API module
44
+ */
45
+ private addDeviceFromModule;
46
+ /**
47
+ * Add a device as a HomeKit accessory
48
+ */
49
+ private addDevice;
50
+ }