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}}

{{end}} diff --git a/web/match_play.go b/web/match_play.go index 3794f51..eb43e31 100644 --- a/web/match_play.go +++ b/web/match_play.go @@ -72,14 +72,15 @@ func (web *Web) matchPlayHandler(w http.ResponseWriter, r *http.Request) { isReplay := matchResult != nil data := struct { *model.EventSettings - PlcIsEnabled bool - MatchesByType map[string]MatchPlayList - CurrentMatchType string - Match *model.Match - AllowSubstitution bool - IsReplay bool + PlcIsEnabled bool + MatchesByType map[string]MatchPlayList + CurrentMatchType string + Match *model.Match + AllowSubstitution bool + IsReplay bool + PlcArmorBlockStatuses map[string]bool }{web.arena.EventSettings, web.arena.Plc.IsEnabled(), matchesByType, currentMatchType, web.arena.CurrentMatch, - web.arena.CurrentMatch.ShouldAllowSubstitution(), isReplay} + web.arena.CurrentMatch.ShouldAllowSubstitution(), isReplay, web.arena.Plc.GetArmorBlockStatuses()} err = template.ExecuteTemplate(w, "base", data) if err != nil { handleWebErr(w, err)