homebridge-lanternic 0.1.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/LICENSE +21 -0
- package/README.md +260 -0
- package/config.schema.json +412 -0
- package/dist/ble/magicLanternBleManager.d.ts +78 -0
- package/dist/ble/magicLanternBleManager.js +325 -0
- package/dist/ble/magicLanternBleManager.js.map +1 -0
- package/dist/ble/magicLanternCommands.d.ts +16 -0
- package/dist/ble/magicLanternCommands.js +49 -0
- package/dist/ble/magicLanternCommands.js.map +1 -0
- package/dist/ble/nobleTypes.d.ts +33 -0
- package/dist/ble/nobleTypes.js +2 -0
- package/dist/ble/nobleTypes.js.map +1 -0
- package/dist/color.d.ts +10 -0
- package/dist/color.js +65 -0
- package/dist/color.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +23 -0
- package/dist/platform.js +150 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +26 -0
- package/dist/platformAccessory.js +139 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +3 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util/async.d.ts +2 -0
- package/dist/util/async.js +24 -0
- package/dist/util/async.js.map +1 -0
- package/dist/util/bluetooth.d.ts +3 -0
- package/dist/util/bluetooth.js +15 -0
- package/dist/util/bluetooth.js.map +1 -0
- package/package.json +81 -0
- package/tools/calibrate.mjs +314 -0
- package/tools/calibrator/app.js +399 -0
- package/tools/calibrator/index.html +91 -0
- package/tools/calibrator/styles.css +302 -0
- package/tools/explore.mjs +73 -0
- package/tools/scan.mjs +88 -0
- package/tools/send-sequence.mjs +76 -0
- package/tools/send.mjs +106 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { API, Characteristic, DynamicPlatformPlugin, Logging, PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { MagicLanternBleManager } from './ble/magicLanternBleManager.js';
|
|
3
|
+
import type { LanternIcAccessoryContext, LanternIcPlatformConfig } from './types.js';
|
|
4
|
+
export declare class LanternIcPlatform implements DynamicPlatformPlugin {
|
|
5
|
+
readonly log: Logging;
|
|
6
|
+
readonly config: LanternIcPlatformConfig;
|
|
7
|
+
readonly api: API;
|
|
8
|
+
readonly Service: typeof Service;
|
|
9
|
+
readonly Characteristic: typeof Characteristic;
|
|
10
|
+
readonly ble: MagicLanternBleManager;
|
|
11
|
+
private readonly accessories;
|
|
12
|
+
constructor(log: Logging, config: LanternIcPlatformConfig, api: API);
|
|
13
|
+
configureAccessory(accessory: PlatformAccessory<LanternIcAccessoryContext>): void;
|
|
14
|
+
private setupAccessories;
|
|
15
|
+
private registerDeviceAccessory;
|
|
16
|
+
private discoverAndMaybeRegister;
|
|
17
|
+
private logCandidate;
|
|
18
|
+
private deviceFromCandidate;
|
|
19
|
+
private isConfigured;
|
|
20
|
+
private discoveryAutoAdd;
|
|
21
|
+
private validDevices;
|
|
22
|
+
private deviceUniqueId;
|
|
23
|
+
}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MagicLanternBleManager } from './ble/magicLanternBleManager.js';
|
|
2
|
+
import { LanternIcPlatformAccessory } from './platformAccessory.js';
|
|
3
|
+
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
4
|
+
import { formatBluetoothAddress, normalizeBluetoothId } from './util/bluetooth.js';
|
|
5
|
+
export class LanternIcPlatform {
|
|
6
|
+
log;
|
|
7
|
+
config;
|
|
8
|
+
api;
|
|
9
|
+
Service;
|
|
10
|
+
Characteristic;
|
|
11
|
+
ble;
|
|
12
|
+
accessories = new Map();
|
|
13
|
+
constructor(log, config, api) {
|
|
14
|
+
this.log = log;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.api = api;
|
|
17
|
+
this.Service = api.hap.Service;
|
|
18
|
+
this.Characteristic = api.hap.Characteristic;
|
|
19
|
+
this.ble = new MagicLanternBleManager(log, config.ble);
|
|
20
|
+
this.api.on('didFinishLaunching', () => {
|
|
21
|
+
this.log.debug('LanternIC didFinishLaunching');
|
|
22
|
+
void this.setupAccessories().catch(error => {
|
|
23
|
+
this.log.error('LanternIC setup failed:', error instanceof Error ? error.message : String(error));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
configureAccessory(accessory) {
|
|
28
|
+
this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
29
|
+
this.accessories.set(accessory.UUID, accessory);
|
|
30
|
+
}
|
|
31
|
+
async setupAccessories() {
|
|
32
|
+
const devices = this.validDevices();
|
|
33
|
+
const activeUuids = new Set();
|
|
34
|
+
for (const device of devices) {
|
|
35
|
+
activeUuids.add(this.registerDeviceAccessory(device, false));
|
|
36
|
+
}
|
|
37
|
+
const discoverySucceeded = await this.discoverAndMaybeRegister(devices, activeUuids);
|
|
38
|
+
for (const [uuid, accessory] of this.accessories) {
|
|
39
|
+
if (activeUuids.has(uuid)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (this.discoveryAutoAdd() && accessory.context.autoDiscovered) {
|
|
43
|
+
const reason = discoverySucceeded ? 'it was previously auto-discovered' : 'discovery scan failed';
|
|
44
|
+
this.log.info(`Keeping cached auto-discovered accessory because ${reason}:`, accessory.displayName);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
this.log.info('Removing accessory no longer in LanternIC config:', accessory.displayName);
|
|
48
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
registerDeviceAccessory(device, autoDiscovered) {
|
|
52
|
+
const uuid = this.api.hap.uuid.generate(this.deviceUniqueId(device));
|
|
53
|
+
const existingAccessory = this.accessories.get(uuid);
|
|
54
|
+
if (existingAccessory) {
|
|
55
|
+
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
56
|
+
existingAccessory.context.device = device;
|
|
57
|
+
existingAccessory.context.autoDiscovered = autoDiscovered;
|
|
58
|
+
this.api.updatePlatformAccessories([existingAccessory]);
|
|
59
|
+
new LanternIcPlatformAccessory(this, existingAccessory);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.log.info('Adding new accessory:', device.name, formatBluetoothAddress(device.address));
|
|
63
|
+
const accessory = new this.api.platformAccessory(device.name, uuid);
|
|
64
|
+
accessory.context.device = device;
|
|
65
|
+
accessory.context.autoDiscovered = autoDiscovered;
|
|
66
|
+
new LanternIcPlatformAccessory(this, accessory);
|
|
67
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
68
|
+
this.accessories.set(uuid, accessory);
|
|
69
|
+
}
|
|
70
|
+
return uuid;
|
|
71
|
+
}
|
|
72
|
+
async discoverAndMaybeRegister(configuredDevices, activeUuids) {
|
|
73
|
+
if (this.config.discovery?.enabled === false) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const scanSeconds = this.config.discovery?.scanSeconds ?? 15;
|
|
77
|
+
const namePrefixes = this.config.discovery?.namePrefixes ?? [
|
|
78
|
+
'Triones',
|
|
79
|
+
'MELK',
|
|
80
|
+
'ELK-BLEDOM',
|
|
81
|
+
'LED',
|
|
82
|
+
'OA',
|
|
83
|
+
];
|
|
84
|
+
const serviceUuids = this.config.discovery?.serviceUuids ?? [
|
|
85
|
+
this.config.ble?.serviceUuid ?? 'fff0',
|
|
86
|
+
];
|
|
87
|
+
try {
|
|
88
|
+
this.log.info(`Scanning ${scanSeconds}s for Magic Lantern BLE candidates...`);
|
|
89
|
+
const candidates = await this.ble.discoverCandidates(scanSeconds * 1000, {
|
|
90
|
+
minRssi: this.config.discovery?.minRssi,
|
|
91
|
+
namePrefixes,
|
|
92
|
+
serviceUuids,
|
|
93
|
+
});
|
|
94
|
+
if (candidates.length === 0) {
|
|
95
|
+
this.log.info('No Magic Lantern BLE candidates found during discovery scan.');
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
for (const candidate of candidates) {
|
|
99
|
+
this.logCandidate(candidate);
|
|
100
|
+
if (!this.discoveryAutoAdd() || this.isConfigured(candidate, configuredDevices)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const device = this.deviceFromCandidate(candidate);
|
|
104
|
+
activeUuids.add(this.registerDeviceAccessory(device, true));
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
this.log.warn('BLE discovery scan failed:', error instanceof Error ? error.message : String(error));
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
logCandidate(candidate) {
|
|
114
|
+
const address = candidate.address ? formatBluetoothAddress(candidate.address) : candidate.id;
|
|
115
|
+
const rssi = typeof candidate.rssi === 'number' ? ` RSSI=${candidate.rssi}` : '';
|
|
116
|
+
const services = candidate.serviceUuids.length > 0 ? ` services=${candidate.serviceUuids.join(',')}` : '';
|
|
117
|
+
this.log.info(`Discovered BLE candidate: ${candidate.name || '(unnamed)'} address=${address}${rssi}${services}`);
|
|
118
|
+
this.log.info(`LanternIC device config: ${JSON.stringify(this.deviceFromCandidate(candidate))}`);
|
|
119
|
+
}
|
|
120
|
+
deviceFromCandidate(candidate) {
|
|
121
|
+
return {
|
|
122
|
+
name: candidate.name || `LanternIC ${candidate.id.slice(-6)}`,
|
|
123
|
+
address: candidate.address || candidate.id,
|
|
124
|
+
manufacturer: 'Magic Lantern',
|
|
125
|
+
model: 'Magic Lantern RGBIC',
|
|
126
|
+
colorOrder: 'rgb',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
isConfigured(candidate, devices) {
|
|
130
|
+
const candidateId = normalizeBluetoothId(candidate.address || candidate.id);
|
|
131
|
+
return devices.some(device => normalizeBluetoothId(device.address) === candidateId);
|
|
132
|
+
}
|
|
133
|
+
discoveryAutoAdd() {
|
|
134
|
+
return this.config.discovery?.autoAdd === true;
|
|
135
|
+
}
|
|
136
|
+
validDevices() {
|
|
137
|
+
const devices = this.config.devices ?? [];
|
|
138
|
+
return devices.filter((device) => {
|
|
139
|
+
if (!device?.name || !device.address) {
|
|
140
|
+
this.log.warn('Skipping LanternIC device with missing name or address');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
deviceUniqueId(device) {
|
|
147
|
+
return `lanternic:${normalizeBluetoothId(device.address)}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
2
|
+
import type { LanternIcAccessoryContext } from './types.js';
|
|
3
|
+
import type { LanternIcPlatform } from './platform.js';
|
|
4
|
+
export declare class LanternIcPlatformAccessory {
|
|
5
|
+
private readonly platform;
|
|
6
|
+
private readonly device;
|
|
7
|
+
private readonly client;
|
|
8
|
+
private readonly accessory;
|
|
9
|
+
private readonly service;
|
|
10
|
+
private readonly state;
|
|
11
|
+
private colorTimer?;
|
|
12
|
+
private colorWriteWaiters;
|
|
13
|
+
constructor(platform: LanternIcPlatform, accessory: PlatformAccessory<LanternIcAccessoryContext>);
|
|
14
|
+
private setOn;
|
|
15
|
+
private setBrightness;
|
|
16
|
+
private setHue;
|
|
17
|
+
private setSaturation;
|
|
18
|
+
private scheduleColorWrite;
|
|
19
|
+
private buildCurrentColorCommand;
|
|
20
|
+
private buildPowerOnCommands;
|
|
21
|
+
private buildBrightnessUpdateCommands;
|
|
22
|
+
private powerMode;
|
|
23
|
+
private brightnessMode;
|
|
24
|
+
private resyncAfterReconnect;
|
|
25
|
+
private saveState;
|
|
26
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { applyColorOrder, clampPercent, hsbToRgb } from './color.js';
|
|
2
|
+
import { buildColorCommand, buildNativeBrightnessCommands, buildPowerOffCommands, buildPowerOnPrefixCommands, shouldScaleRgbBrightness, } from './ble/magicLanternCommands.js';
|
|
3
|
+
export class LanternIcPlatformAccessory {
|
|
4
|
+
platform;
|
|
5
|
+
device;
|
|
6
|
+
client;
|
|
7
|
+
accessory;
|
|
8
|
+
service;
|
|
9
|
+
state;
|
|
10
|
+
colorTimer;
|
|
11
|
+
colorWriteWaiters = [];
|
|
12
|
+
constructor(platform, accessory) {
|
|
13
|
+
this.platform = platform;
|
|
14
|
+
if (!accessory.context.device) {
|
|
15
|
+
throw new Error(`Accessory ${accessory.displayName} is missing LanternIC device context`);
|
|
16
|
+
}
|
|
17
|
+
this.accessory = accessory;
|
|
18
|
+
this.device = accessory.context.device;
|
|
19
|
+
this.client = this.platform.ble.createClient(this.device);
|
|
20
|
+
this.state = {
|
|
21
|
+
on: accessory.context.state?.on ?? false,
|
|
22
|
+
brightness: accessory.context.state?.brightness ?? 100,
|
|
23
|
+
hue: accessory.context.state?.hue ?? 0,
|
|
24
|
+
saturation: accessory.context.state?.saturation ?? 0,
|
|
25
|
+
};
|
|
26
|
+
const informationService = accessory.getService(this.platform.Service.AccessoryInformation)
|
|
27
|
+
?? accessory.addService(this.platform.Service.AccessoryInformation);
|
|
28
|
+
informationService
|
|
29
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, this.device.manufacturer ?? 'Magic Lantern')
|
|
30
|
+
.setCharacteristic(this.platform.Characteristic.Model, this.device.model ?? 'Magic Lantern RGBIC')
|
|
31
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, this.device.address);
|
|
32
|
+
this.service = accessory.getService(this.platform.Service.Lightbulb)
|
|
33
|
+
?? accessory.addService(this.platform.Service.Lightbulb);
|
|
34
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, this.device.name);
|
|
35
|
+
this.service.getCharacteristic(this.platform.Characteristic.On)
|
|
36
|
+
.onGet(() => this.state.on)
|
|
37
|
+
.onSet(value => this.setOn(Boolean(value)));
|
|
38
|
+
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
|
|
39
|
+
.onGet(() => this.state.brightness)
|
|
40
|
+
.onSet(value => this.setBrightness(Number(value)));
|
|
41
|
+
this.service.getCharacteristic(this.platform.Characteristic.Hue)
|
|
42
|
+
.onGet(() => this.state.hue)
|
|
43
|
+
.onSet(value => this.setHue(Number(value)));
|
|
44
|
+
this.service.getCharacteristic(this.platform.Characteristic.Saturation)
|
|
45
|
+
.onGet(() => this.state.saturation)
|
|
46
|
+
.onSet(value => this.setSaturation(Number(value)));
|
|
47
|
+
this.client.onReconnect(() => this.resyncAfterReconnect());
|
|
48
|
+
this.client.start();
|
|
49
|
+
}
|
|
50
|
+
async setOn(onValue) {
|
|
51
|
+
this.state.on = onValue;
|
|
52
|
+
this.saveState();
|
|
53
|
+
if (!onValue) {
|
|
54
|
+
await this.client.writeCommands(buildPowerOffCommands(this.powerMode()));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await this.client.writeCommands(this.buildPowerOnCommands());
|
|
58
|
+
}
|
|
59
|
+
async setBrightness(value) {
|
|
60
|
+
this.state.brightness = clampPercent(Number(value));
|
|
61
|
+
this.saveState();
|
|
62
|
+
if (!this.state.on) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await this.client.writeCommands(this.buildBrightnessUpdateCommands());
|
|
66
|
+
}
|
|
67
|
+
async setHue(value) {
|
|
68
|
+
this.state.hue = Number(value);
|
|
69
|
+
this.saveState();
|
|
70
|
+
await this.scheduleColorWrite();
|
|
71
|
+
}
|
|
72
|
+
async setSaturation(value) {
|
|
73
|
+
this.state.saturation = clampPercent(Number(value));
|
|
74
|
+
this.saveState();
|
|
75
|
+
await this.scheduleColorWrite();
|
|
76
|
+
}
|
|
77
|
+
async scheduleColorWrite() {
|
|
78
|
+
if (!this.state.on) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.colorTimer) {
|
|
82
|
+
clearTimeout(this.colorTimer);
|
|
83
|
+
}
|
|
84
|
+
await new Promise((resolve, reject) => {
|
|
85
|
+
this.colorWriteWaiters.push({ resolve, reject });
|
|
86
|
+
this.colorTimer = setTimeout(() => {
|
|
87
|
+
const waiters = this.colorWriteWaiters.splice(0);
|
|
88
|
+
this.colorTimer = undefined;
|
|
89
|
+
this.client.writeCommands([this.buildCurrentColorCommand()])
|
|
90
|
+
.then(() => {
|
|
91
|
+
for (const waiter of waiters) {
|
|
92
|
+
waiter.resolve();
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch(error => {
|
|
96
|
+
for (const waiter of waiters) {
|
|
97
|
+
waiter.reject(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}, 150);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
buildCurrentColorCommand() {
|
|
104
|
+
const brightness = shouldScaleRgbBrightness(this.brightnessMode()) ? this.state.brightness : 100;
|
|
105
|
+
const rgb = hsbToRgb(this.state.hue, this.state.saturation, brightness);
|
|
106
|
+
const ordered = applyColorOrder(rgb, this.device.colorOrder ?? 'rgb');
|
|
107
|
+
return buildColorCommand(ordered);
|
|
108
|
+
}
|
|
109
|
+
buildPowerOnCommands() {
|
|
110
|
+
return [
|
|
111
|
+
...buildPowerOnPrefixCommands(this.powerMode(), this.brightnessMode(), this.state.brightness),
|
|
112
|
+
this.buildCurrentColorCommand(),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
buildBrightnessUpdateCommands() {
|
|
116
|
+
return [
|
|
117
|
+
...buildNativeBrightnessCommands(this.state.brightness, this.brightnessMode()),
|
|
118
|
+
...(shouldScaleRgbBrightness(this.brightnessMode()) ? [this.buildCurrentColorCommand()] : []),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
powerMode() {
|
|
122
|
+
return this.device.powerMode ?? 'both';
|
|
123
|
+
}
|
|
124
|
+
brightnessMode() {
|
|
125
|
+
return this.device.brightnessMode ?? 'rgb';
|
|
126
|
+
}
|
|
127
|
+
async resyncAfterReconnect() {
|
|
128
|
+
if (!this.state.on) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.platform.log.debug(`[${this.device.name}] Resyncing desired state after BLE reconnect`);
|
|
132
|
+
await this.client.writeCommands(this.buildPowerOnCommands());
|
|
133
|
+
}
|
|
134
|
+
saveState() {
|
|
135
|
+
this.accessory.context.state = { ...this.state };
|
|
136
|
+
this.platform.api.updatePlatformAccessories([this.accessory]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=platformAccessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"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"}
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,sBAAsB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { PlatformConfig } from 'homebridge';
|
|
2
|
+
export type ColorOrder = 'rgb' | 'rbg' | 'grb' | 'gbr' | 'brg' | 'bgr';
|
|
3
|
+
export type PowerMode = 'native' | 'rgbBlack' | 'both';
|
|
4
|
+
export type BrightnessMode = 'rgb' | 'native' | 'both';
|
|
5
|
+
export type NobleBinding = 'default' | 'hci' | 'mac' | 'win';
|
|
6
|
+
export type HciDriver = 'default' | 'native' | 'usb' | 'uart';
|
|
7
|
+
export type WriteMode = 'auto' | 'withResponse' | 'withoutResponse';
|
|
8
|
+
export interface LanternIcDeviceConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
address: string;
|
|
11
|
+
manufacturer?: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
colorOrder?: ColorOrder;
|
|
14
|
+
powerMode?: PowerMode;
|
|
15
|
+
brightnessMode?: BrightnessMode;
|
|
16
|
+
}
|
|
17
|
+
export interface LanternIcDiscoveryConfig {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
autoAdd?: boolean;
|
|
20
|
+
scanSeconds?: number;
|
|
21
|
+
minRssi?: number;
|
|
22
|
+
namePrefixes?: string[];
|
|
23
|
+
serviceUuids?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface LanternIcBleConfig {
|
|
26
|
+
binding?: NobleBinding;
|
|
27
|
+
hciDriver?: HciDriver;
|
|
28
|
+
hciDeviceId?: number;
|
|
29
|
+
hciUserChannel?: boolean;
|
|
30
|
+
hciExtended?: boolean;
|
|
31
|
+
serviceUuid?: string;
|
|
32
|
+
characteristicUuid?: string;
|
|
33
|
+
keepConnected?: boolean;
|
|
34
|
+
connectTimeoutMs?: number;
|
|
35
|
+
scanTimeoutMs?: number;
|
|
36
|
+
writeTimeoutMs?: number;
|
|
37
|
+
retryAttempts?: number;
|
|
38
|
+
retryDelayMs?: number;
|
|
39
|
+
maxRetryDelayMs?: number;
|
|
40
|
+
reconnectDelayMs?: number;
|
|
41
|
+
maxReconnectDelayMs?: number;
|
|
42
|
+
writeDelayMs?: number;
|
|
43
|
+
idleDisconnectMs?: number;
|
|
44
|
+
writeMode?: WriteMode;
|
|
45
|
+
}
|
|
46
|
+
export interface LanternIcPlatformConfig extends PlatformConfig {
|
|
47
|
+
name?: string;
|
|
48
|
+
devices?: LanternIcDeviceConfig[];
|
|
49
|
+
discovery?: LanternIcDiscoveryConfig;
|
|
50
|
+
ble?: LanternIcBleConfig;
|
|
51
|
+
}
|
|
52
|
+
export interface LanternIcStoredState {
|
|
53
|
+
on?: boolean;
|
|
54
|
+
brightness?: number;
|
|
55
|
+
hue?: number;
|
|
56
|
+
saturation?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface LanternIcAccessoryContext {
|
|
59
|
+
autoDiscovered?: boolean;
|
|
60
|
+
device?: LanternIcDeviceConfig;
|
|
61
|
+
state?: LanternIcStoredState;
|
|
62
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const delay = (milliseconds) => {
|
|
2
|
+
if (milliseconds <= 0) {
|
|
3
|
+
return Promise.resolve();
|
|
4
|
+
}
|
|
5
|
+
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
6
|
+
};
|
|
7
|
+
export const withTimeout = async (promise, milliseconds, message) => {
|
|
8
|
+
if (milliseconds <= 0) {
|
|
9
|
+
return promise;
|
|
10
|
+
}
|
|
11
|
+
let timeout;
|
|
12
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
13
|
+
timeout = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
if (timeout) {
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/util/async.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,YAAoB,EAAiB,EAAE;IAC3D,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC9B,OAAmB,EACnB,YAAoB,EACpB,OAAe,EACH,EAAE;IACd,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,OAAkD,CAAC;IAEvD,MAAM,cAAc,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QACtD,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;IACvD,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,EAAE,CAAC;YACZ,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const BLUETOOTH_ID_PATTERN = /[^0-9a-f]/gi;
|
|
2
|
+
export const normalizeBluetoothId = (value) => {
|
|
3
|
+
return value.replace(BLUETOOTH_ID_PATTERN, '').toLowerCase();
|
|
4
|
+
};
|
|
5
|
+
export const formatBluetoothAddress = (value) => {
|
|
6
|
+
const normalized = normalizeBluetoothId(value);
|
|
7
|
+
if (normalized.length !== 12) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return normalized.match(/.{1,2}/g)?.join(':') ?? value;
|
|
11
|
+
};
|
|
12
|
+
export const normalizeUuid = (value) => {
|
|
13
|
+
return value.replace(BLUETOOTH_ID_PATTERN, '').toLowerCase();
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=bluetooth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bluetooth.js","sourceRoot":"","sources":["../../src/util/bluetooth.ts"],"names":[],"mappings":"AAAA,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAU,EAAE;IAC5D,OAAO,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAC/D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAa,EAAU,EAAE;IAC9D,MAAM,UAAU,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE/C,IAAI,UAAU,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAa,EAAU,EAAE;IACrD,OAAO,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAC/D,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "homebridge-lanternic",
|
|
3
|
+
"displayName": "LanternIC",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Homebridge plugin for BLE RGBIC/RGBWIC light strips controlled by the Magic Lantern app.",
|
|
7
|
+
"author": "Gibson Bell",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://github.com/gibsonbell/homebridge-lanternic#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/gibsonbell/homebridge-lanternic.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/gibsonbell/homebridge-lanternic/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"homebridge-plugin",
|
|
19
|
+
"homebridge-config-ui-x",
|
|
20
|
+
"magic-lantern",
|
|
21
|
+
"rgbic",
|
|
22
|
+
"rgbwic",
|
|
23
|
+
"ble",
|
|
24
|
+
"bluetooth"
|
|
25
|
+
],
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"lanternic-calibrate": "tools/calibrate.mjs",
|
|
32
|
+
"lanternic-explore": "tools/explore.mjs",
|
|
33
|
+
"lanternic-scan": "tools/scan.mjs",
|
|
34
|
+
"lanternic-send": "tools/send.mjs",
|
|
35
|
+
"lanternic-send-sequence": "tools/send-sequence.mjs"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"config.schema.json",
|
|
39
|
+
"dist",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"README.md",
|
|
42
|
+
"tools/calibrate.mjs",
|
|
43
|
+
"tools/calibrator",
|
|
44
|
+
"tools/explore.mjs",
|
|
45
|
+
"tools/scan.mjs",
|
|
46
|
+
"tools/send.mjs",
|
|
47
|
+
"tools/send-sequence.mjs"
|
|
48
|
+
],
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": "^22.10.0 || ^24.0.0",
|
|
51
|
+
"homebridge": "^1.8.0 || ^2.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "rimraf ./dist && tsc -p tsconfig.build.json",
|
|
55
|
+
"calibrate": "node tools/calibrate.mjs",
|
|
56
|
+
"explore": "node tools/explore.mjs",
|
|
57
|
+
"lint": "eslint . --max-warnings=0",
|
|
58
|
+
"scan": "node tools/scan.mjs",
|
|
59
|
+
"send": "node tools/send.mjs",
|
|
60
|
+
"send-sequence": "node tools/send-sequence.mjs",
|
|
61
|
+
"smoke:package": "node scripts/smoke-package.mjs",
|
|
62
|
+
"test": "vitest run src",
|
|
63
|
+
"watch": "tsc --watch",
|
|
64
|
+
"prepack": "npm run build",
|
|
65
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@stoprocent/noble": "^2.5.3"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@eslint/js": "^10.0.1",
|
|
72
|
+
"@stoprocent/bleno": "^0.12.5",
|
|
73
|
+
"@types/node": "^24.13.2",
|
|
74
|
+
"eslint": "^10.3.0",
|
|
75
|
+
"homebridge": "^2.0.0",
|
|
76
|
+
"rimraf": "^6.1.3",
|
|
77
|
+
"typescript": "^6.0.3",
|
|
78
|
+
"typescript-eslint": "^8.59.2",
|
|
79
|
+
"vitest": "^4.0.15"
|
|
80
|
+
}
|
|
81
|
+
}
|