Пример #1
0
 def _is_orig(self):
     version_tag = _firmware_version_from_image(self)
     if CHIRP_DEBUG:
         print "@_is_orig, version_tag:", util.hexprint(version_tag)
     try:
         if 'BFB' in version_tag:
             idx = version_tag.index("BFB") + 3
             version = int(version_tag[idx:idx + 3])
             return version < 291
         if any(tag in version_tag
                for tag in ['BF82', 'B82', 'USA', 'BJ55']):
             return False
     except:
         pass
     raise errors.RadioError("Unable to parse version string %s" %
                             version_tag)
Пример #2
0
Файл: th350.py Проект: v0l/chirp
def do_download(radio):
    do_ident(radio)
    data = "TH350 Radio Program data v1.08\x00\x00"
    data += ("\x00" * 16)
    firstack = None
    for i in range(0, 0x1000, 16):
        frame = struct.pack(">cHB", "R", i, 16)
        radio.pipe.write(frame)
        result = radio.pipe.read(20)
        if frame[1:4] != result[1:4]:
            LOG.debug(util.hexprint(result))
            raise errors.RadioError("Invalid response for address 0x%04x" % i)
        data += result[4:]
        do_status(radio, "from", i)

    return memmap.MemoryMap(data)
Пример #3
0
def _h777_write_block(radio, block_addr, block_size):
    serial = radio.pipe

    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
    data = radio.get_mmap()[block_addr:block_addr + 8]

    LOG.debug("Writing Data:")
    LOG.debug(util.hexprint(cmd + data))

    try:
        serial.write(cmd + data)
        if serial.read(1) != CMD_ACK:
            raise Exception("No ACK")
    except:
        raise errors.RadioError("Failed to send block "
                                "to radio at %04x" % block_addr)
Пример #4
0
    def _write_block(self, block_addr, block_size):

        cmd = struct.pack('>cHb', self.CMD_WRITE, block_addr, block_size)
        data = self.get_mmap()[block_addr:block_addr + 8]

        LOG.debug('Writing Data:\n%s%s', util.hexprint(cmd),
                  util.hexprint(data))

        try:
            self._write(cmd + data)
            if self._read(1) != self.CMD_ACK:
                raise Exception('No ACK')
        except Exception:
            msg = 'Failed to send block to radio at %04x' % block_addr
            LOG.debug(msg, exc_info=True)
            raise errors.RadioError(msg)
Пример #5
0
    def _detect_baud(self):
        for baud in [9600, 19200, 38400, 57600]:
            self.pipe.baudrate = baud
            try:
                self.pipe.write("\r\r")
            except:
                break
            self.pipe.read(32)
            try:
                id = self.get_id()
                LOG.info("Radio %s at %i baud" % (id, baud))
                return True
            except errors.RadioError:
                pass

        raise errors.RadioError("No response from radio")
Пример #6
0
def _upload(radio):
    """Upload procedure"""
    # put radio in program mode
    _ident_radio(radio)

    # identify radio
    radio_ident = _get_radio_firmware_version(radio)
    LOG.info("Radio firmware version:")
    LOG.debug(util.hexprint(radio_ident))
    # identify image
    image_ident = _get_image_firmware_version(radio)
    LOG.info("Image firmware version:")
    LOG.debug(util.hexprint(image_ident))

    if radio_ident != "0xFF" * 16 and image_ident == radio_ident:
        _ranges = radio._ranges
    else:
        _ranges = [(0x0000, 0x0DF0), (0x0E00, 0x1800)]

    # UI progress
    status = chirp_common.Status()
    status.cur = 0
    status.max = radio._mem_size / radio._send_block_size
    status.msg = "Cloning to radio..."
    radio.status_fn(status)

    # the fun start here
    for start, end in _ranges:
        for addr in range(start, end, radio._send_block_size):
            # sending the data
            data = radio.get_mmap()[addr:addr + radio._send_block_size]

            frame = _make_frame("X", addr, radio._send_block_size, data)

            _rawsend(radio, frame)
            time.sleep(0.05)

            # receiving the response
            ack = _rawrecv(radio, 1)
            if ack != "\x06":
                msg = "Bad ack writing block 0x%04x" % addr
                raise errors.RadioError(msg)

            # UI Update
            status.cur = addr / radio._send_block_size
            status.msg = "Cloning to radio..."
            radio.status_fn(status)
