homebridge-sony-audio-extended 0.0.1
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/LICENSE +176 -0
- package/README.md +113 -0
- package/config.schema.json +16 -0
- package/dist/api.d.ts +762 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +114 -0
- package/dist/api.js.map +1 -0
- package/dist/discoverer.d.ts +44 -0
- package/dist/discoverer.d.ts.map +1 -0
- package/dist/discoverer.js +208 -0
- package/dist/discoverer.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +31 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +99 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +9 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +12 -0
- package/dist/settings.js.map +1 -0
- package/dist/sonyAudioAccessory.d.ts +66 -0
- package/dist/sonyAudioAccessory.d.ts.map +1 -0
- package/dist/sonyAudioAccessory.js +388 -0
- package/dist/sonyAudioAccessory.js.map +1 -0
- package/dist/sonyAudioAccessorySettings.d.ts +45 -0
- package/dist/sonyAudioAccessorySettings.d.ts.map +1 -0
- package/dist/sonyAudioAccessorySettings.js +134 -0
- package/dist/sonyAudioAccessorySettings.js.map +1 -0
- package/dist/sonyDevice.d.ts +377 -0
- package/dist/sonyDevice.d.ts.map +1 -0
- package/dist/sonyDevice.js +867 -0
- package/dist/sonyDevice.js.map +1 -0
- package/package.json +46 -0
- package/tests/ssdp-client.ts +25 -0
- package/tests/ssdp-server.ts +27 -0
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SonyDevice = void 0;
|
|
7
|
+
const api_1 = require("./api");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const events_1 = require("events");
|
|
10
|
+
/* eslint-disable max-len */
|
|
11
|
+
const url_1 = require("url");
|
|
12
|
+
const ws_1 = __importDefault(require("ws"));
|
|
13
|
+
/**
|
|
14
|
+
* Categories of devices supported by this module
|
|
15
|
+
*/
|
|
16
|
+
const COMPATIBLE_DEVICE_CATEGORIES = [
|
|
17
|
+
'homeTheaterSystem',
|
|
18
|
+
'personalAudio', // !not tested
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Model names that are explicitly supported even if category doesn't match
|
|
22
|
+
*/
|
|
23
|
+
const COMPATIBLE_DEVICE_MODELS = ['STR-AN1000'];
|
|
24
|
+
/**
|
|
25
|
+
* Devices terminals which hasn't in getCurrentExternalTerminalsStatus api
|
|
26
|
+
* from [here](https://developer.sony.com/develop/audio-control-api/api-references/device-uri)
|
|
27
|
+
*/
|
|
28
|
+
const DEVICE_TERMINALS = [
|
|
29
|
+
{
|
|
30
|
+
scheme: 'dlna',
|
|
31
|
+
readonly: true,
|
|
32
|
+
terminal: {
|
|
33
|
+
connection: 'connected',
|
|
34
|
+
title: 'DLNA Music',
|
|
35
|
+
uri: 'dlna:music',
|
|
36
|
+
meta: "meta:pc" /* PC */,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
scheme: 'storage',
|
|
41
|
+
readonly: true,
|
|
42
|
+
terminal: {
|
|
43
|
+
connection: 'connected',
|
|
44
|
+
title: 'USB Storage',
|
|
45
|
+
uri: 'storage:usb1',
|
|
46
|
+
meta: "meta:usbdac" /* USBDAC */,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
scheme: 'radio',
|
|
51
|
+
readonly: true,
|
|
52
|
+
terminal: {
|
|
53
|
+
connection: 'connected',
|
|
54
|
+
title: 'FM Radio',
|
|
55
|
+
uri: 'radio:fm',
|
|
56
|
+
meta: "meta:tuner" /* TUNER */,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
scheme: 'netService',
|
|
61
|
+
readonly: false,
|
|
62
|
+
terminal: {
|
|
63
|
+
connection: 'connected',
|
|
64
|
+
title: 'Audio Network',
|
|
65
|
+
uri: 'netService:audio',
|
|
66
|
+
meta: "meta:source" /* SOURCE */,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
scheme: 'multiroom',
|
|
71
|
+
readonly: false,
|
|
72
|
+
terminal: {
|
|
73
|
+
connection: 'connected',
|
|
74
|
+
title: 'Multiroom Audio',
|
|
75
|
+
uri: 'multiroom:audio',
|
|
76
|
+
meta: "meta:source" /* SOURCE */,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
scheme: 'cast',
|
|
81
|
+
readonly: false,
|
|
82
|
+
terminal: {
|
|
83
|
+
connection: 'connected',
|
|
84
|
+
title: 'Cast Audio',
|
|
85
|
+
uri: 'cast:audio',
|
|
86
|
+
meta: "meta:source" /* SOURCE */,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
scheme: 'extInput',
|
|
91
|
+
readonly: false,
|
|
92
|
+
terminal: {
|
|
93
|
+
connection: 'connected',
|
|
94
|
+
title: 'AirPlay',
|
|
95
|
+
uri: 'extInput:airPlay',
|
|
96
|
+
meta: "meta:btaudio" /* BTAUDIO */,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
const RE_EXT_OUTPUT = new RegExp('extOutput:*');
|
|
101
|
+
const SUBSCRIBE_NOTIFICATIONS = [
|
|
102
|
+
{
|
|
103
|
+
service: 'system',
|
|
104
|
+
notifications: ["notifyPowerStatus" /* POWER */],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
service: 'audio',
|
|
108
|
+
notifications: ["notifyVolumeInformation" /* VOLUME */],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
service: 'avContent',
|
|
112
|
+
notifications: ["notifyExternalTerminalStatus" /* TERMINAL */, "notifyPlayingContentInfo" /* CONTENT */],
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
const RECONNECT_TIMEOUT = 5000;
|
|
116
|
+
const REQUEST_TIMEOUT = 5000;
|
|
117
|
+
const WEBSOCKET_REQUEST_TIMEOUT = 3000;
|
|
118
|
+
const WEBSOCKET_HEARTBEAT_INTERVAL = 60 * 1000;
|
|
119
|
+
class SonyDevice extends events_1.EventEmitter {
|
|
120
|
+
constructor(baseUrl, upnpUrl, udn, apisInfo, log) {
|
|
121
|
+
super();
|
|
122
|
+
/** Flag for emitting of the RESORE event, rised when connection has been lost and after restored */
|
|
123
|
+
this.emitRestoreEvent = false;
|
|
124
|
+
this.systemInfo = {
|
|
125
|
+
area: '',
|
|
126
|
+
bdAddr: '',
|
|
127
|
+
bleID: '',
|
|
128
|
+
cid: '',
|
|
129
|
+
deviceID: '',
|
|
130
|
+
duid: '',
|
|
131
|
+
esn: '',
|
|
132
|
+
generation: '',
|
|
133
|
+
helpUrl: '',
|
|
134
|
+
iconUrl: '',
|
|
135
|
+
initialPowerOnTime: '',
|
|
136
|
+
language: '',
|
|
137
|
+
lastPowerOnTime: '',
|
|
138
|
+
macAddr: '',
|
|
139
|
+
model: '',
|
|
140
|
+
name: '',
|
|
141
|
+
product: '',
|
|
142
|
+
region: '',
|
|
143
|
+
serial: '',
|
|
144
|
+
ssid: '',
|
|
145
|
+
version: '',
|
|
146
|
+
wirelessMacAddr: '',
|
|
147
|
+
};
|
|
148
|
+
this._externalTerminals = null;
|
|
149
|
+
this._volumeInformation = null;
|
|
150
|
+
this.manufacturer = 'Sony Corporation';
|
|
151
|
+
this.readyState = SonyDevice.CREATING;
|
|
152
|
+
this.baseUrl = baseUrl;
|
|
153
|
+
this.upnpUrl = upnpUrl;
|
|
154
|
+
this.UDN = udn;
|
|
155
|
+
this.apisInfo = apisInfo;
|
|
156
|
+
this.log = log;
|
|
157
|
+
// this.systemInfo = systemInfo;
|
|
158
|
+
this.axiosInstance = axios_1.default.create({
|
|
159
|
+
baseURL: this.baseUrl.href,
|
|
160
|
+
headers: { 'content-type': 'application/json' },
|
|
161
|
+
timeout: REQUEST_TIMEOUT,
|
|
162
|
+
});
|
|
163
|
+
this.axiosInstance.interceptors.response.use(SonyDevice.responseInterceptor(this.log));
|
|
164
|
+
this.axiosInstance.interceptors.request.use(SonyDevice.requestInterceptorLogger(this.log));
|
|
165
|
+
if (this.upnpUrl) {
|
|
166
|
+
this.axiosInstanceSoap = axios_1.default.create({
|
|
167
|
+
baseURL: this.upnpUrl.href,
|
|
168
|
+
headers: {
|
|
169
|
+
SOAPACTION: '"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"',
|
|
170
|
+
'Content-Type': 'text/xml; charset="utf-8"',
|
|
171
|
+
},
|
|
172
|
+
timeout: REQUEST_TIMEOUT,
|
|
173
|
+
});
|
|
174
|
+
this.axiosInstanceSoap.interceptors.response.use(SonyDevice.responseInterceptor(this.log));
|
|
175
|
+
this.axiosInstanceSoap.interceptors.request.use(SonyDevice.requestInterceptorLogger(this.log));
|
|
176
|
+
}
|
|
177
|
+
this.wsClients = new Map();
|
|
178
|
+
this.readyState = SonyDevice.READY;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Return device id
|
|
182
|
+
*/
|
|
183
|
+
getDeviceID() {
|
|
184
|
+
return this.systemInfo.serial !== ''
|
|
185
|
+
? this.systemInfo.serial
|
|
186
|
+
: this.systemInfo.macAddr !== ''
|
|
187
|
+
? this.systemInfo.macAddr
|
|
188
|
+
: this.systemInfo.wirelessMacAddr;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Checks the request for API version compliance
|
|
192
|
+
*/
|
|
193
|
+
validateRequest(service, request) {
|
|
194
|
+
var _a;
|
|
195
|
+
const version = request.version;
|
|
196
|
+
const method = request.method;
|
|
197
|
+
const reqApis = (_a = this.apisInfo) === null || _a === void 0 ? void 0 : _a.find((a) => a.service === service);
|
|
198
|
+
const reqApi = reqApis === null || reqApis === void 0 ? void 0 : reqApis.apis.find((m) => m.name === method);
|
|
199
|
+
const validVersions = reqApi === null || reqApi === void 0 ? void 0 : reqApi.versions.map((x) => x.version);
|
|
200
|
+
if (validVersions) {
|
|
201
|
+
return validVersions.includes(version);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async getExternalTerminals() {
|
|
208
|
+
if (!this._externalTerminals) {
|
|
209
|
+
// get the terminals info from device
|
|
210
|
+
const resTerminals = await this.axiosInstance.post('/avContent', JSON.stringify(api_1.ApiRequestCurrentExternalTerminalsStatus));
|
|
211
|
+
const terminals = resTerminals.data;
|
|
212
|
+
this._externalTerminals = terminals.result[0];
|
|
213
|
+
// add other terminals
|
|
214
|
+
const schemes = await this.getSchemes();
|
|
215
|
+
DEVICE_TERMINALS.forEach((t) => {
|
|
216
|
+
var _a, _b;
|
|
217
|
+
if (t.readonly) {
|
|
218
|
+
if (schemes.includes(t.scheme)) {
|
|
219
|
+
// device support that input terminal
|
|
220
|
+
(_a = this._externalTerminals) === null || _a === void 0 ? void 0 : _a.push(t.terminal);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// add all readonly terminals
|
|
225
|
+
(_b = this._externalTerminals) === null || _b === void 0 ? void 0 : _b.push(t.terminal);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return this._externalTerminals;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Terminal is read-only and cannot be select by the user?
|
|
233
|
+
*/
|
|
234
|
+
isReadonlyTerminal(terminal) {
|
|
235
|
+
const readonlyTerminalsUri = DEVICE_TERMINALS.find((t) => !t.readonly && t.terminal.uri === terminal.uri);
|
|
236
|
+
return !!readonlyTerminalsUri;
|
|
237
|
+
}
|
|
238
|
+
async getVolumeInformation() {
|
|
239
|
+
if (!this._volumeInformation) {
|
|
240
|
+
// get the volume info from device
|
|
241
|
+
const resVolumes = await this.axiosInstance.post('/audio', JSON.stringify(api_1.ApiRequestVolumeInformation));
|
|
242
|
+
const volumes = resVolumes.data;
|
|
243
|
+
this._volumeInformation = volumes.result[0];
|
|
244
|
+
}
|
|
245
|
+
return this._volumeInformation;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Returns the active input (if exist) for current active zone
|
|
249
|
+
*/
|
|
250
|
+
async getActiveInput() {
|
|
251
|
+
const service = 'avContent';
|
|
252
|
+
const req = {
|
|
253
|
+
id: 37,
|
|
254
|
+
method: 'getPlayingContentInfo',
|
|
255
|
+
params: [{}],
|
|
256
|
+
version: '1.2',
|
|
257
|
+
};
|
|
258
|
+
const zone = await this.getActiveZone();
|
|
259
|
+
if (zone) {
|
|
260
|
+
req.params[0].output = zone.uri;
|
|
261
|
+
}
|
|
262
|
+
const res = await this.axiosInstance.post('/' + service, JSON.stringify(req));
|
|
263
|
+
const playingInfo = res.data;
|
|
264
|
+
if (playingInfo.result[0].length === 1) {
|
|
265
|
+
// info with only one zone
|
|
266
|
+
return this.getTerminalBySource(playingInfo.result[0][0].source || playingInfo.result[0][0].uri);
|
|
267
|
+
}
|
|
268
|
+
return null; // no active zone
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Return external terminals which are inputs
|
|
272
|
+
*/
|
|
273
|
+
async getInputs() {
|
|
274
|
+
const exTerminals = await this.getExternalTerminals();
|
|
275
|
+
const inputs = exTerminals === null || exTerminals === void 0 ? void 0 : exTerminals.filter((t) => !RE_EXT_OUTPUT.test(t.uri));
|
|
276
|
+
return inputs ? inputs : [];
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Return external terminals which are zone, aka outputs
|
|
280
|
+
*/
|
|
281
|
+
async getZones() {
|
|
282
|
+
const exTerminals = await this.getExternalTerminals();
|
|
283
|
+
const inputs = exTerminals === null || exTerminals === void 0 ? void 0 : exTerminals.filter((t) => RE_EXT_OUTPUT.test(t.uri));
|
|
284
|
+
return inputs ? inputs : null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Return active zone.
|
|
288
|
+
* If no active zone, return `null`.
|
|
289
|
+
* It's mean that only one zone exist, i.e. no output terminals
|
|
290
|
+
*/
|
|
291
|
+
async getActiveZone() {
|
|
292
|
+
const zones = await this.getZones();
|
|
293
|
+
if (zones === null) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const activeZone = zones.find((zone) => zone.active === 'active');
|
|
298
|
+
return activeZone ? activeZone : null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Return the list of schemes that device can handle.
|
|
303
|
+
*/
|
|
304
|
+
async getSchemes() {
|
|
305
|
+
const resSchemes = await this.axiosInstance.post('/avContent', JSON.stringify(api_1.ApiRequestGetSchemeList));
|
|
306
|
+
const schemes = resSchemes.data;
|
|
307
|
+
return schemes.result[0].map((s) => s.scheme);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Check the API response for returned error
|
|
311
|
+
* Decsription of errors [here](https://developer.sony.com/develop/audio-control-api/api-references/error-codes).
|
|
312
|
+
* @param response
|
|
313
|
+
*/
|
|
314
|
+
static responseInterceptor(log) {
|
|
315
|
+
return (response) => {
|
|
316
|
+
log.debug(`Response from device:\n${JSON.stringify(response.data)}`);
|
|
317
|
+
if (typeof response.data === 'object' && response.data !== null) {
|
|
318
|
+
if ('error' in response.data) {
|
|
319
|
+
// TODO: add a device ip address for identification of the device
|
|
320
|
+
const errMsg = `Device API got an error: ${JSON.stringify(response.data)}`;
|
|
321
|
+
return Promise.reject(new api_1.GenericApiError(errMsg));
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
return response;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
return response;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Logging requests for debug
|
|
334
|
+
* @param request
|
|
335
|
+
*/
|
|
336
|
+
static requestInterceptorLogger(log) {
|
|
337
|
+
return (request) => {
|
|
338
|
+
log.debug(`Request to device\n${request.baseURL}:\n${JSON.stringify(request.data)}`);
|
|
339
|
+
return request;
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Create and initialize the new device.
|
|
344
|
+
* Get info about supported api and system
|
|
345
|
+
*/
|
|
346
|
+
static async createDevice(baseUrl, upnpUrl, udn, log) {
|
|
347
|
+
const axiosInstance = axios_1.default.create({
|
|
348
|
+
baseURL: baseUrl.href,
|
|
349
|
+
headers: { 'content-type': 'application/json' },
|
|
350
|
+
timeout: 0, // when device is turning on, some time it has long answer time.
|
|
351
|
+
});
|
|
352
|
+
axiosInstance.interceptors.response.use(SonyDevice.responseInterceptor(log));
|
|
353
|
+
axiosInstance.interceptors.request.use(SonyDevice.requestInterceptorLogger(log));
|
|
354
|
+
// Checks the device against a compatible category of the device
|
|
355
|
+
const resInterfaceInfo = await axiosInstance.post('/system', JSON.stringify(api_1.ApiRequestGetInterfaceInformation));
|
|
356
|
+
const interfaceInfo = resInterfaceInfo.data;
|
|
357
|
+
const productCategory = interfaceInfo.result[0].productCategory;
|
|
358
|
+
const modelName = interfaceInfo.result[0].modelName;
|
|
359
|
+
// Log device information for debugging
|
|
360
|
+
log.debug(`Device info - Model: "${modelName}", Category: "${productCategory}"`);
|
|
361
|
+
// Check if device is compatible by category or by model name
|
|
362
|
+
const isCompatibleCategory = COMPATIBLE_DEVICE_CATEGORIES.includes(productCategory);
|
|
363
|
+
const isCompatibleModel = COMPATIBLE_DEVICE_MODELS.some((model) => modelName && modelName.toUpperCase().includes(model.toUpperCase()));
|
|
364
|
+
if (!isCompatibleCategory && !isCompatibleModel) {
|
|
365
|
+
// device has an incompatible category and is not in the compatible models list
|
|
366
|
+
throw new api_1.IncompatibleDeviceCategoryError(`Device at ${baseUrl.href} has an incompatible category "${productCategory}" and model "${modelName}" is not in the supported models list`);
|
|
367
|
+
}
|
|
368
|
+
const resApiInfo = await axiosInstance.post('/guide', JSON.stringify(api_1.ApiRequestSupportedApiInfo));
|
|
369
|
+
const apisInfo = resApiInfo.data.result[0];
|
|
370
|
+
const device = new SonyDevice(baseUrl, upnpUrl, udn, apisInfo, log);
|
|
371
|
+
// Gets general system information for the device.
|
|
372
|
+
// check the request for API version compliance
|
|
373
|
+
const service = 'system';
|
|
374
|
+
if (!device.validateRequest(service, api_1.ApiRequestSystemInformation)) {
|
|
375
|
+
throw new api_1.UnsupportedVersionApiError(`The specified api version is not supported by the device ${baseUrl.hostname}`);
|
|
376
|
+
}
|
|
377
|
+
const resSystemInfo = await axiosInstance.post('/' + service, JSON.stringify(api_1.ApiRequestSystemInformation));
|
|
378
|
+
const systemInfo = resSystemInfo.data.result[0];
|
|
379
|
+
Object.assign(device.systemInfo, systemInfo);
|
|
380
|
+
device.subscribe();
|
|
381
|
+
return device;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Initialize notifications for given events
|
|
385
|
+
*/
|
|
386
|
+
subscribe() {
|
|
387
|
+
SUBSCRIBE_NOTIFICATIONS.forEach((subscriber) => this.createWebSocket(subscriber.service));
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Disable all notifications subscriptions and close websocket connections
|
|
391
|
+
*/
|
|
392
|
+
unsubscribe() {
|
|
393
|
+
this.readyState = SonyDevice.CLOSING;
|
|
394
|
+
this.wsClients.forEach((ws, service) => {
|
|
395
|
+
this.log.debug(`Device ${this.systemInfo.name} unsubscribing from ${service} service`);
|
|
396
|
+
// unsubscribe from all notifications
|
|
397
|
+
const disables = this.getAvailibleNotifications(service);
|
|
398
|
+
if (ws.readyState === ws_1.default.OPEN) {
|
|
399
|
+
ws.send(JSON.stringify(this.switchNotifications(100, disables, [])));
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Create a new socket for the given service.
|
|
405
|
+
* If socket already exist, only request current notifications subscriptions
|
|
406
|
+
* @param service
|
|
407
|
+
* @returns
|
|
408
|
+
*/
|
|
409
|
+
createWebSocket(service) {
|
|
410
|
+
if (this.wsClients.has(service)) {
|
|
411
|
+
// if socket already created, request current notifications subscriptions
|
|
412
|
+
const ws = this.wsClients.get(service);
|
|
413
|
+
if (ws['subscriptionCommand']) {
|
|
414
|
+
ws.send(ws['subscriptionCommand']);
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const url = new url_1.URL(this.baseUrl.href);
|
|
419
|
+
url.protocol = 'ws';
|
|
420
|
+
url.pathname = url.pathname + '/' + service;
|
|
421
|
+
const ws = new ws_1.default(url);
|
|
422
|
+
function heartbeat(device) {
|
|
423
|
+
device.log.debug(`Device ${device.systemInfo.name} heartbeat init`);
|
|
424
|
+
ws['isAlive'] = true;
|
|
425
|
+
ws['heartbeat'] = setInterval(() => {
|
|
426
|
+
if (ws['isAlive'] === false) {
|
|
427
|
+
ws.terminate();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
device.log.debug(`Device ${device.systemInfo.name} heartbeat`);
|
|
431
|
+
if (ws['subscriptionCommand']) {
|
|
432
|
+
ws.send(ws['subscriptionCommand']);
|
|
433
|
+
}
|
|
434
|
+
ws['heartbeatTimeout'] = setTimeout(() => {
|
|
435
|
+
device.log.debug(`Device ${device.systemInfo.name} heartbeat timeout`);
|
|
436
|
+
ws['isAlive'] = false;
|
|
437
|
+
ws.terminate();
|
|
438
|
+
}, WEBSOCKET_REQUEST_TIMEOUT);
|
|
439
|
+
}, WEBSOCKET_HEARTBEAT_INTERVAL);
|
|
440
|
+
}
|
|
441
|
+
ws.on('open', () => {
|
|
442
|
+
this.log.debug(`Device ${this.systemInfo.name} opened a socked ${url.href}`);
|
|
443
|
+
heartbeat(this);
|
|
444
|
+
// To get current notification settings, send an empty 'switchNotifications'
|
|
445
|
+
// message with an ID of '1'
|
|
446
|
+
ws.send(JSON.stringify(this.switchNotifications(1, [], [])));
|
|
447
|
+
});
|
|
448
|
+
ws.on('message', (data) => {
|
|
449
|
+
const response = JSON.parse(data);
|
|
450
|
+
if ('id' in response) {
|
|
451
|
+
if (response.id === 1) {
|
|
452
|
+
// enable notification
|
|
453
|
+
this.log.debug(`Device ${this.systemInfo.name} received initial message ${data}`);
|
|
454
|
+
const enabled = []; // response.result[0].enabled;
|
|
455
|
+
const disabled = []; // response.result[0].disabled;
|
|
456
|
+
const shouldEnabled = SUBSCRIBE_NOTIFICATIONS.filter((s) => s.service === service)[0].notifications;
|
|
457
|
+
// if shouldEnabled equal to returned enabled, this mean what nothing to do. All ok
|
|
458
|
+
if (shouldEnabled.length === enabled.length) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// else we need to resubscribe
|
|
462
|
+
const all_notifications = [...response.result[0].enabled].concat([
|
|
463
|
+
...response.result[0].disabled,
|
|
464
|
+
]);
|
|
465
|
+
for (let i = 0; i < all_notifications.length; i++) {
|
|
466
|
+
const item = all_notifications[i];
|
|
467
|
+
if (shouldEnabled.includes(item.name)) {
|
|
468
|
+
enabled.push(item);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
disabled.push(item);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (shouldEnabled.length !== enabled.length) {
|
|
475
|
+
// something wrong... or not. For example HT-ZF9 hasn't a notifyExternalTerminalStatus. See #1
|
|
476
|
+
this.log.debug(`Device ${this.systemInfo.name} does not have the required notifier. Should be ${JSON.stringify(shouldEnabled)}, but found ${JSON.stringify(enabled)}`);
|
|
477
|
+
}
|
|
478
|
+
this.log.debug(`Device ${this.systemInfo.name} sent subscribe message ${JSON.stringify(this.switchNotifications(2, disabled, enabled))}`);
|
|
479
|
+
ws['subscriptionCommand'] = JSON.stringify(this.switchNotifications(2, disabled, enabled));
|
|
480
|
+
ws.send(ws['subscriptionCommand']);
|
|
481
|
+
}
|
|
482
|
+
else if (response.id === 100) {
|
|
483
|
+
// unsubscribe from notifications
|
|
484
|
+
clearInterval(ws['heartbeat']);
|
|
485
|
+
clearTimeout(ws['heartbeatTimeout']);
|
|
486
|
+
ws.terminate();
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
this.log.debug(`Device ${this.systemInfo.name} received subscription status ${data}`);
|
|
490
|
+
if (this.emitRestoreEvent) {
|
|
491
|
+
this.emitRestoreEvent = false;
|
|
492
|
+
this.emit("restore" /* RESTORE */);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
// here handle received notification
|
|
498
|
+
this.log.debug(`Device ${this.systemInfo.name} received notification ${data}`);
|
|
499
|
+
this.handleNotificationMessage(response);
|
|
500
|
+
}
|
|
501
|
+
clearTimeout(ws['heartbeatTimeout']);
|
|
502
|
+
});
|
|
503
|
+
ws.on('close', () => {
|
|
504
|
+
this.log.debug(`Device ${this.systemInfo.name} socket closed`);
|
|
505
|
+
this.wsClients.delete(service);
|
|
506
|
+
// If the connection was closed illegally, recreate it.
|
|
507
|
+
if (this.readyState !== SonyDevice.CLOSING) {
|
|
508
|
+
setTimeout(() => {
|
|
509
|
+
this.emitRestoreEvent = true;
|
|
510
|
+
this.createWebSocket(service);
|
|
511
|
+
}, RECONNECT_TIMEOUT);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
ws.on('error', (err) => {
|
|
515
|
+
this.log.debug(`ERROR: Device ${this.systemInfo.name} has a comunication error: ${err.message}`);
|
|
516
|
+
if (this.wsClients.has(service)) {
|
|
517
|
+
this.wsClients.get(service).terminate();
|
|
518
|
+
}
|
|
519
|
+
this.wsClients.delete(service);
|
|
520
|
+
});
|
|
521
|
+
this.wsClients.set(service, ws);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* A switchNotifications Request
|
|
525
|
+
* taken [here](https://developer.sony.com/develop/audio-control-api/get-started/websocket-example#tutorial-step-3)
|
|
526
|
+
* @param id
|
|
527
|
+
* @param disable
|
|
528
|
+
* @param enable
|
|
529
|
+
*/
|
|
530
|
+
switchNotifications(id, disable, enable) {
|
|
531
|
+
return {
|
|
532
|
+
method: 'switchNotifications',
|
|
533
|
+
id: id,
|
|
534
|
+
params: [
|
|
535
|
+
{
|
|
536
|
+
disabled: disable,
|
|
537
|
+
enabled: enable,
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
version: '1.0',
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Returns all availible notifications of the device
|
|
545
|
+
* @param service
|
|
546
|
+
* @returns
|
|
547
|
+
*/
|
|
548
|
+
getAvailibleNotifications(service) {
|
|
549
|
+
const serviceApiInfo = this.apisInfo.find((v) => v.service === service);
|
|
550
|
+
if (!serviceApiInfo) {
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
553
|
+
const notifications = [];
|
|
554
|
+
for (let i = 0; i < serviceApiInfo.notifications.length; i++) {
|
|
555
|
+
const notification = serviceApiInfo.notifications[i];
|
|
556
|
+
for (let j = 0; j < notification.versions.length; j++) {
|
|
557
|
+
const version = notification.versions[j];
|
|
558
|
+
const verNotification = {
|
|
559
|
+
name: notification.name,
|
|
560
|
+
version: version.version,
|
|
561
|
+
};
|
|
562
|
+
notifications.push(verNotification);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return notifications;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Parse the notification message recieved from device
|
|
569
|
+
* @param message
|
|
570
|
+
*/
|
|
571
|
+
handleNotificationMessage(message) {
|
|
572
|
+
switch (message.method) {
|
|
573
|
+
case "notifyPowerStatus" /* POWER */: {
|
|
574
|
+
const msg = message;
|
|
575
|
+
const power = msg.params[0].status === 'active';
|
|
576
|
+
this.emit("power" /* POWER */, power);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "notifyVolumeInformation" /* VOLUME */: {
|
|
580
|
+
const msg = message;
|
|
581
|
+
const volumeInfo = msg.params[0];
|
|
582
|
+
// update _volumeInformation
|
|
583
|
+
if (this._volumeInformation) {
|
|
584
|
+
const volumeIdx = this._volumeInformation.findIndex((v) => v.output === volumeInfo.output);
|
|
585
|
+
if (volumeIdx !== -1) {
|
|
586
|
+
Object.assign(this._volumeInformation[volumeIdx], volumeInfo);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const mute = volumeInfo.mute === 'on'
|
|
590
|
+
? true
|
|
591
|
+
: volumeInfo.mute === 'off'
|
|
592
|
+
? false
|
|
593
|
+
: null;
|
|
594
|
+
const volume = volumeInfo.volume;
|
|
595
|
+
if (volume !== -1) {
|
|
596
|
+
this.emit("volume" /* VOLUME */, volume);
|
|
597
|
+
}
|
|
598
|
+
if (mute !== null) {
|
|
599
|
+
this.emit("mute" /* MUTE */, mute);
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
case "notifyPlayingContentInfo" /* CONTENT */: {
|
|
604
|
+
// receive like {"method":"notifyPlayingContentInfo","params":[{"contentKind":"input","output":"extOutput:zone?zone=1","source":"extInput:video?port=1","uri":"extInput:video?port=1"}],"version":"1.0"}
|
|
605
|
+
const msg = message;
|
|
606
|
+
const source = msg.params[0].source || msg.params[0].uri; // maybe overcheck
|
|
607
|
+
this.emit("source" /* SOURCE */, source);
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
case "notifyExternalTerminalStatus" /* TERMINAL */: {
|
|
611
|
+
// receive like {"method":"notifyExternalTerminalStatus","params":[{"active":"active","connection":"connected","label":"","uri":"extOutput:zone?zone=1"}],"version":"1.0"}
|
|
612
|
+
const msg = message;
|
|
613
|
+
// update _externalTerminals
|
|
614
|
+
msg.params.forEach((updateTerminal) => {
|
|
615
|
+
if (this._externalTerminals) {
|
|
616
|
+
const terminalIdx = this._externalTerminals.findIndex((t) => t.uri === updateTerminal.uri);
|
|
617
|
+
if (terminalIdx !== -1) {
|
|
618
|
+
Object.assign(this._externalTerminals[terminalIdx], updateTerminal);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
this._externalTerminals.push(updateTerminal);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
// if the device is turning off from an external source, a notivication about power doesn't sends.
|
|
626
|
+
// so, force the power status check
|
|
627
|
+
this.getPowerState().then((active) => {
|
|
628
|
+
this.emit("power" /* POWER */, active);
|
|
629
|
+
});
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
default: {
|
|
633
|
+
this.log.error(`Found not implemented notification from device: ${JSON.stringify(message)}`);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Find a terminal by source name
|
|
640
|
+
* @param source the source name received from notifyPlayingContentInfo event
|
|
641
|
+
*/
|
|
642
|
+
getTerminalBySource(source) {
|
|
643
|
+
if (this._externalTerminals === null) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
const terminals = this._externalTerminals.filter((terminal) => terminal.uri === source);
|
|
647
|
+
if (terminals.length !== 0) {
|
|
648
|
+
return terminals[0];
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get current power state.
|
|
656
|
+
* * `true` if power is on
|
|
657
|
+
*/
|
|
658
|
+
async getPowerState() {
|
|
659
|
+
const service = 'system';
|
|
660
|
+
const resPowerInfo = await this.axiosInstance.post('/' + service, JSON.stringify(api_1.ApiRequestGetPowerStatus));
|
|
661
|
+
const powerInfo = resPowerInfo.data;
|
|
662
|
+
return (powerInfo.result[0].status === 'activating' ||
|
|
663
|
+
powerInfo.result[0].status === 'active');
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Get current volume state with device volume settings
|
|
667
|
+
* Volume state returns only for active zone. If no active zone then returns null
|
|
668
|
+
*/
|
|
669
|
+
async getVolumeState() {
|
|
670
|
+
const service = 'audio';
|
|
671
|
+
const resVolumeInfo = await this.axiosInstance.post('/' + service, JSON.stringify(api_1.ApiRequestVolumeInformation));
|
|
672
|
+
const volumeInfo = resVolumeInfo.data;
|
|
673
|
+
const activeZone = await this.getActiveZone();
|
|
674
|
+
if (activeZone) {
|
|
675
|
+
const volumeActiveZone = volumeInfo.result[0].find((vi) => vi.output === activeZone.uri);
|
|
676
|
+
if (volumeActiveZone) {
|
|
677
|
+
return volumeActiveZone;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return null; // no active zone
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Change the audio volume level for the active output zone
|
|
684
|
+
* @param volumeSelector the same as Characteristic.VolumeSelector in homebridge
|
|
685
|
+
* * `0` - increment
|
|
686
|
+
* * `1` - decrement
|
|
687
|
+
*/
|
|
688
|
+
async setVolume(volumeSelector) {
|
|
689
|
+
const service = 'audio';
|
|
690
|
+
const zone = await this.getActiveZone();
|
|
691
|
+
const reqSetVolume = {
|
|
692
|
+
id: 98,
|
|
693
|
+
method: 'setAudioVolume',
|
|
694
|
+
params: [
|
|
695
|
+
{
|
|
696
|
+
output: zone ? zone.uri : '',
|
|
697
|
+
volume: volumeSelector === 0 ? '+1' : '-1',
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
version: '1.1',
|
|
701
|
+
};
|
|
702
|
+
await this.axiosInstance.post('/' + service, JSON.stringify(reqSetVolume));
|
|
703
|
+
return volumeSelector;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Sets the power status of the device.
|
|
707
|
+
* @param power
|
|
708
|
+
* * `true` - set device in the power-on state
|
|
709
|
+
* * `false` - set device in the power-off state
|
|
710
|
+
*/
|
|
711
|
+
async setPower(power) {
|
|
712
|
+
const service = 'system';
|
|
713
|
+
const reqSetPower = {
|
|
714
|
+
id: 55,
|
|
715
|
+
method: 'setPowerStatus',
|
|
716
|
+
params: [
|
|
717
|
+
{
|
|
718
|
+
status: power ? 'active' : 'off',
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
version: '1.1',
|
|
722
|
+
};
|
|
723
|
+
await this.axiosInstance.post('/' + service, JSON.stringify(reqSetPower));
|
|
724
|
+
return power;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Sets the audio mute status.
|
|
728
|
+
* @param mute
|
|
729
|
+
* * `true` - muted
|
|
730
|
+
* * `false` - not muted
|
|
731
|
+
*/
|
|
732
|
+
async setMute(mute) {
|
|
733
|
+
const service = 'audio';
|
|
734
|
+
const zone = await this.getActiveZone();
|
|
735
|
+
const reqSetMute = {
|
|
736
|
+
id: 601,
|
|
737
|
+
method: 'setAudioMute',
|
|
738
|
+
params: [
|
|
739
|
+
{
|
|
740
|
+
mute: mute ? 'on' : 'off',
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
version: '1.1',
|
|
744
|
+
};
|
|
745
|
+
if (zone) {
|
|
746
|
+
reqSetMute.params[0].output = zone.uri;
|
|
747
|
+
}
|
|
748
|
+
await this.axiosInstance.post('/' + service, JSON.stringify(reqSetMute));
|
|
749
|
+
return mute;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Sets the input source
|
|
753
|
+
* @param terminal
|
|
754
|
+
*/
|
|
755
|
+
async setSource(terminal) {
|
|
756
|
+
const service = 'avContent';
|
|
757
|
+
const zone = await this.getActiveZone();
|
|
758
|
+
const reqSetPlayContent = {
|
|
759
|
+
id: 47,
|
|
760
|
+
method: 'setPlayContent',
|
|
761
|
+
params: [
|
|
762
|
+
{
|
|
763
|
+
uri: terminal.uri,
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
version: '1.2',
|
|
767
|
+
};
|
|
768
|
+
if (zone) {
|
|
769
|
+
reqSetPlayContent.params[0].output = zone.uri;
|
|
770
|
+
}
|
|
771
|
+
await this.axiosInstance.post('/' + service, JSON.stringify(reqSetPlayContent));
|
|
772
|
+
return terminal;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Toggles between the play and pause states for the current content.
|
|
776
|
+
*/
|
|
777
|
+
async setPause() {
|
|
778
|
+
const service = 'avContent';
|
|
779
|
+
const zone = await this.getActiveZone();
|
|
780
|
+
const reqPausePlayingContent = {
|
|
781
|
+
id: 31,
|
|
782
|
+
method: 'pausePlayingContent',
|
|
783
|
+
params: [{}],
|
|
784
|
+
version: '1.1',
|
|
785
|
+
};
|
|
786
|
+
if (zone) {
|
|
787
|
+
reqPausePlayingContent.params[0].output = zone.uri;
|
|
788
|
+
}
|
|
789
|
+
await this.axiosInstance.post('/' + service, JSON.stringify(reqPausePlayingContent));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Sends command codes of IR remote commander to device via IP
|
|
794
|
+
* Some info [here](https://pro-bravia.sony.net/develop/integrate/ircc-ip/overview/index.html)
|
|
795
|
+
* @param irCode
|
|
796
|
+
*/
|
|
797
|
+
async sendIRCC(irCode) {
|
|
798
|
+
if (this.axiosInstanceSoap) {
|
|
799
|
+
const data = `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1"><IRCCCode>${irCode}</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>`;
|
|
800
|
+
await this.axiosInstanceSoap.post('', data);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Press Arrow Up to select the menu items
|
|
805
|
+
* @returns
|
|
806
|
+
*/
|
|
807
|
+
async setUp() {
|
|
808
|
+
const irccCode = 'AAAAAgAAALAAAAB4AQ==';
|
|
809
|
+
return this.sendIRCC(irccCode);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Press Arrow Down to select the menu items
|
|
813
|
+
* @returns
|
|
814
|
+
*/
|
|
815
|
+
async setDown() {
|
|
816
|
+
const irccCode = 'AAAAAgAAALAAAAB5AQ==';
|
|
817
|
+
return this.sendIRCC(irccCode);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Press Arrow Right to select the menu items
|
|
821
|
+
* @returns
|
|
822
|
+
*/
|
|
823
|
+
async setRigth() {
|
|
824
|
+
const irccCode = 'AAAAAgAAALAAAAB7AQ==';
|
|
825
|
+
return this.sendIRCC(irccCode);
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Press Arrow Left to select the menu items
|
|
829
|
+
* @returns
|
|
830
|
+
*/
|
|
831
|
+
async setLeft() {
|
|
832
|
+
const irccCode = 'AAAAAgAAALAAAAB6AQ==';
|
|
833
|
+
return this.sendIRCC(irccCode);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Press Select to enter the selection
|
|
837
|
+
* @returns
|
|
838
|
+
*/
|
|
839
|
+
async setSelect() {
|
|
840
|
+
const irccCode = 'AAAAAgAAADAAAAAMAQ==';
|
|
841
|
+
return this.sendIRCC(irccCode);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Press Back for returns to the previous menu or exits a menu
|
|
845
|
+
* @returns
|
|
846
|
+
*/
|
|
847
|
+
async setBack() {
|
|
848
|
+
const irccCode = 'AAAAAwAAARAAAAB9AQ==';
|
|
849
|
+
return this.sendIRCC(irccCode);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Press Information for view some info
|
|
853
|
+
* @returns
|
|
854
|
+
*/
|
|
855
|
+
async setInformation() {
|
|
856
|
+
const irccCode = 'AAAAAgAAADAAAABTAQ==';
|
|
857
|
+
return this.sendIRCC(irccCode);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
exports.SonyDevice = SonyDevice;
|
|
861
|
+
/** The device is creating. */
|
|
862
|
+
SonyDevice.CREATING = 0;
|
|
863
|
+
/** The device is ready to communicate. */
|
|
864
|
+
SonyDevice.READY = 1;
|
|
865
|
+
/** The device is in the process of closing. */
|
|
866
|
+
SonyDevice.CLOSING = 2;
|
|
867
|
+
//# sourceMappingURL=sonyDevice.js.map
|