Sharp Aquos TV Driver

Here is another version to test...

No major changes... just code cleanup - removing useless code, removing experimental code, etc - It should not affect the performance at all.... but I do want to mke sure...

/**
 *  Sharp Aquos TV for Hubitat
 *  	Works on Sharp TVs
 *  	asynchronous, polling control of most Sharp TVs made from 2010 through 2015
 *
 *  	VERY losely based on: https://github.com/halkeye/sharp.aquos.devicetype.groovy
 *      Modified for Hubitat by CybrMage
 */

def version() {return "v0.39"}

import groovy.transform.Field
@Field static java.util.concurrent.TimeUnit SECONDS = java.util.concurrent.TimeUnit.SECONDS
@Field static java.util.concurrent.Semaphore mutex = new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore parseMutex = new java.util.concurrent.Semaphore(1)
@Field static volatile java.util.concurrent.CopyOnWriteArrayList IOBUFFER = []
@Field static volatile String tv_channel_DC2U = ""
@Field static volatile String tv_channel_DC2L = ""
@Field static volatile String tv_channel_DC10 = ""
@Field static volatile String tv_channel_DC11 = ""
@Field static volatile Integer SequenceNumber = 0
@Field static volatile Integer SocketErrors = 0
@Field static volatile Integer MutexErrors = 0
@Field static volatile Boolean InitMode

preferences {
	input("destIp", "text", title: "IP", description: "The IP Address of the Sharp TV",required:true)
	input("destPort", "number", title: "Port", description: "The IP port of the Sharp TV", required:true)
	input("login", "text", title: "Login", description: "The login username")
	input("password", "password", title: "Password", description: "The login password")

	input ("logDebug", "bool", title: "Enable debug logging", defaultValue: true)
	input ("verboseDebug", "bool", title: "Enable verbose debug logging", defaultValue: true)
	input ("eventDebug", "bool", title: "Enable event logging", defaultValue: true)
	def pollTime = [:]
	pollTime << ["1" : "Refresh every minute"]
	pollTime << ["5" : "Refresh every 5 minutes"]
	pollTime << ["10" : "Refresh every 10 minutes"]
	pollTime << ["15" : "Refresh every 15 minutes"]
	pollTime << ["30" : "Refresh every 30 minutes"]
	pollTime << ["60" : "Refresh every 1 hour"]
	input ("pollRate", "enum", title: "Device poll rate", options: pollTime, defaultValue: "5")
}

metadata {
	definition (name: "Sharp Aquos TV", namespace: "SharpAquos", author: "various") {
		capability "Actuator"
		capability "AudioVolume"
//		capability "TV"
		capability "Initialize"
		capability "Switch"
		capability "Refresh"

		attribute "input", "string"
		attribute "inputName", "string"
		attribute "3DMode", "string"
		attribute "3DModeName", "string"
		attribute "SleepTimer", "string"
		attribute "SleepTimerSetting", "string"
		attribute "ViewMode", "string"
		attribute "ViewModeName", "string"
		attribute "SurroundMode", "string"
		attribute "SurroundModeName", "string"
		attribute "VideoMode", "string"
		attribute "VideoModeName", "string"
		attribute "channel_type", "string"
		attribute "channel_source", "string"
		attribute "channel", "string"

		command "channelUp"
		command "channelDown"
		
		command "initialize"
		
//		command "on"
//		command "off"
		
		command "toggleMute"
//		command "mute"
//		command "unmute"
		
		command "set3DMode", [[name:"3D Mode", type: "ENUM", description: "Select a 3D Mode", constraints: getvalid3DModes()]]
		command "setSleepTimer", [[name:"Sleep Timer Setting", type: "ENUM", description: "Set a Sleep Timer", constraints: getvalidSleepTimes()]]
							   
		command "setViewMode", [[name: "View Mode ID*", type: "NUMBER"]]
		command "setSurroundMode", [[name: "Surround Mode ID*", type: "NUMBER"]]
		command "setVideoMode", [[name: "AV Mode ID*", type: "NUMBER"]]
		
		command "inputNext"
		command "inputSelect", [[name: "Input ID*", type: "NUMBER"]]
		command "inputRemoteCommand", [[name: "Remote Control Command ID*", type: "NUMBER"]]
		
		command "setChannel", [[name:"ChannelType", type: "ENUM", description: "Select channel Type", constraints: ["Analog","Digital"]],[name:"ChannelSource", type: "ENUM", description: "Select channel Source", constraints: ["Air","Cable"]],[name:"ChannelID", type:"STRING"]]
		

		command "testParse", [[name: "Handler*", type:"STRING"],[name: "Data*", type: "STRING"]]
		command "testCommand", [[name: "CMD*", type:"STRING"],[name: "PARAM*", type: "STRING"]]
		
//		command "volumeUp"
//		command "volumeDown"
		
	}
}

def refresh() {
	getDeviceState()
}

def initialize() {
	logDebug = logDebug ?: true
	device.updateSetting("logDebug",logDebug)
	verboseDebug = verboseDebug ?: true
	device.updateSetting("verboseDebug",verboseDebug)
	eventDebug = eventDebug ?: true
	device.updateSetting("eventDebug",eventDebug)
	Init()
}

