Thanks for your reply!

On 2013-02-19 10:14, Tom Hayward wrote:
...
A new file is a change to Chirp. After adding the new file, Chirp is
not the same as it was before. But that's not the point here...

I understand that.  In CVS, to add a new file, I do "cvs add <filename".  I presume in Mercurial I do "hg add chirp\id51py" (which I did), which would be added to the repository by a commit.  However, if I do the commit, Chirp's web site seems to imply that creates a fork that creates problems, so I haven't done that.


... "hg status -mar" does nothing to attribute the changes to you, or label them with an appropriate commit message.

I understand that "hg status -mar" is the same as "cvs upd";  ie, just lists the files that have changed or been added, and will be committed on the next commit.


Our goal here isn't just to add new lines of code, but to document who added them and why, so that future readers of the code can ask "what was the guy who wrote this thinking??" and have both the explanation (commit message) and name/email of the guy who wrote it. If we find that your change introduces a regression, we are also easily able to search for commit messages containing "id?1" and revert each as needed until the regression disappears.
hg diff:
For this all to work, you must use a different command, "hg qnew ..."
to create your patch. You will be prompted to enter a commit message,
and when you export it ("hg export tip") your name and email address
will be automatically be added to the header. The commands to
accomplish this are all listed here:

http://chirp.danplanet.com/projects/chirp/wiki/DevelopersProcess#Soft-committing-a-Change

So far, I've been sent that link THREE TIMES.  Each time I have read it IN DETAIL.  I don't want to start an argument here, BUT IT IS FAR FROM CLEAR.  The Mercurial website documentation is poorer;  I had to Google to find the format of the configuration file (named ".hg/hgrc", not ".hg/config" as the Chirp website indicates).  Note that I have worked with several source control systems over the forty years I've been developing, so I have a bit of a clue.

EG:

  1. The first time I ran "hg export tip", it showed changes (by Dan) to files that I hadn't touched.  What's that about?
  2. Subsequent invocations return my changes to two files.  These two files need to be two separate patches (two separate bug #s).
  3. What the hell is "tip" ???
  4. OK, I did "hg qnew -l chirp\id51.py -ef <comment>".  Then I removed it with "hg qpop".  Somewhere in these few steps my changes (including all of id51.py) were deleted.   Is that what you mean by "minimal learning curve" below?  Fortunately (that means having some experience with the batch of new SCM packages out there), I had a complete backup elsewhere.
  5. I just now decided to have multiple backups of my changes.  Perhaps I'll use CVS internally ...
  6. Another "hg qpop" also removed a "patch" named "help";  I'll let you guess as to how that got there (grin).

When using "hg q.." commands, your changes are not permanent. You can
send a work-in-progress patch (see Sending a Change), ask some
questions, modify your code, then use "hg qref" to update the patch.

A little bit of record keeping is necessary for managing a large
codebase, and I think you'll find the learning curve for hg mq is
minimal and saves you effort in the long run.

Tom KD7LXL

<rant>

So far this has been all downhill.  I've spent far more time screwing around with the submission process, that I did to create id51.py (and make proposed changes to id31.py).  I don't have/need/want an ID-31A.  I have an ID-51A, and the free Icom CS-51 software is sufficient for my needs (including the import of some 250 memories via .CSV files).  Frankly, it's less buggy as well.

I thought, "Chirp lacks ID-51 support, and I have one;  let's see if I can help".  That's my sole motivation.  Just as changing DStarCom to open source so that Chirp could use some of that code, was to help (see Dan's blog on my code).  The changes I have to id31.py are because (having cloned id51.py from it) I think there are errors in the memory layout, and it seems reasonable to help fix that as well.

We're all volunteers here, and I don't expect anyone to jump through hoops for me.  On the other hand, I'm retired, and have plenty of spare time to contribute to useful efforts.  Of course, I have MANY choices for how I spend that time (that includes being paid to fly small airplanes in wonderful weather), and screwing around with yet another inadequately documented tool is not high on my list.

I don't know if you have read the book "Psychology of Computer Programming" by Gerald Weinberg.  Written in 1972, it's considered a classic.  He reported that back then, someone was inventing a new computer language about once a week.  This industry is filled with people that "have to" create new ("vanity") languages or tools, with very marginal additional utility (or worse) between them.  Remember "BitKeeper"?  That was the SCM rage a couple years ago.  No one these days counts the productivity cost of learning new tools (for Microsoft, that means new versions of productive software, with the main change being useless changes to the GUI) that are not adequately documented.

</rant>

So, I'll try once more:

   hg qnew -l chirp\id51.py -ef "[id51] Add support for Icom ID-51"

I'm supposed to enter the bug # (553), but "qnew" won't take a "#" on the command line, and up pops a NotePad window with the changed file (??? I just closed it).  So, no info on that.  What follows is the output from the next command. I sure don't see my comment (above), and I sure thought that almost all of the lines would be prefixed by a "+".  So,, my guess is that this will be unacceptable as well.  I'm sure not going to submit any changes to id31.py until I've sorted this out.

   hg export tip

