Issue with LG smart tv

here you go

/**
 *  Copyright (C) Sebastian YEPES
 *  Original Authors: Sam Lalor, Andrew Stanley-Jones
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */

import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.transform.Field

@Field String VERSION = "1.0.0"

@Field List<String> LOG_LEVELS = ["error", "warn", "info", "debug", "trace"]
@Field String DEFAULT_LOG_LEVEL = LOG_LEVELS[2]
@Field static Map callbacks = [:]

metadata {
  definition (name: "LG WebOS TV", namespace: "syepes", author: "Sebastian YEPES", importUrl: "https://raw.githubusercontent.com/syepes/Hubitat/master/Drivers/LG/LG%20WebOS%20TV.groovy") {
    capability "Initialize"
    capability "TV"
    capability "AudioVolume"
    capability "Refresh"
    capability "Switch"
    capability "Notification"

    command "off"
    command "refresh"
    command "refreshInputList"
    command "getMouseURI"
    command "externalInput", ["string"]
    command "sendJson", ["string"]
    command "myApps"
    command "ok"
    command "home"
    command "left"
    command "right"
    command "up"
    command "down"
    command "back"
    command "enter"
    command "notificationIcon", ["string", "string"]
    command "setIcon", ["string", "string"]
    command "clearIcons"
    command "testWebSocketReply", ["string"]

    attribute "availableInputs", "list"

    attribute "channelDesc", "string"
    attribute "channelName", "string"
    attribute "channelFullNumber", "string"
  }

  preferences {
    section { // General
      input name: "logLevel", title: "Log Level", type: "enum", options: LOG_LEVELS, defaultValue: DEFAULT_LOG_LEVEL, required: false
      input name: "logDescText", title: "Log Description Text", type: "bool", defaultValue: false, required: false
    }
    section { // Configuration
      input name: "televisionIp", type: "text", title: "Television IP Address",  defaultValue: "",  required: true
      input name: "televisionMac", type: "text", title: "Television MAC Address", defaultValue: "",  required: true
      input name: "pairingKey", type: "text", title: "Pairing Key", required: true, defaultValue: ""
      input name: "retryDelay", title: "Device Reconnect delay", type: "enum", options: [["5":"Retry every 5 seconds"], ["10":"Retry every 10 seconds"], ["15":"Retry every 15 seconds"], ["30":"Retry every 30 seconds"], ["45":"Retry every 45 seconds"], ["60":"Retry every minute"], ["120":"Retry 2 minute"], ["300":"Retry every 5 minutes"], ["600":"Retry every 10 minutes"]], defaultValue: 60
    }
  }
}


def installed() {
  logger("debug", "installed() - settings: ${settings?.inspect()}")
//    initialize()
}

def refresh() {
  logger("debug", "refresh()")
  state.deviceInfo = null
  state.televisionModel = null
  state.nameToInputId = null

  webosRegister()
}

def webosRegister() {
  logger("debug", "webosRegister() - pairing key: ${state.pairingKey}")
  state.pairFailCount = 0

  def payload = [
    pairingType: "PROMPT",
    forcePairing: false,
    'client-key': state?.pairingKey,
    manifest: [
      appVersion: "1.1",
      signed: [
        localizedVendorNames: [ "": "LG Electronics" ],
        appId: "com.lge.test",
        created: "20140509",
        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",
        ],
        localizedAppNames: [
          "": "LG Remote App",
          "ko-KR": "리모컨 앱",
          "zxx-XX": "ЛГ Rэмotэ AПП",
        ],
        vendorId: "com.lge",
        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",
      ],
      manifestVersion: 1,
      signatures: [
        [
          signatureVersion: 1,
          signature: "eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==",
        ],
      ]
    ]
  ]

  sendWebosCommand(type: "register", payload: payload, callback: { json ->
    logger("trace", "webosRegister() - json: ${json?.inspect()}")

    if (json?.type == "registered") {
      pKey = json.payload["client-key"]
      if (pKey != null) {
        logger("debug", "webosRegister() - received registered client-key: ${pKey}")

        state.pairingKey = pKey
        device.updateSetting("pairingKey",[type:"text", value:"${pKey}"])
        runInMillis(10, webosSubscribeToStatus)
        runInMillis(25, getMouseURI)
        // Hello doesn't seem to do anything?
        if (!state.deviceInfo) runInMillis(50, sendHello)
        if (!state.televisionModel) runInMillis(75, sendRequestInfo)
        if (!state.nameToInputId) runInMillis(100, refreshInputList)
        if (!state.serviceList) runInMillis(125, getServiceList)
      }
      return true
    } else if (json?.type == "response") {
        return false
    }
  })
}

