B5r1oJ0A9G / teufel_raumfeld

Integration for Teufel smart speaker (aka Raumfeld Multiroom) into https://www.home-assistant.io/.
GNU General Public License v3.0
30 stars 6 forks source link

Integration not working while using Spotify Connect #29

Closed c-mellueh closed 2 years ago

c-mellueh commented 2 years ago

As soon as i use my Raumfeld speakers with spotify connect the homeassistant integration stops working. I can't control volume or start/stop songs and also the media card just turns blank: grafik

B5r1oJ0A9G commented 2 years ago

Are you using Spotify in SingleRoom mode or in MultiRoom mode`.

PS: I cannot test Spotify and therefore the integration works with Spotify only to that extend it compatible to the way it is working with Tidal. Some modifications have been made to better support Spotify but these were completely driven by user feedback - what makes development on my end a bit challenging.

c-mellueh commented 2 years ago

Hi, i am using Spotify in SingleRoom mode. I allready noticed your Spotify "problem". I would like to help but do not fully understand how UPNP and DLNA are working. If you have some sources that i can read to understand the underlying tech a bit more i would be really happy.

As far as i understand there seams to be some problems with RuntimeErrors:

If I try

>>> host = "xxx.xxx.xxx.xx"
>>> import hassfeld
>>> rf = hassfeld.RaumfeldHost(host)
>>> rf.start_update_thread()
>>> rf.get_rooms()
['Bad', 'Schlafzimmer', 'Wohnzimmer', 'Kueche']
>>> rf.get_zones()
[]
>>> rf.get_room_volume("Schlafzimmer")

I get: Unexpected error with async_get_volume: <class 'RuntimeError'>Timeout context manager should be used inside a task

B5r1oJ0A9G commented 2 years ago

The current implementation of asyncio in hassfeld breaks the non-asyncio usage: https://github.com/B5r1oJ0A9G/hassfeld/issues/3. I was not able yet to find a suitable implementation that works for both scenarios - asyncio and non-asyncio.

So, to use hassfeld you have to do it currently in an asynchronous manner:

import asyncio
import aiohttp
import hassfeld

async def main():
    host = "teufel-host.example.com"
    port = 47365
    session = aiohttp.ClientSession()
    raumfeld = hassfeld.RaumfeldHost(host, port, session=session)

    asyncio.create_task(raumfeld.async_update_all(session))
    await raumfeld.async_wait_initial_update()

    zone = ["Master Bedroom"]

    media_info = await raumfeld.async_get_media_info(zone)
    print(f"Media info: {media_info}")

    await session.close()

asyncio.run(main())

In regards to informational sources concernign UPnP and more specifically DLNA I don't have the links in my cache anymore. There is plenty information in the Internet though. However, the most important source of information for me was a German forum thread. There I learned 90% about what is needed to develop an application to interface with Teufel/Raumfeld multi-room speakers: HIFI-FORUM - Raumfeld - 3rd-Party Entwickler

c-mellueh commented 2 years ago

I found the Issue. The constant SPOTIFY_ACTIVE in in hassfeld constants.py needs to be lower case. I opened a pull request.

B5r1oJ0A9G commented 2 years ago

Well done! I just created new releases from hassfeld and teufel_raumfeld. Thanks again for your contribution!

c-mellueh commented 2 years ago

ok, one problem solved, some new ones found: async_get_media_info still doesn't work, i kinda know why: there are two Problems:

  1. If we use spotifyconnect in singleroom mode, we need to work with the Mediarenderer of the speaker, because no one is created for the zone (because no zone exists) for debugging i can overwrite this (2nd problem is far more critical)

  2. if we look at the avtransport.xml:

    avtransport.xml
<?xml version="1.0"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <actionList>
    <action>
      <name>SetAVTransportURI</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>CurrentURI</name>
          <direction>in</direction>
          <relatedStateVariable>AVTransportURI</relatedStateVariable>
        </argument>
        <argument>
          <name>CurrentURIMetaData</name>
          <direction>in</direction>
          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>SetNextAVTransportURI</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>NextURI</name>
          <direction>in</direction>
          <relatedStateVariable>AVTransportURI</relatedStateVariable>
        </argument>
        <argument>
          <name>NextURIMetaData</name>
          <direction>in</direction>
          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
          </argument>
      </argumentList>
    </action>
    <action>
      <name>SetNextStartTriggerTime</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>TimeService</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_WallClockService</relatedStateVariable>
        </argument>
        <argument>
          <name>StartTime</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_WallClockTime</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetPositionInfo</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>TrackDuration</name>
          <direction>out</direction>
          <relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
        </argument>
        <argument>
          <name>RelTime</name>
          <direction>out</direction>
          <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetTransportInfo</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>CurrentTransportState</name>
          <direction>out</direction>
          <relatedStateVariable>TransportState</relatedStateVariable>
        </argument>
        <argument>
          <name>CurrentTransportStatus</name>
          <direction>out</direction>
          <relatedStateVariable>TransportStatus</relatedStateVariable>
        </argument>
        <argument>
          <name>CurrentSpeed</name>
          <direction>out</direction>
          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetTransportSettings</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>PlayMode</name>
          <direction>out</direction>
          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Stop</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Rewind</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>Position</name>
          <direction>out</direction>
          <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>FastForward</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>Position</name>
          <direction>out</direction>
          <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Pause</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Play</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>Speed</name>
          <direction>in</direction>
          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Next</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Previous</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>Seek</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>Unit</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
        </argument>
        <argument>
          <name>Target</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>SetPlayMode</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>NewPlayMode</name>
          <direction>in</direction>
          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>EnterManualStandby</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>EnterAutomaticStandby</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>LeaveStandby</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetSpotifyPreset</name>
      <argumentList>
        <argument>
          <name>InstanceID</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
        </argument>
        <argument>
          <name>Button</name>
          <direction>in</direction>
          <relatedStateVariable>A_ARG_TYPE_ButtonNumber</relatedStateVariable>
        </argument>
        <argument>
          <name>Preset</name>
          <direction>out</direction>
          <relatedStateVariable>AVTransportURI</relatedStateVariable>
        </argument>
        <argument>
          <name>Metadata</name>
          <direction>out</direction>
          <relatedStateVariable>A_ARG_TYPE_JsonObject</relatedStateVariable>
        </argument>
      </argumentList>
    </action>
  </actionList>
  <serviceStateTable>
    <stateVariable sendEvents="no">
      <name>TransportState</name>
      <dataType>string</dataType>
      <allowedValueList>
        <allowedValue>STOPPED</allowedValue>
        <allowedValue>PLAYING</allowedValue>
        <allowedValue>TRANSITIONING</allowedValue>
        <allowedValue>NO_MEDIA_PRESENT</allowedValue>
      </allowedValueList>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>TransportStatus</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>AVTransportURI</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>AVTransportURIMetaData</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>CurrentPlayMode</name>
      <dataType>string</dataType>
      <allowedValueList>
        <allowedValue>NORMAL</allowedValue>
        <allowedValue>SHUFFLE</allowedValue>
        <allowedValue>REPEAT_ALL</allowedValue>
        <allowedValue>REPEAT_ONE</allowedValue>
        <allowedValue>RANDOM</allowedValue>
      </allowedValueList>
      <defaultValue>NORMAL</defaultValue>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>CurrentTrackDuration</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>PowerState</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>RelativeTimePosition</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="yes">
      <name>BufferFilled</name>
      <dataType>ui4</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>OwnsAudioResource</name>
      <dataType>boolean</dataType>
    </stateVariable>
    <stateVariable sendEvents="yes">
      <name>LastChange</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_InstanceID</name>
      <dataType>ui4</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_SeekMode</name>
      <dataType>string</dataType>
      <allowedValueList>
        <allowedValue>ABS_TIME</allowedValue>
      </allowedValueList>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_SeekTarget</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>TransportPlaySpeed</name>
      <dataType>string</dataType>
      <allowedValueList>
        <allowedValue>1</allowedValue>
      </allowedValueList>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_WallClockTime</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_WallClockService</name>
      <dataType>string</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_Boolean</name>
      <dataType>boolean</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_ButtonNumber</name>
      <dataType>ui4</dataType>
    </stateVariable>
    <stateVariable sendEvents="no">
      <name>A_ARG_TYPE_JsonObject</name>
      <dataType>string</dataType>
    </stateVariable>
  </serviceStateTable>
</scpd>

there is no 'GetMediaInfo', i think we need to use 'GetSpotifyPreset'

c-mellueh commented 2 years ago

Ok i rewrote async def async_get_media_info(self, zone_room_lst): for debugging:

 async def async_get_media_info(self, zone_room_lst):
        """Get media information of zone."""
        room_loc = "http://192.168.178.28:58435/8bb40e1d-8bcb-4636-b062-9f7f7a11e8be.xml"
        return await upnp.async_get_spotify_preset(self._aiohttp_session,room_loc)

and upnp.async_get_spotify_preset:

async def async_get_spotify_preset(session,location,instance_id=0,button=1):
    """Return media information."""
    action_name = "GetSpotifyPreset"
    upnp_action = await get_dlna_action(
        location, SERVICE_AV_TRANSPORT, action_name, session=session
    )
    response = await upnp_action.async_call(InstanceID=instance_id, Button=button)
    response["Metadata"] = upnp_action.argument(
        "Metadata"
    ).raw_upnp_value
    return response

with this i get {'Preset': 'spotify-preset://?blob=CIGA%2FP%2F%2F%2F%2F%2F%2F%2FwESJHNwb3RpZnk6YWxidW06MU4yMUpIUGw4WVVCZ0lTR2ZZNFNXYxoECAAQASIGCAEQABgAKiRzcG90aWZ5OnRyYWNrOjFpSmZ5NDRIRUs3eFVuNzBHSFFkcVM%3D&pos=0', 'Metadata': '{"active-user":"","spotify-uri":"spotify:album:1N21JHPl8YUBgISGfY4SWc","title":"Die Jungs von AKJ"}'} as response. what confuses me: the title is not the song title but the album title. I don't know how to get the song title.

B5r1oJ0A9G commented 2 years ago

If I recall correctly, then preset is referring to physical buttons on the devices to play a pre-configured media. This could also explain why the metadata is limited to what has to be displayed in the configuration settings for a preset button, e.g. if it is an album the name of the album.

Btw, can you confirm that this bug can be closed? For other aspects not working properly or being missing, don't hesitate to create additional tickets. For general discussion I'd propose to move over to the Discussions area.