[chirp_devel] New Driver not registering

Rick DeWitt
Tue Feb 20 08:04:19 PST 2018


I have written a driver for the Radio Shack PRO-649 scanner, but it does 
not register in the directory.
I can force it by manually adding the 'rs649' to the __init__.py file, 
and then it runs perfectly.
But why won't it register itself? It is in the \chirp\drivers folder on 
my Win7 system.
Also, does anyone know how to invoke the 'show_instructions' function in 
the mainapp? I want to provide some configuration hints. I don't find 
any driver currently using this call.

-- 
Rick DeWitt
AA0RD
Sequim, Washington, USA
360-681-3494

-------------- next part --------------
# Copyright 2017
#
#  Developed for the Radio Shack PRO-649 programmable 200-channel scanner by Rick DeWitt (AA0RD)
#  AA0RD at yahoo.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 2 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/>.

import struct
import logging
import math
import binascii    #Used for debugging outputs

LOG = logging.getLogger(__name__)

from chirp import chirp_common, directory, memmap
from chirp import bitwise, errors, util, platform
from chirp.settings import RadioSettingGroup, RadioSetting, \
    RadioSettingValueBoolean, \
    RadioSettingValueFloat,InvalidValueError, RadioSettings
from textwrap import dedent


MEM_FORMAT = """
#seekto 0x0;
struct {
  ul24 rxfreq;
  u8  unk7:1
      unk6:1 
      unk5:1
      unk4:1
      delay:1
      lockout:1
      unk1:1
      unk0:1;
} chans[200];

struct blok {
  u8 byx[32];
};

#seekto 0x320;        // Going to store names here
struct {
  u8 id[8];           // 8 bytes per name gives 32-byte blocks of 4
} names[200];         // Uses 1600 bytes, Takes us up to 0x960

#seekto 0x960;
struct blok empty27[27];    // 27 empty 32-byte blocks

#seekto 0xCC0;            // unknown data here
struct {
  u8 unkx1[32];
} unkblk1;

#seekto 0xCE0;
struct blok empty9[9];

#seekto 0xE00;    // Settings
struct {
  u8  mtb0[27];
  u8  unk7:1
      unk6:1
      unk5:1
      unk4:1
      pri_set:1
      unk2:1
      unk1:1
      unk0:1;
  ul24 pri_frq;
  u8  ux7:1
      ux6:1
      ux5:1
      ux4:1
      pri_dly:1
      ux2:1
      ux1:1
      ux0:1;
} settings;

#seekto 0xE20;        // Bank enable map
struct {
  ul16  bnk16:1
      bnk15:1
	  bnk14:1
	  bnk13:1
	  bnk12:1
	  bnk11:1
	  bnk10:1
	  bnk9:1
	  bnk8:1
	  bnk7:1
	  bnk6:1
	  bnk5:1
	  bnk4:1
	  bnk3:1
	  bnk2:1
	  bnk1:1;
  u8  x1[30];
} banks;

#seekto 0xE40;
struct blok x2;        // all 0x30 ???

#seekto 0xE60;         // more 0x30 and 00
struct blok x3;

#seekto 0xE80;
struct {
  char  mod_num[7];
} mod_id;

"""

MEM_SIZE = 0xE80
BLOCK_SIZE = 32    # 8 4-byte Chans; no 2-byte checksum
CHANS_BLOCK = 8
BLOCKS = 116
STIMEOUT = 2
BAUD_RATE = 4800
prix = 0            # Start with no priority channel assigned

def _clean_buffer(radio):
    """Empty the radio read buffer."""
    radio.pipe.timeout = 0.005
    LOG.debug("Cleaning buffer..")
    junk = radio.pipe.read(256)
    radio.pipe.timeout = STIMEOUT
    if junk:
        LOG.warning("Got %i bytes of junk before starting" % len(junk))


