Squeezebox and Smarthings


(Greg W) #1

Has anyone attempted or have an idea of how to integrate the Logitech Media Server (aka Squeezebox) into Smarthings?

I think this would be beneficial because it would allow people to develop Sonos-type multi-room audio within a budget. Anyone have an idea of where to start with this or experience with this? Thanks.


(Mike Maxwell) #2

also interested, bump!


(Steven Weinstein) #3

This would be great.


(Mike Maxwell) #4

OK, so I’ve managed to do something with this, and admittedly it’s not much.
My squeezebox setup consists of a win xp squeeze server, two duet controllers and 3 duet players.
My goal which I achieved was to simply be able to turn the players on and off so I could include them in my Harmony ultimate home activities.

The on function calls the squeeze “play” function for the selected player, this turns on the player and fires up the last/current playlist all in one call.

At this point I don’t expect to go any further with this.
If you guys are interested in the gory details (one custom device type, one virtual device type and one virtual smartApp supervisor) I can post the code.
If you only have one player to control, a single device is all that’s needed.

The end result from Harmony’s and smartThings POV are three separate switches that control the power to the players.

Since I run the playlist/media selection from the Duets or the smartPhone app, personally I’m not up for a complete Sonus like implementation for squeeze.

Currently this cannot be done via the squeeze CLI interface (which is how my device control works) as smartthings hubactions are not able to return data from non HTTP call methods.

squeeze server supports HTTP calls as well, however these involve parsing through the servers web HTML, which seems like a lot of work to me.


(Steven Weinstein) #5

I’m interested in the code. Please post!! Thank you!


(Mike Maxwell) #6

OK here we go.
First step is to create the device type for your squeeze server:

/**
 *  squeezeSwitch
 *
 *  Copyright 2014 Mike Maxwell
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
//player mac adddresses, for each required player
preferences {
	input("confp1", "string", title:"Enter Player 1 MAC",defaultValue:"00:00:00:00:00:00", required:true, displayDuringSetup:true)
    input("confp2", "string", title:"Enter Player 2 MAC",defaultValue:"00:00:00:00:00:00", required:true, displayDuringSetup:true)
    input("confp3", "string", title:"Enter Player 3 MAC",defaultValue:"00:00:00:00:00:00", required:true, displayDuringSetup:true)
}

metadata {
	definition (name: "squeezeSwitch", namespace: "mmaxwell", author: "Mike Maxwell") {
		capability "Switch"
        //custom commands for multiple players
        //use the standard (built in on/off) if you only have one player
        command "p1On"
        command "p1Off"
        command "p2On"
        command "p2Off"
        command "p3On"
        command "p3Off"
        //enable and use to create the hex version of your squeeze servers CLI interface
        //ip address and port, this will need to be assigned to the "Device Network Id" field
        //after the device is added to your system
        //command "getNid" 
        
	}

	simulator {
		// TODO: define status and reply messages here
	}

	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
        state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
        state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
        }
	}
    main "switch"
    details(["switch"])
}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"

}

private String makeNetworkId(ipaddr, port) { 
	 String hexIp = ipaddr.tokenize('.').collect { 
     String.format('%02X', it.toInteger()) 
     }.join() 
     String hexPort = String.format('%04X', port()) 
     return "${hexIp}:${hexPort}" 
}
def getNID() {
	log.debug makeNetworkId("192.168.1.200", 9000) //your squeeze server CLI interface ip address and port
}

// handle commands for multiple players
def p1On()	{
	//log.debug settings.confp1
    def ha = new physicalgraph.device.HubAction("${settings.confp1} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p1Off()	{
	//log.debug settings.confp1
    def ha = new physicalgraph.device.HubAction("${settings.confp1} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p2On()	{
	//log.debug settings.confp2
    def ha = new physicalgraph.device.HubAction("${settings.confp2} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p2Off()	{
	//log.debug settings.confp2
    def ha = new physicalgraph.device.HubAction("${settings.confp2} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p3On()	{
	//log.debug settings.confp3
    def ha = new physicalgraph.device.HubAction("${settings.confp3} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p3Off()	{
	//log.debug settings.confp3
    def ha = new physicalgraph.device.HubAction("${settings.confp3} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}

// command for one player only
def on() {
	//log.debug "Executing 'on'"
    def ha = new physicalgraph.device.HubAction("${settings.confp1} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def off() {
	//log.debug "Executing 'off'"
    def ha = new physicalgraph.device.HubAction("${settings.confp1} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}

After creating/publishing and adding this device to your system, you will need to assign the servers ip/port and the individual player MAC addresses to the new device:

Create a new virtual switch type and add an instance to your system for each player, these are not needed if you only have one player as this can be controlled directly from the above device. The input field “squeezeBox” is only used as a note to help you remember which player is connected to these virtual switches.

/**
 *  sqVS
 *
 *  Copyright 2014 Mike Maxwell
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
	metadata {
	definition (name: "sqVS", namespace: "mmaxwell", author: "Mike Maxwell") {
		capability "Switch"
	}
    
    preferences {
        input name: "sq", type: "enum", title: "squeezeBox", description: "Squeeze Box to Connect to", required: true
	}

	simulator {
		// TODO: define status and reply messages here
	}

	tiles {
    standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
        state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
        state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
    }

    standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
        state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
    }

    main "switch"
    details(["switch","refresh"])
}

}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
	// TODO: handle 'switch' attribute

}

// handle commands
def on() {
	log.debug "On"
    sendEvent (name: "switch", value: "on")
}

def off() {
	log.debug "Off"
    sendEvent (name: "switch", value: "off")
}

When creating these devices any random/unique ID will do in the device ID field

Next you need a smartApp to function as the controller for all these. This smartapp (derived from @badgermanus 's 8 way relay project) functions as a middle man, subscribing to the on/off events of each virtual switch (representing your palyers), then firing the appropriate custom command against your squeeze device.

/**
 *  squeezeController
 *
 *  Copyright 2014 Mike Maxwell
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "squeezeController",
    namespace: "mmaxwell",
    author: "Mike Maxwell",
    description: "SqueezeBox virtual supervisor.",
    category: "My Apps",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")


/**
 *  VirtualSwitchParent
 *
 *  Author: badgermanus@gmail.com
 *  Date: 2014-03-26
 */
preferences {
	section("Connect these virtual switches to the squeeze players") {
	    input "switch1", title: "family", "capability.switch", required: true
        input "switch2", title: "master", "capability.switch", required: true
        input "switch3", title: "garage", "capability.switch", required: true
      
	}
    section("Which squeeze server?") {
		input "squeeze", "capability.switch"
    }    
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	subscribe()
}

def updated() {
	log.debug "Updated with settings: ${settings}"
	unsubscribe()
	subscribe()
}


def subscribe() {
	subscribe(switch1, "switch.on", p1On)
	subscribe(switch1, "switch.off", p1Off)
    subscribe(switch2, "switch.on", p2On)
	subscribe(switch2, "switch.off", p2Off)
    subscribe(switch3, "switch.on", p3On)
	subscribe(switch3, "switch.off", p3Off)
}

 
def p1On(evt)
{
	log.debug "switchOn1($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p1On()
}

def p1Off(evt)
{
	log.debug "switchOff1($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p1Off()
}
def p2On(evt)
{
	log.debug "switchOn2($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p2On()
}

def p2Off(evt)
{
	log.debug "switchOff2($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p2Off()
}

def p3On(evt)
{
	log.debug "switchOn3($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p3On()
}

def p3Off(evt)
{
	log.debug "switchOff3($evt.name: $evt.value: $evt.deviceId)"
    squeeze.p3Off()
}

It’s a lot of work just to power up/down each player but it does the job.

It should be noted that your squeeze server needs to have the CLI plugin enabled, I don’t know if this is the default or not.


(Steven Weinstein) #7

