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.
Files changed (2) hide show
  1. package/index.js +34 -27
  2. 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 subtype = "preset_" + String(Math.trunc(preset));
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 === Math.trunc(preset);
136
+ const isOn = this.lastKnownPresetIndex === p;
124
137
  cb(null, isOn);
125
138
  })
126
139
  .on("set", (value, cb) => {
127
- this.handleSetStationPreset(Math.trunc(preset), !!value, cb);
140
+ this.handleSetStationPreset(p, !!value, cb);
128
141
  });
129
142
 
130
- this.stationServices.push({ preset: Math.trunc(preset), name: stationName, service: sw });
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
- if (this.stationServices && this.stationServices.length > 0) {
284
- try {
285
- const preset = await this.client.getPresetIndex();
286
- if (Number.isFinite(preset) && this.lastKnownPresetIndex !== preset) {
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
- FsApiClient.prototype.getPresetIndex = async function () {
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
- await this.fetchText("/fsapi/SET/netRemote.sys.preset.index?value=" + encodeURIComponent(String(preset)));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-frontier-silicon-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Homebridge plugin for Frontier Silicon FSAPI devices, power and volume with safe polling",
5
5
  "license": "ISC",
6
6
  "main": "index.js",