Hi @mrferreira89 - thanks for the driver!
I did some tweaking and added some features. I don't know if you have this in github or in HPM. But here is your code, with some tweaks (buttons, slider, feedback, etc)
/**
- LG webOS TV Driver for Hubitat
- Author: Echistas
- Date: 2023-10-21
- This driver allows Hubitat to control an LG webOS TV.
- 2024-12-17 V.1.1 - hhorigian - Added Volume Feedback, Push Buttons for dashboards, Added Volume Slider Control and Feedback. Added HBO, YouTube, Amazon, Netflix buttons. Added Play, Pause, Stop controls.
*/
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
metadata {
definition(name: "LG wesocketbOS TV", namespace: "Echistas", author: "Echistas") {
capability "Switch"
capability "AudioVolume"
capability "Refresh"
capability "Actuator"
capability "Media Input Source"
capability "MusicPlayer"
capability "Initialize"
capability "PushableButton"
capability "Variable"
attribute "channel", "string"
attribute "mediaInputSource", "string"
attribute "mute", "string"
attribute "volume", "integer"
// Explicitly declare the initialize command
command "initialize"
command "launchApp", [[name: "App Id*", type: "STRING", description: "App ID to launch"]]
command "sendButton", [[name: "Button Name*", type: "STRING", description: "Button name to send"]]
command "channelUp"
command "channelDown"
command "mute"
command "unmute"
command "volumeUp"
command "volumeDown"
command "getstatus"
}
preferences {
input name: "tvIp", type: "text", title: "TV IP Address", description: "Enter your TV's IP address", defaultValue: "192.168.1.1", required: true
input name: "tvMac", type: "text", title: "TV MAC Address", description: "Enter your TV's MAC address", defaultValue: "d1:b0:ce:02:1c:tc", required: true
input name: "pairingKey", type: "text", title: "Pairing Key", description: "Leave blank initially", required: false
input name: "debugLogging", type: "bool", title: "Enable Debug Logging", defaultValue: true
}
}
def installed() {
log.info "LG webOS TV driver installed."
sendEvent(name: "volume", value: 0)
initialize()
}
def updated() {
log.info "LG webOS TV driver updated."
schedule('0/15 * * ? * *', getstatus) //BE CAREFUL this is the check for feedback schedule 15secs
initialize()
}
def initialize() {
if (debugLogging) {
log.debug "Debug logging is enabled."
runIn(1800, logsOff) // Disable debug logging after 30 minutes
}
sendEvent(name: "numberOfButtons", value: "20")
sendEvent(name: "volume", value: 0)
connect()
}
def logsOff() {
log.warn "Debug logging disabled."
device.updateSetting("debugLogging", [value: "false", type: "bool"])
}
def connect() {
if (debugLogging) log.debug "Connecting to TV at ${tvIp}"
try {
interfaces.webSocket.close()
} catch (e) {
if (debugLogging) log.debug "No existing WebSocket to close."
}
interfaces.webSocket.connect("wss://${tvIp}:3001/", pingInterval: 30, headers: ["Content-Type": "application/json"], ignoreSSLIssues: true)
}
def webSocketStatus(String status) {
if (debugLogging) log.debug "WebSocket status: ${status}"
if (status.startsWith("failure:")) {
reconnectWebSocket()
} else if (status == "status: open") {
if (debugLogging) log.debug "WebSocket is open."
startPairing()
} else if (status == "status: closing") {
if (debugLogging) log.debug "WebSocket is closing."
}
}
def reconnectWebSocket() {
if (debugLogging) log.debug "Attempting to reconnect WebSocket..."
runIn(5, connect) // Try to reconnect after 5 seconds
}
def parse(String message) {
if (debugLogging) log.debug "Received message: ${message}"
def jsonSlurper = new JsonSlurper()
def msg = jsonSlurper.parseText(message)
if (msg.type == "registered") {
pairingKey = msg.payload["client-key"]
device.updateSetting("pairingKey", [value: pairingKey, type: "text"])
log.info "Pairing successful. Client Key saved."
} else if (msg.type == "response") {
if (msg.payload["scenario"] == "mastervolume_tv_speaker_ext") { //Was requested a GetVolume
log.info "Volume = " + msg.payload["volume"]
intvolume = msg.payload["volume"]
sendEvent(name: "volume", value: intvolume)
log.info "Mute = " + msg.payload["muted"]
if (msg.payload["muted"] == false) {
sendEvent(name: "mute", value: "unmuted")
} else {
sendEvent(name: "mute", value: "muted")
}
}
// Handle responses to commands
} else if (msg.type == "error") {
log.error "Error from TV: ${msg.error}"
} else {
if (debugLogging) log.debug "Unhandled message type: ${msg.type}"
}
}
def startPairing() {
if (debugLogging) log.debug "Starting pairing process..."
def payload = [
type : "register",
id : "register_0",
payload: [
"client-key": pairingKey ?: "",
"manifest" : [
"manifestVersion": 1,
"appVersion" : "1.1",
"signed" : [
"created" : "20140509",
"appId" : "com.lge.test",
"vendorId" : "com.lge",
"localizedAppNames": [
"": "LG Remote App",
"ko-KR": "리모컨 앱",
"zxx-XX": "ЛГ Rэмotэ AПП"
],
"localizedVendorNames": [
"": "LG Electronics"
],
"permissions": [
"TEST_SECURE",
"CONTROL_INPUT_TEXT",
"CONTROL_MOUSE_AND_KEYBOARD",
"READ_INSTALLED_APPS",
"READ_LGE_SDX",
"READ_NOTIFICATIONS",
"SEARCH",
"WRITE_SETTINGS",
"WRITE_NOTIFICATION_ALERT",
"CONTROL_POWER",
"READ_CURRENT_CHANNEL",
"READ_RUNNING_APPS",
"READ_UPDATE_INFO",
"UPDATE_FROM_REMOTE_APP",
"READ_LGE_TV_INPUT_EVENTS",
"READ_TV_CURRENT_TIME"
],
"serial": "2f930e2d2cfe083771f68e4fe7bb07"
],
"permissions": [
"LAUNCH",
"LAUNCH_WEBAPP",
"APP_TO_APP",
"CLOSE",
"TEST_OPEN",
"TEST_PROTECTED",
"CONTROL_AUDIO",
"CONTROL_DISPLAY",
"CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_RECORDING",
"CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV",
"CONTROL_POWER",
"READ_APP_STATUS",
"READ_CURRENT_CHANNEL",
"READ_INPUT_DEVICE_LIST",
"READ_NETWORK_STATE",
"READ_RUNNING_APPS",
"READ_TV_CHANNEL_LIST",
"WRITE_NOTIFICATION_TOAST",
"READ_POWER_STATE",
"READ_COUNTRY_INFO"
]
]
]
]
sendWebSocketMessage(payload)
}
def on() {
if (debugLogging) log.debug "Turning on TV."
sendWakeOnLan()
runIn(5, connect) // Attempt to connect after 5 seconds
sendEvent(name: "switch", value: "on")
}
def off() {
if (debugLogging) log.debug "Turning off TV."
sendCommand("ssap://system/turnOff")
sendEvent(name: "switch", value: "off")
}
def sendCommand(uri, payload = null) {
def message = [
type : "request",
uri : uri,
id : "req_${now()}",
payload: payload ?: [:]
]
sendWebSocketMessage(message)
}
def sendWebSocketMessage(message) {
def jsonMessage = new JsonBuilder(message).toString()
if (debugLogging) log.debug "Sending message: ${jsonMessage}"
interfaces.webSocket.sendMessage(jsonMessage)
}
def setLevel(volume){
if (debugLogging) log.debug "Setting volume to ${volume}"
def intVolumeLevel = (volume >= 0) ? ((volume <=100) ? volume : 100) : 0
sendCommand("ssap://audio/setVolume", [volume: intVolumeLevel])
sendEvent(name: "volume", value: intVolumeLevel)
}
def setVolume(volumelevel)
{
log.debug("LG setVolume()")
setLevel(volumelevel)
}
def getstatus() {
if (debugLogging) log.debug "Run: Get status."
sendCommand("ssap://audio/getVolume")
}
def pause() {
if (debugLogging) log.debug "Run: Send pause Key."
sendCommand("ssap://media.controls/pause")
}
def play() {
if (debugLogging) log.debug "Run: Send play Key."
sendCommand("ssap://media.controls/play")
}
def stop() {
if (debugLogging) log.debug "Run: Send stop Key."
sendCommand("ssap://media.controls/stop")
}
def volumeUp() {
if (debugLogging) log.debug "Increasing volume."
sendCommand("ssap://audio/volumeUp")
}
def volumeDown() {
if (debugLogging) log.debug "Decreasing volume."
sendCommand("ssap://audio/volumeDown")
}
def mute() {
if (debugLogging) log.debug "Muting audio."
sendCommand("ssap://audio/setMute", [mute: true])
sendEvent(name: "mute", value: "muted")
}
def unmute() {
if (debugLogging) log.debug "Unmuting audio."
sendCommand("ssap://audio/setMute", [mute: false])
sendEvent(name: "mute", value: "unmuted")
}
def setInputSource(source) {
if (debugLogging) log.debug "Setting input source to ${source}"
sendCommand("ssap://tv/switchInput", [inputId: source])
sendEvent(name: "mediaInputSource", value: source)
}
def launchApp(appId) {
if (debugLogging) log.debug "Launching app with ID ${appId}"
sendCommand("ssap://system.launcher/launch", [id: appId])
}
def channelUp() {
if (debugLogging) log.debug "Channel up."
sendCommand("ssap://tv/channelUp")
}
def channelDown() {
if (debugLogging) log.debug "Channel down."
sendCommand("ssap://tv/channelDown")
}
def refresh() {
if (debugLogging) log.debug "Refreshing TV status."
// Implement status refresh logic here if needed
}
def sendButton(buttonName) {
if (debugLogging) log.debug "Sending button command: ${buttonName}"
sendCommand("ssap://input/generateKey", [name: buttonName])
}
def sendWakeOnLan() {
if (debugLogging) log.debug "Sending Wake-on-LAN packet to MAC: ${tvMac}"
def macFormatted = tvMac.replaceAll(":", "").toUpperCase()
def result = new hubitat.device.HubAction(
"wake on lan ${macFormatted}",
hubitat.device.Protocol.LAN,
null
)
sendHubCommand(result)
}
// ===== BUTTON INTERFACE =====
def push(pushed) {
log.debug("push: button = ${pushed}, trigger = ${state.triggered}")
if (pushed == null) {
log.warn("push: pushed is null. Input ignored")
return
}
pushed = pushed.toInteger()
switch(pushed) {
// ===== Physical Remote Commands =====
case 2 : mute(); break //
case 3 : unmute(); break
case 4 : channelUp(); break //
case 5 : channelDown(); break //
case 6 : volumeUp(); break
case 7 : volumeDown(); break
case 8 : arrowLeft(); break
case 9 : arrowRight(); break
case 20: launchApp("amazon"); break
case 21: launchApp("netflix"); break
case 22: launchApp("youtube.leanback.v4"); break
case 23: launchApp("hbo"); break
case 24: play(); break
case 25: pause(); break
case 26: stop(); break
case 27: setInputSource("HDMI_1"); break
case 28: setInputSource("HDMI_2"); break
//case 29: setInputSource("Live_TV"); break
default:
log.debug("push: Invalid Button Number!")
break
}
}