homebridge-eosstb 2.4.0-beta.1 → 2.4.0-beta.3

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 (4) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -2
  3. package/index.js +406 -197
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
  See the [Readme file](https://github.com/jsiegenthaler/homebridge-eosstb/blob/master/README.md) for full plugin documentation.
5
5
  Please restart Homebridge after every plugin update.
6
6
 
7
+
8
+ ## 2.4.0-beta.3 (2026-05-10)
9
+
10
+ This release focusses improved error handling and has extra debugging added to catch a refresh error
11
+
12
+ - Improved handling of errors for web requests
13
+ - Added extra debugging to assist in catching a refresh error in refreshChannelList
14
+
15
+ ## 2.4.0-beta.2 (2026-05-09)
16
+
17
+ This release focusses on ensuring mqtt long-term stability, and fixes an issue where the channel name was not shown on startup.
18
+
19
+ - Rescheduled nightly channel list refresh to 0000-0400 instead of 0000-0600
20
+ - Added an automatic daily mqtt reconnect at a random time between 0400-0600 to avoid long running mqtt sessions. Only restarts if settop box is turned off
21
+ - Fixed issue where current channel was not displayed on plugin startup
22
+
23
+
7
24
  ## 2.4.0-beta.1 (2026-05-09)
8
25
 
9
26
  This release represents a major rewrite of the plugin, significantly improving robustness, HAP compliance, and code quality throughout, and making it work for Switzerland.
@@ -34,6 +51,20 @@ This release represents a major rewrite of the plugin, significantly improving r
34
51
  - Bumped dependency "tough-cookie": "^6.0.1",
35
52
  - Bumped dependency "ws": "^8.20.0"
36
53
 
54
+ ## 2.3.9 (2026-05-09)
55
+ - Fixed Error on Homebridge v2: Cannot read properties of undefined (reading 'STRING')
56
+ - Adapted hidden channel name to reduce warning messages with Homebridge v2
57
+ - Updated iOS and Homebridge version references in Readme
58
+ - Bumped engine "^1.11.4||^2.0.0",
59
+ - Bumped engine "node": "^24.15.0"
60
+ - Bumped dependency "axios": "^1.16.0",
61
+ - Bumped dependency "axios-cookiejar-support": "^7.0.0",
62
+ - Bumped dependency "mqtt": "^5.15.1",
63
+ - Bumped dependency "qs": "^6.15.1",
64
+ - Bumped dependency "semver": "^7.8.0",
65
+ - Bumped dependency "tough-cookie": "^6.0.1",
66
+ - Bumped dependency "ws": "^8.20.0"
67
+
37
68
  ## 2.3.8 (2026-02-27)
38
69
 
39
70
  This is a maintenance release to bring dependencies up to date.
package/README.md CHANGED
@@ -20,7 +20,6 @@
20
20
 
21
21
  The logon method to the backend systems changed in January / February 2024. I managed to get CH working again from April 2026 for CH. NL was confirmed working OK in May 2026. BE was confirmed working OK in June 2024.
22
22
 
23
- If you know anything about session authentication and are able to help, please get in touch.
24
23
  </b><hr>
25
24
 
26
25
  [![npm](https://badgen.net/npm/dt/homebridge-eosstb)](https://www.npmjs.com/package/homebridge-eosstb)
@@ -110,7 +109,7 @@ This plugin is not provided by Telenet or Sunrise or Virgin Media or Ziggo or an
110
109
  ## Requirements
111
110
 
112
111
  - An Apple iPhone or iPad with iOS/iPadOS 14.0 (or later). Developed on iOS 14.1-26.4, earlier versions not tested.
113
- - [Homebridge](https://homebridge.io/) v1.1.116 (or later). Developed on Homebridge 1.1.116-2.0.1, earlier versions not tested.
112
+ - [Homebridge](https://homebridge.io/) v1.1.116 (or later). Developed on Homebridge 1.1.116-2.0.2, earlier versions not tested.
114
113
  - A TV subscription from one of the supported countries and TV providers.
115
114
  - An online account for viewing TV in the web app (often part of your TV package), see the table above.
116
115
  - An ARRIS DCX960 or HUMAX EOS1008R / 2008C / VIP5002W set-top box, provided by your TV provider as part of your TV subscription, called by the system an "EOSSTB", "EOS2STB" or "APLSTB" and marketed under different names in different countries.
package/index.js CHANGED
@@ -355,8 +355,10 @@ class StbPlatform {
355
355
  this.accessories = [];
356
356
  this.stbDevices = []; // store stbDevice in this.stbDevices
357
357
  this.masterChannelList = [];
358
- this.masterChannelListExpiryDate = 0; // epoch = always expired on first run
358
+ this.masterChannelListRefreshedAt = null; // null = never fetched since startup
359
+ this.masterChannelListExpiryDate = 0; // epoch = always expired on first run, forces immediate fetch
359
360
  this.checkChannelListTimeout = null; // nightly scheduler handler
361
+ this.mqttReconnecting = false; // nightly reconnect indicator
360
362
  this.isDev = config.devMode === true;
361
363
  this.debugLevel = this.config.debugLevel || 0; // debugLevel defaults to 0 (minimum)
362
364
 
@@ -822,7 +824,7 @@ class StbPlatform {
822
824
 
823
825
  /**
824
826
  * Schedule the next nightly master channel list refresh.
825
- * Picks a random time between 00:00 and 06:00 the following day,
827
+ * Picks a random time between 00:00 and 04:00 the following day,
826
828
  * then reschedules itself so the pattern repeats indefinitely.
827
829
  *
828
830
  * Using setTimeout (not setInterval) means each day gets a fresh
@@ -834,9 +836,9 @@ class StbPlatform {
834
836
  tomorrow.setDate(tomorrow.getDate() + 1);
835
837
  tomorrow.setHours(0, 0, 0, 0);
836
838
 
837
- // Add a random offset: anywhere from 0 ms up to (but not including) 6 hours
838
- const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
839
- const randomOffsetMs = Math.floor(Math.random() * SIX_HOURS_MS);
839
+ // Add a random offset: anywhere from 0 ms up to (but not including) 4 hours
840
+ const FOUR_HOURS_MS = 4 * 60 * 60 * 1000;
841
+ const randomOffsetMs = Math.floor(Math.random() * FOUR_HOURS_MS);
840
842
 
841
843
  const nextRefreshAt = new Date(tomorrow.getTime() + randomOffsetMs);
842
844
  const msUntilRefresh = nextRefreshAt.getTime() - Date.now();
@@ -850,11 +852,125 @@ class StbPlatform {
850
852
  // Store the timer handle so shutdown can cancel it
851
853
  this.checkChannelListTimeout = setTimeout(async () => {
852
854
  if (this.isShuttingDown) return; // bail out if we're going down
855
+
856
+ // if an MQTT reconnect is in progress, wait a few minutes before
857
+ // refreshing to avoid a race condition during session startup
858
+ if (this.mqttReconnecting) {
859
+ const THREE_MIN_MS = 3 * 60 * 1000;
860
+ const retryDelayMs =
861
+ THREE_MIN_MS + Math.floor(Math.random() * THREE_MIN_MS);
862
+ this.log.info(
863
+ "StbPlatform: channel list refresh deferred - MQTT reconnect in progress, retrying in a few minutes",
864
+ );
865
+ this.checkChannelListTimeout = setTimeout(async () => {
866
+ if (this.isShuttingDown) return;
867
+ await this._refreshChannelList();
868
+ this._scheduleNightlyChannelListRefresh();
869
+ }, retryDelayMs);
870
+ return;
871
+ }
872
+
853
873
  await this._refreshChannelList();
854
874
  this._scheduleNightlyChannelListRefresh(); // reschedule for the next day
855
875
  }, msUntilRefresh);
856
876
  } // end of _scheduleNightlyChannelListRefresh
857
877
 
878
+ /**
879
+ * Schedule the next nightly MQTT reconnect.
880
+ * Picks a random time between 04:00 and 06:00 the following day
881
+ * to avoid overlapping with the channel list refresh (00:00–04:00).
882
+ * Reschedules itself so the pattern repeats indefinitely.
883
+ */
884
+ _scheduleNightlyMqttReconnect() {
885
+ const tomorrow = new Date();
886
+ tomorrow.setDate(tomorrow.getDate() + 1);
887
+ tomorrow.setHours(4, 0, 0, 0);
888
+
889
+ const TWO_HOURS_MS = 2 * 60 * 60 * 1000;
890
+ const randomOffsetMs = Math.floor(Math.random() * TWO_HOURS_MS);
891
+
892
+ const nextReconnectAt = new Date(tomorrow.getTime() + randomOffsetMs);
893
+ const msUntilReconnect = nextReconnectAt.getTime() - Date.now();
894
+
895
+ if (this.debugLevel > 0) {
896
+ this.log.warn(
897
+ `StbPlatform: next nightly MQTT reconnect scheduled for ${nextReconnectAt.toLocaleString()}`,
898
+ );
899
+ }
900
+
901
+ this.mqttReconnectTimeout = setTimeout(async () => {
902
+ if (this.isShuttingDown) return;
903
+ await this._attemptNightlyMqttReconnect();
904
+ // _attemptNightlyMqttReconnect reschedules for the next night once done
905
+ }, msUntilReconnect);
906
+ } // end of _scheduleNightlyMqttReconnect
907
+
908
+ /**
909
+ * Attempt the nightly MQTT reconnect.
910
+ * If any STB is currently online (user may be watching), defers by 1 hour
911
+ * plus a random offset and tries again, rather than interrupting the session.
912
+ * Once the reconnect completes (or fails), reschedules for the next night.
913
+ * Retries 3 times then gives up, and the next reconnect will be the next day.
914
+ */
915
+ async _attemptNightlyMqttReconnect(retryCount = 0) {
916
+ if (this.isShuttingDown) return;
917
+
918
+ const MAX_RETRIES = 3; // give up after 3 deferrals (~3-4.5 hours past 04:00)
919
+
920
+ // check if any STB is currently active - if so, defer to avoid
921
+ // interrupting a user who may be watching TV and using the remote
922
+ const anyStbOnline = this.devices.some(
923
+ (device) => device.currentPowerState === Characteristic.Active.ACTIVE,
924
+ );
925
+
926
+ if (anyStbOnline) {
927
+ // give up if max retries reached
928
+ if (retryCount >= MAX_RETRIES) {
929
+ this.log.info(
930
+ "StbPlatform: nightly MQTT reconnect skipped - STB still active after max retries, rescheduling for next night",
931
+ );
932
+ this._scheduleNightlyMqttReconnect();
933
+ return;
934
+ }
935
+
936
+ // retry in 1 hour plus a random 0–30 min buffer
937
+ const ONE_HOUR_MS = 60 * 60 * 1000;
938
+ const THIRTY_MIN_MS = 30 * 60 * 1000;
939
+ const retryDelayMs =
940
+ ONE_HOUR_MS + Math.floor(Math.random() * THIRTY_MIN_MS);
941
+ const retryAt = new Date(Date.now() + retryDelayMs);
942
+
943
+ this.log.info(
944
+ `StbPlatform: nightly MQTT reconnect deferred - STB is active (attempt ${retryCount + 1}/${MAX_RETRIES}). Retrying at ${retryAt.toLocaleString()}`,
945
+ );
946
+
947
+ // store handle so shutdown can cancel the deferred retry too
948
+ this.mqttReconnectTimeout = setTimeout(async () => {
949
+ if (this.isShuttingDown) return;
950
+ await this._attemptNightlyMqttReconnect(retryCount + 1);
951
+ }, retryDelayMs);
952
+ return; // don't reschedule for next night yet - that happens after a successful reconnect
953
+ }
954
+
955
+ // no STB is active - safe to reconnect
956
+ try {
957
+ this.mqttReconnecting = true; // signal to channel list refresh to pause
958
+ this.log.info("StbPlatform: nightly MQTT reconnect starting...");
959
+ await this.endMqttSession();
960
+ await this.startMqttClient();
961
+ this.log.info("StbPlatform: nightly MQTT reconnect completed");
962
+ } catch (err) {
963
+ this.log.error(
964
+ "StbPlatform: nightly MQTT reconnect failed:",
965
+ err.message,
966
+ );
967
+ } finally {
968
+ this.mqttReconnecting = false; // always clear the flag, even on failure
969
+ }
970
+
971
+ this._scheduleNightlyMqttReconnect(); // reschedule for next night
972
+ } // end of _attemptNightlyMqttReconnect
973
+
858
974
  /**
859
975
  * _runFullStartupSequence
860
976
  *
@@ -872,7 +988,7 @@ class StbPlatform {
872
988
  * 10. startMqttClient — connect to the mqtt broker
873
989
  *
874
990
  * Each step is individually awaited, so failures are caught at the right
875
- * step and errorTitle is accurate when logged.
991
+ * step and stepName is accurate when logged.
876
992
  *
877
993
  * @param {string} watchdogInstance - log prefix for this watchdog invocation
878
994
  * @param {string} debugPrefix - debug() colour prefix
@@ -883,12 +999,13 @@ class StbPlatform {
883
999
  Object.keys(sessionState)[this.currentSessionState],
884
1000
  );
885
1001
 
886
- // errorTitle tracks which step we're on so the catch block can log a
1002
+ // stepName tracks which step we're on so the catch block can log a
887
1003
  // meaningful message rather than a generic "something failed".
888
- let errorTitle = "Failed to get config";
1004
+ let stepName = "";
889
1005
 
890
1006
  try {
891
1007
  // ── Step 1: Get backend config (endpoint URLs) for the country ──────────
1008
+ stepName = "get config";
892
1009
  this.log.debug("%s: ++++ step 1: calling getConfig", watchdogInstance);
893
1010
  debug(debugPrefix + "calling getConfig");
894
1011
 
@@ -902,7 +1019,7 @@ class StbPlatform {
902
1019
  );
903
1020
 
904
1021
  // ── Step 2: Authenticate and create a session ──────────────────────────
905
- errorTitle = "Failed to create session";
1022
+ stepName = "create session";
906
1023
  this.log.debug(
907
1024
  "%s: ++++ step 2: calling createSession for country %s",
908
1025
  watchdogInstance,
@@ -913,6 +1030,12 @@ class StbPlatform {
913
1030
 
914
1031
  const sessionHouseholdId = await this.createSession();
915
1032
  // Result stored in this.session by createSession()
1033
+ // debugging help to get session keys
1034
+ // session object keys: ["accessToken","householdId","refreshToken","refreshTokenExpiry","username","issuedAt"]
1035
+ this.log.debug(
1036
+ "session object keys: %s",
1037
+ JSON.stringify(Object.keys(this.session ?? {})),
1038
+ );
916
1039
 
917
1040
  this.log.debug(
918
1041
  "%s: ++++++ step 2 done: session created, householdId %s",
@@ -921,7 +1044,7 @@ class StbPlatform {
921
1044
  );
922
1045
 
923
1046
  // ── Step 3: Fetch customer profile and assigned devices ────────────────
924
- errorTitle = "Failed to discover platform";
1047
+ stepName = "discover platform";
925
1048
  this.log.debug(
926
1049
  "%s: ++++ step 3: calling getPersonalizationData for householdId %s",
927
1050
  watchdogInstance,
@@ -1017,7 +1140,7 @@ class StbPlatform {
1017
1140
  }
1018
1141
 
1019
1142
  // ── Step 8: Discover and configure HomeKit accessories ─────────────────
1020
- errorTitle = "Failed to discover devices";
1143
+ stepName = "discover devices";
1021
1144
  this.log.debug(
1022
1145
  "%s: ++++ step 8: calling discoverDevices",
1023
1146
  watchdogInstance,
@@ -1035,7 +1158,7 @@ class StbPlatform {
1035
1158
  );
1036
1159
 
1037
1160
  // ── Step 9: Get the mqtt broker token ─────────────────────────────────
1038
- errorTitle = "Failed to start mqtt session";
1161
+ stepName = "start mqtt session";
1039
1162
  this.log.debug("%s: ++++ step 9: calling getMqttToken", watchdogInstance);
1040
1163
  debug(debugPrefix + "calling getMqttToken");
1041
1164
 
@@ -1072,15 +1195,66 @@ class StbPlatform {
1072
1195
  watchdogInstance,
1073
1196
  );
1074
1197
  } catch (errorReason) {
1075
- // One of the steps above threw or rejected. Log the failed step name
1076
- // (errorTitle) alongside the reason for easy diagnosis.
1077
- this.log.warn("%s: %s — %s", watchdogInstance, errorTitle, errorReason);
1198
+ // One of the steps above threw or rejected. Log the failed stepName
1199
+ // alongside the error message for easy diagnosis.
1200
+ const errMsg =
1201
+ errorReason instanceof Error
1202
+ ? errorReason.message
1203
+ : String(errorReason);
1204
+ this.log.warn("%s: Failed to %s: %s", watchdogInstance, stepName, errMsg);
1078
1205
  this.currentSessionState = sessionState.DISCONNECTED;
1079
1206
  this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
1080
1207
  // sessionWatchdogRunning is reset in the finally block of the caller.
1081
1208
  }
1082
1209
  } // end of _runFullStartupSequence
1083
1210
 
1211
+ /**
1212
+ * _handleWebError
1213
+ *
1214
+ * Standardised catch-block handler for all outbound HTTP/Axios calls.
1215
+ * Builds a consistent human-readable error message, sets session state
1216
+ * to DISCONNECTED on ENOTFOUND, logs at debug level, then re-throws.
1217
+ *
1218
+ * @param {Error} error - the caught error
1219
+ * @param {string} action - what the caller was trying to do, e.g.
1220
+ * "get config data for countryCode ch"
1221
+ * @param {string|URL} url - the URL that was called (for debug context)
1222
+ */
1223
+ _handleWebError(error, action, url) {
1224
+ const urlStr = String(url ?? error.config?.url ?? "");
1225
+ let errReason = `Could not ${action}:`;
1226
+
1227
+ if (error.isAxiosError) {
1228
+ errReason += ` ${error.code}`;
1229
+
1230
+ if (error.response) {
1231
+ errReason += ` (HTTP ${error.response.status})`;
1232
+ }
1233
+
1234
+ if (error.code === "ENOTFOUND") {
1235
+ errReason += " - no internet connection";
1236
+ this.currentSessionState = sessionState.DISCONNECTED;
1237
+ }
1238
+ } else {
1239
+ errReason += ` — ${error.message ?? String(error)}`;
1240
+ }
1241
+
1242
+ if (urlStr) errReason += ` — ${urlStr}`;
1243
+
1244
+ // Summary always visible:
1245
+ this.log.error("_handleWebError: %s", errReason);
1246
+
1247
+ this.log.debug("_handleWebError: %s — full error:", errReason, error);
1248
+ if (error.response?.data) {
1249
+ this.log.debug(
1250
+ "_handleWebError: response body: %s",
1251
+ JSON.stringify(error.response.data).substring(0, 400),
1252
+ );
1253
+ }
1254
+
1255
+ throw new Error(errReason, { cause: error });
1256
+ }
1257
+
1084
1258
  /**
1085
1259
  * Discovers all physical devices from the backend and maps them to HomeKit accessories.
1086
1260
  * Creates new accessories for uncached devices, and restores existing ones from cache.
@@ -2609,8 +2783,8 @@ class StbPlatform {
2609
2783
  );
2610
2784
  }
2611
2785
  throw new Error(
2612
- `Step 4 of 5: login did not redirect to login_success.html ` +
2613
- `check your username and password. Final URL: ${finalUrl}`,
2786
+ // use a simple clean message to tell the user his credentials are likely wrong
2787
+ `Step 4 of 5: login failed — check your username and password`,
2614
2788
  );
2615
2789
  }
2616
2790
 
@@ -3136,6 +3310,18 @@ class StbPlatform {
3136
3310
  return;
3137
3311
  }
3138
3312
 
3313
+ // Log session state before the request - DIAGNOSES DEBUG ONLY
3314
+ const tokenAge = this.session?.issuedAt
3315
+ ? Math.round((Date.now() - this.session.issuedAt) / 1000 / 60)
3316
+ : null;
3317
+ const sessionId = this.session?.householdId ?? "(none)";
3318
+
3319
+ this.log.debug(
3320
+ "refreshMasterChannelList: starting | householdId=%s | tokenAgeMinutes=%s",
3321
+ sessionId,
3322
+ tokenAge ?? "unknown",
3323
+ );
3324
+
3139
3325
  // exit immediately if channel list has not expired
3140
3326
  if (Date.now() < this.masterChannelListExpiryDate) {
3141
3327
  if (this.debugLevel > 1) {
@@ -3154,16 +3340,6 @@ class StbPlatform {
3154
3340
  // syntax:
3155
3341
  // https://prod.oesp.virginmedia.com/oesp/v4/GB/eng/web/channels?byLocationId=41043&includeInvisible=true&includeNotEntitled=true&personalised=true&sort=channelNumber
3156
3342
  // https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH
3157
- /*
3158
- let url = COUNTRY_BASE_URLS[this.config.country.toLowerCase()] + '/channels';
3159
- url = url + '?byLocationId=' + this.session.locationId // locationId needed to get user-specific list
3160
- url = url + '&includeInvisible=true' // includeInvisible
3161
- url = url + '&includeNotEntitled=true' // includeNotEntitled
3162
- url = url + '&personalised=true' // personalised
3163
- url = url + '&sort=channelNumber' // sort
3164
- */
3165
- //url = 'https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH'
3166
- //let url = COUNTRY_BASE_URLS[this.config.country.toLowerCase()] + '/eng/web/linear-service/v2/channels';
3167
3343
  const url = new URL(`${this.configsvc.linearService.URL}/v2/channels`);
3168
3344
  url.searchParams.set("cityId", this.customer.cityId);
3169
3345
  url.searchParams.set("language", "en");
@@ -3172,6 +3348,7 @@ class StbPlatform {
3172
3348
  if (this.debugLevel > 1) {
3173
3349
  this.log.warn("refreshMasterChannelList: GET %s", url);
3174
3350
  }
3351
+
3175
3352
  try {
3176
3353
  // call the webservice to get all available channels
3177
3354
  const config = {
@@ -3186,6 +3363,29 @@ class StbPlatform {
3186
3363
  "https://www.horizon.tv/",
3187
3364
  },
3188
3365
  };
3366
+ // extra debugging to help catch refresh issues
3367
+ this.log.debug(
3368
+ "refreshMasterChannelList: request headers | x-oesp-token=%s...%s | x-oesp-username=%s | Referer=%s",
3369
+ this.session.accessToken?.substring(0, 8) ?? "(null)", // first 8 chars only — don't log the full token
3370
+ this.session.accessToken?.slice(-4) ?? "", // last 4 chars
3371
+ this.session.username ?? "(null)",
3372
+ config.headers.Referer,
3373
+ );
3374
+ // extra debugging to help catch refresh issues
3375
+ this.log.debug(
3376
+ "refreshMasterChannelList: preflight | cityId=%s | tokenPresent=%s | tokenLength=%s | username=%s | prevExpiryWas=%s | lastRefreshedAt=%s",
3377
+ this.customer.cityId,
3378
+ !!this.session?.accessToken,
3379
+ this.session?.accessToken?.length ?? 0,
3380
+ this.session?.username ?? "(none)",
3381
+ this.masterChannelListExpiryDate
3382
+ ? new Date(this.masterChannelListExpiryDate).toLocaleString()
3383
+ : "(never set)",
3384
+ this.masterChannelListRefreshedAt
3385
+ ? new Date(this.masterChannelListRefreshedAt).toLocaleString()
3386
+ : "(never)",
3387
+ );
3388
+
3189
3389
  const response = await axiosWS(config);
3190
3390
  if (this.debugLevel > 1) {
3191
3391
  this.log.warn(
@@ -3198,7 +3398,6 @@ class StbPlatform {
3198
3398
  response.data.length,
3199
3399
  );
3200
3400
  }
3201
- //this.log(response.data);
3202
3401
 
3203
3402
  // the header contains the following:
3204
3403
  // Cache-Control: max-age=600, public, stale-if-error=43200
@@ -3239,6 +3438,9 @@ class StbPlatform {
3239
3438
  this.masterChannelList.map((ch) => [ch.id, ch]),
3240
3439
  );
3241
3440
 
3441
+ // record when we refreshed it
3442
+ this.masterChannelListRefreshedAt = Date.now()
3443
+
3242
3444
  this.log(
3243
3445
  "MasterChannelList contains %s channels, valid until %s",
3244
3446
  this.masterChannelList.length,
@@ -3254,15 +3456,11 @@ class StbPlatform {
3254
3456
  }
3255
3457
  return this.masterChannelList;
3256
3458
  } catch (error) {
3257
- const errReason = error.isAxiosError
3258
- ? `${error.code}: ${error.hostname ?? error.config?.url ?? ""}`
3259
- : (error.message ?? String(error));
3260
-
3261
- if (error.isAxiosError && error.code === "ENOTFOUND") {
3262
- this.currentSessionState = sessionState.DISCONNECTED;
3263
- }
3264
- this.log.warn("refreshMasterChannelList error:", errReason);
3265
- throw new Error(errReason, { cause: error }); // { cause } preserves the original for debugging
3459
+ this._handleWebError(
3460
+ error,
3461
+ `refresh master channel list`,
3462
+ url,
3463
+ );
3266
3464
  }
3267
3465
  } // end of refreshMasterChannelList
3268
3466
 
@@ -3327,16 +3525,11 @@ class StbPlatform {
3327
3525
  this.configsvc = response.data; // store the entire config data for future use in this.configsvc
3328
3526
  return this.configsvc;
3329
3527
  } catch (error) {
3330
- let errReason = `Could not get config data for ${countryCode} - check your internet connection`;
3331
- if (error.isAxiosError) {
3332
- errReason = error.code + ": " + (error.hostname || "");
3333
- // if no connection then set session to disconnected to force a session reconnect
3334
- if (error.code === "ENOTFOUND") {
3335
- this.currentSessionState = sessionState.DISCONNECTED;
3336
- }
3337
- }
3338
- this.log.debug(`getConfig error:`, error);
3339
- throw new Error(errReason);
3528
+ this._handleWebError(
3529
+ error,
3530
+ `get config data for countryCode ${countryCode}`,
3531
+ url,
3532
+ );
3340
3533
  }
3341
3534
  } // end of getConfig
3342
3535
 
@@ -3487,20 +3680,11 @@ class StbPlatform {
3487
3680
  //this.log.warn('getPersonalizationData: all done, returnng customerStatus: %s', this.customer.customerStatus);
3488
3681
  return this.customer;
3489
3682
  } catch (error) {
3490
- let errReason =
3491
- "Could not refresh personalization data for " +
3492
- householdId +
3493
- " - check your internet connection";
3494
- if (error.isAxiosError) {
3495
- errReason = error.code + ": " + (error.hostname || "");
3496
- // if no connection then set session to disconnected to force a session reconnect
3497
- if (error.code === "ENOTFOUND") {
3498
- this.currentSessionState = sessionState.DISCONNECTED;
3499
- }
3500
- }
3501
- //this.log('%s %s', errText, (errReason || ''));
3502
- this.log.debug(`getPersonalizationData error:`, error);
3503
- throw error;
3683
+ this._handleWebError(
3684
+ error,
3685
+ `get personalization data for household ${householdId}`,
3686
+ url,
3687
+ );
3504
3688
  }
3505
3689
  } // end of getPersonalizationData
3506
3690
 
@@ -3550,12 +3734,11 @@ class StbPlatform {
3550
3734
  );
3551
3735
  }
3552
3736
  } catch (error) {
3553
- this.log.warn(
3554
- "setPersonalizationDataForDevice failed: %s %s",
3555
- error.response?.status,
3556
- error.response?.statusText,
3737
+ this._handleWebError(
3738
+ error,
3739
+ `set personalization data for device ${deviceId}`,
3740
+ url,
3557
3741
  );
3558
- this.log.debug("setPersonalizationDataForDevice: error:", error);
3559
3742
  }
3560
3743
  } // end of setPersonalizationDataForDevice
3561
3744
 
@@ -3614,15 +3797,11 @@ class StbPlatform {
3614
3797
  }
3615
3798
  return this.entitlements;
3616
3799
  } catch (error) {
3617
- let errReason = `Could not refresh entitlements data for ${householdId} - check your internet connection`;
3618
- if (error.isAxiosError) {
3619
- errReason = `${error.code}: ${error.hostname || ""}`;
3620
- }
3621
- if (error.code === "ENOTFOUND") {
3622
- this.currentSessionState = sessionState.DISCONNECTED;
3623
- }
3624
- this.log.debug(`getEntitlements error:`, error);
3625
- throw new Error(errReason);
3800
+ this._handleWebError(
3801
+ error,
3802
+ `get entitlements data for household ${householdId}`,
3803
+ url,
3804
+ );
3626
3805
  }
3627
3806
  } // end of getEntitlements
3628
3807
 
@@ -3799,29 +3978,11 @@ class StbPlatform {
3799
3978
 
3800
3979
  return this.currentRecordingState;
3801
3980
  } catch (error) {
3802
- if (error.isAxiosError) {
3803
- const errReason =
3804
- "getRecordingState" +
3805
- ": " +
3806
- error.code +
3807
- " " +
3808
- (error.hostname || "") +
3809
- ": " +
3810
- (error.response?.status ?? "") +
3811
- " " +
3812
- (error.response?.statusText ?? "") +
3813
- ": " +
3814
- (error.config?.url ?? "");
3815
- if (error.code === "ENOTFOUND") {
3816
- this.currentSessionState = sessionState.DISCONNECTED;
3817
- }
3818
- this.log.debug("getRecordingState error:", error);
3819
- throw new Error(errReason);
3820
- } else {
3821
- this.log.warn("getRecordingState error:");
3822
- this.log.warn(error);
3823
- throw error;
3824
- }
3981
+ this._handleWebError(
3982
+ error,
3983
+ `get recording status for household ${householdId}`,
3984
+ url,
3985
+ );
3825
3986
  }
3826
3987
  } // end of getRecordingState
3827
3988
 
@@ -4011,29 +4172,11 @@ class StbPlatform {
4011
4172
 
4012
4173
  return this.currentRecordingState;
4013
4174
  } catch (error) {
4014
- if (error.isAxiosError) {
4015
- const errReason =
4016
- "getRecordingBookings" +
4017
- ": " +
4018
- error.code +
4019
- " " +
4020
- (error.hostname || "") +
4021
- ": " +
4022
- (error.response?.status ?? "") +
4023
- " " +
4024
- (error.response?.statusText ?? "") +
4025
- ": " +
4026
- (error.config?.url ?? "");
4027
- if (error.code === "ENOTFOUND") {
4028
- this.currentSessionState = sessionState.DISCONNECTED;
4029
- }
4030
- this.log.debug("getRecordingBookings error:", error);
4031
- throw new Error(errReason);
4032
- } else {
4033
- this.log.warn("getRecordingBookings error:");
4034
- this.log.warn(error);
4035
- throw error;
4036
- }
4175
+ this._handleWebError(
4176
+ error,
4177
+ `get recording bookings for household ${householdId}`,
4178
+ url,
4179
+ );
4037
4180
  }
4038
4181
  } // end of getRecordingBookings
4039
4182
 
@@ -4070,14 +4213,11 @@ class StbPlatform {
4070
4213
  this.log.warn(response.data);
4071
4214
  return true;
4072
4215
  } catch (error) {
4073
- let errReason = `Could not get experimental data for ${householdId} - check your internet connection`;
4074
- if (error.isAxiosError) {
4075
- errReason = `${error.code}: ${error.hostname || ""}`;
4076
- if (error.code === "ENOTFOUND") {
4077
- this.currentSessionState = sessionState.DISCONNECTED;
4078
- }
4079
- }
4080
- this.log.warn(`getExperimentalEndpoint error:`, error);
4216
+ this._handleWebError(
4217
+ error,
4218
+ `get experimental endpoint for household ${householdId}`,
4219
+ url,
4220
+ );
4081
4221
  }
4082
4222
  } // end of getExperimentalEndpoint