def sendHello() {
  logger("debug", "sendHello()")
  sendWebosCommand(type: "hello", id: "hello")
}

def handler_hello(data) {
  logger("debug", "handler_hello() - data: ${data?.inspect()}")
  state.deviceInfo = data
}

def sendRequestInfo() {
  logger("debug", "sendRequestInfo()")

  sendWebosCommand(uri: "system/getSystemInfo", callback: { json ->
    logger("trace", "sendRequestInfo() - json: ${json?.inspect()}")
    state.televisionModel = json.payload?.modelName
    state.televisionReceiver = json.payload?.receiverType
  })
}

def refreshInputList() {
  logger("debug", "refreshInputList() - current list size: ${state.nameToInputId?.size()}")

  sendWebosCommand(uri: "com.webos.applicationManager/listLaunchPoints", payload: [], callback: { json ->
    logger("trace", "refreshInputList() - json: ${json?.inspect()}")
    def inputList = []
    def nameToInputId = [:]
    json?.payload?.launchPoints.each { app ->
      logger("debug", "refreshInputList() - App Name: ${app.title}, App: ${app}")
      inputList += app.title
      nameToInputId[app.title] = app.id
    }

    state.nameToInputId = nameToInputId
    state.inputList = inputList

    sendWebosCommand(uri: 'tv/getExternalInputList', callback: { jsonExt ->
      logger("trace", "refreshInputList() - jsonExt: ${jsonExt?.inspect()}")

      jsonExt?.payload?.devices?.each { device ->
        logger("debug", "refreshInputList() - Device: ${device?.label}")
        inputList += device.label
        nameToInputId[device.label] = device.appId
      }
      state.nameToInputId = nameToInputId
      state.inputList = inputList
      logger("debug", "refreshInputList() - Inputs: ${state.inputList}")

      sendEvent(name: "availableInputs", value: inputList);
    })

  })
}

def getMouseChild() {
  logger("debug", "getMouseChild() - televisionIp: ${televisionIp}")

  try {
    def mouseDev = getChildDevice("LG_TV_Mouse_${televisionIp}")
    if(!mouseDev) mouseDev = addChildDevice("syepes", "LG WebOS Mouse", "LG_TV_Mouse_${televisionIp}")
    return mouseDev
  } catch(e) {
    logger("error", "getMouseChild() - Failed to get mouse dev: ${e}")
  }
  return null
}

def getMouseURI() {
  logger("debug", "getMouseURI()")

  def mouseDev = getMouseChild()

  sendWebosCommand(uri: "com.webos.service.networkinput/getPointerInputSocket", payload: [], callback: { json ->
    logger("trace", "getMouseURI() - json: ${json?.inspect()}")

    if (json?.payload?.socketPath) {
      logger("debug", "getMouseURI() - Send Mouse driver URI: ${json.payload.socketPath}")
      mouseDev?.setMouseURI(json.payload.socketPath)
    }
  })
}

def sendJson(String json) {
  sendCommand(json);
}

def powerEvent(String onOrOff, String type = "digital") {
  logger("debug", "powerEvent() - onOrOff: ${onOrOff}, type: ${type}")

  def descriptionText = "is ${onOrOff}"
  if (state.power != onOrOff){
    logger("info", "powerEvent() - ${descriptionText} [$type]")
  }

  state.power = onOrOff
  sendEvent(name: "switch", value: onOrOff, descriptionText: descriptionText, type: type)
  if (type == "physical") {
    sendEvent(name: "power", value: onOrOff, descriptionText: descriptionText, type: type)
  }

  if ((onOrOff == "off") && (type == "physical")) {
    sendEvent(name: "channelDesc", value: "[off]", descriptionText: descriptionText)
    sendEvent(name: "channelName", value: "[off]", descriptionText: descriptionText)
    sendEvent(name: "input", value: "[off]", descriptionText: descriptionText)
    // Socket status should follow the system reported status
    interfaces.webSocket.close()
  }
}