This is great!! I’m using a few Raspberry PI as players, so I don’t need the power on function. I’m trying find the code that plays the music.

thanks


(Mike Maxwell) #8

[player mac address] play… (this is what I implemented as the on function)
And in the case of hardware players, will turn on the player as well.
This will play what ever was in the last play list.

All the CLI parameters are documented and available via the help files that are part of the base install.
From the web front end, help, then advanced if I recall, then CLI.


#9

Sorry to resurrect an old thread but can anyone assist - I’m having problems setting up my squeezeboxes.

Can someone clarify what this needs to work - I think I’ve set up 3 device handlers - a squeezeController, a squeezeSwitch and an sqVS.

I have then created a new device called squeezeDevice which has the following preferences (but is not in use by anything):

I guess I’m missing something - do I need to create extra devices for each of my players or something?

Thanks in advance.


(Mike Maxwell) #10

I can help, give me a few minutes, I wrote this a long time ago, and barely remember my self… It still works though.


(Mike Maxwell) #11

–sqVS is a virtual switch device, each one represents a separate squeeze player.
These simply represent the on/off state of each player and can be used to turn on or turn off the individual squeeze players from other apps or automations.
–squeezeSwitch is another virtual device, you only need one of these. It represents your squeeze server and contains preference settings for three squeeze players, the mac address for each player needs to be entered into the preferences section of this device, additionally the IP and port of your squeeze server needs to be entered into line 68.
(don’t yell, I wrote this a very very long time ago, and haven’t touched it since.)
–squeezeController is the smartApp that runs all this nonsense.
You select your sqVS devices in the “connect these virtual switches…” section, again there’s three here.
You then select the squeezeSwitch device in the “Which squeeze device” section…
(OMG, I’m embarrassed at how un-intuitive this is…, This was the first app I wrote…)

so Squeeze controller just functions as the glue between each sqVS and the squeezeSwitch, shuttling on off commands back and forth.

In any event, squeezeController and squeezeSwitch were statically built for three players, you can extend should you have more…

The actual device commands implemented (only two, power off, and play) are contained in the squeezeSwitch device.

Hope this helps.


#12

Thanks Mike - very much appreciated.

I’ll give it another whirl tomorrow.

Cheers


(Nathan Block) #13

Does anyone have this working with v2 of the hub? I can telnet to the LMS and send commands there with no problem, but I can’t get it to work with SmartThings. Do the commands come from the local network on the Samsung cloud? Wasn’t sure if I needed to forward port 9090 to that machine or not.

Any help would be greatly appreciated!


(Mike Maxwell) #14

The commands run local between st hub and squeeze server.
I’m on hub v2.


(Nathan Block) #15

Thanks @Mike_Maxwell. I’ll try to delete all of it and start over to see if that fixes it.


(Nathan Block) #16

Looks like the telnet command isn’t getting sent. Not sure why or even where to start to fix it. The debug shows that it’s attempting to send it to the correct hex ip:port, but nothing shows up in the LMS debug log for the command line interface. When I telnet in from a dos prompt it works fine and shows up in the log. Is there some special setting on the hub that needs to be turned on to allow it to send HubAction commands?


(Mike Maxwell) #17

No unfortunately, nothing to turn on at the hub level.
You’re running the code as is at this point, just using the play command?


(Nathan Block) #18

Deleted all of it and started over, still no luck. Changed the port on the server to 9091 and re-added the devices and device handlers, still not working. I’m at a loss for why it’s not working. It appears that the commands are not making it to the server and I have no idea why.


(Mike Maxwell) #19

I’ll have look tonight and see if I can help…


(Nathan Block) #20

Thanks! I installed a packet sniffer and there is nothing coming from the hub to the LAN. The only communication is out to the Smartthings cloud.

Here’s the device handlers I have:

/**
 *  sqVS
 *
 *  Copyright 2014 Mike Maxwell
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
	metadata {
	definition (name: "sqVS", namespace: "mmaxwell", author: "Mike Maxwell") {
		capability "Switch"
	}

    preferences {
        input name: "sq", type: "enum", title: "squeezeBox", description: "Squeeze Box to Connect to", required: true
	}

	simulator {
		// TODO: define status and reply messages here
	}

	tiles {
    standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
        state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
        state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
    }

    standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
        state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
    }

    main "switch"
    details(["switch","refresh"])
}

}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
	// TODO: handle 'switch' attribute

}

// handle commands
def on() {
	log.debug "On"
    sendEvent (name: "switch", value: "on")
}

def off() {
	log.debug "Off"
    sendEvent (name: "switch", value: "off")
}

/**
 *  squeezeSwitch
 *
 *  Copyright 2014 Mike Maxwell
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
//player mac adddresses, for each required player
preferences {
	input("confp1", "string", title:"Enter Player 1 MAC",defaultValue:"00:00:00:00:00:00", required:true, displayDuringSetup:true)
    input("confp2", "string", title:"Enter Player 2 MAC",defaultValue:"00:00:00:00:00:00", required:true, displayDuringSetup:true)
    input("confp3", "string", title:"Enter Player 3 MAC",defaultValue:"00:00:00:00:00:00", required:false, displayDuringSetup:true)
}

metadata {
	definition (name: "squeezeSwitch", namespace: "mmaxwell", author: "Mike Maxwell") {
		capability "Switch"
        //custom commands for multiple players
        //use the standard (built in on/off) if you only have one player
        command "p1On"
        command "p1Off"
        command "p2On"
        command "p2Off"
        command "p3On"
        command "p3Off"
        //enable and use to create the hex version of your squeeze servers CLI interface
        //ip address and port, this will need to be assigned to the "Device Network Id" field
        //after the device is added to your system
        command "getNid" 

	}

	simulator {
		// TODO: define status and reply messages here
	}

	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
        state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
        state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
        }
	}
    main "switch"
    details(["switch"])
}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"

}

private String makeNetworkId(ipaddr, port) { 
	 String hexIp = ipaddr.tokenize('.').collect { 
     String.format('%02X', it.toInteger()) 
     }.join() 
     String hexPort = String.format('%04X', port()) 
     return "${hexIp}:${hexPort}" 
}
def getNID() {
	//log.debug makeNetworkId("192.168.1.200", 9000) //your squeeze server CLI interface ip address and port
        log.debug makeNetworkId("192.168.1.10", 9091) //your squeeze server CLI interface ip address and port

}

// handle commands for multiple players
def p1On()	{
	//log.debug settings.confp1
    def ha = new physicalgraph.device.HubAction("${settings.confp1} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    //log.debug device.deviceNetworkId
    log.debug ha
    return 0
}
def p1Off()	{
	//log.debug settings.confp1
    def ha = new physicalgraph.device.HubAction("${settings.confp1} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p2On()	{
	//log.debug settings.confp2
    def ha = new physicalgraph.device.HubAction("${settings.confp2} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    log.debug ha
    log.debug "${device.deviceNetworkId}"
    return ha
}
def p2Off()	{
	//log.debug settings.confp2
    def ha = new physicalgraph.device.HubAction("${settings.confp2} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    log.debug ha
    log.debug "${device.deviceNetworkId}"
    return ha
}
def p3On()	{
	//log.debug settings.confp3
    def ha = new physicalgraph.device.HubAction("${settings.confp3} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def p3Off()	{
	//log.debug settings.confp3
    def ha = new physicalgraph.device.HubAction("${settings.confp3} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}

// command for one player only
def on() {
	//log.debug "Executing 'on'"
    def ha = new physicalgraph.device.HubAction("${settings.confp1} play\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}
def off() {
	//log.debug "Executing 'off'"
    def ha = new physicalgraph.device.HubAction("${settings.confp1} power 0\r\n\r\n",physicalgraph.device.Protocol.LAN, "${device.deviceNetworkId}")
    return ha
}