homebridge-eosstb 2.4.0-beta.2 → 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.
- package/CHANGELOG.md +21 -0
- package/README.md +1 -2
- package/index.js +227 -178
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ See the [Readme file](https://github.com/jsiegenthaler/homebridge-eosstb/blob/ma
|
|
|
5
5
|
Please restart Homebridge after every plugin update.
|
|
6
6
|
|
|
7
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
|
+
|
|
8
15
|
## 2.4.0-beta.2 (2026-05-09)
|
|
9
16
|
|
|
10
17
|
This release focusses on ensuring mqtt long-term stability, and fixes an issue where the channel name was not shown on startup.
|
|
@@ -44,6 +51,20 @@ This release represents a major rewrite of the plugin, significantly improving r
|
|
|
44
51
|
- Bumped dependency "tough-cookie": "^6.0.1",
|
|
45
52
|
- Bumped dependency "ws": "^8.20.0"
|
|
46
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
|
+
|
|
47
68
|
## 2.3.8 (2026-02-27)
|
|
48
69
|
|
|
49
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
|
[](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.
|
|
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,7 +355,8 @@ class StbPlatform {
|
|
|
355
355
|
this.accessories = [];
|
|
356
356
|
this.stbDevices = []; // store stbDevice in this.stbDevices
|
|
357
357
|
this.masterChannelList = [];
|
|
358
|
-
this.
|
|
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
|
|
360
361
|
this.mqttReconnecting = false; // nightly reconnect indicator
|
|
361
362
|
this.isDev = config.devMode === true;
|
|
@@ -856,9 +857,10 @@ class StbPlatform {
|
|
|
856
857
|
// refreshing to avoid a race condition during session startup
|
|
857
858
|
if (this.mqttReconnecting) {
|
|
858
859
|
const THREE_MIN_MS = 3 * 60 * 1000;
|
|
859
|
-
const retryDelayMs =
|
|
860
|
+
const retryDelayMs =
|
|
861
|
+
THREE_MIN_MS + Math.floor(Math.random() * THREE_MIN_MS);
|
|
860
862
|
this.log.info(
|
|
861
|
-
|
|
863
|
+
"StbPlatform: channel list refresh deferred - MQTT reconnect in progress, retrying in a few minutes",
|
|
862
864
|
);
|
|
863
865
|
this.checkChannelListTimeout = setTimeout(async () => {
|
|
864
866
|
if (this.isShuttingDown) return;
|
|
@@ -903,7 +905,6 @@ class StbPlatform {
|
|
|
903
905
|
}, msUntilReconnect);
|
|
904
906
|
} // end of _scheduleNightlyMqttReconnect
|
|
905
907
|
|
|
906
|
-
|
|
907
908
|
/**
|
|
908
909
|
* Attempt the nightly MQTT reconnect.
|
|
909
910
|
* If any STB is currently online (user may be watching), defers by 1 hour
|
|
@@ -926,7 +927,7 @@ class StbPlatform {
|
|
|
926
927
|
// give up if max retries reached
|
|
927
928
|
if (retryCount >= MAX_RETRIES) {
|
|
928
929
|
this.log.info(
|
|
929
|
-
|
|
930
|
+
"StbPlatform: nightly MQTT reconnect skipped - STB still active after max retries, rescheduling for next night",
|
|
930
931
|
);
|
|
931
932
|
this._scheduleNightlyMqttReconnect();
|
|
932
933
|
return;
|
|
@@ -935,7 +936,8 @@ class StbPlatform {
|
|
|
935
936
|
// retry in 1 hour plus a random 0–30 min buffer
|
|
936
937
|
const ONE_HOUR_MS = 60 * 60 * 1000;
|
|
937
938
|
const THIRTY_MIN_MS = 30 * 60 * 1000;
|
|
938
|
-
const retryDelayMs =
|
|
939
|
+
const retryDelayMs =
|
|
940
|
+
ONE_HOUR_MS + Math.floor(Math.random() * THIRTY_MIN_MS);
|
|
939
941
|
const retryAt = new Date(Date.now() + retryDelayMs);
|
|
940
942
|
|
|
941
943
|
this.log.info(
|
|
@@ -953,12 +955,15 @@ class StbPlatform {
|
|
|
953
955
|
// no STB is active - safe to reconnect
|
|
954
956
|
try {
|
|
955
957
|
this.mqttReconnecting = true; // signal to channel list refresh to pause
|
|
956
|
-
this.log.info(
|
|
958
|
+
this.log.info("StbPlatform: nightly MQTT reconnect starting...");
|
|
957
959
|
await this.endMqttSession();
|
|
958
960
|
await this.startMqttClient();
|
|
959
|
-
this.log.info(
|
|
961
|
+
this.log.info("StbPlatform: nightly MQTT reconnect completed");
|
|
960
962
|
} catch (err) {
|
|
961
|
-
this.log.error(
|
|
963
|
+
this.log.error(
|
|
964
|
+
"StbPlatform: nightly MQTT reconnect failed:",
|
|
965
|
+
err.message,
|
|
966
|
+
);
|
|
962
967
|
} finally {
|
|
963
968
|
this.mqttReconnecting = false; // always clear the flag, even on failure
|
|
964
969
|
}
|
|
@@ -983,7 +988,7 @@ class StbPlatform {
|
|
|
983
988
|
* 10. startMqttClient — connect to the mqtt broker
|
|
984
989
|
*
|
|
985
990
|
* Each step is individually awaited, so failures are caught at the right
|
|
986
|
-
* step and
|
|
991
|
+
* step and stepName is accurate when logged.
|
|
987
992
|
*
|
|
988
993
|
* @param {string} watchdogInstance - log prefix for this watchdog invocation
|
|
989
994
|
* @param {string} debugPrefix - debug() colour prefix
|
|
@@ -994,12 +999,13 @@ class StbPlatform {
|
|
|
994
999
|
Object.keys(sessionState)[this.currentSessionState],
|
|
995
1000
|
);
|
|
996
1001
|
|
|
997
|
-
//
|
|
1002
|
+
// stepName tracks which step we're on so the catch block can log a
|
|
998
1003
|
// meaningful message rather than a generic "something failed".
|
|
999
|
-
let
|
|
1004
|
+
let stepName = "";
|
|
1000
1005
|
|
|
1001
1006
|
try {
|
|
1002
1007
|
// ── Step 1: Get backend config (endpoint URLs) for the country ──────────
|
|
1008
|
+
stepName = "get config";
|
|
1003
1009
|
this.log.debug("%s: ++++ step 1: calling getConfig", watchdogInstance);
|
|
1004
1010
|
debug(debugPrefix + "calling getConfig");
|
|
1005
1011
|
|
|
@@ -1013,7 +1019,7 @@ class StbPlatform {
|
|
|
1013
1019
|
);
|
|
1014
1020
|
|
|
1015
1021
|
// ── Step 2: Authenticate and create a session ──────────────────────────
|
|
1016
|
-
|
|
1022
|
+
stepName = "create session";
|
|
1017
1023
|
this.log.debug(
|
|
1018
1024
|
"%s: ++++ step 2: calling createSession for country %s",
|
|
1019
1025
|
watchdogInstance,
|
|
@@ -1024,6 +1030,12 @@ class StbPlatform {
|
|
|
1024
1030
|
|
|
1025
1031
|
const sessionHouseholdId = await this.createSession();
|
|
1026
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
|
+
);
|
|
1027
1039
|
|
|
1028
1040
|
this.log.debug(
|
|
1029
1041
|
"%s: ++++++ step 2 done: session created, householdId %s",
|
|
@@ -1032,7 +1044,7 @@ class StbPlatform {
|
|
|
1032
1044
|
);
|
|
1033
1045
|
|
|
1034
1046
|
// ── Step 3: Fetch customer profile and assigned devices ────────────────
|
|
1035
|
-
|
|
1047
|
+
stepName = "discover platform";
|
|
1036
1048
|
this.log.debug(
|
|
1037
1049
|
"%s: ++++ step 3: calling getPersonalizationData for householdId %s",
|
|
1038
1050
|
watchdogInstance,
|
|
@@ -1128,7 +1140,7 @@ class StbPlatform {
|
|
|
1128
1140
|
}
|
|
1129
1141
|
|
|
1130
1142
|
// ── Step 8: Discover and configure HomeKit accessories ─────────────────
|
|
1131
|
-
|
|
1143
|
+
stepName = "discover devices";
|
|
1132
1144
|
this.log.debug(
|
|
1133
1145
|
"%s: ++++ step 8: calling discoverDevices",
|
|
1134
1146
|
watchdogInstance,
|
|
@@ -1146,7 +1158,7 @@ class StbPlatform {
|
|
|
1146
1158
|
);
|
|
1147
1159
|
|
|
1148
1160
|
// ── Step 9: Get the mqtt broker token ─────────────────────────────────
|
|
1149
|
-
|
|
1161
|
+
stepName = "start mqtt session";
|
|
1150
1162
|
this.log.debug("%s: ++++ step 9: calling getMqttToken", watchdogInstance);
|
|
1151
1163
|
debug(debugPrefix + "calling getMqttToken");
|
|
1152
1164
|
|
|
@@ -1183,15 +1195,66 @@ class StbPlatform {
|
|
|
1183
1195
|
watchdogInstance,
|
|
1184
1196
|
);
|
|
1185
1197
|
} catch (errorReason) {
|
|
1186
|
-
// One of the steps above threw or rejected. Log the failed
|
|
1187
|
-
//
|
|
1188
|
-
|
|
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);
|
|
1189
1205
|
this.currentSessionState = sessionState.DISCONNECTED;
|
|
1190
1206
|
this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
|
|
1191
1207
|
// sessionWatchdogRunning is reset in the finally block of the caller.
|
|
1192
1208
|
}
|
|
1193
1209
|
} // end of _runFullStartupSequence
|
|
1194
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
|
+
|
|
1195
1258
|
/**
|
|
1196
1259
|
* Discovers all physical devices from the backend and maps them to HomeKit accessories.
|
|
1197
1260
|
* Creates new accessories for uncached devices, and restores existing ones from cache.
|
|
@@ -2720,8 +2783,8 @@ class StbPlatform {
|
|
|
2720
2783
|
);
|
|
2721
2784
|
}
|
|
2722
2785
|
throw new Error(
|
|
2723
|
-
|
|
2724
|
-
|
|
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`,
|
|
2725
2788
|
);
|
|
2726
2789
|
}
|
|
2727
2790
|
|
|
@@ -3247,6 +3310,18 @@ class StbPlatform {
|
|
|
3247
3310
|
return;
|
|
3248
3311
|
}
|
|
3249
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
|
+
|
|
3250
3325
|
// exit immediately if channel list has not expired
|
|
3251
3326
|
if (Date.now() < this.masterChannelListExpiryDate) {
|
|
3252
3327
|
if (this.debugLevel > 1) {
|
|
@@ -3265,16 +3340,6 @@ class StbPlatform {
|
|
|
3265
3340
|
// syntax:
|
|
3266
3341
|
// https://prod.oesp.virginmedia.com/oesp/v4/GB/eng/web/channels?byLocationId=41043&includeInvisible=true&includeNotEntitled=true&personalised=true&sort=channelNumber
|
|
3267
3342
|
// https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH
|
|
3268
|
-
/*
|
|
3269
|
-
let url = COUNTRY_BASE_URLS[this.config.country.toLowerCase()] + '/channels';
|
|
3270
|
-
url = url + '?byLocationId=' + this.session.locationId // locationId needed to get user-specific list
|
|
3271
|
-
url = url + '&includeInvisible=true' // includeInvisible
|
|
3272
|
-
url = url + '&includeNotEntitled=true' // includeNotEntitled
|
|
3273
|
-
url = url + '&personalised=true' // personalised
|
|
3274
|
-
url = url + '&sort=channelNumber' // sort
|
|
3275
|
-
*/
|
|
3276
|
-
//url = 'https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH'
|
|
3277
|
-
//let url = COUNTRY_BASE_URLS[this.config.country.toLowerCase()] + '/eng/web/linear-service/v2/channels';
|
|
3278
3343
|
const url = new URL(`${this.configsvc.linearService.URL}/v2/channels`);
|
|
3279
3344
|
url.searchParams.set("cityId", this.customer.cityId);
|
|
3280
3345
|
url.searchParams.set("language", "en");
|
|
@@ -3283,6 +3348,7 @@ class StbPlatform {
|
|
|
3283
3348
|
if (this.debugLevel > 1) {
|
|
3284
3349
|
this.log.warn("refreshMasterChannelList: GET %s", url);
|
|
3285
3350
|
}
|
|
3351
|
+
|
|
3286
3352
|
try {
|
|
3287
3353
|
// call the webservice to get all available channels
|
|
3288
3354
|
const config = {
|
|
@@ -3297,6 +3363,29 @@ class StbPlatform {
|
|
|
3297
3363
|
"https://www.horizon.tv/",
|
|
3298
3364
|
},
|
|
3299
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
|
+
|
|
3300
3389
|
const response = await axiosWS(config);
|
|
3301
3390
|
if (this.debugLevel > 1) {
|
|
3302
3391
|
this.log.warn(
|
|
@@ -3309,7 +3398,6 @@ class StbPlatform {
|
|
|
3309
3398
|
response.data.length,
|
|
3310
3399
|
);
|
|
3311
3400
|
}
|
|
3312
|
-
//this.log(response.data);
|
|
3313
3401
|
|
|
3314
3402
|
// the header contains the following:
|
|
3315
3403
|
// Cache-Control: max-age=600, public, stale-if-error=43200
|
|
@@ -3350,6 +3438,9 @@ class StbPlatform {
|
|
|
3350
3438
|
this.masterChannelList.map((ch) => [ch.id, ch]),
|
|
3351
3439
|
);
|
|
3352
3440
|
|
|
3441
|
+
// record when we refreshed it
|
|
3442
|
+
this.masterChannelListRefreshedAt = Date.now()
|
|
3443
|
+
|
|
3353
3444
|
this.log(
|
|
3354
3445
|
"MasterChannelList contains %s channels, valid until %s",
|
|
3355
3446
|
this.masterChannelList.length,
|
|
@@ -3365,15 +3456,11 @@ class StbPlatform {
|
|
|
3365
3456
|
}
|
|
3366
3457
|
return this.masterChannelList;
|
|
3367
3458
|
} catch (error) {
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
3374
|
-
}
|
|
3375
|
-
this.log.warn("refreshMasterChannelList error:", errReason);
|
|
3376
|
-
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
|
+
);
|
|
3377
3464
|
}
|
|
3378
3465
|
} // end of refreshMasterChannelList
|
|
3379
3466
|
|
|
@@ -3438,16 +3525,11 @@ class StbPlatform {
|
|
|
3438
3525
|
this.configsvc = response.data; // store the entire config data for future use in this.configsvc
|
|
3439
3526
|
return this.configsvc;
|
|
3440
3527
|
} catch (error) {
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
this.log.debug(`getConfig error:`, error);
|
|
3450
|
-
throw new Error(errReason);
|
|
3528
|
+
this._handleWebError(
|
|
3529
|
+
error,
|
|
3530
|
+
`get config data for countryCode ${countryCode}`,
|
|
3531
|
+
url,
|
|
3532
|
+
);
|
|
3451
3533
|
}
|
|
3452
3534
|
} // end of getConfig
|
|
3453
3535
|
|
|
@@ -3598,20 +3680,11 @@ class StbPlatform {
|
|
|
3598
3680
|
//this.log.warn('getPersonalizationData: all done, returnng customerStatus: %s', this.customer.customerStatus);
|
|
3599
3681
|
return this.customer;
|
|
3600
3682
|
} catch (error) {
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
householdId
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
errReason = error.code + ": " + (error.hostname || "");
|
|
3607
|
-
// if no connection then set session to disconnected to force a session reconnect
|
|
3608
|
-
if (error.code === "ENOTFOUND") {
|
|
3609
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
//this.log('%s %s', errText, (errReason || ''));
|
|
3613
|
-
this.log.debug(`getPersonalizationData error:`, error);
|
|
3614
|
-
throw error;
|
|
3683
|
+
this._handleWebError(
|
|
3684
|
+
error,
|
|
3685
|
+
`get personalization data for household ${householdId}`,
|
|
3686
|
+
url,
|
|
3687
|
+
);
|
|
3615
3688
|
}
|
|
3616
3689
|
} // end of getPersonalizationData
|
|
3617
3690
|
|
|
@@ -3661,12 +3734,11 @@ class StbPlatform {
|
|
|
3661
3734
|
);
|
|
3662
3735
|
}
|
|
3663
3736
|
} catch (error) {
|
|
3664
|
-
this.
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3737
|
+
this._handleWebError(
|
|
3738
|
+
error,
|
|
3739
|
+
`set personalization data for device ${deviceId}`,
|
|
3740
|
+
url,
|
|
3668
3741
|
);
|
|
3669
|
-
this.log.debug("setPersonalizationDataForDevice: error:", error);
|
|
3670
3742
|
}
|
|
3671
3743
|
} // end of setPersonalizationDataForDevice
|
|
3672
3744
|
|
|
@@ -3725,15 +3797,11 @@ class StbPlatform {
|
|
|
3725
3797
|
}
|
|
3726
3798
|
return this.entitlements;
|
|
3727
3799
|
} catch (error) {
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
3734
|
-
}
|
|
3735
|
-
this.log.debug(`getEntitlements error:`, error);
|
|
3736
|
-
throw new Error(errReason);
|
|
3800
|
+
this._handleWebError(
|
|
3801
|
+
error,
|
|
3802
|
+
`get entitlements data for household ${householdId}`,
|
|
3803
|
+
url,
|
|
3804
|
+
);
|
|
3737
3805
|
}
|
|
3738
3806
|
} // end of getEntitlements
|
|
3739
3807
|
|
|
@@ -3910,29 +3978,11 @@ class StbPlatform {
|
|
|
3910
3978
|
|
|
3911
3979
|
return this.currentRecordingState;
|
|
3912
3980
|
} catch (error) {
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
" " +
|
|
3919
|
-
(error.hostname || "") +
|
|
3920
|
-
": " +
|
|
3921
|
-
(error.response?.status ?? "") +
|
|
3922
|
-
" " +
|
|
3923
|
-
(error.response?.statusText ?? "") +
|
|
3924
|
-
": " +
|
|
3925
|
-
(error.config?.url ?? "");
|
|
3926
|
-
if (error.code === "ENOTFOUND") {
|
|
3927
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
3928
|
-
}
|
|
3929
|
-
this.log.debug("getRecordingState error:", error);
|
|
3930
|
-
throw new Error(errReason);
|
|
3931
|
-
} else {
|
|
3932
|
-
this.log.warn("getRecordingState error:");
|
|
3933
|
-
this.log.warn(error);
|
|
3934
|
-
throw error;
|
|
3935
|
-
}
|
|
3981
|
+
this._handleWebError(
|
|
3982
|
+
error,
|
|
3983
|
+
`get recording status for household ${householdId}`,
|
|
3984
|
+
url,
|
|
3985
|
+
);
|
|
3936
3986
|
}
|
|
3937
3987
|
} // end of getRecordingState
|
|
3938
3988
|
|
|
@@ -4122,29 +4172,11 @@ class StbPlatform {
|
|
|
4122
4172
|
|
|
4123
4173
|
return this.currentRecordingState;
|
|
4124
4174
|
} catch (error) {
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
" " +
|
|
4131
|
-
(error.hostname || "") +
|
|
4132
|
-
": " +
|
|
4133
|
-
(error.response?.status ?? "") +
|
|
4134
|
-
" " +
|
|
4135
|
-
(error.response?.statusText ?? "") +
|
|
4136
|
-
": " +
|
|
4137
|
-
(error.config?.url ?? "");
|
|
4138
|
-
if (error.code === "ENOTFOUND") {
|
|
4139
|
-
this.currentSessionState = sessionState.DISCONNECTED;
|
|
4140
|
-
}
|
|
4141
|
-
this.log.debug("getRecordingBookings error:", error);
|
|
4142
|
-
throw new Error(errReason);
|
|
4143
|
-
} else {
|
|
4144
|
-
this.log.warn("getRecordingBookings error:");
|
|
4145
|
-
this.log.warn(error);
|
|
4146
|
-
throw error;
|
|
4147
|
-
}
|
|
4175
|
+
this._handleWebError(
|
|
4176
|
+
error,
|
|
4177
|
+
`get recording bookings for household ${householdId}`,
|
|
4178
|
+
url,
|
|
4179
|
+
);
|
|
4148
4180
|
}
|
|
4149
4181
|
} // end of getRecordingBookings
|
|
4150
4182
|
|
|
@@ -4181,14 +4213,11 @@ class StbPlatform {
|
|
|
4181
4213
|
this.log.warn(response.data);
|
|
4182
4214
|
return true;
|
|
4183
4215
|
} catch (error) {
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
}
|
|
4190
|
-
}
|
|
4191
|
-
this.log.warn(`getExperimentalEndpoint error:`, error);
|
|
4216
|
+
this._handleWebError(
|
|
4217
|
+
error,
|
|
4218
|
+
`get experimental endpoint for household ${householdId}`,
|
|
4219
|
+
url,
|
|
4220
|
+
);
|
|
4192
4221
|
}
|
|
4193
4222
|
} // end of getExperimentalEndpoint
|
|
4194
4223
|
|
|
@@ -4429,7 +4458,11 @@ class StbPlatform {
|
|
|
4429
4458
|
|
|
4430
4459
|
// announce ourselves as an active HGO client before requesting UI status
|
|
4431
4460
|
// the STB uses this retained presence message to decide which clients to respond to
|
|
4432
|
-
this.setHgoState(
|
|
4461
|
+
this.setHgoState(
|
|
4462
|
+
householdId,
|
|
4463
|
+
this.mqttClient.options.clientId,
|
|
4464
|
+
"ONLINE_RUNNING",
|
|
4465
|
+
);
|
|
4433
4466
|
|
|
4434
4467
|
// request initial UI status for each device, with a short delay to allow
|
|
4435
4468
|
// the STB to process the HGO presence announcement first
|
|
@@ -4441,7 +4474,10 @@ class StbPlatform {
|
|
|
4441
4474
|
setTimeout(() => {
|
|
4442
4475
|
this.devices.forEach((device) => {
|
|
4443
4476
|
// request the initial UI status for each device
|
|
4444
|
-
this.getUiStatus(
|
|
4477
|
+
this.getUiStatus(
|
|
4478
|
+
device.deviceId,
|
|
4479
|
+
this.mqttClient.options.clientId,
|
|
4480
|
+
);
|
|
4445
4481
|
|
|
4446
4482
|
// retry after 10 seconds if no CPE.uiStatus response has arrived yet
|
|
4447
4483
|
setTimeout(() => {
|
|
@@ -4452,7 +4488,10 @@ class StbPlatform {
|
|
|
4452
4488
|
device.deviceId,
|
|
4453
4489
|
);
|
|
4454
4490
|
}
|
|
4455
|
-
this.getUiStatus(
|
|
4491
|
+
this.getUiStatus(
|
|
4492
|
+
device.deviceId,
|
|
4493
|
+
this.mqttClient.options.clientId,
|
|
4494
|
+
);
|
|
4456
4495
|
}
|
|
4457
4496
|
}, 10 * 1000); // 10 second retry delay
|
|
4458
4497
|
});
|
|
@@ -4556,7 +4595,8 @@ class StbPlatform {
|
|
|
4556
4595
|
const deviceIndex = this.devices.findIndex(
|
|
4557
4596
|
(device) => device.deviceId === deviceId,
|
|
4558
4597
|
);
|
|
4559
|
-
const stbDevice =
|
|
4598
|
+
const stbDevice =
|
|
4599
|
+
deviceIndex > -1 ? this.stbDevices[deviceIndex] : null;
|
|
4560
4600
|
|
|
4561
4601
|
// Box setting: StandbyPowerConsumption = FastStart / ActiveStart / EcoSlowstart
|
|
4562
4602
|
// "Fast start": when turned off, goes to ONLINE_STANDBY and stays there. Box can be turned on via mqtt
|
|
@@ -4569,7 +4609,10 @@ class StbPlatform {
|
|
|
4569
4609
|
// Detect power-off → power-on transition per device.
|
|
4570
4610
|
// Set PLAY immediately; CPE.uiStatus will overwrite with the
|
|
4571
4611
|
// accurate speed-derived state shortly after.
|
|
4572
|
-
if (
|
|
4612
|
+
if (
|
|
4613
|
+
stbDevice?.previousPowerState ===
|
|
4614
|
+
Characteristic.Active.INACTIVE
|
|
4615
|
+
) {
|
|
4573
4616
|
currMediaState = Characteristic.CurrentMediaState.PLAY;
|
|
4574
4617
|
if (this.debugLevel > 0) {
|
|
4575
4618
|
this.log.warn(
|
|
@@ -4577,7 +4620,7 @@ class StbPlatform {
|
|
|
4577
4620
|
deviceId,
|
|
4578
4621
|
);
|
|
4579
4622
|
}
|
|
4580
|
-
}
|
|
4623
|
+
}
|
|
4581
4624
|
break;
|
|
4582
4625
|
case "ONLINE_STANDBY": // ONLINE_STANDBY: power is off, device is on standby, still reachable over the network, can be turned on via mqtt.
|
|
4583
4626
|
currStatusActive = Characteristic.Active.ACTIVE; // bool, 0 = not active, 1 = active
|
|
@@ -4610,9 +4653,9 @@ class StbPlatform {
|
|
|
4610
4653
|
|
|
4611
4654
|
// After the switch, if box is running, request current UI state
|
|
4612
4655
|
//if (stbState === 'ONLINE_RUNNING') {
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
//}
|
|
4656
|
+
// Small delay gives the STB a moment to settle before responding
|
|
4657
|
+
//setTimeout(() => this.mqttRequestUiStatus(deviceId), 500);
|
|
4658
|
+
//}
|
|
4616
4659
|
}
|
|
4617
4660
|
|
|
4618
4661
|
// handle CPE UI status messages for the STB
|
|
@@ -5002,7 +5045,7 @@ class StbPlatform {
|
|
|
5002
5045
|
this.setHgoState(
|
|
5003
5046
|
this.session.householdId,
|
|
5004
5047
|
this.mqttClient.options.clientId,
|
|
5005
|
-
|
|
5048
|
+
"OFFLINE",
|
|
5006
5049
|
);
|
|
5007
5050
|
|
|
5008
5051
|
// unsubscribe from all subscribedTopics before tearing down the session
|
|
@@ -5010,7 +5053,7 @@ class StbPlatform {
|
|
|
5010
5053
|
this.log.info(
|
|
5011
5054
|
"mqttClient: No topics to unsubscribe from, skipping unsubscribe.",
|
|
5012
5055
|
);
|
|
5013
|
-
|
|
5056
|
+
|
|
5014
5057
|
this.mqttClient.end(false, {}, (endErr) => {
|
|
5015
5058
|
if (endErr) {
|
|
5016
5059
|
this.log.error("MQTT end error:", endErr);
|
|
@@ -5216,17 +5259,16 @@ class StbPlatform {
|
|
|
5216
5259
|
const message = JSON.stringify({
|
|
5217
5260
|
source: mqttClientId,
|
|
5218
5261
|
state: state,
|
|
5219
|
-
deviceType:
|
|
5220
|
-
mac:
|
|
5221
|
-
ipAddress:
|
|
5262
|
+
deviceType: "HGO",
|
|
5263
|
+
mac: "",
|
|
5264
|
+
ipAddress: "",
|
|
5222
5265
|
});
|
|
5223
5266
|
if (this.debugLevel > 0) {
|
|
5224
|
-
this.log.warn(
|
|
5267
|
+
this.log.warn("setHgoState: publishing %s to topic: %s", state, topic);
|
|
5225
5268
|
}
|
|
5226
5269
|
this.mqttPublishMessage(topic, message, { qos: 2, retain: true });
|
|
5227
5270
|
}
|
|
5228
5271
|
|
|
5229
|
-
|
|
5230
5272
|
// send a channel change request to the settopbox via mqtt
|
|
5231
5273
|
// using the CPE.pushToTV message
|
|
5232
5274
|
// the friendlyDeviceName appears on the TV in a popup window
|
|
@@ -5285,7 +5327,10 @@ class StbPlatform {
|
|
|
5285
5327
|
// @param {string} deviceId - The STB device ID (e.g. "000378-EOS2STB-00852052xxxx")
|
|
5286
5328
|
mqttRequestUiStatus(deviceId) {
|
|
5287
5329
|
if (!this.mqttClient?.connected) {
|
|
5288
|
-
this.log.warn(
|
|
5330
|
+
this.log.warn(
|
|
5331
|
+
"%s: mqttRequestUiStatus: MQTT not connected, skipping",
|
|
5332
|
+
deviceId,
|
|
5333
|
+
);
|
|
5289
5334
|
return;
|
|
5290
5335
|
}
|
|
5291
5336
|
if (this.debugLevel > 0) {
|
|
@@ -5296,9 +5341,9 @@ class StbPlatform {
|
|
|
5296
5341
|
}
|
|
5297
5342
|
|
|
5298
5343
|
const payload = JSON.stringify({
|
|
5299
|
-
version:
|
|
5300
|
-
type:
|
|
5301
|
-
source: this.mqttClient.options.clientId,
|
|
5344
|
+
version: "1.3.18",
|
|
5345
|
+
type: "CPE.pullFromTV",
|
|
5346
|
+
source: this.mqttClient.options.clientId, // your mqttClientId
|
|
5302
5347
|
messageTimeStamp: Date.now(),
|
|
5303
5348
|
});
|
|
5304
5349
|
|
|
@@ -5308,8 +5353,7 @@ class StbPlatform {
|
|
|
5308
5353
|
qos: 1,
|
|
5309
5354
|
retain: false,
|
|
5310
5355
|
});
|
|
5311
|
-
|
|
5312
|
-
}
|
|
5356
|
+
}
|
|
5313
5357
|
|
|
5314
5358
|
// set the media state of the settopbox via mqtt
|
|
5315
5359
|
// media state is controlled by speedRate
|
|
@@ -6566,10 +6610,7 @@ class StbDevice {
|
|
|
6566
6610
|
inputSourceService
|
|
6567
6611
|
.getCharacteristic(Characteristic.ConfiguredName)
|
|
6568
6612
|
.setProps({
|
|
6569
|
-
perms: [
|
|
6570
|
-
this.api.hap.Perms.PAIRED_READ,
|
|
6571
|
-
this.api.hap.Perms.NOTIFY,
|
|
6572
|
-
],
|
|
6613
|
+
perms: [this.api.hap.Perms.PAIRED_READ, this.api.hap.Perms.NOTIFY],
|
|
6573
6614
|
})
|
|
6574
6615
|
.onGet(() => this.getInputName(i));
|
|
6575
6616
|
//.onSet((value) => this.setInputName(i, value));
|
|
@@ -7960,7 +8001,10 @@ class StbDevice {
|
|
|
7960
8001
|
// triple rapid VolDown presses triggers setMute
|
|
7961
8002
|
if (volumeSelectorValue === Characteristic.VolumeSelector.DECREMENT) {
|
|
7962
8003
|
// Guard: ensure array is properly initialised
|
|
7963
|
-
if (
|
|
8004
|
+
if (
|
|
8005
|
+
!Array.isArray(this.lastVolDownKeyPress) ||
|
|
8006
|
+
this.lastVolDownKeyPress.length < 3
|
|
8007
|
+
) {
|
|
7964
8008
|
this.lastVolDownKeyPress = [0, 0, 0];
|
|
7965
8009
|
}
|
|
7966
8010
|
|
|
@@ -7969,14 +8013,14 @@ class StbDevice {
|
|
|
7969
8013
|
this.lastVolDownKeyPress = this.lastVolDownKeyPress.slice(0, 3); // keep only last 3
|
|
7970
8014
|
|
|
7971
8015
|
// Now assign the calculated value to the outer variable
|
|
7972
|
-
tripleVolDownPress =
|
|
8016
|
+
tripleVolDownPress =
|
|
8017
|
+
this.lastVolDownKeyPress[0] - this.lastVolDownKeyPress[2];
|
|
7973
8018
|
|
|
7974
8019
|
this.log.debug(
|
|
7975
8020
|
"%s: setVolume: Timediff between volDownKeyPress[0] and volDownKeyPress[2]: %s ms",
|
|
7976
8021
|
this.name,
|
|
7977
8022
|
tripleVolDownPress,
|
|
7978
|
-
);
|
|
7979
|
-
|
|
8023
|
+
);
|
|
7980
8024
|
}
|
|
7981
8025
|
|
|
7982
8026
|
// check for triple press of volDown, send setMute if tripleVolDownPress less than triplePressTime of 800ms
|
|
@@ -8014,7 +8058,11 @@ class StbDevice {
|
|
|
8014
8058
|
|
|
8015
8059
|
try {
|
|
8016
8060
|
if (this.debugLevel > 0) {
|
|
8017
|
-
this.log.warn(
|
|
8061
|
+
this.log.warn(
|
|
8062
|
+
"%s: setVolume: Sending command %s",
|
|
8063
|
+
this.name,
|
|
8064
|
+
command,
|
|
8065
|
+
);
|
|
8018
8066
|
}
|
|
8019
8067
|
await new Promise((resolve, reject) => {
|
|
8020
8068
|
exec(command, (error, _stdout, stderr) => {
|
|
@@ -8380,13 +8428,13 @@ class StbDevice {
|
|
|
8380
8428
|
// logChangeOnly = TRUE: only the changes are logged, no media state change occurs. Needed when sending remote keypresses to prevent double commands
|
|
8381
8429
|
// CHAR_NAMES: TargetMediaState: [ 'UUID', 'PLAY', 'PAUSE', 'STOP' ]
|
|
8382
8430
|
//if (this.debugLevel > 1) {
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8431
|
+
this.log.info(
|
|
8432
|
+
"%s: setTargetMediaState to %s [%s]",
|
|
8433
|
+
this.name,
|
|
8434
|
+
targetMediaState,
|
|
8435
|
+
CHAR_NAMES.TargetMediaState[targetMediaState + 1],
|
|
8436
|
+
);
|
|
8437
|
+
// }
|
|
8390
8438
|
|
|
8391
8439
|
if (!logChangeOnly) {
|
|
8392
8440
|
// send the setMediaState command if we are not just logging the change
|
|
@@ -8399,16 +8447,17 @@ class StbDevice {
|
|
|
8399
8447
|
// PLAY 0 - 1 Play
|
|
8400
8448
|
// PAUSE 1 - 0 Paused
|
|
8401
8449
|
// STOP 2 - 0 Paused
|
|
8402
|
-
const newBoxMediaState =
|
|
8450
|
+
const newBoxMediaState =
|
|
8451
|
+
targetMediaState === Characteristic.TargetMediaState.PLAY ? 1 : 0;
|
|
8403
8452
|
const newBoxMediaStateName = newBoxMediaState === 1 ? "Play" : "Paused";
|
|
8404
8453
|
|
|
8405
8454
|
//if (this.debugLevel >= 0) {
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8455
|
+
this.log(
|
|
8456
|
+
"%s: setTargetMediaState: Calling setMediaState with newBoxMediaState %s [%s]",
|
|
8457
|
+
this.name,
|
|
8458
|
+
newBoxMediaState,
|
|
8459
|
+
newBoxMediaStateName,
|
|
8460
|
+
);
|
|
8412
8461
|
//}
|
|
8413
8462
|
/*
|
|
8414
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.
|
|
6
|
+
"version": "2.4.0-beta.3",
|
|
7
7
|
"platformname": "eosstb",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"axios": "^1.16.0",
|