def initialize() {
  logger("debug", "initialize() - ip: ${televisionIp}, mac: ${televisionMac}, key: ${pairingKey}, debug: ${debug}, logText: ${descriptionText}")
  logger("debug", "initialize() - settings: ${settings.inspect()}")

  // Websocket has closed/errored, erase all callbacks
  callbacks = [:]

  // Set some basic state, clear channel info
  state.sequenceNumber = 1
  state.lastChannel = [:]
  state.pairFailCount = 0

  // When reconnectPending is true it stops reconnectWebsocket
  // from rescheudling initialize()
  state.reconnectPending = false
  state.webSocket = "initialize"

  unschedule()

  def mouseDev = getMouseChild()

  interfaces.webSocket.close()

  if(!televisionMac) {
    def mac = getMACFromIP(televisionIp)
    if (mac){
      device.updateSetting("televisionMac",[value:mac,type:"string"])
    }
  }

  try {
    logger("info", "initialize() - Connecting secure websocket to: wss://${televisionIp}:3001/")
    interfaces.webSocket.connect("wss://${televisionIp}:3001/", ignoreSSLIssues: true))
  } catch(e) {
    logger("error", "initialize() - WebSocket connect ${e?.inspect()}")
  }
}

def webSocketStatus(String status){
  logger("debug", "webSocketStatus() - status: [${status}], State: [${state.webSocket}]")

  if(status.startsWith('failure: ')) {
    //logger("error", "webSocketStatus() - ${status}")

    if ((status == "failure: No route to host (Host unreachable)") || (status == "failure: connect timed out") || status.startsWith("failure: Failed to connect") || status.startsWith("failure: sent ping but didn't receive pong")) {
      logger("info", "webSocketStatus() - WebSocket is closed")
      powerEvent("off", "physical")
    }
    state.webSocket = "closed"
    reconnectWebSocket()
  } else if(status == 'status: open') {
    logger("info", "webSocketStatus() - WebSocket is open")

    // success! reset reconnect delay
    powerEvent("on", "physical")
    state.webSocket = "open"
    webosRegister()
    state.reconnectDelay = 2
  } else if (status == "status: closing"){
    logger("info", "webSocketStatus() - WebSocket connection closing")
    unschedule()

    if (state.webSocket == 'initialize') {
      logger("info", "webSocketStatus() - Ignoring WebSocket close due to initialization")
    } else {
      if (state.power == "on") {
        // TV should be on and reachable - try to reconnect
        reconnectWebSocket(1)
      } else {
        reconnectWebSocket()
      }
    }
    state.webSocket = "closed"
  } else {
    logger("error", "webSocketStatus() - WebSocket error, reconnecting")
    powerEvent("off", "physical")
    state.webSocket = "closed"
    reconnectWebSocket()
  }
}

def reconnectWebSocket(delay = null) {
  logger("debug", "reconnectWebSocket() - delay: ${delay}")

  // first delay is 2 seconds, doubles every time
  if (state.reconnectPending == true) {
    logger("warn", "reconnectWebSocket() - Rejecting additional reconnect request")
    return
  }
  delay = delay ?: state.reconnectDelay
  state.reconnectDelay = delay * 2
  settings_retryDelay = settings.retryDelay.toInteger()
  // don't let delay get too crazy, max it out at user setting
  if (state.reconnectDelay > settings_retryDelay) {
    state.reconnectDelay = settings_retryDelay
  }

  //If the TV is offline, give it some time before trying to reconnect
  state.reconnectPending = true
  runIn(delay, initialize)
}

def updated() {
  logger("debug", "updated() - ip: ${settings.televisionIp}, mac: ${settings.televisionMac}, key: ${settings.pairingKey}")
  initialize()
}

def logsStop() {
  logger("debug", "logsStop()")
}

def setParameters(String IP, String MAC, String TVTYPE, String KEY) {
  logger("debug", "setParameters() - ip: ${IP}, mac: ${MAC}, type: ${TVTYPE}, key: ${KEY}")

  state.televisionIp = IP
  device.updateSetting("televisionIp",[type:"text", value:IP])

  state.televisionMac = MAC
  device.updateSetting("televisionMac",[type:"text", value:MAC])
}

