peteS-UK / sonyavr

Home Assistant Media Player for older Sony AVRs
MIT License
0 stars 0 forks source link

Set Volume and receiving volume level not working on STR-DA5800ES #3

Open mrfarrow72 opened 1 month ago

mrfarrow72 commented 1 month ago

I saw a couple of messages about volume control not working, which I am also seeing. I am using a STR-DA5800ES, so appreciate this integration hasn't been tested with this model, but everything is working apart from volume control. It looks like the byte maps don't match my receiver - maybe it's the same for others. Here are some debug log entries, I hope this helps.

Volume down with remote

My receiver resets to a volume of -20dB on power on. These are the debug entries for 4 'volume down' presses on the remote (-0.5dB each time). Byte 6 (01) doesn't match the FEEDBACK_VOLUME byte map (03). I notice that the last 3 bytes change each time too.

2024-06-23 10:50:06.131 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:80:53'
2024-06-23 10:50:06.131 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:ec:80:53'
2024-06-23 10:50:07.011 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ed:00:d2'
2024-06-23 10:50:07.011 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:ed:00:d2'
2024-06-23 10:50:07.778 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ed:80:52'
2024-06-23 10:50:07.778 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:ed:80:52'
2024-06-23 10:50:08.580 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ee:00:d1'
2024-06-23 10:50:08.581 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:ee:00:d1'

Call service with "Volume Up" and "Volume Down"

The volume up and volume down commands were working fine (the receiver volume adjusted each time), but the same feedback error occurs as above. This is two volume up commands followed by two volume down.

2024-06-23 10:50:33.151 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x55, 0x0, 0x8
2024-06-23 10:50:33.204 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:f1:80:4e'
2024-06-23 10:50:33.205 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:f1:80:4e'
2024-06-23 10:50:38.425 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x55, 0x0, 0x8
2024-06-23 10:50:38.460 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:f2:00:cd'
2024-06-23 10:50:38.461 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:f2:00:cd'
2024-06-23 10:50:53.449 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x56, 0x0, 0x7
2024-06-23 10:50:53.495 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:f1:80:4e'
2024-06-23 10:50:53.495 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:f1:80:4e'
2024-06-23 10:50:54.588 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x56, 0x0, 0x7
2024-06-23 10:50:55.430 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:f1:00:ce'
2024-06-23 10:50:55.431 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug [unknown data packet]b'02:06:a8:92:00:01:f1:00:ce'

Call service with "Set Volume" and a value of "10"

There is no reaction from the receiver for this command, and no further log entries, so I assume the command byte map isn't correct for my receiver. I'm not sure what the value should be as my receiver display always shows negative numbers, but anything outside the range 0-256 throws an exception in the log.

2024-06-23 10:51:22.616 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x6, 0xa0, 0x52, 0x0, 0x3, 0x0, 0xa, 0x0
2024-06-23 10:51:22.616 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume 10
peteS-UK commented 1 month ago

Yes, it's all a bit of guesswork trying to figure this out. It's good that most of it's working for your avr though. There's no doc for any of this so it's just looking at the debug and trying to figure it out ....

The AVRs all seem to behave in the same way - when you send a command, they respond with a status. For my AVR, the volume response packet is

02:06:a8:92:00:03:00:07:b6

The first 6 bytes seem to be the "volume update" packet, then the volume itself is the 8th byte. The reported volume for me is a 0 to 45 scale in steps of 1, so quite different to your db related scale.

It's easy enough to change the packet so that it'll recognise it as "here's the volume" as you've seen. We could probably actually just change the match to be only the first 5 characters of the volume packet. That would be

We could change line 890 from

        if FEEDBACK_VOLUME == data[:-2]:

to

        if FEEDBACK_VOLUME[0:5] == data[0:5]:

But, we then need to work out how to decode the volume itself.

peteS-UK commented 1 month ago

It looks like there's something in the 7th byte which seems to change with the volume. If you can work out what that pattern is, I can certainly have a look at building something in. If it's moving by half a db a time, perhaps it's some counter *2 starting from a mininum value, or offset in some way.

