N0ciple/hass-kef-connector

Speaker disappears in Home Assistant

Opened this issue · 7 comments

After a short while, the kef speaker disappears from Home Assistant and I'm not sure why.

If I try to call the API directly, it works fine:
http://xx.xx.xx.xx/api/getData?path=player:volume&roles=value&value={%22volume%22:%2225%22}

However, if I try to call a volume change via kef_connector, I get an error in the Home Assistant log:

2024-01-05 18:04:01.730 ERROR (MainThread) [homeassistant.components.media_player] kef_connector: Error on device update!
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 575, in _async_add_entity
    await entity.async_device_update(warning=False)
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 959, in async_device_update
    await self.async_update()
  File "/config/custom_components/kef_connector/media_player.py", line 303, in async_update
    self._attr_media_duration = int(await self._speaker.song_length / 1000)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pykefcontrol/kef_connector.py", line 390, in song_length
    return json_output['status']['duration']
           ~~~~~~~~~~~^^^^^^^^^^
KeyError: 'status'

2024-01-05 18:04:24.868 WARNING (MainThread) [homeassistant.helpers.service] Referenced entities media_player.kef_speaker are missing or not currently available


2024-01-05 18:20:46.218 WARNING (MainThread) [homeassistant.helpers.service] Referenced entities   are missing or not currently available

If I restart the speaker, it works fine. The speaker has a dedicated IP address and calling the REST API directly works as expected. Is there anyway to manually make Home Assistant find the speaker without restarting the speaker?

Hello @gurmukhp,

It looks like there was an issue with the _get_player_data function (called when you query the song_length info).
Could you give me the output you have with the following address ?
http://ip-of-the-speaker/api/getData?path=player:player/data&roles=value
if possible both when your speaker is accessible in home assistant and more importantly when it is not !

Maybe there is a slight API variation with your speaker model (that is LSX II if I am not mistaken ?)

Hello!

Thanks for checking in. I went through your library and constructed the URLs to communicate with the speaker directly, they seem to work, even when the kef HAS integration sometimes doesn't.

I created pyscript like so (it's very hacky LOL):

def get_volume():
    url = "http://192.168.68.77/api/getData?path=player:volume&roles=value&value=%22%22{{%22type%22:%22i32_%22,%22i32_%22:{10}}}%22%22"
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            # log.info(resp.json())
            return resp.json()[0]['i32_']

@service
def volume_up():
    task.unique("kef_volume_up", kill_me=True)
    url = "http://192.168.68.77/api/setData?path=player:volume&roles=value&value={%22type%22:%22i32_%22,%22i32_%22:" + str((get_volume() + 10)) + "}"
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            log.info(resp.text())

@service
def volume_down():
    task.unique("kef_volume_down", kill_me=True)
    url = "http://192.168.68.77/api/setData?path=player:volume&roles=value&value={%22type%22:%22i32_%22,%22i32_%22:" + str((get_volume() - 10)) + "}"
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            log.info(resp.text())

@service
def pause():
    url = 'http://192.168.68.77/api/setData?path=player:player/control&roles=activate&value={%22control%22:%22pause%22}'
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            log.info(resp.text())

@service
def next():
    url = 'http://192.168.68.77/api/setData?path=player:player/control&roles=activate&value={%22control%22:%22next%22}'
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            log.info(resp.text())

I have just got the same issue with my LSX2 speakers, from the core log:

2024-08-15 10:29:44.262 ERROR (MainThread) [homeassistant.components.media_player] kef_connector: Error on device update! Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 727, in _async_add_entity await entity.async_device_update(warning=False) File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1300, in async_device_update await self.async_update() File "/config/custom_components/kef_connector/media_player.py", line 372, in async_update self._attr_media_duration = int(await self._speaker.song_length / 1000) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/pykefcontrol/kef_connector.py", line 732, in song_length return json_output["status"]["duration"] ~~~~~~~~~~~^^^^^^^^^^ KeyError: 'status'

Any news on this?

Note: The speakers have fixed IP, and this has worked before, I simply do no know why. Well I just updated everything (thans HA from changing everything from services to action, and breaking my setup!) so I run 2024.8 with all the trimmings.

Run your requested IP command:

[{"trackRoles":{"mediaData":{"metaData":{"serviceNameOverride":"Casting Default Media Receiver","externalAppName":"Default Media Receiver"}}},"mediaRoles":{"type":"audio","path":"googlecastlite:playlogic/Default Media Receiver","mediaData":{"metaData":{"serviceID":"googlecast","playLogicPath":"googlecastlite:playlogic","live":true}},"title":"Chromecast built-in","icon":"skin:iconGooglecast","doNotTrack":true,"timestamp":1723711437490,"audioType":"audioBroadcast","description":"Chromecast built-in"},"controls":{"pause":true,"next_":true,"playMode":{},"previous":true},"playId":{"timestamp":77548,"systemMemberId":"lsxii-d1489ae4-2421-4bed-a973-9e7ce3b96c84"},"state":"playing"}]

And yes it is playing. I can control my KEF LSX2 speakers over DLNA / Chromecast (seems to say different things in different places, sorry) - but I have nothing in KEF Connector, it is just empty, and the definition in configuration.yaml is straight forward:

media_player:

  • platform: kef_connector
    host: 10.168.5.21
    name: "My Kef LSX II Speakers"
    maximum_volume: 0.6
    volume_step: 0.02
    speaker_model: LSX2

Sorry for all comments, but I do not get this problem. Here is why: I do not think this is a KEF Connector issue at all. The ONLY change I did was a number of "service:" calls that was replaced by "action:" in automations.yaml - nothing else. After that change, KEF Connector fails. My speakers are hardwired (not wifi), and DHCP - this can be verified by entering the IP adress of my KEF LSX2 speakers, and one have a raw interface to the speakers settings for network and so on. Everything that has changed the last 24h is I got an automatic forced update to 2024.8 (I hate auto updates, it always breaks something in Home Assistant), and that worked with the exception of "service:" need to be changed to "action:" in autmations.yaml (why oh why could not Home Assistant "change all" on this replacement???? Why had it to break my installation?).

If there is something that broke KEF Connector - and do note I have not updated to any new firmware on my KEF speakers - it is HA 2024.8. Something else changed, under all this.

Edit: Yes of course I have reversed the change from service to action in automations.yaml - no change. This is not a change I can reverse, I can not get my KEF speakers into KEF Connector again, restarting HA from cold = no, restart speakers from cold = no, sofar nothing brings them back.

Yes I can partly control if thru Chromecast/DLNA - but change input I can not from what I can see....

I decided to test something, so I removed (commented out) one line, well the line in the error above:

    if self._state == STATE_PLAYING:
        # Update media length
        # self._attr_media_duration = int(await self._speaker.song_length / 1000)
        # Update media position
        self._attr_media_position = int(await self._speaker.song_status / 1000)
        # Update last media position update
        self._attr_media_position_updated_at = dt_util.utcnow()
    else:
        # Set values to None if no media is playing
        self._attr_media_duration = None
        self._attr_media_position = None
        self._attr_media_position_updated_at = None

Now it kind of works, well except for that line that does not get updated - but at least I have a bit of more control over my Speakers, like selecting source...