def testWebSocketReply(String data) {
    logger("debug", "testWebSocketReply() - data: ${data}")
    parse(data)
}

// parse events into attributes
def parse(String description) {
  logger("debug", "parse() - description: ${description}")

  // parse method is shared between HTTP and Websocket implementations
  def json = null
    try {
      json = new JsonSlurper().parseText(description)
      if(json == null){
        logger("warn", "parse() - String description not parsed")
        return
      }
    } catch(e) {
      logger("error", "parse() - Failed to parse json e = ${e}")
      return
    }

    if (this."handler_${json.id}") {
      this."handler_${json.id}"(json.payload)
    } else if (this."handler_${json.type}") {
      this."handler_${json.type}"(json.payload)
    } else if (callbacks[json.id]) {
      logger("debug", "parse() - callback for json.id: " + json.id)

      callbacks[json.id].delegate = this
      callbacks[json.id].resolveStrategy = Closure.DELEGATE_FIRST
      def done = callbacks[json.id].call(json)
      if ((done instanceof Boolean) && (done == false)) {
        logger("debug", "parse() - callback[${json.id}]: being kept, done is false")
      } else {
        callbacks[json.id] = null
      }
  } else if (json?.type == "error") {
    if (json?.id == "register_0") {
      if (json?.error.take(3) == "403") {
        // 403 error cancels the pairing process
        pairingKey = ""
        state.pairFailCount = state.pairFailCount ? state.pairFailCount + 1 : 1
        logger("debug", "parse() -  received register_0 error: ${json.error} fail count: ${state.pairFailCount}")
        if (state.pairFailCount < 6) { webosRegister() }
      }
    } else {
      if (json?.error.take(3) == "401") {
        logger("warn", "parse() - received error: ${json.error}")
        //if (state.registerPending == false) { webosRegister() }
        //webosRegister()
      }
    }
  }
}

def webosSubscribeToStatus() {
  logger("debug", "webosSubscribeToStatus()")

  sendWebosCommand(uri: 'audio/getStatus', type: 'subscribe', id: 'audio_getStatus')
  sendWebosCommand(uri: 'com.webos.applicationManager/getForegroundAppInfo', type: 'subscribe', id: 'getForegroundAppInfo')
  sendWebosCommand(uri: 'tv/getChannelProgramInfo', type: 'subscribe', id: 'getChannelProgramInfo')
  //sendCommand('{"type":"subscribe","id":"status_%d","uri":"ssap://com.webos.applicationManager/getForegroundAppInfo"}')
  sendCommand('{"type":"subscribe","id":"status_%d","uri":"ssap://com.webos.service.tv.time/getCurrentTime"}')

// schedule a poll every 10 minutes to help keep the websocket open
// runEvery10Minutes("webosSubscribeToStatus")
}

def getServiceList() {
  logger("debug", "getServiceList()")

  state.remove('serviceList')
  state.serviceList = []
  sendWebosCommand(uri: 'api/getServiceList', callback: { json ->
    logger("trace", "getServiceList() - json: ${json.serviceList}")
    json?.payload?.services.each { service ->
      state.serviceList << service?.name
    }
    logger("debug", "getServiceList() - Services: ${state.serviceList}")
  })
}

def handler_audio_getStatus(data) {
    logger("debug", "handler_audio_getStatus() - data: ${data?.inspect()}")
    def descriptionText = "volume is ${data.volume}"

    logger("info", "${descriptionText}")
    sendEvent(name: "volume", value: data.volume, descriptionText: descriptionText)
}

def handler_getForegroundAppInfo(data) {
  logger("debug", "handler_getForegroundAppInfo() - data: ${data?.inspect()}")

  // Some TVs send this message when powering off
  // data: [subscribed:true, appId:, returnValue:true, windowId:, processId:]
  // json for testing: {"type":"response","id":"getForegroundAppInfo","payload":{"subscribed":true,"appId":"","returnValue":true,"windowId":"","processId":""}}
  if (!data.appId && !data.processId) {
    powerEvent("off", "physical")
    logger("info", "handler_getForegroundAppInfo() - Received POWER DOWN notification")
    return
  }

  def appId = data.appId
  def niceName = appId
  state.nameToInputId.each { name, id ->
    if (appId == id) niceName = name
  }

  def descriptionText = "channelName is ${niceName}"
  logger("info", "${descriptionText}")

  sendEvent(name: "channelName", value: niceName, descriptionText: descriptionText)
  if (niceName != "LiveTV") sendEvent(name: "channelDesc", value: "[none]")

  state.lastApp = niceName
  if (niceName == "LiveTV") {
    runIn(3, "getChannelInfo")
  } else {
    state.lastChannel = [:]
  }
}

