Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bc125csv
=============

Channel import and export tool for the Uniden BC125AT, UBC125XLT and UBC126AT.
Channel import and export tool for the Uniden BC125AT, UBC125XLT, UBC126AT, and SR30C.

[![Build Status](https://travis-ci.org/fdev/bc125csv.svg)](https://travis-ci.org/fdev/bc125csv)
[![Code Climate](https://codeclimate.com/github/fdev/bc125csv/badges/gpa.svg)](https://codeclimate.com/github/fdev/bc125csv)
Expand All @@ -28,7 +28,6 @@ Requirements
------------

* Python 2.7+ or 3.4+
* [pyudev](https://pyudev.readthedocs.org/)
* [pySerial](http://pyserial.sourceforge.net/)

Both pyudev and pySerial will be automatically installed on installation.
Expand Down Expand Up @@ -192,8 +191,13 @@ include a carriage return yourself.
Compatibility
-------------

This application is compatible with the Uniden Bearcat models BC125AT, UBC125XLT
and UBC126AT.
This application is compatible with the Uniden Bearcat models BC125AT, UBC125XLT,
UBC126AT, and SR30C.

Note: the SR30C uses a stock UART serial USB chipset (specifically, the CP2104).
Linux kernel v2.6.12+ appears to have the driver, but in other operating systems
it may be necessary to get drivers from the manufacturer:
https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers


License (MIT)
Expand Down
20 changes: 11 additions & 9 deletions bc125csv/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def format_help(self):
dest="noscanner")
parser.add_argument("-o", "--output", dest="output")
parser.add_argument("-r", "--rate", type=int, dest="rate",
choices=(4800, 9600, 19200, 38400, 57600, 115200), default=9600)
choices=(4800, 9600, 19200, 38400, 57600, 115200))
parser.add_argument("-s", "--sparse", action="store_true",
dest="sparse")
parser.add_argument("-v", "--verbose", action="store_true",
Expand Down Expand Up @@ -204,24 +204,26 @@ def get_scanner(self):
if not device:
sys.exit("No compatible scanner was found.")

if not lookup.is_tty(device):
sys.exit("Found a compatible scanner, but no serial tty.\n"
"Please run the following commands with root privileges:\n"
"modprobe usbserial vendor=0x{0} product=0x{1}"
.format(device.get("ID_VENDOR_ID"), device.get("ID_MODEL_ID")))

# Make sure device is writable by current user
if not os.access(device.get("DEVNAME", ""), os.W_OK):
if not os.access(device["port"], os.W_OK):
sys.exit("Found a compatible scanner, but can not write to it.")

scanner = Scanner(device.get("DEVNAME"), self.params.rate)
# the baud rate can be overriden
baudrate = self.params.rate
if baudrate:
device["baudrate"] = baudrate

scanner = Scanner(device)

try:
model = scanner.get_model()
except ScannerException:
sys.exit("Could not get model name from scanner.\n"
"Please try again or reconnect your device.")

if not model in SUPPORTED_MODELS:
sys.exit("Got unsupported model: ", model)
Comment on lines +224 to +225
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check necessary? Only specific combinations of vendor and product id even get here. If the ids match, and we can get a model name, I'm fine with assuming it's a supported model.


self.print_verbose("Found scanner", model)

return scanner
Expand Down
67 changes: 31 additions & 36 deletions bc125csv/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@
import re
import sys

try:
import pyudev
except ImportError: # pragma: no cover
sys.exit("Failed to import pyudev (https://pyudev.readthedocs.org/):,"
" install using:\n pip install pyudev")

try:
import serial
from serial.tools.list_ports import comports
except ImportError: # pragma: no cover
sys.exit("Failed to import pyserial (http://pyserial.sourceforge.net/),"
" install using:\n pip install pyserial")
Expand Down Expand Up @@ -42,7 +37,7 @@
"732","734","743","754",
]

SUPPORTED_MODELS = ("BC125AT", "UBC125XLT", "UBC126AT")
SUPPORTED_MODELS = ("BC125AT", "UBC125XLT", "UBC126AT", "SR30C")


class Channel(object):
Expand Down Expand Up @@ -100,16 +95,16 @@ class Scanner(serial.Serial, object):
(?P<index>\d{1,3}),
(?P<name>[^,]{0,16}),
(?P<freq>\d{5,8}), # 4 decimals, so at least 5 digits
(?P<modulation>AUTO|AM|FM|NFM),
(?P<tq>\d{1,3}),
(?P<modulation>|AUTO|AM|FM|NFM),
(?P<tq>\d{0,3}),
(?P<delay>-10|-5|0|1|2|3|4|5),
(?P<lockout>0|1),
(?P<priority>0|1) # no comma!
$ # No characters after
""", flags=re.VERBOSE)

def __init__(self, port, baudrate=9600): # pragma: no cover
super(Scanner, self).__init__(port=port, baudrate=baudrate)
def __init__(self, device): # pragma: no cover
super(Scanner, self).__init__(**device)

def writeread(self, command): # pragma: no cover
self.write((command + "\r").encode())
Expand Down Expand Up @@ -177,7 +172,7 @@ def get_channel(self, index):
"name": data["name"].strip(),
"frequency": frequency,
"modulation": data["modulation"],
"tqcode": int(data["tq"]),
"tqcode": int(data["tq"] or "0"),
"delay": int(data["delay"]),
"lockout": data["lockout"] == "1",
"priority": data["priority"] == "1",
Expand Down Expand Up @@ -211,7 +206,10 @@ def delete_channel(self, index):
if channel:
result = self.send("DCH,%d" % index)
if not result or result != "DCH,OK":
raise ScannerException("Could not delete channel %d." % index)
# Fall back to zeroing out the channel
result = self.send("CIN,%d,,00000000,,,0,1,0" % index)
if not result or result != "CIN,OK":
raise ScannerException("Could not delete channel %d." % index)
Comment on lines +209 to +212
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why this fallback should be added? The PR description does not mention this addition.



class VirtualScanner(Scanner):
Expand Down Expand Up @@ -267,29 +265,26 @@ class DeviceLookup(object): # pragma: no cover
"""

def __init__(self):
self.context = pyudev.Context()

def is_scanner(self, device):
"""Given USB device is a compatible scanner."""
return device.get("ID_VENDOR_ID") == "1965" and \
device.get("ID_MODEL") in SUPPORTED_MODELS
self.device = self._search_devices()

def is_tty(self, device):
"""Given USB device is a serial tty."""
return device.get("SUBSYSTEM") == "tty"
def _search_devices(self):
"""
Find compatible scanner and return usb device. Returns arguments for Serial constructor.
"""
ports = comports(include_links=False)
for port in ports:
# Uniden Vendor
if port.vid == 6501 and port.product in SUPPORTED_MODELS:
return {
"port": port.device,
"baudrate": 9600
}
# Silicon Laboratories Vendor (SR30C uses this chipset for its USB controller):
if port.vid == 4292 and port.pid == 60000:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use port.product here as well? port.product == "SR30C" would be more readable in my opinion. Since I don't have an SR30C I can't check the value of port.product for this case.

return {
"port": port.device,
"baudrate": 57600
}

def get_device(self):
"""Find compatible scanner and return usb device.

If found a tty device will be returned, otherwise the
usb device will be returned.
"""
# Look for scanner tty
for device in self.context.list_devices():
if self.is_scanner(device) and self.is_tty(device):
return device

# No scanner with tty, look for scanner
for device in self.context.list_devices():
if self.is_scanner(device):
return device
return self.device
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup

description = "Channel import and export tool for the BC125AT, UBC125XLT and UBC126AT."
description = "Channel import and export tool for the BC125AT, UBC125XLT, UBC126AT, and SR30C."
try:
# Convert from Markdown to reStructuredText (supported by PyPi).
import os
Expand All @@ -21,7 +21,7 @@
author = "Folkert de Vries",
author_email = "bc125csv@fdev.nl",
packages = ["bc125csv"],
install_requires = ["pyudev", "pyserial"],
install_requires = ["pyserial"],
entry_points="""
[console_scripts]
bc125csv = bc125csv:main
Expand Down