Author Topic: Control Denon AVR (plugin-free solution)  (Read 8508 times)

Offline cedricm

  • Sr. Newbie
  • *
  • Posts: 37
  • Karma: +1/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #15 on: February 05, 2014, 10:03:31 am »
FYI, the timeout specified in the first post was using the wrong unit (should be sec instead of msec): this should be "denon_tcp:settimeout(0.5)" -- fixed my first post.

On a related subject, I have improved upon this scene by mapping the Denon AVR to a standard Dimmer: dimming level is mapped to the AVR volume + 0% and OFF/ON buttons mapped to power OFF/ON.

The idea of using a standard Dimmer control is that no work is required to get it properly managed by third party remote apps: I can now control the ON/OFF state & volume directly from AutHomation.
And I can still write some scenes to switch the AVR to some known state (volume, favorite station, ...).
The "Denon Dimmer" will also regularly query the AVR to update it's state (useful if changed directly on the AVR or some other app) (and without keeping a connection open), and respond to the Poll commands to immediately update it's state.

Ok that sounds like the beginning of a new plugin, but my goal is not to make it a full featured control, as I don't use the Vera UI to control my devices.
If anybody is interested... contact me!

Offline mda

  • Sr. Member
  • ****
  • Posts: 464
  • Karma: +9/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #16 on: February 08, 2014, 01:25:07 am »
@cedricm ? very interested in the mapping to dimmer and on/off switch for each zone as well as periodic updating/polling, yes ! what do i need to do in order to set those up? Thanks!

Offline cedricm

  • Sr. Newbie
  • *
  • Posts: 37
  • Karma: +1/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #17 on: February 08, 2014, 10:58:34 am »
Ok, here it is:

- Save the following code as /etc/cmh-ludl/I_DenonDimmer.xml and set the appropriate IP address in this file.

- Manually create a new device (Apps / Develop Apps / Create device) with:
  . device type: urn:schemas-upnp-org:device:DimmableLight:1
  . device file: D_DimmableLight1.xml
  . implementation file: I_DenonDimmer.xml

- Reload the interface if needed, you should see the dimmer ;)

- To send commands from a scene, use the following luup code:
  luup.call_action("urn:micasaverde-com:serviceId:HaDevice1", "Poll", {command = "PWON MV10 ZMFAVORITE1"}, <device id>)

  (replace <device id> with the id of the dimmer you just created. You can send multiple commands in a single call by separting them with a space.

No need for my previous code, you can remove it as it's redundant.


If you want a Zone ON/OFF functionnality, you may use virtual switches devices (and call the appropriate luup code upon state change).


Let me know how it goes for you!


<?xml version="1.0"?>
<implementation>
  <functions>

    local socket = require "socket"
    local denon_tcp
    local denon_ip = "<ip address>"

    function logDenon(msg)
        luup.log("DENON: " .. msg)
    end

    function connectDenon()
        if denon_tcp then
            logDenon("Already connected")
            return false
        else
            logDenon("Connecting...")
            denon_tcp = assert(socket.tcp())
            denon_tcp:settimeout(0.5)
            local status, error = denon_tcp:connect(denon_ip, 23)
            if status then
                --logDenon("Connected")
                return true
            else
                denon_tcp = nil
                logDenon("Error connecting: " .. error)
                return false
            end
        end
    end

    function sendDenon(cmd)
        local buf = "", s, status, partial
        status = nil
        logDenon("Sending " .. cmd)

        denon_tcp:settimeout(0)
        buf, status, partial = denon_tcp:receive(9999)

        denon_tcp:settimeout(0.1)
        denon_tcp:send(cmd .. "\r")
        buf, status, partial = denon_tcp:receive(9999)
        if status then
            buf = partial
            if status ~= "timeout" then
                logDenon("Error receiving: " .. status .. ", partial: " .. partial)
            end
        end
        buf = string.gsub(buf, "\r", "")
        logDenon("Received " .. buf)
        sleepDenon()
        return buf
    end

    function disconnectDenon()
        --logDenon("Disconnecting...")
        denon_tcp:close()
        denon_tcp = nil
        logDenon("Disconnected")
    end

    function sleepDenon()
        luup.sleep(500)
    end

    function powerOnDenon()
        local pw_status = sendDenon("PW?")
        if pw_status == "PWSTANDBY" then
            sendDenon("PWON")
            sleepDenon()
        else
            logDenon("Not powering on, current status is " .. pw_status)
        end
    end

    function powerOffDenon()
        sendDenon("PWSTANDBY")
    end

    function initDenon(lul_device)
      if connectDenon() then
            updateDenonStatus(tonumber(lul_device))
            disconnectDenon()
        end
        luup.call_timer('initDenon', 1, "5m", "", lul_device)
    end

   function updateDenonStatus(lul_device)
        local pw_status = sendDenon("PW?")
       if pw_status == "PWSTANDBY" then
            luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "0", lul_device)
        elseif pw_status == "PWON" then
            luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "1", lul_device)
        end

        local mv_status = sendDenon("MV?")
        local vol_string = string.match(mv_status, "MV(%d*)")
        local vol = tonumber(vol_string)
        if vol then
            if string.len(vol_string) > 2 then
                vol = vol / 10
            end
            local vol_max = tonumber(string.match(mv_status, "MVMAX *(%d*)"))
            if vol_max then
                luup.variable_set("urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus", tostring(math.floor(vol*100/vol_max)), lul_device)
            end
        end

        luup.variable_set("urn:micasaverde-com:serviceId:HaDevice1", "LastUpdate", os.time())
   end
  </functions>
  <startup>initDenon</startup>
  <actionList>

    <action>
     <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
     <name>SetTarget</name>
      <run>
          if connectDenon() then
            if lul_settings.newTargetValue == "1" then
                powerOnDenon()
            else
                powerOffDenon()
            end
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
     <name>SetLoadLevelTarget</name>
      <run>
          if connectDenon() then
            local mv_status = sendDenon("MV?")
            local vol = tonumber(lul_settings.newLoadlevelTarget)
            if vol then
                if vol == 0 then
                    powerOffDenon()
                elseif vol == 100 then
                    powerOnDenon()
                else
                    local vol_max = tonumber(string.match(mv_status, "MVMAX *(%d*)"))
                    if vol_max then
                        local vol_percent = math.floor((vol * vol_max / 100) * 2) / 2
                        local vol_string = string.format("%02i", math.floor(vol_percent))
                        if (vol_percent - math.floor(vol_percent) > 0) then
                            vol_string = vol_string .. math.floor((vol_percent - math.floor(vol_percent)) * 10)
                        end
                        sendDenon("MV" .. vol_string)
                    end
                end
            end
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
     <name>Poll</name>
      <run>
          if connectDenon() then
              if lul_settings.command then
                  local cmd
                  for cmd in string.gmatch(lul_settings.command, "%S+") do
                      sendDenon(cmd)
                  end
              end
              updateDenonStatus(lul_device)
              disconnectDenon()
          end
      </run>
    </action>

   </actionList>
