homebridge-lgwebos-tv
Version:
Homebridge plugin to control LG webOS TV.
845 lines (755 loc) • 55.3 kB
JavaScript
import { promises as fsPromises } from 'fs';
import tcpp from 'tcp-ping';
import WebSocket from 'ws';
import EventEmitter from 'events';
import ImpulseGenerator from './impulsegenerator.js';
import { ApiUrls, Pairing, SystemApps } from './constants.js';
class LgWebOsSocket extends EventEmitter {
constructor(config) {
super();
this.host = config.host;
this.inputs = config.inputs;
this.keyFile = config.keyFile;
this.devInfoFile = config.devInfoFile;
this.inputsFile = config.inputsFile;
this.channelsFile = config.channelsFile;
this.getInputsFromDevice = config.getInputsFromDevice;
this.filterSystemApps = config.filterSystemApps;
this.serviceMenu = config.serviceMenu;
this.ezAdjustMenu = config.ezAdjustMenu;
this.enableDebugMode = config.enableDebugMode;
this.sslWebSocket = config.sslWebSocket;
this.url = this.sslWebSocket ? ApiUrls.WssUrl.replace('lgwebostv', this.host) : ApiUrls.WsUrl.replace('lgwebostv', this.host);
this.webSocketPort = this.sslWebSocket ? 3001 : 3000;
this.externalInputsArr = [];
this.socketConnected = false;
this.specjalizedSocketConnected = false;
this.power = false;
this.screenState = 'Suspend';
this.appId = '';
this.appType = '';
this.volume = 0;
this.mute = false;
this.playState = false;
this.brightness = 0;
this.backlight = 0;
this.contrast = 0;
this.color = 0;
this.picturedMode = '';
this.soundMode = '';
this.soundOutput = '';
this.cidCount = 0;
this.webOS = 2.0;
this.modelName = 'LG TV';
//create impulse generator
this.impulseGenerator = new ImpulseGenerator();
this.impulseGenerator.on('heartBeat', async () => {
try {
if (this.socketConnected) {
return;
}
const debug = this.enableDebugMode ? this.emit('debug', `Plugin send heartbeat to TV`) : false;
tcpp.probe(this.host, this.webSocketPort, async (error, online) => {
if (online) {
const debug = this.enableDebugMode ? this.emit('debug', `Plugin received heartbeat from TV`) : false
await this.connect();
}
});
} catch (error) {
this.emit('error', `Impulse generatotr error: ${error}, trying again`);
}
}).on('state', (state) => {
const emit = state ? this.emit('success', `Heartbeat started`) : this.emit('warn', `Heartbeat stopped`);
});
};
async connect() {
try {
const debug = this.enableDebugMode ? this.emit('debug', `Plugin send heartbeat to TV`) : false;
//Read pairing key from file
const pairingKey = await this.readData(this.keyFile);
this.pairingKey = pairingKey.length > 10 ? pairingKey.toString() : '0';
//Socket
const socket = this.sslWebSocket ? new WebSocket(this.url, { rejectUnauthorized: false }) : new WebSocket(this.url);
socket.on('open', async () => {
const debug = this.enableDebugMode ? this.emit('debug', `Plugin received heartbeat from TV`) : false
const debug1 = this.enableDebugMode ? this.emit('debug', `Socket connected`) : false;
this.socket = socket;
this.socketConnected = true;
//connect to device success
this.emit('success', `Socket Connect Success`)
this.heartbeat = setInterval(() => {
if (socket.readyState === socket.OPEN) {
const debug = this.enableDebugMode ? this.emit('debug', `Socket send heartbeat`) : false;
socket.ping(null, false, 'UTF-8');
}
}, 5000);
//Register to tv
Pairing['client-key'] = this.pairingKey;
this.registerId = await this.getCid();
await this.send('register', undefined, Pairing, this.registerId);
})
.on('pong', () => {
const debug = this.enableDebugMode ? this.emit('debug', `Socket received heartbeat`) : false;
})
.on('close', () => {
const debug = this.enableDebugMode ? this.emit('debug', `Socket closed`) : false;
clearInterval(this.heartbeat);
socket.emit('disconnect');
})
.on('message', async (message) => {
const parsedMessage = JSON.parse(message);
const messageId = parsedMessage.id;
const messageType = parsedMessage.type;
const messageData = parsedMessage.payload;
const stringifyMessage = JSON.stringify(messageData, null, 2);
switch (messageId) {
case this.registerId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Start registering to TV: ${stringifyMessage}`) : false;
this.emit('info', 'Please accept authorization on TV');
break;
case 'registered':
const debug1 = this.enableDebugMode ? this.emit('debug', `Registered to TV with key: ${messageData['client-key']}`) : false;
//Save key in file if not saved before
const pairingKey = messageData['client-key'];
if (pairingKey !== this.pairingKey) {
await this.savePairingKey(this.keyFile, pairingKey);
this.emit('info', 'Pairing key saved');
}
//Request specjalized socket
await new Promise(resolve => setTimeout(resolve, 1500));
this.specjalizedSockedId = await this.getCid();
await this.send('request', ApiUrls.SocketUrl, undefined, this.specjalizedSockedId);
//Request system info data
this.systemInfoId = await this.getCid();
await this.send('request', ApiUrls.GetSystemInfo, undefined, this.systemInfoId);
//Request software info data
this.softwareInfoId = await this.getCid();
await this.send('request', ApiUrls.GetSoftwareInfo, undefined, this.softwareInfoId);
//Request channels list
this.channelsId = await this.getCid();
await this.send('request', ApiUrls.GetChannelList, undefined, this.channelsId);
//Request external inputs list
this.externalInputListId = await this.getCid();
await this.send('subscribe', ApiUrls.GetExternalInputList, undefined, this.externalInputListId);
//Request apps list
this.appsId = await this.getCid();
await this.send('request', ApiUrls.GetInstalledApps, undefined, this.appsId);
//Subscribe tv status
await new Promise(resolve => setTimeout(resolve, 3000));
const debug4 = this.enableDebugMode ? this.emit('debug', `Subscirbe tv status`) : false;
await this.subscribeTvStatus();
const debug5 = this.enableDebugMode ? this.emit('debug', `Subscribe tv status successful`) : false;
break;
case 'error':
const debug2 = this.enableDebugMode ? this.emit('debug', `Register to TV error: ${stringifyMessage}`) : false;
break;
default:
const debug3 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Register to TV unknown message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.specjalizedSockedId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Connecting to specjalized socket`) : false;
const socketPath = messageData.socketPath;
const specializedSocket = this.sslWebSocket ? new WebSocket(socketPath, { rejectUnauthorized: false }) : new WebSocket(socketPath);
specializedSocket.on('open', async () => {
const debug = this.enableDebugMode ? this.emit('debug', `Specialized socket connected, path: ${socketPath}`) : false;
this.specializedSocket = specializedSocket;
this.specjalizedSocketConnected = true;
//connect to deice success
this.emit('success', `Specjalized Socket Connect Success`)
specializedSocket.on('close', () => {
const debug = this.enableDebugMode ? this.emit('debug', 'Specialized socket closed') : false;
specializedSocket.emit('disconnect');
})
}).on('error', async (error) => {
const debug = this.enableDebugMode ? this.emit('debug', `Specjalized socket connect error: ${error}`) : false;
specializedSocket.emit('disconnect');
await new Promise(resolve => setTimeout(resolve, 5000));
await this.send('request', ApiUrls.SocketUrl, undefined, this.specjalizedSockedId);
}).on('disconnect', () => {
const message = this.specjalizedSocketConnected ? this.emit('info', 'Specjalized socket disconnected') : false;
this.specjalizedSocketConnected = false;
});
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Connecting to specjalized socket error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Connecting to specjalized socket unknown message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.systemInfoId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `System info: ${stringifyMessage}`) : false;
this.modelName = messageData.modelName ?? 'LG TV';
//restFul
this.emit('restFul', 'systeminfo', messageData);
//mqtt
this.emit('mqtt', 'System info', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `System info error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `System info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.softwareInfoId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Software Info: ${stringifyMessage}`) : false;
const productName = messageData.product_name;
const deviceId = messageData.device_id;
const firmwareRevision = `${messageData.major_ver}.${messageData.minor_ver}`;
const match = productName.match(/\d+(\.\d+)?/);
this.webOS = match ? parseFloat(match[0]) : this.webOS;
//save device info to the file
const obj = {
manufacturer: 'LG Electronics',
modelName: this.modelName,
productName: productName,
deviceId: deviceId,
firmwareRevision: firmwareRevision,
webOS: this.webOS
}
await this.saveData(this.devInfoFile, obj);
//emit device info
this.emit('deviceInfo', this.modelName, productName, deviceId, firmwareRevision);
//restFul
this.emit('restFul', 'softwareinfo', messageData);
//mqtt
this.emit('mqtt', 'Software info', messageData);
break;
case 'error':
const debug3 = this.enableDebugMode ? this.emit('debug', `Software info error: ${stringifyMessage}`) : false;
break;
default:
const debug4 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Software info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.channelsId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Channels list: ${stringifyMessage}`) : false;
const channelsList = messageData.channelList;
const channelListExist = Array.isArray(channelsList) ? channelsList.length > 0 : false;
if (!channelListExist) {
return;
}
const channelsArr = [];
for (const channel of channelsList) {
const name = channel.channelName;
const channelId = channel.channelId;
const number = channel.channelNumber;
const channelsObj = {
'name': name,
'reference': channelId,
'number': number,
'mode': 1
}
channelsArr.push(channelsObj);
}
//save channels to the file
await this.saveData(this.channelsFile, channelsArr);
//restFul
this.emit('restFul', 'channels', messageData);
//mqtt
this.emit('mqtt', 'Channels', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Channels error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Channels list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.externalInputListId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `External input list: ${stringifyMessage}`) : false;
const externalInputList = messageData.devices;
const externalInputListExist = Array.isArray(externalInputList) ? externalInputList.length > 0 : false;
if (!externalInputListExist) {
return;
}
//parse inputs
for (const input of externalInputList) {
const name = input.label;
const reference = input.appId;
const mode = 0;
const obj = {
'name': name,
'reference': reference,
'mode': mode
}
this.externalInputsArr.push(obj);
}
//restFul
this.emit('restFul', 'externalinputlist', messageData);
//mqtt
this.emit('mqtt', 'External Input List', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `External input list error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `External input list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.appsId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Apps list: ${stringifyMessage}`) : false;
const appsList = messageData.apps;
const appsListExist = Array.isArray(appsList) ? appsList.length > 0 : false;
if (!appsListExist) {
return;
}
//parse apps
const appsArr = [];
for (const input of appsList) {
const name = input.title;
const reference = input.id;
const mode = 0;
const obj = {
'name': name,
'reference': reference,
'mode': mode
}
appsArr.push(obj);
}
//add service menu
const serviceMenuInput = {
'name': 'Service Menu',
'reference': 'service.menu',
'mode': 0
}
const pushServiceMenu = this.serviceMenu ? appsArr.push(serviceMenuInput) : false;
//add ez adjust menu
const ezAdjustMenuInput = {
'name': 'EZ Adjust',
'reference': 'ez.adjust',
'mode': 0
}
const pushEzAdjusteMenu = this.ezAdjustMenu ? appsArr.push(ezAdjustMenuInput) : false;
//add external inputs and apps to array
const inputsApps = this.getInputsFromDevice ? [...this.externalInputsArr, ...appsArr] : this.inputs;
//chack duplicated object in array and filter system apps
const inputsArr = [];
for (const input of inputsApps) {
const inputName = input.name;
const inputReference = input.reference;
const duplicatedInput = inputsArr.some(input => input.reference === inputReference);
const filter = this.filterSystemApps ? SystemApps.includes(inputReference) : false;
const push = inputName && inputReference && !filter && !duplicatedInput ? inputsArr.push(input) : false;
}
//save apps to the file
await this.saveData(this.inputsFile, inputsArr);
//restFul
this.emit('restFul', 'apps', messageData);
//mqtt
this.emit('mqtt', 'Apps', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Apps list error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Apps list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.powerStateId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Power: ${stringifyMessage}`) : false;
switch (messageData.state) {
case 'Active':
this.power = true;
this.screenState = 'Active';
break;
case 'Active Standby':
this.power = false;
this.screenState = 'Active Standby';
break;
case 'Screen Saver':
this.power = true;
this.screenState = 'Screen Saver';
break;
case 'Screen Off':
this.power = true;
this.screenState = 'Screen Off';
break;
case 'Suspend':
this.power = false;
this.screenState = 'Suspend';
break;
default:
this.screenState = messageData.state;
const debug1 = this.enableDebugMode ? this.emit('debug', `Unknown power state: ${this.screenState}`) : false;
break;
}
this.emit('powerState', this.power, this.screenState);
const disconnect = !this.power ? socket.emit('powerOff') : false;
const emit = this.screenState === 'Screen Saver' ? this.emit('currentApp', 'com.webos.app.screensaver') : this.emit('currentApp', this.appId);
//restFul
this.emit('restFul', 'power', messageData);
//mqtt
this.emit('mqtt', 'Power', messageData);
break;
case 'error':
if (this.webOS < 3.0) {
this.power = this.socketConnected;
this.screenState = this.socketConnected ? 'Active' : 'Suspend';
this.emit('powerState', this.power, this.screenState);
const disconnect = !this.power ? socket.emit('powerOff') : false;
const debug1 = this.enableDebugMode ? this.emit('debug', `Installed system webOS: ${this.webOS}`) : false;
} else {
const debug1 = this.enableDebugMode ? this.emit('debug', `Power error: ${stringifyMessage}`) : false;
}
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Power received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.currentAppId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `App: ${stringifyMessage}`) : false;
const appId = messageData.appId ?? false;
if (!appId) {
return;
};
const app = appId === 'com.webos.app.factorywin' ? 'service.menu' : appId
this.appId = app;
const emit = this.screenState === 'Screen Saver' ? this.emit('currentApp', 'com.webos.app.screensaver') : this.emit('currentApp', app);
//restFul
this.emit('restFul', 'currentapp', messageData);
//mqtt
this.emit('mqtt', 'Current App', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `App error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `App received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.audioStateId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Audio: ${stringifyMessage}`) : false;
const messageDataKeys = Object.keys(messageData);
const scenarioExist = messageDataKeys.includes('scenario');
const volumeStatusExist = messageDataKeys.includes('volumeStatus');
const volumeStatusKeys = volumeStatusExist ? Object.keys(messageData.volumeStatus) : false;
const soundOutputExist = volumeStatusKeys ? volumeStatusKeys.includes('soundOutput') : false;
//data
const volume = messageData.volume ?? -1;
const mute = messageData.mute === true;
if (volume === -1) {
return;
}
this.emit('audioState', volume, mute, this.power);
this.volume = volume;
this.mute = mute
//restFul
this.emit('restFul', 'audio', messageData);
//mqtt
this.emit('mqtt', 'Audio', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Audio error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Audio received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.currentChannelId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Channel: ${stringifyMessage}`) : false;
const channelId = messageData.channelId ?? false;
const channelName = messageData.channelName;
const channelNumber = messageData.channelNumber;
if (!channelId) {
return;
}
this.emit('currentChannel', channelId, channelName, channelNumber);
//restFul
this.emit('restFul', 'currentchannel', messageData);
//mqtt
this.emit('mqtt', 'Current Channel', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Channel error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Channel received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.pictureSettingsId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Picture settings: ${stringifyMessage}`) : false;
const brightness = messageData.settings.brightness ?? this.brightness;
const backlight = messageData.settings.backlight ?? this.backlight;
const contrast = messageData.settings.contrast ?? this.contrast;
const color = messageData.settings.color ?? this.color;
this.brightness = brightness;
this.backlight = backlight;
this.contrast = contrast;
this.color = color;
this.emit('pictureSettings', brightness, backlight, contrast, color, this.power);
this.emit('pictureMode', 'Unknown', this.power);
//restFul
this.emit('restFul', 'picturesettings', messageData);
//mqtt
this.emit('mqtt', 'Picture Settings', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Picture settings error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Picture settings received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.pictureModeId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Picture mode: ${stringifyMessage}`) : false;
const pictureMode = stringifyMessage.pictureMode ?? false;
if (!pictureMode) {
return;
}
this.emit('pictureMode', pictureMode, this.power);
this.pictureMode = pictureMode;
//restFul
this.emit('restFul', 'picturemode', messageData);
//mqtt
this.emit('mqtt', 'Picture Mode', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Picture mode error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Picture mode received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.soundModeId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Sound mode: ${stringifyMessage}`) : false;
const soundMode = messageData.settings.soundMode ?? false;
if (!soundMode) {
return;
}
this.emit('soundMode', soundMode, this.power);
this.soundMode = soundMode;
//restFul
this.emit('restFul', 'soundmode', messageData);
//mqtt
this.emit('mqtt', 'Sound Mode', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Sound mode error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Sound mode received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.soundOutputId:
switch (messageType) {
case 'response':
const debug = this.enableDebugMode ? this.emit('debug', `Sound output: ${stringifyMessage}`) : false;
const soundOutput = messageData.soundOutput ?? false;
if (!soundOutput) {
return;
}
this.emit('soundOutput', soundOutput, this.power);
this.soundOutput = soundOutput;
//restFul
this.emit('restFul', 'soundoutput', messageData);
//mqtt
this.emit('mqtt', 'Sound Output', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Sound output error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Sound output received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.mediaInfoId:
switch (messageType) {
case 'response':
// {"subscribed":true,"returnValue":true,"foregroundAppInfo":[{"appId":"netflix","playState":"loaded","type":"media","mediaId":"_fdTzfnKXXXXX","windowId":"_Window_Id_1"}]}
// reported playState values: starting, loaded, playing, paused
const debug = this.enableDebugMode ? this.emit('debug', `Media info: ${stringifyMessage}`) : false;
const foregroundAppInfo = messageData.foregroundAppInfo ?? [];
const mediaAppOpen = foregroundAppInfo.length > 0;
if (!mediaAppOpen) {
return;
}
const playState = foregroundAppInfo[0].playState === 'playing';
const appType = foregroundAppInfo[0].type ?? '';
this.playState = playState;
this.appType = appType;
//emit
this.emit('mediaInfo', playState, appType);
//restFul
this.emit('restFul', 'mediainfo', messageData);
//mqtt
this.emit('mqtt', 'Media Info', messageData);
break;
case 'error':
const debug1 = this.enableDebugMode ? this.emit('debug', `Media info error: ${stringifyMessage}`) : false;
break;
default:
const debug2 = this.enableDebugMode ? this.emit('debug', this.emit('debug', `Medias info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`)) : false;
break;
};
break;
case this.alertCid:
const debug = this.enableDebugMode ? this.emit('debug', `Alert: ${stringifyMessage}`) : false;
const alertId = messageData.alertId ?? false;
if (!alertId) {
return;
}
const closeAlert = this.webOS >= 4.0 ? await this.send('request', ApiUrls.CloseAletrt, { alertId: alertId }) : await this.send('button', undefined, { name: 'ENTER' });
break;
case this.toastCid:
const debug1 = this.enableDebugMode ? this.emit('debug', `Toast: ${stringifyMessage}`) : false;
const toastId = messageData.toastId ?? false;
if (!toastId) {
return;
}
const closeToast = this.webOS >= 4.0 ? await this.send('request', ApiUrls.CloseToast, { toastId: toastId }) : await this.send('button', undefined, { name: 'ENTER' });
break;
default:
const debug3 = this.enableDebugMode ? this.emit('debug', `Received message type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`) : false;
break;
}
})
.on('error', (error) => {
const debug = this.enableDebugMode ? this.emit('debug', `Socket connect error: ${error}`) : false;
socket.emit('disconnect');
})
.on('powerOff', () => {
//update TV state
this.emit('powerState', false, this.screenState);
this.emit('playState', false, this.appType);
this.emit('audioState', this.volume, this.mute, this.power);
this.emit('pictureSettings', 0, 0, 0, 0, false);
this.emit('pictureMode', 'Unknown', false);
this.emit('soundMode', this.soundMode, false);
this.emit('soundOutput', this.soundOutput, false);
})
.on('disconnect', () => {
const message = this.socketConnected ? this.emit('info', 'Socket disconnected') : false;
this.socketConnected = false;
this.cidCount = 0;
this.power = false;
this.screenState = 'Suspend';
//update TV state
this.emit('powerState', false, 'Suspend');
this.emit('playState', false, this.appType);
this.emit('audioState', this.volume, this.mute, this.power);
this.emit('pictureSettings', 0, 0, 0, 0, false);
this.emit('pictureMode', this.pictureMode, false);
this.emit('soundMode', this.soundMode, false);
this.emit('soundOutput', this.soundOutput, false);
});
return true;
} catch (error) {
throw new Error(`Connect error: ${error}`);
}
}
async savePairingKey(path, data) {
try {
await fsPromises.writeFile(path, data);
const debug = this.enableDebugMode ? this.emit('debug', `Saved pairing key: ${data}`) : false;
return true;
} catch (error) {
throw new Error(`Save pairing key error: ${error}`);
}
}
async saveData(path, data) {
try {
data = JSON.stringify(data, null, 2);
await fsPromises.writeFile(path, data);
const debug = this.enableDebugMode ? this.emit('debug', `Saved data: ${data}`) : false;
return true;
} catch (error) {
throw new Error(`Save data error: ${error}`);
}
}
async readData(path) {
try {
const data = await fsPromises.readFile(path);
const debug = this.enableDebugMode ? this.emit('debug', `Read data: ${JSON.stringify(data, null, 2)}`) : false;
return data;;
} catch (error) {
throw new Error(`Read data error: ${error}`);
}
}
async subscribeTvStatus() {
try {
this.powerStateId = await this.getCid();
await this.send('subscribe', ApiUrls.GetPowerState, undefined, this.powerStateId);
this.currentAppId = await this.getCid();
await this.send('subscribe', ApiUrls.GetForegroundAppInfo, undefined, this.currentAppId);
this.currentChannelId = await this.getCid();
await this.send('subscribe', ApiUrls.GetCurrentChannel, undefined, this.currentChannelId);
this.audioStateId = await this.getCid();
await this.send('subscribe', ApiUrls.GetAudioStatus, undefined, this.audioStateId);
this.soundOutputId = await this.getCid();
await this.send('subscribe', ApiUrls.GetSoundOutput, undefined, this.soundOutputId);
//picture settings
if (this.webOS >= 4.0) {
const payload = {
category: 'picture',
keys: ['brightness', 'backlight', 'contrast', 'color']
}
this.pictureSettingsId = await this.getCid();
await this.send('subscribe', ApiUrls.GetSystemSettings, payload, this.pictureSettingsId);
}
//picture mode
if (this.webOS >= 4.0) {
const payload = {
category: 'picture',
keys: ['pictureMode']
}
this.pictureModeId = await this.getCid();
//await this.send('alert', ApiUrls.GetSystemSettings, payload, this.pictureModeId);
}
//sound mode
if (this.webOS >= 6.0) {
const payload = {
category: 'sound',
keys: ['soundMode']
}
this.soundModeId = await this.getCid();
await this.send('subscribe', ApiUrls.GetSystemSettings, payload, this.soundModeId);
}
//media info
if (this.webOS >= 7.0) {
this.mediaInfo