homebridge-eosstb 2.4.0-alpha.47 → 2.4.0-beta.2

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 (3) hide show
  1. package/CHANGELOG.md +24 -253
  2. package/index.js +211 -51
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,274 +4,45 @@ 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
- # Bug Fixes and Improvements
8
7
 
9
- ## Current To-Do and In-Work List (For Future Releases, in rough order of priority):
8
+ ## 2.4.0-beta.2 (2026-05-09)
10
9
 
11
- TODO
12
- review all http endpoints, make sure a suitable log entry is being made so I can check the data is plausible
10
+ This release focusses on ensuring mqtt long-term stability, and fixes an issue where the channel name was not shown on startup.
13
11
 
14
- configsvc
15
- authorizationService DONE
16
- personalizationService DONE
17
- purchaseService
18
- recordingService
19
- linearService
20
- sessionService
12
+ - Rescheduled nightly channel list refresh to 0000-0400 instead of 0000-0600
13
+ - 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
14
+ - Fixed issue where current channel was not displayed on plugin startup
21
15
 
22
- IMPORTANR
23
- Implement setTargetMediaState to send the Play/Pause/Stop functions, so that Eve can control the media state. Watch out for sending commands twice when using the remote control. maybe the sendKey redirects to the setmediaState?
24
16
 
25
- Behavious: SetMediaState from 0 to 1 will start playing.
26
- Behavious: SetMediaState from 0 to 1 will start playing.
27
- Behavious: SetMediaState from 2 to 1 will start playing.
28
- Behavious: SetMediaState from 1 to 2 will start playing.
29
- unreliable, compare to webclient... this is driving me crazy!
30
- might have to revert to sendKeys to set Play and Stop/Pause
17
+ ## 2.4.0-beta.1 (2026-05-09)
31
18
 
19
+ This release represents a major rewrite of the plugin, significantly improving robustness, HAP compliance, and code quality throughout, and making it work for Switzerland.
32
20
 
21
+ - Added CH login support: Adapted the login sequence and updated config.schema.json to support the new Switzerland login method
22
+ - Overhauled HAP compliance: Full audit and rewrite of all HAP code for strict compliance
23
+ - Added support for Homebridge v2: Plugin now fully supports Homebridge 2.0.0+ and Node.js 24+; resolved breaking changes introduced by HAP-NodeJS v1 shipped with Homebridge v2
24
+ - Improved MQTT handling: Optimised mqtt code and added clean unsubscribe on plugin shutdown
25
+ - Improved channel list management: Master channel list refresh now runs once daily at a random time between 00:00–06:00
26
+ - Updated channel ConfiguredName to read-only
27
+ - Optimised handling of remote control and media control
28
+ - Improved robustness and startup
29
+ - Improved overall code quality
30
+ - Updated dependencies to current versions
33
31
 
34
- CHECK
35
- make sendKey logging consistent
32
+ ## 2.3.9 (2026-05-09)
36
33
 