</implementation>

Offline mda

  • Sr. Member
  • ****
  • Posts: 464
  • Karma: +9/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #18 on: February 08, 2014, 04:18:45 pm »
thanks !

i set it up and did a test while listening to zone 2 with the main zone off. i dragged the volume slider (which i expected to do nothing as it controls the main zone) and it did indeed do nothing ;) i then clicked ON and here is what i got in the log:

Code: [Select]
50 02/08/14 13:14:39.844 luup_log:289: DENON: Connecting... <0x340ca680>
50 02/08/14 13:14:39.846 luup_log:289: DENON: Sending MV? <0x340ca680>
50 02/08/14 13:14:39.947 luup_log:289: DENON: Received MV43MVMAX 74 <0x340ca680>
50 02/08/14 13:14:40.449 luup_log:289: DENON: Sending MV295 <0x340ca680>
50 02/08/14 13:14:40.550 luup_log:289: DENON: Received MV295MVMAX 72 <0x340ca680>
50 02/08/14 13:14:41.051 luup_log:289: DENON: Sending PW? <0x340ca680>
50 02/08/14 13:14:41.152 luup_log:289: DENON: Received PWONZ2ON <0x340ca680>
50 02/08/14 13:14:41.653 luup_log:289: DENON: Sending MV? <0x340ca680>
50 02/08/14 13:14:41.754 luup_log:289: DENON: Received MV295MVMAX 72 <0x340ca680>
50 02/08/14 13:14:42.257 luup_log:289: DENON: Disconnected <0x340ca680>
50 02/08/14 13:15:35.219 luup_log:289: DENON: Connecting... <0x33cca680>
50 02/08/14 13:15:35.221 luup_log:289: DENON: Sending MV? <0x33cca680>
50 02/08/14 13:15:35.322 luup_log:289: DENON: Received MV295MVMAX 72 <0x33cca680>
50 02/08/14 13:15:35.823 luup_log:289: DENON: Sending PW? <0x33cca680>
50 02/08/14 13:15:35.924 luup_log:289: DENON: Received PWONZ2ON <0x33cca680>
50 02/08/14 13:15:36.425 luup_log:289: DENON: Not powering on, current status is PWONZ2ON <0x33cca680>
50 02/08/14 13:15:36.425 luup_log:289: DENON: Sending PW? <0x33cca680>
50 02/08/14 13:15:36.526 luup_log:289: DENON: Received PWONZ2ON <0x33cca680>
50 02/08/14 13:15:37.027 luup_log:289: DENON: Sending MV? <0x33cca680>
50 02/08/14 13:15:37.128 luup_log:289: DENON: Received MV295MVMAX 72 <0x33cca680>
50 02/08/14 13:15:37.641 luup_log:289: DENON: Disconnected <0x33cca680>
50 02/08/14 13:15:37.651 luup_log:289: DENON: Connecting... <0x32cca680>
50 02/08/14 13:15:37.653 luup_log:289: DENON: Error connecting: connection refused <0x32cca680>