BTW, you probably don't want to change it to understand the volume packet as above without being careful. The volume up and down at the moment actually use the current known volume, and +-1 to it and then set the volume, rather than use the direct up down commands. That's because those commands only work when the display is on for me - so you have to press volume up twice - once to activate the display, then once to actually turn it up/down.

Let me know if you can figure out how to translate the feedback into the volume and I'll take a look. I'll also need to know the min and max volumes - HA understands volume as a value between 0 and 1, so you have to work out the percentage within the volume range.

mrfarrow72 commented 1 month ago

Thanks for coming back. Yes, I did play around with the FEEDBACK_VOLUME map and got it to recognise the packet at least. Working out the volume has stumped me so far though! The four responses I got by using the remote are (last 3 bytes):

ec:80:53 = -20.5dB ed:00:d2 = -21.0dB ed:80:52 = -21.5dB ee:00:d1 = -22.0dB

There are definite patterns there - I'll keep looking!

For the HA volume (0-1), I managed to get a formula to convert it to the dB value on the receiver: (volume * 50) - 55. I currently have buttons setting specific volumes (0.7 for -20dB, 0.86 for -12dB).

peteS-UK commented 1 month ago

So, is the 80 or 00 byte showing if it's .0 or .5?

peteS-UK commented 1 month ago

So, is the 80 or 00 byte showing if it's .0 or .5?

Then ec is -20, ed is -21 etc.

What's the output for -1db, 0db, +1db

mrfarrow72 commented 1 month ago

-1dB isn't an option - the volume goes up and down in 0.5dB increments only. That "80" (128) value definitely matches the .0 and .5 increments. The bytes either side go up/down in sync with the whole number part of the volume value too. I'll take another look tomorrow. Interesting how this receiver seems to be so very different to others. I have the Sony ES remote app on my phone to control it - I need to find a way to intercept what that's sending when I change volume.

peteS-UK commented 1 month ago

Sorry, I'd assumed that your max volume was > 0db. Isn't it - what's the max? I was curious if it did anything different for volumes >=0 db, or if it was just a linear offset. From the previous numbers, is it just an offset of 216 decimal (ignoring the -ve) i.e. ed = 237 = 237-216 = 21, or depending on what it does with +ve values, maybe offset of 258 (237-(258)). No idea what the last byte might do - 2 distinct registers on 0. and .5 volumes, but no idea what that might mean.

mrfarrow72 commented 1 month ago

Finally got round to looking at it again! So I misread your previous question - 1db and +1db are options - it's the increments that are limited to 0.5 steps. I have determined that the minimum volume is -92dB and the maximum is +23dB, in 0.5dB increments. I've managed to get the debug entry for every step, and have worked out a formula based on the last 3 bytes:

-92dB = ...A4:00:1B -0.5dB = ...FF:80:40 0dB = ...00:00:BF 20dB = ...14:00:AB 23dB = ...17:00:A8

Byte 7 represents the whole number: the actual number for >0, or byte-256 for <0 Byte 8 represents the fraction: 0 for 0 and 80 (128) for .5 Byte 9, although they change linearly, seems to be unnecessary

I don't know python, but in C# it would something like: displayed_volume = (byte7 > 128 ? byte7 - 128 : byte) + (byte 8 / 256)

The next step would be to work out what to send to set the volume. I imagine that will be a lot harder!

peteS-UK commented 1 month ago

Hi @mrfarrow72. That looks fine, but that formula doesn't match your previous findings

ec:80:53 = -20.5dB ed:00:d2 = -21.0dB ed:80:52 = -21.5dB ee:00:d1 = -22.0dB

byte 7 there seems to be running in the wrong direction vs. your new formula - was that listing wrong? The formula is easy to do, then we'll see if the direct "change the volume up" command works, rather than the "set the volume to the current volume +1" that I'm currently doing.

mrfarrow72 commented 1 month ago

Good spot - I didn't notice that (I basically started again at minimum and increased all the way to maximum). I think the negative numbers must have been messing with my brain! That original post should have been -19.5, -19.0, -18.5, -18.0, which is an increasing volume.

