diff --git a/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py b/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py new file mode 100644 index 000000000..c81986921 --- /dev/null +++ b/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py @@ -0,0 +1,155 @@ +# Generated by Django 3.2.20 on 2023-07-10 13:26 + +from django.core.exceptions import ImproperlyConfigured +from django.db import migrations +from netjsonconfig import channels + + +def hwmode_to_band(radio): + """ + This function is adapted from + netjsonconfig.backends.openwrt.converters.radios.Radio.__intermediate_band. + + If the configuration defines "band", then no operation is performed. + + If the configuration defines "hwmode", then the value for "band" + is inferred from "hwmode". + + If both "band" and "hwmode" are absent, then value for "band" + is inferred from "protocal" or "channel". + """ + hwmode = radio.pop('hwmode', None) + band = radio.pop('band', None) + if band: + return band + if hwmode: + # Using "hwmode" we can only predict 2GHz and 5GHz radios. + # Support for 802.11ax (2/5/6 GHz) and 802.11ad (60 GHz) + # was added in OpenWrt 21. + if hwmode == '11a': + return '5g' + elif hwmode in ['11b', '11g']: + return '2g' + + channel = radio.get('channel') + protocol = radio.get('protocol') + # Infer radio frequency from protocol if possible + if protocol == '802.11ad': + return '60g' + elif protocol in ['802.11b', '802.11g']: + return '2g' + elif protocol in ['802.11a', '802.11ac']: + return '5g' + # Infer radio frequency from channel of the radio + if channel in channels.channels_2ghz: + return '2g' + elif channel in channels.channels_5ghz: + return '5g' + elif channel in channels.channels_6ghz: + return '6g' + + +def band_to_hwmode(radio): + """ + This function is adapted from + netjsonconfig.backends.openwrt.converters.radios.Radio.__intermediate_hwmode. + + Returns value for "hwmode" option (OpenWrt < 21) + + If the configuration define "band" (introduced in OpenWrt 21), + then the value for "hwmode" is inferred from "band". + """ + hwmode = radio.pop('hwmode', None) + band = radio.pop('band', None) + if hwmode: + return hwmode + if band: + # 802.11ax and 802.11ad were not supported in OpenWrt < 21. + # Hence, we ignore "6g" and "60g" values. + if band == '2g': + if radio['protocol'] == '802.11b': + return '11b' + else: + return '11g' + elif band == '5g': + return '11a' + # Use protocol to infer "hwmode" + protocol = radio['protocol'] + if protocol in ['802.11a', '802.11b', '802.11g']: + # return 11a, 11b or 11g + return protocol[4:] + if protocol == '802.11ac': + return '11a' + # determine hwmode depending on channel used + if radio['channel'] == 0: + # when using automatic channel selection, we need an + # additional parameter to determine the frequency band + return radio.get('hwmode') + elif radio['channel'] <= 13: + return '11g' + else: + return '11a' + + +def update_config_for_model(Model, func, field): + updated_objects = [] + for obj in Model.objects.iterator(chunk_size=100): + update_obj = False + for radio in obj.config.get('radios', []): + if not radio.get(field): + radio[field] = func(radio) + update_obj = True + if update_obj: + updated_objects.append(obj) + if len(updated_objects) > 100: + Model.objects.bulk_update(updated_objects, fields=['config']) + updated_objects = [] + if len(updated_objects) != 0: + Model.objects.bulk_update(updated_objects, fields=['config']) + + +def forward(apps, schema_editor): + """ + Updates "radio" configuration for OpenWrt backend + to use "band" instead of "hwmode". + """ + if not hasattr(channels, 'channels_6ghz'): + raise ImproperlyConfigured( + 'The installed netjsonconfig package does not support' + ' defining "band" property for "radio" configuration introduced' + ' in OpenWrt 21. Install the latest version of netjsonconfig' + ' and run migrations again.' + ) + Config = apps.get_model('config', 'Config') + Template = apps.get_model('config', 'Template') + + update_config_for_model(Config, hwmode_to_band, 'band') + update_config_for_model(Template, hwmode_to_band, 'band') + + +def reverse(apps, schema_editor): + """ + Updates "radio" configuration for OpenWrt backend + to use "hwmode" instead of "band". + """ + if not hasattr(channels, 'channels_6ghz'): + raise ImproperlyConfigured( + 'The installed netjsonconfig package does not support' + ' defining "band" property for "radio" configuration introduced' + ' in OpenWrt 21. Install the latest version of netjsonconfig' + ' and run migrations again.' + ) + Config = apps.get_model('config', 'Config') + Template = apps.get_model('config', 'Template') + + update_config_for_model(Config, band_to_hwmode, 'hwmode') + update_config_for_model(Template, band_to_hwmode, 'hwmode') + + +class Migration(migrations.Migration): + + dependencies = [ + ('config', '0047_add_organizationlimits'), + ] + + operations = [migrations.RunPython(forward, reverse_code=reverse)] diff --git a/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css b/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css index 51fc2cfe4..d29c73f3c 100644 --- a/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +++ b/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css @@ -305,3 +305,9 @@ div.jsoneditor > div > h3.controls{ .jsoneditor-wrapper div.jsoneditor .grid-column > div > .inline-group > .form-row > .vTextField { padding-left: 100px !important } +.jsoneditor-wrapper div[data-schemapath^="root.radios"][data-schemapath$=".hwmode"] { + display: none; +} +.jsoneditor-wrapper div[data-schemapath^="root.radios"][data-schemapath$=".band"] { + display: none; +} diff --git a/requirements.txt b/requirements.txt index 913420ac0..b40e517f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ django-sortedm2m~=3.1.1 django-reversion~=5.0.4 django-taggit~=4.0.0 -netjsonconfig~=1.0.1 +netjsonconfig @ https://github.com/openwisp/netjsonconfig/tarball/master django-x509 @ https://github.com/openwisp/django-x509/tarball/master django-loci @ https://github.com/openwisp/django-loci/tarball/master django-flat-json-widget @ https://github.com/openwisp/django-flat-json-widget/tarball/master