feat(inputs.huebridge): Add plugin (#16352)

This commit is contained in:
Holger 2025-03-07 21:00:49 +01:00 committed by GitHub
parent 88f24052de
commit 666ada1a79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 931 additions and 4 deletions

View File

@ -30,7 +30,6 @@ Pull requests welcome.
- [knot](https://github.com/x70b1/telegraf-knot) - Collect stats from Knot DNS.
- [fritzbox](https://github.com/hdecarne-github/fritzbox-telegraf-plugin) - Gather statistics from [FRITZ!Box](https://avm.de/produkte/fritzbox/) router and repeater
- [linux-psi-telegraf-plugin](https://github.com/gridscale/linux-psi-telegraf-plugin) - Gather pressure stall information ([PSI](https://facebookmicrosites.github.io/psi/)) from the Linux Kernel
- [huebridge](https://github.com/hdecarne-github/huebridge-telegraf-plugin) - Gather smart home statistics from [Hue Bridge](https://www.philips-hue.com/) devices
- [hwinfo](https://github.com/zachstence/hwinfo-telegraf-plugin) - Gather Windows system hardware information from [HWiNFO](https://www.hwinfo.com/)
- [libvirt](https://gitlab.com/warrenio/tools/telegraf-input-libvirt) - Gather libvirt domain stats, based on a historical Telegraf implementation [libvirt](https://libvirt.org/)
- [bacnet](https://github.com/JurajMarcin/telegraf-bacnet) - Gather statistics from BACnet devices, with support for device discovery and Change of Value subscriptions

View File

@ -60,6 +60,7 @@ following works:
- github.com/apache/arrow/go [Apache License 2.0](https://github.com/apache/arrow/blob/master/LICENSE.txt)
- github.com/apache/iotdb-client-go [Apache License 2.0](https://github.com/apache/iotdb-client-go/blob/main/LICENSE)
- github.com/apache/thrift [Apache License 2.0](https://github.com/apache/thrift/blob/master/LICENSE)
- github.com/apapsch/go-jsonmerge [MIT License](https://github.com/apapsch/go-jsonmerge/blob/master/LICENSE)
- github.com/aristanetworks/glog [Apache License 2.0](https://github.com/aristanetworks/glog/blob/master/LICENSE)
- github.com/aristanetworks/goarista [Apache License 2.0](https://github.com/aristanetworks/goarista/blob/master/COPYING)
- github.com/armon/go-metrics [MIT License](https://github.com/armon/go-metrics/blob/master/LICENSE)
@ -96,6 +97,7 @@ following works:
- github.com/blues/jsonata-go [MIT License](https://github.com/blues/jsonata-go/blob/main/LICENSE)
- github.com/bmatcuk/doublestar [MIT License](https://github.com/bmatcuk/doublestar/blob/master/LICENSE)
- github.com/boschrexroth/ctrlx-datalayer-golang [MIT License](https://github.com/boschrexroth/ctrlx-datalayer-golang/blob/main/LICENSE)
- github.com/brutella/dnssd [MIT License](https://github.com/brutella/dnssd/blob/master/LICENSE)
- github.com/bufbuild/protocompile [Apache License 2.0](https://github.com/bufbuild/protocompile/blob/main/LICENSE)
- github.com/caio/go-tdigest [MIT License](https://github.com/caio/go-tdigest/blob/master/LICENSE)
- github.com/cenkalti/backoff [MIT License](https://github.com/cenkalti/backoff/blob/master/LICENSE)
@ -304,6 +306,7 @@ following works:
- github.com/newrelic/newrelic-telemetry-sdk-go [Apache License 2.0](https://github.com/newrelic/newrelic-telemetry-sdk-go/blob/master/LICENSE.md)
- github.com/nsqio/go-nsq [MIT License](https://github.com/nsqio/go-nsq/blob/master/LICENSE)
- github.com/nwaples/tacplus [BSD 2-Clause "Simplified" License](https://github.com/nwaples/tacplus/blob/master/LICENSE)
- github.com/oapi-codegen/runtime [Apache License 2.0](https://github.com/oapi-codegen/runtime/blob/main/LICENSE)
- github.com/olivere/elastic [MIT License](https://github.com/olivere/elastic/blob/release-branch.v7/LICENSE)
- github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil [Apache License 2.0](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/LICENSE)
- github.com/openconfig/gnmi [Apache License 2.0](https://github.com/openconfig/gnmi/blob/master/LICENSE)
@ -343,6 +346,7 @@ following works:
- github.com/rivo/uniseg [MIT License](https://github.com/rivo/uniseg/blob/master/LICENSE.txt)
- github.com/robbiet480/go.nut [MIT License](https://github.com/robbiet480/go.nut/blob/master/LICENSE)
- github.com/robinson/gos7 [BSD 3-Clause "New" or "Revised" License](https://github.com/robinson/gos7/blob/master/LICENSE)
- github.com/rs/zerolog [MIT License](https://github.com/rs/zerolog/blob/master/LICENSE)
- github.com/russross/blackfriday [BSD 2-Clause "Simplified" License](https://github.com/russross/blackfriday/blob/master/LICENSE.txt)
- github.com/safchain/ethtool [Apache License 2.0](https://github.com/safchain/ethtool/blob/master/LICENSE)
- github.com/samber/lo [MIT License](https://github.com/samber/lo/blob/master/LICENSE)
@ -368,6 +372,7 @@ following works:
- github.com/stoewer/go-strcase [MIT License](https://github.com/stoewer/go-strcase/blob/master/LICENSE)
- github.com/stretchr/objx [MIT License](https://github.com/stretchr/objx/blob/master/LICENSE)
- github.com/stretchr/testify [MIT License](https://github.com/stretchr/testify/blob/master/LICENSE)
- github.com/tdrn-org/go-hue [MIT License](https://github.com/tdrn-org/go-log/blob/main/LICENSE)
- github.com/tdrn-org/go-nsdp [MIT License](https://github.com/tdrn-org/go-nsdp/blob/main/LICENSE)
- github.com/testcontainers/testcontainers-go [MIT License](https://github.com/testcontainers/testcontainers-go/blob/main/LICENSE)
- github.com/thomasklein94/packer-plugin-libvirt [Mozilla Public License 2.0](https://github.com/thomasklein94/packer-plugin-libvirt/blob/main/LICENSE)
@ -446,6 +451,7 @@ following works:
- gopkg.in/gorethink/gorethink.v3 [Apache License 2.0](https://github.com/rethinkdb/rethinkdb-go/blob/v3.0.5/LICENSE)
- gopkg.in/inf.v0 [BSD 3-Clause "New" or "Revised" License](https://github.com/go-inf/inf/blob/v0.9.1/LICENSE)
- gopkg.in/ini.v1 [Apache License 2.0](https://github.com/go-ini/ini/blob/master/LICENSE)
- gopkg.in/natefinch/lumberjack.v2 [MIT License](https://github.com/natefinch/lumberjack/blob/v2.2.1/LICENSE)
- gopkg.in/olivere/elastic.v5 [MIT License](https://github.com/olivere/elastic/blob/v5.0.76/LICENSE)
- gopkg.in/tomb.v1 [BSD 3-Clause Clear License](https://github.com/go-tomb/tomb/blob/v1/LICENSE)
- gopkg.in/tomb.v2 [BSD 3-Clause Clear License](https://github.com/go-tomb/tomb/blob/v2/LICENSE)

6
go.mod
View File

@ -192,6 +192,7 @@ require (
github.com/srebhan/protobufquery v1.0.1
github.com/stretchr/testify v1.10.0
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62
github.com/tdrn-org/go-hue v0.3.0
github.com/testcontainers/testcontainers-go v0.35.0
github.com/testcontainers/testcontainers-go/modules/azurite v0.35.0
github.com/testcontainers/testcontainers-go/modules/kafka v0.34.0
@ -282,6 +283,7 @@ require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/awnumar/memcall v0.3.0 // indirect
@ -302,6 +304,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-hostpool v0.1.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/brutella/dnssd v1.2.14 // indirect
github.com/bufbuild/protocompile v0.10.0 // indirect
github.com/caio/go-tdigest/v4 v4.0.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
@ -406,7 +409,7 @@ require (
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-ieproxy v0.0.11 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@ -440,6 +443,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncw/swift/v2 v2.0.3 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.101.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect

17
go.sum
View File

@ -792,6 +792,7 @@ github.com/ProtonMail/gopenpgp/v2 v2.7.4 h1:Vz/8+HViFFnf2A6XX8JOvZMrA6F5puwNvvF2
github.com/ProtonMail/gopenpgp/v2 v2.7.4/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/SAP/go-hdb v1.9.10 h1:Smi3w0y8G9DVxR4z+Tvow8AJNqQq1fdCCMwplyapvR4=
github.com/SAP/go-hdb v1.9.10/go.mod h1:vxYDca44L2eRudZv5JAI6T+IygOfxb7vOCFh/Kj0pug=
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 h1:hhdWprfSpFbN7lz3W1gM40vOgvSh1WCSMxYD6gGB4Hs=
@ -853,6 +854,8 @@ github.com/apache/thrift v0.15.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc h1:LoL75er+LKDHDUfU5tRvFwxH0LjPpZN8OoG8Ll+liGU=
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc/go.mod h1:w648aMHEgFYS6xb0KVMMtZ2uMeemhiKCuD2vj6gY52A=
github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3 h1:Bmjk+DjIi3tTAU0wxGaFbfjGUqlxxSXARq9A96Kgoos=
@ -949,6 +952,8 @@ github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENU
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blues/jsonata-go v1.5.4 h1:XCsXaVVMrt4lcpKeJw6mNJHqQpWU751cnHdCFUq3xd8=
github.com/blues/jsonata-go v1.5.4/go.mod h1:uns2jymDrnI7y+UFYCqsRTEiAH22GyHnNXrkupAVFWI=
github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmatcuk/doublestar/v3 v3.0.0 h1:TQtVPlDnAYwcrVNB2JiGuMc++H5qzWZd9PhkNo5WyHI=
github.com/bmatcuk/doublestar/v3 v3.0.0/go.mod h1:6PcTVMw80pCY1RVuoqu3V++99uQB3vsSYKPTd8AWA0k=
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
@ -963,6 +968,8 @@ github.com/bradenaw/juniper v0.15.2 h1:0JdjBGEF2jP1pOxmlNIrPhAoQN7Ng5IMAY5D0PHMW
github.com/bradenaw/juniper v0.15.2/go.mod h1:UX4FX57kVSaDp4TPqvSjkAAewmRFAfXf27BOs5z9dq8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
github.com/brutella/dnssd v1.2.14 h1:qLpTnRTm5peo2jA30hqMIbCuWn8x3sFg3e9o9ODOobw=
github.com/brutella/dnssd v1.2.14/go.mod h1:tG4GE8orv6+irE5rdsNgb6MJSxm6cyMUKdC5jmD22gk=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@ -1700,6 +1707,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7 h1:JcltaO1HXM5S2KYOYcKgAV7slU0xPy1OcvrVgn98sRQ=
github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7/go.mod h1:MEkhEPFwP3yudWO0lj6vfYpLIB+3eIcuIW+e0AZzUQk=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@ -1788,8 +1796,8 @@ github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -1952,6 +1960,8 @@ github.com/nwaples/tacplus v0.0.3/go.mod h1:y5ZA9N5V2JbmwO766S+ET9zuu5FtL1OtdfBC
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
@ -2246,6 +2256,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/srebhan/cborquery v1.0.3 h1:eOg20ubM9K0PDS/sQb/B29wVAgix0te/UrBqcrZtMPg=
github.com/srebhan/cborquery v1.0.3/go.mod h1:lwe04aEn5nSy4qZcUNTiRBI2b5wcRtyUfr9+Kze58og=
github.com/srebhan/protobufquery v1.0.1 h1:V5NwX0GAQPPghWpoD9Pkm85j66CwISZ/zZW4grzayWs=
@ -2281,6 +2292,8 @@ github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7 h1:Jtcrb09q0AVWe3
github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62 h1:Oj2e7Sae4XrOsk3ij21QjjEgAcVSeo9nkp0dI//cD2o=
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62/go.mod h1:qUzPVlSj2UgxJkVbH0ZwuuiR46U8RBMDT5KLY78Ifpw=
github.com/tdrn-org/go-hue v0.3.0 h1:ywIlfTx9lcDp+n9XyGNY/9mUihW82lsWUanTzvdfeMc=
github.com/tdrn-org/go-hue v0.3.0/go.mod h1:KUnPy2lGoP43ygNoCg6jVEhf8h5fpRn0Esjxq9syCnU=
github.com/tdrn-org/go-nsdp v0.5.0 h1:bOs8qABaP/BSQlWeziZx9gjGkC2ld9UQek9p5w6PvdY=
github.com/tdrn-org/go-nsdp v0.5.0/go.mod h1:zp7CxiCPcyXHo+s6tn+wrNBr1qQe1G/hOh/FybM5xiM=
github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=

View File

@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.huebridge
package all
import _ "github.com/influxdata/telegraf/plugins/inputs/huebridge" // register plugin

View File

@ -0,0 +1,183 @@
# HueBridge Input Plugin
This input plugin gathers status from [Hue Bridge][hue] devices
using the [CLIP API][hue_api] interface of the devices.
⭐ Telegraf v1.34.0
🏷️ iot
💻 all
[hue]: https://www.philips-hue.com/
[hue_api]: https://developers.meethue.com/develop/hue-api-v2/
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Gather smart home status from Hue Bridge
[[inputs.huebridge]]
## URL of bridges to query in the form <scheme>://<bridge id>:<user name>@<address>/
## See documentation for available schemes.
bridges = [ "address://<bridge id>:<user name>@<bridge hostname or address>/" ]
## Manual device to room assignments to apply during status evaluation.
## E.g. for motion sensors which are reported without a room assignment.
# room_assignments = { "Motion sensor 1" = "Living room", "Motion sensor 2" = "Corridor" }
## Timeout for gathering information
# timeout = "10s"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
# tls_key_pwd = "secret"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
```
### Extended bridge access options
The Hue bridges to query can be defined by URLs of the following form:
```text
<locator scheme>://<bridge id>:<user name>@<locator dependent address>/
```
where the `bridge id` is the unique bridge id as returned in
```bash
curl -k https://<address>/api/config/0
```
and the `user name` is the secret user name returned during application
authentication.
To create a new user name issue the following command
after pressing the bridge's link button:
```bash
curl -k -X POST http://<bridge address>/api \
-H 'Content-Type: application/json' \
-d '{"devicetype":"huebridge-telegraf-plugin"}'
```
The `scheme` can have one of the following values and will also determine the
structure of the `address` part.
#### `address` scheme
Addresses a local bridge with `address` being the DNS name or IP address of the
bridge, e.g.
```text
address://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@mybridge/
```
#### `cloud` scheme
With this scheme the plugin discovers a bridge via its cloud registration.
The `address` part defines the discovery endpoint to use.
If not specified otherwise,
the [standard discovery endpoint][discovery_url] is used, e.g.
```text
cloud://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@/
```
[discovery_url]: https://discovery.meethue.com/
#### `mdns` scheme
This scheme uses mDNS to discover the bridge. Leave the `address` part unset
for this scheme like
```text
mdns://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@/
```
#### `remote` scheme
This scheme accesses the bridge via the Cloud Remote API. The `address` part
defines the cloud API endpoint defaulting to the
[standard API endpoint][cloud_api_endpoint].
```text
remote://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@/
```
In order to use this method a Hue Developer Account is required, a Remote App
must be registered and the corresponding Authorization flow must be completed.
See the [Cloud2Cloud Getting Started documentation][cloud_getting_started]
for full details.
Additionally, the `remote_client_id`, `remote_client_secret`, and
`remote_callback_url` parameters must be set in the plugin configuration
exactly as used during the App registration.
Furthermore the `remote_token_dir` parameter must point to the directory
containing the persisted token.
[cloud_api_endpoint]: https://api.meethue.com
[cloud_getting_started]: https://developers.meethue.com/develop/hue-api-v2/cloud2cloud-getting-started/
## Metrics
- `huebridge_light`
- tags
- `bridge_id` - The bridge id (this metrics has been queried from)
- `room` - The name of the room
- `device` - The name of the device
- fields
- `on` (int) - 0: light is off 1: light is on
- `huebridge_temperature`
- tags
- `bridge_id` - The bridge id (this metrics has been queried from)
- `room` - The name of the room
- `device` - The name of the device
- `enabled` - The current status of sensor (active: true|false)
- fields
- `temperature` (float) - The current temperatue (in °Celsius)
- `huebridge_light_level`
- tags
- `bridge_id` - The bridge id (this metrics has been queried from)
- `room` - The name of the room
- `device` - The name of the device
- `enabled` - The current status of sensor (active: true|false)
- fields
- `light_level` (int) - The current light level (in human friendly scale 10.000*log10(lux)+1)
- `light_level_lux` (float) - The current light level (in lux)
- `huebridge_motion_sensor`
- tags
- `bridge_id` - The bridge id (this metrics has been queried from)
- `room` - The name of the room
- `device` - The name of the device
- `enabled` - The current status of sensor (active: true|false)
- fields
- `motion` (int) - 0: no motion detected 1: motion detected
- `huebridge_device_power`
- tags
- `bridge_id` - The bridge id (this metrics has been queried from)
- `room` - The name of the room
- `device` - The name of the device
- fields
- `battery_level` (int) - Power source status (normal, low, critical)
- `battery_state` (string) - Battery charge level (in %)
## Example Output
```text
huebridge_light,huebridge_bridge_id=0123456789ABCDEF,huebridge_room=Name#15,huebridge_device=Name#3 on=0 1734880329
huebridge_temperature,huebridge_room=Name#15,huebridge_device=Name#7,huebridge_device_enabled=true,huebridge_bridge_id=0123456789ABCDEF temperature=17.63 1734880329
huebridge_light_level,huebridge_bridge_id=0123456789ABCDEF,huebridge_room=Name#15,huebridge_device=Name#7,huebridge_device_enabled=true light_level=18948,light_level_lux=78.46934003526889 1734880329
huebridge_motion_sensor,huebridge_bridge_id=0123456789ABCDEF,huebridge_room=Name#15,huebridge_device=Name#7,huebridge_device_enabled=true motion=0 1734880329
huebridge_device_power,huebridge_bridge_id=0123456789ABCDEF,huebridge_room=Name#15,huebridge_device=Name#7 battery_level=100,battery_state=normal 1734880329
```

View File

@ -0,0 +1,457 @@
package huebridge
import (
"fmt"
"maps"
"math"
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/tdrn-org/go-hue"
)
type bridge struct {
url *url.URL
configRoomAssignments map[string]string
rcc *RemoteClientConfig
tcc *tls.ClientConfig
timeout config.Duration
log telegraf.Logger
resolvedClient hue.BridgeClient
resourceTree map[string]string
deviceNames map[string]string
roomAssignments map[string]string
}
func newBridge(rawUrl string, roomAssignments map[string]string, rcc *RemoteClientConfig, tcc *tls.ClientConfig, timeout config.Duration, log telegraf.Logger) (*bridge, error) {
parsedUrl, err := url.Parse(rawUrl)
if err != nil {
return nil, fmt.Errorf("failed to parse bridge URL %s: %w", rawUrl, err)
}
switch parsedUrl.Scheme {
case "address", "cloud", "mdns", "remote":
// Do nothing, those are valid
default:
return nil, fmt.Errorf("unrecognized scheme %s in URL %s", parsedUrl.Scheme, parsedUrl)
}
// All schemes require a password in the URL
_, passwordSet := parsedUrl.User.Password()
if !passwordSet {
return nil, fmt.Errorf("missing password in URL %s", parsedUrl)
}
// Remote scheme also requires a configured rcc
if parsedUrl.Scheme == "remote" {
if rcc.RemoteClientId == "" || rcc.RemoteClientSecret == "" || rcc.RemoteTokenDir == "" {
return nil, fmt.Errorf("missing remote application credentials and/or token director not configured")
}
}
return &bridge{
url: parsedUrl,
configRoomAssignments: roomAssignments,
rcc: rcc,
tcc: tcc,
timeout: timeout,
log: log,
}, nil
}
func (b *bridge) String() string {
return b.url.Redacted()
}
func (b *bridge) process(acc telegraf.Accumulator) error {
if b.resolvedClient == nil {
if err := b.resolve(); err != nil {
return err
}
}
b.log.Tracef("Processing bridge %s", b)
err := b.fetchMetadata()
if err != nil {
// Discard previously resolved client and re-resolve on next process call
b.resolvedClient = nil
return err
}
acc.AddError(b.processLights(acc))
acc.AddError(b.processTemperatures(acc))
acc.AddError(b.processLightLevels(acc))
acc.AddError(b.processMotionSensors(acc))
acc.AddError(b.processDevicePowers(acc))
return nil
}
func (b *bridge) processLights(acc telegraf.Accumulator) error {
getLightsResponse, err := b.resolvedClient.GetLights()
if err != nil {
return fmt.Errorf("failed to access bridge lights on %s: %w", b, err)
}
if getLightsResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge lights from %s: %s", b, getLightsResponse.HTTPResponse.Status)
}
responseData := getLightsResponse.JSON200.Data
if responseData != nil {
for _, light := range *responseData {
tags := make(map[string]string)
tags["bridge_id"] = b.resolvedClient.Bridge().BridgeId
tags["room"] = b.resolveResourceRoom(*light.Id, *light.Metadata.Name)
tags["device"] = *light.Metadata.Name
fields := make(map[string]interface{})
if *light.On.On {
fields["on"] = 1
} else {
fields["on"] = 0
}
acc.AddGauge("huebridge_light", fields, tags)
}
}
return nil
}
func (b *bridge) processTemperatures(acc telegraf.Accumulator) error {
getTemperaturesResponse, err := b.resolvedClient.GetTemperatures()
if err != nil {
return fmt.Errorf("failed to access bridge temperatures on %s: %w", b, err)
}
if getTemperaturesResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge temperatures from %s: %s", b, getTemperaturesResponse.HTTPResponse.Status)
}
responseData := getTemperaturesResponse.JSON200.Data
if responseData != nil {
for _, temperature := range *responseData {
temperatureName := b.resolveDeviceName(*temperature.Id)
tags := make(map[string]string)
tags["bridge_id"] = b.resolvedClient.Bridge().BridgeId
tags["room"] = b.resolveResourceRoom(*temperature.Id, temperatureName)
tags["device"] = temperatureName
tags["enabled"] = strconv.FormatBool(*temperature.Enabled)
fields := make(map[string]interface{})
fields["temperature"] = *temperature.Temperature.TemperatureReport.Temperature
acc.AddGauge("huebridge_temperature", fields, tags)
}
}
return nil
}
func (b *bridge) processLightLevels(acc telegraf.Accumulator) error {
getLightLevelsResponse, err := b.resolvedClient.GetLightLevels()
if err != nil {
return fmt.Errorf("failed to access bridge lights levels on %s: %w", b, err)
}
if getLightLevelsResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge light levels from %s: %s", b, getLightLevelsResponse.HTTPResponse.Status)
}
responseData := getLightLevelsResponse.JSON200.Data
if responseData != nil {
for _, lightLevel := range *responseData {
lightLevelName := b.resolveDeviceName(*lightLevel.Id)
tags := make(map[string]string)
tags["bridge_id"] = b.resolvedClient.Bridge().BridgeId
tags["room"] = b.resolveResourceRoom(*lightLevel.Id, lightLevelName)
tags["device"] = lightLevelName
tags["enabled"] = strconv.FormatBool(*lightLevel.Enabled)
fields := make(map[string]interface{})
fields["light_level"] = *lightLevel.Light.LightLevelReport.LightLevel
fields["light_level_lux"] = math.Pow(10.0, (float64(*lightLevel.Light.LightLevelReport.LightLevel)-1.0)/10000.0)
acc.AddGauge("huebridge_light_level", fields, tags)
}
}
return nil
}
func (b *bridge) processMotionSensors(acc telegraf.Accumulator) error {
getMotionSensorsResponse, err := b.resolvedClient.GetMotionSensors()
if err != nil {
return fmt.Errorf("failed to access bridge motion sensors on %s: %w", b, err)
}
if getMotionSensorsResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge motion sensors from %s: %s", b, getMotionSensorsResponse.HTTPResponse.Status)
}
responseData := getMotionSensorsResponse.JSON200.Data
if responseData != nil {
for _, motionSensor := range *responseData {
motionSensorName := b.resolveDeviceName(*motionSensor.Id)
tags := make(map[string]string)
tags["bridge_id"] = b.resolvedClient.Bridge().BridgeId
tags["room"] = b.resolveResourceRoom(*motionSensor.Id, motionSensorName)
tags["device"] = motionSensorName
tags["enabled"] = strconv.FormatBool(*motionSensor.Enabled)
fields := make(map[string]interface{})
if *motionSensor.Motion.MotionReport.Motion {
fields["motion"] = 1
} else {
fields["motion"] = 0
}
acc.AddGauge("huebridge_motion_sensor", fields, tags)
}
}
return nil
}
func (b *bridge) processDevicePowers(acc telegraf.Accumulator) error {
getDevicePowersResponse, err := b.resolvedClient.GetDevicePowers()
if err != nil {
return fmt.Errorf("failed to access bridge device powers on %s: %w", b, err)
}
if getDevicePowersResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge device powers from %s: %s", b, getDevicePowersResponse.HTTPResponse.Status)
}
responseData := getDevicePowersResponse.JSON200.Data
if responseData != nil {
for _, devicePower := range *responseData {
if devicePower.PowerState.BatteryLevel == nil && devicePower.PowerState.BatteryState == nil {
continue
}
devicePowerName := b.resolveDeviceName(*devicePower.Id)
tags := make(map[string]string)
tags["bridge_id"] = b.resolvedClient.Bridge().BridgeId
tags["room"] = b.resolveResourceRoom(*devicePower.Id, devicePowerName)
tags["device"] = devicePowerName
fields := make(map[string]interface{})
fields["battery_level"] = *devicePower.PowerState.BatteryLevel
fields["battery_state"] = *devicePower.PowerState.BatteryState
acc.AddGauge("huebridge_device_power", fields, tags)
}
}
return nil
}
func (b *bridge) resolve() error {
if b.resolvedClient != nil {
return nil
}
switch b.url.Scheme {
case "address":
return b.resolveViaAddress()
case "cloud":
return b.resolveViaCloud()
case "mdns":
return b.resolveViaMDNS()
case "remote":
return b.resolveViaRemote()
}
return fmt.Errorf("unrecognized bridge URL %s", b)
}
func (b *bridge) resolveViaAddress() error {
locator, err := hue.NewAddressBridgeLocator(b.url.Host)
if err != nil {
return err
}
return b.resolveLocalBridge(locator)
}
func (b *bridge) resolveViaCloud() error {
locator := hue.NewCloudBridgeLocator()
if b.url.Host != "" {
discoveryEndpointUrl, err := url.Parse(fmt.Sprintf("https://%s/", b.url.Host))
if err != nil {
return err
}
discoveryEndpointUrl = discoveryEndpointUrl.JoinPath(b.url.Path)
locator.DiscoveryEndpointUrl = discoveryEndpointUrl
}
tlsConfig, err := b.tcc.TLSConfig()
if err != nil {
return err
}
locator.TlsConfig = tlsConfig
return b.resolveLocalBridge(locator)
}
func (b *bridge) resolveViaMDNS() error {
locator := hue.NewMDNSBridgeLocator()
return b.resolveLocalBridge(locator)
}
func (b *bridge) resolveLocalBridge(locator hue.BridgeLocator) error {
hueBridge, err := locator.Lookup(b.url.User.Username(), time.Duration(b.timeout))
if err != nil {
return err
}
urlPassword, _ := b.url.User.Password()
bridgeClient, err := hueBridge.NewClient(hue.NewLocalBridgeAuthenticator(urlPassword), time.Duration(b.timeout))
if err != nil {
return err
}
b.resolvedClient = bridgeClient
return nil
}
func (b *bridge) resolveViaRemote() error {
var redirectUrl *url.URL
if b.rcc.RemoteCallbackUrl != "" {
parsedRedirectUrl, err := url.Parse(b.rcc.RemoteCallbackUrl)
if err != nil {
return err
}
redirectUrl = parsedRedirectUrl
}
tokenFile := filepath.Join(b.rcc.RemoteTokenDir, b.rcc.RemoteClientId, strings.ToUpper(b.url.User.Username())+".json")
locator, err := hue.NewRemoteBridgeLocator(b.rcc.RemoteClientId, b.rcc.RemoteClientSecret, redirectUrl, tokenFile)
if err != nil {
return err
}
if b.url.Host != "" {
endpointUrl, err := url.Parse(fmt.Sprintf("https://%s/", b.url.Host))
if err != nil {
return err
}
endpointUrl = endpointUrl.JoinPath(b.url.Path)
locator.EndpointUrl = endpointUrl
}
tlsConfig, err := b.tcc.TLSConfig()
if err != nil {
return err
}
locator.TlsConfig = tlsConfig.Clone()
return b.resolveRemoteBridge(locator)
}
func (b *bridge) resolveRemoteBridge(locator *hue.RemoteBridgeLocator) error {
hueBridge, err := locator.Lookup(b.url.User.Username(), time.Duration(b.timeout))
if err != nil {
return err
}
urlPassword, _ := b.url.User.Password()
bridgeClient, err := hueBridge.NewClient(hue.NewRemoteBridgeAuthenticator(locator, urlPassword), time.Duration(b.timeout))
if err != nil {
return err
}
b.resolvedClient = bridgeClient
return nil
}
func (b *bridge) fetchMetadata() error {
err := b.fetchResourceTree()
if err != nil {
return err
}
err = b.fetchDeviceNames()
if err != nil {
return err
}
return b.fetchRoomAssignments()
}
func (b *bridge) fetchResourceTree() error {
getResourcesResponse, err := b.resolvedClient.GetResources()
if err != nil {
return fmt.Errorf("failed to access bridge resources on %s: %w", b, err)
}
if getResourcesResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge resources from %s: %s", b, getResourcesResponse.HTTPResponse.Status)
}
responseData := getResourcesResponse.JSON200.Data
if responseData == nil {
b.resourceTree = make(map[string]string)
return nil
}
b.resourceTree = make(map[string]string, len(*responseData))
for _, resource := range *responseData {
if resource.Owner != nil {
b.resourceTree[*resource.Id] = *resource.Owner.Rid
}
}
return nil
}
func (b *bridge) fetchDeviceNames() error {
getDevicesResponse, err := b.resolvedClient.GetDevices()
if err != nil {
return fmt.Errorf("failed to access bridge devices on %s: %w", b, err)
}
if getDevicesResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge devices from %s: %s", b, getDevicesResponse.HTTPResponse.Status)
}
responseData := getDevicesResponse.JSON200.Data
if responseData == nil {
b.deviceNames = make(map[string]string)
return nil
}
b.deviceNames = make(map[string]string, len(*responseData))
for _, device := range *responseData {
b.deviceNames[*device.Id] = *device.Metadata.Name
}
return nil
}
func (b *bridge) fetchRoomAssignments() error {
getRoomsResponse, err := b.resolvedClient.GetRooms()
if err != nil {
return fmt.Errorf("failed to access bridge rooms on %s: %w", b, err)
}
if getRoomsResponse.HTTPResponse.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch bridge rooms from %s: %s", b, getRoomsResponse.HTTPResponse.Status)
}
responseData := getRoomsResponse.JSON200.Data
if responseData == nil {
b.roomAssignments = maps.Clone(b.configRoomAssignments)
return nil
}
b.roomAssignments = make(map[string]string, len(*responseData))
for _, roomGet := range *responseData {
for _, children := range *roomGet.Children {
b.roomAssignments[*children.Rid] = *roomGet.Metadata.Name
}
}
maps.Copy(b.roomAssignments, b.configRoomAssignments)
return nil
}
func (b *bridge) resolveResourceRoom(resourceId string, resourceName string) string {
roomName := b.roomAssignments[resourceName]
if roomName != "" {
return roomName
}
// If resource does not have a room assigned directly, iterate upwards via
// its owners until we find a room or there is no more owner. The latter
// may happen (e.g. for Motion Sensors) resulting in room name
// "<unassigned>".
currentResourceId := resourceId
for {
// Try next owner
currentResourceId = b.resourceTree[currentResourceId]
if currentResourceId == "" {
// No owner left but no room found
break
}
roomName = b.roomAssignments[currentResourceId]
if roomName != "" {
// Room name found, done
return roomName
}
}
return "<unassigned>"
}
func (b *bridge) resolveDeviceName(resourceId string) string {
deviceName := b.deviceNames[resourceId]
if deviceName != "" {
return deviceName
}
// If resource does not have a device name assigned directly, iterate
// upwards via its owners until we find a room or there is no more
// owner. The latter may happen resulting in device name "<undefined>".
currentResourceId := resourceId
for {
// Try next owner
currentResourceId = b.resourceTree[currentResourceId]
if currentResourceId == "" {
// No owner left but no device found
break
}
deviceName = b.deviceNames[currentResourceId]
if deviceName != "" {
// Device name found, done
return deviceName
}
}
return "<undefined>"
}

View File

@ -0,0 +1,70 @@
//go:generate ../../../tools/readme_config_includer/generator
package huebridge
import (
_ "embed"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type RemoteClientConfig struct {
RemoteClientId string `toml:"remote_client_id"`
RemoteClientSecret string `toml:"remote_client_secret"`
RemoteCallbackUrl string `toml:"remote_callback_url"`
RemoteTokenDir string `toml:"remote_token_dir"`
}
type HueBridge struct {
BridgeUrls []string `toml:"bridges"`
RoomAssignments map[string]string `toml:"room_assignments"`
Timeout config.Duration `toml:"timeout"`
Log telegraf.Logger `toml:"-"`
RemoteClientConfig
tls.ClientConfig
bridges []*bridge
}
func (*HueBridge) SampleConfig() string {
return sampleConfig
}
func (h *HueBridge) Init() error {
h.bridges = make([]*bridge, 0, len(h.BridgeUrls))
for _, bridgeUrl := range h.BridgeUrls {
bridge, err := newBridge(bridgeUrl, h.RoomAssignments, &h.RemoteClientConfig, &h.ClientConfig, h.Timeout, h.Log)
if err != nil {
h.Log.Warnf("Failed to instantiate bridge for URL %s: %s", bridgeUrl, err)
continue
}
h.bridges = append(h.bridges, bridge)
}
return nil
}
func (h *HueBridge) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, bridge := range h.bridges {
wg.Add(1)
go func() {
defer wg.Done()
acc.AddError(bridge.process(acc))
}()
}
wg.Wait()
return nil
}
func init() {
inputs.Add("huebridge", func() telegraf.Input {
return &HueBridge{Timeout: config.Duration(10 * time.Second)}
})
}

View File

@ -0,0 +1,126 @@
package huebridge
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/tdrn-org/go-hue/mock"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/parsers/influx"
"github.com/influxdata/telegraf/testutil"
)
func TestConfig(t *testing.T) {
// Verify plugin can be loaded from config
conf := config.NewConfig()
require.NoError(t, conf.LoadConfig("testdata/conf/huebridge.conf"))
require.Len(t, conf.Inputs, 1)
h, ok := conf.Inputs[0].Input.(*HueBridge)
require.True(t, ok)
// Verify successful Init
require.NoError(t, h.Init())
// Verify everything is setup according to config file
require.Len(t, h.BridgeUrls, 4)
require.Equal(t, "client", h.RemoteClientId)
require.Equal(t, "secret", h.RemoteClientSecret)
require.Equal(t, "url", h.RemoteCallbackUrl)
require.Equal(t, "dir", h.RemoteTokenDir)
require.Len(t, h.RoomAssignments, 2)
require.Equal(t, config.Duration(60*time.Second), h.Timeout)
require.Equal(t, "secret", h.TLSKeyPwd)
require.True(t, h.InsecureSkipVerify)
}
func TestInitSuccess(t *testing.T) {
// Create plugin instance with all types of URL schemes
h := &HueBridge{
BridgeUrls: []string{
"address://12345678:secret@localhost/",
"cloud://12345678:secret@localhost/discovery/",
"mdns://12345678:secret@/",
"remote://12345678:secret@localhost/",
},
RemoteClientConfig: RemoteClientConfig{
RemoteClientId: mock.MockClientId,
RemoteClientSecret: mock.MockClientSecret,
RemoteTokenDir: ".",
},
ClientConfig: tls.ClientConfig{
InsecureSkipVerify: true,
},
Timeout: config.Duration(10 * time.Second),
Log: &testutil.Logger{Name: "huebridge"},
}
// Verify successful Init
require.NoError(t, h.Init())
// Verify successful configuration of all bridge URLs
require.Len(t, h.bridges, len(h.BridgeUrls))
}
func TestInitIgnoreInvalidUrls(t *testing.T) {
// The following URLs are all invalid must all be ignored during Init
h := &HueBridge{
BridgeUrls: []string{
"invalid://12345678:secret@invalid-scheme.net/",
"address://12345678@missing-password.net/",
"cloud://12345678@missing-password.net/",
"mdns://12345678@missing-password.net/",
"remote://12345678@missing-password.net/",
"remote://12345678:secret@missing-remote-config.net/",
},
Timeout: config.Duration(10 * time.Second),
Log: &testutil.Logger{Name: "huebridge"},
}
// Verify successful Init
require.NoError(t, h.Init())
// Verify no bridge have been configured
require.Len(t, h.bridges, 0)
}
func TestGatherLocal(t *testing.T) {
// Start mock server and make plugin targing it
bridgeMock := mock.Start()
require.NotNil(t, bridgeMock)
defer bridgeMock.Shutdown()
h := &HueBridge{
BridgeUrls: []string{
fmt.Sprintf("address://%s:%s@%s/", mock.MockBridgeId, mock.MockBridgeUsername, bridgeMock.Server().Host),
},
RoomAssignments: map[string]string{"Name#7": "Name#15"},
Timeout: config.Duration(10 * time.Second),
Log: &testutil.Logger{Name: "huebridge"},
}
// Verify successful Init
require.NoError(t, h.Init())
// Verify successfull Gather
acc := &testutil.Accumulator{}
require.NoError(t, acc.GatherError(h.Gather))
// Verify collected metrics are as expected
expectedMetrics := loadExpectedMetrics(t, "testdata/metrics/huebridge.txt", telegraf.Gauge)
testutil.RequireMetricsEqual(t, expectedMetrics, acc.GetTelegrafMetrics(), testutil.IgnoreTime(), testutil.SortMetrics())
}
func loadExpectedMetrics(t *testing.T, file string, vt telegraf.ValueType) []telegraf.Metric {
parser := &influx.Parser{}
require.NoError(t, parser.Init())
expectedMetrics, err := testutil.ParseMetricsFromFile(file, parser)
require.NoError(t, err)
for index := range expectedMetrics {
expectedMetrics[index].SetType(vt)
}
return expectedMetrics
}

View File

@ -0,0 +1,20 @@
# Gather smart home status from Hue Bridge
[[inputs.huebridge]]
## URL of bridges to query in the form <scheme>://<bridge id>:<user name>@<address>/
## See documentation for available schemes.
bridges = [ "address://<bridge id>:<user name>@<bridge hostname or address>/" ]
## Manual device to room assignments to apply during status evaluation.
## E.g. for motion sensors which are reported without a room assignment.
# room_assignments = { "Motion sensor 1" = "Living room", "Motion sensor 2" = "Corridor" }
## Timeout for gathering information
# timeout = "10s"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
# tls_key_pwd = "secret"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false

View File

@ -0,0 +1,30 @@
# Gather smart home status from Hue Bridge
[[inputs.huebridge]]
## The Hue bridges to query.
## See README file for all addressing options.
bridges = [
"address://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@mybridgenameorip/",
"cloud://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@discovery.meethue.com/",
"mdns://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@/",
"remote://0123456789ABCDEF:sFlEGnMAFXO6RtZV17aViNUB95G2uXWw64texDzD@api.meethue.com/",
]
remote_client_id = "client"
remote_client_secret = "secret"
remote_callback_url = "url"
remote_token_dir = "dir"
## Manual device to room assignments to apply during status evaluation.
## E.g. for motion sensors which are reported without a room assignment.
room_assignments = { "Device 1" = "Room A", "Device 2" = "Room B" }
## Timeout for gathering information
timeout = "1m"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
tls_key_pwd = "secret"
## Use TLS but skip chain & host verification
insecure_skip_verify = true

View File

@ -0,0 +1,14 @@
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#3,room=Name#15 on=0i 1737181537879611000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#8,room=Name#14 on=0i 1737181537879628000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#12,room=Name#16 on=0i 1737181537879632000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#6,room=Name#13 on=0i 1737181537879634000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#1,room=Name#13 on=0i 1737181537879635000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#2,room=Name#13 on=0i 1737181537879637000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#5,room=Name#15 on=0i 1737181537879639000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#9,room=Name#13 on=0i 1737181537879640000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#11,room=Name#15 on=0i 1737181537879642000
huebridge_light,bridge_id=0123456789ABCDEF,device=Name#4,room=Name#14 on=0i 1737181537879646000
huebridge_temperature,bridge_id=0123456789ABCDEF,device=Name#7,enabled=true,room=Name#15 temperature=17.6299991607666 1737181537879828000
huebridge_light_level,bridge_id=0123456789ABCDEF,device=Name#7,enabled=true,room=Name#15 light_level=18948i,light_level_lux=78.46934003526889 1737181537880034000
huebridge_motion_sensor,bridge_id=0123456789ABCDEF,device=Name#7,enabled=true,room=Name#15 motion=0i 1737181537880213000
huebridge_device_power,bridge_id=0123456789ABCDEF,device=Name#7,room=Name#15 battery_level=100i 1737181537880360000