I've also mapped the entire 0 to 1 range that HA uses from the standard media player. It's not completely linear, because that would be too easy! 0=-92dB and 1=0dB. It's linear from 0.2 (-45dB) to 0.9 (-10dB)

Volume Up and Volume Down for the integration work just fine - moving in 0.5db increments each time.

peteS-UK commented 1 month ago

I don't know python, but in C# it would something like: displayed_volume = (byte7 > 128 ? byte7 - 128 : byte) + (byte 8 / 256)

Similar in python - true if condition else false

displayed_volume = ((byte7 - 256) if byte7 > 128  else byte7) + (byte 8 / 256)
mrfarrow72 commented 1 month ago

Yes, added that and the "volume" attribute works a treat (though volume_level is now negative!). The challenge now is to find some way of working out the byte map for "Set Volume". The ES Remote app that I have only sends up and down commands :-(

        if FEEDBACK_VOLUME == data[:6]:
            _LOGGER.debug("Vol %s:%s", data[6], data[7])
            vol = float(data[6] if data[6] < 128 else (data[6] - 256)) + (data[7] / 256.0)
peteS-UK commented 1 month ago

Yes, we'll need to normalise the volume level we return to HA to deal with the -ve value.

Right now, the volume up and down are using the set volume command. Let's see if the direct up and down commands work....

You'll have ...

    async def async_volume_up(self):
        target_volume = self.state_service.volume + self.scroll_step_volume
        if target_volume <= MAX_VOLUME:
            await self.async_set_volume(target_volume)

change it to

    async def async_volume_up(self):
        self.scroll_step_volume = 0.5
        target_volume = self.state_service.volume + self.scroll_step_volume
        if target_volume <= MAX_VOLUME:
            await self.async_send_command(CMD_VOLUME_UP)

and

    async def async_volume_down(self):
        self.scroll_step_volume = 0.5
        target_volume = self.state_service.volume - self.scroll_step_volume
        if target_volume >= MIN_VOLUME:
            await self.async_send_command(CMD_VOLUME_DOWN)

Let me know if that works

mrfarrow72 commented 1 month ago

So thought I'd just check current state before making those changes, as I saw up/down working the other day. I have the original code in place currently (set_volume(target_volume), but this is what I see in the debug log when I use the "Volume Up" and "Volume Down" commands from the developer tools:

2024-06-26 08:22:39.574 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x55, 0x0, 0x8
2024-06-26 08:22:39.618 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:80:53'
2024-06-26 08:22:39.619 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 236:128
2024-06-26 08:22:39.619 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -19
2024-06-26 08:22:41.896 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x55, 0x0, 0x8
2024-06-26 08:22:41.948 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ed:00:d2'
2024-06-26 08:22:41.948 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 237:0
2024-06-26 08:22:41.949 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -19
2024-06-26 08:22:46.160 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x56, 0x0, 0x7
2024-06-26 08:22:46.209 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:80:53'
2024-06-26 08:22:46.209 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 236:128
2024-06-26 08:22:46.210 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -19
2024-06-26 08:22:47.531 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x56, 0x0, 0x7
2024-06-26 08:22:47.583 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:00:d3'
2024-06-26 08:22:47.583 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 236:0
2024-06-26 08:22:47.583 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -20

Apart from the "Volume" entry missing the decimal, it's sending the CMD_VOLUME_UP and CMD_VOLUME_DOWN commands already (and they are working). Is the code you want me to change called in another way?

mrfarrow72 commented 1 month ago

Ah, got it - the developer tools send the specific commands (which are working), while the media controls for the entity use the set_volume method. That way isn't working because I am now trying to send a floating point number (and negative), but that way never worked before either. So, direct commands work, but set_volume does not.

Update: I made those changes anyway, and now the up/down buttons work from the entity too (as expected). Of course I had to change LOW_VOLUME from 0 to -92 so the logic would trigger.

peteS-UK commented 4 weeks ago

No, the up and down direct commands are also using the same up/down via setting, but the difference is just (as you've found) that the ranges wouldn't be right for max and min volume. i.e. when you're using the entity, it won't do anything with a negative value currently. So, from what you say, it sounds like setting the volume is working ok, since the up and down are working. So, with the max/min changes you've made, does it all work now, apart from the decimal, is it all working now? If so, I'll add some model specific stuff into the main version - probably just look for model information from the setup, or perhaps use the volume response to identify this receiver.

mrfarrow72 commented 4 weeks ago

Sorry, I may have over-complicated things! Basically everything is working apart from the ability to set a specific volume. From these command strings for the service, all of them control the amp apart from the last one (debug entries below).

        match command:
            case "Power On":
                await self.command_service.async_send_command(CMD_POWER_ON)
            case "Power Off":
                await self.command_service.async_send_command(CMD_POWER_OFF)
            case "Mute":
                await self.command_service.async_send_command(CMD_MUTE)
            case "UnMute":
                await self.command_service.async_send_command(CMD_UNMUTE)
            case "Volume Up":
                await self.command_service.async_send_command(CMD_VOLUME_UP)
            case "Volume Down":
                await self.command_service.async_send_command(CMD_VOLUME_DOWN)
            case "Source Up":
                await self.command_service.async_source_up()
            case "Source Down":
                await self.command_service.async_source_down()
            case "Set Sound Field":
                if value is None:
                    _LOGGER.error("You must specific a sound field")
                    return
                await self.async_set_mode(value)
            case "Set Source":
                if value is None:
                    _LOGGER.error("You must specific a source")
                    return
                if value not in SOURCE_MENU_MAP.values():
                    _LOGGER.error('Sound field "%s" is not a valid sound field' % value)
                    return
                await self.async_set_source(value)
            case "Set Volume":
                await self.async_volume_set(int(value))

In these debug entries, you can see a volume up and volume down command, followed by a set volume command (02:06:A0:52:00:03:00:05:00). I passed "5" as that is a valid value that should do something to the amp. HA reported success, but the amp did nothing, and there was no incoming feedback. My assumption is that the command bytemap is not correct for this amp.

2024-06-26 17:05:11.091 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x4, 0xa0, 0x60, 0x0, 0x1, 0x0
2024-06-26 17:05:11.188 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:07:a8:82:00:16:16:21:00:82'
2024-06-26 17:05:11.188 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Power state: True
2024-06-26 17:05:13.528 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:07:a9:82:80:01:00:28:78:ad'
2024-06-26 17:05:13.529 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] FM Tuner: 1 (103.60 MHz) Stereo: True
2024-06-26 17:05:23.534 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x55, 0x0, 0x8
2024-06-26 17:05:23.568 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:80:53'
2024-06-26 17:05:23.568 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 236:128
2024-06-26 17:05:23.568 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -19.50
2024-06-26 17:05:27.598 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x3, 0xa0, 0x56, 0x0, 0x7
2024-06-26 17:05:27.637 INFO (MainThread) [custom_components.sonyavr.sonyavr] Debug b'02:06:a8:92:00:01:ec:00:d3'
2024-06-26 17:05:27.638 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Vol 236:0
2024-06-26 17:05:27.638 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume -20.00
2024-06-26 17:05:35.157 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Command : 0x2, 0x6, 0xa0, 0x52, 0x0, 0x3, 0x0, 0x5, 0x0
2024-06-26 17:05:35.157 DEBUG (MainThread) [custom_components.sonyavr.sonyavr] Volume 5.00
peteS-UK commented 4 weeks ago

Yes, the command map is likely a problem here looking at it - since the current byte map has the volume as a single integer, which doesn't make sense as we know the map is different for you. Have you tried changing the byte map to have the last 3 bytes to match the debug response when a volume is set - i.e. just set the bytemap to something like 0x2, 0x6, 0xa0, 0x52, 0x0, 0x3, 0xec,0x80,0x53 and see if it sets the volume to -19.5? Might be worth a shot. i.e. encode the volume in the same way we're decoding it.

mrfarrow72 commented 4 weeks ago

Was thinking the same, so tried that with a known set of values, but still nothing. Seems odd that all the other command and feedback maps work, but this one doesn't - makes me think it's not that and it's something more obvious. Perhaps just bytes in the wrong positions. I'm away for the weekend now, so may take another look next week. Thanks for entertaining my interest in this! I've learnt some python at least :)

peteS-UK commented 4 weeks ago

Ah well - good that the rest of it works. I'll make some tweaks to the integration to include these changes - I'll just base it on model being set accordingly in the config. In any case, you can easily try various byte maps and see if you get anywhere. It's odd that it doesn't use the same codes to send as the debug - i'll leave the set volume stuff in there for now so we can fiddle around for now.

peteS-UK commented 4 weeks ago

Hi @mrfarrow72 . I've built a new version with a switch in there for the two different modes. Rather than tie it to the model directly, I've tied it to the feedback from the avr - as it seems likely that there are other sony avr's of the same era as yours that work the same. I'll post it here over the weekend - I can't test it with your AVR of course, so grateful if you could take a spin with it - I think it should all work upto the current limit of course of settting volume.

For that, I've left a if/else in for setting the volume directly

    async def async_set_volume(self, vol):
        if self.state_service.volume_model == 3:
            # Normal Volume Model
            cmd = bytearray(
                [
                    0x02,
                    0x06,
                    0xA0,
                    0x52,
                    0x00,
                    0x03,
                    0x00,
                    min(vol, self.state_service.volume_max),
                    0x00,
                ]
            )
        else:
            # Volume Model with float
            cmd = bytearray(
                [
                    0x02,
                    0x06,
                    0xA0,
                    0x52,
                    0x00,
                    0x03,
                    0x00,
                    0x00,  # Volume Byte in integer volume
                    0x00,
                ]
            )
        await self.async_send_command(cmd)
        self.state_service.update_volume(vol)

So, feel free to play around with the else: byte array and see if you can anything going.

peteS-UK commented 4 weeks ago

OK - give these a try ..

sony.zip

To make it easier to look at the set volume byte string, I've added a new command to the developer service called "Byte Array String". This will just send the byte string you put in the command. String are just text, space seperated, without the 0x etc. So, for example,

02 06 a0 52 00 03 00 13 00

It doesn't validate the string format, but should make testing easier. If you can't figure out the bytes to set a volume, we can remove the MediaPlayerEntityFeature.VOLUME_SET feature so that HA won't assume you can set the volume, whilst leaving MediaPlayerEntityFeature.VOLUME_STEP.

mrfarrow72 commented 3 weeks ago

Thanks for those updates - all good and everything working (apart from "set volume"). Nice addition of the byte string command - the constant rebooting was starting to wear a bit thin! I'll start playing around with that to see what I can find.

peteS-UK commented 3 weeks ago

Excellent - glad those changes are working. Yes, I thought just being able to send a command would make life easier when you're just having to guess a bit. Hopefully you can find something that works for volume ....

mrfarrow72 commented 3 weeks ago

Actually, small change - the volume up and down functions are still using the MIN_VOLUME and MAX_VOLUME constants, which means volume down doesn't work. I've changed it to use self.state_service.volume_min:

    async def async_volume_down(self):
        target_volume = self.state_service.volume - self.scroll_step_volume
        if target_volume >= self.state_service.volume_min:
            if self.state_service.volume_model == 3:
                await self.async_set_volume(target_volume)
            else:
                await self.async_send_command(CMD_VOLUME_DOWN)
peteS-UK commented 3 weeks ago

Ooops - Good spot - thanks for testing it. I've updated the original.

peteS-UK commented 2 weeks ago

Hi @mrfarrow72 . I've pushed this new version as a 1.2.0 release this morning with a couple of other fixes and a new sensor entity, for the AVR volume as I was asked for it and it's quite handy. Do you think there's anything else we can do for the volume setting, or should we close this one down now?

mrfarrow72 commented 2 weeks ago

Morning! Well, I had a quick go last night and managed to get the set volume working. It's frustratingly simple too - change 0x03 to 0x01, which is exactly the same change we made to get the volume back! Should have tried that first, oh well. I started trying to convert the volume passed in (which seems to be 0-100) into the two byte values that need to be passed. At the moment it's quite crude - it's not helped that the 0-1 range that HA uses does not map in a linear way to the AVR volume (using the DLNA media player entity):

0.20 - 0.90 maps in 0.5dB increments from -45dB to -10dB (0.20 = -45.0dB, 0.21 = -44.5dB, etc) 0.07 - 0.19 and 0.91 - 1.00 maps in 1dB increments (0.07 = -58dB, 0.08 = -57dB, and 1.0 = 0.0dB) < 0.07 is all over the place! (0.06 = -60dB, 0.05 = -63dB, 0.04 = -67dB, 0.03 = -72dB)

For now, -45dB to -10dB is fine for the volumes I use (-10dB is quite loud!), so I'm just keeping within that range. I was going to think about how the range should map - I'm assuming it should keep in line with what the DLNA media_player entity uses? I.e., if I set volume with 10% using this integration, I should expect the DLNA integration to report 0.1? The alternative is to simply map 0 to 100 to -92 to 23, but I'm not sure what the implications of that are. Did that make any sense? :-)

Anyway, here's what I've got right now, though it needs more testing and tidying up which I hope to look at this weekend some time:

            # Volume Model with float

            # STR-DA5800ES linear volume range is 20% (-45dB) to 90% (-10dB)
            # Fortunately, these are outside the range we need, so just coerce to those

            if vol < 20:
                avr_volume = -45.0
            else:
                if vol > 90:
                    avr_volume = -10
                else:
                    avr_volume = ((vol / 100) * 50) - 55

            cmd = bytearray(
                [
                    0x02,
                    0x06,
                    0xA0,
                    0x52,
                    0x00,
                    0x01,
                    int(avr_volume) if avr_volume > 0 else int(avr_volume) + 256,
                    0x80 if avr_volume % 1 > 0.1 else 0x00,
                    0x00,
                ]
            )
peteS-UK commented 2 weeks ago

That's good news 👍 Do we need to worry about the linearity though? So long as we convert it back the same way we convert it out, isn't the likely to be close enough? Right now, I just convert it as the percentage of the volume range - i.e. the current volume's relative percentage in between the max volume and min volume? Can't we just use that to convert back? Or, am I missing something? If the > 90 is too loud, you can adjust the maximum volume as an option on the configuration - i.e. once you've done the config, click on the three dots next to it and you should be able to change the max volume there.

peteS-UK commented 2 weeks ago

So, in media_player.py, line 248 - remove the int()

    async def async_set_volume_level(self, volume: float) -> None:
        _vol = (volume * self._device.volume_range) + self._device.volume_min
        await self._device.async_volume_set(_vol)

then in sonyavr

            # Volume Model with float
            # convert vol to closest .0 or .5
            vol = round(float(vol) * 2) / 2

            _vol = int(vol) if int(vol) > 0 else int(vol) + 256
            cmd = bytearray(
                [
                    0x02,
                    0x06,
                    0xA0,
                    0x52,
                    0x00,
                    0x01,
                    min(int(_vol), self.state_service.volume_max),
                    0x80 if (vol % 1) == 0.5 else 0x00,
                    0x00,
                ]
            )

Can't test it of course, but think that may be OK.

mrfarrow72 commented 2 weeks ago

Yes, I think you're right, and that's basically where I got to before I figured I should be doing something else on a Friday night! My volume range is 115, so the easiest thing to do I think is just decide where I'd like 0 to be, and then have a 0-100 range from there. While testing I've had two media players on the same dashboard, and I think I got hung up on trying to make them show the same volume number. Of course, ideally I'd rather see the dB level anyway, but that's for another day.

I think as an "issue" this is closed - all commands are working on my receiver now. Everything else is tweaks. I'm pleased to hear someone else is getting value from this.

peteS-UK commented 2 weeks ago

Have a look at the sensor - it should show the db value - let me know if it doesn't work. I'll just push a new release - 1.2.1 - could you try installing it and make sure it works OK for the set volume with those changes I just made.

mrfarrow72 commented 2 weeks ago

Yes, that sensor is working just fine - that'll be useful!. I just realised my maths don't work - my range is actually 230 (I forgot the 0.5 increments). I'm not that fussy about the volume though (-12.0 and -12.5 sound the same), so I could still map percent levels to whole dB increments and just ignore the 0.5 steps. I'll take a look later.

peteS-UK commented 2 weeks ago

I just posted 1.2.1 - give it a try as I jsut reverse the calc we already did the other way.

mrfarrow72 commented 2 weeks ago

Still looking good, though set volume was causing a problem - mostly down to the order of things. The check for max volume was done after the +256 calculation, which forced everything back to volume_max. That constant is a float, so it was trying to create a byte array with a float which caused an exception. I've done the max check first (and also added in a min check since my volumes don't start at 0). Also, int() seemed to be rounding up, so I've forced it to round down (asking for -15.5 actually set -14.5). Here's what I've got:

            # Volume Model with float
            # convert vol to closest .0 or .5
            vol = round(float(vol) * 2) / 2
            vol = max(min(vol, self.state_service.volume_max), self.state_service.volume_min)

            cmd = bytearray(
                [
                    0x02,
                    0x06,
                    0xA0,
                    0x52,
                    0x00,
                    0x01,
                    math.floor(vol) if vol > 0 else math.floor(vol) + 256,
                    0x80 if (vol % 1) == 0.5 else 0x00,
                    0x00,
                ]
            )

