From 33655a7f679e79b01ed73d52d5a39683dbaf669c Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 13 Mar 2014 16:54:21 +0000 Subject: [PATCH] clean up usb initialization and shutdown for fousb, ws28xx, and te923. remove more dead code from ws28xx. --- bin/weewx/drivers/fousb.py | 57 +++++------ bin/weewx/drivers/te923.py | 62 ++++++------ bin/weewx/drivers/ws28xx.py | 182 ++++++++++++++++++++++-------------- docs/changes.txt | 2 + docs/usersguide.htm | 27 +++--- 5 files changed, 176 insertions(+), 154 deletions(-) diff --git a/bin/weewx/drivers/fousb.py b/bin/weewx/drivers/fousb.py index 57f3d3a0..bffcc551 100644 --- a/bin/weewx/drivers/fousb.py +++ b/bin/weewx/drivers/fousb.py @@ -221,7 +221,7 @@ import weewx.abstractstation import weewx.units import weewx.wxformulas -DRIVER_VERSION = '1.5' +DRIVER_VERSION = '1.6' def loader(config_dict, engine): altitude_m = weewx.units.getAltitudeM(config_dict) @@ -587,27 +587,9 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): max_tries: How many times to try before giving up. [Optional. Default is 3] - data_format: Format for data from the station - [Optional. Default is 1080, automatically changes to 3080 as needed] - - vendor_id: The USB vendor ID for the station. - [Optional. Default is 1941] - - product_id: The USB product ID for the station. - [Optional. Default is 8021] - device_id: The USB device ID for the station. Specify this if there are multiple devices of the same type on the bus. [Optional. No default] - - interface: The USB interface. - [Optional. Default is 0] - - usb_endpoint: The IN_endpoint for reading from USB. - [Optional. Default is 0x81] - - usb_read_size: Number of bytes to read from USB. - [Optional. Default is 32 and is the same for all FineOffset devices] """ self.altitude = stn_dict['altitude'] @@ -618,17 +600,18 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): self.timeout = float(stn_dict.get('timeout', 15.0)) self.wait_before_retry = float(stn_dict.get('wait_before_retry', 30.0)) self.max_tries = int(stn_dict.get('max_tries', 3)) - self.interface = int(stn_dict.get('interface', 0)) - self.vendor_id = int(stn_dict.get('vendor_id', '0x1941'), 0) - self.product_id = int(stn_dict.get('product_id', '0x8021'), 0) self.device_id = stn_dict.get('device_id', None) - self.usb_endpoint = int(stn_dict.get('usb_endpoint', '0x81'), 0) - self.usb_read_size = int(stn_dict.get('usb_read_size', '0x20'), 0) - self.data_format = stn_dict.get('data_format', '1080') self.pressure_offset = stn_dict.get('pressure_offset', None) if self.pressure_offset is not None: self.pressure_offset = float(self.pressure_offset) + self.data_format ='1080' + self.vendor_id = 0x1941 + self.product_id = 0x8021 + self.usb_interface = 0 + self.usb_endpoint = 0x81 + self.usb_read_size = 0x20 + # avoid USB activity this many seconds each side of the time when # console is believed to be writing to memory. self.avoid = 3.0 @@ -694,17 +677,24 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): if not dev: logcrt("Cannot find USB device with Vendor=0x%04x ProdID=0x%04x Device=%s" % (self.vendor_id, self.product_id, self.device_id)) raise weewx.WeeWxIOError("Unable to find USB device") + self.devh = dev.open() - # Detach any old claimed interfaces + if not self.devh: + raise weewx.WeeWxIOError("Open USB device failed") + + # be sure kernel does not claim the interface try: - self.devh.detachKernelDriver(self.interface) - except: - pass + self.devh.detachKernelDriver(self.usb_interface) + except Exception, e: + loginf('Detach kernel driver failed: %s' % e) + + # attempt to claim the interface try: - self.devh.claimInterface(self.interface) + self.devh.claimInterface(self.usb_interface) except usb.USBError, e: self.closePort() - logcrt("Unable to claim USB interface: %s" % e) + logcrt("Unable to claim USB interface %s: %s" % + (self.usb_interface, e)) raise weewx.WeeWxIOError(e) def closePort(self): @@ -712,10 +702,7 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): self.devh.releaseInterface() except: pass - try: - self.devh.detachKernelDriver(self.interface) - except: - pass + self.devh = None def _find_device(self): """Find the vendor and product ID on the USB.""" diff --git a/bin/weewx/drivers/te923.py b/bin/weewx/drivers/te923.py index 6e69f7b8..4081d336 100644 --- a/bin/weewx/drivers/te923.py +++ b/bin/weewx/drivers/te923.py @@ -161,7 +161,7 @@ import weewx.abstractstation import weewx.units import weewx.wxformulas -DRIVER_VERSION = '0.8' +DRIVER_VERSION = '0.9' DEBUG_READ = 0 DEBUG_DECODE = 0 DEBUG_PRESSURE = 0 @@ -678,13 +678,11 @@ class Station(object): READ_LENGTH = 0x8 def __init__(self, vendor_id=0x1130, product_id=0x6801, - dev_id=None, ifc=None, cfg=None, memory_size='small'): + dev_id=None, memory_size='small'): self.vendor_id = vendor_id self.product_id = product_id self.device_id = dev_id - self.interface = ifc - self.configuration = cfg - self.handle = None + self.devh = None self.elevation = None self.latitude = None @@ -708,62 +706,56 @@ class Station(object): return dev return None - def open(self): + def open(self, interface=0): dev = self._find(self.vendor_id, self.product_id, self.device_id) if not dev: logcrt("Cannot find USB device with VendorID=0x%04x ProductID=0x%04x DeviceID=%s" % (self.vendor_id, self.product_id, self.device_id)) - raise weewx.WeeWxIOError("Unable to find station on USB") + raise weewx.WeeWxIOError('Unable to find station on USB') - if self.configuration is None: - self.configuration = dev.configurations[0] - if self.interface is None: - self.interface = self.configuration.interfaces[0][0] - self.handle = dev.open() + self.devh = dev.open() + if not self.devh: + raise weewx.WeeWxIOError('Open USB device failed') + # be sure kernel does not claim the interface try: - self.handle.detachKernelDriver(self.interface) - except Exception: - pass + self.devh.detachKernelDriver(interface) + except Exception, e: + loginf('Detach kernel driver failed: %s' % e) + # attempt to claim the interface try: - self.handle.setConfiguration(self.configuration) - self.handle.claimInterface(self.interface) - self.handle.setAltInterface(self.interface) + self.devh.claimInterface(interface) + self.devh.setAltInterface(interface) except usb.USBError, e: self.close() - logcrt("Unable to claim USB interface %s of configuration %s: %s" % - (self.interface, self.configuration, e)) + logcrt("Unable to claim USB interface %s: %s" % (interface, e)) raise weewx.WeeWxIOError(e) def close(self): try: - self.handle.releaseInterface() + self.devh.releaseInterface() except Exception: pass - try: - self.handle.detachKernelDriver(iface) - except Exception: - pass - self.handle = None + self.devh = None # The te923tool does reads in chunks with pause between. According to # the wview implementation, after sending the read command the device will # send back 32 bytes of data within 100 ms. If not, the command was not # properly received. # - # FIXME: must be a better way to know when there is no more data + # FIXME: must be a better way to know when there are no more data def _raw_read(self, addr, timeout=50): reqbuf = [0x05, 0xAF, 0x00, 0x00, 0x00, 0x00, 0xAF, 0xFE] reqbuf[4] = addr / 0x10000 reqbuf[3] = (addr - (reqbuf[4] * 0x10000)) / 0x100 reqbuf[2] = addr - (reqbuf[4] * 0x10000) - (reqbuf[3] * 0x100) reqbuf[5] = (reqbuf[1] ^ reqbuf[2] ^ reqbuf[3] ^ reqbuf[4]) - ret = self.handle.controlMsg(requestType=0x21, - request=usb.REQ_SET_CONFIGURATION, - value=0x0200, - index=0x0000, - buffer=reqbuf, - timeout=timeout) + ret = self.devh.controlMsg(requestType=0x21, + request=usb.REQ_SET_CONFIGURATION, + value=0x0200, + index=0x0000, + buffer=reqbuf, + timeout=timeout) if ret != 8: raise BadRead('Unexpected response to data request: %s != 8' % ret) @@ -772,8 +764,8 @@ class Station(object): rbuf = [] try: while time.time() - start_ts < 5: - buf = self.handle.interruptRead(self.ENDPOINT_IN, - self.READ_LENGTH, timeout) + buf = self.devh.interruptRead(self.ENDPOINT_IN, + self.READ_LENGTH, timeout) if buf: nbytes = buf[0] if nbytes > 7 or nbytes > len(buf)-1: diff --git a/bin/weewx/drivers/ws28xx.py b/bin/weewx/drivers/ws28xx.py index c93d772e..a8d87b1e 100644 --- a/bin/weewx/drivers/ws28xx.py +++ b/bin/weewx/drivers/ws28xx.py @@ -873,7 +873,7 @@ import weewx.units import weewx.wxengine import weewx.wxformulas -DRIVER_VERSION = '0.25' +DRIVER_VERSION = '0.26' # flags for enabling/disabling debug verbosity DEBUG_WRITES = 0 @@ -982,6 +982,12 @@ class WS28xx(weewx.abstractstation.AbstractStation): each will have a unique device identifier. Use this identifier to indicate which device should be used. [Optional. Default is None] + + serial: The transceiver serial number. If there are multiple + devices with the same vendor and product IDs on the bus, each will + have a unique serial number. Use the serial number to indicate which + transceiver should be used. + [Optional. Default is None] """ self.altitude = stn_dict['altitude'] @@ -993,6 +999,7 @@ class WS28xx(weewx.abstractstation.AbstractStation): self.vendor_id = int(stn_dict.get('vendor_id', '0x6666'), 0) self.product_id = int(stn_dict.get('product_id', '0x5555'), 0) self.device_id = stn_dict.get('device_id', None) + self.serial = stn_dict.get('serial', None) self.pressure_offset = stn_dict.get('pressure_offset', None) if self.pressure_offset is not None: self.pressure_offset = float(self.pressure_offset) @@ -1086,7 +1093,7 @@ class WS28xx(weewx.abstractstation.AbstractStation): self._service = CCommunicationService(self.cache_file) self._service.setup(self.frequency, self.vendor_id, self.product_id, self.device_id, - comm_interval=self.comm_interval) + self.serial, comm_interval=self.comm_interval) self._service.startRFThread() def shutDown(self): @@ -1199,9 +1206,6 @@ class WS28xx(weewx.abstractstation.AbstractStation): def get_transceiver_id(self): return self._service.DataStore.getDeviceID() - def get_battery(status, hardware): - return 0 - # The following classes and methods are adapted from the implementation by # eddie de pieri, which is in turn based on the HeavyWeather implementation. @@ -2794,42 +2798,72 @@ class sHID(object): self.timeout = 1000 self.last_dump = None - def open(self, vid, pid, did): - device = self._find_device(vid, pid, did) + def open(self, vid, pid, did, serial): + device = self._find_device(vid, pid, did, serial) if device is None: - logcrt('Cannot find USB device with Vendor=0x%04x ProdID=0x%04x Device=%s' % (vid, pid, did)) - raise weewx.WeeWxIOError('Unable to find USB device') + logcrt('Cannot find USB device with Vendor=0x%04x ProdID=0x%04x Device=%s Serial=%s' % (vid, pid, did, serial)) + raise weewx.WeeWxIOError('Unable to find transceiver on USB') self._open_device(device) def close(self): self._close_device() - def _find_device(self, vid, pid, did): + def _find_device(self, vid, pid, did, serial): for bus in usb.busses(): for dev in bus.devices: if dev.idVendor == vid and dev.idProduct == pid: if did is None or dev.filename == did: - loginf('found transceiver on USB bus=%s device=%s' % (bus.dirname, dev.filename)) - return dev + if serial is None: + loginf('found transceiver at bus=%s device=%s' % + (bus.dirname, dev.filename)) + return dev + else: + handle = dev.open() + try: + buf = shid.readCfg(handle, 0x1F9, 7) + sn = str("%02d"%(buf[0])) + sn += str("%02d"%(buf[1])) + sn += str("%02d"%(buf[2])) + sn += str("%02d"%(buf[3])) + sn += str("%02d"%(buf[4])) + sn += str("%02d"%(buf[5])) + sn += str("%02d"%(buf[6])) + if str(serial) == sn: + loginf('found transceiver at bus=%s device=%s serial=%s' % (bus.dirname, dev.filename, sn)) + return dev + else: + loginf('skipping transceiver with serial %s (looking for %s)' % (sn, serial)) + finally: + del handle return None - def _open_device(self, device, interface=0, configuration=1): - self._device = device - self._configuration = device.configurations[0] - self._interface = self._configuration.interfaces[0][0] - self._endpoint = self._interface.endpoints[0] - self.devh = device.open() - loginf('manufacturer: %s' % self.devh.getString(device.iManufacturer,30)) - loginf('product: %s' % self.devh.getString(device.iProduct,30)) - loginf('interface: %d' % self._interface.interfaceNumber) + def _open_device(self, dev, interface=0): + self.devh = dev.open() + if not self.devh: + raise weewx.WeeWxIOError('Open USB device failed') - # detach any old claimed interfaces + loginf('manufacturer: %s' % self.devh.getString(dev.iManufacturer,30)) + loginf('product: %s' % self.devh.getString(dev.iProduct,30)) + loginf('interface: %d' % interface) + + # be sure kernel does not claim the interface try: - self.devh.detachKernelDriver(self._interface.interfaceNumber) - except Exception: - pass + self.devh.detachKernelDriver(interface) + except Exception, e: + loginf('Detach kernel driver failed: %s' % e) + + # attempt to claim the interface + try: + logdbg('claiming USB interface %d' % interface) + self.devh.claimInterface(interface) + self.devh.setAltInterface(interface) + except usb.USBError, e: + self._close_device() + logcrt('Unable to claim USB interface %s: %s' % (interface, e)) + raise weewx.WeeWxIOError(e) # FIXME: this seems to be specific to ws28xx? + # FIXME: check return values usbWait = 0.05 self.devh.getDescriptor(0x1, 0, 0x12) time.sleep(usbWait) @@ -2837,39 +2871,19 @@ class sHID(object): time.sleep(usbWait) self.devh.getDescriptor(0x2, 0, 0x22) time.sleep(usbWait) - - # attempt to claim the interface - try: - if platform.system() is 'Windows': - loginf('set USB device configuration to %d' % configuration) - self.devh.setConfiguration(configuration) - logdbg('claiming USB interface %d' % interface) - self.devh.claimInterface(interface) - self.devh.setAltInterface(interface) - except usb.USBError, e: - self._close_device() - raise weewx.WeeWxIOError(e) - - # FIXME: this seems to be specific to ws28xx? - # FIXME: check return value - self.devh.controlMsg( - usb.TYPE_CLASS + usb.RECIP_INTERFACE, - 0x000000a, [], 0x0000000, 0x0000000, 1000) - time.sleep(0.05) + self.devh.controlMsg(usb.TYPE_CLASS + usb.RECIP_INTERFACE, + 0xa, [], 0x0, 0x0, 1000) + time.sleep(usbWait) self.devh.getDescriptor(0x22, 0, 0x2a9) time.sleep(usbWait) def _close_device(self): try: - logdbg('release USB interface') + logdbg('releasing USB interface') self.devh.releaseInterface() except Exception: pass - try: - logdbg('detach kernel driver') - self.devh.detachKernelDriver(self._interface.interfaceNumber) - except Exception: - pass + self.devh = None def setTX(self): buf = [0]*0x15 @@ -3074,6 +3088,38 @@ class sHID(object): logdbg('%s: %s%s' % (cmd, pad, strbuf)) self.last_dump = None + def readCfg(self, handle, addr, numBytes): + while numBytes: + buf=[0xcc]*0x0f #0x15 + buf[0] = 0xdd + buf[1] = 0x0a + buf[2] = (addr >>8) & 0xFF + buf[3] = (addr >>0) & 0xFF + handle.controlMsg(usb.TYPE_CLASS + usb.RECIP_INTERFACE, + request=0x0000009, + buffer=buf, + value=0x00003dd, + index=0x0000000, + timeout=1000) + buf = handle.controlMsg(requestType=usb.TYPE_CLASS | + usb.RECIP_INTERFACE | usb.ENDPOINT_IN, + request=usb.REQ_CLEAR_FEATURE, + buffer=0x15, + value=0x00003dc, + index=0x0000000, + timeout=1000) + new_data=[0]*0x15 + if numBytes < 16: + for i in xrange(0, numBytes): + new_data[i] = buf[i+4] + numBytes = 0 + else: + for i in xrange(0, 16): + new_data[i] = buf[i+4] + numBytes -= 16 + addr += 16 + return new_data + class CCommunicationService(object): reg_names = dict() @@ -3326,7 +3372,7 @@ class CCommunicationService(object): changed = self.DataStore.StationConfig.testConfigChanged(cfgBuffer) inBufCS = self.DataStore.StationConfig.getInBufCS() if inBufCS == 0 or inBufCS != cs: - logdbg('handleCurrentData: inBufCS of station not actual') + logdbg('handleCurrentData: inBufCS of station does not match') self.DataStore.setRequestType(ERequestType.rtGetConfig) self.setSleep(0.400,0.400) newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetConfig, cs, idx) @@ -3610,18 +3656,11 @@ class CCommunicationService(object): for r in self.reg_names: self.shid.writeReg(r, self.reg_names[r]) - self.shid.execute(5) - self.shid.setPreamblePattern(0xaa) - self.shid.setState(0) - time.sleep(1) - self.shid.setRX() - def setup(self, frequency_standard, - vendor_id, product_id, device_id, + vendor_id, product_id, device_id, serial, comm_interval=3): self.DataStore.setCommModeInterval(comm_interval) - self.firstSleep, self.nextSleep = self.getSleep(comm_interval) - self.shid.open(vendor_id, product_id, device_id) + self.shid.open(vendor_id, product_id, device_id, serial) self.initTransceiver(frequency_standard) self.DataStore.setTransceiverPresent(True) @@ -3676,7 +3715,7 @@ class CCommunicationService(object): def doRF(self): try: logdbg('setting up rf communication') - self.doRFStartup() + self.doRFSetup() logdbg('starting rf communication') while self.running: self.doRFCommunication() @@ -3689,7 +3728,17 @@ class CCommunicationService(object): finally: logdbg('stopping rf communication') - def doRFStartup(self): + # it is probably not necessary to have two setPreamblePattern invocations. + # however, HeavyWeatherPro seems to do it this way on a first time config. + # doing it this way makes configuration easier during a factory reset and + # when re-establishing communication with the station sensors. + def doRFSetup(self): + self.shid.execute(5) + self.shid.setPreamblePattern(0xaa) + self.shid.setState(0) + time.sleep(1) + self.shid.setRX() + self.shid.setPreamblePattern(0xaa) self.shid.setState(0x1e) time.sleep(1) @@ -3729,15 +3778,6 @@ class CCommunicationService(object): self.firstSleep = firstsleep self.nextSleep = nextsleep - # FIXME: are these values needed now? - # comm_interval first next - # 3 3.980 0.020 - # 5 5.980 0.020 - def getSleep(self, comInt): - if comInt == 3: - return 3.980,0.020 - return 5.980, 0.020 - def timing(self): s = self.firstSleep + self.nextSleep * (self.pollCount - 1) return 'sleep=%s first=%s next=%s count=%s' % ( diff --git a/docs/changes.txt b/docs/changes.txt index 22ec64d5..003e8374 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -21,6 +21,8 @@ Provide explicit pairing feedback using wee_config_ws28xx Count wxengine restarts in logwatch. +Cleaned up USB initialization for fousb, ws28xx, and te923 drivers. + 2.6.2 02/16/14 diff --git a/docs/usersguide.htm b/docs/usersguide.htm index b3556a4d..1e00084b 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -2274,20 +2274,20 @@ Configuration utility for WS-28xx weather stations. Options: -h, --help show this help message and exit --config=FILE configuration file - --check-transceiver check USB transceiver + --check-transceiver check USB transceiver --pair pair the USB transceiver with a station console - --info display weather station configuration - --current get the current weather conditions - --history-since=N display history records since N minutes ago - --history=N display N history records + --info display weather station configuration + --current get the current weather conditions + --history-since=N display history records since N minutes ago + --history=N display N history records --format=FORMAT format for history, one of raw, table, or dict --maxtries=MAXTRIES maximum number of retries, 0 indicates no max --debug display diagnostic information while running Mutating actions will request confirmation before proceeding. -

The WS28xx driver is still in active development; only the +

The WS28xx driver is still in active development; the  highlighted  options - are working.

+ are not yet working.

Pairing

The console and transceiver must be paired. Pairing ensures that your @@ -2297,16 +2297,17 @@ Mutating actions will request confirmation before proceeding. batteries.

There are two ways to pair the console and the transceiver:

    -
  1. The Weewx way. With weewx running, press and hold - the [v] button on the console until you see 'PC' in the display. If - the console pairs with the transceiver, 'PC' will go away within a - second or two.
  2. +
  3. The Weewx way. With weewx + running, press and hold the [v] button on the console until you see + 'PC' in the display. If the console pairs with the transceiver, 'PC' + will go away within a second or two.
  4. The HeavyWeather way. Follow the pairing instructions that came with the station. You will have to run HeavyWeather on a windows computer with the USB transceiver. After HeavyWeather indicates the devices are paired, put the USB transceiver - in your weewx computer and start weewx. Do not power cycle the - station console or you will have to start over.
  5. + in your weewx computer and start weewx. + Do not power cycle the station console or you will have to start + over.

If the console does not pair, you will see messages in the log such as