# HG changeset patch # User Sean Burford # Date 1363134002 -39600 # Node ID fa1ac806391ea053a903ca6dd64eb5508b3b88c0 # Parent 443ea98c0840de12f8ee149ccd8eecc78bb69a51 Add support for band plans (Bug 681) diff -r 443ea98c0840 -r fa1ac806391e chirp/bandplan_au.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/bandplan_au.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,182 @@ +# Copyright 2013 Sean Burford +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +BANDPLAN = { + "name": "Australian Amateur Band Plans", + "updated": "April 2010", + "url": "http://www.wia.org.au/members/bandplans/data/documents/Australian%20Band%20Plans%20100404.pdf", + "bands": { + (28.000e6, 29.700e6): { + "name": "6 Metre Band", + (29.520e6, 29.680e6): { + "name": "FM Simplex and Repeaters", + "mode": "FM", + "step": 20, + (29.620e6, 29.680e6): { + "name": "FM Repeater Outputs", + "offset": -100000, + }, + }, + }, + (50.000e6, 54.000e6): { + (52.525e6, 53.975e6): { + "name": "FM Simplex and Repeaters", + "mode": "FM", + "step": 25, + (53.550e6, 53.975e6): { + "name": "FM Repeater Outputs", + "offset": -1e6, + }, + }, + }, + (144.000e6, 148.000e6): { + "name": "2 Metre Band", + "tones": (91.5, 123.0, 141.3, 146.2, 85.4), + (144.400e6, 144.600e6): { + "name": "Narrow Band Beacons", + "step": 2, + }, + (146.025e6, 147.975e6): { + "name": "FM Simplex and Repeaters", + "mode": "FM", + "step": 25, + (146.625e6, 147.000e6): { + "name": "FM Repeater Outputs Group A", + "offset": -600000, + }, + (147.025e6, 147.375e6): { + "name": "FM Repeater Outputs Group B", + "offset": 600000, + } + } + }, + (420.000e6, 450.000e6): { + "name": "70cm Band", + "tones": (91.5, 123.0, 141.3, 146.2, 85.4), + (432.400e6, 432.600e6): { + "name": "Beacons", + "step": 2, + }, + (438.025e6, 439.975e6): { + "name": "FM Simplex and Repeaters", + "mode": "FM", + "step": 25, + (438.025e6, 438.725e6): { + "name": "FM Repeater Outputs Group A", + "offset": -5e6, + }, + (439.275e6, 439.975e6): { + "name": "FM Repeater Outputs Group B", + "offset": -5e6, + }, + }, + }, + (1240.000e6, 1270.000e6): { + "name": "23cm Band", + (1273.025e6, 1273.975e6): { + "name": "FM Repeater Outputs", + "mode": "FM", + "step": 25, + "offset": 20e6, + }, + (1296.400e6, 1296.600e6): { + "name": "Beacons", + "step": 2, + }, + (1297.025e6, 1300.400e6): { + "name": "General FM Simplex Data", + "mode": "FM", + "step": 25, + }, + }, + (2300.000e6, 2450.000e6): { + "name": "13cm Band", + (2403.400e6, 2403.600e6): { + "name": "Beacons", + "step": 2, + }, + (2425.000e6, 2428.000e6): { + "name": "FM Simplex", + "mode": "FM", + "step": 25, + }, + (2428.025e6, 2429.000e6): { + "name": "FM Duplex (Voice)", + "mode": "FM", + "step": 25, + "offset": 20e6, + }, + (2429.000e6, 2429.975e6): { + "name": "FM Duplex (Data)", + "mode": "FM", + "step": 100, + "offset": 20e6, + }, + }, + (3300.000e6, 3600.000e6): { + "name": "9cm Band", + (3320.000e6, 3340.000e6): { + "name": "9cm Wideband Channel 2: Voice or Data", + "step": 100, + }, + (3400.400e6, 3400.600e6): { + "name": "Beacons", + "step": 2, + }, + (3402.000e6, 3403.000e6): { + "name": "FM Simplex (Voice)", + "mode": "FM", + "step": 100, + }, + (3403.000e6, 3405.000e6): { + "name": "FM Simplex (Data)", + "mode": "FM", + "step": 100, + }, + }, + (5650.000e6, 5850.000e6): { + "name": "6cm Band", + (5760.400e6, 5760.600e6): { + "name": "Beacons", + "step": 2, + }, + (5700.000e6, 5720.000e6): { + "name": "6cm Wideband Channel 2: Data", + "step": 100, + "offset": 70e6, + }, + (5720.000e6, 5740.000e6): { + "name": "6cm Wideband Channel 3: Voice", + "step": 100, + "offset": 70e6, + }, + (5762.000e6, 5763.000e6): { + "name": "FM Simplex (Voice)", + "mode": "FM", + "step": 100, + }, + (5763.000e6, 5765.000e6): { + "name": "FM Simplex (Data)", + "mode": "FM", + "step": 100, + }, + }, + (10.000e9, 10.500e9): { + "name": "3cm Band", + } + } +} + diff -r 443ea98c0840 -r fa1ac806391e chirp/bandplan_iaru_r1.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/bandplan_iaru_r1.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,19 @@ +# Copyright 2013 Sean Burford +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +BANDPLAN = { + "name": "IARU Region 1 Band Plans", + } + diff -r 443ea98c0840 -r fa1ac806391e chirp/bandplan_iaru_r2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/bandplan_iaru_r2.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,20 @@ +# Copyright 2013 Sean Burford +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +BANDPLAN = { + "name": "IARU Region 2 Band Plans", + } + + diff -r 443ea98c0840 -r fa1ac806391e chirp/bandplan_iaru_r3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/bandplan_iaru_r3.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,19 @@ +# Copyright 2013 Sean Burford +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +BANDPLAN = { + "name": "IARU Region 3 Band Plans", + } + diff -r 443ea98c0840 -r fa1ac806391e chirp/bandplan_na.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/bandplan_na.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,80 @@ +# Copyright 2013 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +STD_6M_OFFSETS = [ + (51620000, 51980000, -500000), + (52500000, 52980000, -500000), + (53500000, 53980000, -500000), + ] + +STD_2M_OFFSETS = [ + (145100000, 145500000, -600000), + (146000000, 146400000, 600000), + (146600000, 147000000, -600000), + (147000000, 147400000, 600000), + (147600000, 148000000, -600000), + ] + +STD_220_OFFSETS = [ + (223850000, 224980000, -1600000), + ] + +STD_70CM_OFFSETS = [ + (440000000, 445000000, 5000000), + (447000000, 450000000, -5000000), + ] + +STD_23CM_OFFSETS = [ + (1282000000, 1288000000, -12000000), + ] + +# Standard offsets, indexed by band (wavelength in cm) +STD_OFFSETS = { + 600 : STD_6M_OFFSETS, + 200 : STD_2M_OFFSETS, + 125 : STD_220_OFFSETS, + 70 : STD_70CM_OFFSETS, + 23 : STD_23CM_OFFSETS, + } + +BAND_TO_MHZ = { + 600 : ( 50000000, 54000000 ), + 200 : ( 144000000, 148000000 ), + 125 : ( 219000000, 225000000 ), + 70 : ( 420000000, 450000000 ), + 23 : ( 1240000000, 1300000000 ), +} + +# NB: This only works for some bands, throws an Exception otherwise +def freq_to_band(freq): + """Returns the band (in cm) for a given frequency""" + for band, (lo, hi) in BAND_TO_MHZ.items(): + if int(freq) >= lo and int(freq) < hi: + return band + raise ValueError("No conversion for frequency %i" % freq) + +def get_offset(freq): + chosen_offset = None + + try: + band = freq_to_band(freq) + for lo, hi, offset in STD_OFFSETS[band]: + if int(freq) >= lo and int(freq) < hi: + chosen_offset = offset + except ValueError: + pass + + return chosen_offset + diff -r 443ea98c0840 -r fa1ac806391e chirp/chirp_common.py --- a/chirp/chirp_common.py Tue Mar 05 09:49:47 2013 -0800 +++ b/chirp/chirp_common.py Wed Mar 13 11:20:02 2013 +1100 @@ -68,58 +68,6 @@ MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY", "DIG", "PKT", "NCW", "NCWR", "CWR", "P25", "Auto"] -STD_6M_OFFSETS = [ - (51620000, 51980000, -500000), - (52500000, 52980000, -500000), - (53500000, 53980000, -500000), - ] - -STD_2M_OFFSETS = [ - (145100000, 145500000, -600000), - (146000000, 146400000, 600000), - (146600000, 147000000, -600000), - (147000000, 147400000, 600000), - (147600000, 148000000, -600000), - ] - -STD_220_OFFSETS = [ - (223850000, 224980000, -1600000), - ] - -STD_70CM_OFFSETS = [ - (440000000, 445000000, 5000000), - (447000000, 450000000, -5000000), - ] - -STD_23CM_OFFSETS = [ - (1282000000, 1288000000, -12000000), - ] - -# Standard offsets, indexed by band (wavelength in cm) -STD_OFFSETS = { - 600 : STD_6M_OFFSETS, - 200 : STD_2M_OFFSETS, - 125 : STD_220_OFFSETS, - 70 : STD_70CM_OFFSETS, - 23 : STD_23CM_OFFSETS, - } - -BAND_TO_MHZ = { - 600 : ( 50000000, 54000000 ), - 200 : ( 144000000, 148000000 ), - 125 : ( 219000000, 225000000 ), - 70 : ( 420000000, 450000000 ), - 23 : ( 1240000000, 1300000000 ), -} - -# NB: This only works for some bands, throws an Exception otherwise -def freq_to_band(freq): - """Returns the band (in cm) for a given frequency""" - for band, (lo, hi) in BAND_TO_MHZ.items(): - if int(freq) > lo and int(freq) < hi: - return band - raise Exception("No conversion for frequency %i" % freq) - TONE_MODES = [ "", "Tone", diff -r 443ea98c0840 -r fa1ac806391e chirpui/bandplans.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirpui/bandplans.py Wed Mar 13 11:20:02 2013 +1100 @@ -0,0 +1,90 @@ +# Copyright 2013 Sean Burford +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from chirpui import config +from chirp import bandplan_na, bandplan_au +from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3 + +PLANS = { + "iaru_r1": "IARU Region 1", + "iaru_r2": "IARU Region 2", + "iaru_r3": "IARU Region 3", + "north_america": "North American Amateur", + "australia": "Australian Amateur", + } + +def conf(cf=None): + vals = {} + if cf is None: + cf = config.get() + + for plan, desc in PLANS.items(): + val = cf.get_bool(plan, "bandplan") + vals[plan] = (desc, val) + + return vals + +def _walk_bandplan(freq, bandplan, defaults): + # Override defaults with values from this level. + for name in defaults: + defaults[name] = bandplan.get(name, defaults[name]) + + # Go more specific if the frequency fits within an available sublevel. + for key, vals in bandplan.items(): + if key in defaults: + pass + elif isinstance(key, tuple): + if key[0] > key[1]: + raise Exception("Invalid range %s - %s" % key) + if int(freq) >= key[0] and int(freq) < key[1]: + _walk_bandplan(freq, vals, defaults) + break + else: + raise Exception("Invalid key %s" % key) + +def _determine_defaults(freq, bandplan, defaults): + try: + _walk_bandplan(freq, bandplan.get("bands", {}), defaults) + except Exception, e: + print "Failed to parse %s band plan: %s" % ( + bandplan.get("name", "???"), e) + +def get_defaults(freq): + defaults = {"name": None, + "url": None, + "mode": None, + "step": None, + "tones": None, + "offset": None, + } + + cf = conf() + # This should be ordered from least specific to most specific as + # later bandplans take precedence. + if cf.get("iaru_r1")[1]: + _determine_defaults(freq, bandplan_iaru_r1.BANDPLAN, defaults) + if cf.get("iaru_r2")[1]: + _determine_defaults(freq, bandplan_iaru_r2.BANDPLAN, defaults) + if cf.get("iaru_r3")[1]: + _determine_defaults(freq, bandplan_iaru_r3.BANDPLAN, defaults) + if cf.get("australia")[1]: + _determine_defaults(freq, bandplan_au.BANDPLAN, defaults) + if cf.get("north_america")[1]: + bp_off = bandplan_na.get_offset(freq) + if bp_off is not None: + defaults["offset"] = bp_off + + return defaults + diff -r 443ea98c0840 -r fa1ac806391e chirpui/config.py --- a/chirpui/config.py Tue Mar 05 09:49:47 2013 -0800 +++ b/chirpui/config.py Wed Mar 13 11:20:02 2013 +1100 @@ -54,6 +54,12 @@ def is_defined(self, key, section): return self.__config.has_option(section, key) + def remove_option(self, section, key): + self.__config.remove_option(section, key) + + if not self.__config.items(section): + self.__config.remove_section(section) + class ChirpConfigProxy: def __init__(self, config, section="global"): self._config = config @@ -98,6 +104,10 @@ def is_defined(self, key, section=None): return self._config.is_defined(key, section or self._section) + def remove_option(self, key, section): + self._config.remove_option(section, key) + + _CONFIG = None def get(section="global"): global _CONFIG diff -r 443ea98c0840 -r fa1ac806391e chirpui/mainapp.py --- a/chirpui/mainapp.py Tue Mar 05 09:49:47 2013 -0800 +++ b/chirpui/mainapp.py Wed Mar 13 11:20:02 2013 +1100 @@ -40,6 +40,7 @@ from chirp import CHIRP_VERSION, chirp_common, detect, errors from chirp import icf, ic9x_icf from chirpui import editorset, clone, miscwidgets, config, reporting, fips +from chirpui import bandplans CONF = config.get() @@ -1256,8 +1257,9 @@ conf = config.get() conf.set_bool("no_report", not action.get_active()) - def do_toggle_autorpt(self, action): - CONF.set_bool("autorpt", action.get_active(), "memedit") + def do_toggle_bp(self, _action): + action = _action.get_name() + CONF.set_bool(action, _action.get_active(), "bandplan") def do_toggle_no_smart_tmode(self, action): CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit") @@ -1351,8 +1353,8 @@ self.do_clearq() elif action == "report": self.do_toggle_report(_action) - elif action == "autorpt": - self.do_toggle_autorpt(_action) + elif action in bandplans.PLANS: + self.do_toggle_bp(_action) elif action == "no_smart_tmode": self.do_toggle_no_smart_tmode(_action) elif action == "developer": @@ -1429,7 +1431,10 @@ - + + + + @@ -1485,6 +1490,7 @@ ('export_chirp', None, _("CHIRP Native File"), None, None, self.mh), ('export_csv', None, _("CSV File"), None, None, self.mh), ('stock', None, _("Import from stock config"), None, None, self.mh), + ('bandplan', None, _("Use band plans"), None, None, self.mh), ('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh), ('help', None, _('Help'), None, None, self.mh), ('about', gtk.STOCK_ABOUT, None, None, None, self.mh), @@ -1493,17 +1499,24 @@ conf = config.get() re = not conf.get_bool("no_report"); hu = conf.get_bool("hide_unused", "memedit") - ro = conf.get_bool("autorpt", "memedit") dv = conf.get_bool("developer", "state") st = not conf.get_bool("no_smart_tmode", "memedit") + # Migrate old "automatic repeater offset" setting to + # "North American Amateur Band Plan" + ro = conf.get("autorpt", "memedit") + if ro is not None: + conf.set_bool("north_america", "bandplan", ro) + conf.remove_option("autorpt", "memedit") + toggles = [\ ('report', None, _("Report statistics"), None, None, self.mh, re), ('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu), ('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st), - ('autorpt', None, _("Automatic Repeater Offset"), None, None, self.mh, ro), ('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv), ] + for plan, setting in bandplans.conf(conf).items(): + toggles.append((plan, None, _(setting[0]), None, None, self.mh, setting[1])) self.menu_uim = gtk.UIManager() self.menu_ag = gtk.ActionGroup("MenuBar") @@ -1695,10 +1708,6 @@ d.destroy() CONF.set_bool("warned_about_reporting", True) - if not CONF.is_defined("autorpt", "memedit"): - print "autorpt not set et" - CONF.set_bool("autorpt", True, "memedit") - self.update_recent_files() self.update_stock_configs() self.setup_extra_hotkeys() diff -r 443ea98c0840 -r fa1ac806391e chirpui/memedit.py --- a/chirpui/memedit.py Tue Mar 05 09:49:47 2013 -0800 +++ b/chirpui/memedit.py Wed Mar 13 11:20:02 2013 +1100 @@ -32,7 +32,7 @@ import pickle import os -from chirpui import common, shiftdialog, miscwidgets, config, memdetail +from chirpui import common, shiftdialog, miscwidgets, config, memdetail, bandplans from chirp import chirp_common, errors, directory, import_logic def handle_toggle(_, path, store, col): @@ -122,10 +122,10 @@ return chirp_common.parse_freq(new) def ed_freq(self, _foo, path, new, colnum): - iter = self.store.get_iter(path) - prev, = self.store.get(iter, colnum) + path_iter = self.store.get_iter(path) + prev, = self.store.get(path_iter, colnum) - def set_offset(path, offset): + def set_offset(offset): if offset > 0: dup = "+" elif offset == 0: @@ -135,15 +135,24 @@ offset *= -1 if offset: - self.store.set(iter, self.col(_("Offset")), offset) + self.store.set(path_iter, self.col(_("Offset")), offset) - self.store.set(iter, self.col(_("Duplex")), dup) + self.store.set(path_iter, self.col(_("Duplex")), dup) def set_ts(ts): - self.store.set(iter, self.col(_("Tune Step")), ts) + if ts in chirp_common.TUNING_STEPS: + self.store.set(path_iter, self.col(_("Tune Step")), ts) def get_ts(path): - return self.store.get(iter, self.col(_("Tune Step")))[0] + return self.store.get(path_iter, self.col(_("Tune Step")))[0] + + def set_mode(mode): + if mode in chirp_common.MODES: + self.store.set(path_iter, self.col(_("Mode")), mode) + + def set_tone(tone): + if tone in chirp_common.TONES: + self.store.set(path_iter, self.col(_("Tone")), tone) try: new = chirp_common.parse_freq(new) @@ -154,16 +163,13 @@ if not self._features.has_nostep_tuning: set_ts(chirp_common.required_step(new)) - if new and self._config.get_bool("autorpt") and new != prev: - try: - band = chirp_common.freq_to_band(new) - set_offset(path, 0) - for lo, hi, offset in chirp_common.STD_OFFSETS[band]: - if new > lo and new < hi: - set_offset(path, offset) - break - except Exception, e: - pass + if new and new != prev: + defaults = bandplans.get_defaults(new) + set_offset(defaults.get("offset") or 0) + set_ts(defaults.get("step")) + set_mode(defaults.get("mode")) + if defaults.get("tones"): + set_tone(defaults.get("tones")[0]) return new @@ -190,11 +196,8 @@ # RX frequency as the default TX frequency self.store.set(iter, self.col("Offset"), freq) else: - band = int(freq / 100000000) - if chirp_common.STD_OFFSETS.has_key(band): - offset = chirp_common.STD_OFFSETS[band][0][2] - else: - offset = 0 + defaults = bandplans.get_defaults(freq) + offset = defaults.get("offset") or 0 self.store.set(iter, self.col(_("Offset")), abs(offset)) return new