[chirp_devel] [PATCH] [FT818] New Model Support Yaesu FT-818 #5607

Vinny Stipo
Sun Dec 2 00:25:57 PST 2018


# HG changeset patch
# User Vinny Stipo <v at xpctech.com>
# Date 1543738069 28800
#      Sun Dec 02 00:07:49 2018 -0800
# Node ID 4bddc1caa07f1389711bf6ddf1b0a75f74c80c2f
# Parent  c6cab71d7d7d76b63367f5529a1b2ea075a8db1b
[FT818] New Model Support Yaesu FT-818 #5607

diff --git a/chirp/drivers/ft818.py b/chirp/drivers/ft818.py
new file mode 100755
--- /dev/null
+++ b/chirp/drivers/ft818.py
@@ -0,0 +1,1098 @@
+#
+# Copyright 2012 Filippi Marco <iz3gme.marco at gmail.com>
+# Copyright 2018 Vinny Stipo <v at xpctech.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 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 <http://www.gnu.org/licenses/>.
+
+"""FT818 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
+
+ at directory.register
+class FT818Radio(yaesu_clone.YaesuCloneModeRadio):
+
+    """Yaesu FT-818"""
+    BAUD_RATE = 9600
+    MODEL = "FT-818"
+    _model = ""
+
+    DUPLEX = ["", "-", "+", "split"]
+    # narrow modes has to be at end
+    MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW",
+             "NCWR", "NFM"]
+    TMODES = ["", "Tone", "TSQL", "DTCS"]
+    STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+    STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0]
+    STEPSSSB = [1.0, 2.5, 5.0]
+
+    # warning ranges has to be in this exact order
+    VALID_BANDS = [(100000, 33000000), (33000000, 56000000),
+                   (76000000, 108000000), (108000000, 137000000),
+                   (137000000, 154000000), (420000000, 470000000)]
+
+    CHARSET = list(chirp_common.CHARSET_ASCII)
+    CHARSET.remove("\\")
+
+
+    _memsize = 6573
+    
+    # block 9 (130 Bytes long) is to be repeted 40 times
+    _block_lengths = [2, 40, 208, 208, 208, 208, 198, 53, 130, 118, 130]
+
+    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[16];
+        struct mem_struct vfob[16];
+        struct mem_struct home[4];
+        struct mem_struct qmb;
+        struct mem_struct mtqmb;
+        struct mem_struct mtune;
+
+        #seekto 0x431;
+        u8 visible[25];
+        u8 pmsvisible;
+
+        #seekto 0x44B;
+        u8 filled[25];
+        u8 pmsfilled;
+
+        #seekto 0x465;
+        struct mem_struct memory[200];
+        struct mem_struct pms[2];
+
+        #seekto 0x1903;
+        u8 callsign[7];
+
+        #seekto 0x19AD;
+        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": -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-L": -39,
+        "PMS-U": -38,
+    }
+    LAST_PMS_INDEX = -39
+
+    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 &lt;] and [MODE &gt;] keys while
+                 turning the radio on ("CLONE MODE" will appear on the
+                 display).
+            4. <b>After clicking OK</b>, 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 &lt;] and [MODE &gt;] 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:
+            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)
+
+        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, "
+                  "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
+
+
diff --git a/tests/images/Yaesu_FT-818.img b/tests/images/Yaesu_FT-818.img
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a86870c3805f17e0c98acb300906098efcb0871e
GIT binary patch
literal 6730
zc$~$UYit}>701V$gt$rUxDI(KhOU}Kg*2%wC)}hTT6P{gyR)-9GrOMM*<JDQYn!-^
zNq~Zz3WSl0l}}M5tO`K|rb_B0ZbB4V at en9RtpwuLR4Qr&ZoFGURiUb~1VTtugqd^S
z6EA!Lu^%EOznpW=J@<dkJ$G(iSCqwM|6?=Pw9Y^695{V#ouXVDh<=Lr>rj*tMae2k
zXY{wk^`xRa&~u0UhB#Rl^owMelg<Z^MI=dng3X32Ln0B@*{r47c-#7k#WrNuR7&(F
zkmSZ>8?txAYeV+^Xj&v{2P1hqrF6{9(FoEB#U*4W11Y1#Sfkj4ocq<ZNYu5=+aG=$
z{_*D_M%F0~CW*7jMP>bWla!3{*tY#~NX)^1=X_SNA?LHoI{%aNS;dB&&nkqR56jzK
z=R?=||D6wwpux$3eUhruJZf+6tQ0km;LX%r6agbVg4q}6q6k(amzKoX+W#7DPaCwf
zrYi;cEHxIDwm#OjCCd48%IOYeQ{;Z>D at L@90O at Wv4EAe4cEbj@KN}I-M0LLwlCg+<
z5XgyXuu+jUn4x4dkSPy{fz-gGq!-A{3V72|&UYx;DUbjaJcvM8=gcA|MoDF)OCSzX
zBNIY;1Y#S=_KPxk<}9`u%p1%Ga`5K!{g{|WiIJ at W33qB%VzFHT0R|<c)*ac~OE(|_
zq<elWwx7=7{rIJT5Gp1 at j3XsIPi^i2@|zTRd)QW8=4}U%-}T^i21|ZyKbVA3=i_->
zYT}(1(mZcCg4<Vt9CnRlEC-m_$k9qnAsZvIyzyDGj>@xxwe#*YUdOTQm^VtwgKjM9
z_mem~YJo at 4EfJ!Fb4+iyURz6Y;BwL>kU&GFL7S4TGQqkQyuI at TcnkMaC{fZakkF>A
zWhjC3x%JomU~@QQ<)a90hh#sFdT&-~4UC0KEarV;vwu6}Z5`J2;T&vLSchaZRAMp7
z$WAfW$X0!Mr^^q$LQkPsq$B~P_%5m7T3(ZMu>8_#QYD5_pyWe9ewM<qX(m5~Eg}hP
zA!AFrUjBI>cuS+Qr^@SKqH3tX6|xt|um4UWP)$|Z>8(4rN&RHgu?Vn at gK`CnkzRp>
zv1MyTsj?`EbWo7V&8s-0PLN5iN>o(v>+*hXAA{KXbDk}$myocJ3N_a)5xx$olI4{o
z>527XNvaY`EM)l=IoJQVNfciz%eB2K=@m$TazU(HQlg?-B`@W=+3_NH%b~1Zm3B%<
zfHFSsmK%U<$>Q@#S9Cd}&Gl!ojp^{J-!9mMv1xvc+^3LknTvI#=AI=|3#^Zlp4i;?
zLvjj2Y|yWp=p at moS!QQ?k3hm4q|HEHNBaoQXfd6YGfIe<QMyw?ROAoRs>l0T{^U6l
zfnR4m1WvA5`vzX8WSOM4sW(i*`&4t7%`PAfeA6N?@Z}!ToY*hvP4M>FPN+r|86LC2
zIr!3|)WvwWB*a0LOgXmB^o963bU&w`BzI%Z%S$swa~AGXJG at MC%%)QyL3m~HTV{ty
zq>N%EesdFvpi)c9<U8-+jOGR{MzBWDzk-RO4>AJ3=E5AAL(=i=yxc?X1veTUFzFRY
z*j<WQjS{T4-~IxxqiLX!2F+VyehUn!Hp;pgxq at 43p2iVWgb4-m)_Sy$^n+Z*l<Kr)
zy-u~m`_5*Gkh at V>hY%gPwkB0cYuLaxX`CG(_X0WoJ|_7*?=G7}q9Hvg*IesK(kY5&
ztXsCewM6 at l<I6iGBtW&;{nMJCy}SE&2_!(8#R;gl_5D1#OI&{cC?ZLtOj(y|n~<=F
z7-lR2>MdPz<o(cYDG|8MTGz>hlJm2%*a$JhCc<(6UAgtkpOC6Iyl6r>PuEGz1$!dx
z1mO%@XR3-~OI#Se3aZi%xtTERnFg);#D(YjBjTV;2F{dwdw%!RV3R?%U7_A~fkt<o
z1)?FhUZn(V<{t!ap%M*^MbHJLl!Dl3)eAPKJRCL2=qiNtQ6TGqyqdyQVr$%{S0b8`
zW at IanTTM({FBe+}6E-~w<ZYZcdU6fgDJWsp*r2tLn162?+mKoSvJG<GwMg=&)$8&)
z#3qM?opuX^&3yb!=l at 2slT|s{3FMziOnld3WQV8&6=kY5X&t5z<l8DG+XdpFl9yrL
zb_1EfQPU}n at 8{wJ3L###F6n at fXiltWB~@`^`MT`w(UUl%q+jzKTx>uD;mb&mo)RK?
zIYrc)9V}7;iof{QG&zNwnvoQ8>E0od1J^4{8Kop0KHE$wZ-lUWjz`T1dC&12P;Zi|
zdVqYIXvcN95*2}jcS+hVOG>1KzBPsYoEf60T{7heFbS(3&!a-re_5dwT&qaxT^I(v
zg}1Y+%Qg$&TjhMBIX>}1-wl%MLP?kQLrC}rEX2HB{`w7^w`ey~)P#hp%K2q!oy)Bi
zqMeLk%HA+xFJyjlSNePKI^^Bhpf?jG4$>{&M?pIa7U>~m7K3hiA9Tx4^^pkl=-DRL
z>4ko*(NOWMN^q}wqlXYx)1_Ki_(6*7w4P`61J!uti$ptqx*$hAe|_$50^Tk?gCjsD
zd<&T26?}Oig?Ad%`5oN;TMZ`P9+D;pA#W~Li10T1cN0FIAn8nQIs8qFZ%s18YA9QB
zDZ#G~JVDz1s?Fz+E#iED@&!JR;y2SnWU|vuuBvU44#YWe8>|zXAR9CXiHpA{JuzRc
zMr4T1y$95l=!0BHuy%$SDZb`K9}Ag{aB}p<&~^SUfmZ?fBEw#KLU!Ihf~WPvQF|la
zJ^<c!(_U5fEIGCT_%Q2ONAP#CVQQmzuTDp?MQ>UoM+r<%-Ww3LJnz69((Iq5lecL|
zRWkq8B&h}P_Mbm!-u)9k8n1urjQEN3{texIAKtS^QNG;%6MD=;=lwU at eMbLiPdjp8
zUpq4JdOPxQU-}q(XY|<k=T5#iK6zB>Xq?Io)Ps*rj2yJa-Enn12+ZT72g~DmT^%0@
z9-gQNQ}FMl<HaXjBYnskpE_zyIwyv!xvA>G`ti}vI44I24#EE^=!c%lKdw#Gt2MA6
wope55t2$3vkDoYdP1MVgeR<NH7!HmAADc8!4F~2I#`I7}x;6e- at sUUV1%Ck at rT_o{




More information about the chirp_devel mailing list