Denon AVR control?


#1

Does anyone have a device/app for some basic denon AVR controls? If not does anyone have any advice for what's going to be the easiest route for a non programming madman to bring over myself (assuming I can find a easy to modify working api or groovy script)?


#2

@mike.maxwell and I have been PMing about this since he has a working Onkyo driver. Once he has time he will explore making a Denon driver work via telnet.


#3

I don't think this works with HEOS receivers (I do not have a HEOS) but works fine with older receivers.
Not my work I just converted from SmartThings.

/**
 *  	Denon Network Receiver 
 *    	Based on Denon/Marantz receiver by Kristopher Kubicki
 *    	SmartThings driver to connect your Denon Network Receiver to SmartThings
 *		Tested with AVR-S710W (game1 & game2 inputs are not available), AVR 1912

TESED DENON MODELS
    ModelId
	EnModelUnknown,		//(0)
	EnModelAVRX10,		//(1)
	EnModelAVRX20,		//(2)
	EnModelAVRX30,		//(3)
	EnModelAVRX40,		//(4)
	EnModelAVRX50,		//(5)
	EnModelAVRX70,		//(6)
	EnModelNR15,		//(7)
	EnModelNR16,		//(8)
	EnModelSR50,		//(9)
	EnModelSR60,		//(10)
	EnModelSR70,		//(11)
	EnModelAV77,		//(12)
	EnModelAV88,		//(13)
*/

