diff --git a/groups.go b/groups.go index cc42a26..e908953 100644 --- a/groups.go +++ b/groups.go @@ -1,13 +1,12 @@ package main import ( - hue "github.com/collinux/gohue" log "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" ) type groupCollector struct { - bridge *hue.Bridge + bridge Bridge groupBrightness *prometheus.GaugeVec groupHue *prometheus.GaugeVec groupSaturation *prometheus.GaugeVec @@ -21,7 +20,7 @@ var variableGroupLabelNames = []string{ } // NewGroupCollector Create a new Hue collector for groups -func NewGroupCollector(namespace string, bridge *hue.Bridge) prometheus.Collector { +func NewGroupCollector(namespace string, bridge Bridge) prometheus.Collector { c := groupCollector{ bridge: bridge, groupBrightness: prometheus.NewGaugeVec( diff --git a/groups_test.go b/groups_test.go new file mode 100644 index 0000000..aa08109 --- /dev/null +++ b/groups_test.go @@ -0,0 +1,38 @@ +package main + +import ( + hue "github.com/collinux/gohue" + "github.com/mitchellrj/hue_exporter/test" + "github.com/prometheus/client_golang/prometheus" + "testing" +) + +func TestGroupCollector(t *testing.T) { + bridge := test.NewStubBridge().WithGroups([]hue.Group{ + hue.Group{ + Action: hue.Action{ + Bri: 256, + Hue: 100, + Sat: 80, + }, + State: struct { + AllOn bool `json:"all_on"` + AnyOn bool `json:"any_on"` + }{ + AllOn: true, + AnyOn: true, + }, + Type: "Room", + Name: "Living room", + }, + }) + + metrics := make(chan prometheus.Metric, 5) + collector := NewGroupCollector("test_hue", bridge) + collector.Collect(metrics) + close(metrics) + + for metric := range metrics { + t.Logf("%v\n", metric) + } +} diff --git a/lights.go b/lights.go index 1e291dd..46efd33 100644 --- a/lights.go +++ b/lights.go @@ -1,13 +1,12 @@ package main import ( - hue "github.com/collinux/gohue" log "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" ) type lightCollector struct { - bridge *hue.Bridge + bridge Bridge lightBrightness *prometheus.GaugeVec lightHue *prometheus.GaugeVec lightSaturation *prometheus.GaugeVec @@ -26,7 +25,7 @@ var variableLightLabelNames = []string{ } // NewLightCollector Create a new Hue collector for lights -func NewLightCollector(namespace string, bridge *hue.Bridge) prometheus.Collector { +func NewLightCollector(namespace string, bridge Bridge) prometheus.Collector { c := lightCollector{ bridge: bridge, lightBrightness: prometheus.NewGaugeVec( diff --git a/main.go b/main.go index 3f07484..7b16ae9 100644 --- a/main.go +++ b/main.go @@ -34,36 +34,45 @@ type Config struct { APIKey string `yaml:"api_key"` SensorConfig struct { IgnoreTypes []string `yaml:"ignore_types"` + MatchNames bool `yaml:"match_names"` } `yaml:"sensors"` } -func runServer() { - - raw, err := ioutil.ReadFile(*config) - if err != nil { - log.Fatalf("Error reading config file: %v\n", err) - } +// Bridge is an interface for the bridge struct from Collinux/gohue to allow stubbing in tests +type Bridge interface { + Login(string) error + GetAllSensors() ([]hue.Sensor, error) + GetAllLights() ([]hue.Light, error) + GetAllGroups() ([]hue.Group, error) +} - var cfg Config - err = yaml.Unmarshal(raw, &cfg) +func readConfig(raw []byte, cfg *Config) { + err := yaml.Unmarshal(raw, cfg) if err != nil { log.Fatalf("Error parsing config file: %v\n", err) } +} - bridge, err := hue.NewBridge(cfg.IPAddr) +func newBridge(ipAddr string) Bridge { + bridge, err := hue.NewBridge(ipAddr) if err != nil { - log.Fatalf("Error connecting to Hue bridge at %v: %v\n", cfg.IPAddr, err) + log.Fatalf("Error connecting to Hue bridge at %v: %v\n", ipAddr, err) } + return bridge +} - err = bridge.Login(cfg.APIKey) +func setupPrometheus(bridge Bridge, cfg *Config) { + err := bridge.Login((*cfg).APIKey) if err != nil { - log.Fatalf("Error authenticating with Hue bridge at %v: %v\n", cfg.IPAddr, err) + log.Fatalf("Error authenticating with Hue bridge at %v: %v\n", (*cfg).IPAddr, err) } prometheus.MustRegister(NewGroupCollector(namespace, bridge)) prometheus.MustRegister(NewLightCollector(namespace, bridge)) - prometheus.MustRegister(NewSensorCollector(namespace, bridge, cfg.SensorConfig.IgnoreTypes)) + prometheus.MustRegister(NewSensorCollector(namespace, bridge, (*cfg).SensorConfig.IgnoreTypes, (*cfg).SensorConfig.MatchNames)) +} +func listen() { http.Handle("/metrics", promhttp.Handler()) srv := &http.Server{ Addr: (*addr).String(), @@ -73,6 +82,19 @@ func runServer() { log.Fatal(srv.ListenAndServe()) } +func runServer() { + var cfg Config + + raw, err := ioutil.ReadFile(*config) + if err != nil { + log.Fatalf("Error reading config file: %v\n", err) + } + readConfig(raw, &cfg) + bridge := newBridge(cfg.IPAddr) + setupPrometheus(bridge, &cfg) + listen() +} + func main() { command := kingpin.MustParse(app.Parse(os.Args[1:])) if *version { diff --git a/sensors.go b/sensors.go index b3104d4..82a71b7 100644 --- a/sensors.go +++ b/sensors.go @@ -7,7 +7,7 @@ import ( ) type sensorCollector struct { - bridge *hue.Bridge + bridge Bridge ignoreTypes []string matchNames bool sensorValue *prometheus.GaugeVec @@ -38,7 +38,7 @@ func contains(a []string, x string) bool { } // NewSensorCollector Create a new Hue collector for sensors -func NewSensorCollector(namespace string, bridge *hue.Bridge, ignoreTypes []string, matchNames bool) prometheus.Collector { +func NewSensorCollector(namespace string, bridge Bridge, ignoreTypes []string, matchNames bool) prometheus.Collector { c := sensorCollector{ bridge: bridge, ignoreTypes: ignoreTypes, diff --git a/test/bridge.go b/test/bridge.go new file mode 100644 index 0000000..7950492 --- /dev/null +++ b/test/bridge.go @@ -0,0 +1,77 @@ +package test + +import ( + "context" + "errors" + hue "github.com/collinux/gohue" +) + +type APIFailure int + +const ( + LoginFailure APIFailure = iota + GetGroupsFailure + GetLightsFailure + GetSensorsFailure +) + +type stubHueBridge struct { + ctx context.Context + lights []hue.Light + groups []hue.Group + sensors []hue.Sensor +} + +func NewStubBridge() *stubHueBridge { + return &stubHueBridge{ + ctx: context.Background(), + } +} + +func (s *stubHueBridge) WithFailure(fail APIFailure) *stubHueBridge { + s.ctx = context.WithValue(s.ctx, fail, true) + return s +} + +func (s *stubHueBridge) WithGroups(groups []hue.Group) *stubHueBridge { + s.groups = groups + return s +} + +func (s *stubHueBridge) WithLights(lights []hue.Light) *stubHueBridge { + s.lights = lights + return s +} + +func (s *stubHueBridge) WithSensors(sensors []hue.Sensor) *stubHueBridge { + s.sensors = sensors + return s +} + +func (s *stubHueBridge) Login(apiKey string) error { + if val, ok := s.ctx.Value(LoginFailure).(bool); ok && val { + return errors.New("Deliberate login failure") + } + return nil +} + +func (s *stubHueBridge) GetAllGroups() ([]hue.Group, error) { + if val, ok := s.ctx.Value(GetGroupsFailure).(bool); ok && val { + return []hue.Group{}, errors.New("Deliberate get groups failure") + } + return s.groups, nil +} + +func (s *stubHueBridge) GetAllLights() ([]hue.Light, error) { + if val, ok := s.ctx.Value(GetLightsFailure).(bool); ok && val { + return []hue.Light{}, errors.New("Deliberate get lights failure") + } + return s.lights, nil +} + +func (s *stubHueBridge) GetAllSensors() ([]hue.Sensor, error) { + if val, ok := s.ctx.Value(GetSensorsFailure).(bool); ok && val { + return []hue.Sensor{}, errors.New("Deliberate get sensors failure") + } + return s.sensors, nil +} diff --git a/version.go b/version.go index a44ae0e..9077aa4 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package main -const VERSION = "0.1.0" +const VERSION = "0.2.0"