def Init() {
	if (InitMode == true) {
		log.debug("initialize - Driver " + version() + " - ERROR - initialize() already in progress. Aborting.")
		return
	}
	SocketErrors = 0
	MutexErrors = 0
	log.warn("initialize - Driver " + version() + "  debug [${logDebug}]  verbose [${verboseDebug}]  events [${eventDebug}]  permits [${mutex.availablePermits()}] Init [${InitMode}]  Queue [${mutex.getQueueLength()}]")

	InitMode = true
	
	unschedule()
	releaseMutex()
	parseMutex.reducePermits(Math.abs(1 - parseMutex.availablePermits()))
	closeSocket()
	pauseExecution(500)
	
	if ((destIp == null)||(destIp == "")) {
		log_error("initialize - ERROR - TV IP Address not set")
		InitMode = false
		return
	}
	if ((destPort == null)||(destPort == "")) {
		log_error("initialize - ERROR - TV IP Port not set")
		InitMode = false
		return
	}
	
	pollRate = pollRate ?: "5"
	
	log_info("initialize - IP: ${destIp}  PORT: ${destPort}  USER: ${login}  PASS: ${password}  Poll Rate: ${pollRate}")
	currentHandler = "parse"
	
	// set the deviceNetworkIdentifier. Ideally, this should be the MAC address of the device
	// If the MAC can not be determined, use the IP address
	def TVmac = null
	def macTries = 0
	while ((macTries < 5) && (TVmac == null)) {
		macTries++
		TVmac = getMACFromIP(destIp)
		if (TVmac == null) {
			log_info("initialize - TV mac address not yet available. Retry in 1 second.")
			pauseExecution(1000)
		}
	}
	if (TVmac != null) {
		device.deviceNetworkId = "$TVmac"
	} else {
		device.deviceNetworkId = convertIPtoHex(destIp)
	}
	log_info("initialize - Set DNI = [${device.deviceNetworkId}]")
	
	if (openSocket() == false) {
		InitMode = false
		log_error("initialize - ERROR - Could not connect to TV. Retry pending")
		runIn(10,"Init")
		return
	}
	
	switch(pollRate) {
		case "1" :
			runEvery1Minute("getDeviceState")
			log_info("initialize - Poll Rate setto every 1 minute")
			break
		case "5" :
			runEvery5Minutes("getDeviceState")
			log_info("initialize - Poll Rate set to every 5 minutes")
			break
		case "10" :
			runEvery10Minutes("getDeviceState")
			log_info("initialize - Poll Rate set to every 10 minutes")
			break
		case "15" :
			runEvery15Minutes("getDeviceState")
			log_info("initialize - Poll Rate set to every 15 minutes")
			break
		case "30" :
			runEvery30Minutes("getDeviceState")
			log_info("initialize - Poll Rate set to every 30 minutes")
			break
		case "60" :
			runEvery1hour("getDeviceState")
			log_info("initialize - Poll Rate set to every 1 hour")
			break
	}

	InitMode = false
	request("RSPW","2")
	getTvInfo()
	getDeviceState()
	log_debug("initialize - COMPLETED")
}

def updated() {
	log_debug("updated - IP [${destIp}]  PORT [${destPort}]  USER [${login}]  PASS [${password}]  DEBUG [${LogDebug}]  VERBOSE [${VerboseDebug}]  EVENTS[${eventDebug}]")
	initialize()
}

def installed() {
	log_debug("installed")
}

// global constants
def getvalidSleepTimes() {
	return [
		0: "Off",
		1: "30 Minutes",
		2: "60 Minutes",
		3: "90 Minutes",
		4: "120 Minutes"
	]
}

def getvalid3DModes() {
	return [
		0: "3D Off",
		1: "2D to 3D",
		2: "SBS",
		3: "TAB",
		4: "3D to 2D (SBS)",
		5: "3D to 2D (TAB)",
		6: "3D auto",
		7: "2D auto" 
	]
}

def getvalidViewModes() {
	return [
		1: "Side Bar", 
		2: "S.Stretch",
		3: "Zoom",
		4: "Stretch",
		5: "Normal [PC]",
		6: "Zoom [PC]",
		7: "Stretch [PC]",
		8: "Dot by Dot",
		9: "Full Screen",
		10: "Auto",
		11: "Original"
	]
}

def getvalidSurroundModes() {
	return [
		1: "Normal (On)", 
		2: "Off", 
		4: "3D Hall", 
		5: "3D Movie", 
		6: "3D Standard", 
		7: "3D Stadium"
	]
}

def getvalidVideoModes() {
	return [
		1: "STANDARD",
		2: "MOVIE",
		3: "GAME",
		4: "USER",
		5: "DYNAMIC (Fixed)",
		6: "DYNAMIC",
		7: "PC",
		8: "x.v.Color",
		14: "STANDARD (3D)",
		15: "MOVIE (3D)",
		16: "GAME (3D)",
		100: "AUTO"
	]
}

// NOTE: valid inputs based on LE84x/LE74x models.
//  They may need to be modified to reflect the inputs on other models
def getvalidInputs() {
	return [
		0: "TV",
		1: "HDMI IN 1",
		2: "HDMI IN 2",
		3: "HDMI IN 3",
		4: "HDMI IN 4",
		5: "COMPONENT",
		6: "VIDEO 1",
		7: "VIDEO 2",
		8: "PC IN"
	]
}