i imagine i need to make a copy of the I_ file and tweak the commands for each of zone 2 and 3 then create a device with each of those tweaked I_ files to control the other zones, yes? I will give a try if that makes sense and let you know how it goes. Thanks.


EDIT: just saw this __LEAK__line in the log. Do i need to worry about that? Thanks.

Code: [Select]
50 02/08/14 13:17:25.100 luup_log:289: DENON: Connecting... <0x32647680>
50 02/08/14 13:17:25.102 luup_log:289: DENON: Sending PW? <0x32647680>
50 02/08/14 13:17:25.204 luup_log:289: DENON: Received PWONZ2ON <0x32647680>
50 02/08/14 13:17:25.705 luup_log:289: DENON: Sending MV? <0x32647680>
50 02/08/14 13:17:25.806 luup_log:289: DENON: Received MV295MVMAX 72 __LEAK__ this:4096 start:2281472 to 0x26b3000 <0x32647680>
50 02/08/14 13:17:26.309 luup_log:289: DENON: Disconnected <0x32647680>


EDIT: I clicked "off" and it turned all zones off, but "off" was not showing in UI5. I clicked "on" -- on/off status and volume is not reflected in UI5 (it says vol is 40 when main zone vol is actually 29.5). I think it is getting responses from the Denon it is not expecting. Perhaps it needs to look for substrings within the received response?

Code: [Select]
50 02/08/14 14:10:47.800 luup_log:289: DENON: Sending PWSTANDBY <0x33d76680>
50 02/08/14 14:10:48.831 luup_log:289: DENON: Received ZMOFFMNMEN OFFPWSTANDBY <0x33d76680>
50 02/08/14 14:10:49.333 luup_log:289: DENON: Sending PW? <0x33d76680>
50 02/08/14 14:10:49.434 luup_log:289: DENON: Received PWSTANDBYZ2OFF <0x33d76680>
50 02/08/14 14:10:49.935 luup_log:289: DENON: Sending MV? <0x33d76680>
50 02/08/14 14:10:50.036 luup_log:289: DENON: Received MV295MVMAX 72 <0x33d76680>
50 02/08/14 14:10:50.539 luup_log:289: DENON: Disconnected <0x33d76680>
50 02/08/14 14:11:36.636 luup_log:289: DENON: Connecting... <0x33f76680>
50 02/08/14 14:11:36.638 luup_log:289: DENON: Sending MV? <0x33f76680>
50 02/08/14 14:11:36.739 luup_log:289: DENON: Received MV295MVMAX 72 <0x33f76680>
50 02/08/14 14:11:37.240 luup_log:289: DENON: Sending PW? <0x33f76680>
50 02/08/14 14:11:37.341 luup_log:289: DENON: Received PWSTANDBYZ2OFF <0x33f76680>
50 02/08/14 14:11:37.842 luup_log:289: DENON: Not powering on, current status is PWSTANDBYZ2OFF <0x33f76680>
50 02/08/14 14:11:37.842 luup_log:289: DENON: Sending PW? <0x33f76680>
50 02/08/14 14:11:37.943 luup_log:289: DENON: Received PWSTANDBYZ2OFF <0x33f76680>
50 02/08/14 14:11:38.444 luup_log:289: DENON: Sending MV? <0x33f76680>
50 02/08/14 14:11:38.545 luup_log:289: DENON: Received MV295MVMAX 72 <0x33f76680>
50 02/08/14 14:11:39.049 luup_log:289: DENON: Disconnected <0x33f76680>

for example: i think it is looking for a response of PWSTANDBY to set the "off" button on the vera device but it gets "MNMEN OFFPWSTANDBYZ2OFFZ3OFF" so does not catch it. Similar situation with MV295 vs "MV295MVMAX 755"

