lights extracted
This commit is contained in:
parent
6535c81486
commit
d1d28013d7
6 changed files with 442 additions and 1 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.vscode/launch.json
|
49
README.md
49
README.md
|
@ -1 +1,48 @@
|
|||
# hue_exporter
|
||||
# hue_exporter
|
||||
|
||||
This exporter exports some variables from Philips Hue Bridge
|
||||
(https://www.philips-hue.com)
|
||||
to prometheus.
|
||||
|
||||
## Building
|
||||
|
||||
go get github.com/aexel90/hue_exporter/
|
||||
cd $GOPATH/src/github.com/aexel90/hue_exporter
|
||||
go install
|
||||
|
||||
## Running
|
||||
|
||||
How to create a user for your bridge is described here: https://developers.meethue.com/develop/get-started-2/
|
||||
|
||||
Usage:
|
||||
|
||||
$GOPATH/bin/hue_exporter -h
|
||||
|
||||
Usage of ./hue_exporter:
|
||||
-hue-url string
|
||||
The URL of the bridge
|
||||
-listen-address string
|
||||
The address to listen on for HTTP requests. (default "127.0.0.1:9773")
|
||||
-test
|
||||
test configured metrics
|
||||
-username string
|
||||
The username token having bridge access
|
||||
|
||||
## Example execution
|
||||
|
||||
### Running within prometheus:
|
||||
|
||||
$GOPATH/bin/hue_exporter -hue_url 192.168.xxx.xxx -username ZlEH24zabK2jTpJ...
|
||||
|
||||
# HELP hue_light_status status of lights registered at hue bridge
|
||||
# TYPE hue_light_status gauge
|
||||
hue_light_status{manufacturer_name="...",model_id="...",name="...",state_alert="...",state_bri="...",state_ct="...",state_on="...",state_reachable="...",state_saturation="...",sw_version="...",type="...",unique_id="..."} 1
|
||||
hue_light_status{manufacturer_name="...",model_id="...",name="...",state_alert="...",state_bri="...",state_ct="...",state_on="...",state_reachable="...",state_saturation="...",sw_version="...",type="...",unique_id="..."} 0
|
||||
...
|
||||
|
||||
### Test exporter:
|
||||
|
||||
$GOPATH/bin/hue_exporter -hue_url 192.168.xxx.xxx -username ZlEH24zabK2jTpJ... -test
|
||||
|
||||
## Grafana Dashboard
|
||||
|
||||
|
|
182
collector/collector.go
Normal file
182
collector/collector.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/aexel90/hue_exporter/hue"
|
||||
"github.com/aexel90/hue_exporter/metric"
|
||||
)
|
||||
|
||||
// Collector instance
|
||||
type Collector struct {
|
||||
exporter *hue.Exporter
|
||||
metrics []*metric.Metric
|
||||
}
|
||||
|
||||
// NewHueCollector initialization
|
||||
func NewHueCollector(URL string, username string) (*Collector, error) {
|
||||
|
||||
hueExporter := hue.Exporter{
|
||||
BaseURL: URL,
|
||||
Username: username,
|
||||
}
|
||||
|
||||
return &Collector{&hueExporter, nil}, nil
|
||||
}
|
||||
|
||||
// Describe for prometheus
|
||||
func (collector *Collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
|
||||
collector.metrics = collector.exporter.InitMetrics()
|
||||
collector.initDescAndType()
|
||||
}
|
||||
|
||||
// Collect for prometheus
|
||||
func (collector *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
|
||||
err := collector.collect()
|
||||
if err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
}
|
||||
|
||||
for _, m := range collector.metrics {
|
||||
for _, promResult := range m.PromResult {
|
||||
ch <- prometheus.MustNewConstMetric(promResult.PromDesc, promResult.PromValueType, promResult.Value, promResult.LabelValues...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Test collector metrics
|
||||
func (collector *Collector) Test() {
|
||||
|
||||
collector.metrics = collector.exporter.InitMetrics()
|
||||
collector.initDescAndType()
|
||||
|
||||
err := collector.collect()
|
||||
if err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
}
|
||||
|
||||
err = collector.printResult()
|
||||
}
|
||||
|
||||
func (collector *Collector) printResult() error {
|
||||
|
||||
for _, m := range collector.metrics {
|
||||
fmt.Printf("Metric: %v\n", m.FqName)
|
||||
fmt.Printf(" - Exporter Result:\n")
|
||||
|
||||
for i, result := range m.MetricResult {
|
||||
fmt.Printf(" - Exporter Result %v:\n", i)
|
||||
for key, value := range result {
|
||||
fmt.Printf(" - %s=\"%v\"\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
for _, promResult := range m.PromResult {
|
||||
|
||||
fmt.Printf(" - prom desc: %v\n", promResult.PromDesc)
|
||||
fmt.Printf(" - prom metric type: %v\n", promResult.PromValueType)
|
||||
fmt.Printf(" - prom metric value: %v\n", uint64(promResult.Value))
|
||||
fmt.Printf(" - prom label values: %v\n", promResult.LabelValues)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (collector *Collector) collect() (err error) {
|
||||
|
||||
err = collector.exporter.Collect(collector.metrics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collector.getResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (collector *Collector) getResult() (err error) {
|
||||
|
||||
for _, m := range collector.metrics {
|
||||
m.PromResult = nil
|
||||
for _, metricResult := range m.MetricResult {
|
||||
|
||||
labelValues, err := getLabelValues(m.Labels, metricResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resultValue, err := getResultValue(m.ResultKey, metricResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := metric.PrometheusResult{PromDesc: m.PromDesc, PromValueType: m.PromType, Value: resultValue, LabelValues: labelValues}
|
||||
m.PromResult = append(m.PromResult, &result)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (collector *Collector) initDescAndType() {
|
||||
|
||||
for _, metric := range collector.metrics {
|
||||
|
||||
var help string
|
||||
|
||||
switch metric.HueType {
|
||||
case hue.TypeLight:
|
||||
metric.FqName = "hue_light_status"
|
||||
help = "status of lights registered at hue bridge"
|
||||
metric.PromType = prometheus.GaugeValue
|
||||
}
|
||||
|
||||
labels := []string{}
|
||||
for _, label := range metric.Labels {
|
||||
labels = append(labels, strings.ToLower(label))
|
||||
}
|
||||
|
||||
metric.PromDesc = prometheus.NewDesc(metric.FqName, help, labels, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func getResultValue(resultKey string, result map[string]interface{}) (float64, error) {
|
||||
|
||||
value := result[resultKey]
|
||||
var floatValue float64
|
||||
|
||||
switch tval := value.(type) {
|
||||
case float64:
|
||||
floatValue = tval
|
||||
case int:
|
||||
floatValue = float64(tval)
|
||||
case uint64:
|
||||
floatValue = float64(tval)
|
||||
case bool:
|
||||
if tval {
|
||||
floatValue = 1
|
||||
} else {
|
||||
floatValue = 0
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("[getResultValue] %v in %v - unknown type: %T", resultKey, result, value)
|
||||
}
|
||||
return floatValue, nil
|
||||
}
|
||||
|
||||
func getLabelValues(labelNames []string, result map[string]interface{}) ([]string, error) {
|
||||
|
||||
labelValues := []string{}
|
||||
for _, labelname := range labelNames {
|
||||
labelValue := fmt.Sprintf("%v", result[labelname])
|
||||
labelValue = strings.ToLower(labelValue)
|
||||
labelValues = append(labelValues, labelValue)
|
||||
}
|
||||
return labelValues, nil
|
||||
}
|
127
hue/hue.go
Normal file
127
hue/hue.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package hue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aexel90/hue_exporter/metric"
|
||||
hue "github.com/collinux/gohue"
|
||||
)
|
||||
|
||||
// Exporter data
|
||||
type Exporter struct {
|
||||
BaseURL string
|
||||
Username string
|
||||
}
|
||||
|
||||
const (
|
||||
TypeLight = "light"
|
||||
TypeOther = "???"
|
||||
|
||||
LightLabelName = "Name"
|
||||
LightLabelType = "Type"
|
||||
LightLabelModelID = "Model_ID"
|
||||
LightLabelManufacturerName = "Manufacturer_Name"
|
||||
LightLabelSWVersion = "SW_Version"
|
||||
LightLabelUniqueID = "Unique_ID"
|
||||
LightLabelStateOn = "State_On"
|
||||
LightLabelStateAlert = "State_Alert"
|
||||
LightLabelStateBri = "State_Bri"
|
||||
LightLabelStateCT = "State_CT"
|
||||
LightLabelStateReachable = "State_Reachable"
|
||||
LightLabelStateSaturation = "State_Saturation"
|
||||
)
|
||||
|
||||
// InitMetrics func
|
||||
func (exporter *Exporter) InitMetrics() (metrics []*metric.Metric) {
|
||||
|
||||
metrics = append(metrics, &metric.Metric{
|
||||
HueType: TypeLight,
|
||||
Labels: []string{LightLabelName, LightLabelType, LightLabelModelID, LightLabelManufacturerName, LightLabelSWVersion, LightLabelUniqueID, LightLabelStateOn, LightLabelStateAlert, LightLabelStateBri, LightLabelStateCT, LightLabelStateReachable, LightLabelStateSaturation},
|
||||
ResultKey: LightLabelStateOn})
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
// Collect metrics
|
||||
func (exporter *Exporter) Collect(metrics []*metric.Metric) (err error) {
|
||||
|
||||
bridge := newBridge(exporter.BaseURL)
|
||||
|
||||
err = bridge.Login(exporter.Username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[error login] '%v'", err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
|
||||
var err error
|
||||
|
||||
switch metric.HueType {
|
||||
case TypeLight:
|
||||
err = collectLights(bridge, metric)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectLights(bridge *hue.Bridge, metric *metric.Metric) (err error) {
|
||||
|
||||
metric.MetricResult = nil
|
||||
|
||||
lights, err := bridge.GetAllLights()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[error GetAllLights()] '%v'", err)
|
||||
}
|
||||
|
||||
for _, light := range lights {
|
||||
|
||||
result := make(map[string]interface{})
|
||||
for _, label := range metric.Labels {
|
||||
|
||||
switch label {
|
||||
case LightLabelName:
|
||||
result[LightLabelName] = light.Name
|
||||
case LightLabelType:
|
||||
result[LightLabelType] = light.Type
|
||||
case LightLabelModelID:
|
||||
result[LightLabelModelID] = light.ModelID
|
||||
case LightLabelManufacturerName:
|
||||
result[LightLabelManufacturerName] = light.ManufacturerName
|
||||
case LightLabelSWVersion:
|
||||
result[LightLabelSWVersion] = light.SWVersion
|
||||
case LightLabelUniqueID:
|
||||
result[LightLabelUniqueID] = light.UniqueID
|
||||
case LightLabelStateOn:
|
||||
result[LightLabelStateOn] = light.State.On
|
||||
case LightLabelStateAlert:
|
||||
result[LightLabelStateAlert] = light.State.Alert
|
||||
case LightLabelStateBri:
|
||||
result[LightLabelStateBri] = light.State.Bri
|
||||
case LightLabelStateCT:
|
||||
result[LightLabelStateCT] = light.State.CT
|
||||
case LightLabelStateReachable:
|
||||
result[LightLabelStateReachable] = light.State.Reachable
|
||||
case LightLabelStateSaturation:
|
||||
result[LightLabelStateSaturation] = light.State.Saturation
|
||||
}
|
||||
}
|
||||
|
||||
metric.MetricResult = append(metric.MetricResult, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBridge(ipAddr string) *hue.Bridge {
|
||||
bridge, err := hue.NewBridge(ipAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("Error connecting to Hue bridge at '%v': '%v'\n", ipAddr, err)
|
||||
}
|
||||
return bridge
|
||||
}
|
44
main.go
Normal file
44
main.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/namsral/flag"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/aexel90/hue_exporter/collector"
|
||||
)
|
||||
|
||||
var (
|
||||
flagBridgeURL = flag.String("hue-url", "", "The URL of the bridge")
|
||||
flagUsername = flag.String("username", "", "The username token having bridge access")
|
||||
flagAddress = flag.String("listen-address", "127.0.0.1:9773", "The address to listen on for HTTP requests.")
|
||||
|
||||
flagTest = flag.Bool("test", false, "test configured metrics")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
hueCollector, err := collector.NewHueCollector(*flagBridgeURL, *flagUsername)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// test mode
|
||||
if *flagTest {
|
||||
hueCollector.Test()
|
||||
return
|
||||
}
|
||||
|
||||
prometheus.MustRegister(hueCollector)
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
fmt.Printf("metrics available at http://%s/metrics\n", *flagAddress)
|
||||
log.Fatal(http.ListenAndServe(*flagAddress, nil))
|
||||
}
|
40
metric/metric.go
Normal file
40
metric/metric.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type PrometheusResult struct {
|
||||
PromDesc *prometheus.Desc
|
||||
PromValueType prometheus.ValueType
|
||||
Value float64
|
||||
LabelValues []string
|
||||
}
|
||||
|
||||
// Metric struct
|
||||
type Metric struct {
|
||||
// PromDesc PromDesc `json:"promDesc"`
|
||||
// PromType string `json:"promType"`
|
||||
// ResultKey string `json:"resultKey"`
|
||||
// OkValue string `json:"okValue"`
|
||||
// ResultPath string `json:"resultPath"`
|
||||
// Page string `json:"page"`
|
||||
// Service string `json:"service"`
|
||||
// Action string `json:"action"`
|
||||
// ActionArgument *ActionArg `json:"actionArgument"`
|
||||
|
||||
// Desc *prometheus.Desc
|
||||
|
||||
// Value float64
|
||||
// labelValues []string
|
||||
|
||||
HueType string
|
||||
Labels []string
|
||||
MetricResult []map[string]interface{} //filled during collect
|
||||
ResultKey string
|
||||
FqName string
|
||||
|
||||
PromType prometheus.ValueType
|
||||
PromDesc *prometheus.Desc
|
||||
PromResult []*PrometheusResult
|
||||
}
|
Loading…
Reference in a new issue