gluon-core: add outdoor support for 5 ghz radios

Add the `wifi5.outdoor_chanlist` site configuration that
allows specifying an outdoor channel range that can be
switched to for regulatory compliance.

Upon enabling the outdoor option the device will
 - configure the `outdoor_chanlist` on all 5 GHz radios
 - which may enable DFS/TPC, based on the regulatory domain
 - disable ibss/mesh on the 5 GHz radio, as DFS *will*
   break mesh connections
 - allow for htmode reconfiguration on 5 GHz radios

The outdoor option can be toggled from
 - Advanced Settings
   - W-LAN
     - Outdoor Installation

The `preserve_channel` flag overrules the outdoor channel
selection.
This commit is contained in:
Martin Weinelt 2018-02-11 13:54:06 +01:00
parent 4f60f6dbc6
commit bf55249159
8 changed files with 186 additions and 28 deletions

View File

@ -61,6 +61,7 @@
-- for channel.
wifi5 = {
channel = 44,
outdoor_chanlist = '100-140',
ap = {
ssid = 'alpha-centauri.freifunk.net',
},

View File

@ -166,6 +166,21 @@ wifi24 \: optional
wifi5 \: optional
Same as `wifi24` but for the 5Ghz radio.
Additionally a range of channels that are safe to use outsides on the 5 GHz band can
be set up through ``outdoor_chanlist``, which allows for a space-seperated list of
channels and channel ranges, seperated by a hyphen.
When set this offers the outdoor mode flag for 5 GHz radios in the config mode which
reconfigures the AP to select its channel from outdoor chanlist, while respecting
regulatory specifications, and disables mesh on that radio.
::
wifi5 = {
channel = 44,
outdoor_chanlist = "100-140",
[...]
},
next_node \: package
Configuration of the local node feature of Gluon
::

View File

@ -34,6 +34,9 @@ for _, config in ipairs({'wifi24', 'wifi5'}) do
need_string(in_site({'regdom'})) -- regdom is only required when wifi24 or wifi5 is configured
need_number({config, 'channel'})
if config == 'wifi5' then
need_string_match({config, 'outdoor_chanlist'}, '^[%d%s-]+$', false)
end
obsolete({config, 'supported_rates'}, '802.11b rates are disabled by default.')
obsolete({config, 'basic_rate'}, '802.11b rates are disabled by default.')

View File

@ -49,22 +49,37 @@ if not sysconfig.gluon_version then
end)
end
local function is_outdoor()
return uci:get_bool('gluon', 'wireless', 'outdoor')
end
local function get_channel(radio, config)
local channel
if uci:get_first('gluon-core', 'wireless', 'preserve_channels') then
-- preserved channel always wins
channel = radio.channel
elseif (radio.hwmode == '11a' or radio.hwmode == '11na') and is_outdoor() then
-- actual channel will be picked and probed from chanlist
channel = 'auto'
end
return channel or config.channel()
end
local function get_htmode(radio)
local phy = util.find_phy(radio)
if iwinfo.nl80211.hwmodelist(phy).ac then
return 'VHT20'
else
return 'HT20'
end
if (radio.hwmode == '11a' or radio.hwmode == '11na') and is_outdoor() then
local outdoor_htmode = uci:get('gluon', 'wireless', 'outdoor_' .. radio['.name'] .. '_htmode')
if outdoor_htmode ~= nil then
return outdoor_htmode
end
end
local phy = util.find_phy(radio)
if iwinfo.nl80211.hwmodelist(phy).ac then
return 'VHT20'
end
return 'HT20'
end
local function is_disabled(name)
@ -207,31 +222,49 @@ util.foreach_radio(uci, function(radio, index, config)
uci:set('wireless', radio_name, 'htmode', htmode)
uci:set('wireless', radio_name, 'country', site.regdom())
local hwmode = radio.hwmode
if hwmode == '11g' or hwmode == '11ng' then
uci:set('wireless', radio_name, 'legacy_rates', false)
end
uci:delete('wireless', radio_name, 'supported_rates')
uci:delete('wireless', radio_name, 'basic_rate')
local ibss_disabled = is_disabled('ibss_' .. radio_name)
local mesh_disabled = is_disabled('mesh_' .. radio_name)
local hwmode = radio.hwmode
if hwmode == '11g' or hwmode == '11ng' then
uci:set('wireless', radio_name, 'legacy_rates', false)
elseif (hwmode == '11a' or hwmode == '11na') then
if is_outdoor() then
uci:set('wireless', radio_name, 'channels', config.outdoor_chanlist())
configure_ibss(config.ibss(), radio, index, suffix,
first_non_nil(
ibss_disabled,
mesh_disabled,
config.ibss.disabled(false)
)
)
configure_mesh(config.mesh(), radio, index, suffix,
first_non_nil(
mesh_disabled,
ibss_disabled,
config.mesh.disabled(false)
)
)
-- enforce outdoor channels by filtering the regdom for outdoor channels
local hostapd_options = uci:get_list('wireless', radio_name, 'hostapd_options')
util.add_to_set(hostapd_options, 'country3=0x4f')
uci:set_list('wireless', radio_name, 'hostapd_options', hostapd_options)
uci:delete('wireless', 'ibss_' .. radio_name)
uci:delete('wireless', 'mesh_' .. radio_name)
else
uci:delete('wireless', radio_name, 'channels')
local hostapd_options = uci:get_list('wireless', radio_name, 'hostapd_options')
util.remove_from_set(hostapd_options, 'country3=0x4f')
uci:set_list('wireless', radio_name, 'hostapd_options', hostapd_options)
local ibss_disabled = is_disabled('ibss_' .. radio_name)
local mesh_disabled = is_disabled('mesh_' .. radio_name)
configure_ibss(config.ibss(), radio, index, suffix,
first_non_nil(
ibss_disabled,
mesh_disabled,
config.ibss.disabled(false)
)
)
configure_mesh(config.mesh(), radio, index, suffix,
first_non_nil(
mesh_disabled,
ibss_disabled,
config.mesh.disabled(false)
)
)
end
end
fixup_wan(radio, index)
end)