Code: [Select]
50 02/08/14 14:22:58.690 luup_log:289: DENON: Sending PWSTANDBY <0x33976680>
50 02/08/14 14:22:58.791 luup_log:289: DENON: Received MNMEN OFFPWSTANDBYZ2OFFZ3OFF <0x33976680>
50 02/08/14 14:22:59.292 luup_log:289: DENON: Sending PW? <0x33976680>
50 02/08/14 14:22:59.393 luup_log:289: DENON: Received PWSTANDBYZ2OFF <0x33976680>
50 02/08/14 14:22:59.894 luup_log:289: DENON: Sending MV? <0x33976680>
50 02/08/14 14:22:59.996 luup_log:289: DENON: Received MV295MVMAX 755 <0x33976680>

EDIT: it also might not be computing the volume to send based on slider location correctly. i dragged the volume slider to about 50% (maybe less) and in UI5 the slider jumped down to the 3%. Looking at the log i think it sent an MV value the Denon did not understand and got no response:

Code: [Select]
50 02/08/14 14:19:21.473 luup_log:289: DENON: Sending MV3775 <0x33d76680>
50 02/08/14 14:19:21.574 luup_log:289: DENON: Received  <0x33d76680>
50 02/08/14 14:19:22.075 luup_log:289: DENON: Sending PW? <0x33d76680>
50 02/08/14 14:19:22.176 luup_log:289: DENON: Received PWONZ2ONZ3ON <0x33d76680>
50 02/08/14 14:19:22.677 luup_log:289: DENON: Sending MV? <0x33d76680>
50 02/08/14 14:19:22.778 luup_log:289: DENON: Received MV295MVMAX 755 <0x33d76680>
50 02/08/14 14:19:23.281 luup_log:289: DENON: Disconnected <0x33d76680>
« Last Edit: February 08, 2014, 05:30:33 pm by mda »

Offline cedricm

  • Sr. Newbie
  • *
  • Posts: 37
  • Karma: +1/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #19 on: February 12, 2014, 09:26:23 am »
Hi!

I didn't play yet with a second zone here, so I've no first hand experience on what is doable and best strategy.
However, I'm currently extending my house, and will add this second zone in April ;)
If a second zone can be managed exactly as it was a separate AVR, ie, independent ON/OFF/Volume (albeit with slightly different commands), I guess that your approach is right (building a 2nd instance of the dimmer device).

About the _LEAK_, as far as I know, there's nothing that can be in the LUUP code (which by itself is clean). This is most probably some kind of LUUP bug... but I would not worry about this.

Reading further your post: indeed if you get 'PWSTANDBYZ2OFF', this is not properly managed by my code (which is looking for the exact PWSTANDBY string). Some tweaks in the string comparison logic is indeed required. Nothing difficult, but some time is needed to properly implement & test this ;)

And if you have issues with the volume %, I guess that it's because of 'MVMAX 755'... here I'm have for a 'MVMAX 60'... I didn't expect a 3 digit max volume here ;)
So I guess you need to modify the code a bit to divide MVMAX by 10 if reading a 3 digit string (just like I did for the current volume) -- should be done in updateDenonStatus and SetLoadLevelTarget.


I'm sorry for not being able to implement all of this right now. Currently very busy. But I'll probably get back to this (and maybe implement a ZONE2) in April/May... if you want to wait! (no promises though)

Offline The-Source

  • Jr. Member
  • **
  • Posts: 74
  • Karma: +0/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #20 on: April 27, 2014, 11:03:00 am »
i tried the following as implementation for zone status but it errors in execution ;)

Code: [Select]
<?xml version="1.0"?>
<implementation>
  <functions>

    local socket = require "socket"
    local denon_tcp
    local denon_ip = "<ipadres>"

    function logDenon(msg)
        luup.log("DENON: " .. msg)
    end

    function connectDenon()
        if denon_tcp then
            logDenon("Already connected")
            return false
        else
            logDenon("Connecting...")
            denon_tcp = assert(socket.tcp())
            denon_tcp:settimeout(0.5)
            local status, error = denon_tcp:connect(denon_ip, 23)
            if status then
                --logDenon("Connected")
                return true
            else
                denon_tcp = nil
                logDenon("Error connecting: " .. error)
                return false
            end
        end
    end

    function sendDenon(cmd)
        local buf = "", s, status, partial
        status = nil
        logDenon("Sending " .. cmd)

        denon_tcp:settimeout(0)
        buf, status, partial = denon_tcp:receive(9999)

        denon_tcp:settimeout(0.1)
        denon_tcp:send(cmd .. "\r")
        buf, status, partial = denon_tcp:receive(9999)
        if status then
            buf = partial
            if status ~= "timeout" then
                logDenon("Error receiving: " .. status .. ", partial: " .. partial)
            end
        end
        buf = string.gsub(buf, "\r", "")
        logDenon("Received " .. buf)
        sleepDenon()
        return buf
    end

    function disconnectDenon()
        --logDenon("Disconnecting...")
        denon_tcp:close()
        denon_tcp = nil
        logDenon("Disconnected")
    end

    function sleepDenon()
        luup.sleep(500)
    end

    function powerOnDenon()
        local z2status = sendDenon("Z2?") -- AVR3808 give a response like "Z2NET/USBZ251Z2OFFSVSOURCE"
