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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +45 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/config.schema.json +60 -0
- package/erase.js +31 -0
- package/index.js +235 -0
- package/package.json +47 -0
- package/program.js +32 -0
|
@@ -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
|
+
})
|