37
- CHECK
38
- what are my issues with persistence?
39
- Persisting the input state visibility between restarts would be good
40
- Visibility cannot be set if the user has no WriteAccess to the channel list!!
41
- Persistance test:
42
- 1. Hide the channel SRF info using Home app
43
- 2. Force-close and reopen the home app. Confirm channel is still hidden
44
- 3. Confirm the current visibility state in the plugin logs is correct
45
- 3. Restart the plugin
46
- 4. Observe the current visibility state in the plugin logs - does it get the right value from HomeKit HAP? If so, no persistance required.
47
-
48
- Device name: this comes from backend
49
- Channel name: overwrite not allowed - can we disable in HomeKit. Yes, but prevents setting visibility
50
-
51
- CHALLENGE
52
- The mostWatchedChannelList is not working
53
-
54
- ## 2.4.0-alpha.47 (2026-05-09)
55
- - Improved the startup default setting of media state when a power-on transition is detected
56
-
57
- ## 2.4.0-alpha.46 (2026-05-09)
58
- - Fixed the issue with volume commands
59
-
60
- ## 2.4.0-alpha.45 (2026-05-09)
61
- - Added some debugging to the volume commands to trace an issue
62
-
63
- ## 2.4.0-alpha.44 (2026-05-09)
64
- - Fixed some log level issues
65
- - Fixed issue with Remove volumeDown causing Unhandled error thrown inside write handler for characteristic
66
-
67
- ## 2.4.0-alpha.43 (2026-05-09)
68
- - Bumped dependency "semver": "^7.8.0",
69
-
70
- ## 2.4.0-alpha.42 (2026-05-07)
71
- - changed the name of hidden channels from HIDDEN_xx to HIDDENxx to stop the HAP-NodeJS WARNING: The accessory 'HIDDEN_32' has an invalid 'Name' characteristic
72
-
73
- ## 2.4.0-alpha.42 (2026-05-07)
74
- - changed the name of hidden channels from HIDDEN_xx to HIDDENxx to stop the HAP-NodeJS WARNING: The accessory 'HIDDEN_32' has an invalid 'Name' characteristic
75
-
76
- ## 2.4.0-alpha.41 (2026-05-07)
77
- - fixed typos in this change log
78
-
79
- ## 2.4.0-alpha.40 (2026-05-07)
80
- - Changes to code to support HAP-NodeJS still works with Homebridge v1.11.x
81
- - Reinstated engine "homebridge": "^1.11.4||^2.0.0",
82
-
83
- ## 2.4.0-alpha.39 (2026-05-07)
84
- - fixed issues caused by HAP-NodeJS v1 released with Homebridge v2 where use of enums off the Characteristic class is no longer supported
85
- - due to these HAP-NodeJS changes, support for Homebridge versions below 2.0.0 is no longer provided
86
- - Bumped engine "homebridge": "^2.0.0",
34
+ - Fixed Error on Homebridge v2: Cannot read properties of undefined (reading 'STRING')
35
+ - Adapted hidden channel name to reduce warning messages with Homebridge v2
36
+ - Updated iOS and Homebridge version references in Readme
37
+ - Bumped engine "^1.11.4||^2.0.0",
87
38
  - Bumped engine "node": "^24.15.0"
88
39
  - Bumped dependency "axios": "^1.16.0",
89
40
  - Bumped dependency "axios-cookiejar-support": "^7.0.0",
90
41
  - Bumped dependency "mqtt": "^5.15.1",
91
- - Bumped dependency "puppeteer-core": "^24.43.0",
92
42
  - Bumped dependency "qs": "^6.15.1",
43
+ - Bumped dependency "semver": "^7.8.0",
93
44
  - Bumped dependency "tough-cookie": "^6.0.1",
