Home
login

Kenwood TH-D72 and Linux

I recently found a buyer for my Icom IC-92AD, which enabled me to buy one of the new Kenwood TH-D72 radios. This is my first GPS-enabled device and a new radio to boot. I am thrilled. I got it in the mail in just enough time to scan through the instruction manual to figure out how to use it for the Monday night Beaverton CERT Net. I got on the air without any problem. The manual is not nearly as nice as the Icom manual was. First of all, they don't give you the complete manual printed, only a getting started guide. The manual is on a CD in PDF format.

The TH-D72 has a mini-B USB connector and comes with a cable. Curious, I plugged it in to my computer and saw that it loaded the cp210x driver and gave me a /dev/ttyUSB0 device. Hooray!!! It didn't work. :( It turns out that the Natty kernel I am running has a regression in it (a story for another day). I tried out the Maverick kernel and it works just fine. So running the Maverick kernel, I was able to open up minicom, set the baud rate to 9600, and establish communication with the radio. It is NOT self discoverable. Grrr. I type in something and it gives me back '?'. It appears that there are two modes. With the packet12 TNC enabled, it will echo your keystrokes and give you a 'cmd:' prompt. If you type something wrong, it will say '?EH'. Without the TNC enabled, it does not echo keystrokes and will give you a '?' if it did not understand the command you sent it.

Not seeing an obvious way to figure out the command set, I figured that we should try to reverse engineer it. I installed the MCP-4A program in wine. I tried to run it and it complained that it needed .NET 2.0. I tried installing dotnet20 and found that is not quite enough -- it wants dotnet20sp1 or greater. dotnet20sp2 does not install. dotnet30 does not install. When I run MCP-4A with dotnet20, it throws a few errors and does not give me full use of the program (no menubar, for example), but it does run. I was able to use Wireshark to sniff the USB traffic as I performed a read and write. Then I turned to python to whip up something that can do this natively. This is what I have so far:

#!/usr/bin/python
# coding=utf-8
# ex: set tabstop=4 expandtab shiftwidth=4 softtabstop=4:
#
# © Copyright Vernon Mauery, 2010.  All Rights Reserved
#
# This is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as  published
# by the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This sofware 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 Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this software.  If not, see <http://www.gnu.org/licenses/>.

def command(s, command, *args):
    cmd = command
    if args:
        cmd += " " + " ".join(args)
    print "PC->D72: %s" % cmd
    s.write(cmd + "\r")

    result = ""
    while not result.endswith("\r"):
        result += s.read(8)

    print "D72->PC: %s" % result.strip()

    return result.strip()


def l2b(*l):
    r = ''
    for v in l:
        if type(v) is str:
            r += v
        else:
            r += chr(v)
    return r

def bin2hex(v):
    r = ''
    for i in range(len(v)):
        r += '%02x '%ord(v[i])
    return r

def bin_cmd(s, rlen, *b):
    if b is not None:
        cmd = l2b(*b)
    else:
        cmd = ''
    print "PC->D72: %s" % cmd
    s.write(cmd)
    result = bin2hex(s.read(rlen)).strip()
    print "D72->PC: %s" % result
    return result

def usage(argv):
    print "Usage: %s <serial device> <read-image>" % argv[0]
    sys.exit(1)

if __name__ == "__main__":
    import serial
    import sys

    if len(sys.argv) < 3:
        usage(sys.argv)

    s = serial.Serial(port=sys.argv[1], baudrate=9600, xonxoff=True, timeout=0.25)

    #print get_id(s)
    #print get_memory(s, int(sys.argv[2]))
    print command(s, 'TC 1')
    print command(s, 'ID')
    print command(s, 'TY')
    print command(s, 'FV 0')
    print command(s, 'FV 1')
    print bin_cmd(s, 4, '0M PROGRAM\r')
    s.setBaudrate(57600)
    s.getCTS()
    s.setRTS()
    of = file(sys.argv[2], 'wb')
    for i in range(256):
        sys.stdout.write('\rfetching block %d...' % i)
        sys.stdout.flush()
        s.write(l2b(0x52, 0, i, 0, 0))
        s.read(5) # command response first
        of.write(s.read(256))
        s.write('\x06')
        s.read()
    print
    of.close()
    print bin2hex(s.read(5))
    print bin2hex(s.read(1))
    print bin_cmd(s, 2, 'E')
    s.getCTS()
    s.setRTS()
    s.getCTS()
    s.getCTS()
    s.close()

You run it like this:

$ python thd72.py /dev/ttyUSB0 d72-dump.dat

Unfortunately from what I have seen, two consecutive reads without any changes on the radio seem to have very big differences. It is as though some of the chunks of the file are rotated or shifted by a few bytes (and the shift is not constant throughout). Not seeing an immediate reason for this, I suspect that it is some form of obfuscation. Call me a pessimist.

I will continue to work on this, but I would love to see what others in community are doing as well.

Update:

I forgot to mention that the whole point of this exercise was to find a way to work it into CHIRP. I am currently working on a driver for this radio to enable it in CHIRP. And as I was looking over the tmv71 code in CHIRP, I noticed that I should be reading a response to the read block command _before_ I actually read the block data. This seems to help things out a bit (and I modified the above code to match).

getting the GPS log downloaded from the TH D72

Hi Vernon, any chance you can try to figure out what the commands are to get the GPS track log downloaded from the TH-D72. It looks like you have the machinery set up for that. Cheers, Nepomuk

TH-D72 waypoints and GPSlog

I have forwarded a modification to Vernon to get this data. He can probably verify if these data are valid.