Decode ArmorBlock status register from PLC and surface individual statuses on Match Play page.

This commit is contained in:
Patrick Fairbank
2020-04-04 23:53:25 -07:00
parent 3739cd8690
commit 4e74a7a4cd
8 changed files with 102 additions and 11 deletions

View File

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

View File

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

27
plc/armorblock_string.go Normal file
View File

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

View File

@@ -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<<i) > 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]

View File

@@ -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())
}

View File

@@ -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.

View File

@@ -123,6 +123,9 @@
<p>
<span class="label label-scoring" id="plcStatus"></span><br />
<span class="label label-scoring" id="fieldEstop">E-Stop</span>
{{range $name, $status := .PlcArmorBlockStatuses}}
<br /><span class="label label-scoring" id="plc{{$name}}Status">{{$name}}</span>
{{end}}
</p>
{{end}}
</div>

View File

@@ -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)