diff --git a/cmd/discovery.go b/cmd/discovery.go new file mode 100644 index 0000000..9bfeb4a --- /dev/null +++ b/cmd/discovery.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "os" + "os/signal" + + "github.com/jcodybaker/shellyctl/pkg/discovery" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +func init() { + discoveryFlags(discoveryCmd.Flags(), true) + rootCmd.AddCommand(discoveryCmd) +} + +var discoveryCmd = &cobra.Command{ + Use: "discovery", + Short: "List discoverable devices.", + Run: func(cmd *cobra.Command, args []string) { + ctx, signalStop := signal.NotifyContext(ctx, os.Interrupt) + defer signalStop() + + l := log.Ctx(ctx) + + dOpts, err := discoveryOptionsFromFlags() + if err != nil { + l.Fatal().Err(err).Msg("parsing flags") + } + disc := discovery.NewDiscoverer(dOpts...) + if err := discoveryAddDevices(ctx, disc); err != nil { + l.Fatal().Err(err).Msg("adding devices") + } + + for _, mac := range bleDevices { + if _, err := disc.AddBLE(ctx, mac); err != nil { + l.Fatal().Err(err).Msg("adding BLE device") + } + } + + if bleSearch { + if err := disc.SearchBLE(ctx); err != nil { + l.Fatal().Err(err).Msg("searching bluetooth") + } + } + + }, +} diff --git a/cmd/gen.go b/cmd/gen.go index 000ab29..b25cac6 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -212,7 +212,7 @@ func init() { } baggage.Discoverer = discovery.NewDiscoverer(dOpts...) - if err := discoveryAddHosts(ctx, baggage.Discoverer); err != nil { + if err := discoveryAddDevices(ctx, baggage.Discoverer); err != nil { l.Fatal().Err(err).Msg("adding devices") } return childRun(cmd, args) diff --git a/cmd/helper_discovery.go b/cmd/helper_discovery.go index 1412d9e..ba00929 100644 --- a/cmd/helper_discovery.go +++ b/cmd/helper_discovery.go @@ -16,6 +16,8 @@ import ( var ( hosts []string mdnsSearch bool + bleSearch bool + bleDevices []string mdnsInterface string mdnsZone string mdnsService string @@ -40,6 +42,18 @@ func discoveryFlags(f *pflag.FlagSet, withTTL bool) { false, "if true, devices will be discovered via mDNS") + f.BoolVar( + &bleSearch, + "ble-search", + false, + "if true, devices will be discovered via Bluetooth Low-Energy") + + f.StringArrayVar( + &bleDevices, + "ble-device", + []string{}, + "MAC address of a single bluetooth low-energy device. May be specified multiple times to work with multiple devices.") + f.StringVar( &mdnsInterface, "mdns-interface", @@ -96,7 +110,7 @@ func discoveryFlags(f *pflag.FlagSet, withTTL bool) { } func discoveryOptionsFromFlags() (opts []discovery.DiscovererOption, err error) { - if len(hosts) == 0 && !mdnsSearch { + if len(hosts) == 0 && len(bleDevices) == 0 && !mdnsSearch && !bleSearch { return nil, errors.New("no hosts and or discovery (mDNS)") } if mdnsInterface != "" { @@ -125,12 +139,27 @@ func discoveryOptionsFromFlags() (opts []discovery.DiscovererOption, err error) return opts, err } -func discoveryAddHosts(ctx context.Context, d *discovery.Discoverer) error { +func discoveryAddDevices(ctx context.Context, d *discovery.Discoverer) error { l := log.Ctx(ctx) var wg sync.WaitGroup concurrencyLimit := make(chan struct{}, discoveryConcurrency) defer close(concurrencyLimit) defer wg.Wait() + if len(bleDevices) > 0 { + select { + case concurrencyLimit <- struct{}{}: + case <-ctx.Done(): + return ctx.Err() + } + wg.Add(1) + go func() { + defer func() { + wg.Done() + <-concurrencyLimit + }() + discoveryAddBLEDevices(ctx, d) + }() + } for _, h := range hosts { // This chan send will block if the we exceed discoveryConcurrency. select { @@ -155,3 +184,21 @@ func discoveryAddHosts(ctx context.Context, d *discovery.Discoverer) error { } return nil } + +func discoveryAddBLEDevices(ctx context.Context, d *discovery.Discoverer) error { + l := log.Ctx(ctx) + for _, mac := range bleDevices { + if err := ctx.Err(); err != nil { + return err + } + _, err := d.AddBLE(ctx, mac) + if err == nil { + continue + } + if !skipFailedHosts { + l.Fatal().Err(err).Msg("adding device") + } + l.Warn().Err(err).Msg("adding device; continuing because `skip-failed-hosts=true`") + } + return nil +} diff --git a/cmd/prometheus.go b/cmd/prometheus.go index 4786603..e6d4f87 100644 --- a/cmd/prometheus.go +++ b/cmd/prometheus.go @@ -56,7 +56,7 @@ var prometheusCmd = &cobra.Command{ l.Fatal().Err(err).Msg("parsing flags") } disc := discovery.NewDiscoverer(dOpts...) - if err := discoveryAddHosts(ctx, disc); err != nil { + if err := discoveryAddDevices(ctx, disc); err != nil { l.Fatal().Err(err).Msg("adding devices") } diff --git a/go.mod b/go.mod index fcdcfe3..e7ee010 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,11 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/eclipse/paho.mqtt.golang v1.4.3 // indirect + github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -43,6 +46,7 @@ require ( github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/miekg/dns v1.1.41 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.18.0 // indirect @@ -51,6 +55,8 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -58,6 +64,7 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tinygo-org/cbgo v0.0.4 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect @@ -79,4 +86,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect + tinygo.org/x/bluetooth v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index cd4769d..54dc1f6 100644 --- a/go.sum +++ b/go.sum @@ -318,6 +318,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.1-0.20181010231311-3f9d52f7176a/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= @@ -357,6 +359,8 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs= github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -371,9 +375,12 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= @@ -642,6 +649,8 @@ github.com/mongoose-os/mos v0.0.0-20230313140341-b44964e63a92 h1:uk50uLxsSRtEMKM github.com/mongoose-os/mos v0.0.0-20230313140341-b44964e63a92/go.mod h1:02cswnce2ybKkrJdLrXLnAxkWaqtWcIKpNnl69Dr5xc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 h1:BuVRHr4HHJbk1DHyWkArJ7E8J/VA8ncCr/VLnQFazBo= +github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -691,6 +700,7 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= @@ -756,6 +766,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1 h1:L2YoWezgwpAZ2SEKjXk6yLnwOkM3u7mXq/mKuJeEpFM= +github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= @@ -767,9 +779,12 @@ github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -821,12 +836,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU= +github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -1125,6 +1143,7 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 h1:KzbpndAYEM+4oHRp9JmB2ewj0NHHxO3Z0g7Gus2O1kk= golang.org/x/sys v0.0.0-20211015200801-69063c4bb744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1207,6 +1226,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1473,3 +1493,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +tinygo.org/x/bluetooth v0.8.0 h1:WmuRebsODcUUIlGhesyuNRIAEIUCErhKlrZ9K9aimdI= +tinygo.org/x/bluetooth v0.8.0/go.mod h1:cfsVc0/nGo3nzi6+CeQaXb+anNlmEnSABkKsxer8OAE= diff --git a/pkg/discovery/ble.go b/pkg/discovery/ble.go new file mode 100644 index 0000000..ff489bc --- /dev/null +++ b/pkg/discovery/ble.go @@ -0,0 +1,300 @@ +package discovery + +import ( + "context" + "crypto/rand" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/mongoose-os/mos/common/mgrpc" + "github.com/mongoose-os/mos/common/mgrpc/codec" + "github.com/mongoose-os/mos/common/mgrpc/frame" + "github.com/rs/zerolog/log" + "tinygo.org/x/bluetooth" +) + +var ( + // https://github.com/mongoose-os-libs/rpc-gatts + mongooseGATTServiceID bluetooth.UUID + frameDataCharacteristic bluetooth.UUID + frameControlTxCharacteristic bluetooth.UUID + frameControlRxCharacteristic bluetooth.UUID + + bleMGRPCID int64 +) + +func init() { + var err error + mongooseGATTServiceID, err = bluetooth.ParseUUID("5f6d4f53-5f52-5043-5f53-56435f49445f") + if err != nil { + panic(fmt.Sprintf("parsing BLE service UUID: %v", err)) + } + frameDataCharacteristic, err = bluetooth.ParseUUID("5f6d4f53-5f52-5043-5f64-6174615f5f5f") + if err != nil { + panic(fmt.Sprintf("parsing BLE service UUID: %v", err)) + } + frameControlTxCharacteristic, err = bluetooth.ParseUUID("5f6d4f53-5f52-5043-5f74-785f63746c5f") + if err != nil { + panic(fmt.Sprintf("parsing BLE service UUID: %v", err)) + } + frameControlRxCharacteristic, err = bluetooth.ParseUUID("5f6d4f53-5f52-5043-5f72-785f63746c5f") + if err != nil { + panic(fmt.Sprintf("parsing BLE service UUID: %v", err)) + } + initialID, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) + if err != nil { + panic(fmt.Sprintf("initializing mGRPC ID: %v", err)) + } + bleMGRPCID = initialID.Int64() +} + +func (d *Discoverer) SearchBLE(ctx context.Context) error { + if err := d.enableBLEAdapter(); err != nil { + return err + } + var wg sync.WaitGroup + defer wg.Wait() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + ll := log.Ctx(ctx).With().Str("component", "discovery").Str("subcomponent", "ble").Logger() + + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + if err := d.bleAdapter.StopScan(); err != nil { + ll.Err(err).Msg("stopping BLE scan") + } + }() + err := d.bleAdapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) { + if sr.Address.String() != "D4:D4:DA:09:2E:B6" { + return + } + ll.Info(). + Str("ble_address", sr.Address.String()). + Str("ble_local_name", sr.LocalName()). + Msg("found device") + }) + return err +} + +func (d *Discoverer) AddBLE(ctx context.Context, mac string) (*Device, error) { + if err := d.enableBLEAdapter(); err != nil { + return nil, err + } + return d.addDevice(&Device{ + MACAddr: mac, + ble: &BLEDevice{ + bleAdapter: d.bleAdapter, + }, + }), nil +} + +type BLEDevice struct { + lock sync.Mutex + bleAdapter *bluetooth.Adapter + device *bluetooth.Device + service bluetooth.DeviceService + frameChar bluetooth.DeviceCharacteristic + txChar bluetooth.DeviceCharacteristic + rxChar bluetooth.DeviceCharacteristic +} + +var _ mgrpc.MgRPC = &BLEDevice{} + +func (b *BLEDevice) open(ctx context.Context, mac string) error { + ll := log.Ctx(ctx).With().Str("component", "discovery").Str("subcomponent", "ble").Logger() + device, err := b.searchForBLEDevice(ctx, mac, 30*time.Second) + if err != nil { + return fmt.Errorf("connecting to BLE device: %w", err) + } + if device == nil { + return errors.New("failed to find device") + } + services, err := device.DiscoverServices([]bluetooth.UUID{mongooseGATTServiceID}) + if err != nil { + return fmt.Errorf("discovering BLE services: %w", err) + } + if len(services) == 0 { + return errors.New("device is BLE RPC service") + } + b.lock.Lock() + defer b.lock.Unlock() + b.service = services[0] + + ll.Debug().Str("service", b.service.String()).Msg("found service") + chars, err := b.service.DiscoverCharacteristics(nil) + if err != nil { + return fmt.Errorf("reading characteristics: %w", err) + } + + for _, c := range chars { + ll.Debug().Str("service", b.service.String()). + Str("characteristic", c.String()). + Msg("found characteristic") + if c.UUID() == frameDataCharacteristic { + b.frameChar = c + } + if c.UUID() == frameControlRxCharacteristic { + b.rxChar = c + } + if c.UUID() == frameControlTxCharacteristic { + b.txChar = c + } + } + + if b.frameChar.UUID() == (bluetooth.UUID{}) { + return errors.New("BLE RPC service is missing data characteristic") + } + if b.txChar.UUID() == (bluetooth.UUID{}) { + return errors.New("BLE RPC service is missing tx characteristic") + } + if b.rxChar.UUID() == (bluetooth.UUID{}) { + return errors.New("BLE RPC service is missing rx characteristic") + } + return nil +} + +func (b *BLEDevice) searchForBLEDevice(ctx context.Context, mac string, timeout time.Duration) (device *bluetooth.Device, err error) { + var wg sync.WaitGroup + defer wg.Wait() + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + ll := log.Ctx(ctx).With().Str("component", "discovery").Str("subcomponent", "ble").Logger() + + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + if err := b.bleAdapter.StopScan(); err != nil { + ll.Err(err).Msg("stopping BLE scan") + } + }() + + err = b.bleAdapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) { + if !strings.EqualFold(sr.Address.String(), mac) { + return + } + ll.Info(). + Str("ble_address", sr.Address.String()). + Str("ble_local_name", sr.LocalName()). + Msg("found device") + var err error + device, err = b.bleAdapter.Connect(sr.Address, bluetooth.ConnectionParams{}) + if err != nil { + ll.Err(err).Msg("connecting to bluetooth device") + } + cancel() + }) + return device, err +} + +func (b *BLEDevice) Call( + ctx context.Context, dst string, cmd *frame.Command, getCreds mgrpc.GetCredsCallback, +) (*frame.Response, error) { + ll := log.Ctx(ctx).With(). + Str("component", "discovery"). + Str("subcomponent", "ble"). + Str("method", cmd.Cmd).Logger() + cmd.ID = atomic.AddInt64(&bleMGRPCID, 1) + reqFrame := frame.NewRequestFrame("shellyctl", "", "", cmd, false) + reqFrameBytes, err := json.Marshal(reqFrame) + if err != nil { + return nil, fmt.Errorf("encoding command: %w", err) + } + reqFrameLen := make([]byte, 4) + binary.BigEndian.PutUint32(reqFrameLen, uint32(len(reqFrameBytes))) + ll.Debug().Hex("command length", reqFrameLen).Msg("encoding command length") + if _, err := b.txChar.WriteWithoutResponse(reqFrameLen); err != nil { + return nil, fmt.Errorf("writing tx length: %w", err) + } + err = b.rxChar.EnableNotifications(func(buf []byte) { + ll.Debug().Int("response length", len(buf)).Msg("got response length") + }) + ll.Debug().Str("characteristic", b.rxChar.String()).Msg("enable notifications") + if err != nil { + return nil, fmt.Errorf("enabling notifications: %w", err) + } + ll.Debug().Str("characteristic", b.txChar.String()).Msg("sent tx length") + if _, err := b.frameChar.WriteWithoutResponse(reqFrameBytes); err != nil { + return nil, fmt.Errorf("writing frame: %w", err) + } + mtu, err := b.frameChar.GetMTU() + if err != nil { + return nil, fmt.Errorf("getting mtu: %w", err) + } + ll.Debug().Str("characteristic", b.frameChar.String()). + Str("frame", string(reqFrameBytes)). + Uint16("mtu", mtu). + Msg("sent frame") + t := time.NewTicker(250 * time.Millisecond) + respFrameLenRaw := make([]byte, 4) + var respFrameLen uint32 + for { + select { + case <-t.C: + case <-ctx.Done(): + return nil, errors.New("nope") + } + _, err := b.rxChar.Read(respFrameLenRaw) + if err != nil { + ll.Err(err).Msg("reading response length") + continue + } + respFrameLen = binary.BigEndian.Uint32(respFrameLenRaw) + ll.Debug().Uint32("response_length", respFrameLen).Hex("response_len", respFrameLenRaw).Msg("got response length") + break + } + respBuf := make([]byte, respFrameLen) + for readBytes := 0; readBytes < int(respFrameLen); { + n, err := b.frameChar.Read(respBuf[readBytes:]) + if err != nil { + ll.Err(err).Msg("reading response") + continue + } + readBytes += n + ll.Debug().Str("resp", string(respBuf[0:readBytes])).Msg("got partial message") + } + ll.Info().Str("resp", string(respBuf)).Msg("message is complete") + respFrame := &frame.Frame{} + if err = json.Unmarshal(respBuf, &respFrame); err != nil { + return nil, fmt.Errorf("parsing response message: %w", err) + } + resp := frame.NewResponseFromFrame(respFrame) + if resp.ID != reqFrame.ID { + return nil, fmt.Errorf("response id %d did not match request %d", resp.ID, reqFrame.ID) + } + return resp, nil +} + +func (b *BLEDevice) AddHandler(method string, handler mgrpc.Handler) { +} + +func (b *BLEDevice) Disconnect(ctx context.Context) error { + b.lock.Lock() + defer b.lock.Unlock() + device := b.device + b.device = nil + if device == nil { + return nil + } + return device.Disconnect() +} + +func (b *BLEDevice) IsConnected() bool { + b.lock.Lock() + defer b.lock.Unlock() + return b.device != nil +} + +func (b *BLEDevice) SetCodecOptions(opts *codec.Options) error { + return nil +} diff --git a/pkg/discovery/device.go b/pkg/discovery/device.go index 0094543..d0cdbca 100644 --- a/pkg/discovery/device.go +++ b/pkg/discovery/device.go @@ -3,6 +3,7 @@ package discovery import ( "context" "fmt" + "net/url" "time" "github.com/jcodybaker/go-shelly" @@ -11,16 +12,23 @@ import ( // Device describes one shelly device. type Device struct { - URI string + uri string MACAddr string Specs shelly.DeviceSpecs lastSeen time.Time source discoverySource + ble *BLEDevice } // Open creates an mongoose rpc channel to the device. func (d *Device) Open(ctx context.Context) (mgrpc.MgRPC, error) { - c, err := mgrpc.New(ctx, d.URI, mgrpc.UseHTTPPost()) + if d.ble != nil { + if err := d.ble.open(ctx, d.MACAddr); err != nil { + return nil, err + } + return d.ble, nil + } + c, err := mgrpc.New(ctx, d.uri, mgrpc.UseHTTPPost()) if err != nil { return nil, fmt.Errorf("establishing rpc channel: %w", err) } @@ -45,3 +53,10 @@ func (d *Device) resolveSpecs(ctx context.Context) error { d.MACAddr = resp.MAC return nil } + +func (d *Device) Instance() string { + if d.ble != nil { + return (&url.URL{Scheme: "ble", Host: d.MACAddr}).String() + } + return d.uri +} diff --git a/pkg/discovery/discovery.go b/pkg/discovery/discovery.go index 2c63c97..6fe752d 100644 --- a/pkg/discovery/discovery.go +++ b/pkg/discovery/discovery.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - "net" "net/url" "strings" "sync" "time" "github.com/hashicorp/mdns" + "github.com/rs/zerolog/log" + "tinygo.org/x/bluetooth" ) const ( @@ -25,43 +26,34 @@ const ( func NewDiscoverer(opts ...DiscovererOption) *Discoverer { d := &Discoverer{ - deviceTTL: DefaultDeviceTTL, - now: time.Now, - knownDevices: make(map[string]*Device), - mdnsZone: DefaultMDNSZone, - mdnsService: DefaultMDNSService, - searchTimeout: DefaultMDNSSearchTimeout, - concurrency: DefaultConcurrency, - mdnsQueryFunc: mdns.Query, + knownDevices: make(map[string]*Device), + options: &options{ + bleAdapter: bluetooth.DefaultAdapter, + now: time.Now, + deviceTTL: DefaultDeviceTTL, + mdnsZone: DefaultMDNSZone, + mdnsService: DefaultMDNSService, + searchTimeout: DefaultMDNSSearchTimeout, + concurrency: DefaultConcurrency, + mdnsQueryFunc: mdns.Query, + }, } for _, o := range opts { o(d) } + d.enableBLEAdapter = sync.OnceValue[error](func() error { + log.Logger.Debug().Msg("enabling BLE adapter") + return d.bleAdapter.Enable() + }) return d } // Discoverer finds shelly gen 2 devices and provides basic metadata. type Discoverer struct { + *options knownDevices map[string]*Device - mdnsInterface *net.Interface - mdnsZone string - mdnsService string - mdnsEnabled bool - - searchTimeout time.Duration - concurrency int - - preferIPVersion string - - mdnsQueryFunc func(*mdns.QueryParam) error - now func() time.Time - lock sync.Mutex - - // deviceTTL is relevant for long-lived commands (like prometheus metrics server) when - // mixed with mDNS or other ephemeral discovery. - deviceTTL time.Duration } // AddDeviceByAddress attempts to parse a user-provided URI and add the device. @@ -98,13 +90,18 @@ func (d *Discoverer) AddDeviceByAddress(ctx context.Context, addr string, opts . } dev := &Device{ - URI: u.String(), + uri: u.String(), source: sourceManual, } if err = dev.resolveSpecs(ctx); err != nil { return nil, err } dev.lastSeen = d.now() + dev = d.addDevice(dev, opts...) + return dev, nil +} + +func (d *Discoverer) addDevice(dev *Device, opts ...DeviceOption) *Device { for _, o := range opts { o(dev) } @@ -112,10 +109,10 @@ func (d *Discoverer) AddDeviceByAddress(ctx context.Context, addr string, opts . defer d.lock.Unlock() if existingDev, ok := d.knownDevices[dev.MACAddr]; ok { existingDev.lastSeen = dev.lastSeen - return existingDev, nil + return existingDev } d.knownDevices[dev.MACAddr] = dev - return dev, nil + return dev } // AllDevices returns all known devices. diff --git a/pkg/discovery/options.go b/pkg/discovery/options.go index 554cdb7..6b7c445 100644 --- a/pkg/discovery/options.go +++ b/pkg/discovery/options.go @@ -3,8 +3,33 @@ package discovery import ( "net" "time" + + "github.com/hashicorp/mdns" + "tinygo.org/x/bluetooth" ) +type options struct { + bleAdapter *bluetooth.Adapter + enableBLEAdapter func() error + now func() time.Time + + mdnsInterface *net.Interface + mdnsZone string + mdnsService string + mdnsEnabled bool + + searchTimeout time.Duration + concurrency int + + // deviceTTL is relevant for long-lived commands (like prometheus metrics server) when + // mixed with mDNS or other ephemeral discovery. + deviceTTL time.Duration + + preferIPVersion string + + mdnsQueryFunc func(*mdns.QueryParam) error +} + // DiscovererOption provides optional parameters for the Discoverer. type DiscovererOption func(*Discoverer) @@ -66,4 +91,11 @@ func WithMDNSSearchEnabled(enabled bool) DiscovererOption { } } +// WithBLEAdapter configures a BLE adapter for use in discovery. +func WithBLEAdapter(ble *bluetooth.Adapter) DiscovererOption { + return func(d *Discoverer) { + d.bleAdapter = ble + } +} + type DeviceOption func(*Device) diff --git a/pkg/discovery/test_harness.go b/pkg/discovery/test_harness.go index 295b751..b512c7d 100644 --- a/pkg/discovery/test_harness.go +++ b/pkg/discovery/test_harness.go @@ -64,7 +64,7 @@ func (td *TestDiscoverer) NewTestDevice(t *testing.T, add bool) *TestDevice { u, err := url.Parse(d.s.URL) require.NoError(t, err) u.Path = "/rpc" - d.URI = u.String() + d.uri = u.String() t.Cleanup(d.Shutdown) if add { td.knownDevices[d.MACAddr] = d.Device diff --git a/pkg/gencobra/gencobra.go b/pkg/gencobra/gencobra.go index ab9133b..da0209f 100644 --- a/pkg/gencobra/gencobra.go +++ b/pkg/gencobra/gencobra.go @@ -58,7 +58,7 @@ func RequestToCmd(req shelly.RPCRequestBody, baggage *Baggage) (*cobra.Command, } for _, d := range baggage.Discoverer.AllDevices() { - ll := ll.With().Str("instance", d.URI).Logger() + ll := ll.With().Str("instance", d.Instance()).Logger() conn, err := d.Open(ctx) if err != nil { return err diff --git a/pkg/promserver/server.go b/pkg/promserver/server.go index 2b14431..32616ec 100644 --- a/pkg/promserver/server.go +++ b/pkg/promserver/server.go @@ -286,7 +286,7 @@ func (s *Server) Collect(ch chan<- prometheus.Metric) { func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan<- prometheus.Metric) { l := log.Ctx(ctx).With(). Str("mac", d.MACAddr). - Str("uri", d.URI). + Str("uri", d.Instance()). Logger() c, err := d.Open(s.ctx) if err != nil { @@ -334,7 +334,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.switchOutputOnDesc, prometheus.GaugeValue, ptrBoolToFloat64(sws.Output), - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -351,7 +351,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.totalEnergyWattHoursDesc, prometheus.CounterValue, sws.AEnergy.Total, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -369,7 +369,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.totalReturnedEnergyWattHoursDesc, prometheus.CounterValue, sws.RetAEnergy.Total, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -387,7 +387,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.temperatureCelsiusDesc, prometheus.GaugeValue, *sws.Temperature.C, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -405,7 +405,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.temperatureFahrenheitDesc, prometheus.GaugeValue, *sws.Temperature.F, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -423,7 +423,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.networkFrequencyHertzDesc, prometheus.GaugeValue, *sws.Freq, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -441,7 +441,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.powerFactorDesc, prometheus.GaugeValue, *sws.PF, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -459,7 +459,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.voltageDesc, prometheus.GaugeValue, *sws.Voltage, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -477,7 +477,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.currentAmperesDesc, prometheus.GaugeValue, *sws.Current, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -495,7 +495,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.instantaneousActivePowerWattsDesc, prometheus.GaugeValue, *sws.APower, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -535,7 +535,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.componentErrorDesc, prometheus.GaugeValue, eValue, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -568,7 +568,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.coverPositionDesc, prometheus.GaugeValue, currentPos, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -584,7 +584,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.coverPositionControlEnabled, prometheus.GaugeValue, ptrBoolToFloat64(cs.PosControl), - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -605,7 +605,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.coverStateDesc, prometheus.GaugeValue, stateActive, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -624,7 +624,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.totalEnergyWattHoursDesc, prometheus.CounterValue, cs.AEnergy.Total, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -642,7 +642,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.temperatureCelsiusDesc, prometheus.GaugeValue, *cs.Temperature.C, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -660,7 +660,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.temperatureFahrenheitDesc, prometheus.GaugeValue, *cs.Temperature.F, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -678,7 +678,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.networkFrequencyHertzDesc, prometheus.GaugeValue, *cs.Freq, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -696,7 +696,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.powerFactorDesc, prometheus.GaugeValue, *cs.PF, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -714,7 +714,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.voltageDesc, prometheus.GaugeValue, *cs.Voltage, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -732,7 +732,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.currentAmperesDesc, prometheus.GaugeValue, *cs.Current, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -750,7 +750,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.instantaneousActivePowerWattsDesc, prometheus.GaugeValue, *cs.APower, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -790,7 +790,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.componentErrorDesc, prometheus.GaugeValue, eValue, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -821,7 +821,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.inputEnabledDesc, prometheus.GaugeValue, ptrBoolToFloat64(ic.Enable), - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -839,7 +839,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.inputStateOnDesc, prometheus.GaugeValue, ptrBoolToFloat64(is.State), - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -857,7 +857,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.inputPercentDesc, prometheus.GaugeValue, *is.Percent, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -874,7 +874,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.inputXPercentDesc, prometheus.GaugeValue, *is.XPercent, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, @@ -913,7 +913,7 @@ func (s *Server) collectDevice(ctx context.Context, d *discovery.Device, ch chan s.componentErrorDesc, prometheus.GaugeValue, eValue, - d.URI, + d.Instance(), d.MACAddr, deviceName, componentName, diff --git a/pkg/promserver/server_test.go b/pkg/promserver/server_test.go index dbd52d6..3ab4041 100644 --- a/pkg/promserver/server_test.go +++ b/pkg/promserver/server_test.go @@ -745,7 +745,7 @@ shelly_status_voltage{component="switch",component_name="Lift Pump",device_name= require.NoError(t, err) t.Log(string(body)) - expect := strings.NewReplacer("$INSTANCE_DEVICE_1", d1.URI, "$MAC_DEVICE_1", d1.MACAddr).Replace(tc.expect) + expect := strings.NewReplacer("$INSTANCE_DEVICE_1", d1.Instance(), "$MAC_DEVICE_1", d1.MACAddr).Replace(tc.expect) require.NoError(t, testutil.ScrapeAndCompare(metricserver.URL, bytes.NewBufferString(expect))) }) }