def getChannelInfo() {
  logger("debug", "getChannelInfo()")
  sendWebosCommand(uri: 'tv/getChannelProgramInfo', id: 'getChannelProgramInfo')
}

def handler_getChannelProgramInfo(data) {
  logger("debug", "handler_getChannelProgramInfo() - data: ${data?.inspect()}")

  if (data.errorCode) {
    def lastChannel = [:]
    lastChannel.description = "${data.errorText}"
    state.lastChannel = lastChannel
    sendEvent(name: "channelDesc", value: lastChannel.channelDesc)
    // Resubscribe, after error subscription appears to be ended
    if (device.currentChannelName == "LiveTV") {
      runIn(15, "getChannelInfo")
    }
    return
  }

  def lastChannel = [
    description: "${data.channel?.channelNumber}/${data.channel?.channelName}",
    number: data.channel?.channelNumber,
    majorNumber: data?.channel?.majorNumber ?: data.channel?.channelNumber,
    minorNumber: data?.channel?.minorNumber ?: 0,
    name: data.channel?.channelName ?: "",
  ]

  state.lastChannel = lastChannel
  sendEvent(name: "channelDesc", value: lastChannel.description)
  // This is defined as a number, not a decimal so send the major number
  def descriptionText = "full channel number is ${lastChannel?.majorNumber}-${lastChannel?.minorNumber}"
  sendEvent(name: "channel", value: lastChannel?.majorNumber)
  logger("info", "${descriptionText}")

  descriptionText = "channelName is ${lastChannel.name}"
  sendEvent(name: "channelName", value: lastChannel.name, descriptionText: descriptionText)
  logger("info", "${descriptionText}")
}

def genericHandler(json) {
  logger("debug", "genericHandler() - json: ${data?.inspect()}")
}

def deviceNotification(String notifyMessage) {
  logger("debug", "deviceNotification() - notifyMessage: ${notifyMessage?.inspect()}")

  def icon_info = notifyMessage =~ /^\[(.+?)\](.+)/
  logger("debug", "deviceNotification() - new message $notifyMessage found icon: ${icon_info != null}")

  if (!icon_info) {
    sendWebosCommand(uri: "system.notifications/createToast", payload: [message: notifyMessage])
  } else {
    logger("debug", "deviceNotification() - icon_name match ${icon_name}")
    def icon_name = icon_info[0][1]
    def msg = icon_info[0][2]
    notificationIcon(msg, icon_name)
  }
}

def setIcon(String icon_name, String data) {
  logger("debug", "setIcon() - icon_name: ${icon_name?.inspect()}, data: ${data?.inspect()}")
  state.icon_data[icon_name] = data
}

def clearIcons() {
  logger("debug", "clearIcons()")
  state.icon_data = [:]
}

def notificationIcon(String notifyMessage, String icon_name) {
  logger("debug", "notificationIcon() - notifyMessage: ${notifyMessage?.inspect()}, icon_name: ${icon_name?.inspect()}")

  def base_url = "https://raw.githubusercontent.com/pasnox/oxygen-icons-png/master/oxygen/32x32"
  def icon_extention = "png"
  def full_uri = "${base_url}/${icon_name}.png"

  if (!state.icon_data) {
    state.icon_data = [:]
  }

  if (!state.icon_data[icon_name]) {
    try {
      logger("info", "notificationIcon() - asking for ${full_uri}")

      def start_time = now()
      httpGet(full_uri, { resp ->
        handleIconResponse(resp, [
          icon_extention: icon_extention,
          icon_name: icon_name,
          notify_message: notifyMessage,
          start_time: start_time
        ])
      })

    } catch (Exception e) {
      logger("warn", "notificationIcon() - asking for ${full_uri}")
      deviceNotification("<Failed to find icon: ${e.message}>${notifyMessage}")
    }
  } else {
    String icon = state.icon_data[icon_name]
    logger("debug", "notificationIcon() - icon size: ${icon.size()} sending notifcation: ${notifyMessage} name: ${icon_name} icon: ${state.icon_data[icon_name]}")
    sendWebosCommand(uri: "system.notifications/createToast", payload: [message: notifyMessage, iconData: icon, iconExtension: icon_extention])
  }
}

