diff --git a/field/arena.go b/field/arena.go index b5fc54a..1cfbf48 100644 --- a/field/arena.go +++ b/field/arena.go @@ -662,6 +662,11 @@ func (arena *Arena) checkCanStartMatch() error { if arena.Plc.GetFieldEstop() { return fmt.Errorf("Cannot start match while field emergency stop is active.") } + for name, status := range arena.Plc.GetArmorBlockStatuses() { + if !status { + return fmt.Errorf("Cannot start match while PLC ArmorBlock '%s' is not connected.", name) + } + } } return nil diff --git a/field/arena_notifiers.go b/field/arena_notifiers.go index f98158f..d586d20 100644 --- a/field/arena_notifiers.go +++ b/field/arena_notifiers.go @@ -89,11 +89,13 @@ func (arena *Arena) generateArenaStatusMessage() interface{} { AllianceStations map[string]*AllianceStation TeamWifiStatuses map[string]network.TeamWifiStatus MatchState - CanStartMatch bool - PlcIsHealthy bool - FieldEstop bool + CanStartMatch bool + PlcIsHealthy bool + FieldEstop bool + PlcArmorBlockStatuses map[string]bool }{arena.CurrentMatch.Id, arena.AllianceStations, teamWifiStatuses, arena.MatchState, - arena.checkCanStartMatch() == nil, arena.Plc.IsHealthy, arena.Plc.GetFieldEstop()} + arena.checkCanStartMatch() == nil, arena.Plc.IsHealthy, arena.Plc.GetFieldEstop(), + arena.Plc.GetArmorBlockStatuses()} } func (arena *Arena) generateAudienceDisplayModeMessage() interface{} { diff --git a/plc/armorblock_string.go b/plc/armorblock_string.go new file mode 100644 index 0000000..4efa98d --- /dev/null +++ b/plc/armorblock_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type=armorBlock"; DO NOT EDIT. + +package plc + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[redDs-0] + _ = x[blueDs-1] + _ = x[shieldGenerator-2] + _ = x[controlPanel-3] + _ = x[armorBlockCount-4] +} + +const _armorBlock_name = "redDsblueDsshieldGeneratorcontrolPanelarmorBlockCount" + +var _armorBlock_index = [...]uint8{0, 5, 11, 26, 38, 53} + +func (i armorBlock) String() string { + if i < 0 || i >= armorBlock(len(_armorBlock_index)-1) { + return "armorBlock(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _armorBlock_name[_armorBlock_index[i]:_armorBlock_index[i+1]] +} diff --git a/plc/plc.go b/plc/plc.go index fa9ad71..ec77361 100644 --- a/plc/plc.go +++ b/plc/plc.go @@ -11,6 +11,7 @@ import ( "github.com/Team254/cheesy-arena/websocket" "github.com/goburrow/modbus" "log" + "strings" "time" ) @@ -114,6 +115,17 @@ const ( coilCount ) +// Bitmask for decoding fieldIoConnection into individual ArmorBlock connection statuses. +type armorBlock int + +const ( + redDs armorBlock = iota + blueDs + shieldGenerator + controlPanel + armorBlockCount +) + func (plc *Plc) SetAddress(address string) { plc.address = address plc.resetConnection() @@ -177,6 +189,15 @@ func (plc *Plc) Run() { } } +// Returns a map of ArmorBlocks I/O module names to whether they are connected properly. +func (plc *Plc) GetArmorBlockStatuses() map[string]bool { + statuses := make(map[string]bool, armorBlockCount) + for i := 0; i < int(armorBlockCount); i++ { + statuses[strings.Title(armorBlock(i).String())] = plc.registers[fieldIoConnection]&(1< 0 + } + return statuses +} + // Returns the state of the field emergency stop button (true if e-stop is active). func (plc *Plc) GetFieldEstop() bool { return plc.IsEnabled() && !plc.inputs[fieldEstop] diff --git a/plc/plc_test.go b/plc/plc_test.go index 992ca6d..56d96c9 100644 --- a/plc/plc_test.go +++ b/plc/plc_test.go @@ -34,3 +34,32 @@ func TestBoolToByte(t *testing.T) { assert.Equal(t, bools, byteToBool(bytes, len(bools))) } } + +func TestGetArmorBlockStatuses(t *testing.T) { + var plc Plc + + plc.registers[fieldIoConnection] = 0 + assert.Equal(t, map[string]bool{"RedDs": false, "BlueDs": false, "ShieldGenerator": false, "ControlPanel": false}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 1 + assert.Equal(t, map[string]bool{"RedDs": true, "BlueDs": false, "ShieldGenerator": false, "ControlPanel": false}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 2 + assert.Equal(t, map[string]bool{"RedDs": false, "BlueDs": true, "ShieldGenerator": false, "ControlPanel": false}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 4 + assert.Equal(t, map[string]bool{"RedDs": false, "BlueDs": false, "ShieldGenerator": true, "ControlPanel": false}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 8 + assert.Equal(t, map[string]bool{"RedDs": false, "BlueDs": false, "ShieldGenerator": false, "ControlPanel": true}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 5 + assert.Equal(t, map[string]bool{"RedDs": true, "BlueDs": false, "ShieldGenerator": true, "ControlPanel": false}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 10 + assert.Equal(t, map[string]bool{"RedDs": false, "BlueDs": true, "ShieldGenerator": false, "ControlPanel": true}, + plc.GetArmorBlockStatuses()) + plc.registers[fieldIoConnection] = 15 + assert.Equal(t, map[string]bool{"RedDs": true, "BlueDs": true, "ShieldGenerator": true, "ControlPanel": true}, + plc.GetArmorBlockStatuses()) +} diff --git a/static/js/match_play.js b/static/js/match_play.js index ff4f36e..b4d874e 100644 --- a/static/js/match_play.js +++ b/static/js/match_play.js @@ -187,6 +187,9 @@ var handleArenaStatus = function(data) { $("#plcStatus").attr("data-ready", false); } $("#fieldEstop").attr("data-ready", !data.FieldEstop); + $.each(data.PlcArmorBlockStatuses, function(name, status) { + $("#plc" + name + "Status").attr("data-ready", status); + }); }; // Handles a websocket message to update the match time countdown. diff --git a/templates/match_play.html b/templates/match_play.html index f0d40a3..4288387 100644 --- a/templates/match_play.html +++ b/templates/match_play.html @@ -123,6 +123,9 @@
E-Stop
+ {{range $name, $status := .PlcArmorBlockStatuses}}
+
{{$name}}
+ {{end}}