├── .gitignore ├── graphics ├── RC.png ├── rc1.png ├── inputs.png ├── homekit.png ├── lgwebos.png └── ustawienia.png ├── .github └── workflows │ └── stale.yml ├── LICENSE ├── src ├── impulsegenerator.js ├── wol.js ├── functions.js ├── restful.js ├── mqtt.js ├── constants.js └── lgwebossocket.js ├── package.json ├── index.js ├── sample-config.json ├── README.md ├── CHANGELOG.md └── config.schema.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | .vscode 5 | -------------------------------------------------------------------------------- /graphics/RC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/RC.png -------------------------------------------------------------------------------- /graphics/rc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/rc1.png -------------------------------------------------------------------------------- /graphics/inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/inputs.png -------------------------------------------------------------------------------- /graphics/homekit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/homekit.png -------------------------------------------------------------------------------- /graphics/lgwebos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/lgwebos.png -------------------------------------------------------------------------------- /graphics/ustawienia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz914/homebridge-lgwebos-tv/HEAD/graphics/ustawienia.png -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Runs daily at midnight (UTC) 6 | workflow_dispatch: # Allows you to manually trigger the workflow 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v8 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.' 16 | days-before-stale: 30 17 | days-before-close: 7 18 | stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.' 19 | stale-issue-label: 'stale' 20 | exempt-issue-labels: 'pinned,security' 21 | close-issue-message: 'Closing this issue due to inactivity.' 22 | close-pr-message: 'Closing this pull request due to inactivity.' 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 Grzegorz Kaczor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | -------------------------------------------------------------------------------- /src/impulsegenerator.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | 3 | class ImpulseGenerator extends EventEmitter { 4 | constructor() { 5 | super(); 6 | this.timersState = false; 7 | this.timers = []; 8 | } 9 | 10 | async state(state, timers = []) { 11 | // Stop current timers before new start 12 | if (this.timersState && state) { 13 | await this.state(false); 14 | } 15 | 16 | if (state) { 17 | if (!Array.isArray(timers)) throw new Error('Timers must be an array'); 18 | 19 | for (const { name, sampling } of timers) { 20 | if (!name || !sampling) continue; 21 | 22 | this.emit(name); 23 | 24 | const interval = setInterval(() => { 25 | this.emit(name); 26 | }, sampling); 27 | 28 | this.timers.push(interval); 29 | } 30 | } else { 31 | this.timers.forEach(clearInterval); 32 | this.timers = []; 33 | } 34 | 35 | this.timersState = state; 36 | this.emit('state', state); 37 | return true; 38 | } 39 | } 40 | 41 | export default ImpulseGenerator; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "LG webOS TV", 3 | "name": "homebridge-lgwebos-tv", 4 | "version": "4.0.3", 5 | "description": "Homebridge plugin to control LG webOS TV.", 6 | "license": "MIT", 7 | "author": "grzegorz914", 8 | "maintainers": [ 9 | "grzegorz914" 10 | ], 11 | "homepage": "https://github.com/grzegorz914/homebridge-lgwebos-tv#readme", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/grzegorz914/homebridge-lgwebos-tv.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/grzegorz914/homebridge-lgwebos-tv/issues" 18 | }, 19 | "type": "module", 20 | "exports": { 21 | ".": "./index.js" 22 | }, 23 | "files": [ 24 | "src", 25 | "index.js", 26 | "config.schema.json", 27 | "package.json", 28 | "CHANGELOG.md", 29 | "README.md", 30 | "LICENSE" 31 | ], 32 | "engines": { 33 | "homebridge": "^1.8.0 || ^2.0.0 || ^2.0.0-beta.40 || ^2.0.0-alpha.60", 34 | "node": "^20 || ^22 || ^24 || ^25" 35 | }, 36 | "dependencies": { 37 | "mqtt": "^5.14.1", 38 | "ws": "^8.18.3", 39 | "express": "^5.2.1" 40 | }, 41 | "keywords": [ 42 | "homebridge", 43 | "homebridge-plugin", 44 | "homekit", 45 | "webos", 46 | "lg", 47 | "mqtt", 48 | "restful" 49 | ], 50 | "contributors": [], 51 | "scripts": { 52 | "test": "echo \"Error: no test specified\" && exit 1" 53 | } 54 | } -------------------------------------------------------------------------------- /src/wol.js: -------------------------------------------------------------------------------- 1 | import Dgram from 'dgram'; 2 | import EventEmitter from 'events'; 3 | 4 | class WakeOnLan extends EventEmitter { 5 | constructor(config) { 6 | super(); 7 | this.mac = config.mac; 8 | this.broadcastAddress = config.power?.broadcastAddress || '255.255.255.255'; 9 | this.logError = config.log?.error; 10 | this.logDebug = config.log?.debug; 11 | } 12 | 13 | async wakeOnLan() { 14 | return new Promise((resolve, reject) => { 15 | try { 16 | // Parse MAC once 17 | const macBytes = this.mac.split(':').map(hex => parseInt(hex, 16)); 18 | const magicPacket = Buffer.alloc(102); 19 | magicPacket.fill(0xFF, 0, 6); 20 | for (let i = 6; i < 102; i += 6) { 21 | macBytes.forEach((byte, j) => magicPacket.writeUInt8(byte, i + j)); 22 | } 23 | 24 | const socket = Dgram.createSocket('udp4') 25 | .on('error', (error) => { 26 | reject(error); 27 | }) 28 | .on('close', async () => { 29 | if (this.logDebug) this.emit('debug', `WoL socket closed`); 30 | await new Promise(r => setTimeout(r, 2500)); 31 | resolve(true); 32 | }) 33 | .on('listening', () => { 34 | socket.setBroadcast(true); 35 | const address = socket.address(); 36 | if (this.logDebug) this.emit('debug', `WoL listening: ${address.address}:${address.port}`); 37 | 38 | const sendMagicPacket = (attempt) => { 39 | if (attempt > 4) { 40 | socket.close(); 41 | return; 42 | } 43 | socket.send(magicPacket, 0, magicPacket.length, 9, this.broadcastAddress, (error, bytes) => { 44 | if (error) { 45 | if (this.logError) this.emit('error', error); 46 | socket.close(); 47 | } else { 48 | if (this.logDebug) this.emit('debug', `Sent WoL to ${this.broadcastAddress}:9, ${bytes}B (try ${attempt})`); 49 | setTimeout(() => sendMagicPacket(attempt + 1), 100); 50 | } 51 | }); 52 | }; 53 | 54 | sendMagicPacket(1); 55 | }) 56 | .bind({ port: 0, exclusive: true }); 57 | } catch (error) { 58 | reject(error); 59 | } 60 | }); 61 | } 62 | } 63 | 64 | export default WakeOnLan; 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/functions.js: -------------------------------------------------------------------------------- 1 | import { promises as fsPromises } from 'fs'; 2 | import { exec } from 'node:child_process'; 3 | import { promisify } from 'node:util'; 4 | import { DiacriticsMap } from './constants.js'; 5 | const execAsync = promisify(exec); 6 | 7 | class Functions { 8 | constructor() { 9 | } 10 | 11 | async saveData(path, data, stringify = true) { 12 | try { 13 | data = stringify ? JSON.stringify(data, null, 2) : data; 14 | await fsPromises.writeFile(path, data); 15 | return true; 16 | } catch (error) { 17 | throw new Error(`Save data error: ${error}`); 18 | } 19 | } 20 | 21 | async readData(path, parseJson = false) { 22 | try { 23 | const data = await fsPromises.readFile(path, 'utf8'); 24 | 25 | if (parseJson) { 26 | if (!data.trim()) { 27 | // Empty file when expecting JSON 28 | return null; 29 | } 30 | try { 31 | return JSON.parse(data); 32 | } catch (jsonError) { 33 | throw new Error(`JSON parse error in file "${path}": ${jsonError.message}`); 34 | } 35 | } 36 | 37 | // For non-JSON, just return file content (can be empty string) 38 | return data; 39 | } catch (error) { 40 | if (error.code === 'ENOENT') { 41 | // File does not exist 42 | return null; 43 | } 44 | // Preserve original error details 45 | const wrappedError = new Error(`Read data error for "${path}": ${error.message}`); 46 | wrappedError.original = error; 47 | throw wrappedError; 48 | } 49 | } 50 | 51 | async sanitizeString(str) { 52 | if (!str) return ''; 53 | 54 | // Replace diacritics using map 55 | str = str.replace(/[^\u0000-\u007E]/g, ch => DiacriticsMap[ch] || ch); 56 | 57 | // Replace separators between words with space 58 | str = str.replace(/(\w)[.:;+\-\/]+(\w)/g, '$1 $2'); 59 | 60 | // Replace remaining standalone separators with space 61 | str = str.replace(/[.:;+\-\/]/g, ' '); 62 | 63 | // Remove remaining invalid characters (keep letters, digits, space, apostrophe) 64 | str = str.replace(/[^A-Za-z0-9 ']/g, ' '); 65 | 66 | // Collapse multiple spaces 67 | str = str.replace(/\s+/g, ' '); 68 | 69 | // Trim 70 | return str.trim(); 71 | } 72 | 73 | async scaleValue(value, inMin, inMax, outMin, outMax) { 74 | const scaledValue = parseFloat((((Math.max(inMin, Math.min(inMax, value)) - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin).toFixed(0)); 75 | return scaledValue; 76 | } 77 | 78 | async ping(ipOrHost) { 79 | const isWindows = process.platform === 'win32'; 80 | const cmd = isWindows ? `ping -n 1 ${ipOrHost}` : `ping -c 1 ${ipOrHost}`; 81 | 82 | try { 83 | const { stdout } = await execAsync(cmd); 84 | const alive = /ttl=/i.test(stdout); 85 | const timeMatch = stdout.match(/time[=<]([\d.]+)\s*ms/i); 86 | const time = timeMatch ? parseFloat(timeMatch[1]) : null; 87 | 88 | return { online: alive, time }; 89 | } catch { 90 | return { online: false }; 91 | } 92 | } 93 | } 94 | export default Functions -------------------------------------------------------------------------------- /src/restful.js: -------------------------------------------------------------------------------- 1 | import express, { json } from 'express'; 2 | import EventEmitter from 'events'; 3 | 4 | const DEFAULT_MESSAGE = 'This data is not available at this time.'; 5 | 6 | class RestFul extends EventEmitter { 7 | constructor(config) { 8 | super(); 9 | this.port = config.port; 10 | this.logWarn = config.logWarn; 11 | this.logDebug = config.logDebug; 12 | 13 | this.data = { 14 | systeminfo: DEFAULT_MESSAGE, 15 | softwareinfo: DEFAULT_MESSAGE, 16 | channels: DEFAULT_MESSAGE, 17 | power: DEFAULT_MESSAGE, 18 | apps: DEFAULT_MESSAGE, 19 | audio: DEFAULT_MESSAGE, 20 | currentapp: DEFAULT_MESSAGE, 21 | currentchannel: DEFAULT_MESSAGE, 22 | picturesettings: DEFAULT_MESSAGE, 23 | soundmode: DEFAULT_MESSAGE, 24 | soundoutput: DEFAULT_MESSAGE, 25 | externalinputlist: DEFAULT_MESSAGE, 26 | mediainfo: DEFAULT_MESSAGE 27 | } 28 | this.connect(); 29 | } 30 | 31 | connect() { 32 | try { 33 | const app = express(); 34 | app.set('json spaces', 2); 35 | app.use(json()); 36 | 37 | // Register GET routes for all keys 38 | for (const key of Object.keys(this.data)) { 39 | app.get(`/${key}`, (req, res) => { 40 | res.json(this.data[key]); 41 | }); 42 | } 43 | 44 | // Health check route 45 | app.get('/status', (req, res) => { 46 | res.json({ 47 | status: 'online', 48 | uptime: process.uptime(), 49 | available_paths: Object.keys(this.data).map(k => `/${k}`) 50 | }); 51 | }); 52 | 53 | // POST route to update values 54 | app.post('/', (req, res) => { 55 | try { 56 | const obj = req.body; 57 | if (!obj || typeof obj !== 'object' || Object.keys(obj).length === 0) { 58 | if (this.logWarn) this.emit('warn', 'RESTFul Invalid JSON payload'); 59 | return res.status(400).json({ error: 'RESTFul Invalid JSON payload' }); 60 | } 61 | 62 | const key = Object.keys(obj)[0]; 63 | const value = obj[key]; 64 | this.emit('set', key, value); 65 | this.update(key, value); 66 | 67 | if (this.logdebug) this.emit('debug', `RESTFul post data: ${JSON.stringify(obj, null, 2)}`); 68 | 69 | res.json({ success: true, received: obj }); 70 | } catch (error) { 71 | if (this.logWarn) this.emit('warn', `RESTFul Parse error: ${error}`); 72 | res.status(500).json({ error: 'RESTFul Internal Server Error' }); 73 | } 74 | }); 75 | 76 | // Start the server 77 | app.listen(this.port, () => { 78 | this.emit('connected', `RESTful started on port: ${this.port}`); 79 | }); 80 | } catch (error) { 81 | if (this.logWarn) this.emit('warn', `RESTful Connect error: ${error}`); 82 | } 83 | } 84 | 85 | update(path, data) { 86 | if (this.data.hasOwnProperty(path)) { 87 | this.data[path] = data; 88 | } else { 89 | if (this.logWarn) this.emit('warn', `Unknown RESTFul update path: ${path}, data: ${JSON.stringify(data)}`); 90 | return; 91 | } 92 | 93 | if (this.logdebug) this.emit('debug', `RESTFul update path: ${path}, data: ${JSON.stringify(data)}`); 94 | } 95 | } 96 | export default RestFul; -------------------------------------------------------------------------------- /src/mqtt.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'mqtt'; 2 | import EventEmitter from 'events'; 3 | 4 | class Mqtt extends EventEmitter { 5 | constructor(config) { 6 | super(); 7 | 8 | const url = `mqtt://${config.host}:${config.port}`; 9 | const subscribeTopic = `${config.prefix}/Set`; 10 | 11 | const options = { 12 | clientId: config.clientId, 13 | username: config.user, 14 | password: config.passwd, 15 | protocolVersion: 5, 16 | clean: false, 17 | properties: { 18 | sessionExpiryInterval: 60 * 60, // 1 hour 19 | userProperties: { 20 | source: 'node-client' 21 | } 22 | } 23 | }; 24 | 25 | this.mqttClient = connect(url, options); 26 | 27 | // === CONNECTED === 28 | this.mqttClient.on('connect', async (packet) => { 29 | this.emit('connected', 'MQTT v5 connected.'); 30 | 31 | try { 32 | const result = await this.mqttClient.subscribeAsync(subscribeTopic, { 33 | qos: 1, 34 | properties: { 35 | userProperties: { 36 | type: 'subscription' 37 | } 38 | } 39 | }); 40 | 41 | // MQTT v5 subscription results contain reason codes 42 | if (config.logDebug) this.emit('debug', `Subscribed to ${subscribeTopic}, reason codes: ${JSON.stringify(result)}`); 43 | this.emit('subscribed', `MQTT Subscribe topic: ${subscribeTopic}`); 44 | 45 | } catch (error) { 46 | if (config.logWarn) this.emit('warn', `MQTT Subscribe error: ${error}`); 47 | } 48 | }); 49 | 50 | // === MESSAGE === 51 | this.mqttClient.on('message', (topic, payload, packet) => { 52 | try { 53 | const obj = JSON.parse(payload.toString()); 54 | if (config.logDebug) this.emit('debug', `MQTT Received:\nTopic: ${topic}\nPayload: ${JSON.stringify(obj, null, 2)}\nProperties: ${JSON.stringify(packet.properties, null, 2)}`); 55 | 56 | const key = Object.keys(obj)[0]; 57 | const value = Object.values(obj)[0]; 58 | this.emit('set', key, value); 59 | 60 | } catch (error) { 61 | if (config.logWarn) this.emit('warn', `MQTT Parse error: ${error}`); 62 | } 63 | }); 64 | 65 | // === PUBLISH EVENT === 66 | this.on('publish', async (topic, message) => { 67 | try { 68 | const fullTopic = `${config.prefix}/${topic}`; 69 | const publishMessage = JSON.stringify(message); 70 | 71 | await this.mqttClient.publishAsync(fullTopic, publishMessage, { 72 | qos: 1, 73 | properties: { 74 | contentType: 'application/json', 75 | userProperties: { 76 | source: 'node', 77 | action: 'set' 78 | } 79 | } 80 | }); 81 | 82 | if (config.logDebug) this.emit('debug', `MQTT Publish:\nTopic: ${fullTopic}\nPayload: ${publishMessage}`); 83 | } catch (error) { 84 | if (config.logWarn) this.emit('warn', `MQTT Publish error: ${error}`); 85 | } 86 | }); 87 | 88 | // === ERRORS / STATE === 89 | this.mqttClient.on('error', (err) => { 90 | this.emit('warn', `MQTT Error: ${err.message}`); 91 | }); 92 | 93 | this.mqttClient.on('reconnect', () => { 94 | if (config.logDebug) this.emit('debug', 'MQTT Reconnecting...'); 95 | }); 96 | 97 | this.mqttClient.on('close', () => { 98 | if (config.logDebug) this.emit('debug', 'MQTT Connection closed.'); 99 | }); 100 | } 101 | } 102 | 103 | export default Mqtt; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { mkdirSync, existsSync, writeFileSync } from 'fs'; 3 | import LgWebOsDevice from './src/lgwebosdevice.js'; 4 | import ImpulseGenerator from './src/impulsegenerator.js'; 5 | import { PluginName, PlatformName } from './src/constants.js'; 6 | 7 | class LgWebOsPlatform { 8 | constructor(log, config, api) { 9 | if (!config || !Array.isArray(config.devices)) { 10 | log.warn(`No configuration found for ${PluginName}.`); 11 | return; 12 | } 13 | 14 | this.accessories = []; 15 | 16 | const prefDir = join(api.user.storagePath(), 'lgwebosTv'); 17 | try { 18 | mkdirSync(prefDir, { recursive: true }); 19 | } catch (error) { 20 | log.error(`Prepare directory error: ${error.message ?? error}`); 21 | return; 22 | } 23 | 24 | api.on('didFinishLaunching', async () => { 25 | for (const device of config.devices) { 26 | const { name, host, mac, displayType } = device; 27 | const macValid = /^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$/.test(mac); 28 | if (!name || !host || !macValid || !displayType) { 29 | log.warn(`Device: ${host || 'host missing'}, ${name || 'name missing'}, ${mac || 'mac missing'}${!displayType ? ', disply type disabled' : ''} in config, will not be published in the Home app`); 30 | continue; 31 | } 32 | 33 | //log config 34 | const logLevel = { 35 | devInfo: device.log?.deviceInfo, 36 | success: device.log?.success, 37 | info: device.log?.info, 38 | warn: device.log?.warn, 39 | error: device.log?.error, 40 | debug: device.log?.debug 41 | }; 42 | 43 | if (logLevel.debug) { 44 | log.info(`Device: ${host} ${name}, did finish launching.`); 45 | const safeConfig = { 46 | ...device, 47 | mqtt: { 48 | auth: { 49 | ...device.mqtt?.auth, 50 | passwd: 'removed', 51 | } 52 | }, 53 | }; 54 | log.info(`Device: ${host} ${name}, Config: ${JSON.stringify(safeConfig, null, 2)}`); 55 | } 56 | 57 | const postFix = host.replace(/\./g, ''); 58 | const files = { 59 | key: `${prefDir}/key_${postFix}`, 60 | devInfo: `${prefDir}/devInfo_${postFix}`, 61 | inputs: `${prefDir}/inputs_${postFix}`, 62 | channels: `${prefDir}/channels_${postFix}`, 63 | inputsNames: `${prefDir}/inputsNames_${postFix}`, 64 | inputsVisibility: `${prefDir}/inputsTargetVisibility_${postFix}`, 65 | }; 66 | 67 | try { 68 | Object.values(files).forEach((file) => { 69 | if (!existsSync(file)) { 70 | writeFileSync(file, ''); 71 | } 72 | }); 73 | } catch (error) { 74 | if (logLevel.error) log.error(`Device: ${host} ${name}, Prepare files error: ${error.message ?? error}`); 75 | continue; 76 | } 77 | 78 | try { 79 | // create impulse generator 80 | const impulseGenerator = new ImpulseGenerator() 81 | .on('start', async () => { 82 | try { 83 | const lgDevice = new LgWebOsDevice(api, device, files.key, files.devInfo, files.inputs, files.channels, files.inputsNames, files.inputsVisibility) 84 | .on('devInfo', (info) => log.info(info)) 85 | .on('success', (msg) => log.success(`Device: ${host} ${name}, ${msg}`)) 86 | .on('info', (msg) => log.info(`Device: ${host} ${name}, ${msg}`)) 87 | .on('debug', (msg) => log.info(`Device: ${host} ${name}, debug: ${msg}`)) 88 | .on('warn', (msg) => log.warn(`Device: ${host} ${name}, ${msg}`)) 89 | .on('error', (msg) => log.error(`Device: ${host} ${name}, ${msg}`)); 90 | 91 | const accessory = await lgDevice.start(); 92 | if (accessory) { 93 | api.publishExternalAccessories(PluginName, [accessory]); 94 | if (logLevel.success) log.success(`Device: ${host} ${name}, Published as external accessory.`); 95 | 96 | await new Promise(resolve => setTimeout(resolve, 3000)); 97 | await lgDevice.startStopImpulseGenerator(true, [{ name: 'heartBeat', sampling: 10000 }]); 98 | await impulseGenerator.state(false); 99 | } 100 | } catch (error) { 101 | if (logLevel.error) log.error(`Device: ${host} ${name}, Start impulse generator error: ${error.message ?? error}, trying again.`); 102 | } 103 | }) 104 | .on('state', (state) => { 105 | if (logLevel.debug) log.info(`Device: ${host} ${name}, Start impulse generator ${state ? 'started' : 'stopped'}.`); 106 | }); 107 | 108 | // start impulse generator 109 | await impulseGenerator.state(true, [{ name: 'start', sampling: 120000 }]); 110 | } catch (error) { 111 | if (logLevel.error) log.error(`Device: ${host} ${name}, Did finish launching error: ${error.message ?? error}`); 112 | } 113 | } 114 | }); 115 | } 116 | 117 | configureAccessory(accessory) { 118 | this.accessories.push(accessory); 119 | } 120 | } 121 | 122 | export default (api) => { 123 | api.registerPlatform(PluginName, PlatformName, LgWebOsPlatform); 124 | }; 125 | 126 | -------------------------------------------------------------------------------- /sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bridge": { 3 | "name": "Homebridge", 4 | "username": "AA:BB:CC:DD:EE:FF", 5 | "manufacturer": "homebridge.io", 6 | "model": "homebridge", 7 | "port": 9100, 8 | "pin": "123-45-678" 9 | }, 10 | "description": "HomeKit Bridge", 11 | "ports": { 12 | "start": 9101, 13 | "end": 9150, 14 | "comment": "In this section set the port for Homebridge accessories." 15 | }, 16 | "accessories": [], 17 | "platforms": [ 18 | { 19 | "platform": "LgWebOsTv", 20 | "devices": [ 21 | { 22 | "name": "LG TV", 23 | "host": "192.168.1.8", 24 | "mac": "ab:cd:ef:fe:dc:ba", 25 | "displayType": 1, 26 | "inputs": { 27 | "getFromDevice": false, 28 | "filterSystemApps": false, 29 | "displayOrder": 0, 30 | "data": [ 31 | { 32 | "name": "HDMI 3", 33 | "reference": "com.webos.app.hdmi3", 34 | "mode": 0 35 | }, 36 | { 37 | "name": "BBC ONE HD", 38 | "reference": "1_45_101_101_16521_17540_9018", 39 | "mode": 1 40 | } 41 | ] 42 | }, 43 | "buttons": [ 44 | { 45 | "name": "HDMI 3", 46 | "reference": "com.webos.app.hdmi3", 47 | "mode": 0, 48 | "displayType": 0, 49 | "namePrefix": false 50 | } 51 | ], 52 | "sensors": [ 53 | { 54 | "displayType": 0, 55 | "mode": 0, 56 | "name": "", 57 | "reference": "", 58 | "pulse": false, 59 | "namePrefix": false, 60 | "level": 0 61 | } 62 | ], 63 | "power": { 64 | "broadcastAddress": "255.255.255.255", 65 | "startInput": false, 66 | "startInputReference": "com.webos.app.home" 67 | }, 68 | "volume": { 69 | "displayType": 0, 70 | "name": "Volume", 71 | "namePrefix": false 72 | }, 73 | "sound": { 74 | "modes": { 75 | "data": [ 76 | { 77 | "name": "Cinema", 78 | "reference": "movie", 79 | "displayType": 0, 80 | "namePrefix": false 81 | } 82 | ] 83 | }, 84 | "outputs": { 85 | "data": [ 86 | { 87 | "name": "TV Speaker", 88 | "reference": "tv_speaker", 89 | "displayType": 0, 90 | "namePrefix": false 91 | } 92 | ] 93 | } 94 | }, 95 | "picture": { 96 | "backlightControl": false, 97 | "brightnessControl": false, 98 | "contrastControl": false, 99 | "colorControl": false, 100 | "modes": [ 101 | { 102 | "name": "Cinema", 103 | "reference": "cinema", 104 | "displayType": 0, 105 | "namePrefix": false 106 | } 107 | ] 108 | }, 109 | "screen": { 110 | "turnOnOff": false, 111 | "saverOnOff": false 112 | }, 113 | "log": { 114 | "deviceInfo": true, 115 | "success": true, 116 | "info": false, 117 | "warn": true, 118 | "error": true, 119 | "debug": false 120 | }, 121 | "sslWebSocket": false, 122 | "disableTvService": false, 123 | "infoButtonCommand": "MENU", 124 | "restFul": { 125 | "enable": false, 126 | "port": 3000 127 | }, 128 | "mqtt": { 129 | "enable": false, 130 | "host": "", 131 | "port": 1883, 132 | "clientId": "", 133 | "prefix": "", 134 | "auth": { 135 | "enable": false, 136 | "user": "", 137 | "passwd": "" 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | ] 144 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | 5 | 6 | 7 | # Homebridge LG webOS TV 8 | 9 | [](https://github.com/homebridge/homebridge/wiki/Verified-Plugins) 10 | [](https://www.npmjs.com/package/homebridge-lgwebos-tv) 11 | [](https://www.npmjs.com/package/homebridge-lgwebos-tv) 12 | [](https://www.npmjs.com/package/homebridge-lgwebos-tv) 13 | [](https://github.com/grzegorz914/homebridge-lgwebos-tv/pulls) 14 | [](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues) 15 | 16 | Homebridge plugin for LG webOS. 17 | Tested with OLED65G6V, 32LM6300PLA, 49SK8500, OLED65C7T, 55SK800PLB, OLED48CX, OLED48C2, OLED48C3. 18 | 19 | 20 | 21 | ## Package Requirements 22 | 23 | | Package | Installation | Role | Required | 24 | | --- | --- | --- | --- | 25 | | [Homebridge](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required | 26 | | [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge User Interface | Recommended | 27 | | [LG webOS TV](https://www.npmjs.com/package/homebridge-lgwebos-tv) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-lgwebos-tv/wiki) | Homebridge Plug-In | Required | 28 | 29 | ## Abut The Plugin 30 | 31 | * Support SSL Web Socket for newer TV, plugin config `Advanced Settings >> Device >> SSL WebSocket` 32 | * Power and Screen ON/OFF short press tile in HomeKit app. 33 | * Media control is possible after you go to the RC app (iPhone/iPad). 34 | * Speaker control with hardware buttons after you go to RC app (iPhone/iPad). 35 | * Legacy Volume and Mute control is possible throught extra `Lightbulb / Fan` (slider). 36 | * Inputs can be changed using Inputs selector in Home app, additionally with extra buttons. 37 | * Channels can be changed using Channels selector in Home app, additionally with extra buttons. 38 | * Brightness, Contrast, Backlight, Color, Picture Mode, Sound Mode and Sound Output can be changed using extra buttons. 39 | * Siri can be used for all functions, some times need to create legacy buttons/switches/sensors. 40 | * Automations can be used for all functions, some times need create legacy buttons/switches/sensors. 41 | * Support external integrations, [RESTFul](https://github.com/grzegorz914/homebridge-lgwebos-tv?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-lgwebos-tv?tab=readme-ov-file#mqtt-integration). 42 | 43 | 48 | 49 | ## Configuration 50 | 51 | * Please configure [LG Connect Apps](https://www.lg.com/au/support/product-help/CT20088015-1437132986635) 52 | * Run this plugin as a [Child Bridge](https://github.com/homebridge/homebridge/wiki/Child-Bridges) (Highly Recommended), this prevent crash Homebridge if plugin crashes. 53 | * Install and use [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x/wiki) to configure this plugin. 54 | * The `sample-config.json` can be edited and used as an alternative. 55 | 56 | 59 | 60 | | Key | Description | 61 | | --- | --- | 62 | | `name` | Here set the accessory `Name` to be displayed in `Homebridge/HomeKit`. | 63 | | `host` | Here set the `Hsostname or Address IP` of TV. | 64 | | `mac` | Here set the `Mac Address` of TV. | 65 | | `displayType` | Accessory type to be displayed in Home app: `0 - None / Disabled`, `1 - Television` , `2 - TV Set Top Box`, `3 - TV Streaming Stick`, `4 - Audio Receiver`. | 66 | | `inputs{}` | Inputs object. | 67 | | `inputs.getFromDevice` | This enable load inputs and apps direct from device. | 68 | | `inputs.filterSystemApps` | This enable filter sysem apps, only if `getFromDevice` is `true`. | 69 | | `inputs.displayOrder` | Here select display order of the inputs list, `0 - None`, `1 - Ascending by Name`, `2 - Descending by Name`, `3 - Ascending by Reference`, `4 - Ascending by Reference`. | 70 | | `inputs.data[].name` | Here set `Name` which You want expose to the `Homebridge/HomeKit`. | 71 | | `inputs.data[].reference` | Here set `Reference`. `Live TV`, `HDMI 1`, `HDMI 2` are created by default. | 72 | | `inputs.data[].mode` | Here select input mode, `0 - Input/App`, `1 - Live TV Channel`. | 73 | | `buttons[]` | Buttons array. | 74 | | `buttons[].displayType` | Here select display type in HomeKit app, possible `0 - None / Disabled`, `1 - Outlet`, `2 - Switch`.| 75 | | `buttons[].name` | Here set `Name` which You want expose to the `Homebridge/HomeKit`. | 76 | | `buttons[].mode` | Here select button mode, `0 - Input/App`, `1 - Live TV Channel`, `2 - Remote Control`. | 77 | | `buttons[].reference` | Here set `Reference`, only for `Input/App` or `Live TV Channel` mode, in other case leave empty. | 78 | | `buttons[].command` | Here select `Remote Control` command which will be assigned to the button. | 79 | | `buttons[].namePrefix` | Here enable the accessory name as a prefix for button name.| 80 | | `sensors[]` | Sensors array. | 81 | | `sensors[].displayType` | Here choose the sensor type to be exposed in HomeKit app, possible `0 - None / Disabled`, `1 - Motion Sensor`, `2 - Occupancy Sensor`, `3 - Contact Sensor`. | 82 | | `sensors[].mode` | Here choose the sensor mode, possible `0 - Input`, `1 - Power`, `2 - Volume`, `3 - Mute`, `4 - Sound Mode`, `5 - Sound Output`, `6 - Picture Mode`, `7 - Screen Off`, `8 - Screen Saver`, `9 - Pixel Refresh`, `10 - Play State`, `11 - Channel`. | 83 | | `sensors[].name` | Here set own sensor `Name` which You want expose to the `Homebridge/HomeKit`. | 84 | | `sensors[].reference` | Here set mode `Reference`, sensor fired on switch to this reference. | 85 | | `sensors[].pulse` | Here enable sensor pulse, sensor send pulse and fired on every value change of selected mode. | 86 | | `sensors[].namePrefix` | Here enable the accessory name as a prefix for sensor name. | 87 | | `sensors[].level` | Here set `Level` between `0-100`, sensor fired on this level. | 88 | | `picture{}` | Picture object. | 89 | | `picture.brightnessControl` | This enable possibility adjust the Brightness. | 90 | | `picture.backlightControl` | This enable possibility adjust the Backlight. | 91 | | `picture.contrastControl` | This enable possibility adjust the Contrast. | 92 | | `picture.colorControl` | This enable possibility adjust the Color. | 93 | | `picture.modes[]`| Picture modes array, webOS >= 4.0. | 94 | | `picture.modes[].displayType` | Here select display type in HomeKit app, possible `0 - None / Disabled`, `1 - Outlet`, `2 - Switch`.| 95 | | `picture.modes[].name` | Here set own `Name` which You want expose to the `Homebridge/HomeKit` for this sensor. | 96 | | `picture.modes[].reference` | Here select mode to be exposed in `Homebridge/HomeKit`. | 97 | | `picture.modes[].namePrefix` | Here enable the accessory name as a prefix for picture mode.| 98 | | `sound{}` | Sound object. | 99 | | `sound.modes{}` | Sound mode object. | 100 | | `sound.modes.data[]`| Sound modes array, webOS >= 6.0. | 101 | | `sound.modes.data[].displayType` | Here select display type in HomeKit app, possible `0 - None / Disabled`, `1 - Outlet`, `2 - Switch`.| 102 | | `sound.modes.data[].name` | Here set own `Name` which You want expose to the `Homebridge/HomeKit` for this sensor. | 103 | | `sound.modes.data[].reference` | Here select mode to be exposed in `Homebridge/HomeKit`. | 104 | | `sound.modes.data[].namePrefix` | Here enable the accessory name as a prefix for sound mode.| 105 | | `sound.outputs{}` | Sound output object. | 106 | | `sound.outputs.data[]`| Sound outputs array. | 107 | | `sound.outputs.data[].displayType` | Here select display type in HomeKit app, possible `0 - None / Disabled`, `1 - Outlet`, `2 - Switch`.| 108 | | `sound.outputs.data[].name` | Here set own `Name` which You want expose to the `Homebridge/HomeKit` for this sensor. | 109 | | `sound.outputs.data[].reference` | Here select output to be exposed in `Homebridge/HomeKit`. | 110 | | `sound.outputs.data[].namePrefix` | Here enable the accessory name as a prefix for sound output.| 111 | | `screen{}` | Screen object. | 112 | | `screen.turnOnOff` | This enable possibility turn the screen ON/OFF, webOS >= 4.0. | 113 | | `screen.saverOnOff` | This enable possibility turn the screen saver ON/OFF, webOS >= 4.0. | 114 | | `power{}` | Power object. | 115 | | `power.broadcastAddress` | Her set network `Broadcast Address`, only if You use VLANS in Your network configuration and Your router/switch support IP Directed Broadcast, default is `255.255.255.255`. | 116 | | `power.startInput` | This enable possibilty to set default Input/App after Power ON TV. | 117 | | `power.startInputReference` | Here set the default Input/App reference. | 118 | | `volume{}` | Volume object. | 119 | | `volume.displayType` | Here choice what a additional volume control mode You want to use `0 - None / Disabled`, `1 - Lightbulb`, `2 - Fan`, `3 - TV Speaker (only hardware buttons on R.C. app)`, `4 - TV Speaker / Lightbulb`, `5 - TV Speaker / Fan`. | 120 | | `volume.name` | Here set Your own volume control name or leave empty. | 121 | | `volume.namePrefix` | Here enable the accessory name as a prefix for volume control name. | 122 | | `sslWebSocket` | If enabled, SSL WebSocket will support TV with new firmware. | 123 | | `disableTvService` | This disable TV service and prevent display double services if TV already support HomeKit native. | 124 | | `infoButtonCommand` | Here select the function of `I` button in RC app. | 125 | | `log{}` | Log object. | 126 | | `log.deviceInfo` | If enabled, log device info will be displayed by every connections device to the network. | 127 | | `log.success` | If enabled, success log will be displayed in console. | 128 | | `log.info` | If enabled, info log will be displayed in console. | 129 | | `log.warn` | If enabled, warn log will be displayed in console. | 130 | | `log.error` | If enabled, error log will be displayed in console. | 131 | | `log.debug` | If enabled, debug log will be displayed in console. | 132 | | `restFul{}` | RESTFul object. | 133 | | `restFul.enable` | If enabled, RESTful server will start automatically and respond to any path request. | 134 | | `restFul.port` | Here set the listening `Port` for RESTful server. | 135 | | `mqtt{}` | MQTT object. | 136 | | `mqtt.enable` | If enabled, MQTT Broker will start automatically and publish all awailable PV data. | 137 | | `mqtt.host` | Here set the `IP Address` or `Hostname` for MQTT Broker. | 138 | | `mqtt.port` | Here set the `Port` for MQTT Broker, default 1883. | 139 | | `mqtt.clientId` | Here optional set the `Client Id` of MQTT Broker. | 140 | | `mqtt.prefix` | Here set the `Prefix` for `Topic` or leave empty. | 141 | | `mqtt.auth{}` | MQTT authorization object. | 142 | | `mqtt.auth.enable` | Here enable authorization for MQTT Broker. | 143 | | `mqtt.auth.user` | Here set the MQTT Broker user. | 144 | | `mqtt.auth.passwd` | Here set the MQTT Broker password. | 145 | | `reference` | All can be found in `homebridge_directory/lgwebosTv`, `inputs_xxx` file, where `reference == id`, or `channels_xxx` file, where `reference == channelId`. | 146 | 147 | ### RESTFul Integration 148 | 149 | * POST data as a JSON Object `{Power: true}`, content type must be `application/json` 150 | * Path `status` response all available paths. 151 | * References: 152 | * Picture Mode - `cinema`, `eco`, `expert1`, `expert2`, `game`, `normal`, `photo`, `sports`, `technicolor`, `vivid`, `hdrEffect`, `hdrFilmMaker`, `hdrCinema`, `hdrCinemaBright`, `hdrStandard`, `hdrEffect`, `hdrGame`, `hdrVivid`, `hdrTechnicolor`, `hdrExternal`, `dolbyHdrCinema`, `dolbyHdrCinemaBright`, `dolbyHdrDarkAmazon`, `dolbyHdrStandard`, `dolbyHdrGame`, `dolbyHdrVivid`. 153 | * Sound Mode - `aiSoundPlus`, `standard`, `movie`, `clearVoice`, `news`, `sport`, `music`, `game`. 154 | * Sound Output - `tv_speaker`, `external_speaker`, `external_optical`, `external_arc`, `lineout`, `headphone`, `tv_external_speaker`, `tv_external_headphone`, `bt_soundbar`, `soundbar`. 155 | 156 | | Method | URL | Path | Response | Type | 157 | | --- | --- | --- | --- | --- | 158 | | GET | `http//ip:port` | `systeminfo`, `softwareinfo`, `channels`, `apps`, `power`, `audio`, `currentapp`, `currentchannel`, `picturesettings`, `soundmode`, `soundoutput`, `externalinputlist`, `mediainfo`. | `{"state": Active}` | JSON object. | 159 | 160 | | Method | URL | Key | Value | Type | Description | 161 | | --- | --- | --- | --- | --- | --- | 162 | | POST | `http//ip:port` | `Power` | `true`, `false` | boolean | Power state. | 163 | | | `http//ip:port` | `Input` | `input reference` | string | Set input. | 164 | | | `http//ip:port` | `Channel` | `channel reference` | string | Set channel. | 165 | | | `http//ip:port` | `Volume` | `100` | integer | Set volume. | 166 | | | `http//ip:port` | `Mute` | `true`, `false` | boolean | Set mute. | 167 | | | `http//ip:port` | `Brightness` | `100` | integer | Set brightness. | 168 | | | `http//ip:port` | `Backlight` | `100` | integer | Set backlight. | 169 | | | `http//ip:port` | `Contrast` | `100` | integer | Set contrast. | 170 | | | `http//ip:port` | `Color` | `100` | integer | Set color. | 171 | | | `http//ip:port` | `PictureMode` | `picture mode reference` | string | Set picture mode. | 172 | | | `http//ip:port` | `SoundMode` | `sound mode reference` | string | Set sound mode. | 173 | | | `http//ip:port` | `SoundOutput` | `sound output reference` | string | Set sound output. | 174 | | | `http//ip:port` | `PlayState` | `play`, `pause` | string | Set media play state. | 175 | | | `http//ip:port` | `RcControl` | `REWIND` | string | Send RC command. | 176 | 177 | ### MQTT Integration 178 | 179 | * Subscribe data as a JSON Object `{Power: true}` 180 | * References: 181 | * Picture Mode - `cinema`, `eco`, `expert1`, `expert2`, `game`, `normal`, `photo`, `sports`, `technicolor`, `vivid`, `hdrEffect`, `hdrFilmMaker`, `hdrCinema`, `hdrCinemaBright`, `hdrStandard`, `hdrEffect`, `hdrGame`, `hdrVivid`, `hdrTechnicolor`, `hdrExternal`, `dolbyHdrCinema`, `dolbyHdrCinemaBright`, `dolbyHdrDarkAmazon`, `dolbyHdrStandard`, `dolbyHdrGame`, `dolbyHdrVivid`. 182 | * Sound Mode - `aiSoundPlus`, `standard`, `movie`, `clearVoice`, `news`, `sport`, `music`, `game`. 183 | * Sound Output - `tv_speaker`, `external_speaker`, `external_optical`, `external_arc`, `lineout`, `headphone`, `tv_external_speaker`, `tv_external_headphone`, `bt_soundbar`, `soundbar`. 184 | 185 | | Method | Topic | Message | Type | 186 | | --- | --- | --- | --- | 187 | | Publish | `System Info`, `Software Info`, `Channels`, `Apps`, `Power`, `Audio`, `Current App`, `Current Channel`, `Picture Settings`, `Sound Mode`, `Sound Output`, `External Input List`, `Media Info` | `{"state": Active}` | JSON object. | 188 | 189 | | Method | Topic | Key | Value | Type | Description | 190 | | --- | --- | --- | --- | --- | --- | 191 | | Subscribe | `Set` | `Power` | `true`, `false` | boolean | Power state. | 192 | | | `Set` | `Input` | `input reference` | string | Set input. | 193 | | | `Set` | `Channel` | `channel reference` | string | Set channel. | 194 | | | `Set` | `Volume` | `100` | integer | Set volume. | 195 | | | `Set` | `Mute` | `true`, `false` | boolean | Set mute. | 196 | | | `Set` | `Brightness` | `100` | integer | Set brightness. | 197 | | | `Set` | `Backlight` | `100` | integer | Set backlight. | 198 | | | `Set` | `Contrast` | `100` | integer | Set contrast. | 199 | | | `Set` | `Color` | `100` | integer | Set color. | 200 | | | `Set` | `PictureMode` | `picture mode reference` | string | Set picture mode. | 201 | | | `Set` | `SoundMode` | `sound mode reference` | string | Set sound mode. | 202 | | | `Set` | `SoundOutput` | `sound output reference` | string | Set sound output. | 203 | | | `Set` | `PlayState` | `play`, `pause` | string | Set media play state. | 204 | | | `Set` | `RcControl` | `REWIND` | string | Send RC command. | 205 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const PlatformName = "LgWebOsTv"; 2 | export const PluginName = "homebridge-lgwebos-tv"; 3 | 4 | export const DefaultInputs = [ 5 | { 6 | "name": "Live TV", 7 | "reference": "com.webos.app.livetv", 8 | "mode": 0 9 | }, 10 | { 11 | "name": "HDMI 1", 12 | "reference": "com.webos.app.hdmi1", 13 | "mode": 0 14 | }, 15 | { 16 | "name": "HDMI 2", 17 | "reference": "com.webos.app.hdmi2", 18 | "mode": 0 19 | } 20 | ]; 21 | 22 | export const InputSourceType = [ 23 | "OTHER", 24 | "HOME_SCREEN", 25 | "TUNER", 26 | "HDMI", 27 | "COMPOSITE_VIDEO", 28 | "S_VIDEO", 29 | "COMPONENT_VIDEO", 30 | "DVI", 31 | "AIRPLAY", 32 | "USB", 33 | "APPLICATION" 34 | ]; 35 | 36 | export const ApiUrls = { 37 | "WsUrl": "ws://lgwebostv:3000", 38 | "WssUrl": "wss://lgwebostv:3001", 39 | "SocketUrl": "ssap://com.webos.service.networkinput/getPointerInputSocket", 40 | "ApiGetServiceList": "ssap://api/getServiceList", 41 | "GetSystemInfo": "ssap://system/getSystemInfo", 42 | "GetSoftwareInfo": "ssap://com.webos.service.update/getCurrentSWInformation", 43 | "GetInstalledApps": "ssap://com.webos.applicationManager/listApps", 44 | "GetChannelList": "ssap://tv/getChannelList", 45 | "GetPowerState": "ssap://com.webos.service.tvpower/power/getPowerState", 46 | "GetForegroundAppInfo": "ssap://com.webos.applicationManager/getForegroundAppInfo", 47 | "GetForegroundAppMediaInfo": "ssap://com.webos.media/getForegroundAppInfo", 48 | "GetCurrentChannel": "ssap://tv/getCurrentChannel", 49 | "GetChannelProgramInfo": "ssap://tv/getChannelProgramInfo", 50 | "GetExternalInputList": "ssap://tv/getExternalInputList", 51 | "GetAudioStatus": "ssap://audio/getStatus", 52 | "GetSoundOutput": "ssap://com.webos.service.apiadapter/audio/getSoundOutput", 53 | "GetVolume": "ssap://audio/getVolume", 54 | "GetSystemSettings": "ssap://settings/getSystemSettings", 55 | "GetSystemSettingDesc": "luna://com.webos.service.settings/getSystemSettingDesc", 56 | "GetAppState": "ssap://com.webos.service.appstatus/getAppStatus", 57 | "GetCalibration": "ssap://externalpq/getExternalPqData", 58 | "TurnOff": "ssap://system/turnOff", 59 | "TurnOn": "ssap://system/turnOn", 60 | "TurnOffScreen": "ssap://com.webos.service.tv.power/turnOffScreen", 61 | "TurnOnScreen": "ssap://com.webos.service.tv.power/turnOnScreen", 62 | "TurnOffScreen45": "ssap://com.webos.service.tvpower/power/turnOffScreen", 63 | "TurnOnScreen45": "ssap://com.webos.service.tvpower/power/turnOnScreen", 64 | "LaunchApp": "ssap://system.launcher/launch", 65 | "CloseApp": "ssap://system.launcher/close", 66 | "OpenMediaViewer": "ssap://media.viewer/open", 67 | "CloseMediaViewer": "ssap://media.viewer/close", 68 | "CloseWebApp": "ssap://webapp/closeWebApp", 69 | "OpenChannel": "ssap://tv/openChannel", 70 | "SwitchInput": "ssap://tv/switchInput", 71 | "SetCalibration": "ssap://externalpq/setExternalPqData", 72 | "SetVolume": "ssap://audio/setVolume", 73 | "SetVolumeUp": "ssap://audio/volumeUp", 74 | "SetVolumeDown": "ssap://audio/volumeDown", 75 | "SetMute": "ssap://audio/setMute", 76 | "SetSoundOutput": "ssap://audio/changeSoundOutput", 77 | "Set3dOn": "ssap://com.webos.service.tv.display/set3DOn", 78 | "Set3dOff": "ssap://com.webos.service.tv.display/set3DOff", 79 | "SetMediaPlay": "ssap://media.controls/play", 80 | "SetMediaPause": "ssap://media.controls/pause", 81 | "SetMediaStop": "ssap://media.controls/stop", 82 | "SetMediaRewind": "ssap://media.controls/rewind", 83 | "SetMediaFastForward": "ssap://media.controls/fastForward", 84 | "SetTvChannelUp": "ssap://tv/channelUp", 85 | "SetTvChannelDown": "ssap://tv/channelDown", 86 | "CreateToast": "ssap://system.notifications/createToast", 87 | "CloseToast": "ssap://system.notifications/closeToast", 88 | "CreateAlert": "ssap://system.notifications/createAlert", 89 | "CloseAletrt": "ssap://system.notifications/closeAlert", 90 | "SetConfig": "luna://com.webos.service.config/setConfigs", 91 | "SetSystemSettings": "luna://com.webos.settingsservice/setSystemSettings", 92 | "TurnOnScreenSaver": "luna://com.webos.service.tvpower/power/turnOnScreenSaver", 93 | "RebootTv": "luna://com.webos.service.tvpower/power/reboot", 94 | "RebootTvWebOs5": "luna://com.webos.service.tv.power/reboot", 95 | "ShowInputPicker": "luna://com.webos.surfacemanager/showInputPicker", 96 | "SetDeviceInfo": "luna://com.webos.service.eim/setDeviceInfo", 97 | "EjectDevice": "luna://com.webos.service.attachedstoragemanager/ejectDevice", 98 | "ServiceMenu": "com.webos.app.factorywin" 99 | }; 100 | 101 | export const Pairing = { 102 | "forcePairing": false, 103 | "pairingType": "PROMPT", 104 | "manifest": { 105 | "manifestVersion": 1, 106 | "appVersion": "1.1", 107 | "signed": { 108 | "created": "20140509", 109 | "appId": "com.lge.test", 110 | "vendorId": "com.lge", 111 | "localizedAppNames": { 112 | "": "LG Remote App", 113 | "ko-KR": "리모컨 앱", 114 | "zxx-XX": "ЛГ Rэмotэ AПП" 115 | }, 116 | "localizedVendorNames": { 117 | "": "LG Electronics" 118 | }, 119 | "permissions": [ 120 | "TEST_SECURE", 121 | "CONTROL_INPUT_TEXT", 122 | "CONTROL_MOUSE_AND_KEYBOARD", 123 | "READ_INSTALLED_APPS", 124 | "READ_LGE_SDX", 125 | "READ_NOTIFICATIONS", 126 | "SEARCH", 127 | "WRITE_SETTINGS", 128 | "WRITE_NOTIFICATION_ALERT", 129 | "CONTROL_POWER", 130 | "READ_CURRENT_CHANNEL", 131 | "READ_RUNNING_APPS", 132 | "READ_UPDATE_INFO", 133 | "UPDATE_FROM_REMOTE_APP", 134 | "READ_LGE_TV_INPUT_EVENTS", 135 | "READ_TV_CURRENT_TIME" 136 | ], 137 | "serial": "2f930e2d2cfe083771f68e4fe7bb07" 138 | }, 139 | "permissions": [ 140 | "LAUNCH", 141 | "LAUNCH_WEBAPP", 142 | "APP_TO_APP", 143 | "CLOSE", 144 | "TEST_OPEN", 145 | "TEST_PROTECTED", 146 | "CONTROL_AUDIO", 147 | "CONTROL_DISPLAY", 148 | "CONTROL_INPUT_JOYSTICK", 149 | "CONTROL_INPUT_MEDIA_RECORDING", 150 | "CONTROL_INPUT_MEDIA_PLAYBACK", 151 | "CONTROL_INPUT_TV", 152 | "CONTROL_POWER", 153 | "CONTROL_TV_SCREEN", 154 | "CONTROL_TV_STANBY", 155 | "CONTROL_FAVORITE_GROUP", 156 | "CONTROL_USER_INFO", 157 | "CONTROL_BLUETOOTH", 158 | "CONTROL_TIMER_INFO", 159 | "CONTROL_RECORDING", 160 | "CONTROL_BOX_CHANNEL", 161 | "CONTROL_CHANNEL_BLOCK", 162 | "CONTROL_CHANNEL_GROUP", 163 | "CONTROL_TV_POWER", 164 | "CONTROL_WOL", 165 | "READ_APP_STATUS", 166 | "READ_CURRENT_CHANNEL", 167 | "READ_INPUT_DEVICE_LIST", 168 | "READ_NETWORK_STATE", 169 | "READ_RUNNING_APPS", 170 | "READ_TV_CHANNEL_LIST", 171 | "READ_POWER_STATE", 172 | "READ_COUNTRY_INFO", 173 | "READ_SETTINGS", 174 | "READ_RECORDING_STATE", 175 | "READ_RECORDING_LIST", 176 | "READ_RECORDING_SCHEDULE", 177 | "READ_STORAGE_DEVICE_LIST", 178 | "READ_TV_PROGRAM_INFO", 179 | "READ_TV_ACR_AUTH_TOKEN", 180 | "READ_TV_CONTENT_STATE", 181 | "READ_TV_CURRENT_TIME", 182 | "WRITE_NOTIFICATION_TOAST", 183 | "WRITE_RECORDING_LIST", 184 | "WRITE_RECORDING_SCHEDULE", 185 | "CHECK_BLUETOOTH_DEVICE", 186 | "STB_INTERNAL_CONNECTION", 187 | "ADD_LAUNCHER_CHANNEL", 188 | "SCAN_TV_CHANNELS", 189 | "SET_CHANNEL_SKIP", 190 | "RELEASE_CHANNEL_SKIP", 191 | "DELETE_SELECT_CHANNEL" 192 | ], 193 | "signatures": [ 194 | { 195 | "signatureVersion": 1, 196 | "signature": "eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==" 197 | } 198 | ] 199 | } 200 | }; 201 | 202 | export const SystemApps = [ 203 | "undefined", 204 | "com.webos.app.softwareupdate", 205 | "com.webos.app.acrcomponent", 206 | "com.webos.app.acrhdmi1", 207 | "com.webos.app.acrhdmi2", 208 | "com.webos.app.acrhdmi3", 209 | "com.webos.app.acrhdmi4", 210 | "com.webos.app.usbc1", 211 | "com.webos.app.usbc2", 212 | "com.webos.app.acroverlay", 213 | "com.webos.app.appcasting", 214 | "com.webos.app.miracast-overlay", 215 | "com.webos.app.container", 216 | "com.webos.app.twinzoom-inhdmi1", 217 | "com.webos.app.livezoom-inhdmi1", 218 | "com.webos.app.twinzoom-inhdmi2", 219 | "com.webos.app.livezoom-inhdmi2", 220 | "com.webos.app.twinzoom-inhdmi3", 221 | "com.webos.app.livezoom-inhdmi3", 222 | "com.webos.app.twinzoom-inhdmi4", 223 | "com.webos.app.livezoom-inhdmi4", 224 | "com.webos.app.twinzoom-inphotovideo", 225 | "com.webos.app.livezoom-inphotovideo", 226 | "com.webos.app.twinlivezoom-inphotovideo", 227 | "com.webos.app.livezoom-insmhl", 228 | "com.webos.app.twinzoom-intv", 229 | "com.webos.app.livezoom-intv", 230 | "com.webos.app.twinzoom-inrecordings", 231 | "com.webos.app.livezoom-inrecordings", 232 | "com.webos.app.twinzoom-intwindemo", 233 | "com.webos.app.twinlivezoom-intwindemo", 234 | "com.webos.app.twinzoom-inmiracast", 235 | "com.webos.app.tvtutorial", 236 | "com.webos.app.inputcommon", 237 | "com.webos.app.mvpdwin", 238 | "com.webos.app.mystarter", 239 | "com.webos.app.customersupport", 240 | "com.webos.app.scheduler", 241 | "com.webos.app.cheeringtv", 242 | "com.webos.app.accessibility", 243 | "com.webos.app.adapp", 244 | "com.webos.app.crb", 245 | "com.webos.app.installation", 246 | "com.webos.app.tvuserguide", 247 | "com.webos.app.smhl", 248 | "com.webos.app.store-demo", 249 | "com.webos.app.eula", 250 | "com.webos.app.acrcard", 251 | "com.webos.app.livedmost", 252 | "com.webos.app.twinwizard", 253 | "com.webos.app.tvhotkey", 254 | "com.webos.app.channelsetting", 255 | "com.webos.app.inputmgr", 256 | "com.webos.app.membership", 257 | "com.webos.app.connectionwizard", 258 | "com.webos.app.twindemo", 259 | "com.webos.app.webapphost", 260 | "com.webos.app.remotesetting", 261 | "com.webos.app.facebooklogin", 262 | "com.webos.app.voice", 263 | "com.webos.app.oobe", 264 | "com.webos.app.beanbrowser", 265 | "com.webos.app.remoteservice", 266 | //"com.webos.app.tvsimpleviewer", 267 | "com.webos.app.magicnum", 268 | "com.webos.app.dvrpopup", 269 | "com.webos.app.btspeakerapp", 270 | "com.webos.app.weatherlocation", 271 | "com.webos.app.partialview", 272 | "com.webos.app.recommend", 273 | "com.webos.app.btsurroundautotuning", 274 | "com.webos.app.systemmusic", 275 | "com.webos.app.self-diagnosis", 276 | "com.webos.app.care365", 277 | "com.webos.app.miracast", 278 | "com.webos.app.livepick", 279 | "com.webos.app.onetouchsoundtuning", 280 | "com.webos.app.sync-demo", 281 | "com.webos.app.voiceweb", 282 | "com.webos.app.totalmusic", 283 | "com.webos.app.sportstreamsettings", 284 | "com.webos.app.sheduler", 285 | "com.webos.app.dangbei-overlay", 286 | "com.webos.app.dangbei-card", 287 | "com.webos.app.livemenuplayer-incomponent", 288 | "com.webos.app.livemenuplayer-inscart", 289 | "com.webos.app.livemenuplayer-intv", 290 | "com.webos.app.livemenuplayer-inav1", 291 | "com.webos.app.livemenuplayer-inav2", 292 | "com.webos.app.livemenuplayer-inhdmi1", 293 | "com.webos.app.livemenuplayer-inhdmi2", 294 | "com.webos.app.livemenuplayer-inhdmi3", 295 | "com.webos.app.livemenuplayer-inhdmi4", 296 | "com.webos.app.tips", 297 | "com.webos.app.familycare", 298 | "com.webos.app.firstuse-overlay", 299 | "com.webos.app.gameoptimizer", 300 | "com.webos.app.helpandtips", 301 | "com.webos.app.livetvopapp", 302 | "com.webos.app.picturewizard", 303 | "com.webos.app.remotecontrolguide", 304 | "com.webos.service.homeconnect.app", 305 | "com.webos.service.billing.app", 306 | "com.webos.app.liveginga", 307 | "com.webos.app.liveinteractivecontent", 308 | "com.webos.app.roomconnect-full", 309 | "com.webos.app.voiceview", 310 | "com.webos.app.iot-thirdparty-login", 311 | "com.webos.app.brandshop", 312 | "com.webos.app.lgnlp", 313 | "com.webos.app.actionhandler", 314 | "com.webos.app.channeledit", 315 | //"com.webos.app.factorywin", 316 | "com.webos.exampleapp.enyoapp.epg", 317 | "com.webos.exampleapp.qmlapp.hbbtv", 318 | "com.webos.exampleapp.qmlapp.client.positive.one", 319 | "com.webos.exampleapp.qmlapp.client.positive.two", 320 | "com.webos.exampleapp.qmlapp.client.negative.one", 321 | "com.webos.exampleapp.qmlapp.client.negative.two", 322 | "com.webos.exampleapp.systemui", 323 | "com.webos.exampleapp.groupowner", 324 | "com.webos.exampleapp.qmlapp.livetv", 325 | "com.webos.exampleapp.nav", 326 | "com.webos.exampleapp.qmlapp.epg", 327 | "com.webos.exampleapp.qmlapp.discover", 328 | "com.webos.exampleapp.qmlapp.search", 329 | "com.palm.app.firstuse", 330 | "com.palm.app.remotecontrolguide", 331 | "com.webos.app.shoppinghdmi2", 332 | "com.webos.app.shoppinghdmi1", 333 | "com.webos.app.shoppinghdmi4", 334 | "com.webos.app.shoppingoverlay", 335 | "com.webos.app.shoppinghdmi3", 336 | "alibaba.genie", 337 | "alibaba.genie.view", 338 | //"amazon.alexa.view", 339 | "amazon.alexapr", 340 | "com.webos.app.adhdmi1", 341 | "com.webos.app.adhdmi2", 342 | "com.webos.app.adhdmi3", 343 | "com.webos.app.adhdmi4", 344 | "com.webos.app.adoverlay", 345 | "com.webos.app.adoverlayex", 346 | "com.webos.app.overlaymembership", 347 | "com.webos.chromecast", 348 | //"com.webos.chromecast-settings", 349 | "com.webos.app.fooddelivery", 350 | "com.webos.app.fooddeliveryex", 351 | "com.webos.app.fooddeliveryhdmi1", 352 | "com.webos.app.fooddeliveryhdmi2", 353 | "com.webos.app.fooddeliveryhdmi3", 354 | "com.webos.app.fooddeliveryhdmi4", 355 | "com.webos.app.overlaycontainer", 356 | "com.webos.app.overlaycontainerex", 357 | "com.webos.app.overlaycontainerhdmi1", 358 | "com.webos.app.overlaycontainerhdmi2", 359 | "com.webos.app.overlaycontainerhdmi3", 360 | "com.webos.app.overlaycontainerhdmi4", 361 | ]; 362 | 363 | export const PictureModes = { 364 | "cinema": "Cinema", 365 | "eco": "Eco", 366 | "expert1": "Expert 1", 367 | "expert2": "Expert 2", 368 | "game": "Game", 369 | "normal": "Normal", 370 | "photo": "Photo", 371 | "sports": "ws", 372 | "technicolor": "Technicolor", 373 | "vivid": "Vivid", 374 | "hdrEffect": "HDR Efect", 375 | "hdrCinemaBright": "HDR Cinema Bright", 376 | "hdrExternal": "HDR External", 377 | "hdrGame": "HDR Ganme", 378 | "hdrStandard": "HDR Standard", 379 | "hdrTechnicolor": "HDR Technicolor", 380 | "dolbyHdrCinema": "Dolby HDR Cinema", 381 | "dolbyHdrCinemaBright": "Dolby HDR Cinema Bright", 382 | "dolbyHdrDarkAmazon": "Dolby HDR Dark Amazon", 383 | "dolbyHdrGame": "Dolby HDR Game", 384 | "dolbyHdrStandard": "Dolby HDR Standard", 385 | "dolbyHdrVivid": "Dolby HDR Vivid", 386 | "dolbyStandard": "Dolby Standard" 387 | }; 388 | 389 | export const SoundModes = { 390 | "aiSoundPlus": "AI Sound Plus", 391 | "standard": "Standard", 392 | "movie": "Movie", 393 | "clearVoice": "Clear Voice", 394 | "news": "News", 395 | "sport": "Sport", 396 | "music": "Music", 397 | "game": "Game" 398 | }; 399 | 400 | export const SoundOutputs = { 401 | "tv_speaker": "TV Speaker", 402 | "mastervolume_tv_speaker": "TV Speaker", 403 | "external_speaker": "External Speakre", 404 | "external_optical": "External Optical", 405 | "external_arc": "External ARC", 406 | "lineout": "Line Out", 407 | "headphone": "Headphone", 408 | "tv_external_speaker": "TV External Speaker", 409 | "tv_external_headphone": "TV External Headphone", 410 | "bt_soundbar": "BT Soundbar", 411 | "soundbar": "Soundbar" 412 | }; 413 | 414 | export const DiacriticsMap = { 415 | // Polish 416 | 'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 417 | 'ó': 'o', 'ś': 's', 'ź': 'z', 'ż': 'z', 418 | 'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N', 419 | 'Ó': 'O', 'Ś': 'S', 'Ź': 'Z', 'Ż': 'Z', 420 | 421 | // German 422 | 'ä': 'a', 'ö': 'o', 'ü': 'u', 'ß': 'ss', 423 | 'Ä': 'A', 'Ö': 'O', 'Ü': 'U', 424 | 425 | // French 426 | 'à': 'a', 'â': 'a', 'ç': 'c', 'é': 'e', 'è': 'e', 427 | 'ê': 'e', 'ë': 'e', 'î': 'i', 'ï': 'i', 'ô': 'o', 428 | 'û': 'u', 'ù': 'u', 'ü': 'u', 'ÿ': 'y', 429 | 'À': 'A', 'Â': 'A', 'Ç': 'C', 'É': 'E', 'È': 'E', 430 | 'Ê': 'E', 'Ë': 'E', 'Î': 'I', 'Ï': 'I', 'Ô': 'O', 431 | 'Û': 'U', 'Ù': 'U', 'Ü': 'U', 'Ÿ': 'Y', 432 | 433 | // Spanish & Portuguese 434 | 'á': 'a', 'í': 'i', 'ó': 'o', 'ú': 'u', 'ñ': 'n', 435 | 'Á': 'A', 'Í': 'I', 'Ó': 'O', 'Ú': 'U', 'Ñ': 'N', 436 | 437 | // Scandinavian 438 | 'å': 'a', 'Å': 'A', 'ø': 'o', 'Ø': 'O', 'æ': 'ae', 'Æ': 'AE', 439 | 440 | // Other common 441 | 'Š': 'S', 'š': 's', 'Ž': 'Z', 'ž': 'z' 442 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 5 | 6 | ### NOTE 7 | 8 | - After update to v4.0.0 sensors need to be reconfigured!!! 9 | - After update to v3.0.0 RESTFull and MQTT config settings need to be updated 10 | - After update to v3.10.x and above from previous versions the plugin need to be reconfigured first, in other case will crash!!! 11 | 12 | ## Warning 13 | 14 | - Do not use Homebridge UI > v5.5.0 because of break config.json 15 | 16 | ## [4.0.3] - (16.12.2025) 17 | 18 | ## Changes 19 | 20 | - fix [#290](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/290) 21 | 22 | ## [4.0.1] - (12.12.2025) 23 | 24 | ## Changes 25 | 26 | - after update to v4.x.x sensors need to be reconfigured!!! 27 | - fix start error after update to 4.x.x on specific situation 28 | 29 | ## [4.0.0] - (12.12.2025) 30 | 31 | ## Changes 32 | 33 | - after update to v4.0.0 sensors need to be reconfigured!!! 34 | - full refactor the sensor section code, now is possible to create multiple sensors of differrent types 35 | - config schema updated 36 | - redme updated 37 | - cleanup 38 | 39 | ## [3.11.15] - (09.12.2025) 40 | 41 | ## Changes 42 | 43 | - fix [#288](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/288) 44 | 45 | ## [3.11.14] - (09.12.2025) 46 | 47 | ## Changes 48 | 49 | - moved to MQTT v5 50 | 51 | ## [3.11.13] - (02.12.2025) 52 | 53 | ## Changes 54 | 55 | - bump dependencies (express) 56 | - cleanup 57 | 58 | ## [3.11.10] - (08.11.2025) 59 | 60 | ## Changes 61 | 62 | - stability and performance improvements 63 | - bump dependencies 64 | - config schema updated 65 | - cleanup 66 | 67 | ## [3.11.9] - (01.11.2025) 68 | 69 | ## Changes 70 | 71 | - fix [#284](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/284) 72 | - added support for node v25.x.x 73 | 74 | ## [3.11.8] - (31.10.2025) 75 | 76 | ## Changes 77 | 78 | - fix [#273](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/273) 79 | - Fix [#267](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/267) 80 | - Fix [#279](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/279) 81 | - fix update app listif removed from TV 82 | - fix config not saved in specific situations 83 | - config schema updated 84 | - redme updated 85 | - cleanup 86 | 87 | ## [3.11.1] - (04.10.2025) 88 | 89 | ## Changes 90 | 91 | - prevent plugin crasch if user not reconfigured plugin after update to 3.10.x and above 92 | - fix duplicate volume characteristics and picture params update [#277](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/277) 93 | - RESTFul and MQTT cleanup 94 | - config schema updated 95 | - redme updated 96 | - cleanup 97 | 98 | ## [3.11.0] - (30.09.2025) 99 | 100 | ## Changes 101 | 102 | - added possibility to change accessory display type in Home app 103 | - fix report mute state in buttons 104 | - config schema updated 105 | - redme updated 106 | - cleanup 107 | 108 | ## [3.10.1] - (28.09.2025) 109 | 110 | ## Changes 111 | 112 | - update to v3.10.x and above from previous versions the plugin need to be reconfigured first, in other case will crash!!! 113 | - fix report mute state in buttons 114 | - fix report app/input state in buttons 115 | - fix report channel state in buttons 116 | - update WoL to correct supports VLANS 117 | - router/switch need to support IP Directed Broadcast 118 | - redme updated 119 | - cleanup 120 | 121 | ## [3.10.0] - (24.09.2025) 122 | 123 | ## Changes 124 | 125 | - config schema and json refactor 126 | - after update to v3.10.x and above from previous versions the plugin need to be reconfigured first, in other case will crash!!! 127 | 128 | - added [#265](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/265) 129 | - fix [#269](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/269) 130 | - stability improvements 131 | - bump dependencies 132 | - redme updated 133 | - cleanup 134 | 135 | ## [3.9.17] - (31.08.2025) 136 | 137 | ## Changes 138 | 139 | - stability and performance improvements 140 | - socket refactor 141 | - wake on lan refactor 142 | - cleanup 143 | - redme updated 144 | 145 | ## [3.9.13] - (24.08.2025) 146 | 147 | ## Changes 148 | 149 | - fix [#260](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/260) 150 | - better handle sanitized names 151 | - cleanup 152 | 153 | ## [3.9.11] - (22.08.2025) 154 | 155 | ## Changes 156 | 157 | - fix [#260](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/260) 158 | - fix sound output sensor 159 | - fix app sensor 160 | - cleanup 161 | 162 | ## [3.9.1] - (17.08.2025) 163 | 164 | ## Changes 165 | 166 | - added dynamic input/app add/remove if load from device 167 | - added dynamic firmware info update if changed 168 | - cleanup 169 | 170 | ## [3.9.0] - (16.08.2025) 171 | 172 | ## Changes 173 | 174 | - fix [#264](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/264) 175 | - fix [#249](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/249) 176 | - cleanup 177 | 178 | ## [3.8.8] - (08.07.2025) 179 | 180 | ## Changes 181 | 182 | - some small fixes 183 | - cleanup 184 | 185 | ## [3.8.6] - (02.07.2025) 186 | 187 | ## Changes 188 | 189 | - fix [#257](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/257) 190 | - fix [#253](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/253)now if volume control option is set to disable/nonethe also TV Speakers (hardware control) is disabled 191 | - fix [#255](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/255) 192 | - cleanup 193 | 194 | ## [3.8.0] - (30.05.2025) 195 | 196 | ## Changes 197 | 198 | - added speaker option to volume control 199 | - now if volume control option is set to disable/nonethe also TV Speakers (hardware control) is disabled 200 | - stability improvements 201 | - cleanup 202 | 203 | ## [3.7.6] - (24.05.2025) 204 | 205 | ## Changes 206 | 207 | - fix [#249](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/249) 208 | - bump dependencies 209 | 210 | ## [3.7.5] - (15.05.2025) 211 | 212 | ## Changes 213 | 214 | - fix [#252](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/252) 215 | - bump dependencies 216 | 217 | ## [3.7.4] - (14.04.2025) 218 | 219 | ## Changes 220 | 221 | - fix [#248](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/248) 222 | 223 | ## [3.7.3] - (04.04.2025) 224 | 225 | ## Changes 226 | 227 | - bump dependencies 228 | - cleanup 229 | 230 | ## [3.7.2] - (20.03.2025) 231 | 232 | ## Changes 233 | 234 | - fix [#246](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/246) 235 | 236 | ## [3.7.1] - (20.03.2025) 237 | 238 | ## Changes 239 | 240 | - fix config validation 241 | 242 | ## [3.7.0] - (13.03.2025) 243 | 244 | ## Changes 245 | 246 | - added possibility to disable indyvidual accessory 247 | - bump dependencies 248 | - config schema updated 249 | - redme updated 250 | - cleanup 251 | 252 | ## [3.6.8] - (05.03.2025) 253 | 254 | ## Changes 255 | 256 | - config schema updated 257 | - bump dependencies 258 | - cleanup 259 | 260 | ## [3.6.5] - (13.02.2025) 261 | 262 | ## Changes 263 | 264 | - fix plugin crash if tv services are disabled in plugin config 265 | 266 | ## [3.6.4] - (07.02.2025) 267 | 268 | ## Changes 269 | 270 | - stability and improvements 271 | 272 | ## [3.6.3] - (06.02.2025) 273 | 274 | ## Changes 275 | 276 | - fix HAP-NodeJS WARNING: The accessory has an invalid 'Name' characteristic 'configuredName' 277 | - Please use only alphanumeric, space, and apostrophe characters 278 | - Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis 279 | 280 | ## [3.6.2] - (04.02.2025) 281 | 282 | ## Changes 283 | 284 | - update RESTFul 285 | 286 | ## [3.6.0] - (18.01.2025) 287 | 288 | ## Changes 289 | 290 | - added media play state sensor 291 | - added media info to MQTT and RESTFul 292 | 293 | ## [3.5.6] - (16.01.2025) 294 | 295 | ## Changes 296 | 297 | - functions reorder 298 | 299 | ## [3.5.4] - (15.01.2025) 300 | 301 | ## Changes 302 | 303 | - prevent publish accessory if required data not found 304 | - cleanup 305 | 306 | ## [3.5.0] - (15.01.2025) 307 | 308 | ## Changes 309 | 310 | - added possibility to disable/enable log success, info, warn, error 311 | - bump dependencies 312 | - config schema updated 313 | - redme updated 314 | - cleanup 315 | 316 | ## [3.4.11] - (09.01.2025) 317 | 318 | ## Changes 319 | 320 | - some minor fixes 321 | 322 | ## [3.4.10] - (08.01.2025) 323 | 324 | ## Changes 325 | 326 | - fix update data on first run 327 | 328 | ## [3.4.9] - (08.01.2025) 329 | 330 | ## Changes 331 | 332 | - connect code refactor 333 | - cleanup 334 | 335 | ## [3.4.8] - (06.01.2025) 336 | 337 | ## Changes 338 | 339 | - fix [#237](7https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/237) 340 | - bump dependencies 341 | - redme update 342 | - config schema updated 343 | - cleanup 344 | 345 | ## [3.4.0] - (01.12.2024) 346 | 347 | ## Changes 348 | 349 | - move from commonJS to esm module 350 | - moved constants.json to constants.js 351 | - cleanup 352 | 353 | ## [3.3.0] - (18.11.2024) 354 | 355 | ## Changes 356 | 357 | - fix/enhancement [#223](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/223) 358 | - config schema updated 359 | - cleanup 360 | 361 | ## [3.2.0] - (26.10.2024) 362 | 363 | ## Changes 364 | 365 | - add config validation 366 | - add reconnect if fail on first run 367 | - fix start external integrations 368 | - config schema updated 369 | - cleanup 370 | 371 | ## [3.1.1] - (25.08.2024) 372 | 373 | ## Changes 374 | 375 | - fix [#220](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/220) 376 | - cleanup 377 | 378 | ## [3.1.0] - (23.08.2024) 379 | 380 | ## Changes 381 | 382 | - add control over RESTFul POST JSON Object 383 | - bump dependencies 384 | - cleanup 385 | 386 | ## [3.0.4] - (18.08.2024) 387 | 388 | ## Changes 389 | 390 | - fix correct catch error 391 | 392 | ## [3.0.0] - (14.08.2024) 393 | 394 | ## Changes 395 | 396 | - hide passwords by typing and display in Config UI 397 | - remove return duplicate promises from whole code 398 | - bump dependencies 399 | - cleanup 400 | 401 | ## [2.20.0] - (04.08.2024) 402 | 403 | ## Changes 404 | 405 | - added possiblity to set own volume control name and enable/disable prefix 406 | - config schema updated 407 | - bump dependencies 408 | - cleanup 409 | 410 | ## [2.19.0] - (19.05.2024) 411 | 412 | ## Changes 413 | 414 | - added support for service menu access from the input list 415 | - added support for ez adjust menu access from the input list 416 | - config schema updated 417 | - cleanup 418 | 419 | ## [2.18.0] - (18.05.2024) 420 | 421 | ## Changes 422 | 423 | - added support to control brightness, backlight, contrast, color, picture mode, sound mode, sound output over MQTT 424 | - added support to anable/disable/display type for indyvidual sound and picture mode 425 | - added sound output sensor 426 | - added sound output control 427 | - fixed screen off button state 428 | - updated MQTT and RESTFul 429 | - config schema updated 430 | - cleanup 431 | 432 | ## [2.17.0] - (04.03.2024) 433 | 434 | ## Changes 435 | 436 | - added support to subscribe MQTT and control device 437 | - config schema updated 438 | - cleanup 439 | 440 | ## [2.16.0] - (01.01.2023) 441 | 442 | ## Changes 443 | 444 | - added possibility to disable prefix name for buttons, sensors, picture meodes and sound modes 445 | - config schema updated 446 | - cleanup 447 | 448 | ## [2.15.0] - (29.12.2023) 449 | 450 | ## Changes 451 | 452 | - added possibility to select display inputs order, possible by `None`, `Alphabetically Name`, `Alphabetically Reference` 453 | - config schema updated 454 | - cleanup 455 | 456 | ## [2.14.0] - (05.11.2023) 457 | 458 | ## Changes 459 | 460 | - added possibility to disable load defaults inputs 461 | - fix screen on/off [#177](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/177) 462 | - fix plugin stopped responding after some times [#170](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/170) 463 | - config schema updated 464 | - cleanup 465 | 466 | ## [2.12.0] - (05.09.2023) 467 | 468 | ## Changes 469 | 470 | - added pixel refresh sensor 471 | - added picture mode sensor 472 | - fix sound mode sensor 473 | 474 | ## [2.11.0] - (29.07.2023) 475 | 476 | ## Changes 477 | 478 | - added RESTFul server 479 | - fixed MQTT prefix 480 | - code refactor and cleanup 481 | - config.schema updated 482 | - fixed some minor issues 483 | 484 | ## [2.10.0] - (12.06.2023) 485 | 486 | ## Changes 487 | 488 | - decrease heartbeat time [#172](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/172) 489 | - added possibilty to disable TV services [#169](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/169) 490 | - config.schema updated 491 | - cleanup 492 | 493 | ## [2.9.1] - (27.03.2023) 494 | 495 | ## Changes 496 | 497 | - fixed [#165](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/165) 498 | - added Sound Mode Contact Sensor, only for webOS >= 6.0 499 | - config.schema updated 500 | - cleanup 501 | 502 | ## [2.9.0] - (26.03.2023) 503 | 504 | ## Changes 505 | 506 | - added sound mode control, only for webOS >= 6.0 507 | - config.schema updated 508 | - cleanup 509 | 510 | ## [2.8.0] - (14.02.2023) 511 | 512 | ## Changes 513 | 514 | - rbuild code of specjal socket client to better RC performance 515 | - config.schema updated 516 | - stability and performance improvements 517 | - cleanup 518 | 519 | ## [2.7.0] - (13.02.2023) 520 | 521 | ## Changes 522 | 523 | - standarize function of display type and volume control, now volume control -1 None/Disabled, 0 Slider, 1 Fan, please see in readme 524 | - removed inputs.type, not used anymore 525 | - config.schema updated 526 | - fix expose extra input tile in homekit app 527 | - other small fixes and improvements 528 | - cleanup 529 | 530 | ## [2.6.0] - (09.02.2023) 531 | 532 | ## Changes 533 | 534 | - added heartbeat to keep alive sockets 535 | - logging message updated 536 | - cleanup 537 | 538 | ## [2.5.0] - (24.01.2023) 539 | 540 | ## Changes 541 | 542 | - enchancement [#156](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/156) 543 | - config.schema updated 544 | - cleanup 545 | 546 | ## [2.4.5] - (14.01.2023) 547 | 548 | ## Changes 549 | 550 | - fix sensor volume 551 | 552 | ## [2.4.4] - (14.01.2023) 553 | 554 | ## Changes 555 | 556 | - correct some debug info and mqtt topics 557 | - fix create ssl client 558 | 559 | ## [2.4.3] - (14.01.2023) 560 | 561 | ## Changes 562 | 563 | - added Channel Motion Sensor for use with automations (every Channel change report motion) 564 | - config.schema updated 565 | 566 | ## [2.4.2] - (14.01.2023) 567 | 568 | ## Changes 569 | 570 | - fix state update after restart 571 | - code cleanup 572 | 573 | ## [2.4.1] - (14.01.2023) 574 | 575 | ## Changes 576 | 577 | - added Input Motion Sensor for use with automations (every Input change report motion) 578 | - config.schema updated 579 | 580 | ## [2.4.0] - (14.01.2023) 581 | 582 | ## Changes 583 | 584 | - change websocket library to ws 585 | - added SSL for WebSocket, TV with new firmware 586 | - fix [#151](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/151) 587 | - config schema updated 588 | - code refactor 589 | 590 | ## [2.3.6] - (04.01.2023) 591 | 592 | ## Changes 593 | 594 | - fix save target visibility 595 | - fix save custom names 596 | 597 | ## [2.3.3] - (31.12.2022) 598 | 599 | ## Changes 600 | 601 | - dynamic update accessory information 602 | 603 | ## [2.3.1] - (18.12.2022) 604 | 605 | ## Changes 606 | 607 | - fix [#146](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/146) 608 | 609 | ## [2.3.0] - (18.12.2022) 610 | 611 | ## Changes 612 | 613 | - enhancement [#145](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/145) 614 | - added Power Motion Sensor for use with automations 615 | - added Volume Motion Sensor for use with automations (every volume change report motion) 616 | - added Mute Motion Sensor for use with automations 617 | - added Screen On/Off Motion Sensor for use with automations 618 | - added Screen Saver Motion Sensor for use with automations 619 | - config.schema updated 620 | - other small fixes 621 | 622 | [2.2.7] - (20.10.2022) 623 | 624 | ## Changes 625 | 626 | - fix client.close 627 | 628 | [2.2.6] - (27.09.2022) 629 | 630 | ## Changes 631 | 632 | - fix [#139](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/139) 633 | 634 | [2.2.5] - (14.09.2022) 635 | 636 | ## Changes 637 | 638 | - bump dependencies 639 | - fix read device model in some specific situations 640 | 641 | [2.2.4] - (10.09.2022) 642 | 643 | ## Changes 644 | 645 | - cleanup 646 | 647 | [2.2.3] - (04.09.2022) 648 | 649 | ## Changes 650 | 651 | - fix turn screen on/off for webOs <= 5 652 | 653 | ## [2.2.2] - (03.09.2022) 654 | 655 | ## Changes 656 | 657 | - cleanup 658 | - fix [#138](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/138) 659 | 660 | ## [2.2.0] - (28.08.2022) 661 | 662 | ## Changes 663 | 664 | - cleanup 665 | - added picture control (backlight, brightness, contrast, color) 666 | - fix [#136](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/136) 667 | - fix [#109](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/109) 668 | 669 | ## [2.1.5] - (22.08.2022) 670 | 671 | ## Changes 672 | 673 | - fix error if apps list unknown 674 | - fix error if channels list unknown 675 | - fix screen on/off for webOs >= 5 676 | 677 | ## [2.1.4] - (18.08.2022) 678 | 679 | ## Changes 680 | 681 | - fix special soccket reconnect 682 | 683 | ## [2.1.3] - (18.08.2022) 684 | 685 | ## Changes 686 | 687 | - fix reconnect error 688 | 689 | ## [2.1.2] - (18.08.2022) 690 | 691 | ## Changes 692 | 693 | - fix update device state after first pairing 694 | - performance and stability improvement 695 | - log corrections 696 | 697 | ## [2.1.1] - (14.08.2022) 698 | 699 | ## Changes 700 | 701 | - performance and stability improvement 702 | - rebuild debug log 703 | - prevent publish accessory if payring key not exist or removed 704 | 705 | ## [2.1.0] - (13.08.2022) 706 | 707 | ## Changes 708 | 709 | - rebuild power state and screen state identify 710 | - rebuild debug log 711 | 712 | ## [2.0.4] - (10.08.2022) 713 | 714 | ## Changes 715 | 716 | - remove outdated code 717 | - performance and stability improvements 718 | 719 | ## [2.0.3] - (10.08.2022) 720 | 721 | ## Changes 722 | 723 | - fix data update 724 | 725 | ## [2.0.2] - (09.08.2022) 726 | 727 | ## Changes 728 | 729 | - fix data update 730 | 731 | ## [2.0.0] - (08.08.2022) 732 | 733 | ## Changes 734 | 735 | - full code refactor 736 | - stability improvements 737 | - response improvemets 738 | 739 | ## [1.11.9] - (06.08.2022) 740 | 741 | ## Changes 742 | 743 | - fix [#124](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/124) 744 | 745 | ## [1.11.8] - (23.07.2022) 746 | 747 | ## Changes 748 | 749 | - refactor information service 750 | 751 | ## [1.11.7] - (13.06.2022) 752 | 753 | ## Changes 754 | 755 | - fix [#130](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/130) 756 | 757 | ## [1.11.6] - (05.05.2022) 758 | 759 | ## Changes 760 | 761 | - fix [#127](https://github.com/grzegorz914/homebridge-lgwebos-tv/issues/127) 762 | - more debug logging 763 | 764 | ## [1.11.5] - (25.04.2022) 765 | 766 | ## Changes 767 | 768 | - refactor debug and info log 769 | - refactor mqtt message 770 | 771 | ## [1.11.4] - (24.04.2022) 772 | 773 | ## Changes 774 | 775 | - fix PowerModeSelection 776 | - update config.schema.json 777 | 778 | ## [1.11.2] - (27.02.2022) 779 | 780 | ## Changes 781 | 782 | - fix Info button in RC 783 | 784 | ## [1.11.1] - (22.02.2022) 785 | 786 | ## Added 787 | 788 | - possibility to set custom command for Info button in RC 789 | 790 | ## [1.11.0] - (18.02.2022) 791 | 792 | ## Added 793 | 794 | - MQTT Client, publish all device data 795 | 796 | ## Changes 797 | 798 | - update dependencies 799 | - code refactor 800 | 801 | ## [1.10.14] - (16.01.2022) 802 | 803 | ### Fixed 804 | 805 | - wrog power state report if disconnected from net 806 | 807 | ## [1.10.14] - (16.01.2022) 808 | 809 | ### Changs 810 | 811 | - code cleanup 812 | - update config.schema 813 | 814 | ### Fixed 815 | 816 | - services calculation count 817 | - start input with automations/scenes 818 | 819 | ## [1.10.12/13] - (08.01.2022) 820 | 821 | ### Changed 822 | 823 | - rebuild device info read and write 824 | 825 | ## [1.10.11] - (03.01.2022) 826 | 827 | ### Added 828 | 829 | - ability to disable log device info by every connections device to the network (Advanced Section) 830 | 831 | ## [1.10.10] - (29.12.2021) 832 | 833 | - prevent load plugin if host or mac not set 834 | - prepare directory and files synchronously 835 | 836 | ## [1.10.9] - (28.12.2021) 837 | 838 | - update node minimum requirements 839 | 840 | ## [1.10.8] - (28.12.2021) 841 | 842 | ### Added 843 | 844 | - Selectable display type of buttons in HomeKit app 845 | 846 | ## [1.10.7] - (27.12.2021) 847 | 848 | ## Changes 849 | 850 | - remove branding 851 | - fixed characteristic warning volume 852 | 853 | ## [1.10.6] - (23.12.2021) 854 | 855 | ## Changes 856 | 857 | - fixed RC Socket reconnect 858 | 859 | ## [1.10.5] - (19.12.2021) 860 | 861 | ## Changes 862 | 863 | - added possibility turn ON/OFF the screen for webOS >= 4.0 864 | - fixed power mode selection 865 | - fixed set mute 866 | 867 | ## [1.10.0] - (18.12.2021) 868 | 869 | ## Changes 870 | 871 | - added RC and Volume control as a button 872 | - added correct power state report if screen is Off 873 | - stability and performance improvements 874 | 875 | ## [1.9.41] - (14.12.2021) 876 | 877 | ## Changes 878 | 879 | - removed refreshInterval 880 | 881 | ## [1.9.39] - (13.12.2021) 882 | 883 | ## Changes 884 | 885 | - fixed wrong power state if tv lose connection to net 886 | - added debug mode 887 | 888 | ## [1.9.37] - (10.12.2021) 889 | 890 | ## Changes 891 | 892 | - code rebuild 893 | - stability and performance improvements 894 | 895 | ## [1.9.36] - (28.11.2021) 896 | 897 | ## Changes 898 | 899 | - code rebuild 900 | - stability and performance improvements 901 | - prepare for extends new functionality in next release 902 | 903 | ## [1.9.35] - (28.11.2021) 904 | 905 | ## Changes 906 | 907 | - stability improvement 908 | 909 | ## [1.9.30] - (31.10.2021) 910 | 911 | ## Changes 912 | 913 | - stability improvement 914 | 915 | ## [1.9.23] - (19.10.2021) 916 | 917 | ## Changes 918 | 919 | - code cleanup and rebuild 920 | - performance improvements 921 | - prepare for new functionality 922 | 923 | ## [1.9.19] - (05.10.2021) 924 | 925 | ## Changes 926 | 927 | - code cleanup 928 | 929 | ## [1.9.16] - (01.10.2021) 930 | 931 | ## Changes 932 | 933 | - fixed SIGBUS crash and other improvements 934 | 935 | ## [1.9.15] - (26.09.2021) 936 | 937 | ## Changes 938 | 939 | - config.schema update 940 | 941 | ## [1.9.14] - (24.09.2021) 942 | 943 | ## Changes 944 | 945 | - code cleanup 946 | - updated ES5 to ES6 947 | - updated config.schema 948 | 949 | ## [1.9.13] - (24.09.2021) 950 | 951 | ## Changes 952 | 953 | - code cleanup 954 | 955 | ## [1.9.11] - (14.09.2021) 956 | 957 | ## Changes 958 | 959 | - code cleanup 960 | 961 | ## [1.9.10] - (06.09.2021) 962 | 963 | ## Changes 964 | 965 | - extend filters 966 | - updated config.schema 967 | 968 | ## [1.9.9] - (05.09.2021) 969 | 970 | ## Changes 971 | 972 | - extend filter possibility 973 | - updated config.schema 974 | - code cleanup 975 | 976 | ## [1.9.3] - (02.09.2021) 977 | 978 | ## Changes 979 | 980 | - add more filter for unnecesared inputs from inputs list if load inputs list from device 981 | 982 | ## [1.9.2] - (02.09.2021) 983 | 984 | ## Changes 985 | 986 | - filter unnecesared inputs from inputs list if load inputs list from device 987 | 988 | ## [1.9.0] - (31.08.2021) 989 | 990 | ## Changes 991 | 992 | - code refactorin 993 | - removed not nedded library 994 | - added load inputs list from device 995 | - added default inputs Live TV, HDMI1, HDMI2, no need to create it in config 996 | - many small changes and stability improvements 997 | 998 | ## [1.7.0] - (22.02.2021) 999 | 1000 | ## Changes 1001 | 1002 | - code rebuild, use Characteristic.onSet/onGet 1003 | - require Homebridge 1.3.x or above 1004 | 1005 | ## [1.6.6] - (06.01.2021) 1006 | 1007 | ## Changs 1008 | 1009 | - remove unused dependencies 1010 | 1011 | ## [1.6.3] - (20.11.2020) 1012 | 1013 | ## Changs 1014 | 1015 | - fixed slow response on RC control 1016 | 1017 | ## [1.6.0] - (29.09.2020) 1018 | 1019 | ## Changs 1020 | 1021 | - always check installed app 1022 | - code refactoring 1023 | - update config.schema 1024 | 1025 | ## [1.4.0] - (14.09.2020) 1026 | 1027 | ## Changs 1028 | 1029 | - changes in updateDeviceState 1030 | 1031 | ## [1.3.0] - (14.09.2020) 1032 | 1033 | ## Changs 1034 | 1035 | - added refreshInterval, default 5sec 1036 | 1037 | ## [1.1.0] - (06.09.2020) 1038 | 1039 | ## Changs 1040 | 1041 | - completely reconfigured layout of config schema 1042 | 1043 | ## [1.0.0] - (28.06.2020) 1044 | 1045 | ### Added 1046 | 1047 | - release version. 1048 | 1049 | ## [0.12.0] - (08.06.2020) 1050 | 1051 | ### Added 1052 | 1053 | - added possibility to switch LiveTV channels from the inputs list 1054 | 1055 | ### Fixed 1056 | 1057 | - other fixes 1058 | 1059 | ## [0.11.0] - (23.05.2020) 1060 | 1061 | ### Added 1062 | 1063 | - added possibility to select what a type of extra volume control You want to use (None, Slider, Fan) 1064 | 1065 | ## [0.10.40] - (22.05.2020) 1066 | 1067 | ### Fixed 1068 | 1069 | - fixed RC control 1070 | - other improvements 1071 | 1072 | ## [0.10.0] - (20.05.2020) 1073 | 1074 | ### Added 1075 | 1076 | - added mute ON/OFF to the slider volume 1077 | 1078 | ## [0.9.101] - (18.05.2020) 1079 | 1080 | ### Fixed 1081 | 1082 | - fixed bug in RC control 1083 | - fixed power state 1084 | 1085 | ## [0.9.75] - (17.05.2020) 1086 | 1087 | ### Fixed 1088 | 1089 | - fixed switch input if start with scene or automation 1090 | 1091 | ## [0.9.65] - (16.05.2020) 1092 | 1093 | ### Fixed 1094 | 1095 | - fixed power state 1096 | 1097 | ## [0.9.10] - (14.05.2020) 1098 | 1099 | ### Added 1100 | 1101 | - added descriptions in config.schema.json 1102 | 1103 | ## [0.9.8] - (14.05.2020) 1104 | 1105 | - revert back with defaults inputs 1106 | - added input type to inputs 1107 | - added other fixes in code to prevent app crash without configured inputs 1108 | 1109 | ## [0.9.0] - (14.05.2020) 1110 | 1111 | - added Types to the inputs references (please update Yours config.json) 1112 | - do not add or remove if exist from the config.json default inputs which are now contain in the code 1113 | 1114 | ### [Default inputs 1115 | 1116 | { 1117 | "name": "Live TV", 1118 | "reference": "com.webos.app.livetv", 1119 | "type": "TUNER" 1120 | }, 1121 | { 1122 | "name": "HDMI 1", 1123 | "reference": "com.webos.app.hdmi1", 1124 | "type": "HDMI" 1125 | }, 1126 | { 1127 | "name": "HDMI 2", 1128 | "reference": "com.webos.app.hdmi2", 1129 | "type": "HDMI" 1130 | } 1131 | 1132 | ## [0.8.0] - (10.05.2020) 1133 | 1134 | - code cleanup 1135 | 1136 | ## [0.7.0] - (06.05.2020) 1137 | 1138 | - adapted to HAP-Node JS lib 1139 | 1140 | ## [0.5.57] - (06.05.2020) 1141 | 1142 | - code cleanup 1143 | 1144 | ## [0.5.50] - (05.05.2020) 1145 | 1146 | - fixes and performance inprovements 1147 | - correted logging state 1148 | 1149 | ## [0.5.16] - (05.05.2020) 1150 | 1151 | - added real time read and write data for (lightbulb slider volume cont 1152 | 1153 | ## [0.5.0] - (01.05.2020) 1154 | 1155 | - added support for webOS < 2.5.0 (please update Your config.json) 1156 | 1157 | ## [0.4.4] - (01.05.2020) 1158 | 1159 | - fixes in real time data read and write 1160 | 1161 | ## [0.4.0] - (30.04.2020) 1162 | 1163 | - added realtime data read and write 1164 | 1165 | ## [0.3.4] - (27.04.2020) 1166 | 1167 | - added switch ON/OFF volume control (please update config.json) 1168 | 1169 | ## [0.3.0] - (26.04.2020) 1170 | 1171 | - add Siri volume control 1172 | - add Slider] - (Brightness) volume control 1173 | 1174 | ## [0.2.112] - (21.04.2020) 1175 | 1176 | - different fixes. 1177 | 1178 | ## [0.2.97] - (07.04.2020) 1179 | 1180 | - fixed store of positin in HomeKit fav. 1181 | 1182 | ## [0.2.96] - (06.04.2020) 1183 | 1184 | - name corrections in TV information files 1185 | 1186 | ## [0.2.95] - (05.04.2020) 1187 | 1188 | - read and store appListFile from TV 1189 | 1190 | ## [0.2.93] - (05.04.2020) 1191 | 1192 | - read and store serListFile from TV 1193 | - update README.md 1194 | - update sample-config.json 1195 | 1196 | ## [0.2.91] - (29.03.2020) 1197 | 1198 | - fixes crash if no device name defined 1199 | - fixed config.schema.json 1200 | - fixed store file inside the Homebridge directory 1201 | 1202 | ## [0.2.90] - (29.03.2020) 1203 | 1204 | - some small fixes 1205 | 1206 | ## [0.2.77] - (21.03.2020) 1207 | 1208 | - corrections for homebridge git 1209 | - performance improvement 1210 | 1211 | ## [0.2.1] - (16.02.2020) 1212 | 1213 | - fixed most bugs 1214 | - performance improvements 1215 | - code cleanup 1216 | 1217 | ## [0.0.1] - (10.02.2020) 1218 | 1219 | - initial release 1220 | -------------------------------------------------------------------------------- /src/lgwebossocket.js: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws'; 2 | import EventEmitter from 'events'; 3 | import ImpulseGenerator from './impulsegenerator.js'; 4 | import Functions from './functions.js'; 5 | import { ApiUrls, Pairing } from './constants.js'; 6 | 7 | class LgWebOsSocket extends EventEmitter { 8 | constructor(config, keyFile, devInfoFile, inputsFile, channelsFile, restFulEnabled, mqttEnabled) { 9 | super(); 10 | this.host = config.host; 11 | this.getInputsFromDevice = config.inputs?.getFromDevice || false; 12 | this.inputs = (config.inputs?.data || []).filter(input => input.name && input.reference); 13 | this.logWarn = config.log?.warn || true; 14 | this.logError = config.log?.error || true; 15 | this.logDebug = config.log?.debug || false; 16 | this.sslWebSocket = config.sslWebSocket || false; 17 | this.keyFile = keyFile; 18 | this.devInfoFile = devInfoFile; 19 | this.inputsFile = inputsFile; 20 | this.channelsFile = channelsFile; 21 | this.webSocketPort = this.sslWebSocket ? 3001 : 3000; 22 | 23 | this.restFulEnabled = restFulEnabled; 24 | this.mqttEnabled = mqttEnabled; 25 | 26 | this.functions = new Functions(); 27 | this.socket = null; 28 | this.specializedSocket = null; 29 | this.heartbeat = null; 30 | this.externalInputsArr = []; 31 | this.inputsArr = []; 32 | this.socketConnected = false; 33 | this.specializedSocketConnected = false; 34 | this.cidCount = 0; 35 | 36 | this.power = false; 37 | this.screenState = 'Suspend'; 38 | this.appId = ''; 39 | this.channelId = ''; 40 | this.appType = ''; 41 | this.volume = 0; 42 | this.mute = false; 43 | this.playState = false; 44 | 45 | this.brightness = 0; 46 | this.backlight = 0; 47 | this.contrast = 0; 48 | this.color = 0; 49 | this.picturedMode = ''; 50 | this.soundMode = ''; 51 | this.soundOutput = ''; 52 | 53 | this.tvInfo = { 54 | manufacturer: 'LG Electronics', 55 | modelName: 'LG TV', 56 | productName: '', 57 | deviceId: '', 58 | firmwareRevision: '', 59 | webOS: 2.0 60 | } 61 | 62 | //create impulse generator 63 | this.impulseGenerator = new ImpulseGenerator() 64 | .on('heartBeat', async () => { 65 | try { 66 | if (this.socketConnected || this.connecting) return; 67 | if (this.logDebug) this.emit('debug', `Plugin send heartbeat to TV`); 68 | 69 | const state = await this.functions.ping(this.host); 70 | if (!state.online) { 71 | return; 72 | } 73 | 74 | if (this.logDebug) this.emit('debug', `Plugin received heartbeat from TV`); 75 | 76 | this.connecting = true; 77 | try { 78 | await this.connect(); 79 | } catch (error) { 80 | if (this.logError) this.emit('error', `Connection error: ${error}`); 81 | } finally { 82 | this.connecting = false; 83 | } 84 | } catch (error) { 85 | if (this.logError) this.emit('error', `Impulse generator error: ${error}, trying again`); 86 | } 87 | }) 88 | .on('state', (state) => { 89 | this.emit(state ? 'success' : 'warn', `Heartbeat ${state ? 'started' : 'stopped'}`); 90 | }); 91 | }; 92 | 93 | cleanupSocket = () => { 94 | if (this.heartbeat) { 95 | clearInterval(this.heartbeat); 96 | this.heartbeat = null; 97 | } 98 | this.socket = null; 99 | this.socketConnected = false; 100 | this.cidCount = 0; 101 | this.power = false; 102 | this.screenState = 'Suspend'; 103 | }; 104 | 105 | updateTvState() { 106 | this.emit('powerState', false, this.screenState); 107 | this.emit('currentApp', this.appId, false); 108 | this.emit('audioState', this.volume, this.mute, false); 109 | this.emit('pictureSettings', this.brightness, this.backlight, this.contrast, this.color, false); 110 | this.emit('pictureMode', this.pictureMode, false); 111 | this.emit('soundMode', this.soundMode, false); 112 | this.emit('soundOutput', this.soundOutput, false); 113 | this.emit('mediainfo', this.appId, this.playState, this.appType, false); 114 | } 115 | 116 | async getCid(type) { 117 | try { 118 | switch (type) { 119 | case 'Power': 120 | return this.powerStateId; 121 | case 'App': 122 | return this.currentAppId; 123 | case 'Channel': 124 | return this.currentChannelId; 125 | case 'Audio': 126 | return this.audioStateId; 127 | case 'PictureSettings': 128 | return this.pictureSettingsId; 129 | case 'PictureMode': 130 | return this.pictureModeId; 131 | case 'SoundMode': 132 | return this.soundModeId; 133 | case 'SoundOutput': 134 | return this.soundOutputId; 135 | case 'ExternalInputList': 136 | return this.externalInoutListId; 137 | case 'MediaInfo': 138 | return this.mediaInfoId; 139 | default: 140 | this.cidCount++; 141 | const randomPart = (`0000000${Math.floor(Math.random() * 0xFFFFFFFF).toString(16)}`).slice(-8); 142 | const counterPart = (`000${(this.cidCount).toString(16)}`).slice(-4); 143 | const cid = randomPart + counterPart; 144 | return cid; 145 | } 146 | } catch (error) { 147 | throw new Error(`Get Cid error: ${error}`); 148 | } 149 | } 150 | 151 | async subscribeTvStatus() { 152 | if (this.logDebug) this.emit('debug', `Subscirbe tv status`); 153 | 154 | try { 155 | await new Promise(resolve => setTimeout(resolve, 2000)); 156 | this.channelsId = await this.getCid(); 157 | await this.send('subscribe', ApiUrls.GetChannelList, undefined, this.channelsId); 158 | this.externalInputListId = await this.getCid(); 159 | await this.send('subscribe', ApiUrls.GetExternalInputList, undefined, this.externalInputListId); 160 | this.appsId = await this.getCid(); 161 | await this.send('subscribe', ApiUrls.GetInstalledApps, undefined, this.appsId); 162 | 163 | await new Promise(resolve => setTimeout(resolve, 1000)); 164 | this.powerStateId = await this.getCid(); 165 | await this.send('subscribe', ApiUrls.GetPowerState, undefined, this.powerStateId); 166 | 167 | await new Promise(resolve => setTimeout(resolve, 2000)); 168 | this.currentAppId = await this.getCid(); 169 | await this.send('subscribe', ApiUrls.GetForegroundAppInfo, undefined, this.currentAppId); 170 | this.currentChannelId = await this.getCid(); 171 | await this.send('subscribe', ApiUrls.GetCurrentChannel, undefined, this.currentChannelId); 172 | this.audioStateId = await this.getCid(); 173 | await this.send('subscribe', ApiUrls.GetAudioStatus, undefined, this.audioStateId); 174 | this.soundOutputId = await this.getCid(); 175 | await this.send('subscribe', ApiUrls.GetSoundOutput, undefined, this.soundOutputId); 176 | 177 | //picture 178 | if (this.tvInfo.webOS >= 4.0) { 179 | //settings 180 | let payload = { 181 | category: 'picture', 182 | keys: ['brightness', 'backlight', 'contrast', 'color'] 183 | } 184 | this.pictureSettingsId = await this.getCid(); 185 | await this.send('subscribe', ApiUrls.GetSystemSettings, payload, this.pictureSettingsId); 186 | 187 | //mode 188 | payload = { 189 | category: 'picture', 190 | keys: ['pictureMode'] 191 | } 192 | //this.pictureModeId = await this.getCid(); 193 | //await this.send('alert', ApiUrls.GetSystemSettings, payload, this.pictureModeId); 194 | } 195 | 196 | //sound mode 197 | if (this.tvInfo.webOS >= 6.0) { 198 | const payload = { 199 | category: 'sound', 200 | keys: ['soundMode'] 201 | } 202 | this.soundModeId = await this.getCid(); 203 | await this.send('subscribe', ApiUrls.GetSystemSettings, payload, this.soundModeId); 204 | } 205 | 206 | //media info 207 | if (this.tvInfo.webOS >= 7.0) { 208 | this.mediaInfoId = await this.getCid(); 209 | await this.send('subscribe', ApiUrls.GetForegroundAppMediaInfo, undefined, this.mediaInfoId); 210 | } 211 | 212 | if (this.logDebug) this.emit('debug', `Subscribe tv status successful`); 213 | 214 | return true; 215 | } catch (error) { 216 | throw new Error(`Subscribe TV status error: ${error}`); 217 | } 218 | } 219 | 220 | async send(type, uri, payload, cid, title, message) { 221 | if (!this.socketConnected) { 222 | if (this.logDebug) this.emit('debug', 'Socket not connected'); 223 | return; 224 | } 225 | 226 | try { 227 | payload = payload ?? {}; 228 | cid = cid ?? await this.getCid(); 229 | title = title ?? 'Unknown Title'; 230 | message = message ?? 'Unknown Message'; 231 | let data; 232 | let messageContent; 233 | 234 | // helper to await send without breaking working behavior 235 | const sendAsync = (socket, content) => 236 | new Promise((resolve, reject) => { 237 | socket.send(content, (err) => err ? reject(err) : resolve(true)); 238 | }); 239 | 240 | switch (type) { 241 | case 'button': 242 | if (!this.specializedSocketConnected) { 243 | if (this.logWarn) this.emit('warn', 'Specialized socket not connected'); 244 | return; 245 | } 246 | 247 | const keyValuePairs = Object.entries(payload).map(([k, v]) => `${k}:${v}`); 248 | keyValuePairs.unshift(`type:${type}`); 249 | const array = keyValuePairs.join('\n') + '\n\n'; 250 | 251 | await sendAsync(this.specializedSocket, array); 252 | return true; 253 | case 'alert': 254 | this.alertCid = cid; 255 | const buttons = [{ label: 'Ok', focus: true, buttonType: 'ok', onClick: uri, params: payload }]; 256 | const onClose = { uri, params: payload }; 257 | const onFail = { uri, params: payload }; 258 | const alertPayload = { title, message, modal: true, buttons, onclose: onClose, onfail: onFail, type: 'confirm', isSysReq: true }; 259 | 260 | data = { id: cid, type: 'request', uri: ApiUrls.CreateAlert, payload: alertPayload }; 261 | messageContent = JSON.stringify(data); 262 | await sendAsync(this.socket, messageContent); 263 | if (this.logDebug) this.emit('debug', `Alert send: ${messageContent}`); 264 | return true; 265 | case 'toast': 266 | this.toastCid = cid; 267 | const toastPayload = { message, iconData: null, iconExtension: null, onClick: payload }; 268 | data = { id: cid, type: 'request', uri: ApiUrls.CreateToast, payload: toastPayload }; 269 | messageContent = JSON.stringify(data); 270 | await sendAsync(this.socket, messageContent); 271 | if (this.logDebug) this.emit('debug', `Toast send: ${messageContent}`); 272 | return true; 273 | default: 274 | data = { id: cid, type, uri, payload }; 275 | messageContent = JSON.stringify(data); 276 | await sendAsync(this.socket, messageContent); 277 | if (this.logDebug) this.emit('debug', `Socket send: ${messageContent}`); 278 | return true; 279 | } 280 | } catch (error) { 281 | throw new Error(`Send data error: ${error}`); 282 | } 283 | } 284 | 285 | async updateSensors() { 286 | this.emit('updateSensors', this.power, this.screenState, this.appId, this.volume, this.mute, this.soundMode, this.soundOutput, this.pictureMode, this.playState, this.channelId); 287 | return; 288 | } 289 | 290 | async connect() { 291 | try { 292 | if (this.logDebug) this.emit('debug', `Connect to TV`); 293 | 294 | //Read pairing key from file 295 | const pairingKey = await this.functions.readData(this.keyFile); 296 | this.pairingKey = pairingKey.length > 10 ? pairingKey.toString() : '0'; 297 | 298 | //Socket 299 | const url = this.sslWebSocket ? ApiUrls.WssUrl.replace('lgwebostv', this.host) : ApiUrls.WsUrl.replace('lgwebostv', this.host); 300 | const options = this.sslWebSocket ? { rejectUnauthorized: false } : {}; 301 | const socket = new WebSocket(url, options) 302 | .on('error', (error) => { 303 | if (this.logDebug) this.emit('debug', `Socket error: ${error}`); 304 | socket.close(); 305 | }) 306 | .on('close', () => { 307 | if (this.logDebug) this.emit('debug', `Socket closed`); 308 | this.cleanupSocket(); 309 | this.updateTvState(); 310 | }) 311 | .on('open', async () => { 312 | if (this.logDebug) this.emit('debug', `Plugin received heartbeat from TV`); 313 | 314 | // connect to device success 315 | this.socket = socket; 316 | this.socketConnected = true; 317 | this.emit('success', `Socket Connect Success`); 318 | 319 | // start heartbeat 320 | this.heartbeat = setInterval(() => { 321 | if (socket.readyState === socket.OPEN) { 322 | if (this.logDebug) this.emit('debug', `Socket send heartbeat`); 323 | socket.ping(); 324 | } 325 | }, 5000); 326 | 327 | // Register to TV 328 | try { 329 | Pairing['client-key'] = this.pairingKey; 330 | this.registerId = await this.getCid(); 331 | await this.send('register', undefined, Pairing, this.registerId); 332 | } catch (err) { 333 | if (this.logError) this.emit('error', `Socket register error: ${err}`); 334 | } 335 | }) 336 | .on('pong', () => { 337 | if (this.logDebug) this.emit('debug', `Socket received heartbeat`); 338 | }) 339 | .on('message', async (message) => { 340 | const parsedMessage = JSON.parse(message); 341 | const messageId = parsedMessage.id; 342 | const messageType = parsedMessage.type; 343 | const messageData = parsedMessage.payload; 344 | const stringifyMessage = JSON.stringify(messageData, null, 2); 345 | 346 | let updateSensors = false; 347 | switch (messageId) { 348 | case this.registerId: 349 | switch (messageType) { 350 | case 'response': 351 | if (this.logDebug) this.emit('debug', `Start registering to TV: ${stringifyMessage}`); 352 | if (this.logWarn) this.emit('warn', 'Please accept authorization on TV'); 353 | break; 354 | case 'registered': 355 | if (this.logDebug) this.emit('debug', `Registered to TV with key: ${messageData['client-key']}`); 356 | 357 | //Save key in file if not saved before 358 | const pairingKey = messageData['client-key']; 359 | if (pairingKey !== this.pairingKey) { 360 | await this.functions.saveData(this.keyFile, pairingKey, false); 361 | this.emit('success', 'Pairing key saved'); 362 | } 363 | 364 | //Request specjalized socket 365 | this.specializedSocketId = await this.getCid(); 366 | await this.send('request', ApiUrls.SocketUrl, undefined, this.specializedSocketId); 367 | 368 | //Request system info data 369 | this.systemInfoId = await this.getCid(); 370 | await this.send('request', ApiUrls.GetSystemInfo, undefined, this.systemInfoId); 371 | 372 | //Request software info data 373 | this.softwareInfoId = await this.getCid(); 374 | await this.send('request', ApiUrls.GetSoftwareInfo, undefined, this.softwareInfoId); 375 | 376 | //Subscribe tv status 377 | await this.subscribeTvStatus(); 378 | break; 379 | case 'error': 380 | if (this.logError) this.emit('error', `Register to TV error: ${stringifyMessage}`); 381 | break; 382 | default: 383 | if (this.logDebug) this.emit('debug', `Register to TV unknown message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 384 | break; 385 | }; 386 | break; 387 | case this.specializedSocketId: 388 | switch (messageType) { 389 | case 'response': 390 | if (this.logDebug) this.emit('debug', `Connecting to specialized socket`); 391 | 392 | const socketUrl = messageData.socketPath; 393 | const specializedSocket = new WebSocket(socketUrl, options) 394 | .on('error', (error) => { 395 | if (this.logDebug) this.emit('debug', `Specialized socket connect error: ${error}`); 396 | specializedSocket.close(); 397 | }) 398 | .on('close', async () => { 399 | if (this.logDebug) this.emit('debug', 'Specialized socket closed'); 400 | 401 | this.specializedSocketConnected = false; 402 | this.specializedSocket = null; 403 | 404 | // Retry with backoff 405 | await new Promise(resolve => setTimeout(resolve, 5000)); 406 | if (this.logDebug) this.emit('debug', 'Retrying connect to specialized socket...'); 407 | 408 | try { 409 | await this.send('request', ApiUrls.SocketUrl, undefined, this.specializedSocketId); 410 | } catch (error) { 411 | if (this.logError) this.emit('error', `Specialized socket retry connect error: ${error}`); 412 | } 413 | }) 414 | .on('open', () => { 415 | if (this.logDebug) this.emit('debug', `Specialized socket connected, path: ${socketUrl}`); 416 | 417 | this.specializedSocket = specializedSocket; 418 | this.specializedSocketConnected = true; 419 | this.emit('success', `Specialized Socket Connect Success`); 420 | }); 421 | break; 422 | case 'error': 423 | if (this.logDebug) this.emit('debug', `Specialized socket error: ${stringifyMessage}`); 424 | break; 425 | default: 426 | if (this.logDebug) this.emit('debug', `Specialized socket unknown message: type=${messageType}, id=${messageId}, data=${stringifyMessage}`); 427 | break; 428 | } 429 | break; 430 | case this.systemInfoId: 431 | switch (messageType) { 432 | case 'response': 433 | if (this.logDebug) this.emit('debug', `System info: ${stringifyMessage}`); 434 | this.tvInfo.modelName = messageData.modelName ?? 'LG TV'; 435 | 436 | //restFul 437 | if (this.restFulEnabled) this.emit('restFul', 'systeminfo', messageData); 438 | 439 | //mqtt 440 | if (this.mqttEnabled) this.emit('mqtt', 'System info', messageData); 441 | break; 442 | case 'error': 443 | if (this.logDebug) this.emit('debug', `System info error: ${stringifyMessage}`); 444 | break; 445 | default: 446 | if (this.logDebug) this.emit('debug', `System info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 447 | break; 448 | }; 449 | break; 450 | case this.softwareInfoId: 451 | switch (messageType) { 452 | case 'response': 453 | if (this.logDebug) this.emit('debug', `Software Info: ${stringifyMessage}`); 454 | 455 | this.tvInfo.productName = messageData.product_name; 456 | this.tvInfo.deviceId = messageData.device_id; 457 | this.tvInfo.firmwareRevision = `${messageData.major_ver}.${messageData.minor_ver}`; 458 | this.tvInfo.webOS = Number(messageData.product_name.match(/\d+(\.\d+)?/)[0]) ?? this.tvInfo.webOS; 459 | 460 | await this.functions.saveData(this.devInfoFile, this.tvInfo); 461 | 462 | //emit device info 463 | this.emit('deviceInfo', this.tvInfo); 464 | 465 | //restFul 466 | if (this.restFulEnabled) this.emit('restFul', 'softwareinfo', messageData); 467 | 468 | //mqtt 469 | if (this.mqttEnabled) this.emit('mqtt', 'Software info', messageData); 470 | break; 471 | case 'error': 472 | if (this.logDebug) this.emit('debug', `Software info error: ${stringifyMessage}`); 473 | break; 474 | default: 475 | if (this.logDebug) this.emit('debug', `Software info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 476 | break; 477 | }; 478 | break; 479 | case this.channelsId: 480 | switch (messageType) { 481 | case 'response': 482 | if (this.logDebug) this.emit('debug', `Channels list: ${stringifyMessage}`); 483 | const channelsList = messageData.channelList; 484 | const channelListExist = Array.isArray(channelsList) ? channelsList.length > 0 : false; 485 | if (!channelListExist) return; 486 | 487 | const channelsArr = []; 488 | for (const channel of channelsList) { 489 | const name = channel.channelName; 490 | const channelId = channel.channelId; 491 | const number = channel.channelNumber; 492 | const channelsObj = { 493 | 'name': name, 494 | 'reference': channelId, 495 | 'number': number, 496 | 'mode': 1 497 | } 498 | channelsArr.push(channelsObj); 499 | } 500 | 501 | //save channels to the file 502 | await this.functions.saveData(this.channelsFile, channelsArr); 503 | 504 | //restFul 505 | if (this.restFulEnabled) this.emit('restFul', 'channels', messageData); 506 | 507 | //mqtt 508 | if (this.mqttEnabled) this.emit('mqtt', 'Channels', messageData); 509 | break; 510 | case 'error': 511 | if (this.logDebug) this.emit('debug', `Channels error: ${stringifyMessage}`); 512 | break; 513 | default: 514 | if (this.logDebug) this.emit('debug', `Channels list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 515 | break; 516 | }; 517 | break; 518 | case this.externalInputListId: 519 | switch (messageType) { 520 | case 'response': 521 | if (this.logDebug) this.emit('debug', `External input list: ${stringifyMessage}`); 522 | const externalInputList = messageData.devices; 523 | const externalInputListExist = this.getInputsFromDevice && Array.isArray(externalInputList) ? externalInputList.length > 0 : false; 524 | if (!externalInputListExist) return; 525 | 526 | //parse inputs 527 | const arr = []; 528 | for (const input of externalInputList) { 529 | const obj = { 530 | name: input.label, 531 | reference: input.appId, 532 | mode: 0, 533 | visible: input.visible ?? true 534 | } 535 | arr.push(obj); 536 | } 537 | 538 | //add to external inputs array 539 | this.externalInputsArr = arr; 540 | 541 | //restFul 542 | if (this.restFulEnabled) this.emit('restFul', 'externalinputlist', messageData); 543 | 544 | //mqtt 545 | if (this.mqttEnabled) this.emit('mqtt', 'External Input List', messageData); 546 | break; 547 | case 'error': 548 | if (this.logDebug) this.emit('debug', `External input list error: ${stringifyMessage}`); 549 | break; 550 | default: 551 | if (this.logDebug) this.emit('debug', `External input list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 552 | break; 553 | }; 554 | break; 555 | case this.appsId: 556 | switch (messageType) { 557 | case 'response': 558 | if (this.logDebug) this.emit('debug', `Apps list: ${stringifyMessage}`); 559 | 560 | if (this.getInputsFromDevice) { 561 | // Handle app change reason 562 | const appInstalled = messageData.changeReason === 'appInstalled'; 563 | const appUninstalled = messageData.changeReason === 'appUninstalled'; 564 | const appUpdated = !appInstalled && !appUninstalled; 565 | 566 | // --- Handle uninstall --- 567 | if (appUninstalled && messageData.app) { 568 | this.inputs = this.inputs.filter(input => input.reference !== messageData.app.id); 569 | const inputs = [{ 570 | name: messageData.app.title, 571 | reference: messageData.app.id 572 | }]; 573 | 574 | await this.functions.saveData(this.inputsFile, this.inputs); 575 | this.emit('installedApps', inputs, true); 576 | return; 577 | } 578 | 579 | // Build apps list 580 | const appsList = appInstalled ? [messageData.app] : messageData.apps; 581 | if (!Array.isArray(appsList) || appsList.length === 0) return; 582 | 583 | // Parse apps 584 | const arr = []; 585 | for (const app of appsList) { 586 | if (app?.id && app?.title) { 587 | const input = { 588 | name: app.title, 589 | reference: app.id, 590 | mode: 0, 591 | visible: app.visible 592 | }; 593 | arr.push(input); 594 | } 595 | } 596 | 597 | // Add special menus on app updated 598 | if (appUpdated && this.tvInfo.webOS >= 4.0) arr.push({ name: 'Screen Off', reference: 'com.webos.app.screenoff', mode: 0 }); 599 | this.inputs = [...this.externalInputsArr, ...arr]; 600 | } 601 | 602 | // Save result 603 | await this.functions.saveData(this.inputsFile, this.inputs); 604 | 605 | // Emit the inputs 606 | this.emit('installedApps', this.inputs, false); 607 | 608 | //restFul 609 | if (this.restFulEnabled) this.emit('restFul', 'apps', messageData); 610 | 611 | //mqtt 612 | if (this.mqttEnabled) this.emit('mqtt', 'Apps', messageData); 613 | break; 614 | case 'error': 615 | if (this.logDebug) this.emit('debug', `Apps list error: ${stringifyMessage}`); 616 | break; 617 | default: 618 | if (this.logDebug) this.emit('debug', `Apps list received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 619 | break; 620 | }; 621 | break; 622 | case this.powerStateId: 623 | switch (messageType) { 624 | case 'response': 625 | if (this.logDebug) this.emit('debug', `Power: ${stringifyMessage}`); 626 | 627 | switch (messageData.state) { 628 | case 'Active': 629 | this.power = true; 630 | this.screenState = 'Active'; 631 | this.emit('currentApp', this.appId); 632 | break; 633 | case 'Active Standby': 634 | this.power = false; 635 | this.screenState = 'Active Standby'; 636 | break; 637 | case 'Screen Saver': 638 | this.power = true; 639 | this.screenState = 'Screen Saver'; 640 | this.emit('currentApp', 'com.webos.app.screensaver'); 641 | break; 642 | case 'Screen Off': 643 | this.power = true; 644 | this.screenState = 'Screen Off'; 645 | this.emit('currentApp', 'com.webos.app.screenoff'); 646 | break; 647 | case 'Suspend': 648 | this.power = false; 649 | this.screenState = 'Suspend'; 650 | break; 651 | default: 652 | if (this.logDebug) this.emit('debug', `Unknown power state: ${stringifyMessage}`); 653 | return; 654 | } 655 | 656 | await this.updateSensors(); 657 | this.emit('powerState', this.power, this.screenState); 658 | if (!this.power) this.updateTvState(); 659 | 660 | //restFul 661 | if (this.restFulEnabled) this.emit('restFul', 'power', messageData); 662 | 663 | //mqtt 664 | if (this.mqttEnabled) this.emit('mqtt', 'Power', messageData); 665 | break; 666 | case 'error': 667 | if (this.tvInfo.webOS < 3.0) { 668 | this.power = this.socketConnected; 669 | this.screenState = this.socketConnected ? 'Active' : 'Suspend'; 670 | this.emit('powerState', this.power, this.screenState); 671 | if (!this.power) this.updateTvState(); 672 | if (this.logDebug) this.emit('debug', `Installed system webOS: ${this.tvInfo.webOS}`); 673 | } else { 674 | if (this.logDebug) this.emit('debug', `Power error: ${stringifyMessage}`); 675 | } 676 | break; 677 | default: 678 | if (this.logDebug) this.emit('debug', `Power received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 679 | break; 680 | }; 681 | break; 682 | case this.currentAppId: 683 | switch (messageType) { 684 | case 'response': 685 | if (this.logDebug) this.emit('debug', `App: ${stringifyMessage}`); 686 | const appId = messageData.appId; 687 | if (!appId) return; 688 | this.appId = appId; 689 | 690 | await this.updateSensors(); 691 | this.emit('currentApp', appId, this.power); 692 | 693 | //restFul 694 | if (this.restFulEnabled) this.emit('restFul', 'currentapp', messageData); 695 | 696 | //mqtt 697 | if (this.mqttEnabled) this.emit('mqtt', 'Current App', messageData); 698 | break; 699 | case 'error': 700 | if (this.logDebug) this.emit('debug', `App error: ${stringifyMessage}`); 701 | break; 702 | default: 703 | if (this.logDebug) this.emit('debug', `App received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 704 | break; 705 | }; 706 | break; 707 | case this.audioStateId: 708 | switch (messageType) { 709 | case 'response': 710 | if (this.logDebug) this.emit('debug', `Audio: ${stringifyMessage}`); 711 | const volume = messageData.volume ?? this.volume; 712 | const mute = (messageData.mute ?? messageData.muteStatus) !== undefined ? !!(messageData.mute ?? messageData.muteStatus) : this.mute; 713 | this.volume = volume; 714 | this.mute = mute 715 | 716 | await this.updateSensors(); 717 | this.emit('audioState', volume, mute, this.power); 718 | 719 | const soundOutput = messageData.soundOutput ?? this.soundOutput; 720 | if (soundOutput) { 721 | this.emit('soundOutput', soundOutput, this.power); 722 | this.soundOutput = soundOutput; 723 | } 724 | 725 | //restFul 726 | if (this.restFulEnabled) this.emit('restFul', 'audio', messageData); 727 | 728 | //mqtt 729 | if (this.mqttEnabled) this.emit('mqtt', 'Audio', messageData); 730 | break; 731 | case 'error': 732 | if (this.logDebug) this.emit('debug', `Audio error: ${stringifyMessage}`); 733 | break; 734 | default: 735 | if (this.logDebug) this.emit('debug', `Audio received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 736 | break; 737 | }; 738 | break; 739 | case this.currentChannelId: 740 | switch (messageType) { 741 | case 'response': 742 | if (this.logDebug) this.emit('debug', `Channel: ${stringifyMessage}`); 743 | const channelId = messageData.channelId; 744 | const channelName = messageData.channelName; 745 | const channelNumber = messageData.channelNumber; 746 | if (!channelId) return; 747 | this.channelId = channelId; 748 | 749 | await this.updateSensors(); 750 | this.emit('currentChannel', channelId, channelName, channelNumber, this.power); 751 | 752 | //restFul 753 | if (this.restFulEnabled) this.emit('restFul', 'currentchannel', messageData); 754 | 755 | //mqtt 756 | if (this.mqttEnabled) this.emit('mqtt', 'Current Channel', messageData); 757 | break; 758 | case 'error': 759 | if (this.logDebug) this.emit('debug', `Channel error: ${stringifyMessage}`); 760 | break; 761 | default: 762 | if (this.logDebug) his.emit('debug', `Channel received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 763 | break; 764 | }; 765 | break; 766 | case this.pictureSettingsId: 767 | switch (messageType) { 768 | case 'response': 769 | if (this.logDebug) this.emit('debug', `Picture settings: ${stringifyMessage}`); 770 | const settings = messageData.settings; 771 | if (!settings) return; 772 | 773 | const brightness = messageData.settings.brightness ?? this.brightness; 774 | const backlight = messageData.settings.backlight ?? this.backlight; 775 | const contrast = messageData.settings.contrast ?? this.contrast; 776 | const color = messageData.settings.color ?? this.color; 777 | 778 | this.brightness = brightness; 779 | this.backlight = backlight; 780 | this.contrast = contrast; 781 | this.color = color; 782 | 783 | this.emit('pictureSettings', brightness, backlight, contrast, color, this.power); 784 | 785 | //restFul 786 | if (this.restFulEnabled) this.emit('restFul', 'picturesettings', messageData); 787 | 788 | //mqtt 789 | if (this.mqttEnabled) this.emit('mqtt', 'Picture Settings', messageData); 790 | break; 791 | case 'error': 792 | if (this.logDebug) this.emit('debug', `Picture settings error: ${stringifyMessage}`); 793 | break; 794 | default: 795 | if (this.logDebug) this.emit('debug', `Picture settings received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 796 | break; 797 | }; 798 | break; 799 | case this.pictureModeId: 800 | switch (messageType) { 801 | case 'response': 802 | if (this.logDebug) this.emit('debug', `Picture mode: ${stringifyMessage}`); 803 | const pictureMode = stringifyMessage.pictureMode; 804 | if (!pictureMode) return; 805 | this.pictureMode = pictureMode; 806 | 807 | await this.updateSensors(); 808 | this.emit('pictureMode', pictureMode, this.power); 809 | 810 | //restFul 811 | if (this.restFulEnabled) this.emit('restFul', 'picturemode', messageData); 812 | 813 | //mqtt 814 | if (this.mqttEnabled) this.emit('mqtt', 'Picture Mode', messageData); 815 | break; 816 | case 'error': 817 | if (this.logDebug) this.emit('debug', `Picture mode error: ${stringifyMessage}`); 818 | break; 819 | default: 820 | if (this.logDebug) this.emit('debug', `Picture mode received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 821 | break; 822 | }; 823 | break; 824 | case this.soundModeId: 825 | switch (messageType) { 826 | case 'response': 827 | if (this.logDebug) this.emit('debug', `Sound mode: ${stringifyMessage}`); 828 | const settings = messageData.settings; 829 | if (!settings) return; 830 | 831 | const soundMode = settings.soundMode ?? this.soundMode; 832 | this.soundMode = soundMode; 833 | 834 | await this.updateSensors(); 835 | this.emit('soundMode', soundMode, this.power); 836 | 837 | //restFul 838 | if (this.restFulEnabled) this.emit('restFul', 'soundmode', messageData); 839 | 840 | //mqtt 841 | if (this.mqttEnabled) this.emit('mqtt', 'Sound Mode', messageData); 842 | break; 843 | case 'error': 844 | if (this.logDebug) this.emit('debug', `Sound mode error: ${stringifyMessage}`); 845 | break; 846 | default: 847 | if (this.logDebug) this.emit('debug', `Sound mode received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 848 | break; 849 | }; 850 | break; 851 | case this.soundOutputId: 852 | switch (messageType) { 853 | case 'response': 854 | if (this.logDebug) this.emit('debug', `Sound output: ${stringifyMessage}`); 855 | const soundOutput = messageData.soundOutput ?? this.soundOutput; 856 | if (!soundOutput) return; 857 | this.soundOutput = soundOutput; 858 | 859 | await this.updateSensors(); 860 | this.emit('soundOutput', soundOutput, this.power); 861 | 862 | //restFul 863 | if (this.restFulEnabled) this.emit('restFul', 'soundoutput', messageData); 864 | 865 | //mqtt 866 | if (this.mqttEnabled) this.emit('mqtt', 'Sound Output', messageData); 867 | break; 868 | case 'error': 869 | if (this.logDebug) this.emit('debug', `Sound output error: ${stringifyMessage}`); 870 | break; 871 | default: 872 | if (this.logDebug) this.emit('debug', `Sound output received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 873 | break; 874 | }; 875 | break; 876 | case this.mediaInfoId: 877 | switch (messageType) { 878 | case 'response': 879 | // {"subscribed":true,"returnValue":true,"foregroundAppInfo":[{"appId":"netflix","playState":"loaded","type":"media","mediaId":"_fdTzfnKXXXXX","windowId":"_Window_Id_1"}]} 880 | // reported playState values: starting, loaded, playing, paused 881 | if (this.logDebug) this.emit('debug', `Media info: ${stringifyMessage}`); 882 | const foregroundAppInfo = messageData.foregroundAppInfo ?? []; 883 | const mediaAppOpen = foregroundAppInfo.length > 0; 884 | if (!mediaAppOpen) return; 885 | 886 | const appId = foregroundAppInfo[0].appId ?? this.appId; 887 | const playState = foregroundAppInfo[0].playState === 'playing'; //starting, loaded, unloaded, playing, paused 888 | const appType = foregroundAppInfo[0].type ?? this.appType; 889 | this.appId = appId; 890 | this.playState = playState; 891 | this.appType = appType; 892 | 893 | await this.updateSensors(); 894 | this.emit('mediaInfo', appId, playState, appType, this.power); 895 | 896 | //restFul 897 | if (this.restFulEnabled) this.emit('restFul', 'mediainfo', messageData); 898 | 899 | //mqtt 900 | if (this.mqttEnabled) this.emit('mqtt', 'Media Info', messageData); 901 | break; 902 | case 'error': 903 | if (this.logDebug) this.emit('debug', `Media info error: ${stringifyMessage}`); 904 | break; 905 | default: 906 | if (this.logDebug) this.emit('debug', `Medias info received message, type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 907 | break; 908 | }; 909 | break; 910 | case this.alertCid: 911 | if (this.logDebug) this.emit('debug', `Alert: ${stringifyMessage}`); 912 | const alertId = messageData.alertId ?? false; 913 | if (!alertId) return; 914 | 915 | const closeAlert = this.tvInfo.webOS >= 4.0 ? await this.send('request', ApiUrls.CloseAletrt, { alertId: alertId }) : await this.send('button', undefined, { name: 'ENTER' }); 916 | break; 917 | case this.toastCid: 918 | if (this.logDebug) this.emit('debug', `Toast: ${stringifyMessage}`); 919 | const toastId = messageData.toastId ?? false; 920 | if (!toastId) return; 921 | 922 | const closeToast = this.tvInfo.webOS >= 4.0 ? await this.send('request', ApiUrls.CloseToast, { toastId: toastId }) : await this.send('button', undefined, { name: 'ENTER' }); 923 | break; 924 | default: 925 | if (this.logDebug) this.emit('debug', `Received message type: ${messageType}, id: ${messageId}, data: ${stringifyMessage}`); 926 | break; 927 | } 928 | }); 929 | 930 | return true; 931 | } catch (error) { 932 | throw new Error(`Connect error: ${error}`); 933 | } 934 | } 935 | } 936 | export default LgWebOsSocket; -------------------------------------------------------------------------------- /config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginAlias": "LgWebOsTv", 3 | "pluginType": "platform", 4 | "singular": true, 5 | "fixArrays": true, 6 | "strictValidation": true, 7 | "headerDisplay": "This plugin works with webOS based TV and are exposed to HomeKit as separate accessories and each needs to be manually paired.", 8 | "footerDisplay": "For documentation please see [GitHub repository](https://github.com/grzegorz914/homebridge-lgwebos-tv).", 9 | "schema": { 10 | "type": "object", 11 | "properties": { 12 | "devices": { 13 | "type": "array", 14 | "items": { 15 | "type": "object", 16 | "title": "Device", 17 | "properties": { 18 | "name": { 19 | "title": "Name", 20 | "type": "string", 21 | "placeholder": "LG TV" 22 | }, 23 | "host": { 24 | "title": "IP/Hostname", 25 | "type": "string", 26 | "placeholder": "192.168.1.8 or tv.local", 27 | "pattern": "^(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3}|[a-zA-Z0-9.-]+)$" 28 | }, 29 | "mac": { 30 | "title": "Address Mac", 31 | "type": "string", 32 | "placeholder": "ab:cd:ef:fe:dc:ba", 33 | "pattern": "^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$" 34 | }, 35 | "displayType": { 36 | "title": "Accessory type", 37 | "type": "integer", 38 | "minimum": 0, 39 | "maximum": 4, 40 | "default": 1, 41 | "description": "Accessory type for Home app", 42 | "anyOf": [ 43 | { 44 | "title": "None / Disabled", 45 | "enum": [ 46 | 0 47 | ] 48 | }, 49 | { 50 | "title": "Television", 51 | "enum": [ 52 | 1 53 | ] 54 | }, 55 | { 56 | "title": "TV Set Top Box", 57 | "enum": [ 58 | 2 59 | ] 60 | }, 61 | { 62 | "title": "TV Streaming Stick", 63 | "enum": [ 64 | 3 65 | ] 66 | }, 67 | { 68 | "title": "Audio Receiver", 69 | "enum": [ 70 | 4 71 | ] 72 | } 73 | ] 74 | }, 75 | "inputs": { 76 | "title": "Inputs", 77 | "type": "object", 78 | "properties": { 79 | "getFromDevice": { 80 | "title": "Load Inputs From Device", 81 | "type": "boolean", 82 | "default": false, 83 | "description": "This function get all available inputs direct from device, manually configured inputs will be skipped." 84 | }, 85 | "filterSystemApps": { 86 | "title": "Hide System Apps", 87 | "type": "boolean", 88 | "default": false, 89 | "description": "If enabled, all System Apps will not be displayed on the list of inputs." 90 | }, 91 | "displayOrder": { 92 | "title": "Display Order", 93 | "type": "integer", 94 | "default": 0, 95 | "description": "Here select display order of the inputs list.", 96 | "anyOf": [ 97 | { 98 | "title": "None", 99 | "enum": [ 100 | 0 101 | ] 102 | }, 103 | { 104 | "title": "Ascending by Name", 105 | "enum": [ 106 | 1 107 | ] 108 | }, 109 | { 110 | "title": "Descending by Name", 111 | "enum": [ 112 | 2 113 | ] 114 | }, 115 | { 116 | "title": "Ascending by Reference", 117 | "enum": [ 118 | 3 119 | ] 120 | }, 121 | { 122 | "title": "Descending by Reference", 123 | "enum": [ 124 | 4 125 | ] 126 | } 127 | ] 128 | }, 129 | "data": { 130 | "title": "Inputs", 131 | "type": "array", 132 | "items": { 133 | "title": "Input", 134 | "type": "object", 135 | "properties": { 136 | "name": { 137 | "title": "Name", 138 | "type": "string", 139 | "placeholder": "Name", 140 | "description": "Here set Your own name." 141 | }, 142 | "reference": { 143 | "title": "Reference", 144 | "type": "string", 145 | "placeholder": "Input/App, Channel reference", 146 | "description": "Here set the Input/App, Channel reference." 147 | }, 148 | "mode": { 149 | "title": "Mode", 150 | "type": "integer", 151 | "description": "Here select the function mode.", 152 | "anyOf": [ 153 | { 154 | "title": "Input/App", 155 | "enum": [ 156 | 0 157 | ] 158 | }, 159 | { 160 | "title": "Live TV Channel", 161 | "enum": [ 162 | 1 163 | ] 164 | } 165 | ] 166 | } 167 | } 168 | }, 169 | "condition": { 170 | "functionBody": "return model.devices[arrayIndices].inputs.getFromDevice == false;" 171 | } 172 | } 173 | }, 174 | "required": [ 175 | "displayOrder" 176 | ] 177 | }, 178 | "buttons": { 179 | "title": "Button", 180 | "type": "array", 181 | "items": { 182 | "type": "object", 183 | "properties": { 184 | "displayType": { 185 | "title": "Display Type", 186 | "type": "integer", 187 | "minimum": 0, 188 | "maximum": 2, 189 | "default": 0, 190 | "description": "Here select display type in HomeKit app.", 191 | "anyOf": [ 192 | { 193 | "title": "None / Disabled", 194 | "enum": [ 195 | 0 196 | ] 197 | }, 198 | { 199 | "title": "Outlet", 200 | "enum": [ 201 | 1 202 | ] 203 | }, 204 | { 205 | "title": "Switch", 206 | "enum": [ 207 | 2 208 | ] 209 | } 210 | ] 211 | }, 212 | "name": { 213 | "title": "Name", 214 | "type": "string", 215 | "placeholder": "Name", 216 | "description": "Here set Your own name.", 217 | "condition": { 218 | "functionBody": "return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0;" 219 | } 220 | }, 221 | "mode": { 222 | "title": "Mode", 223 | "type": "integer", 224 | "description": "Here select the function mode.", 225 | "anyOf": [ 226 | { 227 | "title": "Input/App", 228 | "enum": [ 229 | 0 230 | ] 231 | }, 232 | { 233 | "title": "Live TV Channel", 234 | "enum": [ 235 | 1 236 | ] 237 | }, 238 | { 239 | "title": "Remote Control", 240 | "enum": [ 241 | 2 242 | ] 243 | } 244 | ], 245 | "condition": { 246 | "functionBody": "return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0;" 247 | } 248 | }, 249 | "reference": { 250 | "title": "Reference", 251 | "type": "string", 252 | "placeholder": "com.webos.app.sheduler", 253 | "description": "Here set the Input/App/Channel reference.", 254 | "condition": { 255 | "functionBody": "return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode < 2;" 256 | } 257 | }, 258 | "command": { 259 | "title": "RC Command", 260 | "type": "string", 261 | "description": "Here select the remote control command.", 262 | "anyOf": [ 263 | { 264 | "title": "0", 265 | "enum": [ 266 | "0" 267 | ] 268 | }, 269 | { 270 | "title": "1", 271 | "enum": [ 272 | "1" 273 | ] 274 | }, 275 | { 276 | "title": "2", 277 | "enum": [ 278 | "2" 279 | ] 280 | }, 281 | { 282 | "title": "3", 283 | "enum": [ 284 | "3" 285 | ] 286 | }, 287 | { 288 | "title": "4", 289 | "enum": [ 290 | "4" 291 | ] 292 | }, 293 | { 294 | "title": "5", 295 | "enum": [ 296 | "5" 297 | ] 298 | }, 299 | { 300 | "title": "6", 301 | "enum": [ 302 | "6" 303 | ] 304 | }, 305 | { 306 | "title": "7", 307 | "enum": [ 308 | "7" 309 | ] 310 | }, 311 | { 312 | "title": "8", 313 | "enum": [ 314 | "8" 315 | ] 316 | }, 317 | { 318 | "title": "9", 319 | "enum": [ 320 | "9" 321 | ] 322 | }, 323 | { 324 | "title": "Power", 325 | "enum": [ 326 | "POWER" 327 | ] 328 | }, 329 | { 330 | "title": "Volume Up", 331 | "enum": [ 332 | "VOLUMEUP" 333 | ] 334 | }, 335 | { 336 | "title": "Volume Down", 337 | "enum": [ 338 | "VOLUMEDOWN" 339 | ] 340 | }, 341 | { 342 | "title": "Mute", 343 | "enum": [ 344 | "MUTE" 345 | ] 346 | }, 347 | { 348 | "title": "Channel Up", 349 | "enum": [ 350 | "CHANNELUP" 351 | ] 352 | }, 353 | { 354 | "title": "Channel Down", 355 | "enum": [ 356 | "CHANNELDOWN" 357 | ] 358 | }, 359 | { 360 | "title": "Home", 361 | "enum": [ 362 | "HOME" 363 | ] 364 | }, 365 | { 366 | "title": "Menu", 367 | "enum": [ 368 | "MENU" 369 | ] 370 | }, 371 | { 372 | "title": "Q Menu", 373 | "enum": [ 374 | "QMENU" 375 | ] 376 | }, 377 | { 378 | "title": "Menu Up", 379 | "enum": [ 380 | "UP" 381 | ] 382 | }, 383 | { 384 | "title": "Menu Down", 385 | "enum": [ 386 | "DOWN" 387 | ] 388 | }, 389 | { 390 | "title": "Menu Left", 391 | "enum": [ 392 | "LEFT" 393 | ] 394 | }, 395 | { 396 | "title": "Menu Right", 397 | "enum": [ 398 | "RIGHT" 399 | ] 400 | }, 401 | { 402 | "title": "Menu OK", 403 | "enum": [ 404 | "ENTER" 405 | ] 406 | }, 407 | { 408 | "title": "Back", 409 | "enum": [ 410 | "BACK" 411 | ] 412 | }, 413 | { 414 | "title": "Exit", 415 | "enum": [ 416 | "EXIT" 417 | ] 418 | }, 419 | { 420 | "title": "Eject", 421 | "enum": [ 422 | "EJECT" 423 | ] 424 | }, 425 | { 426 | "title": "Info", 427 | "enum": [ 428 | "INFO" 429 | ] 430 | }, 431 | { 432 | "title": "Play", 433 | "enum": [ 434 | "PLAY" 435 | ] 436 | }, 437 | { 438 | "title": "Pause", 439 | "enum": [ 440 | "PAUSE" 441 | ] 442 | }, 443 | { 444 | "title": "Record", 445 | "enum": [ 446 | "RECORD" 447 | ] 448 | }, 449 | { 450 | "title": "Fast Forward", 451 | "enum": [ 452 | "FASTFORWARD" 453 | ] 454 | }, 455 | { 456 | "title": "Rewind", 457 | "enum": [ 458 | "REWIND" 459 | ] 460 | }, 461 | { 462 | "title": "Red", 463 | "enum": [ 464 | "RED" 465 | ] 466 | }, 467 | { 468 | "title": "Green", 469 | "enum": [ 470 | "GREEN" 471 | ] 472 | }, 473 | { 474 | "title": "Yellow", 475 | "enum": [ 476 | "YELLOW" 477 | ] 478 | }, 479 | { 480 | "title": "Blue", 481 | "enum": [ 482 | "BLUE" 483 | ] 484 | }, 485 | { 486 | "title": "Live Zoom", 487 | "enum": [ 488 | "LIVE_ZOOM" 489 | ] 490 | }, 491 | { 492 | "title": "Magnifier Zoom", 493 | "enum": [ 494 | "MAGNIFIER_ZOOM" 495 | ] 496 | }, 497 | { 498 | "title": "3D Mode", 499 | "enum": [ 500 | "3D_MODE" 501 | ] 502 | }, 503 | { 504 | "title": "List", 505 | "enum": [ 506 | "LIST" 507 | ] 508 | }, 509 | { 510 | "title": "AD", 511 | "enum": [ 512 | "AD" 513 | ] 514 | }, 515 | { 516 | "title": "Dash", 517 | "enum": [ 518 | "DASH" 519 | ] 520 | }, 521 | { 522 | "title": "Favorites", 523 | "enum": [ 524 | "FAVORITES" 525 | ] 526 | }, 527 | { 528 | "title": "Program", 529 | "enum": [ 530 | "PROGRAM" 531 | ] 532 | }, 533 | { 534 | "title": "My Apps", 535 | "enum": [ 536 | "MYAPPS" 537 | ] 538 | }, 539 | { 540 | "title": "Aspect Ratio", 541 | "enum": [ 542 | "ASPECT_RATIO" 543 | ] 544 | }, 545 | { 546 | "title": "Screen Remot", 547 | "enum": [ 548 | "SCREEN_REMOT" 549 | ] 550 | }, 551 | { 552 | "title": "Recent", 553 | "enum": [ 554 | "RECENT" 555 | ] 556 | }, 557 | { 558 | "title": "SAP", 559 | "enum": [ 560 | "SAP" 561 | ] 562 | }, 563 | { 564 | "title": "CC", 565 | "enum": [ 566 | "CC" 567 | ] 568 | }, 569 | { 570 | "title": "Click", 571 | "enum": [ 572 | "CLICK" 573 | ] 574 | }, 575 | { 576 | "title": "Teletext", 577 | "enum": [ 578 | "TELETEXT" 579 | ] 580 | }, 581 | { 582 | "title": "Text Option", 583 | "enum": [ 584 | "TEXTOPTION" 585 | ] 586 | }, 587 | { 588 | "title": "BML Data", 589 | "enum": [ 590 | "BML_DATA" 591 | ] 592 | }, 593 | { 594 | "title": "3 Digital Input", 595 | "enum": [ 596 | "3DIGIT_INPUT" 597 | ] 598 | } 599 | ], 600 | "condition": { 601 | "functionBody": "return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].buttons[arrayIndices[1]].mode === 2;" 602 | } 603 | }, 604 | "namePrefix": { 605 | "title": "Prefix", 606 | "type": "boolean", 607 | "default": false, 608 | "description": "Here enable the accessory name as a prefix for button name.", 609 | "condition": { 610 | "functionBody": "return model.devices[arrayIndices[0]].buttons[arrayIndices[1]].displayType > 0;" 611 | } 612 | } 613 | }, 614 | "required": [ 615 | "displayType" 616 | ] 617 | } 618 | }, 619 | "sensors": { 620 | "type": "array", 621 | "items": { 622 | "title": "Sensors", 623 | "type": "object", 624 | "properties": { 625 | "displayType": { 626 | "title": "Display Type", 627 | "type": "integer", 628 | "minimum": 0, 629 | "maximum": 3, 630 | "default": 0, 631 | "anyOf": [ 632 | { 633 | "title": "None / Disabled", 634 | "enum": [ 635 | 0 636 | ] 637 | }, 638 | { 639 | "title": "Motion Sensor", 640 | "enum": [ 641 | 1 642 | ] 643 | }, 644 | { 645 | "title": "Occupancy Sensor", 646 | "enum": [ 647 | 2 648 | ] 649 | }, 650 | { 651 | "title": "Contact Sensor", 652 | "enum": [ 653 | 3 654 | ] 655 | } 656 | ], 657 | "description": "Here select sensor type to be exposed in HomeKit app." 658 | }, 659 | "mode": { 660 | "title": "Mode", 661 | "type": "integer", 662 | "minimum": 0, 663 | "maximum": 11, 664 | "default": 0, 665 | "anyOf": [ 666 | { 667 | "title": "Input", 668 | "enum": [ 669 | 0 670 | ] 671 | }, 672 | { 673 | "title": "Power", 674 | "enum": [ 675 | 1 676 | ] 677 | }, 678 | { 679 | "title": "Volume", 680 | "enum": [ 681 | 2 682 | ] 683 | }, 684 | { 685 | "title": "Mute", 686 | "enum": [ 687 | 3 688 | ] 689 | }, 690 | { 691 | "title": "Sound Mode", 692 | "enum": [ 693 | 4 694 | ] 695 | }, 696 | { 697 | "title": "Sound Output", 698 | "enum": [ 699 | 5 700 | ] 701 | }, 702 | { 703 | "title": "Picture Mode", 704 | "enum": [ 705 | 6 706 | ] 707 | }, 708 | { 709 | "title": "Screen Off", 710 | "enum": [ 711 | 7 712 | ] 713 | }, 714 | { 715 | "title": "Screen Saver", 716 | "enum": [ 717 | 8 718 | ] 719 | }, 720 | { 721 | "title": "Play State", 722 | "enum": [ 723 | 9 724 | ] 725 | }, 726 | { 727 | "title": "Pixel Refresh", 728 | "enum": [ 729 | 10 730 | ] 731 | }, 732 | { 733 | "title": "Channel", 734 | "enum": [ 735 | 11 736 | ] 737 | } 738 | ], 739 | "description": "Here select the sensor mode.", 740 | "condition": { 741 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;" 742 | } 743 | }, 744 | "name": { 745 | "title": "Name", 746 | "type": "string", 747 | "placeholder": "Name", 748 | "description": "Here set Your own name.", 749 | "condition": { 750 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;" 751 | } 752 | }, 753 | "namePrefix": { 754 | "title": "Prefix", 755 | "type": "boolean", 756 | "default": false, 757 | "description": "Here enable the accessory name as a prefix for sensor name.", 758 | "condition": { 759 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;" 760 | } 761 | }, 762 | "pulse": { 763 | "title": "Pulse", 764 | "type": "boolean", 765 | "default": false, 766 | "description": "Here enable sensor pulse, sensor send pulse and fired on every value change of selected mode.", 767 | "condition": { 768 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0;" 769 | } 770 | }, 771 | "reference": { 772 | "title": "Reference", 773 | "type": "string", 774 | "placeholder": "Reference", 775 | "description": "Here set the reference, sensor fired if switch to this referece.", 776 | "condition": { 777 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].pulse === false && (model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 0 || model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 4 || model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 5 || model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 6 || model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 11);" 778 | } 779 | }, 780 | "level": { 781 | "title": "Level", 782 | "type": "number", 783 | "minimum": 0, 784 | "maximum": 100, 785 | "multipleOf": 1, 786 | "default": 100, 787 | "description": "Here set the level between 0-100% at which the sensor fired.", 788 | "condition": { 789 | "functionBody": "return model.devices[arrayIndices[0]].sensors[arrayIndices[1]].displayType > 0 && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].pulse === false && model.devices[arrayIndices[0]].sensors[arrayIndices[1]].mode === 2;" 790 | } 791 | } 792 | }, 793 | "required": [ 794 | "displayType", 795 | "mode" 796 | ] 797 | } 798 | }, 799 | "power": { 800 | "title": "Power", 801 | "type": "object", 802 | "properties": { 803 | "broadcastAddress": { 804 | "title": "Broadcast address", 805 | "type": "string", 806 | "placeholder": "255.255.255.255", 807 | "format": "ipv4", 808 | "description": "Her set network broadcast address, only if You use VLANS in Your network configuration and Your router/switch support IP Directed Broadcast." 809 | }, 810 | "startInput": { 811 | "title": "Default Input", 812 | "type": "boolean", 813 | "default": false, 814 | "description": "This enable possibilty to set default Input/App after Power ON TV." 815 | }, 816 | "startInputReference": { 817 | "title": "Input Reference", 818 | "type": "string", 819 | "default": "com.webos.app.home", 820 | "description": "Here set the default Input/App reference.", 821 | "condition": { 822 | "functionBody": "return model.devices[arrayIndices].power.startInput === true" 823 | } 824 | } 825 | } 826 | }, 827 | "volume": { 828 | "title": "Volume", 829 | "type": "object", 830 | "properties": { 831 | "displayType": { 832 | "title": "Display Type", 833 | "type": "integer", 834 | "minimum": 0, 835 | "maximum": 5, 836 | "default": 0, 837 | "description": "Here select what a extra volume/mute control type You want to use.", 838 | "anyOf": [ 839 | { 840 | "title": "None / Disabled", 841 | "enum": [ 842 | 0 843 | ] 844 | }, 845 | { 846 | "title": "Lightbulb", 847 | "enum": [ 848 | 1 849 | ] 850 | }, 851 | { 852 | "title": "Fan", 853 | "enum": [ 854 | 2 855 | ] 856 | }, 857 | { 858 | "title": "TV Speaker (HW buttons)", 859 | "enum": [ 860 | 3 861 | ] 862 | }, 863 | { 864 | "title": "TV Speaker / Lightbulb", 865 | "enum": [ 866 | 4 867 | ] 868 | }, 869 | { 870 | "title": "TV Speaker / Fan", 871 | "enum": [ 872 | 5 873 | ] 874 | } 875 | ] 876 | }, 877 | "name": { 878 | "title": "Name", 879 | "type": "string", 880 | "placeholder": "Volume Control Name", 881 | "description": "Here set Your own volume control name or leave empty.", 882 | "condition": { 883 | "functionBody": "return model.devices[arrayIndices[0]].volume.displayType > 0;" 884 | } 885 | }, 886 | "namePrefix": { 887 | "title": "Prefix", 888 | "type": "boolean", 889 | "default": false, 890 | "description": "Here enable the accessory name as a prefix for volume control name.", 891 | "condition": { 892 | "functionBody": "return model.devices[arrayIndices[0]].volume.displayType > 0;" 893 | } 894 | } 895 | }, 896 | "required": [ 897 | "displayType" 898 | ] 899 | }, 900 | "sound": { 901 | "title": "Sound", 902 | "type": "object", 903 | "properties": { 904 | "modes": { 905 | "title": "Sound Mode", 906 | "type": "array", 907 | "items": { 908 | "type": "object", 909 | "properties": { 910 | "displayType": { 911 | "title": "Display Type", 912 | "type": "integer", 913 | "minimum": 0, 914 | "maximum": 2, 915 | "default": 0, 916 | "description": "Here select display type in HomeKit app.", 917 | "anyOf": [ 918 | { 919 | "title": "None / Disabled", 920 | "enum": [ 921 | 0 922 | ] 923 | }, 924 | { 925 | "title": "Outlet", 926 | "enum": [ 927 | 1 928 | ] 929 | }, 930 | { 931 | "title": "Switch", 932 | "enum": [ 933 | 2 934 | ] 935 | } 936 | ] 937 | }, 938 | "name": { 939 | "title": "Name", 940 | "type": "string", 941 | "placeholder": "Sound Mode Name", 942 | "description": "Here set Your own Sound Mode name.", 943 | "condition": { 944 | "functionBody": "return model.devices[arrayIndices[0]].sound.modes[arrayIndices[1]].displayType > 0;" 945 | } 946 | }, 947 | "reference": { 948 | "title": "Mode", 949 | "type": "string", 950 | "description": "Here choice the Sound Mode.", 951 | "anyOf": [ 952 | { 953 | "title": "Ai Sound Plus", 954 | "enum": [ 955 | "aiSoundPlus" 956 | ] 957 | }, 958 | { 959 | "title": "Standard", 960 | "enum": [ 961 | "standard" 962 | ] 963 | }, 964 | { 965 | "title": "Movie", 966 | "enum": [ 967 | "movie" 968 | ] 969 | }, 970 | { 971 | "title": "Clear Voice", 972 | "enum": [ 973 | "clearVoice" 974 | ] 975 | }, 976 | { 977 | "title": "News", 978 | "enum": [ 979 | "news" 980 | ] 981 | }, 982 | { 983 | "title": "Sports", 984 | "enum": [ 985 | "sports" 986 | ] 987 | }, 988 | { 989 | "title": "Music", 990 | "enum": [ 991 | "music" 992 | ] 993 | }, 994 | { 995 | "title": "Game", 996 | "enum": [ 997 | "game" 998 | ] 999 | } 1000 | ], 1001 | "condition": { 1002 | "functionBody": "return model.devices[arrayIndices[0]].sound.modes[arrayIndices[1]].displayType > 0;" 1003 | } 1004 | }, 1005 | "namePrefix": { 1006 | "title": "Prefix", 1007 | "type": "boolean", 1008 | "default": false, 1009 | "description": "Here enable the accessory name as a prefix for sound mode.", 1010 | "condition": { 1011 | "functionBody": "return model.devices[arrayIndices[0]].sound.modes[arrayIndices[1]].displayType > 0;" 1012 | } 1013 | } 1014 | }, 1015 | "required": [ 1016 | "displayType" 1017 | ] 1018 | } 1019 | }, 1020 | "outputs": { 1021 | "title": "Sound Output", 1022 | "type": "array", 1023 | "items": { 1024 | "type": "object", 1025 | "properties": { 1026 | "displayType": { 1027 | "title": "Display Type", 1028 | "type": "integer", 1029 | "minimum": 0, 1030 | "maximum": 2, 1031 | "default": 0, 1032 | "description": "Here select display type in HomeKit app.", 1033 | "anyOf": [ 1034 | { 1035 | "title": "None / Disabled", 1036 | "enum": [ 1037 | 0 1038 | ] 1039 | }, 1040 | { 1041 | "title": "Outlet", 1042 | "enum": [ 1043 | 1 1044 | ] 1045 | }, 1046 | { 1047 | "title": "Switch", 1048 | "enum": [ 1049 | 2 1050 | ] 1051 | } 1052 | ] 1053 | }, 1054 | "name": { 1055 | "title": "Name", 1056 | "type": "string", 1057 | "placeholder": "Sound Output Name", 1058 | "description": "Here set Your own Sound Output name.", 1059 | "condition": { 1060 | "functionBody": "return model.devices[arrayIndices[0]].sound.outputs[arrayIndices[1]].displayType > 0;" 1061 | } 1062 | }, 1063 | "reference": { 1064 | "title": "Output", 1065 | "type": "string", 1066 | "description": "Here choice the Sound Output.", 1067 | "anyOf": [ 1068 | { 1069 | "title": "TV Speaker", 1070 | "enum": [ 1071 | "tv_speaker" 1072 | ] 1073 | }, 1074 | { 1075 | "title": "External Speaker", 1076 | "enum": [ 1077 | "external_speaker" 1078 | ] 1079 | }, 1080 | { 1081 | "title": "External Optical", 1082 | "enum": [ 1083 | "external_optical" 1084 | ] 1085 | }, 1086 | { 1087 | "title": "External ARC", 1088 | "enum": [ 1089 | "external_arc" 1090 | ] 1091 | }, 1092 | { 1093 | "title": "Line Out", 1094 | "enum": [ 1095 | "lineout" 1096 | ] 1097 | }, 1098 | { 1099 | "title": "Headphone", 1100 | "enum": [ 1101 | "headphone" 1102 | ] 1103 | }, 1104 | { 1105 | "title": "TV External Speaker", 1106 | "enum": [ 1107 | "tv_external_speaker" 1108 | ] 1109 | }, 1110 | { 1111 | "title": "TV Speaker Headphone", 1112 | "enum": [ 1113 | "tv_speaker_headphone" 1114 | ] 1115 | }, 1116 | { 1117 | "title": "BT Soundbar", 1118 | "enum": [ 1119 | "bt_soundbar" 1120 | ] 1121 | }, 1122 | { 1123 | "title": "Soundbar", 1124 | "enum": [ 1125 | "soundbar" 1126 | ] 1127 | } 1128 | ], 1129 | "condition": { 1130 | "functionBody": "return model.devices[arrayIndices[0]].sound.outputs[arrayIndices[1]].displayType > 0;" 1131 | } 1132 | }, 1133 | "namePrefix": { 1134 | "title": "Prefix", 1135 | "type": "boolean", 1136 | "default": false, 1137 | "description": "Here enable the accessory name as a prefix for sound output.", 1138 | "condition": { 1139 | "functionBody": "return model.devices[arrayIndices[0]].sound.outputs[arrayIndices[1]].displayType > 0;" 1140 | } 1141 | } 1142 | }, 1143 | "required": [ 1144 | "displayType" 1145 | ] 1146 | } 1147 | } 1148 | } 1149 | }, 1150 | "picture": { 1151 | "title": "Picture", 1152 | "type": "object", 1153 | "properties": { 1154 | "brightnessControl": { 1155 | "title": "Brightness", 1156 | "type": "boolean", 1157 | "default": false, 1158 | "description": "This enable possibility adjust the Brightness." 1159 | }, 1160 | "backlightControl": { 1161 | "title": "Backlight", 1162 | "type": "boolean", 1163 | "default": false, 1164 | "description": "This enable possibility adjust the Backlight." 1165 | }, 1166 | "contrastControl": { 1167 | "title": "Contrast", 1168 | "type": "boolean", 1169 | "default": false, 1170 | "description": "This enable possibility adjust the Contrast." 1171 | }, 1172 | "colorControl": { 1173 | "title": "Color", 1174 | "type": "boolean", 1175 | "default": false, 1176 | "description": "This enable possibility adjust the Color." 1177 | }, 1178 | "modes": { 1179 | "title": "Mode", 1180 | "type": "array", 1181 | "items": { 1182 | "type": "object", 1183 | "properties": { 1184 | "displayType": { 1185 | "title": "Display Type", 1186 | "type": "integer", 1187 | "minimum": 0, 1188 | "maximum": 2, 1189 | "default": 0, 1190 | "description": "Here select display type in HomeKit app.", 1191 | "anyOf": [ 1192 | { 1193 | "title": "None / Disabled", 1194 | "enum": [ 1195 | 0 1196 | ] 1197 | }, 1198 | { 1199 | "title": "Outlet", 1200 | "enum": [ 1201 | 1 1202 | ] 1203 | }, 1204 | { 1205 | "title": "Switch", 1206 | "enum": [ 1207 | 2 1208 | ] 1209 | } 1210 | ] 1211 | }, 1212 | "name": { 1213 | "title": "Name", 1214 | "type": "string", 1215 | "placeholder": "Picture Mode Name", 1216 | "description": "Here set Your own Picture Mode name.", 1217 | "condition": { 1218 | "functionBody": "return model.devices[arrayIndices[0]].picture.modes[arrayIndices[1]].displayType > 0;" 1219 | } 1220 | }, 1221 | "reference": { 1222 | "title": "Mode", 1223 | "type": "string", 1224 | "description": "Here choice the Picture Mode.", 1225 | "anyOf": [ 1226 | { 1227 | "title": "Normal", 1228 | "enum": [ 1229 | "normal" 1230 | ] 1231 | }, 1232 | { 1233 | "title": "Cinema", 1234 | "enum": [ 1235 | "cinema" 1236 | ] 1237 | }, 1238 | { 1239 | "title": "Eco", 1240 | "enum": [ 1241 | "eco" 1242 | ] 1243 | }, 1244 | { 1245 | "title": "Game", 1246 | "enum": [ 1247 | "game" 1248 | ] 1249 | }, 1250 | { 1251 | "title": "Photo", 1252 | "enum": [ 1253 | "photo" 1254 | ] 1255 | }, 1256 | { 1257 | "title": "Sports", 1258 | "enum": [ 1259 | "sports" 1260 | ] 1261 | }, 1262 | { 1263 | "title": "Vivid", 1264 | "enum": [ 1265 | "vivid" 1266 | ] 1267 | }, 1268 | { 1269 | "title": "Technicolor", 1270 | "enum": [ 1271 | "technicoloro" 1272 | ] 1273 | }, 1274 | { 1275 | "title": "Filmmaker", 1276 | "enum": [ 1277 | "filmMaker" 1278 | ] 1279 | }, 1280 | { 1281 | "title": "HDR Filmmaker", 1282 | "enum": [ 1283 | "hdrFilmMaker" 1284 | ] 1285 | }, 1286 | { 1287 | "title": "HDR Cinema", 1288 | "enum": [ 1289 | "hdrCinema" 1290 | ] 1291 | }, 1292 | { 1293 | "title": "HDR Cinema Bright", 1294 | "enum": [ 1295 | "hdrCinemaBright" 1296 | ] 1297 | }, 1298 | { 1299 | "title": "HDR Standard", 1300 | "enum": [ 1301 | "hdrStandard" 1302 | ] 1303 | }, 1304 | { 1305 | "title": "HDR Effect", 1306 | "enum": [ 1307 | "hdrEffect" 1308 | ] 1309 | }, 1310 | { 1311 | "title": "HDR Game", 1312 | "enum": [ 1313 | "hdrGame" 1314 | ] 1315 | }, 1316 | { 1317 | "title": "HDR Vivid", 1318 | "enum": [ 1319 | "hdrVivid" 1320 | ] 1321 | }, 1322 | { 1323 | "title": "HDR Technicolor", 1324 | "enum": [ 1325 | "hdrTechnicolor" 1326 | ] 1327 | }, 1328 | { 1329 | "title": "HDR External", 1330 | "enum": [ 1331 | "hdrExternal" 1332 | ] 1333 | }, 1334 | { 1335 | "title": "Dolby HDR Cinema", 1336 | "enum": [ 1337 | "dolbyHdrCinema" 1338 | ] 1339 | }, 1340 | { 1341 | "title": "Dolby HDR Cinema Bright", 1342 | "enum": [ 1343 | "dolbyHdrCinemaBright" 1344 | ] 1345 | }, 1346 | { 1347 | "title": "Dolby HDR Cinema Dark", 1348 | "enum": [ 1349 | "dolbyHdrDarkAmazon" 1350 | ] 1351 | }, 1352 | { 1353 | "title": "Dolby HDR Standard", 1354 | "enum": [ 1355 | "dolbyHdrStandard" 1356 | ] 1357 | }, 1358 | { 1359 | "title": "Dolby HDR Vivid", 1360 | "enum": [ 1361 | "dolbyHdrVivid" 1362 | ] 1363 | }, 1364 | { 1365 | "title": "Dolby HDR Game", 1366 | "enum": [ 1367 | "dolbyHdrGame" 1368 | ] 1369 | }, 1370 | { 1371 | "title": "Expert 1", 1372 | "enum": [ 1373 | "expert1" 1374 | ] 1375 | }, 1376 | { 1377 | "title": "Expert 2", 1378 | "enum": [ 1379 | "expert2" 1380 | ] 1381 | } 1382 | ], 1383 | "condition": { 1384 | "functionBody": "return model.devices[arrayIndices[0]].picture.modes[arrayIndices[1]].displayType > 0;" 1385 | } 1386 | }, 1387 | "namePrefix": { 1388 | "title": "Prefix", 1389 | "type": "boolean", 1390 | "default": false, 1391 | "description": "Here enable the accessory name as a prefix for picture mode.", 1392 | "condition": { 1393 | "functionBody": "return model.devices[arrayIndices[0]].picture.modes[arrayIndices[1]].displayType > 0;" 1394 | } 1395 | } 1396 | }, 1397 | "required": [ 1398 | "displayType" 1399 | ] 1400 | } 1401 | } 1402 | } 1403 | }, 1404 | "screen": { 1405 | "title": "Screen", 1406 | "type": "object", 1407 | "properties": { 1408 | "turnOnOff": { 1409 | "title": "Turn Screen ON/OFF", 1410 | "type": "boolean", 1411 | "default": false, 1412 | "description": "This enable possibility turn screen ON/OFF, webOS >= 4.0." 1413 | }, 1414 | "saverOnOff": { 1415 | "title": "Turn Screen Saver ON/OFF", 1416 | "type": "boolean", 1417 | "default": false, 1418 | "description": "This enable possibility turn screen saver ON/OFF, webOS >= 4.0." 1419 | } 1420 | } 1421 | }, 1422 | "sslWebSocket": { 1423 | "title": "SSL WebSocket", 1424 | "type": "boolean", 1425 | "default": false, 1426 | "description": "This enable SSL WebSocket, support TV with new firmware." 1427 | }, 1428 | "disableTvService": { 1429 | "title": "Disable TV Service", 1430 | "type": "boolean", 1431 | "default": false, 1432 | "description": "This disable TV service and prevent display double services if TV already support HomeKit native." 1433 | }, 1434 | "infoButtonCommand": { 1435 | "title": "Info Button", 1436 | "type": "string", 1437 | "default": "MENU", 1438 | "description": "Here select the function of info button in RC.", 1439 | "anyOf": [ 1440 | { 1441 | "title": "Home", 1442 | "enum": [ 1443 | "HOME" 1444 | ] 1445 | }, 1446 | { 1447 | "title": "Menu", 1448 | "enum": [ 1449 | "MENU" 1450 | ] 1451 | }, 1452 | { 1453 | "title": "Q Menu", 1454 | "enum": [ 1455 | "QMENU" 1456 | ] 1457 | }, 1458 | { 1459 | "title": "Info", 1460 | "enum": [ 1461 | "INFO" 1462 | ] 1463 | }, 1464 | { 1465 | "title": "Red", 1466 | "enum": [ 1467 | "RED" 1468 | ] 1469 | }, 1470 | { 1471 | "title": "Green", 1472 | "enum": [ 1473 | "GREEN" 1474 | ] 1475 | }, 1476 | { 1477 | "title": "Yellow", 1478 | "enum": [ 1479 | "YELLOW" 1480 | ] 1481 | }, 1482 | { 1483 | "title": "Blue", 1484 | "enum": [ 1485 | "BLUE" 1486 | ] 1487 | } 1488 | ] 1489 | }, 1490 | "log": { 1491 | "title": "Log", 1492 | "type": "object", 1493 | "properties": { 1494 | "deviceInfo": { 1495 | "title": "Device Info", 1496 | "type": "boolean", 1497 | "default": true, 1498 | "description": "This enable logging device info by every connections device to the network." 1499 | }, 1500 | "success": { 1501 | "title": "Success", 1502 | "type": "boolean", 1503 | "default": true 1504 | }, 1505 | "info": { 1506 | "title": "Info", 1507 | "type": "boolean", 1508 | "default": false 1509 | }, 1510 | "warn": { 1511 | "title": "Warn", 1512 | "type": "boolean", 1513 | "default": true 1514 | }, 1515 | "error": { 1516 | "title": "Error", 1517 | "type": "boolean", 1518 | "default": true 1519 | }, 1520 | "debug": { 1521 | "title": "Debug", 1522 | "type": "boolean", 1523 | "default": false 1524 | } 1525 | } 1526 | }, 1527 | "restFul": { 1528 | "title": "RESTFul", 1529 | "type": "object", 1530 | "properties": { 1531 | "enable": { 1532 | "title": "Enable", 1533 | "type": "boolean", 1534 | "default": false, 1535 | "description": "This enable RESTful server." 1536 | }, 1537 | "port": { 1538 | "title": "Port", 1539 | "type": "integer", 1540 | "placeholder": 3000, 1541 | "description": "Here set the listening Port for RESTful server.", 1542 | "condition": { 1543 | "functionBody": "return model.devices[arrayIndices].restFul.enable === true;" 1544 | } 1545 | } 1546 | } 1547 | }, 1548 | "mqtt": { 1549 | "title": "MQTT", 1550 | "type": "object", 1551 | "properties": { 1552 | "enable": { 1553 | "title": "Enable", 1554 | "type": "boolean", 1555 | "default": false, 1556 | "description": "This enable MQTT client." 1557 | }, 1558 | "host": { 1559 | "title": "IP/Hostname", 1560 | "type": "string", 1561 | "placeholder": "ip or hostname", 1562 | "format": "hostname", 1563 | "description": "Here set the IP/Hostname of MQTT Broker.", 1564 | "condition": { 1565 | "functionBody": "return model.devices[arrayIndices].mqtt.enable === true;" 1566 | } 1567 | }, 1568 | "port": { 1569 | "title": "Port", 1570 | "type": "integer", 1571 | "placeholder": 1883, 1572 | "description": "Here set the port of MQTT Broker.", 1573 | "condition": { 1574 | "functionBody": "return model.devices[arrayIndices].mqtt.enable === true;" 1575 | } 1576 | }, 1577 | "clientId": { 1578 | "title": "Client ID", 1579 | "type": "string", 1580 | "placeholder": "client id", 1581 | "description": "Here optional set the Client ID of MQTT Broker.", 1582 | "condition": { 1583 | "functionBody": "return model.devices[arrayIndices].mqtt.enable === true" 1584 | } 1585 | }, 1586 | "prefix": { 1587 | "title": "Prefix", 1588 | "type": "string", 1589 | "placeholder": "home", 1590 | "description": "Here set the prefix.", 1591 | "condition": { 1592 | "functionBody": "return model.devices[arrayIndices].mqtt.enable === true;" 1593 | } 1594 | }, 1595 | "auth": { 1596 | "title": "Authorization", 1597 | "type": "object", 1598 | "properties": { 1599 | "enable": { 1600 | "title": "Enable", 1601 | "type": "boolean", 1602 | "default": false, 1603 | "description": "This enable authorization for MQTT Broker." 1604 | }, 1605 | "user": { 1606 | "title": "User", 1607 | "type": "string", 1608 | "placeholder": "user", 1609 | "description": "Here set the user of MQTT Broker.", 1610 | "condition": { 1611 | "functionBody": "return model.devices[arrayIndices].mqtt.auth.enable === true;" 1612 | } 1613 | }, 1614 | "passwd": { 1615 | "title": "Password", 1616 | "type": "string", 1617 | "placeholder": "password", 1618 | "description": "Here set the password of MQTT Broker.", 1619 | "format": "password", 1620 | "condition": { 1621 | "functionBody": "return model.devices[arrayIndices].mqtt.auth.enable === true;" 1622 | } 1623 | } 1624 | }, 1625 | "condition": { 1626 | "functionBody": "return model.devices[arrayIndices].mqtt.enable === true;" 1627 | } 1628 | } 1629 | } 1630 | } 1631 | }, 1632 | "required": [ 1633 | "name", 1634 | "host", 1635 | "mac", 1636 | "displayType" 1637 | ] 1638 | } 1639 | } 1640 | } 1641 | }, 1642 | "layout": [ 1643 | { 1644 | "key": "devices", 1645 | "type": "tabarray", 1646 | "title": "{{ value.name || 'device' }}", 1647 | "items": [ 1648 | "devices[].name", 1649 | "devices[].host", 1650 | "devices[].mac", 1651 | "devices[].displayType", 1652 | { 1653 | "key": "devices[].inputs", 1654 | "type": "section", 1655 | "title": "Inputs", 1656 | "expandable": true, 1657 | "expanded": false, 1658 | "items": [ 1659 | "devices[].inputs.getFromDevice", 1660 | "devices[].inputs.filterSystemApps", 1661 | "devices[].inputs.displayOrder", 1662 | { 1663 | "key": "devices[].inputs.data", 1664 | "type": "tabarray", 1665 | "title": "{{ value.name || 'input' }}", 1666 | "items": [ 1667 | "devices[].inputs.data[].name", 1668 | "devices[].inputs.data[].reference", 1669 | "devices[].inputs.data[].mode" 1670 | ] 1671 | } 1672 | ], 1673 | "condition": { 1674 | "functionBody": "return model.devices[arrayIndices[0]].displayType > 0;" 1675 | } 1676 | }, 1677 | { 1678 | "key": "devices[]", 1679 | "type": "section", 1680 | "title": "Buttons", 1681 | "expandable": true, 1682 | "expanded": false, 1683 | "items": [ 1684 | { 1685 | "key": "devices[].buttons", 1686 | "type": "tabarray", 1687 | "title": "{{ value.name || 'button' }}", 1688 | "items": [ 1689 | "devices[].buttons[].displayType", 1690 | "devices[].buttons[].name", 1691 | "devices[].buttons[].mode", 1692 | "devices[].buttons[].reference", 1693 | "devices[].buttons[].command", 1694 | "devices[].buttons[].namePrefix" 1695 | ] 1696 | } 1697 | ], 1698 | "condition": { 1699 | "functionBody": "return model.devices[arrayIndices[0]].displayType > 0;" 1700 | } 1701 | }, 1702 | { 1703 | "key": "devices[]", 1704 | "type": "section", 1705 | "title": "Advanced Settings", 1706 | "expandable": true, 1707 | "expanded": false, 1708 | "items": [ 1709 | { 1710 | "key": "devices[]", 1711 | "type": "tabarray", 1712 | "title": "{{ value.title }}", 1713 | "items": [ 1714 | { 1715 | "key": "devices[]", 1716 | "title": "Sensors", 1717 | "items": [ 1718 | { 1719 | "key": "devices[].sensors", 1720 | "type": "tabarray", 1721 | "title": "{{ value.name || 'sensor' }}", 1722 | "items": [ 1723 | "devices[].sensors[].displayType", 1724 | "devices[].sensors[].mode", 1725 | "devices[].sensors[].name", 1726 | "devices[].sensors[].namePrefix", 1727 | "devices[].sensors[].pulse", 1728 | "devices[].sensors[].reference", 1729 | "devices[].sensors[].level" 1730 | ] 1731 | } 1732 | ] 1733 | }, 1734 | { 1735 | "key": "devices[].power", 1736 | "title": "Power", 1737 | "items": [ 1738 | "devices[].power.broadcastAddress", 1739 | "devices[].power.startInput", 1740 | "devices[].power.startInputReference" 1741 | ] 1742 | }, 1743 | { 1744 | "key": "devices[].volume", 1745 | "title": "Volume", 1746 | "items": [ 1747 | "devices[].volume.displayType", 1748 | "devices[].volume.name", 1749 | "devices[].volume.namePrefix" 1750 | ] 1751 | }, 1752 | { 1753 | "key": "devices[].sound", 1754 | "title": "Sound", 1755 | "items": [ 1756 | { 1757 | "key": "devices[].sound", 1758 | "type": "tabarray", 1759 | "title": "{{ value.name || 'sensor' }}", 1760 | "items": [ 1761 | { 1762 | "title": "Mode", 1763 | "items": [ 1764 | { 1765 | "key": "devices[].sound.modes", 1766 | "type": "tabarray", 1767 | "title": "{{ value.name || 'mode' }}", 1768 | "items": [ 1769 | "devices[].sound.modes[].displayType", 1770 | "devices[].sound.modes[].name", 1771 | "devices[].sound.modes[].reference", 1772 | "devices[].sound.modes[].namePrefix" 1773 | ] 1774 | } 1775 | ] 1776 | }, 1777 | { 1778 | "title": "Output", 1779 | "items": [ 1780 | { 1781 | "key": "devices[].sound.outputs", 1782 | "type": "tabarray", 1783 | "title": "{{ value.name || 'output' }}", 1784 | "items": [ 1785 | "devices[].sound.outputs[].displayType", 1786 | "devices[].sound.outputs[].name", 1787 | "devices[].sound.outputs[].reference", 1788 | "devices[].sound.outputs[].namePrefix" 1789 | ] 1790 | } 1791 | ] 1792 | } 1793 | ] 1794 | } 1795 | ] 1796 | }, 1797 | { 1798 | "key": "devices[].picture", 1799 | "title": "Picture", 1800 | "items": [ 1801 | "devices[].picture.brightnessControl", 1802 | "devices[].picture.backlightControl", 1803 | "devices[].picture.contrastControl", 1804 | "devices[].picture.colorControl", 1805 | { 1806 | "type": "section", 1807 | "title": "Modes", 1808 | "expandable": true, 1809 | "expanded": false, 1810 | "items": [ 1811 | { 1812 | "key": "devices[].picture.modes", 1813 | "type": "tabarray", 1814 | "title": "{{ value.name || 'mode' }}", 1815 | "items": [ 1816 | "devices[].picture.modes[].displayType", 1817 | "devices[].picture.modes[].name", 1818 | "devices[].picture.modes[].reference", 1819 | "devices[].picture.modes[].namePrefix" 1820 | ] 1821 | } 1822 | ] 1823 | } 1824 | ] 1825 | }, 1826 | { 1827 | "key": "devices[].screen", 1828 | "title": "Screen", 1829 | "items": [ 1830 | "devices[].screen.turnOnOff", 1831 | "devices[].screen.saverOnOff" 1832 | ] 1833 | }, 1834 | { 1835 | "key": "devices[]", 1836 | "title": "Device", 1837 | "items": [ 1838 | "devices[].sslWebSocket", 1839 | "devices[].disableTvService", 1840 | "devices[].infoButtonCommand" 1841 | ] 1842 | }, 1843 | { 1844 | "key": "devices[].log", 1845 | "title": "Log", 1846 | "items": [ 1847 | "devices[].log.deviceInfo", 1848 | "devices[].log.success", 1849 | "devices[].log.info", 1850 | "devices[].log.warn", 1851 | "devices[].log.error", 1852 | "devices[].log.debug" 1853 | ] 1854 | }, 1855 | { 1856 | "title": "External Integrations", 1857 | "items": [ 1858 | { 1859 | "key": "devices[]", 1860 | "type": "tabarray", 1861 | "title": "{{ value.title }}", 1862 | "items": [ 1863 | { 1864 | "key": "devices[].restFul", 1865 | "title": "RESTFul", 1866 | "items": [ 1867 | "devices[].restFul.enable", 1868 | "devices[].restFul.port" 1869 | ] 1870 | }, 1871 | { 1872 | "key": "devices[].mqtt", 1873 | "title": "MQTT", 1874 | "items": [ 1875 | "devices[].mqtt.enable", 1876 | "devices[].mqtt.host", 1877 | "devices[].mqtt.port", 1878 | "devices[].mqtt.clientId", 1879 | "devices[].mqtt.prefix", 1880 | { 1881 | "key": "devices[].mqtt.auth", 1882 | "title": "Authorization", 1883 | "items": [ 1884 | "devices[].mqtt.auth.enable", 1885 | "devices[].mqtt.auth.user", 1886 | { 1887 | "key": "devices[].mqtt.auth.passwd", 1888 | "type": "password" 1889 | } 1890 | ] 1891 | } 1892 | ] 1893 | } 1894 | ] 1895 | } 1896 | ] 1897 | } 1898 | ] 1899 | } 1900 | ], 1901 | "condition": { 1902 | "functionBody": "return model.devices[arrayIndices[0]].displayType > 0;" 1903 | } 1904 | } 1905 | ] 1906 | } 1907 | ] 1908 | } --------------------------------------------------------------------------------