diff --git a/renderer/halow_channels.uc b/renderer/halow_channels.uc new file mode 100644 index 00000000..0f74b031 --- /dev/null +++ b/renderer/halow_channels.uc @@ -0,0 +1,240 @@ +// HaLow (802.11ah / S1G) per-country channel map + lookup API. +// Maps a HaLow country to its valid S1G channels, operating class and +// centre frequency. Data table followed by the lookup helpers below. + +"use strict"; + +const CHANNEL_MAP = { + "AU": { + country_name: "Australia", + channels: { + "28": { country_code: "AU", bw: 1, s1g_chan: 28, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 916, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 36 }, + "30": { country_code: "AU", bw: 1, s1g_chan: 30, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 917, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 40 }, + "32": { country_code: "AU", bw: 1, s1g_chan: 32, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 918, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 44 }, + "34": { country_code: "AU", bw: 1, s1g_chan: 34, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 919, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 48 }, + "36": { country_code: "AU", bw: 1, s1g_chan: 36, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 920, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 52 }, + "38": { country_code: "AU", bw: 1, s1g_chan: 38, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 921, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 56 }, + "40": { country_code: "AU", bw: 1, s1g_chan: 40, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 922, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 60 }, + "42": { country_code: "AU", bw: 1, s1g_chan: 42, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 923, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 64 }, + "44": { country_code: "AU", bw: 1, s1g_chan: 44, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 924, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 116 }, + "46": { country_code: "AU", bw: 1, s1g_chan: 46, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 925, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 120 }, + "48": { country_code: "AU", bw: 1, s1g_chan: 48, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 926, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 124 }, + "50": { country_code: "AU", bw: 1, s1g_chan: 50, s1g_op_class: 22, global_op_class: 50, centre_freq_mhz: 927, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 128 }, + "29": { country_code: "AU", bw: 2, s1g_chan: 29, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 916.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 38 }, + "33": { country_code: "AU", bw: 2, s1g_chan: 33, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 918.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 46 }, + "37": { country_code: "AU", bw: 2, s1g_chan: 37, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 920.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 54 }, + "41": { country_code: "AU", bw: 2, s1g_chan: 41, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 922.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 62 }, + "45": { country_code: "AU", bw: 2, s1g_chan: 45, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 924.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 118 }, + "49": { country_code: "AU", bw: 2, s1g_chan: 49, s1g_op_class: 23, global_op_class: 51, centre_freq_mhz: 926.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 126 }, + "31": { country_code: "AU", bw: 4, s1g_chan: 31, s1g_op_class: 24, global_op_class: 52, centre_freq_mhz: 917.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 42 }, + "39": { country_code: "AU", bw: 4, s1g_chan: 39, s1g_op_class: 24, global_op_class: 52, centre_freq_mhz: 921.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 58 }, + "47": { country_code: "AU", bw: 4, s1g_chan: 47, s1g_op_class: 24, global_op_class: 52, centre_freq_mhz: 925.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 122 }, + "35": { country_code: "AU", bw: 8, s1g_chan: 35, s1g_op_class: 25, global_op_class: 53, centre_freq_mhz: 919.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 50 }, + "43": { country_code: "AU", bw: 8, s1g_chan: 43, s1g_op_class: 25, global_op_class: 53, centre_freq_mhz: 923.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Australia", tx_power_max: 30, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 114 }, + }, + valid_channels: [ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 ], + default_channel: 35 + }, + "CA": { + country_name: "Canada", + channels: { + "3": { country_code: "CA", bw: 1, s1g_chan: 3, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 903.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 136 }, + "5": { country_code: "CA", bw: 1, s1g_chan: 5, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 904.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 36 }, + "7": { country_code: "CA", bw: 1, s1g_chan: 7, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 905.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 40 }, + "9": { country_code: "CA", bw: 1, s1g_chan: 9, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 906.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 44 }, + "11": { country_code: "CA", bw: 1, s1g_chan: 11, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 907.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 48 }, + "13": { country_code: "CA", bw: 1, s1g_chan: 13, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 908.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 52 }, + "15": { country_code: "CA", bw: 1, s1g_chan: 15, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 909.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 56 }, + "17": { country_code: "CA", bw: 1, s1g_chan: 17, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 910.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 60 }, + "19": { country_code: "CA", bw: 1, s1g_chan: 19, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 911.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 64 }, + "21": { country_code: "CA", bw: 1, s1g_chan: 21, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 912.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 100 }, + "23": { country_code: "CA", bw: 1, s1g_chan: 23, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 913.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 104 }, + "25": { country_code: "CA", bw: 1, s1g_chan: 25, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 914.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 108 }, + "27": { country_code: "CA", bw: 1, s1g_chan: 27, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 915.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 112 }, + "29": { country_code: "CA", bw: 1, s1g_chan: 29, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 916.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 116 }, + "31": { country_code: "CA", bw: 1, s1g_chan: 31, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 917.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 120 }, + "33": { country_code: "CA", bw: 1, s1g_chan: 33, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 918.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 124 }, + "35": { country_code: "CA", bw: 1, s1g_chan: 35, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 919.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 128 }, + "37": { country_code: "CA", bw: 1, s1g_chan: 37, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 920.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 149 }, + "39": { country_code: "CA", bw: 1, s1g_chan: 39, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 921.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 153 }, + "41": { country_code: "CA", bw: 1, s1g_chan: 41, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 922.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 157 }, + "43": { country_code: "CA", bw: 1, s1g_chan: 43, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 923.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 161 }, + "45": { country_code: "CA", bw: 1, s1g_chan: 45, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 924.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 165 }, + "47": { country_code: "CA", bw: 1, s1g_chan: 47, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 925.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 169 }, + "49": { country_code: "CA", bw: 1, s1g_chan: 49, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 926.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 173 }, + "6": { country_code: "CA", bw: 2, s1g_chan: 6, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 905, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 38 }, + "10": { country_code: "CA", bw: 2, s1g_chan: 10, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 907, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 46 }, + "14": { country_code: "CA", bw: 2, s1g_chan: 14, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 909, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 54 }, + "18": { country_code: "CA", bw: 2, s1g_chan: 18, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 911, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 62 }, + "22": { country_code: "CA", bw: 2, s1g_chan: 22, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 913, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 102 }, + "26": { country_code: "CA", bw: 2, s1g_chan: 26, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 915, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 110 }, + "30": { country_code: "CA", bw: 2, s1g_chan: 30, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 917, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 118 }, + "34": { country_code: "CA", bw: 2, s1g_chan: 34, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 919, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 126 }, + "38": { country_code: "CA", bw: 2, s1g_chan: 38, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 921, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 151 }, + "42": { country_code: "CA", bw: 2, s1g_chan: 42, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 923, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 159 }, + "46": { country_code: "CA", bw: 2, s1g_chan: 46, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 925, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 167 }, + "8": { country_code: "CA", bw: 4, s1g_chan: 8, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 906, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 42 }, + "16": { country_code: "CA", bw: 4, s1g_chan: 16, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 910, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 58 }, + "24": { country_code: "CA", bw: 4, s1g_chan: 24, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 914, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 106 }, + "32": { country_code: "CA", bw: 4, s1g_chan: 32, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 918, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 122 }, + "40": { country_code: "CA", bw: 4, s1g_chan: 40, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 922, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 155 }, + "12": { country_code: "CA", bw: 8, s1g_chan: 12, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 908, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 50 }, + "28": { country_code: "CA", bw: 8, s1g_chan: 28, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 916, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 114 }, + "44": { country_code: "CA", bw: 8, s1g_chan: 44, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 924, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "Canada", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 163 }, + }, + valid_channels: [ 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49 ], + default_channel: 12 + }, + "EU": { + country_name: "European Union", + channels: { + "1": { country_code: "EU", bw: 1, s1g_chan: 1, s1g_op_class: 6, global_op_class: 66, centre_freq_mhz: 863.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 132 }, + "3": { country_code: "EU", bw: 1, s1g_chan: 3, s1g_op_class: 6, global_op_class: 66, centre_freq_mhz: 864.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 136 }, + "5": { country_code: "EU", bw: 1, s1g_chan: 5, s1g_op_class: 6, global_op_class: 66, centre_freq_mhz: 865.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 36 }, + "7": { country_code: "EU", bw: 1, s1g_chan: 7, s1g_op_class: 6, global_op_class: 66, centre_freq_mhz: 866.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 40 }, + "9": { country_code: "EU", bw: 1, s1g_chan: 9, s1g_op_class: 6, global_op_class: 66, centre_freq_mhz: 867.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 44 }, + "2": { country_code: "EU", bw: 2, s1g_chan: 2, s1g_op_class: 7, global_op_class: 67, centre_freq_mhz: 864, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 134 }, + "6": { country_code: "EU", bw: 2, s1g_chan: 6, s1g_op_class: 7, global_op_class: 67, centre_freq_mhz: 866, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "User assigned EU", tx_power_max: 16.13, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 38 }, + }, + valid_channels: [ 1, 2, 3, 5, 6, 7, 9 ], + default_channel: 2 + }, + "JP": { + country_name: "Japan", + channels: { + "13": { country_code: "JP", bw: 1, s1g_chan: 13, s1g_op_class: 8, global_op_class: 73, centre_freq_mhz: 923, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 36 }, + "15": { country_code: "JP", bw: 1, s1g_chan: 15, s1g_op_class: 8, global_op_class: 73, centre_freq_mhz: 924, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 40 }, + "17": { country_code: "JP", bw: 1, s1g_chan: 17, s1g_op_class: 8, global_op_class: 73, centre_freq_mhz: 925, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 44 }, + "19": { country_code: "JP", bw: 1, s1g_chan: 19, s1g_op_class: 8, global_op_class: 73, centre_freq_mhz: 926, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 48 }, + "21": { country_code: "JP", bw: 1, s1g_chan: 21, s1g_op_class: 8, global_op_class: 73, centre_freq_mhz: 927, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 64 }, + "2": { country_code: "JP", bw: 2, s1g_chan: 2, s1g_op_class: 9, global_op_class: 64, centre_freq_mhz: 923.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 38 }, + "4": { country_code: "JP", bw: 2, s1g_chan: 4, s1g_op_class: 10, global_op_class: 64, centre_freq_mhz: 924.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 54 }, + "6": { country_code: "JP", bw: 2, s1g_chan: 6, s1g_op_class: 9, global_op_class: 64, centre_freq_mhz: 925.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 46 }, + "8": { country_code: "JP", bw: 2, s1g_chan: 8, s1g_op_class: 10, global_op_class: 64, centre_freq_mhz: 926.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 62 }, + "36": { country_code: "JP", bw: 4, s1g_chan: 36, s1g_op_class: 11, global_op_class: 65, centre_freq_mhz: 924.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 42 }, + "38": { country_code: "JP", bw: 4, s1g_chan: 38, s1g_op_class: 12, global_op_class: 65, centre_freq_mhz: 925.5, duty_cycle_ap: 10, duty_cycle_sta: 10, country: "Japan", tx_power_max: 16, duty_cycle_omit_ctrl_resp: true, pkt_spacing_ms: 2, airtime_min_ms: 2, airtime_max_ms: 100, map_5g_chan: 58 }, + }, + valid_channels: [ 2, 4, 6, 8, 13, 15, 17, 19, 21, 36, 38 ], + default_channel: 36 + }, + "US": { + country_name: "USA", + channels: { + "3": { country_code: "US", bw: 1, s1g_chan: 3, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 903.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 136 }, + "5": { country_code: "US", bw: 1, s1g_chan: 5, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 904.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 36 }, + "7": { country_code: "US", bw: 1, s1g_chan: 7, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 905.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 40 }, + "9": { country_code: "US", bw: 1, s1g_chan: 9, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 906.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 44 }, + "11": { country_code: "US", bw: 1, s1g_chan: 11, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 907.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 48 }, + "13": { country_code: "US", bw: 1, s1g_chan: 13, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 908.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 52 }, + "15": { country_code: "US", bw: 1, s1g_chan: 15, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 909.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 56 }, + "17": { country_code: "US", bw: 1, s1g_chan: 17, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 910.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 60 }, + "19": { country_code: "US", bw: 1, s1g_chan: 19, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 911.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 64 }, + "21": { country_code: "US", bw: 1, s1g_chan: 21, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 912.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 100 }, + "23": { country_code: "US", bw: 1, s1g_chan: 23, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 913.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 104 }, + "25": { country_code: "US", bw: 1, s1g_chan: 25, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 914.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 108 }, + "27": { country_code: "US", bw: 1, s1g_chan: 27, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 915.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 112 }, + "29": { country_code: "US", bw: 1, s1g_chan: 29, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 916.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 116 }, + "31": { country_code: "US", bw: 1, s1g_chan: 31, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 917.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 120 }, + "33": { country_code: "US", bw: 1, s1g_chan: 33, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 918.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 124 }, + "35": { country_code: "US", bw: 1, s1g_chan: 35, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 919.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 128 }, + "37": { country_code: "US", bw: 1, s1g_chan: 37, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 920.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 149 }, + "39": { country_code: "US", bw: 1, s1g_chan: 39, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 921.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 153 }, + "41": { country_code: "US", bw: 1, s1g_chan: 41, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 922.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 157 }, + "43": { country_code: "US", bw: 1, s1g_chan: 43, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 923.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 161 }, + "45": { country_code: "US", bw: 1, s1g_chan: 45, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 924.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 165 }, + "47": { country_code: "US", bw: 1, s1g_chan: 47, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 925.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 169 }, + "49": { country_code: "US", bw: 1, s1g_chan: 49, s1g_op_class: 1, global_op_class: 68, centre_freq_mhz: 926.5, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 173 }, + "6": { country_code: "US", bw: 2, s1g_chan: 6, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 905, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 38 }, + "10": { country_code: "US", bw: 2, s1g_chan: 10, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 907, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 46 }, + "14": { country_code: "US", bw: 2, s1g_chan: 14, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 909, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 54 }, + "18": { country_code: "US", bw: 2, s1g_chan: 18, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 911, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 62 }, + "22": { country_code: "US", bw: 2, s1g_chan: 22, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 913, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 102 }, + "26": { country_code: "US", bw: 2, s1g_chan: 26, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 915, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 110 }, + "30": { country_code: "US", bw: 2, s1g_chan: 30, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 917, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 118 }, + "34": { country_code: "US", bw: 2, s1g_chan: 34, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 919, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 126 }, + "38": { country_code: "US", bw: 2, s1g_chan: 38, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 921, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 151 }, + "42": { country_code: "US", bw: 2, s1g_chan: 42, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 923, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 159 }, + "46": { country_code: "US", bw: 2, s1g_chan: 46, s1g_op_class: 2, global_op_class: 69, centre_freq_mhz: 925, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 167 }, + "8": { country_code: "US", bw: 4, s1g_chan: 8, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 906, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 42 }, + "16": { country_code: "US", bw: 4, s1g_chan: 16, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 910, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 58 }, + "24": { country_code: "US", bw: 4, s1g_chan: 24, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 914, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 106 }, + "32": { country_code: "US", bw: 4, s1g_chan: 32, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 918, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 122 }, + "40": { country_code: "US", bw: 4, s1g_chan: 40, s1g_op_class: 3, global_op_class: 70, centre_freq_mhz: 922, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 155 }, + "12": { country_code: "US", bw: 8, s1g_chan: 12, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 908, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 50 }, + "28": { country_code: "US", bw: 8, s1g_chan: 28, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 916, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 114 }, + "44": { country_code: "US", bw: 8, s1g_chan: 44, s1g_op_class: 4, global_op_class: 71, centre_freq_mhz: 924, duty_cycle_ap: 100, duty_cycle_sta: 100, country: "USA", tx_power_max: 36, duty_cycle_omit_ctrl_resp: false, pkt_spacing_ms: 0, airtime_min_ms: 0, airtime_max_ms: 0, map_5g_chan: 163 }, + }, + valid_channels: [ 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49 ], + default_channel: 12 + } +}; + +// --------------------------------------------------------------------------- +// Lookup API +// --------------------------------------------------------------------------- + +function halow_has_country(country) { + return (country != null) && (country in CHANNEL_MAP); +} + +function halow_valid_channels(country) { + let entry = CHANNEL_MAP[country]; + return entry ? entry.valid_channels : []; +} + +function halow_default_channel(country) { + let entry = CHANNEL_MAP[country]; + return entry ? entry.default_channel : null; +} + +function halow_lookup_op_class(country, chan) { + let entry = CHANNEL_MAP[country]; + if (!entry) + return null; + let row = entry.channels[sprintf("%d", chan)]; + return row ? row.global_op_class : null; +} + +function halow_centre_freq(country, chan) { + let entry = CHANNEL_MAP[country]; + if (!entry) + return null; + let row = entry.channels[sprintf("%d", chan)]; + return row ? row.centre_freq_mhz : null; +} + +function halow_resolve_country(country, default_country) { + if (halow_has_country(country)) + return country; + // First fallback: the supplied default country (e.g. from env / board.json). + if (halow_has_country(default_country)) { + warn(sprintf("HaLow: unknown country '%s', falling back to '%s'\n", country, default_country)); + return default_country; + } + // Last-resort fallback: 'US' is always present in the table. + warn(sprintf("HaLow: unknown country '%s' and invalid default '%s', falling back to 'US'\n", country, default_country)); + return 'US'; +} + +function halow_resolve_channel(country, chan) { + let entry = CHANNEL_MAP[country]; + if (!entry) + return null; + if (entry.channels[sprintf("%d", chan)] != null) + return chan; + let fallback = entry.default_channel; + warn(sprintf("HaLow: invalid channel %s for country '%s', falling back to %d\n", chan, country, fallback)); + return fallback; +} + +return { + CHANNEL_MAP, + halow_has_country, + halow_valid_channels, + halow_default_channel, + halow_lookup_op_class, + halow_centre_freq, + halow_resolve_country, + halow_resolve_channel +}; + diff --git a/renderer/templates/interface/ssid.uc b/renderer/templates/interface/ssid.uc index ee324da9..3a6fe62f 100644 --- a/renderer/templates/interface/ssid.uc +++ b/renderer/templates/interface/ssid.uc @@ -458,7 +458,6 @@ uci_comment(output, '### generate HaLow mesh specific configuration'); uci_named_section(output, 'wireless.halowmesh', 'wifi-iface'); uci_set_string(output, 'wireless.halowmesh.device', phy.section); - uci_set_string(output, 'wireless.halowmesh.ifname', 'halow_mesh'); uci_set_string(output, 'wireless.halowmesh.disabled', '0'); uci_set_string(output, 'wireless.halowmesh.beacon_int', '1000'); uci_set_string(output, 'wireless.halowmesh.wds', '0'); diff --git a/renderer/templates/radio.uc b/renderer/templates/radio.uc index c770db85..57bb701e 100644 --- a/renderer/templates/radio.uc +++ b/renderer/templates/radio.uc @@ -1,4 +1,5 @@ {% + let halow = require('halow_channels'); let phys = wiphy.lookup_by_band(radio.band); if (!length(phys)) { @@ -54,7 +55,84 @@ if (!length(radio.valid_channels)) radio.valid_channels = phys[0].channels; - radio.country ??= default_config.country; + // HaLow country resolution. The MORSE chip only supports the regulatory + // domains AU, CA, EU, JP and US. Resolve as follows: + // 1. When HaLow has no country of its own, follow the 2.4/5G peer radio + // (e.g. 5G=JP means HaLow must also be JP, not the default). + // 2. Otherwise inherit the peer/default country. + // 3. Normalize European ISO codes to EU. + // 4. If any radio country is outside the supported set, fall back ALL + // radios to US; if HaLow and the peer explicitly disagree, reject. + let peer_country = null; + for (let r in state.radios) { + if (r.band != "HaLow" && r.country) { + peer_country = r.country; + break; + } + } + + if (radio.band == "HaLow") + radio.country ??= peer_country || default_config.country || 'US'; + else + radio.country ??= default_config.country; + + // EdgeCore: AL (Albania) and BA (Bosnia) are aliased to EU below, but + // their local regulation only permits 1 MHz operation. Remember the + // original code so match_channel() can downgrade any 2 MHz+ channel to + // the 1 MHz default (with a warning, not a reject). + let halow_1mhz_only = (radio.band == "HaLow") && + (radio.country == "AL" || radio.country == "BA"); + + let eu_alias = { + AD: true, AL: true, AT: true, BA: true, BE: true, BG: true, BY: true, + CH: true, CY: true, CZ: true, DD: true, DE: true, DK: true, EE: true, + ES: true, FI: true, FL: true, FR: true, GB: true, GR: true, HR: true, + HU: true, IE: true, IS: true, IT: true, LI: true, LT: true, LU: true, + LV: true, MC: true, MD: true, ME: true, MK: true, MT: true, NL: true, + NO: true, PL: true, PT: true, RO: true, RS: true, SE: true, SI: true, + SK: true, SM: true, TR: true, UA: true, UK: true, VA: true, XK: true + }; + if (radio.band == "HaLow" && radio.country && eu_alias[radio.country]) + radio.country = "EU"; + if (peer_country && eu_alias[peer_country]) + peer_country = "EU"; + + // If any radio country is unsupported by the MORSE chip, all radios + // (2.4G/5G/HaLow) fall back to US. + let morse_supported = { AU: true, CA: true, EU: true, JP: true, US: true }; + let need_us = false; + for (let r in state.radios) { + let c = r.country; + if (c && eu_alias[c]) + c = "EU"; + if (c && !morse_supported[c]) + need_us = true; + } + + if (need_us) { + if (radio.band == "HaLow" && peer_country && radio.country != peer_country) { + warn(sprintf("HaLow country '%s' conflicts with 2G/5G country '%s'", + radio.country, peer_country)); + die(sprintf("HaLow country '%s' conflicts with 2G/5G country '%s'", + radio.country, peer_country)); + } + radio.country = "US"; + if (peer_country) + peer_country = "US"; + } else if (radio.band == "HaLow" && peer_country && radio.country != peer_country) { + warn(sprintf("HaLow country '%s' conflicts with 2G/5G country '%s'", + radio.country, peer_country)); + die(sprintf("HaLow country '%s' conflicts with 2G/5G country '%s'", + radio.country, peer_country)); + } + + // HaLow S1G channel/op_class/frequency are per-country table values, not + // derivable by the generic 5G algorithm. Resolve against the map and seed + // valid_channels from it. + let halow_country = (radio.band == "HaLow") + ? halow.halow_resolve_country(radio.country, peer_country || default_config.country) : null; + if (radio.band == "HaLow") + radio.valid_channels = halow.halow_valid_channels(halow_country); if (length(restrict.country) && !(radio.country in restrict.country)) { warn("Country code is restricted"); @@ -78,8 +156,30 @@ function match_channel(phy, radio) { let wanted_channel = radio.channel; - if (radio.band == "HaLow" && !wanted_channel) - return 12; + if (radio.band == "HaLow") { + let chan = !wanted_channel + ? halow.halow_default_channel(halow_country) + : halow.halow_resolve_channel(halow_country, wanted_channel); + // AL/BA: only 1 MHz allowed. If the resolved channel is + // wider (2 MHz+), fall back to a 1 MHz channel. The country + // default_channel may itself be 2 MHz+ (e.g. EU default is + // ch2 @ 2 MHz), so pick the smallest-numbered 1 MHz channel + // from the map rather than default_channel. + if (halow_1mhz_only) { + let row = halow.CHANNEL_MAP[halow_country]?.channels[sprintf("%d", chan)]; + if (row && row.bw > 1) { + let dflt = null; + let chans = halow.CHANNEL_MAP[halow_country]?.channels; + for (let k, v in chans) + if (v.bw == 1 && (dflt == null || v.s1g_chan < dflt)) + dflt = v.s1g_chan; + warn("HaLow: country '%s' (via AL/BA) allows 1 MHz only, channel %d is %d MHz, falling back to %d", + halow_country, chan, row.bw, dflt); + chan = dflt; + } + } + return chan; + } if (!wanted_channel || wanted_channel == "auto") return 0; @@ -261,7 +361,8 @@ set wireless.{{ phy.section }}.type='morse' set wireless.{{ phy.section }}.band='s1g' set wireless.{{ phy.section }}.hwmode='a' set wireless.{{ phy.section }}.s1g_prim_1mhz_chan_index='auto' -set wireless.{{ phy.section }}.op_class={{ get_s1g_op_class(match_channel(phy, radio)) }} +set wireless.{{ phy.section }}.op_class={{ halow.halow_lookup_op_class(halow_country, match_channel(phy, radio)) }} +set wireless.{{ phy.section }}.frequency={{ halow.halow_centre_freq(halow_country, match_channel(phy, radio)) }} {% endif %} {% if (afc): %} add wireless afc-server diff --git a/renderer/wifi/phy.uc b/renderer/wifi/phy.uc index 51ba8668..ae3ac773 100644 --- a/renderer/wifi/phy.uc +++ b/renderer/wifi/phy.uc @@ -219,10 +219,85 @@ function map5GToS1G() { return mappingTable; } +function scan_morse_phys() { + lookup_paths(); + + let phys = phy_get(); + let ret = {}; + let s1gMapping = map5GToS1G(); + + for (let phy in phys) { + if (!exists(phy, 'wiphy')) + continue; + let phyname = 'phy' + phy.wiphy; + let path = paths[phyname]; + if (!path) + continue; + + // Only process Morse PHYs + let morsePath = '/sys/kernel/debug/ieee80211/' + phyname + '/morse'; + if (!fs.stat(morsePath)) + continue; + + let p = {}; + p.is_morse_phy = true; + + let temp = get_hwmon('phy' + phy.wiphy); + if (temp) + p.temperature = temp / 1000; + + p.tx_ant = phy.wiphy_antenna_tx; + p.rx_ant = phy.wiphy_antenna_rx; + p.tx_ant_avail = phy.wiphy_antenna_avail_tx; + p.rx_ant_avail = phy.wiphy_antenna_avail_rx; + p.frequencies = []; + p.channels = []; + p.dfs_channels = []; + p.htmode = []; + p.band = []; + for (let band in phy.wiphy_bands) { + for (let freq in band?.freqs) { + if (freq.disabled) + continue; + + let channel = freq2channel(freq.freq); + if (freq.freq >= 5160 && freq.freq <= 5885) { + let ch = "" + channel; + if (ch in s1gMapping) { + channel = s1gMapping[ch].s1g_channel; + push(p.channels, channel); + push(p.frequencies, s1gMapping[ch].s1g_freq); + } else { + push(p.channels, channel); + push(p.frequencies, freq.freq); + } + } else { + push(p.channels, channel); + push(p.frequencies, freq.freq); + } + + if (freq.freq < 1000 || (freq.freq >= 5160 && freq.freq <= 5885)) + push(p.band, 'HaLow'); + } + } + + p.band = uniq(p.band); + if (length(p.band)) + ret[path] = p; + } + return ret; +} + function lookup_phys() { let ret = lookup_board(); - if (ret) + if (ret) { + // board.json declares the PCIe wlan, but the USB HaLow (Morse) dongle + // is not in board.json - scan for it and merge it in. + let morse = scan_morse_phys(); + for (let path, phy in morse) + ret[path] = phy; return ret; + } lookup_paths(); let phys = phy_get(); diff --git a/renderer/wifi/survey.uc b/renderer/wifi/survey.uc index 365c3d24..22545860 100644 --- a/renderer/wifi/survey.uc +++ b/renderer/wifi/survey.uc @@ -24,11 +24,18 @@ function wif_get(wdev) { function lookup_survey() { let wifs = wif_get(); + // Tag every survey entry with its owning interface + wiphy. The morse + // HaLow phy uses borrowed 5 GHz shim frequencies that collide with the + // real 5 GHz radio, so frequency alone cannot attribute a survey row to a + // radio; the consumer filters by ifname/wiphy. for (let wif in wifs) for (let survey in survey_get(wif.ifname)) if (!frequency || survey.survey_info.frequency == frequency) - if (survey.survey_info?.time) + if (survey.survey_info?.time) { + survey.survey_info.ifname = wif.ifname; + survey.survey_info.wiphy = wif.wiphy; push(rv.survey, survey.survey_info); + } } lookup_survey(); diff --git a/system/state/wifi.uc b/system/state/wifi.uc index 3b68fcca..e279b99d 100644 --- a/system/state/wifi.uc +++ b/system/state/wifi.uc @@ -113,14 +113,36 @@ export function collect_wifi_radios(state) { if (_chanUtil > 0) radio.chanUtil = _chanUtil; + // Pre-scope the survey to THIS radio's own interfaces before anyone + // consumes it. The morse HaLow phy borrows 5 GHz shim frequencies that + // collide with the real 5 GHz radio, so a frequency-only match would + // cross-contaminate the two radios' telemetry. survey rows are tagged + // with ifname (renderer/wifi/survey.uc); keep only rows whose ifname + // belongs to this radio (untagged rows are left for the frequency + // filter). + let radio_ifnames = {}; + for (let wif in data.interfaces) + if (wif.ifname) + radio_ifnames[wif.ifname] = true; + let radio_survey = { survey: [] }; + for (let k, v in survey.survey) + if (v.ifname == null || radio_ifnames[v.ifname]) + push(radio_survey.survey, v); + // Check if this is a HaLow device and process accordingly - if (!halow_module.process_halow_radio(radio, survey)) { + if (!halow_module.process_halow_radio(radio, radio_survey)) { // Non-HaLow device, process survey normally radio.survey = []; - for (let k, v in survey.survey) + for (let k, v in radio_survey.survey) if (v.frequency in radio.frequency) push(radio.survey, v); } + // Drop the internal attribution tags so the uploaded telemetry keeps + // the exact original survey shape (no extra ifname/wiphy keys). + for (let s in radio.survey) { + delete s.ifname; + delete s.wiphy; + } delete radio.in_use; push(radios, radio); }