diff --git a/README.md b/README.md index 2a7c506..40eb578 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ $ curl http://10.0.100.2:8081/status }, "red2": null, "red3": null - } + }, + "version": "1.2.3" } ``` A null value for a team station indicates that no team is assigned. @@ -135,7 +136,8 @@ $ curl http://10.12.34.1:8081/status "ssid": "1234", "hashedWpaKey": "d40e29b90743ddf71c75bfaedab1333e23bf43eb29f5c8c1ba55756e96e99d84", "wpaKeySalt": "DzCKbEIu53vCmf0p", - "status": "ACTIVE" + "status": "ACTIVE", + "version": "1.2.3" } ``` See the access point API documentation regarding the `hashedWpaKey` and `wpaKeySalt` fields. diff --git a/radio/radio_ap.go b/radio/radio_ap.go index 3333844..79fc89c 100644 --- a/radio/radio_ap.go +++ b/radio/radio_ap.go @@ -28,6 +28,9 @@ type Radio struct { // Map of team station names to their current status. StationStatuses map[string]*StationStatus `json:"stationStatuses"` + // Version of the radio software. + Version string `json:"version"` + // Queue for receiving and buffering configuration requests. ConfigurationRequestChannel chan ConfigurationRequest `json:"-"` @@ -52,6 +55,7 @@ func NewRadio() *Radio { log.Fatal("Unable to determine radio hardware type; exiting.") } log.Printf("Detected radio hardware type: %v", radio.Type) + radio.determineAndSetVersion() // Initialize the device and station interface names that are dependent on the hardware type. switch radio.Type { diff --git a/radio/radio_ap_test.go b/radio/radio_ap_test.go index bfb380f..70efe26 100644 --- a/radio/radio_ap_test.go +++ b/radio/radio_ap_test.go @@ -74,6 +74,7 @@ func TestNewRadio(t *testing.T) { func TestRadio_isStarted(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() // Radio is not started. @@ -96,6 +97,7 @@ func TestRadio_setInitialState(t *testing.T) { fakeTree.valuesForGet["system.@system[0].model"] = "VH-109(AP)" fakeShell := newFakeShell(t) shell = fakeShell + fakeShell.commandOutput["cat /etc/config/vh_firmware"] = "" radio := NewRadio() fakeTree.valuesForGet["wireless.wifi1.channel"] = "23" @@ -122,6 +124,7 @@ func TestRadio_handleConfigurationRequestVividHosting(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell wifiReloadBackoffDuration = 10 * time.Millisecond + fakeShell.commandOutput["cat /etc/config/vh_firmware"] = "" radio := NewRadio() fakeShell.commandOutput["wifi reload wifi1"] = "" @@ -195,6 +198,7 @@ func TestRadio_handleConfigurationRequestLinksys(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell wifiReloadBackoffDuration = 100 * time.Millisecond + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() fakeShell.commandOutput["wifi reload radio0"] = "" @@ -293,6 +297,7 @@ func TestRadio_handleConfigurationRequestErrors(t *testing.T) { shell = fakeShell retryBackoffDuration = 10 * time.Millisecond wifiReloadBackoffDuration = 10 * time.Millisecond + fakeShell.commandOutput["cat /etc/config/vh_firmware"] = "" radio := NewRadio() // wifi reload fails. @@ -355,11 +360,12 @@ func TestRadio_handleConfigurationRequestErrors(t *testing.T) { func TestRadio_updateStationMonitoring(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() // No teams assigned. radio.updateMonitoring() - assert.Empty(t, fakeShell.commandsRun) + assert.Equal(t, 1, len(fakeShell.commandsRun)) // Some teams assigned. fakeShell.reset() diff --git a/radio/radio_common.go b/radio/radio_common.go index 071c524..0bd1be6 100644 --- a/radio/radio_common.go +++ b/radio/radio_common.go @@ -8,6 +8,7 @@ import ( "log" "math/rand" "regexp" + "strings" "time" ) @@ -99,6 +100,24 @@ func TriggerFirmwareUpdate(firmwarePath string) { log.Println("Started sysupgrade successfully.") } +// determineAndSetVersion determines the firmware version of the radio. +func (radio *Radio) determineAndSetVersion() { + model, _ := uciTree.GetLast("system", "@system[0]", "model") + var version string + var err error + if strings.Contains(model, "VH") { + version, err = shell.runCommand("cat", "/etc/config/vh_firmware") + } else { + version, err = shell.runCommand("sh", "-c", "source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION") + } + if err != nil { + log.Printf("Error determining firmware version: %v", err) + radio.Version = "unknown" + } else { + radio.Version = strings.TrimSpace(version) + } +} + func (radio *Radio) handleConfigurationRequest(request ConfigurationRequest) error { // If there are multiple requests queued up, only consider the latest one. numExtraRequests := len(radio.ConfigurationRequestChannel) diff --git a/radio/radio_common_test.go b/radio/radio_common_test.go index b8fe5cd..3935133 100644 --- a/radio/radio_common_test.go +++ b/radio/radio_common_test.go @@ -21,3 +21,43 @@ func TestTriggerFirmwareUpdate(t *testing.T) { TriggerFirmwareUpdate("some-file") assert.Contains(t, fakeShell.commandsRun, "sysupgrade -n some-file") } + +func TestRadio_determineAndSetVersion(t *testing.T) { + fakeTree := newFakeUciTree() + uciTree = fakeTree + fakeShell := newFakeShell(t) + shell = fakeShell + + // Vivid-Hosting success case. + fakeTree.valuesForGet["system.@system[0].model"] = "VH-109(AP)" + fakeShell.commandOutput["cat /etc/config/vh_firmware"] = "\tVH version 1.2.3 \n" + radio := Radio{} + radio.determineAndSetVersion() + assert.Equal(t, "VH version 1.2.3", radio.Version) + + // Vivid-Hosting error case. + fakeTree.reset() + fakeTree.valuesForGet["system.@system[0].model"] = "VH-109(AP)" + fakeShell.reset() + fakeShell.commandErrors["cat /etc/config/vh_firmware"] = errors.New("oops") + radio = Radio{} + radio.determineAndSetVersion() + assert.Equal(t, "unknown", radio.Version) + + // Linksys success case. + fakeTree.reset() + fakeTree.valuesForGet["system.@system[0].model"] = "" + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "\tLinksys v2.3.4 \n" + radio = Radio{} + radio.determineAndSetVersion() + assert.Equal(t, "Linksys v2.3.4", radio.Version) + + // Linksys error case. + fakeTree.reset() + fakeTree.valuesForGet["system.@system[0].model"] = "" + fakeShell.reset() + fakeShell.commandErrors["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = errors.New("oops") + radio = Radio{} + radio.determineAndSetVersion() + assert.Equal(t, "unknown", radio.Version) +} diff --git a/radio/radio_robot.go b/radio/radio_robot.go index 0e95160..ba69a43 100644 --- a/radio/radio_robot.go +++ b/radio/radio_robot.go @@ -48,6 +48,9 @@ type Radio struct { // Enum representing the current configuration stage of the radio. Status radioStatus `json:"status"` + // Version of the radio software. + Version string `json:"version"` + // Queue for receiving and buffering configuration requests. ConfigurationRequestChannel chan ConfigurationRequest `json:"-"` } @@ -66,10 +69,13 @@ const ( // NewRadio creates a new Radio instance and initializes its fields to default values. func NewRadio() *Radio { - return &Radio{ + radio := Radio{ Status: statusBooting, ConfigurationRequestChannel: make(chan ConfigurationRequest, configurationRequestBufferSize), } + radio.determineAndSetVersion() + + return &radio } // isStarted returns true if the Wi-Fi interface is up and running. diff --git a/radio/radio_robot_test.go b/radio/radio_robot_test.go index 4a2c67a..d57504c 100644 --- a/radio/radio_robot_test.go +++ b/radio/radio_robot_test.go @@ -20,6 +20,7 @@ func TestNewRadio(t *testing.T) { func TestRadio_isStarted(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() // Radio is not started. @@ -40,6 +41,9 @@ func TestRadio_setInitialState(t *testing.T) { rand.Seed(0) fakeTree := newFakeUciTree() uciTree = fakeTree + fakeShell := newFakeShell(t) + shell = fakeShell + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() fakeTree.valuesForGet["wireless.@wifi-iface[0].ssid"] = "12345" @@ -78,6 +82,7 @@ func TestRadio_handleConfigurationRequest(t *testing.T) { fakeShell := newFakeShell(t) shell = fakeShell wifiReloadBackoffDuration = 10 * time.Millisecond + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() // Configure to team radio mode. @@ -166,6 +171,7 @@ func TestRadio_handleConfigurationRequestErrors(t *testing.T) { shell = fakeShell retryBackoffDuration = 10 * time.Millisecond wifiReloadBackoffDuration = 10 * time.Millisecond + fakeShell.commandOutput["sh -c source /etc/openwrt_release && echo $DISTRIB_DESCRIPTION"] = "" radio := NewRadio() // wifi reload fails.