# 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): 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): 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): VENDOR = "Luiton" MODEL = "LT-725UV" class Zastone(chirp_common.Alias): VENDOR = "Zastone" MODEL = "BJ-218" class Hesenate(chirp_common.Alias): VENDOR = "Hesenate" MODEL = "BJ-218" @directory.register class BJ218(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio): """Baojie BJ-218 Rad000000io""" 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 = 6 #LT-725UV 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): 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): 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): 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): 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=""): 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] # 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): _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: convert UI floating value 131.8 to 16b 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): # 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): _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: " 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): VARIANT = "Upper" _vfo = "upper" class BJ218Lower(BJ218): VARIANT = "Lower" _vfo = "lower"