diff --git a/README.md b/README.md index 40eb578..f1f5c22 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ The `/status` GET endpoint returns the current status of the access point. It re $ curl http://10.0.100.2:8081/status { "channel": 93, + "channelBandwidth": "HT40", "status": "ACTIVE", "stationStatuses": { "blue1": null, @@ -92,6 +93,7 @@ The `/configuration` POST endpoint allows the access point to be configured. It ``` $ curl http://10.0.100.2:8081/configuration -XPOST -d '{ "channel": 93, + "channelBandwidth": "HT20", "stationConfigurations": { "red1": {"ssid": "1111", "wpaKey": "11111111"}, "blue2": {"ssid": "5555", "wpaKey": "55555555"} @@ -105,6 +107,7 @@ The `/status` endpoint can then be polled to check whether the configuration has $ curl http://10.0.100.2:8081/status { "channel": 93, + "channelBandwidth": "HT20", "status": "CONFIGURING", "stationStatuses": { "blue1": null, diff --git a/main.go b/main.go index 09d7969..dcaa126 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "github.com/patfair/frc-radio-api/radio" "github.com/patfair/frc-radio-api/web" "log" @@ -22,9 +23,11 @@ func main() { setupLogging() radio := radio.NewRadio() + fmt.Println("created radio") // Launch the web server in a separate thread. webServer := web.NewWebServer(radio) + fmt.Println("created webserver") go webServer.Run() // Run the radio event loop in the main thread. @@ -50,5 +53,4 @@ func setupLogging() { log.Printf("error opening log file; logging to stdout instead: %v", err) } log.Println("Starting FRC Radio API...") - } diff --git a/radio/configuration_request_ap.go b/radio/configuration_request_ap.go index 79ef073..3cf858c 100644 --- a/radio/configuration_request_ap.go +++ b/radio/configuration_request_ap.go @@ -13,6 +13,10 @@ type ConfigurationRequest struct { // 5GHz or 6GHz channel number for the radio to use. Set to 0 to leave unchanged. Channel int `json:"channel"` + // Channel bandwidth mode for the radio to use. Valid values are "HT20" and "HT40". Set to an empty string to leave + // unchanged. + ChannelBandwidth string `json:"channelBandwidth"` + // SSID and WPA key for each team station, keyed by alliance and number (e.g. "red1", "blue3). If a station is not // included, its network will be disabled by setting its SSID to a placeholder. StationConfigurations map[string]StationConfiguration `json:"stationConfigurations"` @@ -31,7 +35,7 @@ var validLinksysChannels = []int{36, 40, 44, 48, 149, 153, 157, 161, 165} // Validate checks that all parameters within the configuration request have valid values. func (request ConfigurationRequest) Validate(radio *Radio) error { - if request.Channel == 0 && len(request.StationConfigurations) == 0 { + if request.Channel == 0 && request.ChannelBandwidth == "" && len(request.StationConfigurations) == 0 { return errors.New("empty configuration request") } @@ -54,6 +58,16 @@ func (request ConfigurationRequest) Validate(radio *Radio) error { } } + if request.ChannelBandwidth != "" { + // Validate channel bandwidth. + if radio.Type == TypeLinksys { + return fmt.Errorf("channel bandwidth cannot be changed on %s", radio.Type.String()) + } + if request.ChannelBandwidth != "HT20" && request.ChannelBandwidth != "HT40" { + return fmt.Errorf("invalid channel bandwidth: %s", request.ChannelBandwidth) + } + } + // Validate station configurations. for stationName, stationConfiguration := range request.StationConfigurations { stationNameValid := false diff --git a/radio/configuration_request_ap_test.go b/radio/configuration_request_ap_test.go index ddcab23..e8bfcce 100644 --- a/radio/configuration_request_ap_test.go +++ b/radio/configuration_request_ap_test.go @@ -27,6 +27,16 @@ func TestConfigurationRequest_Validate(t *testing.T) { err = request.Validate(vividHostingRadio) assert.EqualError(t, err, "invalid channel for TypeVividHosting: 36") + // Invalid channel bandwidth. + request = ConfigurationRequest{ChannelBandwidth: "HT30"} + err = request.Validate(vividHostingRadio) + assert.EqualError(t, err, "invalid channel bandwidth: HT30") + + // Channel bandwidth not supported on Linksys. + request = ConfigurationRequest{ChannelBandwidth: "HT20"} + err = request.Validate(linksysRadio) + assert.EqualError(t, err, "channel bandwidth cannot be changed on TypeLinksys") + // Invalid station. request = ConfigurationRequest{ StationConfigurations: map[string]StationConfiguration{"red4": {Ssid: "254", WpaKey: "12345678"}}, diff --git a/radio/radio_ap.go b/radio/radio_ap.go index df06c4e..ade2cb7 100644 --- a/radio/radio_ap.go +++ b/radio/radio_ap.go @@ -22,6 +22,9 @@ type Radio struct { // 5GHz or 6GHz channel number the radio is broadcasting on. Channel int `json:"channel"` + // Channel bandwidth mode for the radio to use. Valid values are "HT20" and "HT40". + ChannelBandwidth string `json:"channelWidth"` + // Enum representing the current configuration stage of the radio. Status radioStatus `json:"status"` @@ -109,6 +112,7 @@ func (radio *Radio) isStarted() bool { func (radio *Radio) setInitialState() { channel, _ := uciTree.GetLast("wireless", radio.device, "channel") radio.Channel, _ = strconv.Atoi(channel) + radio.ChannelBandwidth, _ = uciTree.GetLast("wireless", radio.device, "htmode") _ = radio.updateStationStatuses() } @@ -118,6 +122,10 @@ func (radio *Radio) configure(request ConfigurationRequest) error { uciTree.SetType("wireless", radio.device, "channel", uci.TypeOption, strconv.Itoa(request.Channel)) radio.Channel = request.Channel } + if request.ChannelBandwidth != "" { + uciTree.SetType("wireless", radio.device, "htmode", uci.TypeOption, request.ChannelBandwidth) + radio.ChannelBandwidth = request.ChannelBandwidth + } if radio.Type == TypeLinksys { // Clear the state of the radio before loading teams; the Linksys AP is crash-prone otherwise. diff --git a/web/configuration_api_test.go b/web/configuration_api_test.go index 6929b8b..61e0a30 100644 --- a/web/configuration_api_test.go +++ b/web/configuration_api_test.go @@ -11,6 +11,7 @@ import ( func TestWeb_configurationHandler(t *testing.T) { ap := radio.NewRadio() + ap.Type = radio.TypeVividHosting web := NewWebServer(ap) // Empty request should result in an error. @@ -40,6 +41,7 @@ func TestWeb_configurationHandler(t *testing.T) { ` { "channel": 149, + "channelBandwidth": "HT20", "stationConfigurations": { "red1": {"ssid": "9991", "wpaKey": "11111111"}, "red2": {"ssid": "9992", "wpaKey": "22222222"}, @@ -56,6 +58,7 @@ func TestWeb_configurationHandler(t *testing.T) { if assert.Equal(t, 1, len(ap.ConfigurationRequestChannel)) { request := <-ap.ConfigurationRequestChannel assert.Equal(t, 149, request.Channel) + assert.Equal(t, "HT20", request.ChannelBandwidth) assert.Equal(t, 6, len(request.StationConfigurations)) assert.Equal( t, radio.StationConfiguration{Ssid: "9991", WpaKey: "11111111"}, request.StationConfigurations["red1"],