Loading README.md +36 −9 Original line number Diff line number Diff line Loading @@ -4,29 +4,56 @@ Fablab 3d printing request application ## Minimum Viable Version - [ ] Tests - [ ] *Tests* - [x] User Connection - [ ] User administration - [ ] Backend - [ ] List users, - [ ] Change access level - [ ] Submit a print request - [x] Submit a print request - [x] Upload a STL file - [x] Backend - [x] Front - [x] Create a new Print Request - [x] Backend - [x] Front - [ ] Accept/Reject print request (front) - [ ] Edit the print request - [ ] Manualy set the state - [ ] Delete - [ ] Asign the request - [x] Accept/Reject print request (front) - [x] See STL file in 3d - [x] See print status - [ ] Change Print status (see enum in api) - [ ] Slice - [ ] Upload GCODE - [x] Change Print status (see enum in api) - [x] Slice - [x] Upload GCODE - [x] Download GCODE - [ ] **V2**: Open slicer window - [ ] Message system - [ ] Post message on each request - [ ] New message indication - [ ] **V2**: Send to printer - [ ] Queue management - [ ] Working queues - [ ] Printer management - [ ] Post to octoprint - [ ] Indicate successfull / error in printing - [ ] **v2** Printer management - [ ] **v2** Post to octoprint # Target Architecture ``` +-------------------------------------------------------------------------------------------------------------------------+ | |---------------------------------+ | | +------------------------------------------------+ +----------------+ +---------------+ | | | | | +--->+ DFPM | | | Printer | | +--------+ | | +--------------+ +-------------------+ | | | Printer manager<--------> Octoprint <----> | | |Client | | | | | | <-------+ +----------------+ | | | | | <-----------> Front <------> Request Manager | | +---------------+-----------------+ | | | | | | | | | | +----------------------------------------+ | +--------+ | | +--------------+ +-----------^-------+ | | | | | | | | +-------------+ | | | | | | | | Slicer VM | | | | +------------------> Slicer | | | | +------------------------------------------------+ | manager | | | | +----------------------------------------+ | +-------------------------------------------------------------------------------------------------------------------------+ ``` No newline at end of file back/myfab/__pycache__/api.cpython-38.pyc +2.17 KiB (9.08 KiB) File changed.No diff preview for this file type. View original file View changed file back/myfab/api.py +133 −8 Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ from flask_jwt_extended import ( ) import datetime from myfab.model import Event, PrintRequest, User, init_db_connection, Queue from myfab.model import Event, PrintRequest, User, init_db_connection, Queue, QueueElement from flask_api import status from flask import jsonify import string Loading Loading @@ -50,6 +50,10 @@ REQUEST_ACCESS_OBSERVER = 1 # Allowed to see this request REQUEST_ACCESS_USER = 2 # Is the author of this request REQUEST_ACCESS_OPERATOR = 3 # Is an operator of the system USER_ACCESS_NORMAL = 0 USER_ACCESS_OBSERVER = 1 USER_ACCESS_OPERATOR = 2 # Elements that can be modified by a user (not op) REQUEST_USER_ELEMENTS = set(["title","description","project"]) Loading Loading @@ -108,6 +112,8 @@ def loop_requests(reqs): # if type(reqs) != list: # reqs = [reqs] for req in reqs: if get_access_level_to_request(req, current_user) == REQUEST_ACCESS_DENIED: continue rqs.append({ "request_id": req.id, "title": req.title, Loading @@ -129,7 +135,22 @@ def loop_requests(reqs): def get_access_level_to_request(req: PrintRequest, user): #req is id or req same for user # TODO return REQUEST_ACCESS_OPERATOR # FIXME if req is not PrintRequest: req = get_request_by_id(req) if req is None: raise Exception("No such request") if user is not User: user = get_or_create_user(user) if user.access == USER_ACCESS_OPERATOR: return REQUEST_ACCESS_OPERATOR # user is admin if user.access == USER_ACCESS_OBSERVER: return REQUEST_ACCESS_OBSERVER # user has unlimited view if user.access == USER_ACCESS_NORMAL: if req.author == user.username: return REQUEST_ACCESS_USER # user is creator of the request else: return REQUEST_ACCESS_DENIED # user does not have access def update_requires_operator_rights(update_json): s = set(update_json) Loading Loading @@ -168,9 +189,8 @@ def update_request(req_id): required_access = REQUEST_ACCESS_OPERATOR if req.status > 0 or update_requires_operator_rights(changes) else REQUEST_ACCESS_USER if get_access_level_to_request(req, current_user) < REQUEST_ACCESS_OPERATOR: if get_access_level_to_request(req, current_user) < required_access: return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED # TODO access for e in changes: setattr(req, e, changes[e]) Loading Loading @@ -252,17 +272,119 @@ def get_requests(): #! FIXME Don't get request if not access #region user management routes # user list @app.route("/users") @jwt_required def list_users(): if current_user.access != USER_ACCESS_OPERATOR: return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED users = [] for user in User.select(): users.append({ 'username': user.username, 'fullname': user.fullname, 'access': user.access }) return jsonify(users) #change user TODO @app.route("/users/<username>", methods=['PUT']) @jwt_required def update_user(username): if current_user.access != USER_ACCESS_OPERATOR: return jsonify({'error': 'Access Denied'}), status.HTTP_401_UNAUTHORIZED user = get_or_create_user(username) #FIXME no such user is 500 changes = request.get_json() for e in changes: setattr(user, e, changes[e]) user.save() return jsonify({}) #endregion user management #region Printer manager routes #region printer management routes TODO # list printers & status @app.route("/printers") @jwt_required def list_printers(): """ [ { "id": "generated_id", "name": "given name by operators", "current_print_request": 0, "version": "octoprint & dfpm", "printer_type": "mini_prusa_mk3", "status": "printing, idle, error, out of commission, virtual printer...", "print_progress": 0.0, "infos": {temperatures etc...} "remaining_print_time" } ] """ pass # action on printer TODO v2 @app.route("/printers/<printer_id>/action", methods=["POST"]) @jwt_required def action_printer(printer_id): request.get_json() """ { "action": "pause", "resume", "cancel", "cycle", "disable", "enable", "restart" } """ pass # Edit printer config @app.route("/printers/<printer_id>", methods=["PUT"]) @jwt_required def update_printer(printer_id): pass # Add printer config @app.route("/printers/new", methods=["POST"]) @jwt_required def add_new_printer(): pass #endregion printer management #region queues management #region queues management TODO #list queues @app.route("/queues") @jwt_required def list_queues(): """ [ { "id": "id", "name": "queue_name", "weight": 0, queue weight, "printers": [ids ],* "enabled": true, "process_span": when to process the queue (type unknown) ?? FIXME } ] """ pass # create queue # delete queue # update queue #endregion queues #region Slicer manager routes #region slicer manager routes TODO """ GET /slicer/update Loading Loading @@ -292,6 +414,9 @@ POST /slicer/usage #endregion routes # peewee usage # user = User.select().where(User.username == "test").count() Loading back/myfab/model.py +17 −10 Original line number Diff line number Diff line Loading @@ -71,16 +71,23 @@ class Event(Model): # to form timeline date = DateTimeField(default=datetime.datetime.now()) operartor = ForeignKeyField(User, null=True, default=None) # class Printer(Model): # with connection information # id = AutoField() # name = CharField() # host = CharField() # username = CharField() # password = CharField() # class PrinterQueue(Model): # printer = ForeignKeyField(Printer) # queue = ForeignKeyField(Queue) class Printer(Model): # with connection information id = AutoField() name = CharField() model = CharField() config = CharField() # json current_print = ForeignKeyField(PrintRequest) class PrinterQueue(Model): printer = ForeignKeyField(Printer) queue = ForeignKeyField(Queue) class QueueElement(Model): id = AutoField() state = IntegerField(default=0) # 0: todo, 1: done, 2: err/ canceled ? queue = ForeignKeyField(Queue) time_added = DateTimeField(default=datetime.datetime.now()) # V2, Slicer vm info ? maybe not even necessary Loading back/myfab/queue.py +183 −23 Original line number Diff line number Diff line from myfab.log import info, warn, error from myfab.model import PrintRequest from myfab.model import PrintRequest, QueueElement, Queue, Printer, PrinterQueue import requests as r import json import myfab.log as log QUEUE_ELEMENT_STATE_INQUEUE = 0 QUEUE_ELEMENT_STATE_DONE = 1 QUEUE_ELEMENT_STATE_REMOVED = 2 #region QUEUE UTILS """ Queue manger for printers Add request to the end of the queue """ def enqueue(req, queue): pass """ Get the immediate next item """ def get_next_request(queue): pass class Queue: def rm_queue_top(queue): pass def remove_queue_element(queue_element, reason): pass """ Number of elements before queue_element. 0 means queue_element is the current top of the queue and next dequeued object """ def get_queue_element_pos(queue_element: QueueElement): return QueueElement.select().where(QueueElement.queue == queue_element.queue and QueueElement.state == QUEUE_ELEMENT_STATE_INQUEUE, QueueElement.id < queue_element.id).count() def __init__(self, name): self.name = name self.printers = {} self._queue = [] #endregion def enqueue(self, request: PrintRequest): info(f'[QUEUE] Enqueue {request.id} ({request.title}) in {self.name}') self._queue.append(request) #region PRINTER UTILS & class class PrinterBase: def __init__(self, printer_obj): self.printer_obj = printer_obj self.id = printer_obj.id self.name = printer_obj.name self.current_print : PrintRequest = None self.queues = [] def load_queues(self): self.queues = [] for q in PrinterQueue.select().where(PrinterQueue.printer == Printer.id): self.queues.append(q) def set_print(self, pr : PrintRequest): raise NotImplementedError() def printer_type(self): raise NotImplementedError() def version(self): raise NotImplementedError() def printer_model(self): raise NotImplementedError() def refresh(self): raise NotImplementedError() # Printer not linked to Octoprint but can represent a printer class FakePrinter(PrinterBase): def __init__(self, printer_obj): super().__init__(printer_obj) def refresh(self): pass def __call__(self): return self._queue[0] def printer_type(self): return "fake" def add_printer(self, printer, weight): self.printers[printer] = weight def printer_model(self): return "N/A" def pop(self): self._queue.pop() info(f'[QUEUE] Pop {self.name}') def version(self): return "N/A" # Printer with Octoprint backend class OctoPrinter(PrinterBase): def __init__(self, printer_obj): super().__init__(printer_obj) config = printer_obj.config """ An Octoprint compatible printer { 'hostname': 'hostname or ip', 'port': port 'key': the api-key } """ class Printer: pass self._version = 'Unknown' self.hostname = config.hostname self.port = config.port self.key = config.key self.ready_for_printing = False # can accept new print self.test_and_pull_printer_global_info() self.load_queues() self.printer_state = { 'temperatures': { 'bed': float('nan'), 'tip': float('nan') }, "job": None, "state": { "text": "Unknown", "flags": { "operational": False, "paused": False, "printing": False, "cancelling": False, "pausing": False, "sdReady": False, "error": False, "ready": False, "closedOrError": False } } } self.reset_current_print() def set_print(self, print_obj: PrintRequest): if(not self.ready_for_printing): raise Exception('Printer is not ready') self.printer_obj.current_print = print_obj self.current_print = None self.printer_obj.save() def _refresh_ready_state(self): self.ready_for_printing = self.printer_state['state']['flags']['ready'] and self.current_print == None """ Object was removed from printing bed, printer is ready to operate again """ def reset_current_print(self): self.current_print = None self.printer_obj.current_print = None self.printer_obj.save() self._refresh_printer_status() self._refresh_ready_state() def _make_octoprint_raw_request(self, path, method = 'get', data = None): return json.loads(r.request(method, f'http://{self.hostname}:{self.port}{path}', headers = {'X-Api-Key': self.key}, payload = data).text) def _refresh_printer_status(self): log.info(f'Printer #{self.id} ({self.name}) @ {self.hostname}: refreshing status') status = self._make_octoprint_raw_request('/api/printer') self.printer_state = { "temperatures": { "bed": status['temperature']['bed']['actual'], "tip": status['temperature']['tool0']['actual'] }, "state": status["state"] } if(not status['state']['flags']['printing']): self.printer_state['job'] = None return job = self._make_octoprint_raw_request('/api/job') self.printer_state['job'] = { "printTime": job['progress']['printTime'], "printTimeLeft": job['progress']['printTimeLeft'], "progress": job['progress']['completion'], "print_length": job['filament']['length'] } def test_and_pull_printer_global_info(self): versions = self._make_octoprint_raw_request('/api/version') log.info(f'Printer #{self.id} ({self.name}) @ {self.hostname}: connected to octoprint') log.info(f'Version info: {versions["text"]} API v{versions["text"]})') self._version = versions['text'] def version(self): return self._version def printer_type(self): return "Octoprint" def printer_model(self): return "wallah jsp" # FIXME xptdr #endregion # Octoprint api example """ Loading Loading
README.md +36 −9 Original line number Diff line number Diff line Loading @@ -4,29 +4,56 @@ Fablab 3d printing request application ## Minimum Viable Version - [ ] Tests - [ ] *Tests* - [x] User Connection - [ ] User administration - [ ] Backend - [ ] List users, - [ ] Change access level - [ ] Submit a print request - [x] Submit a print request - [x] Upload a STL file - [x] Backend - [x] Front - [x] Create a new Print Request - [x] Backend - [x] Front - [ ] Accept/Reject print request (front) - [ ] Edit the print request - [ ] Manualy set the state - [ ] Delete - [ ] Asign the request - [x] Accept/Reject print request (front) - [x] See STL file in 3d - [x] See print status - [ ] Change Print status (see enum in api) - [ ] Slice - [ ] Upload GCODE - [x] Change Print status (see enum in api) - [x] Slice - [x] Upload GCODE - [x] Download GCODE - [ ] **V2**: Open slicer window - [ ] Message system - [ ] Post message on each request - [ ] New message indication - [ ] **V2**: Send to printer - [ ] Queue management - [ ] Working queues - [ ] Printer management - [ ] Post to octoprint - [ ] Indicate successfull / error in printing - [ ] **v2** Printer management - [ ] **v2** Post to octoprint # Target Architecture ``` +-------------------------------------------------------------------------------------------------------------------------+ | |---------------------------------+ | | +------------------------------------------------+ +----------------+ +---------------+ | | | | | +--->+ DFPM | | | Printer | | +--------+ | | +--------------+ +-------------------+ | | | Printer manager<--------> Octoprint <----> | | |Client | | | | | | <-------+ +----------------+ | | | | | <-----------> Front <------> Request Manager | | +---------------+-----------------+ | | | | | | | | | | +----------------------------------------+ | +--------+ | | +--------------+ +-----------^-------+ | | | | | | | | +-------------+ | | | | | | | | Slicer VM | | | | +------------------> Slicer | | | | +------------------------------------------------+ | manager | | | | +----------------------------------------+ | +-------------------------------------------------------------------------------------------------------------------------+ ``` No newline at end of file
back/myfab/__pycache__/api.cpython-38.pyc +2.17 KiB (9.08 KiB) File changed.No diff preview for this file type. View original file View changed file
back/myfab/api.py +133 −8 Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ from flask_jwt_extended import ( ) import datetime from myfab.model import Event, PrintRequest, User, init_db_connection, Queue from myfab.model import Event, PrintRequest, User, init_db_connection, Queue, QueueElement from flask_api import status from flask import jsonify import string Loading Loading @@ -50,6 +50,10 @@ REQUEST_ACCESS_OBSERVER = 1 # Allowed to see this request REQUEST_ACCESS_USER = 2 # Is the author of this request REQUEST_ACCESS_OPERATOR = 3 # Is an operator of the system USER_ACCESS_NORMAL = 0 USER_ACCESS_OBSERVER = 1 USER_ACCESS_OPERATOR = 2 # Elements that can be modified by a user (not op) REQUEST_USER_ELEMENTS = set(["title","description","project"]) Loading Loading @@ -108,6 +112,8 @@ def loop_requests(reqs): # if type(reqs) != list: # reqs = [reqs] for req in reqs: if get_access_level_to_request(req, current_user) == REQUEST_ACCESS_DENIED: continue rqs.append({ "request_id": req.id, "title": req.title, Loading @@ -129,7 +135,22 @@ def loop_requests(reqs): def get_access_level_to_request(req: PrintRequest, user): #req is id or req same for user # TODO return REQUEST_ACCESS_OPERATOR # FIXME if req is not PrintRequest: req = get_request_by_id(req) if req is None: raise Exception("No such request") if user is not User: user = get_or_create_user(user) if user.access == USER_ACCESS_OPERATOR: return REQUEST_ACCESS_OPERATOR # user is admin if user.access == USER_ACCESS_OBSERVER: return REQUEST_ACCESS_OBSERVER # user has unlimited view if user.access == USER_ACCESS_NORMAL: if req.author == user.username: return REQUEST_ACCESS_USER # user is creator of the request else: return REQUEST_ACCESS_DENIED # user does not have access def update_requires_operator_rights(update_json): s = set(update_json) Loading Loading @@ -168,9 +189,8 @@ def update_request(req_id): required_access = REQUEST_ACCESS_OPERATOR if req.status > 0 or update_requires_operator_rights(changes) else REQUEST_ACCESS_USER if get_access_level_to_request(req, current_user) < REQUEST_ACCESS_OPERATOR: if get_access_level_to_request(req, current_user) < required_access: return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED # TODO access for e in changes: setattr(req, e, changes[e]) Loading Loading @@ -252,17 +272,119 @@ def get_requests(): #! FIXME Don't get request if not access #region user management routes # user list @app.route("/users") @jwt_required def list_users(): if current_user.access != USER_ACCESS_OPERATOR: return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED users = [] for user in User.select(): users.append({ 'username': user.username, 'fullname': user.fullname, 'access': user.access }) return jsonify(users) #change user TODO @app.route("/users/<username>", methods=['PUT']) @jwt_required def update_user(username): if current_user.access != USER_ACCESS_OPERATOR: return jsonify({'error': 'Access Denied'}), status.HTTP_401_UNAUTHORIZED user = get_or_create_user(username) #FIXME no such user is 500 changes = request.get_json() for e in changes: setattr(user, e, changes[e]) user.save() return jsonify({}) #endregion user management #region Printer manager routes #region printer management routes TODO # list printers & status @app.route("/printers") @jwt_required def list_printers(): """ [ { "id": "generated_id", "name": "given name by operators", "current_print_request": 0, "version": "octoprint & dfpm", "printer_type": "mini_prusa_mk3", "status": "printing, idle, error, out of commission, virtual printer...", "print_progress": 0.0, "infos": {temperatures etc...} "remaining_print_time" } ] """ pass # action on printer TODO v2 @app.route("/printers/<printer_id>/action", methods=["POST"]) @jwt_required def action_printer(printer_id): request.get_json() """ { "action": "pause", "resume", "cancel", "cycle", "disable", "enable", "restart" } """ pass # Edit printer config @app.route("/printers/<printer_id>", methods=["PUT"]) @jwt_required def update_printer(printer_id): pass # Add printer config @app.route("/printers/new", methods=["POST"]) @jwt_required def add_new_printer(): pass #endregion printer management #region queues management #region queues management TODO #list queues @app.route("/queues") @jwt_required def list_queues(): """ [ { "id": "id", "name": "queue_name", "weight": 0, queue weight, "printers": [ids ],* "enabled": true, "process_span": when to process the queue (type unknown) ?? FIXME } ] """ pass # create queue # delete queue # update queue #endregion queues #region Slicer manager routes #region slicer manager routes TODO """ GET /slicer/update Loading Loading @@ -292,6 +414,9 @@ POST /slicer/usage #endregion routes # peewee usage # user = User.select().where(User.username == "test").count() Loading
back/myfab/model.py +17 −10 Original line number Diff line number Diff line Loading @@ -71,16 +71,23 @@ class Event(Model): # to form timeline date = DateTimeField(default=datetime.datetime.now()) operartor = ForeignKeyField(User, null=True, default=None) # class Printer(Model): # with connection information # id = AutoField() # name = CharField() # host = CharField() # username = CharField() # password = CharField() # class PrinterQueue(Model): # printer = ForeignKeyField(Printer) # queue = ForeignKeyField(Queue) class Printer(Model): # with connection information id = AutoField() name = CharField() model = CharField() config = CharField() # json current_print = ForeignKeyField(PrintRequest) class PrinterQueue(Model): printer = ForeignKeyField(Printer) queue = ForeignKeyField(Queue) class QueueElement(Model): id = AutoField() state = IntegerField(default=0) # 0: todo, 1: done, 2: err/ canceled ? queue = ForeignKeyField(Queue) time_added = DateTimeField(default=datetime.datetime.now()) # V2, Slicer vm info ? maybe not even necessary Loading
back/myfab/queue.py +183 −23 Original line number Diff line number Diff line from myfab.log import info, warn, error from myfab.model import PrintRequest from myfab.model import PrintRequest, QueueElement, Queue, Printer, PrinterQueue import requests as r import json import myfab.log as log QUEUE_ELEMENT_STATE_INQUEUE = 0 QUEUE_ELEMENT_STATE_DONE = 1 QUEUE_ELEMENT_STATE_REMOVED = 2 #region QUEUE UTILS """ Queue manger for printers Add request to the end of the queue """ def enqueue(req, queue): pass """ Get the immediate next item """ def get_next_request(queue): pass class Queue: def rm_queue_top(queue): pass def remove_queue_element(queue_element, reason): pass """ Number of elements before queue_element. 0 means queue_element is the current top of the queue and next dequeued object """ def get_queue_element_pos(queue_element: QueueElement): return QueueElement.select().where(QueueElement.queue == queue_element.queue and QueueElement.state == QUEUE_ELEMENT_STATE_INQUEUE, QueueElement.id < queue_element.id).count() def __init__(self, name): self.name = name self.printers = {} self._queue = [] #endregion def enqueue(self, request: PrintRequest): info(f'[QUEUE] Enqueue {request.id} ({request.title}) in {self.name}') self._queue.append(request) #region PRINTER UTILS & class class PrinterBase: def __init__(self, printer_obj): self.printer_obj = printer_obj self.id = printer_obj.id self.name = printer_obj.name self.current_print : PrintRequest = None self.queues = [] def load_queues(self): self.queues = [] for q in PrinterQueue.select().where(PrinterQueue.printer == Printer.id): self.queues.append(q) def set_print(self, pr : PrintRequest): raise NotImplementedError() def printer_type(self): raise NotImplementedError() def version(self): raise NotImplementedError() def printer_model(self): raise NotImplementedError() def refresh(self): raise NotImplementedError() # Printer not linked to Octoprint but can represent a printer class FakePrinter(PrinterBase): def __init__(self, printer_obj): super().__init__(printer_obj) def refresh(self): pass def __call__(self): return self._queue[0] def printer_type(self): return "fake" def add_printer(self, printer, weight): self.printers[printer] = weight def printer_model(self): return "N/A" def pop(self): self._queue.pop() info(f'[QUEUE] Pop {self.name}') def version(self): return "N/A" # Printer with Octoprint backend class OctoPrinter(PrinterBase): def __init__(self, printer_obj): super().__init__(printer_obj) config = printer_obj.config """ An Octoprint compatible printer { 'hostname': 'hostname or ip', 'port': port 'key': the api-key } """ class Printer: pass self._version = 'Unknown' self.hostname = config.hostname self.port = config.port self.key = config.key self.ready_for_printing = False # can accept new print self.test_and_pull_printer_global_info() self.load_queues() self.printer_state = { 'temperatures': { 'bed': float('nan'), 'tip': float('nan') }, "job": None, "state": { "text": "Unknown", "flags": { "operational": False, "paused": False, "printing": False, "cancelling": False, "pausing": False, "sdReady": False, "error": False, "ready": False, "closedOrError": False } } } self.reset_current_print() def set_print(self, print_obj: PrintRequest): if(not self.ready_for_printing): raise Exception('Printer is not ready') self.printer_obj.current_print = print_obj self.current_print = None self.printer_obj.save() def _refresh_ready_state(self): self.ready_for_printing = self.printer_state['state']['flags']['ready'] and self.current_print == None """ Object was removed from printing bed, printer is ready to operate again """ def reset_current_print(self): self.current_print = None self.printer_obj.current_print = None self.printer_obj.save() self._refresh_printer_status() self._refresh_ready_state() def _make_octoprint_raw_request(self, path, method = 'get', data = None): return json.loads(r.request(method, f'http://{self.hostname}:{self.port}{path}', headers = {'X-Api-Key': self.key}, payload = data).text) def _refresh_printer_status(self): log.info(f'Printer #{self.id} ({self.name}) @ {self.hostname}: refreshing status') status = self._make_octoprint_raw_request('/api/printer') self.printer_state = { "temperatures": { "bed": status['temperature']['bed']['actual'], "tip": status['temperature']['tool0']['actual'] }, "state": status["state"] } if(not status['state']['flags']['printing']): self.printer_state['job'] = None return job = self._make_octoprint_raw_request('/api/job') self.printer_state['job'] = { "printTime": job['progress']['printTime'], "printTimeLeft": job['progress']['printTimeLeft'], "progress": job['progress']['completion'], "print_length": job['filament']['length'] } def test_and_pull_printer_global_info(self): versions = self._make_octoprint_raw_request('/api/version') log.info(f'Printer #{self.id} ({self.name}) @ {self.hostname}: connected to octoprint') log.info(f'Version info: {versions["text"]} API v{versions["text"]})') self._version = versions['text'] def version(self): return self._version def printer_type(self): return "Octoprint" def printer_model(self): return "wallah jsp" # FIXME xptdr #endregion # Octoprint api example """ Loading