From 9d46eda9927e8176db57937bc4976c28f81f7b3a Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Wed, 24 Aug 2022 10:37:27 +0200 Subject: [PATCH] release 2.3.0 --- CHANGELOG.md | 7 +- README.md | 30 +++----- index.js | 180 ++++++++++++++++++++++---------------------- package.json | 7 +- src/constans.json | 8 ++ src/xboxlocalapi.js | 106 ++++++++++++++------------ 6 files changed, 178 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb41c3..e1e867a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### NOTE!!! ## After update to 2.x.x the plugin settings (xboxLiveId) need to be updated. -## [2.3.0] - (xx.08.2022) +## [2.3.0] - (24.08.2022) ## Changed - fix MQTT device info -- refactor send debug and info log +- refactor debug and info log - refactor send mqtt message - bump dependencies -- code rebuild +- code cleanup +- added Xbox Guide as default input - fix [#137](https://github.com/grzegorz914/homebridge-xbox-tv/issues/137) ## [2.2.2] - (09.03.2022) diff --git a/README.md b/README.md index 2dcb6aa..41b7ad5 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Homebridge plugin for Microsoft game Consoles. Tested with Xbox One X/S and Xbox ## Troubleshooting * If for some reason the device is not displayed in HomeKit app try this procedure: - * Go to `./homebridge/persist` or `/var/lib/homebridge/persist` for RPI. + * Go to `./homebridge/persist` macOS or `/var/lib/homebridge/persist` for RPI. * Remove `AccessoryInfo.xxx` file which contain Your device data: `{"displayName":"Xbox"}`. * Next remove `IdentifierCashe.xxx` file with same name as `AccessoryInfo.xxx`. * Restart Homebridge and try add it to the HomeKit app again. @@ -60,24 +60,18 @@ Homebridge plugin for Microsoft game Consoles. Tested with Xbox One X/S and Xbox ## Configuration Console * [Device must have Instant-on power mode enabled](https://support.xbox.com/help/hardware-network/power/learn-about-power-modes) * Profile & System > Settings > General > Power mode & startup -* Console need to allow connect from any 3rd app. *Allow Connections from any device* should be enabled. + * Console need to allow connect from any 3rd app. *Allow Connections from any device* should be enabled. * Profile & System > Settings > Devices & Connections > Remote features > Xbox app preferences. ## Authorization Manager * First of all please use built in Authorization Manager. -* Start new authorization need remove old token first, to clear token use Authorization Manager GUI. -* Make sure Your web browser do not block pop-up window, if Yes allow pop-up window for this app. -* If for some reason you cannot use Authorization Manager, please use Authorization Manual Mode (removed ab v2.0.13). + * Start new authorization need remove old token first, to clear token use Authorization Manager GUI. + * Make sure Your web browser do not block pop-up window, if Yes allow pop-up window for this app.

Authentication Manager