Пример #7
0
def _download(radio):
    """Get the memory map"""
    # put radio in program mode
    ident = _ident_radio(radio)

    # UI progress
    status = chirp_common.Status()
    status.cur = 0
    status.max = radio._mem_size / radio._recv_block_size
    status.msg = "Cloning from radio..."
    radio.status_fn(status)

    data = ""
    for addr in range(0, radio._mem_size, radio._recv_block_size):
        frame = _make_frame("R", addr, radio._recv_block_size)
        # DEBUG
        LOG.info("Request sent:")
        LOG.debug(util.hexprint(frame))

        # sending the read request
        _rawsend(radio, frame)

        # now we read
        d = _recv(radio, addr, radio._recv_block_size)

        time.sleep(0.05)

        _rawsend(radio, "\x06")

        ack = _rawrecv(radio, 1)
        if ack != "\x06":
            raise errors.RadioError("Radio refused to send block 0x%04x" %
                                    addr)

        ####time.sleep(0.05)

        # aggregate the data
        data += d

        # UI Update
        status.cur = addr / radio._recv_block_size
        status.msg = "Cloning from radio..."
        radio.status_fn(status)

    data += radio.MODEL.ljust(8)

    return data
Пример #8
0
 def do_getcanadacounties(self):
     try:
         service = self._client.service
         provincelist = service.getCountryInfo(2, self._auth)
         provinces = {}
         clist = []
         for province in provincelist:
             provinces[province[2]] = province[0]
             provinceinfo = service.getStateInfo(province[0], self._auth)
             for county in provinceinfo.countyList:
                 if (county[1] != 'UNKNOWN' and county[1] != 'N/A'
                         and county[1] != 'Provincewide'):
                     # some counties are actually cities but whatever fml
                     clist.append(
                         [province[0], province[2], county[0], county[1]])
     except WebFault, err:
         raise errors.RadioError(err)
Пример #9
0
 def _ip620_read_block(self, block_addr):
     _cmd = struct.pack(">cHb", 'R', block_addr, READ_BLOCK_SIZE)
     _expectedresponse = "W" + _cmd[1:]
     LOG.debug("Reading block %04x..." % (block_addr))
     try:
         self.pipe.write(_cmd)
         _response = self.pipe.read(4 + READ_BLOCK_SIZE)
         if _response[:4] != _expectedresponse:
             raise Exception("Error reading block %04x." % (block_addr))
         _block_data = _response[4:]
         self.pipe.write(CMD_ACK)
         _ack = self.pipe.read(1)
     except:
         raise errors.RadioError("Failed to read block at %04x" % block_addr)
     if _ack != CMD_ACK:
         raise Exception("No ACK reading block %04x." % (block_addr))
     return _block_data
Пример #10
0
def download(radio):
    if command(radio.pipe, "0M PROGRAM") != "0M":
        raise errors.RadioError("No response from radio")

    data = ""
    for i in range(0, 0x7F):
        data += read_block(radio.pipe, i)
        if radio.status_fn:
            s = chirp_common.Status()
            s.msg = "Cloning from radio"
            s.max = 256 * 0x7E
            s.cur = len(data)
            radio.status_fn(s)

    radio.pipe.write("E")

    return memmap.MemoryMap(data)
Пример #11
0
def get_id(ser):
    """Get the ID of the radio attached to @ser"""
    global LAST_BAUD
    bauds = [9600, 19200, 38400, 57600]
    bauds.remove(LAST_BAUD)
    bauds.insert(0, LAST_BAUD)

    for i in bauds:
        print "Trying ID at baud %i" % i
        ser.setBaudrate(i)
        ser.write("\r")
        ser.read(25)
        resp = command(ser, "ID")
        if " " in resp:
            LAST_BAUD = i
            return resp.split(" ")[1]

    raise errors.RadioError("No response from radio")