metadata {
    definition (name: "Denon AVR", namespace: "SB", 
        author: "Bobby Dobrescu") {
        capability "Actuator"
        capability "Switch" 
        capability "Polling"
        capability "Switch Level"
        capability "Music Player" 
        
        attribute "mute", "string"
        attribute "input", "string"     
        attribute "cbl", "string"
        attribute "tv", "string"
		attribute "dvd", "string"
		attribute "mp", "string"
		attribute "bt", "string"
		attribute "game", "string"
		attribute "sMovie", "string"        
		attribute "sMusic", "string"          
		attribute "sPure", "string"        
		attribute "q1", "string"
		attribute "q2", "string"
		attribute "q3", "string"        
		attribute "q4", "string"          
		attribute "zone2", "string" 



        command "mute"
        command "unmute"
        command "toggleMute"
        command "inputSelect", ["string"]
        command "inputNext"
		command "cbl"
		command "tv"
		command "bd"
		command "dvd"
		command "mp"
		command "bt"
		command "game"
		command "sMovie"
		command "sMusic"
		command "sPure"
		command "q1"
		command "q2"
		command "q3"
		command "q4"
		command "sPure" 
		command "z2on"        
		command "z2off"        

        }


preferences {
    input("destIp", "text", title: "IP", description: "The device IP")
    input("destPort", "number", title: "Port", description: "The port you wish to connect", defaultValue: 80)
	input(title: "Denon AVR version: ${getVersionTxt()}" ,description: null, type : "paragraph")
}


    simulator {
        // TODO-: define status and reply messages here
    }

    //tiles {
	tiles(scale: 2) {
		multiAttributeTile(name:"multiAVR", type: "mediaPlayer", width: 6, height: 4) {
           tileAttribute("device.status", key: "PRIMARY_CONTROL") { 	            
            	attributeState ("paused", label: 'Paused', backgroundColor: "#53a7c0", defaultState: true)
				attributeState ("playing", label: 'Playing', backgroundColor: "#79b821")
        	}             
            tileAttribute("device.status", key: "MEDIA_STATUS") { 	            
            	attributeState "playing", label: '${name}', action:"switch.off"
                attributeState "paused", label: '${name}', action:"switch.on"
			}  
            tileAttribute ("device.level", key: "SLIDER_CONTROL") {
           		attributeState ("level", action:"setLevel")
                }       
            tileAttribute ("device.mute", key: "MEDIA_MUTED") {
            	attributeState("unmuted", action:"mute", nextState: "muted")
            	attributeState("muted", action:"unmute", nextState: "unmuted")
        	}
        }        
        standardTile("input1", "device.cbl", width: 2, height: 2, decoration: "flat"){     
            state "OFF", label: 'Cable', action: "cbl", icon:"st.Electronics.electronics3", backgroundColor: "#FFFFFF", nextState:"ON"
            state "ON", label: 'Cable', action: "cbl", icon:"st.Electronics.electronics3" , backgroundColor: "#53a7c0", nextState:"OFF"        
            }
        standardTile("input2", "device.tv", width: 2, height: 2, decoration: "flat"){
        	 state "OFF", label: 'TV', action: "tv", icon:"st.Electronics.electronics18", backgroundColor:"#FFFFFF",nextState:"ON" 
             state "ON", label: 'TV', action: "tv", icon:"st.Electronics.electronics18", backgroundColor: "#53a7c0", nextState:"OFF"             
            }
        standardTile("input3", "device.bd", width: 2, height: 2, decoration: "flat"){
        	state "OFF", label: 'Blu-ray', action: "bd", icon:"st.Electronics.electronics8", backgroundColor: "#FFFFFF",nextState:"ON"  
            state "ON", label: 'Blu-ray', action: "bd", icon:"st.Electronics.electronics8", backgroundColor: "#53a7c0", nextState:"OFF"              
        	}
        standardTile("input4", "device.dvd", width: 2, height: 2, decoration: "flat"){
        	state "OFF", label: 'Chromcast', action: "dvd", icon:"st.Electronics.electronics14", backgroundColor: "#FFFFFF",nextState:"ON"   
            state "ON", label: 'Chromcast', action: "dvd", icon:"st.Electronics.electronics14", backgroundColor: "#53a7c0", nextState:"OFF"               
        	}
		standardTile("input5", "device.mp", width: 2, height: 2, decoration: "flat"){
        	state "OFF", label: 'Amazon TV', action: "mp", icon:"st.Electronics.electronics9", backgroundColor: "#FFFFFF",nextState:"ON"   
            state "ON", label: 'Amazon TV', action: "mp", icon:"st.Electronics.electronics9", backgroundColor: "#53a7c0", nextState:"OFF"              
			}
        standardTile("input6", "device.bt", width: 2, height: 2, decoration: "flat"){
        	state "OFF", label: 'Bluetooth', action: "bt", icon:"st.Entertainment.entertainment2", backgroundColor: "#FFFFFF",nextState:"ON"   
            state "ON", label: 'Bluetooth', action: "bt", icon:"st.Entertainment.entertainment2", backgroundColor: "#53a7c0", nextState:"OFF"             
			}
        standardTile("input7", "device.game", width: 2, height: 2, decoration: "flat"){
        	state "OFF", label: 'Game', action: "game", icon:"st.Electronics.electronics5", backgroundColor: "#FFFFFF",nextState:"ON"   
            state "ON", label: 'Game', action: "game", icon:"st.Electronics.electronics5", backgroundColor: "#53a7c0", nextState:"OFF"   
			}               
		standardTile("input10", "device.sound", width: 4, height: 2, decoration: "flat"){
        	state "sMusic", label: '${currentValue}', action:"sMusic", icon:"st.Entertainment.entertainment3", backgroundColor: "#FFFFFF", nextState:"sMovie"
			state "sMovie", label: '${currentValue}', action:"sMovie", icon:"st.Entertainment.entertainment9", backgroundColor: "#FFFFFF", nextState:"sGame"
			state "sGame", label: '${currentValue}', action:"sGame", icon:"st.Electronics.electronics6", backgroundColor: "#FFFFFF", nextState:"sPure"
			state "sPure", label: '${currentValue}', action:"sPure", icon:"st.Entertainment.entertainment15", backgroundColor: "#FFFFFF", nextState:"sMusic"
            }
        standardTile("input11", "device.q1", width: 1, height: 1, decoration: "flat"){
        	state "OFF", label: 'Quick 1', action: "q1",  backgroundColor: "#53a7c0",nextState:"ON"   // icon:"st.Electronics.electronics5",
            state "ON", label: 'Quick 1', action: "q1", backgroundColor: "#79b821", nextState:"OFF"  //, icon:"st.Electronics.electronics5" 
			}         
        standardTile("input12", "device.q2", width: 1, height: 1, decoration: "flat"){
        	state "OFF", label: 'Quick 2', action: "q2", backgroundColor:"#53a7c0" ,nextState:"ON"   //, icon:"st.Electronics.electronics5"
            state "ON", label: 'Quick 2', action: "q2", backgroundColor: "#79b821" , nextState:"OFF"   
			}         
        standardTile("input13", "device.q3", width: 1, height: 1, decoration: "flat"){
        	state "OFF", label: 'Quick 3', action: "q3", backgroundColor: "#53a7c0",nextState:"ON"   
            state "ON", label: 'Quick 3', action: "q3", backgroundColor: "#79b821", nextState:"OFF"   
			}         
        standardTile("input14", "device.q4", width: 1, height: 1, decoration: "flat"){
        	state "OFF", label: 'Quick 4', action: "q4", backgroundColor: "#53a7c0",nextState:"ON"   
            state "ON", label: 'Quick 4', action: "q4", backgroundColor: "#79b821", nextState:"OFF"   
			}
	standardTile("zone2", "device.zone2", width: 1, height: 1, inactiveLabel: false, decoration: "flat") {
			state "OFF", label:"Zone 2", action:"z2on", backgroundColor:"#53a7c0", nextState:"on"
			state "ON", label:"Zone 2", action:"z2off", backgroundColor:"#79b821", nextState:"off"
        	}            
		standardTile("poll", "device.poll", width: 1, height: 1, decoration: "flat") {
            state "poll", label: "", action: "polling.poll", icon: "st.secondary.refresh", backgroundColor: "#FFFFFF"
        }
main "multiAVR"
        details(["multiAVR", "input1", "input2", "input3","input4", "input5", "input6","input7", "input10", "input11", "input12","input13", "input14", "zone2", "poll"])
    }
}
def parse(String description) {
	//log.debug "Parsing '${description}'"
 	def map = stringToMap(description)
    if(!map.body || map.body == "DQo=") { return }
	def body = new String(map.body.decodeBase64())
	def statusrsp = new XmlSlurper().parseText(body)
	//POWER STATUS	
    def power = statusrsp.Power.value.text()
	if(power == "ON") { 
    	sendEvent(name: "status", value: 'playing')
    }
    if(power != "" && power != "ON") {  
    	sendEvent(name: "status", value: 'paused')
	}
	//VOLUME STATUS    
    def muteLevel = statusrsp.Mute.value.text()
    if(muteLevel == "on") { 
    	sendEvent(name: "mute", value: 'muted')
	}
    if(muteLevel != "" && muteLevel != "on") {
	    sendEvent(name: "mute", value: 'unmuted')
    }
    if(statusrsp.MasterVolume.value.text()) { 
    	def int volLevel = (int) statusrsp.MasterVolume.value.toFloat() ?: -40.0
        volLevel = (volLevel + 80)
        	log.debug "Adjusted volume is ${volLevel}"
        def int curLevel = 36
        try {
        	curLevel = device.currentValue("level")
        	log.debug "Current volume is ${curLevel}"
        } catch(NumberFormatException nfe) { 
        	curLevel = 36
        }
        if(curLevel != volLevel) {
    		sendEvent(name: "level", value: volLevel)
        }
    } 
	//INPUT STATUS
	def inputCanonical = statusrsp.InputFuncSelect.value.text()
            sendEvent(name: "input", value: inputCanonical)
	        log.debug "Current Input is: ${inputCanonical}"
    
    def inputSurr = statusrsp.selectSurround.value.text()
    		sendEvent(name: "sound", value: inputSurr)
	        log.debug "Current Surround is: ${inputSurr}"  
    def inputZone = statusrsp.RenameZone.value.text()
    		//sendEvent(name: "sound", value: inputSurr)
	        log.debug "Current Active Zone is: ${inputZone}"                      
}
    //TILE ACTIONS
    def setLevel(val) {
        sendEvent(name: "mute", value: "unmuted")     
        sendEvent(name: "level", value: val)
        def int scaledVal = val - 80
        request("cmd0=PutMasterVolumeSet%2F$scaledVal")
    }
    def on() {
        sendEvent(name: "status", value: 'playing')
        request('cmd0=PutZone_OnOff%2FON')
    }
    def off() { 
        sendEvent(name: "status", value: 'paused')
        request('cmd0=PutZone_OnOff%2FOFF')
    }
    def z2on() {
        log.debug "Turning on Zone 2"
        sendEvent(name: "zone2", value: "ON")
        request2('cmd0=PutZone_OnOff%2FON')
        }
    def z2off() {
        log.debug "Turning off Zone 2"
        sendEvent(name: "zone2", value: "OFF")
        request2('cmd0=PutZone_OnOff%2FOFF')
        }
    def mute() { 
        sendEvent(name: "mute", value: "muted")
        request('cmd0=PutVolumeMute%2FON')
    }
    def unmute() { 
        sendEvent(name: "mute", value: "unmuted")
        request('cmd0=PutVolumeMute%2FOFF')
    }
    def toggleMute(){
        if(device.currentValue("mute") == "muted") { unmute() }
        else { mute() }
    }
    def cbl() {
        def cmd = "SAT/CBL"
        log.debug "Setting input to ${cmd}"
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F" +cmd)
        }
    def tv() {
        def cmd = "TV"
        log.debug "Setting input to ${cmd}"
        syncTiles(cmd)   
        request("cmd0=PutZone_InputFunction%2F"+cmd)
        }
    def bd() {
        def cmd = "BD"
        log.debug "Setting input to ${cmd}"
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
        }
    def dvd() {
        def cmd = "DVD"
        log.debug "Setting input to ${cmd}"
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
        }
    def mp() {
        def cmd = "MPLAY"
        log.debug "Setting input to '${cmd}'"
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
        }
    def bt() {
        def cmd = "BT"
        log.debug "Setting input to '${cmd}'"
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
    }
    def game() {
        def cmd = "GAME"
        log.debug "Setting input to '${cmd}'" 
        syncTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
    }
    //SOUND MODES
    def sMusic() {
        def cmd = "MUSIC"
        log.debug "Setting input to '${cmd}'"
        request("cmd0=PutSurroundMode%2F"+cmd)
    }
    def sMovie() { 
        def cmd = "MOVIE"
        log.debug "Setting input to '${cmd}'"
        request("cmd0=PutSurroundMode%2F"+cmd)
    }
    def sGame() {
        def cmd = "GAME"
        log.debug "Setting input to '${cmd}'"
        request("cmd0=PutSurroundMode%2F"+cmd)
    }
    def sPure() {
        def cmd = "PURE DIRECT"
        log.debug "Setting input to '${cmd}'"
        request("cmd0=PutZone_InputFunction%2F"+cmd)
    }
    //QUICK MODES
    def q1() {
        def cmd = "1"
        log.debug "Setting quick input to '${cmd}'"
        syncQTiles(cmd)
        request("cmd0=PutUserMode%2FQuick%2F"+cmd)
    }
    def q2() { 
        def cmd = "2"
        log.debug "Setting quick input to '${cmd}'"
        syncQTiles(cmd)
        request("cmd0=PutSurroundMode%2F"+cmd)
    }
    def q3() {
        def cmd = "3"
        log.debug "Setting quick input to '${cmd}'"
        syncQTiles(cmd)
        request("cmd0=PutSurroundMode%2F"+cmd)
    }
    def q4() {
        def cmd = "4"
        log.debug "Setting quick input to '${cmd}'"
        syncQTiles(cmd)
        request("cmd0=PutZone_InputFunction%2F"+cmd)
    }    
    def poll() { 
        //log.debug "Polling requested"
        refresh()
    }
    def syncTiles(cmd){
        if (cmd == "SAT/CBL") sendEvent(name: "cbl", value: "ON")	 
            else sendEvent(name: "cbl", value: "OFF")						
        if (cmd == "TV") sendEvent(name: "tv", value: "ON")	 
            else sendEvent(name: "tv", value: "OFF")						
        if (cmd == "BD") sendEvent(name: "bd", value: "ON")	 
            else sendEvent(name: "bd", value: "OFF")						
        if (cmd == "DVD") sendEvent(name: "dvd", value: "ON")	 
            else sendEvent(name: "dvd", value: "OFF")						
        if (cmd == "MPLAY") sendEvent(name: "mp", value: "ON")	 
            else sendEvent(name: "mp", value: "OFF")						
        if (cmd == "BT") sendEvent(name: "bt", value: "ON")	 
            else sendEvent(name: "bt", value: "OFF")						
        if (cmd == "GAME") sendEvent(name: "game", value: "ON")	 
            else sendEvent(name: "game", value: "OFF")
    }
    def syncQTiles(cmd){
        if (cmd == "1") sendEvent(name: "q1", value: "ON")	 
            else sendEvent(name: "q1", value: "OFF")						
        if (cmd == "2") sendEvent(name: "q2", value: "ON")	 
            else sendEvent(name: "q2", value: "OFF")						
        if (cmd == "3") sendEvent(name: "q3", value: "ON")	 
            else sendEvent(name: "q3", value: "OFF")						
        if (cmd == "4") sendEvent(name: "q4", value: "ON")	 
            else sendEvent(name: "q4", value: "OFF")						
    }
	def refresh() {
        def hosthex = convertIPtoHex(destIp)
        def porthex = convertPortToHex(destPort)
        device.deviceNetworkId = "$hosthex:$porthex" 

        def hubAction = new hubitat.device.HubAction(
                'method': 'GET',
                'path': "/goform/formMainZone_MainZoneXml.xml",
                'headers': [ HOST: "$destIp:$destPort" ] 
            )   
        hubAction
    }
    def request(body) { 
        def hosthex = convertIPtoHex(destIp)
        def porthex = convertPortToHex(destPort)
        device.deviceNetworkId = "$hosthex:$porthex" 

        def hubAction = new hubitat.device.HubAction(
                'method': 'POST',
                'path': "/MainZone/index.put.asp",
                'body': body,
                'headers': [ HOST: "$destIp:$destPort" ]
            ) 

        hubAction
    }
    def request2(body) { 
        def hosthex = convertIPtoHex(destIp)
        def porthex = convertPortToHex(destPort)
        device.deviceNetworkId = "$hosthex:$porthex" 

        def hubAction = new hubitat.device.HubAction(
                'method': 'POST',
                'path': "/Zone2/index.put.asp",
                'body': body,
                'headers': [ HOST: "$destIp:$destPort" ]
            ) 

        hubAction
    }
    
    
    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
    }
    def getVersionTxt(){
        return "2.1"
    }

