Skip to content

Commit

Permalink
refactor!: major MQTT functionality refactor
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This commit is a major
refactoring of the MQTT functionality coinciding
with changes to the underlying library that powers
it. **MQTT entities have been renamed, which will
result in some breakage of automations and
features in Home Assistant.** No functionality has
been lost however, and this change will make it
easier to add additional controls and features
powered by MQTT to Go Hass Agent.
  • Loading branch information
joshuar committed May 5, 2024
1 parent f6333c8 commit decd825
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 209 deletions.
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22.0
require (
fyne.io/fyne/v2 v2.4.5
github.com/cenkalti/backoff/v4 v4.3.0
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/eclipse/paho.golang v0.21.0
github.com/go-playground/validator/v10 v10.19.0
github.com/godbus/dbus/v5 v5.1.0
github.com/grandcat/zeroconf v1.0.0
Expand All @@ -27,11 +27,10 @@ require (
require (
github.com/dolthub/maphash v0.1.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mandykoh/go-parallel v0.1.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
)

require (
Expand All @@ -55,7 +54,7 @@ require (
github.com/go-text/typesetting v0.1.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/joshuar/go-hass-anything/v7 v7.2.0
github.com/joshuar/go-hass-anything/v9 v9.1.0
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/klauspost/compress v1.17.5 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
Expand Down
15 changes: 8 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
github.com/eclipse/paho.golang v0.21.0 h1:cxxEReu+iFbA5RrHfRGxJOh8tXZKDywuehneoeBeyn8=
github.com/eclipse/paho.golang v0.21.0/go.mod h1:GHF6vy7SvDbDHBguaUpfuBkEB5G6j0zKxMG4gbh6QRQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -235,10 +234,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8=
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg=
github.com/joshuar/go-hass-anything/v7 v7.1.0 h1:wEOvu/68rzsxUjbzigb0FxLay8XwCMfrHTuXe7QPK2k=
github.com/joshuar/go-hass-anything/v7 v7.1.0/go.mod h1:ozEpyBHdsl74qveKTh7eFwTPO0bOGzZkn6RZuMFooag=
github.com/joshuar/go-hass-anything/v7 v7.2.0 h1:6FbPnFzDauKF4Y/YYWMhx/QxxWL+L2mU6QL58jwXeiI=
github.com/joshuar/go-hass-anything/v7 v7.2.0/go.mod h1:Sc2im9Z6ZvWJGYBhnP8UaAAPJ81fU3jxGgm8VZmprXY=
github.com/joshuar/go-hass-anything/v9 v9.1.0 h1:dPNfCrMVu1k6+0vnNqm8ncAynAuckrhL6RHpaULgpiY=
github.com/joshuar/go-hass-anything/v9 v9.1.0/go.mod h1:kaNCsLaXjI+7yCnBwO2ZbUViQkoiriu52i8lnPx/NAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
Expand Down Expand Up @@ -395,6 +392,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
Expand Down Expand Up @@ -422,6 +421,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
Expand Down
67 changes: 6 additions & 61 deletions internal/agent/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,12 @@ package agent
import (
"context"

mqtthass "github.com/joshuar/go-hass-anything/v7/pkg/hass"
mqttapi "github.com/joshuar/go-hass-anything/v7/pkg/mqtt"
"github.com/rs/zerolog/log"

"github.com/joshuar/go-hass-agent/internal/preferences"
mqttapi "github.com/joshuar/go-hass-anything/v9/pkg/mqtt"
)

type mqttObj struct {
msgCh chan mqttapi.Msg
entities []*mqtthass.EntityConfig
subscriptions []*mqttapi.Subscription
}

func (o *mqttObj) Name() string {
return preferences.AppName
}

func (o *mqttObj) Configuration() []*mqttapi.Msg {
var msgs []*mqttapi.Msg
for _, c := range o.entities {
if msg, err := mqtthass.MarshalConfig(c); err != nil {
log.Error().Err(err).Msg("Failed to marshal payload for entity.")
} else {
msgs = append(msgs, msg)
}
}
return msgs
}

func (o *mqttObj) Subscriptions() []*mqttapi.Subscription {
var subs []*mqttapi.Subscription
for _, v := range o.entities {
if v.CommandCallback != nil {
if sub, err := mqtthass.MarshalSubscription(v); err != nil {
log.Error().Err(err).Str("entity", v.Entity.Name).
Msg("Error adding subscription.")
} else {
subs = append(subs, sub)
}
}
}
if len(o.subscriptions) > 0 {
subs = append(subs, o.subscriptions...)
}
return subs
}

func (o *mqttObj) States() []*mqttapi.Msg {
return nil
}

func (o *mqttObj) Run(ctx context.Context, client *mqttapi.Client) {
for {
select {
case msg := <-o.msgCh:
if err := client.Publish(&msg); err != nil {
log.Warn().Err(err).Msg("Unable to publish message to MQTT.")
}
case <-ctx.Done():
close(o.msgCh)
return
}
}
type mqttDevice interface {
Subscriptions() []*mqttapi.Subscription
Configs() []*mqttapi.Msg
Msgs() chan *mqttapi.Msg
Setup(ctx context.Context) error
}
115 changes: 95 additions & 20 deletions internal/agent/mqtt_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,110 @@ package agent
import (
"context"

mqtthass "github.com/joshuar/go-hass-anything/v7/pkg/hass"
mqttapi "github.com/joshuar/go-hass-anything/v7/pkg/mqtt"
mqtthass "github.com/joshuar/go-hass-anything/v9/pkg/hass"
mqttapi "github.com/joshuar/go-hass-anything/v9/pkg/mqtt"
"github.com/rs/zerolog/log"

"github.com/joshuar/go-hass-agent/internal/linux/media"
"github.com/joshuar/go-hass-agent/internal/linux/power"
"github.com/joshuar/go-hass-agent/internal/linux/system"
)

// newMQTTObject creates an MQTT object for the agent to use for this operating
// system (Linux).
func newMQTTObject(ctx context.Context) *mqttObj {
var entities []*mqtthass.EntityConfig
var subscriptions []*mqttapi.Subscription
type linuxMQTTDevice struct {
msgs chan *mqttapi.Msg
sensors []*mqtthass.SensorEntity
buttons []*mqtthass.ButtonEntity
numbers []*mqtthass.NumberEntity[int]
switches []*mqtthass.SwitchEntity
controls []*mqttapi.Subscription
}

func (d *linuxMQTTDevice) Subscriptions() []*mqttapi.Subscription {
var subs []*mqttapi.Subscription

for _, button := range d.buttons {
if sub, err := button.MarshalSubscription(); err != nil {
log.Warn().Err(err).Str("entity", button.Name).Msg("Could not create subscription.")
} else {
subs = append(subs, sub)
}
}
for _, number := range d.numbers {
if sub, err := number.MarshalSubscription(); err != nil {
log.Warn().Err(err).Str("entity", number.Name).Msg("Could not create subscription.")
} else {
subs = append(subs, sub)
}
}
for _, sw := range d.switches {
if sub, err := sw.MarshalSubscription(); err != nil {
log.Warn().Err(err).Str("entity", sw.Name).Msg("Could not create subscription.")
} else {
subs = append(subs, sub)
}
}

subs = append(subs, d.controls...)

return subs
}

func (d *linuxMQTTDevice) Configs() []*mqttapi.Msg {
var configs []*mqttapi.Msg

msgCh := make(chan mqttapi.Msg)
for _, sensor := range d.sensors {
if sub, err := sensor.MarshalConfig(); err != nil {
log.Warn().Err(err).Str("entity", sensor.Name).Msg("Could not create subscription.")
} else {
configs = append(configs, sub)
}
}
for _, button := range d.buttons {
if sub, err := button.MarshalConfig(); err != nil {
log.Warn().Err(err).Str("entity", button.Name).Msg("Could not create subscription.")
} else {
configs = append(configs, sub)
}
}
for _, number := range d.numbers {
if sub, err := number.MarshalConfig(); err != nil {
log.Warn().Err(err).Str("entity", number.Name).Msg("Could not create subscription.")
} else {
configs = append(configs, sub)
}
}
for _, sw := range d.switches {
if sub, err := sw.MarshalConfig(); err != nil {
log.Warn().Err(err).Str("entity", sw.Name).Msg("Could not create subscription.")
} else {
configs = append(configs, sub)
}
}

return configs
}

// Add screensaver/screenlock control.
entities = append(entities, power.NewScreenLockControl(ctx))
// Add power controls (poweroff, reboot, suspend, etc.).
entities = append(entities, power.NewPowerControl(ctx)...)
// Add volume control
entities = append(entities, media.VolumeControl(ctx, msgCh)...)
func (d *linuxMQTTDevice) Msgs() chan *mqttapi.Msg {
return d.msgs
}

// Add subscription for issuing D-Bus commands to the Linux device.
subscriptions = append(subscriptions, system.NewDBusCommandSubscription(ctx))
func (d *linuxMQTTDevice) Setup(_ context.Context) error {
return nil
}

return &mqttObj{
entities: entities,
subscriptions: subscriptions,
msgCh: msgCh,
func newMQTTDevice(ctx context.Context) *linuxMQTTDevice {
dev := &linuxMQTTDevice{
msgs: make(chan *mqttapi.Msg),
}

// Add the power controls (suspend, resume, poweroff, etc.).
dev.buttons = append(dev.buttons, power.NewPowerControl(ctx)...)
// Add the screen lock controls.
dev.buttons = append(dev.buttons, power.NewScreenLockControl(ctx))
// Add the volume controls.
dev.numbers = append(dev.numbers, media.VolumeControl(ctx, dev.Msgs()))
// Add the D-Bus command action.
dev.controls = append(dev.controls, system.NewDBusCommandSubscription(ctx))

return dev
}
43 changes: 32 additions & 11 deletions internal/agent/runners.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/robfig/cron/v3"
"github.com/rs/zerolog/log"

mqttapi "github.com/joshuar/go-hass-anything/v7/pkg/mqtt"
mqttapi "github.com/joshuar/go-hass-anything/v9/pkg/mqtt"

"github.com/joshuar/go-hass-agent/internal/device"
"github.com/joshuar/go-hass-agent/internal/hass"
Expand Down Expand Up @@ -163,20 +163,41 @@ func runMQTTWorker(ctx context.Context) {
return
}

o := newMQTTObject(ctx)
// Create an MQTT device for this operating system and run its Setup.
mqttDevice := newMQTTDevice(ctx)
if err := mqttDevice.Setup(ctx); err != nil {
log.Error().Err(err).Msg("Could not set up device MQTT functionality.")
return
}

client, err := mqttapi.NewClient(ctx, prefs, o)
// Create a new connection to the MQTT broker. This will also publish the
// device subscriptions.
client, err := mqttapi.NewClient(ctx, prefs, mqttDevice.Subscriptions(), mqttDevice.Configs())
if err != nil {
log.Error().Err(err).Msg("Could not start MQTT client.")
log.Error().Err(err).Msg("Could not connect to MQTT broker.")
return
}

// Publish the device configs.
log.Debug().Msg("Publishing configs.")
if err := client.Publish(mqttDevice.Configs()...); err != nil {
log.Error().Err(err).Msg("Failed to publish configuration messages.")
}

go func() {
o.Run(ctx, client)
log.Debug().Msg("Listening for messages to publish to MQTT.")
for {
select {
case msg := <-mqttDevice.Msgs():
if err := client.Publish(msg); err != nil {
log.Warn().Err(err).Msg("Unable to publish message to MQTT.")
}
case <-ctx.Done():
return
}
}
}()

log.Debug().Msg("Listening for events on MQTT.")

<-ctx.Done()
}

Expand All @@ -190,15 +211,15 @@ func resetMQTTWorker(ctx context.Context) {
return
}

o := newMQTTObject(ctx)
mqttDevice := newMQTTDevice(ctx)

c, err := mqttapi.NewClient(ctx, prefs, o)
c, err := mqttapi.NewClient(ctx, prefs, nil, nil)
if err != nil {
log.Error().Err(err).Msg("Could not start MQTT client.")
log.Error().Err(err).Msg("Could not connect to MQTT broker.")
return
}

if err := c.Unpublish(o.Configuration()...); err != nil {
if err := c.Unpublish(mqttDevice.Configs()...); err != nil {
log.Error().Err(err).Msg("Failed to reset MQTT.")
}
}
15 changes: 15 additions & 0 deletions internal/linux/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import (
"os/user"
"strings"

mqtthass "github.com/joshuar/go-hass-anything/v9/pkg/hass"
"github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v3/host"

"github.com/joshuar/go-hass-agent/internal/preferences"
)

type Device struct {
Expand Down Expand Up @@ -102,6 +105,18 @@ func NewDevice(name, version string) *Device {
}
}

func MQTTDevice() *mqtthass.Device {
dev := NewDevice(preferences.AppName, preferences.AppVersion)
return &mqtthass.Device{
Name: dev.DeviceName(),
URL: preferences.AppURL,
SWVersion: dev.OsVersion(),
Manufacturer: dev.Manufacturer(),
Model: dev.Model(),
Identifiers: []string{dev.DeviceID()},
}
}

// getDeviceID retrieves the unique host ID of the device running the agent, or
// unknown if that doesn't work.
func getDeviceID() string {
Expand Down
Loading

0 comments on commit decd825

Please sign in to comment.