// // 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 ( "fmt" "strings" "time" ) type SwitchResult uint8 const ( SwitchOK SwitchResult = iota SwitchError ) func (c SwitchResult) String() string { switch c { case SwitchOK: return "OK" case SwitchError: return "error" } return "unknown" } type SwitchResponse struct { Result SwitchResult Message string } type SwitchUpdateType uint8 const ( SwitchAudio SwitchUpdateType = iota SwitchGPI SwitchOC SwitchRelay SwitchSilence ) func (c SwitchUpdateType) String() string { switch c { case SwitchAudio: return "audio" case SwitchGPI: return "gpi" case SwitchOC: return "oc" case SwitchRelay: return "relay" case SwitchSilence: return "silence" } return "unknown" } type SwitchUpdate struct { Type SwitchUpdateType Data string } type SwitchState struct { Audio [SwitchOutputNumMax]struct { Inputs [SwitchInputNumMax]bool Silence bool } AudioInputsUpdated time.Time AudioSilenceUpdated time.Time GPI [SwitchGPINumMax]bool GPIUpdated time.Time Relay [SwitchRelayNumMax]bool RelayUpdated time.Time OC [SwitchOCNumMax]bool OCUpdated time.Time } type AudioSwitch struct { port *SerialPort timeout time.Duration Inputs ConfigSwitchInputs current *SwitchCommand timer *time.Timer unit SwitchUnitID state SwitchState StateChanges chan SwitchState Commands chan *SwitchCommand Updates chan SwitchUpdate } func (sw *AudioSwitch) updateStateAudio(data string) { if len(data) < int(4+2*SwitchInputNumMax) { rhl.Printf("Audioswitch: invalid audio status update (too short)") return } var out SwitchOutputNum if err := out.fromString(data[3:4]); err != nil { rhl.Printf("Audioswitch: invalid audio status update (%s)", err) return } ins := strings.Split(data[5:int(4+2*SwitchInputNumMax)], ",") if len(ins) != int(SwitchInputNumMax) { rhl.Printf("Audioswitch: invalid audio status update (wrong number of inputs)") return } for i := 0; i < int(SwitchInputNumMax); i++ { switch ins[i] { case "0": sw.state.Audio[out-1].Inputs[i] = false case "1": sw.state.Audio[out-1].Inputs[i] = true default: rhl.Printf("Audioswitch: invalid audio status update (state must be either '1' or '0' but is '%s')", ins[i]) } } sw.state.AudioInputsUpdated = time.Now() sw.StateChanges <- sw.state } func (sw *AudioSwitch) updateStateGPI(data string) { if len(data) < 8 { rhl.Printf("Audioswitch: invalid gpi status update (too short)") return } if data[4] != 'A' { // 'SuP,ii,x' var in SwitchGPINum if err := in.fromString(data[4:6]); err != nil { rhl.Printf("Audioswitch: invalid gpi status update: %v", err) return } switch data[7] { case '0': sw.state.GPI[in] = false case '1': sw.state.GPI[in] = true default: rhl.Printf("Audioswitch: invalid gpi status update (state must be either '1' or '0' but is '%s')", data[7:8]) return } sw.state.GPIUpdated = time.Now() sw.StateChanges <- sw.state return } // 'SuP,A,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x' if len(data) < int(5+2*SwitchGPINumMax) { rhl.Printf("Audioswitch: invalid gpi status update (too short)") return } ins := strings.Split(data[6:int(5+2*SwitchGPINumMax)], ",") if len(ins) != int(SwitchGPINumMax) { rhl.Printf("Audioswitch: invalid gpi status update (wrong number of inputs)") return } for i := 0; i < int(SwitchGPINumMax); i++ { switch ins[i] { case "0": sw.state.GPI[i] = false case "1": sw.state.GPI[i] = true default: rhl.Printf("Audioswitch: invalid gpi status update (state must be either '1' or '0' but is '%s')", ins[i]) } } sw.state.GPIUpdated = time.Now() sw.StateChanges <- sw.state } func (sw *AudioSwitch) updateStateRelay(data string) { if len(data) < int(3+2*SwitchRelayNumMax) { rhl.Printf("Audioswitch: invalid relay status update (too short)") return } outs := strings.Split(data[4:int(3+2*SwitchRelayNumMax)], ",") if len(outs) != int(SwitchRelayNumMax) { rhl.Printf("Audioswitch: invalid relay status update (wrong number of outputs)") return } for i := 0; i < int(SwitchRelayNumMax); i++ { switch outs[i] { case "0": sw.state.Relay[i] = false case "1": sw.state.Relay[i] = true default: rhl.Printf("Audioswitch: invalid relay status update (state must be either '1' or '0' but is '%s')", outs[i]) } } sw.state.RelayUpdated = time.Now() sw.StateChanges <- sw.state } func (sw *AudioSwitch) updateStateOC(data string) { if len(data) < int(3+2*SwitchOCNumMax) { rhl.Printf("Audioswitch: invalid oc status update (too short)") return } outs := strings.Split(data[4:int(3+2*SwitchOCNumMax)], ",") if len(outs) != int(SwitchOCNumMax) { rhl.Printf("Audioswitch: invalid oc status update (wrong number of outputs)") return } for i := 0; i < int(SwitchOCNumMax); i++ { switch outs[i] { case "0": sw.state.OC[i] = false case "1": sw.state.OC[i] = true default: rhl.Printf("Audioswitch: invalid oc status update (state must be either '1' or '0' but is '%s')", outs[i]) } } sw.state.OCUpdated = time.Now() sw.StateChanges <- sw.state } func (sw *AudioSwitch) updateStateSilence(data string) { if len(data) < int(3+2*SwitchOutputNumMax) { rhl.Printf("Audioswitch: invalid silence status update (too short)") return } outs := strings.Split(data[4:int(3+2*SwitchOutputNumMax)], ",") if len(outs) != int(SwitchOutputNumMax) { rhl.Printf("Audioswitch: invalid silence status update (wrong number of outputs)") return } for i := 0; i < int(SwitchOutputNumMax); i++ { switch outs[i] { case "0": sw.state.Audio[i].Silence = false case "1": sw.state.Audio[i].Silence = true default: rhl.Printf("Audioswitch: invalid silence status update (state must be either '1' or '0' but is '%s')", outs[i]) } } sw.state.AudioSilenceUpdated = time.Now() sw.StateChanges <- sw.state } func (sw *AudioSwitch) handleData(data string) { if len(data) < 3 { rhl.Printf("Audioswitch: ignoring short line") return } rhdl.Printf("Audioswitch: got data: %q", data) if data[0:3] == "RRR" || data[0:3] == "EEE" { if sw.current != nil { if sw.current.Response != nil { resp := SwitchResponse{Message: data} resp.Result = SwitchError if data[0] == 'R' { resp.Result = SwitchOK } sw.current.Response <- resp } sw.current = nil sw.timer.Stop() sw.timer = nil } else { rhl.Printf("Audioswitch: ignoring unexpected response: %q", data) } return } if data[0] == 'S' && data[1] == ('0'+byte(sw.unit)) { switch data[2] { case 'L': sw.Updates <- SwitchUpdate{SwitchAudio, data} sw.updateStateAudio(data) case 'P': sw.Updates <- SwitchUpdate{SwitchGPI, data} sw.updateStateGPI(data) case 'R': sw.Updates <- SwitchUpdate{SwitchRelay, data} sw.updateStateRelay(data) case 'O': sw.Updates <- SwitchUpdate{SwitchOC, data} sw.updateStateOC(data) case 'S': sw.Updates <- SwitchUpdate{SwitchSilence, data} sw.updateStateSilence(data) } return } rhl.Printf("Audioswitch: ignoring invalid data: %q", data) } func (sw *AudioSwitch) Run() { stop := make(chan bool) sw.port.Run(stop) sw.current = nil sw.timer = nil rhdl.Printf("Audioswitch: handler running...") for { if sw.current != nil { select { case <-stop: return case <-sw.timer.C: if sw.current.Response != nil { sw.current.Response <- fmt.Errorf("command timed out") } sw.current = nil sw.timer = nil case data := <-sw.port.rx: sw.handleData(data) } } else { select { case <-stop: return case cmd := <-sw.Commands: c, err := cmd.Cmd.Generate(append(cmd.Args, sw.unit)...) if err != nil { if cmd.Response != nil { cmd.Response <- err } } else { rhdl.Printf("sending '%s' to switch", c) sw.current = cmd sw.port.tx <- c sw.timer = time.NewTimer(sw.timeout) } case data := <-sw.port.rx: sw.handleData(data) } } } } func SwitchInit(conf *Config) (sw *AudioSwitch, err error) { sw = &AudioSwitch{} sw.timeout = time.Second if conf.Audioswitch.Timeout.Duration > 0 { sw.timeout = conf.Audioswitch.Timeout.Duration } sw.unit = conf.Audioswitch.Unit sw.Inputs = conf.Audioswitch.Inputs sw.StateChanges = make(chan SwitchState, 16) sw.Commands = make(chan *SwitchCommand, 16) sw.Updates = make(chan SwitchUpdate, 32) if sw.port, err = SerialOpen(conf.Audioswitch.Device, conf.Audioswitch.Baudrate, ""); err != nil { err = fmt.Errorf("Audioswitch: error opening serial port: %s", err) return } return }