4083
4223
 
@@ -4278,12 +4418,8 @@ class StbPlatform {
4278
4418
  // ------ device subscriptions ------
4279
4419
  // subscribe only to what we need
4280
4420
 
4281
- // turn on our clientId. This is similar to turning on a box, it tells the server we are online
4282
- // our clientId must be up and running to send commands (power, channel, etc) to the physical device
4283
- // this.setHgoOnlineRunning(householdId, mqttClientId);
4284
-
4285
4421
  // householdId/mqttClientId: subscribe to own clientId to get data for ourselves
4286
- // subscribe to all devices after the setHgoOnlineRunning is sent
4422
+ // subscribe to all devices before the setHgoState is sent
4287
4423
  this.mqttSubscribeToTopic(
4288
4424
  householdId + "/" + this.mqttClient.options.clientId,
4289
4425
  ); // subscribe to our own mqttClientId to get all data
@@ -4320,32 +4456,46 @@ class StbPlatform {
4320
4456
  // reset so the 10-second retry fires correctly if the box doesn't respond
4321
4457
  this.lastMqttUiStatusMessageReceived = null;
4322
4458
 
4459
+ // announce ourselves as an active HGO client before requesting UI status
4460
+ // the STB uses this retained presence message to decide which clients to respond to
4461
+ this.setHgoState(
4462
+ householdId,
4463
+ this.mqttClient.options.clientId,
4464
+ "ONLINE_RUNNING",
4465
+ );
4466
+
4467
+ // request initial UI status for each device, with a short delay to allow
4468
+ // the STB to process the HGO presence announcement first
4323
4469
  // CPE.uiStatus messages are received via the householdId and mqttClientId
4324
4470
  // topics which are already subscribed above.
4325
4471
  // getUiStatus is called here to request the initial UI state from each device.
4326
4472
  // retain: false is used (see getUiStatus) so a retry is scheduled in case the box
4327
4473
  // is temporarily unreachable when the initial request is sent.
4328
- this.devices.forEach((device) => {
4329
- // request the initial UI status for each device
4330
- this.getUiStatus(device.deviceId, this.mqttClient.options.clientId);
4331
-
4332
- // retry getUiStatus after 10 seconds if no CPE.uiStatus response has arrived yet
4333
- // (handles case where box is briefly offline when the initial request is sent)
4334
- setTimeout(() => {
4335
- if (!this.lastMqttUiStatusMessageReceived) {
4336
- if (this.debugLevel > 0) {
4337
- this.log.warn(
4338
- "getUiStatus: no CPE.uiStatus received yet for %s, retrying",
4474
+ setTimeout(() => {
4475
+ this.devices.forEach((device) => {
4476
+ // request the initial UI status for each device
4477
+ this.getUiStatus(
4478
+ device.deviceId,
4479
+ this.mqttClient.options.clientId,
4480
+ );
4481
+
4482
+ // retry after 10 seconds if no CPE.uiStatus response has arrived yet
4483
+ setTimeout(() => {
4484
+ if (!this.lastMqttUiStatusMessageReceived) {
4485
+ if (this.debugLevel > 0) {
4486
+ this.log.warn(
4487
+ "getUiStatus: no CPE.uiStatus received yet for %s, retrying",
4488
+ device.deviceId,
4489
+ );
4490
+ }
4491
+ this.getUiStatus(
4339
4492
  device.deviceId,
4493
+ this.mqttClient.options.clientId,
4340
4494
  );
4341
4495
  }
4342
- this.getUiStatus(
4343
- device.deviceId,
4344
- this.mqttClient.options.clientId,
4345
- );
4346
- }
4347
- }, 10 * 1000); // 10 second retry delay
4348
- });
4496
+ }, 10 * 1000); // 10 second retry delay
4497
+ });
4498
+ }, 500); // 500ms for STB to register our HGO presence before we request status
4349
4499
 
4350
4500
  resolve(true); // all subscriptions registered — session is ready
4351
4501
  } catch (err) {
@@ -4445,7 +4595,8 @@ class StbPlatform {
4445
4595
  const deviceIndex = this.devices.findIndex(
4446
4596
  (device) => device.deviceId === deviceId,
4447
4597
  );
4448
- const stbDevice = deviceIndex > -1 ? this.stbDevices[deviceIndex] : null;
4598
+ const stbDevice =
4599
+ deviceIndex > -1 ? this.stbDevices[deviceIndex] : null;
4449
4600
 
4450
4601
  // Box setting: StandbyPowerConsumption = FastStart / ActiveStart / EcoSlowstart
4451
4602
  // "Fast start": when turned off, goes to ONLINE_STANDBY and stays there. Box can be turned on via mqtt
@@ -4458,15 +4609,18 @@ class StbPlatform {
4458
4609
  // Detect power-off → power-on transition per device.
4459
4610
  // Set PLAY immediately; CPE.uiStatus will overwrite with the
4460
4611
  // accurate speed-derived state shortly after.
4461
- if (stbDevice?.previousPowerState === Characteristic.Active.INACTIVE) {
4612
+ if (
4613
+ stbDevice?.previousPowerState ===
4614
+ Characteristic.Active.INACTIVE
4615
+ ) {
4462
4616
  currMediaState = Characteristic.CurrentMediaState.PLAY;
4463
4617
  if (this.debugLevel > 0) {
4464
4618
  this.log.warn(
4465
- "mqttClient: STB status: power-on transition for %s, setting mediaState to PLAY",
4619
+ "mqttClient: STB status: Power-on transition detected for %s, setting mediaState to PLAY",
4466
4620
  deviceId,
4467
4621
  );
4468
4622
  }
4469
- }
4623
+ }
4470
4624
  break;
4471
4625
  case "ONLINE_STANDBY": // ONLINE_STANDBY: power is off, device is on standby, still reachable over the network, can be turned on via mqtt.
4472
4626
  currStatusActive = Characteristic.Active.ACTIVE; // bool, 0 = not active, 1 = active
@@ -4496,6 +4650,12 @@ class StbPlatform {
4496
4650
  this.log.warn("mqttClient: %s %s", deviceId, stbState);
4497
4651
  }
4498
4652
  }
4653
+
4654
+ // After the switch, if box is running, request current UI state
4655
+ //if (stbState === 'ONLINE_RUNNING') {
4656
+ // Small delay gives the STB a moment to settle before responding
4657
+ //setTimeout(() => this.mqttRequestUiStatus(deviceId), 500);
4658
+ //}
4499
4659
  }
4500
4660
 
4501
4661
  // handle CPE UI status messages for the STB
@@ -4878,16 +5038,26 @@ class StbPlatform {
4878
5038
  return resolve(true);
4879
5039
  }
4880
5040
 
4881
- // unsubscribe from all subscribedTopics before tearing down the session
5041
+ // get all subscribed topics
4882
5042
  const topics = this.subscribedTopics ?? [];
5043
+
5044
+ // announce HGO offline while the connection is still live, before any teardown
5045
+ this.setHgoState(
5046
+ this.session.householdId,
5047
+ this.mqttClient.options.clientId,
5048
+ "OFFLINE",
5049
+ );
5050
+
5051
+ // unsubscribe from all subscribedTopics before tearing down the session
4883
5052
  if (topics.length === 0) {
4884
5053
  this.log.info(
4885
5054
  "mqttClient: No topics to unsubscribe from, skipping unsubscribe.",
4886
5055
  );
4887
- this.mqttClient.end(false, {}, (err) => {
4888
- if (err) {
4889
- this.log.error("MQTT end error:", err);
4890
- return reject(err);
5056
+
5057
+ this.mqttClient.end(false, {}, (endErr) => {
5058
+ if (endErr) {
5059
+ this.log.error("MQTT end error:", endErr);
5060
+ return reject(endErr);
4891
5061
  }
4892
5062
  this.log.info(
4893
5063
  "mqttClient: Disconnected cleanly. No topics found to unsubscribe from.",
@@ -4897,15 +5067,15 @@ class StbPlatform {
4897
5067
  return;
4898
5068
  }
4899
5069
 
4900
- this.mqttClient.unsubscribe(topics, (err) => {
4901
- if (err) {
4902
- this.log.error("MQTT unsubscribe error:", err);
5070
+ this.mqttClient.unsubscribe(topics, (unsubErr) => {
5071
+ if (unsubErr) {
5072
+ this.log.error("MQTT unsubscribe error:", unsubErr);
4903
5073
  // still attempt to end even if unsubscribe failed
4904
5074
  }
4905
- this.mqttClient.end(false, {}, (err) => {
4906
- if (err) {
4907
- this.log.error("MQTT end error:", err);
4908
- return reject(err);
5075
+ this.mqttClient.end(false, {}, (endErr) => {
5076
+ if (endErr) {
5077
+ this.log.error("MQTT end error:", endErr);
5078
+ return reject(endErr);
4909
5079
  }
4910
5080
  this.log.info(
4911
5081
  "mqttClient: Disconnected cleanly. All topics unsubscribed.",
@@ -4990,7 +5160,7 @@ class StbPlatform {
4990
5160
  "mqttPublishMessage: Publish Message:\r\nTopic: %s\r\nMessage: %s\r\nOptions: %s",
4991
5161
  Topic,
4992
5162
  Message,
4993
- Options,
5163
+ JSON.stringify(Options),
4994
5164
  );
4995
5165
  }
4996
5166
  this.mqttClient.publish(Topic, Message, Options, (err) => {
@@ -5081,19 +5251,20 @@ class StbPlatform {
5081
5251
  });
5082
5252
  }
5083
5253
 
5084
- // start the HGO session (switch on)
5085
- setHgoOnlineRunning(householdId, mqttClientId) {
5086
- // {"source":"fd29b575-5f2b-49a0-8efe-62a844ac2b40","state":"ONLINE_RUNNING","deviceType":"HGO","mac":"","ipAddress":""}
5254
+ // set the HGO session state (online or offline)
5255
+ // called on mqtt connect (ONLINE_RUNNING) and on mqtt disconnect (OFFLINE)
5256
+ // retain: true ensures the broker overwrites any previous retained state
5257
+ setHgoState(householdId, mqttClientId, state) {
5087
5258
  const topic = `${householdId}/${mqttClientId}/status`;
5088
5259
  const message = JSON.stringify({
5089
5260
  source: mqttClientId,
5090
- state: "ONLINE_RUNNING",
5261
+ state: state,
5091
5262
  deviceType: "HGO",
5092
5263
  mac: "",
5093
5264
  ipAddress: "",
5094
5265
  });
5095
5266
  if (this.debugLevel > 0) {
5096
- this.log.warn("setHgoOnlineRunning: publishing to topic:", topic);
5267
+ this.log.warn("setHgoState: publishing %s to topic: %s", state, topic);
5097
5268
  }
5098
5269
  this.mqttPublishMessage(topic, message, { qos: 2, retain: true });
5099
5270
  }
@@ -5151,6 +5322,39 @@ class StbPlatform {
5151
5322
  }
5152
5323
  }
5153
5324
 
5325
+ // Request the current UI status from the STB.
5326
+ // The STB responds with a CPE.uiStatus message on the household channel.
5327
+ // @param {string} deviceId - The STB device ID (e.g. "000378-EOS2STB-00852052xxxx")
5328
+ mqttRequestUiStatus(deviceId) {
5329
+ if (!this.mqttClient?.connected) {
5330
+ this.log.warn(
5331
+ "%s: mqttRequestUiStatus: MQTT not connected, skipping",
5332
+ deviceId,
5333
+ );
5334
+ return;
5335
+ }
5336
+ if (this.debugLevel > 0) {
5337
+ this.log.warn(
5338
+ "mqttRequestUiStatus: Requesting UI status for %s",
5339
+ deviceId,
5340
+ );
5341
+ }
5342
+
5343
+ const payload = JSON.stringify({
5344
+ version: "1.3.18",
5345
+ type: "CPE.pullFromTV",
5346
+ source: this.mqttClient.options.clientId, // your mqttClientId
5347
+ messageTimeStamp: Date.now(),
5348
+ });
5349
+
5350
+ const topic = `${this.session.householdId}/${deviceId}`;
5351
+
5352
+ this.mqttPublishMessage(topic, payload, {
5353
+ qos: 1,
5354
+ retain: false,
5355
+ });
5356
+ }
5357
+
5154
5358
  // set the media state of the settopbox via mqtt
5155
5359
  // media state is controlled by speedRate
5156
5360
  // speedRate can be one of: -64 -30 -6 -2 0 2 6 30 64. 0=Paused, 1=Play, >1=FastForward, <0=Rewind
@@ -6406,10 +6610,7 @@ class StbDevice {
6406
6610
  inputSourceService
6407
6611
  .getCharacteristic(Characteristic.ConfiguredName)
6408
6612
  .setProps({
6409
- perms: [
6410
- this.api.hap.Perms.PAIRED_READ,
6411
- this.api.hap.Perms.NOTIFY,
6412
- ],
6613
+ perms: [this.api.hap.Perms.PAIRED_READ, this.api.hap.Perms.NOTIFY],
6413
6614
  })
6414
6615
  .onGet(() => this.getInputName(i));
6415
6616
  //.onSet((value) => this.setInputName(i, value));
@@ -7800,7 +8001,10 @@ class StbDevice {
7800
8001
  // triple rapid VolDown presses triggers setMute
7801
8002
  if (volumeSelectorValue === Characteristic.VolumeSelector.DECREMENT) {
7802
8003
  // Guard: ensure array is properly initialised
7803
- if (!Array.isArray(this.lastVolDownKeyPress) || this.lastVolDownKeyPress.length < 3) {
8004
+ if (
8005
+ !Array.isArray(this.lastVolDownKeyPress) ||
8006
+ this.lastVolDownKeyPress.length < 3
8007
+ ) {
7804
8008
  this.lastVolDownKeyPress = [0, 0, 0];
7805
8009
  }
7806
8010
 
@@ -7809,14 +8013,14 @@ class StbDevice {
7809
8013
  this.lastVolDownKeyPress = this.lastVolDownKeyPress.slice(0, 3); // keep only last 3
7810
8014
 
7811
8015
  // Now assign the calculated value to the outer variable
7812
- tripleVolDownPress = this.lastVolDownKeyPress[0] - this.lastVolDownKeyPress[2];
8016
+ tripleVolDownPress =
8017
+ this.lastVolDownKeyPress[0] - this.lastVolDownKeyPress[2];
7813
8018
 
7814
8019
  this.log.debug(
7815
8020
  "%s: setVolume: Timediff between volDownKeyPress[0] and volDownKeyPress[2]: %s ms",
7816
8021
  this.name,
7817
8022
  tripleVolDownPress,
7818
- );
7819
-
8023
+ );
7820
8024
  }
7821
8025
 
7822
8026
  // check for triple press of volDown, send setMute if tripleVolDownPress less than triplePressTime of 800ms
@@ -7854,7 +8058,11 @@ class StbDevice {
7854
8058
 
7855
8059
  try {
7856
8060
  if (this.debugLevel > 0) {
7857
- this.log.warn("%s: setVolume: Sending command %s", this.name, command);
8061
+ this.log.warn(
8062
+ "%s: setVolume: Sending command %s",
8063
+ this.name,
8064
+ command,
8065
+ );
7858
8066
  }
7859
8067
  await new Promise((resolve, reject) => {
7860
8068
  exec(command, (error, _stdout, stderr) => {
@@ -8220,13 +8428,13 @@ class StbDevice {
8220
8428
  // logChangeOnly = TRUE: only the changes are logged, no media state change occurs. Needed when sending remote keypresses to prevent double commands
8221
8429
  // CHAR_NAMES: TargetMediaState: [ 'UUID', 'PLAY', 'PAUSE', 'STOP' ]
8222
8430
  //if (this.debugLevel > 1) {
8223
- this.log.info(
8224
- "%s: setTargetMediaState to %s [%s]",
8225
- this.name,
8226
- targetMediaState,
8227
- CHAR_NAMES.TargetMediaState[targetMediaState + 1],
8228
- );
8229
- // }
8431
+ this.log.info(
8432
+ "%s: setTargetMediaState to %s [%s]",
8433
+ this.name,
8434
+ targetMediaState,
8435
+ CHAR_NAMES.TargetMediaState[targetMediaState + 1],
8436
+ );
8437
+ // }
8230
8438
 
8231
8439
  if (!logChangeOnly) {
8232
8440
  // send the setMediaState command if we are not just logging the change
@@ -8239,16 +8447,17 @@ class StbDevice {
8239
8447
  // PLAY 0 - 1 Play
8240
8448
  // PAUSE 1 - 0 Paused
8241
8449
  // STOP 2 - 0 Paused
8242
- const newBoxMediaState = targetMediaState === Characteristic.TargetMediaState.PLAY ? 1 : 0;
8450
+ const newBoxMediaState =
8451
+ targetMediaState === Characteristic.TargetMediaState.PLAY ? 1 : 0;
8243
8452
  const newBoxMediaStateName = newBoxMediaState === 1 ? "Play" : "Paused";
8244
8453
 
8245
8454
  //if (this.debugLevel >= 0) {
8246
- this.log(
8247
- "%s: setTargetMediaState: Calling setMediaState with newBoxMediaState %s [%s]",
8248
- this.name,
8249
- newBoxMediaState,
8250
- newBoxMediaStateName,
8251
- );
8455
+ this.log(
8456
+ "%s: setTargetMediaState: Calling setMediaState with newBoxMediaState %s [%s]",
8457
+ this.name,
8458
+ newBoxMediaState,
8459
+ newBoxMediaStateName,
8460
+ );
8252
8461
  //}
8253
8462
  /*
8254
8463
  switch (targetMediaState) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge EOSSTB",
4
4
  "description": "Add your set-top box to Homekit (for Telenet BE, Sunrise CH, UPC SK, Virgin Media GB & IE, Ziggo NL)",
5
5
  "author": "Jochen Siegenthaler (https://github.com/jsiegenthaler/)",
6
- "version": "2.4.0-beta.1",
6
+ "version": "2.4.0-beta.3",
7
7
  "platformname": "eosstb",
8
8
  "dependencies": {
9
9
  "axios": "^1.16.0",