mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Implement 2015 driver station protocol.
This commit is contained in:
32
arena.go
32
arena.go
@@ -160,10 +160,7 @@ func (arena *Arena) AssignTeam(teamId int, station string) error {
|
||||
return nil
|
||||
}
|
||||
if dsConn != nil {
|
||||
err := dsConn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dsConn.Close()
|
||||
arena.AllianceStations[station].team = nil
|
||||
arena.AllianceStations[station].DsConn = nil
|
||||
}
|
||||
@@ -183,10 +180,6 @@ func (arena *Arena) AssignTeam(teamId int, station string) error {
|
||||
}
|
||||
|
||||
arena.AllianceStations[station].team = team
|
||||
arena.AllianceStations[station].DsConn, err = NewDriverStationConnection(team.Id, station)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -327,7 +320,7 @@ func (arena *Arena) CheckCanStartMatch() error {
|
||||
return fmt.Errorf("Cannot start match while an emergency stop is active.")
|
||||
}
|
||||
if !allianceStation.Bypass {
|
||||
if allianceStation.DsConn == nil || !allianceStation.DsConn.DriverStationStatus.RobotLinked {
|
||||
if allianceStation.DsConn == nil || !allianceStation.DsConn.RobotLinked {
|
||||
return fmt.Errorf("Cannot start match until all robots are connected or bypassed.")
|
||||
}
|
||||
}
|
||||
@@ -514,10 +507,11 @@ func (arena *Arena) Run() {
|
||||
|
||||
func (arena *Arena) sendDsPacket(auto bool, enabled bool) {
|
||||
for _, allianceStation := range arena.AllianceStations {
|
||||
if allianceStation.DsConn != nil {
|
||||
allianceStation.DsConn.Auto = auto
|
||||
allianceStation.DsConn.Enabled = enabled && !allianceStation.EmergencyStop && !allianceStation.Bypass
|
||||
err := allianceStation.DsConn.Update()
|
||||
dsConn := allianceStation.DsConn
|
||||
if dsConn != nil {
|
||||
dsConn.Auto = auto
|
||||
dsConn.Enabled = enabled && !allianceStation.EmergencyStop && !allianceStation.Bypass
|
||||
err := dsConn.Update()
|
||||
if err != nil {
|
||||
log.Printf("Unable to send driver station packet for team %d.", allianceStation.team.Id)
|
||||
}
|
||||
@@ -564,3 +558,15 @@ func (arena *Arena) handleLighting() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the alliance station identifier for the given team, or the empty string if the team is not present
|
||||
// in the current match.
|
||||
func (arena *Arena) getAssignedAllianceStation(teamId int) string {
|
||||
for station, allianceStation := range arena.AllianceStations {
|
||||
if allianceStation.team != nil && allianceStation.team.Id == teamId {
|
||||
return station
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -29,24 +29,21 @@ func TestAssignTeam(t *testing.T) {
|
||||
err = mainArena.AssignTeam(254, "B1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, team, *mainArena.AllianceStations["B1"].team)
|
||||
dsConn := mainArena.AllianceStations["B1"].DsConn
|
||||
assert.Equal(t, 254, dsConn.TeamId)
|
||||
assert.Equal(t, "B1", dsConn.AllianceStation)
|
||||
dummyDs := &DriverStationConnection{TeamId: 254}
|
||||
mainArena.AllianceStations["B1"].DsConn = dummyDs
|
||||
|
||||
// Nothing should happen if the same team is assigned to the same station.
|
||||
err = mainArena.AssignTeam(254, "B1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, team, *mainArena.AllianceStations["B1"].team)
|
||||
dsConn2 := mainArena.AllianceStations["B1"].DsConn
|
||||
assert.Equal(t, dsConn, dsConn2) // Pointer equality
|
||||
assert.NotNil(t, mainArena.AllianceStations["B1"])
|
||||
assert.Equal(t, dummyDs, mainArena.AllianceStations["B1"].DsConn) // Pointer equality
|
||||
|
||||
// Test reassignment to another team.
|
||||
err = mainArena.AssignTeam(1114, "B1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, team, *mainArena.AllianceStations["B1"].team)
|
||||
assert.Equal(t, 1114, mainArena.AllianceStations["B1"].DsConn.TeamId)
|
||||
err = dsConn.conn.Close()
|
||||
assert.NotNil(t, err) // Connection should have already been closed.
|
||||
assert.Nil(t, mainArena.AllianceStations["B1"].DsConn)
|
||||
|
||||
// Check assigning zero as the team number.
|
||||
err = mainArena.AssignTeam(0, "R2")
|
||||
@@ -73,6 +70,8 @@ func TestArenaMatchFlow(t *testing.T) {
|
||||
mainArena.Setup()
|
||||
db.CreateTeam(&Team{Id: 254})
|
||||
err = mainArena.AssignTeam(254, "B3")
|
||||
dummyDs := &DriverStationConnection{TeamId: 254}
|
||||
mainArena.AllianceStations["B3"].DsConn = dummyDs
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check pre-match state and packet timing.
|
||||
@@ -94,7 +93,7 @@ func TestArenaMatchFlow(t *testing.T) {
|
||||
mainArena.AllianceStations["R3"].Bypass = true
|
||||
mainArena.AllianceStations["B1"].Bypass = true
|
||||
mainArena.AllianceStations["B2"].Bypass = true
|
||||
mainArena.AllianceStations["B3"].DsConn.DriverStationStatus.RobotLinked = true
|
||||
mainArena.AllianceStations["B3"].DsConn.RobotLinked = true
|
||||
err = mainArena.StartMatch()
|
||||
assert.Nil(t, err)
|
||||
mainArena.Update()
|
||||
@@ -309,8 +308,14 @@ func TestMatchStartRobotLinkEnforcement(t *testing.T) {
|
||||
|
||||
err = mainArena.LoadMatch(&match)
|
||||
assert.Nil(t, err)
|
||||
mainArena.AllianceStations["R1"].DsConn = &DriverStationConnection{TeamId: 101}
|
||||
mainArena.AllianceStations["R2"].DsConn = &DriverStationConnection{TeamId: 102}
|
||||
mainArena.AllianceStations["R3"].DsConn = &DriverStationConnection{TeamId: 103}
|
||||
mainArena.AllianceStations["B1"].DsConn = &DriverStationConnection{TeamId: 104}
|
||||
mainArena.AllianceStations["B2"].DsConn = &DriverStationConnection{TeamId: 105}
|
||||
mainArena.AllianceStations["B3"].DsConn = &DriverStationConnection{TeamId: 106}
|
||||
for _, station := range mainArena.AllianceStations {
|
||||
station.DsConn.DriverStationStatus.RobotLinked = true
|
||||
station.DsConn.RobotLinked = true
|
||||
}
|
||||
err = mainArena.StartMatch()
|
||||
assert.Nil(t, err)
|
||||
@@ -323,7 +328,7 @@ func TestMatchStartRobotLinkEnforcement(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "while an emergency stop is active")
|
||||
}
|
||||
mainArena.AllianceStations["R1"].EmergencyStop = false
|
||||
mainArena.AllianceStations["R1"].DsConn.DriverStationStatus.RobotLinked = false
|
||||
mainArena.AllianceStations["R1"].DsConn.RobotLinked = false
|
||||
err = mainArena.StartMatch()
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Contains(t, err.Error(), "until all robots are connected or bypassed")
|
||||
|
||||
@@ -95,14 +95,13 @@ func (monitor *BandwidthMonitor) updateStationBandwidth(station string, port int
|
||||
// No team assigned; just skip it.
|
||||
return
|
||||
}
|
||||
dsStatus := dsConn.DriverStationStatus
|
||||
secondsSinceLast := time.Now().Sub(monitor.lastBytesTime).Seconds()
|
||||
|
||||
toRobotBytesForPort := uint32(toRobotBytes[fmt.Sprintf("%s.%d", toRobotBytesOid, port)].(wapsnmp.Counter))
|
||||
lastToRobotBytesForPort := uint32(monitor.lastToRobotBytes[fmt.Sprintf("%s.%d", toRobotBytesOid, port)].(wapsnmp.Counter))
|
||||
dsStatus.MBpsToRobot = float64(toRobotBytesForPort-lastToRobotBytesForPort) / 1024 / 1024 / secondsSinceLast
|
||||
dsConn.MBpsToRobot = float64(toRobotBytesForPort-lastToRobotBytesForPort) / 1024 / 1024 / secondsSinceLast
|
||||
|
||||
fromRobotBytesForPort := uint32(fromRobotBytes[fmt.Sprintf("%s.%d", fromRobotBytesOid, port)].(wapsnmp.Counter))
|
||||
lastFromRobotBytesForPort := uint32(monitor.lastFromRobotBytes[fmt.Sprintf("%s.%d", fromRobotBytesOid, port)].(wapsnmp.Counter))
|
||||
dsStatus.MBpsFromRobot = float64(fromRobotBytesForPort-lastFromRobotBytesForPort) / 1024 / 1024 / secondsSinceLast
|
||||
dsConn.MBpsFromRobot = float64(fromRobotBytesForPort-lastFromRobotBytesForPort) / 1024 / 1024 / secondsSinceLast
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
|
||||
var catalystTelnetPort = 23
|
||||
|
||||
const eventServerAddress = "10.0.100.50"
|
||||
|
||||
var catalystMutex sync.Mutex
|
||||
|
||||
// Sets up wired networks for the given set of teams.
|
||||
@@ -42,7 +40,7 @@ func ConfigureTeamEthernet(red1, red2, red3, blue1, blue2, blue3 *Team) error {
|
||||
} else {
|
||||
addTeamVlansCommand += fmt.Sprintf("no access-list 1%d\naccess-list 1%d permit ip "+
|
||||
"10.%d.%d.0 0.0.0.255 host %s\ninterface Vlan%d\nip address 10.%d.%d.61 255.255.255.0\n", vlan,
|
||||
vlan, team.Id/100, team.Id%100, eventServerAddress, vlan, team.Id/100, team.Id%100)
|
||||
vlan, team.Id/100, team.Id%100, driverStationTcpListenAddress, vlan, team.Id/100, team.Id%100)
|
||||
}
|
||||
}
|
||||
replaceTeamVlan(red1, red1Vlan)
|
||||
|
||||
@@ -31,6 +31,6 @@ func TestConfigureCatalyst(t *testing.T) {
|
||||
mockTelnet(t, catalystTelnetPort, "interface Vlan15\nip address 10.2.54.61\n", &command)
|
||||
assert.Nil(t, ConfigureTeamEthernet(nil, &Team{Id: 1114}, nil, nil, &Team{Id: 254}, nil))
|
||||
assert.Equal(t, "password\nenable\npassword\nterminal length 0\nconfig terminal\nno access-list 112\n"+
|
||||
"access-list 112 permit ip 10.11.14.0 0.0.0.255 host 10.0.100.50\ninterface Vlan12\nip address "+
|
||||
"access-list 112 permit ip 10.11.14.0 0.0.0.255 host 10.0.100.5\ninterface Vlan12\nip address "+
|
||||
"10.11.14.61 255.255.255.0\nend\ncopy running-config startup-config\n\nexit\n", command)
|
||||
}
|
||||
|
||||
@@ -7,34 +7,17 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UDP port numbers that the Driver Station sends and receives on.
|
||||
const driverStationSendPort = 1120
|
||||
const driverStationReceivePort = 1160
|
||||
const driverStationProtocolVersion = "11191100"
|
||||
const driverStationLinkTimeoutMs = 500
|
||||
|
||||
type DriverStationStatus struct {
|
||||
TeamId int
|
||||
AllianceStation string
|
||||
DsLinked bool
|
||||
RobotLinked bool
|
||||
Auto bool
|
||||
Enabled bool
|
||||
EmergencyStop bool
|
||||
BatteryVoltage float64
|
||||
DsVersion string
|
||||
PacketCount int
|
||||
MissedPacketCount int
|
||||
DsRobotTripTimeMs int
|
||||
MBpsToRobot float64
|
||||
MBpsFromRobot float64
|
||||
}
|
||||
const (
|
||||
driverStationTcpListenPort = 1750
|
||||
driverStationUdpSendPort = 1120
|
||||
driverStationLinkTimeoutSec = 5
|
||||
maxTcpPacketBytes = 4096
|
||||
)
|
||||
|
||||
type DriverStationConnection struct {
|
||||
TeamId int
|
||||
@@ -42,24 +25,40 @@ type DriverStationConnection struct {
|
||||
Auto bool
|
||||
Enabled bool
|
||||
EmergencyStop bool
|
||||
DriverStationStatus *DriverStationStatus
|
||||
LastPacketTime time.Time
|
||||
LastRobotLinkedTime time.Time
|
||||
DsLinked bool
|
||||
RobotLinked bool
|
||||
BatteryVoltage float64
|
||||
DsRobotTripTimeMs int
|
||||
MissedPacketCount int
|
||||
MBpsToRobot float64
|
||||
MBpsFromRobot float64
|
||||
SecondsSinceLastRobotLink float64
|
||||
conn net.Conn
|
||||
lastPacketTime time.Time
|
||||
lastRobotLinkedTime time.Time
|
||||
packetCount int
|
||||
missedPacketOffset int
|
||||
tcpConn net.Conn
|
||||
udpConn net.Conn
|
||||
log *TeamMatchLog
|
||||
}
|
||||
|
||||
var allianceStationPositionMap = map[string]byte{"R1": 0, "R2": 1, "R3": 2, "B1": 3, "B2": 4, "B3": 5}
|
||||
var driverStationTcpListenAddress = "10.0.100.5" // The DS will try to connect to this address only.
|
||||
|
||||
// Opens a UDP connection for communicating to the driver station.
|
||||
func NewDriverStationConnection(teamId int, station string) (*DriverStationConnection, error) {
|
||||
conn, err := net.Dial("udp4", fmt.Sprintf("10.%d.%d.5:%d", teamId/100, teamId%100, driverStationSendPort))
|
||||
func NewDriverStationConnection(teamId int, allianceStation string, tcpConn net.Conn) (*DriverStationConnection, error) {
|
||||
ipAddress, _, err := net.SplitHostPort(tcpConn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DriverStationConnection{TeamId: teamId, AllianceStation: station,
|
||||
DriverStationStatus: new(DriverStationStatus), conn: conn}, nil
|
||||
log.Printf("Driver station for Team %d connected from %s\n", teamId, ipAddress)
|
||||
|
||||
udpConn, err := net.Dial("udp4", fmt.Sprintf("%s:%d", ipAddress, driverStationUdpSendPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DriverStationConnection{TeamId: teamId, AllianceStation: allianceStation, DsLinked: true,
|
||||
lastPacketTime: time.Now(), tcpConn: tcpConn, udpConn: udpConn}, nil
|
||||
}
|
||||
|
||||
// Sends a control packet to the Driver Station and checks for timeout conditions.
|
||||
@@ -69,112 +68,120 @@ func (dsConn *DriverStationConnection) Update() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if time.Since(dsConn.LastPacketTime).Seconds()*1000 > driverStationLinkTimeoutMs {
|
||||
dsConn.DriverStationStatus.DsLinked = false
|
||||
dsConn.DriverStationStatus.RobotLinked = false
|
||||
dsConn.DriverStationStatus.BatteryVoltage = 0
|
||||
dsConn.DriverStationStatus.MBpsToRobot = 0
|
||||
dsConn.DriverStationStatus.MBpsFromRobot = 0
|
||||
if time.Since(dsConn.lastPacketTime).Seconds() > driverStationLinkTimeoutSec {
|
||||
dsConn.DsLinked = false
|
||||
dsConn.RobotLinked = false
|
||||
dsConn.BatteryVoltage = 0
|
||||
dsConn.MBpsToRobot = 0
|
||||
dsConn.MBpsFromRobot = 0
|
||||
}
|
||||
dsConn.SecondsSinceLastRobotLink = time.Since(dsConn.lastRobotLinkedTime).Seconds()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dsConn *DriverStationConnection) Close() error {
|
||||
func (dsConn *DriverStationConnection) Close() {
|
||||
if dsConn.log != nil {
|
||||
dsConn.log.Close()
|
||||
}
|
||||
return dsConn.conn.Close()
|
||||
}
|
||||
|
||||
// Sets up a watch on the UDP port that Driver Stations send on.
|
||||
func DsPacketListener() (*net.UDPConn, error) {
|
||||
udpAddress, err := net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", driverStationReceivePort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if dsConn.udpConn != nil {
|
||||
dsConn.udpConn.Close()
|
||||
}
|
||||
listen, err := net.ListenUDP("udp4", udpAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listen, nil
|
||||
}
|
||||
|
||||
// Loops indefinitely to read packets and update connection status.
|
||||
func ListenForDsPackets(listener *net.UDPConn) {
|
||||
var data [50]byte
|
||||
for {
|
||||
listener.Read(data[:])
|
||||
dsStatus := decodeStatusPacket(data)
|
||||
|
||||
// Update the status and last packet times for this alliance/team in the global struct.
|
||||
dsConn := mainArena.AllianceStations[dsStatus.AllianceStation].DsConn
|
||||
if dsConn != nil && dsConn.TeamId == dsStatus.TeamId {
|
||||
dsConn.DriverStationStatus = dsStatus
|
||||
dsConn.LastPacketTime = time.Now()
|
||||
if dsStatus.RobotLinked {
|
||||
dsConn.LastRobotLinkedTime = time.Now()
|
||||
}
|
||||
dsConn.SecondsSinceLastRobotLink = time.Since(dsConn.LastRobotLinkedTime).Seconds()
|
||||
dsConn.DriverStationStatus.MissedPacketCount -= dsConn.missedPacketOffset
|
||||
|
||||
// Log the packet if the match is in progress.
|
||||
matchTimeSec := mainArena.MatchTimeSec()
|
||||
if matchTimeSec > 0 && dsConn.log != nil {
|
||||
dsConn.log.LogDsStatus(matchTimeSec, dsStatus)
|
||||
}
|
||||
}
|
||||
if dsConn.tcpConn != nil {
|
||||
dsConn.tcpConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Called at the start of the match to allow for driver station initialization.
|
||||
func (dsConn *DriverStationConnection) signalMatchStart(match *Match) error {
|
||||
// Zero out missed packet count and begin logging.
|
||||
dsConn.missedPacketOffset = dsConn.DriverStationStatus.MissedPacketCount
|
||||
dsConn.missedPacketOffset = dsConn.MissedPacketCount
|
||||
var err error
|
||||
dsConn.log, err = NewTeamMatchLog(dsConn.TeamId, match)
|
||||
return err
|
||||
}
|
||||
|
||||
// Serializes the control information into a packet.
|
||||
func (dsConn *DriverStationConnection) encodeControlPacket() [74]byte {
|
||||
var packet [74]byte
|
||||
func (dsConn *DriverStationConnection) encodeControlPacket() [22]byte {
|
||||
var packet [22]byte
|
||||
|
||||
// Packet number, stored big-endian in two bytes.
|
||||
packet[0] = byte((dsConn.packetCount >> 8) & 0xff)
|
||||
packet[1] = byte(dsConn.packetCount & 0xff)
|
||||
|
||||
// Robot status byte. 0x01=competition mode, 0x02=link, 0x04=check version, 0x08=request DS ID,
|
||||
// 0x10=autonomous, 0x20=enable, 0x40=e-stop not on
|
||||
packet[2] = 0x03
|
||||
// Protocol version.
|
||||
packet[2] = 0
|
||||
|
||||
// Robot status byte.
|
||||
packet[3] = 0
|
||||
if dsConn.Auto {
|
||||
packet[2] |= 0x10
|
||||
packet[3] |= 0x02
|
||||
}
|
||||
if dsConn.Enabled {
|
||||
packet[2] |= 0x20
|
||||
packet[3] |= 0x04
|
||||
}
|
||||
if !dsConn.EmergencyStop {
|
||||
packet[2] |= 0x40
|
||||
if dsConn.EmergencyStop {
|
||||
packet[3] |= 0x80
|
||||
}
|
||||
|
||||
// Alliance station, stored as ASCII characters 'R/B' and '1/2/3'.
|
||||
packet[3] = dsConn.AllianceStation[0]
|
||||
packet[4] = dsConn.AllianceStation[1]
|
||||
// Unknown or unused.
|
||||
packet[4] = 0
|
||||
|
||||
// Static protocol version repeated twice.
|
||||
for i := 0; i < 8; i++ {
|
||||
packet[10+i] = driverStationProtocolVersion[i]
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
packet[18+i] = driverStationProtocolVersion[i]
|
||||
}
|
||||
// Alliance station.
|
||||
packet[5] = allianceStationPositionMap[dsConn.AllianceStation]
|
||||
|
||||
// Calculate and store the 4-byte CRC32 checksum.
|
||||
checksum := crc32.ChecksumIEEE(packet[:])
|
||||
packet[70] = byte((checksum >> 24) & 0xff)
|
||||
packet[71] = byte((checksum >> 16) & 0xff)
|
||||
packet[72] = byte((checksum >> 8) & 0xff)
|
||||
packet[73] = byte((checksum) & 0xff)
|
||||
// Match information.
|
||||
match := mainArena.currentMatch
|
||||
if match.Type == "practice" {
|
||||
packet[6] = 1
|
||||
} else if match.Type == "qualification" {
|
||||
packet[6] = 2
|
||||
} else if match.Type == "elimination" {
|
||||
packet[6] = 3
|
||||
} else {
|
||||
packet[6] = 0
|
||||
}
|
||||
// TODO(patrick): Implement if it ever becomes necessary; the official FMS has a different concept of
|
||||
// match numbers so it's hard to translate.
|
||||
packet[7] = 0 // Match number
|
||||
packet[8] = 1 // Match number
|
||||
packet[9] = 1 // Match repeat number
|
||||
|
||||
// Current time.
|
||||
currentTime := time.Now()
|
||||
packet[10] = byte(((currentTime.Nanosecond() / 1000) >> 24) & 0xff)
|
||||
packet[11] = byte(((currentTime.Nanosecond() / 1000) >> 16) & 0xff)
|
||||
packet[12] = byte(((currentTime.Nanosecond() / 1000) >> 8) & 0xff)
|
||||
packet[13] = byte((currentTime.Nanosecond() / 1000) & 0xff)
|
||||
packet[14] = byte(currentTime.Second())
|
||||
packet[15] = byte(currentTime.Minute())
|
||||
packet[16] = byte(currentTime.Hour())
|
||||
packet[17] = byte(currentTime.Day())
|
||||
packet[18] = byte(currentTime.Month())
|
||||
packet[19] = byte(currentTime.Year() - 1900)
|
||||
|
||||
// Remaining number of seconds in match.
|
||||
var matchSecondsRemaining int
|
||||
switch mainArena.MatchState {
|
||||
case PRE_MATCH:
|
||||
fallthrough
|
||||
case START_MATCH:
|
||||
fallthrough
|
||||
case AUTO_PERIOD:
|
||||
matchSecondsRemaining = mainArena.matchTiming.AutoDurationSec + mainArena.matchTiming.TeleopDurationSec -
|
||||
int(mainArena.MatchTimeSec())
|
||||
case PAUSE_PERIOD:
|
||||
matchSecondsRemaining = mainArena.matchTiming.TeleopDurationSec
|
||||
case TELEOP_PERIOD:
|
||||
fallthrough
|
||||
case ENDGAME_PERIOD:
|
||||
matchSecondsRemaining = mainArena.matchTiming.AutoDurationSec + mainArena.matchTiming.TeleopDurationSec +
|
||||
mainArena.matchTiming.PauseDurationSec - int(mainArena.MatchTimeSec())
|
||||
default:
|
||||
matchSecondsRemaining = 0
|
||||
}
|
||||
packet[20] = byte(matchSecondsRemaining >> 8 & 0xff)
|
||||
packet[21] = byte(matchSecondsRemaining & 0xff)
|
||||
|
||||
// Increment the packet count for next time.
|
||||
dsConn.packetCount++
|
||||
@@ -185,45 +192,136 @@ func (dsConn *DriverStationConnection) encodeControlPacket() [74]byte {
|
||||
// Builds and sends the next control packet to the Driver Station.
|
||||
func (dsConn *DriverStationConnection) sendControlPacket() error {
|
||||
packet := dsConn.encodeControlPacket()
|
||||
_, err := dsConn.conn.Write(packet[:])
|
||||
if err != nil {
|
||||
return err
|
||||
if dsConn.udpConn != nil {
|
||||
_, err := dsConn.udpConn.Write(packet[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserializes a packet from the DS into a structure representing the DS/robot status.
|
||||
func decodeStatusPacket(data [50]byte) *DriverStationStatus {
|
||||
dsStatus := new(DriverStationStatus)
|
||||
dsStatus.DsLinked = true
|
||||
func (dsConn *DriverStationConnection) decodeStatusPacket(data [36]byte) {
|
||||
if data[6]&0x01 != 0 && data[6]&0x08 == 0 {
|
||||
// Robot is not connected.
|
||||
dsConn.RobotLinked = false
|
||||
return
|
||||
}
|
||||
|
||||
// Robot status byte.
|
||||
dsStatus.RobotLinked = (data[2] & 0x02) != 0
|
||||
dsStatus.Auto = (data[2] & 0x10) != 0
|
||||
dsStatus.Enabled = (data[2] & 0x20) != 0
|
||||
dsStatus.EmergencyStop = (data[2] & 0x40) == 0
|
||||
dsConn.RobotLinked = true
|
||||
|
||||
// Team number, stored in two bytes as hundreds and then ones (like the IP address).
|
||||
dsStatus.TeamId = int(data[4])*100 + int(data[5])
|
||||
// Average DS-robot trip time in milliseconds.
|
||||
dsConn.DsRobotTripTimeMs = int(data[1]) / 2
|
||||
|
||||
// Alliance station, stored as ASCII characters 'R/B' and '1/2/3'.
|
||||
dsStatus.AllianceStation = string(data[10:12])
|
||||
// Number of missed packets sent from the DS to the robot.
|
||||
dsConn.MissedPacketCount = int(data[2]) - dsConn.missedPacketOffset
|
||||
|
||||
// Driver Station software version, stored as 8-byte string.
|
||||
dsStatus.DsVersion = string(data[18:26])
|
||||
// Robot battery voltage, stored as volts * 256.
|
||||
dsConn.BatteryVoltage = float64(data[3]) + float64(data[4])/256
|
||||
|
||||
// Number of missed packets sent from the DS to the robot, stored in two big-endian bytes.
|
||||
dsStatus.MissedPacketCount = int(data[26])*256 + int(data[27])
|
||||
|
||||
// Total number of packets sent from the DS to the robot, stored in two big-endian bytes.
|
||||
dsStatus.PacketCount = int(data[28])*256 + int(data[29])
|
||||
|
||||
// Average DS-robot trip time in milliseconds, stored in two big-endian bytes.
|
||||
dsStatus.DsRobotTripTimeMs = int(data[30])*256 + int(data[31])
|
||||
|
||||
// Robot battery voltage, stored (bizarrely) what it looks like in decimal but as two hexadecimal numbers.
|
||||
dsStatus.BatteryVoltage, _ = strconv.ParseFloat(fmt.Sprintf("%x.%x", data[40], data[41]), 32)
|
||||
|
||||
return dsStatus
|
||||
dsConn.lastRobotLinkedTime = time.Now()
|
||||
}
|
||||
|
||||
// Listens for TCP connection requests to Cheesy Arena from driver stations.
|
||||
func ListenForDriverStations() {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", driverStationTcpListenAddress, driverStationTcpListenPort))
|
||||
if err != nil {
|
||||
log.Printf("Error opening driver station socket: %v", err.Error())
|
||||
log.Printf("Change IP address to %s and restart Cheesy Arena to fix.", driverStationTcpListenAddress)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.Printf("Listening for driver stations on port %d\n", driverStationTcpListenPort)
|
||||
for {
|
||||
tcpConn, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Println("Error accepting driver station connection: ", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the team number back and start tracking the driver station.
|
||||
var packet [5]byte
|
||||
_, err = tcpConn.Read(packet[:])
|
||||
if err != nil {
|
||||
log.Println("Error reading initial packet: ", err.Error())
|
||||
continue
|
||||
}
|
||||
if !(packet[0] == 0 && packet[1] == 3 && packet[2] == 24) {
|
||||
log.Printf("Invalid initial packet received: %v", packet)
|
||||
tcpConn.Close()
|
||||
continue
|
||||
}
|
||||
teamId := int(packet[3])<<8 + int(packet[4])
|
||||
|
||||
// Check to see if the team is supposed to be on the field, and notify the DS accordingly.
|
||||
var assignmentPacket [5]byte
|
||||
assignmentPacket[0] = 0 // Packet size
|
||||
assignmentPacket[1] = 3 // Packet size
|
||||
assignmentPacket[2] = 25 // Packet type
|
||||
assignedStation := mainArena.getAssignedAllianceStation(teamId)
|
||||
if assignedStation == "" {
|
||||
log.Printf("Rejecting connection from Team %d, who is not in the current match.", teamId)
|
||||
assignmentPacket[3] = 0
|
||||
assignmentPacket[4] = 2
|
||||
tcpConn.Write(assignmentPacket[:])
|
||||
tcpConn.Close()
|
||||
continue
|
||||
}
|
||||
log.Printf("Accepting connection from Team %d in station %s.", teamId, assignedStation)
|
||||
assignmentPacket[3] = allianceStationPositionMap[assignedStation]
|
||||
assignmentPacket[4] = 0
|
||||
_, err = tcpConn.Write(assignmentPacket[:])
|
||||
if err != nil {
|
||||
log.Println("Error sending driver station assignment packet: %v", err)
|
||||
tcpConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
dsConn, err := NewDriverStationConnection(teamId, assignedStation, tcpConn)
|
||||
if err != nil {
|
||||
log.Println("Error registering driver station connection: %v", err)
|
||||
tcpConn.Close()
|
||||
continue
|
||||
}
|
||||
mainArena.AllianceStations[assignedStation].DsConn = dsConn
|
||||
|
||||
// Spin up a goroutine to handle further TCP communication with this driver station.
|
||||
go dsConn.handleTcpConnection()
|
||||
}
|
||||
}
|
||||
|
||||
func (dsConn *DriverStationConnection) handleTcpConnection() {
|
||||
buffer := make([]byte, maxTcpPacketBytes)
|
||||
for {
|
||||
dsConn.tcpConn.SetReadDeadline(time.Now().Add(time.Second * driverStationLinkTimeoutSec))
|
||||
_, err := dsConn.tcpConn.Read(buffer)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading from connection for Team %d: %v\n", dsConn.TeamId, err.Error())
|
||||
dsConn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
dsConn.DsLinked = true
|
||||
dsConn.lastPacketTime = time.Now()
|
||||
|
||||
packetType := int(buffer[2])
|
||||
switch packetType {
|
||||
case 28:
|
||||
// DS keepalive packet; do nothing.
|
||||
case 22:
|
||||
// Robot status packet.
|
||||
var statusPacket [36]byte
|
||||
copy(statusPacket[:], buffer[2:38])
|
||||
dsConn.decodeStatusPacket(statusPacket)
|
||||
}
|
||||
|
||||
// Log the packet if the match is in progress.
|
||||
matchTimeSec := mainArena.MatchTimeSec()
|
||||
if matchTimeSec > 0 && dsConn.log != nil {
|
||||
dsConn.log.LogDsPacket(matchTimeSec, packetType, dsConn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,50 +4,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEncodeControlPacket(t *testing.T) {
|
||||
dsConn, err := NewDriverStationConnection(254, "R1")
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
mainArena.Setup()
|
||||
|
||||
tcpConn := setupFakeTcpConnection(t)
|
||||
defer tcpConn.Close()
|
||||
dsConn, err := NewDriverStationConnection(254, "R1", tcpConn)
|
||||
assert.Nil(t, err)
|
||||
defer dsConn.Close()
|
||||
|
||||
data := dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 0, 67, 82, 49, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 235, 5, 29}, data)
|
||||
assert.Equal(t, byte(0), data[5])
|
||||
assert.Equal(t, byte(0), data[6])
|
||||
assert.Equal(t, byte(0), data[20])
|
||||
assert.Equal(t, byte(150), data[21])
|
||||
|
||||
// Check the different alliance station values as well as the checksums.
|
||||
// Check the different alliance station values.
|
||||
dsConn.AllianceStation = "R2"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 1, 67, 82, 50, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 141, 17, 174}, data)
|
||||
assert.Equal(t, byte(1), data[5])
|
||||
dsConn.AllianceStation = "R3"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 2, 67, 82, 51, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 206, 203, 150}, data)
|
||||
assert.Equal(t, byte(2), data[5])
|
||||
dsConn.AllianceStation = "B1"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 3, 67, 66, 49, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 57, 55, 68}, data)
|
||||
assert.Equal(t, byte(3), data[5])
|
||||
dsConn.AllianceStation = "B2"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 4, 67, 66, 50, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 101, 225, 16}, data)
|
||||
assert.Equal(t, byte(4), data[5])
|
||||
dsConn.AllianceStation = "B3"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, [74]byte{0, 5, 67, 66, 51, 0, 0, 0, 0, 0, 49, 49, 49, 57, 49, 49, 48, 48, 49, 49, 49, 57,
|
||||
49, 49, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 207, 133, 117}, data)
|
||||
assert.Equal(t, byte(5), data[5])
|
||||
|
||||
// Check packet count rollover.
|
||||
dsConn.packetCount = 255
|
||||
@@ -71,23 +71,58 @@ func TestEncodeControlPacket(t *testing.T) {
|
||||
// Check different robot statuses.
|
||||
dsConn.Auto = true
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(83), data[2])
|
||||
assert.Equal(t, byte(2), data[3])
|
||||
|
||||
dsConn.Enabled = true
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(115), data[2])
|
||||
assert.Equal(t, byte(6), data[3])
|
||||
|
||||
dsConn.Auto = false
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(99), data[2])
|
||||
assert.Equal(t, byte(4), data[3])
|
||||
|
||||
dsConn.EmergencyStop = true
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(35), data[2])
|
||||
assert.Equal(t, byte(132), data[3])
|
||||
|
||||
// Check different match types.
|
||||
mainArena.currentMatch.Type = "practice"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(1), data[6])
|
||||
mainArena.currentMatch.Type = "qualification"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(2), data[6])
|
||||
mainArena.currentMatch.Type = "elimination"
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(3), data[6])
|
||||
|
||||
// Check the countdown at different points during the match.
|
||||
mainArena.MatchState = AUTO_PERIOD
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Duration(4 * time.Second))
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(146), data[21])
|
||||
mainArena.MatchState = PAUSE_PERIOD
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Duration(16 * time.Second))
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(135), data[21])
|
||||
mainArena.MatchState = TELEOP_PERIOD
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Duration(33 * time.Second))
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(119), data[21])
|
||||
mainArena.MatchState = ENDGAME_PERIOD
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Duration(150 * time.Second))
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(2), data[21])
|
||||
mainArena.MatchState = POST_MATCH
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Duration(180 * time.Second))
|
||||
data = dsConn.encodeControlPacket()
|
||||
assert.Equal(t, byte(0), data[21])
|
||||
}
|
||||
|
||||
func TestSendControlPacket(t *testing.T) {
|
||||
dsConn, err := NewDriverStationConnection(254, "R1")
|
||||
tcpConn := setupFakeTcpConnection(t)
|
||||
defer tcpConn.Close()
|
||||
dsConn, err := NewDriverStationConnection(254, "R1", tcpConn)
|
||||
assert.Nil(t, err)
|
||||
defer dsConn.Close()
|
||||
|
||||
@@ -97,64 +132,32 @@ func TestSendControlPacket(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeStatusPacket(t *testing.T) {
|
||||
tcpConn := setupFakeTcpConnection(t)
|
||||
defer tcpConn.Close()
|
||||
dsConn, err := NewDriverStationConnection(254, "R1", tcpConn)
|
||||
assert.Nil(t, err)
|
||||
defer dsConn.Close()
|
||||
|
||||
// Check with no linked robot.
|
||||
data := [50]byte{0, 0, 64, 1, 2, 54, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
98, 200, 63, 43, 0, 11, 0, 240, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus := decodeStatusPacket(data)
|
||||
assert.Equal(t, 254, dsStatus.TeamId)
|
||||
assert.Equal(t, "R1", dsStatus.AllianceStation)
|
||||
assert.Equal(t, false, dsStatus.RobotLinked)
|
||||
assert.Equal(t, false, dsStatus.Auto)
|
||||
assert.Equal(t, false, dsStatus.Enabled)
|
||||
assert.Equal(t, false, dsStatus.EmergencyStop)
|
||||
assert.Equal(t, 0.0, dsStatus.BatteryVoltage)
|
||||
assert.Equal(t, "02121300", dsStatus.DsVersion)
|
||||
assert.Equal(t, 16171, dsStatus.PacketCount)
|
||||
assert.Equal(t, 25288, dsStatus.MissedPacketCount)
|
||||
assert.Equal(t, 11, dsStatus.DsRobotTripTimeMs)
|
||||
data := [36]byte{22, 28, 103, 19, 192, 0, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
dsConn.decodeStatusPacket(data)
|
||||
assert.Equal(t, false, dsConn.RobotLinked)
|
||||
assert.Equal(t, 0.0, dsConn.BatteryVoltage)
|
||||
assert.Equal(t, 0, dsConn.MissedPacketCount)
|
||||
assert.Equal(t, 0, dsConn.DsRobotTripTimeMs)
|
||||
|
||||
// Check different team numbers.
|
||||
data = [50]byte{0, 0, 64, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, 766, dsStatus.TeamId)
|
||||
data = [50]byte{0, 0, 64, 1, 51, 36, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, 5136, dsStatus.TeamId)
|
||||
|
||||
// Check different alliance stations.
|
||||
data = [50]byte{0, 0, 64, 1, 51, 36, 0, 0, 0, 0, 66, 51, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, "B3", dsStatus.AllianceStation)
|
||||
|
||||
// Check different robot statuses.
|
||||
data = [50]byte{0, 0, 66, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, true, dsStatus.RobotLinked)
|
||||
data = [50]byte{0, 0, 98, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, true, dsStatus.Enabled)
|
||||
data = [50]byte{0, 0, 114, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, true, dsStatus.Auto)
|
||||
data = [50]byte{0, 0, 50, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, true, dsStatus.EmergencyStop)
|
||||
|
||||
// Check different battery voltages.
|
||||
data = [50]byte{0, 0, 64, 1, 7, 66, 0, 0, 0, 0, 82, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 255, 255, 255, 255, 82, 0, 0, 0, 0, 0, 25, 117, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
dsStatus = decodeStatusPacket(data)
|
||||
assert.Equal(t, 19.75, dsStatus.BatteryVoltage)
|
||||
// Check with linked robot.
|
||||
data = [36]byte{22, 28, 103, 19, 192, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
dsConn.decodeStatusPacket(data)
|
||||
assert.Equal(t, true, dsConn.RobotLinked)
|
||||
assert.Equal(t, 19.75, dsConn.BatteryVoltage)
|
||||
assert.Equal(t, 103, dsConn.MissedPacketCount)
|
||||
assert.Equal(t, 14, dsConn.DsRobotTripTimeMs)
|
||||
}
|
||||
|
||||
func TestListenForDsPackets(t *testing.T) {
|
||||
func TestListenForDriverStations(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
@@ -163,105 +166,74 @@ func TestListenForDsPackets(t *testing.T) {
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
|
||||
listener, err := DsPacketListener()
|
||||
if assert.Nil(t, err) {
|
||||
go ListenForDsPackets(listener)
|
||||
}
|
||||
driverStationTcpListenAddress = "127.0.0.1"
|
||||
go ListenForDriverStations()
|
||||
mainArena.Setup()
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
dsConn, err := NewDriverStationConnection(254, "B1")
|
||||
defer dsConn.Close()
|
||||
assert.Nil(t, err)
|
||||
mainArena.AllianceStations["B1"].DsConn = dsConn
|
||||
dsConn, err = NewDriverStationConnection(1114, "R3")
|
||||
defer dsConn.Close()
|
||||
assert.Nil(t, err)
|
||||
mainArena.AllianceStations["R3"].DsConn = dsConn
|
||||
|
||||
// Create a socket to send fake DS packets to localhost.
|
||||
conn, err := net.Dial("udp4", fmt.Sprintf("127.0.0.1:%d", driverStationReceivePort))
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check receiving a packet from an expected team.
|
||||
packet := [50]byte{0, 0, 48, 1, 2, 54, 0, 0, 0, 0, 66, 49, 0, 0, 0, 0, 0, 0, 48, 50, 49, 50, 49, 51, 48, 48,
|
||||
152, 160, 152, 160, 1, 0, 255, 255, 82, 0, 0, 0, 0, 0, 25, 117, 0, 0, 0, 0, 42, 7, 189, 111}
|
||||
_, err = conn.Write(packet[:])
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Millisecond * 10) // Allow some time for the goroutine to process the incoming packet.
|
||||
dsStatus := mainArena.AllianceStations["B1"].DsConn.DriverStationStatus
|
||||
if assert.NotNil(t, dsStatus) {
|
||||
assert.Equal(t, 254, dsStatus.TeamId)
|
||||
assert.Equal(t, "B1", dsStatus.AllianceStation)
|
||||
assert.Equal(t, true, dsStatus.DsLinked)
|
||||
assert.Equal(t, false, dsStatus.RobotLinked)
|
||||
assert.Equal(t, true, dsStatus.Auto)
|
||||
assert.Equal(t, true, dsStatus.Enabled)
|
||||
assert.Equal(t, true, dsStatus.EmergencyStop)
|
||||
assert.Equal(t, 19.75, dsStatus.BatteryVoltage)
|
||||
assert.Equal(t, "02121300", dsStatus.DsVersion)
|
||||
assert.Equal(t, 39072, dsStatus.PacketCount)
|
||||
assert.Equal(t, 39072, dsStatus.MissedPacketCount)
|
||||
assert.Equal(t, 256, dsStatus.DsRobotTripTimeMs)
|
||||
// Connect with an invalid initial packet.
|
||||
tcpConn, err := net.Dial("tcp", "127.0.0.1:1750")
|
||||
if assert.Nil(t, err) {
|
||||
dataSend := [5]byte{0, 3, 29, 0, 0}
|
||||
tcpConn.Write(dataSend[:])
|
||||
var dataReceived [100]byte
|
||||
_, err = tcpConn.Read(dataReceived[:])
|
||||
assert.NotNil(t, err)
|
||||
tcpConn.Close()
|
||||
}
|
||||
assert.True(t, time.Since(mainArena.AllianceStations["B1"].DsConn.LastPacketTime).Seconds() < 0.1)
|
||||
assert.True(t, time.Since(mainArena.AllianceStations["B1"].DsConn.LastRobotLinkedTime).Seconds() > 100)
|
||||
packet[2] = byte(98)
|
||||
_, err = conn.Write(packet[:])
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
dsStatus2 := mainArena.AllianceStations["B1"].DsConn.DriverStationStatus
|
||||
if assert.NotNil(t, dsStatus2) {
|
||||
assert.Equal(t, true, dsStatus2.RobotLinked)
|
||||
assert.Equal(t, false, dsStatus2.Auto)
|
||||
assert.Equal(t, true, dsStatus2.Enabled)
|
||||
assert.Equal(t, false, dsStatus2.EmergencyStop)
|
||||
|
||||
// Connect as a team not in the current match.
|
||||
tcpConn, err = net.Dial("tcp", "127.0.0.1:1750")
|
||||
if assert.Nil(t, err) {
|
||||
dataSend := [5]byte{0, 3, 24, 5, 223}
|
||||
tcpConn.Write(dataSend[:])
|
||||
var dataReceived [5]byte
|
||||
_, err = tcpConn.Read(dataReceived[:])
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, [5]byte{0, 3, 25, 0, 2}, dataReceived)
|
||||
tcpConn.Close()
|
||||
}
|
||||
assert.True(t, time.Since(mainArena.AllianceStations["B1"].DsConn.LastPacketTime).Seconds() < 0.1)
|
||||
assert.True(t, time.Since(mainArena.AllianceStations["B1"].DsConn.LastRobotLinkedTime).Seconds() < 0.1)
|
||||
|
||||
// Should log the packet to file if received during a match.
|
||||
dsConn = mainArena.AllianceStations["B1"].DsConn
|
||||
dsConn.signalMatchStart(&Match{Type: "Qualification", DisplayName: "1"})
|
||||
mainArena.matchStartTime = time.Now().Add(-1 * time.Second)
|
||||
mainArena.MatchState = AUTO_PERIOD
|
||||
_, err = conn.Write(packet[:])
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
logContents, err := ioutil.ReadFile(dsConn.log.logFile.Name())
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, string(logContents), "254,B1,true,false,true,false,19.750000,02121300,39072,0,256")
|
||||
// Connect as a team in the current match.
|
||||
mainArena.AssignTeam(1503, "B2")
|
||||
tcpConn, err = net.Dial("tcp", "127.0.0.1:1750")
|
||||
if assert.Nil(t, err) {
|
||||
defer tcpConn.Close()
|
||||
dataSend := [5]byte{0, 3, 24, 5, 223}
|
||||
tcpConn.Write(dataSend[:])
|
||||
var dataReceived [5]byte
|
||||
_, err = tcpConn.Read(dataReceived[:])
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, [5]byte{0, 3, 25, 4, 0}, dataReceived)
|
||||
|
||||
// Should ignore a packet coming from an expected team in the wrong position.
|
||||
statusBefore := mainArena.AllianceStations["R3"].DsConn.DriverStationStatus
|
||||
packet[10] = 'R'
|
||||
packet[11] = '3'
|
||||
packet[2] = 48
|
||||
_, err = conn.Write(packet[:])
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
assert.Equal(t, statusBefore, mainArena.AllianceStations["R3"].DsConn.DriverStationStatus)
|
||||
assert.Equal(t, true, mainArena.AllianceStations["B1"].DsConn.DriverStationStatus.RobotLinked)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
dsConn := mainArena.AllianceStations["B2"].DsConn
|
||||
if assert.NotNil(t, dsConn) {
|
||||
assert.Equal(t, 1503, dsConn.TeamId)
|
||||
assert.Equal(t, "B2", dsConn.AllianceStation)
|
||||
|
||||
// Should ignore a packet coming from an unexpected team.
|
||||
packet[4] = byte(15)
|
||||
packet[5] = byte(3)
|
||||
packet[10] = 'B'
|
||||
packet[11] = '1'
|
||||
packet[2] = 48
|
||||
_, err = conn.Write(packet[:])
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
assert.Equal(t, true, mainArena.AllianceStations["B1"].DsConn.DriverStationStatus.RobotLinked)
|
||||
|
||||
// Should indicate that the connection has dropped if a response isn't received before the timeout.
|
||||
dsConn = mainArena.AllianceStations["B1"].DsConn
|
||||
dsConn.Update()
|
||||
assert.Equal(t, true, dsConn.DriverStationStatus.DsLinked)
|
||||
assert.Equal(t, true, dsConn.DriverStationStatus.RobotLinked)
|
||||
assert.NotEqual(t, 0, dsConn.DriverStationStatus.BatteryVoltage)
|
||||
dsConn.LastPacketTime = dsConn.LastPacketTime.Add(-1 * time.Second)
|
||||
dsConn.Update()
|
||||
assert.Equal(t, false, dsConn.DriverStationStatus.DsLinked)
|
||||
assert.Equal(t, false, dsConn.DriverStationStatus.RobotLinked)
|
||||
assert.Equal(t, 0.0, dsConn.DriverStationStatus.BatteryVoltage)
|
||||
// Check that an unknown packet type gets ignored and a status packet gets decoded.
|
||||
dataSend = [5]byte{0, 3, 37, 0, 0}
|
||||
tcpConn.Write(dataSend[:])
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
dataSend2 := [38]byte{0, 36, 22, 28, 103, 19, 192, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
tcpConn.Write(dataSend2[:])
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
assert.Equal(t, true, dsConn.RobotLinked)
|
||||
assert.Equal(t, 19.75, dsConn.BatteryVoltage)
|
||||
assert.Equal(t, 103, dsConn.MissedPacketCount)
|
||||
assert.Equal(t, 14, dsConn.DsRobotTripTimeMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupFakeTcpConnection(t *testing.T) net.Conn {
|
||||
// Set up a fake TCP endpoint and connection to it.
|
||||
l, err := net.Listen("tcp", ":9999")
|
||||
assert.Nil(t, err)
|
||||
defer l.Close()
|
||||
tcpConn, err := net.Dial("tcp", "127.0.0.1:9999")
|
||||
assert.Nil(t, err)
|
||||
return tcpConn
|
||||
}
|
||||
|
||||
4
main.go
4
main.go
@@ -21,9 +21,7 @@ func main() {
|
||||
|
||||
// Run the webserver and DS packet listener in goroutines and use the main one for the arena state machine.
|
||||
go ServeWebInterface()
|
||||
listener, err := DsPacketListener()
|
||||
checkErr(err)
|
||||
go ListenForDsPackets(listener)
|
||||
go ListenForDriverStations()
|
||||
go MonitorBandwidth()
|
||||
mainArena.Setup()
|
||||
mainArena.Run()
|
||||
|
||||
@@ -70,9 +70,9 @@ var handleStatus = function(data) {
|
||||
if (stationStatus && stationStatus.Bypass) {
|
||||
$("#match").attr("data-status", "bypass");
|
||||
} else if (stationStatus && stationStatus.DsConn) {
|
||||
if (!stationStatus.DsConn.DriverStationStatus.DsLinked) {
|
||||
if (!stationStatus.DsConn.DsLinked) {
|
||||
$("#match").attr("data-status", allianceStation[0]);
|
||||
} else if (!stationStatus.DsConn.DriverStationStatus.RobotLinked) {
|
||||
} else if (!stationStatus.DsConn.RobotLinked) {
|
||||
blink = true;
|
||||
if (!blinkInterval) {
|
||||
blinkInterval = setInterval(function() {
|
||||
|
||||
@@ -11,11 +11,11 @@ var handleStatus = function(data) {
|
||||
// Update the team status view.
|
||||
$.each(data.AllianceStations, function(station, stationStatus) {
|
||||
if (stationStatus.DsConn) {
|
||||
var dsConn = stationStatus.DsConn;
|
||||
$("#status" + station + " .team").text(stationStatus.DsConn.TeamId);
|
||||
var dsStatus = stationStatus.DsConn.DriverStationStatus;
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", dsStatus.DsLinked);
|
||||
$("#status" + station + " .ds-status").text(dsStatus.MBpsToRobot.toFixed(1) + "/" + dsStatus.MBpsFromRobot.toFixed(1));
|
||||
$("#status" + station + " .robot-status").attr("data-status-ok", dsStatus.RobotLinked);
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", dsConn.DsLinked);
|
||||
$("#status" + station + " .ds-status").text(dsConn.MBpsToRobot.toFixed(1) + "/" + dsConn.MBpsFromRobot.toFixed(1));
|
||||
$("#status" + station + " .robot-status").attr("data-status-ok", dsConn.RobotLinked);
|
||||
if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) {
|
||||
$("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed());
|
||||
} else {
|
||||
@@ -26,12 +26,12 @@ var handleStatus = function(data) {
|
||||
lowBatteryThreshold = 12;
|
||||
}
|
||||
$("#status" + station + " .battery-status").attr("data-status-ok",
|
||||
dsStatus.BatteryVoltage > lowBatteryThreshold && dsStatus.RobotLinked);
|
||||
$("#status" + station + " .battery-status").text(dsStatus.BatteryVoltage.toFixed(1) + "V");
|
||||
dsConn.BatteryVoltage > lowBatteryThreshold && dsConn.RobotLinked);
|
||||
$("#status" + station + " .battery-status").text(dsConn.BatteryVoltage.toFixed(1) + "V");
|
||||
$("#status" + station + " .trip-time").attr("data-status-ok", true);
|
||||
$("#status" + station + " .trip-time").text(dsStatus.DsRobotTripTimeMs.toFixed(1) + "ms");
|
||||
$("#status" + station + " .trip-time").text(dsConn.DsRobotTripTimeMs.toFixed(1) + "ms");
|
||||
$("#status" + station + " .packet-loss").attr("data-status-ok", true);
|
||||
$("#status" + station + " .packet-loss").text(dsStatus.MissedPacketCount);
|
||||
$("#status" + station + " .packet-loss").text(dsConn.MissedPacketCount);
|
||||
} else {
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", "");
|
||||
$("#status" + station + " .ds-status").text("");
|
||||
|
||||
@@ -62,10 +62,10 @@ var handleStatus = function(data) {
|
||||
// Update the team status view.
|
||||
$.each(data.AllianceStations, function(station, stationStatus) {
|
||||
if (stationStatus.DsConn) {
|
||||
var dsStatus = stationStatus.DsConn.DriverStationStatus;
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", dsStatus.DsLinked);
|
||||
$("#status" + station + " .ds-status").text(dsStatus.MBpsToRobot.toFixed(1) + "/" + dsStatus.MBpsFromRobot.toFixed(1));
|
||||
$("#status" + station + " .robot-status").attr("data-status-ok", dsStatus.RobotLinked);
|
||||
var dsConn = stationStatus.DsConn;
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", dsConn.DsLinked);
|
||||
$("#status" + station + " .ds-status").text(dsConn.MBpsToRobot.toFixed(1) + "/" + dsConn.MBpsFromRobot.toFixed(1));
|
||||
$("#status" + station + " .robot-status").attr("data-status-ok", dsConn.RobotLinked);
|
||||
if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) {
|
||||
$("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed());
|
||||
} else {
|
||||
@@ -76,8 +76,8 @@ var handleStatus = function(data) {
|
||||
lowBatteryThreshold = 12;
|
||||
}
|
||||
$("#status" + station + " .battery-status").attr("data-status-ok",
|
||||
dsStatus.BatteryVoltage > lowBatteryThreshold && dsStatus.RobotLinked);
|
||||
$("#status" + station + " .battery-status").text(dsStatus.BatteryVoltage.toFixed(1) + "V");
|
||||
dsConn.BatteryVoltage > lowBatteryThreshold && dsConn.RobotLinked);
|
||||
$("#status" + station + " .battery-status").text(dsConn.BatteryVoltage.toFixed(1) + "V");
|
||||
} else {
|
||||
$("#status" + station + " .ds-status").attr("data-status-ok", "");
|
||||
$("#status" + station + " .ds-status").text("");
|
||||
|
||||
@@ -166,12 +166,12 @@ interface Vlan16
|
||||
ip classless
|
||||
ip http server
|
||||
!
|
||||
access-list 111 permit ip 10.0.1.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 112 permit ip 10.0.2.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 113 permit ip 10.0.3.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 114 permit ip 10.0.4.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 115 permit ip 10.0.5.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 116 permit ip 10.0.6.0 0.0.0.255 host 10.0.100.50
|
||||
access-list 111 permit ip 10.0.1.0 0.0.0.255 host 10.0.100.5
|
||||
access-list 112 permit ip 10.0.2.0 0.0.0.255 host 10.0.100.5
|
||||
access-list 113 permit ip 10.0.3.0 0.0.0.255 host 10.0.100.5
|
||||
access-list 114 permit ip 10.0.4.0 0.0.0.255 host 10.0.100.5
|
||||
access-list 115 permit ip 10.0.5.0 0.0.0.255 host 10.0.100.5
|
||||
access-list 116 permit ip 10.0.6.0 0.0.0.255 host 10.0.100.5
|
||||
!
|
||||
snmp-server community 1234Five RO
|
||||
!
|
||||
|
||||
@@ -34,18 +34,17 @@ func NewTeamMatchLog(teamId int, match *Match) (*TeamMatchLog, error) {
|
||||
}
|
||||
|
||||
log := TeamMatchLog{log.New(logFile, "", 0), logFile}
|
||||
log.logger.Println("matchTimeSec,teamId,allianceStation,robotLinked,auto,enabled,emergencyStop," +
|
||||
"batteryVoltage,dsVersion,packetCount,missedPacketCount,dsRobotTripTimeMs")
|
||||
log.logger.Println("matchTimeSec,packetType,teamId,allianceStation,robotLinked,auto,enabled," +
|
||||
"emergencyStop,batteryVoltage,missedPacketCount,dsRobotTripTimeMs")
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// Adds a line to the log when a packet is received.
|
||||
func (log *TeamMatchLog) LogDsStatus(matchTimeSec float64, dsStatus *DriverStationStatus) {
|
||||
log.logger.Printf("%f,%d,%s,%v,%v,%v,%v,%f,%s,%d,%d,%d", matchTimeSec, dsStatus.TeamId,
|
||||
dsStatus.AllianceStation, dsStatus.RobotLinked, dsStatus.Auto, dsStatus.Enabled,
|
||||
dsStatus.EmergencyStop, dsStatus.BatteryVoltage, dsStatus.DsVersion, dsStatus.PacketCount,
|
||||
dsStatus.MissedPacketCount, dsStatus.DsRobotTripTimeMs)
|
||||
func (log *TeamMatchLog) LogDsPacket(matchTimeSec float64, packetType int, dsConn *DriverStationConnection) {
|
||||
log.logger.Printf("%f,%d,%d,%s,%v,%v,%v,%v,%f,%d,%d", matchTimeSec, packetType, dsConn.TeamId,
|
||||
dsConn.AllianceStation, dsConn.RobotLinked, dsConn.Auto, dsConn.Enabled, dsConn.EmergencyStop,
|
||||
dsConn.BatteryVoltage, dsConn.MissedPacketCount, dsConn.DsRobotTripTimeMs)
|
||||
}
|
||||
|
||||
func (log *TeamMatchLog) Close() {
|
||||
|
||||
Reference in New Issue
Block a user