Another change I made was to the constants to force a volume range of 100. This means that the media_player volume controls work better when they go through the async_set_volume_level method, as it means each percentage point corresponds to a whole number volume level. If I don't do that, -17.0, -16.5 and -16.0 all equate to 66% (and there are others), which makes the sliders feel a bit odd.

STR_DA5800ES_MAX_VOLUME = 20.0      # real max is 23
STR_DA5800ES_MIN_VOLUME = -80.0     # real min is -93
STR_DA5800ES_LOW_VOLUME = -40.0
STR_DA5800ES_VOLUME_STEP = 0.5
peteS-UK commented 2 weeks ago

Thanks for the testing - sorry about the order - that's the problem when I can't test it ....

I've corrected the order and adjusted the constants - I know what you mean about the steps being off as annoying - my main avr's range is 10 to -45, so I get the same problems.

The only question I have is around math.floor vs. int. In python, print (int(-15.9)) is -15 not -16. Is this something funny with the .5? Does 15.0 give you 14 or 15?

mrfarrow72 commented 2 weeks ago

Just done a quick test - int() is behaving as we would expect, it's actually another quirk of the AVR (of course). There are two volume bytes, and the second appears to be a "+0.5" type flag. If that byte is 0x80, then add 0.5 to the value in the first byte. These are the volume values I mapped across the zero boundary:

