[chirp_devel] [PATCH] Add RFinder support as an import source

Dan Smith
Tue May 17 15:28:59 PDT 2011


# HG changeset patch
# User Dan Smith <dsmith at 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 at 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"\
+            % "&".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 at 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),



More information about the chirp_devel mailing list