matterbridge-webhooks 1.0.2 → 1.1.0-edge.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/CHANGELOG.md +55 -0
- package/README.md +56 -19
- package/dist/module.js +199 -19
- package/matterbridge-webhooks.config.json +2 -0
- package/matterbridge-webhooks.schema.json +71 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,61 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
6
6
|
|
|
7
7
|
<a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/bmc-button.svg" alt="Buy me a coffee" width="120"></a>
|
|
8
8
|
|
|
9
|
+
## [1.1.0-edge.3] - Not released
|
|
10
|
+
|
|
11
|
+
### Added Webhook devices
|
|
12
|
+
|
|
13
|
+
It allows to create a device based on webhooks.
|
|
14
|
+
|
|
15
|
+
Features:
|
|
16
|
+
|
|
17
|
+
- It is possible to choose the device type from the config.
|
|
18
|
+
- It is possible to set the method with a prefix 'GET#' or 'POST# in the urls. Default if omitted is GET.
|
|
19
|
+
- It is possible to use converters in the url.
|
|
20
|
+
|
|
21
|
+
## Supported device types:
|
|
22
|
+
|
|
23
|
+
| Device type | Urls |
|
|
24
|
+
| -------------- | ------------------------------------ |
|
|
25
|
+
| outlet | on off |
|
|
26
|
+
| onOffLight | on off |
|
|
27
|
+
| dimmerLight | on off brightness |
|
|
28
|
+
| colorTempLight | on off brightness colorTemp |
|
|
29
|
+
| extendedLight | on off brightness colorTemp colorRgb |
|
|
30
|
+
|
|
31
|
+
If there is interest, let me know and I will add all other device types.
|
|
32
|
+
|
|
33
|
+
## Supported request converters:
|
|
34
|
+
|
|
35
|
+
| Converter | Return value |
|
|
36
|
+
| ------------- | ------------------- |
|
|
37
|
+
| ${LEVEL} | matter 1-254 |
|
|
38
|
+
| ${LEVEL100} | percentage 0-100 |
|
|
39
|
+
| ${MIRED} | colorTemp in mired |
|
|
40
|
+
| ${KELVIN} | colorTemp in kelvin |
|
|
41
|
+
| ${HUE} | hue 0-360 |
|
|
42
|
+
| ${SATURATION} | saturation 0-100 |
|
|
43
|
+
| ${COLORX} | colorX 0-1 |
|
|
44
|
+
| ${COLORY} | colorX 0-1 |
|
|
45
|
+
|
|
46
|
+
## Supported cluster attributes:
|
|
47
|
+
|
|
48
|
+
| Attributes | Return value |
|
|
49
|
+
| ------------- | ------------------- |
|
|
50
|
+
| ${level} | matter 1-254 |
|
|
51
|
+
| ${level100} | percentage 0-100 |
|
|
52
|
+
| ${mired} | colorTemp in mired |
|
|
53
|
+
| ${kelvin} | colorTemp in kelvin |
|
|
54
|
+
| ${hue} | hue 0-360 |
|
|
55
|
+
| ${saturation} | saturation 0-100 |
|
|
56
|
+
| ${colorX} | colorX 0-1 |
|
|
57
|
+
| ${colorY} | colorX 0-1 |
|
|
58
|
+
| ${red} | red 0-255 |
|
|
59
|
+
| ${gree} | green 0-255 |
|
|
60
|
+
| ${blue} | blue 0-255 |
|
|
61
|
+
|
|
62
|
+
<a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/bmc-button.svg" alt="Buy me a coffee" width="80"></a>
|
|
63
|
+
|
|
9
64
|
## [1.0.2] - 2025-12-12
|
|
10
65
|
|
|
11
66
|
### Changed
|
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
This plugin allows you to expose
|
|
18
|
+
This plugin allows you to expose single webhooks or complex webhook devices to Matter.
|
|
19
|
+
|
|
20
|
+
# Simple webhooks
|
|
19
21
|
|
|
20
22
|
Features:
|
|
21
23
|
|
|
@@ -24,31 +26,66 @@ Features:
|
|
|
24
26
|
- It is possible to choose the method: GET or POST.
|
|
25
27
|
- The webhook can be tested in the frontend.
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
# Webhook devices
|
|
30
|
+
|
|
31
|
+
Features:
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
- It is possible to choose the device type from the config.
|
|
34
|
+
- It is possible to set the method with a prefix 'GET#' or 'POST# in the urls. Default if omitted is GET.
|
|
35
|
+
- It is possible to use converters and attributes in the url.
|
|
36
|
+
|
|
37
|
+
## Supported device types:
|
|
38
|
+
|
|
39
|
+
| Device type | Urls |
|
|
40
|
+
| -------------- | ------------------------------------ |
|
|
41
|
+
| outlet | on off |
|
|
42
|
+
| onOffLight | on off |
|
|
43
|
+
| dimmerLight | on off brightness |
|
|
44
|
+
| colorTempLight | on off brightness colorTemp |
|
|
45
|
+
| extendedLight | on off brightness colorTemp colorRgb |
|
|
46
|
+
|
|
47
|
+
If there is interest, let me know and I will add all other device types.
|
|
48
|
+
|
|
49
|
+
## Supported request converters:
|
|
50
|
+
|
|
51
|
+
| Converter | Return value |
|
|
52
|
+
| ------------- | ------------------- |
|
|
53
|
+
| ${LEVEL} | matter 1-254 |
|
|
54
|
+
| ${LEVEL100} | percentage 0-100 |
|
|
55
|
+
| ${MIRED} | colorTemp in mired |
|
|
56
|
+
| ${KELVIN} | colorTemp in kelvin |
|
|
57
|
+
| ${HUE} | hue 0-360 |
|
|
58
|
+
| ${SATURATION} | saturation 0-100 |
|
|
59
|
+
| ${COLORX} | colorX 0-1 |
|
|
60
|
+
| ${COLORY} | colorX 0-1 |
|
|
61
|
+
|
|
62
|
+
## Supported cluster attributes:
|
|
63
|
+
|
|
64
|
+
| Attributes | Return value |
|
|
65
|
+
| ------------- | ------------------- |
|
|
66
|
+
| ${level} | matter 1-254 |
|
|
67
|
+
| ${level100} | percentage 0-100 |
|
|
68
|
+
| ${mired} | colorTemp in mired |
|
|
69
|
+
| ${kelvin} | colorTemp in kelvin |
|
|
70
|
+
| ${hue} | hue 0-360 |
|
|
71
|
+
| ${saturation} | saturation 0-100 |
|
|
72
|
+
| ${colorX} | colorX 0-1 |
|
|
73
|
+
| ${colorY} | colorX 0-1 |
|
|
74
|
+
| ${red} | red 0-255 |
|
|
75
|
+
| ${gree} | green 0-255 |
|
|
76
|
+
| ${blue} | blue 0-255 |
|
|
77
|
+
|
|
78
|
+
If you like this project and find it useful, please consider giving it a star on [GitHub](https://github.com/Luligu/matterbridge-webhooks) and sponsoring it.
|
|
79
|
+
|
|
80
|
+
<a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/bmc-button.svg" alt="Buy me a coffee" width="120"></a>
|
|
32
81
|
|
|
33
82
|
## Prerequisites
|
|
34
83
|
|
|
35
84
|
### Matterbridge
|
|
36
85
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
npm install -g matterbridge --omit=dev
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
on Linux you may need the necessary permissions:
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
sudo npm install -g matterbridge --omit=dev
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
See the complete guidelines on [Matterbridge](https://github.com/Luligu/matterbridge/blob/main/README.md) for more information.
|
|
86
|
+
See the complete guidelines on [Matterbridge](https://matterbridge.io/README.html) for more information.
|
|
50
87
|
|
|
51
|
-
## How to add a webhook
|
|
88
|
+
## How to add a simple webhook
|
|
52
89
|
|
|
53
90
|
In the frontend open the plugin config: add a new webhook, enter the webhook name in the first field (replace newKey with the name you want to give to the webhook), select GET or POST and enter the webhook url. The webhook name will be the device name on the controller. The webhook will be exposed like a switch, like an outlet or like a light. When you turn it on, the webhook is called and in a few seconds the switch or the outlet or the light will revert to off.
|
|
54
91
|
|
package/dist/module.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { bridgedNode, MatterbridgeDynamicPlatform, MatterbridgeEndpoint, onOffLight, onOffOutlet, onOffSwitch } from 'matterbridge';
|
|
2
|
-
import { isValidObject } from 'matterbridge/utils';
|
|
1
|
+
import { bridgedNode, colorTemperatureLight, dimmableLight, extendedColorLight, MatterbridgeColorControlServer, MatterbridgeDynamicPlatform, MatterbridgeEndpoint, MatterbridgeLevelControlServer, onOffLight, onOffOutlet, onOffSwitch, } from 'matterbridge';
|
|
2
|
+
import { hslColorToRgbColor, isValidNumber, isValidObject, isValidString, miredToKelvin, wait } from 'matterbridge/utils';
|
|
3
|
+
import { rs } from 'matterbridge/logger';
|
|
3
4
|
import { fetch } from './fetch.js';
|
|
4
5
|
export default function initializePlugin(matterbridge, log, config) {
|
|
5
6
|
return new WebhooksPlatform(matterbridge, log, config);
|
|
6
7
|
}
|
|
7
8
|
export class WebhooksPlatform extends MatterbridgeDynamicPlatform {
|
|
8
|
-
|
|
9
|
-
bridgedDevices = new Map();
|
|
9
|
+
config;
|
|
10
10
|
constructor(matterbridge, log, config) {
|
|
11
11
|
super(matterbridge, log, config);
|
|
12
|
+
this.config = config;
|
|
12
13
|
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.4.0')) {
|
|
13
14
|
throw new Error(`This plugin requires Matterbridge version >= "3.4.0". Please update Matterbridge to the latest version in the frontend.`);
|
|
14
15
|
}
|
|
15
16
|
this.log.info('Initializing platform:', this.config.name);
|
|
16
|
-
this.webhooks = config.webhooks;
|
|
17
17
|
this.log.info('Finished initializing platform:', this.config.name);
|
|
18
18
|
}
|
|
19
19
|
async onStart(reason) {
|
|
20
20
|
this.log.info('onStart called with reason:', reason ?? 'none');
|
|
21
21
|
await this.ready;
|
|
22
22
|
await this.clearSelect();
|
|
23
|
-
let i =
|
|
24
|
-
for (const webhookName in this.webhooks) {
|
|
25
|
-
this.log.debug(`Loading webhook ${
|
|
26
|
-
const webhook = this.webhooks[webhookName];
|
|
23
|
+
let i = 1;
|
|
24
|
+
for (const webhookName in this.config.webhooks) {
|
|
25
|
+
this.log.debug(`Loading webhook ${i} ${webhookName} with method ${this.config.webhooks[webhookName].method} and url ${this.config.webhooks[webhookName].httpUrl}`);
|
|
26
|
+
const webhook = this.config.webhooks[webhookName];
|
|
27
27
|
this.setSelectDevice('webhook' + i, webhookName, undefined, 'hub');
|
|
28
28
|
if (!this.validateDevice(['webhook' + i, webhookName], true))
|
|
29
29
|
continue;
|
|
@@ -33,8 +33,9 @@ export class WebhooksPlatform extends MatterbridgeDynamicPlatform {
|
|
|
33
33
|
.createOnOffClusterServer(false)
|
|
34
34
|
.addRequiredClusterServers()
|
|
35
35
|
.addCommandHandler('on', async () => {
|
|
36
|
-
this.log.info(`Webhook ${webhookName} triggered
|
|
36
|
+
this.log.info(`Webhook ${webhookName} triggered`);
|
|
37
37
|
await device.setAttribute('onOff', 'onOff', false, device.log);
|
|
38
|
+
this.log.debug(`Fetching ${webhook.httpUrl} with ${webhook.method}...`);
|
|
38
39
|
fetch(webhook.httpUrl, webhook.method)
|
|
39
40
|
.then(() => this.log.notice(`Webhook ${webhookName} successful!`))
|
|
40
41
|
.catch((err) => {
|
|
@@ -42,16 +43,87 @@ export class WebhooksPlatform extends MatterbridgeDynamicPlatform {
|
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
await this.registerDevice(device);
|
|
45
|
-
|
|
46
|
+
}
|
|
47
|
+
i = 1;
|
|
48
|
+
for (const outletName in this.config.outlets) {
|
|
49
|
+
this.log.debug(`Loading outlet ${i} ${outletName}...`);
|
|
50
|
+
const webhook = this.config.outlets[outletName];
|
|
51
|
+
this.setSelectDevice('outlet' + i, outletName, undefined, 'hub');
|
|
52
|
+
if (!this.validateDevice(['outlet' + i, outletName], true))
|
|
53
|
+
continue;
|
|
54
|
+
this.log.info(`Registering outlet: ${outletName}...`);
|
|
55
|
+
const device = new MatterbridgeEndpoint([onOffOutlet, bridgedNode], { id: outletName }, this.config.debug)
|
|
56
|
+
.createDefaultBridgedDeviceBasicInformationClusterServer(outletName, 'outlet' + i++, this.matterbridge.aggregatorVendorId, 'Matterbridge', 'Matterbridge Webhook Outlet', 0, this.config.version)
|
|
57
|
+
.createOnOffClusterServer(false)
|
|
58
|
+
.addRequiredClusterServers()
|
|
59
|
+
.addCommandHandler('on', async (data) => {
|
|
60
|
+
this.parseUrl('outlet', outletName, 'on', webhook.onUrl, data);
|
|
61
|
+
})
|
|
62
|
+
.addCommandHandler('off', async (data) => {
|
|
63
|
+
this.parseUrl('outlet', outletName, 'off', webhook.offUrl, data);
|
|
64
|
+
});
|
|
65
|
+
await this.registerDevice(device);
|
|
66
|
+
}
|
|
67
|
+
i = 1;
|
|
68
|
+
for (const lightName in this.config.lights) {
|
|
69
|
+
this.log.debug(`Loading light ${i} ${lightName}...`);
|
|
70
|
+
const webhook = this.config.lights[lightName];
|
|
71
|
+
this.setSelectDevice('light' + i, lightName, undefined, 'hub');
|
|
72
|
+
if (!this.validateDevice(['light' + i, lightName], true))
|
|
73
|
+
continue;
|
|
74
|
+
this.log.info(`Registering light: ${lightName}...`);
|
|
75
|
+
let deviceType = onOffLight;
|
|
76
|
+
if (isValidString(webhook.brightnessUrl, 1))
|
|
77
|
+
deviceType = dimmableLight;
|
|
78
|
+
if (isValidString(webhook.colorTempUrl, 1))
|
|
79
|
+
deviceType = colorTemperatureLight;
|
|
80
|
+
if (isValidString(webhook.rgbUrl, 1))
|
|
81
|
+
deviceType = extendedColorLight;
|
|
82
|
+
const device = new MatterbridgeEndpoint([deviceType, bridgedNode], { id: lightName }, this.config.debug)
|
|
83
|
+
.createDefaultBridgedDeviceBasicInformationClusterServer(lightName, 'light' + i++, this.matterbridge.aggregatorVendorId, 'Matterbridge', 'Matterbridge Webhook Light', 0, this.config.version)
|
|
84
|
+
.createOnOffClusterServer(false)
|
|
85
|
+
.createDefaultColorControlClusterServer(undefined, undefined, undefined, undefined, 250, webhook.minMireds, webhook.maxMireds)
|
|
86
|
+
.createDefaultLevelControlClusterServer()
|
|
87
|
+
.addRequiredClusterServers()
|
|
88
|
+
.addCommandHandler('on', async (data) => {
|
|
89
|
+
this.parseUrl('light', lightName, 'on', webhook.onUrl, data);
|
|
90
|
+
})
|
|
91
|
+
.addCommandHandler('off', async (data) => {
|
|
92
|
+
this.parseUrl('light', lightName, 'off', webhook.offUrl, data);
|
|
93
|
+
})
|
|
94
|
+
.addCommandHandler('moveToLevel', async (data) => {
|
|
95
|
+
this.parseUrl('light', lightName, 'moveToLevel', webhook.brightnessUrl, data);
|
|
96
|
+
})
|
|
97
|
+
.addCommandHandler('moveToLevelWithOnOff', async (data) => {
|
|
98
|
+
this.parseUrl('light', lightName, 'moveToLevelWithOnOff', webhook.brightnessUrl, data);
|
|
99
|
+
})
|
|
100
|
+
.addCommandHandler('moveToColorTemperature', async (data) => {
|
|
101
|
+
this.parseUrl('light', lightName, 'moveToColorTemperature', webhook.colorTempUrl, data);
|
|
102
|
+
})
|
|
103
|
+
.addCommandHandler('moveToHueAndSaturation', async (data) => {
|
|
104
|
+
this.parseUrl('light', lightName, 'moveToHueAndSaturation', webhook.rgbUrl, data);
|
|
105
|
+
})
|
|
106
|
+
.addCommandHandler('moveToHue', async (data) => {
|
|
107
|
+
this.parseUrl('light', lightName, 'moveToHue', webhook.rgbUrl, data);
|
|
108
|
+
})
|
|
109
|
+
.addCommandHandler('moveToSaturation', async (data) => {
|
|
110
|
+
this.parseUrl('light', lightName, 'moveToSaturation', webhook.rgbUrl, data);
|
|
111
|
+
})
|
|
112
|
+
.addCommandHandler('moveToColor', async (data) => {
|
|
113
|
+
this.parseUrl('light', lightName, 'moveToColor', webhook.rgbUrl, data);
|
|
114
|
+
});
|
|
115
|
+
await this.registerDevice(device);
|
|
46
116
|
}
|
|
47
117
|
}
|
|
48
118
|
async onConfigure() {
|
|
49
119
|
await super.onConfigure();
|
|
50
120
|
this.log.info('onConfigure called');
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
121
|
+
for (const device of this.getDevices()) {
|
|
122
|
+
if (device.deviceName && device.deviceName in this.config.webhooks) {
|
|
123
|
+
this.log.info(`Configuring device: ${device.deviceName}`);
|
|
124
|
+
await device.setAttribute('onOff', 'onOff', false, device.log);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
55
127
|
}
|
|
56
128
|
async onAction(action, value, id, formData) {
|
|
57
129
|
this.log.info('onAction called with action:', action, 'and value:', value ?? 'none', 'and id:', id ?? 'none');
|
|
@@ -81,9 +153,9 @@ export class WebhooksPlatform extends MatterbridgeDynamicPlatform {
|
|
|
81
153
|
}
|
|
82
154
|
return;
|
|
83
155
|
}
|
|
84
|
-
for (const webhookName in this.webhooks) {
|
|
85
|
-
if (Object.prototype.hasOwnProperty.call(this.webhooks, webhookName)) {
|
|
86
|
-
const webhook = this.webhooks[webhookName];
|
|
156
|
+
for (const webhookName in this.config.webhooks) {
|
|
157
|
+
if (Object.prototype.hasOwnProperty.call(this.config.webhooks, webhookName)) {
|
|
158
|
+
const webhook = this.config.webhooks[webhookName];
|
|
87
159
|
if (id?.includes(webhookName)) {
|
|
88
160
|
this.log.info(`Testing webhook ${webhookName} method ${webhook.method} url ${webhook.httpUrl}`);
|
|
89
161
|
fetch(webhook.httpUrl, webhook.method)
|
|
@@ -104,6 +176,114 @@ export class WebhooksPlatform extends MatterbridgeDynamicPlatform {
|
|
|
104
176
|
this.log.info('onShutdown called with reason:', reason ?? 'none');
|
|
105
177
|
if (this.config.unregisterOnShutdown === true)
|
|
106
178
|
await this.unregisterAllDevices();
|
|
107
|
-
|
|
179
|
+
}
|
|
180
|
+
async parseUrl(deviceType, deviceName, command, url, data) {
|
|
181
|
+
this.log.info(`Webhook ${deviceType} ${deviceName} ${command} triggered`);
|
|
182
|
+
const endpoint = data.endpoint;
|
|
183
|
+
this.log.debug(`Webhook ${deviceType} ${deviceName} ${command} triggered on endpoint ${endpoint?.deviceName}`);
|
|
184
|
+
let method = 'GET';
|
|
185
|
+
let parsedUrl = url;
|
|
186
|
+
if (url.startsWith('GET#')) {
|
|
187
|
+
method = 'GET';
|
|
188
|
+
parsedUrl = url.replace('GET#', '');
|
|
189
|
+
}
|
|
190
|
+
else if (url.startsWith('POST#')) {
|
|
191
|
+
method = 'POST';
|
|
192
|
+
parsedUrl = url.replace('POST#', '');
|
|
193
|
+
}
|
|
194
|
+
if (parsedUrl.includes('${LEVEL}') && isValidNumber(data.request.level)) {
|
|
195
|
+
parsedUrl = parsedUrl.replace('${LEVEL}', data.request.level.toString());
|
|
196
|
+
}
|
|
197
|
+
if (url.includes('${LEVEL100}') && isValidNumber(data.request.level)) {
|
|
198
|
+
parsedUrl = parsedUrl.replace('${LEVEL100}', Math.round((data.request.level / 254) * 100).toString());
|
|
199
|
+
}
|
|
200
|
+
if (parsedUrl.includes('${KELVIN}') && isValidNumber(data.request.colorTemperatureMireds)) {
|
|
201
|
+
parsedUrl = parsedUrl.replace('${KELVIN}', Math.round(miredToKelvin(data.request.colorTemperatureMireds)).toString());
|
|
202
|
+
}
|
|
203
|
+
if (parsedUrl.includes('${MIRED}') && isValidNumber(data.request.colorTemperatureMireds)) {
|
|
204
|
+
parsedUrl = parsedUrl.replace('${MIRED}', Math.round(data.request.colorTemperatureMireds).toString());
|
|
205
|
+
}
|
|
206
|
+
if (parsedUrl.includes('${COLORX}') && isValidNumber(data.request.colorX, 0, 65279)) {
|
|
207
|
+
parsedUrl = parsedUrl.replace('${COLORX}', this.roundTo(data.request.colorX / 65536, 4).toString());
|
|
208
|
+
}
|
|
209
|
+
if (parsedUrl.includes('${COLORY}') && isValidNumber(data.request.colorY, 0, 65279)) {
|
|
210
|
+
parsedUrl = parsedUrl.replace('${COLORY}', this.roundTo(data.request.colorY / 65536, 4).toString());
|
|
211
|
+
}
|
|
212
|
+
if (parsedUrl.includes('${HUE}') && isValidNumber(data.request.hue, 0, 254)) {
|
|
213
|
+
parsedUrl = parsedUrl.replace('${HUE}', Math.round((data.request.hue * 360) / 254).toString());
|
|
214
|
+
}
|
|
215
|
+
if (parsedUrl.includes('${SATURATION}') && isValidNumber(data.request.saturation, 0, 254)) {
|
|
216
|
+
parsedUrl = parsedUrl.replace('${SATURATION}', Math.round((data.request.saturation * 100) / 254).toString());
|
|
217
|
+
}
|
|
218
|
+
if ((parsedUrl.includes('${level}') || parsedUrl.includes('${level100}')) && isValidNumber(data.attributes.currentLevel, 1, 254)) {
|
|
219
|
+
await wait(100);
|
|
220
|
+
data.attributes = endpoint.stateOf(MatterbridgeLevelControlServer);
|
|
221
|
+
if (isValidNumber(data.attributes.currentLevel, 1, 254)) {
|
|
222
|
+
if (url.includes('${level}'))
|
|
223
|
+
parsedUrl = parsedUrl.replace('${level}', data.attributes.currentLevel.toString());
|
|
224
|
+
if (url.includes('${level100}'))
|
|
225
|
+
parsedUrl = parsedUrl.replace('${level100}', Math.round((data.attributes.currentLevel / 254) * 100).toString());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if ((parsedUrl.includes('${mired}') || parsedUrl.includes('${kelvin}')) &&
|
|
229
|
+
isValidNumber(data.attributes.colorTemperatureMireds, data.attributes.colorTempPhysicalMinMireds, data.attributes.colorTempPhysicalMaxMireds)) {
|
|
230
|
+
await wait(100);
|
|
231
|
+
data.attributes = endpoint.stateOf(MatterbridgeColorControlServer);
|
|
232
|
+
if (isValidNumber(data.attributes.colorTemperatureMireds)) {
|
|
233
|
+
const kelvin = miredToKelvin(data.attributes.colorTemperatureMireds);
|
|
234
|
+
this.log.debug(`Attribute colorTemperatureMireds is ${data.attributes.colorTemperatureMireds}, which is ${kelvin}K`);
|
|
235
|
+
if (url.includes('${mired}'))
|
|
236
|
+
parsedUrl = parsedUrl.replace('${mired}', data.attributes.colorTemperatureMireds.toString());
|
|
237
|
+
if (url.includes('${kelvin}'))
|
|
238
|
+
parsedUrl = parsedUrl.replace('${kelvin}', kelvin.toString());
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if ((parsedUrl.includes('${hue}') || parsedUrl.includes('${saturation}') || parsedUrl.includes('${red}') || parsedUrl.includes('${green}') || parsedUrl.includes('${blue}')) &&
|
|
242
|
+
isValidNumber(data.attributes.currentHue, 0, 254) &&
|
|
243
|
+
isValidNumber(data.attributes.currentSaturation, 0, 254)) {
|
|
244
|
+
await wait(100);
|
|
245
|
+
data.attributes = endpoint.stateOf(MatterbridgeColorControlServer);
|
|
246
|
+
if (isValidNumber(data.attributes.currentHue, 0, 254) && isValidNumber(data.attributes.currentSaturation, 0, 254)) {
|
|
247
|
+
const rgb = hslColorToRgbColor((data.attributes.currentHue * 360) / 254, (data.attributes.currentSaturation * 100) / 254, 50);
|
|
248
|
+
this.log.debug(`Converted hue ${data.attributes.currentHue} and saturation ${data.attributes.currentSaturation} to RGB r: ${rgb.r} g: ${rgb.g} b: ${rgb.b}`);
|
|
249
|
+
if (url.includes('${hue}'))
|
|
250
|
+
parsedUrl = parsedUrl.replace('${hue}', Math.round((data.attributes.currentHue * 360) / 254).toString());
|
|
251
|
+
if (url.includes('${saturation}'))
|
|
252
|
+
parsedUrl = parsedUrl.replace('${saturation}', Math.round((data.attributes.currentSaturation * 100) / 254).toString());
|
|
253
|
+
if (url.includes('${red}') && rgb)
|
|
254
|
+
parsedUrl = parsedUrl.replace('${red}', rgb.r.toString());
|
|
255
|
+
if (url.includes('${green}') && rgb)
|
|
256
|
+
parsedUrl = parsedUrl.replace('${green}', rgb.g.toString());
|
|
257
|
+
if (url.includes('${blue}') && rgb)
|
|
258
|
+
parsedUrl = parsedUrl.replace('${blue}', rgb.b.toString());
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if ((parsedUrl.includes('${colorX}') || parsedUrl.includes('${colorY}')) &&
|
|
262
|
+
isValidNumber(data.attributes.currentX, 0, 65279) &&
|
|
263
|
+
isValidNumber(data.attributes.currentY, 0, 65279)) {
|
|
264
|
+
await wait(100);
|
|
265
|
+
data.attributes = endpoint.stateOf(MatterbridgeColorControlServer);
|
|
266
|
+
if (isValidNumber(data.attributes.currentX, 0, 65279) && isValidNumber(data.attributes.currentY, 0, 65279)) {
|
|
267
|
+
if (url.includes('${colorX}'))
|
|
268
|
+
parsedUrl = parsedUrl.replace('${colorX}', this.roundTo(data.attributes.currentX / 65536, 4).toString());
|
|
269
|
+
if (url.includes('${colorY}'))
|
|
270
|
+
parsedUrl = parsedUrl.replace('${colorY}', this.roundTo(data.attributes.currentY / 65536, 4).toString());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this.log.debug(`Fetching ${parsedUrl} with ${method}...`);
|
|
274
|
+
fetch(parsedUrl, method)
|
|
275
|
+
.then((response) => {
|
|
276
|
+
this.log.notice(`Webhook ${deviceType} ${deviceName} ${command} successful!`);
|
|
277
|
+
this.log.debug(`Webhook ${deviceType} ${deviceName} ${command} response:${rs}\n`, response);
|
|
278
|
+
return;
|
|
279
|
+
})
|
|
280
|
+
.catch((err) => {
|
|
281
|
+
this.log.error(`Webhook ${deviceType} ${deviceName} ${command} failed: ${err instanceof Error ? err.message : err}`);
|
|
282
|
+
});
|
|
283
|
+
return { method, url: parsedUrl };
|
|
284
|
+
}
|
|
285
|
+
roundTo(value, digits) {
|
|
286
|
+
const factor = Math.pow(10, digits);
|
|
287
|
+
return Math.round(value * factor) / factor;
|
|
108
288
|
}
|
|
109
289
|
}
|
|
@@ -75,6 +75,77 @@
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
+
"outlets": {
|
|
79
|
+
"description": "Define each outlet. Enter in the first field the name of the outlet (replace newKey with the name of the outlet) and in the right panel the urls (i.e. \"[GET|POST]#http://mydomain.com/api/device/on\").",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"uniqueItems": true,
|
|
82
|
+
"additionalProperties": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "Outlet parameters:",
|
|
85
|
+
"required": ["onUrl", "offUrl"],
|
|
86
|
+
"properties": {
|
|
87
|
+
"onUrl": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"title": "Outlet On URL",
|
|
90
|
+
"description": "URL to turn on the outlet."
|
|
91
|
+
},
|
|
92
|
+
"offUrl": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"title": "Outlet Off URL",
|
|
95
|
+
"description": "URL to turn off the outlet."
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"lights": {
|
|
101
|
+
"description": "Define each light. Enter in the first field the name of the light (replace newKey with the name of the light) and in the right panel the urls (i.e. [GET|POST]#http://mydomain.com/api/device/on).",
|
|
102
|
+
"type": "object",
|
|
103
|
+
"uniqueItems": true,
|
|
104
|
+
"additionalProperties": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"description": "Light parameters:",
|
|
107
|
+
"required": ["onUrl", "offUrl"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"minMireds": {
|
|
110
|
+
"type": "integer",
|
|
111
|
+
"title": "Minimum Mireds",
|
|
112
|
+
"default": 154,
|
|
113
|
+
"description": "Minimum color temperature in Mireds (Kelvin = 1000000 / Mireds). Default is 154 (6500K)."
|
|
114
|
+
},
|
|
115
|
+
"maxMireds": {
|
|
116
|
+
"type": "integer",
|
|
117
|
+
"title": "Maximum Mireds",
|
|
118
|
+
"default": 500,
|
|
119
|
+
"description": "Maximum color temperature in Mireds (Kelvin = 1000000 / Mireds). Default is 500 (2000K)."
|
|
120
|
+
},
|
|
121
|
+
"onUrl": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"title": "Light On URL",
|
|
124
|
+
"description": "URL to turn on the light."
|
|
125
|
+
},
|
|
126
|
+
"offUrl": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"title": "Light Off URL",
|
|
129
|
+
"description": "URL to turn off the light."
|
|
130
|
+
},
|
|
131
|
+
"brightnessUrl": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"title": "Light Brightness URL",
|
|
134
|
+
"description": "URL to adjust the brightness of the light. (i.e. [GET|POST]:http://mydomain.com/api/device?brightness=${BRIGHTNESS})"
|
|
135
|
+
},
|
|
136
|
+
"colorTempUrl": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"title": "Light Color Temperature URL",
|
|
139
|
+
"description": "URL to adjust the color temperature of the light. (i.e. [GET|POST]:http://mydomain.com/api/device?colorTemp=${COLORTEMP})"
|
|
140
|
+
},
|
|
141
|
+
"rgbUrl": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"title": "Light RGB URL",
|
|
144
|
+
"description": "URL to adjust the RGB color of the light. (i.e. [GET|POST]:http://mydomain.com/api/device?rgb=${RGB})"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
78
149
|
"debug": {
|
|
79
150
|
"description": "Enable the debug for the plugin.",
|
|
80
151
|
"type": "boolean",
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-webhooks",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-edge.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge-webhooks",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.1.0-edge.3",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"node-ansi-logger": "3.1.1",
|