254 0   -2.0
254 128 -1.5
255 0   -1.0
255 128 -0.5
0   0    0.0
0   128  0.5
1   0    1.0
1   128  1.5

So for "-1.5", the first byte actually needs to be the same value as "2.0", to which 0.5 is then added. Now I know that, there are other ways to solve that rather than bringing in math.floor().

peteS-UK commented 2 weeks ago

That's what I half suspected when I asked about the behaviour with .0. Would it be as simple as adding 1 to the 6th byte if (vol % 1) == 0.5

mrfarrow72 commented 2 weeks ago

Yes, that was the first alternative that came to my mind.

peteS-UK commented 2 weeks ago

although likely only when the volume is -ve, so I likely mean subtract 1, not add 1... Let me know if you work it out, and I'll make the tweaks and post a 1.2.2 version.

peteS-UK commented 2 weeks ago

can we just change it to

            _vol = (
                int(_vol)
                if _vol > 0
                else int(_vol) + 256 - (1 if (vol % 1) == 0.5 else 0)
            )
mrfarrow72 commented 2 weeks ago

Yes, that would also work

peteS-UK commented 2 weeks ago

OK - I'll post 1.2.2 and then fingers crossed, we could be all done

peteS-UK commented 2 weeks ago

ok - posted 1.2.2. If you could test it when you get a chance, we can hopefully close this.

Actually, just posted 1.2.3 - same as 1.2.2 but have begun the process of publishing it to HACS, which wants a new version after their test scripts are run.