From: jweigele Date: Tue, 13 Dec 2022 21:43:53 +0000 (-0800) Subject: Fully implemented lights package and further cleaned up logging X-Git-Url: http://git.hexthepla.net/?a=commitdiff_plain;h=bcbda2b4281e7cf56fc199015462d1b258648552;p=rabbit_go Fully implemented lights package and further cleaned up logging * Lights now has working subimplementations for pico and zig * Fixed a bug with switch expiration infinite looping * Interfaces are used to get the specific relay data... probably very messy but it's the smallest amount of function changes I could do * Logging is now bound per relay, to allow more specific output when we're debugging (i.e. friendlyname for those that support it, etc) * Prometheus is basic with no metrics expiration, and a simple one second update loop... but that's how it was before in python SO --- diff --git a/Dockerfile.lights b/Dockerfile.lights new file mode 100644 index 0000000..1801d33 --- /dev/null +++ b/Dockerfile.lights @@ -0,0 +1,21 @@ +# STEP 1 build executable binary +FROM golang:alpine as builder +# Install SSL ca certificates +RUN apk update && apk add git && apk add ca-certificates +# Create appuser +RUN adduser -D -g '' appuser +COPY . $GOPATH/src/mypackage/myapp/ +WORKDIR $GOPATH/src/mypackage/myapp/ +COPY ./go.mod ./ +#COPY ./go.sum ./ +RUN go mod download +WORKDIR $GOPATH/src/mypackage/myapp/lights + +#get dependancies +#RUN go get -d -v +#build the binary +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o /go/bin/lights + +FROM alpine:edge +COPY --from=builder /go/bin/lights /bin/lights +ENTRYPOINT ["/bin/lights", "-config", "/conf/config-docker.json"] diff --git a/go.mod b/go.mod index 4b6fe7c..1bbd00c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 github.com/prometheus/client_golang v1.14.0 github.com/rabbitmq/amqp091-go v1.5.0 + golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 gopkg.in/yaml.v2 v2.4.0 k8s.io/klog/v2 v2.80.1 ) diff --git a/helper/helper.go b/helper/helper.go index aa625c0..24527b4 100644 --- a/helper/helper.go +++ b/helper/helper.go @@ -142,7 +142,7 @@ func Bind(routingKey string, rabbit *RabbitConfig) error { if err != nil { return err } - logger.Info("new queue declared", "queue", rabbit.Queue) + logger.V(2).Info("new queue declared", "queue", rabbit.Queue) } // last two are block on binding and args @@ -150,7 +150,7 @@ func Bind(routingKey string, rabbit *RabbitConfig) error { if err != nil { return err } - logger.Info("Bound queue to exchange", "queue", rabbit.Queue, "routingKey", routingKey) + logger.V(2).Info("Bound queue to exchange", "queue", rabbit.Queue, "routingKey", routingKey) return nil } @@ -170,7 +170,7 @@ func StartConsuming(rabbit RabbitConfig) (<-chan amqp.Delivery, error) { false, // immediate (wait and block please) nil, // args ) - logger.Info("consuming on queue", "queue", rabbit.Queue) + logger.V(2).Info("consuming on queue", "queue", rabbit.Queue) return deliveries, err } @@ -252,9 +252,10 @@ func SendData(rabbitData RabbitSend, rabbit RabbitConfig, verboseSend bool) erro if err != nil { return err } - if verboseSend { logger.Info("Sent data to rabbitmq", "data", rabbitData.Data, "routingKey", routingKey) + } else { + logger.V(2).Info("Sent data to rabbitmq", "data", rabbitData.Data, "routingKey", routingKey) } return nil diff --git a/lights/main.go b/lights/main.go index 308c588..8014a5d 100644 --- a/lights/main.go +++ b/lights/main.go @@ -1,19 +1,29 @@ package main import ( + "encoding/json" "flag" "fmt" "io/ioutil" "math" "os" + "reflect" + "sync" "time" + // prometheus imports + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + // color correction "github.com/lucasb-eyer/go-colorful" + "golang.org/x/exp/slices" "gopkg.in/yaml.v2" "github.com/go-logr/logr" - "github.com/rabbitmq/amqp091-go" "unpiege.net/rabbit_go.git/helper" ) @@ -21,6 +31,13 @@ var ( configFilename string yamlConfigFilename string logger logr.Logger + lightGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "light", + Help: "Used to measure light status in the real world", + }, + []string{"location"}, + ) ) // structs @@ -29,26 +46,121 @@ var ( type RGBConfigYaml struct { RGBRelays []RGBRelay `yaml:"RGBRelays"` RGBZigs []RGBZig `yaml:"RGBZigs"` + RGBPicos []RGBPico `yaml:"RGBPicos"` + Switches []Switch `yaml:"Switches"` } type Switch struct { - state bool - overrideTime time.Time - expiresAt time.Time - parentRelay *RGBRelay + State bool + Location string `yaml:"Location"` + FriendlyName string `yaml:"FriendlyName"` + OverrideSeconds int `yaml:"OverrideSeconds"` + ExpiresAt time.Time + parentRelay *RGBRelay +} + +// Switch definitions +func (curSwitch *Switch) getLocation() string { + return curSwitch.Location } func (curSwitch *Switch) isActive() bool { - return false + curTime := time.Now().UTC() + // this will default true when initialized + // thus, switch will start inactive like we want + if curTime.After(curSwitch.ExpiresAt) { + logger.V(5).Info("Switch is not active", "switch", curSwitch) + return false + } + logger.V(3).Info("Switch is active!", "switch", curSwitch) + return true +} + +func (curSwitch *Switch) setExpired(bubbleUp bool) { + logger.Info("Override removed for switch", "switch", curSwitch) + curTime := time.Now().UTC() + curSwitch.ExpiresAt = curTime + // set every other switch (not including us to avoid the loop) at this location to expired + if curSwitch.parentRelay != nil && bubbleUp { + curSwitch.parentRelay.setSwitchesExpired(curSwitch) + } +} + +func (curSwitch *Switch) setParent(relay *RGBRelay) { + curSwitch.parentRelay = relay +} + +func (curSwitch *Switch) getRoutingKey() string { + // this is just how zigbee switches are setup now + return fmt.Sprintf("zigbee2mqtt.%s", curSwitch.FriendlyName) +} + +func (curSwitch *Switch) setState(state bool) { + curTime := time.Now().UTC() + curSwitch.State = state + curSwitch.ExpiresAt = curTime.Add(time.Duration(curSwitch.OverrideSeconds) * time.Second) + logger.Info("Setting state for switch", "state", state, "switch", curSwitch) +} + +func (curSwitch *Switch) parseState(data map[string]interface{}) { + logger.V(3).Info("Got parseState on switch", "switch", curSwitch, "data", data) + switchAction := data["action"] + // switch on switch, lol + switch switchAction { + case "on_press_release", "on_press": + curSwitch.setState(true) + case "off_press_release", "off_press": + curSwitch.setState(false) + case "down_press", "down_press_release": + curSwitch.setExpired(true) + default: + logger.Error(nil, "Unsupported switch state/data!", "data", data) + } } +// interface defs for relays + type IRGBRelay interface { Init(sendChannel chan helper.RabbitSend) setLastMotion() - addSwitch(newSwitch Switch) + addSwitch(newSwitch *Switch) setPWM(red, green, blue float64) getLocation() string + getMetricValue() float64 +} + +type IRGBFriendly interface { + IRGBRelay + getFriendlyName() string +} + +type IRelayData interface { + getRelayData(red, green, blue float64) (map[string]interface{}, string) +} + +type RelayData struct { + parent *RGBRelay +} + +func (relayData RelayData) getRelayData(red, green, blue float64) (map[string]interface{}, string) { + relayData.parent.logger.V(3).Info("Entering getRelayData for relay") + retval := make(map[string]interface{}, 0) + if relayData.parent.LastState { + retval["red"] = red + retval["green"] = green + retval["blue"] = blue + } else { + retval["red"] = 0.0 + retval["green"] = 0.0 + retval["blue"] = 0.0 + } + retval["location"] = relayData.parent.Location + relayData.parent.logger.V(2).Info("Final relay getRelayData output", "retval", retval) + + return retval, "rgb_relay" + } + type RGBRelay struct { UpdateStaleness int `yaml:"UpdateStaleness"` DimmingTimeout int `yaml:"DimmingTimeout"` @@ -56,8 +168,10 @@ type RGBRelay struct { LastUpdate time.Time LastMotion time.Time LastState bool + logger logr.Logger sendChannel chan helper.RabbitSend - switches []Switch `yaml:"Switches"` + switches []*Switch `yaml:"Switches"` + relayData IRelayData } /*func NewRGBRelay(Location string, DimmingTimeout int, sendChannel chan helper.RabbitSend) *RGBRelay { @@ -68,7 +182,9 @@ type RGBRelay struct { }*/ func (relay *RGBRelay) Init(sendChannel chan helper.RabbitSend) { + relay.logger = logger.WithValues("relay", relay) relay.sendChannel = sendChannel + relay.relayData = RelayData{parent: relay} // initialize defaults, if they weren't set previously if relay.DimmingTimeout == 0 { relay.DimmingTimeout = 300 @@ -91,7 +207,7 @@ func (relay *RGBRelay) shouldOverride() bool { } // getMetricValue is used to set prometheus lights on/off state -func (relay *RGBRelay) getMetricValue() int { +func (relay *RGBRelay) getMetricValue() float64 { if relay.LastState { return 1 } else { @@ -140,56 +256,42 @@ func (relay *RGBRelay) getRoutingKey() string { return "rgb_relay" } -func (relay *RGBRelay) getSetMap(red, green, blue float64) map[string]interface{} { - logger.Info("Entering getSetMap regular for relay", "relay", relay) - retval := make(map[string]interface{}, 0) - if relay.LastState { - retval["red"] = red - retval["green"] = green - retval["blue"] = blue - } else { - retval["red"] = 0.0 - retval["green"] = 0.0 - retval["blue"] = 0.0 +func (relay *RGBRelay) isSwitchActive() bool { + var retval bool + for _, curSwitch := range relay.switches { + if curSwitch.isActive() { + retval = true + } } - retval["location"] = relay.Location return retval } -func (relay *RGBRelay) isSwitchActive() bool { - return false -} - func (relay *RGBRelay) getSwitchOverride() bool { var override bool for _, curSwitch := range relay.switches { if curSwitch.isActive() { - override = curSwitch.state + override = curSwitch.State } } return override } -func (relay *RGBRelay) addSwitch(newSwitch Switch) { - switchPresent := false - for _, curSwitch := range relay.switches { - if curSwitch == newSwitch { - logger.Info("Tried to add a switch twice", "curSwitch", curSwitch, "newSwitch", newSwitch) - switchPresent = true - break - } - - } - if !switchPresent { - logger.Info("Adding new switch to relay", "relay", relay, "switch", newSwitch) +func (relay *RGBRelay) addSwitch(newSwitch *Switch) { + if slices.Contains(relay.switches, newSwitch) { + relay.logger.Error(nil, "Tried to add a switch twice", "switch", newSwitch) + } else { + relay.logger.V(1).Info("Adding new switch to relay", "switch", newSwitch) relay.switches = append(relay.switches, newSwitch) + newSwitch.setParent(relay) } } -func (relay *RGBRelay) setSwitchesExpired(source Switch) { +func (relay *RGBRelay) setSwitchesExpired(source *Switch) { // for every switch except the one that triggered the expiration.. + relay.logger.V(2).Info("triggered expire switches", "source", source, "relay.switches", relay.switches) for _, curSwitch := range relay.switches { + // expire it if curSwitch != source { - // TODO expire the switch without calling the parent + curSwitch.setExpired(false) } } } @@ -201,9 +303,9 @@ func (relay *RGBRelay) setPWM(red, green, blue float64) { } func (relay *RGBRelay) sendUpdate(red, green, blue float64) { - logger.V(3).Info("Sending update for relay", "relay", relay) - rgbData := relay.getSetMap(red, green, blue) - sendItem := helper.RabbitSend{Data: rgbData, RoutingKey: relay.getRoutingKey()} + relay.logger.V(3).Info("Sending update for relay") + rgbData, routingKey := relay.relayData.getRelayData(red, green, blue) + sendItem := helper.RabbitSend{Data: rgbData, RoutingKey: routingKey} relay.sendChannel <- sendItem } @@ -223,27 +325,27 @@ func (relay *RGBRelay) shouldUpdate() bool { if LastState == true && desiredState == true { // last state on, desired on (aka already on) if relay.LastUpdateTooOld() { - logger.Info("Last update too old, refreshing (on)", "relay", relay) + relay.logger.V(2).Info("Last update too old, refreshing (on)") relay.setLastUpdate() retval = true } // otherwise nothing to change } else if LastState == false && desiredState == true { // last off, desired on (aka turn on) - logger.Info("Turning relay on", "relay", relay) + relay.logger.Info("Turning relay on") relay.LastState = true relay.setLastUpdate() retval = true } else if LastState == true && desiredState == false { // last state on, desired off (aka turn off) - logger.Info("Turning relay off", "relay", relay) + relay.logger.Info("Turning relay off") relay.LastState = false relay.setLastUpdate() retval = true } else if LastState == false && desiredState == false { // last state off, desired off (aka already off) if relay.LastUpdateTooOld() { - logger.Info("Last update too old, refreshing (off)", "relay", relay) + relay.logger.V(2).Info("Last update too old, refreshing (off)") relay.setLastUpdate() retval = true } @@ -259,61 +361,33 @@ func (relay *RGBRelay) shouldUpdate() bool { } // RGBZig Defs -type RGBZig struct { - RGBRelay `yaml:"RGBRelay"` - FriendlyName string `yaml:"FriendlyName"` - Sengled bool `yaml:"Sengled"` -} - -/*func NewRGBZig(Location string, DimmingTimeout int, sendChannel chan helper.RabbitSend, FriendlyName string, Sengled bool) *RGBZig { - retval := RGBZig{} - retval.RGBRelay = NewRGBRelay(Location, DimmingTimeout, sendChannel) - retval.FriendlyName = FriendlyName - retval.Sengled = Sengled - return &retval -}*/ - -// RGBZig overrides -func (zig *RGBZig) getRoutingKey() string { - return fmt.Sprintf("zigbee2mqtt.%s.set", zig.FriendlyName) -} - -func (zig *RGBZig) setPWM(red, green, blue float64) { - if zig.shouldUpdate() { - zig.sendUpdate(red, green, blue) - } +type ZigRelayData struct { + parent *RGBZig } -func (zig *RGBZig) sendUpdate(red, green, blue float64) { - logger.V(3).Info("Sending update for zig", "zig", zig) - rgbData := zig.getSetMap(red, green, blue) - sendItem := helper.RabbitSend{Data: rgbData, RoutingKey: zig.getRoutingKey()} - zig.sendChannel <- sendItem -} - -func (zig *RGBZig) getSetMap(red, green, blue float64) map[string]interface{} { - logger.Info("Entering getSetMap regular for zig", "zig", zig) +func (relayData ZigRelayData) getRelayData(red, green, blue float64) (map[string]interface{}, string) { + relayData.parent.logger.V(3).Info("Entering getRelayData for zig") retval := make(map[string]interface{}, 0) // we should be turned on - if zig.LastState { + if relayData.parent.LastState { // correct for different sengled colors - if zig.Sengled { + if relayData.parent.Sengled { red *= 1.5 blue *= 0.75 green *= 3.0 } // gamma correct intermediate values - red = zig.gammaCorrect(red) - green = zig.gammaCorrect(green) - blue = zig.gammaCorrect(blue) + red = relayData.parent.gammaCorrect(red) + green = relayData.parent.gammaCorrect(green) + blue = relayData.parent.gammaCorrect(blue) // color conversions c := colorful.Color{R: red, G: green, B: blue} h, s, v := c.Hsv() // again, sengled changes - if zig.Sengled { - logger.Info("Boosting saturation", "pre", s, "post", s+0.2) + if relayData.parent.Sengled { + logger.V(4).Info("Boosting saturation", "pre", s, "post", s+0.2) s += 0.2 } newColor := colorful.Hsv(h, s, v) @@ -331,58 +405,243 @@ func (zig *RGBZig) getSetMap(red, green, blue float64) map[string]interface{} { // we should be turned off retval["state"] = "OFF" } - logger.Info("Final zig getSetMap output", "retval", retval) - return retval + relayData.parent.logger.V(2).Info("Final zig getRelayData output", "retval", retval) + return retval, relayData.parent.getRoutingKey() + +} + +type RGBZig struct { + RGBRelay `yaml:"RGBRelay"` + FriendlyName string `yaml:"FriendlyName"` + Sengled bool `yaml:"Sengled"` +} + +func (zig *RGBZig) Init(sendChannel chan helper.RabbitSend) { + zig.RGBRelay.Init(sendChannel) + zig.logger = logger.WithValues("relay", zig) + zig.RGBRelay.relayData = ZigRelayData{parent: zig} +} + +// RGBZig added function +func (zig *RGBZig) getFriendlyName() string { + return zig.FriendlyName +} + +// RGBZig overrides +func (zig *RGBZig) getRoutingKey() string { + return fmt.Sprintf("zigbee2mqtt.%s.set", zig.FriendlyName) +} + +// RGBPico defs +type PicoRelayData struct { + parent *RGBPico +} + +func (relayData PicoRelayData) getRelayData(red, green, blue float64) (map[string]interface{}, string) { + relayData.parent.logger.V(3).Info("Entering getRelayData for pico") + // need to do this in two stages so the json encodes properly + actionMap := make(map[string]interface{}, 0) + retval := make(map[string]interface{}, 0) + + // turned on last + if relayData.parent.LastState { + // need to do this in two stages so the json encodes properly + actionMap["red"] = red + actionMap["green"] = green + actionMap["blue"] = blue + actionMap["red"] = 0.0 + actionMap["green"] = 0.0 + actionMap["blue"] = 0.0 + } + var actionData []byte + actionData, err := json.Marshal(actionMap) + if err != nil { + logger.Error(err, "error found when trying to encode pico action data, just ignoring") + } + retval["action"] = string(actionData) + // should be fully constructed for final encoding by rabbit helper + relayData.parent.logger.V(2).Info("Final pico getRelayData output", "retval", retval) + return retval, relayData.parent.getRoutingKey() + +} + +type RGBPico struct { + RGBZig `yaml:"RGBZig"` + relayData PicoRelayData +} + +func (pico *RGBPico) Init(sendChannel chan helper.RabbitSend) { + pico.RGBZig.Init(sendChannel) + pico.logger = logger.WithValues("relay", pico) + pico.RGBZig.RGBRelay.relayData = PicoRelayData{parent: pico} } type RGBManager struct { - relays []IRGBRelay + relays []IRGBRelay + switches []*Switch + friendlyToLocation map[string][]string + friendlyMutex sync.Mutex + rabbitHelper *helper.RabbitConfig } func (manager *RGBManager) addRelay(relay IRGBRelay) { manager.relays = append(manager.relays, relay) + logger.V(2).Info("total relays", "manager.relays", manager.relays) + manager.friendlyMutex.Lock() + defer manager.friendlyMutex.Unlock() + logger.V(4).Info("(re)building friendly to location map now") + + manager.friendlyToLocation = make(map[string][]string, 0) + // build friendlyToLocation map, in order to update linked locations later + for _, relay := range manager.relays { + // check if we implement the interface giving friendlynames (which means we're an RGBZig or subclass) + curZig, ok := relay.(IRGBFriendly) + if ok { + logger.V(4).Info("Found an RGBZig relay", "relay", curZig) + _, isPresent := manager.friendlyToLocation[curZig.getFriendlyName()] + if !isPresent { + manager.friendlyToLocation[curZig.getFriendlyName()] = make([]string, 0) + } + if !slices.Contains(manager.friendlyToLocation[curZig.getFriendlyName()], curZig.getLocation()) { + manager.friendlyToLocation[curZig.getFriendlyName()] = append(manager.friendlyToLocation[curZig.getFriendlyName()], curZig.getLocation()) + } + } + } + + logger.V(4).Info("Final friendlyToLocation map", "map", manager.friendlyToLocation) + } -func (manager *RGBManager) init(rabbit helper.RabbitConfig) { +func (manager *RGBManager) getLinkedLocations(sourceLocation string) []string { + // using a map as a set just to build all the friendly names we care about + uniqueFriendlyNames := make(map[string]int, 0) + + for _, relay := range manager.relays { + // did we get a friendly name capable relay? + curZig, ok := relay.(IRGBFriendly) + if ok { + if curZig.getLocation() == sourceLocation { + uniqueFriendlyNames[curZig.getFriendlyName()] = 1 + } + } + } + + // same thing here, for the retval + var retval []string + // all the keys stored above, aka all the unique friendly names + for friendlyName, _ := range uniqueFriendlyNames { + // all the values in the map, aka all locations for every unique friendly name (may overlap!) + for _, linkedLocation := range manager.friendlyToLocation[friendlyName] { + if !slices.Contains(retval, linkedLocation) { + retval = append(retval, linkedLocation) + } + } + } + + logger.V(3).Info("Linked locations (by friendly)", "sourceLocation", sourceLocation, "linkedLocations", retval) + return retval +} + +func (manager *RGBManager) init(rabbit *helper.RabbitConfig) { + manager.rabbitHelper = rabbit + // these are hardcoded mostly, the two places we know we'll get data bindTopics := []string{"timecolorshift.py", "motion"} for _, bindTopic := range bindTopics { - err := helper.Bind(bindTopic, &rabbit) + err := helper.Bind(bindTopic, manager.rabbitHelper) if err != nil { logger.Error(err, "unable to bind successfully to exchange", "routingKey", "leds") os.Exit(1) } } - deliveries, err := helper.StartConsuming(rabbit) +} + +func (manager *RGBManager) addSwitch(curSwitch *Switch) { + // first, bind state updates for this switch so we see them later + err := helper.Bind(curSwitch.getRoutingKey(), manager.rabbitHelper) + logger.V(2).Info("Bound rabbit to routingKey", "routingKey", curSwitch.getRoutingKey()) if err != nil { - logger.Error(err, "unable to start consuming data from rabbit") + logger.Error(err, "unable to bind successfully to exchange", "routingKey", curSwitch.getRoutingKey()) os.Exit(1) } - go manager.readLoop(deliveries) + // next, add to a list of all switches we monitor, so that we know where to route those state updates + manager.switches = append(manager.switches, curSwitch) + + // finally, for all relays matching the location make sure to add the relevant switches + // huge caveat, needs to be done _after_ all relays are added to the manager for proper matching + for _, curRelay := range manager.relays { + if curRelay.getLocation() == curSwitch.getLocation() { + curRelay.addSwitch(curSwitch) + } + } +} +func (manager *RGBManager) handleSwitch(data map[string]interface{}) { + logger.Info("Got switch data", "data", data) } func (manager *RGBManager) handleMotion(data map[string]interface{}) { logger.V(3).Info("Got motion data", "data", data) - for _, relay := range manager.relays { - if data["location"] == relay.getLocation() { - logger.Info("Matching location for relay", "relay", relay, "data", data) - relay.setLastMotion() + // only care to go further if it's moving, otherwise we'll just age out + if data["motion_detected"].(bool) { + linkedLocations := manager.getLinkedLocations(data["location"].(string)) + for _, relay := range manager.relays { + // matches one of the linked locations we got above + if slices.Contains(linkedLocations, relay.getLocation()) { + logger.V(3).Info("Matching location for relay", "relay", fmt.Sprintf("%v", reflect.ValueOf(relay)), "data", data) + relay.setLastMotion() + } } } } func (manager *RGBManager) handlePWM(data map[string]interface{}) { logger.V(2).Info("Got PWM data", "data", data) + var uniqueFriendlyNames = make(map[string]IRGBFriendly, 0) for _, relay := range manager.relays { - // float64 triplets should always hold as long as we control the sender + // did we get a friendly name capable relay? + curZig, ok := relay.(IRGBFriendly) + if ok { + // set (or overwrite) the friendly name reference to this relay + // essentially, get _only_ one relay per friendly name that we'll action + uniqueFriendlyNames[curZig.getFriendlyName()] = curZig + } else { + // immediately set the PWM for non-friendly name enabled relays (since we know there's no dupes) + relay.setPWM(data["red"].(float64), data["green"].(float64), data["blue"].(float64)) + } + + } + // here, we don't care about the key, just the fact that we only get one relay out per + for _, relay := range uniqueFriendlyNames { relay.setPWM(data["red"].(float64), data["green"].(float64), data["blue"].(float64)) } } -func (manager *RGBManager) readLoop(deliveries <-chan amqp091.Delivery) { +func (manager *RGBManager) updateMetrics() { + for { + // sleep for 1 second then update + time.Sleep(time.Duration(1) * time.Second) + for _, relay := range manager.relays { + // did we get a friendly name capable relay? + curZig, ok := relay.(IRGBFriendly) + // this should give us a fairly unique value, location is friendlyname where enabled, otherwise location is location + if ok { + lightGauge.With(prometheus.Labels{"location": curZig.getFriendlyName()}).Set(curZig.getMetricValue()) + } else { + lightGauge.With(prometheus.Labels{"location": relay.getLocation()}).Set(relay.getMetricValue()) + } + } + } +} +func (manager *RGBManager) readLoop() { + deliveries, err := helper.StartConsuming(*manager.rabbitHelper) + if err != nil { + logger.Error(err, "unable to start consuming data from rabbit") + os.Exit(1) + } + //var counter int for delivery := range deliveries { logger.V(3).Info("got a delivery", "delivery", delivery) @@ -393,12 +652,26 @@ func (manager *RGBManager) readLoop(deliveries <-chan amqp091.Delivery) { continue } switch delivery.RoutingKey { + // first, large bucket categories case "motion": manager.handleMotion(item) case "timecolorshift.py": manager.handlePWM(item) + // now, individual routing keys (entirely switches if they match) default: - logger.Error(nil, "no routing key match on delivery!", "routingKey", delivery.RoutingKey) + var matches bool + for _, curSwitch := range manager.switches { + if delivery.RoutingKey == curSwitch.getRoutingKey() { + matches = true + logger.V(2).Info("Matching routing key for switch, handling", "routingKey", delivery.RoutingKey, "switch", curSwitch) + curSwitch.parseState(item) + } + + } + if !matches { + logger.Error(nil, "no routing key match on delivery!", "routingKey", delivery.RoutingKey) + } + } } @@ -408,10 +681,17 @@ func (manager *RGBManager) readLoop(deliveries <-chan amqp091.Delivery) { func sendLoop(channel chan helper.RabbitSend, rabbit helper.RabbitConfig) { for sendItem := range channel { //helper.SendData(sendItem, rabbit, true) + logger.V(3).Info("Sending item", "item", sendItem) helper.SendData(sendItem, rabbit, false) } } +func promSetup() { + // prometheus setup + http.Handle("/metrics", promhttp.Handler()) + http.ListenAndServe(":9098", nil) +} + func main() { // logging and flag initialization flag.StringVar(&configFilename, "config", "", "the config filename") @@ -441,7 +721,7 @@ func main() { logger.Error(err, "error parsing time config yaml file") os.Exit(1) } - logger.Info("parsed config as follows", "relayDefs", relayDefs) + logger.V(1).Info("parsed config as follows", "relayDefs", relayDefs) } rabbit, err := helper.SetupRabbit(configFilename, "", "lights") // config file, default routing key, source @@ -449,24 +729,43 @@ func main() { logger.Error(err, "unable to setup rabbit") os.Exit(1) } - logger.Info("rabbit", "rabbit", rabbit) channel := make(chan helper.RabbitSend) go sendLoop(channel, rabbit) manager := RGBManager{} - manager.init(rabbit) + manager.init(&rabbit) + + for i, _ := range relayDefs.RGBRelays { + relayDefs.RGBRelays[i].Init(channel) + logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBRelays[i]) + manager.addRelay(&relayDefs.RGBRelays[i]) + } - for _, relay := range relayDefs.RGBRelays { - relay.Init(channel) - manager.addRelay(&relay) + // we want to use the index value, because we're adding by pointer rather than value + for i, _ := range relayDefs.RGBZigs { + relayDefs.RGBZigs[i].Init(channel) + logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBZigs[i]) + manager.addRelay(&relayDefs.RGBZigs[i]) } - for _, relay := range relayDefs.RGBZigs { - relay.Init(channel) - manager.addRelay(&relay) + for i, _ := range relayDefs.RGBPicos { + relayDefs.RGBPicos[i].Init(channel) + logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBPicos[i]) + manager.addRelay(&relayDefs.RGBPicos[i]) } + for i, _ := range relayDefs.Switches { + //relayDefs.Switches[i].Init(channel) + logger.V(1).Info("Adding switch to manager", "switch", relayDefs.Switches[i]) + manager.addSwitch(&relayDefs.Switches[i]) + } + + go manager.readLoop() + go promSetup() + + go manager.updateMetrics() + for { time.Sleep(time.Duration(5) * time.Second) } diff --git a/subbuilds b/subbuilds index a259dfb..ddc5297 100644 --- a/subbuilds +++ b/subbuilds @@ -1 +1 @@ -export BUILDS="timecolorshift wunder reprocess" +export BUILDS="lights timecolorshift wunder reprocess"