Elixir Sonos Controller: Sonex - WIP

Hi Folks!
I am slowly working on a Home Automation controller similar to OpenHAB or HomeAssistant, but built with Elixir!

One feature I am looking to replicate is the ability to control different types of Components, or Things. Here are the compatible HomeAssistant components. A pretty large list and many client/controller libraries for off the shelf home automation products do not exist for Elixir!

Sonos, the most popular wireless speakers system, a common component for home automation controllers, and something I happen to have on hand for example. Libraries/tools exist to control a Sonos system from Python and Javascript, but nothing for Elixir…

To get a better idea of how I want to build common clients to be controlled via the large umbrella home automation project I have decided to work on a Sonos Controller OTP application that can easily be integrated with other projects. It also happens to be a pretty good project to help learn and understand OTP.

After a few days of messing around I think I have a good enough idea how the Sonos UPNP/SOAP API works, but having a bit of trouble with the implementation.

This is still very much a prototype, but lays the foundation for how I envision the controller functioning.
Sonex Project Repository

The Sonos API is a bit richer than HTTP/REST API…

  • UPNP for auto discovery of Sonos devices on a LAN
  • UPNP subscriptions
  • SOAP/XML WebService API

Auto discovery was easy, have that working fine, simple POST requests to control a Sonos Zone e.g. Play, Pause, Volume is working fine.

My concerns are surrounding the subscriptions…
In order keep track of changes made to any of the Sonos Devices by a different controller, you can subscribe to events. By sending an HTTP SUBSCRIBE request to a specified Sonos player, it will emit events back to you as HTTP POSTS with XML body.

Am using HTTPoison as an HTTP Client for sending the POST/SUBSCRIBE messages, and cowboy as the HTTP Server to handle incoming Subscription events.

I am having problems parsing the Incoming XML sent from the Sonos devices I have subscribed to recieve events from.
Not sure if it is malformed or what. Could be using SweetXML improperly…

After cleaning the HTML encoding this is what the XML looks, like:

<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
 <e:property>
  <LastChange>
  <Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/">
   <InstanceID val="0">
    <TransportState val="STOPPED"/>
    <CurrentPlayMode val="NORMAL"/>
    <CurrentCrossfadeMode val="0"/>
    <NumberOfTracks val="0"/>
    <CurrentTrack val="0"/>
    <CurrentSection val="0"/> 
    <CurrentTrackURI val=""/>
    <CurrentTrackDuration val="0:00:00"/>
    <CurrentTrackMetaData val=""/>
    <r:NextTrackURI val=""/>
    <r:NextTrackMetaData val=""/>
    <r:EnqueuedTransportURI val=""/>
    <r:EnqueuedTransportURIMetaData val=""/>
    <PlaybackStorageMedium val="NONE"/>
    <AVTransportURI val=""/>
    <AVTransportURIMetaData val=""/>
    <NextAVTransportURI val=""/>
    <NextAVTransportURIMetaData val=""/>
    <CurrentTransportActions val="Set, Play, Stop, Pause, Seek, Next, Previous"/>
    <r:CurrentValidPlayModes val=""/>
    <r:MuseSessions val=""/>
    <TransportStatus val="OK"/>
    <r:SleepTimerGeneration val="0"/>
    <r:AlarmRunning val="0"/>
    <r:SnoozeRunning val="0"/>
    <r:RestartPending val="0"/>
    <TransportPlaySpeed val="NOT_IMPLEMENTED"/>
    <CurrentMediaDuration val="NOT_IMPLEMENTED"/>
    <RecordStorageMedium val="NOT_IMPLEMENTED"/>
    <PossiblePlaybackStorageMedia val="NONE, NETWORK"/>
    <PossibleRecordStorageMedia val="NOT_IMPLEMENTED"/>
    <RecordMediumWriteStatus val="NOT_IMPLEMENTED"/>
    <CurrentRecordQualityMode val="NOT_IMPLEMENTED"/>
    <PossibleRecordQualityModes val="NOT_IMPLEMENTED"/>
   </InstanceID>
  </Event>
  </LastChange>
 </e:property>
</e:propertyset>

Anyone have experience with SweetXML that could provide some tips for handling XML incoming over HTTP?

Also looking for contributors who want to help with this or other projects related to the Elixir Home Automation Controller, lots of things are lacking Elixir Client libraries.
I cannot build them all myself :stuck_out_tongue:

Thanks a lot for any tips!
Going to be on here regularly, and also Slack/IRC to engage more with the community, I love Elixir!

7 Likes

Sounds like a nice project! Good luck and keep us posted :slight_smile:

1 Like

Wow, this indeed opens my mind. I’m a user of Sonos and a big fan, used it everyday and honestly, couldn’t live without it. I didn’t know there was a Python/Ruby cli to control it.

This seems a lot of fun to me, and I will spend some time on the repository this weekend.

:heart:

1 Like

Sonos was a bit of a mystery to me before jumping into this project, had no idea how it worked.

Learned a lot from the JavaScript and Python implementations though, just trying to translate what they have done into a more functional Elixir pattern.

Still struggling a bit to parse the incoming XML from subscriptions, if that wasn’t a problem would have it all figured out by now…

Appreciate any suggestions to better structure the project as well, I am an Elixir beginner but eager to learn!

1 Like

@harmon25 Hey, I am just a beginner who just finished reading Dave Thomas’s book and currently reading Benjamin Tan’s. I am looking for things to work on. I would gladly collaborate with you if you are interested.

1 Like

hi @sriki,
Very much interested!
Been making pretty good progress on this Sonos controller over the weekend.

Sorted out most of the XML parsing, it could be more efficient, but is working…

Been refactoring based on the feedback I received from this post.

Following this example manged to implement a process for each Sonos player.
Getting a better hang of OTP with these kinda projects!

If you have sonos would be pretty cool to get some help with Sonex.
Also been working on some Arduino integration if that is up your alley.

1 Like

I don’t have a sonos. Which one do you have? I will try to pick a used one for cheap, if I can. I have some arduinos around. I haven’t played with them in some years. But, i can dust them off. I got your source for sonos from git. I took a quick glance at it. Some of it does look normal but some doesn’t make sense without a sonos (yet, but will once I look at it more). Point me to some items/a direction and I will get started. I might be a bit slow at first but could pick up pace once I get to know more.

Some older Sonos devices might be cheap, they are a bit expensive though if you were buying one just for this project…

Take a look at all the components for HomeAssistant.
As a community we should try to recreate all of that functionality in Elixir!

Did not find a Chromecast Elixir module on hex for example…
here is an one for NodeJS

Should be able to figure out what they have done, and implement it in Elixir pretty easily.

If you are looking for multi-room speaker system, get Sonos, it is awesome!

I don’t use NodeJS. So, using that as a reference might not work for me. But, I will look anyway. I have a amazon firestick/Kodi. So, I could look at that too. I will probably have access to wemo/wink lights too. So, I could potentially look at them too. I will have to read up on Chromecast and these other device’s API (if exists) or how they operate behind the curtain to better understand.

Hi @harmon25,
I’m trying to get Sonex to work with my Sonos Player, but when i try to start the application it says:

=INFO REPORT==== 28-Aug-2016::09:40:53 ===
    application: logger
    exited: stopped
    type: temporary
** (Mix) Could not start application sonex: Sonex.start(:normal, []) returned an error: shutdown: failed to start child: Sonex.Discovery
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, :eaddrnotavail}
            (sonex) lib/sonex/discovery.ex:28: Sonex.Discovery.init/1
            (stdlib) gen_server.erl:328: :gen_server.init_it/6
            (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Can i leave the default value for dlna_listen_addr here or do i have to specify another address?

I’ve not used and don’t use a Sonos thing, but that error implies that it was not able to either bind to or connect to some IP address. Might want to check both of those conditions. :slight_smile:

1 Like

Hi @MathijsK93

@OvermindDL1 is correct.

Will need to change https://github.com/harmon25/sonex/blob/master/config/config.exs#L11 to match the IP of the system you are running Sonex on.

The app is still very much unfinished.
But an interesting little project for people looking to get their feet wet with elixir can play around with!

I would be happy to accept any pull requests if you fix it up :stuck_out_tongue:

3 Likes

Thanks for your answers @OvermindDL1 and @harmon25, it’s working now :slight_smile:

I also created a PR. I just started learning Elixir, so i hope this is the correct way.

1 Like

Heads up. I’m looking at it when I have time but if there are other UPNP services on the network discovery blows up in parse_upnp . Hopefully I have some time tonight or tomorrow to really dig thru it.