Пример #12
0
    def load(self, filename=None):
        if filename is None and self._filename is None:
            raise errors.RadioError("Need a location to load from")

        if filename:
            self._filename = filename

        self._blank()

        f = file(self._filename, "rU")
        header = f.readline().strip()

        f.seek(0, 0)
        reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"')

        good = 0
        lineno = 0
        for line in reader:
            lineno += 1
            if lineno == 1:
                header = line
                self.file_has_rTone = "rToneFreq" in header
                self.file_has_cTone = "cToneFreq" in header
                continue

            if len(header) > len(line):
                LOG.error("Line %i has %i columns, expected %i", lineno,
                          len(line), len(header))
                self.errors.append("Column number mismatch on line %i" %
                                   lineno)
                continue

            try:
                mem = self._parse_csv_data_line(header, line)
                if mem.number is None:
                    raise Exception("Invalid Location field" % lineno)
            except Exception, e:
                LOG.error("Line %i: %s", lineno, e)
                self.errors.append("Line %i: %s" % (lineno, e))
                continue

            self._grow(mem.number)
            self.memories[mem.number] = mem
            good += 1
Пример #13
0
    def sync_in(self):
        """Download from radio"""
        try:
            _connect_radio(self)
            data = _read_mem(self)
            data += _read_settings(self)
        except errors.RadioError:
            # Pass through any real errors we raise
            raise
        except Exception:
            # 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()
        return
Пример #14
0
def variable_len_resp(pipe):
    """
    when length of expected reply is not known, read byte at a time
    until the ack character is found.
    """
    response = b""
    i = 0
    toolong = 256  # arbitrary
    while True:
        b = pipe.read(1)
        if b == b'\x06':
            break
        else:
            response += b
            i += 1
            if i > toolong:
                LOG.debug("Response too long. got" + util.hexprint(response))
                raise errors.RadioError("Response from radio too long.")
    return (response)
Пример #15
0
def _recv(radio, addr, length):
    """Get data from the radio """
    # read 4 bytes of header
    hdr = _rawrecv(radio, 4)

    # read data
    data = _rawrecv(radio, length)

    # DEBUG
    LOG.info("Response:")
    LOG.debug(util.hexprint(hdr + data))

    c, a, l = struct.unpack(">BHB", hdr)
    if a != addr or l != length or c != ord("X"):
        LOG.error("Invalid answer for block 0x%04x:" % addr)
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
        raise errors.RadioError("Unknown response from the radio")

    return data
Пример #16
0
def _h777_write_block(radio, block_addr, block_size):
    serial = radio.pipe

    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
    data = radio.get_mmap()[block_addr:block_addr + 8]

    LOG.debug("Writing Data:")
    LOG.debug(util.hexprint(cmd + data))

    try:
        serial.write(cmd + data)
        # Time required to write data blocks varies between individual
        # radios of the Baofeng BF-888S model. The longest seen is
        # ~0.31s.
        if serial.read(1) != CMD_ACK:
            raise Exception("No ACK")
    except:
        raise errors.RadioError("Failed to send block "
                                "to radio at %04x" % block_addr)
Пример #17
0
    def set_memory(self, mem):
        _mem = self._memobj.memories[mem.number]
        _nam = self._memobj.names[mem.number]

        if mem.empty:
            _mem.set_raw(b'\xff' * 16)
            _nam.set_raw(b'\xff' * 16)
            return

        if int(_mem.rxfreq) == 166666665:
            LOG.debug('Initializing new memory %i' % mem.number)
            _mem.set_raw(b'\x00' * 16)

        _nam.name = mem.name.ljust(10)

        _mem.rxfreq = mem.freq // 10
        if mem.duplex == '':
            _mem.txfreq = mem.freq // 10
        elif mem.duplex == 'split':
            _mem.txfreq = mem.offset // 10
        elif mem.duplex == 'off':
            _mem.txfreq.set_raw(b'\xff\xff\xff\xff')
        elif mem.duplex == '-':
            _mem.txfreq = (mem.freq - mem.offset) // 10
        elif mem.duplex == '+':
            _mem.txfreq = (mem.freq + mem.offset) // 10
        else:
            raise errors.RadioError('Unsupported duplex mode %r' % mem.duplex)

        txtone, rxtone = chirp_common.split_tone_encode(mem)
        LOG.debug('tx tone is %s' % repr(txtone))
        LOG.debug('rx tone is %s' % repr(rxtone))
        _mem.txtone = self._encode_tone(*txtone)
        _mem.rxtone = self._encode_tone(*rxtone)

        try:
            _mem.power = POWER_LEVELS.index(mem.power)
        except ValueError:
            _mem.power = 0
        _mem.narrow = mem.mode == 'NFM'
        _mem.scan = mem.skip != 'S'
        if mem.extra:
            self._set_extra(_mem, mem)
