diff --git a/lib/lg_module.ts b/lib/lg_module.ts index 4f79766..743132e 100644 --- a/lib/lg_module.ts +++ b/lib/lg_module.ts @@ -5,8 +5,8 @@ * * sends system/turnOn **/ -import wol from 'wol' -import LGTV from 'lgtv2'; +import { LGTV, Inputs, DefaultSettings } from 'lgtv-ip-control'; +import express from 'express'; /** * Listening to a wildcard topic, anything after the topic is the command to control the TV @@ -17,10 +17,13 @@ import LGTV from 'lgtv2'; **/ export class LgWebOSModule { + + static readonly INPUT_LIVETV = "dtv"; + static readonly INPUT_HDMI1 = "hdmi1"; + static readonly INPUT_HDMI2 = "hdmi2"; + config: LGConfig; lgtv!: LGTV; - connected = false; - reties: 0; constructor(config: LGConfig) { this.config = config; @@ -28,138 +31,157 @@ export class LgWebOSModule { console.log("Subscribing to " + this.config.topic); global.SmartHub.mqttClient.subscribe(this.config.topic + "/#"); global.SmartHub.mqttClient.on('message', this.onMessage.bind(this)); - } - - connect() { - this.lgtv = LGTV({ - url: `ws://${this.config.address}:3000`, - timeout: 30000, - reconnect: 5000, - }); - return new Promise((resolve, reject) => { + DefaultSettings.networkWolAddress = "192.168.1.255"; + DefaultSettings.networkTimeout = 1000; - this.lgtv.on('error', (err: Error) => { - console.log('Connecting to TV failed!'); - }); - - this.lgtv.on('connecting', () => { - this.reties++; - if (this.reties > 4) { - console.log("Max connection attempts exceeded, discounting"); - this.lgtv.disconnect(); - this.lgtv = undefined; - this.reties = 0; - return; - } - console.log('Connecting to TV...'); - }); - - this.lgtv.on('connect', () => { - console.log('Successfully connected!'); - this.connected = true; - this.reties = 0; - resolve(true); - }); - - this.lgtv.on('prompt', () => { - console.log('Please authorize on TV'); - }); - - this.lgtv.on('close', () => { - this.connected = false; - console.log('Connection to TV lost'); - }); - }); + this.lgtv = new LGTV( + this.config.address, + this.config.macAddress, + this.config.key, + DefaultSettings + ); } - async sendCommand(command: string, message: any, powerOn: boolean = false) { - console.log("Sending " + command); + async connect() { + let retries = 0; + try { + await this.lgtv.connect(); + retries = 0; + console.log('Successfully connected!'); + return true; + } catch (err) { + retries++; + if (retries > 4) { + console.log("Max connection attempts exceeded, disconnecting"); + this.lgtv.disconnect(); + retries = 0; + return false; + } + console.log('Connecting to TV failed!'); + throw err; + } + } - if (powerOn && !this.lgtv) { - await this.powerOn(); + private async sendCommand(command: any, powerOn: boolean = false) { + if (powerOn && !this.lgtv.connected) { + await this.powerOnAndConnect(); + } else if (!this.lgtv.connected) { + await this.connect(); } - if (!this.lgtv && !this.connected) { + if (!this.lgtv.connected) { console.log("Ignoring request, TV is not ready."); return; } - return new Promise((resolve, reject) => { - this.lgtv.request("ssap://" + command, JSON.stringify(message), (err: Error | null, res: any) => { - if (err) { - reject(err); - return; - } - - resolve(res); - }); - }); - } - - powerOn() { - if (this.lgtv) { - console.log("TV already on"); - return; + try { + await command(); + } catch (err) { + console.log("Error sending command: " + err); } + } - console.log("Sending WOL to " + this.config.macAddress); - return new Promise(async (resolve, reject) => { - wol.wake(this.config.macAddress, {}, async (err: any, res: any) => { - if (err) { - reject(err); - return - } - - try { - await this.connect(); - console.log("TV ready...") - resolve(true); - } catch (error: any) { - reject(err); - } - }); - }); + async powerOnAndConnect() { + await this.lgtv.powerOn(); + await this.lgtv.connect({ maxRetries: 25, retryTimeout: 5000 }); } async powerOff() { - await this.sendCommand("system/turnOff", {}, true); - this.lgtv.disconnect(); - this.lgtv = undefined; + await this.lgtv.disconnect(); + await this.lgtv.powerOff(); } - volumeUp() { - return this.sendCommand("audio/volumeUp", {}); + async volumeUp() { + await this.sendCommand(async () => { + let currentVolume = await this.lgtv.getCurrentVolume() + 1; + await this.lgtv.setVolume(); + }); } - volumeDown() { - return this.sendCommand("audio/volumeDown", {}); + async volumeDown() { + await this.sendCommand(async () => { + let currentVolume = await this.lgtv.getCurrentVolume() - 1; + await this.lgtv.setVolume(currentVolume); + }); } - volume(level: any) { - return this.sendCommand("audio/setVolume", { volume: level }); + async volume(level: any) { + await this.sendCommand(async () => { + await this.lgtv.setVolume(level); + }); } - channel(channelNum: any) { - return this.sendCommand("tv/openChannel", { channelNumber: channelNum }, true); + async channel(channelNum: any) { + await this.sendCommand(async () => { + await this.lgtv.setDigitalChannel(channelNum); + }, true); } - /** - * com.webos.app.hdmi1 - * com.webos.app.livetv - **/ - input(input: any) { - return this.sendCommand("system.launcher/launch", { id: input }, true); + async input(input: any) { + if (Inputs[input]) { + await this.sendCommand(async () => { + await this.lgtv.setInput(Inputs[input]); + }, true); + } else { + console.log("Unknown input: " + input); + } } - onMessage(topic: any, message: any) { + //{topic_prefix}/audio/setVolume with payload { "volume": 15 } + onMessage(topic: any, payload: any) { if (topic.startsWith(this.config.topic)) { - console.log("Message recieved on " + topic); - var command = topic.replace(this.config.topic + "/", ""); - this.sendCommand(command, JSON.parse(message)); + console.log("Message received on " + topic); + const command = topic.replace(this.config.topic + "/", ""); + + const message = payload.toString(); + switch (command) { + case "audio/volumeUp": + return this.volumeUp(); + case "audio/volumeDown": + return this.volumeDown(); + case "audio/setVolume": + return this.volume(message); + case "system/turnOff": + return this.lgtv.powerOff(); + case "tv/openChannel": + return this.channel(message); + case "system.launcher/launch": + return this.input(Inputs[message]); + default: + console.log("Unknown command: " + command); + return; + } } } + static registerApis() { + let router = express.Router(); + + console.log("Registering /watchTV") + router.post("/watchTV/:id", async (req, res) => { + + let chanNum = req.query.chanNum; + + try { + //Switch TV input to LiveTV + await global.SmartHub.modules[req.params.id].input(LgWebOSModule.INPUT_LIVETV); + + //Change change + await global.SmartHub.modules[req.params.id].channel(chanNum); + + console.log("watchTV completed"); + res.sendStatus(200); + } catch (err) { + console.log(err); + + res.sendStatus(500); + } + }); + + global.SmartHub.express.use('/api', router); + } + static init() { if (!global.SmartHub.config.lg || (global.SmartHub.config.lg as LGConfig[]).length === 0) { console.log("No LG TV configuration found"); @@ -169,6 +191,8 @@ export class LgWebOSModule { (global.SmartHub.config.lg as LGConfig[]).forEach((config) => { global.SmartHub.modules[config.id] = new LgWebOSModule(config); }); + + LgWebOSModule.registerApis(); } } @@ -177,4 +201,5 @@ interface LGConfig { topic: string; address: string; macAddress: string; + key: string; // lgtv-ip-control pairing key } diff --git a/lib/mythtv_module.js b/lib/mythtv_module.js index 9b29d90..bdd1a22 100644 --- a/lib/mythtv_module.js +++ b/lib/mythtv_module.js @@ -1,4 +1,5 @@ import { InputSource } from './onkyo_module'; +import { LgWebOSModule } from './lg_module'; const http = require('http'); const express = require('express'); @@ -100,7 +101,7 @@ export class MythTvModule { try { //Switch TV input to HDMI1 - await SmartHub.modules.livingRoomTV.input("com.webos.app.hdmi2"); + await SmartHub.modules.livingRoomTV.input(LgWebOSModule.INPUT_HDMI1); //Play Recording await SmartHub.modules.mythTvModule.play(chanId, startTime); @@ -117,28 +118,6 @@ export class MythTvModule { }); - console.log("Registering /watchTV") - router.post("/watchTV", async (req, res) => { - - let chanNum = req.query.chanNum; - - try { - //Switch TV input to LiveTV - await SmartHub.modules.lgTvModule.input("com.webos.app.livetv"); - - //Change change - await SmartHub.modules.lgTvModule.channel(chanNum); - - //Switch Reciever input to TV - await SmartHub.modules["TX-NR656"].input(InputSource.TV); - - console.log("watchTV completed"); - res.sendStatus(200); - } catch (err) { - res.sendStatus(500); - } - }); - SmartHub.express.use('/api', router); } } diff --git a/lib/onkyo_module.ts b/lib/onkyo_module.ts index a48846a..56722a8 100644 --- a/lib/onkyo_module.ts +++ b/lib/onkyo_module.ts @@ -27,7 +27,7 @@ export class OnkyoModule { console.log("eISCP Scanning....") // Discover receviers on network, stop after 2 receviers or 5 seconds - eiscp.discover({ devices: this.config.length, timeout: 10 }, (err, result) => { + eiscp.discover({ devices: this.config.length, timeout: 10, address: "192.168.1.255" }, (err, result) => { if (err) { console.log("Error message: " + result); diff --git a/lib/tplink_module.js b/lib/tplink_module.js index bf38b89..f43277f 100644 --- a/lib/tplink_module.js +++ b/lib/tplink_module.js @@ -87,7 +87,7 @@ TPLinkModule.init = function () { let tpLinkModule = new TPLinkModule(SmartHub.config.tplink, SmartHub.mqttClient); tpLinkModule.subscribeHouseTopic(); tpLinkModule.discoverDevices(); - + SmartHub.modules["tpLinkModule"] = tpLinkModule; }; diff --git a/package.json b/package.json index 513148e..8f16b99 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "start": "node dist/server.js", "tsc": "tsc", + "watch": "tsc --watch", "postinstall": "npm run tsc" }, "dependencies": { @@ -13,10 +14,10 @@ "cron": "^1.4.1", "debug": "^4.1.0", "didyoumean2": "^2.0.4", - "eiscp": "file:/home/john/Development/node-eiscp", + "eiscp": "file:/home/john/Development/personal/node-eiscp", "express": "^4.16.4", "firebase-admin": "^10.0.1", - "lgtv2": "file:/home/john/Development/lgtv2", + "lgtv-ip-control": "file:/home/john/Development/personal/lgtv-ip-control/packages/lgtv-ip-control", "mqtt": "^4.3.0", "mysql": "^2.18.1", "plivo": "^4.12.0", @@ -29,9 +30,9 @@ }, "license": "MIT", "devDependencies": { - "@types/lgtv2": "^1.4.5", "@types/cookie-parser": "^1.4.9", "@types/express": "^5.0.3", + "@types/lgtv2": "^1.4.5", "@types/mqtt": "^0.0.34", "@types/node": "^24.5.2", "@types/request": "^2.48.13",