1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
# Please do not change this hardware control module for Quisk.
# It provides USB control of SoftRock hardware.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, time, traceback, math
from quisk_hardware_model import Hardware as BaseHardware
import _quisk as QS
# All USB access is through control transfers using pyusb.
# byte_array = dev.ctrl_transfer (IN, bmRequest, wValue, wIndex, length, timeout)
# len(string_msg) = dev.ctrl_transfer (OUT, bmRequest, wValue, wIndex, string_msg, timeout)
try:
import usb
import usb.core, usb.util
except:
if sys.platform == 'win32':
dlg = wx.MessageDialog(None, "The Python pyusb module is required but not installed. Do you want me to install it?",
"Install Python pyusb", style = wx.YES|wx.NO)
if dlg.ShowModal() == wx.ID_YES:
import subprocess
subprocess.call([sys.executable, "-m", "pip", "install", "pyusb"])
try:
import usb
import usb.core, usb.util
except:
dlg = wx.MessageDialog(None, "Installation of Python pyusb failed. Please install it by hand.",
"Installation failed", style=wx.OK)
dlg.ShowModal()
else:
dlg = wx.MessageDialog(None, "The Python pyusb module is required but not installed. Please install package python-usb.",
"Install Python pyusb", style = wx.OK)
dlg.ShowModal()
DEBUG = 0
# Thanks to Ethan Blanton, KB8OJH, for this patch for the Si570 (many SoftRocks):
# These are used by SetFreqByDirect(); see below.
# The Si570 DCO must be clamped between these values
SI570_MIN_DCO = 4.85e9
SI570_MAX_DCO = 5.67e9
# The Si570 has 6 valid HSDIV values. Subtract 4 from HSDIV before
# stuffing it. We want to find the highest HSDIV first, so start
# from 11.
SI570_HSDIV_VALUES = [11, 9, 7, 6, 5, 4]
IN = usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_VENDOR, usb.util.CTRL_RECIPIENT_DEVICE)
OUT = usb.util.build_request_type(usb.util.CTRL_OUT, usb.util.CTRL_TYPE_VENDOR, usb.util.CTRL_RECIPIENT_DEVICE)
UBYTE2 = struct.Struct('<H')
UBYTE4 = struct.Struct('<L') # Thanks to Sivan Toledo
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.usb_dev = None
self.vfo = None
self.mode = None
self.band = None
self.repeater_freq = None # original repeater output frequency
try:
self.repeater_delay = conf.repeater_delay # delay for changing repeater frequency in seconds
except:
self.repeater_delay = 0.25
self.repeater_time0 = 0 # time of repeater change in frequency
self.ptt_button = 0
self.si570_i2c_address = conf.si570_i2c_address
def open(self): # Called once to open the Hardware
global usb
# find our device
usb_dev = usb.core.find(idVendor=self.conf.usb_vendor_id, idProduct=self.conf.usb_product_id)
if usb_dev is None:
text = 'USB device not found VendorID 0x%X ProductID 0x%X' % (
self.conf.usb_vendor_id, self.conf.usb_product_id)
else:
try: # This exception occurs for the Peabody SDR. Thanks to ON7VQ for figuring out the problem,
usb_dev.set_configuration() # and to David, AE9RB, for the fix.
except:
pass #if DEBUG: traceback.print_exc()
try:
ret = usb_dev.ctrl_transfer(IN, 0x00, 0x0E00, 0, 2)
except:
if DEBUG: traceback.print_exc()
text = "No permission to access the SoftRock USB interface"
usb_dev = None
try: # Second try. Thanks to Ben Cahill.
if DEBUG: print("Second try to open USB with libusb0 backend.")
import usb.backend.libusb0
backend = usb.backend.libusb0.get_backend()
usb_dev = usb.core.find(idVendor=self.conf.usb_vendor_id, idProduct=self.conf.usb_product_id, backend=backend)
try:
usb_dev.set_configuration()
except:
if DEBUG: traceback.print_exc()
try:
ret = usb_dev.ctrl_transfer(IN, 0x00, 0x0E00, 0, 2)
except:
if DEBUG: traceback.print_exc()
usb_dev = None
except:
usb_dev = None
if usb_dev is not None:
self.usb_dev = usb_dev # success
if len(ret) == 2:
ver = "%d.%d" % (ret[1], ret[0])
else:
ver = 'unknown'
sound = self.conf.name_of_sound_capt
if len(sound) > 50:
sound = sound[0:30] + '|||' + sound[-17:]
text = 'Capture from SoftRock USB on %s, Firmware %s' % (sound, ver)
#self.application.bottom_widgets.info_text.SetLabel(text)
if DEBUG and usb_dev:
print ('Startup freq', self.GetStartupFreq())
print ('Run freq', self.GetFreq())
print ('Address 0x%X' % usb_dev.ctrl_transfer(IN, 0x41, 0, 0, 1)[0])
sm = usb_dev.ctrl_transfer(IN, 0x3B, 0, 0, 2)
sm = UBYTE2.unpack(sm)[0]
print ('Smooth tune', sm)
return text
def close(self): # Called once to close the Hardware
pass
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
if self.usb_dev and self.vfo != vfo:
if self.conf.si570_direct_control:
if self.SetFreqByDirect(vfo - self.transverter_offset):
self.vfo = vfo
elif self.SetFreqByValue(vfo - self.transverter_offset):
self.vfo = vfo
if DEBUG:
print ('Change to', vfo)
print ('Run freq', self.GetFreq())
return tune, vfo
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
BaseHardware.ChangeBand(self, band)
self.band = band
self.SetTxLevel()
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
BaseHardware.ChangeMode(self, mode)
self.mode = mode
QS.set_cwkey(0)
self.SetTxLevel()
def SetTxLevel(self):
tx_level = self.conf.tx_level.get(self.band, 70)
if self.mode[0:3] in ('DGT', 'FDV'): # Digital modes; change power by a percentage
reduc = self.application.digital_tx_level
else:
reduc = self.application.tx_level
tx_level = int(tx_level * reduc / 100.0 + 0.5)
if tx_level < 0:
tx_level = 0
elif tx_level > 100:
tx_level = 100
QS.set_mic_out_volume(tx_level)
if DEBUG: print("Change tx_level to", tx_level)
def ReturnFrequency(self):
# Return the current tuning and VFO frequency. If neither have changed,
# you can return (None, None). This is called at about 10 Hz by the main.
# return (tune, vfo) # return changed frequencies
return None, None # frequencies have not changed
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
if time.time() > self.repeater_time0 + self.repeater_delay:
return True
elif offset == 0: # Change back to the original frequency
if self.repeater_freq is not None:
self.repeater_time0 = time.time()
self.ChangeFrequency(self.repeater_freq, self.repeater_freq, 'repeater')
self.repeater_freq = None
else: # Shift to repeater input frequency
self.repeater_time0 = time.time()
self.repeater_freq = self.vfo
vfo = self.vfo + int(offset * 1000) # Convert kHz to Hz
self.ChangeFrequency(vfo, vfo, 'repeater')
return False
def OnSpot(self, level):
pass
def HeartBeat(self): # Called at about 10 Hz by the main
pass
def OnChangeRxTx(self, is_tx): # Called by Quisk when changing between Rx and Tx. "is_tx" is 0 or 1
if not self.usb_dev:
return
try:
self.usb_dev.ctrl_transfer(IN, 0x50, is_tx, 0, 3)
except usb.core.USBError:
if DEBUG: traceback.print_exc()
try:
self.usb_dev.ctrl_transfer(IN, 0x50, is_tx, 0, 3)
except usb.core.USBError:
if DEBUG: traceback.print_exc()
else:
if DEBUG: print("OnChangeRxTx", is_tx)
else:
if DEBUG: print("OnChangeRxTx", is_tx)
def GetStartupFreq(self): # return the startup frequency / 4
if not self.usb_dev:
return 0
ret = self.usb_dev.ctrl_transfer(IN, 0x3C, 0, 0, 4)
s = ret.tobytes()
freq = UBYTE4.unpack(s)[0]
freq = int(freq * 1.0e6 / 2097152.0 / 4.0 + 0.5)
return freq
def GetFreq(self): # return the running frequency / 4
if not self.usb_dev:
return 0
ret = self.usb_dev.ctrl_transfer(IN, 0x3A, 0, 0, 4)
s = ret.tobytes()
freq = UBYTE4.unpack(s)[0]
freq = int(freq * 1.0e6 / 2097152.0 / 4.0 + 0.5)
return freq
def SetFreqByValue(self, freq):
freq = int(freq/1.0e6 * 2097152.0 * 4.0 + 0.5)
if freq <= 0:
return
s = UBYTE4.pack(freq)
try:
self.usb_dev.ctrl_transfer(OUT, 0x32, self.si570_i2c_address + 0x700, 0, s)
except usb.core.USBError:
if DEBUG: traceback.print_exc()
else:
return True
def SetFreqByDirect(self, freq): # Thanks to Ethan Blanton, KB8OJH
if freq == 0.0:
return False
# For now, find the minimum DCO speed that will give us the
# desired frequency; if we're slewing in the future, we want this
# to additionally yield an RFREQ ~= 512.
freq = int(freq * 4)
dco_new = None
hsdiv_new = 0
n1_new = 0
for hsdiv in SI570_HSDIV_VALUES:
n1 = int(math.ceil(SI570_MIN_DCO / (freq * hsdiv)))
if n1 < 1:
n1 = 1
else:
n1 = ((n1 + 1) // 2) * 2
dco = (freq * 1.0) * hsdiv * n1
# Since we're starting with max hsdiv, this can only happen if
# freq was larger than we can handle
if n1 > 128:
continue
if dco < SI570_MIN_DCO or dco > SI570_MAX_DCO:
# This really shouldn't happen
continue
if not dco_new or dco < dco_new:
dco_new = dco
hsdiv_new = hsdiv
n1_new = n1
if not dco_new:
# For some reason, we were unable to calculate a frequency.
# Probably because the frequency requested is outside the range
# of our device.
return False # Failure
rfreq = dco_new / self.conf.si570_xtal_freq
rfreq_int = int(rfreq)
rfreq_frac = int(round((rfreq - rfreq_int) * 2**28))
# It looks like the DG8SAQ protocol just passes r7-r12 straight
# To the Si570 when given command 0x30. Easy enough.
# n1 is stuffed as n1 - 1, hsdiv is stuffed as hsdiv - 4.
hsdiv_new = hsdiv_new - 4
n1_new = n1_new - 1
s = struct.Struct('>BBL').pack((hsdiv_new << 5) + (n1_new >> 2),
((n1_new & 0x3) << 6) + (rfreq_int >> 4),
((rfreq_int & 0xf) << 28) + rfreq_frac)
self.usb_dev.ctrl_transfer(OUT, 0x30, self.si570_i2c_address + 0x700, 0, s)
return True # Success
def PollCwKey(self): # Called frequently by Quisk to check the CW key status
if not self.usb_dev:
return
if self.mode not in ('CWU', 'CWL'):
return
try: # Test key up/down state
ret = self.usb_dev.ctrl_transfer(IN, 0x51, 0, 0, 1)
except:
QS.set_cwkey(0)
if DEBUG: traceback.print_exc()
else:
# bit 0x20 is the tip, bit 0x02 is the ring (ring not used)
if ret[0] & 0x20 == 0: # Tip: key is down
QS.set_cwkey(1)
else: # key is up
QS.set_cwkey(0)
|