Poll access point for network configuration and reflect it in the match play view.

This commit is contained in:
Patrick Fairbank
2018-10-06 18:08:15 -07:00
parent 83ad63fea6
commit 4c340443e8
11 changed files with 543 additions and 79 deletions

View File

@@ -45,7 +45,7 @@ const (
type Arena struct {
Database *model.Database
EventSettings *model.EventSettings
accessPoint *network.AccessPoint
accessPoint network.AccessPoint
networkSwitch *network.Switch
Plc plc.Plc
TbaClient *partner.TbaClient
@@ -143,8 +143,8 @@ func (arena *Arena) LoadSettings() error {
arena.EventSettings = settings
// Initialize the components that depend on settings.
arena.accessPoint = network.NewAccessPoint(settings.ApAddress, settings.ApUsername, settings.ApPassword,
settings.ApTeamChannel, settings.ApAdminChannel, settings.ApAdminWpaKey)
arena.accessPoint.SetSettings(settings.ApAddress, settings.ApUsername, settings.ApPassword,
settings.ApTeamChannel, settings.ApAdminChannel, settings.ApAdminWpaKey, settings.NetworkSecurityEnabled)
arena.networkSwitch = network.NewSwitch(settings.SwitchAddress, settings.SwitchPassword)
arena.Plc.SetAddress(settings.PlcAddress)
arena.TbaClient = partner.NewTbaClient(settings.TbaEventCode, settings.TbaSecretId, settings.TbaSecret)
@@ -559,6 +559,7 @@ func (arena *Arena) Run() {
// Start other loops in goroutines.
go arena.listenForDriverStations()
go arena.listenForDsUdpPackets()
go arena.accessPoint.Run()
go arena.Plc.Run()
for {

View File

@@ -10,6 +10,7 @@ import (
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/led"
"github.com/Team254/cheesy-arena/model"
"github.com/Team254/cheesy-arena/network"
"github.com/Team254/cheesy-arena/vaultled"
"github.com/Team254/cheesy-arena/websocket"
"strconv"
@@ -89,15 +90,22 @@ func (arena *Arena) generateAllianceStationDisplayModeMessage() interface{} {
}
func (arena *Arena) generateArenaStatusMessage() interface{} {
// Convert AP team wifi network status array to a map by station for ease of client use.
teamWifiStatuses := make(map[string]network.TeamWifiStatus)
for i, station := range []string{"R1", "R2", "R3", "B1", "B2", "B3"} {
teamWifiStatuses[station] = arena.accessPoint.TeamWifiStatuses[i]
}
return &struct {
AllianceStations map[string]*AllianceStation
TeamWifiStatuses map[string]network.TeamWifiStatus
MatchState
CanStartMatch bool
PlcIsHealthy bool
FieldEstop bool
GameSpecificData string
}{arena.AllianceStations, arena.MatchState, arena.checkCanStartMatch() == nil, arena.Plc.IsHealthy,
arena.Plc.GetFieldEstop(), arena.CurrentMatch.GameSpecificData}
}{arena.AllianceStations, teamWifiStatuses, arena.MatchState, arena.checkCanStartMatch() == nil,
arena.Plc.IsHealthy, arena.Plc.GetFieldEstop(), arena.CurrentMatch.GameSpecificData}
}
func (arena *Arena) generateAudienceDisplayModeMessage() interface{} {

View File

@@ -9,52 +9,71 @@ import (
"fmt"
"github.com/Team254/cheesy-arena/model"
"golang.org/x/crypto/ssh"
"os"
"log"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const accessPointSshPort = 22
const accessPointConnectTimeoutSec = 1
const accessPointCommandTimeoutSec = 3
const (
accessPointSshPort = 22
accessPointConnectTimeoutSec = 1
accessPointPollPeriodSec = 3
)
type AccessPoint struct {
address string
port int
username string
password string
teamChannel int
adminChannel int
adminWpaKey string
mutex sync.Mutex
address string
username string
password string
teamChannel int
adminChannel int
adminWpaKey string
networkSecurityEnabled bool
mutex sync.Mutex
TeamWifiStatuses [6]TeamWifiStatus
}
func NewAccessPoint(address, username, password string, teamChannel, adminChannel int,
adminWpaKey string) *AccessPoint {
return &AccessPoint{address: address, port: accessPointSshPort, username: username, password: password,
teamChannel: teamChannel, adminChannel: adminChannel, adminWpaKey: adminWpaKey}
type TeamWifiStatus struct {
TeamId int
RadioLinked bool
}
func (ap *AccessPoint) SetSettings(address, username, password string, teamChannel, adminChannel int,
adminWpaKey string, networkSecurityEnabled bool) {
ap.address = address
ap.username = username
ap.password = password
ap.teamChannel = teamChannel
ap.adminChannel = adminChannel
ap.adminWpaKey = adminWpaKey
ap.networkSecurityEnabled = networkSecurityEnabled
}
// Loops indefinitely to read status from the access point.
func (ap *AccessPoint) Run() {
for {
if ap.networkSecurityEnabled {
ap.updateTeamWifiStatuses()
}
time.Sleep(time.Second * accessPointPollPeriodSec)
}
}
// Sets up wireless networks for the given set of teams.
func (ap *AccessPoint) ConfigureTeamWifi(red1, red2, red3, blue1, blue2, blue3 *model.Team) error {
// Make sure multiple configurations aren't being set at the same time.
ap.mutex.Lock()
defer ap.mutex.Unlock()
config, err := ap.generateAccessPointConfig(red1, red2, red3, blue1, blue2, blue3)
if err != nil {
return err
}
command := fmt.Sprintf("uci batch <<ENDCONFIG && wifi radio0\n%s\nENDCONFIG\n", config)
return ap.runCommand(command)
_, err = ap.runCommand(command)
return err
}
func (ap *AccessPoint) ConfigureAdminWifi() error {
// Make sure multiple configurations aren't being set at the same time.
ap.mutex.Lock()
defer ap.mutex.Unlock()
disabled := 0
if ap.adminChannel == 0 {
disabled = 1
@@ -67,40 +86,35 @@ func (ap *AccessPoint) ConfigureAdminWifi() error {
"commit wireless",
}
command := fmt.Sprintf("uci batch <<ENDCONFIG && wifi\n%s\nENDCONFIG\n", strings.Join(commands, "\n"))
return ap.runCommand(command)
_, err := ap.runCommand(command)
return err
}
// Logs into the access point via SSH and runs the given shell command.
func (ap *AccessPoint) runCommand(command string) error {
func (ap *AccessPoint) runCommand(command string) (string, error) {
// Make sure multiple commands aren't being run at the same time.
ap.mutex.Lock()
defer ap.mutex.Unlock()
// Open an SSH connection to the AP.
config := &ssh.ClientConfig{User: ap.username,
Auth: []ssh.AuthMethod{ssh.Password(ap.password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: accessPointConnectTimeoutSec * time.Second}
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", ap.address, ap.port), config)
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", ap.address, accessPointSshPort), config)
if err != nil {
return err
return "", err
}
session, err := conn.NewSession()
if err != nil {
return err
return "", err
}
defer session.Close()
defer conn.Close()
session.Stdout = os.Stdout
// Run the command with a timeout. An error will be returned if the exit status is non-zero.
commandChan := make(chan error, 1)
go func() {
commandChan <- session.Run(command)
}()
select {
case err = <-commandChan:
return err
case <-time.After(accessPointCommandTimeoutSec * time.Second):
return fmt.Errorf("WiFi SSH command timed out after %d seconds", accessPointCommandTimeoutSec)
}
outputBytes, err := session.Output(command)
return string(outputBytes), err
}
func (ap *AccessPoint) generateAccessPointConfig(red1, red2, red3, blue1, blue2, blue3 *model.Team) (string, error) {
@@ -134,9 +148,9 @@ func (ap *AccessPoint) generateAccessPointConfig(red1, red2, red3, blue1, blue2,
// Verifies the validity of the given team's WPA key and adds a network for it to the list to be configured.
func addTeamConfigCommands(position int, team *model.Team, commands *[]string) error {
if team == nil {
*commands = append(*commands, fmt.Sprintf("set wireless.@wifi-iface[%d].disabled='1'", position),
fmt.Sprintf("set wireless.@wifi-iface[%d].ssid=''", position),
fmt.Sprintf("set wireless.@wifi-iface[%d].key=''", position))
*commands = append(*commands, fmt.Sprintf("set wireless.@wifi-iface[%d].disabled='0'", position),
fmt.Sprintf("set wireless.@wifi-iface[%d].ssid='no-team-%d'", position, position),
fmt.Sprintf("set wireless.@wifi-iface[%d].key='no-team-%d'", position, position))
} else {
if len(team.WpaKey) < 8 || len(team.WpaKey) > 63 {
return fmt.Errorf("Invalid WPA key '%s' configured for team %d.", team.WpaKey, team.Id)
@@ -149,3 +163,39 @@ func addTeamConfigCommands(position int, team *model.Team, commands *[]string) e
return nil
}
// Fetches the current wifi network status from the access point and updates the status structure.
func (ap *AccessPoint) updateTeamWifiStatuses() {
output, err := ap.runCommand("iwinfo")
if err != nil {
log.Printf("Error getting wifi info from AP: %v", err)
return
}
if err := decodeWifiInfo(output, ap.TeamWifiStatuses[:]); err != nil {
log.Println(err.Error())
}
}
// Parses the given output from the "iwinfo" command on the AP and updates the given status structure with the result.
func decodeWifiInfo(wifiInfo string, statuses []TeamWifiStatus) error {
ssidRe := regexp.MustCompile("ESSID: \"([-\\w ]*)\"")
ssids := ssidRe.FindAllStringSubmatch(wifiInfo, -1)
linkQualityRe := regexp.MustCompile("Link Quality: ([-\\w ]+)/([-\\w ]+)")
linkQualities := linkQualityRe.FindAllStringSubmatch(wifiInfo, -1)
// There should be at least six networks present -- one for each team on the 5GHz radio, plus one on the 2.4GHz
// radio if the admin network is enabled.
if len(ssids) < 6 || len(linkQualities) < 6 {
return fmt.Errorf("Could not parse wifi info; expected 6 team networks, got %d.", len(ssids))
}
for i := range statuses {
ssid := ssids[i][1]
statuses[i].TeamId, _ = strconv.Atoi(ssid) // Any non-numeric SSIDs will be represented by a zero.
linkQualityNumerator := linkQualities[i][1]
statuses[i].RadioLinked = linkQualityNumerator != "unknown"
}
return nil
}

View File

@@ -4,8 +4,10 @@
package network
import (
"fmt"
"github.com/Team254/cheesy-arena/model"
"github.com/stretchr/testify/assert"
"io/ioutil"
"regexp"
"testing"
)
@@ -18,20 +20,20 @@ func TestConfigureAccessPoint(t *testing.T) {
wpaKeyRe := regexp.MustCompile("key='([-\\w ]*)'")
ap := AccessPoint{teamChannel: 1234, adminChannel: 4321, adminWpaKey: "blorpy"}
// Should disable all team SSIDs if there are no teams.
// Should put dummy values for all team SSIDs if there are no teams.
config, _ := ap.generateAccessPointConfig(nil, nil, nil, nil, nil, nil)
disableds := disabledRe.FindAllStringSubmatch(config, -1)
ssids := ssidRe.FindAllStringSubmatch(config, -1)
wpaKeys := wpaKeyRe.FindAllStringSubmatch(config, -1)
if assert.Equal(t, 6, len(disableds)) && assert.Equal(t, 6, len(ssids)) && assert.Equal(t, 6, len(wpaKeys)) {
for i := 0; i < 6; i++ {
assert.Equal(t, "1", disableds[i][1])
assert.Equal(t, "", ssids[i][1])
assert.Equal(t, "", wpaKeys[i][1])
assert.Equal(t, "0", disableds[i][1])
assert.Equal(t, fmt.Sprintf("no-team-%d", i+1), ssids[i][1])
assert.Equal(t, fmt.Sprintf("no-team-%d", i+1), wpaKeys[i][1])
}
}
// Should configure two SSIDs for two teams and disable the rest.
// Should configure two SSIDs for two teams and put dummy values for the rest.
config, _ = ap.generateAccessPointConfig(&model.Team{Id: 254, WpaKey: "aaaaaaaa"}, nil, nil, nil, nil,
&model.Team{Id: 1114, WpaKey: "bbbbbbbb"})
disableds = disabledRe.FindAllStringSubmatch(config, -1)
@@ -42,9 +44,9 @@ func TestConfigureAccessPoint(t *testing.T) {
assert.Equal(t, "254", ssids[0][1])
assert.Equal(t, "aaaaaaaa", wpaKeys[0][1])
for i := 1; i < 5; i++ {
assert.Equal(t, "1", disableds[i][1])
assert.Equal(t, "", ssids[i][1])
assert.Equal(t, "", wpaKeys[i][1])
assert.Equal(t, "0", disableds[i][1])
assert.Equal(t, fmt.Sprintf("no-team-%d", i+1), ssids[i][1])
assert.Equal(t, fmt.Sprintf("no-team-%d", i+1), wpaKeys[i][1])
}
assert.Equal(t, "0", disableds[5][1])
assert.Equal(t, "1114", ssids[5][1])
@@ -83,3 +85,55 @@ func TestConfigureAccessPoint(t *testing.T) {
assert.Contains(t, err.Error(), "Invalid WPA key")
}
}
func TestDecodeWifiInfo(t *testing.T) {
var statuses [6]TeamWifiStatus
// Test with zero team networks configured.
output, err := ioutil.ReadFile("testdata/iwinfo_0_teams.txt")
if assert.Nil(t, err) {
assert.Nil(t, decodeWifiInfo(string(output), statuses[:]))
assertTeamWifiStatus(t, 0, false, statuses[0])
assertTeamWifiStatus(t, 0, false, statuses[1])
assertTeamWifiStatus(t, 0, false, statuses[2])
assertTeamWifiStatus(t, 0, false, statuses[3])
assertTeamWifiStatus(t, 0, false, statuses[4])
assertTeamWifiStatus(t, 0, false, statuses[5])
}
// Test with two team networks configured.
output, err = ioutil.ReadFile("testdata/iwinfo_2_teams.txt")
if assert.Nil(t, err) {
assert.Nil(t, decodeWifiInfo(string(output), statuses[:]))
assertTeamWifiStatus(t, 0, false, statuses[0])
assertTeamWifiStatus(t, 2471, true, statuses[1])
assertTeamWifiStatus(t, 0, false, statuses[2])
assertTeamWifiStatus(t, 254, false, statuses[3])
assertTeamWifiStatus(t, 0, false, statuses[4])
assertTeamWifiStatus(t, 0, false, statuses[5])
}
// Test with six team networks configured.
output, err = ioutil.ReadFile("testdata/iwinfo_6_teams.txt")
if assert.Nil(t, err) {
assert.Nil(t, decodeWifiInfo(string(output), statuses[:]))
assertTeamWifiStatus(t, 254, false, statuses[0])
assertTeamWifiStatus(t, 1678, false, statuses[1])
assertTeamWifiStatus(t, 2910, true, statuses[2])
assertTeamWifiStatus(t, 604, false, statuses[3])
assertTeamWifiStatus(t, 8, false, statuses[4])
assertTeamWifiStatus(t, 2471, true, statuses[5])
}
// Test with invalid input.
assert.NotNil(t, decodeWifiInfo("", statuses[:]))
output, err = ioutil.ReadFile("testdata/iwinfo_invalid.txt")
if assert.Nil(t, err) {
assert.NotNil(t, decodeWifiInfo(string(output), statuses[:]))
}
}
func assertTeamWifiStatus(t *testing.T, expectedTeamId int, expectedRadioLinked bool, status TeamWifiStatus) {
assert.Equal(t, expectedTeamId, status.TeamId)
assert.Equal(t, expectedRadioLinked, status.RadioLinked)
}

91
network/testdata/iwinfo_0_teams.txt vendored Normal file
View File

@@ -0,0 +1,91 @@
wlan0 ESSID: "no-team-1"
Access Point: 00:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-1 ESSID: "no-team-2"
Access Point: 02:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-2 ESSID: "no-team-3"
Access Point: 06:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-3 ESSID: "no-team-4"
Access Point: 0A:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-4 ESSID: "no-team-5"
Access Point: 0E:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-5 ESSID: "no-team-6"
Access Point: 12:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan1 ESSID: unknown
Access Point: 00:00:00:00:00:00
Mode: Client Channel: unknown (unknown)
Tx-Power: unknown Link Quality: unknown/70
Signal: unknown Noise: unknown
Bit Rate: unknown
Encryption: unknown
Type: nl80211 HW Mode(s): 802.11bgn
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy1

91
network/testdata/iwinfo_2_teams.txt vendored Normal file
View File

@@ -0,0 +1,91 @@
wlan0 ESSID: "no-team-1"
Access Point: 00:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-1 ESSID: "2471"
Access Point: 02:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: 2/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-2 ESSID: "no-team-3"
Access Point: 06:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-3 ESSID: "254"
Access Point: 0A:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-4 ESSID: "no-team-5"
Access Point: 0E:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-5 ESSID: "no-team-6"
Access Point: 12:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -94 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan1 ESSID: unknown
Access Point: 00:00:00:00:00:00
Mode: Client Channel: unknown (unknown)
Tx-Power: unknown Link Quality: unknown/70
Signal: unknown Noise: unknown
Bit Rate: unknown
Encryption: unknown
Type: nl80211 HW Mode(s): 802.11bgn
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy1

91
network/testdata/iwinfo_6_teams.txt vendored Normal file
View File

@@ -0,0 +1,91 @@
wlan0 ESSID: "254"
Access Point: 00:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-1 ESSID: "1678"
Access Point: 02:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-2 ESSID: "2910"
Access Point: 06:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: 23/70
Signal: -85 dBm Noise: -96 dBm
Bit Rate: 6.0 MBit/s
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-3 ESSID: "604"
Access Point: 0A:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-4 ESSID: "8"
Access Point: 0E:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-5 ESSID: "2471"
Access Point: 12:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: 55/70
Signal: -55 dBm Noise: -96 dBm
Bit Rate: 6.0 MBit/s
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan1 ESSID: unknown
Access Point: 00:00:00:00:00:00
Mode: Client Channel: unknown (unknown)
Tx-Power: unknown Link Quality: unknown/70
Signal: unknown Noise: unknown
Bit Rate: unknown
Encryption: unknown
Type: nl80211 HW Mode(s): 802.11bgn
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy1

65
network/testdata/iwinfo_invalid.txt vendored Normal file
View File

@@ -0,0 +1,65 @@
wlan0 ESSID: "254"
Access Point: 00:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-1 ESSID: "1678"
Access Point: 02:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-2 ESSID: "2910"
Access Point: 06:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: 23/70
Signal: -85 dBm Noise: -96 dBm
Bit Rate: 6.0 MBit/s
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan0-3 ESSID: "604"
Access Point: 0A:25:9C:14:65:8A
Mode: Master Channel: 161 (5.805 GHz)
Tx-Power: 23 dBm Link Quality: unknown/70
Signal: unknown Noise: -96 dBm
Bit Rate: unknown
Encryption: WPA2 PSK (CCMP)
Type: nl80211 HW Mode(s): 802.11nac
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy0
wlan1 ESSID: unknown
Access Point: 00:00:00:00:00:00
Mode: Client Channel: unknown (unknown)
Tx-Power: unknown Link Quality: unknown/70
Signal: unknown Noise: unknown
Bit Rate: unknown
Encryption: unknown
Type: nl80211 HW Mode(s): 802.11bgn
Hardware: 11AB:2A55 11AB:0000 [Marvell 88W8864]
TX power offset: none
Frequency offset: none
Supports VAPs: yes PHY name: phy1

View File

@@ -46,7 +46,7 @@
.modal-large {
width: 60%;
}
.ds-status, .radio-status, .robot-status, .battery-status, .bypass-status, .trip-time, .packet-loss {
.ds-status, .radio-status, .robot-status, .bypass-status, .trip-time, .packet-loss {
background-color: #aaa;
color: #000;
border: 1px solid #999;

View File

@@ -5,6 +5,7 @@
var websocket;
var scoreIsReady;
var lowBatteryThreshold = 6;
// Sends a websocket message to load a team into an alliance station.
var substituteTeam = function(team, position) {
@@ -72,30 +73,42 @@ var confirmCommit = function(isReplay) {
var handleArenaStatus = function(data) {
// Update the team status view.
$.each(data.AllianceStations, function(station, stationStatus) {
var wifiStatus = data.TeamWifiStatuses[station];
$("#status" + station + " .radio-status").text(wifiStatus.TeamId);
if (stationStatus.DsConn) {
// Format the driver station status box.
var dsConn = stationStatus.DsConn;
$("#status" + station + " .ds-status").attr("data-status-ok", dsConn.DsLinked);
$("#status" + station + " .robot-status").attr("data-status-ok", dsConn.RobotLinked);
// Format the radio status box according to the connection status of the robot radio.
var radioOkay = stationStatus.Team && stationStatus.Team.Id === wifiStatus.TeamId && wifiStatus.RadioLinked;
$("#status" + station + " .radio-status").attr("data-status-ok", radioOkay);
// Format the robot status box.
var robotOkay = dsConn.BatteryVoltage > lowBatteryThreshold && dsConn.RobotLinked;
$("#status" + station + " .robot-status").attr("data-status-ok", robotOkay);
if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) {
$("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed());
} else {
$("#status" + station + " .robot-status").text("");
$("#status" + station + " .robot-status").text(dsConn.BatteryVoltage.toFixed(1) + "V");
}
var lowBatteryThreshold = 6;
if (matchStates[data.MatchState] === "PRE_MATCH" || matchStates[data.MatchState] === "TIMEOUT_ACTIVE" ||
matchStates[data.MatchState] === "POST_TIMEOUT") {
lowBatteryThreshold = 12;
}
$("#status" + station + " .battery-status").attr("data-status-ok",
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("");
$("#status" + station + " .robot-status").attr("data-status-ok", "");
$("#status" + station + " .robot-status").text("");
$("#status" + station + " .battery-status").attr("data-status-ok", "");
$("#status" + station + " .battery-status").text("");
// Format the robot status box according to whether the AP is configured with the correct SSID.
var expectedTeamId = stationStatus.Team ? stationStatus.Team.Id : 0;
if (wifiStatus.TeamId === expectedTeamId) {
if (wifiStatus.RadioLinked) {
$("#status" + station + " .radio-status").attr("data-status-ok", true);
} else {
$("#status" + station + " .radio-status").attr("data-status-ok", "");
}
} else {
$("#status" + station + " .radio-status").attr("data-status-ok", false);
}
}
if (stationStatus.Estop) {

View File

@@ -63,9 +63,9 @@
<div class="col-lg-6 well well-darkblue">
<div class="row form-group">
<div class="col-lg-4">Blue Teams</div>
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station (Tx/Rx Mbits/s)">DS</div>
<div class="col-lg-2" data-toggle="tooltip" title="Robot">R</div>
<div class="col-lg-2" data-toggle="tooltip" title="Battery">B</div>
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station">DS</div>
<div class="col-lg-2" data-toggle="tooltip" title="Radio (AP Configured SSID)">Rad</div>
<div class="col-lg-2" data-toggle="tooltip" title="Robot">Rbt</div>
<div class="col-lg-2" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
</div>
{{template "matchPlayTeam" dict "team" .Match.Blue1 "color" "B" "position" 1 "data" .}}
@@ -75,9 +75,9 @@
<div class="col-lg-6 well well-darkred">
<div class="row form-group">
<div class="col-lg-4">Red Teams</div>
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station (Tx/Rx Mbits/s)">DS</div>
<div class="col-lg-2" data-toggle="tooltip" title="Robot">R</div>
<div class="col-lg-2" data-toggle="tooltip" title="Battery">B</div>
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station">DS</div>
<div class="col-lg-2" data-toggle="tooltip" title="Radio (AP Configured SSID)">Rad</div>
<div class="col-lg-2" data-toggle="tooltip" title="Robot">Rbt</div>
<div class="col-lg-2" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
</div>
{{template "matchPlayTeam" dict "team" .Match.Red3 "color" "R" "position" 3 "data" .}}
@@ -271,8 +271,8 @@
{{if not .data.AllowSubstitution}}disabled{{end}}>
</div>
<div class="col-lg-2 col-no-padding"><div class="ds-status"></div></div>
<div class="col-lg-2 col-no-padding"><div class="radio-status"></div></div>
<div class="col-lg-2 col-no-padding"><div class="robot-status"></div></div>
<div class="col-lg-2 col-no-padding"><div class="battery-status"></div></div>
<div class="col-lg-2 col-no-padding">
<div class="bypass-status" onclick="toggleBypass('{{.color}}{{.position}}');"></div>
</div>