View File

@ -49,3 +49,25 @@ msgstr ""
"werden. Wenn möglich, ist in den Werten der Sendeleistung der Antennengewinn "
"enthalten; diese Werte sind allerdings für viele Geräte nicht verfügbar oder "
"fehlerhaft."
msgid "Outdoor installation"
msgstr "Outdoor-Installation"
msgid "Node will be installed outdoors"
msgstr "Knoten wird im Außenbereich betrieben"
msgid ""
"Configuring the node for outdoor use tunes the 5 GHz radio to a frequency "
"and transmission power that conforms with the local regulatory requirements. "
"It also enables dynamic frequency selection (DFS; radar detection). At the "
"same time, mesh functionality is disabled as it requires neighbouring nodes "
"to stay on the same channel permanently."
msgstr ""
"Ist der Knoten für den Einsatz im Freien konfiguriert, wird ein WLAN-Kanal auf "
"dem 5-GHz-Band sowie eine Sendeleistung entsprechend den gesetzlichen "
"Frequenzregulatorien gewählt. Gleichzeitig wird die dynamische Frequenzwahl "
"(DFS; Radarerkennung) aktiviert und die Mesh-Funktionalität deaktiviert, da "
"sich Nachbarknoten dauerhaft auf demselben Kanal befinden müssen."
msgid "HT Mode"
msgstr "HT-Modus"

View File

@ -46,3 +46,9 @@ msgstr ""
"<br /><br />Ici vous pouvez aussi configurer la puissance d'émmission se votre Wi-Fi. "
"Prenez note que les valeurs fournies pour la puissance de transmission prennent "
"en compte les gains fournis par l'antenne, et que ces valeurs ne sont pas toujours disponibles ou exactes."
msgid "Outdoor installation"
msgstr "Installation extérieure"
msgid "HT Mode"
msgstr "Mode HT"

View File

@ -33,3 +33,20 @@ msgid ""
"values include the antenna gain where available, but there are many devices "
"for which the gain is unavailable or inaccurate."
msgstr ""
msgid "Outdoor installation"
msgstr ""
msgid "Node will be installed outdoors"
msgstr ""
msgid ""
"Configuring the node for outdoor use tunes the 5 GHz radio to a frequency "
"and transmission power that conforms with the local regulatory requirements. "
"It also enables dynamic frequency selection (DFS; radar detection). At the "
"same time, mesh functionality is disabled as it requires neighbouring nodes "
"to stay on the same channel permanently."
msgstr ""
msgid "HT Mode"
msgstr ""

View File

@ -1,4 +1,5 @@
local iwinfo = require 'iwinfo'
local site = require 'gluon.site'
local uci = require("simple-uci").cursor()
local util = require 'gluon.util'
@ -8,7 +9,6 @@ local function txpower_list(phy)
local off = tonumber(iwinfo.nl80211.txpower_offset(phy)) or 0
local new = { }
local prev = -1
local _, val
for _, val in ipairs(list) do
local dbm = val.dbm + off
local mw = math.floor(10 ^ (dbm / 10))
@ -24,6 +24,17 @@ local function txpower_list(phy)
return new
end
local function has_5ghz_radio()
local result = false
uci:foreach('wireless', 'wifi-device', function(config)
local radio = config['.name']
local hwmode = uci:get('wireless', radio, 'hwmode')
result = result or (hwmode == '11a' or hwmode == '11na')
end)
return result
end
local f = Form(translate("WLAN"))
@ -97,7 +108,57 @@ uci:foreach('wireless', 'wifi-device', function(config)
end
end)
if has_5ghz_radio() then
local r = f:section(Section, translate("Outdoor Installation"), translate(
"Configuring the node for outdoor use tunes the 5 GHz radio to a frequency "
.. "and transmission power that conforms with the local regulatory requirements. "
.. "It also enables dynamic frequency selection (DFS; radar detection). At the "
.. "same time, mesh functionality is disabled as it requires neighbouring nodes "
.. "to stay on the same channel permanently."
))
local outdoor = r:option(Flag, 'outdoor', translate("Node will be installed outdoors"))
outdoor.default = uci:get_bool('gluon', 'wireless', 'outdoor')
function outdoor:write(data)
uci:set('gluon', 'wireless', 'outdoor', data)
end
uci:foreach('wireless', 'wifi-device', function(config)
local radio = config['.name']
local hwmode = uci:get('wireless', radio, 'hwmode')
if hwmode ~= '11a' and hwmode ~= '11na' then
return
end
local phy = util.find_phy(uci:get_all('wireless', radio))
local ht = r:option(ListValue, 'outdoor_htmode', translate('HT Mode') .. ' (' .. radio .. ')')
ht:depends(outdoor, true)
ht.default = uci.get('gluon', 'wireless', 'outdoor_' .. radio .. '_htmode') or 'default'
ht:value('default', translate("(default)"))
for mode, available in pairs(iwinfo.nl80211.htmodelist(phy)) do
if available then
ht:value(mode, mode)
end
end
function ht:write(data)
if data == 'default' then
data = nil
end
uci:set('gluon', 'wireless', 'outdoor_' .. radio .. '_htmode', data)
end
end)
end
function f:write()
uci:commit('gluon')
os.execute('/lib/gluon/upgrade/200-wireless')
uci:commit('wireless')
end