#4

@NoWon - Thanks! quick install and test on an AVR-X4000 and it's doing what I need, set volume, on/off and the inputs I use.

And this was the script I was looking at tackling!! Cheers.

Edit - Also seems to work for an AVRS710W, tho pretty slow to respond.... Not sure if this HEOS or not but it's always been a PITA and I hate it! But this may be where a telnet variant would be more reliable. Denon... what a disappointment they've become!

Unsurprisingly the Denon AVR-X4300 does NOT work with this. I believe it's a HEOS


#5

If I can be of any help testing @mike.maxwell or @ritchierich give me a shout. I'm not a programmer, but I do tinker,... to what effect I'm not always sure, but if that's anything but scary to y'all lmk.


#6

I notice that Denon is now officially supported as of version 2.x. But I don't see any instructions or documentation anywhere on how to actually configure it. Does anyone know where to find the docs for the new driver?


#7

Pretty simple, create a virtual device using the Denon Driver. Within the driver specify the IP Address of your receiver. Make sure you have your receiver setup to accept network control. Good to go.


#8

Works great for Denon 3400. One comment/question; is there a way to get the actual driver code? I would also like to control my second zone. I can modify the code, I just don’t want to reinvent the wheel.


#9

IS that the Heos enabled 3400?


#10

Yes; it is HEOS enabled


