[chirp_devel] [PATCH] [BJ9900] Initial support for Baojie BJ-9900
Marco Filippi IZ3GME
Sat Oct 17 06:31:43 PDT 2015
# HG changeset patch
# User Marco Filippi <iz3gme.marco at gmail.com>
# Date 1445088646 -7200
# Sat Oct 17 15:30:46 2015 +0200
# Node ID f058a8b7061f9febb79806190175d46e7d0e3092
# Parent 5c8afedf0ceefd8988eaa7a93c916e44eb3d5f57
[BJ9900] Initial support for Baojie BJ-9900
implement #1185
diff --git a/chirp/drivers/bj9900.py b/chirp/drivers/bj9900.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/bj9900.py
@@ -0,0 +1,393 @@
+#
+# Copyright 2015 Marco Filippi IZ3GME <iz3gme.marco at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 <http://www.gnu.org/licenses/>.
+
+"""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
+
+ at 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
+ # 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, <b>not the mic connector</b>.
+ 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):
+ 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
+
+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
diff --git a/tests/images/Baojie_BJ-9900.img b/tests/images/Baojie_BJ-9900.img
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..905dda31afef6aa14dbada28f45acc56859675d2
GIT binary patch
literal 6385
zc%1FlUr19?7y$5d&*n6ejQ+e-*j@^f1$T3sn-8(AX%25ruI?raqD2q(5=2C1^im={
znBAPys9uT%(SwEgAW>mG__T**&_kh)-a=YJ&v`rF`Obx;4a}#AGZ-KH?eja|`R=*y
zTGsj(u`0i$OP4NPx^(H%rAwFo_fD3s?y{@~i!6>5*rz#HqNgXC>|s8*2mRJOxPv+7
zUvZ<4&SAc;!dGNz;2`?#m+SK{d(h1pyS at hX7eDMnhe#qd?Qx@{-Lj~8;1M{ToPP)I
z(o<^GXk*?q#C`1kkZ`Y|5B%XZ%wrnV at pqs<?hx)<=ab7G#|*lmgNK_i|7nw`?~AfO
zpyrW!^k?<Ln~eml8!Mx~DBGUtHJBeOqR$q^x_(?2oNyQG`n%)c+BD|Bo)&fd86&~`
zWFO`i`b3=|8w2yxt(gD5RpbY8-E(W`pVox8WO)DQOX$lbXP(yy;kpBP^sjkQKU~dm
z-D|_><zeWb)emF++$#61Yt_CkKHh(R0)6)ccr9}n5H*W8x!=9;1l*~oVEnA^mOAd^
zgLRHeSf>T{HMXOGexqR555c;>xt)*xlb<(gj(9|$UN6+iZe2jXw_x|<%jij(-JeFE
zO4|+>fMyRhqR%$k9)Jr|<vyaOdk)Xl+mhH9jMJ*R-`vG*+*yP^9ldZ$D$GIa>g<Z9
ztKPqK{6P@%g>E4K^K+~Zk>$q|x>r@%=kFnOh{6NRa}@W*Bf1arb9?cA;gQV;Jm?G1
zTsNLK9+?|SE$LTPn)~KPhsgG;@$pow(;ZKF=|u1pcOKEhp_TwOulM6Q{Lv3{WApT?
zoA|#cUAlDX(xv}TUg`J!Fg(EhYkl5XdBywWkw5>Z`15}QS<<&NA at C*&dy|G(#QCL4
zJ!K~yFO6+H-x*RA7jd~hlCwmql2fVQ&BodjZQ9Y~@wQrbBGnwzJITX05v6{V&@*X5
ISEeG$FFCbL0ssI2
More information about the chirp_devel
mailing list