z2on = string.find(z2status, "Z2ON")
        z2off = string.find(z2status, "Z2OFF")

        if ((z2on < 1) and (z2off > 1)) then -- z2off is read from z2status
           sendDenon("Z2ON")
           sleepDenon()
        elseif ((z2on > 1) and (z2off < 0)) then
            logDenon("Not powering on, already on)
else
   logDenon("Failed to read status, current readout is " .. z2status)
        end
    end

    function powerOffDenon()
        sendDenon("Z2OFF")
    end

    function initDenon(lul_device)
      if connectDenon() then
            updateDenonStatus(tonumber(lul_device))
            disconnectDenon()
        end
        luup.call_timer('initDenon', 1, "5m", "", lul_device)
    end

   function updateDenonStatus(lul_device)
        local z2status = sendDenon("Z2?") -- AVR3808 give a response like "Z2NET/USBZ251Z2OFFSVSOURCE"
z2on = string.find(z2status, "Z2ON")
        z2off = string.find(z2status, "Z2OFF")
local z2volume =40 -- setting a save volume
local z2volPos = 2

        if ((z2on < 1) and (z2off > 1)) then -- z2off is read from z2status
            luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "1", lul_device)
z2volPos = z2off - 2
        elseif ((z2on > 1) and (z2off < 0)) then
            luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "0", lul_device)
z2volPos = z2on - 2
        end

        local vol_string = string.sub (z2status, z2volPos, z2volPos+2)
        local vol = tonumber(vol_string)
        if vol then
            if string.len(vol_string) > 2 then
                vol = vol / 10
            end
        --    local vol_max = tonumber(string.match(mv_status, "MVMAX *(%d*)"))  -- skipping this part for now
            if vol > 0 then
                luup.variable_set("urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus", vol, lul_device)
            end
        end

        luup.variable_set("urn:micasaverde-com:serviceId:HaDevice1", "LastUpdate", os.time())
   end
  </functions>
  <startup>initDenon</startup>
  <actionList>

    <action>
     <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
     <name>SetTarget</name>
      <run>
          if connectDenon() then
            if lul_settings.newTargetValue == "1" then
                powerOnDenon()
            else
                powerOffDenon()
            end
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
     <name>SetLoadLevelTarget</name>
      <run>
    if connectDenon() then
    local z2status = sendDenon("Z2?") -- AVR3808 give a response like "Z2NET/USBZ251Z2OFFSVSOURCE"
z2on = string.find(z2status, "Z2ON")
        z2off = string.find(z2status, "Z2OFF")
local z2volume =40 -- setting a save volume
local z2volPos = 2

        if ((z2on < 1) and (z2off > 1)) then -- z2off is read from z2status
z2volPos = z2off - 2
        elseif ((z2on > 1) and (z2off < 0)) then
z2volPos = z2on - 2
        end
local vol_string = string.sub (z2status, z2volPos, z2volPos+2)
        local vol = tonumber(vol_string)
  --          local vol = tonumber(lul_settings.newLoadlevelTarget)
            if vol then
                if vol == 0 then
                    powerOffDenon()
                elseif vol == 100 then
                    powerOnDenon()
                else
   --                 local vol_max = tonumber(string.match(mv_status, "MVMAX *(%d*)"))
   --                 if vol_max then
   --                     local vol_percent = math.floor((vol * vol_max / 100) * 2) / 2
   --                     local vol_string = string.format("%02i", math.floor(vol_percent))
   --                     if (vol_percent - math.floor(vol_percent) > 0) then
   --                         vol_string = vol_string .. math.floor((vol_percent - math.floor(vol_percent)) * 10)
   --                     end
   --                     sendDenon("MV" .. vol_string)
   --                end
                end
            end
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
     <name>Poll</name>
      <run>
          if connectDenon() then
 --             if lul_settings.command then
 --                 for cmd in string.gmatch(lul_settings.command, "%S+") do
 --                     sendDenon(cmd)
 --                 end
 --             end
              updateDenonStatus(lul_device)
              disconnectDenon()
          end
      </run>
    </action>

   </actionList>
