# HG changeset patch # User Mark Adams # Date 1451712875 18000 # Sat Jan 02 00:34:35 2016 -0500 # Node ID 0433bc5fd6d2e53509ae50a4a04f144755775c4a # Parent 8df79446b79c2e08e0988ab156a3b371623d656a [ft50] Add new driver to support Yaesu FT-50. Fixes #1233. diff -r 8df79446b79c -r 0433bc5fd6d2 chirp/drivers/ft50.py --- a/chirp/drivers/ft50.py Sat Dec 19 13:31:26 2015 -0800 +++ b/chirp/drivers/ft50.py Sat Jan 02 00:34:35 2016 -0500 @@ -1,4 +1,4 @@ -# Copyright 2010 Dan Smith +# Copyright 2011 Dan Smith # # 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 @@ -12,44 +12,620 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import time +import os +import logging +import re -from chirp.drivers import yaesu_clone, ft50_ll -from chirp import chirp_common, directory +from chirp.drivers import yaesu_clone +from chirp import chirp_common, directory, errors, bitwise, util +from chirp.settings import RadioSetting, RadioSettingGroup, \ + RadioSettingValueInteger, RadioSettingValueList, \ + RadioSettingValueBoolean, RadioSettingValueString, \ + RadioSettings +from textwrap import dedent +LOG = logging.getLogger(__name__) -# Not working, don't register -# @directory.register +MEM_FORMAT = """ +struct flag_struct { + u8 unknown1f:5, + skip:1, + mask:1, + used:1; +}; + +struct mem_struct { + u8 showname:1, + unknown1:3, + unknown2:2, + unknown3:2; + u8 ishighpower:1, + power:2, + unknown4:1, + tuning_step:4; + u8 codememno:4, + codeorpage:2, + duplex:2; + u8 tmode:2, + tone:6; + u8 unknown5:1, + dtcs:7; + u8 unknown6:6, + mode:2; + bbcd freq[3]; + bbcd offset[3]; + u8 name[4]; +}; + +#seekto 0x000C; +struct { + u8 extendedrx_flg; // Seems to be set to 03 when extended rx is enabled + u8 extendedrx; // Seems to be set to 01 when extended rx is enabled +} extendedrx_struct; // UNFINISHED!! + +#seekto 0x001A; +struct flag_struct flag[100]; + +#seekto 0x079C; +struct flag_struct flag_repeat[100]; + +#seekto 0x00AA; +struct mem_struct memory[100]; +struct mem_struct special[11]; + +#seekto 0x08C7; +struct { + u8 sub_display; + u8 unknown1s; + u8 apo; + u8 timeout; + u8 lock; + u8 rxsave; + u8 lamp; + u8 bell; + u8 cwid[16]; + u8 unknown2s; + u8 artsmode; + u8 artsbeep; + u8 unknown3s; + u8 unknown4s; + struct { + u8 header[3]; + u8 mem_num; + u8 digits[16]; + } autodial[8]; + struct { + u8 header[3]; + u8 mem_num; + u8 digits[32]; + } autodial9_ro; + bbcd pagingcodec_ro[2]; + bbcd pagingcodep[2]; + struct { + bbcd digits[2]; + } pagingcode[6]; + u8 code_dec_c_en:1, + code_dec_p_en:1, + code_dec_1_en:1, + code_dec_2_en:1, + code_dec_3_en:1, + code_dec_4_en:1, + code_dec_5_en:1, + code_dec_6_en:1; + u8 pagingspeed; + u8 pagingdelay; + u8 pagingbell; + u8 paginganswer; + + #seekto 0x0E32; + u8 rptl:1, // repeater input tracking + amod:1, // auto mode + scnl:1, // scan lamp + resm:1, // scan resume mode 0=5sec, 1=carr + ars:1, // automatic repeater shift + keybeep:1, // keypad beep + lck:1, // lock + unknown1c:1; + u8 lgt:1, + pageamsg:1, + unknown2c:1, + bclo:1, // Busy channel lock out + unknown3c:2, + cwid_en:1, // CWID off/on + tsav:1; // TX save + u8 unknown4c:4, + artssped:1, // ARTS/SPED: 0=15s, 1=25s + unknown5c:1, + rvhm:1, // RVHM: 0=home, 1=rev + mon:1; // MON: 0=mon, 1=tcal +} settings; + +#seekto 0x080E; +struct mem_struct vfo_mem[10]; + + + +""" + +# 10 VFO memories: A145, A220, A380, A430, A800, +# B145, B220, B380, B430, B800 + +DUPLEX = ["", "-", "+"] +MODES = ["FM", "AM", "WFM"] +SKIP_VALUES = ["", "S"] +TMODES = ["", "Tone", "TSQL", "DTCS"] +TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] +TONES = list(chirp_common.OLD_TONES) + +#CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ()+-=*/???|0123456789" +# the = displays as an underscored dash on radio +# the first ? is an uppercase delta - \xA7 +# the second ? is an uppercase gamma - \xD1 +# the thrid ? is an uppercase sigma - \xCF +NUMERIC_CHARSET = list("0123456789") +CHARSET = [str(x) for x in range(0, 10)] + \ + [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ + list(" ()+-=*/" + ("\x00" * 3) + "|") + NUMERIC_CHARSET +DTMFCHARSET = NUMERIC_CHARSET + list("ABCD*#") + +POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.0), + chirp_common.PowerLevel("L3", watts=2.5), + chirp_common.PowerLevel("L2", watts=1.0), + chirp_common.PowerLevel("L1", watts=0.1)] +SPECIALS = ["L1", "U1", "L2", "U2", "L3", "U3", "L4", "U4", "L5", "U5", "UNK"] + + +@directory.register class FT50Radio(yaesu_clone.YaesuCloneModeRadio): + """Yaesu FT-50""" BAUD_RATE = 9600 VENDOR = "Yaesu" MODEL = "FT-50" + _model = "" _memsize = 3723 _block_lengths = [10, 16, 112, 16, 16, 1776, 1776, 1] - _block_delay = 0.15 + #_block_delay = 0.15 + _block_size = 8 + + @classmethod + def get_prompts(cls): + rp = chirp_common.RadioPrompts() + rp.pre_download = _(dedent("""\ +1. Turn radio off. +2. Connect cable to MIC/SP jack. +3. Press and hold [PTT] & Knob while turning the + radio on. +4. After clicking OK, press the [PTT] switch to send image.""")) + rp.pre_upload = _(dedent("""\ +1. Turn radio off. +2. Connect cable to MIC/SP jack. +3. Press and hold [PTT] & Knob while turning the + radio on. +4. Press the [MONI] switch ("WAIT" will appear on the LCD).""")) + return rp def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (1, 100) + rf.valid_duplexes = DUPLEX + rf.valid_tmodes = TMODES + rf.valid_power_levels = POWER_LEVELS + rf.valid_tuning_steps = TUNING_STEPS + rf.valid_power_levels = POWER_LEVELS + rf.valid_characters = "".join(CHARSET) + rf.valid_name_length = 4 + rf.valid_modes = MODES + # Specials not yet implementd + #rf.valid_special_chans = SPECIALS + rf.valid_bands = [(76000000, 200000000), + (300000000, 540000000), + (590000000, 999000000)] + #rf.can_odd_split = True + rf.has_ctone = False + rf.has_bank = False + rf.has_settings = True rf.has_dtcs_polarity = False - rf.has_bank = False - rf.valid_modes = ["FM", "WFM", "AM"] + return rf - def _update_checksum(self): - ft50_ll.update_checksum(self._mmap) + def _checksums(self): + return [yaesu_clone.YaesuChecksum(0x0000, 0xE89)] + + def process_mmap(self): + self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): - return ft50_ll.get_raw_memory(self._mmap, number) + return repr(self._memobj.memory[number - 1]) def get_memory(self, number): - return ft50_ll.get_memory(self._mmap, number) + mem = chirp_common.Memory() + _mem = self._memobj.memory[number-1] + _flg = self._memobj.flag[number-1] + mem.number = number - def set_memory(self, number): - return ft50_ll.set_memory(self._mmap, number) + #if not _flg.visible: + # mem.empty = True + if not _flg.used: + mem.empty = True + return mem - def erase_memory(self, number): - return ft50_ll.erase_memory(self._mmap, number) + for i in _mem.name: + mem.name += CHARSET[i & 0x7F] + mem.name = mem.name.rstrip() - def filter_name(self, name): - return name[:4].upper() + mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) + mem.duplex = DUPLEX[_mem.duplex] + mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) + mem.rtone = mem.ctone = TONES[_mem.tone] + mem.tmode = TMODES[_mem.tmode] + mem.mode = MODES[_mem.mode] + mem.tuning_step = TUNING_STEPS[_mem.tuning_step] + mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] + # Power is stored as 2 bits to describe the 3 low power levels + # High power is determined by a different bit. + if not _mem.ishighpower: + mem.power = POWER_LEVELS[3 - _mem.power] + else: + mem.power = POWER_LEVELS[0] + mem.skip = SKIP_VALUES[_flg.skip] + + return mem + + def set_memory(self, mem): + _mem = self._memobj.memory[mem.number-1] + _flg = self._memobj.flag[mem.number-1] + _flg_repeat = self._memobj.flag_repeat[mem.number-1] + + if mem.empty: + _flg.used = False + self._wipe_memory_banks(mem) + return + + if (len(mem.name) == 0): + _mem.name = [0x24] * 4 + _mem.showname = 0 + else: + _mem.showname = 1 + for i in range(0, 4): + _mem.name[i] = CHARSET.index(mem.name.ljust(4)[i]) + + _mem.freq = int(mem.freq / 1000) + _mem.duplex = DUPLEX.index(mem.duplex) + _mem.offset = int(mem.offset / 1000) + _mem.mode = MODES.index(mem.mode) + _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step) + if mem.power: + if (mem.power == POWER_LEVELS[0]): + # The low power level is not changed when high power is selected + _mem.ishighpower = 0x01 + if (_mem.power == 3): + # Set low power to L3 (0x02) if it is set to 3 (new object default) + _mem.power = 0x02 + else: + _mem.ishighpower = 0x00 + _mem.power = 3 - POWER_LEVELS.index(mem.power) + else: + _mem.ishighpower = 0x01 + _mem.power = 0x02 + _mem.tmode = TMODES.index(mem.tmode) + try: + _mem.tone = TONES.index(mem.rtone) + except ValueError: + raise errors.UnsupportedToneError( + ("This radio does not support tone %s" % mem.rtone)) + _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) + + _flg.skip = SKIP_VALUES.index(mem.skip) + + # initialize new channel to safe defaults + if not mem.empty and not _flg.used: + _flg.used = True + _flg.mask = True # Mask = True to be visible on radio + _mem.unknown1 = 0x00 + _mem.unknown2 = 0x00 + _mem.unknown3 = 0x00 + _mem.unknown4 = 0x00 + _mem.unknown5 = 0x00 + _mem.unknown6 = 0x00 + _mem.codememno = 0x02 # Not implemented in chirp + _mem.codeorpage = 0x00 # Not implemented in chirp + + # Duplicate flags to repeated part in memory + _flg_repeat.skip = _flg.skip + _flg_repeat.mask = _flg.mask + _flg_repeat.used = _flg.used + + def _decode_cwid(self, inarr): + LOG.debug("@_decode_chars, type: %s" % type(inarr)) + LOG.debug(inarr) + outstr = "" + for i in inarr: + if i == 0xFF: + break + outstr += CHARSET[i & 0x7F] + LOG.debug(outstr) + return outstr.rstrip() + + def _encode_cwid(self, instr, length=16): + LOG.debug("@_encode_chars, type: %s" % type(instr)) + LOG.debug(instr) + outarr = [] + instr = str(instr) + for i in range(0, length): + if i < len(instr): + outarr.append(CHARSET.index(instr[i])) + else: + outarr.append(0xFF) + return outarr + + def get_settings(self): + _settings = self._memobj.settings + basic = RadioSettingGroup("basic", "Basic") + dtmf = RadioSettingGroup("dtmf", "DTMF Code & Paging") + arts = RadioSettingGroup("arts", "ARTS") + autodial = RadioSettingGroup("autodial", "AutoDial") + top = RadioSettings(basic, autodial, arts, dtmf) + + rs = RadioSetting( + "keybeep", "Keypad Beep", + RadioSettingValueBoolean(_settings.keybeep)) + basic.append(rs) + + rs = RadioSetting( + "scnl", "Scan Lamp", + RadioSettingValueBoolean(_settings.scnl)) + basic.append(rs) + + options = ["off", "30m", "1h", "3h", "5h", "8h"] + rs = RadioSetting( + "apo", "APO time (hrs)", + RadioSettingValueList(options, options[_settings.apo])) + basic.append(rs) + + options = ["off", "1m", "2.5m", "5m", "10m"] + rs = RadioSetting( + "timeout", "Time Out Timer", + RadioSettingValueList(options, options[_settings.timeout])) + basic.append(rs) + + options = ["key", "dial", "key+dial", "ptt", + "key+ptt", "dial+ptt", "all"] + rs = RadioSetting( + "lock", "Lock mode", + RadioSettingValueList(options, options[_settings.lock])) + basic.append(rs) + + options = ["off", "0.2", "0.3", "0.5", "1.0", "2.0"] + rs = RadioSetting( + "rxsave", "RX Save (sec)", + RadioSettingValueList(options, options[_settings.rxsave])) + basic.append(rs) + + options = ["5sec", "key", "tgl"] + rs = RadioSetting( + "lamp", "Lamp mode", + RadioSettingValueList(options, options[_settings.lamp])) + basic.append(rs) + + options = ["off", "1", "3", "5", "8", "rpt"] + rs = RadioSetting( + "bell", "Bell Repetitions", + RadioSettingValueList(options, options[_settings.bell])) + basic.append(rs) + + rs = RadioSetting( + "cwid_en", "CWID Enable", + RadioSettingValueBoolean(_settings.cwid_en)) + arts.append(rs) + + cwid = RadioSettingValueString( + 0, 16, self._decode_cwid(_settings.cwid.get_value())) + cwid.set_charset(CHARSET) + rs = RadioSetting("cwid", "CWID", cwid) + arts.append(rs) + + options = ["off", "rx", "tx", "trx"] + rs = RadioSetting( + "artsmode", "ARTS Mode", + RadioSettingValueList( + options, options[_settings.artsmode])) + arts.append(rs) + + options = ["off", "in range", "always"] + rs = RadioSetting( + "artsbeep", "ARTS Beep", + RadioSettingValueList(options, options[_settings.artsbeep])) + arts.append(rs) + + for i in range(0, 8): + dialsettings = _settings.autodial[i] + dialstr = "" + for c in dialsettings.digits: + if c < len(DTMFCHARSET): + dialstr += DTMFCHARSET[c] + dialentry = RadioSettingValueString(0, 16, dialstr) + dialentry.set_charset(DTMFCHARSET + list(" ")) + rs = RadioSetting("autodial" + str(i+1), + "AutoDial " + str(i+1), dialentry) + autodial.append(rs) + + dialstr = "" + for c in _settings.autodial9_ro.digits: + if c < len(DTMFCHARSET): + dialstr += DTMFCHARSET[c] + dialentry = RadioSettingValueString(0, 32, dialstr) + dialentry.set_mutable(False) + rs = RadioSetting("autodial9_ro", "AutoDial 9 (read only)", dialentry) + autodial.append(rs) + + options = ["50ms", "100ms"] + rs = RadioSetting( + "pagingspeed", "Paging Speed", + RadioSettingValueList(options, options[_settings.pagingspeed])) + dtmf.append(rs) + + options = ["250ms", "450ms", "750ms", "1000ms"] + rs = RadioSetting( + "pagingdelay", "Paging Delay", + RadioSettingValueList(options, options[_settings.pagingdelay])) + dtmf.append(rs) + + options = ["off", "1", "3", "5", "8", "rpt"] + rs = RadioSetting( + "pagingbell", "Paging Bell Repetitions", + RadioSettingValueList(options, options[_settings.pagingbell])) + dtmf.append(rs) + + options = ["off", "ans", "for"] + rs = RadioSetting( + "paginganswer", "Paging Answerback", + RadioSettingValueList(options, options[_settings.paginganswer])) + dtmf.append(rs) + + rs = RadioSetting( + "code_dec_c_en", "Paging Code C Decode Enable", + RadioSettingValueBoolean(_settings.code_dec_c_en)) + dtmf.append(rs) + + _str = str(bitwise.bcd_to_int(_settings.pagingcodec_ro)) + code = RadioSettingValueString(0, 3, _str) + code.set_charset(NUMERIC_CHARSET + list(" ")) + code.set_mutable(False) + rs = RadioSetting("pagingcodec_ro", "Paging Code C (read only)", code) + dtmf.append(rs) + + rs = RadioSetting( + "code_dec_p_en", "Paging Code P Decode Enable", + RadioSettingValueBoolean(_settings.code_dec_p_en)) + dtmf.append(rs) + + _str = str(bitwise.bcd_to_int(_settings.pagingcodep)) + code = RadioSettingValueString(0, 3, _str) + code.set_charset(NUMERIC_CHARSET + list(" ")) + rs = RadioSetting("pagingcodep", "Paging Code P", code) + dtmf.append(rs) + + for i in range(0, 6): + num = str(i+1) + name = "code_dec_" + num + "_en" + rs = RadioSetting( + name, "Paging Code " + num + " Decode Enable", + RadioSettingValueBoolean(getattr(_settings, name))) + dtmf.append(rs) + + _str = str(bitwise.bcd_to_int(_settings.pagingcode[i].digits)) + code = RadioSettingValueString(0, 3, _str) + code.set_charset(NUMERIC_CHARSET + list(" ")) + rs = RadioSetting("pagingcode" + num, "Paging Code " + num, code) + dtmf.append(rs) + + return top + + def set_settings(self, uisettings): + for element in uisettings: + if not isinstance(element, RadioSetting): + self.set_settings(element) + continue + if not element.changed(): + continue + try: + setting = element.get_name() + _settings = self._memobj.settings + if re.match('autodial\d', setting): + # set autodial fields + dtmfstr = str(element.value).strip() + newval = [] + for i in range(0, 16): + if i < len(dtmfstr): + newval.append(DTMFCHARSET.index(dtmfstr[i])) + else: + newval.append(0xFF) + LOG.debug(newval) + idx = int(setting[-1:]) - 1 + _settings = self._memobj.settings.autodial[idx] + _settings.digits = newval + continue + if (setting == "pagingcodep"): + bitwise.int_to_bcd(_settings.pagingcodep, int(element.value)) + continue + if re.match('pagingcode\d', setting): + idx = int(setting[-1:]) - 1 + bitwise.int_to_bcd(_settings.pagingcode[idx].digits, int(element.value)) + continue + newval = element.value + oldval = getattr(_settings, setting) + if setting == "cwid": + newval = self._encode_cwid(newval) + LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval)) + setattr(_settings, setting, newval) + except Exception, e: + LOG.debug(element.get_name()) + raise + + @classmethod + def match_model(cls, filedata, filename): + return len(filedata) == cls._memsize + + def sync_out(self): + self.update_checksums() + return _clone_out(self) + +def _clone_out(radio): + try: + return __clone_out(radio) + except Exception, e: + raise errors.RadioError("Failed to communicate with the radio: %s" % e) + + +def __clone_out(radio): + pipe = radio.pipe + block_lengths = radio._block_lengths + total_written = 0 + + def _status(): + status = chirp_common.Status() + status.msg = "Cloning to radio" + status.max = sum(block_lengths) + status.cur = total_written + radio.status_fn(status) + + start = time.time() + + blocks = 0 + pos = 0 + for block in radio._block_lengths: + blocks += 1 + data = radio.get_mmap()[pos:pos + block] + #LOG.debug(util.hexprint(data)) + + recvd = ""; + # Radio echos every block received + for byte in data: + time.sleep(0.01) + pipe.write(byte) + # flush & sleep so don't loose ack + pipe.flush() + time.sleep(0.01) + recvd += pipe.read(1) # chew the echo + #LOG.debug(util.hexprint(recvd)) + LOG.debug("Bytes sent: %i" % len(data)) + + # Radio does not ack last block + if (blocks < 8): + buf = pipe.read(block) + LOG.debug("ACK attempt: " + util.hexprint(buf)) + if buf and buf[0] != chr(yaesu_clone.CMD_ACK): + buf = pipe.read(block) + if not buf or buf[-1] != chr(yaesu_clone.CMD_ACK): + raise errors.RadioError("Radio did not ack block %i" % blocks) + + total_written += len(data) + _status() + pos += block + + pipe.read(pos) # Chew the echo if using a 2-pin cable + + LOG.debug("Clone completed in %i seconds" % (time.time() - start)) + diff -r 8df79446b79c -r 0433bc5fd6d2 chirp/drivers/ft50_ll.py --- a/chirp/drivers/ft50_ll.py Sat Dec 19 13:31:26 2015 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,319 +0,0 @@ -# Copyright 2010 Dan Smith -# -# 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 . - -from chirp import chirp_common, util, errors, memmap -import time -import logging - -LOG = logging.getLogger(__name__) - -ACK = chr(0x06) - -MEM_LOC_BASE = 0x00AB -MEM_LOC_SIZE = 16 - -POS_DUPLEX = 1 -POS_TMODE = 2 -POS_TONE = 2 -POS_DTCS = 3 -POS_MODE = 4 -POS_FREQ = 5 -POS_OFFSET = 9 -POS_NAME = 11 - -POS_USED = 0x079C - -CHARSET = [str(x) for x in range(0, 10)] + \ - [chr(x) for x in range(ord("A"), ord("Z")+1)] + \ - list(" ()+--*/???|0123456789") - - -def send(s, data): - s.write(data) - r = s.read(len(data)) - if len(r) != len(data): - raise errors.RadioError("Failed to read echo") - - -def read_exact(s, count): - data = "" - i = 0 - while len(data) < count: - if i == 3: - LOG.debug(util.hexprint(data)) - raise errors.RadioError("Failed to read %i (%i) from radio" % - (count, len(data))) - elif i > 0: - LOG.info("Retry %i" % i) - data += s.read(count - len(data)) - i += 1 - - return data - - -def download(radio): - data = "" - - radio.pipe.setTimeout(1) - - for block in radio._block_lengths: - LOG.debug("Doing block %i" % block) - if block > 112: - step = 16 - else: - step = block - for i in range(0, block, step): - # data += read_exact(radio.pipe, step) - chunk = radio.pipe.read(step*2) - LOG.debug("Length of chunk: %i" % len(chunk)) - data += chunk - LOG.debug("Reading %i" % i) - time.sleep(0.1) - send(radio.pipe, ACK) - if radio.status_fn: - status = chirp_common.Status() - status.max = radio._memsize - status.cur = len(data) - status.msg = "Cloning from radio" - radio.status_fn(status) - - r = radio.pipe.read(100) - send(radio.pipe, ACK) - LOG.debug("R: %i" % len(r)) - LOG.debug(util.hexprint(r)) - - LOG.debug("Got: %i Expecting %i" % (len(data), radio._memsize)) - - return memmap.MemoryMap(data) - - -def get_mem_offset(number): - return MEM_LOC_BASE + (number * MEM_LOC_SIZE) - - -def get_raw_memory(map, number): - pos = get_mem_offset(number) - return memmap.MemoryMap(map[pos:pos+MEM_LOC_SIZE]) - - -def get_freq(mmap): - khz = (int("%02x" % (ord(mmap[POS_FREQ])), 10) * 100000) + \ - (int("%02x" % ord(mmap[POS_FREQ+1]), 10) * 1000) + \ - (int("%02x" % ord(mmap[POS_FREQ+2]), 10) * 10) - return khz / 10000.0 - - -def set_freq(mmap, freq): - val = util.bcd_encode(int(freq * 1000), width=6)[:3] - mmap[POS_FREQ] = val - - -def get_tmode(mmap): - val = ord(mmap[POS_TMODE]) & 0xC0 - - tmodemap = { - 0x00: "", - 0x40: "Tone", - 0x80: "TSQL", - 0xC0: "DTCS", - } - - return tmodemap[val] - - -def set_tmode(mmap, tmode): - val = ord(mmap[POS_TMODE]) & 0x3F - - tmodemap = { - "": 0x00, - "Tone": 0x40, - "TSQL": 0x80, - "DTCS": 0xC0, - } - - val |= tmodemap[tmode] - - mmap[POS_TMODE] = val - - -def get_tone(mmap): - val = ord(mmap[POS_TONE]) & 0x3F - - return chirp_common.TONES[val] - - -def set_tone(mmap, tone): - val = ord(mmap[POS_TONE]) & 0xC0 - - mmap[POS_TONE] = val | chirp_common.TONES.index(tone) - - -def get_dtcs(mmap): - val = ord(mmap[POS_DTCS]) - - return chirp_common.DTCS_CODES[val] - - -def set_dtcs(mmap, dtcs): - mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(dtcs) - - -def get_offset(mmap): - khz = (int("%02x" % ord(mmap[POS_OFFSET]), 10) * 10) + \ - (int("%02x" % (ord(mmap[POS_OFFSET+1]) >> 4), 10) * 1) - - return khz / 1000.0 - - -def set_offset(mmap, offset): - val = util.bcd_encode(int(offset * 1000), width=4)[:3] - LOG.debug("Offset:\n%s" % util.hexprint(val)) - mmap[POS_OFFSET] = val - - -def get_duplex(mmap): - val = ord(mmap[POS_DUPLEX]) & 0x03 - - dupmap = { - 0x00: "", - 0x01: "-", - 0x02: "+", - 0x03: "split", - } - - return dupmap[val] - - -def set_duplex(mmap, duplex): - val = ord(mmap[POS_DUPLEX]) & 0xFC - - dupmap = { - "": 0x00, - "-": 0x01, - "+": 0x02, - "split": 0x03, - } - - mmap[POS_DUPLEX] = val | dupmap[duplex] - - -def get_name(mmap): - name = "" - for x in mmap[POS_NAME:POS_NAME+4]: - if ord(x) >= len(CHARSET): - break - name += CHARSET[ord(x)] - return name - - -def set_name(mmap, name): - val = "" - for i in name[:4].ljust(4): - val += chr(CHARSET.index(i)) - mmap[POS_NAME] = val - - -def get_mode(mmap): - val = ord(mmap[POS_MODE]) & 0x03 - - modemap = { - 0x00: "FM", - 0x01: "AM", - 0x02: "WFM", - 0x03: "WFM", - } - - return modemap[val] - - -def set_mode(mmap, mode): - val = ord(mmap[POS_MODE]) & 0xCF - - modemap = { - "FM": 0x00, - "AM": 0x01, - "WFM": 0x02, - } - - mmap[POS_MODE] = val | modemap[mode] - - -def get_used(mmap, number): - return ord(mmap[POS_USED + number]) & 0x01 - - -def set_used(mmap, number, used): - val = ord(mmap[POS_USED + number]) & 0xFC - if used: - val |= 0x03 - mmap[POS_USED + number] = val - - -def get_memory(map, number): - index = number - 1 - mmap = get_raw_memory(map, index) - - mem = chirp_common.Memory() - mem.number = number - if not get_used(map, index): - mem.empty = True - return mem - - mem.freq = get_freq(mmap) - mem.tmode = get_tmode(mmap) - mem.rtone = mem.ctone = get_tone(mmap) - mem.dtcs = get_dtcs(mmap) - mem.offset = get_offset(mmap) - mem.duplex = get_duplex(mmap) - mem.name = get_name(mmap) - mem.mode = get_mode(mmap) - - return mem - - -def set_memory(_map, mem): - index = mem.number - 1 - mmap = get_raw_memory(_map, index) - - if not get_used(_map, index): - mmap[0] = ("\x00" * MEM_LOC_SIZE) - - set_freq(mmap, mem.freq) - set_tmode(mmap, mem.tmode) - set_tone(mmap, mem.rtone) - set_dtcs(mmap, mem.dtcs) - set_offset(mmap, mem.offset) - set_duplex(mmap, mem.duplex) - set_name(mmap, mem.name) - set_mode(mmap, mem.mode) - - _map[get_mem_offset(index)] = mmap.get_packed() - set_used(_map, index, True) - - return _map - - -def erase_memory(map, number): - set_used(map, number-1, False) - return map - - -def update_checksum(map): - cs = 0 - for i in range(0, 3722): - cs += ord(map[i]) - cs %= 256 - LOG.debug("Checksum old=%02x new=%02x" % (ord(map[3722]), cs)) - map[3722] = cs