mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Decode ArmorBlock status register from PLC and surface individual statuses on Match Play page.
This commit is contained in:
@@ -662,6 +662,11 @@ func (arena *Arena) checkCanStartMatch() error {
|
|||||||
if arena.Plc.GetFieldEstop() {
|
if arena.Plc.GetFieldEstop() {
|
||||||
return fmt.Errorf("Cannot start match while field emergency stop is active.")
|
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
|
return nil
|
||||||
|
|||||||
@@ -89,11 +89,13 @@ func (arena *Arena) generateArenaStatusMessage() interface{} {
|
|||||||
AllianceStations map[string]*AllianceStation
|
AllianceStations map[string]*AllianceStation
|
||||||
TeamWifiStatuses map[string]network.TeamWifiStatus
|
TeamWifiStatuses map[string]network.TeamWifiStatus
|
||||||
MatchState
|
MatchState
|
||||||
CanStartMatch bool
|
CanStartMatch bool
|
||||||
PlcIsHealthy bool
|
PlcIsHealthy bool
|
||||||
FieldEstop bool
|
FieldEstop bool
|
||||||
|
PlcArmorBlockStatuses map[string]bool
|
||||||
}{arena.CurrentMatch.Id, arena.AllianceStations, teamWifiStatuses, arena.MatchState,
|
}{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{} {
|
func (arena *Arena) generateAudienceDisplayModeMessage() interface{} {
|
||||||
|
|||||||
27
plc/armorblock_string.go
Normal file
27
plc/armorblock_string.go
Normal 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]]
|
||||||
|
}
|
||||||
21
plc/plc.go
21
plc/plc.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/Team254/cheesy-arena/websocket"
|
"github.com/Team254/cheesy-arena/websocket"
|
||||||
"github.com/goburrow/modbus"
|
"github.com/goburrow/modbus"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,6 +115,17 @@ const (
|
|||||||
coilCount
|
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) {
|
func (plc *Plc) SetAddress(address string) {
|
||||||
plc.address = address
|
plc.address = address
|
||||||
plc.resetConnection()
|
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).
|
// Returns the state of the field emergency stop button (true if e-stop is active).
|
||||||
func (plc *Plc) GetFieldEstop() bool {
|
func (plc *Plc) GetFieldEstop() bool {
|
||||||
return plc.IsEnabled() && !plc.inputs[fieldEstop]
|
return plc.IsEnabled() && !plc.inputs[fieldEstop]
|
||||||
|
|||||||
@@ -34,3 +34,32 @@ func TestBoolToByte(t *testing.T) {
|
|||||||
assert.Equal(t, bools, byteToBool(bytes, len(bools)))
|
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())
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ var handleArenaStatus = function(data) {
|
|||||||
$("#plcStatus").attr("data-ready", false);
|
$("#plcStatus").attr("data-ready", false);
|
||||||
}
|
}
|
||||||
$("#fieldEstop").attr("data-ready", !data.FieldEstop);
|
$("#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.
|
// Handles a websocket message to update the match time countdown.
|
||||||
|
|||||||
@@ -123,6 +123,9 @@
|
|||||||
<p>
|
<p>
|
||||||
<span class="label label-scoring" id="plcStatus"></span><br />
|
<span class="label label-scoring" id="plcStatus"></span><br />
|
||||||
<span class="label label-scoring" id="fieldEstop">E-Stop</span>
|
<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>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,14 +72,15 @@ func (web *Web) matchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
isReplay := matchResult != nil
|
isReplay := matchResult != nil
|
||||||
data := struct {
|
data := struct {
|
||||||
*model.EventSettings
|
*model.EventSettings
|
||||||
PlcIsEnabled bool
|
PlcIsEnabled bool
|
||||||
MatchesByType map[string]MatchPlayList
|
MatchesByType map[string]MatchPlayList
|
||||||
CurrentMatchType string
|
CurrentMatchType string
|
||||||
Match *model.Match
|
Match *model.Match
|
||||||
AllowSubstitution bool
|
AllowSubstitution bool
|
||||||
IsReplay bool
|
IsReplay bool
|
||||||
|
PlcArmorBlockStatuses map[string]bool
|
||||||
}{web.arena.EventSettings, web.arena.Plc.IsEnabled(), matchesByType, currentMatchType, web.arena.CurrentMatch,
|
}{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)
|
err = template.ExecuteTemplate(w, "base", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleWebErr(w, err)
|
handleWebErr(w, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user