homebridge-broadlink-rm-blaster 1.0.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.
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DimmerAccessory = void 0;
4
+ exports.getEffectiveMaxPercent = getEffectiveMaxPercent;
5
+ exports.remapToPhysicalPercent = remapToPhysicalPercent;
6
+ exports.findNearestLevel = findNearestLevel;
7
+ exports.resolveBrightnessCode = resolveBrightnessCode;
8
+ exports.resolvePowerOnLevel = resolvePowerOnLevel;
9
+ const DEFAULT_DEBOUNCE_SECONDS = 0.5;
10
+ function candidateLevels(config) {
11
+ return [
12
+ { percent: 0, code: config.zeroPercentCode },
13
+ ...config.levels.map((level) => ({ percent: level.level, code: level.code })),
14
+ { percent: 100, code: config.hundredPercentCode },
15
+ ];
16
+ }
17
+ function getEffectiveMaxPercent(config) {
18
+ return (config.useMaxBrightnessLevel && config.maxBrightnessLevel !== undefined)
19
+ ? config.maxBrightnessLevel
20
+ : 100;
21
+ }
22
+ function remapToPhysicalPercent(requestedPercent, config) {
23
+ return (requestedPercent * getEffectiveMaxPercent(config)) / 100;
24
+ }
25
+ function findNearestLevel(config, physicalPercent) {
26
+ const candidates = candidateLevels(config);
27
+ return candidates.reduce((closest, candidate) => Math.abs(candidate.percent - physicalPercent) < Math.abs(closest.percent - physicalPercent) ? candidate : closest);
28
+ }
29
+ // resolvePowerOnLevel's own `percent` is deliberately the admin's configured
30
+ // target, not the actual signal's percent (see its comment below) - this
31
+ // looks the real candidate back up by its code so logs can show what was
32
+ // actually sent, the same way setBrightness's log already does.
33
+ function findLevelByCode(config, code) {
34
+ return candidateLevels(config).find((level) => level.code === code);
35
+ }
36
+ // Translates a logical 0-100 request into a physical percent (via the
37
+ // configured max, if any) before matching it to a configured signal. Used for
38
+ // live slider requests and for the "default brightness" power-on tier below,
39
+ // since both are logical values on the same 0-100 scale.
40
+ function resolveBrightnessCode(config, requestedPercent) {
41
+ return findNearestLevel(config, remapToPhysicalPercent(requestedPercent, config));
42
+ }
43
+ // Default brightness is a logical 0-100 target, same scale as a live slider
44
+ // request, so it goes through the same remap-then-nearest-match pipeline -
45
+ // otherwise a configured default could physically exceed the configured max,
46
+ // defeating the point of the cap. Max brightness itself is the ceiling, so it
47
+ // resolves directly rather than being remapped through itself.
48
+ function resolvePowerOnLevel(config, lastKnown) {
49
+ if (config.useLastKnownBrightness && lastKnown) {
50
+ return lastKnown;
51
+ }
52
+ if (config.useDefaultBrightnessLevel && config.defaultBrightnessLevel !== undefined) {
53
+ const percent = config.defaultBrightnessLevel;
54
+ return { percent, code: resolveBrightnessCode(config, percent).code };
55
+ }
56
+ if (config.useMaxBrightnessLevel && config.maxBrightnessLevel !== undefined) {
57
+ const percent = config.maxBrightnessLevel;
58
+ return { percent, code: findNearestLevel(config, percent).code };
59
+ }
60
+ // A guaranteed 100% candidate always exists (hundredPercentCode is
61
+ // required), so "highest" is now definitionally the true 100% signal
62
+ // rather than whatever happened to be the highest configured level.
63
+ return { percent: 100, code: config.hundredPercentCode };
64
+ }
65
+ class DimmerAccessory {
66
+ platform;
67
+ accessory;
68
+ config;
69
+ ip;
70
+ brightnessDebounceTimer;
71
+ // Bumped by any explicit brightness/off action (setBrightness, turnOff), to
72
+ // let setOn recognize when its own resolved level has been superseded by a
73
+ // real request that arrived while its send was still in flight. See the
74
+ // v0.7.3-v0.7.7 history in project memory for why this exists and why it's
75
+ // deliberately narrow (only the async gap of setOn's own send) rather than
76
+ // a longer deferred window.
77
+ //
78
+ // As of this version, setOn/turnOff no longer send the dedicated
79
+ // powerOnCode/powerOffCode signals at all - only the resolved brightness
80
+ // level (on) or zeroPercentCode (off), since a level signal alone has been
81
+ // confirmed to already power the device on. This is an experiment to see
82
+ // whether dropping the extra dedicated signal reduces the receiver
83
+ // confusion/unresponsiveness seen in earlier testing. powerOnCode/
84
+ // powerOffCode remain required config fields (unused for now) so this can
85
+ // be reverted without a config change if it doesn't help.
86
+ brightnessActionId = 0;
87
+ // Every send() call for this dimmer is now a level-setting signal (0%, a
88
+ // configured level, or 100% - powerOnCode/powerOffCode are no longer sent
89
+ // as of v0.7.8), so tracking the last one actually sent lets send() skip a
90
+ // no-op resend outright. This specifically targets a debounce edge case:
91
+ // if two onSet(Brightness) calls that both nearest-match to the same
92
+ // signal arrive further apart than debounceSeconds, each completes its
93
+ // own independent debounce cycle and both would otherwise fire - sending
94
+ // the identical signal to the receiver twice, which earlier testing
95
+ // showed can push it into unwanted auto-dim/cycling behavior.
96
+ lastSentCode;
97
+ constructor(platform, accessory, config, ip) {
98
+ this.platform = platform;
99
+ this.accessory = accessory;
100
+ this.config = config;
101
+ this.ip = ip;
102
+ const service = this.accessory.getService(this.platform.Service.Lightbulb)
103
+ ?? this.accessory.addService(this.platform.Service.Lightbulb);
104
+ service.setCharacteristic(this.platform.Characteristic.Name, this.config.name);
105
+ service.getCharacteristic(this.platform.Characteristic.On)
106
+ .onGet(() => this.getOn())
107
+ .onSet((value) => this.setOn(value));
108
+ service.getCharacteristic(this.platform.Characteristic.Brightness)
109
+ .onGet(() => this.getBrightness())
110
+ .onSet((value) => this.setBrightness(value));
111
+ }
112
+ // Same assumed-state approach as BasicAccessory: a blaster has no feedback,
113
+ // so on/brightness are whatever we last set them to, cached in context.
114
+ getOn() {
115
+ return Boolean(this.accessory.context.on);
116
+ }
117
+ getBrightness() {
118
+ return Number(this.accessory.context.brightnessPercent ?? 0);
119
+ }
120
+ clearBrightnessDebounce() {
121
+ if (this.brightnessDebounceTimer) {
122
+ clearTimeout(this.brightnessDebounceTimer);
123
+ this.brightnessDebounceTimer = undefined;
124
+ }
125
+ }
126
+ async turnOff() {
127
+ this.clearBrightnessDebounce();
128
+ this.brightnessActionId++;
129
+ await this.send(this.config.zeroPercentCode, 'Off (Brightness 0%)');
130
+ this.accessory.context.on = false;
131
+ }
132
+ async setOn(value) {
133
+ const on = Boolean(value);
134
+ if (!on) {
135
+ await this.turnOff();
136
+ return;
137
+ }
138
+ this.clearBrightnessDebounce();
139
+ const lastKnown = this.accessory.context.lastKnownLevel;
140
+ const resolved = resolvePowerOnLevel(this.config, lastKnown);
141
+ const effectiveMax = getEffectiveMaxPercent(this.config);
142
+ const nearestPercent = findLevelByCode(this.config, resolved.code)?.percent ?? resolved.percent;
143
+ const myActionId = ++this.brightnessActionId;
144
+ // The resolved level's own signal is the only thing sent to turn the
145
+ // light on - a dedicated powerOnCode is no longer sent (see the
146
+ // comment on brightnessActionId).
147
+ await this.send(resolved.code, `Brightness ${nearestPercent}% (requested: ${resolved.percent}% of ${effectiveMax}%)`);
148
+ this.accessory.context.on = true;
149
+ if (this.brightnessActionId !== myActionId) {
150
+ // A real Brightness request (e.g. dragging the slider straight up
151
+ // from off) arrived while the send above was still in flight and has
152
+ // already taken over - don't display this guessed level on top of it.
153
+ return;
154
+ }
155
+ this.accessory.context.brightnessPercent = resolved.percent;
156
+ this.accessory.context.lastKnownLevel = resolved;
157
+ this.accessory.getService(this.platform.Service.Lightbulb)
158
+ ?.updateCharacteristic(this.platform.Characteristic.Brightness, resolved.percent);
159
+ }
160
+ // Debounced: a slider drag in the Home app fires many rapid onSet calls, so
161
+ // the actual send() is deferred until debounceSeconds of silence, reset on
162
+ // every call. Display state (and "last known brightness") updates
163
+ // immediately/optimistically so the slider itself tracks the drag smoothly
164
+ // and doesn't lag behind while the send is pending.
165
+ setBrightness(value) {
166
+ this.brightnessActionId++;
167
+ const requestedPercent = Number(value);
168
+ const resolved = resolveBrightnessCode(this.config, requestedPercent);
169
+ const effectiveMax = getEffectiveMaxPercent(this.config);
170
+ this.accessory.context.on = true;
171
+ this.accessory.context.brightnessPercent = requestedPercent;
172
+ this.accessory.context.lastKnownLevel = resolved;
173
+ this.clearBrightnessDebounce();
174
+ const debounceMs = (this.config.debounceSeconds ?? DEFAULT_DEBOUNCE_SECONDS) * 1000;
175
+ this.brightnessDebounceTimer = setTimeout(() => {
176
+ this.brightnessDebounceTimer = undefined;
177
+ this.send(resolved.code, `Brightness ${resolved.percent}% (requested: ${requestedPercent}% of ${effectiveMax}%)`)
178
+ .catch(() => {
179
+ // send() already logs the failure; there's no live characteristic
180
+ // write left to report it back to by the time this timer fires.
181
+ });
182
+ }, debounceMs);
183
+ }
184
+ async send(code, signalName) {
185
+ if (code === this.lastSentCode) {
186
+ return;
187
+ }
188
+ try {
189
+ await this.platform.broadlinkClient.sendCode(this.ip, code);
190
+ this.lastSentCode = code;
191
+ this.platform.log.info(`Sent ${signalName} to ${this.config.name}`);
192
+ }
193
+ catch (error) {
194
+ this.platform.log.error(`Failed to send code for "${this.config.name}": ${error.message}`);
195
+ const { HapStatusError, HAPStatus } = this.platform.api.hap;
196
+ throw new HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
197
+ }
198
+ }
199
+ }
200
+ exports.DimmerAccessory = DimmerAccessory;
201
+ //# sourceMappingURL=dimmerAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dimmerAccessory.js","sourceRoot":"","sources":["../../src/accessories/dimmerAccessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { PlatformAccessory } from 'homebridge';
2
+ import type { BroadlinkRMBlasterPlatform } from '../platform';
3
+ export declare class TemperatureHumiditySensorAccessory {
4
+ private readonly platform;
5
+ private readonly accessory;
6
+ private readonly ip;
7
+ private readonly name;
8
+ constructor(platform: BroadlinkRMBlasterPlatform, accessory: PlatformAccessory, ip: string, name: string);
9
+ private getTemperature;
10
+ private getHumidity;
11
+ private throwNoResponse;
12
+ private poll;
13
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TemperatureHumiditySensorAccessory = void 0;
4
+ const POLL_INTERVAL_MS = 60_000;
5
+ class TemperatureHumiditySensorAccessory {
6
+ platform;
7
+ accessory;
8
+ ip;
9
+ name;
10
+ constructor(platform, accessory, ip, name) {
11
+ this.platform = platform;
12
+ this.accessory = accessory;
13
+ this.ip = ip;
14
+ this.name = name;
15
+ const temperatureService = this.accessory.getService(this.platform.Service.TemperatureSensor)
16
+ ?? this.accessory.addService(this.platform.Service.TemperatureSensor);
17
+ temperatureService.setCharacteristic(this.platform.Characteristic.Name, this.name);
18
+ temperatureService.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
19
+ .onGet(() => this.getTemperature());
20
+ const humidityService = this.accessory.getService(this.platform.Service.HumiditySensor)
21
+ ?? this.accessory.addService(this.platform.Service.HumiditySensor);
22
+ humidityService.setCharacteristic(this.platform.Characteristic.Name, this.name);
23
+ humidityService.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
24
+ .onGet(() => this.getHumidity());
25
+ // Not all RM units actually report real sensor data ("if available" in
26
+ // the todo) - an immediate poll plus a recurring one lets a supported unit
27
+ // start reporting quickly, while an unsupported one just keeps failing
28
+ // quietly and onGet honestly reports "no response" rather than a fake 0.
29
+ this.poll();
30
+ setInterval(() => this.poll(), POLL_INTERVAL_MS);
31
+ }
32
+ getTemperature() {
33
+ const temperature = this.accessory.context.temperature;
34
+ if (temperature === undefined) {
35
+ this.throwNoResponse();
36
+ }
37
+ return temperature;
38
+ }
39
+ getHumidity() {
40
+ const humidity = this.accessory.context.humidity;
41
+ if (humidity === undefined) {
42
+ this.throwNoResponse();
43
+ }
44
+ return humidity;
45
+ }
46
+ throwNoResponse() {
47
+ const { HapStatusError, HAPStatus } = this.platform.api.hap;
48
+ throw new HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
49
+ }
50
+ async poll() {
51
+ try {
52
+ const { temperature, humidity } = await this.platform.broadlinkClient.readTemperatureHumidity(this.ip);
53
+ this.accessory.context.temperature = temperature;
54
+ this.accessory.context.humidity = humidity;
55
+ this.accessory.getService(this.platform.Service.TemperatureSensor)
56
+ ?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, temperature);
57
+ this.accessory.getService(this.platform.Service.HumiditySensor)
58
+ ?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, humidity);
59
+ }
60
+ catch (error) {
61
+ this.platform.log.warn(`Failed to read temperature/humidity from ${this.ip}: ${error.message}`);
62
+ }
63
+ }
64
+ }
65
+ exports.TemperatureHumiditySensorAccessory = TemperatureHumiditySensorAccessory;
66
+ //# sourceMappingURL=temperatureHumiditySensorAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temperatureHumiditySensorAccessory.js","sourceRoot":"","sources":["../../src/accessories/temperatureHumiditySensorAccessory.ts"],"names":[],"mappings":";;;AAIA,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC,MAAa,kCAAkC;IAE1B;IACA;IACA;IACA;IAJnB,YACmB,QAAoC,EACpC,SAA4B,EAC5B,EAAU,EACV,IAAY;QAHZ,aAAQ,GAAR,QAAQ,CAA4B;QACpC,cAAS,GAAT,SAAS,CAAmB;QAC5B,OAAE,GAAF,EAAE,CAAQ;QACV,SAAI,GAAJ,IAAI,CAAQ;QAE7B,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC;eACxF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACxE,kBAAkB,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnF,kBAAkB,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,kBAAkB,CAAC;aAClF,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAEtC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;eAClF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACrE,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAC;aACpF,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAEnC,uEAAuE;QACvE,2EAA2E;QAC3E,uEAAuE;QACvE,yEAAyE;QACzE,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IAEO,cAAc;QACpB,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC;QACvD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IAEO,WAAW;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC;QACjD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,eAAe;QACrB,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAC5D,MAAM,IAAI,cAAc,sDAAyC,CAAC;IACpE,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC;YACH,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEvG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAE3C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC;gBAChE,EAAE,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;YACvF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;gBAC7D,EAAE,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,4CAA4C,IAAI,CAAC,EAAE,KAAM,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;CACF;AA/DD,gFA+DC"}
@@ -0,0 +1,22 @@
1
+ import type { PlatformAccessory } from 'homebridge';
2
+ import type { BroadlinkRMBlasterPlatform } from '../platform';
3
+ import type { TvAccessoryConfig } from '../configTypes';
4
+ export interface RemoteKeyResolution {
5
+ signalName: string;
6
+ code?: string;
7
+ }
8
+ export declare function resolveRemoteKeyCode(config: TvAccessoryConfig, remoteKey: number): RemoteKeyResolution | undefined;
9
+ export declare class TvAccessory {
10
+ private readonly platform;
11
+ private readonly accessory;
12
+ private readonly config;
13
+ private readonly ip;
14
+ constructor(platform: BroadlinkRMBlasterPlatform, accessory: PlatformAccessory, config: TvAccessoryConfig, ip: string);
15
+ private getActive;
16
+ private getMuted;
17
+ private setActive;
18
+ private handleRemoteKey;
19
+ private setMute;
20
+ private handleVolumeSelector;
21
+ private send;
22
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TvAccessory = void 0;
4
+ exports.resolveRemoteKeyCode = resolveRemoteKeyCode;
5
+ const basicAccessory_1 = require("./basicAccessory");
6
+ // HAP's RemoteKey characteristic values (hap-nodejs CharacteristicDefinitions) -
7
+ // only the ones the todo asks for are mapped; REWIND/FAST_FORWARD/NEXT_TRACK/
8
+ // PREVIOUS_TRACK/PLAY_PAUSE (0-3, 11) have no configured signal and fall
9
+ // through to the default case.
10
+ const ARROW_UP = 4;
11
+ const ARROW_DOWN = 5;
12
+ const ARROW_LEFT = 6;
13
+ const ARROW_RIGHT = 7;
14
+ const SELECT = 8;
15
+ const BACK = 9;
16
+ const EXIT = 10;
17
+ const INFORMATION = 15;
18
+ function resolveRemoteKeyCode(config, remoteKey) {
19
+ switch (remoteKey) {
20
+ case ARROW_UP: return { signalName: 'Arrow Up', code: config.arrowUpCode };
21
+ case ARROW_DOWN: return { signalName: 'Arrow Down', code: config.arrowDownCode };
22
+ case ARROW_LEFT: return { signalName: 'Arrow Left', code: config.arrowLeftCode };
23
+ case ARROW_RIGHT: return { signalName: 'Arrow Right', code: config.arrowRightCode };
24
+ case SELECT: return { signalName: 'Select', code: config.selectCode };
25
+ case BACK: return { signalName: 'Back', code: config.backCode };
26
+ case EXIT: return { signalName: 'Exit', code: config.exitCode };
27
+ case INFORMATION: return { signalName: 'Info', code: config.infoCode };
28
+ default: return undefined;
29
+ }
30
+ }
31
+ const PLACEHOLDER_INPUT_IDENTIFIER = 1;
32
+ class TvAccessory {
33
+ platform;
34
+ accessory;
35
+ config;
36
+ ip;
37
+ constructor(platform, accessory, config, ip) {
38
+ this.platform = platform;
39
+ this.accessory = accessory;
40
+ this.config = config;
41
+ this.ip = ip;
42
+ accessory.category = 31 /* this.platform.api.hap.Categories.TELEVISION */;
43
+ const tvService = this.accessory.getService(this.platform.Service.Television)
44
+ ?? this.accessory.addService(this.platform.Service.Television);
45
+ tvService.setCharacteristic(this.platform.Characteristic.ConfiguredName, this.config.name);
46
+ tvService.setCharacteristic(this.platform.Characteristic.Name, this.config.name);
47
+ tvService.setCharacteristic(this.platform.Characteristic.SleepDiscoveryMode, this.platform.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE);
48
+ tvService.setPrimaryService(true);
49
+ tvService.getCharacteristic(this.platform.Characteristic.Active)
50
+ .onGet(() => this.getActive())
51
+ .onSet((value) => this.setActive(value));
52
+ // We don't have real inputs (channels/apps) to switch between - this
53
+ // characteristic and the placeholder InputSource below only exist
54
+ // because HomeKit requires both for a Television service to register
55
+ // and display as a usable remote at all.
56
+ tvService.getCharacteristic(this.platform.Characteristic.ActiveIdentifier)
57
+ .onGet(() => PLACEHOLDER_INPUT_IDENTIFIER)
58
+ .onSet(() => { });
59
+ tvService.getCharacteristic(this.platform.Characteristic.RemoteKey)
60
+ .onSet((value) => this.handleRemoteKey(value));
61
+ const inputService = this.accessory.getService(this.platform.Service.InputSource)
62
+ ?? this.accessory.addService(this.platform.Service.InputSource);
63
+ inputService.setCharacteristic(this.platform.Characteristic.Identifier, PLACEHOLDER_INPUT_IDENTIFIER);
64
+ inputService.setCharacteristic(this.platform.Characteristic.ConfiguredName, this.config.name);
65
+ inputService.setCharacteristic(this.platform.Characteristic.Name, this.config.name);
66
+ inputService.setCharacteristic(this.platform.Characteristic.IsConfigured, this.platform.Characteristic.IsConfigured.CONFIGURED);
67
+ inputService.setCharacteristic(this.platform.Characteristic.InputSourceType, this.platform.Characteristic.InputSourceType.OTHER);
68
+ inputService.setCharacteristic(this.platform.Characteristic.CurrentVisibilityState, this.platform.Characteristic.CurrentVisibilityState.SHOWN);
69
+ tvService.addLinkedService(inputService);
70
+ const speakerService = this.accessory.getService(this.platform.Service.TelevisionSpeaker)
71
+ ?? this.accessory.addService(this.platform.Service.TelevisionSpeaker);
72
+ speakerService.setCharacteristic(this.platform.Characteristic.VolumeControlType, this.platform.Characteristic.VolumeControlType.RELATIVE);
73
+ speakerService.getCharacteristic(this.platform.Characteristic.Mute)
74
+ .onGet(() => this.getMuted())
75
+ .onSet((value) => this.setMute(value));
76
+ speakerService.getCharacteristic(this.platform.Characteristic.VolumeSelector)
77
+ .onSet((value) => this.handleVolumeSelector(value));
78
+ tvService.addLinkedService(speakerService);
79
+ }
80
+ // Same assumed-state approach as the other accessories: a blaster has no
81
+ // feedback, so Active/Mute are whatever we last set them to.
82
+ getActive() {
83
+ return this.accessory.context.active
84
+ ? this.platform.Characteristic.Active.ACTIVE
85
+ : this.platform.Characteristic.Active.INACTIVE;
86
+ }
87
+ getMuted() {
88
+ return Boolean(this.accessory.context.muted);
89
+ }
90
+ async setActive(value) {
91
+ const on = value === this.platform.Characteristic.Active.ACTIVE;
92
+ const code = (0, basicAccessory_1.selectPowerCode)(this.config, on);
93
+ await this.send(code, on ? 'Power On' : 'Power Off');
94
+ this.accessory.context.active = on;
95
+ }
96
+ async handleRemoteKey(value) {
97
+ const resolved = resolveRemoteKeyCode(this.config, Number(value));
98
+ if (!resolved) {
99
+ return;
100
+ }
101
+ if (!resolved.code) {
102
+ this.platform.log.warn(`No ${resolved.signalName} signal configured for "${this.config.name}"`);
103
+ return;
104
+ }
105
+ await this.send(resolved.code, resolved.signalName);
106
+ }
107
+ async setMute(value) {
108
+ if (!this.config.muteCode) {
109
+ this.platform.log.warn(`No Mute signal configured for "${this.config.name}"`);
110
+ return;
111
+ }
112
+ // A remote's mute button is a single toggle signal, not distinct
113
+ // on/off signals, so the same code is sent regardless of direction.
114
+ await this.send(this.config.muteCode, 'Mute');
115
+ this.accessory.context.muted = Boolean(value);
116
+ }
117
+ async handleVolumeSelector(value) {
118
+ const increment = value === this.platform.Characteristic.VolumeSelector.INCREMENT;
119
+ const signalName = increment ? 'Volume Up' : 'Volume Down';
120
+ const code = increment ? this.config.volumeUpCode : this.config.volumeDownCode;
121
+ if (!code) {
122
+ this.platform.log.warn(`No ${signalName} signal configured for "${this.config.name}"`);
123
+ return;
124
+ }
125
+ await this.send(code, signalName);
126
+ }
127
+ async send(code, signalName) {
128
+ try {
129
+ await this.platform.broadlinkClient.sendCode(this.ip, code);
130
+ this.platform.log.info(`Sent ${signalName} to ${this.config.name}`);
131
+ }
132
+ catch (error) {
133
+ this.platform.log.error(`Failed to send code for "${this.config.name}": ${error.message}`);
134
+ const { HapStatusError, HAPStatus } = this.platform.api.hap;
135
+ throw new HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
136
+ }
137
+ }
138
+ }
139
+ exports.TvAccessory = TvAccessory;
140
+ //# sourceMappingURL=tvAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tvAccessory.js","sourceRoot":"","sources":["../../src/accessories/tvAccessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Logger } from 'homebridge';
2
+ export declare function parseHexCode(hexCode: string): Buffer;
3
+ export interface TemperatureHumidityReading {
4
+ temperature: number;
5
+ humidity: number;
6
+ }
7
+ export declare class BroadlinkClient {
8
+ private readonly log;
9
+ private readonly broadlink;
10
+ private readonly devices;
11
+ constructor(log: Logger);
12
+ private getDevice;
13
+ sendCode(ip: string, hexCode: string): Promise<void>;
14
+ readTemperatureHumidity(ip: string): Promise<TemperatureHumidityReading>;
15
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BroadlinkClient = void 0;
7
+ exports.parseHexCode = parseHexCode;
8
+ const kiwicam_broadlinkjs_rm_1 = __importDefault(require("kiwicam-broadlinkjs-rm"));
9
+ // Device type code the library reserves for a manually added RM (RF capable),
10
+ // used when connecting directly by IP instead of relying on UDP discovery.
11
+ const MANUAL_RM_DEVICE_TYPE = 0x2227;
12
+ const BROADLINK_PORT = 80;
13
+ const AUTH_TIMEOUT_MS = 10_000;
14
+ const READ_TIMEOUT_MS = 10_000;
15
+ function parseHexCode(hexCode) {
16
+ return Buffer.from(hexCode.replace(/\s+/g, ''), 'hex');
17
+ }
18
+ class BroadlinkClient {
19
+ log;
20
+ broadlink = new kiwicam_broadlinkjs_rm_1.default();
21
+ devices = new Map();
22
+ constructor(log) {
23
+ this.log = log;
24
+ }
25
+ getDevice(ip) {
26
+ let devicePromise = this.devices.get(ip);
27
+ if (devicePromise) {
28
+ return devicePromise;
29
+ }
30
+ devicePromise = new Promise((resolve, reject) => {
31
+ const timeout = setTimeout(() => {
32
+ reject(new Error(`Timed out authenticating with Broadlink RM at ${ip}`));
33
+ }, AUTH_TIMEOUT_MS);
34
+ const onReady = (device) => {
35
+ if (device.host.address !== ip) {
36
+ return;
37
+ }
38
+ clearTimeout(timeout);
39
+ this.broadlink.removeListener('deviceReady', onReady);
40
+ this.log.info(`Connected to Broadlink RM at ${ip}`);
41
+ resolve(device);
42
+ };
43
+ this.broadlink.on('deviceReady', onReady);
44
+ this.broadlink.addDevice({ address: ip, port: BROADLINK_PORT }, Buffer.alloc(6, 0), MANUAL_RM_DEVICE_TYPE);
45
+ });
46
+ devicePromise.catch(() => this.devices.delete(ip));
47
+ this.devices.set(ip, devicePromise);
48
+ return devicePromise;
49
+ }
50
+ async sendCode(ip, hexCode) {
51
+ const device = await this.getDevice(ip);
52
+ await device.sendData(parseHexCode(hexCode));
53
+ }
54
+ async readTemperatureHumidity(ip) {
55
+ const device = await this.getDevice(ip);
56
+ return new Promise((resolve, reject) => {
57
+ const timeout = setTimeout(() => {
58
+ device.removeListener('temperature', onTemperature);
59
+ reject(new Error(`Timed out reading temperature/humidity from ${ip}`));
60
+ }, READ_TIMEOUT_MS);
61
+ const onTemperature = (temperature, humidity) => {
62
+ clearTimeout(timeout);
63
+ device.removeListener('temperature', onTemperature);
64
+ resolve({ temperature, humidity });
65
+ };
66
+ device.on('temperature', onTemperature);
67
+ device.checkTemperature();
68
+ });
69
+ }
70
+ }
71
+ exports.BroadlinkClient = BroadlinkClient;
72
+ //# sourceMappingURL=broadlinkClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broadlinkClient.js","sourceRoot":"","sources":["../src/broadlinkClient.ts"],"names":[],"mappings":";;;;;;AAUA,oCAEC;AAZD,oFAA2D;AAG3D,8EAA8E;AAC9E,2EAA2E;AAC3E,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,SAAgB,YAAY,CAAC,OAAe;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;AACzD,CAAC;AAOD,MAAa,eAAe;IAIG;IAHZ,SAAS,GAAG,IAAI,gCAAS,EAAE,CAAC;IAC5B,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE9D,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAEpC,SAAS,CAAC,EAAU;QAC1B,IAAI,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,aAAa,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtD,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,MAAM,CAAC,IAAI,KAAK,CAAC,iDAAiD,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC,EAAE,eAAe,CAAC,CAAC;YAEpB,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE;gBACjC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;oBAC/B,OAAO;gBACT,CAAC;gBACD,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;gBACtD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;gBACpD,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC;YAEF,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;YAC1C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAC7G,CAAC,CAAC,CAAC;QAEH,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QACpC,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU,EAAE,OAAe;QACxC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,EAAU;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAExC,OAAO,IAAI,OAAO,CAA6B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACjE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;gBACpD,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,EAAE,EAAE,CAAC,CAAC,CAAC;YACzE,CAAC,EAAE,eAAe,CAAC,CAAC;YAEpB,MAAM,aAAa,GAAG,CAAC,WAAmB,EAAE,QAAgB,EAAE,EAAE;gBAC9D,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;gBACpD,OAAO,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC;YAEF,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;YACxC,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA5DD,0CA4DC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function isValidHexCode(hexCode: string): boolean;
package/dist/cli.js ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.isValidHexCode = isValidHexCode;
5
+ const broadlinkClient_1 = require("./broadlinkClient");
6
+ const USAGE = 'Usage: broadlink-rm-blaster <ip> <hexCode>';
7
+ const HELP = `${USAGE}
8
+
9
+ Sends a single hex code directly to a Broadlink RM, bypassing Homebridge/HomeKit
10
+ entirely (no debounce, no accessory state). Useful for isolating whether a given
11
+ signal behaves oddly on the physical receiver itself.
12
+
13
+ Arguments:
14
+ ip IP address of the Broadlink RM (e.g. 192.168.1.50)
15
+ hexCode Hex-encoded signal to send (e.g. 2600...), whitespace is ignored
16
+
17
+ Options:
18
+ -h, --help Show this help message
19
+ `;
20
+ function isValidHexCode(hexCode) {
21
+ const stripped = hexCode.replace(/\s+/g, '');
22
+ return stripped.length > 0 && stripped.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(stripped);
23
+ }
24
+ function createConsoleLogger() {
25
+ const noop = () => { };
26
+ return {
27
+ prefix: undefined,
28
+ info: (message, ...parameters) => console.log(message, ...parameters),
29
+ success: (message, ...parameters) => console.log(message, ...parameters),
30
+ warn: (message, ...parameters) => console.warn(message, ...parameters),
31
+ error: (message, ...parameters) => console.error(message, ...parameters),
32
+ debug: noop,
33
+ log: (level, message, ...parameters) => console.log(`[${level}]`, message, ...parameters),
34
+ };
35
+ }
36
+ async function main() {
37
+ const [ip, hexCode, ...rest] = process.argv.slice(2);
38
+ if (ip === '-h' || ip === '--help') {
39
+ console.log(HELP);
40
+ process.exit(0);
41
+ }
42
+ if (!ip || !hexCode) {
43
+ console.error(USAGE);
44
+ process.exit(1);
45
+ }
46
+ if (!isValidHexCode(hexCode)) {
47
+ console.error(`Invalid hex code: "${hexCode}"\n` +
48
+ 'Expected an even number of hex digits (0-9, a-f/A-F), optionally separated by whitespace.');
49
+ process.exit(1);
50
+ }
51
+ if (rest.length > 0) {
52
+ console.warn(`Ignoring extra argument(s): ${rest.join(' ')}`);
53
+ }
54
+ const client = new broadlinkClient_1.BroadlinkClient(createConsoleLogger());
55
+ try {
56
+ await client.sendCode(ip, hexCode);
57
+ console.log(`Sent ${hexCode} to ${ip}`);
58
+ process.exit(0);
59
+ }
60
+ catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ console.error(`Failed to send code: ${message}`);
63
+ process.exit(1);
64
+ }
65
+ }
66
+ if (require.main === module) {
67
+ main();
68
+ }
69
+ //# sourceMappingURL=cli.js.map