# HG changeset patch
# User Dean Gibson <data@ae7q.net>
# Date 1361307603 28800
# Node ID b94ce8ef429fb5bf5750d96341c6d38354837b57
# Parent  5528bdcdc34e5966d5b7f0dbb6859f700b5f9366
# Copyright 2012 Dan Smith <dsmith@danplanet.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/>.

from chirp import directory, icf, bitwise, chirp_common

MEM_FORMAT = """
struct {
  u24 freq;
  u16 offset;
  u16 rtone:6,
      ctone:6,
      unknown2:1,
      mode:3;
  u8 dtcs;
  u8 tune_step:4,
     unknown5:4;
  u8 unknown4;
  u8 tmode:4,
     duplex:2,
     dtcs_polarity:2;
  char name[16];
  u8 unknown13;
  u8 urcall[7];
  u8 rpt1call[7];
  u8 rpt2call[7];
} memory[500];

#seekto 0x6A40;
u8 used_flags[70];

#seekto 0x6A86;
u8 skip_flags[69];

#seekto 0x6ACB;
u8 pskp_flags[69];

#seekto 0x6B40;
struct {
  u8 bank;
  u8 index;
} banks[500];

#seekto 0x6FD0;
struct {
  char name[16];
} bank_names[26];

#seekto 0xA8C0;
struct {
  u24 freq;
  u16 offset;
  u8 unknown1[3];
  u8 call[7];
  char name[16];
  char subname[8];
  u8 unknown3[10];
} repeaters[700];

#seekto 0x1384E;
struct {
  u8 call[7];
} rptcall[700];

#seekto 0x14E60;
struct {
  char call[8];
  char tag[4];
} mycall[6];

#seekto 0x14EA8;
struct {
  char call[8];
} urcall[200];

"""

MODES = [ "FM", "NFM", "AM", "DV" ]
MODE_INDEX = [ 0, 1, 3, 5 ]
TMODES = ["", "Tone", "TSQL", "TSQL", "DTCS", "DTCS", "TSQL-R", "DTCS-R"]
DUPLEX = ["", "-", "+"]
DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
TUNING_STEPS = [5.0, 6.25, 0, 0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
                100.0, 125.0, 200.0]

def _decode_call(_call):
    # Why Icom, why?
    call = ""
    shift = 1
    acc = 0
    for val in _call:
        mask = (1 << (shift)) - 1
        call += chr((val >> shift) | acc)
        acc = (val & mask) << (7 - shift)
        shift += 1
    call += chr(acc)
    return call

def _encode_call(call):
    _call = [0x00] * 7
    for i in range(0, 7):
        val = ord(call[i]) << (i + 1)
        if i > 0:
            _call[i-1] |= (val & 0xFF00) >> 8
        _call[i] = val
    _call[6] |= (ord(call[7]) & 0x7F)

    return _call

def _get_freq(_mem):
    freq = int(_mem.freq)
    offs = int(_mem.offset)

    if freq & 0x00200000:
        mult = 6250
    else:
        mult = 5000

    freq &= 0x0003FFFF

    return (freq * mult), (offs * mult)

def _set_freq(_mem, freq, offset):
    if chirp_common.is_fractional_step(freq):
        mult = 6250
        flag = 0x00200000
    else:
        mult = 5000
        flag = 0x00000000

    _mem.freq = (freq / mult) | flag
    _mem.offset = (offset / mult)

class ID51Bank(icf.IcomBank):
    """A ID-51 Bank"""
    def get_name(self):
        _banks = self._model._radio._memobj.bank_names
        return str(_banks[self.index].name).rstrip()

    def set_name(self, name):
        _banks = self._model._radio._memobj.bank_names
        _banks[self.index].name = str(name).ljust(16)[:16]

