From f81430a204b70a1f4176fcddfa9aafa2ca5e7cfe Mon Sep 17 00:00:00 2001 From: Joshua Rich Date: Sun, 13 Oct 2024 11:58:22 +1000 Subject: [PATCH] refactor(agent,linux,preferences,command): :arrow_up: update github.com/joshuar/go-hass-anything to v12 and implement required changes - implement new builder methods for creating MQTT entities - switch webcam controller from image to camera entity --- go.mod | 7 +- go.sum | 12 +- internal/agent/agent_mocks_test.go | 2 +- internal/agent/controllers.go | 4 +- internal/agent/mqtt_controller.go | 4 +- internal/agent/mqtt_controller_linux.go | 24 ++- internal/commands/commands.go | 106 ++++++---- internal/commands/commands_test.go | 63 ++++-- internal/linux/media/camera.go | 234 ++++++++++------------ internal/linux/media/camera_test.go | 229 --------------------- internal/linux/media/mpris.go | 25 ++- internal/linux/media/volume.go | 59 ++++-- internal/linux/power/powerControl.go | 18 +- internal/linux/power/screenLockControl.go | 39 ++-- internal/linux/system/dbusCommand.go | 4 +- internal/preferences/mqtt.go | 4 +- 16 files changed, 337 insertions(+), 497 deletions(-) delete mode 100644 internal/linux/media/camera_test.go diff --git a/go.mod b/go.mod index c817c153d..61fc2f8a4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/goreleaser/nfpm/v2 v2.40.0 github.com/grandcat/zeroconf v1.0.0 github.com/iancoleman/strcase v0.3.0 - github.com/joshuar/go-hass-anything/v11 v11.1.0 + github.com/joshuar/go-hass-anything/v12 v12.0.0 github.com/lthibault/jitterbug/v2 v2.2.2 github.com/lxzan/gws v1.8.8 github.com/magefile/mage v1.15.0 @@ -21,7 +21,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.9.0 github.com/yassinebenaid/godump v0.10.0 - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/text v0.19.0 golang.org/x/tools v0.26.0 gopkg.in/yaml.v3 v3.0.1 @@ -95,7 +95,6 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect - go.uber.org/goleak v1.3.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -106,6 +105,7 @@ require ( fyne.io/systray v1.11.0 // indirect github.com/adrg/xdg v0.5.0 github.com/alecthomas/kong v1.2.1 + github.com/blackjack/webcam v0.6.1 github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/fredbi/uri v1.1.0 // indirect @@ -142,7 +142,6 @@ require ( github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/tklauser/go-sysconf v0.3.14 github.com/tklauser/numcpus v0.8.0 // indirect - github.com/vladimirvivien/go4vl v0.0.5 github.com/yuin/goldmark v1.7.1 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/image v0.18.0 // indirect diff --git a/go.sum b/go.sum index bc04c6ee1..74d29dcf5 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blackjack/webcam v0.6.1 h1:K0T6Q0zto23U99gNAa5q/hFoye6uGcKr2aE6hFoxVoE= +github.com/blackjack/webcam v0.6.1/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -347,8 +349,8 @@ github.com/jfreymuth/pulse v0.1.1/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/joshuar/go-hass-anything/v11 v11.1.0 h1:UhQk52C0yLwzvm/k1I7bq9N38lUC/xuzfroTZfTh32E= -github.com/joshuar/go-hass-anything/v11 v11.1.0/go.mod h1:qxIJmkorMRUBy067+rQ8hnrWBpGgGL74Kc33mcsr0h0= +github.com/joshuar/go-hass-anything/v12 v12.0.0 h1:/627Elwhj5hH9j7U4WbveFdq8s8zm58jSEEGfeljrAI= +github.com/joshuar/go-hass-anything/v12 v12.0.0/go.mod h1:P/G9hYsLGS8eSXoI/T+MTiWqLkN7/LgKlAyB0OjTZug= github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -525,8 +527,6 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/vladimirvivien/go4vl v0.0.5 h1:jHuo/CZOAzYGzrSMOc7anOMNDr03uWH5c1B5kQ+Chnc= -github.com/vladimirvivien/go4vl v0.0.5/go.mod h1:FP+/fG/X1DUdbZl9uN+l33vId1QneVn+W80JMc17OL8= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -590,8 +590,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/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= diff --git a/internal/agent/agent_mocks_test.go b/internal/agent/agent_mocks_test.go index 5bf391c54..d5267287d 100644 --- a/internal/agent/agent_mocks_test.go +++ b/internal/agent/agent_mocks_test.go @@ -8,7 +8,7 @@ import ( fyneui "github.com/joshuar/go-hass-agent/internal/agent/ui/fyneUI" "github.com/joshuar/go-hass-agent/internal/hass/sensor" "github.com/joshuar/go-hass-agent/internal/preferences" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "sync" ) diff --git a/internal/agent/controllers.go b/internal/agent/controllers.go index 4b321f5aa..ee0ab11cd 100644 --- a/internal/agent/controllers.go +++ b/internal/agent/controllers.go @@ -8,8 +8,8 @@ package agent import ( "context" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/hass/sensor" "github.com/joshuar/go-hass-agent/internal/preferences" diff --git a/internal/agent/mqtt_controller.go b/internal/agent/mqtt_controller.go index ffd61e76c..f8226e1fe 100644 --- a/internal/agent/mqtt_controller.go +++ b/internal/agent/mqtt_controller.go @@ -14,8 +14,8 @@ import ( "github.com/adrg/xdg" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/commands" "github.com/joshuar/go-hass-agent/internal/logging" diff --git a/internal/agent/mqtt_controller_linux.go b/internal/agent/mqtt_controller_linux.go index 0ea164915..f6155ba65 100644 --- a/internal/agent/mqtt_controller_linux.go +++ b/internal/agent/mqtt_controller_linux.go @@ -9,8 +9,8 @@ import ( "context" "log/slog" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/linux" "github.com/joshuar/go-hass-agent/internal/linux/media" @@ -26,8 +26,8 @@ type mqttWorker struct { numbers []*mqtthass.NumberEntity[int] switches []*mqtthass.SwitchEntity controls []*mqttapi.Subscription - binarySensors []*mqtthass.BinarySensorEntity - cameras []*mqtthass.ImageEntity + binarySensors []*mqtthass.SensorEntity + cameras []*mqtthass.CameraEntity } type linuxMQTTController struct { @@ -35,13 +35,19 @@ type linuxMQTTController struct { logger *slog.Logger } -// entity is a convienience interface to avoid duplicating a lot of loop content +// stateEntity is a convienience interface to avoid duplicating a lot of loop content // when configuring the controller. -type entity interface { - MarshalSubscription() (*mqttapi.Subscription, error) +type stateEntity interface { MarshalConfig() (*mqttapi.Msg, error) } +// commandEntity is a convienience interface to avoid duplicating a lot of loop content +// when configuring the controller. +type commandEntity interface { + stateEntity + MarshalSubscription() (*mqttapi.Subscription, error) +} + func (c *linuxMQTTController) Subscriptions() []*mqttapi.Subscription { totalLength := len(c.buttons) + len(c.numbers) + len(c.switches) + len(c.cameras) subs := make([]*mqttapi.Subscription, 0, totalLength) @@ -102,7 +108,7 @@ func (c *linuxMQTTController) Msgs() chan *mqttapi.Msg { // generateConfig is a helper function to avoid duplicate code around generating // an entity subscription. -func (c *linuxMQTTController) generateSubscription(e entity) *mqttapi.Subscription { +func (c *linuxMQTTController) generateSubscription(e commandEntity) *mqttapi.Subscription { sub, err := e.MarshalSubscription() if err != nil { c.logger.Warn("Could not create subscription.", slog.Any("error", err)) @@ -115,7 +121,7 @@ func (c *linuxMQTTController) generateSubscription(e entity) *mqttapi.Subscripti // generateConfig is a helper function to avoid duplicate code around generating // an entity config. -func (c *linuxMQTTController) generateConfig(e entity) *mqttapi.Msg { +func (c *linuxMQTTController) generateConfig(e stateEntity) *mqttapi.Msg { cfg, err := e.MarshalConfig() if err != nil { c.logger.Warn("Could not create config.", slog.Any("error", err.Error())) diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 1d30bf61d..5d562fbc5 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -22,8 +22,8 @@ import ( "github.com/pelletier/go-toml/v2" "golang.org/x/exp/constraints" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/logging" "github.com/joshuar/go-hass-agent/internal/preferences" @@ -240,12 +240,17 @@ func (d *Controller) generateButtons(buttonCmds []Command) { } entities = append(entities, - mqtthass.AsButton( - mqtthass.NewEntity(preferences.AppName, name, id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(d.device). - WithIcon(icon). - WithCommandCallback(callback))) + mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(name), + mqtthass.ID(id), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(d.device), + mqtthass.Icon(icon), + ).WithCommand( + mqtthass.CommandCallback(callback), + )) } d.buttons = entities @@ -282,14 +287,22 @@ func (d *Controller) generateSwitches(switchCmds []Command) { } entities = append(entities, - mqtthass.AsSwitch( - mqtthass.NewEntity(preferences.AppName, name, id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(d.device). - WithIcon(icon). - WithStateCallback(stateCallBack). - WithCommandCallback(cmdCallBack), - true)) + mqtthass.NewSwitchEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(name), + mqtthass.ID(id), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(d.device), + mqtthass.Icon(icon), + ). + WithCommand( + mqtthass.CommandCallback(cmdCallBack), + ). + WithState( + mqtthass.StateCallback(stateCallBack), + ). + OptimisticMode()) } d.switches = entities @@ -367,15 +380,28 @@ func (d *Controller) generateNumbers(numberCommands []Command) { } floats = append(floats, - mqtthass.AsNumber( - mqtthass.NewEntity(preferences.AppName, name, id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(d.device). - WithIcon(icon). - WithStateCallback(stateCallBack). - WithCommandCallback(cmdCallBack). - WithValueTemplate(stateValueTemplate), - step, min, max, displayType)) + mqtthass.NewNumberEntity[float64](). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(name), + mqtthass.ID(id), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(d.device), + mqtthass.Icon(icon), + ). + WithCommand( + mqtthass.CommandCallback(cmdCallBack), + ). + WithState( + mqtthass.StateCallback(stateCallBack), + mqtthass.ValueTemplate(stateValueTemplate), + ). + WithMode(displayType). + WithStep(step). + WithMin(min). + WithMax(max). + OptimisticMode()) + default: min := convValue[int](cmd.Min) //nolint:predeclared @@ -390,15 +416,27 @@ func (d *Controller) generateNumbers(numberCommands []Command) { } ints = append(ints, - mqtthass.AsNumber( - mqtthass.NewEntity(preferences.AppName, name, id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(d.device). - WithIcon(icon). - WithStateCallback(stateCallBack). - WithCommandCallback(cmdCallBack). - WithValueTemplate(stateValueTemplate), - step, min, max, displayType)) + mqtthass.NewNumberEntity[int](). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(name), + mqtthass.ID(id), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(d.device), + mqtthass.Icon(icon), + ). + WithCommand( + mqtthass.CommandCallback(cmdCallBack), + ). + WithState( + mqtthass.StateCallback(stateCallBack), + mqtthass.ValueTemplate(stateValueTemplate), + ). + WithMode(displayType). + WithStep(step). + WithMin(min). + WithMax(max). + OptimisticMode()) } } diff --git a/internal/commands/commands_test.go b/internal/commands/commands_test.go index 314ab1370..c3350f92d 100644 --- a/internal/commands/commands_test.go +++ b/internal/commands/commands_test.go @@ -19,32 +19,55 @@ import ( "github.com/go-test/deep" "github.com/stretchr/testify/require" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" ) var mockCommandCallback = func(_ *paho.Publish) {} -var mockButton = mqtthass.AsButton( - mqtthass.NewEntity("test", "test button", "test_button"). - WithOriginInfo(&mqtthass.Origin{}). - WithDeviceInfo(&mqtthass.Device{}). - WithIcon("mdi:test"). - WithCommandCallback(mockCommandCallback)) +var mockButton = mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App("test"), + mqtthass.Name("test button"), + mqtthass.ID("test_button"), + mqtthass.OriginInfo(&mqtthass.Origin{}), + mqtthass.DeviceInfo(&mqtthass.Device{}), + mqtthass.Icon("mdi:test"), + ). + WithCommand( + mqtthass.CommandCallback(mockCommandCallback), + ) -var mockSwitch = mqtthass.AsSwitch( - mqtthass.NewEntity("test", "test switch", "test_switch"). - WithOriginInfo(&mqtthass.Origin{}). - WithDeviceInfo(&mqtthass.Device{}). - WithIcon("mdi:test"). - WithCommandCallback(mockCommandCallback), true) +var mockSwitch = mqtthass.NewSwitchEntity(). + WithDetails( + mqtthass.App("test"), + mqtthass.Name("test switch"), + mqtthass.ID("test_switch"), + mqtthass.OriginInfo(&mqtthass.Origin{}), + mqtthass.DeviceInfo(&mqtthass.Device{}), + mqtthass.Icon("mdi:test"), + ). + WithCommand( + mqtthass.CommandCallback(mockCommandCallback), + ). + OptimisticMode() -var mockNumber = mqtthass.AsNumber( - mqtthass.NewEntity("test", "test number", "test_number"). - WithOriginInfo(&mqtthass.Origin{}). - WithDeviceInfo(&mqtthass.Device{}). - WithIcon("mdi:test"). - WithCommandCallback(mockCommandCallback), 0, 100, 1, mqtthass.NumberAuto) +var mockNumber = mqtthass.NewNumberEntity[int](). + WithDetails( + mqtthass.App("test"), + mqtthass.Name("test number"), + mqtthass.ID("test_number"), + mqtthass.OriginInfo(&mqtthass.Origin{}), + mqtthass.DeviceInfo(&mqtthass.Device{}), + mqtthass.Icon("mdi:test"), + ). + WithCommand( + mqtthass.CommandCallback(mockCommandCallback), + ). + WithStep(1). + WithMin(0). + WithMax(100). + WithMode(mqtthass.NumberAuto) func TestController_Subscriptions(t *testing.T) { var mockButtonSubscription, mockSwitchSubscription, mockNumberSubscription *mqttapi.Subscription diff --git a/internal/linux/media/camera.go b/internal/linux/media/camera.go index e8042945d..93d275055 100644 --- a/internal/linux/media/camera.go +++ b/internal/linux/media/camera.go @@ -8,18 +8,17 @@ package media import ( "context" "encoding/json" + "errors" "fmt" "log/slog" - "time" + "slices" "github.com/eclipse/paho.golang/paho" - "github.com/vladimirvivien/go4vl/device" - "github.com/vladimirvivien/go4vl/v4l2" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + "github.com/blackjack/webcam" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" - "github.com/joshuar/go-hass-agent/internal/logging" "github.com/joshuar/go-hass-agent/internal/preferences" ) @@ -35,7 +34,7 @@ const ( // Some defaults for the device file, formats and image size. var ( defaultDevice = "/dev/video0" - preferredFmts = []v4l2.FourCCType{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV} + preferredFmts = []string{"Motion-JPEG"} defaultHeight uint32 = 640 defaultWidth uint32 = 480 ) @@ -44,178 +43,151 @@ var ( // includes the entity for showing images, as well as button entities for // start/stop commands and a sensor entity showing the recording status. type CameraEntities struct { - Images *mqtthass.ImageEntity + Images *mqtthass.CameraEntity StartButton *mqtthass.ButtonEntity StopButton *mqtthass.ButtonEntity Status *mqtthass.SensorEntity -} - -// cameraControl is an internal struct that contains the data used to control -// the camera and populate the entities. -type cameraControl struct { - device *device.Device - cancelFunc context.CancelFunc - logger *slog.Logger - state string - fps time.Duration + camera *webcam.Webcam + state string } // NewCameraControl is called by the OS controller to provide the entities for a camera. -func NewCameraControl(ctx context.Context, msgCh chan *mqttapi.Msg, mqttDevice *mqtthass.Device) *CameraEntities { - camera := newCamera(ctx) +func NewCameraControl(_ context.Context, msgCh chan *mqttapi.Msg, mqttDevice *mqtthass.Device) *CameraEntities { entities := &CameraEntities{} - entities.Images = mqtthass.AsImage(mqtthass.NewEntity(preferences.AppName, "Webcam", mqttDevice.Name+"_camera"). - WithDeviceInfo(mqttDevice). - WithDefaultOriginInfo(), mqtthass.ModeImage) - - entities.StartButton = mqtthass.AsButton(mqtthass.NewEntity(preferences.AppName, "Start Webcam", mqttDevice.Name+"_start_camera"). - WithDeviceInfo(mqttDevice). - WithDefaultOriginInfo(). - WithIcon(startIcon). - WithCommandCallback(func(_ *paho.Publish) { - err := camera.openCamera(defaultDevice) + entities.Images = mqtthass.NewCameraEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Webcam"), + mqtthass.ID(mqttDevice.Name+"_camera"), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(mqttDevice), + ) + + entities.StartButton = mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Start Webcam"), + mqtthass.ID(mqttDevice.Name+"_start_camera"), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(mqttDevice), + mqtthass.Icon(startIcon), + ).WithCommand( + mqtthass.CommandCallback(func(_ *paho.Publish) { + var err error + // Open the camera device. + entities.camera, err = openCamera(defaultDevice) if err != nil { - camera.logger.Error("Could not open camera device.", slog.Any("error", err)) - + slog.Error("Could not open camera device.", + slog.Any("error", err)) return } - camCtx, cancelFunc := context.WithCancel(ctx) - camera.cancelFunc = cancelFunc - camera.state = startedState + slog.Info("Start recording webcam.") - camera.logger.Debug("Start recording webcam.") + entities.state = startedState - go camera.publishImages(camCtx, entities.Images.GetImageTopic(), msgCh) - msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(camera.state)) + go publishImages(entities.camera, entities.Images.Topic, msgCh) + msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(entities.state)) })) - entities.StopButton = mqtthass.AsButton(mqtthass.NewEntity(preferences.AppName, "Stop Webcam", mqttDevice.Name+"_stop_camera"). - WithDeviceInfo(mqttDevice). - WithDefaultOriginInfo(). - WithIcon(stopIcon). - WithCommandCallback(func(_ *paho.Publish) { - camera.state = stoppedState - if camera.cancelFunc != nil { - camera.cancelFunc() - camera.logger.Debug("Stop recording webcam.") - - if err := camera.closeCamera(); err != nil { - camera.logger.Error("Close camera failed.", slog.Any("error", err)) - } + entities.StopButton = mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Stop Webcam"), + mqtthass.ID(mqttDevice.Name+"_stop_camera"), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(mqttDevice), + mqtthass.Icon(stopIcon), + ).WithCommand( + mqtthass.CommandCallback(func(_ *paho.Publish) { + if err := entities.camera.StopStreaming(); err != nil { + slog.Error("Stop streaming failed.", slog.Any("error", err)) } - msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(camera.state)) - })) - entities.Status = mqtthass.AsSensor(mqtthass.NewEntity(preferences.AppName, "Webcam Status", mqttDevice.Name+"_camera_status"). - WithDeviceInfo(mqttDevice). - WithDefaultOriginInfo(). - WithIcon(statusIcon). - WithValueTemplate("{{ value }}"). - WithStateCallback(func(_ ...any) (json.RawMessage, error) { - return json.RawMessage(camera.state), nil - })) + if err := entities.camera.Close(); err != nil { + slog.Error("Close camera failed.", slog.Any("error", err)) + } + + entities.state = stoppedState + msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(entities.state)) + + slog.Info("Stop recording webcam.") + }), + ) + + entities.Status = mqtthass.NewSensorEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Webcam Status"), + mqtthass.ID(mqttDevice.Name+"_camera_status"), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(mqttDevice), + mqtthass.Icon(statusIcon), + ). + WithState( + mqtthass.StateCallback(func(_ ...any) (json.RawMessage, error) { + return json.RawMessage(entities.state), nil + }), + ) go func() { - msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(camera.state)) + msgCh <- mqttapi.NewMsg(entities.Status.StateTopic, []byte(stoppedState)) }() return entities } -func newCamera(ctx context.Context) *cameraControl { - return &cameraControl{ - logger: logging.FromContext(ctx).With(slog.String("controller", "camera")), - state: stoppedState, - } -} - // openCamera opens the camera device and ensures that it has a preferred image // format, framerate and dimensions. -func (c *cameraControl) openCamera(cameraDevice string) error { - camDev, err := device.Open(cameraDevice) - if err != nil { - return fmt.Errorf("could not open camera %s: %w", cameraDevice, err) - } - - fps, err := camDev.GetFrameRate() +func openCamera(cameraDevice string) (*webcam.Webcam, error) { + cam, err := webcam.Open(cameraDevice) if err != nil { - return fmt.Errorf("could not determine camera frame rate: %w", err) + return nil, fmt.Errorf("could not open camera %s: %w", cameraDevice, err) } - fmtDescs, err := camDev.GetFormatDescriptions() - if err != nil { - return fmt.Errorf("could not determine camera formats: %w", err) - } + // select pixel format + var preferredFormat webcam.PixelFormat - var fmtDesc *v4l2.FormatDescription - for _, preferredFmt := range preferredFmts { - fmtDesc = getFormats(fmtDescs, preferredFmt) - if fmtDesc != nil { + for format, desc := range cam.GetSupportedFormats() { + if slices.Contains(preferredFmts, desc) { + preferredFormat = format break } } - if fmtDesc == nil { - return fmt.Errorf("camera does not support any preferred formats: %w", err) + if preferredFormat == 0 { + return nil, errors.New("could not determine an appropriate format") } - if err = camDev.SetPixFormat(v4l2.PixFormat{ - Width: defaultWidth, - Height: defaultHeight, - PixelFormat: fmtDesc.PixelFormat, - Field: v4l2.FieldNone, - }); err != nil { - return fmt.Errorf("could not configure camera: %w", err) - } - - pixFmt, err := camDev.GetPixFormat() - if err == nil { - c.logger.Debug("Camera configured.", - slog.Any("format", pixFmt), - slog.Any("fps", fps)) + _, _, _, err = cam.SetImageFormat(preferredFormat, defaultWidth, defaultHeight) + if err != nil { + return nil, fmt.Errorf("could not set camera parameters: %w", err) } - c.device = camDev - c.fps = time.Second / time.Duration(fps) - - return nil + return cam, nil } // publishImages loops over the received frames from the camera and wraps them // as a MQTT message to be sent back on the bus. -func (c *cameraControl) publishImages(ctx context.Context, topic string, msgCh chan *mqttapi.Msg) { - if err := c.device.Start(ctx); err != nil { - c.logger.Error("Could not start recording", slog.Any("error", err)) +func publishImages(cam *webcam.Webcam, topic string, msgCh chan *mqttapi.Msg) { + if err := cam.StartStreaming(); err != nil { + slog.Error("Could not start recording", slog.Any("error", err)) return } - for frame := range c.device.GetOutput() { - c.logger.Log(ctx, mqttapi.LevelTrace, "Sending frame.") - msgCh <- mqttapi.NewMsg(topic, frame) - - time.Sleep(c.fps) - } -} - -// closeCamera wraps the v4l2 camera close method. -func (c *cameraControl) closeCamera() error { - if err := c.device.Close(); err != nil { - return fmt.Errorf("could not close camera device: %w", err) - } - - return nil -} + for { + err := cam.WaitForFrame(uint32(5)) + if err != nil && errors.Is(err, &webcam.Timeout{}) { + continue + } -// getFormats finds an appropriate image format to use for the camera. -func getFormats(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCType) *v4l2.FormatDescription { - for _, desc := range fmts { - if desc.PixelFormat == pixEncoding { - return &desc + frame, err := cam.ReadFrame() + if len(frame) == 0 || err != nil { + break } - } - return nil + msgCh <- mqttapi.NewMsg(topic, frame) + } } diff --git a/internal/linux/media/camera_test.go b/internal/linux/media/camera_test.go deleted file mode 100644 index af79b55aa..000000000 --- a/internal/linux/media/camera_test.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) 2024 Joshua Rich -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -//nolint:paralleltest -package media - -import ( - "context" - "log/slog" - "os" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/vladimirvivien/go4vl/device" - - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" -) - -func skipCI(t *testing.T) { - t.Helper() - - if os.Getenv("CI") != "" { - t.Skip("Skipping testing in CI environment") - } -} - -func skipContainer(t *testing.T) { - t.Helper() - - if os.Getenv("DEVCONTAINER") != "" { - t.Skip("Skipping testing in dev container environment") - } -} - -func TestNewCameraControl(t *testing.T) { - msgCh := make(chan *mqttapi.Msg) - defer close(msgCh) - type args struct { - ctx context.Context - msgCh chan *mqttapi.Msg - mqttDevice *mqtthass.Device - } - tests := []struct { - args args - want *CameraEntities - name string - }{ - { - name: "successful", - args: args{ctx: context.TODO(), msgCh: msgCh, mqttDevice: &mqtthass.Device{Name: "test"}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewCameraControl(tt.args.ctx, tt.args.msgCh, tt.args.mqttDevice) - assert.NotNil(t, got.Status) - state := <-msgCh - assert.Equal(t, []byte(stoppedState), state.Message) - assert.NotNil(t, got.Images) - assert.NotNil(t, got.StartButton) - assert.NotNil(t, got.StopButton) - }) - } -} - -func Test_newCamera(t *testing.T) { - type args struct { - ctx context.Context - } - tests := []struct { - args args - want *cameraControl - name string - }{ - { - name: "successful", - args: args{ctx: context.TODO()}, - want: &cameraControl{ - state: stoppedState, - logger: slog.Default().WithGroup("camera"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := newCamera(tt.args.ctx) - assert.NotNil(t, got.logger) - assert.Equal(t, stoppedState, got.state) - }) - } -} - -func Test_cameraControl_openCamera(t *testing.T) { - skipCI(t) - skipContainer(t) - type fields struct { - device *device.Device - cancelFunc context.CancelFunc - logger *slog.Logger - state string - fps time.Duration - } - type args struct { - cameraDevice string - } - tests := []struct { - name string - args args - fields fields - wantErr bool - }{ - { - name: "valid device", - args: args{cameraDevice: defaultDevice}, - fields: fields{logger: slog.Default()}, - }, - { - name: "invalid device", - args: args{cameraDevice: "/dev/null"}, - fields: fields{logger: slog.Default()}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &cameraControl{ - device: tt.fields.device, - cancelFunc: tt.fields.cancelFunc, - logger: tt.fields.logger, - state: tt.fields.state, - fps: tt.fields.fps, - } - if err := c.openCamera(tt.args.cameraDevice); (err != nil) != tt.wantErr { - t.Errorf("cameraControl.openCamera() error = %v, wantErr %v", err, tt.wantErr) - } - if !tt.wantErr { - assert.NotNil(t, c.device) - assert.NotZero(t, c.fps) - err := c.closeCamera() - require.NoError(t, err) - } - }) - } -} - -func Test_cameraControl_publishImages(t *testing.T) { - skipCI(t) - skipContainer(t) - camera := newCamera(context.TODO()) - err := camera.openCamera(defaultDevice) - require.NoError(t, err) - defer camera.closeCamera() //nolint:errcheck - msgCh := make(chan *mqttapi.Msg) - ctx, cancelFunc := context.WithCancel(context.TODO()) - camera.cancelFunc = cancelFunc - - type args struct { - ctx context.Context - msgCh chan *mqttapi.Msg - topic string - } - tests := []struct { - args args - name string - }{ - { - name: "successful", - args: args{ctx: ctx, msgCh: msgCh}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - t.Log("Getting camera images...") - var i int - for range msgCh { - if i == 60 { - camera.cancelFunc() - } - i++ - } - }() - go func() { - defer close(msgCh) - camera.publishImages(tt.args.ctx, tt.args.topic, tt.args.msgCh) //revive:disable:datarace - }() - wg.Wait() - }) - } -} - -func Test_cameraControl_closeCamera(t *testing.T) { - skipCI(t) - skipContainer(t) - camera := newCamera(context.TODO()) - err := camera.openCamera(defaultDevice) - require.NoError(t, err) - - type fields struct { - camera *cameraControl - } - tests := []struct { - fields fields - name string - wantErr bool - }{ - { - name: "successful", - fields: fields{camera: camera}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := tt.fields.camera - if err := c.closeCamera(); (err != nil) != tt.wantErr { - t.Errorf("cameraControl.closeCamera() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/linux/media/mpris.go b/internal/linux/media/mpris.go index 64cc97fac..a0e4b0013 100644 --- a/internal/linux/media/mpris.go +++ b/internal/linux/media/mpris.go @@ -11,8 +11,8 @@ import ( "fmt" "log/slog" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/linux" "github.com/joshuar/go-hass-agent/internal/logging" @@ -48,14 +48,19 @@ func MPRISControl(ctx context.Context, device *mqtthass.Device, msgCh chan *mqtt msgCh: msgCh, } - mprisMonitor.mediaStateEntity = mqtthass.AsSensor( - mqtthass.NewEntity(preferences.AppName, "Media State", device.Name+"_media_state"). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(device). - WithIcon(mediaOffIcon). - WithValueTemplate("{{ value_json.value }}"). - WithStateCallback(mprisMonitor.mprisStateCallback), - ) + mprisMonitor.mediaStateEntity = mqtthass.NewSensorEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Media State"), + mqtthass.ID(device.Name+"_media_state"), + mqtthass.OriginInfo(preferences.MQTTOrigin()), + mqtthass.DeviceInfo(device), + mqtthass.Icon(mediaOffIcon), + ). + WithState( + mqtthass.StateCallback(mprisMonitor.mprisStateCallback), + mqtthass.ValueTemplate("{{ value_json.value }}"), + ) triggerCh, err := dbusx.NewWatch( dbusx.MatchPath(mprisDBusPath), diff --git a/internal/linux/media/volume.go b/internal/linux/media/volume.go index 455ea2b99..d1ec5ed3c 100644 --- a/internal/linux/media/volume.go +++ b/internal/linux/media/volume.go @@ -14,8 +14,8 @@ import ( "github.com/eclipse/paho.golang/paho" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/logging" "github.com/joshuar/go-hass-agent/internal/preferences" @@ -55,25 +55,42 @@ func VolumeControl(ctx context.Context, msgCh chan *mqttapi.Msg, device *mqtthas return nil, nil } - control.volEntity = mqtthass.AsNumber( - mqtthass.NewEntity(preferences.AppName, "Volume", device.Name+"_volume"). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(device). - WithIcon(volIcon). - WithCommandCallback(control.volCommandCallback). - WithStateCallback(control.volStateCallback). - WithValueTemplate("{{ value_json.value }}"), - volStepPc, minVolpc, maxVolpc, mqtthass.NumberSlider) - - control.muteEntity = mqtthass.AsSwitch( - mqtthass.NewEntity(preferences.AppName, "Mute", device.Name+"_mute"). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(device). - WithIcon(muteIcon). - WithCommandCallback(control.muteCommandCallback). - WithStateCallback(control.muteStateCallback). - WithValueTemplate("{{ value }}"), - false).AsTypeSwitch() + control.volEntity = mqtthass.NewNumberEntity[int](). + WithMin(minVolpc). + WithMax(maxVolpc). + WithStep(volStepPc). + WithMode(mqtthass.NumberSlider). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Volume"), + mqtthass.ID(device.Name+"_volume"), + mqtthass.DeviceInfo(device), + mqtthass.Icon(volIcon), + ). + WithState( + mqtthass.StateCallback(control.volStateCallback), + mqtthass.ValueTemplate("{{ value_json.value }}"), + ). + WithCommand( + mqtthass.CommandCallback(control.volCommandCallback), + ) + + control.muteEntity = mqtthass.NewSwitchEntity(). + OptimisticMode(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name("Mute"), + mqtthass.ID(device.Name+"_mute"), + mqtthass.DeviceInfo(device), + mqtthass.Icon(muteIcon), + ). + WithState( + mqtthass.StateCallback(control.muteStateCallback), + mqtthass.ValueTemplate("{{ value }}"), + ). + WithCommand( + mqtthass.CommandCallback(control.muteCommandCallback), + ) update := func() { // Pulseaudio changed state. Get the new state. // Publish and update mute state if it changed. diff --git a/internal/linux/power/powerControl.go b/internal/linux/power/powerControl.go index 3d1be97d3..0369b96cf 100644 --- a/internal/linux/power/powerControl.go +++ b/internal/linux/power/powerControl.go @@ -11,7 +11,7 @@ import ( "github.com/eclipse/paho.golang/paho" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" "github.com/joshuar/go-hass-agent/internal/linux" "github.com/joshuar/go-hass-agent/internal/logging" @@ -103,12 +103,16 @@ func NewPowerControl(ctx context.Context, device *mqtthass.Device) ([]*mqtthass. for _, command := range commands { entities = append(entities, - mqtthass.AsButton( - mqtthass.NewEntity(preferences.AppName, command.name, command.id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(device). - WithIcon(command.icon). - WithCommandCallback(command.callBack))) + mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(command.name), + mqtthass.ID(command.id), + mqtthass.DeviceInfo(device), + mqtthass.Icon(command.icon), + ). + WithCommand(mqtthass.CommandCallback(command.callBack)), + ) } return entities, nil diff --git a/internal/linux/power/screenLockControl.go b/internal/linux/power/screenLockControl.go index 49c8c0b97..ce42f8613 100644 --- a/internal/linux/power/screenLockControl.go +++ b/internal/linux/power/screenLockControl.go @@ -14,8 +14,7 @@ import ( "strings" "github.com/eclipse/paho.golang/paho" - - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" "github.com/joshuar/go-hass-agent/internal/linux" "github.com/joshuar/go-hass-agent/internal/logging" @@ -145,21 +144,27 @@ func NewScreenLockControl(ctx context.Context, device *mqtthass.Device) ([]*mqtt buttons := make([]*mqtthass.ButtonEntity, 0, len(commands)) for _, command := range commands { - buttons = append(buttons, mqtthass.AsButton( - mqtthass.NewEntity(preferences.AppName, command.name, command.id). - WithOriginInfo(preferences.MQTTOrigin()). - WithDeviceInfo(device). - WithIcon(command.icon). - WithCommandCallback(func(_ *paho.Publish) { - if err := command.execute(ctx); err != nil { - logger.Error("Could not execute screen control command.", - slog.String("name", command.name), - slog.String("path", command.path), - slog.String("interface", command.intr), - slog.String("method", command.method), - slog.Any("error", err)) - } - })), + buttons = append(buttons, + mqtthass.NewButtonEntity(). + WithDetails( + mqtthass.App(preferences.AppName), + mqtthass.Name(command.name), + mqtthass.ID(command.id), + mqtthass.DeviceInfo(device), + mqtthass.Icon(command.icon), + ). + WithCommand( + mqtthass.CommandCallback(func(_ *paho.Publish) { + if err := command.execute(ctx); err != nil { + logger.Error("Could not execute screen control command.", + slog.String("name", command.name), + slog.String("path", command.path), + slog.String("interface", command.intr), + slog.String("method", command.method), + slog.Any("error", err)) + } + }), + ), ) } diff --git a/internal/linux/system/dbusCommand.go b/internal/linux/system/dbusCommand.go index ed08010b6..8285e4f0a 100644 --- a/internal/linux/system/dbusCommand.go +++ b/internal/linux/system/dbusCommand.go @@ -12,8 +12,8 @@ import ( "github.com/eclipse/paho.golang/paho" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/linux" "github.com/joshuar/go-hass-agent/internal/logging" diff --git a/internal/preferences/mqtt.go b/internal/preferences/mqtt.go index b7005c72e..e2692d5d6 100644 --- a/internal/preferences/mqtt.go +++ b/internal/preferences/mqtt.go @@ -8,8 +8,8 @@ package preferences import ( "context" - mqtthass "github.com/joshuar/go-hass-anything/v11/pkg/hass" - mqttapi "github.com/joshuar/go-hass-anything/v11/pkg/mqtt" + mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass" + mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt" "github.com/joshuar/go-hass-agent/internal/device" )