lgtv2mqtt2
Version:
connect WebOS LG TV with MQTT
261 lines (219 loc) • 5.83 kB
JavaScript
import mqtt from "mqtt";
import fs from "fs";
import path from "path";
import yargs from "yargs";
import { homedir } from "os";
import LgTvController from "./vendor/LgTvController.js";
import Events from "./vendor/Events.js";
import getConfig from "./get-config.js";
import paths from "./paths.js";
function migrateKeyfile() {
const oldPath = path.join(homedir(), ".lgtv-keyfile");
const newPath = path.join(paths.data, "keyfile");
if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
fs.mkdirSync(paths.data, { recursive: true });
fs.renameSync(oldPath, newPath);
console.log(`Migrated ${oldPath} -> ${newPath}`);
}
}
migrateKeyfile();
const argv = yargs(process.argv)
.option("keyfile", {
describe: "Path to the keyfile",
type: "string",
default: path.join(paths.data, "keyfile"),
})
.option("log-level", {
describe: "Set the log level",
choices: ["debug", "info", "warn", "error"],
default: "info",
})
.parse();
const MQTT_CONFIG = getConfig(
"mqtt.json",
{
host: "MQTT_BROKER_ADDRESS",
username: "MQTT_BROKER_USERNAME",
password: "MQTT_BROKER_PASSWORD",
},
".mqtt-config.json"
);
const LGTV_CONFIG = getConfig(
"lgtv.json",
{
ip: "LGTV_IP",
mac: "LGTV_MAC",
mqttBase: "MQTT_BASE_PATH",
},
".lgtv-config.json"
);
const statusTopic = LGTV_CONFIG.mqttBase + "/lwt";
const client = mqtt.connect({
...MQTT_CONFIG,
will: {
topic: statusTopic,
payload: "Offline",
retain: true,
},
});
const loggerPrecedence = {
debug: ["debug", "info", "warn", "error"],
info: ["info", "warn", "error"],
warn: ["warn", "error"],
error: ["error"],
};
const allowedLoggers = loggerPrecedence[argv["log-level"]];
function NOP() {}
const lg = new LgTvController(
LGTV_CONFIG.ip,
LGTV_CONFIG.mac,
"LG TV",
argv.keyfile,
undefined,
undefined,
{
info: allowedLoggers.includes("info") ? console.info : NOP,
warn: allowedLoggers.includes("warn") ? console.warn : NOP,
debug: allowedLoggers.includes("debug") ? console.debug : NOP,
error: allowedLoggers.includes("error") ? console.error : NOP,
}
);
lg.connect();
const state = {};
const config = {
power: {
onLgEvents: {
[Events.TV_TURNED_ON]: () => {
publishMqttMessageIfDiffers("power", "on");
publishMqttMessageIfDiffers("screen", "on");
},
[Events.TV_TURNED_OFF]: () => {
publishMqttMessageIfDiffers("power", "off");
// reset all other settings to "0" when powered off
publishMqttMessageIfDiffers("backlight", "0");
publishMqttMessageIfDiffers("volume", "0");
publishMqttMessageIfDiffers("screen", "off");
},
},
onMqttMessage: (value) => {
if (value === "on") {
lg.turnOn();
}
if (value === "off") {
lg.turnOff();
}
},
},
volume: {
onLgEvents: {
[Events.AUDIO_STATUS_CHANGED]: (value) => {
publishMqttMessageIfDiffers("volume", `${value.volume}`);
},
},
onMqttMessage: (value) => {
if (!lg.isTvOn()) {
return;
}
lg.setVolumeLevel(parseInt(value));
},
},
backlight: {
onLgEvents: {
[Events.PICTURE_SETTINGS_CHANGED]: (value) => {
publishMqttMessageIfDiffers("backlight", `${value.backlight}`);
},
},
onMqttMessage: (value) => {
if (!lg.isTvOn()) {
return;
}
lg.setBacklight(parseInt(value));
},
},
screen: {
onLgEvents: {
[Events.SCREEN_STATE_CHANGED]: (value) => {
if (value.state === "Screen On" || value.processing === "Screen On") {
publishMqttMessageIfDiffers("screen", "on");
} else if (value.state === "Screen Off") {
publishMqttMessageIfDiffers("screen", "off");
}
},
},
onMqttMessage: (value) => {
if (!lg.isTvOn()) {
return;
}
if (value === "on") {
lg.turnOnTvScreen();
}
if (value === "off") {
lg.turnOffTvScreen();
}
},
},
input: {
onLgEvents: {
[Events.FOREGROUND_APP_CHANGED]: (value) => {
// not sure what else can come up here
if (value.appId.includes("hdmi")) {
publishMqttMessageIfDiffers("input", value.appId);
}
},
},
onMqttMessage: (value) => {
if (!lg.isTvOn()) {
return;
}
lg.launchApp(value);
},
},
};
function publishMqttMessageIfDiffers(topic, value) {
if (state[topic] !== value) {
client.publishAsync(LGTV_CONFIG.mqttBase + "/" + topic, value, {
retain: true,
});
state[topic] = value;
}
}
client.on("message", (topic, message) => {
topic = topic.replace(LGTV_CONFIG.mqttBase + "/", "");
const mqttValue = message.toString();
if (!config[topic]) {
console.log("no config for topic:", topic);
return;
}
console.log(
"got mqtt message for topic:",
topic,
"with value:",
mqttValue,
"current state value is:",
state[topic]
);
if (state[topic] !== mqttValue) {
config[topic].onMqttMessage(mqttValue);
state[topic] = mqttValue;
}
});
Object.values(config).forEach(({ onLgEvents = {} }) => {
Object.entries(onLgEvents).forEach(([event, handler]) => {
lg.on(event, (value) => {
console.log("got lg event:", event, "with value:", value);
handler(value);
});
});
});
client.on("connect", () => {
client.publishAsync(statusTopic, "Online", { retain: true });
Object.keys(config).forEach((topic) => {
console.log("subscribing to topic:", topic);
client.subscribe(LGTV_CONFIG.mqttBase + "/" + topic);
});
});
lg.on(Events.SETUP_FINISHED, () => {
console.log("setup finished!\nlist of external inputs:");
console.log(lg.getExternalInputList());
});