chirp-daily-20170714/0000755000016101777760000000000013132065774015373 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/chirp.xsd0000644000016100007500000000244611717005656015565 0ustar jenkins00000000000000 chirp-daily-20170714/PKG-INFO0000644000016101777760000000027413132065774016473 0ustar jenkinsnogroup00000000000000Metadata-Version: 1.0 Name: chirp Version: daily-20170714 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN chirp-daily-20170714/setup.py0000644000016101777760000001172612476006422017107 0ustar jenkinsnogroup00000000000000import sys import os from chirp import CHIRP_VERSION from chirp.drivers import * import chirp def staticify_chirp_module(): import chirp with file("chirp/__init__.py", "w") as init: print >>init, "CHIRP_VERSION = \"%s\"" % CHIRP_VERSION print >>init, "__all__ = %s\n" % str(chirp.__all__) print "Set chirp/__init__.py::__all__ = %s" % str(chirp.__all__) def staticify_drivers_module(): import chirp.drivers with file("chirp/drivers/__init__.py", "w") as init: print >>init, "__all__ = %s\n" % str(chirp.drivers.__all__) print "Set chirp/drivers/__init__.py::__all__ = %s" % str( chirp.drivers.__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() staticify_drivers_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) for mod in chirp.drivers.__all__: mods.append("chirp.drivers.%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", "chirp.drivers", "chirp.ui"], 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("chirp/ui"): nuke_manifest("include *.xsd", "include share/*.desktop", "include share/chirp.png", "include share/*.1", "include stock_configs/*", "include COPYING") default_build() chirp-daily-20170714/stock_configs/0000755000016101777760000000000013132065774020226 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/stock_configs/FR Marine VHF Channels.csv0000644000016101777760000000724612644407617024602 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 39,SEA 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 44,SEA 75,156.775000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 45,SEA 76,156.825000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 46,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 47,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 48,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 49,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 50,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 51,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 52,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 53,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 54,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 55,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,, 56,SEA 87,157.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 57,SEA 88,157.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, chirp-daily-20170714/stock_configs/KDR444.csv0000644000016101777760000000115613001073401021577 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,KDR444 1,444.600,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 2,KDR444 2,444.650,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 3,KDR444 3,444.800,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 4,KDR444 4,444.825,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 5,KDR444 5,444.850,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 6,KDR444 6,444.875,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 7,KDR444 7,444.925,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 8,KDR444 8,444.975,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, chirp-daily-20170714/stock_configs/UK Business Radio Simple Light Frequencies.csv0000644000016101777760000000207712644407617030562 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,BRSL1,77.687500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 2,BRSL2,86.337500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 3,BRSL3,86.350000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 4,BRSL4,86.362500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 5,BRSL5,86.375000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 6,BRSL6,164.050000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 7,BRSL7,164.062500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 8,BRSL8,169.087500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 9,BRSL9,169.312500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 10,BRSL10,173.050000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 11,BRSL11,173.062500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 12,BRSL12,173.087500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 13,BRSL13,449.312500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 14,BRSL14,449.400000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, 15,BRSL15,449.475000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,, chirp-daily-20170714/stock_configs/US Calling Frequencies.csv0000644000016101777760000000060512644407617025062 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,6m Call,52.525000,,0.000000,,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-daily-20170714/stock_configs/US CA Railroad Channels.csv0000644000016101777760000002736512652345221024777 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,Tstep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,AAR002,159.810000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 2,AAR003,159.930000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 3,AAR004,160.050000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 4,AAR005,160.185000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 5,AAR006,160.200000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 6,AAR007,160.215000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 7,AAR008,160.230000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 8,AAR009,160.245000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 9,AAR010,160.260000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 10,AAR011,160.275000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 11,AAR012,160.290000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 12,AAR013,160.305000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 13,AAR014,160.320000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 14,AAR015,160.335000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 15,AAR016,160.350000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 16,AAR017,160.365000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 17,AAR018,160.380000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 18,AAR019,160.395000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 19,AAR020,160.410000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 20,AAR021,160.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 21,AAR022,160.440000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 22,AAR023,160.455000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 23,AAR024,160.470000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 24,AAR025,160.485000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 25,AAR026,160.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 26,AAR027,160.515000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 27,AAR028,160.530000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 28,AAR029,160.545000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 29,AAR030,160.560000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 30,AAR031,160.575000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 31,AAR032,160.590000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 32,AAR033,160.605000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 33,AAR034,160.620000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 34,AAR035,160.635000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 35,AAR036,160.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 36,AAR037,160.665000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 37,AAR038,160.680000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 38,AAR039,160.695000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 39,AAR040,160.710000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 40,AAR041,160.725000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 41,AAR042,160.740000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 42,AAR043,160.755000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 43,AAR044,160.770000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 44,AAR045,160.785000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 45,AAR046,160.800000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 46,AAR047,160.815000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 47,AAR048,160.830000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 48,AAR049,160.845000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 49,AAR050,160.860000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 50,AAR051,160.875000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 51,AAR052,160.890000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 52,AAR053,160.905000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 53,AAR054,160.920000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 54,AAR055,160.935000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 55,AAR056,160.950000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 56,AAR057,160.965000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 57,AAR058,160.980000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 58,AAR059,160.995000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 59,AAR060,161.010000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 60,AAR061,161.025000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 61,AAR062,161.040000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 62,AAR063,161.055000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 63,AAR064,161.070000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 64,AAR065,161.085000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 65,AAR066,161.100000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 66,AAR067,161.115000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 67,AAR068,161.130000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 68,AAR069,161.145000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 69,AAR070,161.160000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 70,AAR071,161.175000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 71,AAR072,161.190000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 72,AAR073,161.205000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 73,AAR074,161.220000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 74,AAR075,161.235000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 75,AAR076,161.250000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 76,AAR077,161.265000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 77,AAR078,161.280000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 78,AAR079,161.295000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 79,AAR080,161.310000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 80,AAR081,161.325000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 81,AAR082,161.340000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 82,AAR083,161.355000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 83,AAR084,161.370000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 84,AAR085,161.385000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 85,AAR086,161.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 86,AAR087,161.415000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 87,AAR088,161.430000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 88,AAR089,161.445000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 89,AAR090,161.460000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 90,AAR091,161.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 91,AAR092,161.490000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 92,AAR093,161.505000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 93,AAR094,161.520000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 94,AAR095,161.535000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 95,AAR096,161.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 96,AAR097,161.565000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 97,AAR107,160.222500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 98,AAR108,160.237500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 99,AAR109,160.252500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 100,AAR110,160.267500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 101,AAR111,160.282500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 102,AAR112,160.297500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 103,AAR113,160.312500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 104,AAR114,160.327500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 105,AAR115,160.342500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 106,AAR116,160.357500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 107,AAR117,160.372500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 108,AAR118,160.387500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 109,AAR119,160.402500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 110,AAR120,160.417500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 111,AAR121,160.432500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 112,AAR122,160.447500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 113,AAR123,160.462500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 114,AAR124,160.477500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 115,AAR125,160.492500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 116,AAR126,160.507500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 117,AAR127,160.522500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 118,AAR128,160.537500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 119,AAR129,160.552500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 120,AAR130,160.567500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 121,AAR131,160.582500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 122,AAR132,160.597500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 123,AAR133,160.612500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 124,AAR134,160.627500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 125,AAR135,160.642500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 126,AAR136,160.657500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 127,AAR137,160.672500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 128,AAR138,160.687500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 129,AAR139,160.702500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 130,AAR140,160.717500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 131,AAR141,160.732500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 132,AAR142,160.747500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 133,AAR143,160.762500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 134,AAR144,160.777500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 135,AAR145,160.792500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 136,AAR146,160.807500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 137,AAR147,160.822500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 138,AAR148,160.837500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 139,AAR149,160.852500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 140,AAR150,160.867500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 141,AAR151,160.882500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 142,AAR152,160.897500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 143,AAR153,160.912500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 144,AAR154,160.927500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 145,AAR155,160.942500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 146,AAR156,160.957500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 147,AAR157,160.972500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 148,AAR158,160.987500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 149,AAR159,161.002500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 150,AAR160,161.017500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 151,AAR161,161.032500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 152,AAR162,161.047500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 153,AAR163,161.062500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 154,AAR164,161.077500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 155,AAR165,161.092500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 156,AAR166,161.107500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 157,AAR167,161.122500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 158,AAR168,161.137500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 159,AAR169,161.152500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 160,AAR170,161.167500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 161,AAR171,161.182500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 162,AAR172,161.197500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 163,AAR173,161.212500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 164,AAR174,161.227500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 165,AAR175,161.242500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 166,AAR176,161.257500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 167,AAR177,161.272500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 168,AAR178,161.287500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 169,AAR179,161.302500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 170,AAR180,161.317500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 171,AAR181,161.332500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 172,AAR182,161.347500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 173,AAR183,161.362500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 174,AAR184,161.377500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 175,AAR185,161.392500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 176,AAR186,161.407500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 177,AAR187,161.422500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 178,AAR188,161.437500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 179,AAR189,161.452500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 180,AAR190,161.467500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 181,AAR191,161.482500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 182,AAR192,161.497500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 183,AAR193,161.512500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 184,AAR194,161.527500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 185,AAR195,161.542500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 186,AAR196,161.557500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, chirp-daily-20170714/stock_configs/DE Freenet Frequencies.csv0000644000016101777760000000100112644407617025031 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,FRNET1,149.025000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 2,FRNET2,149.037500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 3,FRNET3,149.050000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 4,FRNET4,149.087500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 5,FRNET5,149.100000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, 6,FRNET6,149.112500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,, chirp-daily-20170714/stock_configs/US 60 meter channels (Dial).csv0000644000016101777760000000067512644407617025337 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,60m CH1,5.330500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,, 2,60m CH2,5.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-daily-20170714/stock_configs/US 60 meter channels (Center).csv0000644000016101777760000000067512644407617025706 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,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-daily-20170714/stock_configs/NOAA Weather Alert.csv0000644000016101777760000000135412761227400024065 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,WX1PA7,162.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 2,WX2PA1,162.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 3,WX3PA4,162.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 4,WX4PA2,162.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 5,WX5PA3,162.450000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 6,WX6PA5,162.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 7,WX7PA6,162.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 8,WX8,161.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 9,WX9,161.775000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, 10,WX10,163.275000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,, chirp-daily-20170714/stock_configs/US MURS Channels.csv0000644000016101777760000000070512644407617023562 0ustar jenkinsnogroup00000000000000Location,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-daily-20170714/stock_configs/US Marine VHF Channels.csv0000644000016101777760000000764112644407617024621 0ustar jenkinsnogroup00000000000000Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL 1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 39,DSC 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 44,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 45,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 46,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 47,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 48,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 49,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 50,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 51,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 52,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 53,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 54,AIS 87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 55,AIS 88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,, 56,SEA F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 57,SEA F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 58,SEA F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 59,SEA L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, 60,SEA L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,, chirp-daily-20170714/stock_configs/EU LPD and PMR Channels.csv0000644000016101777760000001257113006307374024533 0ustar jenkinsnogroup00000000000000Location,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 01,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 72,PMR 02,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 73,PMR 03,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 74,PMR 04,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 75,PMR 05,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 76,PMR 06,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 77,PMR 07,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 78,PMR 08,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 81,PMR 09,446.106250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 82,PMR 10,446.118750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 83,PMR 11,446.131250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 84,PMR 12,446.143750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 85,PMR 13,446.156250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 86,PMR 14,446.168750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 87,PMR 15,446.181250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, 88,PMR 16,446.193750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,, chirp-daily-20170714/stock_configs/US FRS and GMRS Channels.csv0000644000016101777760000000275512644407617024711 0ustar jenkinsnogroup00000000000000Location,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-daily-20170714/COPYING0000644000016100007500000010451311717005656014771 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-daily-20170714/setup.cfg0000644000016100007500000000022311717005656015550 0ustar jenkins00000000000000[bdist_rpm] requires = pyserial packager = Dan Smith description = A frequency tool for Icom D-STAR Repeaters vendor = KK7DS chirp-daily-20170714/chirp_memory.xsd0000644000016100007500000001002411717005656017144 0ustar jenkins00000000000000 chirp-daily-20170714/share/0000755000016101777760000000000013132065774016475 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/share/chirp.png0000644000016100007500000002272711717005656016661 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-daily-20170714/share/chirp.desktop0000644000016100007500000000041311717005656017532 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-daily-20170714/share/chirpw.10000644000016100007500000000270211717005656016413 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-daily-20170714/chirpw0000755000016101777760000001013413046277310016607 0ustar jenkinsnogroup00000000000000#!/usr/bin/env python # # Copyright 2014 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 from chirp import logger from chirp import elib_intl from chirp import platform from chirp.drivers import * from chirp.ui import config import sys import os import locale import gettext import argparse import logging LOG = logging.getLogger("chirpw") localepath = platform.get_platform().find_resource("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", "Russian": "ru", "Portuguese (BR)": "pt_BR", "French": "fr", "Spanish": "es_ES", } try: LOG.info("Language: %s", lang_codes[manual_language]) langs = [lang_codes[manual_language]] except KeyError: LOG.error("Unsupported language `%s'" % manual_language) else: lc, encoding = locale.getdefaultlocale() if (lc): langs = [lc] try: langs += os.getenv("LANG").split(":") except: pass try: if os.name == "nt": elib_intl._putenv("LANG", langs[0]) else: os.putenv("LANG", langs[0]) except IndexError: 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] try: vmaj, vmin, vrel = pyver.split(".", 3) except: vmaj, vmin = pyver.split(".", 2) vrel = 0 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() parser = argparse.ArgumentParser() parser.add_argument("files", metavar="file", nargs='*', help="File to open") logger.add_version_argument(parser) parser.add_argument("--profile", action="store_true", help="Enable profiling") logger.add_arguments(parser) args = parser.parse_args() logger.handle_options(args) a = None if True: from chirp.ui import mainapp a = mainapp.ChirpMain() for i in args.files: LOG.info("Opening %s", i) a.do_open(i) a.show() if args.profile: import cProfile import pstats import gtk cProfile.run("gtk.main()", "chirpw.stats") p = pstats.Stats("chirpw.stats") p.sort_stats("cumulative").print_stats(10) else: import gtk gtk.main() if config._CONFIG: config._CONFIG.set("last_dir", platform.get_platform().get_last_dir(), "state") config._CONFIG.save() chirp-daily-20170714/locale/0000755000016101777760000000000013132065774016632 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/es_ES/0000755000016101777760000000000013132065774017630 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/es_ES/LC_MESSAGES/0000755000016101777760000000000013132065774021415 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/es_ES/LC_MESSAGES/CHIRP.mo0000644000016101777760000003631413132065774022626 0ustar jenkinsnogroup00000000000000Þ•Êl ¼ ðñ 6;Up u€ †’¤· ÀÊÓ ÚäÿŠšµÄÚâê òü #; BLU\ r} „Ž ´ Æ ÐÝî0Ia h!r”¯ÄÜö&8? N.Z ‰“˜¶)Ïù(=Y\a ta~Ià*1BVo!ˆªÅÙßð'&8+_‹"¨ËÓ%×-ý++nW"Æéò ,1 7B KU]!t– ¬¸½ÂÈÏçù !+ HSAY› ¦¿Î×àæú 4PX_n¥º ÀËÛëð<Í>9 5F)| )¦ +Ð Uü ¥R!ø!û! " " "" #"$D"i""ˆ"ž"$³"*Ø"# #!#5#<#"X#{#“# ³#Á#Ç#Ì#Ô#Ú#à#ç#î#ö#ü#$$7$‚R$Õ%ñ% &&(&(-&"V&y&&•&œ&«&À& Ø& ä& ñ&ü& ''-'<' Ì'í'(($( -( 8(C(\( c(p(w(‘( ˜(¥(®(!µ( ×(ã( ì(ú()4)S)l)|)‘)ª)À)Ù)ó) * *4 *$U*z*‘*«*$Æ*ë*++ +3+GE+ +˜+#ž+!Â+4ä+,1,M,f,†,‰,, ¨,rµ,W(-€-‰- -·-)Ó-*ý-!(.J._.g.|. ‘.0Ÿ.(Ð..ù. (/-I/w//.ƒ/D²/0÷/{(0!¤0Æ0 Ï0ð0111 1,12181>1!W1y1 11¤1¬1²1Á1 ß1'2(2 12&?2f2v2?|2¼2Ã2Ì2ë2ú23 33'3@3U3!r3”3 œ3¨3"½3à3ù34!414E4Y4`46s48ª4Cã69'7,a8.Ž8'½8Xå8¥>9ä9æ9 ë9ù9::$:(C:l: ‰:”:±:1Í:6ÿ: 6; D;R; m;w;%–;¼;0Ù; << <(<0<8<A<H< O<Y<^<!g<'‰<±<´®]½žÊ°¸Pe‘—Æs?‡¶«ÅÉ~!uÀ$¡5<pz"k/ Ä@Un_ÁO3NÇb`M¦…“>a”ŒJ•QEi*¢Ÿ+ˆo[𹂳¼^Š;W„™–¤›VS1YX¯r F}-(cKB¨tG©¾± ‰8ƒ¥‹ÃD v6ZH9A%mµ0˜.Cf)# €£§=|7qx{¬Lº\Žd¿2,ÈyªT’&h»œ4wIg †·' :­R²ljAdding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCutCutting memory {number}D-STARDTCS CodeDTCS PolDeleteDelete (and shift up)Delete allDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from RFinderImport from RepeaterBookImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRadioRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2016-03-28 02:44+0100 Last-Translator: Alfonso Moratalla Language-Team: Language: es_ES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.4 Añadiendo memoria {number}Ajustar nueva localizaciónTodosHa ocurrido un errorAutoDesplazamiento del repetidor automáticoValor incorrecto para {col}: {val}BancoNombres de los bancosBancosFicheros CHIRPFichero CHIRP nativoImagenes de radio CHIRPFichero CSVFicheros CSVIndicativoCancelarCanceladoNo puede ser importado porqueCambiar idiomaElija un idioma o Auto para usar el de por defecto del sistema operativo. Tendrá que reiniciar la aplicación para que el cambio surta efecto.Elija uno desde el que importar:Progreso del clonadoClonado fallido: {error}ClonandoColumnasComentarioCompletadoConfirmar sobreescrituraCopiarModo cruzadoCortarCortando memoria {number}D-STARCódigo DTCSDTCS PolBorrarBorrar (y desplazar hacia arriba)Borrar todoDetectarDesarrolladorDiferencias entre memorias rawDiferencia entre {a} y {b}Diferencias entre memorias rawPestañas de diferenciasCódigo digital¿Descartar cambios?No mostrar esto otra vezDescargar desde radioDescargando lista MYCALLDescargando lista RPTCALLDescargando lista URCALLDuplexI_ntercambioEditando elemento nuevo, tomando valores por defectoHabilitar funciones de desarrolladorBorrando memoria {loc}Borrando memoria {number}Error importando memorias:Informar de errores está habilitadoError fijando memoriaIntercambiar memoriasExportarExportar a ficheroEl fichero existeEl fichero ha sido modificado, ¿guardar los cambios antes de cerrarlo?FrecuenciaDesdeObteniendo banco para memoria {num}Obteniendo información del bancoObteniendo información del banco para memoria {num}Obteniendo canal {chan}Obteniendo memoria {number}Obteniendo memoria {num}Obteniendo memoria raw {number}IrAyudaOcultar campos no usadosFicheros ICFLos ficheros ICF no se pueden editar, solo mostrarse o importarse en otro fichero. ¿Abrirlo en modo solo-lectura?Si quieres deshabilitar esta característica tiene que hacerlo en el menú AyudaImportarImportar desde ficheroImportar desde RFinderImportar desde RepeaterBookImportar desde configuración por defectoImportar configuración por defecto {name}Importando información del bancoMemoria incompatibleÃndiceInsertar fila encimaInsertar fila debajoError internoError interno: La columna {name} no se encuentraError interno: límite {number} invalidoError interno: No se ha podido subir a {model}Valor no válido para este campoValor no válido. Debe ser un número entero.InversoLocLa localización de memoria será importada enLocalización de la memoria en el fichero que está siendo importadoLocalización {number} ya está siendo importadaLa localización {number} ya ha sido importada. Elija otro valor para 'Nueva Localización' antes de seleccionar 'Importar'Buscando un hueco libre({number})MemoriasLas memorias deben ser continuasRango de memoria:Memoria {number}ModoModeloMover Abaj_oSubirBajarSubirMovidas {count} memoriasMoviendo memoria de {old} a {new}Moviendo {src} a {dst}Mi indicativNombreNingunoNota:DesplazamientoAbrir fichero reciente {name}Abrir configuración por defectoAbrir configuración por defecto {name}OpcionesSobreescribir¿Sobrescribir localización {number}?¿Sobrescribir?PegarMemoria {number} pegada no es compatible con esta radio porque:PuertoPotenciaPreparando lista de memoria...Priming memoryRPT1CALLRPT2CALLRadioMemoria raw {number}Indicativo del repetidorEnviar estadísticasInformar está deshabilitadoRecibiendo información del bancoReversoSeleccionarSeleccionar columnasFijando índice para memoria {num}Fijando memoria {number}Poniendo nombre al bancoDesplazamientoMostrar vacíosMostrar memoria rawMostrar memoria rawSaltarCanales especialesEl fichero {name} ya existe. ¿Quiere sobreescribirlo?La característica de informar de CHIRP está diseñada para ayudar a mejorar la calidad permitiendo a los autores centrarse en los drivers de las radios usadas mas frecuentemente y los errores que se experimentan los usuarios. Los informes no contienen información que identifique al usuario y solo son usadas para fines estadísticos por los autores. Su privacidad es extremadamente importante, pero por favor considere dejar esta característica habilitada para ayudar a hacer CHIRP mejor ¿Seguro que quiere deshabilitar esta caracteristica?El {vendor} {model} tiene multiples sub-dispositivos independientesEl {vendor} {model} opera en modo live. Esto significa que cualquier cambio que haga es enviado inmediatamente a la radio. Por esto, no puede usar las operaciones Guardar o Subir. Si quiere editar el contenido offline, por favor Exporte a un fichero CSV, usando el menu Archivo.Hubo un error mientras se exportaba: {error}Hubo un error durante la importación: {error}Hubo un error abriendo {fname}: {error}Hubo errores mientras se abría {file}. ¡Las memorias afectadas no se podrán importar!Esta operación requiere mover los subsecuentes canales un hueco hasta encontrar una localización vacía. Esto puede tardar MUCHO tiempo. ¿Está seguro de hacerlo?ATonoModo del tonoToneSqlEscalón de sintoníaURCALLNo se puede detectar radio en {port}No se pueden hacer cambios a este modeloTipo de fichero no soportadoSin nombreActualizando listado RPTCALLActualizando listado URCALLActualizando índice del banco para memoria {num}Actualizando información del banco para memoria {num}Subir a radioVX7 CommanderFicheros del VX7 CommanderProveedorColumnas visibles para {radio}Con contribuciones significativas de:Escribiendo memoria {number}¡Solo puede ver diferencias entre dos memorias!Tu indicativo_Copiar_Cortar_Borrar_Editar_Fichero_Pegar_Radio_Reciente_Verinactivo{num} errores mientras se abría:fichero de imagen para {vendor} {model}{vendor} {model} en {port}chirp-daily-20170714/locale/uk_UA/0000755000016101777760000000000013132065774017636 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/uk_UA/LC_MESSAGES/0000755000016101777760000000000013132065774021423 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/uk_UA/LC_MESSAGES/CHIRP.mo0000644000016101777760000004277513132065774022644 0ustar jenkinsnogroup00000000000000Þ•¶Ìû| hi€”˜®³Íè íø þ / 8BK R\wЇ-<RZb jt ‡’ª ±»ÄÚ áëÿ  (9Oc{”¬ ³!½ßú'BW ^.j ™£¨Æ)ß  8Milq „aŽIð:AU!n«¿ÅÖç'ö&+Eq"ޱ¹%½-ã+n="¬Ï ëù !4V lx}‚ˆ§¹Ù áë AUZ`yˆ‘š®ÀÒè :Rg mxˆ<žÍÛ9©5ã)+CUo¥Åkn s} … –$·Üòû $& *K v † ” ¨ ¯ "Ë î ! &!4!:!?!G!M!S!Z!a!i!o!t!Ž!wª!)"#4L##ˆ#¦#6¯#1æ#$!$ 9$D$U$+d$ $$¬$½$Ð$8ã$%ü4%31&!e&,‡&´&É&Ø&é&#ü& ')3']' d'p'1y'«'¾'!Ñ'$ó'#(<(T(7s(0«(,Ü(- ),7)d) s)b)8â)$*'@*Nh*/·*ç*ö*S +^+m+1p+2¢+MÕ+&#,)J,&t,-›,É,Ð,Bß,"-¼1-ˆî- w.„.œ.A¹.8û. 4/ U/$`/"…/!¨/PÊ/W0Ts0DÈ0[ 1i1z1K~1SÊ1C2Áb2/$34T3‰3©3 À3 Ë3"Ø3;û3874'p4˜4°4¸4Ì4Þ41ç4<5CV5 š5¥5-¾5ì5t6{6„62™6Ì6ë6ô6ý6!7:7X72t7 §7´7<Ô7)8&;8b8#s8"—8º8!Ï8dñ8sV9SÊ<#=MB?H?…Ù?F_@¦A«A²AÌAÔAðA9÷AH1B2zB­B'¿B&çB@CFOC,–CÃC!ØCúC* D)6D!`DG‚DÊDâDöDEE 0E:ELEfE vE„E6E(ÔE2ŸWir6œTŽ˜:=®…pm¬¶—J†¯`l›³%sƒ"GzA‹}b“<OLg ˆ‚y­¦ -’ X¡D‘k(  +Hc• ™©\&wU!«_'„]x3t/²du)š9j£h~$,>±žv*‰‡NMP#SI¨¤Š´n¥Y1^”R§ŒK5€48QoBa{;?FeVfC°q–¢Z0 ª7.µ[|E@Adding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCross ModeCutting memory {number}D-STARDTCS CodeDTCS PolDelete (and shift up)DetectDeveloperDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error reporting is enabledError setting memoryExportFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport from RFinderImport from RepeaterBookImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})Memories must be contiguousMemory range:Memory {number}ModeModelMove _UpMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?Pasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image fileProject-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2015-11-30 10:36+0200 Last-Translator: laser Language-Team: laser Language: uk_UA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {number}ÐаÑтроїти нове розташуваннÑÐ’ÑеСталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°ÐвтоÐвтоматичний Ñ€Ð¾Ð·Ð½Ð¾Ñ Ñ€ÐµÐ¿Ñ–Ñ‚ÐµÑ€Ð°ÐŸÐ¾Ð³Ð°Ð½Ðµ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ {col}: {val}БанкІмена банківБанкиCHIRP файлиФайл CHIRPОбрази радіоÑтанцій CHIRPФайл CSVCSV-файлиПозивнийСкаÑуватиСкаÑованоÐе можна імпортувати, оÑкількиЗмінити мовуВиберіть мову або авто Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð² операційній ÑиÑтемі. Вам потрібно буде перезапуÑтити додаток до того, Ñк зміни вÑтуплÑть в ÑилуВиберіть один Ð´Ð»Ñ Ð†Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ з:ПоÑтуп клонуваннÑПомилка клонуваннÑ: {error}КлонуваннÑСтовпціКоментарЗавершеноПідтвердіть замінуКроÑÑ€ÐµÐ¶Ð¸Ð¼ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {number}D-STARDTCS кодDTCS PolВидалити (та зÑунути вгору)ВизначитиРозробникПорівнÑÐ½Ð½Ñ {a} та {b}ПорівнÑти raw пам'ÑтьВкладки порівнÑннÑЦифровий кодСкаÑувати зміни?Ðе показувати наÑтупного разуСкопіювати з радіоÑÑ‚Ð°Ð½Ñ†Ñ–Ñ—Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑпиÑку MYCALLÐ—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑпиÑку RPTCALLÐ—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑпиÑку URCALLДуплекÑ_ÐžÐ±Ð¼Ñ–Ð½Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ елемента, беручи за замовчуваннÑÐ¼Ð£Ð²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— Ñ€Ð¾Ð·Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ°Ð¡Ñ‚Ð¸Ñ€Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {loc}Ð¡Ñ‚Ð¸Ñ€Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {number}Ð—Ð²Ñ–Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾ критичні помилки ввімкнутоПомилка наÑтройки пам'ÑтіЕкÑпортФайл Ñ–ÑнуєФайл змінено, зберегти зміни перед закриттÑм?ЧаÑÑ‚Ð¾Ñ‚Ð°Ð—ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð±Ð°Ð½ÐºÑƒ пам'Ñті {num}ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— Ð±Ð°Ð½ÐºÑƒÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— банку Ð´Ð»Ñ Ð¿Ð°Ð¼'Ñті {num}ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ°Ð½Ð°Ð»Ñƒ {chan}ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {number}ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті {num}ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ RAW пам'Ñті {number}ІтиДовідкаПриховувати невикориÑтовувані полÑICF файлиICF файли не можна редагувати, тільки відобразити або імпортувати. Відкрити в режимі тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ?Якщо ви хочете вимкнути цю функцію, ви можете зробити це в меню ДовідкаІмпортІмпорт з RFinderІмпорт з RepeaterBookІмпорт заводÑької конфігурації {name}Ð†Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— банкуÐеÑуміÑна пам'ÑтьЗміÑтДодати Ñ€Ñдок зверхуДодати Ñ€Ñдок Ð·Ð½Ð¸Ð·ÑƒÐ’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: не знайдено Ñтовпець {name}Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: ÐеприпуÑтиме Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ {number}Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: не вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати на {model}ÐеприпуÑтиме Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ полÑÐеприпуÑтиме значеннÑ. Повинно бути цілим чиÑлом.ІнверÑÑ–ÑLocÐ Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті буде імпортовано Ð´Ð¾Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті у файлі, що імпортуютьÑÑÐ Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ {number} вже Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ {number} вже імпортуютьÑÑ. Виберіть інше Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ 'Ðове розташуваннÑ' перед вибором 'Імпорт'Пошук вільної точки ({number})Пам'Ñть має бути безперервнаДіапазон пам'Ñті:Пам'Ñть {number}РежимМодельПереміÑтити Ð’_горуПеремещенно {count} запиÑів пам'ÑÑ‚Ñ–ÐŸÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ð°Ð¼'Ñті з {old} до {new}ÐŸÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ {src} до {dst}Мій позивнийІм'ÑÐе вказаноПримітка:ЗÑувВідкриті оÑтанній файл {name}Відкрити заводÑькі конфігураціїВідкрите заводÑькі конфігурації {name}ОпціїПерезапиÑатиПерезапиÑати міÑце {number}?ПерезапиÑати?Ð’Ñтавлена пам'Ñть {number} неÑуміÑна із цією радіоÑтанцією тому що:ПортПотужніÑтьПідготовка ÑпиÑку пам'Ñті...Первинна пам'ÑтьRPT1CALLRPT2CALLRAW пам'Ñть {number}Позивний репітераЗвіт ÑтатиÑтикиЗвіти Ð²Ð¸Ð¼ÐºÐ½ÑƒÑ‚Ð¾ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— банкуРеверÑВиберіть СтовпціУÑтановка індекÑу Ð´Ð»Ñ Ð¿Ð°Ð¼'Ñті {num}УÑтановка пам'Ñті {number}ÐаÑтройка назви банаЗміщеннÑПоказувати порожніПоказати raw пам'ÑтьПропуÑтитиСпеціальні каналиФайл {name} вже Ñ–Ñнує. Ви дійÑно хочете перезапиÑати його?Функцію Ð·Ð²Ñ–Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ CHIRP розроблено, щоб допомогти поліпшити ÑкіÑть, дозволÑючи авторам зоÑередитиÑÑ Ð½Ð° драйверах радіоÑтанцій, що найчаÑтіше викориÑтовуєтьÑÑ Ñ– помилках доÑвідчених кориÑтувачів. Звіти не міÑÑ‚Ñть ідентифікаційну інформацію Ñ– викориÑтовуютьÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ Ð´Ð»Ñ ÑтатиÑтичних цілей авторів. Ваша конфіденційніÑть Ñ” надзвичайно важлива, але будь лаÑка, залиште цю функцію включеною, щоб допомогти зробити CHIRP краще! ви впевнені, що хочете відключити цю функцію?{vendor} {model} має кілька незалежних Ñуб приÑтроїв{vendor} {model} працює в режимі реального чаÑу. Це означає, що будь-Ñкі зміни негайно відправлÑютьÑÑ Ð½Ð° радіоÑтанцію. Через це не вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ Ð—Ð°Ð¿Ð¸Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ð¹. Якщо ви хочете редагувати вміÑÑ‚ в автономному режимі, будь лаÑка, ЕкÑпортуйте файл CSV, за допомогою Меню Файл.Виникла помилка під Ñ‡Ð°Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ: {error}СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при відкритті {fname}: {error}Там були помилки при відкритті {file}. Порушена пам'Ñть не буде імпортована!Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ” Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… наÑтупних каналів на одне міÑце до тих пір, поки пуÑте міÑце не буде доÑÑгнуто. Це може зайнÑти багато чаÑу. Ви впевнені, що хочете це зробити?ДоТонТоновий режимToneSqlКрок наÑтройкиURCALLÐе вдалоÑÑ Ð²Ð¸Ñвити радіо на {port}Ðе вдаєтьÑÑ Ð²Ð½ÐµÑти зміни до цієї моделіФайл непідтримуваного типуБез Ð½Ð°Ð·Ð²Ð¸ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑпиÑку RPTCALLÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑпиÑку URCALLÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ð´ÐµÐºÑу банку пам'Ñті {num}ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— банку пам'Ñті {num}ЗапиÑати в радіоÑтанціюКомандир VX7Файли VX7 командираВиробникВидимі Ñтовпці Ð´Ð»Ñ {radio}Значні внеÑки зробили:Ð—Ð°Ð¿Ð¸Ñ Ð¿Ð°Ð¼'Ñті {number}Ви можете порівнÑти тільки дві пам'Ñті!Ваш позивний_Копіювати_Вирізати_Видалити_Редагувати_Файл_Ð’Ñтавити_РадіоÑтанціÑОÑ_танніВ_иглÑдпроÑтоюваннÑ{num} помилки під Ñ‡Ð°Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ:файлу образу {vendor} {model}chirp-daily-20170714/locale/ru/0000755000016101777760000000000013132065774017260 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/ru/LC_MESSAGES/0000755000016101777760000000000013132065774021045 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/ru/LC_MESSAGES/CHIRP.mo0000644000016101777760000004046113132065774022254 0ustar jenkinsnogroup00000000000000Þ•µÄñl 01H\`v{•° µÀ ÆÒä÷   $?ŠOÚõ"* 2< OZr yƒŒ¢ ©³Ç Ù ãð+C\t {!…§Â×ï  &.2 akpŽ)§Ñè149 LaVI¸ !6Xs‡ž¯'¾&æ+ 9"Vy%…-«+Ùn"t— ³ÁÑÖÜå!ü 4@EJPWo¡ ©³ ÐAÛ"(APYbvˆš°ÌÔã/ 5@PU<fÍ£9q5«+áU ¥c    #- 4$Uz™¯$Ä*é $ 2 F M "i Œ ¤ Ä Ò Ø Ý å ë ñ ø ÿ ! !!,!›H!*ä!")" 0"=",F"=s"±"º" Ò"Ý"ã"ò"### +#8#CI##§#5¸$î$%ÿ$%%6%E%\%/o%Ÿ%$´%Ù% à%ë%/ô%$&5&L&`& r&|&+”&$À& å&''7'O' ^'Kj'#¶'#Ú'&þ'1%(,W(„(“(?±(ñ()0)76)Ln)&»)(â)* *96*p*u*2„*·*ã»*UŸ+ õ+,,O;,,‹,'¸, à,$í,$- 7-5D-<z-@·-<ø-O5.….–.Dš.Dß.9$/µ^/309H0‚0¢0 ¸0 Ã0 Ð0+Ü071'@1h1€1 ‡1”1¤1/µ1Jå1Q02 ‚22;¦2â2yü2v3313Â3â3ë3&ô3!4%=4c4'ƒ4«4¼4;Ú4(5(?5 h5!s5•5¥5#´5MØ5&6\·89.0;œ_;Zü;W= ^=k= =Ž=•=4œ=KÑ=2> P>[>x>D”>JÙ>$? C? Q?_?-x?/¦?"Ö?ù?@1@G@Y@ i@ w@@“@£@µ@½@:Î@' A¤b†[I@O„6²©A­£nPµQ“ˆ¯®¬7–3€}œ# «92r.DFXŸ ”ª"pl\!v¦*˜™’…k?³fE4¢‘H;)C(§Rz<>B^ Gcmt5:šx~ZƒWq¡,=ž0 d¨a‹K/UŠMS ›ŒJNV—+8|-{esw‚$ h]‡&Y_`Tiog° •u¥%‰´Žj±L1y'Adding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCross ModeCutting memory {number}D-STARDTCS CodeDTCS PolDelete (and shift up)DetectDeveloperDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error reporting is enabledError setting memoryExportFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport from RFinderImport from RepeaterBookImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})Memories must be contiguousMemory range:Memory {number}ModeModelMove _UpMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?Pasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image fileMIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: POEditor.com Project-Id-Version: CHIRP Language: ru Добавление памÑти {number}ÐÐ¾Ð²Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸ÑÐ’ÑеОшибкаÐвтоÐÐ²Ñ‚Ð¾Ñ€Ð°Ð·Ð½Ð¾Ñ Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¸Ñ‚ÐµÑ€Ð°Ðекорректное значение Ð´Ð»Ñ {col}: {val}БанкИмена банковБанкиCHIRPФайл CHIRPОбразы памÑти ChirpCSVCSVПозывнойОтменаОтмененоÐевозможно импортировать, поÑколькуИзменить ÑзыкВыберите Ñзык или Ðвто Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñзыка операционной ÑиÑтемы. Вам нужно будет перезапуÑтить приложение, прежде чем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð²ÑтупÑÑ‚ в Ñилу.Выберите один Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° из:ЗагрузкаОшибка загрузки {error}ЗагрузкаСтолбцыКомментарийЗавершеноПодтверждение перезапиÑиКроÑÑрежимОбрезка памÑти {number}D-STARDTCSПРДDTCS PolУдалить (и Ñдвинуть вверх)ПроверкаРазработчикDiff of {a} and {b}Diff raw memoriesDiff tabsЦифровой кодÐе ÑохранÑть изменениÑ?Ðе отображать ÑноваЧтение из ÑтанцииЗагрузка MYCALLЗагрузка RPTCALLЗагрузка URCALLДуплекÑОбмен_Ð”Ð»Ñ Ð½Ð¾Ð²Ð¾Ð¹ позиции наÑтройки по умолчаниюРежим разработчикаСтирание памÑти {loc}Стирание памÑти {number}Отчёты об ошибках включеныОшибка уÑтановки памÑтиЭкÑпортФайл ÑущеÑтвуетФайл изменён, Ñохранить изменениÑ?ЧаÑтотаОтПолучение банка памÑти {num}Получение информации из банкаПолучение информации банка Ð´Ð»Ñ Ð¿Ð°Ð¼Ñти {num}Получение канала {chan}Получение памÑти {number}Получение из памÑти {num}Получение бинарной памÑти {number}ОкСправкаСкрыть неиÑпользуемые полÑICFФайлы ICF не могут быть отредактированы, возможно только показать или импортировать в другой файл. Открыть только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ?Отключить Ñту функцию можно в меню СправкаИмпортИмпорт из RFinderИмпорт из RepeaterBookИмпорт предуÑтановленную конфигурацию {name}Импорт информации банкаÐеÑовмеÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ð°Ð¼ÑтьИндекÑÐ’Ñтавка Ñтроки вышеВÑтавка Ñтроки нижеОшибкаОшибка: Столбец {name} не найденОшибка: недопуÑтимый предел {number}Ошибка: Ðевозможно загрузить в {model}Ðеверное значение Ð´Ð»Ñ Ñтого полÑÐеверное значение. Должно быть целое чиÑло.ИнверÑиÑâ„–ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð¿Ð°Ð¼Ñти будет импортирована Ð²ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð¿Ð°Ð¼Ñти в файле импортируетÑÑÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ {number} уже Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð°ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ {number} уже импортирована. Выберите другое значение Ð´Ð»Ñ "ÐÐ¾Ð²Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ" перед выбором "Импорт"ПоиÑк Ñвободной точки ({number})ПамÑть должна быть непрерывнойДиапазон каналовПамÑть {number}РежимМодельВверх_Перемещенно {count} памÑтиПеремещение памÑти из {old} в {new}Перемещение {src} до {dst}Мой позывнойИмÑÐичегоЗаметка:СмещениеОткрыть недавний файл {name}Открыть предуÑтановленные конфигурацииОткрыть предуÑтановленную конфигурацию {name}ОпцииПерезапиÑатьПерезапиÑать раÑположение {number}?ПерезапиÑать?Ð’ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ð°Ñ Ð¿Ð°Ð¼Ñть {number} не ÑовмеÑтима Ñ Ñтой Ñтанцией, потому что:ПортМощноÑтьПодготовка ÑпиÑка памÑти...ÐŸÐµÑ€Ð²Ð¸Ñ‡Ð½Ð°Ñ Ð¿Ð°Ð¼ÑтьRPT1CALLRPT2CALLÐ‘Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ Ð¿Ð°Ð¼Ñть {number}Позывной репитераОтправка ÑтатиÑтикиОтчёты отключеныПолучение информацииОбратныйВыбрать ÑтолбцыУÑтановка индекÑа Ð´Ð»Ñ Ð¿Ð°Ð¼Ñти {num}УÑтановка памÑти {number}УÑтановка имени банкаСдвигПоказывать пуÑтыеShow raw memoryПропуÑкСпециальные каналыФайл {name} уже ÑущеÑтвует. ПерезапиÑать его?Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ отчётов позволÑет авторам получать информацию об ошибках программы, чтобы Ñвоевремменно улучшать её Ð´Ð»Ñ Ð’Ð°Ñ. Ðикакой перÑональной информации при Ñтом не ÑобираетÑÑ Ð¸ не отправлÑетÑÑ. ПозволÑÑ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ðµ отправлÑть отчёты вы помогаете авторам в дальнейшем уÑовершенÑтвовании программы. Ð’Ñ‹ дейÑтвительно хотите отключить Ñту функцию?{vendor} {model} имеет неÑколько различных подуÑтройÑтва{vendor} {model} программируетÑÑ Ð² режиме онлайн. Это означает, что любые Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½ÐµÐ¼ÐµÐ´Ð»ÐµÐ½Ð½Ð¾ загружаютÑÑ Ð² Ñтанцию. По Ñтой причине невозможно иÑпользовать операции Сохранить или Загрузить. ЕÑли вы хотите редактировать данные офлайн, то иÑпользуйте ЭкÑпорт в файл CSV из меню Файл.Ошибка Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ {fname}: {error}Были обнаружены ошибки при открытии. Ð—Ð°Ñ‚Ñ€Ð¾Ð½ÑƒÑ‚Ð°Ñ Ð¿Ð°Ð¼Ñть не Ñможет быть импортирована.Эта Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±ÑƒÐµÑ‚ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð²Ñех поÑледующих каналов в одно меÑто, пока Ñвободное меÑто не дудет доÑтигнуто. Это может занÑть ОЧЕÐЬ много времени. Ð’Ñ‹ уверены, что хотите Ñто Ñделать?ДлÑТонПРДВид ÑубтонаТонШПДШагURCALLÐ¡Ñ‚Ð°Ð½Ñ†Ð¸Ñ Ð½Ðµ обнаружена на {port}Ðевозможно внеÑти Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² Ñту модельÐеподдерживаемый тип файлаÐовыйОбновление RPTCALLОбновление URCALLОбновление индекÑа банка в памÑти {num}Обновление информации банка в памÑти {num}ЗапиÑÑŒ в ÑтанциюVX7 CommanderVX7 CommanderИзготовительВидимые Ñтолбцы Ð´Ð»Ñ {radio}При значительном учаÑтии:запиÑÑŒ памÑти {number}You can only diff two memories!Ваш позывной_Копировать_Вырезать_Удалить_Правка_Файл_Ð’Ñтавить_СтанциÑ_ÐедавниеВид_ожидание{num} ошибки(ок) во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ:Изображение {vendor} {model}chirp-daily-20170714/locale/pt_BR/0000755000016101777760000000000013132065774017640 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/pt_BR/LC_MESSAGES/0000755000016101777760000000000013132065774021425 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/pt_BR/LC_MESSAGES/CHIRP.mo0000644000016101777760000003552313132065774022637 0ustar jenkinsnogroup00000000000000Þ•Êl ¼ ðñ 6;Up u€ †’¤· ÀÊÓ ÚäÿŠšµÄÚâê òü #; BLU\ r} „Ž ´ Æ ÐÝî0Ia h!r”¯ÄÜö&8? N.Z ‰“˜¶)Ïù(=Y\a ta~Ià*1BVo!ˆªÅÙßð'&8+_‹"¨ËÓ%×-ý++nW"Æéò ,1 7B KU]!t– ¬¸½ÂÈÏçù !+ HSAY› ¦¿Î×àæú 4PX_n¥º ÀËÛëð<Í>9 5F)| )¦ +Ð Uü ¥R!ø!û! " " "" #"$D"i""ˆ"ž"$³"*Ø"# #!#5#<#"X#{#“# ³#Á#Ç#Ì#Ô#Ú#à#ç#î#ö#ü#$$7$]R$°%Í%æ%ë%û%& &?&E&T&[&j&& “& Ÿ& ¬&·& À&Ê& è&‰õ&'Ÿ'µ'Î'×' ß' ê'ô' ( (($(>( E(O(X(_( s(( ˆ(–(¨(º( Ì(Ö(å(û()4)O)k)†))#”)"¸)Û)ò) *'*E*[*k*t*Š*6œ* Ó*Þ* á*+.+M+b+{+‘+®+±+·+ Õ+vâ+OY,©,²,Æ,#Ú,þ,'-A-`-u-|-Ž- ¡-*®-'Ù-1.3.+R.~.†.*Š.1µ.)ç.u/(‡/°/¹/Ö/ë/ü/0 0 0 #0 00<0#U0y0’0¡0¦0­0³0º0×0$ï01 1(1 E1S1?Y1™1Ÿ1¥1Ä1×1à1é1ï122"32 V2w2 2‰2&œ2Ã2á2ü233%393>34O3 „3<5ZÌ5+'7+S7'7H§7®ð7Ÿ8¤8¨8±8 ¸8Â8#É8.í89 :9E9_9.x92§9Ú9 ð9þ9 : :!>:`: z:›:ª:²:·:¿:Ç:Ð:×:Þ: ç:ó:ú:;8;´®]½žÊ°¸Pe‘—Æs?‡¶«ÅÉ~!uÀ$¡5<pz"k/ Ä@Un_ÁO3NÇb`M¦…“>a”ŒJ•QEi*¢Ÿ+ˆo[𹂳¼^Š;W„™–¤›VS1YX¯r F}-(cKB¨tG©¾± ‰8ƒ¥‹ÃD v6ZH9A%mµ0˜.Cf)# €£§=|7qx{¬Lº\Žd¿2,ÈyªT’&h»œ4wIg †·' :­R²ljAdding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCutCutting memory {number}D-STARDTCS CodeDTCS PolDeleteDelete (and shift up)Delete allDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from RFinderImport from RepeaterBookImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRadioRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2013-03-30 22:04-0300 Last-Translator: Crezivando Language-Team: Language pt-BR Language: pt-BR MIME-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-15 Content-Transfer-Encoding: 8bit X-Generator: Poedit 1.5.5 Adicionando memória {number}Ajustar Nova LocalizaçãoTudoOcorreu um erroAutoOffset Automático RepetidorasValor inválido para {col}: {val}BancoNomes do BancoBancosArquivos CHIRPArquivo Nativo CHIRPRádio Imagens CHIRPArquivo CSVArquivos CSVIndicativoCancelarCanceladoNão pode ser importado porqueMudar idiomaEscolha um idioma ou Auto para usar o sistema padrão. Você precisará reinicializar o aplicativo antes para que as mudanças tenham efeito.Selecionar um para importar de:Clonagem em ProgressoClonagem falhou: {error}ClonandoColunasComentárioConcluídoConfirmar sobrescreverCopiarModo CrossCortarCortando memória {number}D-STARDTCS CodeDTCS PolApagarApagar (e deslocar)Apagar tudoDetectarDesenvolvedorDiff Memórias RawDiff de {a} e {b}Diff memórias rawDiff tabsCódigo DigitalDescartar Alterações?Não mostrar isto novamenteDescarregar a partir do RádioDescarregando lista MYCALLDescarregando lista RPTCALLDescarregando lista URCALLDuplexT_rocaEditando novo item, tomando padrõesHabilitar Funções de DesenvolvedorApagando memória {loc}Apagando memória {number}Erro ao importar memórias:Relatório de Erros habilitadoErro gravando memóriaTrocar memóriasExportarExportar Para ArquivoArquivo já ExisteArquivo modificado, salvar alterações antes de fechar?FrequênciaDeObtendo banco para memória {num}Obtendo informação do bancoObtendo informação do banco para memória {num}Obtendo canal {chan}Obtendo memória {number}Obtendo memória {num}Obtendo memória raw {number}IrAjudaOcultar Campos Não-utilizadosArquivos ICFArquivos ICF não podem ser editados, somente exibidos ou importados para outro arquivo. Abrir no modo somente-leitura?Se você desejar desabilitar este recurso você pode fazê-lo no menu AjudaImportarImportar do ArquivoImportar de RFinderImportar do Catálogo de RepetidorasImportar do config estoqueImportar configuração de estoque {name}Importando informação do bancoMemória IncompatívelÍndiceInserir row acimaInserir row abaixoErro InternoErro Interno: Coluna {name} não encontradaErro Interndo: limite Inválido {number}Erro Interno: Incapaz de descarregar para {model}Valor Inválido para este campoValor inválido. Deve ser um número inteiro.InversoLocMemória de localização será importada paraLocalização da memória no arquivo sendo importadaLocalização {number} está sendo importadaLocalização {number} está sendo importada. Escolha outro valor para 'Nova Localização' antes de selecionar 'Importar'Procurando por um ponto livre ({number})MemóriasMemórias devem ser contíguasIntervalo de MemóriaMemória {number}ModoModeloMover Abaix_oMover _AcimaMover abaixoMover acimaMemórias {count} movidasMovendo memória de {old} para {new}Movendo {src} para {dst}Meu indicativoNomeNenhumNota:OffsetAbrir arquivo recente {name}Abrir config do estoqueAbrir configuração de estoque {name}OpçõesSobrescreverSobrescrever local {number}?Sobrescrever?ColarMemória colada {number} não é compatível com este rádio porque:PortaPowerPreparando lista de memória...Memória de escorvaRPT1CALLRPT2CALLRádioMemória raw {number}Indicativo da RepetidoraRelatório estatísticoEmissão de Relatórios desabilitadaRecuperando informações do bancoReversoSelecioneSelecionar ColunasConfigurando índice para memória {num}Configurando memória {number}Configurando nome no bancoShiftMostrar VaziasMostrar Memória RawMostrar memória rawSkipCanais EspeciaisO arquivo {name} já existe. Você quer sobrescrevê-loO recurso de relatório do CHIRP é projetado para ajudar melhorar a qualidade permitindo aos autores focalizar os drivers de rádio mais frequentemente usados e erros experimentados pelos usuários. Os relatórios não contêm nenhuma informação de identificação e são utilizados apenas para fins estatísticos pelos autores. Sua privacidade é extremamente importante, mas por favor considere deixar este recurso ativado para ajudar a melhorar o CHIRP! Você tem certeza de que quer desabilitar este recurso?O {vendor} {model} tem vários sub-dispositivos independentesO {vendor} {model} opera em modo ativo. Isto significa que quaisquer alterações que você fizer são imediatamente enviadas para o rádio. Devido a isto você não pode executar as operações Salvar ou Carregar. Se você quiser editar o conteúdo off-line, por favor Exportar para um arquivo CSV, usando o menu Arquivo.Existiu um erro durante exportação: {error}Ocorreu um erro durante importação: {error}Houve um erro ao abrir {fname}: {error}Houve erros ao abrir {file}. As memórias afetadas não serão importáveis!Esta operação requer mover todos os canais subsequentes por um ponto, até que seja preenchido um local vazio. Isto pode levar MUITO tempo. Tem certeza de que quer fazer isto?ParaTomModo TomTomSqlTune StepURCALLIncapaz de detectar rádio na {port}Incapaz de efetuar alterações para este modeloTipo de arquivo não-suportadoSem-TítuloAtualizando lista RPTCALLAtualizando lista URCALLAtualizando índice do banco para memória {num}Atualizando informação do banco para memória {num}Carregar para o RádioVX7 CommanderArquivos VX7 CommanderFornecedorColunas Visíveis para {radio}Com importantes contribuições de:Gravando memória {number}Você só pode diff duas memórias!Seu indicativo_Copiar_Cut_Apagar_Editar_Arquivo_Colar_Rádio_Recente_Visualizarocioso{num} erros durante abertura:Arquivo Imagem {vendor} {model}{vendor} {model} na {port}chirp-daily-20170714/locale/hu/0000755000016101777760000000000013132065774017246 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/hu/LC_MESSAGES/0000755000016101777760000000000013132065774021033 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/hu/LC_MESSAGES/CHIRP.mo0000644000016101777760000004743013132065774022245 0ustar jenkinsnogroup00000000000000Þ•ÿ[ x6y°Í%ã  48NSm‹ˆ 0<Na jt} „Ž©¹%ÊŠð{–¥»ÃË ÓÝð õ  ' .8 AN U`h oy‹Ÿ ± »È ãñ+ò4H`y‘˜œ ¬¶»Î!å"*?WqŒ§¼ÎÕ ä.ð ).E\t ‰ªÆ+æ -a7I™ãêû!,Ni}ƒ”¥'´&Ü+ / I ^ "{ ž ¦ ¸ Ê Ö %Ú -!+.!nZ!"É!ì!õ! " ("6"P"`"e" k"v" "‰"‘"!¨"Ê" à"ì"ñ" ####4#F#f# n#x# •# #A¦#è#í#ó# $!$=$F$U$ g$t$|$…$ Ž$›$¡$´$Í$á$ ó$%%-%?%U%o% w%…%Œ%›%º%Ò%å%î% ô%ÿ%&&$&5&<F&̓&9Q(5‹()Á))ë)+*UA*¥—*<=+z+}+ ‚+Œ+”+ ¦+°+ ·+$Ø+bý+`,v,,•,-ª,&Ø,ÿ, -- 1-?- S-a-u-|-$˜-½-Õ- õ-. ....".).0.8.>.C.T. c.o.~.’.¬.È.ƒã.6g0$ž0$Ã0&è0$141G1L1[1`1z1ž–1 52A2 V2d2w2 Ž2 ˜2 ¤2 ®2 ¼2É2â2ô28 3¬D3ñ34 &4 G4R4 [4g4v4 ’4 œ4 ª4 ¸4"Â4å4 ì4 ö4 5 5 5 5 +5 85!D5f5†5 ¢5¬5¼5×5æ597<7S7l7‡7¤7À7Ç7Ë7Û7 â7ð784"8%W8 }8"‹8!®8!Ð8%ò8979S9g9n9}9.“9 Â9Í9Õ9!ò9#:8:"V:&y:$ :.Å:ô:÷: þ: ;x+;L¤; ñ;þ;<+2<0^< <°<Ê<Ó<ï< =&=*<=4g=œ=µ=Ó=0î= >*>>>R>d>!i>#‹>0¯>}à>#^?‚?‹?)¢?Ì? á?@@@$@ -@7@I@\@$y@ž@·@Ê@ Ï@ ð@ ú@A%A$5A0ZA‹A šA"§A ÊA ×ATäA9B>B!MBoB"„B §B²BÁB×BéBñBúB CCC+CKCaC {CˆC¢CÁCÔCèC DD 'D5D-MD!{DD µDÂD ËDÖDìDE EE8,EBeE;¨GhäG+MI*yI?¤IbäI“GJ1ÛJ KKK)K1K EKQK XK'yK{¡KL 9LCL^L1yL+«L×L ïLýL M!M 7MEM[MdM„M¡M)ÁMëM ýM N N N+N 2N@N INUN ]NhNyN ‹N™N¨N¼NÜN ÷NFt£¾kЍ®„4\¤êÇBƒù@•ÊNlo Cþ_܆á3í(UÎ$Þ?͈¡ÑËÝJÁ¬½ŸöׯÚ Ö»D)±ÓOär>òdñeµÅmë¼’åÐ/« ü³XÙ“xi™,Q- E;¹+îà‡ŽØ÷|'*›ûzn!ª=ÆA—~c:SÉ5‰f}é·ÔZb`]èVuI°ï1ž%²¿y€0"¶Ûœß{ÿGðH^Y 9§ì¦Kç6¢Ïý[&RÕæ‘©úaºŒâW­–¸ÃÄÒ ¥hwã´šóÀTjp#<P˜vÌ‹M‚s.È7…82”qL õøgô%i errors during open, check the debug log for details...and shift all memories up...and shift block upA new version of CHIRP is available: Adding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}Band plans define default channel settings for frequencies in a region. Choose a band plan or None for completely manual channel settings.BrowserCHIRP DocumentationCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChannel defaultsCheck this to change the {name} valueChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCross modeCutCutting memory {number}D-STARDTCS CodeDTCS PolDTCS Rx CodeDeleteDelete allDetailsDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDo not show this next timeDocumentationDocumentation for CHIRP, including FAQs, and help for common problems is available on the CHIRP web site, please go to http://chirp.danplanet.com/projects/chirp/wiki/Documentation Don't show instructions for any radio againDon't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexEVEEVE Files (VX5)E_xchangeEditEdit Memory #{num}Edit Multiple MemoriesEditing new item, taking defaultsEnable Developer FunctionsEnabledErasing memory {loc}Erasing memory {number}Error importing memories:Error in setting value: %sError reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting %s informationGetting channel {chan}Getting memory {number}Getting memory {num}Getting original memory {number}Getting raw memory {number}Getting {type} for memory {num}Getting {type} information for memory {num}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from data sourceImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid setting value: %sInvalid value for %sInvalid value for this fieldInvalid value. Must be an integer.InverseKenwood HMK FilesKenwood ITM FilesLoad ModuleLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories (%(variant)s)Memories must be contiguousMemory range:Memory validation failed:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNo space to insert a rowNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryProceed with experimental driver?Proceed?Python ModulesQuery data sourceQuery failedRFinderRPT1CALLRPT2CALLRX DTCS CodeRadioRadioReference.comRadioReference.com QueryRaw memory {number}Repeater callsignRepeaterBookRepeaterBook QueryRepeaterBook query failedReport statisticsReporting is disabledRetrieving %s informationReverseSave Changes?SelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on %sSettingsShiftShow EmptyShow Raw MemoryShow raw memorySkipSmart Tone ModesSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?This radio's driver is experimental. Do you want to proceed?ToToneTone ModeToneSqlTravel Plus FilesTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnable to paste {src} memories into {dst} rows. Increase the memory bounds or show empty memories.Unsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating mapping information for memory {num}Updating {type} index for memory {num}Upload To RadioVX5 CommanderVX5 Commander FilesVX6 CommanderVX6 Commander FilesVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions from:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidleprzemienniki.netthese memoriesthis memory{instructions}{name} Instructions{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2014-10-08 11:40-0700 PO-Revision-Date: 2015-01-28 13:47+0100 Last-Translator: Attila Joubert Language-Team: English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.7.4 Language: hu_HU %i errors during open, check the debug log for details...és minden memória felfelé lép... és a tömböt felfelé léptetiA CHIRP egy új verziója érhetÅ‘ el:A(z) {number}. memória hozzáadásaÚj hely megadásaMindHiba történtAutoAutomatic Repeater OffsetHibás érték {col}: {val}Egy régió sávtervének alapértelmezett csatorna frekvenciái. Válassz egy sávtervet, vagy ki is hagyhatod a kézi csatorna beállítás befejezéséhez.BöngészÅ‘CHIRP dokumentációCHIRP fájlokCHIRP natív fájlCHIRP Radio adatképekCSV fájlCSV fájlokHívójelMegszakításMekszakítvaNem importálható, mertNyelv választásCsatorna alapértékekEnnek változtatásához ellenÅ‘rizd a {name} értékétVálasszon egy nyelvet, vagy bízza az automatikus nyelvfelismerést az Operációs rendszerbÅ‘l. Ahhoz, hogy a beállítás érvényre jusson, indítsa újra az CHIRP-et! Válasszon egy importálandót:Klónozási folyamatA klónozás sikertelen: {error}KlónozásOszlopokMegjegyzésBefejezÅ‘döttFelülírás jóváhagyásaMásolásKereszt-üzemKereszt-üzemKivágásA(z) {number}. memória kivágásaD-STARDTCS kódDTCS pol.DTCS RX kódTörlés_TörlésRészletekÉrzékelésFejlesztÅ‘iMemóriasorok összehasonlítása{a} és {b} összehasonlításaMemóriasorok különbségeDiff tabsDigitális kódMégegyszer ne mutassa eztDokumentációA leggyakoribb problémákhoz, GYIK-et is magába foglaló CHIRP dokumentáció és súgó a CHIRP honlapján érhetÅ‘ el, kérlek látogass oda! http://chirp.danplanet.com/projects/chirp/wiki/Documentation Mégegyszer ne mutasson utasításokat egy rádióhoz semMégegyszer ne mutassaLetöltés a rádiórólA MYCALL lista letöltéseAz RPTCALL lista letöltéseAz URCALL lista letöltéseDuplexEVEEVE fájl (VX5)Cse_reSz_erkesztésA(z) #{num} memóriaTöbb memória szerkesztéseÚj elem szerkesztése az alapértelmezettek szerintFejlesztÅ‘i funkciók engedélyezéseEngedélyezveA(z) {loc}. memóriahely törléseA(z) {number}. memória törléseHiba a memória importálásakor:Hiba a(z) %s érték beállításakorA hibajelentés engedélyezettMemória beállítási hibaMemóriák cseréjeExportExport fájlbaA fájl már létezikA fájl megváltozott! Menti bezárás elÅ‘tt?FrekvenciaForrás%s információk beolvasásaA(z) {chan}. csatorna kiolvasásaA(z) {number}. memória beolvasásaA {num}. memória beolvasásaA(z) eredeti {number}. memóriasorA(z) {number}. memóriasor kiolvasásaA {num}. memória {type} beolvasásaA {num}. memória {type} adatainak beolvasásaOkSúgóA nemhasznált mezÅ‘k elrejtéseICF fájlokAz ICF fájlok nem szerkeszthetÅ‘k, csak megnézhetÅ‘k, vagy másik fájlba importálhatók. Megnyissam olvashatóként?Ha Ön ezt a lehetÅ‘séget le kívánja tiltani, a Súgóban megtehetiImportálásImportálás fájlbólImportálás adatforrásbólImportálás a csoportos beállításokbólA(z) {name} konfigurációs készlet beolvasásaBank információk importálásaNem kompatibilis memóriaSorszámMemória beszúrása föléMemória beszúrása aláBelsÅ‘ hibaBelsÅ‘ hiba: nincs {name} nevű oszlopBelsÅ‘ hiba: Érvénytelen határ {number}BelsÅ‘ hiba: nem tölthetÅ‘ fel a {model} rádióra.Érvénytelen érték %sÉrvénytelen %s mezőértékÉrvénytelen mezőértékÉrvénytelen érték! Egész szám kell legyen.EllenkezÅ‘Kenwood HMK fájlokKenwood ITM fájlokModul betöltéseHelyMemória helye lesz beimportálvaMemória hely a fájlba importálvaA {number} sorszámú hely már importálva van.A(z) {number}. hely már importálva van. 'Új hely'-nek válasszon másik értéket, mielÅ‘tt az 'importálást' választja!Üres terület keresése ({number})MemóriaMemória (%(variant)s)EgybefüggÅ‘ memóriaterület szükségesMemória tartomány:Memória érvényesítési hiba:A(z){number} memóriaMódModellLefel_éFe_lfeléMozgatás lefeléMozgatás felfelé{count} memória átmozgatvaMemória átmozgatás {old} -> {new}Mozgatás {src} -> {dst}Az én hívójelemNévNincs hely a sor beszúrásáhozEgyik semMegjegyzés:OffszetA legutóbbi {name} fájl megnyitásaA csoportos beállítás megnyitásaA(z) {name} konfigurációs készlet megnyitásaBeállításokFelülírásFelülírja a(z) {number}. helyet?Felülírja?BeillesztésA beillesztett {number}. számú memória nem kompatibilis ezzel a rádióval, mert:PortTeljesítményMemória lista elÅ‘készítés...Memória feltöltésFolytassam kísérleti driver-rel?Folytatod?Python modulokLekérd. adatforrásaLekérdezés hibaRFinderRPT1CALLRPT2CALLRX DTCS kódRádióRadioReference.comRadioReference.com lekérdezés{number}. memóriasorAz átjátszó hívójeleRepeaterBookRepeaterBook lekérdezésRepeaterBook lekérdezés hibaStatisztikai listaListázás letiltva%s információk lekéréseFordítottMenti a változásokat?KiválasztásOszlopok kiválasztásaA {num}. memória sorszámának beállításaA {number} memória beállításaA %s név beállításaBeállításEltolásÜreset isMemóriasor mutatásamemóriasor mutatásaUgrásHang (CTCSS) módSpec. csatornákA {name} nevű fájl már létezik. Felül akarja írni?A CHIRP jelentéskészítÅ‘je a minÅ‘ség növelésére került beépítése lehetÅ‘vé téve a program fejlesztÅ‘inek, hogy a leggyakoribb rádió driver-ekre és a felhasználók által tapasztalt hibákra figyeljenek. A jelentések semmiféle személyes információt nem tartalmaznak és a programozók által, kizárólag statisztikai céllal kerülnek felhasználásra. Az Ön adatainak védelme rendkívül fontos, mi mégis kérjük, engedélyezze a jelentések készítését, hogy a CHIRP egyre jobb legyen! Biztosan letiltja ezt a lehetÅ‘séget?A {vendor} {model} több független altípussal rendelkezikA {vendor} {model} közvetlen kapcsolatban működik. Ez azt jelenti, hogy minden elvégzett módosítás azonnal a rádióra töltÅ‘dik. Emiatt Ön nem választhatja a Mentés vagy Feltöltés műveleteket. Ha Ön kapcsolat nélkül akarja a tartalmat szerkeszteni, kérem válassza az CSV-be exportálást a Fájl menüben!Hiba a(z) {fname} exportálásakor: {error}Hiba történt az importáláskor: {error}A(z) {fname} nevű fájl megnyitásakor fellépÅ‘ hiba: {error}Hiba történt a {file} fájl megnyitásakor. Az érintett memóriák nem lesznek importálhatók.Ez a művelet megköveteli, hogy minden csatorna egyetlen összefüggÅ‘, üres területre kerüljön. Ez sokáig eltarthat. Biztos, hogy elkezdjem?A rádió driver-e kísérleti. Folytatni akarod?-igCTCSSHang (CTCSS) módToneSqlTravel Plus fájlokLépésközURCALLNem érzékelek a {port} porton!Ennél a modellnél nem változtathatóNem sikerült beilleszteni {src} memóriát {dst} sorokba. Növelje a memória határokat, vagy mutasson üres memóriára!Nem támogatott fájltípusNévtelenRPTCALL lista frissítéseURCALL lista frissítése.A {num}. memória információjának frissítéseA {num}. memória {type} index frissítéseFeltöltés a rádióraVX5 CommanderVX5 Commander fájlokVX6 CommanderVX6 Commander fájlokVX7 CommanderVX7 Commander fájlokGyártóA(z) {radio} látható oszlopaiJelentÅ‘sen hozzájárultak:A(z) {number}. memória írásaCsak két memória hasonlítható össze!Az Ön hívójele_Másolás_Kivágás_TörlésSz_erkesztés_Fájl_BeillesztésRá_dióLeg_utóbbiNé_zetvárakozikprzemienniki.netezek a memóriákez a memória{instructions}{name} Utasítások{num} hiba a megnyitás során:{vendor} {model} képfájl{vendor} {model} a {port} portonchirp-daily-20170714/locale/fr/0000755000016101777760000000000013132065774017241 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/fr/LC_MESSAGES/0000755000016101777760000000000013132065774021026 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/fr/LC_MESSAGES/CHIRP.mo0000644000016101777760000003655413132065774022245 0ustar jenkinsnogroup00000000000000Þ•Êl ¼ ðñ 6;Up u€ †’¤· ÀÊÓ ÚäÿŠšµÄÚâê òü #; BLU\ r} „Ž ´ Æ ÐÝî0Ia h!r”¯ÄÜö&8? N.Z ‰“˜¶)Ïù(=Y\a ta~Ià*1BVo!ˆªÅÙßð'&8+_‹"¨ËÓ%×-ý++nW"Æéò ,1 7B KU]!t– ¬¸½ÂÈÏçù !+ HSAY› ¦¿Î×àæú 4PX_n¥º ÀËÛëð<Í>9 5F)| )¦ +Ð Uü ¥R!ø!û! " " "" #"$D"i""ˆ"ž"$³"*Ø"# #!#5#<#"X#{#“# ³#Á#Ç#Ì#Ô#Ú#à#ç#î#ö#ü#$$7$}R$Ð%ì%& &&&"+&"N&q&x&ˆ&&Ÿ&³& É& Õ& â&ì&ô&û&'ˆ*'"³'Ö'ç'( ( (('(>( E(P(W(r( y(ƒ( Œ(–(®(½( Æ(Ò(î( )') :)G)a)"q)!”)"¶)!Ù)û) */ *&<*c*|*+˜*Ä*ã*û*++3+LE+ ’+œ+*Ÿ+Ê+8è+!,6,O,e,„,Š,, ¬,}¹,=7-u-~-™-­-Æ-"å-&./.D.J.b.z.+‰.)µ.7ß./%5/[/c/'g/&/)¶/|à/+]0‰0#’0¶0È0Ù0Þ0 å0ð0 ø01 1'&1N1 h1v1z1€1‡11°1 Ç1è1ð1 ø1 2#2F*2q2 v2'€2¨2»2Ä2Í2Ó2ê2þ23&53\3 d3q3&‹3²3Í3ç3 ð3'þ3&4B4J46Z4E‘4@×678©88â8<9NX9Á§9i:n: s:}:…:‰:*:5»:ñ: ;;9;6X;<;"Ì; ï;ý; <<*=<h<+ƒ<¯<¿<Ç< Ï<Ú<â<ë<ó<ú<==" =0=P=´®]½žÊ°¸Pe‘—Æs?‡¶«ÅÉ~!uÀ$¡5<pz"k/ Ä@Un_ÁO3NÇb`M¦…“>a”ŒJ•QEi*¢Ÿ+ˆo[𹂳¼^Š;W„™–¤›VS1YX¯r F}-(cKB¨tG©¾± ‰8ƒ¥‹ÃD v6ZH9A%mµ0˜.Cf)# €£§=|7qx{¬Lº\Žd¿2,ÈyªT’&h»œ4wIg †·' :­R²ljAdding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCutCutting memory {number}D-STARDTCS CodeDTCS PolDeleteDelete (and shift up)Delete allDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from RFinderImport from RepeaterBookImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRadioRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2014-04-05 22:18+0100 Last-Translator: Matthieu Lapadu-Hargues Language-Team: French Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=ASCII Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 1.5.4 Ajouter la memoire {number}Ajuster nouvel emplacementToutUne erreur s'est produiteAutoDecalage automatique de relais ARSValeur invalide pour {col} : {val}BanqueNom des banquesBanquesFichiers CHIRPFichier CHIRP natifImages de radio CHIRPFichier CSVFichiers CSVIndicatifAnnulerAnnuleNe peut pas etre importe carChanger la langueChoisir une langue ou Auto pour utiliser celle du systeme par defaut. Vous devrez redemarrer l'application pour appliquer le changement.Selectionner importation depuis : Clonage en coursErreur de clonage: {error}ClonageColonnesCommentaireTermineConfirmer l'ecrasementCopierCross modeCouperCouper la memoire {number}D-STARCode DTCSDTCS PolSupprimerSupprimer (et remonter)Tout supprimerDetecterDeveloppeurComparaison memoires brutesComparaison entre {a} et {b}Comparaison memoires brutesComparaison listesDigital codeAnnuler les changements ?Ne plus montrerTelecharger depuis la radio - LireTelechargement de la liste MYCALLTelechargement de la liste RPTCALLTelechargement de la liste URCALLDuplex_PermuterModification nouvel element, valeurs par defautActiver les fonctions de developpementEffacer la memoire {loc}Effacer la memoire {number}Erreur lors de l'importation des memoires :Le rapport d'erreur est activeErreur ecriture memoirePermuter les memoiresExporterExporter vers un fichierLe fichier existeLe fichier a ete modifie, enregistrer les modifications avant de le fermer ?FrequenceDeLecture de la banque pour la memoire {num}Lecture information de banqueLecture de l'information de banque pour la memoire {num}Lecture canal {chan}Lecture memoire {number}Lecture memoire {num}Lecture memoire brute {number}AllerAideCacher les champs inutilisesFichiers ICFLes fichiers ICF ne peuvent pas etre edites, uniquement affiches ou importes dans un autre fichier. Ouvrir en lecture seule ?Pour desactiver cette fonction, utilisez le menu Aide.ImporterImporter depuis un fichierImporter de RFinderImporter de RepeaterBookImporter de la base de donneesImporter la base de donnees {name}Importation de l'information de banqueMemoire incompatibleIndexInserer une ligne avantInserer une ligne apresErreur interneErreur interne : colonne {name} non trouveeErreur interne : {number} limite invalideErreur interne : impossible de telecharger vers {model}Valeur invalide pour ce champValeur invalide. Doit etre un entier.InverseMemL'emplacement memoire sera importe dansEmplacement memoire du fichier importeL'emplacement {number} a deja ete importeL'emplacement {number} a deja ete importe. Choisissez une autre valeur pour 'nouvel emplacement' avant d'utiliser 'Importer'Recherche d'un emplacement libre ({number})MemoiresLes memoires doivent etre contiguesEtendue memoire :Memoire {number}ModeModeleDesce_ndre_MonterDescendreRemonter{count} memoires deplaceesDeplacer la memoire de {old} vers {new}Deplacer {src} vers {dst}Mon indicatifNomAucunNote :DecalageOuvrir le fichier recent {name}Ouvrir base de donneesOuvrir la base de donnees {name}OptionsEcraserEcraser l'emplacement {number} ?Ecraser ?CollerLa memoire collee {number} n'est pas compatible avec cette radio car :PortPuissancePreparation de la liste des memoires...Memoire d'amorcageRPT1CALLRPT2CALLRadioMemoire brute {number}Indicatif du relaisRapporter statistiquesRapport d'utilisation desactiveRecuperation des information de banqueReverseSelectionnerSelectionner les colonnesAssigner son numero a la memoire {num}Regler la memoire {number}Donner un nom a la banqueDecalageMontrer videsAfficher les donnees de memoires brutesMontrer les memoires brutesIgnorerCanaux speciauxLe fichier {name} existe deja. Voulez-vous l'ecraser ?La fonction de rapport de CHIRP est concue pour ameliorer l'application en permettant aux auteurs de se concentrer sur les pilotes des radios les plus frequemment utilisees et les errreurs rencontrees par les utilisateurs. Les rapports ne contiennent aucune donnee d'identification et ne sont utilisee qu'a des fins statistiques par les auteurs. Votre vie privee est extremement importante, mais considerez s'il vous plait que laisser cette fonction activee contribuera a rendre CHIRP encore meilleur ! Etes-vous certain de vouloir desactiver cette fonction ?Le {vendor} {model} a des sous-series multiples et independantesLe {vendor} {model} fonctionne en live mode. Cela signifie que tous les changements que vous faites sont immediatement envoyes a la radio. Pour cette raison, vous ne pouvez pas utiliser les fonctions Enregistrer ou Telecharger vers la radio. Si vous souhaitez editer le contenu hors connexion, vous pouvez Exporter vers un fichier CSV, en utilisant le menu Fichier.Une erreur s'est produite durant l'exportation : {error}Une erreur s'est produite durant l'importation : {error}Une erreur s'est produite a l'ouverture de {fname} : {error}Erreurs a l'ouverture de {file}. Les memoires ne peuvent pas etre importees ! Cette operation necessite le deplacement de tous les canaux suivants jusqu'a ce qu'un emplacement libre soit trouve. Cela peut prendre BEAUCOUP de temps. Etes-vous certain de vouloir le faire ?VersToneTone ModeToneSqlPasURCALLImpossible de detecter la radio sur {port}Impossible de realiser des changements pour ce modeleType de fichier non-supporteSans titreMise a jour de la liste RPTCALLMise a jour de la liste URCALLMise a jour de l'index de banque pour la memoire {num}Mise a jour des informations de banque pour la memoire {num}Telecharger vers la radio - EcrireVX7 CommanderFichiers VX7 CommanderFabricantColonnes visibles pour {radio}Avec les contributions significatives de :Ecrire la memoire {number}Vous ne pouvez comparer que deux memoires !Votre indicatifC_opier_Couper_Supprimer_Editer_FichierCo_ller_Radio_Recent_VoirPret{num} erreurs durant l'ouverture :Fichier images {vendor} {model}{vendor} {model} sur {port}chirp-daily-20170714/locale/de/0000755000016101777760000000000013132065774017222 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/de/LC_MESSAGES/0000755000016101777760000000000013132065774021007 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/de/LC_MESSAGES/CHIRP.mo0000644000016101777760000004151213132065774022214 0ustar jenkinsnogroup00000000000000Þ•æL3|H%Io†šž´¹Óî óþ "5 >HQ Xb}Š3BX`h pz ’ ¨¬Ä ËÕ Þëò  ",>R d n{–¬ÀØñ  $.3!Eg‚ŠŸ·Ñì ).5 dns‘)ªÔë47< OaYI» 5!Np‹Ÿ¥¶Ç'Ö&þ+%Q"n‘™ «·%»-á+n;"ªÍÖ ò*/ 5@ IS[!r” ª¶»ÔÙßæþ0 8B _jAp²·½Ö!å19BKQdx Š—©¿Û ãñø & > S \ b m }  ’ <£ Íà 9®"5è")$)H$+r$Už$¥ô$<š%×%Ú% ß%é%ñ% & & &$5&bZ&½&Ó&Ü&ò&$'*,'W' g'u' ‰'—' «'¹'Í'Ô'"ð'(+( K(Y(_(d(l(r(x((†(Ž(”(™(³(Ï(–ê(+*!­*Ï*æ*ë*+ +)+I+ N+Y+ ^+j+|+ + ™+ £+ ®+ ¸+!Ä+æ+Ÿö+–,§,º,Ù,à, è,ò,ú,- - )- 4-A-`- g-q- z-‡-$-µ-Ä- Ì- Ö-á-ø- .$. ?.L.b.x.Œ.¥.¿.Ø.ß.ã. ó. þ. //!/ Q/ r/|/“/²/"Í/ð/0 %010F0HV0Ÿ0¨0¬0Ê0(ß01111E1_1b1h1 „1|Ž1Z 2 f2r2ˆ2£2"º2Ý2ü23313G33W3.‹31º3!ì3,4;4D4 V4c4%g4'4*µ4yà4)Z5„5!5¯5$À5å5÷5ü5 6 6 6 $6.6(K6t6‘6¡6$¦6Ë6Ñ6Ú6á6ý67.777F7f7 v7K€7Ì7Ñ7Ú7ø7'80888G8\8d8m8v8}88¥8 ¹8Æ8×8î8 99,949G9g99 ”9 ¢9®9½9Ð9 ã9ñ9?:A:9I<tƒ<(ø=(!>1J>T|>®Ñ>@€?Á?Æ? Ë?Õ?Ý?ï?@% @,/@l\@É@ç@ð@A+A0IAzA ŒAšA ®A¼A ÐAÞA òAýABWʤ=ØSu˜ž&Öš¥ÐàTµ0q3™´ˆÛ×Ê‚¬4k ~‡' A new version of CHIRP is available: Adding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCross modeCutCutting memory {number}D-STARDTCS CodeDTCS PolDTCS Rx CodeDeleteDelete (and shift up)Delete allDetailsDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDo not show this next timeDon't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexEVEEVE Files (VX5)E_xchangeEditEdit Memory#{num}Editing new item, taking defaultsEnable Developer FunctionsEnabledErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from data sourceImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseKenwood HMK FilesLoad ModuleLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another valü for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory validation failed:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNo space to insert a rowNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryProceed with experimental driver?Proceed?Python ModulesQuery data sourceRFinderRPT1CALLRPT2CALLRadioRadioReference.comRaw memory {number}Repeater callsignRepeaterBookReport statisticsReporting is disabledRetrieving bank informationReverseSave Changes?SelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankSettingsShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?This radio's driver is experimental. Do you want to proceed?ToToneTone ModeToneSqlTravel Plus FilesTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnable to paste {src} memories into {dst} rows. Increase the memory bounds or show empty memories.Unsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX5 CommanderVX5 Commander FilesVX6 CommanderVX6 Commander FilesVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-10-02 00:01-0700 PO-Revision-Date: 2012-10-02 22:11+0100 Last-Translator: Benjamin, HB9EUK Language-Team: German MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language: de Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.3 X-Poedit-SourceCharset: UTF-8 Eine neue Version von CHIRP ist verfügbar:Hinzufügen von Speicher {number}Neuen Standort wählenAlleEin Fehler ist aufgetretenAutoAutomatischer Repeater-OffsetFalscher Wert für {col}: {val}BankBank NamenBankCHIRP DateiCHIRP Native FileCHIRP Radio ImagesCSV DateiCSV DateiRufzeichenAbbrechenAbgebrochenKann nicht Importiert werden weilSprache wählenWählen Sie eine Sprache oder Auto, um die Defaultsprache vom Betriebssystem zu verwenden. Bevor die Änderungen wirksam werden, müssen Sie Chirp neu starten.Importieren von:Klonen FortschrittKlonen fehlgeschlagen: {error}KlonenSpaltenKommentarBeendetÜberschreiben bestätigenKopierenCross ModeCross ModeAusschneidenAusschneiden Speicher {number}D-StarDTCS CodeDTCS PolDTCS Rx CodeLöschenLöschen (und nach oben verschieben)Alles löschenDetailsErkennungEntwicklerVergleiche RohspeicherDiff of {a} and {b}Vergleiche Roh SpeicherRegisterkarten vergleichenDigital CodeNicht wieder anzeigenNicht wieder anzeigenDownload vom GerätDownloading MYCALL ListeDownloading RPTCALL ListeDownloading URCALL ListeDuplexEVEEVE Files (VX5)A_ustauschBearbeitenSpeiche editieren#{num}Neues Element editieren, verwende StandardwerteEntwickler Funktionen aktivierenAktiviertLösche Speicher {loc}Löschen von Speicher {number}Fehler beim SpeicherimportFehler Reporting ist eingeschaltetFehler beim Setzen der SpeicherSpeicher austauschenExportierenIn Datei exportierenDatei existiertDatei wurde geändert, speichern Sie die Änderungen vor dem Schliessen?FrequenzVonLade Bank für Speicher {num}Lade BankinformationLade Bankinformation für Speicher {num}Lade Kanal {chan}Lade Speicher {number}Lade Speicher {num}Lade Rohspeicher {number}GoHilfeUnbenutzte Felder verbergenICF DateiICF-Dateien können nicht bearbeitet werden, nur angezeigt oder importiert in eine andere Datei. Öffnen im Read-Only-Modus?Wenn Sie diese Funktion deaktivieren möchten, können Sie das im Hilfe Menu machenImportierenImportieren von DateiImport aus der DatenquelleImport von der VorgabeSpeichervorgabe importieren {name}Importieren von SpeicherbänkeSpeicher nicht kompatibelIndexZeile oben einfügenZeile unten einfügenInterner FehlerInterner Fehler: Spalte {name} wurde nicht gefundenInterner Fehler: Ungültige Grenzwert {number}Interner Fehler: Upload zu {model} nicht möglichUngültiger Wert für dieses FeldUngültiger Wert. Muss eine ganze Zahl sein.UmkehrenKenwood HMK DateiModule ladenLocSpeicherstandort wird importiert nachSpeicherposition der importierten DateiStandort {number} wurde bereits importiertStandort {number} wurde bereits importiert. Wählen Sie einen anderen Wert für 'Neuen Standort' vor der Auswahl 'Import'Suche nach einer freien Stelle ({number})SpeicherSpeicher müssen fortlaufend seinSpeicherbereich:Speicher-Validierung fehlgeschlagen:Speicher {number}ModeModelNach _UntenNach _ObenNach UntenNach ObenVerschobene {count} SpeicherVerschiebe Speicher von {old} nach {new}Verschieben {src} nach {dst}Mein RufzeichenNameKein Platz zum Einfügen einer ZeileKeineHinweis:OffsetLetzte Datei öffnen {name}Speichervorgaben öffnenVorgaben öffnen {name}OptionenÜberschreibenÜberschreibe Standort {number}Überschreiben?EinfügenEingefügter Speicher {number} ist nicht mit diesem Gerät kompatibel weil:PortLeistungSpeicher Liste vorbereiten...PrimärspeicherWeiter mit dem experimentellen Treiber?Weiter?Python ModulesDatenquelle abfragenRFinderRPT1CALLRPT2CALLGerätRadioReference.comRohspeicher {number}Repeater RufzeichenRepeaterBookReport StatistikReport ist deaktiviertAbrufen der SpeicherbankZurücksetzenÄnderungen speichern?WählenSpalten auswählenSetze Index für Speicher {num}Setze Speicher {number}Setze Name für BankEinstellungenverschiebenLeere anzeigenZeige RAW SpeicherZeige Roh SpeicherÜberspringenSpezial KanäleDatei {name} existiert bereit, wollen Sie diese überschreiben?Die Reporting-Funktion von CHIRP soll helfen, die Qualität ständig zu verbessern, in dem sich die Autoren auf die Fehler der am häufigsten verwendeten Radio-Treiber konzentrieren können. Die Berichte enthalten keine identifizierbaren Informationen und werden zu rein statistischen Zwecken verwendet. Ihre Privatsphäre ist uns sehr wichtig, bitte lassen Sie diese Funktion aktiviert, so helfen Sie mit, CHIRP weiter zu verbessern! Sind Sie sicher, dass Sie diese Funktion deaktivieren wollen?Der {vendor} {model} hat mehrere unabhängige Sub-GeräteDie {vendor} {model} arbeitet im Live-Modus. Dies bedeutet, dass alle Änderungen sofort an das Radio gesendet werden. Aus diesem Grund können Sie die Funktionen Speichern oder hochladen nicht verwenden. Wenn Sie die Inhalte offline bearbeiten wollen, bitte mit Exportieren in eine CSV-Datei speichern, unter Datei Exportieren .Es gab einen Fehler beim Export: {error}Es gab einen Fehler beim Import: {error}Es gab einen Fehler beim Öffnen {fname}: {error}Es gab Fehler beim Öffnen {file}. Die betroffenen Speicher werden nicht importiert!Dieser Vorgang erfordert eine Verschiebung aller nachfolgenden Kanäle, bis eine leere Stelle gefunden wird. Dies kann sehr lange dauern. Sind Sie sicher dass Sie das wollen?Dieser Geräte Treiber ist experimentell. Wollen Sie fortfahren?NachToneTone ModeToneSqlTravel Plus DateiAbstimmungsschrittURCALLKann Gerät auf {port} nicht erkennenKeine Änderungen bei diesem Modell möglichKann {src} Speicher in {dst} Zeilen einfügen. Erhöhen Sie die Speicher-Grenzen oder zeigen leere Speicher.Nicht unterstuetzter DateitypNamenlosUpdating RPTCALL ListeUpdating URCALL ListeAktualisiere Bank Index für Speicher {num}Aktualisiere Bankinformation für Speicher {num}Upload zum GerätVX5 CommanderVX5 Commander FilesVX6 CommanderVX6 Commander FilesVX7 CommanderVX7 Commander FilesHerstellerSichtbare Spalten für {radio}Mit bedeutenden Beiträgen von:Schreibe Speicher {number}Sie können nur zwei Speicher vergleichen!Ihr Rufzeichen_Kopieren_Ausschneiden_Löschen_Bearbeiten_Datei_Einfügen_Gerät_Aktuell_AnsichtRuhezustand{num} Fehler beim Öffnen:{vendor} {model} Imagedatei{vendor} {model} auf {port}chirp-daily-20170714/locale/en_US/0000755000016101777760000000000013132065774017643 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/en_US/LC_MESSAGES/0000755000016101777760000000000013132065774021430 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/en_US/LC_MESSAGES/CHIRP.mo0000644000016101777760000001130213132065774022627 0ustar jenkinsnogroup00000000000000Þ•3´GLhi ƒ¡´ ½Ç ÏÙëü &0Kf.mœ¡ ´a¾I jq…ž§­Å×íüÍ 5Ú+ < E U c w “ ™ ž ¦ ¬ ² ¹ À È Î Vê A [ g y Œ • Ÿ § ± à Ô ê þ  # > .E t y Œ a– Iø BI]v…¯ÅÔÍä5²+è -;Okqv~„Š‘˜ ¦(+%!  ')#&* -,2 3.$  /01"Automatic Repeater OffsetCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesColumnsDeveloperDiff raw memoriesDiscard Changes?Don't show this againDownload From RadioE_xchangeEnable Developer FunctionsError reporting is enabledExportFile is modified, save changes before closing?HelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport from RFinderImport from RepeaterBookMove _UpNote:Open recent file {name}Report statisticsReporting is disabledSelect ColumnsShow raw memoryThe 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! Are you sure you want to disable this feature?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.There was an error opening {fname}: {error}UntitledUpload To RadioVX7 CommanderVX7 Commander FilesVisible columns for {radio}_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_View{vendor} {model} image fileProject-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2011-11-29 16:07-0800 Last-Translator: Dan Smith Language-Team: English Language: en_US MIME-Version: 1.0 Content-Type: text/plain; charset=ASCII Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Automatic Repeater OffsetCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesColumnsDeveloperDiff raw memoriesDiscard Changes?Don't show this againDownload From RadioE_xchangeEnable Developer FunctionsError reporting is enabledExportFile is modified, save changes before closing?HelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport from RFinderImport from RepeaterBookMove _UpNote:Open recent file {name}Report statisticsReporting is disabledSelect ColumnsShow raw memoryThe 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! Are you sure you want to disable this feature?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.There was an error opening {fname}: {error}UntitledUpload To RadioVX7 CommanderVX7 Commander FilesVisible columns for {radio}_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_View{vendor} {model} image filechirp-daily-20170714/locale/pl/0000755000016101777760000000000013132065774017245 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/pl/LC_MESSAGES/0000755000016101777760000000000013132065774021032 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/pl/LC_MESSAGES/CHIRP.mo0000644000016101777760000002560213132065774022241 0ustar jenkinsnogroup00000000000000Þ•ŽŒ¿üø ù  $ ( > C ] x } ‰ › ® · Á Ê Ñ Û ö   # + 5 H S k r | … › ¦ ­ · Ë Ý î 1I P!Z|—¬Äßô . HRi ¥ ¸aÂI$nu‰¢¶ÇØ'ç&6"Sv~+‚n®"@ \jzˆ!ŸÁ ×ãèíóú 7AB„‰£µÇÝåô ú<+Íh965p+¦¥Òx }‡  ™$ºßõþ 07Sk ‹™Ÿ¤¬²¸¿ÆÎÔÙ¢õ˜´ ÐÚìñ/ 4@Sm v€‘ ˜¢½Ð íø,;W^gx¡ ¨²Æàò !:RZ6b™·Ð&ì. ?I9\–§Á$Þ   # z- L¨ õ þ !)!B!Y!p!1ƒ!0µ!æ!#þ! ""/"(3"B\"!Ÿ"&Á"è"ù" ###&7#^#x#Ž#”#œ# £#&±#Ø#Þ# û#M$U$Z$^$w$’$£$Á$Ê$ Ú$ å$ò$ %%)&%ñP%CB'_†'5æ(‚)Ÿ) £) ­)º).É),ø)%* ?*I* `*n* ƒ**ª*&È*ï*+++%+-+3+ :+ G+ Q+ [+e+\kZ:Œ‰5 ~[ow'0Rƒ< i2Tf"GŠd=YW#*‡4HrŽPu,yAB+e`X!‹];Q.Dhqˆ8Ls7M9@-?xE b…jS/V{C(c_KpO>6|3Uz}„ ) n†‚^€$%gta&lJvm1INF Adding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCompletedConfirm overwritesCross ModeCutting memory {number}D-STARDTCS CodeDTCS PolDelete (and shift up)Delete allDetectDeveloperDiff of {a} and {b}Diff raw memoriesDiscard Changes?Don't show this againDownload From RadioDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error reporting is enabledError setting memoryExchange memoriesExportFile ExistsFile is modified, save changes before closing?FrequencyGetting channel {chan}Getting memory {number}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport from RFinderImport from RepeaterBookIncompatible MemoryInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})Memories must be contiguousMemory range:Memory {number}ModeMove _UpMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}OptionsOverwrite location {number}?Overwrite?Pasted memory {number} is not compatible with this radio because:PortPowerRaw memory {number}Repeater callsignReport statisticsReporting is disabledReverseSelect ColumnsShiftShow EmptyShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error opening {fname}: {error}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?ToneTone ModeToneSqlTune StepUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{vendor} {model} image fileProject-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2011-12-07 11:35+0100 Last-Translator: Grzegorz BÅ‚oÅ„ski-Kubiak Language-Team: Polish Language: pl MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); Dodawanie pamiÄ™ci {number}Dostosuj nowÄ… lokalizacjÄ™WszystkieWystÄ…piÅ‚ błądAutoAutomatyczny offset repeateraZÅ‚a wartość dla {col}: {val}BankPliki CHIRPNatywny plik CHIRPZrzuty z urzÄ…dzeÅ„ CHIRPPlik CSVPliki CSVZnak wywoÅ‚awczyAnulujAnulowanoWybierz do zaimportowania:PostÄ™p klonowaniaKlonowanie nieudane: {error}KlonowanieKolumnyUkoÅ„czono pomyÅ›lniePotwierdź nadpisanieTryb krzyżowyWycinanie pamiÄ™ci {number}D-STARKod DTCSPolaryzacja DTCSSkasuj (i skocz wyżej)_UsuÅ„ wszystkieWykryjDeweloperRóżnice {a} i {b}Porównaj surowe pamiÄ™ciAnulować zmiany?Nie pokazuj tego wiÄ™cejPobierz z urzÄ…dzeniaPobieranie listy RPTCALLPobieranie listy URCALLDupleksZamieÅ„Edycja nowej pozycji, przyjmujÄ™ wartoÅ›ci standardoweWłącz funkcje deweloperskieKasowanie pamiÄ™ci {loc}Kasowanie pamiÄ™ci {number}Raportowanie błędów jest włączoneBłąd ustawiania pamiÄ™ciZamieÅ„ pamiÄ™ciEksportujPlik już istniejePlik zostaÅ‚ zmodyfikowany, zapisać przed zakoÅ„czeniem?CzÄ™stotliwośćPobieranie kanaÅ‚u {chan}Pobieranie pamiÄ™ci {number}Pobieranie surowej pamiÄ™ci {number}OkPomocUkryj nieużywane polaPliki ICFPlik ICF nie może być edytowany, może być wyÅ›wietlany oraz importowany do innego pliku.Otworzyć w trybie do odczytu?JeÅ›li chcesz wyłączyć tÄ… funkcjÄ™ możesz to zrobić w menu PomocImportujImportuj z RFinderImportuj z RepeaterBookNiekompatybilna pamięćUmieść wiersz wyżejUmieść wiersz niżejBłąd wewnÄ™trznyBłąd wewnÄ™trzny: Kolumna {name} nie znalezionaBłąd wewnÄ™trzny: NiewÅ‚aÅ›ciwy limit {number}NiewÅ‚aÅ›ciwa wartośćBłąd.Wartość musi być liczbÄ….odwrotnośćLp.Lokalizacja {number} jest zaimportowana.Lokalizacja {number} jest zaimportowana.Wybierz inna lokalizacjÄ™.Szukaj wolnego miejsca ({number})PamiÄ™ci muszÄ… zachować ciÄ…gÅ‚ośćZakres pamiÄ™ci:Pamięć {number}TrybW górÄ™Przeniesiono {count} pamiÄ™ciPrzenoszenie pamiÄ™ci z {old} do {new}PrzenoszÄ™ {src} do {dst}Mój znak wywoÅ‚awczyNazwa Å»adenUwaga:PrzesuniÄ™cieOtwórz poprzednio otwarty plik {name}OpcjeNadpisać pozycjÄ™ {number}?Nadpisać ?Wklejona komórka pamiÄ™ci {number} jest nie kompatybilna z tym urzÄ…dzeniem:PortMocSurowa pamięć {number}Znak wywoÅ‚awczy repeateraRaport statystykRaportowanie jest wyłączoneOdwrotnyWybierz kolumnyPrzełączPokaż pustePokaż surowÄ… pamięćPrzeskoczKanaÅ‚y specjalnePlik {name} istneje.Czy chcesz nadpisać?Funkcja raportowanie programu CHIRP zostaÅ‚a zaprojektowana by pomóc podnosić jakość dajÄ…c autorom możliwość skupienia siÄ™ na sterownikach urzÄ…dzeÅ„ najczęściej używanych i błędach które napotykajÄ… użytkowników.Raporty nie zawierajÄ… danych identyfikujÄ…cych użytkownika i sÄ… wykorzystywane tylko w celach statystycznych przez autorów.Twoja prywatność jest najważniejsza , lecz rozważ pozostawienie tej funkcji włączonej by pomóc ulepszać oprogramowanie CHIRP!{vendor} {model} posiada kilka niezależnych urzÄ…dzeÅ„ wbudowanychUrzÄ…dzenie {vendor} {model} pracuje w trybie na żywo. To oznacza, że każda zmiana zostaje natychmiast wysÅ‚ana do urzÄ…dzenia. Z tego powodu nie możesz wykonać akcji Zapisz lub WyÅ›lij.JeÅ›li chcesz edytować zwartość w trybie bez połączenia użyj polecenia Eksportuj do pliku CSV korzystajÄ…c z menu Plik.WystÄ…piÅ‚ błąd podczas otwierania {fname}: {error}Ta operacja wymaga przesuniÄ™cia wszystkich komórek pamiÄ™ci o jeden.Może to zająć dużo czasu.Czy na pewno chcesz to zrobić?TonTryb tonuBlokada tonuKrok strojeniaNie mogÄ™ wykryć urzÄ…dzenia na porcie {port}Nie potrafiÄ™ wprowadzić zmian w tym modeluNieobsÅ‚ugiwany typ plikuBez nazwyWyÅ›lij do urzÄ…dzeniaVX7 CommanderPliki VX7 Commander ProducentWidoczne kolumny dla {radio}Zapisywanie pamiÄ™ci {number}Aby porównać wybierz dwie pamiÄ™ci !Znak wywoÅ‚awczy korespondenta_Kopiuj_Wytnij_UsuÅ„_Edycja_Plik_Wklej_UrzÄ…dzenie_Niedawny_PodglÄ…dbezczynny{vendor} {model} plik zrzutuchirp-daily-20170714/locale/nl/0000755000016101777760000000000013132065774017243 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/nl/LC_MESSAGES/0000755000016101777760000000000013132065774021030 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/nl/LC_MESSAGES/CHIRP.mo0000644000016101777760000003642113132065774022240 0ustar jenkinsnogroup00000000000000Þ•Êl ¼ ðñ 6;Up u€ †’¤· ÀÊÓ ÚäÿŠšµÄÚâê òü #; BLU\ r} „Ž ´ Æ ÐÝî0Ia h!r”¯ÄÜö&8? N.Z ‰“˜¶)Ïù(=Y\a ta~Ià*1BVo!ˆªÅÙßð'&8+_‹"¨ËÓ%×-ý++nW"Æéò ,1 7B KU]!t– ¬¸½ÂÈÏçù !+ HSAY› ¦¿Î×àæú 4PX_n¥º ÀËÛëð<Í>9 5F)| )¦ +Ð Uü ¥R!ø!û! " " "" #"$D"i""ˆ"ž"$³"*Ø"# #!#5#<#"X#{#“# ³#Á#Ç#Ì#Ô#Ú#à#ç#î#ö#ü#$$7$]R$°%Ê%ã%é%&&%&D& I&S&Z&j&‡& &¹& Ô& à& ê&"ö& '›''Ã'ã'þ'(( &( 0(:( S( ](h(p(ˆ( (™( ¢('®(Ö( è( ó(ý()1)G) \)j)#~)¢)µ)Ñ)î) * *<*"X*{*Œ*" *Ã*'Þ*+ +"+:+8J+ ƒ+Ž+’+±+0Ð+,,/,%D,j,m,r, ,‰,Q'- y-„-›-²-%Î-,ô-%!.G.\.b.{. ”.(¡.'Ê.7ò.*/,I/ v/ƒ/3Š/6¾/$õ/p0'‹0³0"»0Þ0î0þ01 11-1>1O1%j1 1±1Â1Ç1Ì1Ò1Ù1÷1(2>2 E2S2r22@‰2Ê2Ð2(Ù233%3.343H3d3|3š3¹3 Á3Ì3(à3 4#4>4 D4O4_4 q4{48Œ4(Å4?î6P.7987¹8=ñ8f/9¤–9;:?: D: N:[:j:1q:4£: Ø:ù:;;-1;2_;’; ¦;´;Ì;Ñ; ñ;ÿ;-<G<V<_< h< u< <Š<“<š<¢<©< ±<#Ò<ö<´®]½žÊ°¸Pe‘—Æs?‡¶«ÅÉ~!uÀ$¡5<pz"k/ Ä@Un_ÁO3NÇb`M¦…“>a”ŒJ•QEi*¢Ÿ+ˆo[𹂳¼^Š;W„™–¤›VS1YX¯r F}-(cKB¨tG©¾± ‰8ƒ¥‹ÃD v6ZH9A%mµ0˜.Cf)# €£§=|7qx{¬Lº\Žd¿2,ÈyªT’&h»œ4wIg †·' :­R²ljAdding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCutCutting memory {number}D-STARDTCS CodeDTCS PolDeleteDelete (and shift up)Delete allDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from RFinderImport from RepeaterBookImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRadioRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2012-03-18 12:01+0100 Last-Translator: Michael Tel Language-Team: Dutch Language: nl MIME-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Kanaal toevoegen {number}Nieuwe locatie aanpassenAllesEr is een fout opgetredenAutoAutomatische repeater offsetFoute waarde voor {col}: {val}BankBanknamenBankenCHIRP bestandenCHIRP oorspronkelijk bestandCHIRP radio afbeeldingenKomma gescheiden bestandKomma gescheiden bestandenRoeplettersAnnulerenGeannuleerdKan niet worden gemporteerd, omdatTaal wijzigenKies een taal of Auto voor het gebruik van het standaard besturingssysteem. U dient de toepassing opnieuw te starten voordat de wijziging wordt doorgevoerdKies n om vanuit te importeren:Vooruitgang van het klonenKlonen mislukt: {error}KlonenKolommenOpmerkingVolbrachtOverschrijven bevestigenKopiërenCross-modeKnippenKnippen kanaal {number}D-STARDTCS codeDTCS PolVerwijderenVerwijderen (en naar boven verplaatsen)Alles verwijderenDetecterenOntwerperVerschillen ruwe kanalenVerschil tussen {a} en {b}Verschil ruwe kanalenVerschillen van tabsDigitale codeWijzigen verwerpen?Deze melding niet opnieuw weergevenDownload van radioDownloaden van MYCALL lijstDownloaden van RPTCALL lijstDownloaden van URCALL lijstDuplexW_isselenBewerken van een nieuw item. Standaardwaarden worden genomenOntwikkelaars functies inschakelenWis kanaal {loc}Wis kanaal {number}Fout bij het importen van geheugenFoutrapportage is aangezetFout bij het instellen van het geheugenKanalen wisselenExporterenExporteren naar bestandBestand bestaatHet bestand is aangepast. Wilt u de wijzigingen opslaan?FrequentieVanBank voor kanaal {num} ophalenInformatie van de bank ophalenInformatie van de bank voor kanaal {num} ophalenOphalen kanaal {chan}Ophalen kanaal {number}Ophalen kanaal {num}Ophalen van onbewerkt kanaal {number}GaHelpOngebruikte velden verbergenICF bestandenICF bestanden kunnen niet worden bewerkt, alleen worden weergegeven of in een ander bestand gemporteerd. Openen in de modus alleen-lezen?Als u deze functie wilt uitschakelen, dan kunt u dit doen in het menu HelpImporterenImporteren van bestandImporteren uit RFinderImporteren van RepeaterBookImporteren van aanwezige configuratieImporteren van aanwezige configuratie {name}Importeren van informatie van de bankOnverenigbaar kanaalIndexRegel hierboven invoegenRegel hieronder invoegenInterne foutInterne fout: Kolom {name} niet gevondenInterne fout: Ongeldige limiet {number}Interne fout: Niet in staat om te uploaden naar {model}Ongeldige waarde voor dit veldOngeldige waarde. Het moet een integer zijn.TegengesteldPlaatsGeheugenplaatsen zullen ook gemporteerd worden naarPlaats van het kanaal in het bestand wordt gemporteerdPlaats {number} is reeds gemporteerdPlaats {number} wordt al gemporteerd. Kies een andere waarde voor 'Nieuwe locatie' vr 'Importeren' te selecterenZoeken naar een vrije plaats ({number})KanalenDe kanalen moeten aangrenzend zijnGeheugenbereik:Kanaal {number}ModeModelVerplaats omlaa_gVerplaats om_hoogVerplaats omlaagVerplaats omhoog{count} kanalen verplaatstVerplaats kanaal van {old} naar {new}Verplaatsen van {src} naar {dst}Mijn roeplettersNaamGeenNoot:OffsetRecent geopend bestand {name}Openen aanwezige configuratieOpenen van aanwezige configuratie {name}OptiesOverschrijvenPlaats {number} overschrijven?Overschrijven?PlakkenGeplakt kanaal {number} is niet compatibel met deze radio omdat:PoortVermogenLijst van het kanaal wordt voorbereid...Voorbereiden van geheugenRPT1CALLRPT2CALLRadioRuw kanaal {number}Roepletters van de repeaterStatistieken raporterenRapportering is uitgeschakeldInformatie van de bank ophalenOmkerenSelecterenKolommen selecterenInstellen van de index voor kanaal {num}Kanaal {number} instellenNaam van de bank instellenShiftLege tonenToon ruw kanaalToon ruw geheugenOverslaanSpeciale kanalenHet bestand {name} bestaat al. Wilt u het overschrijven?De rapportagefunctie van CHIRP is ontworpen om de kwaliteit te helpen verbeteren door de auteurs toe te staan zich te concentreren op de radio stuurprogramma's die het meest gebruikt worden en fouten ervaren door de gebruikers. De verslagen bevatten geen identificerende informatie en worden alleen voor statistische doeleinden gebruikt door de auteurs. Uw privacy is uiterst belangrijk, maar kunt u overwegen deze functie ingeschakeld te laten om zo te helpen CHIRP beter maken! Weet u zeker dat u deze functie wilt uitschakelen?De {vendor} {model} heeft meerdere onafhankelijke sub-apparatenDe {vendor} {model} opereert in de live modus. Dit betekent dat wijzigingen die u aanbrengt onmiddellijk naar de radio verzonden worden. Hierdoor kunt u het niet Opslaan of uploaden . Als u wenst de inhoud off line te bewerken, gelieve exporteren naar een CSV-bestand, met behulp van het menu bestand.Er is een fout opgetreden tijdens het exporteren: {error}Er is een fout ontstaan tijdens het importeren: {error}Er is een fout opgetreden bij het openen van {fname}: {error}Er zijn fouten opgetreden tijdens het openen van {file}. De getroffen kanalen zijn niet importeerbaar!Deze bewerking vereist het verplaatsen van alle volgende kanalen totdat een lege locatie is bereikt. Dit kan een lange tijd duren. Weet u zeker dat u dit wilt doen?AanToonToonmodusToon squelchKanaal-afstandURCALLNiet in staat om de radio op {port} te detecterenNiet in staat om wijzigingen voor dit model te makenNiet-ondersteunende bestandstypeNaamloosBijwerken RPTCALL lijstBijwerken URCALL lijstIndex van de bank voor kanaal {num} bijwerkenInformatie van de bank voor kanaal {num} bijwerkenNaar radio uploadenVX7 CommanderVX7 Commander bestandenMerkZichtbare kolommen voor {radio}Met dank aan:Schrijven kanaal {number}U kunt maar van 2 kanalen de verschillen zienUw roepletters_Kopiren_Knippen_VerwijderenBe_werken_Bestanden_Plakken_Radio_RecentBee_ldluieren{num} fouten tijdens het openen:{vendor} {model} afbeeldingsbestand{vendor} {model} op {port}chirp-daily-20170714/locale/it/0000755000016101777760000000000013132065774017246 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/it/LC_MESSAGES/0000755000016101777760000000000013132065774021033 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/locale/it/LC_MESSAGES/CHIRP.mo0000644000016101777760000003561313132065774022245 0ustar jenkinsnogroup00000000000000Þ•Êl ¼ ðñ 6;Up u€ †’¤· ÀÊÓ ÚäÿŠšµÄÚâê òü #; BLU\ r} „Ž ´ Æ ÐÝî0Ia h!r”¯ÄÜö&8? N.Z ‰“˜¶)Ïù(=Y\a ta~Ià*1BVo!ˆªÅÙßð'&8+_‹"¨ËÓ%×-ý++nW"Æéò ,1 7B KU]!t– ¬¸½ÂÈÏçù !+ HSAY› ¦¿Î×àæú 4PX_n¥º ÀËÛëð<Í>9 5F)| )¦ +Ð Uü ¥R!ø!û! " " "" #"$D"i""ˆ"ž"$³"*Ø"# #!#5#<#"X#{#“# ³#Á#Ç#Ì#Ô#Ú#à#ç#î#ö#ü#$$7$nR$Á%Û%ò%ø%&&"5&X& ]&g& l&x&Š&Ÿ& ¨& ²&½& Å&Ï& í&†û&‚'œ'¸'Ø'ç'ï' ø'(( !(,(3(K( R(\(e(m( †(”( ›(¨(¹(Ñ(è( ù())4)C)])x)’)™)?¡)á)ÿ)*9*Y*!s*•*¥*­*½*EÌ* ++&+F+3e+™+´+Ï+!ê+ ,,, 2,r<,D¯,ô,û, --6-(V--ž-¶-½-Ò-ç-/ö-*&./Q."./¤.Ô.Ü.à.4ÿ.+4/€`/%á/0!0 10?0 P0\0 d0n0 w0 0‹0!¤0Æ0ã0õ0ú0111(1$B1g1 o1!{11¬1J´1ÿ12 2+2@2I2R2X2m2ƒ2–2¬2Ç2 Ð2Ú2%ì23-3E3 K3X3k3~3„3/”3ÉÄ37Ž5PÆ5/7+G7-s7o¡7±8Ã8Å8Ê8Ù8 á8ï8-ö82$9W9 p9{9—9.²94á9: &:4: H:S:#p:”:"¯:Ò:ä:ê:ñ:ù:;;;;;#; *;K; j;´®]½žÊ°¸Pe‘—Æs?‡¶«ÅÉ~!uÀ$¡5<pz"k/ Ä@Un_ÁO3NÇb`M¦…“>a”ŒJ•QEi*¢Ÿ+ˆo[𹂳¼^Š;W„™–¤›VS1YX¯r F}-(cKB¨tG©¾± ‰8ƒ¥‹ÃD v6ZH9A%mµ0˜.Cf)# €£§=|7qx{¬Lº\Žd¿2,ÈyªT’&h»œ4wIg †·' :­R²ljAdding memory {number}Adjust New LocationAllAn error has occurredAutoAutomatic Repeater OffsetBad value for {col}: {val}BankBank NamesBanksCHIRP FilesCHIRP Native FileCHIRP Radio ImagesCSV FileCSV FilesCallsignCancelCancelledCannot be imported becauseChange languageChoose a language or Auto to use the operating system default. You will need to restart the application before the change will take effectChoose one to import from:Clone ProgressClone failed: {error}CloningColumnsCommentCompletedConfirm overwritesCopyCross ModeCutCutting memory {number}D-STARDTCS CodeDTCS PolDeleteDelete (and shift up)Delete allDetectDeveloperDiff Raw MemoriesDiff of {a} and {b}Diff raw memoriesDiff tabsDigital CodeDiscard Changes?Don't show this againDownload From RadioDownloading MYCALL listDownloading RPTCALL listDownloading URCALL listDuplexE_xchangeEditing new item, taking defaultsEnable Developer FunctionsErasing memory {loc}Erasing memory {number}Error importing memories:Error reporting is enabledError setting memoryExchange memoriesExportExport To FileFile ExistsFile is modified, save changes before closing?FrequencyFromGetting bank for memory {num}Getting bank informationGetting bank information for memory {num}Getting channel {chan}Getting memory {number}Getting memory {num}Getting raw memory {number}GoHelpHide Unused FieldsICF FilesICF files cannot be edited, only displayed or imported into another file. Open in read-only mode?If you wish to disable this feature you may do so in the Help menuImportImport From FileImport from RFinderImport from RepeaterBookImport from stock configImport stock configuration {name}Importing bank informationIncompatible MemoryIndexInsert row aboveInsert row belowInternal ErrorInternal Error: Column {name} not foundInternal Error: Invalid limit {number}Internal error: Unable to upload to {model}Invalid value for this fieldInvalid value. Must be an integer.InverseLocLocation memory will be imported intoLocation of memory in the file being importedLocation {number} is already being importedLocation {number} is already being imported. Choose another value for 'New Location' before selection 'Import'Looking for a free spot ({number})MemoriesMemories must be contiguousMemory range:Memory {number}ModeModelMove Dow_nMove _UpMove downMove upMoved {count} memoriesMoving memory from {old} to {new}Moving {src} to {dst}My callsignNameNoneNote:OffsetOpen recent file {name}Open stock configOpen stock configuration {name}OptionsOverwriteOverwrite location {number}?Overwrite?PastePasted memory {number} is not compatible with this radio because:PortPowerPreparing memory list...Priming memoryRPT1CALLRPT2CALLRadioRaw memory {number}Repeater callsignReport statisticsReporting is disabledRetrieving bank informationReverseSelectSelect ColumnsSetting index for memory {num}Setting memory {number}Setting name on bankShiftShow EmptyShow Raw MemoryShow raw memorySkipSpecial ChannelsThe file {name} already exists. Do you want to overwrite it?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! Are you sure you want to disable this feature?The {vendor} {model} has multiple independent sub-devicesThe {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.There was an error during export: {error}There was an error during import: {error}There was an error opening {fname}: {error}There were errors while opening {file}. The affected memories will not be importable!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?ToToneTone ModeToneSqlTune StepURCALLUnable to detect radio on {port}Unable to make changes to this modelUnsupported file typeUntitledUpdating RPTCALL listUpdating URCALL listUpdating bank index for memory {num}Updating bank information for memory {num}Upload To RadioVX7 CommanderVX7 Commander FilesVendorVisible columns for {radio}With significant contributions by:Writing memory {number}You can only diff two memories!Your callsign_Copy_Cut_Delete_Edit_File_Paste_Radio_Recent_Viewidle{num} errors during open:{vendor} {model} image file{vendor} {model} on {port}Project-Id-Version: CHIRP Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-16 12:06-0800 PO-Revision-Date: 2014-11-08 17:58+0100 Last-Translator: Dan Smith Language-Team: English Language: it MIME-Version: 1.0 Content-Type: text/plain; charset=ASCII Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.10 Aggiunta memoria {number}Cambia nuova posizioneTuttoSi e' verificato un erroreAutoOffset Ripetitori AutomaticoValore non valido per {col}: {val}BankNomi BankBankFiles CHIRPFile nativo CHIRPImmagine Radio CHIRPFile CSVFiles CSVNominativoAnnullaAnnullatoNon si puo' importare perche'Cambia linguaScegli una lingua o usa Auto per utilizzare la lingua del sistema operativo. Devi riavviare l'applicazione per applicare i cambiamentiScegli da dove importare:Progressione programmazioneProgrammazione fallita: {error}ProgrammazioneColonneCommentoCompletatoConferma sovrascritturaCopiaCross ModeTagliaTaglia memoria {number}D-STARDTCS CodeDTCS PolEliminaElimina (e sposta sopra)Elimina tuttoRilevaSviluppatoreDiff memorie RawDifferenza di {a} e {b}Differenza memorie RawDifferenzia tabsDigital CodeScartare le modifiche?Non mostrarlo di nuovoLeggi da RadioScaricamento lista MYCALLScaricamento lista RPTCALLScaricamento lista URCALLDuplexScambiaModifica di un nuovo elemento, verranno usati valori di defaultAbilita Funzioni SviluppatoreCancellazione memoria {loc}Cancellatura memoria {number}Errore di importazione memorie:Rapporto errori abilitatoErrore di scrittura della memoriaScambia memorieEsportaEsporta in FileIl File esisteIl file e' stato modificato, salvare i cambiamenti prima di chiudere?FrequenzaDaAcquisizione bank per la memoria {num}Acquisizione informazioni bankAcquisizione informazioni bank per la memoria {num}Acquisizione canale {chan}Acquisita memoria {number}Acquisizione memoria {num}Acquisizione memoria Raw {number}VaiAiutoNascondi campi inutilizzatiFiles ICFI Files ICF non possono essere modificati, solo visualizzati o importati in un altro file. Aprire in sola lettura?Se vuoi disabilitare questa opzione puoi farlo nel menu AiutoImportImporta da FileImporta da RFinderImporta da RepeaterBookImporta da Configurazione StockImportazione configurazione stock {name}Importazione informazioni bankMemoria non compatibileIndiceInserisci riga sopraInserisci riga sottoErrore internoErrore interno: nome colonna {name} non trovatoErrore interno: limite non valido {number}Errore interno: impossibile caricare su {model}Valore non valido per questo campoValore non valido. Deve essere un numero interoInvertiPosLa memoria verra' importata inPosizione della memoria nel file che viene importatoLa memoria {number} e' gia' stata importataLa memoria numero {number} e' gia' stata importata. Scegliere un altro valore per 'Nuova memoria' prima di selezionare 'Importa'Ricerca di un posto libero ({number})MemorieLe memorie devono essere contigueRange memorieMemoria {number}ModulazioneModelloMuovi GiuMuovi SuMuovi giuSposta suSpostate {count} memorieSpostata memoria da {old} a {new}Spostamento da {src} a {dst}Il mio nominativoNomeNessunoNote:OffsetApri file recente {name}Apri configurazione stockApertura configurazione stock {name}OpzioniSovrascriviSovrascrivere posizione {number}?Sovrascrivere?IncollaLa memoria incollata {number} non e' compatibile con questa radio perche':PortaPotenzaPreparazione lista memorie...Preparazione memoriaRPT1CALLRPT2CALLRadioMemoria Raw {number}Nominativo RipetitoreReport statisticheReporting disattivatoRecupero informazioni bankRovesciaSelezionaSeleziona colonneScrittura indice per la memoria {num}Scrittura memoria {number}Scrittura nome del bankShiftMostra VuotiMostra memorie RawMostra memorie RawSaltaCanali SpecialiIl file {name} esiste gia. Vuoi sovrascriverlo?La funzione reporting di CHIRP e' fatta per migliorare la qualita' consentendo agli autori di concentrarsi sulle radio piu' utilizzate e sugli errori piu' comuni. I report non contengono informazioni personali e sono utilizzati per soli fini statistici dagli autori. La tua provacy e' molto importante ma per favore considera di lasciare attiva questa opzione per aiutarci a migliorare CHIRP! Sei sicuro di disabilitare questa opzione?Il {vendor} {model} ha sub-device multipli indipendentiLa radio{vendor} {model} opera in live mode. Questo significa che qualsiasi modifica viene immediatamente scritta nella radio. A causa di questo, non puoi utilizzare le funzioni Salva or Scrivi. Se vuoi modificare le memorie offline, per favore Esporta le memorie in un file CSV, usando il menu File.Errore durante l'esportazione del file: {error}Errore durante l'apertura del file: {error}Errore durante l'apertura di {fname}: {error}Ci sono stati degli errori nell'apertura del file {file}. Le memorie interessate non potranno essere importate!Questa operazione richiede lo spostamento dei canali seguenti di un passo alla volta fino alla nuova posizione. L'operazione puo' durare MOLTO tempo. Sei sicuro di volerlo fare?AToneModalita' tonoToneSqlStep sintoniaURCALLImpossibile rilevare radio sulla porta {port}Impossibile effettuare modifiche su questo modelloTipo file non supportatoSenza NomeAggiornamento lista RPTCALLAggiornamento lista URCALLAggiornamento indice bank per la memoria {num}Aggiornamento informazioni bank per la memoria {num}Scrivi su RadioVX7 CommanderFiles VX7 CommanderProduttoreColonne visibili per {radio}Con il significativo contributo di:Scrittura memoria {number}Puoi differenziare solo 2 memorie!Il tuo nominativoCopiaTagliaEliminaModificaFileIncollaRadioRecentiVistapronto{num} errori durante l'apertura:File immagine {vendor} {model}{vendor} {model} su porta {port}chirp-daily-20170714/chirp_banks.xsd0000644000016100007500000000042211717005656016733 0ustar jenkins00000000000000 chirp-daily-20170714/chirp/0000755000016101777760000000000013132065774016500 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/chirp/pyPEG.py0000644000016101777760000002746012475265017020050 0ustar jenkinsnogroup00000000000000# YPL parser 1.5 # written by VB. import re import sys import codecs import exceptions class keyword(unicode): pass class code(unicode): pass class ignore(object): def __init__(self, regex_text, *args): self.regex = re.compile(regex_text, *args) class _and(object): def __init__(self, something): self.obj = something class _not(_and): pass class Name(unicode): def __init__(self, *args): self.line = 0 self.file = u"" class Symbol(list): def __init__(self, name, what): self.__name__ = name self.append(name) self.what = what self.append(what) def __call__(self): return self.what def __unicode__(self): return u'Symbol(' + repr(self.__name__) + ', ' + repr(self.what) + u')' def __repr__(self): return unicode(self) word_regex = re.compile(ur"\w+") rest_regex = re.compile(ur".*") print_trace = False def u(text): if isinstance(text, exceptions.BaseException): text = text.args[0] if type(text) is unicode: return text if isinstance(text, str): if sys.stdin.encoding: return codecs.decode(text, sys.stdin.encoding) else: return codecs.decode(text, "utf-8") return unicode(text) def skip(skipper, text, skipWS, skipComments): if skipWS: t = text.lstrip() else: t = text if skipComments: try: while True: skip, t = skipper.parseLine(t, skipComments, [], skipWS, None) if skipWS: t = t.lstrip() except: pass return t class parser(object): def __init__(self, another=False, p=False): self.restlen = -1 if not(another): self.skipper = parser(True, p) self.skipper.packrat = p else: self.skipper = self self.lines = None self.textlen = 0 self.memory = {} self.packrat = p # 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 def R(result, text): if __debug__: if print_trace: try: if _pattern.__name__ != "comment": sys.stderr.write(u"match: " + _pattern.__name__ + u"\n") except: pass if self.restlen == -1: self.restlen = len(text) else: self.restlen = min(self.restlen, len(text)) res = resultSoFar if name and result: name.line = self.lineNo() res.append(Symbol(name, result)) elif name: name.line = self.lineNo() res.append(Symbol(name, [])) elif result: if isinstance(result, list): res.extend(result) else: res.extend([result]) if self.packrat: self.memory[(len(_textline), id(_pattern))] = (res, text) return res, text def syntaxError(): if self.packrat: self.memory[(len(_textline), id(_pattern))] = False raise SyntaxError() if self.packrat: try: result = self.memory[(len(textline), id(pattern))] if result: return result else: raise SyntaxError() except: pass if callable(pattern): if __debug__: if print_trace: try: if pattern.__name__ != "comment": sys.stderr.write(u"testing with " + pattern.__name__ + u": " + textline[:40] + u"\n") except: pass if pattern.__name__[0] != "_": name = Name(pattern.__name__) pattern = pattern() if callable(pattern): pattern = (pattern,) text = skip(self.skipper, textline, skipWS, skipComments) pattern_type = type(pattern) if pattern_type is str or pattern_type is unicode: if text[:len(pattern)] == pattern: text = skip(self.skipper, text[len(pattern):], skipWS, skipComments) return R(None, text) else: syntaxError() elif pattern_type is keyword: m = word_regex.match(text) if m: if m.group(0) == pattern: text = skip(self.skipper, text[len(pattern):], skipWS, skipComments) return R(None, text) else: syntaxError() else: syntaxError() elif pattern_type is _not: try: r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments) except: return resultSoFar, textline syntaxError() elif pattern_type is _and: r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments) return resultSoFar, textline elif pattern_type is type(word_regex) or pattern_type is ignore: if pattern_type is ignore: pattern = pattern.regex m = pattern.match(text) if m: text = skip(self.skipper, text[len(m.group(0)):], skipWS, skipComments) if pattern_type is ignore: return R(None, text) else: return R(m.group(0), text) else: syntaxError() elif pattern_type is tuple: result = [] n = 1 for p in pattern: if isinstance(p, int): 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 list: 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(u"illegal type in grammar: " + u(pattern_type)) def lineNo(self): if not(self.lines): return u"" if self.restlen == -1: return u"" parsed = self.textlen - self.restlen left, right = 0, len(self.lines) while True: mid = int((right + left) / 2) if self.lines[mid][0] <= parsed: try: if self.lines[mid + 1][0] >= parsed: try: return u(self.lines[mid + 1][1]) + \ u":" + u(self.lines[mid + 1][2]) except: return u"" else: left = mid + 1 except: try: return u(self.lines[mid + 1][1]) + \ u":" + u(self.lines[mid + 1][2]) except: return u"" else: right = mid - 1 if left > right: return u"" # plain module APIs def parseLine(textline, pattern, resultSoFar=[], skipWS=True, skipComments=None, packrat=False): p = parser(p=packrat) text = skip(p.skipper, textline, 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 callable(language): language = language() orig, ld = u"", 0 for line in lineSource: if lineSource.isfirstline(): ld = 1 else: ld += 1 lines.append((len(orig), lineSource.filename(), lineSource.lineno() - 1)) orig += u(line) textlen = len(orig) try: p = parser(p=packrat) p.textlen = len(orig) if lineCount: p.lines = lines else: p.line = None text = skip(p.skipper, orig, 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, u"" 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(u"syntax error in " + u(file) + u":" + u(lineNo) + u": " + lineCont) return result chirp-daily-20170714/chirp/import_logic.py0000644000016101777760000002126712475265017021552 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 logging from chirp import chirp_common, errors LOG = logging.getLogger(__name__) 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 _get_bank_model(radio): for model in radio.get_mapping_models(): if isinstance(model, chirp_common.BankModel): return model return None 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 = _get_bank_model(dst_radio) if not dst_bm: return dst_banks = dst_bm.get_mappings() src_bm = _get_bank_model(src_radio) if not src_bm: return src_banks = src_bm.get_mappings() src_mem_banks = src_bm.get_memory_mappings(src_mem) src_indexes = [src_banks.index(b) for b in src_mem_banks] for bank in dst_bm.get_memory_mappings(dst_mem): dst_bm.remove_memory_from_mapping(dst_mem, bank) for index in src_indexes: try: bank = dst_banks[index] LOG.debug("Adding memory to bank %s" % bank) dst_bm.add_memory_to_mapping(dst_mem, bank) if isinstance(dst_bm, chirp_common.MappingModelIndexInterface): dst_bm.set_memory_index(dst_mem, bank, dst_bm.get_next_mapping_index(bank)) except IndexError: pass chirp-daily-20170714/chirp/memmap.py0000644000016101777760000000503312475265017020330 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 . 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): """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]) 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-daily-20170714/chirp/ui/0000755000016101777760000000000013132065774017115 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/chirp/ui/dstaredit.py0000644000016101777760000001343512475535623021464 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 logging from chirp.ui import common, miscwidgets LOG = logging.getLogger(__name__) 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 LOG.debug("Callsigns: %s" % cse.get_callsigns()) if cse == self.editor_ucall: job = common.RadioJob(None, "set_urcall_list", cse.get_callsigns()) LOG.debug("Set urcall") elif cse == self.editor_rcall: job = common.RadioJob(None, "set_repeater_call_list", cse.get_callsigns()) LOG.debug("Set rcall") elif cse == self.editor_mcall: job = common.RadioJob(None, "set_mycall_list", cse.get_callsigns()) if job: LOG.debug("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 LOG.debug("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): super(DStarEditor, self).__init__(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-daily-20170714/chirp/ui/inputdialog.py0000644000016101777760000001133212475535623022012 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 logging from miscwidgets import make_choice from chirp.ui import reporting LOG = logging.getLogger(__name__) 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)) LOG.error("--- Exception Dialog: %s ---" % exception) LOG.error(traceback.format_exc(limit=100)) LOG.error("----------------------------") 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, _): LOG.debug("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-daily-20170714/chirp/ui/common.py0000644000016101777760000002751212475535623020772 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 import logging from chirp import errors from chirp.ui import reporting, config LOG = logging.getLogger(__name__) CONF = config.get() 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, rthread): gobject.GObject.__init__(self) self.read_only = False self._focused = False self.rthread = rthread 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 def set_read_only(self, read_only): self.read_only = read_only def get_read_only(self): return self.read_only def prepare_close(self): pass def other_editor_changed(self, editor): pass gobject.type_register(Editor) def DBG(*args): if False: LOG.debug(" ".join(args)) 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))) DBG(self.desc) result = func(*self.args, **self.kwargs) except errors.InvalidMemoryLocation, e: result = e except Exception, e: LOG.error("Exception running RadioJob: %s" % e) log_exception() LOG.error("Job Args: %s" % str(self.args)) LOG.error("Job KWArgs: %s" % str(self.kwargs)) LOG.error("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: LOG.error("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, parent=None): threading.Thread.__init__(self) gobject.GObject.__init__(self) self.__queue = {} if parent: self.__runlock = parent._get_run_lock() self.status = lambda msg: parent.status(msg) else: self.__runlock = threading.Lock() self.status = self._status self.__counter = threading.Semaphore(0) self.__lock = threading.Lock() self.__enabled = True self.radio = radio def _get_run_lock(self): return self.__runlock def _qlock(self): self.__lock.acquire() def _qunlock(self): self.__lock.release() def _qsubmit(self, job, priority): if priority not in self.__queue: self.__queue[priority] = [] self.__queue[priority].append(job) self.__counter.release() def _queue_clear_below(self, priority): for i in range(0, priority): if i in self.__queue 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() LOG.debug("RadioThread exiting") def log_exception(): import traceback import sys reporting.report_exception(traceback.format_exc(limit=30)) LOG.error("-- Exception: --") LOG.error(traceback.format_exc(limit=30)) LOG.error("----------------") 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, diffsonly=False): lines_a = a.split(os.linesep) lines_b = b.split(os.linesep) blankprinted = True 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) blankprinted = False elif diffsonly is True: if blankprinted: continue diff += os.linesep blankprinted = True 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) try: fontsize = CONF.get_int("diff_fontsize", "developer") except Exception: fontsize = 11 if fontsize < 4 or fontsize > 144: LOG.info("Unsupported diff_fontsize %i. Using 11." % fontsize) fontsize = 11 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 %i" % fontsize) 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() def unpluralize(string): if string.endswith("s"): return string[:-1] else: return string chirp-daily-20170714/chirp/ui/shiftdialog.py0000644000016101777760000001155612724476202021772 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 import logging from chirp import errors, chirp_common LOG = logging.getLogger(__name__) 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 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 LOG.info("Moving %i to %i" % (src, dst)) self.status(_("Moving {src} to {dst}").format(src=src, dst=dst), count / len(memories)) i.number = dst if i.empty: self.rthread.radio.erase_memory(i.number) else: self.rthread.radio.set_memory(i) count += 1.0 return int(count) def _get_mems_until_hole(self, start, endokay=False, all=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 and not all: 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")) LOG.debug("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: LOG.warn("No memory list?") return 0 def _delete_hole(self, start, all=False): mems = self._get_mems_until_hole(start+1, endokay=True, all=all) if mems: count = self._shift_memories(-1, mems) self.rthread.radio.erase_memory(count+start) return count else: LOG.warn("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, *args): self.status("Waiting for radio to become available", 0) self.rthread.lock() try: count = func(newhole, *args) 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, all=False): self.quiet = quiet self.thread = threading.Thread(target=self.threadfn, args=(newhole, self._delete_hole, all)) self.thread.start() gtk.Dialog.run(self) chirp-daily-20170714/chirp/ui/editorset.py0000644000016101777760000003631412771422200021465 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 import logging from chirp import chirp_common, directory from chirp.drivers import generic_csv, generic_xml from chirp.ui import memedit, dstaredit, bankedit, common, importdialog from chirp.ui import inputdialog, reporting, settingsedit, radiobrowser, config LOG = logging.getLogger(__name__) 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 _make_device_mapping_editors(self, device, devrthread, index): sub_index = 0 memory_editor = self.editors["memedit%i" % index] mappings = device.get_mapping_models() for mapping_model in mappings: members = bankedit.MappingMembershipEditor(devrthread, self, mapping_model) label = mapping_model.get_name() if self.rf.has_sub_devices: label += "(%s)" % device.VARIANT lab = gtk.Label(label) self.tabs.append_page(members.root, lab) self.editors["mapping_members%i%i" % (index, sub_index)] = members basename = common.unpluralize(mapping_model.get_name()) names = bankedit.MappingNameEditor(devrthread, mapping_model) label = "%s Names" % basename if self.rf.has_sub_devices: label += " (%s)" % device.VARIANT lab = gtk.Label(label) self.tabs.append_page(names.root, lab) self.editors["mapping_names%i%i" % (index, sub_index)] = names members.root.show() members.connect("changed", self.editor_changed) if hasattr(mapping_model.get_mappings()[0], "set_name"): names.root.show() members.connect("changed", lambda x: names.mappings_changed()) names.connect("changed", lambda x: members.mappings_changed()) names.connect("changed", self.editor_changed) sub_index += 1 def _make_device_editors(self, device, devrthread, index): if isinstance(device, chirp_common.IcomDstarSupport): memories = memedit.DstarMemoryEditor(devrthread) else: memories = memedit.MemoryEditor(devrthread) memories.connect("usermsg", lambda e, m: self.emit("usermsg", m)) memories.connect("changed", self.editor_changed) if self.rf.has_sub_devices: label = (_("Memories (%(variant)s)") % dict(variant=device.VARIANT)) rf = device.get_features() else: label = _("Memories") rf = self.rf lab = gtk.Label(label) self.tabs.append_page(memories.root, lab) memories.root.show() self.editors["memedit%i" % index] = memories self._make_device_mapping_editors(device, devrthread, index) if isinstance(device, chirp_common.IcomDstarSupport): editor = dstaredit.DStarEditor(devrthread) self.tabs.append_page(editor.root, gtk.Label(_("D-STAR"))) editor.root.show() editor.connect("changed", self.dstar_changed, memories) editor.connect("changed", self.editor_changed) self.editors["dstar"] = editor 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") rthread = common.RadioThread(self.radio) rthread.setDaemon(True) rthread.start() 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 = {} self.rf = self.radio.get_features() if self.rf.has_sub_devices: devices = self.radio.get_sub_devices() else: devices = [self.radio] index = 0 for device in devices: devrthread = common.RadioThread(device, rthread) devrthread.setDaemon(True) devrthread.start() self._make_device_editors(device, devrthread, index) index += 1 if self.rf.has_settings: editor = settingsedit.SettingsEditor(rthread) self.tabs.append_page(editor.root, gtk.Label(_("Settings"))) editor.root.show() editor.connect("changed", self.editor_changed) self.editors["settings"] = editor conf = config.get() if (hasattr(self.rthread.radio, '_memobj') and conf.get_bool("developer", "state")): editor = radiobrowser.RadioBrowser(self.rthread) lab = gtk.Label(_("Browser")) self.tabs.append_page(editor.root, lab) editor.connect("changed", self.editor_changed) self.editors["browser"] = editor self.pack_start(self.tabs) self.tabs.show() 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() button.set_relief(gtk.RELIEF_NONE) button.set_focus_on_click(False) icon = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) icon.show() button.add(icon) 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, dstared, memedit): memedit.set_urcall_list(dstared.editor_ucall.get_callsigns()) memedit.set_repeater_list(dstared.editor_rcall.get_callsigns()) memedit.prefill() def editor_changed(self, target_editor=None): LOG.debug("%s changed" % target_editor) if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() for editor in self.editors.values(): if editor != target_editor: editor.other_editor_changed(target_editor) 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) LOG.debug("Imported %i" % count) dst_rthread._qunlock() if count > 0: self.editor_changed() current_editor = self.get_current_editor() gobject.idle_add(current_editor.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) text = _("The {vendor} {model} has multiple independent sub-devices") d.label.set_text(text.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): current_editor = self.get_current_editor() if not isinstance(current_editor, memedit.MemoryEditor): # FIXME: We need a nice message to let the user know that they # need to select the appropriate memory editor tab before doing # and import so that we know which thread and editor to import # into and refresh. This will do for the moment. common.show_error("Memory editor must be selected before import") 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): # NOTE: this is only called to prime new CSV files, so assume # only one memory editor for now mem = chirp_common.Memory() mem.freq = 146010000 def cb(*args): gobject.idle_add(self.editors["memedit0"].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): for editor in self.editors.values(): editor and editor.set_read_only(read_only) def get_read_only(self): return self.editors["memedit0"].get_read_only() def prepare_close(self): for editor in self.editors.values(): editor and editor.prepare_close() def get_current_editor(self): tabs = self.tabs for lab, e in self.editors.items(): if e and tabs.page_num(e.root) == tabs.get_current_page(): return e raise Exception("No editor selected?") @property def rthread(self): """Magic rthread property to return the rthread of the currently- selected editor""" e = self.get_current_editor() return e.rthread chirp-daily-20170714/chirp/ui/memedit.py0000644000016101777760000017134113114457177021124 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 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 import logging from chirp.ui import common, shiftdialog, miscwidgets, config, memdetail from chirp.ui import bandplans from chirp import chirp_common, errors, directory, import_logic LOG = logging.getLogger(__name__) if __name__ == "__main__": import sys sys.path.insert(0, "..") 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"): chirp_common.ALL_DTCS_CODES, _("DTCS Rx Code"): chirp_common.ALL_DTCS_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, __): new = chirp_common.parse_freq(new) return abs(new) def ed_freq(self, _foo, path, new, colnum): iter = self.store.get_iter(path) was_filled, prev = self.store.get(iter, self.col("_filled"), colnum) def set_offset(offset): if offset > 0: dup = "+" elif offset == 0: dup = "" else: dup = "-" offset *= -1 if dup not in self.choices[_("Duplex")]: LOG.warn("Duplex %s not supported by this radio" % dup) return if offset: self.store.set(iter, self.col(_("Offset")), offset) self.store.set(iter, self.col(_("Duplex")), dup) def set_ts(ts): if ts in self.choices[_("Tune Step")]: self.store.set(iter, self.col(_("Tune Step")), ts) else: LOG.warn("Tune step %s not supported by this radio" % ts) def get_ts(path): return self.store.get(iter, self.col(_("Tune Step")))[0] def set_mode(mode): if mode in self.choices[_("Mode")]: self.store.set(iter, self.col(_("Mode")), mode) else: LOG.warn("Mode %s not supported by this radio (%s)" % (mode, self.choices[_("Mode")])) def set_tone(tone): if tone in self.choices[_("Tone")]: self.store.set(iter, self.col(_("Tone")), tone) else: LOG.warn("Tone %s not supported by this radio" % tone) try: new = chirp_common.parse_freq(new) except ValueError, e: LOG.error(e) new = None if not self._features.has_nostep_tuning: set_ts(chirp_common.required_step(new)) is_changed = new != prev if was_filled else True if new is not None and is_changed: defaults = self.bandplans.get_defaults_for_frequency(new) set_offset(defaults.offset or 0) if defaults.step_khz: set_ts(defaults.step_khz) if defaults.mode: set_mode(defaults.mode) if defaults.tones: set_tone(defaults.tones[0]) 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: defaults = self.bandplans.get_defaults_for_frequency(freq) offset = defaults.offset or 0 self.store.set(iter, self.col(_("Offset")), abs(offset)) return new def ed_tone_field(self, _foo, path, new, col): if self._config.get_bool("no_smart_tmode"): return new iter = self.store.get_iter(path) # Python scoping hurts us here, so store this as a list # that we can modify, instead of helpful variables :( modes = list(self.store.get(iter, self.col(_("Tone Mode")), self.col(_("Cross Mode")))) def _tm(*tmodes): if modes[0] not in tmodes: modes[0] = tmodes[0] self.store.set(iter, self.col(_("Tone Mode")), modes[0]) def _cm(*cmodes): if modes[0] == "Cross" and modes[1] not in cmodes: modes[1] = cmodes[0] self.store.set(iter, self.col(_("Cross Mode")), modes[1]) if col == self.col(_("DTCS Code")): _tm("DTCS", "Cross") _cm(*tuple([x for x in chirp_common.CROSS_MODES if x.startswith("DTCS->")])) elif col == self.col(_("DTCS Rx Code")): _tm("Cross") _cm(*tuple([x for x in chirp_common.CROSS_MODES if x.endswith("->DTCS")])) elif col == self.col(_("DTCS Pol")): _tm("DTCS", "Cross") _cm(*tuple([x for x in chirp_common.CROSS_MODES if "DTCS" in x])) elif col == self.col(_("Tone")): _tm("Tone", "Cross") _cm(*tuple([x for x in chirp_common.CROSS_MODES if x.startswith("Tone->")])) elif col == self.col(_("ToneSql")): _tm("TSQL", "Cross") _cm(*tuple([x for x in chirp_common.CROSS_MODES if x.endswith("->Tone")])) elif col == self.col(_("Cross Mode")): _tm("Cross") 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" or tmode == "TSQL-R": 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" or tmode == "DTCS-R": 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)" or duplex == "off": 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: LOG.error(_("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, _("Tone"): self.ed_tone_field, _("ToneSql"): self.ed_tone_field, _("DTCS Code"): self.ed_tone_field, _("DTCS Rx Code"): self.ed_tone_field, _("DTCS Pol"): self.ed_tone_field, _("Cross Mode"): self.ed_tone_field, } if cap in funcs: new = funcs[cap](rend, path, new, colnum) if new is None: LOG.error(_("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 LOG.debug("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, all=False): 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, all=all) 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 _copy_field(self, src_memory, dst_memory, field): if field.startswith("extra_"): field = field.split("_", 1)[1] value = src_memory.extra[field].value.get_value() dst_memory.extra[field].value = value else: setattr(dst_memory, field, getattr(src_memory, field)) def _apply_multiple(self, src_memory, fields, locations): for location in locations: def apply_and_set(memory): for field in fields: self._copy_field(src_memory, memory, field) cb = (memory.number == locations[-1] and self._set_memory_cb or None) job = common.RadioJob(cb, "set_memory", memory) job.set_desc(_("Writing memory {number}").format( number=memory.number)) self.rthread.submit(job) job = common.RadioJob(apply_and_set, "get_memory", location) job.set_desc(_("Getting original memory {number}").format( number=location)) self.rthread.submit(job) def edit_memory(self, memory, locations): if len(locations) > 1: dlg = memdetail.MultiMemoryDetailEditor(self._features, memory) else: dlg = memdetail.MemoryDetailEditor(self._features, memory) r = dlg.run() if r == gtk.RESPONSE_OK: self.need_refresh = True mem = dlg.get_memory() if len(locations) > 1: self._apply_multiple(memory, dlg.get_fields(), locations) else: if "name" not in mem.immutable: 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) dlg.destroy() def mh(self, _action, store, paths): action = _action.get_name() selected = [] for path in paths: iter = store.get_iter(path) loc, = store.get(iter, self.col(_("Loc"))) selected.append(loc) cur_pos = selected[0] 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 == "delete_sall": changed = self._delete_rows_and_shift(paths, all=True) 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 == "all": changed = self.select_all() elif action == "devshowraw": self._show_raw(cur_pos) elif action == "devdiffraw": self._diff_raw(paths) elif action == "properties": job = common.RadioJob(self.edit_memory, "get_memory", cur_pos) job.set_cb_args(selected) 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 = [ ("cut", _("Cut")), ("copy", _("Copy")), ("paste", _("Paste")), ("all", _("Select All")), ("insert_prev", _("Insert row above")), ("insert_next", _("Insert row below")), ("deletes", _("Delete")), ("delete", issingle and _("this memory") or _("these memories")), ("delete_s", _("...and shift block up")), ("delete_sall", _("...and shift all memories up")), ("move_up", _("Move up")), ("move_dn", _("Move down")), ("exchange", _("Exchange memories")), ("properties", _("P_roperties")), ("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: pathinfo = view.get_path_at_pos(int(event.x), int(event.y)) if pathinfo is not None: path, col, x, y = pathinfo view.grab_focus() sel = view.get_selection() if (not sel.path_is_selected(path)): view.set_cursor(path, col) 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 self._edit_path = self.view.get_cursor() def cell_editing_stopped(self, *args): self._in_editing = False print 'Would activate %s' % str(self._edit_path) self.view.grab_focus() self.view.set_cursor(*self._edit_path) 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) hbox = gtk.HBox() 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: LOG.error(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 _cap not 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() hbox.pack_start(sw, 1, 1, 1) self.view.connect("button_press_event", self.click_cb) hbox.show() return hbox 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: LOG.debug("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 999 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(_("Refresh")) refresh.set_relief(gtk.RELIEF_NONE) refresh.connect("clicked", lambda x: self.prefill()) refresh.show() 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() hbox.pack_start(sep, 0, 0, 2) showspecial = gtk.ToggleButton(_("Special Channels")) showspecial.set_relief(gtk.RELIEF_NONE) 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.ToggleButton(_("Show Empty")) showempty.set_relief(gtk.RELIEF_NONE) 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) sep = gtk.VSeparator() sep.show() hbox.pack_start(sep, 0, 0, 2) props = gtk.Button(_("Properties")) props.set_relief(gtk.RELIEF_NONE) props.connect("clicked", lambda x: self.hotkey( gtk.Action("properties", "", "", 0))) props.show() hbox.pack_start(props, 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_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] LOG.info("%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): super(MemoryEditor, self).__init__(rthread) self.defaults = dict(self.defaults) self._config = config.get("memedit") self.bandplans = bandplans.BandPlans(config.get()) 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", default=True) 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"] self.choices[_("DTCS Code")] = self._features["valid_dtcs_codes"] self.choices[_("DTCS Rx Code")] = self._features["valid_dtcs_codes"] 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.root = vbox # 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="CLIPBOARD") clipboard.set_text(result) clipboard.store() 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: LOG.error("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="CLIPBOARD") clipboard.request_text(self._paste_selection) def select_all(self): self.view.get_selection().select_all() 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])) def other_editor_changed(self, target_editor): self.need_refresh = True 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 i not in self.choices: 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-daily-20170714/chirp/ui/config.py0000644000016101777760000000734512475535623020751 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, raw=False): 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, raw=raw) 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) def remove_option(self, section, key): self.__config.remove_option(section, key) if not self.__config.items(section): self.__config.remove_section(section) class ChirpConfigProxy: def __init__(self, config, section="global"): self._config = config self._section = section def get(self, key, section=None, raw=False): return self._config.get(key, section or self._section, raw=raw) 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, default=False): val = self.get(key, section) if val is None: return default else: return val == "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) def remove_option(self, key, section): self._config.remove_option(section, key) _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-daily-20170714/chirp/ui/__init__.py0000644000016101777760000000125612475535623021236 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-daily-20170714/chirp/ui/radiobrowser.py0000644000016101777760000002372213067126377022203 0ustar jenkinsnogroup00000000000000import gtk import gobject import pango import re import os import logging from chirp import bitwise from chirp.ui import common, config LOG = logging.getLogger(__name__) CONF = config.get() def do_insert_line_with_tags(b, line): def i(text, *tags): b.insert_with_tags_by_name(b.get_end_iter(), text, *tags) def ident(name): if "unknown" in name: i(name, 'grey', 'bold') else: i(name, 'bold') def nonzero(value): i(value, 'red', 'bold') def foo(value): i(value, 'blue', 'bold') m = re.match("^( *)([A-z0-9_]+: )(0x[A-F0-9]+) \((.*)\)$", line) if m: i(m.group(1)) ident(m.group(2)) if m.group(3) == '0x00': i(m.group(3)) else: nonzero(m.group(3)) i(' (') for char in m.group(4): if char == '1': nonzero(char) else: i(char) i(')') return m = re.match("^( *)([A-z0-9_]+: )(.*)$", line) if m: i(m.group(1)) ident(m.group(2)) i(m.group(3)) return m = re.match("^(.*} )([A-z0-9_]+)( \()([0-9]+)( bytes at )(0x[A-F0-9]+)", line) if m: i(m.group(1)) ident(m.group(2)) i(m.group(3)) foo(m.group(4)) i(m.group(5)) foo(m.group(6)) i(")") return i(line) def do_insert_with_tags(buf, text): buf.set_text('') lines = text.split(os.linesep) for line in lines: do_insert_line_with_tags(buf, line) buf.insert_with_tags_by_name(buf.get_end_iter(), os.linesep) def classname(obj): return str(obj.__class__).split('.')[-1] def bitwise_type(classname): return classname.split("DataElement")[0] class FixedEntry(gtk.Entry): def __init__(self, *args, **kwargs): super(FixedEntry, self).__init__(*args, **kwargs) try: fontsize = CONF.get_int("browser_fontsize", "developer") except Exception: fontsize = 10 if fontsize < 4 or fontsize > 144: LOG.warn("Unsupported browser_fontsize %i. Using 10." % fontsize) fontsize = 11 fontdesc = pango.FontDescription("Courier bold %i" % fontsize) self.modify_font(fontdesc) class IntegerEntry(FixedEntry): def _colorize(self, _self): value = self.get_text() if value.startswith("0x"): value = value[2:] value = value.replace("0", "") if not value: self.modify_text(gtk.STATE_NORMAL, None) else: self.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse('red')) def __init__(self, *args, **kwargs): super(IntegerEntry, self).__init__(*args, **kwargs) self.connect("changed", self._colorize) class BitwiseEditor(gtk.HBox): def __init__(self, element): super(BitwiseEditor, self).__init__(False, 3) self._element = element self._build_ui() class IntegerEditor(BitwiseEditor): def _changed(self, entry, base): if not self._update: return value = entry.get_text() if value.startswith("0x"): value = value[2:] self._element.set_value(int(value, base)) self._update_entries(skip=entry) def _update_entries(self, skip=None): self._update = False for ent, format_spec in self._entries: if ent != skip: ent.set_text(format_spec.format(int(self._element))) self._update = True def _build_ui(self): self._entries = [] self._update = True hexdigits = ((self._element.size() / 4) + (self._element.size() % 4 and 1 or 0)) formats = [('Hex', 16, '0x{:0%iX}' % hexdigits), ('Dec', 10, '{:d}'), ('Bin', 2, '{:0%ib}' % self._element.size())] for name, base, format_spec in formats: lab = gtk.Label(name) self.pack_start(lab, 0, 0, 0) lab.show() int(self._element) ent = IntegerEntry() self._entries.append((ent, format_spec)) ent.connect('changed', self._changed, base) self.pack_start(ent, 0, 0, 0) ent.show() self._update_entries() class BCDArrayEditor(BitwiseEditor): def _changed(self, entry, hexent): self._element.set_value(int(entry.get_text())) self._format_hexent(hexent) def _format_hexent(self, hexent): value = "" for i in self._element: a, b = i.get_value() value += "%i%i" % (a, b) hexent.set_text(value) def _build_ui(self): lab = gtk.Label("Dec") lab.show() self.pack_start(lab, 0, 0, 0) ent = FixedEntry() ent.set_text(str(int(self._element))) ent.show() self.pack_start(ent, 1, 1, 1) lab = gtk.Label("Hex") lab.show() self.pack_start(lab, 0, 0, 0) hexent = FixedEntry() hexent.show() self.pack_start(hexent, 1, 1, 1) hexent.set_editable(False) ent.connect('changed', self._changed, hexent) self._format_hexent(hexent) class CharArrayEditor(BitwiseEditor): def _changed(self, entry): self._element.set_value(entry.get_text().ljust(len(self._element))) def _build_ui(self): ent = FixedEntry(len(self._element)) ent.set_text(str(self._element).rstrip("\x00")) ent.connect('changed', self._changed) ent.show() self.pack_start(ent, 1, 1, 1) class OtherEditor(BitwiseEditor): def _build_ui(self): name = classname(self._element) name = bitwise_type(name) if isinstance(self._element, bitwise.arrayDataElement): name += " %s[%i]" % ( bitwise_type(classname(self._element[0])), len(self._element)) l = gtk.Label(name) l.show() self.pack_start(l, 1, 1, 1) class RadioBrowser(common.Editor): def _build_ui(self): self._display = gtk.Table(20, 2) self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) self._tree = gtk.TreeView(self._store) rend = gtk.CellRendererText() tvc = gtk.TreeViewColumn('Element', rend, text=0) self._tree.append_column(tvc) self._tree.connect('button_press_event', self._tree_click) self._tree.set_size_request(200, -1) self.root = gtk.HBox(False, 3) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self._tree) sw.show() self.root.pack_start(sw, 0, 0, 0) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add_with_viewport(self._display) sw.show() self.root.pack_start(sw, 1, 1, 1) self._tree.show() self._display.show() self.root.show() def _fill(self, name, obj, parent=None): iter = self._store.append(parent, (name, obj)) if isinstance(obj, bitwise.structDataElement): for name, item in obj.items(): if isinstance(item, bitwise.structDataElement): self._fill(name, item, iter) elif isinstance(item, bitwise.arrayDataElement): self._fill("%s[%i]" % (name, len(item)), item, iter) elif isinstance(obj, bitwise.arrayDataElement): i = 0 for item in obj: if isinstance(obj[0], bitwise.structDataElement): self._fill("%s[%i]" % (name, i), item, iter) i += 1 def _tree_click(self, view, event): if event.button != 1: return index = [0] def pack(widget, pos): self._display.attach(widget, pos, pos + 1, index[0], index[0] + 1, xoptions=gtk.FILL, yoptions=0) def next_row(): index[0] += 1 def abandon(child): self._display.remove(child) pathinfo = view.get_path_at_pos(int(event.x), int(event.y)) path = pathinfo[0] iter = self._store.get_iter(path) name, obj = self._store.get(iter, 0, 1) self._display.foreach(abandon) for name, item in obj.items(): if item.size() % 8 == 0: name = '%s (%s %i bytes)' % ( name, bitwise_type(classname(item)), item.size() / 8) else: name = '%s (%s %i bits)' % ( name, bitwise_type(classname(item)), item.size()) l = gtk.Label(name + " ") l.set_use_markup(True) l.show() pack(l, 0) if (isinstance(item, bitwise.intDataElement) or isinstance(item, bitwise.bcdDataElement)): e = IntegerEditor(item) elif (isinstance(item, bitwise.arrayDataElement) and isinstance(item[0], bitwise.bcdDataElement)): e = BCDArrayEditor(item) elif (isinstance(item, bitwise.arrayDataElement) and isinstance(item[0], bitwise.charDataElement)): e = CharArrayEditor(item) else: e = OtherEditor(item) e.show() pack(e, 1) next_row() def __init__(self, rthread): super(RadioBrowser, self).__init__(rthread) self._radio = rthread.radio self._focused = False self._build_ui() self._fill('root', self._radio._memobj) def focus(self): self._focused = True def unfocus(self): if self._focused: self.emit("changed") self._focused = False if __name__ == "__main__": from chirp.drivers import * from chirp import directory import sys r = directory.get_radio_by_image(sys.argv[1]) class Foo: radio = r w = gtk.Window() b = RadioBrowser(Foo) w.set_default_size(1024, 768) w.add(b.root) w.show() gtk.main() chirp-daily-20170714/chirp/ui/miscwidgets.py0000644000016101777760000005166212475535623022027 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 os import logging from chirp import platform LOG = logging.getLogger(__name__) 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: LOG.error("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: LOG.error("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: LOG.error("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: LOG.error("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) 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-daily-20170714/chirp/ui/memdetail.py0000644000016101777760000003437212475535623021445 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 import logging from chirp import chirp_common, settings from chirp.ui import miscwidgets, common LOG = logging.getLogger(__name__) 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 set_sensitive(self, sensitive): self._widget.set_sensitive(sensitive) 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, text, colindex=0): label = gtk.Label(text + ":") label.set_alignment(0.0, 0.5) label.show() tab.attach(label, colindex, colindex + 1, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=6, ypadding=3) widget = editor.get_widget() widget.show() tab.attach(widget, colindex + 1, colindex + 2, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3) img = gtk.Image() img.set_size_request(16, -1) img.show() tab.attach(img, colindex + 2, colindex + 3, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3) self._editors[name] = label, editor, img return label, editor, img def _set_doc(self, name, doc): label, editor, _img = self._editors[name] self._tips.set_tip(label, doc) def _make_ui(self): box = gtk.VBox() box.show() notebook = gtk.Notebook() notebook.set_show_border(False) notebook.show() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.show() hbox = gtk.HBox() hbox.pack_start(sw, 1, 1, 1) hbox.show() tab = notebook.append_page(hbox, gtk.Label(_("General"))) table = gtk.Table(len(self._order), 4, False) table.set_resize_mode(gtk.RESIZE_IMMEDIATE) table.show() sw.add_with_viewport(table) def _err(name, msg): try: _img = self._editors[name][2] except KeyError: LOG.error(self._editors.keys()) if msg is None: _img.clear() self._tips.set_tip(_img, "") else: _img.set_from_stock(gtk.STOCK_DIALOG_WARNING, 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) row = 0 for name in self._order: text, editorcls, data = self._elements[name] editor = editorcls(self._features, self._memory, _err, name, data) self._add(table, row, name, editor, text) self._set_doc(name, text) row += 1 if len(self._memory.extra): sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.show() hbox = gtk.HBox() hbox.pack_start(sw, 1, 1, 1) hbox.show() tab = notebook.append_page(hbox, gtk.Label(_("Other"))) table = gtk.Table(len(self._memory.extra), 4, False) table.set_resize_mode(gtk.RESIZE_IMMEDIATE) table.show() sw.add_with_viewport(table) 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(table, 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(table, row, name, editor, setting.get_shortname()) self._set_doc(name, setting.__doc__) row += 1 self._order.append(name) self.vbox.pack_start(notebook, 1, 1, 1) def __init__(self, features, memory, parent=None): self._memory = memory gtk.Dialog.__init__(self, title="Memory Properties", flags=gtk.DIALOG_MODAL, parent=parent, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self.set_size_request(-1, 500) self._tips = gtk.Tooltips() self._features = features 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), "rx_dtcs": (_("RX 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), "power": (_("Power"), PowerChoiceEditor, features.valid_power_levels), "comment": (_("Comment"), StringEditor, 256), } self._order = [ "freq", "name", "tmode", "rtone", "ctone", "cross_mode", "dtcs", "rx_dtcs", "dtcs_polarity", "duplex", "offset", "mode", "tuning_step", "skip", "power", "comment" ] hide_rules = [ ("name", features.has_name), ("tmode", len(features.valid_tmodes) > 0), ("ctone", features.has_ctone), ("dtcs", features.has_dtcs), ("rx_dtcs", features.has_rx_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), ("power", features.valid_power_levels), ("comment", features.has_comment), ] for name, visible in hide_rules: if not visible: del self._elements[name] self._order.remove(name) self._make_ui() 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 class MultiMemoryDetailEditor(MemoryDetailEditor): def __init__(self, features, memory, parent=None): self._selections = dict() super(MultiMemoryDetailEditor, self).__init__(features, memory, parent) def _toggle_selector(self, selector, *widgets): for widget in widgets: widget.set_sensitive(selector.get_active()) def _add(self, tab, row, name, editor, text): label, editor, img = super(MultiMemoryDetailEditor, self)._add( tab, row, name, editor, text, 1) selector = gtk.CheckButton() tab.attach(selector, 0, 1, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=0, ypadding=3) selector.show() self._toggle_selector(selector, label, editor, img) selector.connect("toggled", self._toggle_selector, label, editor, img) self._selections[name] = selector def get_fields(self): return [k for k, v in self._selections.items() if v.get_active()] chirp-daily-20170714/chirp/ui/importdialog.py0000644000016101777760000005366712475535623022206 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 logging from chirp import errors, chirp_common, import_logic from chirp.drivers import generic_xml from chirp.ui import common LOG = logging.getLogger(__name__) 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: LOG.debug("Adding %s to ucall list" % mem.dv_urcall) ulist.append(mem.dv_urcall) ulist_changed = True if mem.dv_rpt1call not in rlist: LOG.debug("Adding %s to rcall list" % mem.dv_rpt1call) rlist.append(mem.dv_rpt1call) rlist_changed = True if mem.dv_rpt2call not in rlist: LOG.debug("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: LOG.error("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): LOG.warn("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 LOG.debug("%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: LOG.error(e) error_messages[new] = str(e) continue job = common.RadioJob(None, "set_memory", mem) desc = _("Setting memory {number}").format(number=mem.number) job.set_desc(desc) 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(): LOG.debug("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: LOG.error("Location %i empty or at limit of destination radio" % number) except errors.InvalidDataError, e: LOG.error("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 chirp.ui 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-daily-20170714/chirp/ui/mainapp.py0000644000016101777760000022660313046277310021120 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 . from datetime import datetime import os import tempfile import urllib import webbrowser from glob import glob import shutil import time import logging import gtk import gobject import sys from chirp.ui import inputdialog, common from chirp import platform, directory, util from chirp.drivers import generic_xml, generic_csv, repeaterbook from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6 from chirp.drivers import icf, ic9x_icf from chirp import CHIRP_VERSION, chirp_common, detect, errors from chirp.ui import editorset, clone, miscwidgets, config, reporting, fips from chirp.ui import bandplans gobject.threads_init() LOG = logging.getLogger(__name__) if __name__ == "__main__": sys.path.insert(0, "..") try: import serial except ImportError, e: common.log_exception() common.show_error("\nThe Pyserial module is not installed!") 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", "all", "properties"]: 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.startswith(_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) label = gtk.Label("") label.set_markup("-1 for either Mem # does a full-file hex " + "dump with diffs highlighted.\n" + "-2 for first Mem # shows " + "only the diffs.") d.vbox.pack_start(label, True, True, 0) label.show() 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, -2, 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 LOG.debug("Selected %s@%i and %s@%i" % (sel_a, sel_chan_a, sel_b, sel_chan_b)) name_a = os.path.basename(sel_a) name_a = name_a[:name_a.rindex(")")] name_b = os.path.basename(sel_b) name_b = name_b[:name_b.rindex(")")] diffwintitle = "%s@%i diff %s@%i" % ( name_a, sel_chan_a, name_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(diffwintitle, 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) try: addrfmt = CONF.get('hexdump_addrfmt', section='developer', raw=True) except: pass a = util.hexprint(eset_a.rthread.radio._mmap.get_packed(), addrfmt=addrfmt) b = util.hexprint(eset_b.rthread.radio._mmap.get_packed(), addrfmt=addrfmt) if sel_chan_a == -2: diffsonly = True else: diffsonly = False common.show_diff_blob(diffwintitle, common.simple_diff(a, b, diffsonly)) 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 = [(_("All files") + " (*.*)", "*"), (_("CHIRP Radio Images") + " (*.img)", "*.img"), (_("CHIRP Files") + " (*.chirp)", "*.chirp"), (_("CSV Files") + " (*.csv)", "*.csv"), (_("DAT Files") + " (*.dat)", "*.dat"), (_("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) else: try: radio = directory.get_radio_by_image(fname) except errors.ImageDetectFailed: radio = self._do_manual_select(fname) if not radio: return LOG.debug("Manually selected %s" % radio) except Exception, e: common.log_exception() common.show_error(os.path.basename(fname) + ": " + str(e)) return first_tab = False try: eset = editorset.EditorSet(radio, 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=e)) return eset.set_read_only(read_only) self._connect_editorset(eset) eset.show() self.tabs.append_page(eset, eset.get_tab_label()) 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.") msg = msg.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): eset = editorset.EditorSet(radio, self, tempname=tempname) eset.connect("want-close", self.do_close) eset.connect("status", self.ev_status) eset.set_read_only(read_only) eset.show() self.tabs.append_page(eset, eset.get_tab_label()) 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) defname_format = CONF.get("default_filename", "global") or \ "{vendor}_{model}_{date}" defname = defname_format.format( vendor=eset.radio.VENDOR, model=eset.radio.MODEL, date=datetime.now().strftime('%Y%m%d') ).replace('/', '_') 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(default_name=defname, 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): basepath = platform.get_platform().find_resource("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))): LOG.info("Skipping existing stock config") continue try: shutil.copy(fn, stock_dir) LOG.debug("Copying %s -> %s" % (fn, stock_dir)) except Exception, e: LOG.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: LOG.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_prompts().experimental 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 _show_instructions(self, radio, message): if message is None: return if CONF.get_bool("clone_instructions", "noconfirm"): return d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) d.set_markup("" + _("{name} Instructions").format( name=radio.get_name()) + "") msg = _("{instructions}").format(instructions=message) d.format_secondary_markup(msg) again = gtk.CheckButton( _("Don't show instructions for any radio again")) again.show() again.connect("toggled", lambda action: self.clonemenu.set_active(not action.get_active())) d.vbox.pack_start(again, 0, 0, 0) h_button_box = d.vbox.get_children()[2] try: ok_button = h_button_box.get_children()[0] ok_button.grab_default() ok_button.grab_focus() except AttributeError: # don't grab focus on GTK+ 2.0 pass d.run() d.destroy() 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 self._show_instructions(rclass, rclass.get_prompts().pre_download) LOG.debug("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 prompts = radio.get_prompts() if prompts.display_pre_upload_prompt_before_opening_port is True: LOG.debug("Opening port after pre_upload prompt.") self._show_instructions(radio, prompts.pre_upload) 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 if prompts.display_pre_upload_prompt_before_opening_port is False: LOG.debug("Opening port before pre_upload prompt.") self._show_instructions(radio, prompts.pre_upload) 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_NO: 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 = [(_("All files") + " (*.*)", "*"), (_("CHIRP Files") + " (*.chirp)", "*.chirp"), (_("CHIRP Radio Images") + " (*.img)", "*.img"), (_("CSV Files") + " (*.csv)", "*.csv"), (_("DAT Files") + " (*.dat)", "*.dat"), (_("EVE Files (VX5)") + " (*.eve)", "*.eve"), (_("ICF Files") + " (*.icf)", "*.icf"), (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"), (_("Kenwood ITM Files") + " (*.itm)", "*.itm"), (_("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_dmrmarc_prompt(self): fields = {"1City": (gtk.Entry(), lambda x: x), "2State": (gtk.Entry(), lambda x: x), "3Country": (gtk.Entry(), lambda x: x), } d = inputdialog.FieldDialog(title=_("DMR-MARC Repeater Database Dump"), 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:], "dmrmarc") or "") while d.run() == gtk.RESPONSE_OK: for k in sorted(fields.keys()): widget, validator = fields[k] try: if validator(widget.get_text()): CONF.set(k[1:], widget.get_text(), "dmrmarc") continue except Exception: pass d.destroy() return True d.destroy() return False def do_dmrmarc(self, do_import): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if not self.do_dmrmarc_prompt(): self.window.set_cursor(None) return city = CONF.get("city", "dmrmarc") state = CONF.get("state", "dmrmarc") country = CONF.get("country", "dmrmarc") # 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() dmrmarcstr = "dmrmarc://%s/%s/%s" % (city, state, country) eset.do_import(dmrmarcstr) else: try: from chirp import dmrmarc radio = dmrmarc.DMRMARCRadio(None) radio.set_params(city, state, country) self.do_open_live(radio, read_only=True) except errors.RadioError, e: common.show_error(e) self.window.set_cursor(None) def do_repeaterbook_political_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") items = fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items() for k, v in 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_political(self, do_import): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if not self.do_repeaterbook_political_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 "%%") print query # 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): LOG.error("Failed, headers were: %s", headers) common.show_error(_("RepeaterBook query failed")) self.window.set_cursor(None) return try: # Validate CSV radio = repeaterbook.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_repeaterbook_proximity_prompt(self): default_band = "--All--" try: code = int(CONF.get("band", "repeaterbook")) for k, v in RB_BANDS.items(): if code == v: default_band = k break except: pass fields = {"1Location": (gtk.Entry(), lambda x: x.get_text()), "2Distance": (gtk.Entry(), lambda x: x.get_text()), "3Band": (miscwidgets.make_choice( sorted(RB_BANDS.keys(), key=key_bands), False, default_band), lambda x: RB_BANDS[x.get_active_text()]), } d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self) for k in sorted(fields.keys()): d.add_field(k[1:], fields[k][0]) if isinstance(fields[k][0], gtk.Entry): fields[k][0].set_text( CONF.get(k[1:].lower(), "repeaterbook") or "") while d.run() == gtk.RESPONSE_OK: valid = True for k, (widget, fn) in fields.items(): try: CONF.set(k[1:].lower(), str(fn(widget)), "repeaterbook") continue except: 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_repeaterbook_proximity(self, do_import): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if not self.do_repeaterbook_proximity_prompt(): self.window.set_cursor(None) return loc = CONF.get("location", "repeaterbook") try: dist = int(CONF.get("distance", "repeaterbook")) except: dist = 20 try: band = int(CONF.get("band", "repeaterbook")) or '%' band = str(band) except: band = '%' query = "https://www.repeaterbook.com/repeaters/downloads/CHIRP/" \ "app_direct.php?loc=%s&band=%s&dist=%s" % (loc, band, dist) print query # 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): LOG.error("Failed, headers were: %s", headers) common.show_error(_("RepeaterBook query failed")) self.window.set_cursor(None) return try: # Validate CSV radio = repeaterbook.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_przemienniki_prompt(self): d = inputdialog.FieldDialog(title='przemienniki.net query', parent=self) fields = { "Country": (miscwidgets.make_choice( ['at', 'bg', 'by', 'ch', 'cz', 'de', 'dk', 'es', 'fi', 'fr', 'hu', 'it', 'lt', 'lv', 'no', 'pl', 'ro', 'se', 'sk', 'ua', 'uk'], False), lambda x: str(x.get_active_text())), "Band": (miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm', '23cm', '13cm', '3cm'], False, '2m'), lambda x: str(x.get_active_text())), "Mode": (miscwidgets.make_choice(['fm', 'dv'], False), lambda x: str(x.get_active_text())), "Only Working": (miscwidgets.make_choice(['', 'yes'], False), lambda x: str(x.get_active_text())), "Latitude": (gtk.Entry(), lambda x: float(x.get_text())), "Longitude": (gtk.Entry(), lambda x: float(x.get_text())), "Range": (gtk.Entry(), lambda x: int(x.get_text())), } for name in sorted(fields.keys()): value, fn = fields[name] d.add_field(name, value) while d.run() == gtk.RESPONSE_OK: query = "http://przemienniki.net/export/chirp.csv?" args = [] for name, (value, fn) in fields.items(): if isinstance(value, gtk.Entry): contents = value.get_text() else: contents = value.get_active_text() if contents: try: _value = fn(value) except ValueError: common.show_error(_("Invalid value for %s") % name) query = None continue args.append("=".join((name.replace(" ", "").lower(), contents))) query += "&".join(args) LOG.debug(query) d.destroy() return query d.destroy() return query def do_przemienniki(self, do_import): url = self.do_przemienniki_prompt() if not url: return fn = tempfile.mktemp(".csv") filename, headers = urllib.urlretrieve(url, fn) if not os.path.exists(filename): LOG.error("Failed, headers were: %s", str(headers)) common.show_error(_("Query failed")) return class PRRadio(generic_csv.CSVRadio, chirp_common.NetworkSourceRadio): VENDOR = "przemienniki.net" MODEL = "" try: radio = PRRadio(filename) except Exception, e: common.show_error(str(e)) return 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() rfstr = "rfinder://%s/%s/%f/%f/%i" % \ (email, passwd, lat, lon, miles) count = eset.do_import(rfstr) else: from chirp.drivers 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() rrstr = "radioreference://%s/%s/%s" % (zipcode, username, passwd) count = eset.do_import(rrstr) 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"), ] 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]) # Set url hook to handle user activating a URL link in the about dialog gtk.about_dialog_set_url_hook(lambda dlg, url: webbrowser.open(url)) d.set_name("CHIRP") d.set_version(CHIRP_VERSION) d.set_copyright("Copyright 2015 Dan Smith (KK7DS)") d.set_website("http://chirp.danplanet.com") d.set_authors(("Dan Smith KK7DS ", _("With significant contributions from:"), "Tom KD7LXL", "Marco IZ3GME", "Jim KC9HI" )) d.set_translator_credits("Polish: Grzegorz SQ2RBY" + os.linesep + "Italian: Fabio IZ2QDH" + os.linesep + "Dutch: Michael PD4MT" + os.linesep + "German: Benjamin HB9EUK" + os.linesep + "Hungarian: Attila HA5JA" + os.linesep + "Russian: Dmitry Slukin" + os.linesep + "Portuguese (BR): Crezivando PP7CJ") d.set_comments(verinfo) d.run() d.destroy() def do_gethelp(self): webbrowser.open("http://chirp.danplanet.com") 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) labelstr = _("Visible columns for {radio}").format(radio=radio_name) label = gtk.Label(labelstr) label.show() vbox.pack_start(label) fields = [] memedit = eset.get_current_editor() # .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: for editortype, editor in eset.editors.iteritems(): if "memedit" in editortype: editor.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) markup = "" + _("Reporting is disabled") + "" d.set_markup(markup) 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\n" "Are 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_no_smart_tmode(self, action): CONF.set_bool("no_smart_tmode", not 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_toggle_clone_instructions(self, action): CONF.set_bool("clone_instructions", not action.get_active(), "noconfirm") def do_change_language(self): langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German", "Hungarian", "Russian", "Portuguese (BR)", "French", "Spanish"] 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: LOG.debug("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 ["qdmrmarc", "idmrmarc"]: self.do_dmrmarc(action[0] == "i") 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 ["qrbookpolitical", "irbookpolitical"]: self.do_repeaterbook_political(action[0] == "i") elif action in ["qrbookproximity", "irbookproximity"]: self.do_repeaterbook_proximity(action[0] == "i") elif action in ["qpr", "ipr"]: self.do_przemienniki(action[0] == "i") elif action == "about": self.do_about() elif action == "gethelp": self.do_gethelp() 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 == "channel_defaults": # The memedit thread also has an instance of bandplans. bp = bandplans.BandPlans(CONF) bp.select_bandplan(self) elif action == "no_smart_tmode": self.do_toggle_no_smart_tmode(_action) elif action == "developer": self.do_toggle_developer(_action) elif action == "clone_instructions": self.do_toggle_clone_instructions(_action) elif action in ["cut", "copy", "paste", "delete", "move_up", "move_dn", "exchange", "all", "devshowraw", "devdiffraw", "properties"]: 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 = """ """ ALT_KEY = "" CTRL_KEY = "" if sys.platform == 'darwin': ALT_KEY = "" CTRL_KEY = "" 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"), "%sx" % CTRL_KEY, None, self.mh), ('copy', None, _("_Copy"), "%sc" % CTRL_KEY, None, self.mh), ('paste', None, _("_Paste"), "%sv" % CTRL_KEY, None, self.mh), ('delete', None, _("_Delete"), "Delete", None, self.mh), ('all', None, _("Select _All"), None, None, self.mh), ('move_up', None, _("Move _Up"), "%sUp" % CTRL_KEY, None, self.mh), ('move_dn', None, _("Move Dow_n"), "%sDown" % CTRL_KEY, None, self.mh), ('exchange', None, _("E_xchange"), "%sx" % CTRL_KEY, None, self.mh), ('properties', None, _("P_roperties"), None, 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'), "%sr" % CTRL_KEY, None, self.mh), ('devdiffraw', None, _("Diff raw memories"), "%sd" % CTRL_KEY, None, self.mh), ('devdifftab', None, _("Diff tabs"), "%st" % CTRL_KEY, None, self.mh), ('language', None, _("Change language"), None, None, self.mh), ('radio', None, _("_Radio"), None, None, self.mh), ('download', None, _("Download From Radio"), "%sd" % ALT_KEY, None, self.mh), ('upload', None, _("Upload To Radio"), "%su" % ALT_KEY, None, self.mh), ('import', None, _("Import"), "%si" % ALT_KEY, None, self.mh), ('export', None, _("Export"), "%se" % ALT_KEY, None, self.mh), ('importsrc', None, _("Import from data source"), None, None, self.mh), ('idmrmarc', None, _("DMR-MARC Repeaters"), 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), ('irbookpolitical', None, _("RepeaterBook political query"), None, None, self.mh), ('irbookproximity', None, _("RepeaterBook proximity query"), None, None, self.mh), ('ipr', None, _("przemienniki.net"), None, None, self.mh), ('querysrc', None, _("Query data source"), None, None, self.mh), ('qdmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh), ('qradioreference', None, _("RadioReference.com"), None, None, self.mh), ('qrfinder', None, _("RFinder"), None, None, self.mh), ('qpr', None, _("przemienniki.net"), None, None, self.mh), ('qrbook', None, _("RepeaterBook"), None, None, self.mh), ('qrbookpolitical', None, _("RepeaterBook political query"), None, None, self.mh), ('qrbookproximity', None, _("RepeaterBook proximity query"), 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), ('channel_defaults', None, _("Channel defaults"), 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), ('gethelp', None, _("Get Help Online..."), None, None, self.mh), ] conf = config.get() re = not conf.get_bool("no_report") hu = conf.get_bool("hide_unused", "memedit", default=True) dv = conf.get_bool("developer", "state") ci = not conf.get_bool("clone_instructions", "noconfirm") st = not conf.get_bool("no_smart_tmode", "memedit") toggles = [('report', None, _("Report Statistics"), None, None, self.mh, re), ('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu), ('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st), ('clone_instructions', None, _("Show Instructions"), None, None, self.mh, ci), ('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.clonemenu = self.menu_uim.get_widget( "/MenuBar/help/clone_instructions") # 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() self.tabs.set_scrollable(True) return self.tabs def close_out(self): num = self.tabs.get_n_pages() while num > 0: num -= 1 LOG.debug("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() def memedit(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): this_platform = platform.get_platform() path = (this_platform.find_resource("chirp.png") or this_platform.find_resource(os.path.join("pixmaps", "chirp.png"))) if os.path.exists(path): self.set_icon_from_file(path) else: LOG.warn("Icon %s not found" % path) def _updates(self, version): if not version: return if version == CHIRP_VERSION: return LOG.info("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): macapp = None # for KK7DS runtime <= R10 try: import gtk_osxapplication macapp = gtk_osxapplication.OSXApplication() except ImportError: pass # for gtk-mac-integration >= 2.0.7 try: import gtkosx_application macapp = gtkosx_application.Application() except ImportError: pass if macapp is None: LOG.error("No MacOS support: %s" % e) return this_platform = platform.get_platform() icon = (this_platform.find_resource("chirp.png") or this_platform.find_resource(os.path.join("pixmaps", "chirp.png"))) if os.path.exists(icon): icon_pixmap = gtk.gdk.pixbuf_new_from_file(icon) macapp.set_dock_icon_pixbuf(icon_pixmap) 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) documentationitem = self.menu_uim.get_widget("/MenuBar/help/gethelp") macapp.insert_app_menu_item(documentationitem, 0) macapp.set_use_quartz_accelerators(False) macapp.ready() LOG.debug("Initialized MacOS support") def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) def expose(window, event): allocation = window.get_allocation() CONF.set_int("window_w", allocation.width, "state") CONF.set_int("window_h", allocation.height, "state") self.connect("expose_event", expose) def state_change(window, event): CONF.set_bool( "window_maximized", event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED, "state") self.connect("window-state-event", state_change) 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) try: width = CONF.get_int("window_w", "state") height = CONF.get_int("window_h", "state") except Exception: width = 800 height = 600 self.set_default_size(width, height) if CONF.get_bool("window_maximized", "state"): self.maximize() 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) 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-daily-20170714/chirp/ui/reporting.py0000644000016101777760000001321012475535623021501 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 import logging from chirp import CHIRP_VERSION, platform REPORT_URL = "http://chirp.danplanet.com/report/report.php?do_report" ENABLED = True THREAD_SEM = threading.Semaphore(10) # Maximum number of outstanding threads LAST = 0 LAST_TYPE = None LOG = logging.getLogger(__name__) try: # Don't let failure to import any of these modules cause trouble from chirp.ui import config import xmlrpclib except: ENABLED = False def should_report(): if not ENABLED: LOG.info("Not reporting due to recent failure") return False conf = config.get() if conf.get_bool("no_report"): LOG.info("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"]: LOG.warn("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) LOG.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 LOG.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 LOG.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): LOG.debug("Checking for updates") proxy = xmlrpclib.ServerProxy(REPORT_URL) ver = proxy.check_for_updates(CHIRP_VERSION, platform.get_platform().os_version_string()) LOG.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: LOG.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 LOG.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(): LOG.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: LOG.debug("Throttling...") return LAST = time.time() LAST_TYPE = func # If there are already too many threads running, bail if not THREAD_SEM.acquire(False): LOG.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-daily-20170714/chirp/ui/bankedit.py0000644000016101777760000003523712475535623021266 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 import logging from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN from chirp import chirp_common from chirp.ui import common, miscwidgets LOG = logging.getLogger(__name__) class MappingNamesJob(common.RadioJob): def __init__(self, model, editor, cb): common.RadioJob.__init__(self, cb, None) self.__model = model self.__editor = editor def execute(self, radio): self.__editor.mappings = [] mappings = self.__model.get_mappings() for mapping in mappings: self.__editor.mappings.append((mapping, mapping.get_name())) gobject.idle_add(self.cb, *self.cb_args) class MappingNameEditor(common.Editor): def refresh(self): def got_mappings(): self._keys = [] for mapping, name in self.mappings: self._keys.append(mapping.get_index()) self.listw.set_item(mapping.get_index(), mapping.get_index(), name) self.listw.connect("item-set", self.mapping_changed) job = MappingNamesJob(self._model, self, got_mappings) job.set_desc(_("Retrieving %s information") % self._type) self.rthread.submit(job) def get_mapping_list(self): mappings = [] keys = self.listw.get_keys() for key in keys: mappings.append(self.listw.get_item(key)[2]) return mappings def mapping_changed(self, listw, key): def cb(*args): self.emit("changed") name = self.listw.get_item(key)[2] mapping, oldname = self.mappings[self._keys.index(key)] def trigger_changed(*args): self.emit("changed") job = common.RadioJob(trigger_changed, "set_name", name) job.set_target(mapping) job.set_desc(_("Setting name on %s") % self._type.lower()) self.rthread.submit(job) return True def __init__(self, rthread, model): super(MappingNameEditor, self).__init__(rthread) self._model = model self._type = common.unpluralize(model.get_name()) types = [(gobject.TYPE_STRING, "key"), (gobject.TYPE_STRING, self._type), (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.mappings = [] 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 def other_editor_changed(self, target_editor): self._loaded = False if self.is_focused(): self.refresh_all_memories() def mappings_changed(self): pass class MemoryMappingsJob(common.RadioJob): def __init__(self, model, cb, number): common.RadioJob.__init__(self, cb, None) self.__model = model self.__number = number def execute(self, radio): mem = radio.get_memory(self.__number) if mem.empty: mappings = [] indexes = [] else: mappings = self.__model.get_memory_mappings(mem) indexes = [] if isinstance(self.__model, chirp_common.MappingModelIndexInterface): for mapping in mappings: indexes.append(self.__model.get_memory_index(mem, mapping)) self.cb(mem, mappings, indexes, *self.cb_args) class MappingMembershipEditor(common.Editor): def _number_to_path(self, number): return (number - self._rf.memory_bounds[0],) def _get_next_mapping_index(self, mapping): # NB: Only works for one-to-one models right now! iter = self._store.get_iter_first() indexes = [] ncols = len(self._cols) + len(self.mappings) 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] mappings = vals[self.C_MAPPINGS:] if True in mappings and mappings.index(True) == mapping: indexes.append(index) iter = self._store.iter_next(iter) index_bounds = self._model.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 mapping 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 mapping index is the column number, minus the 3 label columns mapping, name = self.mappings[colnum - len(self._cols)] loc, = self._store.get(self._store.get_iter(path), self.C_LOC) is_indexed = isinstance(self._model, chirp_common.MappingModelIndexInterface) if rend.get_active(): # Changing from True to False fn = "remove_memory_from_mapping" index = None else: # Changing from False to True fn = "add_memory_to_mapping" if is_indexed: index = self._get_next_mapping_index(colnum - len(self._cols)) else: index = None def do_refresh_memory(*args): # Step 2: Update our notion of the memory's mapping information self.refresh_memory(loc) def do_mapping_index(result, memory): if isinstance(result, Exception): common.show_error("Failed to add {mem} to mapping: {err}" .format(mem=memory.number, err=str(result)), parent=self.editorset.parent_window) return self.emit("changed") # Step 3: Set the memory's mapping index (maybe) if not is_indexed or index is None: return do_refresh_memory() job = common.RadioJob(do_refresh_memory, "set_memory_index", memory, mapping, index) job.set_target(self._model) job.set_desc(_("Updating {type} index " "for memory {num}").format(type=self._type, num=memory.number)) self.rthread.submit(job) def do_mapping_adjustment(memory): # Step 1: Do the mapping add/remove job = common.RadioJob(do_mapping_index, fn, memory, mapping) job.set_target(self._model) job.set_cb_args(memory) job.set_desc(_("Updating mapping information " "for memory {num}").format(num=memory.number)) self.rthread.submit(job) # Step 0: Fetch the memory job = common.RadioJob(do_mapping_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(mappings, memory): self.emit("changed") # Step 2: Set the index job = common.RadioJob(refresh_memory, "set_memory_index", memory, mappings[0], int(new)) job.set_target(self._model) job.set_desc(_("Setting index " "for memory {num}").format(num=memory.number)) self.rthread.submit(job) def get_mapping(memory): # Step 1: Get the first/only mapping job = common.RadioJob(set_index, "get_memory_mappings", memory) job.set_cb_args(memory) job.set_target(self._model) job.set_desc(_("Getting {type} for " "memory {num}").format(type=self._type, num=memory.number)) self.rthread.submit(job) # Step 0: Get the memory job = common.RadioJob(get_mapping, "get_memory", loc) job.set_desc(_("Getting memory {num}").format(num=loc)) self.rthread.submit(job) def __init__(self, rthread, editorset, model): super(MappingMembershipEditor, self).__init__(rthread) self.editorset = editorset self._rf = rthread.radio.get_features() self._model = model self._type = common.unpluralize(model.get_name()) 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_MAPPINGS = 5 # and beyond cols = list(self._cols) self._index_cache = [] for i in range(0, self._model.get_num_mappings()): label = "%s %i" % (self._type, (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) is_indexed = isinstance(self._model, chirp_common.MappingModelIndexInterface) 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(is_indexed) 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() (min, max) = self._rf.memory_bounds for i in range(min, max+1): 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, mappings, 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.mappings)): row.append(i + len(self._cols)) row.append(self.mappings[i][0] in mappings) self._store.set(iter, *tuple(row)) job = MemoryMappingsJob(self._model, got_mem, number) job.set_desc(_("Getting {type} information " "for memory {num}").format(type=self._type, num=number)) self.rthread.submit(job) def refresh_all_memories(self): start = time.time() (min, max) = self._rf.memory_bounds for i in range(min, max+1): self.refresh_memory(i) LOG.debug("Got all %s info in %s" % (self._type, (time.time() - start))) def refresh_mappings(self, and_memories=False): def got_mappings(): for i in range(len(self._cols) - len(self._view_cols) - 1, len(self.mappings)): col = self._view.get_column(i + len(self._view_cols)) mapping, name = self.mappings[i] if name: col.set_title(name) else: col.set_title("(%s)" % i) if and_memories: self.refresh_all_memories() job = MappingNamesJob(self._model, self, got_mappings) job.set_desc(_("Getting %s information") % self._type) self.rthread.submit(job) def focus(self): common.Editor.focus(self) if self._loaded: return self.refresh_mappings(True) self._loaded = True def other_editor_changed(self, target_editor): self._loaded = False if self.is_focused(): self.refresh_all_memories() def mappings_changed(self): self.refresh_mappings() chirp-daily-20170714/chirp/ui/clone.py0000644000016101777760000002134512730176377020601 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 collections import threading import logging import os import gtk import gobject from chirp import platform, directory, detect, chirp_common from chirp.ui import miscwidgets, cloneprog, inputdialog, common, config LOG = logging.getLogger(__name__) 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] else: port = "" if port not 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 = collections.defaultdict(list) for rclass in sorted(directory.DRV_TO_RADIO.values()): if not issubclass(rclass, chirp_common.CloneModeRadio) and \ not issubclass(rclass, chirp_common.LiveRadio): continue vendors[rclass.VENDOR].append(rclass) for alias in rclass.ALIASES: vendors[alias.VENDOR].append(alias) 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 alias_match = None for alias in rclass.ALIASES: if alias.MODEL == model: alias_match = rclass break if alias_match: cs.radio_class = rclass LOG.debug( 'Chose %s alias for %s because model %s selected' % ( alias_match, cs.radio_class, model)) break if not cs.radio_class: common.show_error( _("Internal error: Unable to upload to {model}").format( model=model)) LOG.info(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): LOG.debug("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() LOG.error(_("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() LOG.debug("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-daily-20170714/chirp/ui/bandplans.py0000644000016101777760000001140312475535623021434 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import gtk import logging from chirp import bandplan, bandplan_na, bandplan_au from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3 from chirp.ui import inputdialog LOG = logging.getLogger(__name__) class BandPlans(object): def __init__(self, config): self._config = config self.plans = {} # Migrate old "automatic repeater offset" setting to # "North American Amateur Band Plan" ro = self._config.get("autorpt", "memedit") if ro is not None: self._config.set_bool("north_america", ro, "bandplan") self._config.remove_option("autorpt", "memedit") # And default new users to North America. if not self._config.is_defined("north_america", "bandplan"): self._config.set_bool("north_america", True, "bandplan") for plan in (bandplan_na, bandplan_au, bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3): name = plan.DESC.get("name", plan.SHORTNAME) self.plans[plan.SHORTNAME] = (name, plan) rpt_inputs = [] for band in plan.BANDS: # Check for duplicates. duplicates = [x for x in plan.BANDS if x == band] if len(duplicates) > 1: LOG.warn("Bandplan %s has duplicates %s" % (name, duplicates)) # Add repeater inputs. rpt_input = band.inverse() if rpt_input not in plan.BANDS: rpt_inputs.append(band.inverse()) plan.bands = list(plan.BANDS) plan.bands.extend(rpt_inputs) def get_defaults_for_frequency(self, freq): freq = int(freq) result = bandplan.Band((freq, freq), repr(freq)) for shortname, details in self.plans.items(): if self._config.get_bool(shortname, "bandplan"): matches = [x for x in details[1].bands if x.contains(result)] # Add matches to defaults, favoring more specific matches. matches = sorted(matches, key=lambda x: x.width(), reverse=True) for match in matches: result.mode = match.mode or result.mode result.step_khz = match.step_khz or result.step_khz result.offset = match.offset or result.offset result.duplex = match.duplex or result.duplex result.tones = match.tones or result.tones if match.name: result.name = '/'.join((result.name or '', match.name)) # Limit ourselves to one band plan match for simplicity. # Note that if the user selects multiple band plans by editing # the config file it will work as expected (except where plans # conflict). if matches: break return result def select_bandplan(self, parent_window): plans = ["None"] for shortname, details in self.plans.iteritems(): if self._config.get_bool(shortname, "bandplan"): plans.insert(0, details[0]) else: plans.append(details[0]) d = inputdialog.ChoiceDialog(plans, parent=parent_window, title="Choose Defaults") d.label.set_text(_("Band plans define default channel settings for " "frequencies in a region. Choose a band plan " "or None for completely manual channel " "settings.")) d.label.set_line_wrap(True) r = d.run() if r == gtk.RESPONSE_OK: selection = d.choice.get_active_text() for shortname, details in self.plans.iteritems(): self._config.set_bool(shortname, selection == details[0], "bandplan") if selection == details[0]: LOG.info("Selected band plan %s: %s" % (shortname, selection)) d.destroy() chirp-daily-20170714/chirp/ui/settingsedit.py0000644000016101777760000002064513066655671022213 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 import logging from chirp import chirp_common from chirp import settings from chirp.ui import common, miscwidgets LOG = logging.getLogger(__name__) class RadioSettingProxy(settings.RadioSetting): def __init__(self, setting, editor): self._setting = setting self._editor = editor class SettingsEditor(common.Editor): def __init__(self, rthread): super(SettingsEditor, self).__init__(rthread) # The main box self.root = gtk.HBox(False, 0) # The pane paned = gtk.HPaned() paned.show() self.root.pack_start(paned, 1, 1, 0) # The selection tree self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT) self._view = gtk.TreeView(self._store) self._view.get_selection().connect("changed", self._view_changed_cb) self._view.append_column( gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)) self._view.show() scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.add_with_viewport(self._view) scrolled_window.set_size_request(200, -1) scrolled_window.show() paned.pack1(scrolled_window) # The settings notebook self._notebook = gtk.Notebook() self._notebook.set_show_tabs(False) self._notebook.set_show_border(False) self._notebook.show() paned.pack2(self._notebook) self._changed = False self._settings = None job = common.RadioJob(self._get_settings_cb, "get_settings") job.set_desc("Getting radio settings") self.rthread.submit(job) def _save_settings(self): if self._settings is None: return def setting_cb(result): if isinstance(result, Exception): common.show_error(_("Error in setting value: %s") % result) elif self._changed: self.emit("changed") self._changed = False job = common.RadioJob(setting_cb, "set_settings", self._settings) job.set_desc("Setting radio settings") self.rthread.submit(job) def _do_save_setting(self, widget, value): if isinstance(value, settings.RadioSettingValueInteger): value.set_value(widget.get_adjustment().get_value()) elif isinstance(value, settings.RadioSettingValueFloat): value.set_value(widget.get_text()) 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: LOG.error("Unsupported widget type %s for %s" % (element.value.__class__, element.get_name())) self._changed = True self._save_settings() def _save_setting(self, widget, value): try: self._do_save_setting(widget, value) except settings.InvalidValueError, e: common.show_error(_("Invalid setting value: %s") % e) def _build_ui_tab(self, group): # The scrolled window sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.show() # Notebook tab tab = self._notebook.append_page(sw, gtk.Label(_(group.get_name()))) # Settings table table = gtk.Table(len(group), 2, False) table.set_resize_mode(gtk.RESIZE_IMMEDIATE) table.show() sw.add_with_viewport(table) row = 0 for element in group: if not isinstance(element, settings.RadioSetting): continue # Label label = gtk.Label(element.get_shortname() + ":") label.set_alignment(0.0, 0.5) label.show() table.attach(label, 0, 1, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=6, ypadding=3) if isinstance(element.value, list) and \ isinstance(element.value[0], settings.RadioSettingValueInteger): box = gtk.HBox(True) else: box = gtk.VBox(True) # Widget container box.show() table.attach(box, 1, 2, row, row + 1, xoptions=gtk.FILL, yoptions=0, xpadding=12, ypadding=3) for i in element.keys(): value = element[i] if isinstance(value, settings.RadioSettingValueInteger): widget = gtk.SpinButton() adj = widget.get_adjustment() adj.configure(value.get_value(), value.get_min(), value.get_max(), value.get_step(), 1, 0) widget.connect("value-changed", self._save_setting, value) elif isinstance(value, settings.RadioSettingValueFloat): widget = gtk.Entry() widget.set_width_chars(16) widget.set_text(value.format()) widget.connect("focus-out-event", lambda w, e, v: self._save_setting(w, v), value) elif isinstance(value, settings.RadioSettingValueBoolean): widget = gtk.CheckButton(_("Enabled")) widget.set_active(value.get_value()) widget.connect("toggled", self._save_setting, value) elif isinstance(value, settings.RadioSettingValueList): widget = miscwidgets.make_choice([], editable=False) 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) widget.connect("changed", self._save_setting, value) elif isinstance(value, settings.RadioSettingValueString): widget = gtk.Entry() widget.set_width_chars(32) widget.set_text(str(value).rstrip()) widget.connect("focus-out-event", lambda w, e, v: self._save_setting(w, v), value) else: LOG.error("Unsupported widget type: %s" % value.__class__) widget.set_sensitive(value.get_mutable()) widget.show() box.pack_start(widget, 1, 1, 1) row += 1 return tab def _build_ui_group(self, group, parent): tab = self._build_ui_tab(group) iter = self._store.append(parent) self._store.set(iter, 0, group.get_shortname(), 1, tab) for element in group: if not isinstance(element, settings.RadioSetting): self._build_ui_group(element, iter) def _build_ui(self, settings): if not isinstance(settings, list): raise Exception("Invalid Radio Settings") return self._settings = settings for group in settings: self._build_ui_group(group, None) self._view.expand_all() def _get_settings_cb(self, settings): gobject.idle_add(self._build_ui, settings) def _view_changed_cb(self, selection): (lst, iter) = selection.get_selected() tab, = self._store.get(iter, 1) self._notebook.set_current_page(tab) chirp-daily-20170714/chirp/ui/cloneprog.py0000644000016101777760000000405512475535623021467 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 class CloneProg(gtk.Window): def __init__(self, **args): if "parent" in args: parent = args["parent"] del args["parent"] else: parent = None if "cancel" in args: 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-daily-20170714/chirp/ui/fips.py0000644000016101777760000075120712475535623020450 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 . 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-daily-20170714/chirp/bandplan_iaru_r2.py0000644000016101777760000001502312136152333022244 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from chirp import bandplan SHORTNAME = "iaru_r2" DESC = { "name": "IARU Region 2 (The Americas)", "updated": "October 8, 2010", "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r2_band_plan.pdf" } # Bands are broken up like this so that other plans can import bits. BANDS_160M = ( bandplan.Band((1800000, 2000000), "160 Meter Band"), bandplan.Band((1800000, 1810000), "Digimodes"), bandplan.Band((1810000, 1830000), "CW", mode="CW"), bandplan.Band((1830000, 1840000), "CW, priority for DX", mode="CW"), bandplan.Band((1840000, 1850000), "SSB, priority for DX", mode="LSB"), bandplan.Band((1850000, 1999000), "All modes", mode="LSB"), bandplan.Band((1999000, 2000000), "Beacons", mode="CW"), ) BANDS_80M = ( bandplan.Band((3500000, 4000000), "80 Meter Band"), bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"), bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"), bandplan.Band((3560000, 3580000), "CW", mode="CW"), bandplan.Band((3580000, 3590000), "All narrow band modes, digimodes"), bandplan.Band((3590000, 3600000), "All modes"), bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((3650000, 3700000), "All modes", mode="LSB"), bandplan.Band((3700000, 3775000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"), bandplan.Band((3800000, 4000000), "All modes"), ) BANDS_40M = ( bandplan.Band((7000000, 7300000), "40 Meter Band"), bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"), bandplan.Band((7025000, 7035000), "CW", mode="CW"), bandplan.Band((7035000, 7038000), "All narrow band modes, digimodes"), bandplan.Band((7038000, 7040000), "All narrow band modes, digimodes"), bandplan.Band((7040000, 7043000), "All modes, digimodes"), bandplan.Band((7043000, 7300000), "All modes"), ) BANDS_30M = ( bandplan.Band((10100000, 10150000), "30 Meter Band"), bandplan.Band((10100000, 10130000), "CW", mode="CW"), bandplan.Band((10130000, 10140000), "All narrow band digimodes"), bandplan.Band((10140000, 10150000), "All modes, digimodes, no phone"), ) BANDS_20M = ( bandplan.Band((14000000, 14350000), "20 Meter Band"), bandplan.Band((14000000, 14025000), "CW, priority for DX", mode="CW"), bandplan.Band((14025000, 14060000), "CW, contest preferred", mode="CW"), bandplan.Band((14060000, 14070000), "CW", mode="CW"), bandplan.Band((14070000, 14089000), "All narrow band modes, digimodes"), bandplan.Band((14089000, 14099000), "All modes, digimodes"), bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((14101000, 14112000), "All modes, digimodes"), bandplan.Band((14112000, 14285000), "All modes, SSB contest preferred", mode="USB"), bandplan.Band((14285000, 14300000), "All modes", mode="AM"), bandplan.Band((14300000, 14350000), "All modes"), ) BANDS_17M = ( bandplan.Band((18068000, 18168000), "17 Meter Band"), bandplan.Band((18068000, 18095000), "CW", mode="CW"), bandplan.Band((18095000, 18105000), "All narrow band modes, digimodes"), bandplan.Band((18105000, 18109000), "All narrow band modes, digimodes"), bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((18111000, 18120000), "All modes, digimodes"), bandplan.Band((18120000, 18168000), "All modes"), ) BANDS_15M = ( bandplan.Band((21000000, 21450000), "15 Meter Band"), bandplan.Band((21000000, 21070000), "CW", mode="CW"), bandplan.Band((21070000, 21090000), "All narrow band modes, digimodes"), bandplan.Band((21090000, 21110000), "All narrow band modes, digimodes"), bandplan.Band((21110000, 21120000), "All modes (exc SSB), digimodes"), bandplan.Band((21120000, 21149000), "All narrow band modes"), bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((21151000, 21450000), "All modes", mode="USB"), ) BANDS_12M = ( bandplan.Band((24890000, 24990000), "12 Meter Band"), bandplan.Band((24890000, 24915000), "CW", mode="CW"), bandplan.Band((24915000, 24925000), "All narrow band modes, digimodes"), bandplan.Band((24925000, 24929000), "All narrow band modes, digimodes"), bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((24931000, 24940000), "All modes, digimodes"), bandplan.Band((24940000, 24990000), "All modes", mode="USB"), ) BANDS_10M = ( bandplan.Band((28000000, 29520000), "10 Meter Band"), bandplan.Band((28000000, 28070000), "CW", mode="CW"), bandplan.Band((28070000, 28120000), "All narrow band modes, digimodes"), bandplan.Band((28120000, 28150000), "All narrow band modes, digimodes"), bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"), bandplan.Band((28190000, 28199000), "Regional time shared beacons", mode="CW"), bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((28201000, 28225000), "Continuous duty beacons", mode="CW"), bandplan.Band((28225000, 28300000), "All modes, beacons"), bandplan.Band((28300000, 28320000), "All modes, digimodes"), bandplan.Band((28320000, 29000000), "All modes"), bandplan.Band((29000000, 29200000), "All modes, AM preferred", mode="AM"), bandplan.Band((29200000, 29300000), "All modes including FM, digimodes"), bandplan.Band((29300000, 29510000), "Satellite downlink"), bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"), bandplan.Band((29520000, 29700000), "FM", step_khz=10, mode="NFM"), bandplan.Band((29620000, 29690000), "FM Repeaters", input_offset=-100000), ) BANDS = BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M BANDS = BANDS + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M chirp-daily-20170714/chirp/settings.py0000644000016101777760000003371213034370710020706 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 self._validate_callback = lambda x: x self._mutable = True def set_mutable(self, mutable): self._mutable = mutable def get_mutable(self): return self._mutable def changed(self): """Returns True if the setting has been changed since init""" return self._has_changed def set_validate_callback(self, callback): self._validate_callback = callback def set_value(self, value): """Sets the current value, triggers changed""" if not self.get_mutable(): raise InvalidValueError("This value is not mutable") if self._current is not None and value != self._current: self._has_changed = True self._current = self._validate_callback(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 RadioSettingValueFloat(RadioSettingValue): """A floating-point setting""" def __init__(self, minval, maxval, current, resolution=0.001, precision=4): RadioSettingValue.__init__(self) self._min = minval self._max = maxval self._res = resolution self._pre = precision self.set_value(current) def format(self, value=None): """Formats the value into a string""" if value is None: value = self._current fmt_string = "%%.%if" % self._pre return fmt_string % value def set_value(self, value): try: value = float(value) except: raise InvalidValueError("A floating point value is required") if value > self._max or value < self._min: raise InvalidValueError("Value %s not in range %s-%s" % ( self.format(value), self.format(self._min), self.format(self._max))) # FIXME: honor resolution 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""" 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 __bool__(self): return bool(self.get_value()) __nonzero__ = __bool__ 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 value not 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, charset=chirp_common.CHARSET_ASCII): RadioSettingValue.__init__(self) self._minlength = minlength self._maxlength = maxlength self._charset = charset 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 RadioSettingValueMap(RadioSettingValueList): """Map User Options to Radio Memory Values Provides User Option list for GUI, maintains state, verifies new values, and allows {setting,getting} by User Option OR Memory Value. External conversions not needed. """ def __init__(self, map_entries, mem_val=None, user_option=None): """Create new map Pass in list of 2 member tuples, typically of type (str, int), for each Radio Setting. First member of each tuple is the User Option Name, second is the Memory Value that corresponds. An example is APO: ("Off", 0), ("0.5", 5), ("1.0", 10). """ # Catch bugs early by testing tuple geometry for map_entry in map_entries: if not len(map_entry) == 2: raise InvalidValueError("map_entries must be 2 el tuples " "instead of: %s" % str(map_entry)) user_options = [e[0] for e in map_entries] self._mem_vals = [e[1] for e in map_entries] RadioSettingValueList.__init__(self, user_options, user_options[0]) if mem_val is not None: self.set_mem_val(mem_val) elif user_option is not None: self.set_value(user_option) self._has_changed = False def set_mem_val(self, mem_val): """Change setting to User Option that corresponds to 'mem_val'""" if mem_val in self._mem_vals: index = self._mem_vals.index(mem_val) self.set_value(self._options[index]) else: raise InvalidValueError( "%s is not valid for this setting" % mem_val) def get_mem_val(self): """Get the mem val corresponding to the currently selected user option""" return self._mem_vals[self._options.index(self.get_value())] def __trunc__(self): """Return memory value that matches current user option""" index = self._options.index(self._current) value = self._mem_vals[index] return value def zero_indexed_seq_map(user_options): """RadioSettingValueMap factory method Radio Setting Maps commonly use a list of strings that map to a sequence that starts with 0. Pass in a list of User Options and this function returns a list of tuples of form (str, int). """ mem_vals = range(0, len(user_options)) return zip(user_options, mem_vals) class RadioSettings(list): def __init__(self, *groups): list.__init__(self, groups) def __str__(self): items = [str(self[i]) for i in range(0, len(self))] return "\n".join(items) 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 %s" % type(element)) 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) 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 = "group '%s': {\n" % self._name for element in sorted(self._elements.values()): string += "\t" + 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 RadioSettingGroup""" 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 __init__(self, *args): super(RadioSetting, self).__init__(*args) self._apply_callback = None def set_apply_callback(self, callback, *args): self._apply_callback = lambda: callback(self, *args) def has_apply_callback(self): return self._apply_callback is not None def run_apply_callback(self): return self._apply_callback() 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 name in self._elements: self._elements[name].set_value(value) else: self._elements[name] = value chirp-daily-20170714/chirp/__init__.py0000644000016101777760000000173013132065773020611 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 from glob import glob CHIRP_VERSION="daily-20170714" 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-daily-20170714/chirp/drivers/0000755000016101777760000000000013132065774020156 5ustar jenkinsnogroup00000000000000chirp-daily-20170714/chirp/drivers/vgc.py0000644000016101777760000014101012741363402021276 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # * Pavel Milanes CO7WT # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings from textwrap import dedent MEM_FORMAT = """ struct mem { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rxtone[2]; lbcd txtone[2]; u8 unknown0:2, txp:2, wn:2, unknown1:1, bcl:1; u8 unknown2:2, revert:1, dname:1, unknown3:4; u8 unknown4[2]; }; struct nam { char name[6]; u8 unknown1[2]; }; #seekto 0x0000; struct mem left_memory[500]; #seekto 0x2000; struct mem right_memory[500]; #seekto 0x4000; struct nam left_names[500]; #seekto 0x5000; struct nam right_names[500]; #seekto 0x6000; u8 left_usedflags[64]; #seekto 0x6040; u8 left_scanflags[64]; #seekto 0x6080; u8 right_usedflags[64]; #seekto 0x60C0; u8 right_scanflags[64]; #seekto 0x6160; struct { char line32[32]; } embedded_msg; #seekto 0x6180; struct { u8 sbmute:2, // sub band mute unknown1:1, workmodb:1, // work mode (right side) dw:1, // dual watch audio:1, // audio output mode (stereo/mono) unknown2:1, workmoda:1; // work mode (left side) u8 scansb:1, // scan stop beep aftone:3, // af tone control scand:1, // scan directon scanr:3; // scan resume u8 rxexp:1, // rx expansion ptt:1, // ptt mode display:1, // display select (frequency/clock) omode:1, // operaton mode beep:2, // beep volume spkr:2; // speaker u8 cpuclk:1, // operating mode(cpu clock) fkey:3, // fkey function mrscan:1, // memory scan type color:3; // lcd backlight color u8 vox:2, // vox voxs:3, // vox sensitivity mgain:3; // mic gain u8 wbandb:4, // work band (right side) wbanda:4; // work band (left side) u8 sqlb:4, // squelch level (right side) sqla:4; // squelch level (left side) u8 apo:4, // auto power off ars:1, // automatic repeater shift tot:3; // time out timer u8 stepb:4, // auto step (right side) stepa:4; // auto step (left side) u8 rxcoverm:1, // rx coverage-memory lcdc:3, // lcd contrast rxcoverv:1, // rx coverage-vfo lcdb:3; // lcd brightness u8 smode:1, // smart function mode timefmt:1, // time format datefmt:2, // date format timesig:1, // time signal keyb:3; // key/led brightness u8 dwstop:1, // dual watch stop unknown3:1, sqlexp:1, // sql expansion decbandsel:1, // decoding band select dtmfmodenc:1, // dtmf mode encode bell:3; // bell ringer u8 unknown4:2, btime:6; // lcd backlight time u8 unknown5:2, tz:6; // time zone u8 unknown618E; u8 unknown618F; ul16 offseta; // work offset (left side) ul16 offsetb; // work offset (right side) ul16 mrcha; // selected memory channel (left) ul16 mrchb; // selected memory channel (right) ul16 wpricha; // work priority channel (left) ul16 wprichb; // work priority channel (right) u8 unknown6:3, datasql:2, // data squelch dataspd:1, // data speed databnd:2; // data band select u8 unknown7:1, pfkey2:3, // mic p2 key unknown8:1, pfkey1:3; // mic p1 key u8 unknown9:1, pfkey4:3, // mic p4 key unknowna:1, pfkey3:3; // mic p3 key u8 unknownb:7, dtmfmoddec:1; // dtmf mode decode } settings; #seekto 0x61B0; struct { char line16[16]; } poweron_msg; #seekto 0x6300; struct { u8 unknown1:3, ttdgt:5; // dtmf digit time u8 unknown2:3, ttint:5; // dtmf interval time u8 unknown3:3, tt1stdgt:5; // dtmf 1st digit time u8 unknown4:3, tt1stdly:5; // dtmf 1st digit delay u8 unknown5:3, ttdlyqt:5; // dtmf delay when use qt u8 unknown6:3, ttdkey:5; // dtmf d key function u8 unknown7; u8 unknown8:4, ttautod:4; // dtmf auto dial group } dtmf; #seekto 0x6330; struct { u8 unknown1:7, ttsig:1; // dtmf signal u8 unknown2:4, ttintcode:4; // dtmf interval code u8 unknown3:5, ttgrpcode:3; // dtmf group code u8 unknown4:4, ttautorst:4; // dtmf auto reset time u8 unknown5:5, ttalert:3; // dtmf alert tone/transpond } dtmf2; #seekto 0x6360; struct { u8 code1[8]; // dtmf code u8 code1_len; // dtmf code length u8 unknown1[7]; u8 code2[8]; // dtmf code u8 code2_len; // dtmf code length u8 unknown2[7]; u8 code3[8]; // dtmf code u8 code3_len; // dtmf code length u8 unknown3[7]; u8 code4[8]; // dtmf code u8 code4_len; // dtmf code length u8 unknown4[7]; u8 code5[8]; // dtmf code u8 code5_len; // dtmf code length u8 unknown5[7]; u8 code6[8]; // dtmf code u8 code6_len; // dtmf code length u8 unknown6[7]; u8 code7[8]; // dtmf code u8 code7_len; // dtmf code length u8 unknown7[7]; u8 code8[8]; // dtmf code u8 code8_len; // dtmf code length u8 unknown8[7]; u8 code9[8]; // dtmf code u8 code9_len; // dtmf code length u8 unknown9[7]; } dtmfcode; """ MEM_SIZE = 0x8000 BLOCK_SIZE = 0x40 MODES = ["FM", "Auto", "NFM", "AM"] SKIP_VALUES = ["", "S"] TONES = chirp_common.TONES DTCS_CODES = chirp_common.DTCS_CODES NAME_LENGTH = 6 DTMF_CHARS = list("0123456789ABCD*#") STIMEOUT = 1 # Basic settings lists LIST_AFTONE = ["Low-3", "Low-2", "Low-1", "Normal", "High-1", "High-2"] LIST_SPKR = ["Off", "Front", "Rear", "Front + Rear"] LIST_AUDIO = ["Monaural", "Stereo"] LIST_SBMUTE = ["Off", "TX", "RX", "Both"] LIST_MLNHM = ["Min", "Low", "Normal", "High", "Max"] LIST_PTT = ["Momentary", "Toggle"] LIST_RXEXP = ["General", "Wide coverage"] LIST_VOX = ["Off", "Internal mic", "Front hand-mic", "Rear hand-mic"] LIST_DISPLAY = ["Frequency", "Timer/Clock"] LIST_MINMAX = ["Min"] + ["%s" % x for x in range(2, 8)] + ["Max"] LIST_COLOR = ["White-Blue", "Sky-Blue", "Marine-Blue", "Green", "Yellow-Green", "Orange", "Amber", "White"] LIST_BTIME = ["Continuous"] + ["%s" % x for x in range(1, 61)] LIST_MRSCAN = ["All", "Selected"] LIST_DWSTOP = ["Auto", "Hold"] LIST_SCAND = ["Down", "Up"] LIST_SCANR = ["Busy", "Hold", "1 sec", "3 sec", "5 sec"] LIST_APO = ["Off", ".5", "1", "1.5"] + ["%s" % x for x in range(2, 13)] LIST_BEEP = ["Off", "Low", "High"] LIST_FKEY = ["MHz/AD-F", "AF Dual 1(line-in)", "AF Dual 2(AM)", "AF Dual 3(FM)", "PA", "SQL off", "T-call", "WX"] LIST_PFKEY = ["Off", "SQL off", "TX power", "Scan", "RPT shift", "Reverse", "T-Call"] LIST_AB = ["A", "B"] LIST_COVERAGE = ["In band", "All"] LIST_TOT = ["Off"] + ["%s" % x for x in range(5, 25, 5)] + ["30"] LIST_DATEFMT = ["yyyy/mm/dd", "yyyy/dd/mm", "mm/dd/yyyy", "dd/mm/yyyy"] LIST_TIMEFMT = ["24H", "12H"] LIST_TZ = ["-12 INT DL W", "-11 MIDWAY", "-10 HAST", "-9 AKST", "-8 PST", "-7 MST", "-6 CST", "-5 EST", "-4:30 CARACAS", "-4 AST", "-3:30 NST", "-3 BRASILIA", "-2 MATLANTIC", "-1 AZORES", "-0 LONDON", "+0 LONDON", "+1 ROME", "+2 ATHENS", "+3 MOSCOW", "+3:30 REHRW", "+4 ABUDNABI", "+4:30 KABUL", "+5 ISLMABAD", "+5:30 NEWDELHI", "+6 DHAKA", "+6:30 YANGON", "+7 BANKOK", "+8 BEIJING", "+9 TOKYO", "+10 ADELAIDE", "+10 SYDNET", "+11 NWCLDNIA", "+12 FIJI", "+13 NUKALOFA" ] LIST_BELL = ["Off", "1 time", "3 times", "5 times", "8 times", "Continuous"] LIST_DATABND = ["Main band", "Sub band", "Left band-fixed", "Right band-fixed"] LIST_DATASPD = ["1200 bps", "9600 bps"] LIST_DATASQL = ["Busy/TX", "Busy", "TX"] # Other settings lists LIST_CPUCLK = ["Clock frequency 1", "Clock frequency 2"] # Work mode settings lists LIST_WORK = ["VFO", "Memory System"] LIST_WBANDB = ["Air", "H-V", "GR1-V", "GR1-U", "H-U", "GR2"] LIST_WBANDA = ["Line-in", "AM", "FM"] + LIST_WBANDB LIST_SQL = ["Open"] + ["%s" % x for x in range(1, 10)] LIST_STEP = ["Auto", "2.50 KHz", "5.00 KHz", "6.25 KHz", "8.33 KHz", "9.00 KHz", "10.00 KHz", "12.50 KHz", "15.00 KHz", "20.00 KHz", "25.00 KHz", "50.00 KHz", "100.00 KHz", "200.00 KHz"] LIST_SMODE = ["F-1", "F-2"] # DTMF settings lists LIST_TTDKEY = ["D code"] + ["Send delay %s s" % x for x in range(1, 17)] LIST_TT200 = ["%s ms" % x for x in range(50, 210, 10)] LIST_TT1000 = ["%s ms" % x for x in range(100, 1050, 50)] LIST_TTSIG = ["Code squelch", "Select call"] LIST_TTAUTORST = ["Off"] + ["%s s" % x for x in range(1, 16)] LIST_TTGRPCODE = ["Off"] + list("ABCD*#") LIST_TTINTCODE = DTMF_CHARS LIST_TTALERT = ["Off", "Alert tone", "Transpond", "Transpond-ID code", "Transpond-transpond code"] LIST_TTAUTOD = ["%s" % x for x in range(1, 10)] # valid chars on the LCD VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_" # Power Levels POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=20), chirp_common.PowerLevel("High", watts=50)] # B-TECH UV-50X3 id string UV50X3_id = "VGC6600MD" def _clean_buffer(radio): radio.pipe.timeout = 0.005 junk = radio.pipe.read(256) radio.pipe.timeout = STIMEOUT if junk: Log.debug("Got %i bytes of junk before starting" % len(junk)) def _check_for_double_ack(radio): radio.pipe.timeout = 0.005 c = radio.pipe.read(1) radio.pipe.timeout = STIMEOUT if c and c != '\x06': _exit_program_mode(radio) raise errors.RadioError('Expected nothing or ACK, got %r' % c) def _rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: _exit_program_mode(radio) msg = "Generic error reading data from radio; check your cable." raise errors.RadioError(msg) if len(data) != amount: _exit_program_mode(radio) msg = "Error reading data from radio: not the amount of data we want." raise errors.RadioError(msg) return data def _rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) except: raise errors.RadioError("Error sending data to radio") def _make_frame(cmd, addr, length, data=""): """Pack the info in the headder format""" frame = struct.pack(">BHB", ord(cmd), addr, length) # add the data if set if len(data) != 0: frame += data # return the data return frame def _recv(radio, addr, length=BLOCK_SIZE): """Get data from the radio """ # read 4 bytes of header hdr = _rawrecv(radio, 4) # check for unexpected extra command byte c, a, l = struct.unpack(">BHB", hdr) if hdr[0:2] == "WW" and a != addr: # extra command byte detected # throw away the 1st byte and add the next byte in the buffer hdr = hdr[1:] + _rawrecv(radio, 1) # read 64 bytes (0x40) of data data = _rawrecv(radio, (BLOCK_SIZE)) # DEBUG LOG.info("Response:") LOG.debug(util.hexprint(hdr + data)) c, a, l = struct.unpack(">BHB", hdr) if a != addr or l != length or c != ord("W"): _exit_program_mode(radio) LOG.error("Invalid answer for block 0x%04x:" % addr) LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l)) raise errors.RadioError("Unknown response from the radio") return data def _do_ident(radio): """Put the radio in PROGRAM mode & identify it""" # set the serial discipline radio.pipe.baudrate = 115200 radio.pipe.parity = "N" radio.pipe.timeout = STIMEOUT # flush input buffer _clean_buffer(radio) magic = "V66LINK" _rawsend(radio, magic) # Ok, get the ident string ident = _rawrecv(radio, 9) # check if ident is OK if ident != radio.IDENT: # bad ident msg = "Incorrect model ID, got this:" msg += util.hexprint(ident) LOG.debug(msg) raise errors.RadioError("Radio identification failed.") # DEBUG LOG.info("Positive ident, got this:") LOG.debug(util.hexprint(ident)) return True def _exit_program_mode(radio): endframe = "\x45" _rawsend(radio, endframe) def _download(radio): """Get the memory map""" # put radio in program mode and identify it _do_ident(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in range(0, MEM_SIZE, BLOCK_SIZE): frame = _make_frame("R", addr, BLOCK_SIZE) # DEBUG LOG.info("Request sent:") LOG.debug(util.hexprint(frame)) # sending the read request _rawsend(radio, frame) # now we read d = _recv(radio, addr) # aggregate the data data += d # UI Update status.cur = addr / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) _exit_program_mode(radio) return data def _upload(radio): """Upload procedure""" MEM_SIZE = 0x7000 # put radio in program mode and identify it _do_ident(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) # the fun start here for addr in range(0, MEM_SIZE, BLOCK_SIZE): # sending the data data = radio.get_mmap()[addr:addr + BLOCK_SIZE] frame = _make_frame("W", addr, BLOCK_SIZE, data) _rawsend(radio, frame) # receiving the response ack = _rawrecv(radio, 1) if ack != "\x06": _exit_program_mode(radio) msg = "Bad ack writing block 0x%04x" % addr raise errors.RadioError(msg) _check_for_double_ack(radio) # UI Update status.cur = addr / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) _exit_program_mode(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0x6140:0x6148] #if rid in cls._fileid: if rid in cls.IDENT: return True return False class VGCStyleRadio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """BTECH's UV-50X3""" VENDOR = "BTECH" _air_range = (108000000, 136000000) _vhf_range = (136000000, 174000000) _vhf2_range = (174000000, 250000000) _220_range = (222000000, 225000000) _gen1_range = (300000000, 400000000) _uhf_range = (400000000, 480000000) _gen2_range = (480000000, 520000000) _upper = 499 MODEL = "" IDENT = "" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The UV-50X3 driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow this instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.has_sub_devices = self.VARIANT == "" rf.valid_modes = MODES rf.valid_characters = VALID_CHARS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_skips = SKIP_VALUES rf.valid_name_length = NAME_LENGTH rf.valid_dtcs_codes = DTCS_CODES rf.valid_bands = [self._air_range, self._vhf_range, self._vhf2_range, self._220_range, self._gen1_range, self._uhf_range, self._gen2_range] rf.memory_bounds = (0, self._upper) return rf def get_sub_devices(self): return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)] def sync_in(self): """Download from radio""" try: data = _download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = memmap.MemoryMap(data) self.process_mmap() def sync_out(self): """Upload to radio""" try: _upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def _memory_obj(self, suffix=""): return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix)) def _name_obj(self, suffix=""): return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix)) def _scan_obj(self, suffix=""): return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix)) def _used_obj(self, suffix=""): return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix)) def get_memory(self, number): """Get the mem representation from the radio image""" bitpos = (1 << (number % 8)) bytepos = (number / 8) _mem = self._memory_obj()[number] _names = self._name_obj()[number] _scn = self._scan_obj()[bytepos] _usd = self._used_obj()[bytepos] isused = bitpos & int(_usd) isscan = bitpos & int(_scn) # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # Memory number mem.number = number if not isused: mem.empty = True return mem # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[4] == "\xFF": # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX feq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 # skip if not isscan: mem.skip = "S" # name TAG of the channel mem.name = str(_names.name).strip("\xFF") # power mem.power = POWER_LEVELS[int(_mem.txp)] # wide/narrow mem.mode = MODES[int(_mem.wn)] # tone data rxtone = txtone = None txtone = self.decode_tone(_mem.txtone) rxtone = self.decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra mem.extra = RadioSettingGroup("extra", "Extra") bcl = RadioSetting("bcl", "Busy channel lockout", RadioSettingValueBoolean(bool(_mem.bcl))) mem.extra.append(bcl) revert = RadioSetting("revert", "Revert", RadioSettingValueBoolean(bool(_mem.revert))) mem.extra.append(revert) dname = RadioSetting("dname", "Display name", RadioSettingValueBoolean(bool(_mem.dname))) mem.extra.append(dname) return mem def set_memory(self, mem): """Set the memory data in the eeprom img from the UI""" bitpos = (1 << (mem.number % 8)) bytepos = (mem.number / 8) _mem = self._memory_obj()[mem.number] _names = self._name_obj()[mem.number] _scn = self._scan_obj()[bytepos] _usd = self._used_obj()[bytepos] if mem.empty: _usd &= ~bitpos _scn &= ~bitpos _mem.set_raw("\xFF" * 16) _names.name = ("\xFF" * 6) return else: _usd |= bitpos # frequency _mem.rxfreq = mem.freq / 10 # duplex if mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": for i in _mem.txfreq: i.set_raw("\xFF") elif mem.duplex == "split": _mem.txfreq = mem.offset / 10 else: _mem.txfreq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self.encode_tone(_mem.txtone, txmode, txtone, txpol) self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol) # name TAG of the channel _names.name = mem.name.ljust(6, "\xFF") # power level, # default power level is low _mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power) # wide/narrow _mem.wn = MODES.index(mem.mode) if mem.skip == "S": _scn &= ~bitpos else: _scn |= bitpos # autoset display to display name if filled if mem.extra: # mem.extra only seems to be populated when called from edit panel dname = mem.extra["dname"] else: dname = None if mem.name: _mem.dname = True if dname and not dname.changed(): dname.value = True else: _mem.dname = False if dname and not dname.changed(): dname.value = False # reseting unknowns, this has to be set by hand _mem.unknown0 = 0 _mem.unknown1 = 0 _mem.unknown2 = 0 _mem.unknown3 = 0 # extra settings if len(mem.extra) > 0: # there are setting, parse for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) else: # there are no extra settings, load defaults _mem.bcl = 0 _mem.revert = 0 _mem.dname = 1 def _bbcd2dtmf(self, bcdarr, strlen=16): # doing bbcd, but with support for ABCD*# LOG.debug(bcdarr.get_value()) string = ''.join("%02X" % b for b in bcdarr) LOG.debug("@_bbcd2dtmf, received: %s" % string) string = string.replace('E', '*').replace('F', '#') if strlen <= 16: string = string[:strlen] return string def _dtmf2bbcd(self, value): dtmfstr = value.get_value() dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F') dtmfstr = str.ljust(dtmfstr.strip(), 16, "F") bcdarr = list(bytearray.fromhex(dtmfstr)) LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr) return bcdarr def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") dtmf = RadioSettingGroup("dtmf", "DTMF Settings") top = RadioSettings(basic, other, work, dtmf) # Basic # Audio: A01-A04 aftone = RadioSetting("settings.aftone", "AF tone control", RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[ _mem.settings.aftone])) basic.append(aftone) spkr = RadioSetting("settings.spkr", "Speaker", RadioSettingValueList(LIST_SPKR,LIST_SPKR[ _mem.settings.spkr])) basic.append(spkr) audio = RadioSetting("settings.audio", "Stereo/Mono", RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[ _mem.settings.audio])) basic.append(audio) sbmute = RadioSetting("settings.sbmute", "Sub band mute", RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[ _mem.settings.sbmute])) basic.append(sbmute) # TX/RX: B01-B08 mgain = RadioSetting("settings.mgain", "Mic gain", RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[ _mem.settings.mgain])) basic.append(mgain) ptt = RadioSetting("settings.ptt", "PTT mode", RadioSettingValueList(LIST_PTT,LIST_PTT[ _mem.settings.ptt])) basic.append(ptt) # B03 (per channel) # B04 (per channel) rxexp = RadioSetting("settings.rxexp", "RX expansion", RadioSettingValueList(LIST_RXEXP,LIST_RXEXP[ _mem.settings.rxexp])) basic.append(rxexp) vox = RadioSetting("settings.vox", "Vox", RadioSettingValueList(LIST_VOX, LIST_VOX[ _mem.settings.vox])) basic.append(vox) voxs = RadioSetting("settings.voxs", "Vox sensitivity", RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[ _mem.settings.voxs])) basic.append(voxs) # B08 (per channel) # Display: C01-C06 display = RadioSetting("settings.display", "Display select", RadioSettingValueList(LIST_DISPLAY, LIST_DISPLAY[_mem.settings.display])) basic.append(display) lcdb = RadioSetting("settings.lcdb", "LCD brightness", RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[ _mem.settings.lcdb])) basic.append(lcdb) color = RadioSetting("settings.color", "LCD color", RadioSettingValueList(LIST_COLOR, LIST_COLOR[ _mem.settings.color])) basic.append(color) lcdc = RadioSetting("settings.lcdc", "LCD contrast", RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[ _mem.settings.lcdc])) basic.append(lcdc) btime = RadioSetting("settings.btime", "LCD backlight time", RadioSettingValueList(LIST_BTIME, LIST_BTIME[ _mem.settings.btime])) basic.append(btime) keyb = RadioSetting("settings.keyb", "Key brightness", RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[ _mem.settings.keyb])) basic.append(keyb) # Memory: D01-D04 # D01 (per channel) # D02 (per channel) mrscan = RadioSetting("settings.mrscan", "Memory scan type", RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[ _mem.settings.mrscan])) basic.append(mrscan) # D04 (per channel) # Scan: E01-E04 dwstop = RadioSetting("settings.dwstop", "Dual watch stop", RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[ _mem.settings.dwstop])) basic.append(dwstop) scand = RadioSetting("settings.scand", "Scan direction", RadioSettingValueList(LIST_SCAND,LIST_SCAND[ _mem.settings.scand])) basic.append(scand) scanr = RadioSetting("settings.scanr", "Scan resume", RadioSettingValueList(LIST_SCANR,LIST_SCANR[ _mem.settings.scanr])) basic.append(scanr) scansb = RadioSetting("settings.scansb", "Scan stop beep", RadioSettingValueBoolean(_mem.settings.scansb)) basic.append(scansb) # System: F01-F09 apo = RadioSetting("settings.apo", "Automatic power off [hours]", RadioSettingValueList(LIST_APO, LIST_APO[ _mem.settings.apo])) basic.append(apo) ars = RadioSetting("settings.ars", "Automatic repeater shift", RadioSettingValueBoolean(_mem.settings.ars)) basic.append(ars) beep = RadioSetting("settings.beep", "Beep volume", RadioSettingValueList(LIST_BEEP,LIST_BEEP[ _mem.settings.beep])) basic.append(beep) fkey = RadioSetting("settings.fkey", "F key", RadioSettingValueList(LIST_FKEY,LIST_FKEY[ _mem.settings.fkey])) basic.append(fkey) pfkey1 = RadioSetting("settings.pfkey1", "Mic P1 key", RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[ _mem.settings.pfkey1])) basic.append(pfkey1) pfkey2 = RadioSetting("settings.pfkey2", "Mic P2 key", RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[ _mem.settings.pfkey2])) basic.append(pfkey2) pfkey3 = RadioSetting("settings.pfkey3", "Mic P3 key", RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[ _mem.settings.pfkey3])) basic.append(pfkey3) pfkey4 = RadioSetting("settings.pfkey4", "Mic P4 key", RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[ _mem.settings.pfkey4])) basic.append(pfkey4) omode = RadioSetting("settings.omode", "Operation mode", RadioSettingValueList(LIST_AB,LIST_AB[ _mem.settings.omode])) basic.append(omode) rxcoverm = RadioSetting("settings.rxcoverm", "RX coverage - memory", RadioSettingValueList(LIST_COVERAGE, LIST_COVERAGE[_mem.settings.rxcoverm])) basic.append(rxcoverm) rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO", RadioSettingValueList(LIST_COVERAGE, LIST_COVERAGE[_mem.settings.rxcoverv])) basic.append(rxcoverv) tot = RadioSetting("settings.tot", "Time out timer [min]", RadioSettingValueList(LIST_TOT, LIST_TOT[ _mem.settings.tot])) basic.append(tot) # Timer/Clock: G01-G04 # G01 datefmt = RadioSetting("settings.datefmt", "Date format", RadioSettingValueList(LIST_DATEFMT, LIST_DATEFMT[_mem.settings.datefmt])) basic.append(datefmt) timefmt = RadioSetting("settings.timefmt", "Time format", RadioSettingValueList(LIST_TIMEFMT, LIST_TIMEFMT[_mem.settings.timefmt])) basic.append(timefmt) timesig = RadioSetting("settings.timesig", "Time signal", RadioSettingValueBoolean(_mem.settings.timesig)) basic.append(timesig) tz = RadioSetting("settings.tz", "Time zone", RadioSettingValueList(LIST_TZ, LIST_TZ[ _mem.settings.tz])) basic.append(tz) # Signaling: H01-H06 bell = RadioSetting("settings.bell", "Bell ringer", RadioSettingValueList(LIST_BELL, LIST_BELL[ _mem.settings.bell])) basic.append(bell) # H02 (per channel) dtmfmodenc = RadioSetting("settings.dtmfmodenc", "DTMF mode encode", RadioSettingValueBoolean( _mem.settings.dtmfmodenc)) basic.append(dtmfmodenc) dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode", RadioSettingValueBoolean( _mem.settings.dtmfmoddec)) basic.append(dtmfmoddec) # H04 (per channel) decbandsel = RadioSetting("settings.decbandsel", "DTMF band select", RadioSettingValueList(LIST_AB,LIST_AB[ _mem.settings.decbandsel])) basic.append(decbandsel) sqlexp = RadioSetting("settings.sqlexp", "SQL expansion", RadioSettingValueBoolean(_mem.settings.sqlexp)) basic.append(sqlexp) # Pkt: I01-I03 databnd = RadioSetting("settings.databnd", "Packet data band", RadioSettingValueList(LIST_DATABND,LIST_DATABND[ _mem.settings.databnd])) basic.append(databnd) dataspd = RadioSetting("settings.dataspd", "Packet data speed", RadioSettingValueList(LIST_DATASPD,LIST_DATASPD[ _mem.settings.dataspd])) basic.append(dataspd) datasql = RadioSetting("settings.datasql", "Packet data squelch", RadioSettingValueList(LIST_DATASQL,LIST_DATASQL[ _mem.settings.datasql])) basic.append(datasql) # Other dw = RadioSetting("settings.dw", "Dual watch", RadioSettingValueBoolean(_mem.settings.dw)) other.append(dw) cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency", RadioSettingValueList(LIST_CPUCLK,LIST_CPUCLK[ _mem.settings.cpuclk])) other.append(cpuclk) def _filter(name): filtered = "" for char in str(name): if char in VALID_CHARS: filtered += char else: filtered += " " return filtered line16 = RadioSetting("poweron_msg.line16", "Power-on message", RadioSettingValueString(0, 16, _filter( _mem.poweron_msg.line16))) other.append(line16) line32 = RadioSetting("embedded_msg.line32", "Embedded message", RadioSettingValueString(0, 32, _filter( _mem.embedded_msg.line32))) other.append(line32) # Work workmoda = RadioSetting("settings.workmoda", "Work mode A", RadioSettingValueList(LIST_WORK,LIST_WORK[ _mem.settings.workmoda])) work.append(workmoda) workmodb = RadioSetting("settings.workmodb", "Work mode B", RadioSettingValueList(LIST_WORK,LIST_WORK[ _mem.settings.workmodb])) work.append(workmodb) wbanda = RadioSetting("settings.wbanda", "Work band A", RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[ (_mem.settings.wbanda) - 1])) work.append(wbanda) wbandb = RadioSetting("settings.wbandb", "Work band B", RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[ (_mem.settings.wbandb) - 4])) work.append(wbandb) sqla = RadioSetting("settings.sqla", "Squelch A", RadioSettingValueList(LIST_SQL, LIST_SQL[ _mem.settings.sqla])) work.append(sqla) sqlb = RadioSetting("settings.sqlb", "Squelch B", RadioSettingValueList(LIST_SQL, LIST_SQL[ _mem.settings.sqlb])) work.append(sqlb) stepa = RadioSetting("settings.stepa", "Auto step A", RadioSettingValueList(LIST_STEP,LIST_STEP[ _mem.settings.stepa])) work.append(stepa) stepb = RadioSetting("settings.stepb", "Auto step B", RadioSettingValueList(LIST_STEP,LIST_STEP[ _mem.settings.stepb])) work.append(stepb) mrcha = RadioSetting("settings.mrcha", "Current channel A", RadioSettingValueInteger(0, 499, _mem.settings.mrcha)) work.append(mrcha) mrchb = RadioSetting("settings.mrchb", "Current channel B", RadioSettingValueInteger(0, 499, _mem.settings.mrchb)) work.append(mrchb) val = _mem.settings.offseta / 100.00 offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)", RadioSettingValueFloat(0, 38.00, val, 0.05, 2)) work.append(offseta) val = _mem.settings.offsetb / 100.00 offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)", RadioSettingValueFloat(0, 80.00, val, 0.05, 2)) work.append(offsetb) wpricha = RadioSetting("settings.wpricha", "Priority channel A", RadioSettingValueInteger(0, 499, _mem.settings.wpricha)) work.append(wpricha) wprichb = RadioSetting("settings.wprichb", "Priority channel B", RadioSettingValueInteger(0, 499, _mem.settings.wprichb)) work.append(wprichb) smode = RadioSetting("settings.smode", "Smart function mode", RadioSettingValueList(LIST_SMODE,LIST_SMODE[ _mem.settings.smode])) work.append(smode) # dtmf ttdkey = RadioSetting("dtmf.ttdkey", "D key function", RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[ _mem.dtmf.ttdkey])) dtmf.append(ttdkey) ttdgt = RadioSetting("dtmf.ttdgt", "Digit time", RadioSettingValueList(LIST_TT200, LIST_TT200[ (_mem.dtmf.ttdgt) - 5])) dtmf.append(ttdgt) ttint = RadioSetting("dtmf.ttint", "Interval time", RadioSettingValueList(LIST_TT200, LIST_TT200[ (_mem.dtmf.ttint) - 5])) dtmf.append(ttint) tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time", RadioSettingValueList(LIST_TT200, LIST_TT200[ (_mem.dtmf.tt1stdgt) - 5])) dtmf.append(tt1stdgt) tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time", RadioSettingValueList(LIST_TT1000, LIST_TT1000[ (_mem.dtmf.tt1stdly) - 2])) dtmf.append(tt1stdly) ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt", RadioSettingValueList(LIST_TT1000, LIST_TT1000[ (_mem.dtmf.ttdlyqt) - 2])) dtmf.append(ttdlyqt) ttsig = RadioSetting("dtmf2.ttsig", "Signal", RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[ _mem.dtmf2.ttsig])) dtmf.append(ttsig) ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time", RadioSettingValueList(LIST_TTAUTORST, LIST_TTAUTORST[_mem.dtmf2.ttautorst])) dtmf.append(ttautorst) if _mem.dtmf2.ttgrpcode > 0x06: val = 0x00 else: val = _mem.dtmf2.ttgrpcode ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code", RadioSettingValueList(LIST_TTGRPCODE, LIST_TTGRPCODE[val])) dtmf.append(ttgrpcode) ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code", RadioSettingValueList(LIST_TTINTCODE, LIST_TTINTCODE[_mem.dtmf2.ttintcode])) dtmf.append(ttintcode) if _mem.dtmf2.ttalert > 0x04: val = 0x00 else: val = _mem.dtmf2.ttalert ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond", RadioSettingValueList(LIST_TTALERT, LIST_TTALERT[val])) dtmf.append(ttalert) ttautod = RadioSetting("dtmf.ttautod", "Auto dial group", RadioSettingValueList(LIST_TTAUTOD, LIST_TTAUTOD[_mem.dtmf.ttautod])) dtmf.append(ttautod) # setup 9 dtmf autodial entries for i in map(str, range(1, 10)): objname = "code" + i strname = "Code " + str(i) dtmfsetting = getattr(_mem.dtmfcode, objname) dtmflen = getattr(_mem.dtmfcode, objname + "_len") dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen) code = RadioSettingValueString(0, 16, dtmfstr) code.set_charset(DTMF_CHARS + list(" ")) rs = RadioSetting("dtmfcode." + objname, strname, code) dtmf.append(rs) return top def set_settings(self, settings): _settings = self._memobj.settings _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "line16": setattr(obj, setting, str(element.value).rstrip( " ").ljust(16, "\xFF")) elif setting == "line32": setattr(obj, setting, str(element.value).rstrip( " ").ljust(32, "\xFF")) elif setting == "wbanda": setattr(obj, setting, int(element.value) + 1) elif setting == "wbandb": setattr(obj, setting, int(element.value) + 4) elif setting in ["offseta", "offsetb"]: val = element.value value = int(val.get_value() * 100) setattr(obj, setting, value) elif setting in ["ttdgt", "ttint", "tt1stdgt"]: setattr(obj, setting, int(element.value) + 5) elif setting in ["tt1stdly", "ttdlyqt"]: setattr(obj, setting, int(element.value) + 2) elif re.match('code\d', setting): # set dtmf length field and then get bcd dtmf dtmfstrlen = len(str(element.value).strip()) setattr(_mem.dtmfcode, setting + "_len", dtmfstrlen) dtmfstr = self._dtmf2bbcd(element.value) setattr(_mem.dtmfcode, setting, dtmfstr) elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False @directory.register class UV50X3(VGCStyleRadio): """BTech UV-50X3""" MODEL = "UV-50X3" IDENT = UV50X3_id class UV50X3Left(UV50X3): VARIANT = "Left" _vfo = "left" class UV50X3Right(UV50X3): VARIANT = "Right" _vfo = "right" chirp-daily-20170714/chirp/drivers/ic2820.py0000644000016101777760000002253012476006422021434 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.drivers import icf from chirp import chirp_common, util, directory, 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_ASCII 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-daily-20170714/chirp/drivers/th_uvf8d.py0000644000016101777760000004613012653610223022253 0ustar jenkinsnogroup00000000000000# Copyright 2013 Dan Smith # 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 . """TYT TH-UVF8D radio management module""" # TODO: support FM Radio memories # TODO: support bank B (another 128 memories) # TODO: [setting] Battery Save # TODO: [setting] Tail Eliminate # TODO: [setting] Tail Mode import struct import logging from chirp import chirp_common, bitwise, errors, directory, memmap, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) def uvf8d_identify(radio): """Do identify handshake with TYT TH-UVF8D""" try: radio.pipe.write("\x02PROGRAM") ack = radio.pipe.read(2) if ack != "PG": raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack)) except: raise errors.RadioError("Unable to communicate with the radio") radio.pipe.write("\x02") ident = radio.pipe.read(32) radio.pipe.write("A") r = radio.pipe.read(1) if r != "A": raise errors.RadioError("Ack failed") return ident def tyt_uvf8d_download(radio): data = uvf8d_identify(radio) for i in range(0, 0x4000, 0x20): msg = struct.pack(">cHb", "R", i, 0x20) radio.pipe.write(msg) block = radio.pipe.read(0x20 + 4) if len(block) != (0x20 + 4): raise errors.RadioError("Radio sent a short block") radio.pipe.write("A") ack = radio.pipe.read(1) if ack != "A": raise errors.RadioError("Radio NAKed block") data += block[4:] if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = 0x4000 status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("ENDR") return memmap.MemoryMap(data) def tyt_uvf8d_upload(radio): """Upload to TYT TH-UVF8D""" data = uvf8d_identify(radio) radio.pipe.timeout = 1 if data != radio._mmap[:32]: raise errors.RadioError("Model mis-match: \n%s\n%s" % (util.hexprint(data), util.hexprint(radio._mmap[:32]))) for i in range(0, 0x4000, 0x20): addr = i + 0x20 msg = struct.pack(">cHb", "W", i, 0x20) msg += radio._mmap[addr:(addr + 0x20)] radio.pipe.write(msg) ack = radio.pipe.read(1) if ack != "A": raise errors.RadioError("Radio did not ack block %i" % i) if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = 0x4000 status.msg = "Cloning to radio" radio.status_fn(status) # End of clone? radio.pipe.write("ENDW") # Checksum? final_data = radio.pipe.read(3) # these require working desktop software # TODO: DTMF features (ID, delay, speed, kill, etc.) # TODO: Display Name UVF8D_MEM_FORMAT = """ struct memory { lbcd rx_freq[4]; lbcd tx_freq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 apro:4, rpt_md:2, unknown1:2; u8 bclo:2, wideband:1, ishighpower:1, unknown21:1, vox:1, pttid:2; u8 unknown3:8; u8 unknown4:6, duplex:2; lbcd offset[4]; char unknown5[4]; char name[7]; char unknown6[1]; }; struct fm_broadcast_memory { lbcd freq[3]; u8 unknown; }; struct enable_flags { bit flags[8]; }; #seekto 0x0020; struct memory channels[128]; #seekto 0x2020; struct memory vfo1; struct memory vfo2; #seekto 0x2060; struct { u8 unknown2060:4, tot:4; u8 unknown2061; u8 squelch; u8 unknown2063:4, vox_level:4; u8 tuning_step; char unknown12; u8 lamp_t; char unknown11; u8 unknown2068; u8 ani:1, scan_mode:2, unknown2069:2, beep:1, tx_sel:1, roger:1; u8 light:2, led:2, unknown206a:1, autolk:1, unknown206ax:2; u8 unknown206b:1, b_display:2, a_display:2, ab_switch:1, dwait:1, mode:1; u8 dw:1, unknown206c:6, voice:1; u8 unknown206d:2, rxsave:2, opnmsg:2, lock_mode:2; u8 a_work_area:1, b_work_area:1, unknown206ex:6; u8 a_channel; u8 b_channel; u8 pad3[15]; char ponmsg[7]; } settings; #seekto 0x2E60; struct enable_flags enable[16]; struct enable_flags skip[16]; #seekto 0x2FA0; struct fm_broadcast_memory fm_current; #seekto 0x2FA8; struct fm_broadcast_memory fm_memories[20]; """ THUVF8D_DUPLEX = ["", "-", "+"] THUVF8D_CHARSET = "".join([chr(ord("0") + x) for x in range(0, 10)] + [" -*+"] + [chr(ord("A") + x) for x in range(0, 26)] + ["_/"]) TXSEL_LIST = ["EDIT", "BUSY"] LED_LIST = ["Off", "Auto", "On"] MODE_LIST = ["Memory", "VFO"] AB_LIST = ["A", "B"] DISPLAY_LIST = ["Channel", "Frequency", "Name"] LIGHT_LIST = ["Purple", "Orange", "Blue"] RPTMD_LIST = ["Off", "Reverse", "Talkaround"] VOX_LIST = ["1", "2", "3", "4", "5", "6", "7", "8"] WIDEBAND_LIST = ["Narrow", "Wide"] TOT_LIST = ["Off", "30s", "60s", "90s", "120s", "150s", "180s", "210s", "240s", "270s"] SCAN_MODE_LIST = ["Time", "Carry", "Seek"] OPNMSG_LIST = ["Off", "DC (Battery)", "Message"] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5), chirp_common.PowerLevel("Low", watts=0.5), ] PTTID_LIST = ["Off", "BOT", "EOT", "Both"] BCLO_LIST = ["Off", "Wave", "Call"] APRO_LIST = ["Off", "Compander", "Scramble 1", "Scramble 2", "Scramble 3", "Scramble 4", "Scramble 5", "Scramble 6", "Scramble 7", "Scramble 8"] LOCK_MODE_LIST = ["PTT", "Key", "Key+S", "All"] TUNING_STEPS_LIST = ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"] BACKLIGHT_TIMEOUT_LIST = ["1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", "10s"] SPECIALS = { "VFO1": -2, "VFO2": -1} @directory.register class TYTUVF8DRadio(chirp_common.CloneModeRadio): VENDOR = "TYT" MODEL = "TH-UVF8D" BAUD_RATE = 9600 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 = False rf.has_rx_dtcs = True rf.has_settings = True # it may actually be supported, but I haven't tested rf.can_odd_split = False rf.valid_duplexes = THUVF8D_DUPLEX rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-" rf.valid_bands = [(136000000, 174000000), (400000000, 520000000)] rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS rf.valid_modes = ["FM", "NFM"] rf.valid_special_chans = SPECIALS.keys() rf.valid_name_length = 7 return rf def sync_in(self): self._mmap = tyt_uvf8d_download(self) self.process_mmap() def sync_out(self): tyt_uvf8d_upload(self) @classmethod def match_model(cls, filedata, filename): return filedata.startswith("TYT-F10\x00") def process_mmap(self): self._memobj = bitwise.parse(UVF8D_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.channels[number - 1]) def _get_memobjs(self, number): if isinstance(number, str): return (getattr(self._memobj, number.lower()), None) elif number < 0: for k, v in SPECIALS.items(): if number == v: return (getattr(self._memobj, k.lower()), None) else: return (self._memobj.channels[number - 1], None) def get_memory(self, number): _mem, _name = self._get_memobjs(number) mem = chirp_common.Memory() if isinstance(number, str): mem.number = SPECIALS[number] mem.extd_number = number else: mem.number = number if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"): mem.empty = True return mem if isinstance(number, int): e = self._memobj.enable[(number - 1) / 8] enabled = e.flags[7 - ((number - 1) % 8)] s = self._memobj.skip[(number - 1) / 8] dont_skip = s.flags[7 - ((number - 1) % 8)] else: enabled = True dont_skip = True if not enabled: mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 mem.duplex = THUVF8D_DUPLEX[_mem.duplex] mem.offset = int(_mem.offset) * 10 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(_mem.name).rstrip('\xFF ') if dont_skip: mem.skip = '' else: mem.skip = 'S' mem.mode = _mem.wideband and "FM" or "NFM" 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("bclo", "Busy Channel Lockout", RadioSettingValueList(BCLO_LIST, BCLO_LIST[_mem.bclo])) mem.extra.append(rs) rs = RadioSetting("apro", "APRO", RadioSettingValueList(APRO_LIST, APRO_LIST[_mem.apro])) mem.extra.append(rs) rs = RadioSetting("rpt_md", "Repeater Mode", RadioSettingValueList(RPTMD_LIST, RPTMD_LIST[_mem.rpt_md])) mem.extra.append(rs) return mem def set_memory(self, mem): _mem, _name = self._get_memobjs(mem.number) e = self._memobj.enable[(mem.number - 1) / 8] s = self._memobj.skip[(mem.number - 1) / 8] if mem.empty: _mem.set_raw("\xFF" * 32) e.flags[7 - ((mem.number - 1) % 8)] = False s.flags[7 - ((mem.number - 1) % 8)] = False return else: e.flags[7 - ((mem.number - 1) % 8)] = True if _mem.get_raw() == ("\xFF" * 32): LOG.debug("Initializing empty memory") _mem.set_raw("\x00" * 32) _mem.rx_freq = mem.freq / 10 if 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 _mem.duplex = THUVF8D_DUPLEX.index(mem.duplex) _mem.offset = mem.offset / 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) _mem.name = mem.name.rstrip(' ').ljust(7, "\xFF") flag_index = 7 - ((mem.number - 1) % 8) s.flags[flag_index] = (mem.skip == "") _mem.wideband = mem.mode == "FM" _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("basic", "Basic") top = RadioSettings(group) group.append(RadioSetting( "mode", "Mode", RadioSettingValueList( MODE_LIST, MODE_LIST[_settings.mode]))) group.append(RadioSetting( "ab_switch", "A/B", RadioSettingValueList( AB_LIST, AB_LIST[_settings.ab_switch]))) group.append(RadioSetting( "a_channel", "A Selected Memory", RadioSettingValueInteger(1, 128, _settings.a_channel + 1))) group.append(RadioSetting( "b_channel", "B Selected Memory", RadioSettingValueInteger(1, 128, _settings.b_channel + 1))) group.append(RadioSetting( "a_display", "A Channel Display", RadioSettingValueList( DISPLAY_LIST, DISPLAY_LIST[_settings.a_display]))) group.append(RadioSetting( "b_display", "B Channel Display", RadioSettingValueList( DISPLAY_LIST, DISPLAY_LIST[_settings.b_display]))) group.append(RadioSetting( "tx_sel", "Priority Transmit", RadioSettingValueList( TXSEL_LIST, TXSEL_LIST[_settings.tx_sel]))) group.append(RadioSetting( "vox_level", "VOX Level", RadioSettingValueList( VOX_LIST, VOX_LIST[_settings.vox_level]))) group.append(RadioSetting( "squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch))) group.append(RadioSetting( "dwait", "Dual Wait", RadioSettingValueBoolean(_settings.dwait))) 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( "beep", "Beep", RadioSettingValueBoolean(_settings.beep))) group.append(RadioSetting( "ani", "ANI", RadioSettingValueBoolean(_settings.ani))) 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))) def _filter(name): return str(name).rstrip("\xFF").rstrip() group.append(RadioSetting( "ponmsg", "Power-On Message", RadioSettingValueString(0, 7, _filter(_settings.ponmsg)))) group.append(RadioSetting( "scan_mode", "Scan Mode", RadioSettingValueList( SCAN_MODE_LIST, SCAN_MODE_LIST[_settings.scan_mode]))) group.append(RadioSetting( "autolk", "Auto Lock", RadioSettingValueBoolean(_settings.autolk))) group.append(RadioSetting( "lock_mode", "Keypad Lock Mode", RadioSettingValueList( LOCK_MODE_LIST, LOCK_MODE_LIST[_settings.lock_mode]))) group.append(RadioSetting( "voice", "Voice Prompt", RadioSettingValueBoolean(_settings.voice))) group.append(RadioSetting( "opnmsg", "Opening Message", RadioSettingValueList( OPNMSG_LIST, OPNMSG_LIST[_settings.opnmsg]))) group.append(RadioSetting( "tuning_step", "Tuning Step", RadioSettingValueList( TUNING_STEPS_LIST, TUNING_STEPS_LIST[_settings.tuning_step]))) group.append(RadioSetting( "lamp_t", "Backlight Timeout", RadioSettingValueList( BACKLIGHT_TIMEOUT_LIST, BACKLIGHT_TIMEOUT_LIST[_settings.lamp_t]))) group.append(RadioSetting( "a_work_area", "A Work Area", RadioSettingValueList( AB_LIST, AB_LIST[_settings.a_work_area]))) group.append(RadioSetting( "b_work_area", "B Work Area", RadioSettingValueList( AB_LIST, AB_LIST[_settings.b_work_area]))) return top group.append(RadioSetting( "disnm", "Display Name", RadioSettingValueBoolean(_settings.disnm))) return group def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if element.get_name() == 'rxsave': if bool(element.value.get_value()): _settings.rxsave = 3 else: _settings.rxsave = 0 continue if element.get_name().endswith('_channel'): LOG.debug(element.value, type(element.value)) setattr(_settings, element.get_name(), int(element.value) - 1) continue if not isinstance(element, RadioSetting): self.set_settings(element) continue setattr(_settings, element.get_name(), element.value) chirp-daily-20170714/chirp/drivers/anytone.py0000644000016101777760000003761712646374217022227 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 . import os import struct import time import logging from chirp import bitwise from chirp import chirp_common from chirp import directory from chirp import errors from chirp import memmap from chirp import util from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \ RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean LOG = logging.getLogger(__name__) _mem_format = """ #seekto 0x0100; struct { u8 even_unknown:2, even_pskip:1, even_skip:1, odd_unknown:2, odd_pskip:1, odd_skip:1; } flags[379]; """ mem_format = _mem_format + """ struct memory { bbcd freq[4]; bbcd offset[4]; u8 unknownA:4, tune_step:4; u8 rxdcsextra:1, txdcsextra:1, rxinv:1, txinv:1, channel_width:2, unknownB:2; u8 unknown8:3, is_am:1, power:2, duplex:2; u8 unknown4:4, rxtmode:2, txtmode:2; u8 unknown5:2, txtone:6; u8 unknown6:2, rxtone:6; u8 txcode; u8 rxcode; u8 unknown7[2]; u8 unknown2[5]; char name[7]; u8 unknownZ[2]; }; #seekto 0x0030; struct { char serial[16]; } serial_no; #seekto 0x0050; struct { char date[16]; } version; #seekto 0x0280; struct { u8 unknown1:6, display:2; u8 unknown2[11]; u8 unknown3:3, apo:5; u8 unknown4a[2]; u8 unknown4b:6, mute:2; u8 unknown4; u8 unknown5:5, beep:1, unknown6:2; u8 unknown[334]; char welcome[8]; } settings; #seekto 0x0540; struct memory memblk1[12]; #seekto 0x2000; struct memory memory[758]; #seekto 0x7ec0; struct memory memblk2[10]; """ class FlagObj(object): def __init__(self, flagobj, which): self._flagobj = flagobj self._which = which def _get(self, flag): return getattr(self._flagobj, "%s_%s" % (self._which, flag)) def _set(self, flag, value): return setattr(self._flagobj, "%s_%s" % (self._which, flag), value) def get_skip(self): return self._get("skip") def set_skip(self, value): self._set("skip", value) skip = property(get_skip, set_skip) def get_pskip(self): return self._get("pskip") def set_pskip(self, value): self._set("pskip", value) pskip = property(get_pskip, set_pskip) def set(self): self._set("unknown", 3) self._set("skip", 1) self._set("pskip", 1) def clear(self): self._set("unknown", 0) self._set("skip", 0) self._set("pskip", 0) def get(self): return (self._get("unknown") << 2 | self._get("skip") << 1 | self._get("pskip")) def __repr__(self): return repr(self._flagobj) def _is_loc_used(memobj, loc): return memobj.flags[loc / 2].get_raw() != "\xFF" def _addr_to_loc(addr): return (addr - 0x2000) / 32 def _should_send_addr(memobj, addr): if addr < 0x2000 or addr >= 0x7EC0: return True else: return _is_loc_used(memobj, _addr_to_loc(addr)) def _echo_write(radio, data): try: radio.pipe.write(data) radio.pipe.read(len(data)) except Exception, e: LOG.error("Error writing to radio: %s" % e) raise errors.RadioError("Unable to write to radio") def _read(radio, length): try: data = radio.pipe.read(length) except Exception, e: LOG.error("Error reading from radio: %s" % e) raise errors.RadioError("Unable to read from radio") if len(data) != length: LOG.error("Short read from radio (%i, expected %i)" % (len(data), length)) LOG.debug(util.hexprint(data)) raise errors.RadioError("Short read from radio") return data valid_model = ['QX588UV', 'HR-2040', 'DB-50M\x00', 'DB-750X'] def _ident(radio): radio.pipe.timeout = 1 _echo_write(radio, "PROGRAM") response = radio.pipe.read(3) if response != "QX\x06": LOG.debug("Response was:\n%s" % util.hexprint(response)) raise errors.RadioError("Unsupported model") _echo_write(radio, "\x02") response = radio.pipe.read(16) LOG.debug(util.hexprint(response)) if response[1:8] not in valid_model: LOG.debug("Response was:\n%s" % util.hexprint(response)) raise errors.RadioError("Unsupported model") def _finish(radio): endframe = "\x45\x4E\x44" _echo_write(radio, endframe) result = radio.pipe.read(1) if result != "\x06": LOG.debug("Got:\n%s" % util.hexprint(result)) raise errors.RadioError("Radio did not finish cleanly") def _checksum(data): cs = 0 for byte in data: cs += ord(byte) return cs % 256 def _send(radio, cmd, addr, length, data=None): frame = struct.pack(">cHb", cmd, addr, length) if data: frame += data frame += chr(_checksum(frame[1:])) frame += "\x06" _echo_write(radio, frame) LOG.debug("Sent:\n%s" % util.hexprint(frame)) if data: result = radio.pipe.read(1) if result != "\x06": LOG.debug("Ack was: %s" % repr(result)) raise errors.RadioError( "Radio did not accept block at %04x" % addr) return result = _read(radio, length + 6) LOG.debug("Got:\n%s" % util.hexprint(result)) header = result[0:4] data = result[4:-2] ack = result[-1] if ack != "\x06": LOG.debug("Ack was: %s" % repr(ack)) raise errors.RadioError("Radio NAK'd block at %04x" % addr) _cmd, _addr, _length = struct.unpack(">cHb", header) if _addr != addr or _length != _length: LOG.debug("Expected/Received:") LOG.debug(" Length: %02x/%02x" % (length, _length)) LOG.debug(" Addr: %04x/%04x" % (addr, _addr)) raise errors.RadioError("Radio send unexpected block") cs = _checksum(result[1:-2]) if cs != ord(result[-2]): LOG.debug("Calculated: %02x" % cs) LOG.debug("Actual: %02x" % ord(result[-2])) raise errors.RadioError("Block at 0x%04x failed checksum" % addr) return data def _download(radio): _ident(radio) memobj = None data = "" for start, end in radio._ranges: for addr in range(start, end, 0x10): if memobj is not None and not _should_send_addr(memobj, addr): block = "\xFF" * 0x10 else: block = _send(radio, 'R', addr, 0x10) data += block status = chirp_common.Status() status.cur = len(data) status.max = end status.msg = "Cloning from radio" radio.status_fn(status) if addr == 0x19F0: memobj = bitwise.parse(_mem_format, data) _finish(radio) return memmap.MemoryMap(data) def _upload(radio): _ident(radio) for start, end in radio._ranges: for addr in range(start, end, 0x10): if addr < 0x0100: continue if not _should_send_addr(radio._memobj, addr): continue block = radio._mmap[addr:addr + 0x10] _send(radio, 'W', addr, len(block), block) status = chirp_common.Status() status.cur = addr status.max = end status.msg = "Cloning to radio" radio.status_fn(status) _finish(radio) TONES = [62.5] + list(chirp_common.TONES) TMODES = ['', 'Tone', 'DTCS', ''] DUPLEXES = ['', '-', '+', ''] MODES = ["FM", "FM", "NFM"] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50), chirp_common.PowerLevel("Mid1", watts=25), chirp_common.PowerLevel("Mid2", watts=10), chirp_common.PowerLevel("Low", watts=5)] @directory.register class AnyTone5888UVRadio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """AnyTone 5888UV""" VENDOR = "AnyTone" MODEL = "5888UV" BAUD_RATE = 9600 _file_ident = "QX588UV" # May try to mirror the OEM behavior later _ranges = [ (0x0000, 0x8000), ] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("The Anytone driver is currently experimental. " "There are no known issues with it, but you should " "proceed with caution.") return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_cross = True rf.has_tuning_step = False rf.has_rx_dtcs = True rf.valid_skips = ["", "S", "P"] rf.valid_modes = ["FM", "NFM", "AM"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = ['Tone->DTCS', 'DTCS->Tone', '->Tone', '->DTCS', 'Tone->Tone'] rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES rf.valid_bands = [(108000000, 500000000)] rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-" rf.valid_name_length = 7 rf.valid_power_levels = POWER_LEVELS rf.memory_bounds = (1, 758) return rf def sync_in(self): self._mmap = _download(self) self.process_mmap() def sync_out(self): _upload(self) def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def _get_memobjs(self, number): number -= 1 _mem = self._memobj.memory[number] _flg = FlagObj(self._memobj.flags[number / 2], number % 2 and "even" or "odd") return _mem, _flg def _get_dcs_index(self, _mem, which): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) return (int(extra) << 8) | int(base) def _set_dcs_index(self, _mem, which, index): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) base.set_value(index & 0xFF) extra.set_value(index >> 8) def get_raw_memory(self, number): _mem, _flg = self._get_memobjs(number) return repr(_mem) + repr(_flg) def get_memory(self, number): _mem, _flg = self._get_memobjs(number) mem = chirp_common.Memory() mem.number = number if _flg.get() == 0x0F: mem.empty = True return mem mem.freq = int(_mem.freq) * 100 mem.offset = int(_mem.offset) * 100 mem.name = str(_mem.name).rstrip() mem.duplex = DUPLEXES[_mem.duplex] mem.mode = _mem.is_am and "AM" or MODES[_mem.channel_width] rxtone = txtone = None rxmode = TMODES[_mem.rxtmode] txmode = TMODES[_mem.txtmode] if txmode == "Tone": txtone = TONES[_mem.txtone] elif txmode == "DTCS": txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem, 'tx')] if rxmode == "Tone": rxtone = TONES[_mem.rxtone] elif rxmode == "DTCS": rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem, 'rx')] rxpol = _mem.rxinv and "R" or "N" txpol = _mem.txinv and "R" or "N" chirp_common.split_tone_decode(mem, (txmode, txtone, txpol), (rxmode, rxtone, rxpol)) mem.skip = _flg.get_skip() and "S" or _flg.get_pskip() and "P" or "" mem.power = POWER_LEVELS[_mem.power] return mem def set_memory(self, mem): _mem, _flg = self._get_memobjs(mem.number) if mem.empty: _flg.set() return _flg.clear() _mem.set_raw("\x00" * 32) _mem.freq = mem.freq / 100 _mem.offset = mem.offset / 100 _mem.name = mem.name.ljust(7) _mem.is_am = mem.mode == "AM" _mem.duplex = DUPLEXES.index(mem.duplex) try: _mem.channel_width = MODES.index(mem.mode) except ValueError: _mem.channel_width = 0 ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem) _mem.txtmode = TMODES.index(txmode) _mem.rxtmode = TMODES.index(rxmode) if txmode == "Tone": _mem.txtone = TONES.index(txtone) elif txmode == "DTCS": self._set_dcs_index(_mem, 'tx', chirp_common.ALL_DTCS_CODES.index(txtone)) if rxmode == "Tone": _mem.rxtone = TONES.index(rxtone) elif rxmode == "DTCS": self._set_dcs_index(_mem, 'rx', chirp_common.ALL_DTCS_CODES.index(rxtone)) _mem.txinv = txpol == "R" _mem.rxinv = rxpol == "R" _flg.set_skip(mem.skip == "S") _flg.set_pskip(mem.skip == "P") if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") settings = RadioSettings(basic) display = ["Frequency", "Channel", "Name"] rs = RadioSetting("display", "Display", RadioSettingValueList(display, display[_settings.display])) basic.append(rs) apo = ["Off"] + ['%.1f hour(s)' % (0.5 * x) for x in range(1, 25)] rs = RadioSetting("apo", "Automatic Power Off", RadioSettingValueList(apo, apo[_settings.apo])) basic.append(rs) def filter(s): s_ = "" for i in range(0, 8): c = str(s[i]) s_ += (c if c in chirp_common.CHARSET_ASCII else "") return s_ rs = RadioSetting("welcome", "Welcome Message", RadioSettingValueString(0, 8, filter(_settings.welcome))) basic.append(rs) rs = RadioSetting("beep", "Beep Enabled", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) mute = ["Off", "TX", "RX", "TX/RX"] rs = RadioSetting("mute", "Sub Band Mute", RadioSettingValueList(mute, mute[_settings.mute])) basic.append(rs) return settings def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue name = element.get_name() setattr(_settings, name, element.value) @classmethod def match_model(cls, filedata, filename): return cls._file_ident in filedata[0x20:0x40] @directory.register class IntekHR2040Radio(AnyTone5888UVRadio): """Intek HR-2040""" VENDOR = "Intek" MODEL = "HR-2040" _file_ident = "HR-2040" @directory.register class PolmarDB50MRadio(AnyTone5888UVRadio): """Polmar DB-50M""" VENDOR = "Polmar" MODEL = "DB-50M" _file_ident = "DB-50M" @directory.register class PowerwerxDB750XRadio(AnyTone5888UVRadio): """Powerwerx DB-750X""" VENDOR = "Powerwerx" MODEL = "DB-750X" _file_ident = "DB-750X" def get_settings(self): return {} chirp-daily-20170714/chirp/drivers/ic2200.py0000644000016101777760000002063412476006422021427 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 . from chirp.drivers import icf from chirp import chirp_common, util, directory, 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-daily-20170714/chirp/drivers/ft2800.py0000644000016101777760000001761412726733400021460 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 import os import logging from chirp import util, memmap, chirp_common, bitwise, directory, errors from yaesu_clone import YaesuCloneModeRadio LOG = logging.getLogger(__name__) 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 LOG.debug("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) LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk))) if len(chunk) == 8: LOG.debug("END?") elif len(chunk) != 38: LOG.debug("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) LOG.debug("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 LOG.debug("What is this garbage?\n%s" % util.hexprint(data)) _send(radio.pipe, IDBLOCK) time.sleep(1) ack = radio.pipe.read(300) LOG.debug("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) LOG.debug("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.parity = "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) LOG.info("Downloaded in %.2f sec" % (time.time() - start)) self.process_mmap() def sync_out(self): self.pipe.timeout = 1 self.pipe.parity = "E" start = time.time() try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) LOG.info("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-daily-20170714/chirp/drivers/tmv71_ll.py0000644000016101777760000002142012476257220022173 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 struct import time import logging from chirp import memmap, chirp_common, errors LOG = logging.getLogger(__name__) 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 = "" LOG.debug("PC->V71: %s" % cmd) s.write(cmd + "\r") while not data.endswith("\r") and (time.time() - start) < timeout: data += s.read(1) LOG.debug("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-daily-20170714/chirp/drivers/ft2d.py0000644000016101777760000001145113127357775021402 0ustar jenkinsnogroup00000000000000# Copyright 2010 Dan Smith # Portions Copyright 2017 Wade Simmons # Copyright 2017 Declan Rieb # # 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 logging from textwrap import dedent from chirp.drivers import yaesu_clone, ft1d from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSettings # Differences from Yaesu FT1D # 999 memories, but 901-999 are only for skipping VFO frequencies # Text in memory and memory bank structures is ASCII encoded # Expanded modes # Slightly different clone-mode instructions LOG = logging.getLogger(__name__) TMODES = ["", "Tone", "TSQL", "DTCS", "RTone", "JRfrq", "PRSQL", "Pager"] class FT2Bank(chirp_common.NamedBank): # Like FT1D except for name in ASCII def get_name(self): _bank = self._model._radio._memobj.bank_info[self.index] name = "" for i in _bank.name: if i == 0xff: break name += chr(i & 0xFF) return name.rstrip() def set_name(self, name): _bank = self._model._radio._memobj.bank_info[self.index] _bank.name = [ord(x) for x in name.ljust(16, chr(0xFF))[:16]] class FT2BankModel(ft1d.FT1BankModel): #just need this one to launch FT2Bank """A FT1D bank model""" def __init__(self, radio, name='Banks'): super(FT2BankModel, self).__init__(radio, name) _banks = self._radio._memobj.bank_info self._bank_mappings = [] for index, _bank in enumerate(_banks): bank = FT2Bank(self, "%i" % index, "BANK-%i" % index) bank.index = index self._bank_mappings.append(bank) @directory.register class FT2D(ft1d.FT1Radio): """Yaesu FT-2D""" BAUD_RATE = 38400 VENDOR = "Yaesu" MODEL = "FT2D" # Yaesu doesn't use a hyphen in its documents VARIANT = "R" _model = "AH60M" # Get this from chirp .img file after saving once _has_vibrate = True _mem_params = (999, # size of memories array 999, # size of flags array 0xFECA, # APRS beacon metadata address. 60, # Number of beacons stored. 0x1064A, # APRS beacon content address. 134, # Length of beacon data stored. 60) # Number of beacons stored. @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA terminal. 3. Press and hold [DISP] key while turning on radio ("CLONE" will appear on the display). 4. After clicking OK here in chirp, press the [Send] screen button.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA terminal. 3. Press and hold in [DISP] key while turning on radio ("CLONE" will appear on radio LCD). 4. Press [RECEIVE] screen button ("-WAIT-" will appear on radio LCD). 5. Finally, press OK button below.""")) return rp def get_features(self): # AFAICT only TMODES & memory bounds are different rf = super(FT2D, self).get_features() rf.valid_tmodes = list(TMODES) rf.memory_bounds = (1, 999) return rf def get_bank_model(self): # here only to launch the bank model return FT2BankModel(self) def get_memory(self, number): mem = super(FT2D, self).get_memory(number) flag = self._memobj.flag[number - 1] if number >= 901 and number <= 999: # for FT2D; enforces skip mem.skip = "S" flag.skip = True return mem def _decode_label(self, mem): return str(mem.label).rstrip("\xFF").decode('ascii', 'replace') def _encode_label(self, mem): label = mem.name.rstrip().encode('ascii', 'ignore') return self._add_ff_pad(label, 16) def set_memory(self, mem): flag = self._memobj.flag[mem.number - 1] if mem.number >= 901 and mem.number <= 999: # for FT2D; enforces skip flag.skip = True mem.skip = "S" super(FT2D, self).set_memory(mem)chirp-daily-20170714/chirp/drivers/vx3.py0000644000016101777760000010142113101305576021240 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.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings from textwrap import dedent import os import re import logging LOG = logging.getLogger(__name__) # interesting offsets which may be checksums needed later # 0x0393 checksum1? # 0x0453 checksum1a? # 0x0409 checksum2? # 0x04C9 checksum2a? MEM_FORMAT = """ #seekto 0x7F4A; u8 checksum; #seekto 0x024A; struct { u8 unk01_1:3, att_broadcast:1, att_marine:1, unk01_2:2 att_wx:1; u8 unk02; u8 apo; u8 arts_beep; u8 unk04_1; u8 beep_level; u8 beep_mode; u8 unk04_2; u8 arts_cwid[16]; u8 unk05[10]; u8 channel_counter; u8 unk06_1[2]; u8 dtmf_delay; u8 dtmf_chan_active; u8 unk06_2[5]; u8 emergency_eai_time; u8 emergency_signal; u8 unk07[30]; u8 fw_key_timer; u8 internet_key; u8 lamp; u8 lock_mode; u8 my_key; u8 mic_gain; u8 mem_ch_step; u8 unk08[3]; u8 sql_fm; u8 sql_wfm; u8 radio_am_sql; u8 radio_fm_sql; u8 on_timer; u8 openmsg_mode; u8 openmsg[6]; u8 pager_rxtone1; u8 pager_rxtone2; u8 pager_txtone1; u8 pager_txtone2; u8 password[4]; u8 unk10; u8 priority_time; u8 ptt_delay; u8 rx_save; u8 scan_resume; u8 scan_restart; u8 sub_rx_timer; u8 unk11[7]; u8 tot; u8 wake_up; u8 unk12[2]; u8 vfo_mode:1, arts_cwid_enable:1, scan_lamp:1, fast_tone_search:1, ars:1, dtmf_speed:1, split_tone:1, dtmf_autodialer:1; u8 busy_led:1, tone_search_mute:1, unk14_1:1, bclo:1, band_edge_beep:1, unk14_2:2, txsave:1; u8 unk15_1:2, smart_search:1, emergency_eai:1, unk15_2:2, hm_rv:1, moni_tcall:1; u8 lock:1, unk16_1:1, arts:1, arts_interval:1, unk16_2:1, protect_memory:1, unk16_3:1, mem_storage:1; u8 vol_key_mode:1, unk17_1:2, wx_alert:1, temp_unit:1, unk17_2:2, password_active:1; u8 fm_broadcast_mode:1, fm_antenna:1, am_antenna:1, fm_speaker_out:1, home_vfo:1, unk18_1:2, priority_revert:1; } settings; // banks? #seekto 0x034D; u8 banks_unk1; #seekto 0x0356; struct { u32 unmask; } banks_unmask1; #seekto 0x0409; u8 banks_unk3; #seekto 0x0416; struct { u32 unmask; } banks_unmask2; #seekto 0x04CA; struct { u8 memory[16]; } dtmf[10]; #seekto 0x0B7A; struct { u8 name[6]; } bank_names[24]; #seekto 0x0E0A; struct { u16 channels[100]; } banks[24]; #seekto 0x02EE; struct { u16 in_use; } bank_used[24]; #seekto 0x03FE; struct { u8 speaker; u8 earphone; } volumes; #seekto 0x20CA; struct { u8 even_pskip:1, even_skip:1, even_valid:1, // TODO: should be "showname", i.e., show alpha name even_masked:1, odd_pskip:1, odd_skip:1, odd_valid:1, odd_masked:1; } flags[999]; #seekto 0x244A; struct { u8 unknown1a:2, txnarrow:1, clockshift:1, unknown1b:4; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown2:4, tmode:2; // TODO: tmode should be 6 bits (extended tone modes) u8 name[6]; bbcd offset[3]; u8 unknown3:2, tone:6; u8 unknown4:1, dcs:7; u8 unknown5; u8 smetersquelch; u8 unknown7a:2, attenuate:1, unknown7b:1, automode:1, unknown8:1, bell:2; } memory[999]; """ # fix auto mode setting and auto step setting DUPLEX = ["", "-", "+", "split"] MODES = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio TMODES = ["", "Tone", "TSQL", "DTCS"] # TODO: TMODES = ["", "Tone, "TSQL", "DTCS", "Rev Tone", "User Tone", "Pager", # "Message", "D Code", "Tone/DTCS", "DTCS/Tone"] # 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)) DTMFCHARSET = list("0123456789ABCD*#") 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.rstrip() 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_mappings(self): return len(self.get_mappings()) def get_mappings(self): banks = self._radio._memobj.banks bank_mappings = [] for index, _bank in enumerate(banks): bank = VX3Bank(self, "%i" % index, "b%i" % (index + 1)) bank.index = index bank_mappings.append(bank) return bank_mappings def _get_channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return set() _members = self._radio._memobj.banks[bank.index] return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF]) def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.banks[bank.index] if len(channels_in_bank) > len(_members.channels): raise Exception("Too many entries in bank %d" % bank.index) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channels[index] = channel_number - 1 empty = index + 1 for index in range(empty, len(_members.channels)): _members.channels[index] = 0xFFFF def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = ((len(channels_in_bank) - 1) * 2) _banks_unmask1 = self._radio._memobj.banks_unmask1 _banks_unmask2 = self._radio._memobj.banks_unmask2 _banks_unmask1.unmask = 0x0017FFFF _banks_unmask2.unmask = 0x0017FFFF def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] if channels_in_bank: _bank_used.in_use = ((len(channels_in_bank) - 1) * 2) else: _bank_used.in_use = 0xFFFF def get_mapping_memories(self, bank): memories = [] for channel in self._get_channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._get_channel_numbers_in_bank(bank): 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.unknown7a = 0b0 mem.unknown7b = 0b1 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 41 seconds _block_size = 32 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [BAND] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [V/M] key ("-WAIT-" will appear on the LCD).""")) return rp 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 = True 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, 999) 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]) 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) if _mem.txnarrow and _mem.mode == MODES.index("FM"): # FM narrow mem.mode = "NFM" else: 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 _flag["%s_valid" % nibble] = True _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.txnarrow = True else: _mem.mode = MODES.index(mem.mode) _mem.txnarrow = False _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) def _decode_chars(self, inarr): LOG.debug("@_decode_chars, type: %s" % type(inarr)) LOG.debug(inarr) outstr = "" for i in inarr: if i == 0xFF: break outstr += CHARSET[i & 0x7F] return outstr.rstrip() def _encode_chars(self, instr, length=16): LOG.debug("@_encode_chars, type: %s" % type(instr)) LOG.debug(instr) outarr = [] instr = str(instr) for i in range(length): if i < len(instr): outarr.append(CHARSET.index(instr[i])) else: outarr.append(0xFF) return outarr def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") sound = RadioSettingGroup("sound", "Sound") dtmf = RadioSettingGroup("dtmf", "DTMF") arts = RadioSettingGroup("arts", "ARTS") eai = RadioSettingGroup("eai", "Emergency") msg = RadioSettingGroup("msg", "Messages") top = RadioSettings(basic, sound, arts, dtmf, eai, msg) basic.append(RadioSetting( "att_wx", "Attenuation WX", RadioSettingValueBoolean(_settings.att_wx))) basic.append(RadioSetting( "att_marine", "Attenuation Marine", RadioSettingValueBoolean(_settings.att_marine))) basic.append(RadioSetting( "att_broadcast", "Attenuation Broadcast", RadioSettingValueBoolean(_settings.att_broadcast))) basic.append(RadioSetting( "ars", "Auto Repeater Shift", RadioSettingValueBoolean(_settings.ars))) basic.append(RadioSetting( "home_vfo", "Home->VFO", RadioSettingValueBoolean(_settings.home_vfo))) basic.append(RadioSetting( "bclo", "Busy Channel Lockout", RadioSettingValueBoolean(_settings.bclo))) basic.append(RadioSetting( "busyled", "Busy LED", RadioSettingValueBoolean(_settings.busy_led))) basic.append(RadioSetting( "fast_tone_search", "Fast Tone search", RadioSettingValueBoolean(_settings.fast_tone_search))) basic.append(RadioSetting( "priority_revert", "Priority Revert", RadioSettingValueBoolean(_settings.priority_revert))) basic.append(RadioSetting( "protect_memory", "Protect memory", RadioSettingValueBoolean(_settings.protect_memory))) basic.append(RadioSetting( "scan_lamp", "Scan Lamp", RadioSettingValueBoolean(_settings.scan_lamp))) basic.append(RadioSetting( "split_tone", "Split tone", RadioSettingValueBoolean(_settings.split_tone))) basic.append(RadioSetting( "tone_search_mute", "Tone search mute", RadioSettingValueBoolean(_settings.tone_search_mute))) basic.append(RadioSetting( "txsave", "TX save", RadioSettingValueBoolean(_settings.txsave))) basic.append(RadioSetting( "wx_alert", "WX Alert", RadioSettingValueBoolean(_settings.wx_alert))) opts = ["Bar Int", "Bar Ext"] basic.append(RadioSetting( "am_antenna", "AM antenna", RadioSettingValueList(opts, opts[_settings.am_antenna]))) opts = ["Ext Ant", "Earphone"] basic.append(RadioSetting( "fm_antenna", "FM antenna", RadioSettingValueList(opts, opts[_settings.fm_antenna]))) opts = ["off"] + ["%0.1f" % (t / 60.0) for t in range(30, 750, 30)] basic.append(RadioSetting( "apo", "APO time (hrs)", RadioSettingValueList(opts, opts[_settings.apo]))) opts = ["+/- 5 MHZ", "+/- 10 MHZ", "+/- 50 MHZ", "+/- 100 MHZ"] basic.append(RadioSetting( "channel_counter", "Channel counter", RadioSettingValueList(opts, opts[_settings.channel_counter]))) opts = ["0.3", "0.5", "0.7", "1.0", "1.5"] basic.append(RadioSetting( "fw_key_timer", "FW key timer (s)", RadioSettingValueList(opts, opts[_settings.fw_key_timer]))) opts = ["Home", "Reverse"] basic.append(RadioSetting( "hm_rv", "HM/RV key", RadioSettingValueList(opts, opts[_settings.hm_rv]))) opts = ["%d" % t for t in range(2, 11)] + ["continuous", "off"] basic.append(RadioSetting( "lamp", "Lamp Timer (s)", RadioSettingValueList(opts, opts[_settings.lamp]))) basic.append(RadioSetting( "lock", "Lock", RadioSettingValueBoolean(_settings.lock))) opts = ["key", "ptt", "key+ptt"] basic.append(RadioSetting( "lock_mode", "Lock mode", RadioSettingValueList(opts, opts[_settings.lock_mode]))) opts = ["10", "20", "50", "100"] basic.append(RadioSetting( "mem_ch_step", "Memory Chan step", RadioSettingValueList(opts, opts[_settings.mem_ch_step]))) opts = ["lower", "next"] basic.append(RadioSetting( "mem_storage", "Memory storage mode", RadioSettingValueList(opts, opts[_settings.mem_storage]))) opts = ["%d" % t for t in range(1, 10)] basic.append(RadioSetting( "mic_gain", "Mic gain", RadioSettingValueList(opts, opts[_settings.mic_gain]))) opts = ["monitor", "tone call"] basic.append(RadioSetting( "moni_tcall", "Moni/TCall button", RadioSettingValueList(opts, opts[_settings.moni_tcall]))) opts = ["off"] + \ ["%02d:%02d" % (t / 60, t % 60) for t in range(10, 1450, 10)] basic.append(RadioSetting( "on_timer", "On Timer (hrs)", RadioSettingValueList(opts, opts[_settings.on_timer]))) opts2 = ["off"] + \ ["0.%d" % t for t in range(1, 10)] + \ ["%1.1f" % (t / 10.0) for t in range(10, 105, 5)] basic.append(RadioSetting( "priority_time", "Priority time", RadioSettingValueList(opts2, opts2[_settings.priority_time]))) opts = ["off", "20", "50", "100", "200"] basic.append(RadioSetting( "ptt_delay", "PTT delay (ms)", RadioSettingValueList(opts, opts[_settings.ptt_delay]))) basic.append(RadioSetting( "rx_save", "RX save (s)", RadioSettingValueList(opts2, opts2[_settings.rx_save]))) basic.append(RadioSetting( "scan_restart", "Scan restart (s)", RadioSettingValueList(opts2, opts2[_settings.scan_restart]))) opts = ["%1.1f" % (t / 10.0) for t in range(20, 105, 5)] + \ ["busy", "hold"] basic.append(RadioSetting( "scan_resume", "Scan resume (s)", RadioSettingValueList(opts, opts[_settings.scan_resume]))) opts = ["single", "continuous"] basic.append(RadioSetting( "smart_search", "Smart search", RadioSettingValueList(opts, opts[_settings.smart_search]))) opts = ["off"] + ["TRX %d" % t for t in range(1, 11)] + ["hold"] + \ ["TX %d" % t for t in range(1, 11)] basic.append(RadioSetting( "sub_rx_timer", "Sub RX timer", RadioSettingValueList(opts, opts[_settings.sub_rx_timer]))) opts = ["C", "F"] basic.append(RadioSetting( "temp_unit", "Temperature unit", RadioSettingValueList(opts, opts[_settings.temp_unit]))) opts = ["off"] + ["%1.1f" % (t / 10.0) for t in range(5, 105, 5)] basic.append(RadioSetting( "tot", "Time-out timer (mins)", RadioSettingValueList(opts, opts[_settings.tot]))) opts = ["all", "band"] basic.append(RadioSetting( "vfo_mode", "VFO mode", RadioSettingValueList(opts, opts[_settings.vfo_mode]))) opts = ["off"] + ["%d" % t for t in range(5, 65, 5)] + ["EAI"] basic.append(RadioSetting( "wake_up", "Wake up (s)", RadioSettingValueList(opts, opts[_settings.wake_up]))) opts = ["hold", "3 secs"] basic.append(RadioSetting( "vol_key_mode", "Volume key mode", RadioSettingValueList(opts, opts[_settings.vol_key_mode]))) # subgroup programmable keys opts = ["INTNET", "INT MR", "Set Mode (my key)"] basic.append(RadioSetting( "internet_key", "Internet key", RadioSettingValueList(opts, opts[_settings.internet_key]))) keys = ["Antenna AM", "Antenna FM", "Antenna Attenuator", "Auto Power Off", "Auto Repeater Shift", "ARTS Beep", "ARTS Interval", "Busy Channel Lockout", "Bell Ringer", "Bell Select", "Bank Name", "Band Edge Beep", "Beep Level", "Beep Select", "Beep User", "Busy LED", "Channel Counter", "Clock Shift", "CW ID", "CW Learning", "CW Pitch", "CW Training", "DC Voltage", "DCS Code", "DCS Reverse", "DTMF A/M", "DTMF Delay", "DTMF Set", "DTMF Speed", "EAI Timer", "Emergency Alarm", "Ext Menu", "FW Key", "Half Deviation", "Home/Reverse", "Home > VFO", "INT Code", "INT Conn Mode", "INT A/M", "INT Set", "INT Key", "INTNET", "Lamp", "LED Light", "Lock", "Moni/T-Call", "Mic Gain", "Memory Display", "Memory Write Mode", "Memory Channel Step", "Memory Name Write", "Memory Protect", "Memory Skip", "Message List", "Message Reg", "Message Set", "On Timer", "Open Message", "Pager Answer Back", "Pager Receive Code", "Pager Transmit Code", "Pager Frequency", "Priority Revert", "Priority Timer", "Password", "PTT Delay", "Repeater Shift Direction", "Repeater Shift", "Receive Mode", "Smart Search", "Save Rx", "Save Tx", "Scan Lamp", "Scan Resume", "Scan Restart", "Speaker Out", "Squelch Level", "Squelch Type", "Squelch S Meter", "Squelch Split Tone", "Step", "Stereo", "Sub Rx", "Temp", "Tone Frequency", "Time Out Timer", "Tone Search Mute", "Tone Search Speed", "VFO Band", "VFO Skip", "Volume Mode", "Wake Up", "Weather Alert"] rs = RadioSetting( "my_key", "My key", RadioSettingValueList(keys, keys[_settings.my_key - 16])) # TODO: fix keys list isnt exactly right order # leave disabled in settings for now # basic.append(rs) # sound tab sound.append(RadioSetting( "band_edge_beep", "Band edge beep", RadioSettingValueBoolean(_settings.band_edge_beep))) opts = ["off", "key+scan", "key"] sound.append(RadioSetting( "beep_mode", "Beep mode", RadioSettingValueList(opts, opts[_settings.beep_mode]))) _volumes = self._memobj.volumes opts = map(str, range(0, 33)) sound.append(RadioSetting( "speaker_vol", "Speaker volume", RadioSettingValueList(opts, opts[_volumes.speaker]))) sound.append(RadioSetting( "earphone_vol", "Earphone volume", RadioSettingValueList(opts, opts[_volumes.earphone]))) opts = ["auto", "speaker"] sound.append(RadioSetting( "fm_speaker_out", "FM Speaker out", RadioSettingValueList(opts, opts[_settings.fm_speaker_out]))) opts = ["mono", "stereo"] sound.append(RadioSetting( "fm_broadcast_mode", "FM broadcast mode", RadioSettingValueList( opts, opts[_settings.fm_broadcast_mode]))) opts = map(str, range(16)) sound.append(RadioSetting( "sql_fm", "Squelch level (FM)", RadioSettingValueList(opts, opts[_settings.sql_fm]))) opts = map(str, range(9)) sound.append(RadioSetting( "sql_wfm", "Squelch level (WFM)", RadioSettingValueList(opts, opts[_settings.sql_wfm]))) opts = map(str, range(16)) sound.append(RadioSetting( "radio_am_sql", "Squelch level (Broadcast Radio AM)", RadioSettingValueList(opts, opts[_settings.radio_am_sql]))) opts = map(str, range(9)) sound.append(RadioSetting( "radio_fm_sql", "Squelch level (Broadcast Radio FM)", RadioSettingValueList(opts, opts[_settings.radio_fm_sql]))) # dtmf tab opts = ["manual", "auto"] dtmf.append(RadioSetting( "dtmf_autodialer", "DTMF autodialer mode", RadioSettingValueList(opts, opts[_settings.dtmf_autodialer]))) opts = ["50", "250", "450", "750", "1000"] dtmf.append(RadioSetting( "dtmf_delay", "DTMF delay (ms)", RadioSettingValueList(opts, opts[_settings.dtmf_delay]))) opts = ["50", "100"] dtmf.append(RadioSetting( "dtmf_speed", "DTMF speed (ms)", RadioSettingValueList(opts, opts[_settings.dtmf_speed]))) opts = map(str, range(10)) dtmf.append(RadioSetting( "dtmf_chan_active", "DTMF active", RadioSettingValueList( opts, opts[_settings.dtmf_chan_active]))) for i in range(10): name = "dtmf" + str(i) dtmfsetting = self._memobj.dtmf[i] dtmfstr = "" for c in dtmfsetting.memory: if c < len(DTMFCHARSET): dtmfstr += DTMFCHARSET[c] LOG.debug(dtmfstr) dtmfentry = RadioSettingValueString(0, 16, dtmfstr) dtmfentry.set_charset(DTMFCHARSET + list(" ")) rs = RadioSetting(name, name.upper(), dtmfentry) dtmf.append(rs) # arts tab arts.append(RadioSetting( "arts", "ARTS", RadioSettingValueBoolean(_settings.arts))) opts = ["off", "in range", "always"] arts.append(RadioSetting( "arts_beep", "ARTS beep", RadioSettingValueList(opts, opts[_settings.arts_beep]))) opts = ["15", "25"] arts.append(RadioSetting( "arts_interval", "ARTS interval", RadioSettingValueList(opts, opts[_settings.arts_interval]))) arts.append(RadioSetting( "arts_cwid_enable", "CW ID", RadioSettingValueBoolean(_settings.arts_cwid_enable))) cwid = RadioSettingValueString( 0, 16, self._decode_chars(_settings.arts_cwid.get_value())) cwid.set_charset(CHARSET) arts.append(RadioSetting("arts_cwid", "CW ID", cwid)) # EAI tab eai.append(RadioSetting( "emergency_eai", "EAI", RadioSettingValueBoolean(_settings.emergency_eai))) opts = ["interval %dm" % t for t in range(1, 10)] + \ ["interval %dm" % t for t in range(10, 55, 5)] + \ ["continuous %dm" % t for t in range(1, 10)] + \ ["continuous %dm" % t for t in range(10, 55, 5)] eai.append(RadioSetting( "emergency_eai_time", "EAI time", RadioSettingValueList( opts, opts[_settings.emergency_eai_time]))) opts = ["beep", "strobe", "beep+strobe", "beam", "beep+beam", "cw", "beep+cw", "cwt"] eai.append(RadioSetting( "emergency_signal", "emergency signal", RadioSettingValueList( opts, opts[_settings.emergency_signal]))) # msg tab opts = ["off", "dc voltage", "message"] msg.append(RadioSetting( "openmsg_mode", "Opening message mode", RadioSettingValueList(opts, opts[_settings.openmsg_mode]))) openmsg = RadioSettingValueString( 0, 6, self._decode_chars(_settings.openmsg.get_value())) openmsg.set_charset(CHARSET) msg.append(RadioSetting("openmsg", "Opening Message", openmsg)) return top def set_settings(self, uisettings): for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() _settings = self._memobj.settings if re.match('dtmf\d', setting): # set dtmf fields dtmfstr = str(element.value).strip() newval = [] for i in range(0, 16): if i < len(dtmfstr): newval.append(DTMFCHARSET.index(dtmfstr[i])) else: newval.append(0xFF) LOG.debug(newval) idx = int(setting[-1:]) _settings = self._memobj.dtmf[idx] _settings.memory = newval continue if re.match('.*_vol$', setting): # volume fields voltype = re.sub('_vol$', '', setting) setattr(self._memobj.volumes, voltype, element.value) continue if setting == "my_key": # my_key is memory is off by 9 from list, beware hacks! opts = element.value.get_options() optsidx = opts.index(element.value.get_value()) idx = optsidx + 16 setattr(_settings, "my_key", idx) continue oldval = getattr(_settings, setting) newval = element.value if setting == "arts_cwid": newval = self._encode_chars(newval) if setting == "openmsg": newval = self._encode_chars(newval, 6) LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/bj9900.py0000644000016101777760000003162712611363200021440 0ustar jenkinsnogroup00000000000000# # Copyright 2015 Marco Filippi IZ3GME # # 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 . """Baojie BJ-9900 management module""" from chirp import chirp_common, util, memmap, errors, directory, bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings import struct import time import logging from textwrap import dedent LOG = logging.getLogger(__name__) CMD_ACK = 0x06 @directory.register class BJ9900Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baojie BJ-9900""" VENDOR = "Baojie" MODEL = "BJ-9900" VARIANT = "" BAUD_RATE = 115200 DUPLEX = ["", "-", "+", "split"] MODES = ["NFM", "FM"] TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"] CROSS_MODES = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] STEPS = [5.0, 6.25, 10.0, 12.5, 25.0] VALID_BANDS = [(109000000, 136000000), (136000000, 174000000), (400000000, 470000000)] CHARSET = list(chirp_common.CHARSET_ALPHANUMERIC) CHARSET.remove(" ") POWER_LEVELS = [ chirp_common.PowerLevel("Low", watts=20.00), chirp_common.PowerLevel("High", watts=40.00)] _memsize = 0x18F1 # dat file format is # 2 char per byte hex string # on CR LF terminated lines of 96 char # plus an empty line at the end _datsize = (_memsize * 2) / 96 * 98 + 2 # block are read in same order as original sw eventhough they are not # in physical order _blocks = [ (0x400, 0x1BFF, 0x30), (0x300, 0x32F, 0x30), (0x380, 0x3AF, 0x30), (0x200, 0x22F, 0x30), (0x240, 0x26F, 0x30), (0x270, 0x2A0, 0x31), ] MEM_FORMAT = """ #seekto 0x%X; struct { u32 rxfreq; u16 is_rxdigtone:1, rxdtcs_pol:1, rxtone:14; u8 rxdtmf:4, spmute:4; u8 unknown1; u32 txfreq; u16 is_txdigtone:1, txdtcs_pol:1, txtone:14; u8 txdtmf:4 pttid:4; u8 power:1, wide:1, compandor:1 unknown3:5; u8 namelen; u8 name[7]; } memory[128]; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_upload = rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Remove front head. 3. Connect data cable to radio, use the same connector where head was connected to, not the mic connector. 4. Click OK.""")) rp.experimental = _( 'This is experimental support for BJ-9900 ' 'which is still under development.\n' 'Please ensure you have a good backup with OEM software.\n' 'Also please send in bug and enhancement requests!\n' 'You have been warned. Proceed at your own risk!') return rp def _read(self, addr, blocksize): # read a single block msg = struct.pack(">4sHH", "READ", addr, blocksize) LOG.debug("sending " + util.hexprint(msg)) self.pipe.write(msg) block = self.pipe.read(blocksize) LOG.debug("received " + util.hexprint(block)) if len(block) != blocksize: raise Exception("Unable to read block at addr %04X expected" " %i got %i bytes" % (addr, blocksize, len(block))) return block def _clone_in(self): start = time.time() data = "" status = chirp_common.Status() status.msg = _("Cloning from radio") status.max = self._memsize for addr_from, addr_to, blocksize in self._blocks: for addr in range(addr_from, addr_to, blocksize): data += self._read(addr, blocksize) status.cur = len(data) self.status_fn(status) LOG.info("Clone completed in %i seconds" % (time.time() - start)) return memmap.MemoryMap(data) def _write(self, addr, block): # write a single block msg = struct.pack(">4sHH", "WRIE", addr, len(block)) + block LOG.debug("sending " + util.hexprint(msg)) self.pipe.write(msg) data = self.pipe.read(1) LOG.debug("received " + util.hexprint(data)) if ord(data) != CMD_ACK: raise errors.RadioError( "Radio refused to accept block 0x%04x" % addr) def _clone_out(self): start = time.time() status = chirp_common.Status() status.msg = _("Cloning to radio") status.max = self._memsize pos = 0 for addr_from, addr_to, blocksize in self._blocks: for addr in range(addr_from, addr_to, blocksize): self._write(addr, self._mmap[pos:(pos + blocksize)]) pos += blocksize status.cur = pos self.status_fn(status) LOG.info("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): if len(self._mmap) == self._datsize: self._mmap = memmap.MemoryMap([ chr(int(self._mmap.get(i, 2), 16)) for i in range(0, self._datsize, 2) if self._mmap.get(i, 2) != "\r\n" ]) try: self._memobj = bitwise.parse( self.MEM_FORMAT % self._memstart, self._mmap) except AttributeError: # main variant have no _memstart attribute return def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_dtcs_polarity = True rf.has_nostep_tuning = False rf.valid_modes = list(self.MODES) rf.valid_tmodes = list(self.TMODES) rf.valid_cross_modes = list(self.CROSS_MODES) rf.valid_duplexes = list(self.DUPLEX) rf.has_tuning_step = False # rf.valid_tuning_steps = list(self.STEPS) rf.valid_bands = self.VALID_BANDS rf.valid_skips = [""] rf.valid_power_levels = self.POWER_LEVELS rf.valid_characters = "".join(self.CHARSET) rf.valid_name_length = 7 rf.memory_bounds = (1, 128) rf.can_odd_split = True rf.has_settings = False rf.has_cross = True rf.has_ctone = True rf.has_rx_dtcs = True rf.has_sub_devices = self.VARIANT == "" return rf def get_sub_devices(self): return [BJ9900RadioLeft(self._mmap), BJ9900RadioRight(self._mmap)] def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] if mem.empty: _mem.set_raw("\xff" * (_mem.size() / 8)) # clean up _mem.namelen = 0 return _mem.rxfreq = mem.freq / 10 if 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 _mem.namelen = len(mem.name) for i in range(_mem.namelen): _mem.name[i] = ord(mem.name[i]) rxmode = "" txmode = "" if mem.tmode == "Tone": txmode = "Tone" elif mem.tmode == "TSQL": rxmode = "Tone" txmode = "TSQL" elif mem.tmode == "DTCS": rxmode = "DTCSSQL" txmode = "DTCS" elif mem.tmode == "Cross": txmode, rxmode = mem.cross_mode.split("->", 1) if rxmode == "": _mem.rxdtcs_pol = 1 _mem.is_rxdigtone = 1 _mem.rxtone = 0x3FFF elif rxmode == "Tone": _mem.rxdtcs_pol = 0 _mem.is_rxdigtone = 0 _mem.rxtone = int(mem.ctone * 10) elif rxmode == "DTCSSQL": _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0 _mem.is_rxdigtone = 1 _mem.rxtone = mem.dtcs elif rxmode == "DTCS": _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0 _mem.is_rxdigtone = 1 _mem.rxtone = mem.rx_dtcs if txmode == "": _mem.txdtcs_pol = 1 _mem.is_txdigtone = 1 _mem.txtone = 0x3FFF elif txmode == "Tone": _mem.txdtcs_pol = 0 _mem.is_txdigtone = 0 _mem.txtone = int(mem.rtone * 10) elif txmode == "TSQL": _mem.txdtcs_pol = 0 _mem.is_txdigtone = 0 _mem.txtone = int(mem.ctone * 10) elif txmode == "DTCS": _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0 _mem.is_txdigtone = 1 _mem.txtone = mem.dtcs if (mem.power): _mem.power = self.POWER_LEVELS.index(mem.power) _mem.wide = self.MODES.index(mem.mode) # not supported yet _mem.compandor = 0 _mem.pttid = 0 _mem.txdtmf = 0 _mem.rxdtmf = 0 _mem.spmute = 0 # set to mimic radio behaviour _mem.unknown3 = 0 def get_memory(self, number): _mem = self._memobj.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.rxfreq) * 10 if int(_mem.rxfreq) == int(_mem.txfreq) or _mem.txfreq == 0xFFFFFFFF: 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 _mem.name[:_mem.namelen]: mem.name += chr(char) dtcs_pol = ["N", "N"] if _mem.rxtone == 0x3FFF: rxmode = "" elif _mem.is_rxdigtone == 0: # ctcss rxmode = "Tone" mem.ctone = int(_mem.rxtone) / 10.0 else: # digital rxmode = "DTCS" mem.rx_dtcs = int(_mem.rxtone & 0x3FFF) if _mem.rxdtcs_pol == 1: dtcs_pol[1] = "R" if _mem.txtone == 0x3FFF: txmode = "" elif _mem.is_txdigtone == 0: # ctcss txmode = "Tone" mem.rtone = int(_mem.txtone) / 10.0 else: # digital txmode = "DTCS" mem.dtcs = int(_mem.txtone & 0x3FFF) if _mem.txdtcs_pol == 1: dtcs_pol[0] = "R" 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) mem.power = self.POWER_LEVELS[_mem.power] mem.mode = self.MODES[_mem.wide] return mem @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize or \ (len(filedata) == cls._datsize and filedata[-4:] == "\r\n\r\n") class BJ9900RadioLeft(BJ9900Radio): """Baojie BJ-9900 Left VFO subdevice""" VARIANT = "Left" _memstart = 0x0 class BJ9900RadioRight(BJ9900Radio): """Baojie BJ-9900 Right VFO subdevice""" VARIANT = "Right" _memstart = 0xC00 chirp-daily-20170714/chirp/drivers/th9000.py0000644000016101777760000006755313106521577021473 0ustar jenkinsnogroup00000000000000# Copyright 2015 David Fannin KK6DF # # 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 os import struct import time import logging from chirp import bitwise from chirp import chirp_common from chirp import directory from chirp import errors from chirp import memmap from chirp import util from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \ RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError LOG = logging.getLogger(__name__) # # Chirp Driver for TYT TH-9000D (models: 2M (144 Mhz), 1.25M (220 Mhz) and 70cm (440 Mhz) radios) # # Version 1.0 # # - Skip channels # # Global Parameters # MMAPSIZE = 16384 TONES = [62.5] + list(chirp_common.TONES) TMODES = ['','Tone','DTCS',''] DUPLEXES = ['','err','-','+'] # index 2 not used MODES = ['WFM','FM','NFM'] # 25k, 20k,15k bw TUNING_STEPS=[ 5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0 ] # index 0-9 POWER_LEVELS=[chirp_common.PowerLevel("High", watts=65), chirp_common.PowerLevel("Mid", watts=25), chirp_common.PowerLevel("Low", watts=10)] CROSS_MODES = chirp_common.CROSS_MODES APO_LIST = [ "Off","30 min","1 hr","2 hrs" ] BGCOLOR_LIST = ["Blue","Orange","Purple"] BGBRIGHT_LIST = ["%s" % x for x in range(1,32)] SQUELCH_LIST = ["Off"] + ["Level %s" % x for x in range(1,20)] TIMEOUT_LIST = ["Off"] + ["%s min" % x for x in range(1,30)] TXPWR_LIST = ["60W","25W"] # maximum power for Hi setting TBSTFREQ_LIST = ["1750Hz","2100Hz","1000Hz","1450Hz"] BEEP_LIST = ["Off","On"] SETTING_LISTS = { "auto_power_off": APO_LIST, "bg_color" : BGCOLOR_LIST, "bg_brightness" : BGBRIGHT_LIST, "squelch" : SQUELCH_LIST, "timeout_timer" : TIMEOUT_LIST, "choose_tx_power": TXPWR_LIST, "tbst_freq" : TBSTFREQ_LIST, "voice_prompt" : BEEP_LIST } MEM_FORMAT = """ #seekto 0x0000; struct { u8 unknown0000[16]; char idhdr[16]; u8 unknown0001[16]; } fidhdr; """ #Overall Memory Map: # # Memory Map (Range 0x0100-3FF0, step 0x10): # # Field Start End Size # (hex) (hex) (hex) # # 1 Channel Set Flag 0100 011F 20 # 2 Channel Skip Flag 0120 013F 20 # 3 Blank/Unknown 0140 01EF B0 # 4 Unknown 01F0 01FF 10 # 5 TX/RX Range 0200 020F 10 # 6 Bootup Passwd 0210 021F 10 # 7 Options, Radio 0220 023F 20 # 8 Unknown 0240 019F # 8B Startup Label 03E0 03E7 07 # 9 Channel Bank 2000 38FF 1900 # Channel 000 2000 201F 20 # Channel 001 2020 202F 20 # ... # Channel 199 38E0 38FF 20 # 10 Blank/Unknown 3900 3FFF 6FF 14592 16383 1792 # Total Map Size 16128 (2^8 = 16384) # # TH9000/220 memory map # section: 1 and 2: Channel Set/Skip Flags # # Channel Set (starts 0x100) : Channel Set bit is value 0 if a memory location in the channel bank is active. # Channel Skip (starts 0x120): Channel Skip bit is value 0 if a memory location in the channel bank is active. # # Both flag maps are a total 24 bytes in length, aligned on 32 byte records. # bit = 0 channel set/no skip, 1 is channel not set/skip # # to index a channel: # cbyte = channel / 8 ; # cbit = channel % 8 ; # setflag = csetflag[cbyte].c[cbit] ; # skipflag = cskipflag[cbyte].c[cbit] ; # # channel range is 0-199, range is 32 bytes (last 7 unknown) # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x0100; struct { bit c[8]; } csetflag[32]; struct { u8 unknown0100[7]; } ropt0100; #seekto 0x0120; struct { bit c[8]; } cskipflag[32]; struct { u8 unknown0120[7]; } ropt0120; """ # TH9000 memory map # section: 5 TX/RX Range # used to set the TX/RX range of the radio (e.g. 222-228Mhz for 220 meter) # possible to set range for tx/rx # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x0200; struct { bbcd txrangelow[4]; bbcd txrangehi[4]; bbcd rxrangelow[4]; bbcd rxrangehi[4]; } freqrange; """ # TH9000 memory map # section: 6 bootup_passwd # used to set bootup passwd (see boot_passwd checkbox option) # # options - bootup password # # bytes:bit type description # --------------------------------------------------------------------------- # 6 u8 bootup_passwd[6] bootup passwd, 6 chars, numberic chars 30-39 , see boot_passwd checkbox to set # 10 u8 unknown; # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x0210; struct { u8 bootup_passwd[6]; u8 unknown2010[10]; } ropt0210; """ # TH9000/220 memory map # section: 7 Radio Options # used to set a number of radio options # # bytes:bit type description # --------------------------------------------------------------------------- # 1 u8 display_mode display mode, range 0-2, 0=freq,1=channel,2=name (selecting name affects vfo_mr) # 1 u8 vfo_mr; vfo_mr , 0=vfo, mr=1 # 1 u8 unknown; # 1 u8 squelch; squelch level, range 0-19, hex for menu # 1 u8 unknown[2]; # 1 u8 channel_lock; if display_mode[channel] selected, then lock=1,no lock =0 # 1 u8 unknown; # 1 u8 bg_brightness ; background brightness, range 0-21, hex, menu index # 1 u8 unknown; # 1 u8 bg_color ; bg color, menu index, blue 0 , orange 1, purple 2 # 1 u8 tbst_freq ; tbst freq , menu 0 = 1750Hz, 1=2100 , 2=1000 , 3=1450hz # 1 u8 timeout_timer; timeout timer, hex, value = minutes, 0= no timeout # 1 u8 unknown; # 1 u8 auto_power_off; auto power off, range 0-3, off,30min, 1hr, 2hr, hex menu index # 1 u8 voice_prompt; voice prompt, value 0,1 , Beep ON = 1, Beep Off = 2 # # description of function setup options, starting at 0x0230 # # bytes:bit type description # --------------------------------------------------------------------------- # 1 u8 // 0 # :4 unknown:6 # :1 elim_sql_tail:1 eliminate squelsh tail when no ctcss checkbox (1=checked) # :1 sql_key_function "squelch off" 1 , "squelch momentary off" 0 , menu index # 2 u8 unknown[2] /1-2 # 1 u8 // 3 # :4 unknown:4 # :1 inhibit_init_ops:1 //bit 5 # :1 unknownD:1 # :1 inhibit_setup_bg_chk:1 //bit 7 # :1 unknown:1 # 1 u8 tail_elim_type menu , (off=0,120=1,180=2), // 4 # 1 u8 choose_tx_power menu , (60w=0,25w=1) // 5 # 2 u8 unknown[2]; // 6-7 # 1 u8 bootup_passwd_flag checkbox 1=on, 0=off // 8 # 7 u8 unknown[7]; // 9-F # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x0220; struct { u8 display_mode; u8 vfo_mr; u8 unknown0220A; u8 squelch; u8 unknown0220B[2]; u8 channel_lock; u8 unknown0220C; u8 bg_brightness; u8 unknown0220D; u8 bg_color; u8 tbst_freq; u8 timeout_timer; u8 unknown0220E; u8 auto_power_off; u8 voice_prompt; u8 unknown0230A:6, elim_sql_tail:1, sql_key_function:1; u8 unknown0230B[2]; u8 unknown0230C:4, inhibit_init_ops:1, unknown0230D:1, inhibit_setup_bg_chk:1, unknown0230E:1; u8 tail_elim_type; u8 choose_tx_power; u8 unknown0230F[2]; u8 bootup_passwd_flag; u8 unknown0230G[7]; } settings; """ # TH9000 memory map # section: 8B Startup Label # # bytes:bit type description # --------------------------------------------------------------------------- # 7 char start_label[7] label displayed at startup (usually your call sign) # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x03E0; struct { char startname[7]; } slabel; """ # TH9000/220 memory map # section: 9 Channel Bank # description of channel bank (200 channels , range 0-199) # Each 32 Byte (0x20 hex) record: # bytes:bit type description # --------------------------------------------------------------------------- # 4 bbcd freq[4] receive frequency in packed binary coded decimal # 4 bbcd offset[4] transmit offset in packed binary coded decimal (note: plus/minus direction set by 'duplex' field) # 1 u8 # :4 unknown:4 # :4 tuning_step:4 tuning step, menu index value from 0-9 # 5,6.25,8.33,10,12.5,15,20,25,30,50 # 1 u8 # :4 unknown:4 not yet decoded, used for DCS coding? # :2 channel_width:2 channel spacing, menu index value from 0-3 # 25,20,12.5 # :1 reverse:1 reverse flag, 0=off, 1=on (reverses tx and rx freqs) # :1 txoff:1 transmitt off flag, 0=transmit , 1=do not transmit # 1 u8 # :1 talkaround:1 talkaround flag, 0=off, 1=on (bypasses repeater) # :1 compander:1 compander flag, 0=off, 1=on (turns on/off voice compander option) # :2 unknown:2 # :2 power:2 tx power setting, value range 0-2, 0=hi,1=med,2=lo # :2 duplex:2 duplex settings, 0=simplex,2= minus(-) offset, 3= plus (+) offset (see offset field) # # 1 u8 # :4 unknown:4 # :2 rxtmode:2 rx tone mode, value range 0-2, 0=none, 1=CTCSS, 2=DCS (ctcss tone in field rxtone) # :2 txtmode:2 tx tone mode, value range 0-2, 0=none, 1=CTCSS, 3=DCS (ctcss tone in field txtone) # 1 u8 # :2 unknown:2 # :6 txtone:6 tx ctcss tone, menu index # 1 u8 # :2 unknown:2 # :6 rxtone:6 rx ctcss tone, menu index # 1 u8 txcode ?, not used for ctcss # 1 u8 rxcode ?, not used for ctcss # 3 u8 unknown[3] # 7 char name[7] 7 byte char string for channel name # 1 u8 # :6 unknown:6, # :2 busychannellockout:2 busy channel lockout option , 0=off, 1=repeater, 2=busy (lock out tx if channel busy) # 4 u8 unknownI[4]; # 1 u8 # :7 unknown:7 # :1 scrambler:1 scrambler flag, 0=off, 1=on (turns on tyt scrambler option) # MEM_FORMAT = MEM_FORMAT + """ #seekto 0x2000; struct { bbcd freq[4]; bbcd offset[4]; u8 unknown2000A:4, tuning_step:4; u8 rxdcsextra:1, txdcsextra:1, rxinv:1, txinv:1, channel_width:2, reverse:1, txoff:1; u8 talkaround:1, compander:1, unknown2000C:2, power:2, duplex:2; u8 unknown2000D:4, rxtmode:2, txtmode:2; u8 unknown2000E:2, txtone:6; u8 unknown2000F:2, rxtone:6; u8 txcode; u8 rxcode; u8 unknown2000G[3]; char name[7]; u8 unknown2000H:6, busychannellockout:2; u8 unknown2000I[4]; u8 unknown2000J:7, scrambler:1; } memory[200] ; """ def _echo_write(radio, data): try: radio.pipe.write(data) radio.pipe.read(len(data)) except Exception, e: LOG.error("Error writing to radio: %s" % e) raise errors.RadioError("Unable to write to radio") def _checksum(data): cs = 0 for byte in data: cs += ord(byte) return cs % 256 def _read(radio, length): try: data = radio.pipe.read(length) except Exception, e: LOG.error( "Error reading from radio: %s" % e) raise errors.RadioError("Unable to read from radio") if len(data) != length: LOG.error( "Short read from radio (%i, expected %i)" % (len(data), length)) LOG.debug(util.hexprint(data)) raise errors.RadioError("Short read from radio") return data def _ident(radio): radio.pipe.timeout = 1 _echo_write(radio,"PROGRAM") response = radio.pipe.read(3) if response != "QX\06": LOG.debug( "Response was :\n%s" % util.hexprint(response)) raise errors.RadioError("Unsupported model") _echo_write(radio, "\x02") response = radio.pipe.read(16) LOG.debug(util.hexprint(response)) if response[1:8] != "TH-9000": LOG.error( "Looking for:\n%s" % util.hexprint("TH-9000")) LOG.error( "Response was:\n%s" % util.hexprint(response)) raise errors.RadioError("Unsupported model") def _send(radio, cmd, addr, length, data=None): frame = struct.pack(">cHb", cmd, addr, length) if data: frame += data frame += chr(_checksum(frame[1:])) frame += "\x06" _echo_write(radio, frame) LOG.debug("Sent:\n%s" % util.hexprint(frame)) if data: result = radio.pipe.read(1) if result != "\x06": LOG.debug( "Ack was: %s" % repr(result)) raise errors.RadioError("Radio did not accept block at %04x" % addr) return result = _read(radio, length + 6) LOG.debug("Got:\n%s" % util.hexprint(result)) header = result[0:4] data = result[4:-2] ack = result[-1] if ack != "\x06": LOG.debug("Ack was: %s" % repr(ack)) raise errors.RadioError("Radio NAK'd block at %04x" % addr) _cmd, _addr, _length = struct.unpack(">cHb", header) if _addr != addr or _length != _length: LOG.debug( "Expected/Received:") LOG.debug(" Length: %02x/%02x" % (length, _length)) LOG.debug( " Addr: %04x/%04x" % (addr, _addr)) raise errors.RadioError("Radio send unexpected block") cs = _checksum(result[1:-2]) if cs != ord(result[-2]): LOG.debug( "Calculated: %02x" % cs) LOG.debug( "Actual: %02x" % ord(result[-2])) raise errors.RadioError("Block at 0x%04x failed checksum" % addr) return data def _finish(radio): endframe = "\x45\x4E\x44" _echo_write(radio, endframe) result = radio.pipe.read(1) # TYT radios acknowledge the "endframe" command, Luiton radios do not. if result != "" and result != "\x06": LOG.error( "Got:\n%s" % util.hexprint(result)) raise errors.RadioError("Radio did not finish cleanly") def do_download(radio): _ident(radio) _memobj = None data = "" for start,end in radio._ranges: for addr in range(start,end,0x10): block = _send(radio,'R',addr,0x10) data += block status = chirp_common.Status() status.cur = len(data) status.max = end status.msg = "Downloading from radio" radio.status_fn(status) _finish(radio) return memmap.MemoryMap(data) def do_upload(radio): _ident(radio) for start,end in radio._ranges: for addr in range(start,end,0x10): if addr < 0x0100: continue block = radio._mmap[addr:addr+0x10] _send(radio,'W',addr,len(block),block) status = chirp_common.Status() status.cur = addr status.max = end status.msg = "Uploading to Radio" radio.status_fn(status) _finish(radio) # # The base class, extended for use with other models # class Th9000Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """TYT TH-9000""" VENDOR = "TYT" MODEL = "TH9000 Base" BAUD_RATE = 9600 valid_freq = [(900000000, 999000000)] _memsize = MMAPSIZE _ranges = [(0x0000,0x4000)] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("The TYT TH-9000 driver is an beta version." "Proceed with Caution and backup your data") return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_cross = True rf.has_tuning_step = False rf.has_rx_dtcs = True rf.valid_skips = ["","S"] rf.memory_bounds = (0, 199) rf.valid_name_length = 7 rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-" rf.valid_modes = MODES rf.valid_tmodes = ['','Tone','TSQL','DTCS','Cross'] rf.valid_cross_modes = ['Tone->DTCS','DTCS->Tone', '->Tone','->DTCS','Tone->Tone'] rf.valid_power_levels = POWER_LEVELS rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES rf.valid_bands = self.valid_freq return rf # Do a download of the radio from the serial port def sync_in(self): self._mmap = do_download(self) self.process_mmap() # Do an upload of the radio to the serial port def sync_out(self): do_upload(self) def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # 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]) # not working yet def _get_dcs_index(self, _mem,which): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) return (int(extra) << 8) | int(base) def _set_dcs_index(self, _mem, which, index): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) base.set_value(index & 0xFF) extra.set_value(index >> 8) # 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] # get flag info cbyte = number / 8 ; cbit = 7 - (number % 8) ; setflag = self._memobj.csetflag[cbyte].c[cbit]; skipflag = self._memobj.cskipflag[cbyte].c[cbit]; mem = chirp_common.Memory() mem.number = number # Set the memory number if setflag == 1: mem.empty = True return mem mem.freq = int(_mem.freq) * 100 mem.offset = int(_mem.offset) * 100 mem.name = str(_mem.name).rstrip() # Set the alpha tag mem.duplex = DUPLEXES[_mem.duplex] mem.mode = MODES[_mem.channel_width] mem.power = POWER_LEVELS[_mem.power] rxtone = txtone = None rxmode = TMODES[_mem.rxtmode] txmode = TMODES[_mem.txtmode] # doesn't work if rxmode == "Tone": rxtone = TONES[_mem.rxtone] elif rxmode == "DTCS": rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,'rx')] if txmode == "Tone": txtone = TONES[_mem.txtone] elif txmode == "DTCS": txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,'tx')] rxpol = _mem.rxinv and "R" or "N" txpol = _mem.txinv and "R" or "N" chirp_common.split_tone_decode(mem, (txmode, txtone, txpol), (rxmode, rxtone, rxpol)) mem.skip = "S" if skipflag == 1 else "" # 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] cbyte = mem.number / 8 cbit = 7 - (mem.number % 8) if mem.empty: self._memobj.csetflag[cbyte].c[cbit] = 1 self._memobj.cskipflag[cbyte].c[cbit] = 1 return self._memobj.csetflag[cbyte].c[cbit] = 0 self._memobj.cskipflag[cbyte].c[cbit] = 1 if (mem.skip == "S") else 0 _mem.set_raw("\x00" * 32) _mem.freq = mem.freq / 100 # Convert to low-level frequency _mem.offset = mem.offset / 100 # Convert to low-level frequency _mem.name = mem.name.ljust(7)[:7] # Store the alpha tag _mem.duplex = DUPLEXES.index(mem.duplex) try: _mem.channel_width = MODES.index(mem.mode) except ValueError: _mem.channel_width = 0 ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem) _mem.txtmode = TMODES.index(txmode) _mem.rxtmode = TMODES.index(rxmode) if txmode == "Tone": _mem.txtone = TONES.index(txtone) elif txmode == "DTCS": self._set_dcs_index(_mem,'tx',chirp_common.ALL_DTCS_CODES.index(txtone)) if rxmode == "Tone": _mem.rxtone = TONES.index(rxtone) elif rxmode == "DTCS": self._set_dcs_index(_mem, 'rx', chirp_common.ALL_DTCS_CODES.index(rxtone)) _mem.txinv = txpol == "R" _mem.rxinv = rxpol == "R" if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 def _get_settings(self): _settings = self._memobj.settings _freqrange = self._memobj.freqrange _slabel = self._memobj.slabel basic = RadioSettingGroup("basic","Global Settings") freqrange = RadioSettingGroup("freqrange","Frequency Ranges") top = RadioSettingGroup("top","All Settings",basic,freqrange) settings = RadioSettings(top) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += "" return filtered val = RadioSettingValueString(0,7,_filter(_slabel.startname)) rs = RadioSetting("startname","Startup Label",val) basic.append(rs) rs = RadioSetting("bg_color","LCD Color", RadioSettingValueList(BGCOLOR_LIST, BGCOLOR_LIST[_settings.bg_color])) basic.append(rs) rs = RadioSetting("bg_brightness","LCD Brightness", RadioSettingValueList(BGBRIGHT_LIST, BGBRIGHT_LIST[_settings.bg_brightness])) basic.append(rs) rs = RadioSetting("squelch","Squelch Level", RadioSettingValueList(SQUELCH_LIST, SQUELCH_LIST[_settings.squelch])) basic.append(rs) rs = RadioSetting("timeout_timer","Timeout Timer (TOT)", RadioSettingValueList(TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout_timer])) basic.append(rs) rs = RadioSetting("auto_power_off","Auto Power Off (APO)", RadioSettingValueList(APO_LIST, APO_LIST[_settings.auto_power_off])) basic.append(rs) rs = RadioSetting("voice_prompt","Beep Prompt", RadioSettingValueList(BEEP_LIST, BEEP_LIST[_settings.voice_prompt])) basic.append(rs) rs = RadioSetting("tbst_freq","Tone Burst Frequency", RadioSettingValueList(TBSTFREQ_LIST, TBSTFREQ_LIST[_settings.tbst_freq])) basic.append(rs) rs = RadioSetting("choose_tx_power","Max Level of TX Power", RadioSettingValueList(TXPWR_LIST, TXPWR_LIST[_settings.choose_tx_power])) basic.append(rs) (flow,fhigh) = self.valid_freq[0] flow /= 1000 fhigh /= 1000 fmidrange = (fhigh- flow)/2 rs = RadioSetting("txrangelow","TX Freq, Lower Limit (khz)", RadioSettingValueInteger(flow, flow + fmidrange, int(_freqrange.txrangelow)/10)) freqrange.append(rs) rs = RadioSetting("txrangehi","TX Freq, Upper Limit (khz)", RadioSettingValueInteger(fhigh-fmidrange, fhigh, int(_freqrange.txrangehi)/10)) freqrange.append(rs) rs = RadioSetting("rxrangelow","RX Freq, Lower Limit (khz)", RadioSettingValueInteger(flow, flow+fmidrange, int(_freqrange.rxrangelow)/10)) freqrange.append(rs) rs = RadioSetting("rxrangehi","RX Freq, Upper Limit (khz)", RadioSettingValueInteger(fhigh-fmidrange, fhigh, int(_freqrange.rxrangehi)/10)) freqrange.append(rs) return settings def get_settings(self): try: return self._get_settings() except: import traceback LOG.error( "failed to parse settings") traceback.print_exc() return None def set_settings(self,settings): _settings = self._memobj.settings for element in settings: if not isinstance(element,RadioSetting): self.set_settings(element) continue else: try: name = element.get_name() if name in ["txrangelow","txrangehi","rxrangelow","rxrangehi"]: LOG.debug( "setting %s = %s" % (name,int(element.value)*10)) setattr(self._memobj.freqrange,name,int(element.value)*10) continue if name in ["startname"]: LOG.debug( "setting %s = %s" % (name, element.value)) setattr(self._memobj.slabel,name,element.value) continue obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug( "using apply callback") element.run_apply_callback() else: LOG.debug( "Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug( element.get_name()) raise @classmethod def match_model(cls, filedata, filename): if MMAPSIZE == len(filedata): (flow,fhigh) = cls.valid_freq[0] flow /= 1000000 fhigh /= 1000000 txmin=ord(filedata[0x200])*100 + (ord(filedata[0x201])>>4)*10 + ord(filedata[0x201])%16 txmax=ord(filedata[0x204])*100 + (ord(filedata[0x205])>>4)*10 + ord(filedata[0x205])%16 rxmin=ord(filedata[0x208])*100 + (ord(filedata[0x209])>>4)*10 + ord(filedata[0x209])%16 rxmax=ord(filedata[0x20C])*100 + (ord(filedata[0x20D])>>4)*10 + ord(filedata[0x20D])%16 if ( rxmin >= flow and rxmax <= fhigh and txmin >= flow and txmax <= fhigh ): return True return False # Declaring Aliases (Clones of the real radios) class LT580VHF(chirp_common.Alias): VENDOR = "LUITON" MODEL = "LT-580_VHF" class LT580UHF(chirp_common.Alias): VENDOR = "LUITON" MODEL = "LT-580_UHF" @directory.register class Th9000220Radio(Th9000Radio): """TYT TH-9000 220""" VENDOR = "TYT" MODEL = "TH9000_220" BAUD_RATE = 9600 valid_freq = [(220000000, 260000000)] @directory.register class Th9000144Radio(Th9000220Radio): """TYT TH-9000 144""" VENDOR = "TYT" MODEL = "TH9000_144" BAUD_RATE = 9600 valid_freq = [(136000000, 174000000)] ALIASES = [LT580VHF, ] @directory.register class Th9000440Radio(Th9000220Radio): """TYT TH-9000 440""" VENDOR = "TYT" MODEL = "TH9000_440" BAUD_RATE = 9600 valid_freq = [(400000000, 490000000)] ALIASES = [LT580UHF, ] chirp-daily-20170714/chirp/drivers/ic2100.py0000644000016101777760000001657512476006422021437 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.drivers import icf from chirp import chirp_common, util, directory, bitwise, memmap from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings 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, unknownbit1:1, anm:1, unknownbit2:1, 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, unknownbit1:1, anm:1, unknownbit2:1, 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, unknownbit1:1, anm:1, unknownbit2:1, 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] mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("anm", "Alphanumeric Name", RadioSettingValueBoolean(_mem.anm)) mem.extra.append(rs) 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) _mem.anm = mem.name.strip() != "" _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) for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) chirp-daily-20170714/chirp/drivers/icx8x_ll.py0000644000016101777760000003005612476006422022261 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, 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-daily-20170714/chirp/drivers/uv5x3.py0000644000016101777760000012020413012267422021510 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp.drivers import baofeng_common from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, \ InvalidValueError from textwrap import dedent ##### MAGICS ######################################################### # BTECH UV-5X3 magic string MSTRING_UV5X3 = "\x50\x0D\x0C\x20\x16\x03\x28" ##### ID strings ##################################################### # BTECH UV-5X3 UV5X3_fp1 = "UVVG302" # BFB300 original UV5X3_fp2 = "UVVG301" # UVV300 original UV5X3_fp3 = "UVVG306" # UVV306 original DTMF_CHARS = " 1234567890*#ABCD" STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] LIST_AB = ["A", "B"] LIST_ALMOD = ["Site", "Tone", "Code"] LIST_BANDWIDTH = ["Wide", "Narrow"] LIST_COLOR = ["Off", "Blue", "Orange", "Purple"] LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)] LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"] LIST_MODE = ["Channel", "Name", "Frequency"] LIST_OFF1TO9 = ["Off"] + list("123456789") LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"] LIST_OFFAB = ["Off"] + LIST_AB LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)] LIST_RESUME = ["TO", "CO", "SE"] LIST_PONMSG = ["Full", "Message"] LIST_PTTID = ["Off", "BOT", "EOT", "Both"] LIST_SCODE = ["%s" % x for x in range(1, 16)] LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SHIFTD = ["Off", "+", "-"] LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)] LIST_STEP = [str(x) for x in STEPS] LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)] LIST_TXPOWER = ["High", "Low"] LIST_VOICE = ["Off", "English", "Chinese"] LIST_WORKMODE = ["Frequency", "Channel"] LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"] LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00] def model_match(cls, data): """Match the opened/downloaded image to the correct version""" match_rid1 = False match_rid2 = False rid1 = data[0x1EF0:0x1EF7] if rid1 in cls._fileid: match_rid1 = True if match_rid1: return True else: return False @directory.register class UV5X3(baofeng_common.BaofengCommonHT): """BTech UV-5X3""" VENDOR = "BTECH" MODEL = "UV-5X3" _fileid = [UV5X3_fp3, UV5X3_fp2, UV5X3_fp1] _magic = [MSTRING_UV5X3, ] _magic_response_length = 14 _fw_ver_start = 0x1EF0 _recv_block_size = 0x40 _mem_size = 0x2000 _ack_block = True _ranges = [(0x0000, 0x0DF0), (0x0E00, 0x1800), (0x1EE0, 0x1EF0), (0x1F80, 0x1F90), (0x1FA0, 0x1FB0), (0x1FE0, 0x2000)] _send_block_size = 0x10 MODES = ["FM", "NFM"] VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "!@#$%^&*()+-=[]:\";'<>?,./" LENGTH_NAME = 7 SKIP_VALUES = ["", "S"] DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Low", watts=1.00)] VALID_BANDS = [(130000000, 180000000), (220000000, 226000000), (400000000, 521000000)] PTTID_LIST = LIST_PTTID SCODE_LIST = LIST_SCODE MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown0:4, scode:4; u8 unknown1; u8 unknown2:7, lowpower:1; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B00; struct { u8 code[16]; } pttid[15]; #seekto 0x0C80; struct { u8 inspection[8]; u8 monitor[8]; u8 alarmcode[8]; u8 stun[8]; u8 kill[8]; u8 revive[8]; u8 code[7]; u8 unknown06; u8 dtmfon; u8 dtmfoff; u8 unused00:6, aniid:2; u8 unknown07[5]; u8 masterid[5]; u8 unknown08[3]; u8 viceid[5]; u8 unknown09[3]; u8 unused01:7, mastervice:1; u8 unused02:3, mrevive:1, mkill:1, mstun:1, mmonitor:1, minspection:1; u8 unused03:3, vrevive:1, vkill:1, vstun:1, vmonitor:1, vinspection:1; u8 unused04:6, txdisable:1, rxdisable:1; u8 groupcode; u8 spacecode; u8 delayproctime; u8 resettime; } ani; #seekto 0x0E20; struct { u8 unused00:4, squelch:4; u8 unused01:5, step:3; u8 unknown00; u8 unused02:5, save:3; u8 unused03:4, vox:4; u8 unknown01; u8 unused04:4, abr:4; u8 unused05:7, tdr:1; u8 unused06:7, beep:1; u8 unused07:2, timeout:6; u8 unknown02[4]; u8 unused09:6, voice:2; u8 unknown03; u8 unused10:6, dtmfst:2; u8 unknown04; u8 unused11:6, screv:2; u8 unused12:6, pttid:2; u8 unused13:2, pttlt:6; u8 unused14:6, mdfa:2; u8 unused15:6, mdfb:2; u8 unknown05; u8 unused16:7, sync:1; u8 unknown06[4]; u8 unused17:6, wtled:2; u8 unused18:6, rxled:2; u8 unused19:6, txled:2; u8 unused20:6, almod:2; u8 unknown07; u8 unused21:6, tdrab:2; u8 unused22:7, ste:1; u8 unused23:4, rpste:4; u8 unused24:4, rptrl:4; u8 unused25:7, ponmsg:1; u8 unused26:7, roger:1; u8 unused27:7, dani:1; u8 unused28:2, dtmfg:6; u8 unknown08:6, reset:1, unknown09:1; u8 unknown10[3]; u8 cht; u8 unknown11[13]; u8 displayab:1, unknown12:2, fmradio:1, alarm:1, unknown13:2, menu:1; u8 unknown14; u8 unused29:7, workmode:1; u8 unused30:7, keylock:1; } settings; #seekto 0x0E76; struct { u8 unused0:1, mrcha:7; u8 unused1:1, mrchb:7; } wmchannel; struct vfo { u8 unknown0[8]; u8 freq[8]; u8 offset[6]; ul16 rxtone; ul16 txtone; u8 unused0:7, band:1; u8 unknown3; u8 unknown4:2, sftd:2, scode:4; u8 unknown5; u8 unknown6:1, step:3, unknown7:4; u8 txpower:1, widenarr:1, unknown8:6; }; #seekto 0x0F00; struct { struct vfo a; struct vfo b; } vfo; #seekto 0x0F4E; u16 fm_presets; #seekto 0x1000; struct { char name[7]; u8 unknown[9]; } names[128]; #seekto 0x1ED0; struct { char line1[7]; char line2[7]; } sixpoweron_msg; #seekto 0x1EF0; struct { char line1[7]; char line2[7]; } firmware_msg; struct squelch { u8 sql0; u8 sql1; u8 sql2; u8 sql3; u8 sql4; u8 sql5; u8 sql6; u8 sql7; u8 sql8; u8 sql9; }; #seekto 0x1F80; struct { struct squelch vhf; u8 unknown0[6]; u8 unknown1[16]; struct squelch uhf; } squelch; #seekto 0x1FE0; struct { char line1[7]; char line2[7]; } poweron_msg; struct limit { u8 enable; bbcd lower[2]; bbcd upper[2]; }; #seekto 0x1FF0; struct { struct limit vhf; struct limit vhf2; struct limit uhf; } limits; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The BTech UV-5X3 driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap) def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") fm_preset = RadioSettingGroup("fm_preset", "FM Preset") dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings") dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings") service = RadioSettingGroup("service", "Service Settings") top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe, dtmfd, service) # Basic settings if _mem.settings.squelch > 0x09: val = 0x00 else: val = _mem.settings.squelch rs = RadioSetting("settings.squelch", "Squelch", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[val])) basic.append(rs) if _mem.settings.save > 0x04: val = 0x00 else: val = _mem.settings.save rs = RadioSetting("settings.save", "Battery Saver", RadioSettingValueList( LIST_SAVE, LIST_SAVE[val])) basic.append(rs) if _mem.settings.vox > 0x0A: val = 0x00 else: val = _mem.settings.vox rs = RadioSetting("settings.vox", "Vox", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) if _mem.settings.abr > 0x0A: val = 0x00 else: val = _mem.settings.abr rs = RadioSetting("settings.abr", "Backlight Timeout", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) rs = RadioSetting("settings.tdr", "Dual Watch", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(rs) rs = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(rs) if _mem.settings.timeout > 0x27: val = 0x03 else: val = _mem.settings.timeout rs = RadioSetting("settings.timeout", "Timeout Timer", RadioSettingValueList( LIST_TIMEOUT, LIST_TIMEOUT[val])) basic.append(rs) if _mem.settings.voice > 0x02: val = 0x01 else: val = _mem.settings.voice rs = RadioSetting("settings.voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) rs = RadioSetting("settings.dtmfst", "DTMF Sidetone", RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[ _mem.settings.dtmfst])) basic.append(rs) if _mem.settings.screv > 0x02: val = 0x01 else: val = _mem.settings.screv rs = RadioSetting("settings.screv", "Scan Resume", RadioSettingValueList( LIST_RESUME, LIST_RESUME[val])) basic.append(rs) rs = RadioSetting("settings.pttid", "When to send PTT ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[ _mem.settings.pttid])) basic.append(rs) if _mem.settings.pttlt > 0x1E: val = 0x05 else: val = _mem.settings.pttlt rs = RadioSetting("pttlt", "PTT ID Delay", RadioSettingValueInteger(0, 50, val)) basic.append(rs) rs = RadioSetting("settings.mdfa", "Display Mode (A)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfa])) basic.append(rs) rs = RadioSetting("settings.mdfb", "Display Mode (B)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfb])) basic.append(rs) rs = RadioSetting("settings.sync", "Sync A & B", RadioSettingValueBoolean(_mem.settings.sync)) basic.append(rs) rs = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.wtled])) basic.append(rs) rs = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.rxled])) basic.append(rs) rs = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.txled])) basic.append(rs) if _mem.settings.almod > 0x02: val = 0x00 else: val = _mem.settings.almod rs = RadioSetting("settings.almod", "Alarm Mode", RadioSettingValueList( LIST_ALMOD, LIST_ALMOD[val])) basic.append(rs) if _mem.settings.tdrab > 0x02: val = 0x00 else: val = _mem.settings.tdrab rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority", RadioSettingValueList( LIST_OFFAB, LIST_OFFAB[val])) basic.append(rs) rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)", RadioSettingValueBoolean(_mem.settings.ste)) basic.append(rs) if _mem.settings.rpste > 0x0A: val = 0x00 else: val = _mem.settings.rpste rs = RadioSetting("settings.rpste", "Squelch Tail Eliminate (repeater)", RadioSettingValueList( LIST_RPSTE, LIST_RPSTE[val])) basic.append(rs) if _mem.settings.rptrl > 0x0A: val = 0x00 else: val = _mem.settings.rptrl rs = RadioSetting("settings.rptrl", "STE Repeater Delay", RadioSettingValueList( LIST_STEDELAY, LIST_STEDELAY[val])) basic.append(rs) rs = RadioSetting("settings.ponmsg", "Power-On Message", RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[ _mem.settings.ponmsg])) basic.append(rs) rs = RadioSetting("settings.roger", "Roger Beep", RadioSettingValueBoolean(_mem.settings.roger)) basic.append(rs) rs = RadioSetting("settings.dani", "Decode ANI", RadioSettingValueBoolean(_mem.settings.dani)) basic.append(rs) if _mem.settings.dtmfg > 0x3C: val = 0x14 else: val = _mem.settings.dtmfg rs = RadioSetting("settings.dtmfg", "DTMF Gain", RadioSettingValueInteger(0, 60, val)) basic.append(rs) # Advanced settings rs = RadioSetting("settings.reset", "RESET Menu", RadioSettingValueBoolean(_mem.settings.reset)) advanced.append(rs) rs = RadioSetting("settings.menu", "All Menus", RadioSettingValueBoolean(_mem.settings.menu)) advanced.append(rs) rs = RadioSetting("settings.fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_mem.settings.fmradio)) advanced.append(rs) rs = RadioSetting("settings.alarm", "Alarm Sound", RadioSettingValueBoolean(_mem.settings.alarm)) advanced.append(rs) # Other settings def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = _mem.firmware_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val) other.append(rs) _msg = _mem.sixpoweron_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val) other.append(rs) _msg = _mem.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) if str(_mem.firmware_msg.line1) == "UVVG302": lower = 136 upper = 174 else: lower = 130 upper = 179 rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.lower)) other.append(rs) rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.upper)) other.append(rs) if str(_mem.firmware_msg.line1) == "UVVG302": lower = 200 upper = 230 else: lower = 220 upper = 225 rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf2.lower)) other.append(rs) rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf2.upper)) other.append(rs) if str(_mem.firmware_msg.line1) == "UVVG302": lower = 400 upper = 480 else: lower = 400 upper = 520 rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.lower)) other.append(rs) rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.upper)) other.append(rs) # Work mode settings rs = RadioSetting("settings.displayab", "Display", RadioSettingValueList( LIST_AB, LIST_AB[_mem.settings.displayab])) work.append(rs) rs = RadioSetting("settings.workmode", "VFO/MR Mode", RadioSettingValueList( LIST_WORKMODE, LIST_WORKMODE[_mem.settings.workmode])) work.append(rs) rs = RadioSetting("settings.keylock", "Keypad Lock", RadioSettingValueBoolean(_mem.settings.keylock)) work.append(rs) rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrcha)) work.append(rs) rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrchb)) work.append(rs) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): _vhf_lower = int(_mem.limits.vhf.lower) _vhf_upper = int(_mem.limits.vhf.upper) _vhf2_lower = int(_mem.limits.vhf2.lower) _vhf2_upper = int(_mem.limits.vhf2.upper) _uhf_lower = int(_mem.limits.uhf.lower) _uhf_upper = int(_mem.limits.uhf.upper) value = chirp_common.parse_freq(value) msg = ("Can't be less than %i.0000") if value > 99000000 and value < _vhf_lower * 1000000: raise InvalidValueError(msg % (_vhf_lower)) msg = ("Can't be between %i.9975-%i.0000") if (_vhf_upper + 1) * 1000000 <= value and \ value < _vhf2_lower * 1000000: raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower)) if (_vhf2_upper + 1) * 1000000 <= value and \ value < _uhf_lower * 1000000: raise InvalidValueError(msg % (_vhf2_upper, _uhf_lower)) msg = ("Can't be greater than %i.9975") if value > 99000000 and value >= (_uhf_upper + 1) * 1000000: raise InvalidValueError(msg % (_uhf_upper)) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.a.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.b.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.sftd", "VFO A Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd])) work.append(rs) rs = RadioSetting("vfo.b.sftd", "VFO B Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd])) work.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 1000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 1000 for i in range(5, -1, -1): obj.offset[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset)) rs = RadioSetting("vfo.a.offset", "VFO A Offset", val1a) rs.set_apply_callback(apply_offset, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset)) rs = RadioSetting("vfo.b.offset", "VFO B Offset", val1b) rs.set_apply_callback(apply_offset, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.txpower", "VFO A Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.a.txpower])) work.append(rs) rs = RadioSetting("vfo.b.txpower", "VFO B Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.b.txpower])) work.append(rs) rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.a.widenarr])) work.append(rs) rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.b.widenarr])) work.append(rs) rs = RadioSetting("vfo.a.scode", "VFO A S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.a.scode])) work.append(rs) rs = RadioSetting("vfo.b.scode", "VFO B S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.b.scode])) work.append(rs) rs = RadioSetting("vfo.a.step", "VFO A Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.a.step])) work.append(rs) rs = RadioSetting("vfo.b.step", "VFO B Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.b.step])) work.append(rs) # broadcast FM settings _fm_presets = self._memobj.fm_presets if _fm_presets <= 108.0 * 10 - 650: preset = _fm_presets / 10.0 + 65 elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10: preset = _fm_presets / 10.0 else: preset = 76.0 rs = RadioSetting("fm_presets", "FM Preset(MHz)", RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)) fm_preset.append(rs) # DTMF encode settings for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 16, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("pttid/%i.code" % i, "Signal Code %i" % (i + 1), val) def apply_code(setting, obj): code = [] for j in range(0, 16): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.pttid[i]) dtmfe.append(rs) if _mem.ani.dtmfon > 0xC3: val = 0x03 else: val = _mem.ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) if _mem.ani.dtmfoff > 0xC3: val = 0x03 else: val = _mem.ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) _codeobj = self._memobj.ani.code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 7, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.code", "ANI Code", val) def apply_code(setting, obj): code = [] for j in range(0, 7): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfe.append(rs) rs = RadioSetting("ani.aniid", "When to send ANI ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[_mem.ani.aniid])) dtmfe.append(rs) # DTMF decode settings rs = RadioSetting("ani.mastervice", "Master and Vice ID", RadioSettingValueBoolean(_mem.ani.mastervice)) dtmfd.append(rs) _codeobj = _mem.ani.masterid _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.masterid", "Master Control ID", val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.masterid = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) rs = RadioSetting("ani.minspection", "Master Inspection", RadioSettingValueBoolean(_mem.ani.minspection)) dtmfd.append(rs) rs = RadioSetting("ani.mmonitor", "Master Monitor", RadioSettingValueBoolean(_mem.ani.mmonitor)) dtmfd.append(rs) rs = RadioSetting("ani.mstun", "Master Stun", RadioSettingValueBoolean(_mem.ani.mstun)) dtmfd.append(rs) rs = RadioSetting("ani.mkill", "Master Kill", RadioSettingValueBoolean(_mem.ani.mkill)) dtmfd.append(rs) rs = RadioSetting("ani.mrevive", "Master Revive", RadioSettingValueBoolean(_mem.ani.mrevive)) dtmfd.append(rs) _codeobj = _mem.ani.viceid _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.viceid", "Vice Control ID", val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.viceid = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) rs = RadioSetting("ani.vinspection", "Vice Inspection", RadioSettingValueBoolean(_mem.ani.vinspection)) dtmfd.append(rs) rs = RadioSetting("ani.vmonitor", "Vice Monitor", RadioSettingValueBoolean(_mem.ani.vmonitor)) dtmfd.append(rs) rs = RadioSetting("ani.vstun", "Vice Stun", RadioSettingValueBoolean(_mem.ani.vstun)) dtmfd.append(rs) rs = RadioSetting("ani.vkill", "Vice Kill", RadioSettingValueBoolean(_mem.ani.vkill)) dtmfd.append(rs) rs = RadioSetting("ani.vrevive", "Vice Revive", RadioSettingValueBoolean(_mem.ani.vrevive)) dtmfd.append(rs) _codeobj = _mem.ani.inspection _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.inspection", "Inspection Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.inspection = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) _codeobj = _mem.ani.monitor _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.monitor", "Monitor Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.monitor = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) _codeobj = _mem.ani.alarmcode _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.alarm", "Alarm Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.alarmcode = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) _codeobj = _mem.ani.stun _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.stun", "Stun Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.stun = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) _codeobj = _mem.ani.kill _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.kill", "Kill Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.kill = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) _codeobj = _mem.ani.revive _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 8, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.revive", "Revive Code", val) def apply_code(setting, obj): code = [] for j in range(0, 8): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.revive = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmfd.append(rs) def apply_dmtf_listvalue(setting, obj): LOG.debug("Setting value: "+ str(setting.value) + " from list") val = str(setting.value) index = LIST_DTMF_SPECIAL_DIGITS.index(val) val = LIST_DTMF_SPECIAL_VALUES[index] obj.set_value(val) if _mem.ani.groupcode in LIST_DTMF_SPECIAL_VALUES: idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.ani.groupcode) else: idx = LIST_DTMF_SPECIAL_VALUES.index(0x0B) rs = RadioSetting("ani.groupcode", "Group Code", RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS, LIST_DTMF_SPECIAL_DIGITS[idx])) rs.set_apply_callback(apply_dmtf_listvalue, _mem.ani.groupcode) dtmfd.append(rs) if _mem.ani.spacecode in LIST_DTMF_SPECIAL_VALUES: idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.ani.spacecode) else: idx = LIST_DTMF_SPECIAL_VALUES.index(0x0C) rs = RadioSetting("ani.spacecode", "Space Code", RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS, LIST_DTMF_SPECIAL_DIGITS[idx])) rs.set_apply_callback(apply_dmtf_listvalue, _mem.ani.spacecode) dtmfd.append(rs) if _mem.ani.resettime > 0x9F: val = 0x4F else: val = _mem.ani.resettime rs = RadioSetting("ani.resettime", "Reset Time", RadioSettingValueList(LIST_RESETTIME, LIST_RESETTIME[val])) dtmfd.append(rs) if _mem.ani.delayproctime > 0x27: val = 0x04 else: val = _mem.ani.delayproctime rs = RadioSetting("ani.delayproctime", "Delay Processing Time", RadioSettingValueList(LIST_DELAYPROCTIME, LIST_DELAYPROCTIME[val])) dtmfd.append(rs) # Service settings for band in ["vhf", "uhf"]: for index in range(0, 10): key = "squelch.%s.sql%i" % (band, index) if band == "vhf": _obj = self._memobj.squelch.vhf elif band == "uhf": _obj = self._memobj.squelch.uhf val = RadioSettingValueInteger(0, 123, getattr(_obj, "sql%i" % (index))) if index == 0: val.set_mutable(False) name = "%s Squelch %i" % (band.upper(), index) rs = RadioSetting(key, name, val) service.append(rs) return top @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == 0x200E: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/ftm350.py0000644000016101777760000002775612646374217021573 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 . import time import struct import os import logging from chirp.drivers import yaesu_clone from chirp import chirp_common, directory, errors, util, bitwise, memmap from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings from chirp.settings import RadioSettingValueInteger, RadioSettingValueString LOG = logging.getLogger(__name__) mem_format = """ struct mem { u8 used:1, skip:2, unknown1:5; u8 unknown2:1, mode:3, unknown8:1, oddsplit:1, duplex:2; bbcd freq[3]; u8 unknownA:1, tmode:3, unknownB:4; bbcd split[3]; u8 power:2, tone:6; u8 unknownC:1, dtcs:7; u8 showalpha:1, unknown5:7; u8 unknown6; u8 offset; u8 unknown7[2]; }; struct lab { u8 string[8]; }; #seekto 0x0508; struct { char call[6]; u8 ssid; } aprs_my_callsign; #seekto 0x0480; struct mem left_memory_zero; #seekto 0x04A0; struct lab left_label_zero; #seekto 0x04C0; struct mem right_memory_zero; #seekto 0x04E0; struct lab right_label_zero; #seekto 0x0800; struct mem left_memory[500]; #seekto 0x2860; struct mem right_memory[500]; #seekto 0x48C0; struct lab left_label[518]; struct lab right_label[518]; """ _TMODES = ["", "Tone", "TSQL", "-RVT", "DTCS", "-PR", "-PAG"] TMODES = ["", "Tone", "TSQL", "", "DTCS", "", ""] MODES = ["FM", "AM", "NFM", "", "WFM"] DUPLEXES = ["", "", "-", "+", "split"] # TODO: add japaneese characters (viewable in special menu, scroll backwards) CHARSET = \ ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!"' + '#$%&`()*+,-./:;<=>?@[\\]^_`{|}~?????? ' + '?' * 91) POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=50), chirp_common.PowerLevel("Mid", watts=20), chirp_common.PowerLevel("Low", watts=5)] SKIPS = ["", "S", "P"] def aprs_call_to_str(_call): call = "" for i in str(_call): if i == "\xca": break call += i return call def _safe_read(radio, length): data = "" while len(data) < length: data += radio.pipe.read(length - len(data)) return data def _clone_in(radio): data = "" radio.pipe.timeout = 1 attempts = 30 data = memmap.MemoryMap("\x00" * (radio._memsize + 128)) length = 0 last_addr = 0 while length < radio._memsize: frame = radio.pipe.read(131) if length and not frame: raise errors.RadioError("Radio not responding") if not frame: attempts -= 1 if attempts <= 0: raise errors.RadioError("Radio not responding") if frame: addr, = struct.unpack(">H", frame[0:2]) checksum = ord(frame[130]) block = frame[2:130] cs = 0 for i in frame[:-1]: cs = (cs + ord(i)) % 256 if cs != checksum: LOG.debug("Calc: %02x Real: %02x Len: %i" % (cs, checksum, len(block))) raise errors.RadioError("Block failed checksum") radio.pipe.write("\x06") time.sleep(0.05) if (last_addr + 128) != addr: LOG.debug("Gap, expecting %04x, got %04x" % (last_addr+128, addr)) last_addr = addr data[addr] = block length += len(block) status = chirp_common.Status() status.cur = length status.max = radio._memsize status.msg = "Cloning from radio" radio.status_fn(status) return data def _clone_out(radio): radio.pipe.timeout = 1 # Seriously, WTF Yaesu? ranges = [ (0x0000, 0x0000), (0x0100, 0x0380), (0x0480, 0xFF80), (0x0080, 0x0080), (0xFFFE, 0xFFFE), ] for start, end in ranges: for i in range(start, end+1, 128): block = radio._mmap[i:i + 128] frame = struct.pack(">H", i) + block cs = 0 for byte in frame: cs += ord(byte) frame += chr(cs % 256) radio.pipe.write(frame) ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio refused block %i" % (i / 128)) time.sleep(0.05) status = chirp_common.Status() status.cur = i + 128 status.max = radio._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 if rawfreq > 2000000000: rawfreq = (rawfreq - 2000000000) + 1250 return rawfreq def set_freq(freq, obj, field): """Encode a frequency with any necessary fractional step flags""" obj[field] = freq / 10000 frac = freq % 10000 if frac >= 5000: frac -= 5000 obj[field][0].set_bits(0x80) if frac >= 2500: frac -= 2500 obj[field][0].set_bits(0x40) if frac >= 1250: frac -= 1250 obj[field][0].set_bits(0x20) return freq @directory.register class FTM350Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FTM-350""" BAUD_RATE = 48000 VENDOR = "Yaesu" MODEL = "FTM-350" _model = "" _memsize = 65536 _vfo = "" def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_ctone = False rf.has_settings = self._vfo == "left" rf.has_tuning_step = False rf.has_dtcs_polarity = False rf.has_sub_devices = self.VARIANT == "" rf.valid_skips = [] # FIXME: Finish this rf.valid_tmodes = [""] + [x for x in TMODES if x] rf.valid_modes = [x for x in MODES if x] rf.valid_duplexes = DUPLEXES rf.valid_skips = SKIPS rf.valid_name_length = 8 rf.valid_characters = CHARSET rf.memory_bounds = (0, 500) rf.valid_power_levels = POWER_LEVELS rf.valid_bands = [(500000, 1800000), (76000000, 250000000), (30000000, 1000000000)] rf.can_odd_split = True return rf def get_sub_devices(self): return [FTM350RadioLeft(self._mmap), FTM350RadioRight(self._mmap)] def sync_in(self): try: self._mmap = _clone_in(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to download from radio (%s)" % e) self.process_mmap() def sync_out(self): try: _clone_out(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to upload to radio (%s)" % e) def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def get_raw_memory(self, number): def identity(o): return o def indexed(o): return o[number - 1] if number == 0: suffix = "_zero" fn = identity else: suffix = "" fn = indexed return (repr(fn(self._memory_obj(suffix))) + repr(fn(self._label_obj(suffix)))) def _memory_obj(self, suffix=""): return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix)) def _label_obj(self, suffix=""): return getattr(self._memobj, "%s_label%s" % (self._vfo, suffix)) def get_memory(self, number): if number == 0: _mem = self._memory_obj("_zero") _lab = self._label_obj("_zero") else: _mem = self._memory_obj()[number - 1] _lab = self._label_obj()[number - 1] mem = chirp_common.Memory() mem.number = number if not _mem.used: mem.empty = True return mem mem.freq = get_freq(int(_mem.freq) * 10000) mem.rtone = chirp_common.TONES[_mem.tone] mem.tmode = TMODES[_mem.tmode] if _mem.oddsplit: mem.duplex = "split" mem.offset = get_freq(int(_mem.split) * 10000) else: mem.duplex = DUPLEXES[_mem.duplex] mem.offset = int(_mem.offset) * 50000 mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.mode = MODES[_mem.mode] mem.skip = SKIPS[_mem.skip] mem.power = POWER_LEVELS[_mem.power] for char in _lab.string: if char == 0xCA: break try: mem.name += CHARSET[char] except IndexError: mem.name += "?" mem.name = mem.name.rstrip() return mem def set_memory(self, mem): if mem.number == 0: _mem = self._memory_obj("_zero") _lab = self._label_obj("_zero") else: _mem = self._memory_obj()[mem.number - 1] _lab = self._label_obj()[mem.number - 1] _mem.used = not mem.empty if mem.empty: return set_freq(mem.freq, _mem, 'freq') _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tmode = TMODES.index(mem.tmode) _mem.mode = MODES.index(mem.mode) _mem.skip = SKIPS.index(mem.skip) _mem.oddsplit = 0 _mem.duplex = 0 if mem.duplex == "split": set_freq(mem.offset, _mem, 'split') _mem.oddsplit = 1 else: _mem.offset = mem.offset / 50000 _mem.duplex = DUPLEXES.index(mem.duplex) if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 for i in range(0, 8): try: char = CHARSET.index(mem.name[i]) except IndexError: char = 0xCA _lab.string[i] = char _mem.showalpha = mem.name.strip() != "" @classmethod def match_model(self, filedata, filename): return filedata.startswith("AH033$") def get_settings(self): top = RadioSettings() aprs = RadioSettingGroup("aprs", "APRS") top.append(aprs) myc = self._memobj.aprs_my_callsign rs = RadioSetting("aprs_my_callsign.call", "APRS My Callsign", RadioSettingValueString(0, 6, aprs_call_to_str(myc.call))) aprs.append(rs) rs = RadioSetting("aprs_my_callsign.ssid", "APRS My SSID", RadioSettingValueInteger(0, 15, myc.ssid)) aprs.append(rs) return top def set_settings(self, settings): for setting in settings: if not isinstance(setting, RadioSetting): self.set_settings(setting) continue # Quick hack to make these work if setting.get_name() == "aprs_my_callsign.call": self._memobj.aprs_my_callsign.call = \ setting.value.get_value().upper().replace(" ", "\xCA") elif setting.get_name() == "aprs_my_callsign.ssid": self._memobj.aprs_my_callsign.ssid = setting.value class FTM350RadioLeft(FTM350Radio): VARIANT = "Left" _vfo = "left" class FTM350RadioRight(FTM350Radio): VARIANT = "Right" _vfo = "right" chirp-daily-20170714/chirp/drivers/anytone_ht.py0000644000016101777760000007275112646374217022720 0ustar jenkinsnogroup00000000000000# Copyright 2015 Jim Unroe # # 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 struct import time import logging from chirp import bitwise from chirp import chirp_common from chirp import directory from chirp import errors from chirp import memmap from chirp import util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings LOG = logging.getLogger(__name__) mem_format = """ struct memory { bbcd freq[4]; bbcd offset[4]; u8 unknown1:4, tune_step:4; u8 unknown2:2, txdcsextra:1, txinv:1, channel_width:2, unknown3:1, tx_off:1; u8 unknown4:2, rxdcsextra:1, rxinv:1, power:2, duplex:2; u8 unknown5:4, rxtmode:2, txtmode:2; u8 unknown6:2, txtone:6; u8 unknown7:2, rxtone:6; u8 txcode; u8 rxcode; u8 unknown8[3]; char name[6]; u8 squelch:4, unknown9:2, bcl:2; u8 unknownA; u8 unknownB:7, sqlmode:1; u8 unknownC[4]; }; #seekto 0x0010; struct { u8 unknown1; u8 unknown2:5, bands1:3; char model[7]; u8 unknown3:5, bands2:3; u8 unknown4[6]; u8 unknown5[16]; char date[9]; u8 unknown6[7]; u8 unknown7[16]; u8 unknown8[16]; char dealer[16]; char stockdate[9]; u8 unknown9[7]; char selldate[9]; u8 unknownA[7]; char seller[16]; } oem_info; #seekto 0x0100; u8 used_flags[50]; #seekto 0x0120; u8 skip_flags[50]; #seekto 0x0220; struct { u8 unknown220:6, display:2; u8 unknown221:7, upvfomr:1; u8 unknown222:7, dnvfomr:1; u8 unknown223:7, fmvfomr:1; u8 upmrch; u8 dnmrch; u8 unknown226:1, fmmrch:7; u8 unknown227; u8 unknown228:7, fastscano:1; // obltr-8r only u8 unknown229:6, pause:2; u8 unknown22A:5, stop:3; u8 unknown22B:6, backlight:2; u8 unknown22C:6, color:2; u8 unknown22D:6, vdisplay:2; u8 unknown22E; u8 unknown22F:5, pf1key:3; u8 beep:1, alarmoff:1, main:1, radio:1, unknown230:1, allband:1, elimtail:1, monikey:1; u8 fastscan:1, // termn-8r only keylock:1, unknown231:2, lwenable:1, swenable:1, fmenable:1, amenable:1; u8 unknown232:3, tot:5; u8 unknown233:7, amvfomr:1; u8 unknown234:3, apo:5; u8 unknown235:5, pf2key:3; // keylock for obltr-8r u8 unknown236; u8 unknown237:4, save:4; u8 unknown238:5, tbst:3; u8 unknown239:4, voxlevel:4; u8 unknown23A:3, voxdelay:5; u8 unknown23B:5, tail:3; u8 unknown23C; u8 unknown23D:1, ammrch:7; u8 unknown23E:5, vvolume:3; u8 unknown23F:5, fmam:3; u8 unknown240:4, upmrbank:4; u8 unknown241:7, upwork:1; u8 unknown242:7, uplink:1; u8 unknown243:4, dnmrbank:4; u8 unknown244:7, dnwork:1; u8 unknown245:7, downlink:1; u8 unknown246:7, banklink1:1; u8 unknown247:7, banklink2:1; u8 unknown248:7, banklink3:1; u8 unknown249:7, banklink4:1; u8 unknown24A:7, banklink5:1; u8 unknown24B:7, banklink6:1; u8 unknown24C:7, banklink7:1; u8 unknown24D:7, banklink8:1; u8 unknown24E:7, banklink9:1; u8 unknown24F:7, banklink0:1; u8 unknown250:6, noaa:2; u8 unknown251:5, noaach:3; u8 unknown252:6, part95:2; u8 unknown253:3, gmrs:5; u8 unknown254:5, murs:3; u8 unknown255:5, amsql:3; } settings; #seekto 0x0246; struct { u8 unused:7, bank:1; } banklink[10]; #seekto 0x03E0; struct { char line1[6]; char line2[6]; } welcome_msg; #seekto 0x2000; struct memory memory[200]; """ def _echo_write(radio, data): try: radio.pipe.write(data) except Exception, e: LOG.error("Error writing to radio: %s" % e) raise errors.RadioError("Unable to write to radio") def _read(radio, length): try: data = radio.pipe.read(length) except Exception, e: LOG.error("Error reading from radio: %s" % e) raise errors.RadioError("Unable to read from radio") if len(data) != length: LOG.error("Short read from radio (%i, expected %i)" % (len(data), length)) LOG.debug(util.hexprint(data)) raise errors.RadioError("Short read from radio") return data valid_model = ['TERMN8R', 'OBLTR8R'] def _ident(radio): radio.pipe.timeout = 1 _echo_write(radio, "PROGRAM") response = radio.pipe.read(3) if response != "QX\x06": LOG.debug("Response was:\n%s" % util.hexprint(response)) raise errors.RadioError("Radio did not respond. Check connection.") _echo_write(radio, "\x02") response = radio.pipe.read(16) LOG.debug(util.hexprint(response)) if radio._file_ident not in response: LOG.debug("Response was:\n%s" % util.hexprint(response)) raise errors.RadioError("Unsupported model") def _finish(radio): endframe = "\x45\x4E\x44" _echo_write(radio, endframe) result = radio.pipe.read(1) if result != "\x06": LOG.debug("Got:\n%s" % util.hexprint(result)) raise errors.RadioError("Radio did not finish cleanly") def _checksum(data): cs = 0 for byte in data: cs += ord(byte) return cs % 256 def _send(radio, cmd, addr, length, data=None): frame = struct.pack(">cHb", cmd, addr, length) if data: frame += data frame += chr(_checksum(frame[1:])) frame += "\x06" _echo_write(radio, frame) LOG.debug("Sent:\n%s" % util.hexprint(frame)) if data: result = radio.pipe.read(1) if result != "\x06": LOG.debug("Ack was: %s" % repr(result)) raise errors.RadioError( "Radio did not accept block at %04x" % addr) return result = _read(radio, length + 6) LOG.debug("Got:\n%s" % util.hexprint(result)) header = result[0:4] data = result[4:-2] ack = result[-1] if ack != "\x06": LOG.debug("Ack was: %s" % repr(ack)) raise errors.RadioError("Radio NAK'd block at %04x" % addr) _cmd, _addr, _length = struct.unpack(">cHb", header) if _addr != addr or _length != _length: LOG.debug("Expected/Received:") LOG.debug(" Length: %02x/%02x" % (length, _length)) LOG.debug(" Addr: %04x/%04x" % (addr, _addr)) raise errors.RadioError("Radio send unexpected block") cs = _checksum(result[1:-2]) if cs != ord(result[-2]): LOG.debug("Calculated: %02x" % cs) LOG.debug("Actual: %02x" % ord(result[-2])) raise errors.RadioError("Block at 0x%04x failed checksum" % addr) return data def _download(radio): _ident(radio) memobj = None data = "" for start, end in radio._ranges: for addr in range(start, end, 0x10): block = _send(radio, 'R', addr, 0x10) data += block status = chirp_common.Status() status.cur = len(data) status.max = end status.msg = "Cloning from radio" radio.status_fn(status) _finish(radio) return memmap.MemoryMap(data) def _upload(radio): _ident(radio) for start, end in radio._ranges: for addr in range(start, end, 0x10): if addr < 0x0100: continue block = radio._mmap[addr:addr + 0x10] _send(radio, 'W', addr, len(block), block) status = chirp_common.Status() status.cur = addr status.max = end status.msg = "Cloning to radio" radio.status_fn(status) _finish(radio) APO = ['Off', '30 Minutes', '1 Hour', '2 Hours'] BACKLIGHT = ['Off', 'On', 'Auto'] BCLO = ['Off', 'Repeater', 'Busy'] CHARSET = chirp_common.CHARSET_ASCII COLOR = ['Blue', 'Orange', 'Purple'] DISPLAY = ['Frequency', 'N/A', 'Name'] DUPLEXES = ['', 'N/A', '-', '+', 'split', 'off'] GMRS = ['GMRS %s' % x for x in range(1, 8)] + \ ['GMRS %s' % x for x in range(15, 23)] + \ ['GMRS Repeater %s' % x for x in range(15, 23)] MAIN = ['Up', 'Down'] MODES = ['FM', 'NFM'] MONI = ['Squelch Off Momentarily', 'Squelch Off'] MRBANK = ['Bank %s' % x for x in range(1, 10)] + ['Bank 0'] MURS = ['MURS %s' % x for x in range(1, 6)] NOAA = ['Weather Off', 'Weather On', 'Weather Alerts'] NOAACH = ['WX %s' % x for x in range(1, 8)] PART95 = ['Normal(Part 90)', 'GMRS(Part 95A)', 'MURS(Part 95J)'] PAUSE = ['%s Seconds (TO)' % x for x in range(5, 20, 5)] + ['2 Seconds (CO)'] PFKEYT = ['Off', 'VOLT', 'CALL', 'FHSS', 'SUB PTT', 'ALARM', 'MONI'] PFKEYO = ['Off', 'VOLT', 'CALL', 'SUB PTT', 'ALARM', 'MONI'] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5), chirp_common.PowerLevel("Mid", watts=2), chirp_common.PowerLevel("Low", watts=1)] SAVE = ['Off', '1:2', '1:3', '1:5', '1:8', 'Auto'] SQUELCH = ['%s' % x for x in range(0, 10)] STOP = ['%s Seconds' % x for x in range(0, 4)] + ['Manual'] TAIL = ['Off', '120 Degree', '180 Degree', '240 Degree'] TBST = ['Off', '1750 Hz', '2100 Hz', '1000 Hz', '1450 Hz'] TMODES = ['', 'Tone', 'DTCS', ''] TONES = [62.5] + list(chirp_common.TONES) TOT = ['Off'] + ['%s Seconds' % x for x in range(10, 280, 10)] VDISPLAY = ['Frequency/Channel', 'Battery Voltage', 'Off'] VFOMR = ["VFO", "MR"] VOXLEVEL = ['Off'] + ['%s' % x for x in range(1, 11)] VOXDELAY = ['%.1f Seconds' % (0.1 * x) for x in range(5, 31)] WORKMODE = ["Channel", "Bank"] @directory.register class AnyToneTERMN8RRadio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """AnyTone TERMN-8R""" VENDOR = "AnyTone" MODEL = "TERMN-8R" BAUD_RATE = 9600 _file_ident = "TERMN8R" # May try to mirror the OEM behavior later _ranges = [ (0x0000, 0x8000), ] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("The Anytone driver is currently experimental. " "There are no known issues with it, but you should " "proceed with caution.") return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_cross = True rf.has_tuning_step = False rf.has_rx_dtcs = True rf.valid_skips = ["", "S"] rf.valid_modes = MODES rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES rf.valid_bands = [(136000000, 174000000), (400000000, 520000000)] rf.valid_characters = CHARSET rf.valid_name_length = 6 rf.valid_power_levels = POWER_LEVELS rf.valid_duplexes = DUPLEXES rf.can_odd_split = True rf.memory_bounds = (0, 199) return rf def sync_in(self): self._mmap = _download(self) self.process_mmap() def sync_out(self): _upload(self) def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def _get_dcs_index(self, _mem, which): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) return (int(extra) << 8) | int(base) def _set_dcs_index(self, _mem, which, index): base = getattr(_mem, '%scode' % which) extra = getattr(_mem, '%sdcsextra' % which) base.set_value(index & 0xFF) extra.set_value(index >> 8) def get_memory(self, number): bitpos = (1 << (number % 8)) bytepos = (number / 8) _mem = self._memobj.memory[number] _skp = self._memobj.skip_flags[bytepos] _usd = self._memobj.used_flags[bytepos] mem = chirp_common.Memory() mem.number = number if _usd & bitpos: mem.empty = True return mem mem.freq = int(_mem.freq) * 100 mem.offset = int(_mem.offset) * 100 mem.name = self.filter_name(str(_mem.name).rstrip()) mem.duplex = DUPLEXES[_mem.duplex] mem.mode = MODES[_mem.channel_width] if _mem.tx_off == True: mem.duplex = "off" mem.offset = 0 rxtone = txtone = None rxmode = TMODES[_mem.rxtmode] txmode = TMODES[_mem.txtmode] if txmode == "Tone": txtone = TONES[_mem.txtone] elif txmode == "DTCS": txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem, 'tx')] if rxmode == "Tone": rxtone = TONES[_mem.rxtone] elif rxmode == "DTCS": rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem, 'rx')] rxpol = _mem.rxinv and "R" or "N" txpol = _mem.txinv and "R" or "N" chirp_common.split_tone_decode(mem, (txmode, txtone, txpol), (rxmode, rxtone, rxpol)) if _skp & bitpos: mem.skip = "S" mem.power = POWER_LEVELS[_mem.power] mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueList(BCLO, BCLO[_mem.bcl])) mem.extra.append(rs) rs = RadioSetting("squelch", "Squelch", RadioSettingValueList(SQUELCH, SQUELCH[_mem.squelch])) mem.extra.append(rs) 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.skip_flags[bytepos] _usd = self._memobj.used_flags[bytepos] if mem.empty: _usd |= bitpos _skp |= bitpos _mem.set_raw("\xFF" * 32) return _usd &= ~bitpos if _mem.get_raw() == ("\xFF" * 32): LOG.debug("Initializing empty memory") _mem.set_raw("\x00" * 32) _mem.squelch = 3 _mem.freq = mem.freq / 100 if mem.duplex == "off": _mem.duplex = DUPLEXES.index("") _mem.offset = 0 _mem.tx_off = True elif mem.duplex == "split": diff = mem.offset - mem.freq _mem.duplex = DUPLEXES.index("-") if diff < 0 \ else DUPLEXES.index("+") _mem.offset = abs(diff) / 100 else: _mem.offset = mem.offset / 100 _mem.duplex = DUPLEXES.index(mem.duplex) _mem.name = mem.name.ljust(6) try: _mem.channel_width = MODES.index(mem.mode) except ValueError: _mem.channel_width = 0 ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem) _mem.txtmode = TMODES.index(txmode) _mem.rxtmode = TMODES.index(rxmode) if txmode == "Tone": _mem.txtone = TONES.index(txtone) elif txmode == "DTCS": self._set_dcs_index(_mem, 'tx', chirp_common.ALL_DTCS_CODES.index(txtone)) if rxmode == "Tone": _mem.sqlmode = 1 _mem.rxtone = TONES.index(rxtone) elif rxmode == "DTCS": _mem.sqlmode = 1 self._set_dcs_index(_mem, 'rx', chirp_common.ALL_DTCS_CODES.index(rxtone)) else: _mem.sqlmode = 0 _mem.txinv = txpol == "R" _mem.rxinv = rxpol == "R" if mem.skip: _skp |= bitpos else: _skp &= ~bitpos if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _msg = self._memobj.welcome_msg _oem = self._memobj.oem_info _settings = self._memobj.settings cfg_grp = RadioSettingGroup("cfg_grp", "Function Setup") oem_grp = RadioSettingGroup("oem_grp", "OEM Info") group = RadioSettings(cfg_grp, oem_grp) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered # # Function Setup # rs = RadioSetting("welcome_msg.line1", "Welcome Message 1", RadioSettingValueString( 0, 6, _filter(_msg.line1))) cfg_grp.append(rs) rs = RadioSetting("welcome_msg.line2", "Welcome Message 2", RadioSettingValueString( 0, 6, _filter(_msg.line2))) cfg_grp.append(rs) rs = RadioSetting("display", "Display Mode", RadioSettingValueList(DISPLAY, DISPLAY[_settings.display])) cfg_grp.append(rs) rs = RadioSetting("upvfomr", "Up VFO/MR", RadioSettingValueList(VFOMR, VFOMR[_settings.upvfomr])) cfg_grp.append(rs) rs = RadioSetting("dnvfomr", "Down VFO/MR", RadioSettingValueList(VFOMR, VFOMR[_settings.dnvfomr])) cfg_grp.append(rs) rs = RadioSetting("upwork", "Up Work Mode", RadioSettingValueList(WORKMODE, WORKMODE[_settings.upwork])) cfg_grp.append(rs) rs = RadioSetting("upmrbank", "Up MR Bank", RadioSettingValueList(MRBANK, MRBANK[_settings.upmrbank])) cfg_grp.append(rs) rs = RadioSetting("upmrch", "Up MR Channel", RadioSettingValueInteger(0, 200, _settings.upmrch)) cfg_grp.append(rs) rs = RadioSetting("dnwork", "Down Work Mode", RadioSettingValueList(WORKMODE, WORKMODE[_settings.dnwork])) cfg_grp.append(rs) rs = RadioSetting("dnmrbank", "Down MR Bank", RadioSettingValueList(MRBANK, MRBANK[_settings.dnmrbank])) cfg_grp.append(rs) rs = RadioSetting("dnmrch", "Down MR Channel", RadioSettingValueInteger(0, 200, _settings.dnmrch)) cfg_grp.append(rs) rs = RadioSetting("main", "Main", RadioSettingValueList(MAIN, MAIN[_settings.main])) cfg_grp.append(rs) rs = RadioSetting("pause", "Scan Pause Time", RadioSettingValueList(PAUSE, PAUSE[_settings.pause])) cfg_grp.append(rs) rs = RadioSetting("stop", "Function Keys Stop Time", RadioSettingValueList(STOP, STOP[_settings.stop])) cfg_grp.append(rs) rs = RadioSetting("backlight", "Backlight", RadioSettingValueList(BACKLIGHT, BACKLIGHT[_settings.backlight])) cfg_grp.append(rs) rs = RadioSetting("color", "Backlight Color", RadioSettingValueList(COLOR, COLOR[_settings.color])) cfg_grp.append(rs) rs = RadioSetting("vdisplay", "Vice-Machine Display", RadioSettingValueList(VDISPLAY, VDISPLAY[_settings.vdisplay])) cfg_grp.append(rs) rs = RadioSetting("voxlevel", "Vox Level", RadioSettingValueList(VOXLEVEL, VOXLEVEL[_settings.voxlevel])) cfg_grp.append(rs) rs = RadioSetting("voxdelay", "Vox Delay", RadioSettingValueList(VOXDELAY, VOXDELAY[_settings.voxdelay])) cfg_grp.append(rs) rs = RadioSetting("tot", "Time Out Timer", RadioSettingValueList(TOT, TOT[_settings.tot])) cfg_grp.append(rs) rs = RadioSetting("tbst", "Tone Burst", RadioSettingValueList(TBST, TBST[_settings.tbst])) cfg_grp.append(rs) rs = RadioSetting("monikey", "MONI Key Function", RadioSettingValueList(MONI, MONI[_settings.monikey])) cfg_grp.append(rs) if self.MODEL == "TERMN-8R": rs = RadioSetting("pf1key", "PF1 Key Function", RadioSettingValueList(PFKEYT, PFKEYT[_settings.pf1key])) cfg_grp.append(rs) rs = RadioSetting("pf2key", "PF2 Key Function", RadioSettingValueList(PFKEYT, PFKEYT[_settings.pf2key])) cfg_grp.append(rs) if self.MODEL == "OBLTR-8R": rs = RadioSetting("pf1key", "PF1 Key Function", RadioSettingValueList(PFKEYO, PFKEYO[_settings.pf1key])) cfg_grp.append(rs) rs = RadioSetting("fmam", "PF2 Key Function", RadioSettingValueList(PFKEYO, PFKEYO[_settings.fmam])) cfg_grp.append(rs) rs = RadioSetting("apo", "Automatic Power Off", RadioSettingValueList(APO, APO[_settings.apo])) cfg_grp.append(rs) rs = RadioSetting("save", "Power Save", RadioSettingValueList(SAVE, SAVE[_settings.save])) cfg_grp.append(rs) rs = RadioSetting("tail", "Tail Eliminator Type", RadioSettingValueList(TAIL, TAIL[_settings.tail])) cfg_grp.append(rs) rs = RadioSetting("fmvfomr", "FM VFO/MR", RadioSettingValueList(VFOMR, VFOMR[_settings.fmvfomr])) cfg_grp.append(rs) rs = RadioSetting("fmmrch", "FM MR Channel", RadioSettingValueInteger(0, 100, _settings.fmmrch)) cfg_grp.append(rs) rs = RadioSetting("noaa", "NOAA", RadioSettingValueList(NOAA, NOAA[_settings.noaa])) cfg_grp.append(rs) rs = RadioSetting("noaach", "NOAA Channel", RadioSettingValueList(NOAACH, NOAACH[_settings.noaach])) cfg_grp.append(rs) rs = RadioSetting("part95", "PART95", RadioSettingValueList(PART95, PART95[_settings.part95])) cfg_grp.append(rs) rs = RadioSetting("gmrs", "GMRS", RadioSettingValueList(GMRS, GMRS[_settings.gmrs])) cfg_grp.append(rs) rs = RadioSetting("murs", "MURS", RadioSettingValueList(MURS, MURS[_settings.murs])) cfg_grp.append(rs) for i in range(0, 9): val = self._memobj.banklink[i].bank rs = RadioSetting("banklink/%i.bank" % i, "Bank Link %i" % (i + 1), RadioSettingValueBoolean(val)) cfg_grp.append(rs) val = self._memobj.banklink[9].bank rs = RadioSetting("banklink/9.bank", "Bank Link 0", RadioSettingValueBoolean(val)) cfg_grp.append(rs) rs = RadioSetting("allband", "All Band", RadioSettingValueBoolean(_settings.allband)) cfg_grp.append(rs) rs = RadioSetting("alarmoff", "Alarm Function Off", RadioSettingValueBoolean(_settings.alarmoff)) cfg_grp.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) cfg_grp.append(rs) rs = RadioSetting("radio", "Radio", RadioSettingValueBoolean(_settings.radio)) cfg_grp.append(rs) if self.MODEL == "TERMN-8R": rs = RadioSetting("keylock", "Keylock", RadioSettingValueBoolean(_settings.keylock)) cfg_grp.append(rs) rs = RadioSetting("fastscan", "Fast Scan", RadioSettingValueBoolean(_settings.fastscan)) cfg_grp.append(rs) if self.MODEL == "OBLTR-8R": # "pf2key" is used for OBLTR-8R "keylock" rs = RadioSetting("pf2key", "Keylock", RadioSettingValueBoolean(_settings.pf2key)) cfg_grp.append(rs) rs = RadioSetting("fastscano", "Fast Scan", RadioSettingValueBoolean(_settings.fastscano)) cfg_grp.append(rs) rs = RadioSetting("uplink", "Up Bank Link Select", RadioSettingValueBoolean(_settings.uplink)) cfg_grp.append(rs) rs = RadioSetting("downlink", "Down Bank Link Select", RadioSettingValueBoolean(_settings.downlink)) cfg_grp.append(rs) # # OEM info # val = RadioSettingValueString(0, 7, _filter(_oem.model)) val.set_mutable(False) rs = RadioSetting("oem_info.model", "Model", val) oem_grp.append(rs) val = RadioSettingValueString(0, 9, _filter(_oem.date)) val.set_mutable(False) rs = RadioSetting("oem_info.date", "Date", val) oem_grp.append(rs) val = RadioSettingValueString(0, 16, _filter(_oem.dealer)) val.set_mutable(False) rs = RadioSetting("oem_info.dealer", "Dealer Code", val) oem_grp.append(rs) val = RadioSettingValueString(0, 9, _filter(_oem.stockdate)) val.set_mutable(False) rs = RadioSetting("oem_info.stockdate", "Stock Date", val) oem_grp.append(rs) val = RadioSettingValueString(0, 9, _filter(_oem.selldate)) val.set_mutable(False) rs = RadioSetting("oem_info.selldate", "Sell Date", val) oem_grp.append(rs) 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 else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): return cls._file_ident in filedata[0x10:0x20] @directory.register class AnyToneOBLTR8RRadio(AnyToneTERMN8RRadio): """AnyTone OBLTR-8R""" VENDOR = "AnyTone" MODEL = "OBLTR-8R" _file_ident = "OBLTR8R" chirp-daily-20170714/chirp/drivers/ic9x_icf.py0000644000016101777760000000472512476006422022230 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.drivers import icf, ic9x_icf_ll from chirp import chirp_common, util, directory, errors # Don't register as this module is used to load icf file from File-Open menu # see do_open in mainapp.py 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-daily-20170714/chirp/drivers/icx8x.py0000644000016101777760000001335213067650002021566 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 logging from chirp.drivers import icf, icx8x_ll from chirp import chirp_common, errors, directory LOG = logging.getLogger(__name__) 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") LOG.debug("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 = [5., 10., 12.5, 15., 20., 25., 30., 50.] 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) # LOG.debug("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-daily-20170714/chirp/drivers/vx6.py0000644000016101777760000002745112476006422021257 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.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from textwrap import dedent # 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; struct { u16 in_use; } bank_used[24]; #seekto 0x0214; u16 banksoff1; #seekto 0x0294; u16 banksoff2; #seekto 0x097A; struct { u8 name[6]; } bank_names[24]; #seekto 0x0C0A; struct { u16 channels[100]; } banks[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[500]; #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[999]; """ 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)] class VX6Bank(chirp_common.NamedBank): """A VX6 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.rstrip() 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 VX6BankModel(chirp_common.BankModel): """A VX-6 bank model""" def get_num_mappings(self): return len(self.get_mappings()) def get_mappings(self): banks = self._radio._memobj.banks bank_mappings = [] for index, _bank in enumerate(banks): bank = VX6Bank(self, "%i" % index, "b%i" % (index + 1)) bank.index = index bank_mappings.append(bank) return bank_mappings def _get_channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return set() _members = self._radio._memobj.banks[bank.index] return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF]) def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.banks[bank.index] if len(channels_in_bank) > len(_members.channels): raise Exception("Too many entries in bank %d" % bank.index) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channels[index] = channel_number - 1 empty = index + 1 for index in range(empty, len(_members.channels)): _members.channels[index] = 0xFFFF def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0x0000 # enable # also needed for unit to recognize any banks? self._radio._memobj.banksoff1 = 0x0000 self._radio._memobj.banksoff2 = 0x0000 # TODO: turn back off (0xFFFF) when all banks are empty? def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) if not channels_in_bank: _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0xFFFF # disable bank def get_mapping_memories(self, bank): memories = [] for channel in self._get_channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._get_channel_numbers_in_bank(bank): banks.append(bank) return banks @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 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [BAND] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [V/M] key ("-WAIT-" will appear on the LCD).""")) return rp 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 = True rf.has_bank_names = True 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, 999) 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_bank_model(self): return VX6BankModel(self) chirp-daily-20170714/chirp/drivers/th_uv3r25.py0000644000016101777760000001404012646374217022274 0ustar jenkinsnogroup00000000000000# Copyright 2015 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 . """TYT uv3r (2.5kHz) radio management module""" from chirp import chirp_common, bitwise, directory from chirp.drivers.wouxun import do_download, do_upload from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString from th_uv3r import TYTUV3RRadio, tyt_uv3r_prep, THUV3R_CHARSET def tyt_uv3r_download(radio): tyt_uv3r_prep(radio) return do_download(radio, 0x0000, 0x0B30, 0x0010) def tyt_uv3r_upload(radio): tyt_uv3r_prep(radio) return do_upload(radio, 0x0000, 0x0B30, 0x0010) mem_format = """ // 20 bytes per memory struct memory { ul32 rx_freq; // 4 bytes ul32 tx_freq; // 8 bytes ul16 rx_tone; // 10 bytes ul16 tx_tone; // 12 bytes u8 unknown1a:1, iswide:1, bclo_n:1, vox_n:1, tail:1, power_high:1, voice_mode:2; u8 name[6]; // 19 bytes u8 unknown2; // 20 bytes }; #seekto 0x0010; struct memory memory[128]; #seekto 0x0A80; u8 emptyflags[16]; u8 skipflags[16]; """ VOICE_MODE_LIST = ["Compander", "Scrambler", "None"] @directory.register class TYTUV3R25Radio(TYTUV3RRadio): MODEL = "TH-UV3R-25" _memsize = 2864 POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00), chirp_common.PowerLevel("Low", watts=0.80)] def get_features(self): rf = super(TYTUV3R25Radio, self).get_features() rf.valid_tuning_steps = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 37.50, 50.0, 100.0] rf.valid_power_levels = self.POWER_LEVELS return rf def sync_in(self): self.pipe.timeout = 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 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 mem.freq = _mem.rx_freq * 10 mem.offset = abs(_mem.rx_freq - _mem.tx_freq) * 10 if _mem.tx_freq == _mem.rx_freq: mem.duplex = "" elif _mem.tx_freq < _mem.rx_freq: mem.duplex = "-" elif _mem.tx_freq > _mem.rx_freq: 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() mem.power = self.POWER_LEVELS[not _mem.power_high] mem.extra = RadioSettingGroup("extra", "Extra Settings") rs = RadioSetting("bclo_n", "Busy Channel Lockout", RadioSettingValueBoolean(not _mem.bclo_n)) mem.extra.append(rs) rs = RadioSetting("vox_n", "VOX", RadioSettingValueBoolean(not _mem.vox_n)) mem.extra.append(rs) rs = RadioSetting("tail", "Squelch Tail Elimination", RadioSettingValueBoolean(_mem.tail)) mem.extra.append(rs) rs = RadioSetting("voice_mode", "Voice Mode", RadioSettingValueList( VOICE_MODE_LIST, VOICE_MODE_LIST[_mem.voice_mode-1])) mem.extra.append(rs) 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 _mem.rx_freq = mem.freq / 10 if mem.duplex == "": _mem.tx_freq = _mem.rx_freq elif mem.duplex == "-": _mem.tx_freq = _mem.rx_freq - mem.offset / 10.0 elif mem.duplex == "+": _mem.tx_freq = _mem.rx_freq + mem.offset / 10.0 _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 if mem.power == self.POWER_LEVELS[0]: _mem.power_high = 1 else: _mem.power_high = 0 for element in mem.extra: if element.get_name() == 'voice_mode': setattr(_mem, element.get_name(), int(element.value) + 1) elif element.get_name().endswith('_n'): setattr(_mem, element.get_name(), 1 - int(element.value)) else: setattr(_mem, element.get_name(), element.value) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize chirp-daily-20170714/chirp/drivers/vx5.py0000644000016101777760000002300113101305576021237 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.drivers import yaesu_clone from chirp import chirp_common, directory, errors, bitwise from textwrap import dedent 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 = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.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_mappings(self): return 5 def get_mappings(self): banks = [] for i in range(0, self.get_num_mappings()): bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1)) bank.index = i banks.append(bank) return banks def add_memory_to_mapping(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: # LOG.debug("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_mapping(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_mapping_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_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in [x.number for x in self.get_mapping_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, 0x1FBA)] 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_tuning_steps = STEPS 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.OLD_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) try: _mem.tone = chirp_common.OLD_TONES.index(mem.rtone) except ValueError: raise errors.UnsupportedToneError( ("This radio does not support tone %s" % mem.rtone)) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _flg.skip = mem.skip == "S" _flg.pskip = mem.skip == "P" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/EAR jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [VFO(DW)SC] key to receive the image from the radio.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/EAR jack. 3. Press and hold in the [F/W] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [MR(SKP)SC] key ("CLONE WAIT" will appear on the LCD). 5. Click OK to send image to radio.""")) return rp @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def get_bank_model(self): return VX5BankModel(self) chirp-daily-20170714/chirp/drivers/rh5r_v2.py0000644000016101777760000002033513104535201022004 0ustar jenkinsnogroup00000000000000# Copyright 2017 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 . """Rugged RH5R V2 radio management module""" import struct import logging from chirp import chirp_common, bitwise, errors, directory, memmap, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) def _identify(radio): try: radio.pipe.write("PGM2015") ack = radio.pipe.read(2) if ack != "\x06\x30": raise errors.RadioError("Radio did not ACK first command: %r" % ack) except: LOG.exception('') raise errors.RadioError("Unable to communicate with the radio") def _download(radio): _identify(radio) data = [] for i in range(0, 0x2000, 0x40): msg = struct.pack('>cHb', 'R', i, 0x40) radio.pipe.write(msg) block = radio.pipe.read(0x40 + 4) if len(block) != (0x40 + 4): raise errors.RadioError("Radio sent a short block (%02x/%02x)" % ( len(block), 0x44)) data += block[4:] if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = 0x2000 status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("E") data += 'PGM2015' return memmap.MemoryMap(data) def _upload(radio): _identify(radio) for i in range(0, 0x2000, 0x40): msg = struct.pack('>cHb', 'W', i, 0x40) msg += radio._mmap[i:(i + 0x40)] radio.pipe.write(msg) ack = radio.pipe.read(1) if ack != '\x06': raise errors.RadioError('Radio did not ACK block %i (0x%04x)' % ( i, i)) if radio.status_fn: status = chirp_common.Status() status.cur = i status.max = 0x2000 status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("E") MEM_FORMAT = """ struct memory { bbcd rx_freq[4]; bbcd tx_freq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 unknown10:5, highpower:1, unknown11:2; u8 unknown20:4, narrow:1, unknown21:3; u8 unknown31:1, scanadd:1, unknown32:6; u8 unknown4; }; struct name { char name[7]; }; #seekto 0x0010; struct memory channels[128]; #seekto 0x08C0; struct name names[128]; #seekto 0x2020; struct memory vfo1; struct memory vfo2; """ POWER_LEVELS = [chirp_common.PowerLevel('Low', watts=1), chirp_common.PowerLevel('High', watts=5)] class TYTTHUVF8_V2(chirp_common.CloneModeRadio): VENDOR = "TYT" MODEL = "TH-UVF8F" BAUD_RATE = 9600 _FILEID = 'OEMOEM \XFF' 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 = False rf.can_odd_split = False rf.valid_duplexes = ['', '-', '+'] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-" rf.valid_bands = [(136000000, 174000000), (400000000, 480000000)] 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", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] return rf def sync_in(self): self._mmap = _download(self) self.process_mmap() def sync_out(self): _upload(self) @classmethod def match_model(cls, filedata, filename): return (filedata.endswith("PGM2015") and filedata[0x840:0x848] == cls._FILEID) def process_mmap(self): print MEM_FORMAT self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return (repr(self._memobj.channels[number - 1]) + repr(self._memobj.names[number - 1])) def _get_memobjs(self, number): return (self._memobj.channels[number - 1], self._memobj.names[number - 1]) 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_memory(self, number): _mem, _name = self._get_memobjs(number) mem = chirp_common.Memory() if isinstance(number, str): mem.number = SPECIALS[number] mem.extd_number = number else: mem.number = number if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"): mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 offset = (int(_mem.tx_freq) - int(_mem.rx_freq)) * 10 if not offset: mem.offset = 0 mem.duplex = '' elif offset < 0: mem.offset = abs(offset) mem.duplex = '-' else: mem.offset = offset mem.duplex = '+' 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.mode = 'NFM' if _mem.narrow else 'FM' mem.skip = '' if _mem.scanadd else 'S' mem.power = POWER_LEVELS[int(_mem.highpower)] mem.name = str(_name.name).rstrip('\xFF ') return mem def set_memory(self, mem): _mem, _name = self._get_memobjs(mem.number) if mem.empty: _mem.set_raw('\xFF' * 16) _name.set_raw('\xFF' * 7) return _mem.set_raw('\x00' * 16) _mem.rx_freq = mem.freq / 10 if mem.duplex == '-': mult = -1 elif not mem.duplex: mult = 0 else: mult = 1 _mem.tx_freq = (mem.freq + (mem.offset * mult)) / 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) _mem.narrow = mem.mode == 'NFM' _mem.scanadd = mem.skip != 'S' _mem.highpower = POWER_LEVELS.index(mem.power) if mem.power else 1 _name.name = mem.name.rstrip(' ').ljust(7, '\xFF') @directory.register class RH5RV2(TYTTHUVF8_V2): VENDOR = "Rugged" MODEL = "RH5R-V2" _FILEID = 'RUGGED \xFF' chirp-daily-20170714/chirp/drivers/ft60.py0000644000016101777760000006356513013011022021275 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 import os import logging from chirp.drivers import yaesu_clone from chirp import chirp_common, memmap, bitwise, directory, errors from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) 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) LOG.info("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 flags = 0x00 if ((freq / 1000) % 10) >= 5: flags += 0x80 if chirp_common.is_fractional_step(freq): flags += 0x40 return freqraw, flags def _decode_name(mem): name = "" for i in mem: if i == 0xFF: break try: name += CHARSET[i] except IndexError: LOG.error("Unknown char index: %i " % (i)) return name def _encode_name(mem): name = [None] * 6 for i in range(0, 6): try: name[i] = CHARSET.index(mem[i]) except IndexError: name[i] = CHARSET.index(" ") return name MEM_FORMAT = """ #seekto 0x0024; struct { u8 apo; u8 x25:3, tot:5; u8 x26; u8 x27; u8 x28:4, rf_sql:4; u8 x29:4, int_cd:4; u8 x2A:4, int_mr:4; u8 x2B:5, lock:3; u8 x2C:5, dt_dly:3; u8 x2D:7, dt_spd:1; u8 ar_bep; u8 x2F:6, lamp:2; u8 x30:5, bell:3; u8 x31:5, rxsave:3; u8 x32; u8 x33; u8 x34; u8 x35; u8 x36; u8 x37; u8 wx_alt:1, x38_1:3, ar_int:1, x38_5:3; u8 x39:3, ars:1, vfo_bnd:1, dcs_nr:2, ssrch:1; u8 pri_rvt:1, x3A_1:1, beep_sc:1, edg_bep:1, beep_key:1, inet:2, x3A_7:1; u8 x3B_0:5, scn_md:1, x3B_6:2; u8 x3C_0:2, rev_hm:1, mt_cl:1 resume:2, txsave:1, pag_abk:1; u8 x3D_0:1, scn_lmp:1, x3D_2:1, bsy_led:1, x3D_4:1, tx_led:1, x3D_6:2; u8 x3E_0:2, bclo:1, x3E_3:5; } settings; #seekto 0x09E; ul16 mbs; #seekto 0x0C8; struct { u8 memory[16]; } dtmf[9]; struct mem { 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; u16 unknown5_1:1 offset:15; u8 unknown6[3]; }; #seekto 0x0248; struct mem memory[1000]; #seekto 0x40c8; struct mem pms[100]; #seekto 0x6EC8; // skips:2 for Memory M in [1, 1000] is in flags[(M-1)/4].skip((M-1)%4). // Interpret with SKIPS[]. // PMS memories L0 - U50 aka memory 1001 - 1100 don't have skip flags. struct { u8 skip3:2, skip2:2, skip1:2, skip0:2; } flags[250]; #seekto 0x4708; struct { u8 name[6]; u8 use_name:1, unknown1:7; u8 valid:1, unknown2:7; } names[1000]; #seekto 0x69C8; struct { bbcd memory[128]; } banks[10]; #seekto 0x6FC8; u8 checksum; """ DUPLEX = ["", "", "-", "+", "split", "off"] TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.0), chirp_common.PowerLevel("Mid", watts=2.0), chirp_common.PowerLevel("Low", watts=0.5)] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] SKIPS = ["", "S", "P"] DTMF_CHARS = list("0123456789ABCD*#") CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ [?]^__|`?$%&-()*+,-,/|;/=>?@" SPECIALS = ["%s%d" % (c, i + 1) for i in range(0, 50) for c in ('L', 'U')] class FT60BankModel(chirp_common.BankModel): def get_num_mappings(self): return 10 def get_mappings(self): banks = [] for i in range(0, self.get_num_mappings()): bank = chirp_common.Bank(self, "%i" % (i + 1), "Bank %i" % (i + 1)) bank.index = i banks.append(bank) return banks def add_memory_to_mapping(self, memory, bank): number = (memory.number - 1) / 8 mask = 1 << ((memory.number - 1) & 7) self._radio._memobj.banks[bank.index].memory[number].set_bits(mask) def remove_memory_from_mapping(self, memory, bank): number = (memory.number - 1) / 8 mask = 1 << ((memory.number - 1) & 7) m = self._radio._memobj.banks[bank.index].memory[number] if m.get_bits(mask) != mask: raise Exception("Memory %i is not in bank %s." % (memory.number, bank)) self._radio._memobj.banks[bank.index].memory[number].clr_bits(mask) def get_mapping_memories(self, bank): memories = [] for i in range(*self._radio.get_features().memory_bounds): number = (i - 1) / 8 mask = 1 << ((i - 1) & 7) m = self._radio._memobj.banks[bank.index].memory[number] if m.get_bits(mask) == mask: memories.append(self._radio.get_memory(i)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): number = (memory.number - 1) / 8 mask = 1 << ((memory.number - 1) & 7) m = self._radio._memobj.banks[bank.index].memory[number] if m.get_bits(mask) == mask: banks.append(bank) return banks @directory.register class FT60Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-60""" BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "FT-60" _model = "AH017" _memsize = 28617 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [MONI] switch while turning the radio on. 4. Rotate the DIAL job to select "F8 CLONE". 5. Press the [F/W] key momentarily. 6. After clicking OK, hold the [PTT] switch for one second to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [MONI] switch while turning the radio on. 4. Rotate the DIAL job to select "F8 CLONE". 5. Press the [F/W] key momentarily. 6. Press the [MONI] switch ("--RX--" will appear on the LCD).""")) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 1000) rf.valid_duplexes = DUPLEX[1:] rf.valid_tmodes = TMODES rf.valid_power_levels = POWER_LEVELS rf.valid_tuning_steps = STEPS rf.valid_skips = SKIPS rf.valid_special_chans = SPECIALS 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 = True rf.has_settings = True rf.has_dtcs_polarity = False return rf def get_bank_model(self): return FT60BankModel(self) 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() self.check_checksums() 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_settings(self): _settings = self._memobj.settings repeater = RadioSettingGroup("repeater", "Repeater Settings") ctcss = RadioSettingGroup("ctcss", "CTCSS/DCS/DTMF Settings") arts = RadioSettingGroup("arts", "ARTS Settings") scan = RadioSettingGroup("scan", "Scan Settings") power = RadioSettingGroup("power", "Power Saver Settings") wires = RadioSettingGroup("wires", "WiRES(tm) Settings") eai = RadioSettingGroup("eai", "EAI/EPCS Settings") switch = RadioSettingGroup("switch", "Switch/Knob Settings") misc = RadioSettingGroup("misc", "Miscellaneous Settings") mbls = RadioSettingGroup("banks", "Memory Bank Link Scan") setmode = RadioSettings(repeater, ctcss, arts, scan, power, wires, eai, switch, misc, mbls) # APO opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24 + 1)] misc.append( RadioSetting( "apo", "Automatic Power Off", RadioSettingValueList(opts, opts[_settings.apo]))) # AR.BEP opts = ["OFF", "INRANG", "ALWAYS"] arts.append( RadioSetting( "ar_bep", "ARTS Beep", RadioSettingValueList(opts, opts[_settings.ar_bep]))) # AR.INT opts = ["25 SEC", "15 SEC"] arts.append( RadioSetting( "ar_int", "ARTS Polling Interval", RadioSettingValueList(opts, opts[_settings.ar_int]))) # ARS opts = ["OFF", "ON"] repeater.append( RadioSetting( "ars", "Automatic Repeater Shift", RadioSettingValueList(opts, opts[_settings.ars]))) # BCLO opts = ["OFF", "ON"] misc.append(RadioSetting( "bclo", "Busy Channel Lock-Out", RadioSettingValueList(opts, opts[_settings.bclo]))) # BEEP opts = ["OFF", "KEY", "KEY+SC"] rs = RadioSetting( "beep_key", "Enable the Beeper", RadioSettingValueList( opts, opts[_settings.beep_key + _settings.beep_sc])) def apply_beep(s, obj): setattr(obj, "beep_key", (int(s.value) & 1) or ((int(s.value) >> 1) & 1)) setattr(obj, "beep_sc", (int(s.value) >> 1) & 1) rs.set_apply_callback(apply_beep, self._memobj.settings) switch.append(rs) # BELL opts = ["OFF", "1T", "3T", "5T", "8T", "CONT"] ctcss.append(RadioSetting("bell", "Bell Repetitions", RadioSettingValueList(opts, opts[ _settings.bell]))) # BSY.LED opts = ["ON", "OFF"] misc.append(RadioSetting("bsy_led", "Busy LED", RadioSettingValueList(opts, opts[ _settings.bsy_led]))) # DCS.NR opts = ["TR/X N", "RX R", "TX R", "T/RX R"] ctcss.append(RadioSetting("dcs_nr", "\"Inverted\" DCS Code Decoding", RadioSettingValueList(opts, opts[ _settings.dcs_nr]))) # DT.DLY opts = ["50 MS", "100 MS", "250 MS", "450 MS", "750 MS", "1000 MS"] ctcss.append(RadioSetting("dt_dly", "DTMF Autodialer Delay Time", RadioSettingValueList(opts, opts[ _settings.dt_dly]))) # DT.SPD opts = ["50 MS", "100 MS"] ctcss.append(RadioSetting("dt_spd", "DTMF Autodialer Sending Speed", RadioSettingValueList(opts, opts[ _settings.dt_spd]))) # DT.WRT for i in range(0, 9): dtmf = self._memobj.dtmf[i] str = "" for c in dtmf.memory: if c == 0xFF: break if c < len(DTMF_CHARS): str += DTMF_CHARS[c] val = RadioSettingValueString(0, 16, str, False) val.set_charset(DTMF_CHARS + list("abcd")) rs = RadioSetting("dtmf_%i" % i, "DTMF Autodialer Memory %i" % (i + 1), val) def apply_dtmf(s, obj): str = s.value.get_value().upper().rstrip() val = [DTMF_CHARS.index(x) for x in str] for x in range(len(val), 16): val.append(0xFF) obj.memory = val rs.set_apply_callback(apply_dtmf, dtmf) ctcss.append(rs) # EDG.BEP opts = ["OFF", "ON"] misc.append(RadioSetting("edg_bep", "Band Edge Beeper", RadioSettingValueList(opts, opts[ _settings.edg_bep]))) # I.NET opts = ["OFF", "COD", "MEM"] rs = RadioSetting("inet", "Internet Link Connection", RadioSettingValueList( opts, opts[_settings.inet - 1])) def apply_inet(s, obj): setattr(obj, s.get_name(), int(s.value) + 1) rs.set_apply_callback(apply_inet, self._memobj.settings) wires.append(rs) # INT.CD opts = ["CODE 0", "CODE 1", "CODE 2", "CODE 3", "CODE 4", "CODE 5", "CODE 6", "CODE 7", "CODE 8", "CODE 9", "CODE A", "CODE B", "CODE C", "CODE D", "CODE E", "CODE F"] wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)", RadioSettingValueList(opts, opts[ _settings.int_cd]))) # INT.MR opts = ["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"] wires.append(RadioSetting( "int_mr", "Access Number (DTMF) for Non-WiRES(TM)", RadioSettingValueList(opts, opts[_settings.int_mr]))) # LAMP opts = ["KEY", "5SEC", "TOGGLE"] switch.append(RadioSetting("lamp", "Lamp Mode", RadioSettingValueList(opts, opts[ _settings.lamp]))) # LOCK opts = ["LK KEY", "LKDIAL", "LK K+D", "LK PTT", "LP P+K", "LK P+D", "LK ALL"] rs = RadioSetting("lock", "Control Locking", RadioSettingValueList( opts, opts[_settings.lock - 1])) def apply_lock(s, obj): setattr(obj, s.get_name(), int(s.value) + 1) rs.set_apply_callback(apply_lock, self._memobj.settings) switch.append(rs) # M/T-CL opts = ["MONI", "T-CALL"] switch.append(RadioSetting("mt_cl", "MONI Switch Function", RadioSettingValueList(opts, opts[ _settings.mt_cl]))) # PAG.ABK opts = ["OFF", "ON"] eai.append(RadioSetting("pag_abk", "Paging Answer Back", RadioSettingValueList(opts, opts[ _settings.pag_abk]))) # RESUME opts = ["TIME", "HOLD", "BUSY"] scan.append(RadioSetting("resume", "Scan Resume Mode", RadioSettingValueList(opts, opts[ _settings.resume]))) # REV/HM opts = ["REV", "HOME"] switch.append(RadioSetting("rev_hm", "HM/RV Key Function", RadioSettingValueList(opts, opts[ _settings.rev_hm]))) # RF.SQL opts = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6", "S-7", "S-8", "S-FULL"] misc.append(RadioSetting("rf_sql", "RF Squelch Threshold", RadioSettingValueList(opts, opts[ _settings.rf_sql]))) # PRI.RVT opts = ["OFF", "ON"] scan.append(RadioSetting("pri_rvt", "Priority Revert", RadioSettingValueList(opts, opts[ _settings.pri_rvt]))) # RXSAVE opts = ["OFF", "200 MS", "300 MS", "500 MS", "1 S", "2 S"] power.append(RadioSetting( "rxsave", "Receive Mode Batery Savery Interval", RadioSettingValueList(opts, opts[_settings.rxsave]))) # S.SRCH opts = ["SINGLE", "CONT"] misc.append(RadioSetting("ssrch", "Smart Search Sweep Mode", RadioSettingValueList(opts, opts[ _settings.ssrch]))) # SCN.MD opts = ["MEM", "ONLY"] scan.append(RadioSetting( "scn_md", "Memory Scan Channel Selection Mode", RadioSettingValueList(opts, opts[_settings.scn_md]))) # SCN.LMP opts = ["OFF", "ON"] scan.append(RadioSetting("scn_lmp", "Scan Lamp", RadioSettingValueList(opts, opts[ _settings.scn_lmp]))) # TOT opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30 + 1)] misc.append(RadioSetting("tot", "Timeout Timer", RadioSettingValueList(opts, opts[ _settings.tot]))) # TX.LED opts = ["ON", "OFF"] misc.append(RadioSetting("tx_led", "TX LED", RadioSettingValueList(opts, opts[ _settings.tx_led]))) # TXSAVE opts = ["OFF", "ON"] power.append(RadioSetting("txsave", "Transmitter Battery Saver", RadioSettingValueList(opts, opts[ _settings.txsave]))) # VFO.BND opts = ["BAND", "ALL"] misc.append(RadioSetting("vfo_bnd", "VFO Band Edge Limiting", RadioSettingValueList(opts, opts[ _settings.vfo_bnd]))) # WX.ALT opts = ["OFF", "ON"] scan.append(RadioSetting("wx_alt", "Weather Alert Scan", RadioSettingValueList(opts, opts[ _settings.wx_alt]))) # MBS for i in range(0, 10): opts = ["OFF", "ON"] mbs = (self._memobj.mbs >> i) & 1 rs = RadioSetting("mbs%i" % i, "Bank %s Scan" % (i + 1), RadioSettingValueList(opts, opts[mbs])) def apply_mbs(s, index): if int(s.value): self._memobj.mbs |= (1 << index) else: self._memobj.mbs &= ~(1 << index) rs.set_apply_callback(apply_mbs, i) mbls.append(rs) return setmode def set_settings(self, uisettings): _settings = self._memobj.settings for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: name = element.get_name() value = element.value if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: obj = getattr(_settings, name) setattr(_settings, name, value) LOG.debug("Setting %s: %s" % (name, value)) except Exception, e: LOG.debug(element.get_name()) raise def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) + \ repr(self._memobj.flags[(number - 1) / 4]) + \ repr(self._memobj.names[number - 1]) def get_memory(self, number): mem = chirp_common.Memory() if isinstance(number, str): # pms channel mem.number = 1001 + SPECIALS.index(number) mem.extd_number = number mem.immutable = ["number", "extd_number", "name", "skip"] _mem = self._memobj.pms[mem.number - 1001] _nam = _skp = None elif number > 1000: # pms channel mem.number = number mem.extd_number = SPECIALS[number - 1001] mem.immutable = ["number", "extd_number", "name", "skip"] _mem = self._memobj.pms[mem.number - 1001] _nam = _skp = None else: mem.number = number _mem = self._memobj.memory[mem.number - 1] _nam = self._memobj.names[mem.number - 1] _skp = self._memobj.flags[(mem.number - 1) / 4] 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": if int(_mem.tx_freq) == 0: mem.duplex = "off" else: 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] if _skp is not None: skip = _skp["skip%i" % ((mem.number - 1) % 4)] mem.skip = SKIPS[skip] if _nam is not None: if _nam.use_name and _nam.valid: mem.name = _decode_name(_nam.name).rstrip() return mem def set_memory(self, mem): if mem.number > 1000: # pms channel _mem = self._memobj.pms[mem.number - 1001] _nam = _skp = None else: _mem = self._memobj.memory[mem.number - 1] _nam = self._memobj.names[mem.number - 1] _skp = self._memobj.flags[(mem.number - 1) / 4] assert(_mem) if mem.empty: _mem.used = False return if not _mem.used: _mem.set_raw("\x00" * 16) _mem.used = 1 _mem.freq, flags = _encode_freq(mem.freq) _mem.freq[0].set_bits(flags) if mem.duplex == "split": _mem.tx_freq, flags = _encode_freq(mem.offset) _mem.tx_freq[0].set_bits(flags) _mem.offset = 0 elif mem.duplex == "off": _mem.tx_freq = 0 _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) if _skp is not None: _skp["skip%i" % ((mem.number - 1) % 4)] = SKIPS.index(mem.skip) if _nam is not None: _nam.name = _encode_name(mem.name) _nam.use_name = mem.name.strip() and True or False _nam.valid = _nam.use_name chirp-daily-20170714/chirp/drivers/yaesu_clone.py0000644000016101777760000001704112537474005023040 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 import os import logging from textwrap import dedent from chirp import chirp_common, util, memmap, errors LOG = logging.getLogger(__name__) CMD_ACK = 0x06 def _safe_read(pipe, count): buf = "" first = True for _i in range(0, 60): buf += pipe.read(count - len(buf)) # LOG.debug("safe_read: %i/%i\n" % (len(buf), count)) if buf: if first and buf[0] == chr(CMD_ACK): # LOG.debug("Chewed an ack") buf = buf[1:] # Chew an echo'd ack if using a 2-pin cable first = False if len(buf) == count: break LOG.debug(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 # LOG.debug("Chewed an ack") status = chirp_common.Status() status.msg = "Cloning from radio" status.max = count status.cur = len(data) status_fn(status) LOG.debug("Read %i/%i" % (len(data), count)) return data def __clone_in(radio): pipe = radio.pipe status = chirp_common.Status() status.msg = "Cloning from radio" status.max = radio.get_memsize() 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") if radio.status_fn: status.cur = len(data) radio.status_fn(status) data += chunk if len(data) != radio.get_memsize(): raise errors.RadioError("Received incomplete image from radio") LOG.debug("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) LOG.debug("@_chunk_write, count: %i, blocksize: %i" % (count, block)) 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): LOG.debug("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 LOG.debug("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" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect data cable. 3. Prepare radio for clone. 4. After clicking OK, press the key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect data cable. 3. Prepare radio for clone. 4. Press the key to receive the image.""")) return rp 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) LOG.debug("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_mappings(mem): bm.remove_memory_from_mapping(mem, bank) chirp-daily-20170714/chirp/drivers/vx7.py0000644000016101777760000002543413067650002021253 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.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from textwrap import dedent import logging LOG = logging.getLogger(__name__) 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 = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 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_mappings(self): return 9 def get_mappings(self): banks = [] for i in range(0, self.get_num_mappings()): bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1)) bank.index = i banks.append(bank) return banks def add_memory_to_mapping(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_mapping(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_mapping_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_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in [x.number for x in self.get_mapping_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 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [MON-F] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [BAND] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [MON-F] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [V/M] key ("CLONE WAIT" will appear on the LCD).""")) return rp 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: LOG.error("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-daily-20170714/chirp/drivers/ap510.py0000644000016101777760000007733112712567601021370 0ustar jenkinsnogroup00000000000000# Copyright 2014 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 struct from time import sleep import logging from chirp import chirp_common, directory, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ InvalidValueError, RadioSettings LOG = logging.getLogger(__name__) def chunks(s, t): """ Yield chunks of s in sizes defined in t.""" i = 0 for n in t: yield s[i:i+n] i += n def encode_base100(v): return (v / 100 << 8) + (v % 100) def decode_base100(u16): return 100 * (u16 >> 8 & 0xff) + (u16 & 0xff) def drain(pipe): """Chew up any data waiting on @pipe""" for x in xrange(3): buf = pipe.read(4096) if not buf: return raise errors.RadioError('Your pipes are clogged.') def enter_setup(pipe): """Put AP510 in configuration mode.""" for x in xrange(30): if x % 2: pipe.write("@SETUP") else: pipe.write("\r\nSETUP\r\n") s = pipe.read(64) if s and "\r\nSETUP" in s: return True elif s and "SETUP" in s: return False raise errors.RadioError('Radio did not respond.') def download(radio): status = chirp_common.Status() drain(radio.pipe) status.msg = " Power on AP510 now, waiting " radio.status_fn(status) new = enter_setup(radio.pipe) status.cur = 1 status.max = 5 status.msg = "Downloading" radio.status_fn(status) if new: radio.pipe.write("\r\nDISP\r\n") else: radio.pipe.write("@DISP") buf = "" for status.cur in xrange(status.cur, status.max): buf += radio.pipe.read(1024) if buf.endswith("\r\n"): status.cur = status.max radio.status_fn(status) break radio.status_fn(status) else: raise errors.RadioError("Incomplete data received.") LOG.debug("%04i P7H", self._memobj[self.ATTR_MAP['smartbeacon']])) )) def set_smartbeacon(self, d): self._memobj[self.ATTR_MAP['smartbeacon']] = \ struct.pack(">7H", encode_base100(d['lowspeed']), encode_base100(d['slowrate']), encode_base100(d['highspeed']), encode_base100(d['fastrate']), encode_base100(d['turnslope']), encode_base100(d['turnangle']), encode_base100(d['turntime']), ) class AP510Memory20141215(AP510Memory): """Compatible with firmware version 20141215""" ATTR_MAP = dict(AP510Memory.ATTR_MAP.items() + { 'tx_volume': '21', # 1-6 'rx_volume': '22', # 1-9 'tx_power': '23', # 1: 1 watt, 0: 0.5 watt 'tx_serial_ui_out': '24', 'path1': '25', 'path2': '26', 'path3': '27', # like "WIDE1 1" else "0" 'multiple': '28', 'auto_on': '29', }.items()) def get_multiple(self): return dict(zip( ( 'mice_message', # conveniently matches APRS spec Mic-E messages 'voltage', # voltage in comment 'temperature', # temperature in comment 'tfx', # not sure what the TF/X toggle does 'squelch', # squelch level 0-8 (0 = disabled) 'blueled', # 0: squelch LED on GPS lock # 1: light LED on GPS lock 'telemetry', # 1: enable 'telemetry_every', # two-digit int 'timeslot_enable', # 1: enable Is this implemented in firmware? 'timeslot', # int 00-59 'dcd', # 0: Blue LED displays squelch, # 1: Blue LED displays software DCD 'tf_card' # 0: KML, 1: WPL ), map(int, chunks(self._memobj[self.ATTR_MAP['multiple']], (1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1))) )) def set_multiple(self, d): self._memobj[self.ATTR_MAP['multiple']] = "%(mice_message)1d" \ "%(voltage)1d" \ "%(temperature)1d" \ "%(tfx)1d" \ "%(squelch)1d" \ "%(blueled)1d" \ "%(telemetry)1d" \ "%(telemetry_every)02d" \ "%(timeslot_enable)1d" \ "%(timeslot)02d" \ "%(dcd)1d" \ "%(tf_card)1d" % d def get_smartbeacon(self): # raw: 18=0100300060010240028005 # chunks: 18=010 0300 060 010 240 028 005 return dict(zip(( 'lowspeed', 'slowrate', 'highspeed', 'fastrate', 'turnslope', 'turnangle', 'turntime', ), map(int, chunks( self._memobj[self.ATTR_MAP['smartbeacon']], (3, 4, 3, 3, 3, 3, 3))) )) def set_smartbeacon(self, d): self._memobj[self.ATTR_MAP['smartbeacon']] = "%(lowspeed)03d" \ "%(slowrate)04d" \ "%(highspeed)03d" \ "%(fastrate)03d" \ "%(turnslope)03d" \ "%(turnangle)03d" \ "%(turntime)03d" % d PTT_DELAY = ['60 ms', '120 ms', '180 ms', '300 ms', '480 ms', '600 ms', '1000 ms'] OUTPUT = ['KISS', 'Waypoint out', 'UI out'] PATH = [ '(None)', 'WIDE1-1', 'WIDE1-1,WIDE2-1', 'WIDE1-1,WIDE2-2', 'TEMP1-1', 'TEMP1-1,WIDE 2-1', 'WIDE2-1', ] TABLE = "/\#&0>AW^_acnsuvz" SYMBOL = "".join(map(chr, range(ord("!"), ord("~")+1))) BEACON = ['manual', 'auto', 'auto + manual', 'smart', 'smart + manual'] ALIAS = ['WIDE1-N', 'WIDE2-N', 'WIDE1-N + WIDE2-N'] CHARSET = "".join(map(chr, range(0, 256))) MICE_MESSAGE = ['Emergency', 'Priority', 'Special', 'Committed', 'Returning', 'In Service', 'En Route', 'Off Duty'] TF_CARD = ['WPL', 'KML'] POWER_LEVELS = [chirp_common.PowerLevel("0.5 watt", watts=0.50), chirp_common.PowerLevel("1 watt", watts=1.00)] RP_IMMUTABLE = ["number", "skip", "bank", "extd_number", "name", "rtone", "ctone", "dtcs", "tmode", "dtcs_polarity", "skip", "duplex", "offset", "mode", "tuning_step", "bank_index"] class AP510Radio(chirp_common.CloneModeRadio): """Sainsonic AP510""" BAUD_RATE = 9600 VENDOR = "Sainsonic" MODEL = "AP510" _model = "AVRT5" mem_upper_limit = 0 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.valid_modes = ["FM"] rf.valid_tmodes = [""] rf.valid_characters = "" rf.valid_duplexes = [""] rf.valid_name_length = 0 rf.valid_power_levels = POWER_LEVELS 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.valid_bands = [(136000000, 174000000)] rf.memory_bounds = (0, 0) return rf def sync_in(self): try: data = download(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) # _mmap isn't a Chirp MemoryMap, but since AP510Memory implements # get_packed(), the standard Chirp save feature works. if data.startswith('\r\n00=%s 20141215' % self._model): self._mmap = AP510Memory20141215(data) else: self._mmap = AP510Memory(data) def process_mmap(self): self._mmap.process_data() def sync_out(self): try: upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def load_mmap(self, filename): """Load the radio's memory map from @filename""" mapfile = file(filename, "rb") data = mapfile.read() if data.startswith('\r\n00=%s 20141215' % self._model): self._mmap = AP510Memory20141215(data) else: self._mmap = AP510Memory(data) mapfile.close() def get_raw_memory(self, number): return self._mmap.get_packed() def get_memory(self, number): if number != 0: raise errors.InvalidMemoryLocation("AP510 has only one slot") mem = chirp_common.Memory() mem.number = 0 mem.freq = float(self._mmap.freq) * 1000000 mem.name = "TX/RX" mem.mode = "FM" mem.offset = 0.0 try: mem.power = POWER_LEVELS[int(self._mmap.tx_power)] except NotImplementedError: mem.power = POWER_LEVELS[1] mem.immutable = RP_IMMUTABLE return mem def set_memory(self, mem): if mem.number != 0: raise errors.InvalidMemoryLocation("AP510 has only one slot") self._mmap.freq = "%8.4f" % (mem.freq / 1000000.0) if mem.power: try: self._mmap.tx_power = str(POWER_LEVELS.index(mem.power)) except NotImplementedError: pass def get_settings(self): china = RadioSettingGroup("china", "China Map Fix") smartbeacon = RadioSettingGroup("smartbeacon", "Smartbeacon") aprs = RadioSettingGroup("aprs", "APRS", china, smartbeacon) digipeat = RadioSettingGroup("digipeat", "Digipeat") system = RadioSettingGroup("system", "System") settings = RadioSettings(aprs, digipeat, system) aprs.append(RadioSetting("callsign", "Callsign", RadioSettingValueString(0, 6, self._mmap.callsign[:6]))) aprs.append(RadioSetting("ssid", "SSID", RadioSettingValueInteger( 0, 15, ord(self._mmap.callsign[6]) - 0x30))) pttdelay = PTT_DELAY[int(self._mmap.pttdelay) - 1] aprs.append(RadioSetting("pttdelay", "PTT Delay", RadioSettingValueList(PTT_DELAY, pttdelay))) output = OUTPUT[int(self._mmap.output) - 1] aprs.append(RadioSetting("output", "Output", RadioSettingValueList(OUTPUT, output))) aprs.append(RadioSetting("mice", "Mic-E", RadioSettingValueBoolean(strbool(self._mmap.mice)))) try: mice_msg = MICE_MESSAGE[int(self._mmap.multiple['mice_message'])] aprs.append(RadioSetting("mice_message", "Mic-E Message", RadioSettingValueList(MICE_MESSAGE, mice_msg))) except NotImplementedError: pass try: aprs.append(RadioSetting("path1", "Path 1", RadioSettingValueString(0, 6, self._mmap.path1[:6], autopad=True, charset=CHARSET))) ssid1 = ord(self._mmap.path1[6]) - 0x30 aprs.append(RadioSetting("ssid1", "SSID 1", RadioSettingValueInteger(0, 7, ssid1))) aprs.append(RadioSetting("path2", "Path 2", RadioSettingValueString(0, 6, self._mmap.path2[:6], autopad=True, charset=CHARSET))) ssid2 = ord(self._mmap.path2[6]) - 0x30 aprs.append(RadioSetting("ssid2", "SSID 2", RadioSettingValueInteger(0, 7, ssid2))) aprs.append(RadioSetting("path3", "Path 3", RadioSettingValueString(0, 6, self._mmap.path3[:6], autopad=True, charset=CHARSET))) ssid3 = ord(self._mmap.path3[6]) - 0x30 aprs.append(RadioSetting("ssid3", "SSID 3", RadioSettingValueInteger(0, 7, ssid3))) except NotImplementedError: aprs.append(RadioSetting("path", "Path", RadioSettingValueList(PATH, PATH[int(self._mmap.path)]))) aprs.append(RadioSetting("table", "Table or Overlay", RadioSettingValueList(TABLE, self._mmap.symbol[1]))) aprs.append(RadioSetting("symbol", "Symbol", RadioSettingValueList(SYMBOL, self._mmap.symbol[0]))) aprs.append(RadioSetting("beacon", "Beacon Mode", RadioSettingValueList(BEACON, BEACON[int(self._mmap.beacon) - 1]))) aprs.append(RadioSetting("rate", "Beacon Rate (seconds)", RadioSettingValueInteger(10, 9999, self._mmap.rate))) aprs.append(RadioSetting("comment", "Comment", RadioSettingValueString(0, 34, self._mmap.comment, autopad=False, charset=CHARSET))) try: voltage = self._mmap.multiple['voltage'] aprs.append(RadioSetting("voltage", "Voltage in comment", RadioSettingValueBoolean(voltage))) temperature = self._mmap.multiple['temperature'] aprs.append(RadioSetting("temperature", "Temperature in comment", RadioSettingValueBoolean(temperature))) except NotImplementedError: pass aprs.append(RadioSetting("status", "Status", RadioSettingValueString( 0, 34, self._mmap.status, autopad=False, charset=CHARSET))) try: telemetry = self._mmap.multiple['telemetry'] aprs.append(RadioSetting("telemetry", "Telemetry", RadioSettingValueBoolean(telemetry))) telemetry_every = self._mmap.multiple['telemetry_every'] aprs.append(RadioSetting("telemetry_every", "Telemetry every", RadioSettingValueInteger(1, 99, telemetry_every))) timeslot_enable = self._mmap.multiple['telemetry'] aprs.append(RadioSetting("timeslot_enable", "Timeslot", RadioSettingValueBoolean(timeslot_enable))) timeslot = self._mmap.multiple['timeslot'] aprs.append(RadioSetting("timeslot", "Timeslot (second of minute)", RadioSettingValueInteger(0, 59, timeslot))) except NotImplementedError: pass fields = [ ("chinamapfix", "China map fix", RadioSettingValueBoolean(strbool(self._mmap.chinamapfix[0]))), ("chinalat", "Lat", RadioSettingValueInteger( -45, 45, ord(self._mmap.chinamapfix[2]) - 80)), ("chinalon", "Lon", RadioSettingValueInteger( -45, 45, ord(self._mmap.chinamapfix[1]) - 80)), ] for field in fields: china.append(RadioSetting(*field)) try: # Sometimes when digipeat is disabled, alias is 0xFF alias = ALIAS[int(self._mmap.digipeat[1]) - 1] except ValueError: alias = ALIAS[0] fields = [ ("digipeat", "Digipeat", RadioSettingValueBoolean(strbool(self._mmap.digipeat[0]))), ("alias", "Digipeat Alias", RadioSettingValueList( ALIAS, alias)), ("virtualgps", "Static Position", RadioSettingValueBoolean(strbool(self._mmap.virtualgps[0]))), ("btext", "Static Position BTEXT", RadioSettingValueString( 0, 27, self._mmap.virtualgps[1:], autopad=False, charset=CHARSET)), ] for field in fields: digipeat.append(RadioSetting(*field)) sb = self._mmap.smartbeacon fields = [ ("lowspeed", "Low Speed"), ("highspeed", "High Speed"), ("slowrate", "Slow Rate (seconds)"), ("fastrate", "Fast Rate (seconds)"), ("turnslope", "Turn Slope"), ("turnangle", "Turn Angle"), ("turntime", "Turn Time (seconds)"), ] for field in fields: smartbeacon.append(RadioSetting( field[0], field[1], RadioSettingValueInteger(0, 9999, sb[field[0]]) )) system.append(RadioSetting("version", "Version (read-only)", RadioSettingValueString(0, 14, self._mmap.version))) system.append(RadioSetting("autooff", "Auto off (after 90 minutes)", RadioSettingValueBoolean(strbool(self._mmap.autooff)))) system.append(RadioSetting("beep", "Beep on transmit", RadioSettingValueBoolean(strbool(self._mmap.beep)))) system.append(RadioSetting("highaltitude", "High Altitude", RadioSettingValueBoolean( strbool(self._mmap.highaltitude)))) system.append(RadioSetting("busywait", "Wait for clear channel before transmit", RadioSettingValueBoolean( strbool(self._mmap.busywait)))) try: system.append(RadioSetting("tx_volume", "Transmit volume", RadioSettingValueList( map(str, range(1, 7)), self._mmap.tx_volume))) system.append(RadioSetting("rx_volume", "Receive volume", RadioSettingValueList( map(str, range(1, 10)), self._mmap.rx_volume))) system.append(RadioSetting("squelch", "Squelch", RadioSettingValueList( map(str, range(0, 9)), str(self._mmap.multiple['squelch'])))) system.append(RadioSetting("tx_serial_ui_out", "Tx serial UI out", RadioSettingValueBoolean( strbool(self._mmap.tx_serial_ui_out)))) system.append(RadioSetting("auto_on", "Auto-on with 5V input", RadioSettingValueBoolean( strbool(self._mmap.auto_on[0])))) system.append(RadioSetting( "auto_on_delay", "Auto-off delay after 5V lost (seconds)", RadioSettingValueInteger( 0, 9999, int(self._mmap.auto_on[1:])) )) system.append(RadioSetting("tfx", "TF/X", RadioSettingValueBoolean( self._mmap.multiple['tfx']))) system.append(RadioSetting("blueled", "Light blue LED on GPS lock", RadioSettingValueBoolean( self._mmap.multiple['blueled']))) system.append(RadioSetting("dcd", "Blue LED shows software DCD", RadioSettingValueBoolean( self._mmap.multiple['dcd']))) system.append(RadioSetting("tf_card", "TF card format", RadioSettingValueList( TF_CARD, TF_CARD[int(self._mmap.multiple['tf_card'])]))) except NotImplementedError: pass return settings def set_settings(self, settings): for setting in settings: if not isinstance(setting, RadioSetting): self.set_settings(setting) continue if not setting.changed(): continue try: name = setting.get_name() if name == "callsign": self.set_callsign(callsign=setting.value) elif name == "ssid": self.set_callsign(ssid=int(setting.value)) elif name == "pttdelay": self._mmap.pttdelay = PTT_DELAY.index( str(setting.value)) + 1 elif name == "output": self._mmap.output = OUTPUT.index(str(setting.value)) + 1 elif name in ('mice', 'autooff', 'beep', 'highaltitude', 'busywait', 'tx_serial_ui_out'): setattr(self._mmap, name, boolstr(setting.value)) elif name == "mice_message": multiple = self._mmap.multiple multiple['mice_message'] = MICE_MESSAGE.index( str(setting.value)) self._mmap.multiple = multiple elif name == "path": self._mmap.path = PATH.index(str(setting.value)) elif name == "path1": self._mmap.path1 = "%s%s" % ( setting.value, self._mmap.path1[6]) elif name == "ssid1": self._mmap.path1 = "%s%s" % ( self._mmap.path1[:6], setting.value) elif name == "path2": self._mmap.path2 = "%s%s" % ( setting.value, self._mmap.path2[6]) elif name == "ssid2": self._mmap.path2 = "%s%s" % ( self._mmap.path2[:6], setting.value) elif name == "path3": self._mmap.path3 = "%s%s" % ( setting.value, self._mmap.path3[6]) elif name == "ssid3": self._mmap.path3 = "%s%s" % ( self._mmap.path3[:6], setting.value) elif name == "table": self.set_symbol(table=setting.value) elif name == "symbol": self.set_symbol(symbol=setting.value) elif name == "beacon": self._mmap.beacon = BEACON.index(str(setting.value)) + 1 elif name == "rate": self._mmap.rate = "%04d" % setting.value elif name == "comment": self._mmap.comment = str(setting.value) elif name == "voltage": multiple = self._mmap.multiple multiple['voltage'] = int(setting.value) self._mmap.multiple = multiple elif name == "temperature": multiple = self._mmap.multiple multiple['temperature'] = int(setting.value) self._mmap.multiple = multiple elif name == "status": self._mmap.status = str(setting.value) elif name in ("telemetry", "telemetry_every", "timeslot_enable", "timeslot", "tfx", "blueled", "dcd"): multiple = self._mmap.multiple multiple[name] = int(setting.value) self._mmap.multiple = multiple elif name == "chinamapfix": self.set_chinamapfix(enable=setting.value) elif name == "chinalat": self.set_chinamapfix(lat=int(setting.value)) elif name == "chinalon": self.set_chinamapfix(lon=int(setting.value)) elif name == "digipeat": self.set_digipeat(enable=setting.value) elif name == "alias": self.set_digipeat( alias=str(ALIAS.index(str(setting.value)) + 1)) elif name == "virtualgps": self.set_virtualgps(enable=setting.value) elif name == "btext": self.set_virtualgps(btext=str(setting.value)) elif name == "lowspeed": self.set_smartbeacon(lowspeed=int(setting.value)) elif name == "highspeed": self.set_smartbeacon(highspeed=int(setting.value)) elif name == "slowrate": self.set_smartbeacon(slowrate=int(setting.value)) elif name == "fastrate": self.set_smartbeacon(fastrate=int(setting.value)) elif name == "turnslope": self.set_smartbeacon(turnslope=int(setting.value)) elif name == "turnangle": self.set_smartbeacon(turnangle=int(setting.value)) elif name == "turntime": self.set_smartbeacon(turntime=int(setting.value)) elif name in ("tx_volume", "rx_volume", "squelch"): setattr(self._mmap, name, "%1d" % setting.value) elif name == "auto_on": self._mmap.auto_on = "%s%05d" % ( bool(setting.value) and '1' or 'i', int(self._mmap.auto_on[1:])) elif name == "auto_on_delay": self._mmap.auto_on = "%s%05d" % ( self._mmap.auto_on[0], setting.value) elif name == "tf_card": multiple = self._mmap.multiple multiple['tf_card'] = TF_CARD.index(str(setting.value)) self._mmap.multiple = multiple except: LOG.debug(setting.get_name()) raise def set_callsign(self, callsign=None, ssid=None): if callsign is None: callsign = self._mmap.callsign[:6] if ssid is None: ssid = ord(self._mmap.callsign[6]) - 0x30 self._mmap.callsign = str(callsign) + chr(ssid + 0x30) def set_symbol(self, table=None, symbol=None): if table is None: table = self._mmap.symbol[1] if symbol is None: symbol = self._mmap.symbol[0] self._mmap.symbol = str(symbol) + str(table) def set_chinamapfix(self, enable=None, lat=None, lon=None): if enable is None: enable = strbool(self._mmap.chinamapfix[0]) if lat is None: lat = ord(self._mmap.chinamapfix[2]) - 80 if lon is None: lon = ord(self._mmap.chinamapfix[1]) - 80 self._mmapchinamapfix = boolstr(enable) + chr(lon + 80) + chr(lat + 80) def set_digipeat(self, enable=None, alias=None): if enable is None: enable = strbool(self._mmap.digipeat[0]) if alias is None: alias = self._mmap.digipeat[1] self._mmap.digipeat = boolstr(enable) + alias def set_virtualgps(self, enable=None, btext=None): if enable is None: enable = strbool(self._mmap.virtualgps[0]) if btext is None: btext = self._mmap.virtualgps[1:] self._mmap.virtualgps = boolstr(enable) + btext def set_smartbeacon(self, **kwargs): sb = self._mmap.smartbeacon sb.update(kwargs) if sb['lowspeed'] > sb['highspeed']: raise InvalidValueError("Low speed must be less than high speed") if sb['slowrate'] < sb['fastrate']: raise InvalidValueError("Slow rate must be greater than fast rate") self._mmap.smartbeacon = sb @classmethod def match_model(cls, filedata, filename): return filedata.startswith('\r\n00=' + cls._model) chirp-daily-20170714/chirp/drivers/repeaterbook.py0000644000016101777760000000233413041604710023200 0ustar jenkinsnogroup00000000000000# Copyright 2016 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 from chirp.drivers import generic_csv class RBRadio(generic_csv.CSVRadio, chirp_common.NetworkSourceRadio): VENDOR = "RepeaterBook" MODEL = "" def _clean_comment(self, headers, line, mem): "Converts iso-8859-1 encoded comments to unicode for pyGTK." mem.comment = unicode(mem.comment, 'iso-8859-1') return mem def _clean_name(self, headers, line, mem): "Converts iso-8859-1 encoded names to unicode for pyGTK." mem.name = unicode(mem.name, 'iso-8859-1') return mem chirp-daily-20170714/chirp/drivers/baofeng_common.py0000644000016101777760000004612613006307374023505 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 . """common functions for Baofeng (or similar) handheld radios""" import time import struct import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList LOG = logging.getLogger(__name__) STIMEOUT = 1.5 def _clean_buffer(radio): radio.pipe.timeout = 0.005 junk = radio.pipe.read(256) radio.pipe.timeout = STIMEOUT if junk: LOG.debug("Got %i bytes of junk before starting" % len(junk)) def _rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: msg = "Generic error reading data from radio; check your cable." raise errors.RadioError(msg) if len(data) != amount: msg = "Error reading data from radio: not the amount of data we want." raise errors.RadioError(msg) return data def _rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) except: raise errors.RadioError("Error sending data to radio") def _make_frame(cmd, addr, length, data=""): """Pack the info in the headder format""" frame = struct.pack(">BHB", ord(cmd), addr, length) # add the data if set if len(data) != 0: frame += data # return the data return frame def _recv(radio, addr, length): """Get data from the radio """ # read 4 bytes of header hdr = _rawrecv(radio, 4) # read data data = _rawrecv(radio, length) # DEBUG LOG.info("Response:") LOG.debug(util.hexprint(hdr + data)) c, a, l = struct.unpack(">BHB", hdr) if a != addr or l != length or c != ord("X"): LOG.error("Invalid answer for block 0x%04x:" % addr) LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l)) raise errors.RadioError("Unknown response from the radio") return data def _get_radio_firmware_version(radio): msg = struct.pack(">BHB", ord("S"), radio._fw_ver_start, radio._recv_block_size) radio.pipe.write(msg) block = _recv(radio, radio._fw_ver_start, radio._recv_block_size) _rawsend(radio, "\x06") time.sleep(0.05) version = block[0:16] return version def _image_ident_from_data(data, start, stop): return data[start:stop] def _get_image_firmware_version(radio): return _image_ident_from_data(radio.get_mmap(), radio._fw_ver_start, radio._fw_ver_start + 0x10) def _do_ident(radio, magic): """Put the radio in PROGRAM mode""" # set the serial discipline radio.pipe.baudrate = 9600 radio.pipe.parity = "N" radio.pipe.timeout = STIMEOUT # flush input buffer _clean_buffer(radio) # send request to enter program mode _rawsend(radio, magic) ack = _rawrecv(radio, 1) if ack != "\x06": if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio did not respond") _rawsend(radio, "\x02") # Ok, get the response ident = _rawrecv(radio, radio._magic_response_length) # check if response is OK if not ident.startswith("\xaa") or not ident.endswith("\xdd"): # bad response msg = "Unexpected response, got this:" msg += util.hexprint(ident) LOG.debug(msg) raise errors.RadioError("Unexpected response from radio.") # DEBUG LOG.info("Valid response, got this:") LOG.debug(util.hexprint(ident)) _rawsend(radio, "\x06") ack = _rawrecv(radio, 1) if ack != "\x06": if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio refused clone") return ident def _ident_radio(radio): for magic in radio._magic: 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 _download(radio): """Get the memory map""" # put radio in program mode ident = _ident_radio(radio) # identify radio radio_ident = _get_radio_firmware_version(radio) LOG.info("Radio firmware version:") LOG.debug(util.hexprint(radio_ident)) if radio_ident == "\xFF" * 16: ident += radio.MODEL.ljust(8) # UI progress status = chirp_common.Status() status.cur = 0 status.max = radio._mem_size / radio._recv_block_size status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in range(0, radio._mem_size, radio._recv_block_size): frame = _make_frame("S", addr, radio._recv_block_size) # DEBUG LOG.info("Request sent:") LOG.debug(util.hexprint(frame)) # sending the read request _rawsend(radio, frame) if radio._ack_block: ack = _rawrecv(radio, 1) if ack != "\x06": raise errors.RadioError( "Radio refused to send block 0x%04x" % addr) # now we read d = _recv(radio, addr, radio._recv_block_size) _rawsend(radio, "\x06") time.sleep(0.05) # aggregate the data data += d # UI Update status.cur = addr / radio._recv_block_size status.msg = "Cloning from radio..." radio.status_fn(status) data += ident return data def _upload(radio): """Upload procedure""" # put radio in program mode _ident_radio(radio) # identify radio radio_ident = _get_radio_firmware_version(radio) LOG.info("Radio firmware version:") LOG.debug(util.hexprint(radio_ident)) # identify image image_ident = _get_image_firmware_version(radio) LOG.info("Image firmware version:") LOG.debug(util.hexprint(image_ident)) if radio_ident != "0xFF" * 16 and image_ident == radio_ident: _ranges = radio._ranges else: _ranges = [(0x0000, 0x0DF0), (0x0E00, 0x1800)] # UI progress status = chirp_common.Status() status.cur = 0 status.max = radio._mem_size / radio._send_block_size status.msg = "Cloning to radio..." radio.status_fn(status) # the fun start here for start, end in _ranges: for addr in range(start, end, radio._send_block_size): # sending the data data = radio.get_mmap()[addr:addr + radio._send_block_size] frame = _make_frame("X", addr, radio._send_block_size, data) _rawsend(radio, frame) time.sleep(0.05) # receiving the response ack = _rawrecv(radio, 1) if ack != "\x06": msg = "Bad ack writing block 0x%04x" % addr raise errors.RadioError(msg) # UI Update status.cur = addr / radio._send_block_size status.msg = "Cloning to radio..." radio.status_fn(status) def _split(rf, f1, f2): """Returns False if the two freqs are in the same band (no split) or True otherwise""" # determine if the two freqs are in the same band for low, high in rf.valid_bands: if f1 >= low and f1 <= high and \ f2 >= low and f2 <= high: # if the two freqs are on the same Band this is not a split return False # if you get here is because the freq pairs are split return True class BaofengCommonHT(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baofeng HT Sytle Radios""" VENDOR = "Baofeng" MODEL = "" IDENT = "" def sync_in(self): """Download from radio""" try: data = _download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = memmap.MemoryMap(data) self.process_mmap() def sync_out(self): """Upload to radio""" try: _upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') def get_features(self): """Get the radio's features""" rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = self.MODES rf.valid_characters = self.VALID_CHARS rf.valid_name_length = self.LENGTH_NAME rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_skips = self.SKIP_VALUES rf.valid_dtcs_codes = self.DTCS_CODES rf.memory_bounds = (0, 127) rf.valid_power_levels = self.POWER_LEVELS rf.valid_bands = self.VALID_BANDS return rf 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): # TX freq not set mem.duplex = "off" mem.offset = 0 else: # TX freq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset != 0: if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): mem.duplex = "split" mem.offset = int(_mem.txfreq) * 10 elif offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 for char in _nam.name: if str(char) == "\xFF": char = " " # The OEM 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 = self.DTCS_CODES[index] else: LOG.warn("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 = self.DTCS_CODES[index] else: LOG.warn("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" levels = self.POWER_LEVELS try: mem.power = levels[_mem.lowpower] except IndexError: LOG.error("Radio reported invalid power level %s (in %s)" % (_mem.power, levels)) mem.power = levels[0] mem.mode = _mem.wide and "FM" or "NFM" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) rs = RadioSetting("pttid", "PTT ID", RadioSettingValueList(self.PTTID_LIST, self.PTTID_LIST[_mem.pttid])) mem.extra.append(rs) rs = RadioSetting("scode", "S-CODE", RadioSettingValueList(self.SCODE_LIST, self.SCODE_LIST[_mem.scode])) 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) _nam.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 _namelength = self.get_features().valid_name_length for i in range(_namelength): 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 = self.DTCS_CODES.index(mem.dtcs) + 1 _mem.rxtone = self.DTCS_CODES.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 = self.DTCS_CODES.index(mem.dtcs) + 1 else: _mem.txtone = 0 if rxmode == "Tone": _mem.rxtone = int(mem.ctone * 10) elif rxmode == "DTCS": _mem.rxtone = self.DTCS_CODES.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" if mem.power: _mem.lowpower = self.POWER_LEVELS.index(mem.power) else: _mem.lowpower = 0 # extra settings if len(mem.extra) > 0: # there are setting, parse for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) else: # there are no extra settings, load defaults _mem.bcl = 0 _mem.pttid = 0 _mem.scode = 0 def set_settings(self, settings): _settings = self._memobj.settings _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): for element in settings: try: val = element.value if self._memobj.fm_presets <= 108.0 * 10 - 650: value = int(val.get_value() * 10 - 650) else: value = int(val.get_value() * 10) LOG.debug("Setting fm_presets = %s" % (value)) self._memobj.fm_presets = value except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/wouxun_common.py0000644000016101777760000000512412476257220023446 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 . """vcommon function for wouxun (or similar) radios""" import struct import os import logging from chirp import util, chirp_common, memmap LOG = logging.getLogger(__name__) 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) LOG.debug(util.hexprint(cmd)) radio.pipe.write(cmd) length = len(cmd) + blocksize resp = radio.pipe.read(length) if len(resp) != (len(cmd) + blocksize): LOG.debug(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) LOG.debug(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-daily-20170714/chirp/drivers/uv5r.py0000644000016101777760000017252713074071603021440 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 import os import logging from chirp import chirp_common, errors, util, directory, memmap from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0008; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unused1:3, isuhf:1, scode:4; u8 unknown1:7, txtoneicon:1; u8 mailicon:3, unknown2:3, lowpower:2; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B08; struct { u8 code[5]; u8 unused[11]; } pttid[15]; #seekto 0x0C88; struct { u8 code222[3]; u8 unused222[2]; u8 code333[3]; u8 unused333[2]; u8 alarmcode[3]; u8 unused119[2]; u8 unknown1; u8 code555[3]; u8 unused555[2]; u8 code666[3]; u8 unused666[2]; u8 code777[3]; u8 unused777[2]; u8 unknown2; u8 code60606[5]; u8 code70707[5]; u8 code[5]; u8 unused1:6, aniid:2; u8 unknown[2]; u8 dtmfon; u8 dtmfoff; } 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 unknown12:6, screv:2; u8 pttid; u8 pttlt; u8 mdfa; u8 mdfb; u8 bcl; u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R // calls it autolk. Since this is a minor difference, it will // be referred to by the wrong name for the UV-6. 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; u8 rogerrx; u8 tdrch; // NOTE: The UV-82HP calls this byte rtone, but the UV-6 // calls it tdrch. Since this is a minor difference, it will // be referred to by the wrong name for the UV-82HP. u8 displayab:1, unknown1:2, fmradio:1, alarm:1, unknown2:1, reset:1, menu:1; u8 unknown1:6, singleptt:1, vfomrlock:1; u8 workmode; u8 keylock; } settings; #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:2, sftd:2, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:4, txpower3:2; } 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:2, sftd:2, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:4, txpower3:2; } vfob; #seekto 0x0F56; u16 fm_presets; #seekto 0x1008; struct { char name[7]; u8 unknown2[9]; } names[128]; #seekto 0x1818; struct { char line1[7]; char line2[7]; } sixpoweron_msg; #seekto 0x%04X; struct { char line1[7]; char line2[7]; } poweron_msg; #seekto 0x1838; struct { char line1[7]; char line2[7]; } firmware_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; struct squelch { u8 sql0; u8 sql1; u8 sql2; u8 sql3; u8 sql4; u8 sql5; u8 sql6; u8 sql7; u8 sql8; u8 sql9; }; #seekto 0x18A8; struct { struct squelch vhf; u8 unknown1[6]; u8 unknown2[16]; struct squelch uhf; } squelch_new; #seekto 0x18E8; struct { struct squelch vhf; u8 unknown[6]; struct squelch uhf; } squelch_old; """ # 0x1EC0 - 0x2000 vhf_220_radio = "\x02" BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"] BASETYPE_F11 = ["USA"] BASETYPE_UV82 = ["US2S2", "B82S", "BF82", "N82-2", "N822"] BASETYPE_BJ55 = ["BJ55"] # needed for for the Baojie UV-55 in bjuv55.py BASETYPE_UV6 = ["BF1", "UV6"] BASETYPE_KT980HP = ["BFP3V3 B"] BASETYPE_F8HP = ["BFP3V3 F", "N5R-3", "N5R3", "F5R3", "BFT"] BASETYPE_UV82HP = ["N82-3", "N823"] BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \ BASETYPE_BJ55 + BASETYPE_UV6 + BASETYPE_KT980HP + \ BASETYPE_F8HP + BASETYPE_UV82HP AB_LIST = ["A", "B"] ALMOD_LIST = ["Site", "Tone", "Code"] BANDWIDTH_LIST = ["Wide", "Narrow"] COLOR_LIST = ["Off", "Blue", "Orange", "Purple"] DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)] DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"] MODE_LIST = ["Channel", "Name", "Frequency"] PONMSG_LIST = ["Full", "Message"] PTTID_LIST = ["Off", "BOT", "EOT", "Both"] PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)] RTONE_LIST = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"] RESUME_LIST = ["TO", "CO", "SE"] ROGERRX_LIST = ["Off"] + AB_LIST RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)] SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"] SCODE_LIST = ["%s" % x for x in range(1, 16)] SHIFTD_LIST = ["Off", "+", "-"] STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)] 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] TDRAB_LIST = ["Off"] + AB_LIST TDRCH_LIST = ["CH%s" % x for x in range(1, 129)] TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)] TXPOWER_LIST = ["High", "Low"] TXPOWER3_LIST = ["High", "Mid", "Low"] VOICE_LIST = ["Off", "English", "Chinese"] VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)] WORKMODE_LIST = ["Frequency", "Channel"] SETTING_LISTS = { "almod": ALMOD_LIST, "aniid": PTTID_LIST, "displayab": AB_LIST, "dtmfst": DTMFST_LIST, "dtmfspeed": DTMFSPEED_LIST, "mdfa": MODE_LIST, "mdfb": MODE_LIST, "ponmsg": PONMSG_LIST, "pttid": PTTID_LIST, "rtone": RTONE_LIST, "rogerrx": ROGERRX_LIST, "rpste": RPSTE_LIST, "rxled": COLOR_LIST, "save": SAVE_LIST, "scode": PTTIDCODE_LIST, "screv": RESUME_LIST, "sftd": SHIFTD_LIST, "stedelay": STEDELAY_LIST, "step": STEP_LIST, "step291": STEP291_LIST, "tdrab": TDRAB_LIST, "tdrch": TDRCH_LIST, "timeout": TIMEOUT_LIST, "txled": COLOR_LIST, "txpower": TXPOWER_LIST, "txpower3": TXPOWER3_LIST, "voice": VOICE_LIST, "vox": VOX_LIST, "widenarr": BANDWIDTH_LIST, "workmode": WORKMODE_LIST, "wtled": COLOR_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) UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D" UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25" UV5R_MODEL_F11 = "\x50\xBB\xFF\x13\xA1\x11\xDD" UV5R_MODEL_UV82 = "\x50\xBB\xFF\x20\x13\x01\x05" UV5R_MODEL_UV6 = "\x50\xBB\xFF\x20\x12\x08\x23" UV5R_MODEL_UV6_ORIG = "\x50\xBB\xFF\x12\x03\x98\x4D" UV5R_MODEL_A58 = "\x50\xBB\xFF\x20\x14\x04\x13" def _upper_band_from_data(data): return data[0x03:0x04] def _upper_band_from_image(radio): return _upper_band_from_data(radio.get_mmap()) def _firmware_version_from_data(data, version_start, version_stop): version_tag = data[version_start:version_stop] return version_tag def _firmware_version_from_image(radio): version = _firmware_version_from_data(radio.get_mmap(), radio._fw_ver_file_start, radio._fw_ver_file_stop) LOG.debug("_firmware_version_from_image: " + util.hexprint(version)) return version def _do_ident(radio, magic, secondack=True): serial = radio.pipe serial.timeout = 1 LOG.info("Sending Magic: %s" % util.hexprint(magic)) for byte in magic: serial.write(byte) time.sleep(0.01) ack = serial.read(1) if ack != "\x06": if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio did not respond") serial.write("\x02") # Until recently, the "ident" returned by the radios supported by this # driver have always been 8 bytes long. The image sturcture is the 8 byte # "ident" followed by the downloaded memory data. So all of the settings # structures are offset by 8 bytes. The ident returned from a UV-6 radio # can be 8 bytes (original model) or now 12 bytes. # # To accomodate this, the "ident" is now read one byte at a time until the # last byte ("\xdd") is encountered. The bytes containing the value "\x01" # are discarded to shrink the "ident" length down to 8 bytes to keep the # image data aligned with the existing settings structures. # Ok, get the response response = "" for i in range(1, 13): byte = serial.read(1) response += byte # stop reading once the last byte ("\xdd") is encountered if byte == "\xdd": break # check if response is OK if len(response) in [8, 12]: # DEBUG LOG.info("Valid response, got this:") LOG.debug(util.hexprint(response)) if len(response) == 12: ident = response[0] + response[3] + response[5] + response[7:] else: ident = response else: # bad response msg = "Unexpected response, got this:" msg += util.hexprint(response) LOG.debug(msg) raise errors.RadioError("Unexpected response from radio.") if secondack: serial.write("\x06") ack = serial.read(1) if ack != "\x06": raise errors.RadioError("Radio refused clone") return ident def _read_block(radio, start, size, first_command=False): msg = struct.pack(">BHB", ord("S"), start, size) radio.pipe.write(msg) if first_command is False: ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError( "Radio refused to send second block 0x%04x" % start) 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: LOG.error("Invalid answer for block 0x%04x:" % start) LOG.debug("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: LOG.error("Chunk length was 0x%04i" % len(chunk)) raise errors.RadioError("Radio sent incomplete block 0x%04x" % start) radio.pipe.write("\x06") time.sleep(0.05) return chunk def _get_radio_firmware_version(radio): if radio.MODEL == "BJ-UV55": block = _read_block(radio, 0x1FF0, 0x40, True) version = block[0:6] else: block1 = _read_block(radio, 0x1EC0, 0x40, True) block2 = _read_block(radio, 0x1F00, 0x40, False) block = block1 + block2 version = block[48:62] return version IDENT_BLACKLIST = { "\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3", } def _ident_radio(radio): for magic in radio._idents: error = None try: data = _do_ident(radio, magic) return data except errors.RadioError, e: LOG.error(e) error = e time.sleep(2) for magic, reason in IDENT_BLACKLIST.items(): try: _do_ident(radio, magic, secondack=False) except errors.RadioError as e: # No match, try the next one continue # If we got here, it means we identified the radio as # something other than one of our valid idents. Warn # the user so they can do the right thing. LOG.warning(('Identified radio as a blacklisted model ' '(details: %s)') % reason) raise errors.RadioError(('%s. Please choose the proper vendor/' 'model and try again.') % reason) if error: raise error raise errors.RadioError("Radio did not respond") def _do_download(radio): data = _ident_radio(radio) radio_version = _get_radio_firmware_version(radio) LOG.info("Radio Version is %s" % repr(radio_version)) if "HN5RV" in radio_version: # A radio with HN5RV firmware has been detected. It could be a # UV-5R style radio with HIGH/LOW power levels or it could be a # BF-F8HP style radio with HIGH/MID/LOW power levels. # We are going to count on the user to make the right choice and # then append that model type to the end of the image so it can # be properly detected when loaded. append_model = True elif "\xFF" * 14 in radio_version: # A radio UV-5R style radio that reports no firmware version has # been detected. # We are going to count on the user to make the right choice and # then append that model type to the end of the image so it can # be properly detected when loaded. append_model = True elif not any(type in radio_version for type in radio._basetype): # This radio can't be properly detected by parsing its firmware # version. raise errors.RadioError("Incorrect 'Model' selected.") else: # This radio can be properly detected by parsing its firmware version. # There is no need to append its model type to the end of the image. append_model = False # Main block LOG.debug("downloading main block...") for i in range(0, 0x1800, 0x40): data += _read_block(radio, i, 0x40, False) _do_status(radio, i) _do_status(radio, radio.get_memsize()) LOG.debug("done.") LOG.debug("downloading aux block...") # Auxiliary block starts at 0x1ECO (?) for i in range(0x1EC0, 0x2000, 0x40): data += _read_block(radio, i, 0x40, False) if append_model: data += radio.MODEL.ljust(8) LOG.debug("done.") return memmap.MemoryMap(data) def _send_block(radio, addr, data): msg = struct.pack(">BHB", ord("X"), addr, len(data)) radio.pipe.write(msg + data) time.sleep(0.05) ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio refused to accept block 0x%04x" % addr) def _do_upload(radio): ident = _ident_radio(radio) radio_upper_band = ident[3:4] image_upper_band = _upper_band_from_image(radio) if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio: if image_upper_band != radio_upper_band: raise errors.RadioError("Image not supported by radio") image_version = _firmware_version_from_image(radio) radio_version = _get_radio_firmware_version(radio) LOG.info("Image Version is %s" % repr(image_version)) LOG.info("Radio Version is %s" % repr(radio_version)) # default ranges _ranges_main_default = [ (0x0008, 0x0CF8), (0x0D08, 0x0DF8), (0x0E08, 0x1808) ] _ranges_aux_default = [ (0x1EC0, 0x1EF0), ] # extra aux ranges _ranges_aux_extra = [ (0x1F60, 0x1F70), (0x1F80, 0x1F90), (0x1FC0, 0x1FD0) ] if image_version == radio_version: image_matched_radio = True if image_version.startswith("HN5RV"): ranges_main = _ranges_main_default ranges_aux = _ranges_aux_default + _ranges_aux_extra elif image_version == 0xFF * 14: ranges_main = _ranges_main_default ranges_aux = _ranges_aux_default + _ranges_aux_extra else: ranges_main = radio._ranges_main ranges_aux = radio._ranges_aux elif any(type in radio_version for type in radio._basetype): image_matched_radio = False ranges_main = _ranges_main_default ranges_aux = _ranges_aux_default else: msg = ("The upload was stopped because the firmware " "version of the image (%s) does not match that " "of the radio (%s).") raise errors.RadioError(msg % (image_version, radio_version)) # Main block for start_addr, end_addr in ranges_main: for i in range(start_addr, end_addr, 0x10): _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10]) _do_status(radio, i) _do_status(radio, radio.get_memsize()) if len(radio.get_mmap().get_packed()) == 0x1808: LOG.info("Old image, not writing aux block") return # Old image, no aux block # Auxiliary block at radio address 0x1EC0, our offset 0x1808 for start_addr, end_addr in ranges_aux: for i in range(start_addr, end_addr, 0x10): addr = 0x1808 + (i - 0x1EC0) _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10]) if not image_matched_radio: msg = ("Upload finished, but the 'Other Settings' " "could not be sent because the firmware " "version of the image (%s) does not match " "that of the radio (%s).") raise errors.RadioError(msg % (image_version, radio_version)) UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00), chirp_common.PowerLevel("Low", watts=1.00)] UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00), chirp_common.PowerLevel("Med", 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 + \ "!@#$%^&*()+-=[]:\";'<>?,./" def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) == 0x1950: rid = data[0x1948:0x1950] return rid.startswith(cls.MODEL) elif len(data) == 0x1948: rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop] if any(type in rid for type in cls._basetype): return True else: return False class BaofengUV5R(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baofeng UV-5R""" VENDOR = "Baofeng" MODEL = "UV-5R" BAUD_RATE = 9600 _memsize = 0x1808 _basetype = BASETYPE_UV5R _idents = [UV5R_MODEL_291, UV5R_MODEL_ORIG ] _vhf_range = (136000000, 174000000) _220_range = (220000000, 260000000) _uhf_range = (400000000, 520000000) _mem_params = (0x1828 # poweron_msg offset ) # offset of fw version in image file _fw_ver_file_start = 0x1838 _fw_ver_file_stop = 0x1846 _ranges_main = [ (0x0008, 0x1808), ] _ranges_aux = [ (0x1EC0, 0x2000), ] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('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!') rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on (volume may need to be set at 100%). 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to download image from device.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on (volume may need to be set at 100%). 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to upload image to device.""")) return rp 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"] normal_bands = [self._vhf_range, self._uhf_range] rax_bands = [self._vhf_range, self._220_range] if self._mmap is None: rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]] elif not self._is_orig() and self._my_upper_band() == vhf_220_radio: rf.valid_bands = rax_bands else: rf.valid_bands = normal_bands rf.memory_bounds = (0, 127) return rf @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False if len(filedata) in [0x1808, 0x1948, 0x1950]: match_size = True match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, 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_mem(self, number): return self._memobj.memory[number] def _get_nam(self, number): return self._memobj.names[number] def get_memory(self, number): _mem = self._get_mem(number) _nam = self._get_nam(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: LOG.warn("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: LOG.warn("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" if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"): levels = UV5R_POWER_LEVELS3 else: levels = UV5R_POWER_LEVELS try: mem.power = levels[_mem.lowpower] except IndexError: LOG.error("Radio reported invalid power level %s (in %s)" % (_mem.lowpower, levels)) mem.power = levels[0] mem.mode = _mem.wide and "FM" or "NFM" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) rs = RadioSetting("pttid", "PTT ID", RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])) mem.extra.append(rs) rs = RadioSetting("scode", "PTT ID Code", RadioSettingValueList(PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.scode])) mem.extra.append(rs) return mem def _set_mem(self, number): return self._memobj.memory[number] def _set_nam(self, number): return self._memobj.names[number] def set_memory(self, mem): _mem = self._get_mem(mem.number) _nam = self._get_nam(mem.number) if mem.empty: _mem.set_raw("\xff" * 16) _nam.set_raw("\xff" * 16) return was_empty = False # same method as used in get_memory to find # out whether a raw memory is empty if _mem.get_raw()[0] == "\xff": was_empty = True LOG.debug("UV5R: this mem was empty") else: # memorize old extra-values before erasing the whole memory # used to solve issue 4121 LOG.debug("mem was not empty, memorize extra-settings") prev_bcl = _mem.bcl.get_value() prev_scode = _mem.scode.get_value() prev_pttid = _mem.pttid.get_value() _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 _namelength = self.get_features().valid_name_length for i in range(_namelength): 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" if mem.power: if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"): levels = [str(l) for l in UV5R_POWER_LEVELS3] _mem.lowpower = levels.index(str(mem.power)) else: _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power) else: _mem.lowpower = 0 if not was_empty: # restoring old extra-settings (issue 4121 _mem.bcl.set_value(prev_bcl) _mem.scode.set_value(prev_scode) _mem.pttid.set_value(prev_pttid) for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def _is_orig(self): version_tag = _firmware_version_from_image(self) LOG.debug("@_is_orig, version_tag: %s", util.hexprint(version_tag)) try: if 'BFB' in version_tag: idx = version_tag.index("BFB") + 3 version = int(version_tag[idx:idx + 3]) return version < 291 return False 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 _my_upper_band(self): band_tag = _upper_band_from_image(self) return band_tag def _get_settings(self): _ani = self._memobj.ani _fm_presets = self._memobj.fm_presets _settings = self._memobj.settings _squelch = self._memobj.squelch_new _vfoa = self._memobj.vfoa _vfob = self._memobj.vfob _wmchannel = self._memobj.wmchannel basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") group = RadioSettings(basic, advanced) rs = RadioSetting("squelch", "Carrier Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueList( SAVE_LIST, SAVE_LIST[_settings.save])) basic.append(rs) rs = RadioSetting("vox", "VOX Sensitivity", RadioSettingValueList( VOX_LIST, VOX_LIST[_settings.vox])) advanced.append(rs) if self.MODEL == "UV-6": # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it # autolk. Since this is a minor difference, it will be referred to # by the wrong name for the UV-6. rs = RadioSetting("autolk", "Vox", RadioSettingValueBoolean(_settings.autolk)) advanced.append(rs) if self.MODEL != "UV-6": rs = RadioSetting("abr", "Backlight Timeout", RadioSettingValueInteger(0, 24, _settings.abr)) basic.append(rs) rs = RadioSetting("tdr", "Dual Watch", RadioSettingValueBoolean(_settings.tdr)) advanced.append(rs) if self.MODEL == "UV-6": rs = RadioSetting("tdrch", "Dual Watch Channel", RadioSettingValueList( TDRCH_LIST, TDRCH_LIST[_settings.tdrch])) advanced.append(rs) rs = RadioSetting("tdrab", "Dual Watch TX Priority", RadioSettingValueBoolean(_settings.tdrab)) advanced.append(rs) else: rs = RadioSetting("tdrab", "Dual Watch TX Priority", RadioSettingValueList( TDRAB_LIST, TDRAB_LIST[_settings.tdrab])) advanced.append(rs) if self.MODEL == "UV-6": rs = RadioSetting("alarm", "Alarm Sound", RadioSettingValueBoolean(_settings.alarm)) advanced.append(rs) if _settings.almod > 0x02: val = 0x01 else: val = _settings.almod rs = RadioSetting("almod", "Alarm Mode", RadioSettingValueList( ALMOD_LIST, ALMOD_LIST[val])) 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._is_orig() and self._my_version() < 251: rs = RadioSetting("voice", "Voice", RadioSettingValueBoolean(_settings.voice)) advanced.append(rs) else: rs = RadioSetting("voice", "Voice", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voice])) advanced.append(rs) rs = RadioSetting("screv", "Scan Resume", RadioSettingValueList( RESUME_LIST, RESUME_LIST[_settings.screv])) advanced.append(rs) if self.MODEL != "UV-6": 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) if self.MODEL != "UV-6": rs = RadioSetting("autolk", "Automatic Key Lock", RadioSettingValueBoolean(_settings.autolk)) advanced.append(rs) rs = RadioSetting("fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_settings.fmradio)) advanced.append(rs) if self.MODEL != "UV-6": 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) if self.MODEL == "UV-82": rs = RadioSetting("roger", "Roger Beep (TX)", RadioSettingValueBoolean(_settings.roger)) basic.append(rs) rs = RadioSetting("rogerrx", "Roger Beep (RX)", RadioSettingValueList( ROGERRX_LIST, ROGERRX_LIST[_settings.rogerrx])) basic.append(rs) else: rs = RadioSetting("roger", "Roger Beep", RadioSettingValueBoolean(_settings.roger)) basic.append(rs) 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) if self.MODEL != "UV-6": rs = RadioSetting("reset", "RESET Menu", RadioSettingValueBoolean(_settings.reset)) advanced.append(rs) rs = RadioSetting("menu", "All Menus", RadioSettingValueBoolean(_settings.menu)) advanced.append(rs) if self.MODEL == "F-11": # this is an F-11 only feature rs = RadioSetting("vfomrlock", "VFO/MR Button", RadioSettingValueBoolean(_settings.vfomrlock)) advanced.append(rs) if self.MODEL == "UV-82": # this is a UV-82C only feature rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)", RadioSettingValueBoolean(_settings.vfomrlock)) advanced.append(rs) if self.MODEL == "UV-82HP": # this is a UV-82HP only feature rs = RadioSetting( "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)", RadioSettingValueBoolean(_settings.vfomrlock)) advanced.append(rs) if self.MODEL == "UV-82": # this is an UV-82C only feature rs = RadioSetting("singleptt", "Single PTT (UV-82C only)", RadioSettingValueBoolean(_settings.singleptt)) advanced.append(rs) if self.MODEL == "UV-82HP": # this is an UV-82HP only feature rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)", RadioSettingValueBoolean(_settings.singleptt)) advanced.append(rs) if self.MODEL == "UV-82HP": # this is an UV-82HP only feature rs = RadioSetting( "tdrch", "Tone Burst Frequency (BTech UV-82HP only)", RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch])) 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.firmware_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val) other.append(rs) if self.MODEL != "UV-6": _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) if self.MODEL != "UV-6": workmode = RadioSettingGroup("workmode", "Work Mode Settings") group.append(workmode) rs = RadioSetting("displayab", "Display", RadioSettingValueList( AB_LIST, AB_LIST[_settings.displayab])) workmode.append(rs) rs = RadioSetting("workmode", "VFO/MR Mode", RadioSettingValueList( WORKMODE_LIST, WORKMODE_LIST[_settings.workmode])) workmode.append(rs) rs = RadioSetting("keylock", "Keypad Lock", RadioSettingValueBoolean(_settings.keylock)) workmode.append(rs) rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _wmchannel.mrcha)) workmode.append(rs) rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _wmchannel.mrchb)) workmode.append(rs) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): value = chirp_common.parse_freq(value) if 17400000 <= value and value < 40000000: msg = ("Can't be between 174.00000-400.00000") raise InvalidValueError(msg) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 obj.band = value >= 40000000 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(_vfoa.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, _vfoa) workmode.append(rs) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(_vfob.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, _vfob) workmode.append(rs) rs = RadioSetting("vfoa.sftd", "VFO A Shift", RadioSettingValueList( SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd])) workmode.append(rs) rs = RadioSetting("vfob.sftd", "VFO B Shift", RadioSettingValueList( SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd])) workmode.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 10000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10000 for i in range(3, -1, -1): obj.offset[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_vfoa.offset)) rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a) rs.set_apply_callback(apply_offset, _vfoa) workmode.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_vfob.offset)) rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b) rs.set_apply_callback(apply_offset, _vfob) workmode.append(rs) if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"): rs = RadioSetting("vfoa.txpower3", "VFO A Power", RadioSettingValueList( TXPOWER3_LIST, TXPOWER3_LIST[_vfoa.txpower3])) workmode.append(rs) rs = RadioSetting("vfob.txpower3", "VFO B Power", RadioSettingValueList( TXPOWER3_LIST, TXPOWER3_LIST[_vfob.txpower3])) workmode.append(rs) else: rs = RadioSetting("vfoa.txpower", "VFO A Power", RadioSettingValueList( TXPOWER_LIST, TXPOWER_LIST[_vfoa.txpower])) workmode.append(rs) rs = RadioSetting("vfob.txpower", "VFO B Power", RadioSettingValueList( TXPOWER_LIST, TXPOWER_LIST[_vfob.txpower])) workmode.append(rs) rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.widenarr])) workmode.append(rs) rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.widenarr])) workmode.append(rs) rs = RadioSetting("vfoa.scode", "VFO A PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode])) workmode.append(rs) rs = RadioSetting("vfob.scode", "VFO B PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode])) workmode.append(rs) if not self._is_orig(): rs = RadioSetting("vfoa.step", "VFO A Tuning Step", RadioSettingValueList( STEP291_LIST, STEP291_LIST[_vfoa.step])) workmode.append(rs) rs = RadioSetting("vfob.step", "VFO B Tuning Step", RadioSettingValueList( STEP291_LIST, STEP291_LIST[_vfob.step])) workmode.append(rs) else: rs = RadioSetting("vfoa.step", "VFO A Tuning Step", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfoa.step])) workmode.append(rs) rs = RadioSetting("vfob.step", "VFO B Tuning Step", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfob.step])) workmode.append(rs) fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset") group.append(fm_preset) if _fm_presets <= 108.0 * 10 - 650: preset = _fm_presets / 10.0 + 65 elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10: preset = _fm_presets / 10.0 else: preset = 76.0 rs = RadioSetting("fm_presets", "FM Preset(MHz)", RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)) fm_preset.append(rs) dtmf = RadioSettingGroup("dtmf", "DTMF Settings") group.append(dtmf) dtmfchars = "0123456789 *#ABCD" for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("pttid/%i.code" % i, "PTT ID Code %i" % (i + 1), val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.pttid[i]) dtmf.append(rs) _codeobj = self._memobj.ani.code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("ani.code", "ANI Code", val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, _ani) dtmf.append(rs) rs = RadioSetting("ani.aniid", "ANI ID", RadioSettingValueList(PTTID_LIST, PTTID_LIST[_ani.aniid])) dtmf.append(rs) _codeobj = self._memobj.ani.alarmcode _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 3, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("ani.alarmcode", "Alarm Code", val) def apply_code(setting, obj): alarmcode = [] for j in range(0, 3): try: alarmcode.append(dtmfchars.index(str(setting.value)[j])) except IndexError: alarmcode.append(0xFF) obj.alarmcode = alarmcode rs.set_apply_callback(apply_code, _ani) dtmf.append(rs) rs = RadioSetting("dtmfst", "DTMF Sidetone", RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])) dtmf.append(rs) if _ani.dtmfon > 0xC3: val = 0x00 else: val = _ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(DTMFSPEED_LIST, DTMFSPEED_LIST[val])) dtmf.append(rs) if _ani.dtmfoff > 0xC3: val = 0x00 else: val = _ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(DTMFSPEED_LIST, DTMFSPEED_LIST[val])) dtmf.append(rs) rs = RadioSetting("pttlt", "PTT ID Delay", RadioSettingValueInteger(0, 50, _settings.pttlt)) dtmf.append(rs) if not self._is_orig(): service = RadioSettingGroup("service", "Service Settings") group.append(service) for band in ["vhf", "uhf"]: for index in range(0, 10): key = "squelch_new.%s.sql%i" % (band, index) if band == "vhf": _obj = self._memobj.squelch_new.vhf elif band == "uhf": _obj = self._memobj.squelch_new.uhf name = "%s Squelch %i" % (band.upper(), index) rs = RadioSetting(key, name, RadioSettingValueInteger( 0, 123, getattr(_obj, "sql%i" % (index)))) service.append(rs) return group def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): for element in settings: try: val = element.value if self._memobj.fm_presets <= 108.0 * 10 - 650: value = int(val.get_value() * 10 - 650) else: value = int(val.get_value() * 10) LOG.debug("Setting fm_presets = %s" % (value)) self._memobj.fm_presets = value except Exception, e: LOG.debug(element.get_name()) raise class UV5XAlias(chirp_common.Alias): VENDOR = "Baofeng" MODEL = "UV-5X" class RT5RAlias(chirp_common.Alias): VENDOR = "Retevis" MODEL = "RT-5R" class RT5RVAlias(chirp_common.Alias): VENDOR = "Retevis" MODEL = "RT-5RV" class RT5Alias(chirp_common.Alias): VENDOR = "Retevis" MODEL = "RT5" class RT5_TPAlias(chirp_common.Alias): VENDOR = "Retevis" MODEL = "RT5(tri-power)" class RH5RAlias(chirp_common.Alias): VENDOR = "Rugged" MODEL = "RH5R" @directory.register class BaofengUV5RGeneric(BaofengUV5R): ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias] @directory.register class BaofengF11Radio(BaofengUV5R): VENDOR = "Baofeng" MODEL = "F-11" _basetype = BASETYPE_F11 _idents = [UV5R_MODEL_F11] def _is_orig(self): # Override this for F11 to always return False return False @directory.register class BaofengUV82Radio(BaofengUV5R): MODEL = "UV-82" _basetype = BASETYPE_UV82 _idents = [UV5R_MODEL_UV82] _vhf_range = (130000000, 176000000) _uhf_range = (400000000, 521000000) def _is_orig(self): # Override this for UV82 to always return False return False @directory.register class BaofengUV6Radio(BaofengUV5R): """Baofeng UV-6/UV-7""" VENDOR = "Baofeng" MODEL = "UV-6" _basetype = BASETYPE_UV6 _idents = [UV5R_MODEL_UV6, UV5R_MODEL_UV6_ORIG ] def get_features(self): rf = BaofengUV5R.get_features(self) rf.memory_bounds = (1, 128) return rf def _get_mem(self, number): return self._memobj.memory[number - 1] def _get_nam(self, number): return self._memobj.names[number - 1] def _set_mem(self, number): return self._memobj.memory[number - 1] def _set_nam(self, number): return self._memobj.names[number - 1] def _is_orig(self): # Override this for UV6 to always return False return False @directory.register class IntekKT980Radio(BaofengUV5R): VENDOR = "Intek" MODEL = "KT-980HP" _basetype = BASETYPE_KT980HP _idents = [UV5R_MODEL_291] _vhf_range = (130000000, 180000000) _uhf_range = (400000000, 521000000) def get_features(self): rf = BaofengUV5R.get_features(self) rf.valid_power_levels = UV5R_POWER_LEVELS3 return rf def _is_orig(self): # Override this for KT980HP to always return False return False @directory.register class BaofengBFF8HPRadio(BaofengUV5R): VENDOR = "Baofeng" MODEL = "BF-F8HP" ALIASES = [RT5_TPAlias] _basetype = BASETYPE_F8HP _idents = [UV5R_MODEL_291, UV5R_MODEL_A58 ] _vhf_range = (130000000, 180000000) _uhf_range = (400000000, 521000000) def get_features(self): rf = BaofengUV5R.get_features(self) rf.valid_power_levels = UV5R_POWER_LEVELS3 return rf def _is_orig(self): # Override this for BFF8HP to always return False return False @directory.register class BaofengUV82HPRadio(BaofengUV5R): VENDOR = "Baofeng" MODEL = "UV-82HP" _basetype = BASETYPE_UV82HP _idents = [UV5R_MODEL_UV82] _vhf_range = (136000000, 175000000) _uhf_range = (400000000, 521000000) def get_features(self): rf = BaofengUV5R.get_features(self) rf.valid_power_levels = UV5R_POWER_LEVELS3 return rf def _is_orig(self): # Override this for UV82HP to always return False return False chirp-daily-20170714/chirp/drivers/ft1802.py0000644000016101777760000001776712476006422021471 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.drivers import yaesu_clone from chirp import chirp_common, bitwise, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettings from textwrap import dedent 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 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 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. After clicking OK, press the [MHz(SET)] key to send image.""")) rp.pre_upload = _(dedent("""\ 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. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD).""")) return rp 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-daily-20170714/chirp/drivers/tk8102.py0000644000016101777760000003243413060205711021452 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 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 os import logging from chirp import chirp_common, directory, memmap, errors, util from chirp import bitwise from chirp.settings import RadioSettingGroup, RadioSetting from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList from chirp.settings import RadioSettingValueString, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0030; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; ul16 rx_tone; ul16 tx_tone; u8 signaling:2, unknown1:3, bcl:1, wide:1, beatshift:1; u8 pttid:2, highpower:1, scan:1 unknown2:4; u8 unknown3[2]; } memory[8]; #seekto 0x0310; struct { char line1[32]; char line2[32]; } messages; """ POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("High", watts=50)] MODES = ["NFM", "FM"] PTTID = ["", "BOT", "EOT", "Both"] SIGNAL = ["", "DTMF"] def make_frame(cmd, addr, length, data=""): return struct.pack(">BHB", ord(cmd), addr, length) + data def send(radio, frame): # LOG.debug("%04i P>R: %s" % (len(frame), util.hexprint(frame))) radio.pipe.write(frame) def recv(radio, readdata=True): hdr = radio.pipe.read(4) cmd, addr, length = struct.unpack(">BHB", hdr) if readdata: data = radio.pipe.read(length) # LOG.debug(" PTone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_skips = ["", "S"] rf.valid_bands = [self._range] rf.memory_bounds = (1, self._upper) return rf def sync_in(self): try: self._mmap = do_download(self) except errors.RadioError: self.pipe.write("\x45") raise except Exception, e: raise errors.RadioError("Failed to download from radio: %s" % e) self.process_mmap() def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_out(self): try: do_upload(self) except errors.RadioError: self.pipe.write("\x45") raise except Exception, e: raise errors.RadioError("Failed to upload to radio: %s" % e) 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) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF": mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 offset = (int(_mem.tx_freq) * 10) - mem.freq if offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 self._get_tone(_mem, mem) mem.power = POWER_LEVELS[_mem.highpower] mem.mode = MODES[_mem.wide] mem.skip = not _mem.scan and "S" or "" mem.extra = RadioSettingGroup("all", "All Settings") bcl = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueBoolean(bool(_mem.bcl))) mem.extra.append(bcl) beat = RadioSetting("beatshift", "Beat Shift", RadioSettingValueBoolean(bool(_mem.beatshift))) mem.extra.append(beat) pttid = RadioSetting("pttid", "PTT ID", RadioSettingValueList(PTTID, PTTID[_mem.pttid])) mem.extra.append(pttid) signal = RadioSetting("signaling", "Signaling", RadioSettingValueList(SIGNAL, SIGNAL[ _mem.signaling & 0x01])) mem.extra.append(signal) 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 rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("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] if mem.empty: _mem.set_raw("\xFF" * 16) return _mem.unknown3[0] = 0x07 _mem.unknown3[1] = 0x22 _mem.rx_freq = mem.freq / 10 if 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 self._set_tone(mem, _mem) _mem.highpower = mem.power == POWER_LEVELS[1] _mem.wide = mem.mode == "FM" _mem.scan = mem.skip != "S" for setting in mem.extra: if setting.get_name == "signaling": if setting.value == "DTMF": _mem.signaling = 0x03 else: _mem.signaling = 0x00 else: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _mem = self._memobj basic = RadioSettingGroup("basic", "Basic") top = RadioSettings(basic) def _f(val): string = "" for char in str(val): if char == "\xFF": break string += char return string line1 = RadioSetting("messages.line1", "Message Line 1", RadioSettingValueString(0, 32, _f(_mem.messages.line1), autopad=False)) basic.append(line1) line2 = RadioSetting("messages.line2", "Message Line 2", RadioSettingValueString(0, 32, _f(_mem.messages.line2), autopad=False)) basic.append(line2) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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 "line" in setting: value = str(element.value).ljust(32, "\xFF") else: value = element.value setattr(obj, setting, value) @classmethod def match_model(cls, filedata, filename): model = filedata[0x03D1:0x03D5] LOG.debug(model) return model == cls.MODEL.split("-")[1] @directory.register class KenwoodTK7102Radio(KenwoodTKx102Radio): MODEL = "TK-7102" _range = (136000000, 174000000) _upper = 4 @directory.register class KenwoodTK8102Radio(KenwoodTKx102Radio): MODEL = "TK-8102" _range = (400000000, 500000000) _upper = 4 @directory.register class KenwoodTK7108Radio(KenwoodTKx102Radio): MODEL = "TK-7108" _range = (136000000, 174000000) _upper = 8 @directory.register class KenwoodTK8108Radio(KenwoodTKx102Radio): MODEL = "TK-8108" _range = (400000000, 500000000) _upper = 8 chirp-daily-20170714/chirp/drivers/kyd_IP620.py0000644000016101777760000005277013060205711022135 0ustar jenkinsnogroup00000000000000# Copyright 2015 Lepik.stv # based on modification of Dan Smith's and Jim Unroe 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 . """KYD IP-620 radios management module""" # TODO: Power on message # TODO: Channel name # TODO: Tuning step import struct import time import os import logging from chirp import util, chirp_common, bitwise, memmap, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) IP620_MEM_FORMAT = """ #seekto 0x0000; struct { // Channel memory structure lbcd rx_freq[4]; // RX frequency lbcd tx_freq[4]; // TX frequency ul16 rx_tone; // RX tone ul16 tx_tone; // TX tone u8 unknown_1:4 // n-a busy_loc:2, // NO-00, Crrier wave-01, SM-10 n_a:2; // n-a u8 unknown_2:1 // n-a scan_add:1, // Scan add n_a:1, // n-a w_n:1, // Narrow-0 Wide-1 lout:1, // LOCKOUT OFF-0 ON-1 n_a_:1, // n-a power:2; // Power low-00 middle-01 high-10 u8 unknown_3; // n-a u8 unknown_4; // n-a } memory[200]; #seekto 0x1000; struct { u8 chan_name[6]; //Channel name u8 unknown_1[10]; } chan_names[200]; #seekto 0x0C80; struct { // Settings memory structure ( A-Frequency mode ) lbcd freq_a_rx[4]; lbcd freq_a_tx[4]; ul16 freq_a_rx_tone; // RX tone ul16 freq_a_tx_tone; // TX tone u8 unknown_1_5:4 freq_a_busy_loc:2, n_a:2; u8 unknown_1_6:3 freq_a_w_n:1, n_a:1, na:1, freq_a_power:2; u8 unknown_1_7; u8 unknown_1_8; } settings_freq_a; #seekto 0x0E20; struct { u8 chan_disp_way; // Channel display way u8 step_freq; // Step frequency KHz u8 rf_sql; // Squelch level u8 bat_save; // Battery Saver u8 chan_pri; // Channel PRI u8 end_beep; // End beep u8 tot; // Time-out timer u8 vox; // VOX Gain u8 chan_pri_num; // Channel PRI time Sec u8 n_a_2; u8 ch_mode; // CH mode u8 n_a_3; u8 call_tone; // Call tone u8 beep; // Beep u8 unknown_1_1[2]; u8 unknown_1_2[8]; u8 scan_rev; // Scan rev u8 unknown_1_3[2]; u8 enc; // Frequency lock u8 vox_dly; // VOX Delay u8 wait_back_light;// Wait back light u8 unknown_1_4[2]; } settings; #seekto 0x0E40; struct { u8 fm_radio; // FM radio u8 auto_lock; // Auto lock u8 unknown_1[8]; u8 pon_msg[6]; //Power on msg } settings_misc; #seekto 0x1C80; struct { u8 unknown_1[16]; u8 unknown_2[16]; } settings_radio_3; """ CMD_ACK = "\x06" WRITE_BLOCK_SIZE = 0x10 READ_BLOCK_SIZE = 0x40 CHAR_LENGTH_MAX = 6 OFF_ON_LIST = ["OFF", "ON"] ON_OFF_LIST = ["ON", "OFF"] NO_YES_LIST = ["NO", "YES"] STEP_LIST = ["5.0", "6.25", "10.0", "12.5", "25.0"] BAT_SAVE_LIST = ["OFF", "0.2 Sec", "0.4 Sec", "0.6 Sec", "0.8 Sec","1.0 Sec"] SHIFT_LIST = ["", "-", "+"] SCANM_LIST = ["Time", "Carrier wave", "Search"] ENDBEEP_LIST = ["OFF", "Begin", "End", "Begin/End"] POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("Medium", watts=2.50), chirp_common.PowerLevel("High", watts=5.00)] TIMEOUT_LIST = ["OFF", "1 Min", "3 Min", "10 Min"] TOTALERT_LIST = ["", "OFF"] + ["%s seconds" % x for x in range(1, 11)] VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)] VOXDELAY_LIST = ["0.3 Sec", "0.5 Sec", "1.0 Sec", "1.5 Sec", "2.0 Sec", "3.0 Sec", "4.0 Sec", "5.0 Sec"] PRI_NUM = [3, 5, 8, 10] PRI_NUM_LIST = [str(x) for x in PRI_NUM] CH_FLAG_LIST = ["Channel+Freq", "Channel+Name"] BACKLIGHT_LIST = ["Always Off", "Auto", "Always On"] BUSYLOCK_LIST = ["NO", "Carrier", "SM"] KEYBLOCK_LIST = ["Manual", "Auto"] CALLTONE_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "1750"] RFSQL_LIST = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6","S-7", "S-8", "S-FULL"] IP620_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-* " IP620_BANDS = [ (136000000, 174000000), (200000000, 260000000), (300000000, 340000000), # <--- this band supports only Russian model (ARGUT A-36) (350000000, 390000000), (400000000, 480000000), (420000000, 510000000), (450000000, 520000000), ] @directory.register class IP620Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """KYD IP-620""" VENDOR = "KYD" MODEL = "IP-620" BAUD_RATE = 9600 _ranges = [ (0x0000, 0x2000), ] _memsize = 0x2000 @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and \ filedata[0xF7E:0xF80] == "\x01\xE2" def _ip620_exit_programming_mode(self): try: self.pipe.write("\x06") except errors.RadioError: raise except Exception, e: raise errors.RadioError("Radio refused to exit programming mode: %s" % e) def _ip620_enter_programming_mode(self): try: self.pipe.write("iUHOUXUN") self.pipe.write("\x02") time.sleep(0.2) _ack = self.pipe.read(1) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Error communicating with radio: %s" % e) if not _ack: raise errors.RadioError("No response from radio") elif _ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: self.pipe.write("\x02") _ident = self.pipe.read(8) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Error communicating with radio: %s" % e) if not _ident.startswith("\x06\x4B\x47\x36\x37\x01\x56\xF8"): print util.hexprint(_ident) raise errors.RadioError("Radio returned unknown identification string") try: self.pipe.write(CMD_ACK) _ack = self.pipe.read(1) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Error communicating with radio: %s" % e) if _ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _ip620_write_block(self, block_addr): _cmd = struct.pack(">cHb", 'W', block_addr, WRITE_BLOCK_SIZE) _data = self.get_mmap()[block_addr:block_addr + WRITE_BLOCK_SIZE] LOG.debug("Writing Data:") LOG.debug(util.hexprint(_cmd + _data)) try: self.pipe.write(_cmd + _data) if self.pipe.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def _ip620_read_block(self, block_addr): _cmd = struct.pack(">cHb", 'R', block_addr, READ_BLOCK_SIZE) _expectedresponse = "W" + _cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: self.pipe.write(_cmd) _response = self.pipe.read(4 + READ_BLOCK_SIZE) if _response[:4] != _expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) _block_data = _response[4:] self.pipe.write(CMD_ACK) _ack = self.pipe.read(1) except: raise errors.RadioError("Failed to read block at %04x" % block_addr) if _ack != CMD_ACK: raise Exception("No ACK reading block %04x." % (block_addr)) return _block_data def _do_download(self): self._ip620_enter_programming_mode() _data = "" _status = chirp_common.Status() _status.msg = "Cloning from radio" _status.cur = 0 _status.max = self._memsize for _addr in range(0, self._memsize, READ_BLOCK_SIZE): _status.cur = _addr + READ_BLOCK_SIZE self.status_fn(_status) _block = self._ip620_read_block(_addr) _data += _block LOG.debug("Address: %04x" % _addr) LOG.debug(util.hexprint(_block)) self._ip620_exit_programming_mode() return memmap.MemoryMap(_data) def _do_upload(self): _status = chirp_common.Status() _status.msg = "Uploading to radio" self._ip620_enter_programming_mode() _status.cur = 0 _status.max = self._memsize for _start_addr, _end_addr in self._ranges: for _addr in range(_start_addr, _end_addr, WRITE_BLOCK_SIZE): _status.cur = _addr + WRITE_BLOCK_SIZE self.status_fn(_status) self._ip620_write_block(_addr) self._ip620_exit_programming_mode() @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("This radio driver is currently under development. " "There are no known issues with it, but you should " "proceed with caution. However, proceed at your own risk!") return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True rf.has_cross = False rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = False rf.has_name = False rf.valid_skips = [] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_duplexes = SHIFT_LIST rf.valid_modes = ["FM", "NFM"] rf.memory_bounds = (1, 200) rf.valid_bands = IP620_BANDS rf.valid_characters = ''.join(set(IP620_CHARSET)) rf.valid_name_length = CHAR_LENGTH_MAX return rf def process_mmap(self): self._memobj = bitwise.parse(IP620_MEM_FORMAT, self._mmap) def sync_in(self): try: self._mmap = self._do_download() 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._do_upload() 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) LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone, rxmode, _mem.rx_tone)) def get_memory(self, number): _mem = self._memobj.memory[number - 1] _nam = self._memobj.chan_names[number - 1] def _is_empty(): for i in range(0, 4): if _mem.rx_freq[i].get_raw() != "\xFF": return False return True mem = chirp_common.Memory() mem.number = number if _is_empty(): mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 if int(_mem.rx_freq) == int(_mem.tx_freq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+" mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10 mem.mode = _mem.w_n and "FM" or "NFM" self._get_tone(_mem, mem) mem.power = POWER_LEVELS[_mem.power] mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("lout", "Lock out", RadioSettingValueList(OFF_ON_LIST, OFF_ON_LIST[_mem.lout])) mem.extra.append(rs) rs = RadioSetting("busy_loc", "Busy lock", RadioSettingValueList(BUSYLOCK_LIST, BUSYLOCK_LIST[_mem.busy_loc])) mem.extra.append(rs) rs = RadioSetting("scan_add", "Scan add", RadioSettingValueList(NO_YES_LIST, NO_YES_LIST[_mem.scan_add])) mem.extra.append(rs) #TODO: Show name channel ## count = 0 ## for i in _nam.chan_name: ## if i == 0xFF: ## break ## try: ## mem.name += IP620_CHARSET[i] ## except Exception: ## LOG.error("Unknown name char %i: 0x%02x (mem %i)" % ## (count, i, number - 1)) ## mem.name += " " ## count += 1 ## mem.name = mem.name.rstrip() return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("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] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return _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 == "+": _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 _mem.w_n = mem.mode == "FM" self._set_tone(mem, _mem) _mem.power = mem.power == POWER_LEVELS[1] for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings _settings_misc = self._memobj.settings_misc basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("rf_sql", "Squelch level (SQL)", RadioSettingValueList(RFSQL_LIST, RFSQL_LIST[_settings.rf_sql])) basic.append(rs) rs = RadioSetting("step_freq", "Step frequency KHz (STP)", RadioSettingValueList(STEP_LIST, STEP_LIST[_settings.step_freq])) basic.append(rs) rs = RadioSetting("fm_radio", "FM radio (DW)", RadioSettingValueList(OFF_ON_LIST, OFF_ON_LIST[_settings_misc.fm_radio])) basic.append(rs) rs = RadioSetting("call_tone", "Call tone (CK)", RadioSettingValueList(CALLTONE_LIST, CALLTONE_LIST[_settings.call_tone])) basic.append(rs) rs = RadioSetting("tot", "Time-out timer (TOT)", RadioSettingValueList(TIMEOUT_LIST, TIMEOUT_LIST[_settings.tot])) basic.append(rs) rs = RadioSetting("chan_disp_way", "Channel display way", RadioSettingValueList(CH_FLAG_LIST, CH_FLAG_LIST[_settings.chan_disp_way])) basic.append(rs) rs = RadioSetting("vox", "VOX Gain (VOX)", RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])) basic.append(rs) rs = RadioSetting("vox_dly", "VOX Delay", RadioSettingValueList(VOXDELAY_LIST, VOXDELAY_LIST[_settings.vox_dly])) basic.append(rs) rs = RadioSetting("beep", "Beep (BP)", RadioSettingValueList(OFF_ON_LIST, OFF_ON_LIST[_settings.beep])) basic.append(rs) rs = RadioSetting("auto_lock", "Auto lock (KY)", RadioSettingValueList(NO_YES_LIST, NO_YES_LIST[_settings_misc.auto_lock])) basic.append(rs) rs = RadioSetting("bat_save", "Battery Saver (SAV)", RadioSettingValueList(BAT_SAVE_LIST, BAT_SAVE_LIST[_settings.bat_save])) basic.append(rs) rs = RadioSetting("chan_pri", "Channel PRI (PRI)", RadioSettingValueList(OFF_ON_LIST, OFF_ON_LIST[_settings.chan_pri])) basic.append(rs) rs = RadioSetting("chan_pri_num", "Channel PRI time Sec (PRI)", RadioSettingValueList(PRI_NUM_LIST, PRI_NUM_LIST[_settings.chan_pri_num])) basic.append(rs) rs = RadioSetting("end_beep", "End beep (ET)", RadioSettingValueList(ENDBEEP_LIST, ENDBEEP_LIST[_settings.end_beep])) basic.append(rs) rs = RadioSetting("ch_mode", "CH mode", RadioSettingValueList(ON_OFF_LIST, ON_OFF_LIST[_settings.ch_mode])) basic.append(rs) rs = RadioSetting("scan_rev", "Scan rev (SCAN)", RadioSettingValueList(SCANM_LIST, SCANM_LIST[_settings.scan_rev])) basic.append(rs) rs = RadioSetting("enc", "Frequency lock (ENC)", RadioSettingValueList(OFF_ON_LIST, OFF_ON_LIST[_settings.enc])) basic.append(rs) rs = RadioSetting("wait_back_light", "Wait back light (LED)", RadioSettingValueList(BACKLIGHT_LIST, BACKLIGHT_LIST[_settings.wait_back_light])) basic.append(rs) return top def _set_misc_settings(self, settings): for element in settings: try: setattr(self._memobj.settings_misc, element.get_name(), element.value) except Exception, e: LOG.debug(element.get_name()) raise def set_settings(self, settings): _settings = self._memobj.settings _settings_misc = self._memobj.settings_misc for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() if setting in ["auto_lock","fm_radio"]: oldval = getattr(_settings_misc, setting) else: oldval = getattr(_settings, setting) newval = element.value LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) if setting in ["auto_lock","fm_radio"]: setattr(_settings_misc, setting, newval) else: setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/id51.py0000644000016101777760000000632312612355377021301 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 logging from chirp.drivers import id31 from chirp import directory, bitwise MEM_FORMAT = """ struct { u24 freq; u16 offset; u16 rtone:6, ctone:6, unknown2:1, mode:3; u8 dtcs; u8 tune_step:4, unknown5:4; u8 unknown4; u8 tmode:4, duplex:2, dtcs_polarity:2; char name[16]; u8 unknown13; u8 urcall[7]; u8 rpt1call[7]; u8 rpt2call[7]; } memory[500]; #seekto 0x6A40; u8 used_flags[70]; #seekto 0x6A86; u8 skip_flags[69]; #seekto 0x6ACB; u8 pskp_flags[69]; #seekto 0x6B40; struct { u8 bank; u8 index; } banks[500]; #seekto 0x6FD0; struct { char name[16]; } bank_names[26]; #seekto 0xA8C0; struct { u24 freq; u16 offset; u8 unknown1[3]; u8 call[7]; char name[16]; char subname[8]; u8 unknown3[10]; } repeaters[750]; #seekto 0x1384E; struct { u8 call[7]; } rptcall[750]; #seekto 0x14E60; struct { char call[8]; char tag[4]; } mycall[6]; #seekto 0x14EA8; struct { char call[8]; } urcall[200]; """ LOG = logging.getLogger(__name__) @directory.register class ID51Radio(id31.ID31Radio): """Icom ID-51""" MODEL = "ID-51" _memsize = 0x1FB40 _model = "\x33\x90\x00\x01" _endframe = "Icom Inc\x2E\x44\x41" _ranges = [(0x00000, 0x1FB40, 32)] MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"} @classmethod def match_model(cls, filedata, filename): """Given contents of a stored file (@filedata), return True if this radio driver handles the represented model""" # The default check for ICOM is just to check memory size # Since the ID-51 and ID-51 Plus/Anniversary have exactly # the same memory size, we need to do a more detailed check. if len(filedata) == cls._memsize: LOG.debug('File has correct memory size, ' 'checking 20 bytes at offset 0x1AF40') snip = filedata[0x1AF40:0x1AF60] if snip == ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'): LOG.debug('bytes matched ID-51 Signature') return True else: LOG.debug('bytes did not match ID-51 Signature') return False def get_features(self): rf = super(ID51Radio, self).get_features() rf.valid_bands = [(108000000, 174000000), (400000000, 479000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) chirp-daily-20170714/chirp/drivers/uv6r.py0000644000016101777760000007031512761227400021431 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp.drivers import baofeng_common from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, \ InvalidValueError from textwrap import dedent ##### MAGICS ######################################################### # Baofeng UV-6R magic string MSTRING_UV6R = "\x50\xBB\xFF\x20\x14\x11\x22" ##### ID strings ##################################################### # Baofeng UV-6R UV6R_fp1 = " BF230#1" DTMF_CHARS = "0123456789 *#ABCD" STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] LIST_AB = ["A", "B"] LIST_ALMOD = ["Site", "Tone", "Code"] LIST_BANDWIDTH = ["Wide", "Narrow"] LIST_COLOR = ["Off", "Blue", "Orange", "Purple"] LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"] LIST_MODE = ["Channel", "Name", "Frequency"] LIST_OFF1TO9 = ["Off"] + list("123456789") LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"] LIST_OFFAB = ["Off"] + LIST_AB LIST_RESUME = ["TO", "CO", "SE"] LIST_PONMSG = ["Full", "Message"] LIST_PTTID = ["Off", "BOT", "EOT", "Both"] LIST_SCODE = ["%s" % x for x in range(1, 16)] LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SHIFTD = ["Off", "+", "-"] LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)] LIST_STEP = [str(x) for x in STEPS] LIST_TCALL = ["Off", "1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"] LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)] LIST_TXPOWER = ["High", "Low"] LIST_VOICE = ["Off", "English", "Chinese"] LIST_WORKMODE = ["Frequency", "Channel"] def model_match(cls, data): """Match the opened/downloaded image to the correct version""" match_rid1 = False match_rid2 = False rid1 = data[0x1FF8:0x2000] if rid1 in cls._fileid: match_rid1 = True rid2 = data[0x1FD0:0x1FD5] if rid2 == cls.MODEL: match_rid2 = True if match_rid1 and match_rid2: return True else: return False @directory.register class UV6R(baofeng_common.BaofengCommonHT): """Baofeng UV-6R""" VENDOR = "Baofeng" MODEL = "UV-6R" _fileid = [UV6R_fp1, ] _magic = [MSTRING_UV6R, ] _magic_response_length = 8 _fw_ver_start = 0x1FF0 _recv_block_size = 0x40 _mem_size = 0x2000 _ack_block = False _ranges = [(0x0000, 0x1800), (0x1F40, 0x1F50), (0x1FC0, 0x1FD0), (0x1FE0, 0x1FF0)] _send_block_size = 0x10 MODES = ["FM", "NFM"] VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "!@#$%^&*()+-=[]:\";'<>?,./" LENGTH_NAME = 6 SKIP_VALUES = ["", "S"] DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Low", watts=1.00)] VALID_BANDS = [(136000000, 174000000), (400000000, 520000000)] PTTID_LIST = LIST_PTTID SCODE_LIST = LIST_SCODE MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown0:4, scode:4; u8 unknown1; u8 unknown2:7, lowpower:1; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B00; struct { u8 code[5]; u8 unused[11]; } pttid[15]; #seekto 0x0CAA; struct { u8 code[5]; u8 unused:6, aniid:2; u8 unknown[2]; u8 dtmfon; u8 dtmfoff; } ani; #seekto 0x0E20; struct { u8 unused00:4, squelch:4; u8 unused01:5, step:3; u8 unknown00; u8 unused02:5, save:3; u8 unused03:4, vox:4; u8 unknown01; u8 unused04:4, abr:4; u8 unused05:7, tdr:1; u8 unused06:7, beep:1; u8 unused07:2, timeout:6; u8 unused08:6, tcall:2; u8 unknown02[3]; u8 unused09:6, voice:2; u8 unknown03; u8 unused10:6, dtmfst:2; u8 unknown04; u8 unused11:6, screv:2; u8 unused12:6, pttid:2; u8 unused13:2, pttlt:6; u8 unused14:6, mdfa:2; u8 unused15:6, mdfb:2; u8 unknown05; u8 unused16:7, autolk:1; u8 unknown06[4]; u8 unused17:6, wtled:2; u8 unused18:6, rxled:2; u8 unused19:6, txled:2; u8 unused20:6, almod:2; u8 unknown07[2]; u8 unused22:7, ste:1; u8 unused23:4, rpste:4; u8 unused24:4, rptrl:4; u8 unused25:7, ponmsg:1; u8 unused26:7, roger:1; u8 unused27:7, reset:1; u8 unknown08; u8 displayab:1, unknown09:2, fmradio:1, alarm:1, unknown10:1, reset:1, menu:1; u8 unknown11; u8 unused29:7, workmode:1; u8 unused30:7, keylock:1; u8 cht; } settings; #seekto 0x0E76; struct { u8 unused0:1, mrcha:7; u8 unused1:1, mrchb:7; } wmchannel; struct vfo { u8 unknown0[8]; u8 freq[8]; u8 offset[6]; ul16 rxtone; ul16 txtone; u8 unused0:7, band:1; u8 unknown3; u8 unknown4:2, sftd:2, scode:4; u8 unknown5; u8 unknown6:1, step:3, unknown7:4; u8 txpower:1, widenarr:1, unknown8:6; }; #seekto 0x0F00; struct { struct vfo a; struct vfo b; } vfo; #seekto 0x0F4E; u16 fm_presets; #seekto 0x1000; struct { char name[6]; u8 unknown[10]; } names[128]; #seekto 0x1F40; struct { u8 sql0; u8 sql1; u8 sql2; u8 sql3; u8 sql4; u8 sql5; u8 sql6; u8 sql7; u8 sql8; u8 sql9; } squelch; struct limit { u8 enable; bbcd lower[2]; bbcd upper[2]; }; #seekto 0x1FC0; struct { struct limit vhf; struct limit uhf; } limits; #seekto 0x1FD0; struct { char line1[8]; char line2[8]; } sixpoweron_msg; #seekto 0x1FE0; struct { char line1[7]; char line2[7]; } poweron_msg; #seekto 0x1FF0; struct { char line1[8]; char line2[8]; } firmware_msg; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The BTech UV-6R driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap) def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") fm_preset = RadioSettingGroup("fm_preset", "FM Preset") dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings") service = RadioSettingGroup("service", "Service Settings") top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe, service) # Basic settings if _mem.settings.squelch > 0x09: val = 0x00 else: val = _mem.settings.squelch rs = RadioSetting("settings.squelch", "Squelch", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[val])) basic.append(rs) if _mem.settings.save > 0x04: val = 0x00 else: val = _mem.settings.save rs = RadioSetting("settings.save", "Battery Saver", RadioSettingValueList( LIST_SAVE, LIST_SAVE[val])) basic.append(rs) if _mem.settings.vox > 0x0A: val = 0x00 else: val = _mem.settings.vox rs = RadioSetting("settings.vox", "Vox", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) if _mem.settings.abr > 0x0A: val = 0x00 else: val = _mem.settings.abr rs = RadioSetting("settings.abr", "Backlight Timeout", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) rs = RadioSetting("settings.tdr", "Dual Watch", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(rs) rs = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(rs) if _mem.settings.timeout > 0x27: val = 0x03 else: val = _mem.settings.timeout rs = RadioSetting("settings.timeout", "Timeout Timer", RadioSettingValueList( LIST_TIMEOUT, LIST_TIMEOUT[val])) basic.append(rs) if _mem.settings.voice > 0x02: val = 0x01 else: val = _mem.settings.voice rs = RadioSetting("settings.voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) rs = RadioSetting("settings.dtmfst", "DTMF Sidetone", RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[ _mem.settings.dtmfst])) basic.append(rs) if _mem.settings.screv > 0x02: val = 0x01 else: val = _mem.settings.screv rs = RadioSetting("settings.screv", "Scan Resume", RadioSettingValueList( LIST_RESUME, LIST_RESUME[val])) basic.append(rs) rs = RadioSetting("settings.pttid", "When to send PTT ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[ _mem.settings.pttid])) basic.append(rs) if _mem.settings.pttlt > 0x1E: val = 0x05 else: val = _mem.settings.pttlt rs = RadioSetting("pttlt", "PTT ID Delay", RadioSettingValueInteger(0, 50, val)) basic.append(rs) rs = RadioSetting("settings.mdfa", "Display Mode (A)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfa])) basic.append(rs) rs = RadioSetting("settings.mdfb", "Display Mode (B)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfb])) basic.append(rs) rs = RadioSetting("settings.autolk", "Auto Lock Keypad", RadioSettingValueBoolean(_mem.settings.autolk)) basic.append(rs) rs = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.wtled])) basic.append(rs) rs = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.rxled])) basic.append(rs) rs = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.txled])) basic.append(rs) if _mem.settings.almod > 0x02: val = 0x00 else: val = _mem.settings.almod rs = RadioSetting("settings.almod", "Alarm Mode", RadioSettingValueList( LIST_ALMOD, LIST_ALMOD[val])) basic.append(rs) if _mem.settings.tcall > 0x05: val = 0x00 else: val = _mem.settings.tcall rs = RadioSetting("settings.tcall", "Tone Burst Frequency", RadioSettingValueList( LIST_TCALL, LIST_TCALL[val])) basic.append(rs) rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)", RadioSettingValueBoolean(_mem.settings.ste)) basic.append(rs) if _mem.settings.rpste > 0x0A: val = 0x00 else: val = _mem.settings.rpste rs = RadioSetting("settings.rpste", "Squelch Tail Eliminate (repeater)", RadioSettingValueList( LIST_RPSTE, LIST_RPSTE[val])) basic.append(rs) if _mem.settings.rptrl > 0x0A: val = 0x00 else: val = _mem.settings.rptrl rs = RadioSetting("settings.rptrl", "STE Repeater Delay", RadioSettingValueList( LIST_STEDELAY, LIST_STEDELAY[val])) basic.append(rs) rs = RadioSetting("settings.ponmsg", "Power-On Message", RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[ _mem.settings.ponmsg])) basic.append(rs) rs = RadioSetting("settings.roger", "Roger Beep", RadioSettingValueBoolean(_mem.settings.roger)) basic.append(rs) # Advanced settings rs = RadioSetting("settings.reset", "RESET Menu", RadioSettingValueBoolean(_mem.settings.reset)) advanced.append(rs) rs = RadioSetting("settings.menu", "All Menus", RadioSettingValueBoolean(_mem.settings.menu)) advanced.append(rs) rs = RadioSetting("settings.fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_mem.settings.fmradio)) advanced.append(rs) rs = RadioSetting("settings.alarm", "Alarm Sound", RadioSettingValueBoolean(_mem.settings.alarm)) advanced.append(rs) # Other settings def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = _mem.firmware_msg val = RadioSettingValueString(0, 8, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val) other.append(rs) val = RadioSettingValueString(0, 8, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val) other.append(rs) _msg = _mem.sixpoweron_msg val = RadioSettingValueString(0, 8, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val) other.append(rs) val = RadioSettingValueString(0, 8, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val) other.append(rs) _msg = _mem.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) lower = 136 upper = 174 rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.lower)) other.append(rs) rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.upper)) other.append(rs) lower = 400 upper = 520 rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.lower)) other.append(rs) rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.upper)) other.append(rs) # Work mode settings rs = RadioSetting("settings.displayab", "Display", RadioSettingValueList( LIST_AB, LIST_AB[_mem.settings.displayab])) work.append(rs) rs = RadioSetting("settings.workmode", "VFO/MR Mode", RadioSettingValueList( LIST_WORKMODE, LIST_WORKMODE[_mem.settings.workmode])) work.append(rs) rs = RadioSetting("settings.keylock", "Keypad Lock", RadioSettingValueBoolean(_mem.settings.keylock)) work.append(rs) rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrcha)) work.append(rs) rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrchb)) work.append(rs) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): _vhf_lower = int(_mem.limits.vhf.lower) _vhf_upper = int(_mem.limits.vhf.upper) _uhf_lower = int(_mem.limits.uhf.lower) _uhf_upper = int(_mem.limits.uhf.upper) value = chirp_common.parse_freq(value) msg = ("Can't be less than %i.0000") if value > 99000000 and value < _vhf_lower * 1000000: raise InvalidValueError(msg % _vhf_lower) msg = ("Can't be between %i.9975-%i.0000") if _vhf_upper * 1000000 <= value and value < _uhf_lower * 1000000: raise InvalidValueError(msg % (_vhf_upper - 1, _uhf_lower)) msg = ("Can't be greater than %i.9975") if value > 99000000 and value >= _uhf_upper * 1000000: raise InvalidValueError(msg % (_uhf_upper - 1)) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.a.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.b.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.sftd", "VFO A Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd])) work.append(rs) rs = RadioSetting("vfo.b.sftd", "VFO B Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd])) work.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 1000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 1000 for i in range(5, -1, -1): obj.offset[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset)) rs = RadioSetting("vfo.a.offset", "VFO A Offset", val1a) rs.set_apply_callback(apply_offset, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset)) rs = RadioSetting("vfo.b.offset", "VFO B Offset", val1b) rs.set_apply_callback(apply_offset, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.txpower", "VFO A Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.a.txpower])) work.append(rs) rs = RadioSetting("vfo.b.txpower", "VFO B Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.b.txpower])) work.append(rs) rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.a.widenarr])) work.append(rs) rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.b.widenarr])) work.append(rs) rs = RadioSetting("vfo.a.scode", "VFO A S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.a.scode])) work.append(rs) rs = RadioSetting("vfo.b.scode", "VFO B S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.b.scode])) work.append(rs) rs = RadioSetting("vfo.a.step", "VFO A Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.a.step])) work.append(rs) rs = RadioSetting("vfo.b.step", "VFO B Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.b.step])) work.append(rs) # broadcast FM settings _fm_presets = self._memobj.fm_presets if _fm_presets <= 108.0 * 10 - 650: preset = _fm_presets / 10.0 + 65 elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10: preset = _fm_presets / 10.0 else: preset = 76.0 rs = RadioSetting("fm_presets", "FM Preset(MHz)", RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)) fm_preset.append(rs) # DTMF settings def apply_code(setting, obj, length): code = [] for j in range(0, length): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) pttid = RadioSetting("pttid/%i.code" % i, "Signal Code %i" % (i + 1), val) pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5) dtmfe.append(pttid) if _mem.ani.dtmfon > 0xC3: val = 0x03 else: val = _mem.ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) if _mem.ani.dtmfoff > 0xC3: val = 0x03 else: val = _mem.ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) _codeobj = self._memobj.ani.code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.code", "ANI Code", val) rs.set_apply_callback(apply_code, self._memobj.ani, 5) dtmfe.append(rs) rs = RadioSetting("ani.aniid", "When to send ANI ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[_mem.ani.aniid])) dtmfe.append(rs) # Service settings for index in range(0, 10): key = "squelch.sql%i" % (index) _obj = self._memobj.squelch val = RadioSettingValueInteger(0, 123, getattr(_obj, "sql%i" % (index))) if index == 0: val.set_mutable(False) name = "Squelch %i" % (index) rs = RadioSetting(key, name, val) service.append(rs) return top @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == 0x2008: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/ic2720.py0000644000016101777760000001253712476006422021441 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.drivers import icf from chirp import chirp_common, util, directory, 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-daily-20170714/chirp/drivers/retevis_rt22.py0000644000016101777760000004620413060727311023060 0ustar jenkinsnogroup00000000000000# Copyright 2016 Jim Unroe # # 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 time import os import struct import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettings, \ RadioSettingValueString LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rx_tone; ul16 tx_tone; u8 unknown1; u8 unknown3:2, highpower:1, // Power Level wide:1, // Bandwidth unknown4:4; u8 unknown5[2]; } memory[16]; #seekto 0x012F; struct { u8 voice; // Voice Annunciation u8 tot; // Time-out Timer u8 unknown1[3]; u8 squelch; // Squelch Level u8 save; // Battery Saver u8 beep; // Beep u8 unknown2[2]; u8 vox; // VOX u8 voxgain; // VOX Gain u8 voxdelay; // VOX Delay u8 unknown3[2]; u8 pf2key; // PF2 Key } settings; #seekto 0x017E; u8 skipflags[2]; // SCAN_ADD #seekto 0x0300; struct { char line1[32]; char line2[32]; } embedded_msg; """ CMD_ACK = "\x06" RT22_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=2.00), chirp_common.PowerLevel("High", watts=5.00)] RT22_DTCS = sorted(chirp_common.DTCS_CODES + [645]) PF2KEY_LIST = ["Scan", "Local Alarm", "Remote Alarm"] TIMEOUTTIMER_LIST = [""] + ["%s seconds" % x for x in range(15, 615, 15)] VOICE_LIST = ["Off", "Chinese", "English"] VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)] VOXDELAY_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0"] SETTING_LISTS = { "pf2key": PF2KEY_LIST, "tot": TIMEOUTTIMER_LIST, "voice": VOICE_LIST, "vox": VOX_LIST, "voxdelay": VOXDELAY_LIST, } VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_" def _rt22_enter_programming_mode(radio): serial = radio.pipe magic = "PROGRGS" exito = False for i in range(0, 5): for j in range(0, len(magic)): time.sleep(0.005) serial.write(magic[j]) ack = serial.read(1) try: if ack == CMD_ACK: exito = True break except: LOG.debug("Attempt #%s, failed, trying again" % i) pass # check if we had EXITO if exito is False: msg = "The radio did not accept program mode after five tries.\n" msg += "Check you interface cable and power cycle your radio." raise errors.RadioError(msg) try: serial.write("\x02") ident = serial.read(8) except: _rt22_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if not ident.startswith("P32073"): _rt22_exit_programming_mode(radio) LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: _rt22_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: _rt22_exit_programming_mode(radio) raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x07") ack = serial.read(1) except: _rt22_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if ack != "\x4E": _rt22_exit_programming_mode(radio) raise errors.RadioError("Radio refused to enter programming mode") def _rt22_exit_programming_mode(radio): serial = radio.pipe try: serial.write("E") except: raise errors.RadioError("Radio refused to exit programming mode") def _rt22_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, block_size) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: for j in range(0, len(cmd)): time.sleep(0.005) serial.write(cmd[j]) response = serial.read(4 + block_size) if response[:4] != expectedresponse: _rt22_exit_programming_mode(radio) raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: _rt22_exit_programming_mode(radio) raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: _rt22_exit_programming_mode(radio) raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _rt22_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, block_size) data = radio.get_mmap()[block_addr:block_addr + block_size] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: for j in range(0, len(cmd)): time.sleep(0.005) serial.write(cmd[j]) for j in range(0, len(data)): time.sleep(0.005) serial.write(data[j]) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: _rt22_exit_programming_mode(radio) raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _rt22_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, radio._block_size): status.cur = addr + radio._block_size radio.status_fn(status) block = _rt22_read_block(radio, addr, radio._block_size) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) data += radio.MODEL.ljust(8) _rt22_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _rt22_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr, block_size in radio._ranges: for addr in range(start_addr, end_addr, block_size): status.cur = addr + block_size radio.status_fn(status) _rt22_write_block(radio, addr, block_size) _rt22_exit_programming_mode(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) == 0x0408: rid = data[0x0400:0x0408] return rid.startswith(cls.MODEL) else: return False @directory.register class RT22Radio(chirp_common.CloneModeRadio): """Retevis RT22""" VENDOR = "Retevis" MODEL = "RT22" BAUD_RATE = 9600 _ranges = [ (0x0000, 0x0180, 0x10), (0x01B8, 0x01F8, 0x10), (0x01F8, 0x0200, 0x08), (0x0200, 0x0340, 0x10), ] _memsize = 0x0400 _block_size = 0x40 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True rf.has_cross = True rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = False 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 = RT22_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.memory_bounds = (1, 16) rf.valid_bands = [(400000000, 520000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): """Download from radio""" try: data = do_download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = data self.process_mmap() def sync_out(self): """Upload to radio""" try: do_upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') 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) LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone, rxmode, _mem.rx_tone)) def get_memory(self, number): bitpos = (1 << ((number - 1) % 8)) bytepos = ((number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[number - 1] _skp = self._memobj.skipflags[bytepos] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = _mem.wide and "FM" or "NFM" self._get_tone(_mem, mem) mem.power = RT22_POWER_LEVELS[_mem.highpower] mem.skip = "" if (_skp & bitpos) else "S" LOG.debug("mem.skip %s" % mem.skip) return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone)) def set_memory(self, mem): bitpos = (1 << ((mem.number - 1) % 8)) bytepos = ((mem.number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[mem.number - 1] _skp = self._memobj.skipflags[bytepos] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return _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 _mem.wide = mem.mode == "FM" self._set_tone(mem, _mem) _mem.highpower = mem.power == RT22_POWER_LEVELS[1] if mem.skip != "S": _skp |= bitpos else: _skp &= ~bitpos LOG.debug("_skp %s" % _skp) def get_settings(self): _settings = self._memobj.settings _message = self._memobj.embedded_msg basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("tot", "Time-out timer", RadioSettingValueList( TIMEOUTTIMER_LIST, TIMEOUTTIMER_LIST[_settings.tot])) basic.append(rs) rs = RadioSetting("voice", "Voice Prompts", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voice])) basic.append(rs) rs = RadioSetting("pf2key", "PF2 Key", RadioSettingValueList( PF2KEY_LIST, PF2KEY_LIST[_settings.pf2key])) basic.append(rs) rs = RadioSetting("vox", "Vox", RadioSettingValueBoolean(_settings.vox)) basic.append(rs) rs = RadioSetting("voxgain", "VOX Level", RadioSettingValueList( VOX_LIST, VOX_LIST[_settings.voxgain])) basic.append(rs) rs = RadioSetting("voxdelay", "VOX Delay Time", RadioSettingValueList( VOXDELAY_LIST, VOXDELAY_LIST[_settings.voxdelay])) basic.append(rs) rs = RadioSetting("save", "Battery Save", RadioSettingValueBoolean(_settings.save)) basic.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) def _filter(name): filtered = "" for char in str(name): if char in VALID_CHARS: filtered += char else: filtered += " " return filtered rs = RadioSetting("embedded_msg.line1", "Embedded Message 1", RadioSettingValueString(0, 32, _filter( _message.line1))) basic.append(rs) rs = RadioSetting("embedded_msg.line2", "Embedded Message 2", RadioSettingValueString(0, 32, _filter( _message.line2))) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) in [0x0408, ]: match_size = True # testing the model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False @directory.register class KDC1(RT22Radio): """WLN KD-C1""" VENDOR = "WLN" MODEL = "KD-C1" @directory.register class ZTX6(RT22Radio): """Zastone ZT-X6""" VENDOR = "Zastone" MODEL = "ZT-X6" @directory.register class LT316(RT22Radio): """Luiton LT-316""" VENDOR = "LUITON" MODEL = "LT-316" @directory.register class TDM8(RT22Radio): VENDOR = "TID" MODEL = "TD-M8" chirp-daily-20170714/chirp/drivers/ict8.py0000644000016101777760000001010612712567601021374 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.drivers import icf from chirp import chirp_common, util, directory from chirp import bitwise mem_format = """ struct memory { bbcd freq[3]; bbcd offset[3]; u8 rtone; u8 ctone; }; struct flags { u8 empty:1, skip:1, tmode:2, duplex:2, unknown2:2; }; struct memory memory[100]; #seekto 0x0400; struct { char name[4]; } names[100]; #seekto 0x0600; struct flags flags[100]; """ DUPLEX = ["", "", "-", "+"] TMODES = ["", "", "Tone", "TSQL"] def _get_freq(bcd_array): lastnibble = bcd_array[2].get_bits(0x0F) return (int(bcd_array) - lastnibble) * 1000 + lastnibble * 500 def _set_freq(bcd_array, freq): bitwise.int_to_bcd(bcd_array, freq / 1000) bcd_array[2].set_raw(bcd_array[2].get_bits(0xF0) + freq % 10000 / 500) @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.valid_name_length = 4 rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC 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.names[number]) + str(self._memobj.flags[number])) def get_memory(self, number): _mem = self._memobj.memory[number] _flg = self._memobj.flags[number] _name = self._memobj.names[number] mem = chirp_common.Memory() mem.number = number if _flg.empty: mem.empty = True return mem mem.freq = _get_freq(_mem.freq) mem.offset = _get_freq(_mem.offset) 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 "" if _name.name.get_raw() != "\xFF\xFF\xFF\xFF": mem.name = str(_name.name).rstrip() return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flg = self._memobj.flags[mem.number] _name = self._memobj.names[mem.number] if mem.empty: _flg.empty = True return _mem.set_raw("\x00" * 8) _flg.set_raw("\x00") _set_freq(_mem.freq, mem.freq) _set_freq(_mem.offset, mem.offset) _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" if mem.name: _name.name = mem.name.ljust(4) else: _name.name = "\xFF\xFF\xFF\xFF" chirp-daily-20170714/chirp/drivers/retevis_rt23.py0000644000016101777760000006704313121151576023067 0ustar jenkinsnogroup00000000000000# Copyright 2017 Jim Unroe # # 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 time import os import struct import re import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ struct memory { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rxtone[2]; lbcd txtone[2]; u8 unknown1; u8 pttid:2, // PTT-ID unknown2:1, signaling:1, // Signaling(ANI) unknown3:1, bcl:1, // Busy Channel Lockout unknown4:2; u8 unknown5:3, highpower:1, // Power Level isnarrow:1, // Bandwidth scan:1, // Scan Add unknown6:2; u8 unknown7; }; #seekto 0x0010; struct memory channels[128]; #seekto 0x0810; struct memory vfo_a; struct memory vfo_b; #seekto 0x0830; struct { u8 unknown_0830_1:4, color:2, // Background Color dst:1, // DTMF Side Tone txsel:1; // Priority TX Channel Select u8 scans:2, // Scan Mode unknown_0831:1, autolk:1, // Auto Key Lock save:1, // Battery Save beep:1, // Key Beep voice:2; // Voice Prompt u8 vfomr_fm:1, // FM Radio Display Mode led:2, // Background Light unknown_0832_2:1, dw:1, // FM Radio Dual Watch name:1, // Display Names vfomr_a:2; // Display Mode A u8 opnset:2, // Power On Message unknown_0833_1:3, dwait:1, // Dual Standby vfomr_b:2; // Display Mode B u8 mrcha; // mr a ch num u8 mrchb; // mr b ch num u8 fmch; // fm radio ch num u8 unknown_0837_1:1, ste:1, // Squelch Tail Eliminate roger:1, // Roger Beep unknown_0837_2:1, vox:4; // VOX u8 step:4, // Step unknown_0838_1:4; u8 squelch; // Squelch u8 tot; // Time Out Timer u8 rptmod:1, // Repeater Mode volmod:2, // Volume Mode rptptt:1, // Repeater PTT Switch rptspk:1, // Repeater Speaker relay:3; // Cross Band Repeater Enable u8 unknown_083C:4, // 0x083C rptrl:4; // Repeater TX Delay u8 pf1:4, // Function Key 1 pf2:4; // Function Key 2 u8 vot; // VOX Delay Time } settings; #seekto 0x0848; struct { char line1[7]; } poweron_msg; struct limit { bbcd lower[2]; bbcd upper[2]; }; #seekto 0x0850; struct { struct limit vhf; struct limit uhf; } limits; #seekto 0x08D0; struct { char name[7]; u8 unknown2[1]; } names[128]; #seekto 0x0D20; u8 usedflags[16]; u8 scanflags[16]; #seekto 0x0FA0; struct { u8 unknown_0FA0_1:4, dispab:1, // select a/b unknown_0FA0_2:3; } settings2; """ CMD_ACK = "\x06" BLOCK_SIZE = 0x10 RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("High", watts=2.50)] RT23_DTCS = sorted(chirp_common.DTCS_CODES + [17, 50, 55, 135, 217, 254, 305, 645, 765]) RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \ ":;<=>?@ !\"#$%&'()*+,-./" LIST_COLOR = ["Blue", "Orange", "Purple"] LIST_LED = ["Off", "On", "Auto"] LIST_OPNSET = ["Full", "Voltage", "Message"] LIST_PFKEY = [ "Radio", "Sub-channel Sent", "Scan", "Alarm", "DTMF", "Squelch Off Momentarily", "Battery Power Indicator", "Tone 1750", "Tone 2100", "Tone 1000", "Tone 1450"] LIST_PTTID = ["Off", "BOT", "EOT", "Both"] LIST_RPTMOD = ["Single", "Double"] LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S", "4.5S"] LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"] LIST_SIGNALING = ["No", "DTMF"] LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)] LIST_TXSEL = ["Edit", "Busy"] LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K", "50.00K"] LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"] LIST_VFOMRFM = ["VFO", "Channel"] LIST_VOICE = ["Off", "Chinese", "English"] LIST_VOLMOD = ["Off", "Sub", "Main"] LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"] LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)] def _rt23_enter_programming_mode(radio): serial = radio.pipe magic = "PROIUAM" exito = False for i in range(0, 5): for j in range(0, len(magic)): time.sleep(0.005) serial.write(magic[j]) ack = serial.read(1) try: if ack == CMD_ACK: exito = True break except: LOG.debug("Attempt #%s, failed, trying again" % i) pass # check if we had EXITO if exito is False: msg = "The radio did not accept program mode after five tries.\n" msg += "Check you interface cable and power cycle your radio." raise errors.RadioError(msg) try: serial.write("\x02") ident = serial.read(8) except: raise errors.RadioError("Error communicating with radio") if not ident.startswith("P31183"): LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _rt23_exit_programming_mode(radio): serial = radio.pipe try: serial.write("E") except: raise errors.RadioError("Radio refused to exit programming mode") def _rt23_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + BLOCK_SIZE + 1) if response[:4] != expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) chunk = response[4:] cs = 0 for byte in chunk[:-1]: cs += ord(byte) if ord(chunk[-1]) != (cs & 0xFF): raise Exception("Block failed checksum!") block_data = chunk[:-1] except: raise errors.RadioError("Failed to read block at %04x" % block_addr) return block_data def _rt23_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE) data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE] cs = 0 for byte in data: cs += ord(byte) data += chr(cs & 0xFF) LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _rt23_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) block = _rt23_read_block(radio, addr, BLOCK_SIZE) if addr == 0 and block.startswith("\xFF" * 6): block = "P31183" + "\xFF" * 10 data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) _rt23_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _rt23_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr in radio._ranges: for addr in range(start_addr, end_addr, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) _rt23_write_block(radio, addr, BLOCK_SIZE) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) == 0x1000: rid = data[0x0000:0x0006] return rid == "P31183" else: return False def _split(rf, f1, f2): """Returns False if the two freqs are in the same band (no split) or True otherwise""" # determine if the two freqs are in the same band for low, high in rf.valid_bands: if f1 >= low and f1 <= high and \ f2 >= low and f2 <= high: # if the two freqs are on the same Band this is not a split return False # if you get here is because the freq pairs are split return True @directory.register class RT23Radio(chirp_common.CloneModeRadio): """RETEVIS RT23""" VENDOR = "Retevis" MODEL = "RT23" BAUD_RATE = 9600 _ranges = [ (0x0000, 0x0EC0), ] _memsize = 0x1000 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True 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 = RT23_CHARSET rf.has_name = True 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 = RT23_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["FM", "NFM"] # 25 KHz, 12.5 KHz. rf.memory_bounds = (1, 128) rf.valid_bands = [ (136000000, 174000000), (400000000, 480000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): """Download from radio""" try: data = do_download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = data self.process_mmap() def sync_out(self): """Upload to radio""" try: do_upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_memory(self, number): mem = chirp_common.Memory() _mem = self._memobj.channels[number-1] _nam = self._memobj.names[number - 1] mem.number = number bitpos = (1 << ((number - 1) % 8)) bytepos = ((number - 1) / 8) _scn = self._memobj.scanflags[bytepos] _usd = self._memobj.usedflags[bytepos] isused = bitpos & int(_usd) isscan = bitpos & int(_scn) if not isused: mem.empty = True return mem mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.empty = True return mem if _mem.get_raw() == ("\xFF" * 16): LOG.debug("Initializing empty memory") _mem.set_raw("\x00" * 16) # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[4] == "\xFF": # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX freq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset != 0: if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): mem.duplex = "split" mem.offset = int(_mem.txfreq) * 10 elif offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 for char in _nam.name: if str(char) == "\xFF": char = " " mem.name += str(char) mem.name = mem.name.rstrip() mem.mode = _mem.isnarrow and "NFM" or "FM" rxtone = txtone = None txtone = self.decode_tone(_mem.txtone) rxtone = self.decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) mem.power = RT23_POWER_LEVELS[_mem.highpower] if not isscan: mem.skip = "S" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) rs = RadioSetting("pttid", "PTT ID", RadioSettingValueList( LIST_PTTID, LIST_PTTID[_mem.pttid])) mem.extra.append(rs) rs = RadioSetting("signaling", "Optional Signaling", RadioSettingValueList(LIST_SIGNALING, LIST_SIGNALING[_mem.signaling])) mem.extra.append(rs) return mem def set_memory(self, mem): LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number)) _mem = self._memobj.channels[mem.number - 1] _nam = self._memobj.names[mem.number - 1] bitpos = (1 << ((mem.number - 1) % 8)) bytepos = ((mem.number - 1) / 8) _scn = self._memobj.scanflags[bytepos] _usd = self._memobj.usedflags[bytepos] if mem.empty: _mem.set_raw("\xFF" * 16) _nam.name = ("\xFF" * 7) _usd &= ~bitpos _scn &= ~bitpos return else: _usd |= bitpos if _mem.get_raw() == ("\xFF" * 16): LOG.debug("Initializing empty memory") _mem.set_raw("\x00" * 16) _scn |= bitpos _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 _namelength = self.get_features().valid_name_length for i in range(_namelength): try: _nam.name[i] = mem.name[i] except IndexError: _nam.name[i] = "\xFF" _mem.scan = mem.skip != "S" if mem.skip == "S": _scn &= ~bitpos else: _scn |= bitpos _mem.isnarrow = mem.mode == "NFM" ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self.encode_tone(_mem.txtone, txmode, txtone, txpol) self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol) _mem.highpower = mem.power == RT23_POWER_LEVELS[1] for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") workmode = RadioSettingGroup("workmode", "Workmode Settings") fmradio = RadioSettingGroup("fmradio", "FM Radio Settings") top = RadioSettings(basic, advanced, other, workmode, fmradio) save = RadioSetting("save", "Battery Saver", RadioSettingValueBoolean(_settings.save)) basic.append(save) vox = RadioSetting("vox", "VOX Gain", RadioSettingValueList( LIST_VOX, LIST_VOX[_settings.vox])) basic.append(vox) squelch = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger( 0, 9, _settings.squelch)) basic.append(squelch) relay = RadioSetting("relay", "Repeater", RadioSettingValueBoolean(_settings.relay)) basic.append(relay) tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList( LIST_TOT, LIST_TOT[_settings.tot])) basic.append(tot) beep = RadioSetting("beep", "Key Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(beep) color = RadioSetting("color", "Background Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_settings.color - 1])) basic.append(color) vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList( LIST_VOT, LIST_VOT[_settings.vot])) basic.append(vot) dwait = RadioSetting("dwait", "Dual Standby", RadioSettingValueBoolean(_settings.dwait)) basic.append(dwait) led = RadioSetting("led", "Background Light", RadioSettingValueList( LIST_LED, LIST_LED[_settings.led])) basic.append(led) voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[_settings.voice])) basic.append(voice) roger = RadioSetting("roger", "Roger Beep", RadioSettingValueBoolean(_settings.roger)) basic.append(roger) autolk = RadioSetting("autolk", "Auto Key Lock", RadioSettingValueBoolean(_settings.autolk)) basic.append(autolk) opnset = RadioSetting("opnset", "Open Mode Set", RadioSettingValueList( LIST_OPNSET, LIST_OPNSET[_settings.opnset])) basic.append(opnset) 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.poweron_msg ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message", RadioSettingValueString( 0, 7, _filter(_msg.line1))) basic.append(ponmsg) scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList( LIST_SCANS, LIST_SCANS[_settings.scans])) basic.append(scans) dw = RadioSetting("dw", "FM Radio Dual Watch", RadioSettingValueBoolean(_settings.dw)) basic.append(dw) name = RadioSetting("name", "Display Names", RadioSettingValueBoolean(_settings.name)) basic.append(name) rptrl = RadioSetting("rptrl", "Repeater TX Delay", RadioSettingValueList(LIST_RPTRL, LIST_RPTRL[ _settings.rptrl])) basic.append(rptrl) rptspk = RadioSetting("rptspk", "Repeater Speaker", RadioSettingValueBoolean(_settings.rptspk)) basic.append(rptspk) rptptt = RadioSetting("rptptt", "Repeater PTT Switch", RadioSettingValueBoolean(_settings.rptptt)) basic.append(rptptt) rptmod = RadioSetting("rptmod", "Repeater Mode", RadioSettingValueList( LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod])) basic.append(rptmod) volmod = RadioSetting("volmod", "Volume Mode", RadioSettingValueList( LIST_VOLMOD, LIST_VOLMOD[_settings.volmod])) basic.append(volmod) dst = RadioSetting("dst", "DTMF Side Tone", RadioSettingValueBoolean(_settings.dst)) basic.append(dst) txsel = RadioSetting("txsel", "Priority TX Channel", RadioSettingValueList( LIST_TXSEL, LIST_TXSEL[_settings.txsel])) basic.append(txsel) ste = RadioSetting("ste", "Squelch Tail Eliminate", RadioSettingValueBoolean(_settings.ste)) basic.append(ste) #advanced if _settings.pf1 > 0x0A: val = 0x00 else: val = _settings.pf1 pf1 = RadioSetting("pf1", "PF1 Key", RadioSettingValueList( LIST_PFKEY, LIST_PFKEY[val])) advanced.append(pf1) if _settings.pf2 > 0x0A: val = 0x00 else: val = _settings.pf2 pf2 = RadioSetting("pf2", "PF2 Key", RadioSettingValueList( LIST_PFKEY, LIST_PFKEY[val])) advanced.append(pf2) # other _limit = str(int(_mem.limits.vhf.lower) / 10) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) rs = RadioSetting("limits.vhf.lower", "VHF low", val) other.append(rs) _limit = str(int(_mem.limits.vhf.upper) / 10) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) rs = RadioSetting("limits.vhf.upper", "VHF high", val) other.append(rs) _limit = str(int(_mem.limits.uhf.lower) / 10) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) rs = RadioSetting("limits.uhf.lower", "UHF low", val) other.append(rs) _limit = str(int(_mem.limits.uhf.upper) / 10) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) rs = RadioSetting("limits.uhf.upper", "UHF high", val) other.append(rs) #work mode vfomr_a = RadioSetting("vfomr_a", "Display Mode A", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a])) workmode.append(vfomr_a) vfomr_b = RadioSetting("vfomr_b", "Display Mode B", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b])) workmode.append(vfomr_b) mrcha = RadioSetting("mrcha", "Channel # A", RadioSettingValueInteger( 1, 128, _settings.mrcha)) workmode.append(mrcha) mrchb = RadioSetting("mrchb", "Channel # B", RadioSettingValueInteger( 1, 128, _settings.mrchb)) workmode.append(mrchb) #fm radio vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode", RadioSettingValueList( LIST_VFOMRFM, LIST_VFOMRFM[ _settings.vfomr_fm])) fmradio.append(vfomr_fm) fmch = RadioSetting("fmch", "FM Radio Channel #", RadioSettingValueInteger( 1, 25, _settings.fmch)) fmradio.append(fmch) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "color": setattr(obj, setting, int(element.value) + 1) elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) in [0x1000, ]: match_size = True # testing the model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/radtel_t18.py0000644000016101777760000003516313131073576022505 0ustar jenkinsnogroup00000000000000# Copyright 2017 Jim Unroe # # 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 time import os import struct import unittest import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rxtone[2]; lbcd txtone[2]; u8 unknown1:1, compander:1, scramble:1, skip:1, highpower:1, narrow:1, unknown2:1, bcl:1; u8 unknown3[3]; } memory[16]; #seekto 0x03C0; struct { u8 unknown1:1, scanmode:1, unknown2:2, voiceprompt:2, batterysaver:1, beep:1; u8 squelchlevel; u8 unused2; u8 timeouttimer; u8 voxlevel; u8 unknown3; u8 unused; u8 voxdelay; } settings; """ CMD_ACK = "\x06" BLOCK_SIZE = 0x08 VOICE_LIST = ["Off", "Chinese", "English"] TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds", "120 seconds", "150 seconds", "180 seconds", "210 seconds", "240 seconds", "270 seconds", "300 seconds"] SCANMODE_LIST = ["Carrier", "Time"] VOXLEVEL_LIST = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"] VOXDELAY_LIST = ["0.5 seconds", "1.0 seconds", "1.5 seconds", "2.0 seconds", "2.5 seconds", "3.0 seconds"] SETTING_LISTS = { "voice": VOICE_LIST, "timeouttimer": TIMEOUTTIMER_LIST, "scanmode": SCANMODE_LIST, "voxlevel": VOXLEVEL_LIST, "voxdelay": VOXDELAY_LIST } def _t18_enter_programming_mode(radio): serial = radio.pipe try: serial.write("\x02") time.sleep(0.1) serial.write("1ROGRAM") ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if not ack: raise errors.RadioError("No response from radio") elif ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x02") ident = serial.read(8) except: raise errors.RadioError("Error communicating with radio") if not ident.startswith("SMP558"): LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x05") response = serial.read(6) except: raise errors.RadioError("Error communicating with radio") if not response == ("\xFF" * 6): LOG.debug(util.hexprint(response)) raise errors.RadioError("Radio returned unexpected response") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _t18_exit_programming_mode(radio): serial = radio.pipe try: serial.write("b") except: raise errors.RadioError("Radio refused to exit programming mode") def _t18_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + BLOCK_SIZE) if response[:4] != expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _t18_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE) data = radio.get_mmap()[block_addr:block_addr + 8] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _t18_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) block = _t18_read_block(radio, addr, BLOCK_SIZE) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) _t18_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _t18_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr in radio._ranges: for addr in range(start_addr, end_addr, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) _t18_write_block(radio, addr, BLOCK_SIZE) _t18_exit_programming_mode(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) == cls._memsize: rid = data[0x03D0:0x03D8] return "P558" in rid else: return False @directory.register class T18Radio(chirp_common.CloneModeRadio): """radtel T18""" VENDOR = "Radtel" MODEL = "T18" BAUD_RATE = 9600 _ranges = [ (0x0000, 0x03F0), ] _memsize = 0x03F0 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.valid_skips = ["", "S"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.can_odd_split = True rf.has_rx_dtcs = True rf.has_ctone = True rf.has_cross = True rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.has_tuning_step = False rf.has_bank = False rf.has_name = False rf.memory_bounds = (1, 16) rf.valid_bands = [(400000000, 470000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): self._mmap = do_download(self) self.process_mmap() def sync_out(self): do_upload(self) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _decode_tone(self, val): val = int(val) if val == 16665: return '', None, None elif val >= 12000: return 'DTCS', val - 12000, 'R' elif val >= 8000: return 'DTCS', val - 8000, 'N' else: return 'Tone', val / 10.0, None def _encode_tone(self, memval, mode, value, pol): if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.duplex = "off" mem.offset = 0 elif int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = not _mem.narrow and "FM" or "NFM" mem.skip = _mem.skip and "S" or "" txtone = self._decode_tone(_mem.txtone) rxtone = self._decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueBoolean(not _mem.bcl)) mem.extra.append(rs) rs = RadioSetting("scramble", "Scramble", RadioSettingValueBoolean(not _mem.scramble)) mem.extra.append(rs) rs = RadioSetting("compander", "Compander", RadioSettingValueBoolean(not _mem.compander)) mem.extra.append(rs) return mem def set_memory(self, mem): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number - 1] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return _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 txtone, rxtone = chirp_common.split_tone_encode(mem) self._encode_tone(_mem.txtone, *txtone) self._encode_tone(_mem.rxtone, *rxtone) _mem.narrow = 'N' in mem.mode _mem.skip = mem.skip == "S" for setting in mem.extra: # NOTE: Only three settings right now, all are inverted setattr(_mem, setting.get_name(), not int(setting.value)) def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("squelchlevel", "Squelch level", RadioSettingValueInteger( 0, 9, _settings.squelchlevel)) basic.append(rs) rs = RadioSetting("timeouttimer", "Timeout timer", RadioSettingValueList( TIMEOUTTIMER_LIST, TIMEOUTTIMER_LIST[ _settings.timeouttimer])) basic.append(rs) rs = RadioSetting("scanmode", "Scan mode", RadioSettingValueList( SCANMODE_LIST, SCANMODE_LIST[_settings.scanmode])) basic.append(rs) rs = RadioSetting("voiceprompt", "Voice prompt", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voiceprompt])) basic.append(rs) rs = RadioSetting("voxlevel", "Vox level", RadioSettingValueList( VOXLEVEL_LIST, VOXLEVEL_LIST[_settings.voxlevel])) basic.append(rs) rs = RadioSetting("voxdelay", "VOX delay", RadioSettingValueList( VOXDELAY_LIST, VOXDELAY_LIST[_settings.voxdelay])) basic.append(rs) rs = RadioSetting("batterysaver", "Battery saver", RadioSettingValueBoolean(_settings.batterysaver)) basic.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == cls._memsize: match_size = True # testing the model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/rfinder.py0000644000016101777760000002133612476257220022165 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 urllib import hashlib import re import logging from math import pi, cos, acos, sin, atan2 from chirp import chirp_common, CHIRP_VERSION LOG = logging.getLogger(__name__) 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""" LOG.debug(user) LOG.debug(pw) args = { "email": urllib.quote_plus(user), "pass": hashlib.new("md5", pw).hexdigest(), "lat": "%7.5f" % coords[0], "lon": "%7.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()])) LOG.debug("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: LOG.error("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: LOG.error("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 LOG.error(traceback.format_exc()) LOG.error("Error in received data, cannot continue") LOG.error(e) LOG.error(self.__cheat) LOG.error(line) 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(): LOG.debug(mem) if __name__ == "__main__": _test() chirp-daily-20170714/chirp/drivers/tk760g.py0000644000016101777760000015374013115722176021561 0ustar jenkinsnogroup00000000000000# Copyright 2016 Pavel Milanes, CO7WT, # # 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 logging import struct import time import sys from chirp import chirp_common, directory, memmap, errors, util, bitwise from textwrap import dedent from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettings LOG = logging.getLogger(__name__) ##### IMPORTANT DATA ########################################## # This radios have a span of # 0x00000 - 0x08000 => Radio Memory / Settings data # 0x08000 - 0x10000 => FIRMWARE... hum... ############################################################### MEM_FORMAT = """ #seekto 0x0000; struct { u8 unknown0[14]; // x00-x0d unknown u8 banks; // x0e how many banks are programmed u8 channels; // x0f how many total channels are programmed // -- ul16 tot; // x10 TOT value: range(15, 600, 15); x04b0 = off u8 tot_rekey; // x12 TOT Re-key value range(0, 60); off= 0 u8 unknown1; // x13 unknown u8 tot_reset; // x14 TOT Re-key value range(0, 60); off= 0 u8 unknown2; // x15 unknows u8 tot_alert; // x16 TOT pre alert: range(0,10); 0 = off u8 unknown3[7]; // x17-x1d unknown u8 sql_level; // x1e SQ reference level u8 battery_save; // Only for portable: FF = off, x32 = on // -- u8 unknown4[10]; // x20 u8 unknown5:3, // x2d c2t:1, // 1 bit clear to transpond: 1-off // This is relative to DTMF / 2-Tone settings unknown6:4; u8 unknown7[5]; // x2b-x2f // -- u8 unknown8[16]; // x30 ? u8 unknown9[16]; // x40 ? u8 unknown10[16]; // x50 ? u8 unknown11[16]; // x60 ? // -- u8 add[16]; // x70-x7f 128 bits corresponding add/skip values // -- u8 unknown12:4, // x80 off_hook_decode:1, // 1 bit off hook decode enabled: 1-off off_hook_horn_alert:1, // 1 bit off hook horn alert: 1-off unknown13:2; u8 unknown14; // x81 u8 unknown15:3, // x82 self_prog:1, // 1 bit Self programming enabled: 1-on clone:1, // 1 bit clone enabled: 1-on firmware_prog:1, // 1 bit firmware programming enabled: 1-on unknown16:1, panel_test:1; // 1 bit panel test enabled u8 unknown17; // x83 u8 unknown18:5, // x84 warn_tone:1, // 1 bit warning tone, enabled: 1-on control_tone:1, // 1 bit control tone (key tone), enabled: 1-on poweron_tone:1; // 1 bit power on tone, enabled: 1-on u8 unknown19[5]; // x85-x89 u8 min_vol; // minimum volume posible: range(0,32); 0 = off u8 tone_vol; // minimum tone volume posible: // xff = continous, range(0, 31) u8 unknown20[4]; // x8c-x8f // -- u8 unknown21[4]; // x90-x93 char poweronmesg[8]; // x94-x9b power on mesg 8 bytes, off is "\FF" * 8 u8 unknown22[4]; // x9c-x9f // -- u8 unknown23[7]; // xa0-xa6 char ident[8]; // xa7-xae radio identification string u8 unknown24; // xaf // -- u8 unknown26[11]; // xaf-xba char lastsoftversion[5]; // software version employed to program the radio } settings; #seekto 0xd0; struct { u8 unknown[4]; char radio[6]; char data[6]; } passwords; #seekto 0x0110; struct { u8 kA; // Portable > Closed circle u8 kDA; // Protable > Triangle to Left u8 kGROUP_DOWN; // Protable > Triangle to Right u8 kGROUP_UP; // Protable > Side 1 u8 kSCN; // Portable > Open Circle u8 kMON; // Protable > Side 2 u8 kFOOT; u8 kCH_UP; u8 kCH_DOWN; u8 kVOL_UP; u8 kVOL_DOWN; u8 unknown30[5]; // -- u8 unknown31[4]; u8 kP_KNOB; // Just portable: channel knob u8 unknown32[11]; } keys; #seekto 0x0140; struct { lbcd tf01_rx[4]; lbcd tf01_tx[4]; u8 tf01_u_rx; u8 tf01_u_tx; lbcd tf02_rx[4]; lbcd tf02_tx[4]; u8 tf02_u_rx; u8 tf02_u_tx; lbcd tf03_rx[4]; lbcd tf03_tx[4]; u8 tf03_u_rx; u8 tf03_u_tx; lbcd tf04_rx[4]; lbcd tf04_tx[4]; u8 tf04_u_rx; u8 tf04_u_tx; lbcd tf05_rx[4]; lbcd tf05_tx[4]; u8 tf05_u_rx; u8 tf05_u_tx; lbcd tf06_rx[4]; lbcd tf06_tx[4]; u8 tf06_u_rx; u8 tf06_u_tx; lbcd tf07_rx[4]; lbcd tf07_tx[4]; u8 tf07_u_rx; u8 tf07_u_tx; lbcd tf08_rx[4]; lbcd tf08_tx[4]; u8 tf08_u_rx; u8 tf08_u_tx; lbcd tf09_rx[4]; lbcd tf09_tx[4]; u8 tf09_u_rx; u8 tf09_u_tx; lbcd tf10_rx[4]; lbcd tf10_tx[4]; u8 tf10_u_rx; u8 tf10_u_tx; lbcd tf11_rx[4]; lbcd tf11_tx[4]; u8 tf11_u_rx; u8 tf11_u_tx; lbcd tf12_rx[4]; lbcd tf12_tx[4]; u8 tf12_u_rx; u8 tf12_u_tx; lbcd tf13_rx[4]; lbcd tf13_tx[4]; u8 tf13_u_rx; u8 tf13_u_tx; lbcd tf14_rx[4]; lbcd tf14_tx[4]; u8 tf14_u_rx; u8 tf14_u_tx; lbcd tf15_rx[4]; lbcd tf15_tx[4]; u8 tf15_u_rx; u8 tf15_u_tx; lbcd tf16_rx[4]; lbcd tf16_tx[4]; u8 tf16_u_rx; u8 tf16_u_tx; } test_freq; #seekto 0x200; struct { char line1[32]; char line2[32]; } message; #seekto 0x2000; struct { u8 bnumb; // mem number u8 bank; // to which bank it belongs char name[8]; // name 8 chars u8 unknown20[2]; // unknown yet lbcd rxfreq[4]; // rx freq // -- lbcd txfreq[4]; // tx freq u8 rx_unkw; // unknown yet u8 tx_unkw; // unknown yet ul16 rx_tone; // rx tone ul16 tx_tone; // tx tone u8 unknown23[5]; // unknown yet u8 signaling; // xFF = off, x30 DTMF, x31 2-Tone // See the zone on x7000 // -- u8 ptt_id:2, // ??? BOT = 0, EOT = 1, Both = 2, NONE = 3 beat_shift:1, // 1 = off unknown26:2 // ??? power:1, // power: 0 low / 1 high compander:1, // 1 = off wide:1; // wide 1 / 0 narrow u8 unknown27:6, // ??? busy_lock:1, // 1 = off unknown28:1; // ??? u8 unknown29[14]; // unknown yet } memory[128]; #seekto 0x5900; struct { char model[8]; u8 unknown50[4]; char type[2]; u8 unknown51[2]; // -- char serial[8]; u8 unknown52[8]; } id; #seekto 0x6000; struct { u8 code[8]; u8 unknown60[7]; u8 count; } bot[128]; #seekto 0x6800; struct { u8 code[8]; u8 unknown61[7]; u8 count; } eot[128]; #seekto 0x7000; struct { lbcd dt2_id[5]; // DTMF lbcd ID (000-9999999999) // 2-Tone = "11 f1 ff ff ff" ??? // None = "00 f0 ff ff ff" } dtmf; """ MEM_SIZE = 0x8000 # 32,768 bytes BLOCK_SIZE = 256 BLOCKS = MEM_SIZE / BLOCK_SIZE MEM_BLOCKS = range(0, BLOCKS) # define and empty block of data, as it will be used a lot in this code EMPTY_BLOCK = "\xFF" * 256 RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f) ACK_CMD = "\x06" POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), chirp_common.PowerLevel("High", watts=5)] MODES = ["NFM", "FM"] # 12.5 / 25 Khz VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "_-*()/\-+=)" SKIP_VALUES = ["", "S"] TONES = chirp_common.TONES # TONES.remove(254.1) DTCS_CODES = chirp_common.DTCS_CODES TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)] TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)] TOT_REKEY = ["off"] + ["%s" % x for x in range(1, 61)] TOT_RESET = ["off"] + ["%s" % x for x in range(1, 16)] VOL = ["off"] + ["%s" % x for x in range(1, 32)] TVOL = ["%s" % x for x in range(0, 33)] TVOL[32] = "Continous" SQL = ["off"] + ["%s" % x for x in range(1, 10)] ## BOT = 0, EOT = 1, Both = 2, NONE = 3 #PTTID = ["BOT", "EOT", "Both", "none"] # For debugging purposes debug = False KEYS = { 0x33: "Display character", 0x35: "Home Channel", # Posible portable only, chek it 0x37: "CH down", 0x38: "CH up", 0x39: "Key lock", 0x3a: "Lamp", # Portable only 0x3b: "Public address", 0x3c: "Reverse", # Just in updated firmwares (768G) 0x3d: "Horn alert", 0x3e: "Selectable QT", # Just in updated firmwares (768G) 0x3f: "2-tone encode", 0x40: "Monitor A: open mommentary", 0x41: "Monitor B: Open Toggle", 0x42: "Monitor C: Carrier mommentary", 0x43: "Monitor D: Carrier toogle", 0x44: "Operator selectable tone", 0x45: "Redial", 0x46: "RF Power Low", # portable only ? 0x47: "Scan", 0x48: "Scan del/add", 0x4a: "GROUP down", 0x4b: "GROUP up", #0x4e: "Tone off (Experimental)", # undocumented !!!! 0x4f: "None", 0x50: "VOL down", 0x51: "VOL up", 0x52: "Talk around", 0x5d: "AUX", 0xa1: "Channel Up/Down" # Knob for portables only } def _raw_recv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: raise errors.RadioError("Error reading data from radio") # DEBUG if debug is True: LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data))) return data def _raw_send(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) except: raise errors.RadioError("Error sending data to radio") # DEBUG if debug is True: LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data))) def _close_radio(radio): """Get the radio out of program mode""" _raw_send(radio, "\x45") def _checksum(data): """the radio block checksum algorithm""" cs = 0 for byte in data: cs += ord(byte) return cs % 256 def _send(radio, frame): """Generic send data to the radio""" _raw_send(radio, frame) def _make_frame(cmd, addr): """Pack the info in the format it likes""" return struct.pack(">BH", ord(cmd), addr) def _handshake(radio, msg=""): """Make a full handshake""" # send ACK _raw_send(radio, ACK_CMD) # receive ACK ack = _raw_recv(radio, 1) # check ACK if ack != ACK_CMD: _close_radio(radio) mesg = "Handshake failed " + msg # DEBUG LOG.debug(mesg) raise Exception(mesg) def _check_write_ack(r, ack, addr): """Process the ack from the write process this is half handshake needed in tx data block""" # all ok if ack == ACK_CMD: return True # Explicit BAD checksum if ack == "\x15": _close_radio(r) raise errors.RadioError( "Bad checksum in block %02x write" % addr) # everything else _close_radio(r) raise errors.RadioError( "Problem with the ack to block %02x write, ack %03i" % (addr, int(ack))) def _recv(radio): """Receive data from the radio, 258 bytes split in (cmd, data, checksum) checking the checksum to be correct, and returning just 256 bytes of data or false if short empty block""" rxdata = _raw_recv(radio, BLOCK_SIZE + 2) # when the RX block has two bytes and the first is \x5A # then the block is all \xFF if len(rxdata) == 2 and rxdata[0] == "\x5A": # fast work in linux has to make the handshake, slow windows don't if not sys.platform in ["win32", "cygwin"]: _handshake(radio, "short block") return False elif len(rxdata) != 258: # not the amount of data we want msg = "The radio send %d bytes, we need 258" % len(rxdata) # DEBUG LOG.error(msg) raise errors.RadioError(msg) else: rcs = ord(rxdata[-1]) data = rxdata[1:-1] ccs = _checksum(data) if rcs != ccs: _close_radio(radio) raise errors.RadioError( "Block Checksum Error! real %02x, calculated %02x" % (rcs, ccs)) _handshake(radio, "after checksum") return data def _open_radio(radio, status): """Open the radio into program mode and check if it's the correct model""" # linux min is 0.13, win min is 0.25; set to bigger to be safe radio.pipe.timeout = 0.4 radio.pipe.parity = "E" # DEBUG LOG.debug("Entering program mode.") # max tries tries = 10 # UI status.cur = 0 status.max = tries status.msg = "Entering program mode..." # try a few times to get the radio into program mode exito = False for i in range(0, tries): _raw_send(radio, "PROGRAM") ack = _raw_recv(radio, 1) if ack != ACK_CMD: # DEBUG LOG.debug("Try %s failed, traying again..." % i) time.sleep(0.25) else: exito = True break status.cur += 1 radio.status_fn(status) if exito is False: _close_radio(radio) LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries) raise errors.RadioError("The radio doesn't accept program mode") # DEBUG LOG.debug("Received ACK to the PROGRAM command, send ID query.") _raw_send(radio, "\x02") rid = _raw_recv(radio, 8) if not (radio.TYPE in rid): # bad response, properly close the radio before exception _close_radio(radio) # DEBUG LOG.debug("Incorrect model ID:") LOG.debug(util.hexprint(rid)) raise errors.RadioError( "Incorrect model ID, got %s, it not contains %s" % (rid.strip("\xff"), radio.TYPE)) # DEBUG LOG.debug("Full ident string is:") LOG.debug(util.hexprint(rid)) _handshake(radio) status.msg = "Radio ident success!" radio.status_fn(status) # a pause time.sleep(1) def do_download(radio): """ The download function """ # UI progress status = chirp_common.Status() data = "" count = 0 # open the radio _open_radio(radio, status) # reset UI data status.cur = 0 status.max = MEM_SIZE / 256 status.msg = "Cloning from radio..." radio.status_fn(status) # set the timeout and if windows keep it bigger if sys.platform in ["win32", "cygwin"]: # bigger timeout radio.pipe.timeout = 0.55 else: # Linux can keep up, MAC? radio.pipe.timeout = 0.05 # DEBUG LOG.debug("Starting the download from radio") for addr in MEM_BLOCKS: # send request, but before flush the rx buffer radio.pipe.flush() _send(radio, _make_frame("R", addr)) # now we get the data d = _recv(radio) # if empty block, it return false # aka we asume a empty 256 xFF block if d is False: d = EMPTY_BLOCK data += d # UI Update status.cur = count radio.status_fn(status) count += 1 _close_radio(radio) return memmap.MemoryMap(data) def do_upload(radio): """ The upload function """ # UI progress status = chirp_common.Status() data = "" count = 0 # open the radio _open_radio(radio, status) # update UI status.cur = 0 status.max = MEM_SIZE / 256 status.msg = "Cloning to radio..." radio.status_fn(status) # the default for the original soft as measured radio.pipe.timeout = 0.5 # DEBUG LOG.debug("Starting the upload to the radio") count = 0 raddr = 0 for addr in MEM_BLOCKS: # this is the data block to write data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE] # The blocks from x59-x5F are NOT programmable # The blocks from x11-x1F are writed only if not empty if addr in RO_BLOCKS: # checking if in the range of optional blocks if addr >= 0x10 and addr <= 0x1F: # block is empty ? if data == EMPTY_BLOCK: # no write of this block # but we have to continue updating the counters count += 1 raddr = count * 256 continue else: count += 1 raddr = count * 256 continue if data == EMPTY_BLOCK: frame = _make_frame("Z", addr) + "\xFF" else: cs = _checksum(data) frame = _make_frame("W", addr) + data + chr(cs) _send(radio, frame) # get the ACK ack = _raw_recv(radio, 1) _check_write_ack(radio, ack, addr) # DEBUG LOG.debug("Sending block %02x" % addr) # UI Update status.cur = count radio.status_fn(status) count += 1 raddr = count * 256 _close_radio(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0xA7:0xAE] if (rid in cls.VARIANTS): # correct model return True else: return False class Kenwood60GBankModel(chirp_common.BankModel): """Testing the bank model on kennwood""" channelAlwaysHasBank = True def get_num_mappings(self): return self._radio._num_banks def get_mappings(self): banks = [] for i in range(0, self._radio._num_banks): bindex = i + 1 bank = self._radio._bclass(self, i, "%03i" % bindex) bank.index = i banks.append(bank) return banks def add_memory_to_mapping(self, memory, bank): self._radio._set_bank(memory.number, bank.index) def remove_memory_from_mapping(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)) # We can't "Remove" it for good # the kenwood paradigm don't allow it # instead we move it to bank 0 self._radio._set_bank(memory.number, 0) def get_mapping_memories(self, bank): memories = [] for i in range(0, self._radio._upper): if self._radio._get_bank(i) == bank.index: memories.append(self._radio.get_memory(i)) return memories def get_memory_mappings(self, memory): index = self._radio._get_bank(memory.number) return [self.get_mappings()[index]] class memBank(chirp_common.Bank): """A bank model for kenwood""" # Integral index of the bank (not to be confused with per-memory # bank indexes index = 0 class Kenwood_Serie_60G(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Kenwood Serie 60G Radios base class""" VENDOR = "Kenwood" BAUD_RATE = 9600 _memsize = MEM_SIZE NAME_LENGTH = 8 _range = [136000000, 162000000] _upper = 128 _chs_progs = 0 _num_banks = 128 _bclass = memBank _kind = "" VARIANT = "" MODEL = "" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This driver is experimental; not all features have been ' 'implemented, but it has those features most used by hams.\n' '\n' 'This radios are able to work slightly outside the OEM ' 'frequency limits. After testing, the limit in Chirp has ' 'been set 4% outside the OEM limit. This allows you to use ' 'some models on the ham bands.\n' '\n' 'Nevertheless, each radio has its own hardware limits and ' 'your mileage may vary.\n' ) rp.pre_download = _(dedent("""\ Follow this instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio (unblock it if password protected) 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio (unblock it if password protected) 4 - Do the upload of your radio data """)) return rp def get_features(self): """Return information about this radio's features""" rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = True rf.has_tuning_step = False rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = MODES rf.valid_duplexes = ["", "-", "+", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_characters = VALID_CHARS rf.valid_skips = SKIP_VALUES rf.valid_dtcs_codes = DTCS_CODES rf.valid_bands = [self._range] rf.valid_name_length = 8 rf.memory_bounds = (1, self._upper) return rf def _fill(self, offset, data): """Fill an specified area of the memmap with the passed data""" for addr in range(0, len(data)): self._mmap[offset + addr] = data[addr] def _prep_data(self): """Prepare the areas in the memmap to do a consistend write it has to make an update on the x300 area with banks and channel info; other in the x1000 with banks and channel counts and a last one in x7000 with flag data""" rchs = 0 data = dict() # sorting the data for ch in range(0, self._upper): mem = self._memobj.memory[ch] bnumb = int(mem.bnumb) bank = int(mem.bank) if bnumb != 255 and (bank != 255 and bank != 0): try: data[bank].append(ch) except: data[bank] = list() data[bank].append(ch) data[bank].sort() # counting the real channels rchs = rchs + 1 # updating the channel/bank count self._memobj.settings.channels = rchs self._chs_progs = rchs self._memobj.settings.banks = len(data) # building the data for the memmap fdata = "" for k, v in data.iteritems(): # posible bad data if k == 0: k = 1 raise errors.InvalidValueError( "Invalid bank value '%k', bad data in the image? \ Trying to fix this, review your bank data!" % k) c = 1 for i in v: fdata += chr(k) + chr(c) + chr(k - 1) + chr(i) c = c + 1 # fill to match a full 256 bytes block fdata += (len(fdata) % 256) * "\xFF" # updating the data in the memmap [x300] self._fill(0x300, fdata) # update the info in x1000; it has 2 bytes with # x00 = bank , x01 = bank's channel count # the rest of the 14 bytes are \xff bdata = "" for i in range(1, len(data) + 1): line = chr(i) + chr(len(data[i])) line += "\xff" * 14 bdata += line # fill to match a full 256 bytes block bdata += (256 - (len(bdata)) % 256) * "\xFF" # fill to match the whole area bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK # updating the data in the memmap [x1000] self._fill(0x1000, bdata) # DTMF id for each channel, 5 bytes lbcd at x7000 # ############## TODO ################### fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \ "\xff" * (5 * (self._upper - self._chs_progs)) # write it # updating the data in the memmap [x7000] self._fill(0x7000, fldata) def _set_variant(self): """Select and set the correct variables for the class acording to the correct variant of the radio""" rid = self._mmap[0xA7:0xAE] # indentify the radio variant and set the enviroment to it's values try: self._upper, low, high, self._kind = self.VARIANTS[rid] # Frequency ranges: some model/variants are able to work the near # ham bands, even if they are outside the OEM ranges. # By experimentation we found that 4% at the edges is in most # cases safe and will cover the near ham bands in full self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04] # setting the bank data in the features, 8 & 16 CH dont have banks if self._upper < 32: rf = chirp_common.RadioFeatures() rf.has_bank = False # put the VARIANT in the class, clean the model / CHs / Type # in the same layout as the KPG program self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: " # In the OEM string we show the real OEM ranges self._VARIANT += self._kind + ", %d - %d MHz" % (low, high) except KeyError: LOG.debug("Wrong Kenwood radio, ID or unknown variant") LOG.debug(util.hexprint(rid)) raise errors.RadioError( "Wrong Kenwood radio, ID or unknown variant, see LOG output.") return False def sync_in(self): """Do a download of the radio eeprom""" self._mmap = do_download(self) self.process_mmap() def sync_out(self): """Do an upload to the radio eeprom""" # chirp signature on the eprom ;-) sign = "Chirp" self._fill(0xbb, sign) try: self._prep_data() do_upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def process_mmap(self): """Process the memory object""" # how many channels are programed self._chs_progs = ord(self._mmap[15]) # load the memobj self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # to set the vars on the class to the correct ones self._set_variant() def get_raw_memory(self, number): """Return a raw representation of the memory object, which is very helpful for development""" return repr(self._memobj.memory[number]) def _decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" val = int(val) if val == 65535: return '', None, None elif val >= 0x2800: code = int("%03o" % (val & 0x07FF)) pol = (val & 0x8000) and "R" or "N" return 'DTCS', code, pol else: a = val / 10.0 return 'Tone', a, None def _encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval.set_raw("\xff\xff") elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': val = int("%i" % value, 8) + 0x2800 if pol == "R": val += 0xA000 memval.set_value(val) else: raise Exception("Internal error: invalid mode `%s'" % mode) def _get_scan(self, chan): """Get the channel scan status from the 16 bytes array on the eeprom then from the bits on the byte, return '' or 'S' as needed""" result = "S" byte = int(chan/8) bit = chan % 8 res = self._memobj.settings.add[byte] & (pow(2, bit)) if res > 0: result = "" return result def _set_scan(self, chan, value): """Set the channel scan status from UI to the mem_map""" byte = int(chan/8) bit = chan % 8 # get the actual value to see if I need to change anything actual = self._get_scan(chan) if actual != value: # I have to flip the value rbyte = self._memobj.settings.add[byte] rbyte = rbyte ^ pow(2, bit) self._memobj.settings.add[byte] = rbyte def get_memory(self, number): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[number - 1] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # Memory number mem.number = number # this radio has a setting about the amount of real chans of the 128 # olso in the channel has xff on the Rx freq it's empty if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"): mem.empty = True # but is not enough, you have to crear the memory in the mmap # to get it ready for the sync_out process _mem.set_raw("\xFF" * 48) return mem # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[16] == "\xFF": # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX feq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 # name TAG of the channel mem.name = str(_mem.name).rstrip() # power mem.power = POWER_LEVELS[_mem.power] # wide/marrow mem.mode = MODES[_mem.wide] # skip mem.skip = self._get_scan(number - 1) # tone data rxtone = txtone = None txtone = self._decode_tone(_mem.tx_tone) rxtone = self._decode_tone(_mem.rx_tone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra # bank and number in the channel mem.extra = RadioSettingGroup("extra", "Extra") # validate bank b = int(_mem.bank) if b > 127 or b == 0: _mem.bank = b = 1 bank = RadioSetting("bank", "Bank it belongs", RadioSettingValueInteger(1, 128, b)) mem.extra.append(bank) # validate bnumb if int(_mem.bnumb) > 127: _mem.bank = mem.number bnumb = RadioSetting("bnumb", "Ch number in the bank", RadioSettingValueInteger(0, 127, _mem.bnumb)) mem.extra.append(bnumb) bs = RadioSetting("beat_shift", "Beat shift", RadioSettingValueBoolean( not bool(_mem.beat_shift))) mem.extra.append(bs) cp = RadioSetting("compander", "Compander", RadioSettingValueBoolean( not bool(_mem.compander))) mem.extra.append(cp) bl = RadioSetting("busy_lock", "Busy Channel lock", RadioSettingValueBoolean( not bool(_mem.busy_lock))) mem.extra.append(bl) return mem def set_memory(self, mem): """Set the memory data in the eeprom img from the UI not ready yet, so it will return as is""" # get the eprom representation of this channel _mem = self._memobj.memory[mem.number - 1] # if empty memmory if mem.empty: _mem.set_raw("\xFF" * 48) return # frequency _mem.rxfreq = mem.freq / 10 # this are a mistery yet, but so falr there is no impact # whit this default values for new channels if int(_mem.rx_unkw) == 0xff: _mem.rx_unkw = 0x35 _mem.tx_unkw = 0x32 # duplex if mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": for byte in _mem.txfreq: byte.set_raw("\xFF") else: _mem.txfreq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self._encode_tone(_mem.tx_tone, txmode, txtone, txpol) self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol) # name TAG of the channel _namelength = self.get_features().valid_name_length for i in range(_namelength): try: _mem.name[i] = mem.name[i] except IndexError: _mem.name[i] = "\x20" # power # default power is low if mem.power is None: mem.power = POWER_LEVELS[0] _mem.power = POWER_LEVELS.index(mem.power) # wide/marrow _mem.wide = MODES.index(mem.mode) # scan add property self._set_scan(mem.number - 1, mem.skip) # bank and number in the channel if int(_mem.bnumb) == 0xff: _mem.bnumb = mem.number - 1 _mem.bank = 1 # extra settings for setting in mem.extra: if setting != "bank" or setting != "bnumb": setattr(_mem, setting.get_name(), not bool(setting.value)) # all data get sync after channel mod self._prep_data() return mem @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" sett = self._memobj.settings mess = self._memobj.message keys = self._memobj.keys idm = self._memobj.id passwd = self._memobj.passwords # basic features of the radio basic = RadioSettingGroup("basic", "Basic Settings") # dealer settings dealer = RadioSettingGroup("dealer", "Dealer Settings") # buttons fkeys = RadioSettingGroup("keys", "Front keys config") # TODO / PLANED # adjust feqs #freqs = RadioSettingGroup("freqs", "Adjust Frequencies") top = RadioSettings(basic, dealer, fkeys) # Basic tot = RadioSetting("settings.tot", "Time Out Timer (TOT)", RadioSettingValueList(TOT, TOT[ TOT.index(str(int(sett.tot)))])) basic.append(tot) totalert = RadioSetting("settings.tot_alert", "TOT pre alert", RadioSettingValueList(TOT_PRE, TOT_PRE[int(sett.tot_alert)])) basic.append(totalert) totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time", RadioSettingValueList(TOT_REKEY, TOT_REKEY[int(sett.tot_rekey)])) basic.append(totrekey) totreset = RadioSetting("settings.tot_reset", "TOT reset time", RadioSettingValueList(TOT_RESET, TOT_RESET[int(sett.tot_reset)])) basic.append(totreset) # this feature is for mobile only if self.TYPE[0] == "M": minvol = RadioSetting("settings.min_vol", "Minimum volume", RadioSettingValueList(VOL, VOL[int(sett.min_vol)])) basic.append(minvol) tv = int(sett.tone_vol) if tv == 255: tv = 32 tvol = RadioSetting("settings.tone_vol", "Minimum tone volume", RadioSettingValueList(TVOL, TVOL[tv])) basic.append(tvol) sql = RadioSetting("settings.sql_level", "SQL Ref Level", RadioSettingValueList( SQL, SQL[int(sett.sql_level)])) basic.append(sql) #c2t = RadioSetting("settings.c2t", "Clear to Transpond", #RadioSettingValueBoolean(not sett.c2t)) #basic.append(c2t) ptone = RadioSetting("settings.poweron_tone", "Power On tone", RadioSettingValueBoolean(sett.poweron_tone)) basic.append(ptone) ctone = RadioSetting("settings.control_tone", "Control (key) tone", RadioSettingValueBoolean(sett.control_tone)) basic.append(ctone) wtone = RadioSetting("settings.warn_tone", "Warning tone", RadioSettingValueBoolean(sett.warn_tone)) basic.append(wtone) # Save Battery only for portables? if self.TYPE[0] == "P": bs = int(sett.battery_save) == 0x32 and True or False bsave = RadioSetting("settings.battery_save", "Battery Saver", RadioSettingValueBoolean(bs)) basic.append(bsave) ponm = str(sett.poweronmesg).strip("\xff") pom = RadioSetting("settings.poweronmesg", "Power on message", RadioSettingValueString(0, 8, ponm, False)) basic.append(pom) # dealer valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC mstr = "".join([c for c in self._VARIANT if c in valid_chars]) val = RadioSettingValueString(0, 35, mstr) val.set_mutable(False) mod = RadioSetting("not.mod", "Radio Version", val) dealer.append(mod) sn = str(idm.serial).strip(" \xff") val = RadioSettingValueString(0, 8, sn) val.set_mutable(False) serial = RadioSetting("not.serial", "Serial number", val) dealer.append(serial) svp = str(sett.lastsoftversion).strip(" \xff") val = RadioSettingValueString(0, 5, svp) val.set_mutable(False) sver = RadioSetting("not.softver", "Software Version", val) dealer.append(sver) l1 = str(mess.line1).strip(" \xff") line1 = RadioSetting("message.line1", "Comment 1", RadioSettingValueString(0, 32, l1)) dealer.append(line1) l2 = str(mess.line2).strip(" \xff") line2 = RadioSetting("message.line2", "Comment 2", RadioSettingValueString(0, 32, l2)) dealer.append(line2) sprog = RadioSetting("settings.self_prog", "Self program", RadioSettingValueBoolean(sett.self_prog)) dealer.append(sprog) clone = RadioSetting("settings.clone", "Allow clone", RadioSettingValueBoolean(sett.clone)) dealer.append(clone) panel = RadioSetting("settings.panel_test", "Panel Test", RadioSettingValueBoolean(sett.panel_test)) dealer.append(panel) fmw = RadioSetting("settings.firmware_prog", "Firmware program", RadioSettingValueBoolean(sett.firmware_prog)) dealer.append(fmw) # front keys # The Mobile only parameters are wraped here if self.TYPE[0] == "M": vu = RadioSetting("keys.kVOL_UP", "VOL UP", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kVOL_UP))])) fkeys.append(vu) vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kVOL_DOWN))])) fkeys.append(vd) chu = RadioSetting("keys.kCH_UP", "CH UP", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kCH_UP))])) fkeys.append(chu) chd = RadioSetting("keys.kCH_DOWN", "CH DOWN", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kCH_DOWN))])) fkeys.append(chd) foot = RadioSetting("keys.kFOOT", "Foot switch", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kCH_DOWN))])) fkeys.append(foot) # this is the common buttons for all # 260G model don't have the front keys if not "P2600" in self.TYPE: scn_name = "SCN" if self.TYPE[0] == "P": scn_name = "Open Circle" scn = RadioSetting("keys.kSCN", scn_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kSCN))])) fkeys.append(scn) a_name = "A" if self.TYPE[0] == "P": a_name = "Closed circle" a = RadioSetting("keys.kA", a_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kA))])) fkeys.append(a) da_name = "D/A" if self.TYPE[0] == "P": da_name = "< key" da = RadioSetting("keys.kDA", da_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kDA))])) fkeys.append(da) gu_name = "Triangle up" if self.TYPE[0] == "P": gu_name = "Side 1" gu = RadioSetting("keys.kGROUP_UP", gu_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kGROUP_UP))])) fkeys.append(gu) # Side keys on portables gd_name = "Triangle Down" if self.TYPE[0] == "P": gd_name = "> key" gd = RadioSetting("keys.kGROUP_DOWN", gd_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kGROUP_DOWN))])) fkeys.append(gd) mon_name = "MON" if self.TYPE[0] == "P": mon_name = "Side 2" mon = RadioSetting("keys.kMON", mon_name, RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(keys.kMON))])) fkeys.append(mon) return top def set_settings(self, settings): """Translate the settings in the UI into bit in the mem_struct I don't understand well the method used in many drivers so, I used mine, ugly but works ok""" mobj = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue # Let's roll the ball if "." in element.get_name(): inter, setting = element.get_name().split(".") # you must ignore the settings with "not" # this are READ ONLY attributes if inter == "not": continue obj = getattr(mobj, inter) value = element.value # integers case + special case if setting in ["tot", "tot_alert", "min_vol", "tone_vol", "sql_level", "tot_rekey", "tot_reset"]: # catching the "off" values as zero try: value = int(value) except: value = 0 # tot case step 15 if setting == "tot": value = value * 15 # off is special if value == 0: value = 0x4b0 # Caso tone_vol if setting == "tone_vol": # off is special if value == 32: value = 0xff # Bool types + inverted if setting in ["c2t", "poweron_tone", "control_tone", "warn_tone", "battery_save", "self_prog", "clone", "panel_test"]: value = bool(value) # this cases are inverted if setting == "c2t": value = not value # case battery save is special if setting == "battery_save": if bool(value) is True: value = 0x32 else: value = 0xff # String cases if setting in ["poweronmesg", "line1", "line2"]: # some vars value = str(value) just = 8 # lines with 32 if "line" in setting: just = 32 # empty case if len(value) == 0: value = "\xff" * just else: value = value.ljust(just) # case keys, with special config if inter == "keys": value = KEYS.keys()[KEYS.values().index(str(value))] # Apply al configs done setattr(obj, setting, value) def get_bank_model(self): """Pass the bank model to the UI part""" rf = self.get_features() if rf.has_bank is True: return Kenwood60GBankModel(self) else: return None def _get_bank(self, loc): """Get the bank data for a specific channel""" mem = self._memobj.memory[loc - 1] bank = int(mem.bank) - 1 if bank > self._num_banks or bank < 1: # all channels must belong to a bank, even with just 1 bank return 0 else: return bank def _set_bank(self, loc, bank): """Set the bank data for a specific channel""" try: b = int(bank) if b > 127: b = 0 mem = self._memobj.memory[loc - 1] mem.bank = b + 1 except: msg = "You can't have a channel without a bank, click another bank" raise errors.InvalidDataError(msg) # This kenwwood family is known as "60-G Serie" # all this radios ending in G are compatible: # # Portables VHF TK-260G/270G/272G/278G # Portables UHF TK-360G/370G/372G/378G/388G # # Mobiles VHF TK-760G/762G/768G # Mobiles VHF TK-860G/862G/868G # # WARNING !!!! Radios With Password in the data section ############### # # When a radio has a data password (aka to program it) the last byte (#8) # in the id code change from \xf1 to \xb1; so we remove this last byte # from the identification procedures and variants. # # This effectively render the data password USELESS even if set. # Translation: Chirps will read and write password protected radios # with no problem. @directory.register class TK868G_Radios(Kenwood_Serie_60G): """Kenwood TK-868G Radio M/C""" MODEL = "TK-868G" TYPE = "M8680" VARIANTS = { "M8680\x18\xff": (8, 400, 490, "M"), "M8680;\xff": (128, 350, 390, "C1"), "M86808\xff": (128, 400, 430, "C2"), "M86806\xff": (128, 450, 490, "C3"), } @directory.register class TK862G_Radios(Kenwood_Serie_60G): """Kenwood TK-862G Radio K/E/(N)E""" MODEL = "TK-862G" TYPE = "M8620" VARIANTS = { "M8620\x06\xff": (8, 450, 490, "K"), "M8620\x07\xff": (8, 485, 512, "K2"), "M8620&\xff": (8, 440, 470, "E"), "M8620V\xff": (8, 440, 470, "(N)E"), } @directory.register class TK860G_Radios(Kenwood_Serie_60G): """Kenwood TK-860G Radio K""" MODEL = "TK-860G" TYPE = "M8600" VARIANTS = { "M8600\x08\xff": (128, 400, 430, "K"), "M8600\x06\xff": (128, 450, 490, "K1"), "M8600\x07\xff": (128, 485, 512, "K2"), "M8600\x18\xff": (128, 400, 430, "M"), "M8600\x16\xff": (128, 450, 490, "M1"), "M8600\x17\xff": (128, 485, 520, "M2"), } @directory.register class TK768G_Radios(Kenwood_Serie_60G): """Kenwood TK-768G Radios [M/C]""" MODEL = "TK-768G" TYPE = "M7680" # Note that 8 CH don't have banks VARIANTS = { "M7680\x15\xff": (8, 136, 162, "M2"), "M7680\x14\xff": (8, 148, 174, "M"), "M76805\xff": (128, 136, 162, "C2"), "M76804\xff": (128, 148, 174, "C"), } @directory.register class TK762G_Radios(Kenwood_Serie_60G): """Kenwood TK-762G Radios [K/E/NE]""" MODEL = "TK-762G" TYPE = "M7620" # Note that 8 CH don't have banks VARIANTS = { "M7620\x05\xff": (8, 136, 162, "K2"), "M7620\x04\xff": (8, 148, 172, "K"), "M7620$\xff": (8, 148, 172, "E"), "M7620T\xff": (8, 148, 172, "NE"), } @directory.register class TK760G_Radios(Kenwood_Serie_60G): """Kenwood TK-760G Radios [K/M/(N)E]""" MODEL = "TK-760G" TYPE = "M7600" VARIANTS = { "M7600\x05\xff": (128, 136, 162, "K2"), "M7600\x04\xff": (128, 148, 174, "K"), "M7600\x14\xff": (128, 148, 174, "M"), "M7600T\xff": (128, 148, 174, "NE") } @directory.register class TK388G_Radios(Kenwood_Serie_60G): """Kenwood TK-388 Radio [K/E/M/NE]""" MODEL = "TK-388G" TYPE = "P3880" VARIANTS = { "P3880\x1b\xff": (128, 350, 370, "M") } @directory.register class TK378G_Radios(Kenwood_Serie_60G): """Kenwood TK-378 Radio [K/E/M/NE]""" MODEL = "TK-378G" TYPE = "P3780" VARIANTS = { "P3780\x16\xff": (16, 450, 470, "M"), "P3780\x17\xff": (16, 400, 420, "M1"), "P3780\x36\xff": (128, 490, 512, "C"), "P3780\x39\xff": (128, 403, 430, "C1") } @directory.register class TK372G_Radios(Kenwood_Serie_60G): """Kenwood TK-372 Radio [K/E/M/NE]""" MODEL = "TK-372G" TYPE = "P3720" VARIANTS = { "P3720\x06\xff": (32, 450, 470, "K"), "P3720\x07\xff": (32, 470, 490, "K1"), "P3720\x08\xff": (32, 490, 512, "K2"), "P3720\x09\xff": (32, 403, 430, "K3") } @directory.register class TK370G_Radios(Kenwood_Serie_60G): """Kenwood TK-370 Radio [K/E/M/NE]""" MODEL = "TK-370G" TYPE = "P3700" VARIANTS = { "P3700\x06\xff": (128, 450, 470, "K"), "P3700\x07\xff": (128, 470, 490, "K1"), "P3700\x08\xff": (128, 490, 512, "K2"), "P3700\x09\xff": (128, 403, 430, "K3"), "P3700\x16\xff": (128, 450, 470, "M"), "P3700\x17\xff": (128, 470, 490, "M1"), "P3700\x18\xff": (128, 490, 520, "M2"), "P3700\x19\xff": (128, 403, 430, "M3"), "P3700&\xff": (128, 440, 470, "E"), "P3700V\xff": (128, 440, 470, "NE") } @directory.register class TK360G_Radios(Kenwood_Serie_60G): """Kenwood TK-360 Radio [K/E/M/NE]""" MODEL = "TK-360G" TYPE = "P3600" VARIANTS = { "P3600\x06\xff": (8, 450, 470, "K"), "P3600\x07\xff": (8, 470, 490, "K1"), "P3600\x08\xff": (8, 490, 512, "K2"), "P3600\x09\xff": (8, 403, 430, "K3"), "P3600&\xff": (8, 440, 470, "E"), "P3600)\xff": (8, 406, 430, "E1"), "P3600\x16\xff": (8, 450, 470, "M"), "P3600\x17\xff": (8, 470, 490, "M1"), "P3600\x19\xff": (8, 403, 430, "M2"), "P3600V\xff": (8, 440, 470, "NE"), "P3600Y\xff": (8, 403, 430, "NE1") } @directory.register class TK278G_Radios(Kenwood_Serie_60G): """Kenwood TK-278G Radio C/C1/M/M1""" MODEL = "TK-278G" TYPE = "P2780" # Note that 16 CH don't have banks VARIANTS = { "P27805\xff": (128, 136, 150, "C1"), "P27804\xff": (128, 150, 174, "C"), "P2780\x15\xff": (16, 136, 150, "M1"), "P2780\x14\xff": (16, 150, 174, "M") } @directory.register class TK272G_Radios(Kenwood_Serie_60G): """Kenwood TK-272G Radio K/K1""" MODEL = "TK-272G" TYPE = "P2720" VARIANTS = { "P2720\x05\xfb": (32, 136, 150, "K1"), "P2720\x04\xfb": (32, 150, 174, "K") } @directory.register class TK270G_Radios(Kenwood_Serie_60G): """Kenwood TK-270G Radio K/K1/M/E/NE/NT""" MODEL = "TK-270G" TYPE = "P2700" VARIANTS = { "P2700T\xff": (128, 146, 174, "NE/NT"), "P2700$\xff": (128, 146, 174, "E"), "P2700\x14\xff": (128, 150, 174, "M"), "P2700\x05\xff": (128, 136, 150, "K1"), "P2700\x04\xff": (128, 150, 174, "K") } @directory.register class TK260G_Radios(Kenwood_Serie_60G): """Kenwood TK-260G Radio K/K1/M/E/NE/NT""" MODEL = "TK-260G" _hasbanks = False TYPE = "P2600" VARIANTS = { "P2600U\xff": (8, 136, 150, "N1"), "P2600T\xff": (8, 146, 174, "N"), "P2600$\xff": (8, 150, 174, "E"), "P2600\x14\xff": (8, 150, 174, "M"), "P2600\x05\xff": (8, 136, 150, "K1"), "P2600\x04\xff": (8, 150, 174, "K") } chirp-daily-20170714/chirp/drivers/wouxun.py0000644000016101777760000016762113060205711022075 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 import logging from chirp import util, chirp_common, bitwise, memmap, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueFloat, RadioSettings from wouxun_common import wipe_memory, do_download, do_upload from textwrap import dedent LOG = logging.getLogger(__name__) 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 # LOG.debug("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", "PROGUV6X\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; u8 _0_unknown_1:3, iswidex:1, _0_unknown_2:4; } memory[199]; #seekto 0x0842; u16 fm_presets_0[9]; #seekto 0x0882; u16 fm_presets_1[9]; #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 0x0E00; struct { char welcome1[6]; char welcome2[6]; char single_band[6]; } strings; #seekto 0x0E20; 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:8; 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; u8 sd_available; 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 0x1008; struct { u8 unknown[8]; u8 name[6]; u8 pad[2]; } names[199]; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('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!') rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on. 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to download image from device.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on. 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to upload image to device.""")) return rp @classmethod def _get_querymodel(cls): if isinstance(cls._querymodel, str): while True: yield cls._querymodel else: i = 0 while True: yield cls._querymodel[i % len(cls._querymodel)] i += 1 def _identify(self): """Do the original wouxun identification dance""" query = self._get_querymodel() for _i in range(0, 10): self.pipe.write(query.next()) resp = self.pipe.read(9) if len(resp) != 9: LOG.debug("Got:\n%s" % util.hexprint(resp)) LOG.info("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: LOG.info("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): freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges") fm_preset = RadioSettingGroup("fm_preset", "FM Presets") cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings") group = RadioSettings(cfg_s, freq_ranges, fm_preset) rs = RadioSetting("menu_available", "Menu Available", RadioSettingValueBoolean( self._memobj.settings.menu_available)) cfg_s.append(rs) rs = RadioSetting("vhf_rx_start", "1st band RX Lower Limit (MHz)", RadioSettingValueInteger( 50, 174, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_rx_stop", "1st band RX Upper Limit (MHz)", RadioSettingValueInteger( 50, 174, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_start", "2nd band RX Lower Limit (MHz)", RadioSettingValueInteger( 136, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_stop", "2nd band RX Upper Limit (MHz)", RadioSettingValueInteger( 136, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_start", "1st band TX Lower Limit (MHz)", RadioSettingValueInteger( 50, 174, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_stop", "1st TX Upper Limit (MHz)", RadioSettingValueInteger( 50, 174, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_start", "2st band TX Lower Limit (MHz)", RadioSettingValueInteger( 136, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_stop", "2st band TX Upper Limit (MHz)", RadioSettingValueInteger( 136, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freq_ranges.append(rs) # tell the decoded ranges to UI freq_ranges = self._memobj.freq_ranges self.valid_freq = \ [(decode_freq(freq_ranges.vhf_rx_start) * 1000000, (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000), (decode_freq(freq_ranges.uhf_rx_start) * 1000000, (decode_freq(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])) cfg_s.append(rs) rs = RadioSetting("strings.welcome1", "Power-On Message 1", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.welcome1))) cfg_s.append(rs) rs = RadioSetting("strings.welcome2", "Power-On Message 2", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.welcome2))) cfg_s.append(rs) rs = RadioSetting("strings.single_band", "Single Band Message", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.single_band))) cfg_s.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])) cfg_s.append(rs) rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode", RadioSettingValueList( options, options[self._memobj.settings.vfo_b_ch_disp])) cfg_s.append(rs) options = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"] rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step", RadioSettingValueList( options, options[self._memobj.settings.vfo_a_fr_step])) cfg_s.append(rs) rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step", RadioSettingValueList( options, options[self._memobj.settings.vfo_b_fr_step])) cfg_s.append(rs) rs = RadioSetting("vfo_a_squelch", "VFO A Squelch", RadioSettingValueInteger( 0, 9, self._memobj.settings.vfo_a_squelch)) cfg_s.append(rs) rs = RadioSetting("vfo_b_squelch", "VFO B Squelch", RadioSettingValueInteger( 0, 9, self._memobj.settings.vfo_b_squelch)) cfg_s.append(rs) rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel", RadioSettingValueInteger( 1, 128, self._memobj.settings.vfo_a_cur_chan)) cfg_s.append(rs) rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel", RadioSettingValueInteger( 1, 128, self._memobj.settings.vfo_b_cur_chan)) cfg_s.append(rs) rs = RadioSetting("priority_chan", "Priority channel", RadioSettingValueInteger( 0, 199, self._memobj.settings.priority_chan)) cfg_s.append(rs) rs = RadioSetting("power_save", "Power save", RadioSettingValueBoolean( self._memobj.settings.power_save)) cfg_s.append(rs) options = ["Off", "Scan", "Lamp", "SOS", "Radio"] rs = RadioSetting("pf1_function", "PF1 Function select", RadioSettingValueList( options, options[self._memobj.settings.pf1_function])) cfg_s.append(rs) options = ["Off", "Begin", "End", "Both"] rs = RadioSetting("roger_beep", "Roger beep select", RadioSettingValueList( options, options[self._memobj.settings.roger_beep])) cfg_s.append(rs) options = ["%s" % x for x in range(15, 615, 15)] transmit_time_out = options[self._memobj.settings.transmit_time_out] rs = RadioSetting("transmit_time_out", "TX Time-out Timer", RadioSettingValueList( options, transmit_time_out)) cfg_s.append(rs) rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert", RadioSettingValueInteger( 0, 10, self._memobj.settings.tx_time_out_alert)) cfg_s.append(rs) rs = RadioSetting("vox", "Vox", RadioSettingValueInteger( 0, 10, self._memobj.settings.vox)) cfg_s.append(rs) options = ["Off", "Chinese", "English"] rs = RadioSetting("voice", "Voice", RadioSettingValueList( options, options[self._memobj.settings.voice])) cfg_s.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean( self._memobj.settings.beep)) cfg_s.append(rs) rs = RadioSetting("ani_id_enable", "ANI id enable", RadioSettingValueBoolean( self._memobj.settings.ani_id_enable)) cfg_s.append(rs) rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay", RadioSettingValueInteger( 0, 30, self._memobj.settings.ani_id_tx_delay)) cfg_s.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])) cfg_s.append(rs) options = ["Time", "Carrier", "Search"] rs = RadioSetting("scan_mode", "Scan mode", RadioSettingValueList( options, options[self._memobj.settings.scan_mode])) cfg_s.append(rs) rs = RadioSetting("kbd_lock", "Keyboard lock", RadioSettingValueBoolean( self._memobj.settings.kbd_lock)) cfg_s.append(rs) rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard", RadioSettingValueBoolean( self._memobj.settings.auto_lock_kbd)) cfg_s.append(rs) rs = RadioSetting("auto_backlight", "Auto backlight", RadioSettingValueBoolean( self._memobj.settings.auto_backlight)) cfg_s.append(rs) options = ["CH A", "CH B"] rs = RadioSetting("sos_ch", "SOS CH", RadioSettingValueList( options, options[self._memobj.settings.sos_ch])) cfg_s.append(rs) rs = RadioSetting("stopwatch", "Stopwatch", RadioSettingValueBoolean( self._memobj.settings.stopwatch)) cfg_s.append(rs) rs = RadioSetting("dual_band_receive", "Dual band receive", RadioSettingValueBoolean( self._memobj.settings.dual_band_receive)) cfg_s.append(rs) options = ["VFO A", "VFO B"] rs = RadioSetting("current_vfo", "Current VFO", RadioSettingValueList( options, options[self._memobj.settings.current_vfo])) cfg_s.append(rs) options = ["Dual", "Single"] rs = RadioSetting("sd_available", "Single/Dual Band", RadioSettingValueList( options, options[self._memobj.settings.sd_available])) cfg_s.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])) cfg_s.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])) cfg_s.append(rs) dtmfchars = "0123456789 *#ABCD" _codeobj = self._memobj.settings.ani_id_content _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 6, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val) def apply_ani_id(setting, obj): value = [] for j in range(0, 6): try: value.append(dtmfchars.index(str(setting.value)[j])) except IndexError: value.append(0xFF) obj.ani_id_content = value rs.set_apply_callback(apply_ani_id, self._memobj.settings) cfg_s.append(rs) for i in range(0, 9): if self._memobj.fm_presets_0[i] != 0xFFFF: used = True preset = self._memobj.fm_presets_0[i] / 10.0 + 76 else: used = False preset = 76 rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i + 1), RadioSettingValueBoolean(used), RadioSettingValueFloat(76, 108, preset, 0.1, 1)) fm_preset.append(rs) for i in range(0, 9): if self._memobj.fm_presets_1[i] != 0xFFFF: used = True preset = self._memobj.fm_presets_1[i] / 10.0 + 76 else: used = False preset = 76 rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i + 1), RadioSettingValueBoolean(used), RadioSettingValueFloat(76, 108, preset, 0.1, 1)) fm_preset.append(rs) return group def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "freq_ranges": self._set_freq_settings(element) elif element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue 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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): obj = self._memobj for element in settings: try: (bank, index) = \ (int(a) for a in element.get_name().split("_")[-2:]) val = element.value if val[0].get_value(): value = int(val[1].get_value()*10-760) else: value = 0xffff LOG.debug("Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)) if bank == 0: setting = self._memobj.fm_presets_0 else: setting = self._memobj.fm_presets_1 setting[index] = value except Exception, e: LOG.debug(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: LOG.debug(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 tpol = False 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 = "" rpol = False 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) # always set it even if no dtcs is used mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N") LOG.debug("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) options = ["NFM", "FM"] iswidex = RadioSetting("iswidex", "Mode TX(KG-UV6X)", RadioSettingValueList( options, options[_mem.iswidex])) iswidex.set_doc("Mode TX") mem.extra.append(iswidex) return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("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[0x170:0x173] != "LX-" 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", "HiKGUVD1\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; u8 _0_unknown_1:3, iswidex:1, _0_unknown_2:4; } 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; u8 sd_available; 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 0x0f82; 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 0x1f82; 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): freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges") fm_preset = RadioSettingGroup("fm_preset", "FM Presets") cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings") group = RadioSettings(cfg_s, freq_ranges, fm_preset) rs = RadioSetting("menu_available", "Menu Available", RadioSettingValueBoolean( self._memobj.settings.menu_available)) cfg_s.append(rs) rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)", RadioSettingValueInteger( 1, 1000, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freq_ranges.append(rs) # tell the decoded ranges to UI freq_ranges = self._memobj.freq_ranges self.valid_freq = \ [(decode_freq(freq_ranges.vhf_rx_start) * 1000000, (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000), (decode_freq(freq_ranges.uhf_rx_start) * 1000000, (decode_freq(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", "N/A(KG-UV6X)"] rs = RadioSetting("ponmsg", "Poweron message", RadioSettingValueList( options, options[self._memobj.settings.ponmsg])) cfg_s.append(rs) rs = RadioSetting("strings.welcome1", "Power-On Message 1", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.welcome1))) cfg_s.append(rs) rs = RadioSetting("strings.welcome2", "Power-On Message 2", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.welcome2))) cfg_s.append(rs) rs = RadioSetting("strings.single_band", "Single Band Message", RadioSettingValueString( 0, 6, _filter(self._memobj.strings.single_band))) cfg_s.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])) cfg_s.append(rs) rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode", RadioSettingValueList( options, options[self._memobj.settings.vfo_b_ch_disp])) cfg_s.append(rs) options = \ ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"] rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step", RadioSettingValueList( options, options[self._memobj.settings.vfo_a_fr_step])) cfg_s.append(rs) rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step", RadioSettingValueList( options, options[self._memobj.settings.vfo_b_fr_step])) cfg_s.append(rs) rs = RadioSetting("vfo_a_squelch", "VFO A Squelch", RadioSettingValueInteger( 0, 9, self._memobj.settings.vfo_a_squelch)) cfg_s.append(rs) rs = RadioSetting("vfo_b_squelch", "VFO B Squelch", RadioSettingValueInteger( 0, 9, self._memobj.settings.vfo_b_squelch)) cfg_s.append(rs) rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel", RadioSettingValueInteger( 1, 199, self._memobj.settings.vfo_a_cur_chan)) cfg_s.append(rs) rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel", RadioSettingValueInteger( 1, 199, self._memobj.settings.vfo_b_cur_chan)) cfg_s.append(rs) rs = RadioSetting("priority_chan", "Priority channel", RadioSettingValueInteger( 0, 199, self._memobj.settings.priority_chan)) cfg_s.append(rs) rs = RadioSetting("power_save", "Power save", RadioSettingValueBoolean( self._memobj.settings.power_save)) cfg_s.append(rs) options = ["Off", "Scan", "Lamp", "SOS", "Radio"] rs = RadioSetting("pf1_function", "PF1 Function select", RadioSettingValueList( options, options[self._memobj.settings.pf1_function])) cfg_s.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])) cfg_s.append(rs) options = ["Off", "Begin", "End", "Both"] rs = RadioSetting("roger_beep", "Roger beep select", RadioSettingValueList( options, options[self._memobj.settings.roger_beep])) cfg_s.append(rs) options = ["%s" % x for x in range(15, 615, 15)] transmit_time_out = options[self._memobj.settings.transmit_time_out] rs = RadioSetting("transmit_time_out", "TX Time-out Timer", RadioSettingValueList( options, transmit_time_out)) cfg_s.append(rs) rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert", RadioSettingValueInteger( 0, 10, self._memobj.settings.tx_time_out_alert)) cfg_s.append(rs) rs = RadioSetting("vox", "Vox", RadioSettingValueInteger( 0, 10, self._memobj.settings.vox)) cfg_s.append(rs) options = ["Off", "Chinese", "English"] rs = RadioSetting("voice", "Voice", RadioSettingValueList( options, options[self._memobj.settings.voice])) cfg_s.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean( self._memobj.settings.beep)) cfg_s.append(rs) rs = RadioSetting("ani_id_enable", "ANI id enable", RadioSettingValueBoolean( self._memobj.settings.ani_id_enable)) cfg_s.append(rs) rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay", RadioSettingValueInteger( 0, 30, self._memobj.settings.ani_id_tx_delay)) cfg_s.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])) cfg_s.append(rs) options = ["Time", "Carrier", "Search"] rs = RadioSetting("scan_mode", "Scan mode", RadioSettingValueList( options, options[self._memobj.settings.scan_mode])) cfg_s.append(rs) rs = RadioSetting("kbd_lock", "Keyboard lock", RadioSettingValueBoolean( self._memobj.settings.kbd_lock)) cfg_s.append(rs) rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard", RadioSettingValueBoolean( self._memobj.settings.auto_lock_kbd)) cfg_s.append(rs) rs = RadioSetting("auto_backlight", "Auto backlight", RadioSettingValueBoolean( self._memobj.settings.auto_backlight)) cfg_s.append(rs) options = ["CH A", "CH B"] rs = RadioSetting("sos_ch", "SOS CH", RadioSettingValueList( options, options[self._memobj.settings.sos_ch])) cfg_s.append(rs) rs = RadioSetting("stopwatch", "Stopwatch", RadioSettingValueBoolean( self._memobj.settings.stopwatch)) cfg_s.append(rs) rs = RadioSetting("dual_band_receive", "Dual band receive", RadioSettingValueBoolean( self._memobj.settings.dual_band_receive)) cfg_s.append(rs) options = ["VFO A", "VFO B"] rs = RadioSetting("current_vfo", "Current VFO", RadioSettingValueList( options, options[self._memobj.settings.current_vfo])) cfg_s.append(rs) options = ["Dual", "Single"] rs = RadioSetting("sd_available", "Single/Dual Band", RadioSettingValueList( options, options[self._memobj.settings.sd_available])) cfg_s.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])) cfg_s.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])) cfg_s.append(rs) dtmfchars = "0123456789 *#ABCD" _codeobj = self._memobj.settings.ani_id_content _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 6, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("settings.ani_id_content", "ANI Code", val) def apply_ani_id(setting, obj): value = [] for j in range(0, 6): try: value.append(dtmfchars.index(str(setting.value)[j])) except IndexError: value.append(0xFF) obj.ani_id_content = value rs.set_apply_callback(apply_ani_id, self._memobj.settings) cfg_s.append(rs) for i in range(0, 9): if self._memobj.fm_presets_0[i] != 0xFFFF: used = True preset = self._memobj.fm_presets_0[i]/10.0+76 else: used = False preset = 76 rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i+1), RadioSettingValueBoolean(used), RadioSettingValueFloat(76, 108, preset, 0.1, 1)) fm_preset.append(rs) for i in range(0, 9): if self._memobj.fm_presets_1[i] != 0xFFFF: used = True preset = self._memobj.fm_presets_1[i]/10.0+76 else: used = False preset = 76 rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i+1), RadioSettingValueBoolean(used), RadioSettingValueFloat(76, 108, preset, 0.1, 1)) fm_preset.append(rs) return group def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "freq_ranges": self._set_freq_settings(element) elif element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): obj = self._memobj for element in settings: try: (bank, index) = \ (int(a) for a in element.get_name().split("_")[-2:]) val = element.value if val[0].get_value(): value = int(val[1].get_value()*10-760) else: value = 0xffff LOG.debug("Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)) if bank == 0: setting = self._memobj.fm_presets_0 else: setting = self._memobj.fm_presets_1 setting[index] = value except Exception, e: LOG.debug(element.get_name()) raise @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 KG-816""" MODEL = "KG-816" _querymodel = "HiWOUXUN\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 unknown; u8 _0_unknown_1:3, iswidex:1, _0_unknown_2:4; } 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): freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges (read only)") group = RadioSettings(freq_ranges) rs = RadioSetting("vhf_rx_start", "vhf rx start", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.vhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_rx_stop", "vhf rx stop", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.vhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_start", "uhf rx start", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_rx_stop", "uhf rx stop", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.uhf_rx_stop))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_start", "vhf tx start", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.vhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("vhf_tx_stop", "vhf tx stop", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.vhf_tx_stop))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_start", "uhf tx start", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_start))) freq_ranges.append(rs) rs = RadioSetting("uhf_tx_stop", "uhf tx stop", RadioSettingValueInteger( 66, 520, decode_freq( self._memobj.freq_ranges.uhf_tx_stop))) freq_ranges.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 group @classmethod def match_model(cls, filedata, filename): if len(filedata) == 8192 and \ filedata[0x60:0x64] != "2009" and \ filedata[0x170:0x173] != "LX-" and \ filedata[0xF7E:0xF80] != "\x01\xE2" 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 @directory.register class KG818Radio(KG816Radio): """Wouxun KG-818""" MODEL = "KG-818" @classmethod def match_model(cls, filedata, filename): return False chirp-daily-20170714/chirp/drivers/__init__.py0000644000016101777760000000043112475535623022271 0ustar jenkinsnogroup00000000000000import os import sys from glob import glob module_dir = os.path.dirname(sys.modules["chirp.drivers"].__file__) __all__ = [] for i in sorted(glob(os.path.join(module_dir, "*.py"))): name = os.path.basename(i)[:-3] if not name.startswith("__"): __all__.append(name) chirp-daily-20170714/chirp/drivers/thuv1f.py0000644000016101777760000003506412646374217021761 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 logging from chirp import chirp_common, errors, util, directory, memmap from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) 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) LOG.info("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.timeout = 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": LOG.debug(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): # TYT TH-UVF1 original if filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" + "\x35\x00\x39\x00\x47\x00\x52\x00"): return True # TYT TH-UVF1 V2 elif filedata.startswith("\x14\x40\x14\x80\x43\x00\x45\x00" + "\x13\x60\x17\x40\x40\x00\x47\x00"): return True else: return False 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): LOG.debug("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("basic", "Basic") top = RadioSettings(group) 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): LOG.debug(repr(str(name))) return str(name).rstrip("\xFF").rstrip() group.append( RadioSetting("ponmsg", "Power-On Message", RadioSettingValueString(0, 6, _filter(_settings.ponmsg)))) 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 setattr(_settings, element.get_name(), element.value) chirp-daily-20170714/chirp/drivers/ftm3200d.py0000644000016101777760000001471313111751202021756 0ustar jenkinsnogroup00000000000000# Copyright 2010 Dan Smith # Copyright 2017 Wade Simmons # # 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 logging from textwrap import dedent from chirp.drivers import yaesu_clone, ft1d from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSettings LOG = logging.getLogger(__name__) POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=30), chirp_common.PowerLevel("Hi", watts=65)] TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", None, None, "Pager", "Cross"] CROSS_MODES = [None, "DTCS->", "Tone->DTCS", "DTCS->Tone"] MODES = ["FM", "NFM"] STEPS = [0, 5, 6.25, 10, 12.5, 15, 20, 25, 50, 100] # 0 = auto RFSQUELCH = ["OFF", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"] # Charset is subset of ASCII + some unknown chars \x80-\x86 VALID_CHARS = ["%i" % int(x) for x in range(0, 10)] + \ list(":>=After clicking OK, press the [REV(DW)] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA terminal. 3. Press and hold in the [MHz(SETUP)] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [MHz(SETUP)] key ("-WAIT-" will appear on the LCD).""")) return rp def process_mmap(self): mem_format = ft1d.MEM_FORMAT + MEM_FORMAT self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs_polarity = False rf.valid_modes = list(MODES) rf.valid_tmodes = [x for x in TMODES if x is not None] rf.valid_cross_modes = [x for x in CROSS_MODES if x is not None] rf.valid_duplexes = list(ft1d.DUPLEX) rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(136000000, 174000000)] # rf.valid_skips = SKIPS rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "".join(VALID_CHARS) rf.valid_name_length = 8 rf.memory_bounds = (1, 199) rf.can_odd_split = True rf.has_ctone = False rf.has_bank = False rf.has_bank_names = False # disable until implemented rf.has_settings = False return rf def _decode_label(self, mem): # TODO preserve the unknown \x80-x86 chars? return str(mem.label).rstrip("\xFF").decode('ascii', 'replace') def _encode_label(self, mem): label = mem.name.rstrip().encode('ascii', 'ignore') return self._add_ff_pad(label, 16) def _encode_charsetbits(self, mem): # TODO this is a setting to decide if the memory should be displayed # as a name or frequency. Should we expose this setting to the user # instead of autoselecting it (and losing their preference)? if mem.name.rstrip() == '': return [0x00, 0x00] return [0x00, 0x80] def _decode_power_level(self, mem): return POWER_LEVELS[mem.power - 1] def _encode_power_level(self, mem): return POWER_LEVELS.index(mem.power) + 1 def _decode_mode(self, mem): return MODES[mem.mode_alt] def _encode_mode(self, mem): return MODES.index(mem.mode) def _get_tmode(self, mem, _mem): if _mem.tone_mode > 8: tmode = "Cross" mem.cross_mode = CROSS_MODES[_mem.tone_mode - 8] else: tmode = TMODES[_mem.tone_mode] if tmode == "Pager": # TODO chirp_common does not allow 'Pager' # Expose as a different setting? mem.tmode = "" else: mem.tmode = tmode def _set_tmode(self, _mem, mem): if mem.tmode == "Cross": _mem.tone_mode = 8 + CROSS_MODES.index(mem.cross_mode) else: _mem.tone_mode = TMODES.index(mem.tmode) def _set_mode(self, _mem, mem): _mem.mode_alt = self._encode_mode(mem) def get_bank_model(self): return None def _debank(self, mem): return def _checksums(self): return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8), yaesu_clone.YaesuChecksum(0x06CA, 0x0748), yaesu_clone.YaesuChecksum(0x074A, 0x07C8), yaesu_clone.YaesuChecksum(0x07CA, 0x0848), yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)] def _get_settings(self): # TODO top = RadioSettings() return top @classmethod def _wipe_memory(cls, mem): mem.set_raw("\x00" * (mem.size() / 8)) def sync_out(self): # Need to give enough time for the radio to ACK after writes self.pipe.timeout = 1 return super(FTM3200Radio, self).sync_out() chirp-daily-20170714/chirp/drivers/ft90.py0000644000016101777760000005445312646374217021331 0ustar jenkinsnogroup00000000000000# Copyright 2011 Dan Smith # Copyright 2013 Jens Jensen AF5MI # # 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.drivers import yaesu_clone from chirp import chirp_common, bitwise, memmap, directory, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings import time import os import traceback import string import re import logging from textwrap import dedent LOG = logging.getLogger(__name__) CMD_ACK = chr(0x06) FT90_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] FT90_MODES = ["AM", "FM", "Auto"] # idx 3 (Bell) not supported yet FT90_TMODES = ["", "Tone", "TSQL", "", "DTCS"] FT90_TONES = list(chirp_common.TONES) for tone in [165.5, 171.3, 177.3]: FT90_TONES.remove(tone) FT90_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)] FT90_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)] FT90_DUPLEX = ["", "-", "+", "split"] FT90_CWID_CHARS = list(string.digits) + list(string.uppercase) + list(" ") FT90_DTMF_CHARS = list("0123456789ABCD*#") FT90_SPECIAL = ["vfo_vhf", "home_vhf", "vfo_uhf", "home_uhf", "pms_1L", "pms_1U", "pms_2L", "pms_2U"] @directory.register class FT90Radio(yaesu_clone.YaesuCloneModeRadio): VENDOR = "Yaesu" MODEL = "FT-90" ID = "\x8E\xF6" _memsize = 4063 # block 03 (200 Bytes long) repeats 18 times; channel memories _block_lengths = [2, 232, 24] + ([200] * 18) + [205] mem_format = """ u16 id; #seekto 0x22; struct { u8 dtmf_active; u8 dtmf1_len; u8 dtmf2_len; u8 dtmf3_len; u8 dtmf4_len; u8 dtmf5_len; u8 dtmf6_len; u8 dtmf7_len; u8 dtmf8_len; u8 dtmf1[8]; u8 dtmf2[8]; u8 dtmf3[8]; u8 dtmf4[8]; u8 dtmf5[8]; u8 dtmf6[8]; u8 dtmf7[8]; u8 dtmf8[8]; char cwid[7]; u8 unk1; u8 scan1:2, beep:1, unk3:3, rfsqlvl:2; u8 unk4:2, scan2:1, cwid_en:1, txnarrow:1, dtmfspeed:1, pttlock:2; u8 dtmftxdelay:3, fancontrol:2, unk5:3; u8 dimmer:3, unk6:1, lcdcontrast:4; u8 dcsmode:2, unk16:2, tot:4; u8 unk14; u8 unk8:1, ars:1, lock:1, txpwrsave:1, apo:4; u8 unk15; u8 unk9:4, key_lt:4; u8 unk10:4, key_rt:4; u8 unk11:4, key_p1:4; u8 unk12:4, key_p2:4; u8 unk13:4, key_acc:4; } settings; struct mem_struct { u8 mode:2, isUhf1:1, unknown1:2, step:3; u8 artsmode:2, unknown2:1, isUhf2:1 power:2, shift:2; u8 skip:1, showname:1, unknown3:1, isUhfHi:1, unknown4:1, tmode:3; u32 rxfreq; u32 txfreqoffset; u8 UseDefaultName:1, ars:1, tone:6; u8 packetmode:1, unknown5:1, dcstone:6; char name[7]; }; #seekto 0x86; struct mem_struct vfo_vhf; struct mem_struct home_vhf; struct mem_struct vfo_uhf; struct mem_struct home_uhf; #seekto 0xEB; u8 chan_enable[23]; #seekto 0x101; struct { u8 pms_2U_enable:1, pms_2L_enable:1, pms_1U_enable:1, pms_1L_enable:1, unknown6:4; } special_enables; #seekto 0x102; struct mem_struct memory[180]; #seekto 0xf12; struct mem_struct pms_1L; struct mem_struct pms_1U; struct mem_struct pms_2L; struct mem_struct pms_2U; #seekto 0x0F7B; struct { char demomsg1[50]; char demomsg2[50]; } demomsg; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect mic and hold [ACC] on mic while powering on. ("CLONE" will appear on the display) 3. Replace mic with PC programming cable. 4. After clicking OK, press the [SET] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect mic and hold [ACC] on mic while powering on. ("CLONE" will appear on the display) 3. Replace mic with PC programming cable. 4. Press the [DISP/SS] key ("R" will appear on the lower left of LCD).""")) rp.display_pre_upload_prompt_before_opening_port = False return rp @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_ctone = False rf.has_bank = False rf.has_dtcs_polarity = False rf.has_dtcs = True rf.valid_modes = FT90_MODES rf.valid_tmodes = FT90_TMODES rf.valid_duplexes = FT90_DUPLEX rf.valid_tuning_steps = FT90_STEPS rf.valid_power_levels = FT90_POWER_LEVELS_VHF rf.valid_name_length = 7 rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_skips = ["", "S"] rf.valid_special_chans = FT90_SPECIAL rf.memory_bounds = (1, 180) rf.valid_bands = [(100000000, 230000000), (300000000, 530000000), (810000000, 999975000)] return rf def _read(self, blocksize, blocknum): data = self.pipe.read(blocksize+2) # chew echo'd ack self.pipe.write(CMD_ACK) time.sleep(0.02) self.pipe.read(1) # chew echoed ACK from 1-wire serial if len(data) == blocksize + 2 and data[0] == chr(blocknum): checksum = yaesu_clone.YaesuChecksum(1, blocksize) if checksum.get_existing(data) != checksum.get_calculated(data): raise Exception("Checksum Failed [%02X<>%02X] block %02X, " "data len: %i" % (checksum.get_existing(data), checksum.get_calculated(data), blocknum, len(data))) data = data[1:blocksize + 1] # Chew blocknum and checksum else: raise Exception("Unable to read blocknum %02X " "expected blocksize %i got %i." % (blocknum, blocksize+2, len(data))) return data def _clone_in(self): # Be very patient with the radio self.pipe.timeout = 4 start = time.time() data = "" blocknum = 0 status = chirp_common.Status() status.msg = "Cloning..." self.status_fn(status) status.max = len(self._block_lengths) for blocksize in self._block_lengths: data += self._read(blocksize, blocknum) blocknum += 1 status.cur = blocknum self.status_fn(status) LOG.info("Clone completed in %i seconds, blocks read: %i" % (time.time() - start, blocknum)) return memmap.MemoryMap(data) def _clone_out(self): looppredelay = 0.4 looppostdelay = 1.9 start = time.time() blocknum = 0 pos = 0 status = chirp_common.Status() status.msg = "Cloning to radio..." self.status_fn(status) status.max = len(self._block_lengths) for blocksize in self._block_lengths: checksum = yaesu_clone.YaesuChecksum(pos, pos+blocksize-1) blocknumbyte = chr(blocknum) payloadbytes = self.get_mmap()[pos:pos+blocksize] checksumbyte = chr(checksum.get_calculated(self.get_mmap())) LOG.debug("Block %i - will send from %i to %i byte " % (blocknum, pos, pos + blocksize)) LOG.debug(util.hexprint(blocknumbyte)) LOG.debug(util.hexprint(payloadbytes)) LOG.debug(util.hexprint(checksumbyte)) # send wrapped bytes time.sleep(looppredelay) self.pipe.write(blocknumbyte) self.pipe.write(payloadbytes) self.pipe.write(checksumbyte) tmp = self.pipe.read(blocksize + 2) # chew echo LOG.debug("bytes echoed: ") LOG.debug(util.hexprint(tmp)) # radio is slow to write/ack: time.sleep(looppostdelay) buf = self.pipe.read(1) LOG.debug("ack recd:") LOG.debug(util.hexprint(buf)) if buf != CMD_ACK: raise Exception("Radio did not ack block %i" % blocknum) pos += blocksize blocknum += 1 status.cur = blocknum self.status_fn(status) LOG.info("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: trace = traceback.format_exc() raise errors.RadioError( "Failed to communicate with radio: %s" % trace) self.process_mmap() def sync_out(self): try: self._clone_out() except errors.RadioError: raise except Exception, e: trace = traceback.format_exc() raise errors.RadioError( "Failed to communicate with radio: %s" % trace) def process_mmap(self): self._memobj = bitwise.parse(self.mem_format, self._mmap) def _get_chan_enable(self, number): number = number - 1 bytepos = number // 8 bitpos = number % 8 chan_enable = self._memobj.chan_enable[bytepos] if chan_enable & (1 << bitpos): return True else: return False def _set_chan_enable(self, number, enable): number = number - 1 bytepos = number // 8 bitpos = number % 8 chan_enable = self._memobj.chan_enable[bytepos] if enable: chan_enable = chan_enable | (1 << bitpos) # enable else: chan_enable = chan_enable & ~(1 << bitpos) # disable self._memobj.chan_enable[bytepos] = chan_enable def get_memory(self, number): mem = chirp_common.Memory() if isinstance(number, str): # special channel _mem = getattr(self._memobj, number) mem.number = - len(FT90_SPECIAL) + FT90_SPECIAL.index(number) mem.extd_number = number if re.match('^pms', mem.extd_number): # enable pms_XY channel flag _special_enables = self._memobj.special_enables mem.empty = not getattr(_special_enables, mem.extd_number + "_enable") else: # regular memory _mem = self._memobj.memory[number-1] mem.number = number mem.empty = not self._get_chan_enable(number) if mem.empty: return mem # bail out, do not parse junk mem.freq = _mem.rxfreq * 10 mem.offset = _mem.txfreqoffset * 10 if not _mem.tmode < len(FT90_TMODES): _mem.tmode = 0 mem.tmode = FT90_TMODES[_mem.tmode] mem.rtone = FT90_TONES[_mem.tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcstone] mem.mode = FT90_MODES[_mem.mode] mem.duplex = FT90_DUPLEX[_mem.shift] if mem.freq / 1000000 > 300: mem.power = FT90_POWER_LEVELS_UHF[_mem.power] else: mem.power = FT90_POWER_LEVELS_VHF[_mem.power] # radio has a known bug with 5khz step and squelch if _mem.step == 0 or _mem.step > len(FT90_STEPS)-1: _mem.step = 2 mem.tuning_step = FT90_STEPS[_mem.step] mem.skip = _mem.skip and "S" or "" if not all(char in chirp_common.CHARSET_ASCII for char in str(_mem.name)): # dont display blank/junk name mem.name = "" else: mem.name = str(_mem.name) return mem def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def set_memory(self, mem): if mem.number < 0: # special channels _mem = getattr(self._memobj, mem.extd_number) if re.match('^pms', mem.extd_number): # enable pms_XY channel flag _special_enables = self._memobj.special_enables setattr(_special_enables, mem.extd_number + "_enable", True) else: _mem = self._memobj.memory[mem.number - 1] self._set_chan_enable(mem.number, not mem.empty) _mem.skip = mem.skip == "S" # radio has a known bug with 5khz step and dead squelch if not mem.tuning_step or mem.tuning_step == FT90_STEPS[0]: _mem.step = 2 else: _mem.step = FT90_STEPS.index(mem.tuning_step) _mem.rxfreq = mem.freq / 10 # vfo will unlock if not in right band? if mem.freq > 300000000: # uhf _mem.isUhf1 = 1 _mem.isUhf2 = 1 if mem.freq > 810000000: # uhf hiband _mem.isUhfHi = 1 else: _mem.isUhfHi = 0 else: # vhf _mem.isUhf1 = 0 _mem.isUhf2 = 0 _mem.isUhfHi = 0 _mem.txfreqoffset = mem.offset / 10 _mem.tone = FT90_TONES.index(mem.rtone) _mem.tmode = FT90_TMODES.index(mem.tmode) _mem.mode = FT90_MODES.index(mem.mode) _mem.shift = FT90_DUPLEX.index(mem.duplex) _mem.dcstone = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.step = FT90_STEPS.index(mem.tuning_step) _mem.shift = FT90_DUPLEX.index(mem.duplex) if mem.power: _mem.power = FT90_POWER_LEVELS_VHF.index(mem.power) else: _mem.power = 3 # default to low power if (len(mem.name) == 0): _mem.name = bytearray.fromhex("80ffffffffffff") _mem.showname = 0 else: _mem.name = str(mem.name).ljust(7) _mem.showname = 1 _mem.UseDefaultName = 0 def _decode_cwid(self, cwidarr): cwid = "" LOG.debug("@ +_decode_cwid:") for byte in cwidarr.get_value(): char = int(byte) LOG.debug(char) # bitwise wraps in quotes! get rid of those if char < len(FT90_CWID_CHARS): cwid += FT90_CWID_CHARS[char] return cwid def _encode_cwid(self, cwidarr): cwid = "" LOG.debug("@ _encode_cwid:") for char in cwidarr.get_value(): cwid += chr(FT90_CWID_CHARS.index(char)) LOG.debug(cwid) return cwid def _bbcd2dtmf(self, bcdarr, strlen=16): # doing bbcd, but with support for ABCD*# LOG.debug(bcdarr.get_value()) string = ''.join("%02X" % b for b in bcdarr) LOG.debug("@_bbcd2dtmf, received: %s" % string) string = string.replace('E', '*').replace('F', '#') if strlen <= 16: string = string[:strlen] return string def _dtmf2bbcd(self, dtmf): dtmfstr = dtmf.get_value() dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F') dtmfstr = str.ljust(dtmfstr.strip(), 16, "0") bcdarr = list(bytearray.fromhex(dtmfstr)) LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr) return bcdarr def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") autodial = RadioSettingGroup("autodial", "AutoDial") keymaps = RadioSettingGroup("keymaps", "KeyMaps") top = RadioSettings(basic, keymaps, autodial) rs = RadioSetting( "beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting( "lock", "Lock", RadioSettingValueBoolean(_settings.lock)) basic.append(rs) rs = RadioSetting( "ars", "Auto Repeater Shift", RadioSettingValueBoolean(_settings.ars)) basic.append(rs) rs = RadioSetting( "txpwrsave", "TX Power Save", RadioSettingValueBoolean(_settings.txpwrsave)) basic.append(rs) rs = RadioSetting( "txnarrow", "TX Narrow", RadioSettingValueBoolean(_settings.txnarrow)) basic.append(rs) options = ["Off", "S-3", "S-5", "S-Full"] rs = RadioSetting( "rfsqlvl", "RF Squelch Level", RadioSettingValueList(options, options[_settings.rfsqlvl])) basic.append(rs) options = ["Off", "Band A", "Band B", "Both"] rs = RadioSetting( "pttlock", "PTT Lock", RadioSettingValueList(options, options[_settings.pttlock])) basic.append(rs) rs = RadioSetting( "cwid_en", "CWID Enable", RadioSettingValueBoolean(_settings.cwid_en)) basic.append(rs) cwid = RadioSettingValueString(0, 7, self._decode_cwid(_settings.cwid)) cwid.set_charset(FT90_CWID_CHARS) rs = RadioSetting("cwid", "CWID", cwid) basic.append(rs) options = ["OFF"] + map(str, range(1, 12+1)) rs = RadioSetting( "apo", "APO time (hrs)", RadioSettingValueList(options, options[_settings.apo])) basic.append(rs) options = ["Off"] + map(str, range(1, 60+1)) rs = RadioSetting( "tot", "Time Out Timer (mins)", RadioSettingValueList(options, options[_settings.tot])) basic.append(rs) options = ["off", "Auto/TX", "Auto", "TX"] rs = RadioSetting( "fancontrol", "Fan Control", RadioSettingValueList(options, options[_settings.fancontrol])) basic.append(rs) keyopts = ["Scan Up", "Scan Down", "Repeater", "Reverse", "Tone Burst", "Tx Power", "Home Ch", "VFO/MR", "Tone", "Priority"] rs = RadioSetting( "key_lt", "Left Key", RadioSettingValueList(keyopts, keyopts[_settings.key_lt])) keymaps.append(rs) rs = RadioSetting( "key_rt", "Right Key", RadioSettingValueList(keyopts, keyopts[_settings.key_rt])) keymaps.append(rs) rs = RadioSetting( "key_p1", "P1 Key", RadioSettingValueList(keyopts, keyopts[_settings.key_p1])) keymaps.append(rs) rs = RadioSetting( "key_p2", "P2 Key", RadioSettingValueList(keyopts, keyopts[_settings.key_p2])) keymaps.append(rs) rs = RadioSetting( "key_acc", "ACC Key", RadioSettingValueList(keyopts, keyopts[_settings.key_acc])) keymaps.append(rs) options = map(str, range(0, 12+1)) rs = RadioSetting( "lcdcontrast", "LCD Contrast", RadioSettingValueList(options, options[_settings.lcdcontrast])) basic.append(rs) options = ["off", "d4", "d3", "d2", "d1"] rs = RadioSetting( "dimmer", "Dimmer", RadioSettingValueList(options, options[_settings.dimmer])) basic.append(rs) options = ["TRX Normal", "RX Reverse", "TX Reverse", "TRX Reverse"] rs = RadioSetting( "dcsmode", "DCS Mode", RadioSettingValueList(options, options[_settings.dcsmode])) basic.append(rs) options = ["50 ms", "100 ms"] rs = RadioSetting( "dtmfspeed", "DTMF Speed", RadioSettingValueList(options, options[_settings.dtmfspeed])) autodial.append(rs) options = ["50 ms", "250 ms", "450 ms", "750 ms", "1 sec"] rs = RadioSetting( "dtmftxdelay", "DTMF TX Delay", RadioSettingValueList(options, options[_settings.dtmftxdelay])) autodial.append(rs) options = map(str, range(1, 8 + 1)) rs = RadioSetting( "dtmf_active", "DTMF Active", RadioSettingValueList(options, options[_settings.dtmf_active])) autodial.append(rs) # setup 8 dtmf autodial entries for i in map(str, range(1, 9)): objname = "dtmf" + i dtmfsetting = getattr(_settings, objname) dtmflen = getattr(_settings, objname + "_len") dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen) dtmf = RadioSettingValueString(0, 16, dtmfstr) dtmf.set_charset(FT90_DTMF_CHARS + list(" ")) rs = RadioSetting(objname, objname.upper(), dtmf) autodial.append(rs) return top def set_settings(self, uisettings): _settings = self._memobj.settings for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() oldval = getattr(_settings, setting) newval = element.value if setting == "cwid": newval = self._encode_cwid(newval) if re.match('dtmf\d', setting): # set dtmf length field and then get bcd dtmf dtmfstrlen = len(str(newval).strip()) setattr(_settings, setting + "_len", dtmfstrlen) newval = self._dtmf2bbcd(newval) LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/kenwood_itm.py0000644000016101777760000001016012476257220023044 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 import logging from chirp import chirp_common, errors, directory from chirp.drivers import generic_csv LOG = logging.getLogger(__name__) class OmittedHeaderError(Exception): """An internal exception to indicate that a header was omitted""" pass @directory.register class ITMRadio(generic_csv.CSVRadio): """Kenwood ITM format""" VENDOR = "Kenwood" MODEL = "ITM" FILE_EXTENSION = "itm" ATTR_MAP = { "CH": (int, "number"), "RXF": (chirp_common.parse_freq, "freq"), "NAME": (str, "name"), } def _clean_duplex(self, headers, line, mem): try: txfreq = chirp_common.parse_freq( generic_csv.get_datum_by_header(headers, line, "TXF")) except ValueError: mem.duplex = "off" return mem if mem.freq == txfreq: mem.duplex = "" elif txfreq: mem.duplex = "split" mem.offset = txfreq return mem def _clean_number(self, headers, line, mem): zone = int(generic_csv.get_datum_by_header(headers, line, "ZN")) mem.number = zone * 100 + mem.number return mem def _clean_tmode(self, headers, line, mem): rtone = eval(generic_csv.get_datum_by_header(headers, line, "TXSIG")) ctone = eval(generic_csv.get_datum_by_header(headers, line, "RXSIG")) if rtone: mem.tmode = "Tone" if ctone: mem.tmode = "TSQL" mem.rtone = rtone or 88.5 mem.ctone = ctone or mem.rtone 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, "r") for line in f: if line.strip() == "// Conventional Data": 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(line) == 0: # End of channel data break if len(header) > len(line): LOG.error("Line %i has %i columns, expected %i" % (lineno, len(line), len(header))) self.errors.append("Column number mismatch on line %i" % lineno) continue # 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: LOG.error("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: for e in errors: LOG.error(e) raise errors.InvalidDataError("No channels found") @classmethod def match_model(cls, filedata, filename): return filename.lower().endswith("." + cls.FILE_EXTENSION) chirp-daily-20170714/chirp/drivers/vx510.py0000644000016101777760000001443412476006422021414 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 . from chirp.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise # This driver is unfinished and therefore does not register itself with Chirp. # # Downloads should work consistently but an upload has not been attempted. # # There is a stray byte at 0xC in the radio download that is not present in the # save file from the CE-21 software. This puts the first few bytes of channel 1 # in the wrong location. A quick hack to fix the subsequent channels was to # insert the #seekto dynamically, but this does nothing to fix reading of # channel 1 (memory[0]). MEM_FORMAT = """ u8 unknown1[6]; u8 prioritych; #seekto %d; struct { u8 empty:1, txinhibit:1, tot:1, low_power:1, bclo:1, btlo:1, skip:1, pwrsave:1; u8 unknown2:5, narrow:1, unknown2b:2; u24 name; u8 ctone; u8 rtone; u8 unknown3; bbcd freq_rx[3]; bbcd freq_tx[3]; } memory[32]; char imgname[10]; """ STEPS = [5.0, 6.25] CHARSET = "".join([chr(x) for x in range(ord("0"), ord("9")+1)] + [chr(x) for x in range(ord("A"), ord("Z")+1)]) + "<=>*+-\/_ " TONES = list(chirp_common.TONES) TONES.remove(165.5) TONES.remove(171.3) TONES.remove(177.3) POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("Low", watts=1.0)] # @directory.register class VX510Radio(yaesu_clone.YaesuCloneModeRadio): """Vertex VX-510V""" BAUD_RATE = 9600 VENDOR = "Vertex Standard" MODEL = "VX-510V" _model = "" _memsize = 470 _block_lengths = [10, 460] _block_size = 8 def _checksums(self): return [] # These checksums don't pass, so the alg might be different than Yaesu. # return [yaesu_clone.YaesuChecksum(0, self._memsize - 2)] # return [yaesu_clone.YaesuChecksum(0, 10), # yaesu_clone.YaesuChecksum(12, self._memsize - 1)] def get_features(self): rf = chirp_common.RadioFeatures() rf.can_odd_split = True rf.has_bank = False rf.has_ctone = True rf.has_cross = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = False rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] rf.valid_modes = ["FM", "NFM"] rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.memory_bounds = (1, 32) rf.valid_bands = [(13600000, 174000000)] rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS rf.valid_name_length = 4 rf.valid_characters = CHARSET return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT % 0xA, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def get_memory(self, number): mem = chirp_common.Memory() mem.number = number _mem = self._memobj.memory[number-1] mem.empty = _mem.empty mem.freq = chirp_common.fix_rounded_step(int(_mem.freq_rx) * 1000) for i in range(0, 4): index = (_mem.name >> (i*6)) & 0x3F mem.name += CHARSET[index] freq_tx = chirp_common.fix_rounded_step(int(_mem.freq_tx) * 1000) if _mem.txinhibit: mem.duplex = "off" elif mem.freq == freq_tx: mem.duplex = "" mem.offset = 0 elif 144000000 <= mem.freq < 148000000: mem.duplex = "+" if freq_tx > mem.freq else "-" mem.offset = abs(mem.freq - freq_tx) else: mem.duplex = "split" mem.offset = freq_tx mem.mode = _mem.narrow and "NFM" or "FM" mem.power = POWER_LEVELS[_mem.low_power] rtone = int(_mem.rtone) ctone = int(_mem.ctone) tmode_tx = tmode_rx = "" if rtone & 0x80: tmode_tx = "DTCS" mem.dtcs = chirp_common.DTCS_CODES[int(rtone) - 0x80] elif rtone: tmode_tx = "Tone" mem.rtone = TONES[rtone - 1] if not ctone: # not used, but this is a better default than 88.5 mem.ctone = TONES[rtone - 1] if ctone & 0x80: tmode_rx = "DTCS" mem.rx_dtcs = chirp_common.DTCS_CODES[int(ctone) - 0x80] elif ctone: tmode_rx = "Tone" mem.ctone = TONES[ctone - 1] if tmode_tx == "Tone" and not tmode_rx: mem.tmode = "Tone" elif tmode_tx == tmode_rx and tmode_tx == "Tone" and \ mem.rtone == mem.ctone: mem.tmode = "TSQL" elif tmode_tx == tmode_rx and tmode_tx == "DTCS" and \ mem.dtcs == mem.rx_dtcs: mem.tmode = "DTCS" elif tmode_rx or tmode_tx: mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (tmode_tx, tmode_rx) mem.skip = _mem.skip and "S" or "" return mem @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize # @directory.register class VX510File(VX510Radio, chirp_common.FileBackedRadio): """Vertex CE-21 File""" VENDOR = "Vertex Standard" MODEL = "CE-21 File" _model = "" _memsize = 664 def _checksums(self): return [yaesu_clone.YaesuChecksum(0, self._memsize - 1)] def process_mmap(self): # CE-21 file is missing the 0xC byte, probably a checksum. # It's not a YaesuChecksum. self._memobj = bitwise.parse(MEM_FORMAT % 0x9, self._mmap) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize chirp-daily-20170714/chirp/drivers/th9800.py0000644000016101777760000006456212730176377021506 0ustar jenkinsnogroup00000000000000# Copyright 2014 Tom Hayward # Copyright 2014 Jens Jensen # Copyright 2014 James Lee N1DDK # # 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 bitwise, chirp_common, directory, errors, util, memmap import struct from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings from chirp.chirp_common import format_freq import os import time import logging from datetime import date LOG = logging.getLogger(__name__) TH9800_MEM_FORMAT = """ struct mem { lbcd rx_freq[4]; lbcd tx_freq[4]; lbcd ctcss[2]; lbcd dtcs[2]; u8 power:2, BeatShift:1, unknown0a:2, display:1, // freq=0, name=1 scan:2; u8 fmdev:2, // wide=00, mid=01, narrow=10 scramb:1, compand:1, emphasis:1 unknown1a:2, sqlmode:1; // carrier, tone u8 rptmod:2, // off, -, + reverse:1, talkaround:1, step:4; u8 dtcs_pol:2, bclo:2, unknown3:2, tmode:2; lbcd offset[4]; u8 hsdtype:2, // off, 2-tone, 5-tone, dtmf unknown5a:1, am:1, unknown5b:4; u8 unknown6[3]; char name[6]; u8 empty[2]; }; #seekto 0x%04X; struct mem memory[800]; #seekto 0x%04X; struct { struct mem lower; struct mem upper; } scanlimits[5]; #seekto 0x%04X; struct { u8 unk0xdc20:5, left_sql:3; u8 apo; u8 unk0xdc22:5, backlight:3; u8 unk0xdc23; u8 beep:1, keylock:1, pttlock:2, unk0xdc24_32:2, hyper_chan:1, right_func_key:1; u8 tbst_freq:2, ani_display:1, unk0xdc25_4:1 mute_mode:2, unk0xdc25_10:2; u8 auto_xfer:1, auto_contact:1, unk0xdc26_54:2, auto_am:1, unk0xdc26_210:3; u8 unk0xdc27_76543:5, scan_mode:1, unk0xdc27_1:1, scan_resume:1; u16 scramb_freq; u16 scramb_freq1; u8 exit_delay; u8 unk0xdc2d; u8 unk0xdc2e:5, right_sql:3; u8 unk0xdc2f:4, beep_vol:4; u8 tot; u8 tot_alert; u8 tot_rekey; u8 tot_reset; u8 unk0xdc34; u8 unk0xdc35; u8 unk0xdc36; u8 unk0xdc37; u8 p1; u8 p2; u8 p3; u8 p4; } settings; #seekto 0x%04X; u8 chan_active[128]; u8 scan_enable[128]; u8 priority[128]; #seekto 0x%04X; struct { char sn[8]; char model[8]; char code[16]; u8 empty[8]; lbcd prog_yr[2]; lbcd prog_mon; lbcd prog_day; u8 empty_10f2c[4]; } info; struct { lbcd lorx[4]; lbcd hirx[4]; lbcd lotx[4]; lbcd hitx[4]; } bandlimits[9]; """ BLANK_MEMORY = "\xFF" * 8 + "\x00\x10\x23\x00\xC0\x08\x06\x00" \ "\x00\x00\x76\x00\x00\x00" + "\xFF" * 10 DTCS_POLARITY = ["NN", "RN", "NR", "RR"] SCAN_MODES = ["", "S", "P"] MODES = ["WFM", "FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00), chirp_common.PowerLevel("Mid2", watts=10.00), chirp_common.PowerLevel("Mid1", watts=20.00), chirp_common.PowerLevel("High", watts=50.00)] BUSY_LOCK = ["off", "Carrier", "2 tone"] MICKEYFUNC = ["None", "SCAN", "SQL.OFF", "TCALL", "PPTR", "PRI", "LOW", "TONE", "MHz", "REV", "HOME", "BAND", "VFO/MR"] SQLPRESET = ["Off", "2", "5", "9", "Full"] BANDS = ["30MHz", "50MHz", "60MHz", "108MHz", "150MHz", "250MHz", "350MHz", "450MHz", "850MHz"] STEPS = [2.5, 5.0, 6.25, 7.5, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] class TYTTH9800Base(chirp_common.Radio): """Base class for TYT TH-9800""" VENDOR = "TYT" def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 800) rf.has_bank = False rf.has_tuning_step = True rf.valid_tuning_steps = STEPS rf.can_odd_split = True rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = TMODES rf.has_ctone = False rf.valid_power_levels = POWER_LEVELS rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "#*-+" rf.valid_bands = [(26000000, 33000000), (47000000, 54000000), (108000000, 180000000), (350000000, 399995000), (400000000, 512000000), (750000000, 950000000)] rf.valid_skips = SCAN_MODES rf.valid_modes = MODES + ["AM"] rf.valid_name_length = 6 rf.has_settings = True return rf def process_mmap(self): self._memobj = bitwise.parse( TH9800_MEM_FORMAT % (self._mmap_offset, self._scanlimits_offset, self._settings_offset, self._chan_active_offset, self._info_offset), self._mmap) def get_active(self, banktype, num): """get active flag for channel active, scan enable, or priority banks""" bank = getattr(self._memobj, banktype) index = (num - 1) / 8 bitpos = (num - 1) % 8 mask = 2**bitpos enabled = bank[index] & mask if enabled: return True else: return False def set_active(self, banktype, num, enable=True): """set active flag for channel active, scan enable, or priority banks""" bank = getattr(self._memobj, banktype) index = (num - 1) / 8 bitpos = (num - 1) % 8 mask = 2**bitpos if enable: bank[index] |= mask else: bank[index] &= ~mask def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.empty = not self.get_active("chan_active", number) if mem.empty: return mem mem.freq = int(_mem.rx_freq) * 10 txfreq = int(_mem.tx_freq) * 10 if txfreq == mem.freq: mem.duplex = "" elif txfreq == 0: mem.duplex = "off" mem.offset = 0 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 mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_pol] mem.tmode = TMODES[int(_mem.tmode)] mem.ctone = mem.rtone = int(_mem.ctcss) / 10.0 mem.dtcs = int(_mem.dtcs) mem.name = str(_mem.name) mem.name = mem.name.replace("\xFF", " ").rstrip() if not self.get_active("scan_enable", number): mem.skip = "S" elif self.get_active("priority", number): mem.skip = "P" else: mem.skip = "" mem.mode = _mem.am and "AM" or MODES[int(_mem.fmdev)] mem.power = POWER_LEVELS[_mem.power] mem.tuning_step = STEPS[_mem.step] mem.extra = RadioSettingGroup("extra", "Extra") opts = ["Frequency", "Name"] display = RadioSetting( "display", "Display", RadioSettingValueList(opts, opts[_mem.display])) mem.extra.append(display) bclo = RadioSetting( "bclo", "Busy Lockout", RadioSettingValueList(BUSY_LOCK, BUSY_LOCK[_mem.bclo])) bclo.set_doc("Busy Lockout") mem.extra.append(bclo) emphasis = RadioSetting( "emphasis", "Emphasis", RadioSettingValueBoolean(bool(_mem.emphasis))) emphasis.set_doc("Boosts 300Hz to 2500Hz mic response") mem.extra.append(emphasis) compand = RadioSetting( "compand", "Compand", RadioSettingValueBoolean(bool(_mem.compand))) compand.set_doc("Compress Audio") mem.extra.append(compand) BeatShift = RadioSetting( "BeatShift", "BeatShift", RadioSettingValueBoolean(bool(_mem.BeatShift))) BeatShift.set_doc("Beat Shift") mem.extra.append(BeatShift) TalkAround = RadioSetting( "talkaround", "Talk Around", RadioSettingValueBoolean(bool(_mem.talkaround))) TalkAround.set_doc("Simplex mode when out of range of repeater") mem.extra.append(TalkAround) scramb = RadioSetting( "scramb", "Scramble", RadioSettingValueBoolean(bool(_mem.scramb))) scramb.set_doc("Frequency inversion Scramble") mem.extra.append(scramb) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] _prev_active = self.get_active("chan_active", mem.number) self.set_active("chan_active", mem.number, not mem.empty) if mem.empty or not _prev_active: LOG.debug("initializing memory channel %d" % mem.number) _mem.set_raw(BLANK_MEMORY) if mem.empty: return _mem.rx_freq = mem.freq / 10 if 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 elif mem.duplex == "off": _mem.tx_freq = 0 _mem.offset = 0 else: _mem.tx_freq = mem.freq / 10 _mem.tmode = TMODES.index(mem.tmode) if mem.tmode == "TSQL" or mem.tmode == "DTCS": _mem.sqlmode = 1 else: _mem.sqlmode = 0 _mem.ctcss = mem.rtone * 10 _mem.dtcs = mem.dtcs _mem.dtcs_pol = DTCS_POLARITY.index(mem.dtcs_polarity) _mem.name = mem.name.ljust(6, "\xFF") # autoset display to name if filled, else show frequency if mem.extra: # mem.extra only seems to be populated when called from edit panel display = mem.extra["display"] else: display = None if mem.name: _mem.display = True if display and not display.changed(): display.value = "Name" else: _mem.display = False if display and not display.changed(): display.value = "Frequency" _mem.scan = SCAN_MODES.index(mem.skip) if mem.skip == "P": self.set_active("priority", mem.number, True) self.set_active("scan_enable", mem.number, True) elif mem.skip == "S": self.set_active("priority", mem.number, False) self.set_active("scan_enable", mem.number, False) elif mem.skip == "": self.set_active("priority", mem.number, False) self.set_active("scan_enable", mem.number, True) if mem.mode == "AM": _mem.am = True _mem.fmdev = 0 else: _mem.am = False _mem.fmdev = MODES.index(mem.mode) if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 # low _mem.step = STEPS.index(mem.tuning_step) for setting in mem.extra: LOG.debug("@set_mem:", setting.get_name(), setting.value) setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings _info = self._memobj.info _bandlimits = self._memobj.bandlimits basic = RadioSettingGroup("basic", "Basic") info = RadioSettingGroup("info", "Model Info") top = RadioSettings(basic, info) basic.append(RadioSetting( "beep", "Beep", RadioSettingValueBoolean(_settings.beep))) basic.append(RadioSetting( "beep_vol", "Beep Volume", RadioSettingValueInteger(0, 15, _settings.beep_vol))) basic.append(RadioSetting( "keylock", "Key Lock", RadioSettingValueBoolean(_settings.keylock))) basic.append(RadioSetting( "ani_display", "ANI Display", RadioSettingValueBoolean(_settings.ani_display))) basic.append(RadioSetting( "auto_xfer", "Auto Transfer", RadioSettingValueBoolean(_settings.auto_xfer))) basic.append(RadioSetting( "auto_contact", "Auto Contact Always Remind", RadioSettingValueBoolean(_settings.auto_contact))) basic.append(RadioSetting( "auto_am", "Auto AM", RadioSettingValueBoolean(_settings.auto_am))) basic.append(RadioSetting( "left_sql", "Left Squelch", RadioSettingValueList( SQLPRESET, SQLPRESET[_settings.left_sql]))) basic.append(RadioSetting( "right_sql", "Right Squelch", RadioSettingValueList( SQLPRESET, SQLPRESET[_settings.right_sql]))) # basic.append(RadioSetting("apo", "Auto Power off (0.1h)", # RadioSettingValueInteger(0, 20, _settings.apo))) opts = ["Off"] + ["%0.1f" % (t / 10.0) for t in range(1, 21, 1)] basic.append(RadioSetting( "apo", "Auto Power off (Hours)", RadioSettingValueList(opts, opts[_settings.apo]))) opts = ["Off", "1", "2", "3", "Full"] basic.append(RadioSetting( "backlight", "Display Backlight", RadioSettingValueList(opts, opts[_settings.backlight]))) opts = ["Off", "Right", "Left", "Both"] basic.append(RadioSetting( "pttlock", "PTT Lock", RadioSettingValueList(opts, opts[_settings.pttlock]))) opts = ["Manual", "Auto"] basic.append(RadioSetting( "hyper_chan", "Hyper Channel", RadioSettingValueList(opts, opts[_settings.hyper_chan]))) opts = ["Key 1", "Key 2"] basic.append(RadioSetting( "right_func_key", "Right Function Key", RadioSettingValueList(opts, opts[_settings.right_func_key]))) opts = ["1000Hz", "1450Hz", "1750Hz", "2100Hz"] basic.append(RadioSetting( "tbst_freq", "Tone Burst Frequency", RadioSettingValueList(opts, opts[_settings.tbst_freq]))) opts = ["Off", "TX", "RX", "TX RX"] basic.append(RadioSetting( "mute_mode", "Mute Mode", RadioSettingValueList(opts, opts[_settings.mute_mode]))) opts = ["MEM", "MSM"] scanmode = RadioSetting( "scan_mode", "Scan Mode", RadioSettingValueList(opts, opts[_settings.scan_mode])) scanmode.set_doc("MEM = Normal scan, bypass channels marked skip. " " MSM = Scan only channels marked priority.") basic.append(scanmode) opts = ["TO", "CO"] basic.append(RadioSetting( "scan_resume", "Scan Resume", RadioSettingValueList(opts, opts[_settings.scan_resume]))) opts = ["%0.1f" % (t / 10.0) for t in range(0, 51, 1)] basic.append(RadioSetting( "exit_delay", "Span Transit Exit Delay", RadioSettingValueList(opts, opts[_settings.exit_delay]))) basic.append(RadioSetting( "tot", "Time Out Timer (minutes)", RadioSettingValueInteger(0, 30, _settings.tot))) basic.append(RadioSetting( "tot_alert", "Time Out Timer Pre Alert(seconds)", RadioSettingValueInteger(0, 15, _settings.tot_alert))) basic.append(RadioSetting( "tot_rekey", "Time Out Rekey (seconds)", RadioSettingValueInteger(0, 15, _settings.tot_rekey))) basic.append(RadioSetting( "tot_reset", "Time Out Reset(seconds)", RadioSettingValueInteger(0, 15, _settings.tot_reset))) basic.append(RadioSetting( "p1", "P1 Function", RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p1]))) basic.append(RadioSetting( "p2", "P2 Function", RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p2]))) basic.append(RadioSetting( "p3", "P3 Function", RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p3]))) basic.append(RadioSetting( "p4", "P4 Function", RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p4]))) # opts = ["0", "1"] # basic.append(RadioSetting("x", "Desc", # RadioSettingValueList(opts, opts[_settings.x]))) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered rsvs = RadioSettingValueString(0, 8, _filter(_info.sn)) rsvs.set_mutable(False) rs = RadioSetting("sn", "Serial Number", rsvs) info.append(rs) rsvs = RadioSettingValueString(0, 8, _filter(_info.model)) rsvs.set_mutable(False) rs = RadioSetting("model", "Model Name", rsvs) info.append(rs) rsvs = RadioSettingValueString(0, 16, _filter(_info.code)) rsvs.set_mutable(False) rs = RadioSetting("code", "Model Code", rsvs) info.append(rs) progdate = "%d/%d/%d" % (_info.prog_mon, _info.prog_day, _info.prog_yr) rsvs = RadioSettingValueString(0, 10, progdate) rsvs.set_mutable(False) rs = RadioSetting("progdate", "Last Program Date", rsvs) info.append(rs) # 9 band limits for i in range(0, 9): objname = BANDS[i] + "lorx" objnamepp = BANDS[i] + " Rx Start" # rsv = RadioSettingValueInteger(0, 100000000, # int(_bandlimits[i].lorx)) rsv = RadioSettingValueString( 0, 10, format_freq(int(_bandlimits[i].lorx)*10)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) objname = BANDS[i] + "hirx" objnamepp = BANDS[i] + " Rx end" rsv = RadioSettingValueString( 0, 10, format_freq(int(_bandlimits[i].hirx)*10)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) objname = BANDS[i] + "lotx" objnamepp = BANDS[i] + " Tx Start" rsv = RadioSettingValueString( 0, 10, format_freq(int(_bandlimits[i].lotx)*10)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) objname = BANDS[i] + "hitx" objnamepp = BANDS[i] + " Tx end" rsv = RadioSettingValueString( 0, 10, format_freq(int(_bandlimits[i].hitx)*10)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) return top def set_settings(self, settings): _settings = self._memobj.settings _info = self._memobj.info _bandlimits = self._memobj.bandlimits for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() oldval = getattr(_settings, setting) newval = element.value LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise @directory.register class TYTTH9800File(TYTTH9800Base, chirp_common.FileBackedRadio): """TYT TH-9800 .dat file""" MODEL = "TH-9800 File" FILE_EXTENSION = "dat" _memsize = 69632 _mmap_offset = 0x1100 _scanlimits_offset = 0xC800 + _mmap_offset _settings_offset = 0xCB20 + _mmap_offset _chan_active_offset = 0xCB80 + _mmap_offset _info_offset = 0xfe00 + _mmap_offset def __init__(self, pipe): self.errors = [] self._mmap = None if isinstance(pipe, str): self.pipe = None self.load_mmap(pipe) else: chirp_common.FileBackedRadio.__init__(self, pipe) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and filename.endswith('.dat') def _identify(radio): """Do identify handshake with TYT""" try: radio.pipe.write("\x02PROGRA") ack = radio.pipe.read(1) if ack != "A": util.hexprint(ack) raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack)) except: LOG.debug(util.hexprint(ack)) raise errors.RadioError("Unable to communicate with the radio") radio.pipe.write("M\x02") ident = radio.pipe.read(16) radio.pipe.write("A") r = radio.pipe.read(1) if r != "A": raise errors.RadioError("Ack failed") return ident def _download(radio, memsize=0x10000, blocksize=0x80): """Download from TYT TH-9800""" data = _identify(radio) LOG.info("ident:", util.hexprint(data)) offset = 0x100 for addr in range(offset, memsize, blocksize): msg = struct.pack(">cHB", "R", addr, blocksize) radio.pipe.write(msg) block = radio.pipe.read(blocksize + 4) if len(block) != (blocksize + 4): LOG.debug(util.hexprint(block)) raise errors.RadioError("Radio sent a short block") radio.pipe.write("A") ack = radio.pipe.read(1) if ack != "A": LOG.debug(util.hexprint(ack)) raise errors.RadioError("Radio NAKed block") data += block[4:] if radio.status_fn: status = chirp_common.Status() status.cur = addr status.max = memsize status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("ENDR") return memmap.MemoryMap(data) def _upload(radio, memsize=0xF400, blocksize=0x80): """Upload to TYT TH-9800""" data = _identify(radio) radio.pipe.timeout = 1 if data != radio._mmap[:radio._mmap_offset]: raise errors.RadioError( "Model mis-match: \n%s\n%s" % (util.hexprint(data), util.hexprint(radio._mmap[:radio._mmap_offset]))) # in the factory software they update the last program date when # they upload, So let's do the same today = date.today() y = today.year m = today.month d = today.day _info = radio._memobj.info ly = _info.prog_yr lm = _info.prog_mon ld = _info.prog_day LOG.debug("Updating last program date:%d/%d/%d" % (lm, ld, ly)) LOG.debug(" to today:%d/%d/%d" % (m, d, y)) _info.prog_yr = y _info.prog_mon = m _info.prog_day = d offset = 0x0100 for addr in range(offset, memsize, blocksize): mapaddr = addr + radio._mmap_offset - offset LOG.debug("addr: 0x%04X, mmapaddr: 0x%04X" % (addr, mapaddr)) msg = struct.pack(">cHB", "W", addr, blocksize) msg += radio._mmap[mapaddr:(mapaddr + blocksize)] LOG.debug(util.hexprint(msg)) radio.pipe.write(msg) ack = radio.pipe.read(1) if ack != "A": LOG.debug(util.hexprint(ack)) raise errors.RadioError("Radio did not ack block 0x%04X" % addr) if radio.status_fn: status = chirp_common.Status() status.cur = addr status.max = memsize status.msg = "Cloning to radio" radio.status_fn(status) # End of clone radio.pipe.write("ENDW") # Checksum? final_data = radio.pipe.read(3) LOG.debug("final:", util.hexprint(final_data)) @directory.register class TYTTH9800Radio(TYTTH9800Base, chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): VENDOR = "TYT" MODEL = "TH-9800" BAUD_RATE = 38400 _memsize = 65296 _mmap_offset = 0x0010 _scanlimits_offset = 0xC800 + _mmap_offset _settings_offset = 0xCB20 + _mmap_offset _chan_active_offset = 0xCB80 + _mmap_offset _info_offset = 0xfe00 + _mmap_offset @classmethod def match_model(cls, filedata, filename): if len(filedata) != cls._memsize: return False # TYT set this model for TH-7800 _AND_ TH-9800 if not filedata[0xfe18:0xfe1e] == "TH9800": return False # TH-9800 bandlimits differ from TH-7800. First band is used # (non-zero). first_bandlimit = struct.unpack("BBBBBBBBBBBBBBBB", filedata[0xfe40:0xfe50]) if all(v == 0 for v in first_bandlimit): return False return True @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ( 'This is experimental support for TH-9800 ' 'which is still under development.\n' 'Please ensure you have a good backup with OEM software.\n' 'Also please send in bug and enhancement requests!\n' 'You have been warned. Proceed at your own risk!') return rp def sync_in(self): try: self._mmap = _download(self) except Exception, e: raise errors.RadioError( "Failed to communicate with the radio: %s" % e) self.process_mmap() def sync_out(self): try: _upload(self) except Exception, e: raise errors.RadioError( "Failed to communicate with the radio: %s" % e) chirp-daily-20170714/chirp/drivers/tdxone_tdq8a.py0000644000016101777760000011104513057464312023131 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, \ InvalidValueError from textwrap import dedent LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown1:2, dtmf:1, // DTMF unknown2:1, bcl:1, // Busy Channel Lockout unknown3:3; u8 unknown4:1, scan:1, // Scan Add highpower:1, // TX Power Level wide:1, // BandWidth unknown5:4; u8 unknown6[2]; } memory[128]; #seekto 0x0E17; struct { u8 displayab:1, // Selected Display unknown1:6, unknown2:1; } settings1; #seekto 0x0E22; struct { u8 squelcha; // menu 02a Squelch Level 0xe22 u8 unknown1; u8 tdrab; // TDR A/B 0xe24 u8 roger; // menu 20 Roger Beep 0xe25 u8 timeout; // menu 16 TOT 0xe26 u8 vox; // menu 05 VOX 0xe27 u8 unknown2; u8 mdfb; // menu 27b Memory Display Format B 0xe37 u8 dw; // menu 37 DW 0xe2a u8 tdr; // menu 29 Dual Watch 0xe2b u8 voice; // menu 03 Voice Prompts 0xe2c u8 beep; // menu 01 Key Beep 0xe2d u8 ani; // menu 30 ANI 0xe2e u8 unknown3[4]; u8 pttdly; // menu 31 PTT-ID Delay 0xe33 u8 unknown4; u8 dtmfst; // menu 33 DTMF Side Tone 0xe35 u8 toa; // menu 15 TOT Pre-Alert 0xe36 u8 mdfa; // menu 27a Memory Display Format A 0xe37 u8 screv; // menu 09 Scan Resume Method 0xe38 u8 pttid; // menu 32 PTT-ID Enable 0xe39 u8 ponmsg; // menu 36 Power-on Message 0xe3a u8 pf1; // menu 28 Programmable Function Key 0xe3b u8 unknown5; u8 wtled; // menu 17 Standby LED Color 0xe3d u8 rxled; // menu 18 RX LED Color 0xe3e u8 txled; // menu 19 TX LED Color 0xe3f u8 unknown6; u8 autolk; // menu 06 Auto Key Lock 0xe41 u8 squelchb; // menu 02b Squelch Level 0xe42 u8 control; // Control Code 0xe43 u8 unknown7; u8 ach; // Selected A channel Number 0xe45 u8 unknown8[4]; u8 password[6]; // Control Password 0xe4a-0xe4f u8 unknown9[7]; u8 code[3]; // PTT ID Code 0xe57-0xe59 u8 vfomr; // Frequency/Channel Modevel 0xe5a u8 keylk; // Key Lock 0xe5b u8 unknown10[2]; u8 prioritych; // Priority Channel 0xe5e u8 bch; // Selected B channel Number 0xe5f } settings; struct vfo { u8 unknown0[8]; u8 freq[8]; u8 offset[6]; ul16 rxtone; ul16 txtone; u8 unused0:7, band:1; u8 unknown3; u8 unknown4:2, sftd:2, scode:4; u8 unknown5; u8 unknown6:1, step:3, unknown7:4; u8 txpower:1, widenarr:1, unknown8:6; }; #seekto 0x0F10; struct { struct vfo a; struct vfo b; } vfo; #seekto 0x1010; struct { u8 name[6]; u8 unknown[10]; } names[128]; """ ##### MAGICS ######################################################### # TDXone TD-Q8A magic string MSTRING_TDQ8A = "\x02PYNCRAM" #STIMEOUT = 2 LIST_DTMF = ["QT", "QT+DTMF"] LIST_VOICE = ["Off", "Chinese", "English"] LIST_OFF1TO9 = ["Off"] + list("123456789") LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"] LIST_RESUME = ["Time Operated(TO)", "Carrier Operated(CO)", "Search(SE)"] LIST_COLOR = ["Off", "Blue", "Orange", "Purple"] LIST_MODE = ["Channel", "Frequency", "Name"] LIST_PF1 = ["Off", "Scan", "Lamp", "FM Radio", "Alarm"] LIST_OFF1TO30 = ["OFF"] + ["%s" % x for x in range(1, 31)] LIST_DTMFST = ["Off", "DTMF Sidetone", "ANI Sidetone", "DTMF+ANI Sidetone"] LIST_PONMSG = ["Full", "Welcome", "Battery Voltage"] LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)] LIST_PTTID = ["BOT", "EOT", "Both"] LIST_ROGER = ["Off"] + LIST_PTTID LIST_PRIORITY = ["Off"] + ["%s" % x for x in range(1, 129)] LIST_WORKMODE = ["Frequency", "Channel"] LIST_AB = ["A", "B"] LIST_ALMOD = ["Site", "Tone", "Code"] LIST_BANDWIDTH = ["Wide", "Narrow"] LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)] LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_OFFAB = ["Off"] + LIST_AB LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)] LIST_SCODE = ["%s" % x for x in range(1, 16)] LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SHIFTD = ["Off", "+", "-"] LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)] #LIST_STEP = [str(x) for x in STEPS] LIST_TXPOWER = ["High", "Low"] LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"] LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00] CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-*" STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0] POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5), chirp_common.PowerLevel("Low", watts=1)] VALID_BANDS = [(136000000, 174000000), (400000000, 520000000)] #def _clean_buffer(radio): # radio.pipe.timeout = 0.005 # junk = radio.pipe.read(256) # radio.pipe.timeout = STIMEOUT # if junk: # LOG.debug("Got %i bytes of junk before starting" % len(junk)) def _rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: msg = "Generic error reading data from radio; check your cable." raise errors.RadioError(msg) if len(data) != amount: msg = "Error reading data from radio: not the amount of data we want." raise errors.RadioError(msg) return data def _rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) except: raise errors.RadioError("Error sending data to radio") def _make_frame(cmd, addr, length, data=""): """Pack the info in the headder format""" frame = struct.pack(">BHB", ord(cmd), addr, length) # add the data if set if len(data) != 0: frame += data # return the data return frame def _recv(radio, addr, length): """Get data from the radio """ # read 4 bytes of header hdr = _rawrecv(radio, 4) # read data data = _rawrecv(radio, length) # DEBUG LOG.info("Response:") LOG.debug(util.hexprint(hdr + data)) c, a, l = struct.unpack(">BHB", hdr) if a != addr or l != length or c != ord("W"): LOG.error("Invalid answer for block 0x%04x:" % addr) LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l)) raise errors.RadioError("Unknown response from the radio") return data def _do_ident(radio, magic): """Put the radio in PROGRAM mode""" # set the serial discipline radio.pipe.baudrate = 9600 ####radio.pipe.timeout = STIMEOUT ## flush input buffer #_clean_buffer(radio) # send request to enter program mode _rawsend(radio, magic) ack = _rawrecv(radio, 1) if ack != "\x06": if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio did not respond") _rawsend(radio, "\x02") # Ok, get the response ident = _rawrecv(radio, radio._magic_response_length) # check if response is OK if not ident.startswith("P3107"): # bad response msg = "Unexpected response, got this:" msg += util.hexprint(ident) LOG.debug(msg) raise errors.RadioError("Unexpected response from radio.") # DEBUG LOG.info("Valid response, got this:") LOG.debug(util.hexprint(ident)) _rawsend(radio, "\x06") ack = _rawrecv(radio, 1) if ack != "\x06": if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio refused clone") return ident def _ident_radio(radio): for magic in radio._magic: 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 _download(radio): """Get the memory map""" # put radio in program mode ident = _ident_radio(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = radio._mem_size / radio._recv_block_size status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in range(0, radio._mem_size, radio._recv_block_size): frame = _make_frame("R", addr, radio._recv_block_size) # DEBUG LOG.info("Request sent:") LOG.debug(util.hexprint(frame)) # sending the read request _rawsend(radio, frame) # now we read d = _recv(radio, addr, radio._recv_block_size) time.sleep(0.05) _rawsend(radio, "\x06") ack = _rawrecv(radio, 1) if ack != "\x06": raise errors.RadioError( "Radio refused to send block 0x%04x" % addr) ####time.sleep(0.05) # aggregate the data data += d # UI Update status.cur = addr / radio._recv_block_size status.msg = "Cloning from radio..." radio.status_fn(status) data += radio.MODEL.ljust(8) return data def _upload(radio): """Upload procedure""" # put radio in program mode _ident_radio(radio) addr = 0x0f80 frame = _make_frame("R", addr, radio._recv_block_size) # DEBUG LOG.info("Request sent:") LOG.debug(util.hexprint(frame)) # sending the read request _rawsend(radio, frame) # now we read d = _recv(radio, addr, radio._recv_block_size) time.sleep(0.05) _rawsend(radio, "\x06") ack = _rawrecv(radio, 1) if ack != "\x06": raise errors.RadioError( "Radio refused to send block 0x%04x" % addr) _ranges = radio._ranges # UI progress status = chirp_common.Status() status.cur = 0 status.max = radio._mem_size / radio._send_block_size status.msg = "Cloning to radio..." radio.status_fn(status) # the fun start here for start, end in _ranges: for addr in range(start, end, radio._send_block_size): # sending the data data = radio.get_mmap()[addr:addr + radio._send_block_size] frame = _make_frame("W", addr, radio._send_block_size, data) _rawsend(radio, frame) #time.sleep(0.05) # receiving the response ack = _rawrecv(radio, 1) if ack != "\x06": msg = "Bad ack writing block 0x%04x" % addr raise errors.RadioError(msg) # UI Update status.cur = addr / radio._send_block_size status.msg = "Cloning to radio..." radio.status_fn(status) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) == 0x2008: rid = data[0x2000:0x2008] print rid return rid.startswith(cls.MODEL) else: return False def _split(rf, f1, f2): """Returns False if the two freqs are in the same band (no split) or True otherwise""" # determine if the two freqs are in the same band for low, high in rf.valid_bands: if f1 >= low and f1 <= high and \ f2 >= low and f2 <= high: # if the two freqs are on the same Band this is not a split return False # if you get here is because the freq pairs are split return True @directory.register class TDXoneTDQ8A(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """TDXone TD-Q8A Radio""" VENDOR = "TDXone" MODEL = "TD-Q8A" ####_fileid = [TDQ8A_fp1, ] _magic = [MSTRING_TDQ8A, MSTRING_TDQ8A,] _magic_response_length = 8 _fw_ver_start = 0x1EF0 _recv_block_size = 0x40 _mem_size = 0x2000 #_ranges = [(0x0000, 0x2000)] # same as radio #_ranges = [(0x0010, 0x0810), # (0x0F10, 0x0F30), # (0x1010, 0x1810), # (0x0E20, 0x0E60), # (0x1F10, 0x1F30)] # in increasing order _ranges = [(0x0010, 0x0810), (0x0E20, 0x0E60), (0x0F10, 0x0F30), (0x1010, 0x1810), (0x1F10, 0x1F30)] _send_block_size = 0x10 #DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) #DTCS_CODES = sorted(chirp_common.ALL_DTCS_CODES) #POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), # chirp_common.PowerLevel("High", watts=5.00)] #VALID_BANDS = [(136000000, 174000000), # (400000000, 520000000)] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The TDXone TD-Q8A driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): """Get the radio's features""" rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = False #True rf.has_rx_dtcs = False #True rf.has_dtcs_polarity = False #True rf.has_ctone = True rf.has_cross = True rf.valid_modes = ["FM", "NFM"] #rf.valid_characters = self.VALID_CHARS rf.valid_characters = CHARSET rf.valid_name_length = 6 rf.valid_duplexes = ["", "-", "+", "split", "off"] #rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] #rf.valid_cross_modes = [ # "Tone->Tone", # "DTCS->", # "->DTCS", # "Tone->DTCS", # "DTCS->Tone", # "->Tone", # "DTCS->DTCS"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "->Tone"] rf.valid_skips = ["", "S"] #rf.valid_dtcs_codes = self.DTCS_CODES rf.memory_bounds = (1, 128) rf.valid_power_levels = POWER_LEVELS rf.valid_bands = VALID_BANDS return rf def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): """Download from radio""" try: data = _download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = memmap.MemoryMap(data) self.process_mmap() def sync_out(self): """Upload to radio""" try: _upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') 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_mem(self, number): return self._memobj.memory[number - 1] def _get_nam(self, number): return self._memobj.names[number - 1] def get_memory(self, number): _mem = self._get_mem(number) _nam = self._get_nam(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): # TX freq not set mem.duplex = "off" mem.offset = 0 else: # TX freq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset != 0: if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): mem.duplex = "split" mem.offset = int(_mem.txfreq) * 10 elif offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 if _nam.name: for char in _nam.name: try: mem.name += CHARSET[char] except IndexError: break 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 else: LOG.warn("Bug: txtone is %04x" % _mem.txtone) #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 = self.DTCS_CODES[index] #else: # LOG.warn("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 else: LOG.warn("Bug: rxtone is %04x" % _mem.rxtone) #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 = self.DTCS_CODES[index] #else: # LOG.warn("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 rxmode or txmode: mem.tmode = "Cross" mem.cross_mode = "%s->%s" % (txmode, rxmode) #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 = POWER_LEVELS[1 - _mem.highpower] mem.mode = _mem.wide and "FM" or "NFM" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("dtmf", "DTMF", RadioSettingValueList(LIST_DTMF, LIST_DTMF[_mem.dtmf])) mem.extra.append(rs) rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) return mem def _set_mem(self, number): return self._memobj.memory[number - 1] def _set_nam(self, number): return self._memobj.names[number - 1] def set_memory(self, mem): _mem = self._get_mem(mem.number) _nam = self._get_nam(mem.number) if mem.empty: _mem.set_raw("\xff" * 12 + "\xbf" +"\xff" * 3) _nam.set_raw("\xff" * 16) return #_mem.set_raw("\x00" * 16) _mem.set_raw("\xff" * 12 + "\x9f" +"\xff" * 3) _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 if _nam.name: for i in range(0, 6): try: _nam.name[i] = CHARSET.index(mem.name[i]) except IndexError: _nam.name[i] = 0xFF 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 = self.DTCS_CODES.index(mem.dtcs) + 1 # _mem.rxtone = self.DTCS_CODES.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 = self.DTCS_CODES.index(mem.dtcs) + 1 else: _mem.txtone = 0 if rxmode == "Tone": _mem.rxtone = int(mem.ctone * 10) #elif rxmode == "DTCS": # _mem.rxtone = self.DTCS_CODES.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.highpower = mem.power == POWER_LEVELS[0] for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): # """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") #other = RadioSettingGroup("other", "Other Settings") #work = RadioSettingGroup("work", "Work Mode Settings") #fm_preset = RadioSettingGroup("fm_preset", "FM Preset") #dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings") #dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings") #service = RadioSettingGroup("service", "Service Settings") #top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe, # dtmfd, service) top = RadioSettings(basic, advanced, ) # Basic settings rs = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(rs) if _mem.settings.squelcha > 0x09: val = 0x00 else: val = _mem.settings.squelcha rs = RadioSetting("squelcha", "Squelch Level A", RadioSettingValueInteger(0, 9, _mem.settings.squelcha)) basic.append(rs) if _mem.settings.squelchb > 0x09: val = 0x00 else: val = _mem.settings.squelchb rs = RadioSetting("squelchb", "Squelch Level B", RadioSettingValueInteger(0, 9, _mem.settings.squelchb)) basic.append(rs) if _mem.settings.voice > 0x02: val = 0x01 else: val = _mem.settings.voice rs = RadioSetting("settings.voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) if _mem.settings.vox > 0x0A: val = 0x00 else: val = _mem.settings.vox rs = RadioSetting("settings.vox", "VOX", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) rs = RadioSetting("settings.autolk", "Automatic Key Lock", RadioSettingValueBoolean(_mem.settings.autolk)) basic.append(rs) if _mem.settings.screv > 0x02: val = 0x01 else: val = _mem.settings.screv rs = RadioSetting("settings.screv", "Scan Resume", RadioSettingValueList( LIST_RESUME, LIST_RESUME[val])) basic.append(rs) if _mem.settings.toa > 0x0A: val = 0x00 else: val = _mem.settings.toa rs = RadioSetting("settings.toa", "Time-out Pre-Alert", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) if _mem.settings.timeout > 0x28: val = 0x03 else: val = _mem.settings.timeout rs = RadioSetting("settings.timeout", "Timeout Timer", RadioSettingValueList( LIST_TIMEOUT, LIST_TIMEOUT[val])) basic.append(rs) rs = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.wtled])) basic.append(rs) rs = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.rxled])) basic.append(rs) rs = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.txled])) basic.append(rs) rs = RadioSetting("settings.roger", "Roger Beep", RadioSettingValueList(LIST_ROGER, LIST_ROGER[ _mem.settings.roger])) basic.append(rs) rs = RadioSetting("settings.mdfa", "Display Mode (A)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfa])) basic.append(rs) rs = RadioSetting("settings.mdfb", "Display Mode (B)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfb])) basic.append(rs) rs = RadioSetting("settings.pf1", "PF1 Key Assignment", RadioSettingValueList(LIST_PF1, LIST_PF1[ _mem.settings.pf1])) basic.append(rs) rs = RadioSetting("settings.tdr", "Dual Watch(TDR)", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(rs) rs = RadioSetting("settings.ani", "ANI", RadioSettingValueBoolean(_mem.settings.ani)) basic.append(rs) if _mem.settings.pttdly > 0x0A: val = 0x00 else: val = _mem.settings.pttdly rs = RadioSetting("settings.pttdly", "PTT ID Delay", RadioSettingValueList( LIST_OFF1TO30, LIST_OFF1TO30[val])) basic.append(rs) rs = RadioSetting("settings.pttid", "When to send PTT ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[_mem.settings.pttid])) basic.append(rs) rs = RadioSetting("settings.dtmfst", "DTMF Sidetone", RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[ _mem.settings.dtmfst])) basic.append(rs) rs = RadioSetting("settings.ponmsg", "Power-On Message", RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[ _mem.settings.ponmsg])) basic.append(rs) rs = RadioSetting("settings.dw", "DW", RadioSettingValueBoolean(_mem.settings.dw)) basic.append(rs) # Advanced settings rs = RadioSetting("settings.prioritych", "Priority Channel", RadioSettingValueList(LIST_PRIORITY, LIST_PRIORITY[ _mem.settings.prioritych])) advanced.append(rs) rs = RadioSetting("settings.vfomr", "Work Mode", RadioSettingValueList(LIST_WORKMODE, LIST_WORKMODE[ _mem.settings.vfomr])) advanced.append(rs) dtmfchars = "0123456789" _codeobj = _mem.settings.code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 3, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("settings.code", "PTT-ID Code", val) def apply_code(setting, obj): code = [] for j in range(0, 3): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, _mem.settings) advanced.append(rs) _codeobj = _mem.settings.password _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 6, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("settings.password", "Control Password", val) def apply_code(setting, obj): code = [] for j in range(0, 6): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.password = code rs.set_apply_callback(apply_code, _mem.settings) advanced.append(rs) if _mem.settings.tdrab > 0x01: val = 0x00 else: val = _mem.settings.tdrab rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority", RadioSettingValueList( LIST_AB, LIST_AB[val])) advanced.append(rs) rs = RadioSetting("settings.keylk", "Key Lock", RadioSettingValueBoolean(_mem.settings.keylk)) advanced.append(rs) rs = RadioSetting("settings.control", "Control Code", RadioSettingValueBoolean(_mem.settings.control)) advanced.append(rs) return top """ # Other settings def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = _mem.sixpoweron_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val) other.append(rs) _msg = _mem.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) # DTMF encode settings if _mem.ani.dtmfon > 0xC3: val = 0x03 else: val = _mem.ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) if _mem.ani.dtmfoff > 0xC3: val = 0x03 else: val = _mem.ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) """ def set_settings(self, settings): _settings = self._memobj.settings _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): for element in settings: try: val = element.value if self._memobj.fm_presets <= 108.0 * 10 - 650: value = int(val.get_value() * 10 - 650) else: value = int(val.get_value() * 10) LOG.debug("Setting fm_presets = %s" % (value)) self._memobj.fm_presets = value except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == 0x2008: match_size = True # testing the model fingerprint match_model = model_match(cls, filedata) #if match_size and match_model: if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/th7800.py0000644000016101777760000006110412730176377021471 0ustar jenkinsnogroup00000000000000# Copyright 2014 Tom Hayward # Copyright 2014 Jens Jensen # Copyright 2014 James Lee N1DDK # Copyright 2016 Nathan Crapo (TH-7800 only) # # 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 bitwise, chirp_common, directory, errors, util, memmap import struct from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings, \ RadioSettingValueMap, zero_indexed_seq_map from chirp.chirp_common import format_freq import os import time import logging from datetime import date LOG = logging.getLogger(__name__) TH7800_MEM_FORMAT = """ struct mem { lbcd rx_freq[4]; lbcd tx_freq[4]; lbcd ctcss[2]; lbcd dtcs[2]; u8 power:2, clk_sft:1, unknown0a:2, display:1, // freq=0, name=1 scan:2; u8 fmdev:2, // wide=00, mid=01, narrow=10 scramb:1, compand:1, emphasis:1 unknown1a:2, sqlmode:1; // carrier, tone u8 rptmod:2, // off, -, + reverse:1, talkaround:1, step:4; u8 dtcs_pol:2, unknown3:4, tmode:2; lbcd offset[4]; u8 hsdtype:2, // off, 2-tone, 5-tone, dtmf unknown5a:1, am:1, unknown5b:4; u8 unknown6[3]; char name[6]; u8 empty[2]; }; #seekto 0x%04X; struct mem memory[800]; #seekto 0x%04X; struct { struct mem lower; struct mem upper; } scanlimits[5]; #seekto 0x%04X; struct { u8 unk0xdc20:5, left_sql:3; u8 apo; u8 unk0xdc22:5, backlight:3; u8 unk0xdc23; u8 beep:1, keylock:1, pttlock:2, unk0xdc24_32:2, hyper_chan:1, right_func_key:1; u8 tbst_freq:2, unk0xdc25_4:2, mute_mode:2, unk0xdc25_10:2; u8 ars:1, unk0xdc26_54:3, auto_am:1, unk0xdc26_210:3; u8 unk0xdc27_76543:5, scan_mode:1, unk0xdc27_1:1, scan_resume:1; u16 scramb_freq; u16 scramb_freq1; u8 unk0xdc2c; u8 unk0xdc2d; u8 unk0xdc2e:5, right_sql:3; u8 unk0xdc2f:8; u8 tot; u8 unk0xdc30; u8 unk0xdc31; u8 unk0xdc32; u8 unk0xdc34; u8 unk0xdc35; u8 unk0xdc36; u8 unk0xdc37; u8 p1; u8 p2; u8 p3; u8 p4; } settings; #seekto 0x%04X; u8 chan_active[128]; u8 scan_enable[128]; u8 priority[128]; #seekto 0x%04X; struct { char sn[8]; char model[8]; char code[16]; u8 empty[8]; lbcd prog_yr[2]; lbcd prog_mon; lbcd prog_day; u8 empty_10f2c[4]; } info; struct { lbcd lorx[4]; lbcd hirx[4]; lbcd lotx[4]; lbcd hitx[4]; } bandlimits[9]; """ BLANK_MEMORY = "\xFF" * 8 + "\x00\x10\x23\x00\xC0\x08\x06\x00" \ "\x00\x00\x76\x00\x00\x00" + "\xFF" * 10 DTCS_POLARITY = ["NN", "RN", "NR", "RR"] SCAN_MODES = ["", "S", "P"] MODES = ["WFM", "FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00), chirp_common.PowerLevel("Mid2", watts=10.00), chirp_common.PowerLevel("Mid1", watts=20.00), chirp_common.PowerLevel("High", watts=50.00)] BUSY_LOCK = ["off", "Carrier", "2 tone"] MICKEYFUNC = ["None", "SCAN", "SQL.OFF", "TCALL", "PPTR", "PRI", "LOW", "TONE", "MHz", "REV", "HOME", "BAND", "VFO/MR"] SQLPRESET = ["Off", "2", "5", "9", "Full"] BANDS = ["30MHz", "50MHz", "60MHz", "108MHz", "150MHz", "250MHz", "350MHz", "450MHz", "850MHz"] STEPS = [2.5, 5.0, 6.25, 7.5, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] def add_radio_setting(radio_setting_group, mem_field, ui_name, option_map, current, doc=None): setting = RadioSetting(mem_field, ui_name, RadioSettingValueMap(option_map, current)) if doc is not None: setting.set_doc(doc) radio_setting_group.append(setting) def add_radio_bool(radio_setting_group, mem_field, ui_name, current, doc=None): setting = RadioSetting(mem_field, ui_name, RadioSettingValueBoolean(bool(current))) radio_setting_group.append(setting) class TYTTH7800Base(chirp_common.Radio): """Base class for TYT TH-7800""" VENDOR = "TYT" def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 800) rf.has_bank = False rf.has_tuning_step = True rf.valid_tuning_steps = STEPS rf.can_odd_split = True rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = TMODES rf.has_ctone = False rf.valid_power_levels = POWER_LEVELS rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "#*-+" rf.valid_bands = [(108000000, 180000000), (350000000, 399995000), (400000000, 512000000)] rf.valid_skips = SCAN_MODES rf.valid_modes = MODES + ["AM"] rf.valid_name_length = 6 rf.has_settings = True return rf def process_mmap(self): self._memobj = bitwise.parse( TH7800_MEM_FORMAT % (self._mmap_offset, self._scanlimits_offset, self._settings_offset, self._chan_active_offset, self._info_offset), self._mmap) def get_active(self, banktype, num): """get active flag for channel active, scan enable, or priority banks""" bank = getattr(self._memobj, banktype) index = (num - 1) / 8 bitpos = (num - 1) % 8 mask = 2**bitpos enabled = bank[index] & mask if enabled: return True else: return False def set_active(self, banktype, num, enable=True): """set active flag for channel active, scan enable, or priority banks""" bank = getattr(self._memobj, banktype) index = (num - 1) / 8 bitpos = (num - 1) % 8 mask = 2**bitpos if enable: bank[index] |= mask else: bank[index] &= ~mask def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.empty = not self.get_active("chan_active", number) if mem.empty: return mem mem.freq = int(_mem.rx_freq) * 10 txfreq = int(_mem.tx_freq) * 10 if txfreq == mem.freq: mem.duplex = "" elif txfreq == 0: mem.duplex = "off" mem.offset = 0 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 mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_pol] mem.tmode = TMODES[int(_mem.tmode)] mem.ctone = mem.rtone = int(_mem.ctcss) / 10.0 mem.dtcs = int(_mem.dtcs) mem.name = str(_mem.name) mem.name = mem.name.replace("\xFF", " ").rstrip() if not self.get_active("scan_enable", number): mem.skip = "S" elif self.get_active("priority", number): mem.skip = "P" else: mem.skip = "" mem.mode = _mem.am and "AM" or MODES[int(_mem.fmdev)] mem.power = POWER_LEVELS[_mem.power] mem.tuning_step = STEPS[_mem.step] mem.extra = RadioSettingGroup("extra", "Extra") add_radio_setting(mem.extra, "display", "Display", zero_indexed_seq_map(["Frequency", "Name"]), _mem.display) add_radio_setting(mem.extra, "hsdtype", "HSD TYPE", zero_indexed_seq_map(["OFF", "2TON", "5TON", "DTMF"]), _mem.hsdtype) add_radio_bool(mem.extra, "clk_sft", "CLK-SFT", _mem.clk_sft) add_radio_bool(mem.extra, "compand", "Compand", _mem.compand, doc="Compress Audio") add_radio_bool(mem.extra, "talkaround", "Talk Around", _mem.talkaround, doc="Simplex mode when out of range of repeater") add_radio_bool(mem.extra, "scramb", "Scramble", _mem.scramb, doc="Frequency inversion Scramble") return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] _prev_active = self.get_active("chan_active", mem.number) self.set_active("chan_active", mem.number, not mem.empty) if mem.empty or not _prev_active: LOG.debug("initializing memory channel %d" % mem.number) _mem.set_raw(BLANK_MEMORY) if mem.empty: return _mem.rx_freq = mem.freq / 10 if 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 elif mem.duplex == "off": _mem.tx_freq = 0 _mem.offset = 0 else: _mem.tx_freq = mem.freq / 10 _mem.tmode = TMODES.index(mem.tmode) if mem.tmode == "TSQL" or mem.tmode == "DTCS": _mem.sqlmode = 1 else: _mem.sqlmode = 0 _mem.ctcss = mem.rtone * 10 _mem.dtcs = mem.dtcs _mem.dtcs_pol = DTCS_POLARITY.index(mem.dtcs_polarity) _mem.name = mem.name.ljust(6, "\xFF") # autoset display to name if filled, else show frequency if mem.extra: # mem.extra only seems to be populated when called from edit panel display = mem.extra["display"] else: display = None if mem.name: _mem.display = True if display and not display.changed(): display.value = "Name" else: _mem.display = False if display and not display.changed(): display.value = "Frequency" _mem.scan = SCAN_MODES.index(mem.skip) if mem.skip == "P": self.set_active("priority", mem.number, True) self.set_active("scan_enable", mem.number, True) elif mem.skip == "S": self.set_active("priority", mem.number, False) self.set_active("scan_enable", mem.number, False) elif mem.skip == "": self.set_active("priority", mem.number, False) self.set_active("scan_enable", mem.number, True) if mem.mode == "AM": _mem.am = True _mem.fmdev = 0 else: _mem.am = False _mem.fmdev = MODES.index(mem.mode) if mem.power: _mem.power = POWER_LEVELS.index(mem.power) else: _mem.power = 0 # low _mem.step = STEPS.index(mem.tuning_step) for setting in mem.extra: LOG.debug("@set_mem:", setting.get_name(), setting.value) setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings _info = self._memobj.info _bandlimits = self._memobj.bandlimits basic = RadioSettingGroup("basic", "Basic") info = RadioSettingGroup("info", "Model Info") top = RadioSettings(basic, info) add_radio_bool(basic, "beep", "Beep", _settings.beep) add_radio_bool(basic, "ars", "Auto Repeater Shift", _settings.ars) add_radio_setting(basic, "keylock", "Key Lock", zero_indexed_seq_map(["Manual", "Auto"]), _settings.keylock) add_radio_bool(basic, "auto_am", "Auto AM", _settings.auto_am) add_radio_setting(basic, "left_sql", "Left Squelch", zero_indexed_seq_map(SQLPRESET), _settings.left_sql) add_radio_setting(basic, "right_sql", "Right Squelch", zero_indexed_seq_map(SQLPRESET), _settings.right_sql) add_radio_setting(basic, "apo", "Auto Power off (Hours)", [("Off", 0), ("0.5", 5), ("1.0", 10), ("1.5", 15), ("2.0", 20)], _settings.apo) add_radio_setting(basic, "backlight", "Display Backlight", zero_indexed_seq_map(["Off", "1", "2", "3", "Full"]), _settings.backlight) add_radio_setting(basic, "pttlock", "PTT Lock", zero_indexed_seq_map(["Off", "Right", "Left", "Both"]), _settings.pttlock) add_radio_setting(basic, "hyper_chan", "Hyper Channel", zero_indexed_seq_map(["Manual", "Auto"]), _settings.hyper_chan) add_radio_setting(basic, "right_func_key", "Right Function Key", zero_indexed_seq_map(["Key 1", "Key 2"]), _settings.right_func_key) add_radio_setting(basic, "mute_mode", "Mute Mode", zero_indexed_seq_map(["Off", "TX", "RX", "TX RX"]), _settings.mute_mode) add_radio_setting(basic, "scan_mode", "Scan Mode", zero_indexed_seq_map(["MEM", "MSM"]), _settings.scan_mode, doc="MEM = Normal scan, bypass channels marked " "skip. MSM = Scan only channels marked priority.") add_radio_setting(basic, "scan_resume", "Scan Resume", zero_indexed_seq_map(["Time", "Busy"]), _settings.scan_resume) basic.append(RadioSetting( "tot", "Time Out Timer (minutes)", RadioSettingValueInteger(0, 30, _settings.tot))) add_radio_setting(basic, "p1", "P1 Function", zero_indexed_seq_map(MICKEYFUNC), _settings.p1) add_radio_setting(basic, "p2", "P2 Function", zero_indexed_seq_map(MICKEYFUNC), _settings.p2) add_radio_setting(basic, "p3", "P3 Function", zero_indexed_seq_map(MICKEYFUNC), _settings.p3) add_radio_setting(basic, "p4", "P4 Function", zero_indexed_seq_map(MICKEYFUNC), _settings.p4) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered rsvs = RadioSettingValueString(0, 8, _filter(_info.sn)) rsvs.set_mutable(False) rs = RadioSetting("sn", "Serial Number", rsvs) info.append(rs) rsvs = RadioSettingValueString(0, 8, _filter(_info.model)) rsvs.set_mutable(False) rs = RadioSetting("model", "Model Name", rsvs) info.append(rs) rsvs = RadioSettingValueString(0, 16, _filter(_info.code)) rsvs.set_mutable(False) rs = RadioSetting("code", "Model Code", rsvs) info.append(rs) progdate = "%d/%d/%d" % (_info.prog_mon, _info.prog_day, _info.prog_yr) rsvs = RadioSettingValueString(0, 10, progdate) rsvs.set_mutable(False) rs = RadioSetting("progdate", "Last Program Date", rsvs) info.append(rs) # Band Limits for i in range(0, len(BANDS)): rx_start = int(_bandlimits[i].lorx) * 10 if not rx_start == 0: objname = BANDS[i] + "lorx" objnamepp = BANDS[i] + " Rx Start" rsv = RadioSettingValueString(0, 10, format_freq(rx_start)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) rx_end = int(_bandlimits[i].hirx) * 10 objname = BANDS[i] + "hirx" objnamepp = BANDS[i] + " Rx end" rsv = RadioSettingValueString(0, 10, format_freq(rx_end)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) tx_start = int(_bandlimits[i].lotx) * 10 if not tx_start == 0: objname = BANDS[i] + "lotx" objnamepp = BANDS[i] + " Tx Start" rsv = RadioSettingValueString(0, 10, format_freq(tx_start)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) tx_end = int(_bandlimits[i].hitx) * 10 objname = BANDS[i] + "hitx" objnamepp = BANDS[i] + " Tx end" rsv = RadioSettingValueString(0, 10, format_freq(tx_end)) rsv.set_mutable(False) rs = RadioSetting(objname, objnamepp, rsv) info.append(rs) return top def set_settings(self, settings): _settings = self._memobj.settings _info = self._memobj.info _bandlimits = self._memobj.bandlimits for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() oldval = getattr(_settings, setting) newval = element.value LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise @directory.register class TYTTH7800File(TYTTH7800Base, chirp_common.FileBackedRadio): """TYT TH-7800 .dat file""" MODEL = "TH-7800 File" FILE_EXTENSION = "dat" _memsize = 69632 _mmap_offset = 0x1100 _scanlimits_offset = 0xC800 + _mmap_offset _settings_offset = 0xCB20 + _mmap_offset _chan_active_offset = 0xCB80 + _mmap_offset _info_offset = 0xfe00 + _mmap_offset def __init__(self, pipe): self.errors = [] self._mmap = None if isinstance(pipe, str): self.pipe = None self.load_mmap(pipe) else: chirp_common.FileBackedRadio.__init__(self, pipe) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize and filename.endswith('.dat') def _identify(radio): """Do identify handshake with TYT""" try: radio.pipe.write("\x02SPECPR") ack = radio.pipe.read(1) if ack != "A": util.hexprint(ack) raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack)) except: LOG.debug(util.hexprint(ack)) raise errors.RadioError("Unable to communicate with the radio") radio.pipe.write("G\x02") ident = radio.pipe.read(16) radio.pipe.write("A") r = radio.pipe.read(2) if r != "A": raise errors.RadioError("Ack failed") return ident def _download(radio, memsize=0x10000, blocksize=0x80): """Download from TYT TH-7800""" data = _identify(radio) LOG.info("ident:", util.hexprint(data)) offset = 0x100 for addr in range(offset, memsize, blocksize): msg = struct.pack(">cHB", "R", addr, blocksize) radio.pipe.write(msg) block = radio.pipe.read(blocksize + 4) if len(block) != (blocksize + 4): LOG.debug(util.hexprint(block)) raise errors.RadioError("Radio sent a short block") radio.pipe.write("A") ack = radio.pipe.read(1) if ack != "A": LOG.debug(util.hexprint(ack)) raise errors.RadioError("Radio NAKed block") data += block[4:] if radio.status_fn: status = chirp_common.Status() status.cur = addr status.max = memsize status.msg = "Cloning from radio" radio.status_fn(status) radio.pipe.write("ENDR") return memmap.MemoryMap(data) def _upload(radio, memsize=0xF400, blocksize=0x80): """Upload to TYT TH-7800""" data = _identify(radio) radio.pipe.timeout = 1 if data != radio._mmap[:radio._mmap_offset]: raise errors.RadioError( "Model mis-match: \n%s\n%s" % (util.hexprint(data), util.hexprint(radio._mmap[:radio._mmap_offset]))) # in the factory software they update the last program date when # they upload, So let's do the same today = date.today() y = today.year m = today.month d = today.day _info = radio._memobj.info ly = _info.prog_yr lm = _info.prog_mon ld = _info.prog_day LOG.debug("Updating last program date:%d/%d/%d" % (lm, ld, ly)) LOG.debug(" to today:%d/%d/%d" % (m, d, y)) _info.prog_yr = y _info.prog_mon = m _info.prog_day = d offset = 0x0100 for addr in range(offset, memsize, blocksize): mapaddr = addr + radio._mmap_offset - offset LOG.debug("addr: 0x%04X, mmapaddr: 0x%04X" % (addr, mapaddr)) msg = struct.pack(">cHB", "W", addr, blocksize) msg += radio._mmap[mapaddr:(mapaddr + blocksize)] LOG.debug(util.hexprint(msg)) radio.pipe.write(msg) ack = radio.pipe.read(1) if ack != "A": LOG.debug(util.hexprint(ack)) raise errors.RadioError("Radio did not ack block 0x%04X" % addr) if radio.status_fn: status = chirp_common.Status() status.cur = addr status.max = memsize status.msg = "Cloning to radio" radio.status_fn(status) # End of clone radio.pipe.write("ENDW") # Checksum? final_data = radio.pipe.read(3) LOG.debug("final:", util.hexprint(final_data)) @directory.register class TYTTH7800Radio(TYTTH7800Base, chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): VENDOR = "TYT" MODEL = "TH-7800" BAUD_RATE = 38400 _memsize = 65296 _mmap_offset = 0x0010 _scanlimits_offset = 0xC800 + _mmap_offset _settings_offset = 0xCB20 + _mmap_offset _chan_active_offset = 0xCB80 + _mmap_offset _info_offset = 0xfe00 + _mmap_offset @classmethod def match_model(cls, filedata, filename): if len(filedata) != cls._memsize: return False # TYT used TH9800 as model for TH-7800 _AND_ TH-9800. Check # for TH7800 in case they fix it or if users update the model # in their own radio. if not (filedata[0xfe18:0xfe1e] == "TH9800" or filedata[0xfe18:0xfe1e] == "TH7800"): return False # TH-7800 bandlimits differ from TH-9800. First band Invalid # (zero). first_bandlimit = struct.unpack("BBBBBBBBBBBBBBBB", filedata[0xfe40:0xfe50]) if not all(v == 0 for v in first_bandlimit): return False return True @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ( 'This is experimental support for TH-7800 ' 'which is still under development.\n' 'Please ensure you have a good backup with OEM software.\n' 'Also please send in bug and enhancement requests!\n' 'You have been warned. Proceed at your own risk!') return rp def sync_in(self): try: self._mmap = _download(self) except Exception, e: raise errors.RadioError( "Failed to communicate with the radio: %s" % e) self.process_mmap() def sync_out(self): try: _upload(self) except Exception, e: raise errors.RadioError( "Failed to communicate with the radio: %s" % e) chirp-daily-20170714/chirp/drivers/icf.py0000644000016101777760000004645513101556400021272 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 import logging from chirp import chirp_common, errors, util, memmap from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettings LOG = logging.getLogger(__name__) 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: LOG.error("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: LOG.debug("Saving data...") SAVE_PIPE.write(frame) # LOG.debug("Sending:\n%s" % util.hexprint(frame)) # LOG.debug("Sending:\n%s" % util.hexprint(hed[6:])) if cmd == 0xe4: # Uncomment to avoid cloning to the radio # return frame pass pipe.write(frame) pipe.flush() pipe.read(len(frame)) # discard echoback 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: LOG.error("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: LOG.error("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" LOG.debug("Starting HiSpeed:\n%s" % util.hexprint(buf)) radio.pipe.write(buf) radio.pipe.flush() resp = radio.pipe.read(128) LOG.debug("Response:\n%s" % util.hexprint(resp)) LOG.info("Switching to 38400 baud") radio.pipe.baudrate = 38400 buf = ("\xFE" * 14) + \ "\xEE\xEF" + \ chr(cmd) + \ radio.get_model()[:3] + \ "\x00\xFD" LOG.debug("Starting HiSpeed Clone:\n%s" % util.hexprint(buf)) radio.pipe.write(buf) radio.pipe.flush() radio.pipe.read(len(buf)) # discard echoback def _clone_from_radio(radio): md = get_model_data(radio.pipe) if md[0:4] != radio.get_model(): LOG.info("This model: %s" % util.hexprint(md[0:4])) LOG.info("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) LOG.debug("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): LOG.debug("ICF Size change from %i to %i at %04x" % (last_size, dst - src, src)) last_size = dst - src if addr != src: LOG.debug("ICF GAP %04x - %04x" % (addr, src)) addr = dst elif frame.cmd == CMD_CLONE_END: LOG.debug("End frame (%i):\n%s" % (len(frame.payload), util.hexprint(frame.payload))) LOG.debug("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: LOG.debug("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: LOG.debug("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_mappings(self): return self._radio._num_banks def get_mappings(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_mapping(self, memory, bank): self._radio._set_bank(memory.number, bank.index) def remove_memory_from_mapping(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_mapping_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_mappings(self, memory): index = self._radio._get_bank(memory.number) if index is None: return [] else: return [self.get_mappings()[index]] class IcomIndexedBankModel(IcomBankModel, chirp_common.MappingModelIndexInterface): """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_mappings(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_mapping_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") top = RadioSettings(drvopts) rs = RadioSetting("drv_clone_speed", "Use Hi-Speed Clone", RadioSettingValueBoolean(radio._can_hispeed)) drvopts.append(rs) return top def honor_speed_switch_setting(radio, settings): for element in settings: if element.get_name() == "drvopts": return honor_speed_switch_setting(radio, element) if element.get_name() == "drv_clone_speed": radio.__class__._can_hispeed = element.value.get_value() return chirp-daily-20170714/chirp/drivers/alinco.py0000644000016101777760000005757113057464312022011 0ustar jenkinsnogroup00000000000000# Copyright 2011 Dan Smith # 2016 Matt Weyland # # 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, RadioSettings from textwrap import dedent import time import logging LOG = logging.getLogger(__name__) 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): LOG.debug("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) LOG.debug("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: LOG.debug("Response was:") LOG.debug("|%s|") LOG.debug("Which I converted to:") LOG.debug(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: LOG.debug("Response was:") LOG.debug("|%s|") LOG.debug("Which I converted to:") LOG.debug(util.hexprint(data)) raise Exception("Radio returned less than 16 bytes") return data DJG7EG_MEM_FORMAT = """ #seekto 0x200; ul16 bank[50]; ul16 special_bank[7]; #seekto 0x1200; struct { u8 unknown; ul32 freq; u8 mode; u8 step; ul32 offset; u8 duplex; u8 squelch_type; u8 tx_tone; u8 rx_tone; u8 dcs; #seek 3; u8 skip; #seek 12; char name[32]; } memory[1000]; """ @directory.register class AlincoDJG7EG(AlincoStyleRadio): """Alinco DJ-G7EG""" VENDOR = "Alinco" MODEL = "DJ-G7EG" BAUD_RATE = 57600 # Those are different from the other Alinco radios. STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 150.0, 200.0, 500.0, 1000.0] DUPLEX = ["", "+", "-"] MODES = ["NFM", "FM", "AM", "WFM"] TMODES = ["", "??1", "Tone", "TSQL", "TSQL-R", "DTCS"] # This is a bit of a hack to avoid overwriting _identify() _model = "AL~DJ-G7EG" _memsize = 0x1a7c0 _range = [(500000, 1300000000)] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Ensure your firmware version is 4_10 or higher 2. Turn radio off 3. Connect your interface cable 4. Turn radio on 5. Press and release PTT 3 times while holding MONI key 6. Supported baud rates: 57600 (default) and 19200 (rotate dial while holding MONI to change) 7. Click OK """)) rp.pre_upload = _(dedent("""\ 1. Ensure your firmware version is 4_10 or higher 2. Turn radio off 3. Connect your interface cable 4. Turn radio on 5. Press and release PTT 3 times while holding MONI key 6. Supported baud rates: 57600 (default) and 19200 (rotate dial while holding MONI to change) 7. Click OK """)) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_dtcs_polarity = False rf.has_bank = False rf.has_settings = False rf.valid_modes = self.MODES rf.valid_tmodes = ["", "Tone", "TSQL", "Cross", "TSQL-R", "DTCS"] rf.valid_tuning_steps = self.STEPS rf.valid_bands = self._range rf.valid_skips = ["", "S"] rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 16 rf.memory_bounds = (0, 999) return rf def _download_chunk(self, addr): if addr % 0x40: raise Exception("Addr 0x%04x not on 64-byte boundary" % addr) cmd = "AL~F%05XR\r" % addr self._send(cmd) # Response: "\r\n[ ... data ... ]\r\n # data is encoded in hex, hence we read two chars per byte _data = self._read(2+2*64+2).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) != 64: LOG.debug("Response was:") LOG.debug("|%s|") LOG.debug("Which I converted to:") LOG.debug(util.hexprint(data)) raise Exception("Chunk from radio has wrong size") return data def _detect_baudrate_and_identify(self): if self._identify(): return True else: # Apparenly Alinco support suggests to try again at a lower baud # rate if their cable fails with the default rate. See #4355. LOG.info("Could not talk to radio. Trying again at 19200 baud") self.pipe.baudrate = 19200 return self._identify() def _download(self, limit): self._detect_baudrate_and_identify() data = "\x00"*0x200 for addr in range(0x200, limit, 0x40): data += self._download_chunk(addr) # Other Alinco drivers delay here, but doesn't seem to be necessary # for this model. if self.status_fn: status = chirp_common.Status() status.cur = addr status.max = limit status.msg = "Downloading from radio" self.status_fn(status) return memmap.MemoryMap(data) def _upload_chunk(self, addr): if addr % 0x40: raise Exception("Addr 0x%04x not on 64-byte boundary" % addr) _data = self._mmap[addr:addr+0x40] data = "".join(["%02X" % ord(x) for x in _data]) cmd = "AL~F%05XW%s\r" % (addr, data) self._send(cmd) resp = self._read(6) if resp.strip() != "OK": raise Exception("Unexpected response from radio: %s" % resp) def _upload(self, limit): if not self._detect_baudrate_and_identify(): raise Exception("I can't talk to this model") for addr in range(0x200, self._memsize, 0x40): self._upload_chunk(addr) # Other Alinco drivers delay here, but doesn't seem to be necessary # for this model. if self.status_fn: status = chirp_common.Status() status.cur = addr status.max = self._memsize status.msg = "Uploading to radio" self.status_fn(status) def process_mmap(self): self._memobj = bitwise.parse(DJG7EG_MEM_FORMAT, self._mmap) def get_memory(self, number): _mem = self._memobj.memory[number] mem = chirp_common.Memory() mem.number = number if _mem.unknown == 0: mem.empty = True else: mem.freq = int(_mem.freq) mem.mode = self.MODES[_mem.mode] mem.tuning_step = self.STEPS[_mem.step] mem.offset = int(_mem.offset) mem.duplex = self.DUPLEX[_mem.duplex] if self.TMODES[_mem.squelch_type] == "TSQL" and \ _mem.tx_tone != _mem.rx_tone: mem.tmode = "Cross" mem.cross_mode = "Tone->Tone" else: mem.tmode = self.TMODES[_mem.squelch_type] mem.rtone = ALINCO_TONES[_mem.tx_tone-1] mem.ctone = ALINCO_TONES[_mem.rx_tone-1] mem.dtcs = DCS_CODES[self.VENDOR][_mem.dcs] if _mem.skip: mem.skip = "S" # FIXME find out what every other byte is used for. Japanese? mem.name = str(_mem.name.get_raw()[::2]).rstrip('\0') return mem def set_memory(self, mem): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number] if mem.empty: _mem.unknown = 0x00 # Maybe 0 is empty, 2 is used? else: _mem.unknown = 0x02 _mem.freq = mem.freq _mem.mode = self.MODES.index(mem.mode) _mem.step = self.STEPS.index(mem.tuning_step) _mem.offset = mem.offset _mem.duplex = self.DUPLEX.index(mem.duplex) if mem.tmode == "Cross": _mem.squelch_type = self.TMODES.index("TSQL") try: _mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1 except ValueError: raise errors.UnsupportedToneError( "This radio does not support tone %.1fHz" % mem.rtone) try: _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1 except ValueError: raise errors.UnsupportedToneError( "This radio does not support tone %.1fHz" % mem.ctone) elif mem.tmode == "TSQL": _mem.squelch_type = self.TMODES.index("TSQL") # Note how the same TSQL tone is copied to both memory # locaations try: _mem.tx_tone = ALINCO_TONES.index(mem.ctone)+1 _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1 except ValueError: raise errors.UnsupportedToneError( "This radio does not support tone %.1fHz" % mem.ctone) else: _mem.squelch_type = self.TMODES.index(mem.tmode) try: _mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1 except ValueError: raise errors.UnsupportedToneError( "This radio does not support tone %.1fHz" % mem.rtone) try: _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1 except ValueError: raise errors.UnsupportedToneError( "This radio does not support tone %.1fHz" % mem.ctone) _mem.dcs = DCS_CODES[self.VENDOR].index(mem.dtcs) _mem.skip = (mem.skip == "S") _mem.name = "\x00".join(mem.name).ljust(32, "\x00") chirp-daily-20170714/chirp/drivers/kenwood_live.py0000644000016101777760000012715012735663201023220 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 import logging from chirp import chirp_common, errors, directory, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueBoolean, \ RadioSettingValueString, RadioSettingValueList, RadioSettings LOG = logging.getLogger(__name__) NOCACHE = "CHIRP_NOCACHE" in os.environ DUPLEX = {0: "", 1: "+", 2: "-"} MODES = {0: "FM", 1: "AM"} STEPS = list(chirp_common.TUNING_STEPS) STEPS.append(100.0) KENWOOD_TONES = list(chirp_common.TONES) KENWOOD_TONES.remove(159.8) KENWOOD_TONES.remove(165.5) KENWOOD_TONES.remove(171.3) KENWOOD_TONES.remove(177.3) KENWOOD_TONES.remove(183.5) KENWOOD_TONES.remove(189.9) KENWOOD_TONES.remove(196.6) KENWOOD_TONES.remove(199.5) THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"] LOCK = threading.Lock() COMMAND_RESP_BUFSIZE = 8 LAST_BAUD = 9600 LAST_DELIMITER = ("\r", " ") # The Kenwood TS-2000 uses ";" as a CAT command message delimiter, and all # others use "\n". Also, TS-2000 doesn't space delimite the command fields, # but others do. def command(ser, cmd, *args): """Send @cmd to radio via @ser""" global LOCK, LAST_DELIMITER, COMMAND_RESP_BUFSIZE start = time.time() LOCK.acquire() if args: cmd += LAST_DELIMITER[1] + LAST_DELIMITER[1].join(args) cmd += LAST_DELIMITER[0] LOG.debug("PC->RADIO: %s" % cmd.strip()) ser.write(cmd) result = "" while not result.endswith(LAST_DELIMITER[0]): result += ser.read(COMMAND_RESP_BUFSIZE) if (time.time() - start) > 0.5: LOG.error("Timeout waiting for data") break if result.endswith(LAST_DELIMITER[0]): LOG.debug("RADIO->PC: %s" % result.strip()) result = result[:-1] else: LOG.error("Giving up") LOCK.release() return result.strip() 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) global LAST_DELIMITER command_delimiters = [("\r", " "), (";", "")] for i in bauds: for delimiter in command_delimiters: LAST_DELIMITER = delimiter LOG.info("Trying ID at baud %i with delimiter \"%s\"" % (i, repr(delimiter))) ser.baudrate = i ser.write(LAST_DELIMITER[0]) ser.read(25) resp = command(ser, "ID") # most kenwood radios if " " in resp: LAST_BAUD = i return resp.split(" ")[1] # TS-2000 if "ID019" == resp: LAST_BAUD = i return "TS-2000" 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.timeout = 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 number in self._memcache and not NOCACHE: return self._memcache[number] result = command(self.pipe, *self._cmd_get_memory(number)) if result == "N" or result == "E": mem = chirp_common.Memory() mem.number = number mem.empty = True self._memcache[mem.number] = mem return mem elif " " not in result: LOG.error("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 number not in self._memcache: 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] def _kenwood_get(self, cmd): resp = command(self.pipe, cmd) if " " in resp: return resp.split(" ", 1) else: if resp == cmd: return [resp, ""] else: raise errors.RadioError("Radio refused to return %s" % cmd) def _kenwood_set(self, cmd, value): resp = command(self.pipe, cmd, value) if resp[:len(cmd)] == cmd: 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 set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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 = self._SETTINGS_OPTIONS[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: LOG.error("Unknown type %s" % element.value) class KenwoodOldLiveRadio(KenwoodLiveRadio): _kenwood_valid_tones = list(chirp_common.OLD_TONES) def set_memory(self, memory): supported_tones = list(chirp_common.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) @directory.register class THD7Radio(KenwoodOldLiveRadio): """Kenwood TH-D7""" MODEL = "TH-D7" _kenwood_split = True _SETTINGS_OPTIONS = { "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"], } 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: LOG.warn("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 get_settings(self): main = RadioSettingGroup("main", "Main") 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 = RadioSettings(main, 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", main, "Dual"), ("LK", main, "Lock"), ("LMP", main, "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", main, "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", main, "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 = self._SETTINGS_OPTIONS[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 @directory.register class THD7GRadio(THD7Radio): """Kenwood TH-D7G""" MODEL = "TH-D7G" def get_features(self): rf = super(THD7GRadio, self).get_features() rf.valid_name_length = 8 return rf @directory.register class TMD700Radio(KenwoodOldLiveRadio): """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: LOG.warn("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 @directory.register class TMV7Radio(KenwoodOldLiveRadio): """Kenwood TM-V7""" MODEL = "TM-V7" mem_upper_limit = 200 # Will be updated 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 = [(118000000, 174000000), (300000000, 520000000), (800000000, 999000000)] return rf THG71_STEPS = [5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100] @directory.register class THG71Radio(TMV7Radio): """Kenwood TH-G71""" MODEL = "TH-G71" def get_features(self): rf = TMV7Radio.get_features(self) rf.has_tuning_step = True rf.valid_tuning_steps = list(THG71_STEPS) rf.valid_name_length = 6 rf.has_sub_devices = False rf.valid_bands = [(118000000, 174000000), (320000000, 470000000), (800000000, 945000000)] return rf def _make_mem_spec(self, mem): spec = ( "%011i" % mem.freq, "%X" % THG71_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), "%09i" % mem.offset, "%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 = THG71_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] if spec[13]: mem.offset = int(spec[13]) else: mem.offset = 0 return mem 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 _kenwood_valid_tones = list(KENWOOD_TONES) 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) rf.has_settings = True 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: LOG.warn("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: LOG.warn("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 _SETTINGS_OPTIONS = { "APO": ["Off", "30min", "60min"], "BAL": ["100%:0%", "75%:25%", "50%:50%", "25%:75%", "%0:100%"], "BAT": ["Lithium", "Alkaline"], "CKEY": ["Call", "1750Hz"], "DATP": ["1200bps", "9600bps"], "LAN": ["English", "Japanese"], "MNF": ["Name", "Frequency"], "MRM": ["All Band", "Current Band"], "PT": ["100ms", "250ms", "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"], "VXD": ["250ms", "500ms", "750ms", "1s", "1.5s", "2s", "3s"], } def get_settings(self): main = RadioSettingGroup("main", "Main") aux = RadioSettingGroup("aux", "Aux") save = RadioSettingGroup("save", "Save") display = RadioSettingGroup("display", "Display") dtmf = RadioSettingGroup("dtmf", "DTMF") top = RadioSettings(main, aux, save, display, dtmf) lists = [("APO", save, "Automatic Power Off"), ("BAL", main, "Balance"), ("BAT", save, "Battery Type"), ("CKEY", aux, "CALL Key Set Up"), ("DATP", aux, "Data Packet Speed"), ("LAN", display, "Language"), ("MNF", main, "Memory Display Mode"), ("MRM", main, "Memory Recall Method"), ("PT", dtmf, "DTMF Speed"), ("SCR", main, "Scan Resume"), ("SV", save, "Battery Save"), ("VXD", aux, "VOX Drop Delay"), ] bools = [("ANT", aux, "Bar Antenna"), ("ATT", main, "Attenuator Enabled"), ("ARO", main, "Automatic Repeater Offset"), ("BEP", aux, "Beep for keypad"), ("DL", main, "Dual"), ("DLK", dtmf, "DTMF Lockout On Transmit"), ("ELK", aux, "Enable Locked Tuning"), ("LK", main, "Lock"), ("LMP", display, "Lamp"), ("NSFT", aux, "Noise Shift"), ("TH", aux, "Tx Hold for 1750"), ("TSP", dtmf, "DTMF Fast Transmission"), ("TXH", dtmf, "TX Hold DTMF"), ("TXS", main, "Transmit Inhibit"), ("VOX", aux, "VOX Enable"), ("VXB", aux, "VOX On Busy"), ] ints = [("CNT", display, "Contrast", 1, 16), ("VXG", aux, "VOX Gain", 0, 9), ] strings = [("MES", display, "Power-on Message", 8), ] for setting, group, name in bools: value = self._kenwood_get_bool(setting) rs = RadioSetting(setting, name, RadioSettingValueBoolean(value)) group.append(rs) for setting, group, name in lists: value = self._kenwood_get_int(setting) options = self._SETTINGS_OPTIONS[setting] rs = RadioSetting(setting, name, RadioSettingValueList(options, options[value])) group.append(rs) 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) 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 @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] @directory.register class TMD710Radio(KenwoodLiveRadio): """Kenwood TM-D710""" MODEL = "TM-D710" _upper = 999 _kenwood_valid_tones = list(KENWOOD_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" @directory.register class TMD710GRadio(TMD710Radio): """Kenwood TM-D710G""" MODEL = "TM-D710G" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("This radio driver is currently under development, " "and supports the same features as the TM-D710A/E. " "There are no known issues with it, but you should " "proceed with caution.") return rp THK2_DUPLEX = ["", "+", "-"] THK2_MODES = ["FM", "NFM"] THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/" @directory.register class THK2Radio(KenwoodLiveRadio): """Kenwood TH-K2""" MODEL = "TH-K2" _kenwood_valid_tones = list(KENWOOD_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 = (0, 49) 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 TM271_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] @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 = list(TM271_STEPS) 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) @directory.register class TM281Radio(TM271Radio): """Kenwood TM-281""" MODEL = "TM-281" # seems that this is a perfect clone of TM271 with just a different model @directory.register class TM471Radio(THK2Radio): """Kenwood TM-471""" MODEL = "TM-471" 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 = [(444000000, 479990000)] 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) chirp-daily-20170714/chirp/drivers/ft1d.py0000644000016101777760000020746013115200577021370 0ustar jenkinsnogroup00000000000000# Copyright 2010 Dan Smith # Copyright 2014 Angus Ainslie # # 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 import string import logging from chirp.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueList, RadioSettingValueBoolean, \ InvalidValueError from textwrap import dedent LOG = logging.getLogger(__name__) MEM_SETTINGS_FORMAT = """ #seekto 0x049a; struct { u8 vfo_a; u8 vfo_b; } squelch; #seekto 0x04c1; struct { u8 beep; } beep_select; #seekto 0x04ce; struct { u8 lcd_dimmer; u8 dtmf_delay; u8 unknown0[3]; u8 unknown1:4 lcd_contrast:4; u8 lamp; u8 unknown2[7]; u8 scan_restart; u8 unknown3; u8 scan_resume; u8 unknown4[5]; u8 tot; u8 unknown5[3]; u8 unknown6:2, scan_lamp:1, unknown7:2, dtmf_speed:1, unknown8:1, dtmf_mode:1; u8 busy_led:1, unknown9:7; u8 unknown10[2]; u8 vol_mode:1, unknown11:7; } scan_settings; #seekto 0x064a; struct { u8 unknown0[4]; u8 frequency_band; u8 unknown1:6, manual_or_mr:2; u8 unknown2:7, mr_banks:1; u8 unknown3; u16 mr_index; u16 bank_index; u16 bank_enable; u8 unknown4[5]; u8 unknown5:6, power:2; u8 unknown6:4, tune_step:4; u8 unknown7:6, duplex:2; u8 unknown8:6, tone_mode:2; u8 unknown9:2, tone:6; u8 unknown10; u8 unknown11:6, mode:2; bbcd freq0[4]; bbcd offset_freq[4]; u8 unknown12[2]; char label[16]; u8 unknown13[6]; bbcd band_lower[4]; bbcd band_upper[4]; bbcd rx_freq[4]; u8 unknown14[22]; bbcd freq1[4]; u8 unknown15[11]; u8 unknown16:3, volume:5; u8 unknown17[18]; u8 active_menu_item; u8 checksum; } vfo_info[6]; #seekto 0x047e; struct { u8 unknown1; u8 flag; u16 unknown2; struct { u8 padded_yaesu[16]; } message; } opening_message; #seekto 0x0e4a; struct { u8 memory[16]; } dtmf[10]; #seekto 0x154a; struct { u16 channel[100]; } bank_members[24]; #seekto 0x54a; struct { u16 in_use; } bank_used[24]; #seekto 0x0EFE; struct { u8 unknown[2]; u8 name[16]; } bank_info[24]; """ MEM_FORMAT = """ #seekto 0x2D4A; struct { u8 unknown0:2, mode_alt:1, // mode for FTM-3200D unknown1:5; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown2:2, tone_mode:4; u8 charsetbits[2]; char label[16]; bbcd offset[3]; u8 unknown5:2, tone:6; u8 unknown6:1, dcs:7; u8 unknown7[3]; } memory[%d]; #seekto 0x280A; struct { u8 nosubvfo:1, unknown:3, pskip:1, skip:1, used:1, valid:1; } flag[%d]; """ MEM_APRS_FORMAT = """ #seekto 0xbeca; struct { u8 rx_baud; u8 custom_symbol; struct { char callsign[6]; u8 ssid; } my_callsign; u8 unknown3:4, selected_position_comment:4; u8 unknown4; u8 set_time_manually:1, tx_interval_beacon:1, ring_beacon:1, ring_msg:1, aprs_mute:1, unknown6:1, tx_smartbeacon:1, af_dual:1; u8 unknown7:1, aprs_units_wind_mph:1, aprs_units_rain_inch:1, aprs_units_temperature_f:1 aprs_units_altitude_ft:1, unknown8:1, aprs_units_distance_m:1, aprs_units_position_mmss:1; u8 unknown9:6, aprs_units_speed:2; u8 unknown11:1, filter_other:1, filter_status:1, filter_item:1, filter_object:1, filter_weather:1, filter_position:1, filter_mic_e:1; u8 unknown12; u8 unknown13; u8 unknown14; u8 unknown15:7, latitude_sign:1; u8 latitude_degree; u8 latitude_minute; u8 latitude_second; u8 unknown16:7, longitude_sign:1; u8 longitude_degree; u8 longitude_minute; u8 longitude_second; u8 unknown17:4, selected_position:4; u8 unknown18:5, selected_beacon_status_txt:3; u8 unknown19:4, beacon_interval:4; u8 unknowni21:4, tx_delay:4; u8 unknown21b:6, gps_units_altitude_ft:1, gps_units_position_sss:1; u8 unknown20:6, gps_units_speed:2; u8 unknown21c[4]; struct { struct { char callsign[6]; u8 ssid; } entry[8]; } digi_path_7; u8 unknown22[18]; struct { char padded_string[16]; } message_macro[7]; u8 unknown23:5, selected_msg_group:3; u8 unknown24; struct { char padded_string[9]; } msg_group[8]; u8 unknown25; u8 unknown25a:2, timezone:6; u8 unknown25b[2]; u8 active_smartbeaconing; struct { u8 low_speed_mph; u8 high_speed_mph; u8 slow_rate_min; u8 fast_rate_sec; u8 turn_angle; u8 turn_slop; u8 turn_time_sec; } smartbeaconing_profile[3]; u8 unknown26:2, flash_msg:6; u8 unknown27:2, flash_grp:6; u8 unknown28:2, flash_bln:6; u8 selected_digi_path; struct { struct { char callsign[6]; u8 ssid; } entry[2]; } digi_path_3_6[4]; u8 unknown30:6, selected_my_symbol:2; u8 unknown31[3]; u8 unknown32:2, vibrate_msg:6; u8 unknown33:2, vibrate_grp:6; u8 unknown34:2, vibrate_bln:6; } aprs; #seekto 0xc26a; struct { char padded_string[60]; } aprs_beacon_status_txt[5]; #seekto 0x%04X; struct { bbcd date[3]; bbcd time[2]; u8 sequence; u8 unknown1; u8 unknown2; char sender_callsign[9]; u8 data_type; u8 yeasu_data_type; u8 unknown4:1, callsign_is_ascii:1, unknown5:6; u8 unknown6; u16 pkt_len; u8 unknown7; u16 in_use; u16 unknown8; u16 unknown9; u16 unknown10; } aprs_beacon_meta[%d]; #seekto 0x%04X; struct { char dst_callsign[9]; char path[30]; u16 flags; u8 seperator; char body[%d]; } aprs_beacon_pkt[%d]; #seekto 0x137c4; struct { u8 flag; char dst_callsign[6]; u8 dst_callsign_ssid; char path_and_body[66]; u8 unknown[70]; } aprs_message_pkt[60]; """ MEM_BACKTRACK_FORMAT = """ #seekto 0xdf06; struct { u8 status; // 01 full 08 empty u8 reserved0; // 00 bbcd year; // 17 bbcd mon; // 06 bbcd day; // 01 u8 reserved1; // 06 bbcd hour; // 21 bbcd min; // xx u8 reserved2; // 00 u8 reserved3; // 00 char NShemi[1]; char lat[3]; char lat_min[2]; char lat_dec_sec[4]; char WEhemi[1]; char lon[3]; char lon_min[2]; char lon_dec_sec[4]; } backtrack[3]; """ MEM_CHECKSUM_FORMAT = """ #seekto 0x1FDC9; 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 at index 2 (?) SKIPS = ["", "S", "P"] FT1_DTMF_CHARS = list("0123456789ABCD*#-") 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 FT1Bank(chirp_common.NamedBank): """A FT1D bank""" def get_name(self): _bank = self._model._radio._memobj.bank_info[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 FT1BankModel(chirp_common.BankModel): """A FT1D bank model""" def __init__(self, radio, name='Banks'): super(FT1BankModel, self).__init__(radio, name) _banks = self._radio._memobj.bank_info self._bank_mappings = [] for index, _bank in enumerate(_banks): bank = FT1Bank(self, "%i" % index, "BANK-%i" % index) bank.index = index self._bank_mappings.append(bank) def get_num_mappings(self): return len(self._bank_mappings) def get_mappings(self): return self._bank_mappings def _channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return set() _members = self._radio._memobj.bank_members[bank.index] return set([int(ch) + 1 for ch in _members.channel if ch != 0xFFFF]) def update_vfo(self): chosen_bank = [None, None] chosen_mr = [None, None] flags = self._radio._memobj.flag # Find a suitable bank and MR for VFO A and B. for bank in self.get_mappings(): for channel in self._channel_numbers_in_bank(bank): chosen_bank[0] = bank.index chosen_mr[0] = channel if not flags[channel].nosubvfo: chosen_bank[1] = bank.index chosen_mr[1] = channel break if chosen_bank[1]: break for vfo_index in (0, 1): # 3 VFO info structs are stored as 3 pairs of (master, backup) vfo = self._radio._memobj.vfo_info[vfo_index * 2] vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1] if vfo.checksum != vfo_bak.checksum: LOG.warn("VFO settings are inconsistent with backup") else: if ((chosen_bank[vfo_index] is None) and (vfo.bank_index != 0xFFFF)): LOG.info("Disabling banks for VFO %d" % vfo_index) vfo.bank_index = 0xFFFF vfo.mr_index = 0xFFFF vfo.bank_enable = 0xFFFF elif ((chosen_bank[vfo_index] is not None) and (vfo.bank_index == 0xFFFF)): LOG.info("Enabling banks for VFO %d" % vfo_index) vfo.bank_index = chosen_bank[vfo_index] vfo.mr_index = chosen_mr[vfo_index] vfo.bank_enable = 0x0000 vfo_bak.bank_index = vfo.bank_index vfo_bak.mr_index = vfo.mr_index vfo_bak.bank_enable = vfo.bank_enable def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.bank_members[bank.index] if len(channels_in_bank) > len(_members.channel): raise Exception("Too many entries in bank %d" % bank.index) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channel[index] = channel_number - 1 empty = index + 1 for index in range(empty, len(_members.channel)): _members.channel[index] = 0xFFFF def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0x06 self.update_vfo() def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) if not channels_in_bank: _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0xFFFF self.update_vfo() def get_mapping_memories(self, bank): memories = [] for channel in self._channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._channel_numbers_in_bank(bank): banks.append(bank) return banks # Note: other radios like FTM3200Radio subclass this radio @directory.register class FT1Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT1DR""" BAUD_RATE = 38400 VENDOR = "Yaesu" MODEL = "FT-1D" VARIANT = "R" _model = "AH44M" _memsize = 130507 _block_lengths = [10, 130497] _block_size = 32 _mem_params = (900, # size of memories array 900, # size of flags array 0xFECA, # APRS beacon metadata address. 60, # Number of beacons stored. 0x1064A, # APRS beacon content address. 134, # Length of beacon data stored. 60) # Number of beacons stored. _has_vibrate = False _has_af_dual = True _SG_RE = re.compile(r"(?P[-+NESW]?)(?P[\d]+)[\s\.,]*" "(?P[\d]*)[\s\']*(?P[\d]*)") _RX_BAUD = ("off", "1200 baud", "9600 baud") _TX_DELAY = ("100ms", "150ms", "200ms", "250ms", "300ms", "400ms", "500ms", "750ms", "1000ms") _WIND_UNITS = ("m/s", "mph") _RAIN_UNITS = ("mm", "inch") _TEMP_UNITS = ("C", "F") _ALT_UNITS = ("m", "ft") _DIST_UNITS = ("km", "mile") _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"") _SPEED_UNITS = ("km/h", "knot", "mph") _TIME_SOURCE = ("manual", "GPS") _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30", "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30", "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30", "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30", "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30", "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30", "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30", "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30", "+11:00", "+11:30") _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing") _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3") _BEACON_INT = ("30s", "1m", "2m", "3m", "5m", "10m", "15m", "20m", "30m", "60m") _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4", "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8") _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2", "Message Group 3", "Message Group 4", "Message Group 5", "Message Group 6", "Message Group 7", "Message Group 8") _POSITIONS = ("GPS", "Manual Latitude/Longitude", "Manual Latitude/Longitude", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9") _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds", "10 seconds", "20 seconds", "30 seconds", "60 seconds", "CONTINUOUS", "every 2 seconds", "every 3 seconds", "every 4 seconds", "every 5 seconds", "every 6 seconds", "every 7 seconds", "every 8 seconds", "every 9 seconds", "every 10 seconds", "every 20 seconds", "every 30 seconds", "every 40 seconds", "every 50 seconds", "every minute", "every 2 minutes", "every 3 minutes", "every 4 minutes", "every 5 minutes", "every 6 minutes", "every 7 minutes", "every 8 minutes", "every 9 minutes", "every 10 minutes") _BEEP_SELECT = ("Off", "Key+Scan", "Key") _SQUELCH = ["%d" % x for x in range(0, 16)] _VOLUME = ["%d" % x for x in range(0, 33)] _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal") _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \ ["Busy", "Hold"] _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \ ["%.1fs" % (0.5 * x) for x in range(2, 21)] _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + ["Continuous", "OFF"] _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)] _LCD_DIMMER = ["Level %d" % x for x in range(1, 7)] _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)] _OFF_ON = ("Off", "On") _VOL_MODE = ("Normal", "Auto Back") _DTMF_MODE = ("Manual", "Auto") _DTMF_SPEED = ("50ms", "100ms") _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms") _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected") _BACKTRACK_STATUS = ("Valid", "Invalid") _NS_HEMI = ("N", "S") _WE_HEMI = ("W", "E") @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA terminal. 3. Press and hold in the [F] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [BAND] key to send image.""" )) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA terminal. 3. Press and hold in the [F] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [Dx] key ("-WAIT-" will appear on the LCD).""")) return rp def process_mmap(self): mem_format = MEM_SETTINGS_FORMAT + MEM_FORMAT + MEM_APRS_FORMAT + \ MEM_BACKTRACK_FORMAT + MEM_CHECKSUM_FORMAT self._memobj = bitwise.parse(mem_format % self._mem_params, 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 rf.has_settings = True return rf def get_raw_memory(self, number): return "\n".join([repr(self._memobj.memory[number - 1]), repr(self._memobj.flag[number - 1])]) def _checksums(self): return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8), yaesu_clone.YaesuChecksum(0x06CA, 0x0748), yaesu_clone.YaesuChecksum(0x074A, 0x07C8), yaesu_clone.YaesuChecksum(0x07CA, 0x0848), yaesu_clone.YaesuChecksum(0x0000, 0x1FDC9)] @staticmethod def _add_ff_pad(val, length): return val.ljust(length, "\xFF")[:length] @classmethod def _strip_ff_pads(cls, messages): result = [] for msg_text in messages: result.append(str(msg_text).rstrip("\xFF")) return result 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] self._get_tmode(mem, _mem) mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) mem.mode = self._decode_mode(_mem) mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = STEPS[_mem.tune_step] mem.power = self._decode_power_level(_mem) mem.skip = flag.pskip and "P" or flag.skip and "S" or "" mem.name = self._decode_label(_mem) return mem def _decode_label(self, mem): charset = ''.join(CHARSET).ljust(256, '.') return str(mem.label).rstrip("\xFF").translate(charset) def _encode_label(self, mem): label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()]) return self._add_ff_pad(label, 16) def _encode_charsetbits(self, mem): # We only speak english here in chirpville return [0x00, 0x00] def _decode_power_level(self, mem): return POWER_LEVELS[3 - mem.power] def _encode_power_level(self, mem): return 3 - POWER_LEVELS.index(mem.power) def _decode_mode(self, mem): return MODES[mem.mode] def _encode_mode(self, mem): return MODES.index(mem.mode) def _get_tmode(self, mem, _mem): mem.tmode = TMODES[_mem.tone_mode] def _set_tmode(self, _mem, mem): _mem.tone_mode = TMODES.index(mem.tmode) def _set_mode(self, _mem, mem): _mem.mode = self._encode_mode(mem) def _debank(self, mem): bm = self.get_bank_model() for bank in bm.get_memory_mappings(mem): bm.remove_memory_from_mapping(mem, bank) def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] flag = self._memobj.flag[mem.number - 1] self._debank(mem) if not mem.empty and not flag.valid: self._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) self._set_tmode(_mem, mem) _mem.duplex = DUPLEX.index(mem.duplex) self._set_mode(_mem, mem) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power: _mem.power = self._encode_power_level(mem) else: _mem.power = 0 _mem.label = self._encode_label(mem) charsetbits = self._encode_charsetbits(mem) _mem.charsetbits[0], _mem.charsetbits[1] = charsetbits flag.skip = mem.skip == "S" flag.pskip = mem.skip == "P" @classmethod def _wipe_memory(cls, mem): mem.set_raw("\x00" * (mem.size() / 8)) mem.unknown1 = 0x05 def get_bank_model(self): return FT1BankModel(self) @classmethod def _digi_path_to_str(cls, path): path_cmp = [] for entry in path.entry: callsign = str(entry.callsign).rstrip("\xFF") if not callsign: break path_cmp.append("%s-%d" % (callsign, entry.ssid)) return ",".join(path_cmp) @staticmethod def _latlong_sanity(sign, l_d, l_m, l_s, is_lat): if sign not in (0, 1): sign = 0 if is_lat: d_max = 90 else: d_max = 180 if l_d < 0 or l_d > d_max: l_d = 0 l_m = 0 l_s = 0 if l_m < 0 or l_m > 60: l_m = 0 l_s = 0 if l_s < 0 or l_s > 60: l_s = 0 return sign, l_d, l_m, l_s @classmethod def _latlong_to_str(cls, sign, l_d, l_m, l_s, is_lat, to_sexigesimal=True): sign, l_d, l_m, l_s = cls._latlong_sanity(sign, l_d, l_m, l_s, is_lat) mult = sign and -1 or 1 if to_sexigesimal: return "%d,%d'%d\"" % (mult * l_d, l_m, l_s) return "%0.5f" % (mult * l_d + (l_m / 60.0) + (l_s / (60.0 * 60.0))) @classmethod def _str_to_latlong(cls, lat_long, is_lat): sign = 0 result = [0, 0, 0] lat_long = lat_long.strip() if not lat_long: return 1, 0, 0, 0 try: # DD.MMMMM is the simple case, try that first. val = float(lat_long) if val < 0: sign = 1 val = abs(val) result[0] = int(val) result[1] = int(val * 60) % 60 result[2] = int(val * 3600) % 60 except ValueError: # Try DD MM'SS" if DD.MMMMM failed. match = cls._SG_RE.match(lat_long.strip()) if match: if match.group("sign") and (match.group("sign") in "SE-"): sign = 1 else: sign = 0 if match.group("d"): result[0] = int(match.group("d")) if match.group("m"): result[1] = int(match.group("m")) if match.group("s"): result[2] = int(match.group("s")) elif len(lat_long) > 4: raise Exception("Lat/Long should be DD MM'SS\" or DD.MMMMM") return cls._latlong_sanity(sign, result[0], result[1], result[2], is_lat) def _get_aprs_general_settings(self): menu = RadioSettingGroup("aprs_general", "APRS General") aprs = self._memobj.aprs val = RadioSettingValueString( 0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF")) rs = RadioSetting("aprs.my_callsign.callsign", "My Callsign", val) rs.set_apply_callback(self.apply_callsign, aprs.my_callsign) menu.append(rs) val = RadioSettingValueList( chirp_common.APRS_SSID, chirp_common.APRS_SSID[aprs.my_callsign.ssid]) rs = RadioSetting("aprs.my_callsign.ssid", "My SSID", val) menu.append(rs) val = RadioSettingValueList(self._MY_SYMBOL, self._MY_SYMBOL[aprs.selected_my_symbol]) rs = RadioSetting("aprs.selected_my_symbol", "My Symbol", val) menu.append(rs) symbols = list(chirp_common.APRS_SYMBOLS) selected = aprs.custom_symbol if aprs.custom_symbol >= len(chirp_common.APRS_SYMBOLS): symbols.append("%d" % aprs.custom_symbol) selected = len(symbols) - 1 val = RadioSettingValueList(symbols, symbols[selected]) rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol", val) rs.set_apply_callback(self.apply_custom_symbol, aprs) menu.append(rs) val = RadioSettingValueList( chirp_common.APRS_POSITION_COMMENT, chirp_common.APRS_POSITION_COMMENT[aprs.selected_position_comment]) rs = RadioSetting("aprs.selected_position_comment", "Position Comment", val) menu.append(rs) latitude = self._latlong_to_str(aprs.latitude_sign, aprs.latitude_degree, aprs.latitude_minute, aprs.latitude_second, True, aprs.aprs_units_position_mmss) longitude = self._latlong_to_str(aprs.longitude_sign, aprs.longitude_degree, aprs.longitude_minute, aprs.longitude_second, False, aprs.aprs_units_position_mmss) # TODO: Rebuild this when aprs_units_position_mmss changes. # TODO: Rebuild this when latitude/longitude change. # TODO: Add saved positions p1 - p10 to memory map. position_str = list(self._POSITIONS) # position_str[1] = "%s %s" % (latitude, longitude) # position_str[2] = "%s %s" % (latitude, longitude) val = RadioSettingValueList(position_str, position_str[aprs.selected_position]) rs = RadioSetting("aprs.selected_position", "My Position", val) menu.append(rs) val = RadioSettingValueString(0, 10, latitude) rs = RadioSetting("latitude", "Manual Latitude", val) rs.set_apply_callback(self.apply_lat_long, aprs) menu.append(rs) val = RadioSettingValueString(0, 11, longitude) rs = RadioSetting("longitude", "Manual Longitude", val) rs.set_apply_callback(self.apply_lat_long, aprs) menu.append(rs) val = RadioSettingValueList( self._TIME_SOURCE, self._TIME_SOURCE[aprs.set_time_manually]) rs = RadioSetting("aprs.set_time_manually", "Time Source", val) menu.append(rs) val = RadioSettingValueList(self._TZ, self._TZ[aprs.timezone]) rs = RadioSetting("aprs.timezone", "Timezone", val) menu.append(rs) val = RadioSettingValueList(self._SPEED_UNITS, self._SPEED_UNITS[aprs.aprs_units_speed]) rs = RadioSetting("aprs.aprs_units_speed", "APRS Speed Units", val) menu.append(rs) val = RadioSettingValueList(self._SPEED_UNITS, self._SPEED_UNITS[aprs.gps_units_speed]) rs = RadioSetting("aprs.gps_units_speed", "GPS Speed Units", val) menu.append(rs) val = RadioSettingValueList( self._ALT_UNITS, self._ALT_UNITS[aprs.aprs_units_altitude_ft]) rs = RadioSetting("aprs.aprs_units_altitude_ft", "APRS Altitude Units", val) menu.append(rs) val = RadioSettingValueList( self._ALT_UNITS, self._ALT_UNITS[aprs.gps_units_altitude_ft]) rs = RadioSetting("aprs.gps_units_altitude_ft", "GPS Altitude Units", val) menu.append(rs) val = RadioSettingValueList( self._POS_UNITS, self._POS_UNITS[aprs.aprs_units_position_mmss]) rs = RadioSetting("aprs.aprs_units_position_mmss", "APRS Position Format", val) menu.append(rs) val = RadioSettingValueList( self._POS_UNITS, self._POS_UNITS[aprs.gps_units_position_sss]) rs = RadioSetting("aprs.gps_units_position_sss", "GPS Position Format", val) menu.append(rs) val = RadioSettingValueList( self._DIST_UNITS, self._DIST_UNITS[aprs.aprs_units_distance_m]) rs = RadioSetting("aprs.aprs_units_distance_m", "APRS Distance Units", val) menu.append(rs) val = RadioSettingValueList(self._WIND_UNITS, self._WIND_UNITS[aprs.aprs_units_wind_mph]) rs = RadioSetting("aprs.aprs_units_wind_mph", "APRS Wind Speed Units", val) menu.append(rs) val = RadioSettingValueList( self._RAIN_UNITS, self._RAIN_UNITS[aprs.aprs_units_rain_inch]) rs = RadioSetting("aprs.aprs_units_rain_inch", "APRS Rain Units", val) menu.append(rs) val = RadioSettingValueList( self._TEMP_UNITS, self._TEMP_UNITS[aprs.aprs_units_temperature_f]) rs = RadioSetting("aprs.aprs_units_temperature_f", "APRS Temperature Units", val) menu.append(rs) return menu def _get_aprs_msgs(self): menu = RadioSettingGroup("aprs_msg", "APRS Messages") aprs_msg = self._memobj.aprs_message_pkt for index in range(0, 60): if aprs_msg[index].flag != 255: astring = \ str(aprs_msg[index].dst_callsign).partition("\xFF")[0] val = RadioSettingValueString( 0, 9, chirp_common.sanitize_string(astring) + "-%d" % aprs_msg[index].dst_callsign_ssid) val.set_mutable(False) rs = RadioSetting( "aprs_msg.dst_callsign%d" % index, "Dst Callsign %d" % index, val) menu.append(rs) astring = \ str(aprs_msg[index].path_and_body).partition("\xFF")[0] val = RadioSettingValueString( 0, 66, chirp_common.sanitize_string(astring)) val.set_mutable(False) rs = RadioSetting( "aprs_msg.path_and_body%d" % index, "Body", val) menu.append(rs) return menu def _get_aprs_beacons(self): menu = RadioSettingGroup("aprs_beacons", "APRS Beacons") aprs_beacon = self._memobj.aprs_beacon_pkt aprs_meta = self._memobj.aprs_beacon_meta for index in range(0, 60): # There is probably a more pythonesque way to do this if int(aprs_meta[index].sender_callsign[0]) != 255: callsign = str(aprs_meta[index].sender_callsign).rstrip("\xFF") # LOG.debug("Callsign %s %s" % (callsign, list(callsign))) val = RadioSettingValueString(0, 9, callsign) val.set_mutable(False) rs = RadioSetting( "aprs_beacon.src_callsign%d" % index, "SRC Callsign %d" % index, val) menu.append(rs) if int(aprs_beacon[index].dst_callsign[0]) != 255: val = RadioSettingValueString( 0, 9, str(aprs_beacon[index].dst_callsign).rstrip("\xFF")) val.set_mutable(False) rs = RadioSetting( "aprs_beacon.dst_callsign%d" % index, "DST Callsign %d" % index, val) menu.append(rs) if int(aprs_meta[index].sender_callsign[0]) != 255: date = "%02d/%02d/%02d" % ( aprs_meta[index].date[0], aprs_meta[index].date[1], aprs_meta[index].date[2]) val = RadioSettingValueString(0, 8, date) val.set_mutable(False) rs = RadioSetting("aprs_beacon.date%d" % index, "Date", val) menu.append(rs) time = "%02d:%02d" % ( aprs_meta[index].time[0], aprs_meta[index].time[1]) val = RadioSettingValueString(0, 5, time) val.set_mutable(False) rs = RadioSetting("aprs_beacon.time%d" % index, "Time", val) menu.append(rs) if int(aprs_beacon[index].dst_callsign[0]) != 255: path = str(aprs_beacon[index].path).replace("\x00", " ") path = ''.join(c for c in path if c in string.printable).strip() path = str(path).replace("\xE0", "*") # LOG.debug("path %s %s" % (path, list(path))) val = RadioSettingValueString(0, 32, path) val.set_mutable(False) rs = RadioSetting( "aprs_beacon.path%d" % index, "Digipath", val) menu.append(rs) body = str(aprs_beacon[index].body).rstrip("\xFF") checksum = body[-2:] body = ''.join(s for s in body[:-2] if s in string.printable).translate( None, "\x09\x0a\x0b\x0c\x0d") try: val = RadioSettingValueString(0, 134, body.strip()) except Exception as e: LOG.error("Error in APRS beacon at index %s", index) raise e val.set_mutable(False) rs = RadioSetting("aprs_beacon.body%d" % index, "Body", val) menu.append(rs) return menu def _get_aprs_rx_settings(self): menu = RadioSettingGroup("aprs_rx", "APRS Receive") aprs = self._memobj.aprs val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud]) rs = RadioSetting("aprs.rx_baud", "Modem RX", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.aprs_mute) rs = RadioSetting("aprs.aprs_mute", "APRS Mute", val) menu.append(rs) if self._has_af_dual: val = RadioSettingValueBoolean(aprs.af_dual) rs = RadioSetting("aprs.af_dual", "AF Dual", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.ring_msg) rs = RadioSetting("aprs.ring_msg", "Ring on Message RX", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.ring_beacon) rs = RadioSetting("aprs.ring_beacon", "Ring on Beacon RX", val) menu.append(rs) val = RadioSettingValueList(self._FLASH, self._FLASH[aprs.flash_msg]) rs = RadioSetting("aprs.flash_msg", "Flash on personal message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH, self._FLASH[aprs.vibrate_msg]) rs = RadioSetting("aprs.vibrate_msg", "Vibrate on personal message", val) menu.append(rs) val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs.flash_bln]) rs = RadioSetting("aprs.flash_bln", "Flash on bulletin message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs.vibrate_bln]) rs = RadioSetting("aprs.vibrate_bln", "Vibrate on bulletin message", val) menu.append(rs) val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs.flash_grp]) rs = RadioSetting("aprs.flash_grp", "Flash on group message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs.vibrate_grp]) rs = RadioSetting("aprs.vibrate_grp", "Vibrate on group message", val) menu.append(rs) filter_val = [m.padded_string for m in aprs.msg_group] filter_val = self._strip_ff_pads(filter_val) for index, filter_text in enumerate(filter_val): val = RadioSettingValueString(0, 9, filter_text) rs = RadioSetting("aprs.msg_group_%d" % index, "Message Group %d" % (index + 1), val) menu.append(rs) rs.set_apply_callback(self.apply_ff_padded_string, aprs.msg_group[index]) # TODO: Use filter_val as the list entries and update it on edit. val = RadioSettingValueList( self._MSG_GROUP_NAMES, self._MSG_GROUP_NAMES[aprs.selected_msg_group]) rs = RadioSetting("aprs.selected_msg_group", "Selected Message Group", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_mic_e) rs = RadioSetting("aprs.filter_mic_e", "Receive Mic-E Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_position) rs = RadioSetting("aprs.filter_position", "Receive Position Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_weather) rs = RadioSetting("aprs.filter_weather", "Receive Weather Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_object) rs = RadioSetting("aprs.filter_object", "Receive Object Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_item) rs = RadioSetting("aprs.filter_item", "Receive Item Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_status) rs = RadioSetting("aprs.filter_status", "Receive Status Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_other) rs = RadioSetting("aprs.filter_other", "Receive Other Beacons", val) menu.append(rs) return menu def _get_aprs_tx_settings(self): menu = RadioSettingGroup("aprs_tx", "APRS Transmit") aprs = self._memobj.aprs beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon val = RadioSettingValueList(self._BEACON_TYPE, self._BEACON_TYPE[beacon_type]) rs = RadioSetting("aprs.transmit", "TX Beacons", val) rs.set_apply_callback(self.apply_beacon_type, aprs) menu.append(rs) val = RadioSettingValueList(self._TX_DELAY, self._TX_DELAY[aprs.tx_delay]) rs = RadioSetting("aprs.tx_delay", "TX Delay", val) menu.append(rs) val = RadioSettingValueList(self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval]) rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val) menu.append(rs) desc = [] status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt] status = self._strip_ff_pads(status) for index, msg_text in enumerate(status): val = RadioSettingValueString(0, 60, msg_text) desc.append("Beacon Status Text %d" % (index + 1)) rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1], val) rs.set_apply_callback(self.apply_ff_padded_string, self._memobj.aprs_beacon_status_txt[index]) menu.append(rs) val = RadioSettingValueList(desc, desc[aprs.selected_beacon_status_txt]) rs = RadioSetting("aprs.selected_beacon_status_txt", "Beacon Status Text", val) menu.append(rs) message_macro = [m.padded_string for m in aprs.message_macro] message_macro = self._strip_ff_pads(message_macro) for index, msg_text in enumerate(message_macro): val = RadioSettingValueString(0, 16, msg_text) rs = RadioSetting("aprs.message_macro_%d" % index, "Message Macro %d" % (index + 1), val) rs.set_apply_callback(self.apply_ff_padded_string, aprs.message_macro[index]) menu.append(rs) path_str = list(self._DIGI_PATHS) path_str[3] = self._digi_path_to_str(aprs.digi_path_3_6[0]) val = RadioSettingValueString(0, 22, path_str[3]) rs = RadioSetting("aprs.digi_path_3", "Digi Path 4 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[0]) menu.append(rs) path_str[4] = self._digi_path_to_str(aprs.digi_path_3_6[1]) val = RadioSettingValueString(0, 22, path_str[4]) rs = RadioSetting("aprs.digi_path_4", "Digi Path 5 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[1]) menu.append(rs) path_str[5] = self._digi_path_to_str(aprs.digi_path_3_6[2]) val = RadioSettingValueString(0, 22, path_str[5]) rs = RadioSetting("aprs.digi_path_5", "Digi Path 6 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[2]) menu.append(rs) path_str[6] = self._digi_path_to_str(aprs.digi_path_3_6[3]) val = RadioSettingValueString(0, 22, path_str[6]) rs = RadioSetting("aprs.digi_path_6", "Digi Path 7 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[3]) menu.append(rs) path_str[7] = self._digi_path_to_str(aprs.digi_path_7) val = RadioSettingValueString(0, 88, path_str[7]) rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7) menu.append(rs) # Show friendly messages for empty slots rather than blanks. # TODO: Rebuild this when digi_path_[34567] change. # path_str[3] = path_str[3] or self._DIGI_PATHS[3] # path_str[4] = path_str[4] or self._DIGI_PATHS[4] # path_str[5] = path_str[5] or self._DIGI_PATHS[5] # path_str[6] = path_str[6] or self._DIGI_PATHS[6] # path_str[7] = path_str[7] or self._DIGI_PATHS[7] path_str[3] = self._DIGI_PATHS[3] path_str[4] = self._DIGI_PATHS[4] path_str[5] = self._DIGI_PATHS[5] path_str[6] = self._DIGI_PATHS[6] path_str[7] = self._DIGI_PATHS[7] val = RadioSettingValueList(path_str, path_str[aprs.selected_digi_path]) rs = RadioSetting("aprs.selected_digi_path", "Selected Digi Path", val) menu.append(rs) return menu def _get_aprs_smartbeacon(self): menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon") aprs = self._memobj.aprs val = RadioSettingValueList( self._SMARTBEACON_PROFILE, self._SMARTBEACON_PROFILE[aprs.active_smartbeaconing]) rs = RadioSetting("aprs.active_smartbeaconing", "SmartBeacon profile", val) menu.append(rs) for profile in range(3): pfx = "type%d" % (profile + 1) path = "aprs.smartbeaconing_profile[%d]" % profile prof = aprs.smartbeaconing_profile[profile] low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph) high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph) low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1) rs = RadioSetting("%s.low_speed_mph" % path, "%s Low Speed (mph)" % pfx, low_val) menu.append(rs) rs = RadioSetting("%s.high_speed_mph" % path, "%s High Speed (mph)" % pfx, high_val) menu.append(rs) val = RadioSettingValueInteger(1, 100, prof.slow_rate_min) rs = RadioSetting("%s.slow_rate_min" % path, "%s Slow rate (minutes)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec) rs = RadioSetting("%s.fast_rate_sec" % path, "%s Fast rate (seconds)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(5, 90, prof.turn_angle) rs = RadioSetting("%s.turn_angle" % path, "%s Turn angle (degrees)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(1, 255, prof.turn_slop) rs = RadioSetting("%s.turn_slop" % path, "%s Turn slop" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(5, 180, prof.turn_time_sec) rs = RadioSetting("%s.turn_time_sec" % path, "%s Turn time (seconds)" % pfx, val) menu.append(rs) return menu def _get_dtmf_settings(self): menu = RadioSettingGroup("dtmf_settings", "DTMF") dtmf = self._memobj.scan_settings val = RadioSettingValueList( self._DTMF_MODE, self._DTMF_MODE[dtmf.dtmf_mode]) rs = RadioSetting("scan_settings.dtmf_mode", "DTMF Mode", val) menu.append(rs) val = RadioSettingValueList( self._DTMF_SPEED, self._DTMF_SPEED[dtmf.dtmf_speed]) rs = RadioSetting( "scan_settings.dtmf_speed", "DTMF AutoDial Speed", val) menu.append(rs) val = RadioSettingValueList( self._DTMF_DELAY, self._DTMF_DELAY[dtmf.dtmf_delay]) rs = RadioSetting( "scan_settings.dtmf_delay", "DTMF AutoDial Delay", val) menu.append(rs) for i in range(10): name = "dtmf_%02d" % i dtmfsetting = self._memobj.dtmf[i] dtmfstr = "" for c in dtmfsetting.memory: if c == 0xFF: break if c < len(FT1_DTMF_CHARS): dtmfstr += FT1_DTMF_CHARS[c] dtmfentry = RadioSettingValueString(0, 16, dtmfstr) dtmfentry.set_charset(FT1_DTMF_CHARS + list("abcd ")) rs = RadioSetting(name, name.upper(), dtmfentry) rs.set_apply_callback(self.apply_dtmf, i) menu.append(rs) return menu def _get_misc_settings(self): menu = RadioSettingGroup("misc_settings", "Misc") scan_settings = self._memobj.scan_settings val = RadioSettingValueList( self._LCD_DIMMER, self._LCD_DIMMER[scan_settings.lcd_dimmer]) rs = RadioSetting("scan_settings.lcd_dimmer", "LCD Dimmer", val) menu.append(rs) val = RadioSettingValueList( self._LCD_CONTRAST, self._LCD_CONTRAST[scan_settings.lcd_contrast - 1]) rs = RadioSetting("scan_settings.lcd_contrast", "LCD Contrast", val) rs.set_apply_callback(self.apply_lcd_contrast, scan_settings) menu.append(rs) val = RadioSettingValueList( self._LAMP_KEY, self._LAMP_KEY[scan_settings.lamp]) rs = RadioSetting("scan_settings.lamp", "Lamp", val) menu.append(rs) beep_select = self._memobj.beep_select val = RadioSettingValueList( self._BEEP_SELECT, self._BEEP_SELECT[beep_select.beep]) rs = RadioSetting("beep_select.beep", "Beep Select", val) menu.append(rs) opening_message = self._memobj.opening_message val = RadioSettingValueList( self._OPENING_MESSAGE, self._OPENING_MESSAGE[opening_message.flag]) rs = RadioSetting("opening_message.flag", "Opening Msg Mode", val) menu.append(rs) msg = "" for i in opening_message.message.padded_yaesu: if i == 0xFF: break msg += CHARSET[i & 0x7F] val = RadioSettingValueString(0, 16, msg) rs = RadioSetting("opening_message.message.padded_yaesu", "Opening Message", val) rs.set_apply_callback(self.apply_ff_padded_yaesu, opening_message.message) menu.append(rs) return menu def backtrack_ll_validate(self, number, min, max): if str(number).lstrip('0').strip().isdigit() and \ int(str(number).lstrip('0')) <= max and \ int(str(number).lstrip('0')) >= min: return True return False def backtrack_zero_pad(self, number, l): number = str(number).strip() while len(number) < l: number = '0' + number return str(number) def _get_backtrack_settings(self): menu = RadioSettingGroup("backtrack", "Backtrack") for i in range(3): prefix = '' if i == 0: prefix = "Star " if i == 1: prefix = "L1 " if i == 2: prefix = "L2 " bt_idx = "backtrack[%d]" % i bt = self._memobj.backtrack[i] val = RadioSettingValueList( self._BACKTRACK_STATUS, self._BACKTRACK_STATUS[0 if bt.status == 1 else 1]) rs = RadioSetting( "%s.status" % bt_idx, prefix + "status", val) rs.set_apply_callback(self.apply_backtrack_status, bt) menu.append(rs) if bt.status == 1 and int(bt.year) < 100: val = RadioSettingValueInteger(0, 99, bt.year) else: val = RadioSettingValueInteger(0, 99, 0) rs = RadioSetting( "%s.year" % bt_idx, prefix + "year", val) menu.append(rs) if bt.status == 1 and int(bt.mon) <= 12: val = RadioSettingValueInteger(0, 12, bt.mon) else: val = RadioSettingValueInteger(0, 12, 0) rs = RadioSetting( "%s.mon" % bt_idx, prefix + "month", val) menu.append(rs) if bt.status == 1: val = RadioSettingValueInteger(0, 31, bt.day) else: val = RadioSettingValueInteger(0, 31, 0) rs = RadioSetting( "%s.day" % bt_idx, prefix + "day", val) menu.append(rs) if bt.status == 1: val = RadioSettingValueInteger(0, 23, bt.hour) else: val = RadioSettingValueInteger(0, 23, 0) rs = RadioSetting( "%s.hour" % bt_idx, prefix + "hour", val) menu.append(rs) if bt.status == 1: val = RadioSettingValueInteger(0, 59, bt.min) else: val = RadioSettingValueInteger(0, 59, 0) rs = RadioSetting( "%s.min" % bt_idx, prefix + "min", val) menu.append(rs) if bt.status == 1 and \ (str(bt.NShemi) == 'N' or str(bt.NShemi) == 'S'): val = RadioSettingValueString(0, 1, str(bt.NShemi)) else: val = RadioSettingValueString(0, 1, ' ') rs = RadioSetting( "%s.NShemi" % bt_idx, prefix + "NS hemisphere", val) rs.set_apply_callback(self.apply_NShemi, bt) menu.append(rs) if bt.status == 1 and self.backtrack_ll_validate(bt.lat, 0, 90): val = RadioSettingValueString( 0, 3, self.backtrack_zero_pad(bt.lat, 3)) else: val = RadioSettingValueString(0, 3, ' ') rs = RadioSetting("%s.lat" % bt_idx, prefix + "Latitude", val) rs.set_apply_callback(self.apply_bt_lat, bt) menu.append(rs) if bt.status == 1 and \ self.backtrack_ll_validate(bt.lat_min, 0, 59): val = RadioSettingValueString( 0, 2, self.backtrack_zero_pad(bt.lat_min, 2)) else: val = RadioSettingValueString(0, 2, ' ') rs = RadioSetting( "%s.lat_min" % bt_idx, prefix + "Latitude Minutes", val) rs.set_apply_callback(self.apply_bt_lat_min, bt) menu.append(rs) if bt.status == 1 and \ self.backtrack_ll_validate(bt.lat_dec_sec, 0, 9999): val = RadioSettingValueString( 0, 4, self.backtrack_zero_pad(bt.lat_dec_sec, 4)) else: val = RadioSettingValueString(0, 4, ' ') rs = RadioSetting( "%s.lat_dec_sec" % bt_idx, prefix + "Latitude Decimal Seconds", val) rs.set_apply_callback(self.apply_bt_lat_dec_sec, bt) menu.append(rs) if bt.status == 1 and \ (str(bt.WEhemi) == 'W' or str(bt.WEhemi) == 'E'): val = RadioSettingValueString( 0, 1, str(bt.WEhemi)) else: val = RadioSettingValueString(0, 1, ' ') rs = RadioSetting( "%s.WEhemi" % bt_idx, prefix + "WE hemisphere", val) rs.set_apply_callback(self.apply_WEhemi, bt) menu.append(rs) if bt.status == 1 and self.backtrack_ll_validate(bt.lon, 0, 180): val = RadioSettingValueString( 0, 3, self.backtrack_zero_pad(bt.lon, 3)) else: val = RadioSettingValueString(0, 3, ' ') rs = RadioSetting("%s.lon" % bt_idx, prefix + "Longitude", val) rs.set_apply_callback(self.apply_bt_lon, bt) menu.append(rs) if bt.status == 1 and \ self.backtrack_ll_validate(bt.lon_min, 0, 59): val = RadioSettingValueString( 0, 2, self.backtrack_zero_pad(bt.lon_min, 2)) else: val = RadioSettingValueString(0, 2, ' ') rs = RadioSetting( "%s.lon_min" % bt_idx, prefix + "Longitude Minutes", val) rs.set_apply_callback(self.apply_bt_lon_min, bt) menu.append(rs) if bt.status == 1 and \ self.backtrack_ll_validate(bt.lon_dec_sec, 0, 9999): val = RadioSettingValueString( 0, 4, self.backtrack_zero_pad(bt.lon_dec_sec, 4)) else: val = RadioSettingValueString(0, 4, ' ') rs = RadioSetting( "%s.lon_dec_sec" % bt_idx, prefix + "Longitude Decimal Seconds", val) rs.set_apply_callback(self.apply_bt_lon_dec_sec, bt) menu.append(rs) return menu def _get_scan_settings(self): menu = RadioSettingGroup("scan_settings", "Scan") scan_settings = self._memobj.scan_settings val = RadioSettingValueList( self._VOL_MODE, self._VOL_MODE[scan_settings.vol_mode]) rs = RadioSetting("scan_settings.vol_mode", "Volume Mode", val) menu.append(rs) vfoa = self._memobj.vfo_info[0] val = RadioSettingValueList( self._VOLUME, self._VOLUME[vfoa.volume]) rs = RadioSetting("vfo_info[0].volume", "VFO A Volume", val) rs.set_apply_callback(self.apply_volume, 0) menu.append(rs) vfob = self._memobj.vfo_info[1] val = RadioSettingValueList( self._VOLUME, self._VOLUME[vfob.volume]) rs = RadioSetting("vfo_info[1].volume", "VFO B Volume", val) rs.set_apply_callback(self.apply_volume, 1) menu.append(rs) squelch = self._memobj.squelch val = RadioSettingValueList( self._SQUELCH, self._SQUELCH[squelch.vfo_a]) rs = RadioSetting("squelch.vfo_a", "VFO A Squelch", val) menu.append(rs) val = RadioSettingValueList( self._SQUELCH, self._SQUELCH[squelch.vfo_b]) rs = RadioSetting("squelch.vfo_b", "VFO B Squelch", val) menu.append(rs) val = RadioSettingValueList( self._SCAN_RESTART, self._SCAN_RESTART[scan_settings.scan_restart]) rs = RadioSetting("scan_settings.scan_restart", "Scan Restart", val) menu.append(rs) val = RadioSettingValueList( self._SCAN_RESUME, self._SCAN_RESUME[scan_settings.scan_resume]) rs = RadioSetting("scan_settings.scan_resume", "Scan Resume", val) menu.append(rs) val = RadioSettingValueList( self._OFF_ON, self._OFF_ON[scan_settings.busy_led]) rs = RadioSetting("scan_settings.busy_led", "Busy LED", val) menu.append(rs) val = RadioSettingValueList( self._OFF_ON, self._OFF_ON[scan_settings.scan_lamp]) rs = RadioSetting("scan_settings.scan_lamp", "Scan Lamp", val) menu.append(rs) val = RadioSettingValueList( self._TOT_TIME, self._TOT_TIME[scan_settings.tot]) rs = RadioSetting("scan_settings.tot", "Transmit Timeout (TOT)", val) menu.append(rs) return menu def _get_settings(self): top = RadioSettings(self._get_aprs_general_settings(), self._get_aprs_rx_settings(), self._get_aprs_tx_settings(), self._get_aprs_smartbeacon(), self._get_aprs_msgs(), self._get_aprs_beacons(), self._get_dtmf_settings(), self._get_misc_settings(), self._get_scan_settings(), self._get_backtrack_settings()) return top def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None @staticmethod def apply_custom_symbol(setting, obj): # Ensure new value falls within known bounds, otherwise leave it as # it's a custom value from the radio that's outside our list. if setting.value.get_value() in chirp_common.APRS_SYMBOLS: setattr(obj, "custom_symbol", chirp_common.APRS_SYMBOLS.index(setting.value.get_value())) @classmethod def _apply_callsign(cls, callsign, obj, default_ssid=None): ssid = default_ssid dash_index = callsign.find("-") if dash_index >= 0: ssid = callsign[dash_index + 1:] callsign = callsign[:dash_index] try: ssid = int(ssid) % 16 except ValueError: ssid = default_ssid setattr(obj, "callsign", cls._add_ff_pad(callsign, 6)) if ssid is not None: setattr(obj, "ssid", ssid) def apply_beacon_type(cls, setting, obj): beacon_type = str(setting.value.get_value()) beacon_index = cls._BEACON_TYPE.index(beacon_type) tx_smartbeacon = beacon_index >> 1 tx_interval_beacon = beacon_index & 1 if tx_interval_beacon: setattr(obj, "tx_interval_beacon", 1) setattr(obj, "tx_smartbeacon", 0) elif tx_smartbeacon: setattr(obj, "tx_interval_beacon", 0) setattr(obj, "tx_smartbeacon", 1) else: setattr(obj, "tx_interval_beacon", 0) setattr(obj, "tx_smartbeacon", 0) @classmethod def apply_callsign(cls, setting, obj, default_ssid=None): # Uppercase, strip SSID then FF pad to max string length. callsign = setting.value.get_value().upper() cls._apply_callsign(callsign, obj, default_ssid) def apply_digi_path(self, setting, obj): # Parse and map to aprs.digi_path_4_7[0-3] or aprs.digi_path_8 # and FF terminate. path = str(setting.value.get_value()) callsigns = [c.strip() for c in path.split(",")] for index in range(len(obj.entry)): try: self._apply_callsign(callsigns[index], obj.entry[index], 0) except IndexError: self._apply_callsign("", obj.entry[index], 0) if len(callsigns) > len(obj.entry): raise Exception("This path only supports %d entries" % (index + 1)) @classmethod def apply_ff_padded_string(cls, setting, obj): # FF pad. val = setting.value.get_value() max_len = getattr(obj, "padded_string").size() / 8 val = str(val).rstrip() setattr(obj, "padded_string", cls._add_ff_pad(val, max_len)) @classmethod def apply_lat_long(cls, setting, obj): name = setting.get_name() is_latitude = name.endswith("latitude") lat_long = setting.value.get_value().strip() sign, l_d, l_m, l_s = cls._str_to_latlong(lat_long, is_latitude) LOG.debug("%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s)) setattr(obj, "%s_sign" % name, sign) setattr(obj, "%s_degree" % name, l_d) setattr(obj, "%s_minute" % name, l_m) setattr(obj, "%s_second" % name, l_s) def set_settings(self, settings): _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: if element.has_apply_callback(): LOG.debug("Using apply callback") try: element.run_apply_callback() except NotImplementedError as e: LOG.error(e) continue # Find the object containing setting. obj = _mem bits = element.get_name().split(".") setting = bits[-1] for name in bits[:-1]: if name.endswith("]"): name, index = name.split("[") index = int(index[:-1]) obj = getattr(obj, name)[index] else: obj = getattr(obj, name) try: old_val = getattr(obj, setting) LOG.debug("Setting %s(%r) <= %s" % ( element.get_name(), old_val, element.value)) setattr(obj, setting, element.value) except AttributeError as e: LOG.error("Setting %s is not in the memory map: %s" % (element.get_name(), e)) except Exception, e: LOG.debug(element.get_name()) raise def apply_ff_padded_yaesu(cls, setting, obj): # FF pad yaesus custom string format. rawval = setting.value.get_value() max_len = getattr(obj, "padded_yaesu").size() / 8 rawval = str(rawval).rstrip() val = [CHARSET.index(x) for x in rawval] for x in range(len(val), max_len): val.append(0xFF) obj.padded_yaesu = val def apply_volume(cls, setting, vfo): val = setting.value.get_value() cls._memobj.vfo_info[(vfo * 2)].volume = val cls._memobj.vfo_info[(vfo * 2) + 1].volume = val def apply_lcd_contrast(cls, setting, obj): rawval = setting.value.get_value() val = cls._LCD_CONTRAST.index(rawval) + 1 obj.lcd_contrast = val def apply_dtmf(cls, setting, i): rawval = setting.value.get_value().upper().rstrip() val = [FT1_DTMF_CHARS.index(x) for x in rawval] for x in range(len(val), 16): val.append(0xFF) cls._memobj.dtmf[i].memory = val def apply_backtrack_status(cls, setting, obj): status = setting.value.get_value() if status == 'Valid': val = 1 else: val = 8 setattr(obj, "status", val) def apply_NShemi(cls, setting, obj): hemi = setting.value.get_value().upper() if hemi != 'N' and hemi != 'S': hemi = ' ' setattr(obj, "NShemi", hemi) def apply_WEhemi(cls, setting, obj): hemi = setting.value.get_value().upper() if hemi != 'W' and hemi != 'E': hemi = ' ' setattr(obj, "WEhemi", hemi) def apply_WEhemi(cls, setting, obj): hemi = setting.value.get_value().upper() if hemi != 'W' and hemi != 'E': hemi = ' ' setattr(obj, "WEhemi", hemi) def apply_bt_lat(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 3) setattr(obj, "lat", val) def apply_bt_lat_min(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 2) setattr(obj, "lat_min", val) def apply_bt_lat_dec_sec(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 4) setattr(obj, "lat_dec_sec", val) def apply_bt_lon(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 3) setattr(obj, "lon", val) def apply_bt_lon_min(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 2) setattr(obj, "lon_min", val) def apply_bt_lon_dec_sec(cls, setting, obj): val = setting.value.get_value() val = cls.backtrack_zero_pad(val, 4) setattr(obj, "lon_dec_sec", val) chirp-daily-20170714/chirp/drivers/ic9x_icf_ll.py0000644000016101777760000000733512476006422022717 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 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-daily-20170714/chirp/drivers/kguv8d.py0000644000016101777760000011302613060205711021726 0ustar jenkinsnogroup00000000000000# Copyright 2014 Ron Wellsted M0RNW # # 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 KG-UV8D radio management module""" import time import os import logging from chirp import util, chirp_common, bitwise, memmap, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) CMD_ID = 128 CMD_END = 129 CMD_RD = 130 CMD_WR = 131 MEM_VALID = 158 AB_LIST = ["A", "B"] STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0] STEP_LIST = [str(x) for x in STEPS] ROGER_LIST = ["Off", "BOT", "EOT", "Both"] TIMEOUT_LIST = ["Off"] + [str(x) + "s" for x in range(15, 901, 15)] VOX_LIST = ["Off"] + ["%s" % x for x in range(1, 10)] BANDWIDTH_LIST = ["Narrow", "Wide"] VOICE_LIST = ["Off", "On"] LANGUAGE_LIST = ["Chinese", "English"] SCANMODE_LIST = ["TO", "CO", "SE"] PF1KEY_LIST = ["Call", "VFTX"] PF3KEY_LIST = ["Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio", "Disable"] WORKMODE_LIST = ["VFO", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"] BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \ ["Always Off"] OFFSET_LIST = ["+", "-"] PONMSG_LIST = ["Bitmap", "Battery Volts"] SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"] DTMFST_LIST = ["DT-ST", "ANI-ST", "DT-ANI", "Off"] DTMF_TIMES = ["%s" % x for x in range(50, 501, 10)] RPTSET_LIST = ["X-TWRPT", "X-DIRRPT"] ALERTS = [1750, 2100, 1000, 1450] ALERTS_LIST = [str(x) for x in ALERTS] PTTID_LIST = ["BOT", "EOT", "Both"] LIST_10 = ["Off"] + ["%s" % x for x in range(1, 11)] SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)] SCQT_LIST = ["All", "Decoder", "Encoder"] SMUTESET_LIST = ["Off", "Tx", "Rx", "Tx/Rx"] POWER_LIST = ["Lo", "Hi"] HOLD_TIMES = ["Off"] + ["%s" % x for x in range(100, 5001, 100)] RPTMODE_LIST = ["Radio", "Repeater"] # memory slot 0 is not used, start at 1 (so need 1000 slots, not 999) # structure elements whose name starts with x are currently unidentified _MEM_FORMAT = """ #seekto 0x0044; struct { u32 rx_start; u32 rx_stop; u32 tx_start; u32 tx_stop; } uhf_limits; #seekto 0x0054; struct { u32 rx_start; u32 rx_stop; u32 tx_start; u32 tx_stop; } vhf_limits; #seekto 0x0400; struct { u8 model[8]; u8 unknown[2]; u8 oem1[10]; u8 oem2[10]; u8 unknown2[8]; u8 version[10]; u8 unknown3[6]; u8 date[8]; } oem_info; #seekto 0x0480; struct { u16 lower; u16 upper; } scan_groups[10]; #seekto 0x0500; struct { u8 call_code[6]; } call_groups[20]; #seekto 0x0580; struct { char call_name[6]; } call_group_name[20]; #seekto 0x0800; struct { u8 ponmsg; char dispstr[15]; u8 x0810; u8 x0811; u8 x0812; u8 x0813; u8 x0814; u8 voice; u8 timeout; u8 toalarm; u8 channel_menu; u8 power_save; u8 autolock; u8 keylock; u8 beep; u8 stopwatch; u8 vox; u8 scan_rev; u8 backlight; u8 roger_beep; u8 mode_sw_pwd[6]; u8 reset_pwd[6]; u16 pri_ch; u8 ani_sw; u8 ptt_delay; u8 ani[6]; u8 dtmf_st; u8 bcl_a; u8 bcl_b; u8 ptt_id; u8 prich_sw; u8 rpt_set; u8 rpt_spk; u8 rpt_ptt; u8 alert; u8 pf1_func; u8 pf3_func; u8 workmode_b; u8 workmode_a; u8 x0845; u8 dtmf_tx_time; u8 dtmf_interval; u8 main_ab; u16 work_cha; u16 work_chb; u8 x084d; u8 x084e; u8 x084f; u8 x0850; u8 x0851; u8 x0852; u8 x0853; u8 x0854; u8 rpt_mode; u8 language; u8 x0857; u8 x0858; u8 x0859; u8 x085a; u8 x085b; u8 x085c; u8 x085d; u8 x085e; u8 single_display; u8 ring_time; u8 scg_a; u8 scg_b; u8 x0863; u8 rpt_tone; u8 rpt_hold; u8 scan_det; u8 sc_qt; u8 x0868; u8 smuteset; u8 callcode; } settings; #seekto 0x0880; struct { u32 rxfreq; u32 txoffset; u16 rxtone; u16 txtone; u8 unknown1:6, power:1, unknown2:1; u8 unknown3:1, shift_dir:2 unknown4:2, mute_mode:2, iswide:1; u8 step; u8 squelch; } vfoa; #seekto 0x08c0; struct { u32 rxfreq; u32 txoffset; u16 rxtone; u16 txtone; u8 unknown1:6, power:1, unknown2:1; u8 unknown3:1, shift_dir:2 unknown4:2, mute_mode:2, iswide:1; u8 step; u8 squelch; } vfob; #seekto 0x0900; struct { u32 rxfreq; u32 txfreq; u16 rxtone; u16 txtone; u8 unknown1:6, power:1, unknown2:1; u8 unknown3:2, scan_add:1, unknown4:2, mute_mode:2, iswide:1; u16 padding; } memory[1000]; #seekto 0x4780; struct { u8 name[8]; } names[1000]; #seekto 0x6700; u8 valid[1000]; """ # Support for the Wouxun KG-UV8D radio # Serial coms are at 19200 baud # The data is passed in variable length records # Record structure: # Offset Usage # 0 start of record (\x7d) # 1 Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write) # 2 direction (\xff PC-> Radio, \x00 Radio -> PC) # 3 length of payload (excluding header/checksum) (n) # 4 payload (n bytes) # 4+n+1 checksum - byte sum (% 256) of bytes 1 -> 4+n # # Memory Read Records: # the payload is 3 bytes, first 2 are offset (big endian), # 3rd is number of bytes to read # Memory Write Records: # the maximum payload size (from the Wouxun software) seems to be 66 bytes # (2 bytes location + 64 bytes data). @directory.register class KGUV8DRadio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Wouxun KG-UV8D""" VENDOR = "Wouxun" MODEL = "KG-UV8D" _model = "KG-UV8D" _file_ident = "KGUV8D" BAUD_RATE = 19200 POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1), chirp_common.PowerLevel("H", watts=5)] _mmap = "" def _checksum(self, data): cs = 0 for byte in data: cs += ord(byte) return cs % 256 def _write_record(self, cmd, payload=None): # build the packet _packet = '\x7d' + chr(cmd) + '\xff' _length = 0 if payload: _length = len(payload) # update the length field _packet += chr(_length) if payload: # add the chars to the packet _packet += payload # calculate and add the checksum to the packet _packet += chr(self._checksum(_packet[1:])) LOG.debug("Sent:\n%s" % util.hexprint(_packet)) self.pipe.write(_packet) def _read_record(self): # read 4 chars for the header _header = self.pipe.read(4) if len(_header) != 4: raise errors.RadioError('Radio did not respond') _length = ord(_header[3]) _packet = self.pipe.read(_length) _cs = self._checksum(_header[1:]) _cs += self._checksum(_packet) _cs %= 256 _rcs = ord(self.pipe.read(1)) LOG.debug("_cs =%x", _cs) LOG.debug("_rcs=%x", _rcs) return (_rcs != _cs, _packet) # Identify the radio # # A Gotcha: the first identify packet returns a bad checksum, subsequent # attempts return the correct checksum... (well it does on my radio!) # # The ID record returned by the radio also includes the current frequency range # as 4 bytes big-endian in 10Hz increments # # Offset # 0:10 Model, zero padded (Use first 7 chars for 'KG-UV8D') # 11:14 UHF rx lower limit (in units of 10Hz) # 15:18 UHF rx upper limit # 19:22 UHF tx lower limit # 23:26 UHF tx upper limit # 27:30 VHF rx lower limit # 31:34 VHF rx upper limit # 35:38 VHF tx lower limit # 39:42 VHF tx upper limit # @classmethod def match_model(cls, filedata, filename): return cls._file_ident in filedata[0x400:0x408] def _identify(self): """Do the identification dance""" for _i in range(0, 10): self._write_record(CMD_ID) _chksum_err, _resp = self._read_record() LOG.debug("Got:\n%s" % util.hexprint(_resp)) if _chksum_err: LOG.error("Checksum error: retrying ident...") time.sleep(0.100) continue LOG.debug("Model %s" % util.hexprint(_resp[0:7])) if _resp[0:7] == self._model: return if len(_resp) == 0: raise Exception("Radio not responding") else: raise Exception("Unable to identify radio") def _finish(self): self._write_record(CMD_END) def process_mmap(self): self._memobj = bitwise.parse(_MEM_FORMAT, self._mmap) def sync_in(self): try: self._mmap = self._download() 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._upload() # TODO: This is a dumb, brute force method of downlolading the memory. # it would be smarter to only load the active areas and none of # the padding/unused areas. def _download(self): """Talk to a wouxun KG-UV8D and do a download""" try: self._identify() return self._do_download(0, 32768, 64) except errors.RadioError: raise except Exception, e: LOG.exception('Unknown error during download process') raise errors.RadioError("Failed to communicate with radio: %s" % e) def _do_download(self, start, end, blocksize): # allocate & fill memory image = "" for i in range(start, end, blocksize): req = chr(i / 256) + chr(i % 256) + chr(blocksize) self._write_record(CMD_RD, req) cs_error, resp = self._read_record() if cs_error: # TODO: probably should retry a few times here LOG.debug(util.hexprint(resp)) raise Exception("Checksum error on read") LOG.debug("Got:\n%s" % util.hexprint(resp)) image += resp[2:] if self.status_fn: status = chirp_common.Status() status.cur = i status.max = end status.msg = "Cloning from radio" self.status_fn(status) self._finish() return memmap.MemoryMap(''.join(image)) def _upload(self): """Talk to a wouxun KG-UV8D and do a upload""" try: self._identify() self._do_upload(0, 32768, 64) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) return def _do_upload(self, start, end, blocksize): ptr = start for i in range(start, end, blocksize): req = chr(i / 256) + chr(i % 256) chunk = self.get_mmap()[ptr:ptr + blocksize] self._write_record(CMD_WR, req + chunk) LOG.debug(util.hexprint(req + chunk)) cserr, ack = self._read_record() LOG.debug(util.hexprint(ack)) j = ord(ack[0]) * 256 + ord(ack[1]) if cserr or j != ptr: raise Exception("Radio did not ack block %i" % ptr) ptr += blocksize if self.status_fn: status = chirp_common.Status() status.cur = i status.max = end status.msg = "Cloning to radio" self.status_fn(status) self._finish() def get_features(self): # TODO: This probably needs to be setup correctly to match the true # features of the radio rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_ctone = True rf.has_rx_dtcs = True rf.has_cross = True rf.has_tuning_step = False rf.has_bank = False rf.can_odd_split = True rf.valid_skips = ["", "S"] 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_name_length = 8 rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_bands = [(134000000, 175000000), # supports 2m (400000000, 520000000)] # supports 70cm rf.valid_characters = chirp_common.CHARSET_ASCII rf.memory_bounds = (1, 999) # 999 memories return rf @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = ("This radio driver is currently under development. " "There are no known issues with it, but you should " "proceed with caution.") return rp def get_raw_memory(self, number): return repr(self._memobj.memory[number]) 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 tpol = False if _mem.txtone != 0xFFFF and (_mem.txtone & 0x2800) == 0x2800: tcode, tpol = _get_dcs(_mem.txtone) mem.dtcs = tcode txmode = "DTCS" elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0: mem.rtone = (_mem.txtone & 0x7fff) / 10.0 txmode = "Tone" else: txmode = "" rpol = False if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x2800) == 0x2800: rcode, rpol = _get_dcs(_mem.rxtone) mem.rx_dtcs = rcode rxmode = "DTCS" elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0: mem.ctone = (_mem.rxtone & 0x7fff) / 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) # always set it even if no dtcs is used mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N") LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.txtone, rxmode, _mem.rxtone)) def get_memory(self, number): _mem = self._memobj.memory[number] _nam = self._memobj.names[number] mem = chirp_common.Memory() mem.number = number _valid = self._memobj.valid[mem.number] LOG.debug("%d %s", number, _valid == MEM_VALID) if _valid != MEM_VALID: mem.empty = True return mem else: mem.empty = False mem.freq = int(_mem.rxfreq) * 10 if _mem.txfreq == 0xFFFFFFFF: # TX freq not set 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 char != 0: mem.name += chr(char) mem.name = mem.name.rstrip() self._get_tone(_mem, mem) mem.skip = "" if bool(_mem.scan_add) else "S" mem.power = self.POWER_LEVELS[_mem.power] mem.mode = _mem.iswide and "FM" or "NFM" return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rxtone = txtone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None txtone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rxtone = txtone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": txtone = int(mem.rtone * 10) if rx_mode == "DTCS": rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rxtone = int(mem.ctone * 10) _mem.rxtone = rxtone _mem.txtone = txtone LOG.debug("Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.txtone, rx_mode, _mem.rxtone)) def set_memory(self, mem): number = mem.number _mem = self._memobj.memory[number] _nam = self._memobj.names[number] if mem.empty: _mem.set_raw("\x00" * (_mem.size() / 8)) self._memobj.valid[number] = 0 self._memobj.names[number].set_raw("\x00" * (_nam.size() / 8)) return _mem.rxfreq = int(mem.freq / 10) if mem.duplex == "off": _mem.txfreq = 0xFFFFFFFF elif mem.duplex == "split": _mem.txfreq = int(mem.offset / 10) elif mem.duplex == "off": for i in range(0, 4): _mem.txfreq[i].set_raw("\xFF") elif mem.duplex == "+": _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10) elif mem.duplex == "-": _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10) else: _mem.txfreq = int(mem.freq / 10) _mem.scan_add = int(mem.skip != "S") _mem.iswide = int(mem.mode == "FM") # set the tone self._set_tone(mem, _mem) # set the power if mem.power: _mem.power = self.POWER_LEVELS.index(mem.power) else: _mem.power = True # TODO: set the correct mute mode, for now just # set to mute mode to QT (not QT+DTMF or QT*DTMF) _mem.mute_mode = 0 for i in range(0, len(_nam.name)): if i < len(mem.name) and mem.name[i]: _nam.name[i] = ord(mem.name[i]) else: _nam.name[i] = 0x0 self._memobj.valid[mem.number] = MEM_VALID def _get_settings(self): _settings = self._memobj.settings _vfoa = self._memobj.vfoa _vfob = self._memobj.vfob cfg_grp = RadioSettingGroup("cfg_grp", "Configuration") vfoa_grp = RadioSettingGroup("vfoa_grp", "VFO A Settings") vfob_grp = RadioSettingGroup("vfob_grp", "VFO B Settings") key_grp = RadioSettingGroup("key_grp", "Key Settings") lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits") oem_grp = RadioSettingGroup("oem_grp", "OEM Info") group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp, key_grp, lmt_grp, oem_grp) # # Configuration Settings # rs = RadioSetting("channel_menu", "Menu available in channel mode", RadioSettingValueBoolean(_settings.channel_menu)) cfg_grp.append(rs) rs = RadioSetting("ponmsg", "Poweron message", RadioSettingValueList( PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])) cfg_grp.append(rs) rs = RadioSetting("voice", "Voice Guide", RadioSettingValueBoolean(_settings.voice)) cfg_grp.append(rs) rs = RadioSetting("language", "Language", RadioSettingValueList(LANGUAGE_LIST, LANGUAGE_LIST[_settings. language])) cfg_grp.append(rs) rs = RadioSetting("timeout", "Timeout Timer", RadioSettingValueInteger(15, 900, _settings.timeout * 15, 15)) cfg_grp.append(rs) rs = RadioSetting("toalarm", "Timeout Alarm", RadioSettingValueInteger(0, 10, _settings.toalarm)) cfg_grp.append(rs) rs = RadioSetting("roger_beep", "Roger Beep", RadioSettingValueBoolean(_settings.roger_beep)) cfg_grp.append(rs) rs = RadioSetting("power_save", "Power save", RadioSettingValueBoolean(_settings.power_save)) cfg_grp.append(rs) rs = RadioSetting("autolock", "Autolock", RadioSettingValueBoolean(_settings.autolock)) cfg_grp.append(rs) rs = RadioSetting("keylock", "Keypad Lock", RadioSettingValueBoolean(_settings.keylock)) cfg_grp.append(rs) rs = RadioSetting("beep", "Keypad Beep", RadioSettingValueBoolean(_settings.beep)) cfg_grp.append(rs) rs = RadioSetting("stopwatch", "Stopwatch", RadioSettingValueBoolean(_settings.stopwatch)) cfg_grp.append(rs) rs = RadioSetting("backlight", "Backlight", RadioSettingValueList(BACKLIGHT_LIST, BACKLIGHT_LIST[_settings. backlight])) cfg_grp.append(rs) rs = RadioSetting("dtmf_st", "DTMF Sidetone", RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings. dtmf_st])) cfg_grp.append(rs) rs = RadioSetting("ani-id_sw", "ANI-ID Switch", RadioSettingValueBoolean(_settings.ani_sw)) cfg_grp.append(rs) rs = RadioSetting("ptt-id_delay", "PTT-ID Delay", RadioSettingValueList(PTTID_LIST, PTTID_LIST[_settings.ptt_id])) cfg_grp.append(rs) rs = RadioSetting("ring_time", "Ring Time", RadioSettingValueList(LIST_10, LIST_10[_settings.ring_time])) cfg_grp.append(rs) rs = RadioSetting("scan_rev", "Scan Mode", RadioSettingValueList(SCANMODE_LIST, SCANMODE_LIST[_settings. scan_rev])) cfg_grp.append(rs) rs = RadioSetting("vox", "VOX", RadioSettingValueList(LIST_10, LIST_10[_settings.vox])) cfg_grp.append(rs) rs = RadioSetting("prich_sw", "Priority Channel Switch", RadioSettingValueBoolean(_settings.prich_sw)) cfg_grp.append(rs) rs = RadioSetting("pri_ch", "Priority Channel", RadioSettingValueInteger(1, 999, _settings.pri_ch)) cfg_grp.append(rs) rs = RadioSetting("rpt_mode", "Radio Mode", RadioSettingValueList(RPTMODE_LIST, RPTMODE_LIST[_settings. rpt_mode])) cfg_grp.append(rs) rs = RadioSetting("rpt_set", "Repeater Setting", RadioSettingValueList(RPTSET_LIST, RPTSET_LIST[_settings. rpt_set])) cfg_grp.append(rs) rs = RadioSetting("rpt_spk", "Repeater Mode Speaker", RadioSettingValueBoolean(_settings.rpt_spk)) cfg_grp.append(rs) rs = RadioSetting("rpt_ptt", "Repeater PTT", RadioSettingValueBoolean(_settings.rpt_ptt)) cfg_grp.append(rs) rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration", RadioSettingValueList(DTMF_TIMES, DTMF_TIMES[_settings. dtmf_tx_time])) cfg_grp.append(rs) rs = RadioSetting("dtmf_interval", "DTMF Interval", RadioSettingValueList(DTMF_TIMES, DTMF_TIMES[_settings. dtmf_interval])) cfg_grp.append(rs) rs = RadioSetting("alert", "Alert Tone", RadioSettingValueList(ALERTS_LIST, ALERTS_LIST[_settings.alert])) cfg_grp.append(rs) rs = RadioSetting("rpt_tone", "Repeater Tone", RadioSettingValueBoolean(_settings.rpt_tone)) cfg_grp.append(rs) rs = RadioSetting("rpt_hold", "Repeater Hold Time", RadioSettingValueList(HOLD_TIMES, HOLD_TIMES[_settings. rpt_hold])) cfg_grp.append(rs) rs = RadioSetting("scan_det", "Scan DET", RadioSettingValueBoolean(_settings.scan_det)) cfg_grp.append(rs) rs = RadioSetting("sc_qt", "SC-QT", RadioSettingValueList(SCQT_LIST, SCQT_LIST[_settings.smuteset])) cfg_grp.append(rs) rs = RadioSetting("smuteset", "SubFreq Mute", RadioSettingValueList(SMUTESET_LIST, SMUTESET_LIST[_settings. smuteset])) cfg_grp.append(rs) _pwd = "".join(map(chr, _settings.mode_sw_pwd)) val = RadioSettingValueString(0, 6, _pwd) val.set_mutable(True) rs = RadioSetting("mode_sw_pwd", "Mode Switch Password", val) cfg_grp.append(rs) _pwd = "".join(map(chr, _settings.reset_pwd)) val = RadioSettingValueString(0, 6, _pwd) val.set_mutable(True) rs = RadioSetting("reset_pwd", "Reset Password", val) cfg_grp.append(rs) # # VFO A Settings # rs = RadioSetting("vfoa_mode", "VFO A Workmode", RadioSettingValueList(WORKMODE_LIST, WORKMODE_LIST[_settings. workmode_a])) vfoa_grp.append(rs) rs = RadioSetting("vfoa_chan", "VFO A Channel", RadioSettingValueInteger(1, 999, _settings.work_cha)) vfoa_grp.append(rs) rs = RadioSetting("rxfreqa", "VFO A Rx Frequency", RadioSettingValueInteger( 134000000, 520000000, _vfoa.rxfreq * 10, 5000)) vfoa_grp.append(rs) rs = RadioSetting("txoffa", "VFO A Tx Offset", RadioSettingValueInteger( 0, 520000000, _vfoa.txoffset * 10, 5000)) vfoa_grp.append(rs) # u16 rxtone; # u16 txtone; rs = RadioSetting("vfoa_power", "VFO A Power", RadioSettingValueList( POWER_LIST, POWER_LIST[_vfoa.power])) vfoa_grp.append(rs) # shift_dir:2 rs = RadioSetting("vfoa_iswide", "VFO A NBFM", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide])) vfoa_grp.append(rs) rs = RadioSetting("vfoa_mute_mode", "VFO A Mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode])) vfoa_grp.append(rs) rs = RadioSetting("vfoa_step", "VFO A Step (kHz)", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfoa.step])) vfoa_grp.append(rs) rs = RadioSetting("vfoa_squelch", "VFO A Squelch", RadioSettingValueList( LIST_10, LIST_10[_vfoa.squelch])) vfoa_grp.append(rs) rs = RadioSetting("bcl_a", "Busy Channel Lock-out A", RadioSettingValueBoolean(_settings.bcl_a)) vfoa_grp.append(rs) # # VFO B Settings # rs = RadioSetting("vfob_mode", "VFO B Workmode", RadioSettingValueList( WORKMODE_LIST, WORKMODE_LIST[_settings.workmode_b])) vfob_grp.append(rs) rs = RadioSetting("vfob_chan", "VFO B Channel", RadioSettingValueInteger(1, 999, _settings.work_chb)) vfob_grp.append(rs) rs = RadioSetting("rxfreqb", "VFO B Rx Frequency", RadioSettingValueInteger( 134000000, 520000000, _vfob.rxfreq * 10, 5000)) vfob_grp.append(rs) rs = RadioSetting("txoffb", "VFO B Tx Offset", RadioSettingValueInteger( 0, 520000000, _vfob.txoffset * 10, 5000)) vfob_grp.append(rs) # u16 rxtone; # u16 txtone; rs = RadioSetting("vfob_power", "VFO B Power", RadioSettingValueList( POWER_LIST, POWER_LIST[_vfob.power])) vfob_grp.append(rs) # shift_dir:2 rs = RadioSetting("vfob_iswide", "VFO B NBFM", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide])) vfob_grp.append(rs) rs = RadioSetting("vfob_mute_mode", "VFO B Mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode])) vfob_grp.append(rs) rs = RadioSetting("vfob_step", "VFO B Step (kHz)", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfob.step])) vfob_grp.append(rs) rs = RadioSetting("vfob_squelch", "VFO B Squelch", RadioSettingValueList( LIST_10, LIST_10[_vfob.squelch])) vfob_grp.append(rs) rs = RadioSetting("bcl_b", "Busy Channel Lock-out B", RadioSettingValueBoolean(_settings.bcl_b)) vfob_grp.append(rs) # # Key Settings # _msg = str(_settings.dispstr).split("\0")[0] val = RadioSettingValueString(0, 15, _msg) val.set_mutable(True) rs = RadioSetting("dispstr", "Display Message", val) key_grp.append(rs) _ani = "" for i in _settings.ani: if i < 10: _ani += chr(i + 0x30) else: break val = RadioSettingValueString(0, 6, _ani) val.set_mutable(True) rs = RadioSetting("ani", "ANI code", val) key_grp.append(rs) rs = RadioSetting("pf1_func", "PF1 Key function", RadioSettingValueList( PF1KEY_LIST, PF1KEY_LIST[self._memobj.settings.pf1_func])) key_grp.append(rs) rs = RadioSetting("pf3_func", "PF3 Key function", RadioSettingValueList( PF3KEY_LIST, PF3KEY_LIST[self._memobj.settings.pf3_func])) key_grp.append(rs) # # Scan Group Settings # # settings: # u8 scg_a; # u8 scg_b; # # struct { # u16 lower; # u16 upper; # } scan_groups[10]; # # Call group settings # # # Limits settings # rs = RadioSetting("urx_start", "UHF RX Lower Limit", RadioSettingValueInteger( 400000000, 520000000, self._memobj.uhf_limits.rx_start * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("urx_stop", "UHF RX Upper Limit", RadioSettingValueInteger( 400000000, 520000000, self._memobj.uhf_limits.rx_stop * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("utx_start", "UHF TX Lower Limit", RadioSettingValueInteger( 400000000, 520000000, self._memobj.uhf_limits.tx_start * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("utx_stop", "UHF TX Upper Limit", RadioSettingValueInteger( 400000000, 520000000, self._memobj.uhf_limits.tx_stop * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("vrx_start", "VHF RX Lower Limit", RadioSettingValueInteger( 134000000, 174997500, self._memobj.vhf_limits.rx_start * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("vrx_stop", "VHF RX Upper Limit", RadioSettingValueInteger( 134000000, 174997500, self._memobj.vhf_limits.rx_stop * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("vtx_start", "VHF TX Lower Limit", RadioSettingValueInteger( 134000000, 174997500, self._memobj.vhf_limits.tx_start * 10, 5000)) lmt_grp.append(rs) rs = RadioSetting("vtx_stop", "VHF TX Upper Limit", RadioSettingValueInteger( 134000000, 174997500, self._memobj.vhf_limits.tx_stop * 10, 5000)) lmt_grp.append(rs) # # OEM info # def _decode(lst): _str = ''.join([chr(c) for c in lst if chr(c) in chirp_common.CHARSET_ASCII]) return _str _str = _decode(self._memobj.oem_info.model) val = RadioSettingValueString(0, 15, _str) val.set_mutable(False) rs = RadioSetting("model", "Model", val) oem_grp.append(rs) _str = _decode(self._memobj.oem_info.oem1) val = RadioSettingValueString(0, 15, _str) val.set_mutable(False) rs = RadioSetting("oem1", "OEM String 1", val) oem_grp.append(rs) _str = _decode(self._memobj.oem_info.oem2) val = RadioSettingValueString(0, 15, _str) val.set_mutable(False) rs = RadioSetting("oem2", "OEM String 2", val) oem_grp.append(rs) _str = _decode(self._memobj.oem_info.version) val = RadioSettingValueString(0, 15, _str) val.set_mutable(False) rs = RadioSetting("version", "Software Version", val) oem_grp.append(rs) _str = _decode(self._memobj.oem_info.date) val = RadioSettingValueString(0, 15, _str) val.set_mutable(False) rs = RadioSetting("date", "OEM Date", val) oem_grp.append(rs) return group def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None chirp-daily-20170714/chirp/drivers/tmv71.py0000644000016101777760000000471012726733400021504 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, errors, util from chirp.drivers import tmv71_ll import logging LOG = logging.getLogger(__name__) 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.baudrate = baud self.pipe.write("\r\r") self.pipe.read(32) try: id = tmv71_ll.get_id(self.pipe) LOG.info("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-daily-20170714/chirp/drivers/vx8.py0000644000016101777760000016333113116173000021244 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 os import re import logging from chirp.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings from chirp.settings import RadioSettingValueInteger, RadioSettingValueString from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean from textwrap import dedent LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x047f; struct { u8 flag; u16 unknown; struct { u8 padded_yaesu[16]; } message; } opening_message; #seekto 0x049a; struct { u8 vfo_a; u8 vfo_b; } squelch; #seekto 0x04bf; struct { u8 beep; } beep_select; #seekto 0x04cc; struct { u8 lcd_dimmer; u8 dtmf_delay; u8 unknown0[3]; u8 unknown1:4 lcd_contrast:4; u8 lamp; u8 unknown2[7]; u8 scan_restart; u8 unknown3; u8 scan_resume; u8 unknown4[6]; u8 tot; u8 unknown5[3]; u8 unknown6:2, scan_lamp:1, unknown7:2, dtmf_speed:1, unknown8:1, dtmf_mode:1; u8 busy_led:1, unknown9:7; u8 unknown10[2]; u8 vol_mode:1, unknown11:7; } scan_settings; #seekto 0x54a; struct { u16 in_use; } bank_used[24]; #seekto 0x064a; struct { u8 unknown0[4]; u8 frequency_band; u8 unknown1:6, manual_or_mr:2; u8 unknown2:7, mr_banks:1; u8 unknown3; u16 mr_index; u16 bank_index; u16 bank_enable; u8 unknown4[5]; u8 unknown5:6, power:2; u8 unknown6:4, tune_step:4; u8 unknown7:6, duplex:2; u8 unknown8:6, tone_mode:2; u8 unknown9:2, tone:6; u8 unknown10; u8 unknown11:6, mode:2; bbcd freq0[4]; bbcd offset_freq[4]; u8 unknown12[2]; char label[16]; u8 unknown13[6]; bbcd band_lower[4]; bbcd band_upper[4]; bbcd rx_freq[4]; u8 unknown14[22]; bbcd freq1[4]; u8 unknown15[11]; u8 unknown16:3, volume:5; u8 unknown17[18]; u8 active_menu_item; u8 checksum; } vfo_info[6]; #seekto 0x094a; struct { u8 memory[16]; } dtmf[10]; #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 0xC0CA; struct { u8 unknown0:6, rx_baud:2; u8 unknown1:4, tx_delay:4; u8 custom_symbol; u8 unknown2; struct { char callsign[6]; u8 ssid; } my_callsign; u8 unknown3:4, selected_position_comment:4; u8 unknown4; u8 set_time_manually:1, tx_interval_beacon:1, ring_beacon:1, ring_msg:1, aprs_mute:1, unknown6:1, tx_smartbeacon:1, af_dual:1; u8 unknown7:1, aprs_units_wind_mph:1, aprs_units_rain_inch:1, aprs_units_temperature_f:1 aprs_units_altitude_ft:1, unknown8:1, aprs_units_distance_m:1, aprs_units_position_mmss:1; u8 unknown9:6, aprs_units_speed:2; u8 unknown11:1, filter_other:1, filter_status:1, filter_item:1, filter_object:1, filter_weather:1, filter_position:1, filter_mic_e:1; u8 unknown12:2, timezone:6; u8 unknown13:4, beacon_interval:4; u8 unknown14; u8 unknown15:7, latitude_sign:1; u8 latitude_degree; u8 latitude_minute; u8 latitude_second; u8 unknown16:7, longitude_sign:1; u8 longitude_degree; u8 longitude_minute; u8 longitude_second; u8 unknown17:4, selected_position:4; u8 unknown18:5, selected_beacon_status_txt:3; u8 unknown19:6, gps_units_altitude_ft:1, gps_units_position_sss:1; u8 unknown20:6, gps_units_speed:2; u8 unknown21[4]; struct { struct { char callsign[6]; u8 ssid; } entry[8]; } digi_path_7; u8 unknown22[2]; } aprs; #seekto 0x%04X; struct { char padded_string[16]; } aprs_msg_macro[%d]; #seekto 0x%04X; struct { u8 unknown23:5, selected_msg_group:3; u8 unknown24; struct { char padded_string[9]; } msg_group[8]; u8 unknown25[4]; u8 active_smartbeaconing; struct { u8 low_speed_mph; u8 high_speed_mph; u8 slow_rate_min; u8 fast_rate_sec; u8 turn_angle; u8 turn_slop; u8 turn_time_sec; } smartbeaconing_profile[3]; u8 unknown26:2, flash_msg:6; u8 unknown27:2, flash_grp:6; u8 unknown28:2, flash_bln:6; u8 selected_digi_path; struct { struct { char callsign[6]; u8 ssid; } entry[2]; } digi_path_3_6[4]; u8 unknown30:6, selected_my_symbol:2; u8 unknown31[3]; u8 unknown32:2, vibrate_msg:6; u8 unknown33:2, vibrate_grp:6; u8 unknown34:2, vibrate_bln:6; } aprs2; #seekto 0x%04X; struct { bbcd date[3]; u8 unknown1; bbcd time[2]; u8 sequence; u8 unknown2; u8 sender_callsign[7]; u8 data_type; u8 yeasu_data_type; u8 unknown3; u8 unknown4:1, callsign_is_ascii:1, unknown5:6; u8 unknown6; u16 pkt_len; u16 in_use; } aprs_beacon_meta[%d]; #seekto 0x%04X; struct { u8 dst_callsign[6]; u8 dst_callsign_ssid; u8 src_callsign[6]; u8 src_callsign_ssid; u8 path_and_body[%d]; } aprs_beacon_pkt[%d]; #seekto 0xf92a; struct { char padded_string[60]; } aprs_beacon_status_txt[5]; #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 at index 2 (?) SKIPS = ["", "S", "P"] VX8_DTMF_CHARS = list("0123456789ABCD*#-") 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 __init__(self, radio, name='Banks'): super(VX8BankModel, self).__init__(radio, name) _banks = self._radio._memobj.bank_info self._bank_mappings = [] for index, _bank in enumerate(_banks): bank = VX8Bank(self, "%i" % index, "BANK-%i" % index) bank.index = index self._bank_mappings.append(bank) def get_num_mappings(self): return len(self._bank_mappings) def get_mappings(self): return self._bank_mappings def _channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return set() _members = self._radio._memobj.bank_members[bank.index] return set([int(ch) + 1 for ch in _members.channel if ch != 0xFFFF]) def update_vfo(self): chosen_bank = [None, None] chosen_mr = [None, None] flags = self._radio._memobj.flag # Find a suitable bank and MR for VFO A and B. for bank in self.get_mappings(): for channel in self._channel_numbers_in_bank(bank): chosen_bank[0] = bank.index chosen_mr[0] = channel if not flags[channel].nosubvfo: chosen_bank[1] = bank.index chosen_mr[1] = channel break if chosen_bank[1]: break for vfo_index in (0, 1): # 3 VFO info structs are stored as 3 pairs of (master, backup) vfo = self._radio._memobj.vfo_info[vfo_index * 2] vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1] if vfo.checksum != vfo_bak.checksum: LOG.warn("VFO settings are inconsistent with backup") else: if ((chosen_bank[vfo_index] is None) and (vfo.bank_index != 0xFFFF)): LOG.info("Disabling banks for VFO %d" % vfo_index) vfo.bank_index = 0xFFFF vfo.mr_index = 0xFFFF vfo.bank_enable = 0xFFFF elif ((chosen_bank[vfo_index] is not None) and (vfo.bank_index == 0xFFFF)): LOG.debug("Enabling banks for VFO %d" % vfo_index) vfo.bank_index = chosen_bank[vfo_index] vfo.mr_index = chosen_mr[vfo_index] vfo.bank_enable = 0x0000 vfo_bak.bank_index = vfo.bank_index vfo_bak.mr_index = vfo.mr_index vfo_bak.bank_enable = vfo.bank_enable def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.bank_members[bank.index] if len(channels_in_bank) > len(_members.channel): raise Exception("Too many entries in bank %d" % bank.index) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channel[index] = channel_number - 1 empty = index + 1 for index in range(empty, len(_members.channel)): _members.channel[index] = 0xFFFF def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0x06 self.update_vfo() def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) if not channels_in_bank: _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0xFFFF self.update_vfo() def get_mapping_memories(self, bank): memories = [] for channel in self._channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._channel_numbers_in_bank(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-8R" _model = "AH029" _memsize = 65227 _block_lengths = [10, 65217] _block_size = 32 _mem_params = (0xC128, # APRS message macros 5, # Number of message macros 0xC178, # APRS2 0xC24A, # APRS beacon metadata address. 40, # Number of beacons stored. 0xC60A, # APRS beacon content address. 194, # Length of beacon data stored. 40) # Number of beacons stored. _has_vibrate = False _has_af_dual = True _SG_RE = re.compile(r"(?P[-+NESW]?)(?P[\d]+)[\s\.,]*" "(?P[\d]*)[\s\']*(?P[\d]*)") _RX_BAUD = ("off", "1200 baud", "9600 baud") _TX_DELAY = ("100ms", "200ms", "300ms", "400ms", "500ms", "750ms", "1000ms") _WIND_UNITS = ("m/s", "mph") _RAIN_UNITS = ("mm", "inch") _TEMP_UNITS = ("C", "F") _ALT_UNITS = ("m", "ft") _DIST_UNITS = ("km", "mile") _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"") _SPEED_UNITS = ("km/h", "knot", "mph") _TIME_SOURCE = ("manual", "GPS") _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30", "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30", "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30", "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30", "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30", "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30", "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30", "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30", "+11:00", "+11:30") _BEACON_TYPE = ("Off", "Interval") _BEACON_INT = ("15s", "30s", "1m", "2m", "3m", "5m", "10m", "15m", "30m") _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4", "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8") _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2", "Message Group 3", "Message Group 4", "Message Group 5", "Message Group 6", "Message Group 7", "Message Group 8") _POSITIONS = ("GPS", "Manual Latitude/Longitude", "Manual Latitude/Longitude", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10") _FLASH = ("OFF", "ON") _BEEP_SELECT = ("Off", "Key+Scan", "Key") _SQUELCH = ["%d" % x for x in range(0, 16)] _VOLUME = ["%d" % x for x in range(0, 33)] _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal") _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \ ["Busy", "Hold"] _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \ ["%.1fs" % (0.5 * x) for x in range(2, 21)] _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + \ ["Continuous", "OFF"] _LCD_CONTRAST = ["Level %d" % x for x in range(1, 33)] _LCD_DIMMER = ["Level %d" % x for x in range(1, 5)] _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)] _OFF_ON = ("Off", "On") _VOL_MODE = ("Normal", "Auto Back") _DTMF_MODE = ("Manual", "Auto") _DTMF_SPEED = ("50ms", "100ms") _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms") _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected") @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the [FW] key while turning the radio on ("CLONE" will appear on the display). 4. After clicking OK, press the [BAND] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the [FW] key while turning the radio on ("CLONE" will appear on the display). 4. Press the [MODE] key ("-WAIT-" will appear on the LCD).""")) return rp def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, 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 rf.has_settings = True return rf def get_raw_memory(self, number): return repr(self._memobj.memory[number-1]) def _checksums(self): return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8), yaesu_clone.YaesuChecksum(0x06CA, 0x0748), yaesu_clone.YaesuChecksum(0x074A, 0x07C8), yaesu_clone.YaesuChecksum(0x07CA, 0x0848), yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)] @staticmethod def _add_ff_pad(val, length): return val.ljust(length, "\xFF")[:length] @classmethod def _strip_ff_pads(cls, messages): result = [] for msg_text in messages: result.append(str(msg_text).rstrip("\xFF")) return result 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 "" charset = ''.join(CHARSET).ljust(256, '.') mem.name = str(_mem.label).rstrip("\xFF").translate(charset) return mem def _debank(self, mem): bm = self.get_bank_model() for bank in bm.get_memory_mappings(mem): bm.remove_memory_from_mapping(mem, bank) def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] flag = self._memobj.flag[mem.number-1] self._debank(mem) 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 = self._add_ff_pad(label, 16) # 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) @classmethod def _digi_path_to_str(cls, path): path_cmp = [] for entry in path.entry: callsign = str(entry.callsign).rstrip("\xFF") if not callsign: break path_cmp.append("%s-%d" % (callsign, entry.ssid)) return ",".join(path_cmp) @staticmethod def _latlong_sanity(sign, l_d, l_m, l_s, is_lat): if sign not in (0, 1): sign = 0 if is_lat: d_max = 90 else: d_max = 180 if l_d < 0 or l_d > d_max: l_d = 0 l_m = 0 l_s = 0 if l_m < 0 or l_m > 60: l_m = 0 l_s = 0 if l_s < 0 or l_s > 60: l_s = 0 return sign, l_d, l_m, l_s @classmethod def _latlong_to_str(cls, sign, l_d, l_m, l_s, is_lat, to_sexigesimal=True): sign, l_d, l_m, l_s = cls._latlong_sanity(sign, l_d, l_m, l_s, is_lat) mult = sign and -1 or 1 if to_sexigesimal: return "%d,%d'%d\"" % (mult * l_d, l_m, l_s) return "%0.5f" % (mult * l_d + (l_m / 60.0) + (l_s / (60.0 * 60.0))) @classmethod def _str_to_latlong(cls, lat_long, is_lat): sign = 0 result = [0, 0, 0] lat_long = lat_long.strip() if not lat_long: return 1, 0, 0, 0 try: # DD.MMMMM is the simple case, try that first. val = float(lat_long) if val < 0: sign = 1 val = abs(val) result[0] = int(val) result[1] = int(val * 60) % 60 result[2] = int(val * 3600) % 60 except ValueError: # Try DD MM'SS" if DD.MMMMM failed. match = cls._SG_RE.match(lat_long.strip()) if match: if match.group("sign") and (match.group("sign") in "SE-"): sign = 1 else: sign = 0 if match.group("d"): result[0] = int(match.group("d")) if match.group("m"): result[1] = int(match.group("m")) if match.group("s"): result[2] = int(match.group("s")) elif len(lat_long) > 4: raise Exception("Lat/Long should be DD MM'SS\" or DD.MMMMM") return cls._latlong_sanity(sign, result[0], result[1], result[2], is_lat) def _get_aprs_general_settings(self): menu = RadioSettingGroup("aprs_general", "APRS General") aprs = self._memobj.aprs aprs2 = self._memobj.aprs2 val = RadioSettingValueString( 0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF")) rs = RadioSetting("aprs.my_callsign.callsign", "My Callsign", val) rs.set_apply_callback(self.apply_callsign, aprs.my_callsign) menu.append(rs) val = RadioSettingValueList( chirp_common.APRS_SSID, chirp_common.APRS_SSID[aprs.my_callsign.ssid]) rs = RadioSetting("aprs.my_callsign.ssid", "My SSID", val) menu.append(rs) val = RadioSettingValueList(self._MY_SYMBOL, self._MY_SYMBOL[aprs2.selected_my_symbol]) rs = RadioSetting("aprs2.selected_my_symbol", "My Symbol", val) menu.append(rs) symbols = list(chirp_common.APRS_SYMBOLS) selected = aprs.custom_symbol if aprs.custom_symbol >= len(chirp_common.APRS_SYMBOLS): symbols.append("%d" % aprs.custom_symbol) selected = len(symbols) - 1 val = RadioSettingValueList(symbols, symbols[selected]) rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol", val) rs.set_apply_callback(self.apply_custom_symbol, aprs) menu.append(rs) val = RadioSettingValueList( chirp_common.APRS_POSITION_COMMENT, chirp_common.APRS_POSITION_COMMENT[aprs.selected_position_comment]) rs = RadioSetting("aprs.selected_position_comment", "Position Comment", val) menu.append(rs) latitude = self._latlong_to_str(aprs.latitude_sign, aprs.latitude_degree, aprs.latitude_minute, aprs.latitude_second, True, aprs.aprs_units_position_mmss) longitude = self._latlong_to_str(aprs.longitude_sign, aprs.longitude_degree, aprs.longitude_minute, aprs.longitude_second, False, aprs.aprs_units_position_mmss) # TODO: Rebuild this when aprs_units_position_mmss changes. # TODO: Rebuild this when latitude/longitude change. # TODO: Add saved positions p1 - p10 to memory map. position_str = list(self._POSITIONS) # position_str[1] = "%s %s" % (latitude, longitude) # position_str[2] = "%s %s" % (latitude, longitude) val = RadioSettingValueList(position_str, position_str[aprs.selected_position]) rs = RadioSetting("aprs.selected_position", "My Position", val) menu.append(rs) val = RadioSettingValueString(0, 10, latitude) rs = RadioSetting("latitude", "Manual Latitude", val) rs.set_apply_callback(self.apply_lat_long, aprs) menu.append(rs) val = RadioSettingValueString(0, 11, longitude) rs = RadioSetting("longitude", "Manual Longitude", val) rs.set_apply_callback(self.apply_lat_long, aprs) menu.append(rs) val = RadioSettingValueList( self._TIME_SOURCE, self._TIME_SOURCE[aprs.set_time_manually]) rs = RadioSetting("aprs.set_time_manually", "Time Source", val) menu.append(rs) val = RadioSettingValueList(self._TZ, self._TZ[aprs.timezone]) rs = RadioSetting("aprs.timezone", "Timezone", val) menu.append(rs) val = RadioSettingValueList( self._SPEED_UNITS, self._SPEED_UNITS[aprs.aprs_units_speed]) rs = RadioSetting("aprs.aprs_units_speed", "APRS Speed Units", val) menu.append(rs) val = RadioSettingValueList( self._SPEED_UNITS, self._SPEED_UNITS[aprs.gps_units_speed]) rs = RadioSetting("aprs.gps_units_speed", "GPS Speed Units", val) menu.append(rs) val = RadioSettingValueList( self._ALT_UNITS, self._ALT_UNITS[aprs.aprs_units_altitude_ft]) rs = RadioSetting("aprs.aprs_units_altitude_ft", "APRS Altitude Units", val) menu.append(rs) val = RadioSettingValueList( self._ALT_UNITS, self._ALT_UNITS[aprs.gps_units_altitude_ft]) rs = RadioSetting("aprs.gps_units_altitude_ft", "GPS Altitude Units", val) menu.append(rs) val = RadioSettingValueList( self._POS_UNITS, self._POS_UNITS[aprs.aprs_units_position_mmss]) rs = RadioSetting("aprs.aprs_units_position_mmss", "APRS Position Format", val) menu.append(rs) val = RadioSettingValueList( self._POS_UNITS, self._POS_UNITS[aprs.gps_units_position_sss]) rs = RadioSetting("aprs.gps_units_position_sss", "GPS Position Format", val) menu.append(rs) val = RadioSettingValueList( self._DIST_UNITS, self._DIST_UNITS[aprs.aprs_units_distance_m]) rs = RadioSetting("aprs.aprs_units_distance_m", "APRS Distance Units", val) menu.append(rs) val = RadioSettingValueList( self._WIND_UNITS, self._WIND_UNITS[aprs.aprs_units_wind_mph]) rs = RadioSetting("aprs.aprs_units_wind_mph", "APRS Wind Speed Units", val) menu.append(rs) val = RadioSettingValueList( self._RAIN_UNITS, self._RAIN_UNITS[aprs.aprs_units_rain_inch]) rs = RadioSetting("aprs.aprs_units_rain_inch", "APRS Rain Units", val) menu.append(rs) val = RadioSettingValueList( self._TEMP_UNITS, self._TEMP_UNITS[aprs.aprs_units_temperature_f]) rs = RadioSetting("aprs.aprs_units_temperature_f", "APRS Temperature Units", val) menu.append(rs) return menu def _get_aprs_rx_settings(self): menu = RadioSettingGroup("aprs_rx", "APRS Receive") aprs = self._memobj.aprs aprs2 = self._memobj.aprs2 val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud]) rs = RadioSetting("aprs.rx_baud", "Modem RX", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.aprs_mute) rs = RadioSetting("aprs.aprs_mute", "APRS Mute", val) menu.append(rs) if self._has_af_dual: val = RadioSettingValueBoolean(aprs.af_dual) rs = RadioSetting("aprs.af_dual", "AF Dual", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.ring_msg) rs = RadioSetting("aprs.ring_msg", "Ring on Message RX", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.ring_beacon) rs = RadioSetting("aprs.ring_beacon", "Ring on Beacon RX", val) menu.append(rs) val = RadioSettingValueList(self._FLASH, self._FLASH[aprs2.flash_msg]) rs = RadioSetting("aprs2.flash_msg", "Flash on personal message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH, self._FLASH[aprs2.vibrate_msg]) rs = RadioSetting("aprs2.vibrate_msg", "Vibrate on personal message", val) menu.append(rs) val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs2.flash_bln]) rs = RadioSetting("aprs2.flash_bln", "Flash on bulletin message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs2.vibrate_bln]) rs = RadioSetting("aprs2.vibrate_bln", "Vibrate on bulletin message", val) menu.append(rs) val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs2.flash_grp]) rs = RadioSetting("aprs2.flash_grp", "Flash on group message", val) menu.append(rs) if self._has_vibrate: val = RadioSettingValueList(self._FLASH[:10], self._FLASH[aprs2.vibrate_grp]) rs = RadioSetting("aprs2.vibrate_grp", "Vibrate on group message", val) menu.append(rs) filter_val = [m.padded_string for m in aprs2.msg_group] filter_val = self._strip_ff_pads(filter_val) for index, filter_text in enumerate(filter_val): val = RadioSettingValueString(0, 9, filter_text) rs = RadioSetting("aprs2.msg_group_%d" % index, "Message Group %d" % (index + 1), val) menu.append(rs) rs.set_apply_callback(self.apply_ff_padded_string, aprs2.msg_group[index]) # TODO: Use filter_val as the list entries and update it on edit. val = RadioSettingValueList( self._MSG_GROUP_NAMES, self._MSG_GROUP_NAMES[aprs2.selected_msg_group]) rs = RadioSetting("aprs2.selected_msg_group", "Selected Message Group", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_mic_e) rs = RadioSetting("aprs.filter_mic_e", "Receive Mic-E Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_position) rs = RadioSetting("aprs.filter_position", "Receive Position Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_weather) rs = RadioSetting("aprs.filter_weather", "Receive Weather Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_object) rs = RadioSetting("aprs.filter_object", "Receive Object Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_item) rs = RadioSetting("aprs.filter_item", "Receive Item Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_status) rs = RadioSetting("aprs.filter_status", "Receive Status Beacons", val) menu.append(rs) val = RadioSettingValueBoolean(aprs.filter_other) rs = RadioSetting("aprs.filter_other", "Receive Other Beacons", val) menu.append(rs) return menu def _get_aprs_tx_settings(self): menu = RadioSettingGroup("aprs_tx", "APRS Transmit") aprs = self._memobj.aprs aprs2 = self._memobj.aprs2 beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon val = RadioSettingValueList( self._BEACON_TYPE, self._BEACON_TYPE[beacon_type]) rs = RadioSetting("aprs.transmit", "TX Beacons", val) rs.set_apply_callback(self.apply_beacon_type, aprs) menu.append(rs) val = RadioSettingValueList( self._TX_DELAY, self._TX_DELAY[aprs.tx_delay]) rs = RadioSetting("aprs.tx_delay", "TX Delay", val) menu.append(rs) val = RadioSettingValueList( self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval]) rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val) menu.append(rs) desc = [] status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt] status = self._strip_ff_pads(status) for index, msg_text in enumerate(status): val = RadioSettingValueString(0, 60, msg_text) desc.append("Beacon Status Text %d" % (index + 1)) rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1], val) rs.set_apply_callback(self.apply_ff_padded_string, self._memobj.aprs_beacon_status_txt[index]) menu.append(rs) val = RadioSettingValueList(desc, desc[aprs.selected_beacon_status_txt]) rs = RadioSetting("aprs.selected_beacon_status_txt", "Beacon Status Text", val) menu.append(rs) message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro] message_macro = self._strip_ff_pads(message_macro) for index, msg_text in enumerate(message_macro): val = RadioSettingValueString(0, 16, msg_text) rs = RadioSetting("aprs_msg_macro_%d" % index, "Message Macro %d" % (index + 1), val) rs.set_apply_callback(self.apply_ff_padded_string, self._memobj.aprs_msg_macro[index]) menu.append(rs) path_str = list(self._DIGI_PATHS) path_str[7] = self._digi_path_to_str(aprs.digi_path_7) val = RadioSettingValueString(0, 88, path_str[7]) rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7) menu.append(rs) # Show friendly messages for empty slots rather than blanks. # TODO: Rebuild this when digi_path_[34567] change. # path_str[3] = path_str[3] or self._DIGI_PATHS[3] # path_str[4] = path_str[4] or self._DIGI_PATHS[4] # path_str[5] = path_str[5] or self._DIGI_PATHS[5] # path_str[6] = path_str[6] or self._DIGI_PATHS[6] # path_str[7] = path_str[7] or self._DIGI_PATHS[7] path_str[7] = self._DIGI_PATHS[7] val = RadioSettingValueList(path_str, path_str[aprs2.selected_digi_path]) rs = RadioSetting("aprs2.selected_digi_path", "Selected Digi Path", val) menu.append(rs) return menu def _get_dtmf_settings(self): menu = RadioSettingGroup("dtmf_settings", "DTMF") dtmf = self._memobj.scan_settings val = RadioSettingValueList( self._DTMF_MODE, self._DTMF_MODE[dtmf.dtmf_mode]) rs = RadioSetting("scan_settings.dtmf_mode", "DTMF Mode", val) menu.append(rs) val = RadioSettingValueList( self._DTMF_SPEED, self._DTMF_SPEED[dtmf.dtmf_speed]) rs = RadioSetting("scan_settings.dtmf_speed", "DTMF AutoDial Speed", val) menu.append(rs) val = RadioSettingValueList( self._DTMF_DELAY, self._DTMF_DELAY[dtmf.dtmf_delay]) rs = RadioSetting("scan_settings.dtmf_delay", "DTMF AutoDial Delay", val) menu.append(rs) for i in range(10): name = "dtmf_%02d" % i dtmfsetting = self._memobj.dtmf[i] dtmfstr = "" for c in dtmfsetting.memory: if c == 0xFF: break if c < len(VX8_DTMF_CHARS): dtmfstr += VX8_DTMF_CHARS[c] dtmfentry = RadioSettingValueString(0, 16, dtmfstr) dtmfentry.set_charset(VX8_DTMF_CHARS + list("abcd ")) rs = RadioSetting(name, name.upper(), dtmfentry) rs.set_apply_callback(self.apply_dtmf, i) menu.append(rs) return menu def _get_misc_settings(self): menu = RadioSettingGroup("misc_settings", "Misc") scan_settings = self._memobj.scan_settings val = RadioSettingValueList( self._LCD_DIMMER, self._LCD_DIMMER[scan_settings.lcd_dimmer]) rs = RadioSetting("scan_settings.lcd_dimmer", "LCD Dimmer", val) menu.append(rs) val = RadioSettingValueList( self._LCD_CONTRAST, self._LCD_CONTRAST[scan_settings.lcd_contrast - 1]) rs = RadioSetting("scan_settings.lcd_contrast", "LCD Contrast", val) rs.set_apply_callback(self.apply_lcd_contrast, scan_settings) menu.append(rs) val = RadioSettingValueList( self._LAMP_KEY, self._LAMP_KEY[scan_settings.lamp]) rs = RadioSetting("scan_settings.lamp", "Lamp", val) menu.append(rs) beep_select = self._memobj.beep_select val = RadioSettingValueList( self._BEEP_SELECT, self._BEEP_SELECT[beep_select.beep]) rs = RadioSetting("beep_select.beep", "Beep Select", val) menu.append(rs) opening_message = self._memobj.opening_message val = RadioSettingValueList( self._OPENING_MESSAGE, self._OPENING_MESSAGE[opening_message.flag]) rs = RadioSetting("opening_message.flag", "Opening Msg Mode", val) menu.append(rs) msg = "" for i in opening_message.message.padded_yaesu: if i == 0xFF: break msg += CHARSET[i & 0x7F] val = RadioSettingValueString(0, 16, msg) rs = RadioSetting("opening_message.message.padded_yaesu", "Opening Message", val) rs.set_apply_callback(self.apply_ff_padded_yaesu, opening_message.message) menu.append(rs) return menu def _get_scan_settings(self): menu = RadioSettingGroup("scan_settings", "Scan") scan_settings = self._memobj.scan_settings val = RadioSettingValueList( self._VOL_MODE, self._VOL_MODE[scan_settings.vol_mode]) rs = RadioSetting("scan_settings.vol_mode", "Volume Mode", val) menu.append(rs) vfoa = self._memobj.vfo_info[0] val = RadioSettingValueList( self._VOLUME, self._VOLUME[vfoa.volume]) rs = RadioSetting("vfo_info[0].volume", "VFO A Volume", val) rs.set_apply_callback(self.apply_volume, 0) menu.append(rs) vfob = self._memobj.vfo_info[1] val = RadioSettingValueList( self._VOLUME, self._VOLUME[vfob.volume]) rs = RadioSetting("vfo_info[1].volume", "VFO B Volume", val) rs.set_apply_callback(self.apply_volume, 1) menu.append(rs) squelch = self._memobj.squelch val = RadioSettingValueList( self._SQUELCH, self._SQUELCH[squelch.vfo_a]) rs = RadioSetting("squelch.vfo_a", "VFO A Squelch", val) menu.append(rs) val = RadioSettingValueList( self._SQUELCH, self._SQUELCH[squelch.vfo_b]) rs = RadioSetting("squelch.vfo_b", "VFO B Squelch", val) menu.append(rs) val = RadioSettingValueList( self._SCAN_RESTART, self._SCAN_RESTART[scan_settings.scan_restart]) rs = RadioSetting("scan_settings.scan_restart", "Scan Restart", val) menu.append(rs) val = RadioSettingValueList( self._SCAN_RESUME, self._SCAN_RESUME[scan_settings.scan_resume]) rs = RadioSetting("scan_settings.scan_resume", "Scan Resume", val) menu.append(rs) val = RadioSettingValueList( self._OFF_ON, self._OFF_ON[scan_settings.busy_led]) rs = RadioSetting("scan_settings.busy_led", "Busy LED", val) menu.append(rs) val = RadioSettingValueList( self._OFF_ON, self._OFF_ON[scan_settings.scan_lamp]) rs = RadioSetting("scan_settings.scan_lamp", "Scan Lamp", val) menu.append(rs) val = RadioSettingValueList( self._TOT_TIME, self._TOT_TIME[scan_settings.tot]) rs = RadioSetting("scan_settings.tot", "Transmit Timeout (TOT)", val) menu.append(rs) return menu def _get_settings(self): top = RadioSettings(self._get_aprs_general_settings(), self._get_aprs_rx_settings(), self._get_aprs_tx_settings(), self._get_dtmf_settings(), self._get_misc_settings(), self._get_scan_settings()) return top def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None @staticmethod def apply_custom_symbol(setting, obj): # Ensure new value falls within known bounds, otherwise leave it as # it's a custom value from the radio that's outside our list. if setting.value.get_value() in chirp_common.APRS_SYMBOLS: setattr(obj, "custom_symbol", chirp_common.APRS_SYMBOLS.index(setting.value.get_value())) @classmethod def _apply_callsign(cls, callsign, obj, default_ssid=None): ssid = default_ssid dash_index = callsign.find("-") if dash_index >= 0: ssid = callsign[dash_index + 1:] callsign = callsign[:dash_index] try: ssid = int(ssid) % 16 except ValueError: ssid = default_ssid setattr(obj, "callsign", cls._add_ff_pad(callsign, 6)) if ssid is not None: setattr(obj, "ssid", ssid) def apply_beacon_type(cls, setting, obj): beacon_type = str(setting.value.get_value()) beacon_index = cls._BEACON_TYPE.index(beacon_type) tx_smartbeacon = beacon_index >> 1 tx_interval_beacon = beacon_index & 1 if tx_interval_beacon: setattr(obj, "tx_interval_beacon", 1) setattr(obj, "tx_smartbeacon", 0) elif tx_smartbeacon: setattr(obj, "tx_interval_beacon", 0) setattr(obj, "tx_smartbeacon", 1) else: setattr(obj, "tx_interval_beacon", 0) setattr(obj, "tx_smartbeacon", 0) @classmethod def apply_callsign(cls, setting, obj, default_ssid=None): # Uppercase, strip SSID then FF pad to max string length. callsign = setting.value.get_value().upper() cls._apply_callsign(callsign, obj, default_ssid) def apply_digi_path(self, setting, obj): # Parse and map to aprs.digi_path_4_7[0-3] or aprs.digi_path_8 # and FF terminate. path = str(setting.value.get_value()) callsigns = [c.strip() for c in path.split(",")] for index in range(len(obj.entry)): try: self._apply_callsign(callsigns[index], obj.entry[index], 0) except IndexError: self._apply_callsign("", obj.entry[index], 0) if len(callsigns) > len(obj.entry): raise Exception("This path only supports %d entries" % (index + 1)) @classmethod def apply_ff_padded_string(cls, setting, obj): # FF pad. val = setting.value.get_value() max_len = getattr(obj, "padded_string").size() / 8 val = str(val).rstrip() setattr(obj, "padded_string", cls._add_ff_pad(val, max_len)) @classmethod def apply_lat_long(cls, setting, obj): name = setting.get_name() is_latitude = name.endswith("latitude") lat_long = setting.value.get_value().strip() sign, l_d, l_m, l_s = cls._str_to_latlong(lat_long, is_latitude) LOG.debug("%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s)) setattr(obj, "%s_sign" % name, sign) setattr(obj, "%s_degree" % name, l_d) setattr(obj, "%s_minute" % name, l_m) setattr(obj, "%s_second" % name, l_s) def set_settings(self, settings): _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: if element.has_apply_callback(): LOG.debug("Using apply callback") try: element.run_apply_callback() except NotImplementedError as e: LOG.error(e) continue # Find the object containing setting. obj = _mem bits = element.get_name().split(".") setting = bits[-1] for name in bits[:-1]: if name.endswith("]"): name, index = name.split("[") index = int(index[:-1]) obj = getattr(obj, name)[index] else: obj = getattr(obj, name) try: old_val = getattr(obj, setting) LOG.debug("Setting %s(%r) <= %s" % ( element.get_name(), old_val, element.value)) setattr(obj, setting, element.value) except AttributeError as e: LOG.error("Setting %s is not in the memory map: %s" % (element.get_name(), e)) except Exception, e: LOG.debug(element.get_name()) raise def apply_ff_padded_yaesu(cls, setting, obj): # FF pad yaesus custom string format. rawval = setting.value.get_value() max_len = getattr(obj, "padded_yaesu").size() / 8 rawval = str(rawval).rstrip() val = [CHARSET.index(x) for x in rawval] for x in range(len(val), max_len): val.append(0xFF) obj.padded_yaesu = val def apply_volume(cls, setting, vfo): val = setting.value.get_value() cls._memobj.vfo_info[(vfo*2)].volume = val cls._memobj.vfo_info[(vfo*2)+1].volume = val def apply_lcd_contrast(cls, setting, obj): rawval = setting.value.get_value() val = cls._LCD_CONTRAST.index(rawval) + 1 obj.lcd_contrast = val def apply_dtmf(cls, setting, i): rawval = setting.value.get_value().upper().rstrip() val = [VX8_DTMF_CHARS.index(x) for x in rawval] for x in range(len(val), 16): val.append(0xFF) cls._memobj.dtmf[i].memory = val @directory.register class VX8DRadio(VX8Radio): """Yaesu VX-8DR""" MODEL = "VX-8DR" _model = "AH29D" _mem_params = (0xC128, # APRS message macros 7, # Number of message macros 0xC198, # APRS2 0xC24A, # APRS beacon metadata address. 50, # Number of beacons stored. 0xC6FA, # APRS beacon content address. 146, # Length of beacon data stored. 50) # Number of beacons stored. _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing") _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3") _POSITIONS = ("GPS", "Manual Latitude/Longitude", "Manual Latitude/Longitude", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9") _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds", "10 seconds", "20 seconds", "30 seconds", "60 seconds", "CONTINUOUS", "every 2 seconds", "every 3 seconds", "every 4 seconds", "every 5 seconds", "every 6 seconds", "every 7 seconds", "every 8 seconds", "every 9 seconds", "every 10 seconds", "every 20 seconds", "every 30 seconds", "every 40 seconds", "every 50 seconds", "every minute", "every 2 minutes", "every 3 minutes", "every 4 minutes", "every 5 minutes", "every 6 minutes", "every 7 minutes", "every 8 minutes", "every 9 minutes", "every 10 minutes") _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)] _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected") def _get_aprs_tx_settings(self): menu = RadioSettingGroup("aprs_tx", "APRS Transmit") aprs = self._memobj.aprs aprs2 = self._memobj.aprs2 beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon val = RadioSettingValueList( self._BEACON_TYPE, self._BEACON_TYPE[beacon_type]) rs = RadioSetting("aprs.transmit", "TX Beacons", val) rs.set_apply_callback(self.apply_beacon_type, aprs) menu.append(rs) val = RadioSettingValueList( self._TX_DELAY, self._TX_DELAY[aprs.tx_delay]) rs = RadioSetting("aprs.tx_delay", "TX Delay", val) menu.append(rs) val = RadioSettingValueList( self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval]) rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val) menu.append(rs) desc = [] status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt] status = self._strip_ff_pads(status) for index, msg_text in enumerate(status): val = RadioSettingValueString(0, 60, msg_text) desc.append("Beacon Status Text %d" % (index + 1)) rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1], val) rs.set_apply_callback(self.apply_ff_padded_string, self._memobj.aprs_beacon_status_txt[index]) menu.append(rs) val = RadioSettingValueList(desc, desc[aprs.selected_beacon_status_txt]) rs = RadioSetting("aprs.selected_beacon_status_txt", "Beacon Status Text", val) menu.append(rs) message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro] message_macro = self._strip_ff_pads(message_macro) for index, msg_text in enumerate(message_macro): val = RadioSettingValueString(0, 16, msg_text) rs = RadioSetting("aprs_msg_macro_%d" % index, "Message Macro %d" % (index + 1), val) rs.set_apply_callback(self.apply_ff_padded_string, self._memobj.aprs_msg_macro[index]) menu.append(rs) path_str = list(self._DIGI_PATHS) path_str[3] = self._digi_path_to_str(aprs2.digi_path_3_6[0]) val = RadioSettingValueString(0, 22, path_str[3]) rs = RadioSetting("aprs2.digi_path_3", "Digi Path 4 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[0]) menu.append(rs) path_str[4] = self._digi_path_to_str(aprs2.digi_path_3_6[1]) val = RadioSettingValueString(0, 22, path_str[4]) rs = RadioSetting("aprs2.digi_path_4", "Digi Path 5 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[1]) menu.append(rs) path_str[5] = self._digi_path_to_str(aprs2.digi_path_3_6[2]) val = RadioSettingValueString(0, 22, path_str[5]) rs = RadioSetting("aprs2.digi_path_5", "Digi Path 6 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[2]) menu.append(rs) path_str[6] = self._digi_path_to_str(aprs2.digi_path_3_6[3]) val = RadioSettingValueString(0, 22, path_str[6]) rs = RadioSetting("aprs2.digi_path_6", "Digi Path 7 (2 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[3]) menu.append(rs) path_str[7] = self._digi_path_to_str(aprs.digi_path_7) val = RadioSettingValueString(0, 88, path_str[7]) rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val) rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7) menu.append(rs) # Show friendly messages for empty slots rather than blanks. # TODO: Rebuild this when digi_path_[34567] change. # path_str[3] = path_str[3] or self._DIGI_PATHS[3] # path_str[4] = path_str[4] or self._DIGI_PATHS[4] # path_str[5] = path_str[5] or self._DIGI_PATHS[5] # path_str[6] = path_str[6] or self._DIGI_PATHS[6] # path_str[7] = path_str[7] or self._DIGI_PATHS[7] path_str[3] = self._DIGI_PATHS[3] path_str[4] = self._DIGI_PATHS[4] path_str[5] = self._DIGI_PATHS[5] path_str[6] = self._DIGI_PATHS[6] path_str[7] = self._DIGI_PATHS[7] val = RadioSettingValueList(path_str, path_str[aprs2.selected_digi_path]) rs = RadioSetting("aprs2.selected_digi_path", "Selected Digi Path", val) menu.append(rs) return menu def _get_aprs_smartbeacon(self): menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon") aprs2 = self._memobj.aprs2 val = RadioSettingValueList( self._SMARTBEACON_PROFILE, self._SMARTBEACON_PROFILE[aprs2.active_smartbeaconing]) rs = RadioSetting("aprs2.active_smartbeaconing", "SmartBeacon profile", val) menu.append(rs) for profile in range(3): pfx = "type%d" % (profile + 1) path = "aprs2.smartbeaconing_profile[%d]" % profile prof = aprs2.smartbeaconing_profile[profile] low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph) high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph) low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1) rs = RadioSetting("%s.low_speed_mph" % path, "%s Low Speed (mph)" % pfx, low_val) menu.append(rs) rs = RadioSetting("%s.high_speed_mph" % path, "%s High Speed (mph)" % pfx, high_val) menu.append(rs) val = RadioSettingValueInteger(1, 100, prof.slow_rate_min) rs = RadioSetting("%s.slow_rate_min" % path, "%s Slow rate (minutes)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec) rs = RadioSetting("%s.fast_rate_sec" % path, "%s Fast rate (seconds)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(5, 90, prof.turn_angle) rs = RadioSetting("%s.turn_angle" % path, "%s Turn angle (degrees)" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(1, 255, prof.turn_slop) rs = RadioSetting("%s.turn_slop" % path, "%s Turn slop" % pfx, val) menu.append(rs) val = RadioSettingValueInteger(5, 180, prof.turn_time_sec) rs = RadioSetting("%s.turn_time_sec" % path, "%s Turn time (seconds)" % pfx, val) menu.append(rs) return menu def _get_settings(self): top = RadioSettings(self._get_aprs_general_settings(), self._get_aprs_rx_settings(), self._get_aprs_tx_settings(), self._get_aprs_smartbeacon(), self._get_dtmf_settings(), self._get_misc_settings(), self._get_scan_settings()) return top @directory.register class VX8GERadio(VX8DRadio): """Yaesu VX-8GE""" MODEL = "VX-8GE" _model = "AH041" _has_vibrate = True _has_af_dual = False chirp-daily-20170714/chirp/drivers/ic9x_ll.py0000644000016101777760000003700412726733400022073 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 logging from chirp import chirp_common, util, errors, bitwise from chirp.memmap import MemoryMap LOG = logging.getLogger(__name__) 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: LOG.error("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: LOG.error("Broken frame: %s" % e) # LOG.debug("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" # LOG.debug("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: LOG.debug("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 LOG.debug("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.baudrate == 38400: resp = _send_magic_38400(pipe) if resp: return LOG.info("Switching from 38400 to 4800") pipe.baudrate = 4800 resp = _send_magic_4800(pipe) pipe.baudrate = 38400 if resp: return raise errors.RadioError("Radio not responding") elif pipe.baudrate == 4800: resp = _send_magic_4800(pipe) if resp: return LOG.info("Switching from 4800 to 38400") pipe.baudrate = 38400 resp = _send_magic_38400(pipe) if resp: return pipe.baudrate = 4800 raise errors.RadioError("Radio not responding") else: raise errors.InvalidDataError("Radio in unknown state (%i)" % pipe.baudrate) 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) # LOG.debug("Sending (%i):" % (len(frame.get_raw()))) # LOG.debug(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-daily-20170714/chirp/drivers/icp7.py0000644000016101777760000001522613101305576021371 0ustar jenkinsnogroup00000000000000# Copyright 2017 SASANO Takayoshi (JG1UAA) # # 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.drivers import icf from chirp import chirp_common, directory, bitwise # memory nuber: # 000 - 999 regular memory channels (supported, others not) # 1000 - 1049 scan edges # 1050 - 1249 auto write channels # 1250 call channel (C0) # 1251 call channel (C1) MEM_FORMAT = """ struct { ul32 freq; ul32 offset; ul16 train_sql:2, tmode:3, duplex:2, train_tone:9; ul16 tuning_step:4, rtone:6, ctone:6; ul16 unknown0:6, mode:3, dtcs:7; u8 unknown1:6, dtcs_polarity:2; char name[6]; } memory[1251]; #seekto 0x6b1e; struct { u8 bank; u8 index; } banks[1050]; #seekto 0x689e; u8 used[132]; #seekto 0x6922; u8 skips[132]; #seekto 0x69a6; u8 pskips[132]; #seekto 0x7352; struct { char name[6]; } bank_names[18]; """ MODES = ["FM", "WFM", "AM", "Auto"] TMODES = ["", "Tone", "TSQL", "", "DTCS"] DUPLEX = ["", "-", "+"] DTCS_POLARITY = ["NN", "NR", "RN", "RR"] TUNING_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, 200.0, 0.0] # 0.0 as "Auto" class ICP7Bank(icf.IcomBank): """ICP7 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 ICP7Radio(icf.IcomCloneModeRadio): """Icom IC-P7""" VENDOR = "Icom" MODEL = "IC-P7" _model = "\x28\x69\x00\x01" _memsize = 0x7500 _endframe = "Icom Inc\x2e\x41\x38" _ranges = [(0x0000, 0x7500, 32)] _num_banks = 18 _bank_class = ICP7Bank _can_hispeed = True 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, 999) rf.valid_tmodes = TMODES rf.valid_duplexes = DUPLEX rf.valid_modes = MODES rf.valid_bands = [(495000, 999990000)] rf.valid_skips = ["", "S", "P"] rf.valid_tuning_steps = TUNING_STEPS rf.valid_name_length = 6 rf.has_settings = True 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 mem.freq = _mem.freq / 3 mem.offset = _mem.offset / 3 mem.tmode = TMODES[_mem.tmode] mem.duplex = DUPLEX[_mem.duplex] mem.tuning_step = TUNING_STEPS[_mem.tuning_step] mem.rtone = chirp_common.TONES[_mem.rtone] mem.ctone = chirp_common.TONES[_mem.ctone] mem.mode = MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity] mem.name = str(_mem.name).rstrip() if _skp & bit: mem.skip = "P" if _psk & bit else "S" else: mem.skip = "" 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] if mem.empty: _usd |= bit # We use default value instead of zero-fill # to avoid unexpected behavior. _mem.freq = 15000 _mem.offset = 479985000 _mem.train_sql = ~0 _mem.tmode = ~0 _mem.duplex = ~0 _mem.train_tone = ~0 _mem.tuning_step = ~0 _mem.rtone = ~0 _mem.ctone = ~0 _mem.unknown0 = 0 _mem.mode = ~0 _mem.dtcs = ~0 _mem.unknown1 = ~0 _mem.dtcs_polarity = ~0 _mem.name = " " _skp |= bit _psk |= bit else: _usd &= ~bit _mem.freq = mem.freq * 3 _mem.offset = mem.offset * 3 _mem.train_sql = 0 # Train SQL mode (0:off 1:Tone 2:MSK) _mem.tmode = TMODES.index(mem.tmode) _mem.duplex = DUPLEX.index(mem.duplex) _mem.train_tone = 228 # Train SQL Tone (x10Hz) _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.ctone) _mem.unknown0 = 0 # unknown (always zero) _mem.mode = MODES.index(mem.mode) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.unknown1 = ~0 # unknown (always one) _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity) _mem.name = mem.name.ljust(6)[:6] if mem.skip == "S": _skp |= bit _psk &= ~bit elif mem.skip == "P": _skp |= bit _psk |= bit else: _skp &= ~bit _psk &= ~bit chirp-daily-20170714/chirp/drivers/ict7h.py0000644000016101777760000000715212475535623021557 0ustar jenkinsnogroup00000000000000# 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.drivers import icf from chirp import chirp_common, directory, 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 = (1, 60) 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 - 1] _flag = self._memobj.flags[number - 1] 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 - 1] _flag = self._memobj.flags[mem.number - 1] _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-daily-20170714/chirp/drivers/template.py0000644000016101777760000001072612475535623022355 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, 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 # Convert your low-level frequency to Hertz mem.freq = int(_mem.freq) 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] # Convert to low-level frequency representation _mem.freq = mem.freq _mem.name = mem.name.ljust(8)[:8] # Store the alpha tag chirp-daily-20170714/chirp/drivers/generic_csv.py0000644000016101777760000003501312475535623023025 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 import logging from chirp import chirp_common, errors, directory LOG = logging.getLogger(__name__) 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.file_has_rTone = None # Set in load(), used in _clean_tmode() self.file_has_cTone = None 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 _clean(self, headers, line, mem): """Runs post-processing functions on new mem objects. This is useful for parsing other CSV dialects when multiple columns convert to a single Chirp column.""" for attr in dir(mem): fname = "_clean_%s" % attr if hasattr(self, fname): mem = getattr(self, fname)(headers, line, mem) return mem def _clean_tmode(self, headers, line, mem): """ If there is exactly one of [rToneFreq, cToneFreq] columns in the csv file, use it for both rtone & ctone. Makes TSQL use friendlier.""" if self.file_has_rTone and not self.file_has_cTone: mem.ctone = mem.rtone elif self.file_has_cTone and not self.file_has_rTone: mem.rtone = mem.ctone return mem 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 in headers: try: typ, attr = self.ATTR_MAP[header] except KeyError: continue 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 self._clean(headers, line, 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 self.file_has_rTone = "rToneFreq" in header self.file_has_cTone = "cToneFreq" in header continue if len(header) > len(line): LOG.error("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: LOG.error("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: LOG.error(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) and \ (filedata.startswith("Location,") or filedata == "") @directory.register class CommanderCSVRadio(CSVRadio): """A driver for reading CSV files generated by KG-UV Commander software""" VENDOR = "Commander" MODEL = "KG-UV" FILE_EXTENSION = "csv" MODE_MAP = { "NARR": "NFM", "WIDE": "FM", } SCAN_MAP = { "ON": "", "OFF": "S" } ATTR_MAP = { "#": (int, "number"), "Name": (str, "name"), "RX Freq": (chirp_common.parse_freq, "freq"), "Scan": (lambda v: CommanderCSVRadio.SCAN_MAP.get(v), "skip"), "TX Dev": (lambda v: CommanderCSVRadio.MODE_MAP.get(v), "mode"), "Group/Notes": (str, "comment"), } def _clean_number(self, headers, line, mem): if mem.number == 0: for memory in self.memories: if memory.empty: mem.number = memory.number break return mem def _clean_duplex(self, headers, line, mem): try: txfreq = chirp_common.parse_freq( get_datum_by_header(headers, line, "TX Freq")) except ValueError: mem.duplex = "off" return mem if mem.freq == txfreq: mem.duplex = "" elif txfreq: mem.duplex = "split" mem.offset = txfreq return mem def _clean_tmode(self, headers, line, mem): rtone = get_datum_by_header(headers, line, "Encode") ctone = get_datum_by_header(headers, line, "Decode") if rtone == "OFF": rtone = None else: rtone = float(rtone) if ctone == "OFF": ctone = None else: ctone = float(ctone) if rtone: mem.tmode = "Tone" if ctone: mem.tmode = "TSQL" mem.rtone = rtone or 88.5 mem.ctone = ctone or mem.rtone return mem @classmethod def match_model(cls, filedata, filename): """Match files ending in .csv and using Commander column names.""" return filename.lower().endswith("." + cls.FILE_EXTENSION) and \ filedata.startswith("Name,RX Freq,TX Freq,Decode,Encode,TX Pwr," "Scan,TX Dev,Busy Lck,Group/Notes") or \ filedata.startswith('"#","Name","RX Freq","TX Freq","Decode",' '"Encode","TX Pwr","Scan","TX Dev",' '"Busy Lck","Group/Notes"') @directory.register class RTCSVRadio(CSVRadio): """A driver for reading CSV files generated by RT Systems software""" VENDOR = "RT Systems" MODEL = "CSV" FILE_EXTENSION = "csv" DUPLEX_MAP = { "Minus": "-", "Plus": "+", "Simplex": "", "Split": "split", } SKIP_MAP = { "Off": "", "On": "S", "P Scan": "P", "Skip": "S", } TMODE_MAP = { "None": "", "T Sql": "TSQL", } BOOL_MAP = { "Off": False, "On": True, } ATTR_MAP = { "Channel Number": (int, "number"), "Receive Frequency": (chirp_common.parse_freq, "freq"), "Offset Frequency": (chirp_common.parse_freq, "offset"), "Offset Direction": (lambda v: RTCSVRadio.DUPLEX_MAP.get(v, v), "duplex"), "Operating Mode": (str, "mode"), "Name": (str, "name"), "Tone Mode": (lambda v: RTCSVRadio.TMODE_MAP.get(v, v), "tmode"), "CTCSS": (lambda v: float(v.split(" ")[0]), "rtone"), "DCS": (int, "dtcs"), "Skip": (lambda v: RTCSVRadio.SKIP_MAP.get(v, v), "skip"), "Step": (lambda v: float(v.split(" ")[0]), "tuning_step"), "Mask": (lambda v: RTCSVRadio.BOOL_MAP.get(v, v), "empty",), "Comment": (str, "comment"), } def _clean_duplex(self, headers, line, mem): if mem.duplex == "split": try: val = get_datum_by_header(headers, line, "Transmit Frequency") val = chirp_common.parse_freq(val) mem.offset = val except OmittedHeaderError: pass return mem def _clean_mode(self, headers, line, mem): if mem.mode == "FM": try: val = get_datum_by_header(headers, line, "Half Dev") if self.BOOL_MAP[val]: mem.mode = "FMN" except OmittedHeaderError: pass return mem def _clean_ctone(self, headers, line, mem): # RT Systems only stores a single tone value mem.ctone = mem.rtone return mem @classmethod def match_model(cls, filedata, filename): """Match files ending in .csv and using RT Systems column names.""" # RT Systems provides a different set of columns for each radio. # We attempt to match only the first few columns, hoping they are # consistent across radio models. return filename.lower().endswith("." + cls.FILE_EXTENSION) and \ filedata.startswith("Channel Number,Receive Frequency," "Transmit Frequency,Offset Frequency," "Offset Direction,Operating Mode," "Name,Tone Mode,CTCSS,DCS") chirp-daily-20170714/chirp/drivers/icq7.py0000644000016101777760000002636312476257220021404 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 struct import logging from chirp.drivers import icf from chirp import chirp_common, directory, bitwise from chirp.chirp_common import to_GHz, from_GHz from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueFloat, RadioSettings LOG = logging.getLogger(__name__) 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]; #seekto 0x0767; struct { i8 rit; u8 squelch; u8 lock:1, ritfunct:1, unknown:6; u8 unknown1[6]; u8 d_sel; u8 autorp; u8 priority; u8 resume; u8 pause; u8 p_scan; u8 bnk_scan; u8 expand; u8 ch; u8 beep; u8 light; u8 ap_off; u8 p_save; u8 monitor; u8 speed; u8 edge; u8 lockgroup; } settings; """ 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] AUTORP_LIST = ["Off", "Duplex Only", "Duplex and Tone"] LOCKGROUP_LIST = ["Normal", "No Squelch", "No Volume", "All"] SQUELCH_LIST = ["Open", "Auto"] + ["L%s" % x for x in range(1, 10)] MONITOR_LIST = ["Push", "Hold"] LIGHT_LIST = ["Off", "On", "Auto"] PRIORITY_LIST = ["Off", "On", "Bell"] BANKSCAN_LIST = ["Off", "Bank 0", "Bank 1"] EDGE_LIST = ["%sP" % x for x in range(0, 20)] + ["Band", "All"] PAUSE_LIST = ["%s sec" % x for x in range(2, 22, 2)] + ["Hold"] RESUME_LIST = ["%s sec" % x for x in range(0, 6)] APOFF_LIST = ["Off"] + ["%s min" % x for x in range(30, 150, 30)] D_SEL_LIST = ["100 KHz", "1 MHz", "10 MHz"] @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.has_settings = True 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: LOG.error("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 def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") group = RadioSettings(basic) rs = RadioSetting("ch", "Channel Indication Mode", RadioSettingValueBoolean(_settings.ch)) basic.append(rs) rs = RadioSetting("expand", "Expanded Settings Mode", RadioSettingValueBoolean(_settings.expand)) basic.append(rs) rs = RadioSetting("beep", "Beep Tones", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting("autorp", "Auto Repeater Function", RadioSettingValueList( AUTORP_LIST, AUTORP_LIST[_settings.autorp])) basic.append(rs) rs = RadioSetting("ritfunct", "RIT Runction", RadioSettingValueBoolean(_settings.ritfunct)) basic.append(rs) rs = RadioSetting("rit", "RIT Shift (KHz)", RadioSettingValueInteger(-7, 7, _settings.rit)) basic.append(rs) rs = RadioSetting("lock", "Lock", RadioSettingValueBoolean(_settings.lock)) basic.append(rs) rs = RadioSetting("lockgroup", "Lock Group", RadioSettingValueList( LOCKGROUP_LIST, LOCKGROUP_LIST[_settings.lockgroup])) basic.append(rs) rs = RadioSetting("squelch", "Squelch", RadioSettingValueList( SQUELCH_LIST, SQUELCH_LIST[_settings.squelch])) basic.append(rs) rs = RadioSetting("monitor", "Monitor Switch Function", RadioSettingValueList( MONITOR_LIST, MONITOR_LIST[_settings.monitor])) basic.append(rs) rs = RadioSetting("light", "Display Backlighting", RadioSettingValueList( LIGHT_LIST, LIGHT_LIST[_settings.light])) basic.append(rs) rs = RadioSetting("priority", "Priority Watch Operation", RadioSettingValueList( PRIORITY_LIST, PRIORITY_LIST[_settings.priority])) basic.append(rs) rs = RadioSetting("p_scan", "Frequency Skip Function", RadioSettingValueBoolean(_settings.p_scan)) basic.append(rs) rs = RadioSetting("bnk_scan", "Memory Bank Scan Selection", RadioSettingValueList( BANKSCAN_LIST, BANKSCAN_LIST[_settings.bnk_scan])) basic.append(rs) rs = RadioSetting("edge", "Band Edge Scan Selection", RadioSettingValueList( EDGE_LIST, EDGE_LIST[_settings.edge])) basic.append(rs) rs = RadioSetting("pause", "Scan Pause Time", RadioSettingValueList( PAUSE_LIST, PAUSE_LIST[_settings.pause])) basic.append(rs) rs = RadioSetting("resume", "Scan Resume Time", RadioSettingValueList( RESUME_LIST, RESUME_LIST[_settings.resume])) basic.append(rs) rs = RadioSetting("p_save", "Power Saver", RadioSettingValueBoolean(_settings.p_save)) basic.append(rs) rs = RadioSetting("ap_off", "Auto Power-off Function", RadioSettingValueList( APOFF_LIST, APOFF_LIST[_settings.ap_off])) basic.append(rs) rs = RadioSetting("speed", "Dial Speed Acceleration", RadioSettingValueBoolean(_settings.speed)) basic.append(rs) rs = RadioSetting("d_sel", "Dial Select Step", RadioSettingValueList( D_SEL_LIST, D_SEL_LIST[_settings.d_sel])) basic.append(rs) 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 else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/uvb5.py0000644000016101777760000006325713067650002021415 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 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 logging from chirp import chirp_common, directory, bitwise, memmap, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueFloat, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) mem_format = """ struct memory { lbcd freq[4]; lbcd offset[4]; u8 unknown1:2, txpol:1, rxpol:1, compander:1, unknown2:3; u8 rxtone; u8 txtone; u8 pttid:1, scanadd:1, isnarrow:1, bcl:1, highpower:1, revfreq:1, duplex:2; u8 step; u8 unknown[3]; }; #seekto 0x0000; char ident[32]; u8 blank[16]; struct memory vfo1; struct memory channels[99]; #seekto 0x0850; struct memory vfo2; #seekto 0x09D0; u16 fm_presets[16]; #seekto 0x0A30; struct { u8 name[5]; } names[99]; #seekto 0x0D30; struct { u8 squelch; u8 freqmode_ab:1, save_funct:1, backlight:1, beep_tone_disabled:1, roger:1, tdr:1, scantype:2; u8 language:1, workmode_b:1, workmode_a:1, workmode_fm:1, voice_prompt:1, fm:1, pttid:2; u8 unknown_0:5, timeout:3; u8 mdf_b:2, mdf_a:2, unknown_1:2, txtdr:2; u8 unknown_2:4, ste_disabled:1, unknown_3:2, sidetone:1; u8 vox; u8 unk1; u8 mem_chan_a; u16 fm_vfo; u8 unk4; u8 unk5; u8 mem_chan_b; u8 unk6; u8 last_menu; // number of last menu item accessed } settings; #seekto 0x0D50; struct { u8 code[6]; } pttid; #seekto 0x0F30; struct { lbcd lower_vhf[2]; lbcd upper_vhf[2]; lbcd lower_uhf[2]; lbcd upper_uhf[2]; } limits; #seekto 0x0FF0; struct { u8 vhfsquelch0; u8 vhfsquelch1; u8 vhfsquelch2; u8 vhfsquelch3; u8 vhfsquelch4; u8 vhfsquelch5; u8 vhfsquelch6; u8 vhfsquelch7; u8 vhfsquelch8; u8 vhfsquelch9; u8 unknown1[6]; u8 uhfsquelch0; u8 uhfsquelch1; u8 uhfsquelch2; u8 uhfsquelch3; u8 uhfsquelch4; u8 uhfsquelch5; u8 uhfsquelch6; u8 uhfsquelch7; u8 uhfsquelch8; u8 uhfsquelch9; u8 unknown2[6]; u8 vhfhipwr0; u8 vhfhipwr1; u8 vhfhipwr2; u8 vhfhipwr3; u8 vhfhipwr4; u8 vhfhipwr5; u8 vhfhipwr6; u8 vhfhipwr7; u8 vhflopwr0; u8 vhflopwr1; u8 vhflopwr2; u8 vhflopwr3; u8 vhflopwr4; u8 vhflopwr5; u8 vhflopwr6; u8 vhflopwr7; u8 uhfhipwr0; u8 uhfhipwr1; u8 uhfhipwr2; u8 uhfhipwr3; u8 uhfhipwr4; u8 uhfhipwr5; u8 uhfhipwr6; u8 uhfhipwr7; u8 uhflopwr0; u8 uhflopwr1; u8 uhflopwr2; u8 uhflopwr3; u8 uhflopwr4; u8 uhflopwr5; u8 uhflopwr6; u8 uhflopwr7; } test; """ def do_ident(radio): radio.pipe.timeout = 3 radio.pipe.write("\x05PROGRAM") for x in xrange(10): ack = radio.pipe.read(1) if ack == '\x06': break else: raise errors.RadioError("Radio did not ack programming mode") radio.pipe.write("\x02") ident = radio.pipe.read(8) LOG.debug(util.hexprint(ident)) if not ident.startswith('HKT511'): raise errors.RadioError("Unsupported model") radio.pipe.write("\x06") ack = radio.pipe.read(1) if ack != "\x06": raise errors.RadioError("Radio did not ack ident") def do_status(radio, direction, addr): status = chirp_common.Status() status.msg = "Cloning %s radio" % direction status.cur = addr status.max = 0x1000 radio.status_fn(status) def do_download(radio): do_ident(radio) data = "KT511 Radio Program data v1.08\x00\x00" data += ("\x00" * 16) firstack = None for i in range(0, 0x1000, 16): frame = struct.pack(">cHB", "R", i, 16) radio.pipe.write(frame) result = radio.pipe.read(20) if frame[1:4] != result[1:4]: LOG.debug(util.hexprint(result)) raise errors.RadioError("Invalid response for address 0x%04x" % i) radio.pipe.write("\x06") ack = radio.pipe.read(1) if not firstack: firstack = ack else: if not ack == firstack: LOG.debug("first ack: %s ack received: %s", util.hexprint(firstack), util.hexprint(ack)) raise errors.RadioError("Unexpected response") data += result[4:] do_status(radio, "from", i) return memmap.MemoryMap(data) def do_upload(radio): do_ident(radio) data = radio._mmap[0x0030:] for i in range(0, 0x1000, 16): frame = struct.pack(">cHB", "W", i, 16) frame += data[i:i + 16] radio.pipe.write(frame) ack = radio.pipe.read(1) if ack != "\x06": # UV-B5/UV-B6 radios with 27 menus do not support service settings # and will stop ACKing when the upload reaches 0x0F10 if i == 0x0F10: # must be a radio with 27 menus detected - stop upload break else: LOG.debug("Radio NAK'd block at address 0x%04x" % i) raise errors.RadioError( "Radio NAK'd block at address 0x%04x" % i) LOG.debug("Radio ACK'd block at address 0x%04x" % i) do_status(radio, "to", i) DUPLEX = ["", "-", "+"] UVB5_STEPS = [5.00, 6.25, 10.0, 12.5, 20.0, 25.0] CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ/_+*" SPECIALS = { "VFO1": -2, "VFO2": -1, } POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), chirp_common.PowerLevel("High", watts=5)] @directory.register class BaofengUVB5(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baofeng UV-B5""" VENDOR = "Baofeng" MODEL = "UV-B5" BAUD_RATE = 9600 _memsize = 0x1000 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This version of the UV-B5 driver allows you to ' 'modify the Test Mode 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 these values may ' 'have unintended consequences, including damage to your ' 'device. You have been warned. Proceed at your own risk!') rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on. 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to download image from device.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to mic/spkr connector. 3. Make sure connector is firmly connected. 4. Turn radio on. 5. Ensure that the radio is tuned to channel with no activity. 6. Click OK to upload image to device.""")) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_cross = True rf.has_rx_dtcs = True rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] rf.valid_duplexes = DUPLEX + ["split"] rf.can_odd_split = True rf.valid_skips = ["", "S"] rf.valid_characters = CHARSET rf.valid_name_length = 5 rf.valid_bands = [(130000000, 175000000), (220000000, 269000000), (400000000, 520000000)] rf.valid_modes = ["FM", "NFM"] rf.valid_special_chans = SPECIALS.keys() rf.valid_power_levels = POWER_LEVELS rf.has_ctone = True rf.has_bank = False rf.has_tuning_step = False rf.memory_bounds = (1, 99) return rf 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 process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.channels[number - 1]) def _decode_tone(self, value, flag): if value > 155: mode = val = pol = None elif value > 50: mode = 'DTCS' val = chirp_common.DTCS_CODES[value - 51] pol = flag and 'R' or 'N' elif value: mode = 'Tone' val = chirp_common.TONES[value - 1] pol = None else: mode = val = pol = None return mode, val, pol def _encode_tone(self, _mem, which, mode, val, pol): def _set(field, value): setattr(_mem, "%s%s" % (which, field), value) _set("pol", 0) if mode == "Tone": _set("tone", chirp_common.TONES.index(val) + 1) elif mode == "DTCS": _set("tone", chirp_common.DTCS_CODES.index(val) + 51) _set("pol", pol == "R") else: _set("tone", 0) def _get_memobjs(self, number): if isinstance(number, str): return (getattr(self._memobj, number.lower()), None) elif number < 0: for k, v in SPECIALS.items(): if number == v: return (getattr(self._memobj, k.lower()), None) else: return (self._memobj.channels[number - 1], self._memobj.names[number - 1].name) def get_memory(self, number): _mem, _nam = self._get_memobjs(number) mem = chirp_common.Memory() if isinstance(number, str): mem.number = SPECIALS[number] mem.extd_number = number else: mem.number = number if _mem.freq.get_raw()[0] == "\xFF": mem.empty = True return mem mem.freq = int(_mem.freq) * 10 mem.offset = int(_mem.offset) * 10 chirp_common.split_tone_decode( mem, self._decode_tone(_mem.txtone, _mem.txpol), self._decode_tone(_mem.rxtone, _mem.rxpol)) if _mem.step > 0x05: _mem.step = 0x00 mem.duplex = DUPLEX[_mem.duplex] mem.mode = _mem.isnarrow and "NFM" or "FM" mem.skip = "" if _mem.scanadd else "S" mem.power = POWER_LEVELS[_mem.highpower] if _nam: for char in _nam: try: mem.name += CHARSET[char] except IndexError: break mem.name = mem.name.rstrip() mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(_mem.bcl)) mem.extra.append(rs) rs = RadioSetting("revfreq", "Reverse Duplex", RadioSettingValueBoolean(_mem.revfreq)) mem.extra.append(rs) rs = RadioSetting("pttid", "PTT ID", RadioSettingValueBoolean(_mem.pttid)) mem.extra.append(rs) rs = RadioSetting("compander", "Compander", RadioSettingValueBoolean(_mem.compander)) mem.extra.append(rs) return mem def set_memory(self, mem): _mem, _nam = self._get_memobjs(mem.number) if mem.empty: if _nam is None: raise errors.InvalidValueError("VFO channels can not be empty") _mem.set_raw("\xFF" * 16) return if _mem.get_raw() == ("\xFF" * 16): _mem.set_raw("\x00" * 13 + "\xFF" * 3) _mem.freq = mem.freq / 10 if mem.duplex == "split": diff = mem.offset - mem.freq _mem.duplex = DUPLEX.index("-") if diff < 0 else DUPLEX.index("+") _mem.offset = abs(diff) / 10 else: _mem.offset = mem.offset / 10 _mem.duplex = DUPLEX.index(mem.duplex) tx, rx = chirp_common.split_tone_encode(mem) self._encode_tone(_mem, 'tx', *tx) self._encode_tone(_mem, 'rx', *rx) _mem.isnarrow = mem.mode == "NFM" _mem.scanadd = mem.skip == "" _mem.highpower = mem.power == POWER_LEVELS[1] if _nam: for i in range(0, 5): try: _nam[i] = CHARSET.index(mem.name[i]) except IndexError: _nam[i] = 0xFF for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") group = RadioSettings(basic) options = ["Time", "Carrier", "Search"] rs = RadioSetting("scantype", "Scan Type", RadioSettingValueList(options, options[_settings.scantype])) basic.append(rs) options = ["Off"] + ["%s min" % x for x in range(1, 8)] rs = RadioSetting("timeout", "Time Out Timer", RadioSettingValueList( options, options[_settings.timeout])) basic.append(rs) options = ["A", "B"] rs = RadioSetting("freqmode_ab", "Frequency Mode", RadioSettingValueList( options, options[_settings.freqmode_ab])) basic.append(rs) options = ["Frequency Mode", "Channel Mode"] rs = RadioSetting("workmode_a", "Radio Work Mode(A)", RadioSettingValueList( options, options[_settings.workmode_a])) basic.append(rs) rs = RadioSetting("workmode_b", "Radio Work Mode(B)", RadioSettingValueList( options, options[_settings.workmode_b])) basic.append(rs) options = ["Frequency", "Name", "Channel"] rs = RadioSetting("mdf_a", "Display Format(F1)", RadioSettingValueList( options, options[_settings.mdf_a])) basic.append(rs) rs = RadioSetting("mdf_b", "Display Format(F2)", RadioSettingValueList( options, options[_settings.mdf_b])) basic.append(rs) rs = RadioSetting("mem_chan_a", "Mem Channel (A)", RadioSettingValueInteger( 1, 99, _settings.mem_chan_a)) basic.append(rs) rs = RadioSetting("mem_chan_b", "Mem Channel (B)", RadioSettingValueInteger( 1, 99, _settings.mem_chan_b)) basic.append(rs) options = ["Off", "BOT", "EOT", "Both"] rs = RadioSetting("pttid", "PTT-ID", RadioSettingValueList( options, options[_settings.pttid])) basic.append(rs) dtmfchars = "0123456789ABCD*#" _codeobj = self._memobj.pttid.code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 6, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("pttid.code", "PTT-ID Code", val) def apply_code(setting, obj): code = [] for j in range(0, 6): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.pttid) basic.append(rs) rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("vox", "VOX Level", RadioSettingValueInteger(0, 9, _settings.vox)) basic.append(rs) options = ["Frequency Mode", "Channel Mode"] rs = RadioSetting("workmode_fm", "FM Work Mode", RadioSettingValueList( options, options[_settings.workmode_fm])) basic.append(rs) options = ["Current Frequency", "F1 Frequency", "F2 Frequency"] rs = RadioSetting("txtdr", "Dual Standby TX Priority", RadioSettingValueList(options, options[_settings.txtdr])) basic.append(rs) options = ["English", "Chinese"] rs = RadioSetting("language", "Language", RadioSettingValueList(options, options[_settings.language])) basic.append(rs) rs = RadioSetting("tdr", "Dual Standby", RadioSettingValueBoolean(_settings.tdr)) basic.append(rs) rs = RadioSetting("roger", "Roger Beep", RadioSettingValueBoolean(_settings.roger)) basic.append(rs) rs = RadioSetting("backlight", "Backlight", RadioSettingValueBoolean(_settings.backlight)) basic.append(rs) rs = RadioSetting("save_funct", "Save Mode", RadioSettingValueBoolean(_settings.save_funct)) basic.append(rs) rs = RadioSetting("fm", "FM Function", RadioSettingValueBoolean(_settings.fm)) basic.append(rs) rs = RadioSetting("beep_tone_disabled", "Beep Prompt", RadioSettingValueBoolean( not _settings.beep_tone_disabled)) basic.append(rs) rs = RadioSetting("voice_prompt", "Voice Prompt", RadioSettingValueBoolean(_settings.voice_prompt)) basic.append(rs) rs = RadioSetting("sidetone", "DTMF Side Tone", RadioSettingValueBoolean(_settings.sidetone)) basic.append(rs) rs = RadioSetting("ste_disabled", "Squelch Tail Eliminate", RadioSettingValueBoolean(not _settings.ste_disabled)) basic.append(rs) _limit = int(self._memobj.limits.lower_vhf) / 10 rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (MHz)", RadioSettingValueInteger(128, 270, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.lower_vhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.upper_vhf) / 10 rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (MHz)", RadioSettingValueInteger(128, 270, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.upper_vhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.lower_uhf) / 10 rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (MHz)", RadioSettingValueInteger(400, 520, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.lower_uhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.upper_uhf) / 10 rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (MHz)", RadioSettingValueInteger(400, 520, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.upper_uhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets") group.append(fm_preset) for i in range(0, 16): if self._memobj.fm_presets[i] < 0x01AF: used = True preset = self._memobj.fm_presets[i] / 10.0 + 65 else: used = False preset = 65 rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1), RadioSettingValueBoolean(used), RadioSettingValueFloat(65, 108, preset, 0.1, 1)) fm_preset.append(rs) testmode = RadioSettingGroup("testmode", "Test Mode Settings") group.append(testmode) vhfdata = ["136-139", "140-144", "145-149", "150-154", "155-159", "160-164", "165-169", "170-174"] uhfdata = ["400-409", "410-419", "420-429", "430-439", "440-449", "450-459", "460-469", "470-479"] powernamedata = ["Hi", "Lo"] powerkeydata = ["hipwr", "lopwr"] for power in range(0, 2): for index in range(0, 8): key = "test.vhf%s%i" % (powerkeydata[power], index) name = "%s Mhz %s Power" % (vhfdata[index], powernamedata[power]) rs = RadioSetting( key, name, RadioSettingValueInteger( 0, 255, getattr( self._memobj.test, "vhf%s%i" % (powerkeydata[power], index)))) testmode.append(rs) for power in range(0, 2): for index in range(0, 8): key = "test.uhf%s%i" % (powerkeydata[power], index) name = "%s Mhz %s Power" % (uhfdata[index], powernamedata[power]) rs = RadioSetting( key, name, RadioSettingValueInteger( 0, 255, getattr( self._memobj.test, "uhf%s%i" % (powerkeydata[power], index)))) testmode.append(rs) for band in ["vhf", "uhf"]: for index in range(0, 10): key = "test.%ssquelch%i" % (band, index) name = "%s Squelch %i" % (band.upper(), index) rs = RadioSetting( key, name, RadioSettingValueInteger( 0, 255, getattr( self._memobj.test, "%ssquelch%i" % (band, index)))) testmode.append(rs) return group def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "beep_tone_disabled": setattr(obj, setting, not int(element.value)) elif setting == "ste_disabled": setattr(obj, setting, not int(element.value)) else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): for element in settings: try: index = (int(element.get_name().split("_")[-1])) val = element.value if val[0].get_value(): value = int(val[1].get_value() * 10 - 650) else: value = 0x01AF LOG.debug("Setting fm_presets[%1i] = %s" % (index, value)) setting = self._memobj.fm_presets setting[index] = value except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): return (filedata.startswith("KT511 Radio Program data") and len(filedata) == (cls._memsize + 0x30)) chirp-daily-20170714/chirp/drivers/tk760.py0000644000016101777760000006640413115722176021412 0ustar jenkinsnogroup00000000000000# Copyright 2016 Pavel Milanes CO7WT, # # 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 time import struct import logging LOG = logging.getLogger(__name__) from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettings from textwrap import dedent MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; } memory[32]; #seekto 0x0100; struct { lbcd rx_tone[2]; lbcd tx_tone[2]; } tone[32]; #seekto 0x0180; struct { u8 unknown0:1, unknown1:1, wide:1, // wide: 1 = wide, 0 = narrow power:1, // power: 1 = high, 0 = low busy_lock:1, // busy lock: 1 = off, 0 = on pttid:1, // ptt id: 1 = off, 0 = on dtmf:1, // dtmf signaling: 1 = off, 0 = on twotone:1; // 2-tone signaling: 1 = off, 0 = on } ch_settings[32]; #seekto 0x02B0; struct { u8 unknown10[16]; // x02b0 u8 unknown11[16]; // x02c0 u8 active[4]; // x02d0 u8 scan[4]; // x02d4 u8 unknown12[8]; // x02d8 u8 unknown13; // x02e0 u8 kMON; // 0x02d1 MON Key u8 kA; // 0x02d2 A Key u8 kSCN; // 0x02d3 SCN Key u8 kDA; // 0x02d4 D/A Key u8 unknown14; // x02e5 u8 min_vol; // x02e6 byte 0-31 0 = off u8 poweron_tone; // x02e7 power on tone 0 = off, 1 = on u8 tot; // x02e8 Time out Timer 0 = off, 1 = 30s (max 300) u8 unknown15[3]; // x02e9-x02eb u8 dealer_tuning; // x02ec ? bit 0? 0 = off, 1 = on u8 clone; // x02ed ? bit 0? 0 = off, 1 = on u8 unknown16[2]; // x02ee-x2ef u8 unknown17[16]; // x02f0 u8 unknown18[5]; // x0300 u8 clear2transpond; // x0305 byte 0 = off, 1 = on u8 off_hook_decode; // x0306 byte 0 = off, 1 = on u8 off_hook_hornalert; // x0307 byte 0 = off, 1 = on u8 unknown19[8]; // x0308-x030f u8 unknown20[16]; // x0310 } settings; """ KEYS = { 0x00: "Disabled", 0x01: "Monitor", 0x02: "Talk Around", 0x03: "Horn Alert", 0x04: "Public Adress", 0x05: "Auxiliary", 0x06: "Scan", 0x07: "Scan Del/Add", 0x08: "Home Channel", 0x09: "Operator Selectable Tone" } MEM_SIZE = 0x400 BLOCK_SIZE = 8 MEM_BLOCKS = range(0, (MEM_SIZE / BLOCK_SIZE)) ACK_CMD = "\x06" # from 0.03 up it' s safe # I have to turn it up, some users reported problems with this, was 0.05 TIMEOUT = 0.1 POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), chirp_common.PowerLevel("High", watts=5)] MODES = ["NFM", "FM"] SKIP_VALUES = ["", "S"] TONES = chirp_common.TONES #TONES.remove(254.1) DTCS_CODES = chirp_common.DTCS_CODES TOT = ["off"] + ["%s" % x for x in range(30, 330, 30)] VOL = ["off"] + ["%s" % x for x in range(1, 32)] def rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) #print("<= %02i: %s" % (len(data), util.hexprint(data))) except: raise errors.RadioError("Error reading data from radio") return data def rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) #print("=> %02i: %s" % (len(data), util.hexprint(data))) except: raise errors.RadioError("Error sending data from radio") def send(radio, frame): """Generic send data to the radio""" rawsend(radio, frame) def make_frame(cmd, addr, data=""): """Pack the info in the format it likes""" ts = struct.pack(">BHB", ord(cmd), addr, 8) if data == "": return ts else: if len(data) == 8: return ts + data else: raise errors.InvalidValueError("Data length of unexpected length") def handshake(radio, msg="", full=False): """Make a full handshake, if not full just hals""" # send ACK if commandes if full is True: rawsend(radio, ACK_CMD) # receive ACK ack = rawrecv(radio, 1) # check ACK if ack != ACK_CMD: #close_radio(radio) mesg = "Handshake failed: " + msg raise errors.RadioError(mesg) def recv(radio): """Receive data from the radio, 12 bytes, 4 in the header, 8 as data""" rxdata = rawrecv(radio, 12) if len(rxdata) != 12: raise errors.RadioError( "Received a length of data that is not possible") return cmd, addr, length = struct.unpack(">BHB", rxdata[0:4]) data = "" if length == 8: data = rxdata[4:] return data def open_radio(radio): """Open the radio into program mode and check if it's the correct model""" # Set serial discipline try: radio.pipe.parity = "N" radio.pipe.timeout = TIMEOUT radio.pipe.flush() LOG.debug("Serial port open successful") except: msg = "Serial error: Can't set serial line discipline" raise errors.RadioError(msg) magic = "PROGRAM" LOG.debug("Sending MAGIC") exito = False # it appears that some buggy interfaces/serial devices keep sending # data in the RX line, we will try to catch this garbage here devnull = rawrecv(radio, 256) for i in range(0, 5): LOG.debug("Try %i" % i) for i in range(0, len(magic)): ack = rawrecv(radio, 1) time.sleep(0.05) send(radio, magic[i]) try: handshake(radio, "Radio not entering Program mode") LOG.debug("Radio opened for programming") exito = True break except: LOG.debug("No go, next try") pass # validate the success if exito is False: msg = "Radio refuse to enter into program mode after a few tries" raise errors.RadioError(msg) rawsend(radio, "\x02") ident = rawrecv(radio, 8) # validate the input if len(ident) != 8: LOG.debug("Wrong ID, get only %s bytes, we expect 8" % len(ident)) LOG.debug(hexprint(ident)) msg = "Bad ID received, just %s bytes, we want 8" % len(ident) raise errors.RadioError(msg) handshake(radio, "Comm error after ident", True) LOG.debug("Correct get ident and hanshake") if not (radio.TYPE in ident): LOG.debug("Incorrect model ID:") LOG.debug(util.hexprint(ident)) msg = "Incorrect model ID, got %s, it not contains %s" % \ (ident[0:5], radio.TYPE) raise errors.RadioError(msg) LOG.debug("Full ident string is:") LOG.debug(util.hexprint(ident)) def do_download(radio): """This is your download function""" open_radio(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) data = "" LOG.debug("Starting the downolad") for addr in MEM_BLOCKS: send(radio, make_frame("R", addr * BLOCK_SIZE)) data += recv(radio) handshake(radio, "Rx error in block %03i" % addr, True) LOG.debug("Block: %04x, Pos: %06x" % (addr, addr * BLOCK_SIZE)) # UI Update status.cur = addr status.msg = "Cloning from radio..." radio.status_fn(status) return memmap.MemoryMap(data) def do_upload(radio): """Upload info to radio""" open_radio(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) count = 0 for addr in MEM_BLOCKS: # UI Update status.cur = addr status.msg = "Cloning to radio..." radio.status_fn(status) pos = addr * BLOCK_SIZE if pos > 0x0378: # it seems that from this point forward is read only !?!?!? continue data = radio.get_mmap()[pos:pos + BLOCK_SIZE] send(radio, make_frame("W", pos, data)) LOG.debug("Block: %04x, Pos: %06x" % (addr, pos)) time.sleep(0.1) handshake(radio, "Rx error in block %04x" % addr) def get_rid(data): """Extract the radio identification from the firmware""" rid = data[0x0378:0x0380] # we have to invert rid nrid = "" for i in range(1, len(rid) + 1): nrid += rid[-i] rid = nrid return rid def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = get_rid(data) # DEBUG #print("Full ident string is %s" % util.hexprint(rid)) if (rid in cls.VARIANTS): # correct model return True else: return False class Kenwood_M60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Kenwood Mobile Family 60 Radios""" VENDOR = "Kenwood" _range = [136000000, 500000000] # don't mind, it will be overwritten _upper = 32 VARIANT = "" MODEL = "" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This driver is experimental; not all features have been ' 'implemented, but it has those features most used by hams.\n' '\n' 'This radios are able to work slightly outside the OEM ' 'frequency limits. After testing, the limit in Chirp has ' 'been set 4% outside the OEM limit. This allows you to use ' 'some models on the ham bands.\n' '\n' 'Nevertheless, each radio has its own hardware limits and ' 'your mileage may vary.\n' ) rp.pre_download = _(dedent("""\ Follow this instructions to read your radio: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to write your radio: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.has_name = False rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = MODES rf.valid_duplexes = ["", "-", "+", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_skips = SKIP_VALUES rf.valid_dtcs_codes = DTCS_CODES rf.valid_bands = [self._range] rf.memory_bounds = (1, self._upper) return rf def sync_in(self): """Download from radio""" self._mmap = do_download(self) self.process_mmap() def sync_out(self): """Upload to radio""" # Get the data ready for upload try: self._prep_data() except: raise errors.RadioError("Error processing the radio data") # do the upload try: do_upload(self) except: raise errors.RadioError("Error uploading data to radio") def set_variant(self): """Select and set the correct variables for the class acording to the correct variant of the radio""" rid = get_rid(self._mmap) # indentify the radio variant and set the enviroment to it's values try: self._upper, low, high, self._kind = self.VARIANTS[rid] # Frequency ranges: some model/variants are able to work the near # ham bands, even if they are outside the OEM ranges. # By experimentation we found that a +/- 4% at the edges is in most # cases safe and will cover the near ham band in full self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04] # put the VARIANT in the class, clean the model / CHs / Type # in the same layout as the KPG program self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: " # In the OEM string we show the real OEM ranges self._VARIANT += self._kind + ", %d - %d MHz" % (low, high) except KeyError: LOG.debug("Wrong Kenwood radio, ID or unknown variant") LOG.debug(util.hexprint(rid)) raise errors.RadioError( "Wrong Kenwood radio, ID or unknown variant, see LOG output.") def _prep_data(self): """Prepare the areas in the memmap to do a consistend write it has to make an update on the x200 flag data""" achs = 0 for i in range(0, self._upper): if self.get_active(i) is True: achs += 1 # The x0200 area has the settings for the DTMF/2-Tone per channel, # as by default any of this radios has the DTMF IC installed; # we clean this areas fldata = "\x00\xf0\xff\xff\xff" * achs + \ "\xff" * (5 * (self._upper - achs)) self._fill(0x0200, fldata) def _fill(self, offset, data): """Fill an specified area of the memmap with the passed data""" for addr in range(0, len(data)): self._mmap[offset + addr] = data[addr] def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # to set the vars on the class to the correct ones self.set_variant() def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_scan(self, chan): """Get the channel scan status from the 4 bytes array on the eeprom then from the bits on the byte, return '' or 'S' as needed""" result = "S" byte = int(chan/8) bit = chan % 8 res = self._memobj.settings.scan[byte] & (pow(2, bit)) if res > 0: result = "" return result def set_scan(self, chan, value): """Set the channel scan status from UI to the mem_map""" byte = int(chan/8) bit = chan % 8 # get the actual value to see if I need to change anything actual = self.get_scan(chan) if actual != value: # I have to flip the value rbyte = self._memobj.settings.scan[byte] rbyte = rbyte ^ pow(2, bit) self._memobj.settings.scan[byte] = rbyte def get_active(self, chan): """Get the channel active status from the 4 bytes array on the eeprom then from the bits on the byte, return True/False""" byte = int(chan/8) bit = chan % 8 res = self._memobj.settings.active[byte] & (pow(2, bit)) return bool(res) def set_active(self, chan, value=True): """Set the channel active status from UI to the mem_map""" byte = int(chan/8) bit = chan % 8 # get the actual value to see if I need to change anything actual = self.get_active(chan) if actual != bool(value): # I have to flip the value rbyte = self._memobj.settings.active[byte] rbyte = rbyte ^ pow(2, bit) self._memobj.settings.active[byte] = rbyte def get_memory(self, number): """Get the mem representation from the radio image""" _mem = self._memobj.memory[number - 1] _tone = self._memobj.tone[number - 1] _ch = self._memobj.ch_settings[number - 1] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # Memory number mem.number = number if _mem.get_raw()[0] == "\xFF" or not self.get_active(number - 1): mem.empty = True # but is not enough, you have to crear the memory in the mmap # to get it ready for the sync_out process _mem.set_raw("\xFF" * 8) return mem # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[4] == "\xFF": # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX feq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 # power mem.power = POWER_LEVELS[_ch.power] # wide/marrow mem.mode = MODES[_ch.wide] # skip mem.skip = self.get_scan(number - 1) # tone data rxtone = txtone = None txtone = self.decode_tone(_tone.tx_tone) rxtone = self.decode_tone(_tone.rx_tone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra # bank and number in the channel mem.extra = RadioSettingGroup("extra", "Extra") bl = RadioSetting("busy_lock", "Busy Channel lock", RadioSettingValueBoolean( not bool(_ch.busy_lock))) mem.extra.append(bl) return mem def set_memory(self, mem): """Set the memory data in the eeprom img from the UI not ready yet, so it will return as is""" # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number - 1] _tone = self._memobj.tone[mem.number - 1] _ch = self._memobj.ch_settings[mem.number - 1] # Empty memory if mem.empty: _mem.set_raw("\xFF" * 8) # empty the active bit self.set_active(mem.number - 1, False) return # freq rx _mem.rxfreq = mem.freq / 10 # freq tx if mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": for byte in _mem.txfreq: byte.set_raw("\xFF") else: _mem.txfreq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self.encode_tone(_tone.tx_tone, txmode, txtone, txpol) self.encode_tone(_tone.rx_tone, rxmode, rxtone, rxpol) # power, default power is low if mem.power is None: mem.power = POWER_LEVELS[0] _ch.power = POWER_LEVELS.index(mem.power) # wide/marrow _ch.wide = MODES.index(mem.mode) # skip self.set_scan(mem.number - 1, mem.skip) # extra settings for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) # set the mem a active in the _memmap self.set_active(mem.number - 1) return mem @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" sett = self._memobj.settings # basic features of the radio basic = RadioSettingGroup("basic", "Basic Settings") # buttons fkeys = RadioSettingGroup("keys", "Front keys config") top = RadioSettings(basic, fkeys) # Basic val = RadioSettingValueString(0, 35, self._VARIANT) val.set_mutable(False) mod = RadioSetting("not.mod", "Radio version", val) basic.append(mod) tot = RadioSetting("settings.tot", "Time Out Timer (TOT)", RadioSettingValueList(TOT, TOT[int(sett.tot)])) basic.append(tot) minvol = RadioSetting("settings.min_vol", "Minimum volume", RadioSettingValueList(VOL, VOL[int(sett.min_vol)])) basic.append(minvol) ptone = RadioSetting("settings.poweron_tone", "Power On tone", RadioSettingValueBoolean( bool(sett.poweron_tone))) basic.append(ptone) sprog = RadioSetting("settings.dealer_tuning", "Dealer Tuning", RadioSettingValueBoolean( bool(sett.dealer_tuning))) basic.append(sprog) clone = RadioSetting("settings.clone", "Allow clone", RadioSettingValueBoolean( bool(sett.clone))) basic.append(clone) # front keys mon = RadioSetting("settings.kMON", "MON", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(sett.kMON))])) fkeys.append(mon) a = RadioSetting("settings.kA", "A", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(sett.kA))])) fkeys.append(a) scn = RadioSetting("settings.kSCN", "SCN", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(sett.kSCN))])) fkeys.append(scn) da = RadioSetting("settings.kDA", "D/A", RadioSettingValueList(KEYS.values(), KEYS.values()[KEYS.keys().index( int(sett.kDA))])) fkeys.append(da) return top def set_settings(self, settings): """Translate the settings in the UI into bit in the mem_struct I don't understand well the method used in many drivers so, I used mine, ugly but works ok""" mobj = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue # Let's roll the ball if "." in element.get_name(): inter, setting = element.get_name().split(".") # you must ignore the settings with "not" # this are READ ONLY attributes if inter == "not": continue obj = getattr(mobj, inter) value = element.value # case keys, with special config if setting[0] == "k": value = KEYS.keys()[KEYS.values().index(str(value))] # integers case + special case if setting in ["tot", "min_vol"]: # catching the "off" values as zero try: value = int(value) except: value = 0 # Bool types + inverted if setting in ["poweron_tone", "dealer_tuning", "clone"]: value = bool(value) # Apply al configs done # DEBUG #print("%s: %s" % (setting, value)) setattr(obj, setting, value) # This are the oldest family 60 models (Black keys), just mobiles support here @directory.register class TK760_Radio(Kenwood_M60_Radio): """Kenwood TK-760 Radios""" MODEL = "TK-760" TYPE = "M0760" VARIANTS = { "M0760\x01\x00\x00": (32, 136, 156, "K2"), "M0760\x00\x00\x00": (32, 148, 174, "K") } @directory.register class TK762_Radio(Kenwood_M60_Radio): """Kenwood TK-762 Radios""" MODEL = "TK-762" TYPE = "M0762" VARIANTS = { "M0762\x01\x00\x00": (2, 136, 156, "K2"), "M0762\x00\x00\x00": (2, 148, 174, "K") } @directory.register class TK768_Radio(Kenwood_M60_Radio): """Kenwood TK-768 Radios""" MODEL = "TK-768" TYPE = "M0768" VARIANTS = { "M0768\x21\x00\x00": (32, 136, 156, "K2"), "M0768\x20\x00\x00": (32, 148, 174, "K") } @directory.register class TK860_Radio(Kenwood_M60_Radio): """Kenwood TK-860 Radios""" MODEL = "TK-860" TYPE = "M0860" VARIANTS = { "M0860\x05\x00\x00": (32, 406, 430, "F4"), "M0860\x04\x00\x00": (32, 488, 512, "F3"), "M0860\x03\x00\x00": (32, 470, 496, "F2"), "M0860\x02\x00\x00": (32, 450, 476, "F1") } @directory.register class TK862_Radio(Kenwood_M60_Radio): """Kenwood TK-862 Radios""" MODEL = "TK-862" TYPE = "M0862" VARIANTS = { "M0862\x05\x00\x00": (2, 406, 430, "F4"), "M0862\x04\x00\x00": (2, 488, 512, "F3"), "M0862\x03\x00\x00": (2, 470, 496, "F2"), "M0862\x02\x00\x00": (2, 450, 476, "F1") } @directory.register class TK868_Radio(Kenwood_M60_Radio): """Kenwood TK-868 Radios""" MODEL = "TK-868" TYPE = "M0868" VARIANTS = { "M0868\x25\x00\x00": (32, 406, 430, "F4"), "M0868\x24\x00\x00": (32, 488, 512, "F3"), "M0868\x23\x00\x00": (32, 470, 496, "F2"), "M0868\x22\x00\x00": (32, 450, 476, "F1") } chirp-daily-20170714/chirp/drivers/generic_xml.py0000644000016101777760000001055513046277310023025 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 libxml2 import logging from chirp import chirp_common, errors, xml_ll, platform, directory LOG = logging.getLogger(__name__) def validate_doc(doc): """Validate the document""" path = platform.get_platform().find_resource("chirp.xsd") try: ctx = libxml2.schemaNewParserCtxt(path) schema = ctx.schemaParse() except libxml2.parserError, e: LOG.error("Unable to load schema: %s" % e) LOG.error("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): warnings.append("WARNING: %s" % msg) validctx = schema.schemaNewValidCtxt() validctx.setValidityErrorHandler(_err, _wrn) err = validctx.schemaValidateDoc(doc) for w in warnings: LOG.warn(w) if err: for l in ["--- DOC ---", doc.serialize(format=1).split("\n"), "-----------", errs]: LOG.error(l) 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-daily-20170714/chirp/drivers/kenwood_hmk.py0000644000016101777760000001033012476257220023031 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 import logging from chirp import chirp_common, errors, directory from chirp.drivers import generic_csv LOG = logging.getLogger(__name__) 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): LOG.debug("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: LOG.error("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: for e in errors: LOG.error(e) raise errors.InvalidDataError("No channels found") @classmethod def match_model(cls, filedata, filename): """Match files ending in .hmk""" return filename.lower().endswith("." + cls.FILE_EXTENSION) chirp-daily-20170714/chirp/drivers/retevis_rt1.py0000644000016101777760000005704713026162422023002 0ustar jenkinsnogroup00000000000000# Copyright 2016 Jim Unroe # # 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 time import os import struct import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ InvalidValueError, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rxtone[2]; lbcd txtone[2]; u8 bcl:1, // Busy Lock epilogue:1, // Epilogue (STE) scramble:1, // Scramble compander:1, // Compander skip:1, // Scan Add wide:1, // Bandwidth unknown1:1, highpower:1; // Power Level u8 unknown2[3]; } memory[16]; #seekto 0x0120; struct { u8 hivoltnotx:1, // TX Inhibit when voltage too high lovoltnotx:1, // TX Inhibit when voltage too low unknown1:1, alarm:1, // Incept Alarm scan:1, // Scan tone:1, // Tone voice:2; // Voice u8 unknown2:1, ssave:3, // Super Battery Save unknown3:1, save:3; // Battery Save u8 squelch; // Squelch u8 tot; // Time Out Timer u8 voxi:1, // VOX Inhibit on Receive voxd:2, // VOX Delay voxc:1, // VOX Control voxg:4; // VOX Gain u8 unknown4:4, scanspeed:4; // Scan Speed u8 unknown5:3, scandelay:5; // Scan Delay u8 unknown6:3, prioritych:5; // Priority Channel u8 k1shortp; // Key 1 Short Press u8 k2shortp; // Key 2 Short Press u8 k1longp; // Key 1 Long Press u8 k2longp; // Key 2 Long Press u8 lpt; // Long Press Time } settings; #seekto 0x0170; struct { char fp[8]; } fingerprint; """ CMD_ACK = "\x06" RT1_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00), chirp_common.PowerLevel("High", watts=9.00)] RT1_DTCS = sorted(chirp_common.DTCS_CODES + [645]) LIST_LPT = ["0.5", "1.0", "1.5", "2.0", "2.5"] LIST_SHORT_PRESS = ["Off", "Monitor On/Off", "Power High/Low", "Alarm", "Volt"] LIST_LONG_PRESS = ["Off", "Monitor On/Off", "Monitor(momentary)", "Power High/Low", "Alarm", "Volt", "TX 1750 Hz"] LIST_VOXDELAY = ["0.5", "1.0", "2.0", "3.0"] LIST_VOICE = ["Off", "English", "Chinese"] LIST_TIMEOUTTIMER = ["Off"] + ["%s" % x for x in range(30, 330, 30)] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SSAVE = ["Off"] + ["%s" % x for x in range(1, 7)] LIST_PRIORITYCH = ["Off"] + ["%s" % x for x in range(1, 17)] LIST_SCANSPEED = ["%s" % x for x in range(100, 550, 50)] LIST_SCANDELAY = ["%s" % x for x in range(3, 31)] SETTING_LISTS = { "lpt": LIST_LPT, "k1shortp": LIST_SHORT_PRESS, "k1longp": LIST_LONG_PRESS, "k2shortp": LIST_SHORT_PRESS, "k2longp": LIST_LONG_PRESS, "voxd": LIST_VOXDELAY, "voice": LIST_VOICE, "tot": LIST_TIMEOUTTIMER, "save": LIST_SAVE, "ssave": LIST_SSAVE, "prioritych": LIST_PRIORITYCH, "scanspeed": LIST_SCANSPEED, "scandelay": LIST_SCANDELAY, } # Retevis RT1 fingerprints RT1_VHF_fp = "PXT8K" + "\xF0\x00\x00" # RT1 VHF model RT1_UHF_fp = "PXT8K" + "\xF3\x00\x00" # RT1 UHF model MODELS = [RT1_VHF_fp, RT1_UHF_fp] def _model_from_data(data): return data[0x0170:0x0178] def _model_from_image(radio): return _model_from_data(radio.get_mmap()) def _get_radio_model(radio): block = _rt1_read_block(radio, 0x0170, 0x10) version = block[0:8] return version def _rt1_enter_programming_mode(radio): serial = radio.pipe magic = ["PROGRAMa", "PROGRAMb"] for i in range(0, 2): try: LOG.debug("sending " + magic[i]) serial.write(magic[i]) ack = serial.read(1) except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if not ack: _rt1_exit_programming_mode(radio) raise errors.RadioError("No response from radio") elif ack != CMD_ACK: LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack)) _rt1_exit_programming_mode(radio) raise errors.RadioError("Radio refused to enter programming mode") try: LOG.debug("sending " + util.hexprint("\x02")) serial.write("\x02") ident = serial.read(16) except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if not ident.startswith("PXT8K"): LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ident)) _rt1_exit_programming_mode(radio) LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: LOG.debug("sending " + util.hexprint("MXT8KCUMHS1X7BN/")) serial.write("MXT8KCUMHS1X7BN/") ack = serial.read(1) except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if ack != "\xB2": LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack)) _rt1_exit_programming_mode(radio) raise errors.RadioError("Radio refused to enter programming mode") try: LOG.debug("sending " + util.hexprint(CMD_ACK)) serial.write(CMD_ACK) ack = serial.read(1) except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack)) _rt1_exit_programming_mode(radio) raise errors.RadioError("Radio refused to enter programming mode") # DEBUG LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL)) def _rt1_exit_programming_mode(radio): serial = radio.pipe try: serial.write("E") except: raise errors.RadioError("Radio refused to exit programming mode") def _rt1_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, block_size) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + block_size) if response[:4] != expectedresponse: _rt1_exit_programming_mode(radio) raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: _rt1_exit_programming_mode(radio) raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _rt1_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, block_size) data = radio.get_mmap()[block_addr:block_addr + block_size] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: _rt1_exit_programming_mode(radio) raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _rt1_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, radio._block_size): status.cur = addr + radio._block_size radio.status_fn(status) block = _rt1_read_block(radio, addr, radio._block_size) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) _rt1_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _rt1_enter_programming_mode(radio) image_model = _model_from_image(radio) LOG.info("Image Version is %s" % repr(image_model)) radio_model = _get_radio_model(radio) LOG.info("Radio Version is %s" % repr(radio_model)) bands = ["VHF", "UHF"] image_band = radio_band = "unknown" for i in range(0,2): if image_model == MODELS[i]: image_band = bands[i] if radio_model == MODELS[i]: radio_band = bands[i] if image_model != radio_model: _rt1_exit_programming_mode(radio) msg = ("The upload was stopped because the band supported by " "the image (%s) does not match the band supported by " "the radio (%s).") raise errors.RadioError(msg % (image_band, radio_band)) status.cur = 0 status.max = 0x0190 for start_addr, end_addr, block_size in radio._ranges: for addr in range(start_addr, end_addr, block_size): status.cur = addr + block_size radio.status_fn(status) _rt1_write_block(radio, addr, block_size) _rt1_exit_programming_mode(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0x0170:0x0176] return rid.startswith("PXT8K") @directory.register class RT1Radio(chirp_common.CloneModeRadio): """Retevis RT1""" VENDOR = "Retevis" MODEL = "RT1" BAUD_RATE = 2400 _ranges = [ (0x0000, 0x0190, 0x10), ] _memsize = 0x0400 _block_size = 0x10 _vhf_range = (134000000, 175000000) _uhf_range = (400000000, 521000000) def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True rf.has_cross = True rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = False 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 = RT1_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.memory_bounds = (1, 16) if self._mmap is None: rf.valid_bands = [self._vhf_range, self._uhf_range] elif self._my_band() == RT1_VHF_fp: rf.valid_bands = [self._vhf_range] elif self._my_band() == RT1_UHF_fp: rf.valid_bands = [self._uhf_range] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): self._mmap = do_download(self) self.process_mmap() def sync_out(self): do_upload(self) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def _my_band(self): model_tag = _model_from_image(self) return model_tag def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = _mem.wide and "FM" or "NFM" rxtone = txtone = None txtone = self.decode_tone(_mem.txtone) rxtone = self.decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) mem.power = RT1_POWER_LEVELS[_mem.highpower] if _mem.skip: mem.skip = "S" mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "BCL", RadioSettingValueBoolean(not _mem.bcl)) mem.extra.append(rs) rs = RadioSetting("epilogue", "Epilogue(STE)", RadioSettingValueBoolean(not _mem.epilogue)) mem.extra.append(rs) rs = RadioSetting("compander", "Compander", RadioSettingValueBoolean(not _mem.compander)) mem.extra.append(rs) rs = RadioSetting("scramble", "Scramble", RadioSettingValueBoolean(not _mem.scramble)) 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" * (_mem.size() / 8)) return _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 _mem.wide = mem.mode == "FM" ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self.encode_tone(_mem.txtone, txmode, txtone, txpol) self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol) _mem.highpower = mem.power == RT1_POWER_LEVELS[1] _mem.skip = mem.skip == "S" for setting in mem.extra: setattr(_mem, setting.get_name(), not int(setting.value)) def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("lpt", "Long Press Time[s]", RadioSettingValueList( LIST_LPT, LIST_LPT[_settings.lpt])) basic.append(rs) if _settings.k1shortp > 4: val = 1 else: val = _settings.k1shortp rs = RadioSetting("k1shortp", "Key 1 Short Press", RadioSettingValueList( LIST_SHORT_PRESS, LIST_SHORT_PRESS[val])) basic.append(rs) if _settings.k1longp > 6: val = 3 else: val = _settings.k1longp rs = RadioSetting("k1longp", "Key 1 Long Press", RadioSettingValueList( LIST_LONG_PRESS, LIST_LONG_PRESS[val])) basic.append(rs) if _settings.k2shortp > 4: val = 4 else: val = _settings.k2shortp rs = RadioSetting("k2shortp", "Key 2 Short Press", RadioSettingValueList( LIST_SHORT_PRESS, LIST_SHORT_PRESS[val])) basic.append(rs) if _settings.k2longp > 6: val = 4 else: val = _settings.k2longp rs = RadioSetting("k2longp", "Key 2 Long Press", RadioSettingValueList( LIST_LONG_PRESS, LIST_LONG_PRESS[val])) basic.append(rs) rs = RadioSetting("voxc", "VOX Control", RadioSettingValueBoolean(not _settings.voxc)) basic.append(rs) if _settings.voxg > 8: val = 4 else: val = _settings.voxg + 1 rs = RadioSetting("voxg", "VOX Gain", RadioSettingValueInteger(1, 9, val)) basic.append(rs) rs = RadioSetting("voxd", "VOX Delay Time", RadioSettingValueList( LIST_VOXDELAY, LIST_VOXDELAY[_settings.voxd])) basic.append(rs) rs = RadioSetting("voxi", "VOX Inhibit on Receive", RadioSettingValueBoolean(not _settings.voxi)) basic.append(rs) if _settings.squelch > 8: val = 4 else: val = _settings.squelch rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, val)) basic.append(rs) if _settings.voice == 3: val = 1 else: val = _settings.voice rs = RadioSetting("voice", "Voice Prompts", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) rs = RadioSetting("tone", "Tone", RadioSettingValueBoolean(_settings.tone)) basic.append(rs) rs = RadioSetting("lovoltnotx", "TX Inhibit (when battery < 6 volts)", RadioSettingValueBoolean(_settings.lovoltnotx)) basic.append(rs) rs = RadioSetting("hivoltnotx", "TX Inhibit (when battery > 9 volts)", RadioSettingValueBoolean(_settings.hivoltnotx)) basic.append(rs) if _settings.tot > 10: val = 6 else: val = _settings.tot rs = RadioSetting("tot", "Time-out Timer[s]", RadioSettingValueList( LIST_TIMEOUTTIMER, LIST_TIMEOUTTIMER[val])) basic.append(rs) if _settings.save < 3: val = 0 else: val = _settings.save - 3 rs = RadioSetting("save", "Battery Saver", RadioSettingValueList( LIST_SAVE, LIST_SAVE[val])) basic.append(rs) rs = RadioSetting("ssave", "Super Battery Saver[s]", RadioSettingValueList( LIST_SSAVE, LIST_SSAVE[_settings.ssave])) basic.append(rs) rs = RadioSetting("alarm", "Incept Alarm", RadioSettingValueBoolean(_settings.alarm)) basic.append(rs) rs = RadioSetting("scan", "Scan Function", RadioSettingValueBoolean(_settings.scan)) basic.append(rs) if _settings.prioritych > 15: val = 0 else: val = _settings.prioritych + 1 rs = RadioSetting("prioritych", "Priority Channel", RadioSettingValueList( LIST_PRIORITYCH, LIST_PRIORITYCH[val])) basic.append(rs) if _settings.scanspeed > 8: val = 4 else: val = _settings.scanspeed rs = RadioSetting("scanspeed", "Scan Speed[ms]", RadioSettingValueList( LIST_SCANSPEED, LIST_SCANSPEED[val])) basic.append(rs) if _settings.scandelay > 27: val = 12 else: val = _settings.scandelay rs = RadioSetting("scandelay", "Scan Droupout Delay Time[s]", RadioSettingValueList( LIST_SCANDELAY, LIST_SCANDELAY[val])) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() if setting == "voxc": setattr(obj, setting, not int(element.value)) elif setting == "voxg": setattr(obj, setting, int(element.value) - 1) elif setting == "voxi": setattr(obj, setting, not int(element.value)) elif setting == "voice": if int(element.value) == 2: setattr(obj, setting, int(element.value) + 1) else: setattr(obj, setting, int(element.value)) elif setting == "save": setattr(obj, setting, int(element.value) + 3) elif setting == "prioritych": if int(element.value) == 0: setattr(obj, setting, int(element.value) + 31) else: setattr(obj, setting, int(element.value) - 1) elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) in [0x0400, ]: match_size = True # testing the model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/ft8100.py0000644000016101777760000002176512537474005021464 0ustar jenkinsnogroup00000000000000# Copyright 2010 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 . import time import os from chirp import chirp_common, directory, bitwise, errors from chirp.drivers import yaesu_clone TONES = chirp_common.OLD_TONES TMODES = ["", "Tone"] MODES = ['FM', 'AM'] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] DUPLEX = ["", "-", "+", "split"] # "M" for masked memories, which are invisible until un-masked SKIPS = ["", "S", "M"] POWER_LEVELS_VHF = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=20), chirp_common.PowerLevel("High", watts=50)] POWER_LEVELS_UHF = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Mid", watts=20), chirp_common.PowerLevel("High", watts=35)] SPECIALS = {'1L': -1, '1U': -2, '2L': -3, '2U': -4, 'Home': -5} MEM_FORMAT = """ #seekto 0x{skips:X}; u8 skips[13]; #seekto 0x{enables:X}; u8 enables[13]; struct mem_struct {{ u8 unknown4:2, baud9600:1, am:1, unknown4b:4; u8 power:2, duplex:2, unknown1b:4; u8 unknown2:1, tone_enable:1, tone:6; bbcd freq[3]; bbcd offset[3]; }}; #seekto 0x{memories:X}; struct mem_struct memory[99]; """ @directory.register class FT8100Radio(yaesu_clone.YaesuCloneModeRadio): """Implementation for Yaesu FT-8100""" MODEL = "FT-8100" _memstart = 0 _memsize = 2968 _block_lengths = [10, 32, 114, 101, 101, 97, 128, 128, 128, 128, 128, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1] @classmethod def match_model(cls, data, path): if (len(data) == cls._memsize and data[1:10] == '\x01\x01\x07\x08\x02\x01\x01\x00\x01'): return True return False def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 99) rf.has_ctone = False rf.has_dtcs = False rf.has_dtcs_polarity = False rf.has_bank = False rf.has_name = False rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) rf.valid_duplexes = list(DUPLEX) rf.valid_power_levels = POWER_LEVELS_VHF rf.has_sub_devices = self.VARIANT == '' rf.valid_tuning_steps = list(STEPS) rf.valid_bands = [(110000000, 550000000), (750000000, 1300000000)] rf.valid_skips = SKIPS rf.can_odd_split = True # TODO #rf.valid_special_chans = SPECIALS.keys() # TODO #rf.has_tuning_step = False return rf def sync_in(self): super(FT8100Radio, self).sync_in() self.pipe.write(chr(yaesu_clone.CMD_ACK)) self.pipe.read(1) def sync_out(self): self.update_checksums() return _clone_out(self) def process_mmap(self): if not self._memstart: return mem_format = MEM_FORMAT.format(memories=self._memstart, skips=self._skipstart, enables=self._enablestart ) self._memobj = bitwise.parse(mem_format, self._mmap) def get_sub_devices(self): return [FT8100RadioVHF(self._mmap), FT8100RadioUHF(self._mmap)] def get_memory(self, number): bit, byte = self._bit_byte(number) _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.freq) * 1000 if _mem.tone >= len(TONES) or _mem.duplex >= len(DUPLEX): mem.empty = True return mem else: mem.rtone = TONES[_mem.tone] mem.tmode = TMODES[_mem.tone_enable] mem.mode = MODES[_mem.am] mem.duplex = DUPLEX[_mem.duplex] if _mem.duplex == DUPLEX.index("split"): tx_freq = int(_mem.offset) * 1000 print self.VARIANT, number, tx_freq, mem.freq mem.offset = tx_freq - mem.freq else: mem.offset = int(_mem.offset) * 1000 if int(mem.freq / 100) == 4: mem.power = POWER_LEVELS_UHF[_mem.power] else: mem.power = POWER_LEVELS_VHF[_mem.power] # M01 can't be disabled if not self._memobj.enables[byte] & bit and number != 1: mem.empty = True print 'R', self.VARIANT, number, _mem.baud9600 return mem def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def set_memory(self, mem): bit, byte = self._bit_byte(mem.number) _mem = self._memobj.memory[mem.number - 1] _mem.freq = int(mem.freq / 1000) _mem.tone = TONES.index(mem.rtone) _mem.tone_enable = TMODES.index(mem.tmode) _mem.am = MODES.index(mem.mode) _mem.duplex = DUPLEX.index(mem.duplex) if mem.duplex == "split": tx_freq = mem.freq + mem.offset _mem.split_high = tx_freq / 10000000 _mem.offset = (tx_freq % 10000000) / 1000 else: _mem.offset = int(mem.offset / 1000) if mem.power: _mem.power = POWER_LEVELS_VHF.index(mem.power) else: _mem.power = 0 if mem.empty: self._memobj.enables[byte] &= ~bit else: self._memobj.enables[byte] |= bit # TODO expose these options _mem.baud9600 = 0 _mem.am = 0 # These need to be cleared, otherwise strange things happen _mem.unknown4 = 0 _mem.unknown4b = 0 _mem.unknown1b = 0 _mem.unknown2 = 0 def _checksums(self): return [yaesu_clone.YaesuChecksum(0x0000, 0x0B96)] # I didn't believe this myself, but it seems that there's a bit for # enabling VHF M01, but no bit for UHF01, and the enables are shifted down, # so that the first bit is for M02 def _bit_byte(self, number): if self.VARIANT == 'VHF': bit = 1 << ((number - 1) % 8) byte = (number - 1) / 8 else: bit = 1 << ((number - 2) % 8) byte = (number - 2) / 8 return bit, byte class FT8100RadioVHF(FT8100Radio): """Yaesu FT-8100 VHF subdevice""" VARIANT = "VHF" _memstart = 0x447 _skipstart = 0x02D _enablestart = 0x04D class FT8100RadioUHF(FT8100Radio): """Yaesu FT-8100 UHF subdevice""" VARIANT = "UHF" _memstart = 0x7E6 _skipstart = 0x03A _enablestart = 0x05A def _clone_out(radio): try: return __clone_out(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) 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 = sum(block_lengths) status.cur = total_written radio.status_fn(status) start = time.time() pos = 0 for block in radio._block_lengths: if os.getenv("CHIRP_DEBUG"): print "\nSending %i-%i" % (pos, pos + block) out = radio.get_mmap()[pos:pos + block] # need to chew byte-by-byte here or else we lose the ACK...not sure why for b in out: pipe.write(b) pipe.read(1) # chew the echo ack = pipe.read(1) if ack != chr(yaesu_clone.CMD_ACK): raise Exception("block not ack'ed: %s" % repr(ack)) total_written += len(out) _status() pos += block print "Clone completed in %i seconds" % (time.time() - start) return True chirp-daily-20170714/chirp/drivers/retevis_rt21.py0000644000016101777760000004232613060205711023053 0ustar jenkinsnogroup00000000000000# Copyright 2016 Jim Unroe # # 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 time import os import struct import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rx_tone; ul16 tx_tone; u8 unknown1:3, bcl:2, // Busy Lock unknown2:3; u8 unknown3:2, highpower:1, // Power Level wide:1, // Bandwidth unknown4:4; u8 scramble_type:4, unknown5:4; u8 unknown6:4, scramble_type2:4; } memory[16]; #seekto 0x011D; struct { u8 unused:4, pf1:4; // Programmable Function Key 1 } keys; #seekto 0x012C; struct { u8 use_scramble; // Scramble Enable u8 unknown1[2]; u8 voice; // Voice Annunciation u8 tot; // Time-out Timer u8 totalert; // Time-out Timer Pre-alert u8 unknown2[2]; u8 squelch; // Squelch Level u8 save; // Battery Saver u8 unknown3[3]; u8 use_vox; // VOX Enable u8 vox; // VOX Gain } settings; #seekto 0x017E; u8 skipflags[2]; // SCAN_ADD """ CMD_ACK = "\x06" BLOCK_SIZE = 0x10 RT21_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("High", watts=2.50)] RT21_DTCS = sorted(chirp_common.DTCS_CODES + [17, 50, 55, 135, 217, 254, 305, 645, 765]) BCL_LIST = ["Off", "Carrier", "QT/DQT"] SCRAMBLE_LIST = ["Scramble 1", "Scramble 2", "Scramble 3", "Scramble 4", "Scramble 5", "Scramble 6", "Scramble 7", "Scramble 8"] TIMEOUTTIMER_LIST = ["%s seconds" % x for x in range(15, 615, 15)] TOTALERT_LIST = ["Off"] + ["%s seconds" % x for x in range(1, 11)] VOICE_LIST = ["Off", "Chinese", "English"] VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)] PF1_CHOICES = ["None", "Monitor", "Scan", "Scramble", "Alarm"] PF1_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x0C] SETTING_LISTS = { "bcl": BCL_LIST, "scramble": SCRAMBLE_LIST, "tot": TIMEOUTTIMER_LIST, "totalert": TOTALERT_LIST, "voice": VOICE_LIST, "vox": VOX_LIST, } def _rt21_enter_programming_mode(radio): serial = radio.pipe try: serial.write("PRMZUNE") ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if not ack: raise errors.RadioError("No response from radio") elif ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x02") ident = serial.read(8) except: raise errors.RadioError("Error communicating with radio") if not ident.startswith("P3207"): LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _rt21_exit_programming_mode(radio): serial = radio.pipe try: serial.write("E") except: raise errors.RadioError("Radio refused to exit programming mode") def _rt21_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + BLOCK_SIZE) if response[:4] != expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _rt21_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE) data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _rt21_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) block = _rt21_read_block(radio, addr, BLOCK_SIZE) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) _rt21_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _rt21_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr in radio._ranges: for addr in range(start_addr, end_addr, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) _rt21_write_block(radio, addr, BLOCK_SIZE) _rt21_exit_programming_mode(radio) @directory.register class RT21Radio(chirp_common.CloneModeRadio): """RETEVIS RT21""" VENDOR = "Retevis" MODEL = "RT21" BAUD_RATE = 9600 _ranges = [ (0x0000, 0x0400), ] _memsize = 0x0400 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True rf.has_cross = True rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = False 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 = RT21_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.memory_bounds = (1, 16) rf.valid_bands = [(400000000, 480000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): self._mmap = do_download(self) self.process_mmap() def sync_out(self): do_upload(self) 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 > 0x2000: 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 > 0x2000: 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) LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone, rxmode, _mem.rx_tone)) def get_memory(self, number): bitpos = (1 << ((number - 1) % 8)) bytepos = ((number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[number - 1] _skp = self._memobj.skipflags[bytepos] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if _mem.get_raw() == ("\xFF" * 16): LOG.debug("Initializing empty memory") _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8") if int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = _mem.wide and "FM" or "NFM" self._get_tone(_mem, mem) mem.power = RT21_POWER_LEVELS[_mem.highpower] mem.skip = "" if (_skp & bitpos) else "S" LOG.debug("mem.skip %s" % mem.skip) mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueList( BCL_LIST, BCL_LIST[_mem.bcl])) mem.extra.append(rs) rs = RadioSetting("scramble_type", "Scramble Type", RadioSettingValueList(SCRAMBLE_LIST, SCRAMBLE_LIST[_mem.scramble_type - 8])) mem.extra.append(rs) return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone)) def set_memory(self, mem): bitpos = (1 << ((mem.number - 1) % 8)) bytepos = ((mem.number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[mem.number - 1] _skp = self._memobj.skipflags[bytepos] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return _mem.set_raw("\x00" * 13 + "\x00\x8F\xF8") _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 _mem.wide = mem.mode == "FM" self._set_tone(mem, _mem) _mem.highpower = mem.power == RT21_POWER_LEVELS[1] if mem.skip != "S": _skp |= bitpos else: _skp &= ~bitpos LOG.debug("_skp %s" % _skp) for setting in mem.extra: if setting.get_name() == "scramble_type": setattr(_mem, setting.get_name(), int(setting.value) + 8) setattr(_mem, "scramble_type2", int(setting.value) + 8) else: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _keys = self._memobj.keys _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("tot", "Time-out timer", RadioSettingValueList( TIMEOUTTIMER_LIST, TIMEOUTTIMER_LIST[_settings.tot - 1])) basic.append(rs) rs = RadioSetting("totalert", "TOT Pre-alert", RadioSettingValueList( TOTALERT_LIST, TOTALERT_LIST[_settings.totalert])) basic.append(rs) rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("voice", "Voice Annumciation", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voice])) basic.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueBoolean(_settings.save)) basic.append(rs) rs = RadioSetting("use_scramble", "Scramble", RadioSettingValueBoolean(_settings.use_scramble)) basic.append(rs) rs = RadioSetting("use_vox", "VOX", RadioSettingValueBoolean(_settings.use_vox)) basic.append(rs) rs = RadioSetting("vox", "VOX Gain", RadioSettingValueList( VOX_LIST, VOX_LIST[_settings.vox])) basic.append(rs) def apply_pf1_listvalue(setting, obj): LOG.debug("Setting value: "+ str(setting.value) + " from list") val = str(setting.value) index = PF1_CHOICES.index(val) val = PF1_VALUES[index] obj.set_value(val) if _keys.pf1 in PF1_VALUES: idx = PF1_VALUES.index(_keys.pf1) else: idx = LIST_DTMF_SPECIAL_VALUES.index(0x04) rs = RadioSetting("keys.pf1", "PF1 Key Function", RadioSettingValueList(PF1_CHOICES, PF1_CHOICES[idx])) rs.set_apply_callback(apply_pf1_listvalue, _keys.pf1) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "tot": setattr(obj, setting, int(element.value) + 1) elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/ft50.py0000644000016101777760000005101212656316216021305 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 import logging import re from chirp.drivers import yaesu_clone from chirp import chirp_common, directory, errors, bitwise, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) MEM_FORMAT = """ struct flag_struct { u8 unknown1f:5, skip:1, mask:1, used:1; }; struct mem_struct { u8 showname:1, unknown1:3, unknown2:2, unknown3:2; u8 ishighpower:1, power:2, unknown4:1, tuning_step:4; u8 codememno:4, codeorpage:2, duplex:2; u8 tmode:2, tone:6; u8 unknown5:1, dtcs:7; u8 unknown6:6, mode:2; bbcd freq[3]; bbcd offset[3]; u8 name[4]; }; #seekto 0x000C; struct { u8 extendedrx_flg; // Seems to be set to 03 when extended rx is enabled u8 extendedrx; // Seems to be set to 01 when extended rx is enabled } extendedrx_struct; // UNFINISHED!! #seekto 0x001A; struct flag_struct flag[100]; #seekto 0x079C; struct flag_struct flag_repeat[100]; #seekto 0x00AA; struct mem_struct memory[100]; struct mem_struct special[11]; #seekto 0x08C7; struct { u8 sub_display; u8 unknown1s; u8 apo; u8 timeout; u8 lock; u8 rxsave; u8 lamp; u8 bell; u8 cwid[16]; u8 unknown2s; u8 artsmode; u8 artsbeep; u8 unknown3s; u8 unknown4s; struct { u8 header[3]; u8 mem_num; u8 digits[16]; } autodial[8]; struct { u8 header[3]; u8 mem_num; u8 digits[32]; } autodial9_ro; bbcd pagingcodec_ro[2]; bbcd pagingcodep[2]; struct { bbcd digits[2]; } pagingcode[6]; u8 code_dec_c_en:1, code_dec_p_en:1, code_dec_1_en:1, code_dec_2_en:1, code_dec_3_en:1, code_dec_4_en:1, code_dec_5_en:1, code_dec_6_en:1; u8 pagingspeed; u8 pagingdelay; u8 pagingbell; u8 paginganswer; #seekto 0x0E30; u8 squelch; // squelch u8 unknown0c; u8 rptl:1, // repeater input tracking amod:1, // auto mode scnl:1, // scan lamp resm:1, // scan resume mode 0=5sec, 1=carr ars:1, // automatic repeater shift keybeep:1, // keypad beep lck:1, // lock unknown1c:1; u8 lgt:1, pageamsg:1, unknown2c:1, bclo:1, // Busy channel lock out unknown3c:2, cwid_en:1, // CWID off/on tsav:1; // TX save u8 unknown4c:4, artssped:1, // ARTS/SPED: 0=15s, 1=25s unknown5c:1, rvhm:1, // RVHM: 0=home, 1=rev mon:1; // MON: 0=mon, 1=tcal } settings; #seekto 0x080E; struct mem_struct vfo_mem[10]; """ # 10 VFO memories: A145, A220, A380, A430, A800, # B145, B220, B380, B430, B800 DUPLEX = ["", "-", "+"] MODES = ["FM", "AM", "WFM"] SKIP_VALUES = ["", "S"] TMODES = ["", "Tone", "TSQL", "DTCS"] TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] TONES = list(chirp_common.OLD_TONES) # CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ()+-=*/???|0123456789" # the = displays as an underscored dash on radio # the first ? is an uppercase delta - \xA7 # the second ? is an uppercase gamma - \xD1 # the thrid ? is an uppercase sigma - \xCF NUMERIC_CHARSET = list("0123456789") CHARSET = [str(x) for x in range(0, 10)] + \ [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ list(" ()+-=*/" + ("\x00" * 3) + "|") + NUMERIC_CHARSET DTMFCHARSET = NUMERIC_CHARSET + list("ABCD*#") POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.0), chirp_common.PowerLevel("L3", watts=2.5), chirp_common.PowerLevel("L2", watts=1.0), chirp_common.PowerLevel("L1", watts=0.1)] SPECIALS = ["L1", "U1", "L2", "U2", "L3", "U3", "L4", "U4", "L5", "U5", "UNK"] @directory.register class FT50Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-50""" BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "FT-50" _model = "" _memsize = 3723 _block_lengths = [10, 16, 112, 16, 16, 1776, 1776, 1] # _block_delay = 0.15 _block_size = 8 @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold [PTT] & Knob while turning the radio on. 4. After clicking OK, press the [PTT] switch to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold [PTT] & Knob while turning the radio on. 4. Press the [MONI] switch ("WAIT" will appear on the LCD). 5. Press OK.""")) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 100) rf.valid_duplexes = DUPLEX rf.valid_tmodes = TMODES rf.valid_power_levels = POWER_LEVELS rf.valid_tuning_steps = TUNING_STEPS rf.valid_power_levels = POWER_LEVELS rf.valid_characters = "".join(CHARSET) rf.valid_name_length = 4 rf.valid_modes = MODES # Specials not yet implementd # rf.valid_special_chans = SPECIALS rf.valid_bands = [(76000000, 200000000), (300000000, 540000000), (590000000, 999000000)] # rf.can_odd_split = True rf.has_ctone = False rf.has_bank = False rf.has_settings = True rf.has_dtcs_polarity = False return rf def _checksums(self): return [yaesu_clone.YaesuChecksum(0x0000, 0xE89)] 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 = chirp_common.Memory() _mem = self._memobj.memory[number-1] _flg = self._memobj.flag[number-1] mem.number = number # if not _flg.visible: # mem.empty = True if not _flg.used: mem.empty = True return mem for i in _mem.name: mem.name += CHARSET[i & 0x7F] mem.name = mem.name.rstrip() mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.duplex = DUPLEX[_mem.duplex] mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) mem.rtone = mem.ctone = TONES[_mem.tone] mem.tmode = TMODES[_mem.tmode] mem.mode = MODES[_mem.mode] mem.tuning_step = TUNING_STEPS[_mem.tuning_step] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] # Power is stored as 2 bits to describe the 3 low power levels # High power is determined by a different bit. if not _mem.ishighpower: mem.power = POWER_LEVELS[3 - _mem.power] else: mem.power = POWER_LEVELS[0] mem.skip = SKIP_VALUES[_flg.skip] return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] _flg = self._memobj.flag[mem.number-1] _flg_repeat = self._memobj.flag_repeat[mem.number-1] if mem.empty: _flg.used = False self._wipe_memory_banks(mem) return if (len(mem.name) == 0): _mem.name = [0x24] * 4 _mem.showname = 0 else: _mem.showname = 1 for i in range(0, 4): _mem.name[i] = CHARSET.index(mem.name.ljust(4)[i]) _mem.freq = int(mem.freq / 1000) _mem.duplex = DUPLEX.index(mem.duplex) _mem.offset = int(mem.offset / 1000) _mem.mode = MODES.index(mem.mode) _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) if mem.power: if (mem.power == POWER_LEVELS[0]): # low power level is not changed when high power is selected _mem.ishighpower = 0x01 if (_mem.power == 3): # Set low power to L3 (0x02) if it is # set to 3 (new object default) LOG.debug("SETTING DEFAULT?") _mem.power = 0x02 else: _mem.ishighpower = 0x00 _mem.power = 3 - POWER_LEVELS.index(mem.power) else: _mem.ishighpower = 0x01 _mem.power = 0x02 _mem.tmode = TMODES.index(mem.tmode) try: _mem.tone = TONES.index(mem.rtone) except ValueError: raise errors.UnsupportedToneError( ("This radio does not support tone %s" % mem.rtone)) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _flg.skip = SKIP_VALUES.index(mem.skip) # initialize new channel to safe defaults if not mem.empty and not _flg.used: _flg.used = True _flg.mask = True # Mask = True to be visible on radio _mem.unknown1 = 0x00 _mem.unknown2 = 0x00 _mem.unknown3 = 0x00 _mem.unknown4 = 0x00 _mem.unknown5 = 0x00 _mem.unknown6 = 0x00 _mem.codememno = 0x02 # Not implemented in chirp _mem.codeorpage = 0x00 # Not implemented in chirp # Duplicate flags to repeated part in memory _flg_repeat.skip = _flg.skip _flg_repeat.mask = _flg.mask _flg_repeat.used = _flg.used def _decode_cwid(self, inarr): LOG.debug("@_decode_chars, type: %s" % type(inarr)) LOG.debug(inarr) outstr = "" for i in inarr: if i == 0xFF: break outstr += CHARSET[i & 0x7F] LOG.debug(outstr) return outstr.rstrip() def _encode_cwid(self, instr, length=16): LOG.debug("@_encode_chars, type: %s" % type(instr)) LOG.debug(instr) outarr = [] instr = str(instr) for i in range(0, length): if i < len(instr): outarr.append(CHARSET.index(instr[i])) else: outarr.append(0xFF) return outarr def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") dtmf = RadioSettingGroup("dtmf", "DTMF Code & Paging") arts = RadioSettingGroup("arts", "ARTS") autodial = RadioSettingGroup("autodial", "AutoDial") top = RadioSettings(basic, autodial, arts, dtmf) rs = RadioSetting( "squelch", "Squelch", RadioSettingValueInteger(0, 15, _settings.squelch)) basic.append(rs) rs = RadioSetting( "keybeep", "Keypad Beep", RadioSettingValueBoolean(_settings.keybeep)) basic.append(rs) rs = RadioSetting( "scnl", "Scan Lamp", RadioSettingValueBoolean(_settings.scnl)) basic.append(rs) options = ["off", "30m", "1h", "3h", "5h", "8h"] rs = RadioSetting( "apo", "APO time (hrs)", RadioSettingValueList(options, options[_settings.apo])) basic.append(rs) options = ["off", "1m", "2.5m", "5m", "10m"] rs = RadioSetting( "timeout", "Time Out Timer", RadioSettingValueList(options, options[_settings.timeout])) basic.append(rs) options = ["key", "dial", "key+dial", "ptt", "key+ptt", "dial+ptt", "all"] rs = RadioSetting( "lock", "Lock mode", RadioSettingValueList(options, options[_settings.lock])) basic.append(rs) options = ["off", "0.2", "0.3", "0.5", "1.0", "2.0"] rs = RadioSetting( "rxsave", "RX Save (sec)", RadioSettingValueList(options, options[_settings.rxsave])) basic.append(rs) options = ["5sec", "key", "tgl"] rs = RadioSetting( "lamp", "Lamp mode", RadioSettingValueList(options, options[_settings.lamp])) basic.append(rs) options = ["off", "1", "3", "5", "8", "rpt"] rs = RadioSetting( "bell", "Bell Repetitions", RadioSettingValueList(options, options[_settings.bell])) basic.append(rs) rs = RadioSetting( "cwid_en", "CWID Enable", RadioSettingValueBoolean(_settings.cwid_en)) arts.append(rs) cwid = RadioSettingValueString( 0, 16, self._decode_cwid(_settings.cwid.get_value())) cwid.set_charset(CHARSET) rs = RadioSetting("cwid", "CWID", cwid) arts.append(rs) options = ["off", "rx", "tx", "trx"] rs = RadioSetting( "artsmode", "ARTS Mode", RadioSettingValueList( options, options[_settings.artsmode])) arts.append(rs) options = ["off", "in range", "always"] rs = RadioSetting( "artsbeep", "ARTS Beep", RadioSettingValueList(options, options[_settings.artsbeep])) arts.append(rs) for i in range(0, 8): dialsettings = _settings.autodial[i] dialstr = "" for c in dialsettings.digits: if c < len(DTMFCHARSET): dialstr += DTMFCHARSET[c] dialentry = RadioSettingValueString(0, 16, dialstr) dialentry.set_charset(DTMFCHARSET + list(" ")) rs = RadioSetting("autodial" + str(i+1), "AutoDial " + str(i+1), dialentry) autodial.append(rs) dialstr = "" for c in _settings.autodial9_ro.digits: if c < len(DTMFCHARSET): dialstr += DTMFCHARSET[c] dialentry = RadioSettingValueString(0, 32, dialstr) dialentry.set_mutable(False) rs = RadioSetting("autodial9_ro", "AutoDial 9 (read only)", dialentry) autodial.append(rs) options = ["50ms", "100ms"] rs = RadioSetting( "pagingspeed", "Paging Speed", RadioSettingValueList(options, options[_settings.pagingspeed])) dtmf.append(rs) options = ["250ms", "450ms", "750ms", "1000ms"] rs = RadioSetting( "pagingdelay", "Paging Delay", RadioSettingValueList(options, options[_settings.pagingdelay])) dtmf.append(rs) options = ["off", "1", "3", "5", "8", "rpt"] rs = RadioSetting( "pagingbell", "Paging Bell Repetitions", RadioSettingValueList(options, options[_settings.pagingbell])) dtmf.append(rs) options = ["off", "ans", "for"] rs = RadioSetting( "paginganswer", "Paging Answerback", RadioSettingValueList(options, options[_settings.paginganswer])) dtmf.append(rs) rs = RadioSetting( "code_dec_c_en", "Paging Code C Decode Enable", RadioSettingValueBoolean(_settings.code_dec_c_en)) dtmf.append(rs) _str = str(bitwise.bcd_to_int(_settings.pagingcodec_ro)) code = RadioSettingValueString(0, 3, _str) code.set_charset(NUMERIC_CHARSET + list(" ")) code.set_mutable(False) rs = RadioSetting("pagingcodec_ro", "Paging Code C (read only)", code) dtmf.append(rs) rs = RadioSetting( "code_dec_p_en", "Paging Code P Decode Enable", RadioSettingValueBoolean(_settings.code_dec_p_en)) dtmf.append(rs) _str = str(bitwise.bcd_to_int(_settings.pagingcodep)) code = RadioSettingValueString(0, 3, _str) code.set_charset(NUMERIC_CHARSET + list(" ")) rs = RadioSetting("pagingcodep", "Paging Code P", code) dtmf.append(rs) for i in range(0, 6): num = str(i+1) name = "code_dec_" + num + "_en" rs = RadioSetting( name, "Paging Code " + num + " Decode Enable", RadioSettingValueBoolean(getattr(_settings, name))) dtmf.append(rs) _str = str(bitwise.bcd_to_int(_settings.pagingcode[i].digits)) code = RadioSettingValueString(0, 3, _str) code.set_charset(NUMERIC_CHARSET + list(" ")) rs = RadioSetting("pagingcode" + num, "Paging Code " + num, code) dtmf.append(rs) return top def set_settings(self, uisettings): for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() _settings = self._memobj.settings if re.match('autodial\d', setting): # set autodial fields dtmfstr = str(element.value).strip() newval = [] for i in range(0, 16): if i < len(dtmfstr): newval.append(DTMFCHARSET.index(dtmfstr[i])) else: newval.append(0xFF) LOG.debug(newval) idx = int(setting[-1:]) - 1 _settings = self._memobj.settings.autodial[idx] _settings.digits = newval continue if (setting == "pagingcodep"): bitwise.int_to_bcd(_settings.pagingcodep, int(element.value)) continue if re.match('pagingcode\d', setting): idx = int(setting[-1:]) - 1 bitwise.int_to_bcd(_settings.pagingcode[idx].digits, int(element.value)) continue newval = element.value oldval = getattr(_settings, setting) if setting == "cwid": newval = self._encode_cwid(newval) LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize def sync_out(self): self.update_checksums() return _clone_out(self) def _clone_out(radio): try: return __clone_out(radio) except Exception, e: raise errors.RadioError("Failed to communicate with the radio: %s" % e) 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 = sum(block_lengths) status.cur = total_written radio.status_fn(status) start = time.time() blocks = 0 pos = 0 for block in radio._block_lengths: blocks += 1 data = radio.get_mmap()[pos:pos + block] # LOG.debug(util.hexprint(data)) recvd = "" # Radio echos every block received for byte in data: time.sleep(0.01) pipe.write(byte) # flush & sleep so don't loose ack pipe.flush() time.sleep(0.015) recvd += pipe.read(1) # chew the echo # LOG.debug(util.hexprint(recvd)) LOG.debug("Bytes sent: %i" % len(data)) # Radio does not ack last block if (blocks < 8): buf = pipe.read(block) LOG.debug("ACK attempt: " + util.hexprint(buf)) if buf and buf[0] != chr(yaesu_clone.CMD_ACK): buf = pipe.read(block) if not buf or buf[-1] != chr(yaesu_clone.CMD_ACK): raise errors.RadioError("Radio did not ack block %i" % blocks) total_written += len(data) _status() pos += block pipe.read(pos) # Chew the echo if using a 2-pin cable LOG.debug("Clone completed in %i seconds" % (time.time() - start)) chirp-daily-20170714/chirp/drivers/thd72.py0000644000016101777760000005725013067650002021460 0ustar jenkinsnogroup00000000000000# Copyright 2010 Vernon Mauery # Copyright 2016 Angus Ainslie # # 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 from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings from chirp.settings import RadioSettingValueInteger, RadioSettingValueString from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean import time import struct import sys import logging LOG = logging.getLogger(__name__) # 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 0x02c0; struct { ul32 start_freq; ul32 end_freq; } prog_vfo[6]; #seekto 0x0300; struct { char power_on_msg[8]; u8 unknown0[8]; u8 unknown1[2]; u8 lamp_timer; u8 contrast; u8 battery_saver; u8 APO; u8 unknown2; u8 key_beep; u8 unknown3[8]; u8 unknown4; u8 balance; u8 unknown5[23]; u8 lamp_control; } settings; #seekto 0x0c00; struct { u8 disabled:4, prog_vfo:4; 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" DEFAULT_PROG_VFO = ( (136000000, 174000000), (410000000, 470000000), (118000000, 136000000), (136000000, 174000000), (320000000, 400000000), (400000000, 524000000), ) # index of PROG_VFO used for setting memory.unknown1 and memory.unknown2 # see http://chirp.danplanet.com/issues/1611#note-9 UNKNOWN_LOOKUP = (0, 7, 4, 0, 4, 7) def get_prog_vfo(frequency): for i, (start, end) in enumerate(DEFAULT_PROG_VFO): if start <= frequency < end: return i raise ValueError("Frequency is out of range.") @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 = [] _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)] _LAMP_CONTROL = ["Manual", "Auto"] _LAMP_TIMER = ["Seconds %d" % x for x in range(2, 11)] _BATTERY_SAVER = ["OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds", "0.6 Seconds", "0.8 Seconds", "1 Seconds", "2 Seconds", "3 Seconds", "4 Seconds", "5 Seconds"] _APO = ["OFF", "15 Minutes", "30 Minutes", "60 Minutes"] _AUDIO_BALANCE = ["Center", "A +50%", "A +100%", "B +50%", "B +100%"] _KEY_BEEP = ["OFF", "Radio & GPS", "Radio Only", "GPS Only"] 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.has_settings = True 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.baudrate = baud try: self.pipe.write("\r\r") except: break self.pipe.read(32) try: id = self.get_id() LOG.info("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 == 0xf: 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): LOG.debug("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 == 0xf if mem.empty: flag.disabled = 0xf 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] prog_vfo = get_prog_vfo(mem.freq) flag.prog_vfo = prog_vfo _mem.unknown1 = _mem.unknown2 = UNKNOWN_LOOKUP[prog_vfo] 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) LOG.debug("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.set_raw("\x00\xc8\xb3\x08\x00\x01\x00\x08" "\x08\x00\xc0\x27\x09\x00\x00\x00") def _get_settings(self): top = RadioSettings(self._get_display_settings(), self._get_audio_settings(), self._get_battery_settings()) return top def set_settings(self, settings): _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: if element.has_apply_callback(): LOG.debug("Using apply callback") try: element.run_apply_callback() except NotImplementedError as e: LOG.error(e) continue # Find the object containing setting. obj = _mem bits = element.get_name().split(".") setting = bits[-1] for name in bits[:-1]: if name.endswith("]"): name, index = name.split("[") index = int(index[:-1]) obj = getattr(obj, name)[index] else: obj = getattr(obj, name) try: old_val = getattr(obj, setting) LOG.debug("Setting %s(%r) <= %s" % ( element.get_name(), old_val, element.value)) setattr(obj, setting, element.value) except AttributeError as e: LOG.error("Setting %s is not in the memory map: %s" % (element.get_name(), e)) except Exception, e: LOG.debug(element.get_name()) raise def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None @classmethod def apply_power_on_msg(cls, setting, obj): message = setting.value.get_value() setattr(obj, "power_on_msg", cls._add_ff_pad(message, 8)) def apply_lcd_contrast(cls, setting, obj): rawval = setting.value.get_value() val = cls._LCD_CONTRAST.index(rawval) + 1 obj.contrast = val def apply_lamp_control(cls, setting, obj): rawval = setting.value.get_value() val = cls._LAMP_CONTROL.index(rawval) obj.lamp_control = val def apply_lamp_timer(cls, setting, obj): rawval = setting.value.get_value() val = cls._LAMP_TIMER.index(rawval) + 2 obj.lamp_timer = val def _get_display_settings(self): menu = RadioSettingGroup("display", "Display") display_settings = self._memobj.settings val = RadioSettingValueString( 0, 8, str(display_settings.power_on_msg).rstrip("\xFF")) rs = RadioSetting("display.power_on_msg", "Power on message", val) rs.set_apply_callback(self.apply_power_on_msg, display_settings) menu.append(rs) val = RadioSettingValueList( self._LCD_CONTRAST, self._LCD_CONTRAST[display_settings.contrast - 1]) rs = RadioSetting("display.contrast", "LCD Contrast", val) rs.set_apply_callback(self.apply_lcd_contrast, display_settings) menu.append(rs) val = RadioSettingValueList( self._LAMP_CONTROL, self._LAMP_CONTROL[display_settings.lamp_control]) rs = RadioSetting("display.lamp_control", "Lamp Control", val) rs.set_apply_callback(self.apply_lamp_control, display_settings) menu.append(rs) val = RadioSettingValueList( self._LAMP_TIMER, self._LAMP_TIMER[display_settings.lamp_timer - 2]) rs = RadioSetting("display.lamp_timer", "Lamp Timer", val) rs.set_apply_callback(self.apply_lamp_timer, display_settings) menu.append(rs) return menu def apply_battery_saver(cls, setting, obj): rawval = setting.value.get_value() val = cls._BATTERY_SAVER.index(rawval) obj.battery_saver = val def apply_APO(cls, setting, obj): rawval = setting.value.get_value() val = cls._APO.index(rawval) obj.APO = val def _get_battery_settings(self): menu = RadioSettingGroup("battery", "Battery") battery_settings = self._memobj.settings val = RadioSettingValueList( self._BATTERY_SAVER, self._BATTERY_SAVER[battery_settings.battery_saver]) rs = RadioSetting("battery.battery_saver", "Battery Saver", val) rs.set_apply_callback(self.apply_battery_saver, battery_settings) menu.append(rs) val = RadioSettingValueList( self._APO, self._APO[battery_settings.APO]) rs = RadioSetting("battery.APO", "Auto Power Off", val) rs.set_apply_callback(self.apply_APO, battery_settings) menu.append(rs) return menu def apply_balance(cls, setting, obj): rawval = setting.value.get_value() val = cls._AUDIO_BALANCE.index(rawval) obj.balance = val def apply_key_beep(cls, setting, obj): rawval = setting.value.get_value() val = cls._KEY_BEEP.index(rawval) obj.key_beep = val def _get_audio_settings(self): menu = RadioSettingGroup("audio", "Audio") audio_settings = self._memobj.settings val = RadioSettingValueList( self._AUDIO_BALANCE, self._AUDIO_BALANCE[audio_settings.balance]) rs = RadioSetting("audio.balance", "Balance", val) rs.set_apply_callback(self.apply_balance, audio_settings) menu.append(rs) val = RadioSettingValueList( self._KEY_BEEP, self._KEY_BEEP[audio_settings.key_beep]) rs = RadioSetting("audio.key_beep", "Key Beep", val) rs.set_apply_callback(self.apply_key_beep, audio_settings) menu.append(rs) return menu @staticmethod def _add_ff_pad(val, length): return val.ljust(length, "\xFF")[:length] @classmethod def _strip_ff_pads(cls, messages): result = [] for msg_text in messages: result.append(str(msg_text).rstrip("\xFF")) return result 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-daily-20170714/chirp/drivers/ic9x.py0000644000016101777760000003125212646374217021413 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 time import threading import logging from chirp.drivers import ic9x_ll, icf from chirp import chirp_common, errors, util, directory from chirp import bitwise LOG = logging.getLogger(__name__) 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.timeout = 0.1 self.__memcache = {} self.__bankcache = {} global LOCK self._lock = LOCK def _maybe_send_magic(self): if (time.time() - self.__last) > 1: LOG.debug("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 number in self.__memcache: 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: LOG.debug("Getting %i" % i) mem = self.get_memory(i) if mem: memories.append(mem) LOG.debug("Done: %s" % mem) except errors.InvalidMemoryLocation: pass except errors.InvalidDataError, e: LOG.error("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] LOG.dbeug("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-daily-20170714/chirp/drivers/id51plus.py0000644000016101777760000001036612612355377022207 0ustar jenkinsnogroup00000000000000# Copyright 2015 Eric Dropps # # 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 logging from chirp.drivers import id31 from chirp import directory, bitwise LOG = logging.getLogger(__name__) MEM_FORMAT = """ struct { u24 freq; u16 offset; u16 rtone:6, ctone:6, unknown2:1, mode:3; u8 dtcs; u8 tune_step:4, unknown5:4; u8 unknown4; u8 tmode:4, duplex:2, dtcs_polarity:2; char name[16]; u8 unknown13; u8 urcall[7]; u8 rpt1call[7]; u8 rpt2call[7]; } memory[500]; #seekto 0x6A40; u8 used_flags[70]; #seekto 0x6A86; u8 skip_flags[69]; #seekto 0x6ACB; u8 pskp_flags[69]; #seekto 0x6B40; struct { u8 unknown:3, bank:5; u8 index; } banks[500]; #seekto 0x6FD0; struct { char name[16]; } bank_names[26]; #seekto 0xA8C0; struct { u24 freq; u16 offset; u8 unknown1[4]; u8 call[7]; char name[16]; char subname[8]; u8 unknown3[10]; } repeaters[750]; #seekto 0x1384E; struct { u8 call[7]; } rptcall[750]; #seekto 0x14FBE; struct { char name[16]; } rptgroup_names[30]; #seekto 0x1519E; struct { char call[8]; char tag[4]; } mycall[6]; #seekto 0x151E6; struct { char call[8]; } urcall[200]; #seekto 0x15826; struct { char name[16]; } urcallname[200]; """ @directory.register class ID51PLUSRadio(id31.ID31Radio): """Icom ID-51 Plus/50th Anniversary""" MODEL = "ID-51 Plus" _memsize = 0x1FB40 _model = "\x33\x90\x00\x02" _endframe = "Icom Inc\x2E\x44\x41" _bank_class = id31.ID31Bank _ranges = [(0x00000, 0x1FB40, 32)] MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"} @classmethod def match_model(cls, filedata, filename): """Given contents of a stored file (@filedata), return True if this radio driver handles the represented model""" # The default check for ICOM is just to check memory size # Since the ID-51 and ID-51 Plus/Anniversary have exactly # the same memory size, we need to do a more detailed check. if len(filedata) == cls._memsize: LOG.debug('File has correct memory size, ' 'checking 20 bytes at offset 0x1AF40') snip = filedata[0x1AF40:0x1AF60] if snip != ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'): LOG.debug('bytes matched ID-51 Plus Signature') return True else: LOG.debug('bytes did not match ID-51 Plus Signature') return False def _get_bank(self, loc): _bank = self._memobj.banks[loc] LOG.debug("Bank Value for location %s is %s" % (loc, _bank.bank)) if _bank.bank == 0x1F: return None else: return _bank.bank def _set_bank(self, loc, bank): _bank = self._memobj.banks[loc] if bank is None: _bank.bank = 0x1F else: _bank.bank = bank def get_features(self): rf = super(ID51PLUSRadio, self).get_features() rf.valid_bands = [(108000000, 174000000), (380000000, 479000000)] return rf def get_repeater_call_list(self): calls = [] # Unlike previos DStar radios, there is not a seperate repeater # callsign list. It's only the DV Memory banks. for repeater in self._memobj.repeaters: call = id31._decode_call(repeater.call) if call == "CALLSIGN": call = "" calls.append(call.rstrip()) return calls def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) chirp-daily-20170714/chirp/drivers/hobbypcb.py0000644000016101777760000002751012735745351022331 0ustar jenkinsnogroup00000000000000# Copyright 2016 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 logging import time from chirp import chirp_common, directory, memmap, errors from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings LOG = logging.getLogger(__name__) BAUDS = [1200, 4800, 9600, 19200, 38400, 57600] POWER_LEVELS = [chirp_common.PowerLevel('Low', dBm=10), chirp_common.PowerLevel('High', dBm=24)] TONE_MODES = ['', 'Tone', 'TSQL', ''] def detect_baudrate(radio): bauds = list(BAUDS) bauds.remove(radio.pipe.getBaudrate()) bauds.insert(0, radio.pipe.getBaudrate()) for baud in bauds: radio.pipe.setBaudrate(baud) radio.pipe.setTimeout(0.5) radio.pipe.write('\rFW?\r') resp = radio.pipe.read(2) if resp.strip().startswith('FW'): resp += radio.pipe.read(16) LOG.info('HobbyPCB %s at baud rate %i' % (resp.strip(), baud)) return baud @directory.register class HobbyPCBRSUV3Radio(chirp_common.LiveRadio): """HobbyPCB RS-UV3""" VENDOR = "HobbyPCB" MODEL = "RS-UV3" BAUD_RATE = 19200 def __init__(self, *args, **kwargs): super(HobbyPCBRSUV3Radio, self).__init__(*args, **kwargs) if self.pipe: baud = detect_baudrate(self) if not baud: errors.RadioError('Radio did not respond') def _cmd(self, command, rsize=None): LOG.debug('> %s' % command) self.pipe.write('%s\r' % command) resp = '' if rsize is None: complete = lambda: False elif rsize == 0: rsize = 1 complete = lambda: resp.endswith('\r') else: complete = lambda: len(resp) >= rsize while not complete(): chunk = self.pipe.read(rsize) if not chunk: break resp += chunk LOG.debug('< %r [%i]' % (resp, len(resp))) return resp.strip() def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = False rf.has_name = False rf.has_cross = False rf.has_dtcs = False rf.has_rx_dtcs = False rf.has_dtcs_polarity = False rf.has_tuning_step = False rf.has_mode = False rf.has_settings = True rf.memory_bounds = (1, 9) # This radio supports memories 0-9 rf.valid_bands = [(144000000, 148000000), (220000000, 222000000), (440000000, 450000000), ] rf.valid_tmodes = TONE_MODES rf.valid_power_levels = POWER_LEVELS return rf def get_memory(self, number): _mem = self._cmd('CP%i' % number, 33).split('\r') LOG.debug('Memory elements: %s' % _mem) mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem[0]) * 1000 txfreq = int(_mem[1]) * 1000 mem.offset = abs(txfreq - mem.freq) if mem.freq < txfreq: mem.duplex = '+' elif mem.freq > txfreq: mem.duplex = '-' else: mem.duplex = '' mem.ctone = int(_mem[2]) / 100.0 mem.rtone = mem.ctone mem.tmode = TONE_MODES[int(_mem[3])] mem.power = POWER_LEVELS[int(_mem[5])] return mem def set_memory(self, mem): if mem.tmode in ['', 'Tone']: tone = mem.rtone * 100 else: tone = mem.ctone * 100 if mem.duplex == '+': self._cmd('FT%06i' % ((mem.freq + mem.offset) / 1000)) self._cmd('FR%06i' % (mem.freq / 1000)) elif mem.duplex == '-': self._cmd('FT%06i' % ((mem.freq - mem.offset) / 1000)) self._cmd('FR%06i' % (mem.freq / 1000)) else: self._cmd('FS%06i' % (mem.freq / 1000)) self._cmd('TM%i' % TONE_MODES.index(mem.tmode)) self._cmd('TF%05i' % tone) self._cmd('PW%i' % POWER_LEVELS.index(mem.power)) time.sleep(1) self._cmd('ST%i' % mem.number) def get_settings(self): def _get(cmd): return self._cmd('%s?' % cmd, 0).split(':')[1].strip() cw = RadioSettingGroup('beacon', 'Beacon Settings') cl = RadioSetting('CL%15s', 'CW Callsign', RadioSettingValueString(0, 15, _get('CL'))) cw.append(cl) cf = RadioSetting('CF%4i', 'CW Audio Frequency', RadioSettingValueInteger(400, 1300, int(_get('CF')))) cw.append(cf) cs = RadioSetting('CS%02i', 'CW Speed', RadioSettingValueInteger(5, 25, int(_get('CS')))) cw.append(cs) bc = RadioSetting('BC%03i', 'CW Beacon Timer', RadioSettingValueInteger(0, 600, int(_get('BC')))) cw.append(bc) bm = RadioSetting('BM%15s', 'Beacon Message', RadioSettingValueString(0, 15, _get('BM'))) cw.append(bm) bt = RadioSetting('BT%03i', 'Beacon Timer', RadioSettingValueInteger(0, 600, int(_get('BT')))) cw.append(bt) it = RadioSetting('IT%03i', 'CW ID Timer', RadioSettingValueInteger(0, 500, int(_get('IT')))) cw.append(it) tg = RadioSetting('TG%7s', 'CW Timeout Message', RadioSettingValueString(0, 7, _get('TG'))) cw.append(tg) io = RadioSettingGroup('io', 'IO') af = RadioSetting('AF%i', 'Arduino LPF', RadioSettingValueBoolean(_get('AF') == 'ON')) io.append(af) input_pin = ['OFF', 'SQ OPEN', 'PTT'] ai = RadioSetting('AI%i', 'Arduino Input Pin', RadioSettingValueList( input_pin, input_pin[int(_get('AI'))])) io.append(ai) output_pin = ['LOW', 'SQ OPEN', 'DTMF DETECT', 'TX ON', 'CTCSS DET', 'HIGH'] ao = RadioSetting('AO%i', 'Arduino Output Pin', RadioSettingValueList( output_pin, output_pin[int(_get('AO'))])) io.append(ao) bauds = [str(x) for x in BAUDS] b1 = RadioSetting('B1%i', 'Arduino Baudrate', RadioSettingValueList( bauds, bauds[int(_get('B1'))])) io.append(b1) b2 = RadioSetting('B2%i', 'Main Baudrate', RadioSettingValueList( bauds, bauds[int(_get('B2'))])) io.append(b2) dtmf = RadioSettingGroup('dtmf', 'DTMF Settings') dd = RadioSetting('DD%04i', 'DTMF Tone Duration', RadioSettingValueInteger(50, 2000, int(_get('DD')))) dtmf.append(dd) dr = RadioSetting('DR%i', 'DTMF Tone Detector', RadioSettingValueBoolean(_get('DR') == 'ON')) dtmf.append(dr) gt = RadioSetting('GT%02i', 'DTMF/CW Tone Gain', RadioSettingValueInteger(0, 15, int(_get('GT')))) dtmf.append(gt) sd = RadioSetting('SD%i', 'DTMF/CW Side Tone', RadioSettingValueBoolean(_get('SD') == 'ON')) dtmf.append(sd) general = RadioSettingGroup('general', 'General') dp = RadioSetting('DP%i', 'Pre-Emphasis', RadioSettingValueBoolean(_get('DP') == 'ON')) general.append(dp) fw = RadioSetting('_fw', 'Firmware Version', RadioSettingValueString(0, 20, _get('FW'))) general.append(fw) gm = RadioSetting('GM%02i', 'Mic Gain', RadioSettingValueInteger(0, 15, int(_get('GM')))) general.append(gm) hp = RadioSetting('HP%i', 'Audio High-Pass Filter', RadioSettingValueBoolean(_get('HP') == 'ON')) general.append(hp) ht = RadioSetting('HT%04i', 'Hang Time', RadioSettingValueInteger(0, 5000, int(_get('HT')))) general.append(ht) ledmode = ['OFF', 'ON', 'SQ OPEN', 'BATT CHG STAT'] ld = RadioSetting('LD%i', 'LED Mode', RadioSettingValueList( ledmode, ledmode[int(_get('LD'))])) general.append(ld) sq = RadioSetting('SQ%i', 'Squelch Level', RadioSettingValueInteger(0, 9, int(_get('SQ')))) general.append(sq) to = RadioSetting('TO%03i', 'Timeout Timer', RadioSettingValueInteger(0, 600, int(_get('TO')))) general.append(to) vu = RadioSetting('VU%02i', 'Receiver Audio Volume', RadioSettingValueInteger(0, 39, int(_get('VU')))) general.append(vu) rc = RadioSetting('RC%i', 'Current Channel', RadioSettingValueInteger(0, 9, 0)) rc.set_doc('Choosing one of these values causes the radio ' 'to change to the selected channel. The radio ' 'cannot tell CHIRP what channel is selected.') general.append(rc) return RadioSettings(general, cw, io, dtmf) def set_settings(self, settings): def _set(thing): # Try to only set something if it's new query = '%s?' % thing[:2] cur = self._cmd(query, 0) if cur.strip(): cur = cur.split()[1].strip() new = thing[2:].strip() if cur in ['ON', 'OFF']: cur = int(cur == 'ON') new = int(new) elif cur.isdigit(): cur = int(cur) new = int(new) if new != cur: LOG.info('Setting %s (%r != %r)' % (thing, cur, new)) self._cmd(thing) time.sleep(1) for group in settings: for setting in group: if setting.get_name().startswith('_'): LOG.debug('Skipping %s' % setting) continue cmd = setting.get_name() value = setting.value.get_value() if hasattr(setting.value, '_options'): value = setting.value._options.index(value) fullcmd = (cmd % value).strip() _set(fullcmd) chirp-daily-20170714/chirp/drivers/ict70.py0000644000016101777760000001417212476006422021456 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.drivers import icf from chirp import chirp_common, directory, 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(6)[:6] @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-daily-20170714/chirp/drivers/btech.py0000644000016101777760000040042413132065773021620 0ustar jenkinsnogroup00000000000000# Copyright 2016-2017: # * Pavel Milanes CO7WT, # * Jim Unroe KC9HI, # # 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 time import struct import logging LOG = logging.getLogger(__name__) from time import sleep from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, InvalidValueError from textwrap import dedent # A note about the memmory in these radios # # The real memory of these radios extends to 0x4000 # On read the factory software only uses up to 0x3200 # On write it just uploads the contents up to 0x3100 # # The mem beyond 0x3200 holds the ID data MEM_SIZE = 0x4000 BLOCK_SIZE = 0x40 TX_BLOCK_SIZE = 0x10 ACK_CMD = "\x06" MODES = ["FM", "NFM"] SKIP_VALUES = ["S", ""] TONES = chirp_common.TONES DTCS = sorted(chirp_common.DTCS_CODES + [645]) # lists related to "extra" settings PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"] PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)] OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"] SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"] # lists LIST_AB = ["A", "B"] LIST_ABCD = LIST_AB + ["C", "D"] LIST_ANIL = ["3", "4", "5"] LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)] LIST_COLOR4 = ["Off", "Blue", "Orange", "Purple"] LIST_COLOR8 = ["Black", "White", "Red", "Blue", "Green", "Yellow", "Indego", "Purple", "Gray"] LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"] LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"] LIST_EMCTPX = ["Off"] + LIST_EMCTP LIST_LANGUA = ["English", "Chinese"] LIST_MDF = ["Frequency", "Channel", "Name"] LIST_OFF1TO9 = ["Off"] + ["%s seconds" % x for x in range(1, 10)] LIST_OFF1TO10 = ["Off"] + ["%s seconds" % x for x in range(1, 11)] LIST_OFF1TO50 = ["Off"] + ["%s seconds" % x for x in range(1, 51)] LIST_PONMSG = ["Full", "Message", "Battery voltage"] LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"] LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"] LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)] LIST_SCMODE = ["Off", "PTT-SC", "MEM-SC", "PON-SC"] LIST_SHIFT = ["Off", "+", "-"] LIST_SKIPTX = ["Off", "Skip 1", "Skip 2"] STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0] LIST_STEP = [str(x) for x in STEPS] LIST_SYNC = ["Off", "AB", "CD", "AB+CD"] LIST_TMR = ["OFF", "M+A", "M+B", "M+C", "M+D", "M+A+B", "M+A+C", "M+A+D", "M+B+C", "M+B+D", "M+C+D", "M+A+B+C", "M+A+B+D", "M+A+C+D", "M+B+C+D", "A+B+C+D"] LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)] LIST_TXDISP = ["Power", "Mic Volume"] LIST_TXP = ["High", "Low"] LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"] LIST_VFOMR = ["Frequency", "Channel"] LIST_WIDE = ["Wide", "Narrow"] # lists related to DTMF, 2TONE and 5TONE settings LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3", "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO", "CCITT", "NATEL", "MODAT", "none"] LIST_5TONE_STANDARDS_without_none = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3", "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO", "CCITT", "NATEL", "MODAT"] LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200"] LIST_5TONE_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"] LIST_5TONE_DELAY = ["%s ms" % x for x in range(0, 1010, 10)] LIST_5TONE_RESET = ["%s ms" % x for x in range(100, 8100, 100)] LIST_5TONE_RESET_COLOR = ["%s ms" % x for x in range(100, 20100, 100)] LIST_DTMF_SPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_DTMF_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "#", "*"] LIST_DTMF_VALUES = [0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0D, 0x0E, 0x0F, 0x00, 0x0C, 0x0B ] LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"] LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00] LIST_DTMF_DELAY = ["%s ms" % x for x in range(100, 4100, 100)] CHARSET_DTMF_DIGITS = "0123456789AaBbCcDd#*" LIST_2TONE_DEC = ["A-B", "A-C", "A-D", "B-A", "B-C", "B-D", "C-A", "C-B", "C-D", "D-A", "D-B", "D-C"] LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"] # This is a general serial timeout for all serial read functions. # Practice has show that about 0.7 sec will be enough to cover all radios. STIMEOUT = 0.7 # this var controls the verbosity in the debug and by default it's low (False) # make it True and you will to get a very verbose debug.log debug = False # valid chars on the LCD, Note that " " (space) is stored as "\xFF" VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_" ##### ID strings ##################################################### # BTECH UV2501 pre-production units UV2501pp_fp = "M2C294" # BTECH UV2501 pre-production units 2 + and 1st Gen radios UV2501pp2_fp = "M29204" # B-TECH UV-2501 second generation (2G) radios UV2501G2_fp = "BTG214" # B-TECH UV-2501 third generation (3G) radios UV2501G3_fp = "BTG324" # B-TECH UV-2501+220 pre-production units UV2501_220pp_fp = "M3C281" # extra block read for the 2501+220 pre-production units # the same for all of this radios so far UV2501_220pp_id = " 280528" # B-TECH UV-2501+220 UV2501_220_fp = "M3G201" # new variant, let's call it Generation 2 UV2501_220G2_fp = "BTG211" # B-TECH UV-2501+220 third generation (3G) UV2501_220G3_fp = "BTG311" # B-TECH UV-5001 pre-production units + 1st Gen radios UV5001pp_fp = "V19204" # B-TECH UV-5001 alpha units UV5001alpha_fp = "V28204" # B-TECH UV-5001 second generation (2G) radios UV5001G2_fp = "BTG214" # B-TECH UV-5001 second generation (2G2) UV5001G22_fp = "V2G204" # B-TECH UV-5001 third generation (3G) UV5001G3_fp = "BTG304" # B-TECH UV-25X2 UV25X2_fp = "UC2012" # B-TECH UV-25X4 UV25X4_fp = "UC4014" # B-TECH UV-50X2 UV50X2_fp = "UC2M12" # special var to know when we found a BTECH Gen 3 BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp] # WACCOM Mini-8900 MINI8900_fp = "M28854" # QYT KT-UV980 KTUV980_fp = "H28854" # QYT KT8900 KT8900_fp = "M29154" # New generations KT8900 KT8900_fp1 = "M2C234" KT8900_fp2 = "M2G1F4" KT8900_fp3 = "M2G2F4" KT8900_fp4 = "M2G304" KT8900_fp5 = "M2G314" # this radio has an extra ID KT8900_id = "303688" # KT8900R KT8900R_fp = "M3G1F4" # Second Generation KT8900R_fp1 = "M3G214" # another model KT8900R_fp2 = "M3C234" # another model G4? KT8900R_fp3 = "M39164" # another model KT8900R_fp4 = "M3G314" # this radio has an extra ID KT8900R_id = "280528" # KT7900D (quad band) KT7900D_fp = "VC4004" # KT8900D (dual band) KT8900D_fp = "VC2002" # LUITON LT-588UV LT588UV_fp = "V2G1F4" # Added by rstrickoff gen 2 id LT588UV_fp1 = "V2G214" #### MAGICS # for the Waccom Mini-8900 MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02" # for the B-TECH UV-2501+220 (including pre production ones) MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02" # for the QYT KT8900 & R MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02" MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02" # magic string for all other models MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02" # for the QYT KT7900D & KT8900D MSTRING_KT8900D = "\x55\x20\x16\x08\x01\xFF\xDC\x02" # for the BTECH UV-25X2 and UV-50X2 MSTRING_UV25X2 = "\x55\x20\x16\x12\x28\xFF\xDC\x02" # for the BTECH UV-25X4 MSTRING_UV25X4 = "\x55\x20\x16\x11\x18\xFF\xDC\x02" def _clean_buffer(radio): """Cleaning the read serial buffer, hard timeout to survive an infinite data stream""" # touching the serial timeout to optimize the flushing # restored at the end to the default value radio.pipe.timeout = 0.1 dump = "1" datacount = 0 try: while len(dump) > 0: dump = radio.pipe.read(100) datacount += len(dump) # hard limit to survive a infinite serial data stream # 5 times bigger than a normal rx block (69 bytes) if datacount > 345: seriale = "Please check your serial port selection." raise errors.RadioError(seriale) # restore the default serial timeout radio.pipe.timeout = STIMEOUT except Exception: raise errors.RadioError("Unknown error cleaning the serial buffer") def _rawrecv(radio, amount): """Raw read from the radio device, less intensive way""" data = "" try: data = radio.pipe.read(amount) # DEBUG if debug is True: LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data))) # fail if no data is received if len(data) == 0: raise errors.RadioError("No data received from radio") # notice on the logs if short if len(data) < amount: LOG.warn("Short reading %d bytes from the %d requested." % (len(data), amount)) except: raise errors.RadioError("Error reading data from radio") return data def _send(radio, data): """Send data to the radio device""" try: for byte in data: radio.pipe.write(byte) # Some OS (mainly Linux ones) are too fast on the serial and # get the MCU inside the radio stuck in the early stages, this # hits some models more than others. # # To cope with that we introduce a delay on the writes. # Many option have been tested (delaying only after error occures, # after short reads, only for linux, ...) # Finally, a static delay was chosen as simplest of all solutions # (Michael Wagner, OE4AMW) # (for details, see issue 3993) sleep(0.002) # DEBUG if debug is True: LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data))) except: raise errors.RadioError("Error sending data to radio") def _make_frame(cmd, addr, length, data=""): """Pack the info in the headder format""" frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length) # add the data if set if len(data) != 0: frame += data return frame def _recv(radio, addr): """Get data from the radio all at once to lower syscalls load""" # Get the full 69 bytes at a time to reduce load # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE) # get the whole block block = _rawrecv(radio, BLOCK_SIZE + 5) # basic check if len(block) < (BLOCK_SIZE + 5): raise errors.RadioError("Short read of the block 0x%04x" % addr) # checking for the ack if block[0] != ACK_CMD: raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr) # header validation c, a, l = struct.unpack(">BHB", block[1:5]) if a != addr or l != BLOCK_SIZE or c != ord("X"): LOG.debug("Invalid header for block 0x%04x" % addr) LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l)) raise errors.RadioError("Invalid header for block 0x%04x:" % addr) # return the data return block[5:] def _start_clone_mode(radio, status): """Put the radio in clone mode and get the ident string, 3 tries""" # cleaning the serial buffer _clean_buffer(radio) # prep the data to show in the UI status.cur = 0 status.msg = "Identifying the radio..." status.max = 3 radio.status_fn(status) try: for a in range(0, status.max): # Update the UI status.cur = a + 1 radio.status_fn(status) # send the magic word _send(radio, radio._magic) # Now you get a x06 of ACK if all goes well ack = radio.pipe.read(1) if ack == "\x06": # DEBUG LOG.info("Magic ACK received") status.cur = status.max radio.status_fn(status) return True return False except errors.RadioError: raise except Exception, e: raise errors.RadioError("Error sending Magic to radio:\n%s" % e) def _do_ident(radio, status, upload=False): """Put the radio in PROGRAM mode & identify it""" # set the serial discipline radio.pipe.baudrate = 9600 radio.pipe.parity = "N" # open the radio into program mode if _start_clone_mode(radio, status) is False: msg = "Radio did not enter clone mode" # warning about old versions of QYT KT8900 if radio.MODEL == "KT8900": msg += ". You may want to try it as a WACCOM MINI-8900, there is a" msg += " known variant of this radios that is a clone of it." raise errors.RadioError(msg) # Ok, get the ident string ident = _rawrecv(radio, 49) # basic check for the ident if len(ident) != 49: raise errors.RadioError("Radio send a short ident block.") # check if ident is OK itis = False for fp in radio._fileid: if fp in ident: # got it! itis = True # checking if we are dealing with a Gen 3 BTECH if radio.VENDOR == "BTECH" and fp in BTECH3: radio.btech3 = True break if itis is False: LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident)) raise errors.RadioError("Radio identification failed.") # some radios needs a extra read and check for a code on it, this ones # has the check value in the _id2 var, others simply False if radio._id2 is not False: # lower the timeout here as this radios are reseting due to timeout radio.pipe.timeout = 0.05 # query & receive the extra ID _send(radio, _make_frame("S", 0x3DF0, 16)) id2 = _rawrecv(radio, 21) # WARNING !!!!!! # different radios send a response with a different amount of data # it seems that it's padded with \xff, \x20 and some times with \x00 # we just care about the first 16, our magic string is in there if len(id2) < 16: raise errors.RadioError("The extra ID is short, aborting.") # ok, the correct string must be in the received data if radio._id2 not in id2: LOG.debug("Full *BAD* extra ID on the %s is: \n%s" % (radio.MODEL, util.hexprint(id2))) raise errors.RadioError("The extra ID is wrong, aborting.") # this radios need a extra request/answer here on the upload # the amount of data received depends of the radio type # # also the first block of TX must no have the ACK at the beginning # see _upload for this. if upload is True: # send an ACK _send(radio, ACK_CMD) # the amount of data depend on the radio, so far we have two radios # reading two bytes with an ACK at the end and just ONE with just # one byte (QYT KT8900) # the JT-6188 appears a clone of the last, but reads TWO bytes. # # we will read two bytes with a custom timeout to not penalize the # users for this. # # we just check for a response and last byte being a ACK, that is # the common stone for all radios (3 so far) ack = _rawrecv(radio, 2) # checking if len(ack) == 0 or ack[-1:] != ACK_CMD: raise errors.RadioError("Radio didn't ACK the upload") # restore the default serial timeout radio.pipe.timeout = STIMEOUT # DEBUG LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL)) return True def _download(radio): """Get the memory map""" # UI progress status = chirp_common.Status() # put radio in program mode and identify it _do_ident(radio, status) # the models that doesn't have the extra ID have to make a dummy read here if radio._id2 is False: _send(radio, _make_frame("S", 0, BLOCK_SIZE)) discard = _rawrecv(radio, BLOCK_SIZE + 5) if debug is True: LOG.info("Dummy first block read done, got this:\n\n %s", util.hexprint(discard)) # reset the progress bar in the UI status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning from radio..." status.cur = 0 radio.status_fn(status) # cleaning the serial buffer _clean_buffer(radio) data = "" for addr in range(0, MEM_SIZE, BLOCK_SIZE): # sending the read request _send(radio, _make_frame("S", addr, BLOCK_SIZE)) # read d = _recv(radio, addr) # aggregate the data data += d # UI Update status.cur = addr / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) return data def _upload(radio): """Upload procedure""" # The UPLOAD mem is restricted to lower than 0x3100, # so we will overide that here localy MEM_SIZE = 0x3100 # UI progress status = chirp_common.Status() # put radio in program mode and identify it _do_ident(radio, status, True) # get the data to upload to radio data = radio.get_mmap() # Reset the UI progress status.max = MEM_SIZE / TX_BLOCK_SIZE status.cur = 0 status.msg = "Cloning to radio..." radio.status_fn(status) # the radios that doesn't have the extra ID 'may' do a dummy write, I found # that leveraging the bad ACK and NOT doing the dummy write is ok, as the # dummy write is accepted (it actually writes to the mem!) by the radio. # cleaning the serial buffer _clean_buffer(radio) # the fun start here for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE): # getting the block of data to send d = data[addr:addr + TX_BLOCK_SIZE] # build the frame to send frame = _make_frame("X", addr, TX_BLOCK_SIZE, d) # first block must not send the ACK at the beginning for the # ones that has the extra id, since this have to do a extra step if addr == 0 and radio._id2 is not False: frame = frame[1:] # send the frame _send(radio, frame) # receiving the response ack = _rawrecv(radio, 1) # basic check if len(ack) != 1: raise errors.RadioError("No ACK when writing block 0x%04x" % addr) if not ack in "\x06\x05": raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr) # UI Update status.cur = addr / TX_BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0x3f70:0x3f76] if rid in cls._fileid: return True return False def _decode_ranges(low, high): """Unpack the data in the ranges zones in the memmap and return a tuple with the integer corresponding to the Mhz it means""" ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2]) ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2]) ilow *= 1000000 ihigh *= 1000000 return (ilow, ihigh) def _split(rf, f1, f2): """Returns False if the two freqs are in the same band (no split) or True otherwise""" # determine if the two freqs are in the same band for low, high in rf.valid_bands: if f1 >= low and f1 <= high and \ f2 >= low and f2 <= high: # if the two freqs are on the same Band this is not a split return False # if you get here is because the freq pairs are split return True class BTechMobileCommon(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """BTECH's UV-5001 and alike radios""" VENDOR = "BTECH" MODEL = "" IDENT = "" BANDS = 2 COLOR_LCD = False NAME_LENGTH = 6 _power_levels = [chirp_common.PowerLevel("High", watts=25), chirp_common.PowerLevel("Low", watts=10)] _vhf_range = (130000000, 180000000) _220_range = (200000000, 271000000) _uhf_range = (400000000, 521000000) _350_range = (350000000, 391000000) _upper = 199 _magic = MSTRING _fileid = None _id2 = False btech3 = False @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This driver is experimental.\n' '\n' 'Please keep a copy of your memories with the original software ' 'if you treasure them, this driver is new and may contain' ' bugs.\n' '\n' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow these instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): """Get the radio's features""" # we will use the following var as global global POWER_LEVELS rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = MODES rf.valid_characters = VALID_CHARS rf.valid_name_length = self.NAME_LENGTH rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_skips = SKIP_VALUES rf.valid_dtcs_codes = DTCS rf.memory_bounds = (0, self._upper) # power levels POWER_LEVELS = self._power_levels rf.valid_power_levels = POWER_LEVELS # normal dual bands rf.valid_bands = [self._vhf_range, self._uhf_range] # 220 band if self.BANDS == 3 or self.BANDS == 4: rf.valid_bands.append(self._220_range) # 350 band if self.BANDS == 4: rf.valid_bands.append(self._350_range) return rf def sync_in(self): """Download from radio""" data = _download(self) self._mmap = memmap.MemoryMap(data) self.process_mmap() def sync_out(self): """Upload to radio""" try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Error: %s" % e) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def _decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" pol = None if val in [0, 65535]: return '', None, None elif val > 0x0258: a = val / 10.0 return 'Tone', a, pol else: if val > 0x69: index = val - 0x6A pol = "R" else: index = val - 1 pol = "N" tone = DTCS[index] return 'DTCS', tone, pol def _encode_tone(self, memval, mode, val, pol): """Parse the tone data to encode from UI to mem""" if mode == '' or mode is None: memval.set_raw("\x00\x00") elif mode == 'Tone': memval.set_value(val * 10) elif mode == 'DTCS': # detect the index in the DTCS list try: index = DTCS.index(val) if pol == "N": index += 1 else: index += 0x6A memval.set_value(index) except: msg = "Digital Tone '%d' is not supported" % value LOG.error(msg) raise errors.RadioError(msg) else: msg = "Internal error: invalid mode '%s'" % mode LOG.error(msg) raise errors.InvalidDataError(msg) def get_memory(self, number): """Get the mem representation from the radio image""" _mem = self._memobj.memory[number] _names = self._memobj.names[number] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # Memory number mem.number = number if _mem.get_raw()[0] == "\xFF": mem.empty = True return mem # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[4] == "\xFF": # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX freq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset != 0: if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): mem.duplex = "split" mem.offset = int(_mem.txfreq) * 10 elif offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 # name TAG of the channel mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ") # power mem.power = POWER_LEVELS[int(_mem.power)] # wide/narrow mem.mode = MODES[int(_mem.wide)] # skip mem.skip = SKIP_VALUES[_mem.add] # tone data rxtone = txtone = None txtone = self._decode_tone(_mem.txtone) rxtone = self._decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra mem.extra = RadioSettingGroup("extra", "Extra") if not self.COLOR_LCD or \ (self.COLOR_LCD and not self.VENDOR == "BTECH"): scramble = RadioSetting("scramble", "Scramble", RadioSettingValueBoolean(bool( _mem.scramble))) mem.extra.append(scramble) bcl = RadioSetting("bcl", "Busy channel lockout", RadioSettingValueBoolean(bool(_mem.bcl))) mem.extra.append(bcl) pttid = RadioSetting("pttid", "PTT ID", RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])) mem.extra.append(pttid) # validating scode scode = _mem.scode if _mem.scode != 15 else 0 pttidcode = RadioSetting("scode", "PTT ID signal code", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[scode])) mem.extra.append(pttidcode) optsig = RadioSetting("optsig", "Optional signaling", RadioSettingValueList( OPTSIG_LIST, OPTSIG_LIST[_mem.optsig])) mem.extra.append(optsig) spmute = RadioSetting("spmute", "Speaker mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_mem.spmute])) mem.extra.append(spmute) return mem def set_memory(self, mem): """Set the memory data in the eeprom img from the UI""" # get the eprom representation of this channel _mem = self._memobj.memory[mem.number] _names = self._memobj.names[mem.number] mem_was_empty = False # same method as used in get_memory for determining if mem is empty # doing this BEFORE overwriting it with new values ... if _mem.get_raw()[0] == "\xFF": LOG.debug("This mem was empty before") mem_was_empty = True # if empty memmory if mem.empty: # the channel itself _mem.set_raw("\xFF" * 16) # the name tag _names.set_raw("\xFF" * 16) return # frequency _mem.rxfreq = mem.freq / 10 # duplex if mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": for i in _mem.txfreq: i.set_raw("\xFF") elif mem.duplex == "split": _mem.txfreq = mem.offset / 10 else: _mem.txfreq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self._encode_tone(_mem.txtone, txmode, txtone, txpol) self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol) # name TAG of the channel if len(mem.name) < self.NAME_LENGTH: # we must pad to self.NAME_LENGTH chars, " " = "\xFF" mem.name = str(mem.name).ljust(self.NAME_LENGTH, " ") _names.name = str(mem.name).replace(" ", "\xFF") # power, # default power level is high _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power) # wide/narrow _mem.wide = MODES.index(mem.mode) # scan add property _mem.add = SKIP_VALUES.index(mem.skip) # reseting unknowns, this have to be set by hand _mem.unknown0 = 0 _mem.unknown1 = 0 _mem.unknown2 = 0 _mem.unknown3 = 0 _mem.unknown4 = 0 _mem.unknown5 = 0 _mem.unknown6 = 0 # extra settings if len(mem.extra) > 0: # there are setting, parse LOG.debug("Extra-Setting supplied. Setting them.") for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) else: if mem.empty: LOG.debug("New mem is empty.") else: LOG.debug("New mem is NOT empty") # set extra-settings to default ONLY when apreviously empty or # deleted memory was edited to prevent errors such as #4121 if mem_was_empty : LOG.debug("old mem was empty. Setting default for extras.") _mem.spmute = 0 _mem.optsig = 0 _mem.scramble = 0 _mem.bcl = 0 _mem.pttid = 0 _mem.scode = 0 return mem def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") top = RadioSettings(basic, advanced, other, work) # Basic if self.COLOR_LCD: tmr = RadioSetting("settings.tmr", "Transceiver multi-receive", RadioSettingValueList( LIST_TMR, LIST_TMR[_mem.settings.tmr])) basic.append(tmr) else: tdr = RadioSetting("settings.tdr", "Transceiver dual receive", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(tdr) sql = RadioSetting("settings.sql", "Squelch level", RadioSettingValueInteger(0, 9, _mem.settings.sql)) basic.append(sql) tot = RadioSetting("settings.tot", "Time out timer", RadioSettingValueList( LIST_TOT, LIST_TOT[_mem.settings.tot])) basic.append(tot) if self.VENDOR == "BTECH" or self.COLOR_LCD: apo = RadioSetting("settings.apo", "Auto power off timer", RadioSettingValueList( LIST_APO, LIST_APO[_mem.settings.apo])) basic.append(apo) else: toa = RadioSetting("settings.apo", "Time out alert timer", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[_mem.settings.apo])) basic.append(toa) abr = RadioSetting("settings.abr", "Backlight timer", RadioSettingValueList( LIST_OFF1TO50, LIST_OFF1TO50[_mem.settings.abr])) basic.append(abr) beep = RadioSetting("settings.beep", "Key beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(beep) dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone", RadioSettingValueList( LIST_DTMFST, LIST_DTMFST[_mem.settings.dtmfst])) basic.append(dtmfst) if not self.COLOR_LCD: prisc = RadioSetting("settings.prisc", "Priority scan", RadioSettingValueBoolean( _mem.settings.prisc)) basic.append(prisc) prich = RadioSetting("settings.prich", "Priority channel", RadioSettingValueInteger(0, 199, _mem.settings.prich)) basic.append(prich) screv = RadioSetting("settings.screv", "Scan resume method", RadioSettingValueList( LIST_SCREV, LIST_SCREV[_mem.settings.screv])) basic.append(screv) pttlt = RadioSetting("settings.pttlt", "PTT transmit delay", RadioSettingValueInteger(0, 30, _mem.settings.pttlt)) basic.append(pttlt) if self.VENDOR == "BTECH" and self.COLOR_LCD: emctp = RadioSetting("settings.emctp", "Alarm mode", RadioSettingValueList( LIST_EMCTPX, LIST_EMCTPX[_mem.settings.emctp])) basic.append(emctp) else: emctp = RadioSetting("settings.emctp", "Alarm mode", RadioSettingValueList( LIST_EMCTP, LIST_EMCTP[_mem.settings.emctp])) basic.append(emctp) emcch = RadioSetting("settings.emcch", "Alarm channel", RadioSettingValueInteger(0, 199, _mem.settings.emcch)) basic.append(emcch) if self.COLOR_LCD: if _mem.settings.sigbp > 0x01: val = 0x00 else: val = _mem.settings.sigbp sigbp = RadioSetting("settings.sigbp", "Roger beep", RadioSettingValueBoolean(val)) basic.append(sigbp) else: ringt = RadioSetting("settings.ringt", "Ring time", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[_mem.settings.ringt])) basic.append(ringt) camdf = RadioSetting("settings.camdf", "Display mode A", RadioSettingValueList( LIST_MDF, LIST_MDF[_mem.settings.camdf])) basic.append(camdf) cbmdf = RadioSetting("settings.cbmdf", "Display mode B", RadioSettingValueList( LIST_MDF, LIST_MDF[_mem.settings.cbmdf])) basic.append(cbmdf) if self.COLOR_LCD: ccmdf = RadioSetting("settings.ccmdf", "Display mode C", RadioSettingValueList( LIST_MDF, LIST_MDF[_mem.settings.ccmdf])) basic.append(ccmdf) cdmdf = RadioSetting("settings.cdmdf", "Display mode D", RadioSettingValueList( LIST_MDF, LIST_MDF[_mem.settings.cdmdf])) basic.append(cdmdf) langua = RadioSetting("settings.langua", "Language", RadioSettingValueList( LIST_LANGUA, LIST_LANGUA[_mem.settings.langua])) basic.append(langua) if self.VENDOR == "BTECH": if self.COLOR_LCD: sync = RadioSetting("settings.sync", "Channel display sync", RadioSettingValueList( LIST_SYNC, LIST_SYNC[_mem.settings.sync])) basic.append(sync) else: sync = RadioSetting("settings.sync", "A/B channel sync", RadioSettingValueBoolean( _mem.settings.sync)) basic.append(sync) else: autolk = RadioSetting("settings.sync", "Auto keylock", RadioSettingValueBoolean( _mem.settings.sync)) basic.append(autolk) if not self.COLOR_LCD: ponmsg = RadioSetting("settings.ponmsg", "Power-on message", RadioSettingValueList( LIST_PONMSG, LIST_PONMSG[_mem.settings.ponmsg])) basic.append(ponmsg) if self.COLOR_LCD: mainfc = RadioSetting("settings.mainfc", "Main LCD foreground color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.mainfc])) basic.append(mainfc) mainbc = RadioSetting("settings.mainbc", "Main LCD background color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.mainbc])) basic.append(mainbc) menufc = RadioSetting("settings.menufc", "Menu foreground color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.menufc])) basic.append(menufc) menubc = RadioSetting("settings.menubc", "Menu background color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.menubc])) basic.append(menubc) stafc = RadioSetting("settings.stafc", "Top status foreground color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.stafc])) basic.append(stafc) stabc = RadioSetting("settings.stabc", "Top status background color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.stabc])) basic.append(stabc) sigfc = RadioSetting("settings.sigfc", "Bottom status foreground color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.sigfc])) basic.append(sigfc) sigbc = RadioSetting("settings.sigbc", "Bottom status background color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.sigbc])) basic.append(sigbc) rxfc = RadioSetting("settings.rxfc", "Receiving character color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.rxfc])) basic.append(rxfc) txfc = RadioSetting("settings.txfc", "Transmitting character color", RadioSettingValueList( LIST_COLOR8, LIST_COLOR8[_mem.settings.txfc])) basic.append(txfc) txdisp = RadioSetting("settings.txdisp", "Transmitting status display", RadioSettingValueList( LIST_TXDISP, LIST_TXDISP[_mem.settings.txdisp])) basic.append(txdisp) else: wtled = RadioSetting("settings.wtled", "Standby backlight Color", RadioSettingValueList( LIST_COLOR4, LIST_COLOR4[_mem.settings.wtled])) basic.append(wtled) rxled = RadioSetting("settings.rxled", "RX backlight Color", RadioSettingValueList( LIST_COLOR4, LIST_COLOR4[_mem.settings.rxled])) basic.append(rxled) txled = RadioSetting("settings.txled", "TX backlight Color", RadioSettingValueList( LIST_COLOR4, LIST_COLOR4[_mem.settings.txled])) basic.append(txled) anil = RadioSetting("settings.anil", "ANI length", RadioSettingValueList( LIST_ANIL, LIST_ANIL[_mem.settings.anil])) basic.append(anil) reps = RadioSetting("settings.reps", "Relay signal (tone burst)", RadioSettingValueList( LIST_REPS, LIST_REPS[_mem.settings.reps])) basic.append(reps) repm = RadioSetting("settings.repm", "Relay condition", RadioSettingValueList( LIST_REPM, LIST_REPM[_mem.settings.repm])) basic.append(repm) if self.VENDOR == "BTECH" or self.COLOR_LCD: if self.COLOR_LCD: tmrmr = RadioSetting("settings.tmrmr", "TMR return time", RadioSettingValueList( LIST_OFF1TO50, LIST_OFF1TO50[_mem.settings.tmrmr])) basic.append(tmrmr) else: tdrab = RadioSetting("settings.tdrab", "TDR return time", RadioSettingValueList( LIST_OFF1TO50, LIST_OFF1TO50[_mem.settings.tdrab])) basic.append(tdrab) ste = RadioSetting("settings.ste", "Squelch tail eliminate", RadioSettingValueBoolean(_mem.settings.ste)) basic.append(ste) rpste = RadioSetting("settings.rpste", "Repeater STE", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[_mem.settings.rpste])) basic.append(rpste) rptdl = RadioSetting("settings.rptdl", "Repeater STE delay", RadioSettingValueList( LIST_RPTDL, LIST_RPTDL[_mem.settings.rptdl])) basic.append(rptdl) if str(_mem.fingerprint.fp) in BTECH3: mgain = RadioSetting("settings.mgain", "Mic gain", RadioSettingValueInteger(0, 120, _mem.settings.mgain)) basic.append(mgain) if str(_mem.fingerprint.fp) in BTECH3 or self.COLOR_LCD: dtmfg = RadioSetting("settings.dtmfg", "DTMF gain", RadioSettingValueInteger(0, 60, _mem.settings.dtmfg)) basic.append(dtmfg) if self.VENDOR == "BTECH" and self.COLOR_LCD: mgain = RadioSetting("settings.mgain", "Mic gain", RadioSettingValueInteger(0, 120, _mem.settings.mgain)) basic.append(mgain) skiptx = RadioSetting("settings.skiptx", "Skip TX", RadioSettingValueList( LIST_SKIPTX, LIST_SKIPTX[_mem.settings.skiptx])) basic.append(skiptx) scmode = RadioSetting("settings.scmode", "Scan mode", RadioSettingValueList( LIST_SCMODE, LIST_SCMODE[_mem.settings.scmode])) basic.append(scmode) # Advanced def _filter(name): filtered = "" for char in str(name): if char in VALID_CHARS: filtered += char else: filtered += " " return filtered _msg = self._memobj.poweron_msg if self.COLOR_LCD: line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1", RadioSettingValueString(0, 8, _filter( _msg.line1))) advanced.append(line1) line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2", RadioSettingValueString(0, 8, _filter( _msg.line2))) advanced.append(line2) line3 = RadioSetting("poweron_msg.line3", "Power-on message line 3", RadioSettingValueString(0, 8, _filter( _msg.line3))) advanced.append(line3) line4 = RadioSetting("poweron_msg.line4", "Power-on message line 4", RadioSettingValueString(0, 8, _filter( _msg.line4))) advanced.append(line4) line5 = RadioSetting("poweron_msg.line5", "Power-on message line 5", RadioSettingValueString(0, 8, _filter( _msg.line5))) advanced.append(line5) line6 = RadioSetting("poweron_msg.line6", "Power-on message line 6", RadioSettingValueString(0, 8, _filter( _msg.line6))) advanced.append(line6) line7 = RadioSetting("poweron_msg.line7", "Power-on message line 7", RadioSettingValueString(0, 8, _filter( _msg.line7))) advanced.append(line7) line8 = RadioSetting("poweron_msg.line8", "Static message", RadioSettingValueString(0, 8, _filter( _msg.line8))) advanced.append(line8) else: line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1", RadioSettingValueString(0, 6, _filter( _msg.line1))) advanced.append(line1) line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2", RadioSettingValueString(0, 6, _filter( _msg.line2))) advanced.append(line2) if self.MODEL in ("UV-2501", "UV-5001"): vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching", RadioSettingValueBoolean( _mem.settings2.vfomren)) advanced.append(vfomren) reseten = RadioSetting("settings2.reseten", "RESET", RadioSettingValueBoolean( _mem.settings2.reseten)) advanced.append(reseten) menuen = RadioSetting("settings2.menuen", "Menu", RadioSettingValueBoolean( _mem.settings2.menuen)) advanced.append(menuen) # Other def convert_bytes_to_limit(bytes): limit = "" for byte in bytes: if byte < 10: limit += chr(byte + 0x30) else: break return limit if self.MODEL in ["UV-2501+220", "KT8900R"]: _ranges = self._memobj.ranges220 ranges = "ranges220" else: _ranges = self._memobj.ranges ranges = "ranges" _limit = convert_bytes_to_limit(_ranges.vhf_low) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val) other.append(vhf_low) _limit = convert_bytes_to_limit(_ranges.vhf_high) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val) other.append(vhf_high) if self.BANDS == 3 or self.BANDS == 4: _limit = convert_bytes_to_limit(_ranges.vhf2_low) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val) other.append(vhf2_low) _limit = convert_bytes_to_limit(_ranges.vhf2_high) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val) other.append(vhf2_high) _limit = convert_bytes_to_limit(_ranges.uhf_low) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val) other.append(uhf_low) _limit = convert_bytes_to_limit(_ranges.uhf_high) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val) other.append(uhf_high) if self.BANDS == 4: _limit = convert_bytes_to_limit(_ranges.uhf2_low) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) uhf2_low = RadioSetting("%s.uhf2_low" % ranges, "UHF2 low", val) other.append(uhf2_low) _limit = convert_bytes_to_limit(_ranges.uhf2_high) val = RadioSettingValueString(0, 3, _limit) val.set_mutable(False) uhf2_high = RadioSetting("%s.uhf2_high" % ranges, "UHF2 high", val) other.append(uhf2_high) val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp)) val.set_mutable(False) fp = RadioSetting("fingerprint.fp", "Fingerprint", val) other.append(fp) # Work if self.COLOR_LCD: dispab = RadioSetting("settings2.dispab", "Display", RadioSettingValueList( LIST_ABCD, LIST_ABCD[_mem.settings2.dispab])) work.append(dispab) else: dispab = RadioSetting("settings2.dispab", "Display", RadioSettingValueList( LIST_AB, LIST_AB[_mem.settings2.dispab])) work.append(dispab) if self.COLOR_LCD: vfomra = RadioSetting("settings2.vfomra", "VFO/MR A mode", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_mem.settings2.vfomra])) work.append(vfomra) vfomrb = RadioSetting("settings2.vfomrb", "VFO/MR B mode", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_mem.settings2.vfomrb])) work.append(vfomrb) vfomrc = RadioSetting("settings2.vfomrc", "VFO/MR C mode", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_mem.settings2.vfomrc])) work.append(vfomrc) vfomrd = RadioSetting("settings2.vfomrd", "VFO/MR D mode", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_mem.settings2.vfomrd])) work.append(vfomrd) else: vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode", RadioSettingValueList( LIST_VFOMR, LIST_VFOMR[_mem.settings2.vfomr])) work.append(vfomr) keylock = RadioSetting("settings2.keylock", "Keypad lock", RadioSettingValueBoolean(_mem.settings2.keylock)) work.append(keylock) mrcha = RadioSetting("settings2.mrcha", "MR A channel", RadioSettingValueInteger(0, 199, _mem.settings2.mrcha)) work.append(mrcha) mrchb = RadioSetting("settings2.mrchb", "MR B channel", RadioSettingValueInteger(0, 199, _mem.settings2.mrchb)) work.append(mrchb) if self.COLOR_LCD: mrchc = RadioSetting("settings2.mrchc", "MR C channel", RadioSettingValueInteger(0, 199, _mem.settings2.mrchc)) work.append(mrchc) mrchd = RadioSetting("settings2.mrchd", "MR D channel", RadioSettingValueInteger(0, 199, _mem.settings2.mrchd)) work.append(mrchd) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): _vhf_lower = int(convert_bytes_to_limit(_ranges.vhf_low)) _vhf_upper = int(convert_bytes_to_limit(_ranges.vhf_high)) _uhf_lower = int(convert_bytes_to_limit(_ranges.uhf_low)) _uhf_upper = int(convert_bytes_to_limit(_ranges.uhf_high)) if self.BANDS == 3 or self.BANDS == 4: _vhf2_lower = int(convert_bytes_to_limit(_ranges.vhf2_low)) _vhf2_upper = int(convert_bytes_to_limit(_ranges.vhf2_high)) if self.BANDS == 4: _uhf2_lower = int(convert_bytes_to_limit(_ranges.uhf2_low)) _uhf2_upper = int(convert_bytes_to_limit(_ranges.uhf2_high)) value = chirp_common.parse_freq(value) msg = ("Can't be less then %i.0000") if value > 99000000 and value < _vhf_lower * 1000000: raise InvalidValueError(msg % (_vhf_lower)) msg = ("Can't be betweeb %i.9975-%i.0000") if self.BANDS == 2: if (_vhf_upper + 1) * 1000000 <= value and \ value < _uhf_lower * 1000000: raise InvalidValueError(msg % (_vhf_upper, _uhf_lower)) if self.BANDS == 3: if (_vhf_upper + 1) * 1000000 <= value and \ value < _vhf2_lower * 1000000: raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower)) if (_vhf2_upper + 1) * 1000000 <= value and \ value < _uhf_lower * 1000000: raise InvalidValueError(msg % (_vhf2_upper, _uhf_lower)) if self.BANDS == 4: if (_vhf_upper + 1) * 1000000 <= value and \ value < _vhf2_lower * 1000000: raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower)) if (_vhf2_upper + 1) * 1000000 <= value and \ value < _uhf2_lower * 1000000: raise InvalidValueError(msg % (_vhf2_upper, _uhf2_lower)) if (_uhf2_upper + 1) * 1000000 <= value and \ value < _uhf_lower * 1000000: raise InvalidValueError(msg % (_uhf2_upper, _uhf_lower)) msg = ("Can't be greater then %i.9975") if value > 99000000 and value >= _uhf_upper * 1000000: raise InvalidValueError(msg % (_uhf_upper)) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq( _mem.vfo.a.freq)) val1a.set_validate_callback(my_validate) vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a) vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a) work.append(vfoafreq) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq( _mem.vfo.b.freq)) val1b.set_validate_callback(my_validate) vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b) vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b) work.append(vfobfreq) if self.COLOR_LCD: val1c = RadioSettingValueString(0, 10, convert_bytes_to_freq( _mem.vfo.c.freq)) val1c.set_validate_callback(my_validate) vfocfreq = RadioSetting("vfo.c.freq", "VFO C frequency", val1c) vfocfreq.set_apply_callback(apply_freq, _mem.vfo.c) work.append(vfocfreq) val1d = RadioSettingValueString(0, 10, convert_bytes_to_freq( _mem.vfo.d.freq)) val1d.set_validate_callback(my_validate) vfodfreq = RadioSetting("vfo.d.freq", "VFO D frequency", val1d) vfodfreq.set_apply_callback(apply_freq, _mem.vfo.d) work.append(vfodfreq) vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift", RadioSettingValueList( LIST_SHIFT, LIST_SHIFT[_mem.vfo.a.shiftd])) work.append(vfoashiftd) vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift", RadioSettingValueList( LIST_SHIFT, LIST_SHIFT[_mem.vfo.b.shiftd])) work.append(vfobshiftd) if self.COLOR_LCD: vfocshiftd = RadioSetting("vfo.c.shiftd", "VFO C shift", RadioSettingValueList( LIST_SHIFT, LIST_SHIFT[_mem.vfo.c.shiftd])) work.append(vfocshiftd) vfodshiftd = RadioSetting("vfo.d.shiftd", "VFO D shift", RadioSettingValueList( LIST_SHIFT, LIST_SHIFT[_mem.vfo.d.shiftd])) work.append(vfodshiftd) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 1000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 1000 for i in range(5, -1, -1): obj.offset[i] = value % 10 value /= 10 if self.COLOR_LCD: val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.a.offset)) vfoaoffset = RadioSetting("vfo.a.offset", "VFO A offset (0.000-999.999)", val1a) vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a) work.append(vfoaoffset) val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.b.offset)) vfoboffset = RadioSetting("vfo.b.offset", "VFO B offset (0.000-999.999)", val1b) vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b) work.append(vfoboffset) val1c = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.c.offset)) vfocoffset = RadioSetting("vfo.c.offset", "VFO C offset (0.000-999.999)", val1c) vfocoffset.set_apply_callback(apply_offset, _mem.vfo.c) work.append(vfocoffset) val1d = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.d.offset)) vfodoffset = RadioSetting("vfo.d.offset", "VFO D offset (0.000-999.999)", val1d) vfodoffset.set_apply_callback(apply_offset, _mem.vfo.d) work.append(vfodoffset) else: val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.a.offset)) vfoaoffset = RadioSetting("vfo.a.offset", "VFO A offset (0.000-99.999)", val1a) vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a) work.append(vfoaoffset) val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset( _mem.vfo.b.offset)) vfoboffset = RadioSetting("vfo.b.offset", "VFO B offset (0.000-99.999)", val1b) vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b) work.append(vfoboffset) vfoatxp = RadioSetting("vfo.a.power", "VFO A power", RadioSettingValueList( LIST_TXP, LIST_TXP[_mem.vfo.a.power])) work.append(vfoatxp) vfobtxp = RadioSetting("vfo.b.power", "VFO B power", RadioSettingValueList( LIST_TXP, LIST_TXP[_mem.vfo.b.power])) work.append(vfobtxp) if self.COLOR_LCD: vfoctxp = RadioSetting("vfo.c.power", "VFO C power", RadioSettingValueList( LIST_TXP, LIST_TXP[_mem.vfo.c.power])) work.append(vfoctxp) vfodtxp = RadioSetting("vfo.d.power", "VFO D power", RadioSettingValueList( LIST_TXP, LIST_TXP[_mem.vfo.d.power])) work.append(vfodtxp) vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth", RadioSettingValueList( LIST_WIDE, LIST_WIDE[_mem.vfo.a.wide])) work.append(vfoawide) vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth", RadioSettingValueList( LIST_WIDE, LIST_WIDE[_mem.vfo.b.wide])) work.append(vfobwide) if self.COLOR_LCD: vfocwide = RadioSetting("vfo.c.wide", "VFO C bandwidth", RadioSettingValueList( LIST_WIDE, LIST_WIDE[_mem.vfo.c.wide])) work.append(vfocwide) vfodwide = RadioSetting("vfo.d.wide", "VFO D bandwidth", RadioSettingValueList( LIST_WIDE, LIST_WIDE[_mem.vfo.d.wide])) work.append(vfodwide) vfoastep = RadioSetting("vfo.a.step", "VFO A step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.a.step])) work.append(vfoastep) vfobstep = RadioSetting("vfo.b.step", "VFO B step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.b.step])) work.append(vfobstep) if self.COLOR_LCD: vfocstep = RadioSetting("vfo.c.step", "VFO C step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.c.step])) work.append(vfocstep) vfodstep = RadioSetting("vfo.d.step", "VFO D step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.d.step])) work.append(vfodstep) vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal", RadioSettingValueList( OPTSIG_LIST, OPTSIG_LIST[_mem.vfo.a.optsig])) work.append(vfoaoptsig) vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal", RadioSettingValueList( OPTSIG_LIST, OPTSIG_LIST[_mem.vfo.b.optsig])) work.append(vfoboptsig) if self.COLOR_LCD: vfocoptsig = RadioSetting("vfo.c.optsig", "VFO C optional signal", RadioSettingValueList( OPTSIG_LIST, OPTSIG_LIST[_mem.vfo.c.optsig])) work.append(vfocoptsig) vfodoptsig = RadioSetting("vfo.d.optsig", "VFO D optional signal", RadioSettingValueList( OPTSIG_LIST, OPTSIG_LIST[_mem.vfo.d.optsig])) work.append(vfodoptsig) vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_mem.vfo.a.spmute])) work.append(vfoaspmute) vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_mem.vfo.b.spmute])) work.append(vfobspmute) if self.COLOR_LCD: vfocspmute = RadioSetting("vfo.c.spmute", "VFO C speaker mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_mem.vfo.c.spmute])) work.append(vfocspmute) vfodspmute = RadioSetting("vfo.d.spmute", "VFO D speaker mute", RadioSettingValueList( SPMUTE_LIST, SPMUTE_LIST[_mem.vfo.d.spmute])) work.append(vfodspmute) if not self.COLOR_LCD or \ (self.COLOR_LCD and not self.VENDOR == "BTECH"): vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble", RadioSettingValueBoolean( _mem.vfo.a.scramble)) work.append(vfoascr) vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble", RadioSettingValueBoolean( _mem.vfo.b.scramble)) work.append(vfobscr) if self.COLOR_LCD and not self.VENDOR == "BTECH": vfocscr = RadioSetting("vfo.c.scramble", "VFO C scramble", RadioSettingValueBoolean( _mem.vfo.c.scramble)) work.append(vfocscr) vfodscr = RadioSetting("vfo.d.scramble", "VFO D scramble", RadioSettingValueBoolean( _mem.vfo.d.scramble)) work.append(vfodscr) vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.vfo.a.scode])) work.append(vfoascode) vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.vfo.b.scode])) work.append(vfobscode) if self.COLOR_LCD: vfocscode = RadioSetting("vfo.c.scode", "VFO C PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.vfo.c.scode])) work.append(vfocscode) vfodscode = RadioSetting("vfo.d.scode", "VFO D PTT-ID", RadioSettingValueList( PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.vfo.d.scode])) work.append(vfodscode) pttid = RadioSetting("settings.pttid", "PTT ID", RadioSettingValueList( PTTID_LIST, PTTID_LIST[_mem.settings.pttid])) work.append(pttid) if not self.COLOR_LCD: #FM presets fm_presets = RadioSettingGroup("fm_presets", "FM Presets") top.append(fm_presets) def fm_validate(value): if value == 0: return chirp_common.format_freq(value) if not (87.5 <= value and value <= 108.0): # 87.5-108MHz msg = ("FM-Preset-Frequency: Must be between 87.5 and 108 MHz") raise InvalidValueError(msg) return value def apply_fm_preset_name(setting, obj): valstring = str (setting.value) for i in range(0,6): if valstring[i] in VALID_CHARS: obj[i] = valstring[i] else: obj[i] = '0xff' def apply_fm_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 _presets = self._memobj.fm_radio_preset i = 1 for preset in _presets: line = RadioSetting("fm_presets_"+ str(i), "Station name " + str(i), RadioSettingValueString(0, 6, _filter( preset.broadcast_station_name))) line.set_apply_callback(apply_fm_preset_name, preset.broadcast_station_name) val = RadioSettingValueFloat(0, 108, convert_bytes_to_freq( preset.freq)) fmfreq = RadioSetting("fm_presets_"+ str(i) + "_freq", "Frequency "+ str(i), val) val.set_validate_callback(fm_validate) fmfreq.set_apply_callback(apply_fm_freq, preset) fm_presets.append(line) fm_presets.append(fmfreq) i = i + 1 # DTMF-Setting dtmf_enc_settings = RadioSettingGroup ("dtmf_enc_settings", "DTMF Encoding Settings") dtmf_dec_settings = RadioSettingGroup ("dtmf_dec_settings", "DTMF Decoding Settings") top.append(dtmf_enc_settings) top.append(dtmf_dec_settings) txdisable = RadioSetting("dtmf_settings.txdisable", "TX-Disable", RadioSettingValueBoolean( _mem.dtmf_settings.txdisable)) dtmf_enc_settings.append(txdisable) rxdisable = RadioSetting("dtmf_settings.rxdisable", "RX-Disable", RadioSettingValueBoolean( _mem.dtmf_settings.rxdisable)) dtmf_enc_settings.append(rxdisable) dtmfspeed_on = RadioSetting( "dtmf_settings.dtmfspeed_on", "DTMF Speed (On Time)", RadioSettingValueList(LIST_DTMF_SPEED, LIST_DTMF_SPEED[ _mem.dtmf_settings.dtmfspeed_on])) dtmf_enc_settings.append(dtmfspeed_on) dtmfspeed_off = RadioSetting( "dtmf_settings.dtmfspeed_off", "DTMF Speed (Off Time)", RadioSettingValueList(LIST_DTMF_SPEED, LIST_DTMF_SPEED[ _mem.dtmf_settings.dtmfspeed_off])) dtmf_enc_settings.append(dtmfspeed_off) def memory2string(dmtf_mem): dtmf_string = "" for digit in dmtf_mem: if digit != 255: index = LIST_DTMF_VALUES.index(digit) dtmf_string = dtmf_string + LIST_DTMF_DIGITS[index] return dtmf_string def apply_dmtf_frame(setting, obj): LOG.debug("Setting DTMF-Code: " + str(setting.value) ) val_string = str(setting.value) for i in range(0,16): obj[i] = 255 i = 0 for current_char in val_string: current_char = current_char.upper() index = LIST_DTMF_DIGITS.index(current_char) obj[i] = LIST_DTMF_VALUES[ index ] i = i + 1 codes = self._memobj.dtmf_codes i = 1 for dtmfcode in codes: val = RadioSettingValueString(0, 16, memory2string(dtmfcode.code), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_code_" + str(i) + "_code", "DMTF Code " + str(i), val) line.set_apply_callback(apply_dmtf_frame, dtmfcode.code) dtmf_enc_settings.append(line) i = i + 1 line = RadioSetting("dtmf_settings.mastervice", "Master and Vice ID", RadioSettingValueBoolean( _mem.dtmf_settings.mastervice)) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.masterid), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.masterid", "Master Control ID ", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.masterid) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.minspection", "Master Inspection", RadioSettingValueBoolean( _mem.dtmf_settings.minspection)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.mmonitor", "Master Monitor", RadioSettingValueBoolean( _mem.dtmf_settings.mmonitor)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.mstun", "Master Stun", RadioSettingValueBoolean( _mem.dtmf_settings.mstun)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.mkill", "Master Kill", RadioSettingValueBoolean( _mem.dtmf_settings.mkill)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.mrevive", "Master Revive", RadioSettingValueBoolean( _mem.dtmf_settings.mrevive)) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.viceid), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.viceid", "Vice Control ID ", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.viceid) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.vinspection", "Vice Inspection", RadioSettingValueBoolean( _mem.dtmf_settings.vinspection)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.vmonitor", "Vice Monitor", RadioSettingValueBoolean( _mem.dtmf_settings.vmonitor)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.vstun", "Vice Stun", RadioSettingValueBoolean( _mem.dtmf_settings.vstun)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.vkill", "Vice Kill", RadioSettingValueBoolean( _mem.dtmf_settings.vkill)) dtmf_dec_settings.append(line) line = RadioSetting("dtmf_settings.vrevive", "Vice Revive", RadioSettingValueBoolean( _mem.dtmf_settings.vrevive)) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.inspection), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.inspection", "Inspection", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.inspection) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.alarmcode), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.alarmcode", "Alarm", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.alarmcode) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.kill), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.kill", "Kill", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.kill) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.monitor), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.monitor", "Monitor", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.monitor) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.stun), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.stun", "Stun", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.stun) dtmf_dec_settings.append(line) val = RadioSettingValueString(0, 16, memory2string( _mem.dtmf_settings.revive), False, CHARSET_DTMF_DIGITS) line = RadioSetting("dtmf_settings.revive", "Revive", val) line.set_apply_callback(apply_dmtf_frame, _mem.dtmf_settings.revive) dtmf_dec_settings.append(line) def apply_dmtf_listvalue(setting, obj): LOG.debug("Setting value: "+ str(setting.value) + " from list") val = str(setting.value) index = LIST_DTMF_SPECIAL_DIGITS.index(val) val = LIST_DTMF_SPECIAL_VALUES[index] obj.set_value(val) idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.groupcode) line = RadioSetting( "dtmf_settings.groupcode", "Group Code", RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS, LIST_DTMF_SPECIAL_DIGITS[idx])) line.set_apply_callback(apply_dmtf_listvalue, _mem.dtmf_settings.groupcode) dtmf_dec_settings.append(line) idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.spacecode) line = RadioSetting( "dtmf_settings.spacecode", "Space Code", RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS, LIST_DTMF_SPECIAL_DIGITS[idx])) line.set_apply_callback(apply_dmtf_listvalue, _mem.dtmf_settings.spacecode) dtmf_dec_settings.append(line) if self.COLOR_LCD: line = RadioSetting( "dtmf_settings.resettime", "Reset time", RadioSettingValueList(LIST_5TONE_RESET_COLOR, LIST_5TONE_RESET_COLOR[ _mem.dtmf_settings.resettime])) dtmf_dec_settings.append(line) else: line = RadioSetting( "dtmf_settings.resettime", "Reset time", RadioSettingValueList(LIST_5TONE_RESET, LIST_5TONE_RESET[ _mem.dtmf_settings.resettime])) dtmf_dec_settings.append(line) line = RadioSetting( "dtmf_settings.delayproctime", "Delay processing time", RadioSettingValueList(LIST_DTMF_DELAY, LIST_DTMF_DELAY[ _mem.dtmf_settings.delayproctime])) dtmf_dec_settings.append(line) # 5 Tone Settings stds_5tone = RadioSettingGroup ("stds_5tone", "Standards") codes_5tone = RadioSettingGroup ("codes_5tone", "Codes") group_5tone = RadioSettingGroup ("group_5tone", "5 Tone Settings") group_5tone.append(stds_5tone) group_5tone.append(codes_5tone) top.append(group_5tone) def apply_list_value(setting, obj): options = setting.value.get_options() obj.set_value ( options.index(str(setting.value)) ) _5tone_standards = self._memobj._5tone_std_settings i = 0 for standard in _5tone_standards: std_5tone = RadioSettingGroup ("std_5tone_" + str(i), LIST_5TONE_STANDARDS[i]) stds_5tone.append(std_5tone) period = standard.period if period == 255: LOG.debug("Period for " + LIST_5TONE_STANDARDS[i] + " is not yet configured. Setting to 70ms.") period = 5 if period <= len( LIST_5TONE_STANDARD_PERIODS ): line = RadioSetting( "_5tone_std_settings_" + str(i) + "_period", "Period (ms)", RadioSettingValueList (LIST_5TONE_STANDARD_PERIODS, LIST_5TONE_STANDARD_PERIODS[period])) line.set_apply_callback(apply_list_value, standard.period) std_5tone.append(line) else: LOG.debug("Invalid value for 5tone period! Disabling.") group_tone = standard.group_tone if group_tone == 255: LOG.debug("Group-Tone for " + LIST_5TONE_STANDARDS[i] + " is not yet configured. Setting to A.") group_tone = 10 if group_tone <= len( LIST_5TONE_DIGITS ): line = RadioSetting( "_5tone_std_settings_" + str(i) + "_grouptone", "Group Tone", RadioSettingValueList(LIST_5TONE_DIGITS, LIST_5TONE_DIGITS[ group_tone])) line.set_apply_callback(apply_list_value, standard.group_tone) std_5tone.append(line) else: LOG.debug("Invalid value for 5tone digit! Disabling.") repeat_tone = standard.repeat_tone if repeat_tone == 255: LOG.debug("Repeat-Tone for " + LIST_5TONE_STANDARDS[i] + " is not yet configured. Setting to E.") repeat_tone = 14 if repeat_tone <= len( LIST_5TONE_DIGITS ): line = RadioSetting( "_5tone_std_settings_" + str(i) + "_repttone", "Repeat Tone", RadioSettingValueList(LIST_5TONE_DIGITS, LIST_5TONE_DIGITS[ repeat_tone])) line.set_apply_callback(apply_list_value, standard.repeat_tone) std_5tone.append(line) else: LOG.debug("Invalid value for 5tone digit! Disabling.") i = i + 1 def my_apply_5tonestdlist_value(setting, obj): if LIST_5TONE_STANDARDS.index(str(setting.value)) == 15: obj.set_value(0xFF) else: obj.set_value( LIST_5TONE_STANDARDS. index(str(setting.value)) ) def apply_5tone_frame(setting, obj): LOG.debug("Setting 5 Tone: " + str(setting.value) ) valstring = str(setting.value) if len(valstring) == 0: for i in range(0,5): obj[i] = 255 else: validFrame = True for i in range(0,5): currentChar = valstring[i].upper() if currentChar in LIST_5TONE_DIGITS: obj[i] = LIST_5TONE_DIGITS.index(currentChar) else: validFrame = False LOG.debug("invalid char: " + str(currentChar)) if not validFrame: LOG.debug("setting whole frame to FF" ) for i in range(0,5): obj[i] = 255 def validate_5tone_frame(value): if (len(str(value)) != 5) and (len(str(value)) != 0) : msg = ("5 Tone must have 5 digits or 0 digits") raise InvalidValueError(msg) for digit in str(value): if digit.upper() not in LIST_5TONE_DIGITS: msg = (str(digit) + " is not a valid digit for 5tones") raise InvalidValueError(msg) return value def frame2string(frame): frameString = "" for digit in frame: if digit != 255: frameString = frameString + LIST_5TONE_DIGITS[digit] return frameString _5tone_codes = self._memobj._5tone_codes i = 1 for code in _5tone_codes: code_5tone = RadioSettingGroup ("code_5tone_" + str(i), "5 Tone code " + str(i)) codes_5tone.append(code_5tone) if (code.standard == 255 ): currentVal = 15 else: currentVal = code.standard line = RadioSetting("_5tone_code_" + str(i) + "_std", " Standard", RadioSettingValueList(LIST_5TONE_STANDARDS, LIST_5TONE_STANDARDS[ currentVal]) ) line.set_apply_callback(my_apply_5tonestdlist_value, code.standard) code_5tone.append(line) val = RadioSettingValueString(0, 6, frame2string(code.frame1), False) line = RadioSetting("_5tone_code_" + str(i) + "_frame1", " Frame 1", val) val.set_validate_callback(validate_5tone_frame) line.set_apply_callback(apply_5tone_frame, code.frame1) code_5tone.append(line) val = RadioSettingValueString(0, 6, frame2string(code.frame2), False) line = RadioSetting("_5tone_code_" + str(i) + "_frame2", " Frame 2", val) val.set_validate_callback(validate_5tone_frame) line.set_apply_callback(apply_5tone_frame, code.frame2) code_5tone.append(line) val = RadioSettingValueString(0, 6, frame2string(code.frame3), False) line = RadioSetting("_5tone_code_" + str(i) + "_frame3", " Frame 3", val) val.set_validate_callback(validate_5tone_frame) line.set_apply_callback(apply_5tone_frame, code.frame3) code_5tone.append(line) i = i + 1 _5_tone_decode1 = RadioSetting( "_5tone_settings._5tone_decode_call_frame1", "5 Tone decode call Frame 1", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_call_frame1)) group_5tone.append(_5_tone_decode1) _5_tone_decode2 = RadioSetting( "_5tone_settings._5tone_decode_call_frame2", "5 Tone decode call Frame 2", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_call_frame2)) group_5tone.append(_5_tone_decode2) _5_tone_decode3 = RadioSetting( "_5tone_settings._5tone_decode_call_frame3", "5 Tone decode call Frame 3", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_call_frame3)) group_5tone.append(_5_tone_decode3) _5_tone_decode_disp1 = RadioSetting( "_5tone_settings._5tone_decode_disp_frame1", "5 Tone decode disp Frame 1", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_disp_frame1)) group_5tone.append(_5_tone_decode_disp1) _5_tone_decode_disp2 = RadioSetting( "_5tone_settings._5tone_decode_disp_frame2", "5 Tone decode disp Frame 2", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_disp_frame2)) group_5tone.append(_5_tone_decode_disp2) _5_tone_decode_disp3 = RadioSetting( "_5tone_settings._5tone_decode_disp_frame3", "5 Tone decode disp Frame 3", RadioSettingValueBoolean( _mem._5tone_settings._5tone_decode_disp_frame3)) group_5tone.append(_5_tone_decode_disp3) decode_standard = _mem._5tone_settings.decode_standard if decode_standard == 255: decode_standard = 0 if decode_standard <= len (LIST_5TONE_STANDARDS_without_none) : line = RadioSetting("_5tone_settings.decode_standard", "5 Tone-decode Standard", RadioSettingValueList( LIST_5TONE_STANDARDS_without_none, LIST_5TONE_STANDARDS_without_none[ decode_standard])) group_5tone.append(line) else: LOG.debug("Invalid decode std...") _5tone_delay1 = _mem._5tone_settings._5tone_delay1 if _5tone_delay1 == 255: _5tone_delay1 = 20 if _5tone_delay1 <= len( LIST_5TONE_DELAY ): list = RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ _5tone_delay1]) line = RadioSetting("_5tone_settings._5tone_delay1", "5 Tone Delay Frame 1", list) group_5tone.append(line) else: LOG.debug("Invalid value for 5tone delay (frame1) ! Disabling.") _5tone_delay2 = _mem._5tone_settings._5tone_delay2 if _5tone_delay2 == 255: _5tone_delay2 = 20 LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.") if _5tone_delay2 <= len( LIST_5TONE_DELAY ): list = RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ _5tone_delay2]) line = RadioSetting("_5tone_settings._5tone_delay2", "5 Tone Delay Frame 2", list) group_5tone.append(line) else: LOG.debug("Invalid value for 5tone delay (frame2)! Disabling.") _5tone_delay3 = _mem._5tone_settings._5tone_delay3 if _5tone_delay3 == 255: _5tone_delay3 = 20 LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.") if _5tone_delay3 <= len( LIST_5TONE_DELAY ): list = RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ _5tone_delay3]) line = RadioSetting("_5tone_settings._5tone_delay3", "5 Tone Delay Frame 3", list ) group_5tone.append(line) else: LOG.debug("Invalid value for 5tone delay (frame3)! Disabling.") ext_length = _mem._5tone_settings._5tone_first_digit_ext_length if ext_length == 255: ext_length = 0 LOG.debug("1st Tone ext lenght unconfigured! Resetting to 0") if ext_length <= len( LIST_5TONE_DELAY ): list = RadioSettingValueList( LIST_5TONE_DELAY, LIST_5TONE_DELAY[ ext_length]) line = RadioSetting( "_5tone_settings._5tone_first_digit_ext_length", "First digit extend length", list) group_5tone.append(line) else: LOG.debug("Invalid value for 5tone ext length! Disabling.") decode_reset_time = _mem._5tone_settings.decode_reset_time if decode_reset_time == 255: decode_reset_time = 59 LOG.debug("Decode reset time unconfigured. resetting.") if decode_reset_time <= len(LIST_5TONE_RESET): list = RadioSettingValueList( LIST_5TONE_RESET, LIST_5TONE_RESET[ decode_reset_time]) line = RadioSetting("_5tone_settings.decode_reset_time", "Decode reset time", list) group_5tone.append(line) else: LOG.debug("Invalid value decode reset time! Disabling.") # 2 Tone encode_2tone = RadioSettingGroup ("encode_2tone", "2 Tone Encode") decode_2tone = RadioSettingGroup ("decode_2tone", "2 Code Decode") top.append(encode_2tone) top.append(decode_2tone) duration_1st_tone = self._memobj._2tone.duration_1st_tone if duration_1st_tone == 255: LOG.debug("Duration of first 2 Tone digit is not yet " + "configured. Setting to 600ms") duration_1st_tone = 60 if duration_1st_tone <= len( LIST_5TONE_DELAY ): line = RadioSetting("_2tone.duration_1st_tone", "Duration 1st Tone", RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ duration_1st_tone])) encode_2tone.append(line) duration_2nd_tone = self._memobj._2tone.duration_2nd_tone if duration_2nd_tone == 255: LOG.debug("Duration of second 2 Tone digit is not yet " + "configured. Setting to 600ms") duration_2nd_tone = 60 if duration_2nd_tone <= len( LIST_5TONE_DELAY ): line = RadioSetting("_2tone.duration_2nd_tone", "Duration 2nd Tone", RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ duration_2nd_tone])) encode_2tone.append(line) duration_gap = self._memobj._2tone.duration_gap if duration_gap == 255: LOG.debug("Duration of gap is not yet " + "configured. Setting to 300ms") duration_gap = 30 if duration_gap <= len( LIST_5TONE_DELAY ): line = RadioSetting("_2tone.duration_gap", "Duration of gap", RadioSettingValueList(LIST_5TONE_DELAY, LIST_5TONE_DELAY[ duration_gap])) encode_2tone.append(line) def _2tone_validate(value): if value == 0: return 65535 if value == 65535: return value if not (300 <= value and value <= 3000): msg = ("2 Tone Frequency: Must be between 300 and 3000 Hz") raise InvalidValueError(msg) return value def apply_2tone_freq(setting, obj): val = int(setting.value) if (val == 0) or (val == 65535): obj.set_value(65535) else: obj.set_value(val) i = 1 for code in self._memobj._2tone._2tone_encode: code_2tone = RadioSettingGroup ("code_2tone_" + str(i), "Encode Code " + str(i)) encode_2tone.append(code_2tone) tmp = code.freq1 if tmp == 65535: tmp = 0 val1 = RadioSettingValueInteger(0, 65535, tmp) freq1 = RadioSetting("2tone_code_"+ str(i) + "_freq1", "Frequency 1", val1) val1.set_validate_callback(_2tone_validate) freq1.set_apply_callback(apply_2tone_freq, code.freq1) code_2tone.append(freq1) tmp = code.freq2 if tmp == 65535: tmp = 0 val2 = RadioSettingValueInteger(0, 65535, tmp) freq2 = RadioSetting("2tone_code_"+ str(i) + "_freq2", "Frequency 2", val2) val2.set_validate_callback(_2tone_validate) freq2.set_apply_callback(apply_2tone_freq, code.freq2) code_2tone.append(freq2) i = i + 1 decode_reset_time = _mem._2tone.reset_time if decode_reset_time == 255: decode_reset_time = 59 LOG.debug("Decode reset time unconfigured. resetting.") if decode_reset_time <= len(LIST_5TONE_RESET): list = RadioSettingValueList( LIST_5TONE_RESET, LIST_5TONE_RESET[ decode_reset_time]) line = RadioSetting("_2tone.reset_time", "Decode reset time", list) decode_2tone.append(line) else: LOG.debug("Invalid value decode reset time! Disabling.") def apply_2tone_freq_pair(setting, obj): val = int(setting.value) derived_val = 65535 frqname = str(setting._name[-5:]) derivedname = "derived_from_" + frqname if (val == 0): val = 65535 derived_val = 65535 else: derived_val = int(round(2304000.0/val)) obj[frqname].set_value( val ) obj[derivedname].set_value( derived_val ) LOG.debug("Apply " + frqname + ": " + str(val) + " | " + derivedname + ": " + str(derived_val)) i = 1 for decode_code in self._memobj._2tone._2tone_decode: _2tone_dec_code = RadioSettingGroup ("code_2tone_" + str(i), "Decode Code " + str(i)) decode_2tone.append(_2tone_dec_code) j = 1 for dec in decode_code.decs: val = dec.dec if val == 255: LOG.debug("Dec for Code " + str(i) + " Dec " + str(j) + " is not yet configured. Setting to 0.") val = 0 if val <= len( LIST_2TONE_DEC ): line = RadioSetting( "_2tone_dec_settings_" + str(i) + "_dec_" + str(j), "Dec " + str(j), RadioSettingValueList (LIST_2TONE_DEC, LIST_2TONE_DEC[val])) line.set_apply_callback(apply_list_value, dec.dec) _2tone_dec_code.append(line) else: LOG.debug("Invalid value for 2tone dec! Disabling.") val = dec.response if val == 255: LOG.debug("Response for Code " + str(i) + " Dec " + str(j)+ " is not yet configured. Setting to 0.") val = 0 if val <= len( LIST_2TONE_RESPONSE ): line = RadioSetting( "_2tone_dec_settings_" + str(i) + "_resp_" + str(j), "Response " + str(j), RadioSettingValueList (LIST_2TONE_RESPONSE, LIST_2TONE_RESPONSE[val])) line.set_apply_callback(apply_list_value, dec.response) _2tone_dec_code.append(line) else: LOG.debug("Invalid value for 2tone response! Disabling.") val = dec.alert if val == 255: LOG.debug("Alert for Code " + str(i) + " Dec " + str(j) + " is not yet configured. Setting to 0.") val = 0 if val <= len( PTTIDCODE_LIST ): line = RadioSetting( "_2tone_dec_settings_" + str(i) + "_alert_" + str(j), "Alert " + str(j), RadioSettingValueList (PTTIDCODE_LIST, PTTIDCODE_LIST[val])) line.set_apply_callback(apply_list_value, dec.alert) _2tone_dec_code.append(line) else: LOG.debug("Invalid value for 2tone alert! Disabling.") j = j + 1 freq = self._memobj._2tone.freqs[i-1] for char in ['A', 'B', 'C', 'D']: setting_name = "freq" + str(char) tmp = freq[setting_name] if tmp == 65535: tmp = 0 if tmp != 0: expected = int(round(2304000.0/tmp)) from_mem = freq["derived_from_" + setting_name] if expected != from_mem: LOG.error("Expected " + str(expected) + " but read " + str(from_mem ) + ". Disabling 2Tone Decode Freqs!") break val = RadioSettingValueInteger(0, 65535, tmp) frq = RadioSetting("2tone_dec_"+ str(i) + "_freq" + str(char), ("Decode Frequency " +str(char)), val) val.set_validate_callback(_2tone_validate) frq.set_apply_callback(apply_2tone_freq_pair, freq) _2tone_dec_code.append(frq) i = i + 1 return top def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown0:4, scode:4; u8 unknown1:2, spmute:1, unknown2:3, optsig:2; u8 unknown3:3, scramble:1, unknown4:3, power:1; u8 unknown5:1, wide:1, unknown6:2, bcl:1, add:1, pttid:2; } memory[200]; #seekto 0x0E00; struct { u8 tdr; u8 unknown1; u8 sql; u8 unknown2[2]; u8 tot; u8 apo; // BTech radios use this as the Auto Power Off time // other radios use this as pre-Time Out Alert u8 unknown3; u8 abr; u8 beep; u8 unknown4[4]; u8 dtmfst; u8 unknown5[2]; u8 prisc; u8 prich; u8 screv; u8 unknown6[2]; u8 pttid; u8 pttlt; u8 unknown7; u8 emctp; u8 emcch; u8 ringt; u8 unknown8; u8 camdf; u8 cbmdf; u8 sync; // BTech radios use this as the display sync setting // other radios use this as the auto keypad lock setting u8 ponmsg; u8 wtled; u8 rxled; u8 txled; u8 unknown9[5]; u8 anil; u8 reps; u8 repm; u8 tdrab; u8 ste; u8 rpste; u8 rptdl; u8 mgain; u8 dtmfg; } settings; #seekto 0x0E80; struct { u8 unknown1; u8 vfomr; u8 keylock; u8 unknown2; u8 unknown3:4, vfomren:1, unknown4:1, reseten:1, menuen:1; u8 unknown5[11]; u8 dispab; u8 mrcha; u8 mrchb; u8 menu; } settings2; #seekto 0x0EC0; struct { char line1[6]; char line2[6]; } poweron_msg; struct settings_vfo { u8 freq[8]; u8 offset[6]; u8 unknown2[2]; ul16 rxtone; ul16 txtone; u8 scode; u8 spmute; u8 optsig; u8 scramble; u8 wide; u8 power; u8 shiftd; u8 step; u8 unknown3[4]; }; #seekto 0x0F00; struct { struct settings_vfo a; struct settings_vfo b; } vfo; #seekto 0x1000; struct { char name[6]; u8 unknown1[10]; } names[200]; #seekto 0x2400; struct { u8 period; // one out of LIST_5TONE_STANDARD_PERIODS u8 group_tone; u8 repeat_tone; u8 unused[13]; } _5tone_std_settings[15]; #seekto 0x2500; struct { u8 frame1[5]; u8 frame2[5]; u8 frame3[5]; u8 standard; // one out of LIST_5TONE_STANDARDS } _5tone_codes[15]; #seekto 0x25F0; struct { u8 _5tone_delay1; // * 10ms u8 _5tone_delay2; // * 10ms u8 _5tone_delay3; // * 10ms u8 _5tone_first_digit_ext_length; u8 unknown1; u8 unknown2; u8 unknown3; u8 unknown4; u8 decode_standard; u8 unknown5:5, _5tone_decode_call_frame3:1, _5tone_decode_call_frame2:1, _5tone_decode_call_frame1:1; u8 unknown6:5, _5tone_decode_disp_frame3:1, _5tone_decode_disp_frame2:1, _5tone_decode_disp_frame1:1; u8 decode_reset_time; // * 100 + 100ms } _5tone_settings; #seekto 0x2900; struct { u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B } dtmf_codes[15]; #seekto 0x29F0; struct { u8 dtmfspeed_on; //list with 50..2000ms in steps of 10 u8 dtmfspeed_off; //list with 50..2000ms in steps of 10 u8 unknown0[14]; u8 inspection[16]; u8 monitor[16]; u8 alarmcode[16]; u8 stun[16]; u8 kill[16]; u8 revive[16]; u8 unknown1[16]; u8 unknown2[16]; u8 unknown3[16]; u8 unknown4[16]; u8 unknown5[16]; u8 unknown6[16]; u8 unknown7[16]; u8 masterid[16]; u8 viceid[16]; u8 unused01:7, mastervice:1; u8 unused02:3, mrevive:1, mkill:1, mstun:1, mmonitor:1, minspection:1; u8 unused03:3, vrevive:1, vkill:1, vstun:1, vmonitor:1, vinspection:1; u8 unused04:6, txdisable:1, rxdisable:1; u8 groupcode; u8 spacecode; u8 delayproctime; // * 100 + 100ms u8 resettime; // * 100 + 100ms } dtmf_settings; #seekto 0x2D00; struct { struct { ul16 freq1; u8 unused01[6]; ul16 freq2; u8 unused02[6]; } _2tone_encode[15]; u8 duration_1st_tone; // *10ms u8 duration_2nd_tone; // *10ms u8 duration_gap; // *10ms u8 unused03[13]; struct { struct { u8 dec; // one out of LIST_2TONE_DEC u8 response; // one out of LIST_2TONE_RESPONSE u8 alert; // 1-16 } decs[4]; u8 unused04[4]; } _2tone_decode[15]; u8 unused05[16]; struct { ul16 freqA; ul16 freqB; ul16 freqC; ul16 freqD; // unknown what those values mean, but they are // derived from configured frequencies ul16 derived_from_freqA; // 2304000/freqA ul16 derived_from_freqB; // 2304000/freqB ul16 derived_from_freqC; // 2304000/freqC ul16 derived_from_freqD; // 2304000/freqD }freqs[15]; u8 reset_time; // * 100 + 100ms - 100-8000ms } _2tone; #seekto 0x3000; struct { u8 freq[8]; char broadcast_station_name[6]; u8 unknown[2]; } fm_radio_preset[16]; #seekto 0x3C90; struct { u8 vhf_low[3]; u8 vhf_high[3]; u8 uhf_low[3]; u8 uhf_high[3]; } ranges; // the UV-2501+220 & KT8900R has different zones for storing ranges #seekto 0x3CD0; struct { u8 vhf_low[3]; u8 vhf_high[3]; u8 unknown1[4]; u8 unknown2[6]; u8 vhf2_low[3]; u8 vhf2_high[3]; u8 unknown3[4]; u8 unknown4[6]; u8 uhf_low[3]; u8 uhf_high[3]; } ranges220; #seekto 0x3F70; struct { char fp[6]; } fingerprint; """ class BTech(BTechMobileCommon): """BTECH's UV-5001 and alike radios""" BANDS = 2 COLOR_LCD = False NAME_LENGTH = 6 def set_options(self): """This is to read the options from the image and set it in the environment, for now just the limits of the freqs in the VHF/UHF ranges""" # setting the correct ranges for each radio type if self.MODEL in ["UV-2501+220", "KT8900R"]: # the model 2501+220 has a segment in 220 # and a different position in the memmap # also the QYT KT8900R ranges = self._memobj.ranges220 else: ranges = self._memobj.ranges # the normal dual bands vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high) uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high) # DEBUG LOG.info("Radio ranges: VHF %d to %d" % vhf) LOG.info("Radio ranges: UHF %d to %d" % uhf) # 220Mhz radios case if self.MODEL in ["UV-2501+220", "KT8900R"]: vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high) LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2) self._220_range = vhf2 # set the class with the real data self._vhf_range = vhf self._uhf_range = uhf def process_mmap(self): """Process the mem map into the mem object""" # Get it self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # load specific parameters from the radio image self.set_options() # Declaring Aliases (Clones of the real radios) class JT2705M(chirp_common.Alias): VENDOR = "Jetstream" MODEL = "JT2705M" class JT6188Mini(chirp_common.Alias): VENDOR = "Juentai" MODEL = "JT-6188 Mini" class JT6188Plus(chirp_common.Alias): VENDOR = "Juentai" MODEL = "JT-6188 Plus" class SSGT890(chirp_common.Alias): VENDOR = "Sainsonic" MODEL = "GT-890" class ZastoneMP300(chirp_common.Alias): VENDOR = "Zastone" MODEL = "MP-300" # real radios @directory.register class UV2501(BTech): """Baofeng Tech UV2501""" MODEL = "UV-2501" _fileid = [UV2501G3_fp, UV2501G2_fp, UV2501pp2_fp, UV2501pp_fp] @directory.register class UV2501_220(BTech): """Baofeng Tech UV2501+220""" MODEL = "UV-2501+220" BANDS = 3 _magic = MSTRING_220 _id2 = UV2501_220pp_id _fileid = [UV2501_220G3_fp, UV2501_220G2_fp, UV2501_220_fp, UV2501_220pp_fp] @directory.register class UV5001(BTech): """Baofeng Tech UV5001""" MODEL = "UV-5001" _fileid = [UV5001G3_fp, UV5001G22_fp, UV5001G2_fp, UV5001alpha_fp, UV5001pp_fp] _power_levels = [chirp_common.PowerLevel("High", watts=50), chirp_common.PowerLevel("Low", watts=10)] @directory.register class MINI8900(BTech): """WACCOM MINI-8900""" VENDOR = "WACCOM" MODEL = "MINI-8900" _magic = MSTRING_MINI8900 _fileid = [MINI8900_fp, ] # Clones ALIASES = [JT6188Plus, ] @directory.register class KTUV980(BTech): """QYT KT-UV980""" VENDOR = "QYT" MODEL = "KT-UV980" _vhf_range = (136000000, 175000000) _uhf_range = (400000000, 481000000) _magic = MSTRING_MINI8900 _fileid = [KTUV980_fp, ] # Clones ALIASES = [JT2705M, ] # Please note that there is a version of this radios that is a clone of the # Waccom Mini8900, maybe an early version? @directory.register class KT9800(BTech): """QYT KT8900""" VENDOR = "QYT" MODEL = "KT8900" _vhf_range = (136000000, 175000000) _uhf_range = (400000000, 481000000) _magic = MSTRING_KT8900 _fileid = [KT8900_fp, KT8900_fp1, KT8900_fp2, KT8900_fp3, KT8900_fp4, KT8900_fp5] _id2 = KT8900_id # Clones ALIASES = [JT6188Mini, SSGT890, ZastoneMP300] @directory.register class KT9800R(BTech): """QYT KT8900R""" VENDOR = "QYT" MODEL = "KT8900R" BANDS = 3 _vhf_range = (136000000, 175000000) _220_range = (240000000, 271000000) _uhf_range = (400000000, 481000000) _magic = MSTRING_KT8900R _fileid = [KT8900R_fp, KT8900R_fp1, KT8900R_fp2, KT8900R_fp3, KT8900R_fp4] _id2 = KT8900R_id @directory.register class LT588UV(BTech): """LUITON LT-588UV""" VENDOR = "LUITON" MODEL = "LT-588UV" _vhf_range = (136000000, 175000000) _uhf_range = (400000000, 481000000) _magic = MSTRING_KT8900 _fileid = [LT588UV_fp, LT588UV_fp1] _power_levels = [chirp_common.PowerLevel("High", watts=60), chirp_common.PowerLevel("Low", watts=10)] COLOR_MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown0:4, scode:4; u8 unknown1:2, spmute:1, unknown2:3, optsig:2; u8 unknown3:3, scramble:1, unknown4:3, power:1; u8 unknown5:1, wide:1, unknown6:2, bcl:1, add:1, pttid:2; } memory[200]; #seekto 0x0E00; struct { u8 tmr; u8 unknown1; u8 sql; u8 unknown2[2]; u8 tot; u8 apo; u8 unknown3; u8 abr; u8 beep; u8 unknown4[4]; u8 dtmfst; u8 unknown5[2]; u8 screv; u8 unknown6[2]; u8 pttid; u8 pttlt; u8 unknown7; u8 emctp; u8 emcch; u8 sigbp; u8 unknown8; u8 camdf; u8 cbmdf; u8 ccmdf; u8 cdmdf; u8 langua; u8 sync; // BTech radios use this as the display sync // setting, other radios use this as the auto // keypad lock setting u8 mainfc; u8 mainbc; u8 menufc; u8 menubc; u8 stafc; u8 stabc; u8 sigfc; u8 sigbc; u8 rxfc; u8 txfc; u8 txdisp; u8 unknown9[5]; u8 anil; u8 reps; u8 repm; u8 tmrmr; u8 ste; u8 rpste; u8 rptdl; u8 dtmfg; u8 mgain; u8 skiptx; u8 scmode; } settings; #seekto 0x0E80; struct { u8 unknown1; u8 vfomr; u8 keylock; u8 unknown2; u8 unknown3:4, vfomren:1, unknown4:1, reseten:1, menuen:1; u8 unknown5[11]; u8 dispab; u8 unknown6[2]; u8 menu; u8 unknown7[7]; u8 vfomra; u8 vfomrb; u8 vfomrc; u8 vfomrd; u8 mrcha; u8 mrchb; u8 mrchc; u8 mrchd; } settings2; struct settings_vfo { u8 freq[8]; u8 offset[6]; u8 unknown2[2]; ul16 rxtone; ul16 txtone; u8 scode; u8 spmute; u8 optsig; u8 scramble; u8 wide; u8 power; u8 shiftd; u8 step; u8 unknown3[4]; }; #seekto 0x0F00; struct { struct settings_vfo a; struct settings_vfo b; struct settings_vfo c; struct settings_vfo d; } vfo; #seekto 0x0F80; struct { char line1[8]; char line2[8]; char line3[8]; char line4[8]; char line5[8]; char line6[8]; char line7[8]; char line8[8]; } poweron_msg; #seekto 0x1000; struct { char name[8]; u8 unknown1[8]; } names[200]; #seekto 0x2400; struct { u8 period; // one out of LIST_5TONE_STANDARD_PERIODS u8 group_tone; u8 repeat_tone; u8 unused[13]; } _5tone_std_settings[15]; #seekto 0x2500; struct { u8 frame1[5]; u8 frame2[5]; u8 frame3[5]; u8 standard; // one out of LIST_5TONE_STANDARDS } _5tone_codes[15]; #seekto 0x25F0; struct { u8 _5tone_delay1; // * 10ms u8 _5tone_delay2; // * 10ms u8 _5tone_delay3; // * 10ms u8 _5tone_first_digit_ext_length; u8 unknown1; u8 unknown2; u8 unknown3; u8 unknown4; u8 decode_standard; u8 unknown5:5, _5tone_decode_call_frame3:1, _5tone_decode_call_frame2:1, _5tone_decode_call_frame1:1; u8 unknown6:5, _5tone_decode_disp_frame3:1, _5tone_decode_disp_frame2:1, _5tone_decode_disp_frame1:1; u8 decode_reset_time; // * 100 + 100ms } _5tone_settings; #seekto 0x2900; struct { u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B } dtmf_codes[15]; #seekto 0x29F0; struct { u8 dtmfspeed_on; //list with 50..2000ms in steps of 10 u8 dtmfspeed_off; //list with 50..2000ms in steps of 10 u8 unknown0[14]; u8 inspection[16]; u8 monitor[16]; u8 alarmcode[16]; u8 stun[16]; u8 kill[16]; u8 revive[16]; u8 unknown1[16]; u8 unknown2[16]; u8 unknown3[16]; u8 unknown4[16]; u8 unknown5[16]; u8 unknown6[16]; u8 unknown7[16]; u8 masterid[16]; u8 viceid[16]; u8 unused01:7, mastervice:1; u8 unused02:3, mrevive:1, mkill:1, mstun:1, mmonitor:1, minspection:1; u8 unused03:3, vrevive:1, vkill:1, vstun:1, vmonitor:1, vinspection:1; u8 unused04:6, txdisable:1, rxdisable:1; u8 groupcode; u8 spacecode; u8 delayproctime; // * 100 + 100ms u8 resettime; // * 100 + 100ms } dtmf_settings; #seekto 0x2D00; struct { struct { ul16 freq1; u8 unused01[6]; ul16 freq2; u8 unused02[6]; } _2tone_encode[15]; u8 duration_1st_tone; // *10ms u8 duration_2nd_tone; // *10ms u8 duration_gap; // *10ms u8 unused03[13]; struct { struct { u8 dec; // one out of LIST_2TONE_DEC u8 response; // one out of LIST_2TONE_RESPONSE u8 alert; // 1-16 } decs[4]; u8 unused04[4]; } _2tone_decode[15]; u8 unused05[16]; struct { ul16 freqA; ul16 freqB; ul16 freqC; ul16 freqD; // unknown what those values mean, but they are // derived from configured frequencies ul16 derived_from_freqA; // 2304000/freqA ul16 derived_from_freqB; // 2304000/freqB ul16 derived_from_freqC; // 2304000/freqC ul16 derived_from_freqD; // 2304000/freqD }freqs[15]; u8 reset_time; // * 100 + 100ms - 100-8000ms } _2tone; #seekto 0x3D80; struct { u8 vhf_low[3]; u8 vhf_high[3]; u8 unknown1[4]; u8 unknown2[6]; u8 vhf2_low[3]; u8 vhf2_high[3]; u8 unknown3[4]; u8 unknown4[6]; u8 uhf_low[3]; u8 uhf_high[3]; u8 unknown5[4]; u8 unknown6[6]; u8 uhf2_low[3]; u8 uhf2_high[3]; } ranges; #seekto 0x3F70; struct { char fp[6]; } fingerprint; """ class BTechColor(BTechMobileCommon): """BTECH's Color LCD Mobile and alike radios""" COLOR_LCD = True NAME_LENGTH = 8 def process_mmap(self): """Process the mem map into the mem object""" # Get it self._memobj = bitwise.parse(COLOR_MEM_FORMAT, self._mmap) # load specific parameters from the radio image self.set_options() def set_options(self): """This is to read the options from the image and set it in the environment, for now just the limits of the freqs in the VHF/UHF ranges""" # setting the correct ranges for each radio type ranges = self._memobj.ranges # the normal dual bands vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high) uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high) # DEBUG LOG.info("Radio ranges: VHF %d to %d" % vhf) LOG.info("Radio ranges: UHF %d to %d" % uhf) # the additional bands if self.MODEL in ["UV-25X4", "KT7900D"]: # 200Mhz band vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high) LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2) self._220_range = vhf2 # 350Mhz band uhf2 = _decode_ranges(ranges.uhf2_low, ranges.uhf2_high) LOG.info("Radio ranges: UHF(350) %d to %d" % uhf2) self._350_range = uhf2 # set the class with the real data self._vhf_range = vhf self._uhf_range = uhf # Declaring Aliases (Clones of the real radios) class SKT8900D(chirp_common.Alias): VENDOR = "Surecom" MODEL = "S-KT8900D" # real radios @directory.register class UV25X2(BTechColor): """Baofeng Tech UV25X2""" MODEL = "UV-25X2" BANDS = 2 _vhf_range = (130000000, 180000000) _uhf_range = (400000000, 521000000) _magic = MSTRING_UV25X2 _fileid = [UV25X2_fp, ] @directory.register class UV25X4(BTechColor): """Baofeng Tech UV25X4""" MODEL = "UV-25X4" BANDS = 4 _vhf_range = (130000000, 180000000) _220_range = (200000000, 271000000) _uhf_range = (400000000, 521000000) _350_range = (350000000, 391000000) _magic = MSTRING_UV25X4 _fileid = [UV25X4_fp, ] @directory.register class UV50X2(BTechColor): """Baofeng Tech UV50X2""" MODEL = "UV-50X2" BANDS = 2 _vhf_range = (130000000, 180000000) _uhf_range = (400000000, 521000000) _magic = MSTRING_UV25X2 _fileid = [UV50X2_fp, ] _power_levels = [chirp_common.PowerLevel("High", watts=50), chirp_common.PowerLevel("Low", watts=10)] @directory.register class KT7900D(BTechColor): """QYT KT7900D""" VENDOR = "QYT" MODEL = "KT7900D" BANDS = 4 _vhf_range = (136000000, 175000000) _220_range = (200000000, 271000000) _uhf_range = (400000000, 481000000) _350_range = (350000000, 371000000) _magic = MSTRING_KT8900D _fileid = [KT7900D_fp, ] # Clones ALIASES = [SKT8900D, ] @directory.register class KT8900D(BTechColor): """QYT KT8900D""" VENDOR = "QYT" MODEL = "KT8900D" BANDS = 2 _vhf_range = (136000000, 175000000) _uhf_range = (400000000, 481000000) _magic = MSTRING_KT8900D _fileid = [KT8900D_fp, ] chirp-daily-20170714/chirp/drivers/ic208.py0000644000016101777760000001713212476006422021354 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.drivers import icf from chirp import chirp_common, errors, directory, 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) CHARSET = dict(zip([0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0f], " ()*+-/") + zip(range(0x10, 0x1a), "0123456789") + [(0x1c, '|'), (0x1d, '=')] + zip(range(0x21, 0x3b), "ABCDEFGHIJKLMNOPQRSTUVWXYZ")) CHARSET_REV = dict(zip(CHARSET.values(), CHARSET.keys())) def get_name(_mem): """Decode the name from @_mem""" def _get_char(val): try: return CHARSET[int(val)] except KeyError: return "*" 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): try: return CHARSET_REV[char] except KeyError: return CHARSET_REV["*"] 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, 174000000), (230000000, 550000000), (810000000, 999995000)] rf.valid_special_chans = ["C1", "C2"] + sorted(IC208_SPECIAL) rf.valid_characters = "".join(CHARSET.values()) 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-daily-20170714/chirp/drivers/leixen.py0000644000016101777760000011002213015246222021775 0ustar jenkinsnogroup00000000000000# Copyright 2014 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 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 os import logging from chirp import chirp_common, directory, memmap, errors, util from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0184; struct { u8 unknown:4, sql:4; // squelch level u8 unknown0x0185; u8 obeep:1, // open beep dw_off:1, // dual watch (inverted) kbeep:1, // key beep rbeep:1, // roger beep unknown:2, ctdcsb:1, // ct/dcs busy lock unknown:1; u8 alarm:1, // alarm key unknown1:1, aliasen_off:1, // alias enable (inverted) save:1, // battery save unknown2:2, mrcha:1, // mr/cha vfomr:1; // vfo/mr u8 keylock_off:1, // key lock (inverted) txstop_off:1, // tx stop (inverted) scanm:1, // scan key/mode vir:1, // vox inhibit on receive keylockm:2, // key lock mode lamp:2; // backlight u8 opendis:2, // open display fmen_off:1, // fm enable (inverted) unknown1:1, fmscan_off:1, // fm scan (inverted) fmdw:1, // fm dual watch unknown2:2; u8 step:4, // step vol:4; // volume u8 apo:4, // auto power off tot:4; // time out timer u8 unknown0x018C; u8 voxdt:4, // vox delay time voxgain:4; // vox gain u8 unknown0x018E; u8 unknown0x018F; u8 unknown:3, lptime:5; // long press time u8 keyp2long:4, // p2 key long press keyp2short:4; // p2 key short press u8 keyp1long:4, // p1 key long press keyp1short:4; // p1 key short press u8 keyp3long:4, // p3 key long press keyp3short:4; // p3 key short press u8 unknown0x0194; u8 menuen:1, // menu enable absel:1, // a/b select unknown:2 keymshort:4; // m key short press u8 unknown:4, dtmfst:1, // dtmf sidetone ackdecode:1, // ack decode monitor:2; // monitor u8 unknown1:3, reset:1, // reset enable unknown2:1, keypadmic_off:1, // keypad mic (inverted) unknown3:2; u8 unknown0x0198; u8 unknown1:3, dtmftime:5; // dtmf digit time u8 unknown1:3, dtmfspace:5; // dtmf digit space time u8 unknown1:2, dtmfdelay:6; // dtmf first digit delay u8 unknown1:1, dtmfpretime:7; // dtmf pretime u8 unknown1:2, dtmfdelay2:6; // dtmf * and # digit delay u8 unknown1:3, smfont_off:1, // small font (inverted) unknown:4; } settings; #seekto 0x01cd; struct { u8 rssi136; // squelch base level (vhf) u8 unknown0x01ce; u8 rssi400; // squelch base level (uhf) } service; #seekto 0x0900; struct { char user1[7]; // user message 1 char unknown0x0907; char unknown0x0908[8]; char unknown0x0910[8]; char system[7]; // system message char unknown0x091F; char user2[7]; // user message 2 char unknown0x0927; } messages; struct channel { bbcd rx_freq[4]; bbcd tx_freq[4]; u8 rx_tone; u8 rx_tmode_extra:6, rx_tmode:2; u8 tx_tone; u8 tx_tmode_extra:6, tx_tmode:2; u8 unknown5; u8 pttidoff:1, dtmfoff:1, %(unknownormode)s, tailcut:1, aliasop:1, talkaroundoff:1, voxoff:1, skip:1; u8 %(modeorpower)s, reverseoff:1, blckoff:1, unknown7:1, apro:3; u8 unknown8; }; struct name { char name[7]; u8 pad; }; #seekto 0x%(chanstart)x; struct channel default[%(defaults)i]; struct channel memory[199]; #seekto 0x%(namestart)x; struct name defaultname[%(defaults)i]; struct name name[199]; """ APO_LIST = ["OFF", "10M", "20M", "30M", "40M", "50M", "60M", "90M", "2H", "4H", "6H", "8H", "10H", "12H", "14H", "16H"] SQL_LIST = ["%s" % x for x in range(0, 10)] SCANM_LIST = ["CO", "TO"] TOT_LIST = ["OFF"] + ["%s seconds" % x for x in range(10, 130, 10)] STEP_LIST = ["2.5 KHz", "5 KHz", "6.25 KHz", "10 KHz", "12.5 KHz", "25 KHz"] MONITOR_LIST = ["CTC/DCS", "DTMF", "CTC/DCS and DTMF", "CTC/DCS or DTMF"] VFOMR_LIST = ["MR", "VFO"] MRCHA_LIST = ["MR CHA", "Freq. MR"] VOL_LIST = ["OFF"] + ["%s" % x for x in range(1, 16)] OPENDIS_LIST = ["All", "Lease Time", "User-defined", "Leixen"] LAMP_LIST = ["OFF", "KEY", "CONT"] KEYLOCKM_LIST = ["K+S", "PTT", "KEY", "ALL"] ABSEL_LIST = ["B Channel", "A Channel"] VOXGAIN_LIST = ["%s" % x for x in range(1, 9)] VOXDT_LIST = ["%s seconds" % x for x in range(1, 5)] DTMFTIME_LIST = ["%i milliseconds" % x for x in range(50, 210, 10)] DTMFDELAY_LIST = ["%i milliseconds" % x for x in range(0, 550, 50)] DTMFPRETIME_LIST = ["%i milliseconds" % x for x in range(100, 1100, 100)] DTMFDELAY2_LIST = ["%i milliseconds" % x for x in range(0, 450, 50)] LPTIME_LIST = ["%i milliseconds" % x for x in range(500, 2600, 100)] PFKEYLONG_LIST = ["OFF", "FM", "Monitor Momentary", "Monitor Lock", "SQ Off Momentary", "Mute", "SCAN", "TX Power", "EMG", "VFO/MR", "DTMF", "CALL", "Transmit 1750Hz", "A/B", "Talk Around", "Reverse" ] PFKEYSHORT_LIST = ["OFF", "FM", "BandChange", "Time", "Monitor Lock", "Mute", "SCAN", "TX Power", "EMG", "VFO/MR", "DTMF", "CALL", "Transmit 1750Hz", "A/B", "Talk Around", "Reverse" ] MODES = ["NFM", "FM"] WTFTONES = map(float, xrange(56, 64)) TONES = WTFTONES + chirp_common.TONES DTCS_CODES = [17, 50, 645] + chirp_common.DTCS_CODES DTCS_CODES.sort() TMODES = ["", "Tone", "DTCS", "DTCS"] def _image_ident_from_data(data): return data[0x168:0x178] def _image_ident_from_image(radio): return _image_ident_from_data(radio.get_mmap()) def checksum(frame): x = 0 for b in frame: x ^= ord(b) return chr(x) def make_frame(cmd, addr, data=""): payload = struct.pack(">H", addr) + data header = struct.pack(">BB", ord(cmd), len(payload)) frame = header + payload return frame + checksum(frame) def send(radio, frame): # LOG.debug("%04i P>R: %s" % # (len(frame), # util.hexprint(frame).replace("\n", "\n "))) try: radio.pipe.write(frame) except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) def recv(radio, readdata=True): hdr = radio.pipe.read(4) # LOG.debug("%04i PBBH", hdr) length -= 2 if readdata: data = radio.pipe.read(length) # LOG.debug(" PTone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_characters = chirp_common.CHARSET_ASCII rf.valid_name_length = 7 rf.valid_power_levels = self._power_levels rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_skips = ["", "S"] rf.valid_bands = [(136000000, 174000000), (400000000, 470000000)] rf.memory_bounds = (1, 199) return rf def sync_in(self): try: self._mmap = do_download(self) except Exception, e: finish(self) raise errors.RadioError("Failed to download from radio: %s" % e) self.process_mmap() def process_mmap(self): self._memobj = bitwise.parse( MEM_FORMAT % self._mem_formatter, self._mmap) def sync_out(self): try: do_upload(self) except errors.RadioError: finish(self) raise except Exception, e: raise errors.RadioError("Failed to upload to radio: %s" % e) def get_raw_memory(self, number): name, mem = self._get_memobjs(number) return repr(name) + repr(mem) def _get_tone(self, mem, _mem): rx_tone = tx_tone = None tx_tmode = TMODES[_mem.tx_tmode] rx_tmode = TMODES[_mem.rx_tmode] if tx_tmode == "Tone": tx_tone = TONES[_mem.tx_tone - 1] elif tx_tmode == "DTCS": tx_tone = DTCS_CODES[_mem.tx_tone - 1] if rx_tmode == "Tone": rx_tone = TONES[_mem.rx_tone - 1] elif rx_tmode == "DTCS": rx_tone = DTCS_CODES[_mem.rx_tone - 1] tx_pol = _mem.tx_tmode == 0x03 and "R" or "N" rx_pol = _mem.rx_tmode == 0x03 and "R" or "N" chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol), (rx_tmode, rx_tone, rx_pol)) 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_memobjs(self, number): _mem = self._memobj.memory[number - 1] _name = self._memobj.name[number - 1] return _mem, _name def get_memory(self, number): _mem, _name = self._get_memobjs(number) mem = chirp_common.Memory() mem.number = number if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF": mem.empty = True return mem mem.freq = int(_mem.rx_freq) * 10 if self._is_txinh(_mem): mem.duplex = "off" mem.offset = 0 elif int(_mem.rx_freq) == int(_mem.tx_freq): mem.duplex = "" mem.offset = 0 elif abs(int(_mem.rx_freq) * 10 - int(_mem.tx_freq) * 10) > 70000000: mem.duplex = "split" mem.offset = int(_mem.tx_freq) * 10 else: mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+" mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10 mem.name = str(_name.name).rstrip() self._get_tone(mem, _mem) mem.mode = MODES[_mem.mode] powerindex = _mem.power if _mem.power < len(self._power_levels) else -1 mem.power = self._power_levels[powerindex] mem.skip = _mem.skip and "S" or "" mem.extra = RadioSettingGroup("Extra", "extra") opts = ["On", "Off"] rs = RadioSetting("blckoff", "Busy Channel Lockout", RadioSettingValueList( opts, opts[_mem.blckoff])) mem.extra.append(rs) opts = ["Off", "On"] rs = RadioSetting("tailcut", "Squelch Tail Elimination", RadioSettingValueList( opts, opts[_mem.tailcut])) mem.extra.append(rs) apro = _mem.apro if _mem.apro < 0x5 else 0 opts = ["Off", "Compander", "Scrambler", "TX Scrambler", "RX Scrambler"] rs = RadioSetting("apro", "Audio Processing", RadioSettingValueList( opts, opts[apro])) mem.extra.append(rs) opts = ["On", "Off"] rs = RadioSetting("voxoff", "VOX", RadioSettingValueList( opts, opts[_mem.voxoff])) mem.extra.append(rs) opts = ["On", "Off"] rs = RadioSetting("pttidoff", "PTT ID", RadioSettingValueList( opts, opts[_mem.pttidoff])) mem.extra.append(rs) opts = ["On", "Off"] rs = RadioSetting("dtmfoff", "DTMF", RadioSettingValueList( opts, opts[_mem.dtmfoff])) mem.extra.append(rs) opts = ["Name", "Frequency"] aliasop = RadioSetting("aliasop", "Display", RadioSettingValueList( opts, opts[_mem.aliasop])) mem.extra.append(aliasop) opts = ["On", "Off"] rs = RadioSetting("reverseoff", "Reverse Frequency", RadioSettingValueList( opts, opts[_mem.reverseoff])) mem.extra.append(rs) opts = ["On", "Off"] rs = RadioSetting("talkaroundoff", "Talk Around", RadioSettingValueList( opts, opts[_mem.talkaroundoff])) mem.extra.append(rs) return mem def _set_tone(self, mem, _mem): ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem) _mem.tx_tmode = TMODES.index(txmode) _mem.rx_tmode = TMODES.index(rxmode) if txmode == "Tone": _mem.tx_tone = TONES.index(txtone) + 1 elif txmode == "DTCS": _mem.tx_tmode = txpol == "R" and 0x03 or 0x02 _mem.tx_tone = DTCS_CODES.index(txtone) + 1 if rxmode == "Tone": _mem.rx_tone = TONES.index(rxtone) + 1 elif rxmode == "DTCS": _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02 _mem.rx_tone = DTCS_CODES.index(rxtone) + 1 def set_memory(self, mem): _mem, _name = self._get_memobjs(mem.number) if mem.empty: _mem.set_raw("\xFF" * 16) return elif _mem.get_raw() == ("\xFF" * 16): _mem.set_raw("\xFF" * 8 + "\xFF\x00\xFF\x00\xFF\xFE\xF0\xFC") _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 self._set_tone(mem, _mem) _mem.power = mem.power and self._power_levels.index(mem.power) or 0 _mem.mode = MODES.index(mem.mode) _mem.skip = mem.skip == "S" _name.name = mem.name.ljust(7) # autoset display to name if filled, else show frequency if mem.extra: # mem.extra only seems to be populated when called from edit panel aliasop = mem.extra["aliasop"] else: aliasop = None if mem.name: _mem.aliasop = False if aliasop and not aliasop.changed(): aliasop.value = "Name" else: _mem.aliasop = True if aliasop and not aliasop.changed(): aliasop.value = "Frequency" for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def _get_settings(self): _settings = self._memobj.settings _service = self._memobj.service _msg = self._memobj.messages cfg_grp = RadioSettingGroup("cfg_grp", "Basic Settings") adv_grp = RadioSettingGroup("adv_grp", "Advanced Settings") key_grp = RadioSettingGroup("key_grp", "Key Assignment") group = RadioSettings(cfg_grp, adv_grp, key_grp) # # Basic Settings # rs = RadioSetting("apo", "Auto Power Off", RadioSettingValueList( APO_LIST, APO_LIST[_settings.apo])) cfg_grp.append(rs) rs = RadioSetting("sql", "Squelch Level", RadioSettingValueList( SQL_LIST, SQL_LIST[_settings.sql])) cfg_grp.append(rs) rs = RadioSetting("scanm", "Scan Mode", RadioSettingValueList( SCANM_LIST, SCANM_LIST[_settings.scanm])) cfg_grp.append(rs) rs = RadioSetting("tot", "Time Out Timer", RadioSettingValueList( TOT_LIST, TOT_LIST[_settings.tot])) cfg_grp.append(rs) rs = RadioSetting("step", "Step", RadioSettingValueList( STEP_LIST, STEP_LIST[_settings.step])) cfg_grp.append(rs) rs = RadioSetting("monitor", "Monitor", RadioSettingValueList( MONITOR_LIST, MONITOR_LIST[_settings.monitor])) cfg_grp.append(rs) rs = RadioSetting("vfomr", "VFO/MR", RadioSettingValueList( VFOMR_LIST, VFOMR_LIST[_settings.vfomr])) cfg_grp.append(rs) rs = RadioSetting("mrcha", "MR/CHA", RadioSettingValueList( MRCHA_LIST, MRCHA_LIST[_settings.mrcha])) cfg_grp.append(rs) rs = RadioSetting("vol", "Volume", RadioSettingValueList( VOL_LIST, VOL_LIST[_settings.vol])) cfg_grp.append(rs) rs = RadioSetting("opendis", "Open Display", RadioSettingValueList( OPENDIS_LIST, OPENDIS_LIST[_settings.opendis])) cfg_grp.append(rs) def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " LOG.debug("Filtered: %s" % filtered) return filtered rs = RadioSetting("messages.user1", "User-defined Message 1", RadioSettingValueString(0, 7, _filter(_msg.user1))) cfg_grp.append(rs) rs = RadioSetting("messages.user2", "User-defined Message 2", RadioSettingValueString(0, 7, _filter(_msg.user2))) cfg_grp.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.system)) val.set_mutable(False) rs = RadioSetting("messages.system", "System Message", val) cfg_grp.append(rs) rs = RadioSetting("lamp", "Backlight", RadioSettingValueList( LAMP_LIST, LAMP_LIST[_settings.lamp])) cfg_grp.append(rs) rs = RadioSetting("keylockm", "Key Lock Mode", RadioSettingValueList( KEYLOCKM_LIST, KEYLOCKM_LIST[_settings.keylockm])) cfg_grp.append(rs) rs = RadioSetting("absel", "A/B Select", RadioSettingValueList(ABSEL_LIST, ABSEL_LIST[_settings.absel])) cfg_grp.append(rs) rs = RadioSetting("obeep", "Open Beep", RadioSettingValueBoolean(_settings.obeep)) cfg_grp.append(rs) rs = RadioSetting("rbeep", "Roger Beep", RadioSettingValueBoolean(_settings.rbeep)) cfg_grp.append(rs) rs = RadioSetting("keylock_off", "Key Lock", RadioSettingValueBoolean(not _settings.keylock_off)) cfg_grp.append(rs) rs = RadioSetting("ctdcsb", "CT/DCS Busy Lock", RadioSettingValueBoolean(_settings.ctdcsb)) cfg_grp.append(rs) rs = RadioSetting("alarm", "Alarm Key", RadioSettingValueBoolean(_settings.alarm)) cfg_grp.append(rs) rs = RadioSetting("save", "Battery Save", RadioSettingValueBoolean(_settings.save)) cfg_grp.append(rs) rs = RadioSetting("kbeep", "Key Beep", RadioSettingValueBoolean(_settings.kbeep)) cfg_grp.append(rs) rs = RadioSetting("reset", "Reset Enable", RadioSettingValueBoolean(_settings.reset)) cfg_grp.append(rs) rs = RadioSetting("smfont_off", "Small Font", RadioSettingValueBoolean(not _settings.smfont_off)) cfg_grp.append(rs) rs = RadioSetting("aliasen_off", "Alias Enable", RadioSettingValueBoolean(not _settings.aliasen_off)) cfg_grp.append(rs) rs = RadioSetting("txstop_off", "TX Stop", RadioSettingValueBoolean(not _settings.txstop_off)) cfg_grp.append(rs) rs = RadioSetting("dw_off", "Dual Watch", RadioSettingValueBoolean(not _settings.dw_off)) cfg_grp.append(rs) rs = RadioSetting("fmen_off", "FM Enable", RadioSettingValueBoolean(not _settings.fmen_off)) cfg_grp.append(rs) rs = RadioSetting("fmdw", "FM Dual Watch", RadioSettingValueBoolean(_settings.fmdw)) cfg_grp.append(rs) rs = RadioSetting("fmscan_off", "FM Scan", RadioSettingValueBoolean( not _settings.fmscan_off)) cfg_grp.append(rs) rs = RadioSetting("keypadmic_off", "Keypad MIC", RadioSettingValueBoolean( not _settings.keypadmic_off)) cfg_grp.append(rs) rs = RadioSetting("voxgain", "VOX Gain", RadioSettingValueList( VOXGAIN_LIST, VOXGAIN_LIST[_settings.voxgain])) cfg_grp.append(rs) rs = RadioSetting("voxdt", "VOX Delay Time", RadioSettingValueList( VOXDT_LIST, VOXDT_LIST[_settings.voxdt])) cfg_grp.append(rs) rs = RadioSetting("vir", "VOX Inhibit on Receive", RadioSettingValueBoolean(_settings.vir)) cfg_grp.append(rs) # # Advanced Settings # val = (_settings.dtmftime) - 5 rs = RadioSetting("dtmftime", "DTMF Digit Time", RadioSettingValueList( DTMFTIME_LIST, DTMFTIME_LIST[val])) adv_grp.append(rs) val = (_settings.dtmfspace) - 5 rs = RadioSetting("dtmfspace", "DTMF Digit Space Time", RadioSettingValueList( DTMFTIME_LIST, DTMFTIME_LIST[val])) adv_grp.append(rs) val = (_settings.dtmfdelay) / 5 rs = RadioSetting("dtmfdelay", "DTMF 1st Digit Delay", RadioSettingValueList( DTMFDELAY_LIST, DTMFDELAY_LIST[val])) adv_grp.append(rs) val = (_settings.dtmfpretime) / 10 - 1 rs = RadioSetting("dtmfpretime", "DTMF Pretime", RadioSettingValueList( DTMFPRETIME_LIST, DTMFPRETIME_LIST[val])) adv_grp.append(rs) val = (_settings.dtmfdelay2) / 5 rs = RadioSetting("dtmfdelay2", "DTMF * and # Digit Delay", RadioSettingValueList( DTMFDELAY2_LIST, DTMFDELAY2_LIST[val])) adv_grp.append(rs) rs = RadioSetting("ackdecode", "ACK Decode", RadioSettingValueBoolean(_settings.ackdecode)) adv_grp.append(rs) rs = RadioSetting("dtmfst", "DTMF Sidetone", RadioSettingValueBoolean(_settings.dtmfst)) adv_grp.append(rs) rs = RadioSetting("service.rssi400", "Squelch Base Level (UHF)", RadioSettingValueInteger(0, 255, _service.rssi400)) adv_grp.append(rs) rs = RadioSetting("service.rssi136", "Squelch Base Level (VHF)", RadioSettingValueInteger(0, 255, _service.rssi136)) adv_grp.append(rs) # # Key Settings # val = (_settings.lptime) - 5 rs = RadioSetting("lptime", "Long Press Time", RadioSettingValueList( LPTIME_LIST, LPTIME_LIST[val])) key_grp.append(rs) rs = RadioSetting("keyp1long", "P1 Long Key", RadioSettingValueList( PFKEYLONG_LIST, PFKEYLONG_LIST[_settings.keyp1long])) key_grp.append(rs) rs = RadioSetting("keyp1short", "P1 Short Key", RadioSettingValueList( PFKEYSHORT_LIST, PFKEYSHORT_LIST[_settings.keyp1short])) key_grp.append(rs) rs = RadioSetting("keyp2long", "P2 Long Key", RadioSettingValueList( PFKEYLONG_LIST, PFKEYLONG_LIST[_settings.keyp2long])) key_grp.append(rs) rs = RadioSetting("keyp2short", "P2 Short Key", RadioSettingValueList( PFKEYSHORT_LIST, PFKEYSHORT_LIST[_settings.keyp2short])) key_grp.append(rs) rs = RadioSetting("keyp3long", "P3 Long Key", RadioSettingValueList( PFKEYLONG_LIST, PFKEYLONG_LIST[_settings.keyp3long])) key_grp.append(rs) rs = RadioSetting("keyp3short", "P3 Short Key", RadioSettingValueList( PFKEYSHORT_LIST, PFKEYSHORT_LIST[_settings.keyp3short])) key_grp.append(rs) val = RadioSettingValueList(PFKEYSHORT_LIST, PFKEYSHORT_LIST[_settings.keymshort]) val.set_mutable(_settings.menuen == 0) rs = RadioSetting("keymshort", "M Short Key", val) key_grp.append(rs) val = RadioSettingValueBoolean(_settings.menuen) rs = RadioSetting("menuen", "Menu Enable", val) key_grp.append(rs) return group def get_settings(self): try: return self._get_settings() except: import traceback LOG.error("Failed to parse settings: %s", traceback.format_exc()) return None def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "keylock_off": setattr(obj, setting, not int(element.value)) elif setting == "smfont_off": setattr(obj, setting, not int(element.value)) elif setting == "aliasen_off": setattr(obj, setting, not int(element.value)) elif setting == "txstop_off": setattr(obj, setting, not int(element.value)) elif setting == "dw_off": setattr(obj, setting, not int(element.value)) elif setting == "fmen_off": setattr(obj, setting, not int(element.value)) elif setting == "fmscan_off": setattr(obj, setting, not int(element.value)) elif setting == "keypadmic_off": setattr(obj, setting, not int(element.value)) elif setting == "dtmftime": setattr(obj, setting, int(element.value) + 5) elif setting == "dtmfspace": setattr(obj, setting, int(element.value) + 5) elif setting == "dtmfdelay": setattr(obj, setting, int(element.value) * 5) elif setting == "dtmfpretime": setattr(obj, setting, (int(element.value) + 1) * 10) elif setting == "dtmfdelay2": setattr(obj, setting, int(element.value) * 5) elif setting == "lptime": setattr(obj, setting, int(element.value) + 5) else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): if filedata[0x168:0x170].startswith(cls._file_ident) and \ filedata[0x170:0x178].startswith(cls._model_ident): return True else: return False @directory.register class JetstreamJT270MRadio(LeixenVV898Radio): """Jetstream JT270M""" VENDOR = "Jetstream" MODEL = "JT270M" _file_ident = "JET" _model_ident = 'LX-\x89\x85\x53' @directory.register class JetstreamJT270MHRadio(LeixenVV898Radio): """Jetstream JT270MH""" VENDOR = "Jetstream" MODEL = "JT270MH" _file_ident = "Leixen" _model_ident = 'LX-\x89\x85\x85' _ranges = [(0x0C00, 0x2000)] _mem_formatter = {'unknownormode': 'unknown6:1', 'modeorpower': 'mode:1, power:1', 'chanstart': 0x0C00, 'namestart': 0x1930, 'defaults': 6} def get_features(self): rf = super(JetstreamJT270MHRadio, self).get_features() rf.has_sub_devices = self.VARIANT == '' rf.memory_bounds = (1, 99) return rf def get_sub_devices(self): return [JetstreamJT270MHRadioA(self._mmap), JetstreamJT270MHRadioB(self._mmap)] def _get_memobjs(self, number): number = number * 2 - self._offset _mem = self._memobj.memory[number] _name = self._memobj.name[number] return _mem, _name class JetstreamJT270MHRadioA(JetstreamJT270MHRadio): VARIANT = 'A Band' _offset = 1 class JetstreamJT270MHRadioB(JetstreamJT270MHRadio): VARIANT = 'B Band' _offset = 2 class VV898E(chirp_common.Alias): '''Leixen has called this radio both 898E and S historically, ident is identical''' VENDOR = "Leixen" MODEL = "VV-898E" @directory.register class LeixenVV898SRadio(LeixenVV898Radio): """Leixen VV-898S, also VV-898E which is identical""" VENDOR = "Leixen" MODEL = "VV-898S" ALIASES = [VV898E, ] _model_ident = 'LX-\x89\x85\x75' _mem_formatter = {'unknownormode': 'mode:1', 'modeorpower': 'power:2', 'chanstart': 0x0D00, 'namestart': 0x19B0, 'defaults': 3} _power_levels = [chirp_common.PowerLevel("Low", watts=5), chirp_common.PowerLevel("Med", watts=10), chirp_common.PowerLevel("High", watts=25)] chirp-daily-20170714/chirp/drivers/lt725uv.py0000644000016101777760000005423413107243203021753 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings from textwrap import dedent MEM_FORMAT = """ #seekto 0x0200; struct { u8 unknown1; u8 volume; u8 unknown2[2]; u8 wtled; u8 rxled; u8 txled; u8 ledsw; u8 beep; u8 ring; u8 bcl; u8 tot; } settings; struct vfo { u8 unknown1[2]; u32 rxfreq; u8 unknown2[8]; u8 power; u8 unknown3[3]; u24 offset; u32 step; u8 sql; }; #seekto 0x0300; struct { struct vfo vfoa; } upper; #seekto 0x0380; struct { struct vfo vfob; } lower; struct mem { u32 rxfreq; u16 is_rxdigtone:1, rxdtcs_pol:1, rxtone:14; u8 recvmode; u32 txfreq; u16 is_txdigtone:1, txdtcs_pol:1, txtone:14; u8 botsignal; u8 eotsignal; u8 power:1, wide:1, compandor:1 scrambler:1 unknown:4; u8 namelen; u8 name[6]; u8 unused; }; #seekto 0x0400; struct mem upper_memory[128]; #seekto 0x1000; struct mem lower_memory[128]; """ MEM_SIZE = 0x1C00 BLOCK_SIZE = 0x40 STIMEOUT = 2 LIST_RECVMODE = ["", "QT/DQT", "QT/DQT + Signaling"] LIST_SIGNAL = ["Off"] + ["DTMF%s" % x for x in range(1, 9)] + \ ["DTMF%s + Identity" % x for x in range(1, 9)] + \ ["Identity code"] LIST_POWER = ["Low", "Mid", "High"] LIST_COLOR = ["Off", "Orange", "Blue", "Purple"] LIST_LEDSW = ["Auto", "On"] LIST_RING = ["Off"] + ["%s seconds" % x for x in range(1, 10)] LIST_TIMEOUT = ["Off"] + ["%s seconds" % x for x in range(30, 630, 30)] def _clean_buffer(radio): radio.pipe.timeout = 0.005 junk = radio.pipe.read(256) radio.pipe.timeout = STIMEOUT if junk: Log.debug("Got %i bytes of junk before starting" % len(junk)) def _rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: _exit_program_mode(radio) msg = "Generic error reading data from radio; check your cable." raise errors.RadioError(msg) if len(data) != amount: _exit_program_mode(radio) msg = "Error reading data from radio: not the amount of data we want." raise errors.RadioError(msg) return data def _rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) except: raise errors.RadioError("Error sending data to radio") def _make_frame(cmd, addr, length, data=""): """Pack the info in the headder format""" frame = struct.pack(">4sHH", cmd, addr, length) # add the data if set if len(data) != 0: frame += data # return the data return frame def _recv(radio, addr, length): """Get data from the radio """ data = _rawrecv(radio, length) # DEBUG LOG.info("Response:") LOG.debug(util.hexprint(data)) return data def _do_ident(radio): """Put the radio in PROGRAM mode & identify it""" # set the serial discipline radio.pipe.baudrate = 19200 radio.pipe.parity = "N" radio.pipe.timeout = STIMEOUT # flush input buffer _clean_buffer(radio) magic = "PROM_LIN" _rawsend(radio, magic) ack = _rawrecv(radio, 1) if ack != "\x06": _exit_program_mode(radio) if ack: LOG.debug(repr(ack)) raise errors.RadioError("Radio did not respond") return True def _exit_program_mode(radio): endframe = "EXIT" _rawsend(radio, endframe) def _download(radio): """Get the memory map""" # put radio in program mode and identify it _do_ident(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in range(0, MEM_SIZE, BLOCK_SIZE): frame = _make_frame("READ", addr, BLOCK_SIZE) # DEBUG LOG.info("Request sent:") LOG.debug(util.hexprint(frame)) # sending the read request _rawsend(radio, frame) # now we read d = _recv(radio, addr, BLOCK_SIZE) # aggregate the data data += d # UI Update status.cur = addr / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) _exit_program_mode(radio) data += "LT-725UV" return data def _upload(radio): """Upload procedure""" # put radio in program mode and identify it _do_ident(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) # the fun starts here for addr in range(0, MEM_SIZE, BLOCK_SIZE): # sending the data data = radio.get_mmap()[addr:addr + BLOCK_SIZE] frame = _make_frame("WRIE", addr, BLOCK_SIZE, data) _rawsend(radio, frame) # receiving the response ack = _rawrecv(radio, 1) if ack != "\x06": _exit_program_mode(radio) msg = "Bad ack writing block 0x%04x" % addr raise errors.RadioError(msg) # UI Update status.cur = addr / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) _exit_program_mode(radio) def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0x1C00:0x1C08] if rid == cls.MODEL: return True return False def _split(rf, f1, f2): """Returns False if the two freqs are in the same band (no split) or True otherwise""" # determine if the two freqs are in the same band for low, high in rf.valid_bands: if f1 >= low and f1 <= high and \ f2 >= low and f2 <= high: # if the two freqs are on the same Band this is not a split return False # if you get here is because the freq pairs are split return True @directory.register class LT725UV(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """LUITON LT-725UV Radio""" VENDOR = "LUITON" MODEL = "LT-725UV" MODES = ["NFM", "FM"] TONES = chirp_common.TONES DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) NAME_LENGTH = 6 DTMF_CHARS = list("0123456789ABCD*#") VALID_BANDS = [(136000000, 176000000), (400000000, 480000000)] # valid chars on the LCD VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The LT725UV driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow this instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = True rf.has_offset = True rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.has_sub_devices = self.VARIANT == "" rf.valid_modes = self.MODES rf.valid_characters = self.VALID_CHARS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_skips = [] rf.valid_name_length = self.NAME_LENGTH rf.valid_dtcs_codes = self.DTCS_CODES rf.valid_bands = self.VALID_BANDS rf.memory_bounds = (1, 128) return rf def get_sub_devices(self): return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)] def sync_in(self): """Download from radio""" try: data = _download(self) except errors.RadioError: # Pass through any real errors we raise raise except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during download') raise errors.RadioError('Unexpected error communicating ' 'with the radio') self._mmap = memmap.MemoryMap(data) self.process_mmap() def sync_out(self): """Upload to radio""" try: _upload(self) except: # If anything unexpected happens, make sure we raise # a RadioError and log the problem LOG.exception('Unexpected error during upload') raise errors.RadioError('Unexpected error communicating ' 'with the radio') def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _memory_obj(self, suffix=""): return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix)) def _get_dcs(self, val): return int(str(val)[2:-18]) def _set_dcs(self, val): return int(str(val), 16) def get_memory(self, number): _mem = self._memory_obj()[number - 1] 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 _mem.txfreq == 0xFFFFFFFF: # TX freq not set mem.duplex = "off" mem.offset = 0 elif int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 elif _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): 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 _mem.name[:_mem.namelen]: mem.name += chr(char) dtcs_pol = ["N", "N"] if _mem.rxtone == 0x3FFF: rxmode = "" elif _mem.is_rxdigtone == 0: # ctcss rxmode = "Tone" mem.ctone = int(_mem.rxtone) / 10.0 else: # digital rxmode = "DTCS" mem.rx_dtcs = self._get_dcs(_mem.rxtone) if _mem.rxdtcs_pol == 1: dtcs_pol[1] = "R" if _mem.txtone == 0x3FFF: txmode = "" elif _mem.is_txdigtone == 0: # ctcss txmode = "Tone" mem.rtone = int(_mem.txtone) / 10.0 else: # digital txmode = "DTCS" mem.dtcs = self._get_dcs(_mem.txtone) if _mem.txdtcs_pol == 1: dtcs_pol[0] = "R" 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) mem.mode = self.MODES[_mem.wide] # Extra mem.extra = RadioSettingGroup("extra", "Extra") if _mem.recvmode == 0xFF: val = 0x00 else: val = _mem.recvmode recvmode = RadioSetting("recvmode", "Receiving mode", RadioSettingValueList(LIST_RECVMODE, LIST_RECVMODE[val])) mem.extra.append(recvmode) if _mem.botsignal == 0xFF: val = 0x00 else: val = _mem.botsignal botsignal = RadioSetting("botsignal", "Launch signaling", RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])) mem.extra.append(botsignal) if _mem.eotsignal == 0xFF: val = 0x00 else: val = _mem.eotsignal eotsignal = RadioSetting("eotsignal", "Transmit end signaling", RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])) mem.extra.append(eotsignal) compandor = RadioSetting("compandor", "Compandor", RadioSettingValueBoolean(bool(_mem.compandor))) mem.extra.append(compandor) scrambler = RadioSetting("scrambler", "Scrambler", RadioSettingValueBoolean(bool(_mem.scrambler))) mem.extra.append(scrambler) return mem def set_memory(self, mem): _mem = self._memory_obj()[mem.number - 1] if mem.empty: _mem.set_raw("\xff" * 24) _mem.namelen = 0 return _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7) _mem.rxfreq = mem.freq / 10 if mem.duplex == "off": _mem.txfreq = 0xFFFFFFFF 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 _mem.namelen = len(mem.name) _namelength = self.get_features().valid_name_length for i in range(_namelength): try: _mem.name[i] = ord(mem.name[i]) except IndexError: _mem.name[i] = 0xFF rxmode = "" txmode = "" if mem.tmode == "Tone": txmode = "Tone" elif mem.tmode == "TSQL": rxmode = "Tone" txmode = "TSQL" elif mem.tmode == "DTCS": rxmode = "DTCSSQL" txmode = "DTCS" elif mem.tmode == "Cross": txmode, rxmode = mem.cross_mode.split("->", 1) if rxmode == "": _mem.rxdtcs_pol = 1 _mem.is_rxdigtone = 1 _mem.rxtone = 0x3FFF elif rxmode == "Tone": _mem.rxdtcs_pol = 0 _mem.is_rxdigtone = 0 _mem.rxtone = int(mem.ctone * 10) elif rxmode == "DTCSSQL": _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0 _mem.is_rxdigtone = 1 _mem.rxtone = self._set_dcs(mem.dtcs) elif rxmode == "DTCS": _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0 _mem.is_rxdigtone = 1 _mem.rxtone = self._set_dcs(mem.rx_dtcs) if txmode == "": _mem.txdtcs_pol = 1 _mem.is_txdigtone = 1 _mem.txtone = 0x3FFF elif txmode == "Tone": _mem.txdtcs_pol = 0 _mem.is_txdigtone = 0 _mem.txtone = int(mem.rtone * 10) elif txmode == "TSQL": _mem.txdtcs_pol = 0 _mem.is_txdigtone = 0 _mem.txtone = int(mem.ctone * 10) elif txmode == "DTCS": _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0 _mem.is_txdigtone = 1 _mem.txtone = self._set_dcs(mem.dtcs) _mem.wide = self.MODES.index(mem.mode) # extra settings for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) # Basic volume = RadioSetting("settings.volume", "Volume", RadioSettingValueInteger(0, 20, _mem.settings.volume)) basic.append(volume) powera = RadioSetting("upper.vfoa.power", "Power (Upper)", RadioSettingValueList(LIST_POWER, LIST_POWER[ _mem.upper.vfoa.power])) basic.append(powera) powerb = RadioSetting("lower.vfob.power", "Power (Lower)", RadioSettingValueList(LIST_POWER, LIST_POWER[ _mem.lower.vfob.power])) basic.append(powerb) wtled = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList(LIST_COLOR, LIST_COLOR[ _mem.settings.wtled])) basic.append(wtled) rxled = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList(LIST_COLOR, LIST_COLOR[ _mem.settings.rxled])) basic.append(rxled) txled = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList(LIST_COLOR, LIST_COLOR[ _mem.settings.txled])) basic.append(txled) ledsw = RadioSetting("settings.ledsw", "Back light mode", RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[ _mem.settings.ledsw])) basic.append(ledsw) beep = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(bool(_mem.settings.beep))) basic.append(beep) ring = RadioSetting("settings.ring", "Ring", RadioSettingValueList(LIST_RING, LIST_RING[ _mem.settings.ring])) basic.append(ring) bcl = RadioSetting("settings.bcl", "Busy channel lockout", RadioSettingValueBoolean(bool(_mem.settings.bcl))) basic.append(bcl) tot = RadioSetting("settings.tot", "Timeout Timer", RadioSettingValueList(LIST_TIMEOUT, LIST_TIMEOUT[ _mem.settings.tot])) basic.append(tot) if _mem.upper.vfoa.sql == 0xFF: val = 0x04 else: val = _mem.upper.vfoa.sql sqla = RadioSetting("upper.vfoa.sql", "Squelch (Upper)", RadioSettingValueInteger(0, 9, val)) basic.append(sqla) if _mem.lower.vfob.sql == 0xFF: val = 0x04 else: val = _mem.lower.vfob.sql sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)", RadioSettingValueInteger(0, 9, val)) basic.append(sqlb) return top def set_settings(self, settings): _settings = self._memobj.settings _mem = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif element.value.get_mutable(): LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE + 8: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False class LT725UVUpper(LT725UV): VARIANT = "Upper" _vfo = "upper" class LT725UVLower(LT725UV): VARIANT = "Lower" _vfo = "lower" chirp-daily-20170714/chirp/drivers/id800.py0000644000016101777760000002526212475535623021367 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 . from chirp.drivers import icf from chirp import chirp_common, errors, directory, 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-daily-20170714/chirp/drivers/generic_tpe.py0000644000016101777760000000374012475535623023024 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 from chirp.drivers import generic_csv @directory.register class TpeRadio(generic_csv.CSVRadio): """Generic ARRL Travel Plus""" VENDOR = "ARRL" MODEL = "Travel Plus" FILE_EXTENSION = "tpe" ATTR_MAP = { "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: float(v) if v and float(v) in chirp_common.TONES else 88.5, "rtone"), "Repeater Notes": (str, "comment"), } def _clean_tmode(self, headers, line, mem): try: val = generic_csv.get_datum_by_header(headers, line, "CTCSS Tones") if val and float(val) in chirp_common.TONES: mem.tmode = "Tone" except generic_csv.OmittedHeaderError: pass return mem def _clean_ctone(self, headers, line, mem): # TPE only stores a single tone value mem.ctone = mem.rtone return mem @classmethod def match_model(cls, filedata, filename): return filename.lower().endswith("." + cls.FILE_EXTENSION) chirp-daily-20170714/chirp/drivers/vxa700.py0000644000016101777760000002253312646374217021566 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, util, directory, memmap, errors from chirp import bitwise import time import struct import logging LOG = logging.getLogger(__name__) def _send(radio, data): LOG.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: LOG.debug("%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": LOG.debug(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.timeout = 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.timeout = 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-daily-20170714/chirp/drivers/icw32.py0000644000016101777760000001544712644407617021475 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 logging from chirp.drivers import icf from chirp import chirp_common, util, directory, bitwise LOG = logging.getLogger(__name__) 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: LOG.debug("Canceling scan on left VFO") self._memobj.state.left_scanning = 0 if self._memobj.state.right_scanning: LOG.debug("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) # IC-W32E are the very same as IC-W32A but have a different _model @directory.register class ICW32ERadio(ICW32ARadio): """Icom IC-W32E""" MODEL = "IC-W32E" _model = "\x18\x82\x00\x02" # an extra byte is added to distinguish file images from IC-W32A # it will be allocated and initialized to 0x00 in _clone_from_radio # (icf.py) but radio will not send it # That byte is not sent to radio because the _clone_to_radio use _ranges # for the send cycle _memsize = ICW32ARadio._memsize + 1 def get_sub_devices(self): # this is needed because sub devices must be of a child class return [ICW32ERadioVHF(self._mmap), ICW32ERadioUHF(self._mmap)] @classmethod def match_model(cls, filedata, filename): if not len(filedata) == cls._memsize: return False return filedata[-16 - 1: -1] == "IcomCloneFormat3" and \ filedata[-1] == chr(0x00) # this is the very same as ICW32ARadioVHF but have ICW32ERadio as parent class class ICW32ERadioVHF(ICW32ERadio): """ICW32 VHF subdevice""" VARIANT = "VHF" _limits = (118000000, 174000000) _mem_positions = (0x0000, 0x0DC0) # this is the very same as ICW32ARadioUHF but have ICW32ERadio as parent class class ICW32ERadioUHF(ICW32ERadio): """ICW32 UHF subdevice""" VARIANT = "UHF" _limits = (400000000, 470000000) _mem_positions = (0x06E0, 0x0E2E) chirp-daily-20170714/chirp/drivers/fd268.py0000644000016101777760000007501713022211420021346 0ustar jenkinsnogroup00000000000000# Copyright 2015 Pavel Milanes CO7WT # # 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 os import logging from chirp import chirp_common, directory, memmap, errors, util, bitwise from textwrap import dedent from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rx_freq[4]; lbcd tx_freq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 unknown:4, scrambler:1, unknown1:1, unknown2:1, busy_lock:1; u8 unknown3[3]; } memory[99]; #seekto 0x0640; struct { lbcd vrx_freq[4]; lbcd vtx_freq[4]; lbcd vrx_tone[2]; lbcd vtx_tone[2]; u8 shift_plus:1, shift_minus:1, unknown11:2, scramble:1, unknown12:1, unknown13:1, busy_lock:1; u8 unknown14[3]; } vfo; #seekto 0x07B0; struct { u8 ani_mode; char ani[3]; u8 unknown21[12]; u8 unknown22:5, bw1:1, // twin setting of bw (LCD "romb") bs1:1, // twin setting of bs (LCD "S") warning1:1; // twin setting of warning (LCD "Tune") u8 sql[1]; u8 monitorval; u8 tot[1]; u8 powerrank; u8 unknown23[3]; u8 unknown24[8]; char model[8]; u8 unknown26[8]; u8 step; u8 unknown27:2, power:1, lamp:1, lamp_auto:1, key:1, monitor:1, bw:1; u8 unknown28:3, warning:1, bs:1, unknown29:1, wmem:1, wvfo:1; u8 active_ch; u8 unknown30[4]; u8 unknown31[4]; bbcd vfo_shift[4]; } settings; """ MEM_SIZE = 0x0800 CMD_ACK = "\x06" BLOCK_SIZE = 0x08 POWER_LEVELS = ["Low", "High"] LIST_SQL = ["Off"] + ["%s" % x for x in range(1, 10)] LIST_TOT = ["Off"] + ["%s" % x for x in range(10, 100, 10)] ONOFF = ["Off", "On"] STEPF = ["5", "10", "6.25", "12.5", "25"] ACTIVE_CH = ["%s" % x for x in range(1, 100)] KEY_LOCK = ["Automatic", "Manual"] BW = ["Narrow", "Wide"] W_MODE = ["VFO", "Memory"] VSHIFT = ["None", "-", "+"] POWER_RANK = ["%s" % x for x in range(0, 28)] ANI = ["Off", "BOT", "EOT", "Both"] def raw_recv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) except: raise errors.RadioError("Error reading data from radio") return data def raw_send(radio, data): """Raw write to the radio device""" try: data = radio.pipe.write(data) except: raise errors.RadioError("Error writing data to radio") def make_frame(cmd, addr, length=BLOCK_SIZE): """Pack the info in the format it likes""" return struct.pack(">BHB", ord(cmd), addr, length) def check_ack(r, text): """Check for a correct ACK from radio, raising error 'Text' if something was wrong""" ack = raw_recv(r, 1) if ack != CMD_ACK: raise errors.RadioError(text) else: return True def send(radio, frame, data=""): """Generic send data to the radio""" raw_send(radio, frame) if data != "": raw_send(radio, data) check_ack(radio, "Radio didn't ack the last block of data") def recv(radio): """Generic receive data from the radio, return just data""" # you must get it all 12 at once (4 header + 8 data) rxdata = raw_recv(radio, 12) if (len(rxdata) != 12): raise errors.RadioError( "Radio sent %i bytes, we expected 12" % (len(rxdata))) else: data = rxdata[4:] send(radio, CMD_ACK) check_ack(radio, "Radio didn't ack the sended data") return data def do_magic(radio): """Try to get the radio in program mode, the factory software (FDX-288) tries up to ~16 times to get the correct response, we will do the same, but with a lower count.""" tries = 8 # UI information status = chirp_common.Status() status.cur = 0 status.max = tries status.msg = "Linking to radio, please wait." radio.status_fn(status) # every byte of this magic chain must be send separatedly magic = "\x02PROGRA" # start the fun, finger crossed please... for a in range(0, tries): # UI update status.cur = a radio.status_fn(status) for i in range(0, len(magic)): send(radio, magic[i]) # Now you get a x06 of ACK ack = raw_recv(radio, 1) if ack == CMD_ACK: return True return False def do_program(radio): """Feidaxin program mode and identification dance""" # try to get the radio in program mode ack = do_magic(radio) if not ack: erc = "Radio did not accept program mode, " erc += "check your cable and radio; then try again." raise errors.RadioError(erc) # now we request identification send(radio, "M") send(radio, "\x02") ident = raw_recv(radio, 8) ################# WARNING ########################################## # Feidaxin radios has a "send id" procedure in the initial handshake # but it's worthless, once you do a hardware reset the ident area # get all 0xFF. # # Even FDX-288 software appears to match the model by any other # mean, so I detected on a few images that the 3 first bytes are # unique to each radio model, so for now we use that method untill # proven otherwise #################################################################### LOG.debug("Radio's ID string:") LOG.debug(util.hexprint(ident)) # final ACK send(radio, CMD_ACK) check_ack(radio, "Radio refused to enter programming mode") def do_download(radio): """ The download function """ do_program(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE): send(radio, make_frame("R", addr)) d = recv(radio) data += d # UI Update status.cur = addr radio.status_fn(status) return memmap.MemoryMap(data) def do_upload(radio): """The upload function""" do_program(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE): send(radio, make_frame("W", addr), radio.get_mmap()[addr:addr+BLOCK_SIZE]) # UI Update status.cur = addr radio.status_fn(status) def model_match(cls, data): """Use a experimental guess to determine if the radio you just downloaded or the img opened you is for this model""" # Using a few imgs of some FD radio I found that the four first # bytes it's like the model fingerprint, so we have to testing the # model type with this experimental method so far. fp = data[0:4] if fp == cls._IDENT: return True else: LOG.debug("Unknowd Feidaxing radio, ID:") LOG.debug(util.hexprint(fp)) return False class FeidaxinFD2x8yRadio(chirp_common.CloneModeRadio): """Feidaxin FD-268 & alike Radios""" VENDOR = "Feidaxin" MODEL = "FD-268 & alike Radios" BAUD_RATE = 9600 _memsize = MEM_SIZE _upper = 99 _VFO_DEFAULT = 0 _IDENT = "" _active_ch = ACTIVE_CH @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The program mode of this radio has his tricks, ' 'so this driver is *completely experimental*.') rp.pre_download = _(dedent("""\ This radio has a tricky way of enter into program mode, even the original software has a few tries to get inside. I will try 8 times (most of the time ~3 will doit) and this can take a few seconds, if don't work, try again a few times. If you can get into it, please check the radio and cable. """)) rp.pre_upload = _(dedent("""\ This radio has a tricky way of enter into program mode, even the original software has a few tries to get inside. I will try 8 times (most of the time ~3 will doit) and this can take a few seconds, if don't work, try again a few times. If you can get into it, please check the radio and cable. """)) return rp def get_features(self): """Return information about this radio's features""" rf = chirp_common.RadioFeatures() # this feature is READ ONLY by now. rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.has_name = False rf.has_offset = True rf.has_mode = False rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_duplexes = ["", "-", "+", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] # we have to remove "Tone->" because this is the same to "TQSL" # I get a few days hitting the wall with my head about this... rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] # Power levels are golbal and no per channel, so disabled #rf.valid_power_levels = POWER_LEVELS # this radio has no skips rf.valid_skips = [] # this radio modes are global and not per channel, so just FM rf.valid_modes = ["FM"] rf.valid_bands = [self._range] rf.memory_bounds = (1, self._upper) return rf def sync_in(self): """Do a download of the radio eeprom""" data = do_download(self) # as the radio comm protocol's identification is useless # we test the model after having the img if not model_match(self, data): # ok, wrong model, fire an error erc = "EEPROM fingerprint don't match, check if you " erc += "selected the right radio model." raise errors.RadioError(erc) # all ok self._mmap = data self.process_mmap() def sync_out(self): """Do an upload to the radio eeprom""" do_upload(self) def process_mmap(self): """Process the memory object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): """Return a raw representation of the memory object""" return repr(self._memobj.memory[number]) def _decode_tone(self, val): """Parse the tone data to decode from mem, it returns""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def _encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_memory(self, number): """Extract a high-level memory object from the low-level memory map, This is called to populate a memory in the UI""" # Get a low-level memory object mapped to the image _mem = self._memobj.memory[number - 1] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # number mem.number = number # empty if _mem.get_raw()[0] == "\xFF": mem.empty = True return mem # rx freq mem.freq = int(_mem.rx_freq) * 10 # checking if tx freq is empty, this is "possible" on the # original soft after a warning, and radio is happy with it if _mem.tx_freq.get_raw() == "\xFF\xFF\xFF\xFF": mem.duplex = "off" mem.offset = 0 else: rx = int(_mem.rx_freq) * 10 tx = int(_mem.tx_freq) * 10 if tx == rx: mem.offset = 0 mem.duplex = "" else: mem.duplex = rx > tx and "-" or "+" mem.offset = abs(tx - rx) # tone data txtone = self._decode_tone(_mem.tx_tone) rxtone = self._decode_tone(_mem.rx_tone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra setting group, FD-268 don't uset it at all # FD-288's & others do it? mem.extra = RadioSettingGroup("extra", "Extra") busy = RadioSetting("Busy", "Busy Channel Lockout", RadioSettingValueBoolean( bool(_mem.busy_lock))) mem.extra.append(busy) scramble = RadioSetting("Scrambler", "Scrambler Option", RadioSettingValueBoolean( bool(_mem.scrambler))) mem.extra.append(scramble) # return mem return mem def set_memory(self, mem): """Store details about a high-level memory to the memory map This is called when a user edits a memory in the UI""" # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number - 1] # Empty memory if mem.empty: _mem.set_raw("\xFF" * 16) return # freq rx _mem.rx_freq = mem.freq / 10 # freq tx if mem.duplex == "+": _mem.tx_freq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.tx_freq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": for i in range(0, 4): _mem.tx_freq[i].set_raw("\xFF") else: _mem.tx_freq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self._encode_tone(_mem.tx_tone, txmode, txtone, txpol) self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol) # extra settings for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) return mem def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic") work = RadioSettingGroup("work", "Work Mode Settings") top = RadioSettings(basic, work) # Basic sql = RadioSetting("settings.sql", "Squelch Level", RadioSettingValueList(LIST_SQL, LIST_SQL[ _mem.settings.sql])) basic.append(sql) tot = RadioSetting("settings.tot", "Time out timer", RadioSettingValueList(LIST_TOT, LIST_TOT[ _mem.settings.tot])) basic.append(tot) power = RadioSetting("settings.power", "Actual Power", RadioSettingValueList(POWER_LEVELS, POWER_LEVELS[_mem.settings.power])) basic.append(power) key_lock = RadioSetting("settings.key", "Keyboard Lock", RadioSettingValueList(KEY_LOCK, KEY_LOCK[_mem.settings.key])) basic.append(key_lock) bw = RadioSetting("settings.bw", "Bandwidth", RadioSettingValueList(BW, BW[_mem.settings.bw])) basic.append(bw) powerrank = RadioSetting("settings.powerrank", "Power output adjust", RadioSettingValueList(POWER_RANK, POWER_RANK[_mem.settings.powerrank])) basic.append(powerrank) lamp = RadioSetting("settings.lamp", "LCD Lamp", RadioSettingValueBoolean(_mem.settings.lamp)) basic.append(lamp) lamp_auto = RadioSetting("settings.lamp_auto", "LCD Lamp auto on/off", RadioSettingValueBoolean( _mem.settings.lamp_auto)) basic.append(lamp_auto) bs = RadioSetting("settings.bs", "Battery Save", RadioSettingValueBoolean(_mem.settings.bs)) basic.append(bs) warning = RadioSetting("settings.warning", "Warning Alerts", RadioSettingValueBoolean(_mem.settings.warning)) basic.append(warning) monitor = RadioSetting("settings.monitor", "Monitor key", RadioSettingValueBoolean(_mem.settings.monitor)) basic.append(monitor) # Work mode settings wmset = RadioSetting("settings.wmem", "VFO/MR Mode", RadioSettingValueList( W_MODE, W_MODE[_mem.settings.wmem])) work.append(wmset) active_ch = RadioSetting("settings.active_ch", "Work Channel", RadioSettingValueList(ACTIVE_CH, ACTIVE_CH[_mem.settings.active_ch])) work.append(active_ch) # vfo rx validation if _mem.vfo.vrx_freq.get_raw()[0] == "\xFF": # if the vfo is not set, the UI cares about the # length of the field, so set a default LOG.debug("VFO freq not set, setting it to default %s" % self._VFO_DEFAULT) vfo = self._VFO_DEFAULT else: vfo = int(_mem.vfo.vrx_freq) * 10 vf_freq = RadioSetting("vfo.vrx_freq", "VFO frequency", RadioSettingValueString(0, 10, chirp_common.format_freq(vfo))) work.append(vf_freq) # shift works # VSHIFT = ["None", "-", "+"] sset = 0 if bool(_mem.vfo.shift_minus) is True: sset = 1 elif bool(_mem.vfo.shift_plus) is True: sset = 2 shift = RadioSetting("shift", "VFO Shift", RadioSettingValueList(VSHIFT, VSHIFT[sset])) work.append(shift) # vfo shift validation if none set it to ZERO if _mem.settings.vfo_shift.get_raw()[0] == "\xFF": # if the shift is not set, the UI cares about the # length of the field, so set to zero LOG.debug("VFO shift not set, setting it to zero") vfo_shift = 0 else: vfo_shift = int(_mem.settings.vfo_shift) * 10 offset = RadioSetting("settings.vfo_shift", "VFO Offset", RadioSettingValueString(0, 9, chirp_common.format_freq(vfo_shift))) work.append(offset) step = RadioSetting("settings.step", "VFO step", RadioSettingValueList(STEPF, STEPF[_mem.settings.step])) work.append(step) # at least for FD-268A/B it doesn't work as stated, so disabled # by now #scamble = RadioSetting("vfo.scramble", "Scramble", #RadioSettingValueList(ONOFF, #ONOFF[int(_mem.vfo.scramble)])) #work.append(scamble) #busy_lock = RadioSetting("vfo.busy_lock", "Busy Lock out", #RadioSettingValueList(ONOFF, #ONOFF[int(_mem.vfo.busy_lock)])) #work.append(busy_lock) # FD-288 Family ANI settings if "FD-288" in self.MODEL: ani_mode = RadioSetting("settings.ani_mode", "ANI ID", RadioSettingValueList(ANI, ANI[_mem.settings.ani_mode])) work.append(ani_mode) # it can't be \xFF ani_value = str(_mem.settings.ani) if ani_value == "\xFF\xFF\xFF": ani_value = "200" ani_value = "".join(x for x in ani_value if (int(x) >= 2 and int(x) <= 9)) ani = RadioSetting("settings.ani", "ANI (200-999)", RadioSettingValueString(0, 3, ani_value)) work.append(ani) return top def set_settings(self, settings): """Translate the settings in the UI into bit in the mem_struct I don't understand well the method used in many drivers so, I used mine, ugly but works ok""" def _get_shift(obj): """Get the amount of offset in the memmap""" shift = 0 sf = str(obj.settings.vfo_shift) if sf[0] != "\xFF": shift = int(mobj.settings.vfo_shift) * 10 return shift def _get_vrx(obj): """Get the vfo rx freq""" return int(obj.vfo.vrx_freq) * 10 def _update_vtx(o, rx, offset): """Update the Vfo TX mem from the value of rx & the offset""" # check the shift sign plus = bool(getattr(o, "shift_plus")) minus = bool(getattr(o, "shift_minus")) if plus: o.vtx_freq = (rx + offset) / 10 elif minus: o.vtx_freq = (rx - offset) / 10 else: o.vtx_freq = rx / 10 mobj = self._memobj flag = False for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue # Let's roll the ball if "." in element.get_name(): # real properties, more or less mapeable inter, setting = element.get_name().split(".") obj = getattr(mobj, inter) value = element.value # test on this cases ....... if setting in ["sql", "tot", "powerrank", "active_ch", "ani_mode"]: value = int(value) # test on this cases ....... if setting in ["lamp", "lamp_auto", "bs", "warning", "monitor"]: value = bool(value) # warning and bs have a sister setting in LCD if setting == "warning" or setting == "bs": # aditional setting setattr(obj, setting + "1", value) # monitorval: monitor = 0 > monitorval = 0 # monitorval: monitor = 1 > monitorval = x30 if setting == "monitor": # sister setting in LCD if value: setattr(obj, "monitorval", 0x30) else: setattr(obj, "monitorval", 0) # case power if setting == "power": value = str(value) == "High" and True or False # case key # key => auto = 0, manu = 1 if setting == "key": value = str(value) == "Manual" and True or False # case bandwidth # bw: 0 = nar, 1 = Wide & must equal bw1 if setting == "bw": value = str(value) == "Wide" and True or False # sister attr setattr(obj, "bw1", value) # work mem wmem/wvfo if setting == "wmem": if str(value) == "Memory": value = True # sister attr setattr(obj, "wvfo", not value) else: value = False # sister attr setattr(obj, "wvfo", not value) # case step # STEPF = ["5", "10", "6.25", "12.5", "25"] if setting == "step": value = STEPF.index(str(value)) # case vrx_freq if setting == "vrx_freq": value = chirp_common.parse_freq(str(value)) / 10 # you must calculate the apropiate txfreq from # shift and offset shift = _get_shift(mobj) # update the tx vfo freq _update_vtx(obj, value * 10, shift) # vfo_shift = offset if setting == "vfo_shift": value = chirp_common.parse_freq(str(value)) / 10 # you must calculate the apropiate txfreq from # shift and offset # get vfo rx vrx = _get_vrx(mobj) # update the tx vfo freq _update_vtx(mobj.vfo, vrx, value * 10) # at least for FD-268A/B it doesn't work as stated, so disabled # by now, does this work on the fd-288s? ## case scramble & busy_lock #if setting == "scramble" or setting == "busy_lock": #value = bool(ONOFF.index(str(value))) # ani value, only for FD-288 if setting == "ani": if "FD-268" in self.MODEL: # 268 doesn't have ani setting continue else: # 288 models, validate the input [200-999] | "" # we will left adjust and zero pad to avoid errors # between inputs in the UI value = str(value).strip().ljust(3, "0") value = int(value) if value == 0: value = 200 else: if value > 999 or value < 200: raise errors.InvalidValueError( "The ANI value must be between \ 200 and 999, not %03i" % value) value = str(value) else: # Others that are artifact for real values, not than mapeables # selecting the values to work setting = str(element.get_name()) value = str(element.value) # vfo_shift if setting == "shift": # get shift offset = _get_shift(mobj) # get vfo rx vrx = _get_vrx(mobj) # VSHIFT = ["None", "-", "+"] if value == "+": mobj.vfo.shift_plus = True mobj.vfo.shift_minus = False elif value == "-": mobj.vfo.shift_plus = False mobj.vfo.shift_minus = True else: mobj.vfo.shift_plus = False mobj.vfo.shift_minus = False # update the tx vfo freq _update_vtx(mobj.vfo, vrx, offset) if setting != "shift": setattr(obj, setting, value) @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware fingerprint, this experimental match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False ## FD-268 family: this are the original tested models, FD-268B UHF ## was tested "remotely" with images thanks to AG5M ## I just have the 268A in hand to test @directory.register class FD268ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-268A Radio""" MODEL = "FD-268A" _range = (136000000, 174000000) _VFO_DEFAULT = 145000000 _IDENT = "\xFF\xEE\x46\xFF" @directory.register class FD268BRadio(FeidaxinFD2x8yRadio): """Feidaxin FD-268B Radio""" MODEL = "FD-268B" _range = (400000000, 470000000) _VFO_DEFAULT = 439000000 _IDENT = "\xFF\xEE\x47\xFF" ## FD-288 Family: the only difference from this family to the FD-268's ## are the ANI settings ## Tested hacking the FD-268A memmory @directory.register class FD288ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-288A Radio""" MODEL = "FD-288A" _range = (136000000, 174000000) _VFO_DEFAULT = 145000000 _IDENT = "\xFF\xEE\x4B\xFF" @directory.register class FD288BRadio(FeidaxinFD2x8yRadio): """Feidaxin FD-288B Radio""" MODEL = "FD-288B" _range = (400000000, 470000000) _VFO_DEFAULT = 439000000 _IDENT = "\xFF\xEE\x4C\xFF" ## the following radios was tested hacking the FD-268A memmory with ## the software and found to be clones of FD-268 ones @directory.register class FD150ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-150A Radio""" MODEL = "FD-150A" _range = (136000000, 174000000) _VFO_DEFAULT = 145000000 _IDENT = "\xFF\xEE\x45\xFF" @directory.register class FD160ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-160A Radio""" MODEL = "FD-160A" _range = (136000000, 174000000) _VFO_DEFAULT = 145000000 _IDENT = "\xFF\xEE\x48\xFF" @directory.register class FD450ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-450A Radio""" MODEL = "FD-450A" _range = (400000000, 470000000) _VFO_DEFAULT = 439000000 _IDENT = "\xFF\xEE\x44\xFF" @directory.register class FD460ARadio(FeidaxinFD2x8yRadio): """Feidaxin FD-460A Radio""" MODEL = "FD-460A" _range = (400000000, 470000000) _VFO_DEFAULT = 439000000 _IDENT = "\xFF\xEE\x4A\xFF" @directory.register class FD460UHRadio(FeidaxinFD2x8yRadio): """Feidaxin FD-460UH Radio""" MODEL = "FD-460UH" _range = (400000000, 480000000) _VFO_DEFAULT = 439000000 _IDENT = "\xFF\xF4\x44\xFF" chirp-daily-20170714/chirp/drivers/baofeng_uv3r.py0000644000016101777760000005211612476257220023114 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 . """Baofeng UV3r radio management module""" import time import os import logging from wouxun_common import do_download, do_upload from chirp import util, chirp_common, bitwise, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueInteger, RadioSettingValueString, \ RadioSettingValueFloat, RadioSettings LOG = logging.getLogger(__name__) 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: LOG.debug(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 0x0780; struct { lbcd lower_vhf[2]; lbcd upper_vhf[2]; lbcd lower_uhf[2]; lbcd upper_uhf[2]; } limits; struct vfosettings { lbcd freq[4]; u8 rxtone; u8 unknown1; lbcd offset[3]; u8 txtone; u8 power:1, bandwidth:1, unknown2:4, duplex:2; u8 step; u8 unknown3[4]; }; #seekto 0x0790; struct { struct vfosettings uhf; struct vfosettings vhf; } vfo; #seekto 0x07C2; struct { u8 squelch; u8 vox; u8 timeout; u8 save:1, unknown_1:1, dw:1, ste:1, beep:1, unknown_2:1, bclo:1, ch_flag:1; u8 backlight:2, relaym:1, scanm:1, pri:1, unknown_3:3; u8 unknown_4[3]; u8 pri_ch; } settings; #seekto 0x07E0; u16 fm_presets[16]; #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]; """ STEPS = [5.0, 6.25, 10.0, 12.5, 20.0, 25.0] STEP_LIST = [str(x) for x in STEPS] BACKLIGHT_LIST = ["Off", "Key", "Continuous"] TIMEOUT_LIST = ["Off"] + ["%s sec" % x for x in range(30, 210, 30)] SCANM_LIST = ["TO", "CO"] PRI_CH_LIST = ["Off"] + ["%s" % x for x in range(1, 100)] CH_FLAG_LIST = ["Freq Mode", "Channel Mode"] POWER_LIST = ["Low", "High"] BANDWIDTH_LIST = ["Narrow", "Wide"] DUPLEX_LIST = ["Off", "-", "+"] STE_LIST = ["On", "Off"] 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.has_settings = True rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_modes = ["FM", "NFM"] rf.valid_power_levels = UV3R_POWER_LEVELS rf.valid_bands = [(136000000, 235000000), (400000000, 529000000)] 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: LOG.warn("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: LOG.warn("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) def get_settings(self): _settings = self._memobj.settings _vfo = self._memobj.vfo basic = RadioSettingGroup("basic", "Basic Settings") group = RadioSettings(basic) rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("backlight", "LCD Back Light", RadioSettingValueList( BACKLIGHT_LIST, BACKLIGHT_LIST[_settings.backlight])) basic.append(rs) rs = RadioSetting("beep", "Keypad Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting("vox", "VOX Level (0=OFF)", RadioSettingValueInteger(0, 9, _settings.vox)) basic.append(rs) rs = RadioSetting("dw", "Dual Watch", RadioSettingValueBoolean(_settings.dw)) basic.append(rs) rs = RadioSetting("ste", "Squelch Tail Eliminate", RadioSettingValueList( STE_LIST, STE_LIST[_settings.ste])) basic.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueBoolean(_settings.save)) basic.append(rs) rs = RadioSetting("timeout", "Time Out Timer", RadioSettingValueList( TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout])) basic.append(rs) rs = RadioSetting("scanm", "Scan Mode", RadioSettingValueList( SCANM_LIST, SCANM_LIST[_settings.scanm])) basic.append(rs) rs = RadioSetting("relaym", "Repeater Sound Response", RadioSettingValueBoolean(_settings.relaym)) basic.append(rs) rs = RadioSetting("bclo", "Busy Channel Lock Out", RadioSettingValueBoolean(_settings.bclo)) basic.append(rs) rs = RadioSetting("pri", "Priority Channel Scanning", RadioSettingValueBoolean(_settings.pri)) basic.append(rs) rs = RadioSetting("pri_ch", "Priority Channel", RadioSettingValueList( PRI_CH_LIST, PRI_CH_LIST[_settings.pri_ch])) basic.append(rs) rs = RadioSetting("ch_flag", "Display Mode", RadioSettingValueList( CH_FLAG_LIST, CH_FLAG_LIST[_settings.ch_flag])) basic.append(rs) _limit = int(self._memobj.limits.lower_vhf) / 10 if _limit < 115 or _limit > 239: _limit = 144 rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (115-239 MHz)", RadioSettingValueInteger(115, 235, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.lower_vhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.upper_vhf) / 10 if _limit < 115 or _limit > 239: _limit = 146 rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (115-239 MHz)", RadioSettingValueInteger(115, 235, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.upper_vhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.lower_uhf) / 10 if _limit < 200 or _limit > 529: _limit = 420 rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (200-529 MHz)", RadioSettingValueInteger(200, 529, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.lower_uhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) _limit = int(self._memobj.limits.upper_uhf) / 10 if _limit < 200 or _limit > 529: _limit = 450 rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (200-529 MHz)", RadioSettingValueInteger(200, 529, _limit)) def apply_limit(setting, obj): value = int(setting.value) * 10 obj.upper_uhf = value rs.set_apply_callback(apply_limit, self._memobj.limits) basic.append(rs) vfo_preset = RadioSettingGroup("vfo_preset", "VFO Presets") group.append(vfo_preset) def convert_bytes_to_freq(bytes): real_freq = 0 real_freq = bytes return chirp_common.format_freq(real_freq * 10) def apply_vhf_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 obj.vhf.freq = value val = RadioSettingValueString( 0, 10, convert_bytes_to_freq(int(_vfo.vhf.freq))) rs = RadioSetting("vfo.vhf.freq", "VHF RX Frequency (115.00000-236.00000)", val) rs.set_apply_callback(apply_vhf_freq, _vfo) vfo_preset.append(rs) rs = RadioSetting("vfo.vhf.duplex", "Shift Direction", RadioSettingValueList( DUPLEX_LIST, DUPLEX_LIST[_vfo.vhf.duplex])) vfo_preset.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 real_offset = bytes return chirp_common.format_freq(real_offset * 10000) def apply_vhf_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10000 obj.vhf.offset = value val = RadioSettingValueString( 0, 10, convert_bytes_to_offset(int(_vfo.vhf.offset))) rs = RadioSetting("vfo.vhf.offset", "Offset (0.00-37.995)", val) rs.set_apply_callback(apply_vhf_offset, _vfo) vfo_preset.append(rs) rs = RadioSetting("vfo.vhf.power", "Power Level", RadioSettingValueList( POWER_LIST, POWER_LIST[_vfo.vhf.power])) vfo_preset.append(rs) rs = RadioSetting("vfo.vhf.bandwidth", "Bandwidth", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfo.vhf.bandwidth])) vfo_preset.append(rs) rs = RadioSetting("vfo.vhf.step", "Step", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfo.vhf.step])) vfo_preset.append(rs) def apply_uhf_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 obj.uhf.freq = value val = RadioSettingValueString( 0, 10, convert_bytes_to_freq(int(_vfo.uhf.freq))) rs = RadioSetting("vfo.uhf.freq", "UHF RX Frequency (200.00000-529.00000)", val) rs.set_apply_callback(apply_uhf_freq, _vfo) vfo_preset.append(rs) rs = RadioSetting("vfo.uhf.duplex", "Shift Direction", RadioSettingValueList( DUPLEX_LIST, DUPLEX_LIST[_vfo.uhf.duplex])) vfo_preset.append(rs) def apply_uhf_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10000 obj.uhf.offset = value val = RadioSettingValueString( 0, 10, convert_bytes_to_offset(int(_vfo.uhf.offset))) rs = RadioSetting("vfo.uhf.offset", "Offset (0.00-69.995)", val) rs.set_apply_callback(apply_uhf_offset, _vfo) vfo_preset.append(rs) rs = RadioSetting("vfo.uhf.power", "Power Level", RadioSettingValueList( POWER_LIST, POWER_LIST[_vfo.uhf.power])) vfo_preset.append(rs) rs = RadioSetting("vfo.uhf.bandwidth", "Bandwidth", RadioSettingValueList( BANDWIDTH_LIST, BANDWIDTH_LIST[_vfo.uhf.bandwidth])) vfo_preset.append(rs) rs = RadioSetting("vfo.uhf.step", "Step", RadioSettingValueList( STEP_LIST, STEP_LIST[_vfo.uhf.step])) vfo_preset.append(rs) fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets") group.append(fm_preset) for i in range(0, 16): if self._memobj.fm_presets[i] < 0x01AF: used = True preset = self._memobj.fm_presets[i] / 10.0 + 65 else: used = False preset = 65 rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1), RadioSettingValueBoolean(used), RadioSettingValueFloat(65, 108, preset, 0.1, 1)) fm_preset.append(rs) return group def set_settings(self, settings): _settings = self._memobj.settings for element in settings: if not isinstance(element, RadioSetting): if element.get_name() == "fm_preset": self._set_fm_preset(element) else: self.set_settings(element) continue else: try: name = element.get_name() if "." in name: bits = name.split(".") obj = self._memobj for bit in bits[:-1]: if "/" in bit: bit, index = bit.split("/", 1) index = int(index) obj = getattr(obj, bit)[index] else: obj = getattr(obj, bit) setting = bits[-1] else: obj = _settings setting = element.get_name() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise def _set_fm_preset(self, settings): for element in settings: try: index = (int(element.get_name().split("_")[-1])) val = element.value if val[0].get_value(): value = int(val[1].get_value() * 10 - 650) else: value = 0x01AF LOG.debug("Setting fm_presets[%1i] = %s" % (index, value)) setting = self._memobj.fm_presets setting[index] = value except Exception, e: LOG.debug(element.get_name()) raise @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-daily-20170714/chirp/drivers/puxing_px888k.py0000644000016101777760000020411412772143577023205 0ustar jenkinsnogroup00000000000000# Copyright 2016 Leo Barring # # 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, \ bitwise, settings, errors from struct import pack import logging LOG = logging.getLogger(__name__) SUPPORT_NONSPLIT_DUPLEX_ONLY = False SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = True UNAMBIGUOUS_CROSS_MODES_ONLY = True # With this setting enabled, some CHIRP settings are stored in # thought-to-be junk/padding data of the channel memories. # Enabling this feature while using CHIRP with an actual radio # and any effects thereof is entirely the responsibility of the user. ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES = False MEM_FORMAT = """ // data fields are generally written 0xff if they are unset struct { #seekto 0x0000; struct { struct { // 0-3 bbcd rx_freq[4]; // 4-7 bbcd tx_freq[4]; // 8-9 A-B struct { u8 digital:1, invert:1, high:6; u8 low; } tone[2]; // tx_squelch on 0, rx_squelch on 1 // C // the duplex sign is not used for memories, // but is kept for interface consistency u8 duplex_sign:2, compander:1, txpower:1, modulation_width:1, txrx_reverse:1, bcl:2; // D u8 scrambler_type:3, use_scrambler:1, opt_signal:2, ptt_id_edge:2; // E-F // u8 _unknown_000E[2]; %s } data[128]; struct { // 0-5, alt 8-D char entry[6]; // 6-8, alt E-F char _unknown_0806[2]; } names[128]; #seekto 0x0c20; bit present[128]; #seekto 0x0c30; bit priority[128]; } channel_memory; #seekto 0x0c00; struct { // 0-3 bbcd rx_freq[4]; // 4-7 bbcd tx_freq[4]; // actually offset, but kept for name consistency // 8 struct { u8 digital:1, invert:1, high:6; u8 low; } tone[2]; // tx_squelch on 0, rx_squelch on 1 // C u8 duplex_sign:2, compander:1, txpower:1, modulation_width:1, txrx_reverse:1, bcl:2; // D u8 scrambler_type:3, use_scrambler:1, opt_signal:2, ptt_id_edge:2; // E-F // u8 _unknown_0C0E[2]; %s } vfo_data[2]; #seekto 0xc40; struct { // 0-5 char model_string[6]; // typically PX888D, unknown if rw or ro // 6-7 u8 _unknown_0C46[2]; // 8-9 struct { bbcd lower_freq[2]; bbcd upper_freq[2]; } band_limits[2]; } model_information; #seekto 0x0c50; char radio_information_string[16]; #seekto 0x0c60; struct { // 0 u8 ptt_cancel_sq:1, dis_ptt_id:1, workmode_b:2, use_roger_beep:1, msk_reverse:1, workmode_a:2; // 1 u8 backlight_color:2, backlight_mode:2, dual_single_watch:1, auto_keylock:1, scan_mode:2; // 2 u8 rx_stun:1, tx_stun:1, boot_message_mode:2, battery_save:1, key_beep:1, voice_announce:2; // 3 bbcd squelch_level; // 4 bbcd tx_timeout; // 5 u8 allow_keypad:1, relay_without_disable_tail:1 _unknown_0C65:1, call_channel_active:1, vox_gain:4; // 6 bbcd vox_delay; // 7 bbcd vfo_step; // 8 bbcd ptt_id_type; // 9 u8 keypad_lock:1, _unknown_0C69_1:1, side_button_hold_mode:2, dtmf_sidetone:1, _unknown_0C69_2:1, side_button_click_mode:2; // A u8 roger_beep:4, main_watch:1, _unknown_0C6A:3; // B u8 channel_a; // C u8 channel_b; // D u8 priority_channel; // E u8 wait_time; // F u8 _unknown_0C6F; // 0-7 on next block u8 _unknown_0C70[8]; // 8-D on next block char boot_message[6]; } opt_settings; #seekto 0x0c80; struct { // these fields are used for all ptt id forms (msk/dtmf/5t) // (only one can be active and stored at a time) // and different constraints are applied depending // on the ptt id type u8 entry[7]; u8 length; } ptt_id_data[2]; // 0 is BOT, 1 is EOT #seekto 0x0c90; struct { // 0 u8 _unknown_0C90; // 1 u8 _unknown_0C91_1:3, channel_stepping:1, unknown_0C91_2:1 receive_range:2 unknown_0C91_3:1; // 2-3 u8 _unknown_0C92[2]; // 4-7 u8 vfo_freq[4]; // 8-F and two more blocks struct { u8 entry[4]; } memory[10]; } fm_radio; #seekto 0x0cc0; struct { char id_code[4]; struct { char entry[4]; } phone_book[9]; } msk_settings; #seekto 0x0cf0; struct { // 0-3 bbcd rx_freq[4]; // 4-7 bbcd tx_freq[4]; // 8 struct { u8 digital:1, invert:1, high:6; u8 low; } tone[2]; // tx_squelch on 0, rx_squelch on 1 // C // the duplex sign is not used for the CALL, // channel but is kept for interface consistency u8 duplex_sign:2, compander:1, txpower:1, modulation_width:1, txrx_reverse:1 bcl:2; // D u8 scrambler_type:3, use_scrambler:1, opt_signal:2, ptt_id_edge:2; // E-F // u8 _unknown_0CFE[2]; %s } call_channel; #seekto 0x0d00; struct { // DTMF codes are stored as hex half-bytes, // 0-9 A-D are mapped straight // DTMF '*' is HEX E, DTMF '#' is HEX F // 0x0d00 struct { u8 digit_length; // 0x05 to 0x14 corresponding to 50-200ms u8 inter_digit_pause; // same u8 first_digit_length; // same u8 first_digit_delay; // 0x02 to 0x14 corresponding to 100-1000ms } timing; #seekto 0x0d30; u8 _unknown_0D30[2]; // 0-1 u8 group_code; // 2 u8 reset_time; // 3 u8 alert_transpond; // 4 u8 id_code[4]; // 5-8 u8 _unknown_0D39[4]; // 9-C u8 id_code_length; // D u8 _unknown_0d3e[2]; // E-F // 0x0d40 u8 tx_stun_code[4]; u8 _unknown_0D44[4]; u8 tx_stun_code_length; u8 cancel_tx_stun_code_length; u8 cancel_tx_stun_code[4]; u8 _unknown_0D4E[2]; // 0x0d50 u8 rxtx_stun_code[4]; u8 _unknown_0D54[4]; u8 rxtx_stun_code_length; u8 cancel_rxtx_stun_code_length; u8 cancel_rxtx_stun_code[4]; u8 _unknown_0D4E[2]; // 0x0d60 struct { u8 entry[5]; u8 _unknown_0D65[3]; u8 length; u8 _unknown_0D69[7]; } phone_book[9]; } dtmf_settings; #seekto 0x0e00; struct { u8 delay; u8 _unknown_0E01[5]; u8 alert_transpond; u8 reset_time; u8 tone_standard; u8 id_code[3]; #seekto 0x0e20; struct { u8 period; u8 group_code:4, repeat_code:4; } tone_settings[4]; // the order is ZVEI1 ZVEI2 CCIR1 CCITT #seekto 0x0e40; // 5-Tone tone standard frequency table // unknown use, changing the values does not seem to have // any effect on the produced sound, but the values are not // overwritten either. il16 tone_frequency_table[16]; // 0xe60 u8 tx_stun_code[5]; u8 _unknown_0E65[3]; u8 tx_stun_code_length; u8 cancel_tx_stun_code_length; u8 cancel_tx_stun_code[5]; u8 _unknown_0E6F; // 0xe70 u8 rxtx_stun_code[5]; u8 _unknown_0E75[3]; u8 rxtx_stun_code_length; u8 cancel_rxtx_stun_code_length; u8 cancel_rxtx_stun_code[5]; u8 _unknown_0E7F; // 0xe80 struct { u8 entry[3]; } phone_book[9]; } five_tone_settings; } mem;""" # various magic numbers and strings, apart from the memory format if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: LOG.warn("ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES" "AND/OR DANGEROUS FEATURES ENABLED") MEM_FORMAT = MEM_FORMAT % ( "u8 _unknown_000E_1: 6,\n" " experimental_duplex_mode_indicator: 1,\n" " experimental_cross_mode_indicator: 1;\n" "u8 _unknown_000F;", "u8 _unknown_0C0E_1: 6,\n" " experimental_duplex_mode_indicator: 1,\n" " experimental_cross_mode_indicator: 1;\n" "u8 _unknown_0C0F;", "u8 _unknown_0CFE_1: 6,\n" " experimental_duplex_mode_indicator: 1,\n" " experimental_cross_mode_indicator: 1;\n" "u8 _unknown_0CFF;") # we don't need these settings anymore, because it's exactly what the # experimental features are about SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = False SUPPORT_NONSPLIT_DUPLEX_ONLY = False UNAMBIGUOUS_CROSS_MODES_ONLY = False else: MEM_FORMAT = MEM_FORMAT % ( "u8 _unknown_000E[2];", "u8 _unknown_0C0E[2];", "u8 _unknown_0CFE[2];") FILE_MAGIC = [0xc40, 0xc50, '\x50\x58\x38\x38\x38\x44\x00\xff' '\x13\x40\x17\x60\x40\x00\x48\x00'] HANDSHAKE_OUT = b'XONLINE' HANDSHAKE_IN = [b'PX888D\x00\xff'] LOWER_READ_BOUND = 0 UPPER_READ_BOUND = 0x1000 LOWER_WRITE_BOUND = 0 UPPER_WRITE_BOUND = 0x0fc0 BLOCKSIZE = 64 OFF_INT = ["Off"] + [str(x+1) for x in range(100)] OFF_ON = ["Off", "On"] INACTIVE_ACTIVE = ["Inactive", "Active"] NO_YES = ["No", "Yes"] YES_NO = ["Yes", "No"] BANDS = [(134000000, 176000000), # VHF (400000000, 480000000)] # UHF SPECIAL_CHANNELS = {'VFO-A': -2, 'VFO-B': -1, 'CALL': 0} SPECIAL_NUMBERS = {-2: 'VFO-A', -1: 'VFO-B', 0: 'CALL'} DUPLEX_MODES = ['', '+', '-', 'split'] if SUPPORT_NONSPLIT_DUPLEX_ONLY: DUPLEX_MODES = ['', '+', '-'] TONE_MODES = ["", "Tone", "TSQL", "DTCS", "Cross"] CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS", "Tone->"] if UNAMBIGUOUS_CROSS_MODES_ONLY: CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] MODES = ["NFM", "FM"] # Only the 'High' power level is quantified in the manual for # the radio (4W for VHF, 5W for UHF), a web search turned # up numbers for the 'Low' power level (0.5W for VHF and # 0.7W for UHF), but they are not official to my knowledge # and should be taken with a grain of salt or two. # Numbers used in code is the averages POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.6), chirp_common.PowerLevel("High", watts=4.5)] SKIP_MODES = ["", "S"] BCL_MODES = ["Off", "Carrier", "QT/DQT"] SCRAMBLER_MODES = OFF_INT[0:9] PTT_ID_EDGES = ["Off", "BOT", "EOT", "Both"] OPTSIGN_MODES = ["None", "DTMF", "5-Tone", "MSK"] VFO_STRIDE = ['5kHz', '6.25kHz', '10kHz', '12.5kHz', '25kHz'] AB = ['A', 'B'] WATCH_MODES = ['Single watch', 'Dual watch'] AB_MODES = ['VFO', 'Memory index', 'Memory name', 'Memory frequency'] SCAN_MODES = ["Time", "Carrier", "Seek"] WAIT_TIMES = [("0.3s", 6), ("0.5s", 10)] +\ [("%ds" % t, t*20) for t in range(1, 13)] BUTTON_MODES = ["Send call list data", "Emergency alarm", "Send 1750Hz signal", "Open squelch"] BOOT_MESSAGE_TYPES = ["Off", "Battery voltage", "Custom message"] TALKBACK = ['Off', 'Chinese', 'English'] BACKLIGHT_COLORS = zip(["Blue", "Orange", "Purple"], range(1, 4)) VOX_GAIN = OFF_INT[0:10] VOX_DELAYS = ['1s', '2s', '3s', '4s'] TRANSMIT_ALARMS = ['Off', '30s', '60s', '90s', '120s', '150s', '180s', '210s', '240s', '270s'] DATA_MODES = ['MSK', 'DTMF', '5-Tone'] ASCIIPART = ''.join([chr(x) for x in range(0x20, 0x7f)]) DTMF = "0123456789ABCD*#" HEXADECIMAL = "0123456789ABCDEF" ROGER_BEEP = OFF_INT[0:11] BACKLIGHT_MODES = ["Off", "Auto", "On"] TONE_RESET_TIME = ['Off'] + ['%ds' % x for x in range(1, 256)] DTMF_TONE_RESET_TIME = TONE_RESET_TIME[0:16] DTMF_GROUPS = zip(["Off", "A", "B", "C", "D", "*", "#"], [255]+range(10, 16)) FIVE_TONE_STANDARDS = ['ZVEI1', 'ZVEI2', 'CCIR1', 'CCITT'] # should mimic the defaults in the memedit MemoryEditor somewhat # 0 1 2 3 4 5 6 7 SANE_MEMORY_DEFAULT = b"\x14\x61\x00\x00\x14\x61\x00\x00" + \ b"\xff\xff\xff\xff\xc8\x00\xff\xff" # 8 9 A B C D E F # these two option sets are listed differently like this in the stock software, # so I'm keeping them separate for now if they are in fact identical # in behaviour, that should probably be amended DTMF_ALERT_TRANSPOND = zip(['Off', 'Call alert', 'Transpond-alert', 'Transpond-ID code'], [255]+range(1, 4)) FIVE_TONE_ALERT_TRANSPOND = zip(['Off', 'Alert tone', 'Transpond', 'Transpond-ID code'], [255]+range(1, 4)) BFM_BANDS = ['87.5-108MHz', '76.0-91.0MHz', '76.0-108.0MHz', '65.0-76.0MHz'] BFM_STRIDE = ['100kHz', '50kHz'] def piperead(pipe, amount): """read some data, catch exceptions, validate length of data read""" try: d = pipe.read(amount) except Exception as e: raise errors.RadioError( "Tried to read %d bytes, but got an exception: %s" % (amount, repr(e))) if d is None: raise errors.RadioError( "Tried to read %d bytes, but read operation returned ." % (amount)) if d is None or len(d) != amount: raise errors.RadioError( "Tried to read %d bytes, but got %d bytes instead." % (amount, len(d))) return d def pipewrite(pipe, data): """write some data, catch exceptions, validate length of data written""" try: n = pipe.write(data) except Exception as e: raise errors.RadioError( "Tried to write %d bytes, but got an exception: %s." % (len(data), repr(e))) if n is None: raise errors.RadioError( "Tried to write %d bytes, but operation returned ." % (len(data))) if n != len(data): raise errors.RadioError( "Tried to write %d bytes, but wrote %d bytes instead." % (len(data), n)) def attempt_initial_handshake(pipe): """try to do the initial handshake""" pipewrite(pipe, HANDSHAKE_OUT) x = piperead(pipe, len(HANDSHAKE_IN[0])) if x in HANDSHAKE_IN: return True LOG.debug("Handshake failed: received: %s expected one of: %s" % (repr(x), repr(HANDSHAKE_IN))) return False def initial_handshake(pipe, tries): """do an initial handshake attempt up to tries times""" x = False for i in range(tries): x = attempt_initial_handshake(pipe) if x: break if not x: raise errors.RadioError("Initial handshake failed all ten tries.") def mk_writecommand(addr): """makes a write command from an address specification""" return pack('>cHc', b'W', addr, b'@') def mk_readcommand(addr): """makes a read command from an address specification""" return pack('>cHc', b'R', addr, b'@') def expect_ack(pipe): x = piperead(pipe, 1) if x != b'\x06': LOG.debug( "Did not get ACK. received: %s, expected: '\\x06'" % repr(x)) raise errors.RadioError("Did not get ACK when expected.") def end_communications(pipe): """tell the radio that we are done""" pipewrite(pipe, b'E') expect_ack(pipe) def read_block(pipe, addr): """read and return a chunk of data at specified address""" r = mk_readcommand(addr) w = mk_writecommand(addr) pipewrite(pipe, r) x = piperead(pipe, len(w)) if x != w: raise errors.RadioError("Received data not following protocol.") block = piperead(pipe, BLOCKSIZE) return block def write_block(pipe, addr, block): """write a chunk of data at specified address""" w = mk_writecommand(addr) pipewrite(pipe, w) pipewrite(pipe, block) expect_ack(pipe) def show_progress(radio, blockaddr, upper, msg): """relay read/write information to the user through the gui""" if radio.status_fn: status = chirp_common.Status() status.cur = blockaddr status.max = upper status.msg = msg radio.status_fn(status) def do_download(radio): """download from the radio to the memory map""" initial_handshake(radio.pipe, 10) memory = memmap.MemoryMap(b'\xff'*0x1000) for blockaddr in range(LOWER_READ_BOUND, UPPER_READ_BOUND, BLOCKSIZE): LOG.debug("Reading block "+str(blockaddr)) block = read_block(radio.pipe, blockaddr) memory.set(blockaddr, block) show_progress(radio, blockaddr, UPPER_READ_BOUND, "Reading radio memory... %04x" % blockaddr) end_communications(radio.pipe) return memory def do_upload(radio): """upload from the memory map to the radio""" memory = radio.get_mmap() initial_handshake(radio.pipe, 10) for blockaddr in range(LOWER_WRITE_BOUND, UPPER_WRITE_BOUND, BLOCKSIZE): LOG.debug("Writing block "+str(blockaddr)) block = memory[blockaddr:blockaddr+BLOCKSIZE] write_block(radio.pipe, blockaddr, block) show_progress(radio, blockaddr, UPPER_WRITE_BOUND, "Writing radio memory... % 04x" % blockaddr) end_communications(radio.pipe) def parse_tone(t): """ parse the tone (ctss, dtcs) part of the mmap into more easily handled data types """ # [ mode, value, polarity ] if int(t.high) == 0x3f and int(t.low) == 0xff: return [None, None, None] elif bool(t.digital): t = ['DTCS', (int(t.high) & 0x0f)*100 + ((int(t.low) & 0xf0) >> 4)*10 + (int(t.low) & 0x0f), ['N', 'R'][bool(t.invert)]] if t[1] not in chirp_common.DTCS_CODES: return [None, None, None] else: t = ['Tone', ((int(t.high) & 0xf0) >> 4)*100 + (int(t.high) & 0x0f)*10 + ((int(t.low) & 0xf0) >> 4) + (int(t.low) & 0x0f)/10.0, None] if t[1] not in chirp_common.TONES: return [None, None, None] return t def unparse_tone(t): """parse tone data back into the format used by the radio""" # [ mode, value, polarity ] if t[0] == 'Tone': tint = int(t[1]*10) t0, tint = tint % 10, tint // 10 t1, tint = tint % 10, tint // 10 t2, tint = tint % 10, tint // 10 high = (tint << 4) | t2 low = (t1 << 4) | t0 digital = False invert = False return digital, invert, high, low elif t[0] == 'DTCS': tint = int(t[1]) t0, tint = tint % 10, tint // 10 t1, tint = tint % 10, tint // 10 high = tint low = (t1 << 4) | t0 digital = True invert = t[2] == 'R' return digital, invert, high, low return None def decode_halfbytes(data, mapping, length): """ construct a string from a datatype where each half-byte maps to a character """ s = '' for i in range(length): if i & 1 == 0: s += mapping[(int(data[i >> 1]) & 0xf0) >> 4] else: s += mapping[int(data[i >> 1]) & 0x0f] return s def encode_halfbytes(data, datapad, mapping, fillvalue, fieldlen): """encode data from a string where each character maps to a half-byte""" if len(data) & 1: # pad to an even length data += datapad o = [fillvalue] * fieldlen for i in range(0, len(data), 2): v = (mapping.index(data[i]) << 4) | mapping.index(data[i+1]) o[i >> 1] = v return bytearray(o) def decode_ffstring(data): """decode a string delimited by 0xff""" s = '' for b in data: if int(b) == 0xff: break s += chr(int(b)) return s def encode_ffstring(data, fieldlen): """right-pad to specified length with 0xff bytes""" extra = fieldlen-len(data) if extra > 0: data += '\xff'*extra return bytearray(data) def decode_dtmf(data, length): """decode a field containing dtmf data into a string""" if length == 0xff: return '' return decode_halfbytes(data, DTMF, length) def encode_dtmf(data, length, fieldlen): """encode a string containing dtmf characters into a data field""" return encode_halfbytes(data, '0', DTMF, b'\xff', fieldlen) def decode_5tone(data): """decode a field containing 5-tone data into a string""" if (int(data[2]) & 0x0f) != 0: return '' return decode_halfbytes(data, HEXADECIMAL, 5) def encode_5tone(data, fieldlen): """encode a string containing 5-tone characters into a data field""" return encode_halfbytes(data, '0', HEXADECIMAL, b'\xff', fieldlen) def decode_freq(data): """decode frequency data for the broadcast fm radio memories""" data_out = '' if data[0] != 0xff: data_out = chirp_common.format_freq( int(decode_halfbytes(data, "0123456789", len(data)))*100000) return data_out def encode_freq(data, fieldlen): """encode frequency data for the broadcast fm radio memories""" data_out = bytearray('\xff')*fieldlen if data != '': data_out = encode_halfbytes((('%%0%di' % (fieldlen << 1)) % int(chirp_common.parse_freq(data)/10)), '', '0123456789', '', fieldlen) return data_out def sbyn(s, n): """setting by name""" return filter(lambda x: x.get_name() == n, s)[0] # These helper classes provide a direct link between the value # of the widget shown in the ui, and the setting in the memory # map of the radio, lessening the need to write large chunks # of code, first for populating the ui from the memory map, # then secondly for parsing the values back. # By supplying the memory map entry to the setting instance, # it is possible to automatically 1) initialize the value of # the setting, as well as 2) automatically update the memory # value when the user changes it in the ui, without adding # any code outside the class. class MappedIntegerSettingValue(settings.RadioSettingValueInteger): """" Integer setting, with the possibility to add translation functions between memory map <-> integer setting """ def __init__(self, val_mem, minval, maxval, step=1, int_from_mem=lambda x: int(x), mem_from_int=lambda x: x, autowrite=True): """ val_mem - memory map entry for the value minval - the minimum value allowed maxval - maximum value allowed step - value stepping int_from_mem - function to convert memory entry to integer mem_from_int - function to convert integer to memory entry autowrite - automatically write the memory map entry when the value is changed """ self._val_mem = val_mem self._int_from_mem = int_from_mem self._mem_from_int = mem_from_int self._autowrite = autowrite settings.RadioSettingValueInteger.__init__( self, minval, maxval, self._int_from_mem(val_mem), step) def set_value(self, x): settings.RadioSettingValueInteger.set_value(self, x) if self._autowrite: self.write_mem() def write_mem(self): if self.get_mutable() and self._mem_from_int is not None: self._val_mem.set_value(self._mem_from_int( settings.RadioSettingValueInteger.get_value(self))) class MappedListSettingValue(settings.RadioSettingValueMap): """Mapped list setting""" def __init__(self, val_mem, options, autowrite=True): """ val_mem - memory map entry for the value options - either a list of strings options to present, mapped to integers 0...n in the memory map entry, or a list of tuples ("option description", memory map value) int_from_mem - function to convert memory entry to integer mem_from_int - function to convert integer to memory entry autowrite - automatically write the memory map entry when the value is changed """ self._val_mem = val_mem self._autowrite = autowrite if not isinstance(options[0], tuple): options = zip(options, range(len(options))) settings.RadioSettingValueMap.__init__( self, options, mem_val=int(val_mem)) def set_value(self, value): settings.RadioSettingValueMap.set_value(self, value) if self._autowrite: self.write_mem() def write_mem(self): if self.get_mutable(): self._val_mem.set_value( settings.RadioSettingValueMap.get_mem_val(self)) class MappedCodedStringSettingValue(settings.RadioSettingValueString): """ generic base class for a number of mapped presented-as-strings values which may need conversion between mem and string, and may store a length value in a separate mem field """ def __init__(self, val_mem, len_mem, min_length, max_length, charset=ASCIIPART, padchar=' ', autowrite=True, str_from_mem=lambda mve, lve: str(mve[0:int(lve)]), mem_val_from_str=lambda s, fl: s[0:fl], mem_len_from_int=lambda l: l): """ val_mem - memory map entry for the value len_mem - memory map entry for the length (or None) min_length - length that the string will be right-padded to max_length - maximum length of the string, set as maxlength for the RadioSettingValueString charset - the allowed charset padchar - the character that will be used to pad short strings, if not in the charset, charset[0] is used autowrite - automatically call write_mem when the ui value change str_from_mem - function to convert from memory entry to string value, form: func(value_entry, length_entry or none) -> string mem_val_from_str - function to convert from string value to memory-fitting value, form: func(string, value_entry_length) -> value to store in value entry mem_len_from_int - function to convert from string length to memory-fitting value, form: func(stringlength) -> value to store in length entry """ self._min_length = min_length self._val_mem = val_mem self._len_mem = len_mem self._padchar = padchar if padchar not in charset: self._padchar = charset[0] self._autowrite = autowrite self._str_from_mem = str_from_mem self._mem_val_from_str = mem_val_from_str self._mem_len_from_int = mem_len_from_int settings.RadioSettingValueString.__init__( self, 0, max_length, self._str_from_mem(self._val_mem, self._len_mem), charset=charset, autopad=False) def set_value(self, value): """ Set the value of the string, pad if below minimum length, unless it's '' to provide a distinction between uninitialized/reset data and needs-to-be-padded data """ while len(value) < self._min_length and len(value) != 0: value += self._padchar settings.RadioSettingValueString.set_value(self, value) if self._autowrite: self.write_mem() def write_mem(self): """update the memory""" if not self.get_mutable() or self._mem_val_from_str is None: return v = self.get_value() l = len(v) self._val_mem.set_value(self._mem_val_from_str(v, len(self._val_mem))) if self._len_mem is not None and self._mem_len_from_int is not None: self._len_mem.set_value(self._mem_len_from_int(l)) class MappedFFStringSettingValue(MappedCodedStringSettingValue): """ Mapped string setting, tailored for the puxing px888k, which uses 0xff terminated strings. """ def __init__(self, val_mem, min_length, max_length, charset=ASCIIPART, padchar=' ', autowrite=True): MappedCodedStringSettingValue.__init__( self, val_mem, None, min_length, max_length, charset=charset, padchar=padchar, autowrite=autowrite, str_from_mem=lambda mve, lve: decode_ffstring(mve), mem_val_from_str=lambda s, fl: encode_ffstring(s, fl), mem_len_from_int=None) class MappedDTMFStringSettingValue(MappedCodedStringSettingValue): """ Mapped string setting, tailored for the puxing px888k field pairs (value and length) storing DTMF codes """ def __init__(self, val_mem, len_mem, min_length, max_length, autowrite=True): MappedCodedStringSettingValue.__init__( self, val_mem, len_mem, min_length, max_length, charset=DTMF, padchar='0', autowrite=autowrite, str_from_mem=lambda mve, lve: decode_dtmf(mve, lve), mem_val_from_str=lambda s, fl: encode_dtmf(s, len(s), fl)) class MappedFiveToneStringSettingValue(MappedCodedStringSettingValue): """ Mapped string setting, tailored for the puxing px888k fields storing 5-Tone codes """ def __init__(self, val_mem, autowrite=True): MappedCodedStringSettingValue.__init__( self, val_mem, None, 0, 5, charset=HEXADECIMAL, padchar='0', autowrite=autowrite, str_from_mem=lambda mve, lve: decode_5tone(mve), mem_val_from_str=lambda s, fl: encode_5tone(s, fl), mem_len_from_int=None) class MappedFreqStringSettingValue(MappedCodedStringSettingValue): """ Mapped string setting, tailored for the puxing px888k fields for the broadcast FM radio frequencies """ def __init__(self, val_mem, autowrite=True): MappedCodedStringSettingValue.__init__( self, val_mem, None, 0, 128, charset=ASCIIPART, padchar=' ', autowrite=autowrite, str_from_mem=lambda mve, lve: decode_freq(mve), mem_val_from_str=lambda s, fl: encode_freq(s, fl)) # These functions lessen the amount of boilerplate on the form # x = RadioSetting("AAA", "BBB", SomeKindOfRadioSettingValue( ... )) # to # x = some_kind_of_setting("AAA", "BBB", ... ) def integer_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedIntegerSettingValue(*args, **kwargs)) def list_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedListSettingValue(*args, **kwargs)) def ff_string_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedFFStringSettingValue(*args, **kwargs)) def dtmf_string_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedDTMFStringSettingValue(*args, **kwargs)) def five_tone_string_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedFiveToneStringSettingValue(*args, **kwargs)) def frequency_setting(k, n, *args, **kwargs): return settings.RadioSetting( k, n, MappedFreqStringSettingValue(*args, **kwargs)) @directory.register class Puxing_PX888K_Radio(chirp_common.CloneModeRadio): """Puxing PX-888K""" VENDOR = "Puxing" MODEL = "PX-888K" BAUD_RATE = 9600 @classmethod def match_model(cls, filedata, filename): if len(filedata) == UPPER_READ_BOUND: if filedata[FILE_MAGIC[0]:FILE_MAGIC[1]] == FILE_MAGIC[2]: return True else: LOG.debug("The data at 0x0c40 does not match the PX-888K") else: LOG.debug("The file size does not match.") return False def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank_index = False rf.has_dtcs = True rf.has_ctone = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_mode = True rf.has_offset = True rf.has_name = True rf.has_bank = False rf.has_bank_names = False rf.has_tuning_step = False rf.has_cross = True rf.has_infinite_number = False rf.has_nostep_tuning = False rf.has_comment = False rf.has_settings = True if SUPPORT_NONSPLIT_DUPLEX_ONLY: rf.can_odd_split = False else: rf.can_odd_split = True rf.valid_modes = MODES rf.valid_tmodes = TONE_MODES rf.valid_duplexes = DUPLEX_MODES rf.valid_bands = BANDS rf.valid_skips = SKIP_MODES rf.valid_power_levels = POWER_LEVELS rf.valid_characters = ASCIIPART rf.valid_name_length = 6 rf.valid_cross_modes = CROSS_MODES rf.memory_bounds = (1, 128) rf.valid_special_chans = SPECIAL_CHANNELS.keys() return rf def sync_in(self): self._mmap = do_download(self) self.process_mmap() def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_out(self): do_upload(self) def _set_sane_defaults(self, data): # thanks thayward! data.set_raw(SANE_MEMORY_DEFAULT) def _uninitialize(self, data, n): if isinstance(data, bitwise.arrayDataElement): data.set_value(b"\xff"*n) else: data.set_raw(b"\xff"*n) def _get_memory_structs(self, number): """ fetch the correct data structs no matter if its regular or special channels, no matter if they're referred by name or channel index """ index = 2501 i = -42 designator = 'INVALID' isregular = False iscall = False isvfo = False _data = None _name = None _present = None _priority = None if number in SPECIAL_NUMBERS.keys(): index = number # speical by index designator = SPECIAL_NUMBERS[number] elif number in SPECIAL_CHANNELS.keys(): # special by name index = SPECIAL_CHANNELS[number] designator = number elif number > 0: # regular by number index = number designator = number if index < 0: isvfo = True _data = self._memobj.mem.vfo_data[index+2] elif index == 0: iscall = True _data = self._memobj.mem.call_channel elif index > 0: isregular = True i = number - 1 _data = self._memobj.mem.channel_memory.data[i] _name = self._memobj.mem.channel_memory.names[i].entry _present = self._memobj.mem.channel_memory.present[ (i & 0x78) | (7-(i & 0x07))] _priority = self._memobj.mem.channel_memory.priority[ (i & 0x78) | (7-(i & 0x07))] if _data == bytearray(0xff)*16: self._set_sane_defaults(_data) return (index, designator, _data, _name, _present, _priority, isregular, isvfo, iscall) def get_raw_memory(self, number): x = self._get_memory_structs(number) return repr(x[2]) def get_memory(self, number): mem = chirp_common.Memory() (index, designator, _data, _name, _present, _priority, isregular, isvfo, iscall) = self._get_memory_structs(number) mem.number = index mem.extd_number = designator # handle empty channels if isregular: if bool(_present): mem.empty = False mem.name = str(decode_ffstring(_name)) mem.skip = SKIP_MODES[1-int(_priority)] else: mem.empty = True mem.name = '' return mem else: mem.empty = False mem.name = '' # get frequency data mem.freq = int(_data.rx_freq)*10 mem.offset = int(_data.tx_freq)*10 # interpret frequency data # only the vfo channels support duplex, # memory channels operate in split mode all the time if isvfo: mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)] if mem.duplex == '-': mem.offset = mem.freq - mem.offset elif mem.duplex == '': mem.offset = 0 elif mem.duplex == '+': mem.offset = mem.offset - mem.freq else: if mem.freq == mem.offset: mem.duplex = '' mem.offset = 0 elif SUPPORT_NONSPLIT_DUPLEX_ONLY or \ SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS: if mem.freq > mem.offset: mem.offset = mem.freq - mem.offset mem.duplex = '-' elif mem.freq < mem.offset: mem.offset = mem.offset - mem.freq mem.duplex = '+' else: mem.duplex = 'split' # get tone data txtone = parse_tone(_data.tone[0]) rxtone = parse_tone(_data.tone[1]) chirp_common.split_tone_decode(mem, txtone, rxtone) ###################################################################### if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: # override certain settings based on flags # that we have set in junk areas of the memory # or basically, we BELIEVE this to be junk memory, # hence why it's experimental and dangerous if bool(_data.experimental_cross_mode_indicator) is False: if mem.tmode == 'Tone': mem.cross_mode = 'Tone->' elif mem.tmode == 'TSQL': mem.cross_mode = 'Tone->Tone' elif mem.tmode == 'DTCS': mem.cross_mode = 'DTCS->DTCS' mem.tmode = 'Cross' ###################################################################### # transmit mode and power level mem.mode = MODES[bool(_data.modulation_width)] mem.power = POWER_LEVELS[_data.txpower] # extra channel settings mem.extra = settings.RadioSettingGroup( "extra", "extra", list_setting("Busy channel lockout", "BCL", _data.bcl, BCL_MODES), list_setting("Swap transmit and receive frequencies", "Tx Rx freq swap", _data.txrx_reverse, OFF_ON), list_setting("Use compander", "Use compander", _data.compander, OFF_ON), list_setting("Use scrambler", "Use scrambler", _data.use_scrambler, NO_YES), list_setting("Scrambler selection", "Voice Scrambler", _data.scrambler_type, SCRAMBLER_MODES), list_setting("Send ID code before and/or after transmitting", "PTT ID", _data.ptt_id_edge, PTT_ID_EDGES), list_setting("Optional signal before/after transmission, " + "this setting overrides the PTT ID setting.", "Opt Signal", _data.opt_signal, OPTSIGN_MODES)) ###################################################################### if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: # override certain settings based on flags # that we have set in junk areas of the memory # or basically, we BELIEVE this to be junk memory, # hence why it's experimental and dangerous if bool(_data.experimental_duplex_mode_indicator) is False: # if this flag is set, this means that we in the gui # have set the duplex mode to something # the channel does not really support, # such as split modes for vfo channels, # and non-split modes for the memory channels mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)] mem.freq = int(_data.rx_freq)*10 mem.offset = int(_data.tx_freq)*10 if isvfo: # we want split, so we have to reconstruct it # from -/0/+ modes if mem.duplex == '-': mem.offset = mem.freq - mem.offset elif mem.duplex == '': mem.offset = mem.freq elif mem.duplex == '+': mem.offset = mem.freq + mem.offset mem.duplex = 'split' else: # we want -/0/+, so we have to reconstruct it # from split modes if mem.freq > mem.offset: mem.offset = mem.freq - mem.offset mem.duplex = '-' elif mem.freq < mem.offset: mem.offset = mem.offset - mem.freq mem.duplex = '+' else: mem.offset = 0 mem.duplex = '' ###################################################################### return mem def set_memory(self, mem): (index, designator, _data, _name, _present, _priority, isregular, isvfo, iscall) = self._get_memory_structs(mem.number) mem.number = index mem.extd_number = designator # handle empty channels if mem.empty: if isregular: _present.set_value(False) _priority.set_value(False) self._uninitialize(_data, 16) self._uninitialize(_name, 6) else: raise errors.InvalidValueError( "Can't remove CALL and/or VFO channels!") return # handle regular channel stuff like name and present+priority flags if isregular: if not bool(_present): self._set_sane_defaults(_data) _name.set_value( encode_ffstring(self.filter_name(mem.name), len(_name))) _present.set_value(True) _priority.set_value(1-SKIP_MODES.index(mem.skip)) # frequency data rxf = int(mem.freq/10) txf = int(mem.offset/10) _data.rx_freq.set_value(rxf) if isvfo: # fake split modes on write, for channels # that do not support it, which are some # (the two vfo channels) if mem.duplex == 'split': for band in BANDS: rb = mem.freq in range(band[0], band[1]) tb = mem.offset in range(band[0], band[1]) if rb != tb: raise errors.InvalidValueError( "VFO frequencies should be on the same band") if rxf < txf: _data.duplex_sign.set_value(1) _data.tx_freq.set_value(txf - rxf) elif rxf > txf: _data.duplex_sign.set_value(2) _data.tx_freq.set_value(rxf-txf) else: _data.duplex_sign.set_value(0) _data.tx_freq.set_value(0) else: _data.duplex_sign.set_value(DUPLEX_MODES.index(mem.duplex)) _data.tx_freq.set_value(txf) ###################################################################### if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: if mem.duplex == 'split': _data.experimental_duplex_mode_indicator.set_value(0) else: _data.experimental_duplex_mode_indicator.set_value(1) ###################################################################### else: # fake duplex modes on write, for channels # that do not support it, which are most # (all the memory channels) if mem.duplex == '' or mem.duplex is None: _data.tx_freq.set_value(rxf) elif mem.duplex == '+': _data.tx_freq.set_value(rxf + txf) elif mem.duplex == '-': _data.tx_freq.set_value(rxf - txf) else: _data.tx_freq.set_value(txf) ###################################################################### if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: if mem.duplex != 'split': _data.experimental_duplex_mode_indicator.set_value(0) else: _data.experimental_duplex_mode_indicator.set_value(1) ###################################################################### # tone data tonedata = chirp_common.split_tone_encode(mem) for i in range(2): dihl = unparse_tone(tonedata[i]) if dihl is not None: _data.tone[i].digital.set_value(dihl[0]) _data.tone[i].invert.set_value(dihl[1]) _data.tone[i].high.set_value(dihl[2]) _data.tone[i].low.set_value(dihl[3]) else: _data.tone[i].digital.set_value(1) _data.tone[i].invert.set_value(1) _data.tone[i].high.set_value(0x3f) _data.tone[i].low.set_value(0xff) ###################################################################### if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES: if mem.tmode == 'Cross' and mem.cross_mode in ['Tone->', 'Tone->Tone', 'DTCS->DTCS']: _data.experimental_cross_mode_indicator.set_value(0) else: _data.experimental_cross_mode_indicator.set_value(1) ###################################################################### # transmit mode and power level _data.modulation_width.set_value(MODES.index(mem.mode)) if str(mem.power) == 'High': _data.txpower.set_value(1) else: _data.txpower = 0 def get_settings(self): _model = self._memobj.mem.model_information _settings = self._memobj.mem.opt_settings _ptt_id_data = self._memobj.mem.ptt_id_data _msk_settings = self._memobj.mem.msk_settings _dtmf_settings = self._memobj.mem.dtmf_settings _5tone_settings = self._memobj.mem.five_tone_settings _broadcast = self._memobj.mem.fm_radio # for safety reasons we are showing these as read-only model_unit_settings = [ integer_setting("vhflo", "VHF lower bound", _model.band_limits[0].lower_freq, 134, 176, int_from_mem=lambda x:int(int(x)/10), mem_from_int=None), integer_setting("vhfhi", "VHF upper bound", _model.band_limits[0].upper_freq, 134, 176, int_from_mem=lambda x:int(int(x)/10), mem_from_int=None), integer_setting("uhflo", "UHF lower bound", _model.band_limits[1].lower_freq, 400, 480, int_from_mem=lambda x:int(int(x)/10), mem_from_int=None), integer_setting("uhfhi", "UHF upper bound", _model.band_limits[1].upper_freq, 400, 480, int_from_mem=lambda x:int(int(x)/10), mem_from_int=None), ff_string_setting("model", "Model string", _model.model_string, 0, 6) ] for s in model_unit_settings: s.value.set_mutable(False) model_unit_settings.append(ff_string_setting( "info", "Unit Information", self._memobj.mem.radio_information_string, 0, 16)) # tx/rx related settings radio_channel_settings = [ list_setting("vfostep", "VFO step size", _settings.vfo_step, VFO_STRIDE), list_setting("abwatch", "Main watch", _settings.main_watch, AB), list_setting("watchmade", "Watch mode", _settings.main_watch, WATCH_MODES), list_setting("amode", "A mode", _settings.workmode_a, AB_MODES), list_setting("bmode", "B mode", _settings.workmode_b, AB_MODES), integer_setting("achan", "A channel index", _settings.channel_a, 1, 128, int_from_mem=lambda i:i+1, mem_from_int=lambda i:i-1), integer_setting("bchan", "B channel index", _settings.channel_b, 1, 128, int_from_mem=lambda i:i+1, mem_from_int=lambda i:i-1), integer_setting("pchan", "Priority channel index", _settings.priority_channel, 1, 128, int_from_mem=lambda i:i+1, mem_from_int=lambda i:i-1), list_setting("cactive", "Call channel active?", _settings.call_channel_active, NO_YES), list_setting("scanm", "Scan mode", _settings.scan_mode, SCAN_MODES), list_setting("swait", "Wait time", _settings.wait_time, WAIT_TIMES), # it is unclear what this option below does, # possibly squelch tail elimination? list_setting("tail", "Relay without disable tail (?)", _settings.relay_without_disable_tail, NO_YES), list_setting("batsav", "Battery saving mode", _settings.battery_save, OFF_ON), ] # user interface related settings interface_settings = [ list_setting("sidehold", "Side button hold action", _settings.side_button_hold_mode, BUTTON_MODES), list_setting("sideclick", "Side button click action", _settings.side_button_click_mode, BUTTON_MODES), list_setting("bootmt", "Boot message type", _settings.boot_message_mode, BOOT_MESSAGE_TYPES), ff_string_setting("bootm", "Boot message", _settings.boot_message, 0, 6), list_setting("beep", "Key beep", _settings.key_beep, OFF_ON), list_setting("talkback", "Menu talkback", _settings.voice_announce, TALKBACK), list_setting("sidetone", "DTMF sidetone", _settings.dtmf_sidetone, OFF_ON), list_setting("roger", "Roger beep", _settings.use_roger_beep, ROGER_BEEP), list_setting("backlm", "Backlight mode", _settings.backlight_mode, BACKLIGHT_MODES), list_setting("backlc", "Backlight color", _settings.backlight_color, BACKLIGHT_COLORS), integer_setting("squelch", "Squelch level", _settings.squelch_level, 0, 9), list_setting("voxg", "Vox gain", _settings.vox_gain, VOX_GAIN), list_setting("voxd", "Vox delay", _settings.vox_delay, VOX_DELAYS), list_setting("txal", "Trinsmit time alarm", _settings.tx_timeout, TRANSMIT_ALARMS), ] # settings related to tone/data sending and interpretation data_general_settings = [ list_setting("disptt", "Display PTT ID", _settings.dis_ptt_id, NO_YES), list_setting("pttidt", "PTT ID signal type", _settings.ptt_id_type, DATA_MODES) ] data_msk_settings = [ ff_string_setting("bot", "MSK PTT ID (BOT)", _ptt_id_data[0].entry, 0, 6, autowrite=False), ff_string_setting("eot", "MSK PTT ID (EOT)", _ptt_id_data[1].entry, 0, 6, autowrite=False), ff_string_setting("id", "MSK ID code", _msk_settings.id_code, 0, 4, charset=HEXADECIMAL), list_setting("mskr", "MSK reverse", _settings.msk_reverse, NO_YES) ] data_dtmf_settings = [ dtmf_string_setting("bot", "DTMF PTT ID (BOT)", _ptt_id_data[0].entry, _ptt_id_data[0].length, 0, 8, autowrite=False), dtmf_string_setting("eot", "DTMF PTT ID (EOT)", _ptt_id_data[1].entry, _ptt_id_data[1].length, 0, 8, autowrite=False), dtmf_string_setting("id", "DTMF ID code", _dtmf_settings.id_code, _dtmf_settings.id_code_length, 3, 8), integer_setting("time", "Digit time (ms)", _dtmf_settings.timing.digit_length, 50, 200, step=10, int_from_mem=lambda x:x*10, mem_from_int=lambda x:int(x/10)), integer_setting("pause", "Inter digit time (ms)", _dtmf_settings.timing.digit_length, 50, 200, step=10, int_from_mem=lambda x:x*10, mem_from_int=lambda x:int(x/10)), integer_setting("time1", "First digit time (ms)", _dtmf_settings.timing.digit_length, 50, 200, step=10, int_from_mem=lambda x:x*10, mem_from_int=lambda x:int(x/10)), integer_setting("pause1", "First digit delay (ms)", _dtmf_settings.timing.digit_length, 100, 1000, step=50, int_from_mem=lambda x:x*50, mem_from_int=lambda x:int(x/50)), list_setting("arst", "Auto reset time", _dtmf_settings.reset_time, DTMF_TONE_RESET_TIME), list_setting("grp", "Group code", _dtmf_settings.group_code, DTMF_GROUPS), dtmf_string_setting("stunt", "TX Stun code", _dtmf_settings.tx_stun_code, _dtmf_settings.tx_stun_code_length, 3, 8), dtmf_string_setting("cstunt", "TX Stun cancel code", _dtmf_settings.cancel_tx_stun_code, _dtmf_settings.cancel_tx_stun_code_length, 3, 8), dtmf_string_setting("stunrt", "RX/TX Stun code", _dtmf_settings.rxtx_stun_code, _dtmf_settings.rxtx_stun_code_length, 3, 8), dtmf_string_setting("cstunrt", "RX/TX Stun cancel code", _dtmf_settings.cancel_rxtx_stun_code, _dtmf_settings.cancel_rxtx_stun_code_length, 3, 8), list_setting("altr", "Alert/Transpond", _dtmf_settings.alert_transpond, DTMF_ALERT_TRANSPOND), ] data_5tone_settings = [ five_tone_string_setting("bot", "5-Tone PTT ID (BOT)", _ptt_id_data[0].entry, autowrite=False), five_tone_string_setting("eot", "5-Tone PTT ID (EOT)", _ptt_id_data[1].entry, autowrite=False), five_tone_string_setting("id", "5-tone ID code", _5tone_settings.id_code), list_setting("arst", "Auto reset time", _5tone_settings.reset_time, TONE_RESET_TIME), five_tone_string_setting("stunt", "TX Stun code", _5tone_settings.tx_stun_code), five_tone_string_setting("cstunt", "TX Stun cancel code", _5tone_settings.cancel_tx_stun_code), five_tone_string_setting("stunrt", "RX/TX Stun code", _5tone_settings.rxtx_stun_code), five_tone_string_setting("cstunrt", "RX/TX Stun cancel code", _5tone_settings.cancel_rxtx_stun_code), list_setting("altr", "Alert/Transpond", _5tone_settings.alert_transpond, FIVE_TONE_ALERT_TRANSPOND), list_setting("std", "5-Tone standard", _5tone_settings.tone_standard, FIVE_TONE_STANDARDS), ] for i in range(4): s = ['z1', 'z2', 'c1', 'ct'][i] l = FIVE_TONE_STANDARDS[i] data_5tone_settings.append( settings.RadioSettingGroup( s, '%s settings' % l, integer_setting("%speriod" % s, "%s Period (ms)" % l, _5tone_settings.tone_settings[i].period, 20, 255), list_setting("%sgrp" % s, "%s Group code" % l, _5tone_settings.tone_settings[i].group_code, HEXADECIMAL), list_setting("%srpt" % s, "%s Repeat code" % l, _5tone_settings.tone_settings[i].repeat_code, HEXADECIMAL))) data_msk_call_list = [] data_dtmf_call_list = [] data_5tone_call_list = [] for i in range(9): j = i+1 data_msk_call_list.append( ff_string_setting("ce%d" % i, "MSK call entry %d" % j, _msk_settings.phone_book[i].entry, 0, 4, charset=HEXADECIMAL)) data_dtmf_call_list.append( dtmf_string_setting("ce%d" % i, "DTMF call entry %d" % j, _dtmf_settings.phone_book[i].entry, _dtmf_settings.phone_book[i].length, 0, 10)) data_5tone_call_list.append( five_tone_string_setting("ce%d" % i, "5-Tone call entry %d" % j, _5tone_settings.phone_book[i].entry)), data_settings = data_general_settings data_settings.extend([ settings.RadioSettingGroup("MSK_s", "MSK settings", *data_msk_settings), settings.RadioSettingGroup("MSK_c", "MSK call list", *data_msk_call_list), settings.RadioSettingGroup("DTMF_s", "DTMF settings", *data_dtmf_settings), settings.RadioSettingGroup("DTMF_c", "DTMF call list", *data_dtmf_call_list), settings.RadioSettingGroup("5-Tone_s", "5-tone settings", *data_5tone_settings), settings.RadioSettingGroup("5-Tone_c", "5-tone call list", *data_5tone_call_list) ]) # settings related to the various ways the radio can be locked down locking_settings = [ list_setting("autolock", "Automatic timed keypad lock", _settings.auto_keylock, OFF_ON), list_setting("lockon", "Current status of keypad lock", _settings.keypad_lock, INACTIVE_ACTIVE), list_setting("nokeypad", "Disable keypad", _settings.allow_keypad, YES_NO), list_setting("rxstun", "Disable receiver (rx stun)", _settings.rx_stun, NO_YES), list_setting("txstun", "Disable transmitter (tx stun)", _settings.tx_stun, NO_YES), ] # broadcast fm radio settings broadcast_settings = [ list_setting("band", "Frequency interval", _broadcast.receive_range, BFM_BANDS), list_setting("stride", "VFO step", _broadcast.channel_stepping, BFM_STRIDE), frequency_setting("vfo", "VFO frequency (MHz)", _broadcast.vfo_freq) ] for i in range(10): broadcast_settings.append( frequency_setting("bcd%d" % i, "Memory %d frequency" % i, _broadcast.memory[i].entry)) return settings.RadioSettings( settings.RadioSettingGroup("model", "Model/Unit information", *model_unit_settings), settings.RadioSettingGroup("radio", "Radio/Channel settings", *radio_channel_settings), settings.RadioSettingGroup("interface", "Interface", *interface_settings), settings.RadioSettingGroup("data", "Data", *data_settings), settings.RadioSettingGroup("locking", "Locking", *locking_settings), settings.RadioSettingGroup("broadcast", "Broadcast FM radio settings", *broadcast_settings) ) def set_settings(self, s, parent=''): # The helper classes take care of all settings except these below, # since it is a single instance of settings having interdependencies, # i.e., which value that gets written to the memory depends on # the value of another setting. The value of the ptt id type setting # decides which of the msk/dtmf/5-tone ptt id strings are # actually written to memory. ds = sbyn(s, 'data') idts = sbyn(ds, 'pttidt').value idtv = idts.get_value() cs = sbyn(ds, idtv+'_s') tss = [sbyn(cs, e).value for e in ['bot', 'eot']] for ts in tss: ts.write_mem() chirp-daily-20170714/chirp/drivers/vx2.py0000644000016101777760000005637712476257220021270 0ustar jenkinsnogroup00000000000000# Copyright 2013 Jens Jensen # based on modification of Dan Smith's and Rick Farina'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.drivers import yaesu_clone from chirp import chirp_common, directory, bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings import os import traceback import re import logging LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x7F52; u8 checksum; #seekto 0x005A; u8 banksoff1; #seekto 0x00DA; u8 banksoff2; #seekto 0x0068; u16 prioritychan1; #seekto 0x00E8; u16 prioritychan2; #seekto 0x110; struct { u8 unk1; u8 unk2; u8 nfm_sql; u8 wfm_sql; u8 rfsql; u8 vfomode:1, cwid_en:1, scan_lamp:1, unk3:1, ars:1, beep:1, split:1, dtmfmode:1; u8 busyled:1, unk4:2, bclo:1, edgebeep:1, unk5:2, txsave:1; u8 unk6:2, smartsearch:1, unk7:1, artsinterval:1, unk8:1, hmrv:1, moni_tcall:1; u8 unk9:5, dcsrev:1, unk10:1, mwmode:1; u8 internet_mode:1, internet_key:1, wx_alert:1, unk11:2, att:1, unk12:2; u8 lamp; u8 dimmer; u8 rxsave; u8 resume; u8 chcounter; u8 openmsgmode; u8 openmsg[6]; u8 cwid[16]; u8 unk13[16]; u8 artsbeep; u8 bell; u8 apo; u8 tot; u8 lock; u8 mymenu; u8 unk14[4]; u8 emergmode; } settings; #seekto 0x0192; struct { u8 digits[16]; } dtmf[9]; #seekto 0x016A; struct { u16 in_use; } bank_used[20]; #seekto 0x0396; struct { u8 name[6]; } wxchannels[10]; #seekto 0x05C2; struct { u16 channels[100]; } banks[20]; #seekto 0x1562; 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[500]; struct mem_struct { u8 unknown1:2, txnarrow:1, clk:1, unknown2:4; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, unknown3:4, tmode:2; u8 name[6]; bbcd offset[3]; u8 unknown4:2, tone:6; u8 unknown5:1, dcs:7; u8 unknown6; }; #seekto 0x17C2; struct mem_struct memory[1000]; struct { struct mem_struct lower; struct mem_struct upper; } pms[50]; #seekto 0x03D2; struct mem_struct home[12]; #seekto 0x04E2; struct mem_struct vfo[12]; """ VX2_DUPLEX = ["", "-", "+", "split"] # NFM handled specially in radio VX2_MODES = ["FM", "AM", "WFM", "Auto", "NFM"] VX2_TMODES = ["", "Tone", "TSQL", "DTCS"] VX2_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0] CHARSET = list("0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + "+-/\x00[](){}\x00\x00_" + ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + ("\x00" * 64)) DTMFCHARSET = list("0123456789ABCD*#") POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50), chirp_common.PowerLevel("Low", watts=0.10)] class VX2BankModel(chirp_common.BankModel): """A VX-2 bank model""" def get_num_mappings(self): return len(self.get_mappings()) def get_mappings(self): banks = self._radio._memobj.banks bank_mappings = [] for index, _bank in enumerate(banks): bank = chirp_common.Bank(self, "%i" % index, "b%i" % (index + 1)) bank.index = index bank_mappings.append(bank) return bank_mappings def _get_channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xFFFF: return set() _members = self._radio._memobj.banks[bank.index] return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF]) def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.banks[bank.index] if len(channels_in_bank) > len(_members.channels): raise Exception("Too many entries in bank %d" % bank.index) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channels[index] = channel_number - 1 empty = index + 1 for index in range(empty, len(_members.channels)): _members.channels[index] = 0xFFFF def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0x0000 # also needed for unit to recognize banks? self._radio._memobj.banksoff1 = 0x00 self._radio._memobj.banksoff2 = 0x00 # todo: turn back off (0xFF) when all banks are empty? def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) if not channels_in_bank: _bank_used = self._radio._memobj.bank_used[bank.index] _bank_used.in_use = 0xFFFF def get_mapping_memories(self, bank): memories = [] for channel in self._get_channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._get_channel_numbers_in_bank(bank): banks.append(bank) return banks def _wipe_memory(mem): mem.set_raw("\x00" * (mem.size() / 8)) @directory.register class VX2Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu VX-2""" MODEL = "VX-2" _model = "AH015" BAUD_RATE = 19200 _block_lengths = [10, 8, 32577] _memsize = 32595 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank = True rf.has_settings = True rf.has_dtcs_polarity = False rf.valid_modes = list(set(VX2_MODES)) rf.valid_tmodes = list(VX2_TMODES) rf.valid_duplexes = list(VX2_DUPLEX) rf.valid_tuning_steps = list(VX2_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, 1000) rf.can_odd_split = True rf.has_ctone = False return rf def _checksums(self): return [yaesu_clone.YaesuChecksum(0x0000, 0x7F51)] 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-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 = VX2_TMODES[_mem.tmode] mem.duplex = VX2_DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) if _mem.txnarrow and _mem.mode == VX2_MODES.index("FM"): # narrow + FM mem.mode = "NFM" else: mem.mode = VX2_MODES[_mem.mode] mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = VX2_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 _flag["%s_valid" % nibble] = True _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.tmode = VX2_TMODES.index(mem.tmode) _mem.duplex = VX2_DUPLEX.index(mem.duplex) if mem.mode == "NFM": _mem.mode = VX2_MODES.index("FM") _mem.txnarrow = True else: _mem.mode = VX2_MODES.index(mem.mode) _mem.txnarrow = False _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = VX2_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(): # empty name field, disable name display # leftmost bit of name chararr is: # 1 = display freq, 0 = display name _mem.name[0] |= 0x80 # for now, clear unknown fields for i in range(1, 7): setattr(_mem, "unknown%i" % i, 0) def validate_memory(self, mem): msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem) return msgs def get_bank_model(self): return VX2BankModel(self) def _decode_chars(self, inarr): LOG.debug("@_decode_chars, type: %s" % type(inarr)) LOG.debug(inarr) outstr = "" for i in inarr: if i == 0xFF: break outstr += CHARSET[i & 0x7F] return outstr.rstrip() def _encode_chars(self, instr, length=16): LOG.debug("@_encode_chars, type: %s" % type(instr)) LOG.debug(instr) outarr = [] instr = str(instr) for i in range(0, length): if i < len(instr): outarr.append(CHARSET.index(instr[i])) else: outarr.append(0xFF) return outarr def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") dtmf = RadioSettingGroup("dtmf", "DTMF") arts = RadioSettingGroup("arts", "ARTS") top = RadioSettings(basic, arts, dtmf) options = ["off", "30m", "1h", "3h", "5h", "8h"] rs = RadioSetting( "apo", "APO time (hrs)", RadioSettingValueList(options, options[_settings.apo])) basic.append(rs) rs = RadioSetting( "ars", "Auto Repeater Shift", RadioSettingValueBoolean(_settings.ars)) basic.append(rs) rs = RadioSetting( "att", "Attenuation", RadioSettingValueBoolean(_settings.att)) basic.append(rs) rs = RadioSetting( "bclo", "Busy Channel Lockout", RadioSettingValueBoolean(_settings.bclo)) basic.append(rs) rs = RadioSetting( "beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) options = ["off", "1", "3", "5", "8", "cont"] rs = RadioSetting( "bell", "Bell", RadioSettingValueList(options, options[_settings.bell])) basic.append(rs) rs = RadioSetting( "busyled", "Busy LED", RadioSettingValueBoolean(_settings.busyled)) basic.append(rs) options = ["5", "10", "50", "100"] rs = RadioSetting( "chcounter", "Channel Counter (MHz)", RadioSettingValueList(options, options[_settings.chcounter])) basic.append(rs) rs = RadioSetting( "dcsrev", "DCS Reverse", RadioSettingValueBoolean(_settings.dcsrev)) basic.append(rs) options = map(str, range(0, 12+1)) rs = RadioSetting( "dimmer", "Dimmer", RadioSettingValueList(options, options[_settings.dimmer])) basic.append(rs) rs = RadioSetting( "edgebeep", "Edge Beep", RadioSettingValueBoolean(_settings.edgebeep)) basic.append(rs) options = ["beep", "strobe", "bp+str", "beam", "bp+beam", "cw", "bp+cw"] rs = RadioSetting( "emergmode", "Emergency Mode", RadioSettingValueList(options, options[_settings.emergmode])) basic.append(rs) options = ["Home", "Reverse"] rs = RadioSetting( "hmrv", "HM/RV key", RadioSettingValueList(options, options[_settings.hmrv])) basic.append(rs) options = ["My Menu", "Internet"] rs = RadioSetting( "internet_key", "Internet key", RadioSettingValueList( options, options[_settings.internet_key])) basic.append(rs) options = ["1 APO", "2 AR BEP", "3 AR INT", "4 ARS", "5 ATT", "6 BCLO", "7 BEEP", "8 BELL", "9 BSYLED", "10 CH CNT", "11 CK SFT", "12 CW ID", "13 DC VLT", "14 DCS CD", "15 DCS RV", "16 DIMMER", "17 DTMF", "18 DTMF S", "19 EDG BP", "20 EMG S", "21 HLFDEV", "22 HM/RV", "23 INT MD", "24 LAMP", "25 LOCK", "26 M/T-CL", "27 MW MD", "28 NAME", "29 NM SET", "30 OPNMSG", "31 RESUME", "32 RF SQL", "33 RPT", "34 RX MD", "35 RXSAVE", "36 S SCH", "37 SCNLMP", "38 SHIFT", "39 SKIP", "40 SPLIT", "41 SQL", "42 SQL TYP", "43 STEP", "44 TN FRQ", "45 TOT", "46 TXSAVE", "47 VFO MD", "48 TR SQL (JAPAN)", "48 WX ALT"] rs = RadioSetting( "mymenu", "My Menu function", RadioSettingValueList(options, options[_settings.mymenu - 9])) basic.append(rs) options = ["wires", "link"] rs = RadioSetting( "internet_mode", "Internet mode", RadioSettingValueList( options, options[_settings.internet_mode])) basic.append(rs) options = ["key", "cont", "off"] rs = RadioSetting( "lamp", "Lamp mode", RadioSettingValueList(options, options[_settings.lamp])) basic.append(rs) options = ["key", "dial", "key+dial", "ptt", "key+ptt", "dial+ptt", "all"] rs = RadioSetting( "lock", "Lock mode", RadioSettingValueList(options, options[_settings.lock])) basic.append(rs) options = ["monitor", "tone call"] rs = RadioSetting( "moni_tcall", "MONI key", RadioSettingValueList(options, options[_settings.moni_tcall])) basic.append(rs) options = ["lower", "next"] rs = RadioSetting( "mwmode", "Memory write mode", RadioSettingValueList(options, options[_settings.mwmode])) basic.append(rs) options = map(str, range(0, 15+1)) rs = RadioSetting( "nfm_sql", "NFM Sql", RadioSettingValueList(options, options[_settings.nfm_sql])) basic.append(rs) options = map(str, range(0, 8+1)) rs = RadioSetting( "wfm_sql", "WFM Sql", RadioSettingValueList(options, options[_settings.wfm_sql])) basic.append(rs) options = ["off", "dc", "msg"] rs = RadioSetting( "openmsgmode", "Opening message", RadioSettingValueList(options, options[_settings.openmsgmode])) basic.append(rs) openmsg = RadioSettingValueString( 0, 6, self._decode_chars(_settings.openmsg.get_value())) openmsg.set_charset(CHARSET) rs = RadioSetting("openmsg", "Opening Message", openmsg) basic.append(rs) options = ["3s", "5s", "10s", "busy", "hold"] rs = RadioSetting( "resume", "Resume", RadioSettingValueList(options, options[_settings.resume])) basic.append(rs) options = ["off"] + map(str, range(1, 9+1)) rs = RadioSetting( "rfsql", "RF Sql", RadioSettingValueList(options, options[_settings.rfsql])) basic.append(rs) options = ["off", "200ms", "300ms", "500ms", "1s", "2s"] rs = RadioSetting( "rxsave", "RX pwr save", RadioSettingValueList(options, options[_settings.rxsave])) basic.append(rs) options = ["single", "cont"] rs = RadioSetting( "smartsearch", "Smart search", RadioSettingValueList(options, options[_settings.smartsearch])) basic.append(rs) rs = RadioSetting( "scan_lamp", "Scan lamp", RadioSettingValueBoolean(_settings.scan_lamp)) basic.append(rs) rs = RadioSetting( "split", "Split", RadioSettingValueBoolean(_settings.split)) basic.append(rs) options = ["off", "1", "3", "5", "10"] rs = RadioSetting( "tot", "TOT (mins)", RadioSettingValueList(options, options[_settings.tot])) basic.append(rs) rs = RadioSetting( "txsave", "TX pwr save", RadioSettingValueBoolean(_settings.txsave)) basic.append(rs) options = ["all", "band"] rs = RadioSetting( "vfomode", "VFO mode", RadioSettingValueList(options, options[_settings.vfomode])) basic.append(rs) rs = RadioSetting( "wx_alert", "WX Alert", RadioSettingValueBoolean(_settings.wx_alert)) basic.append(rs) # todo: priority channel # todo: handle WX ch labels # arts settings (ar beep, ar int, cwid en, cwid field) options = ["15s", "25s"] rs = RadioSetting( "artsinterval", "ARTS Interval", RadioSettingValueList( options, options[_settings.artsinterval])) arts.append(rs) options = ["off", "in range", "always"] rs = RadioSetting( "artsbeep", "ARTS Beep", RadioSettingValueList(options, options[_settings.artsbeep])) arts.append(rs) rs = RadioSetting( "cwid_en", "CWID Enable", RadioSettingValueBoolean(_settings.cwid_en)) arts.append(rs) cwid = RadioSettingValueString( 0, 16, self._decode_chars(_settings.cwid.get_value())) cwid.set_charset(CHARSET) rs = RadioSetting("cwid", "CWID", cwid) arts.append(rs) # setup dtmf options = ["manual", "auto"] rs = RadioSetting( "dtmfmode", "DTMF mode", RadioSettingValueList(options, options[_settings.dtmfmode])) dtmf.append(rs) for i in range(0, 8+1): name = "dtmf" + str(i+1) dtmfsetting = self._memobj.dtmf[i] # dtmflen = getattr(_settings, objname + "_len") dtmfstr = "" for c in dtmfsetting.digits: if c < len(DTMFCHARSET): dtmfstr += DTMFCHARSET[c] LOG.debug(dtmfstr) dtmfentry = RadioSettingValueString(0, 16, dtmfstr) dtmfentry.set_charset(DTMFCHARSET + list(" ")) rs = RadioSetting(name, name.upper(), dtmfentry) dtmf.append(rs) return top def set_settings(self, uisettings): for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: setting = element.get_name() _settings = self._memobj.settings if re.match('dtmf\d', setting): # set dtmf fields dtmfstr = str(element.value).strip() newval = [] for i in range(0, 16): if i < len(dtmfstr): newval.append(DTMFCHARSET.index(dtmfstr[i])) else: newval.append(0xFF) LOG.debug(newval) idx = int(setting[-1:]) - 1 _settings = self._memobj.dtmf[idx] _settings.digits = newval continue if setting == "prioritychan": # prioritychan is top-level member, fix 0 index element.value -= 1 _settings = self._memobj if setting == "mymenu": opts = element.value.get_options() optsidx = opts.index(element.value.get_value()) idx = optsidx + 9 setattr(_settings, "mymenu", idx) continue oldval = getattr(_settings, setting) newval = element.value if setting == "cwid": newval = self._encode_chars(newval) if setting == "openmsg": newval = self._encode_chars(newval, 6) LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/icomciv.py0000644000016101777760000005133112761227400022155 0ustar jenkinsnogroup00000000000000 import struct import logging from chirp.drivers import icf from chirp import chirp_common, util, errors, bitwise, directory from chirp.memmap import MemoryMap from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueList, RadioSettingValueBoolean LOG = logging.getLogger(__name__) MEM_FORMAT = """ bbcd number[2]; u8 unknown:3 split:1, unknown_0:4; lbcd freq[5]; u8 unknown2:5, mode:3; u8 filter; u8 unknown_1:3, dig:1, unknown_2:4; """ # http://www.vk4adc.com/ # web/index.php/reference-information/49-general-ref-info/182-civ7400 MEM_IC7000_FORMAT = """ u8 bank; bbcd number[2]; u8 spl:4, skip:4; lbcd freq[5]; u8 mode; u8 filter; u8 duplex:4, tmode:4; bbcd rtone[3]; bbcd ctone[3]; u8 dtcs_polarity; bbcd dtcs[2]; lbcd freq_tx[5]; u8 mode_tx; u8 filter_tx; u8 duplex_tx:4, tmode_tx:4; bbcd rtone_tx[3]; bbcd ctone_tx[3]; u8 dtcs_polarity_tx; bbcd dtcs_tx[2]; char name[9]; """ MEM_IC7100_FORMAT = """ u8 bank; // 1 bank number bbcd number[2]; // 2,3 u8 splitSelect; // 4 split and select memory settings lbcd freq[5]; // 5-9 operating freq u8 mode; // 10 operating mode u8 filter; // 11 filter u8 dataMode; // 12 data mode setting (on or off) u8 duplex:4, // 13 duplex on/-/+ tmode:4; // 13 tone u8 dsql:4, // 14 digital squelch unknown1:4; // 14 zero bbcd rtone[3]; // 15-17 repeater tone freq bbcd ctone[3]; // 18-20 tone squelch setting u8 dtcsPolarity; // 21 DTCS polarity u8 unknown2:4, // 22 zero firstDtcs:4; // 22 first digit of DTCS code u8 secondDtcs:4, // 23 second digit DTCS thirdDtcs:4; // 23 third digit DTCS u8 digitalSquelch; // 24 Digital code squelch setting u8 duplexOffset[3]; // 25-27 duplex offset freq char destCall[8]; // 28-35 destination call sign char accessRepeaterCall[8];// 36-43 access repeater call sign char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign bbcd duplexSettings[47]; // repeat of 5-51 for duplex char name[16]; // 52-60 Name of station """ 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 dtcs_polarity; bbcd dtcs[2]; u8 unknown[11]; char name[9]; """ SPLIT = ["", "spl"] 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) LOG.debug("%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: LOG.debug("Echo differed (%i/%i)" % (len(raw), len(echo))) LOG.debug(util.hexprint(raw)) LOG.debug(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: LOG.debug("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]) LOG.debug("%02x <- %02x:\n%s" % (dst, src, 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 BankMemFrame(MemFrame): """A memory frame for radios with multiple banks""" FORMAT = MEM_IC7000_FORMAT _bnk = 0 def set_location(self, loc, bank=1): self._loc = loc self._bnk = bank self._data = struct.pack( ">BH", int("%02i" % bank, 16), int("%04i" % loc, 16)) def make_empty(self): """Mark as empty so the radio will erase the memory""" self._data = struct.pack( ">BHB", int("%02i" % self._bnk, 16), int("%04i" % self._loc, 16), 0xFF) def get_obj(self): self._data = MemoryMap(str(self._data)) # Make sure we're assignable return bitwise.parse(self.FORMAT, self._data) class IC7100MemFrame(BankMemFrame): FORMAT = MEM_IC7100_FORMAT 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 # complete list of modes from CI-V documentation # each radio supports a subset # WARNING: "S-AM" and "PSK" are not valid (yet) for chirp _MODES = [ "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR" "RTTYR", "S-AM", "PSK", None, None, None, None, None, None, None, None, None, None, None, None, None, "DV", ] def mem_to_ch_bnk(self, mem): l, h = self._bank_index_bounds bank_no = (mem // (h - l + 1)) + l channel = mem % (h - l + 1) + l return (channel, bank_no) 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) LOG.debug("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() LOG.debug("Interface echo: %s" % self._willecho) self.pipe.timeout = 1 # f = Frame() # f.set_command(0x19, 0x00) # self._send_frame(f) # # res = f.read(self.pipe) # if res: # LOG.debug("Result: %x->%x (%i)" % # (res[0], res[1], len(f.get_data()))) # LOG.debug(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"]() if self._rf.has_bank: ch, bnk = self.mem_to_ch_bnk(number) f.set_location(ch, bnk) loc = "bank %i, channel %02i" % (bnk, ch) else: f.set_location(number) loc = "number %i" % number self._send_frame(f) f.read(self.pipe) if f.get_data() and f.get_data()[-1] == "\xFF": return "Memory " + loc + " empty." else: return repr(f.get_obj()) # We have a simple mapping between the memory location in the frequency # editor and (bank, channel) of the radio. The mapping doesn't # change so we use a little math to calculate what bank a location # is in. We can't change the bank a location is in so we just pass. def _get_bank(self, loc): l, h = self._bank_index_bounds return loc // (h - l + 1) def _set_bank(self, loc, bank): pass def get_memory(self, number): LOG.debug("Getting %i" % number) f = self._classes["mem"]() if self._rf.has_bank: ch, bnk = self.mem_to_ch_bnk(number) f.set_location(ch, bnk) LOG.debug("Bank %i, Channel %02i" % (bnk, ch)) else: f.set_location(number) self._send_frame(f) mem = chirp_common.Memory() mem.number = number mem.immutable = [] 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 LOG.debug("Found %i empty" % mem.number) return mem memobj = f.get_obj() LOG.debug(repr(memobj)) try: if memobj.skip == 1: mem.skip = "" else: mem.skip = "S" except AttributeError: pass mem.freq = int(memobj.freq) try: mem.mode = self._MODES[memobj.mode] # We do not know what a variety of the positions between # PSK and DV mean, so let's behave as if those values # are not set to maintain consistency between known-unknown # values and unknown-unknown ones. if mem.mode is None: raise IndexError(memobj.mode) except IndexError: LOG.error( "Bank %s location %s is set for mode %s, but no known " "mode matches that value.", int(memobj.bank), int(memobj.number), repr(memobj.mode), ) raise 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_polarity: if memobj.dtcs_polarity == 0x11: mem.dtcs_polarity = "RR" elif memobj.dtcs_polarity == 0x10: mem.dtcs_polarity = "RN" elif memobj.dtcs_polarity == 0x01: mem.dtcs_polarity = "NR" else: mem.dtcs_polarity = "NN" if self._rf.has_dtcs: 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] if self._rf.can_odd_split and memobj.spl: mem.duplex = "split" mem.offset = int(memobj.freq_tx) mem.immutable = [] else: mem.immutable = ["offset"] mem.extra = RadioSettingGroup("extra", "Extra") try: dig = RadioSetting("dig", "Digital", RadioSettingValueBoolean(bool(memobj.dig))) except AttributeError: pass else: dig.set_doc("Enable digital mode") mem.extra.append(dig) options = ["Wide", "Mid", "Narrow"] try: fil = RadioSetting( "filter", "Filter", RadioSettingValueList(options, options[memobj.filter - 1])) except AttributeError: pass else: fil.set_doc("Filter settings") mem.extra.append(fil) return mem def set_memory(self, mem): LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number)) if self._rf.has_bank: ch, bnk = self.mem_to_ch_bnk(mem.number) LOG.debug("Bank %i, Channel %02i" % (bnk, ch)) f = self._get_template_memory() if mem.empty: if self._rf.has_bank: f.set_location(ch, bnk) else: f.set_location(mem.number) LOG.debug("Making %i empty" % mem.number) f.make_empty() self._send_frame(f) # The next two lines accept the radio's status after setting the memory # and reports the results to the debug log. This is needed for the # IC-7000. No testing was done to see if it breaks memory delete on the # IC-746 or IC-7200. f = self._recv_frame() LOG.debug("Result:\n%s" % util.hexprint(f.get_data())) return # f.set_data(MemoryMap(self.get_raw_memory(mem.number))) # f.initialize() memobj = f.get_obj() if self._rf.has_bank: memobj.bank = bnk memobj.number = ch else: memobj.number = mem.number if mem.skip == "S": memobj.skip = 0 else: try: memobj.skip = 1 except KeyError: pass memobj.freq = int(mem.freq) memobj.mode = self._MODES.index(mem.mode) if self._rf.has_name: name_length = len(memobj.name.get_value()) memobj.name = mem.name.ljust(name_length)[:name_length] if self._rf.valid_tmodes: memobj.tmode = self._rf.valid_tmodes.index(mem.tmode) if self._rf.has_ctone: memobj.ctone = int(mem.ctone * 10) memobj.rtone = int(mem.rtone * 10) if self._rf.has_dtcs_polarity: if mem.dtcs_polarity == "RR": memobj.dtcs_polarity = 0x11 elif mem.dtcs_polarity == "RN": memobj.dtcs_polarity = 0x10 elif mem.dtcs_polarity == "NR": memobj.dtcs_polarity = 0x01 else: memobj.dtcs_polarity = 0x00 if self._rf.has_dtcs: bitwise.int_to_bcd(memobj.dtcs, mem.dtcs) if self._rf.can_odd_split and mem.duplex == "split": memobj.spl = 1 memobj.duplex = 0 memobj.freq_tx = int(mem.offset) memobj.tmode_tx = memobj.tmode memobj.ctone_tx = memobj.ctone memobj.rtone_tx = memobj.rtone memobj.dtcs_polarity_tx = memobj.dtcs_polarity memobj.dtcs_tx = memobj.dtcs elif self._rf.valid_duplexes: memobj.duplex = self._rf.valid_duplexes.index(mem.duplex) for setting in mem.extra: if setting.get_name() == "filter": setattr(memobj, setting.get_name(), int(setting.value) + 1) else: setattr(memobj, setting.get_name(), setting.value) LOG.debug(repr(memobj)) self._send_frame(f) f = self._recv_frame() LOG.debug("Result:\n%s" % util.hexprint(f.get_data())) @directory.register class Icom7200Radio(IcomCIVRadio): """Icom IC-7200""" MODEL = "7200" _model = "\x76" _template = 201 _num_banks = 1 # Banks not supported 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.has_tuning_step = False self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "CWR", "RTTYR"] self._rf.valid_tmodes = [] self._rf.valid_duplexes = [] self._rf.valid_bands = [(30000, 60000000)] self._rf.valid_skips = [] self._rf.memory_bounds = (1, 201) @directory.register class Icom7000Radio(IcomCIVRadio): """Icom IC-7000""" MODEL = "IC-7000" _model = "\x70" _template = 102 _num_banks = 5 # Banks A-E _bank_index_bounds = (1, 99) _bank_class = icf.IcomBank def _initialize(self): self._classes["mem"] = BankMemFrame self._rf.has_bank = True self._rf.has_dtcs_polarity = True self._rf.has_dtcs = True self._rf.has_ctone = True self._rf.has_offset = True self._rf.has_name = True self._rf.has_tuning_step = False self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM"] self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] self._rf.valid_duplexes = ["", "-", "+", "split"] self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)] self._rf.valid_tuning_steps = [] self._rf.valid_skips = ["S", ""] self._rf.valid_name_length = 9 self._rf.valid_characters = chirp_common.CHARSET_ASCII self._rf.memory_bounds = (0, 99 * self._num_banks - 1) self._rf.can_odd_split = True @directory.register class Icom7100Radio(IcomCIVRadio): """Icom IC-7100""" MODEL = "IC-7100" _model = "\x88" _template = 102 _num_banks = 5 _bank_index_bounds = (1, 99) _bank_class = icf.IcomBank def _initialize(self): self._classes["mem"] = IC7100MemFrame self._rf.has_bank = True self._rf.has_bank_index = False self._rf.has_bank_names = 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", "WFM", "CWR", "RTTYR", "DV" ] 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 = 16 self._rf.valid_characters = chirp_common.CHARSET_ASCII self._rf.memory_bounds = (0, 99 * self._num_banks - 1) @directory.register class Icom746Radio(IcomCIVRadio): """Icom IC-746""" MODEL = "746" BAUD_RATE = 9600 _model = "\x56" _template = 102 _num_banks = 1 # Banks not supported 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, (0x88, 0xE0): Icom7100Radio, (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(): LOG.debug("Got data, but not 1 byte:") LOG.debug(util.hexprint(f.get_data())) raise errors.RadioError("Unknown response") raise errors.RadioError("Unsupported model") chirp-daily-20170714/chirp/drivers/puxing.py0000644000016101777760000003640712646374217022060 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 import logging from chirp import util, chirp_common, bitwise, errors, directory from chirp.drivers.wouxun import wipe_memory, do_download, do_upload LOG = logging.getLogger(__name__) 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: LOG.debug(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 = ''.join(set(PUXING_CHARSET)) 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: rf.valid_bands = [PUXING_777_BANDS[1]] elif self._memobj.model.model == PUXING_MODELS[777]: limit_idx = self._memobj.model.limits - 0xEE try: rf.valid_bands = [PUXING_777_BANDS[limit_idx]] except IndexError: LOG.error("Invalid band index %i (0x%02x)" % (limit_idx, self._memobj.model.limits)) rf.valid_bands = [PUXING_777_BANDS[1]] elif self._memobj.model.model == PUXING_MODELS[328]: # There are PX-777 that says to be model 328 ... # for them we only know this freq limits till now if self._memobj.model.limits == 0xEE: rf.valid_bands = [PUXING_777_BANDS[1]] else: raise Exception("Unsupported band limits 0x%02x for PX-777" % (self._memobj.model.limits) + " submodel 328" " - PLEASE REPORT THIS ERROR TO DEVELOPERS!!") 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): # There are PX-777 that says to be model 328 ... return (len(filedata) == 3168 and (ord(filedata[0x080B]) == PUXING_MODELS[777] or (ord(filedata[0x080B]) == PUXING_MODELS[328] and ord(filedata[0x080A]) == 0xEE))) 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] mem.name = mem.name.rstrip() 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.timeout = 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) LOG.info("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: LOG.error("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-daily-20170714/chirp/drivers/ts2000.py0000644000016101777760000002424312652345221021457 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 re from chirp import chirp_common, directory, util, errors from chirp.drivers import kenwood_live from chirp.drivers.kenwood_live import KenwoodLiveRadio, \ command, iserr, NOCACHE TS2000_SSB_STEPS = [1.0, 2.5, 5.0, 10.0] TS2000_FM_STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] TS2000_DUPLEX = dict(kenwood_live.DUPLEX) TS2000_DUPLEX[3] = "=" TS2000_DUPLEX[4] = "split" TS2000_MODES = ["?", "LSB", "USB", "CW", "FM", "AM", "FSK", "CWR", "?", "FSKR"] TS2000_TMODES = ["", "Tone", "TSQL", "DTCS"] TS2000_TONES = list(chirp_common.OLD_TONES) TS2000_TONES.remove(69.3) @directory.register class TS2000Radio(KenwoodLiveRadio): """Kenwood TS-2000""" MODEL = "TS-2000" _upper = 289 _kenwood_split = True _kenwood_valid_tones = list(TS2000_TONES) 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 = ["LSB", "USB", "CW", "FM", "AM"] rf.valid_tmodes = list(TS2000_TMODES) rf.valid_tuning_steps = list(TS2000_SSB_STEPS + TS2000_FM_STEPS) rf.valid_bands = [(1000, 1300000000)] rf.valid_skips = ["", "S"] rf.valid_duplexes = TS2000_DUPLEX.values() # TS-2000 uses ";" as a message separator even though it seems to # allow you to to use all printable ASCII characters at the manual # controls. The radio doesn't send the name after the ";" if you # input one from the manual controls. rf.valid_characters = chirp_common.CHARSET_ASCII.replace(';', '') rf.valid_name_length = 7 # 7 character channel names rf.memory_bounds = (0, self._upper) return rf def _cmd_set_memory(self, number, spec): return "MW0%03i%s" % (number, spec) def _cmd_set_split(self, number, spec): return "MW1%03i%s" % (number, spec) def _cmd_get_memory(self, number): return "MR0%03i" % number def _cmd_get_split(self, number): return "MR1%03i" % number def _cmd_recall_memory(self, number): return "MC%03i" % (number) def _cmd_cur_memory(self, number): return "MC" def _cmd_erase_memory(self, number): # write a memory channel that's effectively zeroed except # for the channel number return "MW%04i%035i" % (number, 0) def erase_memory(self, number): if number not in self._memcache: return resp = command(self.pipe, *self._cmd_erase_memory(number)) if iserr(resp): raise errors.RadioError("Radio refused delete of %i" % number) del self._memcache[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 number in self._memcache 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 mem = self._parse_mem_spec(result) self._memcache[mem.number] = mem # check for split frequency operation if mem.duplex == "" and self._kenwood_split: result = command(self.pipe, *self._cmd_get_split(number)) self._parse_split_spec(mem, result) return mem def _parse_mem_spec(self, spec): mem = chirp_common.Memory() # pad string so indexes match Kenwood docs spec = " " + spec # use the same variable names as the Kenwood docs # _p1 = spec[3] _p2 = spec[4] _p3 = spec[5:7] _p4 = spec[7:18] _p5 = spec[18] _p6 = spec[19] _p7 = spec[20] _p8 = spec[21:23] _p9 = spec[23:25] _p10 = spec[25:28] # _p11 = spec[28] _p12 = spec[29] _p13 = spec[30:39] _p14 = spec[39:41] # _p15 = spec[41] _p16 = spec[42:49] mem.number = int(_p2 + _p3) # concat bank num and chan num mem.freq = int(_p4) mem.mode = TS2000_MODES[int(_p5)] mem.skip = ["", "S"][int(_p6)] mem.tmode = TS2000_TMODES[int(_p7)] # PL and T-SQL are 1 indexed, DTCS is 0 indexed mem.rtone = self._kenwood_valid_tones[int(_p8) - 1] mem.ctone = self._kenwood_valid_tones[int(_p9) - 1] mem.dtcs = chirp_common.DTCS_CODES[int(_p10)] mem.duplex = TS2000_DUPLEX[int(_p12)] mem.offset = int(_p13) # 9-digit if mem.mode in ["AM", "FM"]: mem.tuning_step = TS2000_FM_STEPS[int(_p14)] else: mem.tuning_step = TS2000_SSB_STEPS[int(_p14)] mem.name = _p16 return mem def _parse_split_spec(self, mem, spec): # pad string so indexes match Kenwood docs spec = " " + spec # use the same variable names as the Kenwood docs split_freq = int(spec[7:18]) if mem.freq != split_freq: mem.duplex = "split" mem.offset = split_freq return mem 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): memory.name = memory.name.rstrip() self._memcache[memory.number] = memory # if we're tuned to the channel, reload it r1 = command(self.pipe, *self._cmd_cur_memory(memory.number)) if not iserr(r1): pattern = re.compile("MC([0-9]{3})") match = pattern.search(r1) if match is not None: cur_mem = int(match.group(1)) if cur_mem == memory.number: cur_mem = \ command(self.pipe, *self._cmd_recall_memory(memory.number)) else: raise errors.InvalidDataError("Radio refused %i" % memory.number) # FIXME 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 _make_mem_spec(self, mem): if mem.duplex in " +-": duplex = util.get_dict_rev(TS2000_DUPLEX, mem.duplex) offset = mem.offset elif mem.duplex == "split": duplex = 0 offset = 0 else: print "Bug: unsupported duplex `%s'" % mem.duplex if mem.mode in ["AM", "FM"]: step = TS2000_FM_STEPS.index(mem.tuning_step) else: step = TS2000_SSB_STEPS.index(mem.tuning_step) # TS-2000 won't accept channels with tone mode off if they have # tone values if mem.tmode == "": rtone = 0 ctone = 0 dtcs = 0 else: # PL and T-SQL are 1 indexed, DTCS is 0 indexed rtone = (self._kenwood_valid_tones.index(mem.rtone) + 1) ctone = (self._kenwood_valid_tones.index(mem.ctone) + 1) dtcs = (chirp_common.DTCS_CODES.index(mem.dtcs)) spec = ( "%011i" % mem.freq, "%i" % (TS2000_MODES.index(mem.mode)), "%i" % (mem.skip == "S"), "%i" % TS2000_TMODES.index(mem.tmode), "%02i" % (rtone), "%02i" % (ctone), "%03i" % (dtcs), "0", # REVERSE status "%i" % duplex, "%09i" % offset, "%02i" % step, "0", # Memory Group number (0-9) "%s" % mem.name, ) return spec def _make_split_spec(self, mem): if mem.duplex in " +-": duplex = util.get_dict_rev(TS2000_DUPLEX, mem.duplex) elif mem.duplex == "split": duplex = 0 else: print "Bug: unsupported duplex `%s'" % mem.duplex if mem.mode in ["AM", "FM"]: step = TS2000_FM_STEPS.index(mem.tuning_step) else: step = TS2000_SSB_STEPS.index(mem.tuning_step) # TS-2000 won't accept channels with tone mode off if they have # tone values if mem.tmode == "": rtone = 0 ctone = 0 dtcs = 0 else: # PL and T-SQL are 1 indexed, DTCS is 0 indexed rtone = (self._kenwood_valid_tones.index(mem.rtone) + 1) ctone = (self._kenwood_valid_tones.index(mem.ctone) + 1) dtcs = (chirp_common.DTCS_CODES.index(mem.dtcs)) spec = ( "%011i" % mem.offset, "%i" % (TS2000_MODES.index(mem.mode)), "%i" % (mem.skip == "S"), "%i" % TS2000_TMODES.index(mem.tmode), "%02i" % (rtone), "%02i" % (ctone), "%03i" % (dtcs), "0", # REVERSE status "%i" % duplex, "%09i" % 0, "%02i" % step, "0", # Memory Group number (0-9) "%s" % mem.name, ) return spec chirp-daily-20170714/chirp/drivers/bjuv55.py0000644000016101777760000005021612476257220021653 0ustar jenkinsnogroup00000000000000# Copyright 2013 Jens Jensen AF5MI # Based on work by Jim Unroe, Dan Smith, et al. # Special thanks to Mats SM0BTP for equipment donation. # # 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 import os import logging from chirp.drivers import uv5r from chirp import chirp_common, errors, util, directory, memmap from chirp import bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettingValueFloat, InvalidValueError, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) BJUV55_MODEL = "\x50\xBB\xDD\x55\x63\x98\x4D" COLOR_LIST = ["Off", "Blue", "Red", "Pink"] STEPS = uv5r.STEPS STEPS.remove(2.5) STEP_LIST = [str(x) for x in STEPS] MEM_FORMAT = """ #seekto 0x0008; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unused1:3, isuhf:1, scode:4; u8 unknown1:7, txtoneicon:1; u8 mailicon:3, unknown2:4, lowpower:1; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B08; struct { u8 code[5]; u8 unused[11]; } pttid[15]; #seekto 0x0C88; struct { u8 inspection[5]; u8 monitor[5]; u8 alarmcode[5]; u8 unknown1; u8 stun[5]; u8 kill[5]; u8 revive[5]; u8 unknown2; u8 master_control_id[5]; u8 vice_control_id[5]; u8 code[5]; u8 unused1:6, aniid:2; u8 unknown[2]; u8 dtmfon; u8 dtmfoff; } ani; #seekto 0x0E28; struct { u8 squelch; u8 step; u8 tdrab; u8 tdr; u8 vox; u8 timeout; u8 unk2[6]; u8 abr; u8 beep; u8 ani; u8 unknown3[2]; u8 voice; u8 ring_time; u8 dtmfst; u8 unknown5; u8 unknown12:6, screv:2; u8 pttid; u8 pttlt; u8 mdfa; u8 mdfb; u8 bcl; u8 autolk; u8 sftd; u8 unknown6[3]; u8 wtled; u8 rxled; u8 txled; u8 unknown7[5]; u8 save; u8 unknown8; u8 displayab:1, unknown1:2, fmradio:1, alarm:1, unknown2:1, reset:1, menu:1; u8 vfomrlock; u8 workmode; u8 keylock; u8 workmode_channel; u8 password[6]; u8 unknown10[11]; } settings; #seekto 0x0E7E; struct { u8 mrcha; u8 mrchb; } 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:2, sftd:2, 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:2, sftd:2, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:6; } vfob; #seekto 0x0F57; u8 fm_preset; #seekto 0x1008; struct { char name[6]; u8 unknown2[10]; } names[128]; #seekto 0x%04X; struct { char line1[7]; char line2[7]; } poweron_msg; #seekto 0x1838; struct { char line1[7]; char line2[7]; } firmware_msg; #seekto 0x1849; u8 power_vhf_hi[14]; // 136-174 MHz, 3 MHz divisions u8 power_uhf_hi[14]; // 400-470 MHz, 5 MHz divisions #seekto 0x1889; u8 power_vhf_lo[14]; u8 power_uhf_lo[14]; struct limit { u8 enable; bbcd lower[2]; bbcd upper[2]; }; #seekto 0x1908; struct { struct limit vhf; u8 unk11[11]; struct limit uhf; } limits; """ @directory.register class BaojieBJUV55Radio(uv5r.BaofengUV5R): VENDOR = "Baojie" MODEL = "BJ-UV55" _basetype = ["BJ55"] _idents = [BJUV55_MODEL] _mem_params = (0x1928 # poweron_msg offset ) _fw_ver_file_start = 0x1938 _fw_ver_file_stop = 0x193E def get_features(self): rf = super(BaojieBJUV55Radio, self).get_features() rf.valid_name_length = 6 return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap) def set_memory(self, mem): super(BaojieBJUV55Radio, self).set_memory(mem) _mem = self._memobj.memory[mem.number] if (mem.freq - mem.offset) > (400 * 1000000): _mem.isuhf = True else: _mem.isuhf = False if mem.tmode in ["Tone", "TSQL"]: _mem.txtoneicon = True else: _mem.txtoneicon = False def _get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") group = RadioSettings(basic, advanced) rs = RadioSetting("squelch", "Carrier Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueInteger(0, 4, _settings.save)) basic.append(rs) rs = RadioSetting("abr", "Backlight", RadioSettingValueBoolean(_settings.abr)) basic.append(rs) rs = RadioSetting("tdr", "Dual Watch (BDR)", RadioSettingValueBoolean(_settings.tdr)) advanced.append(rs) rs = RadioSetting("tdrab", "Dual Watch TX Priority", RadioSettingValueList( uv5r.TDRAB_LIST, uv5r.TDRAB_LIST[_settings.tdrab])) advanced.append(rs) rs = RadioSetting("alarm", "Alarm", RadioSettingValueBoolean(_settings.alarm)) advanced.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting("timeout", "Timeout Timer", RadioSettingValueList( uv5r.TIMEOUT_LIST, uv5r.TIMEOUT_LIST[_settings.timeout])) basic.append(rs) rs = RadioSetting("screv", "Scan Resume", RadioSettingValueList( uv5r.RESUME_LIST, uv5r.RESUME_LIST[_settings.screv])) advanced.append(rs) rs = RadioSetting("mdfa", "Display Mode (A)", RadioSettingValueList( uv5r.MODE_LIST, uv5r.MODE_LIST[_settings.mdfa])) basic.append(rs) rs = RadioSetting("mdfb", "Display Mode (B)", RadioSettingValueList( uv5r.MODE_LIST, uv5r.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("fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_settings.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("reset", "RESET Menu", RadioSettingValueBoolean(_settings.reset)) advanced.append(rs) rs = RadioSetting("menu", "All Menus", RadioSettingValueBoolean(_settings.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.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) limit = "limits" 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("displayab", "Display Selected", RadioSettingValueList( options, options[_settings.displayab])) workmode.append(rs) options = ["Frequency", "Channel"] rs = RadioSetting("workmode", "VFO/MR Mode", RadioSettingValueList( options, options[_settings.workmode])) workmode.append(rs) rs = RadioSetting("keylock", "Keypad Lock", RadioSettingValueBoolean(_settings.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) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): value = chirp_common.parse_freq(value) if 17400000 <= value and value < 40000000: raise InvalidValueError("Can't be between 174.00000-400.00000") return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 obj.band = value >= 40000000 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_freq(self._memobj.vfoa.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, self._memobj.vfoa) workmode.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_freq(self._memobj.vfob.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, self._memobj.vfob) workmode.append(rs) options = ["Off", "+", "-"] rs = RadioSetting("vfoa.sftd", "VFO A Shift", RadioSettingValueList( options, options[self._memobj.vfoa.sftd])) workmode.append(rs) rs = RadioSetting("vfob.sftd", "VFO B Shift", RadioSettingValueList( options, options[self._memobj.vfob.sftd])) workmode.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 10000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10000 for i in range(3, -1, -1): obj.offset[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_offset(self._memobj.vfoa.offset)) rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a) rs.set_apply_callback(apply_offset, self._memobj.vfoa) workmode.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_offset(self._memobj.vfob.offset)) rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b) rs.set_apply_callback(apply_offset, self._memobj.vfob) 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) 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) fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset") group.append(fm_preset) preset = self._memobj.fm_preset / 10.0 + 87 rs = RadioSetting("fm_preset", "FM Preset(MHz)", RadioSettingValueFloat(87, 107.5, preset, 0.1, 1)) fm_preset.append(rs) dtmf = RadioSettingGroup("dtmf", "DTMF Settings") group.append(dtmf) dtmfchars = "0123456789 *#ABCD" for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("pttid/%i.code" % i, "PTT ID Code %i" % (i + 1), val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.pttid[i]) dtmf.append(rs) _codeobj = self._memobj.ani.code _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("ani.code", "ANI Code", val) def apply_code(setting, obj): code = [] for j in range(0, 5): try: code.append(dtmfchars.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code rs.set_apply_callback(apply_code, self._memobj.ani) dtmf.append(rs) options = ["Off", "BOT", "EOT", "Both"] rs = RadioSetting("ani.aniid", "ANI ID", RadioSettingValueList( options, options[self._memobj.ani.aniid])) dtmf.append(rs) _codeobj = self._memobj.ani.alarmcode _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(dtmfchars) rs = RadioSetting("ani.alarmcode", "Alarm Code", val) def apply_code(setting, obj): alarmcode = [] for j in range(5): try: alarmcode.append(dtmfchars.index(str(setting.value)[j])) except IndexError: alarmcode.append(0xFF) obj.alarmcode = alarmcode rs.set_apply_callback(apply_code, self._memobj.ani) dtmf.append(rs) rs = RadioSetting("dtmfst", "DTMF Sidetone", RadioSettingValueList( uv5r.DTMFST_LIST, uv5r.DTMFST_LIST[_settings.dtmfst])) dtmf.append(rs) rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList( uv5r.DTMFSPEED_LIST, uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfon])) dtmf.append(rs) rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList( uv5r.DTMFSPEED_LIST, uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff])) dtmf.append(rs) return group def _set_fm_preset(self, settings): for element in settings: try: val = element.value value = int(val.get_value() * 10 - 870) LOG.debug("Setting fm_preset = %s" % (value)) self._memobj.fm_preset = value except Exception, e: LOG.debug(element.get_name()) raise chirp-daily-20170714/chirp/drivers/gmrsuv1.py0000644000016101777760000006535012777357601022154 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp.drivers import baofeng_common from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, \ InvalidValueError from textwrap import dedent ##### MAGICS ######################################################### # BTECH GMRS-V1 magic string MSTRING_GMRSV1 = "\x50\x5F\x20\x15\x12\x15\x4D" ##### ID strings ##################################################### # BTECH GMRS-V1 GMRSV1_fp1 = "US32411" DTMF_CHARS = "0123456789 *#ABCD" STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] LIST_AB = ["A", "B"] LIST_ALMOD = ["Off", "Site", "Tone", "Code"] LIST_BANDWIDTH = ["Wide", "Narrow"] LIST_COLOR = ["Off", "Blue", "Orange", "Purple"] LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"] LIST_MODE = ["Channel", "Name", "Frequency"] LIST_OFF1TO9 = ["Off"] + list("123456789") LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"] LIST_OFFAB = ["Off"] + LIST_AB LIST_RESUME = ["TO", "CO", "SE"] LIST_PONMSG = ["Full", "Message"] LIST_PTTID = ["Off", "BOT", "EOT", "Both"] LIST_SCODE = ["%s" % x for x in range(1, 16)] LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)] LIST_RTONE = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SHIFTD = ["Off", "+", "-"] LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)] LIST_STEP = [str(x) for x in STEPS] LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)] LIST_TXPOWER = ["High", "Low"] LIST_VOICE = ["Off", "English", "Chinese"] LIST_WORKMODE = ["Frequency", "Channel"] def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = data[0x1EF0:0x1EF7] if rid in cls._fileid: return True return False class MyRadioFeatures(chirp_common.RadioFeatures): def validate_memory(self, mem): # Run the normal validation msgs = chirp_common.RadioFeatures.validate_memory(self, mem) # Run my validation if mem.number <= 6 and mem.mode != "NFM": msgs.append(chirp_common.ValidationError( 'Only NFM is supported on this channel')) return msgs @directory.register class GMRSV1(baofeng_common.BaofengCommonHT): """BTech GMRS-V1""" VENDOR = "BTECH" MODEL = "GMRS-V1" _fileid = [GMRSV1_fp1, ] _magic = [MSTRING_GMRSV1, ] _magic_response_length = 8 _fw_ver_start = 0x1EF0 _recv_block_size = 0x40 _mem_size = 0x2000 _ack_block = True _ranges = [(0x0000, 0x0DF0), (0x0E00, 0x1800), (0x1EE0, 0x1EF0), (0x1F60, 0x1F70), (0x1F80, 0x1F90), (0x1FC0, 0x1FD0)] _send_block_size = 0x10 MODES = ["NFM", "FM"] VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "!@#$%^&*()+-=[]:\";'<>?,./" LENGTH_NAME = 7 SKIP_VALUES = ["", "S"] DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Low", watts=2.00)] VALID_BANDS = [(130000000, 180000000), (400000000, 521000000)] PTTID_LIST = LIST_PTTID SCODE_LIST = LIST_SCODE def get_features(self): """Get the radio's features""" rf = MyRadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.can_odd_split = False rf.has_name = True rf.has_offset = False rf.has_mode = True rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = self.MODES rf.valid_characters = self.VALID_CHARS rf.valid_name_length = self.LENGTH_NAME rf.valid_duplexes = [] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_skips = self.SKIP_VALUES rf.valid_dtcs_codes = self.DTCS_CODES rf.memory_bounds = (0, 127) rf.valid_power_levels = self.POWER_LEVELS rf.valid_bands = self.VALID_BANDS return rf MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unknown0:4, scode:4; u8 unknown1; u8 unknown2:7, lowpower:1; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B00; struct { u8 code[5]; u8 unused[11]; } pttid[15]; #seekto 0x0CAA; struct { u8 code[5]; u8 unused1:6, aniid:2; u8 unknown[2]; u8 dtmfon; u8 dtmfoff; } ani; #seekto 0x0E20; struct { u8 unused01:4, squelch:4; u8 unused02; u8 unused03; u8 unused04:5, save:3; u8 unused05:4, vox:4; u8 unused06; u8 unused07:4, abr:4; u8 unused08:7, tdr:1; u8 unused09:7, beep:1; u8 unused10:2, timeout:6; u8 unused11[4]; u8 unused12:6, voice:2; u8 unused13; u8 unused14:6, dtmfst:2; u8 unused15; u8 unused16:6, screv:2; u8 unused17:6, pttid:2; u8 unused18:2, pttlt:6; u8 unused19:6, mdfa:2; u8 unused20:6, mdfb:2; u8 unused21; u8 unused22:7, sync:1; u8 unused23[4]; u8 unused24:6, wtled:2; u8 unused25:6, rxled:2; u8 unused26:6, txled:2; u8 unused27:6, almod:2; u8 unused28:7, dbptt:1; u8 unused29:6, tdrab:2; u8 unused30:7, ste:1; u8 unused31:4, rpste:4; u8 unused32:4, rptrl:4; u8 unused33:7, ponmsg:1; u8 unused34:7, roger:1; u8 unused35:6, rtone:2; u8 unused36; u8 unused37:6, rogerrx:2; u8 unused38; u8 displayab:1, unknown1:2, fmradio:1, alarm:1, unknown2:1, reset:1, menu:1; u8 unused39; u8 workmode; u8 keylock; u8 cht; } settings; #seekto 0x0E76; struct { u8 unused1:1, mrcha:7; u8 unused2:1, mrchb:7; } wmchannel; struct vfo { u8 unknown0[8]; u8 freq[8]; u8 unknown1; u8 offset[4]; u8 unknown2; ul16 rxtone; ul16 txtone; u8 unused1:7, band:1; u8 unknown3; u8 unused2:2, sftd:2, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 txpower:1, widenarr:1, unknown5:4, txpower3:2; }; #seekto 0x0F00; struct { struct vfo a; struct vfo b; } vfo; #seekto 0x0F4E; u16 fm_presets; #seekto 0x1000; struct { char name[7]; u8 unknown1[9]; } names[128]; #seekto 0x1ED0; struct { char line1[7]; char line2[7]; } sixpoweron_msg; #seekto 0x1EE0; struct { char line1[7]; char line2[7]; } poweron_msg; #seekto 0x1EF0; struct { char line1[7]; char line2[7]; } firmware_msg; struct squelch { u8 sql0; u8 sql1; u8 sql2; u8 sql3; u8 sql4; u8 sql5; u8 sql6; u8 sql7; u8 sql8; u8 sql9; }; #seekto 0x1F60; struct { struct squelch vhf; u8 unknown1[6]; u8 unknown2[16]; struct squelch uhf; } squelch; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('The BTech GMRS-V1 driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap) def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") fm_preset = RadioSettingGroup("fm_preset", "FM Preset") dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings") service = RadioSettingGroup("service", "Service Settings") top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe, service) # Basic settings if _mem.settings.squelch > 0x09: val = 0x00 else: val = _mem.settings.squelch rs = RadioSetting("settings.squelch", "Squelch", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[val])) basic.append(rs) if _mem.settings.save > 0x04: val = 0x00 else: val = _mem.settings.save rs = RadioSetting("settings.save", "Battery Saver", RadioSettingValueList( LIST_SAVE, LIST_SAVE[val])) basic.append(rs) if _mem.settings.vox > 0x0A: val = 0x00 else: val = _mem.settings.vox rs = RadioSetting("settings.vox", "Vox", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) if _mem.settings.abr > 0x0A: val = 0x00 else: val = _mem.settings.abr rs = RadioSetting("settings.abr", "Backlight Timeout", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) rs = RadioSetting("settings.tdr", "Dual Watch", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(rs) rs = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(rs) if _mem.settings.timeout > 0x27: val = 0x03 else: val = _mem.settings.timeout rs = RadioSetting("settings.timeout", "Timeout Timer", RadioSettingValueList( LIST_TIMEOUT, LIST_TIMEOUT[val])) basic.append(rs) if _mem.settings.voice > 0x02: val = 0x01 else: val = _mem.settings.voice rs = RadioSetting("settings.voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) rs = RadioSetting("settings.dtmfst", "DTMF Sidetone", RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[ _mem.settings.dtmfst])) basic.append(rs) if _mem.settings.screv > 0x02: val = 0x01 else: val = _mem.settings.screv rs = RadioSetting("settings.screv", "Scan Resume", RadioSettingValueList( LIST_RESUME, LIST_RESUME[val])) basic.append(rs) rs = RadioSetting("settings.pttid", "When to send PTT ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[ _mem.settings.pttid])) basic.append(rs) if _mem.settings.pttlt > 0x1E: val = 0x05 else: val = _mem.settings.pttlt rs = RadioSetting("pttlt", "PTT ID Delay", RadioSettingValueInteger(0, 50, val)) basic.append(rs) rs = RadioSetting("settings.mdfa", "Display Mode (A)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfa])) basic.append(rs) rs = RadioSetting("settings.mdfb", "Display Mode (B)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfb])) basic.append(rs) rs = RadioSetting("settings.sync", "Sync A & B", RadioSettingValueBoolean(_mem.settings.sync)) basic.append(rs) rs = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.wtled])) basic.append(rs) rs = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.rxled])) basic.append(rs) rs = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.txled])) basic.append(rs) val = _mem.settings.almod rs = RadioSetting("settings.almod", "Alarm Mode", RadioSettingValueList( LIST_ALMOD, LIST_ALMOD[val])) basic.append(rs) rs = RadioSetting("settings.dbptt", "Double PTT", RadioSettingValueBoolean(_mem.settings.dbptt)) basic.append(rs) if _mem.settings.tdrab > 0x02: val = 0x00 else: val = _mem.settings.tdrab rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority", RadioSettingValueList( LIST_OFFAB, LIST_OFFAB[val])) basic.append(rs) rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)", RadioSettingValueBoolean(_mem.settings.ste)) basic.append(rs) if _mem.settings.rpste > 0x0A: val = 0x00 else: val = _mem.settings.rpste rs = RadioSetting("settings.rpste", "Squelch Tail Eliminate (repeater)", RadioSettingValueList( LIST_RPSTE, LIST_RPSTE[val])) basic.append(rs) if _mem.settings.rptrl > 0x0A: val = 0x00 else: val = _mem.settings.rptrl rs = RadioSetting("settings.rptrl", "STE Repeater Delay", RadioSettingValueList( LIST_STEDELAY, LIST_STEDELAY[val])) basic.append(rs) rs = RadioSetting("settings.ponmsg", "Power-On Message", RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[ _mem.settings.ponmsg])) basic.append(rs) rs = RadioSetting("settings.roger", "Roger Beep", RadioSettingValueBoolean(_mem.settings.roger)) basic.append(rs) rs = RadioSetting("settings.rtone", "Tone Burst Frequency", RadioSettingValueList(LIST_RTONE, LIST_RTONE[ _mem.settings.rtone])) basic.append(rs) rs = RadioSetting("settings.rogerrx", "Roger Beep (RX)", RadioSettingValueList( LIST_OFFAB, LIST_OFFAB[ _mem.settings.rogerrx])) basic.append(rs) # Advanced settings rs = RadioSetting("settings.reset", "RESET Menu", RadioSettingValueBoolean(_mem.settings.reset)) advanced.append(rs) rs = RadioSetting("settings.menu", "All Menus", RadioSettingValueBoolean(_mem.settings.menu)) advanced.append(rs) rs = RadioSetting("settings.fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_mem.settings.fmradio)) advanced.append(rs) rs = RadioSetting("settings.alarm", "Alarm Sound", RadioSettingValueBoolean(_mem.settings.alarm)) advanced.append(rs) # Other settings def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = _mem.firmware_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val) other.append(rs) _msg = _mem.sixpoweron_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val) other.append(rs) _msg = _mem.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) # Work mode settings rs = RadioSetting("settings.displayab", "Display", RadioSettingValueList( LIST_AB, LIST_AB[_mem.settings.displayab])) work.append(rs) rs = RadioSetting("settings.workmode", "VFO/MR Mode", RadioSettingValueList( LIST_WORKMODE, LIST_WORKMODE[_mem.settings.workmode])) work.append(rs) rs = RadioSetting("settings.keylock", "Keypad Lock", RadioSettingValueBoolean(_mem.settings.keylock)) work.append(rs) rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrcha)) work.append(rs) rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrchb)) work.append(rs) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): value = chirp_common.parse_freq(value) msg = ("Can't be less than %i.0000") if value > 99000000 and value < 130 * 1000000: raise InvalidValueError(msg % (130)) msg = ("Can't be between %i.9975-%i.0000") if (179 + 1) * 1000000 <= value and value < 400 * 1000000: raise InvalidValueError(msg % (179, 400)) msg = ("Can't be greater than %i.9975") if value > 99000000 and value > (520 + 1) * 1000000: raise InvalidValueError(msg % (520)) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.a.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.b.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.step", "VFO A Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.a.step])) work.append(rs) rs = RadioSetting("vfo.b.step", "VFO B Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.b.step])) work.append(rs) # broadcast FM settings _fm_presets = self._memobj.fm_presets if _fm_presets <= 108.0 * 10 - 650: preset = _fm_presets / 10.0 + 65 elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10: preset = _fm_presets / 10.0 else: preset = 76.0 rs = RadioSetting("fm_presets", "FM Preset(MHz)", RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)) fm_preset.append(rs) # DTMF settings def apply_code(setting, obj, length): code = [] for j in range(0, length): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) pttid = RadioSetting("pttid/%i.code" % i, "Signal Code %i" % (i + 1), val) pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5) dtmfe.append(pttid) if _mem.ani.dtmfon > 0xC3: val = 0x03 else: val = _mem.ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) if _mem.ani.dtmfoff > 0xC3: val = 0x03 else: val = _mem.ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) _codeobj = self._memobj.ani.code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.code", "ANI Code", val) rs.set_apply_callback(apply_code, self._memobj.ani, 5) dtmfe.append(rs) rs = RadioSetting("ani.aniid", "When to send ANI ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[_mem.ani.aniid])) dtmfe.append(rs) # Service settings for band in ["vhf", "uhf"]: for index in range(0, 10): key = "squelch.%s.sql%i" % (band, index) if band == "vhf": _obj = self._memobj.squelch.vhf elif band == "uhf": _obj = self._memobj.squelch.uhf val = RadioSettingValueInteger(0, 123, getattr(_obj, "sql%i" % (index))) if index == 0: val.set_mutable(False) name = "%s Squelch %i" % (band.upper(), index) rs = RadioSetting(key, name, val) service.append(rs) return top @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == 0x2008: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/h777.py0000644000016101777760000004363413006307374021231 0ustar jenkinsnogroup00000000000000# -*- coding: utf-8 -*- # Copyright 2013 Andrew Morgan # # 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 time import os import struct import unittest import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rxtone[2]; lbcd txtone[2]; u8 unknown3:1, unknown2:1, unknown1:1, skip:1, highpower:1, narrow:1, beatshift:1, bcl:1; u8 unknown4[3]; } memory[16]; #seekto 0x02B0; struct { u8 voiceprompt; u8 voicelanguage; u8 scan; u8 vox; u8 voxlevel; u8 voxinhibitonrx; u8 lowvolinhibittx; u8 highvolinhibittx; u8 alarm; u8 fmradio; } settings; #seekto 0x03C0; struct { u8 unused:6, batterysaver:1, beep:1; u8 squelchlevel; u8 sidekeyfunction; u8 timeouttimer; u8 unused2[3]; u8 unused3:7, scanmode:1; } settings2; """ CMD_ACK = "\x06" BLOCK_SIZE = 0x08 UPLOAD_BLOCKS = [range(0x0000, 0x0110, 8), range(0x02b0, 0x02c0, 8), range(0x0380, 0x03e0, 8)] # TODO: Is it 1 watt? H777_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("High", watts=5.00)] VOICE_LIST = ["English", "Chinese"] SIDEKEYFUNCTION_LIST = ["Off", "Monitor", "Transmit Power", "Alarm"] TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds", "120 seconds", "150 seconds", "180 seconds", "210 seconds", "240 seconds", "270 seconds", "300 seconds"] SCANMODE_LIST = ["Carrier", "Time"] SETTING_LISTS = { "voice": VOICE_LIST, } def _h777_enter_programming_mode(radio): serial = radio.pipe try: serial.write("\x02") time.sleep(0.1) serial.write("PROGRAM") ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if not ack: raise errors.RadioError("No response from radio") elif ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x02") ident = serial.read(8) except: raise errors.RadioError("Error communicating with radio") if not ident.startswith("P3107"): LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _h777_exit_programming_mode(radio): serial = radio.pipe try: serial.write("E") except: raise errors.RadioError("Radio refused to exit programming mode") def _h777_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + BLOCK_SIZE) if response[:4] != expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _h777_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE) data = radio.get_mmap()[block_addr:block_addr + 8] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _h777_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) block = _h777_read_block(radio, addr, BLOCK_SIZE) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) _h777_exit_programming_mode(radio) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _h777_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr in radio._ranges: for addr in range(start_addr, end_addr, BLOCK_SIZE): status.cur = addr + BLOCK_SIZE radio.status_fn(status) _h777_write_block(radio, addr, BLOCK_SIZE) _h777_exit_programming_mode(radio) @directory.register class H777Radio(chirp_common.CloneModeRadio): """HST H-777""" # VENDOR = "Heng Shun Tong (æ’顺通)" # MODEL = "H-777" VENDOR = "Baofeng" MODEL = "BF-888" BAUD_RATE = 9600 # This code currently requires that ranges start at 0x0000 # and are continious. In the original program 0x0388 and 0x03C8 # are only written (all bytes 0xFF), not read. # _ranges = [ # (0x0000, 0x0110), # (0x02B0, 0x02C0), # (0x0380, 0x03E0) # ] # Memory starts looping at 0x1000... But not every 0x1000. _ranges = [ (0x0000, 0x0110), (0x02B0, 0x02C0), (0x0380, 0x03E0), ] _memsize = 0x03E0 def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.valid_skips = ["", "S"] rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.can_odd_split = True rf.has_rx_dtcs = True rf.has_ctone = True rf.has_cross = True rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.has_tuning_step = False rf.has_bank = False rf.has_name = False rf.memory_bounds = (1, 16) rf.valid_bands = [(400000000, 470000000)] rf.valid_power_levels = H777_POWER_LEVELS return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): self._mmap = do_download(self) self.process_mmap() def sync_out(self): do_upload(self) def get_raw_memory(self, number): return repr(self._memobj.memory[number - 1]) def _decode_tone(self, val): val = int(val) if val == 16665: return '', None, None elif val >= 12000: return 'DTCS', val - 12000, 'R' elif val >= 8000: return 'DTCS', val - 8000, 'N' else: return 'Tone', val / 10.0, None def _encode_tone(self, memval, mode, value, pol): if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_memory(self, number): _mem = self._memobj.memory[number - 1] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.duplex = "off" mem.offset = 0 elif int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = not _mem.narrow and "FM" or "NFM" mem.power = H777_POWER_LEVELS[_mem.highpower] mem.skip = _mem.skip and "S" or "" txtone = self._decode_tone(_mem.txtone) rxtone = self._decode_tone(_mem.rxtone) chirp_common.split_tone_decode(mem, txtone, rxtone) mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueBoolean(not _mem.bcl)) mem.extra.append(rs) rs = RadioSetting("beatshift", "Beat Shift(scramble)", RadioSettingValueBoolean(not _mem.beatshift)) mem.extra.append(rs) return mem def set_memory(self, mem): # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number - 1] if mem.empty: _mem.set_raw("\xFF" * (_mem.size() / 8)) return _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 txtone, rxtone = chirp_common.split_tone_encode(mem) self._encode_tone(_mem.txtone, *txtone) self._encode_tone(_mem.rxtone, *rxtone) _mem.narrow = 'N' in mem.mode _mem.highpower = mem.power == H777_POWER_LEVELS[1] _mem.skip = mem.skip == "S" for setting in mem.extra: # NOTE: Only two settings right now, both are inverted setattr(_mem, setting.get_name(), not int(setting.value)) # When set to one, official programming software (BF-480) shows always # "WFM", even if we choose "NFM". Therefore, for compatibility # purposes, we will set these to zero. _mem.unknown1 = 0 _mem.unknown2 = 0 _mem.unknown3 = 0 def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) # TODO: Check that all these settings actually do what they # say they do. rs = RadioSetting("voiceprompt", "Voice prompt", RadioSettingValueBoolean(_settings.voiceprompt)) basic.append(rs) rs = RadioSetting("voicelanguage", "Voice language", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voicelanguage])) basic.append(rs) rs = RadioSetting("scan", "Scan", RadioSettingValueBoolean(_settings.scan)) basic.append(rs) rs = RadioSetting("settings2.scanmode", "Scan mode", RadioSettingValueList( SCANMODE_LIST, SCANMODE_LIST[self._memobj.settings2.scanmode])) basic.append(rs) rs = RadioSetting("vox", "VOX", RadioSettingValueBoolean(_settings.vox)) basic.append(rs) rs = RadioSetting("voxlevel", "VOX level", RadioSettingValueInteger( 1, 5, _settings.voxlevel + 1)) basic.append(rs) rs = RadioSetting("voxinhibitonrx", "Inhibit VOX on receive", RadioSettingValueBoolean(_settings.voxinhibitonrx)) basic.append(rs) rs = RadioSetting("lowvolinhibittx", "Low voltage inhibit transmit", RadioSettingValueBoolean(_settings.lowvolinhibittx)) basic.append(rs) rs = RadioSetting("highvolinhibittx", "High voltage inhibit transmit", RadioSettingValueBoolean(_settings.highvolinhibittx)) basic.append(rs) rs = RadioSetting("alarm", "Alarm", RadioSettingValueBoolean(_settings.alarm)) basic.append(rs) # TODO: This should probably be called “FM Broadcast Band Radio†# or something. I'm not sure if the model actually has one though. rs = RadioSetting("fmradio", "FM function", RadioSettingValueBoolean(_settings.fmradio)) basic.append(rs) rs = RadioSetting("settings2.beep", "Beep", RadioSettingValueBoolean( self._memobj.settings2.beep)) basic.append(rs) rs = RadioSetting("settings2.batterysaver", "Battery saver", RadioSettingValueBoolean( self._memobj.settings2.batterysaver)) basic.append(rs) rs = RadioSetting("settings2.squelchlevel", "Squelch level", RadioSettingValueInteger( 0, 9, self._memobj.settings2.squelchlevel)) basic.append(rs) rs = RadioSetting("settings2.sidekeyfunction", "Side key function", RadioSettingValueList( SIDEKEYFUNCTION_LIST, SIDEKEYFUNCTION_LIST[ self._memobj.settings2.sidekeyfunction])) basic.append(rs) rs = RadioSetting("settings2.timeouttimer", "Timeout timer", RadioSettingValueList( TIMEOUTTIMER_LIST, TIMEOUTTIMER_LIST[ self._memobj.settings2.timeouttimer])) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() elif setting == "voxlevel": setattr(obj, setting, int(element.value) - 1) else: LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise class H777TestCase(unittest.TestCase): def setUp(self): self.driver = H777Radio(None) self.testdata = bitwise.parse("lbcd foo[2];", memmap.MemoryMap("\x00\x00")) def test_decode_tone_dtcs_normal(self): mode, value, pol = self.driver._decode_tone(8023) self.assertEqual('DTCS', mode) self.assertEqual(23, value) self.assertEqual('N', pol) def test_decode_tone_dtcs_rev(self): mode, value, pol = self.driver._decode_tone(12023) self.assertEqual('DTCS', mode) self.assertEqual(23, value) self.assertEqual('R', pol) def test_decode_tone_tone(self): mode, value, pol = self.driver._decode_tone(885) self.assertEqual('Tone', mode) self.assertEqual(88.5, value) self.assertEqual(None, pol) def test_decode_tone_none(self): mode, value, pol = self.driver._decode_tone(16665) self.assertEqual('', mode) self.assertEqual(None, value) self.assertEqual(None, pol) def test_encode_tone_dtcs_normal(self): self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'N') self.assertEqual(8023, int(self.testdata.foo)) def test_encode_tone_dtcs_rev(self): self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'R') self.assertEqual(12023, int(self.testdata.foo)) def test_encode_tone(self): self.driver._encode_tone(self.testdata.foo, 'Tone', 88.5, 'N') self.assertEqual(885, int(self.testdata.foo)) def test_encode_tone_none(self): self.driver._encode_tone(self.testdata.foo, '', 67.0, 'N') self.assertEqual(16665, int(self.testdata.foo)) chirp-daily-20170714/chirp/drivers/ft7800.py0000644000016101777760000007356612476257220021500 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 import logging import os import re from chirp.drivers import yaesu_clone from chirp import chirp_common, memmap, directory, bitwise, errors from textwrap import dedent from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings from collections import defaultdict LOG = logging.getLogger(__name__) ACK = chr(0x06) MEM_FORMAT = """ #seekto 0x002A; u8 banks_unk2; u8 current_channel; u8 unk3; u8 unk4; u8 current_menu; #seekto 0x0035; u8 banks_unk1; #seekto 0x00C8; struct { u8 memory[16]; } dtmf[16]; #seekto 0x003A; struct { u8 apo; u8 tot; u8 lock:3, arts_interval:1, unk1a:1, prog_panel_acc:3; u8 prog_p1; u8 prog_p2; u8 prog_p3; u8 prog_p4; u8 rf_sql; u8 inet_dtmf_mem:4, inet_dtmf_digit:4; u8 arts_cwid_enable:1, prog_tone_vm:1, unk2a:1, hyper_write:2, memory_only:1, dimmer:2; u8 beep_scan:1, beep_edge:1, beep_key:1, unk3a:1, inet_mode:1, unk3b:1, dtmf_speed:2; u8 dcs_polarity:2, smart_search:1, priority_revert:1, unk4a:1, dtmf_delay:3; u8 unk5a:3, microphone_type:1, scan_resume:1, unk5b:1, arts_mode:2; u8 unk6; } settings; struct mem_struct { u8 used:1, unknown1:1, mode:2, unknown2:1, duplex:3; bbcd freq[3]; u8 clockshift:1, tune_step:3, unknown5:1, // TODO: tmode has extended settings, at least 4 bits tmode:3; bbcd split[3]; u8 power:2, tone:6; u8 unknown6:1, dtcs:7; u8 unknown7[2]; u8 offset; u8 unknown9[3]; }; #seekto 0x0048; struct mem_struct vfos[5]; #seekto 0x01C8; struct mem_struct homes[5]; #seekto 0x0218; u8 arts_cwid[6]; #seekto 0x04C8; struct mem_struct 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"] 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) DTMFCHARSET = list("0123456789ABCD*#") 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], radio._block_size): chunk = radio.pipe.read(radio._block_size) data += chunk if len(chunk) != radio._block_size: 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, radio._block_size): length = min(radio._block_size, block) # LOG.debug("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" MODES = list(MODES) _block_size = 64 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)] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the [MHz(PRI)] key while turning the radio on. 4. Rotate the DIAL job to select "F-7 CLONE". 5. Press and hold in the [BAND(SET)] key. The display will disappear for a moment, then the "CLONE" notation will appear. 6. After clicking OK, press the [V/M(MW)] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the [MHz(PRI)] key while turning the radio on. 4. Rotate the DIAL job to select "F-7 CLONE". 5. Press and hold in the [BAND(SET)] key. The display will disappear for a moment, then the "CLONE" notation will appear. 6. Press the [LOW(ACC)] key ("--RX--" will appear on the display).""")) return rp 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 = self.TMODES 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 = self.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) LOG.info("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) LOG.info("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 = self.TMODES[_mem.tmode] mem.mode = self.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 = self.POWER_LEVELS_UHF[_mem.power] else: mem.power = self.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 = self.TMODES.index(mem.tmode) _mem.mode = self.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 = self.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): super(FT7800BankModel, self).__init__(radio) self.__b2m_cache = defaultdict(list) self.__m2b_cache = defaultdict(list) def __precache(self): if self.__b2m_cache: return for bank in self.get_mappings(): 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_mappings(self): return 20 def get_mappings(self): banks = [] for i in range(0, self.get_num_mappings()): bank = chirp_common.Bank(self, "%i" % i, "BANK-%i" % (i + 1)) bank.index = i banks.append(bank) return banks def add_memory_to_mapping(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_mapping(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] c = self._radio._memobj.bank_channels[bank.index] for i in range(0, upper): _bitmap = c.bitmap[i / 32] ishft = 31 - (i % 32) if _bitmap & (1 << ishft): memories.append(i + 1) return memories def get_mapping_memories(self, bank): self.__precache() return [self._radio.get_memory(n) for n in self.__b2m_cache[bank.index]] def get_memory_mappings(self, memory): self.__precache() _banks = self.get_mappings() return [_banks[b] for b in self.__m2b_cache[memory.number]] @directory.register class FT7800Radio(FTx800Radio): """Yaesu FT-7800""" MODEL = "FT-7800/7900" _model = "AH016" _memsize = 31561 _block_lengths = [8, 31552, 1] TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"] def get_bank_model(self): return FT7800BankModel(self) def get_features(self): rf = FTx800Radio.get_features(self) rf.has_bank = True rf.has_settings = True return rf def set_memory(self, memory): if memory.empty: self._wipe_memory_banks(memory) FTx800Radio.set_memory(self, memory) def _decode_chars(self, inarr): LOG.debug("@_decode_chars, type: %s" % type(inarr)) LOG.debug(inarr) outstr = "" for i in inarr: if i == 0xFF: break outstr += CHARSET[i & 0x7F] return outstr.rstrip() def _encode_chars(self, instr, length=16): LOG.debug("@_encode_chars, type: %s" % type(instr)) LOG.debug(instr) outarr = [] instr = str(instr) for i in range(length): if i < len(instr): outarr.append(CHARSET.index(instr[i])) else: outarr.append(0xFF) return outarr def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic") dtmf = RadioSettingGroup("dtmf", "DTMF") arts = RadioSettingGroup("arts", "ARTS") prog = RadioSettingGroup("prog", "Programmable Buttons") top = RadioSettings(basic, dtmf, arts, prog) basic.append(RadioSetting( "priority_revert", "Priority Revert", RadioSettingValueBoolean(_settings.priority_revert))) basic.append(RadioSetting( "memory_only", "Memory Only mode", RadioSettingValueBoolean(_settings.memory_only))) opts = ["off"] + ["%0.1f" % (t / 60.0) for t in range(30, 750, 30)] basic.append(RadioSetting( "apo", "APO time (hrs)", RadioSettingValueList(opts, opts[_settings.apo]))) basic.append(RadioSetting( "beep_scan", "Beep: Scan", RadioSettingValueBoolean(_settings.beep_scan))) basic.append(RadioSetting( "beep_edge", "Beep: Edge", RadioSettingValueBoolean(_settings.beep_edge))) basic.append(RadioSetting( "beep_key", "Beep: Key", RadioSettingValueBoolean(_settings.beep_key))) opts = ["T/RX Normal", "RX Reverse", "TX Reverse", "T/RX Reverse"] basic.append(RadioSetting( "dcs_polarity", "DCS polarity", RadioSettingValueList(opts, opts[_settings.dcs_polarity]))) opts = ["off", "dim 1", "dim 2", "dim 3"] basic.append(RadioSetting( "dimmer", "Dimmer", RadioSettingValueList(opts, opts[_settings.dimmer]))) opts = ["manual", "auto", "1-auto"] basic.append(RadioSetting( "hyper_write", "Hyper Write", RadioSettingValueList(opts, opts[_settings.hyper_write]))) opts = ["", "key", "dial", "key+dial", "ptt", "ptt+key", "ptt+dial", "all"] basic.append(RadioSetting( "lock", "Lock mode", RadioSettingValueList(opts, opts[_settings.lock]))) opts = ["MH-42", "MH-48"] basic.append(RadioSetting( "microphone_type", "Microphone Type", RadioSettingValueList(opts, opts[_settings.microphone_type]))) opts = ["off"] + ["S-%d" % n for n in range(2, 10)] + ["S-Full"] basic.append(RadioSetting( "rf_sql", "RF Squelch", RadioSettingValueList(opts, opts[_settings.rf_sql]))) opts = ["time", "hold", "busy"] basic.append(RadioSetting( "scan_resume", "Scan Resume", RadioSettingValueList(opts, opts[_settings.scan_resume]))) opts = ["single", "continuous"] basic.append(RadioSetting( "smart_search", "Smart Search", RadioSettingValueList(opts, opts[_settings.smart_search]))) opts = ["off"] + ["%d" % t for t in range(1, 31)] basic.append(RadioSetting( "tot", "Time-out timer (mins)", RadioSettingValueList(opts, opts[_settings.tot]))) # dtmf tab opts = ["50", "100", "250", "450", "750", "1000"] dtmf.append(RadioSetting( "dtmf_delay", "DTMF delay (ms)", RadioSettingValueList(opts, opts[_settings.dtmf_delay]))) opts = ["50", "75", "100"] dtmf.append(RadioSetting( "dtmf_speed", "DTMF speed (ms)", RadioSettingValueList(opts, opts[_settings.dtmf_speed]))) for i in range(16): name = "dtmf%02d" % i dtmfsetting = self._memobj.dtmf[i] dtmfstr = "" for c in dtmfsetting.memory: if c == 0xFF: break if c < len(DTMFCHARSET): dtmfstr += DTMFCHARSET[c] LOG.debug(dtmfstr) dtmfentry = RadioSettingValueString(0, 16, dtmfstr) dtmfentry.set_charset(DTMFCHARSET + list(" ")) rs = RadioSetting(name, name.upper(), dtmfentry) dtmf.append(rs) # arts tab opts = ["off", "in range", "always"] arts.append(RadioSetting( "arts_mode", "ARTS beep", RadioSettingValueList(opts, opts[_settings.arts_mode]))) opts = ["15", "25"] arts.append(RadioSetting( "arts_interval", "ARTS interval", RadioSettingValueList(opts, opts[_settings.arts_interval]))) arts.append(RadioSetting( "arts_cwid_enable", "CW ID", RadioSettingValueBoolean(_settings.arts_cwid_enable))) _arts_cwid = self._memobj.arts_cwid cwid = RadioSettingValueString( 0, 16, self._decode_chars(_arts_cwid.get_value())) cwid.set_charset(CHARSET) arts.append(RadioSetting("arts_cwid", "CW ID", cwid)) # prog buttons opts = ["WX", "Reverse", "Repeater", "SQL Off", "Lock", "Dimmer"] prog.append(RadioSetting( "prog_panel_acc", "Prog Panel - Low(ACC)", RadioSettingValueList(opts, opts[_settings.prog_panel_acc]))) opts = ["Reverse", "Home"] prog.append(RadioSetting( "prog_tone_vm", "TONE | V/M", RadioSettingValueList(opts, opts[_settings.prog_tone_vm]))) opts = ["" for n in range(26)] + \ ["Priority", "Low", "Tone", "MHz", "Reverse", "Home", "Band", "VFO/MR", "Scan", "Sql Off", "TCall", "SSCH", "ARTS", "Tone Freq", "DCSC", "WX", "Repeater"] prog.append(RadioSetting( "prog_p1", "P1", RadioSettingValueList(opts, opts[_settings.prog_p1]))) prog.append(RadioSetting( "prog_p2", "P2", RadioSettingValueList(opts, opts[_settings.prog_p2]))) prog.append(RadioSetting( "prog_p3", "P3", RadioSettingValueList(opts, opts[_settings.prog_p3]))) prog.append(RadioSetting( "prog_p4", "P4", RadioSettingValueList(opts, opts[_settings.prog_p4]))) return top def set_settings(self, uisettings): for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: _settings = self._memobj.settings setting = element.get_name() if re.match('dtmf\d', setting): # set dtmf fields dtmfstr = str(element.value).strip() newval = [] for i in range(0, 16): if i < len(dtmfstr): newval.append(DTMFCHARSET.index(dtmfstr[i])) else: newval.append(0xFF) LOG.debug(newval) idx = int(setting[-2:]) _settings = self._memobj.dtmf[idx] _settings.memory = newval continue if setting == "arts_cwid": oldval = self._memobj.arts_cwid newval = self._encode_chars(newval.get_value(), 6) self._memobj.arts_cwid = newval continue # normal settings newval = element.value oldval = getattr(_settings, setting) LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) setattr(_settings, setting, newval) except Exception, e: LOG.debug(element.get_name()) raise 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_mappings(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] _memstart = 0x0000 TMODES = ["", "Tone", "TSQL", "DTCS"] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the "left" [V/M] key while turning the radio on. 4. Rotate the "right" DIAL knob to select "CLONE START". 5. Press the [SET] key. The display will disappear for a moment, then the "CLONE" notation will appear. 6. After clicking OK, press the "left" [V/M] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to DATA jack. 3. Press and hold in the "left" [V/M] key while turning the radio on. 4. Rotate the "right" DIAL knob to select "CLONE START". 5. Press the [SET] key. The display will disappear for a moment, then the "CLONE" notation will appear. 6. Press the "left" [LOW] key ("CLONE -RX-" will appear on the display).""")) return rp 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] MODES = ["FM", "NFM", "AM"] 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 = self.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-daily-20170714/chirp/drivers/baofeng_wp970i.py0000644000016101777760000007010113060205711023232 0ustar jenkinsnogroup00000000000000# Copyright 2016: # * Jim Unroe KC9HI, # # 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 time import struct import logging import re LOG = logging.getLogger(__name__) from chirp.drivers import baofeng_common from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettingValueFloat, RadioSettings, \ InvalidValueError from textwrap import dedent ##### MAGICS ######################################################### # Baofeng WP970I magic string MSTRING_WP970I = "\x50\xBB\xFF\x20\x14\x04\x13" DTMF_CHARS = "0123456789 *#ABCD" STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] LIST_AB = ["A", "B"] LIST_ALMOD = ["Site", "Tone", "Code"] LIST_BANDWIDTH = ["Wide", "Narrow"] LIST_COLOR = ["Off", "Blue", "Orange", "Purple"] LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)] LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"] LIST_MODE = ["Channel", "Name", "Frequency"] LIST_OFF1TO9 = ["Off"] + list("123456789") LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"] LIST_OFFAB = ["Off"] + LIST_AB LIST_RESUME = ["TO", "CO", "SE"] LIST_PONMSG = ["Full", "Message"] LIST_PTTID = ["Off", "BOT", "EOT", "Both"] LIST_SCODE = ["%s" % x for x in range(1, 16)] LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)] LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"] LIST_SHIFTD = ["Off", "+", "-"] LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)] LIST_STEP = [str(x) for x in STEPS] LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)] LIST_TXPOWER = ["High", "Mid", "Low"] LIST_VOICE = ["Off", "English", "Chinese"] LIST_WORKMODE = ["Frequency", "Channel"] def model_match(cls, data): """Match the opened/downloaded image to the correct version""" if len(data) > 0x2008: rid = data[0x2008:0x2010] return rid.startswith(cls.MODEL) elif len(data) == 0x2008: rid = data[0x1EF0:0x1EF7] return rid in cls._fileid else: return False class WP970I(baofeng_common.BaofengCommonHT): """Baofeng WP970I""" VENDOR = "Baofeng" MODEL = "WP970I" _fileid = [] _magic = [MSTRING_WP970I, ] _magic_response_length = 8 _fw_ver_start = 0x1EF0 _recv_block_size = 0x40 _mem_size = 0x2000 _ack_block = True _ranges = [(0x0000, 0x0DF0), (0x0E00, 0x1800), (0x1EE0, 0x1EF0), (0x1F60, 0x1F70), (0x1F80, 0x1F90), (0x1FC0, 0x1FD0)] _send_block_size = 0x10 MODES = ["NFM", "FM"] VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ "!@#$%^&*()+-=[]:\";'<>?,./" LENGTH_NAME = 6 SKIP_VALUES = ["", "S"] DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00), chirp_common.PowerLevel("Med", watts=3.00), chirp_common.PowerLevel("Low", watts=1.00)] VALID_BANDS = [(130000000, 180000000), (400000000, 521000000)] PTTID_LIST = LIST_PTTID SCODE_LIST = LIST_SCODE MEM_FORMAT = """ #seekto 0x0000; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rxtone; ul16 txtone; u8 unused1:3, isuhf:1, scode:4; u8 unknown1:7, txtoneicon:1; u8 mailicon:3, unknown2:3, lowpower:2; u8 unknown3:1, wide:1, unknown4:2, bcl:1, scan:1, pttid:2; } memory[128]; #seekto 0x0B00; struct { u8 code[5]; u8 unused[11]; } pttid[15]; #seekto 0x0CAA; struct { u8 code[5]; u8 unused1:6, aniid:2; u8 unknown[2]; u8 dtmfon; u8 dtmfoff; } ani; #seekto 0x0E20; 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 unknown12:6, screv:2; 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; u8 rogerrx; u8 tdrch; u8 displayab:1, unknown1:2, fmradio:1, alarm:1, unknown2:1, reset:1, menu:1; u8 unknown1:6, singleptt:1, vfomrlock:1; u8 workmode; u8 keylock; } settings; #seekto 0x0E76; struct { u8 unused1:1, mrcha:7; u8 unused2:1, mrchb:7; } wmchannel; struct vfo { u8 unknown0[8]; u8 freq[8]; u8 offset[6]; ul16 rxtone; ul16 txtone; u8 unused1:7, band:1; u8 unknown3; u8 unused2:2, sftd:2, scode:4; u8 unknown4; u8 unused3:1 step:3, unused4:4; u8 unused5:1, widenarr:1, unused6:4, txpower3:2; }; #seekto 0x0F00; struct { struct vfo a; struct vfo b; } vfo; #seekto 0x0F4E; u16 fm_presets; #seekto 0x1000; struct { char name[7]; u8 unknown1[9]; } names[128]; #seekto 0x1ED0; struct { char line1[7]; char line2[7]; } sixpoweron_msg; #seekto 0x1EE0; struct { char line1[7]; char line2[7]; } poweron_msg; #seekto 0x1EF0; struct { char line1[7]; char line2[7]; } firmware_msg; struct squelch { u8 sql0; u8 sql1; u8 sql2; u8 sql3; u8 sql4; u8 sql5; u8 sql6; u8 sql7; u8 sql8; u8 sql9; }; #seekto 0x1F60; struct { struct squelch vhf; u8 unknown1[6]; u8 unknown2[16]; struct squelch uhf; } squelch; struct limit { u8 enable; bbcd lower[2]; bbcd upper[2]; }; #seekto 0x1FC0; struct { struct limit vhf; struct limit uhf; } limits; """ @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This driver is a beta version.\n' '\n' 'Please save an unedited copy of your first successful\n' 'download to a CHIRP Radio Images(*.img) file.' ) rp.pre_download = _(dedent("""\ Follow these instructions to download your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to upload your info: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap) def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" _mem = self._memobj basic = RadioSettingGroup("basic", "Basic Settings") advanced = RadioSettingGroup("advanced", "Advanced Settings") other = RadioSettingGroup("other", "Other Settings") work = RadioSettingGroup("work", "Work Mode Settings") fm_preset = RadioSettingGroup("fm_preset", "FM Preset") dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings") service = RadioSettingGroup("service", "Service Settings") top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe, service) # Basic settings if _mem.settings.squelch > 0x09: val = 0x00 else: val = _mem.settings.squelch rs = RadioSetting("settings.squelch", "Squelch", RadioSettingValueList( LIST_OFF1TO9, LIST_OFF1TO9[val])) basic.append(rs) if _mem.settings.save > 0x04: val = 0x00 else: val = _mem.settings.save rs = RadioSetting("settings.save", "Battery Saver", RadioSettingValueList( LIST_SAVE, LIST_SAVE[val])) basic.append(rs) if _mem.settings.vox > 0x0A: val = 0x00 else: val = _mem.settings.vox rs = RadioSetting("settings.vox", "Vox", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) if _mem.settings.abr > 0x0A: val = 0x00 else: val = _mem.settings.abr rs = RadioSetting("settings.abr", "Backlight Timeout", RadioSettingValueList( LIST_OFF1TO10, LIST_OFF1TO10[val])) basic.append(rs) rs = RadioSetting("settings.tdr", "Dual Watch", RadioSettingValueBoolean(_mem.settings.tdr)) basic.append(rs) rs = RadioSetting("settings.beep", "Beep", RadioSettingValueBoolean(_mem.settings.beep)) basic.append(rs) if _mem.settings.timeout > 0x27: val = 0x03 else: val = _mem.settings.timeout rs = RadioSetting("settings.timeout", "Timeout Timer", RadioSettingValueList( LIST_TIMEOUT, LIST_TIMEOUT[val])) basic.append(rs) if _mem.settings.voice > 0x02: val = 0x01 else: val = _mem.settings.voice rs = RadioSetting("settings.voice", "Voice Prompt", RadioSettingValueList( LIST_VOICE, LIST_VOICE[val])) basic.append(rs) rs = RadioSetting("settings.dtmfst", "DTMF Sidetone", RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[ _mem.settings.dtmfst])) basic.append(rs) if _mem.settings.screv > 0x02: val = 0x01 else: val = _mem.settings.screv rs = RadioSetting("settings.screv", "Scan Resume", RadioSettingValueList( LIST_RESUME, LIST_RESUME[val])) basic.append(rs) rs = RadioSetting("settings.pttid", "When to send PTT ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[ _mem.settings.pttid])) basic.append(rs) if _mem.settings.pttlt > 0x1E: val = 0x05 else: val = _mem.settings.pttlt rs = RadioSetting("pttlt", "PTT ID Delay", RadioSettingValueInteger(0, 50, val)) basic.append(rs) rs = RadioSetting("settings.mdfa", "Display Mode (A)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfa])) basic.append(rs) rs = RadioSetting("settings.mdfb", "Display Mode (B)", RadioSettingValueList(LIST_MODE, LIST_MODE[ _mem.settings.mdfb])) basic.append(rs) rs = RadioSetting("settings.autolk", "Automatic Key Lock", RadioSettingValueBoolean(_mem.settings.autolk)) basic.append(rs) rs = RadioSetting("settings.wtled", "Standby LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.wtled])) basic.append(rs) rs = RadioSetting("settings.rxled", "RX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.rxled])) basic.append(rs) rs = RadioSetting("settings.txled", "TX LED Color", RadioSettingValueList( LIST_COLOR, LIST_COLOR[_mem.settings.txled])) basic.append(rs) val = _mem.settings.almod rs = RadioSetting("settings.almod", "Alarm Mode", RadioSettingValueList( LIST_ALMOD, LIST_ALMOD[val])) basic.append(rs) if _mem.settings.tdrab > 0x02: val = 0x00 else: val = _mem.settings.tdrab rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority", RadioSettingValueList( LIST_OFFAB, LIST_OFFAB[val])) basic.append(rs) rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)", RadioSettingValueBoolean(_mem.settings.ste)) basic.append(rs) if _mem.settings.rpste > 0x0A: val = 0x00 else: val = _mem.settings.rpste rs = RadioSetting("settings.rpste", "Squelch Tail Eliminate (repeater)", RadioSettingValueList( LIST_RPSTE, LIST_RPSTE[val])) basic.append(rs) if _mem.settings.rptrl > 0x0A: val = 0x00 else: val = _mem.settings.rptrl rs = RadioSetting("settings.rptrl", "STE Repeater Delay", RadioSettingValueList( LIST_STEDELAY, LIST_STEDELAY[val])) basic.append(rs) rs = RadioSetting("settings.ponmsg", "Power-On Message", RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[ _mem.settings.ponmsg])) basic.append(rs) rs = RadioSetting("settings.roger", "Roger Beep", RadioSettingValueBoolean(_mem.settings.roger)) basic.append(rs) # Advanced settings rs = RadioSetting("settings.reset", "RESET Menu", RadioSettingValueBoolean(_mem.settings.reset)) advanced.append(rs) rs = RadioSetting("settings.menu", "All Menus", RadioSettingValueBoolean(_mem.settings.menu)) advanced.append(rs) rs = RadioSetting("settings.fmradio", "Broadcast FM Radio", RadioSettingValueBoolean(_mem.settings.fmradio)) advanced.append(rs) rs = RadioSetting("settings.alarm", "Alarm Sound", RadioSettingValueBoolean(_mem.settings.alarm)) advanced.append(rs) # Other settings def _filter(name): filtered = "" for char in str(name): if char in chirp_common.CHARSET_ASCII: filtered += char else: filtered += " " return filtered _msg = _mem.firmware_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val) other.append(rs) _msg = _mem.sixpoweron_msg val = RadioSettingValueString(0, 7, _filter(_msg.line1)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val) other.append(rs) val = RadioSettingValueString(0, 7, _filter(_msg.line2)) val.set_mutable(False) rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val) other.append(rs) _msg = _mem.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) lower = 130 upper = 179 rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.lower)) other.append(rs) rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.vhf.upper)) other.append(rs) lower = 400 upper = 520 rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.lower)) other.append(rs) rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)", RadioSettingValueInteger( lower, upper, _mem.limits.uhf.upper)) other.append(rs) # Work mode settings rs = RadioSetting("settings.displayab", "Display", RadioSettingValueList( LIST_AB, LIST_AB[_mem.settings.displayab])) work.append(rs) rs = RadioSetting("settings.workmode", "VFO/MR Mode", RadioSettingValueList( LIST_WORKMODE, LIST_WORKMODE[_mem.settings.workmode])) work.append(rs) rs = RadioSetting("settings.keylock", "Keypad Lock", RadioSettingValueBoolean(_mem.settings.keylock)) work.append(rs) rs = RadioSetting("wmchannel.mrcha", "MR A Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrcha)) work.append(rs) rs = RadioSetting("wmchannel.mrchb", "MR B Channel", RadioSettingValueInteger(0, 127, _mem.wmchannel.mrchb)) work.append(rs) def convert_bytes_to_freq(bytes): real_freq = 0 for byte in bytes: real_freq = (real_freq * 10) + byte return chirp_common.format_freq(real_freq * 10) def my_validate(value): value = chirp_common.parse_freq(value) msg = ("Can't be less than %i.0000") if value > 99000000 and value < 130 * 1000000: raise InvalidValueError(msg % (130)) msg = ("Can't be between %i.9975-%i.0000") if (179 + 1) * 1000000 <= value and value < 400 * 1000000: raise InvalidValueError(msg % (179, 400)) msg = ("Can't be greater than %i.9975") if value > 99000000 and value > (520 + 1) * 1000000: raise InvalidValueError(msg % (520)) return chirp_common.format_freq(value) def apply_freq(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 10 for i in range(7, -1, -1): obj.freq[i] = value % 10 value /= 10 val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.a.freq)) val1a.set_validate_callback(my_validate) rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a) rs.set_apply_callback(apply_freq, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(_mem.vfo.b.freq)) val1b.set_validate_callback(my_validate) rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b) rs.set_apply_callback(apply_freq, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.sftd", "VFO A Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd])) work.append(rs) rs = RadioSetting("vfo.b.sftd", "VFO B Shift", RadioSettingValueList( LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd])) work.append(rs) def convert_bytes_to_offset(bytes): real_offset = 0 for byte in bytes: real_offset = (real_offset * 10) + byte return chirp_common.format_freq(real_offset * 1000) def apply_offset(setting, obj): value = chirp_common.parse_freq(str(setting.value)) / 1000 for i in range(5, -1, -1): obj.offset[i] = value % 10 value /= 10 val1a = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset)) rs = RadioSetting("vfo.a.offset", "VFO A Offset", val1a) rs.set_apply_callback(apply_offset, _mem.vfo.a) work.append(rs) val1b = RadioSettingValueString( 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset)) rs = RadioSetting("vfo.b.offset", "VFO B Offset", val1b) rs.set_apply_callback(apply_offset, _mem.vfo.b) work.append(rs) rs = RadioSetting("vfo.a.txpower3", "VFO A Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.a.txpower3])) work.append(rs) rs = RadioSetting("vfo.b.txpower3", "VFO B Power", RadioSettingValueList( LIST_TXPOWER, LIST_TXPOWER[_mem.vfo.b.txpower3])) work.append(rs) rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.a.widenarr])) work.append(rs) rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth", RadioSettingValueList( LIST_BANDWIDTH, LIST_BANDWIDTH[_mem.vfo.b.widenarr])) work.append(rs) rs = RadioSetting("vfo.a.scode", "VFO A S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.a.scode])) work.append(rs) rs = RadioSetting("vfo.b.scode", "VFO B S-CODE", RadioSettingValueList( LIST_SCODE, LIST_SCODE[_mem.vfo.b.scode])) work.append(rs) rs = RadioSetting("vfo.a.step", "VFO A Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.a.step])) work.append(rs) rs = RadioSetting("vfo.b.step", "VFO B Tuning Step", RadioSettingValueList( LIST_STEP, LIST_STEP[_mem.vfo.b.step])) work.append(rs) # broadcast FM settings _fm_presets = self._memobj.fm_presets if _fm_presets <= 108.0 * 10 - 650: preset = _fm_presets / 10.0 + 65 elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10: preset = _fm_presets / 10.0 else: preset = 76.0 rs = RadioSetting("fm_presets", "FM Preset(MHz)", RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)) fm_preset.append(rs) # DTMF settings def apply_code(setting, obj, length): code = [] for j in range(0, length): try: code.append(DTMF_CHARS.index(str(setting.value)[j])) except IndexError: code.append(0xFF) obj.code = code for i in range(0, 15): _codeobj = self._memobj.pttid[i].code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) pttid = RadioSetting("pttid/%i.code" % i, "Signal Code %i" % (i + 1), val) pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5) dtmfe.append(pttid) if _mem.ani.dtmfon > 0xC3: val = 0x03 else: val = _mem.ani.dtmfon rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) if _mem.ani.dtmfoff > 0xC3: val = 0x03 else: val = _mem.ani.dtmfoff rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)", RadioSettingValueList(LIST_DTMFSPEED, LIST_DTMFSPEED[val])) dtmfe.append(rs) _codeobj = self._memobj.ani.code _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F]) val = RadioSettingValueString(0, 5, _code, False) val.set_charset(DTMF_CHARS) rs = RadioSetting("ani.code", "ANI Code", val) rs.set_apply_callback(apply_code, self._memobj.ani, 5) dtmfe.append(rs) rs = RadioSetting("ani.aniid", "When to send ANI ID", RadioSettingValueList(LIST_PTTID, LIST_PTTID[_mem.ani.aniid])) dtmfe.append(rs) # Service settings for band in ["vhf", "uhf"]: for index in range(0, 10): key = "squelch.%s.sql%i" % (band, index) if band == "vhf": _obj = self._memobj.squelch.vhf elif band == "uhf": _obj = self._memobj.squelch.uhf val = RadioSettingValueInteger(0, 123, getattr(_obj, "sql%i" % (index))) if index == 0: val.set_mutable(False) name = "%s Squelch %i" % (band.upper(), index) rs = RadioSetting(key, name, val) service.append(rs) return top @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) in [0x2008, 0x2010]: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False class RH5XAlias(chirp_common.Alias): VENDOR = "Rugged" MODEL = "RH5X" @directory.register class BFA58(WP970I): """Baofeng BF-A58""" VENDOR = "Baofeng" MODEL = "BF-A58" ALIASES = [RH5XAlias] _fileid = ["BFT515 ", "BFT517 "] @directory.register class UV82WP(WP970I): """Baofeng UV82-WP""" VENDOR = "Baofeng" MODEL = "UV-82WP" @directory.register class GT3WP(WP970I): """Baofeng GT-3WP""" VENDOR = "Baofeng" MODEL = "GT-3WP" @directory.register class RT6(WP970I): """Retevis RT6""" VENDOR = "Retevis" MODEL = "RT6" chirp-daily-20170714/chirp/drivers/ic2300.py0000644000016101777760000003262413124401200021412 0ustar jenkinsnogroup00000000000000# Copyright 2017 Windsor Schmidt # # 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, directory, bitwise from chirp.drivers import icf from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueList, RadioSettingValueBoolean, RadioSettings # The Icom IC-2300H is a 65W, 144MHz mobile transceiver based on the IC-2200H. # Unlike the IC-2200H, this model does not accept Icom's UT-118 D-STAR board. # # A simple USB interface based on a typical FT232RL breakout board was used # during development of this module. A schematic diagram is as follows: # # # 3.5mm plug from IC-2300H # sleeve / ring / tip # --.______________ # | | | \ # |______|___|___/ FT232RL breakout # --' | | .------------------. # | +--------| RXD | # | | D1 | | # | +--|>|---| TXD | USB/PC # | | R1 | |--------> # | +--[_]---| VCC (5V) | # | | | # +------------| GND | # `------------------' # # D1: 1N4148 shottky diode # R1: 10K ohm resistor MEM_FORMAT = """ #seekto 0x0000; // channel memories struct { ul16 frequency; ul16 offset; char name[6]; u8 repeater_tone; u8 ctcss_tone; u8 dtcs_code; u8 tuning_step:4, tone_mode:4; u8 unknown1:3, mode_narrow:1, unknown2:4; u8 dtcs_polarity:2, duplex:2, unknown3:1, reverse_duplex:1, unknown4:1, display_style:1; } memory[200]; #seekto 0x1340; // channel memory flags struct { u8 unknown5:2, empty:1, skip:1, bank:4; } flags[200]; #seekto 0x1660; // power-on and regular set menu items struct { u8 key_beep; u8 tx_timeout; u8 auto_repeater; u8 auto_power_off; u8 repeater_lockout; u8 squelch_delay; u8 squelch_type; u8 dtmf_speed; u8 display_type; u8 unknown6; u8 tone_burst; u8 voltage_display; u8 unknown7; u8 display_brightness; u8 display_color; u8 auto_dimmer; u8 display_contrast; u8 scan_pause_timer; u8 mic_gain; u8 scan_resume_timer; u8 weather_alert; u8 bank_link_enable; u8 bank_link[10]; } settings; """ TUNING_STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0] TONE_MODES = ["", "Tone", "TSQL", "DTCS"] DUPLEX = ["", "-", "+"] DTCSP = ["NN", "NR", "RN", "RR"] DTCS_POLARITY = ["NN", "NR", "RN", "RR"] 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 _wipe_memory(mem, char): mem.set_raw(char * (mem.size() / 8)) @directory.register class IC2300Radio(icf.IcomCloneModeRadio): """Icom IC-2300""" VENDOR = "Icom" MODEL = "IC-2300H" _model = "\x32\x51\x00\x01" _memsize = 6304 _endframe = "Icom Inc.C5\xfd" _can_hispeed = True _ranges = [(0x0000, 0x18a0, 32)] # upload entire memory for now def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.valid_modes = ["FM", "NFM"] rf.valid_tmodes = list(TONE_MODES) rf.valid_duplexes = list(DUPLEX) rf.valid_tuning_steps = list(TUNING_STEPS) rf.valid_bands = [(136000000, 174000000)] # USA tx range: 144-148MHz rf.valid_skips = ["", "S"] rf.valid_power_levels = POWER_LEVELS rf.has_settings = True return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) 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_memory(self, number): _mem = self._memobj.memory[number] _flag = self._memobj.flags[number] mem = chirp_common.Memory() mem.number = number if _flag.empty: mem.empty = True return mem mult = int(TUNING_STEPS[_mem.tuning_step] * 1000) mem.freq = (_mem.frequency * mult) mem.offset = (_mem.offset * mult) mem.name = str(_mem.name).rstrip() mem.rtone = chirp_common.TONES[_mem.repeater_tone] mem.ctone = chirp_common.TONES[_mem.ctcss_tone] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs_code] mem.tuning_step = TUNING_STEPS[_mem.tuning_step] mem.tmode = TONE_MODES[_mem.tone_mode] mem.mode = "NFM" if _mem.mode_narrow else "FM" mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity] mem.duplex = DUPLEX[_mem.duplex] mem.skip = "S" if _flag.skip else "" # Reverse duplex mem.extra = RadioSettingGroup("extra", "Extra") rev = RadioSetting("reverse_duplex", "Reverse duplex", RadioSettingValueBoolean(bool(_mem.reverse_duplex))) rev.set_doc("Reverse duplex") mem.extra.append(rev) # Memory display style opt = ["Frequency", "Label"] dsp = RadioSetting("display_style", "Display style", RadioSettingValueList(opt, opt[_mem.display_style])) dsp.set_doc("Memory display style") mem.extra.append(dsp) return mem def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def set_memory(self, mem): 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") mult = mem.tuning_step * 1000 _mem.frequency = (mem.freq / mult) _mem.offset = mem.offset / mult _mem.name = mem.name.ljust(6) _mem.repeater_tone = chirp_common.TONES.index(mem.rtone) _mem.ctcss_tone = chirp_common.TONES.index(mem.ctone) _mem.dtcs_code = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) _mem.tone_mode = TONE_MODES.index(mem.tmode) _mem.mode_narrow = mem.mode.startswith("N") _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity) _mem.duplex = DUPLEX.index(mem.duplex) _flag.skip = mem.skip != "" for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") front_panel = RadioSettingGroup("front_panel", "Front Panel Settings") top = RadioSettings(basic, front_panel) # Transmit timeout opt = ['Disabled', '1 minute'] + \ [s + ' minutes' for s in map(str, range(2, 31))] rs = RadioSetting("tx_timeout", "Transmit timeout (min)", RadioSettingValueList(opt, opt[ _settings.tx_timeout ])) basic.append(rs) # Auto Repeater (USA model only) opt = ["Disabled", "Duplex Only", "Duplex and tone"] rs = RadioSetting("auto_repeater", "Auto repeater", RadioSettingValueList(opt, opt[ _settings.auto_repeater ])) basic.append(rs) # Auto Power Off opt = ["Disabled", "30 minutes", "60 minutes", "120 minutes"] rs = RadioSetting("auto_power_off", "Auto power off", RadioSettingValueList(opt, opt[ _settings.auto_power_off ])) basic.append(rs) # Squelch Delay opt = ["Short", "Long"] rs = RadioSetting("squelch_delay", "Squelch delay", RadioSettingValueList(opt, opt[ _settings.squelch_delay ])) basic.append(rs) # Squelch Type opt = ["Noise squelch", "S-meter squelch", "Squelch attenuator"] rs = RadioSetting("squelch_type", "Squelch type", RadioSettingValueList(opt, opt[ _settings.squelch_type ])) basic.append(rs) # Repeater Lockout opt = ["Disabled", "Repeater lockout", "Busy lockout"] rs = RadioSetting("repeater_lockout", "Repeater lockout", RadioSettingValueList(opt, opt[ _settings.repeater_lockout ])) basic.append(rs) # DTMF Speed opt = ["100ms interval, 5.0 cps", "200ms interval, 2.5 cps", "300ms interval, 1.6 cps", "500ms interval, 1.0 cps"] rs = RadioSetting("dtmf_speed", "DTMF speed", RadioSettingValueList(opt, opt[ _settings.dtmf_speed ])) basic.append(rs) # Scan pause timer opt = [s + ' seconds' for s in map(str, range(2, 22, 2))] + ['Hold'] rs = RadioSetting("scan_pause_timer", "Scan pause timer", RadioSettingValueList( opt, opt[_settings.scan_pause_timer])) basic.append(rs) # Scan Resume Timer opt = ['Immediate'] + \ [s + ' seconds' for s in map(str, range(1, 6))] + ['Hold'] rs = RadioSetting("scan_resume_timer", "Scan resume timer", RadioSettingValueList( opt, opt[_settings.scan_resume_timer])) basic.append(rs) # Weather Alert (USA model only) rs = RadioSetting("weather_alert", "Weather alert", RadioSettingValueBoolean(_settings.weather_alert)) basic.append(rs) # Tone Burst rs = RadioSetting("tone_burst", "Tone burst", RadioSettingValueBoolean(_settings.tone_burst)) basic.append(rs) # Memory Display Type opt = ["Frequency", "Channel", "Name"] rs = RadioSetting("display_type", "Memory display", RadioSettingValueList(opt, opt[_settings.display_type])) front_panel.append(rs) # Display backlight brightness; opt = ["1 (dimmest)", "2", "3", "4 (brightest)"] rs = RadioSetting("display_brightness", "Backlight brightness", RadioSettingValueList( opt, opt[_settings.display_brightness])) front_panel.append(rs) # Display backlight color opt = ["Amber", "Yellow", "Green"] rs = RadioSetting("display_color", "Backlight color", RadioSettingValueList(opt, opt[_settings.display_color])) front_panel.append(rs) # Display contrast opt = ["1 (lightest)", "2", "3", "4 (darkest)"] rs = RadioSetting("display_contrast", "Display contrast", RadioSettingValueList( opt, opt[_settings.display_contrast])) front_panel.append(rs) # Auto dimmer opt = ["Disabled", "Backlight off", "1 (dimmest)", "2", "3"] rs = RadioSetting("auto_dimmer", "Auto dimmer", RadioSettingValueList(opt, opt[_settings.auto_dimmer])) front_panel.append(rs) # Microphone gain opt = ["Low", "High"] rs = RadioSetting("mic_gain", "Microphone gain", RadioSettingValueList(opt, opt[_settings.mic_gain])) front_panel.append(rs) # Key press beep rs = RadioSetting("key_beep", "Key press beep", RadioSettingValueBoolean(_settings.key_beep)) front_panel.append(rs) # Voltage Display; rs = RadioSetting("voltage_display", "Voltage display", RadioSettingValueBoolean(_settings.voltage_display)) front_panel.append(rs) # TODO: Add Bank Links settings to GUI 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 setting = element.get_name() setattr(_settings, setting, element.value) chirp-daily-20170714/chirp/drivers/idrp.py0000644000016101777760000001110012476257220021456 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 serial import logging from chirp import chirp_common, errors, util LOG = logging.getLogger(__name__) 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: LOG.error("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 LOG.debug("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]))) LOG.debug("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-daily-20170714/chirp/drivers/kyd.py0000644000016101777760000003706213060727311021317 0ustar jenkinsnogroup00000000000000# Copyright 2014 Jim Unroe # Copyright 2014 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 time import os import struct import logging from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettings LOG = logging.getLogger(__name__) MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; ul16 rx_tone; ul16 tx_tone; u8 unknown1:3, bcl:2, // Busy Lock unknown2:3; u8 unknown3:2, highpower:1, // Power Level wide:1, // Bandwidth unknown4:4; u8 unknown5[2]; } memory[16]; #seekto 0x012F; struct { u8 voice; // Voice Annunciation u8 tot; // Time-out Timer u8 totalert; // Time-out Timer Pre-alert u8 unknown1[2]; u8 squelch; // Squelch Level u8 save; // Battery Saver u8 beep; // Beep u8 unknown2[3]; u8 vox; // VOX Gain u8 voxdelay; // VOX Delay } settings; #seekto 0x017E; u8 skipflags[2]; // SCAN_ADD """ CMD_ACK = "\x06" NC630A_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("High", watts=5.00)] NC630A_DTCS = sorted(chirp_common.DTCS_CODES + [645]) BCL_LIST = ["Off", "Carrier", "QT/DQT"] TIMEOUTTIMER_LIST = [""] + ["%s seconds" % x for x in range(15, 615, 15)] TOTALERT_LIST = ["", "Off"] + ["%s seconds" % x for x in range(1, 11)] VOICE_LIST = ["Off", "Chinese", "English"] VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)] VOXDELAY_LIST = ["0.3", "0.5", "1.0", "1.5", "2.0", "3.0"] SETTING_LISTS = { "bcl": BCL_LIST, "tot": TIMEOUTTIMER_LIST, "totalert": TOTALERT_LIST, "voice": VOICE_LIST, "vox": VOX_LIST, "voxdelay": VOXDELAY_LIST, } def _nc630a_enter_programming_mode(radio): serial = radio.pipe try: serial.write("PROGRAM") ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if not ack: raise errors.RadioError("No response from radio") elif ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") try: serial.write("\x02") ident = serial.read(8) except: raise errors.RadioError("Error communicating with radio") if not ident.startswith(radio._fileid): LOG.debug(util.hexprint(ident)) raise errors.RadioError("Radio returned unknown identification string") try: serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Error communicating with radio") if ack != CMD_ACK: raise errors.RadioError("Radio refused to enter programming mode") def _nc630a_read_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'R', block_addr, block_size) expectedresponse = "W" + cmd[1:] LOG.debug("Reading block %04x..." % (block_addr)) try: serial.write(cmd) response = serial.read(4 + block_size) if response[:4] != expectedresponse: raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] serial.write(CMD_ACK) ack = serial.read(1) except: raise errors.RadioError("Failed to read block at %04x" % block_addr) if ack != CMD_ACK: raise Exception("No ACK reading block %04x." % (block_addr)) return block_data def _nc630a_write_block(radio, block_addr, block_size): serial = radio.pipe cmd = struct.pack(">cHb", 'W', block_addr, block_size) data = radio.get_mmap()[block_addr:block_addr + block_size] LOG.debug("Writing Data:") LOG.debug(util.hexprint(cmd + data)) try: serial.write(cmd + data) if serial.read(1) != CMD_ACK: raise Exception("No ACK") except: raise errors.RadioError("Failed to send block " "to radio at %04x" % block_addr) def do_download(radio): LOG.debug("download") _nc630a_enter_programming_mode(radio) data = "" status = chirp_common.Status() status.msg = "Cloning from radio" status.cur = 0 status.max = radio._memsize for addr in range(0, radio._memsize, radio._block_size): status.cur = addr + radio._block_size radio.status_fn(status) block = _nc630a_read_block(radio, addr, radio._block_size) data += block LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) return memmap.MemoryMap(data) def do_upload(radio): status = chirp_common.Status() status.msg = "Uploading to radio" _nc630a_enter_programming_mode(radio) status.cur = 0 status.max = radio._memsize for start_addr, end_addr in radio._ranges: for addr in range(start_addr, end_addr, radio._block_size): status.cur = addr + radio._block_size radio.status_fn(status) _nc630a_write_block(radio, addr, radio._block_size) class MT700Alias(chirp_common.Alias): VENDOR = "Plant-Tours" MODEL = "MT-700" @directory.register class NC630aRadio(chirp_common.CloneModeRadio): """KYD NC-630A""" VENDOR = "KYD" MODEL = "NC-630A" ALIASES = [MT700Alias] BAUD_RATE = 9600 _ranges = [ (0x0000, 0x0330), ] _memsize = 0x03C8 _block_size = 0x08 _fileid = "P32073" def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_ctone = True rf.has_cross = True rf.has_rx_dtcs = True rf.has_tuning_step = False rf.can_odd_split = True rf.has_name = False 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 = NC630A_POWER_LEVELS rf.valid_duplexes = ["", "-", "+", "split", "off"] rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz. rf.memory_bounds = (1, 16) rf.valid_bands = [(400000000, 520000000)] return rf def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def sync_in(self): self._mmap = do_download(self) self.process_mmap() def sync_out(self): do_upload(self) 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) LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone, rxmode, _mem.rx_tone)) def get_memory(self, number): bitpos = (1 << ((number - 1) % 8)) bytepos = ((number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[number - 1] _skp = self._memobj.skipflags[bytepos] mem = chirp_common.Memory() mem.number = number mem.freq = int(_mem.rxfreq) * 10 # We'll consider any blank (i.e. 0MHz frequency) to be empty if mem.freq == 0: mem.empty = True return mem if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": mem.freq = 0 mem.empty = True return mem if int(_mem.rxfreq) == int(_mem.txfreq): mem.duplex = "" mem.offset = 0 else: mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 mem.mode = _mem.wide and "FM" or "NFM" self._get_tone(_mem, mem) mem.power = NC630A_POWER_LEVELS[_mem.highpower] mem.skip = "" if (_skp & bitpos) else "S" LOG.debug("mem.skip %s" % mem.skip) mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting("bcl", "Busy Channel Lockout", RadioSettingValueList( BCL_LIST, BCL_LIST[_mem.bcl])) mem.extra.append(rs) return mem def _set_tone(self, mem, _mem): def _set_dcs(code, pol): val = int("%i" % code, 8) + 0x2800 if pol == "R": val += 0x8000 return val rx_mode = tx_mode = None rx_tone = tx_tone = 0xFFFF if mem.tmode == "Tone": tx_mode = "Tone" rx_mode = None tx_tone = int(mem.rtone * 10) elif mem.tmode == "TSQL": rx_mode = tx_mode = "Tone" rx_tone = tx_tone = int(mem.ctone * 10) elif mem.tmode == "DTCS": tx_mode = rx_mode = "DTCS" tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) elif mem.tmode == "Cross": tx_mode, rx_mode = mem.cross_mode.split("->") if tx_mode == "DTCS": tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) elif tx_mode == "Tone": tx_tone = int(mem.rtone * 10) if rx_mode == "DTCS": rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) elif rx_mode == "Tone": rx_tone = int(mem.ctone * 10) _mem.rx_tone = rx_tone _mem.tx_tone = tx_tone LOG.debug("Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone)) def set_memory(self, mem): bitpos = (1 << ((mem.number - 1) % 8)) bytepos = ((mem.number - 1) / 8) LOG.debug("bitpos %s" % bitpos) LOG.debug("bytepos %s" % bytepos) _mem = self._memobj.memory[mem.number - 1] _skp = self._memobj.skipflags[bytepos] if mem.empty: _mem.set_raw("\xFF" * 16) return _mem.set_raw("\x00" * 14 + "\xFF" * 2) _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 _mem.wide = mem.mode == "FM" self._set_tone(mem, _mem) _mem.highpower = mem.power == NC630A_POWER_LEVELS[1] if mem.skip != "S": _skp |= bitpos else: _skp &= ~bitpos LOG.debug("_skp %s" % _skp) for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) def get_settings(self): _settings = self._memobj.settings basic = RadioSettingGroup("basic", "Basic Settings") top = RadioSettings(basic) rs = RadioSetting("tot", "Time-out timer", RadioSettingValueList( TIMEOUTTIMER_LIST, TIMEOUTTIMER_LIST[_settings.tot])) basic.append(rs) rs = RadioSetting("totalert", "TOT Pre-alert", RadioSettingValueList( TOTALERT_LIST, TOTALERT_LIST[_settings.totalert])) basic.append(rs) rs = RadioSetting("vox", "VOX Gain", RadioSettingValueList( VOX_LIST, VOX_LIST[_settings.vox])) basic.append(rs) rs = RadioSetting("voice", "Voice Annumciation", RadioSettingValueList( VOICE_LIST, VOICE_LIST[_settings.voice])) basic.append(rs) rs = RadioSetting("squelch", "Squelch Level", RadioSettingValueInteger(0, 9, _settings.squelch)) basic.append(rs) rs = RadioSetting("voxdelay", "VOX Delay", RadioSettingValueList( VOXDELAY_LIST, VOXDELAY_LIST[_settings.voxdelay])) basic.append(rs) rs = RadioSetting("beep", "Beep", RadioSettingValueBoolean(_settings.beep)) basic.append(rs) rs = RadioSetting("save", "Battery Saver", RadioSettingValueBoolean(_settings.save)) basic.append(rs) return top def set_settings(self, settings): for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue 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() LOG.debug("Setting %s = %s" % (setting, element.value)) setattr(obj, setting, element.value) except Exception, e: LOG.debug(element.get_name()) raise @classmethod def match_model(cls, filedata, filename): match_size = match_model = False # testing the file data size if len(filedata) in [0x338, 0x3C8]: match_size = True # testing model fingerprint if filedata[0x01B8:0x01BE] == cls._fileid: match_model = True if match_size and match_model: return True else: return False chirp-daily-20170714/chirp/drivers/ft857.py0000644000016101777760000014563513067650002021412 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.drivers import ft817 from chirp import chirp_common, errors, directory from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings import os import logging from textwrap import dedent from chirp.util import safe_charset_string LOG = logging.getLogger(__name__) @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->", 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)] CHARSET = list(chirp_common.CHARSET_ASCII) for i in "\\{|}": CHARSET.remove(i) 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 0x00; struct { u16 radioconfig; u8 mem_vfo:2, m_tune:1, home:1, pms_tune:1, qmb:1, mt_qmb:1, vfo_ab:1; u8 unknown; u8 fst:1, lock:1, nb:1, unknown1:2, disp:1, agc:2; u8 vox:1, unknown2:1, bk:1, kyr:1, cw_speed_unit:1, cw_key_rev:1, pwr_meter_mode:2; u8 vfo_b_freq_range:4, vfo_a_freq_range:4; u8 unknown3; u8 disp_mode:2, unknown4:2, disp_contrast:4; u8 unknown5:4, clar_dial_sel:2, beep_tone:2; u8 arts_beep:2, dial_step:1, arts_id:1, unknown6:1, pkt_rate:1, unknown7:2; u8 unknown8:2, lock_mode:2, unknown9:1, cw_pitch:3; u8 sql_rf_gain:1, ars_144:1, ars_430:1, cw_weight:5; u8 cw_delay; u8 cw_delay_hi:1 cw_sidetone:7; u8 unknown10: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, unknown11:1, apo_time:3; u8 dcs_inv:2, unknown12:1, tot_time:5; u8 mic_scan:1, ssb_mic:7; u8 cw_paddle:1, am_mic:7; u8 unknown13:1, fm_mic:7; u8 unknown14:1, dig_mic:7; u8 extended_menu:1, pkt1200:7; u8 unknown15:1, pkt9600:7; i16 dig_shift; i16 dig_disp; i8 r_lsb_car; i8 r_usb_car; i8 t_lsb_car; i8 t_usb_car; u8 unknown16:1, menu_item:7; u8 unknown17[5]; u8 unknown18:1, mtr_peak_hold:1, mic_sel:2, cat_lin_tun:2, unknown19:1, split_tone:1; u8 unknown20:1, beep_vol:7; u8 unknown21:1, dig_vox:7; u8 ext_menu:1, home_vfo:1, scan_mode:2, scan_resume:4; u8 cw_auto_mode:1, cw_training:2, cw_qsk:3, cw_bfo:2; u8 dsp_nr:4, dsp_bpf:2, dsp_mic_eq:2; u8 unknown22:3, dsp_lpf:5; u8 mtr_atx_sel:3, unknown23:1, dsp_hpf:4; u8 unknown24:2, disp_intensity:2, unknown25:1, disp_color:3; u8 unknown26:1, disp_color_vfo:1, disp_color_mtr:1, disp_color_mode:1, disp_color_memgrp:1, unknown27:1, disp_color_band:1, disp_color_arts:1; u8 unknown28:3, disp_color_fix:5; u8 unknown29:1, nb_level:7; u8 unknown30:1, proc_level:7; u8 unknown31:1, rf_power_hf:7; u8 unknown32:2, tuner_atas:3, mem_vfo_dial_mode:3; u8 pg_a; u8 pg_b; u8 pg_c; u8 pg_acc; u8 pg_p1; u8 pg_p2; u8 unknown33:3, xvtr_sel:2, unknown33_1:2, op_filter1:1; u8 unknown34:6, tx_if_filter:2; u8 unknown35:3, xvtr_a_negative:1, xvtr_b_negative:1, mtr_arx_sel:3; u8 beacon_time; u8 unknown36[2]; u8 dig_vox_enable:1, unknown37:2, scope_peakhold:1, scope_width:2, proc:1, unknown38:1; u8 unknown39:1, rf_power_6m:7; u8 unknown40:1, rf_power_vhf:7; u8 unknown41:1, rf_power_uhf:7; } settings; #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 0x1bf3; u8 arts_idw[10]; u8 beacon_text1[40]; u8 beacon_text2[40]; u8 beacon_text3[40]; u32 xvtr_a_offset; u32 xvtr_b_offset; u8 op_filter1_name[4]; u8 op_filter2_name[4]; #seekto 0x1CAD; struct mem_struct sixtymeterchannels[5]; """ _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9") + 1) + range(ord("A"), ord("Z") + 1)] + [" ", "/"] _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET, range(0, len(_CALLSIGN_CHARSET)))) _BEACON_CHARSET = _CALLSIGN_CHARSET + ["+", "."] _BEACON_CHARSET_REV = dict(zip(_BEACON_CHARSET, range(0, len(_BEACON_CHARSET)))) # 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())) FILTERS = ["CFIL", "FIL1", "FIL2"] PROGRAMMABLEOPTIONS = [ "MFa:A/B", "MFa:A=B", "MFa:SPL", "MFb:MW", "MFb:SKIP/MCLR", "MFb:TAG", "MFc:STO", "MFc:RCL", "MFc:PROC", "MFd:RPT", "MFd:REV", "MFd:VOX", "MFe:TON/ENC", "MFe:TON/DEC", "MFe:TDCH", "MFf:ARTS", "MFf:SRCH", "MFf:PMS", "MFg:SCN", "MFg:PRI", "MFg:DW", "MFh:SCOP", "MFh:WID", "MFh:STEP", "MFi:MTR", "MFi:SWR", "MFi:DISP", "MFj:SPOT", "MFj:BK", "MFj:KYR", "MFk:TUNE", "MFk:DOWN", "MFk:UP", "MFl:NB", "MFl:AGC", "MFl:AGC SEL", "MFm:IPO", "MFm:ATT", "MFm:NAR", "MFn:CFIL", "MFn:FIL1", "MFn:FIL2", "MFo:PLY1", "MFo:PLY2", "MFo:PLY3", "MFp:DNR", "MFp:DNF", "MFp:DBF", "01:EXT MENU", "02:144MHz ARS", "03:430MHz ARS", "04:AM&FM DIAL", "05:AM MIC GAIN", "06:AM STEP", "07:APO TIME", "08:ARTS BEEP", "09:ARTS ID", "10:ARTS IDW", "11:BEACON TEXT", "12:BEACON TIME", "13:BEEP TONE", "14:BEEP VOL", "15:CAR LSB R", "16:CAR LSB T", "17:CAR USB R", "18:CAR USB T", "19:CAT RATE", "20:CAT/LIN/TUN", "21:CLAR DIAL SEL", "22:CW AUTO MODE", "23:CW BFO", "24:CW DELAY", "25:CW KEY REV", "26:CW PADDLE", "27:CW PITCH", "28:CW QSK", "29:CW SIDE TONE", "30:CW SPEED", "31:CW TRAINING", "32:CW WEIGHT", "33:DCS CODE", "34:DCS INV", "35:DIAL STEP", "36:DIG DISP", "37:DIG GAIN", "38:DIG MODE", "39:DIG SHIFT", "40:DIG VOX", "41:DISP COLOR", "42:DISP CONTRAST", "43:DISP INTENSITY", "44:DISP MODE", "45:DSP BPF WIDTH", "46:DSP HPF CUTOFF", "47:DSP LPF CUTOFF", "48:DSP MIC EQ", "49:DSP NR LEVEL", "50:EMERGENCY", "51:FM MIC GAIN", "52:FM STEP", "53:HOME->VFO", "54:LOCK MODE", "55:MEM GROUP", "56:MEM TAG", "57:MEM/VFO DIAL MODE", "58:MIC SCAN", "59:MIC SEL", "60:MTR ARX", "61:MTR ATX", "62:MTR PEAK HOLD", "63:NB LEVEL", "64:OP FILTER", "71:PKT 1200", "72:PKT 9600", "73:PKT RATE", "74:PROC LEVEL", "75:RF POWER SET", "76:RPT SHIFT", "77:SCAN MODE", "78:SCAN RESUME", "79:SPLIT TONE", "80:SQL/RF GAIN", "81:SSB MIC GAIN", "82:SSB STEP", "83:TONE FREQ", "84:TX TIME", "85:TUNER/ATAS", "86:TX IF FILTER", "87:VOX DELAY", "88:VOX GAIN", "89:XVTR A FREQ", "90:XVTR B FREQ", "91:XVTR SEL", "MONI", "Q.SPL", "TCALL", "ATC", "USER"] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to CAT/LINEAR jack. 3. Press and hold in the [MODE <] and [MODE >] keys while turning the radio on ("CLONE MODE" will appear on the display). 4. After clicking OK, press the [C](SEND) key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to ACC jack. 3. Press and hold in the [MODE <] and [MODE >] keys while turning the radio on ("CLONE MODE" will appear on the display). 4. Press the [A](RCV) key ("receiving" will appear on the LCD).""" )) return rp 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() 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 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") panelcontr = RadioSettingGroup("panelcontr", "Panel controls") top = RadioSettings(basic, cw, packet, panelcontr, panel, extended) rs = RadioSetting("extended_menu", "Extended menu", RadioSettingValueBoolean(_settings.extended_menu)) extended.append(rs) rs = RadioSetting("ars_144", "144MHz ARS", RadioSettingValueBoolean(_settings.ars_144)) basic.append(rs) rs = RadioSetting("ars_430", "430MHz ARS", RadioSettingValueBoolean(_settings.ars_430)) basic.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 gain", 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) rs = RadioSetting("arts_id", "ARTS ID", RadioSettingValueBoolean(_settings.arts_id)) extended.append(rs) st = RadioSettingValueString(0, 10, safe_charset_string( self._memobj.arts_idw, self._CALLSIGN_CHARSET) ) st.set_charset(self._CALLSIGN_CHARSET) rs = RadioSetting("arts_idw", "ARTS IDW", st) extended.append(rs) st = RadioSettingValueString(0, 40, safe_charset_string( self._memobj.beacon_text1, self._BEACON_CHARSET) ) st.set_charset(self._BEACON_CHARSET) rs = RadioSetting("beacon_text1", "Beacon text1", st) extended.append(rs) st = RadioSettingValueString(0, 40, safe_charset_string( self._memobj.beacon_text2, self._BEACON_CHARSET) ) st.set_charset(self._BEACON_CHARSET) rs = RadioSetting("beacon_text2", "Beacon text2", st) extended.append(rs) st = RadioSettingValueString(0, 40, safe_charset_string( self._memobj.beacon_text3, self._BEACON_CHARSET) ) st.set_charset(self._BEACON_CHARSET) rs = RadioSetting("beacon_text3", "Beacon text3", st) extended.append(rs) options = ["OFF"] + ["%i sec" % i for i in range(1, 256)] rs = RadioSetting("beacon_time", "Beacon time", RadioSettingValueList(options, options[_settings.beacon_time]) ) extended.append(rs) options = ["440Hz", "880Hz", "1760Hz"] rs = RadioSetting("beep_tone", "Beep tone", RadioSettingValueList(options, options[_settings.beep_tone])) panel.append(rs) rs = RadioSetting("beep_vol", "Beep volume", RadioSettingValueInteger(0, 100, _settings.beep_vol)) panel.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 = ["4800", "9600", "38400"] rs = RadioSetting("cat_rate", "CAT rate", RadioSettingValueList(options, options[_settings.cat_rate])) basic.append(rs) options = ["CAT", "Linear", "Tuner"] rs = RadioSetting("cat_lin_tun", "CAT/LIN/TUN selection", RadioSettingValueList(options, options[_settings.cat_lin_tun]) ) extended.append(rs) options = ["MAIN", "VFO/MEM", "CLAR"] # TODO test the 3 options on non D radio # which have only SEL and MAIN rs = RadioSetting("clar_dial_sel", "Clarifier dial selection", RadioSettingValueList(options, options[ _settings.clar_dial_sel])) panel.append(rs) rs = RadioSetting("cw_auto_mode", "CW Automatic mode", RadioSettingValueBoolean(_settings.cw_auto_mode)) cw.append(rs) options = ["USB", "LSB", "AUTO"] rs = RadioSetting("cw_bfo", "CW BFO", RadioSettingValueList(options, options[_settings.cw_bfo])) cw.append(rs) options = ["FULL"] + ["%i ms" % (i * 10) for i in range(3, 301)] val = (_settings.cw_delay + _settings.cw_delay_hi * 256) - 2 rs = RadioSetting("cw_delay", "CW delay", RadioSettingValueList(options, options[val])) cw.append(rs) options = ["Normal", "Reverse"] rs = RadioSetting("cw_key_rev", "CW key reverse", RadioSettingValueList(options, options[_settings.cw_key_rev])) cw.append(rs) rs = RadioSetting("cw_paddle", "CW paddle", RadioSettingValueBoolean(_settings.cw_paddle)) cw.append(rs) options = ["%i Hz" % i for i in range(400, 801, 100)] rs = RadioSetting("cw_pitch", "CW pitch", RadioSettingValueList(options, options[_settings.cw_pitch])) cw.append(rs) options = ["%i ms" % i for i in range(5, 31, 5)] rs = RadioSetting("cw_qsk", "CW QSK", RadioSettingValueList(options, options[_settings.cw_qsk])) cw.append(rs) rs = RadioSetting("cw_sidetone", "CW sidetone volume", RadioSettingValueInteger(0, 100, _settings.cw_sidetone)) 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 = ["Numeric", "Alphabet", "AlphaNumeric"] rs = RadioSetting("cw_training", "CW trainig", RadioSettingValueList(options, options[_settings.cw_training]) ) 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) options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"] rs = RadioSetting("dcs_inv", "DCS inv", RadioSettingValueList(options, options[_settings.dcs_inv])) extended.append(rs) options = ["Fine", "Coarse"] rs = RadioSetting("dial_step", "Dial step", RadioSettingValueList(options, options[_settings.dial_step])) panel.append(rs) rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)", RadioSettingValueInteger(-300, 300, _settings.dig_disp)) packet.append(rs) rs = RadioSetting("dig_mic", "Dig gain", RadioSettingValueInteger(0, 100, _settings.dig_mic)) packet.append(rs) options = ["RTTYL", "RTTYU", "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("dig_vox", "Dig vox", RadioSettingValueInteger(0, 100, _settings.dig_vox)) packet.append(rs) options = ["ARTS", "BAND", "FIX", "MEMGRP", "MODE", "MTR", "VFO"] rs = RadioSetting("disp_color", "Display color mode", RadioSettingValueList(options, options[_settings.disp_color])) panel.append(rs) rs = RadioSetting("disp_color_arts", "Display color ARTS set", RadioSettingValueInteger(0, 1, _settings.disp_color_arts)) panel.append(rs) rs = RadioSetting("disp_color_band", "Display color band set", RadioSettingValueInteger(0, 1, _settings.disp_color_band)) panel.append(rs) rs = RadioSetting("disp_color_memgrp", "Display color memory group set", RadioSettingValueInteger(0, 1, _settings.disp_color_memgrp) ) panel.append(rs) rs = RadioSetting("disp_color_mode", "Display color mode set", RadioSettingValueInteger(0, 1, _settings.disp_color_mode)) panel.append(rs) rs = RadioSetting("disp_color_mtr", "Display color meter set", RadioSettingValueInteger(0, 1, _settings.disp_color_mtr)) panel.append(rs) rs = RadioSetting("disp_color_vfo", "Display color VFO set", RadioSettingValueInteger(0, 1, _settings.disp_color_vfo)) panel.append(rs) rs = RadioSetting("disp_color_fix", "Display color fix set", RadioSettingValueInteger(1, 32, _settings.disp_color_fix + 1 )) panel.append(rs) rs = RadioSetting("disp_contrast", "Contrast", RadioSettingValueInteger(3, 15, _settings.disp_contrast + 2) ) panel.append(rs) rs = RadioSetting("disp_intensity", "Intensity", RadioSettingValueInteger(1, 3, _settings.disp_intensity)) panel.append(rs) options = ["OFF", "Auto1", "Auto2", "ON"] rs = RadioSetting("disp_mode", "Display backlight mode", RadioSettingValueList(options, options[_settings.disp_mode])) panel.append(rs) options = ["60Hz", "120Hz", "240Hz"] rs = RadioSetting("dsp_bpf", "Dsp band pass filter", RadioSettingValueList(options, options[_settings.dsp_bpf])) cw.append(rs) options = ["100Hz", "160Hz", "220Hz", "280Hz", "340Hz", "400Hz", "460Hz", "520Hz", "580Hz", "640Hz", "720Hz", "760Hz", "820Hz", "880Hz", "940Hz", "1000Hz"] rs = RadioSetting("dsp_hpf", "Dsp hi pass filter cut off", RadioSettingValueList(options, options[_settings.dsp_hpf])) basic.append(rs) options = ["1000Hz", "1160Hz", "1320Hz", "1480Hz", "1650Hz", "1800Hz", "1970Hz", "2130Hz", "2290Hz", "2450Hz", "2610Hz", "2770Hz", "2940Hz", "3100Hz", "3260Hz", "3420Hz", "3580Hz", "3740Hz", "3900Hz", "4060Hz", "4230Hz", "4390Hz", "4550Hz", "4710Hz", "4870Hz", "5030Hz", "5190Hz", "5390Hz", "5520Hz", "5680Hz", "5840Hz", "6000Hz"] rs = RadioSetting("dsp_lpf", "Dsp low pass filter cut off", RadioSettingValueList(options, options[_settings.dsp_lpf])) basic.append(rs) options = ["OFF", "LPF", "HPF", "BOTH"] rs = RadioSetting("dsp_mic_eq", "Dsp mic equalization", RadioSettingValueList(options, options[_settings.dsp_mic_eq])) basic.append(rs) rs = RadioSetting("dsp_nr", "DSP noise reduction level", RadioSettingValueInteger(1, 16, _settings.dsp_nr + 1)) basic.append(rs) # emergency only for US model rs = RadioSetting("fm_mic", "FM mic gain", RadioSettingValueInteger(0, 100, _settings.fm_mic)) basic.append(rs) rs = RadioSetting("home_vfo", "Enable HOME to VFO moving", RadioSettingValueBoolean(_settings.home_vfo)) panel.append(rs) options = ["Dial", "Freq", "Panel", "All"] rs = RadioSetting("lock_mode", "Lock mode", RadioSettingValueList(options, options[_settings.lock_mode])) panel.append(rs) rs = RadioSetting("mem_group", "Mem group", RadioSettingValueBoolean(_settings.mem_group)) basic.append(rs) options = ["CW SIDETONE", "CW SPEED", "MHz/MEM GRP", "MIC GAIN", "NB LEVEL", "RF POWER", "STEP"] rs = RadioSetting("mem_vfo_dial_mode", "Mem/VFO dial mode", RadioSettingValueList(options, options[ _settings.mem_vfo_dial_mode ])) panel.append(rs) rs = RadioSetting("mic_scan", "Mic scan", RadioSettingValueBoolean(_settings.mic_scan)) basic.append(rs) options = ["NOR", "RMT", "CAT"] rs = RadioSetting("mic_sel", "Mic selection", RadioSettingValueList(options, options[_settings.mic_sel])) extended.append(rs) options = ["SIG", "CTR", "VLT", "N/A", "FS", "OFF"] rs = RadioSetting("mtr_arx_sel", "Meter receive selection", RadioSettingValueList(options, options[_settings.mtr_arx_sel]) ) extended.append(rs) options = ["PWR", "ALC", "MOD", "SWR", "VLT", "N/A", "OFF"] rs = RadioSetting("mtr_atx_sel", "Meter transmit selection", RadioSettingValueList(options, options[_settings.mtr_atx_sel]) ) extended.append(rs) rs = RadioSetting("mtr_peak_hold", "Meter peak hold", RadioSettingValueBoolean(_settings.mtr_peak_hold)) extended.append(rs) rs = RadioSetting("nb_level", "Noise blanking level", RadioSettingValueInteger(0, 100, _settings.nb_level)) basic.append(rs) st = RadioSettingValueString(0, 4, safe_charset_string( self._memobj.op_filter1_name, self._CALLSIGN_CHARSET) ) st.set_charset(self._CALLSIGN_CHARSET) rs = RadioSetting("op_filter1_name", "Optional filter1 name", st) extended.append(rs) st = RadioSettingValueString(0, 4, safe_charset_string( self._memobj.op_filter2_name, self._CALLSIGN_CHARSET) ) st.set_charset(self._CALLSIGN_CHARSET) rs = RadioSetting("op_filter2_name", "Optional filter2 name", st) extended.append(rs) rs = RadioSetting("pg_a", "Programmable key MFq:A", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_a])) extended.append(rs) rs = RadioSetting("pg_b", "Programmable key MFq:B", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_b])) extended.append(rs) rs = RadioSetting("pg_c", "Programmable key MFq:C", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_c])) extended.append(rs) rs = RadioSetting("pg_acc", "Programmable mic key ACC", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_acc])) extended.append(rs) rs = RadioSetting("pg_p1", "Programmable mic key P1", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_p1])) extended.append(rs) rs = RadioSetting("pg_p2", "Programmable mic key P2", RadioSettingValueList(self.PROGRAMMABLEOPTIONS, self.PROGRAMMABLEOPTIONS[ _settings.pg_p2])) extended.append(rs) rs = RadioSetting("pkt1200", "Packet 1200 gain level", RadioSettingValueInteger(0, 100, _settings.pkt1200)) packet.append(rs) rs = RadioSetting("pkt9600", "Packet 9600 gain level", RadioSettingValueInteger(0, 100, _settings.pkt9600)) packet.append(rs) options = ["1200", "9600"] rs = RadioSetting("pkt_rate", "Packet rate", RadioSettingValueList(options, options[_settings.pkt_rate])) packet.append(rs) rs = RadioSetting("proc_level", "Proc level", RadioSettingValueInteger(0, 100, _settings.proc_level)) basic.append(rs) rs = RadioSetting("rf_power_hf", "Rf power set HF", RadioSettingValueInteger(5, 100, _settings.rf_power_hf)) basic.append(rs) rs = RadioSetting("rf_power_6m", "Rf power set 6m", RadioSettingValueInteger(5, 100, _settings.rf_power_6m)) basic.append(rs) rs = RadioSetting("rf_power_vhf", "Rf power set VHF", RadioSettingValueInteger(5, 50, _settings.rf_power_vhf)) basic.append(rs) rs = RadioSetting("rf_power_uhf", "Rf power set UHF", RadioSettingValueInteger(2, 20, _settings.rf_power_uhf)) basic.append(rs) options = ["TIME", "BUSY", "STOP"] rs = RadioSetting("scan_mode", "Scan mode", RadioSettingValueList(options, options[_settings.scan_mode])) basic.append(rs) rs = RadioSetting("scan_resume", "Scan resume", RadioSettingValueInteger(1, 10, _settings.scan_resume)) basic.append(rs) rs = RadioSetting("split_tone", "Split tone enable", RadioSettingValueBoolean(_settings.split_tone)) extended.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 gain", RadioSettingValueInteger(0, 100, _settings.ssb_mic)) basic.append(rs) options = ["Off"] + ["%i" % i for i in range(1, 21)] rs = RadioSetting("tot_time", "Time-out timer", RadioSettingValueList(options, options[_settings.tot_time])) basic.append(rs) options = ["OFF", "ATAS(HF)", "ATAS(HF&50)", "ATAS(ALL)", "TUNER"] rs = RadioSetting("tuner_atas", "Tuner/ATAS device", RadioSettingValueList(options, options[_settings.tuner_atas])) extended.append(rs) rs = RadioSetting("tx_if_filter", "Transmit IF filter", RadioSettingValueList(self.FILTERS, self.FILTERS[ _settings.tx_if_filter])) basic.append(rs) rs = RadioSetting("vox_delay", "VOX delay (*100 ms)", RadioSettingValueInteger(1, 30, _settings.vox_delay)) basic.append(rs) rs = RadioSetting("vox_gain", "VOX Gain", RadioSettingValueInteger(1, 100, _settings.vox_gain)) basic.append(rs) rs = RadioSetting("xvtr_a", "Xvtr A displacement", RadioSettingValueInteger( -4294967295, 4294967295, self._memobj.xvtr_a_offset * (-1 if _settings.xvtr_a_negative else 1))) extended.append(rs) rs = RadioSetting("xvtr_b", "Xvtr B displacement", RadioSettingValueInteger( -4294967295, 4294967295, self._memobj.xvtr_b_offset * (-1 if _settings.xvtr_b_negative else 1))) extended.append(rs) options = ["OFF", "XVTR A", "XVTR B"] rs = RadioSetting("xvtr_sel", "Transverter function selection", RadioSettingValueList(options, options[_settings.xvtr_sel])) extended.append(rs) rs = RadioSetting("disp", "Display large", RadioSettingValueBoolean(_settings.disp)) panel.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) rs = RadioSetting("scope_peakhold", "Scope max hold", RadioSettingValueBoolean(_settings.scope_peakhold)) panelcontr.append(rs) options = ["21", "31", "127"] rs = RadioSetting("scope_width", "Scope width (channels)", RadioSettingValueList(options, options[_settings.scope_width]) ) panelcontr.append(rs) rs = RadioSetting("proc", "Speech processor", RadioSettingValueBoolean(_settings.proc)) 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() try: LOG.debug("Setting %s(%s) <= %s" % (setting, getattr(obj, setting), element.value)) except AttributeError: LOG.debug("Setting %s <= %s" % (setting, element.value)) if setting == "arts_idw": self._memobj.arts_idw = \ [self._CALLSIGN_CHARSET_REV[x] for x in str(element.value)] elif setting in ["beacon_text1", "beacon_text2", "beacon_text3", "op_filter1_name", "op_filter2_name"]: setattr(self._memobj, setting, [self._BEACON_CHARSET_REV[x] for x in str(element.value)]) elif setting == "cw_delay": val = int(element.value) + 2 setattr(obj, "cw_delay_hi", val / 256) setattr(obj, setting, val & 0xff) elif setting == "dig_vox": val = int(element.value) setattr(obj, "dig_vox_enable", int(val > 0)) setattr(obj, setting, val) elif setting in ["disp_color_fix", "dsp_nr"]: setattr(obj, setting, int(element.value) - 1) elif setting == "disp_contrast": setattr(obj, setting, int(element.value) - 2) elif setting in ["xvtr_a", "xvtr_b"]: val = int(element.value) setattr(obj, setting + "_negative", int(val < 0)) setattr(self._memobj, setting + "_offset", abs(val)) else: setattr(obj, setting, element.value) except: LOG.debug(element.get_name()) raise @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 = "" _US_model = True _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", "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) def get_settings(self): top = FT857Radio.get_settings(self) basic = top[0] rs = RadioSetting("emergency", "Emergency", RadioSettingValueBoolean( self._memobj.settings.emergency)) basic.append(rs) return top chirp-daily-20170714/chirp/drivers/id31.py0000644000016101777760000002147512605412177021276 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.drivers import icf from chirp import directory, bitwise, chirp_common MEM_FORMAT = """ struct { u24 freq; u16 offset; u16 rtone:6, ctone:6, unknown2:1, mode:3; 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]; char tag[4]; } 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)] MODES = {0: "FM", 1: "NFM", 5: "DV"} 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 = self.MODES.values() 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 self.MODES[int(_mem.mode)] == "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] mem.mode = self.MODES[int(_mem.mode)] if 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() if _psk & bit: mem.skip = "P" elif _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.mode = next(i for i, mode in self.MODES.items() if mode == memory.mode) 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-daily-20170714/chirp/drivers/ft817.py0000644000016101777760000013550513013011022021361 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.drivers import yaesu_clone from chirp import chirp_common, util, memmap, errors, directory, bitwise from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueList, \ RadioSettingValueBoolean, RadioSettingValueString, \ RadioSettings import time import logging from textwrap import dedent LOG = logging.getLogger(__name__) CMD_ACK = 0x06 @directory.register class FT817Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-817""" BAUD_RATE = 9600 MODEL = "FT-817" _model = "" _US_model = False 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 = list(chirp_common.CHARSET_ASCII) CHARSET.remove("\\") _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; il16 dig_shift; il16 dig_disp; i8 r_lsb_car; i8 r_usb_car; i8 t_lsb_car; i8 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())) @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to ACC jack. 3. Press and hold in the [MODE <] and [MODE >] keys while turning the radio on ("CLONE MODE" will appear on the display). 4. After clicking OK, press the [A] key to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 2. Connect cable to ACC jack. 3. Press and hold in the [MODE <] and [MODE >] keys while turning the radio on ("CLONE MODE" will appear on the display). 4. Press the [C] key ("RX" will appear on the LCD).""")) return rp def _read(self, block, blocknum, lastblock): # be very patient at first block if blocknum == 0: attempts = 60 else: attempts = 5 for _i in range(0, attempts): 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)) # Chew away the block number and the checksum data = data[1:block + 1] else: if lastblock and self._US_model: raise Exception(_("Unable to read last block. " "This often happens when the selected model " "is US but the radio is a non-US one (or " "widebanded). Please choose the correct " "model and try again.")) else: raise Exception("Unable to read block %02X expected %i got %i" % (blocknum, block + 2, len(data))) LOG.debug("Read %i" % len(data)) return data def _clone_in(self): # Be very patient with the radio self.pipe.timeout = 2 start = time.time() data = "" blocks = 0 status = chirp_common.Status() status.msg = _("Cloning from radio") nblocks = len(self._block_lengths) + 39 status.max = nblocks 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, blocks == nblocks - 1) self.pipe.write(chr(CMD_ACK)) blocks += 1 status.cur = blocks self.status_fn(status) if not self._US_model: status.msg = _("Clone completed, checking for spurious bytes") self.status_fn(status) moredata = self.pipe.read(2) if moredata: raise Exception( _("Radio sent data after the last awaited block, " "this happens when the selected model is a non-US " "but the radio is a US one. " "Please choose the correct model and try again.")) LOG.info("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) LOG.debug("Block %i - will send from %i to %i byte " % (blocks, pos, pos + block)) LOG.debug(util.hexprint(chr(blocks))) LOG.debug(util.hexprint(self.get_mmap()[pos:pos + block])) LOG.debug(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): LOG.debug(util.hexprint(buf)) raise Exception(_("Radio did not ack block %i") % blocks) pos += block blocks += 1 status.cur = blocks self.status_fn(status) LOG.info("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 = [] 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", "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", "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", "extd_number", "name", "dtcs_polarity", "power", "comment"] elif mem.number == -1: _mem = self._memobj.qmb immutable = ["number", "skip", "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 mem.number not 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 key != "extd_number": 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 or _mem.freq == 0xffffffff: 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 == 0xFF: break if chr(i) in self.CHARSET: mem.name += chr(i) else: # radio have some graphical chars that are not supported # we replace those with a * LOG.info("Replacing char %x with *" % i) mem.name += "*" 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 # there are ft857D that have problems with short labels, see bug #937 # some of the radio fill with 0xff and some with blanks # the latter is safe for all ft8x7 radio # so why should i do it only for some? for i in range(0, 8): _mem.name[i] = ord(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 = RadioSettings(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_430)) basic.append(rs) rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level", RadioSettingValueInteger(0, 100, _settings.pkt9600_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) st = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x] for x in self._memobj. callsign])) st.set_charset(self._CALLSIGN_CHARSET) rs = RadioSetting("callsign", "Callsign", st) 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() try: LOG.debug("Setting %s(%s) <= %s" % (setting, getattr(obj, setting), element.value)) except AttributeError: LOG.debug("Setting %s <= %s" % (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: LOG.debug(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 = "" _US_model = True _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", "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[0] rs = RadioSetting("emergency", "Emergency", RadioSettingValueBoolean( self._memobj.settings.emergency)) basic.append(rs) return top chirp-daily-20170714/chirp/drivers/vx170.py0000644000016101777760000000644612476006422021422 0ustar jenkinsnogroup00000000000000# Copyright 2014 Jens Jensen # # 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.drivers import yaesu_clone, ft7800 from chirp import chirp_common, directory, memmap, bitwise, errors from textwrap import dedent import time import os MEM_FORMAT = """ #seekto 0x018A; struct { u16 in_use; } bank_used[24]; #seekto 0x0214; u16 banksoff1; #seekto 0x0294; u16 banksoff2; #seekto 0x097A; struct { u8 name[6]; } bank_names[24]; #seekto 0x0C0A; struct { u16 channels[100]; } banks[24]; #seekto 0x0168; struct { u8 used:1, unknown1:1, mode:1, unknown2:2, duplex:3; bbcd freq[3]; u8 clockshift:1, tune_step:3, unknown5:1, tmode:3; bbcd split[3]; u8 power:2, tone:6; u8 unknown6:1, dtcs:7; u8 unknown7[2]; u8 offset; u8 unknown9[3]; } memory [200]; #seekto 0x0F28; struct { char name[6]; u8 enabled:1, unknown1:7; u8 used:1, unknown2:7; } names[200]; #seekto 0x1768; struct { u8 skip3:2, skip2:2, skip1:2, skip0:2; } flags[50]; """ @directory.register class VX170Radio(ft7800.FTx800Radio): """Yaesu VX-170""" MODEL = "VX-170" _model = "AH022" _memsize = 6057 _block_lengths = [8, 6048, 1] _block_size = 32 POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=5.00), chirp_common.PowerLevel("Med", watts=2.00), chirp_common.PowerLevel("Lo", watts=0.50)] MODES = ["FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn radio off. 2. Connect cable to MIC/SP jack. 3. Press and hold in the [moni] key while turning the radio on. 4. Select CLONE in menu, then press F. Radio restarts in clone mode. ("CLONE" will appear on the display). 5. After clicking OK, breifly hold [PTT] key to send image. ("-TX-" will appear on the LCD). """)) rp.pre_upload = _(dedent("""\ 1. Turn radio off. 3. Press and hold in the [moni] key while turning the radio on. 4. Select CLONE in menu, then press F. Radio restarts in clone mode. ("CLONE" will appear on the display). 5. Press the [moni] key ("-RX-" will appear on the LCD).""")) return rp def _checksums(self): return [yaesu_clone.YaesuChecksum(0x0000, self._memsize - 2)] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_features(self): rf = super(VX170Radio, self).get_features() rf.has_bank = False rf.has_bank_names = False rf.valid_modes = self.MODES rf.memory_bounds = (1, 200) rf.valid_bands = [(137000000, 174000000)] return rf chirp-daily-20170714/chirp/drivers/tk270.py0000644000016101777760000007157113115722176021407 0ustar jenkinsnogroup00000000000000# Copyright 2016 Pavel Milanes CO7WT, # # 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 time import struct import logging LOG = logging.getLogger(__name__) from chirp import chirp_common, directory, memmap from chirp import bitwise, errors, util from chirp.settings import RadioSettingGroup, RadioSetting, \ RadioSettingValueBoolean, RadioSettingValueList, \ RadioSettingValueString, RadioSettingValueInteger, \ RadioSettings from textwrap import dedent MEM_FORMAT = """ #seekto 0x0010; struct { lbcd rxfreq[4]; lbcd txfreq[4]; lbcd rx_tone[2]; lbcd tx_tone[2]; u8 unknown41:1, unknown42:1, power:1, // high power set (1=off) shift:1, // Shift (1=off) busy:1, // Busy lock (1=off) unknown46:1, unknown47:1, unknown48:1; u8 rxen; // xff if off, x00 if enabled (if chan sel = 00) u8 txen; // xff if off, x00 if enabled u8 unknown7; } memory[32]; #seekto 0x0338; u8 scan[4]; // 4 bytes / bit LSBF for the channel #seekto 0x033C; u8 active[4]; // 4 bytes / bit LSBF for the active cha // active = 0 #seekto 0x0340; struct { u8 kMoni; // monitor key funcion u8 kScan; // scan key funcion u8 kDial; // dial key funcion u8 kTa; // ta key funcion u8 kLo; // low key funcion u8 unknown40[7]; // 0x034c u8 tot; // TOT val * 30 steps (x00-0xa) u8 tot_alert; // TOT pre-alert val * 10 steps, (x00-x19) u8 tot_rekey; // TOT rekey val, 0-60, (x00-x3c) u8 tot_reset; // TOT reset val, 0-15, (x00-x0f) // 0x0350 u8 sql; // SQL level val, 0-9 (default 6) u8 unknown50[12]; u8 unknown30:1, unknown31:1, dealer:1, // dealer & test mode (1=on) add:1, // add/del from the scan (1=on) unknown34:1, batt_save:1, // Battery save (1=on) unknown36:1, beep:1; // beep on tone (1=on) u8 unknown51[2]; } settings; #seekto 0x03f0; struct { u8 batt_level; // inverted (ff-val) u8 sq_tight; // sq tight (ff-val) u8 sq_open; // sq open (ff-val) u8 high_power; // High power u8 qt_dev; // QT deviation u8 dqt_dev; // DQT deviation u8 low_power; // low power } tune; """ MEM_SIZE = 0x400 BLOCK_SIZE = 8 MEM_BLOCKS = range(0, (MEM_SIZE / BLOCK_SIZE)) ACK_CMD = "\x06" TIMEOUT = 0.05 # from 0.03 up it' s safe, we set in 0.05 for a margin POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), chirp_common.PowerLevel("High", watts=5)] SKIP_VALUES = ["", "S"] TONES = chirp_common.TONES #TONES.remove(254.1) DTCS_CODES = chirp_common.DTCS_CODES # some vars for the UI off = ["off"] TOT = off + ["%s" % x for x in range(30, 330, 30)] TOT_A = off + ["%s" % x for x in range(10, 260, 10)] TOT_RK = off + ["%s" % x for x in range(1, 61)] TOT_RS = off + ["%s" % x for x in range(1, 16)] SQL = off + ["%s" % x for x in range(1, 10)] # keys MONI = off + ["Monitor momentary", "Monitor lock", "SQ off momentary"] SCAN = off + ["Carrier operated (COS)", "Time operated (TOS)"] YESNO = ["Enabled", "Disabled"] TA = off + ["Turn around", "Reverse"] def rawrecv(radio, amount): """Raw read from the radio device""" data = "" try: data = radio.pipe.read(amount) #print("<= %02i: %s" % (len(data), util.hexprint(data))) except: raise errors.RadioError("Error reading data from radio") return data def rawsend(radio, data): """Raw send to the radio device""" try: radio.pipe.write(data) #print("=> %02i: %s" % (len(data), util.hexprint(data))) except: raise errors.RadioError("Error sending data from radio") def send(radio, frame): """Generic send data to the radio""" rawsend(radio, frame) def make_frame(cmd, addr, data=""): """Pack the info in the format it likes""" ts = struct.pack(">BHB", ord(cmd), addr, 8) if data == "": return ts else: if len(data) == 8: return ts + data else: raise errors.InvalidValueError("Data of unexpected length to send") def handshake(radio, msg="", full=False): """Make a full handshake, if not full just hals""" # send ACK if commandes if full is True: rawsend(radio, ACK_CMD) # receive ACK ack = rawrecv(radio, 1) # check ACK if ack != ACK_CMD: #close_radio(radio) mesg = "Handshake failed: " + msg raise Exception(mesg) def recv(radio): """Receive data from the radio, 12 bytes, 4 in the header, 8 as data""" rxdata = rawrecv(radio, 12) if len(rxdata) != 12: raise errors.RadioError( "Received a length of data that is not possible") return cmd, addr, length = struct.unpack(">BHB", rxdata[0:4]) data = "" if length == 8: data = rxdata[4:] return data def open_radio(radio): """Open the radio into program mode and check if it's the correct model""" # Set serial discipline try: radio.pipe.parity = "N" radio.pipe.timeout = TIMEOUT radio.pipe.flush() except: msg = "Serial error: Can't set serial line discipline" raise errors.RadioError(msg) # we will try to open the radio 5 times, this is an improved mechanism magic = "PROGRAM" exito = False for i in range(0, 5): for i in range(0, len(magic)): ack = rawrecv(radio, 1) time.sleep(0.05) send(radio, magic[i]) try: handshake(radio, "Radio not entering Program mode") exito = True break except: LOG.debug("Attempt #%s, failed, trying again" % i) pass # check if we had EXITO if exito is False: msg = "The radio did not accept program mode after five tries.\n" msg += "Check you interface cable and power cycle your radio." raise errors.RadioError(msg) rawsend(radio, "\x02") ident = rawrecv(radio, 8) handshake(radio, "Comm error after ident", True) if not (radio.TYPE in ident): LOG.debug("Incorrect model ID, got %s" % util.hexprint(ident)) msg = "Incorrect model ID, got %s, it not contains %s" % \ (ident[0:5], radio.TYPE) raise errors.RadioError(msg) def do_download(radio): """This is your download function""" open_radio(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning from radio..." radio.status_fn(status) data = "" for addr in MEM_BLOCKS: send(radio, make_frame("R", addr * BLOCK_SIZE)) data += recv(radio) handshake(radio, "Rx error in block %03i" % addr, True) # DEBUG #print("Block: %04x, Pos: %06x" % (addr, addr * BLOCK_SIZE)) # UI Update status.cur = addr status.msg = "Cloning from radio..." radio.status_fn(status) return memmap.MemoryMap(data) def do_upload(radio): """Upload info to radio""" open_radio(radio) # UI progress status = chirp_common.Status() status.cur = 0 status.max = MEM_SIZE / BLOCK_SIZE status.msg = "Cloning to radio..." radio.status_fn(status) count = 0 for addr in MEM_BLOCKS: # UI Update status.cur = addr status.msg = "Cloning to radio..." radio.status_fn(status) block = addr * BLOCK_SIZE # Beyond 0x03d0 the data is not writable if block > 0x3d0: continue data = radio.get_mmap()[block:block + BLOCK_SIZE] send(radio, make_frame("W", block, data)) time.sleep(0.02) handshake(radio, "Rx error in block %03i" % addr) def get_radio_id(data): """Extract the radio identification from the firmware""" # Reverse the radio id string. MemoryMap does not support the step/stride # slice argument, so it is first sliced to a str then reversed. return data[0x03d0:0x03d8][::-1] def model_match(cls, data): """Match the opened/downloaded image to the correct version""" rid = get_radio_id(data) # DEBUG #print("Full ident string is %s" % util.hexprint(rid)) if (rid in cls.VARIANTS): # correct model return True else: return False class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Kenwood Mobile Family 60 Radios""" VENDOR = "Kenwood" _range = [350000000, 512000000] # don't mind, it will be overited _upper = 32 VARIANT = "" MODEL = "" _kind = "" @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.experimental = \ ('This driver is experimental; not all features have been ' 'implemented, but it has those features most used by hams.\n' '\n' 'This radios are able to work slightly outside the OEM ' 'frequency limits. After testing, the limit in Chirp has ' 'been set 4% outside the OEM limit. This allows you to use ' 'some models on the ham bands.\n' '\n' 'Nevertheless, each radio has its own hardware limits and ' 'your mileage may vary.\n' ) rp.pre_download = _(dedent("""\ Follow this instructions to read your radio: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the download of your radio data """)) rp.pre_upload = _(dedent("""\ Follow this instructions to write your radio: 1 - Turn off your radio 2 - Connect your interface cable 3 - Turn on your radio 4 - Do the upload of your radio data """)) return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.has_settings = True rf.has_bank = False rf.has_tuning_step = False rf.has_name = False rf.has_offset = True rf.has_mode = False rf.has_dtcs = True rf.has_rx_dtcs = True rf.has_dtcs_polarity = True rf.has_ctone = True rf.has_cross = True rf.valid_modes = ["FM"] rf.valid_duplexes = ["", "-", "+", "off"] rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross'] rf.valid_cross_modes = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS"] rf.valid_power_levels = POWER_LEVELS rf.valid_skips = SKIP_VALUES rf.valid_dtcs_codes = DTCS_CODES rf.valid_bands = [self._range] rf.memory_bounds = (1, self._upper) return rf def sync_in(self): """Download from radio""" self._mmap = do_download(self) self.process_mmap() def sync_out(self): """Upload to radio""" # Get the data ready for upload try: self._prep_data() except: raise errors.RadioError("Error processing the radio data") # do the upload try: do_upload(self) except: raise errors.RadioError("Error uploading data to radio") def set_variant(self): """Select and set the correct variables for the class acording to the correct variant of the radio""" rid = get_radio_id(self._mmap) # indentify the radio variant and set the enviroment to it's values try: self._upper, low, high, self._kind = self.VARIANTS[rid] # Frequency ranges: some model/variants are able to work the near # ham bands, even if they are outside the OEM ranges. # By experimentation we found that a +/- 4% at the edges is in most # cases safe and will cover the near ham band in full self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04] # put the VARIANT in the class, clean the model / CHs / Type # in the same layout as the KPG program self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: " # In the OEM string we show the real OEM ranges self._VARIANT += self._kind + ", %d - %d MHz" % (low, high) # DEBUG #print self._VARIANT except KeyError: LOG.debug("Wrong Kenwood radio, ID or unknown variant") LOG.debug(util.hexprint(rid)) raise errors.RadioError( "Wrong Kenwood radio, ID or unknown variant, see LOG output.") def _prep_data(self): """Prepare the areas in the memmap to do a consistent write it has to make an update on the x280 flag data""" achs = 0 for i in range(0, self._upper): if self.get_active(i) is True: achs += 1 # The x0280 area has the settings for the DTMF/2-Tone per channel, # as we don't support this feature yet, # we disabled by cleaning the data #fldata = "\x00\xf0\xff\xff\xff" * achs + \ #"\xff" * (5 * (self._upper - achs)) fldata = "\xFF" * 5 * self._upper self._fill(0x0280, fldata) def _fill(self, offset, data): """Fill an specified area of the memmap with the passed data""" for addr in range(0, len(data)): self._mmap[offset + addr] = data[addr] def process_mmap(self): """Process the mem map into the mem object""" self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) # to set the vars on the class to the correct ones self.set_variant() def get_raw_memory(self, number): return repr(self._memobj.memory[number]) def get_active(self, chan): """Get the channel active status from the 4 bytes array on the eeprom""" byte = int(chan/8) bit = chan % 8 res = self._memobj.active[byte] & (pow(2, bit)) res = not bool(res) return res def set_active(self, chan, value=True): """Set the channel active status from UI to the mem_map""" byte = int(chan/8) bit = chan % 8 # DEBUG #print("SET Chan %s, Byte %s, Bit % s" % (chan, byte, bit)) # get the actual value to see if I need to change anything actual = self.get_active(chan) if actual != bool(value): # DEBUG #print "VALUE %s fliping" % int(not value) # I have to flip the value rbyte = self._memobj.active[byte] rbyte = rbyte ^ pow(2, bit) self._memobj.active[byte] = rbyte def decode_tone(self, val): """Parse the tone data to decode from mem, it returns: Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" if val.get_raw() == "\xFF\xFF": return '', None, None val = int(val) if val >= 12000: a = val - 12000 return 'DTCS', a, 'R' elif val >= 8000: a = val - 8000 return 'DTCS', a, 'N' else: a = val / 10.0 return 'Tone', a, None def encode_tone(self, memval, mode, value, pol): """Parse the tone data to encode from UI to mem""" if mode == '': memval[0].set_raw(0xFF) memval[1].set_raw(0xFF) elif mode == 'Tone': memval.set_value(int(value * 10)) elif mode == 'DTCS': flag = 0x80 if pol == 'N' else 0xC0 memval.set_value(value) memval[1].set_bits(flag) else: raise Exception("Internal error: invalid mode `%s'" % mode) def get_scan(self, chan): """Get the channel scan status from the 4 bytes array on the eeprom then from the bits on the byte, return '' or 'S' as needed""" result = "S" byte = int(chan/8) bit = chan % 8 res = self._memobj.scan[byte] & (pow(2, bit)) if res > 0: result = "" return result def set_scan(self, chan, value): """Set the channel scan status from UI to the mem_map""" byte = int(chan/8) bit = chan % 8 # get the actual value to see if I need to change anything actual = self.get_scan(chan) if actual != value: # I have to flip the value rbyte = self._memobj.scan[byte] rbyte = rbyte ^ pow(2, bit) self._memobj.scan[byte] = rbyte def get_memory(self, number): """Get the mem representation from the radio image""" _mem = self._memobj.memory[number - 1] # Create a high-level memory object to return to the UI mem = chirp_common.Memory() # Memory number mem.number = number if _mem.get_raw()[0] == "\xFF": mem.empty = True # but is not enough, you have to clear the memory in the mmap # to get it ready for the sync_out process, just in case _mem.set_raw("\xFF" * 16) # set the channel to inactive state self.set_active(number - 1, False) return mem # Freq and offset mem.freq = int(_mem.rxfreq) * 10 # tx freq can be blank if _mem.get_raw()[4] == "\xFF" or int(_mem.txen) == 255: # TX freq not set mem.offset = 0 mem.duplex = "off" else: # TX feq set offset = (int(_mem.txfreq) * 10) - mem.freq if offset < 0: mem.offset = abs(offset) mem.duplex = "-" elif offset > 0: mem.offset = offset mem.duplex = "+" else: mem.offset = 0 # power mem.power = POWER_LEVELS[int(_mem.power)] # skip mem.skip = self.get_scan(number - 1) # tone data rxtone = txtone = None txtone = self.decode_tone(_mem.tx_tone) rxtone = self.decode_tone(_mem.rx_tone) chirp_common.split_tone_decode(mem, txtone, rxtone) # Extra # bank and number in the channel mem.extra = RadioSettingGroup("extra", "Extra") bl = RadioSetting("busy", "Busy Channel lock", RadioSettingValueBoolean( not bool(_mem.busy))) mem.extra.append(bl) sf = RadioSetting("shift", "Beat Shift", RadioSettingValueBoolean( not bool(_mem.shift))) mem.extra.append(sf) return mem def set_memory(self, mem): """Set the memory data in the eeprom img from the UI not ready yet, so it will return as is""" # Get a low-level memory object mapped to the image _mem = self._memobj.memory[mem.number - 1] # Empty memory if mem.empty: _mem.set_raw("\xFF" * 16) self.set_active(mem.number - 1, False) return # freq rx _mem.rxfreq = mem.freq / 10 # rx enabled if valid channel, # set tx to on, we decide if off after duplex = off _mem.rxen = 0 _mem.txen = 0 # freq tx if mem.duplex == "+": _mem.txfreq = (mem.freq + mem.offset) / 10 elif mem.duplex == "-": _mem.txfreq = (mem.freq - mem.offset) / 10 elif mem.duplex == "off": # set tx freq on the memap to xff for i in range(0, 4): _mem.txfreq[i].set_raw("\xFF") # erase the txen flag _mem.txen = 255 else: _mem.txfreq = mem.freq / 10 # tone data ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ chirp_common.split_tone_encode(mem) self.encode_tone(_mem.tx_tone, txmode, txtone, txpol) self.encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol) # power, default power is high, as the low is configurable via a key if mem.power is None: mem.power = POWER_LEVELS[1] _mem.power = POWER_LEVELS.index(mem.power) # skip self.set_scan(mem.number - 1, mem.skip) # set as active self.set_active(mem.number - 1, True) # extra settings for setting in mem.extra: setattr(_mem, setting.get_name(), setting.value) return mem @classmethod def match_model(cls, filedata, filename): match_size = False match_model = False # testing the file data size if len(filedata) == MEM_SIZE: match_size = True # testing the firmware model fingerprint match_model = model_match(cls, filedata) if match_size and match_model: return True else: return False def get_settings(self): """Translate the bit in the mem_struct into settings in the UI""" sett = self._memobj.settings # basic features of the radio basic = RadioSettingGroup("basic", "Basic Settings") # keys fkeys = RadioSettingGroup("keys", "Function keys") top = RadioSettings(basic, fkeys) # Basic val = RadioSettingValueString(0, 35, self._VARIANT) val.set_mutable(False) mod = RadioSetting("not.mod", "Radio version", val) basic.append(mod) beep = RadioSetting("settings.beep", "Beep tone", RadioSettingValueBoolean( bool(sett.beep))) basic.append(beep) bsave = RadioSetting("settings.batt_save", "Battery save", RadioSettingValueBoolean( bool(sett.batt_save))) basic.append(bsave) deal = RadioSetting("settings.dealer", "Dealer & Test", RadioSettingValueBoolean( bool(sett.dealer))) basic.append(deal) add = RadioSetting("settings.add", "Del / Add feature", RadioSettingValueBoolean( bool(sett.add))) basic.append(add) # In some cases the values that follows can be 0xFF (HARD RESET) # so we need to take and validate that if int(sett.tot) == 0xff: # 120 sec sett.tot = 4 if int(sett.tot_alert) == 0xff: # 10 secs sett.tot_alert = 1 if int(sett.tot_rekey) == 0xff: # off sett.tot_rekey = 0 if int(sett.tot_reset) == 0xff: # off sett.tot_reset = 0 if int(sett.sql) == 0xff: # a confortable level ~6 sett.sql = 6 tot = RadioSetting("settings.tot", "Time Out Timer (TOT)", RadioSettingValueList(TOT, TOT[int(sett.tot)])) basic.append(tot) tota = RadioSetting("settings.tot_alert", "TOT pre-plert", RadioSettingValueList(TOT_A, TOT_A[int(sett.tot_alert)])) basic.append(tota) totrk = RadioSetting("settings.tot_rekey", "TOT rekey time", RadioSettingValueList(TOT_RK, TOT_RK[int(sett.tot_rekey)])) basic.append(totrk) totrs = RadioSetting("settings.tot_reset", "TOT reset time", RadioSettingValueList(TOT_RS, TOT_RS[int(sett.tot_reset)])) basic.append(totrs) sql = RadioSetting("settings.sql", "Squelch level", RadioSettingValueList(SQL, SQL[int(sett.sql)])) basic.append(sql) # front keys m = int(sett.kMoni) if m > 3: m = 1 mon = RadioSetting("settings.kMoni", "Monitor", RadioSettingValueList(MONI, MONI[m])) fkeys.append(mon) s = int(sett.kScan) if s > 3: s = 1 scn = RadioSetting("settings.kScan", "Scan", RadioSettingValueList(SCAN, SCAN[s])) fkeys.append(scn) d = int(sett.kDial) if d > 1: d = 0 dial = RadioSetting("settings.kDial", "Dial", RadioSettingValueList(YESNO, YESNO[d])) fkeys.append(dial) t = int(sett.kTa) if t > 2: t = 2 ta = RadioSetting("settings.kTa", "Ta", RadioSettingValueList(TA, TA[t])) fkeys.append(ta) l = int(sett.kLo) if l > 1: l = 0 low = RadioSetting("settings.kLo", "Low", RadioSettingValueList(YESNO, YESNO[l])) fkeys.append(low) return top def set_settings(self, settings): """Translate the settings in the UI into bit in the mem_struct I don't understand well the method used in many drivers so, I used mine, ugly but works ok""" mobj = self._memobj for element in settings: if not isinstance(element, RadioSetting): self.set_settings(element) continue # Let's roll the ball if "." in element.get_name(): inter, setting = element.get_name().split(".") # you must ignore the settings with "not" # this are READ ONLY attributes if inter == "not": continue obj = getattr(mobj, inter) value = element.value # integers case + special case if setting in ["tot", "tot_alert", "tot_rekey", \ "tot_reset", "sql", "kMoni", "kScan", \ "kDial", "kTa", "kLo"]: # catching the "off" values as zero try: value = int(value) except: value = 0 # Bool types + inverted if setting in ["beep", "batt_save", "dealer", "add"]: value = bool(value) # Apply al configs done # DEBUG #print("%s: %s" % (setting, value)) setattr(obj, setting, value) # This are the oldest family 60 models just portables support here # Info striped from a hexdump inside the preogram and hack over a # tk-270 @directory.register class TK260_Radio(Kenwood_P60_Radio): """Kenwood TK-260 Radios""" MODEL = "TK-260" TYPE = "P0260" VARIANTS = { "P0260\x20\x00\x00": (4, 136, 150, "F2"), "P0260\x21\x00\x00": (4, 150, 174, "F1"), } @directory.register class TK270_Radio(Kenwood_P60_Radio): """Kenwood TK-270 Radios""" MODEL = "TK-270" TYPE = "P0270" VARIANTS = { "P0270\x10\x00\x00": (32, 136, 150, "F2"), "P0270\x11\x00\x00": (32, 150, 174, "F1"), } @directory.register class TK272_Radio(Kenwood_P60_Radio): """Kenwood TK-272 Radios""" MODEL = "TK-272" TYPE = "P0272" VARIANTS = { "P0272\x10\x00\x00": (10, 136, 150, "F2"), "P0272\x11\x00\x00": (10, 150, 174, "F1"), } @directory.register class TK278_Radio(Kenwood_P60_Radio): """Kenwood TK-278 Radios""" MODEL = "TK-278" TYPE = "P0278" VARIANTS = { "P0278\x00\x00\x00": (32, 136, 150, "F2"), "P0278\x01\x00\x00": (32, 150, 174, "F1"), } @directory.register class TK360_Radio(Kenwood_P60_Radio): """Kenwood TK-360 Radios""" MODEL = "TK-360" TYPE = "P0360" VARIANTS = { "P0360\x24\x00\x00": (4, 450, 470, "F1"), "P0360\x25\x00\x00": (4, 470, 490, "F2"), "P0360\x26\x00\x00": (4, 490, 512, "F3"), "P0360\x23\x00\x00": (4, 406, 430, "F4"), } @directory.register class TK370_Radio(Kenwood_P60_Radio): """Kenwood TK-370 Radios""" MODEL = "TK-370" TYPE = "P0370" VARIANTS = { "P0370\x14\x00\x00": (32, 450, 470, "F1"), "P0370\x15\x00\x00": (32, 470, 490, "F2"), "P0370\x16\x00\x00": (32, 490, 512, "F3"), "P0370\x13\x00\x00": (32, 406, 430, "F4"), } @directory.register class TK372_Radio(Kenwood_P60_Radio): """Kenwood TK-372 Radios""" MODEL = "TK-372" TYPE = "P0372" VARIANTS = { "P0372\x14\x00\x00": (10, 450, 470, "F1"), "P0372\x15\x00\x00": (10, 470, 490, "F2"), } @directory.register class TK378_Radio(Kenwood_P60_Radio): """Kenwood TK-378 Radios""" MODEL = "TK-378" TYPE = "P0378" VARIANTS = { "P0378\x04\x00\x00": (32, 370, 470, "SP1"), "P0378\x02\x00\x00": (32, 350, 427, "SP2"), } chirp-daily-20170714/chirp/drivers/id880.py0000644000016101777760000002551613042326310021357 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.drivers import icf from chirp import chirp_common, directory, bitwise MEM_FORMAT = """ struct { u24 rxmult:3, txmult:3, freq:18; 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] FREQ_MULTIPLIER = [5000, 6250, 6250, 8333, 9000] 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 is 0x01, 0x03, 0x07, etc mask = (1 << i) - 1 # Code gets the upper bits of remainder plus all but the i lower # bits of this byte code = (byte >> i) | rem call += chr(code) # Remainder for next time are the masked bits, moved to the high # places for the next round rem = (byte & mask) << 7 - i # 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 _decode_freq(freq, mult): return int(freq) * FREQ_MULTIPLIER[mult] def _encode_freq(freq): for i, step in reversed(list(enumerate(FREQ_MULTIPLIER))): if freq % step == 0: return freq / step, i raise ValueError("%d cannot be factored by multiplier table." % freq) 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 = _decode_freq(_mem.freq, _mem.rxmult) mem.offset = _decode_freq(_mem.offset, _mem.txmult) 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") _mem.freq, _mem.rxmult = _encode_freq(mem.freq) _mem.offset, _mem.txmult = _encode_freq(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 = 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-daily-20170714/chirp/drivers/th_uv3r.py0000644000016101777760000001773213060727311022124 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 . """TYT uv3r radio management module""" import os import logging from chirp import chirp_common, bitwise, errors, directory from chirp.drivers.wouxun import do_download, do_upload LOG = logging.getLogger(__name__) 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, 520000000)] 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.timeout = 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_mode = tx_mode = "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 LOG.debug(repr(_mem)) @classmethod def match_model(cls, filedata, filename): return len(filedata) == 2320 chirp-daily-20170714/chirp/drivers/ft2900.py0000644000016101777760000012273413013011022021434 0ustar jenkinsnogroup00000000000000# Copyright 2011 Dan Smith # # FT-2900-specific modifications by Richard Cochran, # Initial work on settings by Chris Fosnight, # # 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 import logging from chirp import util, memmap, chirp_common, bitwise, directory, errors from chirp.drivers.yaesu_clone import YaesuCloneModeRadio from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueList, RadioSettingValueString, RadioSettings from textwrap import dedent LOG = logging.getLogger(__name__) def _send(s, data): s.write(data) echo = s.read(len(data)) if data != echo: raise Exception("Failed to read echo") LOG.debug("got echo\n%s\n" % util.hexprint(echo)) ACK = "\x06" INITIAL_CHECKSUM = 0 def _download(radio): blankChunk = "" for _i in range(0, 32): blankChunk += "\xff" LOG.debug("in _download\n") data = "" for _i in range(0, 20): data = radio.pipe.read(20) LOG.debug("Header:\n%s" % util.hexprint(data)) LOG.debug("len(header) = %s\n" % len(data)) if data == radio.IDBLOCK: break if data != radio.IDBLOCK: raise Exception("Failed to read header") _send(radio.pipe, ACK) # initialize data, the big var that holds all memory data = "" _blockNum = 0 while len(data) < radio._block_sizes[1]: _blockNum += 1 time.sleep(0.03) chunk = radio.pipe.read(32) LOG.debug("Block %i " % (_blockNum)) if chunk == blankChunk: LOG.debug("blank chunk\n") else: LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk))) if len(chunk) != 32: LOG.debug("len chunk is %i\n" % (len(chunk))) raise Exception("Failed to get full data block") break else: data += chunk 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) LOG.debug("Total: %i" % len(data)) # radio should send us one final termination byte, containing # checksum chunk = radio.pipe.read(32) if len(chunk) != 1: LOG.debug("len(chunk) is %i\n" % len(chunk)) raise Exception("radio sent extra unknown data") LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk))) # compute checksum cs = INITIAL_CHECKSUM for byte in radio.IDBLOCK: cs += ord(byte) for byte in data: cs += ord(byte) LOG.debug("calculated checksum is %x\n" % (cs & 0xff)) LOG.debug("Radio sent checksum is %x\n" % ord(chunk[0])) if (cs & 0xff) != ord(chunk[0]): raise Exception("Failed checksum on read.") # for debugging purposes, dump the channels, in hex. for _i in range(0, 200): _startData = 1892 + 20 * _i chunk = data[_startData:_startData + 20] LOG.debug("channel %i:\n%s" % (_i, util.hexprint(chunk))) return memmap.MemoryMap(data) def _upload(radio): for _i in range(0, 10): data = radio.pipe.read(256) if not data: break LOG.debug("What is this garbage?\n%s" % util.hexprint(data)) raise Exception("Radio sent unrecognized data") _send(radio.pipe, radio.IDBLOCK) time.sleep(.2) ack = radio.pipe.read(300) LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack))) if ack != ACK: raise Exception("Radio did not ack ID. Check cable, verify" " radio is not locked.\n" " (press & Hold red \"*L\" button to unlock" " radio if needed)") block = 0 cs = INITIAL_CHECKSUM for byte in radio.IDBLOCK: cs += ord(byte) while block < (radio.get_memsize() / 32): data = radio.get_mmap()[block * 32:(block + 1) * 32] LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data))) _send(radio.pipe, data) time.sleep(0.03) for byte in data: cs += ord(byte) 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, chr(cs & 0xFF)) MEM_FORMAT = """ #seekto 0x0080; struct { u8 apo; u8 arts_beep; u8 bell; u8 dimmer; u8 cw_id_string[16]; u8 cw_trng; u8 x95; u8 x96; u8 x97; u8 int_cd; u8 int_set; u8 x9A; u8 x9B; u8 lock; u8 x9D; u8 mic_gain; u8 open_msg; u8 openMsg_Text[6]; u8 rf_sql; u8 unk:6, pag_abk:1, unk:1; u8 pag_cdr_1; u8 pag_cdr_2; u8 pag_cdt_1; u8 pag_cdt_2; u8 prog_p1; u8 xAD; u8 prog_p2; u8 xAF; u8 prog_p3; u8 xB1; u8 prog_p4; u8 xB3; u8 resume; u8 tot; u8 unk:1, cw_id:1, unk:1, ts_speed:1, ars:1, unk:2, dtmf_mode:1; u8 unk:1, ts_mut:1 wires_auto:1, busy_lockout:1, edge_beep:1, unk:3; u8 unk:2, s_search:1, unk:2, cw_trng_units:1, unk:2; u8 dtmf_speed:1, unk:2, arts_interval:1, unk:1, inverted_dcs:1, unk:1, mw_mode:1; u8 unk:2, wires_mode:1, wx_alert:1, unk:1, wx_vol_max:1, revert:1, unk:1; u8 vfo_scan; u8 scan_mode; u8 dtmf_delay; u8 beep; u8 xBF; } settings; #seekto 0x00d0; u8 passwd[4]; u8 mbs; #seekto 0x00c0; struct { u16 in_use; } bank_used[8]; #seekto 0x00ef; u8 currentTone; #seekto 0x00f0; u8 curChannelMem[20]; #seekto 0x1e0; struct { u8 dtmf_string[16]; } dtmf_strings[10]; #seekto 0x0127; u8 curChannelNum; #seekto 0x012a; u8 banksoff1; #seekto 0x15f; u8 checksum1; #seekto 0x16f; u8 curentTone2; #seekto 0x1aa; u16 banksoff2; #seekto 0x1df; u8 checksum2; #seekto 0x0360; struct{ u8 name[6]; } bank_names[8]; #seekto 0x03c4; struct{ u16 channels[50]; } banks[8]; #seekto 0x06e4; 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 0x0764; struct { u8 unknown0:2, isnarrow:1, unknown1:5; u8 unknown2:2, duplex:2, unknown3:1, step:3; bbcd freq[3]; u8 power:2, unknown4:3, tmode:3; u8 name[6]; bbcd offset[3]; u8 ctonesplitflag:1, ctone:7; u8 rx_dtcssplitflag:1, rx_dtcs:7; u8 unknown5; u8 rtonesplitflag:1, rtone:7; u8 dtcssplitflag:1, dtcs:7; } memory[200]; """ MODES = ["FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"] CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone", "Tone->Tone", "DTCS->DTCS"] DUPLEX = ["", "-", "+", "split"] POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=75), chirp_common.PowerLevel("Low3", watts=30), chirp_common.PowerLevel("Low2", watts=10), chirp_common.PowerLevel("Low1", watts=5), ] CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?C[] _" STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] def _decode_tone(radiotone): try: chirptone = chirp_common.TONES[radiotone] except IndexError: chirptone = 100 LOG.debug("found invalid radio tone: %i\n" % radiotone) return chirptone def _decode_dtcs(radiodtcs): try: chirpdtcs = chirp_common.DTCS_CODES[radiodtcs] except IndexError: chirpdtcs = 23 LOG.debug("found invalid radio dtcs code: %i\n" % radiodtcs) return chirpdtcs def _decode_name(mem): name = "" for i in mem: if (i & 0x7F) == 0x7F: break try: name += CHARSET[i & 0x7F] except IndexError: LOG.debug("Unknown char index: %x " % (i)) name = name.strip() return name def _encode_name(mem): if(mem.strip() == ""): return [0xff] * 6 name = [None] * 6 for i in range(0, 6): try: name[i] = CHARSET.index(mem[i]) except IndexError: name[i] = CHARSET.index(" ") name[0] = name[0] | 0x80 return name def _wipe_memory(mem): mem.set_raw("\xff" * (mem.size() / 8)) class FT2900Bank(chirp_common.NamedBank): 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.rstrip() def set_name(self, name): name = name.upper().ljust(6)[:6] _bank = self._model._radio._memobj.bank_names[self.index] _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]] class FT2900BankModel(chirp_common.BankModel): def get_num_mappings(self): return 8 def get_mappings(self): banks = self._radio._memobj.banks bank_mappings = [] for index, _bank in enumerate(banks): bank = FT2900Bank(self, "%i" % index, "b%i" % (index + 1)) bank.index = index bank_mappings.append(bank) return bank_mappings def _get_channel_numbers_in_bank(self, bank): _bank_used = self._radio._memobj.bank_used[bank.index] if _bank_used.in_use == 0xffff: return set() _members = self._radio._memobj.banks[bank.index] return set([int(ch) for ch in _members.channels if ch != 0xffff]) def _update_bank_with_channel_numbers(self, bank, channels_in_bank): _members = self._radio._memobj.banks[bank.index] if len(channels_in_bank) > len(_members.channels): raise Exception("More than %i entries in bank %d" % (len(_members.channels), bank.index)) empty = 0 for index, channel_number in enumerate(sorted(channels_in_bank)): _members.channels[index] = channel_number empty = index + 1 for index in range(empty, len(_members.channels)): _members.channels[index] = 0xffff _bank_used = self._radio._memobj.bank_used[bank.index] if empty == 0: _bank_used.in_use = 0xffff else: _bank_used.in_use = empty - 1 def add_memory_to_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) channels_in_bank.add(memory.number) self._update_bank_with_channel_numbers(bank, channels_in_bank) # tells radio that banks are active self._radio._memobj.banksoff1 = bank.index self._radio._memobj.banksoff2 = bank.index def remove_memory_from_mapping(self, memory, bank): channels_in_bank = self._get_channel_numbers_in_bank(bank) try: channels_in_bank.remove(memory.number) except KeyError: raise Exception("Memory %i is not in bank %s. Cannot remove" % (memory.number, bank)) self._update_bank_with_channel_numbers(bank, channels_in_bank) def get_mapping_memories(self, bank): memories = [] for channel in self._get_channel_numbers_in_bank(bank): memories.append(self._radio.get_memory(channel)) return memories def get_memory_mappings(self, memory): banks = [] for bank in self.get_mappings(): if memory.number in self._get_channel_numbers_in_bank(bank): banks.append(bank) return banks @directory.register class FT2900Radio(YaesuCloneModeRadio): """Yaesu FT-2900""" VENDOR = "Yaesu" MODEL = "FT-2900R/1900R" IDBLOCK = "\x56\x43\x32\x33\x00\x02\x46\x01\x01\x01" BAUD_RATE = 19200 _memsize = 8000 _block_sizes = [8, 8000] def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.can_odd_split = True rf.has_ctone = True rf.has_rx_dtcs = True rf.has_cross = True rf.has_dtcs_polarity = False rf.has_bank = True rf.has_bank_names = True rf.has_settings = True rf.valid_tuning_steps = STEPS rf.valid_modes = MODES rf.valid_tmodes = TMODES rf.valid_cross_modes = CROSS_MODES rf.valid_bands = [(136000000, 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): 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) LOG.info("Downloaded in %.2f sec" % (time.time() - start)) self.process_mmap() def sync_out(self): self.pipe.timeout = 1 start = time.time() try: _upload(self) except errors.RadioError: raise except Exception, e: raise errors.RadioError("Failed to communicate with radio: %s" % e) LOG.info("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] _flag = self._memobj.flags[(number) / 2] nibble = ((number) % 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 _mem.get_raw()[0] == "\xFF" or not valid or not used: mem.empty = True return mem mem.tuning_step = STEPS[_mem.step] mem.freq = int(_mem.freq) * 1000 # compensate for 12.5 kHz tuning steps, add 500 Hz if needed if(mem.tuning_step == 12.5): lastdigit = int(_mem.freq) % 10 if (lastdigit == 2 or lastdigit == 7): mem.freq += 500 mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) mem.duplex = DUPLEX[_mem.duplex] 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 = _decode_tone(_mem.rtone) mem.ctone = _decode_tone(_mem.ctone) # check for unequal ctone/rtone in TSQL mode. map it as a # cross tone mode if mem.rtone != mem.ctone and (mem.tmode == "TSQL" or mem.tmode == "Tone"): mem.tmode = "Cross" mem.cross_mode = "Tone->Tone" mem.dtcs = _decode_dtcs(_mem.dtcs) mem.rx_dtcs = _decode_dtcs(_mem.rx_dtcs) # check for unequal dtcs/rx_dtcs in DTCS mode. map it as a # cross tone mode if mem.dtcs != mem.rx_dtcs and mem.tmode == "DTCS": mem.tmode = "Cross" mem.cross_mode = "DTCS->DTCS" if (int(_mem.name[0]) & 0x80) != 0: mem.name = _decode_name(_mem.name) mem.mode = _mem.isnarrow and "NFM" or "FM" mem.skip = pskip and "P" or skip and "S" or "" mem.power = POWER_LEVELS[3 - _mem.power] 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 "even" or "odd" valid = _flag["%s_valid" % nibble] used = _flag["%s_masked" % nibble] if 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 _flag["%s_valid" % nibble] = True _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.duplex = DUPLEX.index(mem.duplex) # clear all the split tone flags -- we'll set them as needed below _mem.ctonesplitflag = 0 _mem.rx_dtcssplitflag = 0 _mem.rtonesplitflag = 0 _mem.dtcssplitflag = 0 if mem.tmode != "Cross": _mem.tmode = TMODES.index(mem.tmode) # for the non-cross modes, use ONE tone for both send # and receive but figure out where to get it from. if mem.tmode == "TSQL" or mem.tmode == "TSQL-R": _mem.rtone = chirp_common.TONES.index(mem.ctone) _mem.ctone = chirp_common.TONES.index(mem.ctone) else: _mem.rtone = chirp_common.TONES.index(mem.rtone) _mem.ctone = chirp_common.TONES.index(mem.rtone) # and one tone for dtcs, but this is always the sending one _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.rx_dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) else: _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.rx_dtcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs) if mem.cross_mode == "Tone->Tone": # tone->tone cross mode is treated as # TSQL, but with separate tones for # send and receive _mem.tmode = TMODES.index("TSQL") _mem.rtonesplitflag = 1 elif mem.cross_mode == "DTCS->DTCS": # DTCS->DTCS cross mode is treated as # DTCS, but with separate codes for # send and receive _mem.tmode = TMODES.index("DTCS") _mem.dtcssplitflag = 1 else: _mem.tmode = TMODES.index("Cross") + \ CROSS_MODES.index(mem.cross_mode) _mem.isnarrow = MODES.index(mem.mode) _mem.step = STEPS.index(mem.tuning_step) _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" if mem.power: _mem.power = 3 - POWER_LEVELS.index(mem.power) else: _mem.power = 3 _mem.name = _encode_name(mem.name) # set all unknown areas of the memory map to 0 _mem.unknown0 = 0 _mem.unknown1 = 0 _mem.unknown2 = 0 _mem.unknown3 = 0 _mem.unknown4 = 0 _mem.unknown5 = 0 LOG.debug("encoded mem\n%s\n" % (util.hexprint(_mem.get_raw()[0:20]))) def get_settings(self): _settings = self._memobj.settings _dtmf_strings = self._memobj.dtmf_strings _passwd = self._memobj.passwd repeater = RadioSettingGroup("repeater", "Repeater Settings") ctcss = RadioSettingGroup("ctcss", "CTCSS/DCS/EPCS Settings") arts = RadioSettingGroup("arts", "ARTS Settings") mbls = RadioSettingGroup("banks", "Memory Settings") scan = RadioSettingGroup("scan", "Scan Settings") dtmf = RadioSettingGroup("dtmf", "DTMF Settings") wires = RadioSettingGroup("wires", "WiRES(tm) Settings") switch = RadioSettingGroup("switch", "Switch/Knob Settings") disp = RadioSettingGroup("disp", "Display Settings") misc = RadioSettingGroup("misc", "Miscellaneous Settings") setmode = RadioSettings(repeater, ctcss, arts, mbls, scan, dtmf, wires, switch, disp, misc) # numbers and names of settings refer to the way they're # presented in the set menu, as well as the list starting on # page 74 of the manual # 1 APO opts = ["Off", "30 Min", "1 Hour", "3 Hour", "5 Hour", "8 Hour"] misc.append( RadioSetting( "apo", "Automatic Power Off", RadioSettingValueList(opts, opts[_settings.apo]))) # 2 AR.BEP opts = ["Off", "In Range", "Always"] arts.append( RadioSetting( "arts_beep", "ARTS Beep", RadioSettingValueList(opts, opts[_settings.arts_beep]))) # 3 AR.INT opts = ["15 Sec", "25 Sec"] arts.append( RadioSetting( "arts_interval", "ARTS Polling Interval", RadioSettingValueList(opts, opts[_settings.arts_interval]))) # 4 ARS opts = ["Off", "On"] repeater.append( RadioSetting( "ars", "Automatic Repeater Shift", RadioSettingValueList(opts, opts[_settings.ars]))) # 5 BCLO opts = ["Off", "On"] misc.append(RadioSetting( "busy_lockout", "Busy Channel Lock-Out", RadioSettingValueList(opts, opts[_settings.busy_lockout]))) # 6 BEEP opts = ["Off", "Key+Scan", "Key"] switch.append(RadioSetting( "beep", "Enable the Beeper", RadioSettingValueList(opts, opts[_settings.beep]))) # 7 BELL opts = ["Off", "1", "3", "5", "8", "Continuous"] ctcss.append(RadioSetting("bell", "Bell Repetitions", RadioSettingValueList(opts, opts[ _settings.bell]))) # 8 BNK.LNK for i in range(0, 8): opts = ["Off", "On"] mbs = (self._memobj.mbs >> i) & 1 rs = RadioSetting("mbs%i" % i, "Bank %s Scan" % (i + 1), RadioSettingValueList(opts, opts[mbs])) def apply_mbs(s, index): if int(s.value): self._memobj.mbs |= (1 << index) else: self._memobj.mbs &= ~(1 << index) rs.set_apply_callback(apply_mbs, i) mbls.append(rs) # 9 BNK.NM - A per-bank attribute, nothing to do here. # 10 CLK.SFT - A per-channel attribute, nothing to do here. # 11 CW.ID opts = ["Off", "On"] arts.append(RadioSetting("cw_id", "CW ID Enable", RadioSettingValueList(opts, opts[ _settings.cw_id]))) cw_id_text = "" for i in _settings.cw_id_string: try: cw_id_text += CHARSET[i & 0x7F] except IndexError: if i != 0xff: LOG.debug("unknown char index in cw id: %x " % (i)) val = RadioSettingValueString(0, 16, cw_id_text, True) val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz") rs = RadioSetting("cw_id_string", "CW Identifier Text", val) def apply_cw_id(s): str = s.value.get_value().upper().rstrip() mval = "" mval = [chr(CHARSET.index(x)) for x in str] for x in range(len(mval), 16): mval.append(chr(0xff)) for x in range(0, 16): _settings.cw_id_string[x] = ord(mval[x]) rs.set_apply_callback(apply_cw_id) arts.append(rs) # 12 CWTRNG opts = ["Off", "4WPM", "5WPM", "6WPM", "7WPM", "8WPM", "9WPM", "10WPM", "11WPM", "12WPM", "13WPM", "15WPM", "17WPM", "20WPM", "24WPM", "30WPM", "40WPM"] misc.append(RadioSetting("cw_trng", "CW Training", RadioSettingValueList(opts, opts[ _settings.cw_trng]))) # todo: make the setting of the units here affect the display # of the speed. Not critical, but would be slick. opts = ["CPM", "WPM"] misc.append(RadioSetting("cw_trng_units", "CW Training Units", RadioSettingValueList(opts, opts[_settings. cw_trng_units]))) # 13 DC VLT - a read-only status, so nothing to do here # 14 DCS CD - A per-channel attribute, nothing to do here # 15 DCS.RV opts = ["Disabled", "Enabled"] ctcss.append(RadioSetting( "inverted_dcs", "\"Inverted\" DCS Code Decoding", RadioSettingValueList(opts, opts[_settings.inverted_dcs]))) # 16 DIMMER opts = ["Off"] + ["Level %d" % (x) for x in range(1, 11)] disp.append(RadioSetting("dimmer", "Dimmer", RadioSettingValueList(opts, opts[_settings .dimmer]))) # 17 DT.A/M opts = ["Manual", "Auto"] dtmf.append(RadioSetting("dtmf_mode", "DTMF Autodialer", RadioSettingValueList(opts, opts[_settings .dtmf_mode]))) # 18 DT.DLY opts = ["50 ms", "250 ms", "450 ms", "750 ms", "1000 ms"] dtmf.append(RadioSetting("dtmf_delay", "DTMF Autodialer Delay Time", RadioSettingValueList(opts, opts[_settings .dtmf_delay]))) # 19 DT.SET for memslot in range(0, 10): dtmf_memory = "" for i in _dtmf_strings[memslot].dtmf_string: if i != 0xFF: try: dtmf_memory += CHARSET[i] except IndexError: LOG.debug("unknown char index in dtmf: %x " % (i)) val = RadioSettingValueString(0, 16, dtmf_memory, True) val.set_charset(CHARSET + "abcdef") rs = RadioSetting("dtmf_string_%d" % memslot, "DTMF Memory %d" % memslot, val) def apply_dtmf(s, i): LOG.debug("applying dtmf for %x\n" % i) str = s.value.get_value().upper().rstrip() LOG.debug("str is %s\n" % str) mval = "" mval = [chr(CHARSET.index(x)) for x in str] for x in range(len(mval), 16): mval.append(chr(0xff)) for x in range(0, 16): _dtmf_strings[i].dtmf_string[x] = ord(mval[x]) rs.set_apply_callback(apply_dtmf, memslot) dtmf.append(rs) # 20 DT.SPD opts = ["50 ms", "100 ms"] dtmf.append(RadioSetting("dtmf_speed", "DTMF Autodialer Sending Speed", RadioSettingValueList(opts, opts[_settings. dtmf_speed]))) # 21 EDG.BEP opts = ["Off", "On"] mbls.append(RadioSetting("edge_beep", "Band Edge Beeper", RadioSettingValueList(opts, opts[_settings. edge_beep]))) # 22 INT.CD opts = ["DTMF %X" % (x) for x in range(0, 16)] wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)", RadioSettingValueList(opts, opts[ _settings.int_cd]))) # 23 ING MD opts = ["Sister Radio Group", "Friends Radio Group"] wires.append(RadioSetting("wires_mode", "Internet Link Connection Mode", RadioSettingValueList(opts, opts[_settings. wires_mode]))) # 24 INT.A/M opts = ["Manual", "Auto"] wires.append(RadioSetting("wires_auto", "Internet Link Autodialer", RadioSettingValueList(opts, opts[_settings .wires_auto]))) # 25 INT.SET opts = ["F%d" % (x) for x in range(0, 10)] wires.append(RadioSetting("int_set", "Memory Register for " "non-WiRES Internet", RadioSettingValueList(opts, opts[_settings .int_set]))) # 26 LOCK opts = ["Key", "Dial", "Key + Dial", "PTT", "Key + PTT", "Dial + PTT", "All"] switch.append(RadioSetting("lock", "Control Locking", RadioSettingValueList(opts, opts[_settings .lock]))) # 27 MCGAIN opts = ["Level %d" % (x) for x in range(1, 10)] misc.append(RadioSetting("mic_gain", "Microphone Gain", RadioSettingValueList(opts, opts[_settings .mic_gain]))) # 28 MEM.SCN opts = ["Tag 1", "Tag 2", "All Channels"] rs = RadioSetting("scan_mode", "Memory Scan Mode", RadioSettingValueList(opts, opts[_settings .scan_mode - 1])) # this setting is unusual in that it starts at 1 instead of 0. # that is, index 1 corresponds to "Tag 1", and index 0 is invalid. # so we create a custom callback to handle this. def apply_scan_mode(s): myopts = ["Tag 1", "Tag 2", "All Channels"] _settings.scan_mode = myopts.index(s.value.get_value()) + 1 rs.set_apply_callback(apply_scan_mode) mbls.append(rs) # 29 MW MD opts = ["Lower", "Next"] mbls.append(RadioSetting("mw_mode", "Memory Write Mode", RadioSettingValueList(opts, opts[_settings .mw_mode]))) # 30 NM SET - This is per channel, so nothing to do here # 31 OPN.MSG opts = ["Off", "DC Supply Voltage", "Text Message"] disp.append(RadioSetting("open_msg", "Opening Message Type", RadioSettingValueList(opts, opts[_settings. open_msg]))) openmsg = "" for i in _settings.openMsg_Text: try: openmsg += CHARSET[i & 0x7F] except IndexError: if i != 0xff: LOG.debug("unknown char index in openmsg: %x " % (i)) val = RadioSettingValueString(0, 6, openmsg, True) val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz") rs = RadioSetting("openMsg_Text", "Opening Message Text", val) def apply_openmsg(s): str = s.value.get_value().upper().rstrip() mval = "" mval = [chr(CHARSET.index(x)) for x in str] for x in range(len(mval), 6): mval.append(chr(0xff)) for x in range(0, 6): _settings.openMsg_Text[x] = ord(mval[x]) rs.set_apply_callback(apply_openmsg) disp.append(rs) # 32 PAGER - a per-channel attribute # 33 PAG.ABK opts = ["Off", "On"] ctcss.append(RadioSetting("pag_abk", "Paging Answer Back", RadioSettingValueList(opts, opts[_settings .pag_abk]))) # 34 PAG.CDR opts = ["%2.2d" % (x) for x in range(1, 50)] ctcss.append(RadioSetting("pag_cdr_1", "Receive Page Code 1", RadioSettingValueList(opts, opts[_settings .pag_cdr_1]))) ctcss.append(RadioSetting("pag_cdr_2", "Receive Page Code 2", RadioSettingValueList(opts, opts[_settings .pag_cdr_2]))) # 35 PAG.CDT opts = ["%2.2d" % (x) for x in range(1, 50)] ctcss.append(RadioSetting("pag_cdt_1", "Transmit Page Code 1", RadioSettingValueList(opts, opts[_settings .pag_cdt_1]))) ctcss.append(RadioSetting("pag_cdt_2", "Transmit Page Code 2", RadioSettingValueList(opts, opts[_settings .pag_cdt_2]))) # Common Button Options button_opts = ["Squelch Off", "Weather", "Smart Search", "Tone Scan", "Scan", "T Call", "ARTS"] # 36 PRG P1 opts = button_opts + ["DC Volts"] switch.append(RadioSetting( "prog_p1", "P1 Button", RadioSettingValueList(opts, opts[_settings.prog_p1]))) # 37 PRG P2 opts = button_opts + ["Dimmer"] switch.append(RadioSetting( "prog_p2", "P2 Button", RadioSettingValueList(opts, opts[_settings.prog_p2]))) # 38 PRG P3 opts = button_opts + ["Mic Gain"] switch.append(RadioSetting( "prog_p3", "P3 Button", RadioSettingValueList(opts, opts[_settings.prog_p3]))) # 39 PRG P4 opts = button_opts + ["Skip"] switch.append(RadioSetting( "prog_p4", "P4 Button", RadioSettingValueList(opts, opts[_settings.prog_p4]))) # 40 PSWD password = "" for i in _passwd: if i != 0xFF: try: password += CHARSET[i] except IndexError: LOG.debug("unknown char index in password: %x " % (i)) val = RadioSettingValueString(0, 4, password, True) val.set_charset(CHARSET[0:15] + "abcdef ") rs = RadioSetting("passwd", "Password", val) def apply_password(s): str = s.value.get_value().upper().rstrip() mval = "" mval = [chr(CHARSET.index(x)) for x in str] for x in range(len(mval), 4): mval.append(chr(0xff)) for x in range(0, 4): _passwd[x] = ord(mval[x]) rs.set_apply_callback(apply_password) misc.append(rs) # 41 RESUME opts = ["3 Sec", "5 Sec", "10 Sec", "Busy", "Hold"] scan.append(RadioSetting("resume", "Scan Resume Mode", RadioSettingValueList(opts, opts[ _settings.resume]))) # 42 RF.SQL opts = ["Off"] + ["S-%d" % (x) for x in range(1, 10)] misc.append(RadioSetting("rf_sql", "RF Squelch Threshold", RadioSettingValueList(opts, opts[ _settings.rf_sql]))) # 43 RPT - per channel attribute, nothing to do here # 44 RVRT opts = ["Off", "On"] misc.append(RadioSetting("revert", "Priority Revert", RadioSettingValueList(opts, opts[ _settings.revert]))) # 45 S.SRCH opts = ["Single", "Continuous"] misc.append(RadioSetting("s_search", "Smart Search Sweep Mode", RadioSettingValueList(opts, opts[ _settings.s_search]))) # 46 SHIFT - per channel setting, nothing to do here # 47 SKIP = per channel setting, nothing to do here # 48 SPLIT - per channel attribute, nothing to do here # 49 SQL.TYP - per channel attribute, nothing to do here # 50 STEP - per channel attribute, nothing to do here # 51 TEMP - read-only status, nothing to do here # 52 TN FRQ - per channel attribute, nothing to do here # 53 TOT opts = ["Off", "1 Min", "3 Min", "5 Min", "10 Min"] misc.append(RadioSetting("tot", "Timeout Timer", RadioSettingValueList(opts, opts[_settings.tot]))) # 54 TS MUT opts = ["Off", "On"] ctcss.append(RadioSetting("ts_mut", "Tone Search Mute", RadioSettingValueList(opts, opts[_settings .ts_mut]))) # 55 TS SPEED opts = ["Fast", "Slow"] ctcss.append(RadioSetting("ts_speed", "Tone Search Scanner Speed", RadioSettingValueList(opts, opts[_settings .ts_speed]))) # 56 VFO.SCN opts = ["+/- 1MHz", "+/- 2MHz", "+/-5MHz", "All"] scan.append(RadioSetting("vfo_scan", "VFO Scanner Width", RadioSettingValueList(opts, opts[_settings .vfo_scan]))) # 57 WX.ALT opts = ["Off", "On"] misc.append(RadioSetting("wx_alert", "Weather Alert Scan", RadioSettingValueList(opts, opts[ _settings.wx_alert]))) # 58 WX.VOL opts = ["Normal", "Maximum"] misc.append(RadioSetting("wx_vol_max", "Weather Alert Volume", RadioSettingValueList(opts, opts[ _settings.wx_vol_max]))) # 59 W/N DV - this is a per-channel attribute, nothing to do here return setmode def set_settings(self, uisettings): _settings = self._memobj.settings for element in uisettings: if not isinstance(element, RadioSetting): self.set_settings(element) continue if not element.changed(): continue try: name = element.get_name() value = element.value if element.has_apply_callback(): LOG.debug("Using apply callback") element.run_apply_callback() else: obj = getattr(_settings, name) setattr(_settings, name, value) LOG.debug("Setting %s: %s" % (name, value)) except Exception, e: LOG.debug(element.get_name()) raise def get_bank_model(self): return FT2900BankModel(self) @classmethod def match_model(cls, filedata, filename): return len(filedata) == cls._memsize @classmethod def get_prompts(cls): rp = chirp_common.RadioPrompts() rp.pre_download = _(dedent("""\ 1. Turn Radio off. 2. Connect data cable. 3. While holding "A/N LOW" button, turn radio on. 4. After clicking OK, press "SET MHz" to send image.""")) rp.pre_upload = _(dedent("""\ 1. Turn Radio off. 2. Connect data cable. 3. While holding "A/N LOW" button, turn radio on. 4. Press "MW D/MR" to receive image. 5. Make sure display says "-WAIT-" (see note below if not) 6. Click OK to dismiss this dialog and start transfer. Note: if you don't see "-WAIT-" at step 5, try cycling power and pressing and holding red "*L" button to unlock radio, then start back at step 1.""")) return rp # the FT2900E is the European version of the radio, almost identical # to the R (USA) version, except for the model number and ID Block. We # create and register a class for it, with only the needed overrides # NOTE: Disabled until detection is fixed # @directory.register class FT2900ERadio(FT2900Radio): """Yaesu FT-2900E""" MODEL = "FT-2900E/1900E" VARIANT = "E" IDBLOCK = "\x56\x43\x32\x33\x00\x02\x41\x02\x01\x01" chirp-daily-20170714/chirp/elib_intl.py0000644000016101777760000005204512475265017021022 0ustar jenkinsnogroup00000000000000# -*- coding: utf-8 -*- # # Copyright © 2007-2010 Dieter Verfaillie # # This file is part of elib.intl. # # elib.intl is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # elib.intl 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with elib.intl. If not, see . ''' The elib.intl module provides enhanced internationalization (I18N) services for your Python modules and applications. elib.intl wraps Python's :func:`gettext` functionality and adds the following on Microsoft Windows systems: - automatic detection of the current screen language (not necessarily the same as the installation language) provided by MUI packs, - makes sure internationalized C libraries which internally invoke gettext() or dcgettext() can properly locate their message catalogs. This fixes a known limitation in gettext's Windows support when using eg. gtk.builder or gtk.glade. See http://www.gnu.org/software/gettext/FAQ.html#windows_setenv for more information. The elib.intl module defines the following functions: ''' import os import sys import locale import gettext from logging import getLogger __all__ = ['install', 'install_module'] __version__ = '0.0.3' __docformat__ = 'restructuredtext' logger = getLogger('elib.intl') def _isofromlcid(lcid): ''' :param lcid: Microsoft Windows LCID :returns: the ISO 639-1 language code for a given lcid. If there is no ISO 639-1 language code assigned to the language specified by lcid, the ISO 639-2 language code is returned. If the language specified by lcid is unknown in the ISO 639-x database, None is returned. More information can be found on the following websites: - List of ISO 639-1 and ISO 639-2 language codes: http://www.loc.gov/standards/iso639-2/ - List of known lcid's: http://www.microsoft.com/globaldev/reference/lcid-all.mspx - List of known MUI packs: http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx ''' mapping = {1078: 'af', # frikaans - South Africa 1052: 'sq', # lbanian - Albania 1118: 'am', # mharic - Ethiopia 1025: 'ar', # rabic - Saudi Arabia 5121: 'ar', # rabic - Algeria 15361: 'ar', # rabic - Bahrain 3073: 'ar', # rabic - Egypt 2049: 'ar', # rabic - Iraq 11265: 'ar', # rabic - Jordan 13313: 'ar', # rabic - Kuwait 12289: 'ar', # rabic - Lebanon 4097: 'ar', # rabic - Libya 6145: 'ar', # rabic - Morocco 8193: 'ar', # rabic - Oman 16385: 'ar', # rabic - Qatar 10241: 'ar', # rabic - Syria 7169: 'ar', # rabic - Tunisia 14337: 'ar', # rabic - U.A.E. 9217: 'ar', # rabic - Yemen 1067: 'hy', # rmenian - Armenia 1101: 'as', # ssamese 2092: 'az', # zeri (Cyrillic) 1068: 'az', # zeri (Latin) 1069: 'eu', # asque 1059: 'be', # elarusian 1093: 'bn', # engali (India) 2117: 'bn', # engali (Bangladesh) 5146: 'bs', # osnian (Bosnia/Herzegovina) 1026: 'bg', # ulgarian 1109: 'my', # urmese 1027: 'ca', # atalan 1116: 'chr', # herokee - United States 2052: 'zh', # hinese - People's Republic of China 4100: 'zh', # hinese - Singapore 1028: 'zh', # hinese - Taiwan 3076: 'zh', # hinese - Hong Kong SAR 5124: 'zh', # hinese - Macao SAR 1050: 'hr', # roatian 4122: 'hr', # roatian (Bosnia/Herzegovina) 1029: 'cs', # zech 1030: 'da', # anish 1125: 'dv', # ivehi 1043: 'nl', # utch - Netherlands 2067: 'nl', # utch - Belgium 1126: 'bin', # do 1033: 'en', # nglish - United States 2057: 'en', # nglish - United Kingdom 3081: 'en', # nglish - Australia 10249: 'en', # nglish - Belize 4105: 'en', # nglish - Canada 9225: 'en', # nglish - Caribbean 15369: 'en', # nglish - Hong Kong SAR 16393: 'en', # nglish - India 14345: 'en', # nglish - Indonesia 6153: 'en', # nglish - Ireland 8201: 'en', # nglish - Jamaica 17417: 'en', # nglish - Malaysia 5129: 'en', # nglish - New Zealand 13321: 'en', # nglish - Philippines 18441: 'en', # nglish - Singapore 7177: 'en', # nglish - South Africa 11273: 'en', # nglish - Trinidad 12297: 'en', # nglish - Zimbabwe 1061: 'et', # stonian 1080: 'fo', # aroese 1065: None, # ODO: Farsi 1124: 'fil', # ilipino 1035: 'fi', # innish 1036: 'fr', # rench - France 2060: 'fr', # rench - Belgium 11276: 'fr', # rench - Cameroon 3084: 'fr', # rench - Canada 9228: 'fr', # rench - Democratic Rep. of Congo 12300: 'fr', # rench - Cote d'Ivoire 15372: 'fr', # rench - Haiti 5132: 'fr', # rench - Luxembourg 13324: 'fr', # rench - Mali 6156: 'fr', # rench - Monaco 14348: 'fr', # rench - Morocco 58380: 'fr', # rench - North Africa 8204: 'fr', # rench - Reunion 10252: 'fr', # rench - Senegal 4108: 'fr', # rench - Switzerland 7180: 'fr', # rench - West Indies 1122: 'fy', # risian - Netherlands 1127: None, # ODO: Fulfulde - Nigeria 1071: 'mk', # YRO Macedonian 2108: 'ga', # aelic (Ireland) 1084: 'gd', # aelic (Scotland) 1110: 'gl', # alician 1079: 'ka', # eorgian 1031: 'de', # erman - Germany 3079: 'de', # erman - Austria 5127: 'de', # erman - Liechtenstein 4103: 'de', # erman - Luxembourg 2055: 'de', # erman - Switzerland 1032: 'el', # reek 1140: 'gn', # uarani - Paraguay 1095: 'gu', # ujarati 1128: 'ha', # ausa - Nigeria 1141: 'haw', # awaiian - United States 1037: 'he', # ebrew 1081: 'hi', # indi 1038: 'hu', # ungarian 1129: None, # ODO: Ibibio - Nigeria 1039: 'is', # celandic 1136: 'ig', # gbo - Nigeria 1057: 'id', # ndonesian 1117: 'iu', # nuktitut 1040: 'it', # talian - Italy 2064: 'it', # talian - Switzerland 1041: 'ja', # apanese 1099: 'kn', # annada 1137: 'kr', # anuri - Nigeria 2144: 'ks', # ashmiri 1120: 'ks', # ashmiri (Arabic) 1087: 'kk', # azakh 1107: 'km', # hmer 1111: 'kok', # onkani 1042: 'ko', # orean 1088: 'ky', # yrgyz (Cyrillic) 1108: 'lo', # ao 1142: 'la', # atin 1062: 'lv', # atvian 1063: 'lt', # ithuanian 1086: 'ms', # alay - Malaysia 2110: 'ms', # alay - Brunei Darussalam 1100: 'ml', # alayalam 1082: 'mt', # altese 1112: 'mni', # anipuri 1153: 'mi', # aori - New Zealand 1102: 'mr', # arathi 1104: 'mn', # ongolian (Cyrillic) 2128: 'mn', # ongolian (Mongolian) 1121: 'ne', # epali 2145: 'ne', # epali - India 1044: 'no', # orwegian (Bokmᅢᆬl) 2068: 'no', # orwegian (Nynorsk) 1096: 'or', # riya 1138: 'om', # romo 1145: 'pap', # apiamentu 1123: 'ps', # ashto 1045: 'pl', # olish 1046: 'pt', # ortuguese - Brazil 2070: 'pt', # ortuguese - Portugal 1094: 'pa', # unjabi 2118: 'pa', # unjabi (Pakistan) 1131: 'qu', # uecha - Bolivia 2155: 'qu', # uecha - Ecuador 3179: 'qu', # uecha - Peru 1047: 'rm', # haeto-Romanic 1048: 'ro', # omanian 2072: 'ro', # omanian - Moldava 1049: 'ru', # ussian 2073: 'ru', # ussian - Moldava 1083: 'se', # ami (Lappish) 1103: 'sa', # anskrit 1132: 'nso', # epedi 3098: 'sr', # erbian (Cyrillic) 2074: 'sr', # erbian (Latin) 1113: 'sd', # indhi - India 2137: 'sd', # indhi - Pakistan 1115: 'si', # inhalese - Sri Lanka 1051: 'sk', # lovak 1060: 'sl', # lovenian 1143: 'so', # omali 1070: 'wen', # orbian 3082: 'es', # panish - Spain (Modern Sort) 1034: 'es', # panish - Spain (Traditional Sort) 11274: 'es', # panish - Argentina 16394: 'es', # panish - Bolivia 13322: 'es', # panish - Chile 9226: 'es', # panish - Colombia 5130: 'es', # panish - Costa Rica 7178: 'es', # panish - Dominican Republic 12298: 'es', # panish - Ecuador 17418: 'es', # panish - El Salvador 4106: 'es', # panish - Guatemala 18442: 'es', # panish - Honduras 58378: 'es', # panish - Latin America 2058: 'es', # panish - Mexico 19466: 'es', # panish - Nicaragua 6154: 'es', # panish - Panama 15370: 'es', # panish - Paraguay 10250: 'es', # panish - Peru 20490: 'es', # panish - Puerto Rico 21514: 'es', # panish - United States 14346: 'es', # panish - Uruguay 8202: 'es', # panish - Venezuela 1072: None, # ODO: Sutu 1089: 'sw', # wahili 1053: 'sv', # wedish 2077: 'sv', # wedish - Finland 1114: 'syr', # yriac 1064: 'tg', # ajik 1119: None, # ODO: Tamazight (Arabic) 2143: None, # ODO: Tamazight (Latin) 1097: 'ta', # amil 1092: 'tt', # atar 1098: 'te', # elugu 1054: 'th', # hai 2129: 'bo', # ibetan - Bhutan 1105: 'bo', # ibetan - People's Republic of China 2163: 'ti', # igrigna - Eritrea 1139: 'ti', # igrigna - Ethiopia 1073: 'ts', # songa 1074: 'tn', # swana 1055: 'tr', # urkish 1090: 'tk', # urkmen 1152: 'ug', # ighur - China 1058: 'uk', # krainian 1056: 'ur', # rdu 2080: 'ur', # rdu - India 2115: 'uz', # zbek (Cyrillic) 1091: 'uz', # zbek (Latin) 1075: 've', # enda 1066: 'vi', # ietnamese 1106: 'cy', # elsh 1076: 'xh', # hosa 1144: 'ii', # i 1085: 'yi', # iddish 1130: 'yo', # oruba 1077: 'zu'} # ulu return mapping[lcid] def _getscreenlanguage(): ''' :returns: the ISO 639-x language code for this session. If the LANGUAGE environment variable is set, it's value overrides the screen language detection. Otherwise the screen language is determined by the currently selected Microsoft Windows MUI language pack or the Microsoft Windows installation language. Works on Microsoft Windows 2000 and up. ''' if sys.platform == 'win32' or sys.platform == 'nt': # Start with nothing lang = None # Check the LANGUAGE environment variable lang = os.getenv('LANGUAGE') if lang is None: # Start with nothing lcid = None try: from ctypes import windll lcid = windll.kernel32.GetUserDefaultUILanguage() except: logger.debug('Failed to get current screen language ' 'with \'GetUserDefaultUILanguage\'') finally: if lcid is None: lang = 'C' else: lang = _isofromlcid(lcid) logger.debug('Windows screen language is \'%s\' ' '(lcid %s)' % (lang, lcid)) return lang def _putenv(name, value): ''' :param name: environment variable name :param value: environment variable value This function ensures that changes to an environment variable are applied to each copy of the environment variables used by a process. Starting from Python 2.4, os.environ changes only apply to the copy Python keeps (os.environ) and are no longer automatically applied to the other copies for the process. On Microsoft Windows, each process has multiple copies of the environment variables, one managed by the OS and one managed by the C library. We also need to take care of the fact that the C library used by Python is not necessarily the same as the C library used by pygtk and friends. This because the latest releases of pygtk and friends are built with mingw32 and are thus linked against msvcrt.dll. The official gtk+ binaries have always been built in this way. ''' if sys.platform == 'win32' or sys.platform == 'nt': from ctypes import windll from ctypes import cdll from ctypes.util import find_msvcrt # Update Python's copy of the environment variables os.environ[name] = value # Update the copy maintained by Windows (so SysInternals # Process Explorer sees it) try: result = windll.kernel32.SetEnvironmentVariableW(name, value) if result == 0: raise Warning except Exception: logger.debug('Failed to set environment variable \'%s\' ' '(\'kernel32.SetEnvironmentVariableW\')' % name) else: logger.debug('Set environment variable \'%s\' to \'%s\' ' '(\'kernel32.SetEnvironmentVariableW\')' % (name, value)) # Update the copy maintained by msvcrt (used by gtk+ runtime) try: result = cdll.msvcrt._putenv('%s=%s' % (name, value)) if result != 0: raise Warning except Exception: logger.debug('Failed to set environment variable \'%s\' ' '(\'msvcrt._putenv\')' % name) else: logger.debug('Set environment variable \'%s\' to \'%s\' ' '(\'msvcrt._putenv\')' % (name, value)) # Update the copy maintained by whatever c runtime is used by Python try: msvcrt = find_msvcrt() msvcrtname = str(msvcrt).split('.')[0] \ if '.' in msvcrt else str(msvcrt) result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value)) if result != 0: raise Warning except Exception: logger.debug('Failed to set environment variable \'%s\' ' '(\'%s._putenv\')' % (name, msvcrtname)) else: logger.debug('Set environment variable \'%s\' to \'%s\' ' '(\'%s._putenv\')' % (name, value, msvcrtname)) def _dugettext(domain, message): ''' :param domain: translation domain :param message: message to translate :returns: the translated message Unicode version of :func:`gettext.dgettext`. ''' try: t = gettext.translation(domain, gettext._localedirs.get(domain, None), codeset=gettext._localecodesets.get(domain)) except IOError: return message else: return t.ugettext(message) def _install(domain, localedir, asglobal=False): ''' :param domain: translation domain :param localedir: locale directory :param asglobal: if True, installs the function _() in Python’s builtin namespace. Default is False Private function doing all the work for the :func:`elib.intl.install` and :func:`elib.intl.install_module` functions. ''' # prep locale system if asglobal: locale.setlocale(locale.LC_ALL, '') # on windows systems, set the LANGUAGE environment variable if sys.platform == 'win32' or sys.platform == 'nt': _putenv('LANGUAGE', _getscreenlanguage()) # The locale module on Max OS X lacks bindtextdomain so we specifically # test on linux2 here. See commit 4ae8b26fd569382ab66a9e844daa0e01de409ceb if sys.platform == 'linux2': locale.bindtextdomain(domain, localedir) locale.bind_textdomain_codeset(domain, 'UTF-8') locale.textdomain(domain) # initialize Python's gettext interface gettext.bindtextdomain(domain, localedir) gettext.bind_textdomain_codeset(domain, 'UTF-8') if asglobal: gettext.textdomain(domain) # on windows systems, initialize libintl if sys.platform == 'win32' or sys.platform == 'nt': from ctypes import cdll libintl = cdll.intl libintl.bindtextdomain(domain, localedir) libintl.bind_textdomain_codeset(domain, 'UTF-8') if asglobal: libintl.textdomain(domain) del libintl def install(domain, localedir): ''' :param domain: translation domain :param localedir: locale directory Installs the function _() in Python’s builtin namespace, based on domain and localedir. Codeset is always UTF-8. As seen below, you usually mark the strings in your application that are candidates for translation, by wrapping them in a call to the _() function, like this: .. sourcecode:: python import elib.intl elib.intl.install('myapplication', '/path/to/usr/share/locale') print _('This string will be translated.') Note that this is only one way, albeit the most convenient way, to make the _() function available to your application. Because it affects the entire application globally, and specifically Python’s built-in namespace, localized modules should never install _(). Instead, you should use :func:`elib.intl.install_module` to make _() available to your module. ''' _install(domain, localedir, True) gettext.install(domain, localedir, unicode=True) def install_module(domain, localedir): ''' :param domain: translation domain :param localedir: locale directory :returns: an anonymous function object, based on domain and localedir. Codeset is always UTF-8. You may find this function usefull when writing localized modules. Use this code to make _() available to your module: .. sourcecode:: python import elib.intl _ = elib.intl.install_module('mymodule', '/path/to/usr/share/locale') print _('This string will be translated.') When writing packages, you can usually do this in the package's __init__.py file and import the _() function from the package namespace as needed. ''' _install(domain, localedir, False) return lambda message: _dugettext(domain, message) chirp-daily-20170714/chirp/bandplan_iaru_r3.py0000644000016101777760000001251412136152333022247 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from chirp import bandplan SHORTNAME = "iaru_r3" DESC = { "name": "IARU Region 3 (Asia Pacific)", "updated": "16 October 2009", "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r3_band_plan.pdf" } # Bands are broken up like this so that other plans can import bits. BANDS_2100M = ( bandplan.Band((135700, 137800), "137khz Band", mode="CW"), ) BANDS_160M = ( bandplan.Band((1800000, 2000000), "160 Meter Band"), bandplan.Band((1830000, 1840000), "Digimodes", mode="RTTY"), bandplan.Band((1840000, 2000000), "Phone"), ) BANDS_80M = ( bandplan.Band((3500000, 3900000), "80 Meter Band"), bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"), bandplan.Band((3535000, 3900000), "Phone"), bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"), ) BANDS_40M = ( bandplan.Band((7000000, 7300000), "40 Meter Band"), bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"), bandplan.Band((7025000, 7035000), "All narrow band modes, cw", mode="CW"), bandplan.Band((7035000, 7040000), "All narrow band modes, phone"), bandplan.Band((7040000, 7300000), "All modes, digimodes"), ) BANDS_30M = ( bandplan.Band((10100000, 10150000), "30 Meter Band"), bandplan.Band((10100000, 10130000), "CW", mode="CW"), bandplan.Band((10130000, 10150000), "All narrow band digimodes"), ) BANDS_20M = ( bandplan.Band((14000000, 14350000), "20 Meter Band"), bandplan.Band((14000000, 14070000), "CW", mode="CW"), bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"), bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"), bandplan.Band((14101000, 14350000), "All modes, digimodes"), ) BANDS_17M = ( bandplan.Band((18068000, 18168000), "17 Meter Band"), bandplan.Band((18068000, 18100000), "CW", mode="CW"), bandplan.Band((18100000, 18109000), "All narrow band modes, digimodes"), bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((18111000, 18168000), "All modes, digimodes"), ) BANDS_15M = ( bandplan.Band((21000000, 21450000), "15 Meter Band"), bandplan.Band((21000000, 21070000), "CW", mode="CW"), bandplan.Band((21070000, 21125000), "All narrow band modes, digimodes"), bandplan.Band((21125000, 21149000), "All narrow band modes, digimodes"), bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((21151000, 21450000), "All modes", mode="USB"), ) BANDS_12M = ( bandplan.Band((24890000, 24990000), "12 Meter Band"), bandplan.Band((24890000, 24920000), "CW", mode="CW"), bandplan.Band((24920000, 24929000), "All narrow band modes, digimodes"), bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"), ) BANDS_10M = ( bandplan.Band((28000000, 29700000), "10 Meter Band"), bandplan.Band((28000000, 28050000), "CW", mode="CW"), bandplan.Band((28050000, 28150000), "All narrow band modes, digimodes"), bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"), bandplan.Band((28190000, 28199000), "Beacons", mode="CW"), bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((28201000, 28300000), "Beacons", mode="CW"), bandplan.Band((28300000, 29300000), "Phone"), bandplan.Band((29300000, 29510000), "Satellite downlink"), bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"), bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"), ) BANDS_6M = ( bandplan.Band((50000000, 54000000), "6 Meter Band"), bandplan.Band((50000000, 50100000), "Beacons", mode="CW"), bandplan.Band((50100000, 50500000), "Phone and narrow band"), bandplan.Band((50500000, 54000000), "Wide band"), ) BANDS_2M = ( bandplan.Band((144000000, 148000000), "2 Meter Band"), bandplan.Band((144000000, 144035000), "Earth Moon Earth"), bandplan.Band((145800000, 146000000), "Satellite"), ) BANDS_70CM = ( bandplan.Band((430000000, 450000000), "70cm Band"), bandplan.Band((431900000, 432240000), "Earth Moon Earth"), bandplan.Band((435000000, 438000000), "Satellite"), ) BANDS_23CM = ( bandplan.Band((1240000000, 1300000000), "23cm Band"), bandplan.Band((1260000000, 1270000000), "Satellite"), bandplan.Band((1296000000, 1297000000), "Earth Moon Earth"), ) BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M BANDS = BANDS + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM chirp-daily-20170714/chirp/bitwise.py0000644000016101777760000006542512475265017020535 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: # # bit foo[8]; /* Eight single bit values */ # 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) */ # i8 foo; /* Signed 8-bit value */ # i16 foo; /* Signed 16-bit value */ # il16 foo; /* Signed 16-bit value (LE) */ # i24 foo; /* Signed 24-bit value */ # il24 foo; /* Signed 24-bit value (LE) */ # i32 foo; /* Signed 32-bit value */ # il32 foo; /* Signed 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 import logging from chirp import bitwise_grammar from chirp.memmap import MemoryMap LOG = logging.getLogger(__name__) 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): value = self._data[self._offset:self._offset + self._size] return self._get_value(value) 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), repr(str(self))[1:-1]) 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], bcdDataElement): val = 0 if isinstance(self.__items[0], bbcdDataElement): items = self.__items else: items = reversed(self.__items) for i in items: tens, ones = 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): if len(value) != len(self.__items): raise ValueError("String expects exactly %i characters" % len(self.__items)) 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 items(self): index = 0 for item in self.__items: yield (str(index), item) index += 1 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 __xor__(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 __rxor__(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 __ixor__(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 packed = struct.pack(self._endianess + "I", int(value) & 0xFFFFFFFF) self._data[self._offset] = packed[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 i8DataElement(u8DataElement): _size = 1 def _get_value(self, data): return struct.unpack("b", data)[0] def set_value(self, value): self._data[self._offset] = struct.pack("b", int(value)) class i16DataElement(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)) class il16DataElement(i16DataElement): _endianess = "<" class i24DataElement(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))[start:end] class il24DataElement(i24DataElement): _endianess = "<" class i32DataElement(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)) class il32DataElement(i32DataElement): _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)) def set_value(self, value): self._data[self._offset] = int("%02i" % value, 16) def _get_value(self, data): a = (ord(data) & 0xF0) >> 4 b = ord(data) & 0x0F return (a, b) class lbcdDataElement(bcdDataElement): _size = 1 class bbcdDataElement(bcdDataElement): _size = 1 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) >> (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 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 "_structDataElement__init" not in self.__dict__: 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() 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 def __iter__(self): for item in self._generators.values(): yield item def items(self): for key in self._keys: yield key, self._generators[key] class Processor: _types = { "u8": u8DataElement, "u16": u16DataElement, "ul16": ul16DataElement, "u24": u24DataElement, "ul24": ul24DataElement, "u32": u32DataElement, "ul32": ul32DataElement, "i8": i8DataElement, "i16": i16DataElement, "il16": il16DataElement, "i24": i24DataElement, "il24": il24DataElement, "i32": i32DataElement, "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: LOG.warn("WARNING: %i trailing bits unaccounted for in %s" % (bitsleft, bitfield)) return bytes def do_bitarray(self, i, count): if count % 8 != 0: raise ValueError("bit array must be divisible by 8.") class bitDE(bitDataElement): _nbits = 1 _shift = 8 - i % 8 return bitDE(self._data, self._offset) 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): if dtype == "bit": gen = self.do_bitarray(i, count) self._offset += int((i+1) % 8 == 0) else: gen = self._types[dtype](self._data, self._offset) self._offset += (gen.size() / 8) res.append(gen) 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] value = directive[0][1][0][1] if name == "seekto": self._offset = int(value, 0) elif name == "seek": self._offset += int(value, 0) elif name == "printoffset": LOG.debug("%s: %i (0x%08X)" % (value[1:-1], self._offset, self._offset)) def parse_block(self, lang): for t, d in lang: 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) 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-daily-20170714/chirp/logger.py0000644000016101777760000001471012476257220020333 0ustar jenkinsnogroup00000000000000# Copyright 2015 Zachary T Welch # # 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 . r""" The chirp.logger module provides the core logging facilties for CHIRP. It sets up the console and (optionally) a log file. For early debugging, it checks the CHIRP_DEBUG, CHIRP_LOG, and CHIRP_LOG_LEVEL environment variables. """ import os import sys import logging import argparse import platform from chirp import CHIRP_VERSION def version_string(): args = (CHIRP_VERSION, platform.get_platform().os_version_string(), sys.version.split()[0]) return "CHIRP %s on %s (Python %s)" % args class VersionAction(argparse.Action): def __call__(self, parser, namespace, value, option_string=None): print version_string() sys.exit(1) def add_version_argument(parser): parser.add_argument("--version", action=VersionAction, nargs=0, help="Print version and exit") #: Map human-readable logging levels to their internal values. log_level_names = {"critical": logging.CRITICAL, "error": logging.ERROR, "warn": logging.WARNING, "info": logging.INFO, "debug": logging.DEBUG, } class Logger(object): log_format = '[%(asctime)s] %(name)s - %(levelname)s: %(message)s' def __init__(self): # create root logger self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) self.LOG = logging.getLogger(__name__) # Set CHIRP_DEBUG in environment for early console debugging. # It can be a number or a name; otherwise, level is set to 'debug' # in order to maintain backward compatibility. CHIRP_DEBUG = os.getenv("CHIRP_DEBUG") self.early_level = logging.WARNING if CHIRP_DEBUG: try: self.early_level = int(CHIRP_DEBUG) except ValueError: try: self.early_level = log_level_names[CHIRP_DEBUG] except KeyError: self.early_level = logging.DEBUG # If we're on Win32 or MacOS, we don't use the console; instead, # we create 'debug.log', redirect all output there, and set the # console logging handler level to DEBUG. To test this on Linux, # set CHIRP_DEBUG_LOG in the environment. console_stream = None console_format = '%(levelname)s: %(message)s' if hasattr(sys, "frozen") or not os.isatty(0) \ or os.getenv("CHIRP_DEBUG_LOG"): p = platform.get_platform() log = file(p.config_file("debug.log"), "w", 0) sys.stdout = log sys.stderr = log console_stream = log console_format = self.log_format self.early_level = logging.DEBUG self.console = logging.StreamHandler(console_stream) self.console_level = self.early_level self.console.setLevel(self.early_level) self.console.setFormatter(logging.Formatter(console_format)) self.logger.addHandler(self.console) # Set CHIRP_LOG in environment to the name of log file. logname = os.getenv("CHIRP_LOG") self.logfile = None if logname is not None: self.create_log_file(logname) level = os.getenv("CHIRP_LOG_LEVEL") if level is not None: self.set_log_verbosity(level) else: self.set_log_level(logging.DEBUG) if self.early_level <= logging.DEBUG: self.LOG.debug(version_string()) def create_log_file(self, name): if self.logfile is None: self.logname = name # always truncate the log file with file(name, "w") as fh: pass self.logfile = logging.FileHandler(name) format_str = self.log_format self.logfile.setFormatter(logging.Formatter(format_str)) self.logger.addHandler(self.logfile) else: self.logger.error("already logging to " + self.logname) def set_verbosity(self, level): self.LOG.debug("verbosity=%d", level) if level > logging.CRITICAL: level = logging.CRITICAL self.console_level = level self.console.setLevel(level) def set_log_level(self, level): self.LOG.debug("log level=%d", level) if level > logging.CRITICAL: level = logging.CRITICAL self.logfile.setLevel(level) def set_log_level_by_name(self, level): self.set_log_level(log_level_names[level]) instance = None Logger.instance = Logger() def is_visible(level): """Returns True if a message at level will be shown on the console""" return level >= Logger.instance.console_level def add_arguments(parser): parser.add_argument("-q", "--quiet", action="count", default=0, help="Decrease verbosity") parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase verbosity") parser.add_argument("--log", dest="log_file", action="store", default=0, help="Log messages to a file") parser.add_argument("--log-level", action="store", default="debug", help="Log file verbosity (critical, error, warn, " + "info, debug). Defaults to 'debug'.") def handle_options(options): logger = Logger.instance if options.verbose or options.quiet: logger.set_verbosity(30 + 10 * (options.quiet - options.verbose)) if options.log_file: logger.create_log_file(options.log_file) try: level = int(options.log_level) logger.set_log_level(level) except ValueError: logger.set_log_level_by_name(options.log_level) if logger.early_level > logging.DEBUG: logger.LOG.debug(version_string()) chirp-daily-20170714/chirp/errors.py0000644000016101777760000000245212474272620020367 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 . 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-daily-20170714/chirp/bandplan_au.py0000644000016101777760000001115012475265017021315 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from chirp import bandplan, bandplan_iaru_r3 SHORTNAME = "australia" DESC = { "name": "Australian Amateur Band Plan", "updated": "April 2010", "url": "http://www.wia.org.au/members/bandplans/data" "/documents/Australian%20Band%20Plans%20100404.pdf", } BANDS_10M = ( # bandplan.Band((28000000, 29700000), "10 Meter Band"), bandplan.Band((29520000, 29680000), "FM Simplex and Repeaters", mode="FM", step_khz=20), bandplan.Band((29620000, 29680000), "FM Repeaters", input_offset=-100000), ) BANDS_6M = ( # bandplan.Band((50000000, 54000000), "6 Meter Band"), bandplan.Band((52525000, 53975000), "FM Simplex and Repeaters", mode="FM", step_khz=25), bandplan.Band((53550000, 53975000), "FM Repeaters", input_offset=-1000000), ) BANDS_2M = ( bandplan.Band((144000000, 148000000), "2 Meter Band", tones=(91.5, 123.0, 141.3, 146.2, 85.4)), bandplan.Band((144400000, 144600000), "Beacons", step_khz=1), bandplan.Band((146025000, 147975000), "FM Simplex and Repeaters", mode="FM", step_khz=25), bandplan.Band((146625000, 147000000), "FM Repeaters Group A", input_offset=-600000), bandplan.Band((147025000, 147375000), "FM Repeaters Group B", input_offset=600000), ) BANDS_70CM = ( bandplan.Band((420000000, 450000000), "70cm Band", tones=(91.5, 123.0, 141.3, 146.2, 85.4)), bandplan.Band((432400000, 432600000), "Beacons", step_khz=1), bandplan.Band((438025000, 439975000), "FM Simplex and Repeaters", mode="FM", step_khz=25), bandplan.Band((438025000, 438725000), "FM Repeaters Group A", input_offset=-5000000), bandplan.Band((439275000, 439975000), "FM Repeaters Group B", input_offset=-5000000), ) BANDS_23CM = ( # bandplan.Band((1240000000, 1300000000), "23cm Band"), bandplan.Band((1273025000, 1273975000), "FM Repeaters", mode="FM", step_khz=25, input_offset=20000000), bandplan.Band((1296400000, 1296600000), "Beacons", step_khz=1), bandplan.Band((1297025000, 1300400000), "General FM Simplex Data", mode="FM", step_khz=25), ) BANDS_13CM = ( bandplan.Band((2300000000, 2450000000), "13cm Band"), bandplan.Band((2403400000, 2403600000), "Beacons", step_khz=1), bandplan.Band((2425000000, 2428000000), "FM Simplex", mode="FM", step_khz=25), bandplan.Band((2428025000, 2429000000), "FM Duplex (Voice)", mode="FM", step_khz=25, input_offset=20000000), bandplan.Band((2429000000, 2429975000), "FM Duplex (Data)", mode="FM", step_khz=100, input_offset=20000000), ) BANDS_9CM = ( bandplan.Band((3300000000, 3600000000), "9cm Band"), bandplan.Band((3320000000, 3340000000), "WB Channel 2: Voice/Data", step_khz=100), bandplan.Band((3400400000, 3400600000), "Beacons", step_khz=1), bandplan.Band((3402000000, 3403000000), "FM Simplex (Voice)", mode="FM", step_khz=100), bandplan.Band((3403000000, 3405000000), "FM Simplex (Data)", mode="FM", step_khz=100), ) BANDS_6CM = ( bandplan.Band((5650000000, 5850000000), "6cm Band"), bandplan.Band((5760400000, 5760600000), "Beacons", step_khz=1), bandplan.Band((5700000000, 5720000000), "WB Channel 2: Data", step_khz=100, input_offset=70000000), bandplan.Band((5720000000, 5740000000), "WB Channel 3: Voice", step_khz=100, input_offset=70000000), bandplan.Band((5762000000, 5763000000), "FM Simplex (Voice)", mode="FM", step_khz=100), bandplan.Band((5763000000, 5765000000), "FM Simplex (Data)", mode="FM", step_khz=100), ) BANDS = bandplan_iaru_r3.BANDS_20M + bandplan_iaru_r3.BANDS_17M BANDS += bandplan_iaru_r3.BANDS_15M + bandplan_iaru_r3.BANDS_12M BANDS += bandplan_iaru_r3.BANDS_10M + bandplan_iaru_r3.BANDS_6M BANDS += BANDS_10M + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM BANDS += BANDS_13CM + BANDS_9CM + BANDS_6CM chirp-daily-20170714/chirp/platform.py0000644000016101777760000003576013046277310020704 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 import re import logging from subprocess import Popen LOG = logging.getLogger(__name__) def win32_comports_bruteforce(): import win32file import win32con ports = [] for i in range(1, 257): portname = "\\\\.\\COM%i" % i try: mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE port = \ win32file.CreateFile(portname, mode, win32con.FILE_SHARE_READ, None, win32con.OPEN_EXISTING, 0, None) if portname.startswith("\\"): portname = portname[4:] ports.append((portname, "Unknown", "Serial")) win32file.CloseHandle(port) port = None except Exception, e: pass return ports try: from serial.tools.list_ports import comports except: comports = win32_comports_bruteforce def _find_me(): return sys.modules["chirp.platform"].__file__ def natural_sorted(l): def convert(text): return int(text) if text.isdigit() else text.lower() def natural_key(key): return [convert(c) for c in re.split('([0-9]+)', key)] return sorted(l, key=natural_key) 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 find_resource(self, filename): """Searches for files installed to a share/ prefix.""" execpath = self.executable_path() share_candidates = [ os.path.join(execpath, "share"), os.path.join(sys.prefix, "share"), "/usr/local/share", "/usr/share", ] pkgshare_candidates = [os.path.join(i, "chirp") for i in share_candidates] search_paths = [execpath] + pkgshare_candidates + share_candidates for path in search_paths: candidate = os.path.join(path, filename) if os.path.exists(candidate): return candidate return "" 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 "DISPLAY" not in os.environ: LOG.info("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() LOG.debug("calling `%s %s'" % (editor, path)) os.execlp(editor, editor, path) else: sys.exit(0) else: os.waitpid(pid1, 0) LOG.debug("Exec child exited") def open_html_file(self, path): os.system("firefox '%s'" % path) def list_serial_ports(self): ports = ["/dev/ttyS*", "/dev/ttyUSB*", "/dev/ttyAMA*", "/dev/ttyACM*", "/dev/cu.*", "/dev/cuaU*", "/dev/cua0*", "/dev/term/*", "/dev/tty.KeySerial*"] return natural_sorted(sum([glob.glob(x) for x in ports], [])) 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): try: ports = list(comports()) except Exception, e: if comports != win32_comports_bruteforce: LOG.error("Failed to detect win32 serial ports: %s" % e) ports = win32_comports_bruteforce() return natural_sorted([port for port, name, url in 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: LOG.error("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: LOG.error("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: LOG.error("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-daily-20170714/chirp/bandplan_na.py0000644000016101777760000003123313046277310021304 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 bandplan, bandplan_iaru_r2 SHORTNAME = "north_america" DESC = { "name": "North American Band Plan", "url": "http://www.arrl.org/band-plan" } BANDS_160M = ( bandplan.Band((1800000, 2000000), "160 Meter Band", mode="CW"), bandplan.Band((1800000, 1810000), "Digital Modes"), bandplan.Band((1843000, 2000000), "SSB, SSTV and other wideband modes"), bandplan.Band((1995000, 2000000), "Experimental"), bandplan.Band((1999000, 2000000), "Beacons"), ) BANDS_80M = ( bandplan.Band((3500000, 4000000), "80 Meter Band"), bandplan.Band((3570000, 3600000), "RTTY/Data", mode="RTTY"), bandplan.Band((3790000, 3800000), "DX window"), ) BANDS_40M = ( bandplan.Band((7000000, 7300000), "40 Meter Band"), bandplan.Band((7080000, 7125000), "RTTY/Data", mode="RTTY"), ) BANDS_30M = ( bandplan.Band((10100000, 10150000), "30 Meter Band"), bandplan.Band((10130000, 10140000), "RTTY", mode="RTTY"), bandplan.Band((10140000, 10150000), "Packet"), ) BANDS_20M = ( bandplan.Band((14000000, 14350000), "20 Meter Band"), bandplan.Band((14070000, 14095000), "RTTY", mode="RTTY"), bandplan.Band((14095000, 14099500), "Packet"), bandplan.Band((14100500, 14112000), "Packet"), ) BANDS_17M = ( bandplan.Band((18068000, 18168000), "17 Meter Band"), bandplan.Band((18100000, 18105000), "RTTY", mode="RTTY"), bandplan.Band((18105000, 18110000), "Packet"), ) BANDS_15M = ( bandplan.Band((21000000, 21450000), "15 Meter Band"), bandplan.Band((21070000, 21110000), "RTTY/Data", mode="RTTY"), ) BANDS_12M = ( bandplan.Band((24890000, 24990000), "12 Meter Band"), bandplan.Band((24920000, 24925000), "RTTY", mode="RTTY"), bandplan.Band((24925000, 24930000), "Packet"), ) BANDS_10M = ( bandplan.Band((28000000, 29700000), "10 Meter Band"), bandplan.Band((28000000, 28070000), "CW", mode="CW"), bandplan.Band((28070000, 28150000), "RTTY", mode="RTTY"), bandplan.Band((28150000, 28190000), "CW", mode="CW"), bandplan.Band((28201000, 28300000), "Beacons", mode="CW"), bandplan.Band((28300000, 29300000), "Phone"), bandplan.Band((29000000, 29200000), "AM", mode="AM"), bandplan.Band((29300000, 29510000), "Satellite Downlinks"), bandplan.Band((29520000, 29590000), "Repeater Inputs", step_khz=10, mode="FM"), bandplan.Band((29610000, 29700000), "Repeater Outputs", step_khz=10, mode="FM", input_offset=-890000), ) BANDS_6M = ( bandplan.Band((50000000, 54000000), "6 Meter Band"), bandplan.Band((50000000, 50100000), "CW, beacons", mode="CW"), bandplan.Band((50060000, 50080000), "beacon subband"), bandplan.Band((50100000, 50300000), "SSB, CW", mode="USB"), bandplan.Band((50100000, 50125000), "DX window", mode="USB"), bandplan.Band((50300000, 50600000), "All modes"), bandplan.Band((50600000, 50800000), "Nonvoice communications"), bandplan.Band((50800000, 51000000), "Radio remote control", step_khz=20), bandplan.Band((51000000, 51100000), "Pacific DX window"), bandplan.Band((51120000, 51180000), "Digital repeater inputs", step_khz=10), bandplan.Band((51500000, 51600000), "Simplex"), bandplan.Band((51620000, 51980000), "Repeater outputs A", input_offset=-500000), bandplan.Band((51620000, 51680000), "Digital repeater outputs", input_offset=-500000), bandplan.Band((52020000, 52040000), "FM simplex", mode="FM"), bandplan.Band((52500000, 52980000), "Repeater outputs B", input_offset=-500000, step_khz=20, mode="FM"), bandplan.Band((53000000, 53100000), "FM simplex", mode="FM"), bandplan.Band((53100000, 53400000), "Radio remote control", step_khz=100), bandplan.Band((53500000, 53980000), "Repeater outputs C", input_offset=-500000), bandplan.Band((53500000, 53800000), "Radio remote control", step_khz=100), bandplan.Band((53520000, 53900000), "Simplex"), ) BANDS_2M = ( bandplan.Band((144000000, 148000000), "2 Meter Band"), bandplan.Band((144000000, 144050000), "EME (CW)", mode="CW"), bandplan.Band((144050000, 144100000), "General CW and weak signals", mode="CW"), bandplan.Band((144100000, 144200000), "EME and weak-signal SSB", mode="USB"), bandplan.Band((144200000, 144275000), "General SSB operation", mode="USB"), bandplan.Band((144275000, 144300000), "Propagation beacons", mode="CW"), bandplan.Band((144300000, 144500000), "OSCAR subband"), bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="FM"), bandplan.Band((144900000, 145100000), "Weak signal and FM simplex", mode="FM", step_khz=10), bandplan.Band((145100000, 145200000), "Linear translator outputs", input_offset=-600000), bandplan.Band((145200000, 145500000), "FM repeater outputs", input_offset=-600000, mode="FM",), bandplan.Band((145500000, 145800000), "Misc and experimental modes"), bandplan.Band((145800000, 146000000), "OSCAR subband"), bandplan.Band((146400000, 146580000), "Simplex"), bandplan.Band((146610000, 146970000), "Repeater outputs", input_offset=-600000), bandplan.Band((147000000, 147390000), "Repeater outputs", input_offset=600000), bandplan.Band((147420000, 147570000), "Simplex"), ) BANDS_1_25M = ( bandplan.Band((222000000, 225000000), "1.25 Meters"), bandplan.Band((222000000, 222150000), "Weak-signal modes"), bandplan.Band((222000000, 222025000), "EME"), bandplan.Band((222050000, 222060000), "Propagation beacons"), bandplan.Band((222100000, 222150000), "Weak-signal CW & SSB"), bandplan.Band((222150000, 222250000), "Local coordinator's option"), bandplan.Band((223400000, 223520000), "FM simplex", mode="FM"), bandplan.Band((223520000, 223640000), "Digital, packet"), bandplan.Band((223640000, 223700000), "Links, control"), bandplan.Band((223710000, 223850000), "Local coordinator's option"), bandplan.Band((223850000, 224980000), "Repeater outputs only", mode="FM", input_offset=-1600000), ) BANDS_70CM = ( bandplan.Band((420000000, 450000000), "70cm Band"), bandplan.Band((420000000, 426000000), "ATV repeater or simplex"), bandplan.Band((426000000, 432000000), "ATV simplex"), bandplan.Band((432000000, 432070000), "EME (Earth-Moon-Earth)"), bandplan.Band((432070000, 432100000), "Weak-signal CW", mode="CW"), bandplan.Band((432100000, 432300000), "Mixed-mode and weak-signal work"), bandplan.Band((432300000, 432400000), "Propagation beacons"), bandplan.Band((432400000, 433000000), "Mixed-mode and weak-signal work"), bandplan.Band((433000000, 435000000), "Auxiliary/repeater links"), bandplan.Band((435000000, 438000000), "Satellite only (internationally)"), bandplan.Band((438000000, 444000000), "ATV repeater input/repeater links", input_offset=5000000), bandplan.Band((442000000, 445000000), "Repeater input/output (local option)", input_offset=5000000), bandplan.Band((445000000, 447000000), "Shared by aux and control links, " "repeaters, simplex (local option)"), bandplan.Band((447000000, 450000000), "Repeater inputs and outputs " "(local option)", input_offset=-5000000), ) BANDS_33CM = ( bandplan.Band((902000000, 928000000), "33 Centimeter Band"), bandplan.Band((902075000, 902100000), "CW/SSB, Weak signal"), bandplan.Band((902100000, 902125000), "CW/SSB, Weak signal"), bandplan.Band((903000000, 903100000), "CW/SSB, Beacons and weak signal"), bandplan.Band((903100000, 903400000), "CW/SSB, Weak signal"), bandplan.Band((903400000, 909000000), "Mixed modes, Mixed operations " "including control links"), bandplan.Band((909000000, 915000000), "Analog/digital Broadband multimedia " "including ATV, DATV and SS"), bandplan.Band((915000000, 921000000), "Analog/digital Broadband multimedia " "including ATV, DATV and SS"), bandplan.Band((921000000, 927000000), "Analog/digital Broadband multimedia " "including ATV, DATV and SS"), bandplan.Band((927000000, 927075000), "FM / other including DV or CW/SSB", input_offset=-25000000, step_khz=12.5), bandplan.Band((927075000, 927125000), "FM / other including DV. Simplex"), bandplan.Band((927125000, 928000000), "FM / other including DV", input_offset=-25000000, step_khz=12.5), ) BANDS_23CM = ( bandplan.Band((1240000000, 1300000000), "23 Centimeter Band"), bandplan.Band((1240000000, 1246000000), "ATV Channel #1"), bandplan.Band((1246000000, 1248000000), "Point-to-point links paired " "with 1258.000-1260.000", mode="FM"), bandplan.Band((1248000000, 1252000000), "Digital"), bandplan.Band((1252000000, 1258000000), "ATV Channel #2"), bandplan.Band((1258000000, 1260000000), "Point-to-point links paired with 1246.000-1248.000", mode="FM"), bandplan.Band((1240000000, 1260000000), "Regional option, FM ATV"), bandplan.Band((1260000000, 1270000000), "Satellite uplinks, Experimental, " "Simplex ATV"), bandplan.Band((1270000000, 1276000000), "FM, digital Repeater inputs " "(Regional option)", step_khz=25), bandplan.Band((1276000000, 1282000000), "ATV Channel #3"), bandplan.Band((1282000000, 1288000000), "FM, digital repeater outputs", step_khz=25, input_offset=-12000000), bandplan.Band((1288000000, 1294000000), "Various Broadband Experimental, " "Simplex ATV"), bandplan.Band((1290000000, 1294000000), "FM, digital Repeater outputs " "(Regional option)", step_khz=25, input_offset=-20000000), bandplan.Band((1294000000, 1295000000), "FM simplex", mode="FM"), bandplan.Band((1295000000, 1297000000), "Narrow Band Segment"), bandplan.Band((1295000000, 1295800000), "Narrow Band Image, Experimental"), bandplan.Band((1295800000, 1296080000), "CW, SSB, digital EME"), bandplan.Band((1296080000, 1296200000), "CW, SSB Weak Signal"), bandplan.Band((1296200000, 1296400000), "CW, digital Beacons"), bandplan.Band((1296400000, 1297000000), "General Narrow Band"), bandplan.Band((1297000000, 1300000000), "Digital"), ) BANDS_13CM = ( bandplan.Band((2300000000, 2450000000), "13 Centimeter Band"), bandplan.Band((2300000000, 2303000000), "Analog & Digital 0.05-1.0 MHz, " "including full duplex; paired with 2390-2393"), bandplan.Band((2303000000, 2303750000), "Analog & Digital <50kHz; " "paired with 2393 - 2393.750"), bandplan.Band((2303750000, 2304000000), "SSB, CW, digital weak-signal"), bandplan.Band((2304000000, 2304100000), "Weak Signal EME Band, <3kHz"), bandplan.Band((2304100000, 2304300000), "SSB, CW, digital weak-signal, <3kHz"), bandplan.Band((2304300000, 2304400000), "Beacons, <3kHz"), bandplan.Band((2304400000, 2304750000), "SSB, CW, digital weak-signal and " "NBFM, <6kHz"), bandplan.Band((2304750000, 2305000000), "Analog & Digital; paired with " "2394.750-2395, <50kHz"), bandplan.Band((2305000000, 2310000000), "Analog & Digital, paired with " "2395-2400, 0.05 - 1.0 MHz"), bandplan.Band((2310000000, 2390000000), "NON-AMATEUR"), bandplan.Band((2390000000, 2393000000), "Analog & Digital, including full " "duplex; paired with 2300-2303, 0.05 - 1.0 MHz"), bandplan.Band((2393000000, 2393750000), "Analog & Digital; paired with " "2303-2303.750, < 50 kHz"), bandplan.Band((2393750000, 2394750000), "Experimental"), bandplan.Band((2394750000, 2395000000), "Analog & Digital; paired with " "2304.750-2305, < 50 kHz"), bandplan.Band((2395000000, 2400000000), "Analog & Digital, including full " "duplex; paired with 2305-2310, 0.05-1.0 MHz"), bandplan.Band((2400000000, 2410000000), "Amateur Satellite Communications, " "<6kHz"), bandplan.Band((2410000000, 2450000000), "Broadband Modes, 22MHz max."), ) BANDS = bandplan_iaru_r2.BANDS BANDS += BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M BANDS += BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M + BANDS_6M BANDS += BANDS_2M + BANDS_1_25M + BANDS_70CM + BANDS_33CM + BANDS_23CM BANDS += BANDS_13CM chirp-daily-20170714/chirp/detect.py0000644000016101777760000000574212726733400020326 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 import logging from chirp import errors, directory from chirp.drivers import ic9x_ll, icf, kenwood_live, icomciv LOG = logging.getLogger(__name__) 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.baudrate = 9600 md = icf.get_model_data(ser) return _icom_model_data_to_rclass(md) except errors.RadioError, e: LOG.error(e) # ICOM IC-91/92 Live-mode radios @ 4800/38400 baud ser.baudrate = 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.baudrate = 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() LOG.info("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-daily-20170714/chirp/bitwise_grammar.py0000644000016101777760000000544112474272620022230 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, parse as pypeg_parse TYPES = ["bit", "u8", "u16", "ul16", "u24", "ul24", "u32", "ul32", "i8", "i16", "il16", "i24", "il24", "i32", "il32", "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): lines = data.split("\n") for index, line in enumerate(lines): if '//' in line: lines[index] = line[:line.index('//')] class FakeFileInput: """Simulate line-by-line file reading from @data""" line = -1 def isfirstline(self): return self.line == 0 def filename(self): return "input" def lineno(self): return self.line def __iter__(self): return self def next(self): self.line += 1 try: # Note, FileInput objects keep the newlines return lines[self.line] + "\n" except IndexError: raise StopIteration return pypeg_parse(_language, FakeFileInput()) chirp-daily-20170714/chirp/directory.py0000644000016101777760000001064212475535623021065 0ustar jenkinsnogroup00000000000000# 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 import logging from chirp.drivers import icf, rfinder from chirp import chirp_common, util, radioreference, errors LOG = logging.getLogger(__name__) 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: LOG.info("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: LOG.warn("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 LOG.info("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 driver in DRV_TO_RADIO: 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 rclass in RADIO_TO_DRV: return RADIO_TO_DRV[rclass] elif rclass.__bases__[0] in RADIO_TO_DRV: 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: LOG.error("Unsupported model data: %s" % 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) LOG.info("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-daily-20170714/chirp/dmrmarc.py0000644000016101777760000001123313036626111020466 0ustar jenkinsnogroup00000000000000# Copyright 2016 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 json import logging import tempfile import urllib from chirp import chirp_common, errors from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueList LOG = logging.getLogger(__name__) def list_filter(haystack, attr, needles): if not needles or not needles[0]: return haystack return [x for x in haystack if x[attr] in needles] class DMRMARCRadio(chirp_common.NetworkSourceRadio): """DMR-MARC data source""" VENDOR = "DMR-MARC" MODEL = "Repeater database" URL = "http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?" \ "table=repeaters&format=json" def __init__(self, *args, **kwargs): chirp_common.NetworkSourceRadio.__init__(self, *args, **kwargs) self._repeaters = None def set_params(self, city, state, country): """Set the parameters to be used for a query""" self._city = city and [x.strip() for x in city.split(",")] or [''] self._state = state and [x.strip() for x in state.split(",")] or [''] self._country = country and [x.strip() for x in country.split(",")] \ or [''] def do_fetch(self): fn = tempfile.mktemp(".json") filename, headers = urllib.urlretrieve(self.URL, fn) with open(fn, 'r') as f: try: self._repeaters = json.load(f)['repeaters'] except AttributeError: raise errors.RadioError( "Unexpected response from %s" % self.URL) except ValueError as e: raise errors.RadioError( "Invalid JSON from %s. %s" % (self.URL, str(e))) self._repeaters = list_filter(self._repeaters, "city", self._city) self._repeaters = list_filter(self._repeaters, "state", self._state) self._repeaters = list_filter(self._repeaters, "country", self._country) def get_features(self): if not self._repeaters: self.do_fetch() rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, len(self._repeaters)-1) rf.has_bank = False rf.has_comment = True rf.has_ctone = False rf.valid_tmodes = [""] return rf def get_raw_memory(self, number): return repr(self._repeaters[number]) def get_memory(self, number): if not self._repeaters: self.do_fetch() repeater = self._repeaters[number] mem = chirp_common.Memory() mem.number = number mem.name = repeater.get('city') mem.freq = chirp_common.parse_freq(repeater.get('frequency')) offset = chirp_common.parse_freq(repeater.get('offset', '0')) if offset > 0: mem.duplex = "+" elif offset < 0: mem.duplex = "-" else: mem.duplex = "" mem.offset = abs(offset) mem.mode = 'DMR' mem.comment = repeater.get('map_info') mem.extra = RadioSettingGroup("Extra", "extra") rs = RadioSetting( "color_code", "Color Code", RadioSettingValueList( range(16), int(repeater.get('color_code', 0)))) mem.extra.append(rs) return mem def main(): import argparse from pprint import PrettyPrinter parser = argparse.ArgumentParser(description="Fetch DMR-MARC repeater " "database and filter by city, state, and/or country. Multiple items " "combined with a , will be filtered with logical OR.") parser.add_argument("-c", "--city", help="Comma-separated list of cities to include in output.") parser.add_argument("-s", "--state", help="Comma-separated list of states to include in output.") parser.add_argument("--country", help="Comma-separated list of countries to include in output.") args = parser.parse_args() dmrmarc = DMRMARCRadio(None) dmrmarc.set_params(**vars(args)) dmrmarc.do_fetch() pp = PrettyPrinter(indent=2) pp.pprint(dmrmarc._repeaters) if __name__ == "__main__": main() chirp-daily-20170714/chirp/chirp_common.py0000644000016101777760000013276213036626111021531 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 math from chirp import errors, memmap SEPCHAR = "," # 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, ] TONES_EXTRA = [56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 62.5, 63.0, 64.0] OLD_TONES = list(TONES) [OLD_TONES.remove(x) for x in [159.8, 165.5, 171.3, 177.3, 183.5, 189.9, 196.6, 199.5, 206.5, 229.1, 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, ] # 512 Possible DTCS Codes ALL_DTCS_CODES = [] for a in range(0, 8): for b in range(0, 8): for c in range(0, 8): ALL_DTCS_CODES.append((a * 100) + (b * 10) + c) CROSS_MODES = [ "Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone", "->Tone", "DTCS->DTCS", "Tone->" ] MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY", "DIG", "PKT", "NCW", "NCWR", "CWR", "P25", "Auto", "RTTYR", "FSK", "FSKR", "DMR"] 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)]) # http://aprs.org/aprs11/SSIDs.txt APRS_SSID = ( "0 Your primary station usually fixed and message capable", "1 generic additional station, digi, mobile, wx, etc", "2 generic additional station, digi, mobile, wx, etc", "3 generic additional station, digi, mobile, wx, etc", "4 generic additional station, digi, mobile, wx, etc", "5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)", "6 Special activity, Satellite ops, camping or 6 meters, etc", "7 walkie talkies, HT's or other human portable", "8 boats, sailboats, RV's or second main mobile", "9 Primary Mobile (usually message capable)", "10 internet, Igates, echolink, winlink, AVRS, APRN, etc", "11 balloons, aircraft, spacecraft, etc", "12 APRStt, DTMF, RFID, devices, one-way trackers*, etc", "13 Weather stations", "14 Truckers or generally full time drivers", "15 generic additional station, digi, mobile, wx, etc") APRS_POSITION_COMMENT = ( "off duty", "en route", "in service", "returning", "committed", "special", "priority", "custom 0", "custom 1", "custom 2", "custom 3", "custom 4", "custom 5", "custom 6", "EMERGENCY") # http://aprs.org/symbols/symbolsX.txt APRS_SYMBOLS = ( "Police/Sheriff", "[reserved]", "Digi", "Phone", "DX Cluster", "HF Gateway", "Small Aircraft", "Mobile Satellite Groundstation", "Wheelchair", "Snowmobile", "Red Cross", "Boy Scouts", "House QTH (VHF)", "X", "Red Dot", "0 in Circle", "1 in Circle", "2 in Circle", "3 in Circle", "4 in Circle", "5 in Circle", "6 in Circle", "7 in Circle", "8 in Circle", "9 in Circle", "Fire", "Campground", "Motorcycle", "Railroad Engine", "Car", "File Server", "Hurricane Future Prediction", "Aid Station", "BBS or PBBS", "Canoe", "[reserved]", "Eyeball", "Tractor/Farm Vehicle", "Grid Square", "Hotel", "TCP/IP", "[reserved]", "School", "PC User", "MacAPRS", "NTS Station", "Balloon", "Police", "TBD", "Recreational Vehicle", "Space Shuttle", "SSTV", "Bus", "ATV", "National WX Service Site", "Helicopter", "Yacht/Sail Boat", "WinAPRS", "Human/Person", "Triangle", "Mail/Postoffice", "Large Aircraft", "WX Station", "Dish Antenna", "Ambulance", "Bicycle", "Incident Command Post", "Dual Garage/Fire Dept", "Horse/Equestrian", "Fire Truck", "Glider", "Hospital", "IOTA", "Jeep", "Truck", "Laptop", "Mic-Repeater", "Node", "Emergency Operations Center", "Rover (dog)", "Grid Square above 128m", "Repeater", "Ship/Power Boat", "Truck Stop", "Truck (18 wheeler)", "Van", "Water Station", "X-APRS", "Yagi at QTH", "TDB", "[reserved]" ) 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""" freqstr = freqstr.strip() if freqstr == "": return 0 elif freqstr.endswith(" MHz"): return parse_freq(freqstr.split(" ")[0]) elif freqstr.endswith(" kHz"): return int(freqstr.split(" ")[0]) * 1000 if "." in freqstr: mhz, khz = freqstr.split(".") if mhz == "": mhz = 0 khz = khz.ljust(6, "0") if len(khz) > 6: raise ValueError("Invalid kHz value: %s", khz) mhz = int(mhz) * 1000000 khz = int(khz) else: mhz = int(freqstr) * 1000000 khz = 0 return mhz + khz 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 RadioSettingGroup 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 + TONES_EXTRA, "ctone": TONES + TONES_EXTRA, "dtcs": ALL_DTCS_CODES, "rx_dtcs": ALL_DTCS_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 name in self._valid_map 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 %s: %s%s%s %s (%s) r%.1f%s c%.1f%s d%03i%s%s [%.2f]" % \ (self.number if self.extd_number == "" else self.extd_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: raise errors.InvalidDataError( "Location '%s' is not a valid integer" % vals[0]) 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 MemoryMapping(object): """Base class for a memory mapping""" 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 "%s-%s" % (self.__class__.__name__, self._index) def get_name(self): """Returns the mapping name""" return self._name def get_index(self): """Returns the immutable index (string or int)""" return self._index def __eq__(self, other): return self.get_index() == other.get_index() class MappingModel(object): """Base class for a memory mapping model""" def __init__(self, radio, name): self._radio = radio self._name = name def get_name(self): return self._name def get_num_mappings(self): """Returns the number of mappings in the model (should be callable without consulting the radio""" raise NotImplementedError() def get_mappings(self): """Return a list of mappings""" raise NotImplementedError() def add_memory_to_mapping(self, memory, mapping): """Add @memory to @mapping.""" raise NotImplementedError() def remove_memory_from_mapping(self, memory, mapping): """Remove @memory from @mapping. Shall raise exception if @memory is not in @bank""" raise NotImplementedError() def get_mapping_memories(self, mapping): """Return a list of memories in @mapping""" raise NotImplementedError() def get_memory_mappings(self, memory): """Return a list of mappings that @memory is in""" raise NotImplementedError() class Bank(MemoryMapping): """Base class for a radio's Bank""" 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(MappingModel): """A bank model where one memory is in zero or one banks at any point""" def __init__(self, radio, name='Banks'): super(BankModel, self).__init__(radio, name) class MappingModelIndexInterface: """Interface for mappings with index capabilities""" def get_index_bounds(self): """Returns a tuple (lo,hi) of the min and max mapping indices""" raise NotImplementedError() def get_memory_index(self, memory, mapping): """Returns the index of @memory in @mapping""" raise NotImplementedError() def set_memory_index(self, memory, mapping, index): """Sets the index of @memory in @mapping to @index""" raise NotImplementedError() def get_next_mapping_index(self, mapping): """Returns the next available mapping index in @mapping, or raises Exception if full""" raise NotImplementedError() 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 logging from chirp import logger if not logger.is_visible(logging.WARN): return import sys import os sys.stdout.write("\r%s" % status) if status.cur == status.max: sys.stdout.write(os.linesep) class RadioPrompts: """Radio prompt strings""" experimental = None pre_download = None pre_upload = None display_pre_upload_prompt_before_opening_port = True 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_dtcs_codes": [], "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 name not 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_dtcs_codes", list(DTCS_CODES), "Supported DTCS codes") 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_dtcs_codes and \ mem.dtcs not in self.valid_dtcs_codes: msg = ValidationError("DTCS Code %03i not supported" % mem.dtcs) if self.valid_dtcs_codes and \ mem.rx_dtcs not in self.valid_dtcs_codes: msg = ValidationError("DTCS Code %03i not supported" % mem.rx_dtcs) 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 self.valid_bands and \ self.valid_duplexes and \ mem.duplex in ["split", "-", "+"]: if mem.duplex == "split": freq = mem.offset elif mem.duplex == "-": freq = mem.freq - mem.offset elif mem.duplex == "+": freq = mem.freq + mem.offset valid = False for lo, hi in self.valid_bands: if lo <= freq < hi: valid = True break if not valid: msg = ValidationError( ("Tx freq {freq} is out " "of supported range").format(freq=format_freq(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 Alias(object): VENDOR = "Unknown" MODEL = "Unknown" VARIANT = "" class Radio(Alias): """Base class for all Radio drivers""" BAUD_RATE = 9600 HARDWARE_FLOW = False ALIASES = [] 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) @classmethod def get_prompts(cls): """Return a set of strings for use in prompts""" return RadioPrompts() 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_mapping_models(self): """Returns a list of MappingModel objects (or an empty list)""" if hasattr(self, "get_bank_model"): # FIXME: Backwards compatibility for old bank models bank_model = self.get_bank_model() if bank_model: return [bank_model] return [] 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 RadioSettings list 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 = '' rxmode = '' txval = None rxval = None 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 if txmode == "DTCS": txpol = mem.dtcs_polarity[0] else: txpol = None if rxmode == "DTCS": rxpol = mem.dtcs_polarity[1] else: rxpol = None return ((txmode, txval, txpol), (rxmode, rxval, rxpol)) def sanitize_string(astring, validcharset=CHARSET_ASCII, replacechar='*'): myfilter = ''.join( [ [replacechar, chr(x)][chr(x) in validcharset] for x in xrange(256) ]) return astring.translate(myfilter) chirp-daily-20170714/chirp/bandplan_iaru_r1.py0000644000016101777760000001501612475265017022257 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from chirp import bandplan SHORTNAME = "iaru_r1" DESC = { "name": "IARU Region 1 (Europe, Africa, Middle East and Northern Asia)", "url": "http://iaru-r1.org/index.php?option=com_content" "&view=article&id=175&Itemid=127", "updated": "General Conference Sun City 2011", } # Bands are broken up like this so that other plans can import bits. BANDS_2100M = ( bandplan.Band((135700, 137800), "137khz Band", mode="CW"), ) BANDS_160M = ( bandplan.Band((1810000, 2000000), "160 Meter Band"), bandplan.Band((1810000, 1838000), "CW", mode="CW"), bandplan.Band((1838000, 1840000), "All narrow band modes"), bandplan.Band((1840000, 1843000), "All modes, digimodes", mode="RTTY"), ) BANDS_80M = ( bandplan.Band((3500000, 3800000), "80 Meter Band"), bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"), bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"), bandplan.Band((3560000, 3580000), "CW", mode="CW"), bandplan.Band((3580000, 3600000), "All narrow band modes, digimodes"), bandplan.Band((3590000, 3600000), "All narrow band, digimodes, unattended"), bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((3600000, 3700000), "All modes, SSB QRP", mode="LSB"), bandplan.Band((3700000, 3800000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"), ) BANDS_40M = ( bandplan.Band((7000000, 7200000), "40 Meter Band"), bandplan.Band((7000000, 7040000), "CW", mode="CW"), bandplan.Band((7040000, 7047000), "All narrow band modes, digimodes"), bandplan.Band((7047000, 7050000), "All narrow band, digimodes, unattended"), bandplan.Band((7050000, 7053000), "All modes, digimodes, unattended"), bandplan.Band((7053000, 7060000), "All modes, digimodes"), bandplan.Band((7060000, 7100000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((7100000, 7130000), "All modes, R1 Emergency Center Of Activity", mode="LSB"), bandplan.Band((7130000, 7200000), "All modes, SSB contest preferred", mode="LSB"), bandplan.Band((7175000, 7200000), "All modes, SSB DX preferred", mode="LSB"), ) BANDS_30M = ( bandplan.Band((10100000, 10150000), "30 Meter Band"), bandplan.Band((10100000, 10140000), "CW", mode="CW"), bandplan.Band((10140000, 10150000), "All narrow band digimodes"), ) BANDS_20M = ( bandplan.Band((14000000, 14350000), "20 Meter Band"), bandplan.Band((14000000, 14070000), "CW", mode="CW"), bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"), bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"), bandplan.Band((14125000, 14350000), "All modes, SSB contest preferred", mode="USB"), bandplan.Band((14300000, 14350000), "All modes, Global Emergency center of activity", mode="USB"), ) BANDS_17M = ( bandplan.Band((18068000, 18168000), "17 Meter Band"), bandplan.Band((18068000, 18095000), "CW", mode="CW"), bandplan.Band((18095000, 18109000), "All narrow band modes, digimodes"), bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((18111000, 18168000), "All modes, digimodes"), ) BANDS_15M = ( bandplan.Band((21000000, 21450000), "15 Meter Band"), bandplan.Band((21000000, 21070000), "CW", mode="CW"), bandplan.Band((21070000, 21090000), "All narrow band modes, digimodes"), bandplan.Band((21090000, 21110000), "All narrow band, digimodes, unattended"), bandplan.Band((21110000, 21120000), "All modes, digimodes, unattended"), bandplan.Band((21120000, 21149000), "All narrow band modes"), bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((21151000, 21450000), "All modes", mode="USB"), ) BANDS_12M = ( bandplan.Band((24890000, 24990000), "12 Meter Band"), bandplan.Band((24890000, 24915000), "CW", mode="CW"), bandplan.Band((24915000, 24929000), "All narrow band modes, digimodes"), bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"), ) BANDS_10M = ( bandplan.Band((28000000, 29700000), "10 Meter Band"), bandplan.Band((28000000, 28070000), "CW", mode="CW"), bandplan.Band((28070000, 28120000), "All narrow band modes, digimodes"), bandplan.Band((28120000, 28150000), "All narrow band, digimodes, unattended"), bandplan.Band((28150000, 28190000), "All narrow band modes"), bandplan.Band((28190000, 28199000), "Beacons", mode="CW"), bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons", mode="CW"), bandplan.Band((28201000, 28300000), "Beacons", mode="CW"), bandplan.Band((28300000, 28320000), "All modes, digimodes, unattended"), bandplan.Band((28320000, 29100000), "All modes"), bandplan.Band((29100000, 29200000), "FM simplex", mode="NFM", step_khz=10), bandplan.Band((29200000, 29300000), "All modes, digimodes, unattended"), bandplan.Band((29300000, 29510000), "Satellite downlink"), bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"), bandplan.Band((29520000, 29590000), "All modes, FM repeater inputs", step_khz=10, mode="NFM"), bandplan.Band((29600000, 29610000), "FM simplex", step_khz=10, mode="NFM"), bandplan.Band((29620000, 29700000), "All modes, FM repeater outputs", step_khz=10, mode="NFM", input_offset=-100000), bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"), ) BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M chirp-daily-20170714/chirp/radioreference.py0000644000016101777760000001435312753542577022046 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 logging from chirp import chirp_common, errors LOG = logging.getLogger(__name__) try: from suds.client import Client from suds import WebFault 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 = [] try: service = self._client.service zipcode = service.getZipcodeInfo(self._zip, self._auth) county = service.getCountyInfo(zipcode.ctid, self._auth) except WebFault, err: raise errors.RadioError(err) 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: LOG.debug("Fetching category:", cat.cName) for subcat in cat.subcats: LOG.debug("\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: LOG.debug("Fetching category:", cat.cName) for subcat in cat.subcats: try: LOG.debug("\t", subcat.scName) except AttributeError: pass 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: LOG.error("Error: unsupported tone: %s" % 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-daily-20170714/chirp/xml_ll.py0000644000016101777760000001745612474543421020354 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 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-daily-20170714/chirp/bandplan.py0000644000016101777760000000663512474272620020641 0ustar jenkinsnogroup00000000000000# Copyright 2013 Sean Burford # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from chirp import chirp_common class Band(object): def __init__(self, limits, name, mode=None, step_khz=None, input_offset=None, output_offset=None, tones=None): # Apply semantic and chirp limitations to settings. # memedit applies radio limitations when settings are applied. try: assert limits[0] <= limits[1], "Lower freq > upper freq" if mode is not None: assert mode in chirp_common.MODES, "Mode %s not one of %s" % ( mode, chirp_common.MODES) if step_khz is not None: assert step_khz in chirp_common.TUNING_STEPS, ( "step_khz %s not one of %s" % (step_khz, chirp_common.TUNING_STEPS)) if tones: for tone in tones: assert tone in chirp_common.TONES, ( "tone %s not one of %s" % (tone, chirp_common.TONES)) except AssertionError, e: raise ValueError("%s %s: %s" % (name, limits, e)) self.name = name self.mode = mode self.step_khz = step_khz self.tones = tones self.limits = limits self.offset = None self.duplex = "simplex" if input_offset is not None: self.offset = input_offset self.duplex = "rpt TX" elif output_offset is not None: self.offset = output_offset self.duplex = "rpt RX" def __eq__(self, other): return (other.limits[0] == self.limits[0] and other.limits[1] == self.limits[1]) def contains(self, other): return (other.limits[0] >= self.limits[0] and other.limits[1] <= self.limits[1]) def width(self): return self.limits[1] - self.limits[0] def inverse(self): """Create an RX/TX shadow of this band using the offset.""" if not self.offset: return self limits = (self.limits[0] + self.offset, self.limits[1] + self.offset) offset = -1 * self.offset if self.duplex == "rpt RX": return Band(limits, self.name, self.mode, self.step_khz, input_offset=offset, tones=self.tones) return Band(limits, self.name, self.mode, self.step_khz, output_offset=offset, tones=self.tones) def __repr__(self): desc = '%s%s%s%s' % ( self.mode and 'mode: %s ' % (self.mode,) or '', self.step_khz and 'step_khz: %s ' % (self.step_khz,) or '', self.offset and 'offset: %s ' % (self.offset,) or '', self.tones and 'tones: %s ' % (self.tones,) or '') return "%s-%s %s %s %s" % ( self.limits[0], self.limits[1], self.name, self.duplex, desc) chirp-daily-20170714/chirp/util.py0000644000016101777760000000560012627001021020007 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 def hexprint(data, addrfmt=None): """Return a hexdump-like encoding of @data""" if addrfmt is None: addrfmt = '%(addr)03i' block_size = 8 lines = len(data) / block_size if (len(data) % block_size) != 0: lines += 1 data += "\x00" * ((lines * block_size) - len(data)) out = "" for block in range(0, (len(data)/block_size)): addr = block * block_size try: out += addrfmt % locals() except (OverflowError, ValueError, TypeError, KeyError): out += "%03i" % addr out += ': ' left = len(data) - (block * block_size) if left < block_size: limit = left else: limit = block_size for j in range(0, limit): out += "%02x " % ord(data[(block * block_size) + j]) out += " " for j in range(0, limit): char = data[(block * block_size) + 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] def safe_charset_string(indexes, charset, safechar=" "): """Return a string from an array of charset indexes, replaces out of charset values with safechar""" assert safechar in charset _string = "" for i in indexes: try: _string += charset[i] except IndexError: _string += safechar return _string