def getvalidRemoteCommands() {
	return [
		0: "0",
		1: "1",
		2: "2",
		3: "3",
		4: "4",
		5: "5",
		6: "6",
		7: "7",
		8: "8",
		9: "9",
		10: ".",
		11: "ENT",
		12: "POWER",
		13: "DISPLAY", 
		14: "POWER (SOURCE)",
		15: "REWIND", 
		16: "PLAY", 
		17: "FAST FORWARD", 
		18: "PAUSE", 
		19: "SKIP BACK", 
		20: "STOP", 
		21: "SKIP FORWARD", 
		22: "REC", 
		23: "OPTION", 
		24: "SLEEP", 
		27: "CC", 
		28: "AV MODE", 
		29: "VIEW MODE", 
		30: "FLASHBACK", 
		31: "MUTE", 
		32: "VOL -", 
		33: "VOL +", 
		34: "CH Up", 
		35: "CH Down", 
		36: "INPUT", 
		38: "MENU", 
		39: "SmartCentral", 
		40: "ENTER", 
		41: "UP", 
		42: "DOWN", 
		43: "LEFT", 
		44: "RIGHT",  
		45: "RETURN", 
		46: "EXIT", 
		47: "FAVORITE CH", 
		48: "3D Surround", 
		49: "AUDIO", 
		50: "A (red)", 
		51: "B (green)", 
		52: "C (blue)", 
		53: "D (yellow)", 
		54: "FREEZE", 
		55: "FAV APP 1", 
		56: "FAV APP 2", 
		57: "FAV APP 3",
		58: "3D", 
		59: "NETFLIX",
		60: "AAL",
		61: "MANUAL"
	]
}

def getvalidCommands() {
	return [
		"RSPW","POWR","ITGD","ITVD","IAVD","AVMD","VOLM","HPOS",
		"VPOS","CLCK","PHSE","WIDE","MUTE","ACSU","ACHA","OFTM",
		"DCCH","DA2P","DC2U","DC2L","DC10","DC11","CHUP","CHDW",
		"CLCP","TVNM","MNRD","SWVN","IPPV","TDCH","RCKY"
	]
}

def isValidCommand(String Command) {
	if (Command == null) { Command = "NULL" }
	def CMD = Command.take(4)
	def valid = (getvalidCommands().indexOf(CMD) == -1) ? false : true
	return valid
}

def getNextResponse() {
	def idx = -1
	if (IOBUFFER.size() > 1) {
		IOBUFFER.eachWithIndex { it, i -> 
			// `it` is the current element, while `i` is the index
			if ((isValidCommand(it) == false) && (idx == -1)) {
				idx = i
			}
		}
	}
	return idx
}

//  This is the primary parser for the device responses
def parse(String rawData) {
	parseMutex.acquire()
	if (rawData.reverse().take(2).reverse() == "0D") {
		// rawSocket and socket interfaces return Hex encoded string data
		rawData = new String(hubitat.helper.HexUtils.hexStringToByteArray(rawData))
	}
	IOBUFFER = IOBUFFER + rawData.tokenize("\r")
	if (InitMode == true) {
		log_error("parse - Plugin initializing, not parsing.")
		parseMutex.release()
		return
	}
	if (!isValidCommand(IOBUFFER[0])) {
		log_error("parse - Purging orphan response data [${IOBUFFER[0]}].")
		IOBUFFER.remove(0)
	}
	log_info("parse - IOBUFFER: ${IOBUFFER.size()} entries\r [${IOBUFFER}]")
	while (getNextResponse() > 0) {
		if (isValidCommand(IOBUFFER[0]) && !isValidCommand(IOBUFFER[1])) {
			// valid command/response pair at head of buffer
			def Command = IOBUFFER.remove(0)
			def Response = IOBUFFER.remove(0)
			log_info("parse - 1 - COMMAND [${Command}]  RESPONSE [${Response}]")
			if ((this."parse${Command.take(4)}") && (Response != "OK")) this."parse${Command.take(4)}"(Response)
		} else if (isValidCommand(IOBUFFER[0])) {
			def rIdx = getNextResponse()
			if (rIdx > 0) {
				def Response = IOBUFFER.remove(rIdx)
				def Command = IOBUFFER.remove(0)
				log_info("parse - 2 - COMMAND [${Command}]  RESPONSE [${Response}]")
				if ((this."parse${Command.take(4)}") && (Response != "OK")) this."parse${Command.take(4)}"(Response)
			}
		}
	}
	parseMutex.release()
	releaseMutex()
}

def parseTVNM(resp) {
	log_debug("parseTVNM - response '${resp}'")
	state.tv_tvnm = resp
}

def parseMNRD(resp) {
	log_debug("parseMNRD - response '${resp}'")
	state.tv_mnrd = resp
}

def parseSWVN(resp) {
	log_debug("parseSWVN - response '${resp}'")
	state.tv_swvn = resp
}

def parseIPPV(resp) {
	log_debug("parseIPPV - response '${resp}'")
	state.tv_ippv = resp
}

//def parseVolume(resp) {
def parseVOLM(resp) {
	log_debug("parseVolume - response '${resp}'")
	if ((resp != null) && (resp != "ERR")) {
		def VOLdata = 0
		try { VOLdata = resp.toInteger() } catch(e) {}
		send_Event(name: "volume", value: VOLdata ?: 0)
		log_info("parseVolume - SET - volume [${VOLdata ?: 0}]")
	} else {
		log_info("parseVolume - ERROR - invalid volume [${VOLdata}]")
	}
}

