matterbridge-homey 0.1.0 → 0.2.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/CHANGELOG.md +16 -0
- package/README.md +31 -4
- package/dist/features.js +151 -4
- package/dist/module.js +17 -14
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.0] - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- [devices]: Robotic vacuum cleaners with the standard Homey capabilities: start, stop, pause, resume and go home, the state, the clean modes and the battery.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- [docs]: The README explains the default color temperature range of lights and installing the plugin from npm.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- [devices]: The Off mode of a thermostat or air conditioner turns `onoff` off instead of setting the `off` mode, which some drivers ignore.
|
|
20
|
+
- [platform]: A device that fails to build is skipped with an error instead of stopping the plugin from starting.
|
|
21
|
+
- [build]: Test builds of `npm run pack:tarball` are marked private, so Matterbridge reinstalls them from the uploaded tarball when the Docker container is recreated, instead of failing to find a `dev` version on npm.
|
|
22
|
+
|
|
7
23
|
## [0.1.0] - 2026-10-13
|
|
8
24
|
|
|
9
25
|
### Added
|
package/README.md
CHANGED
|
@@ -52,6 +52,9 @@ The Matter device type follows the Homey device class (or the class you chose fo
|
|
|
52
52
|
| `windowcoverings`, `blinds`, `curtain`, `sunshade`, `shutterblinds` | `windowcoverings_set`, `windowcoverings_state` | Window covering (lift) |
|
|
53
53
|
| `lock` | `locked` | Door lock |
|
|
54
54
|
| `fan`, `airpurifier` | `onoff`, `fan_speed` | Fan |
|
|
55
|
+
| `vacuumcleaner` | `vacuumcleaner_state` or `onoff`, `operational_state`, `vacuumcleaner_job_mode` | Robotic vacuum cleaner |
|
|
56
|
+
|
|
57
|
+
The Off mode of a thermostat or air conditioner turns `onoff` off, and the other modes turn it back on. Only devices without `onoff` get the `off` value of `thermostat_mode`, because some drivers don't switch the device off with it.
|
|
55
58
|
|
|
56
59
|
Sensors are added to any device that has them:
|
|
57
60
|
|
|
@@ -67,6 +70,26 @@ Sensors are added to any device that has them:
|
|
|
67
70
|
|
|
68
71
|
Sub-capabilities of sensors, e.g. `measure_temperature.inside`, are exposed too. The outdoor temperature of a thermostat or air conditioner (e.g. `measure_temperature.outdoorTemperature`) is part of the thermostat.
|
|
69
72
|
|
|
73
|
+
### Color temperature of lights
|
|
74
|
+
|
|
75
|
+
Homey doesn't give the color temperature of a light in Kelvin or mireds. Its `light_temperature` goes from 0 (the coldest white of the light) to 1 (the warmest white), and Homey doesn't tell which Kelvin range that is for the light.
|
|
76
|
+
|
|
77
|
+
The plugin therefore maps it onto a default range of 6500 K (153 mireds) to 2000 K (500 mireds) for every light. The coldest and warmest settings of the controller are the coldest and warmest white of the light, but the Kelvin values in between are approximate: a light that only goes from 2700 K to 6500 K looks colder than the controller shows.
|
|
78
|
+
|
|
79
|
+
### Robotic vacuum cleaners
|
|
80
|
+
|
|
81
|
+
Vacuums are supported with the standard Homey capabilities and values only:
|
|
82
|
+
|
|
83
|
+
- Start and stop (the Matter run modes Idle and Cleaning) set `vacuumcleaner_state` to `cleaning` or `stopped`, or turn `onoff` on or off.
|
|
84
|
+
- Go home sets `vacuumcleaner_state` to `docked`. Homey has no standard pause: pause stops the vacuum, resume starts it again.
|
|
85
|
+
- The state comes from `vacuumcleaner_state` (cleaning, docked, charging, stopped) and the standard values of `operational_state` (paused, error).
|
|
86
|
+
- The standard values of `vacuumcleaner_job_mode` (normal, high, turbo, mop, auto) are the Matter clean modes. Matter only changes them while the vacuum is idle.
|
|
87
|
+
- No rooms or maps: Homey has no standard capability for them.
|
|
88
|
+
|
|
89
|
+
Vacuums without `vacuumcleaner_state` or `onoff` aren't exposed. This includes Matter vacuums paired with Homey: Homey's Matter driver uses the Matter state and mode numbers instead of the standard values. Add such a vacuum to your Matter controller directly instead.
|
|
90
|
+
|
|
91
|
+
Apple Home doesn't support a vacuum in a bridge, and the whole bridge becomes unstable. If you use Apple Home, add your vacuums to the black list.
|
|
92
|
+
|
|
70
93
|
### How devices are composed
|
|
71
94
|
|
|
72
95
|
Each Homey device is one Matter bridged device. Its main function and as many sensors as possible share one endpoint, so controllers show one device instead of one per function (Alexa shows every endpoint as a separate device). A function that can't share the endpoint gets a child endpoint: a second sensor of the same kind (e.g. `alarm_motion` and `alarm_presence`) or a sub-capability (e.g. `onoff.output2`).
|
|
@@ -92,11 +115,15 @@ The white and black lists accept Homey device IDs as well as names. Entries by n
|
|
|
92
115
|
|
|
93
116
|
- New Homey devices need a Matterbridge restart; the frontend asks for it. Deleted devices are removed right away.
|
|
94
117
|
- Devices with the same name get their zone appended, e.g. `Ceiling light Kitchen`.
|
|
95
|
-
- Not supported yet: buttons and remotes, media players and TVs (only their `onoff`),
|
|
118
|
+
- Not supported yet: buttons and remotes, media players and TVs (only their `onoff`), window covering tilt, plant moisture, `level_aqi`.
|
|
96
119
|
|
|
97
120
|
## Install
|
|
98
121
|
|
|
99
|
-
|
|
122
|
+
On the Home page of the Matterbridge frontend, enter `matterbridge-homey` in the **Install plugins** panel and click **Install**. Then set the address and API key in the plugin configuration and restart.
|
|
123
|
+
|
|
124
|
+
### Test builds
|
|
125
|
+
|
|
126
|
+
To test changes before they are published, build a tarball and upload it to Matterbridge:
|
|
100
127
|
|
|
101
128
|
```bash
|
|
102
129
|
npm install
|
|
@@ -104,9 +131,9 @@ npm link matterbridge
|
|
|
104
131
|
npm run pack:tarball
|
|
105
132
|
```
|
|
106
133
|
|
|
107
|
-
This creates `matterbridge-homey-<version>-dev-<date>-<time>.tgz` in the project folder (`npm run pack:tarball -- --release` keeps the plain version). On the Home page of the Matterbridge frontend, click **Upload** in the **Install plugins** panel and choose the tarball (the panel is hidden when Matterbridge runs read-only). Matterbridge installs it with `npm install -g <tarball> --omit=dev`, so the host needs access to the npm registry for `homey-api`.
|
|
134
|
+
This creates `matterbridge-homey-<version>-dev-<date>-<time>.tgz` in the project folder (`npm run pack:tarball -- --release` keeps the plain version). On the Home page of the Matterbridge frontend, click **Upload** in the **Install plugins** panel and choose the tarball (the panel is hidden when Matterbridge runs read-only). Matterbridge installs it with `npm install -g <tarball> --omit=dev`, so the host needs access to the npm registry for `homey-api`.
|
|
108
135
|
|
|
109
|
-
Uploading a newer tarball replaces the installed version.
|
|
136
|
+
Uploading a newer tarball replaces the installed version. Test builds are marked private in their `package.json`: when the plugin is missing at start, e.g. after Docker recreated the Matterbridge container for an image update, Matterbridge reinstalls a private plugin from the last uploaded tarball instead of from npm. Release builds are reinstalled from npm.
|
|
110
137
|
|
|
111
138
|
## Development
|
|
112
139
|
|
package/dist/features.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { airQualitySensor, colorTemperatureLight, contactSensor, dimmableLight, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, humiditySensor, lightSensor, occupancySensor, onOffLight, onOffPlugInUnit, pressureSensor, rainSensor, smokeCoAlarm, temperatureSensor, thermostat, waterLeakDetector, windowCovering, } from 'matterbridge';
|
|
2
|
-
import {
|
|
1
|
+
import { airQualitySensor, colorTemperatureLight, contactSensor, dimmableLight, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, humiditySensor, lightSensor, occupancySensor, onOffLight, onOffPlugInUnit, pressureSensor, rainSensor, roboticVacuumCleaner, smokeCoAlarm, temperatureSensor, thermostat, waterLeakDetector, windowCovering, } from 'matterbridge';
|
|
2
|
+
import { MatterbridgeRvcCleanModeServer, MatterbridgeRvcOperationalStateServer, MatterbridgeRvcRunModeServer } from 'matterbridge/devices';
|
|
3
|
+
import { AirQuality, BooleanState, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ColorControl, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, FanControl, IlluminanceMeasurement, LevelControl, OccupancySensing, OnOff, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PressureMeasurement, RelativeHumidityMeasurement, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
|
|
3
4
|
import { kelvinToRGB, miredToKelvin, rgbColorToHslColor, xyToHsl } from 'matterbridge/utils';
|
|
4
5
|
import { colorTemperatureMaxMireds, colorTemperatureMinMireds, dimToLevel, levelToDim, lightTemperatureToMireds, liftToWindowCoveringsSet, luxToMatter, matterColorToUnit, miredsToLightTemperature, setpointToTargetTemperature, toHundredths, toThousandths, unitToMatterColor, windowCoveringsSetToLift, } from './converters.js';
|
|
5
6
|
const lightClasses = new Set(['light']);
|
|
@@ -8,6 +9,7 @@ const coolingClasses = new Set(['airconditioning']);
|
|
|
8
9
|
const coveringClasses = new Set(['windowcoverings', 'blinds', 'curtain', 'sunshade', 'shutterblinds']);
|
|
9
10
|
const fanClasses = new Set(['fan', 'airpurifier']);
|
|
10
11
|
const lockClasses = new Set(['lock']);
|
|
12
|
+
const vacuumClasses = new Set(['vacuumcleaner']);
|
|
11
13
|
const outputClasses = new Set(['socket', 'other']);
|
|
12
14
|
export function hasCapability(device, capabilityId) {
|
|
13
15
|
return device.capabilities.includes(capabilityId);
|
|
@@ -44,6 +46,8 @@ export function planFeatures(device) {
|
|
|
44
46
|
features.push(doorLockFeature());
|
|
45
47
|
else if (fanClasses.has(deviceClass) && hasOnOff)
|
|
46
48
|
features.push(fanFeature(device));
|
|
49
|
+
else if (vacuumClasses.has(deviceClass) && (hasOnOff || hasCapability(device, 'vacuumcleaner_state')))
|
|
50
|
+
features.push(vacuumFeature(device));
|
|
47
51
|
else if (hasOnOff && deviceClass !== 'sensor')
|
|
48
52
|
features.push(plugFeature(device, 'onoff'));
|
|
49
53
|
if (!climate)
|
|
@@ -61,7 +65,7 @@ export function planFeatures(device) {
|
|
|
61
65
|
addSensor(features, device, 'measure_luminance', illuminanceFeature);
|
|
62
66
|
if (['measure_co2', 'measure_co', 'measure_pm25', 'measure_pm10', 'measure_tvoc'].some((id) => hasCapability(device, id)))
|
|
63
67
|
features.push(airQualityFeature(device));
|
|
64
|
-
let hasMainFunction = features.length > 0 && features[0].clusters.some((cluster) => ['OnOff', 'Thermostat', 'WindowCovering', 'DoorLock', 'FanControl'].includes(cluster));
|
|
68
|
+
let hasMainFunction = features.length > 0 && features[0].clusters.some((cluster) => ['OnOff', 'Thermostat', 'WindowCovering', 'DoorLock', 'FanControl', 'RvcRunMode'].includes(cluster));
|
|
65
69
|
for (const capabilityId of device.capabilities) {
|
|
66
70
|
const [base, sub] = capabilityId.split('.');
|
|
67
71
|
if (sub === undefined)
|
|
@@ -319,7 +323,7 @@ function climateFeature(device, isCooling) {
|
|
|
319
323
|
if (!isFromController(actionContext))
|
|
320
324
|
return;
|
|
321
325
|
const modeId = Object.keys(systemModes).find((id) => systemModes[id] === value);
|
|
322
|
-
if (value === Thermostat.SystemMode.Off &&
|
|
326
|
+
if (value === Thermostat.SystemMode.Off && hasOnOff)
|
|
323
327
|
context.set('onoff', false);
|
|
324
328
|
else if (modeId !== undefined && modeIds.has(modeId))
|
|
325
329
|
context.set('thermostat_mode', modeId);
|
|
@@ -469,6 +473,149 @@ function doorLockFeature() {
|
|
|
469
473
|
},
|
|
470
474
|
};
|
|
471
475
|
}
|
|
476
|
+
const vacuumIdleMode = 1;
|
|
477
|
+
const vacuumCleaningMode = 2;
|
|
478
|
+
const vacuumCleanModes = {
|
|
479
|
+
normal: { label: 'Normal', mode: 1, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }] },
|
|
480
|
+
high: { label: 'High', mode: 2, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.DeepClean }] },
|
|
481
|
+
turbo: { label: 'Turbo', mode: 3, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Max }] },
|
|
482
|
+
mop: { label: 'Mop', mode: 4, modeTags: [{ value: RvcCleanMode.ModeTag.Mop }] },
|
|
483
|
+
auto: { label: 'Auto', mode: 5, modeTags: [{ value: RvcCleanMode.ModeTag.Auto }, { value: RvcCleanMode.ModeTag.Vacuum }] },
|
|
484
|
+
};
|
|
485
|
+
const vacuumStates = {
|
|
486
|
+
cleaning: RvcOperationalState.OperationalState.Running,
|
|
487
|
+
spot_cleaning: RvcOperationalState.OperationalState.Running,
|
|
488
|
+
docked: RvcOperationalState.OperationalState.Docked,
|
|
489
|
+
charging: RvcOperationalState.OperationalState.Charging,
|
|
490
|
+
stopped: RvcOperationalState.OperationalState.Stopped,
|
|
491
|
+
};
|
|
492
|
+
const vacuumOperationalStates = {
|
|
493
|
+
stopped: RvcOperationalState.OperationalState.Stopped,
|
|
494
|
+
running: RvcOperationalState.OperationalState.Running,
|
|
495
|
+
paused: RvcOperationalState.OperationalState.Paused,
|
|
496
|
+
error: RvcOperationalState.OperationalState.Error,
|
|
497
|
+
};
|
|
498
|
+
export function getVacuumCleanModes(device) {
|
|
499
|
+
if (!hasCapability(device, 'vacuumcleaner_job_mode'))
|
|
500
|
+
return [];
|
|
501
|
+
const values = getCapability(device, 'vacuumcleaner_job_mode')?.values?.map((value) => value.id) ?? Object.keys(vacuumCleanModes);
|
|
502
|
+
return values.filter((value) => Object.hasOwn(vacuumCleanModes, value));
|
|
503
|
+
}
|
|
504
|
+
function mapStandardValue(mapping, value) {
|
|
505
|
+
return typeof value === 'string' && Object.hasOwn(mapping, value) ? mapping[value] : undefined;
|
|
506
|
+
}
|
|
507
|
+
function vacuumFeature(device) {
|
|
508
|
+
const hasState = hasCapability(device, 'vacuumcleaner_state');
|
|
509
|
+
const cleanModeIds = getVacuumCleanModes(device);
|
|
510
|
+
const { OperationalState } = RvcOperationalState;
|
|
511
|
+
return {
|
|
512
|
+
name: 'vacuum',
|
|
513
|
+
capabilities: ['vacuumcleaner_state', 'onoff', 'operational_state', 'vacuumcleaner_job_mode'],
|
|
514
|
+
deviceTypes: [roboticVacuumCleaner],
|
|
515
|
+
clusters: ['RvcRunMode', 'RvcCleanMode', 'RvcOperationalState'],
|
|
516
|
+
build: (context) => {
|
|
517
|
+
const { endpoint } = context;
|
|
518
|
+
const operationalState = () => {
|
|
519
|
+
const operational = mapStandardValue(vacuumOperationalStates, context.value('operational_state'));
|
|
520
|
+
if (operational === OperationalState.Paused || operational === OperationalState.Error)
|
|
521
|
+
return operational;
|
|
522
|
+
return (mapStandardValue(vacuumStates, context.value('vacuumcleaner_state')) ??
|
|
523
|
+
operational ??
|
|
524
|
+
(context.value('onoff') === true ? OperationalState.Running : OperationalState.Stopped));
|
|
525
|
+
};
|
|
526
|
+
const operationalError = (state) => state === OperationalState.Error
|
|
527
|
+
? { errorStateId: RvcOperationalState.ErrorState.UnableToCompleteOperation }
|
|
528
|
+
: { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateDetails: 'Fully operational' };
|
|
529
|
+
const runMode = (state) => state === OperationalState.Running || state === OperationalState.Paused ? vacuumCleaningMode : vacuumIdleMode;
|
|
530
|
+
const cleanMode = () => {
|
|
531
|
+
const jobMode = context.value('vacuumcleaner_job_mode');
|
|
532
|
+
return vacuumCleanModes[typeof jobMode === 'string' && cleanModeIds.includes(jobMode) ? jobMode : cleanModeIds[0]].mode;
|
|
533
|
+
};
|
|
534
|
+
const state = operationalState();
|
|
535
|
+
endpoint.behaviors.require(MatterbridgeRvcRunModeServer, {
|
|
536
|
+
supportedModes: [
|
|
537
|
+
{ label: 'Idle', mode: vacuumIdleMode, modeTags: [{ value: RvcRunMode.ModeTag.Idle }] },
|
|
538
|
+
{ label: 'Cleaning', mode: vacuumCleaningMode, modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }] },
|
|
539
|
+
],
|
|
540
|
+
currentMode: runMode(state),
|
|
541
|
+
});
|
|
542
|
+
if (cleanModeIds.length > 0) {
|
|
543
|
+
endpoint.behaviors.require(MatterbridgeRvcCleanModeServer, { supportedModes: cleanModeIds.map((id) => vacuumCleanModes[id]), currentMode: cleanMode() });
|
|
544
|
+
}
|
|
545
|
+
endpoint.behaviors.require(MatterbridgeRvcOperationalStateServer, {
|
|
546
|
+
phaseList: null,
|
|
547
|
+
currentPhase: null,
|
|
548
|
+
countdownTime: null,
|
|
549
|
+
operationalStateList: [
|
|
550
|
+
OperationalState.Stopped,
|
|
551
|
+
OperationalState.Running,
|
|
552
|
+
OperationalState.Paused,
|
|
553
|
+
OperationalState.Error,
|
|
554
|
+
OperationalState.SeekingCharger,
|
|
555
|
+
OperationalState.Charging,
|
|
556
|
+
OperationalState.Docked,
|
|
557
|
+
].map((operationalStateId) => ({ operationalStateId })),
|
|
558
|
+
operationalState: state,
|
|
559
|
+
operationalError: operationalError(state),
|
|
560
|
+
});
|
|
561
|
+
const start = () => {
|
|
562
|
+
if (hasState)
|
|
563
|
+
context.set('vacuumcleaner_state', 'cleaning', true);
|
|
564
|
+
else
|
|
565
|
+
context.set('onoff', true, true);
|
|
566
|
+
};
|
|
567
|
+
const stop = (vacuumState, reflect) => {
|
|
568
|
+
if (hasState)
|
|
569
|
+
context.set('vacuumcleaner_state', vacuumState, reflect);
|
|
570
|
+
else
|
|
571
|
+
context.set('onoff', false, reflect);
|
|
572
|
+
};
|
|
573
|
+
endpoint.addCommandHandler('RvcRunMode.changeToMode', ({ request, attributes }) => {
|
|
574
|
+
if (request.newMode === attributes.currentMode)
|
|
575
|
+
return;
|
|
576
|
+
if (request.newMode === vacuumCleaningMode)
|
|
577
|
+
start();
|
|
578
|
+
else if (request.newMode === vacuumIdleMode)
|
|
579
|
+
stop('stopped', true);
|
|
580
|
+
});
|
|
581
|
+
endpoint.addCommandHandler('RvcOperationalState.pause', ({ attributes }) => {
|
|
582
|
+
if (attributes.operationalState === OperationalState.Running || attributes.operationalState === OperationalState.SeekingCharger)
|
|
583
|
+
stop('stopped', false);
|
|
584
|
+
});
|
|
585
|
+
endpoint.addCommandHandler('RvcOperationalState.resume', ({ attributes }) => {
|
|
586
|
+
if (attributes.operationalState === OperationalState.Paused)
|
|
587
|
+
start();
|
|
588
|
+
});
|
|
589
|
+
endpoint.addCommandHandler('RvcOperationalState.goHome', ({ attributes }) => {
|
|
590
|
+
if (attributes.operationalState === OperationalState.Stopped ||
|
|
591
|
+
attributes.operationalState === OperationalState.Running ||
|
|
592
|
+
attributes.operationalState === OperationalState.Paused)
|
|
593
|
+
stop('docked', true);
|
|
594
|
+
});
|
|
595
|
+
if (cleanModeIds.length > 0) {
|
|
596
|
+
endpoint.addCommandHandler('RvcCleanMode.changeToMode', ({ request, attributes }) => {
|
|
597
|
+
const modeId = cleanModeIds.find((id) => vacuumCleanModes[id].mode === request.newMode);
|
|
598
|
+
if (modeId === undefined || request.newMode === attributes.currentMode || endpoint.getAttribute(RvcRunMode, 'currentMode') !== vacuumIdleMode)
|
|
599
|
+
return;
|
|
600
|
+
context.set('vacuumcleaner_job_mode', modeId, true);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
const update = async () => {
|
|
604
|
+
const newState = operationalState();
|
|
605
|
+
await endpoint.updateAttribute(RvcOperationalState, 'operationalState', newState, endpoint.log);
|
|
606
|
+
await endpoint.updateAttribute(RvcOperationalState, 'operationalError', operationalError(newState), endpoint.log);
|
|
607
|
+
await endpoint.updateAttribute(RvcRunMode, 'currentMode', runMode(newState), endpoint.log);
|
|
608
|
+
};
|
|
609
|
+
for (const capabilityId of ['vacuumcleaner_state', 'operational_state', 'onoff'])
|
|
610
|
+
context.bind(capabilityId, update);
|
|
611
|
+
if (cleanModeIds.length > 0) {
|
|
612
|
+
context.bind('vacuumcleaner_job_mode', async () => {
|
|
613
|
+
await endpoint.updateAttribute(RvcCleanMode, 'currentMode', cleanMode(), endpoint.log);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
472
619
|
function temperatureFeature(capabilityId) {
|
|
473
620
|
return {
|
|
474
621
|
name: capabilityId,
|
package/dist/module.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MatterbridgeDynamicPlatform } from 'matterbridge';
|
|
2
|
-
import { CYAN, db, idn, nf, rs } from 'matterbridge/logger';
|
|
1
|
+
import { MatterbridgeDynamicPlatform, roboticVacuumCleaner } from 'matterbridge';
|
|
2
|
+
import { CYAN, db, idn, nf, rs, wr } from 'matterbridge/logger';
|
|
3
3
|
import { fireAndForget, getErrorMessage, inspectError, isValidArray, isValidObject, isValidString } from 'matterbridge/utils';
|
|
4
4
|
import { getExposedCapabilities } from './features.js';
|
|
5
5
|
import { HomeyApiConnection } from './homey.js';
|
|
@@ -179,22 +179,25 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
179
179
|
if (!this.validateDevice(keys))
|
|
180
180
|
return;
|
|
181
181
|
const excludedCapabilities = new Set([...this.config.capabilityBlackList, ...keys.flatMap((key) => this.config.deviceCapabilityBlackList[key] ?? [])]);
|
|
182
|
-
const bridgedDevice = HomeyBridgedDevice.create({
|
|
183
|
-
device: customizeDevice(remapped, undefined, excludedCapabilities),
|
|
184
|
-
name,
|
|
185
|
-
connection: this.homey,
|
|
186
|
-
vendorId: this.matterbridge.aggregatorVendorId,
|
|
187
|
-
logLevel: this.log.logLevel,
|
|
188
|
-
configUrl,
|
|
189
|
-
});
|
|
190
|
-
if (!bridgedDevice) {
|
|
191
|
-
this.log.info(`Device ${CYAN}${name}${nf} has no capabilities left after the capability black lists. Skipping...`);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
182
|
try {
|
|
183
|
+
const bridgedDevice = HomeyBridgedDevice.create({
|
|
184
|
+
device: customizeDevice(remapped, undefined, excludedCapabilities),
|
|
185
|
+
name,
|
|
186
|
+
connection: this.homey,
|
|
187
|
+
vendorId: this.matterbridge.aggregatorVendorId,
|
|
188
|
+
logLevel: this.log.logLevel,
|
|
189
|
+
configUrl,
|
|
190
|
+
});
|
|
191
|
+
if (!bridgedDevice) {
|
|
192
|
+
this.log.info(`Device ${CYAN}${name}${nf} has no capabilities left after the capability black lists. Skipping...`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
195
|
this.log.info(`Registering device ${idn}${name}${rs}${nf} class ${CYAN}${remapped.virtualClass ?? remapped.class}${nf}...`);
|
|
196
196
|
await this.registerDevice(bridgedDevice.endpoint);
|
|
197
197
|
this.bridgedDevices.set(device.id, bridgedDevice);
|
|
198
|
+
if (bridgedDevice.endpoint.deviceTypes.has(roboticVacuumCleaner.code)) {
|
|
199
|
+
this.log.warn(`Device ${CYAN}${name}${wr} is a robotic vacuum cleaner: Apple Home doesn't support it in a bridge. If you use Apple Home, add it to the black list.`);
|
|
200
|
+
}
|
|
198
201
|
}
|
|
199
202
|
catch (error) {
|
|
200
203
|
inspectError(this.log, `Failed to register device ${name}`, error);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-homey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge-homey",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.2.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"homey-api": "3.20.0"
|