homebridge-xbox-tv 4.1.24 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +7 -1
- package/config.schema.json +30 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/hadiscovery.js +89 -0
- package/src/mqtt.js +63 -18
- package/src/xboxdevice.js +72 -1
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
18
18
|
- For plugin < v4.1.0 use Homebridge UI <= v5.5.0
|
|
19
19
|
- For plugin >= v4.1.0 use Homebridge UI >= v5.13.0
|
|
20
20
|
|
|
21
|
+
## [4.2.0] - (23.09.2026)
|
|
22
|
+
|
|
23
|
+
### Changes
|
|
24
|
+
|
|
25
|
+
- added: Home Assistant discovery (`HA Discovery` option in the MQTT section). The device appears in Home Assistant as one media player (class `receiver`) with power, volume, mute, source selection and more. Requires the [MQTT Universal Media Player](https://github.com/grzegorz914/homeassistant-mqtt-media-player) integration
|
|
26
|
+
- added: MQTT availability topic (`<prefix>/Availability`, retained `online` / `offline` last will) when HA Discovery is enabled
|
|
27
|
+
- fix: MQTT/RESTFul `App` command now switches built-in inputs (Dashboard, Settings, Television, Xbox Guide...) the same way as HomeKit
|
|
28
|
+
- readme update
|
|
29
|
+
- sample config update
|
|
30
|
+
|
|
31
|
+
## [4.1.25] - (23.09.2026)
|
|
32
|
+
|
|
33
|
+
### Changes
|
|
34
|
+
|
|
35
|
+
- fix: MQTT client had `protocolVersion: 5` hardcoded, so brokers that only support MQTT 3.1.1 (e.g. the ioBroker MQTT adapter) accepted the TCP connection, failed the handshake, and got disconnected every second forever, with nothing published and no error logged. Added a `Protocol Version` option (5.0 / 3.1.1) in the MQTT section, and a one-time warning if no successful connection is established within 30 seconds
|
|
36
|
+
- readme update
|
|
37
|
+
- sample config update
|
|
38
|
+
|
|
21
39
|
## [4.1.24] - (23.09.2026)
|
|
22
40
|
|
|
23
41
|
### Added
|
|
@@ -25,6 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
25
43
|
- optional RESTful access `Token`. When set, every request (GET and POST) must send the header `Authorization: Bearer <token>`, otherwise the server responds `401`. Leaving it empty keeps the previous behaviour, so existing configs keep working
|
|
26
44
|
- readme update
|
|
27
45
|
|
|
46
|
+
## [4.1.23] - (13.09.2026)
|
|
47
|
+
|
|
48
|
+
### Changes
|
|
49
|
+
|
|
50
|
+
- bump dependencies
|
|
51
|
+
|
|
28
52
|
## [4.1.22] - (18.07.2026)
|
|
29
53
|
|
|
30
54
|
## Changes
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* Apps, Inputs, Games can be switched if `webApiControl` is enabled and console is authorized.
|
|
42
42
|
* Siri can be used for all functions, some times need to be created legacy buttons/switches/sensors.
|
|
43
43
|
* Automations can be used for all functions, some times need to be created legacy buttons/switches/sensors.
|
|
44
|
-
* 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).
|
|
44
|
+
* 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), [HA Discovery](https://github.com/grzegorz914/homeassistant-mqtt-media-player).
|
|
45
45
|
|
|
46
46
|
<p align="center">
|
|
47
47
|
<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>
|
|
@@ -137,6 +137,8 @@
|
|
|
137
137
|
| `mqtt.port` | Here set the `Port` for MQTT Broker, default 1883. |
|
|
138
138
|
| `mqtt.clientId` | Here optional set the `Client Id` of MQTT Broker. |
|
|
139
139
|
| `mqtt.prefix` | Here set the `Prefix` for `Topic` or leave empty. |
|
|
140
|
+
| `mqtt.protocolVersion` | Here select the MQTT protocol version, `5.0` (default) or `3.1.1` for brokers that only support 3.1.1 (e.g. ioBroker MQTT adapter). |
|
|
141
|
+
| `mqtt.haDiscovery` | If enabled, the device is published to Home Assistant as one media player (class `receiver`) with power, volume, mute, source selection and more. Requires the [MQTT Universal Media Player](https://github.com/grzegorz914/homeassistant-mqtt-media-player) integration. |
|
|
140
142
|
| `mqtt.auth{}` | MQTT authorization object. |
|
|
141
143
|
| `mqtt.auth.enable` | Here enable authorization for MQTT Broker. |
|
|
142
144
|
| `mqtt.auth.user` | Here set the MQTT Broker user. |
|
|
@@ -177,6 +179,10 @@
|
|
|
177
179
|
### MQTT Integration
|
|
178
180
|
|
|
179
181
|
* Subscribe data as a JSON Object `{App: "Xbox.Dashboard_8wekyb3d8bbwe!Xbox.Dashboard.Application"}`
|
|
182
|
+
* HA Discovery - if enabled, the device is published to Home Assistant as one media player (class `receiver`) with power, volume, mute, source selection and more. Games and apps are available as sources, volume can be changed only up and down. Requires the [MQTT Universal Media Player](https://github.com/grzegorz914/homeassistant-mqtt-media-player) integration. Additional retained topics:
|
|
183
|
+
* `homeassistant/media_player/<id>/config` - discovery message.
|
|
184
|
+
* `HA State` - `{"power": true, "state": "playing", "muted": false, "source": "9WZDNCRFJ3TJ", "app_name": "Netflix"}`.
|
|
185
|
+
* `Availability` - `online`, `offline` (last will).
|
|
180
186
|
|
|
181
187
|
| Method | Topic | Message | Type |
|
|
182
188
|
| --- | --- | --- | --- |
|
package/config.schema.json
CHANGED
|
@@ -1219,6 +1219,34 @@
|
|
|
1219
1219
|
"functionBody": "return model.devices[arrayIndices].mqtt.enable === true;"
|
|
1220
1220
|
}
|
|
1221
1221
|
},
|
|
1222
|
+
"protocolVersion": {
|
|
1223
|
+
"title": "Protocol Version",
|
|
1224
|
+
"type": "integer",
|
|
1225
|
+
"default": 5,
|
|
1226
|
+
"oneOf": [
|
|
1227
|
+
{
|
|
1228
|
+
"title": "5.0",
|
|
1229
|
+
"enum": [5]
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
"title": "3.1.1",
|
|
1233
|
+
"enum": [4]
|
|
1234
|
+
}
|
|
1235
|
+
],
|
|
1236
|
+
"description": "MQTT protocol version used to connect to the broker. Some brokers (e.g. the ioBroker MQTT adapter) only support 3.1.1 and will silently reject 5.0 connections.",
|
|
1237
|
+
"condition": {
|
|
1238
|
+
"functionBody": "return model.devices[arrayIndices].mqtt.enable === true;"
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
"haDiscovery": {
|
|
1242
|
+
"title": "HA Discovery",
|
|
1243
|
+
"type": "boolean",
|
|
1244
|
+
"default": false,
|
|
1245
|
+
"description": "Publish a Home Assistant MQTT discovery message so the device appears as one media player (class receiver). Requires the MQTT Universal Media Player integration in Home Assistant: https://github.com/grzegorz914/homeassistant-mqtt-media-player",
|
|
1246
|
+
"condition": {
|
|
1247
|
+
"functionBody": "return model.devices[arrayIndices].mqtt.enable === true;"
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1222
1250
|
"auth": {
|
|
1223
1251
|
"title": "Authorization",
|
|
1224
1252
|
"type": "object",
|
|
@@ -1472,6 +1500,8 @@
|
|
|
1472
1500
|
"devices[].mqtt.port",
|
|
1473
1501
|
"devices[].mqtt.clientId",
|
|
1474
1502
|
"devices[].mqtt.prefix",
|
|
1503
|
+
"devices[].mqtt.protocolVersion",
|
|
1504
|
+
"devices[].mqtt.haDiscovery",
|
|
1475
1505
|
{
|
|
1476
1506
|
"key": "devices[].mqtt.auth",
|
|
1477
1507
|
"title": "Authorization",
|
package/index.js
CHANGED
|
@@ -156,6 +156,8 @@ class XboxPlatform {
|
|
|
156
156
|
port: device.mqtt.port || 1883,
|
|
157
157
|
clientId: device.mqtt.clientId ? `microsoft_${device.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `microsoft_${Math.random().toString(16).slice(3)}`,
|
|
158
158
|
prefix: device.mqtt.prefix ? `microsoft/${device.mqtt.prefix}/${name}` : `microsoft/${name}`,
|
|
159
|
+
protocolVersion: device.mqtt.protocolVersion,
|
|
160
|
+
haDiscovery: device.mqtt.haDiscovery,
|
|
159
161
|
user: device.mqtt.auth?.user,
|
|
160
162
|
passwd: device.mqtt.auth?.passwd,
|
|
161
163
|
logWarn: logLevel.warn,
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Home Assistant discovery for the MQTT Universal Media Player integration
|
|
2
|
+
// https://github.com/grzegorz914/homeassistant-mqtt-media-player
|
|
3
|
+
const PLATFORM = 'mqtt_universal_media_player';
|
|
4
|
+
|
|
5
|
+
class HaDiscovery {
|
|
6
|
+
constructor(mqtt, config) {
|
|
7
|
+
// + and # are MQTT wildcards, Home Assistant would subscribe to a pattern instead of the device topic
|
|
8
|
+
if (/[+#]/.test(mqtt.config.prefix)) {
|
|
9
|
+
throw new Error(`MQTT prefix ${mqtt.config.prefix} must not contain + or #, rename the device or set a prefix`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.mqtt = mqtt;
|
|
13
|
+
this.objectId = String(config.objectId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
14
|
+
this.uniqueId = this.objectId;
|
|
15
|
+
this.name = config.name;
|
|
16
|
+
this.deviceClass = config.deviceClass;
|
|
17
|
+
this.device = config.device ?? {};
|
|
18
|
+
this.commands = config.commands ?? {};
|
|
19
|
+
|
|
20
|
+
this.configTopic = `${mqtt.haPrefix}/media_player/${this.objectId}/config`;
|
|
21
|
+
this.stateTopic = `${mqtt.config.prefix}/HA State`;
|
|
22
|
+
this.commandTopic = `${mqtt.config.prefix}/Set`;
|
|
23
|
+
|
|
24
|
+
this.sources = [];
|
|
25
|
+
this.soundModes = [];
|
|
26
|
+
this.state = {};
|
|
27
|
+
this.lastConfig = '';
|
|
28
|
+
this.lastState = '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Publish (or republish when changed) the retained discovery message
|
|
32
|
+
async publishConfig({ sources = this.sources, soundModes = this.soundModes } = {}) {
|
|
33
|
+
this.sources = uniqueByName(sources);
|
|
34
|
+
this.soundModes = uniqueByName(soundModes);
|
|
35
|
+
|
|
36
|
+
const device = { identifiers: [this.uniqueId], name: this.name };
|
|
37
|
+
for (const [key, value] of Object.entries(this.device)) {
|
|
38
|
+
if (value !== undefined && value !== null && value !== '') device[key] = String(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config = {
|
|
42
|
+
platform: PLATFORM,
|
|
43
|
+
unique_id: this.uniqueId,
|
|
44
|
+
device_class: this.deviceClass,
|
|
45
|
+
state_topic: this.stateTopic,
|
|
46
|
+
command_topic: this.commandTopic,
|
|
47
|
+
availability_topic: this.mqtt.availabilityTopic,
|
|
48
|
+
device,
|
|
49
|
+
commands: this.commands,
|
|
50
|
+
sources: this.sources,
|
|
51
|
+
sound_modes: this.soundModes
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const payload = JSON.stringify(config);
|
|
55
|
+
if (payload === this.lastConfig) return false;
|
|
56
|
+
this.lastConfig = payload;
|
|
57
|
+
await this.mqtt.publishRetained(this.configTopic, payload);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Merge a partial state and publish it retained when something changed
|
|
62
|
+
async updateState(partial) {
|
|
63
|
+
for (const [key, value] of Object.entries(partial)) {
|
|
64
|
+
if (value === undefined) continue;
|
|
65
|
+
this.state[key] = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const payload = JSON.stringify(this.state);
|
|
69
|
+
if (payload === this.lastState) return false;
|
|
70
|
+
this.lastState = payload;
|
|
71
|
+
await this.mqtt.publishRetained(this.stateTopic, payload);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Home Assistant needs unique names, keep the first item for a duplicated name
|
|
77
|
+
function uniqueByName(items) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const result = [];
|
|
80
|
+
for (const item of items) {
|
|
81
|
+
const name = String(item.name ?? '').trim();
|
|
82
|
+
if (!name || item.id === undefined || item.id === null || seen.has(name)) continue;
|
|
83
|
+
seen.add(name);
|
|
84
|
+
result.push({ ...item, name });
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default HaDiscovery;
|
package/src/mqtt.js
CHANGED
|
@@ -8,35 +8,56 @@ class Mqtt extends EventEmitter {
|
|
|
8
8
|
|
|
9
9
|
const url = `mqtt://${config.host}:${config.port}`;
|
|
10
10
|
const subscribeTopic = `${config.prefix}/Set`;
|
|
11
|
+
const protocolVersion = config.protocolVersion === 4 ? 4 : 5;
|
|
12
|
+
const isV5 = protocolVersion === 5;
|
|
13
|
+
this.isV5 = isV5;
|
|
14
|
+
|
|
15
|
+
// Home Assistant discovery, availability is reported with a retained last will
|
|
16
|
+
this.haDiscovery = config.haDiscovery === true;
|
|
17
|
+
this.haPrefix = config.haPrefix || 'homeassistant';
|
|
18
|
+
this.availabilityTopic = `${config.prefix}/Availability`;
|
|
11
19
|
|
|
12
20
|
const options = {
|
|
13
21
|
clientId: config.clientId,
|
|
14
22
|
username: config.user,
|
|
15
23
|
password: config.passwd,
|
|
16
|
-
protocolVersion
|
|
17
|
-
clean:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
protocolVersion,
|
|
25
|
+
clean: !isV5,
|
|
26
|
+
...(this.haDiscovery ? {
|
|
27
|
+
will: { topic: this.availabilityTopic, payload: 'offline', qos: 1, retain: true }
|
|
28
|
+
} : {}),
|
|
29
|
+
...(isV5 ? {
|
|
30
|
+
properties: {
|
|
31
|
+
sessionExpiryInterval: 60 * 60,
|
|
32
|
+
userProperties: {
|
|
33
|
+
source: 'node-client'
|
|
34
|
+
}
|
|
22
35
|
}
|
|
23
|
-
}
|
|
36
|
+
} : {})
|
|
24
37
|
};
|
|
25
38
|
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
let hasConnected = false;
|
|
41
|
+
let warnedStalled = false;
|
|
42
|
+
|
|
26
43
|
this.mqttClient = connect(url, options)
|
|
27
44
|
.on('connect', async () => {
|
|
28
|
-
|
|
45
|
+
hasConnected = true;
|
|
46
|
+
this.emit('connected', `MQTT v${protocolVersion} connected.`);
|
|
47
|
+
if (this.haDiscovery) this.publishRetained(this.availabilityTopic, 'online').catch(() => { });
|
|
29
48
|
|
|
30
49
|
try {
|
|
31
50
|
await new Promise((resolve, reject) => {
|
|
32
51
|
this.mqttClient.subscribe(subscribeTopic,
|
|
33
52
|
{
|
|
34
53
|
qos: 1,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
...(isV5 ? {
|
|
55
|
+
properties: {
|
|
56
|
+
userProperties: {
|
|
57
|
+
type: 'subscription'
|
|
58
|
+
}
|
|
38
59
|
}
|
|
39
|
-
}
|
|
60
|
+
} : {})
|
|
40
61
|
},
|
|
41
62
|
(error) => {
|
|
42
63
|
if (error) return reject(error);
|
|
@@ -69,6 +90,10 @@ class Mqtt extends EventEmitter {
|
|
|
69
90
|
if (config.logDebug) this.emit('debug', 'MQTT Reconnecting...');
|
|
70
91
|
})
|
|
71
92
|
.on('close', () => {
|
|
93
|
+
if (!hasConnected && !warnedStalled && Date.now() - startTime > 30000) {
|
|
94
|
+
warnedStalled = true;
|
|
95
|
+
this.emit('warn', `MQTT has not connected after 30s of retries using protocol v${protocolVersion}. Check broker address, credentials, and whether the broker supports this MQTT protocol version.`);
|
|
96
|
+
}
|
|
72
97
|
if (config.logDebug) this.emit('debug', 'MQTT Connection closed.');
|
|
73
98
|
});
|
|
74
99
|
}
|
|
@@ -79,6 +104,24 @@ class Mqtt extends EventEmitter {
|
|
|
79
104
|
});
|
|
80
105
|
}
|
|
81
106
|
|
|
107
|
+
publishRetained(fullTopic, payload) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
// + and # are wildcards, the client would never complete such a publish
|
|
110
|
+
if (/[+#]/.test(fullTopic)) {
|
|
111
|
+
return reject(new Error(`Invalid topic ${fullTopic}, the name or prefix must not contain + or #`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.mqttClient.publish(fullTopic, payload, { qos: 1, retain: true }, (error) => {
|
|
115
|
+
if (error) {
|
|
116
|
+
if (this.config.logWarn) this.emit('warn', `MQTT Publish error: ${error.message}`);
|
|
117
|
+
return reject(error);
|
|
118
|
+
}
|
|
119
|
+
if (this.config.logDebug) this.emit('debug', `MQTT Publish retained Topic: ${fullTopic}, Payload: ${payload}`);
|
|
120
|
+
resolve();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
82
125
|
publish(topic, message) {
|
|
83
126
|
return new Promise((resolve, reject) => {
|
|
84
127
|
const fullTopic = `${this.config.prefix}/${topic}`;
|
|
@@ -87,13 +130,15 @@ class Mqtt extends EventEmitter {
|
|
|
87
130
|
this.mqttClient.publish(fullTopic, publishMessage,
|
|
88
131
|
{
|
|
89
132
|
qos: 1,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
...(this.isV5 ? {
|
|
134
|
+
properties: {
|
|
135
|
+
contentType: 'application/json',
|
|
136
|
+
userProperties: {
|
|
137
|
+
source: 'node',
|
|
138
|
+
action: 'set'
|
|
139
|
+
}
|
|
95
140
|
}
|
|
96
|
-
}
|
|
141
|
+
} : {})
|
|
97
142
|
},
|
|
98
143
|
(error) => {
|
|
99
144
|
if (error) {
|
package/src/xboxdevice.js
CHANGED
|
@@ -2,6 +2,7 @@ import EventEmitter from 'events';
|
|
|
2
2
|
import XboxWebApi from './webApi/xboxwebapi.js';
|
|
3
3
|
import XboxLocalApi from './localApi/xboxlocalapi.js';
|
|
4
4
|
import Functions from './functions.js';
|
|
5
|
+
import HaDiscovery from './hadiscovery.js';
|
|
5
6
|
import { DefaultInputs, WebApi } from './constants.js';
|
|
6
7
|
|
|
7
8
|
let Accessory, Characteristic, Service, Categories, Encode, AccessoryUUID;
|
|
@@ -100,10 +101,18 @@ class XboxDevice extends EventEmitter {
|
|
|
100
101
|
break;
|
|
101
102
|
}
|
|
102
103
|
break;
|
|
103
|
-
case 'App':
|
|
104
|
+
case 'App': {
|
|
105
|
+
// Known inputs (also Dashboard, Settings, Television...) are switched the same way as from HomeKit
|
|
106
|
+
const input = this.inputsServices?.find(i => i.oneStoreProductId === value);
|
|
107
|
+
if (input) {
|
|
108
|
+
await this.setInput(input);
|
|
109
|
+
set = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
104
112
|
const payload = [{ 'oneStoreProductId': value }];
|
|
105
113
|
set = await this.xboxWebApi.send('Shell', 'ActivateApplicationWithOneStoreProductId', payload);
|
|
106
114
|
break;
|
|
115
|
+
}
|
|
107
116
|
case 'Volume':
|
|
108
117
|
switch (value) {
|
|
109
118
|
case 'up':
|
|
@@ -316,6 +325,7 @@ class XboxDevice extends EventEmitter {
|
|
|
316
325
|
|
|
317
326
|
// Only one time run
|
|
318
327
|
if (updated) await this.displayOrder();
|
|
328
|
+
if (updated) this.haPublishConfig();
|
|
319
329
|
|
|
320
330
|
return true;
|
|
321
331
|
} catch (error) {
|
|
@@ -991,6 +1001,64 @@ class XboxDevice extends EventEmitter {
|
|
|
991
1001
|
};
|
|
992
1002
|
}
|
|
993
1003
|
|
|
1004
|
+
//home assistant discovery
|
|
1005
|
+
async setupHaDiscovery() {
|
|
1006
|
+
if (!this.mqttConnected || !this.mqtt.haDiscovery) return;
|
|
1007
|
+
|
|
1008
|
+
try {
|
|
1009
|
+
this.ha = new HaDiscovery(this.mqtt1, {
|
|
1010
|
+
objectId: `xbox_${this.liveId}`,
|
|
1011
|
+
name: this.name,
|
|
1012
|
+
deviceClass: 'receiver',
|
|
1013
|
+
device: {
|
|
1014
|
+
manufacturer: 'Microsoft',
|
|
1015
|
+
model: this.modelName ?? this.savedInfo?.modelName,
|
|
1016
|
+
sw_version: this.savedInfo?.firmwareRevision
|
|
1017
|
+
},
|
|
1018
|
+
commands: {
|
|
1019
|
+
power: { key: 'Power' },
|
|
1020
|
+
// The console can only step the volume
|
|
1021
|
+
volume_step: { key: 'Volume', up: 'up', down: 'down' },
|
|
1022
|
+
mute: { key: 'Mute' },
|
|
1023
|
+
source: { key: 'App' },
|
|
1024
|
+
play_pause: { key: 'RcControl', value: 'playPause' }
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
await this.haPublishConfig();
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (this.logWarn) this.emit('warn', `HA Discovery setup error: ${error}`);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
async haPublishConfig() {
|
|
1034
|
+
if (!this.ha) return;
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
const sources = (this.inputsServices ?? []).map(input => ({ id: input.oneStoreProductId, name: input.name }));
|
|
1038
|
+
await this.ha.publishConfig({ sources });
|
|
1039
|
+
await this.haUpdateState();
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
if (this.logWarn) this.emit('warn', `HA Discovery publish error: ${error}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async haUpdateState() {
|
|
1046
|
+
if (!this.ha) return;
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
const input = this.inputsServices?.find(input => input.identifier === this.inputIdentifier);
|
|
1050
|
+
await this.ha.updateState({
|
|
1051
|
+
power: this.power,
|
|
1052
|
+
state: this.power ? (this.playState ? 'playing' : 'on') : 'off',
|
|
1053
|
+
muted: typeof this.mute === 'boolean' ? this.mute : undefined,
|
|
1054
|
+
source: input?.oneStoreProductId,
|
|
1055
|
+
app_name: input?.name ?? ''
|
|
1056
|
+
});
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
if (this.logWarn) this.emit('warn', `HA Discovery state error: ${error}`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
994
1062
|
//start
|
|
995
1063
|
async start() {
|
|
996
1064
|
try {
|
|
@@ -1022,6 +1090,7 @@ class XboxDevice extends EventEmitter {
|
|
|
1022
1090
|
})
|
|
1023
1091
|
.on('stateChanged', (power) => {
|
|
1024
1092
|
this.power = power;
|
|
1093
|
+
this.haUpdateState();
|
|
1025
1094
|
|
|
1026
1095
|
this.televisionService?.updateCharacteristic(Characteristic.Active, power);
|
|
1027
1096
|
|
|
@@ -1179,6 +1248,7 @@ class XboxDevice extends EventEmitter {
|
|
|
1179
1248
|
this.mute = mute;
|
|
1180
1249
|
this.screenSaver = screenSaver;
|
|
1181
1250
|
this.playState = playState;
|
|
1251
|
+
this.haUpdateState();
|
|
1182
1252
|
if (this.logInfo) {
|
|
1183
1253
|
const name = input ? input.name : reference;
|
|
1184
1254
|
const productId = input ? input.oneStoreProductId : reference;
|
|
@@ -1213,6 +1283,7 @@ class XboxDevice extends EventEmitter {
|
|
|
1213
1283
|
|
|
1214
1284
|
// Prepare accessory
|
|
1215
1285
|
const accessory = await this.prepareAccessory();
|
|
1286
|
+
this.setupHaDiscovery();
|
|
1216
1287
|
return accessory;
|
|
1217
1288
|
} catch (error) {
|
|
1218
1289
|
throw new Error(`Start error: ${error}`);
|