diff --git a/bandwidth_monitor.go b/bandwidth_monitor.go new file mode 100644 index 0000000..1fb0927 --- /dev/null +++ b/bandwidth_monitor.go @@ -0,0 +1,108 @@ +// Copyright 2015 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Methods for monitoring team bandwidth usage across a managed switch. + +package main + +import ( + "fmt" + "github.com/cdevr/WapSNMP" + "log" + "time" +) + +const ( + monitoringIntervalMs = 1000 + toRobotBytesOid = ".1.3.6.1.2.1.2.2.1.10" + fromRobotBytesOid = ".1.3.6.1.2.1.2.2.1.16" + red1Port = 2 + red2Port = 4 + red3Port = 6 + blue1Port = 8 + blue2Port = 10 + blue3Port = 12 +) + +type BandwidthMonitor struct { + snmpClient *wapsnmp.WapSNMP + toRobotOid wapsnmp.Oid + fromRobotOid wapsnmp.Oid + lastToRobotBytes map[string]interface{} + lastFromRobotBytes map[string]interface{} + lastBytesTime time.Time +} + +// Loops indefinitely to query the managed switch via SNMP (Simple Network Management Protocol). +func MonitorBandwidth() { + monitor := BandwidthMonitor{toRobotOid: wapsnmp.MustParseOid(toRobotBytesOid), + fromRobotOid: wapsnmp.MustParseOid(fromRobotBytesOid)} + for { + if eventSettings.NetworkSecurityEnabled && eventSettings.BandwidthMonitoringEnabled { + err := monitor.updateBandwidth() + if err != nil { + log.Printf("Bandwidth monitoring error: %s", err) + } + } + time.Sleep(time.Millisecond * monitoringIntervalMs) + } +} + +func (monitor *BandwidthMonitor) updateBandwidth() error { + if monitor.snmpClient != nil && monitor.snmpClient.Target != eventSettings.SwitchAddress { + // Switch address has changed; must re-create the SNMP client. + monitor.snmpClient.Close() + monitor.snmpClient = nil + } + + if monitor.snmpClient == nil { + var err error + monitor.snmpClient, err = wapsnmp.NewWapSNMP(eventSettings.SwitchAddress, eventSettings.SwitchPassword, + wapsnmp.SNMPv2c, 2*time.Second, 0) + if err != nil { + return err + } + } + + // Retrieve total number of bytes sent/received per port. + toRobotBytes, err := monitor.snmpClient.GetTable(monitor.toRobotOid) + if err != nil { + return err + } + fromRobotBytes, err := monitor.snmpClient.GetTable(monitor.fromRobotOid) + if err != nil { + return err + } + + // Calculate the bandwidth usage over time. + monitor.updateStationBandwidth("R1", red1Port, toRobotBytes, fromRobotBytes) + monitor.updateStationBandwidth("R2", red2Port, toRobotBytes, fromRobotBytes) + monitor.updateStationBandwidth("R3", red3Port, toRobotBytes, fromRobotBytes) + monitor.updateStationBandwidth("B1", blue1Port, toRobotBytes, fromRobotBytes) + monitor.updateStationBandwidth("B2", blue2Port, toRobotBytes, fromRobotBytes) + monitor.updateStationBandwidth("B3", blue3Port, toRobotBytes, fromRobotBytes) + + monitor.lastToRobotBytes = toRobotBytes + monitor.lastFromRobotBytes = fromRobotBytes + monitor.lastBytesTime = time.Now() + return nil +} + +func (monitor *BandwidthMonitor) updateStationBandwidth(station string, port int, toRobotBytes map[string]interface{}, + fromRobotBytes map[string]interface{}) { + dsConn := mainArena.AllianceStations[station].DsConn + if dsConn == nil { + // 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 + + 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 +} diff --git a/db/migrations/20140524160241_CreateEventSettings.sql b/db/migrations/20140524160241_CreateEventSettings.sql index a441990..e02db9a 100644 --- a/db/migrations/20140524160241_CreateEventSettings.sql +++ b/db/migrations/20140524160241_CreateEventSettings.sql @@ -19,7 +19,8 @@ CREATE TABLE event_settings ( apusername VARCHAR(255), appassword VARCHAR(255), switchaddress VARCHAR(255), - switchpassword VARCHAR(255) + switchpassword VARCHAR(255), + bandwidthmonitoringenabled bool ); -- +goose Down diff --git a/driver_station_connection.go b/driver_station_connection.go index 781809f..e136172 100644 --- a/driver_station_connection.go +++ b/driver_station_connection.go @@ -32,6 +32,8 @@ type DriverStationStatus struct { PacketCount int MissedPacketCount int DsRobotTripTimeMs int + MBpsToRobot float64 + MBpsFromRobot float64 } type DriverStationConnection struct { @@ -71,6 +73,8 @@ func (dsConn *DriverStationConnection) Update() error { dsConn.DriverStationStatus.DsLinked = false dsConn.DriverStationStatus.RobotLinked = false dsConn.DriverStationStatus.BatteryVoltage = 0 + dsConn.DriverStationStatus.MBpsToRobot = 0 + dsConn.DriverStationStatus.MBpsFromRobot = 0 } return nil diff --git a/event_settings.go b/event_settings.go index 03578b2..7af9ca7 100644 --- a/event_settings.go +++ b/event_settings.go @@ -6,26 +6,27 @@ package main type EventSettings struct { - Id int - Name string - Code string - DisplayBackgroundColor string - NumElimAlliances int - SelectionRound2Order string - SelectionRound3Order string - TeamInfoDownloadEnabled bool - RedGoalLightsAddress string - BlueGoalLightsAddress string - TbaPublishingEnabled bool - TbaEventCode string - TbaSecretId string - TbaSecret string - NetworkSecurityEnabled bool - ApAddress string - ApUsername string - ApPassword string - SwitchAddress string - SwitchPassword string + Id int + Name string + Code string + DisplayBackgroundColor string + NumElimAlliances int + SelectionRound2Order string + SelectionRound3Order string + TeamInfoDownloadEnabled bool + RedGoalLightsAddress string + BlueGoalLightsAddress string + TbaPublishingEnabled bool + TbaEventCode string + TbaSecretId string + TbaSecret string + NetworkSecurityEnabled bool + ApAddress string + ApUsername string + ApPassword string + SwitchAddress string + SwitchPassword string + BandwidthMonitoringEnabled bool } const eventSettingsId = 0 diff --git a/main.go b/main.go index df63d97..6476799 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ func main() { listener, err := DsPacketListener() checkErr(err) go ListenForDsPackets(listener) + go MonitorBandwidth() mainArena.Setup() mainArena.Run() } diff --git a/setup_settings.go b/setup_settings.go index 4a09444..13410cc 100644 --- a/setup_settings.go +++ b/setup_settings.go @@ -55,6 +55,7 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) { eventSettings.ApPassword = r.PostFormValue("apPassword") eventSettings.SwitchAddress = r.PostFormValue("switchAddress") eventSettings.SwitchPassword = r.PostFormValue("switchPassword") + eventSettings.BandwidthMonitoringEnabled = r.PostFormValue("bandwidthMonitoringEnabled") == "on" err := db.SaveEventSettings(eventSettings) if err != nil { handleWebErr(w, err) diff --git a/static/css/cheesy-arena.css b/static/css/cheesy-arena.css index c526dc4..665c37f 100644 --- a/static/css/cheesy-arena.css +++ b/static/css/cheesy-arena.css @@ -52,7 +52,7 @@ border: 1px solid #999; border-radius: 4px; padding: 0px; - width: 45px; + width: 50px; height: 27px; line-height: 25px; font-size: 14px; diff --git a/static/js/fta_display.js b/static/js/fta_display.js index 6a61046..2f18537 100644 --- a/static/js/fta_display.js +++ b/static/js/fta_display.js @@ -14,6 +14,7 @@ var handleStatus = function(data) { $("#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); if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) { $("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed()); @@ -33,6 +34,7 @@ var handleStatus = function(data) { $("#status" + station + " .packet-loss").text(dsStatus.MissedPacketCount); } 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", ""); diff --git a/static/js/match_play.js b/static/js/match_play.js index 57b142c..445fe1b 100644 --- a/static/js/match_play.js +++ b/static/js/match_play.js @@ -64,6 +64,7 @@ var handleStatus = function(data) { 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); if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) { $("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed()); @@ -79,6 +80,7 @@ var handleStatus = function(data) { $("#status" + station + " .battery-status").text(dsStatus.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", ""); diff --git a/switch_config.txt b/switch_config.txt index cd046c2..15bc479 100644 --- a/switch_config.txt +++ b/switch_config.txt @@ -125,10 +125,11 @@ interface FastEthernet0/24 switchport mode access ! interface GigabitEthernet0/1 - switchport mode dynamic desirable + switchport trunk encapsulation dot1q + switchport mode trunk ! interface GigabitEthernet0/2 - switchport mode dynamic desirable + switchport mode access ! interface Vlan1 ip address 10.0.0.61 255.255.255.0 @@ -170,6 +171,8 @@ 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 ! +snmp-server community 1234Five RO +! line con 0 exec-timeout 0 0 line vty 0 4 diff --git a/templates/fta_display.html b/templates/fta_display.html index 85b22e0..69b82b6 100644 --- a/templates/fta_display.html +++ b/templates/fta_display.html @@ -12,7 +12,7 @@