//def parseMute(resp) {
def parseMUTE(resp) {
	log_debug("parseMute - response '${resp}'")
	if ((resp != null) && (resp != "ERR")) {
		def MUTEdata = 2
		try { MUTEdata = resp.toInteger() } catch(e) {}
		def val = (MUTEdata == 2) ? "unmuted" : "muted"
		send_Event(name: "mute", value: val)
		log_info("parseMute - SET - mute [${val}]")
	} else {
		log_info("parseMute - ERROR - invalid mute [${resp}]")
	}
}

//def parsePower(resp) {
def parsePOWR(resp) {
	log_debug("parsePower - response '${resp}'")
	if ((resp != null) && (resp != "ERR")) {
		def POWRdata = -1
		try { POWRdata = resp.toInteger() } catch(e) {}
		def val = (POWRdata == 1) ? "on" : "off"
		send_Event(name: "power", value: val)
		send_Event(name: "switch", value: val)
		log_info("parsePower - SET - power [${val}]")
	} else {
		log_info("parsePower - ERROR - invalid power [${resp}]")
	}
}

//def parseInput(resp) {
def parseIAVD(resp) {
	log_debug("parseInput - response '${(resp == "ERR")?"ERR -> 0":resp}'")
	def val = 0
	try { val = resp.toInteger() } catch(e) { val = 0 }
	if (validInputs[val] != null) {
		send_Event(name: "input", value: val)
		send_Event(name: "inputName", value: validInputs[val])
		log_info("parseInput - SET - input [${val}]  InputName [${validInputs[val]}]")
	} else {
		log_info("parseInput - ERROR - Invalid Input [${val}]")
	}
}

//def parse3DMode(resp) {
def parseTDCH(val) {
	log_debug("parse3DMode - response '${val}'")
	if ((val != null) && (val != "ERR") && (valid3DModes[val.toInteger()] != null)) {
		send_Event(name: "3DMode", value: val)
		send_Event(name: "3DModeName", value: valid3DModes[val.toInteger()])
		log_info("parse3DMode - SET - 3DMode [${val}]  3DModeName [${valid3DModes[val.toInteger()]}]")
	} else {
		log_info("parse3DMode - ERROR - Invalid 3DMode [${val}]")
	}
}

//def parseSleepTimer(resp) {
def parseOFTM(val) {
	log_debug("parseSleepTimer - response '${val}'")
	if ((val != null) && (val != "ERR") && (validSleepTimes[val.toInteger()] != null)) {
		send_Event(name: "SleepTimer", value: val)
		send_Event(name: "SleepTimerSetting", value: validSleepTimes[val.toInteger()])
		log_info("parseSleepTimer - SET - SleepTimer [${val}]  SleepTimerSetting [${validSleepTimes[val.toInteger()]}]")
	} else {
		log_info("parseSleepTimer - ERROR - Invalid SleepTimer [${val}]")
	}
}

//def parseViewMode(resp) {
def parseWIDE(val) {
	log_debug("parseViewMode - response '${val}'")
	if ((val != null) && (val != "ERR") && (validViewModes[val.toInteger()] != null)) {
		send_Event(name: "ViewMode", value: val)
		send_Event(name: "ViewModeName", value: validViewModes[val.toInteger()])
		log_info("parseViewMode - SET - ViewMode [${val}]  ViewModeName [${validViewModes[val.toInteger()]}]")
	} else {
		log_info("parseViewMode - ERROR - Invalid ViewMode [${val}]")
	}
}

//def parseSurroundMode(resp) {
def parseACSU(val) {
	log_debug("parseSurround - response '${val}'")
	if ((val != null) && (val != "ERR") && (validSurroundModes[val.toInteger()] != null)) {
		send_Event(name: "SurroundMode", value: val)
		send_Event(name: "SurroundModeName", value: validSurroundModes[val.toInteger()])
		log_info("parseSurroundMode - SET - SurroundMode [${val}]  SurroundModeName [${validSurroundModes[val.toInteger()]}]")
	} else {
		log_info("parseSurroundMode - ERROR - Invalid SurroundMode [${val}]")
	}
}

//def parseVideoMode(resp) {
def parseAVMD(val) {
	log_debug("parseVideoMode - response '${val}'")
	if ((val != null) && (val != "ERR") && (validVideoModes[val.toInteger()] != null)) {
		send_Event(name: "VideoMode", value: val)
		send_Event(name: "VideoModeName", value: validVideoModes[val.toInteger()])
		log_info("parseVideoMode - SET - VideoMode [${val}]  VideoModeName [${validVideoModes[val.toInteger()]}]")
	} else {
		log_info("parseVideoMode - ERROR - Invalid VideoMode [${val}]")
	}
}

def parseDCCH(resp) {
	log_debug("parseDCCH - response '${resp}'")
	if (resp == "ERR") {
		log_info("parseDCCH - ERROR - received [${resp}]")
		return
	}
	send_Event(name: "channel_type", value: "Analog")
	send_Event(name: "channel_source", value: "Air/Cable")
	send_Event(name: "channel", value: resp)
	log_info("parseDCCH - SET - channel_type [Analog]  channel_source [Air/Cable]  channel [${resp}]")
}

def parseDA2P(resp) {
	// TV returns channel data in a different format than required to set the channel...
	log_debug("parseDA2P - response '${resp}'")
	if (resp == "ERR") {
		log_info("parseDA2P - ERROR - received [${resp}]")
		return
	}
	send_Event(name: "channel_type", value: "Digital")
	send_Event(name: "channel_source", value: "Air")
	send_Event(name: "channel", value: resp)
	log_info("parseDA2P - SET - channel_type [Digital]  channel_source [Air]  channel [${resp}]")
}