</implementation>
Code above is not working!!
I replaced some lines and added some other variable, ,maybe not the best code but its something.
I also added some comment lines so maybe somebody can make this working :D
I saved this like I_denonDimmerZ2.xml because you need to edit some parts to make this suitable for zone3.
there are only 10 types of people in the world those who understand binary and those who don't ;)

Offline dzmiller

  • Sr. Member
  • ****
  • Posts: 401
  • Karma: +10/-14
Re: Control Denon AVR (plugin-free solution)
« Reply #21 on: April 27, 2014, 11:41:10 am »
2011 and newer Denon receivers apparently have access at port 80 that allows multiple connections. Denon's iphone app apparently uses this access. Compared to access at port 23, the commands available are reduced, but it does include album art. Denon likely made this change to avoid multiple access problem, and comply with apple airplay requirements.
Port 80 is not documented by denon, AFAIK.

Offline cedricm

  • Sr. Newbie
  • *
  • Posts: 37
  • Karma: +1/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #22 on: May 26, 2014, 03:29:25 pm »
Hi there

I'm back ;)
Here is a new and improved version of the "Denon Dimmer".
It still uses a classic dimmer device, mapped to the Denon AVR ON/OFF/Volume controls.
Some bug fixes, and multi-zone support (one independent dimmer per zone)!

Here it is (save the following as /etc/cmh-ludl/I_DenonDimmer.xml)


