summaryrefslogtreecommitdiff
path: root/src/rhctl/playout_server.go
blob: 603414e501d0f2d58e2d74415ebc810fd207dba5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//
//  rhctl
//
//  Copyright (C) 2009-2016 Christian Pointner <equinox@helsinki.at>
//
//  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 <http://www.gnu.org/licenses/>.
//

package main

import (
	"fmt"
	"strings"
	"time"

	"code.helsinki.at/goserial"
)

type ServerHealth bool

const (
	ServerDead  ServerHealth = false
	ServerAlive ServerHealth = true

	ServerChannelReq = "channel?"
)

func (s ServerHealth) String() string {
	switch s {
	case ServerDead:
		return "dead"
	case ServerAlive:
		return "alive"
	}
	return "unknown"
}

func (s ServerHealth) MarshalText() (data []byte, err error) {
	data = []byte(s.String())
	return
}

type ServerState struct {
	Name              string
	Health            ServerHealth
	ResurrectionLevel float64
	Channel           string
	Updated           time.Time
}

type PlayoutServer struct {
	name          string
	device        *goserial.Port
	hbtimeout     time.Duration
	hbtimer       *time.Timer
	hbthreshold   uint
	hbcnt         uint
	state         ServerState
	StateChanges  chan ServerState
	Commands      chan Command
	UpdateRequest chan bool
	SwitchUpdates chan SwitchUpdate
}

func (srv *PlayoutServer) handleHeartbeat() {
	srv.hbtimer.Reset(srv.hbtimeout)
	if (srv.hbcnt + 1) < srv.hbthreshold {
		srv.hbcnt++
		srv.state.ResurrectionLevel = float64(srv.hbcnt) / float64(srv.hbthreshold)
		srv.StateChanges <- srv.state
		return
	}

	old := srv.state.Health
	srv.state.Health = ServerAlive
	srv.state.ResurrectionLevel = 1.0
	srv.state.Updated = time.Now()
	if old != srv.state.Health {
		srv.StateChanges <- srv.state
		rhl.Printf("Server(%s): is back from the dead!", srv.name)
		if srv.state.Channel == "" {
			srv.device.TX <- ServerChannelReq
		}
	}
}

func (srv *PlayoutServer) handleMessage(data string) {
	if data == "hb" {
		// rhdl.Printf("Server(%s): got heartbeat message", srv.name)
		srv.handleHeartbeat()
		return
	}

	rhdl.Printf("Server(%s): got message: %q", srv.name, data)
	if strings.HasPrefix(data, "channel ") {
		if len(data) <= 8 {
			rhl.Printf("Server(%s) sent empty channel name", srv.name)
			return
		}
		srv.state.Channel = data[8:]
		srv.state.Updated = time.Now()
		srv.Commands <- Command{Type: CmdChannel, Args: []string{srv.name, data[8:]}, Response: nil}
		srv.StateChanges <- srv.state
		return
	}
	if strings.HasPrefix(data, "handover ") {
		if len(data) <= 9 {
			rhl.Printf("Server(%s) sent empty handover", srv.name)
			return
		}
		srv.Commands <- Command{Type: CmdServer, Args: []string{data[9:]}, Response: nil}
		return
	}
	rhl.Printf("Server(%s): ignoring unknown message: %q", srv.name, data)
}

func (srv *PlayoutServer) handleHBTimeout() {
	rhl.Printf("Server(%s): heartbeat timed-out", srv.name)
	srv.hbcnt = 0
	srv.state.Health = ServerDead
	srv.state.ResurrectionLevel = 0.0
	srv.state.Updated = time.Now()
	srv.StateChanges <- srv.state
}

func (srv *PlayoutServer) Run() {
	stop := make(chan bool)
	srv.device.Run(stop)
	srv.hbtimer = time.NewTimer(srv.hbtimeout)
	srv.hbcnt = 0
	srv.state.ResurrectionLevel = 0.0

	rhdl.Printf("Server(%s): handler running...", srv.name)
	for {
		select {
		case <-stop:
			return
		case data := <-srv.device.RX:
			srv.handleMessage(data)
		case <-srv.hbtimer.C:
			srv.handleHBTimeout()
		case <-srv.UpdateRequest:
			if srv.state.Health == ServerDead {
				srv.StateChanges <- srv.state
			} else {
				srv.device.TX <- ServerChannelReq
			}
		case update := <-srv.SwitchUpdates:
			srv.device.TX <- update.Data
		}
	}
}

func ServerInit(name string, conf *Config) (srv *PlayoutServer, err error) {
	srv = &PlayoutServer{}
	srv.name = name
	srv.hbtimeout = 3 * time.Second
	if conf.Servers[name].HeartbeatTimeout.Duration > time.Duration(0) {
		srv.hbtimeout = conf.Servers[name].HeartbeatTimeout.Duration
	}
	srv.hbthreshold = 10
	if conf.Servers[name].HeartbeatThreshold > 0 {
		srv.hbthreshold = conf.Servers[name].HeartbeatThreshold
	}
	srv.state.Name = srv.name
	srv.state.Health = ServerDead
	srv.state.ResurrectionLevel = 0.0
	srv.state.Channel = ""
	srv.StateChanges = make(chan ServerState, 16)
	srv.Commands = make(chan Command, 8)
	srv.UpdateRequest = make(chan bool, 8)
	srv.SwitchUpdates = make(chan SwitchUpdate, 32)

	if srv.device, err = goserial.Open(conf.Servers[name].Device, conf.Servers[name].Baudrate, "\n"); err != nil {
		err = fmt.Errorf("Server(%s): error opening serial port: %s", srv.name, err)
		return
	}

	return
}