async def load(self) -> SocketMessageResponse: log().info("loading filament...") for cmd in ['M109 S210', 'G92 E0', 'G1 E100 F150', 'M109 S0']: log().info("executing command from load command chain " + cmd + "...") await self.octoapi.post_command(cmd) return SocketMessageResponse(0, "ok")
async def loop(self): last_connection_try = 0 while True: await self.updateActualState() status = self.actualState["status"]["state"][ "text"] if "status" in self.actualState and "state" in self.actualState[ "status"] and "text" in self.actualState["status"][ "state"] else "unknown" error = self.actualState[ "error"] if "error" in self.actualState else 0 if time.time() - last_connection_try > 20 and (status == 'Closed' or error == 409): log().warning("closed status detected, forcing connection...") last_connection_try = time.time() try: await self.octoapi.connect() log().info(f"printer connected correctly") except HttpException: log().warning( f"could not connect printer, trying again in {20}s") except ClientConnectorError as e: log().error( f"client connection error with octoprint server while trying to connect printer: " + str(e)) except Exception as e: log().error( f"unknown error while trying to connect printer: " + str(e)) if self.connected and self.transmitting: await self.syncWithUlab() await asyncio.sleep(1)
async def syncWithUlab(self): spec = self.diffengine.diff(self.actualState, self.sentState) if len(spec): try: await self.ulabapi.update_status(spec) self.sentState = copy.deepcopy(self.actualState) except Exception as e: log().error(e)
async def instruction(data: str) -> SocketMessageResponse: log().info("incoming instruction: " + data) response = await self.listener(data) if response.status: log().warning( f"response for instruction with status {response.status}: {response.message}" ) return response
async def cancel(self) -> SocketMessageResponse: log().info("cancelling print...") if not self.actualState["status"]["state"]['flags']['printing']: return SocketMessageResponse( 1, "pandora is not in an printing state") await self.octoapi.cancel() await self.octoapi.post_command("G1 Z140") return SocketMessageResponse(0, "ok")
async def _print_file(self, gcode: str) -> None: log().info("printing file " + gcode + '...') if "init_gcode" in self.actualState['settings']: for pre_cmd in self.actualState['settings']["init_gcode"].split( ";"): if len(pre_cmd) < 2: continue log().info("executing init gcode " + pre_cmd) await self.octoapi.post_command(pre_cmd) await self.octoapi.print(gcode.split('/')[-1])
async def download_and_print(): self.actualState["download"]["file"] = data['file'] self.actualState["download"]["completion"] = 0.0 r = await self.ulabapi.download(data['file']) if not r.status == 200: log().warning("error downloading file " + data['file'] + " from url: " + str(r.status)) self.actualState["download"]["file"] = None self.actualState["download"]["completion"] = -1 await self._download_file(r, gcode) await self._print_file(gcode)
async def wifi(self, data: Dict[str, str]) -> SocketMessageResponse: log().info("changing wifi...") for k in ['ssid', 'psk']: if k not in data: return SocketMessageResponse(1, k + " parameter not specified") log().info("new wifi data: ssid=" + data['ssid'] + " psk=" + data['psk']) wifi = 'network={\n ssid="' + data['ssid'] + '"\n psk="' + data[ 'psk'] + '"\n}\n' wpa_supplicant_txt = open("/boot/octopi-wpa-supplicant.txt").read() open("/boot/octopi-wpa-supplicant.txt", "w").write(wifi + wpa_supplicant_txt) return SocketMessageResponse(0, "ok")
async def move(self, data: Dict[str, str]) -> SocketMessageResponse: log().info("moving...") for k in ['axis', 'distance']: if k not in data: return SocketMessageResponse(1, k + " not specified") for cmd in [ 'G91', 'G1 {}{} F1000'.format(data['axis'], data['distance']), 'G90' ]: log().info("executing command from move command chain " + cmd + "...") await self.octoapi.post_command(cmd) return SocketMessageResponse(0, "ok")
async def main(loop: asyncio.AbstractEventLoop): if not os.path.isfile(args.ulab_token_path): log().error( "token file " + args.ulab_token_path + " does not exists, create it with the ulab token inside and restart the system" ) while True: time.sleep(10) ulab_token = open(args.ulab_token_path).read().replace('\n', "").replace( " ", "") octoapi = OctoApi(args.octoprint_url, args.octoprint_config_path) ulabapi = UlabApi(args.ulab_socket_url, args.ulab_backend_url, ulab_token) printer = Printer(octoapi, ulabapi) await printer.ulabapi.connect(loop) await printer.loop()
async def settings(self, data: Dict[str, str]) -> SocketMessageResponse: log().info("changing settings...") keys = [x for x in data if x not in ['instruction']] if not len(keys): return SocketMessageResponse(1, "no new settings has been sent") for k in keys: if k not in self.actualState['settings']: return SocketMessageResponse(1, "setting " + k + " not supported") for k in keys: self.actualState['settings'][k] = data[k] json.dump(self.actualState['settings'], open("../store.json", "w")) await self.syncWithUlab() return SocketMessageResponse(0, "ok")
async def _download_file(self, r: aiohttp.ClientResponse, gcode: str) -> None: f = await aiofiles.open(gcode, mode='wb') readed = 0 while True: if r.content_length: self.actualState["download"][ "completion"] = readed / r.content_length chunk = await r.content.read(1024) if not chunk: break await f.write(chunk) readed += 1024 log().info("file " + gcode + ' downloaded successfully, printing it...') self.actualState["download"]["file"] = None self.actualState["download"]["completion"] = -1 await self.syncWithUlab()
async def print(self, data: Dict[str, str]) -> SocketMessageResponse: log().info("printing...") if 'file' not in data: return SocketMessageResponse(1, "file not specified") if self.actualState['download']['file'] is not None: return SocketMessageResponse( 1, "file " + self.actualState['download']['file'] + " has already been sheduled to download and print") if not self.actualState["status"]["state"]['text'] == 'Operational': return SocketMessageResponse( 1, "pandora is not in an operational state") upload_path = get_args().octoprint_upload_path if not os.path.isdir(upload_path): os.mkdir(upload_path) gcode = upload_path + '/' + (data['file'] if data['file'].endswith( '.gcode') else data['file'] + '.gcode') if not os.path.isfile(gcode): log().info("file " + gcode + " not found, downloading it...") async def download_and_print(): self.actualState["download"]["file"] = data['file'] self.actualState["download"]["completion"] = 0.0 r = await self.ulabapi.download(data['file']) if not r.status == 200: log().warning("error downloading file " + data['file'] + " from url: " + str(r.status)) self.actualState["download"]["file"] = None self.actualState["download"]["completion"] = -1 await self._download_file(r, gcode) await self._print_file(gcode) asyncio.get_running_loop().create_task(download_and_print( )) # todo: get running loop from somewhere cleaner return SocketMessageResponse( 0, "file was not on ucloud, downloading it and printing it...") await self._print_file(gcode) return SocketMessageResponse(0, "ok")
async def updateActualState(self): try: self.actualState["status"] = await self.octoapi.get_status() self.actualState["job"] = await self.octoapi.get_job() self.actualState["error"] = None except HttpException as e: self.actualState["status"] = {"state": {"text": "Disconnected"}} self.actualState["error"] = e.code except ClientConnectorError as e: log().error("error connecting to octoprint server: " + str(e)) self.actualState["status"] = {"state": {"text": "Disconnected"}} self.actualState["error"] = 450 except Exception as e: log().error("unknown error updating status: " + str(e)) self.actualState["status"] = {"state": {"text": "Disconnected"}} self.actualState["error"] = 500
async def command(self, data: Dict[str, str]) -> SocketMessageResponse: log().info("executing command...") if 'command' not in data: log().warning("command not specified") return SocketMessageResponse(1, "command not specified") for cmd in data['command'].split(";"): log().info(cmd) await self.octoapi.post_command(cmd) return SocketMessageResponse(0, "ok")
async def _updateActualState(self): try: self.actualState["status"] = await self.octoapi.get_status() self.actualState["job"] = await self.octoapi.get_job() if self.actualState["status"]["state"]['text'] == 'Closed': log().warning("closed status detected, forcing connection...") await self.octoapi.connect() except HttpException as e: self.actualState["status"] = {"state": {"text": "Disconnected"}} if e.code == 409: log().warning( "octoprint returned 409 while requesting status, forcing connection..." ) await self.octoapi.connect() except ClientConnectorError as e: log().error("error conectando con el servidor de octoprint: " + str(e)) self.actualState["status"] = {"state": {"text": "Disconnected"}}
async def start(self) -> SocketMessageResponse: log().info("starting transmission") self.transmitting = True return SocketMessageResponse(0, "ok")
async def init(data: str = ""): log().info("data initialization requested, starting transmission") await self.init() await self.syncWithUlab()
async def stop(data: str = ""): log().info("socket requested to stop transmission") self.transmitting = False
async def error(e: Exception): self.connected = False self.transmitting = False log().warning("error on Socket: " + str(e))
async def disconnect(data: str = ""): self.connected = False self.transmitting = False log().warning("socket disconnected, oh no! :(")
async def connect(data: str = ""): log().info("socket connected, yujuu! :)") self.connected = True
async def listener(self, data_raw: str) -> SocketMessageResponse: try: data: Dict[str, Union[str, int, float]] = json.loads(data_raw) except json.JSONDecodeError: log().error("error decoding instruction " + data_raw) return SocketMessageResponse(1, "cannot decode instruction") if 'instruction' not in data: log().warning( "received instruction without specifying an instruction name") return SocketMessageResponse(1, "instruction not specified") instruction = data['instruction'] log().info("instruction " + instruction + " detected") if instruction in [ "home", "start", "stop", "print", "command", "load", "unload", "move" ]: error_flag = False try: if self.actualState["status"]["state"]['text'] != 'Operational': error_flag = True except KeyError: error_flag = True except TypeError: error_flag = True if error_flag: log().warning( "instruction not allowed if pandora is not on an operational state" ) return SocketMessageResponse( 1, "printer is not on an operational state") try: if instruction == "start": return await self.start() elif instruction == "stop": return await self.stop() if instruction == 'home': return await self.home() elif instruction == 'print': return await self.print(data) elif instruction == 'cancel': return await self.cancel() elif instruction == 'settings': return await self.settings(data) elif instruction == 'move': return await self.move(data) elif instruction == 'command': return await self.command(data) elif instruction == 'load': return await self.load() elif instruction == 'unload': return await self.unload() elif instruction == 'wifi': return await self.wifi(data) else: return SocketMessageResponse( 1, data['instruction'] + " instruction not supported") except HttpException as e: log().warning("octoapi responded " + str(e.code) + ", to " + json.dumps(data)) return SocketMessageResponse(1, "printer responded " + str(e.code)) except Exception as e: log().error(str(e)) return SocketMessageResponse(1, str(e))
async def stop(self) -> SocketMessageResponse: log().info("stopping transmission") self.transmitting = False return SocketMessageResponse(0, "ok")
async def home(self) -> SocketMessageResponse: log().info("homing...") await self.octoapi.post_command("G28") return SocketMessageResponse(0, "ok")