def handleIconResponse(resp, data) {
  logger("debug", "handleIconResponse() - resp: ${resp?.inspect()}, data: ${data?.inspect()}")

  int n = resp.data?.available()
  logger("debug", "handleIconResponse() - resp.status: ${resp.status} took: ${now() - data.start_time}ms size: ${n}")

  byte[] bytes = new byte[n]
  resp.data.read(bytes, 0, n)
  def base64String = bytes.encodeBase64().toString()
  logger("debug", "handleIconResponse() - size of b64: ${base64String.size()}")

  state.icon_data[data.icon_name] = base64String
  notificationIcon(data.notify_message, data.icon_name)
}

def on() {
  logger("debug", "on()")

  powerEvent("on")
  def mac = settings.televisionMac ?: state.televisionMac
  if (!mac) {
    logger("error", "on() - No mac address know for TV, can't send wake on lan")
    return
  }

  logger("info", "on() - Sending Magic Packet to: ${mac}")
  def result = new hubitat.device.HubAction (
    "wake on lan ${mac}",
    hubitat.device.Protocol.LAN,
    null,[secureCode: “0000”]
  )

  logger("debug", "on() - Sending Magic Packet to: ${mac}, result: ${result}")
  return result
}

def off() {
  logger("debug", "off()")
  powerEvent("off")
  sendWebosCommand(uri: 'system/turnOff')
}

def channelUp() {
  logger("debug", "channelUp()")
  sendWebosCommand(uri: 'tv/channelUp')
}

def channelDown() {
  logger("debug", "channelDown()")
  sendWebosCommand(uri: 'tv/channelDown')
}

// handle commands
def volumeUp() {
  logger("debug", "volumeUp()")
  sendWebosCommand(uri: 'audio/volumeUp')
}

def volumeDown() {
  logger("debug", "volumeDown()")
  sendWebosCommand(uri: 'audio/volumeDown')
}

def setVolume(level) {
  logger("debug", "setVolume() - level: ${level}")
  sendWebosCommand(uri: 'audio/setVolume', payload: [volume: level])
}

def setLevel(level) {
  logger("debug", "setLevel() - level: ${level}")
  setVolume(level)
}

def sendMuteEvent(muted) {
  logger("debug", "sendMuteEvent() - muted: ${muted}")

  def descriptionText = "mute is ${muted}"
  logger("info", "${descriptionText}")
  sendEvent(name: "mute", value: muted, descriptionText: descriptionText)
}

def unmute() {
  logger("debug", "unmute()")
  sendWebosCommand(uri: 'audio/setMute', payload: [mute: false], callback: { json ->
    logger("trace", "unmute() - json: ${json}")
    if (json?.payload?.returnValue) {
      sendMuteEvent("unmuted")
    }
  })
}

def mute() {
  logger("debug", "mute()")
  sendWebosCommand(uri: 'audio/setMute', payload: [mute: true], callback: { json ->
  logger("trace", "mute() - json: ${json}")
    if (json?.payload?.returnValue) {
      sendMuteEvent("muted")
    }
  })
}

def externalInput(String input) {
  logger("debug", "externalInput() - input: ${input}")

  if (state.nameToInputId && state.nameToInputId[input]) {
    input = state.nameToInputId[input]
  }
  sendWebosCommand(uri: "system.launcher/launch", payload: [id: input], callback: { json ->
    logger("trace", "externalInput() - json: ${json}")
  })
}

def enter() {
  logger("debug", "enter()")

  def mouseDev = getMouseChild()
  mouseDev?.sendButton('ENTER')
//return sendWebosCommand(uri: "com.webos.service.ime/sendEnterKey")
}

def back() {
  logger("debug", "back()")
  def mouseDev = getMouseChild()
  mouseDev?.sendButton('BACK')
}

def up() {
  logger("debug", "up()")
  def mouseDev = getMouseChild()
  mouseDev?.sendButton('UP')
}