Пример #18
0
def do_download(radio):
    '''Download memories from the radio'''

    # Get the serial port connection
    serial = radio.pipe

    try:
        enter_program_mode(radio)

        memory_data = bytes()

        # status info for the UI
        status = chirp_common.Status()
        status.cur = 0
        status.max = (MEMORY_ADDRESS_RANGE[1] -
                      MEMORY_ADDRESS_RANGE[0]) / MEMORY_RW_BLOCK_SIZE
        status.msg = 'Cloning from radio...'
        radio.status_fn(status)

        for addr in range(MEMORY_ADDRESS_RANGE[0],
                          MEMORY_ADDRESS_RANGE[1] + MEMORY_RW_BLOCK_SIZE,
                          MEMORY_RW_BLOCK_SIZE):
            read_command = struct.pack('>BHB', 0x52, addr,
                                       MEMORY_RW_BLOCK_SIZE)
            read_response = send_serial_command(serial, read_command,
                                                MEMORY_RW_BLOCK_CMD_SIZE)
            # LOG.debug('read response:\n%s' % util.hexprint(read_response))

            address, data, valid = parse_read_response(read_response)
            memory_data += data

            # update UI
            status.cur = (addr - MEMORY_ADDRESS_RANGE[0])\
                / MEMORY_RW_BLOCK_SIZE
            radio.status_fn(status)

        exit_program_mode(radio)
    except errors.RadioError as e:
        raise e
    except Exception as e:
        raise errors.RadioError('Failed to download from radio: %s' % e)

    return memmap.MemoryMapBytes(memory_data)
Пример #19
0
    def do_fetch(self):
        """Fetches frequencies for all subcategories in a county."""
        self._freqs = []

        try:
            service = self._client.service
            zipcode = service.getZipcodeInfo(self._zip, self._auth)
            county = service.getCountyInfo(zipcode.ctid, self._auth)
        except WebFault as err:
            raise errors.RadioError(err)

        status = chirp_common.Status()
        status.max = 0
        for cat in county.cats:
            status.max += len(cat.subcats)
        status.max += len(county.agencyList)

        for cat in county.cats:
            LOG.debug("Fetching category:", cat.cName)
            for subcat in cat.subcats:
                LOG.debug("\t", subcat.scName)
                result = self._client.service.getSubcatFreqs(
                    subcat.scid, self._auth)
                self._freqs += result
                status.cur += 1
                self.status_fn(status)
        status.max -= len(county.agencyList)
        for agency in county.agencyList:
            agency = self._client.service.getAgencyInfo(agency.aid, self._auth)
            for cat in agency.cats:
                status.max += len(cat.subcats)
            for cat in agency.cats:
                LOG.debug("Fetching category:", cat.cName)
                for subcat in cat.subcats:
                    try:
                        LOG.debug("\t", subcat.scName)
                    except AttributeError:
                        pass
                    result = self._client.service.getSubcatFreqs(
                        subcat.scid, self._auth)
                    self._freqs += result
                    status.cur += 1
                    self.status_fn(status)
Пример #20
0
def _upload(radio):
    cur = 0
    for block in radio._block_lengths:
        for _i in range(0, block, radio._block_size):
            length = min(radio._block_size, block)
            # LOG.debug("i=%i length=%i range: %i-%i" %
            #           (i, length, cur, cur+length))
            _send(radio.pipe, radio.get_mmap()[cur:cur + length])
            if radio.pipe.read(1) != ACK:
                raise errors.RadioError("Radio did not ack block at %i" % cur)
            cur += length
            time.sleep(0.05)

            if radio.status_fn:
                status = chirp_common.Status()
                status.cur = cur
                status.max = radio.get_memsize()
                status.msg = "Cloning to radio"
                radio.status_fn(status)
Пример #21
0
def _upload(radio):
    _identify(radio)
    for i in range(0, 0x2000, 0x40):
        msg = struct.pack('>cHb', 'W', i, 0x40)
        msg += radio._mmap[i:(i + 0x40)]
        radio.pipe.write(msg)
        ack = radio.pipe.read(1)
        if ack != '\x06':
            raise errors.RadioError('Radio did not ACK block %i (0x%04x)' % (
                i, i))

        if radio.status_fn:
            status = chirp_common.Status()
            status.cur = i
            status.max = 0x2000
            status.msg = "Cloning from radio"
            radio.status_fn(status)

    radio.pipe.write("E")
