# HG changeset patch # Parent 141998a54195a4ac9e897b9f605f4ec89f854524 [baojie_bj-218] New driver; replaces Lution lt725uv and includes aliases for lt725uv and two others Addresses issues #4645 and #5407, Fixes #5595 user: Rick DeWitt branch 'default' added chirp/drivers/baojie_bj218.py diff -r 141998a54195 -r 44525cda12b6 chirp/drivers/baojie_bj218.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/baojie_bj218.py Wed Feb 21 07:40:52 2018 -0800 @@ -0,0 +1,1379 @@ +# Copyright 2016: +# Adapted from lt725uv driver: Jim Unroe KC9HI, +# Modified for Baojie BJ-218: 2017 by Rick DeWitt (RJD), +# +# 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,InvalidValueError, RadioSettings +from textwrap import dedent + +MEM_FORMAT = """ +#seekto 0x0200; +struct { + u8 init_bank; + u8 volume; + u16 fm_freq; + u8 wtled; + u8 rxled; + u8 txled; + u8 ledsw; + u8 beep; + u8 ring; + u8 bcl; + u8 tot; + u16 sig_freq; + u16 dtmf_txms; + u8 init_sql; + u8 rptr_mode; +} settings; + +#seekto 0x0240; +struct { + u8 dtmf1_cnt; + u8 dtmf1[7]; + u8 dtmf2_cnt; + u8 dtmf2[7]; + u8 dtmf3_cnt; + u8 dtmf3[7]; + u8 dtmf4_cnt; + u8 dtmf4[7]; + u8 dtmf5_cnt; + u8 dtmf5[7]; + u8 dtmf6_cnt; + u8 dtmf6[7]; + u8 dtmf7_cnt; + u8 dtmf7[7]; + u8 dtmf8_cnt; + u8 dtmf8[7]; +} dtmf_tab; + +#seekto 0x0280; +struct { + u8 native_id_cnt; + u8 native_id_code[7]; + u8 master_id_cnt; + u8 master_id_code[7]; + u8 alarm_cnt; + u8 alarm_code[5]; + u8 id_disp_cnt; + u8 id_disp_code[5]; + u8 revive_cnt; + u8 revive_code[5]; + u8 stun_cnt; + u8 stun_code[5]; + u8 kill_cnt; + u8 kill_code[5]; + u8 monitor_cnt; + u8 monitor_code[5]; + u8 state_now; +} codes; + +#seekto 0x02d0; +struct { + u8 hello1_cnt; + char hello1[7]; + u8 hello2_cnt; + char hello2[7]; + u32 vhf_low; + u32 vhf_high; + u32 uhf_low; + u32 uhf_high; + u8 lims_on; +} hello_lims; + +struct vfo { + u8 frq_chn_mode; + u8 chan_num; + u32 rxfreq; + u16 is_rxdigtone:1, + rxdtcs_pol:1, + rx_tone:14; + u8 rx_mode; + u8 unknown_ff; + u16 is_txdigtone:1, + txdtcs_pol:1, + tx_tone:14; + u8 launch_sig; + u8 tx_end_sig; + u8 power; + u8 fm_bw; + u8 cmp_nder; + u8 scrm_blr; + u8 shift; + u32 offset; + u16 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[7]; +}; + +#seekto 0x0400; +struct mem upper_memory[128]; + +#seekto 0x1000; +struct mem lower_memory[128]; + +#seekto 0x1C00; +struct { + char mod_num[6]; +} mod_id; + +""" + +MEM_SIZE = 0x1C00 +BLOCK_SIZE = 0x40 # 'Standard' 24-byte block +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", "High"] +LIST_COLOR = ["Off", "Orange", "Blue", "Purple"] +LIST_LEDSW = ["Auto", "On"] +LIST_RING = ["Off"] + ["%s" % x for x in range(1, 10)] +LIST_TDR_DEF = ["A-Upper", "B-Lower"] +LIST_TIMEOUT = ["Off"] + ["%s" % x for x in range(30, 630, 30)] +LIST_VFOMODE = ["Frequency Mode", "Channel Mode"] +TONES_CTCSS = sorted(chirp_common.TONES) # RJD: Numeric, Defined in \chirp\chirp_common.py +LIST_CTCSS = ["Off"] +[str(x) for x in TONES_CTCSS] # Converted to strings + # Now append the DxxxN and DxxxI DTCS codes from chirp_common +for x in chirp_common.DTCS_CODES: + LIST_CTCSS.append("D{:03d}N".format(x)) +for x in chirp_common.DTCS_CODES: + LIST_CTCSS.append("D{:03d}R".format(x)) +LIST_BW= ["Narrow", "Wide"] +LIST_SHIFT= ["Off"," + ", " - "] +STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0] +LIST_STEPS = [str(x) for x in STEPS] +LIST_STATE = ["Normal", "Stun", "Kill"] +LIST_SSF = ["1000", "1450", "1750", "2100"] +LIST_DTMFTX= ["50", "100", "150", "200", "300","500"] + +SETTING_LISTS = { +"init_bank": LIST_TDR_DEF , +"tot": LIST_TIMEOUT, +"wtled": LIST_COLOR, +"rxled": LIST_COLOR, +"txled": LIST_COLOR, +"sig_freq": LIST_SSF, +"dtmf_txms": LIST_DTMFTX, +"ledsw": LIST_LEDSW, +"frq_chn_mode": LIST_VFOMODE, +"rx_tone": LIST_CTCSS, +"tx_tone": LIST_CTCSS, +"rx_mode": LIST_RECVMODE, +"launch_sig": LIST_SIGNAL, +"tx_end_sig": LIST_SIGNAL, +"power": LIST_POWER, +"fm_bw": LIST_BW, +"shift": LIST_SHIFT, +"step": LIST_STEPS, +"ring": LIST_RING, +"state_now": LIST_STATE +} + +def _clean_buffer(radio): + """Empty the read buffer.""" + 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 header 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): + """Send the code to terminate radio I/O.""" + 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" + data += "BJ-218" # Append model number to memory map. RJD + + 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] # LT-725UV + rid = data[0x1C00:0x1C06] # BJ-218 RJD + + 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 + +class Luiton(chirp_common.Alias): + """Declare BJ-218 alias for Luiton LT-725UV.""" + VENDOR = "Luiton" + MODEL = "LT-725UV" + NAME_LENGTH = 6 + +class Zastone(chirp_common.Alias): + """Declare BJ-218 alias for Zastone BJ-218.""" + VENDOR = "Zastone" + MODEL = "BJ-218" + NAME_LENGTH = 7 + +class Hesenate(chirp_common.Alias): + """Declare BJ-218 alias for Hesenate BJ-218.""" + VENDOR = "Hesenate" + MODEL = "BJ-218" + NAME_LENGTH = 7 + +@directory.register +class BJ218(chirp_common.CloneModeRadio, + chirp_common.ExperimentalRadio): + """Baojie BJ-218 Radio""" + VENDOR = "Baojie" + MODEL = "BJ-218" + MODES = ["NFM", "FM"] + POWER_LEVELS = [chirp_common.PowerLevel("High", watts=30.00), + chirp_common.PowerLevel("Low", watts=5.00)] + TONES = chirp_common.TONES + DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645]) + NAME_LENGTH = 7 # BJ-218 + DTMF_CHARS = list("0123456789ABCD*#") + + VALID_BANDS = [(136000000, 176000000), + (400000000, 480000000)] + + # valid chars on the LCD + VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \ + "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_" + + ALIASES = [Luiton, Zastone, Hesenate] + + @classmethod + def get_prompts(cls): + """Define standard prompts.""" + rp = chirp_common.RadioPrompts() + rp.experimental = \ + ('The BJ-218 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 + 5 - Turn off your radio + 6 - Unplug the interface cable + """)) + + 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 + 5 - Turn off your radio + 6 - Unplug the interface cable + """)) + return rp + + + def get_features(self): + """Declare radio 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.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_power_levels = self.POWER_LEVELS + 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): + """Load list of valid VFO memory 'banks'.""" + return [BJ218Upper(self._mmap), BJ218Lower(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 string representation of selected UI channel memory structure.""" + if (self._vfo == "upper"): # For some reason the _memory_obj() call fails + return repr(self._memobj.upper_memory[number - 1]) + else : + return repr(self._memobj.lower_memory[number - 1]) + + def _memory_obj(self, suffix=""): + """Create memory object for the upper or lower raw memory bank.""" + 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): + """Standard function call to populate the UI rows.""" + _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] + + # val = _mem.power # RJD + # if _mem.power == 1: # BJ-218 memory only supports low / high + # val = 2 # bit 1 off/on + # mem.power = LIST_POWER[val] # RJD + + return mem + + def set_memory(self, mem): + """Standard function call to update raw memory from UI values.""" + _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) + # if mem.power== 0: _mem.power = 0 + # else: _mem.power= 1 # 'Mid' not allowed, set to high + + + def get_settings(self): + """Translate the bit in the mem_struct into settings in the UI""" + _sets = self._memobj.settings # define mem struct write-back shortcuts + _vfoa = self._memobj.upper.vfoa + _vfob = self._memobj.lower.vfob + _lims = self._memobj.hello_lims + _codes = self._memobj.codes + _dtmf = self._memobj.dtmf_tab + + basic = RadioSettingGroup("basic", "Basic Settings") + a_band = RadioSettingGroup("a_band", "VFO A-Upper Settings") # RJD + b_band = RadioSettingGroup("b_band", "VFO B-Lower Settings") # RJD + codes = RadioSettingGroup("codes", "Codes & DTMF Groups") # RJD + lims = RadioSettingGroup("lims", "PowerOn & Freq Limits") # RJD + group = RadioSettings(basic, a_band, b_band, lims, codes) # RJD + + # Basic Settings + bnd_mode = RadioSetting("settings.init_bank", "TDR Band Default", + RadioSettingValueList(LIST_TDR_DEF, LIST_TDR_DEF[ _sets.init_bank])) + basic.append(bnd_mode) + + volume = RadioSetting("settings.volume", "Volume", + RadioSettingValueInteger(0, 20, + _sets.volume)) + basic.append(volume) + + def my_word2raw(setting, obj, atrb, mlt=10): + """Callback function to convert UI floating value 131.8 to u16 int 1318 .""" + # LOG.warning("Setting: "+str(setting)) + if str( setting.value) == "Off": + frq= 0x0FFFF + else: + frq=int(float(str(setting.value)) * float(mlt)) + if frq == 0 : frq = 0xFFFF + # LOG.warning("Word2raw Frq= "+str(frq)) + setattr(obj, atrb, frq) + return + + def my_adjraw(setting, obj, atrb, fix): + """Callback: add or subtract fix from value.""" + # LOG.warning("Setting: "+str(setting)) + vx = int(str(setting.value)) + value = vx + int(fix) + if value<0: value = 0 + if atrb == "frq_chn_mode" and int(str(setting.value))==2: + value = vx *2 # special handling for frq_chn_mode; 2 => 1 + setattr(obj, atrb, value) + return + + def my_dbl2raw(setting, obj, atrb, flg=1): + """Callback: convert from freq 146.7600 to 14760000 U32.""" + vr = float(str(setting.value)) + value = vr * 100000 + if flg ==1 and value == 0: value = 0xFFFFFFFF # flg=1 means 0 becomes ff, else leave as possible 0 + setattr(obj, atrb, value) + return + + def my_val_list(setting, obj, atrb): + """Callback: from ValueList with non-sequential, actual values.""" + value = int(str(setting.value)) # get the integer value + # LOG.warning("Val from List: "+str(value)) + if atrb == "tot": value = int(value/30) # 30 second increments + setattr(obj, atrb, value) + return + + def my_spcl(setting,obj, atrb): + """Callback: Special handling based on atrb.""" + if atrb == "frq_chn_mode": + idx = LIST_VFOMODE.index (str(setting.value)) # returns 0 or 1 + value = idx * 2 # set bit 1 + setattr(obj, atrb, value) + return + + def my_tone_strn(obj, is_atr, pol_atr, tone_atr): + """Generate the CTCS/DCS tone code string.""" + vx = int(getattr(obj,tone_atr)) + # LOG.warning("Vx= "+str(vx)) + if vx == 16383 or vx== 0: return "Off" # 16383 is all bits set + if getattr(obj,is_atr) ==0: # Simple CTCSS code + tstr = str(vx/10.0) + else: # DCS + if getattr(obj,pol_atr)== 0: tstr = "D{:03x}R".format(vx) + else: tstr = "D{:03x}N".format(vx) + # LOG.warning("Tone String: "+tstr) + return tstr + + def my_set_tone(setting, obj, is_atr, pol_atr, tone_atr): + """Callback- create the tone setting from string code.""" + sx = str(setting.value) # '131.8' or 'D231N' or 'Off' + if sx == "Off": + isx = 1 + polx = 1 + tonx = 0x3FFF + elif sx[0]=="D": # DCS + isx = 1 + if sx[4]=="N": polx=1 + else: polx=0 + tonx = int(sx[1:4],16) + else: # CTCSS + isx = 0 + polx = 0 + tonx = int(float(sx)*10.0) + # LOG.warning("Setting tone: is="+str(isx)+", pol="+str(polx)+", tone="+hex(tonx)) + setattr(obj,is_atr,isx) + setattr(obj,pol_atr,polx) + setattr(obj,tone_atr,tonx) + return + + val = _sets.fm_freq /10.0 + if val == 0 : val = 88.9 # 0 is not valid + rs = RadioSetting("settings.fm_freq", "FM Broadcast Freq (MHz)", + RadioSettingValueFloat(65, 108.0,val, 0.1, 1)) + rs.set_apply_callback(my_word2raw, _sets, "fm_freq") + basic.append(rs) + + wtled = RadioSetting("wtled", "Standby LED Color", + RadioSettingValueList(LIST_COLOR, LIST_COLOR[ + _sets.wtled])) + basic.append(wtled) + + rxled = RadioSetting("rxled", "RX LED Color", + RadioSettingValueList(LIST_COLOR, LIST_COLOR[ + _sets.rxled])) + basic.append(rxled) + + txled = RadioSetting("txled", "TX LED Color", + RadioSettingValueList(LIST_COLOR, LIST_COLOR[ + _sets.txled])) + basic.append(txled) + + ledsw = RadioSetting("ledsw", "Back light mode", + RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[ + _sets.ledsw])) + basic.append(ledsw) + + beep = RadioSetting("settings.beep", "Beep", + RadioSettingValueBoolean(bool(_sets.beep))) + basic.append(beep) + + ring = RadioSetting("ring", "Ring (Secs)", + RadioSettingValueList(LIST_RING, LIST_RING[ + _sets.ring])) + basic.append(ring) + + bcl = RadioSetting("settings.bcl", "Busy channel lockout", + RadioSettingValueBoolean(bool(_sets.bcl))) + basic.append(bcl) + + tmp = str(int(_sets.tot)*30) # _sets.tot has 30 sec step counter + rs = RadioSetting("settings.tot", "Transmit Timeout (Secs)", + RadioSettingValueList(LIST_TIMEOUT, tmp)) + rs.set_apply_callback(my_val_list, _sets, "tot") + basic.append(rs) + + tmp =str(int( _sets.sig_freq)) + rs = RadioSetting("settings.sig_freq", "Single Signaling Tone (Htz)", + RadioSettingValueList(LIST_SSF,tmp)) + rs.set_apply_callback(my_val_list, _sets, "sig_freq") + basic.append(rs) + + tmp =str(int( _sets.dtmf_txms)) + rs = RadioSetting("settings.dtmf_txms", "DTMF Tx Duration (mSecs)", + RadioSettingValueList(LIST_DTMFTX, tmp)) + rs.set_apply_callback(my_val_list, _sets, "dtmf_txms") + basic.append(rs) + + if _sets.init_sql == 0xFF: + val = 0x04 + else: + val = _sets.init_sql + + rs = RadioSetting("settings.init_sql", "Squelch",RadioSettingValueInteger(0, 9, val)) + basic.append(rs) + + rs = RadioSetting("settings.rptr_mode", "Repeater Mode", + RadioSettingValueBoolean(bool(_sets.rptr_mode))) + basic.append(rs) + + # UPPER BAND SETTINGS RJD + + val = _vfoa.frq_chn_mode/2 # Freq Mode, convert bit 1 state to index pointer + rs = RadioSetting("upper.vfoa.frq_chn_mode", "Default Mode", + RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])) + rs.set_apply_callback(my_spcl,_vfoa,"frq_chn_mode") # special handling + a_band.append(rs) + + val =_vfoa.chan_num+1 # add 1 for 1-128 displayed + rs = RadioSetting("upper.vfoa.chan_num", "Initial Chan", RadioSettingValueInteger(1, 128, val)) + rs.set_apply_callback(my_adjraw,_vfoa,"chan_num",-1) + a_band.append(rs) + + val = _vfoa.rxfreq /100000.0 + rs = RadioSetting("upper.vfoa.rxfreq ", "Default Recv Freq (MHz)", + RadioSettingValueFloat(136.0, 176.0,val, 0.001,5)) + rs.set_apply_callback(my_dbl2raw,_vfoa,"rxfreq") + a_band.append(rs) + + tmp = my_tone_strn(_vfoa, "is_rxdigtone", "rxdtcs_pol", "rx_tone") + rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)", + RadioSettingValueList(LIST_CTCSS,tmp)) + rs.set_apply_callback(my_set_tone,_vfoa,"is_rxdigtone", "rxdtcs_pol", "rx_tone") + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.rx_mode", "Default Recv Mode", + RadioSettingValueList(LIST_RECVMODE,LIST_RECVMODE[_vfoa.rx_mode])) + a_band.append(rs) + + tmp = my_tone_strn(_vfoa, "is_txdigtone", "txdtcs_pol", "tx_tone") + rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)", + RadioSettingValueList(LIST_CTCSS,tmp)) + rs.set_apply_callback(my_set_tone,_vfoa,"is_txdigtone", "txdtcs_pol", "tx_tone") + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.launch_sig", "Launch Signaling", + RadioSettingValueList(LIST_SIGNAL,LIST_SIGNAL[_vfoa.launch_sig])) + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.tx_end_sig", "Xmit End Signaling", + RadioSettingValueList(LIST_SIGNAL,LIST_SIGNAL[_vfoa.tx_end_sig])) + a_band.append(rs) + + val = _vfoa.power / 2 # Bit 1 is set (High) or cleared (low) + rs = RadioSetting("upper.vfoa.power", "Bank Power", + RadioSettingValueList(LIST_POWER, LIST_POWER[val])) + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.fm_bw", "Wide/Narrow Band", + RadioSettingValueList(LIST_BW,LIST_BW[_vfoa.fm_bw])) + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.cmp_nder", "Compandor", + RadioSettingValueBoolean(bool(_vfoa.cmp_nder))) + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.scrm_blr", "Scrambler", + RadioSettingValueBoolean(bool(_vfoa.scrm_blr))) + a_band.append(rs) + + rs = RadioSetting("upper.vfoa.shift", "Xmit Shift", + RadioSettingValueList(LIST_SHIFT,LIST_SHIFT[_vfoa.shift])) + a_band.append(rs) + + val = _vfoa.offset / 100000.0 + rs = RadioSetting("upper.vfoa.offset", "Xmit Offset (MHz)", + RadioSettingValueFloat(0, 100.0,val, 0.001,3)) + rs.set_apply_callback(my_dbl2raw,_vfoa,"offset",0) # allow zero value + a_band.append(rs) + + tmp = str(_vfoa.step/100.0) + rs = RadioSetting("step", "Freq step (KHz)", + RadioSettingValueList(LIST_STEPS,tmp)) + rs.set_apply_callback(my_word2raw,_vfoa,"step",100) + a_band.append(rs) + + if _vfoa.sql == 0xFF: + val = 0x04 + else: + val = _vfoa.sql + + rs = RadioSetting("upper.vfoa.sql", "Squelch", + RadioSettingValueInteger(0, 9, val)) + a_band.append(rs) + + # LOWER BAND SETTINGS RJD + + val = _vfob.frq_chn_mode/2 + rs = RadioSetting("lower.vfob.frq_chn_mode", "Default Mode", + RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])) + rs.set_apply_callback(my_spcl,_vfob,"frq_chn_mode") + b_band.append(rs) + + val = _vfob.chan_num + 1 + rs = RadioSetting("lower.vfob.chan_num", "Initial Chan", RadioSettingValueInteger(0, 127, val)) + rs.set_apply_callback(my_adjraw, _vfob,"chan_num",-1) + b_band.append(rs) + + val = _vfob.rxfreq / 100000.0 # RJD + rs = RadioSetting("lower.vfob.rxfreq ", "Default Recv Freq (MHz)", + RadioSettingValueFloat(400.0, 480.0,val, 0.001,5)) + rs.set_apply_callback(my_dbl2raw, _vfob,"rxfreq") + b_band.append(rs) + + tmp = my_tone_strn(_vfob, "is_rxdigtone", "rxdtcs_pol", "rx_tone") + rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)", + RadioSettingValueList(LIST_CTCSS,tmp)) + rs.set_apply_callback(my_set_tone,_vfob,"is_rxdigtone", "rxdtcs_pol", "rx_tone") + b_band.append(rs) + + rs = RadioSetting("lower.vfob.rx_mode", "Default Recv Mode", + RadioSettingValueList(LIST_RECVMODE,LIST_RECVMODE[_vfob.rx_mode])) + b_band.append(rs) + + tmp = my_tone_strn(_vfob, "is_txdigtone", "txdtcs_pol", "tx_tone") + rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)", + RadioSettingValueList(LIST_CTCSS,tmp)) + rs.set_apply_callback(my_set_tone,_vfob,"is_txdigtone", "txdtcs_pol", "tx_tone") + b_band.append(rs) + + rs = RadioSetting("lower.vfob.launch_sig", "Launch Signaling", + RadioSettingValueList(LIST_SIGNAL,LIST_SIGNAL[_vfob.launch_sig])) + b_band.append(rs) + + rs = RadioSetting("lower.vfob.tx_end_sig", "Xmit End Signaling", + RadioSettingValueList(LIST_SIGNAL,LIST_SIGNAL[_vfob.tx_end_sig])) + b_band.append(rs) + + val = _vfob.power/2 + rs = RadioSetting("lower.vfob.power", "Bank Power", + RadioSettingValueList(LIST_POWER, LIST_POWER[val])) + b_band.append(rs) + + rs = RadioSetting("lower.vfob.fm_bw", "Wide/Narrow Band", + RadioSettingValueList(LIST_BW,LIST_BW[_vfob.fm_bw])) + b_band.append(rs) + + rs = RadioSetting("lower.vfob.cmp_nder", "Compandor", + RadioSettingValueBoolean(bool(_vfob.cmp_nder))) + b_band.append(rs) + + rs = RadioSetting("lower.vfob.scrm_blr", "Scrambler", + RadioSettingValueBoolean(bool(_vfob.scrm_blr))) + b_band.append(rs) + + rs = RadioSetting("lower.vfob.shift", "Xmit Shift", + RadioSettingValueList(LIST_SHIFT,LIST_SHIFT[_vfob.shift])) + b_band.append(rs) + + val = _vfob.offset / 100000.0 + rs = RadioSetting("lower.vfob.offset", "Xmit Offset (MHz)", + RadioSettingValueFloat(0, 100.0,val, 0.001,3)) + rs.set_apply_callback(my_dbl2raw, _vfob,"offset",0) + b_band.append(rs) + + tmp = str(_vfob.step/100.0) + rs = RadioSetting("step", "Freq step (KHz)", + RadioSettingValueList(LIST_STEPS,tmp)) + rs.set_apply_callback(my_word2raw, _vfob,"step",100) + b_band.append(rs) + + if _vfob.sql == 0xFF: + val = 0x04 + else: + val = _vfob.sql + + rs = RadioSetting("lower.vfob.sql", "Squelch", + RadioSettingValueInteger(0, 9, val)) + b_band.append(rs) + + # PowerOn & Freq Limits Settings + + def chars2str(cary, knt): + """Convert raw memory char array to a string: NOT a callback.""" + stx = "" + for char in cary[:knt]: + stx += chr(char) + return stx + + def my_str2ary(setting, obj, atrba, atrbc): + """Callback: convert 7-char string to char array with count.""" + ary = "" + knt = 7 + for j in range (6, -1, -1): # strip trailing whitespaces + if str(setting.value)[j]== "" or str(setting.value)[j]== " ": + knt = knt -1 + else: break + # LOG.warning("Knt= "+str(knt)) + for j in range(0, 7,1): + if j < knt: ary += str(setting.value)[j] # 'C','2','6','4'... + else: ary += chr(0xFF) + # LOG.warning("String="+ary) + setattr(obj,atrba,ary) + setattr(obj,atrbc,knt) + return + + tmp = chars2str(_lims.hello1, _lims.hello1_cnt) + rs = RadioSetting("hello_lims.hello1", "Power-On Message 1", + RadioSettingValueString(0, 7, tmp)) + rs.set_apply_callback(my_str2ary,_lims,"hello1","hello1_cnt") + lims.append(rs) + + tmp = chars2str( _lims.hello2,_lims.hello2_cnt) + rs = RadioSetting("hello_lims.hello2", "Power-On Message 2", + RadioSettingValueString(0, 7, tmp)) + rs.set_apply_callback(my_str2ary,_lims,"hello2","hello2_cnt") + lims.append(rs) + +# VALID_BANDS = [(136000000, 176000000),400000000, 480000000)] + + lval = _lims.vhf_low / 100000.0 + uval = _lims.vhf_high / 100000.0 + if lval >= uval : + lval = 144.0 + uval = 158.0 + + rs = RadioSetting("hello_lims.vhf_low", "Lower VHF Band Limit (MHz)", + RadioSettingValueFloat(136.0, 176.0,lval, 0.001,3)) + rs.set_apply_callback(my_dbl2raw, _lims,"vhf_low") + lims.append(rs) + + rs = RadioSetting("hello_lims.vhf_high", "Upper VHF Band Limit (MHz)", + RadioSettingValueFloat(136.0, 176.0,uval, 0.001,3)) + rs.set_apply_callback(my_dbl2raw, _lims,"vhf_high") + lims.append(rs) + + lval = _lims.uhf_low / 100000.0 + uval = _lims.uhf_high / 100000.0 + if lval >= uval : + lval = 420.0 + uval = 470.0 + + rs = RadioSetting("hello_lims.uhf_low", "Lower UHF Band Limit (MHz)", + RadioSettingValueFloat(400.0, 480.0,lval, 0.001,3)) + rs.set_apply_callback(my_dbl2raw, _lims,"uhf_low") + lims.append(rs) + + rs = RadioSetting("hello_lims.uhf_high", "Upper UHF Band Limit (MHz)", + RadioSettingValueFloat(400.0, 480.0,uval, 0.001,3)) + rs.set_apply_callback(my_dbl2raw, _lims,"uhf_high") + lims.append(rs) + + # Codes and DTMF Groups Settings + + def make_dtmf(ary, knt): + """Generate the DTMF code 1-8, NOT a callback.""" + tmp = "" + if knt> 0: + for val in ary[:knt]: + if val >0 and val <=9: tmp += chr(val+48) + elif val == 0x0a: tmp += "0" + elif val == 0x0d: tmp += "A" + elif val == 0x0e: tmp += "B" + elif val == 0x0f: tmp += "C" + elif val == 0x00: tmp += "D" + elif val == 0x0b: tmp += "*" + elif val == 0x0c: tmp += "#" + else: + msg = ("Invalid Character. Must be: 0-9,A,B,C,D,*,#") + raise InvalidValueError(msg) + + # LOG.warning("Tmp: "+tmp) + return tmp + + def my_dtmf2raw(setting, obj, atrba, atrbc, syz=7): + """Callback: DTMF Code; sends 5 or 7-byte string.""" + draw = [] + knt = syz + for j in range (syz-1, -1, -1): # strip trailing whitespaces + if str(setting.value)[j]== "" or str(setting.value)[j]== " ": + knt = knt -1 + else: break + for j in range(0, syz): + bx = str(setting.value)[j] # 'C','2','6','4'... + obx = ord(bx) + dig = 0x0ff + if j < knt and knt > 0: # (Else) is pads + if bx == "0": dig = 0x0a + elif bx == "A": dig = 0x0d + elif bx == "B": dig = 0x0e + elif bx == "C": dig = 0x0f + elif bx == "D": dig = 0x00 + elif bx == "*": dig = 0x0b + elif bx == "#": dig = 0x0c + elif obx>=49 and obx<=57: dig = obx-48 + else: + # LOG.warning("Setting: "+str(setting)+", knt= "+str(knt)+", bx["+str(j)+"]= "+bx+", sofar= "+hex(draw)) + msg = ("Must be: 0-9,A,B,C,D,*,#") + raise InvalidValueError(msg) + # - end if/elif/else for bx + # - end if J<=knt + draw.append(dig) # generate string of bytes + # - end for j + # LOG.warning("DTMF raw= "+str(draw)+", Knt= "+str(knt)) + setattr(obj, atrba, draw) + setattr(obj, atrbc, knt) + return + + tmp = make_dtmf(_codes.native_id_code,_codes.native_id_cnt) + rs = RadioSetting("codes.native_id_code", "Native ID Code", RadioSettingValueString(0, 7, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"native_id_code","native_id_cnt",7) + codes.append(rs) + + tmp = make_dtmf( _codes.master_id_code,_codes.master_id_cnt) + rs = RadioSetting("codes.master_id_code", "Master Control ID Code", RadioSettingValueString(0, 7, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"master_id_code","master_id_cnt",7) + codes.append(rs) + + tmp = make_dtmf( _codes.alarm_code,_codes.alarm_cnt) + rs = RadioSetting("codes.alarm_code", "Alarm Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"alarm_code","alarm_cnt",5) + codes.append(rs) + + tmp = make_dtmf( _codes.id_disp_code,_codes.id_disp_cnt) + rs = RadioSetting("codes.id_disp_code", "Identify Display Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"id_disp_code","id_disp_cnt",5) + codes.append(rs) + + tmp = make_dtmf( _codes.revive_code,_codes.revive_cnt) + rs = RadioSetting("codes.revive_code", "Revive Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw,_codes,"revive_code","revive_cnt",5) + codes.append(rs) + + tmp = make_dtmf( _codes.stun_code,_codes.stun_cnt) + rs = RadioSetting("codes.stun_code", "Remote Stun Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"stun_code","stun_cnt",5) + codes.append(rs) + + tmp = make_dtmf( _codes.kill_code,_codes.kill_cnt) + rs = RadioSetting("codes.kill_code", "Remote KILL Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes,"kill_code","kill_cnt",5) + codes.append(rs) + + tmp = make_dtmf( _codes.monitor_code,_codes.monitor_cnt) + rs = RadioSetting("codes.monitor_code", "Monitor Code", RadioSettingValueString(0, 5, tmp)) + rs.set_apply_callback(my_dtmf2raw, _codes, "monitor_code", "monitor_cnt",5) + codes.append(rs) + + val = _codes.state_now + if val > 2 : + val = 0 + + rs = RadioSetting("codes.state_now", "Current State", + RadioSettingValueList(LIST_STATE,LIST_STATE[val])) + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf1, _dtmf.dtmf1_cnt) +# LOG.warning("DTMF: "+dtm) + rs = RadioSetting("dtmf_tab.dtmf1", "DTMF1 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf1","dtmf1_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf2, _dtmf.dtmf2_cnt) + rs = RadioSetting("dtmf_tab.dtmf2", "DTMF2 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf2","dtmf2_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf3, _dtmf.dtmf3_cnt) + rs = RadioSetting("dtmf_tab.dtmf3", "DTMF3 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf3","dtmf3_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf4, _dtmf.dtmf4_cnt) + rs = RadioSetting("dtmf_tab.dtmf4", "DTMF4 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf4","dtmf4_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf5, _dtmf.dtmf5_cnt) + rs = RadioSetting("dtmf_tab.dtmf5", "DTMF5 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf5","dtmf5_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf6, _dtmf.dtmf6_cnt) + rs = RadioSetting("dtmf_tab.dtmf6", "DTMF6 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf6","dtmf6_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf7, _dtmf.dtmf7_cnt) + rs = RadioSetting("dtmf_tab.dtmf7", "DTMF7 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf7","dtmf7_cnt") + codes.append(rs) + + dtm = make_dtmf(_dtmf.dtmf8, _dtmf.dtmf8_cnt) + rs = RadioSetting("dtmf_tab.dtmf8", "DTMF8 String", RadioSettingValueString(0, 7, dtm)) + rs.set_apply_callback(my_dtmf2raw,_dtmf,"dtmf8","dtmf8_cnt") + codes.append(rs) + + return group # END get_settings() + + + def set_settings(self, settings): + """Copy UI settings back into raw memory.""" + _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): + """Test img memory belongs to this driver.""" + match_size = False + match_model = False + + # testing the file data size + # if len(filedata) == MEM_SIZE + 8: " LT-725UV + if len(filedata) == MEM_SIZE + 6: # BJ-218 RJD + 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 BJ218Upper(BJ218): + """Define the class for the upper memory bank.""" + VARIANT = "Upper" + _vfo = "upper" + + +class BJ218Lower(BJ218): + """Define the class for the lower memory bank.""" + VARIANT = "Lower" + _vfo = "lower"