@directory.register
class ID51Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
    """Icom ID-51"""
    MODEL = "ID-51A"

    _memsize = 0x1FB40
    _model = "\x33\x90\x00\x01"
    _endframe = "Icom Inc\x2E\x44\x41"
    _num_banks = 26
    _bank_class = ID51Bank
    _can_hispeed = True

    _ranges = [(0x00000, 0x1FB40, 32)]

    def _get_bank(self, loc):
        _bank = self._memobj.banks[loc]
        if _bank.bank == 0xFF:
            return None
        else:
            return _bank.bank

    def _set_bank(self, loc, bank):
        _bank = self._memobj.banks[loc]
        if bank is None:
            _bank.bank = 0xFF
        else:
            _bank.bank = bank

    def _get_bank_index(self, loc):
        _bank = self._memobj.banks[loc]
        return _bank.index

    def _set_bank_index(self, loc, index):
        _bank = self._memobj.banks[loc]
        _bank.index = index

    def get_features(self):
        rf = chirp_common.RadioFeatures()
        rf.memory_bounds = (0, 499)
        rf.valid_bands = [(108000000, 174000000), (400000000, 479000000)]
        rf.has_settings = True
        rf.has_ctone = True
        rf.has_bank_index = True
        rf.has_bank_names = True
        rf.valid_tmodes = list(TMODES)
        rf.valid_tuning_steps = sorted(list(TUNING_STEPS))
        rf.valid_modes = list(MODES)
        rf.valid_skips = ["", "S", "P"]
        rf.valid_characters = chirp_common.CHARSET_ASCII
        rf.valid_name_length = 16
        return rf

    def process_mmap(self):
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)

    def get_raw_memory(self, number):
        return repr(self._memobj.memory[number])

    def get_memory(self, number):
        _mem = self._memobj.memory[number]
        _usd = self._memobj.used_flags[number / 8]
        _skp = self._memobj.skip_flags[number / 8]
        _psk = self._memobj.pskp_flags[number / 8]

        bit = (1 << (number % 8))

        if MODES[MODE_INDEX.index(_mem.mode)] == "DV":
            mem = chirp_common.DVMemory()
        else:
            mem = chirp_common.Memory()
        mem.number = number

        if _usd & bit:
            mem.empty = True
            return mem

        mem.freq, mem.offset = _get_freq(_mem)
        mem.name = str(_mem.name).rstrip()
        mem.rtone = chirp_common.TONES[_mem.rtone]
        mem.ctone = chirp_common.TONES[_mem.ctone]
        mem.tmode = TMODES[_mem.tmode]
        mem.duplex = DUPLEX[_mem.duplex]
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
        mem.tuning_step = TUNING_STEPS[_mem.tune_step]
        mem.mode = MODES[MODE_INDEX.index(_mem.mode)]

        if mem.mode == "DV":
            mem.dv_urcall = _decode_call(_mem.urcall).rstrip()
            mem.dv_rpt1call = _decode_call(_mem.rpt1call).rstrip()
            mem.dv_rpt2call = _decode_call(_mem.rpt2call).rstrip()

        if _psk & bit:
            mem.skip = "P"
        if _skp & bit:
            mem.skip = "S"

        return mem

    def set_memory(self, memory):
        _mem = self._memobj.memory[memory.number]
        _usd = self._memobj.used_flags[memory.number / 8]
        _skp = self._memobj.skip_flags[memory.number / 8]
        _psk = self._memobj.pskp_flags[memory.number / 8]

        bit = (1 << (memory.number % 8))

        if memory.empty:
            _usd |= bit
            self._set_bank(memory.number, None)
            return

        _usd &= ~bit

        _set_freq(_mem, memory.freq, memory.offset)
        _mem.name = memory.name.ljust(16)[:16]
        _mem.rtone = chirp_common.TONES.index(memory.rtone)
        _mem.ctone = chirp_common.TONES.index(memory.ctone)
        _mem.tmode = TMODES.index(memory.tmode)
        _mem.duplex = DUPLEX.index(memory.duplex)
        _mem.dtcs = chirp_common.DTCS_CODES.index(memory.dtcs)
        _mem.dtcs_polarity = DTCS_POLARITY.index(memory.dtcs_polarity)
        _mem.tune_step = TUNING_STEPS.index(memory.tuning_step)
        _mem.mode = MODE_INDEX[MODES.index(memory.mode)]

        if isinstance(memory, chirp_common.DVMemory):
            _mem.urcall = _encode_call(memory.dv_urcall.ljust(8))
            _mem.rpt1call = _encode_call(memory.dv_rpt1call.ljust(8))
            _mem.rpt2call = _encode_call(memory.dv_rpt2call.ljust(8))
        elif memory.mode == "DV":
            raise Exception("BUG")

        if memory.skip == "S":
            _skp |=  bit
            _psk &= ~bit
        elif memory.skip == "P":
            _skp &= ~bit
            _psk |=  bit
        else:
            _skp &= ~bit
            _psk &= ~bit

    def get_urcall_list(self):
        calls = []
        for i in range(0, 200):
            call = str(self._memobj.urcall[i].call)
            if call == "CALLSIGN":
                call = ""
            calls.append(call)
        return calls

    def get_mycall_list(self):
        calls = []
        for i in range(0, 6):
            calls.append(str(self._memobj.mycall[i].call))
        return calls

    def get_repeater_call_list(self):
        calls = []
        for rptcall in self._memobj.rptcall:
            call = _decode_call(rptcall.call)
            if call.rstrip() and not call == "CALLSIGN":
                calls.append(call)
        for repeater in self._memobj.repeaters:
            call = _decode_call(repeater.call)
            if call == "CALLSIGN":
                call = ""
            calls.append(call.rstrip())
        return calls