def parseDC2U(resp) {
	log_debug("parseDC2U - response '${resp}'")
	try {
		tv_channel_DC2U = resp.toInteger()
	} catch(e) {
		log_info("parseDC2U - ERROR - could not process response [${resp}]")
		return false
	}
	def channel = resp.toInteger().toString() + ".0"
	send_Event(name: "channel_type", value: "Digital")
	send_Event(name: "channel_source", value: "Cable")
	send_Event(name: "channel", value: channel)
	log_info("parseDC2U - SET - channel_type [Digital]  channel_source [Air]  channel [${channel}]")
}

def parseDC2L(resp) {
	log_debug("parseDC2L - response '${resp}'")
	try {
		tv_channel_DC2L = resp.toInteger()
	} catch(e) {
		log_info("parseDC2L - ERROR - could not process response [${resp}]")
		return false
	}
	def channel = tv_channel_DC2U + "." + tv_channel_DC2L
	send_Event(name: "channel_type", value: "Digital")
	send_Event(name: "channel_source", value: "Cable")
	send_Event(name: "channel", value: channel)
	log_info("parseDC2L - SET - channel_type [Digital]  channel_source [Air]  channel [${channel}]")
}

def parseDC10(resp) {
	log_debug("parseDC10 - response '${resp}'")
	if (resp == "ERR") {
		log_info("parseDC10 - ERROR - received [${resp}]")
		return
	}
	tv_channel_DC10 = resp
	def channel = null
	try {
		channel = (tv_channel_DC10 ?: "?") + resp.toInteger().toString()
	} catch(e) {
		log_info("parseDC10 - ERROR - could not process response [${resp}]")
		return false
	}
	send_Event(name: "channel_type", value: "Digital")
	send_Event(name: "channel_source", value: "Cable")
	send_Event(name: "channel", value: channel)
	log_info("parseDC10 - SET - channel_type [Digital]  channel_source [Air]  channel [${channel}]")
}

def parseDC11(resp) {
	log_debug("parseDC11 - response '${resp}'")
	if (resp == "ERR") {
		log_info("parseDC11 - ERROR - received [${resp}]")
		return
	}
	tv_channel_DC11 = resp
	def channel = null
	try {
		channel = tv_channel_DC11 + (tv_channel_DC10 ?: "????")
	} catch(e) {
		log_info("parseDC11 - ERROR - could not process response [${resp}]")
		return false
	}
	send_Event(name: "channel_type", value: "Digital")
	send_Event(name: "channel_source", value: "Cable")
	send_Event(name: "channel", value: channel)
	log_info("parseDC11 - SET - channel_type [Digital]  channel_source [Air]  channel [${channel}]")
}

def getTvInfo() {
	request("TVNM","1")
	request("MNRD","1")
	request("SWVN","1")
	request("IPPV","1")
	return true
}

def abortDeviceState() {
	log_debug("getDeviceState() ABORTED.")
	return false
}

def getDeviceState() {
	log_debug("getDeviceState() Starting")
	if (request("POWR","?") == "abort") {	return abortDeviceState() }
	if (request("MUTE","?") == "abort") {	return abortDeviceState() }
	if (request("VOLM","?") == "abort") {	return abortDeviceState() }
	if (request("IAVD","?") == "abort") {	return abortDeviceState() }
	if (request("WIDE","?") == "abort") {	return abortDeviceState() }
	if (request("ACSU","?") == "abort") {	return abortDeviceState() }
	if (request("AVMD","?") == "abort") {	return abortDeviceState() }
	if (request("TDCH","?") == "abort") {	return abortDeviceState() }
	if (request("OFTM","?") == "abort") {	return abortDeviceState() }
	def currentInput = device.latestValue("input")
	if ((currentInput == 0) || (currentInput == "0")) {
		// only query channel data if the input is set to TV
		if (!getChannelStatus()) {	return abortDeviceState() }
	}
	log_debug("getDeviceState() Completed.")
	return true
}

def abortChannelStatus() {
	log_debug("getChannelStatus() ABORTED.")
	return "abort"
}

def getChannelStatus() {
	log_debug("getChannelStatus() Starting")
	if (request("DCCH","?") == "abort") { return abortChannelStatus() } 
	if (request("DA2P","?") == "abort") { return abortChannelStatus() }
	if (request("DC2U","?") == "abort") { return abortChannelStatus() }
	if (request("DC2L","?") == "abort") { return abortChannelStatus() }
	if (request("DC10","?") == "abort") { return abortChannelStatus() }
	if (request("DC11","?") == "abort") { return abortChannelStatus() }
	log_debug("getChannelStatus() Completed.")
	return true
}

// device commands
def setVolume(val) {
	log_debug("setVolume(${val})")
	try {
		val = val.toInteger()
	} catch(e) {
		log_info("setVolume - ERROR - invalid volume vale [${val}]")
	}
	if (val < 0) { val = 0 }
	if (val > 60) { val = 60 }
	log_info("setVolume - SET - volume [${channel}]")
	send_Event(name: "mute", value: "unmuted")
	send_Event(name: "volume", value: val.toString())
	request("VOLM",val.toString())
}

def toggleMute() {
	log_debug("toggleMute()")
	request("RCKY","31")
//	pauseExecution(250)
	request("MUTE","?")
	log_debug("toggleMute() - completed")
}

def mute() {
	def currentMute = device.latestValue("mute")
	log_debug("mute()     Currently: [${currentMute}]")
	request("MUTE","1")
	request("MUTE","?")
	log_debug("mute() - completed")
}