def do_download(radio):
    """Download Scanner Memory."""
    radio.pipe.baudrate = BAUD_RATE
    radio.pipe.timeout = STIMEOUT
    radio.pipe.xonxof = True            # For dump mode
    # Get the serial port connection
    serial = radio.pipe
    _clean_buffer(radio)

    # UI progress
    status = chirp_common.Status()
    status.cur = 0
    status.max = BLOCKS
    status.msg = "Downloading from Scanner Memory..."
    radio.status_fn(status)

    #Download data array
    # ---- SO FAR: Does not read the dump ----!!!
    data = ""
    serial.write("\xec")         #  'echo' dump
    ack = serial.read(1)            #  cmd readback
    LOG.warning("CfgAck: " +  binascii.b2a_hex(ack))

    for nb in range(0, BLOCKS):
        serial.write("\x44")        # Gimme a block
        ack = serial.read(1)
        LOG.warning("44Ack: " +  binascii.b2a_hex(ack))
        for nc in range(0, CHANS_BLOCK):
            cnt = 1
            chx = serial.read(cnt)
            if len(chx) == 0:
                msg = "Timeout reading data from scanner; check your cable."
                raise errors.RadioError(msg)
            if len(chx) != cnt:
                msg = "Error reading data from scanner: not the amount of data expected."
                raise errors.RadioError(msg)
            data += chx
            LOG.warning("chx:" + binascii.b2a_hex(chx))
            # end for nc
        cbx = serial.read(2)        # get 2 checksum bytes and ignore
    # UI Update after each 8-chan block
        status.cur =  BLOCKS
        radio.status_fn(status)
    # end for nb
		
    # Append model code
    data += "PRO-649"
    return data


def do_upload(radio):
    """Upload memory to scanner."""
    radio.pipe.baudrate = BAUD_RATE
    radio.pipe.parity = "N"
    radio.pipe.timeout = STIMEOUT
    # Get the serial port connection
    serial = radio.pipe
    _clean_buffer(radio)

    # UI progress
    status = chirp_common.Status()
    status.cur = 0
    status.max = BLOCKS
    status.msg = "Uploading to Scanner Memory..."
    radio.status_fn(status)

# Send prefix
    serial.write("\x55\xab\xcd\x05\x02")
    ack = serial.read(5)
#    LOG.warning("Ack: " +  binascii.b2a_hex(ack))

# Send 116 35-byte blocks of 8 chan info and checksum
    i = 0        # data/memory index
    for nb in range(0, BLOCKS):
        cksum = 0
        serial.write("\x55")
        ack = serial.read(1)
        for nc in range(0, BLOCK_SIZE):        # Write 32 bytes; 8 4-byte chans
            chx = radio.get_mmap()[i]                # returns 1-byte as char
            serial.write(chx)
            ack = serial.read(1)
            cksum += ord(chx)                        # numeric value of char
#            LOG.warning("DatAck: " +  binascii.b2a_hex(ack) + " # " + str(cksum))
            i += 1
        cb1 = cksum % 256
        cb2 =int( math.floor(cksum / 256))
        serial.write(chr(cb1) + chr(cb2))
        ack = serial.read(2)
    # UI Update
        status.cur = nb
        radio.status_fn(status)

    # next nb

class WS1010Alias(chirp_common.Alias):
    """PRO-649 alias for Whistler WS1010."""
    VENDOR = "Whistler"
    MODEL = "WS1010"

class PRS404Alias(chirp_common.Alias):
    """PRO-649 alias for Radio Shack PRO-404."""
    VENDOR= "RadioShack"
    MODEL = "PRO-404"

class PSR100Alias(chirp_common.Alias):
    """PRO-649 alias GRE PSR-100."""
    VENDOR = "GRE"
    MODEL = "PSR-100"


