BeoLink (Bang & Olufsen)

Hi all,

Unfortunately I don’t have the skills to create a new component, so I’m hoping that someone can help me create a Media Player component for Bang & Olufsen speakers (and TV’s).

I can pretty much do everything with my products today from HA, via REST, but I have to create every single switch, or script depending on what I want to do, from scratch.

Auto-detection of players is not strictly necessary, manually entered up addresses will work just fine. But it would be nice to be able to have multiple devices installed.

Hope someone with a Bang and Olufsen-speaker is willing to help :slight_smile:

Thanks!

Best regards CMDK

Hi Christian,

I’d be very much interested in as well. I’ve tried to listen to communication between my speakers and the B&O app to get some API commands. But same as you, have made separate switches and sensors to control them.

Best,
Marton

I once have done the implementation for older Philips TVs, which can be controlled via HTTP requests as well. Maybe having a look at the module that contains the logic (https://github.com/danielperna84/ha-philipsjs) and the HASS-component (https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/media_player/philips_js.py) helps you getting started.

2 Likes

Thank you! It’s a good starting point. I’m not a big coder, but managed to write some small things, so I’ll study this and hopefully will figure it out! :slight_smile:

So I had some time to actually experiment with this. I started listening to the communication between the B&O app and the speaker and so far I have found these:

  • the speaker sends a JSON stream on: http://<ip_address>:8080/BeoNotify/Notifications
  • the volume can be controlled with: curl -H "Content-Type: application/json" -X PUT -d '{"level":'${volume_level}'}' http://192.168.0.50:8080/BeoZone/Zone/Sound/Volume/Speaker/Level
  • to play the song/radio/spotify that has been paused: curl -H "Content-Type: application/json" -X POST http://192.168.0.50:8080/BeoZone/Zone/Stream/Play curl -H "Content-Type: application/json" -X POST http://192.168.0.50:8080/BeoZone/Zone/Stream/Play/Release
  • to pause the song/radio/spotify that has been playing: curl -H "Content-Type: application/json" -X POST http://192.168.0.50:8080/BeoZone/Zone/Stream/Pause curl -H "Content-Type: application/json" -X POST http://192.168.0.50:8080/BeoZone/Zone/Stream/Pause/Release
  • to turn off the speaker (standby mode): curl -H "Content-Type: application/json" -X PUT -d '{"standby":{"powerState":"standby"}}' http://192.168.0.50:8080/BeoDevice/powerManagement/standby

Now just have to make it into a component. I have close to none experience with python, but I hope I can put something together and test it.

I’ll listen to more app-spekaer communication to figure out how the multiroom thing works. That would be the most interesting part of this project for me :slight_smile:

Hi @CMDK ,

I’ve managed to create a basic python library for basic functions. It’s not a component yet, but on the way.
If you want to try it out create 2 files:

beoplay.py

import requests
import json
import logging

LOG = logging.getLogger(__name__)
BASE_URL = 'http://{0}:8080/{1}'
TIMEOUT = 5.0
CONNFAILCOUNT = 5

class BeoPlay(object):
    def __init__(self, host):
        self._host = host
        self._name = None
        self._connfail = 0
        self.on = None
        self.min_volume = None
        self.max_volume = None
        self.volume = None
        self.muted = None
        self.source = None
        self.state = None

    def updateStream(self):
        try:
            if self._connfail:
                LOG.debug("Connfail: %i", self._connfail)
                self._connfail -= 1
                return None
            r = requests.get(BASE_URL.format(self._host, 'BeoNotify/Notifications'), stream = True)
            if (r.status_code != 200):
                return None
            for data_raw in r.iter_lines():
                if data_raw:
                    data = json.loads(data_raw)
                    # get volume
                    self.getVolume(data)
                    # get source
                    self.getSource(data)
                    # get state
                    self.getState(data)
                    # get standby state
                    self.getStandby(data)
        except requests.exceptions.RequestException as err:
            LOG.debug("Exception: %s", str(err))
            self._connfail = CONNFAILCOUNT
            return None

    def _postReq(self, type, path, data):
        try:
            if self._connfail:
                LOG.debug("Connfail: %i", self._connfail)
                self._connfail -= 1
                return False
            print json.dumps(data)
            if type == "PUT":
                r = requests.put(BASE_URL.format(self._host, path), data=json.dumps(data), timeout=TIMEOUT)
            if type == "POST":
                r = requests.post(BASE_URL.format(self._host, path), timeout=TIMEOUT)
            if r.status_code == 200:
                return True
            else:
                return False
        except requests.exceptions.RequestException as err:
            LOG.debug("Exception: %s", str(err))
            self._connfail = CONNFAILCOUNT
            return False

    def getVolume(self, data):
        if data["notification"]["type"] == "VOLUME" and data["notification"]["data"]:
            self.volume = int(data["notification"]["data"]["speaker"]["level"])
            print self.volume

    def getSource(self, data):
        if data["notification"]["type"] == "SOURCE" and data["notification"]["data"]:
            self.source = data["notification"]["data"]["primaryExperience"]["source"]["sourceType"]["type"]
            self.on = True
            print self.source

    def getState(self, data):
        if data["notification"]["type"] == "PROGRESS_INFORMATION" and data["notification"]["data"]:
            self.state = data["notification"]["data"]["state"]
            self.on = True
            print self.state

    def getStandby(self, data):
        if data["notification"]["type"] == "SHUTDOWN" and data["notification"]["data"]:
            if data["notification"]["data"]["reason"] == "standby" or data["notification"]["data"]["reason"] == "allStandby":
                self.on = False
                print self.on

    def setVolume(self, volume):
        if self._postReq('PUT','BeoZone/Zone/Sound/Volume/Speaker/Level', {'level':volume}):
            self.volume = volume

    def Play(self):
        if self._postReq('POST','BeoZone/Zone/Stream/Play',''):
            self.state = "play"

    def Pause(self):
        if self._postReq('POST','BeoZone/Zone/Stream/Pause',''):
            self.state = "pause"

    def Standby(self):
        if self._postReq('PUT','BeoDevice/powerManagement/standby', {"standby":{"powerState":"standby"}}):
            self.on = False

    def turnOn(self):
        if self._postReq('PUT','BeoDevice/powerManagement/standby', {"standby":{"powerState":"on"}}):
            self.on = True

and
beoplay_test.py
replace the ip address with your speaker’s

import beoplay as beoplay

beoplayapi = beoplay.BeoPlay("192.168.0.20")
beoplayapi.updateStream()

Now if you start playing music it should show you the volume, source, state and if you put the speaker to sleep, the standby mode.

Let me know if it works. I’ll try to understand how components work and try to make one.

Big thanks for @danielperna84 for the guide on how this should be built!

Marton

I hope someone can help me out with some knowledge on creating components.

What I have managed to do:

  • create a small library that works. It gets the json stream and extracts some info: volume, state, source, standby. Also made some functions to adjust the volume, play/pause the current track, and turn on/off the speaker. A very minimal but working library.

However I am having trouble connecting it to a component. I managed to create a media player component that shows up in home-assistant and the turn off functionality is working, but I cannot figure out how to do the updating part. And my problem is that the update function will not stop, unless there’s an error, because the json stream is continuous and the updates are keep coming.

How do I do the update call from the media player component side? If I call the json stream update function, it will keep sending me the update took longer than, etc, messages and never update.

I tried to look at other components and I have a guess that is has something to do with asyncio, but I have no experience with this.

I’d greatly appreciate any pointers in the right direction. I’d love to get this component up and running.

Thanks,
Marton

2 Likes

Hi there,

How far did you end up getting with this? I haven’t found any B&O components at all unfortunately.

I have a Beoplay A9 and Beoplay M3 speaker, that I would really like to get better integrated. I can partly get it working with AirPlay, but it is not too good.

For some reason, playing from my Plex server in the Bang & Olufsen app, does not make the plex show up in HA.

Hi pandaym,

Actually marton was extremely helpful in helping creating a component for Bang & Olufsen products, but it has not reached full functionality yet. There is no source selection, but you can see what is playing, select previous and next and pause and play, adjust volume, and put to standby. There is something about the long polling that is not working 100%, but the state will update within 5 secs. Unfortunately marton had to leave the component behind bacause of personal reasons, which is completely ok. But I can give you the instructions for installing tonight, if you want to give it a whirl. This is also an open invitation to anybody who would like to help developing on this component :slight_smile: :slight_smile: :slight_smile:

1 Like

Hi pandaym,

I haven’t worked on it since a long time, because the lack of time. But it’s still on my todo list to finish it up :slight_smile:

If you want to try it here’s what you need to do:

1. Create a folder under you home assistant folder like this:
/homeassistant/custom_packages

copy this file here, but leave the zip, do not unzip.

This is the python library that the component uses. It has not been published to pypi yet, therefore the “hack”.

2. Copy the custom component to your custom components folder:
/homeassistant/custom_components/media_player

Open this file and find this line:
REQUIREMENTS = ['file:///home/homeassistant/.homeassistant/custom_packages/beoplay.zip#beoplay==0.0.5']

Change the path to the library package file. Leave the # and other things at the end.

3. Set up configuration.yaml file:

media_player:
  - platform: beoplay
    host: 192.168.100.22
    name: Living room M3

Restart home assistant and it should work :slight_smile:

As @CMDK said it’s not done but functional. I have the following things on my todo list to fix:

  • remove image, title, etc. from previous play when airplay is the soruce
  • fix title to say the title instead of “play”
  • add mute function
  • add source selection
  • multiroom support
  • device type support: tv, speaker, etc.

It looks like I’ll have time next month to actually pick up on this project again and would like to do so as I have B&O speakers around and would love to use the multiroom functionality with home assistant.

If you guys have any ideas how to solve things or new features, please feel free to add it!

Best,
Marton

2 Likes

Hi marton, good to hear from you!

  • Mute function is a fairly simple command, but of course needs to be coded to be a toggle function.

    PUT :8080/BeoZone/Zone/Sound/Volume/Speaker/Muted
    Content-Type: application/json

Body:

{
    "muted": true
}
  • Available sources could probably be fetched automatically, but personally I would not mind having to enter the sources I had on each product in the configuration.yaml
    indent preformatted text by 4 spaces

Example:

media_player:
  - platform: beoplay
    host: 192.168.100.22
    name: Living room M3
    sources:
      tunein 
      deezer

etc.

  • Source change can be done this way:
    POST /BeoZone/Zone/ActiveSources
    Content-Type: application/json

{ "primaryExperience": {"source": {"id": "<sourcename>:<type>.<itemnumber>.<serialnumber>@products.bang-olufsen.com"} } }

type+itemnumber+serialnumber is also called the JID.

  • I can’t think of a good way to implement multiroom, but I hope you figure it out :wink:
1 Like

Thank you very much for your answers to both of you. Really appreciate a community like this!

My case is that I currently have to add a speaker to my bathroom to play radio when my hue motion sensor out there detects motion. This is my first “project” in Home Assistant that I only recently discovered and started learning about.

Can you guys help me make the right choice of speaker here?

I was weighing between primarily Beoplay M3(based on personal preference and my current setup and the SONOS One(based on assumed best home automation integration.

I decided to buy a SONOS One, as I could always return it within a 30-day period. Also, Beoplay integration vs SONOS integration to HA, based on quick research, made it seemed easier to get to where I want.

I have been testing functionality and differences related to Home Assistant and the rest of my setup(Plex etc) between the SONOS One and Beoplay A9 Mark 2, and I am currently considering returning it for a Beoplay M3. I am just not sure about whether the observations I have done is the whole truth, or if my limited knowledge thus far is affecting my decision.

My experiences, related to actual usage cases are:

SONOS:

-Home Assistant picks up anything I play from the SONOS app with metadata and controls it nicely

-Chromecast audio is not supported

-AirPlay support is there, but seems to be acting up and slow - had problems with my lock screen controls on iPhone

-Lock screen controls when using the SONOS app are not available when there are airplay devices on the network

-Plex in the SONOS app is supported, but I haven’t gotten it to work, cause I do not have remote access enabled. I never needed external access, so my network isn’t really set up for it security wise yet, so haven’t tested

-Voice assistant is not available in my country yet, and basically no ETA anywhere

-Lock screen controls are not working

Beoplay A9

-Hardly works at all with Home Assistant, I managed to get it to start playing something by sending it a long command that took me ages to figure out

-No statuses or anything showing when playing in Home Assistant

-Works great for airplaying and chromecast audio

-Lock screen controls work great

-The Bang & Olufsen app let me access my Plex library through DNLA, however using that does not trigger my Plex component to show anything in HA, and I see nothing in Plex activity monitor

So I need the speaker to be easy to use for my wife, and this not only work with automations. I feel like the missing lock screen controls in SONOS might be a deal breaker for this. I know I can disable airplay on my other devices to make this work, but that would probably not make too much sense.

In the end, I need to make sure I get a speaker that can provide everything I need in my future setup. Also I would prefer B&O if that would handle it.

What would you guys do? SONOS One? Beoplay M3? Something else?

My further setup:

Living room speakers: Beolab 14 Atmos setup driven by a Denon receiver.

Living room media player: A NVIDIA Shield to play my media in KODI, using Plexkodiconnect to base my KODI off of my Plex server.

Synology NAS Plex + Home Assistant server

iPhones and iPads for anything mobile

@CMDK and @marton are any of you guys on the Discord server or something? I’d like to be able to chat to you, if you’re up for it. :slight_smile:

hey @pandaym

I have never used SONOS, just B&O and chromecasts. So I can only talk about that side of my experiences.

I have 3 B&O speakers and I love them. They work and I think they sound good. I was using the chromecast part of them to stream music, but when I started working on the beoplay component, I realised that their full functionality could be used, just need time to figure out how they work and code it (I’m not a coder, so my process is trial and error and slow :slight_smile: )

I almost have a similar setup to you. NAS DLNA, Apple TV, Playstation and the three speakers. One of them is an S8 hooked up to the projector.

I’m on discord, hanging around in the homeassistant server under the name “Marton” :slight_smile: Feel free to write anytime, but I might not be able to respond immediately :slight_smile:

Multiroom is pretty straight forward - you just tell the other speakers to play the same source (ActiveSource), and they’ll figure it out :slight_smile:

E.g. POST {“activeSources”:{“primary":"spotify:[email protected]”} to /BeoZone/Zone/ActiveSources on two different speakers and they’ll play Spotify in tandem.

I ended up implementing integration between Spotify Connect, BeoPlay and Philips Hue in NodeRed before I came across Home-Assistant. Cumbersome :expressionless:

Under some of the endpoints, you can query ?list=recursive+features to get a more exhaustive (and over time expanding) list of features and endpoints deeper down…

I can’t find a way to determine if the speaker is actually plying something other than by querying the JSON stream which I’d prefer not to (messier than the single message endpoints) - any clues?

Anyone know what /BeoZone/Zone/Light is for, BTW?
POSTing an empty JSON to it results in {“error”:{“type”:“INVALID_ARGUMENT”,“message”:“LightControl::Key type “” not known”}} - and I’m a bit nervous just poking at random :wink:

Hi @rmariboe,

Yep it’s not so much that it’s difficult to do the multiroom part, I was more thinking about how to implement it in a logical and useful way in the UI. :slight_smile:

I use it myself to distribute the sound from my old turntable to other rooms with the push of a button :slight_smile:

The light commands are some you use in relation to the Beolink gateway (or beoliving intelligence), and you need to enable ‘redirection of light commands’ in all individual product. Not sure if you can get anything out of it, but you won’t break anything by playing with it :slight_smile:

To get the state of a speaker, try using Martons custom component :slight_smile:

1 Like

Thanks @rmariboe! I’ll try to implement the multiroom this way.

As far as I know it’s only the JSON stream that tells you what is playing. I couldn’t find another way. That’s how I got it implemented in the component.

1 Like

In terms of UI for multiroom, maybe something like this is exactly what we’re missing :smiley:

This looks cool :slight_smile: Haven’t really looked at lovelace yet, but I’ll give it a go.

Hey guys,

I had some time today to look at the component again. I’ve fixed the Title not showing and also added the mute functionality :slight_smile:

I want to fix some things regarding album art, then I’ll upload a new version for you to play with.

Best,
Marton

1 Like