Last active
July 23, 2018 15:28
-
-
Save bradfitz/eb50b05dd0457bf1f72f8338711f2041 to your computer and use it in GitHub Desktop.
Sonos amp control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// turnAmp controls the mPower Mini unit in Barloga, which | |
// powers the rainbow Google Chord AMP hooked up to the speakers. | |
func turnAmp(on bool) bool { | |
c, err := ssh.Dial("tcp", "10.0.0.nnnn:22", &ssh.ClientConfig{ | |
HostKeyCallback: ssh.InsecureIgnoreHostKey(), | |
Config: ssh.Config{ | |
Ciphers: []string{"aes128-cbc"}, | |
}, | |
User: "ubnt", | |
Auth: []ssh.AuthMethod{ssh.Password("xxxxx")}, | |
}) | |
if err != nil { | |
log.Printf("error toggling AMP connecting via ssh: %v", err) | |
return false | |
} | |
defer c.Close() | |
sess, err := c.NewSession() | |
if err != nil { | |
log.Printf("error toggling AMP connecting making ssh.NewSession: %v", err) | |
return false | |
} | |
val := "0" | |
if on { | |
val = "1" | |
} | |
err = sess.Run("echo " + val + " > /proc/power/relay1") | |
if err != nil { | |
log.Printf("Error running echo %s > /proc/power/relay1: %v", val, err) | |
return false | |
} | |
return true | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package sonos | |
// NewDeviceFromIP returns a new Device given its IP address. | |
// This assumes that you've given your Sonos devices static IP | |
// addresses on your network. A future constructor might provide | |
// accessors by name. | |
func NewDeviceFromIP(ip string) *Device { | |
return &Device{ip: ip} | |
} | |
// Device represents a specific Sonos Device, such as a ZonePlayer, | |
// AMP, or Connect. | |
type Device struct { | |
ip string | |
} | |
// Status reports the device's status. States include TRANSITIONING, STOPPED, | |
// PLAYING, PAUSED_PLAYBACK. | |
func (d *Device) Status(ctx context.Context) (string, error) { | |
ipPort := net.JoinHostPort(d.ip, "1400") | |
req, err := http.NewRequest("POST", "http://"+ipPort+"/MediaRenderer/AVTransport/Control", strings.NewReader(` | |
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body> | |
<u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:GetTransportInfo> | |
</s:Body></s:Envelope> | |
`)) | |
if err != nil { | |
return "", err | |
} | |
req.Header["SOAPACTION"] = []string{`"urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"`} | |
req.Header.Set("Content-Type", "text/xml") | |
ctx, cancel := context.WithTimeout(ctx, 5*time.Second) | |
defer cancel() | |
req = req.WithContext(ctx) | |
res, err := http.DefaultTransport.RoundTrip(req) | |
if err != nil { | |
return "", err | |
} | |
defer res.Body.Close() | |
// Response looks like: | |
if res.StatusCode != 200 { | |
return "", fmt.Errorf("unexpected status code from %v: %v", d.ip, res.Status) | |
} | |
if ct := res.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/xml") { | |
return "", fmt.Errorf("unexpected content-type %q from %v", ct, d.ip) | |
} | |
// <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetTransportInfoResponse xmlns:u="urn:\ | |
schemas-upnp-org:service:AVTransport:1"><CurrentTransportState>PAUSED_PLAYBACK</CurrentTransportState><CurrentTransportStatus>OK</CurrentTransportStatus><CurrentSpeed>1</CurrentSpeed><\ | |
/u:GetTransportInfoResponse></s:Body></s:Envelope> | |
type infoRes struct { | |
CurrentTransportState string `xml:"CurrentTransportState"` | |
} | |
type soapBody struct { | |
XMLName xml.Name `xml:"Body"` | |
GetTransportInfoResponse infoRes `xml:"GetTransportInfoResponse"` | |
} | |
type soapEnvelope struct { | |
XMLName xml.Name `xml:"Envelope"` | |
Body soapBody `xml:"Body"` | |
} | |
var env soapEnvelope | |
if err := xml.NewDecoder(res.Body).Decode(&env); err != nil { | |
return "", err | |
} | |
state := env.Body.GetTransportInfoResponse.CurrentTransportState | |
if state == "" { | |
return "", fmt.Errorf("no CurrentTransportState in response") | |
} | |
return state, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func manageBarlogaAmp() { | |
d := sonos.NewDeviceFromIP(devices.BarlogaSonos.IP()) | |
const timeUntilOff = 5 * time.Minute | |
offIn := timeUntilOff | |
var ampState = ampUnknown | |
var lastSonos string | |
for { | |
sonosState, err := d.Status(context.Background()) | |
if err != nil { | |
log.Print(err) | |
time.Sleep(5 * time.Second) | |
continue | |
} | |
if lastSonos != sonosState { | |
log.Printf("sonos state transitioned from %q => %q (amp = %v)", lastSonos, sonosState, ampState) | |
lastSonos = sonosState | |
} | |
switch sonosState { | |
case "PLAYING", "TRANSITIONING": | |
offIn = timeUntilOff | |
if ampState != ampOn && turnAmp(true) { | |
log.Printf("Turned amp on.") | |
ampState = ampOn | |
} | |
default: | |
offIn -= time.Second | |
if offIn < 0 && ampState != ampOff && turnAmp(false) { | |
log.Printf("Turned amp off.") | |
ampState = ampOff | |
} | |
if offIn >= 0 && (offIn < 10*time.Second || int(offIn.Seconds())%10 == 0) { | |
log.Printf("Status: sonos=%q, amp=%s, offIn=%v", sonosState, ampState, offIn) | |
} | |
} | |
time.Sleep(1 * time.Second) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment