ppanagiotis / pymusiccast

Group MusicCast Speakers with Home Assistant
MIT License
37 stars 8 forks source link

Fix volume when max_volume != 100 #1

Closed vigonotion closed 3 years ago

vigonotion commented 4 years ago

My Yamaha R-N402D has a max_volume of 80 set which I cannot increase. The current code includes a calculation for the volume based on the max_volume, which does not work for me (the HA volume never corresponds to the device volume). I opened an issue 1,5 years ago in the Home Assistant repo (see https://github.com/home-assistant/core/issues/21218).

I decided to fix this now myself in this fork, as I don't think the core component will get updated anytime soon (because the change needs to be made in pymusiccast as well which is unmaintained).

ppanagiotis commented 4 years ago

Hi @vigonotion,

Thanks for your PR. I have two musiccast devices and the volume control is a little tricky. Let me explain:

The first one is an WX-010 At this one it only has

YamahaExtendedControl/v1/main/getStatus

...
  "sleep": 0,
  "volume": 0,
  "mute": false,
  "max_volume": 60,
...

so at this model max volume is working great

but

at my yamaha RX-V683 has

...
  "volume": 10,
  "mute": false,
  "max_volume": 161,
...
  "actual_volume": {
    "mode": "db",
    "value": -75.5,
    "unit": "dB"
  },
...

the actual volume for some reason can increase more than the value of the max_volume.

two more snipets to help you undrestand the problem

setting my aplifier at its max volume from home assistant:

  "volume": 161,
  "mute": false,
  "max_volume": 161,
...
  "actual_volume": {
    "mode": "db",
    "value": 0,
    "unit": "dB"
  },

but setting the max volume from remote control

  "volume": 161,
  "mute": false,
  "max_volume": 161,
...
  "actual_volume": {
    "mode": "db",
    "value": 16.5,
    "unit": "dB"
  },

So the problem is that you can't use yamaha api to set the actual_volume at the max value.

Also your PR is a little bit "hardcoded" for your device.

I will try to find a more generic solution.

ppanagiotis commented 4 years ago

Seeking further at this problem I find out that you can increase volume to max value using YamahaExtendedControl/v1/main/setActualVolume?mode=db&value=x I will try to autodiscover if yamaha device supports setActualVolume and use it instead of setVolume.

ppanagiotis commented 4 years ago

@vigonotion Could you please check if this branch resolves your issue with max volume? (It isn't ready yet ;-) )

if you need any help applying the branch feel free to ask me.

vigonotion commented 4 years ago

It's not working for me on my R-N402D and neither for my MusicCast 20.

getFeatures @ R-N402D
{
  "response_code": 0,
  "system": {
    "func_list": [
      "wired_lan",
      "wireless_lan",
      "wireless_direct",
      "network_standby",
      "network_standby_auto",
      "bluetooth_standby",
      "auto_power_standby",
      "bluetooth_tx_setting",
      "speaker_a",
      "speaker_b",
      "headphone",
      "airplay",
      "network_reboot"
    ],
    "zone_num": 1,
    "input_list": [
      {
        "id": "napster",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": true,
        "play_info_type": "netusb"
      },
      {
        "id": "spotify",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "juke",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": true,
        "play_info_type": "netusb"
      },
      {
        "id": "qobuz",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": true,
        "play_info_type": "netusb"
      },
      {
        "id": "tidal",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": true,
        "play_info_type": "netusb"
      },
      {
        "id": "deezer",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": true,
        "play_info_type": "netusb"
      },
      {
        "id": "airplay",
        "distribution_enable": false,
        "rename_enable": false,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "mc_link",
        "distribution_enable": false,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "server",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "net_radio",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "bluetooth",
        "distribution_enable": true,
        "rename_enable": false,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "usb",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "netusb"
      },
      {
        "id": "tuner",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "tuner"
      },
      {
        "id": "optical",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      },
      {
        "id": "coaxial",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      },
      {
        "id": "line1",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      },
      {
        "id": "line2",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      },
      {
        "id": "line3",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      },
      {
        "id": "line_cd",
        "distribution_enable": true,
        "rename_enable": true,
        "account_enable": false,
        "play_info_type": "none"
      }
    ]
  },
  "zone": [
    {
      "id": "main",
      "func_list": [
        "power",
        "sleep",
        "volume",
        "mute",
        "tone_control",
        "balance",
        "signal_info",
        "prepare_input_change",
        "link_control",
        "link_audio_quality"
      ],
      "input_list": [
        "napster",
        "spotify",
        "juke",
        "qobuz",
        "tidal",
        "deezer",
        "airplay",
        "mc_link",
        "server",
        "net_radio",
        "bluetooth",
        "usb",
        "tuner",
        "optical",
        "coaxial",
        "line1",
        "line2",
        "line3",
        "line_cd"
      ],
      "tone_control_mode_list": [
        "manual"
      ],
      "link_control_list": [
        "speed",
        "standard",
        "stability"
      ],
      "link_audio_quality_list": [
        "compressed",
        "uncompressed"
      ],
      "range_step": [
        {
          "id": "volume",
          "min": 0,
          "max": 80,
          "step": 1
        },
        {
          "id": "tone_control",
          "min": -5,
          "max": 5,
          "step": 1
        },
        {
          "id": "balance",
          "min": -10,
          "max": 10,
          "step": 1
        }
      ]
    }
  ],
  "tuner": {
    "func_list": [
      "fm",
      "rds",
      "dab"
    ],
    "range_step": [
      {
        "id": "fm",
        "min": 87500,
        "max": 108000,
        "step": 50
      }
    ],
    "preset": {
      "type": "separate",
      "num": 40
    }
  },
  "netusb": {
    "func_list": [
      "recent_info",
      "play_queue",
      "mc_playlist",
      "streaming_service_use"
    ],
    "preset": {
      "num": 40
    },
    "recent_info": {
      "num": 40
    },
    "play_queue": {
      "size": 200
    },
    "mc_playlist": {
      "size": 200,
      "num": 5
    },
    "net_radio_type": "airable",
    "pandora": {
      "sort_option_list": [
        "date",
        "alphabet"
      ]
    }
  },
  "distribution": {
    "version": 2,
    "compatible_client": [
      2
    ],
    "client_max": 9,
    "server_zone_list": [
      "main"
    ]
  },
  "ccs": {
    "supported": true
  },
  "privacy": {
    "gdpr_supported": true
  }
}
vigonotion commented 4 years ago

I did some package sniffing, and the official MusicCast Android app uses the /setVolume?volume=50 where 50 is the percentage (50%) regardless of the max volume (like my patch).

So maybe we should check if the device is in decibel mode and use the current behaviour and otherwise my patched behaviour?

vigonotion commented 4 years ago

/YamahaExtendedControl/v1/main/getStatus for R-N402D

{"response_code":0,"power":"standby","sleep":0,"volume":61,"mute":false,"max_volume":80,"input":"line1","distribution_enable":true,"tone_control":{"mode":"manual","bass":5,"treble":0},"balance":0,"link_control":"speed","link_audio_quality":"uncompressed","disable_flags":0}

ppanagiotis commented 4 years ago

It's not working for me on my R-N402D and neither for my MusicCast 20.

I borrowed from a friend a MusicCast 20 to check what is going wrong. I set it up at home assistant using both master and volume_fix branch! At both branches volume control works as expected!!! What exactly did you test and said that it isn't working ?

Also at MusicCast 20 setVolume isn't percentage.

curl  http://x.x.x.x/YamahaExtendedControl/v1/main/getStatus|jq .|grep volume
"volume": 10,
curl  'http://x.x.x.x:/YamahaExtendedControl/v1/main/setVolume?volume=40'
{"response_code":0}
curl  http://x.x.x.x:/YamahaExtendedControl/v1/main/getStatus|jq .|grep volume
"volume": 40,

And finaly if I set the volume above max_volume:

curl  'http://x.x.x.x:/YamahaExtendedControl/v1/main/setVolume?volume=90'
{"response_code":4}
TheZoker commented 4 years ago

Hi guys,

I just tested this with my Soundbar 40, which has two MusicCast 20 and one Sub 100 connected.

curl  http://x.x.x.x/YamahaExtendedControl/v1/main/getStatus|jq .|grep volume
  "volume": 49,
  "max_volume": 100,
  "subwoofer_volume": -5,
curl  'http://x.x.x.x:/YamahaExtendedControl/v1/main/setVolume?volume=40'
{"response_code":0}
curl  http://x.x.x.x:/YamahaExtendedControl/v1/main/getStatus|jq .|grep volume
  "volume": 40,
  "max_volume": 100,
  "subwoofer_volume": -5

So I think in this case it's more or less percentage, since the max_volume is 100. I have to admit I did not look at the code, but shouldn't the percentage calculation be something like:

device.volume = (device.max_volume / 100) * volume_percentage

So if I want my MusicCast 20 to have 50% volume, the calculation would be

device.volume = (60 / 100) * 50 = 30

For the soundbar it would be:

device.volume = (100 / 100) * 50 = 50

I think that this should work for (at least) the following devices:

I'm not sure about the actual volume attribute, but for all the above mentioned speakers this should work, right?

Maybe we can create a list of devices and group them by behavior. So all the speakers above are one group with the calculation above. And for other receivers, speakers etc. we need to figure out, what is happening and then use a different formula for calculating the volume for these devices.

vigonotion commented 4 years ago

I understand that formula, but this would lead to diverged numbers between HA and the receiver / app. My R-N402D has a max volume of 80, though it can theoretically go up to 100. When I set the volume with your proposed formula, HA would say 50% volume but my R-N402D (which has a LCD) shows (80/100)*50=40. That's super annoying, because I never set the volume correctly via HA

TheZoker commented 4 years ago

What about a optional manual overwrite?

Something in the config like:

- platform: musiccast_yamaha
  host: `your.speaker.ip.address`
  port: 5009
  max_volume: 100

Then the formula would use this value instead of the default one.

vigonotion commented 4 years ago

Interesting, I like that idea. That would probably mean that if I set the volume to 90 via HA it jumps back to 80, but I'm totally fine with that

TheZoker commented 4 years ago

@ppanagiotis What do you think about this?

ppanagiotis commented 4 years ago

@TheZoker can you please try this branch if works for you? I don't like hardcoded settings, and I want to be sure that there is no other way to fix this problem ;-)

TheZoker commented 4 years ago

Actually I only have a Soundbar 40, a Sub 100 and two MusicCast 20 speaker, so I don't have this problem. I was interested in the group and ungroup functionality (and in a newer version than the current one in HA) and thought I can give some input here.

But having a option like

- platform: musiccast_yamaha
  host: `your.speaker.ip.address`
  port: 5009
  max_volume: 100

it's not hardcoded right? So every user can set individual max volume for every device

vigonotion commented 3 years ago

I accepted that the numbers are not the same on the OSD and Home Assistant, I solved it by putting my receiver into a shelf with a door so I can't even see it anymore and control it exclusively via HA :) I'm therefore gonna close this issue now