def unmute() {
	def currentMute = device.latestValue("mute")
	log_debug("unmute()   Currently: [${currentMute}]")
	request("MUTE","2")
	request("MUTE","?")
	log_debug("mute() - completed")
}

def volumeUp() {
	log_debug("volumeUp()")
	request("RCKY","33")
	request("VOLM","?")
}

def volumeDown() {
	log_debug("volumeDown()")
	request("RCKY","32")
	request("VOLM","?")
}

def inputNext() {
	log_debug("inputNext()")
	def current = device.currentValue("input")
	def selectedInputs = ["1","2","3","4","5","6","7","8","1"]
	def semaphore = 0

	for(selectedInput in selectedInputs) {
		if(semaphore == 1) {
		return inputSelect(selectedInput)
    	}
		if(current == selectedInput) {
		semaphore = 1
    	}
	}

	return inputSelect(selectedInputs[0])
}


def inputRemoteCommand(cmd) {
	log_debug("inputRemoteCommand(${cmd})")
	def cmdInt = cmd.toInteger()
	if ((cmdInt == null) || (validRemoteCommands[cmdInt] == null)) {
		log_info("inputRemoteCommand - invalid command (${cmd})")
	}
	log_info("executing inputRemoteCommand('${validRemoteCommands[cmdInt]}')")
	request("RCKY",cmdInt.toString())
}

def inputSelect(channel) {
	log_debug("inputSelect(${channel})")
	if ((channel == 0) || (channel == "0")) {
		// 0 = TV - not handled by IAVD command
		request("ITVD","0")
	} else {
		request("IAVD",channel.toString())
	}
	request("IAVD","?")
}

def on() {
	log_debug("Turn on TV")
	request("POWR","1")
	request("POWR","?")
}

def off() {
	log_debug("Turn off TV")
	request("POWR","0")
	request("POWR","?")
}

def channelUp() {
	log_debug("Channel Up")
	request("CHUP","0")
}

def channelDown() {
	log_debug("Channel Down")
	request("CHDW","0")
}

def set3DMode(Mode) {
	def idx = null
	getvalid3DModes().each { key, value -> if (value == Mode) idx = key }
	log_debug("set3DMode(${Mode})   IDX: [${idx}]")
	if (idx == null) {
		log_info("set3DMode(${Mode}) - ERROR - 3D Mode is not valid")
		return
	}
	request("TDCH",idx.toString())
	request("TDCH","?")
}

def setSleepTimer(Mode) {
	def idx = null
	getvalidSleepTimes().each { key, value -> if (value == Mode) idx = key }
	log_debug("setSleepTimer(${Mode})   IDX: [${idx}]")
	if (idx == null) {
		log_info("setSleepTimer(${Mode}) - ERROR - Sleep Timer value is not valid")
		return
	}
	request("OFTM",idx.toString())
	request("OFTM","?")
}

def setViewMode(Mode) {
	log_debug("setViewMode(${Mode})")
	if (validViewModes[Mode as int] == null) {
		log_info("setViewMode(${Mode}) - Invalid View mode ID")
		return
	}
	log_info("setViewMode - Setting view mode (${Mode} = ${validViewModes[Mode as int]}]")
	request("WIDE",Mode.toString())
	request("WIDE","?")
}

def setSurroundMode(Mode) {
	log_debug("setSurroundMode(${Mode})")
	if (validSurroundModes[Mode as int] == null) {
		log_info("setSurroundMode(${Mode}) - Invalid Surround mode ID")
		return
	}
	log_info("setSurroundMode - Setting view mode (${Mode} = ${validSurroundModes[Mode as int]}]")
	request("ACSU",Mode.toString())
	request("ACSU","?")
}

def setVideoMode(Mode) {
	log_debug("setVideoMode(${Mode})")
	if (validVideoModes[Mode as int] == null) {
		log_info("setVideoMode(${Mode}) - Invalid Video mode ID")
		return
	}
	log_info("setVideoMode - Setting video mode (${Mode} = ${validVideoModes[Mode as int]}]")
	request("AVMD",Mode.toString())
	request("AVMD","?")
}