Пример #22
0
    def get_memory(self, number):
        print "Getting %i" % number
        f = self._classes["mem"]()
        f.set_location(number)
        self._send_frame(f)

        mem = chirp_common.Memory()
        mem.number = number

        f = self._recv_frame(f)
        if len(f.get_data()) == 0:
            raise errors.RadioError("Radio reported error")
        if f.get_data() and f.get_data()[-1] == "\xFF":
            mem.empty = True
            return mem

        memobj = f.get_obj()
        print repr(memobj)

        mem.freq = int(memobj.freq)
        mem.mode = self._rf.valid_modes[memobj.mode]

        if self._rf.has_name:
            mem.name = str(memobj.name).rstrip()

        if self._rf.valid_tmodes:
            mem.tmode = self._rf.valid_tmodes[memobj.tmode]

        if self._rf.has_dtcs:
            # FIXME
            mem.dtcs = bitwise.bcd_to_int([memobj.dtcs])

        if "Tone" in self._rf.valid_tmodes:
            mem.rtone = int(memobj.rtone) / 10.0

        if "TSQL" in self._rf.valid_tmodes and self._rf.has_ctone:
            mem.ctone = int(memobj.ctone) / 10.0

        if self._rf.valid_duplexes:
            mem.duplex = self._rf.valid_duplexes[memobj.duplex]

        return mem
Пример #23
0
def _r2_write_block(radio, block_addr, block_size):
    serial = radio.pipe

    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
    data = radio.get_mmap()[block_addr:block_addr + block_size]

    LOG.debug("Writing block %04x..." % (block_addr))
    LOG.debug(util.hexprint(cmd + data))

    try:
        for j in range(0, len(cmd)):
            serial.write(cmd[j])
        for j in range(0, len(data)):
            serial.write(data[j])
        if serial.read(1) != CMD_ACK:
            raise Exception("No ACK")
    except:
        _r2_exit_programming_mode(radio)
        raise errors.RadioError("Failed to send block "
                                "%04x to radio" % block_addr)
Пример #24
0
def do_upload(radio):
    radio.pipe.parity = "E"
    radio.pipe.timeout = 1
    do_ident(radio)

    for addr in range(0, 0x0400, 8):
        eaddr = addr + 16
        send(radio, make_frame("W", addr, 8, radio._mmap[eaddr:eaddr + 8]))
        ack = radio.pipe.read(1)
        if ack != "\x06":
            raise errors.RadioError("Radio refused block at %04x" % addr)
        radio.pipe.write("\x06")

        status = chirp_common.Status()
        status.cur = addr
        status.max = 0x0400
        status.msg = "Cloning to radio"
        radio.status_fn(status)

    radio.pipe.write("\x45")
Пример #25
0
def _read_mem(radio):
    """Get the memory map"""
    global BEEPVOL
    # UI progress
    status = chirp_common.Status()
    status.cur = 0
    status.max = radio._upper + 10  # 10 P chans
    status.msg = "Reading Channel Memory..."
    radio.status_fn(status)

    result0 = command(radio.pipe, "EX0120000", 12, W8S)
    BEEPVOL = int(result0[6:12])
    result0 = command(radio.pipe, "EX01200000", 0, W8L)   # Silence beeps
    data = ""
    mrlen = 41      # Expected fixed return string length
    for chn in range(0, (radio._upper + 11)):   # Loop stops at +10
        # Request this mem chn
        r0ch = 999
        r1ch = r0ch
        # return results can come back out of order
        while (r0ch != chn):
            # simplex
            result0 = command(radio.pipe, "MR0%03i" % chn,
                              mrlen, W8S)
            result0 += read_str(radio)
            r0ch = int(result0[3:6])
        while (r1ch != chn):
            # split
            result1 = command(radio.pipe, "MR1%03i" % chn,
                              mrlen, W8S)
            result1 += read_str(radio)
            r1ch = int(result1[3:6])
        data += radio._parse_mem_spec(result0, result1)
        # UI Update
        status.cur = chn
        status.msg = "Reading Channel Memory..."
        radio.status_fn(status)

    if len(data) == 0:       # To satisfy run_tests
        raise errors.RadioError('No data received.')
    return data
