Short question: do i have to install Go for that to run? because i put the files in the named folder, run chmod and tried ./mac2mqtt but it only shows that this is a folder.
edit: got it working, do have to install go and get dependencies manually - perhaps it would be usefull to add that in the readme
edit 2: I got a problem with my m1 Mac - It says
strconv.Atoi: parsing “missing value”: invalid syntax
exit status 1
edit 3: editing the parts with strconv so no fatal error occurs help to get “sleep” and "alive working, but “display sleep” further don’t work=(
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"sync"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
var hostname string
type config struct {
Ip string `yaml:"mqtt_ip"`
Port string `yaml:"mqtt_port"`
User string `yaml:"mqtt_user"`
Password string `yaml:"mqtt_password"`
}
func (c *config) getConfig() *config {
configContent, err := ioutil.ReadFile("mac2mqtt.yaml")
if err != nil {
log.Fatal(err)
}
err = yaml.Unmarshal(configContent, c)
if err != nil {
log.Fatal(err)
}
if c.Ip == "" {
log.Fatal("Must specify mqtt_ip in mac2mqtt.yaml")
}
if c.Port == "" {
log.Fatal("Must specify mqtt_port in mac2mqtt.yaml")
}
if c.User == "" {
log.Fatal("Must specify mqtt_user in mac2mqtt.yaml")
}
if c.Password == "" {
log.Fatal("Must specify mqtt_password in mac2mqtt.yaml")
}
return c
}
func getHostname() string {
hostname, err := os.Hostname()
if err != nil {
log.Fatal(err)
}
// "name.local" => "name"
firstPart := strings.Split(hostname, ".")[0]
// remove all symbols, but [a-zA-Z0-9_-]
reg, err := regexp.Compile("[^a-zA-Z0-9_-]+")
if err != nil {
log.Fatal(err)
}
firstPart = reg.ReplaceAllString(firstPart, "")
return firstPart
}
func getCommandOutput(name string, arg ...string) string {
cmd := exec.Command(name, arg...)
stdout, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
stdoutStr := string(stdout)
stdoutStr = strings.TrimSuffix(stdoutStr, "\n")
return stdoutStr
}
func getMuteStatus() bool {
output := getCommandOutput("/usr/bin/osascript", "-e", "output muted of (get volume settings)")
b, err := strconv.ParseBool(output)
if err != nil {
return b
}
return b
}
func getCurrentVolume() int {
output := getCommandOutput("/usr/bin/osascript", "-e", "output volume of (get volume settings)")
i, err := strconv.Atoi(output)
if err != nil {
return i
}
return i
}
func runCommand(name string, arg ...string) {
cmd := exec.Command(name, arg...)
_, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
}
// from 0 to 100
func setVolume(i int) {
runCommand("/usr/bin/osascript", "-e", "set volume output volume "+strconv.Itoa(i))
}
// true - turn mute on
// false - turn mute off
func setMute(b bool) {
runCommand("/usr/bin/osascript", "-e", "set volume output muted "+strconv.FormatBool(b))
}
func commandSleep() {
runCommand("pmset", "sleepnow")
}
func commandDisplaySleep() {
runCommand("pmset", "displaysleepnow")
}
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
log.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
}
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
log.Println("Connected to MQTT")
token := client.Publish(getTopicPrefix()+"/status/alive", 0, true, "true")
token.Wait()
log.Println("Sending 'true' to topic: " + getTopicPrefix() + "/status/alive")
listen(client, getTopicPrefix()+"/command/#")
}
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
log.Printf("Disconnected from MQTT: %v", err)
}
func getMQTTClient(ip, port, user, password string) mqtt.Client {
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("tcp://%s:%s", ip, port))
opts.SetUsername(user)
opts.SetPassword(password)
opts.OnConnect = connectHandler
opts.OnConnectionLost = connectLostHandler
opts.SetWill(getTopicPrefix()+"/status/alive", "false", 0, true)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
return client
}
func getTopicPrefix() string {
return "mac2mqtt/" + hostname
}
func listen(client mqtt.Client, topic string) {
token := client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) {
if msg.Topic() == getTopicPrefix()+"/command/volume" {
i, err := strconv.Atoi(string(msg.Payload()))
if err == nil && i >= 0 && i <= 100 {
setVolume(i)
updateVolume(client)
updateMute(client)
} else {
log.Println("Incorrect value")
}
}
if msg.Topic() == getTopicPrefix()+"/command/mute" {
b, err := strconv.ParseBool(string(msg.Payload()))
if err == nil {
setMute(b)
updateVolume(client)
updateMute(client)
} else {
log.Println("Incorrect value")
}
}
if msg.Topic() == getTopicPrefix()+"/command/sleep" {
if string(msg.Payload()) == "sleep" {
commandSleep()
}
}
if msg.Topic() == getTopicPrefix()+"/command/displaysleep" {
if string(msg.Payload()) == "displaysleep" {
commandDisplaySleep()
}
}
})
token.Wait()
if token.Error() != nil {
log.Printf("Token error: %s\n", token.Error())
}
}
func updateVolume(client mqtt.Client) {
token := client.Publish(getTopicPrefix()+"/status/volume", 0, false, strconv.Itoa(getCurrentVolume()))
token.Wait()
}
func updateMute(client mqtt.Client) {
token := client.Publish(getTopicPrefix()+"/status/mute", 0, false, strconv.FormatBool(getMuteStatus()))
token.Wait()
}
func main() {
log.Println("Started")
var c config
c.getConfig()
var wg sync.WaitGroup
hostname = getHostname()
mqttClient := getMQTTClient(c.Ip, c.Port, c.User, c.Password)
volumeTicker := time.NewTicker(2 * time.Second)
wg.Add(1)
go func() {
for {
select {
case _ = <-volumeTicker.C:
updateVolume(mqttClient)
updateMute(mqttClient)
}
}
}()
wg.Wait()
}
edit 4: found out, that the error is related to external monitor connected which doesn’t support audio control - so if audio goes through hdmi the app crashes, so before posted dirty fix which prevents error exit solves this. Further “Displaysleepnow” doesn’t work at my Mac - it is working on my MacBook, so I guess its also related to my Mac mini