# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1305671333 25200 # Node ID bf2e12513c27a3b685009ad6561850c8a83f7fca # Parent 72131a755f0b41dc94556813517da998eeeb1759 Add RFinder support as an import source
RFinder is a subscription-based repeater database. This adds an item under the Radio menu for "Import from RFinder".
diff -r 72131a755f0b -r bf2e12513c27 build/version --- a/build/version Mon May 16 16:41:09 2011 -0700 +++ b/build/version Tue May 17 15:28:53 2011 -0700 @@ -1,1 +1,1 @@ -0.1.11 +0.1.11rfinder3 diff -r 72131a755f0b -r bf2e12513c27 chirp/__init__.py --- a/chirp/__init__.py Mon May 16 16:41:09 2011 -0700 +++ b/chirp/__init__.py Tue May 17 15:28:53 2011 -0700 @@ -15,4 +15,4 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/.
-CHIRP_VERSION="0.1.11" +CHIRP_VERSION="0.1.11rfinder2" diff -r 72131a755f0b -r bf2e12513c27 chirp/directory.py --- a/chirp/directory.py Mon May 16 16:41:09 2011 -0700 +++ b/chirp/directory.py Tue May 17 15:28:53 2011 -0700 @@ -22,7 +22,7 @@ from chirp import kenwood_live, tmv71, thd72 from chirp import alinco from chirp import wouxun -from chirp import xml, chirp_common, csv, util +from chirp import xml, chirp_common, csv, util, rfinder
DRV_TO_RADIO = {
@@ -116,6 +116,12 @@ raise Exception("Unsupported model")
def get_radio_by_image(image_file): + if image_file.startswith("rfinder://"): + method, _, email, passwd, lat, lon = image_file.split("/") + rf = rfinder.RFinderRadio(None) + rf.set_params(float(lat), float(lon), email, passwd) + return rf + if image_file.lower().endswith(".chirp"): return xml.XMLRadio(image_file)
diff -r 72131a755f0b -r bf2e12513c27 chirp/rfinder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/rfinder.py Tue May 17 15:28:53 2011 -0700 @@ -0,0 +1,161 @@ +# Copyright 2011 Dan Smith dsmith@danplanet.com +# +# 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 http://www.gnu.org/licenses/. + +import urllib +import hashlib + +from chirp import chirp_common, CHIRP_VERSION + +SCHEMA = [ + "ID", + "TRUSTEE", + "OUTFREQUENCY", + "CITY", + "STATE", + "COUNTRY", + "LATITUDE", + "LONGITUDE", + "CLUB", + "DESCRIPTION", + "NOTES", + "RANGE", + "OFFSETSIGN", + "OFFSETFREQ", + "PL", + "DCS", + "REPEATERTYPE", + "BAND", + "IRLP", + "ECHOLINK", + "DOC_ID", + ] + +class RFinderParser: + def __init__(self): + self.__memories = [] + self.__cheat = {} + + def fetch_data(self, lat, lon, email, passwd): + args = { + "lat" : "%7.5f" % lat, + "lon" : "%8.5f" % lon, + "email" : urllib.quote_plus(email), + "pass" : hashlib.md5(passwd).hexdigest(), + "vers" : "CH%s" % CHIRP_VERSION, + } + + url = "http://sync.rfinder.net/radio/repeaters.nsf/getlocal?openagent&%s%22%5C + % "&".join(["%s=%s" % (k,v) for k,v in args.items()]) + + f = urllib.urlopen(url) + data = f.read() + f.close() + + return data + + def parse_line(self, line): + mem = chirp_common.Memory() + + _vals = line.split("|") + + vals = {} + for i in range(0, len(SCHEMA)): + vals[SCHEMA[i]] = _vals[i] + self.__cheat = vals + + mem.name = vals["TRUSTEE"] + mem.freq = float(vals["OUTFREQUENCY"]) + if vals["OFFSETSIGN"] != "X": + mem.duplex = vals["OFFSETSIGN"] + if vals["OFFSETFREQ"]: + mem.offset = abs(float(vals["OFFSETFREQ"])) + + if vals["PL"] and vals["PL"] != "0": + mem.rtone = float(vals["PL"]) + mem.tmode = "Tone" + elif vals["DCS"] and vals["DCS"] != "0": + mem.dtcs = int(vals["DCS"]) + mem.tmode = "DTCS" + + return mem + + def parse_data(self, data): + number = 1 + for line in data.split("\n"): + if line.startswith("<"): + continue + elif not line.strip(): + continue + try: + mem = self.parse_line(line) + mem.number = number + number += 1 + self.__memories.append(mem) + except Exception, e: + print "Error in record %s:" % self.__cheat["DOC_ID"] + print e + print self.__cheat + print "\n\n" + + def get_memories(self): + return self.__memories + +class RFinderRadio(chirp_common.Radio): + VENDOR = "ITWeRKS" + MODEL = "RFinder" + + def __init__(self, *args, **kwargs): + chirp_common.Radio.__init__(self, *args, **kwargs) + + self._lat = 0 + self._lon = 0 + self._call = "" + self._email = "" + + self._rfp = None + + def set_params(self, lat, lon, call, email): + self._lat = lat + self._lon = lon + self._call = call + self._email = email + + def do_fetch(self): + self._rfp = RFinderParser() + self._rfp.parse_data(self._rfp.fetch_data(self._lat, self._lon, self._call, self._email)) + + def get_features(self): + if not self._rfp: + self.do_fetch() + + rf = chirp_common.RadioFeatures() + rf.memory_bounds = (1, len(self._rfp.get_memories())) + return rf + + def get_memory(self, number): + if not self._rfp: + self.do_fetch() + + return self._rfp.get_memories()[number-1] + +if __name__ == "__main__": + import sys + + rfp = RFinderParser() + data = rfp.fetch_data(45.525, -122.9164, "KK7DS", "dsmith@danplanet.com") + rfp.parse_data(data) + + for m in rfp.get_memories(): + print m diff -r 72131a755f0b -r bf2e12513c27 chirpui/mainapp.py --- a/chirpui/mainapp.py Mon May 16 16:41:09 2011 -0700 +++ b/chirpui/mainapp.py Tue May 17 15:28:53 2011 -0700 @@ -75,7 +75,7 @@ for i in ["cancelq"]: set_action_sensitive(i, eset is not None and not mmap_sens)
- for i in ["export", "import", "close", "columns"]: + for i in ["export", "import", "close", "columns", "rfinder"]: set_action_sensitive(i, eset is not None)
def ev_status(self, editorset, msg): @@ -394,6 +394,67 @@ count = eset.do_import(filen) reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
+ def do_rfinder_prompt(self): + fields = {"1Email" : (gtk.Entry(), + lambda x: "@" in x), + "2Password" : (gtk.Entry(), + lambda x: x), + "3Latitude" : (gtk.Entry(), + lambda x: float(x) < 90 and float(x) > -90), + "4Longitude": (gtk.Entry(), + lambda x: float(x) < 180 and float(x) > -180), + } + + d = inputdialog.FieldDialog(title="RFinder Login", parent=self) + for k in sorted(fields.keys()): + d.add_field(k[1:], fields[k][0]) + fields[k][0].set_text(CONF.get(k[1:], "rfinder") or "") + fields[k][0].set_visibility(k != "2Password") + + while d.run() == gtk.RESPONSE_OK: + valid = True + for k in sorted(fields.keys()): + widget, validator = fields[k] + try: + if validator(widget.get_text()): + CONF.set(k[1:], widget.get_text(), "rfinder") + continue + except Exception: + pass + common.show_error("Invalid value for %s" % k[1:]) + valid = False + break + + if valid: + d.destroy() + return True + + d.destroy() + return False + + def do_rfinder(self): + self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + if not self.do_rfinder_prompt(): + self.window.set_cursor(None) + return + + lat = CONF.get_float("Latitude", "rfinder") + lon = CONF.get_float("Longitude", "rfinder") + passwd = CONF.get("Password", "rfinder") + email = CONF.get("Email", "rfinder") + + # Do this in case the import process is going to take a while + # to make sure we process events leading up to this + gtk.gdk.window_process_all_updates() + while gtk.events_pending(): + gtk.main_iteration(False) + + eset = self.get_current_editorset() + count = eset.do_import("rfinder://%s/%s/%f/%f" % (email, passwd, lat, lon)) + reporting.report_model_usage(eset.rthread.radio, "import", count > 0) + + self.window.set_cursor(None) + def do_export(self): types = [("CSV Files (*.csv)", "csv"), ("CHIRP Files (*.chirp)", "chirp"), @@ -537,6 +598,8 @@ self.do_close() elif action == "import": self.do_import() + elif action == "rfinder": + self.do_rfinder() elif action == "export": self.do_export() elif action == "about": @@ -591,7 +654,8 @@ <menu action="radio" name="radio"> <menuitem action="download"/> <menuitem action="upload"/> - <menu action="recent" name="recent"/> + <separator/> + <menuitem action="rfinder"/> <separator/> <menuitem action="cancelq"/> </menu> @@ -622,6 +686,7 @@ ('upload', None, "Upload To Radio", "<Alt>u", None, self.mh), ('import', None, 'Import', "<Alt>i", None, self.mh), ('export', None, 'Export', "<Alt>e", None, self.mh), + ('rfinder', None, "Import from RFinder", None, None, self.mh), ('export_chirp', None, 'CHIRP Native File', None, None, self.mh), ('export_csv', None, 'CSV File', None, None, self.mh), ('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),