Пример #26
0
def do_upload(radio):
    do_ident(radio)
    data = radio._mmap[0x0030:]

    for i in range(0, 0x1000, 16):
        frame = struct.pack(">cHB", "W", i, 16)
        frame += data[i:i + 16]
        radio.pipe.write(frame)
        ack = radio.pipe.read(1)
        if ack != "\x06":
            # UV-B5/UV-B6 radios with 27 menus do not support service settings
            # and will stop ACKing when the upload reaches 0x0F10
            if i == 0x0F10:
                # must be a radio with 27 menus detected - stop upload
                break
            else:
                LOG.debug("Radio NAK'd block at address 0x%04x" % i)
                raise errors.RadioError("Radio NAK'd block at address 0x%04x" %
                                        i)
        LOG.debug("Radio ACK'd block at address 0x%04x" % i)
        do_status(radio, "to", i)
Пример #27
0
def do_download(radio):
    do_ident(radio)

    data = ""
    data += "\xFF" * (0 - len(data))
    for addr in range(0, radio._memsize, 0x10):
        send(radio, make_frame("R", addr, chr(0x10)))
        _addr, _data = recv(radio)
        if _addr != addr:
            raise errors.RadioError("Radio sent unexpected address")
        data += _data

        status = chirp_common.Status()
        status.cur = addr
        status.max = radio._memsize
        status.msg = "Cloning from radio"
        radio.status_fn(status)

    finish(radio)

    return memmap.MemoryMap(data)
Пример #28
0
def do_download(radio):
    """
    Read memory from the radio.
      call startcomms to go into program mode and check version
      create an mmap
      read the memory blocks and place the data into the mmap
      send "END"
    """
    image = bytearray(radio.get_memsize())
    pipe = radio.pipe  # Get the serial port connection
    progressbar = startcomms(radio, "from")
    for blocknum in range(radio.numblocks):
        for i in range(0, 3):
            if getblock(pipe, 16 * blocknum, image):
                break
            if i == 2:
                raise errors.RadioError("read block from radio failed 3 times")
        progressbar.cur = blocknum
        radio.status_fn(progressbar)
    sendcmd(pipe, b"END", 0)
    return memmap.MemoryMap(bytes(image))
Пример #29
0
def _recv_block(radio, addr, blocksize):
    """Receive a block from the radio ROM"""
    _rawsend(radio, _make_frame("R", addr, blocksize))

    # read 4 bytes of header
    hdr = _rawrecv(radio, 4)

    # read data
    data = _rawrecv(radio, blocksize)

    # DEBUG
    LOG.debug("Response:")
    LOG.debug("\n " + util.hexprint(data))

    c, a, l = struct.unpack(">BHB", hdr)
    if a != addr or l != blocksize or c != ord("R"):
        LOG.error("Invalid answer for block 0x%04x:" % addr)
        LOG.error("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
        raise errors.RadioError("Unknown response from the radio")

    return data
Пример #30
0
def _h777_enter_programming_mode(radio):
    serial = radio.pipe

    try:
        serial.write(b"\x02")
        time.sleep(0.1)
        serial.write(b"PROGRAM")
        ack = serial.read(1)
    except:
        raise errors.RadioError("Error communicating with radio")

    if not ack:
        raise errors.RadioError("No response from radio")
    elif ack != CMD_ACK:
        raise errors.RadioError("Radio refused to enter programming mode")

    original_timeout = serial.timeout
    try:
        serial.write(b"\x02")
        # At least one version of the Baofeng BF-888S has a consistent
        # ~0.33s delay between sending the first five bytes of the
        # version data and the last three bytes. We need to raise the
        # timeout so that the read doesn't finish early.
        serial.timeout = 0.5
        ident = serial.read(8)
    except:
        raise errors.RadioError("Error communicating with radio")
    finally:
        serial.timeout = original_timeout

    if not ident.startswith(b"P3107"):
        LOG.debug(util.hexprint(ident))
        raise errors.RadioError("Radio returned unknown identification string")

    try:
        serial.write(CMD_ACK)
        ack = serial.read(1)
    except:
        raise errors.RadioError("Error communicating with radio")

    if ack != CMD_ACK:
        raise errors.RadioError("Radio refused to enter programming mode")