Skip to content

Commit

Permalink
feat: initial commit & basic features
Browse files Browse the repository at this point in the history
  • Loading branch information
feuerrot committed Oct 12, 2020
0 parents commit a5c419e
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mpzbc
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# mpzbc - music player zigbee client
[zigbee2mqtt](https://www.zigbee2mqtt.io) + [IKEA E1744 SYMFONISK sound controller](https://www.zigbee2mqtt.io/devices/E1744.html) + mpzbc == wireless mpd remote

## Usage
```
go get github.com/feuerrot/mpzbc
MQTTSERVER=mqtthost:1883 MQTTTOPIC=zigbee2mqtt/friendly_e1744_name MPDSERVER=mpdhost:6600 mpzbc
```
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/feuerrot/mpzbc

go 1.14

require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/fhs/gompd v1.0.1
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/fhs/gompd v1.0.1 h1:kBcAhjnAPJQAylZXR0TeH+d2vpjawXlTtKYguqNlF4A=
github.com/fhs/gompd v1.0.1/go.mod h1:b219/mNa9PvRqvkUip51b23hGL3iX4d4q3gNXdtrD04=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
166 changes: 166 additions & 0 deletions mpzbc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

import (
"encoding/json"
"fmt"
"os"
"strconv"
"time"

mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/fhs/gompd/mpd"
)

const volumedelta int = 5

type mpzbc struct {
mqttClient mqtt.Client
mqttServer string
mqttTopic string
mpdClient *mpd.Client
mpdServer string
mpdStatus string
mpdVolume int
}

type control struct {
Action string
Battery int
Linkquality int
}

func (m *mpzbc) mqttMessage(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("Message: %s\n", msg.Payload())
m.updateMPD()

ctrl := control{}
if err := json.Unmarshal(msg.Payload(), &ctrl); err != nil {
fmt.Printf("Unmarshal error: %v", err)
}

switch ctrl.Action {
case "play_pause":
if m.mpdStatus == "play" {
m.mpdClient.Pause(true)
} else {
m.mpdClient.Play(-1)
}
case "rotate_left":
m.updateMPD()
if m.mpdVolume != -1 {
m.mpdClient.SetVolume(m.mpdVolume - volumedelta)
}
case "rotate_right":
m.updateMPD()
if m.mpdVolume != -1 {
m.mpdClient.SetVolume(m.mpdVolume + volumedelta)
}
case "skip_backward":
m.mpdClient.Previous()
case "skip_forward":
m.mpdClient.Next()
}
}

func (m *mpzbc) connectMQTT() error {
fmt.Println("Build MQTT Client")
co := mqtt.NewClientOptions()
co.AddBroker("tcp://" + m.mqttServer)
co.SetClientID(fmt.Sprintf("mpzbc_%x", os.Getpid()))
co.SetAutoReconnect(true)
co.SetCleanSession(true)

co.OnConnect = func(c mqtt.Client) {
if token := c.Subscribe(m.mqttTopic, 0, m.mqttMessage); token.Wait() && token.Error() != nil {
fmt.Printf("error during mqtt subscribe: %v\n", token.Error())
}
}

client := mqtt.NewClient(co)
fmt.Println("Connect to MQTT")
if token := client.Connect(); token.Wait() && token.Error() != nil {
return fmt.Errorf("error during mqtt connect: %v", token.Error())
}

return nil
}

func (m *mpzbc) updateMPD() error {
status, err := m.mpdClient.Status()
if err != nil {
return fmt.Errorf("couldn't get MPD status: %v", err)
}

state, ok := status["state"]
if !ok {
return fmt.Errorf("no state in MPD status")
}
m.mpdStatus = state

volume, ok := status["volume"]
if !ok {
m.mpdVolume = -1
} else {
m.mpdVolume, err = strconv.Atoi(volume)
if err != nil {
return fmt.Errorf("couldn't convert %s to integer: %v", volume, err)
}
}

return nil
}

func (m *mpzbc) connectMPD() error {
mpdClient, err := mpd.Dial("tcp", m.mpdServer)
if err != nil {
return fmt.Errorf("error while connecting to %s: %v", m.mpdServer, err)
}
m.mpdClient = mpdClient

return nil
}

func (m *mpzbc) getEnv() error {
m.mqttServer = os.Getenv("MQTTSERVER")
if m.mqttServer == "" {
return fmt.Errorf("MQTTSERVER is empty")
}

m.mqttTopic = os.Getenv("MQTTTOPIC")
if m.mqttTopic == "" {
return fmt.Errorf("MQTTTOPIC is empty")
}

m.mpdServer = os.Getenv("MPDSERVER")
if m.mpdServer == "" {
return fmt.Errorf("MPDSERVER is empty")
}

return nil
}

func (m *mpzbc) run() error {
if err := m.getEnv(); err != nil {
return fmt.Errorf("couldn't get settings: %v", err)
}
if err := m.connectMPD(); err != nil {
return fmt.Errorf("couldn't connect to MPD: %v", err)
}
if err := m.connectMQTT(); err != nil {
return fmt.Errorf("couldn't connect to MQTT: %v", err)
}
for {
if err := m.updateMPD(); err != nil {
return fmt.Errorf("couldn't update MPD state: %v", err)
}
fmt.Printf("state: %s\tvolume: %d\n", m.mpdStatus, m.mpdVolume)
time.Sleep(1 * time.Second)
}
}

func main() {
client := mpzbc{}
if err := client.run(); err != nil {
fmt.Printf("error during client.run(): %v\n", err)
}
}

0 comments on commit a5c419e

Please sign in to comment.