Newb's implementation for adding control of Yamaha receiver to Hubitat (with screen shots)

I spent several days trying to figure out how to add control of my Yamaha receiver to Hubitat. There are guides out there that assume a lot of knowledge that I didn't have as a NEWB. So I will try to give step by step directions of how I added and made use of a Yamaha receiver driver to my Hubitat. Experts are more than welcome to constructively critique my methods and correct/enhance upon the information I present.

  1. I found 2 Yamaha .groovy files in GitHub.
    a) Driver for Yamaha receiver: Yamaha-NVR-Hubitat/YamahaNetworkReceiver.groovy at main · CreatureOfHubitat/Yamaha-NVR-Hubitat · GitHub
    b) Driver for Yamaha zone: Yamaha-NVR-Hubitat/YamahaZonesDriver.groovy at main · CreatureOfHubitat/Yamaha-NVR-Hubitat · GitHub

Neither of these two .groovy drivers allowed this newb to do anything with my receiver. I ended up having to combine the two drivers then delete the duplicated functions. This is my merge of the Yamaha Receiver .groovy and the Yamaha Zone .groovy files that I use in this thread.

/**
 *  Hubitat Driver: Yamaha Network Receiver
 *
 *  Author: Ben Rimmasch
 *  Derived from redloro@gmail.com's ST work for Yamaha Receivers
 *
 *  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.
 *
 *  https://github.com/PSeitz/yamaha-nodejs
 *  http://<RECEIVER_IP_ADDRESS>/YamahaRemoteControl/desc.xml
 */
import groovy.util.XmlSlurper

metadata {
  definition(name: "Yamaha Receiver", namespace: "codahq-hubitat", author: "Ben Rimmasch") {

  }

  preferences {
    section("Yamaha Receiver") {
      //input name: "receiverName", type: "text", title: "Name", required: true, defaultValue: "Yamaha"
      input name: "receiverIp", type: "text", title: "IP", required: true
      input name: "receiverZones", type: "enum", title: "Zones", required: true, multiple: true, options: ["Main_Zone", "Zone_B", "Zone_2", "Zone_3", "Zone_4"]
      input name: "descriptionTextEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: false
      input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false
      input name: "traceLogEnable", type: "bool", title: "Enable trace logging", defaultValue: false
    }
  }
}

private logInfo(msg) {
  if (descriptionTextEnable) log.info msg
}

def logDebug(msg) {
  if (logEnable) log.debug msg
}

def logTrace(msg) {
  if (traceLogEnable) log.trace msg
}

def updated() {
  updateDNI()
  //removeChildDevices()
  addChildDevices()
}

def uninstalled() {
  removeChildDevices()
}

def parse(String description) {
  def map = parseLanMessage(description)

  def body = getHttpBody(map.body);
  logTrace "Headers: ${map.headers}"
  logTrace "Body: ${body}"

  updateZoneDevices(body.children()[0])
}

private updateZoneDevices(evt) {
  logDebug "updateZoneDevices: ${evt.toString()}"
  if (evt.name() == "System") {
    logDebug "Update all zones"
    childDevices *.zone(evt)
    return
  }

  def zonedevice = getChildDevice(getDeviceId(evt.name()))
  if (zonedevice) {
    zonedevice.zone(evt)
  }

  //check for Zone_B
  zonedevice = getChildDevice(getDeviceId("Zone_B"))
  if (zonedevice && evt.name() == "Main_Zone") {
    zonedevice.zone(evt)
  }
}

private addChildDevices() {
  // add yamaha zone device(s)

  //temporary workaround to add Strings to lists
  def selectedZones = []
  if (settings.receiverZones instanceof java.lang.String) {
    selectedZones = [settings.receiverZones]
  }
  else {
    selectedZones = settings.receiverZones
  }
  selectedZones.each {
    def deviceId = getDeviceId(it)
    if (!getChildDevice(deviceId)) {
      addChildDevice("codahq-hubitat", "Yamaha Zone", deviceId, [name: "Yamaha Zone ${it}", label: "${device.name} - Zone ${it}", isComponent: false])
      logInfo "Added Yamaha zone: ${deviceId}"
    }
  }

  childDevices *.refresh()
}

private removeChildDevices() {
  getChildDevices().each {
    log.warn "deleting ${it}"
    deleteChildDevice(it.deviceNetworkId)
  }
}

private sendCommand(body) {
  logDebug "Yamaha Network Receiver send command: ${groovy.xml.XmlUtil.escapeXml(body)}"

  def hubAction = new hubitat.device.HubAction(
    headers: [HOST: getReceiverAddress()],
    method: "POST",
    path: "/YamahaRemoteControl/ctrl",
    body: body
  )
  sendHubCommand(hubAction)
}

private getHttpHeaders(headers) {
  def obj = [: ]
  new String(headers.decodeBase64()).split("\r\n").each { param ->
      def nameAndValue = param.split(":")
    obj[nameAndValue[0]] = (nameAndValue.length == 1) ? "" : nameAndValue[1].trim()
  }
  return obj
}

private getHttpBody(body) {
  def obj = null;
  if (body) {
    obj = new XmlSlurper().parseText(new String(body))
  }
  return obj
}

private getDeviceId(zone) {
  return "yamaha|${settings.receiverIp}|${zone}".toString()
}

private getReceiverAddress() {
  return settings.receiverIp + ":80"
}

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

private String convertPortToHex(port) {
  return port.toString().format('%04x', port.toInteger()).toUpperCase()
}

/*
private setDeviceNetworkId(ip, port = null){
    def myDNI
    if (port == null) {
        myDNI = ip
    } else {
  	    def iphex = convertIPtoHex(ip)
  	    def porthex = convertPortToHex(port)
        
        myDNI = "$iphex:$porthex"
    }
    log.debug "Device Network Id set to ${myDNI}"
    return myDNI
}
*/

private updateDNI() {
  //if (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni) {
  //   device.deviceNetworkId = state.dni
  //}
  def dni = convertIPtoHex(settings.receiverIp)
  if (dni != device.deviceNetworkId) {
    device.deviceNetworkId = dni
  }
}

private getHostAddress() {
  if (getDeviceDataByName("ip") && getDeviceDataByName("port")) {
    return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
  }
  else {
    return "${ip}:80"
  }
}

I went to the "Drivers section in my Hubitat and selected "New Driver":

I pasted the code (shown above) into the new driver text window:

After clicking "save" I then had the driver registered with hubitat:

I checked my modem/router/firewall DHCP leases and found the IP address of my Yamaha receiver. On "MY" router I can click on the MAC address and it will show me info on the device associated with the lease:


I should add static DHCP address leases to my router for all of these devices so they always come up on the same IP address but that is a different tutorial which is specific to your specific router.

Next I go into the Devices section on Hubitat and select "Add device":

I select "Virtual Device":

After searching for "Yama", selecting "Yamaha Receiver" and giving the device a name I click save:

Which takes me to the device config page. Note the name of the Volume Down button is "volumeDown", this will be used later when adding a button to a Dashboard.


I add the IP address of the Yamaha receiver and select "Main Zone" which is the only Yamaha receiver zone I have speakers connected to. Down at the bottom of the Device Config page I click "Save Device":

At this point I can open the device and control it with the buttons in the device config page.

After which the device appears in my list of devices:

Then I go into one of my Dashboards and add the Yamaha device:

Unfortunately I have not figured out how to create/configure buttons for virtual devices in a dashboard that will actually do anything yet. I will post more when I have had a chance to sit down and work through this. Or maybe someone can give me some direction here?

3 Likes

If the driver has buttons, entering the button names or numbers should work.

Thank you, the light went on (in my head)! I will update my original post as soon as I have a chance to test putting the button name into the dashboard widget. I couldn't figure out where to get the button number from but now I will try the button name instead.

1 Like

Hello, and thank you for this thread which I am attempting to follow.
I am unfortunately struggling with the code.
The zone code from github imports and produces an entry with capabilities but the receiver codefrom the github and from your merge both appear much shorter at 197 lines and neither allow any capabilities.

I think I must be doing something wrong.

Can you help me?

Thank you.

Mark

Does this work with all Yamaha receivers with network capability, or just certain years/models?

I too am having difficulty getting any capabilities to show up after installing the original or modified drivers.

@mark.rogers.oakdene did you ever get yours working?

I'm wondering if there is an error somewhere in this code.

I can't seem to find the code anywhere else, and see the original Yamaha driver code thread was withdrawn.

Sorry, I am having no luck with this at present.

I ended up finding the drivers posted by member @jeff.lilliquist at the end of this post Yamaha Receivers and MusicCast integration . I installed both of these drivers and set my vitual yamaha receiver device to use the yamaha receiver driver. I entered my receiver's static ip in the device details page, saved, and was able to get all the commands to show up in the devices page under 'main zone' child device. However, i could only get 3 of the 'source' commands to work. The other source tabs do not change source on the receiver. Not sure why. It's a good start though.

I just use GET requests to the API via webCoRE to do things like toggle power and switch inputs. I created virtual switches that I attached to the Pistons so I can say "turn on Shield TV", and the receiver will turn on and switch the HDMI 1. I thought about writing something like this, but realized I really only care about automating power and source. Everything else can be done easily enough from the remote.