94
- - Bumped dependency "ws": "^8.20.0"
95
-
96
- ## 2.4.0-alpha.38 (2026-03-07)
97
-
98
- - changes to settargetmediastate & setMediaState
99
-
100
- ## 2.4.0-alpha.37 (2026-03-07)
101
-
102
- - changes to settargetmediastate
103
-
104
- ## 2.4.0-alpha.36 (2026-03-07)
105
-
106
- - changes to settargetmediastate
107
-
108
- ## 2.4.0-alpha.35 (2026-03-07)
109
-
110
- - changes to settargetmediastate
111
-
112
- ## 2.4.0-alpha.34 (2026-03-07)
113
-
114
- - changes to settargetmediastate
115
-
116
- ## 2.4.0-alpha.33 (2026-03-07)
117
-
118
- - optimised getmqtttoken
119
- - checked function of setTargetMediaState , needs testing, controlled by Eve
120
-
121
- ## 2.4.0-alpha.32 (2026-03-07)
122
-
123
- - made all urls into proper url objects
124
- - fixed issue with getMostWatchedChannels
125
-
126
- ## 2.4.0-alpha.31 (2026-03-07)
127
-
128
- - change the master channel list refresh to once a day at a random time between 0000 and 0600. The user setting has no impact and can be removed
129
- - cleaned up some more diagnostic logging
130
-
131
- ## 2.4.0-alpha.30 (2026-03-07)
132
-
133
- - cleaned up some diagnostic logging
134
-
135
- ## 2.4.0-alpha.29 (2026-03-06)
136
-
137
- - optimised refreshDeviceChannelList
138
-
139
- ## 2.4.0-alpha.28 (2026-03-06)
140
-
141
- - fixed issue with detecting status, introduce 2 versions ago
142
-
143
- ## 2.4.0-alpha.27 (2026-03-06)
144
-
145
- -- removed test code to detect plugin shutdown
146
-
147
- ## 2.4.0-alpha.26 (2026-03-06)
148
-
149
- -- added test code to detect plugin shutdown
150
- - added clean unsubscribe on plugin shutdown
151
-
152
- ## 2.4.0-alpha.25 (2026-03-05)
153
-
154
- - fixed introduced bug in setRemoteKey
155
-
156
- ## 2.4.0-alpha.24 (2026-03-05)
157
-
158
- - made channel ConfiguredName read only as the backend defines the channel name
159
-
160
- ## 2.4.0-alpha.23 (2026-03-05)
161
-
162
- - optimised setRemoteKey
163
-
164
- ## 2.4.0-alpha.22 (2026-03-05)
165
-
166
- - fixed error handling in refreshMasterChannelList
167
-
168
- ## 2.4.0-alpha.21 (2026-03-05)
169
-
170
- - fixed logging bug in sendKey
171
-
172
- ## 2.4.0-alpha.20 (2026-03-05)
173
-
174
- - improved accessory information display to show a better serialnumber
175
- - updated set-top box model name for 2008C
176
- - added more robustness to many calls in case we never get a configsvc response
177
- - optimised the mqttDeviceStateHandler
178
- - improved refreshMasterChannelList
179
- - improved a lot of webservice calls, removing promises and going to try-catch
180
-
181
- ## 2.4.0-alpha.19 (2026-03-04)
182
-
183
- - removed some dead code
184
- - enabled handler for setClosedCaptions and setPictureMode, test control from Eve app
185
- - improved send key logging
186
-
187
- ## 2.4.0-alpha.18 (2026-03-03)
188
-
189
- - Fixed dislayOrder crash on startup - check this!
190
- - Optimised post publishExternalAccessories HAP updates
191
- - Changed all updateValue to updateCharacteristic post-publish to be more HAP compliant
192
-
193
- ## 2.4.0-alpha.17 (2026-03-03)
194
-
195
- - Cleaned up the platform code
196
- - Changed all get/set handlers to async
197
- - Optimised all get/set handlers
198
- - More HAP minor bug fixes and optimisations
199
-
200
- ## 2.4.0-alpha.16 (2026-03-03)
201
-
202
- - Fixed bug: TypeError: this.prepareinputSourceServices is not a function
203
-
204
- ## 2.4.0-alpha.15 (2026-03-03)
205
-
206
- - Renamed Current Channnel Id and Current Channel Name to Active Channel Id and Active Channel Name for consistency with Active Identifier
207
-
208
- ## 2.4.0-alpha.14 (2026-03-03)
209
-
210
- - Optimised HAP code for strict compliance
211
- - Removed debug logging
212
-
213
- ## 2.4.0-alpha.13 (2026-03-03)
214
-
215
- - Fixes to logging of getInputName for diagnostics
216
-
217
- ## 2.4.0-alpha.12 (2026-03-03)
218
-
219
- - More improvements to get ConfiguredName working properly on the Eve app (needs testing for both TV and Inputs)
220
-
221
- ## 2.4.0-alpha.11 (2026-03-03)
222
-
223
- - Fixed logging bug in setInputName
224
-
225
- ## 2.4.0-alpha.10 (2026-03-03)
226
-
227
- ## 2.4.0-alpha.9 (2026-03-03)
228
-
229
- ## 2.4.0-alpha.8 (2026-03-03)
230
-
231
- - More improvements to get ConfiguredName working properly on the Eve app (needs testing for both TV and Inputs)
232
-
233
- ## 2.4.0-alpha.7 (2026-03-03)
234
-
235
- - Corrected some debug log levels
236
- - Fixed ConfiguredName being empty on the Eve app (needs testing for both TV and Inputs)
237
-
238
- ## 2.4.0-alpha.6 (2026-03-02)
239
-
240
- - Removed getMute and getVolume, these are not supported
241
- - More code optimisations
242
-
243
- ## 2.4.0-alpha.5 (2026-03-02)
244
-
245
- - More code performance improvements
246
- - Improved all HAP code
247
-
248
- ## 2.4.0-alpha.4 (2026-03-02)
249
-
250
- - Fixed self bug in volume control
251
-
252
- ## 2.4.0-alpha.4 (2026-03-02)
253
-
254
- - Fixed self bug in volume control
255
-
256
- ## 2.4.0-alpha.3 (2026-03-02)
257
-
258
- - Fixed new introduced bug with displayed channel being incorrect (offset by 1)
259
- - Added "devMode": true support to config.json
260
-
261
- ## 2.4.0-alpha.2 (2026-03-01)
262
-
263
- - Improved README.md text
264
- - Improved config.schema.json description text
265
- - Improved discovery of devices and accessory setup
266
- - Improved mqtt handling
267
- - Improved overall code robustness and fixed many small bugs
268
- - Fixed spelling mistakes in comments
269
-
270
- ## 2.4.0-alpha.1 (2026-02-27)
271
-
272
- - Adapted login sequence for CH
273
- - Updated config.schema.json to support new CH login method and improced description texts
274
- - Bumped dependency "axios": "^1.13.6",
45
+ - Bumped dependency "ws": "^8.20.0"
275
46
 
