homebridge-somfy-rfxcom 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.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a problem with the plugin
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **Describe the bug**
10
+ A clear and concise description of what the bug is.
11
+
12
+ **To Reproduce**
13
+ Steps to reproduce the behaviour:
14
+ 1. ...
15
+ 2. ...
16
+ 3. ...
17
+
18
+ **Expected behaviour**
19
+ What you expected to happen.
20
+
21
+ **Logs**
22
+ Paste relevant Homebridge logs here. Enable `debug: true` in the plugin config to get more detail.
23
+
24
+ ```
25
+ <paste logs here>
26
+ ```
27
+
28
+ **Environment**
29
+ - Plugin version:
30
+ - Homebridge version:
31
+ - Node.js version (`node -v`):
32
+ - OS / platform:
33
+ - RFXtrx model:
34
+ - RFY device(s) being controlled (blind / awning / gate / other):
35
+
36
+ **Configuration (sanitized)**
37
+ ```json
38
+ {
39
+ "platform": "SomfyRFXCom",
40
+ ...
41
+ }
42
+ ```
43
+
44
+ **Additional context**
45
+ Add any other context about the problem here.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an enhancement for the plugin
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ **Is your feature request related to a problem? Please describe.**
10
+ A clear and concise description of the problem.
11
+
12
+ **Describe the solution you'd like**
13
+ What you would like the plugin to do.
14
+
15
+ **Describe alternatives you've considered**
16
+ Any alternative solutions or workarounds you've considered.
17
+
18
+ **Additional context**
19
+ Add any other context, screenshots, or RFXcom protocol references here.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 John Hurliman
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,82 @@
1
+ # homebridge-somfy-rfxcom
2
+
3
+ Homebridge plugin for controlling [Somfy RTS](https://www.somfy.com/) blinds, awnings, and gates through HomeKit, via an [RFXtrx433(E)](http://www.rfxcom.com/RFXtrx433E-USB-43392MHz-Transceiver/en) USB transceiver.
4
+
5
+ This fork was rewritten with motorized Somfy RTS gates in mind and is, in my experience, the most reliable option for that use case. It also works with RFY blinds and awnings.
6
+
7
+ Originally based on [glefand/homebridge-rfxcom](https://github.com/glefand/homebridge-rfxcom) and [jhurliman/homebridge-rfxcom](https://github.com/jhurliman/homebridge-rfxcom).
8
+
9
+ ## Features
10
+
11
+ - **Homebridge v2.0 support** — fully compatible with Homebridge 2.0
12
+ - **Homebridge UI configuration** — configure the plugin directly from the Homebridge UI (no manual JSON editing required)
13
+ - **Modern characteristic API** — uses `onGet`/`onSet` for reliable accessory restoration after restarts
14
+ - **Node 22 / 24 support**
15
+
16
+ ## Compatibility
17
+
18
+ | Requirement | Version |
19
+ |---|---|
20
+ | Homebridge | ^1.6.0 \|\| ^2.0.0 |
21
+ | Node.js | ^22.12.0 \|\| ^24.0.0 |
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install -g homebridge-somfy-rfxcom
27
+ ```
28
+
29
+ Or install via the **Homebridge UI** by searching for `homebridge-somfy-rfxcom`.
30
+
31
+ ## Configuration
32
+
33
+ This plugin can be configured through the **Homebridge UI**. Alternatively, add the following to your `config.json`:
34
+
35
+ ```json
36
+ {
37
+ "platforms": [
38
+ {
39
+ "platform": "SomfyRFXCom",
40
+ "name": "Somfy RFXCom",
41
+ "tty": "/dev/ttyUSB0",
42
+ "debug": false,
43
+ "rfyRemotes": [
44
+ {
45
+ "name": "Awning",
46
+ "deviceID": "0x010000/1",
47
+ "openCloseSeconds": 18
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ ```
54
+
55
+ ### Platform Options
56
+
57
+ | Option | Required | Default | Description |
58
+ |---|---|---|---|
59
+ | `platform` | Yes | — | Must be `SomfyRFXCom` |
60
+ | `name` | Yes | — | Display name for the platform |
61
+ | `tty` | No | `/dev/ttyUSB0` | Path to the RFXtrx USB device |
62
+ | `debug` | No | `false` | Enable debug logging |
63
+
64
+ ### RFY Remotes
65
+
66
+ | Option | Required | Default | Description |
67
+ |---|---|---|---|
68
+ | `name` | Yes | — | Display name in HomeKit |
69
+ | `deviceID` | Yes | — | Remote address and unit code (e.g. `0x010000/1`). Found in RFXMngr (Windows). |
70
+ | `openCloseSeconds` | No | `5` | Seconds for the blinds/awning/gate to fully open or close |
71
+
72
+ ## How It Works
73
+
74
+ Each RFY remote creates three switches in HomeKit: **Up**, **Down**, and **Stop**. The active switch auto-resets after `openCloseSeconds` to reflect that the movement has completed.
75
+
76
+ ## Programming a New Remote
77
+
78
+ Before a Somfy device will respond to commands from your RFXtrx, the device's address must be programmed into the motor. Use [RFXMngr](http://www.rfxcom.com/epages/78165469.sf/en_GB/?ObjectPath=/Shops/78165469/Categories/Downloads) (Windows) to assign and program a new remote address, then use that address as the `deviceID` in your config.
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,60 @@
1
+ {
2
+ "pluginAlias": "SomfyRFXCom",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "headerDisplay": "Homebridge plugin for controlling Somfy RTS blinds, awnings, and gates via an RFXtrx433(E) transceiver.",
6
+ "footerDisplay": "For help, see the [README](https://github.com/nroughol-dev/homebridge-somfy-rfxcom#readme).",
7
+ "schema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "name": {
11
+ "title": "Name",
12
+ "type": "string",
13
+ "default": "Somfy RFXCom",
14
+ "required": true,
15
+ "description": "Display name for the platform."
16
+ },
17
+ "tty": {
18
+ "title": "Serial Port",
19
+ "type": "string",
20
+ "default": "/dev/ttyUSB0",
21
+ "description": "Path to the RFXtrx USB device."
22
+ },
23
+ "debug": {
24
+ "title": "Debug Mode",
25
+ "type": "boolean",
26
+ "default": false,
27
+ "description": "Enable debug logging for the RFXtrx communication."
28
+ },
29
+ "rfyRemotes": {
30
+ "title": "RFY Remotes",
31
+ "type": "array",
32
+ "items": {
33
+ "type": "object",
34
+ "properties": {
35
+ "name": {
36
+ "title": "Name",
37
+ "type": "string",
38
+ "required": true,
39
+ "description": "Display name of the remote in HomeKit."
40
+ },
41
+ "deviceID": {
42
+ "title": "Device ID",
43
+ "type": "string",
44
+ "required": true,
45
+ "pattern": "^0x[0-9a-fA-F]+/\\d+$",
46
+ "description": "Remote address and unit code (e.g. 0x000001/1). Found in RFXMngr."
47
+ },
48
+ "openCloseSeconds": {
49
+ "title": "Open/Close Duration",
50
+ "type": "integer",
51
+ "default": 5,
52
+ "minimum": 1,
53
+ "description": "Seconds for the blinds/awning/gate to fully open or close."
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
package/erase.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // Standalone helper to erase an RFY remote address from a Somfy motor.
5
+ // Usage: node erase.js <tty> <deviceID>
6
+ // e.g. node erase.js /dev/ttyUSB0 0x010000/1
7
+
8
+ const rfxcom = require('rfxcom')
9
+
10
+ const tty = process.argv[2] || '/dev/ttyUSB0'
11
+ const deviceID = process.argv[3]
12
+
13
+ if (!deviceID) {
14
+ console.error('Usage: node erase.js <tty> <deviceID>')
15
+ console.error(' e.g. node erase.js /dev/ttyUSB0 0x010000/1')
16
+ process.exit(1)
17
+ }
18
+
19
+ const rfxtrx = new rfxcom.RfxCom(tty, { debug: true })
20
+ const rfy = new rfxcom.Rfy(rfxtrx, rfxcom.rfy.RFY)
21
+
22
+ rfxtrx.initialise(() => {
23
+ rfy.erase(deviceID, (err) => {
24
+ if (err) {
25
+ console.error('Erase failed:', err)
26
+ process.exit(1)
27
+ }
28
+ console.log('complete')
29
+ process.exit(0)
30
+ })
31
+ })
package/index.js ADDED
@@ -0,0 +1,235 @@
1
+ const rfxcom = require('rfxcom')
2
+
3
+ const PLUGIN_ID = 'homebridge-somfy-rfxcom'
4
+ const PLUGIN_NAME = 'SomfyRFXCom'
5
+ const DEFAULT_OPEN_CLOSE_SECONDS = 5
6
+
7
+ let Accessory, Service, Characteristic, UUIDGen
8
+
9
+ module.exports = function(homebridge) {
10
+ Accessory = homebridge.platformAccessory
11
+ Service = homebridge.hap.Service
12
+ Characteristic = homebridge.hap.Characteristic
13
+ UUIDGen = homebridge.hap.uuid
14
+
15
+ homebridge.registerPlatform(PLUGIN_ID, PLUGIN_NAME, RFXComPlatform, true)
16
+ }
17
+
18
+ function RFXComPlatform(log, config, api) {
19
+ this.log = log
20
+ this.config = config || { platform: PLUGIN_NAME }
21
+ this.tty = this.config.tty || '/dev/ttyUSB0'
22
+ this.debug = this.config.debug || false
23
+
24
+ const rfyRemotes = this.config.rfyRemotes || this.config.rfyremotes
25
+ this.rfyRemotes = Array.isArray(rfyRemotes) ? rfyRemotes : []
26
+
27
+ this.accessories = {}
28
+ this.rfxtrx = null
29
+ this.rfy = null
30
+
31
+ if (api) {
32
+ this.api = api
33
+ this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this))
34
+ }
35
+ }
36
+
37
+ // Method to restore accessories from cache
38
+ RFXComPlatform.prototype.configureAccessory = function(accessory) {
39
+ this.log(
40
+ `Loaded from cache: ${accessory.context.name} (${accessory.context
41
+ .switchID})`
42
+ )
43
+
44
+ const existing = this.accessories[accessory.context.switchID]
45
+ if (existing) this.removeAccessory(existing)
46
+
47
+ this.accessories[accessory.context.switchID] = accessory
48
+ }
49
+
50
+ // Method to setup accesories from config.json
51
+ RFXComPlatform.prototype.didFinishLaunching = function() {
52
+ if (!this.rfyRemotes.length) {
53
+ this.log('No RFY remotes configured. Add remotes via the Homebridge UI to get started.')
54
+ this.removeAccessories()
55
+ return
56
+ }
57
+
58
+ this.rfxtrx = new rfxcom.RfxCom(this.tty, { debug: this.debug })
59
+ this.rfy = new rfxcom.Rfy(this.rfxtrx, rfxcom.rfy.RFY)
60
+
61
+ this.rfxtrx.on('disconnect', () => this.log('ERROR: RFXtrx disconnect'))
62
+ this.rfxtrx.on('connectfailed', () => this.log('ERROR: RFXtrx connect fail'))
63
+
64
+ // Compare local config against RFXCom-registered remotes
65
+ this.listRFYRemotes()
66
+ .then(deviceRemotes => {
67
+ this.log(`Received ${deviceRemotes.length} remote(s) from device`)
68
+
69
+ this.rfyRemotes.forEach(remote => {
70
+ // Handle different capitalizations of deviceID
71
+ remote.deviceID = remote.deviceID || remote.deviceId
72
+
73
+ const deviceID = remote.deviceID
74
+ const device = deviceRemotes.find(dR => deviceID === dR.deviceId)
75
+
76
+ if (device) {
77
+ this.addRFYRemote(remote, device)
78
+ this.log(`Added accessories for RFY remote ${remote.deviceID}`)
79
+ } else {
80
+ const msg = deviceRemotes.map(dR => `${dR.deviceId}`).join(', ')
81
+ this.log(`ERROR: RFY remote ${deviceID} not found. Found: ${msg}`)
82
+ }
83
+ })
84
+ })
85
+ .catch(err => {
86
+ this.log(`UNHANDLED ERROR: ${err}`)
87
+ })
88
+ }
89
+
90
+ RFXComPlatform.prototype.listRFYRemotes = function() {
91
+ return new Promise((resolve, reject) => {
92
+ this.rfxtrx.once('rfyremoteslist', remotes => resolve(remotes))
93
+
94
+ this.rfxtrx.initialise(() => {
95
+ this.log('RFXtrx initialized, listing remotes...')
96
+ this.rfy.listRemotes()
97
+ })
98
+ })
99
+ }
100
+
101
+ // Method to add or update HomeKit accessory
102
+ RFXComPlatform.prototype.addRFYRemote = function(remote, device) {
103
+ remote.switches = {}
104
+
105
+ this.addRFYRemoteSwitch(remote, device, 'Up')
106
+ this.addRFYRemoteSwitch(remote, device, 'Down')
107
+ this.addRFYRemoteSwitch(remote, device, 'Stop')
108
+ }
109
+
110
+ RFXComPlatform.prototype.addRFYRemoteSwitch = function(remote, device, type) {
111
+ const deviceID = remote.deviceID
112
+ const switchID = `${deviceID}/${type}`
113
+ const name = `${remote.name} ${type}`
114
+
115
+ let accessory = this.accessories[switchID]
116
+ let isNew = false
117
+
118
+ if (!accessory) {
119
+ this.log(`Creating new accessory: ${switchID}`)
120
+ const uuid = UUIDGen.generate(switchID)
121
+ accessory = new Accessory(remote.name, uuid)
122
+ isNew = true
123
+ } else {
124
+ this.log(`Restoring cached accessory: ${switchID}`)
125
+ }
126
+
127
+ this.accessories[switchID] = accessory
128
+
129
+ accessory.context = {
130
+ deviceID: deviceID,
131
+ switchID: switchID,
132
+ name: name,
133
+ device: device,
134
+ isOn: accessory.context.isOn || false
135
+ }
136
+
137
+ remote.switches[type] = accessory
138
+
139
+ if (!accessory.getService(Service.Switch)) {
140
+ accessory.addService(Service.Switch, name)
141
+ }
142
+
143
+ accessory
144
+ .getService(Service.AccessoryInformation)
145
+ .setCharacteristic(Characteristic.Manufacturer, 'RFXCOM')
146
+ .setCharacteristic(Characteristic.Model, device.remoteType)
147
+ .setCharacteristic(
148
+ Characteristic.SerialNumber,
149
+ `${deviceID}-${device.unitCode}-${type}`
150
+ )
151
+
152
+ accessory
153
+ .on('identify', (paired, callback) => {
154
+ this.log(`${name} identify requested, paired=${paired}`)
155
+ callback()
156
+ })
157
+
158
+ const characteristic = accessory
159
+ .getService(Service.Switch)
160
+ .getCharacteristic(Characteristic.On)
161
+
162
+ characteristic.removeAllListeners('get')
163
+ characteristic.removeAllListeners('set')
164
+
165
+ characteristic
166
+ .onGet(() => accessory.context.isOn)
167
+ .onSet((value) => {
168
+ if (!value || type === 'Stop') {
169
+ this.log(`RFY STOP ${remote.deviceID}`)
170
+ this.rfy.stop(remote.deviceID)
171
+
172
+ setTimeout(() => {
173
+ for (const t in remote.switches)
174
+ this.setSwitch(remote.switches[t], false)
175
+ }, 100)
176
+
177
+ return
178
+ }
179
+
180
+ switch (type) {
181
+ case 'Up':
182
+ this.log(`RFY UP ${remote.deviceID}`)
183
+ this.rfy.up(remote.deviceID)
184
+ break
185
+ case 'Down':
186
+ this.log(`RFY DOWN ${remote.deviceID}`)
187
+ this.rfy.down(remote.deviceID)
188
+ break
189
+ }
190
+
191
+ for (const t in remote.switches)
192
+ this.setSwitch(remote.switches[t], t === type)
193
+
194
+ const ms = isNaN(remote.openCloseSeconds)
195
+ ? DEFAULT_OPEN_CLOSE_SECONDS * 1000
196
+ : Math.round(remote.openCloseSeconds * 1000)
197
+ clearTimeout(accessory.timerID)
198
+ accessory.timerID = setTimeout(() => this.setSwitch(accessory, false), ms)
199
+ })
200
+
201
+ if (isNew) {
202
+ this.api.registerPlatformAccessories(PLUGIN_ID, PLUGIN_NAME, [accessory])
203
+ } else {
204
+ this.api.updatePlatformAccessories([accessory])
205
+ }
206
+
207
+ this.setSwitch(accessory, accessory.context.isOn)
208
+
209
+ return accessory
210
+ }
211
+
212
+ RFXComPlatform.prototype.setSwitch = function(accessory, isOn) {
213
+ this.log(`Updating switch ${accessory.context.switchID}, on=${isOn}`)
214
+
215
+ accessory.context.isOn = isOn
216
+ accessory
217
+ .getService(Service.Switch)
218
+ .getCharacteristic(Characteristic.On)
219
+ .updateValue(isOn)
220
+ }
221
+
222
+ // Method to remove accessories from HomeKit
223
+ RFXComPlatform.prototype.removeAccessory = function(accessory) {
224
+ if (!accessory) return
225
+
226
+ const switchID = accessory.context.switchID
227
+ this.log(`${accessory.context.name} (${switchID}) removed from HomeBridge.`)
228
+ this.api.unregisterPlatformAccessories(PLUGIN_ID, PLUGIN_NAME, [accessory])
229
+ delete this.accessories[switchID]
230
+ }
231
+
232
+ // Method to remove all accessories from HomeKit
233
+ RFXComPlatform.prototype.removeAccessories = function() {
234
+ Object.values(this.accessories).forEach(accessory => this.removeAccessory(accessory))
235
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "homebridge-somfy-rfxcom",
3
+ "displayName": "Somfy RFXcom",
4
+ "version": "2.0.0",
5
+ "description": "Homebridge plugin for controlling Somfy RTS blinds, awnings, and gates via an RFXtrx433(E) transceiver.",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1",
9
+ "rfyerase": "node erase.js",
10
+ "rfyprogram": "node program.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/nroughol-dev/homebridge-somfy-rfxcom.git"
15
+ },
16
+ "author": "Nicolas Roughol",
17
+ "license": "MIT",
18
+ "bugs": {
19
+ "url": "https://github.com/nroughol-dev/homebridge-somfy-rfxcom/issues"
20
+ },
21
+ "homepage": "https://github.com/nroughol-dev/homebridge-somfy-rfxcom#readme",
22
+ "keywords": [
23
+ "homebridge-plugin",
24
+ "rfxcom",
25
+ "rfxtrx",
26
+ "rfxtrx433",
27
+ "rfxtrx433e",
28
+ "433",
29
+ "somfy",
30
+ "rts",
31
+ "rfy",
32
+ "homebridge",
33
+ "homekit",
34
+ "window",
35
+ "blinds",
36
+ "awning",
37
+ "gate",
38
+ "remote"
39
+ ],
40
+ "engines": {
41
+ "homebridge": "^1.6.0 || ^2.0.0",
42
+ "node": "^22.12.0 || ^24.0.0"
43
+ },
44
+ "dependencies": {
45
+ "rfxcom": ">=2.1.0"
46
+ }
47
+ }
package/program.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // Standalone helper to program an RFY remote address into a Somfy motor.
5
+ // Put the motor into programming mode first, then run:
6
+ // node program.js <tty> <deviceID>
7
+ // e.g. node program.js /dev/ttyUSB0 0x010000/1
8
+
9
+ const rfxcom = require('rfxcom')
10
+
11
+ const tty = process.argv[2] || '/dev/ttyUSB0'
12
+ const deviceID = process.argv[3]
13
+
14
+ if (!deviceID) {
15
+ console.error('Usage: node program.js <tty> <deviceID>')
16
+ console.error(' e.g. node program.js /dev/ttyUSB0 0x010000/1')
17
+ process.exit(1)
18
+ }
19
+
20
+ const rfxtrx = new rfxcom.RfxCom(tty, { debug: true })
21
+ const rfy = new rfxcom.Rfy(rfxtrx, rfxcom.rfy.RFY)
22
+
23
+ rfxtrx.initialise(() => {
24
+ rfy.program(deviceID, (err) => {
25
+ if (err) {
26
+ console.error('Program failed:', err)
27
+ process.exit(1)
28
+ }
29
+ console.log('complete')
30
+ process.exit(0)
31
+ })
32
+ })