From 66ba34a990b7d54ae9168d8d5bc6e77fa87ce0a5 Mon Sep 17 00:00:00 2001 From: rphlx Date: Sun, 12 Feb 2012 19:07:27 -0800 Subject: [PATCH 1/5] rph usb initial import --- worker/rph/__init__.py | 0 worker/rph/usbWorker.py | 192 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 worker/rph/__init__.py create mode 100644 worker/rph/usbWorker.py diff --git a/worker/rph/__init__.py b/worker/rph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/worker/rph/usbWorker.py b/worker/rph/usbWorker.py new file mode 100644 index 0000000..abb1dd8 --- /dev/null +++ b/worker/rph/usbWorker.py @@ -0,0 +1,192 @@ +# Modular Python Bitcoin Miner - rph USB +# Copyright (C) 2011-2012 Michael Sparmann (TheSeven) +# Copyright (C) 2011-2012 rphlx +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you +# want to support further development of the Modular Python Bitcoin Miner. + +################################################### +# rph usb worker interface module # +# https://bitcointalk.org/index.php?topic=44891.0 # +################################################### + +import common +import usb +import array +import binascii +import threading +import time +import struct + +class usbWorker(object): + def __init__(self, miner, dict): + self.__dict__ = dict + self.miner = miner + self.children = [] + self.uid = getattr(self, "uid", "") + self.idx = getattr(self, "idx", 0) + self.name = getattr(self, "name", "rph" + str(self.idx)) + self.jobinterval = getattr(self, "jobinterval", 30) + self.jobspersecond = 1. / self.jobinterval # Used by work buffering algorithm + self.mhps = 0 + self.mhashes = 0 + self.jobsaccepted = 0 + self.accepted = 0 + self.rejected = 0 + self.invalid = 0 + self.starttime = time.time() + self.statlock = threading.RLock() + self.mainthread = threading.Thread(None, self.main, self.name + "_main") + self.mainthread.daemon = True + self.mainthread.start() + + # Report statistics about this worker module and its (non-existant) children. + def getstatistics(self, childstats): + # Acquire the statistics lock to stop statistics from changing while we deal with them + with self.statlock: + # Calculate statistics + statistics = { \ + "name": self.name, \ + "children": childstats, \ + "mhashes": self.mhashes, \ + "mhps": self.mhps, \ + "jobsaccepted": self.jobsaccepted, \ + "accepted": self.accepted, \ + "rejected": self.rejected, \ + "invalid": self.invalid, \ + "starttime": self.starttime, \ + "currentpool": self.job.pool.name if self.job != None and self.job.pool != None else None, \ + } + # Return result + return statistics + + def cancel(self, blockchain): + if self.job != None and self.job.pool != None and self.job.pool.blockchain == blockchain: + self.canceled = True + + def find_dev(self): + dev = usb.core.find(idVendor=0xf1f0, find_all=True) + for d in dev: + try: + uid = d.ctrl_transfer(0xc0, 0x96, 0, 0, 16, 5000) + except: + # fallback for old firmware revisions that don't implement uid. + uid = binascii.unhexlify("00000000000000000000000000000000") + pass + uid = binascii.hexlify(uid) + #self.miner.log("got uid : " + uid + "\n", "") + #self.miner.log("want uid: " + self.uid + "\n", "") + if self.uid == "" or self.uid == uid: + self.dev = d + return + raise Exception("unable to find miner") + + def main(self): + while True: + try: + self.error = None + self.job = None + self.checksuccess = False + self.cancelled = False + self.find_dev() + # I'm too sexy for this job. Too sexy for this job. Too sexy: + #job = common.Job(None, binascii.unhexlify("1625cbf1a5bc6ba648d1218441389e00a9dc79768a2fc6f2b79c70cf576febd0"), "\0" * 64 + binascii.unhexlify("4c0afa494de837d81a269421"), binascii.unhexlify("7bc2b302")) + job = common.Job(self.miner, None, None, binascii.unhexlify("0d840c5cc3def3dfdb1dfaf01da77e451c2e786d15fe0876836a6999a4f0fc79"), "\0" * 64 + binascii.unhexlify("12f2f7f34f027f0c1a0e76ba"), None, binascii.unhexlify("d0c984a9")) + self.sendjob(job) + self.polljob() + if self.error != None: raise self.error + if not self.checksuccess: raise Exception("Timeout waiting for validation job to finish") + self.miner.log(self.name + ": Running at %f MH/s\n" % self.mhps, "B") + interval = min(30, 2**32 / 1000000. / self.mhps) + self.jobinterval = min(self.jobinterval, max(0.5, interval * 0.9)) + self.miner.log(self.name + ": Job interval: %f seconds\n" % self.jobinterval, "B") + self.jobspersecond = 1. / self.jobinterval + self.miner.updatehashrate(self) + while True: + self.canceled = False + job = self.miner.getjob(self) + self.jobsaccepted = self.jobsaccepted + 1 + if self.canceled == True: + if job.longpollepoch != job.pool.blockchain.longpollepoch: continue + self.canceled = False; + if self.error != None: raise self.error + self.sendjob(job) + self.polljob() + if self.error != None: raise self.error + except Exception as e: + self.miner.log(self.name + ": %s\n" % e, "rB") + self.error = e + time.sleep(1) + + # poll USB MCU, ~1000 times per second, checking for nonce data, + # a job timeout, or long poll cancellation + def polljob(self): + try: + done = False + a = array.array('B') + while True: + if self.error != None: break + if self.cancelled: break + # ignore pipe errors. (bug in pyusb? they never happen with the C implementation..) + try: + data = self.dev.ctrl_transfer(0xc0, 0x41, 0, 0, 1, 5000) + if len(data): + a = a + data + except: + time.sleep(0.01) + pass + now = time.time() + if len(a) >= 4: + golden = a[0:4] + golden = golden[::-1] + golden = golden.tostring() + self.job.endtime = now + self.job.sendresult(golden, self) + delta = (now - self.job.starttime) + self.mhps = struct.unpack("= self.jobinterval: + # TODO: adjust for communication delays. + if self.job != None and self.job.pool != None: + mhashes = (now - self.job.starttime) * self.mhps + self.job.finish(mhashes, self) + break + except Exception as e: + self.error = e + + def sendjob(self, job): + cmd = binascii.unhexlify("40") + self.dev.ctrl_transfer(0x40, 0x40, 0, 0, cmd + job.state[::-1] + job.data[75:63:-1], 5000) + self.job = job + self.job.starttime = time.time() + # drain any leftover golden chars from the old job. + time.sleep(0.01) + try: + while True: + data = self.dev.ctrl_transfer(0xc0, 0x41, 0, 0, 1, 5000) + if len(data) <= 0: + break + except: + time.sleep(0.01) + pass From d32fb7567412fc7735b639ebb79638c77b6d6ee0 Mon Sep 17 00:00:00 2001 From: rphlx Date: Sun, 12 Feb 2012 19:29:32 -0800 Subject: [PATCH 2/5] rph usb updates for upstream 4b06f622d8aec9bd1d4e54e6bde4cc0a0795587e, 9697c8e03da03a3457abcdd19077fc83f84f90ac --- worker/rph/usbWorker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worker/rph/usbWorker.py b/worker/rph/usbWorker.py index abb1dd8..3d1d1af 100644 --- a/worker/rph/usbWorker.py +++ b/worker/rph/usbWorker.py @@ -99,6 +99,7 @@ def main(self): while True: try: self.error = None + self.mhps = 0 self.job = None self.checksuccess = False self.cancelled = False @@ -130,6 +131,7 @@ def main(self): except Exception as e: self.miner.log(self.name + ": %s\n" % e, "rB") self.error = e + self.mhps = 0 time.sleep(1) # poll USB MCU, ~1000 times per second, checking for nonce data, @@ -154,7 +156,6 @@ def polljob(self): golden = a[0:4] golden = golden[::-1] golden = golden.tostring() - self.job.endtime = now self.job.sendresult(golden, self) delta = (now - self.job.starttime) self.mhps = struct.unpack(" Date: Mon, 18 Jun 2012 20:35:47 -0700 Subject: [PATCH 3/5] Rename rph USB worker --- {worker/rph => modules/rph/usb}/__init__.py | 0 {worker/rph => modules/rph/usb}/usbWorker.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {worker/rph => modules/rph/usb}/__init__.py (100%) rename {worker/rph => modules/rph/usb}/usbWorker.py (100%) diff --git a/worker/rph/__init__.py b/modules/rph/usb/__init__.py similarity index 100% rename from worker/rph/__init__.py rename to modules/rph/usb/__init__.py diff --git a/worker/rph/usbWorker.py b/modules/rph/usb/usbWorker.py similarity index 100% rename from worker/rph/usbWorker.py rename to modules/rph/usb/usbWorker.py From 4af9cdd56dfc57f31b15b178cba9b4ed68808683 Mon Sep 17 00:00:00 2001 From: rphlx Date: Tue, 19 Jun 2012 00:22:56 -0700 Subject: [PATCH 4/5] new rph usb driver (based on ztex driver) --- .gitignore | 1 - modules/rph/__init__.py | 0 modules/rph/usb/__init__.py | 4 + modules/rph/usb/boardproxy.py | 179 +++++++++++++++ modules/rph/usb/driver.py | 115 ++++++++++ modules/rph/usb/rphusbhotplug.py | 269 ++++++++++++++++++++++ modules/rph/usb/rphusbworker.py | 383 +++++++++++++++++++++++++++++++ modules/rph/usb/usbWorker.py | 193 ---------------- 8 files changed, 950 insertions(+), 194 deletions(-) create mode 100644 modules/rph/__init__.py create mode 100644 modules/rph/usb/boardproxy.py create mode 100644 modules/rph/usb/driver.py create mode 100644 modules/rph/usb/rphusbhotplug.py create mode 100644 modules/rph/usb/rphusbworker.py delete mode 100644 modules/rph/usb/usbWorker.py diff --git a/.gitignore b/.gitignore index ad1b078..78d257c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *~ *.log *.pyc -usb __pycache__ worker/fpgamining/firmware/* config/* diff --git a/modules/rph/__init__.py b/modules/rph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/rph/usb/__init__.py b/modules/rph/usb/__init__.py index e69de29..7089c0a 100644 --- a/modules/rph/usb/__init__.py +++ b/modules/rph/usb/__init__.py @@ -0,0 +1,4 @@ +from .rphusbworker import rphUSBWorker +from .rphusbhotplug import rphUSBHotplugWorker + +workerclasses = [rphUSBWorker, rphUSBHotplugWorker] diff --git a/modules/rph/usb/boardproxy.py b/modules/rph/usb/boardproxy.py new file mode 100644 index 0000000..9837b2c --- /dev/null +++ b/modules/rph/usb/boardproxy.py @@ -0,0 +1,179 @@ +# Modular Python Bitcoin Miner +# Copyright (C) 2012 Michael Sparmann (TheSeven) +# Copyright (C) 2011-2012 rphlx +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you +# want to support further development of the Modular Python Bitcoin Miner. + + + +############################################################### +# rph usb out of process board access dispatcher # +############################################################### + + + +import time +import signal +import struct +import traceback +from threading import Thread, Condition, RLock +from multiprocessing import Process +from core.job import Job +from .driver import rphUSBDevice + + +class rphUSBBoardProxy(Process): + + + def __init__(self, rxconn, txconn, serial, takeover, firmware, pollinterval): + super(rphUSBBoardProxy, self).__init__() + self.rxconn = rxconn + self.txconn = txconn + self.serial = serial + self.takeover = takeover + self.firmware = firmware + self.pollinterval = pollinterval + + + def run(self): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + self.lock = RLock() + self.wakeup = Condition() + self.error = None + self.pollingthread = None + self.shutdown = False + self.job = None + self.checklockout = 0 + self.lastnonce = 0 + self.multiplier = 0 + + try: + + # Listen for setup commands + while True: + data = self.rxconn.recv() + + if data[0] == "connect": break + + else: raise Exception("Unknown setup message: %s" % str(data)) + + # Connect to board and upload firmware if neccessary + self.device = rphUSBDevice(self, self.serial, self.takeover, self.firmware) + + # Configure clock + #self._set_multiplier(192000000) + self.send("speed_changed", 16*192000000) + + # Start polling thread + self.pollingthread = Thread(None, self.polling_thread, "polling_thread") + self.pollingthread.daemon = True + self.pollingthread.start() + + self.send("started_up") + + # Listen for commands + while True: + if self.error: raise self.error + + data = self.rxconn.recv() + + if data[0] == "shutdown": break + + elif data[0] == "ping": self.send("pong") + + elif data[0] == "pong": pass + + elif data[0] == "set_pollinterval": + self.pollinterval = data[1] + with self.wakeup: self.wakeup.notify() + + elif data[0] == "send_job": + self.checklockout = time.time() + 1 + self.job = data[1] + with self.wakeup: + start = time.time() + self.device.send_job(data[2][::-1] + data[1][75:63:-1]) + end = time.time() + self.lastnonce = 0 + self.checklockout = end + 0.5 + self.respond(start, end) + + else: raise Exception("Unknown message: %s" % str(data)) + + except: self.log("Exception caught: %s" % traceback.format_exc(), 100, "r") + finally: + self.shutdown = True + with self.wakeup: self.wakeup.notify() + try: self.pollingthread.join(2) + except: pass + self.send("dying") + + + def send(self, *args): + with self.lock: self.txconn.send(args) + + + def respond(self, *args): + self.send("response", *args) + + + def log(self, message, loglevel, format = ""): + self.send("log", message, loglevel, format) + + + def polling_thread(self): + try: + lastshares = [] + counter = 0 + + while not self.shutdown: + + counter += 1 + + # Poll for nonces + now = time.time() + nonces = self.device.read_nonces() + exhausted = False + with self.wakeup: + if len(nonces) > 0 and nonces[0] < self.lastnonce: + self.lastnonce = nonces[0] + exhausted = True + if exhausted: self.send("keyspace_exhausted") + for nonce in nonces: + if not nonce[0] in lastshares: + if self.job: self.send("nonce_found", time.time(), struct.pack(" len(nonces): lastshares.pop(0) + + with self.wakeup: self.wakeup.wait(self.pollinterval) + + except Exception as e: + self.log("Exception caught: %s" % traceback.format_exc(), 100, "r") + self.error = e + # Unblock main thread + self.send("ping") + + + def _set_multiplier(self, multiplier): + multiplier = min(max(multiplier, 1), self.device.maximum_multiplier) + if multiplier == self.multiplier: return + self.device.set_multiplier(multiplier) + self.multiplier = multiplier + self.checklockout = time.time() + 2 + self.send("speed_changed", (multiplier + 1) * self.device.base_frequency * self.device.hashes_per_clock) diff --git a/modules/rph/usb/driver.py b/modules/rph/usb/driver.py new file mode 100644 index 0000000..b0ed3b0 --- /dev/null +++ b/modules/rph/usb/driver.py @@ -0,0 +1,115 @@ +# Modular Python Bitcoin Miner +# Copyright (C) 2012 Michael Sparmann (TheSeven) +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you +# want to support further development of the Modular Python Bitcoin Miner. + + + +######################################### +# rph usb level driver # +######################################### + + + +import time +import usb +import struct +import traceback +from array import array +from threading import RLock +from binascii import hexlify, unhexlify + + + +class rphUSBDevice(object): + + + def __init__(self, proxy, serial, takeover, firmware): + self.lock = RLock() + self.proxy = proxy + self.serial = serial + self.takeover = takeover + self.firmware = firmware + self.handle = None + self.rxdata = array("B") + permissionproblem = False + deviceinuse = False + for bus in usb.busses(): + if self.handle != None: break + for dev in bus.devices: + if self.handle != None: break + if dev.idVendor == 0xf1f0: + try: + handle = dev.open() + _serial = hexlify(handle.controlMsg(0xc0, 0x96, 16, 0, 0, 100)) + #_serial = "0" # handle.getString(dev.iSerialNumber, 100).decode("latin1") + if serial == "" or serial == _serial: + try: + if self.takeover: + handle.reset() + time.sleep(1) + configuration = dev.configurations[0] + interface = configuration.interfaces[0][0] + handle.setConfiguration(configuration.value) + handle.claimInterface(interface.interfaceNumber) + handle.setAltInterface(interface.alternateSetting) + self.handle = handle + self.serial = _serial + except: deviceinuse = True + except: permissionproblem = True + if self.handle == None: + if deviceinuse: + raise Exception("Can not open the specified device, possibly because it is already in use") + if permissionproblem: + raise Exception("Can not open the specified device, possibly due to insufficient permissions") + raise Exception("Can not open the specified device") + + + def set_multiplier(self, multiplier): + return None + #with self.lock: + # self.handle.controlMsg(0x40, 0x83, b"", multiplier, 0, 100) + + + def send_job(self, data): + with self.lock: + self.handle.controlMsg(0x40, 0x40, unhexlify("40") + data, 0, 0, 100) + + + def read_nonces(self): + with self.lock: + char = '' + try: + char = self.handle.controlMsg(0xc0, 0x41, 1, 0, 0, 100) + except: + time.sleep(0.01) + pass + if len(char): + self.rxdata = self.rxdata + char + nonces = [] + if len(self.rxdata) >= 4: + golden = self.rxdata[0:4] + golden = golden[::-1] + golden = golden.tostring() + nonces.append(struct.unpack(" +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you +# want to support further development of the Modular Python Bitcoin Miner. + + + +################################################### +# rph usb hotplug controller module # +# https://bitcointalk.org/index.php?topic=44891.0 # +################################################### + + + +import traceback +from binascii import hexlify +from threading import Condition, Thread +from core.baseworker import BaseWorker +from .rphusbworker import rphUSBWorker + + + +# Worker main class, referenced from __init__.py +class rphUSBHotplugWorker(BaseWorker): + + version = "rph.usb hotplug manager v0.0.1" + default_name = "rph usb hotplug manager" + can_autodetect = True + settings = dict(BaseWorker.settings, **{ + #"takeover": {"title": "Reset board if it appears to be in use", "type": "boolean", "position": 1200}, + #"firmware": {"title": "Firmware file location", "type": "string", "position": 1400}, + "blacklist": { + "title": "Board list type", + "type": "enum", + "values": [ + {"value": True, "title": "Blacklist"}, + {"value": False, "title": "Whitelist"}, + ], + "position": 2000 + }, + "boards": { + "title": "Board list", + "type": "list", + "element": {"title": "Serial number", "type": "string"}, + "position": 2100 + }, + "scaninterval": {"title": "Bus scan interval", "type": "float", "position": 2200}, + "jobinterval": {"title": "Job interval", "type": "float", "position": 5100}, + "pollinterval": {"title": "Poll interval", "type": "float", "position": 5200}, + }) + + + @classmethod + def autodetect(self, core): + try: + found = False + try: + import usb + for bus in usb.busses(): + for dev in bus.devices: + if dev.idVendor == 0xf1f0: + try: + handle = dev.open() + serial = hexlify(handle.controlMsg(0xc0, 0x96, 16, 0, 0, 100)) + try: + configuration = dev.configurations[0] + interface = configuration.interfaces[0][0] + handle.setConfiguration(configuration.value) + handle.claimInterface(interface.interfaceNumber) + handle.releaseInterface() + handle.setConfiguration(0) + found = True + break + except: pass + except: pass + if found: break + except: pass + if found: core.add_worker(self(core)) + except: pass + + + # Constructor, gets passed a reference to the miner core and the saved worker state, if present + def __init__(self, core, state = None): + # Initialize bus scanner wakeup event + self.wakeup = Condition() + + # Let our superclass do some basic initialization and restore the state if neccessary + super(rphUSBHotplugWorker, self).__init__(core, state) + + + # Validate settings, filling them with default values if neccessary. + # Called from the constructor and after every settings change. + def apply_settings(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBHotplugWorker, self).apply_settings() + if not "serial" in self.settings: self.settings.serial = None + if not "takeover" in self.settings: self.settings.takeover = True + if not "firmware" in self.settings or not self.settings.firmware: + self.settings.firmware = "modules/ztex/firmware/" + if not "blacklist" in self.settings: self.settings.blacklist = True + if self.settings.blacklist == "false": self.settings.blacklist = False + else: self.settings.blacklist = not not self.settings.blacklist + if not "boards" in self.settings: self.settings.boards = [] + if not "jobinterval" in self.settings or not self.settings.jobinterval: self.settings.jobinterval = 60 + if not "pollinterval" in self.settings or not self.settings.pollinterval: self.settings.pollinterval = 0.1 + if not "scaninterval" in self.settings or not self.settings.scaninterval: self.settings.scaninterval = 10 + # Push our settings down to our children + fields = ["takeover", "firmware", "jobinterval", "pollinterval"] + for child in self.children: + for field in fields: child.settings[field] = self.settings[field] + child.apply_settings() + # Rescan the bus immediately to apply the new settings + with self.wakeup: self.wakeup.notify() + + + # Reset our state. Called both from the constructor and from self.start(). + def _reset(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBHotplugWorker, self)._reset() + # These need to be set here in order to make the equality check in apply_settings() happy, + # when it is run before starting the module for the first time. (It is called from the constructor.) + + + # Start up the worker module. This is protected against multiple calls and concurrency by a wrapper. + def _start(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBHotplugWorker, self)._start() + # Initialize child map + self.childmap = {} + # Reset the shutdown flag for our threads + self.shutdown = False + # Start up the main thread, which handles pushing work to the device. + self.mainthread = Thread(None, self.main, self.settings.name + "_main") + self.mainthread.daemon = True + self.mainthread.start() + + + # Shut down the worker module. This is protected against multiple calls and concurrency by a wrapper. + def _stop(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBHotplugWorker, self)._stop() + # Set the shutdown flag for our threads, making them terminate ASAP. + self.shutdown = True + # Trigger the main thread's wakeup flag, to make it actually look at the shutdown flag. + with self.wakeup: self.wakeup.notify() + # Wait for the main thread to terminate. + self.mainthread.join(10) + # Shut down child workers + while self.children: + child = self.children.pop(0) + try: + self.core.log(self, "Shutting down worker %s...\n" % (child.settings.name), 800) + child.stop() + except Exception as e: + self.core.log(self, "Could not stop worker %s: %s\n" % (child.settings.name, traceback.format_exc()), 100, "rB") + + + # Main thread entry point + # This thread is responsible for scanning for boards and spawning worker modules for them + def main(self): + # Loop until we are shut down + while not self.shutdown: + + import usb + + try: + boards = {} + for bus in usb.busses(): + for dev in bus.devices: + if dev.idVendor == 0xf1f0: + try: + handle = dev.open() + serial = hexlify(handle.controlMsg(0xc0, 0x96, 16, 0, 0, 100)) + try: + configuration = dev.configurations[0] + interface = configuration.interfaces[0][0] + handle.setConfiguration(configuration.value) + handle.claimInterface(interface.interfaceNumber) + handle.releaseInterface() + handle.setConfiguration(0) + available = True + except: available = False + boards[serial] = available + except: pass + + for serial in boards.keys(): + if self.settings.blacklist: + if serial in self.settings.boards: del boards[serial] + else: + if serial not in self.settings.boards: del boards[serial] + + kill = [] + for serial, child in self.childmap.items(): + if not serial in boards: + kill.append((serial, child)) + + for serial, child in kill: + try: + self.core.log(self, "Shutting down worker %s...\n" % (child.settings.name), 800) + child.stop() + except Exception as e: + self.core.log(self, "Could not stop worker %s: %s\n" % (child.settings.name, traceback.format_exc()), 100, "rB") + childstats = child.get_statistics() + fields = ["ghashes", "jobsaccepted", "jobscanceled", "sharesaccepted", "sharesrejected", "sharesinvalid"] + for field in fields: self.stats[field] += childstats[field] + try: self.child.destroy() + except: pass + del self.childmap[serial] + try: self.children.remove(child) + except: pass + + for serial, available in boards.items(): + if serial in self.childmap: continue + if not available and self.settings.takeover: + try: + for bus in usb.busses(): + if available: break + for dev in bus.devices: + if available: break + if dev.idVendor == 0xf1f0: + handle = dev.open() + _serial = hexlify(handle.controlMsg(0xc0, 0x96, 16, 0, 0, 100)) + if _serial == serial: + handle.reset() + time.sleep(1) + configuration = dev.configurations[0] + interface = configuration.interfaces[0][0] + handle.setConfiguration(configuration.value) + handle.claimInterface(interface.interfaceNumber) + handle.releaseInterface() + handle.setConfiguration(0) + handle.reset() + time.sleep(1) + available = True + except: pass + if available: + child = rphUSBWorker(self.core) + child.settings.name = "rph USB " + serial + child.settings.serial = serial + fields = ["takeover", "firmware", "jobinterval", "pollinterval"] + for field in fields: child.settings[field] = self.settings[field] + child.apply_settings() + self.childmap[serial] = child + self.children.append(child) + try: + self.core.log(self, "Starting up worker %s...\n" % (child.settings.name), 800) + child.start() + except Exception as e: + self.core.log(self, "Could not start worker %s: %s\n" % (child.settings.name, traceback.format_exc()), 100, "rB") + + except: self.core.log(self, "Caught exception: %s\n" % traceback.format_exc(), 100, "rB") + + with self.wakeup: self.wakeup.wait(self.settings.scaninterval) diff --git a/modules/rph/usb/rphusbworker.py b/modules/rph/usb/rphusbworker.py new file mode 100644 index 0000000..4db97a1 --- /dev/null +++ b/modules/rph/usb/rphusbworker.py @@ -0,0 +1,383 @@ +# Modular Python Bitcoin Miner +# Copyright (C) 2012 Michael Sparmann (TheSeven) +# Copyright (C) 2011-2012 rphlx +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you +# want to support further development of the Modular Python Bitcoin Miner. + + + +################################################### +# rph usb worker interface module # +# https://bitcointalk.org/index.php?topic=44891.0 # +################################################### + + + +import time +import traceback +from multiprocessing import Pipe +from threading import RLock, Condition, Thread +from core.baseworker import BaseWorker +from .boardproxy import rphUSBBoardProxy +try: from queue import Queue +except: from Queue import Queue + + + +# Worker main class, referenced from __init__.py +class rphUSBWorker(BaseWorker): + + version = "rph.usb worker v0.0.1" + default_name = "Untitled rph USB worker" + settings = dict(BaseWorker.settings, **{ + "serial": {"title": "Board serial number", "type": "string", "position": 1000}, + #"takeover": {"title": "Reset board if it appears to be in use", "type": "boolean", "position": 1200}, + #"firmware": {"title": "Firmware base path", "type": "string", "position": 1400}, + "jobinterval": {"title": "Job interval", "type": "float", "position": 4100}, + "pollinterval": {"title": "Poll interval", "type": "float", "position": 4200}, + }) + + + # Constructor, gets passed a reference to the miner core and the saved worker state, if present + def __init__(self, core, state = None): + # Let our superclass do some basic initialization and restore the state if neccessary + super(rphUSBWorker, self).__init__(core, state) + + # Initialize proxy access locks and wakeup event + self.lock = RLock() + self.transactionlock = RLock() + self.wakeup = Condition() + self.workloopwakeup = Condition() + + + # Validate settings, filling them with default values if neccessary. + # Called from the constructor and after every settings change. + def apply_settings(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBWorker, self).apply_settings() + if not "serial" in self.settings: self.settings.serial = None + if not "takeover" in self.settings: self.settings.takeover = False + if not "firmware" in self.settings or not self.settings.firmware: + self.settings.firmware = "modules/fpgamining/x6500/firmware/x6500.bit" + if not "jobinterval" in self.settings or not self.settings.jobinterval: self.settings.jobinterval = 60 + if not "pollinterval" in self.settings or not self.settings.pollinterval: self.settings.pollinterval = 0.1 + # We can't switch the device on the fly, so trigger a restart if they changed. + # self.serial is a cached copy of self.settings.serial. + if self.started and self.settings.serial != self.serial: self.async_restart() + # We need to inform the proxy about a poll interval change + if self.started and self.settings.pollinterval != self.pollinterval: self._notify_poll_interval_changed() + + + # Reset our state. Called both from the constructor and from self.start(). + def _reset(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBWorker, self)._reset() + # These need to be set here in order to make the equality check in apply_settings() happy, + # when it is run before starting the module for the first time. (It is called from the constructor.) + self.serial = None + self.pollinterval = None + self.stats.mhps = 0 + self.stats.errorrate = 0 + + + # Start up the worker module. This is protected against multiple calls and concurrency by a wrapper. + def _start(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBWorker, self)._start() + # Cache the port number and baud rate, as we don't like those to change on the fly + self.serial = self.settings.serial + # Reset the shutdown flag for our threads + self.shutdown = False + # Start up the main thread, which handles pushing work to the device. + self.mainthread = Thread(None, self.main, self.settings.name + "_main") + self.mainthread.daemon = True + self.mainthread.start() + + + # Stut down the worker module. This is protected against multiple calls and concurrency by a wrapper. + def _stop(self): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBWorker, self)._stop() + # Set the shutdown flag for our threads, making them terminate ASAP. + self.shutdown = True + # Trigger the main thread's wakeup flag, to make it actually look at the shutdown flag. + with self.wakeup: self.wakeup.notify() + # Ping the proxy, otherwise the main thread might be blocked and can't wake up. + try: self._proxy_message("ping") + except: pass + # Wait for the main thread to terminate, which in turn kills the child workers. + self.mainthread.join(10) + + + # Report custom statistics. + def _get_statistics(self, stats, childstats): + # Let our superclass handle everything that isn't specific to this worker module + super(rphUSBWorker, self)._get_statistics(stats, childstats) + stats.errorrate = self.stats.errorrate + + + # Main thread entry point + # This thread is responsible for booting the individual FPGAs and spawning worker threads for them + def main(self): + # If we're currently shutting down, just die. If not, loop forever, + # to recover from possible errors caught by the huge try statement inside this loop. + # Count how often the except for that try was hit recently. This will be reset if + # there was no exception for at least 5 minutes since the last one. + tries = 0 + while not self.shutdown: + try: + # Record our starting timestamp, in order to back off if we repeatedly die + starttime = time.time() + self.dead = False + + # Check if we have a device serial number + if not self.serial: raise Exception("Device serial number not set!") + + # Try to start the board proxy + proxy_rxconn, self.txconn = Pipe(False) + self.rxconn, proxy_txconn = Pipe(False) + self.pollinterval = self.settings.pollinterval + self.proxy = rphUSBBoardProxy(proxy_rxconn, proxy_txconn, self.serial, + self.settings.takeover, self.settings.firmware, self.pollinterval) + self.proxy.daemon = True + self.proxy.start() + proxy_txconn.close() + self.response = None + self.response_queue = Queue() + + # Tell the board proxy to connect to the board + self._proxy_message("connect") + + while not self.shutdown: + data = self.rxconn.recv() + if self.dead: break + if data[0] == "log": self.core.log(self, "Proxy: %s" % data[1], data[2], data[3]) + elif data[0] == "ping": self._proxy_message("pong") + elif data[0] == "pong": pass + elif data[0] == "dying": raise Exception("Proxy died!") + elif data[0] == "response": self.response_queue.put(data[1:]) + elif data[0] == "started_up": self._notify_proxy_started_up(*data[1:]) + elif data[0] == "nonce_found": self._notify_nonce_found(*data[1:]) + elif data[0] == "speed_changed": self._notify_speed_changed(*data[1:]) + elif data[0] == "error_rate": self._notify_error_rate(*data[1:]) + elif data[0] == "keyspace_exhausted": self._notify_keyspace_exhausted(*data[1:]) + else: raise Exception("Proxy sent unknown message: %s" % str(data)) + + + # If something went wrong... + except Exception as e: + # ...complain about it! + self.core.log(self, "%s\n" % traceback.format_exc(), 100, "rB") + finally: + with self.workloopwakeup: self.workloopwakeup.notify() + try: + for i in range(100): self.response_queue.put(None) + except: pass + try: self.workloopthread.join(2) + except: pass + try: self._proxy_message("shutdown") + except: pass + try: self.proxy.join(4) + except: pass + if not self.shutdown: + tries += 1 + if time.time() - starttime >= 300: tries = 0 + with self.wakeup: + if tries > 5: self.wakeup.wait(30) + else: self.wakeup.wait(1) + # Restart (handled by "while not self.shutdown:" loop above) + + + def _proxy_message(self, *args): + with self.lock: + self.txconn.send(args) + + + def _proxy_transaction(self, *args): + with self.transactionlock: + with self.lock: + self.txconn.send(args) + return self.response_queue.get() + + + def _notify_poll_interval_changed(self): + self.pollinterval = self.settings.pollinterval + try: self._proxy_message("set_pollinterval", self.pollinterval) + except: pass + + + def _notify_proxy_started_up(self): + # Assume a default job interval to make the core start fetching work for us. + # The actual hashrate will be measured (and this adjusted to the correct value) later. + self.jobs_per_second = 1. / self.settings.jobinterval + # This worker will only ever process one job at once. The work fetcher needs this information + # to estimate how many jobs might be required at once in the worst case (after a block was found). + self.parallel_jobs = 1 + # Start up the work loop thread, which handles pushing work to the device. + self.workloopthread = Thread(None, self._workloop, self.settings.name + "_workloop") + self.workloopthread.daemon = True + self.workloopthread.start() + + + def _notify_nonce_found(self, now, nonce): + # Snapshot the current jobs to avoid race conditions + oldjob = self.oldjob + newjob = self.job + # If there is no job, this must be a leftover from somewhere, e.g. previous invocation + # or reiterating the keyspace because we couldn't provide new work fast enough. + # In both cases we can't make any use of that nonce, so just discard it. + if not oldjob and not newjob: return + # Pass the nonce that we found to the work source, if there is one. + # Do this before calculating the hash rate as it is latency critical. + job = None + if newjob: + if newjob.nonce_found(nonce, oldjob): job = newjob + if not job and oldjob: + if oldjob.nonce_found(nonce): job = oldjob + + + def _notify_speed_changed(self, speed): + self.stats.mhps = speed / 1000000. + self.core.event(350, self, "speed", self.stats.mhps * 1000, "%f MH/s" % self.stats.mhps, worker = self) + self.core.log(self, "Running at %f MH/s\n" % self.stats.mhps, 300, "B") + # Calculate the time that the device will need to process 2**32 nonces. + # This is limited at 60 seconds in order to have some regular communication, + # even with very slow devices (and e.g. detect if the device was unplugged). + interval = min(60, 2**32 / speed) + # Add some safety margin and take user's interval setting (if present) into account. + self.jobinterval = min(self.settings.jobinterval, max(0.5, interval * 0.8 - 1)) + self.core.log(self, "Job interval: %f seconds\n" % self.jobinterval, 400, "B") + # Tell the MPBM core that our hash rate has changed, so that it can adjust its work buffer. + self.jobs_per_second = 1. / self.jobinterval + self.core.notify_speed_changed(self) + + + def _notify_error_rate(self, rate): + self.stats.errorrate = rate + + + def _notify_keyspace_exhausted(self): + with self.workloopwakeup: self.workloopwakeup.notify() + self.core.log(self, "Exhausted keyspace!\n", 200, "y") + + + def _send_job(self, job): + return self._proxy_transaction("send_job", job.data, job.midstate) + + + # This function should interrupt processing of the specified job if possible. + # This is necesary to avoid producing stale shares after a new block was found, + # or if a job expires for some other reason. If we don't know about the job, just ignore it. + # Never attempts to fetch a new job in here, always do that asynchronously! + # This needs to be very lightweight and fast. We don't care whether it's a + # graceful cancellation for this module because the work upload overhead is low. + def notify_canceled(self, job, graceful): + # Acquire the wakeup lock to make sure that nobody modifies job/nextjob while we're looking at them. + with self.workloopwakeup: + # If the currently being processed, or currently being uploaded job are affected, + # wake up the main thread so that it can request and upload a new job immediately. + if self.job == job: self.workloopwakeup.notify() + + + # Main thread entry point + # This thread is responsible for fetching work and pushing it to the device. + def _workloop(self): + try: + # Job that the device is currently working on, or that is currently being uploaded. + # This variable is used by BaseWorker to figure out the current work source for statistics. + self.job = None + # Job that was previously being procesed. Has been destroyed, but there might be some late nonces. + self.oldjob = None + + # We keep control of the wakeup lock at all times unless we're sleeping + self.workloopwakeup.acquire() + # Eat up leftover wakeups + self.workloopwakeup.wait(0) + + # Main loop, continues until something goes wrong or we're shutting down. + while not self.shutdown: + + # Fetch a job, add 2 seconds safety margin to the requested minimum expiration time. + # Blocks until one is available. Because of this we need to release the + # wakeup lock temporarily in order to avoid possible deadlocks. + self.workloopwakeup.release() + job = self.core.get_job(self, self.jobinterval + 2) + self.workloopwakeup.acquire() + + # If a new block was found while we were fetching that job, just discard it and get a new one. + if job.canceled: + job.destroy() + continue + + # Upload the job to the device + self._sendjob(job) + + # If the job was already caught by a long poll while we were uploading it, + # jump back to the beginning of the main loop in order to immediately fetch new work. + if self.job.canceled: continue + # Wait while the device is processing the job. If nonces are sent by the device, they + # will be processed by the listener thread. If the job gets canceled, we will be woken up. + self.workloopwakeup.wait(self.jobinterval) + + # If something went wrong... + except Exception as e: + # ...complain about it! + self.core.log(self, "%s\n" % traceback.format_exc(), 100, "rB") + finally: + # We're not doing productive work any more, update stats and destroy current job + self._jobend() + self.stats.mhps = 0 + # Make the proxy and its listener thread restart + self.dead = True + try: self.workloopwakeup.release() + except: pass + # Ping the proxy, otherwise the main thread might be blocked and can't wake up. + try: self._proxy_message("ping") + except: pass + + + # This function uploads a job to the device + def _sendjob(self, job): + # Move previous job to oldjob, and new one to job + self.oldjob = self.job + self.job = job + # Send it to the FPGA + start, now = self._send_job(job) + # Calculate how long the old job was running + if self.oldjob: + if self.oldjob.starttime: + self.oldjob.hashes_processed((now - self.oldjob.starttime) * self.stats.mhps * 1000000) + self.oldjob.destroy() + self.job.starttime = now + + + # This function needs to be called whenever the device terminates working on a job. + # It calculates how much work was actually done for the job and destroys it. + def _jobend(self, now = None): + # Hack to avoid a python bug, don't integrate this into the line above + if not now: now = time.time() + # Calculate how long the job was actually running and multiply that by the hash + # rate to get the number of hashes calculated for that job and update statistics. + if self.job != None: + if self.job.starttime: + self.job.hashes_processed((now - self.job.starttime) * self.stats.mhps * 1000000) + # Destroy the job, which is neccessary to actually account the calculated amount + # of work to the worker and work source, and to remove the job from cancelation lists. + self.oldjob = self.job + self.job.destroy() + self.job = None diff --git a/modules/rph/usb/usbWorker.py b/modules/rph/usb/usbWorker.py deleted file mode 100644 index 3d1d1af..0000000 --- a/modules/rph/usb/usbWorker.py +++ /dev/null @@ -1,193 +0,0 @@ -# Modular Python Bitcoin Miner - rph USB -# Copyright (C) 2011-2012 Michael Sparmann (TheSeven) -# Copyright (C) 2011-2012 rphlx -# -# 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# Please consider donating to 1PLAPWDejJPJnY2ppYCgtw5ko8G5Q4hPzh if you -# want to support further development of the Modular Python Bitcoin Miner. - -################################################### -# rph usb worker interface module # -# https://bitcointalk.org/index.php?topic=44891.0 # -################################################### - -import common -import usb -import array -import binascii -import threading -import time -import struct - -class usbWorker(object): - def __init__(self, miner, dict): - self.__dict__ = dict - self.miner = miner - self.children = [] - self.uid = getattr(self, "uid", "") - self.idx = getattr(self, "idx", 0) - self.name = getattr(self, "name", "rph" + str(self.idx)) - self.jobinterval = getattr(self, "jobinterval", 30) - self.jobspersecond = 1. / self.jobinterval # Used by work buffering algorithm - self.mhps = 0 - self.mhashes = 0 - self.jobsaccepted = 0 - self.accepted = 0 - self.rejected = 0 - self.invalid = 0 - self.starttime = time.time() - self.statlock = threading.RLock() - self.mainthread = threading.Thread(None, self.main, self.name + "_main") - self.mainthread.daemon = True - self.mainthread.start() - - # Report statistics about this worker module and its (non-existant) children. - def getstatistics(self, childstats): - # Acquire the statistics lock to stop statistics from changing while we deal with them - with self.statlock: - # Calculate statistics - statistics = { \ - "name": self.name, \ - "children": childstats, \ - "mhashes": self.mhashes, \ - "mhps": self.mhps, \ - "jobsaccepted": self.jobsaccepted, \ - "accepted": self.accepted, \ - "rejected": self.rejected, \ - "invalid": self.invalid, \ - "starttime": self.starttime, \ - "currentpool": self.job.pool.name if self.job != None and self.job.pool != None else None, \ - } - # Return result - return statistics - - def cancel(self, blockchain): - if self.job != None and self.job.pool != None and self.job.pool.blockchain == blockchain: - self.canceled = True - - def find_dev(self): - dev = usb.core.find(idVendor=0xf1f0, find_all=True) - for d in dev: - try: - uid = d.ctrl_transfer(0xc0, 0x96, 0, 0, 16, 5000) - except: - # fallback for old firmware revisions that don't implement uid. - uid = binascii.unhexlify("00000000000000000000000000000000") - pass - uid = binascii.hexlify(uid) - #self.miner.log("got uid : " + uid + "\n", "") - #self.miner.log("want uid: " + self.uid + "\n", "") - if self.uid == "" or self.uid == uid: - self.dev = d - return - raise Exception("unable to find miner") - - def main(self): - while True: - try: - self.error = None - self.mhps = 0 - self.job = None - self.checksuccess = False - self.cancelled = False - self.find_dev() - # I'm too sexy for this job. Too sexy for this job. Too sexy: - #job = common.Job(None, binascii.unhexlify("1625cbf1a5bc6ba648d1218441389e00a9dc79768a2fc6f2b79c70cf576febd0"), "\0" * 64 + binascii.unhexlify("4c0afa494de837d81a269421"), binascii.unhexlify("7bc2b302")) - job = common.Job(self.miner, None, None, binascii.unhexlify("0d840c5cc3def3dfdb1dfaf01da77e451c2e786d15fe0876836a6999a4f0fc79"), "\0" * 64 + binascii.unhexlify("12f2f7f34f027f0c1a0e76ba"), None, binascii.unhexlify("d0c984a9")) - self.sendjob(job) - self.polljob() - if self.error != None: raise self.error - if not self.checksuccess: raise Exception("Timeout waiting for validation job to finish") - self.miner.log(self.name + ": Running at %f MH/s\n" % self.mhps, "B") - interval = min(30, 2**32 / 1000000. / self.mhps) - self.jobinterval = min(self.jobinterval, max(0.5, interval * 0.9)) - self.miner.log(self.name + ": Job interval: %f seconds\n" % self.jobinterval, "B") - self.jobspersecond = 1. / self.jobinterval - self.miner.updatehashrate(self) - while True: - self.canceled = False - job = self.miner.getjob(self) - self.jobsaccepted = self.jobsaccepted + 1 - if self.canceled == True: - if job.longpollepoch != job.pool.blockchain.longpollepoch: continue - self.canceled = False; - if self.error != None: raise self.error - self.sendjob(job) - self.polljob() - if self.error != None: raise self.error - except Exception as e: - self.miner.log(self.name + ": %s\n" % e, "rB") - self.error = e - self.mhps = 0 - time.sleep(1) - - # poll USB MCU, ~1000 times per second, checking for nonce data, - # a job timeout, or long poll cancellation - def polljob(self): - try: - done = False - a = array.array('B') - while True: - if self.error != None: break - if self.cancelled: break - # ignore pipe errors. (bug in pyusb? they never happen with the C implementation..) - try: - data = self.dev.ctrl_transfer(0xc0, 0x41, 0, 0, 1, 5000) - if len(data): - a = a + data - except: - time.sleep(0.01) - pass - now = time.time() - if len(a) >= 4: - golden = a[0:4] - golden = golden[::-1] - golden = golden.tostring() - self.job.sendresult(golden, self) - delta = (now - self.job.starttime) - self.mhps = struct.unpack("= self.jobinterval: - # TODO: adjust for communication delays. - if self.job != None and self.job.pool != None: - mhashes = (now - self.job.starttime) * self.mhps - self.job.finish(mhashes, self) - break - except Exception as e: - self.error = e - - def sendjob(self, job): - cmd = binascii.unhexlify("40") - self.dev.ctrl_transfer(0x40, 0x40, 0, 0, cmd + job.state[::-1] + job.data[75:63:-1], 5000) - self.job = job - self.job.starttime = time.time() - # drain any leftover golden chars from the old job. - time.sleep(0.01) - try: - while True: - data = self.dev.ctrl_transfer(0xc0, 0x41, 0, 0, 1, 5000) - if len(data) <= 0: - break - except: - time.sleep(0.01) - pass From 089dd92039bcd387e0a31fed029f8551ec0df36b Mon Sep 17 00:00:00 2001 From: rphlx Date: Wed, 20 Jun 2012 01:46:26 -0700 Subject: [PATCH 5/5] rph usb temperature monitoring --- modules/rph/usb/boardproxy.py | 12 ++++++++---- modules/rph/usb/driver.py | 18 +++++++++++++++++- modules/rph/usb/rphusbworker.py | 8 ++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/modules/rph/usb/boardproxy.py b/modules/rph/usb/boardproxy.py index 9837b2c..1b626e0 100644 --- a/modules/rph/usb/boardproxy.py +++ b/modules/rph/usb/boardproxy.py @@ -141,11 +141,8 @@ def polling_thread(self): try: lastshares = [] counter = 0 - + while not self.shutdown: - - counter += 1 - # Poll for nonces now = time.time() nonces = self.device.read_nonces() @@ -161,6 +158,13 @@ def polling_thread(self): lastshares.append(nonce[0]) while len(lastshares) > len(nonces): lastshares.pop(0) + counter += 1 + if counter >= 10: + counter = 0 + # Read temperatures + temp = self.device.read_temps() + self.send("temperature_read", temp) + with self.wakeup: self.wakeup.wait(self.pollinterval) except Exception as e: diff --git a/modules/rph/usb/driver.py b/modules/rph/usb/driver.py index b0ed3b0..1a08905 100644 --- a/modules/rph/usb/driver.py +++ b/modules/rph/usb/driver.py @@ -112,4 +112,20 @@ def read_nonces(self): # values = struct.unpack("