// // rhctl // // Copyright (C) 2009-2016 Christian Pointner // // This file is part of rhctl. // // rhctl is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhctl is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhctl. If not, see . // package main import ( "errors" "fmt" "time" "github.com/btittelbach/pubsub" ) type Mood uint const ( MoodAwakening Mood = iota MoodSad MoodNervous MoodHappy ) func (m Mood) String() string { switch m { case MoodAwakening: return "awakening" case MoodSad: return "sad" case MoodNervous: return "nervous" case MoodHappy: return "happy" } return "unknown" } type State struct { Mood Mood Switch SwitchState ActiveServer string Server map[string]ServerState } type CommandType uint8 const ( CmdState CommandType = iota CmdServer CmdSwitch ) func (c CommandType) String() string { switch c { case CmdState: return "state" case CmdServer: return "server" case CmdSwitch: return "switch" } return "unknown" } type Command struct { Type CommandType Args []string Response chan<- interface{} } type SwitchControl struct { sw *AudioSwitch servers []*PlayoutServer state State snooze <-chan time.Time Updates *pubsub.PubSub Commands chan *Command } func (ctrl *SwitchControl) handleCommand(cmd *Command) { switch cmd.Type { case CmdState: cmd.Response <- ctrl.state case CmdServer: if len(cmd.Args) == 0 { cmd.Response <- fmt.Errorf("no server specified") return } cmd.Response <- ctrl.reconcile(cmd.Args[0], false) case CmdSwitch: if len(cmd.Args) == 0 { cmd.Response <- fmt.Errorf("no command specified") return } c, err := NewSwitchCommandFromStrings(cmd.Args[0], cmd.Args[1:]...) if err != nil { cmd.Response <- fmt.Errorf("switch command syntax error: %s", err.Error()) return } c.Response = cmd.Response ctrl.sw.Commands <- c } } func handleServer(in <-chan ServerState, out chan<- ServerState) { for { update := <-in out <- update } } func (ctrl *SwitchControl) checkMissingOrStaleStates(maxAge time.Duration) { if time.Since(ctrl.state.Switch.AudioInputsChanged) > maxAge { ctrl.sw.Commands <- &SwitchCommand{SwitchCmdStateAudio, nil, nil} } if time.Since(ctrl.state.Switch.AudioSilenceChanged) > maxAge { ctrl.sw.Commands <- &SwitchCommand{SwitchCmdStateSilence, nil, nil} } if time.Since(ctrl.state.Switch.GPIChanged) > maxAge { ctrl.sw.Commands <- &SwitchCommand{SwitchCmdStateGPIAll, nil, nil} } if time.Since(ctrl.state.Switch.RelayChanged) > maxAge { ctrl.sw.Commands <- &SwitchCommand{SwitchCmdStateRelay, nil, nil} } if time.Since(ctrl.state.Switch.OCChanged) > maxAge { ctrl.sw.Commands <- &SwitchCommand{SwitchCmdStateOC, nil, nil} } for _, server := range ctrl.servers { s, exists := ctrl.state.Server[server.name] if !exists || time.Since(s.Changed) > maxAge { server.UpdateRequest <- true } } } func (ctrl *SwitchControl) getSwitchServerAssignment() (swsrv, swch string, err error) { // TODO: implement this return "", "", errors.New("not implemented") } func (ctrl *SwitchControl) selectServer(server string) { // TODO: send out commands to switch } func (ctrl *SwitchControl) reconcile(requestedServer string, snooze bool) (result bool) { rhdl.Printf("SwitchCTRL: reconciling state... (requested server: '%s', snooze: %t)", requestedServer, snooze) switch ctrl.state.Mood { case MoodAwakening: if !snooze { // this not a wakeup of the snooze timer // it's too early to take actions - let's learn a little bit more about current state return } if len(ctrl.servers) < 1 { ctrl.state.Mood = MoodHappy rhdl.Printf("SwitchCTRL: there are no servers configured -> now in mood: %s", ctrl.state.Mood) return } if requestedServer != "" { rhl.Printf("SwitchCTRL: ignoreing preferred server requests while waking-up") return } swsrv, swch, err := ctrl.getSwitchServerAssignment() if err != nil { ctrl.state.Mood = MoodSad rhl.Printf("SwitchCTRL: just woken up... switch input assignments are ambigious or outdated -> now in mood: %s", ctrl.state.Mood) return } rhl.Printf("SwitchCTRL: just woken up... switch is set to server '%s' channel '%s'", swsrv, swch) s, exists := ctrl.state.Server[swsrv] if !exists || s.Health != ServerAlive { rhl.Printf("SwitchCTRL: server '%s' is unknown or dead!", swsrv) for name, state := range ctrl.state.Server { if state.Health == ServerAlive { ctrl.state.Mood = MoodNervous rhl.Printf("SwitchCTRL: found another alive server -> now in mood: %s", ctrl.state.Mood) ctrl.selectServer(name) return } } ctrl.state.Mood = MoodSad rhl.Printf("SwitchCTRL: found no alive server -> now in mood: %s", ctrl.state.Mood) return } ctrl.state.ActiveServer = swsrv if s.Channel != swch { rhl.Printf("SwitchCTRL: switch and server '%s' channel mismatch: switch = '%s', server = '%s'!", swsrv, swch, s.Channel) ctrl.selectServer(ctrl.state.ActiveServer) return } for _, s := range ctrl.state.Server { if s.Health != ServerAlive { ctrl.state.Mood = MoodNervous rhl.Printf("SwitchCTRL: at least one configured server is dead -> now in mood: %s", ctrl.state.Mood) return } } ctrl.state.Mood = MoodHappy rhl.Printf("SwitchCTRL: all servers are alive -> now in mood: %s", ctrl.state.Mood) return default: if len(ctrl.servers) < 1 { ctrl.state.Mood = MoodHappy rhdl.Printf("SwitchCTRL: there are no servers configured -> now in mood: %s", ctrl.state.Mood) return } rhl.Printf("SwitchCTRL: error handling of mood %s is not implemented", ctrl.state.Mood) } // TODO: change active server if it fails or on request // return true if requested server got selected // TODO: change switch output mappings if servers change channels or fail // TODO: set mood dependent on overall-state return } func (ctrl *SwitchControl) Run() { rhdl.Printf("SwitchCTRL: handler running...") ctrl.snooze = time.After(time.Minute) // TODO: hardcode value serverStateChanges := make(chan ServerState, 8) for _, srv := range ctrl.servers { go handleServer(srv.StateChanges, serverStateChanges) } // send out commands to switch and/or server to get missing infos // TODO: this should be called from reconcile but with some sort of rate-limiting... ctrl.checkMissingOrStaleStates(2 * time.Hour) // TODO: hardcoded value ctrl.reconcile("", false) for { select { case <-ctrl.snooze: ctrl.reconcile("", true) case update := <-ctrl.sw.Updates: rhdl.Printf("got update from switch: %+v", update) for _, srv := range ctrl.servers { srv.SwitchUpdates <- update } ctrl.Updates.Pub(update, "switch:"+update.Type.String()) case state := <-ctrl.sw.StateChanges: // rhdl.Printf("got switch state update: %+v", state) ctrl.Updates.Pub(state, "switch:state") ctrl.state.Switch = state ctrl.reconcile("", false) ctrl.Updates.Pub(ctrl.state, "state") case state := <-serverStateChanges: rhdl.Printf("got server state update: %+v", state) ctrl.Updates.Pub(state, "server:state") ctrl.state.Server[state.Name] = state ctrl.reconcile("", false) ctrl.Updates.Pub(ctrl.state, "state") case cmd := <-ctrl.Commands: ctrl.handleCommand(cmd) } } } func SwitchControlInit(conf *Config, sw *AudioSwitch, servers []*PlayoutServer) (ctrl *SwitchControl) { ctrl = &SwitchControl{} ctrl.sw = sw ctrl.servers = servers ctrl.state.Mood = MoodAwakening ctrl.state.Server = make(map[string]ServerState) ctrl.Updates = pubsub.NewNonBlocking(32) ctrl.Commands = make(chan *Command, 8) return }