homebridge-eosstb 2.0.4 → 2.1.0

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 CHANGED
@@ -3,19 +3,24 @@ All notable changes to this project will be documented in this file.
3
3
  See the [Readme file](https://github.com/jsiegenthaler/homebridge-eosstb/blob/master/README.md) for full plugin documentation.
4
4
  Please restart Homebridge after every plugin update.
5
5
 
6
- # IMPORTANT NOTICE
7
- This (v2.x) is a major update over v1.x due to the change in endpoints in the backend systems that occured on 13.10.2022.
8
- Please report all bugs and problems.
9
-
10
-
11
6
  # Bug Fixes and Improvements
12
7
 
13
8
  ## Current To-Do and In-Work List (For Future Releases, in rough order of priority):
14
- * Work on ideas for showing radio channels and using them -> STARTED. Possible only with KeyMacros. How should these appear in the channel list?
15
- * Fix potential problem with getPersonalizationData failing after plugin has been running overnight with ERR_BAD_REQUEST
9
+ * Add ability to log and read current program name
16
10
  * Implement refreshToken capabilities
17
11
 
18
12
 
13
+ ## 2.1.0 (2023-01-03)
14
+ * Added KeyMacro support
15
+ * Added custom characteristics: Current Channel Id and Current Channel Name, useful in automations
16
+ * Added preparation for reading current program name (future feature)
17
+ * Fixed bug where Input Device Type was not always correctly logged
18
+ * Fixed bug where Input Source Type was not always correctly logged
19
+ * Updated iOS version references
20
+ * Bumped dependency "axios": "^1.2.2"
21
+ * Bumped dependency "axios-cookiejar-support": "^4.0.6"
22
+
23
+
19
24
  ## 2.0.4 (2022-12-05)
20
25
  * Fixed model detection of HUMAX EOS1008R boxes
21
26
  * Fixed detection of Status Active to properly show mqtt active or not using Status Active in the Home app
@@ -41,10 +46,15 @@ Please report all bugs and problems.
41
46
  * Bumped Homebridge "homebridge": ">=1.6.0",
42
47
 
43
48
 
49
+ ## 2.0.2-beta.1 (2022-11-19)
50
+ * Added custom characteristic Current Channel Id
51
+ * Added custom characteristic Current Channel Name
52
+ * Optimised some code
53
+
54
+
44
55
  ## 2.0.1 (2022-11-19)
45
56
  * Increased reliability of mqtt messages by setting QoS
46
- * Optimised the GB session code
47
- * Removed some left over debug code
57
+ * Optimised the GB session code Removed some left over debug code
48
58
 
49
59
 
50
60
  ## 2.0.0 (2022-11-14)
@@ -52,4 +62,4 @@ Please report all bugs and problems.
52
62
  * Major startup speed improvements after Homebridge reboot
53
63
  * Improved mqtt performance
54
64
  * Added support of channel sort by most watched
55
- * And many more small bug fixes and improvements included
65
+ * And many more small bug fixes and improvements included
package/README.md CHANGED
@@ -70,7 +70,7 @@ In March 2022, a newer version of the set-top box has started to appear in Telen
70
70
  This plugin is not provided by Magenta or Telenet or Sunrise or Virgin Media or Ziggo any other affiliate of [UPC](https://en.wikipedia.org/wiki/UPC_Broadband). It is neither endorsed nor supported nor developed by [UPC](https://en.wikipedia.org/wiki/UPC_Broadband) or any affiliates. [UPC](https://en.wikipedia.org/wiki/UPC_Broadband) can change their systems at any time and that might break this plugin. But I hope not.
71
71
 
72
72
  ## Requirements
73
- * An Apple iPhone or iPad with iOS/iPadOS 14.0 (or later). Developed on iOS 14.1...16.1, earlier versions not tested.
73
+ * An Apple iPhone or iPad with iOS/iPadOS 14.0 (or later). Developed on iOS 14.1...16.2, earlier versions not tested.
74
74
  * [Homebridge](https://homebridge.io/) v1.1.116 (or later). Developed on Homebridge 1.1.116....1.6.0, earlier versions not tested.
75
75
  * A TV subscription from one of the supported countries and TV providers.
76
76
  * An online account for viewing TV in the web app (often part of your TV package), see the table above.
@@ -83,9 +83,11 @@ This plugin is not provided by Magenta or Telenet or Sunrise or Virgin Media or
83
83
 
84
84
  * **Full Remote-Control Support**: The Apple TV Remote in your iOS device can control your set-top box; including power, menu navigation, play, pause, fast-forward, rewind, channel up/down, volume and mute commands. All keys are fully configurable for single-tap and double-tap.
85
85
 
86
+ * **Powerful Key Macros**: You can program key macros to control your set-top box. Key macros are powerful ways of accessing any content such as radio channels that cannot be accessed directly via a channel number.
87
+
86
88
  * **Siri Support** You can control your box with Siri (to the extent of what Apple Siri supports).
87
89
 
88
- * **Shortcuts Support** You can read and control your box with Shortcuts and HomeKit automations (to the extent of what Apple supports), allowing you to control switch-on and channel selection in Home Automations, Shorcuts and Personal Automations.
90
+ * **Shortcuts Support** You can read and control your box with Shortcuts and HomeKit automations (to the extent of what Apple supports), allowing you to control switch-on and channel selection in Home Automations, Shortcuts and Personal Automations.
89
91
 
90
92
  * **Synchronised Set-Top Box Name**: Changing the name of the set-top box in the Home app changes it on the TV and backend systems in real time, and vice-versa. No reboot required. You can turn off the sync if desired in the config.
91
93
 
@@ -111,7 +113,7 @@ This plugin is not provided by Magenta or Telenet or Sunrise or Virgin Media or
111
113
 
112
114
  * **Fully Configurable**: A large amount of configuration items exist to allow you to configure your plugin the way you want.
113
115
 
114
- * **Future Feature Support**: The plugin also supports current and target media state as well as closed captions, even though the Home app accessory cannot currently display or control this data in the home app (as at iOS 16.1). Hopefully, Apple will add support for these features in the future. You can however use this data in Home Automations or the Shortcuts app.
116
+ * **Future Feature Support**: The plugin also supports current and target media state as well as closed captions, even though the Home app accessory cannot currently display or control this data in the home app (as at iOS 16.2). Hopefully, Apple will add support for these features in the future. You can however use this data in Home Automations or the Shortcuts app.
115
117
 
116
118
 
117
119
 
@@ -172,7 +174,7 @@ The table shows the default key mappings. You can map any Apple TV Remote button
172
174
  The volume controls do not control the set-top box directly, as the set-top box has no volume capability. The set-top box physical remote actually sends IR commands to your TV. If you can control your TV volume via a network connection then the volume controls can be used to send volume commands to your TV via the raspberry pi. This is what the author uses.
173
175
 
174
176
 
175
- ## Using Profiles to better manage your Channel List
177
+ ## Using Profiles to Better Manage your Channel List
176
178
  Many TV providers provide hundreds of TV channels. The Home app is limited to 100 "services", which are TV channels or reserved for system control. This limits the maximum possible channels to 95, and thus the plugin will load the first 95 subscribed channels found, ignoring all non-subscribed channels.
177
179
 
178
180
  If the channels you wish to have in the Home app are not within the first 95 subscribed channels in your TV providers channel list, then you can create a profile on the set-top box, and configure the profile with the channels you want, in the order you want. Enter the same profile name in the plugin config **Profile Name**, and the plugin will load the channels from that profile.
@@ -185,6 +187,10 @@ The profile used by the plugin does not have to be the same as the set-top box's
185
187
  ## Sorting the Channel list
186
188
  The config item **Channel Sort By** allows the channels to be sorted by **Channel Order** (the standard channel order as shown on the TV) or by **Most Watched**. Most Watched is reported by the backend systems and is profile-based. It is not clear how often this list is updated, however for a TV subscription with many channels, this may be a preferable option to show your most watched channels at the top of the channel list.
187
189
 
190
+ ## Using Key Macros to Access Extra Capabilities
191
+ You can program key macros to access any function of your set-top box. This is particularly useful to select radio channels. Key macros are loaded at the end of the channel list. See the [Wiki Key Macros page](https://github.com/jsiegenthaler/homebridge-eosstb/wiki/Key-Macros) for full details.
192
+
193
+
188
194
 
189
195
  ## Limitations
190
196
  ### Channel Count
@@ -195,17 +201,14 @@ Services used in this set-top box accessory are:
195
201
  3. Speaker service (for the controlling the TV accessory volume)
196
202
  4. Input service. The input (TV channels) utilises one service per input. The maximum possible channels (inputs) are thus 100 - 3 = 97. I have limited the inputs to maximum 95, but you can override this in the config (helpful to reduce log entries when debugging). The inputs are hard limited to 95 inputs.
197
203
 
198
- ### Web App Controllers Take Over Sometimes
199
- The eosstb plugin emulates the TV service web app. If the web app is started on a web browser on a laptop or PC, the backend systems may prefer the web app to HomeKit, and disconnect HomeKit from the mqtt session. The mqtt session will try and reconnect if it gets disconnected.
200
-
201
204
  ### Media State (Play/Pause) Limitations
202
- The eosstb plugin can detect the current and target media state and shows STOP, PLAY, PAUSE or LOADING (loading is displayed when fast-forwarding or rewinding) in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the media state (as at iOS 16.1) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
205
+ The eosstb plugin can detect the current and target media state and shows STOP, PLAY, PAUSE or LOADING (loading is displayed when fast-forwarding or rewinding) in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the media state (as at iOS 16.2) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
203
206
 
204
207
  ### Recording State Limitations
205
208
  The eosstb plugin can detect the current recording state of the set-top box, both for local HDD-based recording (for boxes that have a HDD fitted) and for network recording. The plugin shows IDLE, ONGOING_NDVR or ONGOING_LOCALDVR in the Homebridge logs. DVR means digital video recorder; N for network and LOCAL for local HDD based recording. The Apple Home app cannot natively do anything with the recording state but the eosstb plugin uses it to set the inUse charateristic if the set-top box is turned on or is recording to the local HDD. This is useful in Shortcuts or Automations.
206
209
 
207
210
  ### Closed Captions Limitations
208
- The eosstb plugin can detect the closed captions state (**Subtitle options** in the set-top box menu) and shows ENABLED or DISABLED in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the closed captions state (as at iOS 16.1) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
211
+ The eosstb plugin can detect the closed captions state (**Subtitle options** in the set-top box menu) and shows ENABLED or DISABLED in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the closed captions state (as at iOS 16.2) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
209
212
 
210
213
  ## Configuration
211
214
  Add a new platform to the platforms section of your homebridge `config.json`.
@@ -223,7 +226,7 @@ Example minimum (mandatory) configuration:
223
226
  ]
224
227
  ```
225
228
 
226
- Example extended configuration as used on the author with his Samsung TV (where x.x.x.x is the IP address of the TV). An extended configuration allows you to customise the behaviour of each set-top box device. You must identify the devices by their deviceId:
229
+ Example extended configuration as used on the author with his EOSSTB set-top box. An extended configuration allows you to customise the behaviour of each set-top box device. You must identify the devices by their deviceId:
227
230
 
228
231
  ```js
229
232
  "platforms": [
@@ -257,6 +260,10 @@ Example extended configuration as used on the author with his Samsung TV (where
257
260
  "maxChannels": 50
258
261
  },
259
262
  "channels": [
263
+ {
264
+ "channelKeyMacro": "TV MediaTopMenu wait(1000) ArrowRight ArrowRight Enter wait(1000) ArrowDown wait(1500) Enter wait(2000) Enter",
265
+ "channelName": "Last Played Radio"
266
+ },
260
267
  {
261
268
  "channelId": "SV09690",
262
269
  "channelName": "Netflix"
@@ -358,7 +365,9 @@ Some channels such as Netflix are actually apps on the set-top box, and not norm
358
365
 
359
366
  * **channelId**: The channelId, as defined by the TV provider. Unknown channelIds will appear in the Homebridge log.
360
367
 
361
- * **channelNames**: Allows you to add unknown channel names, or to rename any channel as you wish. Required as some channels (e.g., Netflix) are not published on the master channel list. If a channel displays in the Home app like this: "Channel SV09690", then check your TV to see the channel name, and add it to the config. An example is provided for Netflix. Optional, unknown channels are displayed as "Channel xxxxxxx" where xxxxxxx is the channelId.
368
+ * **channelKeyMacro**: The channel key macro (key sequence) to send. If channelKeyMacro is present, channelId is ignored.
369
+
370
+ * **channelNames**: Allows you to add unknown channel names, names for key macros, or to rename any channel as you wish. Required as some channels (e.g., Netflix) are not published on the master channel list. If a channel displays in the Home app like this: "Channel SV09690", then check your TV to see the channel name, and add it to the config. An example is provided for Netflix. Optional, unknown channels are displayed as "Channel xxxxxxx" where xxxxxxx is the channelId.
362
371
 
363
372
 
364
373
  * Telenet BE:
@@ -1118,6 +1118,7 @@
1118
1118
  "type": "array",
1119
1119
  "items": [
1120
1120
  "channels[].channelId",
1121
+ "channels[].channelKeyMacro",
1121
1122
  "channels[].channelName"
1122
1123
  ]
1123
1124
  }
package/index.js CHANGED
@@ -28,7 +28,6 @@ const cookieJar = new tough.CookieJar();
28
28
  const axios = require('axios') //.default; // https://github.com/axios/axios
29
29
  axios.defaults.xsrfCookieName = undefined; // change xsrfCookieName: 'XSRF-TOKEN' to xsrfCookieName: undefined, we do not want this default,
30
30
  const axiosWS = axios.create({
31
- // axios-cookiejar-support v2.0.2 required config
32
31
  jar: cookieJar, //added in axios-cookiejar-support v2.0.x, see https://github.com/3846masa/axios-cookiejar-support/blob/main/MIGRATION.md
33
32
  });
34
33
 
@@ -1350,6 +1349,7 @@ class stbPlatform {
1350
1349
  if (this.config.debugLevel > 2) { this.log('Processing channel:',i,channel.logicalChannelNumber,channel.id, channel.name); } // for debug purposes
1351
1350
  // log the detail of logicalChannelNumber 60 nicktoons, for which I have no subscription, as a test of entitlements
1352
1351
  //if (this.config.debugLevel > 0) { if (channel.logicalChannelNumber == 60){ this.log('DEV: Logging Channel 60 to check entitlements :',channel); } }
1352
+ //if (this.config.debugLevel > 0) { if (channel.logicalChannelNumber == 60){ this.log('DEV: Logging Channel 60 to check entitlements :',channel); } }
1353
1353
  this.masterChannelList.push({
1354
1354
  id: channel.id,
1355
1355
  name: cleanNameForHomeKit(channel.name),
@@ -1379,103 +1379,6 @@ class stbPlatform {
1379
1379
  })
1380
1380
  }
1381
1381
 
1382
- // load all available TV channels at regular intervals into an array
1383
- // new version using endpoints available from 13.10.2022
1384
- // the masterChannelList contains all possible channels, bith subscribed and non-subscribed channels
1385
- async refreshMasterChannelListPrev(callback) {
1386
- // called by refreshMasterChannelList (state handler), thus runs at polling interval
1387
-
1388
- // exit immediately if the session does not exist
1389
- if (currentSessionState != sessionState.CONNECTED) {
1390
- if (this.config.debugLevel > 1) { this.log.warn('refreshMasterChannelList: Session does not exist, exiting'); }
1391
- return false;
1392
- }
1393
-
1394
- // exit immediately if channel list has not expired
1395
- if (this.masterChannelListExpiryDate > Date.now()) {
1396
- if (this.config.debugLevel > 1) { this.log.warn('refreshMasterChannelList: Master channel list has not expired yet. Next refresh will occur after %s', this.masterChannelListExpiryDate.toLocaleString()); }
1397
- return false;
1398
- }
1399
-
1400
- if (this.config.debugLevel > 1) {
1401
- this.log.warn('refreshMasterChannelList: Refreshing master channel list...');
1402
- }
1403
-
1404
- // only continue if a session was created. If the internet conection is down then we have no session
1405
- //if (currentSessionState != sessionState.CONNECTED) { return; }
1406
-
1407
- // channels can be retrieved for the country without having a mqtt session going but then the list is not relevant for the user's locationId
1408
- // so you should add the user's locationId as a parameter, and this needs the accessToken
1409
- // syntax:
1410
- // https://prod.oesp.virginmedia.com/oesp/v4/GB/eng/web/channels?byLocationId=41043&includeInvisible=true&includeNotEntitled=true&personalised=true&sort=channelNumber
1411
- // https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH
1412
- /*
1413
- let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/channels';
1414
- url = url + '?byLocationId=' + this.session.locationId // locationId needed to get user-specific list
1415
- url = url + '&includeInvisible=true' // includeInvisible
1416
- url = url + '&includeNotEntitled=true' // includeNotEntitled
1417
- url = url + '&personalised=true' // personalised
1418
- url = url + '&sort=channelNumber' // sort
1419
- */
1420
- //url = 'https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH'
1421
- let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/linear-service/v2/channels';
1422
- url = url + '?cityId=' + this.customer.cityId; //+ this.customer.cityId // cityId needed to get user-specific list
1423
- url = url + '&language=en'; // language
1424
- url = url + '&entitled=true'; // testing! 760 with all,
1425
- url = url + '&productClass=Orion-DASH'; // productClass, must be Orion-DASH
1426
- //url = url + '&includeNotEntitled=false' // includeNotEntitled testing to see if this parameter is accepted
1427
- if (this.config.debugLevel > 2) { this.log.warn('refreshMasterChannelList: loading inputs from',url); }
1428
-
1429
- // call the webservice to get all available channels
1430
- const axiosConfig = {
1431
- method: 'GET',
1432
- url: url
1433
- };
1434
- axiosWS(axiosConfig)
1435
- .then(response => {
1436
- //this.log(response.data);
1437
- if (this.config.debugLevel > 2) { this.log.warn('refreshMasterChannelList: Processing %s channels...', response.data.length); }
1438
-
1439
- // set the masterChannelListExpiryDate to expire at now + MASTER_CHANNEL_LIST_VALID_FOR_S
1440
- this.masterChannelListExpiryDate =new Date(new Date().getTime() + (MASTER_CHANNEL_LIST_VALID_FOR_S * 1000));
1441
- //this.log('MasterChannelList valid until',this.masterChannelListExpiryDate.toLocaleString())
1442
-
1443
- // load the channel list with all channels found
1444
- this.masterChannelList = [];
1445
- const channels = response.data;
1446
- this.log.debug('Channels to process:',channels.length);
1447
- for(let i=0; i<channels.length; i++) {
1448
- const channel = channels[i];
1449
- // if (this.config.debugLevel > 0) { this.log('Loading channel:',i,channel.logicalChannelNumber,channel.id, channel.name); } // for debug purposes
1450
- // log the detail of logicalChannelNumber 60 nicktoons, for which I have no subscription, as a test of entitlements
1451
- //if (this.config.debugLevel > 0) { if (channel.logicalChannelNumber == 60){ this.log('DEV: Logging Channel 60 to check entitlements :',channel); } }
1452
- this.masterChannelList.push({
1453
- id: channel.id,
1454
- name: cleanNameForHomeKit(channel.name),
1455
- logicalChannelNumber: channel.logicalChannelNumber,
1456
- linearProducts: channel.linearProducts
1457
- });
1458
- }
1459
-
1460
- if (this.config.debugLevel > 0) {
1461
- this.log.warn('refreshMasterChannelList: Master channel list refreshed with %s channels, valid until %s', this.masterChannelList.length, this.masterChannelListExpiryDate.toLocaleString());
1462
- }
1463
- return true;
1464
-
1465
- })
1466
- .catch(error => {
1467
- let errText, errReason;
1468
- errText = 'Failed to refresh the master channel list - check your internet connection:'
1469
- if (error.isAxiosError) {
1470
- errReason = error.code + ': ' + (error.hostname || '');
1471
- // if no connection then set session to disconnected to force a session reconnect
1472
- if (error.code == 'ENOTFOUND') { currentSessionState = sessionState.DISCONNECTED; }
1473
- }
1474
- this.log('%s %s', errText, (errReason || ''));
1475
- this.log.warn(`refreshMasterChannelList error:`, error);
1476
- return error;
1477
- });
1478
- }
1479
1382
 
1480
1383
 
1481
1384
  // get Personalization Data via web request GET
@@ -1902,7 +1805,7 @@ class stbPlatform {
1902
1805
  }
1903
1806
 
1904
1807
  // variables for just in this function
1905
- var deviceId, stbState, currPowerState, currMediaState, currChannelId, currSourceType, profileDataChanged, currRecordingState, currStatusActive, currInputDeviceType, currInputSourceType;
1808
+ var deviceId, stbState, currPowerState, currMediaState, currChannelId, currEventId, currSourceType, profileDataChanged, currRecordingState, currStatusActive, currInputDeviceType, currInputSourceType;
1906
1809
 
1907
1810
  // handle personalizationService messages
1908
1811
  // Topic: Topic: 107xxxx_ch/personalizationService
@@ -2048,6 +1951,7 @@ class stbPlatform {
2048
1951
  // Careful: source is not always present in the data
2049
1952
  if (playerState.source) {
2050
1953
  currChannelId = playerState.source.channelId || NO_CHANNEL_ID; // must be a string
1954
+ currEventId = playerState.source.eventId; // the title (program) id
2051
1955
  if (parent.config.debugLevel > 0 && parent.masterChannelList) {
2052
1956
  let currentChannelName; // let is scoped to the current {} block
2053
1957
  let curChannel = parent.masterChannelList.find(channel => channel.id === currChannelId);
@@ -2120,8 +2024,8 @@ class stbPlatform {
2120
2024
 
2121
2025
 
2122
2026
  // update the device on every message
2123
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2124
- parent.mqttDeviceStateHandler(deviceId, currPowerState, currMediaState, currRecordingState, currChannelId, currSourceType, profileDataChanged, Characteristic.StatusFault.NO_FAULT, null, currStatusActive, currInputDeviceType, currInputSourceType);
2027
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventid, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2028
+ parent.mqttDeviceStateHandler(deviceId, currPowerState, currMediaState, currRecordingState, currChannelId, currEventId, currSourceType, profileDataChanged, Characteristic.StatusFault.NO_FAULT, null, currStatusActive, currInputDeviceType, currInputSourceType);
2125
2029
 
2126
2030
  //end of try
2127
2031
  } catch (err) {
@@ -2139,12 +2043,12 @@ class stbPlatform {
2139
2043
  // https://github.com/mqttjs/MQTT.js#event-close
2140
2044
  mqttClient.on('close', function () {
2141
2045
  try {
2142
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2046
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2143
2047
  parent.currentMqttState = mqttState.closed;
2144
2048
  parent.log('mqttClient: Connection closed');
2145
2049
  currentSessionState = sessionState.DISCONNECTED; // to force a session reconnect
2146
2050
  if (!isShuttingDown) {
2147
- parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2051
+ parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2148
2052
  }
2149
2053
  } catch (err) {
2150
2054
  parent.log.error("Error trapped in mqttClient close event:", err.message);
@@ -2157,10 +2061,10 @@ class stbPlatform {
2157
2061
  // https://github.com/mqttjs/MQTT.js#event-reconnect
2158
2062
  mqttClient.on('reconnect', function () {
2159
2063
  try {
2160
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2064
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2161
2065
  parent.currentMqttState = mqttState.reconnected;
2162
2066
  parent.log('mqttClient: Reconnect started');
2163
- parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2067
+ parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2164
2068
  } catch (err) {
2165
2069
  parent.log.error("Error trapped in mqttClient reconnect event:", err.message);
2166
2070
  parent.log.error(err);
@@ -2172,11 +2076,11 @@ class stbPlatform {
2172
2076
  // https://github.com/mqttjs/MQTT.js#event-disconnect
2173
2077
  mqttClient.on('disconnect', function () {
2174
2078
  try {
2175
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2079
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2176
2080
  parent.currentMqttState = mqttState.disconnected;
2177
2081
  parent.log('mqttClient: Disconnect command received');
2178
2082
  currentSessionState = sessionState.DISCONNECTED; // to force a session reconnect
2179
- parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2083
+ parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2180
2084
  } catch (err) {
2181
2085
  parent.log.error("Error trapped in mqttClient disconnect event:", err.message);
2182
2086
  parent.log.error(err);
@@ -2188,11 +2092,11 @@ class stbPlatform {
2188
2092
  // https://github.com/mqttjs/MQTT.js#event-disconnect
2189
2093
  mqttClient.on('offline', function () {
2190
2094
  try {
2191
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2095
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2192
2096
  parent.currentMqttState = mqttState.offline;
2193
2097
  parent.log('mqttClient: Client is offline');
2194
2098
  currentSessionState = sessionState.DISCONNECTED; // to force a session reconnect
2195
- parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2099
+ parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2196
2100
  } catch (err) {
2197
2101
  parent.log.error("Error trapped in mqttClient offline event:", err.message);
2198
2102
  parent.log.error(err);
@@ -2204,12 +2108,12 @@ class stbPlatform {
2204
2108
  // https://github.com/mqttjs/MQTT.js#event-error
2205
2109
  mqttClient.on('error', function(err) {
2206
2110
  try {
2207
- // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2111
+ // mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType)
2208
2112
  parent.currentMqttState = mqttState.error;
2209
2113
  parent.log.warn('mqttClient: Error', (err.syscall || '') + ' ' + (err.code || '') + ' ' + (err.hostname || ''));
2210
2114
  parent.log.warn('mqttClient: Error object:', err);
2211
2115
  currentSessionState = sessionState.DISCONNECTED; // to force a session reconnect
2212
- parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2116
+ parent.mqttDeviceStateHandler(null, null, null, null, null, null, null, null, Characteristic.StatusFault.GENERAL_FAULT); // set statusFault to GENERAL_FAULT
2213
2117
  mqttClient.end();
2214
2118
  return false;
2215
2119
  } catch (err) {
@@ -2256,15 +2160,15 @@ class stbPlatform {
2256
2160
 
2257
2161
  // handle the state change of the device, calling the updateDeviceState of the relevant device
2258
2162
  // handles multiple devices by deviceId, should the user have more than one device
2259
- mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType) {
2163
+ mqttDeviceStateHandler(deviceId, powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType) {
2260
2164
  try {
2261
2165
  if (this.config.debugLevel > 1) {
2262
- this.log.warn('mqttDeviceStateHandler: calling updateDeviceState with deviceId %s, powerState %s, mediaState %s, channelId %s, sourceType %s, profileDataChanged %s, statusFault %s, programMode %s, statusActive %s, currInputDeviceType %s, currInputSourceType %s', deviceId, powerState, mediaState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType);
2166
+ this.log.warn('mqttDeviceStateHandler: calling updateDeviceState with deviceId %s, powerState %s, mediaState %s, channelId %s, eventId %s, sourceType %s, profileDataChanged %s, statusFault %s, programMode %s, statusActive %s, currInputDeviceType %s, currInputSourceType %s', deviceId, powerState, mediaState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType);
2263
2167
  }
2264
2168
  const deviceIndex = this.devices.findIndex(device => device.deviceId == deviceId)
2265
2169
  if (deviceIndex > -1 && this.stbDevices.length > 0) {
2266
2170
  //this.log.warn('mqttDeviceStateHandler: stbDevices found, calling updateDeviceState');
2267
- this.stbDevices[deviceIndex].updateDeviceState(powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType);
2171
+ this.stbDevices[deviceIndex].updateDeviceState(powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, currInputDeviceType, currInputSourceType);
2268
2172
  }
2269
2173
  } catch (err) {
2270
2174
  this.log.error("Error trapped in mqttDeviceStateHandler:", err.message);
@@ -2406,45 +2310,121 @@ class stbPlatform {
2406
2310
  try {
2407
2311
  if (this.config.debugLevel > 0) { this.log.warn('sendKey: keySequence %s, deviceName %s, deviceId %s', keySequence, deviceName, deviceId); }
2408
2312
  if (mqttUsername) {
2313
+ let hasJustBooted = false; // indicates if the box just booted up during this keyMacro
2314
+ let keyCanBeSkippedAfterBootup = false; // indicates if the current key can be skipped
2315
+ let firstNonSkippableKeyFound = false; // indicates if a non-skippable key was found
2316
+ let defaultWaitDelayActive = false; // indicates if the default wait delay is being used
2409
2317
 
2410
2318
  let keyArray = keySequence.trim().split(' ');
2411
- if (keyArray.length > 1) { this.log('sendKey: processing keySequence %s for %s %s', keySequence, deviceName, deviceId); }
2319
+ if (keyArray.length > 1) { this.log('sendKey: processing keySequence for %s: "%s"', deviceName, keySequence); }
2412
2320
  // supported key1 key2 key3 wait() wait(100)
2413
2321
  for (let i = 0; i < keyArray.length; i++) {
2414
2322
  const keyName = keyArray[i].trim();
2415
2323
  this.log('sendKey: processing key %s of %s: %s', i+1, keyArray.length, keyName);
2324
+ const defaultWaitDelay = 200; // default 200ms
2325
+ const maxWaitDelay = 20000; // default 200ms
2326
+ const waitReadyDelayStep = 500; // the ms wait time in each waitReady loop
2327
+ const maxWaitReadyLoops = maxWaitDelay / waitReadyDelayStep; // the max loop iterations to wait for ready
2328
+ const currKeyIsEscapeOrTvOrWait =
2329
+ keyName.toLowerCase().startsWith('wait(') // current key is a wait
2330
+ || keyName.toLowerCase() == 'escape' // or current key is an Escape
2331
+ || keyName.toLowerCase() == 'tv'; // or current key is TV
2332
+ if (!firstNonSkippableKeyFound && !currKeyIsEscapeOrTvOrWait) {
2333
+ firstNonSkippableKeyFound = true; // first non-escape or non-wait key found
2334
+ }
2335
+ keyCanBeSkippedAfterBootup = false; // reset for each key
2336
+ defaultWaitDelayActive = false; // reset for each key
2337
+
2338
+
2339
+ // for all keys except Power:
2340
+ // check if box is ready (up and running), if not, loop until we hit maxWaitDelay, waiting waitReadyDelayStep ms each loop
2341
+ // loop only while i < maxWaitReadyLoops and current media state = STOP
2342
+ // The device changes CurrentMediaState from STOP to PLAY when it has powered up and is streaming TV
2343
+ // CurrentMediaState=STOP only occurs when the set-top box is turned off, so is a good indicator that it is streaming content
2344
+ // TEST THIS WITH NETFLIX!
2345
+ if (keyName.toLowerCase() != 'power') {
2346
+ const deviceIndex = this.devices.findIndex(device => device.deviceId == deviceId)
2347
+ // detect CurrentMediaState=STOP to show box has just booted
2348
+ if (this.stbDevices[deviceIndex].currentMediaState == Characteristic.CurrentMediaState.STOP) {
2349
+ this.log('sendKey: key %s: waiting for ready for %s', i+1, deviceName);
2350
+ for (let j=0;
2351
+ j<maxWaitReadyLoops
2352
+ && this.stbDevices[deviceIndex].currentMediaState == Characteristic.CurrentMediaState.STOP;
2353
+ j++) {
2354
+ hasJustBooted = true; // indicates that the box just booted up during this keyMacro
2355
+ await waitprom(waitReadyDelayStep); // wait waitReadyDelayStep ms on each loop
2356
+ this.log.debug('sendKey: key %s: loop %s: wait %s ms done, hasJustBooted %s, currentMediaState %s', i+1, j, hasJustBooted, waitReadyDelayStep, currentMediaStateName[this.stbDevices[deviceIndex].currentMediaState]);
2357
+ }
2358
+ this.log.debug('sendKey: key %s: waiting one more delay of %s ms', i+1, waitReadyDelayStep);
2359
+ await waitprom(waitReadyDelayStep); // wait waitReadyDelayStep ms one last time to ensure we have one wait after change from STOP to PLAY
2360
+ this.log('sendKey: key %s: waiting for ready done, hasJustBooted %s, currentMediaState %s', i+1, hasJustBooted, currentMediaStateName[this.stbDevices[deviceIndex].currentMediaState]);
2361
+ }
2362
+ }
2363
+
2416
2364
 
2417
- // if a wait appears, use it
2418
- let waitDelay; // default
2419
- if (keyName.toLowerCase().startsWith('wait(')) {
2420
- this.log.debug('sendKey: reading delay from %s', keyName);
2365
+
2366
+ // check if current key can be skipped.
2367
+ // leading Escape and wait keys can be skipped after a bootup to speed up the selection of a radio channel using a scene
2368
+ // any skipping must stop when the first non-Escape and non-wait key is found
2369
+ this.log.debug('sendKey: key %s: keyArray.length %s, prevKey %s, currKey %s, nextKey %s', i+1, keyArray.length, keyArray[i-1], keyArray[i], keyArray[i+1])
2370
+ if ( hasJustBooted // box has just booted
2371
+ && currKeyIsEscapeOrTvOrWait // current key is escape or tv or wait
2372
+ && !firstNonSkippableKeyFound // have not yet found the first non-skippable key
2373
+ ) {
2374
+ keyCanBeSkippedAfterBootup = true; // we can skip this key as it is a wait or escape
2375
+ }
2376
+
2377
+
2378
+ // to help with debug
2379
+ this.log.debug('sendKey: key %s: hasJustBooted %s, currKeyIsEscapeOrTvOrWait %s, firstNonSkippableKeyFound %s, keyCanBeSkippedAfterBootup %s', i+1, hasJustBooted, currKeyIsEscapeOrTvOrWait, firstNonSkippableKeyFound, keyCanBeSkippedAfterBootup);
2380
+
2381
+
2382
+ // process any wait command if found
2383
+ // but ignore if keyCanBeSkippedAfterBootup
2384
+ let waitDelay;
2385
+ if (keyName.toLowerCase().startsWith('wait(') && !keyCanBeSkippedAfterBootup) {
2386
+ this.log.debug('sendKey: key %s: reading delay from %s', i+1, keyName);
2387
+ // accepts wait(), wait(n)
2421
2388
  waitDelay = keyName.toLowerCase().replace('wait(', '').replace(')','');
2422
- if (waitDelay == ''){ waitDelay = 100; } // default 100ms
2423
- this.log.debug('sendKey: delay read as %s', waitDelay);
2389
+ if (waitDelay == ''){ waitDelay = defaultWaitDelay; } // default wait
2390
+ if (waitDelay > maxWaitDelay){ waitDelay = maxWaitDelay; } // max wait
2391
+ this.log.debug('sendKey: key %s: delay read as %s', i+1, waitDelay);
2424
2392
  }
2425
- // else if not first key and previous key was not wait, and next key is not wait, then set a default delay of 100 ms
2426
- else if (i>0 && i<keyArray.length-1 && !(keyArray[i-1] || '').toLowerCase().startsWith('wait(') && !(keyArray[i+1] || '').toLowerCase().startsWith('wait(')) {
2427
- this.log.debug('sendKey: not first key and neiher previous key %s nor next key %s is wait(). Setting default wait of 100 ms', keyArray[i-1], keyArray[i+1]);
2428
- waitDelay = 100;
2393
+ // else if not key can be skipped, and not first key and previous key was not wait, and current key is not wait, then set a default delay of defaultWaitDelay ms
2394
+ else if ( !keyCanBeSkippedAfterBootup
2395
+ && i>0
2396
+ //&& i<keyArray.length-1
2397
+ && !(keyArray[i-1] || '').toLowerCase().startsWith('wait(') && !(keyArray[i] || '').toLowerCase().startsWith('wait(')
2398
+ ) {
2399
+ this.log.debug('sendKey: key %s: not keyCanBeSkippedAfterBootup and not first key and neither previous key %s nor current key %s is wait(). Setting default wait of %s ms', i+1, keyArray[i-1], keyArray[i], defaultWaitDelay);
2400
+ defaultWaitDelayActive = true;
2401
+ waitDelay = defaultWaitDelay;
2429
2402
  }
2403
+
2430
2404
 
2431
- // add a wait if waitDelay is defined
2405
+ // add a wait if a waitDelay is set
2406
+ //this.log('sendKey: key %s: waitDelay', i+1, waitDelay);
2432
2407
  if (waitDelay) {
2433
- this.log('sendKey: waiting %s ms', waitDelay);
2408
+ if (!defaultWaitDelayActive) { this.log('sendKey: key %s: waiting %s ms', i+1, waitDelay); } // reduce logging in minimum mode if default wait
2434
2409
  await waitprom(waitDelay);
2435
- this.log.debug('sendKey: wait %s done', waitDelay);
2410
+ this.log.debug('sendKey: key %s: wait done', i+1);
2436
2411
  }
2412
+
2437
2413
 
2438
- // send the key if not a wait()
2439
- if (!keyName.toLowerCase().startsWith('wait(')) {
2440
- this.log('sendKey: sending key %s to %s %s', keyName, deviceName, deviceId);
2414
+ // send the key
2415
+ if (hasJustBooted && keyCanBeSkippedAfterBootup) {
2416
+ // when a box has just booted, leading Escapes and waits can be skipped until the first non-Escape and non-wait comand
2417
+ this.log('sendKey: key %s: box has just booted, skipping key %s', i+1, keyName);
2418
+ } else if (!keyName.toLowerCase().startsWith('wait(')) {
2419
+ // send the key if not a wait
2420
+ this.log('sendKey: key %s: sending key %s to %s %s', i+1, keyName, deviceName, deviceId);
2441
2421
  // the web client uses qos:2, so we should as well
2442
2422
  this.mqttPublishMessage(
2443
2423
  mqttUsername + '/' + deviceId,
2444
2424
  '{"id":"' + makeFormattedId(32) + '","type":"CPE.KeyEvent","source":"' + mqttClientId + '","status":{"w3cKey":"' + keyName + '","eventType":"keyDownUp"}}',
2445
2425
  { qos:2, retain:true }
2446
2426
  );
2447
- this.log.debug('sendKey: send %s done', keyName);
2427
+ this.log.debug('sendKey: key %s: send %s done', i+1, keyName);
2448
2428
 
2449
2429
  }
2450
2430
 
@@ -2790,6 +2770,7 @@ class stbDevice {
2790
2770
  this.currentPowerState = Characteristic.Active.INACTIVE;
2791
2771
  this.previousPowerState = Characteristic.Active.INACTIVE;
2792
2772
  this.currentChannelId = NO_CHANNEL_ID; // string eg SV09038
2773
+ this.lastKeyMacroChannelId = null; // string eg $KeyMacro1
2793
2774
  this.currentClosedCaptionsState = Characteristic.ClosedCaptions.DISABLED;
2794
2775
  this.previousClosedCaptionsState = Characteristic.ClosedCaptions.DISABLED;
2795
2776
  this.currentMediaState = Characteristic.CurrentMediaState.STOP;
@@ -3105,6 +3086,40 @@ class stbDevice {
3105
3086
  this.televisionService.getCharacteristic(Characteristic.RemoteKey)
3106
3087
  .on('set', this.setRemoteKey.bind(this));
3107
3088
 
3089
+
3090
+
3091
+
3092
+ // Custom characteristics
3093
+ // these are visible in Shortcuts with the name "Custom"
3094
+ var hapCharacteristic = {};
3095
+ const BASE_UUID = "-0000-3C36-E400-3C36E4FF0012"; // a random UUID used only for my plugin's characteristics, based on 3C36E4-EOSSTB-003656123456
3096
+ // export const BASE_UUID = "-0000-1000-8000-0026BB765291"; // Apple HomeKit base UUID
3097
+
3098
+ // add a custom hap characteristic for the current channel id, appears as Custom in shortcuts
3099
+ // var hapCharacteristic = new Characteristic(characteristic.displayName, characteristic.UUID, characteristic.props);
3100
+ hapCharacteristic = new Characteristic("Current Channel Id", "00000001" + BASE_UUID, {
3101
+ format: Characteristic.Formats.STRING,
3102
+ perms: [Characteristic.Perms.PAIRED_READ, Characteristic.Perms.NOTIFY]
3103
+ })
3104
+ hapCharacteristic.value = ''; // add a default empty value
3105
+ hapCharacteristic.on('get', this.getCurrentChannelId.bind(this));
3106
+ this.televisionService.addCharacteristic(hapCharacteristic); // add the Characteristic to the televisionService
3107
+ // once added, it can be retrieved with
3108
+ //this.televisionService.getCharacteristic('Current Channel Id')
3109
+
3110
+ // add a custom hap characteristic for the current channel name, appears as Custom in shortcuts
3111
+ // var hapCharacteristic = new Characteristic(characteristic.displayName, characteristic.UUID, characteristic.props);
3112
+ hapCharacteristic = new Characteristic("Current Channel Name", "00000002" + BASE_UUID, {
3113
+ format: Characteristic.Formats.STRING,
3114
+ perms: [Characteristic.Perms.PAIRED_READ, Characteristic.Perms.NOTIFY]
3115
+ })
3116
+ hapCharacteristic.value = ''; // add a default empty value
3117
+ hapCharacteristic.on('get', this.getCurrentChannelName.bind(this));
3118
+ this.televisionService.addCharacteristic(hapCharacteristic); // add the Characteristic to the televisionService
3119
+ // once added, it can be retrieved with
3120
+ //this.televisionService.getCharacteristic('Current Channel Name')
3121
+
3122
+
3108
3123
  //this.log('DEBUG: this.televisionService')
3109
3124
  //this.log(this.televisionService)
3110
3125
 
@@ -3278,19 +3293,20 @@ class stbDevice {
3278
3293
  //+++++++++++++++++++++++++++++++++++++++++++++++++++++
3279
3294
 
3280
3295
  // update the device state changed to async
3281
- //async updateDeviceState(powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, callback) {
3282
- async updateDeviceState(powerState, mediaState, recordingState, channelId, sourceType, profileDataChanged, statusFault, programMode, statusActive, inputDeviceType, inputSourceType, callback) {
3296
+ //async updateDeviceState(powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, callback) {
3297
+ async updateDeviceState(powerState, mediaState, recordingState, channelId, eventId, sourceType, profileDataChanged, statusFault, programMode, statusActive, inputDeviceType, inputSourceType, callback) {
3283
3298
  try {
3284
3299
  // runs at the very start, and then every few seconds, so don't log it unless debugging
3285
3300
  // doesn't get the data direct from the settop box, but rather: gets it from the this.currentPowerState and this.currentChannelId variables
3286
3301
  // which are received by the mqtt messages, which occurs very often
3287
3302
  if (this.config.debugLevel > 0) {
3288
- this.log.warn('%s: updateDeviceState: powerState %s, mediaState %s [%s], recordingState %s [%s], channelId %s, sourceType %s, profileDataChanged %s, statusFault %s [%s], programMode %s [%s], statusActive %s [%s], inputDeviceType %s [%s], inputSourceType %s [%s]',
3303
+ this.log.warn('%s: updateDeviceState: powerState %s, mediaState %s [%s], recordingState %s [%s], channelId %s, eventId %s, sourceType %s, profileDataChanged %s, statusFault %s [%s], programMode %s [%s], statusActive %s [%s], inputDeviceType %s [%s], inputSourceType %s [%s]',
3289
3304
  this.name,
3290
3305
  powerState,
3291
3306
  mediaState, currentMediaStateName[mediaState],
3292
3307
  recordingState, recordingStateName[recordingState], // custom characteristic
3293
3308
  channelId,
3309
+ eventId,
3294
3310
  sourceType,
3295
3311
  profileDataChanged,
3296
3312
  statusFault, Object.keys(Characteristic.StatusFault)[statusFault + 1],
@@ -3316,6 +3332,7 @@ class stbDevice {
3316
3332
  if (mediaState != null) { this.currentMediaState = mediaState; }
3317
3333
  if (recordingState != null) { this.currentRecordingState = recordingState; }
3318
3334
  if (channelId != null) { this.currentChannelId = channelId; }
3335
+ if (eventId != null) { this.currentEventId = eventId; }
3319
3336
  if (sourceType != null) { this.currentSourceType = sourceType; }
3320
3337
  this.profileDataChanged = profileDataChanged || false;
3321
3338
  if (statusFault != null) { this.currentStatusFault = statusFault; }
@@ -3323,6 +3340,12 @@ class stbDevice {
3323
3340
  if (statusActive != null) { this.currentStatusActive = statusActive; }
3324
3341
  if (inputDeviceType != null) { this.currentInputDeviceType = inputDeviceType; }
3325
3342
  if (inputSourceType != null) { this.currentInputSourceType = inputSourceType; }
3343
+
3344
+ // force the keyMacro channel if a keyMacro was last selected as the input
3345
+ if (this.lastKeyMacroChannelId) {
3346
+ this.currentChannelId = this.lastKeyMacroChannelId;
3347
+ }
3348
+
3326
3349
 
3327
3350
 
3328
3351
 
@@ -3567,6 +3590,7 @@ class stbDevice {
3567
3590
  }
3568
3591
 
3569
3592
 
3593
+
3570
3594
  // +++++++++++++++ Input Service characteristics ++++++++++++++
3571
3595
  /*
3572
3596
  inputService
@@ -3581,34 +3605,38 @@ class stbDevice {
3581
3605
  */
3582
3606
  // check for change of InputDeviceType state: (a characteristic of Input Source)
3583
3607
 
3584
- let curInp = this.inputServices.findIndex( InputSource => InputSource.subtype == 'input_' + this.currentChannelId ) + 1;
3608
+ //this.log('looking for input subtype ', 'input_' + this.currentChannelId)
3609
+ let currInputIndex = this.inputServices.findIndex( InputSource => InputSource.subtype == 'input_' + this.currentChannelId );
3610
+ let currinputNumber = currInputIndex + 1;
3611
+ if (currInputIndex < 0) { currInputIndex = null; currinputNumber = null; }
3612
+ //this.log('found input index %s input %s subtype %s', currInputIndex, currInputIndex+1, (this.inputServices[currInputIndex] || {}).subtype)
3585
3613
  if (this.previousInputDeviceType !== this.currentInputDeviceType) {
3586
3614
  this.log('%s: Input Device Type changed on input %s %s from %s [%s] to %s [%s]',
3587
3615
  this.name,
3588
- curInp,
3616
+ currinputNumber,
3589
3617
  this.currentChannelId,
3590
3618
  this.previousInputDeviceType, Object.keys(Characteristic.InputDeviceType)[this.previousInputDeviceType + 1],
3591
3619
  this.currentInputDeviceType, Object.keys(Characteristic.InputDeviceType)[this.currentInputDeviceType + 1]
3592
3620
  );
3593
3621
  }
3594
3622
  //this.televisionService.getCharacteristic(Characteristic.InputDeviceType).updateValue(this.currentInputDeviceType);
3595
- this.inputServices[curInp].getCharacteristic(Characteristic.InputDeviceType).updateValue(this.currentInputDeviceType);
3623
+ if (currInputIndex) { this.inputServices[currInputIndex].getCharacteristic(Characteristic.InputDeviceType).updateValue(this.currentInputDeviceType); }
3596
3624
  this.previousInputDeviceType = this.currentInputDeviceType;
3597
3625
 
3598
3626
  // check for change of InputSourceType state: (a characteristic of Input Source)
3599
3627
  if (this.previousInputSourceType !== this.currentInputSourceType) {
3600
3628
  this.log('%s: Input Source Type changed on input %s %s from %s [%s] to %s [%s]',
3601
3629
  this.name,
3602
- curInp,
3630
+ currinputNumber,
3603
3631
  this.currentChannelId,
3604
3632
  this.previousInputSourceType, Object.keys(Characteristic.InputSourceType)[this.previousInputSourceType + 1],
3605
3633
  this.currentInputSourceType, Object.keys(Characteristic.InputSourceType)[this.currentInputSourceType + 1]);
3606
3634
  }
3607
3635
  // [12/11/2022, 12:22:37] [homebridge-eosstb] This plugin generated a warning from the characteristic 'Input Source Type': Characteristic not in required or optional characteristic section for service Television. Adding anyway.. See https://homebridge.io/w/JtMGR for more info.
3608
- this.inputServices[curInp].getCharacteristic(Characteristic.InputSourceType).updateValue(this.currentInputSourceType); // generates Homebridge warning
3636
+ if (currInputIndex) { this.inputServices[currInputIndex].getCharacteristic(Characteristic.InputSourceType).updateValue(this.currentInputSourceType); } // generates Homebridge warning
3609
3637
  this.previousInputSourceType = this.currentInputSourceType;
3610
- //this.log('++++DEBUG: this.inputServices[curInp]')
3611
- //this.log(this.inputServices[curInp])
3638
+ //this.log('++++DEBUG: this.inputServices[currInputIndex]')
3639
+ //this.log(this.inputServices[currInputIndex])
3612
3640
 
3613
3641
  // +++++++++++++++ end of Input Service characteristics ++++++++++++++
3614
3642
 
@@ -3680,7 +3708,6 @@ class stbDevice {
3680
3708
  maxSources = Math.min(configDevice.maxChannels || maxSources, maxSources);
3681
3709
  }
3682
3710
  }
3683
- //this.log("%s: Setting maxSources to %s", this.name, maxSources);
3684
3711
 
3685
3712
 
3686
3713
  // get a user configured Profile, if it exists, otherwise we will use the default Profile for the channel list
@@ -3845,58 +3872,98 @@ class stbDevice {
3845
3872
  // clear the array
3846
3873
  this.channelList = [];
3847
3874
 
3848
- // limit the amount to load
3849
- const maxChs = Math.min(subscribedChIds.length, maxSources);
3875
+ // check for any custom keymacros, they consume slots at the end of the channelList
3876
+ this.log.debug("%s: Checking for KeyMacros", this.name);
3877
+ let keyMacros = [];
3878
+ if (this.config.channels) {
3879
+ keyMacros = this.config.channels.filter(channel => {
3880
+ if (channel.channelKeyMacro) { return true; }
3881
+ })
3882
+ this.log.debug("%s: Found keyMacros: %s", this.name, keyMacros.length);
3883
+ }
3884
+
3885
+ // limit the amount to load to all the channels and all the keyMacros
3886
+ // keyMacros will occupy top slots of channel list
3887
+ const maxChs = Math.min(subscribedChIds.length + keyMacros.length, maxSources);
3888
+ const firstKeyMacroSlot = Math.max(maxChs - keyMacros.length,0); // never go below index 0
3889
+ //this.log("%s: Loading %s channels, starting at channel 1", this.name, subscribedChIds.length);
3890
+ this.log("%s: Loading %s key macros, starting at channel %s ", this.name, keyMacros.length, firstKeyMacroSlot+1);
3891
+
3892
+ // show log of what will be loaded, very useful for debugging
3850
3893
  this.log("%s: Refreshing channels 1 to %s", this.name, maxChs);
3851
3894
  if (maxChs < maxSources) {
3852
3895
  this.log("%s: Hiding channels %s to %s", this.name, maxChs + 1, maxSources);
3853
3896
  }
3854
3897
 
3855
-
3856
3898
  // loop and load all channels from the subscribedChIds in the order defined by the array
3857
3899
  //this.log("Loading all subscribed channels")
3858
- subscribedChIds.forEach((subscribedChId, i) => {
3900
+ for ( let i = 0; i < maxChs; i++ ) {
3901
+ //subscribedChIds.forEach((subscribedChId, i) => {
3859
3902
  //this.log("In forEach loop, processing index %s %s", i, subscribedChId)
3860
3903
 
3861
3904
  // find the channel to load.
3862
3905
  var channel = {};
3863
3906
  var customChannel = {};
3864
-
3865
- // first look in the config channels list for any user-defined custom channel name
3866
- if (this.config.channels) {
3867
- customChannel = this.config.channels.find(channel => channel.id === subscribedChId);
3868
- if ((customChannel || {}).name) {
3869
- customChannel.name = cleanNameForHomeKit(customChannel.name)
3870
- this.log("%s: Found %s in config channels, setting name to %s", this.name, subscribedChId, customChannel.name);
3871
- } else {
3872
- customChannel = {};
3907
+ let k = 0;
3908
+
3909
+ // load a channel if we are in the range of channel numbers not assigned to keymacros
3910
+ if ( i < firstKeyMacroSlot ) {
3911
+ // this slot needs to be occupied by a channel
3912
+
3913
+ // first look in the config channels list for any user-defined custom channel name
3914
+ if (this.config.channels) {
3915
+ customChannel = this.config.channels.find(channel => channel.id === subscribedChIds[i]);
3916
+ if ((customChannel || {}).name) {
3917
+ customChannel.name = cleanNameForHomeKit(customChannel.name)
3918
+ this.log("%s: Found %s in config channels, setting name to %s", this.name, customChannel.id, customChannel.name);
3919
+ } else {
3920
+ customChannel = {};
3921
+ }
3873
3922
  }
3874
- }
3875
3923
 
3876
- // check if the subscribedChId exists in the master channel list, if not, push it, using the user-defined name if one exists, and channelNumber >10000
3877
- this.log.debug("%s: Index %s: Finding %s in master channel list", this.name, i, subscribedChId);
3878
- channel = this.platform.masterChannelList.find(channel => channel.id === subscribedChId);
3879
- if (!channel) {
3880
- const newChName = customChannel.name || "Channel " + subscribedChId;
3881
- this.log("%s: Unknown channel %s [%s] discovered. Adding to the master channel list", this.name, subscribedChId, newChName);
3882
- this.platform.masterChannelList.push({
3883
- id: subscribedChId,
3884
- name: newChName,
3885
- logicalChannelNumber: 10000 + this.platform.masterChannelList.length, // integer
3886
- linearProducts: this.platform.entitlements.entitlements[0].id // must be a valid entitlement id
3887
- });
3888
- // refresh channel as the not found channel will now be in the masterChannelList
3889
- channel = this.platform.masterChannelList.find(channel => channel.id === subscribedChId);
3890
- channel.configuredName = channel.name; // set a configured name same as name
3924
+
3925
+ // check if the subscribedChId exists in the master channel list, if not, push it, using the user-defined name if one exists, and channelNumber >10000
3926
+ this.log.debug("%s: Index %s: Finding %s in master channel list", this.name, i, subscribedChIds[i]);
3927
+ channel = this.platform.masterChannelList.find(channel => channel.id === subscribedChIds[i]);
3928
+ if (!channel) {
3929
+ const newChName = customChannel.name || "Channel " + subscribedChIds[i];
3930
+ this.log("%s: Unknown channel %s [%s] discovered. Adding to the master channel list", this.name, subscribedChIds[i], newChName);
3931
+ this.platform.masterChannelList.push({
3932
+ id: subscribedChIds[i],
3933
+ name: newChName,
3934
+ logicalChannelNumber: 10000 + this.platform.masterChannelList.length, // integer
3935
+ linearProducts: this.platform.entitlements.entitlements[0].id // must be a valid entitlement id
3936
+ });
3937
+ // refresh channel as the not found channel will now be in the masterChannelList
3938
+ channel = this.platform.masterChannelList.find(channel => channel.id === subscribedChIds[i]);
3939
+ channel.configuredName = channel.name; // set a configured name same as name
3940
+ } else {
3941
+ // show some useful debug data
3942
+ this.log.debug("%s: Index %s: Found %s %s in master channel list", this.name, i, channel.id, channel.name);
3943
+ }
3944
+ this.log.debug("%s: Index %s: Loading channel %s %s", this.name, i, i+1, channel.name);
3945
+
3891
3946
  } else {
3892
- // show some useful debug data
3893
- this.log.debug("%s: Index %s: Found %s %s in master channel list", this.name, i, channel.id, channel.name);
3947
+
3948
+ // this slot needs to be occupied by a keyMacro
3949
+ k = i - firstKeyMacroSlot
3950
+ this.log.debug("%s: Index %s: Loading channel %s keyMacro %s %s", this.name, i, i+1, k+1, keyMacros[k].channelName);
3951
+ this.log,debug("%s: Index %s: Load this keyMacro: %s", this.name, i, keyMacros[k]);
3952
+ channel = {
3953
+ "id": '$KeyMacro' + (k+1),
3954
+ "name": keyMacros[k].channelName,
3955
+ "logicalChannelNumber": 20000 + i,
3956
+ "linearProducts": 0,
3957
+ "keyMacro": keyMacros[k].channelKeyMacro,
3958
+ }
3894
3959
  }
3895
3960
 
3896
- // load this channel as an input
3897
- //this.log("loading input %s of %s", i + 1, maxSources)
3961
+
3962
+ // load this channel/keyMacro as an input
3963
+ //this.log("loading input %s of %s", i + 1, maxChs)
3898
3964
  //this.log.warn("%s: Index %s: Checking if %s %s can be loaded", this.name, i, channel.id, channel.name);
3899
- if (i < maxSources) {
3965
+ if (i < maxChs) {
3966
+ this.log.debug("%s: Index %s: Refreshing channel", this.name, i);
3900
3967
 
3901
3968
  // add the user-defined name if one exists
3902
3969
  if (customChannel && customChannel.name) { channel.name = customChannel.name; }
@@ -3930,8 +3997,6 @@ class stbDevice {
3930
3997
  }
3931
3998
  this.inputServices[i].name = channel.configuredName;
3932
3999
  this.inputServices[i].subtype = 'input_' + channel.id; // string, input_SV09038 etc
3933
- //this.inputServices[i].subtype = 'input_' + i+1; // integer, generates input_1 for index 0
3934
- //this.log.warn("DEBUG: input %s subtype set to %s %s",i+1, channel.id, this.inputServices[i].subtype);
3935
4000
 
3936
4001
  // Name can only be set for SharedProfile where order can never be changed
3937
4002
  if (this.profileid == 0) {
@@ -3942,9 +4007,13 @@ class stbDevice {
3942
4007
  .updateCharacteristic(Characteristic.CurrentVisibilityState, channel.visibilityState)
3943
4008
  .updateCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED);
3944
4009
  //inputService.updateCharacteristic(Characteristic.CurrentVisibilityState, Characteristic.TargetVisibilityState.SHOWN);
4010
+ //this.log.warn('this.inputServices[i]')
4011
+ //this.log.warn(this.inputServices[i])
3945
4012
  }
3946
4013
  }
3947
- });
4014
+
4015
+
4016
+ };
3948
4017
 
3949
4018
  // after loading all the channels, reset the ActiveIdentifier (uint32) to the right Identifier (uint32), as it may have moved slots
3950
4019
  // subtype: 'input_SV09038',
@@ -3960,33 +4029,6 @@ class stbDevice {
3960
4029
  }
3961
4030
  }
3962
4031
 
3963
- // load any custom key macros: in work, not for publich consumption yet
3964
- if (this.config.channels && 1==0) {
3965
- this.config.channels.forEach((customChannel, i) => {
3966
- this.log("In forEach loop, processing config channel index %s %s", i, customChannel)
3967
-
3968
- this.log(customChannel)
3969
- // first look in the config channels list for any user-defined custom channel name
3970
- if (customChannel.channelKeyMacro){
3971
- this.log("%s: Found Key Macro in config channels:", this.name, customChannel.channelKeyMacro, customChannel.channelName)
3972
- let i = this.inputServices.length
3973
- this.log("inputService %s is set to", i-1);
3974
- this.log(this.inputServices[i-1]);
3975
- this.inputServices[i]
3976
- .name = customChannel.channelName
3977
- //.subtype = 'input_KM' + 20000 + i; // string, input_KM20000 + channel offset. KM=KeyMacro
3978
- this.inputServices[i].updateCharacteristic(Characteristic.Name, customChannel.channelName) // stays unchanged at Input 01 etc
3979
- .updateCharacteristic(Characteristic.ConfiguredName, customChannel.channelName)
3980
- .updateCharacteristic(Characteristic.CurrentVisibilityState, Characteristic.CurrentVisibilityState.SHOWN)
3981
- .updateCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED);
3982
-
3983
- this.log("inputService %s is set to", i);
3984
- this.log(this.inputServices[i]);
3985
-
3986
- }
3987
- })
3988
- }
3989
-
3990
4032
 
3991
4033
 
3992
4034
  // save to disk
@@ -4001,7 +4043,7 @@ class stbDevice {
4001
4043
  var appsToload = this.platform.profiles[this.profileId].recentlyUsedApps;
4002
4044
  appsToload.forEach( (appId, i) => {
4003
4045
  this.log("loading app", i, appId);
4004
- if (i <= maxSources) {
4046
+ if (i <= maxChs) {
4005
4047
  // get the channel
4006
4048
  var foundIndex = this.platform.masterChannelList.findIndex(channel => channel.id === appId);
4007
4049
  //this.log("foundIndex", foundIndex);
@@ -4056,7 +4098,7 @@ class stbDevice {
4056
4098
 
4057
4099
  }
4058
4100
 
4059
- this.log("%s: Channel list refreshed with %s channels", this.name, Math.min(maxChs, maxSources));
4101
+ this.log("%s: Channel list refreshed with %s channels (including %s key macros)", this.name, Math.min(maxChs, maxSources), keyMacros.length);
4060
4102
  return false;
4061
4103
 
4062
4104
  } catch (err) {
@@ -4361,17 +4403,20 @@ class stbDevice {
4361
4403
  async setInput(input, callback) {
4362
4404
  input = input ?? {} // ensure input is never null or undefined
4363
4405
  if (this.config.debugLevel > 1) { this.log.warn('%s: setInput input %s %s',this.name, input.id, input.name); }
4364
- callback(null); // for rapid response
4365
- var currentChannelName = NO_CHANNEL_NAME;
4366
- const channel = this.platform.masterChannelList.find(channel => channel.id === this.currentChannelId);
4367
- if (channel) { currentChannelName = channel.name; }
4368
-
4369
- //this.log.warn("setInput: go to input %s", input)
4370
- //this.log.warn(channel)
4371
-
4372
- // robustness: only try to switch channel if an input.id exists
4373
- if (input.id){
4374
- this.log('%s: Change channel from %s [%s] to %s [%s]', this.name, this.currentChannelId, currentChannelName, input.id, input.name);
4406
+ callback(); // for rapid response
4407
+ // get current channel, also finds keyMacro channels
4408
+ let channel = this.channelList.find(channel => channel.id === this.currentChannelId);
4409
+ // if not found look in the master channel list
4410
+ if (!channel) { channel = this.platform.masterChannelList.find(channel => channel.id === this.currentChannelId); }
4411
+
4412
+ // robustness: only try to switch channel if an input.id exists. Handle KeyMacros
4413
+ this.log('%s: Change channel from %s [%s] to %s [%s]', this.name, this.currentChannelId, (channel || {}).name || NO_CHANNEL_NAME, input.id, input.name);
4414
+ if (input.id && input.id.startsWith('$KeyMacro')){
4415
+ this.lastKeyMacroChannelId = input.id; // remember last keyMacro id
4416
+ this.platform.sendKey(this.deviceId, this.name, input.keyMacro);
4417
+
4418
+ } else if (input.id){
4419
+ this.lastKeyMacroChannelId = null; // clear last keyMacro channelId
4375
4420
  this.platform.switchChannel(this.deviceId, this.name, input.id, input.name);
4376
4421
  } else {
4377
4422
  this.log.warn('%s: setInput called with no input.id', this.name);
@@ -4384,18 +4429,13 @@ class stbDevice {
4384
4429
  async getInputName(inputId, callback) {
4385
4430
  // fired by the user changing a channel name in Home app accessory setup
4386
4431
  //if (this.config.debugLevel > 1) { this.log.warn('%s: getInputName inputId %s', this.name, inputId); }
4387
-
4388
- // need to read from stored cache, currently not implemented, TO-DO
4389
- var chName = NO_CHANNEL_NAME; // must have a value
4390
- if (this.channelList[inputId-1]) {
4391
- chName = this.channelList[inputId-1].configuredName;
4392
- }
4432
+ const inputName =(this.channelList[inputId-1] || {}).configuredName || ''; // Empty string if not found
4393
4433
  if (this.config.debugLevel > 1) {
4394
- this.log.warn("%s: getInputName for input %s returning '%s'", this.name, inputId, chName);
4434
+ this.log.warn("%s: getInputName for input %s returning '%s'", this.name, inputId, inputName);
4395
4435
  }
4396
- callback(null, chName);
4436
+ callback(null, inputName);
4397
4437
  };
4398
-
4438
+
4399
4439
  // set input name (change the TV channel name)
4400
4440
  async setInputName(inputId, newInputName, callback) {
4401
4441
  // fired by the user changing a channel name in Home app accessory setup
@@ -4427,10 +4467,39 @@ class stbDevice {
4427
4467
  //this.log('%s: Renamed channel %s from %s to %s (valid only for HomeKit)', this.name, inputId+1, oldInputName, newInputName);
4428
4468
  this.log('%s: Renamed channel %s to %s (valid only for HomeKit)', this.name, inputId+1, newInputName);
4429
4469
  }
4430
- callback(null);
4470
+ callback();
4431
4471
  };
4432
4472
 
4433
4473
 
4474
+ // get current channel id (the TV channel identifier, a string)
4475
+ // added in v2.1.0
4476
+ // custom characteristic, returns a string, the event updates the characteristic value automatically
4477
+ async getCurrentChannelId(callback, currentChannelId) {
4478
+ // fired by the user reading the Custom characteristic in Shortcuts
4479
+ // fired when the accessory is first created and HomeKit requests a refresh
4480
+ // fired when the icon is clicked in the Home app and HomeKit requests a refresh
4481
+ // fired when the Home app is opened
4482
+ currentChannelId = this.currentChannelId || ''; // this.currentChannelId is a string eg SV09038. Empty string if not found
4483
+ if (this.config.debugLevel > 1) { this.log.warn("%s: getCurrentChannelId returning '%s'", this.name, currentChannelId); }
4484
+ callback(null, currentChannelId);
4485
+ };
4486
+
4487
+
4488
+ // get current channel name (the TV channel name)
4489
+ // added in v2.1.0
4490
+ // custom characteristic, returns a string, the event updates the characteristic value automatically
4491
+ async getCurrentChannelName(callback, currentChannelName) {
4492
+ // fired by the user reading the Custom characteristic in Shortcuts
4493
+ // fired when the accessory is first created and HomeKit requests a refresh
4494
+ // fired when the icon is clicked in the Home app and HomeKit requests a refresh
4495
+ // fired when the Home app is opened
4496
+ const curChannel = this.platform.masterChannelList.find(channel => channel.id === this.currentChannelId ); // this.currentChannelId is a string eg SV09038
4497
+ // consider setting to Radio if radio is playing
4498
+ currentChannelName = (curChannel || {}).name || ''; // Empty string if not found
4499
+ if (this.config.debugLevel > 1) { this.log.warn("%s: getCurrentChannelName returning '%s'", this.name, currentChannelName); }
4500
+ callback(null, currentChannelName);
4501
+ };
4502
+
4434
4503
 
4435
4504
  // get input visibility state (of the TV channel in the accessory)
4436
4505
  async getInputVisibilityState(inputId, callback) {
@@ -4442,7 +4511,7 @@ class stbDevice {
4442
4511
  // }
4443
4512
  const visibilityState = this.inputServices[inputId-1].getCharacteristic(Characteristic.CurrentVisibilityState).value;
4444
4513
  if (this.config.debugLevel > 2) {
4445
- this.log.warn('%s: getInputVisibilityState for input %s returning %s [%s]', this.name, inputId, visibilityState, Object.keys(Characteristic.CurrentVisibilityState)[visibilityState + 1]);
4514
+ this.log.warn('%s: getInputVisibilityState input %s returning %s [%s]', this.name, inputId, visibilityState, Object.keys(Characteristic.CurrentVisibilityState)[visibilityState + 1]);
4446
4515
  }
4447
4516
  callback(null, visibilityState);
4448
4517
  }
@@ -4460,7 +4529,7 @@ class stbDevice {
4460
4529
  //not yet active
4461
4530
  //this.platform.persistConfig(this.deviceId, this.channelList);
4462
4531
 
4463
- callback(null); // for rapid response
4532
+ callback(); // for rapid response
4464
4533
  }
4465
4534
 
4466
4535
 
@@ -4481,7 +4550,7 @@ class stbDevice {
4481
4550
  if(this.currentClosedCaptionsState !== targetClosedCaptionsState){
4482
4551
  this.log("setClosedCaptions: not yet implemented");
4483
4552
  }
4484
- callback(null);
4553
+ callback();
4485
4554
  }
4486
4555
 
4487
4556
 
@@ -4512,7 +4581,7 @@ class stbDevice {
4512
4581
  if(this.customPictureMode !== targetPictureMode){
4513
4582
  this.log("setPictureMode: not yet implemented");
4514
4583
  }
4515
- callback(null);
4584
+ callback();
4516
4585
  }
4517
4586
 
4518
4587
 
package/package.json CHANGED
@@ -3,11 +3,11 @@
3
3
  "displayName": "Homebridge EOSSTB",
4
4
  "description": "homebridge-plugin - Add your set-top box to Homekit (for Magenta AT, Telenet BE, Sunrise CH, Virgin Media GB & IE, Ziggo NL)",
5
5
  "author": "Jochen Siegenthaler (https://github.com/jsiegenthaler/)",
6
- "version": "2.0.4",
6
+ "version": "2.1.0",
7
7
  "platformname": "eosstb",
8
8
  "dependencies": {
9
- "axios-cookiejar-support": "^4.0.3",
10
- "axios": "^1.2.1",
9
+ "axios-cookiejar-support": "^4.0.6",
10
+ "axios": "^1.2.2",
11
11
  "debug": "^4.3.4",
12
12
  "mqtt": "^4.3.7",
13
13
  "qs": "^6.11.0",