276
47
  ## 2.3.8 (2026-02-27)
277
48
 
package/index.js CHANGED
@@ -357,6 +357,7 @@ class StbPlatform {
357
357
  this.masterChannelList = [];
358
358
  this.masterChannelListExpiryDate = 0; // epoch = always expired on first run
359
359
  this.checkChannelListTimeout = null; // nightly scheduler handler
360
+ this.mqttReconnecting = false; // nightly reconnect indicator
360
361
  this.isDev = config.devMode === true;
361
362
  this.debugLevel = this.config.debugLevel || 0; // debugLevel defaults to 0 (minimum)
362
363
 
@@ -822,7 +823,7 @@ class StbPlatform {
822
823
 
823
824
  /**
824
825
  * Schedule the next nightly master channel list refresh.
825
- * Picks a random time between 00:00 and 06:00 the following day,
826
+ * Picks a random time between 00:00 and 04:00 the following day,
826
827
  * then reschedules itself so the pattern repeats indefinitely.
827
828
  *
828
829
  * Using setTimeout (not setInterval) means each day gets a fresh
@@ -834,9 +835,9 @@ class StbPlatform {
834
835
  tomorrow.setDate(tomorrow.getDate() + 1);
835
836
  tomorrow.setHours(0, 0, 0, 0);
836
837
 
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);
838
+ // Add a random offset: anywhere from 0 ms up to (but not including) 4 hours
839
+ const FOUR_HOURS_MS = 4 * 60 * 60 * 1000;
840
+ const randomOffsetMs = Math.floor(Math.random() * FOUR_HOURS_MS);
840
841
 
841
842
  const nextRefreshAt = new Date(tomorrow.getTime() + randomOffsetMs);
842
843
  const msUntilRefresh = nextRefreshAt.getTime() - Date.now();
@@ -850,11 +851,121 @@ class StbPlatform {
850
851
  // Store the timer handle so shutdown can cancel it
851
852
  this.checkChannelListTimeout = setTimeout(async () => {
852
853
  if (this.isShuttingDown) return; // bail out if we're going down
854
+
855
+ // if an MQTT reconnect is in progress, wait a few minutes before
856
+ // refreshing to avoid a race condition during session startup
857
+ if (this.mqttReconnecting) {
858
+ const THREE_MIN_MS = 3 * 60 * 1000;
859
+ const retryDelayMs = THREE_MIN_MS + Math.floor(Math.random() * THREE_MIN_MS);
860
+ this.log.info(
861
+ 'StbPlatform: channel list refresh deferred - MQTT reconnect in progress, retrying in a few minutes',
862
+ );
863
+ this.checkChannelListTimeout = setTimeout(async () => {
864
+ if (this.isShuttingDown) return;
865
+ await this._refreshChannelList();
866
+ this._scheduleNightlyChannelListRefresh();
867
+ }, retryDelayMs);
868
+ return;
869
+ }
870
+
853
871
  await this._refreshChannelList();
854
872
  this._scheduleNightlyChannelListRefresh(); // reschedule for the next day
855
873
  }, msUntilRefresh);
856
874
  } // end of _scheduleNightlyChannelListRefresh
857
875
 
876
+ /**
877
+ * Schedule the next nightly MQTT reconnect.
878
+ * Picks a random time between 04:00 and 06:00 the following day
879
+ * to avoid overlapping with the channel list refresh (00:00–04:00).
880
+ * Reschedules itself so the pattern repeats indefinitely.
881
+ */
882
+ _scheduleNightlyMqttReconnect() {
883
+ const tomorrow = new Date();
884
+ tomorrow.setDate(tomorrow.getDate() + 1);
885
+ tomorrow.setHours(4, 0, 0, 0);
886
+
887
+ const TWO_HOURS_MS = 2 * 60 * 60 * 1000;
888
+ const randomOffsetMs = Math.floor(Math.random() * TWO_HOURS_MS);
889
+
890
+ const nextReconnectAt = new Date(tomorrow.getTime() + randomOffsetMs);
891
+ const msUntilReconnect = nextReconnectAt.getTime() - Date.now();
892
+
893
+ if (this.debugLevel > 0) {
894
+ this.log.warn(
895
+ `StbPlatform: next nightly MQTT reconnect scheduled for ${nextReconnectAt.toLocaleString()}`,
896
+ );
897
+ }
898
+
899
+ this.mqttReconnectTimeout = setTimeout(async () => {
900
+ if (this.isShuttingDown) return;
901
+ await this._attemptNightlyMqttReconnect();
902
+ // _attemptNightlyMqttReconnect reschedules for the next night once done
903
+ }, msUntilReconnect);
904
+ } // end of _scheduleNightlyMqttReconnect
905
+
906
+
907
+ /**
908
+ * Attempt the nightly MQTT reconnect.
909
+ * If any STB is currently online (user may be watching), defers by 1 hour
910
+ * plus a random offset and tries again, rather than interrupting the session.
911
+ * Once the reconnect completes (or fails), reschedules for the next night.
912
+ * Retries 3 times then gives up, and the next reconnect will be the next day.
913
+ */
914
+ async _attemptNightlyMqttReconnect(retryCount = 0) {
915
+ if (this.isShuttingDown) return;
916
+
917
+ const MAX_RETRIES = 3; // give up after 3 deferrals (~3-4.5 hours past 04:00)
918
+
919
+ // check if any STB is currently active - if so, defer to avoid
920
+ // interrupting a user who may be watching TV and using the remote
921
+ const anyStbOnline = this.devices.some(
922
+ (device) => device.currentPowerState === Characteristic.Active.ACTIVE,
923
+ );
924
+
925
+ if (anyStbOnline) {
926
+ // give up if max retries reached
927
+ if (retryCount >= MAX_RETRIES) {
928
+ this.log.info(
929
+ 'StbPlatform: nightly MQTT reconnect skipped - STB still active after max retries, rescheduling for next night',
930
+ );
931
+ this._scheduleNightlyMqttReconnect();
932
+ return;
933
+ }
934
+
935
+ // retry in 1 hour plus a random 0–30 min buffer
936
+ const ONE_HOUR_MS = 60 * 60 * 1000;
937
+ const THIRTY_MIN_MS = 30 * 60 * 1000;
938
+ const retryDelayMs = ONE_HOUR_MS + Math.floor(Math.random() * THIRTY_MIN_MS);
939
+ const retryAt = new Date(Date.now() + retryDelayMs);
940
+
941
+ this.log.info(
942
+ `StbPlatform: nightly MQTT reconnect deferred - STB is active (attempt ${retryCount + 1}/${MAX_RETRIES}). Retrying at ${retryAt.toLocaleString()}`,
943
+ );
944
+
945
+ // store handle so shutdown can cancel the deferred retry too
946
+ this.mqttReconnectTimeout = setTimeout(async () => {
947
+ if (this.isShuttingDown) return;
948
+ await this._attemptNightlyMqttReconnect(retryCount + 1);
949
+ }, retryDelayMs);
950
+ return; // don't reschedule for next night yet - that happens after a successful reconnect
951
+ }
952
+
953
+ // no STB is active - safe to reconnect
954
+ try {
955
+ this.mqttReconnecting = true; // signal to channel list refresh to pause
956
+ this.log.info('StbPlatform: nightly MQTT reconnect starting...');
957
+ await this.endMqttSession();
958
+ await this.startMqttClient();
959
+ this.log.info('StbPlatform: nightly MQTT reconnect completed');
960
+ } catch (err) {
961
+ this.log.error('StbPlatform: nightly MQTT reconnect failed:', err.message);
962
+ } finally {
963
+ this.mqttReconnecting = false; // always clear the flag, even on failure
964
+ }
965
+
966
+ this._scheduleNightlyMqttReconnect(); // reschedule for next night
967
+ } // end of _attemptNightlyMqttReconnect
968
+
858
969
  /**
859
970
  * _runFullStartupSequence
860
971
  *
@@ -4278,12 +4389,8 @@ class StbPlatform {
4278
4389
  // ------ device subscriptions ------
4279
4390
  // subscribe only to what we need
4280
4391
 
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
4392
  // householdId/mqttClientId: subscribe to own clientId to get data for ourselves
4286
- // subscribe to all devices after the setHgoOnlineRunning is sent
4393
+ // subscribe to all devices before the setHgoState is sent
4287
4394
  this.mqttSubscribeToTopic(
4288
4395
  householdId + "/" + this.mqttClient.options.clientId,
4289
4396
  ); // subscribe to our own mqttClientId to get all data
@@ -4320,32 +4427,36 @@ class StbPlatform {
4320
4427
  // reset so the 10-second retry fires correctly if the box doesn't respond
4321
4428
  this.lastMqttUiStatusMessageReceived = null;
4322
4429
 
4430
+ // announce ourselves as an active HGO client before requesting UI status
4431
+ // the STB uses this retained presence message to decide which clients to respond to
4432
+ this.setHgoState(householdId, this.mqttClient.options.clientId, 'ONLINE_RUNNING');
4433
+
4434
+ // request initial UI status for each device, with a short delay to allow
4435
+ // the STB to process the HGO presence announcement first
4323
4436
  // CPE.uiStatus messages are received via the householdId and mqttClientId
4324
4437
  // topics which are already subscribed above.
4325
4438
  // getUiStatus is called here to request the initial UI state from each device.
4326
4439
  // retain: false is used (see getUiStatus) so a retry is scheduled in case the box
4327
4440
  // 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",
4339
- device.deviceId,
4340
- );
4441
+ setTimeout(() => {
4442
+ this.devices.forEach((device) => {
4443
+ // request the initial UI status for each device
4444
+ this.getUiStatus(device.deviceId, this.mqttClient.options.clientId);
4445
+
4446
+ // retry after 10 seconds if no CPE.uiStatus response has arrived yet
4447
+ setTimeout(() => {
4448
+ if (!this.lastMqttUiStatusMessageReceived) {
4449
+ if (this.debugLevel > 0) {
4450
+ this.log.warn(
4451
+ "getUiStatus: no CPE.uiStatus received yet for %s, retrying",
4452
+ device.deviceId,
4453
+ );
4454
+ }
4455
+ this.getUiStatus(device.deviceId, this.mqttClient.options.clientId);
4341
4456
  }
4342
- this.getUiStatus(
4343
- device.deviceId,
4344
- this.mqttClient.options.clientId,
4345
- );
4346
- }
4347
- }, 10 * 1000); // 10 second retry delay
4348
- });
4457
+ }, 10 * 1000); // 10 second retry delay
4458
+ });
4459
+ }, 500); // 500ms for STB to register our HGO presence before we request status
4349
4460
 
4350
4461
  resolve(true); // all subscriptions registered — session is ready
4351
4462
  } catch (err) {
@@ -4462,7 +4573,7 @@ class StbPlatform {
4462
4573
  currMediaState = Characteristic.CurrentMediaState.PLAY;
4463
4574
  if (this.debugLevel > 0) {
4464
4575
  this.log.warn(
4465
- "mqttClient: STB status: power-on transition for %s, setting mediaState to PLAY",
4576
+ "mqttClient: STB status: Power-on transition detected for %s, setting mediaState to PLAY",
4466
4577
  deviceId,
4467
4578
  );
4468
4579
  }
@@ -4496,6 +4607,12 @@ class StbPlatform {
4496
4607
  this.log.warn("mqttClient: %s %s", deviceId, stbState);
4497
4608
  }
4498
4609
  }
4610
+
4611
+ // After the switch, if box is running, request current UI state
4612
+ //if (stbState === 'ONLINE_RUNNING') {
4613
+ // Small delay gives the STB a moment to settle before responding
4614
+ //setTimeout(() => this.mqttRequestUiStatus(deviceId), 500);
4615
+ //}
4499
4616
  }
4500
4617
 
4501
4618
  // handle CPE UI status messages for the STB
@@ -4878,16 +4995,26 @@ class StbPlatform {
4878
4995
  return resolve(true);
4879
4996
  }
4880
4997
 
4881
- // unsubscribe from all subscribedTopics before tearing down the session
4998
+ // get all subscribed topics
4882
4999
  const topics = this.subscribedTopics ?? [];
5000
+
5001
+ // announce HGO offline while the connection is still live, before any teardown
5002
+ this.setHgoState(
5003
+ this.session.householdId,
5004
+ this.mqttClient.options.clientId,
5005
+ 'OFFLINE',
5006
+ );
5007
+
5008
+ // unsubscribe from all subscribedTopics before tearing down the session
4883
5009
  if (topics.length === 0) {
4884
5010
  this.log.info(
4885
5011
  "mqttClient: No topics to unsubscribe from, skipping unsubscribe.",
4886
5012
  );
4887
- this.mqttClient.end(false, {}, (err) => {
4888
- if (err) {
4889
- this.log.error("MQTT end error:", err);
4890
- return reject(err);
5013
+
5014
+ this.mqttClient.end(false, {}, (endErr) => {
5015
+ if (endErr) {
5016
+ this.log.error("MQTT end error:", endErr);
5017
+ return reject(endErr);
4891
5018
  }
4892
5019
  this.log.info(
4893
5020
  "mqttClient: Disconnected cleanly. No topics found to unsubscribe from.",
@@ -4897,15 +5024,15 @@ class StbPlatform {
4897
5024
  return;
4898
5025
  }
4899
5026
 
4900
- this.mqttClient.unsubscribe(topics, (err) => {
4901
- if (err) {
4902
- this.log.error("MQTT unsubscribe error:", err);
5027
+ this.mqttClient.unsubscribe(topics, (unsubErr) => {
5028
+ if (unsubErr) {
5029
+ this.log.error("MQTT unsubscribe error:", unsubErr);
4903
5030
  // still attempt to end even if unsubscribe failed
4904
5031
  }
4905
- this.mqttClient.end(false, {}, (err) => {
4906
- if (err) {
4907
- this.log.error("MQTT end error:", err);
4908
- return reject(err);
5032
+ this.mqttClient.end(false, {}, (endErr) => {
5033
+ if (endErr) {
5034
+ this.log.error("MQTT end error:", endErr);
5035
+ return reject(endErr);
4909
5036
  }
4910
5037
  this.log.info(
4911
5038
  "mqttClient: Disconnected cleanly. All topics unsubscribed.",
@@ -4990,7 +5117,7 @@ class StbPlatform {
4990
5117
  "mqttPublishMessage: Publish Message:\r\nTopic: %s\r\nMessage: %s\r\nOptions: %s",
4991
5118
  Topic,
4992
5119
  Message,
4993
- Options,
5120
+ JSON.stringify(Options),
4994
5121
  );
4995
5122
  }
4996
5123
  this.mqttClient.publish(Topic, Message, Options, (err) => {
@@ -5081,23 +5208,25 @@ class StbPlatform {
5081
5208
  });
5082
5209
  }
5083
5210
 
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":""}
5211
+ // set the HGO session state (online or offline)
5212
+ // called on mqtt connect (ONLINE_RUNNING) and on mqtt disconnect (OFFLINE)
5213
+ // retain: true ensures the broker overwrites any previous retained state
5214
+ setHgoState(householdId, mqttClientId, state) {
5087
5215
  const topic = `${householdId}/${mqttClientId}/status`;
5088
5216
  const message = JSON.stringify({
5089
5217
  source: mqttClientId,
5090
- state: "ONLINE_RUNNING",
5091
- deviceType: "HGO",
5092
- mac: "",
5093
- ipAddress: "",
5218
+ state: state,
5219
+ deviceType: 'HGO',
5220
+ mac: '',
5221
+ ipAddress: '',
5094
5222
  });
5095
5223
  if (this.debugLevel > 0) {
5096
- this.log.warn("setHgoOnlineRunning: publishing to topic:", topic);
5224
+ this.log.warn('setHgoState: publishing %s to topic: %s', state, topic);
5097
5225
  }
5098
5226
  this.mqttPublishMessage(topic, message, { qos: 2, retain: true });
5099
5227
  }
5100
5228
 
5229
+
5101
5230
  // send a channel change request to the settopbox via mqtt
5102
5231
  // using the CPE.pushToTV message
5103
5232
  // the friendlyDeviceName appears on the TV in a popup window
@@ -5151,6 +5280,37 @@ class StbPlatform {
5151
5280
  }
5152
5281
  }
5153
5282
 
5283
+ // Request the current UI status from the STB.
5284
+ // The STB responds with a CPE.uiStatus message on the household channel.
5285
+ // @param {string} deviceId - The STB device ID (e.g. "000378-EOS2STB-00852052xxxx")
5286
+ mqttRequestUiStatus(deviceId) {
5287
+ if (!this.mqttClient?.connected) {
5288
+ this.log.warn('%s: mqttRequestUiStatus: MQTT not connected, skipping', deviceId);
5289
+ return;
5290
+ }
5291
+ if (this.debugLevel > 0) {
5292
+ this.log.warn(
5293
+ "mqttRequestUiStatus: Requesting UI status for %s",
5294
+ deviceId,
5295
+ );
5296
+ }
5297
+
5298
+ const payload = JSON.stringify({
5299
+ version: '1.3.18',
5300
+ type: 'CPE.pullFromTV',
5301
+ source: this.mqttClient.options.clientId, // your mqttClientId
5302
+ messageTimeStamp: Date.now(),
5303
+ });
5304
+
5305
+ const topic = `${this.session.householdId}/${deviceId}`;
5306
+
5307
+ this.mqttPublishMessage(topic, payload, {
5308
+ qos: 1,
5309
+ retain: false,
5310
+ });
5311
+
5312
+ }
5313
+
5154
5314
  // set the media state of the settopbox via mqtt
5155
5315
  // media state is controlled by speedRate
5156
5316
  // speedRate can be one of: -64 -30 -6 -2 0 2 6 30 64. 0=Paused, 1=Play, >1=FastForward, <0=Rewind
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-alpha.47",
6
+ "version": "2.4.0-beta.2",
7
7
  "platformname": "eosstb",
8
8
  "dependencies": {
9
9
  "axios": "^1.16.0",