@directory.register
class PRO649(chirp_common.CloneModeRadio):
    """Radio Shack PRO-649 Scanner."""
    VENDOR = "RadioShack"
    MODEL = "PRO-649"
    ALIASES = [WS1010Alias, PRS404Alias, PSR100Alias]
    NAME_LENGTH = 7

    @classmethod
    def get_prompts(cls):
        """Define the upload and download info prompts."""
        rp = chirp_common.RadioPrompts()
        rp.pre_download = _(dedent("""\
            SORRY! But the PRO649 scanner does not support
            handshake downloading! 
			At least not with the standard dongle.
            If you try it you will see 'Sending...'
            but the interface will time out.
            """))

        rp.pre_upload = _(dedent("""\
            Follow these instructions to upload your info:

            1 - Turn off your scanner
            2 - Connect your interface cable
            3 - Turn on your scanner
            4 - Do the upload of your scanner data
            5 - Turn off your scanner
            6 - Unplug the interface cable.
            """))
        return rp

    # Attributes defined in chirp_common.py class RadioFeatures
    def get_features(self):
        """Define valid radio features."""
        rf = chirp_common.RadioFeatures()
        rf.has_bank = False
        rf.has_settings = True
        rf.has_comment = False
        rf.has_tuning_step = False
        rf.can_odd_split = False
        rf.has_name = True
        rf.has_offset = False
        rf.has_mode = True            # Using for Delay on/off
        rf.valid_modes = ["DV","Auto"]
        rf.has_dtcs = False
        rf.has_rx_dtcs = False
        rf.has_dtcs_polarity = False
        rf.has_ctone = False
        rf.has_cross = False
        rf.valid_name_length = self.NAME_LENGTH
        rf.valid_duplexes = []
        rf.valid_skips = ["", "S", "P"]	
        rf.memory_bounds = (1, 200)  # This radio supports memories 1-200
        rf.valid_bands = [(29000000, 54000000),     # 10m, 6m, VHF-Low
                          (108000000, 136987500),   # Aircraft
                          (133700000, 174000000),   # 2m, Military,land-mobile, VHF-hi
                          (380000000, 512000000),   # 70-centimeters, UH-Air, feds
                          ]
        return rf

    # Do a download of the radio from the serial port
    def sync_in(self):
        """Standard function call to initiate radio download."""
        try:
            data =do_download(self)
        except errors.RadioError:
            # Pass through any real errors we raise
            raise
        except:
            # 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()
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)

    # Do an upload of the radio to the serial port
    def sync_out(self):
        """Standard function call to initiate radio upload."""
        try:
            do_upload(self)
        except:
            # If anything unexpected happens, make sure we raise
            # a RadioError and log the problem
            LOG.exception('Unexpected error during upload')
            raise errors.RadioError('Unexpected error communicating '
                                    'with the radio')

    # This function supports the 'Show Raw Memory' developer function
    # which is invoked from a UI row right-click pull-down
    def get_raw_memory(self, number):
        """Standard function call to return selected object representation string."""
        return repr(self._memobj.chans[number-1]) + repr(self._memobj.names[number-1])

    def process_mmap(self):
        """Process the mem map into the mem object"""
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)


    # Extract a high-level memory object from the low-level memory map
    # This is called to populate a memory in the UI
    def get_memory(self, number):
        """Standard function call to populate the UI rows."""
        global prix
        # Get a low-level memory object mapped to the image
        _mem = self._memobj.chans[number-1]        # chans array is base-0, number ("loc") is base 1
        _nam = self._memobj.names[number-1]
        _sets = self._memobj.settings
        _prfrq =  _sets.pri_frq * 1250.0  

        # Create a high-level memory object to return to the UI
        mem = chirp_common.Memory()

        mem.number = number
        mem.freq = _mem.rxfreq  * 1250.0            # 1000 * 12.5 step
        for char in _nam.id:
            if char != 0xff:
                mem.name += chr(char)
				
        mem.skip =  ""
        if ((_prfrq  != 0.0) and (mem.freq == _prfrq)):            # This is the priority scan channel
            mem.skip = "P"
            prix = number
        if (_mem.lockout ):        # T/F
            mem.skip = "S"
        mem.mode =  "Auto"            # Delay On
        if (_mem.delay ):
            mem.mode = "DV"
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
        if mem.freq == 0:
            mem.empty = True
            mem.mode = "Auto"
            mem.skip = "S"
            mem.name = "None"

        return mem

    # Store details about a high-level memory to the memory map
    # This is called when a user edits a memory in the UI
    def set_memory(self, mem):
        """Standard function call to update raw memory from UI values."""
        global prix
        # Get a low-level memory object mapped to the image
        _mem = self._memobj.chans[mem.number-1]
        _sets = self._memobj.settings
        _nam = self._memobj.names[mem.number-1]

        # Convert to low-level frequency representation
        _mem.rxfreq = int(mem.freq / 1250.0)

        _namelength = self.get_features().valid_name_length
        for i in range(_namelength):
            try:
                _nam.id[i] = ord(mem.name[i])
            except IndexError:
                _nam.id[i] = 0xFF

        # Set Lockout and Delay (mode)
        _mem.lockout = (mem.skip == "S")
        _mem.delay = (mem.mode == "DV")
        if (mem.skip == "P" ):            # User has designated this chan as the Priority freq
            setattr(_sets, "pri_frq", _mem.rxfreq)        # only the last one sticks
            prix = mem.number
        if ((mem.number == prix) and (mem.skip != "P")):                # Clear the priority freq
            setattr(_sets, "pri_frq",0.0)

    def get_settings(self):
        """Translate the bits in the mem_struct into settings in the UI"""
        _sets = self._memobj.settings                         # define mem struct write-back shortcut
        _bnks = self._memobj.banks
        basic = RadioSettingGroup("basic", "Basic")
        group = RadioSettings(basic)

        def Htz2raw(setting, obj, atrb):     # < Callback
            """Convert floating Freq in Mhz to UL24 integer."""
            vr = float(str(setting.value))        # This function is not invoked now
            value = vr * 1250.0
            setattr(obj, atrb, value)
            return

        def dumfun(setting, obj, atrb): 
            """Dummy call back, to make the setting read-only."""
            return

        rs = RadioSetting("banks.bnk1", "Bank  1",
                            RadioSettingValueBoolean((_bnks.bnk1 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk2", "Bank  2",
                            RadioSettingValueBoolean((_bnks.bnk2 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk3", "Bank  3",
                            RadioSettingValueBoolean((_bnks.bnk3 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk4", "Bank  4",
                            RadioSettingValueBoolean((_bnks.bnk4 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk5", "Bank  5",
                            RadioSettingValueBoolean((_bnks.bnk5 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk6", "Bank  6",
                            RadioSettingValueBoolean((_bnks.bnk6 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk7", "Bank  7",
                            RadioSettingValueBoolean((_bnks.bnk7 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk8", "Bank  8",
                            RadioSettingValueBoolean((_bnks.bnk8 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk9", "Bank  9",
                            RadioSettingValueBoolean((_bnks.bnk9 == 1)))
        basic.append(rs)

        rs = RadioSetting("banks.bnk10", "Bank 10",
                            RadioSettingValueBoolean((_bnks.bnk10 == 1)))
        basic.append(rs)

        rs = RadioSetting("settings.pri_set", "Priority Scan",
                            RadioSettingValueBoolean((_sets.pri_set == 1)))
        basic.append(rs)

        rs = RadioSetting("settings.pri_dly", "Priority Scan Delay",
                            RadioSettingValueBoolean((_sets.pri_dly == 1)))
        basic.append(rs)

        shopri = False
        if (shopri) :            # Only for dev/debug, otherwise confuses user
            val = _sets.pri_frq / 800.0        # display pri freq as read-only
            rs = RadioSetting("settings.pri_frq", "Priority Scan Freq (MHz)",
                              RadioSettingValueFloat(0.0, 480.0,val, 0.001,3))
            rs.set_apply_callback(dumfun, _sets,"pri_frq")
            basic.append(rs)

        return group       # END get_settings()

    def set_settings(self, settings): 
        """Copy UI settings back into raw memory."""
        _settings = self._memobj.settings
        _mem = self._memobj
        for element in settings:
            if not isinstance(element, RadioSetting):
                self.set_settings(element)
                continue
            else:
                try:
                    name = element.get_name()
                    if "." in name:
                        bits = name.split(".")
                        obj = self._memobj
                        for bit in bits[:-1]:
                            if "/" in bit:
                                bit, index = bit.split("/", 1)
                                index = int(index)
                                obj = getattr(obj, bit)[index]
                            else:
                                obj = getattr(obj, bit)
                        setting = bits[-1]
                    else:
                        obj = _settings
                        setting = element.get_name()

                    if element.has_apply_callback():
                        LOG.debug("Using apply callback")
                        element.run_apply_callback()
                    elif element.value.get_mutable():
                        LOG.debug("Setting %s = %s" % (setting, element.value))
                        setattr(obj, setting, element.value)
                except Exception, e:
                    LOG.debug(element.get_name())
                    raise

    @classmethod
    def match_model(cls, filedata, filename):
        """Test img memory belongs to this driver."""
        match_size = False
        match_model = False

        # testing the file data size
        if len(filedata) == MEM_SIZE + 7:      # +'PRO-649'
            match_size = True

        # testing the firmware model fingerprint
        rid = filedata[MEM_SIZE:MEM_SIZE+7]         # 'PRO-649' should be appended

        if rid == cls.MODEL:
            match_model = True

        if match_size and match_model:
            return True
        else:
            return False


More information about the chirp_devel mailing list