diff --git a/field/arena.go b/field/arena.go index aca1ff3..6aba123 100644 --- a/field/arena.go +++ b/field/arena.go @@ -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 { diff --git a/field/arena_notifiers.go b/field/arena_notifiers.go index 0ce0462..0394cb9 100644 --- a/field/arena_notifiers.go +++ b/field/arena_notifiers.go @@ -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{} { diff --git a/network/access_point.go b/network/access_point.go index 9fe5bfd..2cc4eac 100644 --- a/network/access_point.go +++ b/network/access_point.go @@ -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 < 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 +} diff --git a/network/access_point_test.go b/network/access_point_test.go index 4ac8825..85e5380 100644 --- a/network/access_point_test.go +++ b/network/access_point_test.go @@ -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) +} diff --git a/network/testdata/iwinfo_0_teams.txt b/network/testdata/iwinfo_0_teams.txt new file mode 100644 index 0000000..2b14c4b --- /dev/null +++ b/network/testdata/iwinfo_0_teams.txt @@ -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 + diff --git a/network/testdata/iwinfo_2_teams.txt b/network/testdata/iwinfo_2_teams.txt new file mode 100644 index 0000000..c36988f --- /dev/null +++ b/network/testdata/iwinfo_2_teams.txt @@ -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 + diff --git a/network/testdata/iwinfo_6_teams.txt b/network/testdata/iwinfo_6_teams.txt new file mode 100644 index 0000000..171ad5c --- /dev/null +++ b/network/testdata/iwinfo_6_teams.txt @@ -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 + diff --git a/network/testdata/iwinfo_invalid.txt b/network/testdata/iwinfo_invalid.txt new file mode 100644 index 0000000..8bb7a75 --- /dev/null +++ b/network/testdata/iwinfo_invalid.txt @@ -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 + diff --git a/static/css/cheesy-arena.css b/static/css/cheesy-arena.css index 2c5f237..11d6800 100644 --- a/static/css/cheesy-arena.css +++ b/static/css/cheesy-arena.css @@ -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; diff --git a/static/js/match_play.js b/static/js/match_play.js index 10d0682..74d9fa5 100644 --- a/static/js/match_play.js +++ b/static/js/match_play.js @@ -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) { diff --git a/templates/match_play.html b/templates/match_play.html index 8368c89..63f7521 100644 --- a/templates/match_play.html +++ b/templates/match_play.html @@ -63,9 +63,9 @@
Blue Teams
-
DS
-
R
-
B
+
DS
+
Rad
+
Rbt
Byp
{{template "matchPlayTeam" dict "team" .Match.Blue1 "color" "B" "position" 1 "data" .}} @@ -75,9 +75,9 @@
Red Teams
-
DS
-
R
-
B
+
DS
+
Rad
+
Rbt
Byp
{{template "matchPlayTeam" dict "team" .Match.Red3 "color" "R" "position" 3 "data" .}} @@ -271,8 +271,8 @@ {{if not .data.AllowSubstitution}}disabled{{end}}>
+
-