From 763206af3247999015f321301e9074e83c21b136 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 10 Feb 2016 13:29:17 +0100 Subject: [PATCH] Initial commit --- HttpUploadDataStream.py | 32 +++++++++ WifiConnection.py | 142 ++++++++++++++++++++++++++++++++++++++ WifiOutputDevicePlugin.py | 53 ++++++++++++++ __init__.py | 20 ++++++ 4 files changed, 247 insertions(+) create mode 100644 HttpUploadDataStream.py create mode 100644 WifiConnection.py create mode 100644 WifiOutputDevicePlugin.py create mode 100644 __init__.py diff --git a/HttpUploadDataStream.py b/HttpUploadDataStream.py new file mode 100644 index 0000000000..a3e5a82ba4 --- /dev/null +++ b/HttpUploadDataStream.py @@ -0,0 +1,32 @@ +from UM.Signal import Signal, SignalEmitter +class HttpUploadDataStream(SignalEmitter): + def __init__(self): + super().__init__() + self._data_list = [] + self._total_length = 0 + self._read_position = 0 + + progressSignal = Signal() + + def write(self, data): + data = bytes(data,'UTF-8') + size = len(data) + if size < 1: + return + blocks = int(size / 2048) + for n in range(0, blocks): + self._data_list.append(data[n*2048:n*2048+2048]) + self._data_list.append(data[blocks*2048:]) + self._total_length += size + + def read(self, size): + if self._read_position >= len(self._data_list): + return None + ret = self._data_list[self._read_position] + self._read_position += 1 + + self.progressSignal.emit(float(self._read_position) / float(len(self._data_list))) + return ret + + def __len__(self): + return self._total_length \ No newline at end of file diff --git a/WifiConnection.py b/WifiConnection.py new file mode 100644 index 0000000000..5d1a4a6c9c --- /dev/null +++ b/WifiConnection.py @@ -0,0 +1,142 @@ +from UM.OutputDevice.OutputDevice import OutputDevice +from UM.OutputDevice import OutputDeviceError +import threading +import http.client as httpclient +import urllib +import json +import time +import base64 + +from . import HttpUploadDataStream +from UM.i18n import i18nCatalog +from UM.Signal import Signal, SignalEmitter +from UM.Application import Application +from UM.Logger import Logger +i18n_catalog = i18nCatalog("cura") + +class WifiConnection(OutputDevice, SignalEmitter): + def __init__(self, address, info): + super().__init__(address) + self._address = address + self._info = info + self._http_lock = threading.Lock() + self._http_connection = None + self._file = None + self._do_update = True + self._thread = None + self._state = None + self._is_connected = False + self.connect() + self.setName(address) + self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) + self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) + self.setIconName("print") + + connectionStateChanged = Signal() + + def isConnected(self): + return self._is_connected + + def setConnectionState(self, state): + print("setting connection state: " , self._address, " " , state) + if state != self._is_connected: + self._is_connected = state + self.connectionStateChanged.emit(self._address) + else: + self._is_connected = state + + def _update(self): + while self._thread: + state_reply = self._httpRequest('GET', '/api/v1/printer/state') + if state_reply is not None: + self._state = state_reply + if not self._is_connected: + self.setConnectionState(True) + else: + self._state = {'state': 'CONNECTION_ERROR'} + self.setConnectionState(False) + time.sleep(1) + + def close(self): + self._do_update = False + self._is_connected = False + + def requestWrite(self, node): + self._file = getattr( Application.getInstance().getController().getScene(), "gcode_list") + self.startPrint() + + #Open the active connection to the printer so we can send commands + def connect(self): + if self._thread is None: + self._do_update = True + self._thread = threading.Thread(target = self._update) + self._thread.daemon = True + self._thread.start() + + def getCameraImage(self): + pass #Do Nothing + + def startPrint(self): + try: + result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura', 'parameters':''}, {'file': ('file.gcode', self._file)}) + print(result.get('success',False)) + #result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura'}) + except Exception as e: + Logger.log('e' , 'An exception occured in wifi connection: ' , e) + + def _httpRequest(self, method, path, post_data = None, files = None): + with self._http_lock: + self._http_connection = httpclient.HTTPConnection(self._address, timeout = 30) + try: + if files is not None: + boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T' + s = HttpUploadDataStream.HttpUploadDataStream() + for k, v in files.items(): + filename = v[0] + file_contents = v[1] + s.write('--%s\r\n' % (boundary)) + s.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (k, filename)) + s.write('Content-Type: application/octet-stream\r\n') + s.write('Content-Transfer-Encoding: binary\r\n') + s.write('\r\n') + + if file_contents is not None: + for line in file_contents: + s.write(str(line)) + + s.write('\r\n') + + for k, v in post_data.items(): + s.write('--%s\r\n' % (boundary)) + s.write('Content-Disposition: form-data; name="%s"\r\n' % (k)) + s.write('\r\n') + s.write(str(v)) + s.write('\r\n') + s.write('--%s--\r\n' % (boundary)) + + self._http_connection.request(method, path, s, {"Content-type": "multipart/form-data; boundary=%s" % (boundary), "Content-Length": len(s)}) + elif post_data is not None: + + self._http_connection.request(method, path, urllib.urlencode(post_data), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) + else: + self._http_connection.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) + except IOError: + self._http_connection.close() + return None + except Exception as e: + self._http_connection.close() + return None + + try: + response = self._http_connection.getresponse() + response_text = response.read() + except IOError: + self._http_connection.close() + return None + try: + response = json.loads(response_text.decode("utf-8")) + except ValueError: + self._http_connection.close() + return None + self._http_connection.close() + return response diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py new file mode 100644 index 0000000000..e01c078cde --- /dev/null +++ b/WifiOutputDevicePlugin.py @@ -0,0 +1,53 @@ +from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin +from . import WifiConnection + +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange +from UM.Signal import Signal, SignalEmitter + +class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): + def __init__(self): + super().__init__() + self._zero_conf = Zeroconf() + self._browser = None + self._connections = {} + self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + + addConnectionSignal = Signal() + + def start(self): + self._browser = ServiceBrowser(Zeroconf(), u'_ultimaker._tcp.local.', [self._onServiceChanged]) + + def stop(self): + self._zero_conf.close() + + ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + def addConnection(self, name, address, properties): + connection = WifiConnection.WifiConnection(address, properties) + connection.connect() + self._connections[address] = connection + if address == "10.180.1.23": #DEBUG + #if address == "10.180.0.249": #DEBUG + connection.startPrint() + connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + + def _onPrinterConnectionStateChanged(self, address): + print(self._connections[address].isConnected()) + if self._connections[address].isConnected(): + self.getOutputDeviceManager().addOutputDevice(self._connections[address]) + else: + self.getOutputDeviceManager().removeOutputDevice(self._connections[address]) + + def removeConnection(self): + pass + + def _onServiceChanged(self, zeroconf, service_type, name, state_change): + if state_change == ServiceStateChange.Added: + info = zeroconf.get_service_info(service_type, name) + if info: + if info.properties.get(b"type", None): + address = '.'.join(map(lambda n: str(n), info.address)) + self.addConnectionSignal.emit(str(name), address, info.properties) + + elif state_change == ServiceStateChange.Removed: + print("Device disconnected") + #print("HERP DERP") diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000..f06ca20d57 --- /dev/null +++ b/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. +from . import WifiOutputDevicePlugin + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "plugin": { + "name": "Wifi connection", + "author": "Ultimaker", + "description": catalog.i18nc("Wifi connection", "Wifi connection"), + "api": 2 + } + } + +def register(app): + return { "output_device": WifiOutputDevicePlugin.WifiOutputDevicePlugin() } +