Skip to content

Commit e130e2a

Browse files
committed
Add controller error state handling
Resolves #579 We can check the controller error variable first to easily get any error state. If the controller does not yet support this, we still check other pieces of information.
1 parent f1d555a commit e130e2a

4 files changed

Lines changed: 128 additions & 1 deletion

File tree

cmd/oceantv/broadcast_hardware_machine.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,35 @@ func (s *hardwareStarting) enter() {
234234
return
235235
}
236236

237+
// The first check for any known hardware error states.
238+
hwErr, err := s.camera.error(s.broadcastContext)
239+
if err != nil {
240+
errWrapped := fmt.Errorf("could not get hardware error state: %w", err)
241+
s.log(errWrapped.Error())
242+
// NOTE here we could do this, however it's not certain that all ESPs
243+
// will have the latest firmware that supports this, and so it's not
244+
// necessarily a showstopper.
245+
// s.bus.publish(invalidConfigurationEvent{errWrapped})
246+
// return
247+
}
248+
249+
switch {
250+
case errors.Is(hwErr, LowVoltageAlarm):
251+
s.log("controller voltage is low, waiting for recovery before starting")
252+
s.bus.publish(lowVoltageEvent{})
253+
return
254+
case errors.Is(hwErr, None):
255+
// Continue other checks, this is good.
256+
case hwErr != nil:
257+
errWrapped := fmt.Errorf("unhandled controller hardware error: %w", hwErr)
258+
s.log(errWrapped.Error())
259+
s.bus.publish(invalidConfigurationEvent{errWrapped})
260+
return
261+
default:
262+
// This means heErr get failed to get, which at this stage just means
263+
// we have a controller that doesn't have the latest firmware.
264+
}
265+
237266
voltage, err := s.camera.voltage(s.broadcastContext)
238267
if err != nil {
239268
errWrapped := fmt.Errorf("could not get hardware voltage: %w", err)
@@ -1015,10 +1044,32 @@ type hardwareManager interface {
10151044
shutdown(ctx *broadcastContext)
10161045
stop(ctx *broadcastContext)
10171046
publishEventIfStatus(ctx *broadcastContext, event event, status bool, mac int64, store Store, log func(format string, args ...interface{}), publish func(event event))
1047+
error(ctx *broadcastContext) (error, error)
10181048
}
10191049

10201050
type revidCameraClient struct{}
10211051

1052+
type ControllerError string
1053+
1054+
const (
1055+
None ControllerError = ""
1056+
LowVoltageAlarm ControllerError = "LowVoltageAlarm"
1057+
)
1058+
1059+
func (e ControllerError) Error() string {
1060+
return string(e)
1061+
}
1062+
1063+
func (e ControllerError) Is(target error) bool {
1064+
if target == nil {
1065+
return false
1066+
}
1067+
if t, ok := target.(ControllerError); ok {
1068+
return e == t
1069+
}
1070+
return false
1071+
}
1072+
10221073
func (c *revidCameraClient) voltage(ctx *broadcastContext) (float64, error) {
10231074
// Get battery voltage sensor, which we'll use to get scale factor and current voltage value.
10241075
sensor, err := model.GetSensorV2(context.Background(), ctx.store, ctx.cfg.ControllerMAC, ctx.cfg.BatteryVoltagePin)
@@ -1131,6 +1182,15 @@ func (c *revidCameraClient) publishEventIfStatus(ctx *broadcastContext, event ev
11311182
}
11321183
}
11331184

1185+
func (c *revidCameraClient) error(ctx *broadcastContext) (error, error) {
1186+
controllerMACHex := (&model.Device{Mac: ctx.cfg.ControllerMAC}).Hex()
1187+
devErr, err := model.GetVariable(context.Background(), ctx.store, ctx.cfg.SKey, controllerMACHex+".error")
1188+
if err != nil {
1189+
return nil, fmt.Errorf("could not get controller error variable: %w", err)
1190+
}
1191+
return ControllerError(devErr.Value), nil
1192+
}
1193+
11341194
func (sm *hardwareStateMachine) saveHardwareStateToConfig() error {
11351195
sm.log("saving hardware state to config: %v", hardwareStateToString(sm.currentState))
11361196
hardwareState := hardwareStateToString(sm.currentState)

cmd/oceantv/broadcast_machine_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,60 @@ func TestHardwareVoltageAndFaultHandling(t *testing.T) {
17631763
expectedLogs: []string{},
17641764
expectedNotify: map[int64]map[notify.Kind][]string{},
17651765
},
1766+
1767+
{
1768+
desc: "direct broadcast; start with low voltage alarm error, then enter recovery",
1769+
cfg: func(c *BroadcastConfig) {
1770+
c.Enabled = true
1771+
c.SKey = testSiteKey
1772+
c.Start = time.Now().Add(-1 * time.Hour)
1773+
c.End = time.Now().Add(1 * time.Hour)
1774+
c.HardwareState = "hardwareOff"
1775+
c.ControllerMAC = 1
1776+
},
1777+
initialBroadcastState: &directIdle{},
1778+
finalBroadcastState: &directStarting{},
1779+
finalHardwareState: &hardwareRecoveringVoltage{},
1780+
hardwareMan: newDummyHardwareManager(withLowVoltage(), withHardwareError(LowVoltageAlarm)),
1781+
newBroadcastMan: func(t *testing.T, c *BroadcastConfig) BroadcastManager {
1782+
return newDummyManager(t, c)
1783+
},
1784+
expectedEvents: []event{timeEvent{}, startEvent{}, hardwareStartRequestEvent{}, lowVoltageEvent{}},
1785+
expectedLogs: []string{},
1786+
expectedNotify: map[int64]map[notify.Kind][]string{},
1787+
},
1788+
1789+
{
1790+
desc: "direct broadcast; low voltage alarm error, successful voltage recovery",
1791+
cfg: func(c *BroadcastConfig) {
1792+
c.Enabled = true
1793+
c.SKey = testSiteKey
1794+
c.Start = time.Now().Add(-1 * time.Hour)
1795+
c.End = time.Now().Add(1 * time.Hour)
1796+
c.HardwareState = "hardwareOff"
1797+
c.ControllerMAC = 1
1798+
},
1799+
initialBroadcastState: &directIdle{},
1800+
finalBroadcastState: &directStarting{},
1801+
finalHardwareState: &hardwareStarting{},
1802+
hardwareMan: newDummyHardwareManager(withLowVoltage(), withHardwareError(LowVoltageAlarm)),
1803+
newBroadcastMan: func(t *testing.T, c *BroadcastConfig) BroadcastManager {
1804+
return newDummyManager(t, c)
1805+
},
1806+
requiredTicks: 60,
1807+
expectedEvents: append(
1808+
append(
1809+
[]event{
1810+
timeEvent{},
1811+
startEvent{},
1812+
hardwareStartRequestEvent{},
1813+
lowVoltageEvent{},
1814+
}, timeEvents(49)...),
1815+
[]event{voltageRecoveredEvent{}}...,
1816+
),
1817+
expectedLogs: []string{},
1818+
expectedNotify: map[int64]map[notify.Kind][]string{},
1819+
},
17661820
}
17671821

17681822
for _, tt := range tests {

cmd/oceantv/broadcast_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ type dummyHardwareManager struct {
312312
controllerMAC string
313313
cameraMAC string
314314
latestRequest request
315+
hwErr error
315316
}
316317

317318
func withHardwareFault() func(*dummyHardwareManager) {
@@ -326,6 +327,12 @@ func withLowVoltage() func(*dummyHardwareManager) {
326327
}
327328
}
328329

330+
func withHardwareError(err error) func(*dummyHardwareManager) {
331+
return func(h *dummyHardwareManager) {
332+
h.hwErr = err
333+
}
334+
}
335+
329336
func withMACSanitisation() func(*dummyHardwareManager) {
330337
return func(h *dummyHardwareManager) {
331338
h.checkMAC = true
@@ -456,6 +463,12 @@ func (h *dummyHardwareManager) publishEventIfStatus(ctx *broadcastContext, event
456463
publish(event)
457464
}
458465
}
466+
func (h *dummyHardwareManager) error(ctx *broadcastContext) (error, error) {
467+
if h.volts > h.alarmVolts {
468+
return None, nil
469+
}
470+
return h.hwErr, nil
471+
}
459472

460473
// mockNotifier to implement Notifier interface.
461474
type mockNotifier struct {

cmd/oceantv/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import (
4747

4848
const (
4949
projectID = "oceantv"
50-
version = "v0.10.5"
50+
version = "v0.11.0"
5151
projectURL = "https://oceantv.appspot.com"
5252
cronServiceAccount = "oceancron@appspot.gserviceaccount.com"
5353
locationID = "Australia/Adelaide" // TODO: Use site location.

0 commit comments

Comments
 (0)