lights extracted

This commit is contained in:
aexel90 2020-12-22 18:24:23 +01:00
parent 6535c81486
commit d1d28013d7
6 changed files with 442 additions and 1 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vscode/launch.json

View file

@ -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
View 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
View 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
View 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
View 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
}