<?xml version="1.0"?>
<implementation>
  <functions>
   -- Manually create a new device (Apps / Develop Apps / Create device)
   --   device_type: urn:schemas-upnp-org:device:DimmableLight:1
   --   device_file: D_DimmableLight1.xml
   --   impl_file: I_DenonDimmer.xml
   -- + set IP below
   --
   -- To send command from a scene:
   --   luup.call_action("urn:micasaverde-com:serviceId:HaDevice1", "Poll", {command = "PWON S MV10 SIIRADIO ZMFAVORITE1"}, [device id])
   --
    -- Multi zone support:
    --   add as many dimmers as you have zones, and add the following variable to each dimmer ('Advanced' tab, 'Variables'):
   --   . New service: "urn:denon-com:serviceId:Denon1"
    --   . New variable: "Zone"
    --   . New value: "M" (for Main zone) or "2" (for Zone 2)...

   local socket = require "socket"
   local denon_tcp
   local denon_ip = "<IP address>"

   function logDenon(msg, resp)
       if resp then
           luup.log("DENON: " .. msg .. " (" .. string.gsub(resp, "\r", '//') .. ")")
       else
           luup.log("DENON: " .. msg)
       end
   end

   function connectDenon()
       if denon_tcp then
           logDenon("Already connected")
           return false
       else
           logDenon("Connecting...")
           denon_tcp = assert(socket.tcp())
           denon_tcp:settimeout(0.5)
           local status, error = denon_tcp:connect(denon_ip, 23)
           if status then
               --logDenon("Connected")
               return true
           else
               denon_tcp = nil
               logDenon("Error connecting: " .. error)
               return false
           end
       end
   end

   function sendDenon(cmd)
       local buf = "", s, status, partial
       status = nil
       logDenon("Sending " .. cmd)

       denon_tcp:settimeout(0)
       buf, status, partial = denon_tcp:receive(9999)

       denon_tcp:settimeout(0.2)
       denon_tcp:send(cmd .. "\r")
       buf, status, partial = denon_tcp:receive(9999)
       if status then
           buf = partial
           if status ~= "timeout" then
               logDenon("Error receiving: " .. status .. ", partial: " .. partial)
           end
       end
       logDenon("Received", buf)
       sleepDenon()
       return buf
   end

   function disconnectDenon()
       --logDenon("Disconnecting...")
       denon_tcp:close()
       denon_tcp = nil
       logDenon("Disconnected")
   end

   function sleepDenon(delay)
        if delay then
            luup.sleep(delay)
        else
            luup.sleep(500)
        end
   end

   function getDenonZone(lul_device)
       local zone = luup.variable_get("urn:denon-com:serviceId:Denon1", "Zone", lul_device)

       if (zone == 'M') or (zone == '1') or (zone == '2') or (zone == '3') or (zone == '4') then
           return zone
       else
           return nil
       end
   end

   function isDenonOn(lul_device)
       local zone = getDenonZone(lul_device)
       local is_on = false

       if not zone then
            local pw_status = sendDenon("PW?")
           for resp in string.gmatch(pw_status, "[^\r]+") do
              if resp == "PWON" then
                  is_on = true
              end
          end
        else
            local pw_status = sendDenon("Z" .. zone .. "?")
           for resp in string.gmatch(pw_status, "[^\r]+") do
              if resp == "Z" .. zone .. "ON" then
                  is_on = true
              end
          end
        end

       local on_off_string = "OFF"
       if is_on then
           on_off_string = "ON"
       end
       if not zone then
           logDenon("Denon is " .. on_off_string)
       elseif zone == "M" then
           logDenon("Main zone is " .. on_off_string)
       else
           logDenon("Zone " .. zone .. " is " .. on_off_string)
       end
       return is_on
   end

   function getDenonVolumeAndMax(lul_device)
       local zone = getDenonZone(lul_device)
       local vol = nil
       local vol_max = nil

       local mv_status = sendDenon("MV?")
       for resp in string.gmatch(mv_status, "[^\r]+") do
           local vol_string = string.match(resp, "MV(%d*)")
           local v = tonumber(vol_string)
           if v then
               if string.len(vol_string) > 2 then
                   v = v / 10
               end
               vol = v
           end

           vol_string = string.match(resp, "MVMAX *(%d*)")
           v = tonumber(vol_string)
           if v then
               if string.len(vol_string) > 2 then
                   v = v / 10
               end
               vol_max = v
           end
       end
       logDenon("Main volume is " .. tostring(vol) .. "/" .. tostring(vol_max))

       if zone and zone ~= 'M' then
           vol = nil
           local mv_status = sendDenon("Z" .. zone .. "?")
           for resp in string.gmatch(mv_status, "[^\r]+") do
               local vol_string = string.match(resp, "Z" .. zone .. "(%d*)")
               local v = tonumber(vol_string)
               if v then
                   if string.len(vol_string) > 2 then
                       v = v / 10
                   end
                   vol = v
               end
           end

           logDenon("Zone " .. zone .. " volume is " .. tostring(vol) .. "/" .. tostring(vol_max))
       end

       return {vol, vol_max}
   end

   function setDenonVolume(lul_device, volume_percent)
       local zone = getDenonZone(lul_device)
       local vol = tonumber(volume_percent)

       if vol then
           if vol == 0 then
               powerOffDenon(lul_device)
            else
               powerOnDenon(lul_device)
               local volumes = getDenonVolumeAndMax(lul_device)
               if volumes[2] then
                   if not zone or zone == "M" then
                       local vol_percent = math.floor((volume_percent * volumes[2] / 100) * 2) / 2
                       local vol_string = string.format("%02i", math.floor(vol_percent))
                       if (vol_percent - math.floor(vol_percent) > 0) then
                           vol_string = vol_string .. math.floor((vol_percent - math.floor(vol_percent)) * 10)
                       end
                       sendDenon("MV" .. vol_string)
                   else
                       local vol_percent = math.floor(volume_percent * volumes[2] / 100)
                       local vol_string = string.format("%02i", vol_percent)
                       sendDenon("Z" .. zone .. vol_string)
                   end
               end
           end
       end
   end

   function powerOnDenon(lul_device)
       local zone = getDenonZone(lul_device)
       local already_on = isDenonOn(lul_device)
       if isDenonOn(lul_device) then
           logDenon("Not powering ON, is already ON")
       else
           if not zone then
               sendDenon("PWON")
           else
               sendDenon("Z" .. zone .. "ON")
           end
           sleepDenon(1500)
       end
   end

   function powerOffDenon(lul_device)
       local zone = getDenonZone(lul_device)
       if not zone then
           sendDenon("PWSTANDBY")
       else
           sendDenon("Z" .. zone .. "OFF")
       end
   end

   function initDenon(lul_device)
       if connectDenon() then
           updateDenonStatus(tonumber(lul_device))
           disconnectDenon()
       end
       luup.call_timer('initDenon', 1, "5m", "", lul_device)
   end

    function startupDenon(lul_device)
        local zone = getDenonZone(lul_device)
        local zone_i = tonumber(zone)
        if zone_i then -- wait [zone number] minutes before updating the status (avoids multiple instances trying to update at the same time)
           luup.call_timer('initDenon', 1, zone_i .. "m", "", lul_device)
        else
            initDenon(lul_device)
        end
    end

   function updateDenonStatus(lul_device)
       local zone = getDenonZone(lul_device)

       if isDenonOn(lul_device) then
           luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "1", lul_device)

           local volumes = getDenonVolumeAndMax(lul_device)
           if volumes[1] and volumes[2] then
               luup.variable_set("urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus", tostring(math.floor(volumes[1]*100/volumes[2])), lul_device)
           end
       else
           luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", "0", lul_device)
           luup.variable_set("urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus", "0", lul_device)
       end

       luup.variable_set("urn:micasaverde-com:serviceId:HaDevice1", "LastUpdate", os.time(), lul_device)
   end
  </functions>
  <startup>startupDenon</startup>
  <actionList>

    <action>
     <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId>
     <name>SetTarget</name>
      <run>
          if connectDenon() then
            if lul_settings.newTargetValue == "1" then
                powerOnDenon(lul_device)
            else
                powerOffDenon(lul_device)
            end
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:upnp-org:serviceId:Dimming1</serviceId>
     <name>SetLoadLevelTarget</name>
      <run>
          if connectDenon() then
            setDenonVolume(lul_device, lul_settings.newLoadlevelTarget)
            updateDenonStatus(lul_device)
            disconnectDenon()
          end
      </run>
    </action>

    <action>
     <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
     <name>Poll</name>
      <run>
          if connectDenon() then
              if lul_settings.command then
                  local cmd
                  for cmd in string.gmatch(lul_settings.command, "%S+") do
                      if cmd == 'S' then
                          sleepDenon()
                      else
                          sendDenon(cmd)
                      end
                  end
              end
              updateDenonStatus(lul_device)
              disconnectDenon()
          end
      </run>
    </action>

   </actionList>
