Skip to content

Commit

Permalink
Merge pull request #9 from FRCTeam1987/vivid-radio
Browse files Browse the repository at this point in the history
Updates for Vivid Radio
  • Loading branch information
kenschenke authored Oct 31, 2024
2 parents 2374fe0 + 7aa774f commit ad5157d
Show file tree
Hide file tree
Showing 41 changed files with 884 additions and 846 deletions.
181 changes: 99 additions & 82 deletions field/arena.go

Large diffs are not rendered by default.

60 changes: 25 additions & 35 deletions field/arena_notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/FRCTeam1987/crimson-arena/bracket"
"github.com/FRCTeam1987/crimson-arena/game"
"github.com/FRCTeam1987/crimson-arena/model"
"github.com/FRCTeam1987/crimson-arena/network"
"github.com/FRCTeam1987/crimson-arena/websocket"
"strconv"
)
Expand Down Expand Up @@ -66,57 +65,48 @@ func (arena *Arena) configureNotifiers() {
arena.SCCNotifier = websocket.NewNotifier("sccstatus", arena.generateSCCStatusMessage)
}

func (arena *Arena) generateAllianceSelectionMessage() interface{} {
func (arena *Arena) generateAllianceSelectionMessage() any {
return &arena.AllianceSelectionAlliances
}

func (arena *Arena) generateAllianceStationDisplayModeMessage() interface{} {
func (arena *Arena) generateAllianceStationDisplayModeMessage() any {
return arena.AllianceStationDisplayMode
}

func (arena *Arena) generateArenaStatusMessage() interface{} {
// Convert AP team wifi network status array to a map by station for ease of client use.
teamWifiStatuses := make(map[string]network.TeamWifiStatus)
for i, station := range []string{"R1", "R2", "R3", "B1", "B2", "B3"} {
if arena.EventSettings.Ap2TeamChannel == 0 || i < 3 {
teamWifiStatuses[station] = arena.accessPoint.TeamWifiStatuses[i]
} else {
teamWifiStatuses[station] = arena.accessPoint2.TeamWifiStatuses[i]
}
}

startMatchErr := arena.checkCanStartMatch()
startMatchErrString := ""
if startMatchErr != nil {
startMatchErrString = startMatchErr.Error()
}
func (arena *Arena) generateArenaStatusMessage() any {
return &struct {
MatchId int
AllianceStations map[string]*AllianceStation
TeamWifiStatuses map[string]network.TeamWifiStatus
MatchState
CanStartMatch bool
CanStartMatchReason string
AccessPointStatus string
SwitchStart string
PlcIsHealthy bool
FieldEstop bool
PlcArmorBlockStatuses map[string]bool
ScoringSccConnected bool
RedSccConnected bool
BlueSccConnected bool
}{arena.CurrentMatch.Id, arena.AllianceStations, teamWifiStatuses, arena.MatchState,
startMatchErr == nil, startMatchErrString,
arena.Plc.IsHealthy, arena.Plc.GetFieldEstop(),
}{
arena.CurrentMatch.Id,
arena.AllianceStations,
arena.MatchState,
arena.checkCanStartMatch() == nil,
arena.accessPoint.Status,
arena.networkSwitch.Status,
arena.Plc.IsHealthy,
arena.Plc.GetFieldEstop(),
arena.Plc.GetArmorBlockStatuses(),
arena.Scc.IsSccConnected("scoring"),
arena.Scc.IsSccConnected("red"),
arena.Scc.IsSccConnected("blue")}
}

func (arena *Arena) generateAudienceDisplayModeMessage() interface{} {
func (arena *Arena) generateAudienceDisplayModeMessage() any {
return arena.AudienceDisplayMode
}

func (arena *Arena) generateDisplayConfigurationMessage() interface{} {
func (arena *Arena) generateDisplayConfigurationMessage() any {
// Notify() for this notifier must always called from a method that has a lock on the display mutex.
// Make a copy of the map to avoid potential data races; otherwise the same map would get iterated through as it is
// serialized to JSON, outside the mutex lock.
Expand All @@ -127,18 +117,18 @@ func (arena *Arena) generateDisplayConfigurationMessage() interface{} {
return displaysCopy
}

func (arena *Arena) generateEventStatusMessage() interface{} {
func (arena *Arena) generateEventStatusMessage() any {
return arena.EventStatus
}

func (arena *Arena) generateLowerThirdMessage() interface{} {
func (arena *Arena) generateLowerThirdMessage() any {
return &struct {
LowerThird *model.LowerThird
ShowLowerThird bool
}{arena.LowerThird, arena.ShowLowerThird}
}

func (arena *Arena) generateMatchLoadMessage() interface{} {
func (arena *Arena) generateMatchLoadMessage() any {
teams := make(map[string]*model.Team)
for station, allianceStation := range arena.AllianceStations {
teams[station] = allianceStation.Team
Expand Down Expand Up @@ -187,15 +177,15 @@ func (arena *Arena) generateMatchLoadMessage() interface{} {
}
}

func (arena *Arena) generateMatchTimeMessage() interface{} {
func (arena *Arena) generateMatchTimeMessage() any {
return MatchTimeMessage{arena.MatchState, int(arena.MatchTimeSec())}
}

func (arena *Arena) generateMatchTimingMessage() interface{} {
func (arena *Arena) generateMatchTimingMessage() any {
return &game.MatchTiming
}

func (arena *Arena) generateRealtimeScoreMessage() interface{} {
func (arena *Arena) generateRealtimeScoreMessage() any {
fields := struct {
Red *audienceAllianceScoreFields
Blue *audienceAllianceScoreFields
Expand All @@ -207,11 +197,11 @@ func (arena *Arena) generateRealtimeScoreMessage() interface{} {
return &fields
}

func (arena *Arena) generateSCCStatusMessage() interface{} {
func (arena *Arena) generateSCCStatusMessage() any {
return arena.Scc.GenerateNotifierStatus()
}

func (arena *Arena) generateScorePostedMessage() interface{} {
func (arena *Arena) generateScorePostedMessage() any {
// For elimination matches, summarize the state of the series.
var seriesStatus, seriesLeader string
var matchup *bracket.Matchup
Expand Down Expand Up @@ -244,7 +234,7 @@ func (arena *Arena) generateScorePostedMessage() interface{} {
}
}

func (arena *Arena) generateFieldLightsMessage() interface{} {
func (arena *Arena) generateFieldLightsMessage() any {
return &struct {
Lights string
}{arena.FieldLights.GetCurrentStateAsString()}
Expand Down
50 changes: 25 additions & 25 deletions field/arena_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestArenaCheckCanStartMatch(t *testing.T) {
// Check robot state constraints.
err := arena.checkCanStartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match until all robots are connected or bypassed")
assert.Contains(t, err.Error(), "cannot start match until all robots are connected or bypassed")
}
arena.AllianceStations["R1"].Bypass = true
arena.AllianceStations["R2"].Bypass = true
Expand All @@ -68,7 +68,7 @@ func TestArenaCheckCanStartMatch(t *testing.T) {
arena.AllianceStations["B2"].Bypass = true
err = arena.checkCanStartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match until all robots are connected or bypassed")
assert.Contains(t, err.Error(), "cannot start match until all robots are connected or bypassed")
}
arena.AllianceStations["B3"].Bypass = true
assert.Nil(t, arena.checkCanStartMatch())
Expand All @@ -77,7 +77,7 @@ func TestArenaCheckCanStartMatch(t *testing.T) {
arena.Plc.SetAddress("1.2.3.4")
err = arena.checkCanStartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while PLC is not healthy")
assert.Contains(t, err.Error(), "cannot start match while PLC is not healthy")
}
arena.Plc.SetAddress("")
assert.Nil(t, arena.checkCanStartMatch())
Expand All @@ -102,7 +102,7 @@ func TestArenaMatchFlow(t *testing.T) {
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-10 * time.Millisecond)
arena.Update()
assert.Equal(t, lastPacketCount, arena.AllianceStations["B3"].DsConn.packetCount)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, lastPacketCount+1, arena.AllianceStations["B3"].DsConn.packetCount)

Expand Down Expand Up @@ -155,25 +155,25 @@ func TestArenaMatchFlow(t *testing.T) {

// Check e-stop and bypass.
arena.AllianceStations["B3"].Estop = true
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, TeleopPeriod, arena.MatchState)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Auto)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Enabled)
arena.AllianceStations["B3"].Bypass = true
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, TeleopPeriod, arena.MatchState)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Auto)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Enabled)
arena.AllianceStations["B3"].Estop = false
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, TeleopPeriod, arena.MatchState)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Auto)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Enabled)
arena.AllianceStations["B3"].Bypass = false
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, TeleopPeriod, arena.MatchState)
assert.Equal(t, false, arena.AllianceStations["B3"].DsConn.Auto)
Expand All @@ -194,7 +194,7 @@ func TestArenaMatchFlow(t *testing.T) {

arena.AllianceStations["R1"].Bypass = true
arena.ResetMatch()
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-300 * time.Millisecond)
arena.lastDsPacketTime = arena.lastDsPacketTime.Add(-550 * time.Millisecond)
arena.Update()
assert.Equal(t, PreMatch, arena.MatchState)
assert.Equal(t, true, arena.AllianceStations["B3"].DsConn.Auto)
Expand All @@ -216,73 +216,73 @@ func TestArenaStateEnforcement(t *testing.T) {
assert.Nil(t, err)
err = arena.AbortMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot abort match when")
assert.Contains(t, err.Error(), "cannot abort match when")
}
err = arena.StartMatch()
assert.Nil(t, err)
err = arena.LoadMatch(new(model.Match))
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot load match while")
assert.Contains(t, err.Error(), "cannot load match while")
}
err = arena.StartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while")
assert.Contains(t, err.Error(), "cannot start match while")
}
err = arena.ResetMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot reset match while")
assert.Contains(t, err.Error(), "cannot reset match while")
}
arena.MatchState = AutoPeriod
err = arena.LoadMatch(new(model.Match))
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot load match while")
assert.Contains(t, err.Error(), "cannot load match while")
}
err = arena.StartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while")
assert.Contains(t, err.Error(), "cannot start match while")
}
err = arena.ResetMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot reset match while")
assert.Contains(t, err.Error(), "cannot reset match while")
}
arena.MatchState = PausePeriod
err = arena.LoadMatch(new(model.Match))
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot load match while")
assert.Contains(t, err.Error(), "cannot load match while")
}
err = arena.StartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while")
assert.Contains(t, err.Error(), "cannot start match while")
}
err = arena.ResetMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot reset match while")
assert.Contains(t, err.Error(), "cannot reset match while")
}
arena.MatchState = TeleopPeriod
err = arena.LoadMatch(new(model.Match))
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot load match while")
assert.Contains(t, err.Error(), "cannot load match while")
}
err = arena.StartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while")
assert.Contains(t, err.Error(), "cannot start match while")
}
err = arena.ResetMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot reset match while")
assert.Contains(t, err.Error(), "cannot reset match while")
}
arena.MatchState = PostMatch
err = arena.LoadMatch(new(model.Match))
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot load match while")
assert.Contains(t, err.Error(), "cannot load match while")
}
err = arena.StartMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot start match while")
assert.Contains(t, err.Error(), "cannot start match while")
}
err = arena.AbortMatch()
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Cannot abort match when")
assert.Contains(t, err.Error(), "cannot abort match when")
}