def setChannel(String Type, String Source, String ID) {
	log_debug("setChannel(\"${Type}\",\"${Source}\",\"${ID}\")")
	def setID = null
	try {
		setID = ID.toFloat()
	} catch(e) {
		log_info("setChannel - ERROR - Invalid channel Identifier")
		return
	}
	if (Type == "Analog") {
		// analog channel... Air = 2 - 69, cable = 1 - 135
		try {
			setID = ID.toInteger()
		} catch(e) {
			log_info("setChannel - ERROR - Invalid channel Identifier for Analog channels")
			return
		}
		if (Source == "Air") {
			if ((setID < 2) || (setID > 69)) {
				log_info("setChannel - ERROR - Invalid channel Identifier for Analog Air channels")
				return
			}
		} else {
			if ((setID < 1) || (setID > 135)) {
				log_info("setChannel - ERROR - Invalid channel Identifier for Analog Cable channels")
				return
			}
		}
		request("DCCH",setID.toString())
	} else {
		if (Source == "Air") {
			if (ID.length() == 4) {
				def chanx = ID.substring(0,1)+"."+ID.substring(2,3)
				ID = chanx
			}
			if (ID.contains(".")) {
				def chan = ID.tokenize(".")
				def chanMajor = chan[0].toInteger()
				def chanMinor = chan[1].toInteger()
				if ((chanMajor < 0) || (chanMajor > 99) || (chanMinor < 0) || (chanMinor > 99)) {
					log_info("setChannel - ERROR - Invalid channel Identifier for Digital Air channels")
					return
				}
				setID = ("00"+chanMajor.toString()).substring(-2) + ("00"+chanMinor.toString()).substring(-2)
			}
			request("DA2P",setID)
		} else {
			// Digital cable channels
			if (ID.contains(".")) {
				// two part channel numbers
				def chan = ID.tokenize(".")
				def chanMajor = chan[0].toInteger()
				def chanMinor = chan[1].toInteger()
				if ((chanMajor < 0) || (chanMajor > 999) || (chanMinor < 0) || (chanMinor > 999)) {
					log_info("setChannel - ERROR - Invalid channel Identifier for Digital Cable channels")
					return
				}
				setID = ("000" + chanMajor.toString()).reverse().take(3).reverse() + "." + ("000" + chanMinor.toString()).reverse().take(3).reverse()
				request("DC2U",("000" + chanMajor.toString()).reverse().take(3).reverse())
				request("DC2L",("000" + chanMinor.toString()).reverse().take(3).reverse())
			} else {
				// one part channel numbers
				setID = ID.toInteger()
				if (setID > 99999) {
					log_info("setChannel - ERROR - Invalid channel Identifier for Digital Cable channels")
					return
				}
				if (setID < 10000) {
					request("DC10",setID.toString())
					request("DC11","0")
				} else {
					request("DC10",(setID.toString()).reverse().take(4).reverse())
					request("DC11",(setID.toString()).take(1))
				}
			}
		}
	}
	if (setID == null) {
		log_info("setChannel - ERROR Could not determine channels to set for [${Type}][${Source}][${ID}]")
		return
	}
	log_info("setChannel - Setting [${Type}][${Source}] Channel [${setID}]")
	getChannelStatus()
}


// support functions

def testParse(String handler, String Response) {
	log_debug("testParse - Handler '${handler}' [${Response}]")
	currentHandler = handler
	parse(Response)
}

def testCommand(String CMD, String PARAMS) {
	log_debug("rawCommand - CMD [${CMD}] PARAMS [${PARAMS}]")
	request(CMD,PARAMS)

}

def getMutex(Integer PId = null) {
	log_info("getMutex ${PId} - Acquire mutex request [${mutex.availablePermits()}]")
	def MaxMutexLock = 2
	def PID = (PId == null) ? "?"+(SequenceNumber-1).toString()+"?" : PId
	if (!mutex.tryAcquire(MaxMutexLock, SECONDS)) {
		log_info("getMutex ${PID} - Failed to acquire mutex permit [${mutex.availablePermits()}]")
		MutexErrors = MutexErrors + 1
		if (MutexErrors > 4) {
			// this many mutex errors mean the connection has stalled
			log_info("getMutex ${PId} - Connection STALLED??? [${mutex.availablePermits()}]")
			//runIn(1,"Init")
		}
		return false
	}
	log_info("getMutex ${PId} - Acquired mutex permit [${mutex.availablePermits()}]")
	return true
}

def releaseMutex(PId = null) {
	def PID = (PId == null) ? "?"+(SequenceNumber-2).toString()+"?" : PId
	log_info("releaseMutex ${PID} - Release mutex request [${mutex.availablePermits()}]")
	if (mutex.availablePermits() == 0) {
		mutex.release()
	} else {
		if (mutex.availablePermits() > 0) {
			mutex.reducePermits(Math.abs(1 - mutex.availablePermits()))
		}
		log_info("releaseMutex ${PID} - ERROR - Permit release not required [${mutex.availablePermits()}]")
	}
	log_info("releaseMutex ${PID} - Released mutex permit [${mutex.availablePermits()}]")
}

def flushMutex() {
	log_warn("FLUSHMUTEX - flushing mutex queue. Queued [${mutex.hasQueuedThreads()}]  Waiting [${mutex.getQueueLength()}] permits [${mutex.availablePermits()}]")
	// prevent command send / response parsing
	mutex.release(1000)
	def LMC = mutex.availablePermits()
	pauseExecution(100)
	while (LMC != mutex.availablePermits()) { 
		if ( mutex.availablePermits() < 2 ) { break }
		LMC = mutex.availablePermits()
		pauseExecution(100)
	}
	pauseExecution(1000)
	mutex.reducePermits(Math.abs(1 - mutex.availablePermits()))
	MutexErrors = 0
	log_warn("FLUSHMUTEX - FLUSHED - Queued [${mutex.hasQueuedThreads()}]  Waiting [${mutex.getQueueLength()}] permits [${mutex.availablePermits()}]")
}

