[
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n    - cron: '0 0 * * *'  # Runs daily at midnight (UTC)\n  workflow_dispatch: # Allows you to manually trigger the workflow\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v8\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'\n          days-before-stale: 30\n          days-before-close: 7\n          stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'\n          stale-issue-label: 'stale'\n          exempt-issue-labels: 'pinned,security'\n          close-issue-message: 'Closing this issue due to inactivity.'\n          close-pr-message: 'Closing this pull request due to inactivity.'\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nnpm-debug.log\n.DS_Store\n.vscode\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n### NOTE\n\n- After update to 2.x.x the plugin settings (xboxLiveId) need to be updated\n- After update to v3.0.0 RESTFull and MQTT config settings need to be updated\n- After update to v3.4.0 all buttons in config need to be updated\n- After update to v3.9.0 plugin need to be reconfigured and console reauthorized\n- After update to v4.0.0 sensors need to be reconfigured!!!\n\n## Warning\n\n- For plugin < v4.1.0 use Homebridge UI <= v5.5.0\n- For plugin >= v4.1.0 use Homebridge UI >= v5.13.0\n\n## [4.1.13] - (06.04.2026)\n\n## Changes\n\n- refactor inputs switch\n- bump dependencies\n- cleanup\n\n## [4.1.4] - (27.01.2026)\n\n## Changes\n\n- bump dependencies\n- cleanup\n\n## [4.1.1] - (03.01.2026)\n\n## Changes\n\n- bump dependencies\n- warn if console not exist on server\n- cleanup\n\n## [4.1.0] - (01.01.2026)\n\n## Changes\n\n- added support for Homebridge UI >= v5.13.0\n- config schema updated\n- readme updated\n\n## [4.0.0] - (12.12.2025)\n\n## Changes\n\n- after update to v4.0.0 sensors need to be reconfigured!!!\n- full refactor the sensor section code, now is possible to create multiple sensors of differrent types\n- config schema updated\n- redme updated\n- cleanup\n\n## [3.9.16] - (09.12.2025)\n\n## Changes\n\n- moved to MQTT v5\n\n## [3.9.11] - (08.11.2025)\n\n## Changes\n\n- stability and performance improvements\n- bump deependencies\n- config schema updated\n- redme updated\n- cleanup\n\n## [3.9.1] - (30.09.2025)\n\n## Changes\n\n- added possibility to change accessory display type in Home app\n- stability improvements\n- redme updated\n- cleanup\n\n## [3.9.0] - (24.09.2025)\n\n## Changes\n\n- config schema and json refactor\n  - plugin need to be reconfigured\n  - console need to be reauthorized\n- fix [#234](https://github.com/grzegorz914/homebridge-xbox-tv/issues/234)\n- stability improvements\n- bump deependencies\n- redme updated\n- cleanup\n\n## [3.8.1] - (13.09.2025)\n\n## Changes\n\n- fix [#232](https://github.com/grzegorz914/homebridge-xbox-tv/issues/232)\n- bump dependencies\n- cleanup\n\n## [3.8.0] - (10.09.2025)\n\n## Changes\n\n- stability and performance improvement\n- added dynamic firmware info update if changed\n- bump dependencies\n- cleanup\n\n## [3.7.0] - (17.08.2025)\n\n## Changes\n\n- added dynamic add/update/remove input/channel if load from device\n- added dynamic firmware info update if changed\n- cleanup\n\n## [3.6.0] - (30.05.2025)\n\n## Changes\n\n- added speaker option to volume control (for feature use)\n- now if volume control option is set to disable/nonethe  also TV Speakers (hardware control) is disabled\n- stability improvements\n- cleanup\n\n## [3.5.1] - (15.03.2025)\n\n## Changes\n\n- fix [#224](https://github.com/grzegorz914/homebridge-xbox-tv/issues/224)\n- cleanup\n\n## [3.5.0] - (13.03.2025)\n\n## Changes\n\n- added possibility to disable indyvidual accessory\n- bump dependencies\n- config schema updated\n- redme updated\n- cleanup\n\n## [3.4.0] - (05.03.2025)\n\n## Changes\n\n- after update to this version all buttons in config need to be updated\n- refactor code of buttons\n- bump dependencies\n- config schema updated\n- cleanup\n\n## [3.3.11] - (20.02.2025)\n\n## Changes\n\n- stability and improvements\n- fix deprected method\n- cleanup\n\n## [3.3.9] - (07.02.2025)\n\n## Changes\n\n- stability and improvements\n\n## [3.3.8] - (07.02.2025)\n\n## Changes\n\n- fix inputs display order\n\n## [3.3.7] - (06.02.2025)\n\n## Changes\n\n- cleanup and optimizations of web api control\n\n## [3.3.6] - (06.02.2025)\n\n## Changes\n\n- fix HAP-NodeJS WARNING: The accessory has an invalid 'Name' characteristic 'configuredName'\n- Please use only alphanumeric, space, and apostrophe characters\n- Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis\n\n## [3.3.4] - (05.02.2025)\n\n## Changes\n\n- Web Api improvements of authoriztion check and remote management\n- cleanup\n\n## [3.3.3] - (04.02.2025)\n\n## Changes\n\n- fix [#221](https://github.com/grzegorz914/homebridge-xbox-tv/issues/221)\n\n## [3.3.2] - (04.02.2025)\n\n## Changes\n\n- update RESTFul\n- bump dependencies\n\n## [3.3.0] - (19.01.2025)\n\n## Changes\n\n- added possibility to disable/enable log success, info, warn, error\n- refactor cnnect code\n- bump dependencies\n- config schema updated\n- redme updated\n- cleanup\n\n## [3.2.0] - (30.11.2024)\n\n## Changes\n\n- move from commonJS to esm module\n- moved constants.json to constants.js\n- cleanup\n\n## [3.1.12] - (27.09.2024)\n\n## Changes\n\n- fix restFul start [#212](https://github.com/grzegorz914/homebridge-xbox-tv/issues/212)\n- cleanup\n\n## [3.1.6] - (06.09.2024)\n\n## Changes\n\n- Authorization manager layout improvements\n- cleanup\n\n## [3.1.5] - (06.09.2024)\n\n## Changes\n\n- cleanup\n\n## [3.1.4] - (06.09.2024)\n\n## Changes\n\n- fix display duplicated dev info\n- cleanup\n\n## [3.1.3] - (06.09.2024)\n\n## Changes\n\n- refactor web and local api connect code\n- cleanup\n\n## [3.1.0] - (23.08.2024)\n\n## Changes\n\n- add control over RESTFul POST JSON Object\n- cleanup\n\n## [3.0.2] - (18.08.2024)\n\n## Changes\n\n- fixed authorization manager [#204](https://github.com/grzegorz914/homebridge-xbox-tv/issues/204)\n- cleanup\n\n## [3.0.0] - (14.08.2024)\n\n## Changes\n\n### After update to v3.0.0 RESTFull and MQTT config settings need to be updated\n\n- hide passwords by typing and display in Config UI\n- remove return duplicate promises from whole code\n- bump dependencies\n- cleanup\n\n## [2.14.0] - (04.08.2024)\n\n## Changes\n\n- added possiblity to set own volume control name and enable/disable prefix\n- config schema updated\n- bump dependencies\n- cleanup\n\n## [2.13.0] - (05.03.2024)\n\n## Changes\n\n- added support to subscribe MQTT and control device\n- config schema updated\n- cleanup\n\n## [2.12.0] - (02.01.2024)\n\n## Changes\n\n- added possibility to disable prefix name for buttons and sensors\n- config schema updated\n- cleanup\n\n## [2.11.0] - (29.12.2023)\n\n## Changes\n\n- added possibility to select display inputs order, possible by `None`, `Alphabetically Name`, `Alphabetically Reference`\n- config schema updated\n- cleanup\n\n## [2.10.0] - (26.12.2023)\n\n## After update to this version the plugin properties are changed and console must be authorized and settings need to be corrected\n\n## Changes\n\n- full code refactor\n- added possibility toggle Power control between local/web api\n- fixed disconnect problem on first run\n- performance and stability improvements\n- config.schema updated\n- readme updated\n- cleanup\n\n## [2.9.0] - (29.07.2023)\n\n## Changes\n\n- added RESTFul server\n- use JWT token for lokal api if console authorizen\n- code refactor and cleanup\n- config.schema updated\n- fixed some minor issues\n- prepare for next release and features\n\n## [2.8.0] - (20.02.2023)\n\n## Changes\n\n- fix load plugin gui on first start after install\n- authorization manager updated\n- added possibility to set IP Address and Xbox Live ID from Authorization Manager.\n- added possibility to enable Web Api Control from Authorization Manager after authorization successfull done.\n- cleanup\n\n## [2.7.0] - (13.02.2023)\n\n## Changes\n\n- standarize function of display type and volume control, now volume control -1 None/Disabled, 0 Slider, 1 Fan, please see in readme\n- config.schema updated\n- fix expose extra input tile in homekit app\n- other small fixes and improvements\n- cleanup\n\n## [2.6.0] - (12.02.2023)\n\n## Changes\n\n- integrate web api library in to the plugin\n- simplify the authorization manager process(reduced 1 step, correct some words)\n- bump dependencies\n- stability improvements\n- config.schema updated\n- cleanup\n\n## [2.5.0] - (29.01.2023)\n\n## Changes\n\n- update logging\n- added new mqtt topics *Consoles List*, *Profile*, *Apps*, *Storages*, *Status*\n- bump dependencies\n- stability improwements\n- config.schema updated\n- cleanup\n\n## [2.4.0] - (24.01.2023)\n\n## Changes\n\n- added Power Sensor for use with automations (active if power is ON)\n- added Input Sensor for use with automations (activ on every Input change)\n- added Screen Saver Sensor for use with automations (active on change to Screen Saver)\n- added custom Inputs Sensor based on reference for use with automations (active on change to Input)\n- config.schema updated\n- cleanup\n\n## [2.3.16] - (04.01.2023)\n\n## Changes\n\n- fix wrong state after power Off\n- fix display current app\n- fix save target visibility\n- fix save custom names\n\n## [2.3.15] - (04.01.2023)\n\n## Changes\n\n- fix #147 #148\n\n## [2.3.14] - (03.01.2023)\n\n## Changes\n\n- code refactor\n- stability improwements\n\n## [2.3.13] - (31.12.2022)\n\n## Changes\n\n- dynamic update accessory information\n\n## [2.3.12] - (24.12.2022)\n\n## Changed\n\n- fix #145\n\n## [2.3.11] - (18.12.2022)\n\n## Changed\n\n- fix buttons and switch services\n\n## [2.3.10] - (02.12.2022)\n\n## Changed\n\n- fix [#143](https://github.com/grzegorz914/homebridge-xbox-tv/issues/143)\n\n## [2.3.9] - (28.11.2022)\n\n## Changed\n\n- fix [#143](https://github.com/grzegorz914/homebridge-xbox-tv/issues/143)\n- update dependencies\n\n## [2.3.8] - (02.11.2022)\n\n## Changed\n\n- fix error with 2.3.7\n\n## [2.3.7] - (02.11.2022)\n\n## Changed\n\n- code refactor\n\n## [2.3.6] - (10.09.2022)\n\n## Changed\n\n- cleanup\n- added content type properties to inputs\n- bump dependencies\n\n## [2.3.3] - (29.08.2022)\n\n## Changed\n\n- cleanup\n- rebuild mqtt topics\n\n## [2.3.2] - (28.08.2022)\n\n## Changed\n\n- fix publish MQTT message\n\n## [2.3.0] - (24.08.2022)\n\n## Changed\n\n- fix MQTT device info\n- refactor debug and info log\n- refactor send mqtt message\n- bump dependencies\n- code cleanup\n- added Xbox Guide as default input\n- fix [#137](https://github.com/grzegorz914/homebridge-xbox-tv/issues/137)\n\n## [2.2.2] - (09.03.2022)\n\n## Changed\n\n- MQTT Client connection process\n\n## Fixed\n\n- webApiControl switch state\n\n## [2.2.0] - (27.02.2022)\n\n## Added\n\n- MQTT Client, publish all device data\n- possibility to set custom command for Info button in RC\n\n## Changes\n\n- update dependencies\n- code refactor\n\n## [2.1.3] - (28.01.2022)\n\n### Fixed\n\n- offset out of range\n- code refactor\n\n## [2.1.2] - (21.01.2022)\n\n### Fixed\n\n- [#136](https://github.com/grzegorz914/homebridge-xbox-tv/issues/136)\n\n## [2.1.1] - (21.01.2022)\n\n### Changed\n\n- refactor debug message logging\n- update readme\n\n### Fixed\n\n- wrong variables\n- removed unnecessary async\n- report unknown message if power on fail\n\n## [2.1.0] - (21.01.2022)\n\n### Added\n\n- check authorization state of console every 10 min. if powered ON and web api control enabled\n- check cosole data and installed apps every 10 min. if powered ON and web api control enabled\n\n### Changed\n\n- send status message data only if changed\n- debug message logging\n- code refactor\n- code cleanup\n- stability and performance improvements\n\n### Fixed\n\n- unexpected set authorization to true however the console is not authorized\n- data offset out of range [#133](https://github.com/grzegorz914/homebridge-xbox-tv/issues/133)\n- incorrect client authorization on console\n\n## [2.0.13] - (15.01.2022)\n\n### Added\n\n- Network Troubleshooter as defaul input\n\n### Changed\n\n- removed manual authorization method\n- code cleanup\n- redme update\n\n### Fixed\n\n- services calculation count\n\n## [2.0.12] - (09.01.2022)\n\n### Changed\n\n- code cleanup\n\n## [2.0.10/11] - (08.01.2022)\n\n### Changed\n\n- rebuild device info read and write\n\n## [2.0.9] - (03.01.2022)\n\n### Added\n\n- ability to disable log device info by every connections device to the network (Advanced Section)\n\n### Fixed\n\n- unexpected power on after power off\n\n## [2.0.8] - 2021-12-29\n\n### Added\n\n- prevent load plugin if host or xboxLiveId not set\n- prepare directory and files synchronously\n\n## [2.0.6] - 2021-12-28\n\n### Added\n\n- better handle clientId if not defined in config\n\n## [2.0.3] - 2021-12-28\n\n### Added\n\n- Selectable display type of buttons in HomeKit app\n\n## [2.0.2] - 2021-12-28\n\n### Changed\n\n- Changed switches to buttons appear in HomeKit accessory\n\n## [2.0.1] - 2021-12-26\n\n### Fixed\n\n- RC Control\n\n## [2.0.0] - 2021-12-25\n\n### Added\n\n- Screensaver and Settings TV input as default\n- Smartglass library (based on @unknownskl code) as standalone packet, completelly rebuilded\n- Debug mode\n- TV Remote control (buttons)\n- Media control (buttons)\n- Game Pad control (buttons)\n- Clear web api token from plugin config menu\n\n### Changes\n\n- full code rebuild\n- config.schema updated\n- dependencies updated\n- authorizatin manager updated\n- removed bramnding\n\n### Fixed\n\n- memmory leak on some scenerious\n- protocol disconnect if send multiple command at once\n- authorization manager\n\n## [1.8.13] - 2021-12-01\n\n### Fixed\n\n- fix authorization UI Manager open URI\n\n## [1.8.12] - 2021-12-01\n\n### Fixed\n\n- fix authorization UI Manager\n\n## [1.8.8] - 2021-11-04\n\n### Fixed\n\n- fix some connect/disconnect case\n- fix some remote command not send\n\n## [1.8.7] - 2021-11-01\n\n### Changes\n\n- performance improvement\n\n## [1.8.6] - 2021-10-30\n\n### Fixed\n\n- fix powerOn\n\n## [1.8.3] - 2021-10-30\n\n### Fixed\n\n- fix graphic in settings\n\n## [1.8.2] - 2021-10-30\n\n### Changes\n\n- code optimize\n- config.schema update\n- redme update\n\n## [1.8.1] - 2021-10-26\n\n### Fixed\n\n- fixed callback issue ([#105](https://github.com/grzegorz914/homebridge-xbox-tv/issues/105))\n\n## [1.8.0] - 2021-10-26\n\n### Changes\n\n- added possibility Record Game DVR\n- rebuild connection proces to console\n- fixed Authorization Manager error on first run\n- removed 'Undefined Input', not nedded any more\n- code cleanup\n\n## [1.7.9] - 2021-09-26\n\n### Changes\n\n- config.schema update\n\n## [1.7.8] - 2021-09-24\n\n### Changes\n\n- update authorization manager\n- code cleanup\n\n## [1.7.5] - 2021-09-05\n\n### Changes\n\n- update config schema\n- extend fiter possibility\n- code cleanup\n\n## [1.7.3] - 2021-09-03\n\n### Changes\n\n- update config schema\n\n## [1.7.2] - 2021-09-03\n\n### Changes\n\n- added filter for Games, Apps, Dlc\n\n## [1.7.1] - 2021-08-31\n\n### Changes\n\n- code refactorin\n- added default inputs TV, Settings, Dashboard, Accessory, no need to create it in config\n- many small changes and stability improvements\n\n## [1.6.3] - 2021-08-05\n\n### Changes\n\n- added alternative check current running app if reference app is missing\n- removed unnecessary reference property from buttons in config.json\n\n## [1.6.2] - 2021-08-05\n\n### Changes\n\n- added possibility reboot console\n- added possibility switch to Television input\n- code and config reconfigured\n- update config schema\n\n## [1.6.0] - 2021-08-04\n\n### Changes\n\n- fixes\n\n## [1.6.0] - 2021-08-04\n\n### Changes\n\n- added possibility load inputs list direct from device\n- chenged config properties, please adapted config to latest one\n- changed stored files names, may be need authenticate console again or just copy authentication Token to the new created file(authToken_xxxx)\n- update dependencies\n- code rebuild\n\n## [1.5.0] - 2021-04-11\n\n### Changes\n\n- added control over Web Api\n- code rebuild\n\n## [1.4.0] - 2021-02-19\n\n### Changes\n\n- code rebuild, use Characteristic.onSet\n- require Homebridge 1.3.x or above\n\n## [1.3.10] - 2021-02-15\n\n### Added and Fixed\n\n- Add possibility disable log info, options available in config\n- Fix memory leak\n\n## [1.3.2] - 2021-01-18\n\n### Fixed\n\n- Fix log info regarding Input references ([#63](https://github.com/grzegorz914/homebridge-xbox-tv/issues/63))\n\n## [1.3.1] - 2021-01-06\n\n### Fixed\n\n- Fix `getAppChannelLineups` data error.\n\n## [1.3.0] - 2020-11-20\n\n### Fixed\n\n- Dependency bump ([#55](https://github.com/grzegorz914/homebridge-xbox-tv/issues/55))\n\n## [1.2.41] - 2020-11-20\n\n### Fixed\n\n- Fix slow response on RC control.\n\n## [1.2.1] - 2020-09-18\n\n### Changes\n\n- Updated device category to `TV_SET_TOP_BOX` ([#47](https://github.com/grzegorz914/homebridge-xbox-tv/pull/47))\n\n## [1.2.0] - 2020-09-17\n\n### Changes\n\n- Fix send power on until successful ([#38](https://github.com/grzegorz914/homebridge-xbox-tv/issues/38))\n- Fix remote control function ([#28](https://github.com/grzegorz914/homebridge-xbox-tv/issues/28))\n- Add `refreshInterval` with a default of five seconds.\n- Updated config layout.\n\n## [1.1.0] - 2020-09-06\n\n### Changes\n\n- Completely reconfigured layout of the configuration schema.\n\n## [1.0.0] - 2020-06-28\n\n### Added\n\n- Release version.\n\n## [0.9.0] - 2020-05-23\n\n### Added\n\n- Add possibility to select what a type of extra volume control you want to use. None, Slider, Fan.\n\n## [0.8.21] - 2020-05-23\n\n### Changes\n\n- Output app reference to log when opening app ([#22](https://github.com/grzegorz914/homebridge-xbox-tv/issues/22), [#26](https://github.com/grzegorz914/homebridge-xbox-tv/issues/26))\n  - Used for discovering the value to use for `reference` when adding new inputs.\n\n## [0.8.0] - 2020-05-20\n\n### Added\n\n- Add mute ON/OFF to the slider volume.\n\n## [0.7.60] - 2020-05-18\n\n### Fixed\n\n- Fix bug in RC control.\n\n## [0.7.35] - 2020-05-17\n\n### Added\n\n- Add read console configuration after Homebridge restart and save to `/homebridge_folder/xboxTv/` file.\n\n## [0.7.2] - 2020-05-14\n\n### Added\n\n- Add descriptions in `config.schema.json`.\n\n## [0.7.0] - 2020-05-14\n\n### Added\n\n- Revert back with defaults inputs.\n- Add input type to inputs.\n- Add other fixes in code to prevent app crash without configured inputs.\n\n## [0.6.0] - 2020-05-14\n\n### Breaking Changes\n\n- Update your config.json: Add types to the inputs.\n\n### Default Inputs\n\n```json\n\"inputs\": [\n {\n  \"name\": \"TV\",\n  \"reference\": \"Microsoft.Xbox.LiveTV_8wekyb3d8bbwe!Microsoft.Xbox.LiveTV.Application\",\n  \"type\": \"HDMI\"\n },\n {\n  \"name\": \"Dashboard\",\n  \"reference\": \"Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application\",\n  \"type\": \"HOME_SCREEN\"\n },\n {\n  \"name\": \"Settings\",\n  \"reference\": \"Microsoft.Xbox.Settings_8wekyb3d8bbwe!Xbox.Settings.Application\",\n  \"type\": \"OTHER\"\n },\n {\n  \"name\": \"Accessory\",\n  \"reference\": \"Microsoft.XboxDevices_8wekyb3d8bbwe!App\",\n  \"type\": \"OTHER\"\n }\n]\n```\n\n## [0.5.0] - 2020-05-10\n\n### Changes\n\n- Code cleanup.\n- Miscellaneous fixes and performance improvements.\n\n## [0.4.0] - 2020-05-06\n\n### Changes\n\n- Adapted to HAP-Node JS lib.\n\n## [0.3.12] - 2020-05-05\n\n### Changes\n\n- Cleanup code.\n\n### Breaking Changes\n\n- Update your config.json: replace `apps` with `inputs`.\n\n## [0.3.12] - 2020-05-05\n\n### Changes\n\n- Fix and performance improvements.\n- Corrected logging state.\n\n## [0.3.9] - 2020-05-05\n\n### Added\n\n- Add real time read and write data for lightbulb slider volume value.\n\n## [0.2.3] - 2020-04-27\n\n### Added\n\n- Add switch ON/OFF volume control.\n\n## [0.2.1] - 2020-04-27\n\n### Added\n\n- Add Siri volume control.\n- Add slider or Brightness volume control.\n\n## [0.1.39] - 2020-04-21\n\n- Different fixes.\n\n## [0.1.12] - 2020-04-13\n\n- Fix memory leak.\n\n## [0.1.9] - 2020-04-07\n\n- Fix store of position in HomeKit favorites.\n\n## [0.1.6] - 2020-04-06\n\n- Test 2.\n\n## [0.1.5] - 2020-04-05\n\n- Test 1.\n\n## [0.1.2] - 2020-04-05\n\n- Some improvements.\n\n## [0.1.1] - 2020-04-05\n\n- Update `README.md`.\n- Update `sample-config.json`.\n\n## [0.1.0] - 2020-03-29\n\n- Fix crash if no device name defined.\n- Fix `config.schema.json`.\n- Fix store file inside the Homebridge directory.\n\n## [0.0.118] - 2020-03-29\n\n- Small fixes.\n\n## [0.0.115] - 2020-03-21\n\n- Corrections for Homebridge git.\n- Performance improvements.\n\n## [0.0.1] - 2020-02-05\n\n- Initial release.\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the plugin locally (requires a working Homebridge setup)\nhomebridge\n\n# No test suite exists — the test script exits with an error by design\nnpm test\n```\n\nThere is no lint or build step. The project uses native ES modules (`\"type\": \"module\"` in package.json) and runs directly in Node.js without transpilation.\n\n## Architecture\n\nThis is a Homebridge platform plugin that exposes Xbox consoles as HomeKit TV accessories. The entry point is [index.js](index.js), which registers `XboxPlatform` under the `XboxTv` platform name.\n\n### Startup flow\n\n`XboxPlatform` (index.js) reads `config.devices[]` and, for each device, runs a startup `ImpulseGenerator` that retries every 120 s until `XboxDevice.start()` succeeds. Once a device is up, it is published via `api.publishExternalAccessories()` and the startup generator stops, handing off to `XboxDevice`'s own connect generator (6 s interval).\n\n### Core classes\n\n- **[src/xboxdevice.js](src/xboxdevice.js)** — Central device class (`EventEmitter`). Orchestrates both APIs, builds all HAP services/characteristics, handles HomeKit gets/sets, and drives external integrations. All log events bubble up via `emit('info'|'warn'|'error'|'debug'|'success')`.\n- **[src/impulsegenerator.js](src/impulsegenerator.js)** — Lightweight interval scheduler. `state(true, [{name, sampling}])` starts named intervals that fire events; `state(false)` clears them. Used as a polling/heartbeat mechanism throughout the codebase.\n- **[src/functions.js](src/functions.js)** — Shared utilities: file I/O (`readData`/`saveData`), network ping, diacritics normalization.\n- **[src/constants.js](src/constants.js)** — All protocol constants: HAP `InputSourceTypes`, Web API URLs/scopes, Local API channel UUIDs, SmartGlass message type/flag maps, default built-in inputs (Dashboard, Settings, etc.), `DiacriticsMap`.\n\n### Two control paths\n\n**Local API** (`src/localApi/`) — UDP-based SmartGlass protocol over the LAN:\n- `xboxlocalapi.js` — Manages a UDP socket, discovery/connect handshake, heartbeat ping, and inbound message dispatch.\n- `sgcrypto.js` — ECDH key exchange (P-256) + AES-128 + HMAC-SHA256 for the SmartGlass session.\n- `simple.js` / `message.js` / `packets.js` / `structure.js` — Packet construction and parsing for the two SmartGlass packet families (simple pre-auth, encrypted post-auth).\n\n**Web API** (`src/webApi/`) — Microsoft Xbox cloud REST API:\n- `xboxwebapi.js` — Polls authorization every 15 min, sends commands via `axios` to `xccs.xboxlive.com`, retrieves console status and installed app list.\n- `authentication.js` — OAuth 2.0 flow (OAuth → User token → XSTS token) with token persistence to `authToken_<host>` file.\n- `providers/` — Individual REST endpoint wrappers (achievements, catalog, people, etc.). These are used by the web API to fetch app/game metadata.\n\n### External integrations\n\n- **[src/restful.js](src/restful.js)** — Express HTTP server. GET routes expose device state; POST `/` accepts `{key: value}` JSON to send commands back to the device.\n- **[src/mqtt.js](src/mqtt.js)** — MQTT v5 client. Publishes state updates to `microsoft/<prefix>/<name>/<key>` topics; subscribes to `…/Set` for inbound commands.\n\n### Persistent storage\n\nAll per-device state files live under `<homebridge-storage>/xboxTv/` with a host-IP-derived suffix (dots stripped):\n- `authToken_<host>` — OAuth + XSTS tokens (JSON)\n- `devInfo_<host>` — Console hardware info\n- `inputs_<host>` — Installed app/game list from Web API\n- `inputsNames_<host>` — User-customized input display names (from HomeKit)\n- `inputsTargetVisibility_<host>` — Per-input visibility state (from HomeKit)\n\n### Homebridge UI\n\n`homebridge-ui/server.js` runs as a `HomebridgePluginUiServer` and exposes two custom endpoints used by the Config UI:\n- `/clearToken` — Wipes the auth token file so the OAuth flow can restart.\n- `/startAuthorization` — Drives the OAuth token exchange when the user pastes a code.\n\n### HomeKit accessory model\n\nEach Xbox is published as an external accessory (`Categories.TELEVISION`). Services created in `XboxDevice.start()`:\n- `Television` — primary, with `ActiveIdentifier` mapped to inputs\n- `TelevisionSpeaker` — volume/mute\n- Up to 85 `InputSource` services (built-in defaults + Web API app list + user-configured static inputs)\n- Optional `Switch`/`Outlet` services for each configured button\n- Optional `MotionSensor`/`OccupancySensor`/`ContactSensor` services for each configured sensor\n- Optional `Lightbulb` service for volume control\n\nInput identifiers are 1-based integers assigned at build time; display order can be sorted by name or reference via `inputs.displayOrder` config.\n"
  },
  {
    "path": "LICENSE",
    "content": "# MIT License\n\nCopyright (c) 2022 Grzegorz Kaczor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Xbox and controller\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/homebridge-xbox-tv.png\" width=\"640\"></a>\n</p>\n\n<span align=\"center\">\n\n# Homebridge Xbox TV\n\n[![verified-by-homebridge](https://img.shields.io/badge/homebridge-verified-purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)\n[![npm](https://shields.io/npm/dt/homebridge-xbox-tv?color=purple)](https://www.npmjs.com/package/homebridge-xbox-tv)\n[![npm](https://shields.io/npm/v/homebridge-xbox-tv?color=purple)](https://www.npmjs.com/package/homebridge-xbox-tv)\n[![npm](https://img.shields.io/npm/v/homebridge-xbox-tv/beta.svg?style=flat-square)](https://www.npmjs.com/package/homebridge-xbox-tv)\n[![GitHub pull requests](https://img.shields.io/github/issues-pr/grzegorz914/homebridge-xbox-tv.svg)](https://github.com/grzegorz914/homebridge-xbox-tv/pulls)\n[![GitHub issues](https://img.shields.io/github/issues/grzegorz914/homebridge-xbox-tv.svg)](https://github.com/grzegorz914/homebridge-xbox-tv/issues)\n\n<a href=\"https://buycoffee.to/grzegorz914\" target=\"_blank\"><img src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/main/graphics/buycoffee-button.png\" style=\"width: 234px; height: 61px\" alt=\"Supports My Work\"></a> <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/main/graphics/QR_buycoffee.png\" width=\"61\"></a>\n\n</span>\n\n## Package Requirements\n\n| Package | Installation | Role | Required |\n| --- | --- | --- | --- |\n| [Homebridge](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required |\n| [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge Web User Interface | Required |\n| [Xbox TV](https://www.npmjs.com/package/homebridge-xbox-tv) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-xbox-tv/wiki) | Homebridge Plug-In | Required |\n\n## Warning\n\n* For plugin < v4.1.0 use Homebridge UI <= v5.5.0.\n* For plugin >= v4.1.0 use Homebridge UI >= v5.13.0.\n\n## About The Plugin\n\n* Power ON/OFF short press tile in HomeKit app.\n* Reboot Console with button, rquired `webApiControl` enabled.\n* Record Game DVR with button, rquired `webApiControl` enabled.\n* RC/Media/Volume control from RC app on iPhone/iPad and possible over web/local api.\n* Speaker control from RC app on iPhone/iPad `Speaker Service`.\n* Legacy Volume/Mute control is possible throught extra `lightbulb`/`fan` (slider).\n* Apps, Inputs, Games can be switched if `webApiControl` is enabled and console is authorized.\n* Siri can be used for all functions, some times need to be created legacy buttons/switches/sensors.\n* Automations can be used for all functions, some times need to be created legacy buttons/switches/sensors.\n* Support external integrations, [RESTFul](https://github.com/grzegorz914/homebridge-xbox-tv?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-xbox-tv?tab=readme-ov-file#mqtt-integration).\n\n<p align=\"center\">\n <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Accessory tile in the HomeKit app\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/homekit.png\" width=\"382\" /></a>\n <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Changing the accessory input\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/inputs.png\" width=\"135\" /></a>\n <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Arrow pointing to the remote control icon in the control center\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/rc1.png\" width=\"135\" /></a>\n  <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Remote control interface\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/RC.png\" width=\"135\" /></a>\n</p>\n\n## Authorization Manager\n\n* First of all please use built in Authorization Manager.\n  * Start new authorization need remove old token first, to clear token use Authorization Manager GUI.\n  * Make sure Your web browser do not block pop-up window, if Yes allow pop-up window for this app.\n\n<p align=\"center\">\n  <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img alt=\"Authentication Manager\" src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/config manager.png\" width=\"540\"></a>\n</p>\n\n## Configuration\n\n* [Device must have Instant-on power mode enabled](https://support.xbox.com/help/hardware-network/power/learn-about-power-modes)\n  * Profile & System > Settings > General > Power mode & startup\n  * Console need to allow connect from any 3rd app. *Allow Connections from any device* should be enabled.\n  * Profile & System > Settings > Devices & Connections > Remote features > Xbox app preferences.\n* Run this plugin as a [Child Bridge](https://github.com/homebridge/homebridge/wiki/Child-Bridges) (Highly Recommended), this prevent crash Homebridge if plugin crashes.\n* Install and use [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x/wiki) to configure this plugin.\n* The `sample-config.json` can be edited and used as an alternative (advanced users).\n\n<p align=\"center\">\n <a href=\"https://github.com/grzegorz914/homebridge-xbox-tv\"><img src=\"https://raw.githubusercontent.com/grzegorz914/homebridge-xbox-tv/master/graphics/ustawienia.png\" width=\"840\"></a>\n</p>\n\n| Key | Description |\n| --- | --- |\n| `name` | Here set the accessory `Name` to be displayed in `Homebridge/HomeKit`. |\n| `host` | Here set the `Hsostname or Address IP` of Console.|\n| `xboxLiveId` | On your console select Profile > Settings > System > Console info, listed as ``Xbox network device ID``. `You can only find the Xbox network device ID in Settings on your console, this is different from your console serial number`. |\n| `displayType` | Accessory type to be displayed in Home app: `0 - None/Disabled`, `1 - Television` , `2 - TV Set Top Box`, `3 - TV Streaming Stick`, `4 - Audio Receiver`. |\n| `webApi{}` | Web Api object. |\n| `webApi.enable` | This enable console control over Web Api. Additional functions are available in `Advanced Settings` section. |\n| `webApi.token` | Required if `webApiControl` enabled, use Authorization Manager to get it. |\n| `webApi.clientId` | Here set your own Client Id from Azure AD or leave empty if you do not have own account. |\n| `webApi.clientSecret` | Here set your Client Secret from Azure AD or leave empty if you do not have own account. |\n| `inputs{}` | Inputs object. |\n| `inputs.getFromDevice`| If enabled, apps will be loaded from device, only available if `webApiControl` enabled. |\n| `inputs.filterGames` | If enabled, Games will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. |\n| `inputs.filterApps` | If enabled, Apps will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. |\n| `inputs.filterSystemApps` | If enabled, System Apps (Accessory, Microsoft Store, Television) will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. |\n| `inputs.filterDlc` | If enabled, Dlc will be hidden and not displayed in the inputs list, only available if `webApiControl` enabled. |\n| `inputs.displayOrder` | Here select display order of the inputs list, `0 -None`, `1 -Ascending by Name`, `2 - Descending by Name`, `3 - Ascending by Reference`, `4 - Ascending by Reference`. |\n| `inputs.data[]` | Inputs data array. |\n| `inputs.data[].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. |\n| `inputs.data[].reference` | Required to identify current running app. |\n| `inputs.data[].oneStoreProductId` | Required to switch apps. |\n| `inputs.data[].contentType` | Here select from available content types. |\n| `buttons[]` | Buttons array. |\n| `buttons[].displayType` | Here select display type in HomeKit app, possible `0 - None/Disabled`, `1 - Outlet`, `2 - Switch`. |\n| `buttons[].name` | Here set `Button Name` which You want expose to the `Homebridge/HomeKit`. |\n| `buttons[].mode` | Here select button mode, `0 - Media Control`, `1 - Game Pad Control`, `2 - TV Remote Control`, `3 - Console Control`, `4 - Game/App Control`. |\n| `buttons[].mediaCommand` | Here select media control command. |\n| `buttons[].gamePadCommand` | Here select game pad control command. |\n| `buttons[].tvRemoteCommand` | Here select tv remote control command. |\n| `buttons[].consoleControlCommand` | Here select console control command. |\n| `buttons[].gameAppControlCommand` | Here set `oneStoreProductId`, only possible if `webApiControl` enabled. |\n| `buttons[].namePrefix` | Here enable/disable the accessory name as a prefix for button name.|\n| `sensors[]` | Sensors array. |\n| `sensors[].displayType` | Here choose the sensor type to be exposed in HomeKit app, possible `0 - None/Disabled`, `1 - Motion Sensor`, `2 - Occupancy Sensor`, `3 - Contact Sensor`. |\n| `sensors[].mode` | Here choose the sensor mode, possible `0 - App / Games`, `1 - Power`, `2 - Volume`, `3 - Mute`, `4 - Screen Saver`, `5 - Play State`. |\n| `sensors[].name` | Here set own sensor `Name` which You want expose to the `Homebridge/HomeKit`. |\n| `sensors[].reference` | Here set mode `Reference` like `Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application`, sensor fired on switch to this reference. |\n| `sensors[].pulse` | Here enable sensor pulse, sensor send pulse and fired on every value change of selected mode. |\n| `sensors[].namePrefix` | Here enable the accessory name as a prefix for sensor name. |\n| `sensors[].level` | Here set `Level` between `0-100`, sensor fired on this level. |\n| `volume{}` | Volume object. |\n| `volume.displayType` | Here choice what a additional volume control mode You want to use `0 - None/Disabled`, `1 - Lightbulb`, `2 - Fan`, `3 - TV Speaker (only hardware buttons on R.C. app)`, `4 - TV Speaker / Lightbulb`, `5 - TV Speaker / Fan`. |\n| `volume.name` | Here set Your own volume control name or leave empty. |\n| `volume.namePrefix` | Here enable/disable the accessory name as a prefix for volume control name. |\n| `infoButtonCommand` | Here select the function of `I` button in RC app. |\n| `log{}` | Log object. |\n| `log.deviceInfo` | If enabled, log device info will be displayed by every connections device to the network. |\n| `log.sSuccess` | If enabled, success log will be displayed in console. |\n| `log.info` | If enabled, info log will be displayed in console. |\n| `log.warn` | If enabled, warn log will be displayed in console. |\n| `log.error` | If enabled, error log will be displayed in console. |\n| `log.debug` | If enabled, debug log will be displayed in console. |\n| `restFul{}` | RESTFul object. |\n| `restFul.enable` | If enabled, RESTful server will start automatically and respond to any path request. |\n| `restFul.port` | Here set the listening `Port` for RESTful server. |\n| `mqtt{}` | MQTT object. |\n| `mqtt.enable` | If enabled, MQTT Broker will start automatically and publish all awailable PV data. |\n| `mqtt.host` | Here set the `IP Address` or `Hostname` for MQTT Broker. |\n| `mqtt.port` | Here set the `Port` for MQTT Broker, default 1883. |\n| `mqtt.clientId` | Here optional set the `Client Id` of MQTT Broker. |\n| `mqtt.prefix` | Here set the `Prefix` for `Topic` or leave empty. |\n| `mqtt.auth{}` | MQTT authorization object. |\n| `mqtt.auth.enable` | Here enable authorization for MQTT Broker. |\n| `mqtt.auth.user` | Here set the MQTT Broker user. |\n| `mqtt.auth.passwd` | Here set the MQTT Broker password. |\n| `reference`, `oneStoreProductId` | If web Api enabled then all available in `./homebridge/xboxTv/inputs_xxxxxx` file. |\n\n## Create App on Azure AD\n\n* Go to [Azure Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)\n* Register new *App* + *New registration*\n  * Enter a name for your app\n  * Set *Supported account types* to *Personal Microsoft accounts only*\n  * Click register\n  * Choose *Redirect URIs* -> *Add a Redirect URI*\n  * Click *Add a platform* -> *Mobile and desktop applications*\n  * Enter custom redirect URI *<http://localhost:8888/auth/callback>*\n* From the overview of your app page, copy *Application (client) ID* to `webApiClientId`\n* Save restart plugin and authorize console again and have fun.\n\n### RESTFul Integration\n\n* POST data as a JSON Object `{Power: true}`, content type must be `application/json`\n* Path `status` response all available paths.\n\n| Method | URL | Path | Response | Type |\n| --- | --- | --- | --- | --- |\n| GET | `http//ip:port` | `info`, `state`, `consoleslist`, `profile`, `apps`, `storages`, `status`. | `{\"power\": true, \"app\": Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application}` | JSON object. |\n\n| Method | URL | Key | Value | Type | Description |\n| --- | --- | --- | --- | --- | --- |\n| POST | `http//ip:port` | `Power` | `true`, `false` | boolean | Power state. |\n|      | `http//ip:port` | `App` | `Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application` | string | Set app. |\n|      | `http//ip:port` | `RcControl` | `fastForward` | string | Send RC command. |\n|      | `http//ip:port` | `Volume` | `up`, `down` | string | Set volume. |\n|      | `http//ip:port` | `Mute` | `true`, `false` | boolean | Set mute. |\n\n### MQTT Integration\n\n* Subscribe data as a JSON Object `{App: \"Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application\"}`\n\n| Method | Topic | Message | Type |\n| --- | --- | --- | --- |\n| Publish | `Info`, `State`, `Consoles List`, `Profile`, `Apps`, `Storages`, `Status` | `{\"power\": true, \"app\": Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application}` | JSON object. |\n\n| Method | Topic | Key | Value | Type | Description |\n| --- | --- | --- | --- | --- | --- |\n| Subscribe | `Set` | `Power` | `true`, `false` | boolean | Power state. |\n|           | `Set` | `App` | `Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application` | string | Set app. |\n|           | `Set` | `RcControl` | `fastForward` | string | Send RC command. |\n|           | `Set` | `Volume` | `up`, `down` | string | Set volume. |\n|           | `Set` | `Mute` | `true`, `false` | boolean | Set mute. |\n"
  },
  {
    "path": "config.schema.json",
    "content": "{\n\t\"pluginAlias\": \"XboxTv\",\n\t\"pluginType\": \"platform\",\n\t\"singular\": true,\n\t\"fixArrays\": true,\n\t\"strictValidation\": true,\n\t\"customUi\": true,\n\t\"headerDisplay\": \"This plugin works with Xbox Game Consoles. Devices are exposed to HomeKit as separate accessories and each needs to be manually paired.\",\n\t\"footerDisplay\": \"For documentation please see [GitHub repository](https://github.com/grzegorz914/homebridge-xbox-tv).\",\n\t\"schema\": {\n\t\t\"type\": \"object\",\n\t\t\"properties\": {\n\t\t\t\"name\": {\n\t\t\t\t\"title\": \"Platform\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"Xbox TV\"\n\t\t\t},\n\t\t\t\"devices\": {\n\t\t\t\t\"type\": \"array\",\n\t\t\t\t\"items\": {\n\t\t\t\t\t\"title\": \"Device\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\"title\": \"Name\",\n\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\"placeholder\": \"Game console\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"host\": {\n\t\t\t\t\t\t\t\"title\": \"IP/Hostname\",\n\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\"placeholder\": \"192.168.1.10 or xbox.local\",\n\t\t\t\t\t\t\t\"pattern\": \"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\\\\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*)$\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"xboxLiveId\": {\n\t\t\t\t\t\t\t\"title\": \"Live ID\",\n\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\"placeholder\": \"FD00000000000000\",\n\t\t\t\t\t\t\t\"description\": \"Xbox Live ID\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\"title\": \"Accessory type\",\n\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\"maximum\": 4,\n\t\t\t\t\t\t\t\"default\": 2,\n\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"None / Disabled\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Television\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"TV Set Top Box\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"TV Streaming Stick\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Audio Receiver\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"description\": \"Accessory type for Home app\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"webApi\": {\n\t\t\t\t\t\t\t\"title\": \"Web Api\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Enable\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable console control over Web Api.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"token\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Token\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"Web Api Token\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here put the reponse Token (value after ?code=) from the authorization URL.\",\n\t\t\t\t\t\t\t\t\t\"format\": \"password\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].webApi.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"clientId\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Client Id\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"a34ac209-edab-4b08-91e7-a4558d8da1bd\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set your own Client Id from Azure AD or leave empty if you do not have own account.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].webApi.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"clientSecret\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Client Secret\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"Client Secret\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set your Client Secret from Azure AD or leave empty if you do not have own account.\",\n\t\t\t\t\t\t\t\t\t\"format\": \"password\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].webApi.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"allOf\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\"enable\"\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"const\": true\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\"token\"\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"inputs\": {\n\t\t\t\t\t\t\t\"title\": \"Inputs\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"getFromDevice\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Load Inputs From Device\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"description\": \"This function get all available inputs direct from device, manually configured inputs will be skipped.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].webApi.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"filterGames\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Hide Games\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"If enabled, Games will not be displayed on the list of inputs.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"filterApps\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Hide Apps\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"If enabled, Apps will not be displayed on the list of inputs.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"filterSystemApps\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Hide System Apps\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"If enabled, System Apps (Accessory, TV, Network Troubleshooter, Xbox Guide) will not be displayed on the list of inputs.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"filterDlc\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Hide DLC\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"If enabled, DLC will not be displayed on the list of inputs.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"displayOrder\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Display Order\",\n\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\"maximum\": 4,\n\t\t\t\t\t\t\t\t\t\"description\": \"Here select display order of the inputs list.\",\n\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Ascending by Name\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Descending by Name\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Ascending by Reference\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Descending by Reference\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"data\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Inputs\",\n\t\t\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Input\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Name\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Input name\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set Your own name.\"\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"titleId\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Title Id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Input Title Id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set title Id. If web api is enable all available in */var/lib/homebridge/xboxTv/inputs_xxxxxx* file.\"\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"reference\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Reference\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Input reference\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set reference. If web api is enable all available in */var/lib/homebridge/xboxTv/inputs_xxxxxx* file.\"\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"oneStoreProductId\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Product Id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"oneStoreProductId\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the *oneStoreProductId*. If web api enable, switch app/games will be possible, all available in */var/lib/homebridge/xboxTv/inputs_xxxxxx* file.\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].webApi.enable === true;\"\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"contentType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Content Type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select source input type.\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Game\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Game\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Application\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"App\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"System Appliction\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"System App\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Dlc\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Dlc\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].inputs.getFromDevice === false || model.devices[arrayIndices].webApi.enable === false;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"buttons\": {\n\t\t\t\t\t\t\t\"title\": \"Button\",\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Display Type\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\t\"maximum\": 2,\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select display type in HomeKit app.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Outlet\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Switch\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Name\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Button name\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set Your own name.\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Mode\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\t\"maximum\": 4,\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the function mode.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Media Control\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad Control\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"TV Remote Control\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Console Control\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Game/App Control\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"mediaCommand\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Media Command\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the media command.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Play\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"play\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Pause\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"pause\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Play/Pause\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"playPause\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Stop\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"stop\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Record\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"record\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Next Track\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"nextTrack\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Previous Track\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"prevTrack\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Fast Forward\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"fastForward\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Rewind\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"rewind\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Channel Up\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"channelUp\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Channel Down\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"channelDown\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Back\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"back\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"View\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"view\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Menu\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"menu\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Seek\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"seek\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"gamePadCommand\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad Command\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the controler command.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Nexus\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"nexus\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"View\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"view\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Menu\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"menu\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"A\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"a\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"B\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"b\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"X\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"x\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Y\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"y\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Up\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"up\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Down\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"down\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Left\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"left\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Right\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"right\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 1;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"tvRemoteCommand\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"TV Remote Command\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the tv remote command.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Volume Up\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"volUp\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Volume Down\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"volDown\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Mute\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"volMute\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 2;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"consoleControlCommand\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Console Control Command\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the console control command.\",\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Reboot\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"reboot\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Record Game DVR\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"recordGameDvr\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 3;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"gameAppControlCommand\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Product Id\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"oneStoreProductId\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the *oneStoreProductId*. If web api enabled, switch app/games will be possible, all available in */var/lib/homebridge/xboxTv/inputs_xxxxxx* file.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 4;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"namePrefix\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Prefix\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here enable/disable the accessory name as a prefix for button name.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"allOf\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"mediaCommand\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"gamePadCommand\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"tvRemoteCommand\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"consoleControlCommand\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"gameAppControlCommand\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sensors\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"title\": \"Sensors\",\n\t\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Display Type\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\t\"maximum\": 3,\n\t\t\t\t\t\t\t\t\t\t\"default\": 0,\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Motion Sensor\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Occupancy Sensor\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Contact Sensor\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select sensor type to be exposed in HomeKit app.\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Mode\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\t\"maximum\": 5,\n\t\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"App / Games\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Power\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Volume\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Mute\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Screen Saver\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Play State\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t5\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here select the sensor mode.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Name\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Name\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set Your own name.\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"namePrefix\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Prefix\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here enable the accessory name as a prefix for sensor name.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"pulse\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Pulse\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here enable sensor pulse, sensor send pulse and fired on every value change of selected mode.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"reference\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Reference\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"Reference\",\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the channel reference, sensor fired if switch to this reference.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].pulse === false && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 0;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"level\": {\n\t\t\t\t\t\t\t\t\t\t\"title\": \"Level\",\n\t\t\t\t\t\t\t\t\t\t\"type\": \"number\",\n\t\t\t\t\t\t\t\t\t\t\"minimum\": 0,\n\t\t\t\t\t\t\t\t\t\t\"maximum\": 100,\n\t\t\t\t\t\t\t\t\t\t\"multipleOf\": 1,\n\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the level between 0-100% at which the sensor fired.\",\n\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].pulse === false && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 2;\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"allOf\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"pulse\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"pulse\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"const\": false\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"reference\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"pulse\"\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"pulse\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"const\": false\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"mode\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"level\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"volume\": {\n\t\t\t\t\t\t\t\"title\": \"Volume\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"displayType\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Display Type\",\n\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here select what a extra volume/mute control type You want to use.\",\n\t\t\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Lightbulb\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Fan\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"TV Speaker (HW buttons)\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"TV Speaker / Lightbulb\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"TV Speaker / Fan\",\n\t\t\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\t\t5\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Name\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"Volume Control Name\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set Your own volume control name or leave empty.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].volume.displayType > 0;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"namePrefix\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Prefix\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here enable/disable the accessory name as a prefix for volume control name.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices[0]].volume.displayType > 0;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"infoButtonCommand\": {\n\t\t\t\t\t\t\t\"title\": \"Info Button\",\n\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\"default\": \"nexus\",\n\t\t\t\t\t\t\t\"description\": \"Here select the function of info button in RC.\",\n\t\t\t\t\t\t\t\"anyOf\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad View\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"view\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad Nexus\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"nexus\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad Menu\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"menu\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad A\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"a\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad B\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"b\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad X\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"x\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"title\": \"Game Pad Y\",\n\t\t\t\t\t\t\t\t\t\"enum\": [\n\t\t\t\t\t\t\t\t\t\t\"y\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"log\": {\n\t\t\t\t\t\t\t\"title\": \"Log\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"deviceInfo\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Device Info\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": true,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level device info, will display overall device info by every plugin restart\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"success\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Success\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": true,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level success\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"info\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Info\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level info\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"warn\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Warn\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": true,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level warn\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"error\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Error\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": true,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level error\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"debug\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Debug\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable log level debug\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"restFul\": {\n\t\t\t\t\t\t\t\"title\": \"RESTFul\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Enable\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable RESTful server.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"port\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Port\",\n\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": 3000,\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set the listening Port for RESTful server.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].restFul.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"mqtt\": {\n\t\t\t\t\t\t\t\"title\": \"MQTT\",\n\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Enable\",\n\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\"default\": false,\n\t\t\t\t\t\t\t\t\t\"description\": \"This enable MQTT client.\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"host\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"IP/Hostname\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"192.168.1.20 or mqtt.local\",\n\t\t\t\t\t\t\t\t\t\"pattern\": \"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\\\\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*)$\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set the IP/Hostname of MQTT Broker.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"port\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Port\",\n\t\t\t\t\t\t\t\t\t\"type\": \"integer\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": 1883,\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set the port of MQTT Broker.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"clientId\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Client ID\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"client id\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here optional set the Client ID of MQTT Broker.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.enable === true\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"prefix\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Prefix\",\n\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\"placeholder\": \"home\",\n\t\t\t\t\t\t\t\t\t\"description\": \"Here set the prefix.\",\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.enable === true;\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"auth\": {\n\t\t\t\t\t\t\t\t\t\"title\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Enable\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\"description\": \"This enable authorization for MQTT Broker.\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"user\": {\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"User\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"user\",\n\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the user of MQTT Broker.\",\n\t\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.auth.enable === true;\"\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\"passwd\": {\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Password\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\"placeholder\": \"password\",\n\t\t\t\t\t\t\t\t\t\t\t\"description\": \"Here set the password of MQTT Broker.\",\n\t\t\t\t\t\t\t\t\t\t\t\"format\": \"password\",\n\t\t\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.auth.enable === true;\"\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\t\t\t\t\"functionBody\": \"return model.devices[arrayIndices].mqtt.enable === true;\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"allOf\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enable\"\n\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"const\": true\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"user\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"passwd\"\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"allOf\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"if\": {\n\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\"enable\"\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\t\t\t\t\t\"enable\": {\n\t\t\t\t\t\t\t\t\t\t\t\t\"const\": true\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"then\": {\n\t\t\t\t\t\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\t\t\t\t\t\"host\"\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\n\t\t\t\t\t\t\"name\",\n\t\t\t\t\t\t\"host\",\n\t\t\t\t\t\t\"xboxLiveId\",\n\t\t\t\t\t\t\"displayType\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"layout\": [\n\t\t{\n\t\t\t\"key\": \"devices\",\n\t\t\t\"type\": \"tabarray\",\n\t\t\t\"title\": \"{{ value.name || 'Device' }}\",\n\t\t\t\"items\": [\n\t\t\t\t\"devices[].name\",\n\t\t\t\t\"devices[].host\",\n\t\t\t\t\"devices[].xboxLiveId\",\n\t\t\t\t\"devices[].displayType\",\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"devices[].inputs\",\n\t\t\t\t\t\"type\": \"section\",\n\t\t\t\t\t\"title\": \"Inputs\",\n\t\t\t\t\t\"expandable\": true,\n\t\t\t\t\t\"expanded\": false,\n\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\"devices[].inputs.getFromDevice\",\n\t\t\t\t\t\t\"devices[].inputs.filterGames\",\n\t\t\t\t\t\t\"devices[].inputs.filterApps\",\n\t\t\t\t\t\t\"devices[].inputs.filterSystemApps\",\n\t\t\t\t\t\t\"devices[].inputs.filterDlc\",\n\t\t\t\t\t\t\"devices[].inputs.displayOrder\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"key\": \"devices[].inputs.data\",\n\t\t\t\t\t\t\t\"type\": \"tabarray\",\n\t\t\t\t\t\t\t\"title\": \"{{ value.name || 'Input' }}\",\n\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\"devices[].inputs.data[].name\",\n\t\t\t\t\t\t\t\t\"devices[].inputs.data[].titleId\",\n\t\t\t\t\t\t\t\t\"devices[].inputs.data[].reference\",\n\t\t\t\t\t\t\t\t\"devices[].inputs.data[].oneStoreProductId\",\n\t\t\t\t\t\t\t\t\"devices[].inputs.data[].contentType\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\"type\": \"section\",\n\t\t\t\t\t\"title\": \"Buttons\",\n\t\t\t\t\t\"expandable\": true,\n\t\t\t\t\t\"expanded\": false,\n\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"key\": \"devices[].buttons\",\n\t\t\t\t\t\t\t\"type\": \"tabarray\",\n\t\t\t\t\t\t\t\"title\": \"{{ value.name || 'Button' }}\",\n\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\"devices[].buttons[].displayType\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].name\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].mode\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].mediaCommand\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].gamePadCommand\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].tvRemoteCommand\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].consoleControlCommand\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].gameAppControlCommand\",\n\t\t\t\t\t\t\t\t\"devices[].buttons[].namePrefix\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\"type\": \"section\",\n\t\t\t\t\t\"title\": \"Advanced Settings\",\n\t\t\t\t\t\"expandable\": true,\n\t\t\t\t\t\"expanded\": false,\n\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\t\t\"type\": \"tabarray\",\n\t\t\t\t\t\t\t\"title\": \"{{ value.title }}\",\n\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[].volume\",\n\t\t\t\t\t\t\t\t\t\"title\": \"Volume\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\"devices[].volume.displayType\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].volume.name\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].volume.namePrefix\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\t\t\t\t\"title\": \"Sensors\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].sensors\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"tabarray\",\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"{{ value.name || 'Sensor' }}\",\n\t\t\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].displayType\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].mode\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].name\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].namePrefix\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].pulse\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].reference\",\n\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].sensors[].level\"\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\t\t\t\t\"title\": \"Device\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\"devices[].infoButtonCommand\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[].webApi\",\n\t\t\t\t\t\t\t\t\t\"title\": \"Web Api\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\"devices[].webApi.enable\",\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].webApi.token\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"password\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].webApi.clientId\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"password\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].webApi.clientSecret\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"password\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[].log\",\n\t\t\t\t\t\t\t\t\t\"title\": \"Log\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.deviceInfo\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.success\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.info\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.warn\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.error\",\n\t\t\t\t\t\t\t\t\t\t\"devices[].log.debug\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\t\t\t\t\"title\": \"External Integrations\",\n\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[]\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"tabarray\",\n\t\t\t\t\t\t\t\t\t\t\t\"title\": \"{{ value.title }}\",\n\t\t\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].restFul\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"RESTFul\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].restFul.enable\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].restFul.port\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].mqtt\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"MQTT\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.enable\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.host\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.port\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.clientId\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.prefix\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].mqtt.auth\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"title\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"items\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.auth.enable\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"devices[].mqtt.auth.user\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"key\": \"devices[].mqtt.auth.passwd\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"password\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"
  },
  {
    "path": "homebridge-ui/public/index.html",
    "content": "<div class=\"container mt-3\">\n  <div class=\"text-center\">\n    <img src=\"homebridge-xbox-tv.png\" alt=\"Image\" height=\"120\" />\n  </div>\n\n  <div id=\"authorizationManager\" class=\"card card-body mt-2\">\n    <form id=\"configForm\">\n      <div class=\"text-center\">\n        <label id=\"deviceName\" class=\"fw-bold\" style=\"font-size: 23px;\">Xbox</label><br>\n        <label id=\"info\" class=\"d-block\" style=\"font-size: 17px;\"></label>\n        <label id=\"info1\" class=\"d-block\" style=\"font-size: 15px;\"></label>\n      </div>\n\n      <div class=\"mb-2\">\n        <label for=\"deviceHost\" class=\"form-label\">Host</label>\n        <input id=\"deviceHost\" type=\"text\" class=\"form-control\" required>\n      </div>\n\n      <div class=\"mb-2 position-relative\">\n        <label for=\"deviceLiveId\" class=\"form-label\">Xbox Live ID</label>\n        <div class=\"input-group\">\n          <input id=\"deviceLiveId\" type=\"password\" class=\"form-control\" autocomplete=\"new-password\" required>\n          <button type=\"button\" id=\"toggleLiveId\" class=\"btn btn-outline-secondary\">\n            <i class=\"fas fa-eye\"></i>\n          </button>\n        </div>\n      </div>\n\n      <div class=\"mb-2 position-relative\">\n        <label for=\"deviceToken\" class=\"form-label\">Web API Token</label>\n        <div class=\"input-group\">\n          <input id=\"deviceToken\" type=\"password\" class=\"form-control\" autocomplete=\"new-password\" required>\n          <button type=\"button\" id=\"toggleToken\" class=\"btn btn-outline-secondary\">\n            <i class=\"fas fa-eye\"></i>\n          </button>\n        </div>\n      </div>\n\n      <div class=\"form-check mb-2\">\n        <input id=\"deviceWebApiControl\" type=\"checkbox\" class=\"form-check-input\">\n        <label for=\"deviceWebApiControl\" class=\"form-check-label\">Web API Control</label>\n      </div>\n\n      <div class=\"text-center\">\n        <button id=\"startAuthorizationButton\" type=\"button\" class=\"btn btn-primary\">Start Authorization</button>\n        <button id=\"clearTokenButton\" type=\"button\" class=\"btn btn-secondary\">Clear Web API Token</button>\n        <button id=\"configButton\" type=\"button\" class=\"btn btn-secondary\"><i class=\"fas fa-gear\"></i></button>\n      </div>\n    </form>\n    <div id=\"consoleButton\" class=\"d-flex flex-wrap justify-content-center gap-1 mt-3\"></div>\n  </div>\n</div>\n\n<script>\n  (async () => {\n    const pluginConfig = await homebridge.getPluginConfig();\n\n    if (!pluginConfig.length) {\n      pluginConfig.push({});\n      await homebridge.updatePluginConfig(pluginConfig);\n      homebridge.showSchemaForm();\n      return;\n    }\n\n    const devices = pluginConfig[0].devices || [];\n    const devicesCount = devices.length;\n\n    // Helper to get DOM elements\n    const $ = id => document.getElementById(id);\n\n    // Helper to set button classes\n    const setButtonClass = (activeIndex) => {\n      for (let j = 0; j < devicesCount; j++) {\n        $(`button${j}`).className = j === activeIndex ? \"btn btn-primary\" : \"btn btn-secondary\";\n      }\n    };\n\n    // Helper to update the device form\n    const updateDeviceForm = (device) => {\n      $('deviceName').innerHTML = device.name || '';\n      $('deviceHost').value = device.host || '';\n      $('deviceLiveId').value = device.xboxLiveId || '';\n      $('deviceToken').value = device.webApi.token || '';\n      $('deviceWebApiControl').checked = device.webApi.enable || false;\n\n      const tokenLength = device.webApi.token?.length || 0;\n      $('startAuthorizationButton').innerText = tokenLength <= 10 ? \"Start Authorization\" : \"Check State\";\n      $('deviceWebApiControl').disabled = tokenLength <= 10;\n\n      if (tokenLength <= 10) {\n        $('deviceWebApiControl').checked = false;\n        device.webApi.enable = false;\n      }\n    };\n\n    // Create buttons for each device\n    const container = document.getElementById(\"consoleButton\");\n    container.style.display = 'flex';\n    container.style.flexWrap = 'wrap';\n    container.style.justifyContent = 'center';\n    container.style.gap = '0.25rem';\n\n    devices.forEach((device, i) => {\n      this.device = device;\n\n      const button = document.createElement(\"button\");\n      button.type = \"button\";\n      button.id = `button${i}`;\n      button.className = \"btn btn-primary\";\n      button.style.textTransform = 'none';\n      button.innerText = device.name || `Device ${i + 1}`;\n      container.appendChild(button);\n\n      button.addEventListener(\"click\", async () => {\n        setButtonClass(i);\n        updateDeviceForm(devices[i]);\n        this.device = device;\n      });\n\n      // Auto-select the first device\n      if (i === devicesCount - 1) {\n        $(\"button0\").click();\n      }\n    });\n\n    // Show the authorization form\n    $(\"authorizationManager\").style.display = \"block\";\n\n    // Config button toggle\n    let configButtonState = false;\n    $(\"configButton\").addEventListener(\"click\", () => {\n      configButtonState = !configButtonState;\n      homebridge[configButtonState ? 'showSchemaForm' : 'hideSchemaForm']();\n      configButton.className = configButtonState ? 'btn btn-primary' : 'btn btn-secondary';\n    });\n\n    // Token toggle\n    $(\"toggleLiveId\").addEventListener(\"click\", () => {\n      const liveIdInput = $(\"deviceLiveId\");\n      const icon = document.querySelector('#toggleLiveId i');\n\n      if (liveIdInput.type === 'password') {\n        liveIdInput.type = 'text';\n        icon.classList.replace('fa-eye', 'fa-eye-slash');\n      } else {\n        liveIdInput.type = 'password';\n        icon.classList.replace('fa-eye-slash', 'fa-eye');\n      }\n    });\n\n    // Token toggle\n    $(\"toggleToken\").addEventListener(\"click\", () => {\n      const tokenInput = $(\"deviceToken\");\n      const icon = document.querySelector('#toggleToken i');\n\n      if (tokenInput.type === 'password') {\n        tokenInput.type = 'text';\n        icon.classList.replace('fa-eye', 'fa-eye-slash');\n      } else {\n        tokenInput.type = 'password';\n        icon.classList.replace('fa-eye-slash', 'fa-eye');\n      }\n    });\n\n    // Update config on form input\n    $(\"configForm\").addEventListener(\"input\", async () => {\n      const currentDevice = this.device;\n\n      currentDevice.host = $('deviceHost').value;\n      currentDevice.xboxLiveId = $('deviceLiveId').value;\n      currentDevice.webApi.token = $('deviceToken').value;\n      currentDevice.webApi.enable = $('deviceWebApiControl').checked;\n\n      const tokenLength = currentDevice.webApi.token?.length || 0;\n      $('startAuthorizationButton').innerText = tokenLength <= 10 ? \"Start Authorization\" : \"Check State\";\n\n      if (tokenLength <= 10) {\n        $('startAuthorizationButton').removeAttribute('disabled');\n      }\n\n      await homebridge.updatePluginConfig(pluginConfig);\n      await homebridge.savePluginConfig(pluginConfig);\n    });\n\n    function updateInfo(id, text, color) {\n      const el = document.getElementById(id);\n      if (el) {\n        el.innerText = text;\n        el.style.color = color;\n      }\n    }\n\n    // Clear token button logic\n    $(\"clearTokenButton\").addEventListener(\"click\", async () => {\n      updateInfo('info', 'Start clearing token...', 'yellow');\n      homebridge.showSpinner();\n\n      try {\n        const host = this.device.host;\n        await homebridge.request('/clearToken', { host });\n\n        Object.assign(this.device, {\n          webApi: {\n            token: '',\n            enable: false\n          }\n        });\n\n        updateDeviceForm(this.device);\n        updateInfo('info', \"Web API token cleared, now you can start a new authorization process.\", \"green\");\n        $(\"startAuthorizationButton\").removeAttribute(\"disabled\");\n\n        await homebridge.updatePluginConfig(pluginConfig);\n        await homebridge.savePluginConfig(pluginConfig);\n      } catch (error) {\n        updateInfo('info', \"Clear Web API token error.\", \"yellow\");\n        updateInfo('info1', `Error: ${error}`, \"red\");\n      } finally {\n        homebridge.hideSpinner();\n      }\n    });\n\n    // Start authorization logic\n    $(\"startAuthorizationButton\").addEventListener(\"click\", async () => {\n      $(\"startAuthorizationButton\").setAttribute(\"disabled\", \"true\"); // blokada wielokliku\n      updateInfo('info', \"Starting authorization...\", \"yellow\");\n      homebridge.showSpinner();\n\n      try {\n        const { host, webApi } = this.device;\n        const { token, clientId, clientSecret } = webApi;\n\n        const response = await homebridge.request('/startAuthorization', {\n          host, token, clientId, clientSecret\n        });\n\n        const { info, status } = response;\n\n        switch (status) {\n          case 0: // Authorized\n            updateInfo('info', info, \"green\");\n            $(\"startAuthorizationButton\").innerText = \"Check State\";\n            $(\"deviceWebApiControl\").disabled = false;\n            break;\n          case 1: // Needs user interaction\n            $(\"startAuthorizationButton\").innerText = \"Activate Console\";\n            $(\"deviceWebApiControl\").checked = false;\n            $(\"deviceWebApiControl\").disabled = true;\n            webApi.enable = false;\n\n            let timeLeft = 10;\n            const timerId = setInterval(() => {\n              if (timeLeft <= 0) {\n                open(info);\n                clearInterval(timerId);\n                updateInfo('info', \"Now paste the *code* into *Web API Token* and press *Activate Console*.\", \"yellow\");\n              } else {\n                updateInfo('info', `After ${timeLeft} sec, sign in to Xbox Live and authorize. Then copy the code after ?code= and return here.`, \"yellow\");\n                timeLeft--;\n              }\n            }, 1000);\n            break;\n          case 2: // Successfully authorized\n            updateInfo('info', info, \"green\");\n            $(\"startAuthorizationButton\").innerText = \"Check State\";\n            $(\"deviceWebApiControl\").disabled = false;\n            $(\"deviceWebApiControl\").checked = true;\n            webApi.enable = true;\n\n            await homebridge.updatePluginConfig(pluginConfig);\n            await homebridge.savePluginConfig(pluginConfig);\n            break;\n        }\n      } catch (error) {\n        updateInfo('info', \"Authorization error.\", \"yellow\");\n        updateInfo('info1', `Error: ${JSON.stringify(error)}`, \"red\");\n      } finally {\n        $(\"startAuthorizationButton\").removeAttribute(\"disabled\");\n        homebridge.hideSpinner();\n      }\n    });\n\n  })();\n</script>"
  },
  {
    "path": "homebridge-ui/server.js",
    "content": "import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils';\nimport Authentication from '../src/webApi/authentication.js';\nimport Functions from '../src/functions.js';\n\nclass PluginUiServer extends HomebridgePluginUiServer {\n  constructor() {\n    super();\n    this.functions = new Functions();\n\n    //clear web api token\n    this.onRequest('/clearToken', this.clearToken.bind(this));\n\n    //start console authorization\n    this.onRequest('/startAuthorization', this.startAuthorization.bind(this));\n\n    //this MUST be called when you are ready to accept requests\n    this.ready();\n  };\n\n  async clearToken(payload) {\n    const hostKey = payload.host.replace(/\\./g, '');\n    const tokensFile = `${this.homebridgeStoragePath}/xboxTv/authToken_${hostKey}`;\n\n    try {\n      const emptyTokens = { oauth: {}, user: {}, xsts: {} };\n      await this.functions.saveData(tokensFile, emptyTokens);\n      return true;\n    } catch (error) {\n      throw new Error(`Clear token error: ${error?.message ?? error}`);\n    }\n  }\n\n  async startAuthorization(payload) {\n    const hostKey = payload.host.replace(/\\./g, '');\n    const tokensFile = `${this.homebridgeStoragePath}/xboxTv/authToken_${hostKey}`;\n\n    const authConfig = {\n      clientId: payload.clientId,\n      clientSecret: payload.clientSecret,\n      tokensFile\n    };\n\n    const authentication = new Authentication(authConfig);\n    const webApiToken = payload.token;\n\n    try {\n      // Case: Console already authorized\n      await authentication.checkAuthorization();\n      return {\n        info: 'Console authorized and activated. To start a new process, please clear the Web API Token first.',\n        status: 0  // Authorized\n      };\n    } catch {\n      if (webApiToken) {\n        try {\n          await authentication.accessToken(webApiToken);\n          return {\n            info: 'Activation successful! Now restart the plugin and have fun!',\n            status: 2  // Token activated\n          };\n        } catch (error) {\n          throw new Error(`Activation console error: ${error?.message ?? error}`);\n        }\n      }\n\n      // No token, generate auth URL\n      try {\n        const oauth2URI = await authentication.generateAuthorizationUrl();\n        return {\n          info: oauth2URI,\n          status: 1  // Needs user authorization\n        };\n      } catch (error) {\n        throw new Error(error);\n      }\n    }\n  }\n}\n\n(() => {\n  return new PluginUiServer();\n})();\n"
  },
  {
    "path": "index.js",
    "content": "import { join } from 'path';\nimport { mkdirSync, existsSync, writeFileSync } from 'fs';\nimport XboxDevice from './src/xboxdevice.js';\nimport ImpulseGenerator from './src/impulsegenerator.js';\nimport { PluginName, PlatformName } from './src/constants.js';\n\nclass XboxPlatform {\n\tconstructor(log, config, api) {\n\t\t// only load if configured\n\t\tif (!config || !Array.isArray(config.devices)) {\n\t\t\tlog.warn(`No configuration found for ${PluginName}`);\n\t\t\treturn;\n\t\t}\n\t\tthis.accessories = [];\n\n\t\tconst prefDir = join(api.user.storagePath(), 'xboxTv');\n\t\ttry {\n\t\t\tmkdirSync(prefDir, { recursive: true });\n\t\t} catch (error) {\n\t\t\tlog.error(`Prepare directory error: ${error.message ?? error}`);\n\t\t\treturn;\n\t\t}\n\n\t\tapi.on('didFinishLaunching', () => {\n\t\t\t// Each device is set up independently — a failure in one does not\n\t\t\t// block the others. Promise.allSettled runs all in parallel.\n\t\t\tPromise.allSettled(\n\t\t\t\tconfig.devices.map(device =>\n\t\t\t\t\tthis.setupDevice(device, prefDir, log, api)\n\t\t\t\t)\n\t\t\t).then(results => {\n\t\t\t\tresults.forEach((result, i) => {\n\t\t\t\t\tif (result.status === 'rejected') {\n\t\t\t\t\t\tlog.error(`Device[${i}] setup error: ${result.reason?.message ?? result.reason}`);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n\n\t// ── Per-device setup ──────────────────────────────────────────────────────\n\n\tasync setupDevice(device, prefDir, log, api) {\n\t\tconst { name, host, xboxLiveId, displayType } = device;\n\n\t\tif (!name || !host || !xboxLiveId || !displayType) {\n\t\t\tlog.warn(`Device: ${host || 'host missing'}, ${name || 'name missing'}, ${xboxLiveId || 'xbox live id missing'}${!displayType ? ', display type disabled' : ''} in config, will not be published in the Home app`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst logLevel = {\n\t\t\tdevInfo: device.log?.deviceInfo,\n\t\t\tsuccess: device.log?.success,\n\t\t\tinfo: device.log?.info,\n\t\t\twarn: device.log?.warn,\n\t\t\terror: device.log?.error,\n\t\t\tdebug: device.log?.debug,\n\t\t};\n\n\t\tif (logLevel.debug) {\n\t\t\tlog.info(`Device: ${host} ${name}, did finish launching.`);\n\t\t\tconst safeConfig = {\n\t\t\t\t...device,\n\t\t\t\txboxLiveId: 'removed',\n\t\t\t\twebApi: {\n\t\t\t\t\ttoken: 'removed',\n\t\t\t\t\tclientSecret: 'removed',\n\t\t\t\t\tclientId: 'removed',\n\t\t\t\t},\n\t\t\t\tmqtt: {\n\t\t\t\t\tauth: {\n\t\t\t\t\t\t...device.mqtt?.auth,\n\t\t\t\t\t\tpasswd: 'removed',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t\tlog.info(`Device: ${host} ${name}, Config: ${JSON.stringify(safeConfig, null, 2)}.`);\n\t\t}\n\n\t\t// Resolve all file paths up front — before the impulse generator starts,\n\t\t// so a file-creation failure aborts early rather than inside the retry loop.\n\t\tconst postFix = host.split('.').join('');\n\t\tconst authTokenFile = `${prefDir}/authToken_${postFix}`;\n\t\tconst devInfoFile = `${prefDir}/devInfo_${postFix}`;\n\t\tconst inputsFile = `${prefDir}/inputs_${postFix}`;\n\t\tconst inputsNamesFile = `${prefDir}/inputsNames_${postFix}`;\n\t\tconst inputsTargetVisibilityFile = `${prefDir}/inputsTargetVisibility_${postFix}`;\n\n\t\ttry {\n\t\t\tconst files = [\n\t\t\t\tauthTokenFile,\n\t\t\t\tdevInfoFile,\n\t\t\t\tinputsFile,\n\t\t\t\tinputsNamesFile,\n\t\t\t\tinputsTargetVisibilityFile,\n\t\t\t];\n\n\t\t\tfiles.forEach(file => {\n\t\t\t\tif (!existsSync(file)) {\n\t\t\t\t\twriteFileSync(file, '');\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (logLevel.error) log.error(`Device: ${host} ${name}, Prepare files error: ${error.message ?? error}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// The startup impulse generator retries the full connect cycle\n\t\t// every 120 s until it succeeds, then hands off to the xboxDevice\n\t\t// impulse generator and stops itself.\n\t\tconst impulseGenerator = new ImpulseGenerator()\n\t\t\t.on('start', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.startDevice({\n\t\t\t\t\t\tdevice, name, host,\n\t\t\t\t\t\tauthTokenFile, devInfoFile, inputsFile, inputsNamesFile, inputsTargetVisibilityFile,\n\t\t\t\t\t\tlogLevel, log, api, impulseGenerator,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (logLevel.error) log.error(`Device: ${host} ${name}, Start impulse generator error: ${error.message ?? error}, trying again.`);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.on('state', (state) => {\n\t\t\t\tif (logLevel.debug) log.info(`Device: ${host} ${name}, Start impulse generator ${state ? 'started' : 'stopped'}.`);\n\t\t\t});\n\n\t\tawait impulseGenerator.state(true, [{ name: 'start', sampling: 120_000 }]);\n\t}\n\n\t// ── Connect and register a single Xbox device as a Homebridge accessory ───\n\n\tasync startDevice({ device, name, host, authTokenFile, devInfoFile, inputsFile, inputsNamesFile, inputsTargetVisibilityFile, logLevel, log, api, impulseGenerator }) {\n\t\tconst xboxDevice = new XboxDevice(api, device, authTokenFile, devInfoFile, inputsFile, inputsNamesFile, inputsTargetVisibilityFile)\n\t\t\t.on('devInfo', (info) => logLevel.devInfo && log.info(info))\n\t\t\t.on('success', (msg) => logLevel.success && log.success(`Device: ${host} ${name}, ${msg}`))\n\t\t\t.on('info', (msg) => logLevel.info && log.info(`Device: ${host} ${name}, ${msg}`))\n\t\t\t.on('debug', (msg) => logLevel.debug && log.info(`Device: ${host} ${name}, debug: ${msg}`))\n\t\t\t.on('warn', (msg) => logLevel.warn && log.warn(`Device: ${host} ${name}, ${msg}`))\n\t\t\t.on('error', (msg) => logLevel.error && log.error(`Device: ${host} ${name}, ${msg}`));\n\n\t\tconst accessory = await xboxDevice.start();\n\t\tif (!accessory) return;\n\n\t\tapi.publishExternalAccessories(PluginName, [accessory]);\n\t\tif (logLevel.success) log.success(`Device: ${host} ${name}, Published as external accessory.`);\n\n\t\t// Hand off to the xboxDevice impulse generator and stop the startup one.\n\t\tawait xboxDevice.startStopImpulseGenerator(true, [{ name: 'connect', sampling: 6000 }]);\n\t\tawait impulseGenerator.state(false);\n\t}\n\n\t// ── Homebridge accessory cache ────────────────────────────────────────────\n\n\tconfigureAccessory(accessory) {\n\t\tthis.accessories.push(accessory);\n\t}\n}\n\nexport default (api) => {\n\tapi.registerPlatform(PluginName, PlatformName, XboxPlatform);\n};"
  },
  {
    "path": "package.json",
    "content": "{\n  \"displayName\": \"Xbox TV\",\n  \"name\": \"homebridge-xbox-tv\",\n  \"version\": \"4.1.17\",\n  \"description\": \"Homebridge plugin to control Xbox game consoles.\",\n  \"license\": \"MIT\",\n  \"author\": \"grzegorz914\",\n  \"maintainers\": [\n    \"grzegorz914\"\n  ],\n  \"homepage\": \"https://github.com/grzegorz914/homebridge-xbox-tv#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/grzegorz914/homebridge-xbox-tv.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/grzegorz914/homebridge-xbox-tv/issues\"\n  },\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./index.js\"\n  },\n  \"files\": [\n    \"src\",\n    \"homebridge-ui\",\n    \"index.js\",\n    \"config.schema.json\",\n    \"package.json\",\n    \"CHANGELOG.md\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"engines\": {\n    \"homebridge\": \"^1.8.0 || ^2.0.0 || ^2.0.0-beta.79 || ^2.0.0-alpha.81\",\n    \"node\": \"^20 || ^22 || ^24 || ^25\"\n  },\n  \"dependencies\": {\n    \"@homebridge/plugin-ui-utils\": \"^2.2.3\",\n    \"mqtt\": \"^5.15.1\",\n    \"elliptic\": \"^6.6.1\",\n\n    \"hex-to-binary\": \"^1.0.1\",\n    \"jsrsasign\": \"^11.1.3\",\n    \"uuid\": \"^14.0.0\",\n    \"express\": \"^5.2.1\",\n    \"axios\": \"^1.15.2\"\n  },\n  \"keywords\": [\n    \"homebridge\",\n    \"homebridge-plugin\",\n    \"homekit\",\n    \"xbox\",\n    \"microsoft\",\n    \"smartglass\",\n    \"mqtt\",\n    \"restful\"\n  ],\n  \"funding\": {\n    \"type\": \"Buy Coffee To\",\n    \"url\": \"https://buycoffee.to/grzegorz914\"\n  },\n  \"contributors\": [],\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  }\n}"
  },
  {
    "path": "sample-config.json",
    "content": "{\n\t\"bridge\": {\n\t\t\"name\": \"Homebridge\",\n\t\t\"username\": \"AA:BB:CC:DD:EE:FF\",\n\t\t\"manufacturer\": \"homebridge.io\",\n\t\t\"model\": \"homebridge\",\n\t\t\"port\": 9100,\n\t\t\"pin\": \"123-45-678\"\n\t},\n\t\"description\": \"HomeKit Bridge\",\n\t\"ports\": {\n\t\t\"start\": 9101,\n\t\t\"end\": 9150,\n\t\t\"comment\": \"In this section set the port for Homebridge accessories.\"\n\t},\n\t\"accessories\": [],\n\t\"platforms\": [\n\t\t{\n\t\t\t\"platform\": \"XboxTv\",\n\t\t\t\"devices\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Xbox One\",\n\t\t\t\t\t\"host\": \"192.168.1.6\",\n\t\t\t\t\t\"xboxLiveId\": \"FD0000000000\",\n\t\t\t\t\t\"displayType\": 1,\n\t\t\t\t\t\"webApi\": {\n\t\t\t\t\t\t\"enable\": false,\n\t\t\t\t\t\t\"token\": \"\",\n\t\t\t\t\t\t\"clientId\": \"\",\n\t\t\t\t\t\t\"clientSecret\": \"\"\n\t\t\t\t\t},\n\t\t\t\t\t\"inputs\": {\n\t\t\t\t\t\t\"getFromDevice\": false,\n\t\t\t\t\t\t\"filterGames\": false,\n\t\t\t\t\t\t\"filterApps\": false,\n\t\t\t\t\t\t\"filterSystemApps\": false,\n\t\t\t\t\t\t\"filterDlc\": false,\n\t\t\t\t\t\t\"displayOrder\": 0,\n\t\t\t\t\t\t\"data\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"name\": \"A Way Out\",\n\t\t\t\t\t\t\t\t\"reference\": \"AWayOut_zwks512sysnyr!AppAWayOut\",\n\t\t\t\t\t\t\t\t\"oneStoreProductId\": \"\",\n\t\t\t\t\t\t\t\t\"contentType\": \"Game\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t\"buttons\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"displayType\": 0,\n\t\t\t\t\t\t\t\"name\": \"Media Play\",\n\t\t\t\t\t\t\t\"mode\": 0,\n\t\t\t\t\t\t\t\"mediaCommand\": \"play\",\n\t\t\t\t\t\t\t\"namePrefix\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"sensors\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"displayType\": 0,\n\t\t\t\t\t\t\t\"mode\": 0,\n\t\t\t\t\t\t\t\"name\": \"\",\n\t\t\t\t\t\t\t\"reference\": \"\",\n\t\t\t\t\t\t\t\"pulse\": false,\n\t\t\t\t\t\t\t\"namePrefix\": false,\n\t\t\t\t\t\t\t\"level\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"volume\": {\n\t\t\t\t\t\t\"displayType\": 0,\n\t\t\t\t\t\t\"name\": \"Volume\",\n\t\t\t\t\t\t\"namePrefix\": false\n\t\t\t\t\t},\n\t\t\t\t\t\"log\": {\n\t\t\t\t\t\t\"deviceInfo\": true,\n\t\t\t\t\t\t\"success\": true,\n\t\t\t\t\t\t\"info\": false,\n\t\t\t\t\t\t\"warn\": true,\n\t\t\t\t\t\t\"error\": true,\n\t\t\t\t\t\t\"debug\": false\n\t\t\t\t\t},\n\t\t\t\t\t\"infoButtonCommand\": \"nexus\",\n\t\t\t\t\t\"restFul\": {\n\t\t\t\t\t\t\"enable\": false,\n\t\t\t\t\t\t\"port\": 3000\n\t\t\t\t\t},\n\t\t\t\t\t\"mqtt\": {\n\t\t\t\t\t\t\"enable\": false,\n\t\t\t\t\t\t\"host\": \"\",\n\t\t\t\t\t\t\"port\": 1883,\n\t\t\t\t\t\t\"clientId\": \"\",\n\t\t\t\t\t\t\"prefix\": \"\",\n\t\t\t\t\t\t\"auth\": {\n\t\t\t\t\t\t\t\"enable\": false,\n\t\t\t\t\t\t\t\"user\": \"\",\n\t\t\t\t\t\t\t\"passwd\": \"\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"
  },
  {
    "path": "src/constants.js",
    "content": "export const PlatformName = \"XboxTv\";\nexport const PluginName = \"homebridge-xbox-tv\";\n\nexport const DefaultInputs = [\n    {\n        \"oneStoreProductId\": \"Screensaver\",\n        \"titleId\": \"851275400\",\n        \"reference\": \"Xbox.IdleScreen_8wekyb3d8bbwe!Xbox.IdleScreen.Application\",\n        \"isGame\": false,\n        \"name\": \"Screensaver\",\n        \"contentType\": \"Dashboard\"\n    },\n    {\n        \"oneStoreProductId\": \"Dashboard\",\n        \"titleId\": \"750323071\",\n        \"reference\": \"Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application\",\n        \"isGame\": false,\n        \"name\": \"Dashboard\",\n        \"contentType\": \"Dashboard\"\n    },\n    {\n        \"oneStoreProductId\": \"Settings\",\n        \"titleId\": \"1837352387\",\n        \"reference\": \"Microsoft.Xbox.Settings_8wekyb3d8bbwe!Xbox.Settings.Application\",\n        \"isGame\": false,\n        \"name\": \"Settings\",\n        \"contentType\": \"Dashboard\"\n    },\n    {\n        \"oneStoreProductId\": \"Television\",\n        \"titleId\": \"371594669\",\n        \"reference\": \"Microsoft.Xbox.LiveTV_8wekyb3d8bbwe!Microsoft.Xbox.LiveTV.Application\",\n        \"isGame\": false,\n        \"name\": \"Television\",\n        \"contentType\": \"systemApp\"\n    },\n    {\n        \"oneStoreProductId\": \"SettingsTv\",\n        \"titleId\": \"2019308066\",\n        \"reference\": \"Microsoft.Xbox.TvSettings_8wekyb3d8bbwe!Microsoft.Xbox.TvSettings.Application\",\n        \"isGame\": false,\n        \"name\": \"Settings TV\",\n        \"contentType\": \"Dashboard\"\n    },\n    {\n        \"oneStoreProductId\": \"Accessory\",\n        \"titleId\": \"758407307\",\n        \"reference\": \"Microsoft.XboxDevices_8wekyb3d8bbwe!App\",\n        \"isGame\": false,\n        \"name\": \"Accessory\",\n        \"contentType\": \"systemApp\"\n    },\n    {\n        \"oneStoreProductId\": \"NetworkTroubleshooter\",\n        \"titleId\": \"1614319806\",\n        \"reference\": \"Xbox.NetworkTroubleshooter_8wekyb3d8bbwe!Xbox.NetworkTroubleshooter.Application\",\n        \"isGame\": false,\n        \"name\": \"Network Troubleshooter\",\n        \"contentType\": \"systemApp\"\n    },\n    {\n        \"oneStoreProductId\": \"MicrosoftStore\",\n        \"titleId\": \"1864271209\",\n        \"reference\": \"Microsoft.storify_8wekyb3d8bbwe!App\",\n        \"isGame\": false,\n        \"name\": \"Microsoft Store\",\n        \"contentType\": \"Dashboard\"\n    },\n    {\n        \"oneStoreProductId\": \"XboxGuide\",\n        \"titleId\": \"1052052983\",\n        \"reference\": \"Xbox.Guide_8wekyb3d8bbwe!Xbox.Guide.Application\",\n        \"isGame\": false,\n        \"name\": \"Xbox Guide\",\n        \"contentType\": \"systemApp\"\n    }\n];\n\nexport const InputSourceTypes = [\n    \"OTHER\",\n    \"HOME_SCREEN\",\n    \"TUNER\",\n    \"HDMI\",\n    \"COMPOSITE_VIDEO\",\n    \"S_VIDEO\",\n    \"COMPONENT_VIDEO\",\n    \"DVI\",\n    \"AIRPLAY\",\n    \"USB\",\n    \"APPLICATION\"\n];\n\nexport const WebApi = {\n    \"Url\": {\n        \"oauth2\": \"https://login.live.com/oauth20_authorize.srf\",\n        \"AccessToken\": \"https://login.live.com/oauth20_token.srf\",\n        \"RefreshToken\": \"https://login.live.com/oauth20_token.srf\",\n        \"UserToken\": \"https://user.auth.xboxlive.com/user/authenticate\",\n        \"XstsToken\": \"https://xsts.auth.xboxlive.com/xsts/authorize\",\n        \"Redirect\": \"http://localhost:8888/auth/callback\",\n        \"Xccs\": \"https://xccs.xboxlive.com\"\n    },\n    \"Scopes\": \"XboxLive.signin XboxLive.offline_access\",\n    \"ClientId\": \"a34ac209-edab-4b08-91e7-a4558d8da1bd\",\n    \"Console\": {\n        \"Name\": {\n            \"XboxSeriesX\": \"Xbox Series X\",\n            \"XboxSeriesS\": \"Xbox Series S\",\n            \"XboxOne\": \"Xbox One\",\n            \"XboxOneS\": \"Xbox One S\",\n            \"XboxOneX\": \"Xbox One X\"\n        },\n        \"PowerState\": {\n            \"Off\": 0,\n            \"On\": 1,\n            \"ConnectedStandby\": 2,\n            \"SystemUpdate\": 3,\n            \"Unknown\": 4\n        },\n        \"PlaybackState\": {//0 - STOP, 1 - PLAY, 2 - PAUSE\n            \"Stopped\": 0,\n            \"Playing\": 1,\n            \"Paused\": 2,\n            \"Unknown\": 3\n        },\n        \"PlaybackStateHomeKit\": { //0 - PLAY, 1 - PAUSE, 2 - STOP, 3 - LOADING, 4 - INTERRUPTED\n            \"Stopped\": 2,\n            \"Playing\": 0,\n            \"Paused\": 1,\n            \"Unknown\": 4\n        }\n    }\n};\n\nexport const LocalApi = {\n    \"ParticipantId\": {\n        \"Target\": 0\n    },\n    \"ClientId\": \"e8ff5828-5cce-4f90-89a4-117d127e3838\",\n    \"Console\": {\n        \"Name\": {\n            \"1\": \"Xbox One\",\n            \"2\": \"Xbox 360\",\n            \"3\": \"Windows Desktop\",\n            \"4\": \"Windows Store\",\n            \"5\": \"Windows Phone\",\n            \"6\": \"iPhone\",\n            \"7\": \"iPad\",\n            \"8\": \"Android\"\n        },\n        \"PairingState\": {\n            \"0\": \"Not Paired\",\n            \"1\": \"Paired\"\n        }\n    },\n    \"Channels\": {\n        \"System\": {\n            \"Input\": {\n                \"Id\": 0,\n                \"Uuid\": \"fa20b8ca66fb46e0adb60b978a59d35f\",\n                \"Commands\": {\n                    \"unpress\": 0,\n                    \"enroll\": 1,\n                    \"nexus\": 2,\n                    \"view\": 4,\n                    \"menu\": 8,\n                    \"a\": 16,\n                    \"b\": 32,\n                    \"x\": 64,\n                    \"y\": 128,\n                    \"up\": 256,\n                    \"down\": 512,\n                    \"left\": 1024,\n                    \"right\": 2048,\n                    \"leftShoulder\": 4096,\n                    \"rightShoulder\": 8192,\n                    \"leftThumbstick\": 16384,\n                    \"rightThumbstick\": 32768\n                }\n            },\n            \"TvRemote\": {\n                \"Id\": 1,\n                \"Uuid\": \"d451e3b360bb4c71b3dbf994b1aca3a7\",\n                \"Commands\": {\n                    \"volUp\": \"btn.vol_up\",\n                    \"volDown\": \"btn.vol_down\",\n                    \"volMute\": \"btn.vol_mute\"\n                },\n                \"MessageType\": {\n                    \"Error\": \"Error\",\n                    \"GetConfiguration\": \"GetConfiguration\",\n                    \"GetHeadendInfo\": \"GetHeadendInfo\",\n                    \"GetLiveTVInfo\": \"GetLiveTVInfo\",\n                    \"GetProgramInfo\": \"GetProgramInfo\",\n                    \"GetRecentChannels\": \"GetRecentChannels\",\n                    \"GetTunerLineups\": \"GetTunerLineups\",\n                    \"GetAppChannelData\": \"GetAppChannelData\",\n                    \"GetAppChannelLineups\": \"GetAppChannelLineups\",\n                    \"GetAppChannelProgramData\": \"GetAppChannelProgramData\",\n                    \"SendKey\": \"SendKey\",\n                    \"SetChannel\": \"SetChannel\"\n                }\n            },\n            \"Media\": {\n                \"Id\": 2,\n                \"Uuid\": \"48a9ca24eb6d4e128c43d57469edd3cd\",\n                \"Commands\": {\n                    \"play\": 2,\n                    \"pause\": 4,\n                    \"playPause\": 8,\n                    \"stop\": 16,\n                    \"record\": 32,\n                    \"nextTrack\": 64,\n                    \"previousTrack\": 128,\n                    \"fastForward\": 256,\n                    \"rewind\": 512,\n                    \"channelUp\": 1024,\n                    \"channelDown\": 2048,\n                    \"back\": 4096,\n                    \"view\": 8192,\n                    \"menu\": 16384,\n                    \"seek\": 32786\n                }\n            },\n            \"Text\": {\n                \"Id\": 3,\n                \"Uuid\": \"7af3e6a2488b40cba93179c04b7da3a0\"\n            },\n            \"Broadcast\": {\n                \"Id\": 4,\n                \"Uuid\": \"b6a117d8f5e245d7862e8fd8e3156476\"\n            },\n            \"Title\": {\n                \"Id\": 5,\n                \"Uuid\": \"00000000000000000000000000000000\"\n            }\n        }\n    },\n    \"Media\": {\n        \"Types\": {\n            \"0\": \"No Media\",\n            \"1\": \"Music\",\n            \"2\": \"Video\",\n            \"3\": \"Image\",\n            \"4\": \"Conversation\",\n            \"5\": \"Game\"\n        },\n        \"PlaybackState\": {\n            \"0\": \"Closed\",\n            \"1\": \"Changing\",\n            \"2\": \"Stopped\",\n            \"3\": \"Playing\",\n            \"4\": \"Paused\"\n        },\n        \"SoundLevel\": {\n            \"0\": \"Muted\",\n            \"1\": \"Low\",\n            \"2\": \"Full\"\n        }\n    },\n    \"Messages\": {\n        \"Category\": {\n            \"d00d\": \"message\",\n            \"dd00\": \"simple\",\n            \"dd01\": \"simple\",\n            \"dd02\": \"simple\",\n            \"cc00\": \"simple\",\n            \"cc01\": \"simple\"\n        },\n        \"CategoryTypes\": {\n            \"d00d\": \"message\",\n            \"dd00\": \"discoveryRequest\",\n            \"dd01\": \"discoveryResponse\",\n            \"dd02\": \"powerOn\",\n            \"cc00\": \"connectRequest\",\n            \"cc01\": \"connectResponse\",\n        },\n        \"Types\": {\n            0x1: \"acknowledge\",\n            0x2: \"group\",\n            0x3: \"localJoin\",\n            0x5: \"stopActivity\",\n            0x19: \"auxilaryStream\",\n            0x1A: \"activeSurfaceChange\",\n            0x1B: \"navigate\",\n            0x1C: \"json\",\n            0x1D: \"tunnel\",\n            0x1E: \"consoleStatus\",\n            0x1F: \"titleTextConfiguration\",\n            0x20: \"titleTextInput\",\n            0x21: \"titleTextSelection\",\n            0x22: \"mirroringRequest\",\n            0x23: \"titleLaunch\",\n            0x26: \"channelStartRequest\",\n            0x27: \"channelStartResponse\",\n            0x28: \"channelStop\",\n            0x29: \"system\",\n            0x2A: \"disconnect\",\n            0x2E: \"titleTouch\",\n            0x2F: \"accelerometer\",\n            0x30: \"gyrometer\",\n            0x31: \"inclinometer\",\n            0x32: \"compass\",\n            0x33: \"orientation\",\n            0x36: \"pairedIdentityStateChanged\",\n            0x37: \"unsnap\",\n            0x38: \"recordGameDvr\",\n            0x39: \"powerOff\",\n            0xF00: \"mediaControllerRemoved\",\n            0xF01: \"mediaCommand\",\n            0xF02: \"mediaCommandResult\",\n            0xF03: \"mediaState\",\n            0xF0A: \"gamepad\",\n            0xF2B: \"systemTextConfiguration\",\n            0xF2C: \"systemTextInput\",\n            0xF2E: \"systemTouch\",\n            0xF34: \"systemTextAck\",\n            0xF35: \"systemTextDone\"\n        },\n        \"Flags\": {\n            acknowledge: Buffer.from('8001', 'hex'),\n            0x2: \"group\",\n            localJoin: Buffer.from('2003', 'hex'),\n            0x5: \"stopActivity\",\n            0x19: \"auxilaryStream\",\n            0x1A: \"activeSurfaceChange\",\n            0x1B: \"navigate\",\n            json: Buffer.from('a01c', 'hex'),\n            0x1D: \"tunnel\",\n            consoleStatus: Buffer.from('a01e', 'hex'),\n            0x1F: \"titleTextConfiguration\",\n            0x20: \"titleTextInput\",\n            0x21: \"titleTextSelection\",\n            0x22: \"mirroringRequest\",\n            0x23: \"titleLaunch\",\n            channelStartRequest: Buffer.from('a026', 'hex'),\n            channelStartResponse: Buffer.from('a027', 'hex'),\n            0x28: \"channelStop\",\n            0x29: \"system\",\n            disconnect: Buffer.from('802a', 'hex'),\n            0x2E: \"titleTouch\",\n            0x2F: \"accelerometer\",\n            0x30: \"gyrometer\",\n            0x31: \"inclinometer\",\n            0x32: \"compass\",\n            0x33: \"orientation\",\n            0x36: \"pairedIdentityStateChanged\",\n            0x37: \"unsnap\",\n            recordGameDvr: Buffer.from('a038', 'hex'),\n            powerOff: Buffer.from('a039', 'hex'),\n            0xF00: \"mediaControllerRemoved\",\n            mediaCommand: Buffer.from('af01', 'hex'),\n            mediaCommandResult: Buffer.from('af02', 'hex'),\n            mediaState: Buffer.from('af03', 'hex'),\n            gamepad: Buffer.from('8f0a', 'hex'),\n            0xF2B: \"systemTextConfiguration\",\n            0xF2C: \"systemTextInput\",\n            0xF2E: \"systemTouch\",\n            0xF34: \"systemTextAck\",\n            0xF35: \"systemTextDone\",\n            powerOn: Buffer.from('dd02', 'hex'),\n            discoveryRequest: Buffer.from('dd00', 'hex'),\n            discoveryResponse: Buffer.from('dd01', 'hex'),\n            connectRequest: Buffer.from('cc00', 'hex'),\n            connectResponse: Buffer.from('cc01', 'hex'),\n        }\n    }\n};\n\nexport const DiacriticsMap = {\n    // Polish\n    'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n',\n    'ó': 'o', 'ś': 's', 'ź': 'z', 'ż': 'z',\n    'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N',\n    'Ó': 'O', 'Ś': 'S', 'Ź': 'Z', 'Ż': 'Z',\n\n    // German\n    'ä': 'a', 'ö': 'o', 'ü': 'u', 'ß': 'ss',\n    'Ä': 'A', 'Ö': 'O', 'Ü': 'U',\n\n    // French\n    'à': 'a', 'â': 'a', 'ç': 'c', 'é': 'e', 'è': 'e',\n    'ê': 'e', 'ë': 'e', 'î': 'i', 'ï': 'i', 'ô': 'o',\n    'û': 'u', 'ù': 'u', 'ü': 'u', 'ÿ': 'y',\n    'À': 'A', 'Â': 'A', 'Ç': 'C', 'É': 'E', 'È': 'E',\n    'Ê': 'E', 'Ë': 'E', 'Î': 'I', 'Ï': 'I', 'Ô': 'O',\n    'Û': 'U', 'Ù': 'U', 'Ü': 'U', 'Ÿ': 'Y',\n\n    // Spanish & Portuguese\n    'á': 'a', 'í': 'i', 'ó': 'o', 'ú': 'u', 'ñ': 'n',\n    'Á': 'A', 'Í': 'I', 'Ó': 'O', 'Ú': 'U', 'Ñ': 'N',\n\n    // Scandinavian\n    'å': 'a', 'Å': 'A', 'ø': 'o', 'Ø': 'O', 'æ': 'ae', 'Æ': 'AE',\n\n    // Other common\n    'Š': 'S', 'š': 's', 'Ž': 'Z', 'ž': 'z'\n};"
  },
  {
    "path": "src/functions.js",
    "content": "import { promises as fsPromises } from 'fs';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { DiacriticsMap } from './constants.js';\nconst execAsync = promisify(exec);\n\nclass Functions {\n    constructor() {\n    }\n\n    async saveData(path, data, stringify = true) {\n        try {\n            data = stringify ? JSON.stringify(data, null, 2) : data;\n            await fsPromises.writeFile(path, data);\n            return true;\n        } catch (error) {\n            throw new Error(`Save data error: ${error}`);\n        }\n    }\n\n    async readData(path, parseJson = false) {\n        try {\n            const data = await fsPromises.readFile(path, 'utf8');\n\n            if (parseJson) {\n                if (!data.trim()) {\n                    // Empty file when expecting JSON\n                    return null;\n                }\n                try {\n                    return JSON.parse(data);\n                } catch (jsonError) {\n                    throw new Error(`JSON parse error in file \"${path}\": ${jsonError.message}`);\n                }\n            }\n\n            // For non-JSON, just return file content (can be empty string)\n            return data;\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                // File does not exist\n                return null;\n            }\n            // Preserve original error details\n            const wrappedError = new Error(`Read data error for \"${path}\": ${error.message}`);\n            wrappedError.original = error;\n            throw wrappedError;\n        }\n    }\n\n    async sanitizeString(str) {\n        if (!str) return '';\n\n        // Replace diacritics using map\n        str = str.replace(/[^\\u0000-\\u007E]/g, ch => DiacriticsMap[ch] || ch);\n\n        // Replace separators between words with space\n        str = str.replace(/(\\w)[.:;+\\-\\/]+(\\w)/g, '$1 $2');\n\n        // Replace remaining standalone separators with space\n        str = str.replace(/[.:;+\\-\\/]/g, ' ');\n\n        // Remove remaining invalid characters (keep letters, digits, space, apostrophe)\n        str = str.replace(/[^A-Za-z0-9 ']/g, ' ');\n\n        // Collapse multiple spaces\n        str = str.replace(/\\s+/g, ' ');\n\n        // Trim\n        return str.trim();\n    }\n\n    async scaleValue(value, inMin, inMax, outMin, outMax) {\n        const scaledValue = parseFloat((((Math.max(inMin, Math.min(inMax, value)) - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin).toFixed(0));\n        return scaledValue;\n    }\n\n    async ping(ipOrHost) {\n        const isWindows = process.platform === 'win32';\n        const cmd = isWindows ? `ping -n 1 ${ipOrHost}` : `ping -c 1 ${ipOrHost}`;\n\n        try {\n            const { stdout } = await execAsync(cmd);\n            const alive = /ttl=/i.test(stdout);\n            const timeMatch = stdout.match(/time[=<]([\\d.]+)\\s*ms/i);\n            const time = timeMatch ? parseFloat(timeMatch[1]) : null;\n\n            return { online: alive, time };\n        } catch {\n            return { online: false };\n        }\n    }\n}\nexport default Functions"
  },
  {
    "path": "src/impulsegenerator.js",
    "content": "import EventEmitter from 'events';\n\nclass ImpulseGenerator extends EventEmitter {\n    constructor() {\n        super();\n        this.timersState = false;\n        this.timers = [];\n    }\n\n    async state(state, timers = [], runOnStart = true) {\n        // Stop current timers before new start\n        if (this.timersState && state) {\n            await this.state(false);\n        }\n\n        if (state) {\n            if (!Array.isArray(timers)) throw new Error('Timers must be an array');\n\n            for (const { name, sampling } of timers) {\n                if (!name || !sampling) continue;\n\n                if (runOnStart) this.emit(name);\n\n                const interval = setInterval(() => {\n                    this.emit(name);\n                }, sampling);\n\n                this.timers.push(interval);\n            }\n        } else {\n            this.timers.forEach(clearInterval);\n            this.timers = [];\n        }\n\n        this.timersState = state;\n        this.emit('state', state);\n        return true;\n    }\n}\n\nexport default ImpulseGenerator;\n\n"
  },
  {
    "path": "src/localApi/message.js",
    "content": "import HexToBin from 'hex-to-binary';\nimport Packets from './packets.js';\nimport Structure from './structure.js';\nimport { LocalApi } from '../constants.js';\n\nclass Message {\n    constructor(type) {\n        this.type = type;\n        this.packets = new Packets();\n        this.packet = this.packets[type];\n        this.channelId = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00';\n    }\n\n    readFlags(flags) {\n        const binaryFlag = HexToBin(flags.toString('hex'));\n        const version = parseInt(binaryFlag.slice(0, 2), 2);\n        const needAcknowlegde = binaryFlag.slice(2, 3) === '1';\n        const isFragment = binaryFlag.slice(3, 4) === '1';\n        const type = LocalApi.Messages.Types[parseInt(binaryFlag.slice(4, 16), 2)];\n        return { version, needAcknowlegde, isFragment, type };\n    }\n\n    set(key, value, subkey = false) {\n        if (subkey === false) {\n            this.packet[key].value = value;\n        } else {\n            this.packet[subkey][key].value = value;\n        }\n    }\n\n    pack(crypto, sequenceNumber, targetParticipantId, sourceParticipantId, channelId = false) {\n        const structure = new Structure();\n        for (const name in this.packet) {\n            this.packet[name].pack(structure);\n        }\n\n        // Padding PKCS7\n        if (structure.toBuffer().length % 16 > 0) {\n            const padStart = structure.toBuffer().length % 16;\n            const padTotal = 16 - padStart;\n            for (let i = padStart + 1; i <= 16; i++) {\n                structure.writeUInt8(padTotal);\n            }\n        }\n\n        const header = new Structure();\n        header.writeBytes(Buffer.from('d00d', 'hex'));\n        header.writeUInt16(structure.toBuffer().length);\n        header.writeUInt32(sequenceNumber);\n        header.writeUInt32(targetParticipantId);\n        header.writeUInt32(sourceParticipantId);\n        header.writeBytes(LocalApi.Messages.Flags[this.type]);\n        header.writeBytes(Buffer.from(channelId || this.channelId));\n\n        // Original sgcrypto.encrypt(data, key, iv) and decrypt(data, iv, key) — kept as-is\n        const payloadEncrypted = crypto.encrypt(structure.toBuffer(), crypto.getKey(), crypto.encrypt(header.toBuffer().subarray(0, 16), crypto.getIv()));\n        let packet = Buffer.concat([header.toBuffer(), payloadEncrypted]);\n        const payloadProtected = crypto.sign(packet);\n        return Buffer.concat([packet, Buffer.from(payloadProtected)]);\n    }\n\n    unpack(crypto = undefined, data = false) {\n        const structure = new Structure(data);\n        const typeHex = structure.readBytes(2).toString('hex');\n\n        let packet = {\n            typeHex,\n            payloadLength: structure.readUInt16(),\n            sequenceNumber: structure.readUInt32(),\n            targetParticipantId: structure.readUInt32(),\n            sourceParticipantId: structure.readUInt32(),\n            flags: this.readFlags(structure.readBytes(2)),\n            channelId: structure.readBytes(8),\n            payloadProtected: structure.readBytes()\n        };\n\n        packet.type = packet.flags.type;\n        // FIX: extract signature BEFORE truncating payloadProtected.\n        // Original order was reversed: truncate first, then subarray(-32) of truncated buffer\n        // → read bytes [-64..-32] instead of the actual last 32 bytes [-32..].\n        packet.signature = packet.payloadProtected.subarray(-32);\n        packet.payloadProtected = Buffer.from(packet.payloadProtected.subarray(0, -32));\n        this.type = packet.type;\n        this.channelId = packet.channelId;\n\n        if (packet.payloadProtected.length > 0 && crypto) {\n            // Original: decrypt(data, iv) — matches sgcrypto.decrypt(data, iv, key)\n            const payloadDecrypted = crypto.decrypt(packet.payloadProtected, crypto.encrypt(data.subarray(0, 16), crypto.getIv()));\n            const structurePayloadProtected = new Structure(payloadDecrypted);\n            packet.payloadDecrypted = structurePayloadProtected.toBuffer();\n            packet.payloadProtected = {};\n\n            const packetDef = this.packets[packet.type];\n            if (packetDef) {\n                for (const name in packetDef) {\n                    packet.payloadProtected[name] = packetDef[name].unpack(structurePayloadProtected);\n                }\n            }\n        }\n\n        return packet;\n    }\n}\n\nexport default Message;"
  },
  {
    "path": "src/localApi/packets.js",
    "content": "import { LocalApi } from '../constants.js';\n\nclass Packets {\n    constructor(type) {\n        this.type = type;\n\n        const types = {\n            flags(length, value) {\n                const packet = {\n                    value,\n                    length,\n                    pack(packetStructure) {\n                        return packetStructure.writeBytes(setFlags(this.value));\n                    },\n                    unpack(packetStructure) {\n                        return readFlags(packetStructure.readBytes(this.length));\n                    }\n                };\n                return packet;\n            },\n\n            bytes(length = 0, value = Buffer.alloc(length)) {\n                const packet = {\n                    value,\n                    length,\n                    pack(packetStructure) {\n                        return packetStructure.writeBytes(this.value);\n                    },\n                    unpack(packetStructure) {\n                        const readLength = this.length > 0 ? this.length : packetStructure.packet.length - packetStructure.offset;\n                        return packetStructure.readBytes(readLength);\n                    }\n                };\n                return packet;\n            },\n\n            uInt16(value) {\n                const packet = {\n                    value,\n                    pack(packetStructure) {\n                        return packetStructure.writeUInt16(this.value);\n                    },\n                    unpack(packetStructure) {\n                        return packetStructure.readUInt16();\n                    }\n                };\n                return packet;\n            },\n\n            uInt32(value) {\n                const packet = {\n                    value,\n                    pack(packetStructure) {\n                        return packetStructure.writeUInt32(this.value);\n                    },\n                    unpack(packetStructure) {\n                        return packetStructure.readUInt32();\n                    }\n                };\n                return packet;\n            },\n\n            sInt32(value) {\n                const packet = {\n                    value,\n                    pack(packetStructure) {\n                        return packetStructure.writeInt32(this.value);\n                    },\n                    unpack(packetStructure) {\n                        return packetStructure.readInt32();\n                    }\n                };\n                return packet;\n            },\n\n            // FIX: uInt64 value must be a Buffer — '' produces a 0-byte buffer,\n            // corrupting the 8-byte field on the wire. Default is Buffer.alloc(length).\n            uInt64(length, value = Buffer.alloc(length)) {\n                const packet = {\n                    value: Buffer.isBuffer(value) ? value : Buffer.alloc(length),\n                    length,\n                    pack(packetStructure) {\n                        return packetStructure.writeBytes(this.value);\n                    },\n                    unpack(packetStructure) {\n                        return packetStructure.readBytes(length);\n                    }\n                };\n                return packet;\n            },\n\n            sgString(value) {\n                const packet = {\n                    value,\n                    pack(packetStructure) {\n                        return packetStructure.writeSGString(this.value);\n                    },\n                    unpack(packetStructure) {\n                        return packetStructure.readSGString().toString();\n                    }\n                };\n                return packet;\n            },\n\n            sgArray(type, value = []) {\n                const packet = {\n                    value,\n                    type,\n                    pack(packetStructure) {\n                        packetStructure.writeUInt16(this.value.length);\n                        const arrayStructure = packets[this.type];\n                        for (const item of this.value) {\n                            Object.keys(arrayStructure).forEach(name => {\n                                arrayStructure[name].value = item[name];\n                                packetStructure = arrayStructure[name].pack(packetStructure);\n                            });\n                        }\n                        return packetStructure;\n                    },\n                    unpack(packetStructure) {\n                        const arrayCount = packetStructure.readUInt16();\n                        const array = [];\n                        for (let i = 0; i < arrayCount; i++) {\n                            const arrayStructure = packets[this.type];\n                            const item = {};\n                            Object.keys(arrayStructure).forEach(name => {\n                                item[name] = arrayStructure[name].unpack(packetStructure);\n                            });\n                            array.push(item);\n                        }\n                        return array;\n                    }\n                };\n                return packet;\n            },\n\n            sgList(type, value = []) {\n                const packet = {\n                    value,\n                    type,\n                    pack(packetStructure) {\n                        packetStructure.writeUInt32(this.value.length);\n                        const arrayStructure = packets[this.type];\n                        for (const item of this.value) {\n                            Object.keys(arrayStructure).forEach(name => {\n                                arrayStructure[name].value = item[name];\n                                packetStructure = arrayStructure[name].pack(packetStructure);\n                            });\n                        }\n                        return packetStructure;\n                    },\n                    unpack(packetStructure) {\n                        const arrayCount = packetStructure.readUInt32();\n                        const array = [];\n                        for (let i = 0; i < arrayCount; i++) {\n                            const arrayStructure = packets[this.type];\n                            const item = {};\n                            Object.keys(arrayStructure).forEach(name => {\n                                item[name] = arrayStructure[name].unpack(packetStructure);\n                            });\n                            array.push(item);\n                        }\n                        return array;\n                    }\n                };\n                return packet;\n            },\n\n            mapper(map, item) {\n                const packet = {\n                    item,\n                    value: false,\n                    pack(packetStructure) {\n                        return item.pack(packetStructure);\n                    },\n                    unpack(packetStructure) {\n                        const key = item.unpack(packetStructure);\n                        return map[key] ?? key;\n                    }\n                };\n                return packet;\n            }\n        };\n\n        // FIX: uInt64 fields that were '' now default to Buffer.alloc(8) via fixed uInt64() factory.\n        // acknowledge keeps sgList (UInt32 count) — console accepts it and original worked with it.\n        const packets = {\n            powerOn: { liveId: types.sgString() },\n            json: { json: types.sgString('{}') },\n            discoveryRequest: { flags: types.uInt32(0), clientType: types.uInt16(3), minVersion: types.uInt16(0), maxVersion: types.uInt16(2) },\n            discoveryResponse: { flags: types.uInt32(0), clientType: types.uInt16(0), consoleName: types.sgString(), uuid: types.sgString(), lastError: types.uInt32(0), certificateLength: types.uInt16(0), certificate: types.bytes() },\n            connectRequest: { uuid: types.bytes(16, ''), publicKeyType: types.uInt16(0), publicKey: types.bytes(64, ''), iv: types.bytes(16, ''), payloadProtected: types.bytes() },\n            connectResponse: { iv: types.bytes(16, ''), payloadProtected: types.bytes() },\n            connectRequestProtected: { userHash: types.sgString(''), token: types.sgString(''), connectRequestNum: types.uInt32(0), connectRequestGroupStart: types.uInt32(0), connectRequestGroupEnd: types.uInt32(1) },\n            connectResponseProtected: { connectResult: types.uInt16(1), pairingState: types.uInt16(2), participantId: types.uInt32(0) },\n            localJoin: { clientType: types.uInt16(3), nativeWidth: types.uInt16(1080), nativeHeight: types.uInt16(1920), dpiX: types.uInt16(96), dpiY: types.uInt16(96), deviceCapabilities: types.uInt64(8, Buffer.from('ffffffffffffffff', 'hex')), clientVersion: types.uInt32(15), osMajorVersion: types.uInt32(6), osMinorVersion: types.uInt32(2), displayName: types.sgString('Xbox-TV') },\n            channelStartRequest: { channelRequestId: types.uInt32(0), titleId: types.uInt32(0), service: types.bytes(16, ''), activityId: types.uInt32(0) },\n            channelStartResponse: { channelRequestId: types.uInt32(0), channelTargetId: types.uInt64(8), result: types.uInt32(0) },\n            acknowledge: { lowWatermark: types.uInt32(0), processedList: types.sgList('processedList', []), rejectedList: types.sgList('rejectedList', []) },\n            processedList: { id: types.uInt32(0) },\n            rejectedList: { id: types.uInt32(0) },\n            consoleStatus: { liveTvProvider: types.uInt32(0), majorVersion: types.uInt32(0), minorVersion: types.uInt32(0), buildNumber: types.uInt32(0), locale: types.sgString('en-US'), activeTitles: types.sgArray('activeTitle') },\n            activeTitle: { flags: types.bytes(2), titleId: types.uInt32(0), productId: types.bytes(16, ''), sandboxId: types.bytes(16, ''), aumId: types.sgString('') },\n            recordGameDvr: { startTimeDelta: types.sInt32(0), endTimeDelta: types.sInt32(0) },\n            gamepad: { timestamp: types.uInt64(8), buttons: types.uInt16(0), leftTrigger: types.uInt32(0), rightTrigger: types.uInt32(0), leftThumbstickX: types.uInt32(0), leftThumbstickY: types.uInt32(0), rightThumbstickX: types.uInt32(0), rightThumbstickY: types.uInt32(0) },\n            mediaState: { titleId: types.uInt32(0), aumId: types.sgString(), assetId: types.sgString(), mediaType: types.mapper(LocalApi.Media.Types, types.uInt16(0)), soundLevel: types.mapper(LocalApi.Media.SoundLevel, types.uInt16(0)), enabledCommands: types.uInt32(0), playbackStatus: types.mapper(LocalApi.Media.PlaybackState, types.uInt16(0)), rate: types.uInt32(0), position: types.uInt64(8), mediaStart: types.uInt64(8), mediaEnd: types.uInt64(8), minSeek: types.uInt64(8), maxSeek: types.uInt64(8), metadata: types.sgArray('mediaStateList', []) },\n            mediaStateList: { name: types.sgString(), value: types.sgString() },\n            mediaCommand: { requestId: types.uInt64(8), titleId: types.uInt32(0), command: types.uInt32(0) },\n            powerOff: { liveId: types.sgString('') },\n            disconnect: { reason: types.uInt32(1), errorCode: types.uInt32(0) }\n        };\n\n        return packets;\n    }\n}\n\nexport default Packets;"
  },
  {
    "path": "src/localApi/sgcrypto.js",
    "content": "import JsRsaSign from 'jsrsasign';\nimport Crypto from 'crypto';\nimport { EOL } from 'os';\nimport Elliptic from 'elliptic';\nconst EC = Elliptic.ec;\nconst IV = Buffer.from('\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00');\n\nclass SgCrypto {\n    constructor() {\n        this.key = false;\n        this.iv = false;\n        this.hashKey = false;\n        this.ec = new EC('p256'); // P-256\n    }\n\n    async getPublicKey(certificate) {\n        try {\n            certificate = certificate.toString('base64').match(/.{0,64}/g).join('\\n');\n            const pem = `-----BEGIN CERTIFICATE-----${EOL}${certificate}-----END CERTIFICATE-----`;\n\n            const ecKey = JsRsaSign.X509.getPublicKeyFromCertPEM(pem);\n            const sha512 = Crypto.createHash('sha512');\n\n            const key1 = this.ec.genKeyPair();\n            const key2 = this.ec.keyFromPublic(ecKey.pubKeyHex, 'hex');\n\n            const shared1 = key1.derive(key2.getPublic());\n            const derivedSecret = Buffer.from(shared1.toString(16), 'hex');\n            const publicKeyClient = key1.getPublic('hex');\n\n            const preSalt = Buffer.from('d637f1aae2f0418c', 'hex');\n            const postSalt = Buffer.from('a8f81a574e228ab7', 'hex');\n            const prePostSalt = Buffer.concat([preSalt, derivedSecret, postSalt]);\n\n            const shaSecret = sha512.update(prePostSalt).digest();\n\n            const publicKey = Buffer.from(publicKeyClient.substring(2), 'hex');\n            const secret = Buffer.from(shaSecret.toString('hex'), 'hex');\n\n            this.key = secret.subarray(0, 16);\n            this.iv = secret.subarray(16, 32);\n            this.hashKey = secret.subarray(32, 64);\n\n            return { publicKey, iv: this.iv };\n        } catch (error) {\n            throw new Error(`sign public key error: ${error}`);\n        }\n    }\n\n    getKey() {\n        return this.key;\n    }\n\n    getIv() {\n        return this.iv;\n    }\n\n    encrypt(data, key, iv) {\n        data = Buffer.from(data);\n        key = key || this.key;\n        iv = iv || IV;\n\n        const cipher = Crypto.createCipheriv('aes-128-cbc', key, iv);\n        cipher.setAutoPadding(false);\n\n        let encryptedPayload = cipher.update(data, 'binary', 'binary');\n        encryptedPayload += cipher.final('binary');\n        return Buffer.from(encryptedPayload, 'binary');\n    }\n\n    // Note: argument order (data, iv, key) differs from encrypt(data, key, iv) — kept as original.\n    // All call sites pass (data, iv) relying on key defaulting to this.key.\n    decrypt(data, iv, key) {\n        key = key || this.key;\n        iv = iv || IV;\n\n        const decipher = Crypto.createDecipheriv('aes-128-cbc', key, iv);\n        decipher.setAutoPadding(false);\n\n        let decryptedPayload = decipher.update(data, 'binary', 'binary');\n        decryptedPayload += decipher.final('binary');\n        return this.removePadding(Buffer.from(decryptedPayload, 'binary'));\n    }\n\n    sign(data) {\n        const hashHmac = Crypto.createHmac('sha256', this.hashKey);\n        hashHmac.update(data, 'binary', 'binary');\n        return Buffer.from(hashHmac.digest('binary'), 'binary');\n    }\n\n    removePadding(payload) {\n        const payloadLength = payload.subarray(-1).readUInt8(0);\n        if (payloadLength > 0 && payloadLength < 16) {\n            return payload.subarray(0, payload.length - payloadLength);\n        }\n        return payload;\n    }\n}\n\nexport default SgCrypto;"
  },
  {
    "path": "src/localApi/simple.js",
    "content": "import Packets from './packets.js';\nimport Structure from './structure.js';\nimport { LocalApi } from '../constants.js';\n\nclass Simple {\n    constructor(type) {\n        this.type = type;\n        this.packets = new Packets();\n        this.packet = this.packets[type];\n        this.packetProtected = this.packet.payloadProtected ? this.packets[`${type}Protected`] : false;\n    }\n\n    // === Helpers ===\n    static applyPKCS7Padding(structure) {\n        const blockSize = 16;\n        const length = structure.toBuffer().length;\n        const padTotal = blockSize - (length % blockSize || blockSize);\n        for (let i = 0; i < padTotal; i++) {\n            structure.writeUInt8(padTotal);\n        }\n    }\n\n    set(key, value, isProtected = false) {\n        const targetPacket = isProtected ? this.packetProtected : this.packet;\n        if (!targetPacket || !targetPacket[key]) return;\n        const currentLength = targetPacket[key].length || 0;\n        targetPacket[key].value = value;\n        targetPacket[key].length = currentLength > 0 ? value.length : currentLength;\n    }\n\n    pack(crypto = false) {\n        const structure = new Structure();\n        let packet;\n        let payloadProtectedLength = 0;\n        let payloadProtectedLengthReal = 0;\n\n        for (const name in this.packet) {\n            if (name === 'payloadProtected' && this.packetProtected) {\n                const structureProtected = new Structure();\n                for (const fieldName in this.packetProtected) {\n                    if (this.packet.payloadProtected?.value?.[fieldName] !== undefined) {\n                        this.packetProtected[fieldName].value = this.packet.payloadProtected.value[fieldName];\n                    }\n                    this.packetProtected[fieldName].pack(structureProtected);\n                }\n\n                payloadProtectedLength = structureProtected.toBuffer().length;\n                Simple.applyPKCS7Padding(structureProtected);\n                payloadProtectedLengthReal = structureProtected.toBuffer().length;\n\n                const payloadEncrypted = crypto.encrypt(\n                    structureProtected.toBuffer(),\n                    crypto.getKey(),\n                    this.packet.iv?.value\n                );\n                structure.writeBytes(payloadEncrypted);\n            } else {\n                this.packet[name].pack(structure);\n            }\n        }\n\n        const payload = structure.toBuffer();\n        switch (this.type) {\n            case 'powerOn':\n                // FIX: powerOn (dd02) wire format: [type 2B][unprotectedLen 2B][protectedLen 2B][version=2 2B][payload]\n                // Original pack1('', ...) produced [type][len][00 00][payload] — missing protectedLen field.\n                {\n                    const hdr = new Structure();\n                    hdr.writeUInt16(payload.length); // unprotected_payload_length\n                    hdr.writeUInt16(0);              // protected_payload_length = 0\n                    hdr.writeUInt16(2);              // version = 2\n                    packet = Buffer.concat([LocalApi.Messages.Flags.powerOn, hdr.toBuffer(), payload]);\n                }\n                break;\n            case 'discoveryRequest':\n                packet = this.pack1(LocalApi.Messages.Flags.discoveryRequest, payload, Buffer.from('0000', 'hex'));\n                break;\n            case 'discoveryResponse':\n                packet = this.pack1(LocalApi.Messages.Flags.discoveryResponse, payload, Buffer.from([0, 2]));\n                break;\n            case 'connectRequest':\n                packet = this.pack1(LocalApi.Messages.Flags.connectRequest, payload, Buffer.from('0002', 'hex'), payloadProtectedLength, payloadProtectedLengthReal);\n                const payloadProtected = crypto.sign(packet);\n                packet = Buffer.concat([packet, Buffer.from(payloadProtected)]);\n                break;\n            case 'connectRequestProtected':\n                Simple.applyPKCS7Padding(structure);\n                // FIX: original passed crypto.getIv() as key — encrypt(data, key, iv) requires key first\n                let payloadEncrypted = crypto.encrypt(structure.toBuffer(), crypto.getKey(), crypto.getIv());\n                payloadEncrypted = new Structure(payloadEncrypted);\n                packet = payloadEncrypted.toBuffer();\n                break;\n            case 'connectResponse':\n                packet = this.pack1(LocalApi.Messages.Flags.connectResponse, payload, Buffer.from([0, 2]));\n                break;\n            default:\n                packet = payload;\n        }\n        return packet;\n    }\n\n    pack1(type, payload, version, payloadProtectedLength = 0, payloadProtectedLengthReal = 0) {\n        const structure = new Structure();\n        const structureProtected = new Structure();\n\n        if (payloadProtectedLength > 0) {\n            structure.writeUInt16(payload.length - payloadProtectedLengthReal);\n            const payloadLength = structure.toBuffer();\n            structureProtected.writeUInt16(payloadProtectedLength);\n            payloadProtectedLength = structureProtected.toBuffer();\n            return Buffer.concat([type, payloadLength, payloadProtectedLength, version, payload]);\n        }\n\n        structure.writeUInt16(payload.length);\n        const payloadLength = structure.toBuffer();\n        return Buffer.concat([type, payloadLength, Buffer.from([0, version[1] || 0]), payload]);\n    }\n\n    unpack(crypto = undefined, data = false) {\n        const structure = new Structure(data);\n        const typeHex = structure.readBytes(2).toString('hex');\n        const type = typeHex === 'dd02' ? 'powerOn' : this.type;\n\n        let packet = {\n            typeHex,\n            type,\n            payloadLength: structure.readUInt16(),\n            version: structure.readUInt16(),\n        };\n\n        if (packet.version !== 0 && packet.version !== 2) {\n            packet.payloadProtectedLength = packet.version;\n            packet.version = structure.readUInt16();\n        }\n\n        for (const name in this.packet) {\n            packet[name] = this.packet[name].unpack(structure);\n            this.set(name, packet[name]);\n        }\n\n        if (packet.payloadProtected !== undefined) {\n            // FIX: extract signature BEFORE truncating the buffer\n            const signature = packet.payloadProtected.subarray(-32);\n            const encryptedData = packet.payloadProtected.subarray(0, -32);\n            // Original: decrypt(data, iv) — matches sgcrypto.decrypt(data, iv, key) signature\n            const decrypted = crypto.decrypt(encryptedData, packet.iv).subarray(0, packet.payloadProtectedLength);\n\n            packet.payloadProtected = {};\n            const structurePayloadDecrypted = new Structure(decrypted);\n            const packetProtected = this.packets[`${packet.type}Protected`];\n\n            for (const name in packetProtected) {\n                packet.payloadProtected[name] = packetProtected[name].unpack(structurePayloadDecrypted);\n                this.set('payloadProtected', packet.payloadProtected);\n            }\n\n            // Note: crypto.verify not implemented in original sgcrypto — skipped intentionally\n            packet.signature = signature;\n        }\n\n        return packet;\n    }\n}\n\nexport default Simple;"
  },
  {
    "path": "src/localApi/structure.js",
    "content": "class Structure {\n    constructor(packet) {\n        this.packet = packet ? packet : Buffer.alloc(0);\n        this.totalLength = this.packet.length;\n        this.offset = 0;\n    }\n\n    writeSGString(data) {\n        if (typeof data !== 'string') {\n            throw new Error('data must be a string');\n        }\n\n        const dataLength = Buffer.byteLength(data, 'utf8');\n        if (dataLength > 65535) {\n            throw new Error('data exceeds the maximum allowed length');\n        }\n\n        const stringLengthBuffer = Buffer.allocUnsafe(2);\n        stringLengthBuffer.writeUInt16BE(dataLength, 0);\n\n        const dataBuffer = Buffer.from(data, 'utf8');\n        const nullTerminator = Buffer.from([0]);\n\n        this.add(Buffer.concat([stringLengthBuffer, dataBuffer, nullTerminator]));\n        return this;\n    }\n\n    readSGString() {\n        const stringLength = this.readUInt16();\n        const stringBuffer = this.packet.subarray(this.offset, this.offset + stringLength);\n        this.offset += stringLength + 1; // skip null terminator\n        return stringBuffer.toString('utf8');\n    }\n\n    writeBytes(data, type) {\n        const dataBuffer = Buffer.from(data, type);\n        this.add(dataBuffer);\n        return this;\n    }\n\n    readBytes(length = false) {\n        let rawData;\n        if (length === false) {\n            rawData = this.packet.subarray(this.offset);\n            this.offset = this.totalLength;\n        } else {\n            rawData = this.packet.subarray(this.offset, this.offset + length);\n            this.offset += length;\n        }\n        return rawData;\n    }\n\n    writeUInt8(data) {\n        if (data < 0 || data > 255) {\n            throw new Error('data must be a valid unsigned 8-bit integer');\n        }\n        const buf = Buffer.allocUnsafe(1);\n        buf.writeUInt8(data, 0);\n        this.add(buf);\n        return this;\n    }\n\n    readUInt8() {\n        const value = this.packet.readUInt8(this.offset);\n        this.offset += 1;\n        return value;\n    }\n\n    writeUInt16(data) {\n        if (data < 0 || data > 65535) {\n            throw new Error('data must be a valid unsigned 16-bit integer');\n        }\n        const buf = Buffer.allocUnsafe(2);\n        buf.writeUInt16BE(data, 0);\n        this.add(buf);\n        return this;\n    }\n\n    readUInt16() {\n        const value = this.packet.readUInt16BE(this.offset);\n        this.offset += 2;\n        return value;\n    }\n\n    writeUInt32(data) {\n        if (data < 0 || data > 0xFFFFFFFF) {\n            throw new Error('data must be a valid unsigned 32-bit integer');\n        }\n        const buf = Buffer.allocUnsafe(4);\n        buf.writeUInt32BE(data, 0);\n        this.add(buf);\n        return this;\n    }\n\n    readUInt32() {\n        const value = this.packet.readUInt32BE(this.offset);\n        this.offset += 4;\n        return value;\n    }\n\n    writeInt32(data) {\n        if (data < -2147483648 || data > 2147483647) {\n            throw new Error('data must be a valid signed 32-bit integer');\n        }\n        const buf = Buffer.allocUnsafe(4);\n        buf.writeInt32BE(data, 0);\n        this.add(buf);\n        return this;\n    }\n\n    readInt32() {\n        const value = this.packet.readInt32BE(this.offset);\n        this.offset += 4;\n        return value;\n    }\n\n    writeUInt64(value) {\n        if (typeof value !== 'bigint') {\n            throw new Error('value must be a BigInt');\n        }\n        const buf = Buffer.allocUnsafe(8);\n        buf.writeUInt32BE(Number(value >> 32n), 0);\n        buf.writeUInt32BE(Number(value & 0xFFFFFFFFn), 4);\n        this.add(buf);\n        return this;\n    }\n\n    readUInt64() {\n        const high = this.packet.readUInt32BE(this.offset);\n        const low = this.packet.readUInt32BE(this.offset + 4);\n        this.offset += 8;\n        return (BigInt(high) << 32n) | BigInt(low);\n    }\n\n    toBuffer() {\n        return this.packet;\n    }\n\n    add(data) {\n        if (!Buffer.isBuffer(data)) {\n            throw new Error('Data must be a Buffer object');\n        }\n        this.packet = Buffer.concat([this.packet, data]);\n        this.totalLength = this.packet.length;\n    }\n}\n\nexport default Structure;\n"
  },
  {
    "path": "src/localApi/xboxlocalapi.js",
    "content": "import dgram from 'dgram';\nimport { parse as UuIdParse, v4 as UuIdv4 } from 'uuid';\nimport EventEmitter from 'events';\nimport SimplePacket from './simple.js';\nimport MessagePacket from './message.js';\nimport SGCrypto from './sgcrypto.js';\nimport { LocalApi } from '../constants.js';\nimport ImpulseGenerator from '../impulsegenerator.js';\nimport Functions from '../functions.js';\n\nclass XboxLocalApi extends EventEmitter {\n    constructor(config, tokensFile, devInfoFile, restFulEnabled, mqttEnabled) {\n        super();\n\n        this.crypto = new SGCrypto();\n        this.host = config.host;\n        this.liveId = config.xboxLiveId;\n        this.logSuccess = config.log?.success;\n        this.logWarn = config.log?.warn;\n        this.logError = config.log?.error;\n        this.logDebug = config.log?.debug;\n        this.tokensFile = tokensFile;\n        this.devInfoFile = devInfoFile;\n\n        // FIX: guard with || false so undefined becomes false\n        this.restFulEnabled = restFulEnabled || false;\n        this.mqttEnabled = mqttEnabled || false;\n\n        this.connected = false;\n        this.power = false;\n        this.volume = 0;\n        this.mute = false;\n        this.titleId = '';\n        this.reference = '';\n        this.playState = false;\n\n        this.firstRun = false;\n        this.fragments = {};\n        this.socket = null;\n        this.acknowledgeInterval = null;\n        this.sequenceNumber = 0;\n        this.sourceParticipantId = 0;\n        this.functions = new Functions();\n\n        //create impulse generator\n        this.impulseGenerator = new ImpulseGenerator()\n            .on('connect', async () => {\n                try {\n                    if (this.connected || this.connecting) return;\n                    if (this.logDebug) this.emit('debug', `Plugin send heartbeat to console`);\n\n                    const state = await this.functions.ping(this.host);\n                    if (!state.online) {\n                        return;\n                    }\n\n                    if (this.logDebug) this.emit('debug', `Plugin received heartbeat from console`);\n\n                    this.connecting = true;\n                    try {\n                        await this.connect();\n                        // FIX: discoveryRequest in try (not finally) — only sent when socket is ready\n                        const discoveryRequest = new SimplePacket('discoveryRequest');\n                        const message = discoveryRequest.pack(this.crypto);\n                        await this.sendSocketMessage(message, 'discoveryRequest');\n                    } catch (error) {\n                        if (this.logError) this.emit('error', `Connection error: ${error}`);\n                    } finally {\n                        // FIX: always release the connecting lock so next heartbeat can retry\n                        this.connecting = false;\n                    }\n                } catch (error) {\n                    if (this.logError) this.emit('error', `Local API heartbeat error: ${error}, will retry`);\n                }\n            })\n            .on('state', (state) => {\n                this.emit(state ? 'success' : 'warn', `Local Api monitoring ${state ? 'started' : 'stopped'}`);\n            });\n    };\n\n    async updateState() {\n        // FIX: clearInterval before nulling — without this the old timer survives\n        // reconnect and fires a spurious disconnect after 14 s of the new session.\n        if (this.acknowledgeInterval) {\n            clearInterval(this.acknowledgeInterval);\n        }\n        this.socket = null;\n        this.connected = false;\n        this.firstRun = false;\n        this.acknowledgeInterval = null;\n        this.sequenceNumber = 0;\n        this.targetParticipantId = 0;\n        this.sourceParticipantId = 0;\n        this.power = false;\n\n        this.emit('stateChanged', this.power, this.titleId, this.reference, this.volume, this.mute, this.playState);\n        return true;\n    };\n\n    async getSequenceNumber() {\n        const seq = this.sequenceNumber;\n        this.sequenceNumber = (this.sequenceNumber + 1) >>> 0;\n        // FIX: typo 'Sqquence' → 'Sequence'\n        if (this.logDebug) this.emit('debug', `Sequence number set to: ${this.sequenceNumber}`);\n        return seq;\n    };\n\n    async sendSocketMessage(message, type, host = this.host) {\n        return new Promise((resolve, reject) => {\n            if (!this.socket) {\n                return reject(new Error(`Socket not initialized, cannot send message: ${type}`));\n            }\n\n            const offset = 0;\n            const length = message.byteLength;\n            this.socket.send(message, offset, length, 5050, host, (error, bytes) => {\n                if (error) {\n                    return reject(new Error(`Socket send error: ${error}`));\n                }\n\n                if (this.logDebug) this.emit('debug', `Socket send: ${type} → ${host}, ${bytes}B`);\n                resolve(true);\n            });\n        });\n    };\n\n    async connect() {\n        return new Promise((resolve, reject) => {\n            try {\n                this.socket = dgram.createSocket('udp4')\n                    .on('error', (error) => {\n                        if (this.logError) this.emit('error', `Socket error: ${error}`);\n                        this.socket?.close();\n                        reject(`Socket error: ${error}`);\n                    })\n                    .on('close', async () => {\n                        if (this.logDebug) this.emit('debug', 'Socket closed.');\n                        await this.updateState();\n                    })\n                    .on('listening', () => {\n                        this.socket.setBroadcast(true);\n                        const address = this.socket.address();\n                        if (this.logDebug) this.emit('debug', `Socket start listening: ${address.address}:${address.port}`);\n                        resolve(true);\n                    })\n                    .on('message', async (data) => {\n                        try {\n                            // get message type in hex\n                            const messageTypeHex = data.subarray(0, 2).toString('hex');\n                            if (this.logDebug) this.emit('debug', `Received message type: ${messageTypeHex}`);\n\n                            // check message type exists\n                            if (!Object.keys(LocalApi.Messages.Category).includes(messageTypeHex)) {\n                                if (this.logWarn) this.emit('warn', `Received unknown message type: ${messageTypeHex}, message: ${data}`);\n                                return;\n                            }\n\n                            // get message type and request\n                            const messageType = LocalApi.Messages.Category[messageTypeHex];\n                            const messageRequest = LocalApi.Messages.CategoryTypes[messageTypeHex];\n\n                            // create packet structure\n                            let packetStructure;\n                            switch (messageRequest) {\n                                case 'discoveryRequest':\n                                case 'discoveryResponse':\n                                case 'connectRequest':\n                                case 'connectResponse':\n                                    packetStructure = new SimplePacket(messageRequest);\n                                    break;\n                                case 'message':\n                                    packetStructure = new MessagePacket(messageRequest);\n                                    break;\n                                default:\n                                    if (this.logDebug) this.emit('debug', `No handler for type: ${messageTypeHex}`);\n                                    return;\n                            }\n\n                            // unpack packet\n                            let packet;\n                            try {\n                                packet = packetStructure.unpack(this.crypto, data);\n                                if (this.logDebug) this.emit('debug', `Received packet type: ${packet.type}, packet: ${JSON.stringify(packet, null, 2)}`);\n                            } catch (error) {\n                                if (this.logError) this.emit('error', `Failed to unpack packet type: ${messageType}, error: ${error.message}`);\n                                return;\n                            }\n\n                            if (messageType === 'message') {\n                                const targetId = packet.targetParticipantId;\n\n                                // FIX: targetId=0 is a broadcast (console keepalive acknowledge) —\n                                // must pass through regardless of our sourceParticipantId.\n                                if (targetId !== 0 && targetId !== this.sourceParticipantId) {\n                                    if (this.logDebug) this.emit('debug', `ParticipantId mismatch: ${targetId} !== ${this.sourceParticipantId}. Ignoring packet`);\n                                    return;\n                                }\n\n                                if (packet.flags.needAcknowlegde) {\n                                    try {\n                                        const acknowledge = new MessagePacket('acknowledge');\n                                        acknowledge.set('lowWatermark', packet.sequenceNumber);\n                                        acknowledge.packet.processedList.value.push({ id: packet.sequenceNumber });\n                                        const sequenceNumber1 = await this.getSequenceNumber();\n                                        const message = acknowledge.pack(this.crypto, sequenceNumber1, this.targetParticipantId, this.sourceParticipantId);\n                                        await this.sendSocketMessage(message, 'acknowledge');\n                                    } catch (error) {\n                                        if (this.logError) this.emit('error', `Heartbeat error: ${error}`);\n                                    }\n                                }\n                            }\n\n                            // handle packet types\n                            switch (packet.type) {\n                                case 'json':\n                                    const fragments = this.fragments;\n                                    let jsonMessage;\n\n                                    try {\n                                        jsonMessage = JSON.parse(packet.payloadProtected.json);\n                                    } catch (error) {\n                                        if (this.logDebug) this.emit('debug', `Failed to parse JSON payload: ${error.message}`);\n                                        return;\n                                    }\n\n                                    const datagramId = jsonMessage.datagramId;\n                                    if (datagramId) {\n                                        if (!fragments[datagramId]) {\n                                            fragments[datagramId] = {\n                                                partials: {},\n                                                getValue() {\n                                                    const buffers = Object.keys(this.partials)\n                                                        .sort((a, b) => Number(a) - Number(b))\n                                                        .map(offset => Buffer.from(this.partials[offset], 'base64'));\n                                                    return Buffer.concat(buffers);\n                                                },\n                                                isValid() {\n                                                    try {\n                                                        JSON.parse(this.getValue().toString());\n                                                        return true;\n                                                    } catch {\n                                                        return false;\n                                                    }\n                                                }\n                                            };\n                                        }\n\n                                        fragments[datagramId].partials[jsonMessage.fragmentOffset] = jsonMessage.fragmentData;\n\n                                        if (fragments[datagramId].isValid()) {\n                                            const fullJson = fragments[datagramId].getValue().toString();\n                                            packet.payloadProtected = JSON.parse(fullJson);\n\n                                            if (this.logDebug) this.emit('debug', `Reassembled JSON packet: ${fullJson}`);\n                                            delete fragments[datagramId];\n                                        }\n                                    }\n                                    break;\n                                case 'discoveryResponse':\n                                    if (this.connected) return;\n\n                                    const deviceType = packet.clientType;\n                                    const deviceName = packet.consoleName;\n                                    const certificate = packet.certificate;\n                                    // FIX: typo 'athorized' → 'authorized'\n                                    let authorized = false;\n\n                                    if (this.logDebug) this.emit('debug', `Discovered device: ${LocalApi.Console.Name[deviceType] || 'Unknown'}, name: ${deviceName}`);\n\n                                    if (!certificate) {\n                                        if (this.logError) this.emit('error', 'Certificate missing from device packet');\n                                        return;\n                                    }\n\n                                    let token = null;\n                                    let userHash = null;\n                                    try {\n                                        const response = await this.functions.readData(this.tokensFile, true);\n                                        token = response?.xsts?.Token || null;\n                                        userHash = response.xsts.DisplayClaims?.xui?.[0]?.uhs;\n\n                                        if (token && userHash) {\n                                            authorized = true;\n                                        }\n                                    } catch (error) {\n                                        this.emit('debug', 'No valid token data found, connecting anonymously');\n                                    }\n\n                                    try {\n                                        const data = await this.crypto.getPublicKey(certificate);\n                                        if (this.logDebug) this.emit('debug', `Signed public key: ${data.publicKey.toString('hex')}, iv: ${data.iv.toString('hex')}`);\n\n                                        const connectRequest = new SimplePacket('connectRequest');\n                                        const uuidBuffer = Buffer.from(UuIdParse(UuIdv4()));\n                                        if (uuidBuffer.length !== 16) {\n                                            if (this.logError) this.emit('error', 'Invalid UUID length');\n                                            return;\n                                        }\n\n                                        connectRequest.set('uuid', uuidBuffer);\n                                        connectRequest.set('publicKey', data.publicKey);\n                                        connectRequest.set('iv', data.iv);\n\n                                        if (authorized) {\n                                            const sequenceNumber = await this.getSequenceNumber();\n                                            connectRequest.set('userHash', userHash, true);\n                                            connectRequest.set('token', token, true);\n                                            connectRequest.set('connectRequestNum', sequenceNumber);\n                                            connectRequest.set('connectRequestGroupStart', 0);\n                                            connectRequest.set('connectRequestGroupEnd', 1);\n                                        }\n\n                                        // Track auth state so powerOff() can detect anonymous sessions\n                                        this.authorized = authorized;\n                                        if (this.logDebug) this.emit('debug', `Client connecting using: ${authorized ? 'XSTS token' : 'Anonymous'}`);\n\n                                        const message = connectRequest.pack(this.crypto);\n                                        await this.sendSocketMessage(message, 'connectRequest');\n                                    } catch (error) {\n                                        if (this.logError) this.emit('error', `Sign certificate error: ${error}`);\n                                    }\n                                    break;\n                                case 'connectResponse':\n                                    const { connectResult, pairingState, participantId } = packet.payloadProtected;\n                                    const errorTable = {\n                                        0: 'Success.',\n                                        1: 'Pending login. Reconnect to complete.',\n                                        2: 'Unknown.',\n                                        3: 'Anonymous connections disabled.',\n                                        4: 'Device limit exceeded.',\n                                        5: 'Remote connect is disabled on the console.',\n                                        6: 'User authentication failed.',\n                                        7: 'User Sign-In failed.',\n                                        8: 'User Sign-In timeout.',\n                                        9: 'User Sign-In required.'\n                                    };\n\n                                    if (connectResult !== 0) {\n                                        if (this.logError) this.emit('error', `Connect error: ${errorTable[connectResult] || connectResult}`);\n                                        return;\n                                    }\n                                    if (this.logDebug) this.emit('debug', `Client connected, pairing state: ${LocalApi.Console.PairingState[pairingState]}`);\n                                    this.connected = true;\n\n                                    try {\n                                        this.sourceParticipantId = participantId;\n                                        this.targetParticipantId = packet.sourceParticipantId || this.targetParticipantId || 0;\n\n                                        const sequenceNumber = await this.getSequenceNumber();\n                                        const localJoin = new MessagePacket('localJoin');\n                                        const message = localJoin.pack(this.crypto, sequenceNumber, this.targetParticipantId, this.sourceParticipantId);\n                                        await this.sendSocketMessage(message, 'localJoin');\n\n                                        this.firstRun = true;\n                                    } catch (error) {\n                                        if (this.logError) this.emit('error', `Send local join error: ${error}`);\n                                    }\n                                    break;\n                                case 'consoleStatus':\n                                    if (!packet.payloadProtected) return;\n\n                                    if (this.firstRun) {\n                                        if (this.logSuccess) this.emit('success', `Connect Success`);\n\n                                        const { majorVersion, minorVersion, buildNumber, locale } = packet.payloadProtected;\n                                        const firmwareRevision = `${majorVersion}.${minorVersion}.${buildNumber}`;\n\n                                        const info = { locale, firmwareRevision };\n                                        this.emit('deviceInfo', info);\n                                        this.firstRun = false;\n                                    }\n\n                                    const activeTitles = Array.isArray(packet.payloadProtected.activeTitles) ? packet.payloadProtected.activeTitles : [];\n\n                                    // FIX: power derived from activeTitles presence\n                                    const power = activeTitles.length > 0;\n\n                                    if (power) {\n                                        // FIX: use last element — activeTitles ordered oldest→newest,\n                                        // last entry is the foreground title\n                                        const title = activeTitles[0];\n                                        this.titleId = title.titleId;\n                                        this.reference = title.aumId;\n                                    }\n\n                                    this.power = power;\n                                    this.playState = false;\n\n                                    // FIX: emit stateChanged always — when activeTitles is empty\n                                    // (console turning off), power=false must reach HomeKit immediately.\n                                    this.emit('stateChanged', this.power, this.titleId, this.reference, this.volume, this.mute, this.playState);\n                                    if (this.logDebug) this.emit('debug', `Status changed, power: ${this.power}, app Id: ${this.titleId}, reference: ${this.reference}`);\n\n                                    const statusState = { power: this.power, titleId: this.titleId, reference: this.reference, volume: this.volume, mute: this.mute };\n                                    if (this.restFulEnabled) this.emit('restFul', 'state', statusState);\n                                    if (this.mqttEnabled) this.emit('mqtt', 'State', statusState);\n\n                                    // Inactivity watchdog — consoleStatus is the primary sign-of-life.\n                                    // We ping the host every 5 s to reset the timer when the console is\n                                    // idle between status packets. After 14 s silence the socket is\n                                    // closed so the impulse generator can reconnect.\n                                    this.heartBeatStartTime = Date.now();\n                                    if (!this.acknowledgeInterval) {\n                                        this.acknowledgeInterval = setInterval(async () => {\n                                            const elapsed = (Date.now() - this.heartBeatStartTime) / 1000;\n                                            if (this.logDebug) this.emit('debug', `Console last seen: ${elapsed.toFixed(1)}s ago`);\n\n                                            if (elapsed >= 14) {\n                                                clearInterval(this.acknowledgeInterval);\n                                                this.acknowledgeInterval = null;\n                                                if (this.logDebug) this.emit('debug', `Console inactivity timeout — disconnecting`);\n                                                // Close socket first so on('close') triggers updateState().\n                                                // Calling updateState() directly would null this.socket before\n                                                // close(), causing the old socket to leak in the OS.\n                                                const socketToClose = this.socket;\n                                                this.socket = null;\n                                                this.connected = false;\n                                                if (socketToClose) socketToClose.close();\n                                                return;\n                                            }\n\n                                            // Network ping every 5 s — resets the watchdog when console\n                                            // is reachable but has no state change to report.\n                                            if (Math.round(elapsed) % 5 === 0 && Math.round(elapsed) > 0) {\n                                                try {\n                                                    const pingResult = await this.functions.ping(this.host);\n                                                    if (pingResult.online) {\n                                                        this.heartBeatStartTime = Date.now();\n                                                        if (this.logDebug) this.emit('debug', `Ping OK — console reachable`);\n                                                    } else {\n                                                        if (this.logDebug) this.emit('debug', `Ping failed — console unreachable`);\n                                                    }\n                                                } catch (error) {\n                                                    if (this.logError) this.emit('error', `Ping error: ${error}`);\n                                                }\n                                            }\n                                        }, 1000);\n                                    }\n                                    break;\n                                case 'acknowledge':\n                                    this.heartBeatStartTime = Date.now();\n\n                                    if (!this.acknowledgeInterval) {\n                                        this.acknowledgeInterval = setInterval(async () => {\n                                            const elapsed = (Date.now() - this.heartBeatStartTime) / 1000;\n                                            if (this.logDebug) this.emit('debug', `Socket received heart beat: ${elapsed.toFixed(1)} sec ago`);\n\n                                            if (elapsed >= 14) {\n                                                clearInterval(this.acknowledgeInterval);\n\n                                                const sequenceNumber = await this.getSequenceNumber();\n                                                const disconnect = new MessagePacket('disconnect');\n                                                disconnect.set('reason', 2);\n                                                disconnect.set('errorCode', 0);\n                                                const message = disconnect.pack(this.crypto, sequenceNumber, this.targetParticipantId, this.sourceParticipantId);\n                                                await this.sendSocketMessage(message, 'disconnect');\n                                                await this.updateState();\n                                            }\n                                        }, 1000);\n                                    }\n                                    break;\n                                case 'pairedIdentityStateChanged':\n                                    const pairingState1 = packet.payloadProtected.pairingState || 0;\n                                    if (this.logDebug) this.emit('debug', `Client pairing state: ${LocalApi.Console.PairingState[pairingState1]}`);\n                                    break;\n                                default:\n                                    if (this.logWarn) this.emit('warn', `Received unknown packet type: ${packet.type}`);\n                                    break;\n                            }\n                        } catch (error) {\n                            if (this.logError) this.emit('error', `Handle message error: ${error.message || error}`);\n                        }\n                    })\n                    .bind();\n            } catch (error) {\n                reject(`Connect error: ${error.message || error}`);\n            };\n        });\n    };\n};\nexport default XboxLocalApi;"
  },
  {
    "path": "src/mqtt.js",
    "content": "import { connect } from 'mqtt';\nimport EventEmitter from 'events';\n\nclass Mqtt extends EventEmitter {\n    constructor(config) {\n        super();\n        this.config = config;\n\n        const url = `mqtt://${config.host}:${config.port}`;\n        const subscribeTopic = `${config.prefix}/Set`;\n\n        const options = {\n            clientId: config.clientId,\n            username: config.user,\n            password: config.passwd,\n            protocolVersion: 5,\n            clean: false,\n            properties: {\n                sessionExpiryInterval: 60 * 60,\n                userProperties: {\n                    source: 'node-client'\n                }\n            }\n        };\n\n        this.mqttClient = connect(url, options)\n            .on('connect', async () => {\n                this.emit('connected', 'MQTT v5 connected.');\n\n                try {\n                    await new Promise((resolve, reject) => {\n                        this.mqttClient.subscribe(subscribeTopic,\n                            {\n                                qos: 1,\n                                properties: {\n                                    userProperties: {\n                                        type: 'subscription'\n                                    }\n                                }\n                            },\n                            (error) => {\n                                if (error) return reject(error);\n                                resolve();\n                            }\n                        );\n                    });\n\n                    this.emit('connected', `MQTT Subscribe topic: ${subscribeTopic}`);\n                } catch (error) {\n                    if (config.logWarn) this.emit('warn', `MQTT Subscribe error: ${error.message}`);\n                }\n            })\n            .on('message', (topic, payload, packet) => {\n                try {\n                    const parsedMessage = JSON.parse(payload.toString());\n                    if (config.logDebug) this.emit('debug', `MQTT Received Topic: ${topic}, Payload: ${JSON.stringify(parsedMessage, null, 2)}`);\n\n                    for (const [key, value] of Object.entries(parsedMessage)) {\n                        this.emit('set', key, value);\n                    }\n                } catch (error) {\n                    if (config.logWarn) this.emit('warn', `MQTT Parse error: ${error.message}`);\n                }\n            })\n            .on('error', (error) => {\n                this.emit('warn', `MQTT Error: ${error.message}`);\n            })\n            .on('reconnect', () => {\n                if (config.logDebug) this.emit('debug', 'MQTT Reconnecting...');\n            })\n            .on('close', () => {\n                if (config.logDebug) this.emit('debug', 'MQTT Connection closed.');\n            });\n    }\n\n    publish(topic, message) {\n        return new Promise((resolve, reject) => {\n            const fullTopic = `${this.config.prefix}/${topic}`;\n            const publishMessage = JSON.stringify(message);\n\n            this.mqttClient.publish(fullTopic, publishMessage,\n                {\n                    qos: 1,\n                    properties: {\n                        contentType: 'application/json',\n                        userProperties: {\n                            source: 'node',\n                            action: 'set'\n                        }\n                    }\n                },\n                (error) => {\n                    if (error) {\n                        if (this.config.logWarn) this.emit('warn', `MQTT Publish error: ${error.message}`);\n                        return reject(error);\n                    }\n\n                    if (this.config.logDebug) this.emit('debug', `MQTT Publish Topic: ${fullTopic}, Payload: ${publishMessage}`);\n                    resolve();\n                }\n            );\n        });\n    }\n}\n\nexport default Mqtt;"
  },
  {
    "path": "src/restful.js",
    "content": "import express, { json } from 'express';\nimport EventEmitter from 'events';\n\nconst DEFAULT_MESSAGE = 'This data is not available at this time.';\n\nclass RestFul extends EventEmitter {\n    constructor(config) {\n        super();\n        this.port = config.port;\n        this.logWarn = config.logWarn;\n        this.logDebug = config.logDebug;\n\n        this.restFulData = {\n            info: DEFAULT_MESSAGE,\n            state: DEFAULT_MESSAGE,\n            operation: DEFAULT_MESSAGE,\n            consoleslist: DEFAULT_MESSAGE,\n            profile: DEFAULT_MESSAGE,\n            apps: DEFAULT_MESSAGE,\n            storages: DEFAULT_MESSAGE,\n            status: DEFAULT_MESSAGE\n        }\n        this.connect();\n    }\n\n    connect() {\n        try {\n            const app = express();\n            app.set('json spaces', 2);\n            app.use(json());\n\n            // Register GET routes for all keys\n            for (const key of Object.keys(this.restFulData)) {\n                app.get(`/${key}`, (req, res) => {\n                    res.json(this.restFulData[key]);\n                });\n            }\n\n            // Health check route\n            app.get('/status', (req, res) => {\n                res.json({\n                    status: 'online',\n                    uptime: process.uptime(),\n                    available_paths: Object.keys(this.restFulData).map(k => `/${k}`)\n                });\n            });\n\n            // POST route to update values\n            app.post('/', (req, res) => {\n                try {\n                    const obj = req.body;\n                    if (!obj || typeof obj !== 'object' || Object.keys(obj).length === 0) {\n                        if (this.logWarn) this.emit('warn', 'RESTFul Invalid JSON payload');\n                        return res.status(400).json({ error: 'RESTFul Invalid JSON payload' });\n                    }\n\n                    const key = Object.keys(obj)[0];\n                    const value = obj[key];\n                    this.emit('set', key, value);\n                    this.update(key, value);\n\n                    if (this.logDebug) this.emit('debug', `RESTFul post data: ${JSON.stringify(obj, null, 2)}`);\n\n                    res.json({ success: true, received: obj });\n                } catch (error) {\n                    if (this.logWarn) this.emit('warn', `RESTFul Parse error: ${error}`);\n                    res.status(500).json({ error: 'RESTFul Internal Server Error' });\n                }\n            });\n\n            // Start the server\n            app.listen(this.port, () => {\n                this.emit('connected', `RESTful started on port: ${this.port}`);\n            });\n        } catch (error) {\n            if (this.logWarn) this.emit('warn', `RESTful Connect error: ${error}`);\n        }\n    }\n\n    update(path, data) {\n        if (this.restFulData.hasOwnProperty(path)) {\n            this.restFulData[path] = data;\n        } else {\n            if (this.logWarn) this.emit('warn', `Unknown RESTFul update path: ${path}, data: ${JSON.stringify(data)}`);\n            return;\n        }\n\n        if (this.logDebug) this.emit('debug', `RESTFul update path: ${path}, data: ${JSON.stringify(data)}`);\n    }\n}\nexport default RestFul;"
  },
  {
    "path": "src/webApi/authentication.js",
    "content": "import QueryString from 'querystring';\nimport axios from 'axios';\nimport { WebApi } from '../constants.js';\nimport Functions from '../functions.js';\n\nclass Authentication {\n    constructor(config) {\n        this.webApiClientId = config.clientId || WebApi.ClientId;\n        this.webApiClientSecret = config.clientSecret;\n        this.tokensFile = config.tokensFile;\n        this.tokens = {\n            oauth: {},\n            user: {},\n            xsts: {}\n        };\n\n        this.functions = new Functions();\n    }\n\n    async refreshToken(token) {\n        try {\n            const payload = {\n                'client_id': this.webApiClientId,\n                'grant_type': 'refresh_token',\n                'scope': WebApi.Scopes,\n                'refresh_token': token,\n            };\n\n            if (this.webApiClientSecret) {\n                payload.client_secret = this.webApiClientSecret;\n            }\n\n            const postData = QueryString.stringify(payload);\n            // BŁ12 FIX: axios.post expects config object as 3rd arg, not bare headers object\n            const response = await axios.post(WebApi.Url.RefreshToken, postData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });\n            const refreshToken = response.data;\n            refreshToken.issued = new Date().toISOString();\n            this.tokens.oauth = refreshToken;\n            return true;\n        } catch (error) {\n            throw new Error(`Refresh token error: ${error}`);\n        }\n    }\n\n    async getUserToken(accessToken) {\n        try {\n            const payload = {\n                'RelyingParty': 'http://auth.xboxlive.com',\n                'TokenType': 'JWT',\n                'Properties': {\n                    'AuthMethod': 'RPS',\n                    'SiteName': 'user.auth.xboxlive.com',\n                    'RpsTicket': `d=${accessToken}`\n                }\n            };\n\n            const postData = JSON.stringify(payload);\n            const response = await axios.post(WebApi.Url.UserToken, postData, { headers: { 'Content-Type': 'application/json' } });\n            const userToken = response.data;\n            this.tokens.user = userToken;\n            this.tokens.xsts = {};\n            return true;\n        } catch (error) {\n            throw new Error(`User token error: ${error}`);\n        }\n    }\n\n    async getXstsToken(userToken) {\n        try {\n            const payload = {\n                'RelyingParty': 'http://xboxlive.com',\n                'TokenType': 'JWT',\n                'Properties': {\n                    'UserTokens': [userToken],\n                    'SandboxId': 'RETAIL',\n                }\n            };\n\n            const postData = JSON.stringify(payload);\n            // BŁ12 FIX: was passing bare headers object without { headers } wrapper\n            const response = await axios.post(WebApi.Url.XstsToken, postData, { headers: { 'Content-Type': 'application/json', 'x-xbl-contract-version': '1' } });\n            const xstsToken = response.data;\n            this.tokens.xsts = xstsToken;\n            return true;\n        } catch (error) {\n            throw new Error(`Xsts token error: ${error}`);\n        }\n    }\n\n    async accessToken(webApiToken) {\n        try {\n            const payload = {\n                'client_id': this.webApiClientId,\n                'grant_type': 'authorization_code',\n                'scope': WebApi.Scopes,\n                'code': webApiToken,\n                'redirect_uri': WebApi.Url.Redirect\n            };\n\n            if (this.webApiClientSecret) {\n                payload.client_secret = this.webApiClientSecret;\n            }\n\n            const postData = QueryString.stringify(payload);\n            // BŁ12 FIX: consistent { headers } wrapper\n            const response = await axios.post(WebApi.Url.AccessToken, postData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });\n            const accessToken = response.data;\n            accessToken.issued = new Date().toISOString();\n            this.tokens.oauth = accessToken;\n            await this.functions.saveData(this.tokensFile, this.tokens);\n            return true;\n        } catch (error) {\n            throw new Error(`Access token error: ${error}`);\n        }\n    }\n\n    async refreshTokens(type) {\n        switch (type) {\n            case 'user':\n                if (this.tokens.user.Token) {\n                    const tokenExpired = new Date() > new Date(this.tokens.user.NotAfter).getTime();\n                    if (tokenExpired) {\n                        try {\n                            await this.refreshToken(this.tokens.oauth.refresh_token);\n                            await this.getUserToken(this.tokens.oauth.access_token);\n                            await this.refreshTokens('xsts');\n                            return true;\n                        } catch (error) {\n                            throw new Error(error);\n                        }\n                    } else {\n                        try {\n                            await this.refreshTokens('xsts');\n                            return true;\n                        } catch (error) {\n                            throw new Error(error);\n                        }\n                    }\n                } else {\n                    try {\n                        await this.getUserToken(this.tokens.oauth.access_token);\n                        await this.refreshTokens('xsts');\n                        return true;\n                    } catch (error) {\n                        throw new Error(error);\n                    }\n                }\n            case 'xsts':\n                if (this.tokens.xsts.Token) {\n                    const tokenExpired = new Date() > new Date(this.tokens.xsts.NotAfter).getTime();\n                    if (tokenExpired) {\n                        try {\n                            // BŁ13 FIX: was calling refreshTokens('xsts') after getXstsToken → infinite recursion\n                            // when token expired. Now just refresh once and return.\n                            await this.getXstsToken(this.tokens.user.Token);\n                            return true;\n                        } catch (error) {\n                            throw new Error(error);\n                        }\n                    } else {\n                        return true;\n                    }\n                } else {\n                    try {\n                        await this.getXstsToken(this.tokens.user.Token);\n                        return true;\n                    } catch (error) {\n                        throw new Error(error);\n                    }\n                }\n            default:\n                throw new Error(`Unknown refresh token type: ${type}`);\n        }\n    }\n\n    async checkAuthorization() {\n        if (this.webApiClientId) {\n            try {\n                const tokens = await this.functions.readData(this.tokensFile, true);\n                this.tokens = !tokens ? this.tokens : tokens;\n                const refreshToken = this.tokens.oauth.refresh_token ?? false;\n\n                if (refreshToken) {\n                    await this.refreshTokens('user');\n                    await this.functions.saveData(this.tokensFile, this.tokens);\n                    return { headers: `XBL3.0 x=${this.tokens.xsts.DisplayClaims.xui[0].uhs};${this.tokens.xsts.Token}`, tokens: this.tokens };\n                } else {\n                    throw new Error('No oauth token found. Use authorization manager first.');\n                }\n            } catch (error) {\n                throw new Error(error);\n            }\n        } else {\n            throw new Error(`Authorization not possible, check plugin settings - Client Id: ${this.webApiClientId}`);\n        }\n    }\n\n    async generateAuthorizationUrl() {\n        try {\n            const payload = {\n                'client_id': this.webApiClientId,\n                'response_type': 'code',\n                'approval_prompt': 'auto',\n                'scope': WebApi.Scopes,\n                'redirect_uri': WebApi.Url.Redirect\n            };\n            const params = QueryString.stringify(payload);\n            const oauth2URI = `${WebApi.Url.oauth2}?${params}`;\n            return oauth2URI;\n        } catch (error) {\n            throw new Error(`Authorization URL error: ${error}`);\n        }\n    }\n}\nexport default Authentication;"
  },
  {
    "path": "src/webApi/providers/achievements.js",
    "content": "import axios from 'axios';\n\nclass Archivements {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '2';\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getTitleAchievements(continuationToken = 0) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://achievements.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/history/titles?continuationToken=${continuationToken}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getTitleAchievements360(continuationToken = 0) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                this.headers['x-xbl-contract-version'] = 1\n                const url = `https://achievements.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/history/titles?continuationToken=${continuationToken}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getTitleId(titleId, continuationToken = 0) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://achievements.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/achievements?titleId=${titleId}&continuationToken=${continuationToken}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getTitleId360(titleId, continuationToken = 0) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                this.headers['x-xbl-contract-version'] = 1\n                const url = `https://achievements.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/achievements?titleId=${titleId}&continuationToken=${continuationToken}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Archivements;"
  },
  {
    "path": "src/webApi/providers/catalog.js",
    "content": "import QueryString from 'querystring';\nimport axios from 'axios';\n\nclass Catalog {\n    constructor(authorizationHeaders) {\n        const headers = authorizationHeaders;\n        headers = { 'MS-CV': '0' };\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    searchTitle(query, marketLocale = 'US', languagesLocale = 'en-US') {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const searchParams = {\n                    \"languages\": languagesLocale,\n                    \"market\": marketLocale,\n                    \"platformdependencyname\": 'windows.xbox',\n                    \"productFamilyNames\": \"Games,Apps\",\n                    \"query\": query,\n                    \"topProducts\": 25,\n                }\n                const queryParams = QueryString.stringify(searchParams);\n                const url = `https://displaycatalog.mp.microsoft.com/v7.0/productFamilies/autosuggest?${queryParams}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getProductId(query, marketLocale = 'US', languagesLocale = 'en-US') {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const searchParams = {\n                    \"actionFilter\": 'Browse',\n                    \"bigIds\": [query],\n                    \"fieldsTemplate\": 'details',\n                    \"languages\": languagesLocale,\n                    \"market\": marketLocale,\n                }\n\n                const queryParams = QueryString.stringify(searchParams);\n                const url = `https://displaycatalog.mp.microsoft.com/v7.0/products?${queryParams}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getProductFromAlternateId(titleId, titleType, marketLocale = 'US', languagesLocale = 'en-US') {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const searchParams = {\n                    \"top\": 25,\n                    \"alternateId\": titleType,\n                    \"fieldsTemplate\": 'details',\n                    \"languages\": languagesLocale,\n                    \"market\": marketLocale,\n                    \"value\": titleId,\n                }\n\n                const queryParams = QueryString.stringify(searchParams);\n                const url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup${queryParams}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Catalog;\n"
  },
  {
    "path": "src/webApi/providers/gameclips.js",
    "content": "import QueryString from 'querystring';\nimport axios from 'axios';\n\nclass GameClip {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '1';\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getUserGameclips() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://gameclipsmetadata.xboxlive.com/users/me/clips`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getCommunityGameclipsByTitleId(titleId) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://gameclipsmetadata.xboxlive.com/public/titles/${titleId}clips/saved?qualifier=created`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getGameclipsByXuid(titleId, skipItems, maxItems) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const params = {\n                    skipitems: skipItems || 0,\n                    maxitems: maxItems || 25,\n                }\n\n                if (titleId !== undefined) {\n                    params.titleid = titleId\n                }\n\n                const queryParams = QueryString.stringify(params);\n                const url = `https://gameclipsmetadata.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/clips?${queryParams}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default GameClip;"
  },
  {
    "path": "src/webApi/providers/messages.js",
    "content": "import axios from 'axios';\n\nclass Messages {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        \n         //create axios instance\n         this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getInbox() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://xblmessaging.xboxlive.com/network/Xbox/users/me/inbox`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n\n    }\n\n    getConversation() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://xblmessaging.xboxlive.com/network/Xbox/users/me/conversations/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})?maxItems=100`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Messages;"
  },
  {
    "path": "src/webApi/providers/people.js",
    "content": "import axios from 'axios';\n\nclass People {\n    constructor(authorizationHeaders) {\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '3';\n        \n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getFriends() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const params = [\n                    'preferredcolor',\n                    'detail',\n                    'multiplayersummary',\n                    'presencedetail',\n                ]\n\n                const url = `https://peoplehub.xboxlive.com/users/me/people/social/decoration/${params.join(',')}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    recentPlayers() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://peoplehub.xboxlive.com/users/me/people/recentplayers`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default People;"
  },
  {
    "path": "src/webApi/providers/pins.js",
    "content": "import axios from 'axios';\n\nclass Pins {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        headers['Content-Type'] = 'application/json';\n       \n         //create axios instance\n         this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getPins(list = 'XBLPins') {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://eplists.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/lists/PINS/${list}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getSaveForLater() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://eplists.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/lists/PINS/SaveForLater`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Pins;"
  },
  {
    "path": "src/webApi/providers/screenshots.js",
    "content": "import QueryString from 'querystring';\nimport axios from 'axios';\n\nclass Catalog {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '5';\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getUserScreenshots() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://screenshotsmetadata.xboxlive.com/users/me/screenshot`;\n                const response = await this.axiosInstance(url);\n                resolve(response,data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n\n    }\n\n    getCommunityScreenshotsByTitleId(titleId) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://screenshotsmetadata.xboxlive.com/public/titles/${titleId}/screenshots?qualifier=created&maxItems=10`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getScreenshotsByXuid(titleId, skipItems, maxItems) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const params = {\n                    skipitems: skipItems || 0,\n                    maxitems: maxItems || 25,\n                }\n\n                if (titleId !== undefined) {\n                    params.titleid = titleId\n                }\n\n                const queryParams = QueryString.stringify(params);\n                const url = `https://screenshotsmetadata.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/screenshots?${queryParams}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Catalog;"
  },
  {
    "path": "src/webApi/providers/social.js",
    "content": "import axios from 'axios';\n\nclass Social {\n    constructor(authorizationHeaders) {\n        const headers = authorizationHeaders;\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getFriends() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://social.xboxlive.com/users/me/summary`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default Social;"
  },
  {
    "path": "src/webApi/providers/titlehub.js",
    "content": "import axios from 'axios';\n\nclass TitleHub {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getTitleHistory() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const params = [\n                    'achievement',\n                    'image',\n                    'scid',\n                ]\n\n                const url = `https://titlehub.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/titles/titlehistory/decoration/${params.join(',')}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n    getTitleId(titleId) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const params = [\n                    'achievement',\n                    'image',\n                    'detail',\n                    'scid',\n                    'alternateTitleId'\n                ]\n\n                const url = `https://titlehub.xboxlive.com/users/xuid(${this.tokens.xsts.DisplayClaims.xui[0].xid})/titles/titleid(${titleId})/decoration/${params.join(',')}`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default TitleHub;"
  },
  {
    "path": "src/webApi/providers/userpresence.js",
    "content": "import axios from 'axios';\n\nclass UserPresence {\n    constructor(authorizationHeaders) {\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '3';\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getCurrentUser() {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://userpresence.xboxlive.com/users/me?level=all`;\n                const response = await this.axiosInstance(url);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default UserPresence;"
  },
  {
    "path": "src/webApi/providers/userstats.js",
    "content": "import axios from 'axios';\n\nclass UserStats {\n    constructor(tokens, authorizationHeaders) {\n        this.tokens = tokens;\n        const headers = authorizationHeaders;\n        headers['x-xbl-contract-version'] = '2';\n\n        //create axios instance\n        this.axiosInstance = axios.create({\n            method: 'GET',\n            headers: headers\n        });\n    }\n\n    getUserTitleStats(titleId) {\n        return new Promise(async (resolve, reject) => {\n            try {\n                const url = `https://userstats.xboxlive.com/batch`;\n                const params = `{\"arrangebyfield\":\"xuid\",\"xuids\":[\"${this.tokens.xsts.DisplayClaims.xui[0].xid}\"],\"groups\":[{\"name\":\"Hero\",\"titleId\":\"${titleId}\"}],\"stats\":[{\"name\":\"MinutesPlayed\",\"titleId\":\"${titleId}\"}]}`;\n                const response = await this.httpClient.request('POST', url, this.headers, params);\n                resolve(response.data);\n            } catch (error) {\n                reject(error);\n            };\n        });\n    }\n\n}\nexport default UserStats;\n"
  },
  {
    "path": "src/webApi/xboxwebapi.js",
    "content": "import EventEmitter from 'events';\nimport { v4 as UuIdv4 } from 'uuid';\nimport axios from 'axios';\nimport Authentication from './authentication.js';\nimport ImpulseGenerator from '../impulsegenerator.js';\nimport Functions from '../functions.js';\nimport { WebApi, DefaultInputs } from '../constants.js';\n\nclass XboxWebApi extends EventEmitter {\n    constructor(config, authTokenFile, inputsFile, restFulEnabled, mqttEnabled) {\n        super();\n        this.liveId = config.xboxLiveId;\n        this.getInputsFromDevice = config.inputs?.getFromDevice;\n        this.logWarn = config.log?.warn;\n        this.logError = config.log?.error;\n        this.logDebug = config.log?.debug;\n        this.inputsFile = inputsFile;\n\n        // BŁ9 FIX: store restFul/mqtt flags passed from xboxdevice\n        this.restFulEnabled = restFulEnabled || false;\n        this.mqttEnabled = mqttEnabled || false;\n\n        // Variables\n        this.consoleAuthorized = false;\n        this.rmEnabled = false;\n        this.functions = new Functions();\n\n        const authConfig = {\n            clientId: config.webApi?.clientId,\n            clientSecret: config.webApi?.clientSecret,\n            tokensFile: authTokenFile\n        }\n        this.authentication = new Authentication(authConfig);\n\n        // Impulse generator\n        this.call = false;\n        this.impulseGenerator = new ImpulseGenerator()\n            .on('checkAuthorization', async () => {\n                if (this.call) return;\n                try {\n                    this.call = true;\n                    await this.checkAuthorization();\n                } catch (error) {\n                    if (this.logError) this.emit('error', `Web Api generator error: ${error}`);\n                } finally {\n                    this.call = false;\n                }\n            })\n            .on('state', (state) => {\n                this.emit(state ? 'success' : 'warn', `Web Api monitoring ${state ? 'started' : 'stopped'}`);\n            });\n    }\n\n    async checkAuthorization() {\n        try {\n            const data = await this.authentication.checkAuthorization();\n            if (this.logDebug) this.emit('debug', `Authorization headers: ${JSON.stringify(data.headers, null, 2)}`);\n\n            const authorized = data.tokens?.xsts?.Token?.trim() || false;\n            if (!authorized) {\n                if (this.logWarn) this.emit('warn', `Not authorized`);\n                return false;\n            }\n            this.consoleAuthorized = true;\n\n            // Axios instance with global timeout and retry\n            this.axiosInstance = axios.create({\n                baseURL: WebApi.Url.Xccs,\n                timeout: 5000,\n                headers: {\n                    'Authorization': data.headers,\n                    'Accept-Language': 'en-US',\n                    'x-xbl-contract-version': '4',\n                    'x-xbl-client-name': 'XboxApp',\n                    'x-xbl-client-type': 'UWA',\n                    'x-xbl-client-version': '39.39.22001.0',\n                    'skillplatform': 'RemoteManagement',\n                    'Content-Type': 'application/json'\n                }\n            });\n\n            this.axiosInstance.interceptors.response.use(null, async (error) => {\n                const config = error.config;\n                if (!config || !config.retryCount) config.retryCount = 0;\n                if (config.retryCount < 2 && (error.code === 'ECONNABORTED' || error.response?.status === 429)) {\n                    config.retryCount += 1;\n                    if (this.logDebug) this.emit('debug', `Retry ${config.retryCount} for ${config.url}`);\n                    await new Promise(res => setTimeout(res, 1000));\n                    return this.axiosInstance(config);\n                }\n                return Promise.reject(error);\n            });\n\n            // Check console data\n            const consoleExist = await this.consolesList();\n            if (!consoleExist) return false;\n\n            await this.consoleStatus();\n            await this.installedApps();\n\n            return true;\n        } catch (error) {\n            throw new Error(`Check authorization error: ${error}`);\n        }\n    }\n\n    async consolesList() {\n        try {\n            const { data } = await this.axiosInstance.get('/lists/devices?queryCurrentDevice=false&includeStorageDevices=true');\n            if (this.logDebug) this.emit('debug', `Consoles list data: ${JSON.stringify(data, null, 2)}`);\n\n            const status = data.status?.errorCode === 'OK';\n            // BŁ11 FIX: typo errorMerssage → errorMessage (applies to all methods)\n            const error = data.status?.errorMessage;\n            if (!status) {\n                if (this.logDebug) this.emit('debug', `Console list data error: ${error}`);\n                return false;\n            }\n\n            const console = data.result.find(c => c.id === this.liveId);\n            if (!console) {\n                if (this.logWarn) this.emit('warn', `Console with Live ID ${this.liveId} not found on server`);\n                return false;\n            }\n\n            const obj = {\n                id: console.id,\n                name: console.name,\n                locale: console.locale,\n                region: console.region,\n                consoleType: WebApi.Console.Name[console.consoleType],\n                powerState: WebApi.Console.PowerState[console.powerState],\n                digitalAssistantRemoteControlEnabled: !!console.digitalAssistantRemoteControlEnabled,\n                remoteManagementEnabled: !!console.remoteManagementEnabled,\n                consoleStreamingEnabled: !!console.consoleStreamingEnabled,\n                wirelessWarning: !!console.wirelessWarning,\n                outOfHomeWarning: !!console.outOfHomeWarning,\n                storageDevices: console.storageDevices.map(s => ({\n                    id: s.storageDeviceId,\n                    name: s.storageDeviceName,\n                    isDefault: s.isDefault,\n                    freeSpaceBytes: s.freeSpaceBytes,\n                    totalSpaceBytes: s.totalSpaceBytes,\n                    isGen9Compatible: s.isGen9Compatible\n                }))\n            };\n\n            if (!obj.remoteManagementEnabled && this.logWarn) this.emit('warn', `Console with Live ID ${this.liveId} remote management not enabled`);\n            this.rmEnabled = obj.remoteManagementEnabled;\n\n            if (this.restFulEnabled) this.emit('restFul', 'consoleslist', data);\n            if (this.mqttEnabled) this.emit('mqtt', 'Consoles List', data);\n\n            return true;\n        } catch (error) {\n            throw new Error(`Consoles list error: ${error}`);\n        }\n    }\n\n    async consoleStatus() {\n        try {\n            const url = `/consoles/${this.liveId}`;\n            const { data } = await this.axiosInstance.get(url);\n            if (this.logDebug) this.emit('debug', `Console status data: ${JSON.stringify(data, null, 2)}`);\n\n            const status = {\n                id: data.id,\n                name: data.name,\n                locale: data.locale,\n                region: data.region,\n                consoleType: WebApi.Console.Name[data.consoleType],\n                powerState: WebApi.Console.PowerState[data.powerState],\n                playbackState: data.playbackState,\n                loginState: data.loginState,\n                focusAppAumid: data.focusAppAumid,\n                isTvConfigured: !!data.isTvConfigured,\n                digitalAssistantRemoteControlEnabled: !!data.digitalAssistantRemoteControlEnabled,\n                consoleStreamingEnabled: !!data.consoleStreamingEnabled,\n                remoteManagementEnabled: !!data.remoteManagementEnabled,\n                // BŁ11 FIX: typo errorMerssage → errorMessage\n                status: data.status?.errorCode === 'OK',\n                error: data.status?.errorMessage\n            };\n\n            if (!status.status) {\n                if (this.logDebug) this.emit('debug', `Console status error: ${status.error}`);\n                return;\n            }\n\n            this.emit('consoleStatus', status);\n\n            if (this.restFulEnabled) this.emit('restFul', 'status', data);\n            if (this.mqttEnabled) this.emit('mqtt', 'Status', data);\n\n            return true;\n        } catch (error) {\n            throw new Error(`Console status error: ${error}`);\n        }\n    }\n\n    async installedApps() {\n        if (!this.getInputsFromDevice) return true;\n\n        try {\n            const url = `/lists/installedApps?deviceId=${this.liveId}`;\n            const { data } = await this.axiosInstance.get(url);\n            if (this.logDebug) this.emit('debug', `Installed apps data: ${JSON.stringify(data, null, 2)}`);\n\n            const status = data.status?.errorCode === 'OK';\n            // BŁ11 FIX: typo errorMerssage → errorMessage\n            const error = data.status?.errorMessage;\n            if (!status) {\n                if (this.logDebug) this.emit('debug', `Installed apps data error: ${error}`);\n                return false;\n            }\n\n            const apps = data.result.filter(a => a.name && a.aumid).map(a => ({\n                name: a.name,\n                oneStoreProductId: a.oneStoreProductId,\n                reference: a.aumid,\n                titleId: a.titleId,\n                isGame: a.isGame,\n                contentType: a.contentType,\n                mode: 0,\n            }));\n\n            if (this.restFulEnabled) this.emit('restFul', 'apps', data);\n            if (this.mqttEnabled) this.emit('mqtt', 'Apps', data);\n\n            const inputs = [...DefaultInputs, ...apps];\n            await this.functions.saveData(this.inputsFile, inputs);\n            this.emit('installedApps', inputs, false);\n\n            return true;\n        } catch (error) {\n            throw new Error(`Installed apps error: ${error}`);\n        }\n    }\n\n    async mediaState(tokens) {\n        try {\n            const url = `/users/xuid(${tokens.xsts.DisplayClaims.xui[0].xid})/devices/${this.liveId}/media`;\n            const { data } = await this.axiosInstance.get(url);\n            if (this.logDebug) this.emit('debug', `Media state data: ${JSON.stringify(data, null, 2)}`);\n\n            const status = data.status?.errorCode === 'OK';\n            // BŁ11 FIX: typo errorMerssage → errorMessage\n            const error = data.status?.errorMessage;\n            if (!status) {\n                if (this.logDebug) this.emit('debug', `Media state data error: ${error}`);\n                return false;\n            }\n\n            const state = {\n                state: data.state,\n                title: data.title,\n                artist: data.artist,\n                album: data.album,\n                position: data.position,\n                duration: data.duration,\n                canSeek: !!data.canSeek,\n                volume: data.volume,\n                muted: !!data.muted,\n            };\n\n            this.emit('mediaState', state);\n\n            if (this.restFulEnabled) this.emit('restFul', 'mediastate', data);\n            if (this.mqttEnabled) this.emit('mqtt', 'Media State', data);\n\n            return true;\n        } catch (error) {\n            throw new Error(`Media state error: ${error}`);\n        }\n    }\n\n    async send(commandType, command, payload) {\n        if (!this.consoleAuthorized || !this.rmEnabled) {\n            if (this.logWarn) this.emit('warn', `Not authorized or remote management not enabled`);\n            return;\n        }\n\n        const postParams = {\n            destination: 'Xbox',\n            type: commandType,\n            command,\n            sessionId: UuIdv4(),\n            sourceId: 'com.microsoft.smartglass',\n            parameters: payload ?? [],\n            linkedXboxId: this.liveId\n        };\n\n        try {\n            const response = await this.axiosInstance.post('/commands', postParams);\n            if (this.logDebug) this.emit('debug', `Command ${command} result: ${JSON.stringify(response.data)}`);\n            return true;\n        } catch (error) {\n            await new Promise(resolve => setTimeout(resolve, 1000));\n            if (command === 'WakeUp') this.emit('stateChanged', false);\n            if (command === 'TurnOff') this.emit('stateChanged', true);\n            throw new Error(`Failed to send command: type=${commandType}, command=${command}, error=${error.message}`);\n        }\n    }\n\n    // Media / shell helpers\n    async next() { return this.send('Media', 'Next'); }\n    async previous() { return this.send('Media', 'Previous'); }\n    async pause() { return this.send('Media', 'Pause'); }\n    async play() { return this.send('Media', 'Play'); }\n    async goBack() { return this.send('Shell', 'GoBack'); }\n}\n\nexport default XboxWebApi;"
  },
  {
    "path": "src/xboxdevice.js",
    "content": "import EventEmitter from 'events';\nimport RestFul from './restful.js';\nimport Mqtt from './mqtt.js';\nimport XboxWebApi from './webApi/xboxwebapi.js';\nimport XboxLocalApi from './localApi/xboxlocalapi.js';\nimport Functions from './functions.js';\nimport { DefaultInputs, WebApi } from './constants.js';\n\nlet Accessory, Characteristic, Service, Categories, Encode, AccessoryUUID;\n\nclass XboxDevice extends EventEmitter {\n    constructor(api, device, authTokenFile, devInfoFile, inputsFile, inputsNamesFile, inputsTargetVisibilityFile) {\n        super();\n\n        Accessory = api.platformAccessory;\n        Characteristic = api.hap.Characteristic;\n        Service = api.hap.Service;\n        Categories = api.hap.Categories;\n        Encode = api.hap.encode;\n        AccessoryUUID = api.hap.uuid;\n\n        //device configuration\n        this.device = device;\n        this.name = device.name;\n        this.liveId = device.xboxLiveId;\n        this.displayType = device.displayType;\n        this.webApiControl = device.webApi?.enable || false;\n        this.getInputsFromDevice = device.webApi?.enable ? device.inputs?.getFromDevice : false;\n        this.filterGames = device.inputs?.filterGames || false;\n        this.filterApps = device.inputs?.filterApps || false;\n        this.filterSystemApps = device.inputs?.filterSystemApps || false;\n        this.filterDlc = device.inputs?.filterDlc || false;\n        this.inputsDisplayOrder = device.inputs?.displayOrder || 0;\n        this.inputs = (device.inputs?.data || []).filter(input => input.name && input.reference);\n        this.buttons = (device.buttons ?? []).filter(button => (button.displayType ?? 0) > 0);\n        this.sensors = Array.isArray(device.sensors) ? (device.sensors ?? []).filter(sensor => (sensor.displayType ?? 0) > 0 && (sensor.mode ?? -1) >= 0) : [];\n        this.volumeControl = device.volume?.displayType || 0;\n        this.volumeControlName = device.volume?.name || 'Volume';\n        this.volumeControlNamePrefix = device.volume?.namePrefix || false;\n        this.infoButtonCommand = device.infoButtonCommand || 'nexus';\n        this.logInfo = device.log?.info || false;\n        this.logWarn = device.log?.warn || false;\n        this.logDebug = device.log?.debug || false;\n        this.authTokenFile = authTokenFile;\n        this.devInfoFile = devInfoFile;\n        this.inputsFile = inputsFile;\n        this.inputsNamesFile = inputsNamesFile;\n        this.inputsTargetVisibilityFile = inputsTargetVisibilityFile;\n\n        //external integrations\n        this.restFul = device.restFul ?? {};\n        this.restFulConnected = false;\n        this.mqtt = device.mqtt ?? {};\n        this.mqttConnected = false;\n        this.functions = new Functions();\n\n        //sensors\n        for (const sensor of this.sensors) {\n            sensor.serviceType = ['', Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];\n            sensor.characteristicType = ['', Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];\n            sensor.state = false;\n        }\n\n        //buttons\n        for (const button of this.buttons) {\n            button.reference = [button.mediaCommand, button.gamePadCommand, button.tvRemoteCommand, button.consoleControlCommand, button.gameAppControlCommand][button.mode];\n            button.serviceType = ['', Service.Outlet, Service.Switch][button.displayType];\n            button.state = false;\n        }\n\n        //variable\n        this.modelName = 'Xbox';\n        this.inputIdentifier = 1;\n        this.power = false;\n        this.volume = 0;\n        this.mute = false;\n        this.playState = false;\n        this.mediaState = 2;\n        this.reference = '';\n        this.screenSaver = false;\n        this.consoleAuthorized = false;\n    }\n\n    async setOverExternalIntegration(integration, key, value) {\n        if (!this.consoleAuthorized && this.logWarn) {\n            this.emit('warn', `Set over external integration not possible, web api not enabled`);\n            return;\n        }\n\n        try {\n            let set = false;\n            switch (key) {\n                case 'Power':\n                    switch (value) {\n                        case true: //on\n                            set = await this.xboxWebApi.send('Power', 'WakeUp');\n                            break;\n                        case false: //off\n                            set = await this.xboxWebApi.send('Power', 'TurnOff');\n                            break;\n                    }\n                    break;\n                case 'App':\n                    const payload = [{ 'oneStoreProductId': value }];\n                    set = await this.xboxWebApi.send('Shell', 'ActivateApplicationWithOneStoreProductId', payload);\n                    break;\n                case 'Volume':\n                    switch (value) {\n                        case 'up':\n                            set = await this.xboxWebApi.send('Volume', 'Up');\n                            break;\n                        case 'down':\n                            set = await this.xboxWebApi.send('Volume', 'Down');\n                            break;\n                    }\n                    break;\n                case 'Mute':\n                    switch (value) {\n                        case true:\n                            set = await this.xboxWebApi.send('Audio', 'Mute');\n                            break;\n                        case false:\n                            set = await this.xboxWebApi.send('Audio', 'Unmute');\n                            break;\n                    }\n                    break;\n                case 'RcControl':\n                    set = await this.xboxWebApi.send('Shell', 'InjectKey', [{ 'keyType': value }]);\n                    break;\n                default:\n                    if (this.logWarn) this.emit('warn', `${integration}, received key: ${key}, value: ${value}`);\n                    break;\n            };\n            return set;\n        } catch (error) {\n            throw new Error(`${integration} set key: ${key}, value: ${value}, error: ${error}`);\n        }\n    }\n\n    async externalIntegrations() {\n        //RESTFul server\n        const restFulEnabled = this.restFul.enable || false;\n        if (restFulEnabled) {\n            try {\n                this.restFul1 = new RestFul({\n                    port: this.restFul.port || 3000,\n                    logWarn: this.logWarn,\n                    logDebug: this.logDebug\n                })\n                    .on('connected', (message) => {\n                        this.emit('success', message);\n                        this.restFulConnected = true;\n                    })\n                    .on('set', async (key, value) => {\n                        try {\n                            await this.setOverExternalIntegration('RESTFul', key, value);\n                        } catch (error) {\n                            if (this.logWarn) this.emit('warn', `RESTFul set error: ${error}`);\n                        }\n                    })\n                    .on('debug', (debug) => this.emit('debug', debug))\n                    .on('warn', (warn) => this.emit('warn', warn))\n                    .on('error', (error) => this.emit('error', error));\n            } catch (error) {\n                if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);\n            }\n        }\n\n        //mqtt client\n        const mqttEnabled = this.mqtt.enable || false;\n        if (mqttEnabled) {\n            try {\n                this.mqtt1 = new Mqtt({\n                    host: this.mqtt.host,\n                    port: this.mqtt.port || 1883,\n                    clientId: this.mqtt.clientId ? `microsoft_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `microsoft_${Math.random().toString(16).slice(3)}`,\n                    prefix: this.mqtt.prefix ? `microsoft/${this.mqtt.prefix}/${this.name}` : `microsoft/${this.name}`,\n                    user: this.mqtt.auth?.user,\n                    passwd: this.mqtt.auth?.passwd,\n                    logWarn: this.logWarn,\n                    logDebug: this.logDebug\n                })\n                    .on('connected', (message) => {\n                        this.emit('success', message);\n                        this.mqttConnected = true;\n                    })\n                    .on('subscribed', (message) => {\n                        this.emit('success', message);\n                    })\n                    .on('set', async (key, value) => {\n                        try {\n                            await this.setOverExternalIntegration('MQTT', key, value);\n                        } catch (error) {\n                            if (this.logWarn) this.emit('warn', `MQTT set error: ${error}`);\n                        }\n                    })\n                    .on('debug', (debug) => this.emit('debug', debug))\n                    .on('warn', (warn) => this.emit('warn', warn))\n                    .on('error', (error) => this.emit('error', error));\n            } catch (error) {\n                if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);\n            }\n        };\n\n        return true;\n    }\n\n    async prepareDataForAccessory() {\n        try {\n            //read dev info from file\n            this.savedInfo = await this.functions.readData(this.devInfoFile, true) ?? {};\n            if (this.logDebug) this.emit('debug', `Read saved Info: ${JSON.stringify(this.savedInfo, null, 2)}`);\n\n            //read inputs file\n            this.savedInputs = await this.functions.readData(this.inputsFile, true) ?? [];\n            if (this.logDebug) this.emit('debug', `Read saved Inputs: ${JSON.stringify(this.savedInputs, null, 2)}`);\n\n            //read inputs names from file\n            this.savedInputsNames = await this.functions.readData(this.inputsNamesFile, true) ?? {};\n            if (this.logDebug) this.emit('debug', `Read saved Inputs Names: ${JSON.stringify(this.savedInputsNames, null, 2)}`);\n\n            //read inputs visibility from file\n            this.savedInputsTargetVisibility = await this.functions.readData(this.inputsTargetVisibilityFile, true) ?? {};\n            if (this.logDebug) this.emit('debug', `Read saved Inputs Target Visibility: ${JSON.stringify(this.savedInputsTargetVisibility, null, 2)}`);\n\n            return true;\n        } catch (error) {\n            throw new Error(`Prepare data for accessory error: ${error}`);\n        }\n    }\n\n    async startStopImpulseGenerator(state, timers = []) {\n        try {\n            //start web api impulse generator\n            if (this.webApiControl) await this.xboxWebApi.impulseGenerator.state(true, [{ name: 'checkAuthorization', sampling: 900000 }]);\n\n            //start impulse generator\n            await this.xboxLocalApi.impulseGenerator.state(state, timers);\n            return true;\n        } catch (error) {\n            throw new Error(`Impulse generator start error: ${error}`);\n        }\n    }\n\n    async displayOrder() {\n        try {\n            const sortStrategies = {\n                1: (a, b) => a.name.localeCompare(b.name),\n                2: (a, b) => b.name.localeCompare(a.name),\n                3: (a, b) => a.reference.localeCompare(b.reference),\n                4: (a, b) => b.reference.localeCompare(a.reference),\n            };\n\n            const sortFn = sortStrategies[this.inputsDisplayOrder];\n\n            // Sort only if a valid function exists\n            if (sortFn) {\n                this.inputsServices.sort(sortFn);\n            }\n\n            // Debug\n            if (this.logDebug) {\n                const orderDump = this.inputsServices.map(svc => ({\n                    name: svc.name,\n                    reference: svc.reference,\n                    identifier: svc.identifier,\n                }));\n                this.emit('debug', `Inputs display order:\\n${JSON.stringify(orderDump, null, 2)}`);\n            }\n\n            // Always update DisplayOrder characteristic, even for \"none\"\n            const displayOrder = this.inputsServices.map(svc => svc.identifier);\n            const encodedOrder = Encode(1, displayOrder).toString('base64');\n            this.televisionService.updateCharacteristic(Characteristic.DisplayOrder, encodedOrder);\n\n            return;\n        } catch (error) {\n            throw new Error(`Display order error: ${error}`);\n        }\n    }\n\n    async addRemoveOrUpdateInput(inputs, remove = false) {\n        try {\n            if (!this.inputsServices) return;\n\n            let updated = false;\n\n            for (const input of inputs) {\n                if (this.inputsServices.length >= 85 && !remove) continue;\n\n                // Filter\n                const contentType = input.contentType;\n                const filterGames = this.filterGames && contentType === 'Game';\n                const filterApps = this.filterApps && contentType === 'App';\n                const filterSystemApps = this.filterSystemApps && contentType === 'systemApp';\n                const filterDlc = this.filterDlc && contentType === 'Dlc';\n                if (filterGames || filterApps || filterSystemApps || filterDlc) continue;\n\n                const inputReference = input.reference;\n                const savedName = this.savedInputsNames[inputReference] ?? input.name;\n                const sanitizedName = await this.functions.sanitizeString(savedName);\n                const inputMode = input.mode ?? 0;\n                const inputTitleId = input.titleId;\n                const inputOneStoreProductId = input.oneStoreProductId;\n                const inputVisibility = this.savedInputsTargetVisibility[inputReference] ?? 0;\n\n                if (remove) {\n                    const svc = this.inputsServices.find(s => s.reference === inputReference);\n                    if (svc) {\n                        if (this.logDebug) this.emit('debug', `Removing input: ${input.name}, reference: ${inputReference}`);\n                        this.accessory.removeService(svc);\n                        this.inputsServices = this.inputsServices.filter(s => s.reference !== inputReference);\n                        updated = true;\n                    }\n                    continue;\n                }\n\n                let inputService = this.inputsServices.find(s => s.reference === inputReference);\n                if (inputService) {\n                    const nameChanged = inputService.name !== sanitizedName;\n                    if (nameChanged) {\n                        inputService.name = sanitizedName;\n                        inputService\n                            .updateCharacteristic(Characteristic.Name, sanitizedName)\n                            .updateCharacteristic(Characteristic.ConfiguredName, sanitizedName);\n                        if (this.logDebug) this.emit('debug', `Updated Input: ${input.name}, reference: ${inputReference}`);\n                        updated = true;\n                    }\n                } else {\n                    const identifier = this.inputsServices.length + 1;\n                    inputService = this.accessory.addService(Service.InputSource, sanitizedName, `Input ${inputReference}`);\n                    inputService.identifier = identifier;\n                    inputService.reference = inputReference;\n                    inputService.name = sanitizedName;\n                    inputService.mode = inputMode;\n                    inputService.titleId = inputTitleId;\n                    inputService.oneStoreProductId = inputOneStoreProductId;\n                    inputService.visibility = inputVisibility;\n\n                    inputService\n                        .setCharacteristic(Characteristic.Identifier, identifier)\n                        .setCharacteristic(Characteristic.Name, sanitizedName)\n                        .setCharacteristic(Characteristic.ConfiguredName, sanitizedName)\n                        .setCharacteristic(Characteristic.IsConfigured, 1)\n                        .setCharacteristic(Characteristic.InputSourceType, inputMode)\n                        .setCharacteristic(Characteristic.CurrentVisibilityState, inputVisibility)\n                        .setCharacteristic(Characteristic.TargetVisibilityState, inputVisibility);\n\n                    // ConfiguredName persistence\n                    inputService.getCharacteristic(Characteristic.ConfiguredName)\n                        .onSet(async (value) => {\n                            try {\n                                value = await this.functions.sanitizeString(value);\n                                inputService.name = value;\n                                this.savedInputsNames[inputReference] = value;\n                                await this.functions.saveData(this.inputsNamesFile, this.savedInputsNames);\n                                if (this.logDebug) this.emit('debug', `Saved Input: ${input.name}, reference: ${inputReference}`);\n                                await this.displayOrder();\n                            } catch (error) {\n                                if (this.logWarn) this.emit('warn', `Save Input Name error: ${error}`);\n                            }\n                        });\n\n                    // TargetVisibility persistence\n                    inputService.getCharacteristic(Characteristic.TargetVisibilityState)\n                        .onSet(async (state) => {\n                            try {\n                                inputService.visibility = state;\n                                this.savedInputsTargetVisibility[inputReference] = state;\n                                await this.functions.saveData(this.inputsTargetVisibilityFile, this.savedInputsTargetVisibility);\n                                if (this.logDebug) this.emit('debug', `Saved Input: ${input.name}, reference: ${inputReference}, target visibility: ${state ? 'HIDDEN' : 'SHOWN'}`);\n                            } catch (error) {\n                                if (this.logWarn) this.emit('warn', `Save Target Visibility error: ${error}`);\n                            }\n                        });\n\n                    this.inputsServices.push(inputService);\n                    this.televisionService.addLinkedService(inputService);\n\n                    if (this.logDebug) this.emit('debug', `Added Input: ${input.name}, reference: ${inputReference}`);\n                    updated = true;\n                }\n            }\n\n            // Only one time run\n            if (updated) await this.displayOrder();\n\n            return true;\n        } catch (error) {\n            throw new Error(`Add/Remove/Update input error: ${error}`);\n        }\n    }\n\n    async setInput(input) {\n        try {\n            const { oneStoreProductId, name, reference } = input;\n            let channelName;\n            let command;\n            let payload;\n            switch (oneStoreProductId) {\n                case 'Dashboard': case 'Settings': case 'SettingsTv': case 'Accessory': case 'Screensaver': case 'NetworkTroubleshooter': case 'MicrosoftStore':\n                    channelName = 'Shell';\n                    command = 'GoHome';\n                    break;\n                case 'Television':\n                    channelName = 'TV';\n                    command = 'ShowGuide';\n                    break;\n                case 'XboxGuide':\n                    channelName = 'Shell';\n                    command = 'ShowGuideTab';\n                    payload = [{ 'tabName': 'Guide' }];\n                    break;\n                default:\n                    channelName = 'Shell';\n                    command = 'ActivateApplicationWithOneStoreProductId';\n                    payload = [{ 'oneStoreProductId': oneStoreProductId }];\n                    break;\n            }\n\n            await this.xboxWebApi.send(channelName, command, payload);\n            if (this.logInfo) this.emit('info', `Set game/app: ${name}, reference: ${reference}, product id: ${oneStoreProductId}`);\n\n            return;\n        } catch (error) {\n            if (this.logWarn) this.emit('warn', `Set game/app error: ${error}`);\n        }\n    }\n\n    //Prepare accessory\n    async prepareAccessory() {\n        try {\n            //Accessory\n            if (this.logDebug) this.emit('debug', `Prepare accessory`);\n            const accessoryName = this.name;\n            const accessoryUUID = AccessoryUUID.generate(this.liveId);\n            const accessoryCategory = [Categories.OTHER, Categories.TELEVISION, Categories.TV_SET_TOP_BOX, Categories.TV_STREAMING_STICK, Categories.AUDIO_RECEIVER][this.displayType];\n            const accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory);\n            this.accessory = accessory;\n\n            //Prepare information service\n            this.informationService = accessory.getService(Service.AccessoryInformation)\n                .setCharacteristic(Characteristic.Manufacturer, this.savedInfo.manufacturer)\n                .setCharacteristic(Characteristic.Model, this.savedInfo.modelName)\n                .setCharacteristic(Characteristic.SerialNumber, this.savedInfo.serialNumber ?? this.liveId)\n                .setCharacteristic(Characteristic.FirmwareRevision, this.savedInfo.firmwareRevision)\n                .setCharacteristic(Characteristic.ConfiguredName, accessoryName);\n\n            //Prepare television service\n            if (this.logDebug) this.emit('debug', `Prepare television service`);\n            this.televisionService = accessory.addService(Service.Television, `${accessoryName} Television`, 'Television');\n            this.televisionService.setCharacteristic(Characteristic.ConfiguredName, accessoryName);\n            this.televisionService.setCharacteristic(Characteristic.SleepDiscoveryMode, 1);\n\n            this.televisionService.getCharacteristic(Characteristic.Active)\n                .onGet(async () => {\n                    const state = this.power;\n                    return state;\n                })\n                .onSet(async (state) => {\n                    if (!!state === this.power) return;\n\n                    if (!this.consoleAuthorized && this.logWarn) {\n                        this.emit('warn', `Set power not possible, web api not enabled`);\n                        return;\n                    }\n\n                    try {\n                        await this.xboxWebApi.send('Power', state ? 'WakeUp' : 'TurnOff');\n                        if (this.logInfo) this.emit('info', `Set Power: ${state ? 'ON' : 'OFF'}`);\n                        await new Promise(resolve => setTimeout(resolve, 2000));\n                    } catch (error) {\n                        if (this.logWarn) this.emit('warn', `Set Power, error: ${error}`);\n                    }\n                });\n\n            this.televisionService.getCharacteristic(Characteristic.ActiveIdentifier)\n                .onGet(async () => {\n                    const inputIdentifier = this.inputIdentifier;\n                    return inputIdentifier;\n                })\n                .onSet(async (activeIdentifier) => {\n                    if (!this.consoleAuthorized && this.logWarn) {\n                        this.emit('warn', `Set game/app not possible, web api not enabled`);\n                        return;\n                    }\n\n                    try {\n                        const input = this.inputsServices.find(i => i.identifier === activeIdentifier);\n                        if (!input) {\n                            if (this.logWarn) this.emit('warn', `Game/App with identifier ${activeIdentifier} not found`);\n                            return;\n                        }\n\n                        if (!this.power) {\n                            if (this.logDebug) this.emit('debug', `Device is off, deferring game/app switch to '${activeIdentifier}'`);\n\n                            (async () => {\n                                for (let attempt = 0; attempt < 20; attempt++) {\n                                    await new Promise(resolve => setTimeout(resolve, 1500));\n\n                                    if (this.power) {\n                                        // if input didn't switch → retry\n                                        if (this.inputIdentifier !== activeIdentifier) {\n                                            if (this.logDebug) this.emit('debug', `Retrying game/app switch (${attempt + 1}/20)`);\n                                            await this.setInput(input);\n                                        } else {\n                                            // success\n                                            this.televisionService.updateCharacteristic(Characteristic.ActiveIdentifier, activeIdentifier);\n                                            if (this.logInfo) this.emit('info', `Game/App set successfully: ${input.name}`);\n                                            return;\n                                        }\n                                    }\n                                }\n\n                                if (this.logWarn) this.emit('warn', `Failed to set game/app after retries: ${input.name}`);\n                            })().catch(err => {\n                                if (this.logWarn) this.emit('warn', `Set game/app retry error: ${err}`);\n                            });\n\n                            return;\n                        }\n\n                        // device is on\n                        await this.setInput(input);\n                        if (this.logInfo) this.emit('info', `Set game/app name: ${input.name}, Reference: ${input.reference}`);\n                    } catch (error) {\n                        if (this.logWarn) this.emit('warn', `Set game/app error: ${JSON.stringify(error, null, 2)}`);\n                    }\n                });\n\n            this.televisionService.getCharacteristic(Characteristic.RemoteKey)\n                .onSet(async (remoteKey) => {\n                    if (!this.consoleAuthorized && this.logWarn) {\n                        this.emit('warn', `Set remote key not possible, web api not enabled`);\n                        return;\n                    }\n\n                    try {\n                        let channelName;\n                        let command;\n\n                        switch (remoteKey) {\n                            case 0: //REWIND\n                                channelName = 'Shell';\n                                command = 'rewind';\n                                break;\n                            case 1: //FAST_FORWARD\n                                channelName = 'Shell';\n                                command = 'fastForward';\n                                break;\n                            case 2: //NEXT_TRACK\n                                channelName = 'Shell';\n                                command = 'nextTrack';\n                                break;\n                            case 3: //PREVIOUS_TRACK\n                                channelName = 'Shell';\n                                command = 'previousTrack';\n                                break;\n                            case 4: //ARROW_UP\n                                channelName = 'Shell';\n                                command = 'up';\n                                break;\n                            case 5: //ARROW_DOWN\n                                channelName = 'Shell';\n                                command = 'down';\n                                break;\n                            case 6: //ARROW_LEFT\n                                channelName = 'Shell';\n                                command = 'left';\n                                break;\n                            case 7: //ARROW_RIGHT\n                                channelName = 'Shell';\n                                command = 'right';\n                                break;\n                            case 8: //SELECT\n                                channelName = 'Shell';\n                                command = 'a';\n                                break;\n                            case 9: //BACK\n                                channelName = 'Shell';\n                                command = 'b';\n                                break;\n                            case 10: //EXIT\n                                channelName = 'Shell';\n                                command = 'nexus';\n                                break;\n                            case 11: //PLAY_PAUSE\n                                channelName = 'Shell';\n                                command = 'playPause';\n                                break;\n                            case 15: //INFORMATION\n                                channelName = 'Shell';\n                                command = this.infoButtonCommand;\n                                break;\n                        }\n\n                        await this.xboxWebApi.send(channelName, 'InjectKey', [{ 'keyType': command }]);\n                        if (this.logInfo) this.emit('info', `Remote Key: ${command}`);\n                    } catch (error) {\n                        if (this.logWarn) this.emit('warn', `Set Remote Key error: ${JSON.stringify(error, null, 2)}`);\n                    }\n                });\n\n            this.televisionService.getCharacteristic(Characteristic.CurrentMediaState)\n                .onGet(async () => {\n                    //apple: 0 - PLAY, 1 - PAUSE, 2 - STOP, 3 - LOADING, 4 - INTERRUPTED\n                    //xbox:  0 - STOP, 1 - PLAY,  2 - PAUSE\n                    const value = this.mediaState;\n                    return value;\n                });\n\n            this.televisionService.getCharacteristic(Characteristic.TargetMediaState)\n                .onGet(async () => {\n                    //0 - PLAY, 1 - PAUSE, 2 - STOP\n                    const value = this.mediaState;\n                    return value;\n                })\n                .onSet(async (value) => {\n                    // BŁ2 FIX: removed dead variables (newMediaState, setMediaState which was always false)\n                    try {\n                        if (this.logInfo) this.emit('info', `Set Target Media: ${['PLAY', 'PAUSE', 'STOP', 'LOADING', 'INTERRUPTED'][value]}`);\n                    } catch (error) {\n                        if (this.logWarn) this.emit('warn', `Set Target Media error: ${error}`);\n                    }\n                });\n\n            this.televisionService.getCharacteristic(Characteristic.PowerModeSelection)\n                .onSet(async (powerModeSelection) => {\n                    if (!this.consoleAuthorized && this.logWarn) {\n                        this.emit('warn', `Set power mode selection not possible, web api not enabled`);\n                        return;\n                    }\n\n                    try {\n                        switch (powerModeSelection) {\n                            case 0: //SHOW\n                                await this.xboxWebApi.send('Shell', 'InjectKey', [{ 'keyType': 'nexus' }]);\n                                break;\n                            case 1: //HIDE\n                                await this.xboxWebApi.send('Shell', 'InjectKey', [{ 'keyType': 'b' }]);\n                                break;\n                        };\n                        if (this.logInfo) this.emit('info', `Set Power Mode Selection: ${powerModeSelection === 0 ? 'SHOW' : 'HIDE'}`);\n                    } catch (error) {\n                        if (this.logWarn) this.emit('warn', `Set Power Mode Selection error: ${error}`);\n                    }\n                });\n\n            //prepare inputs service\n            if (this.logDebug) this.emit('debug', `Prepare inputs service`);\n            this.inputsServices = [];\n            await this.addRemoveOrUpdateInput(this.savedInputs, false);\n\n            //Prepare volume service\n            if (this.volumeControl > 0) {\n                const volumeServiceName = this.volumeControlNamePrefix ? `${accessoryName} ${this.volumeControlName}` : this.volumeControlName;\n\n                switch (this.volumeControl) {\n                    case 1: //lightbulb\n                        if (this.logDebug) this.emit('debug', `Prepare volume service lightbulb`);\n                        this.volumeServiceLightbulb = accessory.addService(Service.Lightbulb, volumeServiceName, 'Lightbulb Speaker');\n                        this.volumeServiceLightbulb.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceLightbulb.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceLightbulb.getCharacteristic(Characteristic.Brightness)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Volume: ${value}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceLightbulb.getCharacteristic(Characteristic.On)\n                            .onGet(async () => {\n                                const state = this.power ? !this.mute : false;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Mute: ${!state ? 'ON' : 'OFF'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Mute error: ${error}`);\n                                }\n                            });\n                        break;\n                    case 2: //fan\n                        if (this.logDebug) this.emit('debug', `Prepare volume service fan`);\n                        this.volumeServiceFan = accessory.addService(Service.Fan, volumeServiceName, 'Fan Speaker');\n                        this.volumeServiceFan.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceFan.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceFan.getCharacteristic(Characteristic.RotationSpeed)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Volume: ${value}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceFan.getCharacteristic(Characteristic.On)\n                            .onGet(async () => {\n                                const state = this.power ? !this.mute : false;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Mute: ${!state ? 'ON' : 'OFF'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Mute error: ${error}`);\n                                }\n                            });\n                        break;\n                    case 3: // tv speaker\n                        if (this.logDebug) this.emit('debug', `Prepare television speaker service`);\n                        this.volumeServiceTvSpeaker = accessory.addService(Service.TelevisionSpeaker, volumeServiceName, 'TV Speaker');\n                        this.volumeServiceTvSpeaker.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Active)\n                            .onGet(async () => {\n                                const state = this.power;\n                                return state;\n                            })\n                            .onSet(async (state) => { });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeControlType)\n                            .onGet(async () => {\n                                const state = 3;\n                                return state;\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeSelector)\n                            .onSet(async (volumeSelector) => {\n                                if (!this.consoleAuthorized && this.logWarn) {\n                                    this.emit('warn', `Set volume selector not possible, web api not enabled`);\n                                    return;\n                                }\n\n                                try {\n                                    switch (volumeSelector) {\n                                        case 0: //Up\n                                            await this.xboxWebApi.send('Volume', 'Up');\n                                            break;\n                                        case 1: //Down\n                                            await this.xboxWebApi.send('Volume', 'Down');\n                                            break;\n                                    }\n                                    if (this.logInfo) this.emit('info', `Set Volume Selector: ${volumeSelector ? 'Down' : 'UP'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume Selector error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Volume)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Volume: ${value}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Mute)\n                            .onGet(async () => {\n                                const state = this.mute;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Mute: ${state ? 'ON' : 'OFF'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Mute error: ${error}`);\n                                }\n                            });\n                        break;\n                    case 4: // tv speaker + lightbulb\n                        if (this.logDebug) this.emit('debug', `Prepare television speaker service`);\n                        this.volumeServiceTvSpeaker = accessory.addService(Service.TelevisionSpeaker, volumeServiceName, 'TV Speaker');\n                        this.volumeServiceTvSpeaker.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Active)\n                            .onGet(async () => {\n                                const state = this.power;\n                                return state;\n                            })\n                            .onSet(async (state) => { });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeControlType)\n                            .onGet(async () => {\n                                const state = 3;\n                                return state;\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeSelector)\n                            .onSet(async (volumeSelector) => {\n                                if (!this.consoleAuthorized && this.logWarn) {\n                                    this.emit('warn', `Set volume selector not possible, web api not enabled`);\n                                    return;\n                                }\n\n                                try {\n                                    switch (volumeSelector) {\n                                        case 0: //Up\n                                            await this.xboxWebApi.send('Volume', 'Up');\n                                            break;\n                                        case 1: //Down\n                                            await this.xboxWebApi.send('Volume', 'Down');\n                                            break;\n                                    }\n                                    if (this.logInfo) this.emit('info', `Set Volume Selector: ${volumeSelector ? 'Down' : 'UP'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume Selector error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Volume)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Volume: ${value}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Mute)\n                            .onGet(async () => {\n                                const state = this.mute;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Mute: ${state ? 'ON' : 'OFF'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Mute error: ${error}`);\n                                }\n                            });\n\n                        // lightbulb\n                        if (this.logDebug) this.emit('debug', `Prepare volume service lightbulb`);\n                        this.volumeServiceLightbulb = accessory.addService(Service.Lightbulb, volumeServiceName, 'Lightbulb Speaker');\n                        this.volumeServiceLightbulb.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceLightbulb.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceLightbulb.getCharacteristic(Characteristic.Brightness)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.Volume, value);\n                            });\n                        this.volumeServiceLightbulb.getCharacteristic(Characteristic.On)\n                            .onGet(async () => {\n                                const state = this.power ? !this.mute : false;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.Mute, !state);\n                            });\n                        break;\n                    case 5: // tv speaker + fan\n                        if (this.logDebug) this.emit('debug', `Prepare television speaker service`);\n                        this.volumeServiceTvSpeaker = accessory.addService(Service.TelevisionSpeaker, volumeServiceName, 'TV Speaker');\n                        this.volumeServiceTvSpeaker.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Active)\n                            .onGet(async () => {\n                                const state = this.power;\n                                return state;\n                            })\n                            .onSet(async (state) => { });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeControlType)\n                            .onGet(async () => {\n                                const state = 3;\n                                return state;\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.VolumeSelector)\n                            .onSet(async (volumeSelector) => {\n                                if (!this.consoleAuthorized && this.logWarn) {\n                                    this.emit('warn', `Set volume selector not possible, web api not enabled`);\n                                    return;\n                                }\n\n                                try {\n                                    switch (volumeSelector) {\n                                        case 0: //Up\n                                            await this.xboxWebApi.send('Volume', 'Up');\n                                            break;\n                                        case 1: //Down\n                                            await this.xboxWebApi.send('Volume', 'Down');\n                                            break;\n                                    }\n                                    if (this.logInfo) this.emit('info', `Set Volume Selector: ${volumeSelector ? 'Down' : 'UP'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume Selector error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Volume)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Volume: ${value}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Volume error: ${error}`);\n                                }\n                            });\n                        this.volumeServiceTvSpeaker.getCharacteristic(Characteristic.Mute)\n                            .onGet(async () => {\n                                const state = this.mute;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                try {\n                                    if (this.logInfo) this.emit('info', `Set Mute: ${!state ? 'ON' : 'OFF'}`);\n                                } catch (error) {\n                                    if (this.logWarn) this.emit('warn', `Set Mute error: ${error}`);\n                                }\n                            });\n\n                        // fan\n                        if (this.logDebug) this.emit('debug', `Prepare volume service fan`);\n                        this.volumeServiceFan = accessory.addService(Service.Fan, volumeServiceName, 'Fan Speaker');\n                        this.volumeServiceFan.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                        this.volumeServiceFan.setCharacteristic(Characteristic.ConfiguredName, volumeServiceName);\n                        this.volumeServiceFan.getCharacteristic(Characteristic.RotationSpeed)\n                            .onGet(async () => {\n                                const volume = this.volume;\n                                return volume;\n                            })\n                            .onSet(async (value) => {\n                                this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.Volume, value);\n                            });\n                        this.volumeServiceFan.getCharacteristic(Characteristic.On)\n                            .onGet(async () => {\n                                const state = this.power ? !this.mute : false;\n                                return state;\n                            })\n                            .onSet(async (state) => {\n                                this.volumeServiceTvSpeaker.setCharacteristic(Characteristic.Mute, !state);\n                            });\n                        break;\n                }\n            }\n\n            //prepare sensor service\n            const possibleSensorCount = 99 - this.accessory.services.length;\n            const maxSensorCount = this.sensors.length >= possibleSensorCount ? possibleSensorCount : this.sensors.length;\n            if (maxSensorCount > 0) {\n                this.sensorServices = [];\n                if (this.logDebug) this.emit('debug', `Prepare sensors services`);\n                for (let i = 0; i < maxSensorCount; i++) {\n                    const sensor = this.sensors[i];\n                    const name = sensor.name || `Sensor ${i}`;\n                    const namePrefix = sensor.namePrefix;\n                    const serviceType = sensor.serviceType;\n                    const characteristicType = sensor.characteristicType;\n\n                    const serviceName = namePrefix ? `${accessoryName} ${name}` : name;\n                    const sensorService = new serviceType(serviceName, `Sensor ${i}`);\n                    sensorService.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                    sensorService.setCharacteristic(Characteristic.ConfiguredName, serviceName);\n                    sensorService.getCharacteristic(characteristicType)\n                        .onGet(async () => {\n                            const state = sensor.state;\n                            return state;\n                        });\n                    this.sensorServices.push(sensorService);\n                    accessory.addService(sensorService);\n                }\n            }\n\n            //Prepare buttons services\n            const possibleButtonsCount = 99 - this.accessory.services.length;\n            const maxButtonsCount = this.buttons.length >= possibleButtonsCount ? possibleButtonsCount : this.buttons.length;\n            if (maxButtonsCount > 0) {\n                if (this.logDebug) this.emit('debug', `Prepare buttons services`);\n\n                this.buttonsServices = [];\n                for (let i = 0; i < maxButtonsCount; i++) {\n                    const button = this.buttons[i];\n                    const buttonName = button.name || `Button ${i}`;\n                    const buttonMode = button.mode;\n                    const buttonCommand = button.reference;\n                    const namePrefix = button.namePrefix;\n                    const serviceType = button.serviceType;\n\n                    const serviceName = namePrefix ? `${accessoryName} ${buttonName}` : buttonName;\n                    const buttonService = new serviceType(serviceName, `Button ${i}`);\n                    buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName);\n                    buttonService.setCharacteristic(Characteristic.ConfiguredName, serviceName);\n                    buttonService.getCharacteristic(Characteristic.On)\n                        .onGet(async () => {\n                            const state = button.state;\n                            return state;\n                        })\n                        .onSet(async (state) => {\n                            if (!this.consoleAuthorized && this.logWarn) {\n                                this.emit('warn', `Set button not possible, web api not enabled`);\n                                return;\n                            }\n\n                            if (!this.power) {\n                                if (this.logWarn) this.emit('warn', `console is off`);\n                                return;\n                            }\n\n                            try {\n                                switch (buttonMode) {\n                                    case 0: case 1: case 2:\n                                        if (state) await this.xboxWebApi.send('Shell', 'InjectKey', [{ 'keyType': buttonCommand }]);\n                                        break;\n                                    case 3:\n                                        switch (buttonCommand) {\n                                            case 'reboot':\n                                                if (state) await this.xboxWebApi.send('Power', 'Reboot');\n                                                break;\n                                            case 'recordGameDvr':\n                                                if (state) await this.xboxLocalApi.recordGameDvr();\n                                                break;\n                                        }\n                                        break;\n                                    case 4:\n                                        switch (buttonCommand) {\n                                            case 'Dashboard': case 'Settings': case 'SettingsTv': case 'Accessory': case 'Screensaver': case 'NetworkTroubleshooter': case 'MicrosoftStore':\n                                                if (state) await this.xboxWebApi.send('Shell', 'GoHome');\n                                                break;\n                                            case 'Television':\n                                                if (state) await this.xboxWebApi.send('TV', 'ShowGuide');\n                                                break;\n                                            case 'XboxGuide':\n                                                if (state) await this.xboxWebApi.send('Shell', 'ShowGuideTab', [{ 'tabName': 'Guide' }]);\n                                                break;\n                                            default:\n                                                if (state) await this.xboxWebApi.send('Shell', 'ActivateApplicationWithOneStoreProductId', [{ 'oneStoreProductId': buttonCommand }]);\n                                                break;\n                                        }\n                                        break;\n                                }\n                            } catch (error) {\n                                if (this.logWarn) this.emit('warn', `Set Button error: ${error}`);\n                            }\n                        });\n                    this.buttonsServices.push(buttonService);\n                    accessory.addService(buttonService);\n                }\n            }\n\n            return accessory;\n        } catch (error) {\n            throw new Error(error)\n        };\n    }\n\n    //start\n    async start() {\n        try {\n            // Save inputs\n            if (!this.getInputsFromDevice) {\n                const inputs = [...DefaultInputs, ...this.inputs];\n                await this.functions.saveData(this.inputsFile, inputs);\n            }\n\n            // BŁ3/BŁ4 FIX: resolve restFulEnabled/mqttEnabled flags up front,\n            // never pass undefined channelsFile to constructors\n            const restFulEnabled = this.restFul.enable || false;\n            const mqttEnabled = this.mqtt.enable || false;\n\n            // Web api client\n            if (this.webApiControl) {\n                try {\n                    this.xboxWebApi = new XboxWebApi(this.device, this.authTokenFile, this.inputsFile, restFulEnabled, mqttEnabled)\n                        .on('consoleStatus', (status) => {\n                            this.modelName = status.consoleType;\n                            this.mediaState = WebApi.Console.PlaybackStateHomeKit[status.playbackState];\n                            this.playState = this.mediaState === 0;\n\n                            this.informationService?.setCharacteristic(Characteristic.Model, this.modelName);\n                            this.televisionService?.updateCharacteristic(Characteristic.CurrentMediaState, this.mediaState);\n                        })\n                        .on('installedApps', async (inputs, remove) => {\n                            await this.addRemoveOrUpdateInput(inputs, remove);\n                        })\n                        .on('stateChanged', (power) => {\n                            this.power = power;\n\n                            this.televisionService?.updateCharacteristic(Characteristic.Active, power);\n\n                            if (this.logInfo) {\n                                this.emit('info', `Power: ${power ? 'ON' : 'OFF'}`);\n                            }\n                        })\n                        .on('success', (success) => this.emit('success', success))\n                        .on('info', (info) => this.emit('info', info))\n                        .on('debug', (debug) => this.emit('debug', debug))\n                        .on('warn', (warn) => this.emit('warn', warn))\n                        .on('error', (error) => this.emit('error', error))\n                        .on('restFul', (path, data) => {\n                            if (this.restFulConnected) this.restFul1.update(path, data);\n                        })\n                        .on('mqtt', (topic, message) => {\n                            if (this.mqttConnected) this.mqtt1.emit('publish', topic, message);\n                        });\n\n                    // Check authorization\n                    this.consoleAuthorized = await this.xboxWebApi.checkAuthorization();\n                } catch (error) {\n                    this.emit('error', `Start web api error: ${error}`);\n                }\n            }\n\n            // BŁ4 FIX: pass restFulEnabled/mqttEnabled booleans, not channelsFile string\n            this.xboxLocalApi = new XboxLocalApi(this.device, this.authTokenFile, this.devInfoFile, restFulEnabled, mqttEnabled)\n                .on('deviceInfo', async (info) => {\n                    this.emit('devInfo', `-------- ${this.name} --------`);\n                    this.emit('devInfo', `Manufacturer:  Microsoft`);\n                    this.emit('devInfo', `Model: ${this.modelName}`);\n                    this.emit('devInfo', `Serialnr: ${this.liveId}`);\n                    this.emit('devInfo', `Firmware: ${info.firmwareRevision}`);\n                    this.emit('devInfo', `Locale: ${info.locale}`);\n                    this.emit('devInfo', `----------------------------------`);\n\n                    const obj = {\n                        manufacturer: 'Microsoft',\n                        modelName: this.modelName,\n                        serialNumber: this.liveId,\n                        firmwareRevision: info.firmwareRevision,\n                        locale: info.locale\n                    };\n\n                    await this.functions.saveData(this.devInfoFile, obj);\n\n                    this.informationService\n                        ?.setCharacteristic(Characteristic.Model, this.modelName)\n                        .setCharacteristic(Characteristic.FirmwareRevision, info.firmwareRevision);\n                })\n                .on('stateChanged', async (power, titleId, reference, volume, mute, playState) => {\n                    const input = this.inputsServices?.find(input => input.reference === reference || input.titleId === titleId) ?? false;\n                    const inputIdentifier = input ? input.identifier : this.inputIdentifier;\n\n                    // Update characteristics\n                    this.televisionService\n                        ?.updateCharacteristic(Characteristic.Active, power)\n                        .updateCharacteristic(Characteristic.ActiveIdentifier, inputIdentifier);\n\n                    this.volumeServiceTvSpeaker\n                        ?.updateCharacteristic(Characteristic.Active, power)\n                        .updateCharacteristic(Characteristic.Volume, volume)\n                        .updateCharacteristic(Characteristic.Mute, mute);\n\n                    const muteV = this.power ? !mute : false;\n                    this.volumeServiceLightbulb\n                        ?.updateCharacteristic(Characteristic.Brightness, volume)\n                        .updateCharacteristic(Characteristic.On, muteV);\n\n                    this.volumeServiceFan\n                        ?.updateCharacteristic(Characteristic.RotationSpeed, volume)\n                        .updateCharacteristic(Characteristic.On, muteV);\n\n                    // BŁ6 FIX: removed volumeServiceSpeaker update — service was never created\n\n                    // sensors\n                    const screenSaver = (reference === 'Xbox.IdleScreen_8wekyb3d8bbwe!Xbox.IdleScreen.Application');\n\n                    // BŁ5 FIX: unified playState key to 5 in both maps (was 6 in current, 5 in previous)\n                    const currentStateModeMap = {\n                        0: reference,\n                        1: power,\n                        2: volume,\n                        3: mute,\n                        4: screenSaver,\n                        5: playState,\n                    };\n\n                    const previousStateModeMap = {\n                        0: this.reference,\n                        1: this.power,\n                        2: this.volume,\n                        3: this.mute,\n                        4: this.screenSaver,\n                        5: this.playState,\n                    };\n\n                    for (let i = 0; i < this.sensors.length; i++) {\n                        let state = false;\n\n                        const sensor = this.sensors[i];\n                        const currentValue = currentStateModeMap[sensor.mode];\n                        const previousValue = previousStateModeMap[sensor.mode];\n                        const pulse = sensor.pulse;\n                        const reference = sensor.reference;\n                        const level = sensor.level;\n                        const characteristicType = sensor.characteristicType;\n                        const isActiveMode = power;\n\n                        if (pulse && currentValue !== previousValue) {\n                            for (let step = 0; step < 2; step++) {\n                                state = isActiveMode ? (step === 0) : false;\n                                sensor.state = state;\n                                this.sensorServices?.[i]?.updateCharacteristic(characteristicType, state);\n                                await new Promise(resolve => setTimeout(resolve, 500));\n                            }\n                        } else {\n                            if (isActiveMode) {\n                                switch (sensor.mode) {\n                                    case 0: // reference mode\n                                        state = currentValue === reference;\n                                        break;\n                                    case 2: // volume mode\n                                        state = currentValue === level;\n                                        break;\n                                    case 1: // power\n                                    case 3: // mute\n                                    case 4: // screenSaver\n                                    case 5: // playState\n                                        state = currentValue === true;\n                                        break;\n                                    default:\n                                        state = false;\n                                }\n                            }\n\n                            sensor.state = state;\n                            this.sensorServices?.[i]?.updateCharacteristic(characteristicType, state);\n                        }\n                    }\n\n                    //buttons\n                    for (let i = 0; i < this.buttons.length; i++) {\n                        const button = this.buttons[i];\n                        const state = power ? button.reference === reference : false;\n                        button.state = state;\n                        this.buttonsServices?.[i]?.updateCharacteristic(Characteristic.On, state);\n                    }\n\n                    this.inputIdentifier = inputIdentifier;\n                    this.power = power;\n                    this.reference = reference;\n                    this.volume = volume;\n                    this.mute = mute;\n                    this.screenSaver = screenSaver;\n                    this.playState = playState;\n                    if (this.logInfo) {\n                        const name = input ? input.name : reference;\n                        const productId = input ? input.oneStoreProductId : reference;\n                        this.emit('info', `Power: ${power ? 'ON' : 'OFF'}`);\n                        this.emit('info', `Input Name: ${name}`);\n                        this.emit('info', `Reference: ${reference}`);\n                        this.emit('info', `Title Id: ${titleId}`);\n                        this.emit('info', `Product Id: ${productId}`);\n                        this.emit('info', `Volume: ${volume}%`);\n                        this.emit('info', `Mute: ${mute ? 'ON' : 'OFF'}`);\n                        this.emit('info', `Media State: ${['PLAY', 'PAUSE', 'STOPPED', 'LOADING', 'INTERRUPTED'][this.mediaState]}`);\n                    }\n                })\n                .on('success', (success) => this.emit('success', success))\n                .on('info', (info) => this.emit('info', info))\n                .on('debug', (debug) => this.emit('debug', debug))\n                .on('warn', (warn) => this.emit('warn', warn))\n                .on('error', (error) => this.emit('error', error))\n                .on('restFul', (path, data) => {\n                    if (this.restFulConnected) this.restFul1.update(path, data);\n                })\n                .on('mqtt', async (topic, message) => {\n                    if (this.mqttConnected) await this.mqtt1.publish(topic, message);\n                });\n\n            // Connect to local api\n            const connect = await this.xboxLocalApi.connect();\n            if (!connect) return false;\n\n            // Start external integrations\n            if (restFulEnabled || mqttEnabled) await this.externalIntegrations();\n\n            //prepare data for accessory\n            await this.prepareDataForAccessory();\n\n            // Prepare accessory\n            const accessory = await this.prepareAccessory();\n            return accessory;\n        } catch (error) {\n            throw new Error(`Start error: ${error}`);\n        }\n    }\n}\nexport default XboxDevice;"
  }
]