err = arena.ResetMatch()
Expand Down
2 changes: 1 addition & 1 deletion field/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (display *Display) ToUrl() string {
return builder.String()
}

func (display *Display) generateDisplayConfigurationMessage() interface{} {
func (display *Display) generateDisplayConfigurationMessage() any {
return display.ToUrl()
}

Expand Down
17 changes: 7 additions & 10 deletions field/driver_station_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type DriverStationConnection struct {
Estop bool
DsLinked bool
RadioLinked bool
RioLinked bool
RobotLinked bool
BatteryVoltage float64
DsRobotTripTimeMs int
Expand Down Expand Up @@ -99,6 +100,7 @@ func (arena *Arena) listenForDsUdpPackets() {
dsConn.DsLinked = true
dsConn.lastPacketTime = time.Now()

dsConn.RioLinked = data[3]&0x08 != 0
dsConn.RadioLinked = data[3]&0x10 != 0
dsConn.RobotLinked = data[3]&0x20 != 0
if dsConn.RobotLinked {
Expand All @@ -120,6 +122,7 @@ func (dsConn *DriverStationConnection) update(arena *Arena) error {

if time.Since(dsConn.lastPacketTime).Seconds() > driverStationUdpLinkTimeoutSec {
dsConn.DsLinked = false
dsConn.RioLinked = false
dsConn.RadioLinked = false
dsConn.RobotLinked = false
dsConn.BatteryVoltage = 0
Expand All @@ -142,11 +145,11 @@ func (dsConn *DriverStationConnection) close() {
}

// Called at the start of the match to allow for driver station initialization.
func (dsConn *DriverStationConnection) signalMatchStart(match *model.Match) error {
func (dsConn *DriverStationConnection) signalMatchStart(match *model.Match, wifiStatus *network.TeamWifiStatus) error {
// Zero out missed packet count and begin logging.
dsConn.missedPacketOffset = dsConn.MissedPacketCount
var err error
dsConn.log, err = NewTeamMatchLog(dsConn.TeamId, match)
dsConn.log, err = NewTeamMatchLog(dsConn.TeamId, match, wifiStatus)
return err
}

Expand Down Expand Up @@ -223,15 +226,9 @@ func (dsConn *DriverStationConnection) encodeControlPacket(arena *Arena) [22]byt
// Remaining number of seconds in match.
var matchSecondsRemaining int
switch arena.MatchState {
case PreMatch:
fallthrough
case TimeoutActive:
fallthrough
case PostTimeout:
case PreMatch, TimeoutActive, PostTimeout:
matchSecondsRemaining = game.MatchTiming.AutoDurationSec
case StartMatch:
fallthrough
case AutoPeriod:
case StartMatch, AutoPeriod:
matchSecondsRemaining = game.MatchTiming.AutoDurationSec - int(arena.MatchTimeSec())
case PausePeriod:
matchSecondsRemaining = game.MatchTiming.TeleopDurationSec
Expand Down
Loading

0 comments on commit ad5157d

Please sign in to comment.