-### Authorization Manual Mode (removed ab v2.0.13) -* After enable `webApiControl` option, restart the plugin and go to Homebridge console log. -* Follow the instructions in the console log. -* Start new authorization need remove old token first, go to *./homebridge/xboxTv/* and remove token file. - ### Configuration * Run this plugin as a child bridge (Highly Recommended). * Install and use [Homebridge Config UI X](https://github.com/oznu/homebridge-config-ui-x/wiki) to configure this plugin (Highly Recommended). @@ -108,7 +102,7 @@ Homebridge plugin for Microsoft game Consoles. Tested with Xbox One X/S and Xbox | `filterApps` | If enabled, Apps will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. | | `filterSystemApps` | If enabled, System Apps (Accessory, Microsoft Store, Television) will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. | | `filterDlc` | If enabled, Dlc will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. | -| `inputs.name` | Here set *Input Name* which You want expose to the *Homebridge/HomeKit*, `Screensaver`, `Television`, `TV Settings`, `Dashboard`, `Accessory`, `Settings`, `Network Troubleshooter`, `Microsoft Store` are created by default. | +| `inputs.name` | Here set *Input Name* which You want expose to the *Homebridge/HomeKit*, `Screensaver`, `Television`, `TV Settings`, `Dashboard`, `Accessory`, `Settings`, `Network Troubleshooter`, `Microsoft Store`, `Xbox Guide` are created by default. | | `inputs.reference` | Required to identify current running app. | | `inputs.oneStoreProductId` | Required to switch apps. | | `inputs.type` | Here select from available types. | @@ -203,13 +197,13 @@ Homebridge plugin for Microsoft game Consoles. Tested with Xbox One X/S and Xbox ``` ### Adding to HomeKit -Each accessory needs to be manually paired. -1. Open the Home app on your device. -2. Tap the Home tab, then tap . -3. Tap *Add Accessory*, and select *I Don't Have a Code, Cannot Scan* or *More options*. -4. Select Your accessory and press add anyway. -5. Enter the PIN or scan the QR code, this can be found in Homebridge UI or Homebridge logs. -6. Complete the accessory setup. +* Each accessory needs to be manually paired. + * Open the Home app on your device. + * Tap the Home tab, then tap . + * Tap *Add Accessory*, and select *I Don't Have a Code, Cannot Scan* or *More options*. + * Select Your accessory and press add anyway. + * Enter the PIN or scan the QR code, this can be found in Homebridge UI or Homebridge logs. + * Complete the accessory setup. ## Limitations * That maximum Services for 1 accessory is 100. If Services > 100, accessory stop responding. diff --git a/index.js b/index.js index 666f1e6..8eeab8a 100644 --- a/index.js +++ b/index.js @@ -27,12 +27,12 @@ class XBOXPLATFORM { constructor(log, config, api) { // only load if configured if (!config || !Array.isArray(config.devices)) { - log('No configuration found for %s', PLUGIN_NAME); + log(`No configuration found for ${PLUGIN_NAME}`); return; } this.log = log; this.api = api; - this.devicesConfig = config.devices || []; + this.devicesConfig = config.devices; this.accessories = []; this.api.on('didFinishLaunching', () => { @@ -105,12 +105,13 @@ class XBOXDEVICE { } const inputsCount = this.inputs.length; for (let j = 0; j < inputsCount; j++) { + const input = this.inputs[j]; const obj = { - 'name': this.inputs[j].name, - 'titleId': this.inputs[j].titleId, - 'reference': this.inputs[j].reference, - 'oneStoreProductId': this.inputs[j].oneStoreProductId, - 'type': this.inputs[j].type, + 'name': input.name, + 'titleId': input.titleId, + 'reference': input.reference, + 'oneStoreProductId': input.oneStoreProductId, + 'type': input.type, 'contentType': 'Game' } inputsArr.push(obj); @@ -139,9 +140,6 @@ class XBOXDEVICE { this.mediaState = 0; this.inputIdentifier = 0; - this.pictureMode = 0; - this.brightness = 0; - this.prefDir = path.join(api.user.storagePath(), 'xboxTv'); this.authTokenFile = `${this.prefDir}/authToken_${this.host.split('.').join('')}`; this.devInfoFile = `${this.prefDir}/devInfo_${this.host.split('.').join('')}`; @@ -243,7 +241,7 @@ class XBOXDEVICE { .on('message', (message) => { this.log('Device: %s %s, %s', this.host, this.name, message); }) - .on('deviceInfo', async (firmwareRevision) => { + .on('deviceInfo', async (firmwareRevision, locale) => { if (!this.disableLogDeviceInfo) { this.log('-------- %s --------', this.name); this.log('Manufacturer: %s', this.manufacturer); @@ -253,31 +251,29 @@ class XBOXDEVICE { this.log('----------------------------------'); } - const obj = { - 'manufacturer': this.manufacturer, - 'modelName': this.modelName, - 'serialNumber': this.serialNumber, - 'firmwareRevision': firmwareRevision - }; - const devInfo = JSON.stringify(obj, null, 2); try { + const obj = { + 'manufacturer': this.manufacturer, + 'modelName': this.modelName, + 'serialNumber': this.serialNumber, + 'firmwareRevision': firmwareRevision + }; + const devInfo = JSON.stringify(obj, null, 2); const writeDevInfo = await fsPromises.writeFile(this.devInfoFile, devInfo); - const debug = this.enableDebugMode ? this.log('Device: %s %s, debug writeDevInfo: %s', this.host, this.name, devInfo) : false; + const debug = this.enableDebugMode ? this.log('Device: %s %s, debug saved device info: %s', this.host, this.name, devInfo) : false; } catch (error) { - this.log.error('Device: %s %s, get Device Info error: %s', this.host, this.name, error); + this.log.error('Device: %s %s, device info error: %s', this.host, this.name, error); }; this.firmwareRevision = firmwareRevision; }) .on('stateChanged', (power, titleId, inputReference, volume, mute, mediaState) => { - - const powerState = power; const inputIdentifier = this.inputsReference.indexOf(inputReference) >= 0 ? this.inputsReference.indexOf(inputReference) : this.inputsTitleId.indexOf(titleId) >= 0 ? this.inputsTitleId.indexOf(titleId) : this.inputIdentifier; //update characteristics if (this.televisionService) { this.televisionService - .updateCharacteristic(Characteristic.Active, powerState) + .updateCharacteristic(Characteristic.Active, power) .updateCharacteristic(Characteristic.ActiveIdentifier, inputIdentifier); }; @@ -297,7 +293,7 @@ class XBOXDEVICE { }; }; - this.powerState = powerState; + this.powerState = power; this.volume = volume; this.muteState = mute; this.mediaState = mediaState; @@ -368,17 +364,18 @@ class XBOXDEVICE { const consolesListCount = consolesListData.length; for (let i = 0; i < consolesListCount; i++) { - const id = consolesListData[i].id; - const name = consolesListData[i].name; - const locale = consolesListData[i].locale; - const region = consolesListData[i].region; - const consoleType = consolesListData[i].consoleType; - const powerState = CONSTANS.ConsolePowerState[consolesListData[i].powerState]; // 0 - Off, 1 - On, 2 - ConnectedStandby, 3 - SystemUpdate - const digitalAssistantRemoteControlEnabled = (consolesListData[i].digitalAssistantRemoteControlEnabled == true); - const remoteManagementEnabled = (consolesListData[i].remoteManagementEnabled == true); - const consoleStreamingEnabled = (consolesListData[i].consoleStreamingEnabled == true); - const wirelessWarning = (consolesListData[i].wirelessWarning == true); - const outOfHomeWarning = (consolesListData[i].outOfHomeWarning == true); + const console = consolesListData[i]; + const id = console.id; + const name = console.name; + const locale = console.locale; + const region = console.region; + const consoleType = console.consoleType; + const powerState = CONSTANS.ConsolePowerState[console.powerState]; // 0 - Off, 1 - On, 2 - ConnectedStandby, 3 - SystemUpdate + const digitalAssistantRemoteControlEnabled = (console.digitalAssistantRemoteControlEnabled == true); + const remoteManagementEnabled = (console.remoteManagementEnabled == true); + const consoleStreamingEnabled = (console.consoleStreamingEnabled == true); + const wirelessWarning = (console.wirelessWarning == true); + const outOfHomeWarning = (console.outOfHomeWarning == true); this.consolesId.push(id); this.consolesName.push(name); @@ -392,14 +389,15 @@ class XBOXDEVICE { this.consolesWirelessWarning.push(wirelessWarning); this.consolesOutOfHomeWarning.push(outOfHomeWarning); - const consolesStorageDevicesCount = consolesListData[i].storageDevices.length; + const consolesStorageDevicesCount = console.storageDevices.length; for (let j = 0; j < consolesStorageDevicesCount; j++) { - const storageDeviceId = consolesListData[i].storageDevices[j].storageDeviceId; - const storageDeviceName = consolesListData[i].storageDevices[j].storageDeviceName; - const isDefault = (consolesListData[i].storageDevices[j].isDefault == true); - const freeSpaceBytes = consolesListData[i].storageDevices[j].freeSpaceBytes; - const totalSpaceBytes = consolesListData[i].storageDevices[j].totalSpaceBytes; - const isGen9Compatible = consolesListData[i].storageDevices[j].isGen9Compatible; + const consoleStorageDevice = console.storageDevices[j]; + const storageDeviceId = consoleStorageDevice.storageDeviceId; + const storageDeviceName = consoleStorageDevice.storageDeviceName; + const isDefault = (consoleStorageDevice.isDefault == true); + const freeSpaceBytes = consoleStorageDevice.freeSpaceBytes; + const totalSpaceBytes = consoleStorageDevice.totalSpaceBytes; + const isGen9Compatible = consoleStorageDevice.isGen9Compatible; this.consolesStorageDeviceId.push(storageDeviceId); this.consolesStorageDeviceName.push(storageDeviceName); @@ -434,18 +432,20 @@ class XBOXDEVICE { const profileUsersCount = userProfileData.length; for (let i = 0; i < profileUsersCount; i++) { - const id = userProfileData[i].id; - const hostId = userProfileData[i].hostId; - const isSponsoredUser = userProfileData[i].isSponsoredUser; + const userProfile = userProfileData[i]; + const id = userProfile.id; + const hostId = userProfile.hostId; + const isSponsoredUser = userProfile.isSponsoredUser; this.userProfileId.push(id); this.userProfileHostId.push(hostId); this.userProfileIsSponsoredUser.push(isSponsoredUser); - const profileUsersSettingsCount = userProfileData[i].settings.length; + const profileUsersSettingsCount = userProfile.settings.length; for (let j = 0; j < profileUsersSettingsCount; j++) { - const id = userProfileData[i].settings[j].id; - const value = userProfileData[i].settings[j].value; + const userProfileSettings = userProfileData[i].settings[j]; + const id = userProfileSettings.id; + const value = userProfileSettings.value; this.userProfileSettingsId.push(id); this.userProfileSettingsValue.push(value); @@ -476,22 +476,23 @@ class XBOXDEVICE { const inputsData = getInstalledAppsData.result; const inputsCount = inputsData.length; for (let i = 0; i < inputsCount; i++) { - const oneStoreProductId = inputsData[i].oneStoreProductId; - const titleId = inputsData[i].titleId; - const aumid = inputsData[i].aumid; - const lastActiveTime = inputsData[i].lastActiveTime; - const isGame = (inputsData[i].isGame == true); - const name = inputsData[i].name; - const contentType = inputsData[i].contentType; - const instanceId = inputsData[i].instanceId; - const storageDeviceId = inputsData[i].storageDeviceId; - const uniqueId = inputsData[i].uniqueId; - const legacyProductId = inputsData[i].legacyProductId; - const version = inputsData[i].version; - const sizeInBytes = inputsData[i].sizeInBytes; - const installTime = inputsData[i].installTime; - const updateTime = inputsData[i].updateTime; - const parentId = inputsData[i].parentId; + const input = inputsData[i]; + const oneStoreProductId = input.oneStoreProductId; + const titleId = input.titleId; + const aumid = input.aumid; + const lastActiveTime = input.lastActiveTime; + const isGame = (input.isGame == true); + const name = input.name; + const contentType = input.contentType; + const instanceId = input.instanceId; + const storageDeviceId = input.storageDeviceId; + const uniqueId = input.uniqueId; + const legacyProductId = input.legacyProductId; + const version = input.version; + const sizeInBytes = input.sizeInBytes; + const installTime = input.installTime; + const updateTime = input.updateTime; + const parentId = input.parentId; const type = 'APPLICATION'; const inputsObj = { @@ -535,12 +536,13 @@ class XBOXDEVICE { const storageDevicesCount = storageDeviceData.length; for (let i = 0; i < storageDevicesCount; i++) { - const storageDeviceId = storageDeviceData[i].storageDeviceId; - const storageDeviceName = storageDeviceData[i].storageDeviceName; - const isDefault = (storageDeviceData[i].isDefault == true); - const freeSpaceBytes = storageDeviceData[i].freeSpaceBytes; - const totalSpaceBytes = storageDeviceData[i].totalSpaceBytes; - const isGen9Compatible = storageDeviceData[i].isGen9Compatible; + const storageDevice = storageDeviceData[i]; + const storageDeviceId = storageDevice.storageDeviceId; + const storageDeviceName = storageDevice.storageDeviceName; + const isDefault = (storageDevice.isDefault == true); + const freeSpaceBytes = storageDevice.freeSpaceBytes; + const totalSpaceBytes = storageDevice.totalSpaceBytes; + const isGen9Compatible = storageDevice.isGen9Compatible; this.storageDeviceId.push(storageDeviceId); this.storageDeviceName.push(storageDeviceName); @@ -562,7 +564,7 @@ class XBOXDEVICE { this.log.debug('Device: %s %s, requesting device info from Web API.', this.host, this.name); try { const getConsoleStatusData = await this.xboxWebApi.getProvider('smartglass').getConsoleStatus(this.xboxLiveId); - const debug = this.enableDebugMode ? this.log('Device: %s %s, debug getConsoleStatusData, result: %s', this.host, this.name, getConsoleStatusData) : false; + const debug = this.enableDebugMode ? this.log('Device: %s %s, debug getConsoleStatusData, status: %s result: %s', this.host, this.name, getConsoleStatusData.status, getConsoleStatusData) : false; const consoleStatusData = getConsoleStatusData; const id = consoleStatusData.id; @@ -659,7 +661,7 @@ class XBOXDEVICE { const inputName = this.inputsName[inputIdentifier]; const inputReference = this.inputsReference[inputIdentifier]; const inputOneStoreProductId = this.inputsOneStoreProductId[inputIdentifier]; - const setDashboard = (inputOneStoreProductId === 'Dashboard' || inputOneStoreProductId === 'Settings' || inputOneStoreProductId === 'SettingsTv' || inputOneStoreProductId === 'Accessory' || inputOneStoreProductId === 'Screensaver' || inputOneStoreProductId === 'NetworkTroubleshooter'); + const setDashboard = (inputOneStoreProductId === 'Dashboard' || inputOneStoreProductId === 'Settings' || inputOneStoreProductId === 'SettingsTv' || inputOneStoreProductId === 'Accessory' || inputOneStoreProductId === 'Screensaver' || inputOneStoreProductId === 'NetworkTroubleshooter' || inputOneStoreProductId === 'XboxGuide'); const setTelevision = (inputOneStoreProductId === 'Television'); const setApp = ((inputOneStoreProductId != undefined && inputOneStoreProductId != '0') && !setDashboard && !setTelevision); try { @@ -903,12 +905,13 @@ class XBOXDEVICE { const inputsArr = new Array(); const allInputsCount = allInputs.length; for (let i = 0; i < allInputsCount; i++) { - const contentType = allInputs[i].contentType; + const input = allInputs[i]; + const contentType = input.contentType; const filterGames = this.filterGames ? (contentType != 'Game') : true; const filterApps = this.filterApps ? (contentType != 'App') : true; const filterSystemApps = this.filterSystemApps ? (contentType != 'systemApp') : true; const filterDlc = this.filterDlc ? (contentType != 'Dlc') : true; - const push = (this.getInputsFromDevice) ? (filterGames && filterApps && filterSystemApps && filterDlc) ? inputsArr.push(allInputs[i]) : false : inputsArr.push(allInputs[i]); + const push = (this.getInputsFromDevice) ? (filterGames && filterApps && filterSystemApps && filterDlc) ? inputsArr.push(input) : false : inputsArr.push(input); } //check available inputs and possible inputs count (max 93) @@ -916,21 +919,23 @@ class XBOXDEVICE { const inputsCount = inputs.length; const maxInputsCount = (inputsCount < 93) ? inputsCount : 93; for (let j = 0; j < maxInputsCount; j++) { + //get input + const input = inputs[j]; //get title Id - const inputTitleId = (inputs[j].titleId != undefined) ? inputs[j].titleId : undefined; + const inputTitleId = (input.titleId != undefined) ? input.titleId : undefined; //get input reference - const inputReference = (inputs[j].reference != undefined) ? inputs[j].reference : undefined; + const inputReference = (input.reference != undefined) ? input.reference : undefined; //get input oneStoreProductId - const inputOneStoreProductId = (inputs[j].oneStoreProductId != undefined) ? inputs[j].oneStoreProductId : undefined; + const inputOneStoreProductId = (input.oneStoreProductId != undefined) ? input.oneStoreProductId : undefined; //get input name - const inputName = (savedInputsNames[inputTitleId] != undefined) ? savedInputsNames[inputTitleId] : (savedInputsNames[inputReference] != undefined) ? savedInputsNames[inputReference] : (savedInputsNames[inputOneStoreProductId] != undefined) ? savedInputsNames[inputOneStoreProductId] : inputs[j].name; + const inputName = (savedInputsNames[inputTitleId] != undefined) ? savedInputsNames[inputTitleId] : (savedInputsNames[inputReference] != undefined) ? savedInputsNames[inputReference] : (savedInputsNames[inputOneStoreProductId] != undefined) ? savedInputsNames[inputOneStoreProductId] : input.name; //get input type - const inputType = (inputs[j].type != undefined) ? CONSTANS.InputSourceTypes.indexOf(inputs[j].type) : 10; + const inputType = (input.type != undefined) ? CONSTANS.InputSourceTypes.indexOf(input.type) : 10; //get input configured const isConfigured = 1; @@ -954,9 +959,9 @@ class XBOXDEVICE { const nameIdentifier = (inputTitleId != undefined) ? inputTitleId : (inputReference != undefined) ? inputReference : (inputOneStoreProductId != undefined) ? inputOneStoreProductId : false; let newName = savedInputsNames; newName[nameIdentifier] = name; - const newCustomName = JSON.stringify(newName); + const newCustomName = JSON.stringify(newName, null, 2); try { - const writeNewCustomName = (nameIdentifier != false) ? await fsPromises.writeFile(this.inputsNamesFile, newCustomName) : false; + const writeNewCustomName = nameIdentifier ? await fsPromises.writeFile(this.inputsNamesFile, newCustomName) : false; const logInfo = this.disableLogInfo ? false : this.log('Device: %s %s, saved new Input name successful, name: %s, product Id: %s', this.host, accessoryName, newCustomName, inputOneStoreProductId); } catch (error) { this.log.error('Device: %s %s, saved new Input Name error: %s', this.host, accessoryName, error); @@ -969,9 +974,9 @@ class XBOXDEVICE { const targetVisibilityIdentifier = (inputTitleId != undefined) ? inputTitleId : (inputReference != undefined) ? inputReference : (inputOneStoreProductId != undefined) ? inputOneStoreProductId : false; let newState = savedTargetVisibility; newState[targetVisibilityIdentifier] = state; - const newTargetVisibility = JSON.stringify(newState); + const newTargetVisibility = JSON.stringify(newState, null, 2); try { - const writeNewTargetVisibility = (targetVisibilityIdentifier != false) ? await fsPromises.writeFile(this.inputsTargetVisibilityFile, newTargetVisibility) : false; + const writeNewTargetVisibility = targetVisibilityIdentifier ? await fsPromises.writeFile(this.inputsTargetVisibilityFile, newTargetVisibility) : false; const logInfo = this.disableLogInfo ? false : this.log('Device: %s %s, saved new Target Visibility successful, input: %s, state: %s', this.host, accessoryName, inputName, state ? 'HIDEN' : 'SHOWN'); inputService.setCharacteristic(Characteristic.CurrentVisibilityState, state); } catch (error) { @@ -1044,7 +1049,7 @@ class XBOXDEVICE { return state; }) .onSet(async (state) => { - const setDashboard = (buttonOneStoreProductId === 'Dashboard' || buttonOneStoreProductId === 'Settings' || buttonOneStoreProductId === 'SettingsTv' || buttonOneStoreProductId === 'Accessory' || buttonOneStoreProductId === 'Screensaver' || buttonOneStoreProductId === 'NetworkTroubleshooter'); + const setDashboard = (buttonOneStoreProductId === 'Dashboard' || buttonOneStoreProductId === 'Settings' || buttonOneStoreProductId === 'SettingsTv' || buttonOneStoreProductId === 'Accessory' || buttonOneStoreProductId === 'Screensaver' || buttonOneStoreProductId === 'NetworkTroubleshooter' || buttonOneStoreProductId === 'XboxGuide'); const setTelevision = (buttonOneStoreProductId === 'Television'); const setApp = ((buttonOneStoreProductId != undefined && buttonOneStoreProductId != '0') && !setDashboard && !setTelevision); try { @@ -1053,18 +1058,17 @@ class XBOXDEVICE { const rebootConsole = (this.powerState && state && this.webApiEnabled && buttonMode == 4) ? await this.xboxWebApi.getProvider('smartglass').reboot(this.xboxLiveId) : false; const setAppInput = (this.powerState && state && this.webApiEnabled && buttonMode == 5) ? setApp ? await this.xboxWebApi.getProvider('smartglass').launchApp(this.xboxLiveId, buttonOneStoreProductId) : setDashboard ? await this.xboxWebApi.getProvider('smartglass').launchDashboard(this.xboxLiveId) : setTelevision ? await this.xboxWebApi.getProvider('smartglass').launchOneGuide(this.xboxLiveId) : false : false; const logInfo = this.disableLogInfo ? false : this.log('Device: %s %s, set button successful, name: %s, command: %s', this.host, accessoryName, buttonName, buttonCommand); + buttonService.updateCharacteristic(Characteristic.On, false); } catch (error) { this.log.error('Device: %s %s, set button error, name: %s, error: %s', this.host, accessoryName, buttonName, error); - }; - setTimeout(() => { buttonService.updateCharacteristic(Characteristic.On, false); - }, 200); + }; }); accessory.addService(buttonService); } } - const debug3 = this.enableDebugMode ? this.log('Device: %s %s, publishExternalAccessory.', this.host, accessoryName) : false; this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]); + const debug3 = this.enableDebugMode ? this.log(`Device: ${ this.host} ${accessoryName}, published as external accessory.`) : false; } }; \ No newline at end of file diff --git a/package.json b/package.json index 9642664..b07e52a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Xbox TV", "name": "homebridge-xbox-tv", - "version": "2.3.0-beta.4", + "version": "2.3.0", "description": "Homebridge plugin (https://github.com/homebridge/homebridge) to control Xbox game consoles.", "license": "MIT", "author": "grzegorz914", @@ -37,10 +37,11 @@ "async-mqtt": "^2.6.2", "elliptic": "^6.5.4", "hex-to-binary": ">=1.0.1", - "jsrsasign": "^10.5.26", + "jsrsasign": "^10.5.27", "uuid": ">=8.3.2", "uuid-parse": ">=1.1.0", - "xbox-webapi": "^1.3.0" + "xbox-webapi": "^1.4.0", + "ping": "^0.4.2" }, "keywords": [ "homebridge", diff --git a/src/constans.json b/src/constans.json index 63918f1..4577e27 100644 --- a/src/constans.json +++ b/src/constans.json @@ -82,6 +82,14 @@ "oneStoreProductId": "MicrosoftStore", "type": "HOME_SCREEN", "contentType": "systemApp" + }, + { + "name": "Xbox Guide", + "titleId": "1052052983", + "reference": "Xbox.Guide_8wekyb3d8bbwe!Xbox.Guide.Application", + "oneStoreProductId": "XboxGuide", + "type": "HOME_SCREEN", + "contentType": "systemApp" } ], "InputSourceTypes": ["OTHER", "HOME_SCREEN", "TUNER", "HDMI", "COMPOSITE_VIDEO", "S_VIDEO", "COMPONENT_VIDEO", "DVI", "AIRPLAY", "USB", "APPLICATION"], diff --git a/src/xboxlocalapi.js b/src/xboxlocalapi.js index a8d266c..57665ca 100644 --- a/src/xboxlocalapi.js +++ b/src/xboxlocalapi.js @@ -8,6 +8,7 @@ const JsRsaSign = require('jsrsasign'); const EventEmitter = require('events'); const Packer = require('./packet/packer'); const SGCrypto = require('./sgcrypto'); +const Ping = require('ping'); const CONSTANS = require('./constans.json'); class XBOXLOCALAPI extends EventEmitter { @@ -46,6 +47,14 @@ class XBOXLOCALAPI extends EventEmitter { this.channelRequestId = null; this.message = {}; + this.power = false; + this.volume = 0; + this.mute = true; + this.titleId = ''; + this.reference = ''; + this.mediaState = 0; + this.emitDevInfo = true; + //dgram socket this.socket = new Dgram.createSocket('udp4'); this.socket.on('error', (error) => { @@ -117,8 +126,6 @@ class XBOXLOCALAPI extends EventEmitter { }; }; - clearTimeout(this.closeConnection); - if (this.function == 'status') { const decodedMessage = JSON.stringify(this.response.packetDecoded.protectedPayload); if (this.message === decodedMessage) { @@ -137,13 +144,15 @@ class XBOXLOCALAPI extends EventEmitter { setInterval(async () => { if (!this.isConnected) { - try { + const state = await Ping.promise.probe(this.host, { + timeout: 3 + }); + const debug = this.debugLog ? this.emit('debug', `Ping state: ${JSON.stringify(state, null, 2)}`) : false; + if (state.alive) { const discoveryPacket = new Packer('simple.discoveryRequest'); const message = discoveryPacket.pack(); await this.sendSocketMessage(message); - } catch (error) { - const debug = this.debugLog ? this.emit('debug', `Send discovery error: ${error}`) : false - }; + } }; }, 5000); }) @@ -209,7 +218,7 @@ class XBOXLOCALAPI extends EventEmitter { if (connectionResult == 0) { const debug = this.debugLog ? this.emit('debug', 'Connect response received.') : false; - const debug1 = this.debugLog ? this.emit('debug', 'Stop discovery.') : false; + this.isConnected = true; try { const localJoin = new Packer('message.localJoin'); @@ -231,7 +240,6 @@ class XBOXLOCALAPI extends EventEmitter { 8: 'Sign-in timeout.', 9: 'Sign-in required.' }; - this.isConnected = false; this.emit('error', `Connect error: ${errorTable[connectionResult]}`); }; }) @@ -250,6 +258,7 @@ class XBOXLOCALAPI extends EventEmitter { this.emit('error', `Send acknowledge error: ${error}`) }; + clearTimeout(this.closeConnection); if (this.isConnected) { this.closeConnection = setTimeout(() => { const debug = this.debugLog ? this.emit('debug', `Last message was: 12 seconds ago, send disconnect.`) : false; @@ -258,41 +267,41 @@ class XBOXLOCALAPI extends EventEmitter { }; }) .on('status', (message) => { - if (message.packetDecoded.protectedPayload.apps[0] != undefined) { + if (message.packetDecoded.protectedPayload != undefined) { const decodedMessage = message.packetDecoded.protectedPayload; - const debug = this.debugLog ? this.emit('debug', `Status: ${JSON.stringify(decodedMessage)}`) : false; - - if (!this.isConnected) { - this.emit('connected', 'Connected.'); + const debug = this.debugLog ? this.emit('debug', `Status message: ${JSON.stringify(decodedMessage)}`) : false; + if (this.emitDevInfo) { const majorVersion = decodedMessage.majorVersion; const minorVersion = decodedMessage.minorVersion; - const buildNumber = decodedMessage.buildNumber + const buildNumber = decodedMessage.buildNumber; + const locale = decodedMessage.locale; const firmwareRevision = `${majorVersion}.${minorVersion}.${buildNumber}`; - this.emit('deviceInfo', firmwareRevision); + this.emit('connected', 'Connected.'); + this.emit('deviceInfo', firmwareRevision, locale); + this.emitDevInfo = false; }; - this.isConnected = true; - const appsArray = new Array(); - const appsCount = decodedMessage.apps.length; - for (let i = 0; i < appsCount; i++) { - const titleId = decodedMessage.apps[i].titleId; - const reference = decodedMessage.apps[i].aumId; - const app = { - titleId: titleId, - reference: reference - }; - appsArray.push(app); - const debug = this.debugLog ? this.emit('debug', `Status changed, app Id: ${titleId}, reference: ${reference}`) : false; - } - const power = this.isConnected; + const appsCount = Array.isArray(decodedMessage.apps) ? decodedMessage.apps.length : 0; + const power = true; const volume = 0; const mute = power ? power : true; - const titleId = appsArray[appsCount - 1].titleId; - const inputReference = appsArray[appsCount - 1].reference; const mediaState = 0; - this.emit('stateChanged', power, titleId, inputReference, volume, mute, mediaState); - const mqtt1 = this.mqttEnabled ? this.emit('mqtt', 'State', JSON.stringify(decodedMessage, null, 2)) : false; + const titleId = (appsCount == 1) ? decodedMessage.apps[0].titleId : (appsCount == 2) ? decodedMessage.apps[1].titleId : this.titleId; + const inputReference = (appsCount == 1) ? decodedMessage.apps[0].reference : (appsCount == 2) ? decodedMessage.apps[1].reference : this.reference; + + if (this.power != power || this.titleId != titleId || this.inputReference != inputReference || this.volume != volume || this.mute != mute || this.mediaState != mediaState) { + this.power = power; + this.titleId = titleId; + this.reference = inputReference; + this.volume = volume; + this.mute = mute; + this.mediaState = mediaState; + + this.emit('stateChanged', power, titleId, inputReference, volume, mute, mediaState); + const debug = this.debugLog ? this.emit('debug', `Status changed, app Id: ${titleId}, reference: ${inputReference}`) : false; + const mqtt1 = this.mqttEnabled ? this.emit('mqtt', 'State', JSON.stringify(decodedMessage, null, 2)) : false; + }; }; }).on('channelResponse', (message) => { if (message.packetDecoded.protectedPayload.result == 0) { @@ -582,27 +591,28 @@ class XBOXLOCALAPI extends EventEmitter { }; async disconnect() { - if (this.isConnected) { - const debug = this.debugLog ? this.emit('debug', 'Disconnecting...') : false; - try { - const disconnect = new Packer('message.disconnect'); - disconnect.set('reason', 4); - disconnect.set('errorCode', 0); - const message = disconnect.pack(this); - await this.sendSocketMessage(message); + const debug = this.debugLog ? this.emit('debug', 'Disconnecting...') : false; + clearTimeout(this.closeConnection); - clearTimeout(this.closeConnection); + try { + const disconnect = new Packer('message.disconnect'); + disconnect.set('reason', 4); + disconnect.set('errorCode', 0); + const message = disconnect.pack(this); + await this.sendSocketMessage(message); + this.emit('disconnected', 'Disconnected.'); + + setTimeout(() => { this.isConnected = false; this.requestNum = 0; this.channelTargetId = null; this.channelRequestId = null; + this.power = false; + this.emitDevInfo = true; this.emit('stateChanged', false, 0, 0, 0, true, 0); - this.emit('disconnected', 'Disconnected.'); - } catch (error) { - this.emit('error', `Send disconnect error: ${error}`) - }; - } else { - const debug = this.debugLog ? this.emit('debug', 'Console already disconnected.') : false; + }, 3000); + } catch (error) { + this.emit('error', `Send disconnect error: ${error}`) }; };