From cd888ded328421cca386403e36ace9b659bbedfe Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 30 Mar 2015 11:08:50 +0200 Subject: [PATCH 01/34] Added USB print stub --- USBPrintDevice.py | 6 ++++++ __init__.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 USBPrintDevice.py create mode 100644 __init__.py diff --git a/USBPrintDevice.py b/USBPrintDevice.py new file mode 100644 index 0000000000..3f5f57fbea --- /dev/null +++ b/USBPrintDevice.py @@ -0,0 +1,6 @@ +from UM.StorageDevice import StorageDevice +from UM.Signal import Signal, SignalEmitter + +class USBPrintDevice(StorageDevice, SignalEmitter): + def __init__(self): + super().__init__() \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000..05b12c8e15 --- /dev/null +++ b/__init__.py @@ -0,0 +1,15 @@ +from . import USBPrintDevice + +def getMetaData(): + return { + 'type': 'storage_device', + 'plugin': { + 'name': 'Local File Storage', + 'author': 'Jaime van Kessel', + 'version': '1.0', + 'description': 'Accepts G-Code and sends them to a printer. ' + } + } + +def register(app): + return USBPrintDevice.USBPrintDevice() \ No newline at end of file From b4df277cc32c9f4a23b41a36bec687e7370e95d3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 09:41:58 +0200 Subject: [PATCH 02/34] Refactoring of USBPrinting plugin --- PrinterConnection.py | 150 ++++++++++++++++++++++++++++++ USBPrintDevice.py | 6 -- USBPrinterManager.py | 81 ++++++++++++++++ __init__.py | 4 +- avr_isp/__init__.py | 0 avr_isp/chipDB.py | 25 +++++ avr_isp/intelHex.py | 46 +++++++++ avr_isp/ispBase.py | 63 +++++++++++++ avr_isp/stk500v2.py | 216 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 583 insertions(+), 8 deletions(-) create mode 100644 PrinterConnection.py delete mode 100644 USBPrintDevice.py create mode 100644 USBPrinterManager.py create mode 100644 avr_isp/__init__.py create mode 100644 avr_isp/chipDB.py create mode 100644 avr_isp/intelHex.py create mode 100644 avr_isp/ispBase.py create mode 100644 avr_isp/stk500v2.py diff --git a/PrinterConnection.py b/PrinterConnection.py new file mode 100644 index 0000000000..d96c7b1055 --- /dev/null +++ b/PrinterConnection.py @@ -0,0 +1,150 @@ +from UM.Logger import Logger +from .avr_isp import stk500v2 + +class PrinterConnection(): + def __init__(self, serial_port): + super().__init__() + + self._serial = None + self._serial_port = serial_port + self._error_state = None + + self._connect_thread = threading.Thread(target = self._connect) + self._connect_thread.daemon = True + + self._is_connected = False + self._is_connecting = False + self._required_responses_auto_baud = 10 + + self._listen_thread = threading.Thread(target=self._listen) + self._listen_thread.daemon = True + #self._listen_thread.start() + + ## Try to connect the serial. This simply starts the thread. + def connect(self): + self._connect_thread.start() + + def _connect(self): + self._is_connecting = True + programmer.connect(serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. + try: + self._serial = programmer.leaveISP() + # Create new printer connection + self.active_printer_connection = PrinterConnection(temp_serial) + Logger.log('i', "Established connection on port %s" % serial_port) + break + except ispBase.IspError as (e): + Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(serial_port,str(e))) + except: + Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % serial_port) + + if self._serial is None: + #Device is not arduino based, so we need to cycle the baud rates. + for baud_rate in self._getBaudrateList(): + timeout_time = time.time() + 5 + if self._serial = None: + self._serial = serial.Serial(str(self._port), baud_rate, timeout=5, writeTimeout=10000) + else: + if not self.setBaudRate(baud_rate): + continue #Could not set the baud rate, go to the next + sucesfull_responses = 0 + while timeout_time > time.time(): + line = self._readline(): + if "T:" in line: + self._serial.timeout = 0.5 + self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it + sucesfull_responses += 1 + if sucesfull_responses >= self._required_responses_auto_baud: + self.setIsConnected(True) + return + self.setIsConnected(False) + else: + self.setIsConnected(True) + return #Stop trying to connect, we are connected. + + def _listen(self): + time.sleep(5) + pass + + def setBaudRate(self, baud_rate): + try: + self._serial.baudrate = baud_rate + except: + return False + + def setIsConnected(self, state): + self._is_connecting = False + if state != state: + self._is_connected = state + else: + Logger.log('w', "Printer connection state was not changed") + + if self._is_connected: + self._listen_thread.start() #Start listening + + + def isConnected(self): + return self._is_connected = True + + def _listen(self): + while True: + line = self._readline() + if line is None: + break #None is only returned when something went wrong. Stop listening + + if line.startswith('Error:'): + #Oh YEAH, consistency. + # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" + # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" + # So we can have an extra newline in the most common case. Awesome work people. + if re.match('Error:[0-9]\n', line): + line = line.rstrip() + self._readline() + #Skip the communication errors, as those get corrected. + if 'Extruder switched off' in line or 'Temperature heated bed switched off' in line or 'Something is wrong, please turn off the printer.' in line: + if not self.hasError(): + self._error_state = line[6:] + if ' T:' in line or line.startswith('T:'): #Temperature message + try: + print("TEMPERATURE", float(re.search("T: *([0-9\.]*)", line).group(1))) + except: + pass + if 'B:' in line: #Check if it's a bed temperature + try: + print("BED TEMPERATURE" ,float(re.search("B: *([0-9\.]*)", line).group(1))) + except: + pass + #TODO: temperature changed callback + + + + def hasError(self): + return self._error_state == None ? False : True + + + def _readline(self): + if self._serial is None: + return None + try: + ret = self._serial.readline() + except: + self._log("Unexpected error while reading serial port.")) + self._errorValue = getExceptionString() + self.close(True) + return None + if ret == '': + return '' + #self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip())) + return ret + + + ## Create a list of baud rates at which we can communicate. + # \return list of int + def _getBaudrateList(): + ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600] + #if profile.getMachineSetting('serial_baud_auto') != '': + #prev = int(profile.getMachineSetting('serial_baud_auto')) + #if prev in ret: + #ret.remove(prev) + #ret.insert(0, prev) + return ret + \ No newline at end of file diff --git a/USBPrintDevice.py b/USBPrintDevice.py deleted file mode 100644 index 3f5f57fbea..0000000000 --- a/USBPrintDevice.py +++ /dev/null @@ -1,6 +0,0 @@ -from UM.StorageDevice import StorageDevice -from UM.Signal import Signal, SignalEmitter - -class USBPrintDevice(StorageDevice, SignalEmitter): - def __init__(self): - super().__init__() \ No newline at end of file diff --git a/USBPrinterManager.py b/USBPrinterManager.py new file mode 100644 index 0000000000..af21f29c69 --- /dev/null +++ b/USBPrinterManager.py @@ -0,0 +1,81 @@ +from UM.Signal import Signal, SignalEmitter +from UM.PluginObject import PluginObject + +import threading +import platform +import glob +import time + +class USBPrinterManager(SignalEmitter,PluginObject): + def __init__(self): + super().__init__() + self._serial_port_list = [] + self._printer_connections = [] + + self._check_ports_thread = threading.Thread(target=self._updateConnectionList) + self._check_ports_thread.daemon = True + self._check_ports_thread.start() + + ## Check all serial ports and create a PrinterConnection object for them. + # Note that this does not validate if the serial ports are actually usable! + # This is only done when the connect function is called. + def _updateConnectionList(self): + while True: + temp_serial_port_list = self.getSerialPortList(only_list_usb = True) + if temp_serial_port_list != self._serial_port_list: # Something changed about the list since we last changed something. + disconnected_ports = [port for port in self._serial_port_list if port not in temp_serial_port_list ] + self._serial_port_list = temp_serial.port_list + + for serial_port in self._serial_port_list: + if self.getConnectionByPort(serial_port) is None: #If it doesn't already exist, add it + self._printer_connections.append(PrinterConnection(serial_port)) + + for serial_port in disconnected_ports: # Close connections and remove them from list. + connection = self.getConnectionByPort(serial_port) + connection.close() + self._printer_connections.remove(connection) + + time.sleep(5) #Throttle, as we don't need this information to be updated every single second. + + def connectAllConnections(self): + for connection in self._printer_connections: + connection.connect() + + def getActiveConnections(self): + return [connection for connection in self._printer_connections if connection.isConnected()] + + ## + def getConnectionByPort(self, serial_port): + for printer_connection in self._printer_connections: + if serial_port == printer_connection.getSerialPort(): + return printer_connection + return None + + ## Create a list of serial ports on the system. + # \param only_list_usb If true, only usb ports are listed + def getSerialPortList(self,only_list_usb=False): + base_list=[] + if platform.system() == "Windows": + try: + key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") + i=0 + while True: + values = _winreg.EnumValue(key, i) + if not base_list or 'USBSER' in values[0]: + base_list+=[values[1]] + i+=1 + except: + pass + + if base_list: + base_list = base_list + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.usb*") + base_list = filter(lambda s: not 'Bluetooth' in s, base_list) #Filter because mac sometimes puts them in the list + #prev = profile.getMachineSetting('serial_port_auto') + #if prev in base_list: + # base_list.remove(prev) + # base_list.insert(0, prev) + else: + base_list = base_list + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*") + glob.glob('/dev/serial/by-id/*') + #if version.isDevVersion() and not base_list: + #base_list.append('VIRTUAL') + return base_list \ No newline at end of file diff --git a/__init__.py b/__init__.py index 05b12c8e15..b645182496 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ from . import USBPrintDevice - +from . import USBPrinterManager def getMetaData(): return { 'type': 'storage_device', @@ -12,4 +12,4 @@ def getMetaData(): } def register(app): - return USBPrintDevice.USBPrintDevice() \ No newline at end of file + return USBPrinterManager.USBPrinterManager() diff --git a/avr_isp/__init__.py b/avr_isp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/avr_isp/chipDB.py b/avr_isp/chipDB.py new file mode 100644 index 0000000000..b28906aea6 --- /dev/null +++ b/avr_isp/chipDB.py @@ -0,0 +1,25 @@ +""" +Database of AVR chips for avr_isp programming. Contains signatures and flash sizes from the AVR datasheets. +To support more chips add the relevant data to the avrChipDB list. +""" +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" + +avrChipDB = { + 'ATMega1280': { + 'signature': [0x1E, 0x97, 0x03], + 'pageSize': 128, + 'pageCount': 512, + }, + 'ATMega2560': { + 'signature': [0x1E, 0x98, 0x01], + 'pageSize': 128, + 'pageCount': 1024, + }, +} + +def getChipFromDB(sig): + for chip in avrChipDB.values(): + if chip['signature'] == sig: + return chip + return False + diff --git a/avr_isp/intelHex.py b/avr_isp/intelHex.py new file mode 100644 index 0000000000..21fb46394c --- /dev/null +++ b/avr_isp/intelHex.py @@ -0,0 +1,46 @@ +""" +Module to read intel hex files into binary data blobs. +IntelHex files are commonly used to distribute firmware +See: http://en.wikipedia.org/wiki/Intel_HEX +""" +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" +import io + +def readHex(filename): + """ + Read an verify an intel hex file. Return the data as an list of bytes. + """ + data = [] + extraAddr = 0 + f = io.open(filename, "r") + for line in f: + line = line.strip() + if len(line) < 1: + continue + if line[0] != ':': + raise Exception("Hex file has a line not starting with ':'") + recLen = int(line[1:3], 16) + addr = int(line[3:7], 16) + extraAddr + recType = int(line[7:9], 16) + if len(line) != recLen * 2 + 11: + raise Exception("Error in hex file: " + line) + checkSum = 0 + for i in xrange(0, recLen + 5): + checkSum += int(line[i*2+1:i*2+3], 16) + checkSum &= 0xFF + if checkSum != 0: + raise Exception("Checksum error in hex file: " + line) + + if recType == 0:#Data record + while len(data) < addr + recLen: + data.append(0) + for i in xrange(0, recLen): + data[addr + i] = int(line[i*2+9:i*2+11], 16) + elif recType == 1: #End Of File record + pass + elif recType == 2: #Extended Segment Address Record + extraAddr = int(line[9:13], 16) * 16 + else: + print(recType, recLen, addr, checkSum, line) + f.close() + return data diff --git a/avr_isp/ispBase.py b/avr_isp/ispBase.py new file mode 100644 index 0000000000..b404030500 --- /dev/null +++ b/avr_isp/ispBase.py @@ -0,0 +1,63 @@ +""" +General interface for Isp based AVR programmers. +The ISP AVR programmer can load firmware into AVR chips. Which are commonly used on 3D printers. + + Needs to be subclassed to support different programmers. + Currently only the stk500v2 subclass exists. +""" +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" + +from . import chipDB + +class IspBase(): + """ + Base class for ISP based AVR programmers. + Functions in this class raise an IspError when something goes wrong. + """ + def programChip(self, flashData): + """ Program a chip with the given flash data. """ + self.curExtAddr = -1 + self.chip = chipDB.getChipFromDB(self.getSignature()) + if not self.chip: + raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") + self.chipErase() + + print("Flashing %i bytes" % len(flashData)) + self.writeFlash(flashData) + print("Verifying %i bytes" % len(flashData)) + self.verifyFlash(flashData) + + def getSignature(self): + """ + Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. + This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. + """ + sig = [] + sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) + return sig + + def chipErase(self): + """ + Do a full chip erase, clears all data, and lockbits. + """ + self.sendISP([0xAC, 0x80, 0x00, 0x00]) + + def writeFlash(self, flashData): + """ + Write the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined writeFlash") + + def verifyFlash(self, flashData): + """ + Verify the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined verifyFlash") + +class IspError(): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py new file mode 100644 index 0000000000..d69f05cdd2 --- /dev/null +++ b/avr_isp/stk500v2.py @@ -0,0 +1,216 @@ +""" +STK500v2 protocol implementation for programming AVR chips. +The STK500v2 protocol is used by the ArduinoMega2560 and a few other Arduino platforms to load firmware. +""" +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" +import os, struct, sys, time + +from serial import Serial +from serial import SerialException +from serial import SerialTimeoutException + +from . import ispBase, intelHex + +class Stk500v2(ispBase.IspBase): + def __init__(self): + self.serial = None + self.seq = 1 + self.lastAddr = -1 + self.progressCallback = None + + def connect(self, port = 'COM22', speed = 115200): + if self.serial is not None: + self.close() + try: + self.serial = Serial(str(port), speed, timeout=1, writeTimeout=10000) + except SerialException as e: + raise ispBase.IspError("Failed to open serial port") + except: + raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0])) + self.seq = 1 + + #Reset the controller + for n in xrange(0, 2): + self.serial.setDTR(True) + time.sleep(0.1) + self.serial.setDTR(False) + time.sleep(0.1) + time.sleep(0.2) + + self.serial.flushInput() + self.serial.flushOutput() + if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]: + self.close() + raise ispBase.IspError("Failed to enter programming mode") + + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + if self.sendMessage([0xEE])[1] == 0x00: + self._has_checksum = True + else: + self._has_checksum = False + self.serial.timeout = 5 + + def close(self): + if self.serial is not None: + self.serial.close() + self.serial = None + + #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface. + # This allows you to use the serial port without opening it again. + def leaveISP(self): + if self.serial is not None: + if self.sendMessage([0x11]) != [0x11, 0x00]: + raise ispBase.IspError("Failed to leave programming mode") + ret = self.serial + self.serial = None + return ret + return None + + def isConnected(self): + return self.serial is not None + + def hasChecksumFunction(self): + return self._has_checksum + + def sendISP(self, data): + recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]]) + return recv[2:6] + + def writeFlash(self, flashData): + #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + pageSize = self.chip['pageSize'] * 2 + flashSize = pageSize * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + + loadCount = (len(flashData) + pageSize - 1) / pageSize + for i in xrange(0, loadCount): + recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)]) + if self.progressCallback is not None: + if self._has_checksum: + self.progressCallback(i + 1, loadCount) + else: + self.progressCallback(i + 1, loadCount*2) + + def verifyFlash(self, flashData): + if self._has_checksum: + self.sendMessage([0x06, 0x00, (len(flashData) >> 17) & 0xFF, (len(flashData) >> 9) & 0xFF, (len(flashData) >> 1) & 0xFF]) + res = self.sendMessage([0xEE]) + checksum_recv = res[2] | (res[3] << 8) + checksum = 0 + for d in flashData: + checksum += d + checksum &= 0xFFFF + if hex(checksum) != hex(checksum_recv): + raise ispBase.IspError('Verify checksum mismatch: 0x%x != 0x%x' % (checksum & 0xFFFF, checksum_recv)) + else: + #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + + loadCount = (len(flashData) + 0xFF) / 0x100 + for i in xrange(0, loadCount): + recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] + if self.progressCallback is not None: + self.progressCallback(loadCount + i + 1, loadCount*2) + for j in xrange(0, 0x100): + if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]: + raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j)) + + def sendMessage(self, data): + message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E) + for c in data: + message += struct.pack(">B", c) + checksum = 0 + for c in message: + checksum ^= ord(c) + message += struct.pack(">B", checksum) + try: + self.serial.write(message) + self.serial.flush() + except SerialTimeoutException: + raise ispBase.IspError('Serial send timeout') + self.seq = (self.seq + 1) & 0xFF + return self.recvMessage() + + def recvMessage(self): + state = 'Start' + checksum = 0 + while True: + s = self.serial.read() + if len(s) < 1: + raise ispBase.IspError("Timeout") + b = struct.unpack(">B", s)[0] + checksum ^= b + #print(hex(b)) + if state == 'Start': + if b == 0x1B: + state = 'GetSeq' + checksum = 0x1B + elif state == 'GetSeq': + state = 'MsgSize1' + elif state == 'MsgSize1': + msgSize = b << 8 + state = 'MsgSize2' + elif state == 'MsgSize2': + msgSize |= b + state = 'Token' + elif state == 'Token': + if b != 0x0E: + state = 'Start' + else: + state = 'Data' + data = [] + elif state == 'Data': + data.append(b) + if len(data) == msgSize: + state = 'Checksum' + elif state == 'Checksum': + if checksum != 0: + state = 'Start' + else: + return data + +def portList(): + ret = [] + import _winreg + key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") + i=0 + while True: + try: + values = _winreg.EnumValue(key, i) + except: + return ret + if 'USBSER' in values[0]: + ret.append(values[1]) + i+=1 + return ret + +def runProgrammer(port, filename): + """ Run an STK500v2 program on serial port 'port' and write 'filename' into flash. """ + programmer = Stk500v2() + programmer.connect(port = port) + programmer.programChip(intelHex.readHex(filename)) + programmer.close() + +def main(): + """ Entry point to call the stk500v2 programmer from the commandline. """ + import threading + if sys.argv[1] == 'AUTO': + print(portList()) + for port in portList(): + threading.Thread(target=runProgrammer, args=(port,sys.argv[2])).start() + time.sleep(5) + else: + programmer = Stk500v2() + programmer.connect(port = sys.argv[1]) + programmer.programChip(intelHex.readHex(sys.argv[2])) + sys.exit(1) + +if __name__ == '__main__': + main() From 220562aa05fae1bb057d915c953a1a0f6cc66542 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 09:59:24 +0200 Subject: [PATCH 03/34] Bugfixes --- PrinterConnection.py | 23 +++++++++++++---------- USBPrinterManager.py | 9 +++++---- __init__.py | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index d96c7b1055..fa421c5b02 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -1,5 +1,6 @@ from UM.Logger import Logger from .avr_isp import stk500v2 +import threading class PrinterConnection(): def __init__(self, serial_port): @@ -19,12 +20,14 @@ class PrinterConnection(): self._listen_thread = threading.Thread(target=self._listen) self._listen_thread.daemon = True #self._listen_thread.start() + def getSerialPort(self): + return self._serial_port ## Try to connect the serial. This simply starts the thread. def connect(self): self._connect_thread.start() - def _connect(self): + def _connect(self): self._is_connecting = True programmer.connect(serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. try: @@ -32,8 +35,7 @@ class PrinterConnection(): # Create new printer connection self.active_printer_connection = PrinterConnection(temp_serial) Logger.log('i', "Established connection on port %s" % serial_port) - break - except ispBase.IspError as (e): + except ispBase.IspError as e: Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(serial_port,str(e))) except: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % serial_port) @@ -42,14 +44,14 @@ class PrinterConnection(): #Device is not arduino based, so we need to cycle the baud rates. for baud_rate in self._getBaudrateList(): timeout_time = time.time() + 5 - if self._serial = None: + if self._serial is None: self._serial = serial.Serial(str(self._port), baud_rate, timeout=5, writeTimeout=10000) else: if not self.setBaudRate(baud_rate): continue #Could not set the baud rate, go to the next sucesfull_responses = 0 while timeout_time > time.time(): - line = self._readline(): + line = self._readline() if "T:" in line: self._serial.timeout = 0.5 self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it @@ -63,7 +65,6 @@ class PrinterConnection(): return #Stop trying to connect, we are connected. def _listen(self): - time.sleep(5) pass def setBaudRate(self, baud_rate): @@ -82,10 +83,12 @@ class PrinterConnection(): if self._is_connected: self._listen_thread.start() #Start listening + def close(self): + pass #TODO: handle def isConnected(self): - return self._is_connected = True - + return self._is_connected + def _listen(self): while True: line = self._readline() @@ -118,7 +121,7 @@ class PrinterConnection(): def hasError(self): - return self._error_state == None ? False : True + return False def _readline(self): @@ -127,7 +130,7 @@ class PrinterConnection(): try: ret = self._serial.readline() except: - self._log("Unexpected error while reading serial port.")) + self._log("Unexpected error while reading serial port.") self._errorValue = getExceptionString() self.close(True) return None diff --git a/USBPrinterManager.py b/USBPrinterManager.py index af21f29c69..d5d27f758a 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -1,10 +1,12 @@ from UM.Signal import Signal, SignalEmitter from UM.PluginObject import PluginObject +from . import PrinterConnection import threading import platform import glob import time +import os class USBPrinterManager(SignalEmitter,PluginObject): def __init__(self): @@ -24,17 +26,16 @@ class USBPrinterManager(SignalEmitter,PluginObject): temp_serial_port_list = self.getSerialPortList(only_list_usb = True) if temp_serial_port_list != self._serial_port_list: # Something changed about the list since we last changed something. disconnected_ports = [port for port in self._serial_port_list if port not in temp_serial_port_list ] - self._serial_port_list = temp_serial.port_list - + self._serial_port_list = temp_serial_port_list for serial_port in self._serial_port_list: if self.getConnectionByPort(serial_port) is None: #If it doesn't already exist, add it - self._printer_connections.append(PrinterConnection(serial_port)) + if not os.path.islink(serial_port): #Only add the connection if it's a non symbolic link + self._printer_connections.append(PrinterConnection.PrinterConnection(serial_port)) for serial_port in disconnected_ports: # Close connections and remove them from list. connection = self.getConnectionByPort(serial_port) connection.close() self._printer_connections.remove(connection) - time.sleep(5) #Throttle, as we don't need this information to be updated every single second. def connectAllConnections(self): diff --git a/__init__.py b/__init__.py index b645182496..ee73863826 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,3 @@ -from . import USBPrintDevice from . import USBPrinterManager def getMetaData(): return { From 9d889cf2dbd2d3fbfb243da6015a91137ee3f493 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 10:00:06 +0200 Subject: [PATCH 04/34] Bugfixes --- USBPrinterManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index d5d27f758a..940a4c5dae 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -17,6 +17,8 @@ class USBPrinterManager(SignalEmitter,PluginObject): self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() + time.sleep(6) + self.connectAllConnections() ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! From 3fa02d3710f7cc7e82752d8b394b5242ee16c742 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 10:40:21 +0200 Subject: [PATCH 05/34] formatting --- PrinterConnection.py | 3 +- USBPrinterManager.py | 2 +- avr_isp/chipDB.py | 30 ++-- avr_isp/intelHex.py | 76 ++++----- avr_isp/ispBase.py | 94 +++++------ avr_isp/stk500v2.py | 370 ++++++++++++++++++++++--------------------- 6 files changed, 289 insertions(+), 286 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index fa421c5b02..e8343be959 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -29,7 +29,8 @@ class PrinterConnection(): def _connect(self): self._is_connecting = True - programmer.connect(serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. + programmer = stk500v2.Stk500v2() + programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. try: self._serial = programmer.leaveISP() # Create new printer connection diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 940a4c5dae..1e5645439b 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -17,7 +17,7 @@ class USBPrinterManager(SignalEmitter,PluginObject): self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() - time.sleep(6) + time.sleep(2) self.connectAllConnections() ## Check all serial ports and create a PrinterConnection object for them. diff --git a/avr_isp/chipDB.py b/avr_isp/chipDB.py index b28906aea6..ba9cf434d5 100644 --- a/avr_isp/chipDB.py +++ b/avr_isp/chipDB.py @@ -1,25 +1,25 @@ """ Database of AVR chips for avr_isp programming. Contains signatures and flash sizes from the AVR datasheets. To support more chips add the relevant data to the avrChipDB list. +This is a python 3 conversion of the code created by David Braam for the Cura project. """ -__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" avrChipDB = { - 'ATMega1280': { - 'signature': [0x1E, 0x97, 0x03], - 'pageSize': 128, - 'pageCount': 512, - }, - 'ATMega2560': { - 'signature': [0x1E, 0x98, 0x01], - 'pageSize': 128, - 'pageCount': 1024, - }, + 'ATMega1280': { + 'signature': [0x1E, 0x97, 0x03], + 'pageSize': 128, + 'pageCount': 512, + }, + 'ATMega2560': { + 'signature': [0x1E, 0x98, 0x01], + 'pageSize': 128, + 'pageCount': 1024, + }, } def getChipFromDB(sig): - for chip in avrChipDB.values(): - if chip['signature'] == sig: - return chip - return False + for chip in avrChipDB.values(): + if chip['signature'] == sig: + return chip + return False diff --git a/avr_isp/intelHex.py b/avr_isp/intelHex.py index 21fb46394c..75c7d3a5b2 100644 --- a/avr_isp/intelHex.py +++ b/avr_isp/intelHex.py @@ -2,45 +2,45 @@ Module to read intel hex files into binary data blobs. IntelHex files are commonly used to distribute firmware See: http://en.wikipedia.org/wiki/Intel_HEX +This is a python 3 conversion of the code created by David Braam for the Cura project. """ -__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" import io def readHex(filename): - """ - Read an verify an intel hex file. Return the data as an list of bytes. - """ - data = [] - extraAddr = 0 - f = io.open(filename, "r") - for line in f: - line = line.strip() - if len(line) < 1: - continue - if line[0] != ':': - raise Exception("Hex file has a line not starting with ':'") - recLen = int(line[1:3], 16) - addr = int(line[3:7], 16) + extraAddr - recType = int(line[7:9], 16) - if len(line) != recLen * 2 + 11: - raise Exception("Error in hex file: " + line) - checkSum = 0 - for i in xrange(0, recLen + 5): - checkSum += int(line[i*2+1:i*2+3], 16) - checkSum &= 0xFF - if checkSum != 0: - raise Exception("Checksum error in hex file: " + line) - - if recType == 0:#Data record - while len(data) < addr + recLen: - data.append(0) - for i in xrange(0, recLen): - data[addr + i] = int(line[i*2+9:i*2+11], 16) - elif recType == 1: #End Of File record - pass - elif recType == 2: #Extended Segment Address Record - extraAddr = int(line[9:13], 16) * 16 - else: - print(recType, recLen, addr, checkSum, line) - f.close() - return data + """ + Read an verify an intel hex file. Return the data as an list of bytes. + """ + data = [] + extraAddr = 0 + f = io.open(filename, "r") + for line in f: + line = line.strip() + if len(line) < 1: + continue + if line[0] != ':': + raise Exception("Hex file has a line not starting with ':'") + recLen = int(line[1:3], 16) + addr = int(line[3:7], 16) + extraAddr + recType = int(line[7:9], 16) + if len(line) != recLen * 2 + 11: + raise Exception("Error in hex file: " + line) + checkSum = 0 + for i in range(0, recLen + 5): + checkSum += int(line[i*2+1:i*2+3], 16) + checkSum &= 0xFF + if checkSum != 0: + raise Exception("Checksum error in hex file: " + line) + + if recType == 0:#Data record + while len(data) < addr + recLen: + data.append(0) + for i in xrange(0, recLen): + data[addr + i] = int(line[i*2+9:i*2+11], 16) + elif recType == 1: #End Of File record + pass + elif recType == 2: #Extended Segment Address Record + extraAddr = int(line[9:13], 16) * 16 + else: + print(recType, recLen, addr, checkSum, line) + f.close() + return data diff --git a/avr_isp/ispBase.py b/avr_isp/ispBase.py index b404030500..16eeec8e67 100644 --- a/avr_isp/ispBase.py +++ b/avr_isp/ispBase.py @@ -4,60 +4,60 @@ The ISP AVR programmer can load firmware into AVR chips. Which are commonly used Needs to be subclassed to support different programmers. Currently only the stk500v2 subclass exists. + This is a python 3 conversion of the code created by David Braam for the Cura project. """ -__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" from . import chipDB class IspBase(): - """ - Base class for ISP based AVR programmers. - Functions in this class raise an IspError when something goes wrong. - """ - def programChip(self, flashData): - """ Program a chip with the given flash data. """ - self.curExtAddr = -1 - self.chip = chipDB.getChipFromDB(self.getSignature()) - if not self.chip: - raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") - self.chipErase() - - print("Flashing %i bytes" % len(flashData)) - self.writeFlash(flashData) - print("Verifying %i bytes" % len(flashData)) - self.verifyFlash(flashData) + """ + Base class for ISP based AVR programmers. + Functions in this class raise an IspError when something goes wrong. + """ + def programChip(self, flashData): + """ Program a chip with the given flash data. """ + self.curExtAddr = -1 + self.chip = chipDB.getChipFromDB(self.getSignature()) + if not self.chip: + raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") + self.chipErase() + + print("Flashing %i bytes" % len(flashData)) + self.writeFlash(flashData) + print("Verifying %i bytes" % len(flashData)) + self.verifyFlash(flashData) - def getSignature(self): - """ - Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. - This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. - """ - sig = [] - sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) - sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) - sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) - return sig - - def chipErase(self): - """ - Do a full chip erase, clears all data, and lockbits. - """ - self.sendISP([0xAC, 0x80, 0x00, 0x00]) + def getSignature(self): + """ + Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. + This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. + """ + sig = [] + sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) + return sig - def writeFlash(self, flashData): - """ - Write the flash data, needs to be implemented in a subclass. - """ - raise IspError("Called undefined writeFlash") + def chipErase(self): + """ + Do a full chip erase, clears all data, and lockbits. + """ + self.sendISP([0xAC, 0x80, 0x00, 0x00]) - def verifyFlash(self, flashData): - """ - Verify the flash data, needs to be implemented in a subclass. - """ - raise IspError("Called undefined verifyFlash") + def writeFlash(self, flashData): + """ + Write the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined writeFlash") + + def verifyFlash(self, flashData): + """ + Verify the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined verifyFlash") class IspError(): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index d69f05cdd2..a3855109f1 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -1,8 +1,8 @@ """ STK500v2 protocol implementation for programming AVR chips. The STK500v2 protocol is used by the ArduinoMega2560 and a few other Arduino platforms to load firmware. +This is a python 3 conversion of the code created by David Braam for the Cura project. """ -__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" import os, struct, sys, time from serial import Serial @@ -12,205 +12,207 @@ from serial import SerialTimeoutException from . import ispBase, intelHex class Stk500v2(ispBase.IspBase): - def __init__(self): - self.serial = None - self.seq = 1 - self.lastAddr = -1 - self.progressCallback = None - - def connect(self, port = 'COM22', speed = 115200): - if self.serial is not None: - self.close() - try: - self.serial = Serial(str(port), speed, timeout=1, writeTimeout=10000) - except SerialException as e: - raise ispBase.IspError("Failed to open serial port") - except: - raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0])) - self.seq = 1 + def __init__(self): + self.serial = None + self.seq = 1 + self.lastAddr = -1 + self.progressCallback = None + + def connect(self, port = 'COM22', speed = 115200): + if self.serial is not None: + self.close() + try: + self.serial = Serial(str(port), speed, timeout=1, writeTimeout=10000) + except SerialException as e: + raise ispBase.IspError("Failed to open serial port") + except: + raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0])) + self.seq = 1 - #Reset the controller - for n in xrange(0, 2): - self.serial.setDTR(True) - time.sleep(0.1) - self.serial.setDTR(False) - time.sleep(0.1) - time.sleep(0.2) + #Reset the controller + for n in range(0, 2): + self.serial.setDTR(True) + time.sleep(0.1) + self.serial.setDTR(False) + time.sleep(0.1) + time.sleep(0.2) - self.serial.flushInput() - self.serial.flushOutput() - if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]: - self.close() - raise ispBase.IspError("Failed to enter programming mode") + self.serial.flushInput() + self.serial.flushOutput() + if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]: + self.close() + raise ispBase.IspError("Failed to enter programming mode") - self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) - if self.sendMessage([0xEE])[1] == 0x00: - self._has_checksum = True - else: - self._has_checksum = False - self.serial.timeout = 5 + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + if self.sendMessage([0xEE])[1] == 0x00: + self._has_checksum = True + else: + self._has_checksum = False + self.serial.timeout = 5 - def close(self): - if self.serial is not None: - self.serial.close() - self.serial = None + def close(self): + if self.serial is not None: + self.serial.close() + self.serial = None #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface. # This allows you to use the serial port without opening it again. - def leaveISP(self): - if self.serial is not None: - if self.sendMessage([0x11]) != [0x11, 0x00]: - raise ispBase.IspError("Failed to leave programming mode") - ret = self.serial - self.serial = None - return ret - return None - - def isConnected(self): - return self.serial is not None + def leaveISP(self): + if self.serial is not None: + if self.sendMessage([0x11]) != [0x11, 0x00]: + raise ispBase.IspError("Failed to leave programming mode") + ret = self.serial + self.serial = None + return ret + return None + + def isConnected(self): + return self.serial is not None - def hasChecksumFunction(self): - return self._has_checksum + def hasChecksumFunction(self): + return self._has_checksum - def sendISP(self, data): - recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]]) - return recv[2:6] - - def writeFlash(self, flashData): - #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension - pageSize = self.chip['pageSize'] * 2 - flashSize = pageSize * self.chip['pageCount'] - if flashSize > 0xFFFF: - self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) - else: - self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) - - loadCount = (len(flashData) + pageSize - 1) / pageSize - for i in xrange(0, loadCount): - recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)]) - if self.progressCallback is not None: - if self._has_checksum: - self.progressCallback(i + 1, loadCount) - else: - self.progressCallback(i + 1, loadCount*2) - - def verifyFlash(self, flashData): - if self._has_checksum: - self.sendMessage([0x06, 0x00, (len(flashData) >> 17) & 0xFF, (len(flashData) >> 9) & 0xFF, (len(flashData) >> 1) & 0xFF]) - res = self.sendMessage([0xEE]) - checksum_recv = res[2] | (res[3] << 8) - checksum = 0 - for d in flashData: - checksum += d - checksum &= 0xFFFF - if hex(checksum) != hex(checksum_recv): - raise ispBase.IspError('Verify checksum mismatch: 0x%x != 0x%x' % (checksum & 0xFFFF, checksum_recv)) - else: - #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension - flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount'] - if flashSize > 0xFFFF: - self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) - else: - self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + def sendISP(self, data): + recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]]) + return recv[2:6] + + def writeFlash(self, flashData): + #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + pageSize = self.chip['pageSize'] * 2 + flashSize = pageSize * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + + loadCount = (len(flashData) + pageSize - 1) / pageSize + for i in xrange(0, loadCount): + recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)]) + if self.progressCallback is not None: + if self._has_checksum: + self.progressCallback(i + 1, loadCount) + else: + self.progressCallback(i + 1, loadCount*2) + + def verifyFlash(self, flashData): + if self._has_checksum: + self.sendMessage([0x06, 0x00, (len(flashData) >> 17) & 0xFF, (len(flashData) >> 9) & 0xFF, (len(flashData) >> 1) & 0xFF]) + res = self.sendMessage([0xEE]) + checksum_recv = res[2] | (res[3] << 8) + checksum = 0 + for d in flashData: + checksum += d + checksum &= 0xFFFF + if hex(checksum) != hex(checksum_recv): + raise ispBase.IspError('Verify checksum mismatch: 0x%x != 0x%x' % (checksum & 0xFFFF, checksum_recv)) + else: + #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) - loadCount = (len(flashData) + 0xFF) / 0x100 - for i in xrange(0, loadCount): - recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] - if self.progressCallback is not None: - self.progressCallback(loadCount + i + 1, loadCount*2) - for j in xrange(0, 0x100): - if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]: - raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j)) + loadCount = (len(flashData) + 0xFF) / 0x100 + for i in xrange(0, loadCount): + recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] + if self.progressCallback is not None: + self.progressCallback(loadCount + i + 1, loadCount*2) + for j in xrange(0, 0x100): + if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]: + raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j)) - def sendMessage(self, data): - message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E) - for c in data: - message += struct.pack(">B", c) - checksum = 0 - for c in message: - checksum ^= ord(c) - message += struct.pack(">B", checksum) - try: - self.serial.write(message) - self.serial.flush() - except SerialTimeoutException: - raise ispBase.IspError('Serial send timeout') - self.seq = (self.seq + 1) & 0xFF - return self.recvMessage() - - def recvMessage(self): - state = 'Start' - checksum = 0 - while True: - s = self.serial.read() - if len(s) < 1: - raise ispBase.IspError("Timeout") - b = struct.unpack(">B", s)[0] - checksum ^= b - #print(hex(b)) - if state == 'Start': - if b == 0x1B: - state = 'GetSeq' - checksum = 0x1B - elif state == 'GetSeq': - state = 'MsgSize1' - elif state == 'MsgSize1': - msgSize = b << 8 - state = 'MsgSize2' - elif state == 'MsgSize2': - msgSize |= b - state = 'Token' - elif state == 'Token': - if b != 0x0E: - state = 'Start' - else: - state = 'Data' - data = [] - elif state == 'Data': - data.append(b) - if len(data) == msgSize: - state = 'Checksum' - elif state == 'Checksum': - if checksum != 0: - state = 'Start' - else: - return data + def sendMessage(self, data): + message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E) + for c in data: + message += struct.pack(">B", c) + checksum = 0 + print("messsage " , message) + for c in message: + print(c) + checksum ^= ord(c) + message += struct.pack(">B", checksum) + try: + self.serial.write(message) + self.serial.flush() + except SerialTimeoutException: + raise ispBase.IspError('Serial send timeout') + self.seq = (self.seq + 1) & 0xFF + return self.recvMessage() + + def recvMessage(self): + state = 'Start' + checksum = 0 + while True: + s = self.serial.read() + if len(s) < 1: + raise ispBase.IspError("Timeout") + b = struct.unpack(">B", s)[0] + checksum ^= b + #print(hex(b)) + if state == 'Start': + if b == 0x1B: + state = 'GetSeq' + checksum = 0x1B + elif state == 'GetSeq': + state = 'MsgSize1' + elif state == 'MsgSize1': + msgSize = b << 8 + state = 'MsgSize2' + elif state == 'MsgSize2': + msgSize |= b + state = 'Token' + elif state == 'Token': + if b != 0x0E: + state = 'Start' + else: + state = 'Data' + data = [] + elif state == 'Data': + data.append(b) + if len(data) == msgSize: + state = 'Checksum' + elif state == 'Checksum': + if checksum != 0: + state = 'Start' + else: + return data def portList(): - ret = [] - import _winreg - key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") - i=0 - while True: - try: - values = _winreg.EnumValue(key, i) - except: - return ret - if 'USBSER' in values[0]: - ret.append(values[1]) - i+=1 - return ret + ret = [] + import _winreg + key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") + i=0 + while True: + try: + values = _winreg.EnumValue(key, i) + except: + return ret + if 'USBSER' in values[0]: + ret.append(values[1]) + i+=1 + return ret def runProgrammer(port, filename): - """ Run an STK500v2 program on serial port 'port' and write 'filename' into flash. """ - programmer = Stk500v2() - programmer.connect(port = port) - programmer.programChip(intelHex.readHex(filename)) - programmer.close() + """ Run an STK500v2 program on serial port 'port' and write 'filename' into flash. """ + programmer = Stk500v2() + programmer.connect(port = port) + programmer.programChip(intelHex.readHex(filename)) + programmer.close() def main(): - """ Entry point to call the stk500v2 programmer from the commandline. """ - import threading - if sys.argv[1] == 'AUTO': - print(portList()) - for port in portList(): - threading.Thread(target=runProgrammer, args=(port,sys.argv[2])).start() - time.sleep(5) - else: - programmer = Stk500v2() - programmer.connect(port = sys.argv[1]) - programmer.programChip(intelHex.readHex(sys.argv[2])) - sys.exit(1) + """ Entry point to call the stk500v2 programmer from the commandline. """ + import threading + if sys.argv[1] == 'AUTO': + print(portList()) + for port in portList(): + threading.Thread(target=runProgrammer, args=(port,sys.argv[2])).start() + time.sleep(5) + else: + programmer = Stk500v2() + programmer.connect(port = sys.argv[1]) + programmer.programChip(intelHex.readHex(sys.argv[2])) + sys.exit(1) if __name__ == '__main__': - main() + main() From bd651c6bcb29831f9c36ed70f102b453345922dd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 11:27:44 +0200 Subject: [PATCH 06/34] Listening now correctly works --- PrinterConnection.py | 20 ++++++++++---------- USBPrinterManager.py | 4 ++-- avr_isp/ispBase.py | 2 +- avr_isp/stk500v2.py | 4 +--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index e8343be959..a4dff409b6 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -1,5 +1,5 @@ from UM.Logger import Logger -from .avr_isp import stk500v2 +from .avr_isp import stk500v2, ispBase import threading class PrinterConnection(): @@ -34,12 +34,12 @@ class PrinterConnection(): try: self._serial = programmer.leaveISP() # Create new printer connection - self.active_printer_connection = PrinterConnection(temp_serial) - Logger.log('i', "Established connection on port %s" % serial_port) + self.active_printer_connection = PrinterConnection(self._serial_port) + Logger.log('i', "Established connection on port %s" % self._serial_port) except ispBase.IspError as e: - Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(serial_port,str(e))) + Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) except: - Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % serial_port) + Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) if self._serial is None: #Device is not arduino based, so we need to cycle the baud rates. @@ -76,7 +76,7 @@ class PrinterConnection(): def setIsConnected(self, state): self._is_connecting = False - if state != state: + if self._is_connected != state: self._is_connected = state else: Logger.log('w', "Printer connection state was not changed") @@ -96,18 +96,18 @@ class PrinterConnection(): if line is None: break #None is only returned when something went wrong. Stop listening - if line.startswith('Error:'): + if line.startswith(b'Error:'): #Oh YEAH, consistency. # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" # So we can have an extra newline in the most common case. Awesome work people. - if re.match('Error:[0-9]\n', line): + if re.match(b'Error:[0-9]\n', line): line = line.rstrip() + self._readline() #Skip the communication errors, as those get corrected. - if 'Extruder switched off' in line or 'Temperature heated bed switched off' in line or 'Something is wrong, please turn off the printer.' in line: + if b'Extruder switched off' in line or b'Temperature heated bed switched off' in line or b'Something is wrong, please turn off the printer.' in line: if not self.hasError(): self._error_state = line[6:] - if ' T:' in line or line.startswith('T:'): #Temperature message + if b' T:' in line or line.startswith(b'T:'): #Temperature message try: print("TEMPERATURE", float(re.search("T: *([0-9\.]*)", line).group(1))) except: diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 1e5645439b..07d7132368 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -17,8 +17,8 @@ class USBPrinterManager(SignalEmitter,PluginObject): self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() - time.sleep(2) - self.connectAllConnections() + #time.sleep(2) + #self.connectAllConnections() ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! diff --git a/avr_isp/ispBase.py b/avr_isp/ispBase.py index 16eeec8e67..8d65f03f71 100644 --- a/avr_isp/ispBase.py +++ b/avr_isp/ispBase.py @@ -56,7 +56,7 @@ class IspBase(): """ raise IspError("Called undefined verifyFlash") -class IspError(): +class IspError(BaseException): def __init__(self, value): self.value = value def __str__(self): diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index a3855109f1..b2d2449727 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -127,10 +127,8 @@ class Stk500v2(ispBase.IspBase): for c in data: message += struct.pack(">B", c) checksum = 0 - print("messsage " , message) for c in message: - print(c) - checksum ^= ord(c) + checksum ^= c message += struct.pack(">B", checksum) try: self.serial.write(message) From e82fa1754896de0706cb902461b33ba161e87a2a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 31 Mar 2015 17:02:33 +0200 Subject: [PATCH 07/34] usb connecting now correctly works for ultimaker + keeping connection allive when idling --- PrinterConnection.py | 219 ++++++++++++++++++++++++++++++++++--------- USBPrinterManager.py | 6 +- 2 files changed, 181 insertions(+), 44 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index a4dff409b6..428f1b93ec 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -1,6 +1,9 @@ from UM.Logger import Logger from .avr_isp import stk500v2, ispBase +import serial import threading +import time +import queue class PrinterConnection(): def __init__(self, serial_port): @@ -19,7 +22,57 @@ class PrinterConnection(): self._listen_thread = threading.Thread(target=self._listen) self._listen_thread.daemon = True - #self._listen_thread.start() + + self._heatup_wait_start_time = time.time() + + ## Queue for commands that need to be send. Used when command is sent when a print is active. + self._command_queue = queue.Queue() + + self._is_printing = False + + ## Set when print is started in order to check running time. + self._print_start_time = None + self._print_start_time_100 = None + + ## Keep track where in the provided g-code the print is + self._gcode_position = 0 + + self._gcode = None + self._extruder_count = 1 + self._extruder_temperatures = [0] * self._extruder_count + self._target_extruder_temperatures = [0] * self._extruder_count + self._target_bed_temperature = 0 + self._bed_temperature = 0 + + # In order to keep the connection alive we request the temperature every so often from a different extruder. + # This index is the extruder we requested data from the last time. + self._temperature_requested_extruder_index = 0 + + #TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object + def setNumExtuders(self, num): + self._extruder_count = num + self._extruder_temperatures = [0] * self._extruder_count + self._target_extruder_temperatures = [0] * self._extruder_count + + + #TODO: Needs more logic + def isPrinting(self): + if not self._is_connected or self._serial is None: + return False + + return self._is_printing + + def printGCode(self, gcode_list): + if self.isPrinting() or not self._is_connected: + return + self._gcode = gcode_list + self._gcode_position = 0 + self._print_start_time_100 = None + self._is_printing = True + self._print_start_time = time.time() + for i in range(0, 4): #Push first 4 entries before accepting other inputs + self._sendNextGcodeLine() + def getSerialPort(self): return self._serial_port @@ -30,48 +83,47 @@ class PrinterConnection(): def _connect(self): self._is_connecting = True programmer = stk500v2.Stk500v2() - programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. + try: + programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. self._serial = programmer.leaveISP() # Create new printer connection - self.active_printer_connection = PrinterConnection(self._serial_port) Logger.log('i', "Established connection on port %s" % self._serial_port) except ispBase.IspError as e: Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) except: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) - if self._serial is None: - #Device is not arduino based, so we need to cycle the baud rates. - for baud_rate in self._getBaudrateList(): - timeout_time = time.time() + 5 - if self._serial is None: - self._serial = serial.Serial(str(self._port), baud_rate, timeout=5, writeTimeout=10000) - else: - if not self.setBaudRate(baud_rate): - continue #Could not set the baud rate, go to the next - sucesfull_responses = 0 - while timeout_time > time.time(): - line = self._readline() - if "T:" in line: - self._serial.timeout = 0.5 - self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it - sucesfull_responses += 1 - if sucesfull_responses >= self._required_responses_auto_baud: - self.setIsConnected(True) - return - self.setIsConnected(False) - else: - self.setIsConnected(True) - return #Stop trying to connect, we are connected. - - def _listen(self): - pass + for baud_rate in self._getBaudrateList(): + timeout_time = time.time() + 20 + if self._serial is None: + self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout=3, writeTimeout=10000) + else: + if not self.setBaudRate(baud_rate): + continue #Could not set the baud rate, go to the next + time.sleep(1.5) #Ensure that w are not talking to the bootloader + sucesfull_responses = 0 + self._serial.write(b"\n") + self._sendCommand("M105") + while timeout_time > time.time(): + line = self._readline() + if b"T:" in line: + self._serial.timeout = 0.5 + self._serial.write(b"\n") + self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it + sucesfull_responses += 1 + if sucesfull_responses >= self._required_responses_auto_baud: + self._serial.timeout = 2 + self.setIsConnected(True) + return + self.setIsConnected(False) def setBaudRate(self, baud_rate): try: self._serial.baudrate = baud_rate - except: + return True + except Exception as e: + print(e) return False def setIsConnected(self, state): @@ -85,14 +137,61 @@ class PrinterConnection(): self._listen_thread.start() #Start listening def close(self): - pass #TODO: handle + if self._serial != None: + self._serial.close() + self.setIsConnected(False) + self._serial = None def isConnected(self): return self._is_connected + def _sendCommand(self, cmd): + if self._serial is None: + return + if 'M109' in cmd or 'M190' in cmd: + self._heatup_wait_start_time = time.time() + if 'M104' in cmd or 'M109' in cmd: + try: + t = 0 + if 'T' in cmd: + t = int(re.search('T([0-9]+)', cmd).group(1)) + self._target_extruder_temperatures[t] = float(re.search('S([0-9]+)', cmd).group(1)) + except: + pass + if 'M140' in cmd or 'M190' in cmd: + try: + self._target_bed_temperature = float(re.search('S([0-9]+)', cmd).group(1)) + except: + pass + Logger.log('i','Sending: %s' % (cmd)) + try: + command = (cmd + '\n').encode() + self._serial.write(command) + except serial.SerialTimeoutException: + Logger.log("w","Serial timeout while writing to serial port, trying again.") + try: + time.sleep(0.5) + self._serial.write((cmd + '\n').encode()) + except Exception as e: + Logger.log("e","Unexpected error while writing serial port %s " % e) + self.close() + except Exception as e: + Logger.log('e',"Unexpected error while writing serial port %s" % e) + self.close() + + def __del__(self): + self.close() + + def sendCommand(self, cmd): + if self.isPrinting(): + self._command_queue.put(cmd) + elif self.isConnected(): + self._sendCommand(cmd) + def _listen(self): - while True: + while self._is_connected: line = self._readline() + print("listening: " ,line.decode('utf-8',"replace")) if line is None: break #None is only returned when something went wrong. Stop listening @@ -102,28 +201,64 @@ class PrinterConnection(): # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" # So we can have an extra newline in the most common case. Awesome work people. if re.match(b'Error:[0-9]\n', line): - line = line.rstrip() + self._readline() + line = line.rstrip() + self._readline() #Skip the communication errors, as those get corrected. if b'Extruder switched off' in line or b'Temperature heated bed switched off' in line or b'Something is wrong, please turn off the printer.' in line: if not self.hasError(): self._error_state = line[6:] - if b' T:' in line or line.startswith(b'T:'): #Temperature message + elif b' T:' in line or line.startswith(b'T:'): #Temperature message try: - print("TEMPERATURE", float(re.search("T: *([0-9\.]*)", line).group(1))) + print("TEMPERATURE", float(re.search("T: *([0-9\.]*)", line).group(1))) + self._extruder_temperatures[self._temperatureRequestExtruder] = float(re.search("T: *([0-9\.]*)", line).group(1)) except: pass - if 'B:' in line: #Check if it's a bed temperature + if b'B:' in line: #Check if it's a bed temperature try: print("BED TEMPERATURE" ,float(re.search("B: *([0-9\.]*)", line).group(1))) except: pass #TODO: temperature changed callback + if self._is_printing: + if b'ok' in line: + if not self._commandQueue.empty(): + self._sendCommand(self._commandQueue.get()) + else: + self._sendNextGcodeLine() + + else: #Request the temperature on comm timeout (every 2 seconds) when we are not printing.) + if line == b'': + if self._extruder_count > 0: + self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count + self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index) + else: + self.sendCommand("M105") + def _sendNextGcodeLine(self): + if self._gcode_position >= len(self._gcode_list): + #self._changeState(self.STATE_OPERATIONAL) + return + if self._gcode_position == 100: + self._print_start_time_100 = time.time() + line = self._gcode_list[self._gcode_position] + + try: + if line == 'M0' or line == 'M1': + #self.setPause(True) + line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. + if ('G0' in line or 'G1' in line) and 'Z' in line: + z = float(re.search('Z([0-9\.]*)', line).group(1)) + if self._current_z != z: + self._current_z = z + except Exception as e: + self._log("Unexpected error: %s" % e) + checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcode_position, line))) + self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) + self._gcode_position += 1 + def hasError(self): - return False - + return False def _readline(self): if self._serial is None: @@ -133,17 +268,17 @@ class PrinterConnection(): except: self._log("Unexpected error while reading serial port.") self._errorValue = getExceptionString() - self.close(True) + self.close() return None - if ret == '': - return '' + #if ret == '': + #return '' #self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip())) return ret ## Create a list of baud rates at which we can communicate. # \return list of int - def _getBaudrateList(): + def _getBaudrateList(self): ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600] #if profile.getMachineSetting('serial_baud_auto') != '': #prev = int(profile.getMachineSetting('serial_baud_auto')) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 07d7132368..ad1c7de4d7 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -17,8 +17,10 @@ class USBPrinterManager(SignalEmitter,PluginObject): self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() - #time.sleep(2) - #self.connectAllConnections() + time.sleep(2) + self.connectAllConnections() + #time.sleep(1) + #self._printer_connections[0]._sendCommand("M109") ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! From 208e9d28bf0e4bf2a93b55abcdb009ce06b215ce Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 3 Apr 2015 10:45:08 +0200 Subject: [PATCH 08/34] Fixed temperature extraction regular expression --- PrinterConnection.py | 92 +++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 428f1b93ec..d0226b775e 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -4,6 +4,7 @@ import serial import threading import time import queue +import re class PrinterConnection(): def __init__(self, serial_port): @@ -16,8 +17,14 @@ class PrinterConnection(): self._connect_thread = threading.Thread(target = self._connect) self._connect_thread.daemon = True + # Printer is connected self._is_connected = False + + # Printer is in the process of connecting self._is_connecting = False + + # The baud checking is done by sending a number of m105 commands to the printer and waiting for a readable + # response. If the baudrate is correct, this should make sense, else we get giberish. self._required_responses_auto_baud = 10 self._listen_thread = threading.Thread(target=self._listen) @@ -37,13 +44,27 @@ class PrinterConnection(): ## Keep track where in the provided g-code the print is self._gcode_position = 0 + # List of gcode lines to be printed self._gcode = None + + # Number of extruders self._extruder_count = 1 + + # Temperatures of all extruders self._extruder_temperatures = [0] * self._extruder_count + + # Target temperatures of all extruders self._target_extruder_temperatures = [0] * self._extruder_count + + #Target temperature of the bed self._target_bed_temperature = 0 + + # Temperature of the bed self._bed_temperature = 0 + # Current Z stage location + self._current_z = 0 + # In order to keep the connection alive we request the temperature every so often from a different extruder. # This index is the extruder we requested data from the last time. self._temperature_requested_extruder_index = 0 @@ -59,9 +80,9 @@ class PrinterConnection(): def isPrinting(self): if not self._is_connected or self._serial is None: return False - return self._is_printing + ## Provide a list of G-Codes that need to be printed def printGCode(self, gcode_list): if self.isPrinting() or not self._is_connected: return @@ -71,19 +92,22 @@ class PrinterConnection(): self._is_printing = True self._print_start_time = time.time() for i in range(0, 4): #Push first 4 entries before accepting other inputs - self._sendNextGcodeLine() + self._sendNextGcodeLine() + ## Get the serial port string of this connection. + # \return serial port def getSerialPort(self): return self._serial_port - ## Try to connect the serial. This simply starts the thread. + ## Try to connect the serial. This simply starts the thread, which runs _connect. def connect(self): - self._connect_thread.start() + if not self._connect_thread.isAlive(): + self._connect_thread.start() + ## Private connect function run by thread. Can be started by calling connect. def _connect(self): self._is_connecting = True - programmer = stk500v2.Stk500v2() - + programmer = stk500v2.Stk500v2() try: programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. self._serial = programmer.leaveISP() @@ -94,36 +118,39 @@ class PrinterConnection(): except: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) - for baud_rate in self._getBaudrateList(): - timeout_time = time.time() + 20 + # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information. + for baud_rate in self._getBaudrateList(): #Cycle all baud rates (auto detect) + if self._serial is None: self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout=3, writeTimeout=10000) else: if not self.setBaudRate(baud_rate): continue #Could not set the baud rate, go to the next - time.sleep(1.5) #Ensure that w are not talking to the bootloader + time.sleep(1.5) #Ensure that we are not talking to the bootloader. 1.5 sec seems to be the magic number sucesfull_responses = 0 + timeout_time = time.time() + 5 self._serial.write(b"\n") - self._sendCommand("M105") + self._sendCommand("M105") #Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it while timeout_time > time.time(): - line = self._readline() + line = self._readline() if b"T:" in line: self._serial.timeout = 0.5 self._serial.write(b"\n") - self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it + self._sendCommand("M105") sucesfull_responses += 1 if sucesfull_responses >= self._required_responses_auto_baud: - self._serial.timeout = 2 + self._serial.timeout = 2 #Reset serial timeout self.setIsConnected(True) return self.setIsConnected(False) + + ## Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those. def setBaudRate(self, baud_rate): try: self._serial.baudrate = baud_rate return True except Exception as e: - print(e) return False def setIsConnected(self, state): @@ -135,7 +162,8 @@ class PrinterConnection(): if self._is_connected: self._listen_thread.start() #Start listening - + + ## Close the printer connection def close(self): if self._serial != None: self._serial.close() @@ -145,6 +173,8 @@ class PrinterConnection(): def isConnected(self): return self._is_connected + ## Directly send the command, withouth checking connection state (eg; printing). + # \param cmd string with g-code def _sendCommand(self, cmd): if self._serial is None: return @@ -163,7 +193,7 @@ class PrinterConnection(): self._target_bed_temperature = float(re.search('S([0-9]+)', cmd).group(1)) except: pass - Logger.log('i','Sending: %s' % (cmd)) + #Logger.log('i','Sending: %s' % (cmd)) try: command = (cmd + '\n').encode() self._serial.write(command) @@ -179,22 +209,26 @@ class PrinterConnection(): Logger.log('e',"Unexpected error while writing serial port %s" % e) self.close() + ## Ensure that close gets called when object is destroyed def __del__(self): self.close() + ## Send a command to printer. + # \param cmd string with g-code def sendCommand(self, cmd): if self.isPrinting(): self._command_queue.put(cmd) elif self.isConnected(): self._sendCommand(cmd) + ## Listen thread function. def _listen(self): + temperature_request_timeout = time.time() while self._is_connected: line = self._readline() - print("listening: " ,line.decode('utf-8',"replace")) + #print("listening: " ,line.decode('utf-8',"replace")) if line is None: break #None is only returned when something went wrong. Stop listening - if line.startswith(b'Error:'): #Oh YEAH, consistency. # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" @@ -208,23 +242,37 @@ class PrinterConnection(): self._error_state = line[6:] elif b' T:' in line or line.startswith(b'T:'): #Temperature message try: - print("TEMPERATURE", float(re.search("T: *([0-9\.]*)", line).group(1))) - self._extruder_temperatures[self._temperatureRequestExtruder] = float(re.search("T: *([0-9\.]*)", line).group(1)) + print("TEMPERATURE", float(re.search(b"T: *([0-9\.]*)", line).group(1))) + self._extruder_temperatures[self._temperatureRequestExtruder] = float(re.search(b"T: *([0-9\.]*)", line).group(1)) except: pass if b'B:' in line: #Check if it's a bed temperature try: - print("BED TEMPERATURE" ,float(re.search("B: *([0-9\.]*)", line).group(1))) + print("BED TEMPERATURE" ,float(re.search(b"B: *([0-9\.]*)", line).group(1))) except: pass #TODO: temperature changed callback if self._is_printing: + if time.time() > temperature_request_timeout: #When printing, request temperature every 5 seconds. + if self._extruderCount > 0: + self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount + self.sendCommand("M105 T%d" % (self._temperatureRequestExtruder)) + else: + self.sendCommand("M105") + temperature_request_timeout = time.time() + 5 + if b'ok' in line: if not self._commandQueue.empty(): self._sendCommand(self._commandQueue.get()) else: self._sendNextGcodeLine() + elif b"resend" in line.lower() or b"rs" in line: + try: + self._gcode_position = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1]) + except: + if b"rs" in line: + self._gcode_position = int(line.split()[1]) else: #Request the temperature on comm timeout (every 2 seconds) when we are not printing.) if line == b'': @@ -233,7 +281,7 @@ class PrinterConnection(): self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index) else: self.sendCommand("M105") - + ## Send next Gcode in the gcode list def _sendNextGcodeLine(self): if self._gcode_position >= len(self._gcode_list): #self._changeState(self.STATE_OPERATIONAL) From 4de427396658d7277409dcebaf299b30cb5e2912 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 7 Apr 2015 10:37:29 +0200 Subject: [PATCH 09/34] backend of usb printing now works --- PrinterConnection.py | 37 ++++++++++++++----------- USBPrinterManager.py | 64 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index d0226b775e..86ea0fbf60 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -5,6 +5,7 @@ import threading import time import queue import re +import functools class PrinterConnection(): def __init__(self, serial_port): @@ -87,6 +88,8 @@ class PrinterConnection(): if self.isPrinting() or not self._is_connected: return self._gcode = gcode_list + #Reset line number. If this is not done, first line is sometimes ignored + self._gcode.insert(0,"M110 N0") self._gcode_position = 0 self._print_start_time_100 = None self._is_printing = True @@ -196,6 +199,7 @@ class PrinterConnection(): #Logger.log('i','Sending: %s' % (cmd)) try: command = (cmd + '\n').encode() + #self._serial.write(b'\n') self._serial.write(command) except serial.SerialTimeoutException: Logger.log("w","Serial timeout while writing to serial port, trying again.") @@ -224,9 +228,9 @@ class PrinterConnection(): ## Listen thread function. def _listen(self): temperature_request_timeout = time.time() + ok_timeout = time.time() while self._is_connected: line = self._readline() - #print("listening: " ,line.decode('utf-8',"replace")) if line is None: break #None is only returned when something went wrong. Stop listening if line.startswith(b'Error:'): @@ -241,8 +245,7 @@ class PrinterConnection(): if not self.hasError(): self._error_state = line[6:] elif b' T:' in line or line.startswith(b'T:'): #Temperature message - try: - print("TEMPERATURE", float(re.search(b"T: *([0-9\.]*)", line).group(1))) + try: self._extruder_temperatures[self._temperatureRequestExtruder] = float(re.search(b"T: *([0-9\.]*)", line).group(1)) except: pass @@ -255,21 +258,23 @@ class PrinterConnection(): if self._is_printing: if time.time() > temperature_request_timeout: #When printing, request temperature every 5 seconds. - if self._extruderCount > 0: - self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount - self.sendCommand("M105 T%d" % (self._temperatureRequestExtruder)) + if self._extruder_count > 0: + self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count + self.sendCommand("M105 T%d" % (self._temperature_requested_extruder_index)) else: self.sendCommand("M105") temperature_request_timeout = time.time() + 5 - + if line == b'' and time.time() > ok_timeout: + line = b'ok' #Force a timeout (basicly, send next command) if b'ok' in line: - if not self._commandQueue.empty(): - self._sendCommand(self._commandQueue.get()) + ok_timeout = time.time() + 5 + if not self._command_queue.empty(): + self._sendCommand(self._command_queue.get()) else: self._sendNextGcodeLine() elif b"resend" in line.lower() or b"rs" in line: try: - self._gcode_position = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1]) + self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1]) except: if b"rs" in line: self._gcode_position = int(line.split()[1]) @@ -283,13 +288,15 @@ class PrinterConnection(): self.sendCommand("M105") ## Send next Gcode in the gcode list def _sendNextGcodeLine(self): - if self._gcode_position >= len(self._gcode_list): + if self._gcode_position >= len(self._gcode): #self._changeState(self.STATE_OPERATIONAL) return if self._gcode_position == 100: self._print_start_time_100 = time.time() - line = self._gcode_list[self._gcode_position] - + line = self._gcode[self._gcode_position] + if ';' in line: + line = line[:line.find(';')] + line = line.strip() try: if line == 'M0' or line == 'M1': #self.setPause(True) @@ -300,10 +307,10 @@ class PrinterConnection(): self._current_z = z except Exception as e: self._log("Unexpected error: %s" % e) - checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcode_position, line))) + checksum = functools.reduce(lambda x,y: x^y, map(ord, 'N%d%s' % (self._gcode_position, line))) + self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) self._gcode_position += 1 - def hasError(self): return False diff --git a/USBPrinterManager.py b/USBPrinterManager.py index ad1c7de4d7..f58c8f5237 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -13,14 +13,21 @@ class USBPrinterManager(SignalEmitter,PluginObject): super().__init__() self._serial_port_list = [] self._printer_connections = [] - self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() - time.sleep(2) - self.connectAllConnections() + + ## DEBUG CODE #time.sleep(1) - #self._printer_connections[0]._sendCommand("M109") + #self.connectAllConnections() + #time.sleep(5) + #f = open("Orb.gcode") + #lines = f.readlines() + #print(len(lines)) + #print(len(self._printer_connections)) + #self.sendGCodeToAllActive(lines) + #print("sending heat " , self.sendCommandToAllActive("M104 S190")) + ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! @@ -28,6 +35,7 @@ class USBPrinterManager(SignalEmitter,PluginObject): def _updateConnectionList(self): while True: temp_serial_port_list = self.getSerialPortList(only_list_usb = True) + print(temp_serial_port_list) if temp_serial_port_list != self._serial_port_list: # Something changed about the list since we last changed something. disconnected_ports = [port for port in self._serial_port_list if port not in temp_serial_port_list ] self._serial_port_list = temp_serial_port_list @@ -42,14 +50,58 @@ class USBPrinterManager(SignalEmitter,PluginObject): self._printer_connections.remove(connection) time.sleep(5) #Throttle, as we don't need this information to be updated every single second. + ## Attempt to connect with all possible connections. def connectAllConnections(self): + print("DERP DERP") for connection in self._printer_connections: + print("connection ",connection) connection.connect() - + + ## send gcode to printer and start printing + def sendGCodeByPort(self, serial_port, gcode_list): + printer_connection = self.getConnectionByPort(serial_port) + if printer_connection is not None: + printer_connection.printGCode(gcode_list) + return True + return False + + ## Send gcode to all active printers. + # \return True if there was at least one active connection. + def sendGCodeToAllActive(self, gcode_list): + for printer_connection in self.getActiveConnections(): + printer_connection.printGCode(gcode_list) + if len(self.getActiveConnections()): + return True + else: + return False + + ## Send a command to a printer indentified by port + # \param serial port String indentifieing the port + # \param command String with the g-code command to send. + # \return True if connection was found, false otherwise + def sendCommandByPort(self, serial_port, command): + printer_connection = self.getConnectionByPort(serial_port) + if printer_connection is not None: + printer_connection.sendCommand(command) + return True + return False + + ## Send a command to all active (eg; connected) printers + # \param command String with the g-code command to send. + # \return True if at least one connection was found, false otherwise + def sendCommandToAllActive(self, command): + for printer_connection in self.getActiveConnections(): + printer_connection.sendCommand(command) + if len(self.getActiveConnections()): + return True + else: + return False + + ## Get a list of printer connection objects that are connected. def getActiveConnections(self): return [connection for connection in self._printer_connections if connection.isConnected()] - ## + ## get a printer connection object by serial port def getConnectionByPort(self, serial_port): for printer_connection in self._printer_connections: if serial_port == printer_connection.getSerialPort(): From c6724fcb94e83fb61b1e0aa57925463c0604c3df Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 7 Apr 2015 10:54:16 +0200 Subject: [PATCH 10/34] Removed some stray prints --- USBPrinterManager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index f58c8f5237..15db396b72 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -35,7 +35,6 @@ class USBPrinterManager(SignalEmitter,PluginObject): def _updateConnectionList(self): while True: temp_serial_port_list = self.getSerialPortList(only_list_usb = True) - print(temp_serial_port_list) if temp_serial_port_list != self._serial_port_list: # Something changed about the list since we last changed something. disconnected_ports = [port for port in self._serial_port_list if port not in temp_serial_port_list ] self._serial_port_list = temp_serial_port_list @@ -52,9 +51,7 @@ class USBPrinterManager(SignalEmitter,PluginObject): ## Attempt to connect with all possible connections. def connectAllConnections(self): - print("DERP DERP") for connection in self._printer_connections: - print("connection ",connection) connection.connect() ## send gcode to printer and start printing From 352d8658aa82f5bec36c7aab9f00234f27c77afe Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 7 Apr 2015 14:49:10 +0200 Subject: [PATCH 11/34] Serial printer connections are now added to output devices --- PrinterConnection.py | 27 ++++++++++++++++++++++----- USBPrinterManager.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 86ea0fbf60..b155700843 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -7,7 +7,11 @@ import queue import re import functools -class PrinterConnection(): +from UM.Application import Application +from UM.Signal import Signal, SignalEmitter + + +class PrinterConnection(SignalEmitter): def __init__(self, serial_port): super().__init__() @@ -156,15 +160,27 @@ class PrinterConnection(): except Exception as e: return False + + def setIsConnected(self, state): self._is_connecting = False if self._is_connected != state: self._is_connected = state + self.connectionStateChanged.emit(self._serial_port) + if self._is_connected: + self._listen_thread.start() #Start listening + '''Application.getInstance().addOutputDevice(self._serial_port, { + 'id': self._serial_port, + 'function': self.printGCode, + 'description': 'Print with USB {0}'.format(self._serial_port), + 'icon': 'print_usb', + 'priority': 1 + })''' + else: Logger.log('w', "Printer connection state was not changed") - if self._is_connected: - self._listen_thread.start() #Start listening + connectionStateChanged = Signal() ## Close the printer connection def close(self): @@ -251,7 +267,8 @@ class PrinterConnection(): pass if b'B:' in line: #Check if it's a bed temperature try: - print("BED TEMPERATURE" ,float(re.search(b"B: *([0-9\.]*)", line).group(1))) + #print("BED TEMPERATURE" ,float(re.search(b"B: *([0-9\.]*)", line).group(1))) + pass except: pass #TODO: temperature changed callback @@ -306,7 +323,7 @@ class PrinterConnection(): if self._current_z != z: self._current_z = z except Exception as e: - self._log("Unexpected error: %s" % e) + Logger.log('e', "Unexpected error: %s" % e) checksum = functools.reduce(lambda x,y: x^y, map(ord, 'N%d%s' % (self._gcode_position, line))) self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 15db396b72..e4526ef095 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -1,7 +1,9 @@ from UM.Signal import Signal, SignalEmitter from UM.PluginObject import PluginObject from . import PrinterConnection - +from UM.Application import Application +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Scene.SceneNode import SceneNode import threading import platform import glob @@ -41,11 +43,15 @@ class USBPrinterManager(SignalEmitter,PluginObject): for serial_port in self._serial_port_list: if self.getConnectionByPort(serial_port) is None: #If it doesn't already exist, add it if not os.path.islink(serial_port): #Only add the connection if it's a non symbolic link - self._printer_connections.append(PrinterConnection.PrinterConnection(serial_port)) + connection = PrinterConnection.PrinterConnection(serial_port) + connection.connect() + connection.connectionStateChanged.connect(self.serialConectionStateCallback) + self._printer_connections.append(connection) for serial_port in disconnected_ports: # Close connections and remove them from list. connection = self.getConnectionByPort(serial_port) - connection.close() + if connection != None: + connection.close() self._printer_connections.remove(connection) time.sleep(5) #Throttle, as we don't need this information to be updated every single second. @@ -93,7 +99,28 @@ class USBPrinterManager(SignalEmitter,PluginObject): return True else: return False + + + def serialConectionStateCallback(self,serial_port): + connection = self.getConnectionByPort(serial_port) + if connection.isConnected(): + Application.getInstance().addOutputDevice(serial_port, { + 'id': serial_port, + 'function': self._writeToSerial, + 'description': 'Write to USB {0}'.format(serial_port), + 'icon': 'print_usb', + 'priority': 1 + }) + def _writeToSerial(self, serial_port): + for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): + if type(node) is not SceneNode or not node.getMeshData(): + continue + + gcode = getattr(node.getMeshData(), 'gcode', False) + self.sendGCodeByPort(serial_port,gcode.split('\n')) + + ## Get a list of printer connection objects that are connected. def getActiveConnections(self): return [connection for connection in self._printer_connections if connection.isConnected()] From c0d39ff287e963acd98a80003f1475c9b9df0860 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 7 Apr 2015 15:19:44 +0200 Subject: [PATCH 12/34] fixed exceptions caused by disconnecting printer --- PrinterConnection.py | 4 ++-- USBPrinterManager.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index b155700843..95ed0d1998 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -338,8 +338,8 @@ class PrinterConnection(SignalEmitter): try: ret = self._serial.readline() except: - self._log("Unexpected error while reading serial port.") - self._errorValue = getExceptionString() + Logger.log('e',"Unexpected error while reading serial port.") + #self._errorValue = getExceptionString() self.close() return None #if ret == '': diff --git a/USBPrinterManager.py b/USBPrinterManager.py index e4526ef095..83e67a55ca 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -51,8 +51,8 @@ class USBPrinterManager(SignalEmitter,PluginObject): for serial_port in disconnected_ports: # Close connections and remove them from list. connection = self.getConnectionByPort(serial_port) if connection != None: + self._printer_connections.remove(connection) connection.close() - self._printer_connections.remove(connection) time.sleep(5) #Throttle, as we don't need this information to be updated every single second. ## Attempt to connect with all possible connections. @@ -111,7 +111,8 @@ class USBPrinterManager(SignalEmitter,PluginObject): 'icon': 'print_usb', 'priority': 1 }) - + else: + Application.getInstance().removeOutputDevice(serial_port) def _writeToSerial(self, serial_port): for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): if type(node) is not SceneNode or not node.getMeshData(): From 8d211365300206c052703ddb37d915ac5fc518a8 Mon Sep 17 00:00:00 2001 From: daid Date: Wed, 8 Apr 2015 13:57:53 +0200 Subject: [PATCH 13/34] Use the new CuraEngine GCode protocol instead of temp files. --- PrinterConnection.py | 10 +++------- USBPrinterManager.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 95ed0d1998..0e574a55c0 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -93,7 +93,7 @@ class PrinterConnection(SignalEmitter): return self._gcode = gcode_list #Reset line number. If this is not done, first line is sometimes ignored - self._gcode.insert(0,"M110 N0") + self._gcode.insert(0, "M110") self._gcode_position = 0 self._print_start_time_100 = None self._is_printing = True @@ -150,8 +150,7 @@ class PrinterConnection(SignalEmitter): self.setIsConnected(True) return self.setIsConnected(False) - - + ## Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those. def setBaudRate(self, baud_rate): try: @@ -159,9 +158,7 @@ class PrinterConnection(SignalEmitter): return True except Exception as e: return False - - - + def setIsConnected(self, state): self._is_connecting = False if self._is_connected != state: @@ -358,4 +355,3 @@ class PrinterConnection(SignalEmitter): #ret.remove(prev) #ret.insert(0, prev) return ret - \ No newline at end of file diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 83e67a55ca..6ec9062544 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -113,15 +113,15 @@ class USBPrinterManager(SignalEmitter,PluginObject): }) else: Application.getInstance().removeOutputDevice(serial_port) + def _writeToSerial(self, serial_port): - for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): - if type(node) is not SceneNode or not node.getMeshData(): - continue - - gcode = getattr(node.getMeshData(), 'gcode', False) - self.sendGCodeByPort(serial_port,gcode.split('\n')) - - + gcode_list = getattr(Application.getInstance().getController().getScene(), 'gcode_list', None) + if gcode_list: + final_list = [] + for gcode in gcode_list: + final_list += gcode.split('\n') + self.sendGCodeByPort(serial_port, gcode_list) + ## Get a list of printer connection objects that are connected. def getActiveConnections(self): return [connection for connection in self._printer_connections if connection.isConnected()] From 5e8f767b78d5ed52e9fd3013da8901ae67c46759 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 10 Apr 2015 11:25:16 +0200 Subject: [PATCH 14/34] Added window for usb printing --- PrinterConnection.py | 21 ++++++++++++- USBPrinterManager.py | 75 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 95ed0d1998..a9be3d9309 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -32,6 +32,8 @@ class PrinterConnection(SignalEmitter): # response. If the baudrate is correct, this should make sense, else we get giberish. self._required_responses_auto_baud = 10 + self._progress = 0 + self._listen_thread = threading.Thread(target=self._listen) self._listen_thread.daemon = True @@ -327,7 +329,24 @@ class PrinterConnection(SignalEmitter): checksum = functools.reduce(lambda x,y: x^y, map(ord, 'N%d%s' % (self._gcode_position, line))) self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) - self._gcode_position += 1 + self._gcode_position += 1 + self.setProgress(( self._gcode_position / len(self._gcode)) * 100) + self.progressChanged.emit(self._progress, self._serial_port) + + progressChanged = Signal() + + def setProgress(self, progress): + self._progress = progress + self.progressChanged.emit(self._progress, self._serial_port) + + def cancelPrint(self): + self._gcode_position = 0 + self.setProgress(0) + self._gcode = [] + # Turn of temperatures + self._sendCommand("M140 S0") + self._sendCommand("M109 S0") + self._is_printing = False def hasError(self): return False diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 83e67a55ca..3dde9aca0b 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -10,16 +10,27 @@ import glob import time import os -class USBPrinterManager(SignalEmitter,PluginObject): - def __init__(self): - super().__init__() +from PyQt5.QtQuick import QQuickView +from PyQt5.QtCore import QUrl, QObject,pyqtSlot , pyqtProperty,pyqtSignal + +class USBPrinterManager(QObject, SignalEmitter,PluginObject): + def __init__(self, parent = None): + super().__init__(parent) self._serial_port_list = [] self._printer_connections = [] self._check_ports_thread = threading.Thread(target=self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() + self._progress = 50 + + ## DEBUG CODE + self.view = QQuickView() + self.view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) + self.view.show() + self.view.engine().rootContext().setContextProperty('manager',self) + #time.sleep(1) #self.connectAllConnections() #time.sleep(5) @@ -31,9 +42,27 @@ class USBPrinterManager(SignalEmitter,PluginObject): #print("sending heat " , self.sendCommandToAllActive("M104 S190")) + #def spawnInterface(self): + #view = QQuickView() + #view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) + #view.show() + ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! # This is only done when the connect function is called. + + + processingProgress = pyqtSignal(float, arguments = ['amount']) + #@pyqtProperty(float, notify = processingProgress) + @pyqtProperty(float,notify = processingProgress) + def progress(self): + return self._progress + + @pyqtSlot() + def test(self): + print("derp") + + def _updateConnectionList(self): while True: temp_serial_port_list = self.getSerialPortList(only_list_usb = True) @@ -46,6 +75,7 @@ class USBPrinterManager(SignalEmitter,PluginObject): connection = PrinterConnection.PrinterConnection(serial_port) connection.connect() connection.connectionStateChanged.connect(self.serialConectionStateCallback) + connection.progressChanged.connect(self.onProgress) self._printer_connections.append(connection) for serial_port in disconnected_ports: # Close connections and remove them from list. @@ -55,6 +85,11 @@ class USBPrinterManager(SignalEmitter,PluginObject): connection.close() time.sleep(5) #Throttle, as we don't need this information to be updated every single second. + def onProgress(self, progress, serial_port): + self._progress = progress + self.processingProgress.emit(progress) + pass + ## Attempt to connect with all possible connections. def connectAllConnections(self): for connection in self._printer_connections: @@ -67,6 +102,10 @@ class USBPrinterManager(SignalEmitter,PluginObject): printer_connection.printGCode(gcode_list) return True return False + @pyqtSlot() + def cancelPrint(self): + for printer_connection in self.getActiveConnections(): + printer_connection.cancelPrint() ## Send gcode to all active printers. # \return True if there was at least one active connection. @@ -103,17 +142,27 @@ class USBPrinterManager(SignalEmitter,PluginObject): def serialConectionStateCallback(self,serial_port): connection = self.getConnectionByPort(serial_port) - if connection.isConnected(): - Application.getInstance().addOutputDevice(serial_port, { - 'id': serial_port, - 'function': self._writeToSerial, - 'description': 'Write to USB {0}'.format(serial_port), - 'icon': 'print_usb', - 'priority': 1 - }) - else: - Application.getInstance().removeOutputDevice(serial_port) + if connection is not None: + if connection.isConnected(): + Application.getInstance().addOutputDevice(serial_port, { + 'id': serial_port, + 'function': self._writeToSerial, + 'description': 'Write to USB {0}'.format(serial_port), + 'icon': 'print_usb', + 'priority': 1 + }) + else: + Application.getInstance().removeOutputDevice(serial_port) + + def _writeToSerial(self, serial_port): + ## Create USB control window + #self.view = QQuickView() + #self.view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) + #self.view.show() + + #self.view.engine().rootContext().setContextProperty('manager',self) + for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): if type(node) is not SceneNode or not node.getMeshData(): continue From 988741d418672be44d6ef30f5412fcd83f6ba978 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 10 Apr 2015 12:01:06 +0200 Subject: [PATCH 15/34] added missing qml --- ControlWindow.qml | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ControlWindow.qml diff --git a/ControlWindow.qml b/ControlWindow.qml new file mode 100644 index 0000000000..944c48a14d --- /dev/null +++ b/ControlWindow.qml @@ -0,0 +1,42 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +Rectangle +{ + width: 300; height: 100 + ColumnLayout + { + Text + { + text: "Hello world!" + color: "blue" + } + RowLayout + { + Button + { + text: "Print" + onClicked: { manager.startPrint() } + } + Button + { + text: "Cancel" + onClicked: { manager.cancelPrint() } + } + } + ProgressBar + { + id: prog; + value: manager.progress + minimumValue: 0; + maximumValue: 100; + Layout.maximumWidth:parent.width + Layout.preferredWidth:230 + Layout.preferredHeight:25 + Layout.minimumWidth:230 + Layout.minimumHeight:25 + width: 230 + height: 25 + } + } +} \ No newline at end of file From d7262c6ac69356337bba6be49d6a4765d15769cb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 10 Apr 2015 13:41:28 +0200 Subject: [PATCH 16/34] Added bed & extruder temp + error state logging to usb gui --- ControlWindow.qml | 19 ++++++++-- PrinterConnection.py | 42 +++++++++++++++++---- USBPrinterManager.py | 87 ++++++++++++++++++++++++++++++++------------ 3 files changed, 114 insertions(+), 34 deletions(-) diff --git a/ControlWindow.qml b/ControlWindow.qml index 944c48a14d..4ea83f7bb3 100644 --- a/ControlWindow.qml +++ b/ControlWindow.qml @@ -6,10 +6,21 @@ Rectangle width: 300; height: 100 ColumnLayout { - Text + RowLayout { - text: "Hello world!" - color: "blue" + Text + { + text: "extruder temperature " + manager.extruderTemperature + } + Text + { + text: "bed temperature " + manager.bedTemperature + } + Text + { + text: "" + manager.error + } + } RowLayout { @@ -17,11 +28,13 @@ Rectangle { text: "Print" onClicked: { manager.startPrint() } + enabled: manager.progress == 0 ? true : false } Button { text: "Cancel" onClicked: { manager.cancelPrint() } + enabled: manager.progress == 0 ? false: true } } ProgressBar diff --git a/PrinterConnection.py b/PrinterConnection.py index 0ec7435817..9d3cb1c767 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -119,9 +119,7 @@ class PrinterConnection(SignalEmitter): programmer = stk500v2.Stk500v2() try: programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. - self._serial = programmer.leaveISP() - # Create new printer connection - Logger.log('i', "Established connection on port %s" % self._serial_port) + self._serial = programmer.leaveISP() except ispBase.IspError as e: Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) except: @@ -150,6 +148,7 @@ class PrinterConnection(SignalEmitter): if sucesfull_responses >= self._required_responses_auto_baud: self._serial.timeout = 2 #Reset serial timeout self.setIsConnected(True) + Logger.log('i', "Established connection on port %s" % self._serial_port) return self.setIsConnected(False) @@ -223,9 +222,11 @@ class PrinterConnection(SignalEmitter): self._serial.write((cmd + '\n').encode()) except Exception as e: Logger.log("e","Unexpected error while writing serial port %s " % e) + self._setErrorState("Unexpected error while writing serial port %s " % e) self.close() except Exception as e: Logger.log('e',"Unexpected error while writing serial port %s" % e) + self._setErrorState("Unexpected error while writing serial port %s " % e) self.close() ## Ensure that close gets called when object is destroyed @@ -240,6 +241,28 @@ class PrinterConnection(SignalEmitter): elif self.isConnected(): self._sendCommand(cmd) + def _setErrorState(self, error): + self._error_state = error + self.onError.emit(error) + + onError = Signal() + + def _setExtruderTemperature(self, index, temperature): + try: + self._extruder_temperatures[index] = temperature + self.onExtruderTemperatureChange.emit(self._serial_port,index,temperature) + except: + pass + + onExtruderTemperatureChange = Signal() + + def _setBedTemperature(self, temperature): + self._bed_temperature = temperature + self.onBedTemperatureChange.emit(self._serial_port,temperature) + + onBedTemperatureChange = Signal() + + ## Listen thread function. def _listen(self): temperature_request_timeout = time.time() @@ -258,16 +281,19 @@ class PrinterConnection(SignalEmitter): #Skip the communication errors, as those get corrected. if b'Extruder switched off' in line or b'Temperature heated bed switched off' in line or b'Something is wrong, please turn off the printer.' in line: if not self.hasError(): - self._error_state = line[6:] + self._setErrorState(line[6:]) + #self._error_state = line[6:] elif b' T:' in line or line.startswith(b'T:'): #Temperature message try: - self._extruder_temperatures[self._temperatureRequestExtruder] = float(re.search(b"T: *([0-9\.]*)", line).group(1)) + self._setExtruderTemperature(self._temperature_requested_extruder_index,float(re.search(b"T: *([0-9\.]*)", line).group(1))) + #self._extruder_temperatures[self._temperature_requested_extruder_index] = float(re.search(b"T: *([0-9\.]*)", line).group(1)) except: pass if b'B:' in line: #Check if it's a bed temperature try: + self._setBedTemperature(float(re.search(b"B: *([0-9\.]*)", line).group(1))) #print("BED TEMPERATURE" ,float(re.search(b"B: *([0-9\.]*)", line).group(1))) - pass + except: pass #TODO: temperature changed callback @@ -323,6 +349,7 @@ class PrinterConnection(SignalEmitter): self._current_z = z except Exception as e: Logger.log('e', "Unexpected error: %s" % e) + self._setErrorState("Unexpected error: %s" %e) checksum = functools.reduce(lambda x,y: x^y, map(ord, 'N%d%s' % (self._gcode_position, line))) self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) @@ -354,7 +381,8 @@ class PrinterConnection(SignalEmitter): try: ret = self._serial.readline() except: - Logger.log('e',"Unexpected error while reading serial port.") + Logger.log('e',"Unexpected error while reading serial port.") + self._setErrorState("Printer has been disconnected") #self._errorValue = getExceptionString() self.close() return None diff --git a/USBPrinterManager.py b/USBPrinterManager.py index e3102d2e33..feabfd6541 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -22,15 +22,13 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): self._check_ports_thread.daemon = True self._check_ports_thread.start() - self._progress = 50 - + self._progress = 0 - ## DEBUG CODE - self.view = QQuickView() - self.view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) - self.view.show() - self.view.engine().rootContext().setContextProperty('manager',self) + self.view = None + self._extruder_temp = 0 + self._bed_temp = 0 + self._error_message = "" #time.sleep(1) #self.connectAllConnections() #time.sleep(5) @@ -42,27 +40,37 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): #print("sending heat " , self.sendCommandToAllActive("M104 S190")) - #def spawnInterface(self): - #view = QQuickView() - #view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) - #view.show() - - ## Check all serial ports and create a PrinterConnection object for them. - # Note that this does not validate if the serial ports are actually usable! - # This is only done when the connect function is called. - + def spawnInterface(self,serial_port): + if self.view is None: + self.view = QQuickView() + self.view.engine().rootContext().setContextProperty('manager',self) + self.view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) + self.view.show() + processingProgress = pyqtSignal(float, arguments = ['amount']) - #@pyqtProperty(float, notify = processingProgress) @pyqtProperty(float,notify = processingProgress) def progress(self): return self._progress - @pyqtSlot() - def test(self): - print("derp") + pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) + @pyqtProperty(float,notify = pyqtExtruderTemperature) + def extruderTemperature(self): + return self._extruder_temp + pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) + @pyqtProperty(float,notify = pyqtBedTemperature) + def bedTemperature(self): + return self._bed_temp + pyqtError = pyqtSignal(str, arguments = ['amount']) + @pyqtProperty(str,notify = pyqtError) + def error(self): + return self._error_message + + ## Check all serial ports and create a PrinterConnection object for them. + # Note that this does not validate if the serial ports are actually usable! + # This (the validation) is only done when the connect function is called. def _updateConnectionList(self): while True: temp_serial_port_list = self.getSerialPortList(only_list_usb = True) @@ -76,6 +84,9 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): connection.connect() connection.connectionStateChanged.connect(self.serialConectionStateCallback) connection.progressChanged.connect(self.onProgress) + connection.onExtruderTemperatureChange.connect(self.onExtruderTemperature) + connection.onBedTemperatureChange.connect(self.onBedTemperature) + connection.onError.connect(self.onError) self._printer_connections.append(connection) for serial_port in disconnected_ports: # Close connections and remove them from list. @@ -85,6 +96,24 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): connection.close() time.sleep(5) #Throttle, as we don't need this information to be updated every single second. + def onExtruderTemperature(self, serial_port, index,temperature): + #print("ExtruderTemperature " , serial_port, " " , index, " " , temperature) + self._extruder_temp = temperature + self.pyqtExtruderTemperature.emit(temperature) + + pass + + def onBedTemperature(self, serial_port,temperature): + self._bed_temperature = temperature + self.pyqtBedTemperature.emit(temperature) + #print("bedTemperature " , serial_port, " " , temperature) + pass + + def onError(self, error): + self._error_message = error + self.pyqtError.emit(error) + pass + def onProgress(self, progress, serial_port): self._progress = progress self.processingProgress.emit(progress) @@ -102,6 +131,7 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): printer_connection.printGCode(gcode_list) return True return False + @pyqtSlot() def cancelPrint(self): for printer_connection in self.getActiveConnections(): @@ -145,21 +175,30 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): if connection.isConnected(): Application.getInstance().addOutputDevice(serial_port, { 'id': serial_port, - 'function': self._writeToSerial, + 'function': self.spawnInterface, 'description': 'Write to USB {0}'.format(serial_port), 'icon': 'print_usb', 'priority': 1 }) else: Application.getInstance().removeOutputDevice(serial_port) - - def _writeToSerial(self, serial_port): + + '''def _writeToSerial(self, serial_port): gcode_list = getattr(Application.getInstance().getController().getScene(), 'gcode_list', None) if gcode_list: final_list = [] for gcode in gcode_list: final_list += gcode.split('\n') - self.sendGCodeByPort(serial_port, gcode_list) + self.sendGCodeByPort(serial_port, gcode_list)''' + @pyqtSlot() + def startPrint(self): + gcode_list = getattr(Application.getInstance().getController().getScene(), 'gcode_list', None) + if gcode_list: + final_list = [] + for gcode in gcode_list: + final_list += gcode.split('\n') + self.sendGCodeToAllActive(gcode_list) + ## Get a list of printer connection objects that are connected. def getActiveConnections(self): From f5e8e7df80ad1ab9b53f0e6d29987f5bd5e76951 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Apr 2015 15:40:25 +0200 Subject: [PATCH 17/34] Added upload firmware function to printer connection --- PrinterConnection.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 9d3cb1c767..e9d44ac10e 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -9,6 +9,7 @@ import functools from UM.Application import Application from UM.Signal import Signal, SignalEmitter +from UM.Resources import Resources class PrinterConnection(SignalEmitter): @@ -112,6 +113,29 @@ class PrinterConnection(SignalEmitter): def connect(self): if not self._connect_thread.isAlive(): self._connect_thread.start() + + + def updateFirmware(self, file_name): + if self._is_connecting or self._is_connected: + return False + + hex_file = intelHex.readHex(file_name) + if len(hex_file) == 0: + Logger.log('e', "Unable to read provided hex file. Could not update firmware") + return False + programmer = stk500v2.Stk500v2() + programmer.connect(self._serial_port) + time.sleep(1) #Give programmer some time to connect + if not programmer.isConnected(): + Logger.log('e', "Unable to connect with serial. Could not update firmware") + return False + try: + programmer.programChip(hex_file) + except Exception as e: + Logger.log("e", "Exception while trying to update firmware" , e) + return False + programmer.close() + return True ## Private connect function run by thread. Can be started by calling connect. def _connect(self): @@ -121,7 +145,7 @@ class PrinterConnection(SignalEmitter): programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. self._serial = programmer.leaveISP() except ispBase.IspError as e: - Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) + Logger.log('i', "Could not establish connect ion on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) except: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) From 6e9a07d73c2c650598544b59401a4ec9813c7d9d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Apr 2015 10:32:06 +0200 Subject: [PATCH 18/34] Added check to prevent printing when firmware is being updated --- PrinterConnection.py | 13 +++++++++---- USBPrinterManager.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index e9d44ac10e..c4447643d6 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -77,6 +77,8 @@ class PrinterConnection(SignalEmitter): # This index is the extruder we requested data from the last time. self._temperature_requested_extruder_index = 0 + self._updating_firmware = False + #TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object def setNumExtuders(self, num): self._extruder_count = num @@ -111,10 +113,9 @@ class PrinterConnection(SignalEmitter): ## Try to connect the serial. This simply starts the thread, which runs _connect. def connect(self): - if not self._connect_thread.isAlive(): + if not self._updating_firmware and not self._connect_thread.isAlive(): self._connect_thread.start() - - + def updateFirmware(self, file_name): if self._is_connecting or self._is_connected: return False @@ -124,15 +125,19 @@ class PrinterConnection(SignalEmitter): Logger.log('e', "Unable to read provided hex file. Could not update firmware") return False programmer = stk500v2.Stk500v2() + programmer.progressCallback = self.setProgress programmer.connect(self._serial_port) time.sleep(1) #Give programmer some time to connect if not programmer.isConnected(): Logger.log('e', "Unable to connect with serial. Could not update firmware") return False + self._updating_firmware = True try: programmer.programChip(hex_file) + self._updating_firmware = False except Exception as e: Logger.log("e", "Exception while trying to update firmware" , e) + self._updating_firmware = False return False programmer.close() return True @@ -383,7 +388,7 @@ class PrinterConnection(SignalEmitter): progressChanged = Signal() - def setProgress(self, progress): + def setProgress(self, progress,max_progress = 100): self._progress = progress self.progressChanged.emit(self._progress, self._serial_port) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index feabfd6541..ba0db5cfc7 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -9,6 +9,7 @@ import platform import glob import time import os +import sys from PyQt5.QtQuick import QQuickView from PyQt5.QtCore import QUrl, QObject,pyqtSlot , pyqtProperty,pyqtSignal @@ -29,6 +30,7 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): self._extruder_temp = 0 self._bed_temp = 0 self._error_message = "" + #time.sleep(1) #self.connectAllConnections() #time.sleep(5) @@ -103,6 +105,39 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): pass + + def updateFirmwareBySerial(self, serial_port): + printer_connection = self.getConnectionByPort(serial_port) + if printer_connection is not None: + printer_connection.updateFirmware(Resources.getPath(Resources.FirmwareLocation, self._getDefaultFirmwareName())) + + def _getDefaultFirmwareName(self): + machine_type = Application.getInstance().getActiveMachine().getTypeID() + firmware_name = "" + baudrate = 250000 + if sys.platform.startswith('linux'): + baudrate = 115200 + if machine_type == "ultimaker_original": + firmware_name = 'MarlinUltimaker' + firmware_name += '-%d' % (baudrate) + elif machine_type == "ultimaker_original_plus": + firmware_name = 'MarlinUltimaker-UMOP-%d' % (baudrate) + elif machine_type == "Witbox": + return "MarlinWitbox.hex" + elif machine_type == "ultimaker2go": + return "MarlinUltimaker2go.hex" + elif machine_type == "ultimaker2extended": + return "MarlinUltimaker2extended.hex" + elif machine_type == "ultimaker2": + return "MarlinUltimaker2.hex" + + + ##TODO: Add check for multiple extruders + + if firmware_name != "": + firmware_name += ".hex" + return firmware_name + def onBedTemperature(self, serial_port,temperature): self._bed_temperature = temperature self.pyqtBedTemperature.emit(temperature) From 0defece4bdf330bf0cb47297a5755c48d73d4c70 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Apr 2015 14:31:52 +0200 Subject: [PATCH 19/34] Added extensions as a working plugin type --- USBPrinterManager.py | 12 +++++++++--- __init__.py | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index ba0db5cfc7..6180ed66e8 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -1,20 +1,21 @@ from UM.Signal import Signal, SignalEmitter -from UM.PluginObject import PluginObject from . import PrinterConnection from UM.Application import Application from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode +from UM.Resources import Resources import threading import platform import glob import time import os import sys +from UM.Extension import Extension from PyQt5.QtQuick import QQuickView from PyQt5.QtCore import QUrl, QObject,pyqtSlot , pyqtProperty,pyqtSignal -class USBPrinterManager(QObject, SignalEmitter,PluginObject): +class USBPrinterManager(QObject, SignalEmitter, Extension): def __init__(self, parent = None): super().__init__(parent) self._serial_port_list = [] @@ -31,6 +32,8 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): self._bed_temp = 0 self._error_message = "" + self.addMenuItem("Update firmware", self.updateAllFirmware) + #time.sleep(1) #self.connectAllConnections() #time.sleep(5) @@ -105,7 +108,10 @@ class USBPrinterManager(QObject, SignalEmitter,PluginObject): pass - + def updateAllFirmware(self): + for printer_connection in self._printer_connections: + printer_connection.updateFirmware(Resources.getPath(Resources.FirmwareLocation, self._getDefaultFirmwareName())) + def updateFirmwareBySerial(self, serial_port): printer_connection = self.getConnectionByPort(serial_port) if printer_connection is not None: diff --git a/__init__.py b/__init__.py index ee73863826..c5c0a4733d 100644 --- a/__init__.py +++ b/__init__.py @@ -1,12 +1,12 @@ from . import USBPrinterManager def getMetaData(): return { - 'type': 'storage_device', + 'type': 'extension', 'plugin': { - 'name': 'Local File Storage', + 'name': 'USB printing', 'author': 'Jaime van Kessel', 'version': '1.0', - 'description': 'Accepts G-Code and sends them to a printer. ' + 'description': 'Accepts G-Code and sends them to a printer. Plugin can also update firmware ' } } From f40855a203efec75248f5fbcdc24c0b6f4ce75a5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 15 Apr 2015 14:45:56 +0200 Subject: [PATCH 20/34] Added pointcloud alignment tool for scanner --- USBPrinterManager.py | 6 +++++- __init__.py | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 6180ed66e8..31ec5a5298 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -15,6 +15,10 @@ from UM.Extension import Extension from PyQt5.QtQuick import QQuickView from PyQt5.QtCore import QUrl, QObject,pyqtSlot , pyqtProperty,pyqtSignal +from UM.i18n import i18nCatalog + +i18n_catalog = i18nCatalog('plugins') + class USBPrinterManager(QObject, SignalEmitter, Extension): def __init__(self, parent = None): super().__init__(parent) @@ -32,7 +36,7 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): self._bed_temp = 0 self._error_message = "" - self.addMenuItem("Update firmware", self.updateAllFirmware) + self.addMenuItem(i18n_catalog.i18n("Update firmware"), self.updateAllFirmware) #time.sleep(1) #self.connectAllConnections() diff --git a/__init__.py b/__init__.py index c5c0a4733d..243fc5ba0f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,9 @@ from . import USBPrinterManager + +from UM.i18n import i18nCatalog + +i18n_catalog = i18nCatalog('plugins') + def getMetaData(): return { 'type': 'extension', @@ -6,9 +11,9 @@ def getMetaData(): 'name': 'USB printing', 'author': 'Jaime van Kessel', 'version': '1.0', - 'description': 'Accepts G-Code and sends them to a printer. Plugin can also update firmware ' + 'description': i18n_catalog.i18nc('usb printing description','Accepts G-Code and sends them to a printer. Plugin can also update firmware') } } - + def register(app): return USBPrinterManager.USBPrinterManager() From f02016e9e4e24943e9288fe482af7f41eea93a03 Mon Sep 17 00:00:00 2001 From: daid Date: Thu, 16 Apr 2015 14:02:50 +0200 Subject: [PATCH 21/34] Add missing import. --- USBPrinterManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 31ec5a5298..4bcbef5715 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -261,6 +261,7 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): def getSerialPortList(self,only_list_usb=False): base_list=[] if platform.system() == "Windows": + import _winreg try: key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") i=0 From d91316a7434af6ea520f2968faf1b25ea680e71c Mon Sep 17 00:00:00 2001 From: daid Date: Thu, 16 Apr 2015 14:36:19 +0200 Subject: [PATCH 22/34] Python2->3 fix. --- USBPrinterManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 4bcbef5715..badbff5e38 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -261,12 +261,12 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): def getSerialPortList(self,only_list_usb=False): base_list=[] if platform.system() == "Windows": - import _winreg + import winreg try: - key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") + key=winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") i=0 while True: - values = _winreg.EnumValue(key, i) + values = winreg.EnumValue(key, i) if not base_list or 'USBSER' in values[0]: base_list+=[values[1]] i+=1 From 40d34564c1518f2ac82b2d4248050e14dda1d257 Mon Sep 17 00:00:00 2001 From: daid Date: Fri, 17 Apr 2015 07:38:00 +0200 Subject: [PATCH 23/34] Fix some sloppy coding from Nallath. --- PrinterConnection.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index c4447643d6..c0181a2406 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -1,5 +1,5 @@ from UM.Logger import Logger -from .avr_isp import stk500v2, ispBase +from .avr_isp import stk500v2, ispBase, intelHex import serial import threading import time @@ -158,7 +158,11 @@ class PrinterConnection(SignalEmitter): for baud_rate in self._getBaudrateList(): #Cycle all baud rates (auto detect) if self._serial is None: - self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout=3, writeTimeout=10000) + try: + self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout=3, writeTimeout=10000) + except serial.SerialException: + Logger.log('i', "Could not open port %s" % self._serial_port) + return else: if not self.setBaudRate(baud_rate): continue #Could not set the baud rate, go to the next @@ -179,6 +183,7 @@ class PrinterConnection(SignalEmitter): self.setIsConnected(True) Logger.log('i', "Established connection on port %s" % self._serial_port) return + self.close() self.setIsConnected(False) ## Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those. @@ -211,7 +216,7 @@ class PrinterConnection(SignalEmitter): ## Close the printer connection def close(self): - if self._serial != None: + if self._serial is not None: self._serial.close() self.setIsConnected(False) self._serial = None From de03470dbc41ab23cdf6623da3f10d81e4b4d9bd Mon Sep 17 00:00:00 2001 From: daid Date: Fri, 17 Apr 2015 08:52:36 +0200 Subject: [PATCH 24/34] Add gcode prefix message handling. Fix gcode flavor issue. --- PrinterConnection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index c0181a2406..d5fb1e1690 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -150,7 +150,7 @@ class PrinterConnection(SignalEmitter): programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. self._serial = programmer.leaveISP() except ispBase.IspError as e: - Logger.log('i', "Could not establish connect ion on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) + Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) except: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) From 34aac653b4c0a90bc74d55553ccfd544e7a3eb3b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Apr 2015 09:55:18 +0200 Subject: [PATCH 25/34] Updating firmware now spawns progress window --- FirmwareUpdateWindow.qml | 29 +++++++++++++++++++++++++++++ USBPrinterManager.py | 39 ++++++++++++++++++++------------------- 2 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 FirmwareUpdateWindow.qml diff --git a/FirmwareUpdateWindow.qml b/FirmwareUpdateWindow.qml new file mode 100644 index 0000000000..2ae6ed40d5 --- /dev/null +++ b/FirmwareUpdateWindow.qml @@ -0,0 +1,29 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +Rectangle +{ + width: 300; height: 100 + ColumnLayout + { + + Text + { + text: "Updating firmware + } + ProgressBar + { + id: prog; + value: manager.progress + minimumValue: 0; + maximumValue: 100; + Layout.maximumWidth:parent.width + Layout.preferredWidth:230 + Layout.preferredHeight:25 + Layout.minimumWidth:230 + Layout.minimumHeight:25 + width: 230 + height: 25 + } + } +} \ No newline at end of file diff --git a/USBPrinterManager.py b/USBPrinterManager.py index badbff5e38..8f2ba5e90a 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -31,30 +31,29 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): self._progress = 0 - self.view = None + self._control_view = None + self._firmware_view = None self._extruder_temp = 0 self._bed_temp = 0 self._error_message = "" + ## Add menu item to top menu. self.addMenuItem(i18n_catalog.i18n("Update firmware"), self.updateAllFirmware) - - #time.sleep(1) - #self.connectAllConnections() - #time.sleep(5) - #f = open("Orb.gcode") - #lines = f.readlines() - #print(len(lines)) - #print(len(self._printer_connections)) - #self.sendGCodeToAllActive(lines) - #print("sending heat " , self.sendCommandToAllActive("M104 S190")) - - def spawnInterface(self,serial_port): - if self.view is None: - self.view = QQuickView() - self.view.engine().rootContext().setContextProperty('manager',self) - self.view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) - self.view.show() + def spawnFirmwareInterface(self, serial_port): + if self._firmware_view is None: + self._firmware_view = QQuickView() + self._firmware_view.engine().rootContext().setContextProperty('manager',self) + self._firmware_view.setSource(QUrl("plugins/USBPrinting/FirmwareUpdateWindow.qml")) + self._firmware_view.show() + + + def spawnControlInterface(self,serial_port): + if self._control_view is None: + self._control_view = QQuickView() + self._control_view.engine().rootContext().setContextProperty('manager',self) + self._control_view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) + self._control_view.show() processingProgress = pyqtSignal(float, arguments = ['amount']) @@ -113,12 +112,14 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): pass def updateAllFirmware(self): + self.spawnFirmwareInterface("") for printer_connection in self._printer_connections: printer_connection.updateFirmware(Resources.getPath(Resources.FirmwareLocation, self._getDefaultFirmwareName())) def updateFirmwareBySerial(self, serial_port): printer_connection = self.getConnectionByPort(serial_port) if printer_connection is not None: + self.spawnFirmwareInterface(printer_connection.getSerialPort()) printer_connection.updateFirmware(Resources.getPath(Resources.FirmwareLocation, self._getDefaultFirmwareName())) def _getDefaultFirmwareName(self): @@ -220,7 +221,7 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): if connection.isConnected(): Application.getInstance().addOutputDevice(serial_port, { 'id': serial_port, - 'function': self.spawnInterface, + 'function': self.spawnControlInterface, 'description': 'Write to USB {0}'.format(serial_port), 'icon': 'print_usb', 'priority': 1 From a07781fa264893b0b724b402e8954df83414f447 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Apr 2015 10:40:44 +0200 Subject: [PATCH 26/34] Bugfixes to usb printing --- FirmwareUpdateWindow.qml | 2 +- PrinterConnection.py | 9 ++++++--- avr_isp/intelHex.py | 2 +- avr_isp/stk500v2.py | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/FirmwareUpdateWindow.qml b/FirmwareUpdateWindow.qml index 2ae6ed40d5..e3777e853f 100644 --- a/FirmwareUpdateWindow.qml +++ b/FirmwareUpdateWindow.qml @@ -9,7 +9,7 @@ Rectangle Text { - text: "Updating firmware + text: "Updating firmware" } ProgressBar { diff --git a/PrinterConnection.py b/PrinterConnection.py index d5fb1e1690..d94beea6c5 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -117,9 +117,9 @@ class PrinterConnection(SignalEmitter): self._connect_thread.start() def updateFirmware(self, file_name): + print("Update firmware; " , self._is_connecting, " ", self._is_connected ) if self._is_connecting or self._is_connected: - return False - + self.close() hex_file = intelHex.readHex(file_name) if len(hex_file) == 0: Logger.log('e', "Unable to read provided hex file. Could not update firmware") @@ -136,7 +136,7 @@ class PrinterConnection(SignalEmitter): programmer.programChip(hex_file) self._updating_firmware = False except Exception as e: - Logger.log("e", "Exception while trying to update firmware" , e) + Logger.log("e", "Exception while trying to update firmware %s" %e) self._updating_firmware = False return False programmer.close() @@ -173,6 +173,9 @@ class PrinterConnection(SignalEmitter): self._sendCommand("M105") #Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it while timeout_time > time.time(): line = self._readline() + if line is None: + self.setIsConnected(False) # something went wrong with reading, could be that close was called. + return if b"T:" in line: self._serial.timeout = 0.5 self._serial.write(b"\n") diff --git a/avr_isp/intelHex.py b/avr_isp/intelHex.py index 75c7d3a5b2..822936ef1b 100644 --- a/avr_isp/intelHex.py +++ b/avr_isp/intelHex.py @@ -34,7 +34,7 @@ def readHex(filename): if recType == 0:#Data record while len(data) < addr + recLen: data.append(0) - for i in xrange(0, recLen): + for i in range(0, recLen): data[addr + i] = int(line[i*2+9:i*2+11], 16) elif recType == 1: #End Of File record pass diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index b2d2449727..778adc7da3 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -86,7 +86,7 @@ class Stk500v2(ispBase.IspBase): self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) loadCount = (len(flashData) + pageSize - 1) / pageSize - for i in xrange(0, loadCount): + for i in range(0, loadCount): recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)]) if self.progressCallback is not None: if self._has_checksum: @@ -114,11 +114,11 @@ class Stk500v2(ispBase.IspBase): self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) loadCount = (len(flashData) + 0xFF) / 0x100 - for i in xrange(0, loadCount): + for i in range(0, loadCount): recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] if self.progressCallback is not None: self.progressCallback(loadCount + i + 1, loadCount*2) - for j in xrange(0, 0x100): + for j in range(0, 0x100): if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]: raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j)) From d66faf6ecd65785bfd54387b98604388dead87f6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Apr 2015 10:55:37 +0200 Subject: [PATCH 27/34] Added thread safety checks to close function --- PrinterConnection.py | 11 +++++++---- avr_isp/stk500v2.py | 10 +++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index d94beea6c5..ff5e97584d 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -117,7 +117,6 @@ class PrinterConnection(SignalEmitter): self._connect_thread.start() def updateFirmware(self, file_name): - print("Update firmware; " , self._is_connecting, " ", self._is_connected ) if self._is_connecting or self._is_connected: self.close() hex_file = intelHex.readHex(file_name) @@ -219,9 +218,13 @@ class PrinterConnection(SignalEmitter): ## Close the printer connection def close(self): + if self._connect_thread.isAlive(): + self._connect_thread.join() if self._serial is not None: - self._serial.close() self.setIsConnected(False) + self._listen_thread.join() + self._serial.close() + self._serial = None def isConnected(self): @@ -417,8 +420,8 @@ class PrinterConnection(SignalEmitter): return None try: ret = self._serial.readline() - except: - Logger.log('e',"Unexpected error while reading serial port.") + except Exception as e: + Logger.log('e',"Unexpected error while reading serial port. %s" %e) self._setErrorState("Printer has been disconnected") #self._errorValue = getExceptionString() self.close() diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index 778adc7da3..f4c352f977 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -76,18 +76,18 @@ class Stk500v2(ispBase.IspBase): recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]]) return recv[2:6] - def writeFlash(self, flashData): + def writeFlash(self, flash_data): #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension - pageSize = self.chip['pageSize'] * 2 - flashSize = pageSize * self.chip['pageCount'] + page_size = self.chip['pageSize'] * 2 + flashSize = page_size * self.chip['pageCount'] if flashSize > 0xFFFF: self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) else: self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) - loadCount = (len(flashData) + pageSize - 1) / pageSize + loadCount = (len(flash_data) + page_size - 1) / page_size for i in range(0, loadCount): - recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)]) + recv = self.sendMessage([0x13, page_size >> 8, page_size & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flash_data[(i * page_size):(i * page_size + page_size)]) if self.progressCallback is not None: if self._has_checksum: self.progressCallback(i + 1, loadCount) From cc9e67533c4418e17ebaa2fc2b52ada102eee84f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Apr 2015 11:25:52 +0200 Subject: [PATCH 28/34] Moved updating firmware to own thread --- PrinterConnection.py | 22 ++++++++++++++++------ avr_isp/ispBase.py | 1 + avr_isp/stk500v2.py | 12 ++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index ff5e97584d..57aedd55a4 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -38,6 +38,9 @@ class PrinterConnection(SignalEmitter): self._listen_thread = threading.Thread(target=self._listen) self._listen_thread.daemon = True + self._update_firmware_thread = threading.Thread(target= self._updateFirmware) + self._update_firmware_thread.demon = True + self._heatup_wait_start_time = time.time() ## Queue for commands that need to be send. Used when command is sent when a print is active. @@ -79,6 +82,8 @@ class PrinterConnection(SignalEmitter): self._updating_firmware = False + self._firmware_file_name = None + #TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object def setNumExtuders(self, num): self._extruder_count = num @@ -116,20 +121,20 @@ class PrinterConnection(SignalEmitter): if not self._updating_firmware and not self._connect_thread.isAlive(): self._connect_thread.start() - def updateFirmware(self, file_name): + def _updateFirmware(self): if self._is_connecting or self._is_connected: self.close() - hex_file = intelHex.readHex(file_name) + hex_file = intelHex.readHex(self._firmware_file_name) if len(hex_file) == 0: Logger.log('e', "Unable to read provided hex file. Could not update firmware") - return False + return programmer = stk500v2.Stk500v2() programmer.progressCallback = self.setProgress programmer.connect(self._serial_port) time.sleep(1) #Give programmer some time to connect if not programmer.isConnected(): Logger.log('e', "Unable to connect with serial. Could not update firmware") - return False + return self._updating_firmware = True try: programmer.programChip(hex_file) @@ -137,9 +142,14 @@ class PrinterConnection(SignalEmitter): except Exception as e: Logger.log("e", "Exception while trying to update firmware %s" %e) self._updating_firmware = False - return False + return programmer.close() - return True + return + + def updateFirmware(self, file_name): + self._firmware_file_name = file_name + self._update_firmware_thread.start() + ## Private connect function run by thread. Can be started by calling connect. def _connect(self): diff --git a/avr_isp/ispBase.py b/avr_isp/ispBase.py index 8d65f03f71..bfed45f0ac 100644 --- a/avr_isp/ispBase.py +++ b/avr_isp/ispBase.py @@ -26,6 +26,7 @@ class IspBase(): self.writeFlash(flashData) print("Verifying %i bytes" % len(flashData)) self.verifyFlash(flashData) + print("Completed") def getSignature(self): """ diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index f4c352f977..1af5416a40 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -80,19 +80,19 @@ class Stk500v2(ispBase.IspBase): #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension page_size = self.chip['pageSize'] * 2 flashSize = page_size * self.chip['pageCount'] + print("Writing flash") if flashSize > 0xFFFF: self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) else: self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) - - loadCount = (len(flash_data) + page_size - 1) / page_size - for i in range(0, loadCount): + load_count = (len(flash_data) + page_size - 1) / page_size + for i in range(0, int(load_count)): recv = self.sendMessage([0x13, page_size >> 8, page_size & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flash_data[(i * page_size):(i * page_size + page_size)]) if self.progressCallback is not None: if self._has_checksum: - self.progressCallback(i + 1, loadCount) + self.progressCallback(i + 1, load_count) else: - self.progressCallback(i + 1, loadCount*2) + self.progressCallback(i + 1, load_count*2) def verifyFlash(self, flashData): if self._has_checksum: @@ -114,7 +114,7 @@ class Stk500v2(ispBase.IspBase): self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) loadCount = (len(flashData) + 0xFF) / 0x100 - for i in range(0, loadCount): + for i in range(0, int(loadCount)): recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] if self.progressCallback is not None: self.progressCallback(loadCount + i + 1, loadCount*2) From 5133d0b2a110e879a0a78973798084dec6234a41 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Apr 2015 13:31:02 +0200 Subject: [PATCH 29/34] Progress of firmware update now works correctly --- FirmwareUpdateWindow.qml | 2 +- PrinterConnection.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/FirmwareUpdateWindow.qml b/FirmwareUpdateWindow.qml index e3777e853f..bc5a33c262 100644 --- a/FirmwareUpdateWindow.qml +++ b/FirmwareUpdateWindow.qml @@ -9,7 +9,7 @@ Rectangle Text { - text: "Updating firmware" + text: manager.progress == 0 ? "Starting firmware update, may take a while.": manager.progress > 99 ? "Firmware update completed.": "Updating firmware." } ProgressBar { diff --git a/PrinterConnection.py b/PrinterConnection.py index 57aedd55a4..28630dc0c5 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -410,7 +410,8 @@ class PrinterConnection(SignalEmitter): progressChanged = Signal() def setProgress(self, progress,max_progress = 100): - self._progress = progress + self._progress = progress / max_progress * 100 #Convert to scale of 0-100 + #self._progress = progress self.progressChanged.emit(self._progress, self._serial_port) def cancelPrint(self): From 4a965e04b4b2bd5bc0abc9342806f4f371b60714 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 22 Apr 2015 11:00:12 +0200 Subject: [PATCH 30/34] Plugin registering is now done by dict instead of single object or list --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 243fc5ba0f..7f1b367b49 100644 --- a/__init__.py +++ b/__init__.py @@ -16,4 +16,4 @@ def getMetaData(): } def register(app): - return USBPrinterManager.USBPrinterManager() + return {"extension":USBPrinterManager.USBPrinterManager()} From f0601675f2c119ad9f75ed9397e9f122040bf3db Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Apr 2015 13:35:16 +0200 Subject: [PATCH 31/34] Code cleanup & added more documentation --- PrinterConnection.py | 123 ++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 28630dc0c5..40669770ef 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -1,4 +1,3 @@ -from UM.Logger import Logger from .avr_isp import stk500v2, ispBase, intelHex import serial import threading @@ -10,7 +9,7 @@ import functools from UM.Application import Application from UM.Signal import Signal, SignalEmitter from UM.Resources import Resources - +from UM.Logger import Logger class PrinterConnection(SignalEmitter): def __init__(self, serial_port): @@ -84,30 +83,32 @@ class PrinterConnection(SignalEmitter): self._firmware_file_name = None - #TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object + # TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object def setNumExtuders(self, num): self._extruder_count = num self._extruder_temperatures = [0] * self._extruder_count self._target_extruder_temperatures = [0] * self._extruder_count - - #TODO: Needs more logic + ## Is the printer actively printing def isPrinting(self): if not self._is_connected or self._serial is None: return False return self._is_printing - ## Provide a list of G-Codes that need to be printed + ## Start a print based on a g-code. + # \param gcode_list List with gcode (strings). def printGCode(self, gcode_list): if self.isPrinting() or not self._is_connected: return self._gcode = gcode_list + #Reset line number. If this is not done, first line is sometimes ignored self._gcode.insert(0, "M110") self._gcode_position = 0 self._print_start_time_100 = None self._is_printing = True self._print_start_time = time.time() + for i in range(0, 4): #Push first 4 entries before accepting other inputs self._sendNextGcodeLine() @@ -120,22 +121,29 @@ class PrinterConnection(SignalEmitter): def connect(self): if not self._updating_firmware and not self._connect_thread.isAlive(): self._connect_thread.start() - + + ## Private fuction (threaded) that actually uploads the firmware. def _updateFirmware(self): if self._is_connecting or self._is_connected: self.close() hex_file = intelHex.readHex(self._firmware_file_name) + if len(hex_file) == 0: Logger.log('e', "Unable to read provided hex file. Could not update firmware") return + programmer = stk500v2.Stk500v2() programmer.progressCallback = self.setProgress programmer.connect(self._serial_port) - time.sleep(1) #Give programmer some time to connect + + time.sleep(1) # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. + if not programmer.isConnected(): Logger.log('e', "Unable to connect with serial. Could not update firmware") return + self._updating_firmware = True + try: programmer.programChip(hex_file) self._updating_firmware = False @@ -144,27 +152,27 @@ class PrinterConnection(SignalEmitter): self._updating_firmware = False return programmer.close() - return + ## Upload new firmware to machine + # \param filename full path of firmware file to be uploaded def updateFirmware(self, file_name): self._firmware_file_name = file_name self._update_firmware_thread.start() - - + ## Private connect function run by thread. Can be started by calling connect. def _connect(self): self._is_connecting = True programmer = stk500v2.Stk500v2() try: - programmer.connect(self._serial_port) #Connect with the serial, if this succeeds, it's an arduino based usb device. + programmer.connect(self._serial_port) # Connect with the serial, if this succeeds, it's an arduino based usb device. self._serial = programmer.leaveISP() except ispBase.IspError as e: Logger.log('i', "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e))) - except: + except Exception as e: Logger.log('i', "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information. - for baud_rate in self._getBaudrateList(): #Cycle all baud rates (auto detect) + for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect) if self._serial is None: try: @@ -174,17 +182,18 @@ class PrinterConnection(SignalEmitter): return else: if not self.setBaudRate(baud_rate): - continue #Could not set the baud rate, go to the next - time.sleep(1.5) #Ensure that we are not talking to the bootloader. 1.5 sec seems to be the magic number + continue # Could not set the baud rate, go to the next + time.sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 sec seems to be the magic number sucesfull_responses = 0 timeout_time = time.time() + 5 self._serial.write(b"\n") - self._sendCommand("M105") #Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it + self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with 'T:' in it while timeout_time > time.time(): line = self._readline() if line is None: - self.setIsConnected(False) # something went wrong with reading, could be that close was called. + self.setIsConnected(False) # Something went wrong with reading, could be that close was called. return + if b"T:" in line: self._serial.timeout = 0.5 self._serial.write(b"\n") @@ -193,9 +202,9 @@ class PrinterConnection(SignalEmitter): if sucesfull_responses >= self._required_responses_auto_baud: self._serial.timeout = 2 #Reset serial timeout self.setIsConnected(True) - Logger.log('i', "Established connection on port %s" % self._serial_port) + Logger.log('i', "Established printer connection on port %s" % self._serial_port) return - self.close() + self.close() # Unable to connect, wrap up. self.setIsConnected(False) ## Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those. @@ -291,21 +300,29 @@ class PrinterConnection(SignalEmitter): elif self.isConnected(): self._sendCommand(cmd) + ## Set the error state with a message. + # \param error String with the error message. def _setErrorState(self, error): self._error_state = error self.onError.emit(error) onError = Signal() + ## Private function to set the temperature of an extruder + # \param index index of the extruder + # \param temperature recieved temperature def _setExtruderTemperature(self, index, temperature): try: self._extruder_temperatures[index] = temperature - self.onExtruderTemperatureChange.emit(self._serial_port,index,temperature) - except: + self.onExtruderTemperatureChange.emit(self._serial_port, index, temperature) + except Exception as e: pass onExtruderTemperatureChange = Signal() + ## Private function to set the temperature of the bed. + # As all printers (as of time of writing) only support a single heated bed, + # these are not indexed as with extruders. def _setBedTemperature(self, temperature): self._bed_temperature = temperature self.onBedTemperatureChange.emit(self._serial_port,temperature) @@ -315,90 +332,93 @@ class PrinterConnection(SignalEmitter): ## Listen thread function. def _listen(self): + Logger.log('i', "Printer connection listen thread started for %s" % self._serial_port) temperature_request_timeout = time.time() ok_timeout = time.time() while self._is_connected: line = self._readline() + if line is None: - break #None is only returned when something went wrong. Stop listening + break # None is only returned when something went wrong. Stop listening + if line.startswith(b'Error:'): - #Oh YEAH, consistency. + # Oh YEAH, consistency. # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" # So we can have an extra newline in the most common case. Awesome work people. if re.match(b'Error:[0-9]\n', line): line = line.rstrip() + self._readline() - #Skip the communication errors, as those get corrected. + + # Skip the communication errors, as those get corrected. if b'Extruder switched off' in line or b'Temperature heated bed switched off' in line or b'Something is wrong, please turn off the printer.' in line: if not self.hasError(): self._setErrorState(line[6:]) - #self._error_state = line[6:] elif b' T:' in line or line.startswith(b'T:'): #Temperature message try: self._setExtruderTemperature(self._temperature_requested_extruder_index,float(re.search(b"T: *([0-9\.]*)", line).group(1))) - #self._extruder_temperatures[self._temperature_requested_extruder_index] = float(re.search(b"T: *([0-9\.]*)", line).group(1)) except: pass - if b'B:' in line: #Check if it's a bed temperature + if b'B:' in line: # Check if it's a bed temperature try: self._setBedTemperature(float(re.search(b"B: *([0-9\.]*)", line).group(1))) - #print("BED TEMPERATURE" ,float(re.search(b"B: *([0-9\.]*)", line).group(1))) - - except: + except Exception as e: pass #TODO: temperature changed callback - + if self._is_printing: - if time.time() > temperature_request_timeout: #When printing, request temperature every 5 seconds. + if time.time() > temperature_request_timeout: # When printing, request temperature every 5 seconds. if self._extruder_count > 0: self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count self.sendCommand("M105 T%d" % (self._temperature_requested_extruder_index)) else: self.sendCommand("M105") temperature_request_timeout = time.time() + 5 + if line == b'' and time.time() > ok_timeout: - line = b'ok' #Force a timeout (basicly, send next command) + line = b'ok' # Force a timeout (basicly, send next command) + if b'ok' in line: ok_timeout = time.time() + 5 if not self._command_queue.empty(): self._sendCommand(self._command_queue.get()) else: self._sendNextGcodeLine() - elif b"resend" in line.lower() or b"rs" in line: + elif b"resend" in line.lower() or b"rs" in line: # Because a resend can be asked with 'resend' and 'rs' try: self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1]) except: if b"rs" in line: self._gcode_position = int(line.split()[1]) - else: #Request the temperature on comm timeout (every 2 seconds) when we are not printing.) + else: # Request the temperature on comm timeout (every 2 seconds) when we are not printing.) if line == b'': if self._extruder_count > 0: self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index) else: self.sendCommand("M105") + Logger.log('i', "Printer connection listen thread stopped for %s" % self._serial_port) + ## Send next Gcode in the gcode list def _sendNextGcodeLine(self): if self._gcode_position >= len(self._gcode): - #self._changeState(self.STATE_OPERATIONAL) return if self._gcode_position == 100: self._print_start_time_100 = time.time() line = self._gcode[self._gcode_position] + if ';' in line: line = line[:line.find(';')] line = line.strip() try: if line == 'M0' or line == 'M1': - #self.setPause(True) line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. if ('G0' in line or 'G1' in line) and 'Z' in line: z = float(re.search('Z([0-9\.]*)', line).group(1)) if self._current_z != z: self._current_z = z except Exception as e: - Logger.log('e', "Unexpected error: %s" % e) + Logger.log('e', "Unexpected error with printer connection: %s" % e) self._setErrorState("Unexpected error: %s" %e) checksum = functools.reduce(lambda x,y: x^y, map(ord, 'N%d%s' % (self._gcode_position, line))) @@ -409,23 +429,28 @@ class PrinterConnection(SignalEmitter): progressChanged = Signal() - def setProgress(self, progress,max_progress = 100): + ## Set the progress of the print. + # It will be normalized (based on max_progress) to range 0 - 100 + def setProgress(self, progress, max_progress = 100): self._progress = progress / max_progress * 100 #Convert to scale of 0-100 - #self._progress = progress self.progressChanged.emit(self._progress, self._serial_port) + ## Cancel the current print. Printer connection wil continue to listen. def cancelPrint(self): self._gcode_position = 0 self.setProgress(0) self._gcode = [] + # Turn of temperatures self._sendCommand("M140 S0") self._sendCommand("M109 S0") self._is_printing = False - + + ## Check if the process did not encounter an error yet. def hasError(self): - return False - + return self._error_state != None + + ## private read line used by printer connection to listen for data on serial port. def _readline(self): if self._serial is None: return None @@ -434,22 +459,12 @@ class PrinterConnection(SignalEmitter): except Exception as e: Logger.log('e',"Unexpected error while reading serial port. %s" %e) self._setErrorState("Printer has been disconnected") - #self._errorValue = getExceptionString() self.close() return None - #if ret == '': - #return '' - #self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip())) return ret - ## Create a list of baud rates at which we can communicate. # \return list of int def _getBaudrateList(self): ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600] - #if profile.getMachineSetting('serial_baud_auto') != '': - #prev = int(profile.getMachineSetting('serial_baud_auto')) - #if prev in ret: - #ret.remove(prev) - #ret.insert(0, prev) return ret From 13d2d7e42ad1968751bd1d714441c7dae8468249 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Apr 2015 13:43:21 +0200 Subject: [PATCH 32/34] More cleanup --- USBPrinterManager.py | 102 +++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 8f2ba5e90a..e2344078fd 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -24,58 +24,63 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): super().__init__(parent) self._serial_port_list = [] self._printer_connections = [] - self._check_ports_thread = threading.Thread(target=self._updateConnectionList) + self._check_ports_thread = threading.Thread(target = self._updateConnectionList) self._check_ports_thread.daemon = True self._check_ports_thread.start() self._progress = 0 - - + self._control_view = None self._firmware_view = None self._extruder_temp = 0 self._bed_temp = 0 self._error_message = "" - ## Add menu item to top menu. + ## Add menu item to top menu of the application. self.addMenuItem(i18n_catalog.i18n("Update firmware"), self.updateAllFirmware) + ## Show firmware interface. + # This will create the view if its not already created. def spawnFirmwareInterface(self, serial_port): if self._firmware_view is None: self._firmware_view = QQuickView() self._firmware_view.engine().rootContext().setContextProperty('manager',self) self._firmware_view.setSource(QUrl("plugins/USBPrinting/FirmwareUpdateWindow.qml")) - self._firmware_view.show() - + self._firmware_view.show() + ## Show control interface. + # This will create the view if its not already created. def spawnControlInterface(self,serial_port): if self._control_view is None: self._control_view = QQuickView() self._control_view.engine().rootContext().setContextProperty('manager',self) self._control_view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) - self._control_view.show() - - - processingProgress = pyqtSignal(float, arguments = ['amount']) + self._control_view.show() + @pyqtProperty(float,notify = processingProgress) def progress(self): return self._progress - pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) + processingProgress = pyqtSignal(float, arguments = ['amount']) + @pyqtProperty(float,notify = pyqtExtruderTemperature) def extruderTemperature(self): return self._extruder_temp - pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) + pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) + @pyqtProperty(float,notify = pyqtBedTemperature) def bedTemperature(self): return self._bed_temp - pyqtError = pyqtSignal(str, arguments = ['amount']) + pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) + @pyqtProperty(str,notify = pyqtError) def error(self): return self._error_message + pyqtError = pyqtSignal(str, arguments = ['amount']) + ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! # This (the validation) is only done when the connect function is called. @@ -86,8 +91,8 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): disconnected_ports = [port for port in self._serial_port_list if port not in temp_serial_port_list ] self._serial_port_list = temp_serial_port_list for serial_port in self._serial_port_list: - if self.getConnectionByPort(serial_port) is None: #If it doesn't already exist, add it - if not os.path.islink(serial_port): #Only add the connection if it's a non symbolic link + if self.getConnectionByPort(serial_port) is None: # If it doesn't already exist, add it + if not os.path.islink(serial_port): # Only add the connection if it's a non symbolic link connection = PrinterConnection.PrinterConnection(serial_port) connection.connect() connection.connectionStateChanged.connect(self.serialConectionStateCallback) @@ -102,14 +107,7 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): if connection != None: self._printer_connections.remove(connection) connection.close() - time.sleep(5) #Throttle, as we don't need this information to be updated every single second. - - def onExtruderTemperature(self, serial_port, index,temperature): - #print("ExtruderTemperature " , serial_port, " " , index, " " , temperature) - self._extruder_temp = temperature - self.pyqtExtruderTemperature.emit(temperature) - - pass + time.sleep(5) # Throttle, as we don't need this information to be updated every single second. def updateAllFirmware(self): self.spawnFirmwareInterface("") @@ -142,35 +140,38 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): elif machine_type == "ultimaker2": return "MarlinUltimaker2.hex" - ##TODO: Add check for multiple extruders if firmware_name != "": firmware_name += ".hex" return firmware_name + ## Callback for extruder temperature change + def onExtruderTemperature(self, serial_port, index, temperature): + self._extruder_temp = temperature + self.pyqtExtruderTemperature.emit(temperature) + + ## Callback for bed temperature change def onBedTemperature(self, serial_port,temperature): self._bed_temperature = temperature self.pyqtBedTemperature.emit(temperature) - #print("bedTemperature " , serial_port, " " , temperature) - pass + ## Callback for error def onError(self, error): self._error_message = error self.pyqtError.emit(error) - pass - + + ## Callback for progress change def onProgress(self, progress, serial_port): self._progress = progress self.processingProgress.emit(progress) - pass - + ## Attempt to connect with all possible connections. def connectAllConnections(self): for connection in self._printer_connections: connection.connect() - ## send gcode to printer and start printing + ## Send gcode to printer and start printing def sendGCodeByPort(self, serial_port, gcode_list): printer_connection = self.getConnectionByPort(serial_port) if printer_connection is not None: @@ -214,9 +215,10 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): return True else: return False - - - def serialConectionStateCallback(self,serial_port): + + ## Callback if the connection state of a connection is changed. + # This adds or removes the connection as a possible output device. + def serialConectionStateCallback(self, serial_port): connection = self.getConnectionByPort(serial_port) if connection.isConnected(): Application.getInstance().addOutputDevice(serial_port, { @@ -229,13 +231,6 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): else: Application.getInstance().removeOutputDevice(serial_port) - '''def _writeToSerial(self, serial_port): - gcode_list = getattr(Application.getInstance().getController().getScene(), 'gcode_list', None) - if gcode_list: - final_list = [] - for gcode in gcode_list: - final_list += gcode.split('\n') - self.sendGCodeByPort(serial_port, gcode_list)''' @pyqtSlot() def startPrint(self): gcode_list = getattr(Application.getInstance().getController().getScene(), 'gcode_list', None) @@ -245,12 +240,11 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): final_list += gcode.split('\n') self.sendGCodeToAllActive(gcode_list) - - ## Get a list of printer connection objects that are connected. + ## Get a list of printer connection objects that are connected. def getActiveConnections(self): return [connection for connection in self._printer_connections if connection.isConnected()] - ## get a printer connection object by serial port + ## Get a printer connection object by serial port def getConnectionByPort(self, serial_port): for printer_connection in self._printer_connections: if serial_port == printer_connection.getSerialPort(): @@ -260,29 +254,23 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): ## Create a list of serial ports on the system. # \param only_list_usb If true, only usb ports are listed def getSerialPortList(self,only_list_usb=False): - base_list=[] + base_list = [] if platform.system() == "Windows": import winreg try: - key=winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") - i=0 + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") + i = 0 while True: values = winreg.EnumValue(key, i) if not base_list or 'USBSER' in values[0]: - base_list+=[values[1]] - i+=1 - except: + base_list + =[values[1]] + i += 1 + except Exception as e: pass if base_list: base_list = base_list + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.usb*") - base_list = filter(lambda s: not 'Bluetooth' in s, base_list) #Filter because mac sometimes puts them in the list - #prev = profile.getMachineSetting('serial_port_auto') - #if prev in base_list: - # base_list.remove(prev) - # base_list.insert(0, prev) + base_list = filter(lambda s: not 'Bluetooth' in s, base_list) # Filter because mac sometimes puts them in the list else: base_list = base_list + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*") + glob.glob('/dev/serial/by-id/*') - #if version.isDevVersion() and not base_list: - #base_list.append('VIRTUAL') return base_list \ No newline at end of file From 25cfce2803a6c19ab3d4976b588701acb049410b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Apr 2015 15:31:02 +0200 Subject: [PATCH 33/34] Fixed caused by cleanup --- USBPrinterManager.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/USBPrinterManager.py b/USBPrinterManager.py index e2344078fd..accf30eae5 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -13,7 +13,7 @@ import sys from UM.Extension import Extension from PyQt5.QtQuick import QQuickView -from PyQt5.QtCore import QUrl, QObject,pyqtSlot , pyqtProperty,pyqtSignal +from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal from UM.i18n import i18nCatalog @@ -39,6 +39,11 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): ## Add menu item to top menu of the application. self.addMenuItem(i18n_catalog.i18n("Update firmware"), self.updateAllFirmware) + pyqtError = pyqtSignal(str, arguments = ['amount']) + processingProgress = pyqtSignal(float, arguments = ['amount']) + pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) + pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) + ## Show firmware interface. # This will create the view if its not already created. def spawnFirmwareInterface(self, serial_port): @@ -60,27 +65,19 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): @pyqtProperty(float,notify = processingProgress) def progress(self): return self._progress - - processingProgress = pyqtSignal(float, arguments = ['amount']) - + @pyqtProperty(float,notify = pyqtExtruderTemperature) def extruderTemperature(self): return self._extruder_temp - - pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) - + @pyqtProperty(float,notify = pyqtBedTemperature) def bedTemperature(self): return self._bed_temp - - pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) - + @pyqtProperty(str,notify = pyqtError) def error(self): return self._error_message - pyqtError = pyqtSignal(str, arguments = ['amount']) - ## Check all serial ports and create a PrinterConnection object for them. # Note that this does not validate if the serial ports are actually usable! # This (the validation) is only done when the connect function is called. @@ -263,7 +260,7 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): while True: values = winreg.EnumValue(key, i) if not base_list or 'USBSER' in values[0]: - base_list + =[values[1]] + base_list += [values[1]] i += 1 except Exception as e: pass From 83c367cdf4b04b62718e0b7124f25500dad057ea Mon Sep 17 00:00:00 2001 From: daid Date: Fri, 24 Apr 2015 17:07:32 +0200 Subject: [PATCH 34/34] Some style fixing, and added a test script to test for the major style violations. --- PrinterConnection.py | 4 ++-- USBPrinterManager.py | 20 ++++++++++---------- avr_isp/ispBase.py | 2 ++ avr_isp/stk500v2.py | 10 +++++++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/PrinterConnection.py b/PrinterConnection.py index 28630dc0c5..a2021ca67d 100644 --- a/PrinterConnection.py +++ b/PrinterConnection.py @@ -149,8 +149,7 @@ class PrinterConnection(SignalEmitter): def updateFirmware(self, file_name): self._firmware_file_name = file_name self._update_firmware_thread.start() - - + ## Private connect function run by thread. Can be started by calling connect. def _connect(self): self._is_connecting = True @@ -378,6 +377,7 @@ class PrinterConnection(SignalEmitter): self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index) else: self.sendCommand("M105") + ## Send next Gcode in the gcode list def _sendNextGcodeLine(self): if self._gcode_position >= len(self._gcode): diff --git a/USBPrinterManager.py b/USBPrinterManager.py index 8f2ba5e90a..300f6e7ef6 100644 --- a/USBPrinterManager.py +++ b/USBPrinterManager.py @@ -19,6 +19,7 @@ from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog('plugins') + class USBPrinterManager(QObject, SignalEmitter, Extension): def __init__(self, parent = None): super().__init__(parent) @@ -48,26 +49,25 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): self._firmware_view.show() - def spawnControlInterface(self,serial_port): + def spawnControlInterface(self, serial_port): if self._control_view is None: self._control_view = QQuickView() self._control_view.engine().rootContext().setContextProperty('manager',self) self._control_view.setSource(QUrl("plugins/USBPrinting/ControlWindow.qml")) self._control_view.show() - - + processingProgress = pyqtSignal(float, arguments = ['amount']) - @pyqtProperty(float,notify = processingProgress) + @pyqtProperty(float, notify=processingProgress) def progress(self): return self._progress - + pyqtExtruderTemperature = pyqtSignal(float, arguments = ['amount']) - @pyqtProperty(float,notify = pyqtExtruderTemperature) + @pyqtProperty(float, notify=pyqtExtruderTemperature) def extruderTemperature(self): return self._extruder_temp pyqtBedTemperature = pyqtSignal(float, arguments = ['amount']) - @pyqtProperty(float,notify = pyqtBedTemperature) + @pyqtProperty(float, notify=pyqtBedTemperature) def bedTemperature(self): return self._bed_temp @@ -268,15 +268,15 @@ class USBPrinterManager(QObject, SignalEmitter, Extension): i=0 while True: values = winreg.EnumValue(key, i) - if not base_list or 'USBSER' in values[0]: - base_list+=[values[1]] + if 'USBSER' in values[0]: + base_list += [values[1]] i+=1 except: pass if base_list: base_list = base_list + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.usb*") - base_list = filter(lambda s: not 'Bluetooth' in s, base_list) #Filter because mac sometimes puts them in the list + base_list = filter(lambda s: 'Bluetooth' not in s, base_list) #Filter because mac sometimes puts them in the list #prev = profile.getMachineSetting('serial_port_auto') #if prev in base_list: # base_list.remove(prev) diff --git a/avr_isp/ispBase.py b/avr_isp/ispBase.py index bfed45f0ac..1ba1317f91 100644 --- a/avr_isp/ispBase.py +++ b/avr_isp/ispBase.py @@ -57,8 +57,10 @@ class IspBase(): """ raise IspError("Called undefined verifyFlash") + class IspError(BaseException): def __init__(self, value): self.value = value + def __str__(self): return repr(self.value) diff --git a/avr_isp/stk500v2.py b/avr_isp/stk500v2.py index 1af5416a40..c0e3b0ec89 100644 --- a/avr_isp/stk500v2.py +++ b/avr_isp/stk500v2.py @@ -3,7 +3,10 @@ STK500v2 protocol implementation for programming AVR chips. The STK500v2 protocol is used by the ArduinoMega2560 and a few other Arduino platforms to load firmware. This is a python 3 conversion of the code created by David Braam for the Cura project. """ -import os, struct, sys, time +import os +import struct +import sys +import time from serial import Serial from serial import SerialException @@ -11,6 +14,7 @@ from serial import SerialTimeoutException from . import ispBase, intelHex + class Stk500v2(ispBase.IspBase): def __init__(self): self.serial = None @@ -55,8 +59,8 @@ class Stk500v2(ispBase.IspBase): self.serial.close() self.serial = None - #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface. - # This allows you to use the serial port without opening it again. + #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface. + # This allows you to use the serial port without opening it again. def leaveISP(self): if self.serial is not None: if self.sendMessage([0x11]) != [0x11, 0x00]: