homebridge-frontier-silicon-plugin 1.2.0 → 1.2.1
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/index.js +34 -27
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -24,15 +24,25 @@ function FrontierSiliconAccessory(log, config) {
|
|
|
24
24
|
|
|
25
25
|
this.enableVolume = config.enableVolume !== false;
|
|
26
26
|
|
|
27
|
+
// Keep both by default
|
|
27
28
|
this.exposeSpeakerService = config.exposeSpeakerService !== false;
|
|
28
29
|
this.exposeVolumeSlider = config.exposeVolumeSlider !== false;
|
|
29
30
|
|
|
31
|
+
// When selecting a station, power on first
|
|
30
32
|
this.autoPowerOnOnPreset = config.autoPowerOnOnPreset !== false;
|
|
31
33
|
|
|
34
|
+
// Stations: [{ name: "Radio 2", preset: 0 }, ...]
|
|
35
|
+
// Important: preset numbers refer to the radio preset slots.
|
|
36
|
+
// If the radio says "empty preset", store that station to that preset slot on the radio first.
|
|
32
37
|
this.stations = Array.isArray(config.stations) ? config.stations : [];
|
|
33
38
|
|
|
34
39
|
this.lastKnownPower = null;
|
|
40
|
+
|
|
41
|
+
// Store last known RADIO volume on device scale 0..100
|
|
35
42
|
this.lastKnownRadioVolume = null;
|
|
43
|
+
|
|
44
|
+
// We cannot reliably read current preset index on all firmwares.
|
|
45
|
+
// We track what we last selected via HomeKit and sync switches accordingly.
|
|
36
46
|
this.lastKnownPresetIndex = null;
|
|
37
47
|
|
|
38
48
|
this.isUpdatingStationSwitches = false;
|
|
@@ -52,12 +62,14 @@ function FrontierSiliconAccessory(log, config) {
|
|
|
52
62
|
.setCharacteristic(Characteristic.Model, "FSAPI Radio")
|
|
53
63
|
.setCharacteristic(Characteristic.SerialNumber, this.ip || "unknown");
|
|
54
64
|
|
|
65
|
+
// Power switch
|
|
55
66
|
this.switchService = new Service.Switch(this.name);
|
|
56
67
|
this.switchService
|
|
57
68
|
.getCharacteristic(Characteristic.On)
|
|
58
69
|
.on("get", this.handleGetPower.bind(this))
|
|
59
70
|
.on("set", this.handleSetPower.bind(this));
|
|
60
71
|
|
|
72
|
+
// Volume services
|
|
61
73
|
if (this.enableVolume) {
|
|
62
74
|
if (this.exposeSpeakerService) {
|
|
63
75
|
this.speakerService = new Service.Speaker(this.name + " Speaker");
|
|
@@ -90,9 +102,8 @@ function FrontierSiliconAccessory(log, config) {
|
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
// Station switches
|
|
93
106
|
this.stationServices = [];
|
|
94
|
-
this.stationServiceByPreset = new Map();
|
|
95
|
-
|
|
96
107
|
this.buildStationServices();
|
|
97
108
|
|
|
98
109
|
this.startPolling();
|
|
@@ -112,7 +123,9 @@ FrontierSiliconAccessory.prototype.buildStationServices = function () {
|
|
|
112
123
|
if (!stationName) continue;
|
|
113
124
|
if (!Number.isFinite(preset)) continue;
|
|
114
125
|
|
|
115
|
-
const
|
|
126
|
+
const p = Math.trunc(preset);
|
|
127
|
+
const subtype = "preset_" + String(p);
|
|
128
|
+
|
|
116
129
|
if (seenSubtypes.has(subtype)) continue;
|
|
117
130
|
seenSubtypes.add(subtype);
|
|
118
131
|
|
|
@@ -120,15 +133,14 @@ FrontierSiliconAccessory.prototype.buildStationServices = function () {
|
|
|
120
133
|
|
|
121
134
|
sw.getCharacteristic(Characteristic.On)
|
|
122
135
|
.on("get", (cb) => {
|
|
123
|
-
const isOn = this.lastKnownPresetIndex ===
|
|
136
|
+
const isOn = this.lastKnownPresetIndex === p;
|
|
124
137
|
cb(null, isOn);
|
|
125
138
|
})
|
|
126
139
|
.on("set", (value, cb) => {
|
|
127
|
-
this.handleSetStationPreset(
|
|
140
|
+
this.handleSetStationPreset(p, !!value, cb);
|
|
128
141
|
});
|
|
129
142
|
|
|
130
|
-
this.stationServices.push({ preset:
|
|
131
|
-
this.stationServiceByPreset.set(Math.trunc(preset), sw);
|
|
143
|
+
this.stationServices.push({ preset: p, name: stationName, service: sw });
|
|
132
144
|
}
|
|
133
145
|
};
|
|
134
146
|
|
|
@@ -213,13 +225,15 @@ FrontierSiliconAccessory.prototype.handleSetStationPreset = async function (pres
|
|
|
213
225
|
this.lastKnownPower = true;
|
|
214
226
|
this.switchService.getCharacteristic(Characteristic.On).updateValue(true);
|
|
215
227
|
} catch (_e) {
|
|
228
|
+
// ignore
|
|
216
229
|
}
|
|
217
230
|
}
|
|
218
231
|
|
|
219
232
|
await this.client.setPresetIndex(preset);
|
|
220
|
-
this.lastKnownPresetIndex = preset;
|
|
221
233
|
|
|
234
|
+
this.lastKnownPresetIndex = preset;
|
|
222
235
|
this.syncStationSwitchesFromPreset(preset);
|
|
236
|
+
|
|
223
237
|
callback(null);
|
|
224
238
|
} catch (err) {
|
|
225
239
|
this.log.warn("Preset set failed.", toMsg(err));
|
|
@@ -280,17 +294,10 @@ FrontierSiliconAccessory.prototype.startPolling = function () {
|
|
|
280
294
|
}
|
|
281
295
|
}
|
|
282
296
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
this.lastKnownPresetIndex = preset;
|
|
288
|
-
this.syncStationSwitchesFromPreset(preset);
|
|
289
|
-
}
|
|
290
|
-
} catch (err) {
|
|
291
|
-
if (this.log.debug) this.log.debug("Polling preset failed.", toMsg(err));
|
|
292
|
-
}
|
|
293
|
-
}
|
|
297
|
+
// Preset polling is intentionally not implemented for this firmware,
|
|
298
|
+
// because netRemote.sys.preset.index does not exist.
|
|
299
|
+
// If you want full sync from the radio front panel later,
|
|
300
|
+
// we can add alternative detection based on play.info nodes.
|
|
294
301
|
};
|
|
295
302
|
|
|
296
303
|
tick();
|
|
@@ -328,15 +335,14 @@ FsApiClient.prototype.setVolume = async function (volume) {
|
|
|
328
335
|
await this.fetchText("/fsapi/SET/netRemote.sys.audio.volume?value=" + v);
|
|
329
336
|
};
|
|
330
337
|
|
|
331
|
-
|
|
332
|
-
const text = await this.fetchText("/fsapi/GET/netRemote.sys.preset.index");
|
|
333
|
-
const value = parseFsapiValue(text);
|
|
334
|
-
const n = Number(value);
|
|
335
|
-
return Number.isFinite(n) ? Math.trunc(n) : null;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
+
// DIR3010 style preset selection via navigation state
|
|
338
339
|
FsApiClient.prototype.setPresetIndex = async function (preset) {
|
|
339
|
-
|
|
340
|
+
const p = Math.trunc(Number(preset));
|
|
341
|
+
if (!Number.isFinite(p)) throw new Error("Invalid preset");
|
|
342
|
+
|
|
343
|
+
await this.fetchText("/fsapi/SET/netRemote.nav.state?value=1");
|
|
344
|
+
await this.fetchText("/fsapi/SET/netRemote.nav.action.selectPreset?value=" + encodeURIComponent(String(p)));
|
|
345
|
+
await this.fetchText("/fsapi/SET/netRemote.nav.state?value=0");
|
|
340
346
|
};
|
|
341
347
|
|
|
342
348
|
FsApiClient.prototype.fetchText = async function (pathAndQuery) {
|
|
@@ -399,6 +405,7 @@ function toMsg(err) {
|
|
|
399
405
|
return String(err);
|
|
400
406
|
}
|
|
401
407
|
|
|
408
|
+
// Non linear volume mapping
|
|
402
409
|
function homekitToRadioVolume(homekitValue) {
|
|
403
410
|
const x = clampInt(Number(homekitValue), 0, 100) / 100;
|
|
404
411
|
return clampInt(Math.round(Math.pow(x, 2) * 100), 0, 100);
|
package/package.json
CHANGED