def down() {
  logger("debug", "down()")
  def mouseDev = getMouseChild()
  mouseDev?.sendButton('DOWN')
}

def left() {
  logger("debug", "left()")
  def mouseDev = getMouseChild()
  mouseDev?.left()
}

def right() {
  logger("debug", "right()")
  def mouseDev = getMouseChild()
  mouseDev?.right()
}

def myApps() {
  logger("debug", "myApps()")
  sendWebosCommand(uri: 'system.launcher/launch', payload: [id: 'com.webos.app.discovery'])
}

def play() {
  logger("debug", "play()")
  sendWebosCommand(uri: "media.controls/play")
}

def pause() {
  logger("debug", "pause()")
  sendWebosCommand(uri: "media.controls/pause")
}

def home() {
  logger("debug", "home()")
  logger("debug", "home() - OLD Inputs: ${state.inputList} total length: ${state.toString().length()}")

  state.remove('serviceList')
  state.serviceList = []
  sendWebosCommand(uri: 'api/getServiceList', callback: { json ->
    logger("trace", "home() - getServiceList: ${json?.payload}")

    json?.payload?.services.each { service ->
      state.serviceList << service?.name
    }
    logger("info", "home() - Services: ${state.serviceList}")
  })
}

def sendCommand(cmd) {
  logger("debug", "sendCommand() - cmd: ${cmd?.inspect()}")

  def msg = String.format(cmd,state.sequenceNumber)
  logger("debug", "sendCommand() - msg: ${msg?.inspect()}")

  try {
    // send the command
    interfaces.webSocket.sendMessage(msg)
  } catch (Exception e) {
    logger("warn", "sendCommand() - Exception ${e}")
  }
  state.sequenceNumber++
}

def sendWebosCommand(Map params) {
  logger("debug", "sendWebosCommand() - params: ${params?.inspect()}")

  def id = params.id ?: ("command_" + state.sequenceNumber++)
  def cb = params.callback ?: { genericHandler(it) }
  def message_data = [
    'id': id,
    'type': params.type ?: "request",
  ]

  if (params.uri) {
    message_data.uri = "ssap://" + params.uri
  }

  if (params.payload) {
    message_data.payload = params.payload
  }

  def json = JsonOutput.toJson(message_data)
  logger("debug", "sendWebosCommand() - Sending: ${json} storing callback: ${id}")

  callbacks[id] = cb
  interfaces.webSocket.sendMessage(json)
  logger("debug", "sendWebosCommand() - Sending json: ${json}")
}

private void parseStatus(state, json) {
  logger("debug", "parseStatus() - state: ${state?.inspect()}, json: ${json?.inspect()}")

  def rResp = false
  if ((state.power == "off") && !(json?.payload?.subscribed == true)) {
    // when TV has indicated power off, do not process status messages unless they are subscriptions
    logger("debug", "parseStatus() - ignoring unsubscribed status updated during power off... message: ${json}")
    return
  }

  if (json?.payload?.returnValue == true) {
    // The last (valid) message sent by the TV when powering off is a subscription response for foreground app status with appId, windowId and processID all NULL
    if (json?.payload?.subscribed) {
      logger("debug", "parseStatus() - appID: "+ (description.contains("appId")?"T":"F") +", windowId: "+ (description.contains("windowId")?"T":"F") +", processId: "+ (description.contains("processId")?"T":"F"))

      if (description.contains("appId") && description.contains("windowId") && description.contains("processId")) {
        if ((json?.payload?.appId == null) || (json?.payload?.appId == "")) {
          // The TV is powering off - change the power state, but leave the websocket to time out
          powerEvent("off", "physical")
          logger("info", "Received POWER DOWN notification")
        }
      }
    }
  }
}


/**
 * @param level Level to log at, see LOG_LEVELS for options
 * @param msg Message to log
 */
private logger(level, msg) {
  if (level && msg) {
    Integer levelIdx = LOG_LEVELS.indexOf(level)
    Integer setLevelIdx = LOG_LEVELS.indexOf(logLevel)
    if (setLevelIdx<0) {
      setLevelIdx = LOG_LEVELS.indexOf(DEFAULT_LOG_LEVEL)
    }
    if (levelIdx<= setLevelIdx) {
      log."${level}" "${device.displayName} ${msg}"
    }
  }
}