Format, add run on printer, start working on calibration
This commit is contained in:
parent
036465a50b
commit
9910b72d05
16 changed files with 173 additions and 81 deletions
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import smtplib
|
||||||
from email import encoders
|
from email import encoders
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
import smtplib
|
|
||||||
|
|
||||||
|
|
||||||
class FastMailSMTP(smtplib.SMTP_SSL):
|
class FastMailSMTP(smtplib.SMTP_SSL):
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from attrs import define
|
from attrs import define
|
||||||
|
|
||||||
|
|
||||||
OPTION_PATTERN = re.compile("; (?P<key>[a-z0-9_]+) = (?P<value>.*?)\n")
|
OPTION_PATTERN = re.compile("; (?P<key>[a-z0-9_]+) = (?P<value>.*?)\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,20 @@
|
||||||
|
|
||||||
"""The core app entrypoint."""
|
"""The core app entrypoint."""
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
from contextlib import closing
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from importlib.resources import files
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tomllib
|
from shutil import copyfile
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import click
|
import click
|
||||||
|
import tomllib
|
||||||
from flask import Flask, request
|
from flask import Flask, request
|
||||||
|
|
||||||
|
from tentacles import workers
|
||||||
from tentacles.blueprints import (
|
from tentacles.blueprints import (
|
||||||
admin_ui,
|
admin_ui,
|
||||||
api,
|
api,
|
||||||
|
@ -18,11 +24,7 @@ from tentacles.blueprints import (
|
||||||
user_ui,
|
user_ui,
|
||||||
)
|
)
|
||||||
from tentacles.db import Db
|
from tentacles.db import Db
|
||||||
from tentacles.globals import _ctx, Ctx, ctx
|
from tentacles.globals import Ctx, _ctx, ctx
|
||||||
from tentacles import workers
|
|
||||||
from contextlib import closing
|
|
||||||
from tentacles.blueprints.util import salt
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -136,6 +138,28 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
||||||
with closing(db_factory(app)) as db:
|
with closing(db_factory(app)) as db:
|
||||||
db.migrate()
|
db.migrate()
|
||||||
|
|
||||||
|
# Register embedded gcode files as usable files
|
||||||
|
with closing(db_factory(app)) as db:
|
||||||
|
for f in files("tentacles.gcode").iterdir():
|
||||||
|
print(type(f), repr(f))
|
||||||
|
if f.is_file() and f.name.endswith(".gcode"):
|
||||||
|
sanitized_path = os.path.join(
|
||||||
|
"$ROOT_FOLDER",
|
||||||
|
app.config["UPLOAD_FOLDER"],
|
||||||
|
f.name,
|
||||||
|
)
|
||||||
|
real_path = sanitized_path.replace(
|
||||||
|
"$ROOT_FOLDER", app.config["ROOT_FOLDER"]
|
||||||
|
)
|
||||||
|
if not os.path.exists(real_path):
|
||||||
|
copyfile(f, real_path)
|
||||||
|
db.create_file(
|
||||||
|
uid=None,
|
||||||
|
filename=f.name,
|
||||||
|
path=sanitized_path,
|
||||||
|
)
|
||||||
|
log.info("Registered calibration script %s", f.name)
|
||||||
|
|
||||||
# Configuring cherrypy is kinda awful
|
# Configuring cherrypy is kinda awful
|
||||||
cherrypy.server.unsubscribe()
|
cherrypy.server.unsubscribe()
|
||||||
server = cherrypy._cpserver.Server()
|
server = cherrypy._cpserver.Server()
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .api import requires_admin
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
flash,
|
flash,
|
||||||
|
@ -11,8 +9,10 @@ from flask import (
|
||||||
render_template,
|
render_template,
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tentacles.globals import ctx
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
|
from .api import requires_admin
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("admin", __name__)
|
BLUEPRINT = Blueprint("admin", __name__)
|
||||||
|
@ -96,9 +96,9 @@ def handle_add_printer():
|
||||||
flash("Printer created")
|
flash("Printer created")
|
||||||
return redirect("/admin/printers")
|
return redirect("/admin/printers")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.exception("Failed to create printer")
|
log.exception("Failed to create printer")
|
||||||
flash(f"Unable to create printer", category="error")
|
flash("Unable to create printer", category="error")
|
||||||
|
|
||||||
return render_template("printers.html.j2")
|
return render_template("printers.html.j2")
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
"""API endpoints supporting the 'ui'."""
|
"""API endpoints supporting the 'ui'."""
|
||||||
|
|
||||||
from hashlib import sha3_256
|
|
||||||
import os
|
import os
|
||||||
|
from hashlib import sha3_256
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import Blueprint, current_app, request
|
from flask import Blueprint, current_app, request
|
||||||
from tentacles.globals import ctx
|
|
||||||
|
|
||||||
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
BLUEPRINT = Blueprint("api", __name__, url_prefix="/api")
|
BLUEPRINT = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
|
@ -96,12 +96,11 @@ def create_file(location: Optional[str] = None):
|
||||||
current_app.config["UPLOAD_FOLDER"],
|
current_app.config["UPLOAD_FOLDER"],
|
||||||
sanitized_filename,
|
sanitized_filename,
|
||||||
)
|
)
|
||||||
if os.path.exists(sanitized_path):
|
|
||||||
return {"error": "file exists already"}, 409
|
|
||||||
|
|
||||||
real_path = sanitized_path.replace(
|
real_path = sanitized_path.replace(
|
||||||
"$ROOT_FOLDER", current_app.config["ROOT_FOLDER"]
|
"$ROOT_FOLDER", current_app.config["ROOT_FOLDER"]
|
||||||
)
|
)
|
||||||
|
if os.path.exists(real_path):
|
||||||
|
return {"error": "file exists already"}, 409
|
||||||
|
|
||||||
print(file.filename, real_path)
|
print(file.filename, real_path)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .api import create_file
|
|
||||||
from .util import requires_auth
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
flash,
|
flash,
|
||||||
|
@ -14,8 +11,11 @@ from flask import (
|
||||||
request,
|
request,
|
||||||
send_file,
|
send_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tentacles.globals import ctx
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
|
from .api import create_file
|
||||||
|
from .util import requires_auth
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("files", __name__)
|
BLUEPRINT = Blueprint("files", __name__)
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .util import requires_auth
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
flash,
|
flash,
|
||||||
|
@ -11,14 +9,19 @@ from flask import (
|
||||||
render_template,
|
render_template,
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tentacles.globals import ctx
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
|
from .util import requires_auth
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("jobs", __name__)
|
BLUEPRINT = Blueprint("jobs", __name__)
|
||||||
|
|
||||||
|
|
||||||
def maybe(f, x):
|
def maybe(f, x):
|
||||||
|
if x == "None":
|
||||||
|
return None
|
||||||
|
|
||||||
if x is not None:
|
if x is not None:
|
||||||
return f(x)
|
return f(x)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
from .util import is_logged_in, salt
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
|
@ -14,8 +12,10 @@ from flask import (
|
||||||
render_template,
|
render_template,
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tentacles.globals import ctx
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
|
from .util import is_logged_in, salt
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("user", __name__)
|
BLUEPRINT = Blueprint("user", __name__)
|
||||||
|
@ -121,7 +121,7 @@ def post_register():
|
||||||
|
|
||||||
return render_template("register.html.j2")
|
return render_template("register.html.j2")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.exception("Error encountered while registering a user...")
|
log.exception("Error encountered while registering a user...")
|
||||||
|
|
||||||
flash("Unable to register that username", category="error")
|
flash("Unable to register that username", category="error")
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import current_app, flash, redirect
|
from flask import current_app, flash, redirect
|
||||||
from tentacles.globals import ctx
|
|
||||||
|
|
||||||
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sqlite3
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from hashlib import sha3_256
|
from hashlib import sha3_256
|
||||||
from importlib.resources import files
|
from importlib.resources import files
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
import logging
|
|
||||||
import sqlite3
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from types import GeneratorType, new_class
|
from types import GeneratorType, new_class
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -18,7 +18,6 @@ from aiosql.aiosql import (
|
||||||
from aiosql.queries import Queries
|
from aiosql.queries import Queries
|
||||||
from aiosql.query_loader import QueryLoader
|
from aiosql.query_loader import QueryLoader
|
||||||
|
|
||||||
|
|
||||||
_sqlite = get_adapter("sqlite3")
|
_sqlite = get_adapter("sqlite3")
|
||||||
_loader = QueryLoader(_sqlite, None)
|
_loader = QueryLoader(_sqlite, None)
|
||||||
_queries = Queries(_sqlite, kwargs_only=False)
|
_queries = Queries(_sqlite, kwargs_only=False)
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
|
|
||||||
from attrs import define
|
from attrs import define
|
||||||
from tentacles.db import Db
|
|
||||||
from werkzeug.local import LocalProxy
|
from werkzeug.local import LocalProxy
|
||||||
|
|
||||||
|
from tentacles.db import Db
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
class Ctx:
|
class Ctx:
|
||||||
|
|
|
@ -35,6 +35,12 @@ ALTER TABLE jobs ADD COLUMN color_id INTEGER DEFAULT (NULL);
|
||||||
-- name: migration-0003-jobs-add-continuous#
|
-- name: migration-0003-jobs-add-continuous#
|
||||||
ALTER TABLE jobs ADD COLUMN continuous BOOLEAN DEFAULT (FALSE);
|
ALTER TABLE jobs ADD COLUMN continuous BOOLEAN DEFAULT (FALSE);
|
||||||
|
|
||||||
|
-- name: migration-0004-jobs-add-mapped-at#
|
||||||
|
ALTER TABLE jobs ADD COLUMN mapped_at TEXT;
|
||||||
|
|
||||||
|
-- name: migration-0005-jobs-add-mapped-at#
|
||||||
|
ALTER TABLE jobs ADD COLUMN requested_at TEXT;
|
||||||
|
|
||||||
-- name: create-job^
|
-- name: create-job^
|
||||||
INSERT INTO jobs (
|
INSERT INTO jobs (
|
||||||
user_id
|
user_id
|
||||||
|
@ -42,6 +48,7 @@ INSERT INTO jobs (
|
||||||
, color_id
|
, color_id
|
||||||
, printer_id
|
, printer_id
|
||||||
, continuous
|
, continuous
|
||||||
|
, requested_at
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
:uid
|
:uid
|
||||||
|
@ -49,6 +56,7 @@ VALUES (
|
||||||
, :cid
|
, :cid
|
||||||
, :pid
|
, :pid
|
||||||
, :cont
|
, :cont
|
||||||
|
, datetime('now')
|
||||||
)
|
)
|
||||||
RETURNING
|
RETURNING
|
||||||
*
|
*
|
||||||
|
@ -125,15 +133,59 @@ ORDER BY
|
||||||
, id
|
, id
|
||||||
;
|
;
|
||||||
|
|
||||||
|
-- name: list-live-jobs
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
j.id as id
|
||||||
|
, j.file_id
|
||||||
|
, coalesce(j.color_id, fa.color_id) as color_id
|
||||||
|
, fa.id as analysis_id
|
||||||
|
, fa.max_x
|
||||||
|
, fa.max_y
|
||||||
|
, fa.max_z
|
||||||
|
, fa.max_bed
|
||||||
|
, fa.max_end
|
||||||
|
, fa.nozzle_diameter
|
||||||
|
, fa.filament_id
|
||||||
|
, (SELECT name FROM filament WHERE id = fa.filament_id) AS filament_name
|
||||||
|
, (SELECT name AS name FROM filament_color WHERE id = coalesce(j.color_id, fa.color_id)) AS color_name
|
||||||
|
, j.status_id
|
||||||
|
, (SELECT name FROM job_statuses WHERE id = j.status_id) AS status
|
||||||
|
, j.started_at
|
||||||
|
, j.time_left
|
||||||
|
, j.cancelled_at
|
||||||
|
, j.finished_at
|
||||||
|
, j.user_id
|
||||||
|
, j.printer_id
|
||||||
|
, j.continuous
|
||||||
|
FROM jobs j
|
||||||
|
INNER JOIN files f
|
||||||
|
ON j.file_id = f.id
|
||||||
|
LEFT JOIN file_analysis fa
|
||||||
|
ON fa.file_id = f.id
|
||||||
|
WHERE
|
||||||
|
finished_at IS NULL
|
||||||
|
AND cancelled_at IS NULL
|
||||||
|
AND (:uid IS NULL OR j.user_id = :uid)
|
||||||
|
AND f.id IS NOT NULL
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
status_id DESC
|
||||||
|
, continuous DESC
|
||||||
|
, id
|
||||||
|
;
|
||||||
|
|
||||||
-- name: poll-job-queue^
|
-- name: poll-job-queue^
|
||||||
SELECT
|
SELECT
|
||||||
*
|
*
|
||||||
FROM jobs
|
FROM jobs
|
||||||
WHERE
|
WHERE
|
||||||
started_at IS NULL
|
mapped_at IS NULL
|
||||||
|
AND started_at IS NULL
|
||||||
AND finished_at IS NULL
|
AND finished_at IS NULL
|
||||||
AND printer_id IS NULL
|
AND printer_id IS NULL
|
||||||
LIMIT 1
|
|
||||||
;
|
;
|
||||||
|
|
||||||
-- name: list-job-history
|
-- name: list-job-history
|
||||||
|
@ -208,6 +260,7 @@ WHERE
|
||||||
UPDATE jobs
|
UPDATE jobs
|
||||||
SET
|
SET
|
||||||
printer_id = :pid
|
printer_id = :pid
|
||||||
|
, mapped_at = datetime('now')
|
||||||
WHERE
|
WHERE
|
||||||
id = :jid
|
id = :jid
|
||||||
;
|
;
|
||||||
|
|
|
@ -149,14 +149,6 @@ input[type="image"] {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-menu {
|
|
||||||
border-color: $black;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-black {
|
.border-black {
|
||||||
border-color: $black;
|
border-color: $black;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% import "macros.html.j2" as macros %}
|
{% import "macros.html.j2" as macros %}
|
||||||
<h2>Job queue</h2>
|
<h2>Job queue</h2>
|
||||||
{% with jobs = ctx.db.list_job_queue(uid=None if ctx.is_admin else ctx.uid) %}
|
{% with jobs = ctx.db.list_live_jobs(uid=None if ctx.is_admin else ctx.uid) %}
|
||||||
{% if jobs %}
|
{% if jobs %}
|
||||||
{% for job in jobs %}
|
{% for job in jobs %}
|
||||||
<div class="job row u-flex">
|
<div class="job row u-flex">
|
||||||
|
|
|
@ -4,16 +4,28 @@
|
||||||
<form class="start-menu inline u-flex" method="post" action="/jobs">
|
<form class="start-menu inline u-flex" method="post" action="/jobs">
|
||||||
<input type="hidden" name="action" value="enqueue" />
|
<input type="hidden" name="action" value="enqueue" />
|
||||||
<input type="hidden" name="file_id" value="{{ file.id }}" />
|
<input type="hidden" name="file_id" value="{{ file.id }}" />
|
||||||
<select name="color_id">
|
<div class="u-flex u-ml-auto u-mv-auto u-flex u-ml-1">
|
||||||
{%- for c in ctx.db.list_colors() %}
|
<label class="u-flex u-mv-auto" for="printer_id">Printer</label>
|
||||||
<option value="{{c.id}}" {% if file.color_id == c.id %}selected{%endif%}>{{c.name}}</option>
|
<select class="u-flex u-mv-auto" name="printer_id">
|
||||||
{%- endfor %}
|
<option value="None">Any</option>
|
||||||
</select>
|
{%- for p in ctx.db.list_printers() %}
|
||||||
|
<option value="{{p.id}}">{{p.name}}</option>
|
||||||
|
{%- endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="u-flex u-ml-auto u-mv-auto u-flex u-ml-1">
|
||||||
|
<label class="u-flex u-mv-auto" for="color_id">Color</label>
|
||||||
|
<select class="u-flex u-mv-auto" name="color_id">
|
||||||
|
{%- for c in ctx.db.list_colors() %}
|
||||||
|
<option value="{{c.id}}" {% if file.color_id == c.id %}selected{%endif%}>{{c.name}}</option>
|
||||||
|
{%- endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="u-flex u-ml-auto u-mv-auto u-flex u-ml-1">
|
<div class="u-flex u-ml-auto u-mv-auto u-flex u-ml-1">
|
||||||
<label class="u-flex u-mv-auto" for="continuous">Cont.</label>
|
<label class="u-flex u-mv-auto" for="continuous">Cont.</label>
|
||||||
<input class="u-flex u-mv-auto" type="checkbox" name="continuous" false>
|
<input class="u-flex u-mv-auto" type="checkbox" name="continuous" false>
|
||||||
</div>
|
</div>
|
||||||
<input id="submit" type="image" src="/static/print.svg" height="24" width="24" />
|
<input class="u-flex u-mv-auto" id="submit" type="image" src="/static/print.svg" height="24" width="24" />
|
||||||
</form>
|
</form>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
|
@ -7,26 +7,28 @@ Supporting the core app with asynchronous maintenance tasks.
|
||||||
Mostly related to monitoring and managing Printer state.
|
Mostly related to monitoring and managing Printer state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import cache
|
from functools import cache
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from cherrypy.process.plugins import Monitor
|
from cherrypy.process.plugins import Monitor
|
||||||
from fastmail import FastMailSMTP
|
from flask import Flask as App
|
||||||
from flask import Flask as App, render_template
|
from flask import render_template
|
||||||
from gcode import analyze_gcode_file
|
|
||||||
from octorest import OctoRest
|
|
||||||
from requests import Response
|
from requests import Response
|
||||||
from requests.exceptions import (
|
from requests.exceptions import (
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
HTTPError,
|
HTTPError,
|
||||||
Timeout,
|
Timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from fastmail import FastMailSMTP
|
||||||
|
from gcode import analyze_gcode_file
|
||||||
|
from octorest import OctoRest
|
||||||
from tentacles.db import Db
|
from tentacles.db import Db
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,14 +137,9 @@ def poll_printers(app: App, db: Db) -> None:
|
||||||
def assign_jobs(app: App, db: Db) -> None:
|
def assign_jobs(app: App, db: Db) -> None:
|
||||||
"""Assign jobs to printers. Uploading files and job state management is handled separately."""
|
"""Assign jobs to printers. Uploading files and job state management is handled separately."""
|
||||||
|
|
||||||
for job in db.list_job_queue(uid=None):
|
# FIXME: Push the scheduler into SQL
|
||||||
# FIXME: Jobs which have been mapped are still in the "queue" until they finish
|
if job := db.poll_job_queue():
|
||||||
# Ignore such as they have already been mapped
|
for printer in db.list_idle_printers():
|
||||||
if job.printer_id:
|
|
||||||
continue
|
|
||||||
|
|
||||||
idle = list(db.list_idle_printers())
|
|
||||||
for printer in idle:
|
|
||||||
if (
|
if (
|
||||||
job.analysis_id is not None
|
job.analysis_id is not None
|
||||||
and printer.limit_x >= job.max_x
|
and printer.limit_x >= job.max_x
|
||||||
|
@ -153,9 +150,15 @@ def assign_jobs(app: App, db: Db) -> None:
|
||||||
and printer.nozzle_diameter == job.nozzle_diameter
|
and printer.nozzle_diameter == job.nozzle_diameter
|
||||||
and printer.filament_id == job.filament_id
|
and printer.filament_id == job.filament_id
|
||||||
and (
|
and (
|
||||||
printer.color_id == job.color_id
|
|
||||||
# Note that the default/undefined color is #1
|
# Note that the default/undefined color is #1
|
||||||
or job.color_id == 1
|
job.color_id == 1
|
||||||
|
or printer.color_id == job.color_id
|
||||||
|
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
# Note that the null printer ID serves as 'any'
|
||||||
|
job.printer_id is None
|
||||||
|
or job.printer_id == printer.id
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
db.assign_job(jid=job.id, pid=printer.id)
|
db.assign_job(jid=job.id, pid=printer.id)
|
||||||
|
@ -276,28 +279,25 @@ def pull_jobs(app: App, db: Db) -> None:
|
||||||
"""\
|
"""\
|
||||||
G90 ; Absolute motion coordinates
|
G90 ; Absolute motion coordinates
|
||||||
G1 X155 Y310 F9000 ; Traverse to the back of the bed without lowering
|
G1 X155 Y310 F9000 ; Traverse to the back of the bed without lowering
|
||||||
|
|
||||||
M118 A1 action:notification Cooling bed
|
|
||||||
M104 S0 ; Turn off the hotend if it's on
|
M104 S0 ; Turn off the hotend if it's on
|
||||||
M190 R30 T600 ; Cool the bed
|
M190 R45 ; Cool the bed
|
||||||
|
M190 R40 ; Cool the bed
|
||||||
|
M190 R35 ; Cool the bed
|
||||||
|
M190 R30 ; Cool the bed
|
||||||
M140 S0 ; Turn off the bed
|
M140 S0 ; Turn off the bed
|
||||||
M118 A1 action:notification Bed cooled
|
|
||||||
|
|
||||||
M118 A1 action:notification Bed clear start
|
|
||||||
G1 X155 Y310 Z0.5 F9000 ; Lower the head to a push height
|
G1 X155 Y310 Z0.5 F9000 ; Lower the head to a push height
|
||||||
G1 X155 Y0 F9000 ; Traverse the head to the front of the bed to knock off any objects
|
G1 X155 Y0 Z0.5 F9000 ; Traverse the head almost to the front
|
||||||
G1 X155 Y200 Z250 F9000 ; Return the head to the presentation state
|
G1 X200 Y0 F9000 ; Traverse the head across to knock anything off
|
||||||
M118 A1 action:notification Bed clear end
|
G1 X150 Y200 Z250 F9000 ; Return the head to the presentation state
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
client.gcode(
|
else:
|
||||||
"""\
|
client.gcode(
|
||||||
M118 A1 action:notification Present bed start
|
"""\
|
||||||
G1 X150 Y200 Z250 F9000 ; Return the head to the presentation state
|
G1 X150 Y200 Z250 F9000 ; Return the head to the presentation state
|
||||||
M118 A1 action:notification Present bed end
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# And having dispatched that gcode mark the job done
|
# And having dispatched that gcode mark the job done
|
||||||
# FIXME: There should be some more state sync here....
|
# FIXME: There should be some more state sync here....
|
||||||
|
@ -311,10 +311,20 @@ M118 A1 action:notification Present bed end
|
||||||
|
|
||||||
elif printer_state.get("error"):
|
elif printer_state.get("error"):
|
||||||
log.warn(f"Job {job.id} has failed")
|
log.warn(f"Job {job.id} has failed")
|
||||||
|
client.gcode(
|
||||||
|
"""\
|
||||||
|
G1 X150 Y200 Z250 F9000 ; Return the head to the presentation state
|
||||||
|
"""
|
||||||
|
)
|
||||||
db.finish_job(jid=job.id, state="failed")
|
db.finish_job(jid=job.id, state="failed")
|
||||||
|
|
||||||
elif printer_state.get("cancelling"):
|
elif printer_state.get("cancelling"):
|
||||||
log.info(f"Job {job.id} has been acknowledged as cancelled")
|
log.info(f"Job {job.id} has been acknowledged as cancelled")
|
||||||
|
client.gcode(
|
||||||
|
"""\
|
||||||
|
G1 X150 Y200 Z250 F9000 ; Return the head to the presentation state
|
||||||
|
"""
|
||||||
|
)
|
||||||
db.finish_job(jid=job.id, state="cancelled")
|
db.finish_job(jid=job.id, state="cancelled")
|
||||||
|
|
||||||
elif printer_state.get("printing"):
|
elif printer_state.get("printing"):
|
||||||
|
@ -402,7 +412,7 @@ def analyze_files(app: App, db: Db):
|
||||||
def debug_queue(app: App, db: Db):
|
def debug_queue(app: App, db: Db):
|
||||||
output = ["---"]
|
output = ["---"]
|
||||||
|
|
||||||
for job in db.list_job_queue(uid=None):
|
for job in db.list_live_jobs(uid=None):
|
||||||
output.append("Job " + repr(job))
|
output.append("Job " + repr(job))
|
||||||
|
|
||||||
for printer in db.list_idle_printers():
|
for printer in db.list_idle_printers():
|
||||||
|
|
Loading…
Reference in a new issue