def request(String CMD, String Params) {
	// block the thread if the mutex is locked
	if (InitMode == true) {
		log_error("request - ERROR. Request [$CMD] [$Params] dropped. Plugin initializing.")
		return false
	}
	def PID = SequenceNumber++
	if (getMutex(PID) == false) {
		log_debug("request ${PID} - Socket [${state.socketState}]  CMD [${CMD}]  Params [${Params}]")
		log_error("request ${PID} - ERROR. Unable to acquire mutex permit.[${mutex.availablePermits()}]")
		if (MutexErrors > 4) {
			return "abort"
		}
		return false
	} else if (mutex.availablePermits() > 500) {
		log_debug("request ${PID} - Socket [${state.socketState}]  CMD [${CMD}]  Params [${Params}]")
		log_error("request ${PID} - ERROR. Thread aborted by mutex flush. [${mutex.availablePermits()}]")
		return "abort"
	}
	if (InitMode == true) {
		log_error("request - ERROR. Request ${PID} [$CMD] [$Params] dropped. Plugin initializing.")
		return "abort"
	}
	log_info("request ${PID} - Socket [${state.socketState}]  CMD [${CMD}]  Params [${Params}]")
	log_info("request ${PID} - IOBUFFER [\r${IOBUFFER}\r]")

	if (destIp == null) {
		log_error("request ${PID} - ERROR. TV IP Address not set")
		// release the mutex on error
		releaseMutex(PID)
		return false
	}
	if (destPort == null) {
		log_error("request ${PID} - ERROR. TV IP Port not set")
		// release the mutex on error
		releaseMutex(PID)
		return false
	}
	def Credentials = ""
	if (login != null) {
		if ((password == null) || (password == "")) {
			log_error("request ${PID} - ERROR. TV login username is set, so login password must also be set")
			// release the mutex on error
			releaseMutex(PID)
			return false
		}
		Credentials = login + "\r" + password + "\r"
	}

	CMD = CMD + "    "
	Params = Params + "    "
	def Command = Credentials + CMD.take(4) + Params.take(4)

	if (!sendSocketCommand(Command)) {
		log_error("request - ERROR - send to device failed")
		// release the mutex on error
		releaseMutex(PID)
		return false
	}
	return true
}

def sendSocketCommand(Command) {
	try {
		log_debug("sendSocketCommand - sending [${Command}]")
		parseMutex.acquire()
		def msg = new hubitat.device.HubAction(Command, hubitat.device.Protocol.TELNET)
		sendHubCommand(msg)
		IOBUFFER.add(Command)
		parseMutex.release()
		return true
	} catch(e) {
		log_error("sendSocketCommand - ERROR - Caught exception '${e}'")
		return false
	}
	return true
}

def closeSocket() {
	log_debug("closeSocket: Socket close requested.")
	state.socketState = "closing"
//		interfaces.rawSocket.close()
//		hubitat.helper.InterfaceUtils.socketClose(device)
	telnetClose()
	state.socketState = "closed"
	pauseExecution(100)
	flushMutex()
	parseMutex.release()
	parseMutex.release()
	parseMutex.release()
	parseMutex.reducePermits(Math.abs(1 - parseMutex.availablePermits()))
	log_debug("closeSocket: Socket closed. [${mutex.availablePermits()}]  [${parseMutex.availablePermits()}]")
	return true
}

def openSocket() {
	if (state.socketState == "open") {
		log_debug("openSocket: Socket already opened.")
		return true
	}
	log_debug("openSocket: Connecting to ${destIp}:${destPort}")
	try {
		telnetConnect([termChars:[13]], destIp, destPort.toInteger(), null, null)
		pauseExecution(1000)
		state.socketState = "open"
		log_debug("openSocket: Socket opened.")
		parseMutex.acquire()
		IOBUFFER = []
		parseMutex.release()
		SocketErrors = 0
		return true
	}
	catch(e) {
		log_error("openSocket: exception = [${e}]")
		state.socketState == "closed"
		return false
	}
}

def telnetStatus(String message) {
	log.warn("telnetStatus - Socket [${state.socketState}]  Message [${message}]")
	if (state.socketState == "closed") { return }
	switch(message) {
		case "send error: Broken pipe (Write failed)":
			closeSocket()
			state.socketState = "closed"
			log_debug("telnetStatus - Write Failed - Attempting reconnect")
			pauseExecution(5000)
			return openSocket()
			break;
		case "receive error: Stream is closed":
		case "receive error: Stream closed.":
			SocketErrors = SocketErrors + 1
			if ((state.socketState != "closing") && (SocketErrors < 10)) {
				closeSocket()
				state.socketState = "closed"
				log_debug("telnetStatus - Stream Closed - Attempting reconnect [${SocketErrors}]")
				pauseExecution(5000)
				return openSocket()
			}
			state.socketState = "closed"
			if (SocketErrors > 9) {
				log_debug("telnetStatus - Stream Closed - Too many reconnects - execute initialize() to restart")
			}
			return
			break;
		case "send error: Socket closed":
			closeSocket()
			state.socketState = "closed"
			log_debug("telnetStatus - Socket Closed - Attempting reconnect")
			pauseExecution(5000)
			return openSocket()
			break;
	}
	log_debug("telnetStatus - UNHANDLED socket status [${message}]")
}

def send_Event(eData) {
	if (eventDebug) log.info("SharpAquos - EVENT - [${eData.toString()}]")
	if (eData.isStateChange == null) { eData.isStateChange = false }
	sendEvent(name: eData.name, value: eData.value, displayed:false, isStateChange: eData.isStateChange)
}

def log_debug(debugData) {
	if (logDebug) log.debug("SharpAquos - " + debugData)
}

def log_warn(debugData) {
	if (logDebug) log.warn("SharpAquos - " + debugData)
}

def log_info(debugData) {
	if (verboseDebug) log_debug("SharpAquos - " + debugData)
}

private log_error(debugData) {
	log.error("SharpAquos - " + debugData)
}

private String convertIPtoHex(ipAddress) {
	String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02X', it.toInteger() ) }.join()
	return hex
}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04X', port.toInteger() )
	return hexport
}