</implementation>

Offline tomgru

  • Hero Member
  • *****
  • Posts: 1403
  • Karma: +18/-6
Re: Control Denon AVR (plugin-free solution)
« Reply #23 on: September 05, 2014, 12:13:26 pm »
2011 and newer Denon receivers apparently have access at port 80 that allows multiple connections. Denon's iphone app apparently uses this access. Compared to access at port 23, the commands available are reduced, but it does include album art. Denon likely made this change to avoid multiple access problem, and comply with apple airplay requirements.
Port 80 is not documented by denon, AFAIK.

@dzmiller, over in the plugin thread, i'm having what sounds like a similar issue.  The plugin routinely loses connection, but works fine (for awhile) if I turn the denon (AVR-X2000) on/off, then reload vera.  What is weird is that my other clients don't have this problem at all (web interface, Windows Phone App and Android tablet app).  all seem to work fine in unison. 

Zoot on the other thread has been great and trying to troubleshoot, but we can't figure it out.  Any ideas you (and others) can think of?

http://forum.micasaverde.com/index.php/topic,5863.405.html

Offline Spilota

  • Sr. Newbie
  • *
  • Posts: 29
  • Karma: +0/-1
Re: Control Denon AVR (plugin-free solution)
« Reply #24 on: January 09, 2016, 10:53:43 am »
Does this work in UI7?

I have a similar control mapped to a Pioneer receiver, however it seems that "Switch Power" is never called.  Clicking "off" on the UI control sets the dim level to 0 but never calls SwitchPower.

Offline markdstjohn

  • Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
Re: Control Denon AVR (plugin-free solution)
« Reply #25 on: December 24, 2017, 11:03:42 pm »
I created a version of a Denon Dimmer that uses the HTTP API to work around the single connection issues that occur when using the port 23 API. To use it you can do the following:

1. Download I_DenonVolume1.xml and L_DenonVolume1.lua from https://github.com/markdstjohn/vera-denon-dimmer
2. Click Apps -> Develop Apps -> Luup files
3. Upload the I_DenonVolume1.xml and L_DenonVolume1.lua files
4. Click "Create device"
5. Set "Description" to a name that you'd like to use for the receiver like "Living Room Receiver"
6. Set "device type" to the following: urn:schemas-upnp-org:device:DimmableLight:1
7. Set "Upnp Device Filename" to the following: D_DimmableLight1.xml
8. Set "Upnp Implementation Filename" to the following: I_DenonVolume1.xml
9. Set "IP address" to the IP address of the receiver that you'd like to control with the dimmer
10. Set "Room" to the room that contains your device
11. Click "Create device"
12. Click Settings -> Z-Wave Settings -> "Advanced tab" -> Reload engine

Repeat steps #4-12 to add additional devices for any additional receivers you'd like to add.

Note that since this uses the HTTP API, it doesn't maintain a constant connection to the receiver and therefore doesn't automatically update when you change the volume or power on the receiver itself. However, you can run Luup to poll the device from within a scene if you want to update it to the current status using the following:

luup.call_action("urn:micasaverde-com:serviceId:HaDevice1", "Poll", {}, YOUR_DEVICE_ID)