homebridge-broadlink-rm-blaster 1.3.2 → 1.5.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 +28 -0
- package/README.md +23 -5
- package/config.schema.json +74 -0
- package/dist/accessories/advancedAccessory.d.ts +15 -0
- package/dist/accessories/advancedAccessory.js +94 -0
- package/dist/accessories/advancedAccessory.js.map +1 -0
- package/dist/broadlinkClient.d.ts +5 -0
- package/dist/broadlinkClient.js +98 -0
- package/dist/broadlinkClient.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/configFile.d.ts +9 -0
- package/dist/configFile.js +62 -0
- package/dist/configFile.js.map +1 -0
- package/dist/configTypes.d.ts +11 -0
- package/dist/learner.d.ts +2 -0
- package/dist/learner.js +457 -0
- package/dist/learner.js.map +1 -0
- package/dist/platform.js +17 -0
- package/dist/platform.js.map +1 -1
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.js +119 -0
- package/dist/terminal.js.map +1 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## [1.5.0] - 2026-08-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Advanced Accessories: a new standalone accessory type where one button
|
|
13
|
+
press sends multiple signals in sequence, with a configurable timeout
|
|
14
|
+
between them. An optional Off signal gives it real on/off state (a
|
|
15
|
+
normal Switch); without one, it's an auto-resetting momentary trigger
|
|
16
|
+
instead. `broadlink-rm-learner` can learn these too.
|
|
17
|
+
|
|
18
|
+
## [1.4.0] - 2026-08-05
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `broadlink-rm-learner`, an interactive CLI to learn IR/RF codes straight
|
|
22
|
+
from a remote and write the resulting Basic Accessory, TV, or Dimmer
|
|
23
|
+
Light into `config.json` - backs up `config.json` to `config.json.backup`
|
|
24
|
+
once per session before writing anything. RF codes are learned at a known
|
|
25
|
+
frequency (defaults to 433.92 MHz, entered by hand) rather than an
|
|
26
|
+
automatic sweep, which proved unreliable on real hardware. Menu choices
|
|
27
|
+
resolve on a single keypress, no Enter required. Connects to the RM
|
|
28
|
+
device before prompting to press any button, rather than after. Dimmer
|
|
29
|
+
Light learning asks the number of brightness levels up front, then Power
|
|
30
|
+
On/Off, then each brightness signal from 0% up to 100% last.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- README: moved the learner CLI's docs into a new "Learning hex codes"
|
|
34
|
+
section under Configuration (previously part of Debugging), and removed
|
|
35
|
+
the link to the standalone `learn-broadlink-rm4-codes` project.
|
|
36
|
+
|
|
9
37
|
## [1.3.2] - 2026-08-04
|
|
10
38
|
|
|
11
39
|
### Added
|
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
# Broadlink RM Blaster 1.
|
|
1
|
+
# Broadlink RM Blaster 1.5.0
|
|
2
2
|
|
|
3
3
|
**This Homebridge plugin has been 100% vibe coded with Claude.**
|
|
4
4
|
|
|
5
5
|
Blast RF and IR signals from Broadlink RM devices using Homebridge. Sends
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[
|
|
6
|
+
hex codes to a known device IP; this plugin does not autodiscover devices.
|
|
7
|
+
Codes can be learned interactively via the included `broadlink-rm-learner`
|
|
8
|
+
command (see [Learning hex codes](#learning-hex-codes)).
|
|
9
9
|
|
|
10
10
|
## Requirements
|
|
11
11
|
|
|
12
12
|
- A Broadlink RM device, unlocked using the official Broadlink app
|
|
13
|
+
- A fixed IP for the RM device, since the plugin does no autodiscovery
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -21,19 +22,36 @@ hb-service add homebridge-broadlink-rm-blaster
|
|
|
21
22
|
## Features
|
|
22
23
|
|
|
23
24
|
- **Multiple RM devices** configure several Broadlink RMs and assign each accessory to whichever one
|
|
25
|
+
- **Interactive code learning** learn codes straight from your remote and add them to your config
|
|
24
26
|
- **Basic accessories** power on/off
|
|
27
|
+
- **Advanced accessories** one press sends multiple signals in sequence, with a configurable timeout between them
|
|
25
28
|
- **Dimmer lights** one signal per discrete brightness level
|
|
26
29
|
- **TVs** power on/off plus a usable remote in the iOS Remote app
|
|
27
30
|
- **Temperature/humidity sensor** optionally expose sensor data
|
|
28
31
|
- **ntfy.sh notifications** push notification when a RM fails to connect
|
|
29
32
|
- **MQTT publishing** optionally publish sensor data to a MQTT broker
|
|
30
|
-
- Fully configurable via the Homebridge Config UI X plugin settings form
|
|
33
|
+
- **Fully configurable** via the Homebridge Config UI X plugin settings form
|
|
31
34
|
|
|
32
35
|
## Configuration
|
|
33
36
|
|
|
34
37
|
This plugin can be fully configured from the Homebridge Config UI X plugin
|
|
35
38
|
settings form.
|
|
36
39
|
|
|
40
|
+
## Learning hex codes
|
|
41
|
+
|
|
42
|
+
To learn codes straight from a remote and add the resulting accessory to
|
|
43
|
+
your config, run this in the Homebridge shell:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
broadlink-rm-learner
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Run `broadlink-rm-learner --help` for usage details. Advanced Accessories
|
|
50
|
+
(multiple signals per press) can be learned too, alongside Basic
|
|
51
|
+
Accessories, TVs, and Dimmer Lights.
|
|
52
|
+
|
|
53
|
+
Backs up `config.json` to `config.json.backup` once per session before writing anything.
|
|
54
|
+
|
|
37
55
|
## Debugging
|
|
38
56
|
|
|
39
57
|
To send a single hex code straight to your RM, bypassing Homebridge/HomeKit entirely run this in the Homebridge shell:
|
package/config.schema.json
CHANGED
|
@@ -154,6 +154,53 @@
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
},
|
|
157
|
+
"advancedAccessories": {
|
|
158
|
+
"title": "Advanced Accessories",
|
|
159
|
+
"type": "array",
|
|
160
|
+
"items": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"properties": {
|
|
163
|
+
"name": {
|
|
164
|
+
"title": "Name",
|
|
165
|
+
"type": "string",
|
|
166
|
+
"required": true
|
|
167
|
+
},
|
|
168
|
+
"rmDevice": {
|
|
169
|
+
"title": "RM Device",
|
|
170
|
+
"type": "string",
|
|
171
|
+
"required": true,
|
|
172
|
+
"description": "Must match the name of one of the configured RM devices exactly."
|
|
173
|
+
},
|
|
174
|
+
"offCode": {
|
|
175
|
+
"title": "Off Signal (leave empty for an auto-resetting trigger instead of a real on/off switch)",
|
|
176
|
+
"type": "string",
|
|
177
|
+
"required": false
|
|
178
|
+
},
|
|
179
|
+
"timeoutSeconds": {
|
|
180
|
+
"title": "Timeout Between Signals (seconds)",
|
|
181
|
+
"type": "number",
|
|
182
|
+
"minimum": 0,
|
|
183
|
+
"default": 0.5,
|
|
184
|
+
"required": false
|
|
185
|
+
},
|
|
186
|
+
"signals": {
|
|
187
|
+
"title": "Signals",
|
|
188
|
+
"type": "array",
|
|
189
|
+
"minItems": 1,
|
|
190
|
+
"items": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"properties": {
|
|
193
|
+
"code": {
|
|
194
|
+
"title": "Signal",
|
|
195
|
+
"type": "string",
|
|
196
|
+
"required": true
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
157
204
|
"dimmers": {
|
|
158
205
|
"title": "Dimmer Lights",
|
|
159
206
|
"type": "array",
|
|
@@ -405,6 +452,33 @@
|
|
|
405
452
|
"type": "help",
|
|
406
453
|
"helpvalue": "<div style=\"height: 1.5rem\"></div>"
|
|
407
454
|
},
|
|
455
|
+
{
|
|
456
|
+
"type": "array",
|
|
457
|
+
"key": "advancedAccessories",
|
|
458
|
+
"title": "Advanced Accessories",
|
|
459
|
+
"expandable": true,
|
|
460
|
+
"expanded": false,
|
|
461
|
+
"items": [
|
|
462
|
+
"advancedAccessories[].name",
|
|
463
|
+
"advancedAccessories[].rmDevice",
|
|
464
|
+
"advancedAccessories[].offCode",
|
|
465
|
+
"advancedAccessories[].timeoutSeconds",
|
|
466
|
+
{
|
|
467
|
+
"type": "array",
|
|
468
|
+
"key": "advancedAccessories[].signals",
|
|
469
|
+
"title": "Signals",
|
|
470
|
+
"expandable": true,
|
|
471
|
+
"expanded": false,
|
|
472
|
+
"items": [
|
|
473
|
+
"advancedAccessories[].signals[].code"
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"type": "help",
|
|
480
|
+
"helpvalue": "<div style=\"height: 1.5rem\"></div>"
|
|
481
|
+
},
|
|
408
482
|
{
|
|
409
483
|
"type": "array",
|
|
410
484
|
"key": "dimmers",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
2
|
+
import type { BroadlinkRMBlasterPlatform } from '../platform';
|
|
3
|
+
import type { AdvancedAccessoryConfig } from '../configTypes';
|
|
4
|
+
export declare class AdvancedAccessory {
|
|
5
|
+
private readonly platform;
|
|
6
|
+
private readonly accessory;
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly ip;
|
|
9
|
+
constructor(platform: BroadlinkRMBlasterPlatform, accessory: PlatformAccessory, config: AdvancedAccessoryConfig, ip: string);
|
|
10
|
+
private getOn;
|
|
11
|
+
private setOn;
|
|
12
|
+
private turnOff;
|
|
13
|
+
private sendSequence;
|
|
14
|
+
private scheduleAutoReset;
|
|
15
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AdvancedAccessory = void 0;
|
|
4
|
+
const DEFAULT_TIMEOUT_SECONDS = 0.5;
|
|
5
|
+
// How long to wait before resetting an auto-resetting (no offCode) trigger
|
|
6
|
+
// back to off. Not a stylistic choice - HomeKit's own optimistic UI update
|
|
7
|
+
// ignores a follow-up characteristic update that arrives too soon after the
|
|
8
|
+
// triggering write, so the tile visually sticks "on" unless this is a real
|
|
9
|
+
// delay (same lesson learned from this project's old dimmer up/down
|
|
10
|
+
// switches, removed in v0.7.0, but the timing constraint still applies to
|
|
11
|
+
// any momentary HomeKit switch).
|
|
12
|
+
const RESET_DELAY_MS = 1000;
|
|
13
|
+
function sleep(ms) {
|
|
14
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
}
|
|
16
|
+
class AdvancedAccessory {
|
|
17
|
+
platform;
|
|
18
|
+
accessory;
|
|
19
|
+
config;
|
|
20
|
+
ip;
|
|
21
|
+
constructor(platform, accessory, config, ip) {
|
|
22
|
+
this.platform = platform;
|
|
23
|
+
this.accessory = accessory;
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.ip = ip;
|
|
26
|
+
const service = this.accessory.getService(this.platform.Service.Switch)
|
|
27
|
+
?? this.accessory.addService(this.platform.Service.Switch);
|
|
28
|
+
service.setCharacteristic(this.platform.Characteristic.Name, this.config.name);
|
|
29
|
+
service.getCharacteristic(this.platform.Characteristic.On)
|
|
30
|
+
.onGet(() => this.getOn())
|
|
31
|
+
.onSet((value) => this.setOn(value));
|
|
32
|
+
}
|
|
33
|
+
// Same as every other blaster accessory - no feedback from the device
|
|
34
|
+
// being controlled, so "On" is an assumed state we track ourselves.
|
|
35
|
+
getOn() {
|
|
36
|
+
return Boolean(this.accessory.context.on);
|
|
37
|
+
}
|
|
38
|
+
async setOn(value) {
|
|
39
|
+
const on = Boolean(value);
|
|
40
|
+
if (!on) {
|
|
41
|
+
await this.turnOff();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await this.sendSequence();
|
|
46
|
+
this.accessory.context.on = true;
|
|
47
|
+
this.platform.log.info(`Sent signal sequence to ${this.config.name}`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.platform.log.error(`Failed to send code for "${this.config.name}": ${error.message}`);
|
|
51
|
+
const { HapStatusError, HAPStatus } = this.platform.api.hap;
|
|
52
|
+
throw new HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
|
|
53
|
+
}
|
|
54
|
+
if (!this.config.offCode) {
|
|
55
|
+
this.scheduleAutoReset();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async turnOff() {
|
|
59
|
+
if (!this.config.offCode) {
|
|
60
|
+
// Momentary trigger - nothing to send, just reflect the state.
|
|
61
|
+
this.accessory.context.on = false;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await this.platform.broadlinkClient.sendCode(this.ip, this.config.offCode);
|
|
66
|
+
this.accessory.context.on = false;
|
|
67
|
+
this.platform.log.info(`Sent Off to ${this.config.name}`);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
this.platform.log.error(`Failed to send code for "${this.config.name}": ${error.message}`);
|
|
71
|
+
const { HapStatusError, HAPStatus } = this.platform.api.hap;
|
|
72
|
+
throw new HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async sendSequence() {
|
|
76
|
+
const timeoutMs = (this.config.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000;
|
|
77
|
+
const { signals } = this.config;
|
|
78
|
+
for (let i = 0; i < signals.length; i++) {
|
|
79
|
+
await this.platform.broadlinkClient.sendCode(this.ip, signals[i].code);
|
|
80
|
+
if (i < signals.length - 1) {
|
|
81
|
+
await sleep(timeoutMs);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
scheduleAutoReset() {
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
this.accessory.context.on = false;
|
|
88
|
+
this.accessory.getService(this.platform.Service.Switch)
|
|
89
|
+
?.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
90
|
+
}, RESET_DELAY_MS);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.AdvancedAccessory = AdvancedAccessory;
|
|
94
|
+
//# sourceMappingURL=advancedAccessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advancedAccessory.js","sourceRoot":"","sources":["../../src/accessories/advancedAccessory.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Logger } from 'homebridge';
|
|
2
2
|
import type { NtfyNotifier } from './ntfyNotifier';
|
|
3
3
|
export declare function parseHexCode(hexCode: string): Buffer;
|
|
4
|
+
export declare function buildFindRfPacket(frequencyMHz: number): Buffer;
|
|
4
5
|
export interface TemperatureHumidityReading {
|
|
5
6
|
temperature: number;
|
|
6
7
|
humidity: number;
|
|
@@ -14,5 +15,9 @@ export declare class BroadlinkClient {
|
|
|
14
15
|
private getBroadlink;
|
|
15
16
|
private getDevice;
|
|
16
17
|
sendCode(ip: string, hexCode: string): Promise<void>;
|
|
18
|
+
connect(ip: string): Promise<void>;
|
|
17
19
|
readTemperatureHumidity(ip: string): Promise<TemperatureHumidityReading>;
|
|
20
|
+
private waitForEvent;
|
|
21
|
+
learnIrCode(ip: string, signal: AbortSignal): Promise<string>;
|
|
22
|
+
learnRfCode(ip: string, frequencyMHz: number, signal: AbortSignal): Promise<string>;
|
|
18
23
|
}
|
package/dist/broadlinkClient.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BroadlinkClient = void 0;
|
|
7
7
|
exports.parseHexCode = parseHexCode;
|
|
8
|
+
exports.buildFindRfPacket = buildFindRfPacket;
|
|
8
9
|
const kiwicam_broadlinkjs_rm_1 = __importDefault(require("kiwicam-broadlinkjs-rm"));
|
|
9
10
|
// Device type code the library reserves for a manually added RM (RF capable),
|
|
10
11
|
// used when connecting directly by IP instead of relying on UDP discovery.
|
|
@@ -12,9 +13,40 @@ const MANUAL_RM_DEVICE_TYPE = 0x2227;
|
|
|
12
13
|
const BROADLINK_PORT = 80;
|
|
13
14
|
const AUTH_TIMEOUT_MS = 10_000;
|
|
14
15
|
const READ_TIMEOUT_MS = 10_000;
|
|
16
|
+
const LEARN_POLL_INTERVAL_MS = 1_000;
|
|
17
|
+
const LEARN_TIMEOUT_MS = 20_000;
|
|
15
18
|
function parseHexCode(hexCode) {
|
|
16
19
|
return Buffer.from(hexCode.replace(/\s+/g, ''), 'hex');
|
|
17
20
|
}
|
|
21
|
+
// Builds the raw "find RF packet at a known frequency" command - matches
|
|
22
|
+
// python-broadlink's rmpro.find_rf_packet(frequency=...), which is what
|
|
23
|
+
// actually works reliably (confirmed: our own blind-sweep attempt via
|
|
24
|
+
// enterRFSweep()/checkRFData()/checkRFData2() failed on real hardware, while
|
|
25
|
+
// this known-frequency approach - same as the learn-broadlink-rm4-codes
|
|
26
|
+
// Python project - succeeded for IR; RF still needed this fix).
|
|
27
|
+
//
|
|
28
|
+
// The "new firmware"/RM4-generation wire format (confirmed against
|
|
29
|
+
// python-broadlink's actual rmminib._send, not guessed) is
|
|
30
|
+
// [length:2][command:4][data], all little-endian, where length = data.length
|
|
31
|
+
// + 4. kiwicam-broadlinkjs-rm's own request_header ([0x04, 0x00]) is only
|
|
32
|
+
// the length prefix for an EMPTY data payload, and its command-sending
|
|
33
|
+
// methods (checkData() etc) only send a 1-byte command instead of the full
|
|
34
|
+
// 4-byte one - both of those gaps are silently absorbed by AES block padding
|
|
35
|
+
// for empty-payload commands, which is why checkData()/enterLearning() work
|
|
36
|
+
// fine despite being technically malformed. That padding can't save a
|
|
37
|
+
// command with a real (non-zero) payload like this one: the frequency bytes
|
|
38
|
+
// would land 3 bytes early, inside what should still be the command field.
|
|
39
|
+
// So this builds the full, correct frame by hand instead of reusing
|
|
40
|
+
// request_header.
|
|
41
|
+
function buildFindRfPacket(frequencyMHz) {
|
|
42
|
+
const data = Buffer.alloc(4);
|
|
43
|
+
data.writeUInt32LE(Math.round(frequencyMHz * 1000), 0);
|
|
44
|
+
const length = Buffer.alloc(2);
|
|
45
|
+
length.writeUInt16LE(data.length + 4, 0);
|
|
46
|
+
const command = Buffer.alloc(4);
|
|
47
|
+
command.writeUInt32LE(0x1b, 0);
|
|
48
|
+
return Buffer.concat([length, command, data]);
|
|
49
|
+
}
|
|
18
50
|
// The manual/direct-by-IP placeholder MAC. Confirmed (via a real regression:
|
|
19
51
|
// see the comment on broadlinkInstances below) that real Broadlink RM4 Pro
|
|
20
52
|
// units silently refuse to authenticate at all if this is anything other
|
|
@@ -82,6 +114,13 @@ class BroadlinkClient {
|
|
|
82
114
|
const device = await this.getDevice(ip);
|
|
83
115
|
await device.sendData(parseHexCode(hexCode));
|
|
84
116
|
}
|
|
117
|
+
// Establishes (or reuses, since getDevice caches per IP) a connection
|
|
118
|
+
// without sending any command - lets a caller wait for a real connection
|
|
119
|
+
// and report on it before prompting for anything time-sensitive (e.g. "now
|
|
120
|
+
// press the button on the remote").
|
|
121
|
+
async connect(ip) {
|
|
122
|
+
await this.getDevice(ip);
|
|
123
|
+
}
|
|
85
124
|
async readTemperatureHumidity(ip) {
|
|
86
125
|
const device = await this.getDevice(ip);
|
|
87
126
|
return new Promise((resolve, reject) => {
|
|
@@ -98,6 +137,65 @@ class BroadlinkClient {
|
|
|
98
137
|
device.checkTemperature();
|
|
99
138
|
});
|
|
100
139
|
}
|
|
140
|
+
// Waits for `event` to fire, actively re-issuing `poll()` at a fixed
|
|
141
|
+
// interval in the meantime (this library never pushes learning data
|
|
142
|
+
// unprompted - it has to be asked again on a timer, same pattern as the
|
|
143
|
+
// Python reference project this CLI flow is modeled on). `signal` lets the
|
|
144
|
+
// caller cancel mid-wait (Ctrl-C / "press q to cancel" in the learner CLI).
|
|
145
|
+
waitForEvent(device, event, poll, signal, timeoutMessage) {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
if (signal.aborted) {
|
|
148
|
+
reject(new Error('Cancelled'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const cleanup = () => {
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
clearInterval(interval);
|
|
154
|
+
device.removeListener(event, onData);
|
|
155
|
+
signal.removeEventListener('abort', onAbort);
|
|
156
|
+
};
|
|
157
|
+
const onData = (data) => {
|
|
158
|
+
cleanup();
|
|
159
|
+
resolve(data);
|
|
160
|
+
};
|
|
161
|
+
const onAbort = () => {
|
|
162
|
+
cleanup();
|
|
163
|
+
reject(new Error('Cancelled'));
|
|
164
|
+
};
|
|
165
|
+
const timeout = setTimeout(() => {
|
|
166
|
+
cleanup();
|
|
167
|
+
reject(new Error(timeoutMessage));
|
|
168
|
+
}, LEARN_TIMEOUT_MS);
|
|
169
|
+
const interval = setInterval(poll, LEARN_POLL_INTERVAL_MS);
|
|
170
|
+
device.on(event, onData);
|
|
171
|
+
signal.addEventListener('abort', onAbort);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// One-shot: press the remote button any time after this is called.
|
|
175
|
+
async learnIrCode(ip, signal) {
|
|
176
|
+
const device = await this.getDevice(ip);
|
|
177
|
+
device.enterLearning();
|
|
178
|
+
try {
|
|
179
|
+
const data = await this.waitForEvent(device, 'rawData', () => device.checkData(), signal, 'Timed out waiting for a signal.');
|
|
180
|
+
return data.toString('hex');
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
device.cancelLearn();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Known-frequency RF learning (see buildFindRfPacket above for why) - also
|
|
187
|
+
// one-shot: press the button any time after this is called, same as IR.
|
|
188
|
+
async learnRfCode(ip, frequencyMHz, signal) {
|
|
189
|
+
const device = await this.getDevice(ip);
|
|
190
|
+
await device.sendPacket(0x6a, buildFindRfPacket(frequencyMHz));
|
|
191
|
+
try {
|
|
192
|
+
const data = await this.waitForEvent(device, 'rawData', () => device.checkData(), signal, 'Timed out waiting for a signal.');
|
|
193
|
+
return data.toString('hex');
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
device.cancelLearn();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
101
199
|
}
|
|
102
200
|
exports.BroadlinkClient = BroadlinkClient;
|
|
103
201
|
//# sourceMappingURL=broadlinkClient.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadlinkClient.js","sourceRoot":"","sources":["../src/broadlinkClient.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"broadlinkClient.js","sourceRoot":"","sources":["../src/broadlinkClient.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.isValidHexCode = isValidHexCode;
|
|
5
|
+
exports.createConsoleLogger = createConsoleLogger;
|
|
5
6
|
const broadlinkClient_1 = require("./broadlinkClient");
|
|
6
7
|
const USAGE = 'Usage: broadlink-rm-blaster <ip> <hexCode>';
|
|
7
8
|
const HELP = `${USAGE}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAsBA,wCAGC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlasterPlatformConfig } from './configTypes';
|
|
2
|
+
export interface HomebridgeConfigFile {
|
|
3
|
+
platforms?: Array<Record<string, unknown>>;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function findConfigPath(explicitPath?: string): string;
|
|
7
|
+
export declare function findPlatformBlock(config: HomebridgeConfigFile): BlasterPlatformConfig | undefined;
|
|
8
|
+
export declare function backupConfig(configPath: string): void;
|
|
9
|
+
export declare function writeConfigAtomically(configPath: string, config: HomebridgeConfigFile): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findConfigPath = findConfigPath;
|
|
37
|
+
exports.findPlatformBlock = findPlatformBlock;
|
|
38
|
+
exports.backupConfig = backupConfig;
|
|
39
|
+
exports.writeConfigAtomically = writeConfigAtomically;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const settings_1 = require("./settings");
|
|
43
|
+
function findConfigPath(explicitPath) {
|
|
44
|
+
const configPath = explicitPath ?? path.join(process.cwd(), 'config.json');
|
|
45
|
+
if (!fs.existsSync(configPath)) {
|
|
46
|
+
throw new Error(`Could not find a config.json at "${configPath}". Run this from your Homebridge storage ` +
|
|
47
|
+
'directory, or pass --config <path>.');
|
|
48
|
+
}
|
|
49
|
+
return configPath;
|
|
50
|
+
}
|
|
51
|
+
function findPlatformBlock(config) {
|
|
52
|
+
return config.platforms?.find((platform) => platform.platform === settings_1.PLATFORM_NAME);
|
|
53
|
+
}
|
|
54
|
+
function backupConfig(configPath) {
|
|
55
|
+
fs.copyFileSync(configPath, `${configPath}.backup`);
|
|
56
|
+
}
|
|
57
|
+
function writeConfigAtomically(configPath, config) {
|
|
58
|
+
const tempPath = path.join(path.dirname(configPath), `.${path.basename(configPath)}.tmp`);
|
|
59
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
60
|
+
fs.renameSync(tempPath, configPath);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=configFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configFile.js","sourceRoot":"","sources":["../src/configFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,wCASC;AAED,8CAEC;AAED,oCAEC;AAED,sDAIC;AAlCD,uCAAyB;AACzB,2CAA6B;AAE7B,yCAA2C;AAQ3C,SAAgB,cAAc,CAAC,YAAqB;IAClD,MAAM,UAAU,GAAG,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC;IAC3E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,oCAAoC,UAAU,2CAA2C;YACzF,qCAAqC,CACtC,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAgB,iBAAiB,CAAC,MAA4B;IAC5D,OAAO,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,KAAK,wBAAa,CAAsC,CAAC;AACxH,CAAC;AAED,SAAgB,YAAY,CAAC,UAAkB;IAC7C,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,UAAU,SAAS,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,qBAAqB,CAAC,UAAkB,EAAE,MAA4B;IACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1F,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/configTypes.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface DimmerAccessoryConfig {
|
|
|
32
32
|
debounceSeconds?: number;
|
|
33
33
|
levels: BrightnessLevelConfig[];
|
|
34
34
|
}
|
|
35
|
+
export interface AdvancedSignalConfig {
|
|
36
|
+
code: string;
|
|
37
|
+
}
|
|
38
|
+
export interface AdvancedAccessoryConfig {
|
|
39
|
+
name: string;
|
|
40
|
+
rmDevice: string;
|
|
41
|
+
signals: AdvancedSignalConfig[];
|
|
42
|
+
offCode?: string;
|
|
43
|
+
timeoutSeconds?: number;
|
|
44
|
+
}
|
|
35
45
|
export interface TvAccessoryConfig {
|
|
36
46
|
name: string;
|
|
37
47
|
rmDevice: string;
|
|
@@ -52,6 +62,7 @@ export interface TvAccessoryConfig {
|
|
|
52
62
|
export interface BlasterPlatformConfig extends PlatformConfig {
|
|
53
63
|
rmDevices: RmDeviceConfig[];
|
|
54
64
|
accessories?: BasicAccessoryConfig[];
|
|
65
|
+
advancedAccessories?: AdvancedAccessoryConfig[];
|
|
55
66
|
dimmers?: DimmerAccessoryConfig[];
|
|
56
67
|
tvs?: TvAccessoryConfig[];
|
|
57
68
|
ntfyTopic?: string;
|