homebridge-broadlink-rm-blaster 1.3.2 → 1.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/CHANGELOG.md +19 -0
- package/README.md +20 -5
- 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/learner.d.ts +2 -0
- package/dist/learner.js +396 -0
- package/dist/learner.js.map +1 -0
- 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,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## [1.4.0] - 2026-08-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `broadlink-rm-learner`, an interactive CLI to learn IR/RF codes straight
|
|
13
|
+
from a remote and write the resulting Basic Accessory, TV, or Dimmer
|
|
14
|
+
Light into `config.json` - backs up `config.json` to `config.json.backup`
|
|
15
|
+
once per session before writing anything. RF codes are learned at a known
|
|
16
|
+
frequency (defaults to 433.92 MHz, entered by hand) rather than an
|
|
17
|
+
automatic sweep, which proved unreliable on real hardware. Menu choices
|
|
18
|
+
resolve on a single keypress, no Enter required. Connects to the RM
|
|
19
|
+
device before prompting to press any button, rather than after. Dimmer
|
|
20
|
+
Light learning asks the number of brightness levels up front, then Power
|
|
21
|
+
On/Off, then each brightness signal from 0% up to 100% last.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- README: moved the learner CLI's docs into a new "Learning hex codes"
|
|
25
|
+
section under Configuration (previously part of Debugging), and removed
|
|
26
|
+
the link to the standalone `learn-broadlink-rm4-codes` project.
|
|
27
|
+
|
|
9
28
|
## [1.3.2] - 2026-08-04
|
|
10
29
|
|
|
11
30
|
### Added
|
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
# Broadlink RM Blaster 1.
|
|
1
|
+
# Broadlink RM Blaster 1.4.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,33 @@ 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
|
|
25
27
|
- **Dimmer lights** one signal per discrete brightness level
|
|
26
28
|
- **TVs** power on/off plus a usable remote in the iOS Remote app
|
|
27
29
|
- **Temperature/humidity sensor** optionally expose sensor data
|
|
28
30
|
- **ntfy.sh notifications** push notification when a RM fails to connect
|
|
29
31
|
- **MQTT publishing** optionally publish sensor data to a MQTT broker
|
|
30
|
-
- Fully configurable via the Homebridge Config UI X plugin settings form
|
|
32
|
+
- **Fully configurable** via the Homebridge Config UI X plugin settings form
|
|
31
33
|
|
|
32
34
|
## Configuration
|
|
33
35
|
|
|
34
36
|
This plugin can be fully configured from the Homebridge Config UI X plugin
|
|
35
37
|
settings form.
|
|
36
38
|
|
|
39
|
+
## Learning hex codes
|
|
40
|
+
|
|
41
|
+
To learn codes straight from a remote and add the resulting accessory to
|
|
42
|
+
your config, run this in the Homebridge shell:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
broadlink-rm-learner
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run `broadlink-rm-learner --help` for usage details.
|
|
49
|
+
|
|
50
|
+
Backs up `config.json` to `config.json.backup` once per session before writing anything.
|
|
51
|
+
|
|
37
52
|
## Debugging
|
|
38
53
|
|
|
39
54
|
To send a single hex code straight to your RM, bypassing Homebridge/HomeKit entirely run this in the Homebridge shell:
|
|
@@ -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/learner.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const broadlinkClient_1 = require("./broadlinkClient");
|
|
39
|
+
const cli_1 = require("./cli");
|
|
40
|
+
const configFile_1 = require("./configFile");
|
|
41
|
+
const settings_1 = require("./settings");
|
|
42
|
+
const terminal_1 = require("./terminal");
|
|
43
|
+
const USAGE = 'Usage: broadlink-rm-learner [--config <path>]';
|
|
44
|
+
const DEFAULT_RF_FREQUENCY_MHZ = 433.92;
|
|
45
|
+
const HELP = `${USAGE}
|
|
46
|
+
|
|
47
|
+
Interactively learns IR/RF codes from a Broadlink RM and adds the finished
|
|
48
|
+
accessory straight into your Homebridge config.json - a guided walkthrough
|
|
49
|
+
per accessory type, same idea as the learn-broadlink-rm4-codes project but
|
|
50
|
+
built in, and it writes the config for you.
|
|
51
|
+
|
|
52
|
+
A backup is made once per session, before anything is written, saved
|
|
53
|
+
alongside your config as config.json.backup.
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--config <path> Path to config.json (defaults to ./config.json)
|
|
57
|
+
-h, --help Show this help message
|
|
58
|
+
`;
|
|
59
|
+
// Set only for the duration of an in-flight learnIrCode/learnRfCode call, so
|
|
60
|
+
// Ctrl-C can abort it cleanly instead of the process just dying mid-learn.
|
|
61
|
+
let activeAbortController = null;
|
|
62
|
+
async function askNonEmpty(prompt) {
|
|
63
|
+
for (;;) {
|
|
64
|
+
const answer = (await (0, terminal_1.readLine)(prompt)).trim();
|
|
65
|
+
if (answer) {
|
|
66
|
+
return answer;
|
|
67
|
+
}
|
|
68
|
+
console.log('This field is required.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function pickRmDevice(rmDevices) {
|
|
72
|
+
console.log('\nWhich RM device?');
|
|
73
|
+
rmDevices.forEach((device, index) => console.log(` ${index + 1}. ${device.name} (${device.ip})`));
|
|
74
|
+
const choices = rmDevices.map((_, index) => String(index + 1));
|
|
75
|
+
const choice = await (0, terminal_1.readKey)('Choice: ', choices);
|
|
76
|
+
return rmDevices[Number(choice) - 1];
|
|
77
|
+
}
|
|
78
|
+
// Connects (or confirms the cached connection) up front, so "press the
|
|
79
|
+
// button now" prompts never appear before the RM is actually ready to
|
|
80
|
+
// receive a learning command.
|
|
81
|
+
async function connectToDevice(client, ip) {
|
|
82
|
+
console.log(`\nConnecting to ${ip}...`);
|
|
83
|
+
try {
|
|
84
|
+
await client.connect(ip);
|
|
85
|
+
console.log('Connected.');
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
console.log(`Failed to connect: ${message}`);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function askFrequency() {
|
|
95
|
+
for (;;) {
|
|
96
|
+
const answer = (await (0, terminal_1.readLine)(`\nRF frequency in MHz (Enter for ${DEFAULT_RF_FREQUENCY_MHZ}): `)).trim();
|
|
97
|
+
if (!answer) {
|
|
98
|
+
return DEFAULT_RF_FREQUENCY_MHZ;
|
|
99
|
+
}
|
|
100
|
+
const frequency = Number(answer);
|
|
101
|
+
if (Number.isFinite(frequency) && frequency > 0) {
|
|
102
|
+
return frequency;
|
|
103
|
+
}
|
|
104
|
+
console.log(`Enter a positive number, e.g. ${DEFAULT_RF_FREQUENCY_MHZ}.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function askSignalSettings() {
|
|
108
|
+
const choice = await (0, terminal_1.readKey)('\nIs the remote for this accessory IR or RF? [I/r]: ', ['', 'i', 'r']);
|
|
109
|
+
if (choice === 'r') {
|
|
110
|
+
const frequencyMHz = await askFrequency();
|
|
111
|
+
return { signalType: 'rf', frequencyMHz };
|
|
112
|
+
}
|
|
113
|
+
return { signalType: 'ir' };
|
|
114
|
+
}
|
|
115
|
+
async function runLearnLoop(client, ip, settings, label) {
|
|
116
|
+
for (;;) {
|
|
117
|
+
console.log(settings.signalType === 'rf'
|
|
118
|
+
? `\nLearning "${label}" - press the button now (${settings.frequencyMHz} MHz)...`
|
|
119
|
+
: `\nLearning "${label}" - press the button now...`);
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
activeAbortController = controller;
|
|
122
|
+
let hex;
|
|
123
|
+
try {
|
|
124
|
+
hex = settings.signalType === 'rf'
|
|
125
|
+
? await client.learnRfCode(ip, settings.frequencyMHz, controller.signal)
|
|
126
|
+
: await client.learnIrCode(ip, controller.signal);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
activeAbortController = null;
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
if (message === 'Cancelled') {
|
|
132
|
+
return { status: 'cancelled' };
|
|
133
|
+
}
|
|
134
|
+
console.log(`Failed: ${message}`);
|
|
135
|
+
const retry = await (0, terminal_1.readKey)('Enter to retry, or "q" to cancel this accessory: ', ['', 'q']);
|
|
136
|
+
if (retry === 'q') {
|
|
137
|
+
return { status: 'cancelled' };
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
activeAbortController = null;
|
|
142
|
+
console.log(`Captured: ${hex}`);
|
|
143
|
+
const confirm = await (0, terminal_1.readKey)('Enter to keep, "r" to retry, or "q" to cancel this accessory: ', ['', 'r', 'q']);
|
|
144
|
+
if (confirm === 'r') {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (confirm === 'q') {
|
|
148
|
+
return { status: 'cancelled' };
|
|
149
|
+
}
|
|
150
|
+
return { status: 'learned', hex };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function learnRequiredSignal(client, ip, settings, label) {
|
|
154
|
+
return runLearnLoop(client, ip, settings, label);
|
|
155
|
+
}
|
|
156
|
+
async function learnOptionalSignal(client, ip, settings, label) {
|
|
157
|
+
const choice = await (0, terminal_1.readKey)(`\nLearn "${label}"? Enter to learn it, "s" to skip, or "q" to cancel this accessory: `, ['', 's', 'q']);
|
|
158
|
+
if (choice === 's') {
|
|
159
|
+
return { status: 'skipped' };
|
|
160
|
+
}
|
|
161
|
+
if (choice === 'q') {
|
|
162
|
+
return { status: 'cancelled' };
|
|
163
|
+
}
|
|
164
|
+
return runLearnLoop(client, ip, settings, label);
|
|
165
|
+
}
|
|
166
|
+
async function learnBasicAccessory(client, rmDevices, pendingItems) {
|
|
167
|
+
const rmDevice = await pickRmDevice(rmDevices);
|
|
168
|
+
if (!(await connectToDevice(client, rmDevice.ip))) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const name = await askNonEmpty('\nName for this accessory: ');
|
|
172
|
+
const settings = await askSignalSettings();
|
|
173
|
+
const powerOn = await learnRequiredSignal(client, rmDevice.ip, settings, 'Power On');
|
|
174
|
+
if (powerOn.status === 'cancelled') {
|
|
175
|
+
console.log('Cancelled - nothing saved for this accessory.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const powerOff = await learnOptionalSignal(client, rmDevice.ip, settings, 'Power Off (skip to reuse Power On)');
|
|
179
|
+
if (powerOff.status === 'cancelled') {
|
|
180
|
+
console.log('Cancelled - nothing saved for this accessory.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const item = {
|
|
184
|
+
name,
|
|
185
|
+
rmDevice: rmDevice.name,
|
|
186
|
+
accessoryType: 'light',
|
|
187
|
+
powerOnCode: powerOn.hex,
|
|
188
|
+
};
|
|
189
|
+
if (powerOff.status === 'learned') {
|
|
190
|
+
item.powerOffCode = powerOff.hex;
|
|
191
|
+
}
|
|
192
|
+
pendingItems.push({ type: 'accessories', item });
|
|
193
|
+
console.log(`\n"${name}" queued to be saved.`);
|
|
194
|
+
}
|
|
195
|
+
async function learnTv(client, rmDevices, pendingItems) {
|
|
196
|
+
const rmDevice = await pickRmDevice(rmDevices);
|
|
197
|
+
if (!(await connectToDevice(client, rmDevice.ip))) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const name = await askNonEmpty('\nName for this TV: ');
|
|
201
|
+
const settings = await askSignalSettings();
|
|
202
|
+
const powerOn = await learnRequiredSignal(client, rmDevice.ip, settings, 'Power On');
|
|
203
|
+
if (powerOn.status === 'cancelled') {
|
|
204
|
+
console.log('Cancelled - nothing saved for this TV.');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const item = {
|
|
208
|
+
name,
|
|
209
|
+
rmDevice: rmDevice.name,
|
|
210
|
+
powerOnCode: powerOn.hex,
|
|
211
|
+
};
|
|
212
|
+
const optionalSignals = [
|
|
213
|
+
['Power Off (skip to reuse Power On)', 'powerOffCode'],
|
|
214
|
+
['Volume Up', 'volumeUpCode'],
|
|
215
|
+
['Volume Down', 'volumeDownCode'],
|
|
216
|
+
['Mute', 'muteCode'],
|
|
217
|
+
['Arrow Up', 'arrowUpCode'],
|
|
218
|
+
['Arrow Down', 'arrowDownCode'],
|
|
219
|
+
['Arrow Left', 'arrowLeftCode'],
|
|
220
|
+
['Arrow Right', 'arrowRightCode'],
|
|
221
|
+
['Select', 'selectCode'],
|
|
222
|
+
['Info', 'infoCode'],
|
|
223
|
+
['Back', 'backCode'],
|
|
224
|
+
['Exit', 'exitCode'],
|
|
225
|
+
];
|
|
226
|
+
for (const [label, field] of optionalSignals) {
|
|
227
|
+
const result = await learnOptionalSignal(client, rmDevice.ip, settings, label);
|
|
228
|
+
if (result.status === 'cancelled') {
|
|
229
|
+
console.log('Cancelled - nothing saved for this TV.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (result.status === 'learned') {
|
|
233
|
+
item[field] = result.hex;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
pendingItems.push({ type: 'tvs', item });
|
|
237
|
+
console.log(`\n"${name}" queued to be saved.`);
|
|
238
|
+
}
|
|
239
|
+
async function askStepCount() {
|
|
240
|
+
for (;;) {
|
|
241
|
+
const answer = (await (0, terminal_1.readLine)('\nHow many brightness steps, including 100% (e.g. 5 for 20/40/60/80/100)? Minimum 2: ')).trim();
|
|
242
|
+
const steps = Number(answer);
|
|
243
|
+
if (Number.isInteger(steps) && steps >= 2) {
|
|
244
|
+
return steps;
|
|
245
|
+
}
|
|
246
|
+
console.log('Enter a whole number of 2 or more.');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function learnDimmer(client, rmDevices, pendingItems) {
|
|
250
|
+
const rmDevice = await pickRmDevice(rmDevices);
|
|
251
|
+
if (!(await connectToDevice(client, rmDevice.ip))) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const name = await askNonEmpty('\nName for this dimmer light: ');
|
|
255
|
+
const settings = await askSignalSettings();
|
|
256
|
+
const steps = await askStepCount();
|
|
257
|
+
const requiredSignals = [
|
|
258
|
+
['Power On', 'powerOnCode'],
|
|
259
|
+
['Power Off', 'powerOffCode'],
|
|
260
|
+
];
|
|
261
|
+
const codes = {};
|
|
262
|
+
for (const [label, field] of requiredSignals) {
|
|
263
|
+
const result = await learnRequiredSignal(client, rmDevice.ip, settings, label);
|
|
264
|
+
if (result.status === 'cancelled') {
|
|
265
|
+
console.log('Cancelled - nothing saved for this dimmer light.');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
codes[field] = result.hex;
|
|
269
|
+
}
|
|
270
|
+
const zero = await learnRequiredSignal(client, rmDevice.ip, settings, 'Brightness 0%');
|
|
271
|
+
if (zero.status === 'cancelled') {
|
|
272
|
+
console.log('Cancelled - nothing saved for this dimmer light.');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
codes.zeroPercentCode = zero.hex;
|
|
276
|
+
const levels = [];
|
|
277
|
+
for (let i = 1; i < steps; i++) {
|
|
278
|
+
const percent = Math.round((i * 100) / steps);
|
|
279
|
+
const result = await learnRequiredSignal(client, rmDevice.ip, settings, `Brightness ${percent}%`);
|
|
280
|
+
if (result.status === 'cancelled') {
|
|
281
|
+
console.log('Cancelled - nothing saved for this dimmer light.');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
levels.push({ level: percent, code: result.hex });
|
|
285
|
+
}
|
|
286
|
+
const hundred = await learnRequiredSignal(client, rmDevice.ip, settings, 'Brightness 100%');
|
|
287
|
+
if (hundred.status === 'cancelled') {
|
|
288
|
+
console.log('Cancelled - nothing saved for this dimmer light.');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
codes.hundredPercentCode = hundred.hex;
|
|
292
|
+
const item = {
|
|
293
|
+
name,
|
|
294
|
+
rmDevice: rmDevice.name,
|
|
295
|
+
powerOnCode: codes.powerOnCode,
|
|
296
|
+
powerOffCode: codes.powerOffCode,
|
|
297
|
+
zeroPercentCode: codes.zeroPercentCode,
|
|
298
|
+
hundredPercentCode: codes.hundredPercentCode,
|
|
299
|
+
levels,
|
|
300
|
+
};
|
|
301
|
+
pendingItems.push({ type: 'dimmers', item });
|
|
302
|
+
console.log(`\n"${name}" queued to be saved.`);
|
|
303
|
+
}
|
|
304
|
+
function printFallback(pendingItems) {
|
|
305
|
+
console.error('\nHere is what would have been added - add it manually if needed:');
|
|
306
|
+
console.error(JSON.stringify(pendingItems.map((pending) => pending.item), null, 2));
|
|
307
|
+
}
|
|
308
|
+
function finishAndExit(configPath, pendingItems) {
|
|
309
|
+
if (pendingItems.length === 0) {
|
|
310
|
+
console.log('\nNothing learned this session - exiting.');
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}
|
|
313
|
+
let freshConfig;
|
|
314
|
+
try {
|
|
315
|
+
freshConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
console.error(`\nCould not re-read config.json to save: ${error.message}`);
|
|
319
|
+
printFallback(pendingItems);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
const platform = (0, configFile_1.findPlatformBlock)(freshConfig);
|
|
323
|
+
if (!platform) {
|
|
324
|
+
console.error(`\nCould not find the "${settings_1.PLATFORM_NAME}" platform in config.json anymore - not writing, to avoid losing changes.`);
|
|
325
|
+
printFallback(pendingItems);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const platformRecord = platform;
|
|
329
|
+
for (const pending of pendingItems) {
|
|
330
|
+
const list = platformRecord[pending.type] ?? [];
|
|
331
|
+
list.push(pending.item);
|
|
332
|
+
platformRecord[pending.type] = list;
|
|
333
|
+
}
|
|
334
|
+
(0, configFile_1.writeConfigAtomically)(configPath, freshConfig);
|
|
335
|
+
console.log(`\nSaved ${pendingItems.length} item(s) to ${configPath}. Restart Homebridge to pick up the changes.`);
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
async function main() {
|
|
339
|
+
const args = process.argv.slice(2);
|
|
340
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
341
|
+
console.log(HELP);
|
|
342
|
+
process.exit(0);
|
|
343
|
+
}
|
|
344
|
+
const configFlagIndex = args.indexOf('--config');
|
|
345
|
+
const explicitConfigPath = configFlagIndex !== -1 ? args[configFlagIndex + 1] : undefined;
|
|
346
|
+
let configPath;
|
|
347
|
+
try {
|
|
348
|
+
configPath = (0, configFile_1.findConfigPath)(explicitConfigPath);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
console.error(error.message);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
355
|
+
const platform = (0, configFile_1.findPlatformBlock)(config);
|
|
356
|
+
if (!platform || !platform.rmDevices?.length) {
|
|
357
|
+
console.error(`Could not find a configured "${settings_1.PLATFORM_NAME}" platform with at least one RM device in ${configPath}.\n` +
|
|
358
|
+
'Configure at least one RM device in the Homebridge Config UI X plugin settings first.');
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
(0, configFile_1.backupConfig)(configPath);
|
|
362
|
+
console.log(`Backed up config.json to ${configPath}.backup`);
|
|
363
|
+
const client = new broadlinkClient_1.BroadlinkClient((0, cli_1.createConsoleLogger)());
|
|
364
|
+
const pendingItems = [];
|
|
365
|
+
(0, terminal_1.initTerminal)();
|
|
366
|
+
(0, terminal_1.setSigintHandler)(() => {
|
|
367
|
+
console.log('\n\nCtrl-C - stopping.');
|
|
368
|
+
activeAbortController?.abort();
|
|
369
|
+
finishAndExit(configPath, pendingItems);
|
|
370
|
+
});
|
|
371
|
+
for (;;) {
|
|
372
|
+
console.log('\nWhat would you like to learn?');
|
|
373
|
+
console.log(' 1. Basic Accessory (light/switch/outlet/fan)');
|
|
374
|
+
console.log(' 2. TV');
|
|
375
|
+
console.log(' 3. Dimmer Light');
|
|
376
|
+
console.log(' q. Quit and save');
|
|
377
|
+
const choice = await (0, terminal_1.readKey)('Choice: ', ['1', '2', '3', 'q']);
|
|
378
|
+
if (choice === '1') {
|
|
379
|
+
await learnBasicAccessory(client, platform.rmDevices, pendingItems);
|
|
380
|
+
}
|
|
381
|
+
else if (choice === '2') {
|
|
382
|
+
await learnTv(client, platform.rmDevices, pendingItems);
|
|
383
|
+
}
|
|
384
|
+
else if (choice === '3') {
|
|
385
|
+
await learnDimmer(client, platform.rmDevices, pendingItems);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
finishAndExit(configPath, pendingItems);
|
|
392
|
+
}
|
|
393
|
+
if (require.main === module) {
|
|
394
|
+
main();
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=learner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learner.js","sourceRoot":"","sources":["../src/learner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type SigintHandler = () => void;
|
|
2
|
+
export declare function setSigintHandler(handler: SigintHandler): void;
|
|
3
|
+
export declare function initTerminal(): void;
|
|
4
|
+
export declare function readKey(promptText: string, choices: string[]): Promise<string>;
|
|
5
|
+
export declare function readLine(promptText: string): Promise<string>;
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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.setSigintHandler = setSigintHandler;
|
|
37
|
+
exports.initTerminal = initTerminal;
|
|
38
|
+
exports.readKey = readKey;
|
|
39
|
+
exports.readLine = readLine;
|
|
40
|
+
const readline = __importStar(require("readline"));
|
|
41
|
+
let sigintHandler = null;
|
|
42
|
+
function setSigintHandler(handler) {
|
|
43
|
+
sigintHandler = handler;
|
|
44
|
+
}
|
|
45
|
+
function initTerminal() {
|
|
46
|
+
readline.emitKeypressEvents(process.stdin);
|
|
47
|
+
if (process.stdin.isTTY) {
|
|
48
|
+
process.stdin.setRawMode(true);
|
|
49
|
+
}
|
|
50
|
+
process.stdin.resume();
|
|
51
|
+
process.stdin.setEncoding('utf8');
|
|
52
|
+
}
|
|
53
|
+
function isCtrlC(str, key) {
|
|
54
|
+
return Boolean(key?.ctrl && key.name === 'c') || str === '';
|
|
55
|
+
}
|
|
56
|
+
function isEnter(key) {
|
|
57
|
+
return key?.name === 'return' || key?.name === 'enter';
|
|
58
|
+
}
|
|
59
|
+
// Resolves as soon as a keystroke matches one of `choices` - no Enter
|
|
60
|
+
// needed. Enter itself is normalized to ''.
|
|
61
|
+
function readKey(promptText, choices) {
|
|
62
|
+
process.stdout.write(promptText);
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
66
|
+
};
|
|
67
|
+
const onKeypress = (str, key) => {
|
|
68
|
+
if (isCtrlC(str, key)) {
|
|
69
|
+
cleanup();
|
|
70
|
+
sigintHandler?.();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const normalized = isEnter(key) ? '' : (str ?? '').toLowerCase();
|
|
74
|
+
if (choices.includes(normalized)) {
|
|
75
|
+
cleanup();
|
|
76
|
+
console.log(normalized);
|
|
77
|
+
resolve(normalized);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
process.stdin.on('keypress', onKeypress);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// A minimal hand-rolled line editor (echo + backspace) since raw mode
|
|
84
|
+
// doesn't echo or line-buffer anything itself.
|
|
85
|
+
function readLine(promptText) {
|
|
86
|
+
process.stdout.write(promptText);
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let buffer = '';
|
|
89
|
+
const cleanup = () => {
|
|
90
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
91
|
+
};
|
|
92
|
+
const onKeypress = (str, key) => {
|
|
93
|
+
if (isCtrlC(str, key)) {
|
|
94
|
+
cleanup();
|
|
95
|
+
sigintHandler?.();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (isEnter(key)) {
|
|
99
|
+
cleanup();
|
|
100
|
+
process.stdout.write('\n');
|
|
101
|
+
resolve(buffer);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (key?.name === 'backspace') {
|
|
105
|
+
if (buffer.length > 0) {
|
|
106
|
+
buffer = buffer.slice(0, -1);
|
|
107
|
+
process.stdout.write('\b \b');
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (str && !key?.ctrl && !key?.meta) {
|
|
112
|
+
buffer += str;
|
|
113
|
+
process.stdout.write(str);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
process.stdin.on('keypress', onKeypress);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-broadlink-rm-blaster",
|
|
3
3
|
"displayName": "Broadlink RM Blaster",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"description": "Blast RF and IR signals from Broadlink RM devices using Homebridge",
|
|
6
6
|
"author": "Luud Jacobs",
|
|
7
7
|
"license": "MIT",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"main": "dist/index.js",
|
|
21
21
|
"bin": {
|
|
22
|
-
"broadlink-rm-blaster": "dist/cli.js"
|
|
22
|
+
"broadlink-rm-blaster": "dist/cli.js",
|
|
23
|
+
"broadlink-rm-learner": "dist/learner.js"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist",
|