homebridge-openrgb-multi 6.4.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/.gitlab-ci.yml +24 -0
- package/README.md +1 -0
- package/dist/Device.d.ts +21 -0
- package/dist/Device.js +139 -0
- package/dist/Device.js.map +1 -0
- package/dist/Zone.d.ts +10 -0
- package/dist/Zone.js +64 -0
- package/dist/Zone.js.map +1 -0
- package/dist/accessories/ZoneBrightnessAccessory.d.ts +9 -0
- package/dist/accessories/ZoneBrightnessAccessory.js +35 -0
- package/dist/accessories/ZoneBrightnessAccessory.js.map +1 -0
- package/dist/accessories/ZoneEffectAccessory.d.ts +11 -0
- package/dist/accessories/ZoneEffectAccessory.js +54 -0
- package/dist/accessories/ZoneEffectAccessory.js.map +1 -0
- package/dist/accessories/ZoneSceneTransitionAccessory.d.ts +9 -0
- package/dist/accessories/ZoneSceneTransitionAccessory.js +37 -0
- package/dist/accessories/ZoneSceneTransitionAccessory.js.map +1 -0
- package/dist/accessories/index.d.ts +3 -0
- package/dist/accessories/index.js +20 -0
- package/dist/accessories/index.js.map +1 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/platformFactory.d.ts +11 -0
- package/dist/platformFactory.js +87 -0
- package/dist/platformFactory.js.map +1 -0
- package/dist/services/PersistenceService.d.ts +13 -0
- package/dist/services/PersistenceService.js +83 -0
- package/dist/services/PersistenceService.js.map +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +38 -0
- package/dist/test.js.map +1 -0
- package/dist/types/Context.d.ts +14 -0
- package/dist/types/Context.js +3 -0
- package/dist/types/Context.js.map +1 -0
- package/dist/types/configuration/ConfigurationVector2.d.ts +4 -0
- package/dist/types/configuration/ConfigurationVector2.js +3 -0
- package/dist/types/configuration/ConfigurationVector2.js.map +1 -0
- package/dist/types/configuration/DeviceConfiguration.d.ts +8 -0
- package/dist/types/configuration/DeviceConfiguration.js +3 -0
- package/dist/types/configuration/DeviceConfiguration.js.map +1 -0
- package/dist/types/configuration/PlatformConfiguration.d.ts +10 -0
- package/dist/types/configuration/PlatformConfiguration.js +3 -0
- package/dist/types/configuration/PlatformConfiguration.js.map +1 -0
- package/dist/types/configuration/SegmentConfiguration.d.ts +7 -0
- package/dist/types/configuration/SegmentConfiguration.js +3 -0
- package/dist/types/configuration/SegmentConfiguration.js.map +1 -0
- package/dist/types/configuration/ZoneConfiguration.d.ts +6 -0
- package/dist/types/configuration/ZoneConfiguration.js +3 -0
- package/dist/types/configuration/ZoneConfiguration.js.map +1 -0
- package/dist/types/configuration/index.d.ts +5 -0
- package/dist/types/configuration/index.js +22 -0
- package/dist/types/configuration/index.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/persistence/PlatformPersistence.d.ts +6 -0
- package/dist/types/persistence/PlatformPersistence.js +3 -0
- package/dist/types/persistence/PlatformPersistence.js.map +1 -0
- package/dist/types/persistence/ZonePersistence.d.ts +5 -0
- package/dist/types/persistence/ZonePersistence.js +3 -0
- package/dist/types/persistence/ZonePersistence.js.map +1 -0
- package/dist/types/persistence/index.d.ts +2 -0
- package/dist/types/persistence/index.js +19 -0
- package/dist/types/persistence/index.js.map +1 -0
- package/package.json +53 -0
- package/src/Device.ts +175 -0
- package/src/Zone.ts +82 -0
- package/src/accessories/ZoneBrightnessAccessory.ts +27 -0
- package/src/accessories/ZoneEffectAccessory.ts +44 -0
- package/src/accessories/ZoneSceneTransitionAccessory.ts +29 -0
- package/src/accessories/index.ts +3 -0
- package/src/constants.ts +11 -0
- package/src/index.ts +16 -0
- package/src/platformFactory.ts +116 -0
- package/src/services/PersistenceService.ts +74 -0
- package/src/services/index.ts +1 -0
- package/src/test.ts +44 -0
- package/src/types/Context.ts +15 -0
- package/src/types/configuration/ConfigurationVector2.ts +4 -0
- package/src/types/configuration/DeviceConfiguration.ts +9 -0
- package/src/types/configuration/PlatformConfiguration.ts +11 -0
- package/src/types/configuration/SegmentConfiguration.ts +8 -0
- package/src/types/configuration/ZoneConfiguration.ts +7 -0
- package/src/types/configuration/index.ts +5 -0
- package/src/types/index.ts +3 -0
- package/src/types/persistence/PlatformPersistence.ts +7 -0
- package/src/types/persistence/ZonePersistence.ts +5 -0
- package/src/types/persistence/index.ts +2 -0
- package/tsconfig.json +20 -0
package/src/Device.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import ReadWriteLock from 'rwlock';
|
|
2
|
+
import { Vector2 } from 'three';
|
|
3
|
+
import { utils as openRgbUtils } from 'openrgb-sdk';
|
|
4
|
+
import OpenRGBDevice from 'openrgb-sdk/types/device';
|
|
5
|
+
import {
|
|
6
|
+
colorToString,
|
|
7
|
+
ColorHSV,
|
|
8
|
+
ColorRGB,
|
|
9
|
+
DEFAULT_COLOR,
|
|
10
|
+
convertColorSpace,
|
|
11
|
+
ColorSpace,
|
|
12
|
+
} from '@manganese/palette-kit-core';
|
|
13
|
+
import { Controller, ApplyParameters } from '@manganese/effect-kit';
|
|
14
|
+
import { Context, DeviceConfiguration } from './types';
|
|
15
|
+
import { logBoolean } from 'helpers-for-homebridge';
|
|
16
|
+
|
|
17
|
+
export class Device implements Controller {
|
|
18
|
+
private openRgbDeviceId: number = -1;
|
|
19
|
+
private lock = new ReadWriteLock();
|
|
20
|
+
private colors: ColorHSV[];
|
|
21
|
+
private size: number;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly configuration: DeviceConfiguration,
|
|
25
|
+
private readonly context: Context
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
public get name() {
|
|
29
|
+
return this.configuration.name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private get position(): Vector2 {
|
|
33
|
+
return new Vector2(
|
|
34
|
+
this.configuration.position.x,
|
|
35
|
+
this.configuration.position.y
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private get virtual() {
|
|
40
|
+
return this.configuration.virtual ?? false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async initialize() {
|
|
44
|
+
if (this.virtual) {
|
|
45
|
+
this.context.log.info(
|
|
46
|
+
`Initialized virtual device <${this.configuration.name}>`
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
const openRgbDevices =
|
|
50
|
+
await (this.context.openRgbClient.getAllControllerData() as unknown as Promise<
|
|
51
|
+
OpenRGBDevice[]
|
|
52
|
+
>);
|
|
53
|
+
const openRgbDevice = openRgbDevices.find(
|
|
54
|
+
(openRgb) =>
|
|
55
|
+
(openRgb as any).serial.replace(/\s+$/g, '') ===
|
|
56
|
+
this.configuration.serial
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!openRgbDevice) {
|
|
60
|
+
this.context.log.warn(
|
|
61
|
+
`Could not resolve device <${this.name}> to OpenRGB controller`
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
this.openRgbDeviceId = openRgbDevice.deviceId;
|
|
65
|
+
this.size = openRgbDevice.leds.length;
|
|
66
|
+
this.colors = new Array(this.size).fill(DEFAULT_COLOR);
|
|
67
|
+
this.context.log.info(
|
|
68
|
+
`Resolved device <${this.name}> to OpenRGB controller ID [${this.openRgbDeviceId}] with size ${this.size}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async withWriteLock(callback: () => Promise<void>) {
|
|
75
|
+
if (!this.virtual && this.openRgbDeviceId < 0) {
|
|
76
|
+
this.context.log.warn(
|
|
77
|
+
`Ignoring write lock request because device <${this.name}> was not initialized`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return this.lock.writeLock(async (release) => {
|
|
84
|
+
try {
|
|
85
|
+
await callback();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.context.log.error('Error while holding write lock', error);
|
|
88
|
+
} finally {
|
|
89
|
+
release();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public getSize() {
|
|
95
|
+
return this.size;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public getIndexPosition(index: number) {
|
|
99
|
+
const { segments } = this.configuration;
|
|
100
|
+
|
|
101
|
+
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
|
102
|
+
const segment = segments[segmentIndex];
|
|
103
|
+
const segmentLength = segment.toIndex - segment.fromIndex;
|
|
104
|
+
|
|
105
|
+
if (index < segmentLength) {
|
|
106
|
+
const ratio = index / segmentLength;
|
|
107
|
+
|
|
108
|
+
return new Vector2(
|
|
109
|
+
this.position.x +
|
|
110
|
+
segment.fromPosition.x +
|
|
111
|
+
(segment.toPosition.x - segment.fromPosition.x) * ratio,
|
|
112
|
+
this.position.y +
|
|
113
|
+
segment.fromPosition.y +
|
|
114
|
+
(segment.toPosition.y - segment.fromPosition.y) * ratio
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
index -= segmentLength;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return this.position;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async setActive(active: boolean) {
|
|
125
|
+
await this.withWriteLock(async () => {
|
|
126
|
+
if (this.virtual) return;
|
|
127
|
+
|
|
128
|
+
this.context.log.debug(
|
|
129
|
+
`Applied <${this.name}>.active = ${logBoolean(active)}`
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public async applyRange(
|
|
135
|
+
fromIndex: number,
|
|
136
|
+
toIndex: number,
|
|
137
|
+
colors: ColorHSV[],
|
|
138
|
+
parameters?: ApplyParameters
|
|
139
|
+
) {
|
|
140
|
+
await this.withWriteLock(async () => {
|
|
141
|
+
if (!this.virtual) {
|
|
142
|
+
this.context.openRgbClient.updateLeds(
|
|
143
|
+
this.openRgbDeviceId,
|
|
144
|
+
new Array(this.size)
|
|
145
|
+
.fill(DEFAULT_COLOR)
|
|
146
|
+
.map((defaultColor, index) => {
|
|
147
|
+
if (index >= fromIndex && index < toIndex) {
|
|
148
|
+
return colors[index - fromIndex];
|
|
149
|
+
} else {
|
|
150
|
+
return this.colors[index];
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
.map((colorHsv) => {
|
|
154
|
+
return openRgbUtils.HSVColor(
|
|
155
|
+
colorHsv.hue * 360,
|
|
156
|
+
colorHsv.saturation,
|
|
157
|
+
colorHsv.value
|
|
158
|
+
);
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
for (let index = fromIndex; index < toIndex; index++) {
|
|
163
|
+
this.colors[index] = colors[index - fromIndex];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.context.log.debug(
|
|
168
|
+
`Applied <${this.name}>[${fromIndex}-${toIndex}] = [\n${colors
|
|
169
|
+
.map(colorToString)
|
|
170
|
+
.map((colorString) => ' ' + colorString)
|
|
171
|
+
.join(',\n')}\n]`
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/Zone.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Context, ZoneConfiguration } from './types';
|
|
2
|
+
import { Device } from './Device';
|
|
3
|
+
import {
|
|
4
|
+
logAccessoryName,
|
|
5
|
+
logBoolean,
|
|
6
|
+
logPercent,
|
|
7
|
+
} from 'helpers-for-homebridge';
|
|
8
|
+
import { Effect, Zone as EffectKitZone } from '@manganese/effect-kit';
|
|
9
|
+
|
|
10
|
+
export class Zone extends EffectKitZone {
|
|
11
|
+
constructor(
|
|
12
|
+
protected override configuration: ZoneConfiguration,
|
|
13
|
+
protected override context: Context
|
|
14
|
+
) {
|
|
15
|
+
const devices = new Set(
|
|
16
|
+
configuration.devices.map((deviceConfiguration) => {
|
|
17
|
+
return new Device(deviceConfiguration, context);
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
super(configuration, devices, context);
|
|
22
|
+
|
|
23
|
+
this.devices = devices;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Initialization
|
|
27
|
+
public async initialize() {
|
|
28
|
+
await super.initialize();
|
|
29
|
+
|
|
30
|
+
if (this.context.persistence) {
|
|
31
|
+
const active = await this.context.persistence.getZoneActive(this.id);
|
|
32
|
+
const brightness = await this.context.persistence.getZoneBrightness(
|
|
33
|
+
this.id
|
|
34
|
+
);
|
|
35
|
+
const effectId = await this.context.persistence.getZoneEffectId(this.id);
|
|
36
|
+
|
|
37
|
+
if (effectId) {
|
|
38
|
+
const effect = this.getEffect<Effect<any>>(effectId);
|
|
39
|
+
|
|
40
|
+
this.context.log.debug(
|
|
41
|
+
`Restoring persisted zone ${logAccessoryName(
|
|
42
|
+
this.name
|
|
43
|
+
)} effect ID = [${effect.name}]`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
await this.setEffectId(effectId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.context.log.debug(
|
|
50
|
+
`Restoring persisted zone ${logAccessoryName(
|
|
51
|
+
this.name
|
|
52
|
+
)} active = ${logBoolean(active)}, brightness = ${logPercent(
|
|
53
|
+
brightness
|
|
54
|
+
)}`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
await this.setBrightness(brightness);
|
|
58
|
+
await this.setActive(active);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.on('updateActive', async (active: boolean) => {
|
|
62
|
+
await this.context.persistence.setZoneActive(this.id, active);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.on('updateBrightness', async (brightness: number) => {
|
|
66
|
+
await this.context.persistence.setZoneBrightness(this.id, brightness);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.on('updateEffect', async (effectId: string | null) => {
|
|
70
|
+
if (effectId) {
|
|
71
|
+
await this.context.persistence.setZoneEffectId(this.id, effectId);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Devices
|
|
77
|
+
private devices: Set<Device>;
|
|
78
|
+
|
|
79
|
+
private get devicesArray() {
|
|
80
|
+
return Array.from(this.devices);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BrightnessAccessory } from 'helpers-for-homebridge';
|
|
2
|
+
import { Context } from '../types';
|
|
3
|
+
import { Zone } from '../Zone';
|
|
4
|
+
|
|
5
|
+
export class ZoneBrightnessAccessory extends BrightnessAccessory {
|
|
6
|
+
constructor(
|
|
7
|
+
private readonly zone: Zone,
|
|
8
|
+
context: Context
|
|
9
|
+
) {
|
|
10
|
+
super(
|
|
11
|
+
{
|
|
12
|
+
name: zone.name + ' Brightness',
|
|
13
|
+
serial: zone.id + '_brightness',
|
|
14
|
+
model: 'OpenRGB Multi Zone Brightness',
|
|
15
|
+
},
|
|
16
|
+
context
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public override async getBrightness(): Promise<number> {
|
|
21
|
+
return this.zone.getBrightness();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public override async setBrightness(brightness: number): Promise<void> {
|
|
25
|
+
return this.zone.setBrightness(brightness);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SelectionAccessory } from 'helpers-for-homebridge';
|
|
2
|
+
import { Context } from '../types';
|
|
3
|
+
import { Zone } from '../Zone';
|
|
4
|
+
import { ZONE_EFFECT_SELECTION_ACCESSORY_MODE } from '../constants';
|
|
5
|
+
|
|
6
|
+
export class ZoneEffectAccessory extends SelectionAccessory {
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly zone: Zone,
|
|
9
|
+
context: Context
|
|
10
|
+
) {
|
|
11
|
+
super(
|
|
12
|
+
{
|
|
13
|
+
name: zone.name + ' Effect',
|
|
14
|
+
serial: zone.id + '_effect',
|
|
15
|
+
model: 'OpenRGB Multi Zone Effect',
|
|
16
|
+
mode: ZONE_EFFECT_SELECTION_ACCESSORY_MODE,
|
|
17
|
+
options: Array.from(zone.getEffects().values()).map((effect) => {
|
|
18
|
+
return {
|
|
19
|
+
name: effect.name,
|
|
20
|
+
id: effect.id,
|
|
21
|
+
hapIdentifier: effect.hapIdentifier,
|
|
22
|
+
};
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
context
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected override async getActiveOptionId() {
|
|
30
|
+
return this.zone.getEffectIdOrDefault();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected override async setActiveOptionId(effectId: string) {
|
|
34
|
+
await this.zone.setActiveEffectId(effectId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected override async getActive() {
|
|
38
|
+
return this.zone.active;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected override async setActive(active: boolean) {
|
|
42
|
+
await this.zone.setActive(active);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SwitchAccessory } from 'helpers-for-homebridge';
|
|
2
|
+
import { Context } from '../types';
|
|
3
|
+
import { Zone } from '../Zone';
|
|
4
|
+
|
|
5
|
+
export class ZoneSceneTransitionAccessory extends SwitchAccessory {
|
|
6
|
+
constructor(
|
|
7
|
+
private readonly zone: Zone,
|
|
8
|
+
context: Context
|
|
9
|
+
) {
|
|
10
|
+
super(
|
|
11
|
+
{
|
|
12
|
+
name: zone.name + ' Scene Transition',
|
|
13
|
+
serial: zone.id + '_scene_transition',
|
|
14
|
+
model: 'OpenRGB Multi Zone Scene Transition',
|
|
15
|
+
},
|
|
16
|
+
context
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public override async getOn(): Promise<boolean> {
|
|
21
|
+
return this.zone.sceneTransitionActive;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public override async setOn(on: boolean): Promise<void> {
|
|
25
|
+
if (!on) return;
|
|
26
|
+
|
|
27
|
+
this.zone.sceneTransition();
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SimpleLogger from 'simple-node-logger';
|
|
2
|
+
import { SelectionAccessoryMode } from 'helpers-for-homebridge';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_PERSISTENCE_FILENAME = 'openrgb.json';
|
|
5
|
+
export const DEFAULT_OPENRGB_HOST = 'localhost';
|
|
6
|
+
export const DEFAULT_OPENRGB_PORT = 6742;
|
|
7
|
+
export const DEFAULT_LOG_LEVEL: SimpleLogger.STANDARD_LEVELS = 'info';
|
|
8
|
+
export const DEFAULT_DURATION = 1000;
|
|
9
|
+
export const EFFECT_TRANSITION_DURATION = 500;
|
|
10
|
+
export const ZONE_EFFECT_SELECTION_ACCESSORY_MODE: SelectionAccessoryMode =
|
|
11
|
+
'outlet';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createPlatform } from './platformFactory';
|
|
2
|
+
|
|
3
|
+
export * from './types';
|
|
4
|
+
export * from './constants';
|
|
5
|
+
export * from './Zone';
|
|
6
|
+
export * from './Device';
|
|
7
|
+
|
|
8
|
+
export default (homebridge: any) => {
|
|
9
|
+
const Platform = createPlatform(homebridge);
|
|
10
|
+
|
|
11
|
+
homebridge.registerPlatform(
|
|
12
|
+
'homebridge-openrgb-multi',
|
|
13
|
+
'OpenRGBMulti',
|
|
14
|
+
Platform
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Logger } from 'simple-node-logger';
|
|
2
|
+
import { Client as OpenRGBClient } from 'openrgb-sdk';
|
|
3
|
+
import { API, Logging } from 'homebridge';
|
|
4
|
+
import { HomebridgeLogAppender } from 'helpers-for-homebridge';
|
|
5
|
+
import { Palette } from '@manganese/palette-kit-core';
|
|
6
|
+
import { PaletteClient } from '@manganese/palette-kit-client';
|
|
7
|
+
|
|
8
|
+
import { Context, PlatformConfiguration } from './types';
|
|
9
|
+
import { PersistenceService } from './services';
|
|
10
|
+
import {
|
|
11
|
+
ZoneBrightnessAccessory,
|
|
12
|
+
ZoneEffectAccessory,
|
|
13
|
+
ZoneSceneTransitionAccessory,
|
|
14
|
+
} from './accessories';
|
|
15
|
+
import { Zone } from './Zone';
|
|
16
|
+
import { DEFAULT_OPENRGB_HOST, DEFAULT_OPENRGB_PORT } from './constants';
|
|
17
|
+
|
|
18
|
+
export const createPlatform = (homebridge: API) => {
|
|
19
|
+
class Platform {
|
|
20
|
+
context: Context;
|
|
21
|
+
zones = new Map<string /* zone ID */, Zone>();
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
homebridgeLog: Logging,
|
|
25
|
+
readonly configuration: PlatformConfiguration
|
|
26
|
+
) {
|
|
27
|
+
const log = new Logger({
|
|
28
|
+
level: 'debug',
|
|
29
|
+
appenders: [new HomebridgeLogAppender(homebridgeLog)],
|
|
30
|
+
});
|
|
31
|
+
const persistence = new PersistenceService({
|
|
32
|
+
homebridge,
|
|
33
|
+
log,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const openRgbClient = new OpenRGBClient(
|
|
37
|
+
'default',
|
|
38
|
+
configuration.openRgbPort ?? DEFAULT_OPENRGB_PORT,
|
|
39
|
+
configuration.openRgbHost ?? DEFAULT_OPENRGB_HOST
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const palette = new Palette({}, { log });
|
|
43
|
+
const paletteClient = configuration.paletteClient
|
|
44
|
+
? new PaletteClient(
|
|
45
|
+
configuration.paletteClient,
|
|
46
|
+
{
|
|
47
|
+
log,
|
|
48
|
+
},
|
|
49
|
+
palette
|
|
50
|
+
)
|
|
51
|
+
: null;
|
|
52
|
+
|
|
53
|
+
this.context = {
|
|
54
|
+
homebridge,
|
|
55
|
+
openRgbClient,
|
|
56
|
+
palette,
|
|
57
|
+
paletteClient,
|
|
58
|
+
log,
|
|
59
|
+
persistence,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
configuration.zones.forEach((zoneConfiguration) => {
|
|
63
|
+
const zone = new Zone(zoneConfiguration, this.context);
|
|
64
|
+
|
|
65
|
+
this.zones.set(zone.id, zone);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async accessories(callback) {
|
|
70
|
+
await this.context.openRgbClient.connect();
|
|
71
|
+
await this.context.paletteClient.connect();
|
|
72
|
+
await Promise.all(
|
|
73
|
+
Array.from(this.zones.values()).map(async (zone) => {
|
|
74
|
+
return await zone.initialize();
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const zoneAccessories = Array.from(this.zones.values()).flatMap(
|
|
79
|
+
(zone) => {
|
|
80
|
+
const zoneEffectAccessory = new ZoneEffectAccessory(
|
|
81
|
+
zone,
|
|
82
|
+
this.context
|
|
83
|
+
);
|
|
84
|
+
const zoneBrightnessAccessory = new ZoneBrightnessAccessory(
|
|
85
|
+
zone,
|
|
86
|
+
this.context
|
|
87
|
+
);
|
|
88
|
+
const zoneSceneTransitionAccessory = new ZoneSceneTransitionAccessory(
|
|
89
|
+
zone,
|
|
90
|
+
this.context
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
zone.on('updateBrightness', (brightness) => {
|
|
94
|
+
zoneBrightnessAccessory.updateBrightness(brightness);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
zone.on('updateSceneTransitionActive', (sceneTransitionActive) => {
|
|
98
|
+
zoneSceneTransitionAccessory.update(sceneTransitionActive);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return [
|
|
102
|
+
zoneEffectAccessory,
|
|
103
|
+
zoneBrightnessAccessory,
|
|
104
|
+
zoneSceneTransitionAccessory,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const accessories = [...zoneAccessories];
|
|
110
|
+
|
|
111
|
+
callback(accessories);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Platform;
|
|
116
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BasicPersistenceService,
|
|
3
|
+
Context,
|
|
4
|
+
logAccessoryName,
|
|
5
|
+
} from 'helpers-for-homebridge';
|
|
6
|
+
import { PlatformPersistence, ZonePersistence } from '../types';
|
|
7
|
+
import { DEFAULT_PERSISTENCE_FILENAME } from '../constants';
|
|
8
|
+
|
|
9
|
+
export class PersistenceService extends BasicPersistenceService<PlatformPersistence> {
|
|
10
|
+
constructor(context: Context) {
|
|
11
|
+
super(
|
|
12
|
+
{
|
|
13
|
+
fileName: DEFAULT_PERSISTENCE_FILENAME,
|
|
14
|
+
defaultValue: {
|
|
15
|
+
zones: {},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
context
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getZonePersistence(zoneId: string): Promise<ZonePersistence | null> {
|
|
23
|
+
const persistence = await this.readPersistence();
|
|
24
|
+
|
|
25
|
+
return persistence.zones?.[zoneId] ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getZoneActive(zoneId: string): Promise<boolean> {
|
|
29
|
+
const zonePersistence = await this.getZonePersistence(zoneId);
|
|
30
|
+
|
|
31
|
+
return zonePersistence?.active || false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getZoneBrightness(zoneId: string): Promise<number> {
|
|
35
|
+
const zonePersistence = await this.getZonePersistence(zoneId);
|
|
36
|
+
|
|
37
|
+
return zonePersistence?.brightness ?? 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getZoneEffectId(zoneId: string): Promise<string | null /* effect ID*/> {
|
|
41
|
+
const zonePersistence = await this.getZonePersistence(zoneId);
|
|
42
|
+
|
|
43
|
+
return zonePersistence?.effectId ?? null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async mergeZonePersistence(
|
|
47
|
+
zoneId: string,
|
|
48
|
+
zonePersistence: Partial<ZonePersistence>
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
await this.mergePersistence({
|
|
51
|
+
zones: {
|
|
52
|
+
[zoneId]: zonePersistence,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async setZoneActive(zoneId: string, active: boolean): Promise<void> {
|
|
58
|
+
await this.mergeZonePersistence(zoneId, {
|
|
59
|
+
active,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async setZoneBrightness(zoneId: string, brightness: number): Promise<void> {
|
|
64
|
+
await this.mergeZonePersistence(zoneId, {
|
|
65
|
+
brightness,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async setZoneEffectId(zoneId: string, effectId: string): Promise<void> {
|
|
70
|
+
await this.mergeZonePersistence(zoneId, {
|
|
71
|
+
effectId,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PersistenceService';
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Client as OpenRGBClient } from 'openrgb-sdk';
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
const openRgbClient = new OpenRGBClient(
|
|
5
|
+
'default',
|
|
6
|
+
6742,
|
|
7
|
+
'living-room-node.local.mangane.se'
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
await openRgbClient.connect();
|
|
11
|
+
|
|
12
|
+
const openRgbDevices = await openRgbClient.getAllControllerData();
|
|
13
|
+
|
|
14
|
+
console.log(openRgbDevices);
|
|
15
|
+
const openRgbDevice = (openRgbDevices as any).find(
|
|
16
|
+
(openRgbDevice) =>
|
|
17
|
+
(openRgbDevice as any).serial.replace(/\s+$/g, '') === '282039U09401607'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
console.log(JSON.stringify((openRgbDevices[0] as any).serial));
|
|
21
|
+
console.log(
|
|
22
|
+
JSON.stringify((openRgbDevices[0] as any).serial.replace(/\s/g, ''))
|
|
23
|
+
);
|
|
24
|
+
console.log(openRgbDevice);
|
|
25
|
+
|
|
26
|
+
// openRgbClient.updateLeds(
|
|
27
|
+
// openRgbDevice.deviceId,
|
|
28
|
+
// openRgbDevice.colors.map((color, index) => {
|
|
29
|
+
// if (index !== 8) return color;
|
|
30
|
+
|
|
31
|
+
// return {
|
|
32
|
+
// red: 255,
|
|
33
|
+
// green: 0,
|
|
34
|
+
// blue: 255,
|
|
35
|
+
// };
|
|
36
|
+
// })
|
|
37
|
+
// );
|
|
38
|
+
|
|
39
|
+
// // const openRgbDevices = await Promise.all(
|
|
40
|
+
// // openRgbClient.getAllControllerData()
|
|
41
|
+
// // );
|
|
42
|
+
|
|
43
|
+
// // console.log(openRgbDevices);
|
|
44
|
+
})();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { API } from 'homebridge';
|
|
2
|
+
import { Logger } from 'simple-node-logger';
|
|
3
|
+
import OpenRGBClient from 'openrgb-sdk/types/client';
|
|
4
|
+
import { PaletteClient } from '@manganese/palette-kit-client';
|
|
5
|
+
import { Palette } from '@manganese/palette-kit-core';
|
|
6
|
+
import { PersistenceService } from '../services';
|
|
7
|
+
|
|
8
|
+
export interface Context {
|
|
9
|
+
homebridge: API;
|
|
10
|
+
openRgbClient: OpenRGBClient;
|
|
11
|
+
palette: Palette;
|
|
12
|
+
paletteClient: PaletteClient | null;
|
|
13
|
+
log: Logger;
|
|
14
|
+
persistence: PersistenceService;
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ClientConfiguration } from '@manganese/palette-kit-client';
|
|
2
|
+
import { ZoneConfiguration } from '.';
|
|
3
|
+
|
|
4
|
+
export interface PlatformConfiguration {
|
|
5
|
+
discoveryInterval?: number;
|
|
6
|
+
networkInterfaces?: string[];
|
|
7
|
+
openRgbHost: string;
|
|
8
|
+
openRgbPort: number;
|
|
9
|
+
paletteClient?: ClientConfiguration;
|
|
10
|
+
zones: ZoneConfiguration[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ZoneConfiguration as EffectKitZoneConfiguration } from '@manganese/effect-kit';
|
|
2
|
+
import { DeviceConfiguration } from './DeviceConfiguration';
|
|
3
|
+
|
|
4
|
+
export interface ZoneConfiguration extends EffectKitZoneConfiguration {
|
|
5
|
+
trueOff?: boolean;
|
|
6
|
+
devices: DeviceConfiguration[];
|
|
7
|
+
}
|