chirp-0.3.1/0000755000016101777760000000000012130403637014014 5ustar jenkinsnogroup00000000000000chirp-0.3.1/chirp.xsd0000644000016100007500000000244611717005656014217 0ustar jenkins00000000000000 chirp-0.3.1/PKG-INFO0000644000016101777760000000026312130403637015112 0ustar jenkinsnogroup00000000000000Metadata-Version: 1.0 Name: chirp Version: 0.3.1 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN chirp-0.3.1/setup.py0000644000016101777760000001076012066005671015536 0ustar jenkinsnogroup00000000000000import sys import os from chirp import CHIRP_VERSION from chirp import * import chirp def staticify_chirp_module(): import chirp init = file("chirp/__init__.py", "w") print >>init, "CHIRP_VERSION = \"%s\"" % CHIRP_VERSION print >>init, "__all__ = %s\n" % str(chirp.__all__) init.close() print "Set chirp.py::__all__ = %s" % str(chirp.__all__) def win32_build(): from distutils.core import setup import py2exe try: # if this doesn't work, try import modulefinder import py2exe.mf as modulefinder import win32com for p in win32com.__path__[1:]: modulefinder.AddPackagePath("win32com", p) for extra in ["win32com.shell"]: #,"win32com.mapi" __import__(extra) m = sys.modules[extra] for p in m.__path__[1:]: modulefinder.AddPackagePath(extra, p) except ImportError: # no build path setup, no worries. pass staticify_chirp_module() opts = { "py2exe" : { "includes" : "pango,atk,gobject,cairo,pangocairo,win32gui,win32com,win32com.shell,email.iterators,email.generator,gio", "compressed" : 1, "optimize" : 2, "bundle_files" : 3, # "packages" : "" } } mods = [] for mod in chirp.__all__: mods.append("chirp.%s" % mod) opts["py2exe"]["includes"] += ("," + ",".join(mods)) setup( zipfile=None, windows=[{'script' : "chirpw", 'icon_resources': [(0x0004, 'share/chirp.ico')], }], options=opts) def macos_build(): from setuptools import setup import shutil APP = ['chirp-%s.py' % CHIRP_VERSION] shutil.copy("chirpw", APP[0]) DATA_FILES = [('../Frameworks', ['/opt/local/lib/libpangox-1.0.dylib']), ('../Resources/', ['/opt/local/lib/pango']), ] OPTIONS = {'argv_emulation': True, "includes" : "gtk,atk,pangocairo,cairo"} setup( app=APP, data_files=DATA_FILES, options={'py2app': OPTIONS}, setup_requires=['py2app'], ) EXEC = 'bash ./build/macos/make_pango.sh /opt/local dist/chirp-%s.app' % CHIRP_VERSION #print "exec string: %s" % EXEC os.system(EXEC) def default_build(): from distutils.core import setup from glob import glob os.system("make -C locale clean all") desktop_files = glob("share/*.desktop") #form_files = glob("forms/*.x?l") image_files = glob("images/*") _locale_files = glob("locale/*/LC_MESSAGES/CHIRP.mo") stock_configs = glob("stock_configs/*") locale_files = [] for f in _locale_files: locale_files.append(("share/chirp/%s" % os.path.dirname(f), [f])) print "LOC: %s" % str(locale_files) xsd_files = glob("chirp*.xsd") setup( name="chirp", packages=["chirp", "chirpui"], version=CHIRP_VERSION, scripts=["chirpw"], data_files=[('share/applications', desktop_files), ('share/chirp/images', image_files), ('share/chirp', xsd_files), ('share/doc/chirp', ['COPYING']), ('share/pixmaps', ['share/chirp.png']), ('share/man/man1', ["share/chirpw.1"]), ('share/chirp/stock_configs', stock_configs), ] + locale_files) def rpttool_build(): from distutils.core import setup setup(name="rpttool", packages=["chirp"], version="0.3", scripts=["rpttool"], description="A frequency tool for ICOM D-STAR Repeaters", data_files=[('/usr/sbin', ["tools/icomsio.sh"])], ) def nuke_manifest(*files): for i in ["MANIFEST", "MANIFEST.in"]: if os.path.exists(i): os.remove(i) if not files: return f = file("MANIFEST.in", "w") for fn in files: print >>f, fn f.close() if sys.platform == "darwin": macos_build() elif sys.platform == "win32": win32_build() else: if os.path.exists("rpttool"): nuke_manifest("include tools/icomsio.sh", "include README.rpttool") rpttool_build() if os.path.exists("chirpui"): nuke_manifest("include *.xsd", "include share/*.desktop", "include share/chirp.png", "include share/*.1", "include stock_configs/*", "include COPYING") default_build() chirp-0.3.1/stock_configs/0000755000016101777760000000000012130403637016647 5ustar jenkinsnogroup00000000000000chirp-0.3.1/stock_configs/US Calling Frequencies.csv0000644000016100007500000000057611717005656022057 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL 1,6m Call,52.525000,-,0.500000,,88.5,88.5,023,NN,FM,5.00,,,,, 2,2m Call,146.520000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 3,220 Call,223.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 4,70cm Call,446.000000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, chirp-0.3.1/stock_configs/US 60 meter channels (Dial).csv0000644000016100007500000000066511717005656022324 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL 1,60m CH1,5.330500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 2,60m CH2,3.346500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 3,60m CH3,5.357000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 4,60m CH4,5.371500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 5,60m CH5,5.403500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, chirp-0.3.1/stock_configs/Marine VHF Channels.csv0000644000016100007500000001016612023560646021267 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 0,MVHF L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 1,MVHF L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 2,MVHF F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 3,MVHF F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 4,MVHF F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 5,MVHF K06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 6,MVHF K67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 7,MVHF K08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 8,MVHF K68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 9,MVHF K09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 10,MVHF K69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 11,MVHF K10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 12,MDSC K70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 13,MVHF K11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 14,MVHF K71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 15,MVHF K12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 16,MVHF K72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 17,MVHF K13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 18,MVHF K73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 19,MVHF K14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 20,MVHF K74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 21,MVHF K15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 22,MVHF K16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 23,MVHF K17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 24,MVHF K77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 25,MVHF K60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 26,MVHF K01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 27,MVHF K61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 28,MVHF K02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 29,MVHF K62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 30,MVHF K03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 31,MVHF K63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 32,MVHF K04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 33,MVHF K64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 34,MVHF K05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 35,MVHF K65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 36,MVHF K66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 37,MVHF K07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 38,MVHF K18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 39,MVHF K78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 40,MVHF K19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 41,MVHF K79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 42,MVHF K20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,, 43,MVHF K80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 44,MVHF K21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 45,MVHF K81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 46,MVHF K22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 47,MVHF K82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 48,MVHF K23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 49,MVHF K83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 50,MVHF K24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 51,MVHF K84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 52,MVHF K25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 53,MVHF K85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 54,MVHF K26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 55,MVHF K86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 56,MVHF K27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 57,MAIS K87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 58,MVHF K28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, 59,MAIS K88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,, chirp-0.3.1/stock_configs/US 60 meter channels (Center).csv0000644000016100007500000000066511717005656022673 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL 1,60m CH1,5.332000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 2,60m CH2,5.348000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 3,60m CH3,5.358500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 4,60m CH4,5.373000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 5,60m CH5,5.405000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, chirp-0.3.1/stock_configs/NOAA Weather Alert.csv0000644000016100007500000000071212023560646021056 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,NOAA1,162.4,,0,,88.5,88.5,23,NN,FM,5,,,,, 2,NOAA2,162.425,,0,,88.5,88.5,23,NN,FM,5,,,,, 3,NOAA3,162.45,,0,,88.5,88.5,23,NN,FM,5,,,,, 4,NOAA4,162.475,,0,,88.5,88.5,23,NN,FM,5,,,,, 5,NOAA5,162.5,,0,,88.5,88.5,23,NN,FM,5,,,,, 6,NOAA6,162.525,,0,,88.5,88.5,23,NN,FM,5,,,,, 7,NOAA7,162.55,,0,,88.5,88.5,23,NN,FM,5,,,,, chirp-0.3.1/stock_configs/US MURS Channels.csv0000644000016100007500000000071212023560646020542 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,MURS 1,151.820000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,, 2,MURS 2,151.880000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,, 3,MURS 3,151.940000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,, 4,Blue Dot,154.570000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,, 5,Green Dot,154.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,, chirp-0.3.1/stock_configs/EU LPD and PMR Channels.csv0000644000016100007500000001156012023560646021522 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,LPD 01,433.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 2,LPD 02,433.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 3,LPD 03,433.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 4,LPD 04,433.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 5,LPD 05,433.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 6,LPD 06,433.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 7,LPD 07,433.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 8,LPD 08,433.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 9,LPD 09,433.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 10,LPD 10,433.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 11,LPD 11,433.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 12,LPD 12,433.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 13,LPD 13,433.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 14,LPD 14,433.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 15,LPD 15,433.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 16,LPD 16,433.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 17,LPD 17,433.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 18,LPD 18,433.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 19,LPD 19,433.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 20,LPD 20,433.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 21,LPD 21,433.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 22,LPD 22,433.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 23,LPD 23,433.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 24,LPD 24,433.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 25,LPD 25,433.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 26,LPD 26,433.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 27,LPD 27,433.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 28,LPD 28,433.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 29,LPD 29,433.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 30,LPD 30,433.800000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 31,LPD 31,433.825000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 32,LPD 32,433.850000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 33,LPD 33,433.875000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 34,LPD 34,433.900000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 35,LPD 35,433.925000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 36,LPD 36,433.950000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 37,LPD 37,433.975000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 38,LPD 38,434.000000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 39,LPD 39,434.025000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 40,LPD 40,434.050000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 41,LPD 41,434.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 42,LPD 42,434.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 43,LPD 43,434.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 44,LPD 44,434.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 45,LPD 45,434.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 46,LPD 46,434.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 47,LPD 47,434.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 48,LPD 48,434.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 49,LPD 49,434.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 50,LPD 50,434.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 51,LPD 51,434.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 52,LPD 52,434.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 53,LPD 53,434.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 54,LPD 54,434.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 55,LPD 55,434.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 56,LPD 56,434.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 57,LPD 57,434.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 58,LPD 58,434.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 59,LPD 59,434.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 60,LPD 60,434.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 61,LPD 61,434.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 62,LPD 62,434.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 63,LPD 63,434.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 64,LPD 64,434.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 65,LPD 65,434.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 66,LPD 66,434.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 67,LPD 67,434.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 68,LPD 68,434.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 69,LPD 69,434.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,, 71,PMR 1,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 72,PMR 2,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 73,PMR 3,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 74,PMR 4,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 75,PMR 5,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 76,PMR 6,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 77,PMR 7,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, 78,PMR 8,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,, chirp-0.3.1/stock_configs/US FRS and GMRS Channels.csv0000644000016100007500000000275412046141665021673 0ustar jenkins00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,FRS1,462.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 2,FRS2,462.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 3,FRS3,462.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 4,FRS4,462.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 5,FRS5,462.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 6,FRS6,462.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 7,FRS7,462.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 8,FRS8,467.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 9,FRS9,467.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 10,FRS10,467.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 11,FRS11,467.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 12,FRS12,467.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 13,FRS13,467.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 14,FRS14,467.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,, 15,GMRS1,462.550000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 16,GMRS2,462.575000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 17,GMRS3,462.600000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 18,GMRS4,462.625000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 19,GMRS5,462.650000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 20,GMRS6,462.675000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 21,GMRS7,462.700000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, 22,GMRS8,462.725000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,, chirp-0.3.1/COPYING0000644000016100007500000010451311717005656013423 0ustar jenkins00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . chirp-0.3.1/setup.cfg0000644000016100007500000000022311717005656014202 0ustar jenkins00000000000000[bdist_rpm] requires = pyserial packager = Dan Smith description = A frequency tool for Icom D-STAR Repeaters vendor = KK7DS chirp-0.3.1/chirp_memory.xsd0000644000016100007500000001002411717005656015576 0ustar jenkins00000000000000 chirp-0.3.1/chirpui/0000755000016101777760000000000012130403637015457 5ustar jenkinsnogroup00000000000000chirp-0.3.1/chirpui/dstaredit.py0000644000016101777760000001336612130403635020023 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import gtk import gobject from chirpui import common, miscwidgets WIDGETW = 80 WIDGETH = 30 class CallsignEditor(gtk.HBox): __gsignals__ = { "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } def _cs_changed(self, listw, callid): if callid == 0 and self.first_fixed: return False self.emit("changed") return True def make_list(self, width): cols = [ (gobject.TYPE_INT, ""), (gobject.TYPE_INT, ""), (gobject.TYPE_STRING, _("Callsign")), ] self.listw = miscwidgets.KeyedListWidget(cols) self.listw.show() self.listw.set_editable(1, True) self.listw.connect("item-set", self._cs_changed) rend = self.listw.get_renderer(1) rend.set_property("family", "Monospace") rend.set_property("width-chars", width) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add_with_viewport(self.listw) sw.show() return sw def __init__(self, first_fixed=False, width=8): gtk.HBox.__init__(self, False, 2) self.first_fixed = first_fixed self.listw = None self.pack_start(self.make_list(width), 1, 1, 1) def set_callsigns(self, calls): if self.first_fixed: st = 1 else: st = 0 values = [] i = 1 for call in calls[st:]: self.listw.set_item(i, i, call) i += 1 def get_callsigns(self): calls = [] keys = self.listw.get_keys() for key in keys: id, idx, call = self.listw.get_item(key) calls.append(call) if self.first_fixed: calls.insert(0, "") return calls class DStarEditor(common.Editor): def __cs_changed(self, cse): job = None print "Callsigns: %s" % cse.get_callsigns() if cse == self.editor_ucall: job = common.RadioJob(None, "set_urcall_list", cse.get_callsigns()) print "Set urcall" elif cse == self.editor_rcall: job = common.RadioJob(None, "set_repeater_call_list", cse.get_callsigns()) print "Set rcall" elif cse == self.editor_mcall: job = common.RadioJob(None, "set_mycall_list", cse.get_callsigns()) if job: print "Submitting job to update call lists" self.rthread.submit(job) self.emit("changed") def make_callsigns(self): box = gtk.HBox(True, 2) fixed = self.rthread.radio.get_features().has_implicit_calls frame = gtk.Frame(_("Your callsign")) self.editor_ucall = CallsignEditor(first_fixed=fixed) self.editor_ucall.set_size_request(-1, 200) self.editor_ucall.show() frame.add(self.editor_ucall) frame.show() box.pack_start(frame, 1, 1, 0) frame = gtk.Frame(_("Repeater callsign")) self.editor_rcall = CallsignEditor(first_fixed=fixed) self.editor_rcall.set_size_request(-1, 200) self.editor_rcall.show() frame.add(self.editor_rcall) frame.show() box.pack_start(frame, 1, 1, 0) frame = gtk.Frame(_("My callsign")) self.editor_mcall = CallsignEditor() self.editor_mcall.set_size_request(-1, 200) self.editor_mcall.show() frame.add(self.editor_mcall) frame.show() box.pack_start(frame, 1, 1, 0) box.show() return box def focus(self): if self.loaded: return self.loaded = True print "Loading callsigns..." def set_ucall(calls): self.editor_ucall.set_callsigns(calls) self.editor_ucall.connect("changed", self.__cs_changed) def set_rcall(calls): self.editor_rcall.set_callsigns(calls) self.editor_rcall.connect("changed", self.__cs_changed) def set_mcall(calls): self.editor_mcall.set_callsigns(calls) self.editor_mcall.connect("changed", self.__cs_changed) job = common.RadioJob(set_ucall, "get_urcall_list") job.set_desc(_("Downloading URCALL list")) self.rthread.submit(job) job = common.RadioJob(set_rcall, "get_repeater_call_list") job.set_desc(_("Downloading RPTCALL list")) self.rthread.submit(job) job = common.RadioJob(set_mcall, "get_mycall_list") job.set_desc(_("Downloading MYCALL list")) self.rthread.submit(job) def __init__(self, rthread): common.Editor.__init__(self) self.rthread = rthread self.loaded = False self.editor_ucall = self.editor_rcall = None vbox = gtk.VBox(False, 2) vbox.pack_start(self.make_callsigns(), 0, 0, 0) tmp = gtk.Label("") tmp.show() vbox.pack_start(tmp, 1, 1, 1) self.root = vbox chirp-0.3.1/chirpui/inputdialog.py0000644000016100007500000001124211717005656016720 0ustar jenkins00000000000000# Copyright 2008 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 . import gtk from miscwidgets import make_choice from chirpui import reporting class TextInputDialog(gtk.Dialog): def respond_ok(self, _): self.response(gtk.RESPONSE_OK) def __init__(self, **args): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, buttons=buttons, **args) self.label = gtk.Label() self.label.set_size_request(300, 100) # pylint: disable-msg=E1101 self.vbox.pack_start(self.label, 1, 1, 0) self.text = gtk.Entry() self.text.connect("activate", self.respond_ok, None) # pylint: disable-msg=E1101 self.vbox.pack_start(self.text, 1, 1, 0) self.label.show() self.text.show() class ChoiceDialog(gtk.Dialog): editable = False def __init__(self, choices, **args): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, buttons=buttons, **args) self.label = gtk.Label() self.label.set_size_request(300, 100) # pylint: disable-msg=E1101 self.vbox.pack_start(self.label, 1, 1, 0) self.label.show() try: default = choices[0] except IndexError: default = None self.choice = make_choice(sorted(choices), self.editable, default) # pylint: disable-msg=E1101 self.vbox.pack_start(self.choice, 1, 1, 0) self.choice.show() self.set_default_response(gtk.RESPONSE_OK) class EditableChoiceDialog(ChoiceDialog): editable = True def __init__(self, choices, **args): ChoiceDialog.__init__(self, choices, **args) self.choice.child.set_activates_default(True) class ExceptionDialog(gtk.MessageDialog): def __init__(self, exception, **args): gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK, type=gtk.MESSAGE_ERROR, **args) self.set_property("text", _("An error has occurred")) self.format_secondary_text(str(exception)) import traceback import sys reporting.report_exception(traceback.format_exc(limit=30)) print "--- Exception Dialog: %s ---" % exception traceback.print_exc(limit=100, file=sys.stdout) print "----------------------------" class FieldDialog(gtk.Dialog): def __init__(self, **kwargs): if "buttons" not in kwargs.keys(): kwargs["buttons"] = (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) self.__fields = {} self.set_default_response(gtk.RESPONSE_OK) gtk.Dialog.__init__(self, **kwargs) def response(self, _): print "Blocking response" return def add_field(self, label, widget, validator=None): box = gtk.HBox(True, 2) lab = gtk.Label(label) lab.show() widget.set_size_request(150, -1) widget.show() box.pack_start(lab, 0, 0, 0) box.pack_start(widget, 0, 0, 0) box.show() # pylint: disable-msg=E1101 self.vbox.pack_start(box, 0, 0, 0) self.__fields[label] = widget def get_field(self, label): return self.__fields.get(label, None) class OverwriteDialog(gtk.MessageDialog): def __init__(self, filename): gtk.Dialog.__init__(self, buttons=(_("Overwrite"), gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self.set_property("text", _("File Exists")) text = \ _("The file {name} already exists. " "Do you want to overwrite it?").format(name=filename) self.format_secondary_text(text) if __name__ == "__main__": # pylint: disable-msg=C0103 d = FieldDialog(buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK)) d.add_field("Foo", gtk.Entry()) d.add_field("Bar", make_choice(["A", "B"])) d.run() gtk.main() d.destroy() chirp-0.3.1/chirpui/common.py0000644000016101777760000002524612130403635017330 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import gtk import gobject import pango import threading import time import os import traceback from chirp import errors from chirpui import reporting class Editor(gobject.GObject): __gsignals__ = { 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'usermsg' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), } root = None def __init__(self): gobject.GObject.__init__(self) self._focused = False def is_focused(self): return self._focused def focus(self): self._focused = True def unfocus(self): self._focused = False def copy_selection(self, cut=False): pass def paste_selection(self): pass def hotkey(self, action): pass gobject.type_register(Editor) def DBG(*args): if False: print " ".join(args) VERBOSE = False class RadioJob: def __init__(self, cb, func, *args, **kwargs): self.cb = cb self.cb_args = () self.func = func self.args = args self.kwargs = kwargs self.desc = "Working" self.target = None self.tb = traceback.format_stack() def __str__(self): return "RadioJob(%s,%s,%s)" % (self.func, self.args, self.kwargs) def set_desc(self, desc): self.desc = desc def set_cb_args(self, *args): self.cb_args = args def set_target(self, target): self.target = target def _execute(self, target, func): try: DBG("Running %s (%s %s)" % (self.func, str(self.args), str(self.kwargs))) if VERBOSE: print self.desc result = func(*self.args, **self.kwargs) except errors.InvalidMemoryLocation, e: result = e except Exception, e: print "Exception running RadioJob: %s" % e log_exception() print "Job Args: %s" % str(self.args) print "Job KWArgs: %s" % str(self.kwargs) print "Job Called from:%s%s" % (os.linesep, "".join(self.tb[:-1])) result = e if self.cb: gobject.idle_add(self.cb, result, *self.cb_args) def execute(self, radio): if not self.target: self.target = radio try: func = getattr(self.target, self.func) except AttributeError, e: print "No such radio function `%s' in %s" % (self.func, self.target) return self._execute(self.target, func) class RadioThread(threading.Thread, gobject.GObject): __gsignals__ = { "status" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), } def __init__(self, radio): threading.Thread.__init__(self) gobject.GObject.__init__(self) self.__queue = {} self.__counter = threading.Semaphore(0) self.__enabled = True self.__lock = threading.Lock() self.__runlock = threading.Lock() self.radio = radio def _qlock(self): self.__lock.acquire() def _qunlock(self): self.__lock.release() def _qsubmit(self, job, priority): if not self.__queue.has_key(priority): self.__queue[priority] = [] self.__queue[priority].append(job) self.__counter.release() def _queue_clear_below(self, priority): for i in range(0, priority): if self.__queue.has_key(i) and len(self.__queue[i]) != 0: return False return True def _qlock_when_idle(self, priority=10): while True: DBG("Attempting queue lock (%i)" % len(self.__queue)) self._qlock() if self._queue_clear_below(priority): return self._qunlock() time.sleep(0.1) # This is the external lock, which stops any threads from running # so that the radio can be operated synchronously def lock(self): self.__runlock.acquire() def unlock(self): self.__runlock.release() def submit(self, job, priority=0): self._qlock() self._qsubmit(job, priority) self._qunlock() def flush(self, priority=None): self._qlock() if priority is None: for i in self.__queue.keys(): self.__queue[i] = [] else: self.__queue[priority] = [] self._qunlock() def stop(self): self.flush() self.__counter.release() self.__enabled = False def status(self, msg): jobs = 0 for i in dict(self.__queue): jobs += len(self.__queue[i]) gobject.idle_add(self.emit, "status", "[%i] %s" % (jobs, msg)) def _queue_pop(self, priority): try: return self.__queue[priority].pop(0) except IndexError: return None def run(self): last_job_desc = "idle" while self.__enabled: DBG("Waiting for a job") if last_job_desc: self.status(_("Completed") + " " + last_job_desc + \ " (" + _("idle") + ")") self.__counter.acquire() self._qlock() for i in sorted(self.__queue.keys()): job = self._queue_pop(i) if job: DBG("Running job at priority %i" % i) break self._qunlock() if job: self.lock() self.status(job.desc) job.execute(self.radio) last_job_desc = job.desc self.unlock() print "RadioThread exiting" def log_exception(): import traceback import sys reporting.report_exception(traceback.format_exc(limit=30)) print "-- Exception: --" traceback.print_exc(limit=30, file=sys.stdout) print "------" def show_error(msg, parent=None): d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent, type=gtk.MESSAGE_ERROR) d.set_property("text", msg) if not parent: d.set_position(gtk.WIN_POS_CENTER_ALWAYS) d.run() d.destroy() def ask_yesno_question(msg, parent=None): d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=parent, type=gtk.MESSAGE_QUESTION) d.set_property("text", msg) if not parent: d.set_position(gtk.WIN_POS_CENTER_ALWAYS) r = d.run() d.destroy() return r == gtk.RESPONSE_YES def combo_select(box, value): store = box.get_model() iter = store.get_iter_first() while iter: if store.get(iter, 0)[0] == value: box.set_active_iter(iter) return True iter = store.iter_next(iter) return False def _add_text(d, text): v = gtk.TextView() v.get_buffer().set_text(text) v.set_editable(False) v.set_cursor_visible(False) v.show() sw = gtk.ScrolledWindow() sw.add(v) sw.show() d.vbox.pack_start(sw, 1,1,1) return v def show_error_text(msg, text, parent=None): d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent, type=gtk.MESSAGE_ERROR) d.set_property("text", msg) _add_text(d, text) if not parent: d.set_position(gtk.WIN_POS_CENTER_ALWAYS) d.set_size_request(600, 400) d.run() d.destroy() def show_warning(msg, text, parent=None, buttons=None, title="Warning", can_squelch=False): if buttons is None: buttons = gtk.BUTTONS_OK d = gtk.MessageDialog(buttons=buttons, parent=parent, type=gtk.MESSAGE_WARNING) d.set_title(title) d.set_property("text", msg) l = gtk.Label(_("Details") + ":") l.show() d.vbox.pack_start(l, 0, 0, 0) l = gtk.Label(_("Proceed?")) l.show() d.get_action_area().pack_start(l, 0, 0, 0) d.get_action_area().reorder_child(l, 0) textview = _add_text(d, text) textview.set_wrap_mode(gtk.WRAP_WORD) if not parent: d.set_position(gtk.WIN_POS_CENTER_ALWAYS) if can_squelch: cb = gtk.CheckButton(_("Do not show this next time")) cb.show() d.vbox.pack_start(cb, 0, 0, 0) d.set_size_request(600, 400) r = d.run() d.destroy() if can_squelch: return r, cb.get_active() return r def simple_diff(a, b): lines_a = a.split(os.linesep) lines_b = b.split(os.linesep) diff = "" for i in range(0, len(lines_a)): if lines_a[i] != lines_b[i]: diff += "-%s%s" % (lines_a[i], os.linesep) diff += "+%s%s" % (lines_b[i], os.linesep) else: diff += " %s%s" % (lines_a[i], os.linesep) return diff # A quick hacked up tool to show a blob of text in a dialog window # using fixed-width fonts. It also highlights lines that start with # a '-' in red bold font and '+' with blue bold font. def show_diff_blob(title, result): d = gtk.Dialog(title=title, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK)) b = gtk.TextBuffer() tags = b.get_tag_table() for color in ["red", "blue", "green", "grey"]: tag = gtk.TextTag(color) tag.set_property("foreground", color) tags.add(tag) tag = gtk.TextTag("bold") tag.set_property("weight", pango.WEIGHT_BOLD) tags.add(tag) lines = result.split(os.linesep) for line in lines: if line.startswith("-"): tags = ("red", "bold") elif line.startswith("+"): tags = ("blue", "bold") else: tags = () b.insert_with_tags_by_name(b.get_end_iter(), line + os.linesep, *tags) v = gtk.TextView(b) fontdesc = pango.FontDescription("Courier 11") v.modify_font(fontdesc) v.set_editable(False) v.show() s = gtk.ScrolledWindow() s.add(v) s.show() d.vbox.pack_start(s, 1, 1, 1) d.set_size_request(600, 400) d.run() d.destroy() chirp-0.3.1/chirpui/shiftdialog.py0000644000016101777760000001124612106644073020336 0ustar jenkinsnogroup00000000000000# # Copyright 2008 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 . import gtk import gobject import threading from chirp import errors, chirp_common class ShiftDialog(gtk.Dialog): def __init__(self, rthread, parent=None): gtk.Dialog.__init__(self, title=_("Shift"), buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) self.set_position(gtk.WIN_POS_CENTER_ALWAYS) self.rthread = rthread self.__prog = gtk.ProgressBar() self.__prog.show() self.__labl = gtk.Label("") self.__labl.show() self.vbox.pack_start(self.__prog, 1, 1, 1) self.vbox.pack_start(self.__labl, 0, 0, 0) self.quiet = False self.thread = None self.set_response_sensitive(gtk.RESPONSE_OK, False) def _status(self, msg, prog): self.__labl.set_text(msg) self.__prog.set_fraction(prog) def status(self, msg, prog): gobject.idle_add(self._status, msg, prog) def _shift_memories(self, delta, memories): count = 0.0 for i in memories: src = i.number dst = src + delta print "Moving %i to %i" % (src, dst) self.status(_("Moving {src} to {dst}").format(src=src, dst=dst), count / len(memories)) i.number = dst self.rthread.radio.set_memory(i) count += 1.0 return int(count) def _get_mems_until_hole(self, start, endokay=False): mems = [] llimit, ulimit = self.rthread.radio.get_features().memory_bounds pos = start while pos <= ulimit: self.status(_("Looking for a free spot ({number})").format(\ number=pos), 0) try: mem = self.rthread.radio.get_memory(pos) if mem.empty: break except errors.InvalidMemoryLocation: break mems.append(mem) pos += 1 if pos > ulimit and not endokay: raise errors.InvalidMemoryLocation(_("No space to insert a row")) print "Found a hole: %i" % pos return mems def _insert_hole(self, start): mems = self._get_mems_until_hole(start) mems.reverse() if mems: ret = self._shift_memories(1, mems) if ret: # Clear the hole we made self.rthread.radio.erase_memory(start) return ret else: print "No memory list?" return 0 def _delete_hole(self, start): mems = self._get_mems_until_hole(start+1, endokay=True) if mems: count = self._shift_memories(-1, mems) self.rthread.radio.erase_memory(count+start) return count else: print "No memory list?" return 0 def finished(self): if self.quiet: gobject.idle_add(self.response, gtk.RESPONSE_OK) else: gobject.idle_add(self.set_response_sensitive, gtk.RESPONSE_OK, True) def threadfn(self, newhole, func): self.status("Waiting for radio to become available", 0) self.rthread.lock() try: count = func(newhole) except errors.InvalidMemoryLocation, e: self.status(str(e), 0) self.finished() return self.rthread.unlock() self.status(_("Moved {count} memories").format(count=count), 1) self.finished() def insert(self, newhole, quiet=False): self.quiet = quiet self.thread = threading.Thread(target=self.threadfn, args=(newhole,self._insert_hole)) self.thread.start() gtk.Dialog.run(self) def delete(self, newhole, quiet=False): self.quiet = quiet self.thread = threading.Thread(target=self.threadfn, args=(newhole,self._delete_hole)) self.thread.start() gtk.Dialog.run(self) chirp-0.3.1/chirpui/editorset.py0000644000016101777760000003157712130403635020046 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import os import gtk import gobject from chirp import chirp_common, directory, generic_csv, generic_xml from chirpui import memedit, dstaredit, bankedit, common, importdialog from chirpui import inputdialog, reporting, settingsedit class EditorSet(gtk.VBox): __gsignals__ = { "want-close" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "status" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "usermsg": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "editor-selected" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), } def __init__(self, source, parent_window=None, filename=None, tempname=None): gtk.VBox.__init__(self, True, 0) self.parent_window = parent_window if isinstance(source, str): self.filename = source self.radio = directory.get_radio_by_image(self.filename) elif isinstance(source, chirp_common.Radio): self.radio = source self.filename = filename or tempname or source.VARIANT else: raise Exception("Unknown source type") self.rthread = common.RadioThread(self.radio) self.rthread.setDaemon(True) self.rthread.start() self.rthread.connect("status", lambda e, m: self.emit("status", m)) self.tabs = gtk.Notebook() self.tabs.connect("switch-page", self.tab_selected) self.tabs.set_tab_pos(gtk.POS_LEFT) self.editors = { "memedit" : None, "dstar" : None, "bank_names" : None, "bank_members" : None, "settings" : None, } if isinstance(self.radio, chirp_common.IcomDstarSupport): self.editors["memedit"] = memedit.DstarMemoryEditor(self.rthread) self.editors["dstar"] = dstaredit.DStarEditor(self.rthread) else: self.editors["memedit"] = memedit.MemoryEditor(self.rthread) self.editors["memedit"].connect("usermsg", lambda e, m: self.emit("usermsg", m)) rf = self.radio.get_features() if rf.has_bank: self.editors["bank_members"] = \ bankedit.BankMembershipEditor(self.rthread, self) if rf.has_bank_names: self.editors["bank_names"] = bankedit.BankNameEditor(self.rthread) if rf.has_settings: self.editors["settings"] = settingsedit.SettingsEditor(self.rthread) lab = gtk.Label(_("Memories")) self.tabs.append_page(self.editors["memedit"].root, lab) self.editors["memedit"].root.show() if self.editors["dstar"]: lab = gtk.Label(_("D-STAR")) self.tabs.append_page(self.editors["dstar"].root, lab) self.editors["dstar"].root.show() self.editors["dstar"].connect("changed", self.dstar_changed) if self.editors["bank_names"]: lab = gtk.Label(_("Bank Names")) self.tabs.append_page(self.editors["bank_names"].root, lab) self.editors["bank_names"].root.show() self.editors["bank_names"].connect("changed", self.banks_changed) if self.editors["bank_members"]: lab = gtk.Label(_("Banks")) self.tabs.append_page(self.editors["bank_members"].root, lab) self.editors["bank_members"].root.show() self.editors["bank_members"].connect("changed", self.banks_changed) if self.editors["settings"]: lab = gtk.Label(_("Settings")) self.tabs.append_page(self.editors["settings"].root, lab) self.editors["settings"].root.show() self.pack_start(self.tabs) self.tabs.show() # pylint: disable-msg=E1101 self.editors["memedit"].connect("changed", self.editor_changed) self.label = self.text_label = None self.make_label() self.modified = (tempname is not None) if tempname: self.filename = tempname self.update_tab() def make_label(self): self.label = gtk.HBox(False, 0) self.text_label = gtk.Label("") self.text_label.show() self.label.pack_start(self.text_label, 1, 1, 1) button = gtk.Button("X") button.set_relief(gtk.RELIEF_NONE) button.connect("clicked", lambda x: self.emit("want-close")) button.show() self.label.pack_start(button, 0, 0, 0) self.label.show() def update_tab(self): fn = os.path.basename(self.filename) if self.modified: text = "%s*" % fn else: text = fn self.text_label.set_text(self.radio.get_name() + ": " + text) def save(self, fname=None): if not fname: fname = self.filename if not os.path.exists(self.filename): return # Probably before the first "Save as" else: self.filename = fname self.rthread.lock() try: self.radio.save(fname) except: self.rthread.unlock() raise self.rthread.unlock() self.modified = False self.update_tab() def dstar_changed(self, *args): print "D-STAR editor changed" memedit = self.editors["memedit"] dstared = self.editors["dstar"] memedit.set_urcall_list(dstared.editor_ucall.get_callsigns()) memedit.set_repeater_list(dstared.editor_rcall.get_callsigns()) memedit.prefill() if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() def banks_changed(self, *args): print "Banks changed" if self.editors["bank_members"]: self.editors["bank_members"].banks_changed() if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() def editor_changed(self, *args): if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() if self.editors["bank_members"]: self.editors["bank_members"].memories_changed() def get_tab_label(self): return self.label def is_modified(self): return self.modified def _do_import_locked(self, dlgclass, src_radio, dst_rthread): # An import/export action needs to be done in the absence of any # other queued changes. So, we make sure that nothing else is # staged for the thread and lock it up. Then we use the hidden # interface to queue our own changes before opening it up to the # rest of the world. dst_rthread._qlock_when_idle(5) # Suspend job submission when idle dialog = dlgclass(src_radio, dst_rthread.radio, self.parent_window) r = dialog.run() dialog.hide() if r != gtk.RESPONSE_OK: dst_rthread._qunlock() return count = dialog.do_import(dst_rthread) print "Imported %i" % count dst_rthread._qunlock() if count > 0: self.editor_changed() gobject.idle_add(self.editors["memedit"].prefill) return count def choose_sub_device(self, radio): devices = radio.get_sub_devices() choices = [x.VARIANT for x in devices] d = inputdialog.ChoiceDialog(choices) d.label.set_text(_("The {vendor} {model} has multiple " "independent sub-devices").format( \ vendor=radio.VENDOR, model=radio.MODEL) + os.linesep + \ _("Choose one to import from:")) r = d.run() chosen = d.choice.get_active_text() d.destroy() if r == gtk.RESPONSE_CANCEL: raise Exception(_("Cancelled")) for d in devices: if d.VARIANT == chosen: return d raise Exception(_("Internal Error")) def do_import(self, filen): try: src_radio = directory.get_radio_by_image(filen) except Exception, e: common.show_error(e) return if isinstance(src_radio, chirp_common.NetworkSourceRadio): ww = importdialog.WaitWindow("Querying...", self.parent_window) ww.show() def status(status): ww.set(float(status.cur) / float(status.max)) try: src_radio.status_fn = status src_radio.do_fetch() except Exception, e: common.show_error(e) ww.hide() return ww.hide() try: if src_radio.get_features().has_sub_devices: src_radio = self.choose_sub_device(src_radio) except Exception, e: common.show_error(e) return if len(src_radio.errors) > 0: _filen = os.path.basename(filen) common.show_error_text(_("There were errors while opening {file}. " "The affected memories will not " "be importable!").format(file=_filen), "\r\n".join(src_radio.errors)) try: count = self._do_import_locked(importdialog.ImportDialog, src_radio, self.rthread) reporting.report_model_usage(src_radio, "importsrc", True) except Exception, e: common.log_exception() common.show_error(_("There was an error during " "import: {error}").format(error=e)) def do_export(self, filen): try: if filen.lower().endswith(".csv"): dst_radio = generic_csv.CSVRadio(filen) elif filen.lower().endswith(".chirp"): dst_radio = generic_xml.XMLRadio(filen) else: raise Exception(_("Unsupported file type")) except Exception, e: common.log_exception() common.show_error(e) return dst_rthread = common.RadioThread(dst_radio) dst_rthread.setDaemon(True) dst_rthread.start() try: count = self._do_import_locked(importdialog.ExportDialog, self.rthread.radio, dst_rthread) except Exception, e: common.log_exception() common.show_error(_("There was an error during " "export: {error}").format(error=e), self.parent_window) return if count <= 0: return # Wait for thread queue to complete dst_rthread._qlock_when_idle() try: dst_radio.save(filename=filen) except Exception, e: common.log_exception() common.show_error(_("There was an error during " "export: {error}").format(error=e), self) def prime(self): mem = chirp_common.Memory() mem.freq = 146010000 def cb(*args): gobject.idle_add(self.editors["memedit"].prefill) job = common.RadioJob(cb, "set_memory", mem) job.set_desc(_("Priming memory")) self.rthread.submit(job) def tab_selected(self, notebook, foo, pagenum): widget = notebook.get_nth_page(pagenum) for k,v in self.editors.items(): if v and v.root == widget: v.focus() self.emit("editor-selected", k) elif v: v.unfocus() def set_read_only(self, read_only=True): self.editors["memedit"].set_read_only(read_only) def get_read_only(self): return self.editors["memedit"].get_read_only() def prepare_close(self): self.editors["memedit"].prepare_close() def get_current_editor(self): for e in self.editors.values(): if e and self.tabs.page_num(e.root) == self.tabs.get_current_page(): return e raise Exception("No editor selected?") chirp-0.3.1/chirpui/memedit.py0000644000016101777760000015512712130403635017466 0ustar jenkinsnogroup00000000000000# # Copyright 2008 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 . if __name__ == "__main__": import sys sys.path.insert(0, "..") import threading import gtk import pango from gobject import TYPE_INT, \ TYPE_DOUBLE as TYPE_FLOAT, \ TYPE_STRING, \ TYPE_BOOLEAN, \ TYPE_PYOBJECT, \ TYPE_INT64 import gobject import pickle import os from chirpui import common, shiftdialog, miscwidgets, config, memdetail from chirp import chirp_common, errors, directory, import_logic def handle_toggle(_, path, store, col): store[path][col] = not store[path][col] def handle_ed(_, iter, new, store, col): old, = store.get(iter, col) if old != new: store.set(iter, col, new) return True else: return False class ValueErrorDialog(gtk.MessageDialog): def __init__(self, exception, **args): gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK, **args) self.set_property("text", _("Invalid value for this field")) self.format_secondary_text(str(exception)) def iter_prev(store, iter): row = store.get_path(iter)[0] if row == 0: return None return store.get_iter((row - 1,)) class MemoryEditor(common.Editor): cols = [ (_("Loc") , TYPE_INT, gtk.CellRendererText, ), (_("Frequency") , TYPE_INT64, gtk.CellRendererText, ), (_("Name") , TYPE_STRING, gtk.CellRendererText, ), (_("Tone Mode") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Tone") , TYPE_FLOAT, gtk.CellRendererCombo, ), (_("ToneSql") , TYPE_FLOAT, gtk.CellRendererCombo, ), (_("DTCS Code") , TYPE_INT, gtk.CellRendererCombo, ), (_("DTCS Rx Code") , TYPE_INT, gtk.CellRendererCombo, ), (_("DTCS Pol") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Cross Mode") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Duplex") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Offset") , TYPE_INT64, gtk.CellRendererText, ), (_("Mode") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Power") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Tune Step") , TYPE_FLOAT, gtk.CellRendererCombo, ), (_("Skip") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Comment") , TYPE_STRING, gtk.CellRendererText, ), ("_filled" , TYPE_BOOLEAN, None, ), ("_hide_cols" , TYPE_PYOBJECT,None, ), ("_extd" , TYPE_STRING, None, ), ] defaults = { _("Name") : "", _("Frequency") : 146010000, _("Tone") : 88.5, _("ToneSql") : 88.5, _("DTCS Code") : 23, _("DTCS Rx Code") : 23, _("DTCS Pol") : "NN", _("Cross Mode") : "Tone->Tone", _("Duplex") : "", _("Offset") : 0, _("Mode") : "FM", _("Power") : "", _("Tune Step") : 5.0, _("Tone Mode") : "", _("Skip") : "", _("Comment") : "", } choices = { _("Tone") : chirp_common.TONES, _("ToneSql") : chirp_common.TONES, _("DTCS Code") : sorted(chirp_common.DTCS_CODES + chirp_common.DTCS_EXTRA_CODES), _("DTCS Rx Code") : sorted(chirp_common.DTCS_CODES + chirp_common.DTCS_EXTRA_CODES), _("DTCS Pol") : ["NN", "NR", "RN", "RR"], _("Mode") : chirp_common.MODES, _("Power") : [], _("Duplex") : ["", "-", "+", "split", "off"], _("Tune Step") : chirp_common.TUNING_STEPS, _("Tone Mode") : ["", "Tone", "TSQL", "DTCS"], _("Cross Mode") : chirp_common.CROSS_MODES, } def ed_name(self, _, __, new, ___): return self.rthread.radio.filter_name(new) def ed_offset(self, _, path, new, __): 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) def set_offset(path, offset): if offset > 0: dup = "+" elif offset == 0: dup = "" else: dup = "-" offset *= -1 if offset: self.store.set(iter, self.col(_("Offset")), offset) self.store.set(iter, self.col(_("Duplex")), dup) def set_ts(ts): self.store.set(iter, self.col(_("Tune Step")), ts) def get_ts(path): return self.store.get(iter, self.col(_("Tune Step")))[0] try: new = chirp_common.parse_freq(new) except ValueError, e: print e new = None 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 return new def ed_loc(self, _, path, new, __): iter = self.store.get_iter(path) curloc, = self.store.get(iter, self.col(_("Loc"))) job = common.RadioJob(None, "erase_memory", curloc) job.set_desc(_("Erasing memory {loc}").format(loc=curloc)) self.rthread.submit(job) self.need_refresh = True return new def ed_duplex(self, _foo1, path, new, _foo2): if new == "": return # Fast path outta here iter = self.store.get_iter(path) freq, = self.store.get(iter, self.col(_("Frequency"))) if new == "split": # If we're going to split mode, use the current # 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 self.store.set(iter, self.col(_("Offset")), abs(offset)) return new def _get_cols_to_hide(self, iter): tmode, duplex, cmode = self.store.get(iter, self.col(_("Tone Mode")), self.col(_("Duplex")), self.col(_("Cross Mode"))) hide = [] txmode, rxmode = cmode.split("->") if tmode == "Tone": hide += [self.col(_("ToneSql")), self.col(_("DTCS Code")), self.col(_("DTCS Rx Code")), self.col(_("DTCS Pol")), self.col(_("Cross Mode"))] elif tmode == "TSQL": if self._features.has_ctone: hide += [self.col(_("Tone"))] hide += [self.col(_("DTCS Code")), self.col(_("DTCS Rx Code")), self.col(_("DTCS Pol")), self.col(_("Cross Mode"))] elif tmode == "DTCS": hide += [self.col(_("Tone")), self.col(_("ToneSql")), self.col(_("Cross Mode")), self.col(_("DTCS Rx Code"))] elif tmode == "" or tmode == "(None)": hide += [self.col(_("Tone")), self.col(_("ToneSql")), self.col(_("DTCS Code")), self.col(_("DTCS Rx Code")), self.col(_("DTCS Pol")), self.col(_("Cross Mode"))] elif tmode == "Cross": if txmode != "Tone": hide += [self.col(_("Tone"))] if txmode != "DTCS": hide += [self.col(_("DTCS Code"))] if rxmode != "Tone": hide += [self.col(_("ToneSql"))] if rxmode != "DTCS": hide += [self.col(_("DTCS Rx Code"))] if "DTCS" not in cmode: hide += [self.col(_("DTCS Pol"))] if duplex == "" or duplex == "(None)": hide += [self.col(_("Offset"))] return hide def maybe_hide_cols(self, iter): hide_cols = self._get_cols_to_hide(iter) self.store.set(iter, self.col("_hide_cols"), hide_cols) def edited(self, rend, path, new, cap): if self.read_only: common.show_error(_("Unable to make changes to this model")) return iter = self.store.get_iter(path) if not self.store.get(iter, self.col("_filled"))[0] \ and self.store.get(iter, self.col(_("Frequency")))[0] == 0: print _("Editing new item, taking defaults") self.insert_new(iter) colnum = self.col(cap) funcs = { _("Loc") : self.ed_loc, _("Name") : self.ed_name, _("Frequency") : self.ed_freq, _("Duplex") : self.ed_duplex, _("Offset") : self.ed_offset, } if funcs.has_key(cap): new = funcs[cap](rend, path, new, colnum) if new is None: print _("Bad value for {col}: {val}").format(col=cap, val=new) return if self.store.get_column_type(colnum) == TYPE_INT: new = int(new) elif self.store.get_column_type(colnum) == TYPE_FLOAT: new = float(new) elif self.store.get_column_type(colnum) == TYPE_BOOLEAN: new = bool(new) elif self.store.get_column_type(colnum) == TYPE_STRING: if new == "(None)": new = "" if not handle_ed(rend, iter, new, self.store, self.col(cap)) and \ cap != _("Frequency"): # No change was made # For frequency, we make an exception, since the handler might # have altered the duplex. That needs to be fixed. return mem = self._get_memory(iter) msgs = self.rthread.radio.validate_memory(mem) if msgs: common.show_error(_("Error setting memory") + \ "\r\n\r\n".join(msgs)) self.prefill() return mem.empty = False job = common.RadioJob(self._set_memory_cb, "set_memory", mem) job.set_desc(_("Writing memory {number}").format(number=mem.number)) self.rthread.submit(job) self.store.set(iter, self.col("_filled"), True) self.maybe_hide_cols(iter) persist_defaults = [_("Power"), _("Frequency"), _("Mode")] if cap in persist_defaults: self.defaults[cap] = new def _render(self, colnum, val, iter=None, hide=[]): if colnum in hide and self.hide_unused: return "" if colnum == self.col(_("Frequency")): val = chirp_common.format_freq(val) elif colnum in [self.col(_("DTCS Code")), self.col(_("DTCS Rx Code"))]: val = "%03i" % int(val) elif colnum == self.col(_("Offset")): val = chirp_common.format_freq(val) elif colnum in [self.col(_("Tone")), self.col(_("ToneSql"))]: val = "%.1f" % val elif colnum in [self.col(_("Tone Mode")), self.col(_("Duplex"))]: if not val: val = "(None)" elif colnum == self.col(_("Loc")) and iter is not None: extd, = self.store.get(iter, self.col("_extd")) if extd: val = extd return val def render(self, _, rend, model, iter, colnum): val, hide = model.get(iter, colnum, self.col("_hide_cols")) val = self._render(colnum, val, iter, hide or []) rend.set_property("text", "%s" % val) def insert_new(self, iter, loc=None): line = [] for key, val in self.defaults.items(): line.append(self.col(key)) line.append(val) if not loc: loc, = self.store.get(iter, self.col(_("Loc"))) self.store.set(iter, 0, loc, *tuple(line)) return self._get_memory(iter) def insert_easy(self, store, _iter, delta): if delta < 0: iter = store.insert_before(_iter) else: iter = store.insert_after(_iter) newpos, = store.get(_iter, self.col(_("Loc"))) newpos += delta print "Insert easy: %i" % delta mem = self.insert_new(iter, newpos) job = common.RadioJob(None, "set_memory", mem) job.set_desc(_("Writing memory {number}").format(number=mem.number)) self.rthread.submit(job) def insert_hard(self, store, _iter, delta, warn=True): if isinstance(self.rthread.radio, chirp_common.LiveRadio) and warn: txt = _("This operation requires moving all subsequent channels " "by one spot until an empty location is reached. This " "can take a LONG time. Are you sure you want to do this?") if not common.ask_yesno_question(txt): return False # No change if delta <= 0: iter = _iter else: iter = store.iter_next(_iter) pos, = store.get(iter, self.col("Loc")) sd = shiftdialog.ShiftDialog(self.rthread) if delta == 0: sd.delete(pos) sd.destroy() self.prefill() else: sd.insert(pos) sd.destroy() job = common.RadioJob(lambda x: self.prefill(), "erase_memory", pos) job.set_desc(_("Adding memory {number}").format(number=pos)) self.rthread.submit(job) return True # We changed memories def _delete_rows(self, paths): to_remove = [] for path in paths: iter = self.store.get_iter(path) cur_pos, = self.store.get(iter, self.col("Loc")) to_remove.append(cur_pos) self.store.set(iter, self.col("_filled"), False) job = common.RadioJob(None, "erase_memory", cur_pos) job.set_desc(_("Erasing memory {number}").format(number=cur_pos)) self.rthread.submit(job) def handler(mem): if not isinstance(mem, Exception): if not mem.empty or self.show_empty: gobject.idle_add(self.set_memory, mem) job = common.RadioJob(handler, "get_memory", cur_pos) job.set_desc(_("Getting memory {number}").format(number=cur_pos)) self.rthread.submit(job) if not self.show_empty: # We need to actually remove the rows from the store # now, but carefully! Get a list of deleted locations # in order and proceed from the first path in the list # until we run out of rows or we've removed all the # desired ones. to_remove.sort() to_remove.reverse() iter = self.store.get_iter(paths[0]) while to_remove and iter: pos, = self.store.get(iter, self.col(_("Loc"))) if pos in to_remove: to_remove.remove(pos) if not self.store.remove(iter): break # This was the last row else: iter = self.store.iter_next(iter) return True # We changed memories def _delete_rows_and_shift(self, paths): iter = self.store.get_iter(paths[0]) starting_loc, = self.store.get(iter, self.col(_("Loc"))) for i in range(0, len(paths)): sd = shiftdialog.ShiftDialog(self.rthread) sd.delete(starting_loc, quiet=True) sd.destroy() self.prefill() return True # We changed memories def _move_up_down(self, paths, action): if action.endswith("up"): delta = -1 donor_path = paths[-1] victim_path = paths[0] else: delta = 1 donor_path = paths[0] victim_path = paths[-1] try: victim_path = (victim_path[0] + delta,) if victim_path[0] < 0: raise ValueError() donor_loc = self.store.get(self.store.get_iter(donor_path), self.col(_("Loc")))[0] victim_loc = self.store.get(self.store.get_iter(victim_path), self.col(_("Loc")))[0] except ValueError: self.emit("usermsg", "No room to %s" % (action.replace("_", " "))) return False # No change class Context: pass ctx = Context() ctx.victim_mem = None ctx.donor_loc = donor_loc ctx.done_count = 0 ctx.path_count = len(paths) # Steps: # 1. Grab the victim (the one that will need to be saved and moved # from the front to the back or back to the front) and save it # 2. Grab each memory along the way, storing it in the +delta # destination location after we get it # 3. If we're the final move, then schedule storing the victim # in the hole we created def update_selection(): sel = self.view.get_selection() sel.unselect_all() for path in paths: gobject.idle_add(sel.select_path, (path[0]+delta,)) def save_victim(mem, ctx): ctx.victim_mem = mem def store_victim(mem, dest): old = mem.number mem.number = dest job = common.RadioJob(None, "set_memory", mem) job.set_desc(\ _("Moving memory from {old} to {new}").format(old=old, new=dest)) self.rthread.submit(job) self._set_memory(self.store.get_iter(donor_path), mem) update_selection() def move_mem(mem, delta, ctx, iter): old = mem.number mem.number += delta job = common.RadioJob(None, "set_memory", mem) job.set_desc(\ _("Moving memory from {old} to {new}").format(old=old, new=old+delta)) self.rthread.submit(job) self._set_memory(iter, mem) ctx.done_count += 1 if ctx.done_count == ctx.path_count: store_victim(ctx.victim_mem, ctx.donor_loc) job = common.RadioJob(lambda m: save_victim(m, ctx), "get_memory", victim_loc) job.set_desc(_("Getting memory {number}").format(number=victim_loc)) self.rthread.submit(job) for i in range(len(paths)): path = paths[i] if delta > 0: dest = i+1 else: dest = i-1 if dest < 0 or dest >= len(paths): dest = victim_path else: dest = paths[dest] iter = self.store.get_iter(path) loc, = self.store.get(iter, self.col(_("Loc"))) job = common.RadioJob(move_mem, "get_memory", loc) job.set_cb_args(delta, ctx, self.store.get_iter(dest)) job.set_desc("Getting memory %i" % loc) self.rthread.submit(job) return True # We (scheduled some) change to the memories def _exchange_memories(self, paths): if len(paths) != 2: self.emit("usermsg", "Select two memories first") return False loc_a, = self.store.get(self.store.get_iter(paths[0]), self.col(_("Loc"))) loc_b, = self.store.get(self.store.get_iter(paths[1]), self.col(_("Loc"))) def store_mem(mem, dst): src = mem.number mem.number = dst job = common.RadioJob(None, "set_memory", mem) job.set_desc(_("Moving memory from {old} to {new}").format(old=src, new=dst)) self.rthread.submit(job) if dst == loc_a: self.prefill() job = common.RadioJob(lambda m: store_mem(m, loc_b), "get_memory", loc_a) job.set_desc(_("Getting memory {number}").format(number=loc_a)) self.rthread.submit(job) job = common.RadioJob(lambda m: store_mem(m, loc_a), "get_memory", loc_b) job.set_desc(_("Getting memory {number}").format(number=loc_b)) self.rthread.submit(job) # We (scheduled some) change to the memories return True def _show_raw(self, cur_pos): def idle_show_raw(result): gobject.idle_add(common.show_diff_blob, _("Raw memory {number}").format(number=cur_pos), result) job = common.RadioJob(idle_show_raw, "get_raw_memory", cur_pos) job.set_desc(_("Getting raw memory {number}").format(number=cur_pos)) self.rthread.submit(job) def _diff_raw(self, paths): if len(paths) != 2: common.show_error(_("You can only diff two memories!")) return loc_a = self.store.get(self.store.get_iter(paths[0]), self.col(_("Loc")))[0] loc_b = self.store.get(self.store.get_iter(paths[1]), self.col(_("Loc")))[0] raw = {} def diff_raw(which, result): raw[which] = _("Memory {number}").format(number=which) + \ os.linesep + result if len(raw.keys()) == 2: diff = common.simple_diff(raw[loc_a], raw[loc_b]) gobject.idle_add(common.show_diff_blob, _("Diff of {a} and {b}").format(a=loc_a, b=loc_b), diff) job = common.RadioJob(lambda r: diff_raw(loc_a, r), "get_raw_memory", loc_a) job.set_desc(_("Getting raw memory {number}").format(number=loc_a)) self.rthread.submit(job) job = common.RadioJob(lambda r: diff_raw(loc_b, r), "get_raw_memory", loc_b) job.set_desc(_("Getting raw memory {number}").format(number=loc_b)) self.rthread.submit(job) def edit_memory(self, memory): dlg = memdetail.MemoryDetailEditor(self._features, memory) r = dlg.run() if r == gtk.RESPONSE_OK: self.need_refresh = True mem = dlg.get_memory() mem.name = self.rthread.radio.filter_name(mem.name) job = common.RadioJob(self._set_memory_cb, "set_memory", mem) job.set_desc(_("Writing memory {number}").format(number=mem.number)) self.rthread.submit(job) self.emit("changed") dlg.destroy() def mh(self, _action, store, paths): action = _action.get_name() iter = store.get_iter(paths[0]) cur_pos, = store.get(iter, self.col(_("Loc"))) require_contiguous = ["delete_s", "move_up", "move_dn"] if action in require_contiguous: last = paths[0][0] for path in paths[1:]: if path[0] != last+1: self.emit("usermsg", _("Memories must be contiguous")) return last = path[0] changed = False if action == "insert_next": changed = self.insert_hard(store, iter, 1) elif action == "insert_prev": changed = self.insert_hard(store, iter, -1) elif action == "delete": changed = self._delete_rows(paths) elif action == "delete_s": changed = self._delete_rows_and_shift(paths) elif action in ["move_up", "move_dn"]: changed = self._move_up_down(paths, action) elif action == "exchange": changed = self._exchange_memories(paths) elif action in ["cut", "copy"]: changed = self.copy_selection(action=="cut") elif action == "paste": changed = self.paste_selection() elif action == "devshowraw": self._show_raw(cur_pos) elif action == "devdiffraw": self._diff_raw(paths) elif action == "edit": job = common.RadioJob(self.edit_memory, "get_memory", cur_pos) self.rthread.submit(job) if changed: self.emit("changed") def hotkey(self, action): if self._in_editing: # Don't forward potentially-dangerous hotkeys to the menu # handler if we're editing a cell right now return self.emit("usermsg", "") (store, paths) = self.view.get_selection().get_selected_rows() if len(paths) == 0: return self.mh(action, store, paths) def make_context_menu(self): if self._config.get_bool("developer", "state"): devmenu = """ """ else: devmenu = "" menu_xml = """ %s """ % devmenu (store, paths) = self.view.get_selection().get_selected_rows() issingle = len(paths) == 1 istwo = len(paths) == 2 actions = [ ("edit", _("Edit")), ("insert_prev", _("Insert row above")), ("insert_next", _("Insert row below")), ("delete", issingle and _("Delete") or _("Delete all")), ("delete_s", _("Delete (and shift up)")), ("move_up", _("Move up")), ("move_dn", _("Move down")), ("exchange", _("Exchange memories")), ("cut", _("Cut")), ("copy", _("Copy")), ("paste", _("Paste")), ("devshowraw", _("Show Raw Memory")), ("devdiffraw", _("Diff Raw Memories")), ] no_multiple = ["insert_prev", "insert_next", "paste", "devshowraw"] only_two = ["devdiffraw", "exchange"] ag = gtk.ActionGroup("Menu") for name, label in actions: a = gtk.Action(name, label, "", 0) a.connect("activate", self.mh, store, paths) if name in no_multiple: a.set_sensitive(issingle) if name in only_two: a.set_sensitive(istwo) ag.add_action(a) if issingle: iter = store.get_iter(paths[0]) cur_pos, = store.get(iter, self.col(_("Loc"))) if cur_pos == self._features.memory_bounds[1]: ag.get_action("delete_s").set_sensitive(False) uim = gtk.UIManager() uim.insert_action_group(ag, 0) uim.add_ui_from_string(menu_xml) return uim.get_widget("/Menu") def click_cb(self, view, event): self.emit("usermsg", "") if event.button != 3: return False menu = self.make_context_menu() menu.popup(None, None, None, event.button, event.time) return True def get_column_visible(self, col): column = self.view.get_column(col) return column.get_visible() def set_column_visible(self, col, visible): column = self.view.get_column(col) column.set_visible(visible) def cell_editing_started(self, rend, event, path): self._in_editing = True def cell_editing_stopped(self, *args): self._in_editing = False def make_editor(self): types = tuple([x[1] for x in self.cols]) self.store = gtk.ListStore(*types) self.view = gtk.TreeView(self.store) self.view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.view.set_rules_hint(True) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self.view) filled = self.col("_filled") default_col_order = [x for x,y,z in self.cols if z] try: col_order = self._config.get("column_order_%s" % \ self.__class__.__name__).split(",") if len(col_order) != len(default_col_order): raise Exception() for i in col_order: if i not in default_col_order: raise Exception() except Exception, e: print e col_order = default_col_order non_editable = ["Loc"] unsupported_cols = self.get_unsupported_columns() visible_cols = self.get_columns_visible() cols = {} i = 0 for _cap, _type, _rend in self.cols: if not _rend: continue rend = _rend() rend.connect('editing-started', self.cell_editing_started) rend.connect('editing-canceled', self.cell_editing_stopped) rend.connect('edited', self.cell_editing_stopped) if _type == TYPE_BOOLEAN: #rend.set_property("activatable", True) #rend.connect("toggled", handle_toggle, self.store, i) col = gtk.TreeViewColumn(_cap, rend, active=i, sensitive=filled) elif _rend == gtk.CellRendererCombo: if isinstance(self.choices[_cap], gtk.ListStore): choices = self.choices[_cap] else: choices = gtk.ListStore(TYPE_STRING, TYPE_STRING) for choice in self.choices[_cap]: choices.append([choice, self._render(i, choice)]) rend.set_property("model", choices) rend.set_property("text-column", 1) rend.set_property("editable", True) rend.set_property("has-entry", False) rend.connect("edited", self.edited, _cap) col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled) col.set_cell_data_func(rend, self.render, i) else: rend.set_property("editable", _cap not in non_editable) rend.connect("edited", self.edited, _cap) col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled) col.set_cell_data_func(rend, self.render, i) col.set_reorderable(True) col.set_sort_column_id(i) col.set_resizable(True) col.set_min_width(1) col.set_visible(not _cap.startswith("_") and _cap in visible_cols and not _cap in unsupported_cols) cols[_cap] = col i += 1 for cap in col_order: self.view.append_column(cols[cap]) self.store.set_sort_column_id(self.col(_("Loc")), gtk.SORT_ASCENDING) self.view.show() sw.show() self.view.connect("button_press_event", self.click_cb) return sw def col(self, caption): try: return self._cached_cols[caption] except KeyError: raise Exception(_("Internal Error: Column {name} not found").format(name=caption)) def prefill(self): self.store.clear() self._rows_in_store = 0 lo = int(self.lo_limit_adj.get_value()) hi = int(self.hi_limit_adj.get_value()) def handler(mem, number): if not isinstance(mem, Exception): if not mem.empty or self.show_empty: gobject.idle_add(self.set_memory, mem) else: mem = chirp_common.Memory() mem.number = number mem.name = "ERROR" mem.empty = True gobject.idle_add(self.set_memory, mem) for i in range(lo, hi+1): job = common.RadioJob(handler, "get_memory", i) job.set_desc(_("Getting memory {number}").format(number=i)) job.set_cb_args(i) self.rthread.submit(job, 2) if self.show_special: for i in self._features.valid_special_chans: job = common.RadioJob(handler, "get_memory", i) job.set_desc(_("Getting channel {chan}").format(chan=i)) job.set_cb_args(i) self.rthread.submit(job, 2) def _set_memory(self, iter, memory): self.store.set(iter, self.col("_filled"), not memory.empty, self.col(_("Loc")), memory.number, self.col("_extd"), memory.extd_number, self.col(_("Name")), memory.name, self.col(_("Frequency")), memory.freq, self.col(_("Tone Mode")), memory.tmode, self.col(_("Tone")), memory.rtone, self.col(_("ToneSql")), memory.ctone, self.col(_("DTCS Code")), memory.dtcs, self.col(_("DTCS Rx Code")), memory.rx_dtcs, self.col(_("DTCS Pol")), memory.dtcs_polarity, self.col(_("Cross Mode")), memory.cross_mode, self.col(_("Duplex")), memory.duplex, self.col(_("Offset")), memory.offset, self.col(_("Mode")), memory.mode, self.col(_("Power")), memory.power or "", self.col(_("Tune Step")), memory.tuning_step, self.col(_("Skip")), memory.skip, self.col(_("Comment")), memory.comment) hide = self._get_cols_to_hide(iter) self.store.set(iter, self.col("_hide_cols"), hide) def set_memory(self, memory): iter = self.store.get_iter_first() while iter is not None: loc, = self.store.get(iter, self.col(_("Loc"))) if loc == memory.number: return self._set_memory(iter, memory) iter = self.store.iter_next(iter) iter = self.store.append() self._rows_in_store += 1 self._set_memory(iter, memory) def clear_memory(self, number): iter = self.store.get_iter_first() while iter: loc, = self.store.get(iter, self.col(_("Loc"))) if loc == number: print "Deleting %i" % number # FIXME: Make the actual remove happen on callback self.store.remove(iter) job = common.RadioJob(None, "erase_memory", number) job.set_desc(_("Erasing memory {number}").format(number=number)) self.rthread.submit() break iter = self.store.iter_next(iter) def _set_mem_vals(self, mem, vals, iter): power_levels = {"" : None} for i in self._features.valid_power_levels: power_levels[str(i)] = i mem.freq = vals[self.col(_("Frequency"))] mem.number = vals[self.col(_("Loc"))] mem.extd_number = vals[self.col("_extd")] mem.name = vals[self.col(_("Name"))] mem.vfo = 0 mem.rtone = vals[self.col(_("Tone"))] mem.ctone = vals[self.col(_("ToneSql"))] mem.dtcs = vals[self.col(_("DTCS Code"))] mem.rx_dtcs = vals[self.col(_("DTCS Rx Code"))] mem.tmode = vals[self.col(_("Tone Mode"))] mem.cross_mode = vals[self.col(_("Cross Mode"))] mem.dtcs_polarity = vals[self.col(_("DTCS Pol"))] mem.duplex = vals[self.col(_("Duplex"))] mem.offset = vals[self.col(_("Offset"))] mem.mode = vals[self.col(_("Mode"))] mem.power = power_levels[vals[self.col(_("Power"))]] mem.tuning_step = vals[self.col(_("Tune Step"))] mem.skip = vals[self.col(_("Skip"))] mem.comment = vals[self.col(_("Comment"))] mem.empty = not vals[self.col("_filled")] def _get_memory(self, iter): vals = self.store.get(iter, *range(0, len(self.cols))) mem = chirp_common.Memory() self._set_mem_vals(mem, vals, iter) return mem def _limit_key(self, which): if which not in ["lo", "hi"]: raise Exception(_("Internal Error: Invalid limit {number").format(number=which)) return "%s_%s" % (directory.radio_class_id(self.rthread.radio.__class__), which) def _store_limit(self, sb, which): self._config.set_int(self._limit_key(which), int(sb.get_value())) def make_controls(self, min, max): hbox = gtk.HBox(False, 2) lab = gtk.Label(_("Memory range:")) lab.show() hbox.pack_start(lab, 0, 0, 0) lokey = self._limit_key("lo") hikey = self._limit_key("hi") lostart = self._config.is_defined(lokey) and \ self._config.get_int(lokey) or min histart = self._config.is_defined(hikey) and \ self._config.get_int(hikey) or 25 self.lo_limit_adj = gtk.Adjustment(lostart, min, max-1, 1, 10) lo = gtk.SpinButton(self.lo_limit_adj) lo.connect("value-changed", self._store_limit, "lo") lo.show() hbox.pack_start(lo, 0, 0, 0) lab = gtk.Label(" - ") lab.show() hbox.pack_start(lab, 0, 0, 0) self.hi_limit_adj = gtk.Adjustment(histart, min+1, max, 1, 10) hi = gtk.SpinButton(self.hi_limit_adj) hi.connect("value-changed", self._store_limit, "hi") hi.show() hbox.pack_start(hi, 0, 0, 0) refresh = gtk.Button(_("Go")) refresh.show() refresh.connect("clicked", lambda x: self.prefill()) hbox.pack_start(refresh, 0, 0, 0) def activate_go(widget): refresh.clicked() def set_hi(widget, event): loval = self.lo_limit_adj.get_value() hival = self.hi_limit_adj.get_value() if loval >= hival: self.hi_limit_adj.set_value(loval + 25) lo.connect_after("focus-out-event", set_hi) lo.connect_after("activate", activate_go) hi.connect_after("activate", activate_go) sep = gtk.VSeparator() sep.show() sep.set_size_request(20, -1) hbox.pack_start(sep, 0, 0, 0) showspecial = gtk.CheckButton(_("Special Channels")) showspecial.set_active(self.show_special) showspecial.connect("toggled", lambda x: self.set_show_special(x.get_active())) showspecial.show() hbox.pack_start(showspecial, 0, 0, 0) showempty = gtk.CheckButton(_("Show Empty")) showempty.set_active(self.show_empty); showempty.connect("toggled", lambda x: self.set_show_empty(x.get_active())) showempty.show() hbox.pack_start(showempty, 0, 0, 0) hbox.show() return hbox def set_show_special(self, show): self.show_special = show self.prefill() self._config.set_bool("show_special", show) def set_show_empty(self, show): self.show_empty = show self.prefill() self._config.set_bool("hide_empty", not show) def set_read_only(self, read_only): self.read_only = read_only def get_read_only(self): return self.read_only def set_hide_unused(self, hide_unused): self.hide_unused = hide_unused self.prefill() self._config.set_bool("hide_unused", hide_unused) def __cache_columns(self): # We call self.col() a lot. Caching the name->column# lookup # makes a significant performance improvement self._cached_cols = {} i = 0 for x in self.cols: self._cached_cols[x[0]] = i i += 1 def get_unsupported_columns(self): maybe_hide = [ ("has_dtcs", _("DTCS Code")), ("has_rx_dtcs", _("DTCS Rx Code")), ("has_dtcs_polarity", _("DTCS Pol")), ("has_mode", _("Mode")), ("has_offset", _("Offset")), ("has_name", _("Name")), ("has_tuning_step", _("Tune Step")), ("has_name", _("Name")), ("has_ctone", _("ToneSql")), ("has_cross", _("Cross Mode")), ("has_comment", _("Comment")), ("valid_tmodes", _("Tone Mode")), ("valid_tmodes", _("Tone")), ("valid_duplexes", _("Duplex")), ("valid_skips", _("Skip")), ("valid_power_levels", _("Power")), ] unsupported = [] for feature, colname in maybe_hide: if feature.startswith("has_"): supported = self._features[feature] print "%s supported: %s" % (colname, supported) elif feature.startswith("valid_"): supported = len(self._features[feature]) != 0 if not supported: unsupported.append(colname) return unsupported def get_columns_visible(self): unsupported = self.get_unsupported_columns() driver = directory.radio_class_id(self.rthread.radio.__class__) user_visible = self._config.get(driver, "memedit_columns") if user_visible: user_visible = user_visible.split(",") else: # No setting for this radio, so assume all user_visible = [x[0] for x in self.cols if x not in unsupported] return user_visible def __init__(self, rthread): common.Editor.__init__(self) self.rthread = rthread self.defaults = dict(self.defaults) self._config = config.get("memedit") self.allowed_bands = [144, 440] self.count = 100 self.show_special = self._config.get_bool("show_special") self.show_empty = not self._config.get_bool("hide_empty") self.hide_unused = self._config.get_bool("hide_unused") self.read_only = False self.need_refresh = False self._in_editing = False self.lo_limit_adj = self.hi_limit_adj = None self.store = self.view = None self.__cache_columns() self._features = self.rthread.radio.get_features() (min, max) = self._features.memory_bounds self.choices[_("Mode")] = self._features["valid_modes"] self.choices[_("Tone Mode")] = self._features["valid_tmodes"] self.choices[_("Cross Mode")] = self._features["valid_cross_modes"] self.choices[_("Skip")] = self._features["valid_skips"] self.choices[_("Power")] = [str(x) for x in self._features["valid_power_levels"]] self.choices[_("DTCS Pol")] = self._features["valid_dtcs_pols"] if self._features["valid_power_levels"]: self.defaults[_("Power")] = self._features["valid_power_levels"][0] self.choices[_("Duplex")] = list(self._features.valid_duplexes) if self.defaults[_("Mode")] not in self._features.valid_modes: self.defaults[_("Mode")] = self._features.valid_modes[0] vbox = gtk.VBox(False, 2) vbox.pack_start(self.make_controls(min, max), 0, 0, 0) vbox.pack_start(self.make_editor(), 1, 1, 1) vbox.show() self.prefill() self.choices["Mode"] = self._features.valid_modes self.root = vbox self.prefill() # Run low priority jobs to get the rest of the memories hi = int(self.hi_limit_adj.get_value()) for i in range(hi, max+1): job = common.RadioJob(None, "get_memory", i) job.set_desc(_("Getting memory {number}").format(number=i)) self.rthread.submit(job, 10) def _set_memory_cb(self, result): if isinstance(result, Exception): # FIXME: This can't be in the thread dlg = ValueErrorDialog(result) dlg.run() dlg.destroy() self.prefill() elif self.need_refresh: self.prefill() self.need_refresh = False self.emit('changed') def copy_selection(self, cut=False): (store, paths) = self.view.get_selection().get_selected_rows() maybe_cut = [] selection = [] for path in paths: iter = store.get_iter(path) mem = self._get_memory(iter) selection.append(mem.dupe()) maybe_cut.append((iter, mem)) if cut: for iter, mem in maybe_cut: mem.empty = True job = common.RadioJob(self._set_memory_cb, "erase_memory", mem.number) job.set_desc(_("Cutting memory {number}").format(number=mem.number)) self.rthread.submit(job) self._set_memory(iter, mem) result = pickle.dumps((self._features, selection)) clipboard = gtk.Clipboard(selection="PRIMARY") clipboard.set_text(result) return cut # Only changed if we did a cut def _paste_selection(self, clipboard, text, data): if not text: return (store, paths) = self.view.get_selection().get_selected_rows() if len(paths) > 1: common.show_error("To paste, select only the starting location") return iter = store.get_iter(paths[0]) always = False try: src_features, mem_list = pickle.loads(text) except Exception: print "Paste failed to unpickle" return if (paths[0][0] + len(mem_list)) > self._rows_in_store: common.show_error(_("Unable to paste {src} memories into " "{dst} rows. Increase the memory bounds " "or show empty memories.").format(\ src=len(mem_list), dst=(self._rows_in_store - paths[0][0]))) return for mem in mem_list: if mem.empty: iter = self.store.iter_next(iter) continue loc, filled = store.get(iter, self.col(_("Loc")), self.col("_filled")) if filled and not always: d = miscwidgets.YesNoDialog(title=_("Overwrite?"), buttons=(gtk.STOCK_YES, 1, gtk.STOCK_NO, 2, gtk.STOCK_CANCEL, 3, "All", 4)) d.set_text(_("Overwrite location {number}?").format(number=loc)) r = d.run() d.destroy() if r == 4: always = True elif r == 3: break elif r == 2: iter = store.iter_next(iter) continue mem.name = self.rthread.radio.filter_name(mem.name) src_number = mem.number mem.number = loc try: mem = import_logic.import_mem(self.rthread.radio, src_features, mem) except import_logic.DestNotCompatible: msgs = self.rthread.radio.validate_memory(mem) errs = [x for x in msgs if isinstance(x, chirp_common.ValidationError)] if errs: d = miscwidgets.YesNoDialog(title=_("Incompatible Memory"), buttons=(gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 2)) d.set_text(_("Pasted memory {number} is not compatible with " "this radio because:").format(number=src_number) +\ os.linesep + os.linesep.join(msgs)) r = d.run() d.destroy() if r == 2: break else: iter = store.iter_next(iter) continue self._set_memory(iter, mem) iter = store.iter_next(iter) job = common.RadioJob(self._set_memory_cb, "set_memory", mem) job.set_desc(_("Writing memory {number}").format(number=mem.number)) self.rthread.submit(job) def paste_selection(self): clipboard = gtk.Clipboard(selection="PRIMARY") clipboard.request_text(self._paste_selection) def prepare_close(self): cols = self.view.get_columns() self._config.set("column_order_%s" % self.__class__.__name__, ",".join([x.get_title() for x in cols])) class DstarMemoryEditor(MemoryEditor): def _get_cols_to_hide(self, iter): hide = MemoryEditor._get_cols_to_hide(self, iter) mode, = self.store.get(iter, self.col(_("Mode"))) if mode != "DV": hide += [self.col("URCALL"), self.col("RPT1CALL"), self.col("RPT2CALL")] return hide def render(self, null, rend, model, iter, colnum): MemoryEditor.render(self, null, rend, model, iter, colnum) vals = model.get(iter, *tuple(range(0, len(self.cols)))) val = vals[colnum] def _enabled(sensitive): rend.set_property("sensitive", sensitive) def d_unless_mode(mode): _enabled(vals[self.col(_("Mode"))] == mode) _dv_columns = [_("URCALL"), _("RPT1CALL"), _("RPT2CALL"), _("Digital Code")] dv_columns = [self.col(x) for x in _dv_columns] if colnum in dv_columns: d_unless_mode("DV") def _get_memory(self, iter): vals = self.store.get(iter, *range(0, len(self.cols))) if vals[self.col(_("Mode"))] != "DV": return MemoryEditor._get_memory(self, iter) mem = chirp_common.DVMemory() MemoryEditor._set_mem_vals(self, mem, vals, iter) mem.dv_urcall = vals[self.col(_("URCALL"))] mem.dv_rpt1call = vals[self.col(_("RPT1CALL"))] mem.dv_rpt2call = vals[self.col(_("RPT2CALL"))] mem.dv_code = vals[self.col(_("Digital Code"))] return mem def __init__(self, rthread): # I think self.cols is "static" or "unbound" or something else # like that and += modifies the type, not self (how bizarre) self.cols = list(self.cols) new_cols = [("URCALL", TYPE_STRING, gtk.CellRendererCombo), ("RPT1CALL", TYPE_STRING, gtk.CellRendererCombo), ("RPT2CALL", TYPE_STRING, gtk.CellRendererCombo), ("Digital Code", TYPE_INT, gtk.CellRendererText), ] for col in new_cols: index = self.cols.index(("_filled", TYPE_BOOLEAN, None)) self.cols.insert(index, col) self.choices = dict(self.choices) self.defaults = dict(self.defaults) self.choices["URCALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) self.choices["RPT1CALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) self.choices["RPT2CALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) self.defaults["URCALL"] = "" self.defaults["RPT1CALL"] = "" self.defaults["RPT2CALL"] = "" self.defaults["Digital Code"] = 0 MemoryEditor.__init__(self, rthread) def ucall_cb(calls): self.defaults["URCALL"] = calls[0] for call in calls: self.choices["URCALL"].append((call, call)) if self._features.requires_call_lists: ujob = common.RadioJob(ucall_cb, "get_urcall_list") ujob.set_desc(_("Downloading URCALL list")) rthread.submit(ujob) def rcall_cb(calls): self.defaults["RPT1CALL"] = calls[0] self.defaults["RPT2CALL"] = calls[0] for call in calls: self.choices["RPT1CALL"].append((call, call)) self.choices["RPT2CALL"].append((call, call)) if self._features.requires_call_lists: rjob = common.RadioJob(rcall_cb, "get_repeater_call_list") rjob.set_desc(_("Downloading RPTCALL list")) rthread.submit(rjob) _dv_columns = ["URCALL", "RPT1CALL", "RPT2CALL", "Digital Code"] if not self._features.requires_call_lists: for i in _dv_columns: if not self.choices.has_key(i): continue column = self.view.get_column(self.col(i)) rend = column.get_cell_renderers()[0] rend.set_property("has-entry", True) for i in _dv_columns: col = self.view.get_column(self.col(i)) rend = col.get_cell_renderers()[0] rend.set_property("family", "Monospace") def set_urcall_list(self, urcalls): store = self.choices["URCALL"] store.clear() for call in urcalls: store.append((call, call)) def set_repeater_list(self, repeaters): for listname in ["RPT1CALL", "RPT2CALL"]: store = self.choices[listname] store.clear() for call in repeaters: store.append((call, call)) def _set_memory(self, iter, memory): MemoryEditor._set_memory(self, iter, memory) if isinstance(memory, chirp_common.DVMemory): self.store.set(iter, self.col("URCALL"), memory.dv_urcall, self.col("RPT1CALL"), memory.dv_rpt1call, self.col("RPT2CALL"), memory.dv_rpt2call, self.col("Digital Code"), memory.dv_code, ) else: self.store.set(iter, self.col("URCALL"), "", self.col("RPT1CALL"), "", self.col("RPT2CALL"), "", self.col("Digital Code"), 0, ) class ID800MemoryEditor(DstarMemoryEditor): pass chirp-0.3.1/chirpui/config.py0000644000016101777760000000643612130403635017305 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . from chirp import platform from ConfigParser import ConfigParser import os class ChirpConfig: def __init__(self, basepath, name="chirp.config"): self.__basepath = basepath self.__name = name self._default_section = "global" self.__config = ConfigParser() cfg = os.path.join(basepath, name) if os.path.exists(cfg): self.__config.read(cfg) def save(self): cfg = os.path.join(self.__basepath, self.__name) cfg_file = file(cfg, "w") self.__config.write(cfg_file) cfg_file.close() def get(self, key, section): if not self.__config.has_section(section): return None if not self.__config.has_option(section, key): return None return self.__config.get(section, key) def set(self, key, value, section): if not self.__config.has_section(section): self.__config.add_section(section) self.__config.set(section, key, value) def is_defined(self, key, section): return self.__config.has_option(section, key) class ChirpConfigProxy: def __init__(self, config, section="global"): self._config = config self._section = section def get(self, key, section=None): return self._config.get(key, section or self._section) def set(self, key, value, section=None): return self._config.set(key, value, section or self._section) def get_int(self, key, section=None): try: return int(self.get(key, section)) except ValueError: return 0 def set_int(self, key, value, section=None): if not isinstance(value, int): raise ValueError("Value is not an integer") self.set(key, "%i" % value, section) def get_float(self, key, section=None): try: return float(self.get(key, section)) except ValueError: return 0 def set_float(self, key, value, section=None): if not isinstance(value, float): raise ValueError("Value is not an integer") self.set(key, "%i" % value, section) def get_bool(self, key, section=None): return self.get(key, section) == "True" def set_bool(self, key, value, section=None): self.set(key, str(bool(value)), section) def is_defined(self, key, section=None): return self._config.is_defined(key, section or self._section) _CONFIG = None def get(section="global"): global _CONFIG p = platform.get_platform() if not _CONFIG: _CONFIG = ChirpConfig(p.config_dir()) return ChirpConfigProxy(_CONFIG, section) chirp-0.3.1/chirpui/__init__.py0000644000016100007500000000125611717005656016144 0ustar jenkins00000000000000# Copyright 2008 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 . chirp-0.3.1/chirpui/miscwidgets.py0000644000016100007500000005204111717005656016725 0ustar jenkins00000000000000# Copyright 2008 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 . import gtk import gobject import pango import os from chirp import platform class KeyedListWidget(gtk.HBox): __gsignals__ = { "item-selected" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "item-toggled" : (gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)), "item-set" : (gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,)), } def _toggle(self, rend, path, colnum): if self.__toggle_connected: self.__store[path][colnum] = not self.__store[path][colnum] iter = self.__store.get_iter(path) id, = self.__store.get(iter, 0) self.emit("item-toggled", id, self.__store[path][colnum]) def _edited(self, rend, path, new, colnum): iter = self.__store.get_iter(path) key, oldval = self.__store.get(iter, 0, colnum) self.__store.set(iter, colnum, new) if not self.emit("item-set", key): self.__store.set(iter, colnum, oldval) def _mouse(self, view, event): x, y = event.get_coords() path = self.__view.get_path_at_pos(int(x), int(y)) if path: self.__view.set_cursor_on_cell(path[0]) sel = self.get_selected() if sel: self.emit("item-selected", sel) def _make_view(self): colnum = -1 for typ, cap in self.columns: colnum += 1 if colnum == 0: continue # Key column if typ in [gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_FLOAT]: rend = gtk.CellRendererText() rend.set_property("ellipsize", pango.ELLIPSIZE_END) column = gtk.TreeViewColumn(cap, rend, text=colnum) elif typ in [gobject.TYPE_BOOLEAN]: rend = gtk.CellRendererToggle() rend.connect("toggled", self._toggle, colnum) column = gtk.TreeViewColumn(cap, rend, active=colnum) else: raise Exception("Unsupported type %s" % typ) column.set_sort_column_id(colnum) self.__view.append_column(column) self.__view.connect("button_press_event", self._mouse) def set_item(self, key, *values): iter = self.__store.get_iter_first() while iter: id, = self.__store.get(iter, 0) if id == key: self.__store.insert_after(iter, row=(id,)+values) self.__store.remove(iter) return iter = self.__store.iter_next(iter) self.__store.append(row=(key,) + values) self.emit("item-set", key) def get_item(self, key): iter = self.__store.get_iter_first() while iter: vals = self.__store.get(iter, *tuple(range(len(self.columns)))) if vals[0] == key: return vals iter = self.__store.iter_next(iter) return None def del_item(self, key): iter = self.__store.get_iter_first() while iter: id, = self.__store.get(iter, 0) if id == key: self.__store.remove(iter) return True iter = self.__store.iter_next(iter) return False def has_item(self, key): return self.get_item(key) is not None def get_selected(self): try: (store, iter) = self.__view.get_selection().get_selected() return store.get(iter, 0)[0] except Exception, e: print "Unable to find selected: %s" % e return None def select_item(self, key): if key is None: sel = self.__view.get_selection() sel.unselect_all() return True iter = self.__store.get_iter_first() while iter: if self.__store.get(iter, 0)[0] == key: selection = self.__view.get_selection() path = self.__store.get_path(iter) selection.select_path(path) return True iter = self.__store.iter_next(iter) return False def get_keys(self): keys = [] iter = self.__store.get_iter_first() while iter: key, = self.__store.get(iter, 0) keys.append(key) iter = self.__store.iter_next(iter) return keys def __init__(self, columns): gtk.HBox.__init__(self, True, 0) self.columns = columns types = tuple([x for x,y in columns]) self.__store = gtk.ListStore(*types) self.__view = gtk.TreeView(self.__store) self.pack_start(self.__view, 1, 1, 1) self.__toggle_connected = False self._make_view() self.__view.show() def connect(self, signame, *args): if signame == "item-toggled": self.__toggle_connected = True gtk.HBox.connect(self, signame, *args) def set_editable(self, column, is_editable): col = self.__view.get_column(column) rend = col.get_cell_renderers()[0] rend.set_property("editable", True) rend.connect("edited", self._edited, column + 1) def set_sort_column(self, column, value=None): if not value: value = column col = self.__view.get_column(column) col.set_sort_column_id(value) def get_renderer(self, colnum): return self.__view.get_column(colnum).get_cell_renderers()[0] class ListWidget(gtk.HBox): __gsignals__ = { "click-on-list" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gtk.TreeView, gtk.gdk.Event)), "item-toggled" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), } store_type = gtk.ListStore def mouse_cb(self, view, event): self.emit("click-on-list", view, event) # pylint: disable-msg=W0613 def _toggle(self, render, path, column): self._store[path][column] = not self._store[path][column] iter = self._store.get_iter(path) vals = tuple(self._store.get(iter, *tuple(range(self._ncols)))) for cb in self.toggle_cb: cb(*vals) self.emit("item-toggled", vals) def make_view(self, columns): self._view = gtk.TreeView(self._store) for _type, _col in columns: if _col.startswith("__"): continue index = columns.index((_type, _col)) if _type == gobject.TYPE_STRING or \ _type == gobject.TYPE_INT or \ _type == gobject.TYPE_FLOAT: rend = gtk.CellRendererText() column = gtk.TreeViewColumn(_col, rend, text=index) column.set_resizable(True) rend.set_property("ellipsize", pango.ELLIPSIZE_END) elif _type == gobject.TYPE_BOOLEAN: rend = gtk.CellRendererToggle() rend.connect("toggled", self._toggle, index) column = gtk.TreeViewColumn(_col, rend, active=index) else: raise Exception("Unknown column type (%i)" % index) column.set_sort_column_id(index) self._view.append_column(column) self._view.connect("button_press_event", self.mouse_cb) def __init__(self, columns, parent=True): gtk.HBox.__init__(self) # pylint: disable-msg=W0612 col_types = tuple([x for x, y in columns]) self._ncols = len(col_types) self._store = self.store_type(*col_types) self._view = None self.make_view(columns) self._view.show() if parent: self.pack_start(self._view, 1, 1, 1) self.toggle_cb = [] def packable(self): return self._view def add_item(self, *vals): if len(vals) != self._ncols: raise Exception("Need %i columns" % self._ncols) args = [] i = 0 for val in vals: args.append(i) args.append(val) i += 1 args = tuple(args) iter = self._store.append() self._store.set(iter, *args) def _remove_item(self, model, path, iter, match): vals = model.get(iter, *tuple(range(0, self._ncols))) if vals == match: model.remove(iter) def remove_item(self, *vals): if len(vals) != self._ncols: raise Exception("Need %i columns" % self._ncols) def remove_selected(self): try: (lst, iter) = self._view.get_selection().get_selected() lst.remove(iter) except Exception, e: print "Unable to remove selected: %s" % e def get_selected(self, take_default=False): (lst, iter) = self._view.get_selection().get_selected() if not iter and take_default: iter = lst.get_iter_first() return lst.get(iter, *tuple(range(self._ncols))) def move_selected(self, delta): (lst, iter) = self._view.get_selection().get_selected() pos = int(lst.get_path(iter)[0]) try: target = None if delta > 0 and pos > 0: target = lst.get_iter(pos-1) elif delta < 0: target = lst.get_iter(pos+1) except Exception, e: return False if target: return lst.swap(iter, target) def _get_value(self, model, path, iter, lst): lst.append(model.get(iter, *tuple(range(0, self._ncols)))) def get_values(self): lst = [] self._store.foreach(self._get_value, lst) return lst def set_values(self, lst): self._store.clear() for i in lst: self.add_item(*i) class TreeWidget(ListWidget): store_type = gtk.TreeStore # pylint: disable-msg=W0613 def _toggle(self, render, path, column): self._store[path][column] = not self._store[path][column] iter = self._store.get_iter(path) vals = tuple(self._store.get(iter, *tuple(range(self._ncols)))) piter = self._store.iter_parent(iter) if piter: parent = self._store.get(piter, self._key)[0] else: parent = None for cb in self.toggle_cb: cb(parent, *vals) def __init__(self, columns, key, parent=True): ListWidget.__init__(self, columns, parent) self._key = key def _add_item(self, piter, *vals): args = [] i = 0 for val in vals: args.append(i) args.append(val) i += 1 args = tuple(args) iter = self._store.append(piter) self._store.set(iter, *args) def _iter_of(self, key, iter=None): if not iter: iter = self._store.get_iter_first() while iter is not None: _id = self._store.get(iter, self._key)[0] if _id == key: return iter iter = self._store.iter_next(iter) return None def add_item(self, parent, *vals): if len(vals) != self._ncols: raise Exception("Need %i columns" % self._ncols) if not parent: self._add_item(None, *vals) else: iter = self._iter_of(parent) if iter: self._add_item(iter, *vals) else: raise Exception("Parent not found: %s", parent) def _set_values(self, parent, vals): if isinstance(vals, dict): for key, val in vals.items(): iter = self._store.append(parent) self._store.set(iter, self._key, key) self._set_values(iter, val) elif isinstance(vals, list): for i in vals: self._set_values(parent, i) elif isinstance(vals, tuple): self._add_item(parent, *vals) else: print "Unknown type: %s" % vals def set_values(self, vals): self._store.clear() self._set_values(self._store.get_iter_first(), vals) def del_item(self, parent, key): iter = self._iter_of(key, self._store.iter_children(self._iter_of(parent))) if iter: self._store.remove(iter) else: raise Exception("Item not found") def get_item(self, parent, key): iter = self._iter_of(key, self._store.iter_children(self._iter_of(parent))) if iter: return self._store.get(iter, *(tuple(range(0, self._ncols)))) else: raise Exception("Item not found") def set_item(self, parent, *vals): iter = self._iter_of(vals[self._key], self._store.iter_children(self._iter_of(parent))) if iter: args = [] i = 0 for val in vals: args.append(i) args.append(val) i += 1 self._store.set(iter, *(tuple(args))) else: raise Exception("Item not found") class ProgressDialog(gtk.Window): def __init__(self, title, parent=None): gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self.set_modal(True) self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.set_title(title) if parent: self.set_transient_for(parent) self.set_resizable(False) vbox = gtk.VBox(False, 2) self.label = gtk.Label("") self.label.set_size_request(100, 50) self.label.show() self.pbar = gtk.ProgressBar() self.pbar.show() vbox.pack_start(self.label, 0, 0, 0) vbox.pack_start(self.pbar, 0, 0, 0) vbox.show() self.add(vbox) def set_text(self, text): self.label.set_text(text) self.queue_draw() while gtk.events_pending(): gtk.main_iteration_do(False) def set_fraction(self, frac): self.pbar.set_fraction(frac) self.queue_draw() while gtk.events_pending(): gtk.main_iteration_do(False) class LatLonEntry(gtk.Entry): def __init__(self, *args): gtk.Entry.__init__(self, *args) self.connect("changed", self.format) def format(self, entry): string = entry.get_text() if string is None: return deg = u"\u00b0" while " " in string: if "." in string: break elif deg not in string: string = string.replace(" ", deg) elif "'" not in string: string = string.replace(" ", "'") elif '"' not in string: string = string.replace(" ", '"') else: string = string.replace(" ", "") entry.set_text(string) def parse_dd(self, string): return float(string) def parse_dm(self, string): string = string.strip() string = string.replace(' ', ' ') (_degrees, _minutes) = string.split(' ', 2) degrees = int(_degrees) minutes = float(_minutes) return degrees + (minutes / 60.0) def parse_dms(self, string): string = string.replace(u"\u00b0", " ") string = string.replace('"', ' ') string = string.replace("'", ' ') string = string.replace(' ', ' ') string = string.strip() items = string.split(' ') if len(items) > 3: raise Exception("Invalid format") elif len(items) == 3: deg = items[0] mns = items[1] sec = items[2] elif len(items) == 2: deg = items[0] mns = items[1] sec = 0 elif len(items) == 1: deg = items[0] mns = 0 sec = 0 else: deg = 0 mns = 0 sec = 0 degrees = int(deg) minutes = int(mns) seconds = float(sec) return degrees + (minutes / 60.0) + (seconds / 3600.0) def value(self): string = self.get_text() try: return self.parse_dd(string) except: try: return self.parse_dm(string) except: try: return self.parse_dms(string) except Exception, e: print "DMS: %s" % e raise Exception("Invalid format") def validate(self): try: self.value() return True except: return False class YesNoDialog(gtk.Dialog): def __init__(self, title="", parent=None, buttons=None): gtk.Dialog.__init__(self, title=title, parent=parent, buttons=buttons) self._label = gtk.Label("") self._label.show() # pylint: disable-msg=E1101 self.vbox.pack_start(self._label, 1, 1, 1) def set_text(self, text): self._label.set_text(text) def make_choice(options, editable=True, default=None): if editable: sel = gtk.combo_box_entry_new_text() else: sel = gtk.combo_box_new_text() for opt in options: sel.append_text(opt) if default: try: idx = options.index(default) sel.set_active(idx) except: pass return sel class FilenameBox(gtk.HBox): __gsignals__ = { "filename-changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } def do_browse(self, _, dir): if self.filename.get_text(): start = os.path.dirname(self.filename.get_text()) else: start = None if dir: fn = platform.get_platform().gui_select_dir(start) else: fn = platform.get_platform().gui_save_file(start, types=self.types) if fn: self.filename.set_text(fn) def do_changed(self, _): self.emit("filename_changed") def __init__(self, find_dir=False, types=[]): gtk.HBox.__init__(self, False, 0) self.types = types self.filename = gtk.Entry() self.filename.show() self.pack_start(self.filename, 1, 1, 1) browse = gtk.Button("...") browse.show() self.pack_start(browse, 0, 0, 0) self.filename.connect("changed", self.do_changed) browse.connect("clicked", self.do_browse, find_dir) def set_filename(self, fn): self.filename.set_text(fn) def get_filename(self): return self.filename.get_text() def make_pixbuf_choice(options, default=None): store = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING) box = gtk.ComboBox(store) cell = gtk.CellRendererPixbuf() box.pack_start(cell, True) box.add_attribute(cell, "pixbuf", 0) cell = gtk.CellRendererText() box.pack_start(cell, True) box.add_attribute(cell, "text", 1) _default = None for pic, value in options: iter = store.append() store.set(iter, 0, pic, 1, value) if default == value: _default = options.index((pic, value)) if _default: box.set_active(_default) return box def test(): win = gtk.Window(gtk.WINDOW_TOPLEVEL) lst = ListWidget([(gobject.TYPE_STRING, "Foo"), (gobject.TYPE_BOOLEAN, "Bar")]) lst.add_item("Test1", True) lst.set_values([("Test2", True), ("Test3", False)]) lst.show() win.add(lst) win.show() win1 = ProgressDialog("foo") win1.show() win2 = gtk.Window(gtk.WINDOW_TOPLEVEL) lle = LatLonEntry() lle.show() win2.add(lle) win2.show() win3 = gtk.Window(gtk.WINDOW_TOPLEVEL) lst = TreeWidget([(gobject.TYPE_STRING, "Id"), (gobject.TYPE_STRING, "Value")], 1) #l.add_item(None, "Foo", "Bar") #l.add_item("Foo", "Bar", "Baz") lst.set_values({"Fruit" : [("Apple", "Red"), ("Orange", "Orange")], "Pizza" : [("Cheese", "Simple"), ("Pepperoni", "Yummy")]}) lst.add_item("Fruit", "Bananna", "Yellow") lst.show() win3.add(lst) win3.show() def print_val(entry): if entry.validate(): print "Valid: %s" % entry.value() else: print "Invalid" lle.connect("activate", print_val) lle.set_text("45 13 12") try: gtk.main() except KeyboardInterrupt: pass print lst.get_values() if __name__ == "__main__": test() chirp-0.3.1/chirpui/memdetail.py0000644000016101777760000002730212130403635017774 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 . import gtk import os from chirp import chirp_common, settings from chirpui import miscwidgets, common POL = ["NN", "NR", "RN", "RR"] class ValueEditor: """Base class""" def __init__(self, features, memory, errfn, name, data=None): self._features = features self._memory = memory self._errfn = errfn self._name = name self._widget = None self._init(data) def _init(self, data): """Type-specific initialization""" def get_widget(self): """Returns the widget associated with this editor""" return self._widget def _mem_value(self): """Returns the raw value from the memory associated with this name""" if self._name.startswith("extra_"): return self._memory.extra[self._name.split("_", 1)[1]].value else: return getattr(self._memory, self._name) def _get_value(self): """Returns the value from the widget that should be set in the memory""" def update(self): """Updates the memory object with self._getvalue()""" try: newval = self._get_value() except ValueError, e: self._errfn(self._name, str(e)) return str(e) if self._name.startswith("extra_"): try: self._memory.extra[self._name.split("_", 1)[1]].value = newval except settings.InternalError, e: self._errfn(self._name, str(e)) return str(e) else: try: setattr(self._memory, self._name, newval) except chirp_common.ImmutableValueError, e: if getattr(self._memory, self._name) != self._get_value(): self._errfn(self._name, str(e)) return str(e) except ValueError, e: self._errfn(self._name, str(e)) return str(e) all_msgs = self._features.validate_memory(self._memory) errs = [] for msg in all_msgs: if isinstance(msg, chirp_common.ValidationError): errs.append(str(msg)) if errs: self._errfn(self._name, errs) else: self._errfn(self._name, None) class StringEditor(ValueEditor): def _init(self, data): self._widget = gtk.Entry(int(data)) self._widget.set_text(str(self._mem_value())) self._widget.connect("changed", self.changed) def _get_value(self): return self._widget.get_text() def changed(self, _widget): self.update() class ChoiceEditor(ValueEditor): def _init(self, data): self._widget = miscwidgets.make_choice([str(x) for x in data], False, str(self._mem_value())) self._widget.connect("changed", self.changed) def _get_value(self): return self._widget.get_active_text() def changed(self, _widget): self.update() class PowerChoiceEditor(ChoiceEditor): def _init(self, data): self._choices = data ChoiceEditor._init(self, data) def _get_value(self): choice = self._widget.get_active_text() for level in self._choices: if str(level) == choice: return level raise Exception("Internal error: power level went missing") class IntChoiceEditor(ChoiceEditor): def _get_value(self): return int(self._widget.get_active_text()) class FloatChoiceEditor(ChoiceEditor): def _get_value(self): return float(self._widget.get_active_text()) class FreqEditor(StringEditor): def _init(self, data): StringEditor._init(self, 0) def _mem_value(self): return chirp_common.format_freq(StringEditor._mem_value(self)) def _get_value(self): return chirp_common.parse_freq(self._widget.get_text()) class BooleanEditor(ValueEditor): def _init(self, data): self._widget = gtk.CheckButton("Enabled") self._widget.set_active(self._mem_value()) self._widget.connect("toggled", self.toggled) def _get_value(self): return self._widget.get_active() def toggled(self, _widget): self.update() class OffsetEditor(FreqEditor): pass class MemoryDetailEditor(gtk.Dialog): """Detail editor for a memory""" def _add(self, tab, row, name, editor, labeltxt): label = gtk.Label(labeltxt) img = gtk.Image() label.show() tab.attach(label, 0, 1, row, row+1) editor.get_widget().show() tab.attach(editor.get_widget(), 1, 2, row, row+1) img.set_size_request(15, -1) img.show() tab.attach(img, 2, 3, row, row+1) self._editors[name] = label, editor, img def _set_doc(self, name, doc): label, editor, _img = self._editors[name] self._tips.set_tip(label, doc) self._tips.set_tip(editor.get_widget(), doc) def _make_ui(self): tab = gtk.Table(len(self._order), 3, False) self.vbox.pack_start(tab, 1, 1, 1) tab.show() row = 0 def _err(name, msg): try: _img = self._editors[name][2] except KeyError: print self._editors.keys() if msg is None: _img.clear() self._tips.set_tip(_img, "") else: _img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) self._tips.set_tip(_img, str(msg)) self._errors[self._order.index(name)] = msg is not None self.set_response_sensitive(gtk.RESPONSE_OK, True not in self._errors) for name in self._order: labeltxt, editorcls, data = self._elements[name] editor = editorcls(self._features, self._memory, _err, name, data) self._add(tab, row, name, editor, labeltxt) row += 1 for setting in self._memory.extra: name = "extra_%s" % setting.get_name() if isinstance(setting.value, settings.RadioSettingValueBoolean): editor = BooleanEditor(self._features, self._memory, _err, name) self._add(tab, row, name, editor, setting.get_shortname()) self._set_doc(name, setting.__doc__) elif isinstance(setting.value, settings.RadioSettingValueList): editor = ChoiceEditor(self._features, self._memory, _err, name, setting.value.get_options()) self._add(tab, row, name, editor, setting.get_shortname()) self._set_doc(name, setting.__doc__) row += 1 self._order.append(name) def __init__(self, features, memory, parent=None): gtk.Dialog.__init__(self, title=_("Edit Memory" "#{num}").format(num=memory.number), flags=gtk.DIALOG_MODAL, parent=parent, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self._tips = gtk.Tooltips() self._features = features self._memory = memory self._editors = {} self._elements = { "freq" : (_("Frequency"), FreqEditor, None), "name" : (_("Name"), StringEditor, features.valid_name_length), "tmode" : (_("Tone Mode"), ChoiceEditor, features.valid_tmodes), "rtone" : (_("Tone"), FloatChoiceEditor, chirp_common.TONES), "ctone" : (_("ToneSql"), FloatChoiceEditor, chirp_common.TONES), "dtcs" : (_("DTCS Code"), IntChoiceEditor, chirp_common.DTCS_CODES), "dtcs_polarity" : (_("DTCS Pol"), ChoiceEditor, POL), "cross_mode" : (_("Cross mode"), ChoiceEditor, features.valid_cross_modes), "duplex" : (_("Duplex"), ChoiceEditor, features.valid_duplexes), "offset" : (_("Offset"), OffsetEditor, None), "mode" : (_("Mode"), ChoiceEditor, features.valid_modes), "tuning_step" : (_("Tune Step"), FloatChoiceEditor, features.valid_tuning_steps), "skip" : (_("Skip"), ChoiceEditor, features.valid_skips), "comment" : (_("Comment"), StringEditor, 256), } self._order = ["freq", "name", "tmode", "rtone", "ctone", "cross_mode", "dtcs", "dtcs_polarity", "duplex", "offset", "mode", "tuning_step", "skip", "comment"] if self._features.has_rx_dtcs: self._elements['rx_dtcs'] = (_("RX DTCS Code"), IntChoiceEditor, chirp_common.DTCS_CODES) self._order.insert(self._order.index("dtcs") + 1, "rx_dtcs") if self._features.valid_power_levels: self._elements["power"] = (_("Power"), PowerChoiceEditor, features.valid_power_levels) self._order.insert(self._order.index("skip"), "power") self._make_ui() self.set_default_size(400, -1) hide_rules = [ ("name", features.has_name), ("tmode", len(features.valid_tmodes) > 0), ("ctone", features.has_ctone), ("dtcs", features.has_dtcs), ("dtcs_polarity", features.has_dtcs_polarity), ("cross_mode", "Cross" in features.valid_tmodes), ("duplex", len(features.valid_duplexes) > 0), ("offset", features.has_offset), ("mode", len(features.valid_modes) > 0), ("tuning_step", features.has_tuning_step), ("skip", len(features.valid_skips) > 0), ("comment", features.has_comment), ] for name, visible in hide_rules: if not visible: for widget in self._editors[name]: if isinstance(widget, ValueEditor): widget.get_widget().hide() else: widget.hide() self._errors = [False] * len(self._order) self.connect("response", self._validate) def _validate(self, _dialog, response): if response == gtk.RESPONSE_OK: all_msgs = self._features.validate_memory(self._memory) errors = [] for msg in all_msgs: if isinstance(msg, chirp_common.ValidationError): errors.append(msg) if errors: common.show_error_text(_("Memory validation failed:"), os.linesep + os.linesep.join(errors)) self.emit_stop_by_name('response') def get_memory(self): self._memory.empty = False return self._memory chirp-0.3.1/chirpui/importdialog.py0000644000016101777760000005371212105270073020531 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import gtk import gobject import pango from chirp import errors, chirp_common, generic_xml, import_logic from chirpui import common class WaitWindow(gtk.Window): def __init__(self, msg, parent=None): gtk.Window.__init__(self) self.set_title("Please Wait") self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) if parent: self.set_transient_for(parent) self.set_position(gtk.WIN_POS_CENTER_ON_PARENT) else: self.set_position(gtk.WIN_POS_CENTER) vbox = gtk.VBox(False, 2) l = gtk.Label(msg) l.show() vbox.pack_start(l) self.prog = gtk.ProgressBar() self.prog.show() vbox.pack_start(self.prog) vbox.show() self.add(vbox) def grind(self): while gtk.events_pending(): gtk.main_iteration(False) self.prog.pulse() def set(self, fraction): while gtk.events_pending(): gtk.main_iteration(False) self.prog.set_fraction(fraction) class ImportMemoryBankJob(common.RadioJob): def __init__(self, cb, dst_mem, src_radio, src_mem): common.RadioJob.__init__(self, cb, None) self.__dst_mem = dst_mem self.__src_radio = src_radio self.__src_mem = src_mem def execute(self, radio): import_logic.import_bank(radio, self.__src_radio, self.__dst_mem, self.__src_mem) if self.cb: gobject.idle_add(self.cb, *self.cb_args) class ImportDialog(gtk.Dialog): def _check_for_dupe(self, location): iter = self.__store.get_iter_first() while iter: imp, loc = self.__store.get(iter, self.col_import, self.col_nloc) if imp and loc == location: return True iter = self.__store.iter_next(iter) return False def _toggle(self, rend, path, col): iter = self.__store.get_iter(path) imp, nloc = self.__store.get(iter, self.col_import, self.col_nloc) if not imp and self._check_for_dupe(nloc): d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) d.set_property("text", _("Location {number} is already being imported. " "Choose another value for 'New Location' " "before selection 'Import'").format(\ number=nloc)) d.run() d.destroy() else: self.__store[path][col] = not imp def _render(self, _, rend, model, iter, colnum): newloc, imp = model.get(iter, self.col_nloc, self.col_import) lo,hi = self.dst_radio.get_features().memory_bounds rend.set_property("text", "%i" % newloc) if newloc in self.used_list and imp: rend.set_property("foreground", "goldenrod") rend.set_property("weight", pango.WEIGHT_BOLD) elif newloc < lo or newloc > hi: rend.set_property("foreground", "red") rend.set_property("weight", pango.WEIGHT_BOLD) else: rend.set_property("foreground", "black") rend.set_property("weight", pango.WEIGHT_NORMAL) def _edited(self, rend, path, new, col): iter = self.__store.get_iter(path) if col == self.col_nloc: nloc, = self.__store.get(iter, self.col_nloc) try: val = int(new) except ValueError: common.show_error(_("Invalid value. Must be an integer.")) return if val == nloc: return if self._check_for_dupe(val): d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) d.set_property("text", _("Location {number} is already being " "imported").format(number=val)) d.run() d.destroy() return self.record_use_of(val) elif col == self.col_name or col == self.col_comm: val = str(new) else: return self.__store.set(iter, col, val) def get_import_list(self): import_list = [] iter = self.__store.get_iter_first() while iter: old, new, name, comm, enb = self.__store.get(iter, self.col_oloc, self.col_nloc, self.col_name, self.col_comm, self.col_import) if enb: import_list.append((old, new, name, comm)) iter = self.__store.iter_next(iter) return import_list def ensure_calls(self, dst_rthread, import_list): rlist_changed = False ulist_changed = False if not isinstance(self.dst_radio, chirp_common.IcomDstarSupport): return ulist = self.dst_radio.get_urcall_list() rlist = self.dst_radio.get_repeater_call_list() for old, new in import_list: mem = self.src_radio.get_memory(old) if isinstance(mem, chirp_common.DVMemory): if mem.dv_urcall not in ulist: print "Adding %s to ucall list" % mem.dv_urcall ulist.append(mem.dv_urcall) ulist_changed = True if mem.dv_rpt1call not in rlist: print "Adding %s to rcall list" % mem.dv_rpt1call rlist.append(mem.dv_rpt1call) rlist_changed = True if mem.dv_rpt2call not in rlist: print "Adding %s to rcall list" % mem.dv_rpt2call rlist.append(mem.dv_rpt2call) rlist_changed = True if ulist_changed: job = common.RadioJob(None, "set_urcall_list", ulist) job.set_desc(_("Updating URCALL list")) dst_rthread._qsubmit(job, 0) if rlist_changed: job = common.RadioJob(None, "set_repeater_call_list", ulist) job.set_desc(_("Updating RPTCALL list")) dst_rthread._qsubmit(job, 0) return def _convert_power(self, dst_levels, src_levels, mem): if not dst_levels: mem.power = None return elif not mem.power: # Source radio does not support power levels, so choose the # first (highest) level from the destination radio. mem.power = dst_levels[0] return "" # If both radios support power levels, we need to decide how to # convert the source power level to a valid one for the destination # radio. To do that, find the absolute level of the source value # and calculate the different between it and all the levels of the # destination, choosing the one that matches most closely. deltas = [abs(mem.power - power) for power in dst_levels] mem.power = dst_levels[deltas.index(min(deltas))] def do_soft_conversions(self, dst_features, src_features, mem): self._convert_power(dst_features.valid_power_levels, src_features.valid_power_levels, mem) return mem def do_import_banks(self): try: dst_banks = self.dst_radio.get_banks() src_banks = self.src_radio.get_banks() if not dst_banks or not src_banks: raise Exception() except Exception: print "One or more of the radios doesn't support banks" return if not isinstance(self.dst_radio, generic_xml.XMLRadio) and \ len(dst_banks) != len(src_banks): print "Source and destination radios have a different number of banks" else: self.dst_radio.set_banks(src_banks) def do_import(self, dst_rthread): i = 0 error_messages = {} import_list = self.get_import_list() src_features = self.src_radio.get_features() for old, new, name, comm in import_list: i += 1 print "%sing %i -> %i" % (self.ACTION, old, new) src = self.src_radio.get_memory(old) try: mem = import_logic.import_mem(self.dst_radio, src_features, src, {"number" : new, "name" : name, "comment": comm}) except import_logic.ImportError, e: print e error_messages[new] = str(e) continue job = common.RadioJob(None, "set_memory", mem) job.set_desc(_("Setting memory {number}").format(number=mem.number)) dst_rthread._qsubmit(job, 0) job = ImportMemoryBankJob(None, mem, self.src_radio, src) job.set_desc(_("Importing bank information")) dst_rthread._qsubmit(job, 0) if error_messages.keys(): msg = _("Error importing memories:") + "\r\n" for num, msgs in error_messages.items(): msg += "%s: %s" % (num, ",".join(msgs)) common.show_error(msg) return i def make_view(self): editable = [self.col_nloc, self.col_name, self.col_comm] self.__store = gtk.ListStore(gobject.TYPE_BOOLEAN, # Import gobject.TYPE_INT, # Source loc gobject.TYPE_INT, # Destination loc gobject.TYPE_STRING, # Name gobject.TYPE_STRING, # Frequency gobject.TYPE_STRING, # Comment gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) self.__view = gtk.TreeView(self.__store) self.__view.show() tips = gtk.Tooltips() for k in self.caps.keys(): t = self.types[k] if t == gobject.TYPE_BOOLEAN: rend = gtk.CellRendererToggle() rend.connect("toggled", self._toggle, k) column = gtk.TreeViewColumn(self.caps[k], rend, active=k, sensitive=self.col_okay, activatable=self.col_okay) else: rend = gtk.CellRendererText() if k in editable: rend.set_property("editable", True) rend.connect("edited", self._edited, k) column = gtk.TreeViewColumn(self.caps[k], rend, text=k, sensitive=self.col_okay) if k == self.col_nloc: column.set_cell_data_func(rend, self._render, k) if k in self.tips.keys(): print "Doing %s" % k lab = gtk.Label(self.caps[k]) column.set_widget(lab) tips.set_tip(lab, self.tips[k]) lab.show() column.set_sort_column_id(k) self.__view.append_column(column) self.__view.set_tooltip_column(self.col_tmsg) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self.__view) sw.show() return sw def __select_all(self, button, state): iter = self.__store.get_iter_first() while iter: _state, okay, = self.__store.get(iter, self.col_import, self.col_okay) if state is None: _state = not _state and okay else: _state = state and okay self.__store.set(iter, self.col_import, _state) iter = self.__store.iter_next(iter) def __incrnew(self, button, delta): iter = self.__store.get_iter_first() while iter: pos = self.__store.get(iter, self.col_nloc)[0] pos += delta if pos < 0: pos = 0 self.__store.set(iter, self.col_nloc, pos) iter = self.__store.iter_next(iter) def __autonew(self, button): pos = self.dst_radio.get_features().memory_bounds[0] iter = self.__store.get_iter_first() while iter: selected, okay = self.__store.get(iter, self.col_import, self.col_okay) if selected and okay: self.__store.set(iter, self.col_nloc, pos) pos += 1 iter = self.__store.iter_next(iter) def __revrnew(self, button): positions = [] iter = self.__store.get_iter_first() while iter: positions.append(self.__store.get(iter, self.col_nloc)[0]) iter = self.__store.iter_next(iter) iter = self.__store.get_iter_first() while iter: self.__store.set(iter, self.col_nloc, positions.pop()) iter = self.__store.iter_next(iter) def make_select(self): hbox = gtk.HBox(True, 2) all = gtk.Button(_("All")); all.connect("clicked", self.__select_all, True) all.set_size_request(50, 25) all.show() hbox.pack_start(all, 0, 0, 0) none = gtk.Button(_("None")); none.connect("clicked", self.__select_all, False) none.set_size_request(50, 25) none.show() hbox.pack_start(none, 0, 0, 0) inv = gtk.Button(_("Inverse")) inv.connect("clicked", self.__select_all, None) inv.set_size_request(50, 25) inv.show() hbox.pack_start(inv, 0, 0, 0) frame = gtk.Frame(_("Select")) frame.show() frame.add(hbox) hbox.show() return frame def make_adjust(self): hbox = gtk.HBox(True, 2) incr = gtk.Button("+100") incr.connect("clicked", self.__incrnew, 100) incr.set_size_request(50, 25) incr.show() hbox.pack_start(incr, 0, 0, 0) incr = gtk.Button("+10") incr.connect("clicked", self.__incrnew, 10) incr.set_size_request(50, 25) incr.show() hbox.pack_start(incr, 0, 0, 0) incr = gtk.Button("+1") incr.connect("clicked", self.__incrnew, 1) incr.set_size_request(50, 25) incr.show() hbox.pack_start(incr, 0, 0, 0) decr = gtk.Button("-1") decr.connect("clicked", self.__incrnew, -1) decr.set_size_request(50, 25) decr.show() hbox.pack_start(decr, 0, 0, 0) decr = gtk.Button("-10") decr.connect("clicked", self.__incrnew, -10) decr.set_size_request(50, 25) decr.show() hbox.pack_start(decr, 0, 0, 0) decr = gtk.Button("-100") decr.connect("clicked", self.__incrnew, -100) decr.set_size_request(50, 25) decr.show() hbox.pack_start(decr, 0, 0, 0) auto = gtk.Button(_("Auto")) auto.connect("clicked", self.__autonew) auto.set_size_request(50, 25) auto.show() hbox.pack_start(auto, 0, 0, 0) revr = gtk.Button(_("Reverse")) revr.connect("clicked", self.__revrnew) revr.set_size_request(50, 25) revr.show() hbox.pack_start(revr, 0, 0, 0) frame = gtk.Frame(_("Adjust New Location")) frame.show() frame.add(hbox) hbox.show() return frame def make_options(self): hbox = gtk.HBox(True, 2) confirm = gtk.CheckButton(_("Confirm overwrites")) confirm.connect("toggled", __set_confirm) confirm.show() hbox.pack_start(confirm, 0, 0, 0) frame = gtk.Frame(_("Options")) frame.add(hbox) frame.show() hbox.show() return frame def make_controls(self): hbox = gtk.HBox(False, 2) hbox.pack_start(self.make_select(), 0, 0, 0) hbox.pack_start(self.make_adjust(), 0, 0, 0) #hbox.pack_start(self.make_options(), 0, 0, 0) hbox.show() return hbox def build_ui(self): self.vbox.pack_start(self.make_view(), 1, 1, 1) self.vbox.pack_start(self.make_controls(), 0, 0, 0) def record_use_of(self, number): lo, hi = self.dst_radio.get_features().memory_bounds if number < lo or number > hi: return try: mem = self.dst_radio.get_memory(number) if mem and not mem.empty and number not in self.used_list: self.used_list.append(number) except errors.InvalidMemoryLocation: print "Location %i empty or at limit of destination radio" % number except errors.InvalidDataError, e: print "Got error from radio, assuming %i beyond limits: %s" % \ (number, e) def populate_list(self): start, end = self.src_radio.get_features().memory_bounds for i in range(start, end+1): if end > 50 and i % (end/50) == 0: self.ww.set(float(i) / end) try: mem = self.src_radio.get_memory(i) except errors.InvalidMemoryLocation, e: continue except Exception, e: self.__store.append(row=(False, i, i, "ERROR", chirp_common.format_freq(0), "", False, str(e), )) self.record_use_of(i) continue if mem.empty: continue self.ww.set(float(i) / end) try: msgs = self.dst_radio.validate_memory( import_logic.import_mem(self.dst_radio, self.src_radio.get_features(), mem)) except import_logic.DestNotCompatible: msgs = self.dst_radio.validate_memory(mem) errs = [x for x in msgs if isinstance(x, chirp_common.ValidationError)] if errs: msg = _("Cannot be imported because") + ":\r\n" msg += ",".join(errs) else: errs = [] msg = "Memory can be imported into target" self.__store.append(row=(not bool(msgs), mem.number, mem.number, mem.name, chirp_common.format_freq(mem.freq), mem.comment, not bool(errs), msg )) self.record_use_of(mem.number) TITLE = _("Import From File") ACTION = _("Import") def __init__(self, src_radio, dst_radio, parent=None): gtk.Dialog.__init__(self, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), title=self.TITLE, parent=parent) self.col_import = 0 self.col_nloc = 1 self.col_oloc = 2 self.col_name = 3 self.col_freq = 4 self.col_comm = 5 self.col_okay = 6 self.col_tmsg = 7 self.caps = { self.col_import : self.ACTION, self.col_nloc : _("To"), self.col_oloc : _("From"), self.col_name : _("Name"), self.col_freq : _("Frequency"), self.col_comm : _("Comment"), } self.tips = { self.col_nloc : _("Location memory will be imported into"), self.col_oloc : _("Location of memory in the file being imported"), } self.types = { self.col_import : gobject.TYPE_BOOLEAN, self.col_oloc : gobject.TYPE_INT, self.col_nloc : gobject.TYPE_INT, self.col_name : gobject.TYPE_STRING, self.col_freq : gobject.TYPE_STRING, self.col_comm : gobject.TYPE_STRING, self.col_okay : gobject.TYPE_BOOLEAN, self.col_tmsg : gobject.TYPE_STRING, } self.src_radio = src_radio self.dst_radio = dst_radio self.used_list = [] self.not_used_list = [] self.build_ui() self.set_default_size(600, 400) self.ww = WaitWindow(_("Preparing memory list..."), parent=parent) self.ww.show() self.ww.grind() self.populate_list() self.ww.hide() class ExportDialog(ImportDialog): TITLE = _("Export To File") ACTION = _("Export") if __name__ == "__main__": from chirpui import editorset import sys f = sys.argv[1] rc = editorset.radio_class_from_file(f) radio = rc(f) d = ImportDialog(radio) d.run() print d.get_import_list() chirp-0.3.1/chirpui/mainapp.py0000644000016101777760000016634012130403635017466 0ustar jenkinsnogroup00000000000000# Copyright 2008 Dan Smith # Copyright 2012 Tom Hayward # # 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 . import os import tempfile import urllib from glob import glob import shutil import time import gtk import gobject gobject.threads_init() if __name__ == "__main__": import sys sys.path.insert(0, "..") from chirpui import inputdialog, common try: import serial except ImportError,e: common.log_exception() common.show_error("\nThe Pyserial module is not installed!") from chirp import platform, generic_xml, generic_csv, directory, util from chirp import ic9x, kenwood_live, idrp, vx7, vx5, vx6 from chirp import CHIRP_VERSION, chirp_common, detect, errors from chirp import icf, ic9x_icf from chirpui import editorset, clone, miscwidgets, config, reporting, fips CONF = config.get() KEEP_RECENT = 8 RB_BANDS = { "--All--" : 0, "10 meters (29MHz)" : 29, "6 meters (54MHz)" : 5, "2 meters (144MHz)" : 14, "1.25 meters (220MHz)" : 22, "70 centimeters (440MHz)" : 4, "33 centimeters (900MHz)" : 9, "23 centimeters (1.2GHz)" : 12, } def key_bands(band): if band.startswith("-"): return -1 amount, units, mhz = band.split(" ") scale = units == "meters" and 100 or 1 return 100000 - (float(amount) * scale) class ModifiedError(Exception): pass class ChirpMain(gtk.Window): def get_current_editorset(self): page = self.tabs.get_current_page() if page is not None: return self.tabs.get_nth_page(page) else: return None def ev_tab_switched(self, pagenum=None): def set_action_sensitive(action, sensitive): self.menu_ag.get_action(action).set_sensitive(sensitive) if pagenum is not None: eset = self.tabs.get_nth_page(pagenum) else: eset = self.get_current_editorset() upload_sens = bool(eset and isinstance(eset.radio, chirp_common.CloneModeRadio)) if not eset or isinstance(eset.radio, chirp_common.LiveRadio): save_sens = False elif isinstance(eset.radio, chirp_common.NetworkSourceRadio): save_sens = False else: save_sens = True for i in ["import", "importsrc", "stock"]: set_action_sensitive(i, eset is not None and not eset.get_read_only()) for i in ["save", "saveas"]: set_action_sensitive(i, save_sens) for i in ["upload"]: set_action_sensitive(i, upload_sens) for i in ["cancelq"]: set_action_sensitive(i, eset is not None and not save_sens) for i in ["export", "close", "columns", "irbook", "irfinder", "move_up", "move_dn", "exchange", "iradioreference", "cut", "copy", "paste", "delete", "viewdeveloper"]: set_action_sensitive(i, eset is not None) def ev_status(self, editorset, msg): self.sb_radio.pop(0) self.sb_radio.push(0, msg) def ev_usermsg(self, editorset, msg): self.sb_general.pop(0) self.sb_general.push(0, msg) def ev_editor_selected(self, editorset, editortype): mappings = { "memedit" : ["view", "edit"], } for _editortype, actions in mappings.items(): for _action in actions: action = self.menu_ag.get_action(_action) action.set_sensitive(_editortype == editortype) def _connect_editorset(self, eset): eset.connect("want-close", self.do_close) eset.connect("status", self.ev_status) eset.connect("usermsg", self.ev_usermsg) eset.connect("editor-selected", self.ev_editor_selected) def do_diff_radio(self): if self.tabs.get_n_pages() < 2: common.show_error("Diff tabs requires at least two open tabs!") return esets = [] for i in range(0, self.tabs.get_n_pages()): esets.append(self.tabs.get_nth_page(i)) d = gtk.Dialog(title="Diff Radios", buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), parent=self) choices = [] for eset in esets: choices.append("%s %s (%s)" % (eset.rthread.radio.VENDOR, eset.rthread.radio.MODEL, eset.filename)) choice_a = miscwidgets.make_choice(choices, False, choices[0]) choice_a.show() chan_a = gtk.SpinButton() chan_a.get_adjustment().set_all(1, -1, 999, 1, 10, 0) chan_a.show() hbox = gtk.HBox(False, 3) hbox.pack_start(choice_a, 1, 1, 1) hbox.pack_start(chan_a, 0, 0, 0) hbox.show() d.vbox.pack_start(hbox, 0, 0, 0) choice_b = miscwidgets.make_choice(choices, False, choices[1]) choice_b.show() chan_b = gtk.SpinButton() chan_b.get_adjustment().set_all(1, -1, 999, 1, 10, 0) chan_b.show() hbox = gtk.HBox(False, 3) hbox.pack_start(choice_b, 1, 1, 1) hbox.pack_start(chan_b, 0, 0, 0) hbox.show() d.vbox.pack_start(hbox, 0, 0, 0) r = d.run() sel_a = choice_a.get_active_text() sel_chan_a = chan_a.get_value() sel_b = choice_b.get_active_text() sel_chan_b = chan_b.get_value() d.destroy() if r == gtk.RESPONSE_CANCEL: return if sel_a == sel_b: common.show_error("Can't diff the same tab!") return print "Selected %s@%i and %s@%i" % (sel_a, sel_chan_a, sel_b, sel_chan_b) eset_a = esets[choices.index(sel_a)] eset_b = esets[choices.index(sel_b)] def _show_diff(mem_b, mem_a): # Step 3: Show the diff diff = common.simple_diff(mem_a, mem_b) common.show_diff_blob("Differences", diff) def _get_mem_b(mem_a): # Step 2: Get memory b job = common.RadioJob(_show_diff, "get_raw_memory", int(sel_chan_b)) job.set_cb_args(mem_a) eset_b.rthread.submit(job) if sel_chan_a >= 0 and sel_chan_b >= 0: # Diff numbered memory # Step 1: Get memory a job = common.RadioJob(_get_mem_b, "get_raw_memory", int(sel_chan_a)) eset_a.rthread.submit(job) elif isinstance(eset_a.rthread.radio, chirp_common.CloneModeRadio) and\ isinstance(eset_b.rthread.radio, chirp_common.CloneModeRadio): # Diff whole (can do this without a job, since both are clone-mode) a = util.hexprint(eset_a.rthread.radio._mmap.get_packed()) b = util.hexprint(eset_b.rthread.radio._mmap.get_packed()) common.show_diff_blob("Differences", common.simple_diff(a, b)) else: common.show_error("Cannot diff whole live-mode radios!") def do_new(self): eset = editorset.EditorSet(_("Untitled") + ".csv", self) self._connect_editorset(eset) eset.prime() eset.show() tab = self.tabs.append_page(eset, eset.get_tab_label()) self.tabs.set_current_page(tab) def _do_manual_select(self, filename): radiolist = {} for drv, radio in directory.DRV_TO_RADIO.items(): if not issubclass(radio, chirp_common.CloneModeRadio): continue radiolist["%s %s" % (radio.VENDOR, radio.MODEL)] = drv lab = gtk.Label("""Unable to detect model! If you think that it is valid, you can select a radio model below to force an open attempt. If selecting the model manually works, please file a bug on the website and attach your image. If selecting the model does not work, it is likely that you are trying to open some other type of file. """) lab.set_justify(gtk.JUSTIFY_FILL) lab.set_line_wrap(True) lab.set_use_markup(True) lab.show() choice = miscwidgets.make_choice(sorted(radiolist.keys()), False, sorted(radiolist.keys())[0]) d = gtk.Dialog(title="Detection Failed", buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) d.vbox.pack_start(lab, 0, 0, 0) d.vbox.pack_start(choice, 0, 0, 0) d.vbox.set_spacing(5) choice.show() d.set_default_size(400, 200) #d.set_resizable(False) r = d.run() d.destroy() if r != gtk.RESPONSE_OK: return try: rc = directory.DRV_TO_RADIO[radiolist[choice.get_active_text()]] return rc(filename) except: return def do_open(self, fname=None, tempname=None): if not fname: types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"), (_("CHIRP Files") + " (*.chirp)", "*.chirp"), (_("CSV Files") + " (*.csv)", "*.csv"), (_("EVE Files (VX5)") + " (*.eve)", "*.eve"), (_("ICF Files") + " (*.icf)", "*.icf"), (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"), (_("VX6 Commander Files") + " (*.vx6)", "*.vx6"), (_("VX7 Commander Files") + " (*.vx7)", "*.vx7"), ] fname = platform.get_platform().gui_open_file(types=types) if not fname: return self.record_recent_file(fname) if icf.is_icf_file(fname): a = common.ask_yesno_question(\ _("ICF files cannot be edited, only displayed or imported " "into another file. Open in read-only mode?"), self) if not a: return read_only = True else: read_only = False if icf.is_9x_icf(fname): # We have to actually instantiate the IC9xICFRadio to get its # sub-devices radio = ic9x_icf.IC9xICFRadio(fname) devices = radio.get_sub_devices() del radio else: try: radio = directory.get_radio_by_image(fname) except errors.ImageDetectFailed: radio = self._do_manual_select(fname) if not radio: return print "Manually selected %s" % radio except Exception, e: common.log_exception() common.show_error(os.path.basename(fname) + ": " + str(e)) return if radio.get_features().has_sub_devices: devices = radio.get_sub_devices() else: devices = [radio] prio = len(devices) first_tab = False for device in devices: try: eset = editorset.EditorSet(device, self, filename=fname, tempname=tempname) except Exception, e: common.log_exception() common.show_error( _("There was an error opening {fname}: {error}").format( fname=fname, error=error)) return eset.set_read_only(read_only) self._connect_editorset(eset) eset.show() tab = self.tabs.append_page(eset, eset.get_tab_label()) if first_tab: self.tabs.set_current_page(tab) first_tab = False if hasattr(eset.rthread.radio, "errors") and \ eset.rthread.radio.errors: msg = _("{num} errors during open:").format(num=len(eset.rthread.radio.errors)) common.show_error_text(msg, "\r\n".join(eset.rthread.radio.errors)) def do_live_warning(self, radio): d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) d.set_markup("" + _("Note:") + "") msg = _("The {vendor} {model} operates in live mode. " "This means that any changes you make are immediately sent " "to the radio. Because of this, you cannot perform the " "Save or Upload operations. If you wish to " "edit the contents offline, please Export to a CSV " "file, using the File menu.").format(vendor=radio.VENDOR, model=radio.MODEL) d.format_secondary_markup(msg) again = gtk.CheckButton(_("Don't show this again")) again.show() d.vbox.pack_start(again, 0, 0, 0) d.run() CONF.set_bool("live_mode", again.get_active(), "noconfirm") d.destroy() def do_open_live(self, radio, tempname=None, read_only=False): if radio.get_features().has_sub_devices: devices = radio.get_sub_devices() else: devices = [radio] first_tab = True for device in devices: eset = editorset.EditorSet(device, self, tempname=tempname) eset.connect("want-close", self.do_close) eset.connect("status", self.ev_status) eset.set_read_only(read_only) eset.show() tab = self.tabs.append_page(eset, eset.get_tab_label()) if first_tab: self.tabs.set_current_page(tab) first_tab = False if isinstance(radio, chirp_common.LiveRadio): reporting.report_model_usage(radio, "live", True) if not CONF.get_bool("live_mode", "noconfirm"): self.do_live_warning(radio) def do_save(self, eset=None): if not eset: eset = self.get_current_editorset() # For usability, allow Ctrl-S to short-circuit to Save-As if # we are working on a yet-to-be-saved image if not os.path.exists(eset.filename): return self.do_saveas() eset.save() def do_saveas(self): eset = self.get_current_editorset() label = _("{vendor} {model} image file").format(\ vendor=eset.radio.VENDOR, model=eset.radio.MODEL) types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION, eset.radio.FILE_EXTENSION)] if isinstance(eset.radio, vx7.VX7Radio): types += [(_("VX7 Commander") + " (*.vx7)", "vx7")] elif isinstance(eset.radio, vx6.VX6Radio): types += [(_("VX6 Commander") + " (*.vx6)", "vx6")] elif isinstance(eset.radio, vx5.VX5Radio): types += [(_("EVE") + " (*.eve)", "eve")] types += [(_("VX5 Commander") + " (*.vx5)", "vx5")] while True: fname = platform.get_platform().gui_save_file(types=types) if not fname: return if os.path.exists(fname): dlg = inputdialog.OverwriteDialog(fname) owrite = dlg.run() dlg.destroy() if owrite == gtk.RESPONSE_OK: break else: break try: eset.save(fname) except Exception,e: d = inputdialog.ExceptionDialog(e) d.run() d.destroy() def cb_clonein(self, radio, emsg=None): radio.pipe.close() reporting.report_model_usage(radio, "download", bool(emsg)) if not emsg: self.do_open_live(radio, tempname="(" + _("Untitled") + ")") else: d = inputdialog.ExceptionDialog(emsg) d.run() d.destroy() def cb_cloneout(self, radio, emsg= None): radio.pipe.close() reporting.report_model_usage(radio, "upload", True) if emsg: d = inputdialog.ExceptionDialog(emsg) d.run() d.destroy() def _get_recent_list(self): recent = [] for i in range(0, KEEP_RECENT): fn = CONF.get("recent%i" % i, "state") if fn: recent.append(fn) return recent def _set_recent_list(self, recent): for fn in recent: CONF.set("recent%i" % recent.index(fn), fn, "state") def update_recent_files(self): i = 0 for fname in self._get_recent_list(): action_name = "recent%i" % i path = "/MenuBar/file/recent" old_action = self.menu_ag.get_action(action_name) if old_action: self.menu_ag.remove_action(old_action) file_basename = os.path.basename(fname).replace("_", "__") action = gtk.Action(action_name, "_%i. %s" % (i+1, file_basename), _("Open recent file {name}").format(name=fname), "") action.connect("activate", lambda a,f: self.do_open(f), fname) mid = self.menu_uim.new_merge_id() self.menu_uim.add_ui(mid, path, action_name, action_name, gtk.UI_MANAGER_MENUITEM, False) self.menu_ag.add_action(action) i += 1 def record_recent_file(self, filename): recent_files = self._get_recent_list() if filename not in recent_files: if len(recent_files) == KEEP_RECENT: del recent_files[-1] recent_files.insert(0, filename) self._set_recent_list(recent_files) self.update_recent_files() def import_stock_config(self, action, config): eset = self.get_current_editorset() count = eset.do_import(config) def copy_shipped_stock_configs(self, stock_dir): execpath = platform.get_platform().executable_path() basepath = os.path.abspath(os.path.join(execpath, "stock_configs")) if not os.path.exists(basepath): basepath = "/usr/share/chirp/stock_configs" files = glob(os.path.join(basepath, "*.csv")) for fn in files: if os.path.exists(os.path.join(stock_dir, os.path.basename(fn))): print "Skipping existing stock config" continue try: shutil.copy(fn, stock_dir) print "Copying %s -> %s" % (fn, stock_dir) except Exception, e: print "ERROR: Unable to copy %s to %s: %s" % (fn, stock_dir, e) return False return True def update_stock_configs(self): stock_dir = platform.get_platform().config_file("stock_configs") if not os.path.isdir(stock_dir): try: os.mkdir(stock_dir) except Exception, e: print "ERROR: Unable to create directory: %s" % stock_dir return if not self.copy_shipped_stock_configs(stock_dir): return def _do_import_action(config): name = os.path.splitext(os.path.basename(config))[0] action_name = "stock-%i" % configs.index(config) path = "/MenuBar/radio/stock" action = gtk.Action(action_name, name, _("Import stock " "configuration {name}").format(name=name), "") action.connect("activate", self.import_stock_config, config) mid = self.menu_uim.new_merge_id() mid = self.menu_uim.add_ui(mid, path, action_name, action_name, gtk.UI_MANAGER_MENUITEM, False) self.menu_ag.add_action(action) def _do_open_action(config): name = os.path.splitext(os.path.basename(config))[0] action_name = "openstock-%i" % configs.index(config) path = "/MenuBar/file/openstock" action = gtk.Action(action_name, name, _("Open stock " "configuration {name}").format(name=name), "") action.connect("activate", lambda a,c: self.do_open(c), config) mid = self.menu_uim.new_merge_id() mid = self.menu_uim.add_ui(mid, path, action_name, action_name, gtk.UI_MANAGER_MENUITEM, False) self.menu_ag.add_action(action) configs = glob(os.path.join(stock_dir, "*.csv")) for config in configs: _do_import_action(config) _do_open_action(config) def _confirm_experimental(self, rclass): sql_key = "warn_experimental_%s" % directory.radio_class_id(rclass) if CONF.is_defined(sql_key, "state") and \ not CONF.get_bool(sql_key, "state"): return True title = _("Proceed with experimental driver?") text = rclass.get_experimental_warning() msg = _("This radio's driver is experimental. " "Do you want to proceed?") resp, squelch = common.show_warning(msg, text, title=title, buttons=gtk.BUTTONS_YES_NO, can_squelch=True) if resp == gtk.RESPONSE_YES: CONF.set_bool(sql_key, not squelch, "state") return resp == gtk.RESPONSE_YES def do_download(self, port=None, rtype=None): d = clone.CloneSettingsDialog(parent=self) settings = d.run() d.destroy() if not settings: return rclass = settings.radio_class if issubclass(rclass, chirp_common.ExperimentalRadio) and \ not self._confirm_experimental(rclass): # User does not want to proceed with experimental driver return print "User selected %s %s on port %s" % (rclass.VENDOR, rclass.MODEL, settings.port) try: ser = serial.Serial(port=settings.port, baudrate=rclass.BAUD_RATE, rtscts=rclass.HARDWARE_FLOW, timeout=0.25) ser.flushInput() except serial.SerialException, e: d = inputdialog.ExceptionDialog(e) d.run() d.destroy() return radio = settings.radio_class(ser) fn = tempfile.mktemp() if isinstance(radio, chirp_common.CloneModeRadio): ct = clone.CloneThread(radio, "in", cb=self.cb_clonein, parent=self) ct.start() else: self.do_open_live(radio) def do_upload(self, port=None, rtype=None): eset = self.get_current_editorset() radio = eset.radio settings = clone.CloneSettings() settings.radio_class = radio.__class__ d = clone.CloneSettingsDialog(settings, parent=self) settings = d.run() d.destroy() if not settings: return if isinstance(radio, chirp_common.ExperimentalRadio) and \ not self._confirm_experimental(radio.__class__): # User does not want to proceed with experimental driver return try: ser = serial.Serial(port=settings.port, baudrate=radio.BAUD_RATE, rtscts=radio.HARDWARE_FLOW, timeout=0.25) ser.flushInput() except serial.SerialException, e: d = inputdialog.ExceptionDialog(e) d.run() d.destroy() return radio.set_pipe(ser) ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self) ct.start() def do_close(self, tab_child=None): if tab_child: eset = tab_child else: eset = self.get_current_editorset() if not eset: return False if eset.is_modified(): dlg = miscwidgets.YesNoDialog(title=_("Save Changes?"), parent=self, buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES, gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) dlg.set_text(_("File is modified, save changes before closing?")) res = dlg.run() dlg.destroy() if res == gtk.RESPONSE_YES: self.do_save(eset) elif res == gtk.RESPONSE_CANCEL: raise ModifiedError() eset.rthread.stop() eset.rthread.join() eset.prepare_close() if eset.radio.pipe: eset.radio.pipe.close() if isinstance(eset.radio, chirp_common.LiveRadio): action = self.menu_ag.get_action("openlive") if action: action.set_sensitive(True) page = self.tabs.page_num(eset) if page is not None: self.tabs.remove_page(page) return True def do_import(self): types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"), (_("CHIRP Radio Images") + " (*.img)", "*.img"), (_("CSV Files") + " (*.csv)", "*.csv"), (_("EVE Files (VX5)") + " (*.eve)", "*.eve"), (_("ICF Files") + " (*.icf)", "*.icf"), (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"), (_("Travel Plus Files") + " (*.tpe)", "*.tpe"), (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"), (_("VX6 Commander Files") + " (*.vx6)", "*.vx6"), (_("VX7 Commander Files") + " (*.vx7)", "*.vx7")] filen = platform.get_platform().gui_open_file(types=types) if not filen: return eset = self.get_current_editorset() count = eset.do_import(filen) reporting.report_model_usage(eset.rthread.radio, "import", count > 0) def do_repeaterbook_prompt(self): if not CONF.get_bool("has_seen_credit", "repeaterbook"): d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) d.set_markup("RepeaterBook\r\n" + \ "North American Repeater Directory") d.format_secondary_markup("For more information about this " +\ "free service, please go to\r\n" +\ "http://www.repeaterbook.com") d.run() d.destroy() CONF.set_bool("has_seen_credit", True, "repeaterbook") default_state = "Oregon" default_county = "--All--" default_band = "--All--" try: try: code = int(CONF.get("state", "repeaterbook")) except: code = CONF.get("state", "repeaterbook") for k,v in fips.FIPS_STATES.items(): if code == v: default_state = k break code = CONF.get("county", "repeaterbook") for k,v in fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items(): if code == v: default_county = k break code = int(CONF.get("band", "repeaterbook")) for k,v in RB_BANDS.items(): if code == v: default_band = k break except: pass state = miscwidgets.make_choice(sorted(fips.FIPS_STATES.keys()), False, default_state) county = miscwidgets.make_choice(sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()), False, default_county) band = miscwidgets.make_choice(sorted(RB_BANDS.keys(), key=key_bands), False, default_band) def _changed(box, county): state = fips.FIPS_STATES[box.get_active_text()] county.get_model().clear() for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()): county.append_text(fips_county) county.set_active(0) state.connect("changed", _changed, county) d = inputdialog.FieldDialog(title="RepeaterBook Query", parent=self) d.add_field("State", state) d.add_field("County", county) d.add_field("Band", band) r = d.run() d.destroy() if r != gtk.RESPONSE_OK: return False code = fips.FIPS_STATES[state.get_active_text()] county_id = fips.FIPS_COUNTIES[code][county.get_active_text()] freq = RB_BANDS[band.get_active_text()] CONF.set("state", str(code), "repeaterbook") CONF.set("county", str(county_id), "repeaterbook") CONF.set("band", str(freq), "repeaterbook") return True def do_repeaterbook(self, do_import): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if not self.do_repeaterbook_prompt(): self.window.set_cursor(None) return try: code = "%02i" % int(CONF.get("state", "repeaterbook")) except: try: code = CONF.get("state", "repeaterbook") except: code = '41' # Oregon default try: county = CONF.get("county", "repeaterbook") except: county = '%' # --All-- default try: band = int(CONF.get("band", "repeaterbook")) except: band = 14 # 2m default query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php?" + \ "func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \ "&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%" query = query % (code, band and band or "%%", county and county or "%%") # 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) fn = tempfile.mktemp(".csv") filename, headers = urllib.urlretrieve(query, fn) if not os.path.exists(filename): print "Failed, headers were:" print str(headers) common.show_error("RepeaterBook query failed") self.window.set_cursor(None) return class RBRadio(generic_csv.CSVRadio, chirp_common.NetworkSourceRadio): VENDOR = "RepeaterBook" MODEL = "" try: # Validate CSV radio = RBRadio(filename) if radio.errors: reporting.report_misc_error("repeaterbook", ("query=%s\n" % query) + ("\n") + ("\n".join(radio.errors))) except errors.InvalidDataError, e: common.show_error(str(e)) self.window.set_cursor(None) return except Exception, e: common.log_exception() reporting.report_model_usage(radio, "import", True) self.window.set_cursor(None) if do_import: eset = self.get_current_editorset() count = eset.do_import(filename) else: self.do_open_live(radio, read_only=True) 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), "5Range_in_Miles": (gtk.Entry(), lambda x: int(x) > 0 and int(x) < 5000), } d = inputdialog.FieldDialog(title="RFinder Login", parent=self) for k in sorted(fields.keys()): d.add_field(k[1:].replace("_", " "), 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, do_import): 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") miles = CONF.get_int("Range_in_Miles", "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) if do_import: eset = self.get_current_editorset() count = eset.do_import("rfinder://%s/%s/%f/%f/%i" % (email, passwd, lat, lon, miles)) else: from chirp import rfinder radio = rfinder.RFinderRadio(None) radio.set_params((lat, lon), miles, email, passwd) self.do_open_live(radio, read_only=True) self.window.set_cursor(None) def do_radioreference_prompt(self): fields = {"1Username" : (gtk.Entry(), lambda x: x), "2Password" : (gtk.Entry(), lambda x: x), "3Zipcode" : (gtk.Entry(), lambda x: x), } d = inputdialog.FieldDialog(title="RadioReference.com Query", 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:], "radioreference") 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(), "radioreference") 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_radioreference(self, do_import): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if not self.do_radioreference_prompt(): self.window.set_cursor(None) return username = CONF.get("Username", "radioreference") passwd = CONF.get("Password", "radioreference") zipcode = CONF.get("Zipcode", "radioreference") # 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) if do_import: eset = self.get_current_editorset() count = eset.do_import("radioreference://%s/%s/%s" % (zipcode, username, passwd)) else: try: from chirp import radioreference radio = radioreference.RadioReferenceRadio(None) radio.set_params(zipcode, username, passwd) self.do_open_live(radio, read_only=True) except errors.RadioError, e: common.show_error(e) self.window.set_cursor(None) def do_export(self): types = [(_("CSV Files") + " (*.csv)", "csv"), (_("CHIRP Files") + " (*.chirp)", "chirp"), ] eset = self.get_current_editorset() if os.path.exists(eset.filename): base = os.path.basename(eset.filename) if "." in base: base = base[:base.rindex(".")] defname = base else: defname = "radio" filen = platform.get_platform().gui_save_file(default_name=defname, types=types) if not filen: return if os.path.exists(filen): dlg = inputdialog.OverwriteDialog(filen) owrite = dlg.run() dlg.destroy() if owrite != gtk.RESPONSE_OK: return os.remove(filen) count = eset.do_export(filen) reporting.report_model_usage(eset.rthread.radio, "export", count > 0) def do_about(self): d = gtk.AboutDialog() d.set_transient_for(self) import sys verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % ( \ ".".join([str(x) for x in gtk.gtk_version]), ".".join([str(x) for x in gtk.pygtk_version]), sys.version.split()[0]) d.set_name("CHIRP") d.set_version(CHIRP_VERSION) d.set_copyright("Copyright 2012 Dan Smith (KK7DS)") d.set_website("http://chirp.danplanet.com") d.set_authors(("Dan Smith ", _("With significant contributions by:"), "Marco IZ3GME", "Rick WZ3RO", "Tom KD7LXL", "Vernon N7OH" )) d.set_translator_credits("Polish: Grzegorz SQ2RBY" + os.linesep + "Italian: Fabio IZ2QDH" + os.linesep + "Dutch: Michael PD4MT" + os.linesep + "German: Benjamin HB9EUK") d.set_comments(verinfo) d.run() d.destroy() def do_columns(self): eset = self.get_current_editorset() driver = directory.get_driver(eset.rthread.radio.__class__) radio_name = "%s %s %s" % (eset.rthread.radio.VENDOR, eset.rthread.radio.MODEL, eset.rthread.radio.VARIANT) d = gtk.Dialog(title=_("Select Columns"), parent=self, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) vbox = gtk.VBox() vbox.show() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.add_with_viewport(vbox) sw.show() d.vbox.pack_start(sw, 1, 1, 1) d.set_size_request(-1, 300) d.set_resizable(False) label = gtk.Label(_("Visible columns for {radio}").format(radio=radio_name)) label.show() vbox.pack_start(label) fields = [] memedit = eset.editors["memedit"] unsupported = memedit.get_unsupported_columns() for colspec in memedit.cols: if colspec[0].startswith("_"): continue elif colspec[0] in unsupported: continue label = colspec[0] visible = memedit.get_column_visible(memedit.col(label)) widget = gtk.CheckButton(label) widget.set_active(visible) fields.append(widget) vbox.pack_start(widget, 1, 1, 1) widget.show() res = d.run() selected_columns = [] if res == gtk.RESPONSE_OK: for widget in fields: colnum = memedit.col(widget.get_label()) memedit.set_column_visible(colnum, widget.get_active()) if widget.get_active(): selected_columns.append(widget.get_label()) d.destroy() CONF.set(driver, ",".join(selected_columns), "memedit_columns") def do_hide_unused(self, action): eset = self.get_current_editorset() if eset is None: conf = config.get("memedit") conf.set_bool("hide_unused", action.get_active()) else: eset.editors["memedit"].set_hide_unused(action.get_active()) def do_clearq(self): eset = self.get_current_editorset() eset.rthread.flush() def do_copy(self, cut): eset = self.get_current_editorset() eset.get_current_editor().copy_selection(cut) def do_paste(self): eset = self.get_current_editorset() eset.get_current_editor().paste_selection() def do_delete(self): eset = self.get_current_editorset() eset.get_current_editor().copy_selection(True) def do_toggle_report(self, action): if not action.get_active(): d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=self) d.set_markup("" + _("Reporting is disabled") + "") msg = _("The reporting feature of CHIRP is designed to help " "improve quality by allowing the authors to focus " "on the radio drivers used most often and errors " "experienced by the users. The reports contain no " "identifying information and are used only for statistical " "purposes by the authors. Your privacy is extremely " "important, but please consider leaving this feature " "enabled to help make CHIRP better!\n\nAre you " "sure you want to disable this feature?") d.format_secondary_markup(msg.replace("\n", "\r\n")) r = d.run() d.destroy() if r == gtk.RESPONSE_NO: action.set_active(not action.get_active()) 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_developer(self, action): conf = config.get() conf.set_bool("developer", action.get_active(), "state") for name in ["viewdeveloper", "loadmod"]: devaction = self.menu_ag.get_action(name) devaction.set_visible(action.get_active()) def do_change_language(self): langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German", "Hungarian"] d = inputdialog.ChoiceDialog(langs, parent=self, title="Choose Language") d.label.set_text(_("Choose a language or Auto to use the " "operating system default. You will need to " "restart the application before the change " "will take effect")) d.label.set_line_wrap(True) r = d.run() if r == gtk.RESPONSE_OK: print "Chose language %s" % d.choice.get_active_text() conf = config.get() conf.set("language", d.choice.get_active_text(), "state") d.destroy() def load_module(self): types = [(_("Python Modules") + "*.py", "*.py")] filen = platform.get_platform().gui_open_file(types=types) if not filen: return # We're in development mode, so we need to tell the directory to # allow a loaded module to override an existing driver, against # its normal better judgement directory.enable_reregistrations() try: module = file(filen) code = module.read() module.close() pyc = compile(code, filen, 'exec') # See this for why: # http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec exec(pyc, globals(), globals()) except Exception, e: common.log_exception() common.show_error("Unable to load module: %s" % e) def mh(self, _action, *args): action = _action.get_name() if action == "quit": gtk.main_quit() elif action == "new": self.do_new() elif action == "open": self.do_open() elif action == "save": self.do_save() elif action == "saveas": self.do_saveas() elif action.startswith("download"): self.do_download(*args) elif action.startswith("upload"): self.do_upload(*args) elif action == "close": self.do_close() elif action == "import": self.do_import() elif action in ["qrfinder", "irfinder"]: self.do_rfinder(action[0] == "i") elif action in ["qradioreference", "iradioreference"]: self.do_radioreference(action[0] == "i") elif action == "export": self.do_export() elif action in ["qrbook", "irbook"]: self.do_repeaterbook(action[0] == "i") elif action == "about": self.do_about() elif action == "columns": self.do_columns() elif action == "hide_unused": self.do_hide_unused(_action) elif action == "cancelq": self.do_clearq() elif action == "report": self.do_toggle_report(_action) elif action == "autorpt": self.do_toggle_autorpt(_action) elif action == "developer": self.do_toggle_developer(_action) elif action in ["cut", "copy", "paste", "delete", "move_up", "move_dn", "exchange", "devshowraw", "devdiffraw"]: self.get_current_editorset().get_current_editor().hotkey(_action) elif action == "devdifftab": self.do_diff_radio() elif action == "language": self.do_change_language() elif action == "loadmod": self.load_module() else: return self.ev_tab_switched() def make_menubar(self): menu_xml = """ """ actions = [\ ('file', None, _("_File"), None, None, self.mh), ('new', gtk.STOCK_NEW, None, None, None, self.mh), ('open', gtk.STOCK_OPEN, None, None, None, self.mh), ('openstock', None, _("Open stock config"), None, None, self.mh), ('recent', None, _("_Recent"), None, None, self.mh), ('save', gtk.STOCK_SAVE, None, None, None, self.mh), ('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh), ('loadmod', None, _("Load Module"), None, None, self.mh), ('close', gtk.STOCK_CLOSE, None, None, None, self.mh), ('quit', gtk.STOCK_QUIT, None, None, None, self.mh), ('edit', None, _("_Edit"), None, None, self.mh), ('cut', None, _("_Cut"), "x", None, self.mh), ('copy', None, _("_Copy"), "c", None, self.mh), ('paste', None, _("_Paste"), "v", None, self.mh), ('delete', None, _("_Delete"), "Delete", None, self.mh), ('move_up', None, _("Move _Up"), "Up", None, self.mh), ('move_dn', None, _("Move Dow_n"), "Down", None, self.mh), ('exchange', None, _("E_xchange"), "x", None, self.mh), ('view', None, _("_View"), None, None, self.mh), ('columns', None, _("Columns"), None, None, self.mh), ('viewdeveloper', None, _("Developer"), None, None, self.mh), ('devshowraw', None, _('Show raw memory'), "r", None, self.mh), ('devdiffraw', None, _("Diff raw memories"), "d", None, self.mh), ('devdifftab', None, _("Diff tabs"), "t", None, self.mh), ('language', None, _("Change language"), None, None, self.mh), ('radio', None, _("_Radio"), None, None, self.mh), ('download', None, _("Download From Radio"), "d", None, self.mh), ('upload', None, _("Upload To Radio"), "u", None, self.mh), ('import', None, _("Import"), "i", None, self.mh), ('export', None, _("Export"), "x", None, self.mh), ('importsrc', None, _("Import from data source"), None, None, self.mh), ('iradioreference', None, _("RadioReference.com"), None, None, self.mh), ('irfinder', None, _("RFinder"), None, None, self.mh), ('irbook', None, _("RepeaterBook"), None, None, self.mh), ('querysrc', None, _("Query data source"), None, None, self.mh), ('qradioreference', None, _("RadioReference.com"), None, None, self.mh), ('qrfinder', None, _("RFinder"), None, None, self.mh), ('qrbook', None, _("RepeaterBook"), None, None, self.mh), ('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), ('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), ] 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") toggles = [\ ('report', None, _("Report statistics"), None, None, self.mh, re), ('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu), ('autorpt', None, _("Automatic Repeater Offset"), None, None, self.mh, ro), ('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv), ] self.menu_uim = gtk.UIManager() self.menu_ag = gtk.ActionGroup("MenuBar") self.menu_ag.add_actions(actions) self.menu_ag.add_toggle_actions(toggles) self.menu_uim.insert_action_group(self.menu_ag, 0) self.menu_uim.add_ui_from_string(menu_xml) self.add_accel_group(self.menu_uim.get_accel_group()) self.recentmenu = self.menu_uim.get_widget("/MenuBar/file/recent") # Initialize self.do_toggle_developer(self.menu_ag.get_action("developer")) return self.menu_uim.get_widget("/MenuBar") def make_tabs(self): self.tabs = gtk.Notebook() return self.tabs def close_out(self): num = self.tabs.get_n_pages() while num > 0: num -= 1 print "Closing %i" % num try: self.do_close(self.tabs.get_nth_page(num)) except ModifiedError: return False gtk.main_quit() return True def make_status_bar(self): box = gtk.HBox(False, 2) self.sb_general = gtk.Statusbar() self.sb_general.set_has_resize_grip(False) self.sb_general.show() box.pack_start(self.sb_general, 1,1,1) self.sb_radio = gtk.Statusbar() self.sb_radio.set_has_resize_grip(True) self.sb_radio.show() box.pack_start(self.sb_radio, 1,1,1) box.show() return box def ev_delete(self, window, event): if not self.close_out(): return True # Don't exit def ev_destroy(self, window): if not self.close_out(): return True # Don't exit def setup_extra_hotkeys(self): accelg = self.menu_uim.get_accel_group() memedit = lambda a: self.get_current_editorset().editors["memedit"].hotkey(a) actions = [ # ("action_name", "key", function) ] for name, key, fn in actions: a = gtk.Action(name, name, name, "") a.connect("activate", fn) self.menu_ag.add_action_with_accel(a, key) a.set_accel_group(accelg) a.connect_accelerator() def _set_icon(self): execpath = platform.get_platform().executable_path() path = os.path.abspath(os.path.join(execpath, "share", "chirp.png")) if not os.path.exists(path): path = "/usr/share/pixmaps/chirp.png" if os.path.exists(path): self.set_icon_from_file(path) else: print "Icon %s not found" % path def _updates(self, version): if not version: return if version == CHIRP_VERSION: return print "Server reports version %s is available" % version # Report new updates every seven days intv = 3600 * 24 * 7 if CONF.is_defined("last_update_check", "state") and \ (time.time() - CONF.get_int("last_update_check", "state")) < intv: return CONF.set_int("last_update_check", int(time.time()), "state") d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self, type=gtk.MESSAGE_INFO) d.set_property("text", _("A new version of CHIRP is available: " + "{ver}. ".format(ver=version) + "It is recommended that you upgrade, so " + "go to http://chirp.danplanet.com soon!")) d.run() d.destroy() def _init_macos(self, menu_bar): try: import gtk_osxapplication macapp = gtk_osxapplication.OSXApplication() except ImportError, e: print "No MacOS support: %s" % e return menu_bar.hide() macapp.set_menu_bar(menu_bar) quititem = self.menu_uim.get_widget("/MenuBar/file/quit") quititem.hide() aboutitem = self.menu_uim.get_widget("/MenuBar/help/about") macapp.insert_app_menu_item(aboutitem, 0) macapp.set_use_quartz_accelerators(False) macapp.ready() print "Initialized MacOS support" def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) d = CONF.get("last_dir", "state") if d and os.path.isdir(d): platform.get_platform().set_last_dir(d) vbox = gtk.VBox(False, 2) self._recent = [] self.menu_ag = None mbar = self.make_menubar() if os.name != "nt": self._set_icon() # Windows gets the icon from the exe if os.uname()[0] == "Darwin": self._init_macos(mbar) vbox.pack_start(mbar, 0, 0, 0) self.tabs = None tabs = self.make_tabs() tabs.connect("switch-page", lambda n, _, p: self.ev_tab_switched(p)) tabs.connect("page-removed", lambda *a: self.ev_tab_switched()) tabs.show() self.ev_tab_switched() vbox.pack_start(tabs, 1, 1, 1) vbox.pack_start(self.make_status_bar(), 0, 0, 0) vbox.show() self.add(vbox) self.set_default_size(800, 600) self.set_title("CHIRP") self.connect("delete_event", self.ev_delete) self.connect("destroy", self.ev_destroy) if not CONF.get_bool("warned_about_reporting") and \ not CONF.get_bool("no_report"): d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self) d.set_markup("" + _("Error reporting is enabled") + "") d.format_secondary_markup(\ _("If you wish to disable this feature you may do so in " "the Help menu")) d.run() 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() def updates_callback(ver): gobject.idle_add(self._updates, ver) if not CONF.get_bool("skip_update_check", "state"): reporting.check_for_updates(updates_callback) chirp-0.3.1/chirpui/reporting.py0000644000016101777760000001314612130403635020045 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . # README: # # I know that collecting data is not very popular. I don't like it # either. However, it's hard to tell what drivers people are using # and I think it would be helpful if I had that information. This is # completely optional, so you can turn it off if you want. It doesn't # report anything other than version and model usage information. The # code below is very conservative, and will disable itself if reporting # fails even once or takes too long to perform. It's run in a thread # so that the user shouldn't even notice it's happening. # import threading import os import time from chirp import CHIRP_VERSION, platform REPORT_URL = "http://chirp.danplanet.com/report/report.php?do_report" ENABLED = True DEBUG = os.getenv("CHIRP_DEBUG") == "y" THREAD_SEM = threading.Semaphore(10) # Maximum number of outstanding threads LAST = 0 LAST_TYPE = None try: # Don't let failure to import any of these modules cause trouble from chirpui import config import xmlrpclib except: ENABLED = False def debug(string): if DEBUG: print string def should_report(): if not ENABLED: debug("Not reporting due to recent failure") return False conf = config.get() if conf.get_bool("no_report"): debug("Reporting disabled") return False return True def _report_model_usage(model, direction, success): global ENABLED if direction not in ["live", "download", "upload", "import", "export", "importsrc"]: print "Invalid direction `%s'" % direction return True # This is a bug, but not fatal model = "%s_%s" % (model.VENDOR, model.MODEL) data = "%s,%s,%s" % (model, direction, success) debug("Reporting model usage: %s" % data) proxy = xmlrpclib.ServerProxy(REPORT_URL) id = proxy.report_stats(CHIRP_VERSION, platform.get_platform().os_version_string(), "model_use", data) # If the server returns zero, it wants us to shut up return id != 0 def _report_exception(stack): global ENABLED debug("Reporting exception") proxy = xmlrpclib.ServerProxy(REPORT_URL) id = proxy.report_exception(CHIRP_VERSION, platform.get_platform().os_version_string(), "exception", stack) # If the server returns zero, it wants us to shut up return id != 0 def _report_misc_error(module, data): global ENABLED debug("Reporting misc error with %s" % module) proxy = xmlrpclib.ServerProxy(REPORT_URL) id = proxy.report_misc_error(CHIRP_VERSION, platform.get_platform().os_version_string(), module, data) # If the server returns zero, it wants us to shut up return id != 0 def _check_for_updates(callback): debug("Checking for updates") proxy = xmlrpclib.ServerProxy(REPORT_URL) ver = proxy.check_for_updates(CHIRP_VERSION, platform.get_platform().os_version_string()) debug("Server reports version %s is latest" % ver) callback(ver) return True class ReportThread(threading.Thread): def __init__(self, func, *args): threading.Thread.__init__(self) self.__func = func self.__args = args def _run(self): try: return self.__func(*self.__args) except Exception, e: debug("Failed to report: %s" % e) return False def run(self): start = time.time() result = self._run() if not result: # Reporting failed ENABLED = False elif (time.time() - start) > 15: # Reporting took too long debug("Time to report was %.2f sec -- Disabling" % \ (time.time()-start)) ENABLED = False THREAD_SEM.release() def dispatch_thread(func, *args): global LAST global LAST_TYPE # If reporting is disabled or failing, bail if not should_report(): debug("Reporting is disabled") return # If the time between now and the last report is less than 5 seconds, bail delta = time.time() - LAST if delta < 5 and func == LAST_TYPE: debug("Throttling...") return LAST = time.time() LAST_TYPE = func # If there are already too many threads running, bail if not THREAD_SEM.acquire(False): debug("Too many threads already running") return t = ReportThread(func, *args) t.start() def report_model_usage(model, direction, success): dispatch_thread(_report_model_usage, model, direction, success) def report_exception(stack): dispatch_thread(_report_exception, stack) def report_misc_error(module, data): dispatch_thread(_report_misc_error, module, data) # Calls callback with the latest version def check_for_updates(callback): dispatch_thread(_check_for_updates, callback) chirp-0.3.1/chirpui/bankedit.py0000644000016101777760000003322512130403635017615 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import gtk import gobject import time from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN from chirp import chirp_common from chirpui import common, miscwidgets class BankNamesJob(common.RadioJob): def __init__(self, bm, editor, cb): common.RadioJob.__init__(self, cb, None) self.__bm = bm self.__editor = editor def execute(self, radio): self.__editor.banks = [] banks = self.__bm.get_banks() for bank in banks: self.__editor.banks.append((bank, bank.get_name())) gobject.idle_add(self.cb, *self.cb_args) class BankNameEditor(common.Editor): def refresh(self): def got_banks(): self._keys = [] for bank, name in self.banks: self._keys.append(bank.get_index()) self.listw.set_item(bank.get_index(), bank.get_index(), name) self.listw.connect("item-set", self.bank_changed) job = BankNamesJob(self._bm, self, got_banks) job.set_desc(_("Retrieving bank information")) self.rthread.submit(job) def get_bank_list(self): banks = [] keys = self.listw.get_keys() for key in keys: banks.append(self.listw.get_item(key)[2]) return banks def bank_changed(self, listw, key): def cb(*args): self.emit("changed") name = self.listw.get_item(key)[2] bank, oldname = self.banks[self._keys.index(key)] def trigger_changed(*args): self.emit("changed") job = common.RadioJob(trigger_changed, "set_name", name) job.set_target(bank) job.set_desc(_("Setting name on bank")) self.rthread.submit(job) return True def __init__(self, rthread): common.Editor.__init__(self) self.rthread = rthread self._bm = rthread.radio.get_bank_model() types = [(gobject.TYPE_STRING, "key"), (gobject.TYPE_STRING, _("Bank")), (gobject.TYPE_STRING, _("Name"))] self.listw = miscwidgets.KeyedListWidget(types) self.listw.set_editable(1, True) self.listw.set_sort_column(0, 1) self.listw.set_sort_column(1, -1) self.listw.show() self.banks = [] sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add_with_viewport(self.listw) self.root = sw self._loaded = False def focus(self): if self._loaded: return self.refresh() self._loaded = True class MemoryBanksJob(common.RadioJob): def __init__(self, bm, cb, number): common.RadioJob.__init__(self, cb, None) self.__bm = bm self.__number = number def execute(self, radio): mem = radio.get_memory(self.__number) if mem.empty: banks = [] indexes = [] else: banks = self.__bm.get_memory_banks(mem) indexes = [] if isinstance(self.__bm, chirp_common.BankIndexInterface): for bank in banks: indexes.append(self.__bm.get_memory_index(mem, bank)) self.cb(mem, banks, indexes, *self.cb_args) class BankMembershipEditor(common.Editor): def _number_to_path(self, number): return (number - self._rf.memory_bounds[0],) def _get_next_bank_index(self, bank): # NB: Only works for one-to-one bank models right now! iter = self._store.get_iter_first() indexes = [] ncols = len(self._cols) + len(self.banks) while iter: vals = self._store.get(iter, *tuple([n for n in range(0, ncols)])) loc = vals[self.C_LOC] index = vals[self.C_INDEX] banks = vals[self.C_BANKS:] if True in banks and banks.index(True) == bank: indexes.append(index) iter = self._store.iter_next(iter) index_bounds = self._bm.get_index_bounds() num_indexes = index_bounds[1] - index_bounds[0] indexes.sort() for i in range(0, num_indexes): if i not in indexes: return i + index_bounds[0] # In case not zero-origin index return 0 # If the bank is full, just wrap around! def _toggled_cb(self, rend, path, colnum): try: if not rend.get_sensitive(): return except AttributeError: # support PyGTK < 2.22 iter = self._store.get_iter(path) if not self._store.get(iter, self.C_FILLED)[0]: return # The bank index is the column number, minus the 3 label columns bank, name = self.banks[colnum - len(self._cols)] loc, = self._store.get(self._store.get_iter(path), self.C_LOC) if rend.get_active(): # Changing from True to False fn = "remove_memory_from_bank" index = None else: # Changing from False to True fn = "add_memory_to_bank" if self._rf.has_bank_index: index = self._get_next_bank_index(colnum - len(self._cols)) else: index = None def do_refresh_memory(*args): # Step 2: Update our notion of the memory's bank information self.refresh_memory(loc) def do_bank_index(result, memory): if isinstance(result, Exception): common.show_error("Failed to add {mem} to bank: {err}" .format(mem=memory.number, err=str(result)), parent=self.editorset.parent_window) return self.emit("changed") # Step 3: Set the memory's bank index (maybe) if not self._rf.has_bank_index or index is None: return do_refresh_memory() job = common.RadioJob(do_refresh_memory, "set_memory_index", memory, bank, index) job.set_target(self._bm) job.set_desc(_("Updating bank index " "for memory {num}").format(num=memory.number)) self.rthread.submit(job) def do_bank_adjustment(memory): # Step 1: Do the bank add/remove job = common.RadioJob(do_bank_index, fn, memory, bank) job.set_target(self._bm) job.set_cb_args(memory) job.set_desc(_("Updating bank information " "for memory {num}").format(num=memory.number)) self.rthread.submit(job) # Step 0: Fetch the memory job = common.RadioJob(do_bank_adjustment, "get_memory", loc) job.set_desc(_("Getting memory {num}").format(num=loc)) self.rthread.submit(job) def _index_edited_cb(self, rend, path, new): loc, = self._store.get(self._store.get_iter(path), self.C_LOC) def refresh_memory(*args): self.refresh_memory(loc) def set_index(banks, memory): self.emit("changed") # Step 2: Set the index job = common.RadioJob(refresh_memory, "set_memory_index", memory, banks[0], int(new)) job.set_target(self._bm) job.set_desc(_("Setting index " "for memory {num}").format(num=memory.number)) self.rthread.submit(job) def get_bank(memory): # Step 1: Get the first/only bank job = common.RadioJob(set_index, "get_memory_banks", memory) job.set_cb_args(memory) job.set_target(self._bm) job.set_desc(_("Getting bank for " "memory {num}").format(num=memory.number)) self.rthread.submit(job) # Step 0: Get the memory job = common.RadioJob(get_bank, "get_memory", loc) job.set_desc(_("Getting memory {num}").format(num=loc)) self.rthread.submit(job) def __init__(self, rthread, editorset): common.Editor.__init__(self) self.rthread = rthread self.editorset = editorset self._rf = rthread.radio.get_features() self._bm = rthread.radio.get_bank_model() self._view_cols = [ (_("Loc"), TYPE_INT, gtk.CellRendererText, ), (_("Frequency"), TYPE_STRING, gtk.CellRendererText, ), (_("Name"), TYPE_STRING, gtk.CellRendererText, ), (_("Index"), TYPE_INT, gtk.CellRendererText, ), ] self._cols = [ ("_filled", TYPE_BOOLEAN, None, ), ] + self._view_cols self.C_FILLED = 0 self.C_LOC = 1 self.C_FREQ = 2 self.C_NAME = 3 self.C_INDEX = 4 self.C_BANKS = 5 # and beyond cols = list(self._cols) self._index_cache = [] for i in range(0, self._bm.get_num_banks()): label = "Bank %i" % (i+1) cols.append((label, TYPE_BOOLEAN, gtk.CellRendererToggle)) self._store = gtk.ListStore(*tuple([y for x,y,z in cols])) self._view = gtk.TreeView(self._store) colnum = 0 for label, dtype, rtype in cols: if not rtype: colnum += 1 continue rend = rtype() if dtype == TYPE_BOOLEAN: rend.set_property("activatable", True) rend.connect("toggled", self._toggled_cb, colnum) col = gtk.TreeViewColumn(label, rend, active=colnum, sensitive=self.C_FILLED) else: col = gtk.TreeViewColumn(label, rend, text=colnum, sensitive=self.C_FILLED) self._view.append_column(col) col.set_resizable(True) if colnum == self.C_NAME: col.set_visible(self._rf.has_name) elif colnum == self.C_INDEX: rend.set_property("editable", True) rend.connect("edited", self._index_edited_cb) col.set_visible(self._rf.has_bank_index) colnum += 1 # A non-rendered column to absorb extra space in the row self._view.append_column(gtk.TreeViewColumn()) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self._view) self._view.show() for i in range(*self._rf.memory_bounds): iter = self._store.append() self._store.set(iter, self.C_FILLED, False, self.C_LOC, i, self.C_FREQ, 0, self.C_NAME, "", self.C_INDEX, 0) self.root = sw self._loaded = False def refresh_memory(self, number): def got_mem(memory, banks, indexes): iter = self._store.get_iter(self._number_to_path(memory.number)) row = [self.C_FILLED, not memory.empty, self.C_LOC, memory.number, self.C_FREQ, chirp_common.format_freq(memory.freq), self.C_NAME, memory.name, # Hack for only one index right now self.C_INDEX, indexes and indexes[0] or 0, ] for i in range(0, len(self.banks)): row.append(i + len(self._cols)) row.append(self.banks[i][0] in banks) self._store.set(iter, *tuple(row)) if memory.number == self._rf.memory_bounds[1] - 1: print "Got all bank info in %s" % (time.time() - self._start) job = MemoryBanksJob(self._bm, got_mem, number) job.set_desc(_("Getting bank information " "for memory {num}").format(num=number)) self.rthread.submit(job) def refresh_all_memories(self): for i in range(*self._rf.memory_bounds): self.refresh_memory(i) def refresh_banks(self, and_memories=False): def got_banks(): for i in range(len(self._cols) - len(self._view_cols) - 1, len(self.banks)): col = self._view.get_column(i + len(self._view_cols)) bank, name = self.banks[i] if name: col.set_title(name) else: col.set_title("(%s)" % i) if and_memories: self.refresh_all_memories() job = BankNamesJob(self._bm, self, got_banks) job.set_desc(_("Getting bank information")) self.rthread.submit(job) def focus(self): common.Editor.focus(self) if self._loaded: return self._start = time.time() self.refresh_banks(True) self._loaded = True def memories_changed(self): self._loaded = False if self.is_focused(): self.refresh_all_memories() def banks_changed(self): self.refresh_banks() chirp-0.3.1/chirpui/clone.py0000644000016101777760000002001412107114672017130 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import threading import os import gtk import gobject from chirp import platform, directory, detect, chirp_common from chirpui import miscwidgets, cloneprog, inputdialog, common, config AUTO_DETECT_STRING = "Auto Detect (Icom Only)" class CloneSettings: def __init__(self): self.port = None self.radio_class = None def __str__(self): s = "" if self.radio_class: return _("{vendor} {model} on {port}").format(\ vendor=self.radio_class.VENDOR, model=self.radio_class.MODEL, port=self.port) class CloneSettingsDialog(gtk.Dialog): def __make_field(self, label, widget): l = gtk.Label(label) self.__table.attach(l, 0, 1, self.__row, self.__row+1) self.__table.attach(widget, 1, 2, self.__row, self.__row+1) self.__row += 1 l.show() widget.show() def __make_port(self, port): conf = config.get("state") ports = platform.get_platform().list_serial_ports() if not port: if conf.get("last_port"): port = conf.get("last_port") elif ports: port = ports[0] if not port in ports: ports.insert(0, port) return miscwidgets.make_choice(ports, True, port) def __make_model(self): return miscwidgets.make_choice([], False) def __make_vendor(self, model): vendors = {} for rclass in sorted(directory.DRV_TO_RADIO.values()): if not issubclass(rclass, chirp_common.CloneModeRadio) and \ not issubclass(rclass, chirp_common.LiveRadio): continue if not vendors.has_key(rclass.VENDOR): vendors[rclass.VENDOR] = [] vendors[rclass.VENDOR].append(rclass) self.__vendors = vendors conf = config.get("state") if not conf.get("last_vendor"): conf.set("last_vendor", sorted(vendors.keys())[0]) last_vendor = conf.get("last_vendor") if last_vendor not in vendors.keys(): last_vendor = vendors.keys()[0] v = miscwidgets.make_choice(sorted(vendors.keys()), False, last_vendor) def _changed(box, vendors, model): models = vendors[box.get_active_text()] added_models = [] model.get_model().clear() for rclass in sorted(models, key=lambda c: c.__name__): if rclass.MODEL not in added_models: model.append_text(rclass.MODEL) added_models.append(rclass.MODEL) if box.get_active_text() in detect.DETECT_FUNCTIONS: model.insert_text(0, _("Detect")) added_models.insert(0, _("Detect")) model_names = [x.MODEL for x in models] if conf.get("last_model") in model_names: model.set_active(added_models.index(conf.get("last_model"))) else: model.set_active(0) v.connect("changed", _changed, vendors, model) _changed(v, vendors, model) return v def __make_ui(self, settings): self.__table = gtk.Table(3, 2) self.__table.set_row_spacings(3) self.__table.set_col_spacings(10) self.__row = 0 self.__port = self.__make_port(settings and settings.port or None) self.__modl = self.__make_model() self.__vend = self.__make_vendor(self.__modl) self.__make_field(_("Port"), self.__port) self.__make_field(_("Vendor"), self.__vend) self.__make_field(_("Model"), self.__modl) if settings and settings.radio_class: common.combo_select(self.__vend, settings.radio_class.VENDOR) self.__modl.get_model().clear() self.__modl.append_text(settings.radio_class.MODEL) common.combo_select(self.__modl, settings.radio_class.MODEL) self.__vend.set_sensitive(False) self.__modl.set_sensitive(False) self.__table.show() self.vbox.pack_start(self.__table, 1, 1, 1) def __init__(self, settings=None, parent=None, title=_("Radio")): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, title, parent=parent, flags=gtk.DIALOG_MODAL) self.__make_ui(settings) self.__cancel_button = self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) self.__okay_button = self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) self.__okay_button.grab_default() self.__okay_button.grab_focus() def run(self): r = gtk.Dialog.run(self) if r != gtk.RESPONSE_OK: return None vendor = self.__vend.get_active_text() model = self.__modl.get_active_text() cs = CloneSettings() cs.port = self.__port.get_active_text() if model == _("Detect"): try: cs.radio_class = detect.DETECT_FUNCTIONS[vendor](cs.port) if not cs.radio_class: raise Exception(_("Unable to detect radio on {port}").format(port=cs.port)) except Exception, e: d = inputdialog.ExceptionDialog(e) d.run() d.destroy() return None else: for rclass in directory.DRV_TO_RADIO.values(): if rclass.MODEL == model: cs.radio_class = rclass break if not cs.radio_class: common.show_error(_("Internal error: Unable to upload to {model}").format(model=model)) print self.__vendors return None conf = config.get("state") conf.set("last_port", cs.port) conf.set("last_vendor", cs.radio_class.VENDOR) conf.set("last_model", model) return cs class CloneCancelledException(Exception): pass class CloneThread(threading.Thread): def __status(self, status): gobject.idle_add(self.__progw.status, status) def __init__(self, radio, direction, cb=None, parent=None): threading.Thread.__init__(self) self.__radio = radio self.__out = direction == "out" self.__cback = cb self.__cancelled = False self.__progw = cloneprog.CloneProg(parent=parent, cancel=self.cancel) def cancel(self): self.__radio.pipe.close() self.__cancelled = True def run(self): print "Clone thread started" gobject.idle_add(self.__progw.show) self.__radio.status_fn = self.__status try: if self.__out: self.__radio.sync_out() else: self.__radio.sync_in() emsg = None except Exception, e: common.log_exception() print _("Clone failed: {error}").format(error=e) emsg = e gobject.idle_add(self.__progw.hide) # NB: Compulsory close of the radio's serial connection self.__radio.pipe.close() print "Clone thread ended" if self.__cback and not self.__cancelled: gobject.idle_add(self.__cback, self.__radio, emsg) if __name__ == "__main__": d = CloneSettingsDialog("/dev/ttyUSB0") r = d.run() print r chirp-0.3.1/chirpui/settingsedit.py0000644000016101777760000001740112130403635020540 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 2 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 . import gtk import gobject from chirp import chirp_common, settings from chirpui import common, miscwidgets class RadioSettingProxy(settings.RadioSetting): def __init__(self, setting, editor): self._setting = setting self._editor = editor class SettingsEditor(common.Editor): def __init__(self, rthread): common.Editor.__init__(self) self._rthread = rthread self.root = gtk.HBox(False, 10) self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) self._view = gtk.TreeView(self._store) self._view.set_size_request(150, -1) self._view.connect("button-press-event", self._group_selected) self._view.show() self.root.pack_start(self._view, 0, 0, 0) col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0) self._view.append_column(col) self._table = gtk.Table(20, 3) self._table.set_col_spacings(10) self._table.show() sw = gtk.ScrolledWindow() sw.add_with_viewport(self._table) sw.show() self.root.pack_start(sw, 1, 1, 1) self._index = 0 self._top_setting_group = None job = common.RadioJob(self._build_ui, "get_settings") job.set_desc("Getting radio settings") self._rthread.submit(job) def _save_settings(self): if self._top_setting_group is None: return job = common.RadioJob(None, "set_settings", self._top_setting_group) job.set_desc("Setting radio settings") self._rthread.submit(job) def _load_setting(self, value, widget): if isinstance(value, settings.RadioSettingValueInteger): adj = widget.get_adjustment() adj.configure(value.get_value(), value.get_min(), value.get_max(), value.get_step(), 1, 0) elif isinstance(value, settings.RadioSettingValueBoolean): widget.set_active(value.get_value()) elif isinstance(value, settings.RadioSettingValueList): model = widget.get_model() model.clear() for option in value.get_options(): widget.append_text(option) current = value.get_value() index = value.get_options().index(current) widget.set_active(index) elif isinstance(value, settings.RadioSettingValueString): widget.set_text(str(value).rstrip()) else: print "Unsupported widget type %s for %s" % (value.__class__, element.get_name()) def _save_setting(self, widget, value): if isinstance(value, settings.RadioSettingValueInteger): value.set_value(widget.get_adjustment().get_value()) elif isinstance(value, settings.RadioSettingValueBoolean): value.set_value(widget.get_active()) elif isinstance(value, settings.RadioSettingValueList): value.set_value(widget.get_active_text()) elif isinstance(value, settings.RadioSettingValueString): value.set_value(widget.get_text()) else: print "Unsupported widget type %s for %s" % (\ element.value.__class__, element.get_name()) self._save_settings() def _build_ui_group(self, group): def pack(widget, pos): self._table.attach(widget, pos, pos+1, self._index, self._index+1, xoptions=gtk.FILL, yoptions=0) def abandon(child): self._table.remove(child) self._table.foreach(abandon) self._index = 0 for element in group: if not isinstance(element, settings.RadioSetting): continue label = gtk.Label(element.get_shortname()) label.set_alignment(1.0, 0.5) label.show() pack(label, 0) if isinstance(element.value, list) and \ isinstance(element.value[0], settings.RadioSettingValueInteger): arraybox = gtk.HBox(3, True) else: arraybox = gtk.VBox(3, True) pack(arraybox, 1) arraybox.show() widgets = [] for index in element.keys(): value = element[index] if isinstance(value, settings.RadioSettingValueInteger): widget = gtk.SpinButton() print "Digits: %i" % widget.get_digits() signal = "value-changed" elif isinstance(value, settings.RadioSettingValueBoolean): widget = gtk.CheckButton(_("Enabled")) signal = "toggled" elif isinstance(value, settings.RadioSettingValueList): widget = miscwidgets.make_choice([], editable=False) signal = "changed" elif isinstance(value, settings.RadioSettingValueString): widget = gtk.Entry() signal = "changed" else: print "Unsupported widget type: %s" % value.__class__ # Make sure the widget gets left-aligned to match up # with its label lalign = gtk.Alignment(0, 0, 0, 0) lalign.add(widget) lalign.show() arraybox.pack_start(lalign, 1, 1, 1) widget.show() self._load_setting(value, widget) widget.connect(signal, self._save_setting, value) self._index += 1 def _build_tree(self, group, parent): iter = self._store.append(parent) self._store.set(iter, 0, group.get_shortname(), 1, group) if self._set_default is None: # If we haven't found the first page with actual settings on it # yet, then look for one here for element in group: if isinstance(element, settings.RadioSetting): self._set_default = self._store.get_path(iter), group break for element in group: if not isinstance(element, settings.RadioSetting): self._build_tree(element, iter) self._view.expand_all() def _build_ui_real(self, group): if not isinstance(group, settings.RadioSettingGroup): print "Toplevel is not a group" return self._set_default = None self._top_setting_group = group self._build_tree(group, None) self._view.set_cursor(self._set_default[0]) self._build_ui_group(self._set_default[1]) def _build_ui(self, group): gobject.idle_add(self._build_ui_real, group) def _group_selected(self, view, event): if event.button != 1: return try: path, col, x, y = view.get_path_at_pos(int(event.x), int(event.y)) except TypeError: return # Didn't click on an actual item group, = self._store.get(self._store.get_iter(path), 1) if group: self._build_ui_group(group) chirp-0.3.1/chirpui/cloneprog.py0000644000016100007500000000407111717005656016373 0ustar jenkins00000000000000# Copyright 2008 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 . import gtk class CloneProg(gtk.Window): def __init__(self, **args): if args.has_key("parent"): parent = args["parent"] del args["parent"] else: parent = None if args.has_key("cancel"): cancel = args["cancel"] del args["cancel"] else: cancel = None gtk.Window.__init__(self, **args) self.set_transient_for(parent) self.set_modal(True) self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.set_position(gtk.WIN_POS_CENTER_ON_PARENT) vbox = gtk.VBox(False, 2) vbox.show() self.add(vbox) self.set_title(_("Clone Progress")) self.set_resizable(False) self.infolabel = gtk.Label(_("Cloning")) self.infolabel.show() vbox.pack_start(self.infolabel, 1, 1, 1) self.progbar = gtk.ProgressBar() self.progbar.set_fraction(0.0) self.progbar.show() vbox.pack_start(self.progbar, 0, 0, 0) cancel_b = gtk.Button(_("Cancel")) cancel_b.connect("clicked", lambda b: cancel()) cancel_b.show() vbox.pack_start(cancel_b, 0, 0, 0) def status(self, _status): self.infolabel.set_text(_status.msg) if _status.cur > _status.max: _status.cur = _status.max self.progbar.set_fraction(_status.cur / float(_status.max)) chirp-0.3.1/chirpui/fips.py0000644000016100007500000075062712023560646015357 0ustar jenkins00000000000000# Copyright 2012 Tom Hayward # # 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 . FIPS_STATES = { "Alaska" : 2, "Alabama" : 1, "Arkansas" : 5, "Arizona" : 4, "California" : 6, "Colorado" : 8, "Connecticut" : 9, "District of Columbia" : 11, "Delaware" : 10, "Florida" : 12, "Georgia" : 13, "Guam" : 66, "Hawaii" : 15, "Iowa" : 19, "Idaho" : 16, "Illinois" : 17, "Indiana" : 18, "Kansas" : 20, "Kentucky" : 21, "Louisiana" : 22, "Massachusetts" : 25, "Maryland" : 24, "Maine" : 23, "Michigan" : 26, "Minnesota" : 27, "Missouri" : 29, "Mississippi" : 28, "Montana" : 30, "North Carolina" : 37, "North Dakota" : 38, "Nebraska" : 31, "New Hampshire" : 33, "New Jersey" : 34, "New Mexico" : 35, "Nevada" : 32, "New York" : 36, "Ohio" : 39, "Oklahoma" : 40, "Oregon" : 41, "Pennsylvania" : 42, "Puerto Rico" : 72, "Rhode Island" : 44, "South Carolina" : 45, "South Dakota" : 46, "Tennessee" : 47, "Texas" : 48, "Utah" : 49, "Virginia" : 51, "Virgin Islands" : 78, "Vermont" : 50, "Washington" : 53, "Wisconsin" : 55, "West Virginia" : 54, "Wyoming" : 56, "Alberta" : "CA01", "British Columbia" : "CA02", "Manitoba" : "CA03", "New Brunswick" : "CA04", "Newfoundland and Labrador": "CA05", "Northwest Territories": "CA13", "Nova Scotia" : "CA07", "Nunavut" : "CA14", "Ontario" : "CA08", "Prince Edward Island" : "CA09", "Quebec" : "CA10", "Saskatchewan" : "CA11", "Yukon" : "CA12", } FIPS_COUNTIES = { 1: { '--All--': '%', 'Autauga County, AL': '001', 'Baldwin County, AL': '003', 'Barbour County, AL': '005', 'Bibb County, AL': '007', 'Blount County, AL': '009', 'Bullock County, AL': '011', 'Butler County, AL': '013', 'Calhoun County, AL': '015', 'Chambers County, AL': '017', 'Cherokee County, AL': '019', 'Chilton County, AL': '021', 'Choctaw County, AL': '023', 'Clarke County, AL': '025', 'Clay County, AL': '027', 'Cleburne County, AL': '029', 'Coffee County, AL': '031', 'Colbert County, AL': '033', 'Conecuh County, AL': '035', 'Coosa County, AL': '037', 'Covington County, AL': '039', 'Crenshaw County, AL': '041', 'Cullman County, AL': '043', 'Dale County, AL': '045', 'Dallas County, AL': '047', 'DeKalb County, AL': '049', 'Elmore County, AL': '051', 'Escambia County, AL': '053', 'Etowah County, AL': '055', 'Fayette County, AL': '057', 'Franklin County, AL': '059', 'Geneva County, AL': '061', 'Greene County, AL': '063', 'Hale County, AL': '065', 'Henry County, AL': '067', 'Houston County, AL': '069', 'Jackson County, AL': '071', 'Jefferson County, AL': '073', 'Lamar County, AL': '075', 'Lauderdale County, AL': '077', 'Lawrence County, AL': '079', 'Lee County, AL': '081', 'Limestone County, AL': '083', 'Lowndes County, AL': '085', 'Macon County, AL': '087', 'Madison County, AL': '089', 'Marengo County, AL': '091', 'Marion County, AL': '093', 'Marshall County, AL': '095', 'Mobile County, AL': '097', 'Monroe County, AL': '099', 'Montgomery County, AL': '101', 'Morgan County, AL': '103', 'Perry County, AL': '105', 'Pickens County, AL': '107', 'Pike County, AL': '109', 'Randolph County, AL': '111', 'Russell County, AL': '113', 'Shelby County, AL': '117', 'St. Clair County, AL': '115', 'Sumter County, AL': '119', 'Talladega County, AL': '121', 'Tallapoosa County, AL': '123', 'Tuscaloosa County, AL': '125', 'Walker County, AL': '127', 'Washington County, AL': '129', 'Wilcox County, AL': '131', 'Winston County, AL': '133'}, 2: { '--All--': '%', 'Aleutians East Borough, AK': '013', 'Aleutians West Census Area, AK': '016', 'Anchorage Borough/municipality, AK': '020', 'Bethel Census Area, AK': '050', 'Bristol Bay Borough, AK': '060', 'Denali Borough, AK': '068', 'Dillingham Census Area, AK': '070', 'Fairbanks North Star Borough, AK': '090', 'Haines Borough, AK': '100', 'Juneau Borough/city, AK': '110', 'Kenai Peninsula Borough, AK': '122', 'Ketchikan Gateway Borough, AK': '130', 'Kodiak Island Borough, AK': '150', 'Lake and Peninsula Borough, AK': '164', 'Matanuska-Susitna Borough, AK': '170', 'Nome Census Area, AK': '180', 'North Slope Borough, AK': '185', 'Northwest Arctic Borough, AK': '188', 'Prince of Wales-Outer Ketchikan Census Area, AK': '201', 'Sitka Borough/city, AK': '220', 'Skagway-Hoonah-Angoon Census Area, AK': '232', 'Southeast Fairbanks Census Area, AK': '240', 'Valdez-Cordova Census Area, AK': '261', 'Wade Hampton Census Area, AK': '270', 'Wrangell-Petersburg Census Area, AK': '280', 'Yakutat Borough, AK': '282', 'Yukon-Koyukuk Census Area, AK': '290'}, 4: { '--All--': '%', 'Apache County, AZ': '001', 'Cochise County, AZ': '003', 'Coconino County, AZ': '005', 'Gila County, AZ': '007', 'Graham County, AZ': '009', 'Greenlee County, AZ': '011', 'La Paz County, AZ': '012', 'Maricopa County, AZ': '013', 'Mohave County, AZ': '015', 'Navajo County, AZ': '017', 'Pima County, AZ': '019', 'Pinal County, AZ': '021', 'Santa Cruz County, AZ': '023', 'Yavapai County, AZ': '025', 'Yuma County, AZ': '027'}, 5: { '--All--': '%', 'Arkansas County, AR': '001', 'Ashley County, AR': '003', 'Baxter County, AR': '005', 'Benton County, AR': '007', 'Boone County, AR': '009', 'Bradley County, AR': '011', 'Calhoun County, AR': '013', 'Carroll County, AR': '015', 'Chicot County, AR': '017', 'Clark County, AR': '019', 'Clay County, AR': '021', 'Cleburne County, AR': '023', 'Cleveland County, AR': '025', 'Columbia County, AR': '027', 'Conway County, AR': '029', 'Craighead County, AR': '031', 'Crawford County, AR': '033', 'Crittenden County, AR': '035', 'Cross County, AR': '037', 'Dallas County, AR': '039', 'Desha County, AR': '041', 'Drew County, AR': '043', 'Faulkner County, AR': '045', 'Franklin County, AR': '047', 'Fulton County, AR': '049', 'Garland County, AR': '051', 'Grant County, AR': '053', 'Greene County, AR': '055', 'Hempstead County, AR': '057', 'Hot Spring County, AR': '059', 'Howard County, AR': '061', 'Independence County, AR': '063', 'Izard County, AR': '065', 'Jackson County, AR': '067', 'Jefferson County, AR': '069', 'Johnson County, AR': '071', 'Lafayette County, AR': '073', 'Lawrence County, AR': '075', 'Lee County, AR': '077', 'Lincoln County, AR': '079', 'Little River County, AR': '081', 'Logan County, AR': '083', 'Lonoke County, AR': '085', 'Madison County, AR': '087', 'Marion County, AR': '089', 'Miller County, AR': '091', 'Mississippi County, AR': '093', 'Monroe County, AR': '095', 'Montgomery County, AR': '097', 'Nevada County, AR': '099', 'Newton County, AR': '101', 'Ouachita County, AR': '103', 'Perry County, AR': '105', 'Phillips County, AR': '107', 'Pike County, AR': '109', 'Poinsett County, AR': '111', 'Polk County, AR': '113', 'Pope County, AR': '115', 'Prairie County, AR': '117', 'Pulaski County, AR': '119', 'Randolph County, AR': '121', 'Saline County, AR': '125', 'Scott County, AR': '127', 'Searcy County, AR': '129', 'Sebastian County, AR': '131', 'Sevier County, AR': '133', 'Sharp County, AR': '135', 'St. Francis County, AR': '123', 'Stone County, AR': '137', 'Union County, AR': '139', 'Van Buren County, AR': '141', 'Washington County, AR': '143', 'White County, AR': '145', 'Woodruff County, AR': '147', 'Yell County, AR': '149'}, 6: { '--All--': '%', 'Alameda County, CA': '001', 'Alpine County, CA': '003', 'Amador County, CA': '005', 'Butte County, CA': '007', 'Calaveras County, CA': '009', 'Colusa County, CA': '011', 'Contra Costa County, CA': '013', 'Del Norte County, CA': '015', 'El Dorado County, CA': '017', 'Fresno County, CA': '019', 'Glenn County, CA': '021', 'Humboldt County, CA': '023', 'Imperial County, CA': '025', 'Inyo County, CA': '027', 'Kern County, CA': '029', 'Kings County, CA': '031', 'Lake County, CA': '033', 'Lassen County, CA': '035', 'Los Angeles County, CA': '037', 'Madera County, CA': '039', 'Marin County, CA': '041', 'Mariposa County, CA': '043', 'Mendocino County, CA': '045', 'Merced County, CA': '047', 'Modoc County, CA': '049', 'Mono County, CA': '051', 'Monterey County, CA': '053', 'Napa County, CA': '055', 'Nevada County, CA': '057', 'Orange County, CA': '059', 'Placer County, CA': '061', 'Plumas County, CA': '063', 'Riverside County, CA': '065', 'Sacramento County, CA': '067', 'San Benito County, CA': '069', 'San Bernardino County, CA': '071', 'San Diego County, CA': '073', 'San Francisco County/city, CA': '075', 'San Joaquin County, CA': '077', 'San Luis Obispo County, CA': '079', 'San Mateo County, CA': '081', 'Santa Barbara County, CA': '083', 'Santa Clara County, CA': '085', 'Santa Cruz County, CA': '087', 'Shasta County, CA': '089', 'Sierra County, CA': '091', 'Siskiyou County, CA': '093', 'Solano County, CA': '095', 'Sonoma County, CA': '097', 'Stanislaus County, CA': '099', 'Sutter County, CA': '101', 'Tehama County, CA': '103', 'Trinity County, CA': '105', 'Tulare County, CA': '107', 'Tuolumne County, CA': '109', 'Ventura County, CA': '111', 'Yolo County, CA': '113', 'Yuba County, CA': '115'}, 8: { '--All--': '%', 'Adams County, CO': '001', 'Alamosa County, CO': '003', 'Arapahoe County, CO': '005', 'Archuleta County, CO': '007', 'Baca County, CO': '009', 'Bent County, CO': '011', 'Boulder County, CO': '013', 'Broomfield County/city, CO': '014', 'Chaffee County, CO': '015', 'Cheyenne County, CO': '017', 'Clear Creek County, CO': '019', 'Conejos County, CO': '021', 'Costilla County, CO': '023', 'Crowley County, CO': '025', 'Custer County, CO': '027', 'Delta County, CO': '029', 'Denver County/city, CO': '031', 'Dolores County, CO': '033', 'Douglas County, CO': '035', 'Eagle County, CO': '037', 'El Paso County, CO': '041', 'Elbert County, CO': '039', 'Fremont County, CO': '043', 'Garfield County, CO': '045', 'Gilpin County, CO': '047', 'Grand County, CO': '049', 'Gunnison County, CO': '051', 'Hinsdale County, CO': '053', 'Huerfano County, CO': '055', 'Jackson County, CO': '057', 'Jefferson County, CO': '059', 'Kiowa County, CO': '061', 'Kit Carson County, CO': '063', 'La Plata County, CO': '067', 'Lake County, CO': '065', 'Larimer County, CO': '069', 'Las Animas County, CO': '071', 'Lincoln County, CO': '073', 'Logan County, CO': '075', 'Mesa County, CO': '077', 'Mineral County, CO': '079', 'Moffat County, CO': '081', 'Montezuma County, CO': '083', 'Montrose County, CO': '085', 'Morgan County, CO': '087', 'Otero County, CO': '089', 'Ouray County, CO': '091', 'Park County, CO': '093', 'Phillips County, CO': '095', 'Pitkin County, CO': '097', 'Prowers County, CO': '099', 'Pueblo County, CO': '101', 'Rio Blanco County, CO': '103', 'Rio Grande County, CO': '105', 'Routt County, CO': '107', 'Saguache County, CO': '109', 'San Juan County, CO': '111', 'San Miguel County, CO': '113', 'Sedgwick County, CO': '115', 'Summit County, CO': '117', 'Teller County, CO': '119', 'Washington County, CO': '121', 'Weld County, CO': '123', 'Yuma County, CO': '125'}, 9: { '--All--': '%', 'Fairfield County, CT': '001', 'Hartford County, CT': '003', 'Litchfield County, CT': '005', 'Middlesex County, CT': '007', 'New Haven County, CT': '009', 'New London County, CT': '011', 'Tolland County, CT': '013', 'Windham County, CT': '015'}, 10: { '--All--': '%', 'Kent County, DE': '001', 'New Castle County, DE': '003', 'Sussex County, DE': '005'}, 11: { '--All--': '%', 'District of Columbia': '001'}, 12: { '--All--': '%', 'Alachua County, FL': '001', 'Baker County, FL': '003', 'Bay County, FL': '005', 'Bradford County, FL': '007', 'Brevard County, FL': '009', 'Broward County, FL': '011', 'Calhoun County, FL': '013', 'Charlotte County, FL': '015', 'Citrus County, FL': '017', 'Clay County, FL': '019', 'Collier County, FL': '021', 'Columbia County, FL': '023', 'DeSoto County, FL': '027', 'Dixie County, FL': '029', 'Duval County, FL': '031', 'Escambia County, FL': '033', 'Flagler County, FL': '035', 'Franklin County, FL': '037', 'Gadsden County, FL': '039', 'Gilchrist County, FL': '041', 'Glades County, FL': '043', 'Gulf County, FL': '045', 'Hamilton County, FL': '047', 'Hardee County, FL': '049', 'Hendry County, FL': '051', 'Hernando County, FL': '053', 'Highlands County, FL': '055', 'Hillsborough County, FL': '057', 'Holmes County, FL': '059', 'Indian River County, FL': '061', 'Jackson County, FL': '063', 'Jefferson County, FL': '065', 'Lafayette County, FL': '067', 'Lake County, FL': '069', 'Lee County, FL': '071', 'Leon County, FL': '073', 'Levy County, FL': '075', 'Liberty County, FL': '077', 'Madison County, FL': '079', 'Manatee County, FL': '081', 'Marion County, FL': '083', 'Martin County, FL': '085', 'Miami-Dade County, FL': '086', 'Monroe County, FL': '087', 'Nassau County, FL': '089', 'Okaloosa County, FL': '091', 'Okeechobee County, FL': '093', 'Orange County, FL': '095', 'Osceola County, FL': '097', 'Palm Beach County, FL': '099', 'Pasco County, FL': '101', 'Pinellas County, FL': '103', 'Polk County, FL': '105', 'Putnam County, FL': '107', 'Santa Rosa County, FL': '113', 'Sarasota County, FL': '115', 'Seminole County, FL': '117', 'St. Johns County, FL': '109', 'St. Lucie County, FL': '111', 'Sumter County, FL': '119', 'Suwannee County, FL': '121', 'Taylor County, FL': '123', 'Union County, FL': '125', 'Volusia County, FL': '127', 'Wakulla County, FL': '129', 'Walton County, FL': '131', 'Washington County, FL': '133'}, 13: { '--All--': '%', 'Appling County, GA': '001', 'Atkinson County, GA': '003', 'Bacon County, GA': '005', 'Baker County, GA': '007', 'Baldwin County, GA': '009', 'Banks County, GA': '011', 'Barrow County, GA': '013', 'Bartow County, GA': '015', 'Ben Hill County, GA': '017', 'Berrien County, GA': '019', 'Bibb County, GA': '021', 'Bleckley County, GA': '023', 'Brantley County, GA': '025', 'Brooks County, GA': '027', 'Bryan County, GA': '029', 'Bulloch County, GA': '031', 'Burke County, GA': '033', 'Butts County, GA': '035', 'Calhoun County, GA': '037', 'Camden County, GA': '039', 'Candler County, GA': '043', 'Carroll County, GA': '045', 'Catoosa County, GA': '047', 'Charlton County, GA': '049', 'Chatham County, GA': '051', 'Chattahoochee County, GA': '053', 'Chattooga County, GA': '055', 'Cherokee County, GA': '057', 'Clarke County, GA': '059', 'Clay County, GA': '061', 'Clayton County, GA': '063', 'Clinch County, GA': '065', 'Cobb County, GA': '067', 'Coffee County, GA': '069', 'Colquitt County, GA': '071', 'Columbia County, GA': '073', 'Cook County, GA': '075', 'Coweta County, GA': '077', 'Crawford County, GA': '079', 'Crisp County, GA': '081', 'Dade County, GA': '083', 'Dawson County, GA': '085', 'DeKalb County, GA': '089', 'Decatur County, GA': '087', 'Dodge County, GA': '091', 'Dooly County, GA': '093', 'Dougherty County, GA': '095', 'Douglas County, GA': '097', 'Early County, GA': '099', 'Echols County, GA': '101', 'Effingham County, GA': '103', 'Elbert County, GA': '105', 'Emanuel County, GA': '107', 'Evans County, GA': '109', 'Fannin County, GA': '111', 'Fayette County, GA': '113', 'Floyd County, GA': '115', 'Forsyth County, GA': '117', 'Franklin County, GA': '119', 'Fulton County, GA': '121', 'Gilmer County, GA': '123', 'Glascock County, GA': '125', 'Glynn County, GA': '127', 'Gordon County, GA': '129', 'Grady County, GA': '131', 'Greene County, GA': '133', 'Gwinnett County, GA': '135', 'Habersham County, GA': '137', 'Hall County, GA': '139', 'Hancock County, GA': '141', 'Haralson County, GA': '143', 'Harris County, GA': '145', 'Hart County, GA': '147', 'Heard County, GA': '149', 'Henry County, GA': '151', 'Houston County, GA': '153', 'Irwin County, GA': '155', 'Jackson County, GA': '157', 'Jasper County, GA': '159', 'Jeff Davis County, GA': '161', 'Jefferson County, GA': '163', 'Jenkins County, GA': '165', 'Johnson County, GA': '167', 'Jones County, GA': '169', 'Lamar County, GA': '171', 'Lanier County, GA': '173', 'Laurens County, GA': '175', 'Lee County, GA': '177', 'Liberty County, GA': '179', 'Lincoln County, GA': '181', 'Long County, GA': '183', 'Lowndes County, GA': '185', 'Lumpkin County, GA': '187', 'Macon County, GA': '193', 'Madison County, GA': '195', 'Marion County, GA': '197', 'McDuffie County, GA': '189', 'McIntosh County, GA': '191', 'Meriwether County, GA': '199', 'Miller County, GA': '201', 'Mitchell County, GA': '205', 'Monroe County, GA': '207', 'Montgomery County, GA': '209', 'Morgan County, GA': '211', 'Murray County, GA': '213', 'Muscogee County, GA': '215', 'Newton County, GA': '217', 'Oconee County, GA': '219', 'Oglethorpe County, GA': '221', 'Paulding County, GA': '223', 'Peach County, GA': '225', 'Pickens County, GA': '227', 'Pierce County, GA': '229', 'Pike County, GA': '231', 'Polk County, GA': '233', 'Pulaski County, GA': '235', 'Putnam County, GA': '237', 'Quitman County, GA': '239', 'Rabun County, GA': '241', 'Randolph County, GA': '243', 'Richmond County, GA': '245', 'Rockdale County, GA': '247', 'Schley County, GA': '249', 'Screven County, GA': '251', 'Seminole County, GA': '253', 'Spalding County, GA': '255', 'Stephens County, GA': '257', 'Stewart County, GA': '259', 'Sumter County, GA': '261', 'Talbot County, GA': '263', 'Taliaferro County, GA': '265', 'Tattnall County, GA': '267', 'Taylor County, GA': '269', 'Telfair County, GA': '271', 'Terrell County, GA': '273', 'Thomas County, GA': '275', 'Tift County, GA': '277', 'Toombs County, GA': '279', 'Towns County, GA': '281', 'Treutlen County, GA': '283', 'Troup County, GA': '285', 'Turner County, GA': '287', 'Twiggs County, GA': '289', 'Union County, GA': '291', 'Upson County, GA': '293', 'Walker County, GA': '295', 'Walton County, GA': '297', 'Ware County, GA': '299', 'Warren County, GA': '301', 'Washington County, GA': '303', 'Wayne County, GA': '305', 'Webster County, GA': '307', 'Wheeler County, GA': '309', 'White County, GA': '311', 'Whitfield County, GA': '313', 'Wilcox County, GA': '315', 'Wilkes County, GA': '317', 'Wilkinson County, GA': '319', 'Worth County, GA': '321'}, 15: { '--All--': '%', 'Hawaii County, HI': '001', 'Honolulu County/city, HI': '003', 'Kauai County, HI': '007', 'Maui County, HI': '009'}, 16: { '--All--': '%', 'Ada County, ID': '001', 'Adams County, ID': '003', 'Bannock County, ID': '005', 'Bear Lake County, ID': '007', 'Benewah County, ID': '009', 'Bingham County, ID': '011', 'Blaine County, ID': '013', 'Boise County, ID': '015', 'Bonner County, ID': '017', 'Bonneville County, ID': '019', 'Boundary County, ID': '021', 'Butte County, ID': '023', 'Camas County, ID': '025', 'Canyon County, ID': '027', 'Caribou County, ID': '029', 'Cassia County, ID': '031', 'Clark County, ID': '033', 'Clearwater County, ID': '035', 'Custer County, ID': '037', 'Elmore County, ID': '039', 'Franklin County, ID': '041', 'Fremont County, ID': '043', 'Gem County, ID': '045', 'Gooding County, ID': '047', 'Idaho County, ID': '049', 'Jefferson County, ID': '051', 'Jerome County, ID': '053', 'Kootenai County, ID': '055', 'Latah County, ID': '057', 'Lemhi County, ID': '059', 'Lewis County, ID': '061', 'Lincoln County, ID': '063', 'Madison County, ID': '065', 'Minidoka County, ID': '067', 'Nez Perce County, ID': '069', 'Oneida County, ID': '071', 'Owyhee County, ID': '073', 'Payette County, ID': '075', 'Power County, ID': '077', 'Shoshone County, ID': '079', 'Teton County, ID': '081', 'Twin Falls County, ID': '083', 'Valley County, ID': '085', 'Washington County, ID': '087'}, 17: { '--All--': '%', 'Adams County, IL': '001', 'Alexander County, IL': '003', 'Bond County, IL': '005', 'Boone County, IL': '007', 'Brown County, IL': '009', 'Bureau County, IL': '011', 'Calhoun County, IL': '013', 'Carroll County, IL': '015', 'Cass County, IL': '017', 'Champaign County, IL': '019', 'Christian County, IL': '021', 'Clark County, IL': '023', 'Clay County, IL': '025', 'Clinton County, IL': '027', 'Coles County, IL': '029', 'Cook County, IL': '031', 'Crawford County, IL': '033', 'Cumberland County, IL': '035', 'De Witt County, IL': '039', 'DeKalb County, IL': '037', 'Douglas County, IL': '041', 'DuPage County, IL': '043', 'Edgar County, IL': '045', 'Edwards County, IL': '047', 'Effingham County, IL': '049', 'Fayette County, IL': '051', 'Ford County, IL': '053', 'Franklin County, IL': '055', 'Fulton County, IL': '057', 'Gallatin County, IL': '059', 'Greene County, IL': '061', 'Grundy County, IL': '063', 'Hamilton County, IL': '065', 'Hancock County, IL': '067', 'Hardin County, IL': '069', 'Henderson County, IL': '071', 'Henry County, IL': '073', 'Iroquois County, IL': '075', 'Jackson County, IL': '077', 'Jasper County, IL': '079', 'Jefferson County, IL': '081', 'Jersey County, IL': '083', 'Jo Daviess County, IL': '085', 'Johnson County, IL': '087', 'Kane County, IL': '089', 'Kankakee County, IL': '091', 'Kendall County, IL': '093', 'Knox County, IL': '095', 'La Salle County, IL': '099', 'Lake County, IL': '097', 'Lawrence County, IL': '101', 'Lee County, IL': '103', 'Livingston County, IL': '105', 'Logan County, IL': '107', 'Macon County, IL': '115', 'Macoupin County, IL': '117', 'Madison County, IL': '119', 'Marion County, IL': '121', 'Marshall County, IL': '123', 'Mason County, IL': '125', 'Massac County, IL': '127', 'McDonough County, IL': '109', 'McHenry County, IL': '111', 'McLean County, IL': '113', 'Menard County, IL': '129', 'Mercer County, IL': '131', 'Monroe County, IL': '133', 'Montgomery County, IL': '135', 'Morgan County, IL': '137', 'Moultrie County, IL': '139', 'Ogle County, IL': '141', 'Peoria County, IL': '143', 'Perry County, IL': '145', 'Piatt County, IL': '147', 'Pike County, IL': '149', 'Pope County, IL': '151', 'Pulaski County, IL': '153', 'Putnam County, IL': '155', 'Randolph County, IL': '157', 'Richland County, IL': '159', 'Rock Island County, IL': '161', 'Saline County, IL': '165', 'Sangamon County, IL': '167', 'Schuyler County, IL': '169', 'Scott County, IL': '171', 'Shelby County, IL': '173', 'St. Clair County, IL': '163', 'Stark County, IL': '175', 'Stephenson County, IL': '177', 'Tazewell County, IL': '179', 'Union County, IL': '181', 'Vermilion County, IL': '183', 'Wabash County, IL': '185', 'Warren County, IL': '187', 'Washington County, IL': '189', 'Wayne County, IL': '191', 'White County, IL': '193', 'Whiteside County, IL': '195', 'Will County, IL': '197', 'Williamson County, IL': '199', 'Winnebago County, IL': '201', 'Woodford County, IL': '203'}, 18: { '--All--': '%', 'Adams County, IN': '001', 'Allen County, IN': '003', 'Bartholomew County, IN': '005', 'Benton County, IN': '007', 'Blackford County, IN': '009', 'Boone County, IN': '011', 'Brown County, IN': '013', 'Carroll County, IN': '015', 'Cass County, IN': '017', 'Clark County, IN': '019', 'Clay County, IN': '021', 'Clinton County, IN': '023', 'Crawford County, IN': '025', 'Daviess County, IN': '027', 'DeKalb County, IN': '033', 'Dearborn County, IN': '029', 'Decatur County, IN': '031', 'Delaware County, IN': '035', 'Dubois County, IN': '037', 'Elkhart County, IN': '039', 'Fayette County, IN': '041', 'Floyd County, IN': '043', 'Fountain County, IN': '045', 'Franklin County, IN': '047', 'Fulton County, IN': '049', 'Gibson County, IN': '051', 'Grant County, IN': '053', 'Greene County, IN': '055', 'Hamilton County, IN': '057', 'Hancock County, IN': '059', 'Harrison County, IN': '061', 'Hendricks County, IN': '063', 'Henry County, IN': '065', 'Howard County, IN': '067', 'Huntington County, IN': '069', 'Jackson County, IN': '071', 'Jasper County, IN': '073', 'Jay County, IN': '075', 'Jefferson County, IN': '077', 'Jennings County, IN': '079', 'Johnson County, IN': '081', 'Knox County, IN': '083', 'Kosciusko County, IN': '085', 'LaGrange County, IN': '087', 'LaPorte County, IN': '091', 'Lake County, IN': '089', 'Lawrence County, IN': '093', 'Madison County, IN': '095', 'Marion County, IN': '097', 'Marshall County, IN': '099', 'Martin County, IN': '101', 'Miami County, IN': '103', 'Monroe County, IN': '105', 'Montgomery County, IN': '107', 'Morgan County, IN': '109', 'Newton County, IN': '111', 'Noble County, IN': '113', 'Ohio County, IN': '115', 'Orange County, IN': '117', 'Owen County, IN': '119', 'Parke County, IN': '121', 'Perry County, IN': '123', 'Pike County, IN': '125', 'Porter County, IN': '127', 'Posey County, IN': '129', 'Pulaski County, IN': '131', 'Putnam County, IN': '133', 'Randolph County, IN': '135', 'Ripley County, IN': '137', 'Rush County, IN': '139', 'Scott County, IN': '143', 'Shelby County, IN': '145', 'Spencer County, IN': '147', 'St. Joseph County, IN': '141', 'Starke County, IN': '149', 'Steuben County, IN': '151', 'Sullivan County, IN': '153', 'Switzerland County, IN': '155', 'Tippecanoe County, IN': '157', 'Tipton County, IN': '159', 'Union County, IN': '161', 'Vanderburgh County, IN': '163', 'Vermillion County, IN': '165', 'Vigo County, IN': '167', 'Wabash County, IN': '169', 'Warren County, IN': '171', 'Warrick County, IN': '173', 'Washington County, IN': '175', 'Wayne County, IN': '177', 'Wells County, IN': '179', 'White County, IN': '181', 'Whitley County, IN': '183'}, 19: { '--All--': '%', 'Adair County, IA': '001', 'Adams County, IA': '003', 'Allamakee County, IA': '005', 'Appanoose County, IA': '007', 'Audubon County, IA': '009', 'Benton County, IA': '011', 'Black Hawk County, IA': '013', 'Boone County, IA': '015', 'Bremer County, IA': '017', 'Buchanan County, IA': '019', 'Buena Vista County, IA': '021', 'Butler County, IA': '023', 'Calhoun County, IA': '025', 'Carroll County, IA': '027', 'Cass County, IA': '029', 'Cedar County, IA': '031', 'Cerro Gordo County, IA': '033', 'Cherokee County, IA': '035', 'Chickasaw County, IA': '037', 'Clarke County, IA': '039', 'Clay County, IA': '041', 'Clayton County, IA': '043', 'Clinton County, IA': '045', 'Crawford County, IA': '047', 'Dallas County, IA': '049', 'Davis County, IA': '051', 'Decatur County, IA': '053', 'Delaware County, IA': '055', 'Des Moines County, IA': '057', 'Dickinson County, IA': '059', 'Dubuque County, IA': '061', 'Emmet County, IA': '063', 'Fayette County, IA': '065', 'Floyd County, IA': '067', 'Franklin County, IA': '069', 'Fremont County, IA': '071', 'Greene County, IA': '073', 'Grundy County, IA': '075', 'Guthrie County, IA': '077', 'Hamilton County, IA': '079', 'Hancock County, IA': '081', 'Hardin County, IA': '083', 'Harrison County, IA': '085', 'Henry County, IA': '087', 'Howard County, IA': '089', 'Humboldt County, IA': '091', 'Ida County, IA': '093', 'Iowa County, IA': '095', 'Jackson County, IA': '097', 'Jasper County, IA': '099', 'Jefferson County, IA': '101', 'Johnson County, IA': '103', 'Jones County, IA': '105', 'Keokuk County, IA': '107', 'Kossuth County, IA': '109', 'Lee County, IA': '111', 'Linn County, IA': '113', 'Louisa County, IA': '115', 'Lucas County, IA': '117', 'Lyon County, IA': '119', 'Madison County, IA': '121', 'Mahaska County, IA': '123', 'Marion County, IA': '125', 'Marshall County, IA': '127', 'Mills County, IA': '129', 'Mitchell County, IA': '131', 'Monona County, IA': '133', 'Monroe County, IA': '135', 'Montgomery County, IA': '137', 'Muscatine County, IA': '139', "O'Brien County, IA": '141', 'Osceola County, IA': '143', 'Page County, IA': '145', 'Palo Alto County, IA': '147', 'Plymouth County, IA': '149', 'Pocahontas County, IA': '151', 'Polk County, IA': '153', 'Pottawattamie County, IA': '155', 'Poweshiek County, IA': '157', 'Ringgold County, IA': '159', 'Sac County, IA': '161', 'Scott County, IA': '163', 'Shelby County, IA': '165', 'Sioux County, IA': '167', 'Story County, IA': '169', 'Tama County, IA': '171', 'Taylor County, IA': '173', 'Union County, IA': '175', 'Van Buren County, IA': '177', 'Wapello County, IA': '179', 'Warren County, IA': '181', 'Washington County, IA': '183', 'Wayne County, IA': '185', 'Webster County, IA': '187', 'Winnebago County, IA': '189', 'Winneshiek County, IA': '191', 'Woodbury County, IA': '193', 'Worth County, IA': '195', 'Wright County, IA': '197'}, 20: { '--All--': '%', 'Allen County, KS': '001', 'Anderson County, KS': '003', 'Atchison County, KS': '005', 'Barber County, KS': '007', 'Barton County, KS': '009', 'Bourbon County, KS': '011', 'Brown County, KS': '013', 'Butler County, KS': '015', 'Chase County, KS': '017', 'Chautauqua County, KS': '019', 'Cherokee County, KS': '021', 'Cheyenne County, KS': '023', 'Clark County, KS': '025', 'Clay County, KS': '027', 'Cloud County, KS': '029', 'Coffey County, KS': '031', 'Comanche County, KS': '033', 'Cowley County, KS': '035', 'Crawford County, KS': '037', 'Decatur County, KS': '039', 'Dickinson County, KS': '041', 'Doniphan County, KS': '043', 'Douglas County, KS': '045', 'Edwards County, KS': '047', 'Elk County, KS': '049', 'Ellis County, KS': '051', 'Ellsworth County, KS': '053', 'Finney County, KS': '055', 'Ford County, KS': '057', 'Franklin County, KS': '059', 'Geary County, KS': '061', 'Gove County, KS': '063', 'Graham County, KS': '065', 'Grant County, KS': '067', 'Gray County, KS': '069', 'Greeley County, KS': '071', 'Greenwood County, KS': '073', 'Hamilton County, KS': '075', 'Harper County, KS': '077', 'Harvey County, KS': '079', 'Haskell County, KS': '081', 'Hodgeman County, KS': '083', 'Jackson County, KS': '085', 'Jefferson County, KS': '087', 'Jewell County, KS': '089', 'Johnson County, KS': '091', 'Kearny County, KS': '093', 'Kingman County, KS': '095', 'Kiowa County, KS': '097', 'Labette County, KS': '099', 'Lane County, KS': '101', 'Leavenworth County, KS': '103', 'Lincoln County, KS': '105', 'Linn County, KS': '107', 'Logan County, KS': '109', 'Lyon County, KS': '111', 'Marion County, KS': '115', 'Marshall County, KS': '117', 'McPherson County, KS': '113', 'Meade County, KS': '119', 'Miami County, KS': '121', 'Mitchell County, KS': '123', 'Montgomery County, KS': '125', 'Morris County, KS': '127', 'Morton County, KS': '129', 'Nemaha County, KS': '131', 'Neosho County, KS': '133', 'Ness County, KS': '135', 'Norton County, KS': '137', 'Osage County, KS': '139', 'Osborne County, KS': '141', 'Ottawa County, KS': '143', 'Pawnee County, KS': '145', 'Phillips County, KS': '147', 'Pottawatomie County, KS': '149', 'Pratt County, KS': '151', 'Rawlins County, KS': '153', 'Reno County, KS': '155', 'Republic County, KS': '157', 'Rice County, KS': '159', 'Riley County, KS': '161', 'Rooks County, KS': '163', 'Rush County, KS': '165', 'Russell County, KS': '167', 'Saline County, KS': '169', 'Scott County, KS': '171', 'Sedgwick County, KS': '173', 'Seward County, KS': '175', 'Shawnee County, KS': '177', 'Sheridan County, KS': '179', 'Sherman County, KS': '181', 'Smith County, KS': '183', 'Stafford County, KS': '185', 'Stanton County, KS': '187', 'Stevens County, KS': '189', 'Sumner County, KS': '191', 'Thomas County, KS': '193', 'Trego County, KS': '195', 'Wabaunsee County, KS': '197', 'Wallace County, KS': '199', 'Washington County, KS': '201', 'Wichita County, KS': '203', 'Wilson County, KS': '205', 'Woodson County, KS': '207', 'Wyandotte County, KS': '209'}, 21: { '--All--': '%', 'Adair County, KY': '001', 'Allen County, KY': '003', 'Anderson County, KY': '005', 'Ballard County, KY': '007', 'Barren County, KY': '009', 'Bath County, KY': '011', 'Bell County, KY': '013', 'Boone County, KY': '015', 'Bourbon County, KY': '017', 'Boyd County, KY': '019', 'Boyle County, KY': '021', 'Bracken County, KY': '023', 'Breathitt County, KY': '025', 'Breckinridge County, KY': '027', 'Bullitt County, KY': '029', 'Butler County, KY': '031', 'Caldwell County, KY': '033', 'Calloway County, KY': '035', 'Campbell County, KY': '037', 'Carlisle County, KY': '039', 'Carroll County, KY': '041', 'Carter County, KY': '043', 'Casey County, KY': '045', 'Christian County, KY': '047', 'Clark County, KY': '049', 'Clay County, KY': '051', 'Clinton County, KY': '053', 'Crittenden County, KY': '055', 'Cumberland County, KY': '057', 'Daviess County, KY': '059', 'Edmonson County, KY': '061', 'Elliott County, KY': '063', 'Estill County, KY': '065', 'Fayette County, KY': '067', 'Fleming County, KY': '069', 'Floyd County, KY': '071', 'Franklin County, KY': '073', 'Fulton County, KY': '075', 'Gallatin County, KY': '077', 'Garrard County, KY': '079', 'Grant County, KY': '081', 'Graves County, KY': '083', 'Grayson County, KY': '085', 'Green County, KY': '087', 'Greenup County, KY': '089', 'Hancock County, KY': '091', 'Hardin County, KY': '093', 'Harlan County, KY': '095', 'Harrison County, KY': '097', 'Hart County, KY': '099', 'Henderson County, KY': '101', 'Henry County, KY': '103', 'Hickman County, KY': '105', 'Hopkins County, KY': '107', 'Jackson County, KY': '109', 'Jefferson County, KY': '111', 'Jessamine County, KY': '113', 'Johnson County, KY': '115', 'Kenton County, KY': '117', 'Knott County, KY': '119', 'Knox County, KY': '121', 'Larue County, KY': '123', 'Laurel County, KY': '125', 'Lawrence County, KY': '127', 'Lee County, KY': '129', 'Leslie County, KY': '131', 'Letcher County, KY': '133', 'Lewis County, KY': '135', 'Lincoln County, KY': '137', 'Livingston County, KY': '139', 'Logan County, KY': '141', 'Lyon County, KY': '143', 'Madison County, KY': '151', 'Magoffin County, KY': '153', 'Marion County, KY': '155', 'Marshall County, KY': '157', 'Martin County, KY': '159', 'Mason County, KY': '161', 'McCracken County, KY': '145', 'McCreary County, KY': '147', 'McLean County, KY': '149', 'Meade County, KY': '163', 'Menifee County, KY': '165', 'Mercer County, KY': '167', 'Metcalfe County, KY': '169', 'Monroe County, KY': '171', 'Montgomery County, KY': '173', 'Morgan County, KY': '175', 'Muhlenberg County, KY': '177', 'Nelson County, KY': '179', 'Nicholas County, KY': '181', 'Ohio County, KY': '183', 'Oldham County, KY': '185', 'Owen County, KY': '187', 'Owsley County, KY': '189', 'Pendleton County, KY': '191', 'Perry County, KY': '193', 'Pike County, KY': '195', 'Powell County, KY': '197', 'Pulaski County, KY': '199', 'Robertson County, KY': '201', 'Rockcastle County, KY': '203', 'Rowan County, KY': '205', 'Russell County, KY': '207', 'Scott County, KY': '209', 'Shelby County, KY': '211', 'Simpson County, KY': '213', 'Spencer County, KY': '215', 'Taylor County, KY': '217', 'Todd County, KY': '219', 'Trigg County, KY': '221', 'Trimble County, KY': '223', 'Union County, KY': '225', 'Warren County, KY': '227', 'Washington County, KY': '229', 'Wayne County, KY': '231', 'Webster County, KY': '233', 'Whitley County, KY': '235', 'Wolfe County, KY': '237', 'Woodford County, KY': '239'}, 22: { '--All--': '%', 'Acadia Parish, LA': '001', 'Allen Parish, LA': '003', 'Ascension Parish, LA': '005', 'Assumption Parish, LA': '007', 'Avoyelles Parish, LA': '009', 'Beauregard Parish, LA': '011', 'Bienville Parish, LA': '013', 'Bossier Parish, LA': '015', 'Caddo Parish, LA': '017', 'Calcasieu Parish, LA': '019', 'Caldwell Parish, LA': '021', 'Cameron Parish, LA': '023', 'Catahoula Parish, LA': '025', 'Claiborne Parish, LA': '027', 'Concordia Parish, LA': '029', 'De Soto Parish, LA': '031', 'East Baton Rouge Parish, LA': '033', 'East Carroll Parish, LA': '035', 'East Feliciana Parish, LA': '037', 'Evangeline Parish, LA': '039', 'Franklin Parish, LA': '041', 'Grant Parish, LA': '043', 'Iberia Parish, LA': '045', 'Iberville Parish, LA': '047', 'Jackson Parish, LA': '049', 'Jefferson Davis Parish, LA': '053', 'Jefferson Parish, LA': '051', 'La Salle Parish, LA': '059', 'Lafayette Parish, LA': '055', 'Lafourche Parish, LA': '057', 'Lincoln Parish, LA': '061', 'Livingston Parish, LA': '063', 'Madison Parish, LA': '065', 'Morehouse Parish, LA': '067', 'Natchitoches Parish, LA': '069', 'Orleans Parish, LA': '071', 'Ouachita Parish, LA': '073', 'Plaquemines Parish, LA': '075', 'Pointe Coupee Parish, LA': '077', 'Rapides Parish, LA': '079', 'Red River Parish, LA': '081', 'Richland Parish, LA': '083', 'Sabine Parish, LA': '085', 'St. Bernard Parish, LA': '087', 'St. Charles Parish, LA': '089', 'St. Helena Parish, LA': '091', 'St. James Parish, LA': '093', 'St. John the Baptist Parish, LA': '095', 'St. Landry Parish, LA': '097', 'St. Martin Parish, LA': '099', 'St. Mary Parish, LA': '101', 'St. Tammany Parish, LA': '103', 'Tangipahoa Parish, LA': '105', 'Tensas Parish, LA': '107', 'Terrebonne Parish, LA': '109', 'Union Parish, LA': '111', 'Vermilion Parish, LA': '113', 'Vernon Parish, LA': '115', 'Washington Parish, LA': '117', 'Webster Parish, LA': '119', 'West Baton Rouge Parish, LA': '121', 'West Carroll Parish, LA': '123', 'West Feliciana Parish, LA': '125', 'Winn Parish, LA': '127'}, 23: { '--All--': '%', 'Androscoggin County, ME': '001', 'Aroostook County, ME': '003', 'Cumberland County, ME': '005', 'Franklin County, ME': '007', 'Hancock County, ME': '009', 'Kennebec County, ME': '011', 'Knox County, ME': '013', 'Lincoln County, ME': '015', 'Oxford County, ME': '017', 'Penobscot County, ME': '019', 'Piscataquis County, ME': '021', 'Sagadahoc County, ME': '023', 'Somerset County, ME': '025', 'Waldo County, ME': '027', 'Washington County, ME': '029', 'York County, ME': '031'}, 24: { '--All--': '%', 'Allegany County, MD': '001', 'Anne Arundel County, MD': '003', 'Baltimore County, MD': '005', 'Baltimore city, MD': '510', 'Calvert County, MD': '009', 'Caroline County, MD': '011', 'Carroll County, MD': '013', 'Cecil County, MD': '015', 'Charles County, MD': '017', 'Dorchester County, MD': '019', 'Frederick County, MD': '021', 'Garrett County, MD': '023', 'Harford County, MD': '025', 'Howard County, MD': '027', 'Kent County, MD': '029', 'Montgomery County, MD': '031', "Prince George's County, MD": '033', "Queen Anne's County, MD": '035', 'Somerset County, MD': '039', "St. Mary's County, MD": '037', 'Talbot County, MD': '041', 'Washington County, MD': '043', 'Wicomico County, MD': '045', 'Worcester County, MD': '047'}, 25: { '--All--': '%', 'Barnstable County, MA': '001', 'Berkshire County, MA': '003', 'Bristol County, MA': '005', 'Dukes County, MA': '007', 'Essex County, MA': '009', 'Franklin County, MA': '011', 'Hampden County, MA': '013', 'Hampshire County, MA': '015', 'Middlesex County, MA': '017', 'Nantucket County/town, MA': '019', 'Norfolk County, MA': '021', 'Plymouth County, MA': '023', 'Suffolk County, MA': '025', 'Worcester County, MA': '027'}, 26: { '--All--': '%', 'Alcona County, MI': '001', 'Alger County, MI': '003', 'Allegan County, MI': '005', 'Alpena County, MI': '007', 'Antrim County, MI': '009', 'Arenac County, MI': '011', 'Baraga County, MI': '013', 'Barry County, MI': '015', 'Bay County, MI': '017', 'Benzie County, MI': '019', 'Berrien County, MI': '021', 'Branch County, MI': '023', 'Calhoun County, MI': '025', 'Cass County, MI': '027', 'Charlevoix County, MI': '029', 'Cheboygan County, MI': '031', 'Chippewa County, MI': '033', 'Clare County, MI': '035', 'Clinton County, MI': '037', 'Crawford County, MI': '039', 'Delta County, MI': '041', 'Dickinson County, MI': '043', 'Eaton County, MI': '045', 'Emmet County, MI': '047', 'Genesee County, MI': '049', 'Gladwin County, MI': '051', 'Gogebic County, MI': '053', 'Grand Traverse County, MI': '055', 'Gratiot County, MI': '057', 'Hillsdale County, MI': '059', 'Houghton County, MI': '061', 'Huron County, MI': '063', 'Ingham County, MI': '065', 'Ionia County, MI': '067', 'Iosco County, MI': '069', 'Iron County, MI': '071', 'Isabella County, MI': '073', 'Jackson County, MI': '075', 'Kalamazoo County, MI': '077', 'Kalkaska County, MI': '079', 'Kent County, MI': '081', 'Keweenaw County, MI': '083', 'Lake County, MI': '085', 'Lapeer County, MI': '087', 'Leelanau County, MI': '089', 'Lenawee County, MI': '091', 'Livingston County, MI': '093', 'Luce County, MI': '095', 'Mackinac County, MI': '097', 'Macomb County, MI': '099', 'Manistee County, MI': '101', 'Marquette County, MI': '103', 'Mason County, MI': '105', 'Mecosta County, MI': '107', 'Menominee County, MI': '109', 'Midland County, MI': '111', 'Missaukee County, MI': '113', 'Monroe County, MI': '115', 'Montcalm County, MI': '117', 'Montmorency County, MI': '119', 'Muskegon County, MI': '121', 'Newaygo County, MI': '123', 'Oakland County, MI': '125', 'Oceana County, MI': '127', 'Ogemaw County, MI': '129', 'Ontonagon County, MI': '131', 'Osceola County, MI': '133', 'Oscoda County, MI': '135', 'Otsego County, MI': '137', 'Ottawa County, MI': '139', 'Presque Isle County, MI': '141', 'Roscommon County, MI': '143', 'Saginaw County, MI': '145', 'Sanilac County, MI': '151', 'Schoolcraft County, MI': '153', 'Shiawassee County, MI': '155', 'St. Clair County, MI': '147', 'St. Joseph County, MI': '149', 'Tuscola County, MI': '157', 'Van Buren County, MI': '159', 'Washtenaw County, MI': '161', 'Wayne County, MI': '163', 'Wexford County, MI': '165'}, 27: { '--All--': '%', 'Aitkin County, MN': '001', 'Anoka County, MN': '003', 'Becker County, MN': '005', 'Beltrami County, MN': '007', 'Benton County, MN': '009', 'Big Stone County, MN': '011', 'Blue Earth County, MN': '013', 'Brown County, MN': '015', 'Carlton County, MN': '017', 'Carver County, MN': '019', 'Cass County, MN': '021', 'Chippewa County, MN': '023', 'Chisago County, MN': '025', 'Clay County, MN': '027', 'Clearwater County, MN': '029', 'Cook County, MN': '031', 'Cottonwood County, MN': '033', 'Crow Wing County, MN': '035', 'Dakota County, MN': '037', 'Dodge County, MN': '039', 'Douglas County, MN': '041', 'Faribault County, MN': '043', 'Fillmore County, MN': '045', 'Freeborn County, MN': '047', 'Goodhue County, MN': '049', 'Grant County, MN': '051', 'Hennepin County, MN': '053', 'Houston County, MN': '055', 'Hubbard County, MN': '057', 'Isanti County, MN': '059', 'Itasca County, MN': '061', 'Jackson County, MN': '063', 'Kanabec County, MN': '065', 'Kandiyohi County, MN': '067', 'Kittson County, MN': '069', 'Koochiching County, MN': '071', 'Lac qui Parle County, MN': '073', 'Lake County, MN': '075', 'Lake of the Woods County, MN': '077', 'Le Sueur County, MN': '079', 'Lincoln County, MN': '081', 'Lyon County, MN': '083', 'Mahnomen County, MN': '087', 'Marshall County, MN': '089', 'Martin County, MN': '091', 'McLeod County, MN': '085', 'Meeker County, MN': '093', 'Mille Lacs County, MN': '095', 'Morrison County, MN': '097', 'Mower County, MN': '099', 'Murray County, MN': '101', 'Nicollet County, MN': '103', 'Nobles County, MN': '105', 'Norman County, MN': '107', 'Olmsted County, MN': '109', 'Otter Tail County, MN': '111', 'Pennington County, MN': '113', 'Pine County, MN': '115', 'Pipestone County, MN': '117', 'Polk County, MN': '119', 'Pope County, MN': '121', 'Ramsey County, MN': '123', 'Red Lake County, MN': '125', 'Redwood County, MN': '127', 'Renville County, MN': '129', 'Rice County, MN': '131', 'Rock County, MN': '133', 'Roseau County, MN': '135', 'Scott County, MN': '139', 'Sherburne County, MN': '141', 'Sibley County, MN': '143', 'St. Louis County, MN': '137', 'Stearns County, MN': '145', 'Steele County, MN': '147', 'Stevens County, MN': '149', 'Swift County, MN': '151', 'Todd County, MN': '153', 'Traverse County, MN': '155', 'Wabasha County, MN': '157', 'Wadena County, MN': '159', 'Waseca County, MN': '161', 'Washington County, MN': '163', 'Watonwan County, MN': '165', 'Wilkin County, MN': '167', 'Winona County, MN': '169', 'Wright County, MN': '171', 'Yellow Medicine County, MN': '173'}, 28: { '--All--': '%', 'Adams County, MS': '001', 'Alcorn County, MS': '003', 'Amite County, MS': '005', 'Attala County, MS': '007', 'Benton County, MS': '009', 'Bolivar County, MS': '011', 'Calhoun County, MS': '013', 'Carroll County, MS': '015', 'Chickasaw County, MS': '017', 'Choctaw County, MS': '019', 'Claiborne County, MS': '021', 'Clarke County, MS': '023', 'Clay County, MS': '025', 'Coahoma County, MS': '027', 'Copiah County, MS': '029', 'Covington County, MS': '031', 'DeSoto County, MS': '033', 'Forrest County, MS': '035', 'Franklin County, MS': '037', 'George County, MS': '039', 'Greene County, MS': '041', 'Grenada County, MS': '043', 'Hancock County, MS': '045', 'Harrison County, MS': '047', 'Hinds County, MS': '049', 'Holmes County, MS': '051', 'Humphreys County, MS': '053', 'Issaquena County, MS': '055', 'Itawamba County, MS': '057', 'Jackson County, MS': '059', 'Jasper County, MS': '061', 'Jefferson County, MS': '063', 'Jefferson Davis County, MS': '065', 'Jones County, MS': '067', 'Kemper County, MS': '069', 'Lafayette County, MS': '071', 'Lamar County, MS': '073', 'Lauderdale County, MS': '075', 'Lawrence County, MS': '077', 'Leake County, MS': '079', 'Lee County, MS': '081', 'Leflore County, MS': '083', 'Lincoln County, MS': '085', 'Lowndes County, MS': '087', 'Madison County, MS': '089', 'Marion County, MS': '091', 'Marshall County, MS': '093', 'Monroe County, MS': '095', 'Montgomery County, MS': '097', 'Neshoba County, MS': '099', 'Newton County, MS': '101', 'Noxubee County, MS': '103', 'Oktibbeha County, MS': '105', 'Panola County, MS': '107', 'Pearl River County, MS': '109', 'Perry County, MS': '111', 'Pike County, MS': '113', 'Pontotoc County, MS': '115', 'Prentiss County, MS': '117', 'Quitman County, MS': '119', 'Rankin County, MS': '121', 'Scott County, MS': '123', 'Sharkey County, MS': '125', 'Simpson County, MS': '127', 'Smith County, MS': '129', 'Stone County, MS': '131', 'Sunflower County, MS': '133', 'Tallahatchie County, MS': '135', 'Tate County, MS': '137', 'Tippah County, MS': '139', 'Tishomingo County, MS': '141', 'Tunica County, MS': '143', 'Union County, MS': '145', 'Walthall County, MS': '147', 'Warren County, MS': '149', 'Washington County, MS': '151', 'Wayne County, MS': '153', 'Webster County, MS': '155', 'Wilkinson County, MS': '157', 'Winston County, MS': '159', 'Yalobusha County, MS': '161', 'Yazoo County, MS': '163'}, 29: { '--All--': '%', 'Adair County, MO': '001', 'Andrew County, MO': '003', 'Atchison County, MO': '005', 'Audrain County, MO': '007', 'Barry County, MO': '009', 'Barton County, MO': '011', 'Bates County, MO': '013', 'Benton County, MO': '015', 'Bollinger County, MO': '017', 'Boone County, MO': '019', 'Buchanan County, MO': '021', 'Butler County, MO': '023', 'Caldwell County, MO': '025', 'Callaway County, MO': '027', 'Camden County, MO': '029', 'Cape Girardeau County, MO': '031', 'Carroll County, MO': '033', 'Carter County, MO': '035', 'Cass County, MO': '037', 'Cedar County, MO': '039', 'Chariton County, MO': '041', 'Christian County, MO': '043', 'Clark County, MO': '045', 'Clay County, MO': '047', 'Clinton County, MO': '049', 'Cole County, MO': '051', 'Cooper County, MO': '053', 'Crawford County, MO': '055', 'Dade County, MO': '057', 'Dallas County, MO': '059', 'Daviess County, MO': '061', 'DeKalb County, MO': '063', 'Dent County, MO': '065', 'Douglas County, MO': '067', 'Dunklin County, MO': '069', 'Franklin County, MO': '071', 'Gasconade County, MO': '073', 'Gentry County, MO': '075', 'Greene County, MO': '077', 'Grundy County, MO': '079', 'Harrison County, MO': '081', 'Henry County, MO': '083', 'Hickory County, MO': '085', 'Holt County, MO': '087', 'Howard County, MO': '089', 'Howell County, MO': '091', 'Iron County, MO': '093', 'Jackson County, MO': '095', 'Jasper County, MO': '097', 'Jefferson County, MO': '099', 'Johnson County, MO': '101', 'Knox County, MO': '103', 'Laclede County, MO': '105', 'Lafayette County, MO': '107', 'Lawrence County, MO': '109', 'Lewis County, MO': '111', 'Lincoln County, MO': '113', 'Linn County, MO': '115', 'Livingston County, MO': '117', 'Macon County, MO': '121', 'Madison County, MO': '123', 'Maries County, MO': '125', 'Marion County, MO': '127', 'McDonald County, MO': '119', 'Mercer County, MO': '129', 'Miller County, MO': '131', 'Mississippi County, MO': '133', 'Moniteau County, MO': '135', 'Monroe County, MO': '137', 'Montgomery County, MO': '139', 'Morgan County, MO': '141', 'New Madrid County, MO': '143', 'Newton County, MO': '145', 'Nodaway County, MO': '147', 'Oregon County, MO': '149', 'Osage County, MO': '151', 'Ozark County, MO': '153', 'Pemiscot County, MO': '155', 'Perry County, MO': '157', 'Pettis County, MO': '159', 'Phelps County, MO': '161', 'Pike County, MO': '163', 'Platte County, MO': '165', 'Polk County, MO': '167', 'Pulaski County, MO': '169', 'Putnam County, MO': '171', 'Ralls County, MO': '173', 'Randolph County, MO': '175', 'Ray County, MO': '177', 'Reynolds County, MO': '179', 'Ripley County, MO': '181', 'Saline County, MO': '195', 'Schuyler County, MO': '197', 'Scotland County, MO': '199', 'Scott County, MO': '201', 'Shannon County, MO': '203', 'Shelby County, MO': '205', 'St. Charles County, MO': '183', 'St. Clair County, MO': '185', 'St. Francois County, MO': '187', 'St. Louis County, MO': '189', 'St. Louis city, MO': '510', 'Ste. Genevieve County, MO': '186', 'Stoddard County, MO': '207', 'Stone County, MO': '209', 'Sullivan County, MO': '211', 'Taney County, MO': '213', 'Texas County, MO': '215', 'Vernon County, MO': '217', 'Warren County, MO': '219', 'Washington County, MO': '221', 'Wayne County, MO': '223', 'Webster County, MO': '225', 'Worth County, MO': '227', 'Wright County, MO': '229'}, 30: { '--All--': '%', 'Beaverhead County, MT': '001', 'Big Horn County, MT': '003', 'Blaine County, MT': '005', 'Broadwater County, MT': '007', 'Carbon County, MT': '009', 'Carter County, MT': '011', 'Cascade County, MT': '013', 'Chouteau County, MT': '015', 'Custer County, MT': '017', 'Daniels County, MT': '019', 'Dawson County, MT': '021', 'Deer Lodge County, MT': '023', 'Fallon County, MT': '025', 'Fergus County, MT': '027', 'Flathead County, MT': '029', 'Gallatin County, MT': '031', 'Garfield County, MT': '033', 'Glacier County, MT': '035', 'Golden Valley County, MT': '037', 'Granite County, MT': '039', 'Hill County, MT': '041', 'Jefferson County, MT': '043', 'Judith Basin County, MT': '045', 'Lake County, MT': '047', 'Lewis and Clark County, MT': '049', 'Liberty County, MT': '051', 'Lincoln County, MT': '053', 'Madison County, MT': '057', 'McCone County, MT': '055', 'Meagher County, MT': '059', 'Mineral County, MT': '061', 'Missoula County, MT': '063', 'Musselshell County, MT': '065', 'Park County, MT': '067', 'Petroleum County, MT': '069', 'Phillips County, MT': '071', 'Pondera County, MT': '073', 'Powder River County, MT': '075', 'Powell County, MT': '077', 'Prairie County, MT': '079', 'Ravalli County, MT': '081', 'Richland County, MT': '083', 'Roosevelt County, MT': '085', 'Rosebud County, MT': '087', 'Sanders County, MT': '089', 'Sheridan County, MT': '091', 'Silver Bow County, MT': '093', 'Stillwater County, MT': '095', 'Sweet Grass County, MT': '097', 'Teton County, MT': '099', 'Toole County, MT': '101', 'Treasure County, MT': '103', 'Valley County, MT': '105', 'Wheatland County, MT': '107', 'Wibaux County, MT': '109', 'Yellowstone County, MT': '111'}, 31: { '--All--': '%', 'Adams County, NE': '001', 'Antelope County, NE': '003', 'Arthur County, NE': '005', 'Banner County, NE': '007', 'Blaine County, NE': '009', 'Boone County, NE': '011', 'Box Butte County, NE': '013', 'Boyd County, NE': '015', 'Brown County, NE': '017', 'Buffalo County, NE': '019', 'Burt County, NE': '021', 'Butler County, NE': '023', 'Cass County, NE': '025', 'Cedar County, NE': '027', 'Chase County, NE': '029', 'Cherry County, NE': '031', 'Cheyenne County, NE': '033', 'Clay County, NE': '035', 'Colfax County, NE': '037', 'Cuming County, NE': '039', 'Custer County, NE': '041', 'Dakota County, NE': '043', 'Dawes County, NE': '045', 'Dawson County, NE': '047', 'Deuel County, NE': '049', 'Dixon County, NE': '051', 'Dodge County, NE': '053', 'Douglas County, NE': '055', 'Dundy County, NE': '057', 'Fillmore County, NE': '059', 'Franklin County, NE': '061', 'Frontier County, NE': '063', 'Furnas County, NE': '065', 'Gage County, NE': '067', 'Garden County, NE': '069', 'Garfield County, NE': '071', 'Gosper County, NE': '073', 'Grant County, NE': '075', 'Greeley County, NE': '077', 'Hall County, NE': '079', 'Hamilton County, NE': '081', 'Harlan County, NE': '083', 'Hayes County, NE': '085', 'Hitchcock County, NE': '087', 'Holt County, NE': '089', 'Hooker County, NE': '091', 'Howard County, NE': '093', 'Jefferson County, NE': '095', 'Johnson County, NE': '097', 'Kearney County, NE': '099', 'Keith County, NE': '101', 'Keya Paha County, NE': '103', 'Kimball County, NE': '105', 'Knox County, NE': '107', 'Lancaster County, NE': '109', 'Lincoln County, NE': '111', 'Logan County, NE': '113', 'Loup County, NE': '115', 'Madison County, NE': '119', 'McPherson County, NE': '117', 'Merrick County, NE': '121', 'Morrill County, NE': '123', 'Nance County, NE': '125', 'Nemaha County, NE': '127', 'Nuckolls County, NE': '129', 'Otoe County, NE': '131', 'Pawnee County, NE': '133', 'Perkins County, NE': '135', 'Phelps County, NE': '137', 'Pierce County, NE': '139', 'Platte County, NE': '141', 'Polk County, NE': '143', 'Red Willow County, NE': '145', 'Richardson County, NE': '147', 'Rock County, NE': '149', 'Saline County, NE': '151', 'Sarpy County, NE': '153', 'Saunders County, NE': '155', 'Scotts Bluff County, NE': '157', 'Seward County, NE': '159', 'Sheridan County, NE': '161', 'Sherman County, NE': '163', 'Sioux County, NE': '165', 'Stanton County, NE': '167', 'Thayer County, NE': '169', 'Thomas County, NE': '171', 'Thurston County, NE': '173', 'Valley County, NE': '175', 'Washington County, NE': '177', 'Wayne County, NE': '179', 'Webster County, NE': '181', 'Wheeler County, NE': '183', 'York County, NE': '185'}, 32: { '--All--': '%', 'Carson City, NV': '510', 'Churchill County, NV': '001', 'Clark County, NV': '003', 'Douglas County, NV': '005', 'Elko County, NV': '007', 'Esmeralda County, NV': '009', 'Eureka County, NV': '011', 'Humboldt County, NV': '013', 'Lander County, NV': '015', 'Lincoln County, NV': '017', 'Lyon County, NV': '019', 'Mineral County, NV': '021', 'Nye County, NV': '023', 'Pershing County, NV': '027', 'Storey County, NV': '029', 'Washoe County, NV': '031', 'White Pine County, NV': '033'}, 33: { '--All--': '%', 'Belknap County, NH': '001', 'Carroll County, NH': '003', 'Cheshire County, NH': '005', 'Coos County, NH': '007', 'Grafton County, NH': '009', 'Hillsborough County, NH': '011', 'Merrimack County, NH': '013', 'Rockingham County, NH': '015', 'Strafford County, NH': '017', 'Sullivan County, NH': '019'}, 34: { '--All--': '%', 'Atlantic County, NJ': '001', 'Bergen County, NJ': '003', 'Burlington County, NJ': '005', 'Camden County, NJ': '007', 'Cape May County, NJ': '009', 'Cumberland County, NJ': '011', 'Essex County, NJ': '013', 'Gloucester County, NJ': '015', 'Hudson County, NJ': '017', 'Hunterdon County, NJ': '019', 'Mercer County, NJ': '021', 'Middlesex County, NJ': '023', 'Monmouth County, NJ': '025', 'Morris County, NJ': '027', 'Ocean County, NJ': '029', 'Passaic County, NJ': '031', 'Salem County, NJ': '033', 'Somerset County, NJ': '035', 'Sussex County, NJ': '037', 'Union County, NJ': '039', 'Warren County, NJ': '041'}, 35: { '--All--': '%', 'Bernalillo County, NM': '001', 'Catron County, NM': '003', 'Chaves County, NM': '005', 'Cibola County, NM': '006', 'Colfax County, NM': '007', 'Curry County, NM': '009', 'DeBaca County, NM': '011', 'Dona Ana County, NM': '013', 'Eddy County, NM': '015', 'Grant County, NM': '017', 'Guadalupe County, NM': '019', 'Harding County, NM': '021', 'Hidalgo County, NM': '023', 'Lea County, NM': '025', 'Lincoln County, NM': '027', 'Los Alamos County, NM': '028', 'Luna County, NM': '029', 'McKinley County, NM': '031', 'Mora County, NM': '033', 'Otero County, NM': '035', 'Quay County, NM': '037', 'Rio Arriba County, NM': '039', 'Roosevelt County, NM': '041', 'San Juan County, NM': '045', 'San Miguel County, NM': '047', 'Sandoval County, NM': '043', 'Santa Fe County, NM': '049', 'Sierra County, NM': '051', 'Socorro County, NM': '053', 'Taos County, NM': '055', 'Torrance County, NM': '057', 'Union County, NM': '059', 'Valencia County, NM': '061'}, 36: { '--All--': '%', 'Albany County, NY': '001', 'Allegany County, NY': '003', 'Bronx County, NY': '005', 'Broome County, NY': '007', 'Cattaraugus County, NY': '009', 'Cayuga County, NY': '011', 'Chautauqua County, NY': '013', 'Chemung County, NY': '015', 'Chenango County, NY': '017', 'Clinton County, NY': '019', 'Columbia County, NY': '021', 'Cortland County, NY': '023', 'Delaware County, NY': '025', 'Dutchess County, NY': '027', 'Erie County, NY': '029', 'Essex County, NY': '031', 'Franklin County, NY': '033', 'Fulton County, NY': '035', 'Genesee County, NY': '037', 'Greene County, NY': '039', 'Hamilton County, NY': '041', 'Herkimer County, NY': '043', 'Jefferson County, NY': '045', 'Kings County, NY': '047', 'Lewis County, NY': '049', 'Livingston County, NY': '051', 'Madison County, NY': '053', 'Monroe County, NY': '055', 'Montgomery County, NY': '057', 'Nassau County, NY': '059', 'New York County, NY': '061', 'Niagara County, NY': '063', 'Oneida County, NY': '065', 'Onondaga County, NY': '067', 'Ontario County, NY': '069', 'Orange County, NY': '071', 'Orleans County, NY': '073', 'Oswego County, NY': '075', 'Otsego County, NY': '077', 'Putnam County, NY': '079', 'Queens County, NY': '081', 'Rensselaer County, NY': '083', 'Richmond County, NY': '085', 'Rockland County, NY': '087', 'Saratoga County, NY': '091', 'Schenectady County, NY': '093', 'Schoharie County, NY': '095', 'Schuyler County, NY': '097', 'Seneca County, NY': '099', 'St. Lawrence County, NY': '089', 'Steuben County, NY': '101', 'Suffolk County, NY': '103', 'Sullivan County, NY': '105', 'Tioga County, NY': '107', 'Tompkins County, NY': '109', 'Ulster County, NY': '111', 'Warren County, NY': '113', 'Washington County, NY': '115', 'Wayne County, NY': '117', 'Westchester County, NY': '119', 'Wyoming County, NY': '121', 'Yates County, NY': '123'}, 37: { '--All--': '%', 'Alamance County, NC': '001', 'Alexander County, NC': '003', 'Alleghany County, NC': '005', 'Anson County, NC': '007', 'Ashe County, NC': '009', 'Avery County, NC': '011', 'Beaufort County, NC': '013', 'Bertie County, NC': '015', 'Bladen County, NC': '017', 'Brunswick County, NC': '019', 'Buncombe County, NC': '021', 'Burke County, NC': '023', 'Cabarrus County, NC': '025', 'Caldwell County, NC': '027', 'Camden County, NC': '029', 'Carteret County, NC': '031', 'Caswell County, NC': '033', 'Catawba County, NC': '035', 'Chatham County, NC': '037', 'Cherokee County, NC': '039', 'Chowan County, NC': '041', 'Clay County, NC': '043', 'Cleveland County, NC': '045', 'Columbus County, NC': '047', 'Craven County, NC': '049', 'Cumberland County, NC': '051', 'Currituck County, NC': '053', 'Dare County, NC': '055', 'Davidson County, NC': '057', 'Davie County, NC': '059', 'Duplin County, NC': '061', 'Durham County, NC': '063', 'Edgecombe County, NC': '065', 'Forsyth County, NC': '067', 'Franklin County, NC': '069', 'Gaston County, NC': '071', 'Gates County, NC': '073', 'Graham County, NC': '075', 'Granville County, NC': '077', 'Greene County, NC': '079', 'Guilford County, NC': '081', 'Halifax County, NC': '083', 'Harnett County, NC': '085', 'Haywood County, NC': '087', 'Henderson County, NC': '089', 'Hertford County, NC': '091', 'Hoke County, NC': '093', 'Hyde County, NC': '095', 'Iredell County, NC': '097', 'Jackson County, NC': '099', 'Johnston County, NC': '101', 'Jones County, NC': '103', 'Lee County, NC': '105', 'Lenoir County, NC': '107', 'Lincoln County, NC': '109', 'Macon County, NC': '113', 'Madison County, NC': '115', 'Martin County, NC': '117', 'McDowell County, NC': '111', 'Mecklenburg County, NC': '119', 'Mitchell County, NC': '121', 'Montgomery County, NC': '123', 'Moore County, NC': '125', 'Nash County, NC': '127', 'New Hanover County, NC': '129', 'Northampton County, NC': '131', 'Onslow County, NC': '133', 'Orange County, NC': '135', 'Pamlico County, NC': '137', 'Pasquotank County, NC': '139', 'Pender County, NC': '141', 'Perquimans County, NC': '143', 'Person County, NC': '145', 'Pitt County, NC': '147', 'Polk County, NC': '149', 'Randolph County, NC': '151', 'Richmond County, NC': '153', 'Robeson County, NC': '155', 'Rockingham County, NC': '157', 'Rowan County, NC': '159', 'Rutherford County, NC': '161', 'Sampson County, NC': '163', 'Scotland County, NC': '165', 'Stanly County, NC': '167', 'Stokes County, NC': '169', 'Surry County, NC': '171', 'Swain County, NC': '173', 'Transylvania County, NC': '175', 'Tyrrell County, NC': '177', 'Union County, NC': '179', 'Vance County, NC': '181', 'Wake County, NC': '183', 'Warren County, NC': '185', 'Washington County, NC': '187', 'Watauga County, NC': '189', 'Wayne County, NC': '191', 'Wilkes County, NC': '193', 'Wilson County, NC': '195', 'Yadkin County, NC': '197', 'Yancey County, NC': '199'}, 38: { '--All--': '%', 'Adams County, ND': '001', 'Barnes County, ND': '003', 'Benson County, ND': '005', 'Billings County, ND': '007', 'Bottineau County, ND': '009', 'Bowman County, ND': '011', 'Burke County, ND': '013', 'Burleigh County, ND': '015', 'Cass County, ND': '017', 'Cavalier County, ND': '019', 'Dickey County, ND': '021', 'Divide County, ND': '023', 'Dunn County, ND': '025', 'Eddy County, ND': '027', 'Emmons County, ND': '029', 'Foster County, ND': '031', 'Golden Valley County, ND': '033', 'Grand Forks County, ND': '035', 'Grant County, ND': '037', 'Griggs County, ND': '039', 'Hettinger County, ND': '041', 'Kidder County, ND': '043', 'LaMoure County, ND': '045', 'Logan County, ND': '047', 'McHenry County, ND': '049', 'McIntosh County, ND': '051', 'McKenzie County, ND': '053', 'McLean County, ND': '055', 'Mercer County, ND': '057', 'Morton County, ND': '059', 'Mountrail County, ND': '061', 'Nelson County, ND': '063', 'Oliver County, ND': '065', 'Pembina County, ND': '067', 'Pierce County, ND': '069', 'Ramsey County, ND': '071', 'Ransom County, ND': '073', 'Renville County, ND': '075', 'Richland County, ND': '077', 'Rolette County, ND': '079', 'Sargent County, ND': '081', 'Sheridan County, ND': '083', 'Sioux County, ND': '085', 'Slope County, ND': '087', 'Stark County, ND': '089', 'Steele County, ND': '091', 'Stutsman County, ND': '093', 'Towner County, ND': '095', 'Traill County, ND': '097', 'Walsh County, ND': '099', 'Ward County, ND': '101', 'Wells County, ND': '103', 'Williams County, ND': '105'}, 39: { '--All--': '%', 'Adams County, OH': '001', 'Allen County, OH': '003', 'Ashland County, OH': '005', 'Ashtabula County, OH': '007', 'Athens County, OH': '009', 'Auglaize County, OH': '011', 'Belmont County, OH': '013', 'Brown County, OH': '015', 'Butler County, OH': '017', 'Carroll County, OH': '019', 'Champaign County, OH': '021', 'Clark County, OH': '023', 'Clermont County, OH': '025', 'Clinton County, OH': '027', 'Columbiana County, OH': '029', 'Coshocton County, OH': '031', 'Crawford County, OH': '033', 'Cuyahoga County, OH': '035', 'Darke County, OH': '037', 'Defiance County, OH': '039', 'Delaware County, OH': '041', 'Erie County, OH': '043', 'Fairfield County, OH': '045', 'Fayette County, OH': '047', 'Franklin County, OH': '049', 'Fulton County, OH': '051', 'Gallia County, OH': '053', 'Geauga County, OH': '055', 'Greene County, OH': '057', 'Guernsey County, OH': '059', 'Hamilton County, OH': '061', 'Hancock County, OH': '063', 'Hardin County, OH': '065', 'Harrison County, OH': '067', 'Henry County, OH': '069', 'Highland County, OH': '071', 'Hocking County, OH': '073', 'Holmes County, OH': '075', 'Huron County, OH': '077', 'Jackson County, OH': '079', 'Jefferson County, OH': '081', 'Knox County, OH': '083', 'Lake County, OH': '085', 'Lawrence County, OH': '087', 'Licking County, OH': '089', 'Logan County, OH': '091', 'Lorain County, OH': '093', 'Lucas County, OH': '095', 'Madison County, OH': '097', 'Mahoning County, OH': '099', 'Marion County, OH': '101', 'Medina County, OH': '103', 'Meigs County, OH': '105', 'Mercer County, OH': '107', 'Miami County, OH': '109', 'Monroe County, OH': '111', 'Montgomery County, OH': '113', 'Morgan County, OH': '115', 'Morrow County, OH': '117', 'Muskingum County, OH': '119', 'Noble County, OH': '121', 'Ottawa County, OH': '123', 'Paulding County, OH': '125', 'Perry County, OH': '127', 'Pickaway County, OH': '129', 'Pike County, OH': '131', 'Portage County, OH': '133', 'Preble County, OH': '135', 'Putnam County, OH': '137', 'Richland County, OH': '139', 'Ross County, OH': '141', 'Sandusky County, OH': '143', 'Scioto County, OH': '145', 'Seneca County, OH': '147', 'Shelby County, OH': '149', 'Stark County, OH': '151', 'Summit County, OH': '153', 'Trumbull County, OH': '155', 'Tuscarawas County, OH': '157', 'Union County, OH': '159', 'Van Wert County, OH': '161', 'Vinton County, OH': '163', 'Warren County, OH': '165', 'Washington County, OH': '167', 'Wayne County, OH': '169', 'Williams County, OH': '171', 'Wood County, OH': '173', 'Wyandot County, OH': '175'}, 40: { '--All--': '%', 'Adair County, OK': '001', 'Alfalfa County, OK': '003', 'Atoka County, OK': '005', 'Beaver County, OK': '007', 'Beckham County, OK': '009', 'Blaine County, OK': '011', 'Bryan County, OK': '013', 'Caddo County, OK': '015', 'Canadian County, OK': '017', 'Carter County, OK': '019', 'Cherokee County, OK': '021', 'Choctaw County, OK': '023', 'Cimarron County, OK': '025', 'Cleveland County, OK': '027', 'Coal County, OK': '029', 'Comanche County, OK': '031', 'Cotton County, OK': '033', 'Craig County, OK': '035', 'Creek County, OK': '037', 'Custer County, OK': '039', 'Delaware County, OK': '041', 'Dewey County, OK': '043', 'Ellis County, OK': '045', 'Garfield County, OK': '047', 'Garvin County, OK': '049', 'Grady County, OK': '051', 'Grant County, OK': '053', 'Greer County, OK': '055', 'Harmon County, OK': '057', 'Harper County, OK': '059', 'Haskell County, OK': '061', 'Hughes County, OK': '063', 'Jackson County, OK': '065', 'Jefferson County, OK': '067', 'Johnston County, OK': '069', 'Kay County, OK': '071', 'Kingfisher County, OK': '073', 'Kiowa County, OK': '075', 'Latimer County, OK': '077', 'Le Flore County, OK': '079', 'Lincoln County, OK': '081', 'Logan County, OK': '083', 'Love County, OK': '085', 'Major County, OK': '093', 'Marshall County, OK': '095', 'Mayes County, OK': '097', 'McClain County, OK': '087', 'McCurtain County, OK': '089', 'McIntosh County, OK': '091', 'Murray County, OK': '099', 'Muskogee County, OK': '101', 'Noble County, OK': '103', 'Nowata County, OK': '105', 'Okfuskee County, OK': '107', 'Oklahoma County, OK': '109', 'Okmulgee County, OK': '111', 'Osage County, OK': '113', 'Ottawa County, OK': '115', 'Pawnee County, OK': '117', 'Payne County, OK': '119', 'Pittsburg County, OK': '121', 'Pontotoc County, OK': '123', 'Pottawatomie County, OK': '125', 'Pushmataha County, OK': '127', 'Roger Mills County, OK': '129', 'Rogers County, OK': '131', 'Seminole County, OK': '133', 'Sequoyah County, OK': '135', 'Stephens County, OK': '137', 'Texas County, OK': '139', 'Tillman County, OK': '141', 'Tulsa County, OK': '143', 'Wagoner County, OK': '145', 'Washington County, OK': '147', 'Washita County, OK': '149', 'Woods County, OK': '151', 'Woodward County, OK': '153'}, 41: { '--All--': '%', 'Baker County, OR': '001', 'Benton County, OR': '003', 'Clackamas County, OR': '005', 'Clatsop County, OR': '007', 'Columbia County, OR': '009', 'Coos County, OR': '011', 'Crook County, OR': '013', 'Curry County, OR': '015', 'Deschutes County, OR': '017', 'Douglas County, OR': '019', 'Gilliam County, OR': '021', 'Grant County, OR': '023', 'Harney County, OR': '025', 'Hood River County, OR': '027', 'Jackson County, OR': '029', 'Jefferson County, OR': '031', 'Josephine County, OR': '033', 'Klamath County, OR': '035', 'Lake County, OR': '037', 'Lane County, OR': '039', 'Lincoln County, OR': '041', 'Linn County, OR': '043', 'Malheur County, OR': '045', 'Marion County, OR': '047', 'Morrow County, OR': '049', 'Multnomah County, OR': '051', 'Polk County, OR': '053', 'Sherman County, OR': '055', 'Tillamook County, OR': '057', 'Umatilla County, OR': '059', 'Union County, OR': '061', 'Wallowa County, OR': '063', 'Wasco County, OR': '065', 'Washington County, OR': '067', 'Wheeler County, OR': '069', 'Yamhill County, OR': '071'}, 42: { '--All--': '%', 'Adams County, PA': '001', 'Allegheny County, PA': '003', 'Armstrong County, PA': '005', 'Beaver County, PA': '007', 'Bedford County, PA': '009', 'Berks County, PA': '011', 'Blair County, PA': '013', 'Bradford County, PA': '015', 'Bucks County, PA': '017', 'Butler County, PA': '019', 'Cambria County, PA': '021', 'Cameron County, PA': '023', 'Carbon County, PA': '025', 'Centre County, PA': '027', 'Chester County, PA': '029', 'Clarion County, PA': '031', 'Clearfield County, PA': '033', 'Clinton County, PA': '035', 'Columbia County, PA': '037', 'Crawford County, PA': '039', 'Cumberland County, PA': '041', 'Dauphin County, PA': '043', 'Delaware County, PA': '045', 'Elk County, PA': '047', 'Erie County, PA': '049', 'Fayette County, PA': '051', 'Forest County, PA': '053', 'Franklin County, PA': '055', 'Fulton County, PA': '057', 'Greene County, PA': '059', 'Huntingdon County, PA': '061', 'Indiana County, PA': '063', 'Jefferson County, PA': '065', 'Juniata County, PA': '067', 'Lackawanna County, PA': '069', 'Lancaster County, PA': '071', 'Lawrence County, PA': '073', 'Lebanon County, PA': '075', 'Lehigh County, PA': '077', 'Luzerne County, PA': '079', 'Lycoming County, PA': '081', 'McKean County, PA': '083', 'Mercer County, PA': '085', 'Mifflin County, PA': '087', 'Monroe County, PA': '089', 'Montgomery County, PA': '091', 'Montour County, PA': '093', 'Northampton County, PA': '095', 'Northumberland County, PA': '097', 'Perry County, PA': '099', 'Philadelphia County/city, PA': '101', 'Pike County, PA': '103', 'Potter County, PA': '105', 'Schuylkill County, PA': '107', 'Snyder County, PA': '109', 'Somerset County, PA': '111', 'Sullivan County, PA': '113', 'Susquehanna County, PA': '115', 'Tioga County, PA': '117', 'Union County, PA': '119', 'Venango County, PA': '121', 'Warren County, PA': '123', 'Washington County, PA': '125', 'Wayne County, PA': '127', 'Westmoreland County, PA': '129', 'Wyoming County, PA': '131', 'York County, PA': '133'}, 44: { '--All--': '%', 'Bristol County, RI': '001', 'Kent County, RI': '003', 'Newport County, RI': '005', 'Providence County, RI': '007', 'Washington County, RI': '009'}, 45: { '--All--': '%', 'Abbeville County, SC': '001', 'Aiken County, SC': '003', 'Allendale County, SC': '005', 'Anderson County, SC': '007', 'Bamberg County, SC': '009', 'Barnwell County, SC': '011', 'Beaufort County, SC': '013', 'Berkeley County, SC': '015', 'Calhoun County, SC': '017', 'Charleston County, SC': '019', 'Cherokee County, SC': '021', 'Chester County, SC': '023', 'Chesterfield County, SC': '025', 'Clarendon County, SC': '027', 'Colleton County, SC': '029', 'Darlington County, SC': '031', 'Dillon County, SC': '033', 'Dorchester County, SC': '035', 'Edgefield County, SC': '037', 'Fairfield County, SC': '039', 'Florence County, SC': '041', 'Georgetown County, SC': '043', 'Greenville County, SC': '045', 'Greenwood County, SC': '047', 'Hampton County, SC': '049', 'Horry County, SC': '051', 'Jasper County, SC': '053', 'Kershaw County, SC': '055', 'Lancaster County, SC': '057', 'Laurens County, SC': '059', 'Lee County, SC': '061', 'Lexington County, SC': '063', 'Marion County, SC': '067', 'Marlboro County, SC': '069', 'McCormick County, SC': '065', 'Newberry County, SC': '071', 'Oconee County, SC': '073', 'Orangeburg County, SC': '075', 'Pickens County, SC': '077', 'Richland County, SC': '079', 'Saluda County, SC': '081', 'Spartanburg County, SC': '083', 'Sumter County, SC': '085', 'Union County, SC': '087', 'Williamsburg County, SC': '089', 'York County, SC': '091'}, 46: { '--All--': '%', 'Aurora County, SD': '003', 'Beadle County, SD': '005', 'Bennett County, SD': '007', 'Bon Homme County, SD': '009', 'Brookings County, SD': '011', 'Brown County, SD': '013', 'Brule County, SD': '015', 'Buffalo County, SD': '017', 'Butte County, SD': '019', 'Campbell County, SD': '021', 'Charles Mix County, SD': '023', 'Clark County, SD': '025', 'Clay County, SD': '027', 'Codington County, SD': '029', 'Corson County, SD': '031', 'Custer County, SD': '033', 'Davison County, SD': '035', 'Day County, SD': '037', 'Deuel County, SD': '039', 'Dewey County, SD': '041', 'Douglas County, SD': '043', 'Edmunds County, SD': '045', 'Fall River County, SD': '047', 'Faulk County, SD': '049', 'Grant County, SD': '051', 'Gregory County, SD': '053', 'Haakon County, SD': '055', 'Hamlin County, SD': '057', 'Hand County, SD': '059', 'Hanson County, SD': '061', 'Harding County, SD': '063', 'Hughes County, SD': '065', 'Hutchinson County, SD': '067', 'Hyde County, SD': '069', 'Jackson County, SD': '071', 'Jerauld County, SD': '073', 'Jones County, SD': '075', 'Kingsbury County, SD': '077', 'Lake County, SD': '079', 'Lawrence County, SD': '081', 'Lincoln County, SD': '083', 'Lyman County, SD': '085', 'Marshall County, SD': '091', 'McCook County, SD': '087', 'McPherson County, SD': '089', 'Meade County, SD': '093', 'Mellette County, SD': '095', 'Miner County, SD': '097', 'Minnehaha County, SD': '099', 'Moody County, SD': '101', 'Pennington County, SD': '103', 'Perkins County, SD': '105', 'Potter County, SD': '107', 'Roberts County, SD': '109', 'Sanborn County, SD': '111', 'Shannon County, SD': '113', 'Spink County, SD': '115', 'Stanley County, SD': '117', 'Sully County, SD': '119', 'Todd County, SD': '121', 'Tripp County, SD': '123', 'Turner County, SD': '125', 'Union County, SD': '127', 'Walworth County, SD': '129', 'Yankton County, SD': '135', 'Ziebach County, SD': '137'}, 47: { '--All--': '%', 'Anderson County, TN': '001', 'Bedford County, TN': '003', 'Benton County, TN': '005', 'Bledsoe County, TN': '007', 'Blount County, TN': '009', 'Bradley County, TN': '011', 'Campbell County, TN': '013', 'Cannon County, TN': '015', 'Carroll County, TN': '017', 'Carter County, TN': '019', 'Cheatham County, TN': '021', 'Chester County, TN': '023', 'Claiborne County, TN': '025', 'Clay County, TN': '027', 'Cocke County, TN': '029', 'Coffee County, TN': '031', 'Crockett County, TN': '033', 'Cumberland County, TN': '035', 'Davidson County, TN': '037', 'DeKalb County, TN': '041', 'Decatur County, TN': '039', 'Dickson County, TN': '043', 'Dyer County, TN': '045', 'Fayette County, TN': '047', 'Fentress County, TN': '049', 'Franklin County, TN': '051', 'Gibson County, TN': '053', 'Giles County, TN': '055', 'Grainger County, TN': '057', 'Greene County, TN': '059', 'Grundy County, TN': '061', 'Hamblen County, TN': '063', 'Hamilton County, TN': '065', 'Hancock County, TN': '067', 'Hardeman County, TN': '069', 'Hardin County, TN': '071', 'Hawkins County, TN': '073', 'Haywood County, TN': '075', 'Henderson County, TN': '077', 'Henry County, TN': '079', 'Hickman County, TN': '081', 'Houston County, TN': '083', 'Humphreys County, TN': '085', 'Jackson County, TN': '087', 'Jefferson County, TN': '089', 'Johnson County, TN': '091', 'Knox County, TN': '093', 'Lake County, TN': '095', 'Lauderdale County, TN': '097', 'Lawrence County, TN': '099', 'Lewis County, TN': '101', 'Lincoln County, TN': '103', 'Loudon County, TN': '105', 'Macon County, TN': '111', 'Madison County, TN': '113', 'Marion County, TN': '115', 'Marshall County, TN': '117', 'Maury County, TN': '119', 'McMinn County, TN': '107', 'McNairy County, TN': '109', 'Meigs County, TN': '121', 'Monroe County, TN': '123', 'Montgomery County, TN': '125', 'Moore County, TN': '127', 'Morgan County, TN': '129', 'Obion County, TN': '131', 'Overton County, TN': '133', 'Perry County, TN': '135', 'Pickett County, TN': '137', 'Polk County, TN': '139', 'Putnam County, TN': '141', 'Rhea County, TN': '143', 'Roane County, TN': '145', 'Robertson County, TN': '147', 'Rutherford County, TN': '149', 'Scott County, TN': '151', 'Sequatchie County, TN': '153', 'Sevier County, TN': '155', 'Shelby County, TN': '157', 'Smith County, TN': '159', 'Stewart County, TN': '161', 'Sullivan County, TN': '163', 'Sumner County, TN': '165', 'Tipton County, TN': '167', 'Trousdale County, TN': '169', 'Unicoi County, TN': '171', 'Union County, TN': '173', 'Van Buren County, TN': '175', 'Warren County, TN': '177', 'Washington County, TN': '179', 'Wayne County, TN': '181', 'Weakley County, TN': '183', 'White County, TN': '185', 'Williamson County, TN': '187', 'Wilson County, TN': '189'}, 48: { '--All--': '%', 'Anderson County, TX': '001', 'Andrews County, TX': '003', 'Angelina County, TX': '005', 'Aransas County, TX': '007', 'Archer County, TX': '009', 'Armstrong County, TX': '011', 'Atascosa County, TX': '013', 'Austin County, TX': '015', 'Bailey County, TX': '017', 'Bandera County, TX': '019', 'Bastrop County, TX': '021', 'Baylor County, TX': '023', 'Bee County, TX': '025', 'Bell County, TX': '027', 'Bexar County, TX': '029', 'Blanco County, TX': '031', 'Borden County, TX': '033', 'Bosque County, TX': '035', 'Bowie County, TX': '037', 'Brazoria County, TX': '039', 'Brazos County, TX': '041', 'Brewster County, TX': '043', 'Briscoe County, TX': '045', 'Brooks County, TX': '047', 'Brown County, TX': '049', 'Burleson County, TX': '051', 'Burnet County, TX': '053', 'Caldwell County, TX': '055', 'Calhoun County, TX': '057', 'Callahan County, TX': '059', 'Cameron County, TX': '061', 'Camp County, TX': '063', 'Carson County, TX': '065', 'Cass County, TX': '067', 'Castro County, TX': '069', 'Chambers County, TX': '071', 'Cherokee County, TX': '073', 'Childress County, TX': '075', 'Clay County, TX': '077', 'Cochran County, TX': '079', 'Coke County, TX': '081', 'Coleman County, TX': '083', 'Collin County, TX': '085', 'Collingsworth County, TX': '087', 'Colorado County, TX': '089', 'Comal County, TX': '091', 'Comanche County, TX': '093', 'Concho County, TX': '095', 'Cooke County, TX': '097', 'Coryell County, TX': '099', 'Cottle County, TX': '101', 'Crane County, TX': '103', 'Crockett County, TX': '105', 'Crosby County, TX': '107', 'Culberson County, TX': '109', 'Dallam County, TX': '111', 'Dallas County, TX': '113', 'Dawson County, TX': '115', 'DeWitt County, TX': '123', 'Deaf Smith County, TX': '117', 'Delta County, TX': '119', 'Denton County, TX': '121', 'Dickens County, TX': '125', 'Dimmit County, TX': '127', 'Donley County, TX': '129', 'Duval County, TX': '131', 'Eastland County, TX': '133', 'Ector County, TX': '135', 'Edwards County, TX': '137', 'El Paso County, TX': '141', 'Ellis County, TX': '139', 'Erath County, TX': '143', 'Falls County, TX': '145', 'Fannin County, TX': '147', 'Fayette County, TX': '149', 'Fisher County, TX': '151', 'Floyd County, TX': '153', 'Foard County, TX': '155', 'Fort Bend County, TX': '157', 'Franklin County, TX': '159', 'Freestone County, TX': '161', 'Frio County, TX': '163', 'Gaines County, TX': '165', 'Galveston County, TX': '167', 'Garza County, TX': '169', 'Gillespie County, TX': '171', 'Glasscock County, TX': '173', 'Goliad County, TX': '175', 'Gonzales County, TX': '177', 'Gray County, TX': '179', 'Grayson County, TX': '181', 'Gregg County, TX': '183', 'Grimes County, TX': '185', 'Guadalupe County, TX': '187', 'Hale County, TX': '189', 'Hall County, TX': '191', 'Hamilton County, TX': '193', 'Hansford County, TX': '195', 'Hardeman County, TX': '197', 'Hardin County, TX': '199', 'Harris County, TX': '201', 'Harrison County, TX': '203', 'Hartley County, TX': '205', 'Haskell County, TX': '207', 'Hays County, TX': '209', 'Hemphill County, TX': '211', 'Henderson County, TX': '213', 'Hidalgo County, TX': '215', 'Hill County, TX': '217', 'Hockley County, TX': '219', 'Hood County, TX': '221', 'Hopkins County, TX': '223', 'Houston County, TX': '225', 'Howard County, TX': '227', 'Hudspeth County, TX': '229', 'Hunt County, TX': '231', 'Hutchinson County, TX': '233', 'Irion County, TX': '235', 'Jack County, TX': '237', 'Jackson County, TX': '239', 'Jasper County, TX': '241', 'Jeff Davis County, TX': '243', 'Jefferson County, TX': '245', 'Jim Hogg County, TX': '247', 'Jim Wells County, TX': '249', 'Johnson County, TX': '251', 'Jones County, TX': '253', 'Karnes County, TX': '255', 'Kaufman County, TX': '257', 'Kendall County, TX': '259', 'Kenedy County, TX': '261', 'Kent County, TX': '263', 'Kerr County, TX': '265', 'Kimble County, TX': '267', 'King County, TX': '269', 'Kinney County, TX': '271', 'Kleberg County, TX': '273', 'Knox County, TX': '275', 'La Salle County, TX': '283', 'Lamar County, TX': '277', 'Lamb County, TX': '279', 'Lampasas County, TX': '281', 'Lavaca County, TX': '285', 'Lee County, TX': '287', 'Leon County, TX': '289', 'Liberty County, TX': '291', 'Limestone County, TX': '293', 'Lipscomb County, TX': '295', 'Live Oak County, TX': '297', 'Llano County, TX': '299', 'Loving County, TX': '301', 'Lubbock County, TX': '303', 'Lynn County, TX': '305', 'Madison County, TX': '313', 'Marion County, TX': '315', 'Martin County, TX': '317', 'Mason County, TX': '319', 'Matagorda County, TX': '321', 'Maverick County, TX': '323', 'McCulloch County, TX': '307', 'McLennan County, TX': '309', 'McMullen County, TX': '311', 'Medina County, TX': '325', 'Menard County, TX': '327', 'Midland County, TX': '329', 'Milam County, TX': '331', 'Mills County, TX': '333', 'Mitchell County, TX': '335', 'Montague County, TX': '337', 'Montgomery County, TX': '339', 'Moore County, TX': '341', 'Morris County, TX': '343', 'Motley County, TX': '345', 'Nacogdoches County, TX': '347', 'Navarro County, TX': '349', 'Newton County, TX': '351', 'Nolan County, TX': '353', 'Nueces County, TX': '355', 'Ochiltree County, TX': '357', 'Oldham County, TX': '359', 'Orange County, TX': '361', 'Palo Pinto County, TX': '363', 'Panola County, TX': '365', 'Parker County, TX': '367', 'Parmer County, TX': '369', 'Pecos County, TX': '371', 'Polk County, TX': '373', 'Potter County, TX': '375', 'Presidio County, TX': '377', 'Rains County, TX': '379', 'Randall County, TX': '381', 'Reagan County, TX': '383', 'Real County, TX': '385', 'Red River County, TX': '387', 'Reeves County, TX': '389', 'Refugio County, TX': '391', 'Roberts County, TX': '393', 'Robertson County, TX': '395', 'Rockwall County, TX': '397', 'Runnels County, TX': '399', 'Rusk County, TX': '401', 'Sabine County, TX': '403', 'San Augustine County, TX': '405', 'San Jacinto County, TX': '407', 'San Patricio County, TX': '409', 'San Saba County, TX': '411', 'Schleicher County, TX': '413', 'Scurry County, TX': '415', 'Shackelford County, TX': '417', 'Shelby County, TX': '419', 'Sherman County, TX': '421', 'Smith County, TX': '423', 'Somervell County, TX': '425', 'Starr County, TX': '427', 'Stephens County, TX': '429', 'Sterling County, TX': '431', 'Stonewall County, TX': '433', 'Sutton County, TX': '435', 'Swisher County, TX': '437', 'Tarrant County, TX': '439', 'Taylor County, TX': '441', 'Terrell County, TX': '443', 'Terry County, TX': '445', 'Throckmorton County, TX': '447', 'Titus County, TX': '449', 'Tom Green County, TX': '451', 'Travis County, TX': '453', 'Trinity County, TX': '455', 'Tyler County, TX': '457', 'Upshur County, TX': '459', 'Upton County, TX': '461', 'Uvalde County, TX': '463', 'Val Verde County, TX': '465', 'Van Zandt County, TX': '467', 'Victoria County, TX': '469', 'Walker County, TX': '471', 'Waller County, TX': '473', 'Ward County, TX': '475', 'Washington County, TX': '477', 'Webb County, TX': '479', 'Wharton County, TX': '481', 'Wheeler County, TX': '483', 'Wichita County, TX': '485', 'Wilbarger County, TX': '487', 'Willacy County, TX': '489', 'Williamson County, TX': '491', 'Wilson County, TX': '493', 'Winkler County, TX': '495', 'Wise County, TX': '497', 'Wood County, TX': '499', 'Yoakum County, TX': '501', 'Young County, TX': '503', 'Zapata County, TX': '505', 'Zavala County, TX': '507'}, 49: { '--All--': '%', 'Beaver County, UT': '001', 'Box Elder County, UT': '003', 'Cache County, UT': '005', 'Carbon County, UT': '007', 'Daggett County, UT': '009', 'Davis County, UT': '011', 'Duchesne County, UT': '013', 'Emery County, UT': '015', 'Garfield County, UT': '017', 'Grand County, UT': '019', 'Iron County, UT': '021', 'Juab County, UT': '023', 'Kane County, UT': '025', 'Millard County, UT': '027', 'Morgan County, UT': '029', 'Piute County, UT': '031', 'Rich County, UT': '033', 'Salt Lake County, UT': '035', 'San Juan County, UT': '037', 'Sanpete County, UT': '039', 'Sevier County, UT': '041', 'Summit County, UT': '043', 'Tooele County, UT': '045', 'Uintah County, UT': '047', 'Utah County, UT': '049', 'Wasatch County, UT': '051', 'Washington County, UT': '053', 'Wayne County, UT': '055', 'Weber County, UT': '057'}, 50: { '--All--': '%', 'Addison County, VT': '001', 'Bennington County, VT': '003', 'Caledonia County, VT': '005', 'Chittenden County, VT': '007', 'Essex County, VT': '009', 'Franklin County, VT': '011', 'Grand Isle County, VT': '013', 'Lamoille County, VT': '015', 'Orange County, VT': '017', 'Orleans County, VT': '019', 'Rutland County, VT': '021', 'Washington County, VT': '023', 'Windham County, VT': '025', 'Windsor County, VT': '027'}, 51: { '--All--': '%', 'Accomack County, VA': '001', 'Albemarle County, VA': '003', 'Alexandria city, VA': '510', 'Alleghany County, VA': '005', 'Amelia County, VA': '007', 'Amherst County, VA': '009', 'Appomattox County, VA': '011', 'Arlington County, VA': '013', 'Augusta County, VA': '015', 'Bath County, VA': '017', 'Bedford County, VA': '019', 'Bedford city, VA': '515', 'Bland County, VA': '021', 'Botetourt County, VA': '023', 'Bristol city, VA': '520', 'Brunswick County, VA': '025', 'Buchanan County, VA': '027', 'Buckingham County, VA': '029', 'Buena Vista city, VA': '530', 'Campbell County, VA': '031', 'Caroline County, VA': '033', 'Carroll County, VA': '035', 'Charles City County, VA': '036', 'Charlotte County, VA': '037', 'Charlottesville city, VA': '540', 'Chesapeake city, VA': '550', 'Chesterfield County, VA': '041', 'Clarke County, VA': '043', 'Colonial Heights city, VA': '570', 'Covington city, VA': '580', 'Craig County, VA': '045', 'Culpeper County, VA': '047', 'Cumberland County, VA': '049', 'Danville city, VA': '590', 'Dickenson County, VA': '051', 'Dinwiddie County, VA': '053', 'Emporia city, VA': '595', 'Essex County, VA': '057', 'Fairfax County, VA': '059', 'Fairfax city, VA': '600', 'Falls Church city, VA': '610', 'Fauquier County, VA': '061', 'Floyd County, VA': '063', 'Fluvanna County, VA': '065', 'Franklin County, VA': '067', 'Franklin city, VA': '620', 'Frederick County, VA': '069', 'Fredericksburg city, VA': '630', 'Galax city, VA': '640', 'Giles County, VA': '071', 'Gloucester County, VA': '073', 'Goochland County, VA': '075', 'Grayson County, VA': '077', 'Greene County, VA': '079', 'Greensville County, VA': '081', 'Halifax County, VA': '083', 'Hampton city, VA': '650', 'Hanover County, VA': '085', 'Harrisonburg city, VA': '660', 'Henrico County, VA': '087', 'Henry County, VA': '089', 'Highland County, VA': '091', 'Hopewell city, VA': '670', 'Isle of Wight County, VA': '093', 'James City County, VA': '095', 'King George County, VA': '099', 'King William County, VA': '101', 'King and Queen County, VA': '097', 'Lancaster County, VA': '103', 'Lee County, VA': '105', 'Lexington city, VA': '678', 'Loudoun County, VA': '107', 'Louisa County, VA': '109', 'Lunenburg County, VA': '111', 'Lynchburg city, VA': '680', 'Madison County, VA': '113', 'Manassas Park city, VA': '685', 'Manassas city, VA': '683', 'Martinsville city, VA': '690', 'Mathews County, VA': '115', 'Mecklenburg County, VA': '117', 'Middlesex County, VA': '119', 'Montgomery County, VA': '121', 'Nelson County, VA': '125', 'New Kent County, VA': '127', 'Newport News city, VA': '700', 'Norfolk city, VA': '710', 'Northampton County, VA': '131', 'Northumberland County, VA': '133', 'Norton city, VA': '720', 'Nottoway County, VA': '135', 'Orange County, VA': '137', 'Page County, VA': '139', 'Patrick County, VA': '141', 'Petersburg city, VA': '730', 'Pittsylvania County, VA': '143', 'Poquoson city, VA': '735', 'Portsmouth city, VA': '740', 'Powhatan County, VA': '145', 'Prince Edward County, VA': '147', 'Prince George County, VA': '149', 'Prince William County, VA': '153', 'Pulaski County, VA': '155', 'Radford city, VA': '750', 'Rappahannock County, VA': '157', 'Richmond County, VA': '159', 'Richmond city, VA': '760', 'Roanoke County, VA': '161', 'Roanoke city, VA': '770', 'Rockbridge County, VA': '163', 'Rockingham County, VA': '165', 'Russell County, VA': '167', 'Salem city, VA': '775', 'Scott County, VA': '169', 'Shenandoah County, VA': '171', 'Smyth County, VA': '173', 'Southampton County, VA': '175', 'Spotsylvania County, VA': '177', 'Stafford County, VA': '179', 'Staunton city, VA': '790', 'Suffolk city, VA': '800', 'Surry County, VA': '181', 'Sussex County, VA': '183', 'Tazewell County, VA': '185', 'Virginia Beach city, VA': '810', 'Warren County, VA': '187', 'Washington County, VA': '191', 'Waynesboro city, VA': '820', 'Westmoreland County, VA': '193', 'Williamsburg city, VA': '830', 'Winchester city, VA': '840', 'Wise County, VA': '195', 'Wythe County, VA': '197', 'York County, VA': '199'}, 53: { '--All--': '%', 'Adams County, WA': '001', 'Asotin County, WA': '003', 'Benton County, WA': '005', 'Chelan County, WA': '007', 'Clallam County, WA': '009', 'Clark County, WA': '011', 'Columbia County, WA': '013', 'Cowlitz County, WA': '015', 'Douglas County, WA': '017', 'Ferry County, WA': '019', 'Franklin County, WA': '021', 'Garfield County, WA': '023', 'Grant County, WA': '025', 'Grays Harbor County, WA': '027', 'Island County, WA': '029', 'Jefferson County, WA': '031', 'King County, WA': '033', 'Kitsap County, WA': '035', 'Kittitas County, WA': '037', 'Klickitat County, WA': '039', 'Lewis County, WA': '041', 'Lincoln County, WA': '043', 'Mason County, WA': '045', 'Okanogan County, WA': '047', 'Pacific County, WA': '049', 'Pend Oreille County, WA': '051', 'Pierce County, WA': '053', 'San Juan County, WA': '055', 'Skagit County, WA': '057', 'Skamania County, WA': '059', 'Snohomish County, WA': '061', 'Spokane County, WA': '063', 'Stevens County, WA': '065', 'Thurston County, WA': '067', 'Wahkiakum County, WA': '069', 'Walla Walla County, WA': '071', 'Whatcom County, WA': '073', 'Whitman County, WA': '075', 'Yakima County, WA': '077'}, 54: { '--All--': '%', 'Barbour County, WV': '001', 'Berkeley County, WV': '003', 'Boone County, WV': '005', 'Braxton County, WV': '007', 'Brooke County, WV': '009', 'Cabell County, WV': '011', 'Calhoun County, WV': '013', 'Clay County, WV': '015', 'Doddridge County, WV': '017', 'Fayette County, WV': '019', 'Gilmer County, WV': '021', 'Grant County, WV': '023', 'Greenbrier County, WV': '025', 'Hampshire County, WV': '027', 'Hancock County, WV': '029', 'Hardy County, WV': '031', 'Harrison County, WV': '033', 'Jackson County, WV': '035', 'Jefferson County, WV': '037', 'Kanawha County, WV': '039', 'Lewis County, WV': '041', 'Lincoln County, WV': '043', 'Logan County, WV': '045', 'Marion County, WV': '049', 'Marshall County, WV': '051', 'Mason County, WV': '053', 'McDowell County, WV': '047', 'Mercer County, WV': '055', 'Mineral County, WV': '057', 'Mingo County, WV': '059', 'Monongalia County, WV': '061', 'Monroe County, WV': '063', 'Morgan County, WV': '065', 'Nicholas County, WV': '067', 'Ohio County, WV': '069', 'Pendleton County, WV': '071', 'Pleasants County, WV': '073', 'Pocahontas County, WV': '075', 'Preston County, WV': '077', 'Putnam County, WV': '079', 'Raleigh County, WV': '081', 'Randolph County, WV': '083', 'Ritchie County, WV': '085', 'Roane County, WV': '087', 'Summers County, WV': '089', 'Taylor County, WV': '091', 'Tucker County, WV': '093', 'Tyler County, WV': '095', 'Upshur County, WV': '097', 'Wayne County, WV': '099', 'Webster County, WV': '101', 'Wetzel County, WV': '103', 'Wirt County, WV': '105', 'Wood County, WV': '107', 'Wyoming County, WV': '109'}, 55: { '--All--': '%', 'Adams County, WI': '001', 'Ashland County, WI': '003', 'Barron County, WI': '005', 'Bayfield County, WI': '007', 'Brown County, WI': '009', 'Buffalo County, WI': '011', 'Burnett County, WI': '013', 'Calumet County, WI': '015', 'Chippewa County, WI': '017', 'Clark County, WI': '019', 'Columbia County, WI': '021', 'Crawford County, WI': '023', 'Dane County, WI': '025', 'Dodge County, WI': '027', 'Door County, WI': '029', 'Douglas County, WI': '031', 'Dunn County, WI': '033', 'Eau Claire County, WI': '035', 'Florence County, WI': '037', 'Fond du Lac County, WI': '039', 'Forest County, WI': '041', 'Grant County, WI': '043', 'Green County, WI': '045', 'Green Lake County, WI': '047', 'Iowa County, WI': '049', 'Iron County, WI': '051', 'Jackson County, WI': '053', 'Jefferson County, WI': '055', 'Juneau County, WI': '057', 'Kenosha County, WI': '059', 'Kewaunee County, WI': '061', 'La Crosse County, WI': '063', 'Lafayette County, WI': '065', 'Langlade County, WI': '067', 'Lincoln County, WI': '069', 'Manitowoc County, WI': '071', 'Marathon County, WI': '073', 'Marinette County, WI': '075', 'Marquette County, WI': '077', 'Menominee County, WI': '078', 'Milwaukee County, WI': '079', 'Monroe County, WI': '081', 'Oconto County, WI': '083', 'Oneida County, WI': '085', 'Outagamie County, WI': '087', 'Ozaukee County, WI': '089', 'Pepin County, WI': '091', 'Pierce County, WI': '093', 'Polk County, WI': '095', 'Portage County, WI': '097', 'Price County, WI': '099', 'Racine County, WI': '101', 'Richland County, WI': '103', 'Rock County, WI': '105', 'Rusk County, WI': '107', 'Sauk County, WI': '111', 'Sawyer County, WI': '113', 'Shawano County, WI': '115', 'Sheboygan County, WI': '117', 'St. Croix County, WI': '109', 'Taylor County, WI': '119', 'Trempealeau County, WI': '121', 'Vernon County, WI': '123', 'Vilas County, WI': '125', 'Walworth County, WI': '127', 'Washburn County, WI': '129', 'Washington County, WI': '131', 'Waukesha County, WI': '133', 'Waupaca County, WI': '135', 'Waushara County, WI': '137', 'Winnebago County, WI': '139', 'Wood County, WI': '141'}, 56: { '--All--': '%', 'Albany County, WY': '001', 'Big Horn County, WY': '003', 'Campbell County, WY': '005', 'Carbon County, WY': '007', 'Converse County, WY': '009', 'Crook County, WY': '011', 'Fremont County, WY': '013', 'Goshen County, WY': '015', 'Hot Springs County, WY': '017', 'Johnson County, WY': '019', 'Laramie County, WY': '021', 'Lincoln County, WY': '023', 'Natrona County, WY': '025', 'Niobrara County, WY': '027', 'Park County, WY': '029', 'Platte County, WY': '031', 'Sheridan County, WY': '033', 'Sublette County, WY': '035', 'Sweetwater County, WY': '037', 'Teton County, WY': '039', 'Uinta County, WY': '041', 'Washakie County, WY': '043', 'Weston County, WY': '045'}, 72: { '--All--': '%', 'Adjuntas Municipio, PR': '001', 'Aguada Municipio, PR': '003', 'Aguadilla Municipio, PR': '005', 'Aguas Buenas Municipio, PR': '007', 'Aibonito Municipio, PR': '009', 'Anasco Municipio, PR': '011', 'Arecibo Municipio, PR': '013', 'Arroyo Municipio, PR': '015', 'Barceloneta Municipio, PR': '017', 'Barranquitas Municipio, PR': '019', 'Bayamon Municipio, PR': '021', 'Cabo Rojo Municipio, PR': '023', 'Caguas Municipio, PR': '025', 'Camuy Municipio, PR': '027', 'Canovanas Municipio, PR': '029', 'Carolina Municipio, PR': '031', 'Catano Municipio, PR': '033', 'Cayey Municipio, PR': '035', 'Ceiba Municipio, PR': '037', 'Ciales Municipio, PR': '039', 'Cidra Municipio, PR': '041', 'Coamo Municipio, PR': '043', 'Comerio Municipio, PR': '045', 'Corozal Municipio, PR': '047', 'Culebra Municipio, PR': '049', 'Dorado Municipio, PR': '051', 'Fajardo Municipio, PR': '053', 'Florida Municipio, PR': '054', 'Guanica Municipio, PR': '055', 'Guayama Municipio, PR': '057', 'Guayanilla Municipio, PR': '059', 'Guaynabo Municipio, PR': '061', 'Gurabo Municipio, PR': '063', 'Hatillo Municipio, PR': '065', 'Hormigueros Municipio, PR': '067', 'Humacao Municipio, PR': '069', 'Isabela Municipio, PR': '071', 'Jayuya Municipio, PR': '073', 'Juana Diaz Municipio, PR': '075', 'Juncos Municipio, PR': '077', 'Lajas Municipio, PR': '079', 'Lares Municipio, PR': '081', 'Las Marias Municipio, PR': '083', 'Las Piedras Municipio, PR': '085', 'Loiza Municipio, PR': '087', 'Luquillo Municipio, PR': '089', 'Manati Municipio, PR': '091', 'Maricao Municipio, PR': '093', 'Maunabo Municipio, PR': '095', 'Mayaguez Municipio, PR': '097', 'Moca Municipio, PR': '099', 'Morovis Municipio, PR': '101', 'Naguabo Municipio, PR': '103', 'Naranjito Municipio, PR': '105', 'Orocovis Municipio, PR': '107', 'Patillas Municipio, PR': '109', 'Penuelas Municipio, PR': '111', 'Ponce Municipio, PR': '113', 'Quebradillas Municipio, PR': '115', 'Rincon Municipio, PR': '117', 'Rio Grande Municipio, PR': '119', 'Sabana Grande Municipio, PR': '121', 'Salinas Municipio, PR': '123', 'San German Municipio, PR': '125', 'San Juan Municipio, PR': '127', 'San Lorenzo Municipio, PR': '129', 'San Sebastian Municipio, PR': '131', 'Santa Isabel Municipio, PR': '133', 'Toa Alta Municipio, PR': '135', 'Toa Baja Municipio, PR': '137', 'Trujillo Alto Municipio, PR': '139', 'Utuado Municipio, PR': '141', 'Vega Alta Municipio, PR': '143', 'Vega Baja Municipio, PR': '145', 'Vieques Municipio, PR': '147', 'Villalba Municipio, PR': '149', 'Yabucoa Municipio, PR': '151', 'Yauco Municipio, PR': '153'}, '01': { 'Autauga County, AL': '001', 'Baldwin County, AL': '003', 'Barbour County, AL': '005', 'Bibb County, AL': '007', 'Blount County, AL': '009', 'Bullock County, AL': '011', 'Butler County, AL': '013', 'Calhoun County, AL': '015', 'Chambers County, AL': '017', 'Cherokee County, AL': '019', 'Chilton County, AL': '021', 'Choctaw County, AL': '023', 'Clarke County, AL': '025', 'Clay County, AL': '027', 'Cleburne County, AL': '029', 'Coffee County, AL': '031', 'Colbert County, AL': '033', 'Conecuh County, AL': '035', 'Coosa County, AL': '037', 'Covington County, AL': '039', 'Crenshaw County, AL': '041', 'Cullman County, AL': '043', 'Dale County, AL': '045', 'Dallas County, AL': '047', 'DeKalb County, AL': '049', 'Elmore County, AL': '051', 'Escambia County, AL': '053', 'Etowah County, AL': '055', 'Fayette County, AL': '057', 'Franklin County, AL': '059', 'Geneva County, AL': '061', 'Greene County, AL': '063', 'Hale County, AL': '065', 'Henry County, AL': '067', 'Houston County, AL': '069', 'Jackson County, AL': '071', 'Jefferson County, AL': '073', 'Lamar County, AL': '075', 'Lauderdale County, AL': '077', 'Lawrence County, AL': '079', 'Lee County, AL': '081', 'Limestone County, AL': '083', 'Lowndes County, AL': '085', 'Macon County, AL': '087', 'Madison County, AL': '089', 'Marengo County, AL': '091', 'Marion County, AL': '093', 'Marshall County, AL': '095', 'Mobile County, AL': '097', 'Monroe County, AL': '099', 'Montgomery County, AL': '101', 'Morgan County, AL': '103', 'Perry County, AL': '105', 'Pickens County, AL': '107', 'Pike County, AL': '109', 'Randolph County, AL': '111', 'Russell County, AL': '113', 'Shelby County, AL': '117', 'St. Clair County, AL': '115', 'Sumter County, AL': '119', 'Talladega County, AL': '121', 'Tallapoosa County, AL': '123', 'Tuscaloosa County, AL': '125', 'Walker County, AL': '127', 'Washington County, AL': '129', 'Wilcox County, AL': '131', 'Winston County, AL': '133'}, '02': { 'Aleutians East Borough, AK': '013', 'Aleutians West Census Area, AK': '016', 'Anchorage Borough/municipality, AK': '020', 'Bethel Census Area, AK': '050', 'Bristol Bay Borough, AK': '060', 'Denali Borough, AK': '068', 'Dillingham Census Area, AK': '070', 'Fairbanks North Star Borough, AK': '090', 'Haines Borough, AK': '100', 'Juneau Borough/city, AK': '110', 'Kenai Peninsula Borough, AK': '122', 'Ketchikan Gateway Borough, AK': '130', 'Kodiak Island Borough, AK': '150', 'Lake and Peninsula Borough, AK': '164', 'Matanuska-Susitna Borough, AK': '170', 'Nome Census Area, AK': '180', 'North Slope Borough, AK': '185', 'Northwest Arctic Borough, AK': '188', 'Prince of Wales-Outer Ketchikan Census Area, AK': '201', 'Sitka Borough/city, AK': '220', 'Skagway-Hoonah-Angoon Census Area, AK': '232', 'Southeast Fairbanks Census Area, AK': '240', 'Valdez-Cordova Census Area, AK': '261', 'Wade Hampton Census Area, AK': '270', 'Wrangell-Petersburg Census Area, AK': '280', 'Yakutat Borough, AK': '282', 'Yukon-Koyukuk Census Area, AK': '290'}, '04': { 'Apache County, AZ': '001', 'Cochise County, AZ': '003', 'Coconino County, AZ': '005', 'Gila County, AZ': '007', 'Graham County, AZ': '009', 'Greenlee County, AZ': '011', 'La Paz County, AZ': '012', 'Maricopa County, AZ': '013', 'Mohave County, AZ': '015', 'Navajo County, AZ': '017', 'Pima County, AZ': '019', 'Pinal County, AZ': '021', 'Santa Cruz County, AZ': '023', 'Yavapai County, AZ': '025', 'Yuma County, AZ': '027'}, '05': { 'Arkansas County, AR': '001', 'Ashley County, AR': '003', 'Baxter County, AR': '005', 'Benton County, AR': '007', 'Boone County, AR': '009', 'Bradley County, AR': '011', 'Calhoun County, AR': '013', 'Carroll County, AR': '015', 'Chicot County, AR': '017', 'Clark County, AR': '019', 'Clay County, AR': '021', 'Cleburne County, AR': '023', 'Cleveland County, AR': '025', 'Columbia County, AR': '027', 'Conway County, AR': '029', 'Craighead County, AR': '031', 'Crawford County, AR': '033', 'Crittenden County, AR': '035', 'Cross County, AR': '037', 'Dallas County, AR': '039', 'Desha County, AR': '041', 'Drew County, AR': '043', 'Faulkner County, AR': '045', 'Franklin County, AR': '047', 'Fulton County, AR': '049', 'Garland County, AR': '051', 'Grant County, AR': '053', 'Greene County, AR': '055', 'Hempstead County, AR': '057', 'Hot Spring County, AR': '059', 'Howard County, AR': '061', 'Independence County, AR': '063', 'Izard County, AR': '065', 'Jackson County, AR': '067', 'Jefferson County, AR': '069', 'Johnson County, AR': '071', 'Lafayette County, AR': '073', 'Lawrence County, AR': '075', 'Lee County, AR': '077', 'Lincoln County, AR': '079', 'Little River County, AR': '081', 'Logan County, AR': '083', 'Lonoke County, AR': '085', 'Madison County, AR': '087', 'Marion County, AR': '089', 'Miller County, AR': '091', 'Mississippi County, AR': '093', 'Monroe County, AR': '095', 'Montgomery County, AR': '097', 'Nevada County, AR': '099', 'Newton County, AR': '101', 'Ouachita County, AR': '103', 'Perry County, AR': '105', 'Phillips County, AR': '107', 'Pike County, AR': '109', 'Poinsett County, AR': '111', 'Polk County, AR': '113', 'Pope County, AR': '115', 'Prairie County, AR': '117', 'Pulaski County, AR': '119', 'Randolph County, AR': '121', 'Saline County, AR': '125', 'Scott County, AR': '127', 'Searcy County, AR': '129', 'Sebastian County, AR': '131', 'Sevier County, AR': '133', 'Sharp County, AR': '135', 'St. Francis County, AR': '123', 'Stone County, AR': '137', 'Union County, AR': '139', 'Van Buren County, AR': '141', 'Washington County, AR': '143', 'White County, AR': '145', 'Woodruff County, AR': '147', 'Yell County, AR': '149'}, '06': { 'Alameda County, CA': '001', 'Alpine County, CA': '003', 'Amador County, CA': '005', 'Butte County, CA': '007', 'Calaveras County, CA': '009', 'Colusa County, CA': '011', 'Contra Costa County, CA': '013', 'Del Norte County, CA': '015', 'El Dorado County, CA': '017', 'Fresno County, CA': '019', 'Glenn County, CA': '021', 'Humboldt County, CA': '023', 'Imperial County, CA': '025', 'Inyo County, CA': '027', 'Kern County, CA': '029', 'Kings County, CA': '031', 'Lake County, CA': '033', 'Lassen County, CA': '035', 'Los Angeles County, CA': '037', 'Madera County, CA': '039', 'Marin County, CA': '041', 'Mariposa County, CA': '043', 'Mendocino County, CA': '045', 'Merced County, CA': '047', 'Modoc County, CA': '049', 'Mono County, CA': '051', 'Monterey County, CA': '053', 'Napa County, CA': '055', 'Nevada County, CA': '057', 'Orange County, CA': '059', 'Placer County, CA': '061', 'Plumas County, CA': '063', 'Riverside County, CA': '065', 'Sacramento County, CA': '067', 'San Benito County, CA': '069', 'San Bernardino County, CA': '071', 'San Diego County, CA': '073', 'San Francisco County/city, CA': '075', 'San Joaquin County, CA': '077', 'San Luis Obispo County, CA': '079', 'San Mateo County, CA': '081', 'Santa Barbara County, CA': '083', 'Santa Clara County, CA': '085', 'Santa Cruz County, CA': '087', 'Shasta County, CA': '089', 'Sierra County, CA': '091', 'Siskiyou County, CA': '093', 'Solano County, CA': '095', 'Sonoma County, CA': '097', 'Stanislaus County, CA': '099', 'Sutter County, CA': '101', 'Tehama County, CA': '103', 'Trinity County, CA': '105', 'Tulare County, CA': '107', 'Tuolumne County, CA': '109', 'Ventura County, CA': '111', 'Yolo County, CA': '113', 'Yuba County, CA': '115'}, '08': { 'Adams County, CO': '001', 'Alamosa County, CO': '003', 'Arapahoe County, CO': '005', 'Archuleta County, CO': '007', 'Baca County, CO': '009', 'Bent County, CO': '011', 'Boulder County, CO': '013', 'Broomfield County/city, CO': '014', 'Chaffee County, CO': '015', 'Cheyenne County, CO': '017', 'Clear Creek County, CO': '019', 'Conejos County, CO': '021', 'Costilla County, CO': '023', 'Crowley County, CO': '025', 'Custer County, CO': '027', 'Delta County, CO': '029', 'Denver County/city, CO': '031', 'Dolores County, CO': '033', 'Douglas County, CO': '035', 'Eagle County, CO': '037', 'El Paso County, CO': '041', 'Elbert County, CO': '039', 'Fremont County, CO': '043', 'Garfield County, CO': '045', 'Gilpin County, CO': '047', 'Grand County, CO': '049', 'Gunnison County, CO': '051', 'Hinsdale County, CO': '053', 'Huerfano County, CO': '055', 'Jackson County, CO': '057', 'Jefferson County, CO': '059', 'Kiowa County, CO': '061', 'Kit Carson County, CO': '063', 'La Plata County, CO': '067', 'Lake County, CO': '065', 'Larimer County, CO': '069', 'Las Animas County, CO': '071', 'Lincoln County, CO': '073', 'Logan County, CO': '075', 'Mesa County, CO': '077', 'Mineral County, CO': '079', 'Moffat County, CO': '081', 'Montezuma County, CO': '083', 'Montrose County, CO': '085', 'Morgan County, CO': '087', 'Otero County, CO': '089', 'Ouray County, CO': '091', 'Park County, CO': '093', 'Phillips County, CO': '095', 'Pitkin County, CO': '097', 'Prowers County, CO': '099', 'Pueblo County, CO': '101', 'Rio Blanco County, CO': '103', 'Rio Grande County, CO': '105', 'Routt County, CO': '107', 'Saguache County, CO': '109', 'San Juan County, CO': '111', 'San Miguel County, CO': '113', 'Sedgwick County, CO': '115', 'Summit County, CO': '117', 'Teller County, CO': '119', 'Washington County, CO': '121', 'Weld County, CO': '123', 'Yuma County, CO': '125'}, '09': { 'Fairfield County, CT': '001', 'Hartford County, CT': '003', 'Litchfield County, CT': '005', 'Middlesex County, CT': '007', 'New Haven County, CT': '009', 'New London County, CT': '011', 'Tolland County, CT': '013', 'Windham County, CT': '015'}, '10': { 'Kent County, DE': '001', 'New Castle County, DE': '003', 'Sussex County, DE': '005'}, '11': { 'District of Columbia': '001'}, '12': { 'Alachua County, FL': '001', 'Baker County, FL': '003', 'Bay County, FL': '005', 'Bradford County, FL': '007', 'Brevard County, FL': '009', 'Broward County, FL': '011', 'Calhoun County, FL': '013', 'Charlotte County, FL': '015', 'Citrus County, FL': '017', 'Clay County, FL': '019', 'Collier County, FL': '021', 'Columbia County, FL': '023', 'DeSoto County, FL': '027', 'Dixie County, FL': '029', 'Duval County, FL': '031', 'Escambia County, FL': '033', 'Flagler County, FL': '035', 'Franklin County, FL': '037', 'Gadsden County, FL': '039', 'Gilchrist County, FL': '041', 'Glades County, FL': '043', 'Gulf County, FL': '045', 'Hamilton County, FL': '047', 'Hardee County, FL': '049', 'Hendry County, FL': '051', 'Hernando County, FL': '053', 'Highlands County, FL': '055', 'Hillsborough County, FL': '057', 'Holmes County, FL': '059', 'Indian River County, FL': '061', 'Jackson County, FL': '063', 'Jefferson County, FL': '065', 'Lafayette County, FL': '067', 'Lake County, FL': '069', 'Lee County, FL': '071', 'Leon County, FL': '073', 'Levy County, FL': '075', 'Liberty County, FL': '077', 'Madison County, FL': '079', 'Manatee County, FL': '081', 'Marion County, FL': '083', 'Martin County, FL': '085', 'Miami-Dade County, FL': '086', 'Monroe County, FL': '087', 'Nassau County, FL': '089', 'Okaloosa County, FL': '091', 'Okeechobee County, FL': '093', 'Orange County, FL': '095', 'Osceola County, FL': '097', 'Palm Beach County, FL': '099', 'Pasco County, FL': '101', 'Pinellas County, FL': '103', 'Polk County, FL': '105', 'Putnam County, FL': '107', 'Santa Rosa County, FL': '113', 'Sarasota County, FL': '115', 'Seminole County, FL': '117', 'St. Johns County, FL': '109', 'St. Lucie County, FL': '111', 'Sumter County, FL': '119', 'Suwannee County, FL': '121', 'Taylor County, FL': '123', 'Union County, FL': '125', 'Volusia County, FL': '127', 'Wakulla County, FL': '129', 'Walton County, FL': '131', 'Washington County, FL': '133'}, '13': { 'Appling County, GA': '001', 'Atkinson County, GA': '003', 'Bacon County, GA': '005', 'Baker County, GA': '007', 'Baldwin County, GA': '009', 'Banks County, GA': '011', 'Barrow County, GA': '013', 'Bartow County, GA': '015', 'Ben Hill County, GA': '017', 'Berrien County, GA': '019', 'Bibb County, GA': '021', 'Bleckley County, GA': '023', 'Brantley County, GA': '025', 'Brooks County, GA': '027', 'Bryan County, GA': '029', 'Bulloch County, GA': '031', 'Burke County, GA': '033', 'Butts County, GA': '035', 'Calhoun County, GA': '037', 'Camden County, GA': '039', 'Candler County, GA': '043', 'Carroll County, GA': '045', 'Catoosa County, GA': '047', 'Charlton County, GA': '049', 'Chatham County, GA': '051', 'Chattahoochee County, GA': '053', 'Chattooga County, GA': '055', 'Cherokee County, GA': '057', 'Clarke County, GA': '059', 'Clay County, GA': '061', 'Clayton County, GA': '063', 'Clinch County, GA': '065', 'Cobb County, GA': '067', 'Coffee County, GA': '069', 'Colquitt County, GA': '071', 'Columbia County, GA': '073', 'Cook County, GA': '075', 'Coweta County, GA': '077', 'Crawford County, GA': '079', 'Crisp County, GA': '081', 'Dade County, GA': '083', 'Dawson County, GA': '085', 'DeKalb County, GA': '089', 'Decatur County, GA': '087', 'Dodge County, GA': '091', 'Dooly County, GA': '093', 'Dougherty County, GA': '095', 'Douglas County, GA': '097', 'Early County, GA': '099', 'Echols County, GA': '101', 'Effingham County, GA': '103', 'Elbert County, GA': '105', 'Emanuel County, GA': '107', 'Evans County, GA': '109', 'Fannin County, GA': '111', 'Fayette County, GA': '113', 'Floyd County, GA': '115', 'Forsyth County, GA': '117', 'Franklin County, GA': '119', 'Fulton County, GA': '121', 'Gilmer County, GA': '123', 'Glascock County, GA': '125', 'Glynn County, GA': '127', 'Gordon County, GA': '129', 'Grady County, GA': '131', 'Greene County, GA': '133', 'Gwinnett County, GA': '135', 'Habersham County, GA': '137', 'Hall County, GA': '139', 'Hancock County, GA': '141', 'Haralson County, GA': '143', 'Harris County, GA': '145', 'Hart County, GA': '147', 'Heard County, GA': '149', 'Henry County, GA': '151', 'Houston County, GA': '153', 'Irwin County, GA': '155', 'Jackson County, GA': '157', 'Jasper County, GA': '159', 'Jeff Davis County, GA': '161', 'Jefferson County, GA': '163', 'Jenkins County, GA': '165', 'Johnson County, GA': '167', 'Jones County, GA': '169', 'Lamar County, GA': '171', 'Lanier County, GA': '173', 'Laurens County, GA': '175', 'Lee County, GA': '177', 'Liberty County, GA': '179', 'Lincoln County, GA': '181', 'Long County, GA': '183', 'Lowndes County, GA': '185', 'Lumpkin County, GA': '187', 'Macon County, GA': '193', 'Madison County, GA': '195', 'Marion County, GA': '197', 'McDuffie County, GA': '189', 'McIntosh County, GA': '191', 'Meriwether County, GA': '199', 'Miller County, GA': '201', 'Mitchell County, GA': '205', 'Monroe County, GA': '207', 'Montgomery County, GA': '209', 'Morgan County, GA': '211', 'Murray County, GA': '213', 'Muscogee County, GA': '215', 'Newton County, GA': '217', 'Oconee County, GA': '219', 'Oglethorpe County, GA': '221', 'Paulding County, GA': '223', 'Peach County, GA': '225', 'Pickens County, GA': '227', 'Pierce County, GA': '229', 'Pike County, GA': '231', 'Polk County, GA': '233', 'Pulaski County, GA': '235', 'Putnam County, GA': '237', 'Quitman County, GA': '239', 'Rabun County, GA': '241', 'Randolph County, GA': '243', 'Richmond County, GA': '245', 'Rockdale County, GA': '247', 'Schley County, GA': '249', 'Screven County, GA': '251', 'Seminole County, GA': '253', 'Spalding County, GA': '255', 'Stephens County, GA': '257', 'Stewart County, GA': '259', 'Sumter County, GA': '261', 'Talbot County, GA': '263', 'Taliaferro County, GA': '265', 'Tattnall County, GA': '267', 'Taylor County, GA': '269', 'Telfair County, GA': '271', 'Terrell County, GA': '273', 'Thomas County, GA': '275', 'Tift County, GA': '277', 'Toombs County, GA': '279', 'Towns County, GA': '281', 'Treutlen County, GA': '283', 'Troup County, GA': '285', 'Turner County, GA': '287', 'Twiggs County, GA': '289', 'Union County, GA': '291', 'Upson County, GA': '293', 'Walker County, GA': '295', 'Walton County, GA': '297', 'Ware County, GA': '299', 'Warren County, GA': '301', 'Washington County, GA': '303', 'Wayne County, GA': '305', 'Webster County, GA': '307', 'Wheeler County, GA': '309', 'White County, GA': '311', 'Whitfield County, GA': '313', 'Wilcox County, GA': '315', 'Wilkes County, GA': '317', 'Wilkinson County, GA': '319', 'Worth County, GA': '321'}, '15': { 'Hawaii County, HI': '001', 'Honolulu County/city, HI': '003', 'Kauai County, HI': '007', 'Maui County, HI': '009'}, '16': { 'Ada County, ID': '001', 'Adams County, ID': '003', 'Bannock County, ID': '005', 'Bear Lake County, ID': '007', 'Benewah County, ID': '009', 'Bingham County, ID': '011', 'Blaine County, ID': '013', 'Boise County, ID': '015', 'Bonner County, ID': '017', 'Bonneville County, ID': '019', 'Boundary County, ID': '021', 'Butte County, ID': '023', 'Camas County, ID': '025', 'Canyon County, ID': '027', 'Caribou County, ID': '029', 'Cassia County, ID': '031', 'Clark County, ID': '033', 'Clearwater County, ID': '035', 'Custer County, ID': '037', 'Elmore County, ID': '039', 'Franklin County, ID': '041', 'Fremont County, ID': '043', 'Gem County, ID': '045', 'Gooding County, ID': '047', 'Idaho County, ID': '049', 'Jefferson County, ID': '051', 'Jerome County, ID': '053', 'Kootenai County, ID': '055', 'Latah County, ID': '057', 'Lemhi County, ID': '059', 'Lewis County, ID': '061', 'Lincoln County, ID': '063', 'Madison County, ID': '065', 'Minidoka County, ID': '067', 'Nez Perce County, ID': '069', 'Oneida County, ID': '071', 'Owyhee County, ID': '073', 'Payette County, ID': '075', 'Power County, ID': '077', 'Shoshone County, ID': '079', 'Teton County, ID': '081', 'Twin Falls County, ID': '083', 'Valley County, ID': '085', 'Washington County, ID': '087'}, '17': { 'Adams County, IL': '001', 'Alexander County, IL': '003', 'Bond County, IL': '005', 'Boone County, IL': '007', 'Brown County, IL': '009', 'Bureau County, IL': '011', 'Calhoun County, IL': '013', 'Carroll County, IL': '015', 'Cass County, IL': '017', 'Champaign County, IL': '019', 'Christian County, IL': '021', 'Clark County, IL': '023', 'Clay County, IL': '025', 'Clinton County, IL': '027', 'Coles County, IL': '029', 'Cook County, IL': '031', 'Crawford County, IL': '033', 'Cumberland County, IL': '035', 'De Witt County, IL': '039', 'DeKalb County, IL': '037', 'Douglas County, IL': '041', 'DuPage County, IL': '043', 'Edgar County, IL': '045', 'Edwards County, IL': '047', 'Effingham County, IL': '049', 'Fayette County, IL': '051', 'Ford County, IL': '053', 'Franklin County, IL': '055', 'Fulton County, IL': '057', 'Gallatin County, IL': '059', 'Greene County, IL': '061', 'Grundy County, IL': '063', 'Hamilton County, IL': '065', 'Hancock County, IL': '067', 'Hardin County, IL': '069', 'Henderson County, IL': '071', 'Henry County, IL': '073', 'Iroquois County, IL': '075', 'Jackson County, IL': '077', 'Jasper County, IL': '079', 'Jefferson County, IL': '081', 'Jersey County, IL': '083', 'Jo Daviess County, IL': '085', 'Johnson County, IL': '087', 'Kane County, IL': '089', 'Kankakee County, IL': '091', 'Kendall County, IL': '093', 'Knox County, IL': '095', 'La Salle County, IL': '099', 'Lake County, IL': '097', 'Lawrence County, IL': '101', 'Lee County, IL': '103', 'Livingston County, IL': '105', 'Logan County, IL': '107', 'Macon County, IL': '115', 'Macoupin County, IL': '117', 'Madison County, IL': '119', 'Marion County, IL': '121', 'Marshall County, IL': '123', 'Mason County, IL': '125', 'Massac County, IL': '127', 'McDonough County, IL': '109', 'McHenry County, IL': '111', 'McLean County, IL': '113', 'Menard County, IL': '129', 'Mercer County, IL': '131', 'Monroe County, IL': '133', 'Montgomery County, IL': '135', 'Morgan County, IL': '137', 'Moultrie County, IL': '139', 'Ogle County, IL': '141', 'Peoria County, IL': '143', 'Perry County, IL': '145', 'Piatt County, IL': '147', 'Pike County, IL': '149', 'Pope County, IL': '151', 'Pulaski County, IL': '153', 'Putnam County, IL': '155', 'Randolph County, IL': '157', 'Richland County, IL': '159', 'Rock Island County, IL': '161', 'Saline County, IL': '165', 'Sangamon County, IL': '167', 'Schuyler County, IL': '169', 'Scott County, IL': '171', 'Shelby County, IL': '173', 'St. Clair County, IL': '163', 'Stark County, IL': '175', 'Stephenson County, IL': '177', 'Tazewell County, IL': '179', 'Union County, IL': '181', 'Vermilion County, IL': '183', 'Wabash County, IL': '185', 'Warren County, IL': '187', 'Washington County, IL': '189', 'Wayne County, IL': '191', 'White County, IL': '193', 'Whiteside County, IL': '195', 'Will County, IL': '197', 'Williamson County, IL': '199', 'Winnebago County, IL': '201', 'Woodford County, IL': '203'}, '18': { 'Adams County, IN': '001', 'Allen County, IN': '003', 'Bartholomew County, IN': '005', 'Benton County, IN': '007', 'Blackford County, IN': '009', 'Boone County, IN': '011', 'Brown County, IN': '013', 'Carroll County, IN': '015', 'Cass County, IN': '017', 'Clark County, IN': '019', 'Clay County, IN': '021', 'Clinton County, IN': '023', 'Crawford County, IN': '025', 'Daviess County, IN': '027', 'DeKalb County, IN': '033', 'Dearborn County, IN': '029', 'Decatur County, IN': '031', 'Delaware County, IN': '035', 'Dubois County, IN': '037', 'Elkhart County, IN': '039', 'Fayette County, IN': '041', 'Floyd County, IN': '043', 'Fountain County, IN': '045', 'Franklin County, IN': '047', 'Fulton County, IN': '049', 'Gibson County, IN': '051', 'Grant County, IN': '053', 'Greene County, IN': '055', 'Hamilton County, IN': '057', 'Hancock County, IN': '059', 'Harrison County, IN': '061', 'Hendricks County, IN': '063', 'Henry County, IN': '065', 'Howard County, IN': '067', 'Huntington County, IN': '069', 'Jackson County, IN': '071', 'Jasper County, IN': '073', 'Jay County, IN': '075', 'Jefferson County, IN': '077', 'Jennings County, IN': '079', 'Johnson County, IN': '081', 'Knox County, IN': '083', 'Kosciusko County, IN': '085', 'LaGrange County, IN': '087', 'LaPorte County, IN': '091', 'Lake County, IN': '089', 'Lawrence County, IN': '093', 'Madison County, IN': '095', 'Marion County, IN': '097', 'Marshall County, IN': '099', 'Martin County, IN': '101', 'Miami County, IN': '103', 'Monroe County, IN': '105', 'Montgomery County, IN': '107', 'Morgan County, IN': '109', 'Newton County, IN': '111', 'Noble County, IN': '113', 'Ohio County, IN': '115', 'Orange County, IN': '117', 'Owen County, IN': '119', 'Parke County, IN': '121', 'Perry County, IN': '123', 'Pike County, IN': '125', 'Porter County, IN': '127', 'Posey County, IN': '129', 'Pulaski County, IN': '131', 'Putnam County, IN': '133', 'Randolph County, IN': '135', 'Ripley County, IN': '137', 'Rush County, IN': '139', 'Scott County, IN': '143', 'Shelby County, IN': '145', 'Spencer County, IN': '147', 'St. Joseph County, IN': '141', 'Starke County, IN': '149', 'Steuben County, IN': '151', 'Sullivan County, IN': '153', 'Switzerland County, IN': '155', 'Tippecanoe County, IN': '157', 'Tipton County, IN': '159', 'Union County, IN': '161', 'Vanderburgh County, IN': '163', 'Vermillion County, IN': '165', 'Vigo County, IN': '167', 'Wabash County, IN': '169', 'Warren County, IN': '171', 'Warrick County, IN': '173', 'Washington County, IN': '175', 'Wayne County, IN': '177', 'Wells County, IN': '179', 'White County, IN': '181', 'Whitley County, IN': '183'}, '19': { 'Adair County, IA': '001', 'Adams County, IA': '003', 'Allamakee County, IA': '005', 'Appanoose County, IA': '007', 'Audubon County, IA': '009', 'Benton County, IA': '011', 'Black Hawk County, IA': '013', 'Boone County, IA': '015', 'Bremer County, IA': '017', 'Buchanan County, IA': '019', 'Buena Vista County, IA': '021', 'Butler County, IA': '023', 'Calhoun County, IA': '025', 'Carroll County, IA': '027', 'Cass County, IA': '029', 'Cedar County, IA': '031', 'Cerro Gordo County, IA': '033', 'Cherokee County, IA': '035', 'Chickasaw County, IA': '037', 'Clarke County, IA': '039', 'Clay County, IA': '041', 'Clayton County, IA': '043', 'Clinton County, IA': '045', 'Crawford County, IA': '047', 'Dallas County, IA': '049', 'Davis County, IA': '051', 'Decatur County, IA': '053', 'Delaware County, IA': '055', 'Des Moines County, IA': '057', 'Dickinson County, IA': '059', 'Dubuque County, IA': '061', 'Emmet County, IA': '063', 'Fayette County, IA': '065', 'Floyd County, IA': '067', 'Franklin County, IA': '069', 'Fremont County, IA': '071', 'Greene County, IA': '073', 'Grundy County, IA': '075', 'Guthrie County, IA': '077', 'Hamilton County, IA': '079', 'Hancock County, IA': '081', 'Hardin County, IA': '083', 'Harrison County, IA': '085', 'Henry County, IA': '087', 'Howard County, IA': '089', 'Humboldt County, IA': '091', 'Ida County, IA': '093', 'Iowa County, IA': '095', 'Jackson County, IA': '097', 'Jasper County, IA': '099', 'Jefferson County, IA': '101', 'Johnson County, IA': '103', 'Jones County, IA': '105', 'Keokuk County, IA': '107', 'Kossuth County, IA': '109', 'Lee County, IA': '111', 'Linn County, IA': '113', 'Louisa County, IA': '115', 'Lucas County, IA': '117', 'Lyon County, IA': '119', 'Madison County, IA': '121', 'Mahaska County, IA': '123', 'Marion County, IA': '125', 'Marshall County, IA': '127', 'Mills County, IA': '129', 'Mitchell County, IA': '131', 'Monona County, IA': '133', 'Monroe County, IA': '135', 'Montgomery County, IA': '137', 'Muscatine County, IA': '139', "O'Brien County, IA": '141', 'Osceola County, IA': '143', 'Page County, IA': '145', 'Palo Alto County, IA': '147', 'Plymouth County, IA': '149', 'Pocahontas County, IA': '151', 'Polk County, IA': '153', 'Pottawattamie County, IA': '155', 'Poweshiek County, IA': '157', 'Ringgold County, IA': '159', 'Sac County, IA': '161', 'Scott County, IA': '163', 'Shelby County, IA': '165', 'Sioux County, IA': '167', 'Story County, IA': '169', 'Tama County, IA': '171', 'Taylor County, IA': '173', 'Union County, IA': '175', 'Van Buren County, IA': '177', 'Wapello County, IA': '179', 'Warren County, IA': '181', 'Washington County, IA': '183', 'Wayne County, IA': '185', 'Webster County, IA': '187', 'Winnebago County, IA': '189', 'Winneshiek County, IA': '191', 'Woodbury County, IA': '193', 'Worth County, IA': '195', 'Wright County, IA': '197'}, '20': { 'Allen County, KS': '001', 'Anderson County, KS': '003', 'Atchison County, KS': '005', 'Barber County, KS': '007', 'Barton County, KS': '009', 'Bourbon County, KS': '011', 'Brown County, KS': '013', 'Butler County, KS': '015', 'Chase County, KS': '017', 'Chautauqua County, KS': '019', 'Cherokee County, KS': '021', 'Cheyenne County, KS': '023', 'Clark County, KS': '025', 'Clay County, KS': '027', 'Cloud County, KS': '029', 'Coffey County, KS': '031', 'Comanche County, KS': '033', 'Cowley County, KS': '035', 'Crawford County, KS': '037', 'Decatur County, KS': '039', 'Dickinson County, KS': '041', 'Doniphan County, KS': '043', 'Douglas County, KS': '045', 'Edwards County, KS': '047', 'Elk County, KS': '049', 'Ellis County, KS': '051', 'Ellsworth County, KS': '053', 'Finney County, KS': '055', 'Ford County, KS': '057', 'Franklin County, KS': '059', 'Geary County, KS': '061', 'Gove County, KS': '063', 'Graham County, KS': '065', 'Grant County, KS': '067', 'Gray County, KS': '069', 'Greeley County, KS': '071', 'Greenwood County, KS': '073', 'Hamilton County, KS': '075', 'Harper County, KS': '077', 'Harvey County, KS': '079', 'Haskell County, KS': '081', 'Hodgeman County, KS': '083', 'Jackson County, KS': '085', 'Jefferson County, KS': '087', 'Jewell County, KS': '089', 'Johnson County, KS': '091', 'Kearny County, KS': '093', 'Kingman County, KS': '095', 'Kiowa County, KS': '097', 'Labette County, KS': '099', 'Lane County, KS': '101', 'Leavenworth County, KS': '103', 'Lincoln County, KS': '105', 'Linn County, KS': '107', 'Logan County, KS': '109', 'Lyon County, KS': '111', 'Marion County, KS': '115', 'Marshall County, KS': '117', 'McPherson County, KS': '113', 'Meade County, KS': '119', 'Miami County, KS': '121', 'Mitchell County, KS': '123', 'Montgomery County, KS': '125', 'Morris County, KS': '127', 'Morton County, KS': '129', 'Nemaha County, KS': '131', 'Neosho County, KS': '133', 'Ness County, KS': '135', 'Norton County, KS': '137', 'Osage County, KS': '139', 'Osborne County, KS': '141', 'Ottawa County, KS': '143', 'Pawnee County, KS': '145', 'Phillips County, KS': '147', 'Pottawatomie County, KS': '149', 'Pratt County, KS': '151', 'Rawlins County, KS': '153', 'Reno County, KS': '155', 'Republic County, KS': '157', 'Rice County, KS': '159', 'Riley County, KS': '161', 'Rooks County, KS': '163', 'Rush County, KS': '165', 'Russell County, KS': '167', 'Saline County, KS': '169', 'Scott County, KS': '171', 'Sedgwick County, KS': '173', 'Seward County, KS': '175', 'Shawnee County, KS': '177', 'Sheridan County, KS': '179', 'Sherman County, KS': '181', 'Smith County, KS': '183', 'Stafford County, KS': '185', 'Stanton County, KS': '187', 'Stevens County, KS': '189', 'Sumner County, KS': '191', 'Thomas County, KS': '193', 'Trego County, KS': '195', 'Wabaunsee County, KS': '197', 'Wallace County, KS': '199', 'Washington County, KS': '201', 'Wichita County, KS': '203', 'Wilson County, KS': '205', 'Woodson County, KS': '207', 'Wyandotte County, KS': '209'}, '21': { 'Adair County, KY': '001', 'Allen County, KY': '003', 'Anderson County, KY': '005', 'Ballard County, KY': '007', 'Barren County, KY': '009', 'Bath County, KY': '011', 'Bell County, KY': '013', 'Boone County, KY': '015', 'Bourbon County, KY': '017', 'Boyd County, KY': '019', 'Boyle County, KY': '021', 'Bracken County, KY': '023', 'Breathitt County, KY': '025', 'Breckinridge County, KY': '027', 'Bullitt County, KY': '029', 'Butler County, KY': '031', 'Caldwell County, KY': '033', 'Calloway County, KY': '035', 'Campbell County, KY': '037', 'Carlisle County, KY': '039', 'Carroll County, KY': '041', 'Carter County, KY': '043', 'Casey County, KY': '045', 'Christian County, KY': '047', 'Clark County, KY': '049', 'Clay County, KY': '051', 'Clinton County, KY': '053', 'Crittenden County, KY': '055', 'Cumberland County, KY': '057', 'Daviess County, KY': '059', 'Edmonson County, KY': '061', 'Elliott County, KY': '063', 'Estill County, KY': '065', 'Fayette County, KY': '067', 'Fleming County, KY': '069', 'Floyd County, KY': '071', 'Franklin County, KY': '073', 'Fulton County, KY': '075', 'Gallatin County, KY': '077', 'Garrard County, KY': '079', 'Grant County, KY': '081', 'Graves County, KY': '083', 'Grayson County, KY': '085', 'Green County, KY': '087', 'Greenup County, KY': '089', 'Hancock County, KY': '091', 'Hardin County, KY': '093', 'Harlan County, KY': '095', 'Harrison County, KY': '097', 'Hart County, KY': '099', 'Henderson County, KY': '101', 'Henry County, KY': '103', 'Hickman County, KY': '105', 'Hopkins County, KY': '107', 'Jackson County, KY': '109', 'Jefferson County, KY': '111', 'Jessamine County, KY': '113', 'Johnson County, KY': '115', 'Kenton County, KY': '117', 'Knott County, KY': '119', 'Knox County, KY': '121', 'Larue County, KY': '123', 'Laurel County, KY': '125', 'Lawrence County, KY': '127', 'Lee County, KY': '129', 'Leslie County, KY': '131', 'Letcher County, KY': '133', 'Lewis County, KY': '135', 'Lincoln County, KY': '137', 'Livingston County, KY': '139', 'Logan County, KY': '141', 'Lyon County, KY': '143', 'Madison County, KY': '151', 'Magoffin County, KY': '153', 'Marion County, KY': '155', 'Marshall County, KY': '157', 'Martin County, KY': '159', 'Mason County, KY': '161', 'McCracken County, KY': '145', 'McCreary County, KY': '147', 'McLean County, KY': '149', 'Meade County, KY': '163', 'Menifee County, KY': '165', 'Mercer County, KY': '167', 'Metcalfe County, KY': '169', 'Monroe County, KY': '171', 'Montgomery County, KY': '173', 'Morgan County, KY': '175', 'Muhlenberg County, KY': '177', 'Nelson County, KY': '179', 'Nicholas County, KY': '181', 'Ohio County, KY': '183', 'Oldham County, KY': '185', 'Owen County, KY': '187', 'Owsley County, KY': '189', 'Pendleton County, KY': '191', 'Perry County, KY': '193', 'Pike County, KY': '195', 'Powell County, KY': '197', 'Pulaski County, KY': '199', 'Robertson County, KY': '201', 'Rockcastle County, KY': '203', 'Rowan County, KY': '205', 'Russell County, KY': '207', 'Scott County, KY': '209', 'Shelby County, KY': '211', 'Simpson County, KY': '213', 'Spencer County, KY': '215', 'Taylor County, KY': '217', 'Todd County, KY': '219', 'Trigg County, KY': '221', 'Trimble County, KY': '223', 'Union County, KY': '225', 'Warren County, KY': '227', 'Washington County, KY': '229', 'Wayne County, KY': '231', 'Webster County, KY': '233', 'Whitley County, KY': '235', 'Wolfe County, KY': '237', 'Woodford County, KY': '239'}, '22': { 'Acadia Parish, LA': '001', 'Allen Parish, LA': '003', 'Ascension Parish, LA': '005', 'Assumption Parish, LA': '007', 'Avoyelles Parish, LA': '009', 'Beauregard Parish, LA': '011', 'Bienville Parish, LA': '013', 'Bossier Parish, LA': '015', 'Caddo Parish, LA': '017', 'Calcasieu Parish, LA': '019', 'Caldwell Parish, LA': '021', 'Cameron Parish, LA': '023', 'Catahoula Parish, LA': '025', 'Claiborne Parish, LA': '027', 'Concordia Parish, LA': '029', 'De Soto Parish, LA': '031', 'East Baton Rouge Parish, LA': '033', 'East Carroll Parish, LA': '035', 'East Feliciana Parish, LA': '037', 'Evangeline Parish, LA': '039', 'Franklin Parish, LA': '041', 'Grant Parish, LA': '043', 'Iberia Parish, LA': '045', 'Iberville Parish, LA': '047', 'Jackson Parish, LA': '049', 'Jefferson Davis Parish, LA': '053', 'Jefferson Parish, LA': '051', 'La Salle Parish, LA': '059', 'Lafayette Parish, LA': '055', 'Lafourche Parish, LA': '057', 'Lincoln Parish, LA': '061', 'Livingston Parish, LA': '063', 'Madison Parish, LA': '065', 'Morehouse Parish, LA': '067', 'Natchitoches Parish, LA': '069', 'Orleans Parish, LA': '071', 'Ouachita Parish, LA': '073', 'Plaquemines Parish, LA': '075', 'Pointe Coupee Parish, LA': '077', 'Rapides Parish, LA': '079', 'Red River Parish, LA': '081', 'Richland Parish, LA': '083', 'Sabine Parish, LA': '085', 'St. Bernard Parish, LA': '087', 'St. Charles Parish, LA': '089', 'St. Helena Parish, LA': '091', 'St. James Parish, LA': '093', 'St. John the Baptist Parish, LA': '095', 'St. Landry Parish, LA': '097', 'St. Martin Parish, LA': '099', 'St. Mary Parish, LA': '101', 'St. Tammany Parish, LA': '103', 'Tangipahoa Parish, LA': '105', 'Tensas Parish, LA': '107', 'Terrebonne Parish, LA': '109', 'Union Parish, LA': '111', 'Vermilion Parish, LA': '113', 'Vernon Parish, LA': '115', 'Washington Parish, LA': '117', 'Webster Parish, LA': '119', 'West Baton Rouge Parish, LA': '121', 'West Carroll Parish, LA': '123', 'West Feliciana Parish, LA': '125', 'Winn Parish, LA': '127'}, '23': { 'Androscoggin County, ME': '001', 'Aroostook County, ME': '003', 'Cumberland County, ME': '005', 'Franklin County, ME': '007', 'Hancock County, ME': '009', 'Kennebec County, ME': '011', 'Knox County, ME': '013', 'Lincoln County, ME': '015', 'Oxford County, ME': '017', 'Penobscot County, ME': '019', 'Piscataquis County, ME': '021', 'Sagadahoc County, ME': '023', 'Somerset County, ME': '025', 'Waldo County, ME': '027', 'Washington County, ME': '029', 'York County, ME': '031'}, '24': { 'Allegany County, MD': '001', 'Anne Arundel County, MD': '003', 'Baltimore County, MD': '005', 'Baltimore city, MD': '510', 'Calvert County, MD': '009', 'Caroline County, MD': '011', 'Carroll County, MD': '013', 'Cecil County, MD': '015', 'Charles County, MD': '017', 'Dorchester County, MD': '019', 'Frederick County, MD': '021', 'Garrett County, MD': '023', 'Harford County, MD': '025', 'Howard County, MD': '027', 'Kent County, MD': '029', 'Montgomery County, MD': '031', "Prince George's County, MD": '033', "Queen Anne's County, MD": '035', 'Somerset County, MD': '039', "St. Mary's County, MD": '037', 'Talbot County, MD': '041', 'Washington County, MD': '043', 'Wicomico County, MD': '045', 'Worcester County, MD': '047'}, '25': { 'Barnstable County, MA': '001', 'Berkshire County, MA': '003', 'Bristol County, MA': '005', 'Dukes County, MA': '007', 'Essex County, MA': '009', 'Franklin County, MA': '011', 'Hampden County, MA': '013', 'Hampshire County, MA': '015', 'Middlesex County, MA': '017', 'Nantucket County/town, MA': '019', 'Norfolk County, MA': '021', 'Plymouth County, MA': '023', 'Suffolk County, MA': '025', 'Worcester County, MA': '027'}, '26': { 'Alcona County, MI': '001', 'Alger County, MI': '003', 'Allegan County, MI': '005', 'Alpena County, MI': '007', 'Antrim County, MI': '009', 'Arenac County, MI': '011', 'Baraga County, MI': '013', 'Barry County, MI': '015', 'Bay County, MI': '017', 'Benzie County, MI': '019', 'Berrien County, MI': '021', 'Branch County, MI': '023', 'Calhoun County, MI': '025', 'Cass County, MI': '027', 'Charlevoix County, MI': '029', 'Cheboygan County, MI': '031', 'Chippewa County, MI': '033', 'Clare County, MI': '035', 'Clinton County, MI': '037', 'Crawford County, MI': '039', 'Delta County, MI': '041', 'Dickinson County, MI': '043', 'Eaton County, MI': '045', 'Emmet County, MI': '047', 'Genesee County, MI': '049', 'Gladwin County, MI': '051', 'Gogebic County, MI': '053', 'Grand Traverse County, MI': '055', 'Gratiot County, MI': '057', 'Hillsdale County, MI': '059', 'Houghton County, MI': '061', 'Huron County, MI': '063', 'Ingham County, MI': '065', 'Ionia County, MI': '067', 'Iosco County, MI': '069', 'Iron County, MI': '071', 'Isabella County, MI': '073', 'Jackson County, MI': '075', 'Kalamazoo County, MI': '077', 'Kalkaska County, MI': '079', 'Kent County, MI': '081', 'Keweenaw County, MI': '083', 'Lake County, MI': '085', 'Lapeer County, MI': '087', 'Leelanau County, MI': '089', 'Lenawee County, MI': '091', 'Livingston County, MI': '093', 'Luce County, MI': '095', 'Mackinac County, MI': '097', 'Macomb County, MI': '099', 'Manistee County, MI': '101', 'Marquette County, MI': '103', 'Mason County, MI': '105', 'Mecosta County, MI': '107', 'Menominee County, MI': '109', 'Midland County, MI': '111', 'Missaukee County, MI': '113', 'Monroe County, MI': '115', 'Montcalm County, MI': '117', 'Montmorency County, MI': '119', 'Muskegon County, MI': '121', 'Newaygo County, MI': '123', 'Oakland County, MI': '125', 'Oceana County, MI': '127', 'Ogemaw County, MI': '129', 'Ontonagon County, MI': '131', 'Osceola County, MI': '133', 'Oscoda County, MI': '135', 'Otsego County, MI': '137', 'Ottawa County, MI': '139', 'Presque Isle County, MI': '141', 'Roscommon County, MI': '143', 'Saginaw County, MI': '145', 'Sanilac County, MI': '151', 'Schoolcraft County, MI': '153', 'Shiawassee County, MI': '155', 'St. Clair County, MI': '147', 'St. Joseph County, MI': '149', 'Tuscola County, MI': '157', 'Van Buren County, MI': '159', 'Washtenaw County, MI': '161', 'Wayne County, MI': '163', 'Wexford County, MI': '165'}, '27': { 'Aitkin County, MN': '001', 'Anoka County, MN': '003', 'Becker County, MN': '005', 'Beltrami County, MN': '007', 'Benton County, MN': '009', 'Big Stone County, MN': '011', 'Blue Earth County, MN': '013', 'Brown County, MN': '015', 'Carlton County, MN': '017', 'Carver County, MN': '019', 'Cass County, MN': '021', 'Chippewa County, MN': '023', 'Chisago County, MN': '025', 'Clay County, MN': '027', 'Clearwater County, MN': '029', 'Cook County, MN': '031', 'Cottonwood County, MN': '033', 'Crow Wing County, MN': '035', 'Dakota County, MN': '037', 'Dodge County, MN': '039', 'Douglas County, MN': '041', 'Faribault County, MN': '043', 'Fillmore County, MN': '045', 'Freeborn County, MN': '047', 'Goodhue County, MN': '049', 'Grant County, MN': '051', 'Hennepin County, MN': '053', 'Houston County, MN': '055', 'Hubbard County, MN': '057', 'Isanti County, MN': '059', 'Itasca County, MN': '061', 'Jackson County, MN': '063', 'Kanabec County, MN': '065', 'Kandiyohi County, MN': '067', 'Kittson County, MN': '069', 'Koochiching County, MN': '071', 'Lac qui Parle County, MN': '073', 'Lake County, MN': '075', 'Lake of the Woods County, MN': '077', 'Le Sueur County, MN': '079', 'Lincoln County, MN': '081', 'Lyon County, MN': '083', 'Mahnomen County, MN': '087', 'Marshall County, MN': '089', 'Martin County, MN': '091', 'McLeod County, MN': '085', 'Meeker County, MN': '093', 'Mille Lacs County, MN': '095', 'Morrison County, MN': '097', 'Mower County, MN': '099', 'Murray County, MN': '101', 'Nicollet County, MN': '103', 'Nobles County, MN': '105', 'Norman County, MN': '107', 'Olmsted County, MN': '109', 'Otter Tail County, MN': '111', 'Pennington County, MN': '113', 'Pine County, MN': '115', 'Pipestone County, MN': '117', 'Polk County, MN': '119', 'Pope County, MN': '121', 'Ramsey County, MN': '123', 'Red Lake County, MN': '125', 'Redwood County, MN': '127', 'Renville County, MN': '129', 'Rice County, MN': '131', 'Rock County, MN': '133', 'Roseau County, MN': '135', 'Scott County, MN': '139', 'Sherburne County, MN': '141', 'Sibley County, MN': '143', 'St. Louis County, MN': '137', 'Stearns County, MN': '145', 'Steele County, MN': '147', 'Stevens County, MN': '149', 'Swift County, MN': '151', 'Todd County, MN': '153', 'Traverse County, MN': '155', 'Wabasha County, MN': '157', 'Wadena County, MN': '159', 'Waseca County, MN': '161', 'Washington County, MN': '163', 'Watonwan County, MN': '165', 'Wilkin County, MN': '167', 'Winona County, MN': '169', 'Wright County, MN': '171', 'Yellow Medicine County, MN': '173'}, '28': { 'Adams County, MS': '001', 'Alcorn County, MS': '003', 'Amite County, MS': '005', 'Attala County, MS': '007', 'Benton County, MS': '009', 'Bolivar County, MS': '011', 'Calhoun County, MS': '013', 'Carroll County, MS': '015', 'Chickasaw County, MS': '017', 'Choctaw County, MS': '019', 'Claiborne County, MS': '021', 'Clarke County, MS': '023', 'Clay County, MS': '025', 'Coahoma County, MS': '027', 'Copiah County, MS': '029', 'Covington County, MS': '031', 'DeSoto County, MS': '033', 'Forrest County, MS': '035', 'Franklin County, MS': '037', 'George County, MS': '039', 'Greene County, MS': '041', 'Grenada County, MS': '043', 'Hancock County, MS': '045', 'Harrison County, MS': '047', 'Hinds County, MS': '049', 'Holmes County, MS': '051', 'Humphreys County, MS': '053', 'Issaquena County, MS': '055', 'Itawamba County, MS': '057', 'Jackson County, MS': '059', 'Jasper County, MS': '061', 'Jefferson County, MS': '063', 'Jefferson Davis County, MS': '065', 'Jones County, MS': '067', 'Kemper County, MS': '069', 'Lafayette County, MS': '071', 'Lamar County, MS': '073', 'Lauderdale County, MS': '075', 'Lawrence County, MS': '077', 'Leake County, MS': '079', 'Lee County, MS': '081', 'Leflore County, MS': '083', 'Lincoln County, MS': '085', 'Lowndes County, MS': '087', 'Madison County, MS': '089', 'Marion County, MS': '091', 'Marshall County, MS': '093', 'Monroe County, MS': '095', 'Montgomery County, MS': '097', 'Neshoba County, MS': '099', 'Newton County, MS': '101', 'Noxubee County, MS': '103', 'Oktibbeha County, MS': '105', 'Panola County, MS': '107', 'Pearl River County, MS': '109', 'Perry County, MS': '111', 'Pike County, MS': '113', 'Pontotoc County, MS': '115', 'Prentiss County, MS': '117', 'Quitman County, MS': '119', 'Rankin County, MS': '121', 'Scott County, MS': '123', 'Sharkey County, MS': '125', 'Simpson County, MS': '127', 'Smith County, MS': '129', 'Stone County, MS': '131', 'Sunflower County, MS': '133', 'Tallahatchie County, MS': '135', 'Tate County, MS': '137', 'Tippah County, MS': '139', 'Tishomingo County, MS': '141', 'Tunica County, MS': '143', 'Union County, MS': '145', 'Walthall County, MS': '147', 'Warren County, MS': '149', 'Washington County, MS': '151', 'Wayne County, MS': '153', 'Webster County, MS': '155', 'Wilkinson County, MS': '157', 'Winston County, MS': '159', 'Yalobusha County, MS': '161', 'Yazoo County, MS': '163'}, '29': { 'Adair County, MO': '001', 'Andrew County, MO': '003', 'Atchison County, MO': '005', 'Audrain County, MO': '007', 'Barry County, MO': '009', 'Barton County, MO': '011', 'Bates County, MO': '013', 'Benton County, MO': '015', 'Bollinger County, MO': '017', 'Boone County, MO': '019', 'Buchanan County, MO': '021', 'Butler County, MO': '023', 'Caldwell County, MO': '025', 'Callaway County, MO': '027', 'Camden County, MO': '029', 'Cape Girardeau County, MO': '031', 'Carroll County, MO': '033', 'Carter County, MO': '035', 'Cass County, MO': '037', 'Cedar County, MO': '039', 'Chariton County, MO': '041', 'Christian County, MO': '043', 'Clark County, MO': '045', 'Clay County, MO': '047', 'Clinton County, MO': '049', 'Cole County, MO': '051', 'Cooper County, MO': '053', 'Crawford County, MO': '055', 'Dade County, MO': '057', 'Dallas County, MO': '059', 'Daviess County, MO': '061', 'DeKalb County, MO': '063', 'Dent County, MO': '065', 'Douglas County, MO': '067', 'Dunklin County, MO': '069', 'Franklin County, MO': '071', 'Gasconade County, MO': '073', 'Gentry County, MO': '075', 'Greene County, MO': '077', 'Grundy County, MO': '079', 'Harrison County, MO': '081', 'Henry County, MO': '083', 'Hickory County, MO': '085', 'Holt County, MO': '087', 'Howard County, MO': '089', 'Howell County, MO': '091', 'Iron County, MO': '093', 'Jackson County, MO': '095', 'Jasper County, MO': '097', 'Jefferson County, MO': '099', 'Johnson County, MO': '101', 'Knox County, MO': '103', 'Laclede County, MO': '105', 'Lafayette County, MO': '107', 'Lawrence County, MO': '109', 'Lewis County, MO': '111', 'Lincoln County, MO': '113', 'Linn County, MO': '115', 'Livingston County, MO': '117', 'Macon County, MO': '121', 'Madison County, MO': '123', 'Maries County, MO': '125', 'Marion County, MO': '127', 'McDonald County, MO': '119', 'Mercer County, MO': '129', 'Miller County, MO': '131', 'Mississippi County, MO': '133', 'Moniteau County, MO': '135', 'Monroe County, MO': '137', 'Montgomery County, MO': '139', 'Morgan County, MO': '141', 'New Madrid County, MO': '143', 'Newton County, MO': '145', 'Nodaway County, MO': '147', 'Oregon County, MO': '149', 'Osage County, MO': '151', 'Ozark County, MO': '153', 'Pemiscot County, MO': '155', 'Perry County, MO': '157', 'Pettis County, MO': '159', 'Phelps County, MO': '161', 'Pike County, MO': '163', 'Platte County, MO': '165', 'Polk County, MO': '167', 'Pulaski County, MO': '169', 'Putnam County, MO': '171', 'Ralls County, MO': '173', 'Randolph County, MO': '175', 'Ray County, MO': '177', 'Reynolds County, MO': '179', 'Ripley County, MO': '181', 'Saline County, MO': '195', 'Schuyler County, MO': '197', 'Scotland County, MO': '199', 'Scott County, MO': '201', 'Shannon County, MO': '203', 'Shelby County, MO': '205', 'St. Charles County, MO': '183', 'St. Clair County, MO': '185', 'St. Francois County, MO': '187', 'St. Louis County, MO': '189', 'St. Louis city, MO': '510', 'Ste. Genevieve County, MO': '186', 'Stoddard County, MO': '207', 'Stone County, MO': '209', 'Sullivan County, MO': '211', 'Taney County, MO': '213', 'Texas County, MO': '215', 'Vernon County, MO': '217', 'Warren County, MO': '219', 'Washington County, MO': '221', 'Wayne County, MO': '223', 'Webster County, MO': '225', 'Worth County, MO': '227', 'Wright County, MO': '229'}, '30': { 'Beaverhead County, MT': '001', 'Big Horn County, MT': '003', 'Blaine County, MT': '005', 'Broadwater County, MT': '007', 'Carbon County, MT': '009', 'Carter County, MT': '011', 'Cascade County, MT': '013', 'Chouteau County, MT': '015', 'Custer County, MT': '017', 'Daniels County, MT': '019', 'Dawson County, MT': '021', 'Deer Lodge County, MT': '023', 'Fallon County, MT': '025', 'Fergus County, MT': '027', 'Flathead County, MT': '029', 'Gallatin County, MT': '031', 'Garfield County, MT': '033', 'Glacier County, MT': '035', 'Golden Valley County, MT': '037', 'Granite County, MT': '039', 'Hill County, MT': '041', 'Jefferson County, MT': '043', 'Judith Basin County, MT': '045', 'Lake County, MT': '047', 'Lewis and Clark County, MT': '049', 'Liberty County, MT': '051', 'Lincoln County, MT': '053', 'Madison County, MT': '057', 'McCone County, MT': '055', 'Meagher County, MT': '059', 'Mineral County, MT': '061', 'Missoula County, MT': '063', 'Musselshell County, MT': '065', 'Park County, MT': '067', 'Petroleum County, MT': '069', 'Phillips County, MT': '071', 'Pondera County, MT': '073', 'Powder River County, MT': '075', 'Powell County, MT': '077', 'Prairie County, MT': '079', 'Ravalli County, MT': '081', 'Richland County, MT': '083', 'Roosevelt County, MT': '085', 'Rosebud County, MT': '087', 'Sanders County, MT': '089', 'Sheridan County, MT': '091', 'Silver Bow County, MT': '093', 'Stillwater County, MT': '095', 'Sweet Grass County, MT': '097', 'Teton County, MT': '099', 'Toole County, MT': '101', 'Treasure County, MT': '103', 'Valley County, MT': '105', 'Wheatland County, MT': '107', 'Wibaux County, MT': '109', 'Yellowstone County, MT': '111'}, '31': { 'Adams County, NE': '001', 'Antelope County, NE': '003', 'Arthur County, NE': '005', 'Banner County, NE': '007', 'Blaine County, NE': '009', 'Boone County, NE': '011', 'Box Butte County, NE': '013', 'Boyd County, NE': '015', 'Brown County, NE': '017', 'Buffalo County, NE': '019', 'Burt County, NE': '021', 'Butler County, NE': '023', 'Cass County, NE': '025', 'Cedar County, NE': '027', 'Chase County, NE': '029', 'Cherry County, NE': '031', 'Cheyenne County, NE': '033', 'Clay County, NE': '035', 'Colfax County, NE': '037', 'Cuming County, NE': '039', 'Custer County, NE': '041', 'Dakota County, NE': '043', 'Dawes County, NE': '045', 'Dawson County, NE': '047', 'Deuel County, NE': '049', 'Dixon County, NE': '051', 'Dodge County, NE': '053', 'Douglas County, NE': '055', 'Dundy County, NE': '057', 'Fillmore County, NE': '059', 'Franklin County, NE': '061', 'Frontier County, NE': '063', 'Furnas County, NE': '065', 'Gage County, NE': '067', 'Garden County, NE': '069', 'Garfield County, NE': '071', 'Gosper County, NE': '073', 'Grant County, NE': '075', 'Greeley County, NE': '077', 'Hall County, NE': '079', 'Hamilton County, NE': '081', 'Harlan County, NE': '083', 'Hayes County, NE': '085', 'Hitchcock County, NE': '087', 'Holt County, NE': '089', 'Hooker County, NE': '091', 'Howard County, NE': '093', 'Jefferson County, NE': '095', 'Johnson County, NE': '097', 'Kearney County, NE': '099', 'Keith County, NE': '101', 'Keya Paha County, NE': '103', 'Kimball County, NE': '105', 'Knox County, NE': '107', 'Lancaster County, NE': '109', 'Lincoln County, NE': '111', 'Logan County, NE': '113', 'Loup County, NE': '115', 'Madison County, NE': '119', 'McPherson County, NE': '117', 'Merrick County, NE': '121', 'Morrill County, NE': '123', 'Nance County, NE': '125', 'Nemaha County, NE': '127', 'Nuckolls County, NE': '129', 'Otoe County, NE': '131', 'Pawnee County, NE': '133', 'Perkins County, NE': '135', 'Phelps County, NE': '137', 'Pierce County, NE': '139', 'Platte County, NE': '141', 'Polk County, NE': '143', 'Red Willow County, NE': '145', 'Richardson County, NE': '147', 'Rock County, NE': '149', 'Saline County, NE': '151', 'Sarpy County, NE': '153', 'Saunders County, NE': '155', 'Scotts Bluff County, NE': '157', 'Seward County, NE': '159', 'Sheridan County, NE': '161', 'Sherman County, NE': '163', 'Sioux County, NE': '165', 'Stanton County, NE': '167', 'Thayer County, NE': '169', 'Thomas County, NE': '171', 'Thurston County, NE': '173', 'Valley County, NE': '175', 'Washington County, NE': '177', 'Wayne County, NE': '179', 'Webster County, NE': '181', 'Wheeler County, NE': '183', 'York County, NE': '185'}, '32': { 'Carson City, NV': '510', 'Churchill County, NV': '001', 'Clark County, NV': '003', 'Douglas County, NV': '005', 'Elko County, NV': '007', 'Esmeralda County, NV': '009', 'Eureka County, NV': '011', 'Humboldt County, NV': '013', 'Lander County, NV': '015', 'Lincoln County, NV': '017', 'Lyon County, NV': '019', 'Mineral County, NV': '021', 'Nye County, NV': '023', 'Pershing County, NV': '027', 'Storey County, NV': '029', 'Washoe County, NV': '031', 'White Pine County, NV': '033'}, '33': { 'Belknap County, NH': '001', 'Carroll County, NH': '003', 'Cheshire County, NH': '005', 'Coos County, NH': '007', 'Grafton County, NH': '009', 'Hillsborough County, NH': '011', 'Merrimack County, NH': '013', 'Rockingham County, NH': '015', 'Strafford County, NH': '017', 'Sullivan County, NH': '019'}, '34': { 'Atlantic County, NJ': '001', 'Bergen County, NJ': '003', 'Burlington County, NJ': '005', 'Camden County, NJ': '007', 'Cape May County, NJ': '009', 'Cumberland County, NJ': '011', 'Essex County, NJ': '013', 'Gloucester County, NJ': '015', 'Hudson County, NJ': '017', 'Hunterdon County, NJ': '019', 'Mercer County, NJ': '021', 'Middlesex County, NJ': '023', 'Monmouth County, NJ': '025', 'Morris County, NJ': '027', 'Ocean County, NJ': '029', 'Passaic County, NJ': '031', 'Salem County, NJ': '033', 'Somerset County, NJ': '035', 'Sussex County, NJ': '037', 'Union County, NJ': '039', 'Warren County, NJ': '041'}, '35': { 'Bernalillo County, NM': '001', 'Catron County, NM': '003', 'Chaves County, NM': '005', 'Cibola County, NM': '006', 'Colfax County, NM': '007', 'Curry County, NM': '009', 'DeBaca County, NM': '011', 'Dona Ana County, NM': '013', 'Eddy County, NM': '015', 'Grant County, NM': '017', 'Guadalupe County, NM': '019', 'Harding County, NM': '021', 'Hidalgo County, NM': '023', 'Lea County, NM': '025', 'Lincoln County, NM': '027', 'Los Alamos County, NM': '028', 'Luna County, NM': '029', 'McKinley County, NM': '031', 'Mora County, NM': '033', 'Otero County, NM': '035', 'Quay County, NM': '037', 'Rio Arriba County, NM': '039', 'Roosevelt County, NM': '041', 'San Juan County, NM': '045', 'San Miguel County, NM': '047', 'Sandoval County, NM': '043', 'Santa Fe County, NM': '049', 'Sierra County, NM': '051', 'Socorro County, NM': '053', 'Taos County, NM': '055', 'Torrance County, NM': '057', 'Union County, NM': '059', 'Valencia County, NM': '061'}, '36': { 'Albany County, NY': '001', 'Allegany County, NY': '003', 'Bronx County, NY': '005', 'Broome County, NY': '007', 'Cattaraugus County, NY': '009', 'Cayuga County, NY': '011', 'Chautauqua County, NY': '013', 'Chemung County, NY': '015', 'Chenango County, NY': '017', 'Clinton County, NY': '019', 'Columbia County, NY': '021', 'Cortland County, NY': '023', 'Delaware County, NY': '025', 'Dutchess County, NY': '027', 'Erie County, NY': '029', 'Essex County, NY': '031', 'Franklin County, NY': '033', 'Fulton County, NY': '035', 'Genesee County, NY': '037', 'Greene County, NY': '039', 'Hamilton County, NY': '041', 'Herkimer County, NY': '043', 'Jefferson County, NY': '045', 'Kings County, NY': '047', 'Lewis County, NY': '049', 'Livingston County, NY': '051', 'Madison County, NY': '053', 'Monroe County, NY': '055', 'Montgomery County, NY': '057', 'Nassau County, NY': '059', 'New York County, NY': '061', 'Niagara County, NY': '063', 'Oneida County, NY': '065', 'Onondaga County, NY': '067', 'Ontario County, NY': '069', 'Orange County, NY': '071', 'Orleans County, NY': '073', 'Oswego County, NY': '075', 'Otsego County, NY': '077', 'Putnam County, NY': '079', 'Queens County, NY': '081', 'Rensselaer County, NY': '083', 'Richmond County, NY': '085', 'Rockland County, NY': '087', 'Saratoga County, NY': '091', 'Schenectady County, NY': '093', 'Schoharie County, NY': '095', 'Schuyler County, NY': '097', 'Seneca County, NY': '099', 'St. Lawrence County, NY': '089', 'Steuben County, NY': '101', 'Suffolk County, NY': '103', 'Sullivan County, NY': '105', 'Tioga County, NY': '107', 'Tompkins County, NY': '109', 'Ulster County, NY': '111', 'Warren County, NY': '113', 'Washington County, NY': '115', 'Wayne County, NY': '117', 'Westchester County, NY': '119', 'Wyoming County, NY': '121', 'Yates County, NY': '123'}, '37': { 'Alamance County, NC': '001', 'Alexander County, NC': '003', 'Alleghany County, NC': '005', 'Anson County, NC': '007', 'Ashe County, NC': '009', 'Avery County, NC': '011', 'Beaufort County, NC': '013', 'Bertie County, NC': '015', 'Bladen County, NC': '017', 'Brunswick County, NC': '019', 'Buncombe County, NC': '021', 'Burke County, NC': '023', 'Cabarrus County, NC': '025', 'Caldwell County, NC': '027', 'Camden County, NC': '029', 'Carteret County, NC': '031', 'Caswell County, NC': '033', 'Catawba County, NC': '035', 'Chatham County, NC': '037', 'Cherokee County, NC': '039', 'Chowan County, NC': '041', 'Clay County, NC': '043', 'Cleveland County, NC': '045', 'Columbus County, NC': '047', 'Craven County, NC': '049', 'Cumberland County, NC': '051', 'Currituck County, NC': '053', 'Dare County, NC': '055', 'Davidson County, NC': '057', 'Davie County, NC': '059', 'Duplin County, NC': '061', 'Durham County, NC': '063', 'Edgecombe County, NC': '065', 'Forsyth County, NC': '067', 'Franklin County, NC': '069', 'Gaston County, NC': '071', 'Gates County, NC': '073', 'Graham County, NC': '075', 'Granville County, NC': '077', 'Greene County, NC': '079', 'Guilford County, NC': '081', 'Halifax County, NC': '083', 'Harnett County, NC': '085', 'Haywood County, NC': '087', 'Henderson County, NC': '089', 'Hertford County, NC': '091', 'Hoke County, NC': '093', 'Hyde County, NC': '095', 'Iredell County, NC': '097', 'Jackson County, NC': '099', 'Johnston County, NC': '101', 'Jones County, NC': '103', 'Lee County, NC': '105', 'Lenoir County, NC': '107', 'Lincoln County, NC': '109', 'Macon County, NC': '113', 'Madison County, NC': '115', 'Martin County, NC': '117', 'McDowell County, NC': '111', 'Mecklenburg County, NC': '119', 'Mitchell County, NC': '121', 'Montgomery County, NC': '123', 'Moore County, NC': '125', 'Nash County, NC': '127', 'New Hanover County, NC': '129', 'Northampton County, NC': '131', 'Onslow County, NC': '133', 'Orange County, NC': '135', 'Pamlico County, NC': '137', 'Pasquotank County, NC': '139', 'Pender County, NC': '141', 'Perquimans County, NC': '143', 'Person County, NC': '145', 'Pitt County, NC': '147', 'Polk County, NC': '149', 'Randolph County, NC': '151', 'Richmond County, NC': '153', 'Robeson County, NC': '155', 'Rockingham County, NC': '157', 'Rowan County, NC': '159', 'Rutherford County, NC': '161', 'Sampson County, NC': '163', 'Scotland County, NC': '165', 'Stanly County, NC': '167', 'Stokes County, NC': '169', 'Surry County, NC': '171', 'Swain County, NC': '173', 'Transylvania County, NC': '175', 'Tyrrell County, NC': '177', 'Union County, NC': '179', 'Vance County, NC': '181', 'Wake County, NC': '183', 'Warren County, NC': '185', 'Washington County, NC': '187', 'Watauga County, NC': '189', 'Wayne County, NC': '191', 'Wilkes County, NC': '193', 'Wilson County, NC': '195', 'Yadkin County, NC': '197', 'Yancey County, NC': '199'}, '38': { 'Adams County, ND': '001', 'Barnes County, ND': '003', 'Benson County, ND': '005', 'Billings County, ND': '007', 'Bottineau County, ND': '009', 'Bowman County, ND': '011', 'Burke County, ND': '013', 'Burleigh County, ND': '015', 'Cass County, ND': '017', 'Cavalier County, ND': '019', 'Dickey County, ND': '021', 'Divide County, ND': '023', 'Dunn County, ND': '025', 'Eddy County, ND': '027', 'Emmons County, ND': '029', 'Foster County, ND': '031', 'Golden Valley County, ND': '033', 'Grand Forks County, ND': '035', 'Grant County, ND': '037', 'Griggs County, ND': '039', 'Hettinger County, ND': '041', 'Kidder County, ND': '043', 'LaMoure County, ND': '045', 'Logan County, ND': '047', 'McHenry County, ND': '049', 'McIntosh County, ND': '051', 'McKenzie County, ND': '053', 'McLean County, ND': '055', 'Mercer County, ND': '057', 'Morton County, ND': '059', 'Mountrail County, ND': '061', 'Nelson County, ND': '063', 'Oliver County, ND': '065', 'Pembina County, ND': '067', 'Pierce County, ND': '069', 'Ramsey County, ND': '071', 'Ransom County, ND': '073', 'Renville County, ND': '075', 'Richland County, ND': '077', 'Rolette County, ND': '079', 'Sargent County, ND': '081', 'Sheridan County, ND': '083', 'Sioux County, ND': '085', 'Slope County, ND': '087', 'Stark County, ND': '089', 'Steele County, ND': '091', 'Stutsman County, ND': '093', 'Towner County, ND': '095', 'Traill County, ND': '097', 'Walsh County, ND': '099', 'Ward County, ND': '101', 'Wells County, ND': '103', 'Williams County, ND': '105'}, '39': { 'Adams County, OH': '001', 'Allen County, OH': '003', 'Ashland County, OH': '005', 'Ashtabula County, OH': '007', 'Athens County, OH': '009', 'Auglaize County, OH': '011', 'Belmont County, OH': '013', 'Brown County, OH': '015', 'Butler County, OH': '017', 'Carroll County, OH': '019', 'Champaign County, OH': '021', 'Clark County, OH': '023', 'Clermont County, OH': '025', 'Clinton County, OH': '027', 'Columbiana County, OH': '029', 'Coshocton County, OH': '031', 'Crawford County, OH': '033', 'Cuyahoga County, OH': '035', 'Darke County, OH': '037', 'Defiance County, OH': '039', 'Delaware County, OH': '041', 'Erie County, OH': '043', 'Fairfield County, OH': '045', 'Fayette County, OH': '047', 'Franklin County, OH': '049', 'Fulton County, OH': '051', 'Gallia County, OH': '053', 'Geauga County, OH': '055', 'Greene County, OH': '057', 'Guernsey County, OH': '059', 'Hamilton County, OH': '061', 'Hancock County, OH': '063', 'Hardin County, OH': '065', 'Harrison County, OH': '067', 'Henry County, OH': '069', 'Highland County, OH': '071', 'Hocking County, OH': '073', 'Holmes County, OH': '075', 'Huron County, OH': '077', 'Jackson County, OH': '079', 'Jefferson County, OH': '081', 'Knox County, OH': '083', 'Lake County, OH': '085', 'Lawrence County, OH': '087', 'Licking County, OH': '089', 'Logan County, OH': '091', 'Lorain County, OH': '093', 'Lucas County, OH': '095', 'Madison County, OH': '097', 'Mahoning County, OH': '099', 'Marion County, OH': '101', 'Medina County, OH': '103', 'Meigs County, OH': '105', 'Mercer County, OH': '107', 'Miami County, OH': '109', 'Monroe County, OH': '111', 'Montgomery County, OH': '113', 'Morgan County, OH': '115', 'Morrow County, OH': '117', 'Muskingum County, OH': '119', 'Noble County, OH': '121', 'Ottawa County, OH': '123', 'Paulding County, OH': '125', 'Perry County, OH': '127', 'Pickaway County, OH': '129', 'Pike County, OH': '131', 'Portage County, OH': '133', 'Preble County, OH': '135', 'Putnam County, OH': '137', 'Richland County, OH': '139', 'Ross County, OH': '141', 'Sandusky County, OH': '143', 'Scioto County, OH': '145', 'Seneca County, OH': '147', 'Shelby County, OH': '149', 'Stark County, OH': '151', 'Summit County, OH': '153', 'Trumbull County, OH': '155', 'Tuscarawas County, OH': '157', 'Union County, OH': '159', 'Van Wert County, OH': '161', 'Vinton County, OH': '163', 'Warren County, OH': '165', 'Washington County, OH': '167', 'Wayne County, OH': '169', 'Williams County, OH': '171', 'Wood County, OH': '173', 'Wyandot County, OH': '175'}, '40': { 'Adair County, OK': '001', 'Alfalfa County, OK': '003', 'Atoka County, OK': '005', 'Beaver County, OK': '007', 'Beckham County, OK': '009', 'Blaine County, OK': '011', 'Bryan County, OK': '013', 'Caddo County, OK': '015', 'Canadian County, OK': '017', 'Carter County, OK': '019', 'Cherokee County, OK': '021', 'Choctaw County, OK': '023', 'Cimarron County, OK': '025', 'Cleveland County, OK': '027', 'Coal County, OK': '029', 'Comanche County, OK': '031', 'Cotton County, OK': '033', 'Craig County, OK': '035', 'Creek County, OK': '037', 'Custer County, OK': '039', 'Delaware County, OK': '041', 'Dewey County, OK': '043', 'Ellis County, OK': '045', 'Garfield County, OK': '047', 'Garvin County, OK': '049', 'Grady County, OK': '051', 'Grant County, OK': '053', 'Greer County, OK': '055', 'Harmon County, OK': '057', 'Harper County, OK': '059', 'Haskell County, OK': '061', 'Hughes County, OK': '063', 'Jackson County, OK': '065', 'Jefferson County, OK': '067', 'Johnston County, OK': '069', 'Kay County, OK': '071', 'Kingfisher County, OK': '073', 'Kiowa County, OK': '075', 'Latimer County, OK': '077', 'Le Flore County, OK': '079', 'Lincoln County, OK': '081', 'Logan County, OK': '083', 'Love County, OK': '085', 'Major County, OK': '093', 'Marshall County, OK': '095', 'Mayes County, OK': '097', 'McClain County, OK': '087', 'McCurtain County, OK': '089', 'McIntosh County, OK': '091', 'Murray County, OK': '099', 'Muskogee County, OK': '101', 'Noble County, OK': '103', 'Nowata County, OK': '105', 'Okfuskee County, OK': '107', 'Oklahoma County, OK': '109', 'Okmulgee County, OK': '111', 'Osage County, OK': '113', 'Ottawa County, OK': '115', 'Pawnee County, OK': '117', 'Payne County, OK': '119', 'Pittsburg County, OK': '121', 'Pontotoc County, OK': '123', 'Pottawatomie County, OK': '125', 'Pushmataha County, OK': '127', 'Roger Mills County, OK': '129', 'Rogers County, OK': '131', 'Seminole County, OK': '133', 'Sequoyah County, OK': '135', 'Stephens County, OK': '137', 'Texas County, OK': '139', 'Tillman County, OK': '141', 'Tulsa County, OK': '143', 'Wagoner County, OK': '145', 'Washington County, OK': '147', 'Washita County, OK': '149', 'Woods County, OK': '151', 'Woodward County, OK': '153'}, '41': { 'Baker County, OR': '001', 'Benton County, OR': '003', 'Clackamas County, OR': '005', 'Clatsop County, OR': '007', 'Columbia County, OR': '009', 'Coos County, OR': '011', 'Crook County, OR': '013', 'Curry County, OR': '015', 'Deschutes County, OR': '017', 'Douglas County, OR': '019', 'Gilliam County, OR': '021', 'Grant County, OR': '023', 'Harney County, OR': '025', 'Hood River County, OR': '027', 'Jackson County, OR': '029', 'Jefferson County, OR': '031', 'Josephine County, OR': '033', 'Klamath County, OR': '035', 'Lake County, OR': '037', 'Lane County, OR': '039', 'Lincoln County, OR': '041', 'Linn County, OR': '043', 'Malheur County, OR': '045', 'Marion County, OR': '047', 'Morrow County, OR': '049', 'Multnomah County, OR': '051', 'Polk County, OR': '053', 'Sherman County, OR': '055', 'Tillamook County, OR': '057', 'Umatilla County, OR': '059', 'Union County, OR': '061', 'Wallowa County, OR': '063', 'Wasco County, OR': '065', 'Washington County, OR': '067', 'Wheeler County, OR': '069', 'Yamhill County, OR': '071'}, '42': { 'Adams County, PA': '001', 'Allegheny County, PA': '003', 'Armstrong County, PA': '005', 'Beaver County, PA': '007', 'Bedford County, PA': '009', 'Berks County, PA': '011', 'Blair County, PA': '013', 'Bradford County, PA': '015', 'Bucks County, PA': '017', 'Butler County, PA': '019', 'Cambria County, PA': '021', 'Cameron County, PA': '023', 'Carbon County, PA': '025', 'Centre County, PA': '027', 'Chester County, PA': '029', 'Clarion County, PA': '031', 'Clearfield County, PA': '033', 'Clinton County, PA': '035', 'Columbia County, PA': '037', 'Crawford County, PA': '039', 'Cumberland County, PA': '041', 'Dauphin County, PA': '043', 'Delaware County, PA': '045', 'Elk County, PA': '047', 'Erie County, PA': '049', 'Fayette County, PA': '051', 'Forest County, PA': '053', 'Franklin County, PA': '055', 'Fulton County, PA': '057', 'Greene County, PA': '059', 'Huntingdon County, PA': '061', 'Indiana County, PA': '063', 'Jefferson County, PA': '065', 'Juniata County, PA': '067', 'Lackawanna County, PA': '069', 'Lancaster County, PA': '071', 'Lawrence County, PA': '073', 'Lebanon County, PA': '075', 'Lehigh County, PA': '077', 'Luzerne County, PA': '079', 'Lycoming County, PA': '081', 'McKean County, PA': '083', 'Mercer County, PA': '085', 'Mifflin County, PA': '087', 'Monroe County, PA': '089', 'Montgomery County, PA': '091', 'Montour County, PA': '093', 'Northampton County, PA': '095', 'Northumberland County, PA': '097', 'Perry County, PA': '099', 'Philadelphia County/city, PA': '101', 'Pike County, PA': '103', 'Potter County, PA': '105', 'Schuylkill County, PA': '107', 'Snyder County, PA': '109', 'Somerset County, PA': '111', 'Sullivan County, PA': '113', 'Susquehanna County, PA': '115', 'Tioga County, PA': '117', 'Union County, PA': '119', 'Venango County, PA': '121', 'Warren County, PA': '123', 'Washington County, PA': '125', 'Wayne County, PA': '127', 'Westmoreland County, PA': '129', 'Wyoming County, PA': '131', 'York County, PA': '133'}, '44': { 'Bristol County, RI': '001', 'Kent County, RI': '003', 'Newport County, RI': '005', 'Providence County, RI': '007', 'Washington County, RI': '009'}, '45': { 'Abbeville County, SC': '001', 'Aiken County, SC': '003', 'Allendale County, SC': '005', 'Anderson County, SC': '007', 'Bamberg County, SC': '009', 'Barnwell County, SC': '011', 'Beaufort County, SC': '013', 'Berkeley County, SC': '015', 'Calhoun County, SC': '017', 'Charleston County, SC': '019', 'Cherokee County, SC': '021', 'Chester County, SC': '023', 'Chesterfield County, SC': '025', 'Clarendon County, SC': '027', 'Colleton County, SC': '029', 'Darlington County, SC': '031', 'Dillon County, SC': '033', 'Dorchester County, SC': '035', 'Edgefield County, SC': '037', 'Fairfield County, SC': '039', 'Florence County, SC': '041', 'Georgetown County, SC': '043', 'Greenville County, SC': '045', 'Greenwood County, SC': '047', 'Hampton County, SC': '049', 'Horry County, SC': '051', 'Jasper County, SC': '053', 'Kershaw County, SC': '055', 'Lancaster County, SC': '057', 'Laurens County, SC': '059', 'Lee County, SC': '061', 'Lexington County, SC': '063', 'Marion County, SC': '067', 'Marlboro County, SC': '069', 'McCormick County, SC': '065', 'Newberry County, SC': '071', 'Oconee County, SC': '073', 'Orangeburg County, SC': '075', 'Pickens County, SC': '077', 'Richland County, SC': '079', 'Saluda County, SC': '081', 'Spartanburg County, SC': '083', 'Sumter County, SC': '085', 'Union County, SC': '087', 'Williamsburg County, SC': '089', 'York County, SC': '091'}, '46': { 'Aurora County, SD': '003', 'Beadle County, SD': '005', 'Bennett County, SD': '007', 'Bon Homme County, SD': '009', 'Brookings County, SD': '011', 'Brown County, SD': '013', 'Brule County, SD': '015', 'Buffalo County, SD': '017', 'Butte County, SD': '019', 'Campbell County, SD': '021', 'Charles Mix County, SD': '023', 'Clark County, SD': '025', 'Clay County, SD': '027', 'Codington County, SD': '029', 'Corson County, SD': '031', 'Custer County, SD': '033', 'Davison County, SD': '035', 'Day County, SD': '037', 'Deuel County, SD': '039', 'Dewey County, SD': '041', 'Douglas County, SD': '043', 'Edmunds County, SD': '045', 'Fall River County, SD': '047', 'Faulk County, SD': '049', 'Grant County, SD': '051', 'Gregory County, SD': '053', 'Haakon County, SD': '055', 'Hamlin County, SD': '057', 'Hand County, SD': '059', 'Hanson County, SD': '061', 'Harding County, SD': '063', 'Hughes County, SD': '065', 'Hutchinson County, SD': '067', 'Hyde County, SD': '069', 'Jackson County, SD': '071', 'Jerauld County, SD': '073', 'Jones County, SD': '075', 'Kingsbury County, SD': '077', 'Lake County, SD': '079', 'Lawrence County, SD': '081', 'Lincoln County, SD': '083', 'Lyman County, SD': '085', 'Marshall County, SD': '091', 'McCook County, SD': '087', 'McPherson County, SD': '089', 'Meade County, SD': '093', 'Mellette County, SD': '095', 'Miner County, SD': '097', 'Minnehaha County, SD': '099', 'Moody County, SD': '101', 'Pennington County, SD': '103', 'Perkins County, SD': '105', 'Potter County, SD': '107', 'Roberts County, SD': '109', 'Sanborn County, SD': '111', 'Shannon County, SD': '113', 'Spink County, SD': '115', 'Stanley County, SD': '117', 'Sully County, SD': '119', 'Todd County, SD': '121', 'Tripp County, SD': '123', 'Turner County, SD': '125', 'Union County, SD': '127', 'Walworth County, SD': '129', 'Yankton County, SD': '135', 'Ziebach County, SD': '137'}, '47': { 'Anderson County, TN': '001', 'Bedford County, TN': '003', 'Benton County, TN': '005', 'Bledsoe County, TN': '007', 'Blount County, TN': '009', 'Bradley County, TN': '011', 'Campbell County, TN': '013', 'Cannon County, TN': '015', 'Carroll County, TN': '017', 'Carter County, TN': '019', 'Cheatham County, TN': '021', 'Chester County, TN': '023', 'Claiborne County, TN': '025', 'Clay County, TN': '027', 'Cocke County, TN': '029', 'Coffee County, TN': '031', 'Crockett County, TN': '033', 'Cumberland County, TN': '035', 'Davidson County, TN': '037', 'DeKalb County, TN': '041', 'Decatur County, TN': '039', 'Dickson County, TN': '043', 'Dyer County, TN': '045', 'Fayette County, TN': '047', 'Fentress County, TN': '049', 'Franklin County, TN': '051', 'Gibson County, TN': '053', 'Giles County, TN': '055', 'Grainger County, TN': '057', 'Greene County, TN': '059', 'Grundy County, TN': '061', 'Hamblen County, TN': '063', 'Hamilton County, TN': '065', 'Hancock County, TN': '067', 'Hardeman County, TN': '069', 'Hardin County, TN': '071', 'Hawkins County, TN': '073', 'Haywood County, TN': '075', 'Henderson County, TN': '077', 'Henry County, TN': '079', 'Hickman County, TN': '081', 'Houston County, TN': '083', 'Humphreys County, TN': '085', 'Jackson County, TN': '087', 'Jefferson County, TN': '089', 'Johnson County, TN': '091', 'Knox County, TN': '093', 'Lake County, TN': '095', 'Lauderdale County, TN': '097', 'Lawrence County, TN': '099', 'Lewis County, TN': '101', 'Lincoln County, TN': '103', 'Loudon County, TN': '105', 'Macon County, TN': '111', 'Madison County, TN': '113', 'Marion County, TN': '115', 'Marshall County, TN': '117', 'Maury County, TN': '119', 'McMinn County, TN': '107', 'McNairy County, TN': '109', 'Meigs County, TN': '121', 'Monroe County, TN': '123', 'Montgomery County, TN': '125', 'Moore County, TN': '127', 'Morgan County, TN': '129', 'Obion County, TN': '131', 'Overton County, TN': '133', 'Perry County, TN': '135', 'Pickett County, TN': '137', 'Polk County, TN': '139', 'Putnam County, TN': '141', 'Rhea County, TN': '143', 'Roane County, TN': '145', 'Robertson County, TN': '147', 'Rutherford County, TN': '149', 'Scott County, TN': '151', 'Sequatchie County, TN': '153', 'Sevier County, TN': '155', 'Shelby County, TN': '157', 'Smith County, TN': '159', 'Stewart County, TN': '161', 'Sullivan County, TN': '163', 'Sumner County, TN': '165', 'Tipton County, TN': '167', 'Trousdale County, TN': '169', 'Unicoi County, TN': '171', 'Union County, TN': '173', 'Van Buren County, TN': '175', 'Warren County, TN': '177', 'Washington County, TN': '179', 'Wayne County, TN': '181', 'Weakley County, TN': '183', 'White County, TN': '185', 'Williamson County, TN': '187', 'Wilson County, TN': '189'}, '48': { 'Anderson County, TX': '001', 'Andrews County, TX': '003', 'Angelina County, TX': '005', 'Aransas County, TX': '007', 'Archer County, TX': '009', 'Armstrong County, TX': '011', 'Atascosa County, TX': '013', 'Austin County, TX': '015', 'Bailey County, TX': '017', 'Bandera County, TX': '019', 'Bastrop County, TX': '021', 'Baylor County, TX': '023', 'Bee County, TX': '025', 'Bell County, TX': '027', 'Bexar County, TX': '029', 'Blanco County, TX': '031', 'Borden County, TX': '033', 'Bosque County, TX': '035', 'Bowie County, TX': '037', 'Brazoria County, TX': '039', 'Brazos County, TX': '041', 'Brewster County, TX': '043', 'Briscoe County, TX': '045', 'Brooks County, TX': '047', 'Brown County, TX': '049', 'Burleson County, TX': '051', 'Burnet County, TX': '053', 'Caldwell County, TX': '055', 'Calhoun County, TX': '057', 'Callahan County, TX': '059', 'Cameron County, TX': '061', 'Camp County, TX': '063', 'Carson County, TX': '065', 'Cass County, TX': '067', 'Castro County, TX': '069', 'Chambers County, TX': '071', 'Cherokee County, TX': '073', 'Childress County, TX': '075', 'Clay County, TX': '077', 'Cochran County, TX': '079', 'Coke County, TX': '081', 'Coleman County, TX': '083', 'Collin County, TX': '085', 'Collingsworth County, TX': '087', 'Colorado County, TX': '089', 'Comal County, TX': '091', 'Comanche County, TX': '093', 'Concho County, TX': '095', 'Cooke County, TX': '097', 'Coryell County, TX': '099', 'Cottle County, TX': '101', 'Crane County, TX': '103', 'Crockett County, TX': '105', 'Crosby County, TX': '107', 'Culberson County, TX': '109', 'Dallam County, TX': '111', 'Dallas County, TX': '113', 'Dawson County, TX': '115', 'DeWitt County, TX': '123', 'Deaf Smith County, TX': '117', 'Delta County, TX': '119', 'Denton County, TX': '121', 'Dickens County, TX': '125', 'Dimmit County, TX': '127', 'Donley County, TX': '129', 'Duval County, TX': '131', 'Eastland County, TX': '133', 'Ector County, TX': '135', 'Edwards County, TX': '137', 'El Paso County, TX': '141', 'Ellis County, TX': '139', 'Erath County, TX': '143', 'Falls County, TX': '145', 'Fannin County, TX': '147', 'Fayette County, TX': '149', 'Fisher County, TX': '151', 'Floyd County, TX': '153', 'Foard County, TX': '155', 'Fort Bend County, TX': '157', 'Franklin County, TX': '159', 'Freestone County, TX': '161', 'Frio County, TX': '163', 'Gaines County, TX': '165', 'Galveston County, TX': '167', 'Garza County, TX': '169', 'Gillespie County, TX': '171', 'Glasscock County, TX': '173', 'Goliad County, TX': '175', 'Gonzales County, TX': '177', 'Gray County, TX': '179', 'Grayson County, TX': '181', 'Gregg County, TX': '183', 'Grimes County, TX': '185', 'Guadalupe County, TX': '187', 'Hale County, TX': '189', 'Hall County, TX': '191', 'Hamilton County, TX': '193', 'Hansford County, TX': '195', 'Hardeman County, TX': '197', 'Hardin County, TX': '199', 'Harris County, TX': '201', 'Harrison County, TX': '203', 'Hartley County, TX': '205', 'Haskell County, TX': '207', 'Hays County, TX': '209', 'Hemphill County, TX': '211', 'Henderson County, TX': '213', 'Hidalgo County, TX': '215', 'Hill County, TX': '217', 'Hockley County, TX': '219', 'Hood County, TX': '221', 'Hopkins County, TX': '223', 'Houston County, TX': '225', 'Howard County, TX': '227', 'Hudspeth County, TX': '229', 'Hunt County, TX': '231', 'Hutchinson County, TX': '233', 'Irion County, TX': '235', 'Jack County, TX': '237', 'Jackson County, TX': '239', 'Jasper County, TX': '241', 'Jeff Davis County, TX': '243', 'Jefferson County, TX': '245', 'Jim Hogg County, TX': '247', 'Jim Wells County, TX': '249', 'Johnson County, TX': '251', 'Jones County, TX': '253', 'Karnes County, TX': '255', 'Kaufman County, TX': '257', 'Kendall County, TX': '259', 'Kenedy County, TX': '261', 'Kent County, TX': '263', 'Kerr County, TX': '265', 'Kimble County, TX': '267', 'King County, TX': '269', 'Kinney County, TX': '271', 'Kleberg County, TX': '273', 'Knox County, TX': '275', 'La Salle County, TX': '283', 'Lamar County, TX': '277', 'Lamb County, TX': '279', 'Lampasas County, TX': '281', 'Lavaca County, TX': '285', 'Lee County, TX': '287', 'Leon County, TX': '289', 'Liberty County, TX': '291', 'Limestone County, TX': '293', 'Lipscomb County, TX': '295', 'Live Oak County, TX': '297', 'Llano County, TX': '299', 'Loving County, TX': '301', 'Lubbock County, TX': '303', 'Lynn County, TX': '305', 'Madison County, TX': '313', 'Marion County, TX': '315', 'Martin County, TX': '317', 'Mason County, TX': '319', 'Matagorda County, TX': '321', 'Maverick County, TX': '323', 'McCulloch County, TX': '307', 'McLennan County, TX': '309', 'McMullen County, TX': '311', 'Medina County, TX': '325', 'Menard County, TX': '327', 'Midland County, TX': '329', 'Milam County, TX': '331', 'Mills County, TX': '333', 'Mitchell County, TX': '335', 'Montague County, TX': '337', 'Montgomery County, TX': '339', 'Moore County, TX': '341', 'Morris County, TX': '343', 'Motley County, TX': '345', 'Nacogdoches County, TX': '347', 'Navarro County, TX': '349', 'Newton County, TX': '351', 'Nolan County, TX': '353', 'Nueces County, TX': '355', 'Ochiltree County, TX': '357', 'Oldham County, TX': '359', 'Orange County, TX': '361', 'Palo Pinto County, TX': '363', 'Panola County, TX': '365', 'Parker County, TX': '367', 'Parmer County, TX': '369', 'Pecos County, TX': '371', 'Polk County, TX': '373', 'Potter County, TX': '375', 'Presidio County, TX': '377', 'Rains County, TX': '379', 'Randall County, TX': '381', 'Reagan County, TX': '383', 'Real County, TX': '385', 'Red River County, TX': '387', 'Reeves County, TX': '389', 'Refugio County, TX': '391', 'Roberts County, TX': '393', 'Robertson County, TX': '395', 'Rockwall County, TX': '397', 'Runnels County, TX': '399', 'Rusk County, TX': '401', 'Sabine County, TX': '403', 'San Augustine County, TX': '405', 'San Jacinto County, TX': '407', 'San Patricio County, TX': '409', 'San Saba County, TX': '411', 'Schleicher County, TX': '413', 'Scurry County, TX': '415', 'Shackelford County, TX': '417', 'Shelby County, TX': '419', 'Sherman County, TX': '421', 'Smith County, TX': '423', 'Somervell County, TX': '425', 'Starr County, TX': '427', 'Stephens County, TX': '429', 'Sterling County, TX': '431', 'Stonewall County, TX': '433', 'Sutton County, TX': '435', 'Swisher County, TX': '437', 'Tarrant County, TX': '439', 'Taylor County, TX': '441', 'Terrell County, TX': '443', 'Terry County, TX': '445', 'Throckmorton County, TX': '447', 'Titus County, TX': '449', 'Tom Green County, TX': '451', 'Travis County, TX': '453', 'Trinity County, TX': '455', 'Tyler County, TX': '457', 'Upshur County, TX': '459', 'Upton County, TX': '461', 'Uvalde County, TX': '463', 'Val Verde County, TX': '465', 'Van Zandt County, TX': '467', 'Victoria County, TX': '469', 'Walker County, TX': '471', 'Waller County, TX': '473', 'Ward County, TX': '475', 'Washington County, TX': '477', 'Webb County, TX': '479', 'Wharton County, TX': '481', 'Wheeler County, TX': '483', 'Wichita County, TX': '485', 'Wilbarger County, TX': '487', 'Willacy County, TX': '489', 'Williamson County, TX': '491', 'Wilson County, TX': '493', 'Winkler County, TX': '495', 'Wise County, TX': '497', 'Wood County, TX': '499', 'Yoakum County, TX': '501', 'Young County, TX': '503', 'Zapata County, TX': '505', 'Zavala County, TX': '507'}, '49': { 'Beaver County, UT': '001', 'Box Elder County, UT': '003', 'Cache County, UT': '005', 'Carbon County, UT': '007', 'Daggett County, UT': '009', 'Davis County, UT': '011', 'Duchesne County, UT': '013', 'Emery County, UT': '015', 'Garfield County, UT': '017', 'Grand County, UT': '019', 'Iron County, UT': '021', 'Juab County, UT': '023', 'Kane County, UT': '025', 'Millard County, UT': '027', 'Morgan County, UT': '029', 'Piute County, UT': '031', 'Rich County, UT': '033', 'Salt Lake County, UT': '035', 'San Juan County, UT': '037', 'Sanpete County, UT': '039', 'Sevier County, UT': '041', 'Summit County, UT': '043', 'Tooele County, UT': '045', 'Uintah County, UT': '047', 'Utah County, UT': '049', 'Wasatch County, UT': '051', 'Washington County, UT': '053', 'Wayne County, UT': '055', 'Weber County, UT': '057'}, '50': { 'Addison County, VT': '001', 'Bennington County, VT': '003', 'Caledonia County, VT': '005', 'Chittenden County, VT': '007', 'Essex County, VT': '009', 'Franklin County, VT': '011', 'Grand Isle County, VT': '013', 'Lamoille County, VT': '015', 'Orange County, VT': '017', 'Orleans County, VT': '019', 'Rutland County, VT': '021', 'Washington County, VT': '023', 'Windham County, VT': '025', 'Windsor County, VT': '027'}, '51': { 'Accomack County, VA': '001', 'Albemarle County, VA': '003', 'Alexandria city, VA': '510', 'Alleghany County, VA': '005', 'Amelia County, VA': '007', 'Amherst County, VA': '009', 'Appomattox County, VA': '011', 'Arlington County, VA': '013', 'Augusta County, VA': '015', 'Bath County, VA': '017', 'Bedford County, VA': '019', 'Bedford city, VA': '515', 'Bland County, VA': '021', 'Botetourt County, VA': '023', 'Bristol city, VA': '520', 'Brunswick County, VA': '025', 'Buchanan County, VA': '027', 'Buckingham County, VA': '029', 'Buena Vista city, VA': '530', 'Campbell County, VA': '031', 'Caroline County, VA': '033', 'Carroll County, VA': '035', 'Charles City County, VA': '036', 'Charlotte County, VA': '037', 'Charlottesville city, VA': '540', 'Chesapeake city, VA': '550', 'Chesterfield County, VA': '041', 'Clarke County, VA': '043', 'Colonial Heights city, VA': '570', 'Covington city, VA': '580', 'Craig County, VA': '045', 'Culpeper County, VA': '047', 'Cumberland County, VA': '049', 'Danville city, VA': '590', 'Dickenson County, VA': '051', 'Dinwiddie County, VA': '053', 'Emporia city, VA': '595', 'Essex County, VA': '057', 'Fairfax County, VA': '059', 'Fairfax city, VA': '600', 'Falls Church city, VA': '610', 'Fauquier County, VA': '061', 'Floyd County, VA': '063', 'Fluvanna County, VA': '065', 'Franklin County, VA': '067', 'Franklin city, VA': '620', 'Frederick County, VA': '069', 'Fredericksburg city, VA': '630', 'Galax city, VA': '640', 'Giles County, VA': '071', 'Gloucester County, VA': '073', 'Goochland County, VA': '075', 'Grayson County, VA': '077', 'Greene County, VA': '079', 'Greensville County, VA': '081', 'Halifax County, VA': '083', 'Hampton city, VA': '650', 'Hanover County, VA': '085', 'Harrisonburg city, VA': '660', 'Henrico County, VA': '087', 'Henry County, VA': '089', 'Highland County, VA': '091', 'Hopewell city, VA': '670', 'Isle of Wight County, VA': '093', 'James City County, VA': '095', 'King George County, VA': '099', 'King William County, VA': '101', 'King and Queen County, VA': '097', 'Lancaster County, VA': '103', 'Lee County, VA': '105', 'Lexington city, VA': '678', 'Loudoun County, VA': '107', 'Louisa County, VA': '109', 'Lunenburg County, VA': '111', 'Lynchburg city, VA': '680', 'Madison County, VA': '113', 'Manassas Park city, VA': '685', 'Manassas city, VA': '683', 'Martinsville city, VA': '690', 'Mathews County, VA': '115', 'Mecklenburg County, VA': '117', 'Middlesex County, VA': '119', 'Montgomery County, VA': '121', 'Nelson County, VA': '125', 'New Kent County, VA': '127', 'Newport News city, VA': '700', 'Norfolk city, VA': '710', 'Northampton County, VA': '131', 'Northumberland County, VA': '133', 'Norton city, VA': '720', 'Nottoway County, VA': '135', 'Orange County, VA': '137', 'Page County, VA': '139', 'Patrick County, VA': '141', 'Petersburg city, VA': '730', 'Pittsylvania County, VA': '143', 'Poquoson city, VA': '735', 'Portsmouth city, VA': '740', 'Powhatan County, VA': '145', 'Prince Edward County, VA': '147', 'Prince George County, VA': '149', 'Prince William County, VA': '153', 'Pulaski County, VA': '155', 'Radford city, VA': '750', 'Rappahannock County, VA': '157', 'Richmond County, VA': '159', 'Richmond city, VA': '760', 'Roanoke County, VA': '161', 'Roanoke city, VA': '770', 'Rockbridge County, VA': '163', 'Rockingham County, VA': '165', 'Russell County, VA': '167', 'Salem city, VA': '775', 'Scott County, VA': '169', 'Shenandoah County, VA': '171', 'Smyth County, VA': '173', 'Southampton County, VA': '175', 'Spotsylvania County, VA': '177', 'Stafford County, VA': '179', 'Staunton city, VA': '790', 'Suffolk city, VA': '800', 'Surry County, VA': '181', 'Sussex County, VA': '183', 'Tazewell County, VA': '185', 'Virginia Beach city, VA': '810', 'Warren County, VA': '187', 'Washington County, VA': '191', 'Waynesboro city, VA': '820', 'Westmoreland County, VA': '193', 'Williamsburg city, VA': '830', 'Winchester city, VA': '840', 'Wise County, VA': '195', 'Wythe County, VA': '197', 'York County, VA': '199'}, '53': { 'Adams County, WA': '001', 'Asotin County, WA': '003', 'Benton County, WA': '005', 'Chelan County, WA': '007', 'Clallam County, WA': '009', 'Clark County, WA': '011', 'Columbia County, WA': '013', 'Cowlitz County, WA': '015', 'Douglas County, WA': '017', 'Ferry County, WA': '019', 'Franklin County, WA': '021', 'Garfield County, WA': '023', 'Grant County, WA': '025', 'Grays Harbor County, WA': '027', 'Island County, WA': '029', 'Jefferson County, WA': '031', 'King County, WA': '033', 'Kitsap County, WA': '035', 'Kittitas County, WA': '037', 'Klickitat County, WA': '039', 'Lewis County, WA': '041', 'Lincoln County, WA': '043', 'Mason County, WA': '045', 'Okanogan County, WA': '047', 'Pacific County, WA': '049', 'Pend Oreille County, WA': '051', 'Pierce County, WA': '053', 'San Juan County, WA': '055', 'Skagit County, WA': '057', 'Skamania County, WA': '059', 'Snohomish County, WA': '061', 'Spokane County, WA': '063', 'Stevens County, WA': '065', 'Thurston County, WA': '067', 'Wahkiakum County, WA': '069', 'Walla Walla County, WA': '071', 'Whatcom County, WA': '073', 'Whitman County, WA': '075', 'Yakima County, WA': '077'}, '54': { 'Barbour County, WV': '001', 'Berkeley County, WV': '003', 'Boone County, WV': '005', 'Braxton County, WV': '007', 'Brooke County, WV': '009', 'Cabell County, WV': '011', 'Calhoun County, WV': '013', 'Clay County, WV': '015', 'Doddridge County, WV': '017', 'Fayette County, WV': '019', 'Gilmer County, WV': '021', 'Grant County, WV': '023', 'Greenbrier County, WV': '025', 'Hampshire County, WV': '027', 'Hancock County, WV': '029', 'Hardy County, WV': '031', 'Harrison County, WV': '033', 'Jackson County, WV': '035', 'Jefferson County, WV': '037', 'Kanawha County, WV': '039', 'Lewis County, WV': '041', 'Lincoln County, WV': '043', 'Logan County, WV': '045', 'Marion County, WV': '049', 'Marshall County, WV': '051', 'Mason County, WV': '053', 'McDowell County, WV': '047', 'Mercer County, WV': '055', 'Mineral County, WV': '057', 'Mingo County, WV': '059', 'Monongalia County, WV': '061', 'Monroe County, WV': '063', 'Morgan County, WV': '065', 'Nicholas County, WV': '067', 'Ohio County, WV': '069', 'Pendleton County, WV': '071', 'Pleasants County, WV': '073', 'Pocahontas County, WV': '075', 'Preston County, WV': '077', 'Putnam County, WV': '079', 'Raleigh County, WV': '081', 'Randolph County, WV': '083', 'Ritchie County, WV': '085', 'Roane County, WV': '087', 'Summers County, WV': '089', 'Taylor County, WV': '091', 'Tucker County, WV': '093', 'Tyler County, WV': '095', 'Upshur County, WV': '097', 'Wayne County, WV': '099', 'Webster County, WV': '101', 'Wetzel County, WV': '103', 'Wirt County, WV': '105', 'Wood County, WV': '107', 'Wyoming County, WV': '109'}, '55': { 'Adams County, WI': '001', 'Ashland County, WI': '003', 'Barron County, WI': '005', 'Bayfield County, WI': '007', 'Brown County, WI': '009', 'Buffalo County, WI': '011', 'Burnett County, WI': '013', 'Calumet County, WI': '015', 'Chippewa County, WI': '017', 'Clark County, WI': '019', 'Columbia County, WI': '021', 'Crawford County, WI': '023', 'Dane County, WI': '025', 'Dodge County, WI': '027', 'Door County, WI': '029', 'Douglas County, WI': '031', 'Dunn County, WI': '033', 'Eau Claire County, WI': '035', 'Florence County, WI': '037', 'Fond du Lac County, WI': '039', 'Forest County, WI': '041', 'Grant County, WI': '043', 'Green County, WI': '045', 'Green Lake County, WI': '047', 'Iowa County, WI': '049', 'Iron County, WI': '051', 'Jackson County, WI': '053', 'Jefferson County, WI': '055', 'Juneau County, WI': '057', 'Kenosha County, WI': '059', 'Kewaunee County, WI': '061', 'La Crosse County, WI': '063', 'Lafayette County, WI': '065', 'Langlade County, WI': '067', 'Lincoln County, WI': '069', 'Manitowoc County, WI': '071', 'Marathon County, WI': '073', 'Marinette County, WI': '075', 'Marquette County, WI': '077', 'Menominee County, WI': '078', 'Milwaukee County, WI': '079', 'Monroe County, WI': '081', 'Oconto County, WI': '083', 'Oneida County, WI': '085', 'Outagamie County, WI': '087', 'Ozaukee County, WI': '089', 'Pepin County, WI': '091', 'Pierce County, WI': '093', 'Polk County, WI': '095', 'Portage County, WI': '097', 'Price County, WI': '099', 'Racine County, WI': '101', 'Richland County, WI': '103', 'Rock County, WI': '105', 'Rusk County, WI': '107', 'Sauk County, WI': '111', 'Sawyer County, WI': '113', 'Shawano County, WI': '115', 'Sheboygan County, WI': '117', 'St. Croix County, WI': '109', 'Taylor County, WI': '119', 'Trempealeau County, WI': '121', 'Vernon County, WI': '123', 'Vilas County, WI': '125', 'Walworth County, WI': '127', 'Washburn County, WI': '129', 'Washington County, WI': '131', 'Waukesha County, WI': '133', 'Waupaca County, WI': '135', 'Waushara County, WI': '137', 'Winnebago County, WI': '139', 'Wood County, WI': '141'}, '56': { 'Albany County, WY': '001', 'Big Horn County, WY': '003', 'Campbell County, WY': '005', 'Carbon County, WY': '007', 'Converse County, WY': '009', 'Crook County, WY': '011', 'Fremont County, WY': '013', 'Goshen County, WY': '015', 'Hot Springs County, WY': '017', 'Johnson County, WY': '019', 'Laramie County, WY': '021', 'Lincoln County, WY': '023', 'Natrona County, WY': '025', 'Niobrara County, WY': '027', 'Park County, WY': '029', 'Platte County, WY': '031', 'Sheridan County, WY': '033', 'Sublette County, WY': '035', 'Sweetwater County, WY': '037', 'Teton County, WY': '039', 'Uinta County, WY': '041', 'Washakie County, WY': '043', 'Weston County, WY': '045'}, '72': { 'Adjuntas Municipio, PR': '001', 'Aguada Municipio, PR': '003', 'Aguadilla Municipio, PR': '005', 'Aguas Buenas Municipio, PR': '007', 'Aibonito Municipio, PR': '009', 'Anasco Municipio, PR': '011', 'Arecibo Municipio, PR': '013', 'Arroyo Municipio, PR': '015', 'Barceloneta Municipio, PR': '017', 'Barranquitas Municipio, PR': '019', 'Bayamon Municipio, PR': '021', 'Cabo Rojo Municipio, PR': '023', 'Caguas Municipio, PR': '025', 'Camuy Municipio, PR': '027', 'Canovanas Municipio, PR': '029', 'Carolina Municipio, PR': '031', 'Catano Municipio, PR': '033', 'Cayey Municipio, PR': '035', 'Ceiba Municipio, PR': '037', 'Ciales Municipio, PR': '039', 'Cidra Municipio, PR': '041', 'Coamo Municipio, PR': '043', 'Comerio Municipio, PR': '045', 'Corozal Municipio, PR': '047', 'Culebra Municipio, PR': '049', 'Dorado Municipio, PR': '051', 'Fajardo Municipio, PR': '053', 'Florida Municipio, PR': '054', 'Guanica Municipio, PR': '055', 'Guayama Municipio, PR': '057', 'Guayanilla Municipio, PR': '059', 'Guaynabo Municipio, PR': '061', 'Gurabo Municipio, PR': '063', 'Hatillo Municipio, PR': '065', 'Hormigueros Municipio, PR': '067', 'Humacao Municipio, PR': '069', 'Isabela Municipio, PR': '071', 'Jayuya Municipio, PR': '073', 'Juana Diaz Municipio, PR': '075', 'Juncos Municipio, PR': '077', 'Lajas Municipio, PR': '079', 'Lares Municipio, PR': '081', 'Las Marias Municipio, PR': '083', 'Las Piedras Municipio, PR': '085', 'Loiza Municipio, PR': '087', 'Luquillo Municipio, PR': '089', 'Manati Municipio, PR': '091', 'Maricao Municipio, PR': '093', 'Maunabo Municipio, PR': '095', 'Mayaguez Municipio, PR': '097', 'Moca Municipio, PR': '099', 'Morovis Municipio, PR': '101', 'Naguabo Municipio, PR': '103', 'Naranjito Municipio, PR': '105', 'Orocovis Municipio, PR': '107', 'Patillas Municipio, PR': '109', 'Penuelas Municipio, PR': '111', 'Ponce Municipio, PR': '113', 'Quebradillas Municipio, PR': '115', 'Rincon Municipio, PR': '117', 'Rio Grande Municipio, PR': '119', 'Sabana Grande Municipio, PR': '121', 'Salinas Municipio, PR': '123', 'San German Municipio, PR': '125', 'San Juan Municipio, PR': '127', 'San Lorenzo Municipio, PR': '129', 'San Sebastian Municipio, PR': '131', 'Santa Isabel Municipio, PR': '133', 'Toa Alta Municipio, PR': '135', 'Toa Baja Municipio, PR': '137', 'Trujillo Alto Municipio, PR': '139', 'Utuado Municipio, PR': '141', 'Vega Alta Municipio, PR': '143', 'Vega Baja Municipio, PR': '145', 'Vieques Municipio, PR': '147', 'Villalba Municipio, PR': '149', 'Yabucoa Municipio, PR': '151', 'Yauco Municipio, PR': '153'}, "CA01": { '--All--': '%', }, "CA02": { '--All--': '%', }, "CA03": { '--All--': '%', }, "CA04": { '--All--': '%', }, "CA05": { '--All--': '%', }, "CA13": { '--All--': '%', }, "CA07": { '--All--': '%', }, "CA14": { '--All--': '%', }, "CA08": { '--All--': '%', }, "CA09": { '--All--': '%', }, "CA10": { '--All--': '%', }, "CA11": { '--All--': '%', }, "CA12": { '--All--': '%', }, } if __name__ == "__main__": from sys import argv from pprint import PrettyPrinter pp = PrettyPrinter(indent=2) import csv fipsreader = csv.reader(open(argv[1],'rb'), delimiter=',', quotechar='"') for row in fipsreader: try: FIPS_COUNTIES[int(row[1])][row[3]] = row[2] except KeyError: FIPS_COUNTIES[int(row[1])] = {'--All--': '%'} FIPS_COUNTIES[int(row[1])][row[3]] = row[2] pp.pprint(FIPS_COUNTIES) chirp-0.3.1/share/0000755000016101777760000000000012130403637015116 5ustar jenkinsnogroup00000000000000chirp-0.3.1/share/chirp.png0000644000016100007500000002272711717005656015313 0ustar jenkins00000000000000‰PNG  IHDRÿh>k&1sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœíy|ÕÚÇ3™$ÝÓ–¦ mé-´¥ÊÚBYd‡{yµ_à*Vy¯+‚x\P/ïå•÷¾(ÈEe¸ˆ ˆ€¥‚ÚÒ ººo”¦K–™yÿh3$m’&™$Mh¾ŸO>Éœ9ó<çLæ9Ûœó‚eY A)–‹Åâ‘AxÐ4M)•JÂhNœ8±$I²$I*Y–•Ñ4`3˲G½žèÍø ‚p°W,'ËårAdd$’’’ˆ€€H$˜R€Ø ÖH³#Ü{Ï·#ÜCÀòé4UI’¨©©AII nß¾ŒŒ ¶¹¹™*š¦HeY¶Í ƒÆOÄF‘H´R( ž{î9Ìš5 &%²/!ˆžK‡9õôL{Óm2322ðñÇãØ±c`FÅ0ÌG,ËþI§`è1~‚ \(ŠºÆ²lÌ3Ï<ƒÅ‹ÃÃÃCŸ »Äù:žkÈ´7ݶYUU…W^yçÎA¹ ÃŒfY¶£Ç5ÝŸ ˆ±XœáêêêµiÓ& >\§2{Çþ´M5dÚ›nGÒóõ×_ã…^MÓÍ4MdY¶D+ަñá&‹«ƒ‚‚<·nÝ ‰rœ¡ý걆L{Óm/2³³³1{öl¶­­­…¦é ÍqRóŠ¢®º¸¸ôÃwdÝ}©Ç2ÿud>§OŸ&H’ô"Iòªæ9Îø ‚Ø æ£>rhÃ7¾¥°-tÛ£{K»£Ü7cõXZf||<>ýôS°,Óe續Ÿ 7‘HôʳÏ>ë°}|5ÎÄ~ô8kxû‘9þ|Ìœ9$I¾Lt¾¾çjþ]b±XðÌ3ÏðR`¯ô×ÄQk*SÂl¥Ûõ˜*óŸÿü'H’¤ìºŒ_$=±dɸ¹¹ñJ̃ˆ#?¶Òã¬á­«ÇR2ýýý‘œœ ’$çID²B¡LŸ>ÝâJm³±®kÈì÷Íž ×_| ÃPA$S^4h‚ƒƒy%ÐÑÑwƒÛÚÚŸŸœœ¦iº§cªÃ4Ïu#Âàysd›š]ñ¹V},þîa¦Ü#’$áêê www¸»»ÃÃÃQQQˆ‹‹C\\$I´âA/\áááÁÊd²—(±XýôS 4kÖ¬áÕ4d,Ërõq÷p}çø^¯Ož®ëÕŸu¿ßÕÕžžž ìñ_‡……!,, 3fÌÀÑ£G1sæL¬Zµ «V­I’z$=Öðú4h†!(ÍœþDkk+V¯^ëׯcÑ¢EHLLìG¥RáæÍ›ÈÌÌDVv6ÚÚÚÀ2,X–«Ã l A H²óÿ# ‚$:û~ë I€ÀƒóŸE{;äm­`bWWDDD zÈLš4 ^^^\\üñDNN>ûì3üúë¯Ø¶mtµ~½†×‡º°søŽ¬9¥cEE^}õU´··cõêÕ=¦3çççãÂÏ?#;; à èᑈ^°n¾€.c#Bãw—Aé1:]ñ´~ßÿ††qÞÿMvÊèú­­¯S¾“n°,:î5£¡ðêoæã•_qêôiLž<sfÏÖ*âââ°fÍìØ±Ó§OÇáÇêê9R ß›L»k×.ÄÇÇ[\™-0µÉ_^^ŽgŸ}ÁÁÁX¶l\]]¹8­­­øêàA\¾|LChbâ‚@(²^œX–Eù¯—wøZª«ðÈäɘ3g<==¹( Ã`÷îÝ(**ÂÑ£Gµ >óL1h[Ëß±Y™™ˆ›ÿ$b}„@`ëd;±&±ã2vÊ.^À7[?Bkk+R’“tö—.]ŠÏ?ÿO?ý4Î;g´ç*GîðætÖ¯_ÆÆF¼ôÒK …:'ˆìÞ³y…˜ý?››òŸNÃÀ ›0¬~gÏÃÁC‡¸p’$ñì³ÏB.—ã/ù‹ÅõÚÃ@_wÖøMÉøåË—qòäI,[¶ îîî\ø¡¯¿ÆõÌLLY»^Á¡VK«û" þaLYû>Ò.]¾ý‹{S#‹ñÜsÏáøñã8xð Ö5Ž\Ãë“é°Æo, …ï½÷¦M›¦õÿäÉ“øéÂLþë[ð‹èÃ:é ü†ôwÖãÊõëØýÅ\xHHRRRðÖ[oA&“™,ב ‡4~S2¾cÇtttàÑGåÂòóóñí‰Hzí¯Æô>ËËɃ‰OD$¦­û;®þv /^ä§L™‘H„­[·x€ß÷[<}„®LÊår8p=öÄb1€Î~þ¡Ã‡1dækëd:±3$!¡ˆ_¸‡ƽ{÷tN ž;w.¶nÝŠ††×9R ¯Æøuqúôi1cÆpa—/_Fm]†Ï²SæÄž:÷1¸ùàèÑc\XBB$ vïÞÍKv_Öð½év8ã7%“_~ù%&NœÈ­ÈS©TøæØ1Ä<>bO/×8é$‰ø?¤"ýr:×Ï'cÇŽÅ!7\|«áõéq8ã7–ââb@ÓWAZZ”¡sëÔ9±G‚FŒ‚»Ÿiii\XBBŠ‹‹‘™™Ùëõ}Ù 0WC¿)™ÌÌÌD`` |||¸°¬ìl„Œ›èœ®ë¤'Á³~‡K¿þÊI¥RDFFâÔ©STcû^_˜C¿.ôe233ááá\˲())Á€¨¡6LGÂ/:u55P*•\XPPîܹÃ?}jÞøõ‘™™‰ÈÈH¶í­­ð‹ŠîÃT9±g¼BBÁ²,ª««ï‡yyi‹=ô©qã7%“ ବ AAA\XQQܼ}àæ×¿½9ÑÐÕžüPYYÉ…I$ÔÖÖpÜ^Ÿ‡1~c!‚k¶‰D÷ûö%%%ð‹v6ùÆ+tªªª¸c‰D‚ºº:½ñq OC¿©™”ËåÀ-à€{2Ü‚ô]âÄ À=(U55ܱ——Á0 /¹ö4ЧÆa—ôʤ®šŸÀÒ*‹¦V*ÐPX€ÖÚšn¼ŸˆÁð 6Þ' ²½ ·ÓDLžÚéå§ò–{¨øí …°‰ðJåõ«èhn†tX,<ùŒµy9Õèï‹ÜÝ!kÖüŠÛé— lo×{ÞÍw¤Cc èšÅÉ‘‡dŭܱD"Ã0hjj‚ŸŸŸV\¾µ±­jx}8¬ñBWÍO `T4~–Å·¯üÚ›š Föè㙺Ä(‘wï"}ëG€°ñu>Ì­µ5Hßú„®®¼?çÈAÔßÌG⋯ò6þÂN¡ìâƒq’Dò?wÂÕÇ×$Ù×÷ìDk]­Á8xôãOy»7{x I£ Q»þª©©éaü}‰% »7~s2©ê2rM_ûEYÔøï–—¢½© bOOL\µºÇù¦Òb\ûbª3¯FÿƒÀйÉ2c¶V­Pàòg[ÐXTˆÊë¿að´™fÉ÷òJ ¢ý¶F!“áÇõï@VSæŠ;„ð[š-r÷@{Û}ã‹ÅpqqAmm­–«okÔðÖÀ»7~cÑ̤zÍ~GG÷›¢(°4~yK €¤„ðëé ! ßÁý˘‹—DgWÇ;4 E…¼ `w?©NÙ.Þ>P¶·ƒQ)u\e"O´··i…yxxàîÝ»½^ë(}jìÚøÍͤz6™LõžB¡ô=Ó×g›‹_ô0Ìxïïf_0u¾Sc; o¢ðŒöŒ8Z¡@õÎ)²";Aÿx5¹ÙZa Y Zª«‚€‹—iûòéBèî¹ñS±Ç>5vmüº0&“EÁÍÍM˃——:Ên[=}jXšîrËmÞŸ/‰Ó™We{;šJŠø&Ïjܹ’Ž;WÒužó ÅÀÑ fË.ùéß:ÃÅžžŸ4®¾ü7Ÿáþ7Øj Ïhêq8ã7‰D‚ÖÖû£¶ÞÞÞhkÒ½6ÛÔæåàçÿýoÄÀÄU5ùú©o¾«sÀ¯±¨ß¿ñº%’hb›‡¨Ù¿G[C=.þß´75!4q<Æ,{ .&î˜Û©kÖAÛÓùŠ€òèSCËåZŶ¤´´gΜÁo¿ý†ôôtܾ}¡¡¡HLLÄèÑ£1kÖ,DDôô:enÁa㯮®EQ&–ò-½½½µjþNãoXÖ"ŠÐµ³ùʨT¨/¸Ùã|óÛP´¶¢¹¼”·.GBäîw©?Ü¥þ˜ùÁ‡8ÿÁ;¸þ ­­˜ðÚ_x-¥&)ÊꋲT „"mš¯q­Qó,‹O>ùï¾û.”J%·|qq1ÊÊÊðÍ7ßà7ÞÀ;#åË—÷ºÅ˜Q-d£sb8xð 233‘——Ç¡D"All,Fމ íÙú2éëëËyf:ŸV© —µXd-¿Oä`ˆ=½ o¹‡3kþ¬7^Àð‡xërTÜ¥þ˜ùþßñã¯CMv¾_½ “þô&|""{¿¸ rP&Öü|šìåååxî¹ç‘‘¥R©µ¨ˆKMs…ÁºuëpäÈìÞ½[g+À,nügΜÁ|…BÑ##ÍÍÍøå—_põêU8pï½÷Æ×C†%ú?ÝWc©Û,büA`ΆÿCmn6d5Õè¾SIQð é°X£eŠ==9C„ž-Á]}`øü';›º<·oßFK×TsS #ÞÆŸ››ËmˆÁŠ¢››Û#ÜÜn€X,†D"Ñ2~gÍïÄ Š–H<=¹ã¦¦&¸¸¸À×W{%"ŸZ?77—{uÇ–e‘­÷¼UwìÉËËãÝ|:wIÍËË3ûz]™ô÷÷×2~___Èj / uÒ¿i­®‚TzßÕ[cc#¯Z_êõ&–¢{Ád,¼???ß"¥˜B¡@AAVß÷ýAAAhÒXo/•J!ok…¢Õv |œ8²Új­™¨MMMZ¼šŠ®g8<<Ü"ÜÀÅÅQQQ}³cOXXïQK sÕ]hhïk±MÉdwãWÿ©23¼±:yðQ¶·¡½¥¥‡ñw.-1 íá‡æ-âââL²?‹îØk‘…A &&†·My!!!¨¯¯çÂD"<%ÈjÆï¤'j7dšÆ÷î]£j~SkÞGy„÷X™X,ÆÔ©SûnÇžØØX³'*h¢P({j¨%Þ÷‡……¡¦FÛ¿žŸTê¬ùèDVSwOO­)³Ý›ý–rر|ùrøùù™Ýj&IÞÞÞxýuÝ+ø€+-åØ $$$IjÍúù¡µºÊÀUNú+²šH5šü÷î݃J¥2z´_cjÞ¤¤$¬X±À$Ù/¾ø¢Þ&¿Mwì™>}:¦L™bVß_(báÂ…5j”Áxf¹)¢( 8Ûqèño5àbÚIÿ¥­º þÝû€û|¬ámgݺuصk<==µ\ÍëB$ÁÓÓ;vìÀ† LÒ£+í[Õ÷æ›obéÒ¥¨¬¬4ú½¿@ @tt4–/_n©dôȤºß¯F*•BÖPF¥©gÙ¬µ  Ü8ø/ܹü+ä²ø„Gâá'ŸÁ ìx¯²{w¢¡àH¡ñcDêÞË—µÓ,ƒOx„ÅÒ Eç~@Á÷'!«­†G@¢ç̵èjCSÕVÃoä}ÿ 555ð÷÷780g‰±©””L˜0+V¬À÷ߥR WWWÐ4 @€ööv…BÌš5 ü1üýýÍÒÓ‹=ýÞÞÞøê«¯ðé§ŸbÏž= wʯº™óòË/#55UkÐÃÒþÍ"""´–öúùùeY´ÖÕÂ3h ÙrM…Q*ñý«ÐR]¦k€´67gÞú ’VüƒÆO0[vCá-œ}{5À²`ºæ\”þ|×®à÷ÿû‰Ùî³h…§ÿúdµÕ=Ò0*%”mm¸¶ûs³åæ~s²šjiþõ“x¥¹&çŠÎþÀ>'Ÿ¦QøÃ)ÔÝ4¶§9ÔßÌI’ ãÂÊÊÊ0vìX½×X£  ÔÔT¼ÿþûHMMÅC=d”ᛊUڽÇÇæÍ›uuuÈËËEQˆ‰‰Ûy0 Gmm-†á š!!h(¼… †Ç,IåoWtû•gY¨::ÐXT`–˯ö¦F´TUê<ǨT¨¼~Õd™jî\I7ævÜ--†4¦§CMc¨ÉÊAQ`u¼&&…BTg\ƒt¨åæ}ôFm^Â#"9#S*•½¿.lõ\;ÄŽ=R©Tk®´©ðÍdXXhšF}}=×WŠ:׳³€ÿüƒÙé2CSŠ ’4{ʱBÖbð<-—wº£6qD0"Ím­zÏ÷.»,­»[È2ŒÍ§`×çÞÀ˜¨û¬”——ƒaŒ9Òn×\=júd—^[ÞÌ€€xyy¡¬¬Œ 6lê n‚V*,ž}ø‰©Çš†o¤y;ûx‡B o”˜ 3ËðÀ/z¨Á4ûðp‡æB ÿñóe¶lS¡• Ô :úþ fII bbbàêêªóGªáõa7[t[+“A ))Ik¹ðàÁƒA°,êoæó–o,ñóŸìtÞ}–%Dä´™p—ú›%— I<ô‡§õ©±›„êbøü§º”ôLósàæg~‹.|òT¸ ðë‘nR@Á]€° “Í–m* ·n,‹ÈÈûŽLKKK‘`õ>KÑ' {l9™œ8q"rss¹i¾B¡ᑃQ“sÃàu–Ä+8S×¾Wõ˜G×øÃ™s0féó¼d›ŒáóŸ!p» ]]‘ôêëˆ7‰$$TošG-YÆ+Í¡ÓÞzŸÛx“ ; Qјööû6} [››Ððp­÷쥥¥zûûöb¼|õØ|Çž¾ÈdRRš››QQQÁMÕŒËÙ™ÀÂE×­ÿØáHþdšï”£ãî]Î÷?_‚@ܼˆžóh*)‚@$†wx¸E6¸ð‹¿ŸææføDDZ$Í@§_ÿïý²Ú´TVÀ386pÏÝúÜ<u¿›ÑÜÜŒºº:Œ3Æhö\ÃëÃn·ë²d&ýüü…œœÎø‡ †ïNž-—›äCž/„@Ðé:<¬÷¸¦"tsÓ¹c0_¸4[‚€G@ <{k• uùˆšöV\\ ‰D‚!C,¿Ã²=6mö÷eI8iÒ$-OA¨Ëïé:ÌIÿ¡±¨´J¥eè%%%ÜäžîØ“ñò¥Ïûü¶º™&L@AAär9€ÎY†ƒ£¢lÚïwbT\»Œ°ðp­e¼åååHH07aÀ1 ‰>7~cá{3GŽ ‘H„›7ïoª‹;¿üܹy§“~˲(¿ð#Æizss3 ‘”Ôs÷!{3^¾zlfü}]R…„„äääpáãÇG[C=ª2¯[<NìŸÚì,´ÝmÒªåÏœ9ƒ!C†`âĉFÉèëçšvWó[ófNš4 ÙÙÙÜ+?wwwŒ3…ßgùN‹ÜoaÄÈ‘pww´¶¶"-- úÓŸ,®Ë ›_fR3lÖ¬YhiiÑZå7eòdT\»ŠÖ:§KïþÄ+é¨ËÏż”.ììÙ³ Drrrøöh¼|鳚ߖ™Tãíí¹sçâüùó\ØàÁƒŒ‚NZU·ûAÙÞŽÌ=;0cútnÝIGG~úé'¼öÚk¼¼Q;R!awÍþî˜SÃâé§ŸF^^ªªî»òzlî\œ:á¬ýû,‹ôÍÿ€‡Âܹs¹àóçÏÃÓÓ .ìq‰½¯¹zÔXÝøû¢†7¤'::£GÖªýG…ˆˆHdìÙi“t9é;²ìCc~.^yé%ÎCO[[Ο?åË—÷êJK#Õðú°›šß–…Djj*ÒÓÓÑÖÖÆ…=õ‡…¸s55ÙYVÑé¤oa¿íú 7OÅ Ïÿ‘Û2‹alß¾ÁÁÁX²ÄüEPú°çBªÆo/}Ý™:u*|||pöìY.làÀxdòdüöÙ'6÷òãĺÈ[Zö? òâ¼¾j•ÖÒÝC‡¡ººû÷ï×šè£Æž—¯›×üöÐ Io¾ù&NŸ>[·nqçSRRì7?¬~ •×Ì÷€ãÄ>`Y…gNáäŠçAÔ×aÍoh-ÛMKKCZZöîÝkÖžš8b!!ðnJJ ·Ÿ½%áSKó­á{‹¶¶6ìß¿‰‰‰‹Å ( ‰ `hgwïKÓð‹·YáäÄrÔßÊÇÅÿ†;¿\DÊ£"õé§áææÆÏÏÏÇÎ;ñü¿ûÝï,úlY"Ìšz²²²püøqë­ê³‡¾·°•+W"##;wîÄŠ+@‚ÀÌ‹Á‘‘ø|Ç4ÜĸW_‡ØË<¸NlK]^òŽ~ÊŒkŸ”„y/¿¤µ« Ã08uêNž<‰W_}‹™¶¤Ûkx½:°»víB|¼e—‚:JéXSSƒyóæá¡‡ÂÂ… µ¼¤Þ½{Û>û ·ËË’0‘Ófv:Ùt¶ú †¦Á¨”`T*­ÏÝò2Ü|8†Ž––¤§§ãÂÏ?âÆÁý ( ¡$%„@(„€¢@ …]aHJÈÅéÕz†µŒ:¯̲ X• ,Mk°Úxhú¾1w6¬‰6HIDATgº ÜAu‡$ÉÎü (¨Î¼SÕù- @Qº¾)Š%À…¢  ì:¦( ”Øõï®óšßÝ÷G ÀÅÅEçvq™™™øî»ïP__7â©§žÒû˜ƒ½<Ãæè¦‚ÐÚÂڱŠöññÁ–-[pøða¬_¿999˜7ožV+ÀÓÓ3fÌÀŒ3PYY‰ÖÖV¨T*­RÙYC©kž£iš»×½•þ$IB ýQŒ1ñ4Ì‘>srrpâÄ Ü¹s‹/ƪU«z´ôá¨}xcuÓ]¥PB¡mhh°X*ì)“æè~â‰'€uëÖaݺu1bfÏžˆˆ­xÚn«/'ÆsëÖ-|ûí·(,,Ä“O>‰?ÿùÏ yàjx>”––‚ –ʺº:þžMÄž»¡¡¡Ø¾};rssñùçŸcÆ 6lfÏž˜Ûí"㈰,˵|è®n…fËGó»{˜1q»‡«ÃhšFKK Š‹‹‘œœŒ½{÷bðàÁÓê¨ÆkŠ]a%%% IRA1 #«¬¬ôµV‚¬­np\\6mÚ„²²2lß¾[¶lAHH† ¢Õ$îÞ”6çÐùŠaîaîþÛÐ9}aÆ\«y½¡ßºŒ°ûGݤ4‚  ! AQD"(Š‚P(„H$êñ­þíîî‘H±X¬ÇÅÅ?þ8ââÌÛBL&cÂøÒ—-äÒÒR°,+#|?hРYGŽá-ÔžG8-¡§¶¶ûöíCYY”J% ×·ïþÑ ï>`ê~}÷þºæ±P(ì10¦Yàhž×õÑu®ûõšMƒTSÅ¥f¸f˜æÇ’ÿß0G‘ÉW···7ÛÞÞ~† àè±cǬS€±Ø[&íQ˲= e×f•šÛÝ ‚èó´Û»nG‘Ù—z.^¼ˆiÓ¦@ Ųì1úܹs‚gžyFg¢ŒÁžûðö¤G³©k)™Æê57ÌVºíQ½¥¯îM›6$IMÓÇHËå‡wíÚ¥µÄÕ–ØÛ ¶G=}ÙGtäûf¬kÈ´·ÿ§¦¦ß}÷Ë0Ìàþª¾%r¹œÞ³gÅ”[ƒþð: ‰¾ÑãÈi7–Å‹ƒaÀ ËøY–mS([víÚ…¬,Ë8³p63­«Ç2ûÃ}{Ð cuïÙ³çÏŸ˲Ÿ°,Û„æì>Š¢ò=<<†î߿ߤ%¾Î$ûÕc ™ö¦ÛQdö•žk×®aâĉ`&Ÿan¢Š–3š¦Gµ··Ë^~ùeÔÕÕéL¨%É—þ Ç2ûÃ}ë¯5|w²²²0}út–eY˲ZjWóDUUÕ½§žzJkcKk`o7Øõ8ûðÖÕc ™öòÿGÆu2§IEND®B`‚chirp-0.3.1/share/chirp.desktop0000644000016100007500000000041311717005656016164 0ustar jenkins00000000000000[Desktop Entry] Version=0.1 Encoding=UTF-8 Type=Application Exec=chirpw Icon=chirp.png StartupNotify=true Terminal=false Categories=Application;HamRadio MimeType=inode/directory Name=CHIRP Comment=CHIRP Radio Programming Tool GenericName=CHIRP Radio Programming Tool chirp-0.3.1/share/chirpw.10000644000016100007500000000270211717005656015045 0ustar jenkins00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH CHIRP 1 "February 12, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME chirpw \- A tool for programming two-way radio equipment .SH SYNOPSIS .B chirpw .RI [ options ] .br .SH DESCRIPTION This manual page documents briefly the .B chirpw command. .PP \fBchirpw\fP is a tool for programming two-way radio equipment It provides a generic user interface to the programming data and process that can drive many radio models under the hood. .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`--'). A summary of options is included below. .TP .B \-\-help Show summary of options. .TP .B \-\-profile Enable Profiling. .SH AUTHOR chirpw was written by Dan Smith. .PP This manual page was written by Dan Smith (with help from Steve Conklin), for the Debian project (and may be used by others). chirp-0.3.1/chirpw0000755000016101777760000001004612130403635015235 0ustar jenkinsnogroup00000000000000#!/usr/bin/python # # Copyright 2008 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 . from chirp import platform, CHIRP_VERSION from chirpui import config # Hack to setup environment platform.get_platform() import gtk import sys import os import locale import gettext p = platform.get_platform() if hasattr(sys, "frozen"): log = file(p.config_file("debug.log"), "w", 0) sys.stderr = log sys.stdout = log elif not os.isatty(0): log = file(p.config_file("debug.log"), "w", 0) sys.stdout = log sys.stderr = log print "CHIRP %s on %s (Python %s)" % (CHIRP_VERSION, platform.get_platform().os_version_string(), sys.version.split()[0]) execpath = platform.get_platform().executable_path() localepath = os.path.abspath(os.path.join(execpath, "locale")) if not os.path.exists(localepath): localepath = "/usr/share/chirp/locale" conf = config.get() manual_language = conf.get("language", "state") langs = [] if manual_language and manual_language != "Auto": lang_codes = { "English" : "en_US", "Polish" : "pl", "Italian" : "it", "Dutch" : "nl", "German" : "de", "Hungarian" : "hu", } try: print lang_codes[manual_language] langs = [lang_codes[manual_language]] except KeyError: print "Unsupported language `%s'" % manual_language else: lc, encoding = locale.getdefaultlocale() if (lc): langs = [lc] try: langs += os.getenv("LANG").split(":") except: pass path = "locale" gettext.bindtextdomain("CHIRP", localepath) gettext.textdomain("CHIRP") lang = gettext.translation("CHIRP", localepath, languages=langs, fallback=True) # Python <2.6 does not have str.format(), which chirp uses to make translation # strings nicer. So, instead of installing the gettext standard "_()" function, # we can install our own, which returns a string of the following class, # which emulates the new behavior, thus allowing us to run on older Python # versions. class CompatStr(str): def format(self, **kwargs): base = lang.gettext(self) for k,v in kwargs.items(): base = base.replace("{%s}" % k, str(v)) return base pyver = sys.version.split()[0] vmaj, vmin, vrel = pyver.split(".", 3) if int(vmaj) < 2 or int(vmin) < 6: # Python <2.6, emulate str.format() import __builtin__ def lang_with_format(string): return CompatStr(string) __builtin__._ = lang_with_format else: # Python >=2.6, use normal gettext behavior lang.install() from chirp import * from chirpui import mainapp, config a = mainapp.ChirpMain() profile = False if len(sys.argv) > 1: arg = sys.argv[1] index = 2 if arg == "--profile": profile = True elif arg == "--help": print "Usage: %s [file...]" % sys.argv[0] sys.exit(0) else: index = 1 else: index = 1 for i in sys.argv[index:]: print "Opening %s" % i a.do_open(i) a.show() if profile: import cProfile, pstats cProfile.run("gtk.main()", "chirpw.stats") p = pstats.Stats("chirpw.stats") p.sort_stats("cumulative").print_stats(10) else: gtk.main() if config._CONFIG: config._CONFIG.set("last_dir", platform.get_platform().get_last_dir(), "state") config._CONFIG.save() chirp-0.3.1/chirp_banks.xsd0000644000016100007500000000042211717005656015365 0ustar jenkins00000000000000 chirp-0.3.1/chirp/0000755000016101777760000000000012130403637015121 5ustar jenkinsnogroup00000000000000chirp-0.3.1/chirp/ic2820.py0000644000016101777760000002253412066005671016414 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . from chirp import chirp_common, icf, util, directory from chirp import bitwise MEM_FORMAT = """ struct { u32 freq; u32 offset; char urcall[8]; char r1call[8]; char r2call[8]; u8 unknown1; u8 unknown2:1, duplex:2, tmode:3, unknown3:2; u16 ctone:6, rtone:6, tune_step:4; u16 dtcs:7, mode:3, unknown4:6; u8 unknown5:1, digital_code:7; u8 unknown6:2, dtcs_polarity:2, unknown7:4; char name[8]; } memory[522]; #seekto 0x61E0; u8 used_flags[66]; #seekto 0x6222; u8 skip_flags[65]; u8 pskip_flags[65]; #seekto 0x62A4; struct { u8 bank; u8 index; } bank_info[500]; #seekto 0x66C0; struct { char name[8]; } bank_names[26]; #seekto 0x6970; struct { char call[8]; u8 unknown[4]; } mycall[6]; #seekto 0x69B8; struct { char call[8]; } urcall[60]; struct { char call[8]; } rptcall[60]; """ TMODES = ["", "Tone", "??0", "TSQL", "??1", "??2", "DTCS"] DUPLEX = ["", "-", "+", "+"] # Not sure about index 3 MODES = ["FM", "NFM", "AM", "??", "DV"] DTCSP = ["NN", "NR", "RN", "RR"] MEM_LOC_SIZE = 48 class IC2820Bank(icf.IcomNamedBank): """An IC2820 bank""" def get_name(self): _banks = self._model._radio._memobj.bank_names return str(_banks[self.index].name).rstrip() def set_name(self, name): _banks = self._model._radio._memobj.bank_names _banks[self.index].name = str(name).ljust(8)[:8] def _get_special(): special = {"C0" : 500 + 20, "C1" : 500 + 21} for i in range(0, 10): ida = "%iA" % i idb = "%iB" % i special[ida] = 500 + i * 2 special[idb] = 500 + i * 2 + 1 return special def _resolve_memory_number(number): if isinstance(number, str): return _get_special()[number] else: return number def _wipe_memory(mem, char): mem.set_raw(char * (mem.size() / 8)) @directory.register class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom IC-2820""" VENDOR = "Icom" MODEL = "IC-2820H" _model = "\x29\x70\x00\x01" _memsize = 44224 _endframe = "Icom Inc\x2e68" _ranges = [(0x0000, 0x6960, 32), (0x6960, 0x6980, 16), (0x6980, 0x7160, 32), (0x7160, 0x7180, 16), (0x7180, 0xACC0, 32),] _num_banks = 26 _bank_class = IC2820Bank _can_hispeed = True MYCALL_LIMIT = (1, 7) URCALL_LIMIT = (1, 61) RPTCALL_LIMIT = (1, 61) _memories = {} def _get_bank(self, loc): _bank = self._memobj.bank_info[loc] if _bank.bank == 0xFF: return None else: return _bank.bank def _set_bank(self, loc, bank): _bank = self._memobj.bank_info[loc] if bank is None: _bank.bank = 0xFF else: _bank.bank = bank def _get_bank_index(self, loc): _bank = self._memobj.bank_info[loc] return _bank.index def _set_bank_index(self, loc, index): _bank = self._memobj.bank_info[loc] _bank.index = index def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank_index = True rf.has_bank_names = True rf.requires_call_lists = False rf.memory_bounds = (0, 499) rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(set(DUPLEX)) rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS) rf.valid_bands = [(118000000, 999990000)] rf.valid_skips = ["", "S", "P"] rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC rf.valid_name_length = 8 rf.valid_special_chans = sorted(_get_special().keys()) return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_memory(self, number): number = _resolve_memory_number(number) bitpos = (1 << (number % 8)) bytepos = number / 8 _mem = self._memobj.memory[number] _used = self._memobj.used_flags[bytepos] is_used = ((_used & bitpos) == 0) if is_used and MODES[_mem.mode] == "DV": mem = chirp_common.DVMemory() mem.dv_urcall = str(_mem.urcall).rstrip() mem.dv_rpt1call = str(_mem.r1call).rstrip() mem.dv_rpt2call = str(_mem.r2call).rstrip() else: mem = chirp_common.Memory() mem.number = number if number < 500: _skip = self._memobj.skip_flags[bytepos] _pskip = self._memobj.pskip_flags[bytepos] if _skip & bitpos: mem.skip = "S" elif _pskip & bitpos: mem.skip = "P" else: mem.extd_number = util.get_dict_rev(_get_special(), number) mem.immutable = ["number", "skip", "bank", "bank_index", "extd_number"] if not is_used: mem.empty = True return mem mem.freq = int(_mem.freq) mem.offset = int(_mem.offset) mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCSP[_mem.dtcs_polarity] if _mem.tune_step > 8: mem.tuning_step = 5.0 # Sometimes TS is garbage? else: mem.tuning_step = chirp_common.TUNING_STEPS[_mem.tune_step] mem.name = str(_mem.name).rstrip() return mem def set_memory(self, mem): bitpos = (1 << (mem.number % 8)) bytepos = mem.number / 8 _mem = self._memobj.memory[mem.number] _used = self._memobj.used_flags[bytepos] was_empty = _used & bitpos if mem.number < 500: skip = self._memobj.skip_flags[bytepos] pskip = self._memobj.pskip_flags[bytepos] if mem.skip == "S": skip |= bitpos else: skip &= ~bitpos if mem.skip == "P": pskip |= bitpos else: pskip &= ~bitpos if mem.empty: _used |= bitpos _wipe_memory(_mem, "\xFF") self._set_bank(mem.number, None) return _used &= ~bitpos if was_empty: _wipe_memory(_mem, "\x00") _mem.freq = mem.freq _mem.offset = mem.offset _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity) _mem.tune_step = chirp_common.TUNING_STEPS.index(mem.tuning_step) _mem.name = mem.name.ljust(8) if isinstance(mem, chirp_common.DVMemory): _mem.urcall = mem.dv_urcall.ljust(8) _mem.r1call = mem.dv_rpt1call.ljust(8) _mem.r2call = mem.dv_rpt2call.ljust(8) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_urcall_list(self): _calls = self._memobj.urcall calls = [] for i in range(*self.URCALL_LIMIT): calls.append(str(_calls[i-1].call)) return calls def get_repeater_call_list(self): _calls = self._memobj.rptcall calls = [] for i in range(*self.RPTCALL_LIMIT): calls.append(str(_calls[i-1].call)) return calls def get_mycall_list(self): _calls = self._memobj.mycall calls = [] for i in range(*self.MYCALL_LIMIT): calls.append(str(_calls[i-1].call)) return calls def set_urcall_list(self, calls): _calls = self._memobj.urcall for i in range(*self.URCALL_LIMIT): try: call = calls[i-1] except IndexError: call = " " * 8 _calls[i-1].call = call.ljust(8)[:8] def set_repeater_call_list(self, calls): _calls = self._memobj.rptcall for i in range(*self.RPTCALL_LIMIT): try: call = calls[i-1] except IndexError: call = " " * 8 _calls[i-1].call = call.ljust(8)[:8] def set_mycall_list(self, calls): _calls = self._memobj.mycall for i in range(*self.MYCALL_LIMIT): try: call = calls[i-1] except IndexError: call = " " * 8 _calls[i-1].call = call.ljust(8)[:8] chirp-0.3.1/chirp/pyPEG.py0000644000016101777760000002376112130403635016466 0ustar jenkinsnogroup00000000000000# YPL parser 0.45 # written by VB. import re class keyword(str): pass class code(str): pass class ignore(object): def __init__(self, regex_text): self.regex = re.compile(regex_text) class _and(object): def __init__(self, something): self.obj = something class _not(_and): pass class Name(str): def __init__(self, *args): self.line = 0 self.file = "" word_regex = re.compile(r"\w+") rest_regex = re.compile(r".*") ignoring = ignore("") def skip(skipper, text, pattern, skipWS, skipComments): if skipWS: t = text.strip() else: t = text if skipComments: try: while True: skip, t = skipper.parseLine(t, skipComments, [], skipWS, None) if skipWS: t = t.strip() except: pass return t class parser(object): def __init__(self, another = False): self.restlen = -1 if not(another): self.skipper = parser(True) else: self.skipper = self self.lines = None self.textlen = 0 self.memory = {} self.packrat = False # parseLine(): # textline: text to parse # pattern: pyPEG language description # resultSoFar: parsing result so far (default: blank list []) # skipWS: Flag if whitespace should be skipped (default: True) # skipComments: Python functions returning pyPEG for matching comments # # returns: pyAST, textrest # # raises: SyntaxError(reason) if textline is detected not being in language # described by pattern # # SyntaxError(reason) if pattern is an illegal language description def parseLine(self, textline, pattern, resultSoFar = [], skipWS = True, skipComments = None): name = None _textline = textline _pattern = pattern _packrat = self.packrat _memory = self.memory def R(result, text): if self.restlen == -1: self.restlen = len(text) else: self.restlen = min(self.restlen, len(text)) res = resultSoFar if name and result: res.append((name, result)) elif name: res.append((name, [])) elif result: if type(result) is type([]): res.extend(result) else: res.extend([result]) if _packrat: if name: _memory[(len(_textline), id(_pattern))] = (res, text) return res, text def syntaxError(): if _packrat: if name: _memory[(len(_textline), id(_pattern))] = False raise SyntaxError() if type(pattern) is type(lambda x: 0): if _packrat: try: result = _memory[(len(_textline), id(_pattern))] if result: return result else: raise SyntaxError() except: pass if pattern.__name__[0] != "_": name = Name(pattern.__name__) name.line = self.lineNo() pattern = pattern() if type(pattern) is type(lambda x: 0): pattern = (pattern,) text = skip(self.skipper, textline, pattern, skipWS, skipComments) pattern_type = type(pattern) if pattern_type is type(""): if text[:len(pattern)] == pattern: text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments) return R(None, text) else: syntaxError() elif pattern_type is type(keyword("")): m = word_regex.match(text) if m: if m.group(0) == pattern: text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments) return R(None, text) else: syntaxError() else: syntaxError() elif pattern_type is type(_not("")): try: r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments) except: return resultSoFar, textline syntaxError() elif pattern_type is type(_and("")): r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments) return resultSoFar, textline elif pattern_type is type(word_regex) or pattern_type is type(ignoring): if pattern_type is type(ignoring): pattern = pattern.regex m = pattern.match(text) if m: text = skip(self.skipper, text[len(m.group(0)):], pattern, skipWS, skipComments) if pattern_type is type(ignoring): return R(None, text) else: return R(m.group(0), text) else: syntaxError() elif pattern_type is type((None,)): result = [] n = 1 for p in pattern: if type(p) is type(0): n = p else: if n>0: for i in range(n): result, text = self.parseLine(text, p, result, skipWS, skipComments) elif n==0: if text == "": pass else: try: newResult, newText = self.parseLine(text, p, result, skipWS, skipComments) result, text = newResult, newText except SyntaxError: pass elif n<0: found = False while True: try: newResult, newText = self.parseLine(text, p, result, skipWS, skipComments) result, text, found = newResult, newText, True except SyntaxError: break if n == -2 and not(found): syntaxError() n = 1 return R(result, text) elif pattern_type is type([]): result = [] found = False for p in pattern: try: result, text = self.parseLine(text, p, result, skipWS, skipComments) found = True except SyntaxError: pass if found: break if found: return R(result, text) else: syntaxError() else: raise SyntaxError("illegal type in grammar: " + str(pattern_type)) def lineNo(self): if not(self.lines): return "" if self.restlen == -1: return "" parsed = self.textlen - self.restlen left, right = 0, len(self.lines) while True: mid = (right + left) / 2 if self.lines[mid][0] <= parsed: try: if self.lines[mid + 1][0] >= parsed: try: return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2]) except: return "" else: left = mid + 1 except: try: return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2]) except: return "" else: right = mid - 1 if left > right: return "" # plain module API def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments = None, packrat = False): p = parser() p.packrat = packrat text = skip(p.skipper, textline, pattern, skipWS, skipComments) ast, text = p.parseLine(text, pattern, resultSoFar, skipWS, skipComments) return ast, text # parse(): # language: pyPEG language description # lineSource: a fileinput.FileInput object # skipWS: Flag if whitespace should be skipped (default: True) # skipComments: Python function which returns pyPEG for matching comments # packrat: use memoization # lineCount: add line number information to AST # # returns: pyAST # # raises: SyntaxError(reason), if a parsed line is not in language # SyntaxError(reason), if the language description is illegal def parse(language, lineSource, skipWS = True, skipComments = None, packrat = False, lineCount = True): lines, lineNo = [], 0 while type(language) is type(lambda x: 0): language = language() orig, ld = "", 0 for line in lineSource: if lineSource.isfirstline(): ld = 1 else: ld += 1 lines.append((len(orig), lineSource.filename(), lineSource.lineno() - 1)) orig += line textlen = len(orig) try: p = parser() p.packrat = packrat p.textlen = len(orig) if lineCount: p.lines = lines else: p.line = None text = skip(p.skipper, orig, language, skipWS, skipComments) result, text = p.parseLine(text, language, [], skipWS, skipComments) if text: raise SyntaxError() except SyntaxError, msg: parsed = textlen - p.restlen textlen = 0 nn, lineNo, file = 0, 0, "" for n, ld, l in lines: if n >= parsed: break else: lineNo = l nn += 1 file = ld lineNo += 1 nn -= 1 lineCont = orig.splitlines()[nn] raise SyntaxError("syntax error in " + file + ":" + str(l) + ": " + lineCont) return result chirp-0.3.1/chirp/ic2200.py0000644000016100007500000002064212025023645014735 0ustar jenkins00000000000000# Copyright 2008 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 . from chirp import chirp_common, icf, util, directory from chirp import bitwise MEM_FORMAT = """ struct { ul16 freq; ul16 offset; char name[6]; u8 unknown1:2, rtone:6; u8 unknown2:2, ctone:6; u8 unknown3:1, dtcs:7; u8 unknown4:4, tuning_step:4; u8 unknown5[3]; u8 unknown6:4, urcall:4; u8 r1call:4, r2call:4; u8 unknown7:1, digital_code:7; u8 is_625:1, unknown8:1, mode_am:1, mode_narrow:1, power:2, tmode:2; u8 dtcs_polarity:2, duplex:2, unknown10:4; u8 unknown11; u8 mode_dv:1, unknown12:7; } memory[207]; #seekto 0x1370; struct { u8 unknown:2, empty:1, skip:1, bank:4; } flags[207]; #seekto 0x15F0; struct { char call[8]; } mycalls[6]; struct { char call[8]; } urcalls[6]; struct { char call[8]; } rptcalls[6]; """ TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+"] DTCSP = ["NN", "NR", "RN", "RR"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=65), chirp_common.PowerLevel("Mid", watts=25), chirp_common.PowerLevel("MidLow", watts=10), chirp_common.PowerLevel("Low", watts=5)] def _get_special(): special = { "C" : 206 } for i in range(0, 3): ida = "%iA" % (i+1) idb = "%iB" % (i+1) num = 200 + i * 2 special[ida] = num special[idb] = num + 1 return special def _wipe_memory(mem, char): mem.set_raw(char * (mem.size() / 8)) @directory.register class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom IC-2200""" VENDOR = "Icom" MODEL = "IC-2200H" _model = "\x26\x98\x00\x01" _memsize = 6848 _endframe = "Icom Inc\x2eD8" _can_hispeed = True _memories = [] _ranges = [(0x0000, 0x1340, 32), (0x1340, 0x1360, 16), (0x1360, 0x136B, 8), (0x1370, 0x1380, 16), (0x1380, 0x15E0, 32), (0x15E0, 0x1600, 16), (0x1600, 0x1640, 32), (0x1640, 0x1660, 16), (0x1660, 0x1680, 32), (0x16E0, 0x1860, 32), (0x1880, 0x1AB0, 32), (0x1AB8, 0x1AC0, 8), ] MYCALL_LIMIT = (0, 6) URCALL_LIMIT = (0, 6) RPTCALL_LIMIT = (0, 6) def _get_bank(self, loc): _flag = self._memobj.flags[loc] if _flag.bank == 0x0A: return None else: return _flag.bank def _set_bank(self, loc, bank): _flag = self._memobj.flags[loc] if bank is None: _flag.bank = 0x0A else: _flag.bank = bank def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.valid_modes = ["FM", "NFM", "AM", "NAM", "DV"] rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(118000000, 174000000)] rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS rf.valid_special_chans = sorted(_get_special().keys()) rf.has_settings = True return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_memory(self, number): if isinstance(number, str): number = _get_special()[number] _mem = self._memobj.memory[number] _flag = self._memobj.flags[number] if _mem.mode_dv and not _flag.empty: mem = chirp_common.DVMemory() mem.dv_urcall = \ str(self._memobj.urcalls[_mem.urcall].call).rstrip() mem.dv_rpt1call = \ str(self._memobj.rptcalls[_mem.r1call].call).rstrip() mem.dv_rpt2call = \ str(self._memobj.rptcalls[_mem.r2call].call).rstrip() else: mem = chirp_common.Memory() mem.number = number if number < 200: mem.skip = _flag.skip and "S" or "" else: mem.extd_number = util.get_dict_rev(_get_special(), number) mem.immutable = ["number", "skip", "bank", "bank_index", "extd_number"] if _flag.empty: mem.empty = True mem.power = POWER_LEVELS[0] return mem mult = _mem.is_625 and 6250 or 5000 mem.freq = (_mem.freq * mult) mem.offset = (_mem.offset * mult) mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.mode = _mem.mode_dv and "DV" or _mem.mode_am and "AM" or "FM" if _mem.mode_narrow: mem.mode = "N%s" % mem.mode mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCSP[_mem.dtcs_polarity] mem.tuning_step = STEPS[_mem.tuning_step] mem.name = str(_mem.name).replace("\x0E", "").rstrip() mem.power = POWER_LEVELS[_mem.power] return mem def get_memories(self, lo=0, hi=199): return [m for m in self._memories if m.number >= lo and m.number <= hi] def set_memory(self, mem): if isinstance(mem.number, str): number = _get_special()[mem.number] else: number = mem.number _mem = self._memobj.memory[number] _flag = self._memobj.flags[number] was_empty = int(_flag.empty) _flag.empty = mem.empty if mem.empty: _wipe_memory(_mem, "\xFF") return if was_empty: _wipe_memory(_mem, "\x00") _mem.unknown8 = 0 _mem.is_625 = chirp_common.is_fractional_step(mem.freq) mult = _mem.is_625 and 6250 or 5000 _mem.freq = mem.freq / mult _mem.offset = mem.offset / mult _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode_dv = mem.mode == "DV" _mem.mode_am = mem.mode.endswith("AM") _mem.mode_narrow = mem.mode.startswith("N") _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity) _mem.tuning_step = STEPS.index(mem.tuning_step) _mem.name = mem.name.ljust(6) if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 if number < 200: _flag.skip = mem.skip != "" if isinstance(mem, chirp_common.DVMemory): urcalls = self.get_urcall_list() rptcalls = self.get_repeater_call_list() _mem.urcall = urcalls.index(mem.dv_urcall) _mem.r1call = rptcalls.index(mem.dv_rpt1call) _mem.r2call = rptcalls.index(mem.dv_rpt2call) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_urcall_list(self): return [str(x.call).rstrip() for x in self._memobj.urcalls] def get_repeater_call_list(self): return [str(x.call).rstrip() for x in self._memobj.rptcalls] def get_mycall_list(self): return [str(x.call).rstrip() for x in self._memobj.mycalls] def set_urcall_list(self, calls): for i in range(*self.URCALL_LIMIT): self._memobj.urcalls[i].call = calls[i].ljust(8) def set_repeater_call_list(self, calls): for i in range(*self.RPTCALL_LIMIT): self._memobj.rptcalls[i].call = calls[i].ljust(8) def set_mycall_list(self, calls): for i in range(*self.MYCALL_LIMIT): self._memobj.mycalls[i].call = calls[i].ljust(8) chirp-0.3.1/chirp/ft2800.py0000644000016100007500000001764512023560645014776 0ustar jenkins00000000000000# Copyright 2011 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 . import time import os from chirp import util, memmap, chirp_common, bitwise, directory, errors from chirp.yaesu_clone import YaesuCloneModeRadio DEBUG = os.getenv("CHIRP_DEBUG") and True or False CHUNK_SIZE = 16 def _send(s, data): for i in range(0, len(data), CHUNK_SIZE): chunk = data[i:i+CHUNK_SIZE] s.write(chunk) echo = s.read(len(chunk)) if chunk != echo: raise Exception("Failed to read echo chunk") IDBLOCK = "\x0c\x01\x41\x33\x35\x02\x00\xb8" TRAILER = "\x0c\x02\x41\x33\x35\x00\x00\xb7" ACK = "\x0C\x06\x00" def _download(radio): data = "" for _i in range(0, 10): data = radio.pipe.read(8) if data == IDBLOCK: break if DEBUG: print "Header:\n%s" % util.hexprint(data) if len(data) != 8: raise Exception("Failed to read header") _send(radio.pipe, ACK) data = "" while len(data) < radio._block_sizes[1]: time.sleep(0.1) chunk = radio.pipe.read(38) if DEBUG: print "Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)) if len(chunk) == 8: print "END?" elif len(chunk) != 38: print "Should fail?" break #raise Exception("Failed to get full data block") else: cs = 0 for byte in chunk[:-1]: cs += ord(byte) if ord(chunk[-1]) != (cs & 0xFF): raise Exception("Block failed checksum!") data += chunk[5:-1] _send(radio.pipe, ACK) if radio.status_fn: status = chirp_common.Status() status.max = radio._block_sizes[1] status.cur = len(data) status.msg = "Cloning from radio" radio.status_fn(status) if DEBUG: print "Total: %i" % len(data) return memmap.MemoryMap(data) def _upload(radio): for _i in range(0, 10): data = radio.pipe.read(256) if not data: break print "What is this garbage?\n%s" % util.hexprint(data) _send(radio.pipe, IDBLOCK) time.sleep(1) ack = radio.pipe.read(300) if DEBUG: print "Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)) if ack != ACK: raise Exception("Radio did not ack ID") block = 0 while block < (radio.get_memsize() / 32): data = "\x0C\x03\x00\x00" + chr(block) data += radio.get_mmap()[block*32:(block+1)*32] cs = 0 for byte in data: cs += ord(byte) data += chr(cs & 0xFF) if DEBUG: print "Writing block %i:\n%s" % (block, util.hexprint(data)) _send(radio.pipe, data) time.sleep(0.1) ack = radio.pipe.read(3) if ack != ACK: raise Exception("Radio did not ack block %i" % block) if radio.status_fn: status = chirp_common.Status() status.max = radio._block_sizes[1] status.cur = block * 32 status.msg = "Cloning to radio" radio.status_fn(status) block += 1 _send(radio.pipe, TRAILER) MEM_FORMAT = """ struct { bbcd freq[4]; u8 unknown1[4]; bbcd offset[2]; u8 unknown2[2]; u8 pskip:1, skip:1, unknown3:1, isnarrow:1, power:2, duplex:2; u8 unknown4:6, tmode:2; u8 tone; u8 dtcs; } memory[200]; #seekto 0x0E00; struct { char name[6]; } names[200]; """ MODES = ["FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+", ""] POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=65), chirp_common.PowerLevel("Mid", watts=25), chirp_common.PowerLevel("Low2", watts=10), chirp_common.PowerLevel("Low1", watts=5), ] CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + "()+-=*/???|_" @directory.register class FT2800Radio(YaesuCloneModeRadio): """Yaesu FT-2800""" VENDOR = "Yaesu" MODEL = "FT-2800M" _block_sizes = [8, 7680] _memsize = 7680 def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.has_ctone = False rf.has_tuning_step = False rf.has_dtcs_polarity = False rf.has_bank = False rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] rf.valid_modes = MODES rf.valid_tmodes = TMODES rf.valid_bands = [(137000000, 174000000)] rf.valid_power_levels = POWER_LEVELS rf.valid_duplexes = DUPLEX rf.valid_skips = ["", "S", "P"] rf.valid_name_length = 6 rf.valid_characters = CHARSET return rf def sync_in(self): self.pipe.setParity("E") start = time.time() try: self._mmap = _download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) print "Downloaded in %.2f sec" % (time.time() - start) self.process_mmap() def sync_out(self): self.pipe.setTimeout(1) self.pipe.setParity("E") start = time.time() try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) print "Uploaded in %.2f sec" % (time.time() - start) def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): _mem = self._memobj.memory[number] _nam = self._memobj.names[number] mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[0] == "\xFF": mem.empty = True return mem mem.freq = int(_mem.freq) * 10 mem.offset = int(_mem.offset) * 100000 mem.duplex = DUPLEX[_mem.duplex] mem.tmode = TMODES[_mem.tmode] mem.rtone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.name = str(_nam.name).rstrip() mem.mode = _mem.isnarrow and "NFM" or "FM" mem.skip = _mem.pskip and "P" or _mem.skip and "S" or "" mem.power = POWER_LEVELS[_mem.power] return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _nam = self._memobj.names[mem.number] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return if _mem.get_raw()[0] == "\xFF": # Emtpy -> Non-empty, so initialize _mem.set_raw("\x00" * (_mem.size() / 8)) _mem.freq = mem.freq / 10 _mem.offset = mem.offset / 100000 _mem.duplex = DUPLEX.index(mem.duplex) _mem.tmode = TMODES.index(mem.tmode) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.isnarrow = MODES.index(mem.mode) _mem.pskip = mem.skip == "P" _mem.skip = mem.skip == "S" if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 _nam.name = mem.name.ljust(6)[:6] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize chirp-0.3.1/chirp/tmv71_ll.py0000644000016100007500000002135011717005656015511 0ustar jenkins00000000000000# Copyright 2010 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 . import struct, time from chirp import memmap, chirp_common, errors DEBUG = True POS_MODE = 5 POS_DUP = 6 POS_TMODE = 6 POS_RTONE = 7 POS_CTONE = 8 POS_DTCS = 9 POS_OFFSET = 10 MEM_LOC_BASE = 0x1700 MEM_LOC_SIZE = 16 MEM_TAG_BASE = 0x5800 MEM_FLG_BASE = 0x0E00 V71_SPECIAL = {} for i in range(0, 10): V71_SPECIAL["L%i" % i] = 1000 + (i * 2) V71_SPECIAL["U%i" % i] = 1000 + (i * 2) + 1 for i in range(0, 10): V71_SPECIAL["WX%i" % (i + 1)] = 1020 + i V71_SPECIAL["C VHF"] = 1030 V71_SPECIAL["C UHF"] = 1031 V71_SPECIAL_REV = {} for k,v in V71_SPECIAL.items(): V71_SPECIAL_REV[v] = k def command(s, cmd, timeout=0.5): start = time.time() data = "" if DEBUG: print "PC->V71: %s" % cmd s.write(cmd + "\r") while not data.endswith("\r") and (time.time() - start) < timeout: data += s.read(1) if DEBUG: print "V71->PC: %s" % data.strip() return data.strip() def get_id(s): r = command(s, "ID") if r.startswith("ID "): return r.split(" ")[1] else: raise errors.RadioError("No response to ID command") EXCH_R = "R\x00\x00\x00" EXCH_W = "W\x00\x00\x00" def read_block(s, block, count=256): s.write(struct.pack(" (max(V71_SPECIAL.values()) + 1): raise errors.InvalidMemoryLocation("Number must be between 0 and 999") mem = chirp_common.Memory() mem.number = number if number > 999: mem.extd_number = V71_SPECIAL_REV[number] if not get_used(map, number): mem.empty = True return mem mmap = get_raw_mem(map, number) mem.freq = get_freq(mmap) mem.name = get_name(map, number) mem.tmode = get_tmode(mmap) mem.rtone = get_tone(mmap, POS_RTONE) mem.ctone = get_tone(mmap, POS_CTONE) mem.dtcs = get_dtcs(mmap) mem.duplex = get_duplex(mmap) mem.offset = get_offset(mmap) mem.mode = get_mode(mmap) if number < 999: mem.skip = get_skip(map, number) if number > 999: mem.immutable = ["number", "bank", "extd_number", "name"] if number > 1020 and number < 1030: mem.immutable += ["freq"] # FIXME: ALL return mem def initialize(mmap): mmap[0] = \ "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \ "\x08\x00\xc0\x27\x09\x00\x00\xff" def set_memory(map, mem): if mem.number < 0 or mem.number > (max(V71_SPECIAL.values()) + 1): raise errors.InvalidMemoryLocation("Number must be between 0 and 999") mmap = memmap.MemoryMap(get_raw_mem(map, mem.number)) if not get_used(map, mem.number): initialize(mmap) set_freq(mmap, mem.freq) if mem.number < 999: set_name(map, mem.number, mem.name) set_tmode(mmap, mem.tmode) set_tone(mmap, mem.rtone, POS_RTONE) set_tone(mmap, mem.ctone, POS_CTONE) set_dtcs(mmap, mem.dtcs) set_duplex(mmap, mem.duplex) set_offset(mmap, mem.offset) set_mode(mmap, mem.mode) base = get_mem_offset(mem.number) map[base] = mmap.get_packed() set_used(map, mem.number, mem.freq) if mem.number < 999: set_skip(map, mem.number, mem.skip) return map if __name__ == "__main__": import sys import serial s = serial.Serial(port=sys.argv[1], baudrate=9600, dsrdtr=True, timeout=0.25) #s.write("\r\r") #print get_id(s) data = download(s) file(sys.argv[2], "wb").write(data) chirp-0.3.1/chirp/vx3.py0000644000016101777760000001754712130403635016227 0ustar jenkinsnogroup00000000000000# Copyright 2011 Rick Farina # based on modification of Dan Smith's original work # # 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 2 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 chirp import chirp_common, yaesu_clone, directory from chirp import bitwise #interesting offsets which may be checksums needed later #0x0393 checksum1? #0x0453 checksum1a? #0x0409 checksum2? #0x04C9 checksum2a? MEM_FORMAT = """ #seekto 0x7F4A; u8 checksum; #seekto 0x0B7A; struct { u8 name[6]; } bank_names[24]; #seekto 0x20CA; struct { u8 even_pskip:1, even_skip:1, even_valid:1, even_masked:1, odd_pskip:1, odd_skip:1, odd_valid:1, odd_masked:1; } flags[900]; #seekto 0x244A; struct { u8 unknown1; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown2:4, tmode:2; u8 name[6]; bbcd offset[3]; u8 unknown3:2, tone:6; u8 unknown4:1, dcs:7; u8 unknown5; u8 unknown6; u8 unknown7:4, automode:1, unknown8:3; } memory[900]; """ #fix auto mode setting and auto step setting DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM", "FM"] # last is auto TMODES = ["", "Tone", "TSQL", "DTCS"] #still need to verify 9 is correct, and add auto: look at byte 1 and 20 STEPS = [ 5.0, 9, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0 ] #STEPS = list(chirp_common.TUNING_STEPS) #STEPS.remove(6.25) #STEPS.remove(30.0) #STEPS.append(100.0) #STEPS.append(9.0) #this fails because 9 is out of order in the list #Empty char should be 0xFF but right now we are coding in a space CHARSET = list("0123456789" + \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + \ "+-/\x00[](){}\x00\x00_" + \ ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + \ ("\x00" * 64)) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50), chirp_common.PowerLevel("Low", watts=0.10)] class VX3Bank(chirp_common.NamedBank): """A VX3 Bank""" def get_name(self): _bank = self._model._radio._memobj.bank_names[self.index] name = "" for i in _bank.name: if i == 0xFF: break name += CHARSET[i & 0x7F] return name def set_name(self, name): name = name.upper() _bank = self._model._radio._memobj.bank_names[self.index] _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]] class VX3BankModel(chirp_common.BankModel): """A VX-3 bank model""" def get_num_banks(self): return 24 def get_banks(self): _banks = self._radio._memobj.bank_names banks = [] for i in range(0, self.get_num_banks()): bank = VX3Bank(self, "%i" % i, "Bank-%i" % i) bank.index = i banks.append(bank) return banks def _wipe_memory(mem): mem.set_raw("\x00" * (mem.size() / 8)) #the following settings are set to match the defaults #on the radio, some of these fields are unknown mem.name = [0xFF for _i in range(0, 6)] mem.unknown5 = 0x0D #not sure what this is mem.unknown7 = 0x01 #this likely is part of autostep mem.automode = 0x01 #autoselect mode @directory.register class VX3Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-3""" BAUD_RATE = 19200 VENDOR = "Yaesu" MODEL = "VX-3" # 41 48 30 32 38 _model = "AH028" _memsize = 32587 _block_lengths = [ 10, 32577 ] #right now this reads in 45 seconds and writes in 123 seconds #attempts to speed it up appear unstable, more testing required _block_size = 8 def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_bank_names = True rf.has_dtcs_polarity = False rf.valid_modes = list(set(MODES)) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(500000, 999000000)] rf.valid_skips = ["", "S", "P"] rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 6 rf.memory_bounds = (1, 900) rf.can_odd_split = True rf.has_ctone = False return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): _mem = self._memobj.memory[number-1] _flag = self._memobj.flags[(number-1)/2] nibble = ((number-1) % 2) and "even" or "odd" used = _flag["%s_masked" % nibble] valid = _flag["%s_valid" % nibble] pskip = _flag["%s_pskip" % nibble] skip = _flag["%s_skip" % nibble] mem = chirp_common.Memory() mem.number = number if not used: mem.empty = True if not valid: mem.empty = True mem.power = POWER_LEVELS[0] return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = int(_mem.offset) * 1000 mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = STEPS[_mem.tune_step] mem.skip = pskip and "P" or skip and "S" or "" mem.power = POWER_LEVELS[~_mem.power & 0x01] for i in _mem.name: if i == 0xFF: break mem.name += CHARSET[i & 0x7F] mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flag = self._memobj.flags[(mem.number-1)/2] nibble = ((mem.number-1) % 2) and "even" or "odd" used = _flag["%s_masked" % nibble] valid = _flag["%s_valid" % nibble] if not mem.empty and not valid: _wipe_memory(_mem) if mem.empty and valid and not used: _flag["%s_valid" % nibble] = False return _flag["%s_masked" % nibble] = not mem.empty if mem.empty: return _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power == POWER_LEVELS[1]: # Low _mem.power = 0x00 else: # Default to High _mem.power = 0x03 _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" for i in range(0, 6): _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i]) if mem.name.strip(): _mem.name[0] |= 0x80 def validate_memory(self, mem): msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem) return msgs def get_bank_model(self): return VX3BankModel(self) chirp-0.3.1/chirp/ic2100.py0000644000016100007500000001530312023560645014736 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, icf, util, directory from chirp import bitwise, memmap MEM_FORMAT = """ struct { bbcd freq[2]; u8 freq_10khz:4, freq_1khz:3, zero:1; u8 unknown1; bbcd offset[2]; u8 is_12_5:1, unknownbits:3, duplex:2, tmode:2; u8 ctone; u8 rtone; char name[6]; u8 unknown3; } memory[100]; #seekto 0x0640; struct { bbcd freq[2]; u8 freq_10khz:4, freq_1khz:3, zero:1; u8 unknown1; bbcd offset[2]; u8 is_12_5:1, unknownbits:3, duplex:2, tmode:2; u8 ctone; u8 rtone; } special[7]; #seekto 0x0680; struct { bbcd freq[2]; u8 freq_10khz:4, freq_1khz:3, zero:1; u8 unknown1; bbcd offset[2]; u8 is_12_5:1, unknownbits:3, duplex:2, tmode:2; u8 ctone; u8 rtone; } call[2]; #seekto 0x06F0; struct { u8 flagbits; } skipflags[14]; #seekto 0x0700; struct { u8 flagbits; } usedflags[14]; """ TMODES = ["", "Tone", "", "TSQL"] DUPLEX = ["", "", "+", "-"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0] def _get_special(): special = { "C": 506 } for i in range(0, 3): ida = "%iA" % (i + 1) idb = "%iB" % (i + 1) num = 500 + (i * 2) special[ida] = num special[idb] = num + 1 return special def _get_freq(mem): freq = (int(mem.freq) * 100000) + \ (mem.freq_10khz * 10000) + \ (mem.freq_1khz * 1000) if mem.is_12_5: if chirp_common.is_12_5(freq): pass elif mem.freq_1khz == 2: freq += 500 elif mem.freq_1khz == 5: freq += 2500 elif mem.freq_1khz == 7: freq += 500 else: raise Exception("Unable to resolve 12.5kHz: %i" % freq) return freq def _set_freq(mem, freq): mem.freq = freq / 100000 mem.freq_10khz = (freq / 10000) % 10 khz = (freq / 1000) % 10 mem.freq_1khz = khz mem.is_12_5 = chirp_common.is_12_5(freq) def _get_offset(mem): raw = memmap.MemoryMap(mem.get_raw()) if ord(raw[5]) & 0x0A: raw[5] = ord(raw[5]) & 0xF0 mem.set_raw(raw.get_packed()) offset = int(mem.offset) * 1000 + 5000 raw[5] = ord(raw[5]) | 0x0A mem.set_raw(raw.get_packed()) return offset else: return int(mem.offset) * 1000 def _set_offset(mem, offset): if (offset % 10) == 5000: extra = 0x0A offset -= 5000 else: extra = 0x00 mem.offset = offset / 1000 raw = memmap.MemoryMap(mem.get_raw()) raw[5] = ord(raw[5]) | extra mem.set_raw(raw.get_packed()) def _wipe_memory(mem, char): mem.set_raw(char * (mem.size() / 8)) @directory.register class IC2100Radio(icf.IcomCloneModeRadio): """Icom IC-2100""" VENDOR = "Icom" MODEL = "IC-2100H" _model = "\x20\x88\x00\x01" _memsize = 2016 _endframe = "Icom Inc\x2e" _ranges = [(0x0000, 0x07E0, 32)] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 100) rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_tuning_step = False rf.has_mode = False rf.valid_modes = ["FM"] rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(118000000, 174000000)] rf.valid_skips = ["", "S"] rf.valid_special_chans = sorted(_get_special().keys()) return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_memory(self, number): mem = chirp_common.Memory() if isinstance(number, str): if number == "C": number = _get_special()[number] _mem = self._memobj.call[0] else: number = _get_special()[number] _mem = self._memobj.special[number - 500] empty = False else: number -= 1 _mem = self._memobj.memory[number] _emt = self._memobj.usedflags[number / 8].flagbits empty = (1 << (number % 8)) & int(_emt) if not empty: mem.name = str(_mem.name).rstrip() _skp = self._memobj.skipflags[number / 8].flagbits isskip = (1 << (number % 8)) & int(_skp) mem.number = number + 1 if number <= 100: mem.skip = isskip and "S" or "" else: mem.extd_number = util.get_dict_rev(_get_special(), number) mem.immutable = ["number", "skip", "extd_number"] if empty: mem.empty = True return mem mem.freq = _get_freq(_mem) mem.offset = _get_offset(_mem) mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] return mem def set_memory(self, mem): if mem.number == "C": _mem = self._memobj.call[0] elif isinstance(mem.number, str): _mem = self._memobj.special[_get_special[mem.number] - 500] else: number = mem.number - 1 _mem = self._memobj.memory[number] _emt = self._memobj.usedflags[number / 8].flagbits mask = 1 << (number % 8) if mem.empty: _emt |= mask else: _emt &= ~mask _skp = self._memobj.skipflags[number / 8].flagbits if mem.skip == "S": _skp |= mask else: _skp &= ~mask _mem.name = mem.name.ljust(6) _set_freq(_mem, mem.freq) _set_offset(_mem, mem.offset) _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) chirp-0.3.1/chirp/icx8x_ll.py0000644000016100007500000003022511717005656015577 0ustar jenkins00000000000000# Copyright 2008 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 . import struct from chirp import chirp_common, errors from chirp.memmap import MemoryMap from chirp.chirp_common import to_MHz POS_FREQ_START = 0 POS_FREQ_END = 2 POS_OFFSET = 2 POS_NAME_START = 4 POS_NAME_END = 9 POS_RTONE = 9 POS_CTONE = 10 POS_DTCS = 11 POS_TUNE_STEP = 17 POS_TMODE = 21 POS_MODE = 21 POS_MULT_FLAG = 21 POS_DTCS_POL = 22 POS_DUPLEX = 22 POS_DIG = 23 POS_TXI = 23 POS_FLAGS_START= 0x1370 POS_MYCALL = 0x15E0 POS_URCALL = 0x1640 POS_RPCALL = 0x16A0 POS_RP2CALL = 0x1700 MEM_LOC_SIZE = 24 ICx8x_SPECIAL = { "C" : 206 } ICx8x_SPECIAL_REV = { 206 : "C" } for i in range(0, 3): idA = "%iA" % i idB = "%iB" % i num = 200 + i * 2 ICx8x_SPECIAL[idA] = num ICx8x_SPECIAL[idB] = num + 1 ICx8x_SPECIAL_REV[num] = idA ICx8x_SPECIAL_REV[num+1] = idB def bank_name(index): char = chr(ord("A") + index) return "BANK-%s" % char def get_freq(mmap, base): if (ord(mmap[POS_MULT_FLAG]) & 0x80) == 0x80: mult = 6250 else: mult = 5000 val = struct.unpack(">= 4 icx8x_ts = list(chirp_common.TUNING_STEPS) del icx8x_ts[1] try: return icx8x_ts[tsidx] except IndexError: raise errors.InvalidDataError("TS index %i out of range (%i)" % (tsidx, len(icx8x_ts))) def set_tune_step(mmap, tstep): val = struct.unpack("B", mmap[POS_TUNE_STEP])[0] & 0x0F icx8x_ts = list(chirp_common.TUNING_STEPS) del icx8x_ts[1] tsidx = icx8x_ts.index(tstep) val |= (tsidx << 4) mmap[POS_TUNE_STEP] = val def get_mode(mmap): val = struct.unpack("B", mmap[POS_DIG])[0] & 0x08 if val == 0x08: return "DV" val = struct.unpack("B", mmap[POS_MODE])[0] & 0x20 if val == 0x20: return "NFM" else: return "FM" def set_mode(mmap, mode): dig = struct.unpack("B", mmap[POS_DIG])[0] & 0xF7 val = struct.unpack("B", mmap[POS_MODE])[0] & 0xDF if mode == "FM": pass elif mode == "NFM": val |= 0x20 elif mode == "DV": dig |= 0x08 else: raise errors.InvalidDataError("%s mode not supported" % mode) mmap[POS_DIG] = dig mmap[POS_MODE] = val def is_used(mmap, number): if number == ICx8x_SPECIAL["C"]: return True return (ord(mmap[POS_FLAGS_START + number]) & 0x20) == 0 def set_used(mmap, number, used=True): if number == ICx8x_SPECIAL["C"]: return val = struct.unpack("B", mmap[POS_FLAGS_START + number])[0] & 0xDF if not used: val |= 0x20 mmap[POS_FLAGS_START + number] = val def get_skip(mmap, number): val = struct.unpack("B", mmap[POS_FLAGS_START + number])[0] & 0x10 if val != 0: return "S" else: return "" def set_skip(mmap, number, skip): if skip == "P": raise errors.InvalidDataError("PSKIP not supported by this model") val = struct.unpack("B", mmap[POS_FLAGS_START + number])[0] & 0xEF if skip == "S": val |= 0x10 mmap[POS_FLAGS_START + number] = val def get_call_indices(mmap): return ord(mmap[18]) & 0x0F, \ (ord(mmap[19]) & 0xF0) >> 4, \ ord(mmap[19]) & 0x0F def set_call_indices(_map, mmap, urcall, r1call, r2call): ulist = [] for i in range(0, 6): ulist.append(get_urcall(_map, i)) rlist = [] for i in range(0, 6): rlist.append(get_rptcall(_map, i)) try: if not urcall: uindex = 0 else: uindex = ulist.index(urcall) except ValueError: raise errors.InvalidDataError("Call `%s' not in URCALL list" % urcall) try: if not r1call: r1index = 0 else: r1index = rlist.index(r1call) except ValueError: raise errors.InvalidDataError("Call `%s' not in RCALL list" % r1call) try: if not r2call: r2index = 0 else: r2index = rlist.index(r2call) except ValueError: raise errors.InvalidDataError("Call `%s' not in RCALL list" % r2call) mmap[18] = (ord(mmap[18]) & 0xF0) | uindex mmap[19] = (r1index << 4) | r2index # -- def get_mem_offset(number): return number * MEM_LOC_SIZE def get_raw_memory(mmap, number): offset = get_mem_offset(number) return MemoryMap(mmap[offset:offset + MEM_LOC_SIZE]) def get_bank(mmap, number): val = ord(mmap[POS_FLAGS_START + number]) & 0x0F if val >= 10: return None else: return val def set_bank(mmap, number, bank): if bank > 9: raise errors.InvalidDataError("Invalid bank number %i" % bank) if bank is None: index = 0x0A else: index = bank val = ord(mmap[POS_FLAGS_START + number]) & 0xF0 val |= index mmap[POS_FLAGS_START + number] = val def _get_memory(_map, mmap, base): if get_mode(mmap) == "DV": mem = chirp_common.DVMemory() i_ucall, i_r1call, i_r2call = get_call_indices(mmap) mem.dv_urcall = get_urcall(_map, i_ucall) mem.dv_rpt1call = get_rptcall(_map, i_r1call) mem.dv_rpt2call = get_rptcall(_map, i_r2call) else: mem = chirp_common.Memory() mem.freq = get_freq(mmap, base) mem.name = get_name(mmap) mem.rtone = get_rtone(mmap) mem.ctone = get_ctone(mmap) mem.dtcs = get_dtcs(mmap) mem.dtcs_polarity = get_dtcs_polarity(mmap) mem.offset = get_dup_offset(mmap) mem.duplex = get_duplex(mmap) mem.tmode = get_tone_enabled(mmap) mem.tuning_step = get_tune_step(mmap) mem.mode = get_mode(mmap) return mem def get_memory(_map, number, base): if not is_used(_map, number): mem = chirp_common.Memory() if number < 200: mem.number = number mem.empty = True return mem else: mmap = get_raw_memory(_map, number) mem = _get_memory(_map, mmap, base) mem.number = number if number < 200: mem.skip = get_skip(_map, number) else: mem.extd_number = ICx8x_SPECIAL_REV[number] mem.immutable = ["number", "skip", "bank", "bank_index", "extd_number"] return mem def clear_tx_inhibit(mmap): txi = struct.unpack("B", mmap[POS_TXI])[0] txi |= 0x40 mmap[POS_TXI] = txi def set_memory(_map, memory, base): mmap = get_raw_memory(_map, memory.number) set_freq(mmap, memory.freq, base) set_name(mmap, memory.name) set_rtone(mmap, memory.rtone) set_ctone(mmap, memory.ctone) set_dtcs(mmap, memory.dtcs) set_dtcs_polarity(mmap, memory.dtcs_polarity) set_dup_offset(mmap, memory.offset) set_duplex(mmap, memory.duplex) set_tone_enabled(mmap, memory.tmode) set_tune_step(mmap, memory.tuning_step) set_mode(mmap, memory.mode) if memory.number < 200: set_skip(_map, memory.number, memory.skip) if isinstance(memory, chirp_common.DVMemory): set_call_indices(_map, mmap, memory.dv_urcall, memory.dv_rpt1call, memory.dv_rpt2call) if not is_used(_map, memory.number): clear_tx_inhibit(mmap) _map[get_mem_offset(memory.number)] = mmap.get_packed() set_used(_map, memory.number) return _map def erase_memory(_map, number): set_used(_map, number, False) return _map def call_location(base, index): return base + (16 * index) def get_urcall(mmap, index): if index > 5: raise errors.InvalidDataError("URCALL index %i must be <= 5" % index) start = call_location(POS_URCALL, index) return mmap[start:start+8].rstrip() def get_rptcall(mmap, index): if index > 5: raise errors.InvalidDataError("RPTCALL index %i must be <= 5" % index) start = call_location(POS_RPCALL, index) return mmap[start:start+8].rstrip() def get_mycall(mmap, index): if index > 5: raise errors.InvalidDataError("MYCALL index %i must be <= 5" % index) start = call_location(POS_MYCALL, index) return mmap[start:start+8].rstrip() def set_urcall(mmap, index, call): if index > 5: raise errors.InvalidDataError("URCALL index %i must be <= 5" % index) start = call_location(POS_URCALL, index) mmap[start] = call.ljust(12) return mmap def set_rptcall(mmap, index, call): if index > 5: raise errors.InvalidDataError("RPTCALL index %i must be <= 5" % index) start = call_location(POS_RPCALL, index) mmap[start] = call.ljust(12) start = call_location(POS_RP2CALL, index) mmap[start] = call.ljust(12) return mmap def set_mycall(mmap, index, call): if index > 5: raise errors.InvalidDataError("MYCALL index %i must be <= 5" % index) start = call_location(POS_MYCALL, index) mmap[start] = call.ljust(12) return mmap chirp-0.3.1/chirp/import_logic.py0000644000016101777760000002067012130403635020165 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . from chirp import chirp_common, errors class ImportError(Exception): """An import error""" pass class DestNotCompatible(ImportError): """Memory is not compatible with the destination radio""" pass def ensure_has_calls(radio, memory): """Make sure @radio has the necessary D-STAR callsigns for @memory""" ulist_changed = rlist_changed = False ulist = radio.get_urcall_list() rlist = radio.get_repeater_call_list() if memory.dv_urcall and memory.dv_urcall not in ulist: for i in range(0, len(ulist)): if not ulist[i].strip(): ulist[i] = memory.dv_urcall ulist_changed = True break if not ulist_changed: raise errors.RadioError("No room to add callsign %s" % memory.dv_urcall) rlist_add = [] if memory.dv_rpt1call and memory.dv_rpt1call not in rlist: rlist_add.append(memory.dv_rpt1call) if memory.dv_rpt2call and memory.dv_rpt2call not in rlist: rlist_add.append(memory.dv_rpt2call) while rlist_add: call = rlist_add.pop() for i in range(0, len(rlist)): if not rlist[i].strip(): rlist[i] = call call = None rlist_changed = True break if call: raise errors.RadioError("No room to add callsign %s" % call) if ulist_changed: radio.set_urcall_list(ulist) if rlist_changed: radio.set_repeater_call_list(rlist) # Filter the name according to the destination's rules def _import_name(dst_radio, _srcrf, mem): mem.name = dst_radio.filter_name(mem.name) def _import_power(dst_radio, _srcrf, mem): levels = dst_radio.get_features().valid_power_levels if not levels: mem.power = None return elif mem.power is None: # Source radio did not support power levels, so choose the # first (highest) level from the destination radio. mem.power = levels[0] return # If both radios support power levels, we need to decide how to # convert the source power level to a valid one for the destination # radio. To do that, find the absolute level of the source value # and calculate the different between it and all the levels of the # destination, choosing the one that matches most closely. deltas = [abs(mem.power - power) for power in levels] mem.power = levels[deltas.index(min(deltas))] def _import_tone(dst_radio, srcrf, mem): dstrf = dst_radio.get_features() # Some radios keep separate tones for Tone and TSQL modes (rtone and # ctone). If we're importing to or from radios with differing models, # do the conversion here. if srcrf.has_ctone and not dstrf.has_ctone: # If copying from a radio with separate rtone/ctone to a radio # without, and the tmode is TSQL, then use the ctone value if mem.tmode == "TSQL": mem.rtone = mem.ctone elif not srcrf.has_ctone and dstrf.has_ctone: # If copying from a radio without separate rtone/ctone to a radio # with it, set the dest ctone to the src rtone if mem.tmode == "TSQL": mem.ctone = mem.rtone def _import_dtcs(dst_radio, srcrf, mem): dstrf = dst_radio.get_features() # Some radios keep separate DTCS codes for tx and rx # If we're importing to or from radios with differing models, # do the conversion here. if srcrf.has_rx_dtcs and not dstrf.has_rx_dtcs: # If copying from a radio with separate codes to a radio # without, and the tmode is DTCS, then use the rx_dtcs value if mem.tmode == "DTCS": mem.dtcs = mem.rx_dtcs elif not srcrf.has_rx_dtcs and dstrf.has_rx_dtcs: # If copying from a radio without separate codes to a radio # with it, set the dest rx_dtcs to the src dtcs if mem.tmode == "DTCS": mem.rx_dtcs = mem.dtcs def _guess_mode_by_frequency(freq): ranges = [ (0, 136000000, "AM"), (136000000, 9999000000, "FM"), ] for lo, hi, mode in ranges: if freq > lo and freq <= hi: return mode # If we don't know, assume FM return "FM" def _import_mode(dst_radio, srcrf, mem): dstrf = dst_radio.get_features() # Some radios support an "Auto" mode. If we're importing from one # that does to one that does not, guess at the proper mode based on the # frequency if mem.mode == "Auto" and mem.mode not in dstrf.valid_modes: mode = _guess_mode_by_frequency(mem.freq) if mode not in dstrf.valid_modes: raise DestNotCompatible("Destination does not support %s" % mode) mem.mode = mode def _make_offset_with_split(rxfreq, txfreq): offset = txfreq - rxfreq if offset == 0: return "", offset elif offset > 0: return "+", offset elif offset < 0: return "-", offset * -1 def _import_duplex(dst_radio, srcrf, mem): dstrf = dst_radio.get_features() # If a radio does not support odd split, we can use an equivalent offset if mem.duplex == "split" and mem.duplex not in dstrf.valid_duplexes: mem.duplex, mem.offset = _make_offset_with_split(mem.freq, mem.offset) # Enforce maximum offset ranges = [ ( 0, 500000000, 15000000), (500000000, 3000000000, 50000000), ] for lo, hi, limit in ranges: if lo < mem.freq <= hi: if abs(mem.offset) > limit: raise DestNotCompatible("Unable to create import memory: " "offset is abnormally large.") def import_mem(dst_radio, src_features, src_mem, overrides={}): """Perform import logic to create a destination memory from src_mem that will be compatible with @dst_radio""" dst_rf = dst_radio.get_features() if isinstance(src_mem, chirp_common.DVMemory): if not isinstance(dst_radio, chirp_common.IcomDstarSupport): raise DestNotCompatible("Destination radio does not support D-STAR") if dst_rf.requires_call_lists: ensure_has_calls(dst_radio, src_mem) dst_mem = src_mem.dupe() for k, v in overrides.items(): dst_mem.__dict__[k] = v helpers = [_import_name, _import_power, _import_tone, _import_dtcs, _import_mode, _import_duplex, ] for helper in helpers: helper(dst_radio, src_features, dst_mem) msgs = dst_radio.validate_memory(dst_mem) errs = [x for x in msgs if isinstance(x, chirp_common.ValidationError)] if errs: raise DestNotCompatible("Unable to create import memory: %s" %\ ", ".join(errs)) return dst_mem def import_bank(dst_radio, src_radio, dst_mem, src_mem): """Attempt to set the same banks for @mem(by index) in @dst_radio that it has in @src_radio""" dst_bm = dst_radio.get_bank_model() if not dst_bm: return dst_banks = dst_bm.get_banks() src_bm = src_radio.get_bank_model() if not src_bm: return src_banks = src_bm.get_banks() src_mem_banks = src_bm.get_memory_banks(src_mem) src_indexes = [src_banks.index(b) for b in src_mem_banks] for bank in dst_bm.get_memory_banks(dst_mem): dst_bm.remove_memory_from_bank(dst_mem, bank) for index in src_indexes: try: bank = dst_banks[index] print "Adding memory to bank %s" % bank dst_bm.add_memory_to_bank(dst_mem, bank) if isinstance(dst_bm, chirp_common.BankIndexInterface): dst_bm.set_memory_index(dst_mem, bank, dst_bm.get_next_bank_index(bank)) except IndexError: pass chirp-0.3.1/chirp/memmap.py0000644000016100007500000000513412023560645015315 0ustar jenkins00000000000000# Copyright 2008 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 . from chirp import util class MemoryMap: """ A pythonic memory map interface """ def __init__(self, data): self._data = list(data) def printable(self, start=None, end=None, printit=True): """Return a printable representation of the memory map""" if not start: start = 0 if not end: end = len(self._data) string = util.hexprint(self._data[start:end]) if printit: print string return string def get(self, start, length=1): """Return a chunk of memory of @length bytes from @start""" if start == -1: return "".join(self._data[start:]) else: return "".join(self._data[start:start+length]) def set(self, pos, value): """Set a chunk of memory at @pos to @value""" if isinstance(value, int): self._data[pos] = chr(value) elif isinstance(value, str): for byte in value: self._data[pos] = byte pos += 1 else: raise ValueError("Unsupported type %s for value" % \ type(value).__name__) def get_packed(self): """Return the entire memory map as raw data""" return "".join(self._data) def __len__(self): return len(self._data) def __getslice__(self, start, end): return self.get(start, end-start) def __getitem__(self, pos): return self.get(pos) def __setitem__(self, pos, value): """ NB: Setting a value of more than one character overwrites len(value) bytes of the map, unlike a typical array! """ self.set(pos, value) def __str__(self): return self.get_packed() def __repr__(self): return self.printable(printit=False) def truncate(self, size): """Truncate the memory map to @size""" self._data = self._data[:size] chirp-0.3.1/chirp/ic9x_icf.py0000644000016100007500000000454412023560645015542 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, icf, ic9x_icf_ll, util, directory, errors @directory.register class IC9xICFRadio(chirp_common.CloneModeRadio): VENDOR = "Icom" MODEL = "IC-91/92AD" VARIANT = "ICF File" _model = None _upper = 1200 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.memory_bounds = (0, self._upper) rf.has_sub_devices = True rf.valid_modes = ["FM", "AM"] if "A" in self.VARIANT: rf.valid_modes.append("WFM") else: rf.valid_modes.append("DV") rf.valid_modes.append("NFM") return rf def get_raw_memory(self, number): raw = ic9x_icf_ll.get_raw_memory(self._mmap, number).get_packed() return util.hexprint(raw) def get_memory(self, number): return ic9x_icf_ll.get_memory(self._mmap, number) def load_mmap(self, filename): _mdata, self._mmap = icf.read_file(filename) def get_sub_devices(self): return [IC9xICFRadioA(self._mmap), IC9xICFRadioB(self._mmap)] class IC9xICFRadioA(IC9xICFRadio): VARIANT = "ICF File Band A" _upper = 800 def get_memory(self, number): if number > self._upper: raise errors.InvalidMemoryLocation("Number must be <800") return ic9x_icf_ll.get_memory(self._mmap, number) class IC9xICFRadioB(IC9xICFRadio): VARIANT = "ICF File Band B" _upper = 400 def get_memory(self, number): if number > self._upper: raise errors.InvalidMemoryLocation("Number must be <400") mem = ic9x_icf_ll.get_memory(self._mmap, 850 + number) mem.number = number return mem chirp-0.3.1/chirp/icx8x.py0000644000016100007500000001330312023560645015101 0ustar jenkins00000000000000# Copyright 2008 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 . from chirp import chirp_common, icf, icx8x_ll, errors, directory def _isuhf(pipe): try: md = icf.get_model_data(pipe) val = ord(md[20]) uhf = val & 0x10 except: raise errors.RadioError("Unable to probe radio band") print "Radio is a %s82" % (uhf and "U" or "V") return uhf @directory.register class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom IC-V/U82""" VENDOR = "Icom" MODEL = "IC-V82/U82" _model = "\x28\x26\x00\x01" _memsize = 6464 _endframe = "Icom Inc\x2eCD" _memories = [] _ranges = [(0x0000, 0x1340, 32), (0x1340, 0x1360, 16), (0x1360, 0x136B, 8), (0x1370, 0x1440, 32), (0x1460, 0x15D0, 32), (0x15E0, 0x1930, 32), (0x1938, 0x1940, 8), ] MYCALL_LIMIT = (0, 6) URCALL_LIMIT = (0, 6) RPTCALL_LIMIT = (0, 6) def _get_bank(self, loc): return icx8x_ll.get_bank(self._mmap, loc) def _set_bank(self, loc, bank): return icx8x_ll.set_bank(self._mmap, loc, bank) def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.valid_modes = ["FM", "NFM", "DV"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = ["", "-", "+"] rf.valid_tuning_steps = [x for x in chirp_common.TUNING_STEPS if x != 6.25] if self._isuhf: rf.valid_bands = [(420000000, 470000000)] else: rf.valid_bands = [(118000000, 176000000)] rf.valid_skips = ["", "S"] rf.valid_name_length = 5 rf.valid_special_chans = sorted(icx8x_ll.ICx8x_SPECIAL.keys()) return rf def _get_type(self): flag = (_isuhf(self.pipe) != 0) if self._isuhf is not None and (self._isuhf != flag): raise errors.RadioError("VHF/UHF model mismatch") self._isuhf = flag return flag def __init__(self, pipe): icf.IcomCloneModeRadio.__init__(self, pipe) # Until I find a better way, I'll stash a boolean to indicate # UHF-ness in an unused region of memory. If we're opening a # file, look for the flag. If we're syncing from serial, set # that flag. if isinstance(pipe, str): self._isuhf = (ord(self._mmap[0x1930]) != 0) #print "Found %s image" % (self.isUHF and "UHF" or "VHF") else: self._isuhf = None def sync_in(self): self._get_type() icf.IcomCloneModeRadio.sync_in(self) self._mmap[0x1930] = self._isuhf and 1 or 0 def sync_out(self): self._get_type() icf.IcomCloneModeRadio.sync_out(self) def get_memory(self, number): if not self._mmap: self.sync_in() if self._isuhf: base = 400 else: base = 0 if isinstance(number, str): try: number = icx8x_ll.ICx8x_SPECIAL[number] except KeyError: raise errors.InvalidMemoryLocation("Unknown channel %s" % \ number) return icx8x_ll.get_memory(self._mmap, number, base) def set_memory(self, memory): if not self._mmap: self.sync_in() if self._isuhf: base = 400 else: base = 0 if memory.empty: self._mmap = icx8x_ll.erase_memory(self._mmap, memory.number) else: self._mmap = icx8x_ll.set_memory(self._mmap, memory, base) def get_raw_memory(self, number): return icx8x_ll.get_raw_memory(self._mmap, number) def get_urcall_list(self): calls = [] for i in range(*self.URCALL_LIMIT): call = icx8x_ll.get_urcall(self._mmap, i) calls.append(call) return calls def get_repeater_call_list(self): calls = [] for i in range(*self.RPTCALL_LIMIT): call = icx8x_ll.get_rptcall(self._mmap, i) calls.append(call) return calls def get_mycall_list(self): calls = [] for i in range(*self.MYCALL_LIMIT): call = icx8x_ll.get_mycall(self._mmap, i) calls.append(call) return calls def set_urcall_list(self, calls): for i in range(*self.URCALL_LIMIT): try: call = calls[i] except IndexError: call = " " * 8 icx8x_ll.set_urcall(self._mmap, i, call) def set_repeater_call_list(self, calls): for i in range(*self.RPTCALL_LIMIT): try: call = calls[i] except IndexError: call = " " * 8 icx8x_ll.set_rptcall(self._mmap, i, call) def set_mycall_list(self, calls): for i in range(*self.MYCALL_LIMIT): try: call = calls[i] except IndexError: call = " " * 8 icx8x_ll.set_mycall(self._mmap, i, call) chirp-0.3.1/chirp/vx6.py0000644000016101777760000002053712120543516016225 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . from chirp import chirp_common, yaesu_clone, directory from chirp import bitwise # flags.{even|odd}_pskip: These are actually "preferential *scan* channels". # Is that what they mean on other radios as well? # memory { # step_changed: Channel step has been changed. Bit stays on even after # you switch back to default step. Don't know why you would # care # half_deviation: 2.5 kHz deviation # cpu_shifted: CPU freq has been shifted (to move a birdie out of channel) # power: 0-3: ["L1", "L2", "L3", "Hi"] # pager: Set if this is a paging memory # tmodes: 0-7: ["", "Tone", "TSQL", "DTCS", "Rv Tn", "D Code", # "T DCS", "D Tone"] # Rv Tn: Reverse CTCSS - mutes receiver on tone # The final 3 are for split: # D Code: DCS Encode only # T DCS: Encodes tone, decodes DCS code # D Tone: Encodes DCS code, decodes tone # } MEM_FORMAT = """ #seekto 0x018A; u16 bank_sizes[24]; #seekto 0x097A; struct { u8 name[6]; } bank_names[24]; #seekto 0x0C0A; struct { u16 channel[100]; } bank_channels[24]; #seekto 0x1ECA; struct { u8 even_pskip:1, even_skip:1, even_valid:1, even_masked:1, odd_pskip:1, odd_skip:1, odd_valid:1, odd_masked:1; } flags[450]; #seekto 0x21CA; struct { u8 unknown11:1, step_changed:1, half_deviation:1, cpu_shifted:1, unknown12:4; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown2:2, pager:1, tmode:3; u8 name[6]; bbcd offset[3]; u8 tone; u8 dcs; u8 unknown5; } memory[900]; """ DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM", "FM"] # last is auto TMODES = ["", "Tone", "TSQL", "DTCS"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0, 200.0, 5.0] # last is auto, 9.0k and 200.0k are unadvertised CHARSET = ["%i" % int(x) for x in range(0, 10)] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ list(" +-/\x00[]__" + ("\x00" * 9) + "$%%\x00**.|=\\\x00@") + \ list("\x00" * 100) POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("L3", watts=2.50), chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L1", watts=0.30)] POWER_LEVELS_220 = [chirp_common.PowerLevel("Hi", watts=1.50), chirp_common.PowerLevel("L3", watts=1.00), chirp_common.PowerLevel("L2", watts=0.50), chirp_common.PowerLevel("L1", watts=0.20)] @directory.register class VX6Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-6""" BAUD_RATE = 19200 VENDOR = "Yaesu" MODEL = "VX-6" _model = "AH021" _memsize = 32587 _block_lengths = [10, 32578] _block_size = 16 def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_dtcs_polarity = False rf.valid_modes = ["FM", "WFM", "AM", "NFM"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = DUPLEX rf.valid_tuning_steps = STEPS rf.valid_power_levels = POWER_LEVELS rf.memory_bounds = (1, 900) rf.valid_bands = [(500000, 998990000)] rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 6 rf.can_odd_split = True rf.has_ctone = False return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) + \ repr(self._memobj.flags[(number-1)/2]) def get_memory(self, number): _mem = self._memobj.memory[number-1] _flg = self._memobj.flags[(number-1)/2] nibble = ((number-1) % 2) and "even" or "odd" used = _flg["%s_masked" % nibble] valid = _flg["%s_valid" % nibble] pskip = _flg["%s_pskip" % nibble] skip = _flg["%s_skip" % nibble] mem = chirp_common.Memory() mem.number = number if not used: mem.empty = True if not valid: mem.empty = True mem.power = POWER_LEVELS[0] return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone & 0x3f] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.mode = MODES[_mem.mode] if mem.mode == "FM" and _mem.half_deviation: mem.mode = "NFM" mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs & 0x7f] mem.tuning_step = STEPS[_mem.tune_step] mem.skip = pskip and "P" or skip and "S" or "" if mem.freq > 220000000 and mem.freq < 225000000: mem.power = POWER_LEVELS_220[3 - _mem.power] else: mem.power = POWER_LEVELS[3 - _mem.power] for i in _mem.name: if i == 0xFF: break mem.name += CHARSET[i & 0x7F] mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flag = self._memobj.flags[(mem.number-1)/2] nibble = ((mem.number-1) % 2) and "even" or "odd" used = _flag["%s_masked" % nibble] valid = _flag["%s_valid" % nibble] # initialize new channel to safe defaults if not mem.empty and not valid: _flag["%s_valid" % nibble] = True _mem.unknown11 = 0 _mem.step_changed = 0 _mem.cpu_shifted = 0 _mem.unknown12 = 0 _mem.unknown2 = 0 _mem.pager = 0 _mem.unknown5 = 0 if mem.empty and valid and not used: _flag["%s_valid" % nibble] = False return _flag["%s_masked" % nibble] = not mem.empty if mem.empty: return _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) if mem.mode == "NFM": _mem.mode = MODES.index("FM") _mem.half_deviation = 1 else: _mem.mode = MODES.index(mem.mode) _mem.half_deviation = 0 _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power: _mem.power = 3 - POWER_LEVELS.index(mem.power) else: _mem.power = 0 _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" _mem.name = [0xFF] * 6 for i in range(0, 6): _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i]) if mem.name.strip(): _mem.name[0] |= 0x80 # def get_banks(self): # _banks = self._memobj.bank_names # # banks = [] # for bank in _banks: # name = "" # for i in bank.name: # name += CHARSET[i & 0x7F] # banks.append(name.rstrip()) # # return banks # # # Return channels for a bank. Bank given as number # def get_bank_channels(self, bank): # nchannels = 0 # size = self._memobj.bank_sizes[bank] # if size <= 198: # nchannels = 1 + size/2 # _channels = self._memobj.bank_channels[bank] # channels = [] # for i in range(0, nchannels): # channels.append(int(_channels.channel[i])) # # return channels chirp-0.3.1/chirp/vx5.py0000644000016101777760000002061712130403635016221 0ustar jenkinsnogroup00000000000000# Copyright 2011 Dan Smith # Copyright 2012 Tom Hayward # # 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 chirp import chirp_common, yaesu_clone, directory from chirp import bitwise MEM_FORMAT = """ #seekto 0x002A; struct { u8 current_member; } bank_used[5]; #seekto 0x0032; struct { struct { u8 status; u8 channel; } members[24]; } bank_groups[5]; #seekto 0x012A; struct { u8 zeros:4, pskip: 1, skip: 1, visible: 1, used: 1; } flag[220]; #seekto 0x0269; struct { u8 unknown1; u8 unknown2:2, half_deviation:1, unknown3:5; u8 unknown4:4, tuning_step:4; bbcd freq[3]; u8 icon:6, mode:2; char name[8]; bbcd offset[3]; u8 tmode:4, power:2, duplex:2; u8 unknown7:2, tone:6; u8 unknown8:1, dtcs:7; u8 unknown9; } memory[220]; #seekto 0x1D03; u8 current_bank; """ TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM"] STEPS = list(chirp_common.TUNING_STEPS) STEPS.remove(6.25) STEPS.remove(30.0) STEPS.append(100.0) STEPS.append(9.0) POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("L3", watts=2.50), chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L1", watts=0.05)] class VX5BankModel(chirp_common.BankModel): def get_num_banks(self): return 5 def get_banks(self): banks = [] for i in range(0, self.get_num_banks()): bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1)) bank.index = i banks.append(bank) return banks def add_memory_to_bank(self, memory, bank): _members = self._radio._memobj.bank_groups[bank.index].members _bank_used = self._radio._memobj.bank_used[bank.index] for i in range(0, len(_members)): if _members[i].status == 0xFF: #print "empty found, inserting %d at %d" % (memory.number, i) if self._radio._memobj.current_bank == 0xFF: self._radio._memobj.current_bank = bank.index _members[i].status = 0x00 _members[i].channel = memory.number - 1 _bank_used.current_member = i return True raise Exception(_("{bank} is full").format(bank=bank)) def remove_memory_from_bank(self, memory, bank): _members = self._radio._memobj.bank_groups[bank.index].members _bank_used = self._radio._memobj.bank_used[bank.index] found = False remaining_members = 0 for i in range(0, len(_members)): if _members[i].status == 0x00: if _members[i].channel == (memory.number - 1): _members[i].status = 0xFF found = True else: remaining_members += 1 if not found: raise Exception(_("Memory {num} not in " "bank {bank}").format(num=memory.number, bank=bank)) if not remaining_members: _bank_used.current_member = 0xFF def get_bank_memories(self, bank): memories = [] _members = self._radio._memobj.bank_groups[bank.index].members _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.current_member == 0xFF: return memories for member in _members: if member.status == 0xFF: continue memories.append(self._radio.get_memory(member.channel+1)) return memories def get_memory_banks(self, memory): banks = [] for bank in self.get_banks(): if memory.number in [x.number for x in self.get_bank_memories(bank)]: banks.append(bank) return banks @directory.register class VX5Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-5""" BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "VX-5" _model = "" _memsize = 8123 _block_lengths = [10, 16, 8097] _block_size = 8 def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x1FB9) ] def get_features(self): rf = chirp_common.RadioFeatures() rf.can_odd_split = True rf.has_bank = True rf.has_ctone = False rf.has_dtcs_polarity = False rf.valid_modes = MODES + ["NFM"] rf.valid_tmodes = TMODES rf.valid_duplexes = DUPLEX rf.memory_bounds = (1, 220) rf.valid_bands = [( 500000, 16000000), ( 48000000, 729000000), (800000000, 999000000)] rf.valid_skips = ["", "S", "P"] rf.valid_power_levels = POWER_LEVELS rf.valid_name_length = 8 rf.valid_characters = chirp_common.CHARSET_ASCII return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def get_memory(self, number): _mem = self._memobj.memory[number-1] _flg = self._memobj.flag[number-1] mem = chirp_common.Memory() mem.number = number if not _flg.visible: mem.empty = True if not _flg.used: mem.empty = True return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.duplex = DUPLEX[_mem.duplex] mem.name = self.filter_name(str(_mem.name).rstrip()) mem.mode = MODES[_mem.mode] if mem.mode == "FM" and _mem.half_deviation: mem.mode = "NFM" mem.tuning_step = STEPS[_mem.tuning_step] mem.offset = int(_mem.offset) * 1000 mem.power = POWER_LEVELS[3 - _mem.power] mem.tmode = TMODES[_mem.tmode & 0x3] # masked so bad mems can be read if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.skip = _flg.pskip and "P" or _flg.skip and "S" or "" return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flg = self._memobj.flag[mem.number-1] # initialize new channel to safe defaults if not mem.empty and not _flg.used: _flg.used = True _mem.unknown1 = 0x00 _mem.unknown2 = 0x00 _mem.unknown3 = 0x00 _mem.unknown4 = 0x00 _mem.icon = 12 # file cabinet icon _mem.unknown7 = 0x00 _mem.unknown8 = 0x00 _mem.unknown9 = 0x00 if mem.empty and _flg.used and not _flg.visible: _flg.used = False return _flg.visible = not mem.empty if mem.empty: self._wipe_memory_banks(mem) return _mem.freq = int(mem.freq / 1000) _mem.duplex = DUPLEX.index(mem.duplex) _mem.name = mem.name.ljust(8) if mem.mode == "NFM": _mem.mode = MODES.index("FM") _mem.half_deviation = 1 else: _mem.mode = MODES.index(mem.mode) _mem.half_deviation = 0 _mem.tuning_step = STEPS.index(mem.tuning_step) _mem.offset = int(mem.offset / 1000) if mem.power: _mem.power = 3 - POWER_LEVELS.index(mem.power) else: _mem.power = 0 _mem.tmode = TMODES.index(mem.tmode) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _flg.skip = mem.skip == "S" _flg.pskip = mem.skip == "P" @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def get_bank_model(self): return VX5BankModel(self) chirp-0.3.1/chirp/ft60.py0000644000016101777760000002045312105130272016250 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . import time from chirp import chirp_common, yaesu_clone, memmap, bitwise, directory from chirp import errors ACK = "\x06" def _send(pipe, data): pipe.write(data) echo = pipe.read(len(data)) if echo != data: raise errors.RadioError("Error reading echo (Bad cable?)") def _download(radio): data = "" for i in range(0, 10): chunk = radio.pipe.read(8) if len(chunk) == 8: data += chunk break elif chunk: raise Exception("Received invalid response from radio") time.sleep(1) print "Trying again..." if not data: raise Exception("Radio is not responding") _send(radio.pipe, ACK) for i in range(0, 448): chunk = radio.pipe.read(64) data += chunk _send(radio.pipe, ACK) if len(chunk) == 1 and i == 447: break elif len(chunk) != 64: raise Exception("Reading block %i was short (%i)" % (i, len(chunk))) if radio.status_fn: status = chirp_common.Status() status.cur = i * 64 status.max = radio.get_memsize() status.msg = "Cloning from radio" radio.status_fn(status) return memmap.MemoryMap(data) def _upload(radio): _send(radio.pipe, radio.get_mmap()[0:8]) ack = radio.pipe.read(1) if ack != ACK: raise Exception("Radio did not respond") for i in range(0, 448): offset = 8 + (i * 64) _send(radio.pipe, radio.get_mmap()[offset:offset+64]) ack = radio.pipe.read(1) if ack != ACK: raise Exception("Radio did not ack block %i" % i) if radio.status_fn: status = chirp_common.Status() status.cur = offset+64 status.max = radio.get_memsize() status.msg = "Cloning to radio" radio.status_fn(status) def _decode_freq(freqraw): freq = int(freqraw) * 10000 if freq > 8000000000: freq = (freq - 8000000000) + 5000 if freq > 4000000000: freq -= 4000000000 for i in range(0, 3): freq += 2500 if chirp_common.required_step(freq) == 12.5: break return freq def _encode_freq(freq): freqraw = freq / 10000 if ((freq / 1000) % 10) == 5: freqraw += 800000 if chirp_common.is_fractional_step(freq): freqraw += 400000 return freqraw MEM_FORMAT = """ #seekto 0x0238; struct { u8 used:1, unknown1:1, isnarrow:1, isam:1, duplex:4; bbcd freq[3]; u8 unknown2:1, step:3, unknown2_1:1, tmode:3; bbcd tx_freq[3]; u8 power:2, tone:6; u8 unknown4:1, dtcs:7; u8 unknown5[2]; u8 offset; u8 unknown6[3]; } memory[1000]; #seekto 0x6EC8; struct { u8 skip0:2, skip1:2, skip2:2, skip3:2; } flags[500]; #seekto 0x4700; struct { u8 name[6]; u8 use_name:1, unknown1:7; u8 valid:1, unknown2:7; } names[1000]; #seekto 0x6FC8; u8 checksum; """ DUPLEX = ["", "", "-", "+", "split"] TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.0), chirp_common.PowerLevel("Mid", watts=2.5), chirp_common.PowerLevel("Low", watts=1.0)] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] SKIPS = ["", "P", "S"] CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ [?]^__|`?$%&-()*+,-,/|;/=>?@" @directory.register class FT60Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-60""" BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "FT-60" _model = "AH017" _memsize = 28617 def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 999) rf.valid_duplexes = DUPLEX rf.valid_tmodes = TMODES rf.valid_power_levels = POWER_LEVELS rf.valid_tuning_steps = STEPS rf.valid_skips = SKIPS rf.valid_characters = CHARSET rf.valid_name_length = 6 rf.valid_modes = ["FM", "NFM", "AM"] rf.valid_bands = [(108000000, 520000000), (700000000, 999990000)] rf.can_odd_split = True rf.has_ctone = False rf.has_bank = False rf.has_dtcs_polarity = False return rf def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x6FC7) ] def sync_in(self): try: self._mmap = _download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() def sync_out(self): self.update_checksums() try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) + \ repr(self._memobj.flags[number/4]) + \ repr(self._memobj.names[number]) def get_memory(self, number): _mem = self._memobj.memory[number] _skp = self._memobj.flags[number/4] _nam = self._memobj.names[number] skip = _skp["skip%i" % (number%4)] mem = chirp_common.Memory() mem.number = number if not _mem.used: mem.empty = True return mem mem.freq = _decode_freq(_mem.freq) mem.offset = int(_mem.offset) * 50000 mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = _decode_freq(_mem.tx_freq) mem.tmode = TMODES[_mem.tmode] mem.rtone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.power = POWER_LEVELS[_mem.power] mem.mode = _mem.isam and "AM" or _mem.isnarrow and "NFM" or "FM" mem.tuning_step = STEPS[_mem.step] mem.skip = SKIPS[skip] if _nam.use_name and _nam.valid: for i in _nam.name: if i == 0xFF: break try: mem.name += CHARSET[i] except IndexError: print "Memory %i: Unknown char index: %i " % (number, i) mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _skp = self._memobj.flags[mem.number/4] _nam = self._memobj.names[mem.number] if mem.empty: _mem.used = False return if not _mem.used: _mem.set_raw("\x00" * 16) _mem.used = 1 print "Wiped" _mem.freq = _encode_freq(mem.freq) if mem.duplex == "split": _mem.tx_freq = _encode_freq(mem.offset) _mem.offset = 0 else: _mem.tx_freq = 0 _mem.offset = mem.offset / 50000 _mem.duplex = DUPLEX.index(mem.duplex) _mem.tmode = TMODES.index(mem.tmode) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0 _mem.isnarrow = mem.mode == "NFM" _mem.isam = mem.mode == "AM" _mem.step = STEPS.index(mem.tuning_step) _skp["skip%i" % (mem.number%4)] = SKIPS.index(mem.skip) for i in range(0, 6): try: _nam.name[i] = CHARSET.index(mem.name[i]) except IndexError: _nam.name[i] = CHARSET.index(" ") _nam.use_name = mem.name.strip() and True or False _nam.valid = _nam.use_name chirp-0.3.1/chirp/ft50_ll.py0000644000016100007500000001560211717005656015314 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, util, errors, memmap import time ACK = chr(0x06) MEM_LOC_BASE = 0x00AB MEM_LOC_SIZE = 16 POS_DUPLEX = 1 POS_TMODE = 2 POS_TONE = 2 POS_DTCS = 3 POS_MODE = 4 POS_FREQ = 5 POS_OFFSET = 9 POS_NAME = 11 POS_USED = 0x079C CHARSET = [str(x) for x in range(0, 10)] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ list(" ()+--*/???|0123456789") def send(s, data): s.write(data) r = s.read(len(data)) if len(r) != len(data): raise errors.RadioError("Failed to read echo") def read_exact(s, count): data = "" i = 0 while len(data) < count: if i == 3: print util.hexprint(data) raise errors.RadioError("Failed to read %i (%i) from radio" % (count, len(data))) elif i > 0: print "Retry %i" % i data += s.read(count - len(data)) i += 1 return data def download(radio): data = "" radio.pipe.setTimeout(1) for block in radio._block_lengths: print "Doing block %i" % block if block > 112: step = 16 else: step = block for i in range(0, block, step): #data += read_exact(radio.pipe, step) chunk = radio.pipe.read(step*2) print "Length of chunk: %i" % len(chunk) data += chunk print "Reading %i" % i time.sleep(0.1) send(radio.pipe, ACK) if radio.status_fn: status = chirp_common.Status() status.max = radio._memsize status.cur = len(data) status.msg = "Cloning from radio" radio.status_fn(status) r = radio.pipe.read(100) send(radio.pipe, ACK) print "R: %i" % len(r) print util.hexprint(r) print "Got: %i Expecting %i" % (len(data), radio._memsize) return memmap.MemoryMap(data) def get_mem_offset(number): return MEM_LOC_BASE + (number * MEM_LOC_SIZE) def get_raw_memory(map, number): pos = get_mem_offset(number) return memmap.MemoryMap(map[pos:pos+MEM_LOC_SIZE]) def get_freq(mmap): khz = (int("%02x" % (ord(mmap[POS_FREQ])), 10) * 100000) + \ (int("%02x" % ord(mmap[POS_FREQ+1]), 10) * 1000) + \ (int("%02x" % ord(mmap[POS_FREQ+2]), 10) * 10) return khz / 10000.0 def set_freq(mmap, freq): val = util.bcd_encode(int(freq * 1000), width=6)[:3] mmap[POS_FREQ] = val def get_tmode(mmap): val = ord(mmap[POS_TMODE]) & 0xC0 tmodemap = { 0x00 : "", 0x40 : "Tone", 0x80 : "TSQL", 0xC0 : "DTCS", } return tmodemap[val] def set_tmode(mmap, tmode): val = ord(mmap[POS_TMODE]) & 0x3F tmodemap = { "" : 0x00, "Tone" : 0x40, "TSQL" : 0x80, "DTCS" : 0xC0, } val |= tmodemap[tmode] mmap[POS_TMODE] = val def get_tone(mmap): val = ord(mmap[POS_TONE]) & 0x3F return chirp_common.TONES[val] def set_tone(mmap, tone): val = ord(mmap[POS_TONE]) & 0xC0 mmap[POS_TONE] = val | chirp_common.TONES.index(tone) def get_dtcs(mmap): val = ord(mmap[POS_DTCS]) return chirp_common.DTCS_CODES[val] def set_dtcs(mmap, dtcs): mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(dtcs) def get_offset(mmap): khz = (int("%02x" % ord(mmap[POS_OFFSET]), 10) * 10) + \ (int("%02x" % (ord(mmap[POS_OFFSET+1]) >> 4), 10) * 1) return khz / 1000.0 def set_offset(mmap, offset): val = util.bcd_encode(int(offset * 1000), width=4)[:3] print "Offfset:\n%s"% util.hexprint(val) mmap[POS_OFFSET] = val def get_duplex(mmap): val = ord(mmap[POS_DUPLEX]) & 0x03 dupmap = { 0x00 : "", 0x01 : "-", 0x02 : "+", 0x03 : "split", } return dupmap[val] def set_duplex(mmap, duplex): val = ord(mmap[POS_DUPLEX]) & 0xFC dupmap = { "" : 0x00, "-" : 0x01, "+" : 0x02, "split" : 0x03, } mmap[POS_DUPLEX] = val | dupmap[duplex] def get_name(mmap): name = "" for x in mmap[POS_NAME:POS_NAME+4]: if ord(x) >= len(CHARSET): break name += CHARSET[ord(x)] return name def set_name(mmap, name): val = "" for i in name[:4].ljust(4): val += chr(CHARSET.index(i)) mmap[POS_NAME] = val def get_mode(mmap): val = ord(mmap[POS_MODE]) & 0x03 modemap = { 0x00 : "FM", 0x01 : "AM", 0x02 : "WFM", 0x03 : "WFM", } return modemap[val] def set_mode(mmap, mode): val = ord(mmap[POS_MODE]) & 0xCF modemap = { "FM" : 0x00, "AM" : 0x01, "WFM" : 0x02, } mmap[POS_MODE] = val | modemap[mode] def get_used(mmap, number): return ord(mmap[POS_USED + number]) & 0x01 def set_used(mmap, number, used): val = ord(mmap[POS_USED + number]) & 0xFC if used: val |= 0x03 mmap[POS_USED + number] = val def get_memory(map, number): index = number - 1 mmap = get_raw_memory(map, index) mem = chirp_common.Memory() mem.number = number if not get_used(map, index): mem.empty = True return mem mem.freq = get_freq(mmap) mem.tmode = get_tmode(mmap) mem.rtone = mem.ctone = get_tone(mmap) mem.dtcs = get_dtcs(mmap) mem.offset = get_offset(mmap) mem.duplex = get_duplex(mmap) mem.name = get_name(mmap) mem.mode = get_mode(mmap) return mem def set_memory(_map, mem): index = mem.number - 1 mmap = get_raw_memory(_map, index) if not get_used(_map, index): mmap[0] = ("\x00" * MEM_LOC_SIZE) set_freq(mmap, mem.freq) set_tmode(mmap, mem.tmode) set_tone(mmap, mem.rtone) set_dtcs(mmap, mem.dtcs) set_offset(mmap, mem.offset) set_duplex(mmap, mem.duplex) set_name(mmap, mem.name) set_mode(mmap, mem.mode) _map[get_mem_offset(index)] = mmap.get_packed() set_used(_map, index, True) return _map def erase_memory(map, number): set_used(map, number-1, False) return map def update_checksum(map): cs = 0 for i in range(0, 3722): cs += ord(map[i]) cs %= 256 print "Checksum old=%02x new=%02x" % (ord(map[3722]), cs) map[3722] = cs chirp-0.3.1/chirp/yaesu_clone.py0000644000016101777760000001527512130403635020011 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . CMD_ACK = 0x06 from chirp import chirp_common, util, memmap, errors import time, os def _safe_read(pipe, count): buf = "" first = True for _i in range(0, 60): buf += pipe.read(count - len(buf)) #print "safe_read: %i/%i\n" % (len(buf), count) if buf: if first and buf[0] == chr(CMD_ACK): #print "Chewed an ack" buf = buf[1:] # Chew an echo'd ack if using a 2-pin cable first = False if len(buf) == count: break print util.hexprint(buf) return buf def _chunk_read(pipe, count, status_fn): block = 32 data = "" for _i in range(0, count, block): data += pipe.read(block) if data: if data[0] == chr(CMD_ACK): data = data[1:] # Chew an echo'd ack if using a 2-pin cable #print "Chewed an ack" status = chirp_common.Status() status.msg = "Cloning from radio" status.max = count status.cur = len(data) status_fn(status) if os.getenv("CHIRP_DEBUG"): print "Read %i/%i" % (len(data), count) return data def __clone_in(radio): pipe = radio.pipe start = time.time() data = "" blocks = 0 for block in radio._block_lengths: blocks += 1 if blocks == len(radio._block_lengths): chunk = _chunk_read(pipe, block, radio.status_fn) else: chunk = _safe_read(pipe, block) pipe.write(chr(CMD_ACK)) if not chunk: raise errors.RadioError("No response from radio") data += chunk if len(data) != radio.get_memsize(): raise errors.RadioError("Received incomplete image from radio") print "Clone completed in %i seconds" % (time.time() - start) return memmap.MemoryMap(data) def _clone_in(radio): try: return __clone_in(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) def _chunk_write(pipe, data, status_fn, block): delay = 0.03 count = 0 for i in range(0, len(data), block): chunk = data[i:i+block] pipe.write(chunk) count += len(chunk) #print "Count is %i" % count time.sleep(delay) status = chirp_common.Status() status.msg = "Cloning to radio" status.max = len(data) status.cur = count status_fn(status) def __clone_out(radio): pipe = radio.pipe block_lengths = radio._block_lengths total_written = 0 def _status(): status = chirp_common.Status() status.msg = "Cloning to radio" status.max = block_lengths[0] + block_lengths[1] + block_lengths[2] status.cur = total_written radio.status_fn(status) start = time.time() blocks = 0 pos = 0 for block in radio._block_lengths: blocks += 1 if blocks != len(radio._block_lengths): #print "Sending %i-%i" % (pos, pos+block) pipe.write(radio.get_mmap()[pos:pos+block]) buf = pipe.read(1) if buf and buf[0] != chr(CMD_ACK): buf = pipe.read(block) if not buf or buf[-1] != chr(CMD_ACK): raise Exception("Radio did not ack block %i" % blocks) else: _chunk_write(pipe, radio.get_mmap()[pos:], radio.status_fn, radio._block_size) pos += block pipe.read(pos) # Chew the echo if using a 2-pin cable print "Clone completed in %i seconds" % (time.time() - start) def _clone_out(radio): try: return __clone_out(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) class YaesuChecksum: """A Yaesu Checksum Object""" def __init__(self, start, stop, address=None): self._start = start self._stop = stop if address: self._address = address else: self._address = stop + 1 def get_existing(self, mmap): """Return the existing checksum in mmap""" return ord(mmap[self._address]) def get_calculated(self, mmap): """Return the calculated value of the checksum""" cs = 0 for i in range(self._start, self._stop+1): cs += ord(mmap[i]) return cs % 256 def update(self, mmap): """Update the checksum with the data in @mmap""" mmap[self._address] = self.get_calculated(mmap) def __str__(self): return "%04X-%04X (@%04X)" % (self._start, self._stop, self._address) class YaesuCloneModeRadio(chirp_common.CloneModeRadio): """Base class for all Yaesu clone-mode radios""" _block_lengths = [8, 65536] _block_size = 8 VENDOR = "Yaesu" _model = "ABCDE" def _checksums(self): """Return a list of checksum objects that need to be calculated""" return [] def update_checksums(self): """Update the radio's checksums from the current memory map""" for checksum in self._checksums(): checksum.update(self._mmap) def check_checksums(self): """Validate the checksums stored in the memory map""" for checksum in self._checksums(): if checksum.get_existing(self._mmap) != \ checksum.get_calculated(self._mmap): raise errors.RadioError("Checksum Failed [%s]" % checksum) print "Checksum %s: OK" % checksum def sync_in(self): self._mmap = _clone_in(self) self.check_checksums() self.process_mmap() def sync_out(self): self.update_checksums() _clone_out(self) @classmethod def match_model(cls, filedata, filename): return filedata[:5] == cls._model and len(filedata) == cls._memsize def _wipe_memory_banks(self, mem): """Remove @mem from all the banks it is currently in""" bm = self.get_bank_model() for bank in bm.get_memory_banks(mem): bm.remove_memory_from_bank(mem, bank) chirp-0.3.1/chirp/vx7.py0000644000016101777760000002416412130403635016224 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . from chirp import chirp_common, yaesu_clone, directory from chirp import bitwise MEM_FORMAT = """ #seekto 0x0611; u8 checksum1; #seekto 0x0691; u8 checksum2; #seekto 0x0742; struct { u16 in_use; } bank_used[9]; #seekto 0x0EA2; struct { u16 members[48]; } bank_members[9]; #seekto 0x3F52; u8 checksum3; #seekto 0x1202; struct { u8 even_pskip:1, even_skip:1, even_valid:1, even_masked:1, odd_pskip:1, odd_skip:1, odd_valid:1, odd_masked:1; } flags[225]; #seekto 0x1322; struct { u8 unknown1; u8 power:2, duplex:2, tune_step:4; bbcd freq[3]; u8 zeros1:2, ones:2, zeros2:2, mode:2; u8 name[8]; u8 zero; bbcd offset[3]; u8 zeros3:2, tone:6; u8 zeros4:1, dcs:7; u8 zeros5:5, is_split_tone:1, tmode:2; u8 charset; } memory[450]; """ DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM", "Auto"] TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"] CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone"] STEPS = list(chirp_common.TUNING_STEPS) STEPS.remove(6.25) STEPS.remove(30.0) STEPS.append(100.0) STEPS.append(9.0) CHARSET = ["%i" % int(x) for x in range(0, 10)] + \ [" "] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ [chr(x) for x in range(ord("a"), ord("z")+1)] + \ list(".,:;!\"#$%&'()*+-.=<>?@[?]^_\\{|}") + \ list("\x00" * 100) POWER_LEVELS = [chirp_common.PowerLevel("L1", watts=0.05), chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L3", watts=2.50), chirp_common.PowerLevel("Hi", watts=5.00) ] POWER_LEVELS_220 = [chirp_common.PowerLevel("L1", watts=0.05), chirp_common.PowerLevel("L2", watts=0.30)] def _is220(freq): return freq >= 222000000 and freq <= 225000000 class VX7BankModel(chirp_common.BankModel): """A VX-7 Bank model""" def get_num_banks(self): return 9 def get_banks(self): banks = [] for i in range(0, self.get_num_banks()): bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1)) bank.index = i banks.append(bank) return banks def add_memory_to_bank(self, memory, bank): _members = self._radio._memobj.bank_members[bank.index] _bank_used = self._radio._memobj.bank_used[bank.index] for i in range(0, 48): if _members.members[i] == 0xFFFF: _members.members[i] = memory.number - 1 _bank_used.in_use = 0x0000 break def remove_memory_from_bank(self, memory, bank): _members = self._radio._memobj.bank_members[bank.index].members _bank_used = self._radio._memobj.bank_used[bank.index] found = False remaining_members = 0 for i in range(0, len(_members)): if _members[i] == (memory.number - 1): _members[i] = 0xFFFF found = True elif _members[i] != 0xFFFF: remaining_members += 1 if not found: raise Exception("Memory {num} not in " + "bank {bank}".format(num=memory.number, bank=bank)) if not remaining_members: _bank_used.in_use = 0xFFFF def get_bank_memories(self, bank): memories = [] _members = self._radio._memobj.bank_members[bank.index].members _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return memories for number in _members: if number == 0xFFFF: continue memories.append(self._radio.get_memory(number+1)) return memories def get_memory_banks(self, memory): banks = [] for bank in self.get_banks(): if memory.number in [x.number for x in self.get_bank_memories(bank)]: banks.append(bank) return banks def _wipe_memory(mem): mem.set_raw("\x00" * (mem.size() / 8)) mem.unknown1 = 0x05 mem.ones = 0x03 @directory.register class VX7Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-7""" BAUD_RATE = 19200 VENDOR = "Yaesu" MODEL = "VX-7" _model = "" _memsize = 16211 _block_lengths = [ 10, 8, 16193 ] _block_size = 8 def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0592, 0x0610), yaesu_clone.YaesuChecksum(0x0612, 0x0690), yaesu_clone.YaesuChecksum(0x0000, 0x3F51), ] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = True rf.has_dtcs_polarity = False rf.valid_modes = list(set(MODES)) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(500000, 999000000)] rf.valid_skips = ["", "S", "P"] rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 8 rf.memory_bounds = (1, 450) rf.can_odd_split = True rf.has_ctone = False rf.has_cross = True rf.valid_cross_modes = list(CROSS_MODES) return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def get_memory(self, number): _mem = self._memobj.memory[number-1] _flag = self._memobj.flags[(number-1)/2] nibble = ((number-1) % 2) and "even" or "odd" used = _flag["%s_masked" % nibble] valid = _flag["%s_valid" % nibble] pskip = _flag["%s_pskip" % nibble] skip = _flag["%s_skip" % nibble] mem = chirp_common.Memory() mem.number = number if not used: mem.empty = True if not valid: mem.empty = True mem.power = POWER_LEVELS[0] return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = int(_mem.offset) * 1000 mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] if not _mem.is_split_tone: mem.tmode = TMODES[_mem.tmode] mem.cross_mode = CROSS_MODES[0] else: mem.tmode = "Cross" mem.cross_mode = CROSS_MODES[int(_mem.tmode)] mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = STEPS[_mem.tune_step] mem.skip = pskip and "P" or skip and "S" or "" if _is220(mem.freq): levels = POWER_LEVELS_220 else: levels = POWER_LEVELS try: mem.power = levels[_mem.power] except IndexError: print "Radio reported invalid power level %s (in %s)" % ( _mem.power, levels) mem.power = levels[0] for i in _mem.name: if i == "\xFF": break mem.name += CHARSET[i] mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flag = self._memobj.flags[(mem.number-1)/2] nibble = ((mem.number-1) % 2) and "even" or "odd" valid = _flag["%s_valid" % nibble] used = _flag["%s_masked" % nibble] if not mem.empty and not valid: _wipe_memory(_mem) self._wipe_memory_banks(mem) if mem.empty and valid and not used: _flag["%s_valid" % nibble] = False return _flag["%s_masked" % nibble] = not mem.empty if mem.empty: return _flag["%s_valid" % nibble] = True _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.tone = chirp_common.TONES.index(mem.rtone) if mem.tmode != "Cross": _mem.is_split_tone = 0 _mem.tmode = TMODES.index(mem.tmode) else: _mem.is_split_tone = 1 _mem.tmode = CROSS_MODES.index(mem.cross_mode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power: if _is220(mem.freq): levels = [str(l) for l in POWER_LEVELS_220] _mem.power = levels.index(str(mem.power)) else: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" for i in range(0, 8): _mem.name[i] = CHARSET.index(mem.name.ljust(8)[i]) def validate_memory(self, mem): msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem) if _is220(mem.freq): if str(mem.power) not in [str(l) for l in POWER_LEVELS_220]: msgs.append(chirp_common.ValidationError(\ "Power level %s not supported on 220MHz band" % \ mem.power)) return msgs @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def get_bank_model(self): return VX7BankModel(self) chirp-0.3.1/chirp/wouxun_common.py0000644000016100007500000000524512036210646016756 0ustar jenkins00000000000000# # Copyright 2012 Filippi Marco # # 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 . """vcommon function for wouxun (or similar) radios""" import struct import os from chirp import util, chirp_common, memmap def wipe_memory(_mem, byte): """Cleanup a memory""" _mem.set_raw(byte * (_mem.size() / 8)) def do_download(radio, start, end, blocksize): """Initiate a download of @radio between @start and @end""" image = "" for i in range(start, end, blocksize): cmd = struct.pack(">cHb", "R", i, blocksize) if os.getenv("CHIRP_DEBUG"): print util.hexprint(cmd) radio.pipe.write(cmd) length = len(cmd) + blocksize resp = radio.pipe.read(length) if len(resp) != (len(cmd) + blocksize): print util.hexprint(resp) raise Exception("Failed to read full block (%i!=%i)" % \ (len(resp), len(cmd) + blocksize)) radio.pipe.write("\x06") radio.pipe.read(1) image += resp[4:] if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = end status.msg = "Cloning from radio" radio.status_fn(status) return memmap.MemoryMap(image) def do_upload(radio, start, end, blocksize): """Initiate an upload of @radio between @start and @end""" ptr = start for i in range(start, end, blocksize): cmd = struct.pack(">cHb", "W", i, blocksize) chunk = radio.get_mmap()[ptr:ptr+blocksize] ptr += blocksize radio.pipe.write(cmd + chunk) if os.getenv("CHIRP_DEBUG"): print util.hexprint(cmd + chunk) ack = radio.pipe.read(1) if not ack == "\x06": raise Exception("Radio did not ack block %i" % ptr) #radio.pipe.write(ack) if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = end status.msg = "Cloning to radio" radio.status_fn(status) chirp-0.3.1/chirp/uv5r.py0000644000016101777760000007732712130403635016412 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 2 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 . import struct import time from chirp import chirp_common, errors, util, directory, memmap from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueList, RadioSettingValueBoolean, \ RadioSettingValueString MEM_FORMAT = """ #seekto 0x0008; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unused1:4, scode:4; u8 unknown1[1]; u8 unknown2:7, lowpower:1; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttideot:1, pttidbot:1; } memory[128]; #seekto 0x0CB2; struct { u8 code[5]; } ani; #seekto 0x0E28; struct { u8 squelch; u8 step; u8 unknown1; u8 save; u8 vox; u8 unknown2; u8 abr; u8 tdr; u8 beep; u8 timeout; u8 unknown3[4]; u8 voice; u8 unknown4; u8 dtmfst; u8 unknown5; u8 screv; u8 pttid; u8 pttlt; u8 mdfa; u8 mdfb; u8 bcl; u8 autolk; u8 sftd; u8 unknown6[3]; u8 wtled; u8 rxled; u8 txled; u8 almod; u8 band; u8 tdrab; u8 ste; u8 rpste; u8 rptrl; u8 ponmsg; u8 roger; } settings[2]; #seekto 0x0E52; struct { u8 displayab:1, unknown1:2, fmradio:1, alarm:1, unknown2:1, reset:1, menu:1; u8 unknown3; u8 workmode; u8 keylock; } extra; #seekto 0x0E7E; struct { u8 unused1:1, mrcha:7; u8 unused2:1, mrchb:7; } wmchannel; #seekto 0x0F10; struct { u8 freq[8]; u8 unknown1; u8 offset[4]; u8 unknown2; ul16 rxtone; ul16 txtone; u8 unused1:7, band:1; u8 unknown3; u8 unused2:4, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:6; } vfoa; #seekto 0x0F30; struct { u8 freq[8]; u8 unknown1; u8 offset[4]; u8 unknown2; ul16 rxtone; ul16 txtone; u8 unused1:7, band:1; u8 unknown3; u8 unused2:4, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:6; } vfob; #seekto 0x1000; struct { u8 unknown1[8]; char name[7]; u8 unknown2; } names[128]; #seekto 0x1818; struct { char line1[7]; char line2[7]; } sixpoweron_msg; #seekto 0x1828; struct { char line1[7]; char line2[7]; } poweron_msg; struct limit { u8 enable; bbcd lower[2]; bbcd upper[2]; }; #seekto 0x1908; struct { struct limit vhf; struct limit uhf; } limits_new; #seekto 0x1910; struct { u8 unknown1[2]; struct limit vhf; u8 unknown2; u8 unknown3[8]; u8 unknown4[2]; struct limit uhf; } limits_old; """ # 0x1EC0 - 0x2000 STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0] STEP_LIST = [str(x) for x in STEPS] STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] STEP291_LIST = [str(x) for x in STEPS] TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)] VOICE_LIST = ["Off", "English", "Chinese"] DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"] RESUME_LIST = ["TO", "CO", "SE"] MODE_LIST = ["Channel", "Name", "Frequency"] COLOR_LIST = ["Off", "Blue", "Orange", "Purple"] ALMOD_LIST = ["Site", "Tone", "Code"] TDRAB_LIST = ["Off", "A", "B"] PONMSG_LIST = ["Full", "Message"] RPSTE_LIST = ["%s" % x for x in range(1, 11, 1)] RPSTE_LIST.insert(0, "OFF") STEDELAY_LIST = ["%s ms" % x for x in range(100, 1100, 100)] STEDELAY_LIST.insert(0, "OFF") SCODE_LIST = ["%s" % x for x in range(1, 16)] SETTING_LISTS = { "step" : STEP_LIST, "step291" : STEP291_LIST, "timeout" : TIMEOUT_LIST, "voice" : VOICE_LIST, "dtmfst" : DTMFST_LIST, "screv" : RESUME_LIST, "mdfa" : MODE_LIST, "mdfb" : MODE_LIST, "wtled" : COLOR_LIST, "rxled" : COLOR_LIST, "txled" : COLOR_LIST, "almod" : ALMOD_LIST, "tdrab" : TDRAB_LIST, "ponmsg" : PONMSG_LIST, "rpste" : RPSTE_LIST, "stedelay" : STEDELAY_LIST, "scode" : SCODE_LIST, } def _do_status(radio, block): status = chirp_common.Status() status.msg = "Cloning" status.cur = block status.max = radio.get_memsize() radio.status_fn(status) def validate_orig(ident): try: ver = int(ident[4:7]) if ver >= 291: raise errors.RadioError("Radio version %i not supported" % ver) except ValueError: raise errors.RadioError("Radio reported invalid version string") def validate_291(ident): if ident[4:7] != "\x30\x04\x50": raise errors.RadioError("Radio version not supported") UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D" UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25" IDENTS = [UV5R_MODEL_ORIG, UV5R_MODEL_291, ] def _firmware_version_from_image(radio): return radio.get_mmap()[0x1838:0x1848] def _do_ident(radio, magic): serial = radio.pipe serial.setTimeout(1) print "Sending Magic: %s" % util.hexprint(magic) serial.write(magic) ack = serial.read(1) if ack != "\x06": if ack: print repr(ack) raise errors.RadioError("Radio did not respond") serial.write("\x02") ident = serial.read(8) print "Ident:\n%s" % util.hexprint(ident) serial.write("\x06") ack = serial.read(1) if ack != "\x06": raise errors.RadioError("Radio refused clone") return ident def _read_block(radio, start, size): msg = struct.pack(">BHB", ord("S"), start, size) radio.pipe.write(msg) answer = radio.pipe.read(4) if len(answer) != 4: raise errors.RadioError("Radio refused to send block 0x%04x" % start) cmd, addr, length = struct.unpack(">BHB", answer) if cmd != ord("X") or addr != start or length != size: print "Invalid answer for block 0x%04x:" % start print "CMD: %s ADDR: %04x SIZE: %02x" % (cmd, addr, length) raise errors.RadioError("Unknown response from radio") chunk = radio.pipe.read(0x40) if not chunk: raise errors.RadioError("Radio did not send block 0x%04x" % start) elif len(chunk) != size: print "Chunk length was 0x%04i" % len(chunk) raise errors.RadioError("Radio sent incomplete block 0x%04x" % start) radio.pipe.write("\x06") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio refused to send block 0x%04x" % start) return chunk def _get_radio_firmware_version(radio): block1 = _read_block(radio, 0x1EC0, 0x40) block2 = _read_block(radio, 0x1F00, 0x40) block = block1 + block2 return block[48:64] def _ident_radio(radio): for magic in IDENTS: error = None try: data = _do_ident(radio, magic) return data except errors.RadioError, e: print e error = e time.sleep(2) if error: raise error raise errors.RadioError("Radio did not respond") def _do_download(radio): data = _ident_radio(radio) # Main block for i in range(0, 0x1800, 0x40): data += _read_block(radio, i, 0x40) _do_status(radio, i) # Auxiliary block starts at 0x1ECO (?) for i in range(0x1EC0, 0x2000, 0x40): data += _read_block(radio, i, 0x40) return memmap.MemoryMap(data) def _send_block(radio, addr, data): msg = struct.pack(">BHB", ord("X"), addr, len(data)) radio.pipe.write(msg + data) ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio refused to accept block 0x%04x" % addr) def _do_upload(radio): _ident_radio(radio) image_version = _firmware_version_from_image(radio) radio_version = _get_radio_firmware_version(radio) print "Image is %s" % repr(image_version) print "Radio is %s" % repr(radio_version) if "BFB" not in radio_version: raise errors.RadioError("Unsupported firmware version: `%s'" % radio_version) # Main block for i in range(0x08, 0x1808, 0x10): _send_block(radio, i - 0x08, radio.get_mmap()[i:i+0x10]) _do_status(radio, i) if len(radio.get_mmap().get_packed()) == 0x1808: print "Old image, not writing aux block" return # Old image, no aux block if image_version != radio_version: raise errors.RadioError("Upload finished, but the 'Other Settings' " "could not be sent because the firmware " "version of the image does not match that " "of the radio") # Auxiliary block at radio address 0x1EC0, our offset 0x1808 for i in range(0x1EC0, 0x2000, 0x10): addr = 0x1808 + (i - 0x1EC0) _send_block(radio, i, radio.get_mmap()[addr:addr+0x10]) UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00), chirp_common.PowerLevel("Low", watts=1.00)] UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645]) UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \ "!@#$%^&*()+-=[]:\";'<>?,./" # Uncomment this to actually register this radio in CHIRP @directory.register class BaofengUV5R(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baofeng UV-5R""" VENDOR = "Baofeng" MODEL = "UV-5R" BAUD_RATE = 9600 _memsize = 0x1808 @classmethod def get_experimental_warning(cls): return ('Due to the fact that the manufacturer continues to ' 'release new versions of the firmware with obscure and ' 'hard-to-track changes, this driver may not work with ' 'your device. Thus far and to the best knowledge of the ' 'author, no UV-5R radios have been harmed by using CHIRP. ' 'However, proceed at your own risk!') def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_cross = True rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = True rf.valid_name_length = 7 rf.valid_characters = UV5R_CHARSET rf.valid_skips = ["", "S"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] rf.valid_power_levels = UV5R_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["FM", "NFM"] rf.valid_bands = [(136000000, 174000000), (400000000, 520000000)] rf.memory_bounds = (0, 127) return rf @classmethod def match_model(cls, filedata, filename): return len(filedata) in [0x1808, 0x1948] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): try: self._mmap = _do_download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() def sync_out(self): try: _do_upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def _is_txinh(self, _mem): raw_tx = "" for i in range(0, 4): raw_tx += _mem.txfreq[i].get_raw() return raw_tx == "\xFF\xFF\xFF\xFF" def get_memory(self, number): _mem = self._memobj.memory[number] _nam = self._memobj.names[number] mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[0] == "\xff": mem.empty = True return mem mem.freq = int(_mem.rxfreq) * 10 if self._is_txinh(_mem): mem.duplex = "off" mem.offset = 0 elif int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000: mem.duplex = "split" mem.offset = int(_mem.txfreq) * 10 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 for char in _nam.name: if str(char) == "\xFF": char = " " # The UV-5R software may have 0xFF mid-name mem.name += str(char) mem.name = mem.name.rstrip() dtcs_pol = ["N", "N"] if _mem.txtone in [0, 0xFFFF]: txmode = "" elif _mem.txtone >= 0x0258: txmode = "Tone" mem.rtone = int(_mem.txtone) / 10.0 elif _mem.txtone <= 0x0258: txmode = "DTCS" if _mem.txtone > 0x69: index = _mem.txtone - 0x6A dtcs_pol[0] = "R" else: index = _mem.txtone - 1 mem.dtcs = UV5R_DTCS[index] else: print "Bug: txtone is %04x" % _mem.txtone if _mem.rxtone in [0, 0xFFFF]: rxmode = "" elif _mem.rxtone >= 0x0258: rxmode = "Tone" mem.ctone = int(_mem.rxtone) / 10.0 elif _mem.rxtone <= 0x0258: rxmode = "DTCS" if _mem.rxtone >= 0x6A: index = _mem.rxtone - 0x6A dtcs_pol[1] = "R" else: index = _mem.rxtone - 1 mem.rx_dtcs = UV5R_DTCS[index] else: print "Bug: rxtone is %04x" % _mem.rxtone if txmode == "Tone" and not rxmode: mem.tmode = "Tone" elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone: mem.tmode = "TSQL" elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs: mem.tmode = "DTCS" elif rxmode or txmode: mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (txmode, rxmode) mem.dtcs_polarity = "".join(dtcs_pol) if not _mem.scan: mem.skip = "S" mem.power = UV5R_POWER_LEVELS[_mem.lowpower] mem.mode = _mem.wide and "FM" or "NFM" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _nam = self._memobj.names[mem.number] if mem.empty: _mem.set_raw("\xff" * 16) return _mem.set_raw("\x00" * 16) _mem.rxfreq = mem.freq / 10 if mem.duplex == "off": for i in range(0, 4): _mem.txfreq[i].set_raw("\xFF") elif mem.duplex == "split": _mem.txfreq = mem.offset / 10 elif mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 else: _mem.txfreq = mem.freq / 10 for i in range(0, 7): try: _nam.name[i] = mem.name[i] except IndexError: _nam.name[i] = "\xFF" rxmode = txmode = "" if mem.tmode == "Tone": _mem.txtone = int(mem.rtone * 10) _mem.rxtone = 0 elif mem.tmode == "TSQL": _mem.txtone = int(mem.ctone * 10) _mem.rxtone = int(mem.ctone * 10) elif mem.tmode == "DTCS": rxmode = txmode = "DTCS" _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1 _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1 elif mem.tmode == "Cross": txmode, rxmode = mem.cross_mode.split("->", 1) if txmode == "Tone": _mem.txtone = int(mem.rtone * 10) elif txmode == "DTCS": _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1 else: _mem.txtone = 0 if rxmode == "Tone": _mem.rxtone = int(mem.ctone * 10) elif rxmode == "DTCS": _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1 else: _mem.rxtone = 0 else: _mem.rxtone = 0 _mem.txtone = 0 if txmode == "DTCS" and mem.dtcs_polarity[0] == "R": _mem.txtone += 0x69 if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R": _mem.rxtone += 0x69 _mem.scan = mem.skip != "S" _mem.wide = mem.mode == "FM" _mem.lowpower = mem.power == UV5R_POWER_LEVELS[1] for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def _is_orig(self): version_tag = _firmware_version_from_image(self) try: if 'BFB' in version_tag: idx = version_tag.index("BFB") + 3 version = int(version_tag[idx:idx+3]) return version < 291 except: pass raise errors.RadioError("Unable to parse version string %s" % version_tag) def _my_version(self): version_tag = _firmware_version_from_image(self) if 'BFB' in version_tag: idx = version_tag.index("BFB") + 3 return int(version_tag[idx:idx+3]) raise Exception("Unrecognized firmware version string") def _get_settings(self): _settings = self._memobj.settings[0] basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") group = RadioSettingGroup("top", "All Settings", basic, advanced) rs = RadioSetting("squelch", "Carrier Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("dtmfst", "DTMF Sidetone", RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])) advanced.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueInteger(0, 4, _settings.save)) basic.append(rs) rs = RadioSetting("vox", "VOX Sensitivity", RadioSettingValueInteger(0, 10, _settings.vox)) advanced.append(rs) rs = RadioSetting("abr", "Backlight Timeout", RadioSettingValueInteger(0, 24, _settings.abr)) basic.append(rs) rs = RadioSetting("tdr", "Dual Watch", RadioSettingValueBoolean(_settings.tdr)) advanced.append(rs) rs = RadioSetting("tdrab", "Dual Watch Priority", RadioSettingValueList(TDRAB_LIST, TDRAB_LIST[_settings.tdrab])) advanced.append(rs) rs = RadioSetting("almod", "Alarm Mode", RadioSettingValueList(ALMOD_LIST, ALMOD_LIST[_settings.almod])) advanced.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting("timeout", "Timeout Timer", RadioSettingValueList(TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout])) basic.append(rs) if self._my_version() >= 251: rs = RadioSetting("voice", "Voice", RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])) advanced.append(rs) else: rs = RadioSetting("voice", "Voice", RadioSettingValueBoolean(_settings.voice)) advanced.append(rs) rs = RadioSetting("screv", "Scan Resume", RadioSettingValueList(RESUME_LIST, RESUME_LIST[_settings.screv])) advanced.append(rs) rs = RadioSetting("mdfa", "Display Mode (A)", RadioSettingValueList(MODE_LIST, MODE_LIST[_settings.mdfa])) basic.append(rs) rs = RadioSetting("mdfb", "Display Mode (B)", RadioSettingValueList(MODE_LIST, MODE_LIST[_settings.mdfb])) basic.append(rs) rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueBoolean(_settings.bcl)) advanced.append(rs) rs = RadioSetting("autolk", "Automatic Key Lock", RadioSettingValueBoolean(_settings.autolk)) advanced.append(rs) rs = RadioSetting("extra.fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(self._memobj.extra.fmradio)) advanced.append(rs) rs = RadioSetting("wtled", "Standby LED Color", RadioSettingValueList(COLOR_LIST, COLOR_LIST[_settings.wtled])) basic.append(rs) rs = RadioSetting("rxled", "RX LED Color", RadioSettingValueList(COLOR_LIST, COLOR_LIST[_settings.rxled])) basic.append(rs) rs = RadioSetting("txled", "TX LED Color", RadioSettingValueList(COLOR_LIST, COLOR_LIST[_settings.txled])) basic.append(rs) rs = RadioSetting("roger", "Roger Beep", RadioSettingValueBoolean(_settings.roger)) basic.append(rs) try: _ani = self._memobj.ani.code rs = RadioSetting("ani.code", "ANI Code", RadioSettingValueInteger(0, 9, _ani[0]), RadioSettingValueInteger(0, 9, _ani[1]), RadioSettingValueInteger(0, 9, _ani[2]), RadioSettingValueInteger(0, 9, _ani[3]), RadioSettingValueInteger(0, 9, _ani[4])) advanced.append(rs) except Exception: print ("Your ANI code is not five digits, which is not currently" " supported in CHIRP.") rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)", RadioSettingValueBoolean(_settings.ste)) advanced.append(rs) rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])) advanced.append(rs) rs = RadioSetting("rptrl", "STE Repeater Delay", RadioSettingValueList(STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl])) advanced.append(rs) rs = RadioSetting("extra.reset", "RESET Menu", RadioSettingValueBoolean(self._memobj.extra.reset)) advanced.append(rs) rs = RadioSetting("extra.menu", "All Menus", RadioSettingValueBoolean(self._memobj.extra.menu)) advanced.append(rs) if len(self._mmap.get_packed()) == 0x1808: # Old image, without aux block return group other = RadioSettingGroup("other", "Other Settings") group.append(other) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = self._memobj.sixpoweron_msg rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", RadioSettingValueString(0, 7, _filter(_msg.line1))) other.append(rs) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", RadioSettingValueString(0, 7, _filter(_msg.line2))) other.append(rs) _msg = self._memobj.poweron_msg rs = RadioSetting("poweron_msg.line1", "Power-On Message 1", RadioSettingValueString(0, 7, _filter(_msg.line1))) other.append(rs) rs = RadioSetting("poweron_msg.line2", "Power-On Message 2", RadioSettingValueString(0, 7, _filter(_msg.line2))) other.append(rs) rs = RadioSetting("ponmsg", "Power-On Message", RadioSettingValueList(PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])) other.append(rs) if self._is_orig(): limit = "limits_old" else: limit = "limits_new" vhf_limit = getattr(self._memobj, limit).vhf rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, vhf_limit.lower)) other.append(rs) rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, vhf_limit.upper)) other.append(rs) rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled", RadioSettingValueBoolean(vhf_limit.enable)) other.append(rs) uhf_limit = getattr(self._memobj, limit).uhf rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, uhf_limit.lower)) other.append(rs) rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, uhf_limit.upper)) other.append(rs) rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled", RadioSettingValueBoolean(uhf_limit.enable)) other.append(rs) workmode = RadioSettingGroup("workmode", "Work Mode Settings") group.append(workmode) options = ["A", "B"] rs = RadioSetting("extra.displayab", "Display", RadioSettingValueList(options, options[self._memobj.extra.displayab])) workmode.append(rs) options = ["Frequency", "Channel"] rs = RadioSetting("extra.workmode", "VFO/MR Mode", RadioSettingValueList(options, options[self._memobj.extra.workmode])) workmode.append(rs) rs = RadioSetting("extra.keylock", "Keypad Lock", RadioSettingValueBoolean(self._memobj.extra.keylock)) workmode.append(rs) _mrcna = self._memobj.wmchannel.mrcha rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _mrcna)) workmode.append(rs) _mrcnb = self._memobj.wmchannel.mrchb rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _mrcnb)) workmode.append(rs) options = ["VHF", "UHF"] rs = RadioSetting("vfoa.band", "VFO A Band", RadioSettingValueList(options, options[self._memobj.vfoa.band])) workmode.append(rs) rs = RadioSetting("vfob.band", "VFO B Band", RadioSettingValueList(options, options[self._memobj.vfob.band])) workmode.append(rs) options = ["High", "Low"] rs = RadioSetting("vfoa.txpower", "VFO A Power", RadioSettingValueList(options, options[self._memobj.vfoa.txpower])) workmode.append(rs) rs = RadioSetting("vfob.txpower", "VFO B Power", RadioSettingValueList(options, options[self._memobj.vfob.txpower])) workmode.append(rs) options = ["Wide", "Narrow"] rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth", RadioSettingValueList(options, options[self._memobj.vfoa.widenarr])) workmode.append(rs) rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth", RadioSettingValueList(options, options[self._memobj.vfob.widenarr])) workmode.append(rs) options = ["%s" % x for x in range(1, 16)] rs = RadioSetting("vfoa.scode", "VFO A PTT-ID", RadioSettingValueList(options, options[self._memobj.vfoa.scode])) workmode.append(rs) rs = RadioSetting("vfob.scode", "VFO B PTT-ID", RadioSettingValueList(options, options[self._memobj.vfob.scode])) workmode.append(rs) if self._my_version() >= 291: rs = RadioSetting("vfoa.step", "VFO A Tuning Step", RadioSettingValueList(STEP291_LIST, STEP291_LIST[self._memobj.vfoa.step])) workmode.append(rs) rs = RadioSetting("vfob.step", "VFO B Tuning Step", RadioSettingValueList(STEP291_LIST, STEP291_LIST[self._memobj.vfob.step])) workmode.append(rs) else: rs = RadioSetting("vfoa.step", "VFO A Tuning Step", RadioSettingValueList(STEP_LIST, STEP_LIST[self._memobj.vfoa.step])) workmode.append(rs) rs = RadioSetting("vfob.step", "VFO B Tuning Step", RadioSettingValueList(STEP_LIST, STEP_LIST[self._memobj.vfob.step])) workmode.append(rs) return group def get_settings(self): try: return self._get_settings() except: import traceback print "Failed to parse settings:" traceback.print_exc() return None def set_settings(self, settings): _settings = self._memobj.settings[0] for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue try: if "." in element.get_name(): bits = element.get_name().split(".") obj = self._memobj for bit in bits[:-1]: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() print "Setting %s = %s" % (setting, element.value) setattr(obj, setting, element.value) except Exception, e: print element.get_name() raise chirp-0.3.1/chirp/ft1802.py0000644000016101777760000001654012105130272016417 0ustar jenkinsnogroup00000000000000# Copyright 2012 Tom Hayward # # 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 . # FT-1802 Clone Proceedure # 1. Turn radio off. # 2. Connect cable to mic jack. # 3. Press and hold in the [LOW(A/N)] key while turning the radio on. # 4. In Chirp, choose Download from Radio. # 5. Press the [MHz(SET)] key to send image. # or # 4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD). # 5. In Chirp, choose Upload to Radio. from chirp import chirp_common, bitwise, directory, yaesu_clone from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean MEM_FORMAT = """ #seekto 0x06ea; struct { u8 odd_pskip:1, odd_skip:1, odd_visible:1, odd_valid:1, even_pskip:1, even_skip:1, even_visible:1, even_valid:1; } flags[100]; #seekto 0x076a; struct { u8 unknown1a:1, step_changed:1, narrow:1, clk_shift:1, unknown1b:4; u8 unknown2a:2, duplex:2, unknown2b:1, tune_step:3; bbcd freq[3]; u8 power:2, unknown3:3, tmode:3; u8 name[6]; bbcd offset[3]; u8 tone; u8 dtcs; u8 unknown4; } memory[200]; """ MODES = ["FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"] CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone"] DUPLEX = ["", "-", "+", "split"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] POWER_LEVELS = [chirp_common.PowerLevel("LOW1", watts=5), chirp_common.PowerLevel("LOW2", watts=10), chirp_common.PowerLevel("LOW3", watts=25), chirp_common.PowerLevel("HIGH", watts=50), ] CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?()?_" @directory.register class FT1802Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-1802""" VENDOR = "Yaesu" MODEL = "FT-1802M" BAUD_RATE = 19200 _model = "AH023" _block_lengths = [10, 8001] _memsize = 8011 def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.can_odd_split = True rf.has_ctone = False rf.has_tuning_step = True rf.has_dtcs_polarity = False # in radio settings, not per memory rf.has_bank = False # has banks, but not implemented rf.valid_tuning_steps = STEPS rf.valid_modes = MODES rf.valid_tmodes = TMODES rf.valid_bands = [(137000000, 174000000)] rf.valid_power_levels = POWER_LEVELS rf.valid_duplexes = DUPLEX rf.valid_skips = ["", "S", "P"] rf.valid_name_length = 6 rf.valid_characters = CHARSET rf.has_cross = True rf.valid_cross_modes = list(CROSS_MODES) return rf def _checksums(self): return [yaesu_clone.YaesuChecksum(0, self._memsize-2)] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) + \ repr(self._memobj.flags[number/2]) def get_memory(self, number): _mem = self._memobj.memory[number] _flag = self._memobj.flags[number/2] nibble = (number % 2) and "odd" or "even" visible = _flag["%s_visible" % nibble] valid = _flag["%s_valid" % nibble] pskip = _flag["%s_pskip" % nibble] skip = _flag["%s_skip" % nibble] mem = chirp_common.Memory() mem.number = number if not visible: mem.empty = True if not valid: mem.empty = True return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) mem.duplex = DUPLEX[_mem.duplex] mem.tuning_step = _mem.step_changed and STEPS[_mem.tune_step] or STEPS[0] if _mem.tmode < TMODES.index("Cross"): mem.tmode = TMODES[_mem.tmode] mem.cross_mode = CROSS_MODES[0] else: mem.tmode = "Cross" mem.cross_mode = CROSS_MODES[_mem.tmode - TMODES.index("Cross")] mem.rtone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] for i in _mem.name: if i == 0xFF: break if i & 0x80 == 0x80: # first bit in name is "show name" mem.name += CHARSET[0x80 ^ int(i)] else: mem.name += CHARSET[i] mem.name = mem.name.rstrip() mem.mode = _mem.narrow and "NFM" or "FM" mem.skip = pskip and "P" or skip and "S" or "" mem.power = POWER_LEVELS[_mem.power] mem.extra = RadioSettingGroup("extra", "Extra Settings") rs = RadioSetting("clk_shift", "Clock Shift", RadioSettingValueBoolean(_mem.clk_shift)) mem.extra.append(rs) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flag = self._memobj.flags[mem.number/2] nibble = (mem.number % 2) and "odd" or "even" valid = _flag["%s_valid" % nibble] visible = _flag["%s_visible" % nibble] if not mem.empty and not valid: _flag["%s_valid" % nibble] = True _mem.unknown1a = 0x00 _mem.clk_shift = 0x00 _mem.unknown1b = 0x00 _mem.unknown2a = 0x00 _mem.unknown2b = 0x00 _mem.unknown3 = 0x00 _mem.unknown4 = 0x00 if mem.empty and valid and not visible: _flag["%s_valid" % nibble] = False return _flag["%s_visible" % nibble] = not mem.empty if mem.empty: return _flag["%s_valid" % nibble] = True _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.duplex = DUPLEX.index(mem.duplex) _mem.tune_step = STEPS.index(mem.tuning_step) _mem.step_changed = mem.tuning_step != STEPS[0] if mem.tmode != "Cross": _mem.tmode = TMODES.index(mem.tmode) else: _mem.tmode = TMODES.index("Cross") + CROSS_MODES.index(mem.cross_mode) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.name = [0xFF] * 6 for i in range(0, len(mem.name)): try: _mem.name[i] = CHARSET.index(mem.name[i]) except IndexError: raise Exception("Character `%s' not supported") if _mem.name[0] != 0xFF: _mem.name[0] += 0x80 # show name instead of frequency _mem.narrow = MODES.index(mem.mode) _mem.power = 3 if mem.power is None else POWER_LEVELS.index(mem.power) _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" for element in mem.extra: setattr(_mem, element.get_name(), element.value) chirp-0.3.1/chirp/ic2720.py0000644000016100007500000001254412023560645014752 0ustar jenkins00000000000000# Copyright 2011 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 . from chirp import chirp_common, icf, directory from chirp import bitwise MEM_FORMAT = """ struct { u32 freq; u32 offset; u8 unknown1:2, rtone:6; u8 unknown2:2, ctone:6; u8 unknown3:1, dtcs:7; u8 unknown4:2, unknown5:2, tuning_step:4; u8 unknown6:2, tmode:2, duplex:2, unknown7:2; u8 power:2, is_fm:1, unknown8:1, dtcs_polarity:2, unknown9:2; u8 unknown[2]; } memory[200]; #seekto 0x0E20; u8 skips[25]; #seekto 0x0EB0; u8 used[25]; #seekto 0x0E40; struct { u8 bank_even:4, bank_odd:4; } banks[100]; """ TMODES = ["", "Tone", "TSQL", "DTCS"] POWER = ["High", "Low", "Med"] DTCS_POLARITY = ["NN", "NR", "RN", "RR"] STEPS = [5.0, 10.0, 12.5, 15, 20, 25, 30, 50] MODES = ["FM", "AM"] DUPLEX = ["", "", "-", "+"] POWER_LEVELS_VHF = [chirp_common.PowerLevel("High", watts=50), chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=15)] POWER_LEVELS_UHF = [chirp_common.PowerLevel("High", watts=35), chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=15)] @directory.register class IC2720Radio(icf.IcomCloneModeRadio): """Icom IC-2720""" VENDOR = "Icom" MODEL = "IC-2720H" _model = "\x24\x92\x00\x01" _memsize = 5152 _endframe = "Icom Inc\x2eA0" _ranges = [(0x0000, 0x1400, 32)] def _get_bank(self, loc): _bank = self._memobj.banks[loc / 2] if loc % 2: bank = _bank.bank_odd else: bank = _bank.bank_even if bank == 0x0A: return None else: return bank def _set_bank(self, loc, index): _bank = self._memobj.banks[loc / 2] if index is None: index = 0x0A if loc % 2: _bank.bank_odd = index else: _bank.bank_even = index def get_features(self): rf = chirp_common.RadioFeatures() rf.has_name = False rf.memory_bounds = (0, 199) rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(set(DUPLEX)) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(118000000, 999990000)] rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS_VHF return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): bitpos = (1 << (number % 8)) bytepos = (number / 8) _mem = self._memobj.memory[number] _skp = self._memobj.skips[bytepos] _usd = self._memobj.used[bytepos] mem = chirp_common.Memory() mem.number = number if _usd & bitpos: mem.empty = True return mem mem.freq = int(_mem.freq) mem.offset = int(_mem.offset) mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.tmode = TMODES[_mem.tmode] mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity] mem.tuning_step = STEPS[_mem.tuning_step] mem.mode = _mem.is_fm and "FM" or "AM" mem.duplex = DUPLEX[_mem.duplex] mem.skip = (_skp & bitpos) and "S" or "" if int(mem.freq / 100000000) == 1: mem.power = POWER_LEVELS_VHF[_mem.power] else: mem.power = POWER_LEVELS_UHF[_mem.power] return mem def set_memory(self, mem): bitpos = (1 << (mem.number % 8)) bytepos = (mem.number / 8) _mem = self._memobj.memory[mem.number] _skp = self._memobj.skips[bytepos] _usd = self._memobj.used[bytepos] if mem.empty: _usd |= bitpos self._set_bank(mem.number, None) return _usd &= ~bitpos _mem.freq = mem.freq _mem.offset = mem.offset _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tmode = TMODES.index(mem.tmode) _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity) _mem.tuning_step = STEPS.index(mem.tuning_step) _mem.is_fm = mem.mode == "FM" _mem.duplex = DUPLEX.index(mem.duplex) if mem.skip == "S": _skp |= bitpos else: _skp &= ~bitpos if mem.power: _mem.power = POWER_LEVELS_VHF.index(mem.power) else: _mem.power = 0 chirp-0.3.1/chirp/ict8.py0000644000016101777760000000642212051555045016351 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 . from chirp import chirp_common, icf, util, directory from chirp import bitwise mem_format = """ struct memory { bbcd freq[4]; bbcd offset[2]; u8 rtone; u8 ctone; }; struct flags { u8 empty:1, skip:1, tmode:2, duplex:2, unknown2:2; }; struct memory memory[100]; #seekto 0x0600; struct flags flags[100]; """ DUPLEX = ["", "", "-", "+"] TMODES = ["", "", "Tone", "TSQL"] @directory.register class ICT8ARadio(icf.IcomCloneModeRadio): """Icom IC-T8A""" VENDOR = "Icom" MODEL = "IC-T8A" _model = "\x19\x03\x00\x01" _memsize = 0x07B0 _endframe = "Icom Inc\x2e" _ranges = [(0x0000, 0x07B0, 16)] def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = TMODES rf.valid_duplexes = DUPLEX rf.valid_bands = [(50000000, 54000000), (118000000, 174000000), (400000000, 470000000)] rf.valid_skips = ["", "S"] rf.valid_modes = ["FM"] rf.memory_bounds = (0, 99) rf.has_name = False rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_tuning_step = False rf.has_mode = False rf.has_bank = False return rf def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def get_raw_memory(self, number): return (str(self._memobj.memory[number]) + str(self._memobj.duptone[number])) def get_memory(self, number): _mem = self._memobj.memory[number] _flg = self._memobj.flags[number] mem = chirp_common.Memory() mem.number = number if _flg.empty: mem.empty = True return mem mem.freq = int(_mem.freq) * 10 mem.offset = int(_mem.offset) * 1000 mem.rtone = chirp_common.TONES[_mem.rtone - 1] mem.ctone = chirp_common.TONES[_mem.ctone - 1] mem.duplex = DUPLEX[_flg.duplex] mem.tmode = TMODES[_flg.tmode] mem.skip = _flg.skip and "S" or "" return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flg = self._memobj.flags[mem.number] if mem.empty: _flg.empty = True return _mem.set_raw("\x00" * 8) _flg.set_raw("\x00") _mem.freq = mem.freq / 10 _mem.offset = mem.offset / 1000 _mem.rtone = chirp_common.TONES.index(mem.rtone) + 1 _mem.ctone = chirp_common.TONES.index(mem.ctone) + 1 _flg.duplex = DUPLEX.index(mem.duplex) _flg.tmode = TMODES.index(mem.tmode) _flg.skip = mem.skip == "S" chirp-0.3.1/chirp/rfinder.py0000644000016100007500000002135712023560645015477 0ustar jenkins00000000000000# Copyright 2011 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 . import urllib import hashlib import re from math import pi, cos, acos, sin, atan2 from chirp import chirp_common, CHIRP_VERSION EARTH_RADIUS = 3963.1 SCHEMA = [ "ID", "TRUSTEE", "OUTFREQUENCY", "CITY", "STATE", "COUNTRY", "LATITUDE", "LONGITUDE", "CLUB", "DESCRIPTION", "NOTES", "RANGE", "OFFSETSIGN", "OFFSETFREQ", "PL", "DCS", "REPEATERTYPE", "BAND", "IRLP", "ECHOLINK", "DOC_ID", ] def deg2rad(deg): """Convert degrees to radians""" return deg * (pi / 180) def rad2deg(rad): """Convert radians to degrees""" return rad / (pi / 180) def dm2deg(degrees, minutes): """Convert degrees and minutes to decimal degrees""" return degrees + (minutes / 60.0) def deg2dm(decdeg): """Convert decimal degrees to degrees and minutes""" degrees = int(decdeg) minutes = (decdeg - degrees) * 60.0 return degrees, minutes def nmea2deg(nmea, direction="N"): """Convert NMEA-encoded value to float""" deg = int(nmea) / 100 try: minutes = nmea % (deg * 100) except ZeroDivisionError: minutes = int(nmea) if direction == "S" or direction == "W": sign = -1 else: sign = 1 return dm2deg(deg, minutes) * sign def deg2nmea(deg): """Convert degrees to a NMEA-encoded value""" degrees, minutes = deg2dm(deg) return (degrees * 100) + minutes def meters2feet(meters): """Convert meters to feet""" return meters * 3.2808399 def feet2meters(feet): """Convert feet to meters""" return feet * 0.3048 def distance(lat_a, lon_a, lat_b, lon_b): """Calculate the distance between two points""" lat_a = deg2rad(lat_a) lon_a = deg2rad(lon_a) lat_b = deg2rad(lat_b) lon_b = deg2rad(lon_b) earth_radius = EARTH_RADIUS tmp = (cos(lat_a) * cos(lon_a) * \ cos(lat_b) * cos(lon_b)) + \ (cos(lat_a) * sin(lon_a) * \ cos(lat_b) * sin(lon_b)) + \ (sin(lat_a) * sin(lat_b)) # Correct round-off error (which is just *silly*) if tmp > 1: tmp = 1 elif tmp < -1: tmp = -1 dist = acos(tmp) return dist * earth_radius def bearing(lat_a, lon_a, lat_b, lon_b): """Calculate the bearing between two points""" lat_me = deg2rad(lat_a) lat_u = deg2rad(lat_b) lon_d = deg2rad(lon_b - lon_a) posy = sin(lon_d) * cos(lat_u) posx = cos(lat_me) * sin(lat_u) - \ sin(lat_me) * cos(lat_u) * cos(lon_d) bear = rad2deg(atan2(posy, posx)) return (bear + 360) % 360 def fuzzy_to(lat_a, lon_a, lat_b, lon_b): """Calculate a fuzzy distance to a point""" bear = bearing(lat_a, lon_a, lat_b, lon_b) dirs = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"] delta = 22.5 angle = 0 direction = "?" for i in dirs: if bear > angle and bear < (angle + delta): direction = i angle += delta return direction class RFinderParser: """Parser for RFinder's data format""" def __init__(self, lat, lon): self.__memories = [] self.__cheat = {} self.__lat = lat self.__lon = lon def fetch_data(self, user, pw, coords, radius): """Fetches the data for a set of parameters""" print user print pw args = { "email" : urllib.quote_plus(user), "pass" : hashlib.new("md5", pw).hexdigest(), "lat" : "%7.5f" % coords[0], "lon" : "%8.5f" % coords[1], "radius": "%i" % radius, "vers" : "CH%s" % CHIRP_VERSION, } _url = "https://www.rfinder.net/query.php?%s" % (\ "&".join(["%s=%s" % (k,v) for k,v in args.items()])) print "Query URL: %s" % _url f = urllib.urlopen(_url) data = f.read() f.close() match = re.match("^/#SERVERMSG#/(.*)/#ENDMSG#/", data) if match: raise Exception(match.groups()[0]) return data def _parse_line(self, line): mem = chirp_common.Memory() _vals = line.split("|") vals = {} for i in range(0, len(SCHEMA)): try: vals[SCHEMA[i]] = _vals[i] except IndexError: print "No such vals %s" % SCHEMA[i] self.__cheat = vals mem.name = vals["TRUSTEE"] mem.freq = chirp_common.parse_freq(vals["OUTFREQUENCY"]) if vals["OFFSETSIGN"] != "X": mem.duplex = vals["OFFSETSIGN"] if vals["OFFSETFREQ"]: mem.offset = chirp_common.parse_freq(vals["OFFSETFREQ"]) if vals["PL"] and float(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" if vals["NOTES"]: mem.comment = vals["NOTES"].strip() if vals["LATITUDE"] and vals["LONGITUDE"]: try: lat = float(vals["LATITUDE"]) lon = float(vals["LONGITUDE"]) dist = distance(self.__lat, self.__lon, lat, lon) bear = fuzzy_to(self.__lat, self.__lon, lat, lon) mem.comment = "(%imi %s) %s" % (dist, bear, mem.comment) except Exception, e: print "Failed to calculate distance: %s" % e return mem def parse_data(self, data): """Parse the fetched 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: import traceback, sys traceback.print_exc(file=sys.stdout) print "Error in received data, cannot continue" print e print self.__cheat print line print "\n\n" def get_memories(self): """Return the Memory objects associated with the fetched data""" return self.__memories class RFinderRadio(chirp_common.NetworkSourceRadio): """A network source radio that supports the RFinder repeater directory""" VENDOR = "ITWeRKS" MODEL = "RFinder" def __init__(self, *args, **kwargs): chirp_common.NetworkSourceRadio.__init__(self, *args, **kwargs) self._lat = 0 self._lon = 0 self._user = "" self._pass = "" self._miles = 25 self._rfp = None def set_params(self, (lat, lon), miles, email, password): """Sets the parameters to use for the query""" self._lat = lat self._lon = lon self._miles = miles self._user = email self._pass = password def do_fetch(self): self._rfp = RFinderParser(self._lat, self._lon) self._rfp.parse_data(self._rfp.fetch_data(self._user, self._pass, (self._lat, self._lon), self._miles)) def get_features(self): if not self._rfp: self.do_fetch() rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, len(self._rfp.get_memories())) rf.has_bank = False rf.has_ctone = False rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = ["", "FM", "NFM", "AM", "NAM", "DV"] return rf def get_memory(self, number): if not self._rfp: self.do_fetch() return self._rfp.get_memories()[number-1] def _test(): rfp = RFinderParser() data = rfp.fetch_data("KK7DS", "dsmith@danplanet.com", (45.5, -122.91), 25) rfp.parse_data(data) for mem in rfp.get_memories(): print mem if __name__ == "__main__": _test() chirp-0.3.1/chirp/settings.py0000644000016101777760000002257212130403635017341 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 2 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 chirp import chirp_common class InvalidValueError(Exception): """An invalid value was specified for a given setting""" pass class InternalError(Exception): """A driver provided an invalid settings object structure""" pass class RadioSettingValue: """Base class for a single radio setting""" def __init__(self): self._current = None self._has_changed = False def changed(self): """Returns True if the setting has been changed since init""" return self._has_changed def set_value(self, value): """Sets the current value, triggers changed""" if self._current != None and value != self._current: self._has_changed = True self._current = value def get_value(self): """Gets the current value""" return self._current def __trunc__(self): return int(self.get_value()) def __str__(self): return str(self.get_value()) class RadioSettingValueInteger(RadioSettingValue): """An integer setting""" def __init__(self, minval, maxval, current, step=1): RadioSettingValue.__init__(self) self._min = minval self._max = maxval self._step = step self.set_value(current) def set_value(self, value): try: value = int(value) except: raise InvalidValueError("An integer is required") if value > self._max or value < self._min: raise InvalidValueError("Value %i not in range %i-%i" % (value, self._min, self._max)) RadioSettingValue.set_value(self, value) def get_min(self): """Returns the minimum allowed value""" return self._min def get_max(self): """Returns the maximum allowed value""" return self._max def get_step(self): """Returns the step increment""" return self._step class RadioSettingValueBoolean(RadioSettingValue): """A boolean setting""" def __init__(self, current): RadioSettingValue.__init__(self) self.set_value(current) def set_value(self, value): RadioSettingValue.set_value(self, bool(value)) def __str__(self): return str(bool(self.get_value())) class RadioSettingValueList(RadioSettingValue): """A list-of-strings setting""" def __init__(self, options, current): RadioSettingValue.__init__(self) self._options = options self.set_value(current) def set_value(self, value): if not value in self._options: raise InvalidValueError("%s is not valid for this setting" % value) RadioSettingValue.set_value(self, value) def get_options(self): """Returns the list of valid option values""" return self._options def __trunc__(self): return self._options.index(self._current) class RadioSettingValueString(RadioSettingValue): """A string setting""" def __init__(self, minlength, maxlength, current, autopad=True): RadioSettingValue.__init__(self) self._minlength = minlength self._maxlength = maxlength self._charset = chirp_common.CHARSET_ASCII self._autopad = autopad self.set_value(current) def set_charset(self, charset): """Sets the set of allowed characters""" self._charset = charset def set_value(self, value): if len(value) < self._minlength or len(value) > self._maxlength: raise InvalidValueError("Value must be between %i and %i chars" % (\ self._minlength, self._maxlength)) if self._autopad: value = value.ljust(self._maxlength) for char in value: if char not in self._charset: raise InvalidValueError("Value contains invalid " + "character `%s'" % char) RadioSettingValue.set_value(self, value) def __str__(self): return self._current class RadioSettingGroup(object): """A group of settings""" def _validate(self, element): # RadioSettingGroup can only contain RadioSettingGroup objects if not isinstance(element, RadioSettingGroup): raise InternalError("Incorrect type") def __init__(self, name, shortname, *elements): self._name = name # Setting identifier self._shortname = shortname # Short human-readable name/description self.__doc__ = name # Longer explanation/documentation self._elements = {} self._element_order = [] for element in elements: self._validate(element) print "Appending element to %s" % self._name self.append(element) def get_name(self): """Returns the group name""" return self._name def get_shortname(self): """Returns the short group identifier""" return self._shortname def set_doc(self, doc): """Sets the docstring for the group""" self.__doc__ = doc def __str__(self): string = "{Settings Group %s:\n" % self._name for element in self._elements.values(): string += str(element) + "\n" string += "}" return string # Kinda list interface def append(self, element): """Adds an element to the group""" self[element.get_name()] = element def __iter__(self): class RSGIterator: """Iterator for a RadioSettingsGroup""" def __init__(self, rsg): self.__rsg = rsg self.__i = 0 def __iter__(self): return self def next(self): """Next Iterator Interface""" if self.__i >= len(self.__rsg.keys()): raise StopIteration() e = self.__rsg[self.__rsg.keys()[self.__i]] self.__i += 1 return e return RSGIterator(self) # Dictionary interface def __len__(self): return len(self._elements) def __getitem__(self, name): return self._elements[name] def __setitem__(self, name, value): if name in self._element_order: raise KeyError("Duplicate item %s" % name) self._elements[name] = value self._element_order.append(name) def items(self): """Returns a key=>value set of elements, like a dict""" return [(name, self._elements[name]) for name in self._element_order] def keys(self): """Returns a list of string element names""" return self._element_order def values(self): """Returns the list of elements""" return [self._elements[name] for name in self._element_order] class RadioSetting(RadioSettingGroup): """A single setting, which could be an array of items like a group""" def _validate(self, value): # RadioSetting can only contain RadioSettingValue objects if not isinstance(value, RadioSettingValue): raise InternalError("Incorrect type") def changed(self): """Returns True if any of the elements in the group have been changed""" for element in self._elements.values(): if element.changed(): return True return False def __str__(self): return "%s:%s" % (self._name, self.value) def __repr__(self): return "[RadioSetting %s:%s]" % (self._name, self._value) # Magic foo.value attribute def __getattr__(self, name): if name == "value": if len(self) == 1: return self._elements[self._element_order[0]] else: return self._elements.values() else: return self.__dict__[name] def __setattr__(self, name, value): if name == "value": if len(self) == 1: self._elements[self._element_order[0]].set_value(value) else: raise InternalError("Setting %s is not a scalar" % self._name) else: self.__dict__[name] = value # List interface def append(self, value): index = len(self._element_order) self._elements[index] = value self._element_order.append(index) def __getitem__(self, name): if not isinstance(name, int): raise IndexError("Index `%s' is not an integer" % name) return self._elements[name] def __setitem__(self, name, value): if not isinstance(name, int): raise IndexError("Index `%s' is not an integer" % name) if self._elements.has_key(name): self._elements[name].set_value(value) else: self._elements[name] = value chirp-0.3.1/chirp/wouxun.py0000644000016101777760000011343712130403635017047 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . """Wouxun radios management module""" import time import os from chirp import util, chirp_common, bitwise, memmap, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString from chirp.wouxun_common import wipe_memory, do_download, do_upload FREQ_ENCODE_TABLE = [ 0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf ] def encode_freq(freq): """Convert frequency (4 decimal digits) to wouxun format (2 bytes)""" enc = 0 div = 1000 for i in range(0, 4): enc <<= 4 enc |= FREQ_ENCODE_TABLE[ (freq/div) % 10 ] div /= 10 return enc def decode_freq(data): """Convert from wouxun format (2 bytes) to frequency (4 decimal digits)""" freq = 0 shift = 12 for i in range(0, 4): freq *= 10 freq += FREQ_ENCODE_TABLE.index( (data>>shift) & 0xf ) shift -= 4 # print "data %04x freq %d shift %d" % (data, freq, shift) return freq @directory.register class KGUVD1PRadio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Wouxun KG-UVD1P,UV2,UV3""" VENDOR = "Wouxun" MODEL = "KG-UVD1P" _model = "KG669V" _querymodel = "HiWOUXUN\x02" CHARSET = list("0123456789") + [chr(x + ord("A")) for x in range(0, 26)] + \ list("?+-") POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Low", watts=1.00)] valid_freq = [(136000000, 175000000), (216000000, 520000000)] _MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; ul16 rx_tone; ul16 tx_tone; u8 _3_unknown_1:4, bcl:1, _3_unknown_2:3; u8 splitdup:1, skip:1, power_high:1, iswide:1, _2_unknown_2:4; u8 unknown[2]; } memory[199]; #seekto 0x0970; struct { u16 vhf_rx_start; u16 vhf_rx_stop; u16 uhf_rx_start; u16 uhf_rx_stop; u16 vhf_tx_start; u16 vhf_tx_stop; u16 uhf_tx_start; u16 uhf_tx_stop; } freq_ranges; #seekto 0x0E5C; struct { u8 unknown_flag1:7, menu_available:1; } settings; #seekto 0x1008; struct { u8 unknown[8]; u8 name[6]; u8 pad[2]; } names[199]; """ @classmethod def get_experimental_warning(cls): return ('This version of the Wouxun driver allows you to modify the ' 'frequency range settings of your radio. This has been tested ' 'and reports from other users indicate that it is a safe ' 'thing to do. However, modifications to this value may have ' 'unintended consequences, including damage to your device. ' 'You have been warned. Proceed at your own risk!') def _identify(self): """Do the original wouxun identification dance""" for _i in range(0, 5): self.pipe.write(self._querymodel) resp = self.pipe.read(9) if len(resp) != 9: print "Got:\n%s" % util.hexprint(resp) print "Retrying identification..." time.sleep(1) continue if resp[2:8] != self._model: raise Exception("I can't talk to this model (%s)" % util.hexprint(resp)) return if len(resp) == 0: raise Exception("Radio not responding") else: raise Exception("Unable to identify radio") def _start_transfer(self): """Tell the radio to go into transfer mode""" self.pipe.write("\x02\x06") time.sleep(0.05) ack = self.pipe.read(1) if ack != "\x06": raise Exception("Radio refused transfer mode") def _download(self): """Talk to an original wouxun and do a download""" try: self._identify() self._start_transfer() return do_download(self, 0x0000, 0x2000, 0x0040) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def _upload(self): """Talk to an original wouxun and do an upload""" try: self._identify() self._start_transfer() return do_upload(self, 0x0000, 0x2000, 0x0010) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def sync_in(self): self._mmap = self._download() self.process_mmap() def sync_out(self): self._upload() def process_mmap(self): if len(self._mmap.get_packed()) != 8192: print "NOTE: Fixing old-style Wouxun image" # Originally, CHIRP's wouxun image had eight bytes of # static data, followed by the first memory at offset # 0x0008. Between 0.1.11 and 0.1.12, this was fixed to 16 # bytes of (whatever) followed by the first memory at # offset 0x0010, like the radio actually stores it. So, # if we find one of those old ones, convert it to the new # format, padding 16 bytes of 0xFF in front. self._mmap = memmap.MemoryMap(("\xFF" * 16) + \ self._mmap.get_packed()[8:8184]) self._memobj = bitwise.parse(self._MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_cross_modes = [ "Tone->Tone", "Tone->DTCS", "DTCS->Tone", "DTCS->", "->Tone", "->DTCS", "DTCS->DTCS", ] rf.valid_modes = ["FM", "NFM"] rf.valid_power_levels = self.POWER_LEVELS rf.valid_bands = self.valid_freq rf.valid_characters = "".join(self.CHARSET) rf.valid_name_length = 6 rf.valid_duplexes = ["", "+", "-", "split", "off"] rf.has_ctone = True rf.has_rx_dtcs = True rf.has_cross = True rf.has_tuning_step = False rf.has_bank = False rf.has_settings = True rf.memory_bounds = (1, 128) rf.can_odd_split = True return rf def get_settings(self): freqranges = RadioSettingGroup("freqranges", "Freq ranges") top = RadioSettingGroup("top", "All Settings", freqranges) rs = RadioSetting("menu_available", "Menu Available", RadioSettingValueBoolean( self._memobj.settings.menu_available)) top.append(rs) rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)", RadioSettingValueInteger(136, 174, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freqranges.append(rs) rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)", RadioSettingValueInteger(136, 174, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)", RadioSettingValueInteger(216, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freqranges.append(rs) rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)", RadioSettingValueInteger(216, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)", RadioSettingValueInteger(136, 174, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freqranges.append(rs) rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)", RadioSettingValueInteger(136, 174, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)", RadioSettingValueInteger(216, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freqranges.append(rs) rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)", RadioSettingValueInteger(216, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freqranges.append(rs) # tell the decoded ranges to UI self.valid_freq = [ ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000), ( decode_freq(self._memobj.freq_ranges.uhf_rx_start) * 1000000, (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)] return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): if element.get_name() != "freqranges" : self.set_settings(element) else: self._set_freq_settings(element) else: try: if "." in element.get_name(): bits = element.get_name().split(".") obj = self._memobj for bit in bits[:-1]: obj = getattr(obj, bit) setting = bits[-1] else: obj = self._memobj.settings setting = element.get_name() print "Setting %s = %s" % (setting, element.value) setattr(obj, setting, element.value) except Exception, e: print element.get_name() raise def _set_freq_settings(self, settings): for element in settings: try: setattr(self._memobj.freq_ranges, element.get_name(), encode_freq(int(element.value))) except Exception, e: print element.get_name() raise def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _get_tone(self, _mem, mem): def _get_dcs(val): code = int("%03o" % (val & 0x07FF)) pol = (val & 0x8000) and "R" or "N" return code, pol if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800: tcode, tpol = _get_dcs(_mem.tx_tone) mem.dtcs = tcode txmode = "DTCS" elif _mem.tx_tone != 0xFFFF: mem.rtone = _mem.tx_tone / 10.0 txmode = "Tone" else: txmode = "" if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800: rcode, rpol = _get_dcs(_mem.rx_tone) mem.rx_dtcs = rcode rxmode = "DTCS" elif _mem.rx_tone != 0xFFFF: mem.ctone = _mem.rx_tone / 10.0 rxmode = "Tone" else: rxmode = "" if txmode == "Tone" and not rxmode: mem.tmode = "Tone" elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone: mem.tmode = "TSQL" elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs: mem.tmode = "DTCS" elif rxmode or txmode: mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (txmode, rxmode) if mem.tmode == "DTCS": mem.dtcs_polarity = "%s%s" % (tpol, rpol) if os.getenv("CHIRP_DEBUG"): print "Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone, rxmode, _mem.rx_tone) def _is_txinh(self, _mem): raw_tx = "" for i in range(0, 4): raw_tx += _mem.tx_freq[i].get_raw() return raw_tx == "\xFF\xFF\xFF\xFF" def get_memory(self, number): _mem = self._memobj.memory[number - 1] _nam = self._memobj.names[number - 1] mem = chirp_common.Memory() mem.number = number if _mem.get_raw() == ("\xff" * 16): mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 if _mem.splitdup: mem.duplex = "split" elif self._is_txinh(_mem): mem.duplex = "off" elif int(_mem.rx_freq) < int(_mem.tx_freq): mem.duplex = "+" elif int(_mem.rx_freq) > int(_mem.tx_freq): mem.duplex = "-" if mem.duplex == "" or mem.duplex == "off": mem.offset = 0 elif mem.duplex == "split": mem.offset = int(_mem.tx_freq) * 10 else: mem.offset = abs(int(_mem.tx_freq) - int(_mem.rx_freq)) * 10 if not _mem.skip: mem.skip = "S" if not _mem.iswide: mem.mode = "NFM" self._get_tone(_mem, mem) mem.power = self.POWER_LEVELS[not _mem.power_high] for i in _nam.name: if i == 0xFF: break mem.name += self.CHARSET[i] mem.extra = RadioSettingGroup("extra", "Extra") bcl = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(bool(_mem.bcl))) bcl.set_doc("Busy Channel Lockout") mem.extra.append(bcl) return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0xA000 return val if mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") elif mem.tmode == "Tone": tx_mode = mem.tmode rx_mode = None else: tx_mode = rx_mode = mem.tmode if tx_mode == "DTCS": _mem.tx_tone = mem.tmode != "DTCS" and \ _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \ _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0]) elif tx_mode: _mem.tx_tone = tx_mode == "Tone" and \ int(mem.rtone * 10) or int(mem.ctone * 10) else: _mem.tx_tone = 0xFFFF if rx_mode == "DTCS": _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode: _mem.rx_tone = int(mem.ctone * 10) else: _mem.rx_tone = 0xFFFF if os.getenv("CHIRP_DEBUG"): print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone) def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] _nam = self._memobj.names[mem.number - 1] if mem.empty: wipe_memory(_mem, "\xFF") return if _mem.get_raw() == ("\xFF" * 16): wipe_memory(_mem, "\x00") _mem.rx_freq = int(mem.freq / 10) if mem.duplex == "split": _mem.tx_freq = int(mem.offset / 10) elif mem.duplex == "off": for i in range(0, 4): _mem.tx_freq[i].set_raw("\xFF") elif mem.duplex == "+": _mem.tx_freq = int(mem.freq / 10) + int(mem.offset / 10) elif mem.duplex == "-": _mem.tx_freq = int(mem.freq / 10) - int(mem.offset / 10) else: _mem.tx_freq = int(mem.freq / 10) _mem.splitdup = mem.duplex == "split" _mem.skip = mem.skip != "S" _mem.iswide = mem.mode != "NFM" self._set_tone(mem, _mem) if mem.power: _mem.power_high = not self.POWER_LEVELS.index(mem.power) else: _mem.power_high = True _nam.name = [0xFF] * 6 for i in range(0, len(mem.name)): try: _nam.name[i] = self.CHARSET.index(mem.name[i]) except IndexError: raise Exception("Character `%s' not supported") for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) @classmethod def match_model(cls, filedata, filename): # New-style image (CHIRP 0.1.12) if len(filedata) == 8192 and \ filedata[0x60:0x64] != "2009" and \ filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \ filedata[0x0d70:0x0d80] == "\xff\xff\xff\xff\xff\xff\xff\xff" \ "\xff\xff\xff\xff\xff\xff\xff\xff": # those areas are (seems to be) unused return True # Old-style image (CHIRP 0.1.11) if len(filedata) == 8200 and \ filedata[0:4] == "\x01\x00\x00\x00": return True return False @directory.register class KGUV6DRadio(KGUVD1PRadio): """Wouxun KG-UV6 (D and X variants)""" MODEL = "KG-UV6" _querymodel = "HiWXUVD1\x02" _MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; ul16 rx_tone; ul16 tx_tone; u8 _3_unknown_1:4, bcl:1, _3_unknown_2:3; u8 splitdup:1, skip:1, power_high:1, iswide:1, _2_unknown_2:4; u8 pad[2]; } memory[199]; #seekto 0x0F00; struct { char welcome1[6]; char welcome2[6]; char single_band[6]; } strings; #seekto 0x0F20; struct { u8 unknown_flag_01:6, vfo_b_ch_disp:2; u8 unknown_flag_02:5, vfo_a_fr_step:3; u8 unknown_flag_03:4, vfo_a_squelch:4; u8 unknown_flag_04:7, power_save:1; u8 unknown_flag_05:5, pf2_function:3; u8 unknown_flag_06:6, roger_beep:2; u8 unknown_flag_07:2, transmit_time_out:6; u8 unknown_flag_08:4, vox:4; u8 unknown_1[4]; u8 unknown_flag_09:6, voice:2; u8 unknown_flag_10:7, beep:1; u8 unknown_flag_11:7, ani_id_enable:1; u8 unknown_2[2]; u8 unknown_flag_12:5, vfo_b_fr_step:3; u8 unknown_3[1]; u8 unknown_flag_13:3, ani_id_tx_delay:5; u8 unknown_4[1]; u8 unknown_flag_14:6, ani_id_sidetone:2; u8 unknown_flag_15:4, tx_time_out_alert:4; u8 unknown_flag_16:6, vfo_a_ch_disp:2; u8 unknown_flag_15:6, scan_mode:2; u8 unknown_flag_16:7, kbd_lock:1; u8 unknown_flag_17:6, ponmsg:2; u8 unknown_flag_18:5, pf1_function:3; u8 unknown_5[1]; u8 unknown_flag_19:7, auto_backlight:1; u8 unknown_flag_20:7, sos_ch:1; u8 unknown_6[2]; u8 unknown_flag_21:7, auto_lock_kbd:1; u8 unknown_flag_22:4, vfo_b_squelch:4; u8 unknown_7[1]; u8 unknown_flag_23:7, stopwatch:1; u8 vfo_a_cur_chan; u8 unknown_flag_24:7, dual_band_receive:1; u8 current_vfo:1, unknown_flag_24:7; u8 unknown_8[2]; u8 mode_password[6]; u8 reset_password[6]; u8 ani_id_content[6]; u8 unknown_flag_25:7, menu_available:1; u8 unknown_9[1]; u8 priority_chan; u8 vfo_b_cur_chan; } settings; #seekto 0x0f60; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; ul16 rx_tone; ul16 tx_tone; u8 _3_unknown_3:4, bcl:1, _3_unknown_4:3; u8 splitdup:1, _2_unknown_3:1, power_high:1, iswide:1, _2_unknown_4:4; u8 pad[2]; } vfo_settings[2]; #seekto 0x0f80; u16 fm_presets_0[9]; #seekto 0x0ff0; struct { u16 vhf_rx_start; u16 vhf_rx_stop; u16 uhf_rx_start; u16 uhf_rx_stop; u16 vhf_tx_start; u16 vhf_tx_stop; u16 uhf_tx_start; u16 uhf_tx_stop; } freq_ranges; #seekto 0x1010; struct { u8 name[6]; u8 pad[10]; } names[199]; #seekto 0x1f60; struct { u8 unknown_flag_26:6, tx_offset_dir:2; u8 tx_offset[6]; u8 pad[9]; } vfo_offset[2]; #seekto 0x1f80; u16 fm_presets_1[9]; """ def get_features(self): rf = KGUVD1PRadio.get_features(self) rf.memory_bounds = (1, 199) return rf def get_settings(self): freqranges = RadioSettingGroup("freqranges", "Freq ranges") top = RadioSettingGroup("top", "All Settings", freqranges) rs = RadioSetting("menu_available", "Menu Available", RadioSettingValueBoolean( self._memobj.settings.menu_available)) top.append(rs) rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freqranges.append(rs) rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freqranges.append(rs) rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freqranges.append(rs) rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freqranges.append(rs) rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)", RadioSettingValueInteger(1, 1000, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freqranges.append(rs) # tell the decoded ranges to UI self.valid_freq = [ ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000), ( decode_freq(self._memobj.freq_ranges.uhf_rx_start) * 1000000, (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)] def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered # add some radio specific settings options = ["Off", "Welcome", "V bat"] rs = RadioSetting("ponmsg", "Poweron message", RadioSettingValueList(options, options[self._memobj.settings.ponmsg])) top.append(rs) rs = RadioSetting("strings.welcome1", "Power-On Message 1", RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome1))) top.append(rs) rs = RadioSetting("strings.welcome2", "Power-On Message 2", RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome2))) top.append(rs) rs = RadioSetting("strings.single_band", "Single Band Message", RadioSettingValueString(0, 6, _filter(self._memobj.strings.single_band))) top.append(rs) options = ["Channel", "ch/freq","Name", "VFO"] rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode", RadioSettingValueList(options, options[self._memobj.settings.vfo_a_ch_disp])) top.append(rs) rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode", RadioSettingValueList(options, options[self._memobj.settings.vfo_b_ch_disp])) top.append(rs) # TODO - vfo_a_fr_step # TODO -vfo_b_fr_step:3; rs = RadioSetting("vfo_a_squelch", "VFO A Squelch", RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_a_squelch)) top.append(rs) rs = RadioSetting("vfo_b_squelch", "VFO B Squelch", RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_b_squelch)) top.append(rs) rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel", RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_a_cur_chan)) top.append(rs) rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel", RadioSettingValueInteger(0, 199, self._memobj.settings.vfo_b_cur_chan)) top.append(rs) rs = RadioSetting("priority_chan", "Priority channel", RadioSettingValueInteger(0, 199, self._memobj.settings.priority_chan)) top.append(rs) rs = RadioSetting("power_save", "Power save", RadioSettingValueBoolean(self._memobj.settings.power_save)) top.append(rs) options = ["Off", "Scan", "Lamp", "SOS", "Radio"] rs = RadioSetting("pf1_function", "PF1 Function select", RadioSettingValueList(options, options[self._memobj.settings.pf1_function])) top.append(rs) options = ["Off", "Radio", "fr/ch", "Rpt", "Stopwatch", "Lamp", "SOS"] rs = RadioSetting("pf2_function", "PF2 Function select", RadioSettingValueList(options, options[self._memobj.settings.pf2_function])) top.append(rs) options = ["Off", "Begin", "End", "Both"] rs = RadioSetting("roger_beep", "Roger beep select", RadioSettingValueList(options, options[self._memobj.settings.roger_beep])) top.append(rs) # TODO - transmit_time_out:6; rs = RadioSetting("vox", "Vox", RadioSettingValueInteger(0, 10, self._memobj.settings.vox)) top.append(rs) options = ["Off", "Chinese", "English"] rs = RadioSetting("voice", "Voice", RadioSettingValueList(options, options[self._memobj.settings.voice])) top.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(self._memobj.settings.beep)) top.append(rs) rs = RadioSetting("ani_id_enable", "ANI id enable", RadioSettingValueBoolean(self._memobj.settings.ani_id_enable)) top.append(rs) rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay", RadioSettingValueInteger(0, 30, self._memobj.settings.ani_id_tx_delay)) top.append(rs) options = ["Off", "Key", "ANI", "Key+ANI"] rs = RadioSetting("ani_id_sidetone", "ANI id sidetone", RadioSettingValueList(options, options[self._memobj.settings.ani_id_sidetone])) top.append(rs) # TODO tx_time_out_alert:4; options = ["Time", "Carrier", "Search"] rs = RadioSetting("scan_mode", "Scan mode", RadioSettingValueList(options, options[self._memobj.settings.scan_mode])) top.append(rs) rs = RadioSetting("kbd_lock", "Keyboard lock", RadioSettingValueBoolean(self._memobj.settings.kbd_lock)) top.append(rs) rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard", RadioSettingValueBoolean(self._memobj.settings.auto_lock_kbd)) top.append(rs) rs = RadioSetting("auto_backlight", "Auto backlight", RadioSettingValueBoolean(self._memobj.settings.auto_backlight)) top.append(rs) options = ["CH A", "CH B"] rs = RadioSetting("sos_ch", "SOS CH", RadioSettingValueList(options, options[self._memobj.settings.sos_ch])) top.append(rs) rs = RadioSetting("stopwatch", "Stopwatch", RadioSettingValueBoolean(self._memobj.settings.stopwatch)) top.append(rs) rs = RadioSetting("dual_band_receive", "Dual band receive", RadioSettingValueBoolean(self._memobj.settings.dual_band_receive)) top.append(rs) options = ["VFO A", "VFO B"] rs = RadioSetting("current_vfo", "Current VFO", RadioSettingValueList(options, options[self._memobj.settings.current_vfo])) top.append(rs) _pwd = self._memobj.settings.mode_password rs = RadioSetting("mode_password", "Mode password (000000 disabled)", RadioSettingValueInteger(0, 9, _pwd[0]), RadioSettingValueInteger(0, 9, _pwd[1]), RadioSettingValueInteger(0, 9, _pwd[2]), RadioSettingValueInteger(0, 9, _pwd[3]), RadioSettingValueInteger(0, 9, _pwd[4]), RadioSettingValueInteger(0, 9, _pwd[5])) top.append(rs) _pwd = self._memobj.settings.reset_password rs = RadioSetting("reset_password", "Reset password (000000 disabled)", RadioSettingValueInteger(0, 9, _pwd[0]), RadioSettingValueInteger(0, 9, _pwd[1]), RadioSettingValueInteger(0, 9, _pwd[2]), RadioSettingValueInteger(0, 9, _pwd[3]), RadioSettingValueInteger(0, 9, _pwd[4]), RadioSettingValueInteger(0, 9, _pwd[5])) top.append(rs) try: _ani = self._memobj.settings.ani_id_content rs = RadioSetting("ani_id_content", "ANI Code", RadioSettingValueInteger(0, 9, _ani[0]), RadioSettingValueInteger(0, 9, _ani[1]), RadioSettingValueInteger(0, 9, _ani[2]), RadioSettingValueInteger(0, 9, _ani[3]), RadioSettingValueInteger(0, 9, _ani[4]), RadioSettingValueInteger(0, 9, _ani[5])) top.append(rs) except Exception: print ("Your ANI code is not five digits, which is not currently" " supported in CHIRP.") return top @classmethod def match_model(cls, filedata, filename): if len(filedata) == 8192 and \ filedata[0x1f77:0x1f7d] == "WELCOM": return True return False @directory.register class KG816Radio(KGUVD1PRadio, chirp_common.ExperimentalRadio): """Wouxun KG816""" MODEL = "KG816" _MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; ul16 rx_tone; ul16 tx_tone; u8 _3_unknown_1:4, bcl:1, _3_unknown_2:3; u8 splitdup:1, skip:1, power_high:1, iswide:1, _2_unknown_2:4; u8 unknown[2]; } memory[199]; #seekto 0x0d70; struct { u16 vhf_rx_start; u16 vhf_rx_stop; u16 uhf_rx_start; u16 uhf_rx_stop; u16 vhf_tx_start; u16 vhf_tx_stop; u16 uhf_tx_start; u16 uhf_tx_stop; } freq_ranges; #seekto 0x1010; struct { u8 name[6]; u8 pad[10]; } names[199]; """ @classmethod def get_experimental_warning(cls): return ('We have not that much information on this model ' 'up to now we only know it has the same memory ' 'organization of KGUVD1 but uses 199 memories. ' 'it has been reported to work but ' 'proceed at your own risk!') def get_features(self): rf = KGUVD1PRadio.get_features(self) rf.memory_bounds = (1, 199) # this is the only known difference return rf def get_settings(self): freqranges = RadioSettingGroup("freqranges", "Freq ranges (read only)") top = RadioSettingGroup("top", "All Settings", freqranges) rs = RadioSetting("vhf_rx_start", "vhf rx start", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freqranges.append(rs) rs = RadioSetting("vhf_rx_stop", "vhf rx stop", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_rx_start", "uhf rx start", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freqranges.append(rs) rs = RadioSetting("uhf_rx_stop", "uhf rx stop", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freqranges.append(rs) rs = RadioSetting("vhf_tx_start", "vhf tx start", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freqranges.append(rs) rs = RadioSetting("vhf_tx_stop", "vhf tx stop", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freqranges.append(rs) rs = RadioSetting("uhf_tx_start", "uhf tx start", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freqranges.append(rs) rs = RadioSetting("uhf_tx_stop", "uhf tx stop", RadioSettingValueInteger(136, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freqranges.append(rs) # tell the decoded ranges to UI self.valid_freq = [ ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000)] return top @classmethod def match_model(cls, filedata, filename): if len(filedata) == 8192 and \ filedata[0x60:0x64] != "2009" and \ filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \ filedata[0x0d70:0x0d80] != "\xff\xff\xff\xff\xff\xff\xff\xff" \ "\xff\xff\xff\xff\xff\xff\xff\xff": return True return False chirp-0.3.1/chirp/__init__.py0000644000016101777760000000171712130403636017237 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . CHIRP_VERSION="0.3.1" import os import sys from glob import glob module_dir = os.path.dirname(sys.modules["chirp"].__file__) __all__ = [] for i in glob(os.path.join(module_dir, "*.py")): name = os.path.basename(i)[:-3] if not name.startswith("__"): __all__.append(name) chirp-0.3.1/chirp/thuv1f.py0000644000016100007500000003453512023560645015265 0ustar jenkins00000000000000# Copyright 2012 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 2 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 . import struct from chirp import chirp_common, errors, util, directory, memmap from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueList, RadioSettingValueBoolean, \ RadioSettingValueString def uvf1_identify(radio): """Do identify handshake with TYT TH-UVF1""" radio.pipe.write("PROG333") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio did not respond") radio.pipe.write("\x02") ident = radio.pipe.read(16) print "Ident:\n%s" % util.hexprint(ident) radio.pipe.write("\x06") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio did not ack identification") return ident def uvf1_download(radio): """Download from TYT TH-UVF1""" data = uvf1_identify(radio) for i in range(0, 0x1000, 0x10): msg = struct.pack(">BHB", ord("R"), i, 0x10) radio.pipe.write(msg) block = radio.pipe.read(0x10 + 4) if len(block) != (0x10 + 4): raise errors.RadioError("Radio sent a short block") radio.pipe.write("\x06") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio NAKed block") data += block[4:] status = chirp_common.Status() status.cur = i status.max = 0x1000 status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("\x45") return memmap.MemoryMap(data) def uvf1_upload(radio): """Upload to TYT TH-UVF1""" data = uvf1_identify(radio) radio.pipe.setTimeout(1) if data != radio._mmap[:16]: raise errors.RadioError("Unable to talk to this model") for i in range(0, 0x1000, 0x10): addr = i + 0x10 msg = struct.pack(">BHB", ord("W"), i, 0x10) msg += radio._mmap[addr:addr+0x10] radio.pipe.write(msg) ack = radio.pipe.read(1) if ack != "\x06": print repr(ack) raise errors.RadioError("Radio did not ack block %i" % i) status = chirp_common.Status() status.cur = i status.max = 0x1000 status.msg = "Cloning to radio" radio.status_fn(status) # End of clone? radio.pipe.write("\x45") THUV1F_MEM_FORMAT = """ struct mem { bbcd rx_freq[4]; bbcd tx_freq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 unknown1:1, pttid:2, unknown2:2, ishighpower:1, unknown3:2; u8 unknown4:4, isnarrow:1, vox:1, bcl:2; u8 unknown5:1, scan:1, unknown6:3, scramble_code:3; u8 unknown7; }; struct name { char name[7]; }; #seekto 0x0020; struct mem memory[128]; #seekto 0x0840; struct { u8 scans:2, autolk:1, unknown1:5; u8 light:2, unknown6:2, disnm:1, voice:1, beep:1, rxsave:1; u8 led:2, unknown5:3, ani:1, roger:1, dw:1; u8 opnmsg:2, unknown4:1, dwait:1, unknown9:4; u8 squelch; u8 unknown2:4, tot:4; u8 unknown3:4, vox_level:4; u8 pad[10]; char ponmsg[6]; } settings; #seekto 0x08D0; struct name names[128]; """ LED_LIST = ["Off", "On", "Auto"] LIGHT_LIST = ["Purple", "Orange", "Blue"] VOX_LIST = ["1", "2", "3", "4", "5", "6", "7", "8"] TOT_LIST = ["Off", "30s", "60s", "90s", "120s", "150s", "180s", "210s", "240s", "270s"] SCANS_LIST = ["Time", "Carry", "Seek"] OPNMSG_LIST = ["Off", "DC", "Message"] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5), chirp_common.PowerLevel("Low", watts=1), ] PTTID_LIST = ["Off", "BOT", "EOT", "Both"] BCL_LIST = ["Off", "CSQ", "QT/DQT"] CODES_LIST = [x for x in range(1, 9)] @directory.register class TYTTHUVF1Radio(chirp_common.CloneModeRadio): """TYT TH-UVF1""" VENDOR = "TYT" MODEL = "TH-UVF1" def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 128) rf.has_bank = False rf.has_ctone = True rf.has_tuning_step = False rf.has_cross = True rf.has_rx_dtcs = True rf.has_settings = True rf.can_odd_split = True rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-" rf.valid_bands = [(136000000, 174000000), (420000000, 470000000)] rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS rf.valid_modes = ["FM", "NFM"] rf.valid_name_length = 7 rf.valid_cross_modes = ["Tone->Tone", "DTCS->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->"] return rf def sync_in(self): try: self._mmap = uvf1_download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() def sync_out(self): try: uvf1_upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) @classmethod def match_model(cls, filedata, filename): return filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" + "\x35\x00\x39\x00\x47\x00\x52\x00") def process_mmap(self): self._memobj = bitwise.parse(THUV1F_MEM_FORMAT, self._mmap) def _decode_tone(self, toneval): pol = "N" rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF) if toneval[0].get_bits(0xFF) == 0xFF: mode = "" val = 0 elif toneval[1].get_bits(0xC0) == 0xC0: mode = "DTCS" val = int("%x" % (rawval & 0x3FFF)) pol = "R" elif toneval[1].get_bits(0x80): mode = "DTCS" val = int("%x" % (rawval & 0x3FFF)) else: mode = "Tone" val = int(toneval) / 10.0 return mode, val, pol def _encode_tone(self, _toneval, mode, val, pol): toneval = 0 if mode == "Tone": toneval = int("%i" % (val * 10), 16) elif mode == "DTCS": toneval = int("%i" % val, 16) toneval |= 0x8000 if pol == "R": toneval |= 0x4000 else: toneval = 0xFFFF _toneval[0].set_raw(toneval & 0xFF) _toneval[1].set_raw((toneval >> 8) & 0xFF) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _is_txinh(self, _mem): raw_tx = "" for i in range(0, 4): raw_tx += _mem.tx_freq[i].get_raw() return raw_tx == "\xFF\xFF\xFF\xFF" def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"): mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 txfreq = int(_mem.tx_freq) * 10 if self._is_txinh(_mem): mem.duplex = "off" mem.offset = 0 elif txfreq == mem.freq: mem.duplex = "" elif abs(txfreq - mem.freq) > 70000000: mem.duplex = "split" mem.offset = txfreq elif txfreq < mem.freq: mem.duplex = "-" mem.offset = mem.freq - txfreq elif txfreq > mem.freq: mem.duplex = "+" mem.offset = txfreq - mem.freq txmode, txval, txpol = self._decode_tone(_mem.tx_tone) rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone) chirp_common.split_tone_decode(mem, (txmode, txval, txpol), (rxmode, rxval, rxpol)) mem.name = str(self._memobj.names[number - 1].name) mem.name = mem.name.replace("\xFF", " ").rstrip() mem.skip = not _mem.scan and "S" or "" mem.mode = _mem.isnarrow and "NFM" or "FM" mem.power = POWER_LEVELS[1 - _mem.ishighpower] mem.extra = RadioSettingGroup("extra", "Extra Settings") rs = RadioSetting("pttid", "PTT ID", RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])) mem.extra.append(rs) rs = RadioSetting("vox", "VOX", RadioSettingValueBoolean(_mem.vox)) mem.extra.append(rs) rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueList(BCL_LIST, BCL_LIST[_mem.bcl])) mem.extra.append(rs) rs = RadioSetting("scramble_code", "Scramble Code", RadioSettingValueList(CODES_LIST, CODES_LIST[_mem.scramble_code])) mem.extra.append(rs) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] if mem.empty: _mem.set_raw("\xFF" * 16) return if _mem.get_raw() == ("\xFF" * 16): print "Initializing empty memory" _mem.set_raw("\x00" * 16) _mem.rx_freq = mem.freq / 10 if mem.duplex == "off": for i in range(0, 4): _mem.tx_freq[i].set_raw("\xFF") elif mem.duplex == "split": _mem.tx_freq = mem.offset / 10 elif mem.duplex == "-": _mem.tx_freq = (mem.freq - mem.offset) / 10 elif mem.duplex == "+": _mem.tx_freq = (mem.freq + mem.offset) / 10 else: _mem.tx_freq = mem.freq / 10 (txmode, txval, txpol), (rxmode, rxval, rxpol) = \ chirp_common.split_tone_encode(mem) self._encode_tone(_mem.tx_tone, txmode, txval, txpol) self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol) self._memobj.names[mem.number - 1].name = mem.name.ljust(7, "\xFF") _mem.scan = mem.skip == "" _mem.isnarrow = mem.mode == "NFM" _mem.ishighpower = mem.power == POWER_LEVELS[0] for element in mem.extra: setattr(_mem, element.get_name(), element.value) def get_settings(self): _settings = self._memobj.settings group = RadioSettingGroup("top", "All Settings") group.append( RadioSetting("led", "LED Mode", RadioSettingValueList(LED_LIST, LED_LIST[_settings.led]))) group.append( RadioSetting("light", "Light Color", RadioSettingValueList(LIGHT_LIST, LIGHT_LIST[_settings.light]))) group.append( RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch))) group.append( RadioSetting("vox_level", "VOX Level", RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox_level]))) group.append( RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep))) group.append( RadioSetting("ani", "ANI", RadioSettingValueBoolean(_settings.ani))) group.append( RadioSetting("dwait", "D.WAIT", RadioSettingValueBoolean(_settings.dwait))) group.append( RadioSetting("tot", "Timeout Timer", RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot]))) group.append( RadioSetting("roger", "Roger Beep", RadioSettingValueBoolean(_settings.roger))) group.append( RadioSetting("dw", "Dual Watch", RadioSettingValueBoolean(_settings.dw))) group.append( RadioSetting("rxsave", "RX Save", RadioSettingValueBoolean(_settings.rxsave))) group.append( RadioSetting("scans", "Scans", RadioSettingValueList(SCANS_LIST, SCANS_LIST[_settings.scans]))) group.append( RadioSetting("autolk", "Auto Lock", RadioSettingValueBoolean(_settings.autolk))) group.append( RadioSetting("voice", "Voice", RadioSettingValueBoolean(_settings.voice))) group.append( RadioSetting("opnmsg", "Opening Message", RadioSettingValueList(OPNMSG_LIST, OPNMSG_LIST[_settings.opnmsg]))) group.append( RadioSetting("disnm", "Display Name", RadioSettingValueBoolean(_settings.disnm))) def _filter(name): print repr(str(name)) return str(name).rstrip("\xFF").rstrip() group.append( RadioSetting("ponmsg", "Power-On Message", RadioSettingValueString(0, 7, _filter(_settings.ponmsg)))) return group def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue setattr(_settings, element.get_name(), element.value) chirp-0.3.1/chirp/bitwise.py0000644000016101777760000006070312130403635017145 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . # Language: # # Example definitions: # # u8 foo; /* Unsigned 8-bit value */ # u16 foo; /* Unsigned 16-bit value */ # ul16 foo; /* Unsigned 16-bit value (LE) */ # u24 foo; /* Unsigned 24-bit value */ # ul24 foo; /* Unsigned 24-bit value (LE) */ # u32 foo; /* Unsigned 32-bit value */ # ul32 foo; /* Unsigned 32-bit value (LE) */ # char foo; /* Character (single-byte */ # lbcd foo; /* BCD-encoded byte (LE) */ # bbcd foo; /* BCD-encoded byte (BE) */ # char foo[8]; /* 8-char array */ # struct { # u8 foo; # u16 bar; # } baz; /* Structure with u8 and u16 */ # # Example directives: # # #seekto 0x1AB; /* Set the data offset to 0x1AB */ # #seek 4; /* Set the data offset += 4 */ # #printoffset "foobar" /* Echo the live data offset, # prefixed by string while parsing */ # # Usage: # # Create a data definition in a string, and pass it and the data # to parse to the parse() function. The result is a structure with # dict-like objects for structures, indexed by name, and lists of # objects for arrays. The actual data elements can be interpreted # as integers directly (for int types). Strings and BCD arrays # behave as expected. import struct import os from chirp import bitwise_grammar from chirp.memmap import MemoryMap class ParseError(Exception): """Indicates an error parsing a definition""" pass def format_binary(nbits, value, pad=8): s = "" for i in range(0, nbits): s = "%i%s" % (value & 0x01, s) value >>= 1 return "%s%s" % ((pad - len(s)) * ".", s) def bits_between(start, end): bits = (1 << (end - start )) - 1 return bits << start def pp(structure, level=0): for i in structure: if isinstance(i, list): pp(i, level+2) elif isinstance(i, tuple): if isinstance(i[1], str): print "%s%s: %s" % (" " * level, i[0], i[1]) else: print "%s%s:" % (" " * level, i[0]) pp(i, level+2) elif isinstance(i, str): print "%s%s" % (" " * level, i) def array_copy(dst, src): """Copy an array src into DataElement array dst""" if len(dst) != len(src): raise Exception("Arrays differ in size") for i in range(0, len(dst)): dst[i].set_value(src[i]) def bcd_to_int(bcd_array): """Convert an array of bcdDataElement like \x12\x34 into an int like 1234""" value = 0 for bcd in bcd_array: a, b = bcd.get_value() value = (value * 100) + (a * 10) + b return value def int_to_bcd(bcd_array, value): """Convert an int like 1234 into bcdDataElements like "\x12\x34" """ for i in reversed(range(0, len(bcd_array))): bcd_array[i].set_value(value % 100) value /= 100 def get_string(char_array): """Convert an array of charDataElements into a string""" return "".join([x.get_value() for x in char_array]) def set_string(char_array, string): """Set an array of charDataElements from a string""" array_copy(char_array, list(string)) class DataElement: _size = 1 def __init__(self, data, offset, count=1): self._data = data self._offset = offset self._count = count def size(self): return self._size * 8 def get_offset(self): return self._offset def _get_value(self, data): raise Exception("Not implemented") def get_value(self): return self._get_value(self._data[self._offset:self._offset+self._size]) def set_value(self, value): raise Exception("Not implemented for %s" % self.__class__) def get_raw(self): return self._data[self._offset:self._offset+self._size] def set_raw(self, data): self._data[self._offset] = data[:self._size] def __getattr__(self, name): raise AttributeError("Unknown attribute %s in %s" % (name, self.__class__)) def __repr__(self): return "(%s:%i bytes @ %04x)" % (self.__class__.__name__, self._size, self._offset) class arrayDataElement(DataElement): def __repr__(self): if isinstance(self.__items[0], bcdDataElement): return "%i:[(%i)]" % (len(self.__items), int(self)) if isinstance(self.__items[0], charDataElement): return "%i:[(%s)]" % (len(self.__items), str(self)) s = "%i:[" % len(self.__items) s += ",".join([repr(item) for item in self.__items]) s += "]" return s def __init__(self, offset): self.__items = [] self._offset = offset def append(self, item): self.__items.append(item) def get_value(self): return list(self.__items) def get_raw(self): return "".join([item.get_raw() for item in self.__items]) def __setitem__(self, index, val): self.__items[index].set_value(val) def __getitem__(self, index): return self.__items[index] def __len__(self): return len(self.__items) def __str__(self): if isinstance(self.__items[0], charDataElement): return "".join([x.get_value() for x in self.__items]) else: return str(self.__items) def __int__(self): if isinstance(self.__items[0], bbcdDataElement): val = 0 for i in self.__items: tens, ones = i.get_value() val = (val * 100) + (tens * 10) + ones return val elif isinstance(self.__items[0], lbcdDataElement): val = 0 for i in reversed(self.__items): ones, tens = i.get_value() val = (val * 100) + (tens * 10) + ones return val else: raise ValueError("Cannot coerce this to int") def __set_value_bbcd(self, value): for i in reversed(self.__items): twodigits = value % 100 value /= 100 i.set_value(twodigits) def __set_value_lbcd(self, value): for i in self.__items: twodigits = value % 100 value /= 100 i.set_value(twodigits) def __set_value_char(self, value): for i in range(0, len(self.__items)): self.__items[i].set_value(value[i]) def set_value(self, value): if isinstance(self.__items[0], bbcdDataElement): self.__set_value_bbcd(int(value)) elif isinstance(self.__items[0], lbcdDataElement): self.__set_value_lbcd(int(value)) elif isinstance(self.__items[0], charDataElement): self.__set_value_char(str(value)) elif len(value) != len(self.__items): raise ValueError("Array cardinality mismatch") else: for i in range(0, len(value)): self.__items[i].set_value(value[i]) def index(self, value): index = 0 for i in self.__items: if i.get_value() == value: return index index += 1 raise IndexError() def __iter__(self): return iter(self.__items) def size(self): size = 0 for i in self.__items: size += i.size() return size class intDataElement(DataElement): def __repr__(self): fmt = "0x%%0%iX" % (self._size * 2) return fmt % int(self) def __int__(self): return self.get_value() def __invert__(self): return ~self.get_value() def __trunc__(self): return self.get_value() def __abs__(self): return abs(self.get_value()) def __mod__(self, val): return self.get_value() % val def __mul__(self, val): return self.get_value() * val def __div__(self, val): return self.get_value() / val def __add__(self, val): return self.get_value() + val def __sub__(self, val): return self.get_value() - val def __or__(self, val): return self.get_value() | val def __and__(self, val): return self.get_value() & val def __radd__(self, val): return val + self.get_value() def __rsub__(self, val): return val - self.get_value() def __rmul__(self, val): return val * self.get_value() def __rdiv__(self, val): return val / self.get_value() def __rand__(self, val): return val & self.get_value() def __ror__(self, val): return val | self.get_value() def __rmod__(self, val): return val % self.get_value() def __lshift__(self, val): return self.get_value() << val def __rshift__(self, val): return self.get_value() >> val def __iadd__(self, val): self.set_value(self.get_value() + val) return self def __isub__(self, val): self.set_value(self.get_value() - val) return self def __imul__(self, val): self.set_value(self.get_value() * val) return self def __idiv__(self, val): self.set_value(self.get_value() / val) return self def __imod__(self, val): self.set_value(self.get_value() % val) return self def __iand__(self, val): self.set_value(self.get_value() & val) return self def __ior__(self, val): self.set_value(self.get_value() | val) return self def __index__(self): return abs(self) def __eq__(self, val): return self.get_value() == val def __ne__(self, val): return self.get_value() != val def __lt__(self, val): return self.get_value() < val def __le__(self, val): return self.get_value() <= val def __gt__(self, val): return self.get_value() > val def __ge__(self, val): return self.get_value() >= val def __nonzero__(self): return self.get_value() != 0 class u8DataElement(intDataElement): _size = 1 def _get_value(self, data): return ord(data) def set_value(self, value): self._data[self._offset] = (int(value) & 0xFF) class u16DataElement(intDataElement): _size = 2 _endianess = ">" def _get_value(self, data): return struct.unpack(self._endianess + "H", data)[0] def set_value(self, value): self._data[self._offset] = struct.pack(self._endianess + "H", int(value) & 0xFFFF) class ul16DataElement(u16DataElement): _endianess = "<" class u24DataElement(intDataElement): _size = 3 _endianess = ">" def _get_value(self, data): pre = self._endianess == ">" and "\x00" or "" post = self._endianess == "<" and "\x00" or "" return struct.unpack(self._endianess + "I", pre+data+post)[0] def set_value(self, value): if self._endianess == "<": start = 0 end = 3 else: start = 1 end = 4 self._data[self._offset] = struct.pack(self._endianess + "I", int(value) & 0xFFFFFFFF)[start:end] class ul24DataElement(u24DataElement): _endianess = "<" class u32DataElement(intDataElement): _size = 4 _endianess = ">" def _get_value(self, data): return struct.unpack(self._endianess + "I", data)[0] def set_value(self, value): self._data[self._offset] = struct.pack(self._endianess + "I", int(value) & 0xFFFFFFFF) class ul32DataElement(u32DataElement): _endianess = "<" class charDataElement(DataElement): _size = 1 def __str__(self): return str(self.get_value()) def __int__(self): return ord(self.get_value()) def _get_value(self, data): return data def set_value(self, value): self._data[self._offset] = str(value) class bcdDataElement(DataElement): def __int__(self): tens, ones = self.get_value() return (tens * 10) + ones def set_bits(self, mask): self._data[self._offset] = ord(self._data[self._offset]) | int(mask) def clr_bits(self, mask): self._data[self._offset] = ord(self._data[self._offset]) & ~int(mask) def get_bits(self, mask): return ord(self._data[self._offset]) & int(mask) def set_raw(self, data): if isinstance(data, int): self._data[self._offset] = data & 0xFF elif isinstance(data, str): self._data[self._offset] = data[0] else: raise TypeError("Unable to set bcdDataElement from type %s" % type(data)) class lbcdDataElement(bcdDataElement): _size = 1 def _get_value(self, data): a = (ord(data) & 0xF0) >> 4 b = ord(data) & 0x0F return (b, a) def set_value(self, value): value = int("%02i" % value, 16) self._data[self._offset] = value class bbcdDataElement(bcdDataElement): _size = 1 def _get_value(self, data): a = (ord(data) & 0xF0) >> 4 b = ord(data) & 0x0F return (a, b) def set_value(self, value): self._data[self._offset] = int("%02i" % value, 16) class bitDataElement(intDataElement): _nbits = 0 _shift = 0 _subgen = u8DataElement # Default to a byte def __repr__(self): fmt = "0x%%0%iX (%%sb)" % (self._size * 2) return fmt % (int(self), format_binary(self._nbits, self.get_value())) def get_value(self): data = self._subgen(self._data, self._offset).get_value() mask = bits_between(self._shift-self._nbits, self._shift) val = data & mask #print "start: %i bits: %i" % (self._shift, self._nbits) #print "data: %04x" % data #print "mask: %04x" % mask #print " val: %04x" % val val >>= (self._shift - self._nbits) return val def set_value(self, value): mask = bits_between(self._shift-self._nbits, self._shift) data = self._subgen(self._data, self._offset).get_value() data &= ~mask #print "data: %04x" % data #print "mask: %04x" % mask #print "valu: %04x" % value value = ((int(value) << (self._shift-self._nbits)) & mask) | data self._subgen(self._data, self._offset).set_value(value) def size(self): return self._nbits class structDataElement(DataElement): def __repr__(self): s = "struct {" + os.linesep for prop in self._keys: s += " %15s: %s%s" % (prop, repr(self._generators[prop]), os.linesep) s += "} %s (%i bytes at 0x%04X)%s" % (self._name, self.size() / 8, self._offset, os.linesep) return s def __init__(self, *args, **kwargs): self._generators = {} self._keys = [] self._count = 1 if "name" in kwargs.keys(): self._name = kwargs["name"] del kwargs["name"] else: self._name = "(anonymous)" DataElement.__init__(self, *args, **kwargs) self.__init = True def _value(self, data, generators): result = {} for name, gen in generators.items(): result[name] = gen.get_value(data) return result def get_value(self): result = [] for i in range(0, self._count): result.append(self._value(self._data, self._generators[i])) if self._count == 1: return result[0] else: return result def __getitem__(self, key): return self._generators[key] def __setitem__(self, key, value): if key in self._generators: self._generators[key].set_value(value) else: self._generators[key] = value self._keys.append(key) def __getattr__(self, name): try: return self._generators[name] except KeyError: raise AttributeError("No attribute %s in struct" % name) def __setattr__(self, name, value): if not self.__dict__.has_key("_structDataElement__init"): self.__dict__[name] = value else: self.__dict__["_generators"][name].set_value(value) def size(self): size = 0 for name, gen in self._generators.items(): if not isinstance(gen, list): gen = [gen] i = 0 for el in gen: i += 1 size += el.size() #print "Size of %s[%i] = %i" % (name, i, el.size()) return size def get_raw(self): size = self.size() / 8 return self._data[self._offset:self._offset+size] def set_raw(self, buffer): if len(buffer) != (self.size() / 8): raise ValueError("Struct size mismatch during set_raw()") self._data[self._offset] = buffer class Processor: _types = { "u8" : u8DataElement, "u16" : u16DataElement, "ul16" : ul16DataElement, "u24" : u24DataElement, "ul24" : ul24DataElement, "u32" : u32DataElement, "ul32" : ul32DataElement, "char" : charDataElement, "lbcd" : lbcdDataElement, "bbcd" : bbcdDataElement, } def __init__(self, data, offset): self._data = data self._offset = offset self._obj = None self._user_types = {} def do_symbol(self, symdef, gen): name = symdef[1] self._generators[name] = gen def do_bitfield(self, dtype, bitfield): bytes = self._types[dtype](self._data, 0).size() / 8 bitsleft = bytes * 8 for _bitdef, defn in bitfield: name = defn[0][1] bits = int(defn[1][1]) if bitsleft < 0: raise ParseError("Invalid bitfield spec") class bitDE(bitDataElement): _nbits = bits _shift = bitsleft _subgen = self._types[dtype] self._generators[name] = bitDE(self._data, self._offset) bitsleft -= bits if bitsleft: print "WARNING: %i trailing bits unaccounted for in %s" % (bitsleft, bitfield) return bytes def parse_defn(self, defn): dtype = defn[0] if defn[1][0] == "bitfield": size = self.do_bitfield(dtype, defn[1][1]) count = 1 self._offset += size else: if defn[1][0] == "array": sym = defn[1][1][0] count = int(defn[1][1][1][1]) else: count = 1 sym = defn[1] name = sym[1] res = arrayDataElement(self._offset) size = 0 for i in range(0, count): gen = self._types[dtype](self._data, self._offset) res.append(gen) self._offset += (gen.size() / 8) if count == 1: self._generators[name] = res[0] else: self._generators[name] = res def parse_struct_decl(self, struct): block = struct[:-1] if block[0][0] == "symbol": # This is a pre-defined struct block = self._user_types[block[0][1]] deftype = struct[-1] if deftype[0] == "array": name = deftype[1][0][1] count = int(deftype[1][1][1]) elif deftype[0] == "symbol": name = deftype[1] count = 1 result = arrayDataElement(self._offset) for i in range(0, count): element = structDataElement(self._data, self._offset, count, name=name) result.append(element) tmp = self._generators self._generators = element self.parse_block(block) self._generators = tmp if count == 1: self._generators[name] = result[0] else: self._generators[name] = result def parse_struct_defn(self, struct): name = struct[0][1] block = struct[1:] self._user_types[name] = block def parse_struct(self, struct): if struct[0][0] == "struct_defn": return self.parse_struct_defn(struct[0][1]) elif struct [0][0] == "struct_decl": return self.parse_struct_decl(struct[0][1]) else: raise Exception("Internal error: What is `%s'?" % struct[0][0]) def parse_directive(self, directive): name = directive[0][0] if name == "seekto": offset = directive[0][1][0][1] if offset.startswith("0x"): offset = int(offset[2:], 16) else: offset = int(offset) #print "NOTICE: Setting offset to %i (0x%X)" % (offset, offset) self._offset = offset elif name == "seek": offset = int(directive[0][1][0][1]) self._offset += offset elif name == "printoffset": string = directive[0][1][0][1] print "%s: %i (0x%08X)" % (string[1:-1], self._offset, self._offset) def parse_block(self, lang): for t, d in lang: #print t if t == "struct": self.parse_struct(d) elif t == "definition": self.parse_defn(d) elif t == "directive": self.parse_directive(d) def parse(self, lang): self._generators = structDataElement(self._data, self._offset) self.parse_block(lang[0]) return self._generators def parse(spec, data, offset=0): ast = bitwise_grammar.parse(spec) p = Processor(data, offset) return p.parse(ast) if __name__ == "__main__": defn = """ struct mytype { u8 foo; }; struct mytype bar; struct { u8 foo; u8 highbit:1, sixzeros:6, lowbit:1; char string[3]; bbcd fourdigits[2]; } mystruct; """ data = "\xab\x7F\x81abc\x12\x34" tree = parse(defn, data) print repr(tree) print "Foo %i" % tree.mystruct.foo print "Highbit: %i SixZeros: %i: Lowbit: %i" % (tree.mystruct.highbit, tree.mystruct.sixzeros, tree.mystruct.lowbit) print "String: %s" % tree.mystruct.string print "Fourdigits: %i" % tree.mystruct.fourdigits import sys sys.exit(0) test = """ struct { u16 bar; u16 baz; u8 one; u8 upper:2, twobit:1, onebit:1, lower:4; u8 array[3]; char str[3]; bbcd bcdL[2]; } foo[2]; u8 tail; """ data = "\xfe\x10\x00\x08\xFF\x23\x01\x02\x03abc\x34\x89" data = (data * 2) + "\x12" data = MemoryMap(data) ast = bitwise_grammar.parse(test) # Just for testing, pretty-print the tree pp(ast) # Mess with it a little p = Processor(data, 0) obj = p.parse(ast) print "Object: %s" % obj print obj["foo"][0]["bcdL"] print obj["tail"] print obj["foo"][0]["bar"] obj["foo"][0]["bar"].set_value(255 << 8) obj["foo"][0]["twobit"].set_value(0) obj["foo"][0]["onebit"].set_value(1) print "%i" % int(obj["foo"][0]["bar"]) for i in obj["foo"][0]["array"]: print int(i) obj["foo"][0]["array"][1].set_value(255) for i in obj["foo"][0]["bcdL"]: print i.get_value() int_to_bcd(obj["foo"][0]["bcdL"], 1234) print bcd_to_int(obj["foo"][0]["bcdL"]) set_string(obj["foo"][0]["str"], "xyz") print get_string(obj["foo"][0]["str"]) print repr(data.get_packed()) chirp-0.3.1/chirp/icf.py0000644000016101777760000004611612130403635016242 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import struct import re import time from chirp import chirp_common, errors, util, memmap from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean CMD_CLONE_OUT = 0xE2 CMD_CLONE_IN = 0xE3 CMD_CLONE_DAT = 0xE4 CMD_CLONE_END = 0xE5 SAVE_PIPE = None class IcfFrame: """A single ICF communication frame""" src = 0 dst = 0 cmd = 0 payload = "" def __str__(self): addrs = { 0xEE : "PC", 0xEF : "Radio"} cmds = {0xE0 : "ID", 0xE1 : "Model", 0xE2 : "Clone out", 0xE3 : "Clone in", 0xE4 : "Clone data", 0xE5 : "Clone end", 0xE6 : "Clone result"} return "%s -> %s [%s]:\n%s" % (addrs[self.src], addrs[self.dst], cmds[self.cmd], util.hexprint(self.payload)) def __init__(self): pass def parse_frame_generic(data): """Parse an ICF frame of unknown type from the beginning of @data""" frame = IcfFrame() frame.src = ord(data[2]) frame.dst = ord(data[3]) frame.cmd = ord(data[4]) try: end = data.index("\xFD") except ValueError: return None, data frame.payload = data[5:end] return frame, data[end+1:] class RadioStream: """A class to make reading a stream of IcfFrames easier""" def __init__(self, pipe): self.pipe = pipe self.data = "" def _process_frames(self): if not self.data.startswith("\xFE\xFE"): raise errors.InvalidDataError("Out of sync with radio") elif len(self.data) < 5: return [] # Not enough data for a full frame frames = [] while self.data: try: cmd = ord(self.data[4]) except IndexError: break # Out of data try: frame, rest = parse_frame_generic(self.data) if not frame: break elif frame.src == 0xEE and frame.dst == 0xEF: # PC echo, ignore pass else: frames.append(frame) self.data = rest except errors.InvalidDataError, e: print "Failed to parse frame (cmd=%i): %s" % (cmd, e) return [] return frames def get_frames(self, nolimit=False): """Read any pending frames from the stream""" while True: _data = self.pipe.read(64) if not _data: break else: self.data += _data if not nolimit and len(self.data) > 128 and "\xFD" in self.data: break # Give us a chance to do some status if len(self.data) > 1024: break # Avoid an endless loop of chewing garbage if not self.data: return [] return self._process_frames() def get_model_data(pipe, mdata="\x00\x00\x00\x00"): """Query the radio connected to @pipe for its model data""" send_clone_frame(pipe, 0xe0, mdata, raw=True) stream = RadioStream(pipe) frames = stream.get_frames() if len(frames) != 1: raise errors.RadioError("Unexpected response from radio") return frames[0].payload def get_clone_resp(pipe, length=None): """Read the response to a clone frame""" def exit_criteria(buf, length): """Stop reading a clone response if we have enough data or encounter the end of a frame""" if length is None: return buf.endswith("\xfd") else: return len(buf) == length resp = "" while not exit_criteria(resp, length): resp += pipe.read(1) return resp def send_clone_frame(pipe, cmd, data, raw=False, checksum=False): """Send a clone frame with @cmd and @data to the radio attached to @pipe""" cs = 0 if raw: hed = data else: hed = "" for byte in data: val = ord(byte) hed += "%02X" % val cs += val if checksum: cs = ((cs ^ 0xFFFF) + 1) & 0xFF cs = "%02X" % cs else: cs = "" frame = "\xfe\xfe\xee\xef%s%s%s\xfd" % (chr(cmd), hed, cs) if SAVE_PIPE: print "Saving data..." SAVE_PIPE.write(frame) #print "Sending:\n%s" % util.hexprint(frame) #print "Sending:\n%s" % util.hexprint(hed[6:]) if cmd == 0xe4: # Uncomment to avoid cloning to the radio # return frame pass pipe.write(frame) return frame def process_bcd(bcddata): """Convert BCD-encoded data to raw""" data = "" i = 0 while i < range(len(bcddata)) and i+1 < len(bcddata): try: val = int("%s%s" % (bcddata[i], bcddata[i+1]), 16) i += 2 data += struct.pack("B", val) except ValueError, e: print "Failed to parse byte: %s" % e break return data def process_data_frame(frame, _mmap): """Process a data frame, adding the payload to @_mmap""" _data = process_bcd(frame.payload) if len(_mmap) >= 0x10000: saddr, = struct.unpack(">I", _data[0:4]) length, = struct.unpack("B", _data[4]) data = _data[5:5+length] else: saddr, = struct.unpack(">H", _data[0:2]) length, = struct.unpack("B", _data[2]) data = _data[3:3+length] try: _mmap[saddr] = data except IndexError: print "Error trying to set %i bytes at %05x (max %05x)" % \ (bytes, saddr, len(_mmap)) return saddr, saddr + length def start_hispeed_clone(radio, cmd): """Send the magic incantation to the radio to go fast""" buf = ("\xFE" * 20) + \ "\xEE\xEF\xE8" + \ radio.get_model() + \ "\x00\x00\x02\x01\xFD" print "Starting HiSpeed:\n%s" % util.hexprint(buf) radio.pipe.write(buf) radio.pipe.flush() resp = radio.pipe.read(128) print "Response:\n%s" % util.hexprint(resp) print "Switching to 38400 baud" radio.pipe.setBaudrate(38400) buf = ("\xFE" * 14) + \ "\xEE\xEF" + \ chr(cmd) + \ radio.get_model()[:3] + \ "\x00\xFD" print "Starting HiSpeed Clone:\n%s" % util.hexprint(buf) radio.pipe.write(buf) radio.pipe.flush() def _clone_from_radio(radio): md = get_model_data(radio.pipe) if md[0:4] != radio.get_model(): print "This model: %s" % util.hexprint(md[0:4]) print "Supp model: %s" % util.hexprint(radio.get_model()) raise errors.RadioError("I can't talk to this model") if radio.is_hispeed(): start_hispeed_clone(radio, CMD_CLONE_OUT) else: send_clone_frame(radio.pipe, CMD_CLONE_OUT, radio.get_model(), raw=True) print "Sent clone frame" stream = RadioStream(radio.pipe) addr = 0 _mmap = memmap.MemoryMap(chr(0x00) * radio.get_memsize()) last_size = 0 while True: frames = stream.get_frames() if not frames: break for frame in frames: if frame.cmd == CMD_CLONE_DAT: src, dst = process_data_frame(frame, _mmap) if last_size != (dst - src): print "ICF Size change from %i to %i at %04x" % (last_size, dst - src, src) last_size = dst - src if addr != src: print "ICF GAP %04x - %04x" % (addr, src) addr = dst elif frame.cmd == CMD_CLONE_END: print "End frame (%i):\n%s" % (len(frame.payload), util.hexprint(frame.payload)) print "Last addr: %04x" % addr if radio.status_fn: status = chirp_common.Status() status.msg = "Cloning from radio" status.max = radio.get_memsize() status.cur = addr radio.status_fn(status) return _mmap def clone_from_radio(radio): """Do a full clone out of the radio's memory""" try: return _clone_from_radio(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) def send_mem_chunk(radio, start, stop, bs=32): """Send a single chunk of the radio's memory from @start-@stop""" _mmap = radio.get_mmap() status = chirp_common.Status() status.msg = "Cloning to radio" status.max = radio.get_memsize() for i in range(start, stop, bs): if i + bs < stop: size = bs else: size = stop - i if radio.get_memsize() >= 0x10000: chunk = struct.pack(">IB", i, size) else: chunk = struct.pack(">HB", i, size) chunk += _mmap[i:i+size] send_clone_frame(radio.pipe, CMD_CLONE_DAT, chunk, checksum=True) if radio.status_fn: status.cur = i+bs radio.status_fn(status) return True def _clone_to_radio(radio): global SAVE_PIPE # Uncomment to save out a capture of what we actually write to the radio # SAVE_PIPE = file("pipe_capture.log", "w", 0) md = get_model_data(radio.pipe) if md[0:4] != radio.get_model(): raise errors.RadioError("I can't talk to this model") # This mimics what the Icom software does, but isn't required and just # takes longer # md = get_model_data(radio.pipe, model=md[0:2]+"\x00\x00") # md = get_model_data(radio.pipe, model=md[0:2]+"\x00\x00") stream = RadioStream(radio.pipe) if radio.is_hispeed(): start_hispeed_clone(radio, CMD_CLONE_IN) else: send_clone_frame(radio.pipe, CMD_CLONE_IN, radio.get_model(), raw=True) frames = [] for start, stop, bs in radio.get_ranges(): if not send_mem_chunk(radio, start, stop, bs): break frames += stream.get_frames() send_clone_frame(radio.pipe, CMD_CLONE_END, radio.get_endframe(), raw=True) if SAVE_PIPE: SAVE_PIPE.close() SAVE_PIPE = None for i in range(0, 10): try: frames += stream.get_frames(True) result = frames[-1] except IndexError: print "Waiting for clone result..." time.sleep(0.5) if len(frames) == 0: raise errors.RadioError("Did not get clone result from radio") return result.payload[0] == '\x00' def clone_to_radio(radio): """Initiate a full memory clone out to @radio""" try: return _clone_to_radio(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) def convert_model(mod_str): """Convert an ICF-style model string into what we get from the radio""" data = "" for i in range(0, len(mod_str), 2): hexval = mod_str[i:i+2] intval = int(hexval, 16) data += chr(intval) return data def convert_data_line(line): """Convert an ICF data line to raw memory format""" if line.startswith("#"): return "" line = line.strip() if len(line) == 38: # Small memory (< 0x10000) size = int(line[4:6], 16) data = line[6:] else: # Large memory (>= 0x10000) size = int(line[8:10], 16) data = line[10:] _mmap = "" i = 0 while i < (size * 2): try: val = int("%s%s" % (data[i], data[i+1]), 16) i += 2 _mmap += struct.pack("B", val) except ValueError, e: print "Failed to parse byte: %s" % e break return _mmap def read_file(filename): """Read an ICF file and return the model string and memory data""" f = file(filename) mod_str = f.readline() dat = f.readlines() model = convert_model(mod_str.strip()) _mmap = "" for line in dat: if not line.startswith("#"): _mmap += convert_data_line(line) return model, memmap.MemoryMap(_mmap) def is_9x_icf(filename): """Returns True if @filename is an IC9x ICF file""" f = file(filename) mdata = f.read(8) f.close() return mdata in ["30660000", "28880000"] def is_icf_file(filename): """Returns True if @filename is an ICF file""" f = file(filename) data = f.readline() data += f.readline() f.close() data = data.replace("\n", "").replace("\r", "") return bool(re.match("^[0-9]{8}#", data)) class IcomBank(chirp_common.Bank): """A bank that works for all Icom radios""" # Integral index of the bank (not to be confused with per-memory # bank indexes index = 0 class IcomNamedBank(IcomBank): """A bank with an adjustable name""" def set_name(self, name): """Set the name of the bank""" pass class IcomBankModel(chirp_common.BankModel): """Icom radios all have pretty much the same simple bank model. This central implementation can, with a few icom-specific radio interfaces serve most/all of them""" def get_num_banks(self): return self._radio._num_banks def get_banks(self): banks = [] for i in range(0, self._radio._num_banks): index = chr(ord("A") + i) bank = self._radio._bank_class(self, index, "BANK-%s" % index) bank.index = i banks.append(bank) return banks def add_memory_to_bank(self, memory, bank): self._radio._set_bank(memory.number, bank.index) def remove_memory_from_bank(self, memory, bank): if self._radio._get_bank(memory.number) != bank.index: raise Exception("Memory %i not in bank %s. Cannot remove." % \ (memory.number, bank)) self._radio._set_bank(memory.number, None) def get_bank_memories(self, bank): memories = [] for i in range(*self._radio.get_features().memory_bounds): if self._radio._get_bank(i) == bank.index: memories.append(self._radio.get_memory(i)) return memories def get_memory_banks(self, memory): index = self._radio._get_bank(memory.number) if index is None: return [] else: return [self.get_banks()[index]] class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface): """Generic bank model for Icom radios with indexed banks""" def get_index_bounds(self): return self._radio._bank_index_bounds def get_memory_index(self, memory, bank): return self._radio._get_bank_index(memory.number) def set_memory_index(self, memory, bank, index): if bank not in self.get_memory_banks(memory): raise Exception("Memory %i is not in bank %s" % (memory.number, bank)) if index not in range(*self._radio._bank_index_bounds): raise Exception("Invalid index") self._radio._set_bank_index(memory.number, index) def get_next_bank_index(self, bank): indexes = [] for i in range(*self._radio.get_features().memory_bounds): if self._radio._get_bank(i) == bank.index: indexes.append(self._radio._get_bank_index(i)) for i in range(0, 256): if i not in indexes: return i raise errors.RadioError("Out of slots in this bank") class IcomCloneModeRadio(chirp_common.CloneModeRadio): """Base class for Icom clone-mode radios""" VENDOR = "Icom" BAUDRATE = 9600 _model = "\x00\x00\x00\x00" # 4-byte model string _endframe = "" # Model-unique ending frame _ranges = [] # Ranges of the mmap to send to the radio _num_banks = 10 # Most simple Icoms have 10 banks, A-J _bank_index_bounds = (0, 99) _bank_class = IcomBank _can_hispeed = False @classmethod def is_hispeed(cls): """Returns True if the radio supports hispeed cloning""" return cls._can_hispeed @classmethod def get_model(cls): """Returns the Icom model data for this radio""" return cls._model @classmethod def get_endframe(cls): """Returns the magic clone end frame for this radio""" return cls._endframe @classmethod def get_ranges(cls): """Returns the ranges this radio likes to have in a clone""" return cls._ranges def sync_in(self): self._mmap = clone_from_radio(self) self.process_mmap() def sync_out(self): clone_to_radio(self) def get_bank_model(self): rf = self.get_features() if rf.has_bank: if rf.has_bank_index: return IcomIndexedBankModel(self) else: return IcomBankModel(self) else: return None # Icom-specific bank routines def _get_bank(self, loc): """Get the integral bank index of memory @loc, or None""" raise Exception("Not implemented") def _set_bank(self, loc, index): """Set the integral bank index of memory @loc to @index, or no bank if None""" raise Exception("Not implemented") def get_settings(self): return make_speed_switch_setting(self) def set_settings(self, settings): return honor_speed_switch_setting(self, settings) class IcomLiveRadio(chirp_common.LiveRadio): """Base class for an Icom Live-mode radio""" VENDOR = "Icom" BAUD_RATE = 38400 _num_banks = 26 # Most live Icoms have 26 banks, A-Z _bank_index_bounds = (0, 99) _bank_class = IcomBank def get_bank_model(self): rf = self.get_features() if rf.has_bank: if rf.has_bank_index: return IcomIndexedBankModel(self) else: return IcomBankModel(self) else: return None def make_speed_switch_setting(radio): if not radio.__class__._can_hispeed: return [] drvopts = RadioSettingGroup("drvopts", "Driver Options") rs = RadioSetting("drv_clone_speed", "Use Hi-Speed Clone", RadioSettingValueBoolean(radio._can_hispeed)) drvopts.append(rs) return drvopts def honor_speed_switch_setting(radio, settings): for element in settings: if element.get_name() == "drvopts": return honor_speed_switch_setting(radio, settings) if element.get_name() == "drv_clone_speed": radio.__class__._can_hispeed = element.value.get_value() return chirp-0.3.1/chirp/alinco.py0000644000016101777760000003572312105130272016744 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . from chirp import chirp_common, bitwise, memmap, errors, directory, util from chirp.settings import RadioSettingGroup, RadioSetting from chirp.settings import RadioSettingValueBoolean import time DRX35_MEM_FORMAT = """ #seekto 0x0120; u8 used_flags[25]; #seekto 0x0200; struct { u8 new_used:1, unknown1:1, isnarrow:1, isdigital:1, ishigh:1, unknown2:3; u8 unknown3:6, duplex:2; u8 unknown4:4, tmode:4; u8 unknown5:4, step:4; bbcd freq[4]; u8 unknown6[1]; bbcd offset[3]; u8 rtone; u8 ctone; u8 dtcs_tx; u8 dtcs_rx; u8 name[7]; u8 unknown8[2]; u8 unknown9:6, power:2; u8 unknownA[6]; } memory[100]; #seekto 0x0130; u8 skips[25]; """ # 0000 0111 # 0000 0010 # Response length is: # 1. \r\n # 2. Four-digit address, followed by a colon # 3. 16 bytes in hex (32 characters) # 4. \r\n RLENGTH = 2 + 5 + 32 + 2 STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0] def isascii(data): for byte in data: if (ord(byte) < ord(" ") or ord(byte) > ord("~")) and \ byte not in "\r\n": return False return True def tohex(data): if isascii(data): return repr(data) string = "" for byte in data: string += "%02X" % ord(byte) return string class AlincoStyleRadio(chirp_common.CloneModeRadio): """Base class for all known Alinco radios""" _memsize = 0 _model = "NONE" def _send(self, data): print "PC->R: (%2i) %s" % (len(data), tohex(data)) self.pipe.write(data) self.pipe.read(len(data)) def _read(self, length): data = self.pipe.read(length) print "R->PC: (%2i) %s" % (len(data), tohex(data)) return data def _download_chunk(self, addr): if addr % 16: raise Exception("Addr 0x%04x not on 16-byte boundary" % addr) cmd = "AL~F%04XR\r\n" % addr self._send(cmd) resp = self._read(RLENGTH).strip() if len(resp) == 0: raise errors.RadioError("No response from radio") if ":" not in resp: raise errors.RadioError("Unexpected response from radio") addr, _data = resp.split(":", 1) data = "" for i in range(0, len(_data), 2): data += chr(int(_data[i:i+2], 16)) if len(data) != 16: print "Response was:" print "|%s|" print "Which I converted to:" print util.hexprint(data) raise Exception("Radio returned less than 16 bytes") return data def _download(self, limit): self._identify() data = "" for addr in range(0, limit, 16): data += self._download_chunk(addr) time.sleep(0.1) if self.status_fn: status = chirp_common.Status() status.cur = addr + 16 status.max = self._memsize status.msg = "Downloading from radio" self.status_fn(status) self._send("AL~E\r\n") self._read(20) return memmap.MemoryMap(data) def _identify(self): for _i in range(0, 3): self._send("%s\r\n" % self._model) resp = self._read(6) if resp.strip() == "OK": return True time.sleep(1) return False def _upload_chunk(self, addr): if addr % 16: raise Exception("Addr 0x%04x not on 16-byte boundary" % addr) _data = self._mmap[addr:addr+16] data = "".join(["%02X" % ord(x) for x in _data]) cmd = "AL~F%04XW%s\r\n" % (addr, data) self._send(cmd) def _upload(self, limit): if not self._identify(): raise Exception("I can't talk to this model") for addr in range(0x100, limit, 16): self._upload_chunk(addr) time.sleep(0.1) if self.status_fn: status = chirp_common.Status() status.cur = addr + 16 status.max = self._memsize status.msg = "Uploading to radio" self.status_fn(status) self._send("AL~E\r\n") self.pipe._read(20) def process_mmap(self): self._memobj = bitwise.parse(DRX35_MEM_FORMAT, self._mmap) def sync_in(self): try: self._mmap = self._download(self._memsize) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() def sync_out(self): try: self._upload(self._memsize) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) DUPLEX = ["", "-", "+"] TMODES = ["", "Tone", "", "TSQL"] + [""] * 12 TMODES[12] = "DTCS" DCS_CODES = { "Alinco" : chirp_common.DTCS_CODES, "Jetstream" : [17] + chirp_common.DTCS_CODES, } CHARSET = (["\x00"] * 0x30) + \ [chr(x + ord("0")) for x in range(0, 10)] + \ [chr(x + ord("A")) for x in range(0, 26)] + [" "] + \ list("\x00" * 128) def _get_name(_mem): name = "" for i in _mem.name: if i in [0x00, 0xFF]: break name += CHARSET[i] return name def _set_name(mem, _mem): name = [0x00] * 7 j = 0 for i in range(0, 7): try: name[j] = CHARSET.index(mem.name[i]) j += 1 except IndexError: pass except ValueError: pass return name ALINCO_TONES = list(chirp_common.TONES) ALINCO_TONES.remove(159.8) ALINCO_TONES.remove(165.5) ALINCO_TONES.remove(171.3) ALINCO_TONES.remove(177.3) ALINCO_TONES.remove(183.5) ALINCO_TONES.remove(189.9) ALINCO_TONES.remove(196.6) ALINCO_TONES.remove(199.5) ALINCO_TONES.remove(206.5) ALINCO_TONES.remove(229.1) ALINCO_TONES.remove(254.1) class DRx35Radio(AlincoStyleRadio): """Base class for the DR-x35 radios""" _range = [(118000000, 155000000)] _power_levels = [] _valid_tones = ALINCO_TONES def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = ["FM", "NFM"] rf.valid_skips = ["", "S"] rf.valid_bands = self._range rf.memory_bounds = (0, 99) rf.has_ctone = True rf.has_bank = False rf.has_dtcs_polarity = False rf.valid_tuning_steps = STEPS rf.valid_name_length = 7 rf.valid_power_levels = self._power_levels return rf def _get_used(self, number): _usd = self._memobj.used_flags[number / 8] bit = (0x80 >> (number % 8)) return _usd & bit def _set_used(self, number, is_used): _usd = self._memobj.used_flags[number / 8] bit = (0x80 >> (number % 8)) if is_used: _usd |= bit else: _usd &= ~bit def _get_power(self, _mem): if self._power_levels: return self._power_levels[_mem.ishigh] return None def _set_power(self, _mem, mem): if self._power_levels: _mem.ishigh = mem.power is None or \ mem.power == self._power_levels[1] def _get_extra(self, _mem, mem): mem.extra = RadioSettingGroup("extra", "Extra") dig = RadioSetting("isdigital", "Digital", RadioSettingValueBoolean(bool(_mem.isdigital))) dig.set_doc("Digital/Packet mode enabled") mem.extra.append(dig) def _set_extra(self, _mem, mem): for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_memory(self, number): _mem = self._memobj.memory[number] _skp = self._memobj.skips[number / 8] _usd = self._memobj.used_flags[number / 8] bit = (0x80 >> (number % 8)) mem = chirp_common.Memory() mem.number = number if not self._get_used(number) and self.MODEL != "JT220M": mem.empty = True return mem mem.freq = int(_mem.freq) * 100 mem.rtone = self._valid_tones[_mem.rtone] mem.ctone = self._valid_tones[_mem.ctone] mem.duplex = DUPLEX[_mem.duplex] mem.offset = int(_mem.offset) * 100 mem.tmode = TMODES[_mem.tmode] mem.dtcs = DCS_CODES[self.VENDOR][_mem.dtcs_tx] mem.tuning_step = STEPS[_mem.step] if _mem.isnarrow: mem.mode = "NFM" mem.power = self._get_power(_mem) if _skp & bit: mem.skip = "S" mem.name = _get_name(_mem).rstrip() self._get_extra(_mem, mem) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _skp = self._memobj.skips[mem.number / 8] _usd = self._memobj.used_flags[mem.number / 8] bit = (0x80 >> (mem.number % 8)) if self._get_used(mem.number) and not mem.empty: # Initialize the memory _mem.set_raw("\x00" * 32) self._set_used(mem.number, not mem.empty) if mem.empty: return _mem.freq = mem.freq / 100 try: _tone = mem.rtone _mem.rtone = self._valid_tones.index(mem.rtone) _tone = mem.ctone _mem.ctone = self._valid_tones.index(mem.ctone) except ValueError: raise errors.UnsupportedToneError("This radio does not support " + "tone %.1fHz" % _tone) _mem.duplex = DUPLEX.index(mem.duplex) _mem.offset = mem.offset / 100 _mem.tmode = TMODES.index(mem.tmode) _mem.dtcs_tx = DCS_CODES[self.VENDOR].index(mem.dtcs) _mem.dtcs_rx = DCS_CODES[self.VENDOR].index(mem.dtcs) _mem.step = STEPS.index(mem.tuning_step) _mem.isnarrow = mem.mode == "NFM" self._set_power(_mem, mem) if mem.skip: _skp |= bit else: _skp &= ~bit _mem.name = _set_name(mem, _mem) self._set_extra(_mem, mem) @directory.register class DR03Radio(DRx35Radio): """Alinco DR03""" VENDOR = "Alinco" MODEL = "DR03T" _model = "DR135" _memsize = 4096 _range = [(28000000, 29695000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x28) @directory.register class DR06Radio(DRx35Radio): """Alinco DR06""" VENDOR = "Alinco" MODEL = "DR06T" _model = "DR435" _memsize = 4096 _range = [(50000000, 53995000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x50) @directory.register class DR135Radio(DRx35Radio): """Alinco DR135""" VENDOR = "Alinco" MODEL = "DR135T" _model = "DR135" _memsize = 4096 _range = [(118000000, 173000000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x01) and filedata[0x65] == chr(0x44) @directory.register class DR235Radio(DRx35Radio): """Alinco DR235""" VENDOR = "Alinco" MODEL = "DR235T" _model = "DR235" _memsize = 4096 _range = [(216000000, 280000000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x02) and filedata[0x65] == chr(0x22) @directory.register class DR435Radio(DRx35Radio): """Alinco DR435""" VENDOR = "Alinco" MODEL = "DR435T" _model = "DR435" _memsize = 4096 _range = [(350000000, 511000000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x04) and filedata[0x65] == chr(0x00) @directory.register class DJ596Radio(DRx35Radio): """Alinco DJ596""" VENDOR = "Alinco" MODEL = "DJ596" _model = "DJ596" _memsize = 4096 _range = [(136000000, 174000000), (400000000, 511000000)] _power_levels = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("High", watts=5.00)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x64] == chr(0x45) and filedata[0x65] == chr(0x01) @directory.register class JT220MRadio(DRx35Radio): """Jetstream JT220""" VENDOR = "Jetstream" MODEL = "JT220M" _model = "DR136" _memsize = 8192 _range = [(216000000, 280000000)] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0x60:0x64] == "2009" @directory.register class DJ175Radio(DRx35Radio): """Alinco DJ175""" VENDOR = "Alinco" MODEL = "DJ175" _model = "DJ175" _memsize = 6896 _range = [(136000000, 174000000), (400000000, 511000000)] _power_levels = [ chirp_common.PowerLevel("Low", watts=0.50), chirp_common.PowerLevel("Mid", watts=2.00), chirp_common.PowerLevel("High", watts=5.00), ] @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def _get_used(self, number): return self._memobj.memory[number].new_used def _set_used(self, number, is_used): self._memobj.memory[number].new_used = is_used def _get_power(self, _mem): return self._power_levels[_mem.power] def _set_power(self, _mem, mem): if mem.power in self._power_levels: _mem.power = self._power_levels.index(mem.power) def _download_chunk(self, addr): if addr % 16: raise Exception("Addr 0x%04x not on 16-byte boundary" % addr) cmd = "AL~F%04XR\r\n" % addr self._send(cmd) _data = self._read(34).strip() if len(_data) == 0: raise errors.RadioError("No response from radio") data = "" for i in range(0, len(_data), 2): data += chr(int(_data[i:i+2], 16)) if len(data) != 16: print "Response was:" print "|%s|" print "Which I converted to:" print util.hexprint(data) raise Exception("Radio returned less than 16 bytes") return data chirp-0.3.1/chirp/kenwood_live.py0000644000016101777760000011032612072751071020166 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . import threading import os import sys import time NOCACHE = os.environ.has_key("CHIRP_NOCACHE") if __name__ == "__main__": import sys sys.path.insert(0, "..") from chirp import chirp_common, errors, directory, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueBoolean, \ RadioSettingValueString, RadioSettingValueList DEBUG = True DUPLEX = { 0 : "", 1 : "+", 2 : "-" } MODES = { 0 : "FM", 1 : "AM" } STEPS = list(chirp_common.TUNING_STEPS) STEPS.append(100.0) THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"] LOCK = threading.Lock() def command(ser, cmd, *args): """Send @cmd to radio via @ser""" global LOCK start = time.time() LOCK.acquire() if args: cmd += " " + " ".join(args) if DEBUG: print "PC->RADIO: %s" % cmd ser.write(cmd + "\r") result = "" while not result.endswith("\r"): result += ser.read(8) if (time.time() - start) > 0.5: print "Timeout waiting for data" break if DEBUG: print "D7->PC: %s" % result.strip() LOCK.release() return result.strip() LAST_BAUD = 9600 def get_id(ser): """Get the ID of the radio attached to @ser""" global LAST_BAUD bauds = [9600, 19200, 38400, 57600] bauds.remove(LAST_BAUD) bauds.insert(0, LAST_BAUD) for i in bauds: print "Trying ID at baud %i" % i ser.setBaudrate(i) ser.write("\r") ser.read(25) resp = command(ser, "ID") if " " in resp: LAST_BAUD = i return resp.split(" ")[1] raise errors.RadioError("No response from radio") def get_tmode(tone, ctcss, dcs): """Get the tone mode based on the values of the tone, ctcss, dcs""" if dcs and int(dcs) == 1: return "DTCS" elif int(ctcss): return "TSQL" elif int(tone): return "Tone" else: return "" def iserr(result): """Returns True if the @result from a radio is an error""" return result in ["N", "?"] class KenwoodLiveRadio(chirp_common.LiveRadio): """Base class for all live-mode kenwood radios""" BAUD_RATE = 9600 VENDOR = "Kenwood" MODEL = "" _vfo = 0 _upper = 200 _kenwood_split = False _kenwood_valid_tones = list(chirp_common.TONES) def __init__(self, *args, **kwargs): chirp_common.LiveRadio.__init__(self, *args, **kwargs) self.__memcache = {} if self.pipe: self.pipe.setTimeout(0.1) radio_id = get_id(self.pipe) if radio_id != self.MODEL.split(" ")[0]: raise Exception("Radio reports %s (not %s)" % (radio_id, self.MODEL)) command(self.pipe, "AI", "0") def _cmd_get_memory(self, number): return "MR", "%i,0,%03i" % (self._vfo, number) def _cmd_get_memory_name(self, number): return "MNA", "%i,%03i" % (self._vfo, number) def _cmd_get_split(self, number): return "MR", "%i,1,%03i" % (self._vfo, number) def _cmd_set_memory(self, number, spec): if spec: spec = "," + spec return "MW", "%i,0,%03i%s" % (self._vfo, number, spec) def _cmd_set_memory_name(self, number, name): return "MNA", "%i,%03i,%s" % (self._vfo, number, name) def _cmd_set_split(self, number, spec): return "MW", "%i,1,%03i,%s" % (self._vfo, number, spec) def get_raw_memory(self, number): return command(self.pipe, *self._cmd_get_memory(number)) def get_memory(self, number): if number < 0 or number > self._upper: raise errors.InvalidMemoryLocation( \ "Number must be between 0 and %i" % self._upper) if self.__memcache.has_key(number) and not NOCACHE: return self.__memcache[number] result = command(self.pipe, *self._cmd_get_memory(number)) if result == "N": mem = chirp_common.Memory() mem.number = number mem.empty = True self.__memcache[mem.number] = mem return mem elif " " not in result: print "Not sure what to do with this: `%s'" % result raise errors.RadioError("Unexpected result returned from radio") value = result.split(" ")[1] spec = value.split(",") mem = self._parse_mem_spec(spec) self.__memcache[mem.number] = mem result = command(self.pipe, *self._cmd_get_memory_name(number)) if " " in result: value = result.split(" ", 1)[1] if value.count(",") == 2: _zero, _loc, mem.name = value.split(",") else: _loc, mem.name = value.split(",") if mem.duplex == "" and self._kenwood_split: result = command(self.pipe, *self._cmd_get_split(number)) if " " in result: value = result.split(" ", 1)[1] self._parse_split_spec(mem, value.split(",")) return mem def _make_mem_spec(self, mem): pass def _parse_mem_spec(self, spec): pass def _parse_split_spec(self, mem, spec): mem.duplex = "split" mem.offset = int(spec[2]) def _make_split_spec(self, mem): return ("%011i" % mem.offset, "0") def set_memory(self, memory): if memory.number < 0 or memory.number > self._upper: raise errors.InvalidMemoryLocation( \ "Number must be between 0 and %i" % self._upper) spec = self._make_mem_spec(memory) spec = ",".join(spec) r1 = command(self.pipe, *self._cmd_set_memory(memory.number, spec)) if not iserr(r1): time.sleep(0.5) r2 = command(self.pipe, *self._cmd_set_memory_name(memory.number, memory.name)) if not iserr(r2): memory.name = memory.name.rstrip() self.__memcache[memory.number] = memory else: raise errors.InvalidDataError("Radio refused name %i: %s" %\ (memory.number, repr(memory.name))) else: raise errors.InvalidDataError("Radio refused %i" % memory.number) if memory.duplex == "split" and self._kenwood_split: spec = ",".join(self._make_split_spec(memory)) result = command(self.pipe, *self._cmd_set_split(memory.number, spec)) if iserr(result): raise errors.InvalidDataError("Radio refused %i" % \ memory.number) def erase_memory(self, number): if not self.__memcache.has_key(number): return resp = command(self.pipe, *self._cmd_set_memory(number, "")) if iserr(resp): raise errors.RadioError("Radio refused delete of %i" % number) del self.__memcache[number] TH_D7_SETTINGS = { "BAL" : ["4:0", "3:1", "2:2", "1:3", "0:4"], "BEP" : ["Off", "Key", "Key+Data", "All"], "BEPT" : ["Off", "Mine", "All New"], # D700 has fourth "All" "DS" : ["Data Band", "Both Bands"], "DTB" : ["A", "B"], "DTBA" : ["A", "B", "A:TX/B:RX"], # D700 has fourth A:RX/B:TX "DTX" : ["Manual", "PTT", "Auto"], "ICO" : ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV", "Plane", "Speedboat", "Car", "Bicycle"], "MNF" : ["Name", "Frequency"], "PKSA" : ["1200", "9600"], "POSC" : ["Off Duty", "Enroute", "In Service", "Returning", "Committed", "Special", "Priority", "Emergency"], "PT" : ["100ms", "200ms", "500ms", "750ms", "1000ms", "1500ms", "2000ms"], "SCR" : ["Time", "Carrier", "Seek"], "SV" : ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s", "2s", "3s", "4s", "5s"], "TEMP" : ["F", "C"], "TXI" : ["30sec", "1min", "2min", "3min", "4min", "5min", "10min", "20min", "30min"], "UNIT" : ["English", "Metric"], "WAY" : ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA", "9 digit NMEA", "6 digit Magellan", "DGPS"], } @directory.register class THD7Radio(KenwoodLiveRadio): """Kenwood TH-D7""" MODEL = "TH-D7" _kenwood_split = True def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_mode = True rf.has_tuning_step = False rf.can_odd_split = True rf.valid_duplexes = ["", "-", "+", "split"] rf.valid_modes = MODES.values() rf.valid_tmodes = ["", "Tone", "TSQL"] rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC rf.valid_name_length = 7 rf.memory_bounds = (1, self._upper) return rf def _make_mem_spec(self, mem): if mem.duplex in " -+": duplex = util.get_dict_rev(DUPLEX, mem.duplex) offset = mem.offset else: duplex = 0 offset = 0 spec = ( \ "%011i" % mem.freq, "%X" % STEPS.index(mem.tuning_step), "%i" % duplex, "0", "%i" % (mem.tmode == "Tone"), "%i" % (mem.tmode == "TSQL"), "", # DCS Flag "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1), "", # DCS Code "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1), "%09i" % offset, "%i" % util.get_dict_rev(MODES, mem.mode), "%i" % ((mem.skip == "S") and 1 or 0)) return spec def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[2]) mem.freq = int(spec[3], 10) mem.tuning_step = STEPS[int(spec[4], 16)] mem.duplex = DUPLEX[int(spec[5])] mem.tmode = get_tmode(spec[7], spec[8], spec[9]) mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1] mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1] if spec[11] and spec[11].isdigit(): mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1] else: print "Unknown or invalid DCS: %s" % spec[11] if spec[13]: mem.offset = int(spec[13]) else: mem.offset = 0 mem.mode = MODES[int(spec[14])] mem.skip = int(spec[15]) and "S" or "" return mem def _kenwood_get(self, cmd): resp = command(self.pipe, cmd) if " " in resp: return resp.split(" ", 1) else: raise errors.RadioError("Radio refused to return %s" % cmd) def _kenwood_set(self, cmd, value): resp = command(self.pipe, cmd, value) if " " in resp: return raise errors.RadioError("Radio refused to set %s" % cmd) def _kenwood_get_bool(self, cmd): _cmd, result = self._kenwood_get(cmd) return result == "1" def _kenwood_set_bool(self, cmd, value): return self._kenwood_set(cmd, str(int(value))) def _kenwood_get_int(self, cmd): _cmd, result = self._kenwood_get(cmd) return int(result) def _kenwood_set_int(self, cmd, value, digits=1): return self._kenwood_set(cmd, ("%%0%ii" % digits) % value) def get_settings(self): aux = RadioSettingGroup("aux", "Aux") tnc = RadioSettingGroup("tnc", "TNC") save = RadioSettingGroup("save", "Save") display = RadioSettingGroup("display", "Display") dtmf = RadioSettingGroup("dtmf", "DTMF") radio = RadioSettingGroup("radio", "Radio", aux, tnc, save, display, dtmf) sky = RadioSettingGroup("sky", "SkyCommand") aprs = RadioSettingGroup("aprs", "APRS") top = RadioSettingGroup("top", "All Settings", radio, aprs, sky) bools = [("AMR", aprs, "APRS Message Auto-Reply"), ("AIP", aux, "Advanced Intercept Point"), ("ARO", aux, "Automatic Repeater Offset"), ("BCN", aprs, "Beacon"), ("CH", radio, "Channel Mode Display"), #("DIG", aprs, "APRS Digipeater"), ("DL", all, "Dual"), ("LK", all, "Lock"), ("LMP", all, "Lamp"), ("TSP", dtmf, "DTMF Fast Transmission"), ("TXH", dtmf, "TX Hold"), ] for setting, group, name in bools: value = self._kenwood_get_bool(setting) rs = RadioSetting(setting, name, RadioSettingValueBoolean(value)) group.append(rs) lists = [("BAL", all, "Balance"), ("BEP", aux, "Beep"), ("BEPT", aprs, "APRS Beep"), ("DS", tnc, "Data Sense"), ("DTB", tnc, "Data Band"), ("DTBA", aprs, "APRS Data Band"), ("DTX", aprs, "APRS Data TX"), #("ICO", aprs, "APRS Icon"), ("MNF", all, "Memory Display Mode"), ("PKSA", aprs, "APRS Packet Speed"), ("POSC", aprs, "APRS Position Comment"), ("PT", dtmf, "DTMF Speed"), ("SV", save, "Battery Save"), ("TEMP", aprs, "APRS Temperature Units"), ("TXI", aprs, "APRS Transmit Interval"), #("UNIT", aprs, "APRS Display Units"), ("WAY", aprs, "Waypoint Mode"), ] for setting, group, name in lists: value = self._kenwood_get_int(setting) options = TH_D7_SETTINGS[setting] rs = RadioSetting(setting, name, RadioSettingValueList(options, options[value])) group.append(rs) ints = [("CNT", display, "Contrast", 1, 16), ] for setting, group, name, minv, maxv in ints: value = self._kenwood_get_int(setting) rs = RadioSetting(setting, name, RadioSettingValueInteger(minv, maxv, value)) group.append(rs) strings = [("MES", display, "Power-on Message", 8), ("MYC", aprs, "APRS Callsign", 8), ("PP", aprs, "APRS Path", 32), ("SCC", sky, "SkyCommand Callsign", 8), ("SCT", sky, "SkyCommand To Callsign", 8), #("STAT", aprs, "APRS Status Text", 32), ] for setting, group, name, length in strings: _cmd, value = self._kenwood_get(setting) rs = RadioSetting(setting, name, RadioSettingValueString(0, length, value)) group.append(rs) return top def set_settings(self, settings): for element in settings: if not element.changed(): continue if isinstance(element.value, RadioSettingValueBoolean): self._kenwood_set_bool(element.get_name(), element.value) elif isinstance(element.value, RadioSettingValueList): options = TH_D7_SETTINGS[element.get_name()] self._kenwood_set_int(element.get_name(), options.index(str(element.value))) elif isinstance(element.value, RadioSettingValueInteger): if element.value.get_max() > 9: digits = 2 else: digits = 1 self._kenwood_set_int(element.get_name(), element.value, digits) elif isinstance(element.value, RadioSettingValueString): self._kenwood_set(element.get_name(), str(element.value)) else: print "Unknown type %s" % element.value @directory.register class THD7GRadio(THD7Radio): """Kenwood TH-D7G""" MODEL = "TH-D7G" @directory.register class TMD700Radio(KenwoodLiveRadio): """Kenwood TH-D700""" MODEL = "TM-D700" _kenwood_split = True def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs = True rf.has_dtcs_polarity = False rf.has_bank = False rf.has_mode = False rf.has_tuning_step = False rf.can_odd_split = True rf.valid_duplexes = ["", "-", "+", "split"] rf.valid_modes = ["FM"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC rf.valid_name_length = 8 rf.memory_bounds = (1, self._upper) return rf def _make_mem_spec(self, mem): if mem.duplex in " -+": duplex = util.get_dict_rev(DUPLEX, mem.duplex) else: duplex = 0 spec = ( \ "%011i" % mem.freq, "%X" % STEPS.index(mem.tuning_step), "%i" % duplex, "0", "%i" % (mem.tmode == "Tone"), "%i" % (mem.tmode == "TSQL"), "%i" % (mem.tmode == "DTCS"), "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1), "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1), "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1), "%09i" % mem.offset, "%i" % util.get_dict_rev(MODES, mem.mode), "%i" % ((mem.skip == "S") and 1 or 0)) return spec def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[2]) mem.freq = int(spec[3]) mem.tuning_step = STEPS[int(spec[4], 16)] mem.duplex = DUPLEX[int(spec[5])] mem.tmode = get_tmode(spec[7], spec[8], spec[9]) mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1] mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1] if spec[11] and spec[11].isdigit(): mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1] else: print "Unknown or invalid DCS: %s" % spec[11] if spec[13]: mem.offset = int(spec[13]) else: mem.offset = 0 mem.mode = MODES[int(spec[14])] mem.skip = int(spec[15]) and "S" or "" return mem OLD_TONES = list(chirp_common.TONES) OLD_TONES.remove(159.8) OLD_TONES.remove(165.5) OLD_TONES.remove(171.3) OLD_TONES.remove(177.3) OLD_TONES.remove(183.5) OLD_TONES.remove(189.9) OLD_TONES.remove(196.6) OLD_TONES.remove(199.5) OLD_TONES.remove(206.5) OLD_TONES.remove(229.1) OLD_TONES.remove(254.1) @directory.register class TMV7Radio(KenwoodLiveRadio): """Kenwood TM-V7""" MODEL = "TM-V7" mem_upper_limit = 200 # Will be updated _kenwood_valid_tones = list(OLD_TONES) def set_memory(self, memory): supported_tones = list(OLD_TONES) supported_tones.remove(69.3) if memory.rtone not in supported_tones: raise errors.UnsupportedToneError("This radio does not support " + "tone %.1fHz" % memory.rtone) if memory.ctone not in supported_tones: raise errors.UnsupportedToneError("This radio does not support " + "tone %.1fHz" % memory.ctone) return KenwoodLiveRadio.set_memory(self, memory) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_mode = False rf.has_tuning_step = False rf.valid_modes = ["FM"] rf.valid_tmodes = ["", "Tone", "TSQL"] rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC rf.valid_name_length = 7 rf.has_sub_devices = True rf.memory_bounds = (1, self._upper) return rf def _make_mem_spec(self, mem): spec = ( \ "%011i" % mem.freq, "%X" % STEPS.index(mem.tuning_step), "%i" % util.get_dict_rev(DUPLEX, mem.duplex), "0", "%i" % (mem.tmode == "Tone"), "%i" % (mem.tmode == "TSQL"), "0", "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1), "000", "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1), "", "0") return spec def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[2]) mem.freq = int(spec[3]) mem.tuning_step = STEPS[int(spec[4], 16)] mem.duplex = DUPLEX[int(spec[5])] if int(spec[7]): mem.tmode = "Tone" elif int(spec[8]): mem.tmode = "TSQL" mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1] mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1] return mem def get_sub_devices(self): return [TMV7RadioVHF(self.pipe), TMV7RadioUHF(self.pipe)] def __test_location(self, loc): mem = self.get_memory(loc) if not mem.empty: # Memory was not empty, must be valid return True # Mem was empty (or invalid), try to set it if self._vfo == 0: mem.freq = 144000000 else: mem.freq = 440000000 mem.empty = False try: self.set_memory(mem) except Exception: # Failed, so we're past the limit return False # Erase what we did try: self.erase_memory(loc) except Exception: pass # V7A Can't delete just yet return True def _detect_split(self): return 50 class TMV7RadioSub(TMV7Radio): """Base class for the TM-V7 sub devices""" def __init__(self, pipe): TMV7Radio.__init__(self, pipe) self._detect_split() class TMV7RadioVHF(TMV7RadioSub): """TM-V7 VHF subdevice""" VARIANT = "VHF" _vfo = 0 class TMV7RadioUHF(TMV7RadioSub): """TM-V7 UHF subdevice""" VARIANT = "UHF" _vfo = 1 @directory.register class TMG707Radio(TMV7Radio): """Kenwood TM-G707""" MODEL = "TM-G707" def get_features(self): rf = TMV7Radio.get_features(self) rf.has_sub_devices = False rf.memory_bounds = (1, 180) rf.valid_bands = [(144000000, 148000000), (430000000, 450000000)] return rf THF6A_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] THF6A_DUPLEX = dict(DUPLEX) THF6A_DUPLEX[3] = "split" @directory.register class THF6ARadio(KenwoodLiveRadio): """Kenwood TH-F6""" MODEL = "TH-F6" _upper = 399 _kenwood_split = True def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs_polarity = False rf.has_bank = False rf.can_odd_split = True rf.valid_modes = list(THF6_MODES) rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_tuning_steps = list(THF6A_STEPS) rf.valid_bands = [(1000, 1300000000)] rf.valid_skips = ["", "S"] rf.valid_duplexes = THF6A_DUPLEX.values() rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 8 rf.memory_bounds = (0, self._upper) return rf def _cmd_set_memory(self, number, spec): if spec: spec = "," + spec return "MW", "0,%03i%s" % (number, spec) def _cmd_get_memory(self, number): return "MR", "0,%03i" % number def _cmd_get_memory_name(self, number): return "MNA", "%03i" % number def _cmd_set_memory_name(self, number, name): return "MNA", "%03i,%s" % (number, name) def _cmd_get_split(self, number): return "MR", "1,%03i" % number def _cmd_set_split(self, number, spec): return "MW", "1,%03i,%s" % (number, spec) def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[1]) mem.freq = int(spec[2]) mem.tuning_step = THF6A_STEPS[int(spec[3], 16)] mem.duplex = THF6A_DUPLEX[int(spec[4])] mem.tmode = get_tmode(spec[6], spec[7], spec[8]) mem.rtone = self._kenwood_valid_tones[int(spec[9])] mem.ctone = self._kenwood_valid_tones[int(spec[10])] if spec[11] and spec[11].isdigit(): mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])] else: print "Unknown or invalid DCS: %s" % spec[11] if spec[12]: mem.offset = int(spec[12]) else: mem.offset = 0 mem.mode = THF6_MODES[int(spec[13])] if spec[14] == "1": mem.skip = "S" return mem def _make_mem_spec(self, mem): if mem.duplex in " +-": duplex = util.get_dict_rev(THF6A_DUPLEX, mem.duplex) offset = mem.offset elif mem.duplex == "split": duplex = 0 offset = 0 else: print "Bug: unsupported duplex `%s'" % mem.duplex spec = ( \ "%011i" % mem.freq, "%X" % THF6A_STEPS.index(mem.tuning_step), "%i" % duplex, "0", "%i" % (mem.tmode == "Tone"), "%i" % (mem.tmode == "TSQL"), "%i" % (mem.tmode == "DTCS"), "%02i" % (self._kenwood_valid_tones.index(mem.rtone)), "%02i" % (self._kenwood_valid_tones.index(mem.ctone)), "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)), "%09i" % offset, "%i" % (THF6_MODES.index(mem.mode)), "%i" % (mem.skip == "S")) return spec @directory.register class THF7ERadio(THF6ARadio): """Kenwood TH-F7""" MODEL = "TH-F7" D710_DUPLEX = ["", "+", "-", "split"] D710_MODES = ["FM", "NFM", "AM"] D710_SKIP = ["", "S"] D710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] D710_TONES = list(chirp_common.TONES) D710_TONES.remove(159.8) D710_TONES.remove(165.5) D710_TONES.remove(171.3) D710_TONES.remove(177.3) D710_TONES.remove(183.5) D710_TONES.remove(189.9) D710_TONES.remove(196.6) D710_TONES.remove(199.5) @directory.register class TMD710Radio(KenwoodLiveRadio): """Kenwood TM-D710""" MODEL = "TM-D710" _upper = 999 _kenwood_valid_tones = list(D710_TONES) def get_features(self): rf = chirp_common.RadioFeatures() rf.can_odd_split = True rf.has_dtcs_polarity = False rf.has_bank = False rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = D710_MODES rf.valid_duplexes = D710_DUPLEX rf.valid_tuning_steps = D710_STEPS rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',','') rf.valid_name_length = 8 rf.valid_skips = D710_SKIP rf.memory_bounds = (0, 999) return rf def _cmd_get_memory(self, number): return "ME", "%03i" % number def _cmd_get_memory_name(self, number): return "MN", "%03i" % number def _cmd_set_memory(self, number, spec): return "ME", "%03i,%s" % (number, spec) def _cmd_set_memory_name(self, number, name): return "MN", "%03i,%s" % (number, name) def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[0]) mem.freq = int(spec[1]) mem.tuning_step = D710_STEPS[int(spec[2], 16)] mem.duplex = D710_DUPLEX[int(spec[3])] # Reverse if int(spec[5]): mem.tmode = "Tone" elif int(spec[6]): mem.tmode = "TSQL" elif int(spec[7]): mem.tmode = "DTCS" mem.rtone = self._kenwood_valid_tones[int(spec[8])] mem.ctone = self._kenwood_valid_tones[int(spec[9])] mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])] mem.offset = int(spec[11]) mem.mode = D710_MODES[int(spec[12])] # TX Frequency if int(spec[13]): mem.duplex = "split" mem.offset = int(spec[13]) # Unknown mem.skip = D710_SKIP[int(spec[15])] # Memory Lockout return mem def _make_mem_spec(self, mem): spec = ( \ "%010i" % mem.freq, "%X" % D710_STEPS.index(mem.tuning_step), "%i" % (0 if mem.duplex == "split" else \ D710_DUPLEX.index(mem.duplex)), "0", # Reverse "%i" % (mem.tmode == "Tone" and 1 or 0), "%i" % (mem.tmode == "TSQL" and 1 or 0), "%i" % (mem.tmode == "DTCS" and 1 or 0), "%02i" % (self._kenwood_valid_tones.index(mem.rtone)), "%02i" % (self._kenwood_valid_tones.index(mem.ctone)), "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)), "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset "%i" % D710_MODES.index(mem.mode), "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq "0", # Unknown "%i" % D710_SKIP.index(mem.skip), # Memory Lockout ) return spec @directory.register class THD72Radio(TMD710Radio): """Kenwood TH-D72""" MODEL = "TH-D72 (live mode)" HARDWARE_FLOW = sys.platform == "darwin" # only OS X driver needs hw flow def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[0]) mem.freq = int(spec[1]) mem.tuning_step = D710_STEPS[int(spec[2], 16)] mem.duplex = D710_DUPLEX[int(spec[3])] # Reverse if int(spec[5]): mem.tmode = "Tone" elif int(spec[6]): mem.tmode = "TSQL" elif int(spec[7]): mem.tmode = "DTCS" mem.rtone = self._kenwood_valid_tones[int(spec[9])] mem.ctone = self._kenwood_valid_tones[int(spec[10])] mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])] mem.offset = int(spec[13]) mem.mode = D710_MODES[int(spec[14])] # TX Frequency if int(spec[15]): mem.duplex = "split" mem.offset = int(spec[15]) # Lockout mem.skip = D710_SKIP[int(spec[17])] # Memory Lockout return mem def _make_mem_spec(self, mem): spec = ( \ "%010i" % mem.freq, "%X" % D710_STEPS.index(mem.tuning_step), "%i" % (0 if mem.duplex == "split" else \ D710_DUPLEX.index(mem.duplex)), "0", # Reverse "%i" % (mem.tmode == "Tone" and 1 or 0), "%i" % (mem.tmode == "TSQL" and 1 or 0), "%i" % (mem.tmode == "DTCS" and 1 or 0), "0", "%02i" % (self._kenwood_valid_tones.index(mem.rtone)), "%02i" % (self._kenwood_valid_tones.index(mem.ctone)), "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)), "0", "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset "%i" % D710_MODES.index(mem.mode), "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq "0", # Unknown "%i" % D710_SKIP.index(mem.skip), # Memory Lockout ) return spec @directory.register class TMV71Radio(TMD710Radio): """Kenwood TM-V71""" MODEL = "TM-V71" THK2_DUPLEX = ["", "+", "-"] THK2_MODES = ["FM", "NFM"] THK2_TONES = list(chirp_common.TONES) THK2_TONES.remove(159.8) # ?? THK2_TONES.remove(165.5) # ?? THK2_TONES.remove(171.3) # ?? THK2_TONES.remove(177.3) # ?? THK2_TONES.remove(183.5) # ?? THK2_TONES.remove(189.9) # ?? THK2_TONES.remove(196.6) # ?? THK2_TONES.remove(199.5) # ?? THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/" @directory.register class THK2Radio(KenwoodLiveRadio): """Kenwood TH-K2""" MODEL = "TH-K2" _kenwood_valid_tones = list(THK2_TONES) def get_features(self): rf = chirp_common.RadioFeatures() rf.can_odd_split = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_tuning_step = False rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = THK2_MODES rf.valid_duplexes = THK2_DUPLEX rf.valid_characters = THK2_CHARS rf.valid_name_length = 6 rf.valid_bands = [(136000000, 173990000)] rf.valid_skips = ["", "S"] rf.valid_tuning_steps = [5.0] rf.memory_bounds = (1, 50) return rf def _cmd_get_memory(self, number): return "ME", "%02i" % number def _cmd_get_memory_name(self, number): return "MN", "%02i" % number def _cmd_set_memory(self, number, spec): return "ME", "%02i,%s" % (number, spec) def _cmd_set_memory_name(self, number, name): return "MN", "%02i,%s" % (number, name) def _parse_mem_spec(self, spec): mem = chirp_common.Memory() mem.number = int(spec[0]) mem.freq = int(spec[1]) #mem.tuning_step = mem.duplex = THK2_DUPLEX[int(spec[3])] if int(spec[5]): mem.tmode = "Tone" elif int(spec[6]): mem.tmode = "TSQL" elif int(spec[7]): mem.tmode = "DTCS" mem.rtone = self._kenwood_valid_tones[int(spec[8])] mem.ctone = self._kenwood_valid_tones[int(spec[9])] mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])] mem.offset = int(spec[11]) mem.mode = THK2_MODES[int(spec[12])] mem.skip = int(spec[16]) and "S" or "" return mem def _make_mem_spec(self, mem): try: rti = self._kenwood_valid_tones.index(mem.rtone) cti = self._kenwood_valid_tones.index(mem.ctone) except ValueError: raise errors.UnsupportedToneError() spec = ( \ "%010i" % mem.freq, "0", "%i" % THK2_DUPLEX.index(mem.duplex), "0", "%i" % int(mem.tmode == "Tone"), "%i" % int(mem.tmode == "TSQL"), "%i" % int(mem.tmode == "DTCS"), "%02i" % rti, "%02i" % cti, "%03i" % chirp_common.DTCS_CODES.index(mem.dtcs), "%08i" % mem.offset, "%i" % THK2_MODES.index(mem.mode), "0", "%010i" % 0, "0", "%i" % int(mem.skip == "S") ) return spec @directory.register class TM271Radio(THK2Radio): """Kenwood TM-271""" MODEL = "TM-271" def get_features(self): rf = chirp_common.RadioFeatures() rf.can_odd_split = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_tuning_step = False rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = THK2_MODES rf.valid_duplexes = THK2_DUPLEX rf.valid_characters = THK2_CHARS rf.valid_name_length = 6 rf.valid_bands = [(137000000, 173990000)] rf.valid_skips = ["", "S"] rf.valid_tuning_steps = [5.0] rf.memory_bounds = (0, 99) return rf def _cmd_get_memory(self, number): return "ME", "%03i" % number def _cmd_get_memory_name(self, number): return "MN", "%03i" % number def _cmd_set_memory(self, number, spec): return "ME", "%03i,%s" % (number, spec) def _cmd_set_memory_name(self, number, name): return "MN", "%03i,%s" % (number, name) def do_test(): """Dev test""" mem = chirp_common.Memory() mem.number = 1 mem.freq = 144000000 mem.duplex = "split" mem.offset = 146000000 tc = THF6ARadio class FakeSerial: """Faked serial line""" buf = "" def write(self, buf): """Write""" self.buf = buf def read(self, count): """Read""" if self.buf[:2] == "ID": return "ID %s\r" % tc.MODEL return self.buf def setTimeout(self, foo): """Set Timeout""" pass def setBaudrate(self, foo): """Set Baudrate""" pass radio = tc(FakeSerial()) radio.set_memory(mem) if __name__ == "__main__": do_test() chirp-0.3.1/chirp/ic9x_icf_ll.py0000644000016100007500000000735612023560645016235 0ustar jenkins00000000000000# Copyright 2010 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 . import struct from chirp import chirp_common from chirp.memmap import MemoryMap MEM_LOC_SIZE_A = 20 MEM_LOC_SIZE_B = MEM_LOC_SIZE_A + 1 + (3 * 8) POS_FREQ = 0 POS_OFFSET = 3 POS_TONE = 5 POS_MODE = 6 POS_DTCS = 7 POS_TS = 8 POS_DTCSPOL = 11 POS_DUPLEX = 11 POS_NAME = 12 def get_mem_offset(number): """Get the offset into the memory map for memory @number""" if number < 850: return MEM_LOC_SIZE_A * number else: return (MEM_LOC_SIZE_A * 850) + (MEM_LOC_SIZE_B * (number - 850)) def get_raw_memory(mmap, number): """Return a raw representation of memory @number""" offset = get_mem_offset(number) if number >= 850: size = MEM_LOC_SIZE_B else: size = MEM_LOC_SIZE_A return MemoryMap(mmap[offset:offset+size]) def get_freq(mmap): """Return the memory frequency""" if ord(mmap[10]) & 0x10: mult = 6250 else: mult = 5000 val, = struct.unpack(">I", "\x00" + mmap[POS_FREQ:POS_FREQ+3]) return val * mult def get_offset(mmap): """Return the memory offset""" val, = struct.unpack(">H", mmap[POS_OFFSET:POS_OFFSET+2]) return val * 5000 def get_rtone(mmap): """Return the memory rtone""" val = (ord(mmap[POS_TONE]) & 0xFC) >> 2 return chirp_common.TONES[val] def get_ctone(mmap): """Return the memory ctone""" val = (ord(mmap[POS_TONE]) & 0x03) | ((ord(mmap[POS_TONE+1]) & 0xF0) >> 4) return chirp_common.TONES[val] def get_dtcs(mmap): """Return the memory dtcs value""" val = ord(mmap[POS_DTCS]) >> 1 return chirp_common.DTCS_CODES[val] def get_mode(mmap): """Return the memory mode""" val = ord(mmap[POS_MODE]) & 0x07 modemap = ["FM", "NFM", "WFM", "AM", "DV", "FM"] return modemap[val] def get_ts(mmap): """Return the memory tuning step""" val = (ord(mmap[POS_TS]) & 0xF0) >> 4 if val == 14: return 5.0 # Coerce "Auto" to 5.0 icf_ts = list(chirp_common.TUNING_STEPS) icf_ts.insert(2, 8.33) icf_ts.insert(3, 9.00) icf_ts.append(100.0) icf_ts.append(125.0) icf_ts.append(200.0) return icf_ts[val] def get_dtcs_polarity(mmap): """Return the memory dtcs polarity""" val = (ord(mmap[POS_DTCSPOL]) & 0x03) pols = ["NN", "NR", "RN", "RR"] return pols[val] def get_duplex(mmap): """Return the memory duplex""" val = (ord(mmap[POS_DUPLEX]) & 0x0C) >> 2 dup = ["", "-", "+", ""] return dup[val] def get_name(mmap): """Return the memory name""" return mmap[POS_NAME:POS_NAME+8] def get_memory(_mmap, number): """Get memory @number from global memory map @_mmap""" mmap = get_raw_memory(_mmap, number) mem = chirp_common.Memory() mem.number = number mem.freq = get_freq(mmap) mem.offset = get_offset(mmap) mem.rtone = get_rtone(mmap) mem.ctone = get_ctone(mmap) mem.dtcs = get_dtcs(mmap) mem.mode = get_mode(mmap) mem.tuning_step = get_ts(mmap) mem.dtcs_polarity = get_dtcs_polarity(mmap) mem.duplex = get_duplex(mmap) mem.name = get_name(mmap) mem.empty = mem.freq == 0 return mem chirp-0.3.1/chirp/tmv71.py0000644000016100007500000000462011717005656015023 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, errors, util from chirp import tmv71_ll class TMV71ARadio(chirp_common.CloneModeRadio): BAUD_RATE = 9600 VENDOR = "Kenwood" MODEL = "TM-V71A" mem_upper_limit = 1022 _memsize = 32512 _model = "" # FIXME: REMOVE def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 999) return rf def _detect_baud(self): for baud in [9600, 19200, 38400, 57600]: self.pipe.setBaudrate(baud) self.pipe.write("\r\r") self.pipe.read(32) try: id = tmv71_ll.get_id(self.pipe) print "Radio %s at %i baud" % (id, baud) return True except errors.RadioError: pass raise errors.RadioError("No response from radio") def get_raw_memory(self, number): return util.hexprint(tmv71_ll.get_raw_mem(self._mmap, number)) def get_special_locations(self): return sorted(tmv71_ll.V71_SPECIAL.keys()) def get_memory(self, number): if isinstance(number, str): try: number = tmv71_ll.V71_SPECIAL[number] except KeyError: raise errors.InvalidMemoryLocation("Unknown channel %s" % \ number) return tmv71_ll.get_memory(self._mmap, number) def set_memory(self, mem): return tmv71_ll.set_memory(self._mmap, mem) def erase_memory(self, number): tmv71_ll.set_used(self._mmap, number, 0) def sync_in(self): self._detect_baud() self._mmap = tmv71_ll.download(self) def sync_out(self): self._detect_baud() tmv71_ll.upload(self) chirp-0.3.1/chirp/vx8.py0000644000016101777760000002172312130403635016223 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . from chirp import chirp_common, yaesu_clone, directory from chirp import bitwise MEM_FORMAT = """ #seekto 0x54a; struct { u16 in_use; } bank_used[24]; #seekto 0x135A; struct { u8 unknown[2]; u8 name[16]; } bank_info[24]; #seekto 0x198a; struct { u16 channel[100]; } bank_members[24]; #seekto 0x2C4A; struct { u8 nosubvfo:1, unknown:3, pskip:1, skip:1, used:1, valid:1; } flag[900]; #seekto 0x328A; struct { u8 unknown1; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown2:4, tone_mode:2; u8 charsetbits[2]; char label[16]; bbcd offset[3]; u8 unknown5:2, tone:6; u8 unknown6:1, dcs:7; u8 unknown7[3]; } memory[900]; #seekto 0xFECA; u8 checksum; """ TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM"] STEPS = list(chirp_common.TUNING_STEPS) STEPS.remove(30.0) STEPS.append(100.0) STEPS.insert(2, 0.0) # There is a skipped tuning step ad index 2 (?) SKIPS = ["", "S", "P"] CHARSET = ["%i" % int(x) for x in range(0, 10)] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ [" ",] + \ [chr(x) for x in range(ord("a"), ord("z")+1)] + \ list(".,:;*#_-/&()@!?^ ") + list("\x00" * 100) POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("L3", watts=2.50), chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L1", watts=0.05)] class VX8Bank(chirp_common.NamedBank): """A VX-8 bank""" def get_name(self): _bank = self._model._radio._memobj.bank_info[self.index] _bank_used = self._model._radio._memobj.bank_used[self.index] name = "" for i in _bank.name: if i == 0xFF: break name += CHARSET[i & 0x7F] return name.rstrip() def set_name(self, name): _bank = self._model._radio._memobj.bank_info[self.index] _bank.name = [CHARSET.index(x) for x in name.ljust(16)[:16]] class VX8BankModel(chirp_common.BankModel): """A VX-8 bank model""" def get_num_banks(self): return 24 def get_banks(self): banks = [] _banks = self._radio._memobj.bank_info index = 0 for _bank in _banks: bank = VX8Bank(self, "%i" % index, "BANK-%i" % index) bank.index = index banks.append(bank) index += 1 return banks def add_memory_to_bank(self, memory, bank): _members = self._radio._memobj.bank_members[bank.index] _bank_used = self._radio._memobj.bank_used[bank.index] for i in range(0, 100): if _members.channel[i] == 0xFFFF: _members.channel[i] = memory.number - 1 _bank_used.in_use = 0x06 break def remove_memory_from_bank(self, memory, bank): _members = self._radio._memobj.bank_members[bank.index] _bank_used = self._radio._memobj.bank_used[bank.index] remaining_members = 0 found = False for i in range(0, len(_members.channel)): if _members.channel[i] == (memory.number - 1): _members.channel[i] = 0xFFFF found = True elif _members.channel[i] != 0xFFFF: remaining_members += 1 if not found: raise Exception("Memory %i is not in bank %s. Cannot remove" % \ (memory.number, bank)) if not remaining_members: _bank_used.in_use = 0xFFFF def get_bank_memories(self, bank): memories = [] _members = self._radio._memobj.bank_members[bank.index] _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return memories for channel in _members.channel: if channel != 0xFFFF: memories.append(self._radio.get_memory(int(channel)+1)) return memories def get_memory_banks(self, memory): banks = [] for bank in self.get_banks(): if memory.number in \ [x.number for x in self.get_bank_memories(bank)]: banks.append(bank) return banks def _wipe_memory(mem): mem.set_raw("\x00" * (mem.size() / 8)) mem.unknown1 = 0x05 @directory.register class VX8Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-8""" BAUD_RATE = 38400 VENDOR = "Yaesu" MODEL = "VX-8" VARIANT = "R" _model = "AH029" _memsize = 65227 _block_lengths = [ 10, 65217 ] _block_size = 32 def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs_polarity = False rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(500000, 999900000)] rf.valid_skips = SKIPS rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 16 rf.memory_bounds = (1, 900) rf.can_odd_split = True rf.has_ctone = False rf.has_bank_names = True return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0xFEC9) ] def get_memory(self, number): flag = self._memobj.flag[number-1] _mem = self._memobj.memory[number-1] mem = chirp_common.Memory() mem.number = number if not flag.used: mem.empty = True if not flag.valid: mem.empty = True return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = int(_mem.offset) * 1000 mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] mem.tmode = TMODES[_mem.tone_mode] mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = STEPS[_mem.tune_step] mem.power = POWER_LEVELS[3 - _mem.power] mem.skip = flag.pskip and "P" or flag.skip and "S" or "" for i in str(_mem.label): if i == "\xFF": break mem.name += CHARSET[ord(i)] return mem def _debank(self, mem): bm = self.get_bank_model() for bank in bm.get_memory_banks(mem): bm.remove_memory_from_bank(mem, bank) def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] flag = self._memobj.flag[mem.number-1] if not mem.empty and not flag.valid: _wipe_memory(_mem) if mem.empty and flag.valid and not flag.used: flag.valid = False return flag.used = not mem.empty flag.valid = flag.used if mem.empty: return if mem.freq < 30000000 or \ (mem.freq > 88000000 and mem.freq < 108000000) or \ mem.freq > 580000000: flag.nosubvfo = True # Masked from VFO B else: flag.nosubvfo = False # Available in both VFOs _mem.freq = int(mem.freq / 1000) _mem.offset = int(mem.offset / 1000) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.tone_mode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power: _mem.power = 3 - POWER_LEVELS.index(mem.power) else: _mem.power = 0 label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()]) _mem.label = label.ljust(16, "\xFF") # We only speak english here in chirpville _mem.charsetbits[0] = 0x00 _mem.charsetbits[1] = 0x00 flag.skip = mem.skip == "S" flag.pskip = mem.skip == "P" def get_bank_model(self): return VX8BankModel(self) @directory.register class VX8DRadio(VX8Radio): """Yaesu VX-8DR""" _model = "AH29D" VARIANT = "DR" chirp-0.3.1/chirp/ic9x_ll.py0000644000016101777760000003662312105270073017046 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import struct from chirp import chirp_common, util, errors, bitwise from chirp.memmap import MemoryMap TUNING_STEPS = [ 5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15, 20, 25, 30, 50, 100, 125, 200 ] MODES = ["FM", "NFM", "WFM", "AM", "DV"] DUPLEX = ["", "-", "+"] TMODES = ["", "Tone", "TSQL", "TSQL", "DTCS", "DTCS"] DTCS_POL = ["NN", "NR", "RN", "RR"] MEM_LEN = 34 DV_MEM_LEN = 60 # Dirty hack until I clean up this IC9x mess class IC9xMemory(chirp_common.Memory): """A dirty hack to stash bank information in a memory""" _bank = None _bank_index = 0 def __init__(self): chirp_common.Memory.__init__(self) class IC9xDVMemory(chirp_common.DVMemory): """See above dirty hack""" _bank = None _bank_index = 0 def __init__(self): chirp_common.DVMemory.__init__(self) def _ic9x_parse_frames(buf): frames = [] while "\xfe\xfe" in buf: try: start = buf.index("\xfe\xfe") end = buf[start:].index("\xfd") + start + 1 except Exception, e: print "No trailing bit" break framedata = buf[start:end] buf = buf[end:] try: frame = IC92Frame() frame.from_raw(framedata[2:-1]) frames.append(frame) except errors.InvalidDataError, e: print "Broken frame: %s" % e #print "Parsed %i frames" % len(frames) return frames def ic9x_send(pipe, buf): """Send @buf to @pipe, wrapped in a header and trailer. Attempt to read any response frames, which are returned as a list""" # Add header and trailer realbuf = "\xfe\xfe" + buf + "\xfd" #print "Sending:\n%s" % util.hexprint(realbuf) pipe.write(realbuf) pipe.flush() data = "" while True: buf = pipe.read(4096) if not buf: break data += buf return _ic9x_parse_frames(data) class IC92Frame: """IC9x frame base class""" def get_vfo(self): """Return the vfo number""" return ord(self._map[0]) def set_vfo(self, vfo): """Set the vfo number""" self._map[0] = chr(vfo) def from_raw(self, data): """Construct the frame from raw data""" self._map = MemoryMap(data) def from_frame(self, frame): """Construct the frame by copying another frame""" self._map = MemoryMap(frame.get_raw()) def __init__(self, subcmd=0, flen=0, cmd=0x1A): self._map = MemoryMap("\x00" * (4 + flen)) self._map[0] = "\x01\x80" + chr(cmd) + chr(subcmd) def get_payload(self): """Return the entire payload (sans header)""" return self._map[4:] def get_raw(self): """Return the raw version of the frame""" return self._map.get_packed() def __str__(self): string = "Frame VFO=%i (len = %i)\n" % (self.get_vfo(), len(self.get_payload())) string += util.hexprint(self.get_payload()) string += "\n" return string def send(self, pipe, verbose=False): """Send the frame to the radio via @pipe""" if verbose: print "Sending:\n%s" % util.hexprint(self.get_raw()) response = ic9x_send(pipe, self.get_raw()) if len(response) == 0: raise errors.InvalidDataError("No response from radio") return response[0] def __setitem__(self, start, value): self._map[start+4] = value def __getitem__(self, index): return self._map[index+4] def __getslice__(self, start, end): return self._map[start+4:end+4] class IC92GetBankFrame(IC92Frame): """A frame for requesting bank information""" def __init__(self): IC92Frame.__init__(self, 0x09) def send(self, pipe, verbose=False): rframes = ic9x_send(pipe, self.get_raw()) if len(rframes) == 0: raise errors.InvalidDataError("No response from radio") return rframes class IC92BankFrame(IC92Frame): """A frame for bank information""" def __init__(self): # 1 byte for identifier # 8 bytes for name IC92Frame.__init__(self, 0x0B, 9) def get_name(self): """Return the bank name""" return self[1:] def get_identifier(self): """Return the letter for the bank (A-Z)""" return self[0] def set_name(self, name): """Set the bank name""" self[1] = name[:8].ljust(8) def set_identifier(self, ident): """Set the letter for the bank (A-Z)""" self[0] = ident[0] class IC92MemClearFrame(IC92Frame): """A frame for clearing (erasing) a memory""" def __init__(self, loc): # 2 bytes for location # 1 byte for 0xFF IC92Frame.__init__(self, 0x00, 4) self[0] = struct.pack(">BHB", 1, int("%i" % loc, 16), 0xFF) class IC92MemGetFrame(IC92Frame): """A frame for requesting a memory""" def __init__(self, loc, iscall=False): # 2 bytes for location IC92Frame.__init__(self, 0x00, 3) if iscall: call = 2 else: call = 1 self[0] = struct.pack(">BH", call, int("%i" % loc, 16)) class IC92GetCallsignFrame(IC92Frame): """A frame for getting callsign information""" def __init__(self, calltype, number): IC92Frame.__init__(self, calltype, 1, 0x1D) self[0] = chr(number) class IC92CallsignFrame(IC92Frame): """A frame to communicate callsign information""" command = 0 # Invalid width = 8 def __init__(self, number=0, callsign=""): # 1 byte for index # $width bytes for callsign IC92Frame.__init__(self, self.command, self.width+1, 0x1D) self[0] = chr(number) + callsign[:self.width].ljust(self.width) def get_callsign(self): """Return the actual callsign""" return self[1:self.width+1].rstrip() class IC92YourCallsignFrame(IC92CallsignFrame): """URCALL frame""" command = 6 # Your class IC92RepeaterCallsignFrame(IC92CallsignFrame): """RPTCALL frame""" command = 7 # Repeater class IC92MyCallsignFrame(IC92CallsignFrame): """MYCALL frame""" command = 8 # My width = 12 # 4 bytes for /STID MEMORY_FRAME_FORMAT = """ struct { u8 vfo; bbcd number[2]; lbcd freq[5]; lbcd offset[4]; u8 unknown8; bbcd rtone[2]; bbcd ctone[2]; bbcd dtcs[2]; u8 unknown9[2]; u8 unknown2:1, mode:3, tuning_step:4; u8 unknown1:3, tmode: 3, duplex: 2; u8 unknown5:4, dtcs_polarity:2, pskip:1, skip:1; char bank; bbcd bank_index[1]; char name[8]; u8 unknown10; u8 digital_code; char rpt2call[8]; char rpt1call[8]; char urcall[8]; } mem[1]; """ class IC92MemoryFrame(IC92Frame): """A frame for communicating memory information""" def __init__(self): IC92Frame.__init__(self, 0, DV_MEM_LEN) # For good measure, here is a whole, valid memory block # at 146.010 FM. Since the 9x will complain if any bits # are invalid, it's easiest to start with a known-good one # since we don't set everything. self[0] = \ "\x01\x00\x03\x00\x00\x01\x46\x01" + \ "\x00\x00\x60\x00\x00\x08\x85\x08" + \ "\x85\x00\x23\x22\x80\x06\x00\x00" + \ "\x00\x00\x20\x20\x20\x20\x20\x20" + \ "\x20\x20\x00\x00\x20\x20\x20\x20" + \ "\x20\x20\x20\x20\x4b\x44\x37\x52" + \ "\x45\x58\x20\x43\x43\x51\x43\x51" + \ "\x43\x51\x20\x20" def set_vfo(self, vfo): IC92Frame.set_vfo(self, vfo) if vfo == 1: self._map.truncate(MEM_LEN + 4) def set_iscall(self, iscall): """This frame refers to a call channel if @iscall is True""" if iscall: self[0] = 2 else: self[0] = 1 def get_iscall(self): """Return True if this frame refers to a call channel""" return ord(self[0]) == 2 def set_memory(self, mem): """Take Memory object @mem and configure the frame accordingly""" if mem.number < 0: self.set_iscall(True) mem.number = abs(mem.number) - 1 print "Memory is %i (call %s)" % (mem.number, self.get_iscall()) _mem = bitwise.parse(MEMORY_FRAME_FORMAT, self).mem _mem.number = mem.number _mem.freq = mem.freq _mem.offset = mem.offset _mem.rtone = int(mem.rtone * 10) _mem.ctone = int(mem.ctone * 10) _mem.dtcs = int(mem.dtcs) _mem.mode = MODES.index(mem.mode) _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) _mem.duplex = DUPLEX.index(mem.duplex) _mem.tmode = TMODES.index(mem.tmode) _mem.dtcs_polarity = DTCS_POL.index(mem.dtcs_polarity) if mem._bank is not None: _mem.bank = chr(ord("A") + mem._bank) _mem.bank_index = mem._bank_index _mem.skip = mem.skip == "S" _mem.pskip = mem.skip == "P" _mem.name = mem.name.ljust(8)[:8] if mem.mode == "DV": _mem.urcall = mem.dv_urcall.upper().ljust(8)[:8] _mem.rpt1call = mem.dv_rpt1call.upper().ljust(8)[:8] _mem.rpt2call = mem.dv_rpt2call.upper().ljust(8)[:8] _mem.digital_code = mem.dv_code def get_memory(self): """Return a Memory object based on the contents of the frame""" _mem = bitwise.parse(MEMORY_FRAME_FORMAT, self).mem if MODES[_mem.mode] == "DV": mem = IC9xDVMemory() else: mem = IC9xMemory() mem.number = int(_mem.number) if self.get_iscall(): mem.number = -1 - mem.number mem.freq = int(_mem.freq) mem.offset = int(_mem.offset) mem.rtone = int(_mem.rtone) / 10.0 mem.ctone = int(_mem.ctone) / 10.0 mem.dtcs = int(_mem.dtcs) mem.mode = MODES[int(_mem.mode)] mem.tuning_step = TUNING_STEPS[int(_mem.tuning_step)] mem.duplex = DUPLEX[int(_mem.duplex)] mem.tmode = TMODES[int(_mem.tmode)] mem.dtcs_polarity = DTCS_POL[int(_mem.dtcs_polarity)] if int(_mem.bank) != 0: mem._bank = ord(str(_mem.bank)) - ord("A") mem._bank_index = int(_mem.bank_index) if _mem.skip: mem.skip = "S" elif _mem.pskip: mem.skip = "P" else: mem.skip = "" mem.name = str(_mem.name).rstrip() if mem.mode == "DV": mem.dv_urcall = str(_mem.urcall).rstrip() mem.dv_rpt1call = str(_mem.rpt1call).rstrip() mem.dv_rpt2call = str(_mem.rpt2call).rstrip() mem.dv_code = int(_mem.digital_code) return mem def _send_magic_4800(pipe): cmd = "\x01\x80\x19" magic = ("\xFE" * 25) + cmd for _i in [0, 1]: resp = ic9x_send(pipe, magic) if resp: return resp[0].get_raw()[0] == "\x80" return True def _send_magic_38400(pipe): cmd = "\x01\x80\x19" #rsp = "\x80\x01\x19" magic = ("\xFE" * 400) + cmd for _i in [0, 1]: resp = ic9x_send(pipe, magic) if resp: return resp[0].get_raw()[0] == "\x80" return False def send_magic(pipe): """Send the magic incantation to wake up an ic9x radio""" if pipe.getBaudrate() == 38400: resp = _send_magic_38400(pipe) if resp: return print "Switching from 38400 to 4800" pipe.setBaudrate(4800) resp = _send_magic_4800(pipe) pipe.setBaudrate(38400) if resp: return raise errors.RadioError("Radio not responding") elif pipe.getBaudrate() == 4800: resp = _send_magic_4800(pipe) if resp: return print "Switching from 4800 to 38400" pipe.setBaudrate(38400) resp = _send_magic_38400(pipe) if resp: return pipe.setBaudrate(4800) raise errors.RadioError("Radio not responding") else: raise errors.InvalidDataError("Radio in unknown state (%i)" % \ pipe.getBaudrate()) def get_memory_frame(pipe, vfo, number): """Get the memory frame for @vfo and @number via @pipe""" if number < 0: number = abs(number + 1) call = True else: call = False frame = IC92MemGetFrame(number, call) frame.set_vfo(vfo) return frame.send(pipe) def get_memory(pipe, vfo, number): """Get a memory object for @vfo and @number via @pipe""" rframe = get_memory_frame(pipe, vfo, number) if len(rframe.get_payload()) < 1: raise errors.InvalidMemoryLocation("No response from radio") if rframe.get_payload()[3] == '\xff': raise errors.InvalidMemoryLocation("Radio says location is empty") mf = IC92MemoryFrame() mf.from_frame(rframe) return mf.get_memory() def set_memory(pipe, vfo, memory): """Set memory @memory on @vfo via @pipe""" frame = IC92MemoryFrame() frame.set_memory(memory) frame.set_vfo(vfo) #print "Sending (%i):" % (len(frame.get_raw())) #print util.hexprint(frame.get_raw()) rframe = frame.send(pipe) if rframe.get_raw()[2] != "\xfb": raise errors.InvalidDataError("Radio reported error:\n%s" %\ util.hexprint(rframe.get_payload())) def erase_memory(pipe, vfo, number): """Erase memory @number on @vfo via @pipe""" frame = IC92MemClearFrame(number) frame.set_vfo(vfo) rframe = frame.send(pipe) if rframe.get_raw()[2] != "\xfb": raise errors.InvalidDataError("Radio reported error") def get_banks(pipe, vfo): """Get banks for @vfo via @pipe""" frame = IC92GetBankFrame() frame.set_vfo(vfo) rframes = frame.send(pipe) if vfo == 1: base = 180 else: base = 237 banks = [] for i in range(base, base+26): bframe = IC92BankFrame() bframe.from_frame(rframes[i]) banks.append(bframe.get_name().rstrip()) return banks def set_banks(pipe, vfo, banks): """Set banks for @vfo via @pipe""" for i in range(0, 26): bframe = IC92BankFrame() bframe.set_vfo(vfo) bframe.set_identifier(chr(i + ord("A"))) bframe.set_name(banks[i]) rframe = bframe.send(pipe) if rframe.get_payload() != "\xfb": raise errors.InvalidDataError("Radio reported error") def get_call(pipe, cstype, number): """Get @cstype callsign @number via @pipe""" cframe = IC92GetCallsignFrame(cstype.command, number) cframe.set_vfo(2) rframe = cframe.send(pipe) cframe = IC92CallsignFrame() cframe.from_frame(rframe) return cframe.get_callsign() def set_call(pipe, cstype, number, call): """Set @cstype @call at position @number via @pipe""" cframe = cstype(number, call) cframe.set_vfo(2) rframe = cframe.send(pipe) if rframe.get_payload() != "\xfb": raise errors.RadioError("Radio reported error") chirp-0.3.1/chirp/ict7h.py0000644000016100007500000000712212023560645015056 0ustar jenkins00000000000000# Copyright 2012 Eric Allen # # 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 chirp import chirp_common, icf, directory from chirp import bitwise mem_format = """ struct { bbcd freq[2]; u8 lastfreq:4, fraction:4; bbcd offset[2]; u8 unknown; u8 rtone; u8 ctone; } memory[60]; #seekto 0x0270; struct { u8 empty:1, tmode:2, duplex:2, unknown3:1, skip:1, unknown4:1; } flags[60]; """ TMODES = ["", "", "Tone", "TSQL", "TSQL"] # last one is pocket beep DUPLEX = ["", "", "-", "+"] MODES = ["FM"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0] @directory.register class ICT7HRadio(icf.IcomCloneModeRadio): VENDOR = "Icom" MODEL = "IC-T7H" _model = "\x18\x10\x00\x01" _memsize = 0x03B0 _endframe = "Icom Inc\x2e" _ranges = [(0x0000, _memsize, 16)] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 59) rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(118000000, 174000000), (400000000, 470000000)] rf.valid_skips = ["", "S"] rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_name = False rf.has_tuning_step = False return rf def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): _mem = self._memobj.memory[number] _flag = self._memobj.flags[number] mem = chirp_common.Memory() mem.number = number mem.empty = _flag.empty == 1 and True or False mem.freq = int(_mem.freq) * 100000 mem.freq += _mem.lastfreq * 10000 mem.freq += int((_mem.fraction / 2.0) * 1000) mem.offset = int(_mem.offset) * 10000 mem.rtone = chirp_common.TONES[_mem.rtone - 1] mem.ctone = chirp_common.TONES[_mem.ctone - 1] mem.tmode = TMODES[_flag.tmode] mem.duplex = DUPLEX[_flag.duplex] mem.mode = "FM" if _flag.skip: mem.skip = "S" return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flag = self._memobj.flags[mem.number] _mem.freq = int(mem.freq / 100000) topfreq = int(mem.freq / 100000) * 100000 lastfreq = int((mem.freq - topfreq) / 10000) _mem.lastfreq = lastfreq midfreq = (mem.freq - topfreq - lastfreq * 10000) _mem.fraction = midfreq / 500 _mem.offset = mem.offset / 10000 _mem.rtone = chirp_common.TONES.index(mem.rtone) + 1 _mem.ctone = chirp_common.TONES.index(mem.ctone) + 1 _flag.tmode = TMODES.index(mem.tmode) _flag.duplex = DUPLEX.index(mem.duplex) _flag.skip = mem.skip == "S" and 1 or 0 _flag.empty = mem.empty and 1 or 0 chirp-0.3.1/chirp/errors.py0000644000016100007500000000244412023560645015356 0ustar jenkins00000000000000# Copyright 2008 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 . class InvalidDataError(Exception): """The radio driver encountered some invalid data""" pass class InvalidValueError(Exception): """An invalid value for a given parameter was used""" pass class InvalidMemoryLocation(Exception): """The requested memory location does not exist""" pass class RadioError(Exception): """An error occurred while talking to the radio""" pass class UnsupportedToneError(Exception): """The radio does not support the specified tone value""" pass class ImageDetectFailed(Exception): """The driver for the supplied image could not be determined""" pass chirp-0.3.1/chirp/template.py0000644000016100007500000001105612023560645015654 0ustar jenkins00000000000000# Copyright 2012 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 2 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 chirp import chirp_common, directory, memmap from chirp import bitwise # Here is where we define the memory map for the radio. Since # We often just know small bits of it, we can use #seekto to skip # around as needed. # # Our fake radio includes just a single array of ten memory objects, # With some very basic settings, a 32-bit unsigned integer for the # frequency (in Hertz) and an eight-character alpha tag # MEM_FORMAT = """ #seekto 0x0000; struct { u32 freq; char name[8]; } memory[10]; """ def do_download(radio): """This is your download function""" # NOTE: Remove this in your real implementation! return memmap.MemoryMap("\x00" * 1000) # Get the serial port connection serial = radio.pipe # Our fake radio is just a simple download of 1000 bytes # from the serial port. Do that one byte at a time and # store them in the memory map data = "" for _i in range(0, 1000): data = serial.read(1) return memmap.MemoryMap(data) def do_upload(radio): """This is your upload function""" # NOTE: Remove this in your real implementation! raise Exception("This template driver does not really work!") # Get the serial port connection serial = radio.pipe # Our fake radio is just a simple upload of 1000 bytes # to the serial port. Do that one byte at a time, reading # from our memory map for i in range(0, 1000): serial.write(radio.get_mmap()[i]) # Uncomment this to actually register this radio in CHIRP # @directory.register class TemplateRadio(chirp_common.CloneModeRadio): """Acme Template""" VENDOR = "Acme" # Replace this with your vendor MODEL = "Template" # Replace this with your model BAUD_RATE = 9600 # Replace this with your baud rate # Return information about this radio's features, including # how many memories it has, what bands it supports, etc def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.memory_bounds = (0, 9) # This radio supports memories 0-9 rf.valid_bands = [(144000000, 148000000), # Supports 2-meters (440000000, 450000000), # Supports 70-centimeters ] return rf # Do a download of the radio from the serial port def sync_in(self): self._mmap = do_download(self) self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # Do an upload of the radio to the serial port def sync_out(self): do_upload(self) # Return a raw representation of the memory object, which # is very helpful for development def get_raw_memory(self, number): return repr(self._memobj.memory[number]) # Extract a high-level memory object from the low-level memory map # This is called to populate a memory in the UI def get_memory(self, number): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[number] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() mem.number = number # Set the memory number mem.freq = int(_mem.freq) # Convert your low-level frequency # to Hertz mem.name = str(_mem.name).rstrip() # Set the alpha tag # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem # Store details about a high-level memory to the memory map # This is called when a user edits a memory in the UI def set_memory(self, mem): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number] _mem.freq = mem.freq # Convert to low-level frequency # representation _mem.name = mem.name.ljust(8)[:8] # Store the alpha tag chirp-0.3.1/chirp/generic_csv.py0000644000016101777760000001747112130403635017772 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import os import csv from chirp import chirp_common, errors, directory class OmittedHeaderError(Exception): """Internal exception to signal that a column has been omitted""" pass def get_datum_by_header(headers, data, header): """Return the column corresponding to @headers[@header] from @data""" if header not in headers: raise OmittedHeaderError("Header %s not provided" % header) try: return data[headers.index(header)] except IndexError: raise OmittedHeaderError("Header %s not provided on this line" % \ header) def write_memory(writer, mem): """Write @mem using @writer if not empty""" if mem.empty: return writer.writerow(mem.to_csv()) @directory.register class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport): """A driver for Generic CSV files""" VENDOR = "Generic" MODEL = "CSV" FILE_EXTENSION = "csv" ATTR_MAP = { "Location" : (int, "number"), "Name" : (str, "name"), "Frequency" : (chirp_common.parse_freq, "freq"), "Duplex" : (str, "duplex"), "Offset" : (chirp_common.parse_freq, "offset"), "Tone" : (str, "tmode"), "rToneFreq" : (float, "rtone"), "cToneFreq" : (float, "ctone"), "DtcsCode" : (int, "dtcs"), "DtcsPolarity" : (str, "dtcs_polarity"), "Mode" : (str, "mode"), "TStep" : (float, "tuning_step"), "Skip" : (str, "skip"), "URCALL" : (str, "dv_urcall"), "RPT1CALL" : (str, "dv_rpt1call"), "RPT2CALL" : (str, "dv_rpt2call"), "Comment" : (str, "comment"), } def _blank(self): self.errors = [] self.memories = [] for i in range(0, 1000): mem = chirp_common.Memory() mem.number = i mem.empty = True self.memories.append(mem) def __init__(self, pipe): chirp_common.FileBackedRadio.__init__(self, None) self.memories = [] self._filename = pipe if self._filename and os.path.exists(self._filename): self.load() else: self._blank() def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.requires_call_lists = False rf.has_implicit_calls = False rf.memory_bounds = (0, len(self.memories)) rf.has_infinite_number = True rf.has_nostep_tuning = True rf.has_comment = True rf.valid_modes = list(chirp_common.MODES) rf.valid_tmodes = list(chirp_common.TONE_MODES) rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS) rf.valid_bands = [(1, 10000000000)] rf.valid_skips = ["", "S"] rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 999 return rf def _parse_csv_data_line(self, headers, line): mem = chirp_common.Memory() try: if get_datum_by_header(headers, line, "Mode") == "DV": mem = chirp_common.DVMemory() except OmittedHeaderError: pass for header, (typ, attr) in self.ATTR_MAP.items(): try: val = get_datum_by_header(headers, line, header) if not val and typ == int: val = None else: val = typ(val) if hasattr(mem, attr): setattr(mem, attr, val) except OmittedHeaderError, e: pass except Exception, e: raise Exception("[%s] %s" % (attr, e)) return mem def load(self, filename=None): if filename is None and self._filename is None: raise errors.RadioError("Need a location to load from") if filename: self._filename = filename self._blank() f = file(self._filename, "rU") header = f.readline().strip() f.seek(0, 0) reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"') good = 0 lineno = 0 for line in reader: lineno += 1 if lineno == 1: header = line continue if len(header) > len(line): print "Line %i has %i columns, expected %i" % (lineno, len(line), len(header)) self.errors.append("Column number mismatch on line %i" % lineno) continue try: mem = self._parse_csv_data_line(header, line) if mem.number is None: raise Exception("Invalid Location field" % lineno) except Exception, e: print "Line %i: %s" % (lineno, e) self.errors.append("Line %i: %s" % (lineno, e)) continue self._grow(mem.number) self.memories[mem.number] = mem good += 1 if not good: print self.errors raise errors.InvalidDataError("No channels found") def save(self, filename=None): if filename is None and self._filename is None: raise errors.RadioError("Need a location to save to") if filename: self._filename = filename f = file(self._filename, "wb") writer = csv.writer(f, delimiter=chirp_common.SEPCHAR) writer.writerow(chirp_common.Memory.CSV_FORMAT) for mem in self.memories: write_memory(writer, mem) f.close() # MMAP compatibility def save_mmap(self, filename): return self.save(filename) def load_mmap(self, filename): return self.load(filename) def get_memories(self, lo=0, hi=999): return [x for x in self.memories if x.number >= lo and x.number <= hi] def get_memory(self, number): try: return self.memories[number] except: raise errors.InvalidMemoryLocation("No such memory %s" % number) def _grow(self, target): delta = target - len(self.memories) if delta < 0: return delta += 1 for i in range(len(self.memories), len(self.memories) + delta + 1): mem = chirp_common.Memory() mem.empty = True mem.number = i self.memories.append(mem) def set_memory(self, newmem): self._grow(newmem.number) self.memories[newmem.number] = newmem def erase_memory(self, number): mem = chirp_common.Memory() mem.number = number mem.empty = True self.memories[number] = mem def get_raw_memory(self, number): return ",".join(chirp_common.Memory.CSV_FORMAT) + \ os.linesep + \ ",".join(self.memories[number].to_csv()) @classmethod def match_model(cls, _filedata, filename): """Match files ending in .CSV""" return filename.lower().endswith("." + cls.FILE_EXTENSION) chirp-0.3.1/chirp/icq7.py0000644000016101777760000001105712070764471016353 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . from chirp import chirp_common, icf, directory from chirp import bitwise from chirp.chirp_common import to_GHz, from_GHz MEM_FORMAT = """ struct { bbcd freq[3]; u8 fractional:1, unknown:7; bbcd offset[2]; u16 ctone:6 rtone:6, tune_step:4; } memory[200]; #seekto 0x0690; struct { u8 tmode:2, duplex:2, skip:1, pskip:1, mode:2; } flags[200]; #seekto 0x0690; u8 flags_whole[200]; """ TMODES = ["", "", "Tone", "TSQL", "TSQL"] # last one is pocket beep DUPLEX = ["", "", "-", "+"] MODES = ["FM", "WFM", "AM", "Auto"] STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] @directory.register class ICQ7Radio(icf.IcomCloneModeRadio): """Icom IC-Q7A""" VENDOR = "Icom" MODEL = "IC-Q7A" _model = "\x19\x95\x00\x01" _memsize = 0x7C0 _endframe = "Icom Inc\x2e" _ranges = [(0x0000, 0x07C0, 16)] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [( 1000000, 823995000), (849000000, 868995000), (894000000, 1309995000)] rf.valid_skips = ["", "S", "P"] rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_name = False return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return (repr(self._memobj.memory[number]) + repr(self._memobj.flags[number])) def get_memory(self, number): _mem = self._memobj.memory[number] _flag = self._memobj.flags[number] mem = chirp_common.Memory() mem.number = number if self._memobj.flags_whole[number] == 0xFF: mem.empty = True return mem mem.freq = int(_mem.freq) * 1000 if _mem.fractional: mem.freq = chirp_common.fix_rounded_step(mem.freq) mem.offset = int(_mem.offset) * 1000 try: mem.rtone = chirp_common.TONES[_mem.rtone] except IndexError: mem.rtone = 88.5 try: mem.ctone = chirp_common.TONES[_mem.ctone] except IndexError: mem.ctone = 88.5 try: mem.tuning_step = STEPS[_mem.tune_step] except IndexError: print "Invalid tune step index %i" % _mem.tune_step mem.tmode = TMODES[_flag.tmode] mem.duplex = DUPLEX[_flag.duplex] if mem.freq < 30000000: mem.mode = "AM" else: mem.mode = MODES[_flag.mode] if _flag.pskip: mem.skip = "P" elif _flag.skip: mem.skip = "S" return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flag = self._memobj.flags[mem.number] if mem.empty: self._memobj.flags_whole[mem.number] = 0xFF return _mem.set_raw("\x00" * 8) if mem.freq > to_GHz(1): _mem.freq = (mem.freq / 1000) - to_GHz(1) upper = from_GHz(mem.freq) << 4 _mem.freq[0].clr_bits(0xF0) _mem.freq[0].set_bits(upper) else: _mem.freq = mem.freq / 1000 _mem.fractional = chirp_common.is_fractional_step(mem.freq) _mem.offset = mem.offset / 1000 _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.tune_step = STEPS.index(mem.tuning_step) _flag.tmode = TMODES.index(mem.tmode) _flag.duplex = DUPLEX.index(mem.duplex) _flag.mode = MODES.index(mem.mode) _flag.skip = mem.skip == "S" and 1 or 0 _flag.pskip = mem.skip == "P" and 1 or 0 chirp-0.3.1/chirp/generic_xml.py0000644000016100007500000001063612023560645016340 0ustar jenkins00000000000000# Copyright 2008 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 . import os import libxml2 from chirp import chirp_common, errors, xml_ll, platform, directory def validate_doc(doc): """Validate the document""" basepath = platform.get_platform().executable_path() path = os.path.abspath(os.path.join(basepath, "chirp.xsd")) if not os.path.exists(path): path = "/usr/share/chirp/chirp.xsd" try: ctx = libxml2.schemaNewParserCtxt(path) schema = ctx.schemaParse() except libxml2.parserError, e: print "Unable to load schema: %s" % e print "Path: %s" % path raise errors.RadioError("Unable to load schema") del ctx errs = [] warnings = [] def _err(msg, *_args): errs.append("ERROR: %s" % msg) def _wrn(msg, *_args): print "WARNING: %s" % msg warnings.append("WARNING: %s" % msg) validctx = schema.schemaNewValidCtxt() validctx.setValidityErrorHandler(_err, _wrn) err = validctx.schemaValidateDoc(doc) print os.linesep.join(warnings) if err: print "---DOC---\n%s\n------" % doc.serialize(format=1) print os.linesep.join(errs) raise errors.RadioError("Schema error") def default_banks(): """Return an empty set of banks""" banks = [] for i in range(0, 26): banks.append("Bank-%s" % (chr(ord("A") + i))) return banks @directory.register class XMLRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport): """Generic XML driver""" VENDOR = "Generic" MODEL = "XML" FILE_EXTENSION = "chirp" def __init__(self, pipe): chirp_common.FileBackedRadio.__init__(self, None) self._filename = pipe if self._filename and os.path.exists(self._filename): self.doc = libxml2.parseFile(self._filename) validate_doc(self.doc) else: self.doc = libxml2.newDoc("1.0") radio = self.doc.newChild(None, "radio", None) radio.newChild(None, "memories", None) radio.newChild(None, "banks", None) radio.newProp("version", "0.1.1") def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False #rf.has_bank_index = True rf.requires_call_lists = False rf.has_implicit_calls = False rf.memory_bounds = (0, 1000) rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 999 rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] return rf def load(self, filename=None): if not self._filename and not filename: raise errors.RadioError("Need a location to load from") if filename: self._filename = filename self.doc = libxml2.parseFile(self._filename) validate_doc(self.doc) def save(self, filename=None): if not self._filename and not filename: raise errors.RadioError("Need a location to save to") if filename: self._filename = filename f = file(self._filename, "w") f.write(self.doc.serialize(format=1)) f.close() def get_memories(self, lo=0, hi=999): mems = [] for i in range(lo, hi): try: mems.append(xml_ll.get_memory(self.doc, i)) except errors.InvalidMemoryLocation: pass return mems def get_memory(self, number): mem = xml_ll.get_memory(self.doc, number) return mem def set_memory(self, mem): xml_ll.set_memory(self.doc, mem) def erase_memory(self, number): xml_ll.del_memory(self.doc, number) @classmethod def match_model(cls, _filedata, filename): """Match this driver if the extension matches""" return filename.lower().endswith("." + cls.FILE_EXTENSION) chirp-0.3.1/chirp/kenwood_hmk.py0000644000016101777760000000777112130403635020012 0ustar jenkinsnogroup00000000000000# Copyright 2008 Dan Smith # Copyright 2012 Tom Hayward # # 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 . import csv from chirp import chirp_common, errors, directory, generic_csv class OmittedHeaderError(Exception): """An internal exception to indicate that a header was omitted""" pass @directory.register class HMKRadio(generic_csv.CSVRadio): """Kenwood HMK format""" VENDOR = "Kenwood" MODEL = "HMK" FILE_EXTENSION = "hmk" DUPLEX_MAP = { " ": "", "S": "split", "+": "+", "-": "-", } SKIP_MAP = { "Off": "", "On": "S", } TMODE_MAP = { "Off": "", "T": "Tone", "CT": "TSQL", "DCS": "DTCS", "": "Cross", } ATTR_MAP = { "!!Ch" : (int, "number"), "M.Name" : (str, "name"), "Rx Freq." : (chirp_common.parse_freq, "freq"), "Shift/Split" : (lambda v: HMKRadio.DUPLEX_MAP[v], "duplex"), "Offset" : (chirp_common.parse_freq, "offset"), "T/CT/DCS" : (lambda v: HMKRadio.TMODE_MAP[v], "tmode"), "TO Freq." : (float, "rtone"), "CT Freq." : (float, "ctone"), "DCS Code" : (int, "dtcs"), "Mode" : (str, "mode"), "Rx Step" : (float, "tuning_step"), "L.Out" : (lambda v: HMKRadio.SKIP_MAP[v], "skip"), } def load(self, filename=None): if filename is None and self._filename is None: raise errors.RadioError("Need a location to load from") if filename: self._filename = filename self._blank() f = file(self._filename, "r") for line in f: if line.strip() == "// Memory Channels": break reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"') good = 0 lineno = 0 for line in reader: lineno += 1 if lineno == 1: header = line continue if len(header) > len(line): print "Line %i has %i columns, expected %i" % (lineno, len(line), len(header)) self.errors.append("Column number mismatch on line %i" % lineno) continue # hmk stores Tx Freq. in its own field, but Chirp expects the Tx # Freq. for odd-split channels to be in the Offset field. # If channel is odd-split, copy Tx Freq. field to Offset field. if line[header.index('Shift/Split')] == "S": line[header.index('Offset')] = line[header.index('Tx Freq.')] # fix EU decimal line = [i.replace(',','.') for i in line] try: mem = self._parse_csv_data_line(header, line) if mem.number is None: raise Exception("Invalid Location field" % lineno) except Exception, e: print "Line %i: %s" % (lineno, e) self.errors.append("Line %i: %s" % (lineno, e)) continue self._grow(mem.number) self.memories[mem.number] = mem good += 1 if not good: print self.errors raise errors.InvalidDataError("No channels found") chirp-0.3.1/chirp/platform.py0000644000016101777760000003347212130403635017326 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . import os import sys import glob from subprocess import Popen try: from serial.tools.list_ports import comports except: def comports(): import win32file import win32con ports = [] for i in range(1, 257): portname = "COM%i" % i print "Trying %s" % portname try: mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE port = \ win32file.CreateFile(portname, mode, win32con.FILE_SHARE_READ, None, win32con.OPEN_EXISTING, 0, None) ports.append((portname,"Unknown","Serial")) win32file.CloseHandle(port) port = None except Exception, e: print "Failed: %s" % e pass return ports def _find_me(): return sys.modules["chirp.platform"].__file__ class Platform: """Base class for platform-specific functions""" def __init__(self, basepath): self._base = basepath self._last_dir = self.default_dir() def get_last_dir(self): """Return the last directory used""" return self._last_dir def set_last_dir(self, last_dir): """Set the last directory used""" self._last_dir = last_dir def config_dir(self): """Return the preferred configuration file directory""" return self._base def log_dir(self): """Return the preferred log file directory""" logdir = os.path.join(self.config_dir(), "logs") if not os.path.isdir(logdir): os.mkdir(logdir) return logdir def filter_filename(self, filename): """Filter @filename for platform-forbidden characters""" return filename def log_file(self, filename): """Return the full path to a log file with @filename""" filename = self.filter_filename(filename + ".txt").replace(" ", "_") return os.path.join(self.log_dir(), filename) def config_file(self, filename): """Return the full path to a config file with @filename""" return os.path.join(self.config_dir(), self.filter_filename(filename)) def open_text_file(self, path): """Spawn the necessary program to open a text file at @path""" raise NotImplementedError("The base class can't do that") def open_html_file(self, path): """Spawn the necessary program to open an HTML file at @path""" raise NotImplementedError("The base class can't do that") def list_serial_ports(self): """Return a list of valid serial ports""" return [] def default_dir(self): """Return the default directory for this platform""" return "." def gui_open_file(self, start_dir=None, types=[]): """Prompt the user to pick a file to open""" import gtk if not start_dir: start_dir = self._last_dir dlg = gtk.FileChooserDialog("Select a file to open", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) if start_dir and os.path.isdir(start_dir): dlg.set_current_folder(start_dir) for desc, spec in types: ff = gtk.FileFilter() ff.set_name(desc) ff.add_pattern(spec) dlg.add_filter(ff) res = dlg.run() fname = dlg.get_filename() dlg.destroy() if res == gtk.RESPONSE_OK: self._last_dir = os.path.dirname(fname) return fname else: return None def gui_save_file(self, start_dir=None, default_name=None, types=[]): """Prompt the user to pick a filename to save""" import gtk if not start_dir: start_dir = self._last_dir dlg = gtk.FileChooserDialog("Save file as", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) if start_dir and os.path.isdir(start_dir): dlg.set_current_folder(start_dir) if default_name: dlg.set_current_name(default_name) extensions = {} for desc, ext in types: ff = gtk.FileFilter() ff.set_name(desc) ff.add_pattern("*.%s" % ext) extensions[desc] = ext dlg.add_filter(ff) res = dlg.run() fname = dlg.get_filename() ext = extensions[dlg.get_filter().get_name()] if fname and not fname.endswith(".%s" % ext): fname = "%s.%s" % (fname, ext) dlg.destroy() if res == gtk.RESPONSE_OK: self._last_dir = os.path.dirname(fname) return fname else: return None def gui_select_dir(self, start_dir=None): """Prompt the user to pick a directory""" import gtk if not start_dir: start_dir = self._last_dir dlg = gtk.FileChooserDialog("Choose folder", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) if start_dir and os.path.isdir(start_dir): dlg.set_current_folder(start_dir) res = dlg.run() fname = dlg.get_filename() dlg.destroy() if res == gtk.RESPONSE_OK and os.path.isdir(fname): self._last_dir = fname return fname else: return None def os_version_string(self): """Return a string that describes the OS/platform version""" return "Unknown Operating System" def executable_path(self): """Return a full path to the program executable""" def we_are_frozen(): return hasattr(sys, "frozen") if we_are_frozen(): # Win32, find the directory of the executable return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding())) else: # UNIX: Find the parent directory of this module return os.path.dirname(os.path.abspath(os.path.join(_find_me(), ".."))) def _unix_editor(): macos_textedit = "/Applications/TextEdit.app/Contents/MacOS/TextEdit" if os.path.exists(macos_textedit): return macos_textedit else: return "gedit" class UnixPlatform(Platform): """A platform module suitable for UNIX systems""" def __init__(self, basepath): if not basepath: basepath = os.path.abspath(os.path.join(self.default_dir(), ".chirp")) if not os.path.isdir(basepath): os.mkdir(basepath) Platform.__init__(self, basepath) # This is a hack that needs to be properly fixed by importing the # latest changes to this module from d-rats. In the interest of # time, however, I'll throw it here if sys.platform == "darwin": if not os.environ.has_key("DISPLAY"): print "Forcing DISPLAY for MacOS" os.environ["DISPLAY"] = ":0" os.environ["PANGO_RC_FILE"] = "../Resources/etc/pango/pangorc" def default_dir(self): return os.path.abspath(os.getenv("HOME")) def filter_filename(self, filename): return filename.replace("/", "") def open_text_file(self, path): pid1 = os.fork() if pid1 == 0: pid2 = os.fork() if pid2 == 0: editor = _unix_editor() print "calling `%s %s'" % (editor, path) os.execlp(editor, editor, path) else: sys.exit(0) else: os.waitpid(pid1, 0) print "Exec child exited" def open_html_file(self, path): os.system("firefox '%s'" % path) def list_serial_ports(self): return sorted(glob.glob("/dev/ttyS*") + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/cu.*") + glob.glob("/dev/term/*") + glob.glob("/dev/tty.KeySerial*")) def os_version_string(self): try: issue = file("/etc/issue.net", "r") ver = issue.read().strip().replace("\r", "").replace("\n", "")[:64] issue.close() ver = "%s - %s" % (os.uname()[0], ver) except Exception: ver = " ".join(os.uname()) return ver class Win32Platform(Platform): """A platform module suitable for Windows systems""" def __init__(self, basepath=None): if not basepath: appdata = os.getenv("APPDATA") if not appdata: appdata = "C:\\" basepath = os.path.abspath(os.path.join(appdata, "CHIRP")) if not os.path.isdir(basepath): os.mkdir(basepath) Platform.__init__(self, basepath) def default_dir(self): return os.path.abspath(os.path.join(os.getenv("USERPROFILE"), "Desktop")) def filter_filename(self, filename): for char in "/\\:*?\"<>|": filename = filename.replace(char, "") return filename def open_text_file(self, path): Popen(["notepad", path]) return def open_html_file(self, path): os.system("explorer %s" % path) def list_serial_ports(self): def cmp(a, b): try: return int(a[3:]) - int(b[3:]) except: return 0 ports = [port for port, name, url in comports()] ports.sort(cmp=cmp) return ports def gui_open_file(self, start_dir=None, types=[]): import win32gui typestrs = "" for desc, spec in types: typestrs += "%s\0%s\0" % (desc, spec) if not typestrs: typestrs = None try: fname, _, _ = win32gui.GetOpenFileNameW(Filter=typestrs) except Exception, e: print "Failed to get filename: %s" % e return None return str(fname) def gui_save_file(self, start_dir=None, default_name=None, types=[]): import win32gui import win32api (pform, _, _, _, _) = win32api.GetVersionEx() typestrs = "" custom = "%s\0*.%s\0" % (types[0][0], types[0][1]) for desc, ext in types[1:]: typestrs += "%s\0%s\0" % (desc, "*.%s" % ext) if pform > 5: typestrs = "%s\0%s\0" % (types[0][0], "*.%s" % types[0][1]) + \ typestrs if not typestrs: typestrs = custom custom = None def_ext = "*.%s" % types[0][1] try: fname, _, _ = win32gui.GetSaveFileNameW(File=default_name, CustomFilter=custom, DefExt=def_ext, Filter=typestrs) except Exception, e: print "Failed to get filename: %s" % e return None return str(fname) def gui_select_dir(self, start_dir=None): from win32com.shell import shell try: pidl, _, _ = shell.SHBrowseForFolder() fname = shell.SHGetPathFromIDList(pidl) except Exception, e: print "Failed to get directory: %s" % e return None return str(fname) def os_version_string(self): import win32api vers = { 4: "Win2k", 5: "WinXP", 6: "WinVista/7", } (pform, sub, build, _, _) = win32api.GetVersionEx() return vers.get(pform, "Win32 (Unknown %i.%i:%i)" % (pform, sub, build)) def _get_platform(basepath): if os.name == "nt": return Win32Platform(basepath) else: return UnixPlatform(basepath) PLATFORM = None def get_platform(basepath=None): """Return the platform singleton""" global PLATFORM if not PLATFORM: PLATFORM = _get_platform(basepath) return PLATFORM def _do_test(): __pform = get_platform() print "Config dir: %s" % __pform.config_dir() print "Default dir: %s" % __pform.default_dir() print "Log file (foo): %s" % __pform.log_file("foo") print "Serial ports: %s" % __pform.list_serial_ports() print "OS Version: %s" % __pform.os_version_string() #__pform.open_text_file("d-rats.py") #print "Open file: %s" % __pform.gui_open_file() #print "Save file: %s" % __pform.gui_save_file(default_name="Foo.txt") print "Open folder: %s" % __pform.gui_select_dir("/tmp") if __name__ == "__main__": _do_test() chirp-0.3.1/chirp/ft50.py0000644000016100007500000000334611717655313014630 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, yaesu_clone, ft50_ll, directory # Not working, don't register #@directory.register class FT50Radio(yaesu_clone.YaesuCloneModeRadio): BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "FT-50" _memsize = 3723 _block_lengths = [10, 16, 112, 16, 16, 1776, 1776, 1] _block_delay = 0.15 def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 100) rf.has_dtcs_polarity = False rf.has_bank = False rf.valid_modes = [ "FM", "WFM", "AM" ] return rf def _update_checksum(self): ft50_ll.update_checksum(self._mmap) def get_raw_memory(self, number): return ft50_ll.get_raw_memory(self._mmap, number) def get_memory(self, number): return ft50_ll.get_memory(self._mmap, number) def set_memory(self, number): return ft50_ll.set_memory(self._mmap, number) def erase_memory(self, number): return ft50_ll.erase_memory(self._mmap, number) def filter_name(self, name): return name[:4].upper() chirp-0.3.1/chirp/thd72.py0000644000016101777760000003640512120543516016433 0ustar jenkinsnogroup00000000000000# Copyright 2010 Vernon Mauery # # 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 chirp import chirp_common, errors, util, directory from chirp import bitwise, memmap import time, struct, sys DEBUG = True # TH-D72 memory map # 0x0000..0x0200: startup password and other stuff # 0x0200..0x0400: current channel and other settings # 0x244,0x246: last menu numbers # 0x249: last f menu number # 0x0400..0x0c00: APRS settings and likely other settings # 0x0c00..0x1500: memory channel flags # 0x1500..0x5380: 0-999 channels # 0x5380..0x54c0: 0-9 scan channels # 0x54c0..0x5560: 0-9 wx channels # 0x5560..0x5e00: ? # 0x5e00..0x7d40: 0-999 channel names # 0x7d40..0x7de0: ? # 0x7de0..0x7e30: wx channel names # 0x7e30..0x7ed0: ? # 0x7ed0..0x7f20: group names # 0x7f20..0x8b00: ? # 0x8b00..0x9c00: last 20 APRS entries # 0x9c00..0xe500: ? # 0xe500..0xe7d0: startup bitmap # 0xe7d0..0xe800: startup bitmap filename # 0xe800..0xead0: gps-logger bitmap # 0xe8d0..0xeb00: gps-logger bipmap filename # 0xeb00..0xff00: ? # 0xff00..0xffff: stuff? # memory channel # 0 1 2 3 4 5 6 7 8 9 a b c d e f # [freq ] ? mode tmode/duplex rtone ctone dtcs cross_mode [offset] ? mem_format = """ #seekto 0x0000; struct { ul16 version; u8 shouldbe32; u8 efs[11]; u8 unknown0[3]; u8 radio_custom_image; u8 gps_custom_image; u8 unknown1[7]; u8 passwd[6]; } frontmatter; #seekto 0x0c00; struct { u8 disabled:7, unknown0:1; u8 skip; } flag[1032]; #seekto 0x1500; struct { ul32 freq; u8 unknown1; u8 mode; u8 tone_mode:4, duplex:4; u8 rtone; u8 ctone; u8 dtcs; u8 cross_mode; ul32 offset; u8 unknown2; } memory[1032]; #seekto 0x5e00; struct { char name[8]; } channel_name[1000]; #seekto 0x7de0; struct { char name[8]; } wx_name[10]; #seekto 0x7ed0; struct { char name[8]; } group_name[10]; """ THD72_SPECIAL = {} for i in range(0, 10): THD72_SPECIAL["L%i" % i] = 1000 + (i * 2) THD72_SPECIAL["U%i" % i] = 1000 + (i * 2) + 1 for i in range(0, 10): THD72_SPECIAL["WX%i" % (i + 1)] = 1020 + i THD72_SPECIAL["C VHF"] = 1030 THD72_SPECIAL["C UHF"] = 1031 THD72_SPECIAL_REV = {} for k,v in THD72_SPECIAL.items(): THD72_SPECIAL_REV[v] = k TMODES = { 0x08 : "Tone", 0x04 : "TSQL", 0x02 : "DTCS", 0x01 : "Cross", 0x00 : "", } TMODES_REV = { "" : 0x00, "Cross" : 0x01, "DTCS" : 0x02, "TSQL" : 0x04, "Tone" : 0x08, } MODES = { 0x00 : "FM", 0x01 : "NFM", 0x02 : "AM", } MODES_REV = { "FM" : 0x00, "NFM": 0x01, "AM" : 0x2, } DUPLEX = { 0x00 : "", 0x01 : "+", 0x02 : "-", 0x04 : "split", } DUPLEX_REV = { "" : 0x00, "+" : 0x01, "-" : 0x02, "split" : 0x04, } EXCH_R = "R\x00\x00\x00\x00" EXCH_W = "W\x00\x00\x00\x00" # Uploads result in "MCP Error" and garbage data in memory # Clone driver disabled in favor of error-checking live driver. @directory.register class THD72Radio(chirp_common.CloneModeRadio): BAUD_RATE = 9600 VENDOR = "Kenwood" MODEL = "TH-D72 (clone mode)" HARDWARE_FLOW = sys.platform == "darwin" # only OS X driver needs hw flow mem_upper_limit = 1022 _memsize = 65536 _model = "" # FIXME: REMOVE _dirty_blocks = [] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 1031) rf.valid_bands = [(118000000, 174000000), (320000000, 524000000)] rf.has_cross = True rf.can_odd_split = True rf.has_dtcs_polarity = False rf.has_tuning_step = False rf.has_bank = False rf.valid_tuning_steps = [] rf.valid_modes = MODES_REV.keys() rf.valid_tmodes = TMODES_REV.keys() rf.valid_duplexes = DUPLEX_REV.keys() rf.valid_skips = ["", "S"] rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC rf.valid_name_length = 8 return rf def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) self._dirty_blocks = [] def _detect_baud(self): for baud in [9600, 19200, 38400, 57600]: self.pipe.setBaudrate(baud) try: self.pipe.write("\r\r") except: break self.pipe.read(32) try: id = self.get_id() print "Radio %s at %i baud" % (id, baud) return True except errors.RadioError: pass raise errors.RadioError("No response from radio") def get_special_locations(self): return sorted(THD72_SPECIAL.keys()) def add_dirty_block(self, memobj): block = memobj._offset / 256 if block not in self._dirty_blocks: self._dirty_blocks.append(block) self._dirty_blocks.sort() print "dirty blocks:", self._dirty_blocks def get_channel_name(self, number): if number < 999: name = str(self._memobj.channel_name[number].name) + '\xff' elif number >= 1020 and number < 1030: number -= 1020 name = str(self._memobj.wx_name[number].name) + '\xff' else: return '' return name[:name.index('\xff')].rstrip() def set_channel_name(self, number, name): name = name[:8] + '\xff'*8 if number < 999: self._memobj.channel_name[number].name = name[:8] self.add_dirty_block(self._memobj.channel_name[number]) elif number >= 1020 and number < 1030: number -= 1020 self._memobj.wx_name[number].name = name[:8] self.add_dirty_block(self._memobj.wx_name[number]) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) + \ repr(self._memobj.flag[(number)]) def get_memory(self, number): if isinstance(number, str): try: number = THD72_SPECIAL[number] except KeyError: raise errors.InvalidMemoryLocation("Unknown channel %s" % \ number) if number < 0 or number > (max(THD72_SPECIAL.values()) + 1): raise errors.InvalidMemoryLocation("Number must be between 0 and 999") _mem = self._memobj.memory[number] flag = self._memobj.flag[number] mem = chirp_common.Memory() mem.number = number if number > 999: mem.extd_number = THD72_SPECIAL_REV[number] if flag.disabled == 0x7f: mem.empty = True return mem mem.name = self.get_channel_name(number) mem.freq = int(_mem.freq) mem.tmode = TMODES[int(_mem.tone_mode)] mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.duplex = DUPLEX[int(_mem.duplex)] mem.offset = int(_mem.offset) mem.mode = MODES[int(_mem.mode)] if number < 999: mem.skip = chirp_common.SKIP_VALUES[int(flag.skip)] mem.cross_mode = chirp_common.CROSS_MODES[_mem.cross_mode] if number > 999: mem.cross_mode = chirp_common.CROSS_MODES[0] mem.immutable = ["number", "bank", "extd_number", "cross_mode"] if number >= 1020 and number < 1030: mem.immutable += ["freq", "offset", "tone", "mode", "tmode", "ctone", "skip"] # FIXME: ALL else: mem.immutable += ["name"] return mem def set_memory(self, mem): print "set_memory(%d)"%mem.number if mem.number < 0 or mem.number > (max(THD72_SPECIAL.values()) + 1): raise errors.InvalidMemoryLocation("Number must be between 0 and 999") # weather channels can only change name, nothing else if mem.number >= 1020 and mem.number < 1030: self.set_channel_name(mem.number, mem.name) return flag = self._memobj.flag[mem.number] self.add_dirty_block(self._memobj.flag[mem.number]) # only delete non-WX channels was_empty = flag.disabled == 0x7f if mem.empty: flag.disabled = 0x7f return flag.disabled = 0 _mem = self._memobj.memory[mem.number] self.add_dirty_block(_mem) if was_empty: self.initialize(_mem) _mem.freq = mem.freq if mem.number < 999: self.set_channel_name(mem.number, mem.name) _mem.tone_mode = TMODES_REV[mem.tmode] _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.cross_mode = chirp_common.CROSS_MODES.index(mem.cross_mode) _mem.duplex = DUPLEX_REV[mem.duplex] _mem.offset = mem.offset _mem.mode = MODES_REV[mem.mode] if mem.number < 999: flag.skip = chirp_common.SKIP_VALUES.index(mem.skip) def sync_in(self): self._detect_baud() self._mmap = self.download() self.process_mmap() def sync_out(self): self._detect_baud() if len(self._dirty_blocks): self.upload(self._dirty_blocks) else: self.upload() def read_block(self, block, count=256): self.pipe.write(struct.pack("D72: %s" % cmd self.pipe.write(cmd + "\r") while not data.endswith("\r") and (time.time() - start) < timeout: data += self.pipe.read(1) if DEBUG: print "D72->PC: %s" % data.strip() return data.strip() def get_id(self): r = self.command("ID") if r.startswith("ID "): return r.split(" ")[1] else: raise errors.RadioError("No response to ID command") def initialize(self, mmap): mmap[0] = \ "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \ "\x08\x00\xc0\x27\x09\x00\x00\xff" if __name__ == "__main__": import sys import serial import detect import getopt def fixopts(opts): r = {} for opt in opts: k,v = opt r[k] = v return r def usage(): print "Usage: %s <-i input.img>|<-o output.img> -p port [[-f first-addr] [-l last-addr] | [-b list,of,blocks]]" % sys.argv[0] sys.exit(1) opts, args = getopt.getopt(sys.argv[1:], "i:o:p:f:l:b:") opts = fixopts(opts) first = last = 0 blocks = None if '-i' in opts: fname = opts['-i'] download = False elif '-o' in opts: fname = opts['-o'] download = True else: usage() if '-p' in opts: port = opts['-p'] else: usage() if '-f' in opts: first = int(opts['-f'],0) if '-l' in opts: last = int(opts['-l'],0) if '-b' in opts: blocks = [int(b, 0) for b in opts['-b'].split(',')] blocks.sort() ser = serial.Serial(port=port, baudrate=9600, timeout=0.25) r = THD72Radio(ser) memmax = r._memsize if not download: memmax -= 512 if blocks is None: if first < 0 or first > (r._memsize - 1): raise errors.RadioError("first address out of range") if (last > 0 and last < first) or last > memmax: raise errors.RadioError("last address out of range") elif last == 0: last = memmax first /= 256 if last % 256 != 0: last += 256 last /= 256 blocks = range(first, last) if download: data = r.download(True, blocks) file(fname, "wb").write(data) else: r._mmap = file(fname, "rb").read(r._memsize) r.upload(blocks) print "\nDone" chirp-0.3.1/chirp/ic9x.py0000644000016100007500000003135612023560645014722 0ustar jenkins00000000000000# Copyright 2008 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 . import time import threading from chirp import chirp_common, errors, ic9x_ll, icf, util, directory from chirp import bitwise IC9XA_SPECIAL = {} IC9XB_SPECIAL = {} for i in range(0, 25): idA = "%iA" % i idB = "%iB" % i Anum = 800 + i * 2 Bnum = 400 + i * 2 IC9XA_SPECIAL[idA] = Anum IC9XA_SPECIAL[idB] = Bnum IC9XB_SPECIAL[idA] = Bnum IC9XB_SPECIAL[idB] = Bnum + 1 IC9XA_SPECIAL["C0"] = IC9XB_SPECIAL["C0"] = -1 IC9XA_SPECIAL["C1"] = IC9XB_SPECIAL["C1"] = -2 IC9X_SPECIAL = { 0 : {}, 1 : IC9XA_SPECIAL, 2 : IC9XB_SPECIAL, } CHARSET = chirp_common.CHARSET_ALPHANUMERIC + \ "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~" LOCK = threading.Lock() class IC9xBank(icf.IcomNamedBank): """Icom 9x Bank""" def get_name(self): banks = self._model._radio._ic9x_get_banks() return banks[self.index] def set_name(self, name): banks = self._model._radio._ic9x_get_banks() banks[self.index] = name self._model._radio._ic9x_set_banks(banks) @directory.register class IC9xRadio(icf.IcomLiveRadio): """Base class for Icom IC-9x radios""" MODEL = "IC-91/92AD" _model = "ic9x" # Fake model info for detect.py vfo = 0 __last = 0 _upper = 300 _num_banks = 26 _bank_class = IC9xBank def _get_bank(self, loc): mem = self.get_memory(loc) return mem._bank def _set_bank(self, loc, bank): mem = self.get_memory(loc) mem._bank = bank self.set_memory(mem) def _get_bank_index(self, loc): mem = self.get_memory(loc) return mem._bank_index def _set_bank_index(self, loc, index): mem = self.get_memory(loc) mem._bank_index = index self.set_memory(mem) def __init__(self, *args, **kwargs): icf.IcomLiveRadio.__init__(self, *args, **kwargs) if self.pipe: self.pipe.setTimeout(0.1) self.__memcache = {} self.__bankcache = {} global LOCK self._lock = LOCK def _maybe_send_magic(self): if (time.time() - self.__last) > 1: print "Sending magic" ic9x_ll.send_magic(self.pipe) self.__last = time.time() def get_memory(self, number): if isinstance(number, str): try: number = IC9X_SPECIAL[self.vfo][number] except KeyError: raise errors.InvalidMemoryLocation("Unknown channel %s" % \ number) if number < -2 or number > 999: raise errors.InvalidValueError("Number must be between 0 and 999") if self.__memcache.has_key(number): return self.__memcache[number] self._lock.acquire() try: self._maybe_send_magic() mem = ic9x_ll.get_memory(self.pipe, self.vfo, number) except errors.InvalidMemoryLocation: mem = chirp_common.Memory() mem.number = number if number < self._upper: mem.empty = True except: self._lock.release() raise self._lock.release() if number > self._upper or number < 0: mem.extd_number = util.get_dict_rev(IC9X_SPECIAL, [self.vfo][number]) mem.immutable = ["number", "skip", "bank", "bank_index", "extd_number"] self.__memcache[mem.number] = mem return mem def get_raw_memory(self, number): self._lock.acquire() try: ic9x_ll.send_magic(self.pipe) mframe = ic9x_ll.get_memory_frame(self.pipe, self.vfo, number) except: self._lock.release() raise self._lock.release() return repr(bitwise.parse(ic9x_ll.MEMORY_FRAME_FORMAT, mframe)) def get_memories(self, lo=0, hi=None): if hi is None: hi = self._upper memories = [] for i in range(lo, hi + 1): try: print "Getting %i" % i mem = self.get_memory(i) if mem: memories.append(mem) print "Done: %s" % mem except errors.InvalidMemoryLocation: pass except errors.InvalidDataError, e: print "Error talking to radio: %s" % e break return memories def set_memory(self, _memory): # Make sure we mirror the DV-ness of the new memory we're # setting, and that we capture the Bank value of any currently # stored memory (unless the special type is provided) and # communicate that to the low-level routines with the special # subclass if isinstance(_memory, ic9x_ll.IC9xMemory) or \ isinstance(_memory, ic9x_ll.IC9xDVMemory): memory = _memory else: if isinstance(_memory, chirp_common.DVMemory): memory = ic9x_ll.IC9xDVMemory() memory.clone(self.get_memory(_memory.number)) else: memory = ic9x_ll.IC9xMemory() memory.clone(self.get_memory(_memory.number)) memory.clone(_memory) self._lock.acquire() self._maybe_send_magic() try: if memory.empty: ic9x_ll.erase_memory(self.pipe, self.vfo, memory.number) else: ic9x_ll.set_memory(self.pipe, self.vfo, memory) memory = ic9x_ll.get_memory(self.pipe, self.vfo, memory.number) except: self._lock.release() raise self._lock.release() self.__memcache[memory.number] = memory def _ic9x_get_banks(self): if len(self.__bankcache.keys()) == 26: return [self.__bankcache[k] for k in sorted(self.__bankcache.keys())] self._lock.acquire() try: self._maybe_send_magic() banks = ic9x_ll.get_banks(self.pipe, self.vfo) except: self._lock.release() raise self._lock.release() i = 0 for bank in banks: self.__bankcache[i] = bank i += 1 return banks def _ic9x_set_banks(self, banks): if len(banks) != len(self.__bankcache.keys()): raise errors.InvalidDataError("Invalid bank list length (%i:%i)" %\ (len(banks), len(self.__bankcache.keys()))) cached_names = [str(self.__bankcache[x]) \ for x in sorted(self.__bankcache.keys())] need_update = False for i in range(0, 26): if banks[i] != cached_names[i]: need_update = True self.__bankcache[i] = banks[i] print "Updating %s: %s -> %s" % (chr(i + ord("A")), cached_names[i], banks[i]) if need_update: self._lock.acquire() try: self._maybe_send_magic() ic9x_ll.set_banks(self.pipe, self.vfo, banks) except: self._lock.release() raise self._lock.release() def get_sub_devices(self): return [IC9xRadioA(self.pipe), IC9xRadioB(self.pipe)] def get_features(self): rf = chirp_common.RadioFeatures() rf.has_sub_devices = True rf.valid_special_chans = IC9X_SPECIAL[self.vfo].keys() return rf class IC9xRadioA(IC9xRadio): """IC9x Band A subdevice""" VARIANT = "Band A" vfo = 1 _upper = 849 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = True rf.has_bank_index = True rf.has_bank_names = True rf.memory_bounds = (0, self._upper) rf.valid_modes = ["FM", "WFM", "AM"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = ["", "-", "+"] rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS) rf.valid_bands = [(500000, 9990000000)] rf.valid_skips = ["", "S", "P"] rf.valid_characters = CHARSET rf.valid_name_length = 8 return rf class IC9xRadioB(IC9xRadio, chirp_common.IcomDstarSupport): """IC9x Band B subdevice""" VARIANT = "Band B" vfo = 2 _upper = 399 MYCALL_LIMIT = (1, 7) URCALL_LIMIT = (1, 61) RPTCALL_LIMIT = (1, 61) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = True rf.has_bank_index = True rf.has_bank_names = True rf.requires_call_lists = False rf.memory_bounds = (0, self._upper) rf.valid_modes = ["FM", "NFM", "AM", "DV"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = ["", "-", "+"] rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS) rf.valid_bands = [(118000000, 174000000), (350000000, 470000000)] rf.valid_skips = ["", "S", "P"] rf.valid_characters = CHARSET rf.valid_name_length = 8 return rf def __init__(self, *args, **kwargs): IC9xRadio.__init__(self, *args, **kwargs) self.__rcalls = [] self.__mcalls = [] self.__ucalls = [] def __get_call_list(self, cache, cstype, ulimit): if cache: return cache calls = [] self._maybe_send_magic() for i in range(ulimit - 1): call = ic9x_ll.get_call(self.pipe, cstype, i+1) calls.append(call) return calls def __set_call_list(self, cache, cstype, ulimit, calls): for i in range(ulimit - 1): blank = " " * 8 try: acall = cache[i] except IndexError: acall = blank try: bcall = calls[i] except IndexError: bcall = blank if acall == bcall: continue # No change to this one self._maybe_send_magic() ic9x_ll.set_call(self.pipe, cstype, i+1, calls[i]) return calls def get_mycall_list(self): self.__mcalls = self.__get_call_list(self.__mcalls, ic9x_ll.IC92MyCallsignFrame, self.MYCALL_LIMIT[1]) return self.__mcalls def get_urcall_list(self): self.__ucalls = self.__get_call_list(self.__ucalls, ic9x_ll.IC92YourCallsignFrame, self.URCALL_LIMIT[1]) return self.__ucalls def get_repeater_call_list(self): self.__rcalls = self.__get_call_list(self.__rcalls, ic9x_ll.IC92RepeaterCallsignFrame, self.RPTCALL_LIMIT[1]) return self.__rcalls def set_mycall_list(self, calls): self.__mcalls = self.__set_call_list(self.__mcalls, ic9x_ll.IC92MyCallsignFrame, self.MYCALL_LIMIT[1], calls) def set_urcall_list(self, calls): self.__ucalls = self.__set_call_list(self.__ucalls, ic9x_ll.IC92YourCallsignFrame, self.URCALL_LIMIT[1], calls) def set_repeater_call_list(self, calls): self.__rcalls = self.__set_call_list(self.__rcalls, ic9x_ll.IC92RepeaterCallsignFrame, self.RPTCALL_LIMIT[1], calls) def _test(): import serial ser = IC9xRadioB(serial.Serial(port="/dev/ttyUSB1", baudrate=38400, timeout=0.1)) print ser.get_urcall_list() print "-- FOO --" ser.set_urcall_list(["K7TAY", "FOOBAR", "BAZ"]) if __name__ == "__main__": _test() chirp-0.3.1/chirp/ict70.py0000644000016101777760000001421012130403635016415 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . from chirp import chirp_common, icf, directory from chirp import bitwise MEM_FORMAT = """ struct { u24 freq; ul16 offset; char name[6]; u8 unknown2:2, rtone:6; u8 unknown3:2, ctone:6; u8 unknown4:1, dtcs:7; u8 tuning_step:4, narrow:1, unknown5:1, duplex:2; u8 unknown6:1, power:2, dtcs_polarity:2, tmode:3; } memory[300]; #seekto 0x12E0; u8 used[38]; #seekto 0x1306; u8 skips[38]; #seekto 0x132C; u8 pskips[38]; #seekto 0x1360; struct { u8 bank; u8 index; } banks[300]; #seekto 0x16D0; struct { char name[6]; } bank_names[26]; """ TMODES = ["", "Tone", "TSQL", "TSQL", "DTCS", "DTCS"] DUPLEX = ["", "-", "+"] DTCS_POLARITY = ["NN", "NR", "RN", "RR"] TUNING_STEPS = [5.0, 5.0, 5.0, 5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 200.0] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5), chirp_common.PowerLevel("Low", watts=0.5), chirp_common.PowerLevel("Mid", watts=1.0), ] class ICT70Bank(icf.IcomBank): """ICT70 bank""" def get_name(self): _bank = self._model._radio._memobj.bank_names[self.index] return str(_bank.name).rstrip() def set_name(self, name): _bank = self._model._radio._memobj.bank_names[self.index] _bank.name = name.ljust(8)[:8] @directory.register class ICT70Radio(icf.IcomCloneModeRadio): """Icom IC-T70""" VENDOR = "Icom" MODEL = "IC-T70" _model = "\x32\x53\x00\x01" _memsize = 0x19E0 _endframe = "Icom Inc\x2eCF" _ranges = [(0x0000, 0x19E0, 32)] _num_banks = 26 _bank_class = ICT70Bank def _get_bank(self, loc): _bank = self._memobj.banks[loc] if _bank.bank != 0xFF: return _bank.bank else: return None def _set_bank(self, loc, bank): _bank = self._memobj.banks[loc] if bank is None: _bank.bank = 0xFF else: _bank.bank = bank def _get_bank_index(self, loc): _bank = self._memobj.banks[loc] return _bank.index def _set_bank_index(self, loc, index): _bank = self._memobj.banks[loc] _bank.index = index def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 299) rf.valid_tmodes = TMODES rf.valid_duplexes = DUPLEX rf.valid_power_levels = POWER_LEVELS rf.valid_modes = ["FM", "NFM"] rf.valid_bands = [(136000000, 174000000), (400000000, 479000000)] rf.valid_skips = ["", "S", "P"] rf.valid_tuning_steps = TUNING_STEPS rf.valid_name_length = 6 rf.has_ctone = True rf.has_bank = True rf.has_bank_index = True rf.has_bank_names = True return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): bit = 1 << (number % 8) byte = int(number / 8) _mem = self._memobj.memory[number] _usd = self._memobj.used[byte] _skp = self._memobj.skips[byte] _psk = self._memobj.pskips[byte] mem = chirp_common.Memory() mem.number = number if _usd & bit: mem.empty = True return mem if _mem.freq & 0x800000: mem.freq = (_mem.freq & ~0x800000) * 6250 else: mem.freq = _mem.freq * 5000 mem.offset = _mem.offset * 5000 mem.name = str(_mem.name).rstrip() mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.tuning_step = TUNING_STEPS[_mem.tuning_step] mem.mode = _mem.narrow and "NFM" or "FM" mem.duplex = DUPLEX[_mem.duplex] mem.power = POWER_LEVELS[_mem.power] mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity] mem.tmode = TMODES[_mem.tmode] mem.skip = (_psk & bit and "P") or (_skp & bit and "S") or "" return mem def set_memory(self, mem): bit = 1 << (mem.number % 8) byte = int(mem.number / 8) _mem = self._memobj.memory[mem.number] _usd = self._memobj.used[byte] _skp = self._memobj.skips[byte] _psk = self._memobj.pskips[byte] _mem.set_raw("\x00" * (_mem.size() / 8)) if mem.empty: _usd |= bit return _usd &= ~bit if chirp_common.is_12_5(mem.freq): _mem.freq = (mem.freq / 6250) | 0x800000 else: _mem.freq = mem.freq / 5000 _mem.offset = mem.offset / 5000 _mem.name = mem.name.ljust(6)[:6] _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) _mem.narrow = mem.mode == "NFM" _mem.duplex = DUPLEX.index(mem.duplex) _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity) _mem.tmode = TMODES.index(mem.tmode) if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 if mem.skip == "S": _skp |= bit _psk &= ~bit elif mem.skip == "P": _skp &= ~bit _psk |= bit else: _skp &= ~bit _psk &= ~bit chirp-0.3.1/chirp/ic208.py0000644000016101777760000001674412071506071016334 0ustar jenkinsnogroup00000000000000# 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 . from chirp import chirp_common, icf, errors, directory from chirp import bitwise MEM_FORMAT = """ struct memory { u24 freq; u16 offset; u8 power:2, rtone:6; u8 duplex:2, ctone:6; u8 unknown1:1, dtcs:7; u8 tuning_step:4, unknown2:4; u8 unknown3; u8 alt_mult:1, unknown4:1, is_fm:1, is_wide:1, unknown5:2, tmode:2; u16 dtcs_polarity:2, usealpha:1, empty:1, name1:6, name2:6; u24 name3:6, name4:6, name5:6, name6:6; }; struct memory memory[510]; struct { u8 unknown1:1, empty:1, pskip:1, skip:1, bank:4; } flags[512]; struct memory call[2]; """ MODES = ["AM", "FM", "NFM", "NAM"] TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "", "-", "+"] DTCS_POL = ["NN", "NR", "RN", "RR"] STEPS = [5.0, 10.0, 12.5, 15, 20.0, 25.0, 30.0, 50.0, 100.0, 200.0] POWER = [chirp_common.PowerLevel("High", watts=50), chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=15), ] IC208_SPECIAL = [] for i in range(1, 6): IC208_SPECIAL.append("%iA" % i) IC208_SPECIAL.append("%iB" % i) ALPHA_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ" NUMERIC_CHARSET = "0123456789+-=*/()|" def get_name(_mem): """Decode the name from @_mem""" def _get_char(val): if val == 0: return " " elif val & 0x20: return ALPHA_CHARSET[val & 0x1F] else: return NUMERIC_CHARSET[val & 0x0F] name_bytes = [_mem.name1, _mem.name2, _mem.name3, _mem.name4, _mem.name5, _mem.name6] name = "" for val in name_bytes: name += _get_char(val) return name.rstrip() def set_name(_mem, name): """Encode @name in @_mem""" def _get_index(char): if char == " ": return 0 elif char.isalpha(): return ALPHA_CHARSET.index(char) | 0x20 else: return NUMERIC_CHARSET.index(char) | 0x10 name = name.ljust(6)[:6] _mem.usealpha = bool(name.strip()) # The element override calling convention makes this harder to automate. # It's just six, so do it manually _mem.name1 = _get_index(name[0]) _mem.name2 = _get_index(name[1]) _mem.name3 = _get_index(name[2]) _mem.name4 = _get_index(name[3]) _mem.name5 = _get_index(name[4]) _mem.name6 = _get_index(name[5]) @directory.register class IC208Radio(icf.IcomCloneModeRadio): """Icom IC800""" VENDOR = "Icom" MODEL = "IC-208H" _model = "\x26\x32\x00\x01" _memsize = 0x2600 _endframe = "Icom Inc\x2e30" _can_hispeed = True _memories = [] _ranges = [(0x0000, 0x2600, 32)] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 500) rf.has_bank = True rf.valid_tuning_steps = list(STEPS) rf.valid_tmodes = list(TMODES) rf.valid_modes = list(MODES) rf.valid_duplexes = list(DUPLEX) rf.valid_power_levels = list(POWER) rf.valid_skips = ["", "S", "P"] rf.valid_bands = [(118000000, 173995000), (230000000, 549995000), (810000000, 999990000)] rf.valid_special_chans = ["C1", "C2"] + sorted(IC208_SPECIAL) return rf def get_raw_memory(self, number): _mem, _flg, index = self._get_memory(number) return repr(_mem) + repr(_flg) def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def _get_bank(self, loc): _flg = self._memobj.flags[loc-1] if _flg.bank >= 0x0A: return None else: return _flg.bank def _set_bank(self, loc, bank): _flg = self._memobj.flags[loc-1] if bank is None: _flg.bank = 0x0A else: _flg.bank = bank def _get_memory(self, number): if isinstance(number, str): if "A" in number or "B" in number: index = 501 + IC208_SPECIAL.index(number) _mem = self._memobj.memory[index - 1] _flg = self._memobj.flags[index - 1] else: index = int(number[1]) - 1 _mem = self._memobj.call[index] _flg = self._memobj.flags[510 + index] index = index + -10 elif number <= 0: index = 10 - abs(number) _mem = self._memobj.call[index] _flg = self._memobj.flags[index + 510] else: index = number _mem = self._memobj.memory[number - 1] _flg = self._memobj.flags[number - 1] return _mem, _flg, index def get_memory(self, number): _mem, _flg, index = self._get_memory(number) mem = chirp_common.Memory() mem.number = index if isinstance(number, str): mem.extd_number = number else: mem.skip = _flg.pskip and "P" or _flg.skip and "S" or "" if _flg.empty: mem.empty = True return mem mult = _mem.alt_mult and 6250 or 5000 mem.freq = int(_mem.freq) * mult mem.offset = int(_mem.offset) * 5000 mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCS_POL[_mem.dtcs_polarity] mem.duplex = DUPLEX[_mem.duplex] mem.tmode = TMODES[_mem.tmode] mem.mode = ((not _mem.is_wide and "N" or "") + (_mem.is_fm and "FM" or "AM")) mem.tuning_step = STEPS[_mem.tuning_step] mem.name = get_name(_mem) mem.power = POWER[_mem.power] return mem def set_memory(self, mem): _mem, _flg, index = self._get_memory(mem.number) if mem.empty: _flg.empty = True self._set_bank(mem.number, None) return if _flg.empty: _mem.set_raw("\x00" * 16) _flg.empty = False _mem.alt_mult = chirp_common.is_fractional_step(mem.freq) _mem.freq = mem.freq / (_mem.alt_mult and 6250 or 5000) _mem.offset = mem.offset / 5000 _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.dtcs_polarity = DTCS_POL.index(mem.dtcs_polarity) _mem.duplex = DUPLEX.index(mem.duplex) _mem.tmode = TMODES.index(mem.tmode) _mem.is_fm = "FM" in mem.mode _mem.is_wide = mem.mode[0] != "N" _mem.tuning_step = STEPS.index(mem.tuning_step) set_name(_mem, mem.name) try: _mem.power = POWER.index(mem.power) except Exception: pass if not isinstance(mem.number, str): _flg.skip = mem.skip == "S" _flg.pskip = mem.skip == "P" chirp-0.3.1/chirp/id800.py0000644000016100007500000002531312025023645014662 0ustar jenkins00000000000000# Copyright 2008 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 . from chirp import chirp_common, icf, errors, directory from chirp import bitwise MEM_FORMAT = """ #seekto 0x0020; struct { u24 freq; u16 offset; u8 unknown0:2, rtone:6; u8 duplex:2, ctone:6; u8 dtcs; u8 tuning_step:4, unknown1:4; u8 unknown2; u8 mult_flag:1, unknown3:5, tmode:2; u16 dtcs_polarity:2, usealpha:1, empty:1, name1:6, name2:6; u24 name3:6, name4:6, name5:6, name6:6; u8 unknown5; u8 unknown6:1, digital_code:7; u8 urcall; u8 rpt1call; u8 rpt2call; u8 unknown7:1, mode:3, unknown8:4; } memory[500]; #seekto 0x2BF4; struct { u8 unknown1:1, empty:1, pskip:1, skip:1, bank:4; } flags[500]; #seekto 0x3220; struct { char call[8]; } mycalls[8]; #seekto 0x3250; struct { char call[8]; } urcalls[99]; #seekto 0x3570; struct { char call[8]; } rptcalls[59]; """ MODES = ["FM", "NFM", "AM", "NAM", "DV"] TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "", "-", "+"] DTCS_POL = ["NN", "NR", "RN", "RR"] STEPS = [5.0, 10.0, 12.5, 15, 20.0, 25.0, 30.0, 50.0, 100.0, 200.0, 6.25] ID800_SPECIAL = { "C2" : 510, "C1" : 511, } ID800_SPECIAL_REV = { 510 : "C2", 511 : "C1", } for i in range(0, 5): idA = "%iA" % (i + 1) idB = "%iB" % (i + 1) num = 500 + i * 2 ID800_SPECIAL[idA] = num ID800_SPECIAL[idB] = num + 1 ID800_SPECIAL_REV[num] = idA ID800_SPECIAL_REV[num+1] = idB ALPHA_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ" NUMERIC_CHARSET = "0123456789+-=*/()|" def get_name(_mem): """Decode the name from @_mem""" def _get_char(val): if val == 0: return " " elif val & 0x20: return ALPHA_CHARSET[val & 0x1F] else: return NUMERIC_CHARSET[val & 0x0F] name_bytes = [_mem.name1, _mem.name2, _mem.name3, _mem.name4, _mem.name5, _mem.name6] name = "" for val in name_bytes: name += _get_char(val) return name.rstrip() def set_name(_mem, name): """Encode @name in @_mem""" def _get_index(char): if char == " ": return 0 elif char.isalpha(): return ALPHA_CHARSET.index(char) | 0x20 else: return NUMERIC_CHARSET.index(char) | 0x10 name = name.ljust(6)[:6] _mem.usealpha = bool(name.strip()) # The element override calling convention makes this harder to automate. # It's just six, so do it manually _mem.name1 = _get_index(name[0]) _mem.name2 = _get_index(name[1]) _mem.name3 = _get_index(name[2]) _mem.name4 = _get_index(name[3]) _mem.name5 = _get_index(name[4]) _mem.name6 = _get_index(name[5]) @directory.register class ID800v2Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom ID800""" VENDOR = "Icom" MODEL = "ID-800H" VARIANT = "v2" _model = "\x27\x88\x02\x00" _memsize = 14528 _endframe = "Icom Inc\x2eCB" _can_hispeed = True _memories = [] _ranges = [(0x0020, 0x2B18, 32), (0x2B18, 0x2B20, 8), (0x2B20, 0x2BE0, 32), (0x2BE0, 0x2BF4, 20), (0x2BF4, 0x2C00, 12), (0x2C00, 0x2DE0, 32), (0x2DE0, 0x2DF4, 20), (0x2DF4, 0x2E00, 12), (0x2E00, 0x2E20, 32), (0x2F00, 0x3070, 32), (0x30D0, 0x30E0, 16), (0x30E0, 0x3160, 32), (0x3160, 0x3180, 16), (0x3180, 0x31A0, 32), (0x31A0, 0x31B0, 16), (0x3220, 0x3240, 32), (0x3240, 0x3260, 16), (0x3260, 0x3560, 32), (0x3560, 0x3580, 16), (0x3580, 0x3720, 32), (0x3720, 0x3780, 8), (0x3798, 0x37A0, 8), (0x37A0, 0x37B0, 16), (0x37B0, 0x37B1, 1), (0x37D8, 0x37E0, 8), (0x37E0, 0x3898, 32), (0x3898, 0x389A, 2), (0x38A8, 0x38C0, 16),] MYCALL_LIMIT = (1, 7) URCALL_LIMIT = (1, 99) RPTCALL_LIMIT = (1, 59) def _get_bank(self, loc): _flg = self._memobj.flags[loc-1] if _flg.bank >= 0x0A: return None else: return _flg.bank def _set_bank(self, loc, bank): _flg = self._memobj.flags[loc-1] if bank is None: _flg.bank = 0x0A else: _flg.bank = bank def get_features(self): rf = chirp_common.RadioFeatures() rf.has_implicit_calls = True rf.has_settings = True rf.has_bank = True rf.valid_modes = list(MODES) rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = ["", "-", "+"] rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(118000000, 173995000), (230000000, 549995000), (810000000, 999990000)] rf.valid_skips = ["", "S", "P"] rf.valid_name_length = 6 rf.valid_special_chans = sorted(ID800_SPECIAL.keys()) rf.memory_bounds = (1, 499) return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_memory(self, number): if isinstance(number, str): try: number = ID800_SPECIAL[number] + 1 # Because we subtract below except KeyError: raise errors.InvalidMemoryLocation("Unknown channel %s" % \ number) _mem = self._memobj.memory[number-1] _flg = self._memobj.flags[number-1] if MODES[_mem.mode] == "DV": urcalls = self.get_urcall_list() rptcalls = self.get_repeater_call_list() mem = chirp_common.DVMemory() mem.dv_urcall = urcalls[_mem.urcall] mem.dv_rpt1call = rptcalls[_mem.rpt1call] mem.dv_rpt2call = rptcalls[_mem.rpt2call] mem.dv_code = _mem.digital_code else: mem = chirp_common.Memory() mem.number = number if _flg.empty: mem.empty = True return mem mult = _mem.mult_flag and 6250 or 5000 mem.freq = _mem.freq * mult mem.offset = _mem.offset * 5000 mem.duplex = DUPLEX[_mem.duplex] mem.mode = MODES[_mem.mode] mem.tmode = TMODES[_mem.tmode] mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCS_POL[_mem.dtcs_polarity] mem.tuning_step = STEPS[_mem.tuning_step] mem.name = get_name(_mem) mem.skip = _flg.pskip and "P" or _flg.skip and "S" or "" return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flg = self._memobj.flags[mem.number-1] _flg.empty = mem.empty if mem.empty: self._set_bank(mem.number, None) return mult = chirp_common.is_fractional_step(mem.freq) and 6250 or 5000 _mem.mult_flag = mult == 6250 _mem.freq = mem.freq / mult _mem.offset = mem.offset / 5000 _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.tmode = TMODES.index(mem.tmode) _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.dtcs_polarity = DTCS_POL.index(mem.dtcs_polarity) _mem.tuning_step = STEPS.index(mem.tuning_step) set_name(_mem, mem.name) _flg.pskip = mem.skip == "P" _flg.skip = mem.skip == "S" if mem.mode == "DV": urcalls = self.get_urcall_list() rptcalls = self.get_repeater_call_list() if not isinstance(mem, chirp_common.DVMemory): raise errors.InvalidDataError("DV mode is not a DVMemory!") try: err = mem.dv_urcall _mem.urcall = urcalls.index(mem.dv_urcall) err = mem.dv_rpt1call _mem.rpt1call = rptcalls.index(mem.dv_rpt1call) err = mem.dv_rpt2call _mem.rpt2call = rptcalls.index(mem.dv_rpt2call) except IndexError: raise errors.InvalidDataError("DV Call %s not in list" % err) else: _mem.urcall = 0 _mem.rpt1call = 0 _mem.rpt2call = 0 def sync_in(self): icf.IcomCloneModeRadio.sync_in(self) self.process_mmap() def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def get_urcall_list(self): calls = ["CQCQCQ"] for i in range(*self.URCALL_LIMIT): calls.append(str(self._memobj.urcalls[i-1].call).rstrip()) return calls def get_repeater_call_list(self): calls = ["*NOTUSE*"] for i in range(*self.RPTCALL_LIMIT): calls.append(str(self._memobj.rptcalls[i-1].call).rstrip()) return calls def get_mycall_list(self): calls = [] for i in range(*self.MYCALL_LIMIT): calls.append(str(self._memobj.mycalls[i-1].call).rstrip()) return calls def set_urcall_list(self, calls): for i in range(*self.URCALL_LIMIT): try: call = calls[i].upper() # Skip the implicit CQCQCQ except IndexError: call = " " * 8 self._memobj.urcalls[i-1].call = call.ljust(8)[:8] def set_repeater_call_list(self, calls): for i in range(*self.RPTCALL_LIMIT): try: call = calls[i].upper() # Skip the implicit blank except IndexError: call = " " * 8 self._memobj.rptcalls[i-1].call = call.ljust(8)[:8] def set_mycall_list(self, calls): for i in range(*self.MYCALL_LIMIT): try: call = calls[i-1].upper() except IndexError: call = " " * 8 self._memobj.mycalls[i-1].call = call.ljust(8)[:8] chirp-0.3.1/chirp/detect.py0000644000016101777760000000611512051555045016751 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . import serial from chirp import errors, icf, directory, ic9x_ll from chirp import kenwood_live, icomciv def _icom_model_data_to_rclass(md): for _rtype, rclass in directory.DRV_TO_RADIO.items(): if rclass.VENDOR != "Icom": continue if not hasattr(rclass, 'get_model') or not rclass.get_model(): continue if rclass.get_model()[:4] == md[:4]: return rclass raise errors.RadioError("Unknown radio type %02x%02x%02x%02x" %\ (ord(md[0]), ord(md[1]), ord(md[2]), ord(md[3]))) def _detect_icom_radio(ser): # ICOM VHF/UHF Clone-type radios @ 9600 baud try: ser.setBaudrate(9600) md = icf.get_model_data(ser) return _icom_model_data_to_rclass(md) except errors.RadioError, e: print e # ICOM IC-91/92 Live-mode radios @ 4800/38400 baud ser.setBaudrate(4800) try: ic9x_ll.send_magic(ser) return _icom_model_data_to_rclass("ic9x") except errors.RadioError: pass # ICOM CI/V Radios @ various bauds for rate in [9600, 4800, 19200]: try: ser.setBaudrate(rate) return icomciv.probe_model(ser) except errors.RadioError: pass ser.close() raise errors.RadioError("Unable to get radio model") def detect_icom_radio(port): """Detect which Icom model is connected to @port""" ser = serial.Serial(port=port, timeout=0.5) try: result = _detect_icom_radio(ser) except Exception: ser.close() raise ser.close() print "Auto-detected %s %s on %s" % (result.VENDOR, result.MODEL, port) return result def detect_kenwoodlive_radio(port): """Detect which Kenwood model is connected to @port""" ser = serial.Serial(port=port, baudrate=9600, timeout=0.5) r_id = kenwood_live.get_id(ser) ser.close() models = {} for rclass in directory.DRV_TO_RADIO.values(): if rclass.VENDOR == "Kenwood": models[rclass.MODEL] = rclass if r_id in models.keys(): return models[r_id] else: raise errors.RadioError("Unsupported model `%s'" % r_id) DETECT_FUNCTIONS = { "Icom" : detect_icom_radio, "Kenwood" : detect_kenwoodlive_radio, } chirp-0.3.1/chirp/generic_tpe.py0000644000016101777760000000350012130403635017753 0ustar jenkinsnogroup00000000000000# Copyright 2012 Tom Hayward # # 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 . import UserDict from chirp import chirp_common, directory, generic_csv class TpeMap(UserDict.UserDict): """Pretend we're a dict""" def items(self): return [ ("Sequence Number" , (int, "number")), ("Location" , (str, "comment")), ("Call Sign" , (str, "name")), ("Output Frequency", (chirp_common.parse_freq, "freq")), ("Input Frequency" , (str, "duplex")), ("CTCSS Tones" , (lambda v: "Tone" if float(v) in chirp_common.TONES else "", "tmode")), ("CTCSS Tones" , (lambda v: float(v) if float(v) in chirp_common.TONES else 88.5, "rtone")), ("CTCSS Tones" , (lambda v: float(v) if float(v) in chirp_common.TONES else 88.5, "ctone")), ] @directory.register class TpeRadio(generic_csv.CSVRadio): """Generic ARRL Travel Plus""" VENDOR = "ARRL" MODEL = "Travel Plus" FILE_EXTENSION = "tpe" ATTR_MAP = TpeMap() chirp-0.3.1/chirp/vxa700.py0000644000016100007500000002247212023560645015072 0ustar jenkins00000000000000# Copyright 2012 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 . from chirp import chirp_common, util, directory, memmap, errors from chirp import bitwise import time import struct def _debug(string): pass print string def _send(radio, data): _debug("Sending %s" % repr(data)) radio.pipe.write(data) radio.pipe.flush() echo = radio.pipe.read(len(data)) if len(echo) != len(data): raise errors.RadioError("Invalid echo") def _spoonfeed(radio, data): #count = 0 _debug("Writing %i:\n%s" % (len(data), util.hexprint(data))) for byte in data: radio.pipe.write(byte) radio.pipe.flush() time.sleep(0.01) continue # This is really unreliable for some reason, # so just blindly send the data echo = radio.pipe.read(1) if echo != byte: print "%02x != %02x" % (ord(echo), ord(byte)) raise errors.RadioError("No echo?") #count += 1 def _download(radio): count = 0 data = "" while len(data) < radio.get_memsize(): count += 1 chunk = radio.pipe.read(133) if len(chunk) == 0 and len(data) == 0 and count < 30: continue if len(chunk) != 132: raise errors.RadioError("Got short block (length %i)" % len(chunk)) checksum = ord(chunk[-1]) _flag, _length, _block, _data, checksum = \ struct.unpack("BBB128sB", chunk) cs = 0 for byte in chunk[:-1]: cs += ord(byte) if (cs % 256) != checksum: raise errors.RadioError("Invalid checksum at 0x%02x" % len(data)) data += _data _send(radio, "\x06") if radio.status_fn: status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = len(data) status.max = radio.get_memsize() radio.status_fn(status) return memmap.MemoryMap(data) def _upload(radio): for i in range(0, radio.get_memsize(), 128): chunk = radio.get_mmap()[i:i+128] cs = 0x20 + 130 + (i / 128) for byte in chunk: cs += ord(byte) _spoonfeed(radio, struct.pack("BBB128sB", 0x20, 130, i / 128, chunk, cs % 256)) radio.pipe.write("") # This is really unreliable for some reason, so just # blindly proceed # ack = radio.pipe.read(1) ack = "\x06" time.sleep(0.5) if ack != "\x06": print repr(ack) raise errors.RadioError("Radio did not ack block %i" % (i / 132)) #radio.pipe.read(1) if radio.status_fn: status = chirp_common.Status() status.msg = "Cloning to radio" status.cur = i status.max = radio.get_memsize() radio.status_fn(status) MEM_FORMAT = """ struct memory_struct { u8 unknown1; u8 unknown2:2, isfm:1, power:2, step:3; u8 unknown5:2, showname:1, skip:1, duplex:2, unknown6:2; u8 tmode:2, unknown7:6; u8 unknown8; u8 unknown9:2, tone:6; u8 dtcs; u8 name[8]; u16 freq; u8 offset; }; u8 headerbytes[6]; #seekto 0x0006; u8 invisible_bits[13]; u8 bitfield_pad[3]; u8 invalid_bits[13]; #seekto 0x017F; struct memory_struct memory[100]; """ CHARSET = "".join(["%i" % i for i in range(0, 10)]) + \ "".join([chr(ord("A") + i) for i in range(0, 26)]) + \ "".join([chr(ord("a") + i) for i in range(0,26)]) + \ "., :;!\"#$%&'()*+-/=<>?@[?]^_`{|}????~??????????????????????????" TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+", ""] POWER = [chirp_common.PowerLevel("Low1", watts=0.050), chirp_common.PowerLevel("Low2", watts=1.000), chirp_common.PowerLevel("Low3", watts=2.500), chirp_common.PowerLevel("High", watts=5.000)] def _wipe_memory(_mem): _mem.set_raw("\x00" * (_mem.size() / 8)) @directory.register class VXA700Radio(chirp_common.CloneModeRadio): """Vertex Standard VXA-700""" VENDOR = "Vertex Standard" MODEL = "VXA-700" _memsize = 4096 def sync_in(self): try: self.pipe.setTimeout(2) self._mmap = _download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate " + "with the radio: %s" % e) self.process_mmap() def sync_out(self): #header[4] = 0x00 <- default # 0xFF <- air band only # 0x01 <- air band only # 0x02 <- air band only try: self.pipe.setTimeout(2) _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate " + "with the radio: %s" % e) def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_ctone = False rf.has_dtcs_polarity = False rf.has_tuning_step = False rf.valid_tmodes = TMODES rf.valid_name_length = 8 rf.valid_characters = CHARSET rf.valid_skips = ["", "S"] rf.valid_bands = [(88000000, 165000000)] rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] rf.valid_modes = ["AM", "FM"] rf.valid_power_levels = POWER rf.memory_bounds = (1, 100) return rf def _get_mem(self, number): return self._memobj.memory[number - 1] def get_raw_memory(self, number): _mem = self._get_mem(number) return repr(_mem) + util.hexprint(_mem.get_raw()) def get_memory(self, number): _mem = self._get_mem(number) byte = (number - 1) / 8 bit = 1 << ((number - 1) % 8) mem = chirp_common.Memory() mem.number = number if self._memobj.invisible_bits[byte] & bit: mem.empty = True if self._memobj.invalid_bits[byte] & bit: mem.empty = True return mem if _mem.step & 0x05: # Not sure this is right, but it seems to be mult = 6250 else: mult = 5000 mem.freq = int(_mem.freq) * mult mem.rtone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.offset = int(_mem.offset) * 5000 * 10 mem.mode = _mem.isfm and "FM" or "AM" mem.skip = _mem.skip and "S" or "" mem.power = POWER[_mem.power] for char in _mem.name: try: mem.name += CHARSET[char] except IndexError: break mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._get_mem(mem.number) byte = (mem.number - 1) / 8 bit = 1 << ((mem.number - 1) % 8) if mem.empty and self._memobj.invisible_bits[byte] & bit: self._memobj.invalid_bits[byte] |= bit return if mem.empty: self._memobj.invisible_bits[byte] |= bit return if self._memobj.invalid_bits[byte] & bit: _wipe_memory(_mem) self._memobj.invisible_bits[byte] &= ~bit self._memobj.invalid_bits[byte] &= ~bit _mem.unknown2 = 0x02 # Channels don't display without this _mem.unknown7 = 0x01 # some bit in this field is related to _mem.unknown8 = 0xFF # being able to transmit if chirp_common.required_step(mem.freq) == 12.5: mult = 6250 _mem.step = 0x05 else: mult = 5000 _mem.step = 0x00 _mem.freq = mem.freq / mult _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.offset = mem.offset / 5000 / 10 _mem.isfm = mem.mode == "FM" _mem.skip = mem.skip == "S" try: _mem.power = POWER.index(mem.power) except ValueError: _mem.power = 3 # High for i in range(0, 8): try: _mem.name[i] = CHARSET.index(mem.name[i]) except IndexError: _mem.name[i] = 0x40 _mem.showname = bool(mem.name.strip()) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ ord(filedata[5]) == 0x0F chirp-0.3.1/chirp/bitwise_grammar.py0000644000016101777760000000375612130403635020660 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . import re from chirp.pyPEG import keyword, parseLine TYPES = ["u8", "u16", "ul16", "u24", "ul24", "u32", "ul32", "char", "lbcd", "bbcd"] DIRECTIVES = ["seekto", "seek", "printoffset"] def string(): return re.compile(r"\"[^\"]*\"") def symbol(): return re.compile(r"\w+") def count(): return re.compile(r"([1-9][0-9]*|0x[0-9a-fA-F]+)") def bitdef(): return symbol, ":", count, -1 def _bitdeflist(): return bitdef, -1, (",", bitdef) def bitfield(): return -2, _bitdeflist def array(): return symbol, '[', count, ']' def _typedef(): return re.compile(r"(%s)" % "|".join(TYPES)) def definition(): return _typedef, [array, bitfield, symbol], ";" def seekto(): return keyword("seekto"), count def seek(): return keyword("seek"), count def printoffset(): return keyword("printoffset"), string def directive(): return "#", [seekto, seek, printoffset], ";" def _block_inner(): return -2, [definition, struct, directive] def _block(): return "{", _block_inner, "}" def struct_defn(): return symbol, _block def struct_decl(): return [symbol, _block], [array, symbol] def struct(): return keyword("struct"), [struct_defn, struct_decl], ";" def _language(): return _block_inner def parse(data): return parseLine(data, _language, resultSoFar=[]) chirp-0.3.1/chirp/directory.py0000644000016100007500000001056112023560645016045 0ustar jenkins00000000000000# Copyright 2010 Dan Smith # Copyright 2012 Tom Hayward # # 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 . import os import tempfile from chirp import icf from chirp import chirp_common, util, rfinder, radioreference, errors def radio_class_id(cls): """Return a unique identification string for @cls""" ident = "%s_%s" % (cls.VENDOR, cls.MODEL) if cls.VARIANT: ident += "_%s" % cls.VARIANT ident = ident.replace("/", "_") ident = ident.replace(" ", "_") ident = ident.replace("(", "") ident = ident.replace(")", "") return ident ALLOW_DUPS = False def enable_reregistrations(): """Set the global flag ALLOW_DUPS=True, which will enable a driver to re-register for a slot in the directory without triggering an exception""" global ALLOW_DUPS if not ALLOW_DUPS: print "NOTE: driver re-registration enabled" ALLOW_DUPS = True def register(cls): """Register radio @cls with the directory""" global DRV_TO_RADIO ident = radio_class_id(cls) if ident in DRV_TO_RADIO.keys(): if ALLOW_DUPS: print "Replacing existing driver id `%s'" % ident else: raise Exception("Duplicate radio driver id `%s'" % ident) DRV_TO_RADIO[ident] = cls RADIO_TO_DRV[cls] = ident print "Registered %s = %s" % (ident, cls.__name__) return cls DRV_TO_RADIO = {} RADIO_TO_DRV = {} def get_radio(driver): """Get radio driver class by identification string""" if DRV_TO_RADIO.has_key(driver): return DRV_TO_RADIO[driver] else: raise Exception("Unknown radio type `%s'" % driver) def get_driver(rclass): """Get the identification string for a given class""" if RADIO_TO_DRV.has_key(rclass): return RADIO_TO_DRV[rclass] elif RADIO_TO_DRV.has_key(rclass.__bases__[0]): return RADIO_TO_DRV[rclass.__bases__[0]] else: raise Exception("Unknown radio type `%s'" % rclass) def icf_to_image(icf_file, img_file): # FIXME: Why is this here? """Convert an ICF file to a .img file""" mdata, mmap = icf.read_file(icf_file) img_data = None for model in DRV_TO_RADIO.values(): try: if model._model == mdata: img_data = mmap.get_packed()[:model._memsize] break except Exception: pass # Skip non-Icoms if img_data: f = file(img_file, "wb") f.write(img_data) f.close() else: print "Unsupported model data:" print util.hexprint(mdata) raise Exception("Unsupported model") def get_radio_by_image(image_file): """Attempt to get the radio class that owns @image_file""" if image_file.startswith("radioreference://"): _, _, zipcode, username, password = image_file.split("/", 4) rr = radioreference.RadioReferenceRadio(None) rr.set_params(zipcode, username, password) return rr if image_file.startswith("rfinder://"): _, _, email, passwd, lat, lon, miles = image_file.split("/") rf = rfinder.RFinderRadio(None) rf.set_params((float(lat), float(lon)), int(miles), email, passwd) return rf if os.path.exists(image_file) and icf.is_icf_file(image_file): tempf = tempfile.mktemp() icf_to_image(image_file, tempf) print "Auto-converted %s -> %s" % (image_file, tempf) image_file = tempf if os.path.exists(image_file): f = file(image_file, "rb") filedata = f.read() f.close() else: filedata = "" for rclass in DRV_TO_RADIO.values(): if not issubclass(rclass, chirp_common.FileBackedRadio): continue if rclass.match_model(filedata, image_file): return rclass(image_file) raise errors.ImageDetectFailed("Unknown file format") chirp-0.3.1/chirp/icw32.py0000644000016100007500000001254312023560645014772 0ustar jenkins00000000000000# Copyright 2011 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 . from chirp import chirp_common, icf, util, directory from chirp import bitwise MEM_FORMAT = """ #seekto 0x%x; struct { bbcd freq[3]; bbcd offset[3]; u8 ctone; u8 rtone; char name[8]; } memory[111]; #seekto 0x%x; struct { u8 empty:1, skip:1, tmode:2, duplex:2, unk3:1, am:1; } flag[111]; #seekto 0x0E9C; struct { u8 unknown1:7, right_scan_direction:1; u8 right_scanning:1, unknown2:7; u8 unknown3:7, left_scan_direction:1; u8 left_scanning:1, unknown4:7; } state[1]; #seekto 0x0F20; struct { bbcd freq[3]; bbcd offset[3]; u8 ctone; u8 rtone; } callchans[2]; """ DUPLEX = ["", "", "-", "+"] TONE = ["", "", "Tone", "TSQL"] def _get_special(): special = {} for i in range(0, 5): special["M%iA" % (i+1)] = 100 + i*2 special["M%iB" % (i+1)] = 100 + i*2 + 1 return special @directory.register class ICW32ARadio(icf.IcomCloneModeRadio): """Icom IC-W32A""" VENDOR = "Icom" MODEL = "IC-W32A" _model = "\x18\x82\x00\x01" _memsize = 4064 _endframe = "Icom Inc\x2e" _ranges = [(0x0000, 0x0FE0, 16)] _limits = (0, 0) _mem_positions = (0, 1) def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 99) rf.valid_bands = [self._limits] if int(self._limits[0] / 100) == 1: rf.valid_modes = ["FM", "AM"] else: rf.valid_modes = ["FM"] rf.valid_tmodes = ["", "Tone", "TSQL"] rf.valid_name_length = 8 rf.valid_special_chans = sorted(_get_special().keys()) rf.has_sub_devices = self.VARIANT == "" rf.has_ctone = True rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_mode = "AM" in rf.valid_modes rf.has_tuning_step = False rf.has_bank = False return rf def process_mmap(self): fmt = MEM_FORMAT % self._mem_positions self._memobj = bitwise.parse(fmt, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): if isinstance(number, str): number = _get_special()[number] _mem = self._memobj.memory[number] _flg = self._memobj.flag[number] mem = chirp_common.Memory() mem.number = number if number < 100: # Normal memories mem.skip = _flg.skip and "S" or "" else: # Special memories mem.extd_number = util.get_dict_rev(_get_special(), number) if _flg.empty: mem.empty = True return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = int(_mem.offset) * 100 if str(_mem.name)[0] != chr(0xFF): mem.name = str(_mem.name).rstrip() mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.mode = _flg.am and "AM" or "FM" mem.duplex = DUPLEX[_flg.duplex] mem.tmode = TONE[_flg.tmode] if number > 100: mem.immutable = ["number", "skip", "extd_number", "name"] return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flg = self._memobj.flag[mem.number] _flg.empty = mem.empty if mem.empty: return _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 100 if mem.name: _mem.name = mem.name.ljust(8)[:8] else: _mem.name = "".join(["\xFF" * 8]) _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _flg.duplex = DUPLEX.index(mem.duplex) _flg.tmode = TONE.index(mem.tmode) _flg.skip = mem.skip == "S" _flg.am = mem.mode == "AM" if self._memobj.state.left_scanning: print "Canceling scan on left VFO" self._memobj.state.left_scanning = 0 if self._memobj.state.right_scanning: print "Canceling scan on right VFO" self._memobj.state.right_scanning = 0 def get_sub_devices(self): return [ICW32ARadioVHF(self._mmap), ICW32ARadioUHF(self._mmap)] @classmethod def match_model(cls, filedata, filename): if not len(filedata) == cls._memsize: return False return filedata[-16:] == "IcomCloneFormat3" class ICW32ARadioVHF(ICW32ARadio): """ICW32 VHF subdevice""" VARIANT = "VHF" _limits = (118000000, 174000000) _mem_positions = (0x0000, 0x0DC0) class ICW32ARadioUHF(ICW32ARadio): """ICW32 UHF subdevice""" VARIANT = "UHF" _limits = (400000000, 470000000) _mem_positions = (0x06E0, 0x0E2E) chirp-0.3.1/chirp/baofeng_uv3r.py0000644000016100007500000002115712036210646016421 0ustar jenkins00000000000000# Copyright 2011 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 . """Baofeng UV3r radio management module""" import time import os from chirp import util, chirp_common, bitwise, errors, directory from chirp.wouxun_common import do_download, do_upload if os.getenv("CHIRP_DEBUG"): DEBUG = True else: DEBUG = False def _uv3r_prep(radio): radio.pipe.write("\x05PROGRAM") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio did not ACK first command") radio.pipe.write("\x02") ident = radio.pipe.read(8) if len(ident) != 8: print util.hexprint(ident) raise errors.RadioError("Radio did not send identification") radio.pipe.write("\x06") if radio.pipe.read(1) != "\x06": raise errors.RadioError("Radio did not ACK ident") def uv3r_prep(radio): """Do the UV3R identification dance""" for _i in range(0, 10): try: return _uv3r_prep(radio) except errors.RadioError, e: time.sleep(1) raise e def uv3r_download(radio): """Talk to a UV3R and do a download""" try: uv3r_prep(radio) return do_download(radio, 0x0000, 0x0E40, 0x0010) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def uv3r_upload(radio): """Talk to a UV3R and do an upload""" try: uv3r_prep(radio) return do_upload(radio, 0x0000, 0x0E40, 0x0010) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) UV3R_MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rx_freq[4]; u8 rxtone; lbcd offset[4]; u8 txtone; u8 ishighpower:1, iswide:1, dtcsinvt:1, unknown1:1, dtcsinvr:1, unknown2:1, duplex:2; u8 unknown; lbcd tx_freq[4]; } tx_memory[99]; #seekto 0x0810; struct { lbcd rx_freq[4]; u8 rxtone; lbcd offset[4]; u8 txtone; u8 ishighpower:1, iswide:1, dtcsinvt:1, unknown1:1, dtcsinvr:1, unknown2:1, duplex:2; u8 unknown; lbcd tx_freq[4]; } rx_memory[99]; #seekto 0x1008; struct { u8 unknown[8]; u8 name[6]; u8 pad[2]; } names[128]; """ UV3R_DUPLEX = ["", "-", "+", ""] UV3R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00), chirp_common.PowerLevel("Low", watts=0.50)] UV3R_DTCS_POL = ["NN", "NR", "RN", "RR"] @directory.register class UV3RRadio(chirp_common.CloneModeRadio): """Baofeng UV-3R""" VENDOR = "Baofeng" MODEL = "UV-3R" def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_modes = ["FM", "NFM"] rf.valid_power_levels = UV3R_POWER_LEVELS rf.valid_bands = [(136000000, 174000000), (400000000, 470000000)] rf.valid_skips = [] rf.valid_duplexes = ["", "-", "+", "split"] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS"] rf.has_ctone = True rf.has_cross = True rf.has_tuning_step = False rf.has_bank = False rf.has_name = False rf.can_odd_split = True rf.memory_bounds = (1, 99) return rf def sync_in(self): self._mmap = uv3r_download(self) self.process_mmap() def sync_out(self): uv3r_upload(self) def process_mmap(self): self._memobj = bitwise.parse(UV3R_MEM_FORMAT, self._mmap) def get_memory(self, number): _mem = self._memobj.rx_memory[number - 1] mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[0] == "\xff": mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 mem.offset = int(_mem.offset) * 10 mem.duplex = UV3R_DUPLEX[_mem.duplex] if mem.offset > 60000000: if mem.duplex == "+": mem.offset = mem.freq + mem.offset elif mem.duplex == "-": mem.offset = mem.freq - mem.offset mem.duplex = "split" mem.power = UV3R_POWER_LEVELS[1 - _mem.ishighpower] if not _mem.iswide: mem.mode = "NFM" dtcspol = (int(_mem.dtcsinvt) << 1) + _mem.dtcsinvr mem.dtcs_polarity = UV3R_DTCS_POL[dtcspol] if _mem.txtone in [0, 0xFF]: txmode = "" elif _mem.txtone < 0x33: mem.rtone = chirp_common.TONES[_mem.txtone - 1] txmode = "Tone" elif _mem.txtone >= 0x33: tcode = chirp_common.DTCS_CODES[_mem.txtone - 0x33] mem.dtcs = tcode txmode = "DTCS" else: print "Bug: tx_mode is %02x" % _mem.txtone if _mem.rxtone in [0, 0xFF]: rxmode = "" elif _mem.rxtone < 0x33: mem.ctone = chirp_common.TONES[_mem.rxtone - 1] rxmode = "Tone" elif _mem.rxtone >= 0x33: rcode = chirp_common.DTCS_CODES[_mem.rxtone - 0x33] mem.dtcs = rcode rxmode = "DTCS" else: print "Bug: rx_mode is %02x" % _mem.rxtone if txmode == "Tone" and not rxmode: mem.tmode = "Tone" elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone: mem.tmode = "TSQL" elif txmode == rxmode and txmode == "DTCS": mem.tmode = "DTCS" elif rxmode or txmode: mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (txmode, rxmode) return mem def _set_tone(self, _mem, which, value, mode): if mode == "Tone": val = chirp_common.TONES.index(value) + 1 elif mode == "DTCS": val = chirp_common.DTCS_CODES.index(value) + 0x33 elif mode == "": val = 0 else: raise errors.RadioError("Internal error: tmode %s" % mode) setattr(_mem, which, val) def _set_memory(self, mem, _mem): if mem.empty: _mem.set_raw("\xff" * 16) return _mem.rx_freq = mem.freq / 10 if mem.duplex == "split": diff = mem.freq - mem.offset _mem.offset = abs(diff) / 10 _mem.duplex = UV3R_DUPLEX.index(diff < 0 and "+" or "-") for i in range(0, 4): _mem.tx_freq[i].set_raw("\xFF") else: _mem.offset = mem.offset / 10 _mem.duplex = UV3R_DUPLEX.index(mem.duplex) _mem.tx_freq = (mem.freq + mem.offset) / 10 _mem.ishighpower = mem.power == UV3R_POWER_LEVELS[0] _mem.iswide = mem.mode == "FM" _mem.dtcsinvt = mem.dtcs_polarity[0] == "R" _mem.dtcsinvr = mem.dtcs_polarity[1] == "R" rxtone = txtone = 0 rxmode = txmode = "" if mem.tmode == "DTCS": rxmode = txmode = "DTCS" rxtone = txtone = mem.dtcs elif mem.tmode and mem.tmode != "Cross": rxtone = txtone = mem.tmode == "Tone" and mem.rtone or mem.ctone txmode = "Tone" rxmode = mem.tmode == "TSQL" and "Tone" or "" elif mem.tmode == "Cross": txmode, rxmode = mem.cross_mode.split("->", 1) if txmode == "DTCS": txtone = mem.dtcs elif txmode == "Tone": txtone = mem.rtone if rxmode == "DTCS": rxtone = mem.dtcs elif rxmode == "Tone": rxtone = mem.ctone self._set_tone(_mem, "txtone", txtone, txmode) self._set_tone(_mem, "rxtone", rxtone, rxmode) def set_memory(self, mem): _tmem = self._memobj.tx_memory[mem.number - 1] _rmem = self._memobj.rx_memory[mem.number - 1] self._set_memory(mem, _tmem) self._set_memory(mem, _rmem) @classmethod def match_model(cls, filedata, filename): return len(filedata) == 3648 def get_raw_memory(self, number): _rmem = self._memobj.tx_memory[number - 1] _tmem = self._memobj.rx_memory[number - 1] return repr(_rmem) + repr(_tmem) chirp-0.3.1/chirp/chirp_common.py0000644000016101777760000012236712130403635020161 0ustar jenkinsnogroup00000000000000# Copyright 2008 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 . SEPCHAR = "," #print "Using separation character of '%s'" % SEPCHAR import math from chirp import errors, memmap # 50 Tones TONES = [ 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1, ] # 104 DTCS Codes DTCS_CODES = [ 23, 25, 26, 31, 32, 36, 43, 47, 51, 53, 54, 65, 71, 72, 73, 74, 114, 115, 116, 122, 125, 131, 132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174, 205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252, 255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325, 331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412, 413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464, 465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606, 612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723, 731, 732, 734, 743, 754, ] # Some radios have some strange codes DTCS_EXTRA_CODES = [ 17, 645 ] CROSS_MODES = [ "Tone->Tone", "Tone->DTCS", "DTCS->Tone", "DTCS->", "->Tone", "->DTCS", "DTCS->DTCS", ] 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", "TSQL", "DTCS", "DTCS-R", "TSQL-R", "Cross", ] TUNING_STEPS = [ 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 200.0, # Need to fix drivers using this list as an index! 9.0, 1.0, 2.5, ] SKIP_VALUES = [ "", "S", "P" ] CHARSET_UPPER_NUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890" CHARSET_ALPHANUMERIC = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890" CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~")+1)]) def watts_to_dBm(watts): """Converts @watts in watts to dBm""" return int(10 * math.log10(int(watts * 1000))) def dBm_to_watts(dBm): """Converts @dBm from dBm to watts""" return int(math.pow(10, (dBm - 30) / 10)) class PowerLevel: """Represents a power level supported by a radio""" def __init__(self, label, watts=0, dBm=0): if watts: dBm = watts_to_dBm(watts) self._power = int(dBm) self._label = label def __str__(self): return str(self._label) def __int__(self): return self._power def __sub__(self, val): return int(self) - int(val) def __add__(self, val): return int(self) + int(val) def __eq__(self, val): if val is not None: return int(self) == int(val) return False def __lt__(self, val): return int(self) < int(val) def __gt__(self, val): return int(self) > int(val) def __nonzero__(self): return int(self) != 0 def __repr__(self): return "%s (%i dBm)" % (self._label, self._power) def parse_freq(freqstr): """Parse a frequency string and return the value in integral Hz""" if "." in freqstr: mhz, khz = freqstr.split(".") else: mhz = freqstr khz = "0" if not mhz.isdigit() and khz.isdigit(): raise ValueError("Invalid value") # Make kHz exactly six decimal places return int(("%s%-6s" % (mhz, khz)).replace(" ", "0")) def format_freq(freq): """Format a frequency given in Hz as a string""" return "%i.%06i" % (freq / 1000000, freq % 1000000) class ImmutableValueError(ValueError): pass class Memory: """Base class for a single radio memory""" freq = 0 number = 0 extd_number = "" name = "" vfo = 0 rtone = 88.5 ctone = 88.5 dtcs = 23 rx_dtcs = 23 tmode = "" cross_mode = "Tone->Tone" dtcs_polarity = "NN" skip = "" power = None duplex = "" offset = 600000 mode = "FM" tuning_step = 5.0 comment = "" empty = False immutable = [] # A RadioSettingsGroup of additional settings supported by the radio, # or an empty list if none extra = [] def __init__(self): self.freq = 0 self.number = 0 self.extd_number = "" self.name = "" self.vfo = 0 self.rtone = 88.5 self.ctone = 88.5 self.dtcs = 23 self.rx_dtcs = 23 self.tmode = "" self.cross_mode = "Tone->Tone" self.dtcs_polarity = "NN" self.skip = "" self.power = None self.duplex = "" self.offset = 600000 self.mode = "FM" self.tuning_step = 5.0 self.comment = "" self.empty = False self.immutable = [] _valid_map = { "rtone" : TONES, "ctone" : TONES, "dtcs" : DTCS_CODES + DTCS_EXTRA_CODES, "rx_dtcs" : DTCS_CODES + DTCS_EXTRA_CODES, "tmode" : TONE_MODES, "dtcs_polarity" : ["NN", "NR", "RN", "RR"], "cross_mode" : CROSS_MODES, "mode" : MODES, "duplex" : ["", "+", "-", "split", "off"], "skip" : SKIP_VALUES, "empty" : [True, False], "dv_code" : [x for x in range(0, 100)], } def __repr__(self): return "Memory[%i]" % self.number def dupe(self): """Return a deep copy of @self""" mem = self.__class__() for k, v in self.__dict__.items(): mem.__dict__[k] = v return mem def clone(self, source): """Absorb all of the properties of @source""" for k, v in source.__dict__.items(): self.__dict__[k] = v CSV_FORMAT = ["Location", "Name", "Frequency", "Duplex", "Offset", "Tone", "rToneFreq", "cToneFreq", "DtcsCode", "DtcsPolarity", "Mode", "TStep", "Skip", "Comment", "URCALL", "RPT1CALL", "RPT2CALL"] def __setattr__(self, name, val): if not hasattr(self, name): raise ValueError("No such attribute `%s'" % name) if name in self.immutable: raise ImmutableValueError("Field %s is not " % name + "mutable on this memory") if self._valid_map.has_key(name) and val not in self._valid_map[name]: raise ValueError("`%s' is not in valid list: %s" % (\ val, self._valid_map[name])) self.__dict__[name] = val def format_freq(self): """Return a properly-formatted string of this memory's frequency""" return format_freq(self.freq) def parse_freq(self, freqstr): """Set the frequency from a string""" self.freq = parse_freq(freqstr) return self.freq def __str__(self): if self.tmode == "Tone": tenc = "*" else: tenc = " " if self.tmode == "TSQL": tsql = "*" else: tsql = " " if self.tmode == "DTCS": dtcs = "*" else: dtcs = " " if self.duplex == "": dup = "/" else: dup = self.duplex return "Memory %i: %s%s%s %s (%s) r%.1f%s c%.1f%s d%03i%s%s [%.2f]"% \ (self.number, format_freq(self.freq), dup, format_freq(self.offset), self.mode, self.name, self.rtone, tenc, self.ctone, tsql, self.dtcs, dtcs, self.dtcs_polarity, self.tuning_step) def to_csv(self): """Return a CSV representation of this memory""" return [ "%i" % self.number, "%s" % self.name, format_freq(self.freq), "%s" % self.duplex, format_freq(self.offset), "%s" % self.tmode, "%.1f" % self.rtone, "%.1f" % self.ctone, "%03i" % self.dtcs, "%s" % self.dtcs_polarity, "%s" % self.mode, "%.2f" % self.tuning_step, "%s" % self.skip, "%s" % self.comment, "", "", "", ""] @classmethod def _from_csv(cls, _line): line = _line.strip() if line.startswith("Location"): raise errors.InvalidMemoryLocation("Non-CSV line") vals = line.split(SEPCHAR) if len(vals) < 11: raise errors.InvalidDataError("CSV format error " + "(14 columns expected)") if vals[10] == "DV": mem = DVMemory() else: mem = Memory() mem.really_from_csv(vals) return mem def really_from_csv(self, vals): """Careful parsing of split-out @vals""" try: self.number = int(vals[0]) except: print "Loc: %s" % vals[0] raise errors.InvalidDataError("Location is not a valid integer") self.name = vals[1] try: self.freq = float(vals[2]) except: raise errors.InvalidDataError("Frequency is not a valid number") if vals[3].strip() in ["+", "-", ""]: self.duplex = vals[3].strip() else: raise errors.InvalidDataError("Duplex is not +,-, or empty") try: self.offset = float(vals[4]) except: raise errors.InvalidDataError("Offset is not a valid number") self.tmode = vals[5] if self.tmode not in TONE_MODES: raise errors.InvalidDataError("Invalid tone mode `%s'" % self.tmode) try: self.rtone = float(vals[6]) except: raise errors.InvalidDataError("rTone is not a valid number") if self.rtone not in TONES: raise errors.InvalidDataError("rTone is not valid") try: self.ctone = float(vals[7]) except: raise errors.InvalidDataError("cTone is not a valid number") if self.ctone not in TONES: raise errors.InvalidDataError("cTone is not valid") try: self.dtcs = int(vals[8], 10) except: raise errors.InvalidDataError("DTCS code is not a valid number") if self.dtcs not in DTCS_CODES: raise errors.InvalidDataError("DTCS code is not valid") try: self.rx_dtcs = int(vals[8], 10) except: raise errors.InvalidDataError("DTCS Rx code is not a valid number") if self.rx_dtcs not in DTCS_CODES: raise errors.InvalidDataError("DTCS Rx code is not valid") if vals[9] in ["NN", "NR", "RN", "RR"]: self.dtcs_polarity = vals[9] else: raise errors.InvalidDataError("DtcsPolarity is not valid") if vals[10] in MODES: self.mode = vals[10] else: raise errors.InvalidDataError("Mode is not valid") try: self.tuning_step = float(vals[11]) except: raise errors.InvalidDataError("Tuning step is invalid") try: self.skip = vals[12] except: raise errors.InvalidDataError("Skip value is not valid") return True class DVMemory(Memory): """A Memory with D-STAR attributes""" dv_urcall = "CQCQCQ" dv_rpt1call = "" dv_rpt2call = "" dv_code = 0 def __str__(self): string = Memory.__str__(self) string += " <%s,%s,%s>" % (self.dv_urcall, self.dv_rpt1call, self.dv_rpt2call) return string def to_csv(self): return [ "%i" % self.number, "%s" % self.name, format_freq(self.freq), "%s" % self.duplex, format_freq(self.offset), "%s" % self.tmode, "%.1f" % self.rtone, "%.1f" % self.ctone, "%03i" % self.dtcs, "%s" % self.dtcs_polarity, "%s" % self.mode, "%.2f" % self.tuning_step, "%s" % self.skip, "%s" % self.comment, "%s" % self.dv_urcall, "%s" % self.dv_rpt1call, "%s" % self.dv_rpt2call, "%i" % self.dv_code] def really_from_csv(self, vals): Memory.really_from_csv(self, vals) self.dv_urcall = vals[15].rstrip()[:8] self.dv_rpt1call = vals[16].rstrip()[:8] self.dv_rpt2call = vals[17].rstrip()[:8] try: self.dv_code = int(vals[18].strip()) except Exception: self.dv_code = 0 class Bank: """Base class for a radio's Bank""" def __init__(self, model, index, name): self._model = model self._index = index self._name = name def __str__(self): return self.get_name() def __repr__(self): return "Bank-%s" % self._index def get_name(self): """Returns the static or user-adjustable bank name""" return self._name def get_index(self): """Returns the immutable bank index (string or int)""" return self._index def __eq__(self, other): return self.get_index() == other.get_index() class NamedBank(Bank): """A bank that can have a name""" def set_name(self, name): """Changes the user-adjustable bank name""" self._name = name class BankModel: """A bank model where one memory is in zero or one banks at any point""" def __init__(self, radio): self._radio = radio def get_num_banks(self): """Returns the number of banks (should be callable without consulting the radio""" raise Exception("Not implemented") def get_banks(self): """Return a list of banks""" raise Exception("Not implemented") def add_memory_to_bank(self, memory, bank): """Add @memory to @bank.""" raise Exception("Not implemented") def remove_memory_from_bank(self, memory, bank): """Remove @memory from @bank. Shall raise exception if @memory is not in @bank.""" raise Exception("Not implemented") def get_bank_memories(self, bank): """Return a list of memories in @bank""" raise Exception("Not implemented") def get_memory_banks(self, memory): """Returns a list of the banks that @memory is in""" raise Exception("Not implemented") class BankIndexInterface: """Interface for banks with index capabilities""" def get_index_bounds(self): """Returns a tuple (lo,hi) of the minimum and maximum bank indices""" raise Exception("Not implemented") def get_memory_index(self, memory, bank): """Returns the index of @memory in @bank""" raise Exception("Not implemented") def set_memory_index(self, memory, bank, index): """Sets the index of @memory in @bank to @index""" raise Exception("Not implemented") def get_next_bank_index(self, bank): """Returns the next available bank index in @bank, or raises Exception if full""" raise Exception("Not implemented") class MTOBankModel(BankModel): """A bank model where one memory can be in multiple banks at once """ pass def console_status(status): """Write a status object to the console""" import sys sys.stderr.write("\r%s" % status) BOOLEAN = [True, False] class RadioFeatures: """Radio Feature Flags""" _valid_map = { # General "has_bank_index" : BOOLEAN, "has_dtcs" : BOOLEAN, "has_rx_dtcs" : BOOLEAN, "has_dtcs_polarity" : BOOLEAN, "has_mode" : BOOLEAN, "has_offset" : BOOLEAN, "has_name" : BOOLEAN, "has_bank" : BOOLEAN, "has_bank_names" : BOOLEAN, "has_tuning_step" : BOOLEAN, "has_ctone" : BOOLEAN, "has_cross" : BOOLEAN, "has_infinite_number" : BOOLEAN, "has_nostep_tuning" : BOOLEAN, "has_comment" : BOOLEAN, "has_settings" : BOOLEAN, # Attributes "valid_modes" : [], "valid_tmodes" : [], "valid_duplexes" : [], "valid_tuning_steps" : [], "valid_bands" : [], "valid_skips" : [], "valid_power_levels" : [], "valid_characters" : "", "valid_name_length" : 0, "valid_cross_modes" : [], "valid_dtcs_pols" : [], "valid_special_chans" : [], "has_sub_devices" : BOOLEAN, "memory_bounds" : (0, 0), "can_odd_split" : BOOLEAN, # D-STAR "requires_call_lists" : BOOLEAN, "has_implicit_calls" : BOOLEAN, } def __setattr__(self, name, val): if name.startswith("_"): self.__dict__[name] = val return elif not name in self._valid_map.keys(): raise ValueError("No such attribute `%s'" % name) if type(self._valid_map[name]) == tuple: # Tuple, cardinality must match if type(val) != tuple or len(val) != len(self._valid_map[name]): raise ValueError("Invalid value `%s' for attribute `%s'" % \ (val, name)) elif type(self._valid_map[name]) == list and not self._valid_map[name]: # Empty list, must be another list if type(val) != list: raise ValueError("Invalid value `%s' for attribute `%s'" % \ (val, name)) elif type(self._valid_map[name]) == str: if type(val) != str: raise ValueError("Invalid value `%s' for attribute `%s'" % \ (val, name)) elif type(self._valid_map[name]) == int: if type(val) != int: raise ValueError("Invalid value `%s' for attribute `%s'" % \ (val, name)) elif val not in self._valid_map[name]: # Value not in the list of valid values raise ValueError("Invalid value `%s' for attribute `%s'" % (val, name)) self.__dict__[name] = val def __getattr__(self, name): raise AttributeError("pylint is confused by RadioFeatures") def init(self, attribute, default, doc=None): """Initialize a feature flag @attribute with default value @default, and documentation string @doc""" self.__setattr__(attribute, default) self.__docs[attribute] = doc def get_doc(self, attribute): """Return the description of @attribute""" return self.__docs[attribute] def __init__(self): self.__docs = {} self.init("has_bank_index", False, "Indicates that memories in a bank can be stored in " + "an order other than in main memory") self.init("has_dtcs", True, "Indicates that DTCS tone mode is available") self.init("has_rx_dtcs", False, "Indicates that radio can use two different DTCS codes for rx and tx") self.init("has_dtcs_polarity", True, "Indicates that the DTCS polarity can be changed") self.init("has_mode", True, "Indicates that multiple emission modes are supported") self.init("has_offset", True, "Indicates that the TX offset memory property is supported") self.init("has_name", True, "Indicates that an alphanumeric memory name is supported") self.init("has_bank", True, "Indicates that memories may be placed into banks") self.init("has_bank_names", False, "Indicates that banks may be named") self.init("has_tuning_step", True, "Indicates that memories store their tuning step") self.init("has_ctone", True, "Indicates that the radio keeps separate tone frequencies " + "for repeater and CTCSS operation") self.init("has_cross", False, "Indicates that the radios supports different tone modes " + "on transmit and receive") self.init("has_infinite_number", False, "Indicates that the radio is not constrained in the " + "number of memories that it can store") self.init("has_nostep_tuning", False, "Indicates that the radio does not require a valid " + "tuning step to store a frequency") self.init("has_comment", False, "Indicates that the radio supports storing a comment " + "with each memory") self.init("has_settings", False, "Indicates that the radio supports general settings") self.init("valid_modes", list(MODES), "Supported emission (or receive) modes") self.init("valid_tmodes", [], "Supported tone squelch modes") self.init("valid_duplexes", ["", "+", "-"], "Supported duplex modes") self.init("valid_tuning_steps", list(TUNING_STEPS), "Supported tuning steps") self.init("valid_bands", [], "Supported frequency ranges") self.init("valid_skips", ["", "S"], "Supported memory scan skip settings") self.init("valid_power_levels", [], "Supported power levels") self.init("valid_characters", CHARSET_UPPER_NUMERIC, "Supported characters for a memory's alphanumeric tag") self.init("valid_name_length", 6, "The maximum number of characters in a memory's " + "alphanumeric tag") self.init("valid_cross_modes", list(CROSS_MODES), "Supported tone cross modes") self.init("valid_dtcs_pols", ["NN", "RN", "NR", "RR"], "Supported DTCS polarities") self.init("valid_special_chans", [], "Supported special channel names") self.init("has_sub_devices", False, "Indicates that the radio behaves as two semi-independent " + "devices") self.init("memory_bounds", (0, 1), "The minimum and maximum channel numbers") self.init("can_odd_split", False, "Indicates that the radio can store an independent " + "transmit frequency") self.init("requires_call_lists", True, "[D-STAR] Indicates that the radio requires all callsigns " + "to be in the master list and cannot be stored " + "arbitrarily in each memory channel") self.init("has_implicit_calls", False, "[D-STAR] Indicates that the radio has an implied " + "callsign at the beginning of the master URCALL list") def is_a_feature(self, name): """Returns True if @name is a valid feature flag name""" return name in self._valid_map.keys() def __getitem__(self, name): return self.__dict__[name] def validate_memory(self, mem): """Return a list of warnings and errors that will be encoundered if trying to set @mem on the current radio""" msgs = [] lo, hi = self.memory_bounds if not self.has_infinite_number and \ (mem.number < lo or mem.number > hi) and \ mem.extd_number not in self.valid_special_chans: msg = ValidationWarning("Location %i is out of range" % mem.number) msgs.append(msg) if (self.valid_modes and mem.mode not in self.valid_modes and mem.mode != "Auto"): msg = ValidationError("Mode %s not supported" % mem.mode) msgs.append(msg) if self.valid_tmodes and mem.tmode not in self.valid_tmodes: msg = ValidationError("Tone mode %s not supported" % mem.tmode) msgs.append(msg) else: if mem.tmode == "Cross": if self.valid_cross_modes and \ mem.cross_mode not in self.valid_cross_modes: msg = ValidationError("Cross tone mode %s not supported" % \ mem.cross_mode) msgs.append(msg) if self.has_dtcs_polarity and \ mem.dtcs_polarity not in self.valid_dtcs_pols: msg = ValidationError("DTCS Polarity %s not supported" % \ mem.dtcs_polarity) msgs.append(msg) if self.valid_duplexes and mem.duplex not in self.valid_duplexes: msg = ValidationError("Duplex %s not supported" % mem.duplex) msgs.append(msg) ts = mem.tuning_step if self.valid_tuning_steps and ts not in self.valid_tuning_steps and \ not self.has_nostep_tuning: msg = ValidationError("Tuning step %.2f not supported" % ts) msgs.append(msg) if self.valid_bands: valid = False for lo, hi in self.valid_bands: if lo <= mem.freq < hi: valid = True break if not valid: msg = ValidationError( ("Frequency {freq} is out " "of supported range").format(freq=format_freq(mem.freq))) msgs.append(msg) if mem.power and \ self.valid_power_levels and \ mem.power not in self.valid_power_levels: msg = ValidationWarning("Power level %s not supported" % mem.power) msgs.append(msg) if self.valid_tuning_steps and not self.has_nostep_tuning: try: step = required_step(mem.freq) if step not in self.valid_tuning_steps: msg = ValidationError("Frequency requires %.2fkHz step" %\ required_step(mem.freq)) msgs.append(msg) except errors.InvalidDataError, e: msgs.append(str(e)) if self.valid_characters: for char in mem.name: if char not in self.valid_characters: msgs.append(ValidationWarning("Name character " + "`%s'" % char + " not supported")) break return msgs class ValidationMessage(str): """Base class for Validation Errors and Warnings""" pass class ValidationWarning(ValidationMessage): """A non-fatal warning during memory validation""" pass class ValidationError(ValidationMessage): """A fatal error during memory validation""" pass class Radio: """Base class for all Radio drivers""" BAUD_RATE = 9600 HARDWARE_FLOW = False VENDOR = "Unknown" MODEL = "Unknown" VARIANT = "" def status_fn(self, status): """Deliver @status to the UI""" console_status(status) def __init__(self, pipe): self.errors = [] self.pipe = pipe def get_features(self): """Return a RadioFeatures object for this radio""" return RadioFeatures() @classmethod def get_name(cls): """Return a printable name for this radio""" return "%s %s" % (cls.VENDOR, cls.MODEL) def set_pipe(self, pipe): """Set the serial object to be used for communications""" self.pipe = pipe def get_memory(self, number): """Return a Memory object for the memory at location @number""" pass def erase_memory(self, number): """Erase memory at location @number""" mem = Memory() mem.number = number mem.empty = True self.set_memory(mem) def get_memories(self, lo=None, hi=None): """Get all the memories between @lo and @hi""" pass def set_memory(self, memory): """Set the memory object @memory""" pass def get_bank_model(self): """Returns either a BankModel or None if not supported""" return None def get_raw_memory(self, number): """Return a raw string describing the memory at @number""" pass def filter_name(self, name): """Filter @name to just the length and characters supported""" rf = self.get_features() if rf.valid_characters == rf.valid_characters.upper(): # Radio only supports uppercase, so help out here name = name.upper() return "".join([x for x in name[:rf.valid_name_length] if x in rf.valid_characters]) def get_sub_devices(self): """Return a list of sub-device Radio objects, if RadioFeatures.has_sub_devices is True""" return [] def validate_memory(self, mem): """Return a list of warnings and errors that will be encoundered if trying to set @mem on the current radio""" rf = self.get_features() return rf.validate_memory(mem) def get_settings(self): """Returns a RadioSettingGroup containing one or more RadioSettingGroup or RadioSetting objects. These represent general setting knobs and dials that can be adjusted on the radio. If this function is implemented, the has_settings RadioFeatures flag should be True and set_settings() must be implemented as well.""" pass def set_settings(self, settings): """Accepts the top-level RadioSettingGroup returned from get_settings() and adjusts the values in the radio accordingly. This function expects the entire RadioSettingGroup hierarchy returned from get_settings(). If this function is implemented, the has_settings RadioFeatures flag should be True and get_settings() must be implemented as well.""" pass class FileBackedRadio(Radio): """A file-backed radio stores its data in a file""" FILE_EXTENSION = "img" def __init__(self, *args, **kwargs): Radio.__init__(self, *args, **kwargs) self._memobj = None def save(self, filename): """Save the radio's memory map to @filename""" self.save_mmap(filename) def load(self, filename): """Load the radio's memory map object from @filename""" self.load_mmap(filename) def process_mmap(self): """Process a newly-loaded or downloaded memory map""" pass def load_mmap(self, filename): """Load the radio's memory map from @filename""" mapfile = file(filename, "rb") self._mmap = memmap.MemoryMap(mapfile.read()) mapfile.close() self.process_mmap() def save_mmap(self, filename): """ try to open a file and write to it If IOError raise a File Access Error Exception """ try: mapfile = file(filename, "wb") mapfile.write(self._mmap.get_packed()) mapfile.close() except IOError: raise Exception("File Access Error") def get_mmap(self): """Return the radio's memory map object""" return self._mmap class CloneModeRadio(FileBackedRadio): """A clone-mode radio does a full memory dump in and out and we store an image of the radio into an image file""" _memsize = 0 def __init__(self, pipe): self.errors = [] self._mmap = None if isinstance(pipe, str): self.pipe = None self.load_mmap(pipe) elif isinstance(pipe, memmap.MemoryMap): self.pipe = None self._mmap = pipe self.process_mmap() else: FileBackedRadio.__init__(self, pipe) def get_memsize(self): """Return the radio's memory size""" return self._memsize @classmethod def match_model(cls, filedata, filename): """Given contents of a stored file (@filedata), return True if this radio driver handles the represented model""" # Unless the radio driver does something smarter, claim # support if the data is the same size as our memory. # Ideally, each radio would perform an intelligent analysis to # make this determination to avoid model conflicts with # memories of the same size. return len(filedata) == cls._memsize def sync_in(self): "Initiate a radio-to-PC clone operation" pass def sync_out(self): "Initiate a PC-to-radio clone operation" pass class LiveRadio(Radio): """Base class for all Live-Mode radios""" pass class NetworkSourceRadio(Radio): """Base class for all radios based on a network source""" def do_fetch(self): """Fetch the source data from the network""" pass class IcomDstarSupport: """Base interface for radios supporting Icom's D-STAR technology""" MYCALL_LIMIT = (1, 1) URCALL_LIMIT = (1, 1) RPTCALL_LIMIT = (1, 1) def get_urcall_list(self): """Return a list of URCALL callsigns""" return [] def get_repeater_call_list(self): """Return a list of RPTCALL callsigns""" return [] def get_mycall_list(self): """Return a list of MYCALL callsigns""" return [] def set_urcall_list(self, calls): """Set the URCALL callsign list""" pass def set_repeater_call_list(self, calls): """Set the RPTCALL callsign list""" pass def set_mycall_list(self, calls): """Set the MYCALL callsign list""" pass class ExperimentalRadio: """Interface for experimental radios""" @classmethod def get_experimental_warning(cls): return ("This radio's driver is marked as experimental and may " + "be unstable or unsafe to use.") class Status: """Clone status object for conveying clone progress to the UI""" name = "Job" msg = "Unknown" max = 100 cur = 0 def __str__(self): try: pct = (self.cur / float(self.max)) * 100 nticks = int(pct) / 10 ticks = "=" * nticks except ValueError: pct = 0.0 ticks = "?" * 10 return "|%-10s| %2.1f%% %s" % (ticks, pct, self.msg) def is_fractional_step(freq): """Returns True if @freq requires a 12.5kHz or 6.25kHz step""" return not is_5_0(freq) and (is_12_5(freq) or is_6_25(freq)) def is_5_0(freq): """Returns True if @freq is reachable by a 5kHz step""" return (freq % 5000) == 0 def is_12_5(freq): """Returns True if @freq is reachable by a 12.5kHz step""" return (freq % 12500) == 0 def is_6_25(freq): """Returns True if @freq is reachable by a 6.25kHz step""" return (freq % 6250) == 0 def is_2_5(freq): """Returns True if @freq is reachable by a 2.5kHz step""" return (freq % 2500) == 0 def required_step(freq): """Returns the simplest tuning step that is required to reach @freq""" if is_5_0(freq): return 5.0 elif is_12_5(freq): return 12.5 elif is_6_25(freq): return 6.25 elif is_2_5(freq): return 2.5 else: raise errors.InvalidDataError("Unable to calculate the required " + "tuning step for %i.%5i" % \ (freq / 1000000, freq % 1000000)) def fix_rounded_step(freq): """Some radios imply the last bit of 12.5kHz and 6.25kHz step frequencies. Take the base @freq and return the corrected one""" try: required_step(freq) return freq except errors.InvalidDataError: pass try: required_step(freq + 500) return freq + 500 except errors.InvalidDataError: pass try: required_step(freq + 250) return freq + 250 except errors.InvalidDataError: pass try: required_step(freq + 750) return float(freq + 750) except errors.InvalidDataError: pass raise errors.InvalidDataError("Unable to correct rounded frequency " + \ format_freq(freq)) def _name(name, len, just_upper): """Justify @name to @len, optionally converting to all uppercase""" if just_upper: name = name.upper() return name.ljust(len)[:len] def name6(name, just_upper=True): """6-char name""" return _name(name, 6, just_upper) def name8(name, just_upper=False): """8-char name""" return _name(name, 8, just_upper) def name16(name, just_upper=False): """16-char name""" return _name(name, 16, just_upper) def to_GHz(val): """Convert @val in GHz to Hz""" return val * 1000000000 def to_MHz(val): """Convert @val in MHz to Hz""" return val * 1000000 def to_kHz(val): """Convert @val in kHz to Hz""" return val * 1000 def from_GHz(val): """Convert @val in Hz to GHz""" return val / 100000000 def from_MHz(val): """Convert @val in Hz to MHz""" return val / 100000 def from_kHz(val): """Convert @val in Hz to kHz""" return val / 100 def split_tone_decode(mem, txtone, rxtone): """ Set tone mode and values on @mem based on txtone and rxtone specs like: None, None, None "Tone", 123.0, None "DTCS", 23, "N" """ txmode, txval, txpol = txtone rxmode, rxval, rxpol = rxtone mem.dtcs_polarity = "%s%s" % (txpol or "N", rxpol or "N") if not txmode and not rxmode: # No tone return if txmode == "Tone" and not rxmode: mem.tmode = "Tone" mem.rtone = txval return if txmode == rxmode == "Tone" and txval == rxval: # TX and RX same tone -> TSQL mem.tmode = "TSQL" mem.ctone = txval return if txmode == rxmode == "DTCS" and txval == rxval: mem.tmode = "DTCS" mem.dtcs = txval return mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (txmode or "", rxmode or "") if txmode == "Tone": mem.rtone = txval elif txmode == "DTCS": mem.dtcs = txval if rxmode == "Tone": mem.ctone = rxval elif rxmode == "DTCS": mem.rx_dtcs = rxval def split_tone_encode(mem): """ Returns TX, RX tone specs based on @mem like: None, None, None "Tone", 123.0, None "DTCS", 23, "N" """ txmode = txval = None txpol = mem.dtcs_polarity[0] rxmode = rxval = None rxpol = mem.dtcs_polarity[1] if mem.tmode == "Tone": txmode = "Tone" txval = mem.rtone elif mem.tmode == "TSQL": txmode = rxmode = "Tone" txval = rxval = mem.ctone elif mem.tmode == "DTCS": txmode = rxmode = "DTCS" txval = rxval = mem.dtcs elif mem.tmode == "Cross": txmode, rxmode = mem.cross_mode.split("->", 1) if txmode == "Tone": txval = mem.rtone elif txmode == "DTCS": txval = mem.dtcs if rxmode == "Tone": rxval = mem.ctone elif rxmode == "DTCS": rxval = mem.rx_dtcs return ((txmode, txval, txpol), (rxmode, rxval, rxpol)) chirp-0.3.1/chirp/icomciv.py0000644000016100007500000002660712023560645015502 0ustar jenkins00000000000000 import struct from chirp import chirp_common, icf, util, errors, bitwise, directory from chirp.memmap import MemoryMap DEBUG = True MEM_FORMAT = """ bbcd number[2]; u8 unknown1; lbcd freq[5]; u8 unknown2:5, mode:3; """ MEM_VFO_FORMAT = """ u8 vfo; bbcd number[2]; u8 unknown1; lbcd freq[5]; u8 unknown2:5, mode:3; u8 unknown1; u8 unknown2:2, duplex:2, unknown3:1, tmode:3; u8 unknown4; bbcd rtone[2]; u8 unknown5; bbcd ctone[2]; u8 unknown6[2]; bbcd dtcs; u8 unknown[17]; char name[9]; """ mem_duptone_format = """ bbcd number[2]; u8 unknown1; lbcd freq[5]; u8 unknown2:5, mode:3; u8 unknown1; u8 unknown2:2, duplex:2, unknown3:1, tmode:3; u8 unknown4; bbcd rtone[2]; u8 unknown5; bbcd ctone[2]; u8 unknown6[2]; bbcd dtcs; u8 unknown[11]; char name[9]; """ class Frame: """Base class for an ICF frame""" _cmd = 0x00 _sub = 0x00 def __init__(self): self._data = "" def set_command(self, cmd, sub): """Set the command number (and optional subcommand)""" self._cmd = cmd self._sub = sub def get_data(self): """Return the data payload""" return self._data def set_data(self, data): """Set the data payload""" self._data = data def send(self, src, dst, serial, willecho=True): """Send the frame over @serial, using @src and @dst addresses""" raw = struct.pack("BBBBBB", 0xFE, 0xFE, src, dst, self._cmd, self._sub) raw += str(self._data) + chr(0xFD) if DEBUG: print "%02x -> %02x (%i):\n%s" % (src, dst, len(raw), util.hexprint(raw)) serial.write(raw) if willecho: echo = serial.read(len(raw)) if echo != raw and echo: print "Echo differed (%i/%i)" % (len(raw), len(echo)) print util.hexprint(raw) print util.hexprint(echo) def read(self, serial): """Read the frame from @serial""" data = "" while not data.endswith(chr(0xFD)): char = serial.read(1) if not char: print "Read %i bytes total" % len(data) raise errors.RadioError("Timeout") data += char if data == chr(0xFD): raise errors.RadioError("Radio reported error") src, dst = struct.unpack("BB", data[2:4]) if DEBUG: print "%02x <- %02x:\n%s" % (src, dst, util.hexprint(data)) self._cmd = ord(data[4]) self._sub = ord(data[5]) self._data = data[6:-1] return src, dst def get_obj(self): raise errors.RadioError("Generic frame has no structure") class MemFrame(Frame): """A memory frame""" _cmd = 0x1A _sub = 0x00 _loc = 0 def set_location(self, loc): """Set the memory location number""" self._loc = loc self._data = struct.pack(">H", int("%04i" % loc, 16)) def make_empty(self): """Mark as empty so the radio will erase the memory""" self._data = struct.pack(">HB", int("%04i" % self._loc, 16), 0xFF) def is_empty(self): """Return True if memory is marked as empty""" return len(self._data) < 5 def get_obj(self): """Return a bitwise parsed object""" self._data = MemoryMap(str(self._data)) # Make sure we're assignable return bitwise.parse(MEM_FORMAT, self._data) def initialize(self): """Initialize to sane values""" self._data = MemoryMap("".join(["\x00"] * (self.get_obj().size() / 8))) class MultiVFOMemFrame(MemFrame): """A memory frame for radios with multiple VFOs""" def set_location(self, loc, vfo=1): self._loc = loc self._data = struct.pack(">BH", vfo, int("%04i" % loc, 16)) def get_obj(self): self._data = MemoryMap(str(self._data)) # Make sure we're assignable return bitwise.parse(MEM_VFO_FORMAT, self._data) class DupToneMemFrame(MemFrame): def get_obj(self): self._data = MemoryMap(str(self._data)) return bitwise.parse(mem_duptone_format, self._data) class IcomCIVRadio(icf.IcomLiveRadio): """Base class for ICOM CIV-based radios""" BAUD_RATE = 19200 MODEL = "CIV Radio" _model = "\x00" _template = 0 def _send_frame(self, frame): return frame.send(ord(self._model), 0xE0, self.pipe, willecho=self._willecho) def _recv_frame(self, frame=None): if not frame: frame = Frame() frame.read(self.pipe) return frame def _initialize(self): pass def _detect_echo(self): echo_test = "\xfe\xfe\xe0\xe0\xfa\xfd" self.pipe.write(echo_test) resp = self.pipe.read(6) print "Echo:\n%s" % util.hexprint(resp) return resp == echo_test def __init__(self, *args, **kwargs): icf.IcomLiveRadio.__init__(self, *args, **kwargs) self._classes = { "mem" : MemFrame, } if self.pipe: self._willecho = self._detect_echo() print "Interface echo: %s" % self._willecho self.pipe.setTimeout(1) #f = Frame() #f.set_command(0x19, 0x00) #self._send_frame(f) # #res = f.read(self.pipe) #if res: # print "Result: %x->%x (%i)" % (res[0], res[1], len(f.get_data())) # print util.hexprint(f.get_data()) # #self._id = f.get_data()[0] self._rf = chirp_common.RadioFeatures() self._initialize() def get_features(self): return self._rf def _get_template_memory(self): f = self._classes["mem"]() f.set_location(self._template) self._send_frame(f) f.read(self.pipe) return f def get_raw_memory(self, number): f = self._classes["mem"]() f.set_location(number) self._send_frame(f) f.read(self.pipe) return repr(f.get_obj()) def get_memory(self, number): print "Getting %i" % number f = self._classes["mem"]() f.set_location(number) self._send_frame(f) mem = chirp_common.Memory() mem.number = number f = self._recv_frame(f) if len(f.get_data()) == 0: raise errors.RadioError("Radio reported error") if f.get_data() and f.get_data()[-1] == "\xFF": mem.empty = True return mem memobj = f.get_obj() print repr(memobj) mem.freq = int(memobj.freq) mem.mode = self._rf.valid_modes[memobj.mode] if self._rf.has_name: mem.name = str(memobj.name).rstrip() if self._rf.valid_tmodes: mem.tmode = self._rf.valid_tmodes[memobj.tmode] if self._rf.has_dtcs: # FIXME mem.dtcs = bitwise.bcd_to_int([memobj.dtcs]) if "Tone" in self._rf.valid_tmodes: mem.rtone = int(memobj.rtone) / 10.0 if "TSQL" in self._rf.valid_tmodes and self._rf.has_ctone: mem.ctone = int(memobj.ctone) / 10.0 if self._rf.valid_duplexes: mem.duplex = self._rf.valid_duplexes[memobj.duplex] return mem def set_memory(self, mem): f = self._get_template_memory() if mem.empty: f.set_location(mem.number) f.make_empty() self._send_frame(f) return #f.set_data(MemoryMap(self.get_raw_memory(mem.number))) #f.initialize() memobj = f.get_obj() memobj.number = mem.number memobj.freq = int(mem.freq) memobj.mode = self._rf.valid_modes.index(mem.mode) if self._rf.has_name: memobj.name = mem.name.ljust(9)[:9] if self._rf.valid_tmodes: memobj.tmode = self._rf.valid_tmodes.index(mem.tmode) if self._rf.valid_duplexes: memobj.duplex = self._rf.valid_duplexes.index(mem.duplex) if self._rf.has_ctone: memobj.ctone = int(mem.ctone * 10) memobj.rtone = int(mem.rtone * 10) print repr(memobj) self._send_frame(f) f = self._recv_frame() print "Result:\n%s" % util.hexprint(f.get_data()) @directory.register class Icom7200Radio(IcomCIVRadio): """Icom IC-7200""" MODEL = "7200" _model = "\x76" _template = 201 def _initialize(self): self._rf.has_bank = False self._rf.has_dtcs_polarity = False self._rf.has_dtcs = False self._rf.has_ctone = False self._rf.has_offset = False self._rf.has_name = False self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY"] self._rf.valid_tmodes = [] self._rf.valid_duplexes = [] self._rf.valid_bands = [(1800000, 59000000)] self._rf.valid_tuning_steps = [] self._rf.valid_skips = [] self._rf.memory_bounds = (1, 200) @directory.register class Icom7000Radio(IcomCIVRadio): """Icom IC-7000""" MODEL = "7000" _model = "\x70" _template = 102 def _initialize(self): self._classes["mem"] = MultiVFOMemFrame self._rf.has_bank = False self._rf.has_dtcs_polarity = True self._rf.has_dtcs = True self._rf.has_ctone = True self._rf.has_offset = False self._rf.has_name = True self._rf.has_tuning_step = False self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"] self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] self._rf.valid_duplexes = ["", "-", "+"] self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)] self._rf.valid_tuning_steps = [] self._rf.valid_skips = [] self._rf.valid_name_length = 9 self._rf.valid_characters = chirp_common.CHARSET_ASCII self._rf.memory_bounds = (1, 99) @directory.register class Icom746Radio(IcomCIVRadio): """Icom IC-746""" MODEL = "746" BAUD_RATE = 9600 _model = "\x56" _template = 102 def _initialize(self): self._classes["mem"] = DupToneMemFrame self._rf.has_bank = False self._rf.has_dtcs_polarity = False self._rf.has_dtcs = False self._rf.has_ctone = True self._rf.has_offset = False self._rf.has_name = True self._rf.has_tuning_step = False self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"] self._rf.valid_tmodes = ["", "Tone", "TSQL"] self._rf.valid_duplexes = ["", "-", "+"] self._rf.valid_bands = [(30000, 199999999)] self._rf.valid_tuning_steps = [] self._rf.valid_skips = [] self._rf.valid_name_length = 9 self._rf.valid_characters = chirp_common.CHARSET_ASCII self._rf.memory_bounds = (1, 99) CIV_MODELS = { (0x76, 0xE0) : Icom7200Radio, (0x70, 0xE0) : Icom7000Radio, (0x46, 0xE0) : Icom746Radio, } def probe_model(ser): """Probe the radio attatched to @ser for its model""" f = Frame() f.set_command(0x19, 0x00) for model, controller in CIV_MODELS.keys(): f.send(model, controller, ser) try: f.read(ser) except errors.RadioError: continue if len(f.get_data()) == 1: model = ord(f.get_data()[0]) return CIV_MODELS[(model, controller)] if f.get_data(): print "Got data, but not 1 byte:" print util.hexprint(f.get_data()) raise errors.RadioError("Unknown response") raise errors.RadioError("Unsupported model") chirp-0.3.1/chirp/puxing.py0000644000016101777760000003507212067772271017030 0ustar jenkinsnogroup00000000000000# Copyright 2011 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 . """Puxing radios management module""" import time import os from chirp import util, chirp_common, bitwise, errors, directory from chirp.wouxun_common import wipe_memory, do_download, do_upload if os.getenv("CHIRP_DEBUG"): DEBUG = True else: DEBUG = False def _puxing_prep(radio): radio.pipe.write("\x02PROGRA") ack = radio.pipe.read(1) if ack != "\x06": raise Exception("Radio did not ACK first command") radio.pipe.write("M\x02") ident = radio.pipe.read(8) if len(ident) != 8: print util.hexprint(ident) raise Exception("Radio did not send identification") radio.pipe.write("\x06") if radio.pipe.read(1) != "\x06": raise Exception("Radio did not ACK ident") def puxing_prep(radio): """Do the Puxing PX-777 identification dance""" for _i in range(0, 10): try: return _puxing_prep(radio) except Exception, e: time.sleep(1) raise e def puxing_download(radio): """Talk to a Puxing PX-777 and do a download""" try: puxing_prep(radio) return do_download(radio, 0x0000, 0x0C60, 0x0008) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def puxing_upload(radio): """Talk to a Puxing PX-777 and do an upload""" try: puxing_prep(radio) return do_upload(radio, 0x0000, 0x0C40, 0x0008) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Low", watts=1.00)] PUXING_CHARSET = list("0123456789") + \ [chr(x + ord("A")) for x in range(0, 26)] + \ list("- ") PUXING_MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 _3_unknown_1; u8 _2_unknown_1:2, power_high:1, iswide:1, skip:1, bclo:2, _2_unknown_2:1; u8 _4_unknown1:7, pttid:1; u8 unknown; } memory[128]; #seekto 0x080A; struct { u8 limits; u8 model; } model[1]; #seekto 0x0850; struct { u8 name[6]; u8 pad[2]; } names[128]; """ # Limits # 67- 72: 0xEE # 136-174: 0xEF # 240-260: 0xF0 # 350-390: 0xF1 # 400-430: 0xF2 # 430-450: 0xF3 # 450-470: 0xF4 # 470-490: 0xF5 # 400-470: 0xF6 # 460-520: 0xF7 PUXING_MODELS = { 328 : 0x38, 338 : 0x39, 777 : 0x3A, } PUXING_777_BANDS = [ ( 67000000, 72000000), (136000000, 174000000), (240000000, 260000000), (350000000, 390000000), (400000000, 430000000), (430000000, 450000000), (450000000, 470000000), (470000000, 490000000), (400000000, 470000000), (460000000, 520000000), ] @directory.register class Puxing777Radio(chirp_common.CloneModeRadio): """Puxing PX-777""" VENDOR = "Puxing" MODEL = "PX-777" def sync_in(self): self._mmap = puxing_download(self) self.process_mmap() def sync_out(self): puxing_upload(self) def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = ["FM", "NFM"] rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" rf.valid_name_length = 6 rf.has_ctone = False rf.has_tuning_step = False rf.has_bank = False rf.memory_bounds = (1, 128) if not hasattr(self, "_memobj") or self._memobj is None: limit_idx = 1 else: limit_idx = self._memobj.model.limits - 0xEE try: rf.valid_bands = [PUXING_777_BANDS[limit_idx]] except IndexError: print "Invalid band index %i (0x%02x)" % \ (limit_idx, self._memobj.model.limits) rf.valid_bands = [PUXING_777_BANDS[1]] return rf def process_mmap(self): self._memobj = bitwise.parse(PUXING_MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) + "\r\n" + \ repr(self._memobj.names[number - 1]) @classmethod def match_model(cls, filedata, filename): if len(filedata) > 0x080B and \ ord(filedata[0x080B]) != PUXING_MODELS[777]: return False return len(filedata) == 3168 def get_memory(self, number): _mem = self._memobj.memory[number - 1] _nam = self._memobj.names[number - 1] def _is_empty(): for i in range(0, 4): if _mem.rx_freq[i].get_raw() != "\xFF": return False return True def _is_no_tone(field): return field.get_raw() in ["\x00\x00", "\xFF\xFF"] def _get_dtcs(value): # Upper nibble 0x80 -> DCS, 0xC0 -> Inv. DCS if value > 12000: return "R", value - 12000 elif value > 8000: return "N", value - 8000 else: raise Exception("Unable to convert DCS value") def _do_dtcs(mem, txfield, rxfield): if int(txfield) < 8000 or int(rxfield) < 8000: raise Exception("Split tone not supported") if txfield[0].get_raw() == "\xFF": tp, tx = "N", None else: tp, tx = _get_dtcs(int(txfield)) if rxfield[0].get_raw() == "\xFF": rp, rx = "N", None else: rp, rx = _get_dtcs(int(rxfield)) if not rx: rx = tx if not tx: tx = rx if tx != rx: raise Exception("Different RX and TX DCS codes not supported") mem.dtcs = tx mem.dtcs_polarity = "%s%s" % (tp, rp) mem = chirp_common.Memory() mem.number = number if _is_empty(): mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 mem.offset = (int(_mem.tx_freq) * 10) - mem.freq if mem.offset < 0: mem.duplex = "-" elif mem.offset: mem.duplex = "+" mem.offset = abs(mem.offset) if not _mem.skip: mem.skip = "S" if not _mem.iswide: mem.mode = "NFM" if _is_no_tone(_mem.tx_tone): pass # No tone elif int(_mem.tx_tone) > 8000 or \ (not _is_no_tone(_mem.rx_tone) and int(_mem.rx_tone) > 8000): mem.tmode = "DTCS" _do_dtcs(mem, _mem.tx_tone, _mem.rx_tone) else: mem.rtone = int(_mem.tx_tone) / 10.0 mem.tmode = _is_no_tone(_mem.rx_tone) and "Tone" or "TSQL" mem.power = POWER_LEVELS[not _mem.power_high] for i in _nam.name: if i == 0xFF: break mem.name += PUXING_CHARSET[i] return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] _nam = self._memobj.names[mem.number - 1] if mem.empty: wipe_memory(_mem, "\xFF") return _mem.rx_freq = mem.freq / 10 if mem.duplex == "+": _mem.tx_freq = (mem.freq / 10) + (mem.offset / 10) elif mem.duplex == "-": _mem.tx_freq = (mem.freq / 10) - (mem.offset / 10) else: _mem.tx_freq = (mem.freq / 10) _mem.skip = mem.skip != "S" _mem.iswide = mem.mode != "NFM" _mem.rx_tone[0].set_raw("\xFF") _mem.rx_tone[1].set_raw("\xFF") _mem.tx_tone[0].set_raw("\xFF") _mem.tx_tone[1].set_raw("\xFF") if mem.tmode == "DTCS": _mem.tx_tone = int("%x" % int("%i" % (mem.dtcs), 16)) _mem.rx_tone = int("%x" % int("%i" % (mem.dtcs), 16)) # Argh. Set the high order two bits to signal DCS or Inv. DCS txm = mem.dtcs_polarity[0] == "N" and 0x80 or 0xC0 rxm = mem.dtcs_polarity[1] == "N" and 0x80 or 0xC0 _mem.tx_tone[1].set_raw(chr(ord(_mem.tx_tone[1].get_raw()) | txm)) _mem.rx_tone[1].set_raw(chr(ord(_mem.rx_tone[1].get_raw()) | rxm)) elif mem.tmode: _mem.tx_tone = int(mem.rtone * 10) if mem.tmode == "TSQL": _mem.rx_tone = int(_mem.tx_tone) if mem.power: _mem.power_high = not POWER_LEVELS.index(mem.power) else: _mem.power_high = True # Default to disabling the busy channel lockout # 00 == Close # 01 == Carrier # 10 == QT/DQT _mem.bclo = 0 _nam.name = [0xFF] * 6 for i in range(0, len(mem.name)): try: _nam.name[i] = PUXING_CHARSET.index(mem.name[i]) except IndexError: raise Exception("Character `%s' not supported") def puxing_2r_prep(radio): """Do the Puxing 2R identification dance""" radio.pipe.setTimeout(0.2) radio.pipe.write("PROGRAM\x02") ack = radio.pipe.read(1) if ack != "\x06": raise Exception("Radio is not responding") radio.pipe.write(ack) ident = radio.pipe.read(16) print "Radio ident: %s (%i)" % (repr(ident), len(ident)) def puxing_2r_download(radio): """Talk to a Puxing 2R and do a download""" try: puxing_2r_prep(radio) return do_download(radio, 0x0000, 0x0FE0, 0x0010) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def puxing_2r_upload(radio): """Talk to a Puxing 2R and do an upload""" try: puxing_2r_prep(radio) return do_upload(radio, 0x0000, 0x0FE0, 0x0010) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) PUXING_2R_MEM_FORMAT = """ #seekto 0x0010; struct { lbcd freq[4]; lbcd offset[4]; u8 rx_tone; u8 tx_tone; u8 duplex:2, txdtcsinv:1, rxdtcsinv:1, simplex:1, unknown2:1, iswide:1, ishigh:1; u8 name[5]; } memory[128]; """ PX2R_DUPLEX = ["", "+", "-", ""] PX2R_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.0), chirp_common.PowerLevel("High", watts=2.0)] PX2R_CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ +" @directory.register class Puxing2RRadio(chirp_common.CloneModeRadio): """Puxing PX-2R""" VENDOR = "Puxing" MODEL = "PX-2R" _memsize = 0x0FE0 def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_modes = ["FM", "NFM"] rf.valid_power_levels = PX2R_POWER_LEVELS rf.valid_bands = [(400000000, 500000000)] rf.valid_characters = PX2R_CHARSET rf.valid_name_length = 5 rf.valid_duplexes = ["", "+", "-"] rf.valid_skips = [] rf.has_ctone = False rf.has_tuning_step = False rf.has_bank = False rf.memory_bounds = (1, 128) rf.can_odd_split = False return rf @classmethod def match_model(cls, filedata, filename): return (len(filedata) == cls._memsize) and \ filedata[-16:] != "IcomCloneFormat3" def sync_in(self): self._mmap = puxing_2r_download(self) self.process_mmap() def sync_out(self): puxing_2r_upload(self) def process_mmap(self): self._memobj = bitwise.parse(PUXING_2R_MEM_FORMAT, self._mmap) def get_memory(self, number): _mem = self._memobj.memory[number-1] mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[0:4] == "\xff\xff\xff\xff": mem.empty = True return mem mem.freq = int(_mem.freq) * 10 mem.offset = int(_mem.offset) * 10 mem.mode = _mem.iswide and "FM" or "NFM" mem.duplex = PX2R_DUPLEX[_mem.duplex] mem.power = PX2R_POWER_LEVELS[_mem.ishigh] if _mem.tx_tone >= 0x33: mem.dtcs = chirp_common.DTCS_CODES[_mem.tx_tone - 0x33] mem.tmode = "DTCS" mem.dtcs_polarity = \ (_mem.txdtcsinv and "R" or "N") + \ (_mem.rxdtcsinv and "R" or "N") elif _mem.tx_tone: mem.rtone = chirp_common.TONES[_mem.tx_tone - 1] mem.tmode = _mem.rx_tone and "TSQL" or "Tone" count = 0 for i in _mem.name: if i == 0xFF: break try: mem.name += PX2R_CHARSET[i] except Exception: print "Unknown name char %i: 0x%02x (mem %i)" % (count, i, number) mem.name += " " count += 1 mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] if mem.empty: _mem.set_raw("\xff" * 16) return _mem.freq = mem.freq / 10 _mem.offset = mem.offset / 10 _mem.iswide = mem.mode == "FM" _mem.duplex = PX2R_DUPLEX.index(mem.duplex) _mem.ishigh = mem.power == PX2R_POWER_LEVELS[1] if mem.tmode == "DTCS": _mem.tx_tone = chirp_common.DTCS_CODES.index(mem.dtcs) + 0x33 _mem.rx_tone = chirp_common.DTCS_CODES.index(mem.dtcs) + 0x33 _mem.txdtcsinv = mem.dtcs_polarity[0] == "R" _mem.rxdtcsinv = mem.dtcs_polarity[1] == "R" elif mem.tmode in ["Tone", "TSQL"]: _mem.tx_tone = chirp_common.TONES.index(mem.rtone) + 1 _mem.rx_tone = mem.tmode == "TSQL" and int(_mem.tx_tone) or 0 else: _mem.tx_tone = 0 _mem.rx_tone = 0 for i in range(0, 5): try: _mem.name[i] = PX2R_CHARSET.index(mem.name[i]) except IndexError: _mem.name[i] = 0xFF def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) chirp-0.3.1/chirp/radioreference.py0000644000016100007500000001370312023560645017017 0ustar jenkins00000000000000# Copyright 2012 Tom Hayward # # 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 chirp import chirp_common, errors try: from suds.client import Client HAVE_SUDS = True except ImportError: HAVE_SUDS = False MODES = { "FM" : "FM", "AM" : "AM", "FMN" : "NFM", "D-STAR": "DV", "USB" : "USB", "LSB" : "LSB", "P25" : "P25", } class RadioReferenceRadio(chirp_common.NetworkSourceRadio): """RadioReference.com data source""" VENDOR = "Radio Reference LLC" MODEL = "RadioReference.com" URL = "http://api.radioreference.com/soap2/?wsdl" APPKEY = "46785108" def __init__(self, *args, **kwargs): chirp_common.NetworkSourceRadio.__init__(self, *args, **kwargs) if not HAVE_SUDS: raise errors.RadioError( "Suds library required for RadioReference.com import.\n" + \ "Try installing your distribution's python-suds package.") self._auth = {"appKey": self.APPKEY, "username": "", "password": ""} self._client = Client(self.URL) self._freqs = None self._modes = None self._zip = None def set_params(self, zipcode, username, password): """Set the parameters to be used for a query""" self._zip = zipcode self._auth["username"] = username self._auth["password"] = password def do_fetch(self): """Fetches frequencies for all subcategories in a county.""" self._freqs = [] zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth) county = self._client.service.getCountyInfo(zipcode.ctid, self._auth) status = chirp_common.Status() status.max = 0 for cat in county.cats: status.max += len(cat.subcats) status.max += len(county.agencyList) for cat in county.cats: print "Fetching category:", cat.cName for subcat in cat.subcats: print "\t", subcat.scName result = self._client.service.getSubcatFreqs(subcat.scid, self._auth) self._freqs += result status.cur += 1 self.status_fn(status) status.max -= len(county.agencyList) for agency in county.agencyList: agency = self._client.service.getAgencyInfo(agency.aid, self._auth) for cat in agency.cats: status.max += len(cat.subcats) for cat in agency.cats: print "Fetching category:", cat.cName for subcat in cat.subcats: print "\t", subcat.scName result = self._client.service.getSubcatFreqs(subcat.scid, self._auth) self._freqs += result status.cur += 1 self.status_fn(status) def get_features(self): if not self._freqs: self.do_fetch() rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, len(self._freqs)-1) rf.has_bank = False rf.has_ctone = False rf.valid_tmodes = ["", "TSQL", "DTCS"] return rf def get_raw_memory(self, number): return repr(self._freqs[number]) def get_memory(self, number): if not self._freqs: self.do_fetch() freq = self._freqs[number] mem = chirp_common.Memory() mem.number = number mem.name = freq.alpha or freq.descr or "" mem.freq = chirp_common.parse_freq(str(freq.out)) if freq["in"] == 0.0: mem.duplex = "" else: mem.duplex = "split" mem.offset = chirp_common.parse_freq(str(freq["in"])) if freq.tone is not None: if str(freq.tone) == "CSQ": # Carrier Squelch mem.tmode = "" else: try: tone, tmode = freq.tone.split(" ") except Exception: tone, tmode = None, None if tmode == "PL": mem.tmode = "TSQL" mem.rtone = mem.ctone = float(tone) elif tmode == "DPL": mem.tmode = "DTCS" mem.dtcs = int(tone) else: print "Error: unsupported tone" print freq try: mem.mode = self._get_mode(freq.mode) except KeyError: # skip memory if mode is unsupported mem.empty = True return mem mem.comment = freq.descr.strip() return mem def _get_mode(self, modeid): if not self._modes: self._modes = {} for mode in self._client.service.getMode("0", self._auth): # sax.text.Text cannot be coerced directly to int self._modes[int(str(mode.mode))] = str(mode.modeName) return MODES[self._modes[int(str(modeid))]] def main(): """ Usage: cd ~/src/chirp.hg python ./chirp/radioreference.py [ZIPCODE] [USERNAME] [PASSWORD] """ import sys rrr = RadioReferenceRadio(None) rrr.set_params(zipcode=sys.argv[1], username=sys.argv[2], password=sys.argv[3]) rrr.do_fetch() print rrr.get_raw_memory(0) print rrr.get_memory(0) if __name__ == "__main__": main() chirp-0.3.1/chirp/xml_ll.py0000644000016100007500000001746512023560645015342 0ustar jenkins00000000000000# Copyright 2008 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 . import re from chirp import chirp_common, errors def get_memory(doc, number): """Extract a Memory object from @doc""" ctx = doc.xpathNewContext() base = "//radio/memories/memory[@location=%i]" % number fields = ctx.xpathEval(base) if len(fields) > 1: raise errors.RadioError("%i memories claiming to be %i" % (len(fields), number)) elif len(fields) == 0: raise errors.InvalidMemoryLocation("%i does not exist" % number) memnode = fields[0] def _get(ext): path = base + ext result = ctx.xpathEval(path) if result: return result[0].getContent() else: return "" if _get("/mode/text()") == "DV": mem = chirp_common.DVMemory() mem.dv_urcall = _get("/dv/urcall/text()") mem.dv_rpt1call = _get("/dv/rpt1call/text()") mem.dv_rpt2call = _get("/dv/rpt2call/text()") try: mem.dv_code = _get("/dv/digitalCode/text()") except ValueError: mem.dv_code = 0 else: mem = chirp_common.Memory() mem.number = int(memnode.prop("location")) mem.name = _get("/longName/text()") mem.freq = chirp_common.parse_freq(_get("/frequency/text()")) mem.rtone = float(_get("/squelch[@id='rtone']/tone/text()")) mem.ctone = float(_get("/squelch[@id='ctone']/tone/text()")) mem.dtcs = int(_get("/squelch[@id='dtcs']/code/text()"), 10) mem.dtcs_polarity = _get("/squelch[@id='dtcs']/polarity/text()") try: sql = _get("/squelchSetting/text()") if sql == "rtone": mem.tmode = "Tone" elif sql == "ctone": mem.tmode = "TSQL" elif sql == "dtcs": mem.tmode = "DTCS" else: mem.tmode = "" except IndexError: mem.tmode = "" dmap = {"positive" : "+", "negative" : "-", "none" : ""} dupx = _get("/duplex/text()") mem.duplex = dmap.get(dupx, "") mem.offset = chirp_common.parse_freq(_get("/offset/text()")) mem.mode = _get("/mode/text()") mem.tuning_step = float(_get("/tuningStep/text()")) skip = _get("/skip/text()") if skip == "none": mem.skip = "" else: mem.skip = skip #FIXME: bank support in .chirp files needs to be re-written #bank_id = _get("/bank/@bankId") #if bank_id: # mem.bank = int(bank_id) # bank_index = _get("/bank/@bankIndex") # if bank_index: # mem.bank_index = int(bank_index) return mem def set_memory(doc, mem): """Set @mem in @doc""" ctx = doc.xpathNewContext() base = "//radio/memories/memory[@location=%i]" % mem.number fields = ctx.xpathEval(base) if len(fields) > 1: raise errors.RadioError("%i memories claiming to be %i" % (len(fields), mem.number)) elif len(fields) == 1: fields[0].unlinkNode() radio = ctx.xpathEval("//radio/memories")[0] memnode = radio.newChild(None, "memory", None) memnode.newProp("location", "%i" % mem.number) sname_filter = "[^A-Z0-9/ >-]" sname = memnode.newChild(None, "shortName", None) sname.addContent(re.sub(sname_filter, "", mem.name.upper()[:6])) lname_filter = "[^.A-Za-z0-9/ >-]" lname = memnode.newChild(None, "longName", None) lname.addContent(re.sub(lname_filter, "", mem.name[:16])) freq = memnode.newChild(None, "frequency", None) freq.newProp("units", "MHz") freq.addContent(chirp_common.format_freq(mem.freq)) rtone = memnode.newChild(None, "squelch", None) rtone.newProp("id", "rtone") rtone.newProp("type", "repeater") tone = rtone.newChild(None, "tone", None) tone.addContent("%.1f" % mem.rtone) ctone = memnode.newChild(None, "squelch", None) ctone.newProp("id", "ctone") ctone.newProp("type", "ctcss") tone = ctone.newChild(None, "tone", None) tone.addContent("%.1f" % mem.ctone) dtcs = memnode.newChild(None, "squelch", None) dtcs.newProp("id", "dtcs") dtcs.newProp("type", "dtcs") code = dtcs.newChild(None, "code", None) code.addContent("%03i" % mem.dtcs) polr = dtcs.newChild(None, "polarity", None) polr.addContent(mem.dtcs_polarity) sset = memnode.newChild(None, "squelchSetting", None) if mem.tmode == "Tone": sset.addContent("rtone") elif mem.tmode == "TSQL": sset.addContent("ctone") elif mem.tmode == "DTCS": sset.addContent("dtcs") dmap = {"+" : "positive", "-" : "negative", "" : "none"} dupx = memnode.newChild(None, "duplex", None) dupx.addContent(dmap[mem.duplex]) oset = memnode.newChild(None, "offset", None) oset.newProp("units", "MHz") oset.addContent(chirp_common.format_freq(mem.offset)) mode = memnode.newChild(None, "mode", None) mode.addContent(mem.mode) step = memnode.newChild(None, "tuningStep", None) step.newProp("units", "kHz") step.addContent("%.5f" % mem.tuning_step) if mem.skip: skip = memnode.newChild(None, "skip", None) skip.addContent(mem.skip) #FIXME: .chirp bank support needs to be redone #if mem.bank is not None: # bank = memnode.newChild(None, "bank", None) # bank.newProp("bankId", str(int(mem.bank))) # if mem.bank_index >= 0: # bank.newProp("bankIndex", str(int(mem.bank_index))) if isinstance(mem, chirp_common.DVMemory): dv = memnode.newChild(None, "dv", None) ur = dv.newChild(None, "urcall", None) ur.addContent(mem.dv_urcall) r1 = dv.newChild(None, "rpt1call", None) if mem.dv_rpt1call and mem.dv_rpt1call != "*NOTUSE*": r1.addContent(mem.dv_rpt1call) r2 = dv.newChild(None, "rpt2call", None) if mem.dv_rpt2call and mem.dv_rpt2call != "*NOTUSE*": r2.addContent(mem.dv_rpt2call) dc = dv.newChild(None, "digitalCode", None) dc.addContent(str(mem.dv_code)) def del_memory(doc, number): """Remove memory @number from @doc""" path = "//radio/memories/memory[@location=%i]" % number ctx = doc.xpathNewContext() fields = ctx.xpathEval(path) for field in fields: field.unlinkNode() def _get_bank(node): bank = chirp_common.Bank(node.prop("label")) ident = int(node.prop("id")) return ident, bank def get_banks(doc): """Return a list of banks from @doc""" path = "//radio/banks/bank" ctx = doc.xpathNewContext() fields = ctx.xpathEval(path) banks = [] for field in fields: banks.append(_get_bank(field)) def _cmp(itema, itemb): return itema[0] - itemb[0] banks.sort(cmp=_cmp) return [x[1] for x in banks] def set_banks(doc, banklist): """Set the list of banks in @doc""" path = "//radio/banks/bank" ctx = doc.xpathNewContext() fields = ctx.xpathEval(path) for field in fields: field.unlinkNode() path = "//radio/banks" ctx = doc.xpathNewContext() banks = ctx.xpathEval(path)[0] i = 0 for bank in banklist: banknode = banks.newChild(None, "bank", None) banknode.newProp("id", "%i" % i) banknode.newProp("label", "%s" % bank) i += 1 chirp-0.3.1/chirp/ft7800.py0000644000016101777760000004344112130403635016427 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . import time from chirp import chirp_common, yaesu_clone, memmap, directory from chirp import bitwise, errors from collections import defaultdict ACK = chr(0x06) MEM_FORMAT = """ #seekto 0x04C8; struct { u8 used:1, unknown1:1, mode:2, unknown2:1, duplex:3; bbcd freq[3]; u8 unknown3:1, tune_step:3, unknown5:2, tmode:2; bbcd split[3]; u8 power:2, tone:6; u8 unknown6:1, dtcs:7; u8 unknown7[2]; u8 offset; u8 unknown9[3]; } memory[1000]; #seekto 0x4988; struct { char name[6]; u8 enabled:1, unknown1:7; u8 used:1, unknown2:7; } names[1000]; #seekto 0x6c48; struct { u32 bitmap[32]; } bank_channels[20]; #seekto 0x7648; struct { u8 skip0:2, skip1:2, skip2:2, skip3:2; } flags[250]; #seekto 0x7B48; u8 checksum; """ MODES = ["FM", "AM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "", "-", "+", "split"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] SKIPS = ["", "S", "P", ""] CHARSET = ["%i" % int(x) for x in range(0, 10)] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ list(" " * 10) + \ list("*+,- /| [ ] _") + \ list("\x00" * 100) POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50), chirp_common.PowerLevel("Mid1", watts=20), chirp_common.PowerLevel("Mid2", watts=10), chirp_common.PowerLevel("Low", watts=5)] POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35), chirp_common.PowerLevel("Mid1", watts=20), chirp_common.PowerLevel("Mid2", watts=10), chirp_common.PowerLevel("Low", watts=5)] def _send(ser, data): for i in data: ser.write(i) time.sleep(0.002) echo = ser.read(len(data)) if echo != data: raise errors.RadioError("Error reading echo (Bad cable?)") def _download(radio): data = "" chunk = "" for i in range(0, 30): chunk += radio.pipe.read(radio._block_lengths[0]) if chunk: break if len(chunk) != radio._block_lengths[0]: raise Exception("Failed to read header (%i)" % len(chunk)) data += chunk _send(radio.pipe, ACK) for i in range(0, radio._block_lengths[1], 64): chunk = radio.pipe.read(64) data += chunk if len(chunk) != 64: break time.sleep(0.01) _send(radio.pipe, ACK) if radio.status_fn: status = chirp_common.Status() status.max = radio.get_memsize() status.cur = i+len(chunk) status.msg = "Cloning from radio" radio.status_fn(status) data += radio.pipe.read(1) _send(radio.pipe, ACK) return memmap.MemoryMap(data) def _upload(radio): cur = 0 for block in radio._block_lengths: for _i in range(0, block, 64): length = min(64, block) #print "i=%i length=%i range: %i-%i" % (i, length, # cur, cur+length) _send(radio.pipe, radio.get_mmap()[cur:cur+length]) if radio.pipe.read(1) != ACK: raise errors.RadioError("Radio did not ack block at %i" % cur) cur += length time.sleep(0.05) if radio.status_fn: status = chirp_common.Status() status.cur = cur status.max = radio.get_memsize() status.msg = "Cloning to radio" radio.status_fn(status) def get_freq(rawfreq): """Decode a frequency that may include a fractional step flag""" # Ugh. The 0x80 and 0x40 indicate values to add to get the # real frequency. Gross. if rawfreq > 8000000000: rawfreq = (rawfreq - 8000000000) + 5000 if rawfreq > 4000000000: rawfreq = (rawfreq - 4000000000) + 2500 return rawfreq def set_freq(freq, obj, field): """Encode a frequency with any necessary fractional step flags""" obj[field] = freq / 10000 if (freq % 1000) == 500: obj[field][0].set_bits(0x40) if (freq % 10000) >= 5000: obj[field][0].set_bits(0x80) return freq class FTx800Radio(yaesu_clone.YaesuCloneModeRadio): """Base class for FT-7800,7900,8800,8900 radios""" BAUD_RATE = 9600 VENDOR = "Yaesu" def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 999) rf.has_bank = False rf.has_ctone = False rf.has_dtcs_polarity = False rf.valid_modes = MODES rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_duplexes = ["", "-", "+", "split"] rf.valid_tuning_steps = STEPS rf.valid_bands = [(108000000, 520000000), (700000000, 990000000)] rf.valid_skips = ["", "S", "P"] rf.valid_power_levels = POWER_LEVELS_VHF rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 6 rf.can_odd_split = True return rf def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x7B47) ] def sync_in(self): start = time.time() try: self._mmap = _download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) print "Download finished in %i seconds" % (time.time() - start) self.check_checksums() self.process_mmap() def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_out(self): self.update_checksums() start = time.time() try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) print "Upload finished in %i seconds" % (time.time() - start) def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def _get_mem_offset(self, mem, _mem): if mem.duplex == "split": return get_freq(int(_mem.split) * 10000) else: return (_mem.offset * 5) * 10000 def _set_mem_offset(self, mem, _mem): if mem.duplex == "split": set_freq(mem.offset, _mem, "split") else: _mem.offset = (int(mem.offset / 10000) / 5) def _get_mem_name(self, mem, _mem): _nam = self._memobj.names[mem.number - 1] name = "" if _nam.used: for i in str(_nam.name): name += CHARSET[ord(i)] return name.rstrip() def _set_mem_name(self, mem, _mem): _nam = self._memobj.names[mem.number - 1] if mem.name.rstrip(): name = [chr(CHARSET.index(x)) for x in mem.name.ljust(6)[:6]] _nam.name = "".join(name) _nam.used = 1 _nam.enabled = 1 else: _nam.used = 0 _nam.enabled = 0 def _get_mem_skip(self, mem, _mem): _flg = self._memobj.flags[(mem.number - 1) / 4] flgidx = (mem.number - 1) % 4 return SKIPS[_flg["skip%i" % flgidx]] def _set_mem_skip(self, mem, _mem): _flg = self._memobj.flags[(mem.number - 1) / 4] flgidx = (mem.number - 1) % 4 _flg["skip%i" % flgidx] = SKIPS.index(mem.skip) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.empty = not _mem.used if mem.empty: return mem mem.freq = get_freq(int(_mem.freq) * 10000) mem.rtone = chirp_common.TONES[_mem.tone] mem.tmode = TMODES[_mem.tmode] mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] if self.get_features().has_tuning_step: mem.tuning_step = STEPS[_mem.tune_step] mem.duplex = DUPLEX[_mem.duplex] mem.offset = self._get_mem_offset(mem, _mem) mem.name = self._get_mem_name(mem, _mem) if int(mem.freq / 100) == 4: mem.power = POWER_LEVELS_UHF[_mem.power] else: mem.power = POWER_LEVELS_VHF[_mem.power] mem.skip = self._get_mem_skip(mem, _mem) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] _mem.used = int(not mem.empty) if mem.empty: return set_freq(mem.freq, _mem, "freq") _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.tmode = TMODES.index(mem.tmode) _mem.mode = MODES.index(mem.mode) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) if self.get_features().has_tuning_step: _mem.tune_step = STEPS.index(mem.tuning_step) _mem.duplex = DUPLEX.index(mem.duplex) _mem.split = mem.duplex == "split" and int (mem.offset / 10000) or 0 if mem.power: _mem.power = POWER_LEVELS_VHF.index(mem.power) else: _mem.power = 0 _mem.unknown5 = 0 # Make sure we don't leave garbage here # NB: Leave offset after mem name for the 8800! self._set_mem_name(mem, _mem) self._set_mem_offset(mem, _mem) self._set_mem_skip(mem, _mem) class FT7800BankModel(chirp_common.BankModel): """Yaesu FT-7800/7900 bank model""" def __init__(self, radio): chirp_common.BankModel.__init__(self, radio) self.__b2m_cache = defaultdict(list) self.__m2b_cache = defaultdict(list) def __precache(self): if self.__b2m_cache: return for bank in self.get_banks(): self.__b2m_cache[bank.index] = self._get_bank_memories(bank) for memnum in self.__b2m_cache[bank.index]: self.__m2b_cache[memnum].append(bank.index) def get_num_banks(self): return 20 def get_banks(self): banks = [] for i in range(0, self.get_num_banks()): bank = chirp_common.Bank(self, "%i" % i, "BANK-%i" % (i + 1)) bank.index = i banks.append(bank) return banks def add_memory_to_bank(self, memory, bank): self.__precache() index = memory.number - 1 _bitmap = self._radio._memobj.bank_channels[bank.index] ishft = 31 - (index % 32) _bitmap.bitmap[index / 32] |= (1 << ishft) self.__m2b_cache[memory.number].append(bank.index) self.__b2m_cache[bank.index].append(memory.number) def remove_memory_from_bank(self, memory, bank): self.__precache() index = memory.number - 1 _bitmap = self._radio._memobj.bank_channels[bank.index] ishft = 31 - (index % 32) if not (_bitmap.bitmap[index / 32] & (1 << ishft)): raise Exception("Memory {num} is " + "not in bank {bank}".format(num=memory.number, bank=bank)) _bitmap.bitmap[index / 32] &= ~(1 << ishft) self.__b2m_cache[bank.index].remove(memory.number) self.__m2b_cache[memory.number].remove(bank.index) def _get_bank_memories(self, bank): memories = [] upper = self._radio.get_features().memory_bounds[1] for i in range(0, upper): _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i/32] ishft = 31 - (i % 32) if _bitmap & (1 << ishft): memories.append(i + 1) return memories def get_bank_memories(self, bank): self.__precache() return [self._radio.get_memory(n) for n in self.__b2m_cache[bank.index]] def get_memory_banks(self, memory): self.__precache() _banks = self.get_banks() return [_banks[b] for b in self.__m2b_cache[memory.number]] @directory.register class FT7800Radio(FTx800Radio): """Yaesu FT-7800""" MODEL = "FT-7800" _model = "AH016" _memsize = 31561 def get_bank_model(self): return FT7800BankModel(self) def get_features(self): rf = FTx800Radio.get_features(self) rf.has_bank = True return rf def set_memory(self, memory): if memory.empty: self._wipe_memory_banks(memory) FTx800Radio.set_memory(self, memory) class FT7900Radio(FT7800Radio): """Yaesu FT-7900""" MODEL = "FT-7900" MEM_FORMAT_8800 = """ #seekto 0x%X; struct { u8 used:1, unknown1:1, mode:2, unknown2:1, duplex:3; bbcd freq[3]; u8 unknown3:1, tune_step:3, power:2, tmode:2; bbcd split[3]; u8 nameused:1, unknown5:1, tone:6; u8 namevalid:1, dtcs:7; u8 name[6]; } memory[500]; #seekto 0x51C8; struct { u8 skip0:2, skip1:2, skip2:2, skip3:2; } flags[250]; #seekto 0x%X; struct { u32 bitmap[16]; } bank_channels[10]; #seekto 0x7B48; u8 checksum; """ class FT8800BankModel(FT7800BankModel): def get_num_banks(self): return 10 @directory.register class FT8800Radio(FTx800Radio): """Base class for Yaesu FT-8800""" MODEL = "FT-8800" _model = "AH018" _memsize = 22217 _block_lengths = [8, 22208, 1] _block_size = 64 _memstart = 0x0000 def get_features(self): rf = FTx800Radio.get_features(self) rf.has_sub_devices = self.VARIANT == "" rf.has_bank = True rf.memory_bounds = (1, 500) return rf def get_sub_devices(self): return [FT8800RadioLeft(self._mmap), FT8800RadioRight(self._mmap)] def get_bank_model(self): return FT8800BankModel(self) def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x56C7) ] def process_mmap(self): if not self._memstart: return self._memobj = bitwise.parse(MEM_FORMAT_8800 % (self._memstart, self._bankstart), self._mmap) def _get_mem_offset(self, mem, _mem): if mem.duplex == "split": return get_freq(int(_mem.split) * 10000) # The offset is packed into the upper two bits of the last four # bytes of the name (?!) val = 0 for i in _mem.name[2:6]: val <<= 2 val |= ((i & 0xC0) >> 6) return (val * 5) * 10000 def _set_mem_offset(self, mem, _mem): if mem.duplex == "split": set_freq(mem.offset, _mem, "split") return val = int(mem.offset / 10000) / 5 for i in reversed(range(2, 6)): _mem.name[i] = (_mem.name[i] & 0x3F) | ((val & 0x03) << 6) val >>= 2 def _get_mem_name(self, mem, _mem): name = "" if _mem.namevalid: for i in _mem.name: index = int(i) & 0x3F if index < len(CHARSET): name += CHARSET[index] return name.rstrip() def _set_mem_name(self, mem, _mem): _mem.name = [CHARSET.index(x) for x in mem.name.ljust(6)[:6]] _mem.namevalid = 1 _mem.nameused = bool(mem.name.rstrip()) class FT8800RadioLeft(FT8800Radio): """Yaesu FT-8800 Left VFO subdevice""" VARIANT = "Left" _memstart = 0x0948 _bankstart = 0x4BC8 class FT8800RadioRight(FT8800Radio): """Yaesu FT-8800 Right VFO subdevice""" VARIANT = "Right" _memstart = 0x2948 _bankstart = 0x4BC8 MEM_FORMAT_8900 = """ #seekto 0x0708; struct { u8 used:1, skip:2, sub_used:1, unknown2:1, duplex:3; bbcd freq[3]; u8 mode:2, nameused:1, unknown4:1, power:2, tmode:2; bbcd split[3]; u8 unknown5:2, tone:6; u8 namevalid:1, dtcs:7; u8 name[6]; } memory[799]; #seekto 0x51C8; struct { u8 skip0:2, skip1:2, skip2:2, skip3:2; } flags[400]; #seekto 0x7B48; u8 checksum; """ @directory.register class FT8900Radio(FT8800Radio): """Yaesu FT-8900""" MODEL = "FT-8900" _model = "AH008" _memsize = 14793 _block_lengths = [8, 14784, 1] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT_8900, self._mmap) def get_features(self): rf = FT8800Radio.get_features(self) rf.has_sub_devices = False rf.has_bank = False rf.valid_modes = MODES rf.valid_bands = [( 28000000, 29700000), ( 50000000, 54000000), (108000000, 180000000), (320000000, 480000000), (700000000, 985000000)] rf.memory_bounds = (1, 799) rf.has_tuning_step = False return rf def _checksums(self): return [ yaesu_clone.YaesuChecksum(0x0000, 0x39C7) ] def _get_mem_skip(self, mem, _mem): return SKIPS[_mem.skip] def _set_mem_skip(self, mem, _mem): _mem.skip = SKIPS.index(mem.skip) def get_memory(self, number): mem = FT8800Radio.get_memory(self, number) _mem = self._memobj.memory[number - 1] return mem def set_memory(self, mem): FT8800Radio.set_memory(self, mem) # The 8900 has a bit flag that tells the radio whether or not # the memory should show up on the sub (right) band _mem = self._memobj.memory[mem.number - 1] if mem.freq < 108000000 or mem.freq > 480000000: _mem.sub_used = 0 else: _mem.sub_used = 1 chirp-0.3.1/chirp/idrp.py0000644000016100007500000001114712023560645015000 0ustar jenkins00000000000000# Copyright 2008 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 . import serial from chirp import chirp_common, errors from chirp import util DEBUG_IDRP = False def parse_frames(buf): """Parse frames from the radio""" frames = [] while "\xfe\xfe" in buf: try: start = buf.index("\xfe\xfe") end = buf[start:].index("\xfd") + start + 1 except Exception: print "Unable to parse frames" break frames.append(buf[start:end]) buf = buf[end:] return frames def send(pipe, buf): """Send data in @buf to @pipe""" pipe.write("\xfe\xfe%s\xfd" % buf) pipe.flush() data = "" while True: buf = pipe.read(4096) if not buf: break data += buf if DEBUG_IDRP: print "Got: \n%s" % util.hexprint(buf) return parse_frames(data) def send_magic(pipe): """Send the magic wakeup call to @pipe""" send(pipe, ("\xfe" * 15) + "\x01\x7f\x19") def drain(pipe): """Chew up any data waiting on @pipe""" while True: buf = pipe.read(4096) if not buf: break def set_freq(pipe, freq): """Set the frequency of the radio on @pipe to @freq""" freqbcd = util.bcd_encode(freq, bigendian=False, width=9) buf = "\x01\x7f\x05" + freqbcd drain(pipe) send_magic(pipe) resp = send(pipe, buf) for frame in resp: if len(frame) == 6: if frame[4] == "\xfb": return True raise errors.InvalidDataError("Repeater reported error") def get_freq(pipe): """Get the frequency of the radio attached to @pipe""" buf = "\x01\x7f\x1a\x09" drain(pipe) send_magic(pipe) resp = send(pipe, buf) for frame in resp: if frame[4] == "\x03": els = frame[5:10] freq = int("%02x%02x%02x%02x%02x" % (ord(els[4]), ord(els[3]), ord(els[2]), ord(els[1]), ord(els[0]))) if DEBUG_IDRP: print "Freq: %f" % freq return freq raise errors.InvalidDataError("No frequency frame received") RP_IMMUTABLE = ["number", "skip", "bank", "extd_number", "name", "rtone", "ctone", "dtcs", "tmode", "dtcs_polarity", "skip", "duplex", "offset", "mode", "tuning_step", "bank_index"] class IDRPx000V(chirp_common.LiveRadio): """Icom IDRP-*""" BAUD_RATE = 19200 VENDOR = "Icom" MODEL = "ID-2000V/4000V/2D/2V" _model = "0000" # Unknown mem_upper_limit = 0 def get_features(self): rf = chirp_common.RadioFeatures() rf.valid_modes = ["DV"] rf.valid_tmodes = [] rf.valid_characters = "" rf.valid_duplexes = [""] rf.valid_name_length = 0 rf.valid_skips = [] rf.valid_tuning_steps = [] rf.has_bank = False rf.has_ctone = False rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_mode = False rf.has_name = False rf.has_offset = False rf.has_tuning_step = False rf.memory_bounds = (0, 0) return rf def get_memory(self, number): if number != 0: raise errors.InvalidMemoryLocation("Repeaters have only one slot") mem = chirp_common.Memory() mem.number = 0 mem.freq = get_freq(self.pipe) mem.name = "TX/RX" mem.mode = "DV" mem.offset = 0.0 mem.immutable = RP_IMMUTABLE return mem def set_memory(self, mem): if mem.number != 0: raise errors.InvalidMemoryLocation("Repeaters have only one slot") set_freq(self.pipe, mem.freq) def do_test(): """Get the frequency of /dev/icom""" ser = serial.Serial(port="/dev/icom", baudrate=19200, timeout=0.5) #set_freq(pipe, 439.920) get_freq(ser) if __name__ == "__main__": do_test() chirp-0.3.1/chirp/ft857.py0000644000016101777760000002652712130403635016362 0ustar jenkinsnogroup00000000000000# # Copyright 2012 Filippi Marco # # 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 . """FT857 - FT857/US management module""" from chirp import ft817, chirp_common, errors, directory @directory.register class FT857Radio(ft817.FT817Radio): """Yaesu FT-857/897""" MODEL = "FT-857/897" _model = "" TMODES = { 0x04 : "Tone", 0x05 : "TSQL", # 0x08 : "DTCS Enc", not supported in UI yet 0x0a : "DTCS", 0xff : "Cross", 0x00 : "", } TMODES_REV = dict(zip(TMODES.values(), TMODES.keys())) CROSS_MODES = { 0x01 : "->Tone", 0x02 : "->DTCS", # 0x04 : "Tone->", not supported in UI yet 0x05 : "Tone->Tone", 0x06 : "Tone->DTCS", 0x08 : "DTCS->", 0x09 : "DTCS->Tone", 0x0a : "DTCS->DTCS", } CROSS_MODES_REV = dict(zip(CROSS_MODES.values(), CROSS_MODES.keys())) _memsize = 7341 # block 9 (140 Bytes long) is to be repeted 40 times # should be 42 times but this way I can use original 817 functions _block_lengths = [ 2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140, 38, 176] # warning ranges has to be in this exact order VALID_BANDS = [(100000, 33000000), (33000000, 56000000), (76000000, 108000000), (108000000, 137000000), (137000000, 164000000), (420000000, 470000000)] MEM_FORMAT = """ struct mem_struct{ u8 tag_on_off:1, tag_default:1, unknown1:3, mode:3; u8 duplex:2, is_duplex:1, is_cwdig_narrow:1, is_fm_narrow:1, freq_range:3; u8 skip:1, unknokwn1_1:1, ipo:1, att:1, unknown2:4; u8 ssb_step:2, am_step:3, fm_step:3; u8 unknown3:3, is_split_tone:1, tmode:4; u8 unknown4:2, tx_mode:3, tx_freq_range:3; u8 unknown5:1, unknown_toneflag:1, tone:6; u8 unknown6:1, unknown_rxtoneflag:1, rxtone:6; u8 unknown7:1, dcs:7; u8 unknown8:1, rxdcs:7; ul16 rit; u32 freq; u32 offset; u8 name[8]; }; #seekto 0x54; struct mem_struct vfoa[16]; struct mem_struct vfob[16]; struct mem_struct home[4]; struct mem_struct qmb; struct mem_struct mtqmb; struct mem_struct mtune; #seekto 0x4a9; u8 visible[25]; ul16 pmsvisible; #seekto 0x4c4; u8 filled[25]; ul16 pmsfilled; #seekto 0x4df; struct mem_struct memory[200]; struct mem_struct pms[10]; #seekto 0x1CAD; struct mem_struct sixtymeterchannels[5]; """ # WARNING Index are hard wired in memory management code !!! SPECIAL_MEMORIES = { "VFOa-1.8M" : -37, "VFOa-3.5M" : -36, "VFOa-5M" : -35, "VFOa-7M" : -34, "VFOa-10M" : -33, "VFOa-14M" : -32, "VFOa-18M" : -31, "VFOa-21M" : -30, "VFOa-24M" : -29, "VFOa-28M" : -28, "VFOa-50M" : -27, "VFOa-FM" : -26, "VFOa-AIR" : -25, "VFOa-144" : -24, "VFOa-430" : -23, "VFOa-HF" : -22, "VFOb-1.8M" : -21, "VFOb-3.5M" : -20, "VFOb-5M" : -19, "VFOb-7M" : -18, "VFOb-10M" : -17, "VFOb-14M" : -16, "VFOb-18M" : -15, "VFOb-21M" : -14, "VFOb-24M" : -13, "VFOb-28M" : -12, "VFOb-50M" : -11, "VFOb-FM" : -10, "VFOb-AIR" : -9, "VFOb-144M" : -8, "VFOb-430M" : -7, "VFOb-HF" : -6, "HOME HF" : -5, "HOME 50M" : -4, "HOME 144M" : -3, "HOME 430M" : -2, "QMB" : -1, } FIRST_VFOB_INDEX = -6 LAST_VFOB_INDEX = -21 FIRST_VFOA_INDEX = -22 LAST_VFOA_INDEX = -37 SPECIAL_PMS = { "PMS-1L" : -47, "PMS-1U" : -46, "PMS-2L" : -45, "PMS-2U" : -44, "PMS-3L" : -43, "PMS-3U" : -42, "PMS-4L" : -41, "PMS-4U" : -40, "PMS-5L" : -39, "PMS-5U" : -38, } LAST_PMS_INDEX = -47 SPECIAL_MEMORIES.update(SPECIAL_PMS) SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(), SPECIAL_MEMORIES.keys())) def get_features(self): rf = ft817.FT817Radio.get_features(self) rf.has_cross = True rf.has_ctone = True rf.has_rx_dtcs = True rf.valid_tmodes = self.TMODES_REV.keys() rf.valid_cross_modes = self.CROSS_MODES_REV.keys() rf.has_settings = False # not implemented yet return rf def _get_duplex(self, mem, _mem): # radio set is_duplex only for + and - but not for split # at the same time it does not complain if we set it same way 817 does # (so no set_duplex here) mem.duplex = self.DUPLEX[_mem.duplex] def _get_tmode(self, mem, _mem): if not _mem.is_split_tone: mem.tmode = self.TMODES[int(_mem.tmode)] else: mem.tmode = "Cross" mem.cross_mode = self.CROSS_MODES[int(_mem.tmode)] if mem.tmode == "Tone": mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] elif mem.tmode == "TSQL": mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] elif mem.tmode == "DTCS Enc": # UI does not support it yet but # this code has alreay been tested mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs] elif mem.tmode == "DTCS": mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs] elif mem.tmode == "Cross": mem.ctone = chirp_common.TONES[_mem.rxtone] # don't want to fail for this try: mem.rtone = chirp_common.TONES[_mem.tone] except IndexError: mem.rtone = chirp_common.TONES[0] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.rxdcs] def _set_tmode(self, mem, _mem): if mem.tmode != "Cross": _mem.is_split_tone = 0 _mem.tmode = self.TMODES_REV[mem.tmode] else: _mem.tmode = self.CROSS_MODES_REV[mem.cross_mode] _mem.is_split_tone = 1 if mem.tmode == "Tone": _mem.tone = _mem.rxtone = chirp_common.TONES.index(mem.rtone) elif mem.tmode == "TSQL": _mem.tone = _mem.rxtone = chirp_common.TONES.index(mem.ctone) elif mem.tmode == "DTCS Enc": # UI does not support it yet but # this code has alreay been tested _mem.dcs = _mem.rxdcs = chirp_common.DTCS_CODES.index(mem.dtcs) elif mem.tmode == "DTCS": _mem.dcs = _mem.rxdcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs) elif mem.tmode == "Cross": _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.rxtone = chirp_common.TONES.index(mem.ctone) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.rxdcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs) # have to put this bit to 0 otherwise we get strange display in tone # frequency (menu 83). See bug #88 and #163 _mem.unknown_toneflag = 0 # dunno if there's the same problem here but to be safe ... _mem.unknown_rxtoneflag = 0 @directory.register class FT857USRadio(FT857Radio): """Yaesu FT857/897 (US version)""" # seems that radios configured for 5MHz operations send one paket more # than others so we have to distinguish sub models MODEL = "FT-857/897 (US)" _model = "" _memsize = 7481 # block 9 (140 Bytes long) is to be repeted 40 times # should be 42 times but this way I can use original 817 functions _block_lengths = [ 2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140, 38, 176, 140] SPECIAL_60M = { "M-601" : -52, "M-602" : -51, "M-603" : -50, "M-604" : -49, "M-605" : -48, } LAST_SPECIAL60M_INDEX = -52 SPECIAL_MEMORIES = dict(FT857Radio.SPECIAL_MEMORIES) SPECIAL_MEMORIES.update(SPECIAL_60M) SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(), SPECIAL_MEMORIES.keys())) # this is identical to the one in FT817ND_US_Radio but we inherit from 857 def _get_special_60m(self, number): mem = chirp_common.Memory() mem.number = self.SPECIAL_60M[number] mem.extd_number = number _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX + mem.number] mem = self._get_memory(mem, _mem) mem.immutable = ["number", "skip", "rtone", "ctone", "extd_number", "name", "dtcs", "tmode", "cross_mode", "dtcs_polarity", "power", "duplex", "offset", "comment", "empty"] return mem # this is identical to the one in FT817ND_US_Radio but we inherit from 857 def _set_special_60m(self, mem): if mem.empty: # can't delete 60M memories! raise Exception("Sorry, 60M memory can't be deleted") cur_mem = self._get_special_60m(self.SPECIAL_MEMORIES_REV[mem.number]) for key in cur_mem.immutable: if cur_mem.__dict__[key] != mem.__dict__[key]: raise errors.RadioError("Editing field `%s' " % key + "is not supported on M-60x channels") if mem.mode not in ["USB", "LSB", "CW", "CWR", "NCW", "NCWR", "DIG"]: raise errors.RadioError("Mode {mode} is not valid " "in 60m channels".format(mode=mem.mode)) _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX + mem.number] self._set_memory(mem, _mem) def get_memory(self, number): if number in self.SPECIAL_60M.keys(): return self._get_special_60m(number) elif number < 0 and \ self.SPECIAL_MEMORIES_REV[number] in self.SPECIAL_60M.keys(): # I can't stop delete operation from loosing extd_number but # I know how to get it back return self._get_special_60m(self.SPECIAL_MEMORIES_REV[number]) else: return FT857Radio.get_memory(self, number) def set_memory(self, memory): if memory.number in self.SPECIAL_60M.values(): return self._set_special_60m(memory) else: return FT857Radio.set_memory(self, memory) chirp-0.3.1/chirp/id31.py0000644000016101777760000002153412130403635016236 0ustar jenkinsnogroup00000000000000# Copyright 2012 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 . from chirp import directory, icf, bitwise, chirp_common MEM_FORMAT = """ struct { u24 freq; u16 offset; u16 rtone:6, ctone:6, unknown2:1, is_dv:1, unknown2_0:1, is_narrow:1; u8 dtcs; u8 tune_step:4, unknown5:4; u8 unknown4; u8 tmode:4, duplex:2, dtcs_polarity:2; char name[16]; u8 unknow13; u8 urcall[7]; u8 rpt1call[7]; u8 rpt2call[7]; } memory[500]; #seekto 0x69C0; u8 used_flags[70]; #seekto 0x6A06; u8 skip_flags[69]; #seekto 0x6A4B; u8 pskp_flags[69]; #seekto 0x6AC0; struct { u8 bank; u8 index; } banks[500]; #seekto 0x6F50; struct { char name[16]; } bank_names[26]; #seekto 0x74BF; struct { u8 unknown0; u24 freq; u16 offset; u8 unknown1[3]; u8 call[7]; char name[16]; char subname[8]; u8 unknown3[9]; } repeaters[700]; #seekto 0xFABC; struct { u8 call[7]; } rptcall[700]; #seekto 0x10F20; struct { char call[8]; } mycall[6]; #seekto 0x10F68; struct { char call[8]; } urcall[200]; """ TMODES = ["", "Tone", "TSQL", "TSQL", "DTCS", "DTCS", "TSQL-R", "DTCS-R"] DUPLEX = ["", "-", "+"] DTCS_POLARITY = ["NN", "NR", "RN", "RR"] TUNING_STEPS = [5.0, 6.25, 0, 0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 200.0] def _decode_call(_call): # Why Icom, why? call = "" shift = 1 acc = 0 for val in _call: mask = (1 << (shift)) - 1 call += chr((val >> shift) | acc) acc = (val & mask) << (7 - shift) shift += 1 call += chr(acc) return call def _encode_call(call): _call = [0x00] * 7 for i in range(0, 7): val = ord(call[i]) << (i + 1) if i > 0: _call[i-1] |= (val & 0xFF00) >> 8 _call[i] = val _call[6] |= (ord(call[7]) & 0x7F) return _call def _get_freq(_mem): freq = int(_mem.freq) offs = int(_mem.offset) if freq & 0x00200000: mult = 6250 else: mult = 5000 freq &= 0x0003FFFF return (freq * mult), (offs * mult) def _set_freq(_mem, freq, offset): if chirp_common.is_fractional_step(freq): mult = 6250 flag = 0x00200000 else: mult = 5000 flag = 0x00000000 _mem.freq = (freq / mult) | flag _mem.offset = (offset / mult) class ID31Bank(icf.IcomBank): """A ID-31 Bank""" def get_name(self): _banks = self._model._radio._memobj.bank_names return str(_banks[self.index].name).rstrip() def set_name(self, name): _banks = self._model._radio._memobj.bank_names _banks[self.index].name = str(name).ljust(16)[:16] @directory.register class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom ID-31""" MODEL = "ID-31A" _memsize = 0x15500 _model = "\x33\x22\x00\x01" _endframe = "Icom Inc\x2E\x41\x38" _num_banks = 26 _bank_class = ID31Bank _can_hispeed = True _ranges = [(0x00000, 0x15500, 32)] def _get_bank(self, loc): _bank = self._memobj.banks[loc] if _bank.bank == 0xFF: return None else: return _bank.bank def _set_bank(self, loc, bank): _bank = self._memobj.banks[loc] if bank is None: _bank.bank = 0xFF else: _bank.bank = bank def _get_bank_index(self, loc): _bank = self._memobj.banks[loc] return _bank.index def _set_bank_index(self, loc, index): _bank = self._memobj.banks[loc] _bank.index = index def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 499) rf.valid_bands = [(400000000, 479000000)] rf.has_settings = True rf.has_ctone = True rf.has_bank_index = True rf.has_bank_names = True rf.valid_tmodes = list(TMODES) rf.valid_tuning_steps = sorted(list(TUNING_STEPS)) rf.valid_modes = ["FM", "NFM", "DV"] rf.valid_skips = ["", "S", "P"] rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 16 return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): _mem = self._memobj.memory[number] _usd = self._memobj.used_flags[number / 8] _skp = self._memobj.skip_flags[number / 8] _psk = self._memobj.pskp_flags[number / 8] bit = (1 << (number % 8)) if _mem.is_dv: mem = chirp_common.DVMemory() else: mem = chirp_common.Memory() mem.number = number if _usd & bit: mem.empty = True return mem mem.freq, mem.offset = _get_freq(_mem) mem.name = str(_mem.name).rstrip() mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity] mem.tuning_step = TUNING_STEPS[_mem.tune_step] if _mem.is_dv: mem.mode = "DV" mem.dv_urcall = _decode_call(_mem.urcall).rstrip() mem.dv_rpt1call = _decode_call(_mem.rpt1call).rstrip() mem.dv_rpt2call = _decode_call(_mem.rpt2call).rstrip() elif _mem.is_narrow: mem.mode = "NFM" else: mem.mode = "FM" if _psk & bit: mem.skip = "P" if _skp & bit: mem.skip = "S" return mem def set_memory(self, memory): _mem = self._memobj.memory[memory.number] _usd = self._memobj.used_flags[memory.number / 8] _skp = self._memobj.skip_flags[memory.number / 8] _psk = self._memobj.pskp_flags[memory.number / 8] bit = (1 << (memory.number % 8)) if memory.empty: _usd |= bit self._set_bank(memory.number, None) return _usd &= ~bit _set_freq(_mem, memory.freq, memory.offset) _mem.name = memory.name.ljust(16)[:16] _mem.rtone = chirp_common.TONES.index(memory.rtone) _mem.ctone = chirp_common.TONES.index(memory.ctone) _mem.tmode = TMODES.index(memory.tmode) _mem.duplex = DUPLEX.index(memory.duplex) _mem.dtcs = chirp_common.DTCS_CODES.index(memory.dtcs) _mem.dtcs_polarity = DTCS_POLARITY.index(memory.dtcs_polarity) _mem.tune_step = TUNING_STEPS.index(memory.tuning_step) _mem.is_narrow = memory.mode in ["NFM", "DV"] _mem.is_dv = memory.mode == "DV" if isinstance(memory, chirp_common.DVMemory): _mem.urcall = _encode_call(memory.dv_urcall.ljust(8)) _mem.rpt1call = _encode_call(memory.dv_rpt1call.ljust(8)) _mem.rpt2call = _encode_call(memory.dv_rpt2call.ljust(8)) elif memory.mode == "DV": raise Exception("BUG") if memory.skip == "S": _skp |= bit _psk &= ~bit elif memory.skip == "P": _skp &= ~bit _psk |= bit else: _skp &= ~bit _psk &= ~bit def get_urcall_list(self): calls = [] for i in range(0, 200): call = str(self._memobj.urcall[i].call) if call == "CALLSIGN": call = "" calls.append(call) return calls def get_mycall_list(self): calls = [] for i in range(0, 6): calls.append(str(self._memobj.mycall[i].call)) return calls def get_repeater_call_list(self): calls = [] for rptcall in self._memobj.rptcall: call = _decode_call(rptcall.call) if call.rstrip() and not call == "CALLSIGN": calls.append(call) for repeater in self._memobj.repeaters: call = _decode_call(repeater.call) if call == "CALLSIGN": call = "" calls.append(call.rstrip()) return calls if __name__ == "__main__": print repr(_decode_call(_encode_call("KD7REX B"))) print repr(_decode_call(_encode_call(" B"))) print repr(_decode_call(_encode_call(" "))) chirp-0.3.1/chirp/ft817.py0000644000016101777760000012671512130403635016356 0ustar jenkinsnogroup00000000000000# # Copyright 2012 Filippi Marco # # 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 . """FT817 - FT817ND - FT817ND/US management module""" from chirp import chirp_common, yaesu_clone, util, memmap, errors, directory from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString import time, os CMD_ACK = 0x06 @directory.register class FT817Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-817""" BAUD_RATE = 9600 MODEL = "FT-817" _model = "" DUPLEX = ["", "-", "+", "split"] # narrow modes has to be at end MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW", "NCWR", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0] STEPSSSB = [1.0, 2.5, 5.0] # warning ranges has to be in this exact order VALID_BANDS = [(100000, 33000000), (33000000, 56000000), (76000000, 108000000), (108000000, 137000000), (137000000, 154000000), (420000000, 470000000)] CHARSET = [chr(x) for x in range(0, 256)] # Hi not used in memory POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("L3", watts=2.50), chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L1", watts=0.5)] _memsize = 6509 # block 9 (130 Bytes long) is to be repeted 40 times _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 118] MEM_FORMAT = """ struct mem_struct { u8 tag_on_off:1, tag_default:1, unknown1:3, mode:3; u8 duplex:2, is_duplex:1, is_cwdig_narrow:1, is_fm_narrow:1, freq_range:3; u8 skip:1, unknown2:1, ipo:1, att:1, unknown3:4; u8 ssb_step:2, am_step:3, fm_step:3; u8 unknown4:6, tmode:2; u8 unknown5:2, tx_mode:3, tx_freq_range:3; u8 unknown6:1, unknown_toneflag:1, tone:6; u8 unknown7:1, dcs:7; ul16 rit; u32 freq; u32 offset; u8 name[8]; }; #seekto 0x4; struct { u8 fst:1, lock:1, nb:1, pbt:1, unknownb:1, dsp:1, agc:2; u8 vox:1, vlt:1, bk:1, kyr:1, unknown5:1, cw_paddle:1, pwr_meter_mode:2; u8 vfob_band_select:4, vfoa_band_select:4; u8 unknowna; u8 backlight:2, color:2, contrast:4; u8 beep_freq:1, beep_volume:7; u8 arts_beep:2, main_step:1, cw_id:1, scope:1, pkt_rate:1, resume_scan:2; u8 op_filter:2, lock_mode:2, cw_pitch:4; u8 sql_rf_gain:1, ars_144:1, ars_430:1, cw_weight:5; u8 cw_delay; u8 unknown8:1, sidetone:7; u8 batt_chg:2, cw_speed:6; u8 disable_amfm_dial:1, vox_gain:7; u8 cat_rate:2, emergency:1, vox_delay:5; u8 dig_mode:3, mem_group:1, unknown9:1, apo_time:3; u8 dcs_inv:2, unknown10:1, tot_time:5; u8 mic_scan:1, ssb_mic:7; u8 mic_key:1, am_mic:7; u8 unknown11:1, fm_mic:7; u8 unknown12:1, dig_mic:7; u8 extended_menu:1, pkt_mic:7; u8 unknown14:1, pkt9600_mic:7; ul16 dig_shift; ul16 dig_disp; u8 r_lsb_car; u8 r_usb_car; u8 t_lsb_car; u8 t_usb_car; u8 unknown15:2, menu_item:6; u8 unknown16:4, menu_sel:4; u16 unknown17; u8 art:1, scn_mode:2, dw:1, pri:1, unknown18:1, tx_power:2; u8 spl:1, unknown:1, uhf_antenna:1, vhf_antenna:1, air_antenna:1, bc_antenna:1, sixm_antenna:1, hf_antenna:1; } settings; #seekto 0x2A; struct mem_struct vfoa[15]; struct mem_struct vfob[15]; struct mem_struct home[4]; struct mem_struct qmb; struct mem_struct mtqmb; struct mem_struct mtune; #seekto 0x3FD; u8 visible[25]; u8 pmsvisible; #seekto 0x417; u8 filled[25]; u8 pmsfilled; #seekto 0x431; struct mem_struct memory[200]; struct mem_struct pms[2]; #seekto 0x18cf; u8 callsign[7]; #seekto 0x1979; struct mem_struct sixtymeterchannels[5]; """ _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9")+1) + range(ord("A"), ord("Z")+1) + [ord(" ")]] _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET, range(0,len(_CALLSIGN_CHARSET)))) # WARNING Index are hard wired in memory management code !!! SPECIAL_MEMORIES = { "VFOa-1.8M" : -35, "VFOa-3.5M" : -34, "VFOa-7M" : -33, "VFOa-10M" : -32, "VFOa-14M" : -31, "VFOa-18M" : -30, "VFOa-21M" : -29, "VFOa-24M" : -28, "VFOa-28M" : -27, "VFOa-50M" : -26, "VFOa-FM" : -25, "VFOa-AIR" : -24, "VFOa-144" : -23, "VFOa-430" : -22, "VFOa-HF" : -21, "VFOb-1.8M" : -20, "VFOb-3.5M" : -19, "VFOb-7M" : -18, "VFOb-10M" : -17, "VFOb-14M" : -16, "VFOb-18M" : -15, "VFOb-21M" : -14, "VFOb-24M" : -13, "VFOb-28M" : -12, "VFOb-50M" : -11, "VFOb-FM" : -10, "VFOb-AIR" : -9, "VFOb-144M" : -8, "VFOb-430M" : -7, "VFOb-HF" : -6, "HOME HF" : -5, "HOME 50M" : -4, "HOME 144M" : -3, "HOME 430M" : -2, "QMB" : -1, } FIRST_VFOB_INDEX = -6 LAST_VFOB_INDEX = -20 FIRST_VFOA_INDEX = -21 LAST_VFOA_INDEX = -35 SPECIAL_PMS = { "PMS-L" : -37, "PMS-U" : -36, } LAST_PMS_INDEX = -37 SPECIAL_MEMORIES.update(SPECIAL_PMS) SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(), SPECIAL_MEMORIES.keys())) def _read(self, block, blocknum): for _i in range(0, 60): data = self.pipe.read(block+2) if data: break time.sleep(0.5) if len(data) == block+2 and data[0] == chr(blocknum): checksum = yaesu_clone.YaesuChecksum(1, block) if checksum.get_existing(data) != \ checksum.get_calculated(data): raise Exception("Checksum Failed [%02X<>%02X] block %02X" % (checksum.get_existing(data), checksum.get_calculated(data), blocknum)) data = data[1:block+1] # Chew away the block number and the checksum else: raise Exception("Unable to read block %02X expected %i got %i" % (blocknum, block+2, len(data))) if os.getenv("CHIRP_DEBUG"): print "Read %i" % len(data) return data def _clone_in(self): # Be very patient with the radio self.pipe.setTimeout(2) start = time.time() data = "" blocks = 0 status = chirp_common.Status() status.msg = "Cloning from radio" status.max = len(self._block_lengths) + 39 for block in self._block_lengths: if blocks == 8: # repeated read of 40 block same size (memory area) repeat = 40 else: repeat = 1 for _i in range(0, repeat): data += self._read(block, blocks) self.pipe.write(chr(CMD_ACK)) blocks += 1 status.cur = blocks self.status_fn(status) print "Clone completed in %i seconds" % (time.time() - start) return memmap.MemoryMap(data) def _clone_out(self): delay = 0.5 start = time.time() blocks = 0 pos = 0 status = chirp_common.Status() status.msg = "Cloning to radio" status.max = len(self._block_lengths) + 39 for block in self._block_lengths: if blocks == 8: # repeated read of 40 block same size (memory area) repeat = 40 else: repeat = 1 for _i in range(0, repeat): time.sleep(0.01) checksum = yaesu_clone.YaesuChecksum(pos, pos+block-1) if os.getenv("CHIRP_DEBUG"): print "Block %i - will send from %i to %i byte " % \ (blocks, pos, pos + block) print util.hexprint(chr(blocks)) print util.hexprint(self.get_mmap()[pos:pos+block]) print util.hexprint(chr(checksum.get_calculated( self.get_mmap()))) self.pipe.write(chr(blocks)) self.pipe.write(self.get_mmap()[pos:pos+block]) self.pipe.write(chr(checksum.get_calculated(self.get_mmap()))) buf = self.pipe.read(1) if not buf or buf[0] != chr(CMD_ACK): time.sleep(delay) buf = self.pipe.read(1) if not buf or buf[0] != chr(CMD_ACK): if os.getenv("CHIRP_DEBUG"): print util.hexprint(buf) raise Exception("Radio did not ack block %i" % blocks) pos += block blocks += 1 status.cur = blocks self.status_fn(status) print "Clone completed in %i seconds" % (time.time() - start) def sync_in(self): try: self._mmap = self._clone_in() except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() def sync_out(self): try: self._clone_out() except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def process_mmap(self): self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_dtcs_polarity = False rf.has_nostep_tuning = True rf.valid_modes = list(set(self.MODES)) rf.valid_tmodes = list(self.TMODES) rf.valid_duplexes = list(self.DUPLEX) rf.valid_tuning_steps = list(self.STEPSFM) rf.valid_bands = self.VALID_BANDS rf.valid_skips = ["", "S"] rf.valid_power_levels = self.POWER_LEVELS rf.valid_characters = "".join(self.CHARSET) rf.valid_name_length = 8 rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys()) rf.memory_bounds = (1, 200) rf.can_odd_split = True rf.has_ctone = False rf.has_settings = True return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def _get_duplex(self, mem, _mem): if _mem.is_duplex == 1: mem.duplex = self.DUPLEX[_mem.duplex] else: mem.duplex = "" def _get_tmode(self, mem, _mem): mem.tmode = self.TMODES[_mem.tmode] mem.rtone = chirp_common.TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] def _set_duplex(self, mem, _mem): _mem.duplex = self.DUPLEX.index(mem.duplex) _mem.is_duplex = mem.duplex != "" def _set_tmode(self, mem, _mem): _mem.tmode = self.TMODES.index(mem.tmode) # have to put this bit to 0 otherwise we get strange display in tone # frequency (menu 83). See bug #88 and #163 _mem.unknown_toneflag = 0 _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) def get_memory(self, number): if isinstance(number, str): return self._get_special(number) elif number < 0: # I can't stop delete operation from loosing extd_number but # I know how to get it back return self._get_special(self.SPECIAL_MEMORIES_REV[number]) else: return self._get_normal(number) def set_memory(self, memory): if memory.number < 0: return self._set_special(memory) else: return self._set_normal(memory) def _get_special(self, number): mem = chirp_common.Memory() mem.number = self.SPECIAL_MEMORIES[number] mem.extd_number = number if mem.number in range(self.FIRST_VFOA_INDEX, self.LAST_VFOA_INDEX - 1, -1): _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number] immutable = ["number", "skip", "rtone", "ctone", "extd_number", "name", "dtcs_polarity", "power", "comment"] elif mem.number in range(self.FIRST_VFOB_INDEX, self.LAST_VFOB_INDEX - 1, -1): _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number] immutable = ["number", "skip", "rtone", "ctone", "extd_number", "name", "dtcs_polarity", "power", "comment"] elif mem.number in range(-2, -6, -1): _mem = self._memobj.home[5 + mem.number] immutable = ["number", "skip", "rtone", "ctone", "extd_number", "dtcs_polarity", "power", "comment"] elif mem.number == -1: _mem = self._memobj.qmb immutable = ["number", "skip", "rtone", "ctone", "extd_number", "name", "dtcs_polarity", "power", "comment"] elif mem.number in self.SPECIAL_PMS.values(): bitindex = -self.LAST_PMS_INDEX + mem.number used = (self._memobj.pmsvisible >> bitindex) & 0x01 valid = (self._memobj.pmsfilled >> bitindex) & 0x01 if not used: mem.empty = True if not valid: mem.empty = True return mem _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number] immutable = ["number", "skip", "rtone", "ctone", "extd_number", "dtcs", "tmode", "cross_mode", "dtcs_polarity", "power", "duplex", "offset", "comment"] else: raise Exception("Sorry, special memory index %i " % mem.number + "unknown you hit a bug!!") mem = self._get_memory(mem, _mem) mem.immutable = immutable return mem def _set_special(self, mem): if mem.empty and not mem.number in self.SPECIAL_PMS.values(): # can't delete special memories! raise Exception("Sorry, special memory can't be deleted") cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number]) # TODO add frequency range check for vfo and home memories if mem.number in range(self.FIRST_VFOA_INDEX, self.LAST_VFOA_INDEX -1, -1): _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number] elif mem.number in range(self.FIRST_VFOB_INDEX, self.LAST_VFOB_INDEX -1, -1): _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number] elif mem.number in range(-2, -6, -1): _mem = self._memobj.home[5 + mem.number] elif mem.number == -1: _mem = self._memobj.qmb elif mem.number in self.SPECIAL_PMS.values(): # this case has to be last because 817 pms keys overlap with # 857 derived class other special memories bitindex = -self.LAST_PMS_INDEX + mem.number wasused = (self._memobj.pmsvisible >> bitindex) & 0x01 wasvalid = (self._memobj.pmsfilled >> bitindex) & 0x01 if mem.empty: if wasvalid and not wasused: # pylint get confused by &= operator self._memobj.pmsfilled = self._memobj.pmsfilled & \ ~ (1 << bitindex) # pylint get confused by &= operator self._memobj.pmsvisible = self._memobj.pmsvisible & \ ~ (1 << bitindex) return # pylint get confused by |= operator self._memobj.pmsvisible = self._memobj.pmsvisible | 1 << bitindex self._memobj.pmsfilled = self._memobj.pmsfilled | 1 << bitindex _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number] else: raise Exception("Sorry, special memory index %i " % mem.number + "unknown you hit a bug!!") for key in cur_mem.immutable: if cur_mem.__dict__[key] != mem.__dict__[key]: raise errors.RadioError("Editing field `%s' " % key + "is not supported on this channel") self._set_memory(mem, _mem) def _get_normal(self, number): _mem = self._memobj.memory[number-1] used = (self._memobj.visible[(number-1)/8] >> (number-1)%8) & 0x01 valid = (self._memobj.filled[(number-1)/8] >> (number-1)%8) & 0x01 mem = chirp_common.Memory() mem.number = number if not used: mem.empty = True if not valid: mem.empty = True return mem return self._get_memory(mem, _mem) def _set_normal(self, mem): _mem = self._memobj.memory[mem.number-1] wasused = (self._memobj.visible[(mem.number - 1) / 8] >> (mem.number - 1) % 8) & 0x01 wasvalid = (self._memobj.filled[(mem.number - 1) / 8] >> (mem.number - 1) % 8) & 0x01 if mem.empty: if mem.number == 1: # as Dan says "yaesus are not good about that :(" # if you ulpoad an empty image you can brick your radio raise Exception("Sorry, can't delete first memory") if wasvalid and not wasused: self._memobj.filled[(mem.number-1) / 8] &= \ ~(1 << (mem.number - 1) % 8) _mem.set_raw("\xFF" * (_mem.size() / 8)) # clean up self._memobj.visible[(mem.number-1) / 8] &= \ ~(1 << (mem.number - 1) % 8) return if not wasvalid: _mem.set_raw("\x00" * (_mem.size() / 8)) # clean up self._memobj.visible[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8 self._memobj.filled[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8 self._set_memory(mem, _mem) def _get_memory(self, mem, _mem): mem.freq = int(_mem.freq) * 10 mem.offset = int(_mem.offset) * 10 self._get_duplex(mem, _mem) mem.mode = self.MODES[_mem.mode] if mem.mode == "FM": if _mem.is_fm_narrow == 1: mem.mode = "NFM" mem.tuning_step = self.STEPSFM[_mem.fm_step] elif mem.mode == "AM": mem.tuning_step = self.STEPSAM[_mem.am_step] elif mem.mode == "CW" or mem.mode == "CWR": if _mem.is_cwdig_narrow == 1: mem.mode = "N" + mem.mode mem.tuning_step = self.STEPSSSB[_mem.ssb_step] else: try: mem.tuning_step = self.STEPSSSB[_mem.ssb_step] except IndexError: pass mem.skip = _mem.skip and "S" or "" self._get_tmode(mem, _mem) if _mem.tag_on_off == 1: for i in _mem.name: if i == "\xFF": break mem.name += self.CHARSET[i] mem.name = mem.name.rstrip() else: mem.name = "" mem.extra = RadioSettingGroup("extra", "Extra") ipo = RadioSetting("ipo", "IPO", RadioSettingValueBoolean(bool(_mem.ipo))) ipo.set_doc("Bypass preamp") mem.extra.append(ipo) att = RadioSetting("att", "ATT", RadioSettingValueBoolean(bool(_mem.att))) att.set_doc("10dB front end attenuator") mem.extra.append(att) return mem def _set_memory(self, mem, _mem): if len(mem.name) > 0: # not supported in chirp # so I make label visible if have one _mem.tag_on_off = 1 else: _mem.tag_on_off = 0 _mem.tag_default = 0 # never use default label "CH-nnn" self._set_duplex(mem, _mem) if mem.mode[0] == "N": # is it narrow? _mem.mode = self.MODES.index(mem.mode[1:]) # here I suppose it's safe to set both _mem.is_fm_narrow = _mem.is_cwdig_narrow = 1 else: _mem.mode = self.MODES.index(mem.mode) # here I suppose it's safe to set both _mem.is_fm_narrow = _mem.is_cwdig_narrow = 0 i = 0 for lo, hi in self.VALID_BANDS: if mem.freq > lo and mem.freq < hi: break i += 1 _mem.freq_range = i # all this should be safe also when not in split but ... if mem.duplex == "split": _mem.tx_mode = _mem.mode i = 0 for lo, hi in self.VALID_BANDS: if mem.offset >= lo and mem.offset < hi: break i += 1 _mem.tx_freq_range = i _mem.skip = mem.skip == "S" self._set_tmode(mem, _mem) try: _mem.ssb_step = self.STEPSSSB.index(mem.tuning_step) except ValueError: pass try: _mem.am_step = self.STEPSAM.index(mem.tuning_step) except ValueError: pass try: _mem.fm_step = self.STEPSFM.index(mem.tuning_step) except ValueError: pass _mem.rit = 0 # not supported in chirp _mem.freq = mem.freq / 10 _mem.offset = mem.offset / 10 for i in range(0, 8): _mem.name[i] = self.CHARSET.index(mem.name.ljust(8)[i]) for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def validate_memory(self, mem): msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem) lo, hi = self.VALID_BANDS[2] # this is fm broadcasting if mem.freq >= lo and mem.freq <= hi: if mem.mode != "FM": msgs.append(chirp_common.ValidationError( "Only FM is supported in this band")) # TODO check that step is valid in current mode return msgs @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") cw = RadioSettingGroup("cw", "CW") packet = RadioSettingGroup("packet", "Digital & packet") panel = RadioSettingGroup("panel", "Panel settings") extended = RadioSettingGroup("extended", "Extended") antenna = RadioSettingGroup("antenna", "Antenna selection") panelcontr = RadioSettingGroup("panelcontr", "Panel controls") top = RadioSettingGroup("top", "All Settings", basic, cw, packet, panelcontr, panel, extended, antenna) rs = RadioSetting("ars_144", "144 ARS", RadioSettingValueBoolean(_settings.ars_144)) basic.append(rs) rs = RadioSetting("ars_430", "430 ARS", RadioSettingValueBoolean(_settings.ars_144)) basic.append(rs) rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level", RadioSettingValueInteger(0, 100, _settings.am_mic)) packet.append(rs) options = ["enable", "disable"] rs = RadioSetting("disable_amfm_dial", "AM&FM Dial", RadioSettingValueList(options, options[_settings.disable_amfm_dial])) panel.append(rs) rs = RadioSetting("am_mic", "AM mic level", RadioSettingValueInteger(0, 100, _settings.am_mic)) basic.append(rs) options = ["OFF", "1h", "2h", "3h", "4h", "5h", "6h"] rs = RadioSetting("apo_time", "APO time", RadioSettingValueList(options, options[_settings.apo_time])) basic.append(rs) options = ["OFF", "Range", "All"] rs = RadioSetting("arts_beep", "ARTS beep", RadioSettingValueList(options, options[_settings.arts_beep])) basic.append(rs) options = ["OFF", "ON", "Auto"] rs = RadioSetting("backlight", "Backlight", RadioSettingValueList(options, options[_settings.backlight])) panel.append(rs) options = ["6h", "8h", "10h"] rs = RadioSetting("batt_chg", "Battery charge", RadioSettingValueList(options, options[_settings.batt_chg])) basic.append(rs) options = ["440Hz", "880Hz"] rs = RadioSetting("beep_freq", "Beep frequency", RadioSettingValueList(options, options[_settings.beep_freq])) panel.append(rs) rs = RadioSetting("beep_volume", "Beep volume", RadioSettingValueInteger(0, 100, _settings.beep_volume)) panel.append(rs) options = ["4800", "9600", "38400"] rs = RadioSetting("cat_rate", "CAT rate", RadioSettingValueList(options, options[_settings.cat_rate])) basic.append(rs) options = ["Blue", "Amber", "Violet"] rs = RadioSetting("color", "Color", RadioSettingValueList(options, options[_settings.color])) panel.append(rs) rs = RadioSetting("contrast", "Contrast", RadioSettingValueInteger(1, 12,_settings.contrast-1)) panel.append(rs) rs = RadioSetting("cw_delay", "CW delay (*10 ms)", RadioSettingValueInteger(1, 250, _settings.cw_delay)) cw.append(rs) rs = RadioSetting("cw_id", "CW id", RadioSettingValueBoolean(_settings.cw_id)) cw.append(rs) options = ["Normal", "Reverse"] rs = RadioSetting("cw_paddle", "CW paddle", RadioSettingValueList(options, options[_settings.cw_paddle])) cw.append(rs) options = ["%i Hz" % i for i in range(300,1001,50)] rs = RadioSetting("cw_pitch", "CW pitch", RadioSettingValueList(options, options[_settings.cw_pitch])) cw.append(rs) options = ["%i wpm" % i for i in range(4,61)] rs = RadioSetting("cw_speed", "CW speed", RadioSettingValueList(options, options[_settings.cw_speed])) cw.append(rs) options = ["1:%1.1f" % (i/10) for i in range(25,46,1)] rs = RadioSetting("cw_weight", "CW weight", RadioSettingValueList(options, options[_settings.cw_weight])) cw.append(rs) rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)", RadioSettingValueInteger(-300, 300, _settings.dig_disp)) packet.append(rs) rs = RadioSetting("dig_mic", "Dig mic", RadioSettingValueInteger(0, 100, _settings.dig_mic)) packet.append(rs) options = ["RTTY", "PSK31-L", "PSK31-U", "USER-L", "USER-U"] rs = RadioSetting("dig_mode", "Dig mode", RadioSettingValueList(options, options[_settings.dig_mode])) packet.append(rs) rs = RadioSetting("dig_shift", "Dig shift (*10 Hz)", RadioSettingValueInteger(-300, 300, _settings.dig_shift)) packet.append(rs) rs = RadioSetting("fm_mic", "FM mic", RadioSettingValueInteger(0, 100, _settings.fm_mic)) basic.append(rs) options = ["Dial", "Freq", "Panel"] rs = RadioSetting("lock_mode", "Lock mode", RadioSettingValueList(options, options[_settings.lock_mode])) panel.append(rs) options = ["Fine", "Coarse"] rs = RadioSetting("main_step", "Main step", RadioSettingValueList(options, options[_settings.main_step])) panel.append(rs) rs = RadioSetting("mem_group", "Mem group", RadioSettingValueBoolean(_settings.mem_group)) basic.append(rs) rs = RadioSetting("mic_key", "Mic key", RadioSettingValueBoolean(_settings.mic_key)) cw.append(rs) rs = RadioSetting("mic_scan", "Mic scan", RadioSettingValueBoolean(_settings.mic_scan)) basic.append(rs) options = ["Off", "SSB", "CW"] rs = RadioSetting("op_filter", "Optional filter", RadioSettingValueList(options, options[_settings.op_filter])) basic.append(rs) rs = RadioSetting("pkt_mic", "Packet mic", RadioSettingValueInteger(0, 100, _settings.pkt_mic)) packet.append(rs) options = ["1200", "9600"] rs = RadioSetting("pkt_rate", "Packet rate", RadioSettingValueList(options, options[_settings.pkt_rate])) packet.append(rs) options = ["Off", "3 sec", "5 sec", "10 sec"] rs = RadioSetting("resume_scan", "Resume scan", RadioSettingValueList(options, options[_settings.resume_scan])) basic.append(rs) options = ["Cont", "Chk"] rs = RadioSetting("scope", "Scope", RadioSettingValueList(options, options[_settings.scope])) basic.append(rs) rs = RadioSetting("sidetone", "Sidetone", RadioSettingValueInteger(0, 100, _settings.sidetone)) cw.append(rs) options = ["RF-Gain", "Squelch"] rs = RadioSetting("sql_rf_gain", "Squelch/RF-Gain", RadioSettingValueList(options, options[_settings.sql_rf_gain])) panel.append(rs) rs = RadioSetting("ssb_mic", "SSB Mic", RadioSettingValueInteger(0, 100, _settings.ssb_mic)) basic.append(rs) options = ["%i" % i for i in range(0, 21)] options[0] = "Off" rs = RadioSetting("tot_time", "Time-out timer", RadioSettingValueList(options, options[_settings.tot_time])) basic.append(rs) rs = RadioSetting("vox_delay", "VOX delay (*100 ms)", RadioSettingValueInteger(1, 25, _settings.vox_delay)) basic.append(rs) rs = RadioSetting("vox_gain", "VOX Gain", RadioSettingValueInteger(0, 100, _settings.vox_gain)) basic.append(rs) rs = RadioSetting("extended_menu", "Extended menu", RadioSettingValueBoolean(_settings.extended_menu)) extended.append(rs) options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"] rs = RadioSetting("dcs_inv", "DCS coding", RadioSettingValueList(options, options[_settings.dcs_inv])) extended.append(rs) rs = RadioSetting("r_lsb_car", "LSB Rx carrier point (*10 Hz)", RadioSettingValueInteger(-30, 30, _settings.r_lsb_car)) extended.append(rs) rs = RadioSetting("r_usb_car", "USB Rx carrier point (*10 Hz)", RadioSettingValueInteger(-30, 30, _settings.r_usb_car)) extended.append(rs) rs = RadioSetting("t_lsb_car", "LSB Tx carrier point (*10 Hz)", RadioSettingValueInteger(-30, 30, _settings.t_lsb_car)) extended.append(rs) rs = RadioSetting("t_usb_car", "USB Tx carrier point (*10 Hz)", RadioSettingValueInteger(-30, 30, _settings.t_usb_car)) extended.append(rs) options = ["Hi", "L3", "L2", "L1"] rs = RadioSetting("tx_power", "TX power", RadioSettingValueList(options, options[_settings.tx_power])) basic.append(rs) options = ["Front", "Rear"] rs = RadioSetting("hf_antenna", "HF", RadioSettingValueList(options, options[_settings.hf_antenna])) antenna.append(rs) rs = RadioSetting("sixm_antenna", "6M", RadioSettingValueList(options, options[_settings.sixm_antenna])) antenna.append(rs) rs = RadioSetting("bc_antenna", "Broadcasting", RadioSettingValueList(options, options[_settings.bc_antenna])) antenna.append(rs) rs = RadioSetting("air_antenna", "Air band", RadioSettingValueList(options, options[_settings.air_antenna])) antenna.append(rs) rs = RadioSetting("vhf_antenna", "VHF", RadioSettingValueList(options, options[_settings.vhf_antenna])) antenna.append(rs) rs = RadioSetting("uhf_antenna", "UHF", RadioSettingValueList(options, options[_settings.uhf_antenna])) antenna.append(rs) s = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x] for x in self._memobj.callsign])) s.set_charset(self._CALLSIGN_CHARSET) rs = RadioSetting("callsign", "Callsign", s) cw.append(rs) rs = RadioSetting("spl", "Split", RadioSettingValueBoolean(_settings.spl)) panelcontr.append(rs) options = ["None", "Up", "Down"] rs = RadioSetting("scn_mode", "Scan mode", RadioSettingValueList(options, options[_settings.scn_mode])) panelcontr.append(rs) rs = RadioSetting("pri", "Priority", RadioSettingValueBoolean(_settings.pri)) panelcontr.append(rs) rs = RadioSetting("dw", "Dual watch", RadioSettingValueBoolean(_settings.dw)) panelcontr.append(rs) rs = RadioSetting("art", "Auto-range transponder", RadioSettingValueBoolean(_settings.art)) panelcontr.append(rs) rs = RadioSetting("nb", "Noise blanker", RadioSettingValueBoolean(_settings.nb)) panelcontr.append(rs) options = ["Auto", "Fast", "Slow", "Off"] rs = RadioSetting("agc", "AGC", RadioSettingValueList(options, options[_settings.agc])) panelcontr.append(rs) options = ["PWR", "ALC", "SWR", "MOD"] rs = RadioSetting("pwr_meter_mode", "Power meter mode", RadioSettingValueList(options, options[_settings.pwr_meter_mode])) panelcontr.append(rs) rs = RadioSetting("vox", "Vox", RadioSettingValueBoolean(_settings.vox)) panelcontr.append(rs) rs = RadioSetting("bk", "Semi break-in", RadioSettingValueBoolean(_settings.bk)) cw.append(rs) rs = RadioSetting("kyr", "Keyer", RadioSettingValueBoolean(_settings.kyr)) cw.append(rs) options = ["enabled", "disabled"] rs = RadioSetting("fst", "Fast", RadioSettingValueList(options, options[_settings.fst])) panelcontr.append(rs) options = ["enabled", "disabled"] rs = RadioSetting("lock", "Lock", RadioSettingValueList(options, options[_settings.lock])) panelcontr.append(rs) return top def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue try: if "." in element.get_name(): bits = element.get_name().split(".") obj = self._memobj for bit in bits[:-1]: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if os.getenv("CHIRP_DEBUG"): print "Setting %s(%s) <= %s" % (setting, getattr(obj, setting), element.value) if setting == "contrast": setattr(obj, setting, int(element.value)+1) elif setting == "callsign": self._memobj.callsign = \ [self._CALLSIGN_CHARSET_REV[x] for x in str(element.value)] else: setattr(obj, setting, element.value) except Exception, e: print element.get_name() raise @directory.register class FT817NDRadio(FT817Radio): """Yaesu FT-817ND""" MODEL = "FT-817ND" _model = "" _memsize = 6521 # block 9 (130 Bytes long) is to be repeted 40 times _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130] @directory.register class FT817NDUSRadio(FT817Radio): """Yaesu FT-817ND (US version)""" # seems that radios configured for 5MHz operations send one paket # more than others so we have to distinguish sub models MODEL = "FT-817ND (US)" _model = "" _memsize = 6651 # block 9 (130 Bytes long) is to be repeted 40 times _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130, 130] SPECIAL_60M = { "M-601" : -42, "M-602" : -41, "M-603" : -40, "M-604" : -39, "M-605" : -38, } LAST_SPECIAL60M_INDEX = -42 SPECIAL_MEMORIES = dict(FT817Radio.SPECIAL_MEMORIES) SPECIAL_MEMORIES.update(SPECIAL_60M) SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(), SPECIAL_MEMORIES.keys())) def _get_special_60m(self, number): mem = chirp_common.Memory() mem.number = self.SPECIAL_60M[number] mem.extd_number = number _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX + mem.number] mem = self._get_memory(mem, _mem) mem.immutable = ["number", "skip", "rtone", "ctone", "extd_number", "name", "dtcs", "tmode", "cross_mode", "dtcs_polarity", "power", "duplex", "offset", "comment", "empty"] return mem def _set_special_60m(self, mem): if mem.empty: # can't delete 60M memories! raise Exception("Sorry, 60M memory can't be deleted") cur_mem = self._get_special_60m(self.SPECIAL_MEMORIES_REV[mem.number]) for key in cur_mem.immutable: if cur_mem.__dict__[key] != mem.__dict__[key]: raise errors.RadioError("Editing field `%s' " % key + "is not supported on M-60x channels") if mem.mode not in ["USB", "LSB", "CW", "CWR", "NCW", "NCWR", "DIG"]: raise errors.RadioError("Mode {mode} is not valid " "in 60m channels".format(mode=mem.mode)) _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX + mem.number] self._set_memory(mem, _mem) def get_memory(self, number): if number in self.SPECIAL_60M.keys(): return self._get_special_60m(number) elif number < 0 and \ self.SPECIAL_MEMORIES_REV[number] in self.SPECIAL_60M.keys(): # I can't stop delete operation from loosing extd_number but # I know how to get it back return self._get_special_60m(self.SPECIAL_MEMORIES_REV[number]) else: return FT817Radio.get_memory(self, number) def set_memory(self, memory): if memory.number in self.SPECIAL_60M.values(): return self._set_special_60m(memory) else: return FT817Radio.set_memory(self, memory) def get_settings(self): top = FT817Radio.get_settings(self) basic = top["basic"] rs = RadioSetting("emergency", "Emergency", RadioSettingValueBoolean(self._memobj.settings.emergency)) basic.append(rs) return top chirp-0.3.1/chirp/util.py0000644000016100007500000000442612023560645015021 0ustar jenkins00000000000000# Copyright 2008 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 . import struct def hexprint(data): """Return a hexdump-like encoding of @data""" line_sz = 8 lines = len(data) / line_sz if (len(data) % line_sz) != 0: lines += 1 data += "\x00" * ((lines * line_sz) - len(data)) out = "" for i in range(0, (len(data)/line_sz)): out += "%03i: " % (i * line_sz) left = len(data) - (i * line_sz) if left < line_sz: limit = left else: limit = line_sz for j in range(0, limit): out += "%02x " % ord(data[(i * line_sz) + j]) out += " " for j in range(0, limit): char = data[(i * line_sz) + j] if ord(char) > 0x20 and ord(char) < 0x7E: out += "%s" % char else: out += "." out += "\n" return out def bcd_encode(val, bigendian=True, width=None): """This is really old and shouldn't be used anymore""" digits = [] while val != 0: digits.append(val % 10) val /= 10 result = "" if len(digits) % 2 != 0: digits.append(0) while width and width > len(digits): digits.append(0) for i in range(0, len(digits), 2): newval = struct.pack("B", (digits[i+1] << 4) | digits[i]) if bigendian: result = newval + result else: result = result + newval return result def get_dict_rev(thedict, value): """Return the first matching key for a given @value in @dict""" _dict = {} for k, v in thedict.items(): _dict[v] = k return _dict[value] chirp-0.3.1/chirp/id880.py0000644000016100007500000002570012025023645014672 0ustar jenkins00000000000000# Copyright 2010 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 . from chirp import chirp_common, icf, directory from chirp import bitwise MEM_FORMAT = """ struct { u24 freq; u16 offset; u16 rtone:6, ctone:6, unknown1:1, mode:3; u8 dtcs; u8 tune_step:4, unknown2:4; u8 unknown3; u8 unknown4:1, tmode:3, duplex:2, dtcs_polarity:2; char name[8]; u8 unknwon5:1, digital_code:7; char urcall[7]; char r1call[7]; char r2call[7]; } memory[1000]; #seekto 0xAA80; u8 used_flags[132]; #seekto 0xAB04; u8 skip_flags[132]; u8 pskip_flags[132]; #seekto 0xAD00; struct { u8 bank; u8 index; } bank_info[1000]; #seekto 0xB550; struct { char name[6]; } bank_names[26]; #seekto 0xDE56; struct { char call[8]; char extension[4]; } mycall[6]; struct { char call[8]; } urcall[60]; struct { char call[8]; char extension[4]; } rptcall[99]; #seekto 0x0FB8; u8 name_flags[132]; """ TMODES = ["", "Tone", "?2", "TSQL", "DTCS", "TSQL-R", "DTCS-R", ""] DUPLEX = ["", "-", "+", "?3"] DTCSP = ["NN", "NR", "RN", "RR"] MODES = ["FM", "NFM", "?2", "AM", "NAM", "DV"] STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 200.0] def decode_call(sevenbytes): """Decode a callsign from a packed region @sevenbytes""" if len(sevenbytes) != 7: raise Exception("%i (!=7) bytes to decode_call" % len(sevenbytes)) i = 0 rem = 0 call = "" for byte in [ord(x) for x in sevenbytes]: i += 1 mask = (1 << i) - 1 # Mask is 0x01, 0x03, 0x07, etc code = (byte >> i) | rem # Code gets the upper bits of remainder # plus all but the i lower bits of this # byte call += chr(code) rem = (byte & mask) << 7 - i # Remainder for next time are the masked # bits, moved to the high places for the # next round # After seven trips gathering overflow bits, we chould have seven # left, which is the final character call += chr(rem) return call.rstrip() def encode_call(call): """Encode @call into a 7-byte region""" call = call.ljust(8) buf = [] for i in range(0, 8): byte = ord(call[i]) if i > 0: last = buf[i-1] himask = ~((1 << (7-i)) - 1) & 0x7F last |= (byte & himask) >> (7-i) buf[i-1] = last else: himask = 0 buf.append((byte & ~himask) << (i+1)) return "".join([chr(x) for x in buf[:7]]) def _get_freq(_mem): val = int(_mem.freq) if val & 0x00200000: mult = 6250 else: mult = 5000 val &= 0x0003FFFF return (val * mult) def _set_freq(_mem, freq): if chirp_common.is_fractional_step(freq): mult = 6250 flag = 0x00200000 else: mult = 5000 flag = 0x00000000 _mem.freq = (freq / mult) | flag def _wipe_memory(mem, char): mem.set_raw(char * (mem.size() / 8)) class ID880Bank(icf.IcomNamedBank): """ID880 Bank""" def get_name(self): _bank = self._model._radio._memobj.bank_names[self.index] return str(_bank.name).rstrip() def set_name(self, name): _bank = self._model._radio._memobj.bank_names[self.index] _bank.name = name.ljust(6)[:6] @directory.register class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): """Icom ID880""" VENDOR = "Icom" MODEL = "ID-880H" _model = "\x31\x67\x00\x01" _memsize = 62976 _endframe = "Icom Inc\x2eB1" _ranges = [(0x0000, 0xF5c0, 32), (0xF5c0, 0xf5e0, 16), (0xf5e0, 0xf600, 32)] _num_banks = 26 _bank_class = ID880Bank _can_hispeed = True MYCALL_LIMIT = (1, 7) URCALL_LIMIT = (1, 60) RPTCALL_LIMIT = (1, 99) def _get_bank(self, loc): _bank = self._memobj.bank_info[loc] if _bank.bank == 0xFF: return None else: return _bank.bank def _set_bank(self, loc, bank): _bank = self._memobj.bank_info[loc] if bank is None: _bank.bank = 0xFF else: _bank.bank = bank def _get_bank_index(self, loc): _bank = self._memobj.bank_info[loc] return _bank.index def _set_bank_index(self, loc, index): _bank = self._memobj.bank_info[loc] _bank.index = index def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.requires_call_lists = False rf.has_settings = True rf.has_bank = True rf.has_bank_index = True rf.has_bank_names = True rf.valid_modes = [x for x in MODES if x is not None] rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = STEPS rf.valid_bands = [(118000000, 173995000), (230000000, 549995000), (810000000, 823990000), (849000000, 868990000), (894000000, 999990000)] rf.valid_skips = ["", "S", "P"] rf.valid_name_length = 8 rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + \ "!\"#$%&'()*+,-./:;<=>?@[\]^" rf.memory_bounds = (0, 999) return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_memory(self, number): bytepos = number / 8 bitpos = 1 << (number % 8) _mem = self._memobj.memory[number] _used = self._memobj.used_flags[bytepos] is_used = ((_used & bitpos) == 0) if is_used and MODES[_mem.mode] == "DV": mem = chirp_common.DVMemory() mem.dv_urcall = decode_call(str(_mem.urcall)) mem.dv_rpt1call = decode_call(str(_mem.r1call)) mem.dv_rpt2call = decode_call(str(_mem.r2call)) else: mem = chirp_common.Memory() mem.number = number if number < 1000: _skip = self._memobj.skip_flags[bytepos] _pskip = self._memobj.pskip_flags[bytepos] if _skip & bitpos: mem.skip = "S" elif _pskip & bitpos: mem.skip = "P" else: pass # FIXME: Special memories if not is_used: mem.empty = True return mem mem.freq = _get_freq(_mem) mem.offset = (_mem.offset * 5) * 1000 mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCSP[_mem.dtcs_polarity] if _mem.tune_step >= len(STEPS): mem.tuning_step = 5.0 else: mem.tuning_step = STEPS[_mem.tune_step] mem.name = str(_mem.name).rstrip() return mem def set_memory(self, mem): bitpos = (1 << (mem.number % 8)) bytepos = mem.number / 8 _mem = self._memobj.memory[mem.number] _used = self._memobj.used_flags[bytepos] _namf = self._memobj.name_flags[bytepos] was_empty = _used & bitpos if mem.empty: _used |= bitpos _wipe_memory(_mem, "\xFF") self._set_bank(mem.number, None) return _used &= ~bitpos if was_empty: _wipe_memory(_mem, "\x00") _set_freq(_mem, mem.freq) _mem.offset = int((mem.offset / 1000) / 5) _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.mode = MODES.index(mem.mode) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity) _mem.tune_step = STEPS.index(mem.tuning_step) _mem.name = mem.name.ljust(8) if mem.name.strip(): _namf |= bitpos else: _namf &= ~bitpos if isinstance(mem, chirp_common.DVMemory): _mem.urcall = encode_call(mem.dv_urcall) _mem.r1call = encode_call(mem.dv_rpt1call) _mem.r2call = encode_call(mem.dv_rpt2call) if mem.number < 1000: skip = self._memobj.skip_flags[bytepos] pskip = self._memobj.pskip_flags[bytepos] if mem.skip == "S": skip |= bitpos else: skip &= ~bitpos if mem.skip == "P": pskip |= bitpos else: pskip &= ~bitpos def get_urcall_list(self): _calls = self._memobj.urcall calls = ["CQCQCQ"] for i in range(*self.URCALL_LIMIT): calls.append(str(_calls[i-1].call)) return calls def get_mycall_list(self): _calls = self._memobj.mycall calls = [] for i in range(*self.MYCALL_LIMIT): calls.append(str(_calls[i-1].call)) return calls def get_repeater_call_list(self): _calls = self._memobj.rptcall calls = ["*NOTUSE*"] for _i in range(*self.RPTCALL_LIMIT): # FIXME: Not sure where the repeater list actually is calls.append("UNSUPRTD") continue return calls @classmethod def match_model(cls, filedata, filename): # This is a horrid hack, given that people can change the GPS-A # destination, but it should suffice in most cases until we get # a rich container file format return len(filedata) == cls._memsize and "API880," in filedata # This radio isn't really supported yet and detects as a conflict with # the ID-880. So, don't register right now @directory.register class ID80Radio(ID880Radio): """Icom ID80""" MODEL = "ID-80H" _model = "\x31\x55\x00\x01" @classmethod def match_model(cls, filedata, filename): # This is a horrid hack, given that people can change the GPS-A # destination, but it should suffice in most cases until we get # a rich container file format return len(filedata) == cls._memsize and "API80," in filedata chirp-0.3.1/chirp/th_uv3r.py0000644000016100007500000001776112036210646015441 0ustar jenkins00000000000000# Copyright 2011 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 . """TYT uv3r radio management module""" import os from chirp import chirp_common, bitwise, errors, directory from chirp.wouxun_common import do_download, do_upload if os.getenv("CHIRP_DEBUG"): DEBUG = True else: DEBUG = False def tyt_uv3r_prep(radio): try: radio.pipe.write("PROGRAMa") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio did not ACK first command") except: raise errors.RadioError("Unable to communicate with the radio") def tyt_uv3r_download(radio): tyt_uv3r_prep(radio) return do_download(radio, 0x0000, 0x0910, 0x0010) def tyt_uv3r_upload(radio): tyt_uv3r_prep(radio) return do_upload(radio, 0x0000, 0x0910, 0x0010) mem_format = """ struct memory { ul24 duplex:2, bit:1, iswide:1, bits:2, is625:1, freq:17; ul16 offset; ul16 rx_tone; ul16 tx_tone; u8 unknown; u8 name[6]; }; #seekto 0x0010; struct memory memory[128]; #seekto 0x0870; u8 emptyflags[16]; u8 skipflags[16]; """ THUV3R_DUPLEX = ["", "+", "-"] THUV3R_CHARSET = "".join([chr(ord("0") + x) for x in range(0, 10)] + [" -*+"] + [chr(ord("A") + x) for x in range(0, 26)] + ["_/"]) @directory.register class TYTUV3RRadio(chirp_common.CloneModeRadio): VENDOR = "TYT" MODEL = "TH-UV3R" BAUD_RATE = 2400 _memsize = 2320 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_tuning_step = False rf.has_cross = True rf.memory_bounds = (1, 128) rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS"] rf.valid_skips = [] rf.valid_modes = ["FM", "NFM"] rf.valid_name_length = 6 rf.valid_characters = THUV3R_CHARSET rf.valid_bands = [(136000000, 470000000)] rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 25.0, 37.50, 50.0, 100.0] rf.valid_skips = ["", "S"] return rf def sync_in(self): self.pipe.setTimeout(2) self._mmap = tyt_uv3r_download(self) self.process_mmap() def sync_out(self): tyt_uv3r_upload(self) def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _decode_tone_value(self, value): if value == 0xFFFF: return "", "N", 0 elif value & 0x8000: # FIXME: rev pol pol = value & 0x4000 and "R" or "N" return "DTCS", pol, int("%x" % (value & 0x0FFF)) else: return "Tone", "N", int("%x" % value) / 10.0 def _decode_tone(self, mem, _mem): tx_mode, tpol, tx_tone = self._decode_tone_value(_mem.tx_tone) rx_mode, rpol, rx_tone = self._decode_tone_value(_mem.rx_tone) if rx_mode == tx_mode == "": return mem.dtcs_polarity = "%s%s" % (tpol, rpol) if rx_mode == tx_mode == "DTCS": # Break this for now until we can support this in chirp tx_tone = rx_tone if rx_mode in ["", tx_mode] and rx_tone in [0, tx_tone]: mem.tmode = rx_mode == "Tone" and "TSQL" or tx_mode if mem.tmode == "DTCS": mem.dtcs = tx_tone elif mem.tmode == "TSQL": mem.ctone = tx_tone else: mem.rtone = tx_tone return mem.cross_mode = "%s->%s" % (tx_mode, rx_mode) mem.tmode = "Cross" if tx_mode == "Tone": mem.rtone = tx_tone elif tx_mode == "DTCS": mem.dtcs = tx_tone if rx_mode == "Tone": mem.ctone = rx_tone elif rx_mode == "DTCS": mem.dtcs = rx_tone # No support for different codes yet def _encode_tone(self, mem, _mem): if mem.tmode == "": _mem.tx_tone = _mem.rx_tone = 0xFFFF return def _tone(val): return int("%i" % (val * 10), 16) def _dcs(val, pol): polmask = pol == "R" and 0xC000 or 0x8000 return int("%i" % (val), 16) | polmask rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": rx_mode = "" tx_mode = "Tone" tx_tone = _tone(mem.rtone) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = _tone(mem.ctone) elif mem.tmode == "DTCS": rx_tone = tx_tone = "DTCS" tx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->", 1) if tx_mode == "DTCS": tx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = _tone(mem.rtone) if rx_mode == "DTCS": rx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = _tone(mem.ctone) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number bit = 1 << ((number - 1) % 8) byte = (number - 1) / 8 if self._memobj.emptyflags[byte] & bit: mem.empty = True return mem mult = _mem.is625 and 6250 or 5000 mem.freq = _mem.freq * mult mem.offset = _mem.offset * 5000 mem.duplex = THUV3R_DUPLEX[_mem.duplex] mem.mode = _mem.iswide and "FM" or "NFM" self._decode_tone(mem, _mem) mem.skip = (self._memobj.skipflags[byte] & bit) and "S" or "" for char in _mem.name: try: c = THUV3R_CHARSET[char] except: c = "" mem.name += c mem.name = mem.name.rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] bit = 1 << ((mem.number - 1) % 8) byte = (mem.number - 1) / 8 if mem.empty: self._memobj.emptyflags[byte] |= bit _mem.set_raw("\xFF" * 16) return self._memobj.emptyflags[byte] &= ~bit if chirp_common.is_fractional_step(mem.freq): mult = 6250 _mem.is625 = True else: mult = 5000 _mem.is625 = False _mem.freq = mem.freq / mult _mem.offset = mem.offset / 5000 _mem.duplex = THUV3R_DUPLEX.index(mem.duplex) _mem.iswide = mem.mode == "FM" self._encode_tone(mem, _mem) if mem.skip: self._memobj.skipflags[byte] |= bit else: self._memobj.skipflags[byte] &= ~bit name = [] for char in mem.name.ljust(6): try: c = THUV3R_CHARSET.index(char) except: c = THUV3R_CHARSET.index(" ") name.append(c) _mem.name = name print repr(_mem) @classmethod def match_model(cls, filedata, filename): return len(filedata) == 2320