#11

Great! I have a 4300h and thought they had killed network control in favor of the Heos app. I will have to check this out.


#12

I just tried the built in DH for Denon AVR. It does work with the Denon 4300h which is a Heos based AVR.

One trick I found: at first, it appeared to not support changing inputs. However, you have to give them a name and then it will work.

Change the input at the AVR, the DH will pick up the change and display it on the top right.

You then use the driver to assign a name to that input. After that using the input number in the DH will work to change the input on the Denon.

So switch to each input, then use the top left box to assign a name, and it will work after that.


#13

then after verifying on, off work.
Go to the AVR, or using the remote, switch to all the inputs you would like to use.
These will then be added one after the other to the driver.
Once you have all the inputs you need, they can be named with an alias of your choice by entering the desired alias into the name box, then clicking edit current input name.


#14

aah, so it wasn't the name, it was selecting them locally that fixed it. The name helps and a great feature, I never remember what input number I've assign to what device in the Denon setup menu. :stuck_out_tongue_winking_eye:


#15

This was an intentional design decision given the huge number of different possible input names across the entire device line.


#16

Any chance there could be support for "Zones" I love having the built in driver but it can't control different zones. From working on a driver for ST I think a very easy way to do this would be to allow a 2nd device to be created which represents zone 2, 3. Then you could control that zone just like the main zone.


#17

Doesn't seem to work with Denon-AVR-S740H. I'm not getting any events...occasionally I'm getting a telnet error 404 page not found error in the logs. I'll check to see if any of the ports are closed on my router...but right now looks like it won't work for me.


#18

Denon devices only support one connection, so if any other app is using it, it won't work.


#19

Good thought...but unless my neighbour is using my AVR no other apps.
I confirmed I can bring up the web app by force browsing to my AVR IP address at port: 10080.

Found this article which may lead to more investigation on my part:


#20

It's great being able to control my receiver via HE, thanks all! I'm greedy, any suggestions at controlling it via alexa? That's my ultimate goal. All I can currently do is turn it on/off.