Implement 2015 driver station protocol.

This commit is contained in:
Patrick Fairbank
2016-08-24 20:54:31 -07:00
parent 1358ae6b83
commit a987086160
13 changed files with 447 additions and 372 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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("");

View File

@@ -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("");

View File

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

View File

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