dsf-python 3.5.1rc1__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsf/__init__.py +18 -1
- dsf/commands/code.py +4 -1
- dsf/commands/code_interception.py +1 -1
- dsf/commands/files.py +4 -3
- dsf/commands/generic.py +10 -10
- dsf/commands/http_endpoints.py +5 -5
- dsf/commands/object_model.py +3 -3
- dsf/commands/packages.py +2 -2
- dsf/commands/plugins.py +7 -9
- dsf/commands/user_sessions.py +4 -4
- dsf/connections/base_connection.py +7 -1
- dsf/connections/init_messages/client_init_messages.py +9 -9
- dsf/connections/intercept_connection.py +2 -1
- dsf/http.py +51 -10
- dsf/object_model/heat/heater.py +23 -1
- dsf/object_model/job/gcode_fileinfo.py +6 -0
- dsf/object_model/job/times_left.py +11 -0
- dsf/object_model/model_collection.py +15 -6
- dsf/object_model/move/axis.py +11 -0
- dsf/object_model/move/extruder.py +11 -0
- dsf/object_model/move/input_shaping.py +7 -20
- dsf/object_model/network/network_interface.py +14 -1
- dsf/object_model/sensors/analog_sensor.py +20 -0
- dsf/object_model/sensors/analog_sensor_type.py +3 -3
- dsf/object_model/sensors/filament_monitors/Duet3DFilamentMonitor.py +11 -0
- dsf/object_model/sensors/filament_monitors/pulsed_filament_monitor.py +10 -0
- dsf/object_model/sensors/probe_type.py +3 -0
- dsf/object_model/spindles/spindle_type.py +14 -0
- dsf/object_model/spindles/spindles.py +48 -27
- dsf/object_model/tools/tools.py +24 -0
- dsf/object_model/utils.py +8 -3
- {dsf_python-3.5.1rc1.dist-info → dsf_python-3.6.0.dist-info}/METADATA +18 -5
- {dsf_python-3.5.1rc1.dist-info → dsf_python-3.6.0.dist-info}/RECORD +36 -35
- {dsf_python-3.5.1rc1.dist-info → dsf_python-3.6.0.dist-info}/WHEEL +1 -1
- {dsf_python-3.5.1rc1.dist-info → dsf_python-3.6.0.dist-info/licenses}/LICENSE +0 -0
- {dsf_python-3.5.1rc1.dist-info → dsf_python-3.6.0.dist-info}/top_level.txt +0 -0
dsf/__init__.py
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
__version__ = "3.6.0"
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
# Default socket file path
|
|
2
7
|
SOCKET_FILE = "/run/dsf/dcs.sock"
|
|
3
8
|
|
|
9
|
+
# Try to read socket file path from config
|
|
10
|
+
config_path = "/opt/dsf/conf/config.json"
|
|
11
|
+
if os.path.exists(config_path):
|
|
12
|
+
try:
|
|
13
|
+
with open(config_path, 'r') as f:
|
|
14
|
+
config = json.load(f)
|
|
15
|
+
socket_dir = config.get("SocketDirectory", "/run/dsf")
|
|
16
|
+
socket_file = config.get("SocketFile", "dcs.sock")
|
|
17
|
+
SOCKET_FILE = os.path.join(socket_dir, socket_file)
|
|
18
|
+
except (json.JSONDecodeError, IOError):
|
|
19
|
+
pass # Use default if config file is invalid or inaccessible
|
|
20
|
+
|
|
4
21
|
# allowed connection per unix server
|
|
5
22
|
DEFAULT_BACKLOG = 4
|
|
6
23
|
|
dsf/commands/code.py
CHANGED
|
@@ -134,7 +134,7 @@ class Code(BaseCommand):
|
|
|
134
134
|
if self.type == CodeType.Comment:
|
|
135
135
|
return "(comment)"
|
|
136
136
|
|
|
137
|
-
prefix = "G53 " if self.
|
|
137
|
+
prefix = "G53 " if self.is_flag_set(CodeFlags.EnforceAbsolutePosition) else ""
|
|
138
138
|
if self.majorNumber is not None:
|
|
139
139
|
if self.minorNumber is not None:
|
|
140
140
|
return f"{prefix}{self.type}{self.majorNumber}.{self.minorNumber}"
|
|
@@ -158,3 +158,6 @@ class Code(BaseCommand):
|
|
|
158
158
|
KeywordType.Echo: "echo",
|
|
159
159
|
KeywordType.Global: "global",
|
|
160
160
|
}.get(self.keyword)
|
|
161
|
+
|
|
162
|
+
def is_flag_set(self, flag: CodeFlags):
|
|
163
|
+
return self.flags & flag != 0
|
|
@@ -28,4 +28,4 @@ def resolve_code(rtype: MessageType, content: Optional[str]):
|
|
|
28
28
|
raise TypeError("rtype must be a MessageType")
|
|
29
29
|
if content is not None and not isinstance(content, str):
|
|
30
30
|
raise TypeError("content must be None or a string")
|
|
31
|
-
return BaseCommand("Resolve", **{"
|
|
31
|
+
return BaseCommand("Resolve", **{"type": rtype, "content": content})
|
dsf/commands/files.py
CHANGED
|
@@ -9,15 +9,16 @@ def get_file_info(file_name: str, read_thumbnail_content: bool = False):
|
|
|
9
9
|
"""
|
|
10
10
|
if not isinstance(file_name, str) or not file_name:
|
|
11
11
|
raise TypeError("file_name must be a string")
|
|
12
|
-
return BaseCommand("GetFileInfo", **{"
|
|
12
|
+
return BaseCommand("GetFileInfo", **{"fileName": file_name, "readThumbnailContent": read_thumbnail_content})
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def resolve_path(path: str):
|
|
15
|
+
def resolve_path(path: str, base_directory: str = None):
|
|
16
16
|
"""
|
|
17
17
|
Resolve a RepRapFirmware-style path to an actual file path
|
|
18
18
|
:param path: Path that is RepRapFirmware-compatible
|
|
19
|
+
:param base_directory: Optional base directory to resolve the path relative to
|
|
19
20
|
:returns: The resolved path
|
|
20
21
|
"""
|
|
21
22
|
if not isinstance(path, str) or not path:
|
|
22
23
|
raise TypeError("path must be a string")
|
|
23
|
-
return BaseCommand("ResolvePath", **{"
|
|
24
|
+
return BaseCommand("ResolvePath", **{"path": path, "baseDirectory": base_directory})
|
dsf/commands/generic.py
CHANGED
|
@@ -17,7 +17,7 @@ def check_password(password: str):
|
|
|
17
17
|
"""
|
|
18
18
|
if not isinstance(password, str) or not password:
|
|
19
19
|
raise TypeError("password must be a string")
|
|
20
|
-
return BaseCommand("CheckPassword", **{"
|
|
20
|
+
return BaseCommand("CheckPassword", **{"password": password})
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def evaluate_expression(channel: CodeChannel, expression: str):
|
|
@@ -33,7 +33,7 @@ def evaluate_expression(channel: CodeChannel, expression: str):
|
|
|
33
33
|
raise TypeError("channel must be a CodeChannel")
|
|
34
34
|
if not isinstance(expression, str) or not expression:
|
|
35
35
|
raise TypeError("expression must be a string")
|
|
36
|
-
return BaseCommand("EvaluateExpression", **{"
|
|
36
|
+
return BaseCommand("EvaluateExpression", **{"channel": channel, "expression": expression})
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def flush(channel: CodeChannel, sync_file_streams: bool = False, if_executing: bool = True):
|
|
@@ -57,7 +57,7 @@ def flush(channel: CodeChannel, sync_file_streams: bool = False, if_executing: b
|
|
|
57
57
|
if not isinstance(if_executing, bool):
|
|
58
58
|
raise TypeError("if_executing must be a boolean")
|
|
59
59
|
return BaseCommand("Flush",
|
|
60
|
-
**{"
|
|
60
|
+
**{"channel": channel, "syncFileStreams": sync_file_streams, "ifExecuting": if_executing})
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def invalidate_channel(channel: CodeChannel):
|
|
@@ -70,7 +70,7 @@ def invalidate_channel(channel: CodeChannel):
|
|
|
70
70
|
"""
|
|
71
71
|
if not isinstance(channel, CodeChannel):
|
|
72
72
|
raise TypeError("channel must be a CodeChannel")
|
|
73
|
-
return BaseCommand("InvalidateChannel", **{"
|
|
73
|
+
return BaseCommand("InvalidateChannel", **{"channel": channel})
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def set_update_status(updating: bool):
|
|
@@ -81,7 +81,7 @@ def set_update_status(updating: bool):
|
|
|
81
81
|
"""
|
|
82
82
|
if not isinstance(updating, bool):
|
|
83
83
|
raise TypeError("updating must be a boolean")
|
|
84
|
-
return BaseCommand("SetUpdateStatus", **{"
|
|
84
|
+
return BaseCommand("SetUpdateStatus", **{"updating": updating})
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def simple_code(code: str, channel: CodeChannel = CodeChannel.DEFAULT_CHANNEL, async_exec: bool = False):
|
|
@@ -101,7 +101,7 @@ def simple_code(code: str, channel: CodeChannel = CodeChannel.DEFAULT_CHANNEL, a
|
|
|
101
101
|
raise TypeError("code must be a string")
|
|
102
102
|
if not isinstance(channel, CodeChannel):
|
|
103
103
|
raise TypeError("channel must be a CodeChannel")
|
|
104
|
-
return BaseCommand("SimpleCode", **{"
|
|
104
|
+
return BaseCommand("SimpleCode", **{"code": code, "channel": channel, "executeAsynchronously": async_exec})
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def write_message(
|
|
@@ -129,9 +129,9 @@ def write_message(
|
|
|
129
129
|
return BaseCommand(
|
|
130
130
|
"WriteMessage",
|
|
131
131
|
**{
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
132
|
+
"type": message_type,
|
|
133
|
+
"content": content,
|
|
134
|
+
"outputMessage": output_message,
|
|
135
|
+
"logLevel": log_level,
|
|
136
136
|
},
|
|
137
137
|
)
|
dsf/commands/http_endpoints.py
CHANGED
|
@@ -25,10 +25,10 @@ def add_http_endpoint(endpoint_type: HttpEndpointType, namespace: str, path: str
|
|
|
25
25
|
return BaseCommand(
|
|
26
26
|
"AddHttpEndpoint",
|
|
27
27
|
**{
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"endpointType": endpoint_type,
|
|
29
|
+
"namespace": namespace,
|
|
30
|
+
"path": path,
|
|
31
|
+
"isUploadRequest": is_upload_request,
|
|
32
32
|
},
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -49,5 +49,5 @@ def remove_http_endpoint(endpoint_type: HttpEndpointType, namespace: str, path:
|
|
|
49
49
|
raise TypeError("path must be a string")
|
|
50
50
|
return BaseCommand(
|
|
51
51
|
"RemoveHttpEndpoint",
|
|
52
|
-
**{"
|
|
52
|
+
**{"endpointType": endpoint_type, "namespace": namespace, "path": path},
|
|
53
53
|
)
|
dsf/commands/object_model.py
CHANGED
|
@@ -24,7 +24,7 @@ def patch_object_model(key: str, patch: str):
|
|
|
24
24
|
raise TypeError("key must be a string")
|
|
25
25
|
if not isinstance(patch, str) or not patch:
|
|
26
26
|
raise TypeError("patch must be a string")
|
|
27
|
-
return BaseCommand("PatchObjectModel", **{"
|
|
27
|
+
return BaseCommand("PatchObjectModel", **{"key": key, "patch": patch})
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def set_network_protocol(protocol: str, enabled: bool):
|
|
@@ -38,7 +38,7 @@ def set_network_protocol(protocol: str, enabled: bool):
|
|
|
38
38
|
raise TypeError("protocol must be a string")
|
|
39
39
|
if not isinstance(enabled, bool):
|
|
40
40
|
raise TypeError("enabled must be a boolean")
|
|
41
|
-
return BaseCommand("SetNetworkProtocol", **{"
|
|
41
|
+
return BaseCommand("SetNetworkProtocol", **{"networkProtocol": protocol, "enabled": enabled})
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def set_object_model(property_path: str, value: str):
|
|
@@ -53,7 +53,7 @@ def set_object_model(property_path: str, value: str):
|
|
|
53
53
|
raise TypeError("property_path must be a string")
|
|
54
54
|
if not isinstance(value, str):
|
|
55
55
|
raise TypeError("value must be a string")
|
|
56
|
-
return BaseCommand("SetObjectModel", **{"
|
|
56
|
+
return BaseCommand("SetObjectModel", **{"propertyPath": property_path, "value": value})
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def sync_object_model():
|
dsf/commands/packages.py
CHANGED
|
@@ -7,7 +7,7 @@ def install_system_package(package_file: str):
|
|
|
7
7
|
"""
|
|
8
8
|
if not isinstance(package_file, str) or not package_file:
|
|
9
9
|
raise TypeError("package_file must be a string")
|
|
10
|
-
return BaseCommand("InstallSystemPackage", **{"
|
|
10
|
+
return BaseCommand("InstallSystemPackage", **{"packageFile": package_file})
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def uninstall_system_package(package: str):
|
|
@@ -16,4 +16,4 @@ def uninstall_system_package(package: str):
|
|
|
16
16
|
"""
|
|
17
17
|
if not isinstance(package, str) or not package:
|
|
18
18
|
raise TypeError("package must be a string")
|
|
19
|
-
return BaseCommand("UninstallSystemPackage", **{"
|
|
19
|
+
return BaseCommand("UninstallSystemPackage", **{"package": package})
|
dsf/commands/plugins.py
CHANGED
|
@@ -8,7 +8,7 @@ def install_plugin(plugin_file: str):
|
|
|
8
8
|
"""
|
|
9
9
|
if not isinstance(plugin_file, str) or not plugin_file:
|
|
10
10
|
raise TypeError("plugin_file must be a string")
|
|
11
|
-
return BaseCommand("InstallPlugin", **{"
|
|
11
|
+
return BaseCommand("InstallPlugin", **{"pluginFile": plugin_file})
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def reload_plugin(plugin: str):
|
|
@@ -18,10 +18,10 @@ def reload_plugin(plugin: str):
|
|
|
18
18
|
"""
|
|
19
19
|
if not isinstance(plugin, str) or not plugin:
|
|
20
20
|
raise TypeError("plugin must be a string")
|
|
21
|
-
return BaseCommand("ReloadPlugin", **{"
|
|
21
|
+
return BaseCommand("ReloadPlugin", **{"plugin": plugin})
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def set_plugin_data(plugin: str, key: str, value
|
|
24
|
+
def set_plugin_data(plugin: str, key: str, value):
|
|
25
25
|
"""
|
|
26
26
|
Update custom plugin data in the object model
|
|
27
27
|
May be used to update only the own plugin data unless the plugin has the ManagePlugins permission.
|
|
@@ -34,10 +34,8 @@ def set_plugin_data(plugin: str, key: str, value: str):
|
|
|
34
34
|
raise TypeError("plugin must be a string")
|
|
35
35
|
if not isinstance(key, str) or not key:
|
|
36
36
|
raise TypeError("key must be a string")
|
|
37
|
-
if not isinstance(value, str):
|
|
38
|
-
raise TypeError("value must be a string")
|
|
39
37
|
return BaseCommand(
|
|
40
|
-
"SetPluginData", **{"
|
|
38
|
+
"SetPluginData", **{"plugin": plugin, "key": key, "value": value}
|
|
41
39
|
)
|
|
42
40
|
|
|
43
41
|
|
|
@@ -49,7 +47,7 @@ def start_plugin(plugin: str, save_state: bool = True):
|
|
|
49
47
|
"""
|
|
50
48
|
if not isinstance(plugin, str) or not plugin:
|
|
51
49
|
raise TypeError("plugin must be a string")
|
|
52
|
-
return BaseCommand("StartPlugin", **{"
|
|
50
|
+
return BaseCommand("StartPlugin", **{"plugin": plugin, "saveState": save_state})
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
def start_plugins():
|
|
@@ -65,7 +63,7 @@ def stop_plugin(plugin: str, save_state: bool = True):
|
|
|
65
63
|
"""
|
|
66
64
|
if not isinstance(plugin, str) or not plugin:
|
|
67
65
|
raise TypeError("plugin must be a string")
|
|
68
|
-
return BaseCommand("StopPlugin", **{"
|
|
66
|
+
return BaseCommand("StopPlugin", **{"plugin": plugin, "saveState": save_state})
|
|
69
67
|
|
|
70
68
|
|
|
71
69
|
def stop_plugins():
|
|
@@ -81,4 +79,4 @@ def uninstall_plugin(plugin: str):
|
|
|
81
79
|
"""
|
|
82
80
|
if not isinstance(plugin, str) or not plugin:
|
|
83
81
|
raise TypeError("plugin must be a string")
|
|
84
|
-
return BaseCommand("UninstallPlugin", **{"
|
|
82
|
+
return BaseCommand("UninstallPlugin", **{"plugin": plugin})
|
dsf/commands/user_sessions.py
CHANGED
|
@@ -19,9 +19,9 @@ def add_user_session(access_level: AccessLevel, session_type: SessionType, origi
|
|
|
19
19
|
return BaseCommand(
|
|
20
20
|
"AddUserSession",
|
|
21
21
|
**{
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"accessLevel": access_level,
|
|
23
|
+
"sessionType": session_type,
|
|
24
|
+
"origin": origin,
|
|
25
25
|
},
|
|
26
26
|
)
|
|
27
27
|
|
|
@@ -33,4 +33,4 @@ def remove_user_session(session_id: int):
|
|
|
33
33
|
"""
|
|
34
34
|
if not isinstance(session_id, int):
|
|
35
35
|
raise TypeError("session_id must be an integer")
|
|
36
|
-
return BaseCommand("RemoveUserSession", **{"
|
|
36
|
+
return BaseCommand("RemoveUserSession", **{"id": session_id})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import socket
|
|
3
|
+
import time
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
from .exceptions import IncompatibleVersionException, InternalServerException, TaskCanceledException
|
|
@@ -25,7 +26,8 @@ class BaseConnection:
|
|
|
25
26
|
|
|
26
27
|
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
27
28
|
self.socket.connect(socket_file)
|
|
28
|
-
self.socket.
|
|
29
|
+
self.socket.settimeout(self.timeout if self.timeout > 0 else None)
|
|
30
|
+
# self.socket.setblocking(True)
|
|
29
31
|
server_init_msg = server_init_message.ServerInitMessage.from_json(
|
|
30
32
|
json.loads(self.socket.recv(50).decode("utf8"))
|
|
31
33
|
)
|
|
@@ -98,7 +100,11 @@ class BaseConnection:
|
|
|
98
100
|
json_string = json_string[:end_index]
|
|
99
101
|
else:
|
|
100
102
|
found = False
|
|
103
|
+
start_time = time.time()
|
|
101
104
|
while not found:
|
|
105
|
+
if (self.timeout > 0) and (time.time() - start_time > self.timeout):
|
|
106
|
+
raise TimeoutError("Timeout while waiting for JSON response")
|
|
107
|
+
|
|
102
108
|
# Refill the buffer and check again
|
|
103
109
|
BUFF_SIZE = 4096 # 4 KiB
|
|
104
110
|
data = b""
|
|
@@ -70,12 +70,12 @@ def intercept_init_message(
|
|
|
70
70
|
return ClientInitMessage(
|
|
71
71
|
ConnectionMode.INTERCEPT,
|
|
72
72
|
**{
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
73
|
+
"interceptionMode": intercept_mode,
|
|
74
|
+
"channels": channels,
|
|
75
|
+
"autoFlush": auto_flush,
|
|
76
|
+
"autoEvaluateExpressions": auto_evaluate_expression,
|
|
77
|
+
"filters": filters,
|
|
78
|
+
"priorityCodes": priority_codes,
|
|
79
79
|
},
|
|
80
80
|
)
|
|
81
81
|
|
|
@@ -90,8 +90,8 @@ def subscribe_init_message(subscription_mode: SubscriptionMode, filter_string: s
|
|
|
90
90
|
return ClientInitMessage(
|
|
91
91
|
ConnectionMode.SUBSCRIBE,
|
|
92
92
|
**{
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
93
|
+
"subscriptionMode": subscription_mode,
|
|
94
|
+
"filter": filter_string,
|
|
95
|
+
"filters": filter_list,
|
|
96
96
|
},
|
|
97
97
|
)
|
|
@@ -37,8 +37,9 @@ class InterceptConnection(BaseCommandConnection):
|
|
|
37
37
|
auto_evaluate_expression: bool = True,
|
|
38
38
|
priority_codes: bool = False,
|
|
39
39
|
debug: bool = False,
|
|
40
|
+
timeout: int = 0
|
|
40
41
|
):
|
|
41
|
-
super().__init__(debug)
|
|
42
|
+
super().__init__(debug, timeout)
|
|
42
43
|
self.interception_mode = interception_mode
|
|
43
44
|
self.channels = channels if channels is not None else CodeChannel.list()
|
|
44
45
|
self.filters = filters
|
dsf/http.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import socket
|
|
4
|
+
import stat
|
|
5
|
+
import errno
|
|
3
6
|
import os
|
|
4
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
5
8
|
from enum import Enum
|
|
@@ -11,10 +14,11 @@ from .object_model import HttpEndpointType
|
|
|
11
14
|
class HttpResponseType(str, Enum):
|
|
12
15
|
"""Enumeration of supported HTTP responses"""
|
|
13
16
|
|
|
14
|
-
StatusCode = "
|
|
15
|
-
PlainText = "
|
|
16
|
-
JSON = "
|
|
17
|
-
File = "
|
|
17
|
+
StatusCode = "statuscode"
|
|
18
|
+
PlainText = "plainText"
|
|
19
|
+
JSON = "json"
|
|
20
|
+
File = "file"
|
|
21
|
+
URI = "uri"
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class ReceivedHttpRequest:
|
|
@@ -47,7 +51,7 @@ class HttpEndpointConnection:
|
|
|
47
51
|
"""Close the connection"""
|
|
48
52
|
self.writer.close()
|
|
49
53
|
|
|
50
|
-
async def read_request(self):
|
|
54
|
+
async def read_request(self) -> ReceivedHttpRequest:
|
|
51
55
|
"""
|
|
52
56
|
Read information about the last HTTP request.
|
|
53
57
|
Note that a call to this method may fail!
|
|
@@ -67,9 +71,9 @@ class HttpEndpointConnection:
|
|
|
67
71
|
try:
|
|
68
72
|
await self.send(
|
|
69
73
|
{
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
74
|
+
"statusCode": status_code,
|
|
75
|
+
"response": response,
|
|
76
|
+
"responseType": response_type,
|
|
73
77
|
}
|
|
74
78
|
)
|
|
75
79
|
finally:
|
|
@@ -136,7 +140,8 @@ class HttpEndpointUnixSocket:
|
|
|
136
140
|
if self._loop is not None:
|
|
137
141
|
# TODO: this enables correctly ending the loop. Why?
|
|
138
142
|
self._loop.set_debug(True)
|
|
139
|
-
self._server
|
|
143
|
+
if self._server is not None:
|
|
144
|
+
self._server.close()
|
|
140
145
|
self._loop.stop()
|
|
141
146
|
self.event_loop.cancel()
|
|
142
147
|
self.executor.shutdown(wait=False)
|
|
@@ -149,11 +154,47 @@ class HttpEndpointUnixSocket:
|
|
|
149
154
|
"""Set the handler to handle client connections"""
|
|
150
155
|
self.handler = handler
|
|
151
156
|
|
|
157
|
+
def _create_socket(self, path: str):
|
|
158
|
+
path = os.fspath(path)
|
|
159
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
160
|
+
|
|
161
|
+
# Check for abstract socket. `str` and `bytes` paths are supported.
|
|
162
|
+
if path[0] not in (0, '\x00'):
|
|
163
|
+
try:
|
|
164
|
+
if stat.S_ISSOCK(os.stat(path).st_mode):
|
|
165
|
+
os.remove(path)
|
|
166
|
+
except FileNotFoundError:
|
|
167
|
+
pass
|
|
168
|
+
except OSError as err:
|
|
169
|
+
# Directory may have permissions only to create socket.
|
|
170
|
+
# logger.error('Unable to check or remove stale UNIX socket '
|
|
171
|
+
# '%r: %r', path, err)
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
sock.bind(path)
|
|
176
|
+
os.chmod(path, os.stat(path).st_mode | stat.S_IWGRP | stat.S_IRGRP)
|
|
177
|
+
except OSError as exc:
|
|
178
|
+
sock.close()
|
|
179
|
+
if exc.errno == errno.EADDRINUSE:
|
|
180
|
+
# Let's improve the error message by adding
|
|
181
|
+
# with what exact address it occurs.
|
|
182
|
+
msg = f'Address {path!r} is already in use'
|
|
183
|
+
raise OSError(errno.EADDRINUSE, msg) from None
|
|
184
|
+
else:
|
|
185
|
+
raise
|
|
186
|
+
except:
|
|
187
|
+
sock.close()
|
|
188
|
+
raise
|
|
189
|
+
|
|
190
|
+
return sock
|
|
191
|
+
|
|
152
192
|
def start_connection_listener(self):
|
|
153
193
|
try:
|
|
154
194
|
self._loop = asyncio.new_event_loop()
|
|
195
|
+
sock = self._create_socket(self.socket_file)
|
|
155
196
|
self._server = asyncio.start_unix_server(
|
|
156
|
-
self.handle_connection,
|
|
197
|
+
self.handle_connection, sock=sock, backlog=self.backlog
|
|
157
198
|
)
|
|
158
199
|
self._loop.create_task(self._server)
|
|
159
200
|
self._loop.run_forever()
|
dsf/object_model/heat/heater.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import List, Union
|
|
3
3
|
|
|
4
4
|
from .heater_model import HeaterModel
|
|
5
5
|
from .heater_monitor import HeaterMonitor
|
|
@@ -39,6 +39,10 @@ class Heater(ModelObject):
|
|
|
39
39
|
self._avg_pwm = 0
|
|
40
40
|
# Current temperature of the heater (in C)
|
|
41
41
|
self._current = -273.15
|
|
42
|
+
# Current feedforward PWM boost applied to the heater
|
|
43
|
+
self._extr_pwm_boost = None
|
|
44
|
+
# Current temperature boost applied to the heater
|
|
45
|
+
self._extr_temp_boost = None
|
|
42
46
|
# Maximum temperature allowed for this heater (in C)
|
|
43
47
|
# This is only temporary and should be replaced by a representation of the heater protection as in RRF
|
|
44
48
|
self._max = 285
|
|
@@ -89,6 +93,24 @@ class Heater(ModelObject):
|
|
|
89
93
|
def current(self, value: float):
|
|
90
94
|
self._current = float(value)
|
|
91
95
|
|
|
96
|
+
@property
|
|
97
|
+
def extr_pwm_boost(self) -> Union[float, None]:
|
|
98
|
+
"""Current feedforward PWM boost applied to the heater"""
|
|
99
|
+
return self._extr_pwm_boost
|
|
100
|
+
|
|
101
|
+
@extr_pwm_boost.setter
|
|
102
|
+
def extr_pwm_boost(self, value: Union[float, None]):
|
|
103
|
+
self._extr_pwm_boost = float(value) if value is not None else None
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def extr_temp_boost(self) -> Union[float, None]:
|
|
107
|
+
"""Current temperature boost applied to the heater"""
|
|
108
|
+
return self._extr_temp_boost
|
|
109
|
+
|
|
110
|
+
@extr_temp_boost.setter
|
|
111
|
+
def extr_temp_boost(self, value: Union[float, None]):
|
|
112
|
+
self._extr_temp_boost = float(value) if value is not None else None
|
|
113
|
+
|
|
92
114
|
@property
|
|
93
115
|
def max(self) -> float:
|
|
94
116
|
"""Maximum temperature allowed for this heater (in C)
|
|
@@ -11,6 +11,7 @@ class GCodeFileInfo(ModelObject):
|
|
|
11
11
|
|
|
12
12
|
def __init__(self):
|
|
13
13
|
super().__init__()
|
|
14
|
+
self._custom_info = {}
|
|
14
15
|
self._filament = []
|
|
15
16
|
self._file_name = ""
|
|
16
17
|
self._generated_by = ""
|
|
@@ -23,6 +24,11 @@ class GCodeFileInfo(ModelObject):
|
|
|
23
24
|
self._size = 0
|
|
24
25
|
self._thumbnails = ModelCollection(ThumbnailInfo)
|
|
25
26
|
|
|
27
|
+
@property
|
|
28
|
+
def custom_info(self) -> dict:
|
|
29
|
+
"""Custom information extracted from the G-code file"""
|
|
30
|
+
return self._custom_info
|
|
31
|
+
|
|
26
32
|
@property
|
|
27
33
|
def filament(self) -> List[float]:
|
|
28
34
|
"""Filament consumption per extruder drive (in mm)"""
|
|
@@ -14,6 +14,8 @@ class TimesLeft(ModelObject):
|
|
|
14
14
|
self._file = None
|
|
15
15
|
# Time left based on the slicer reports (see M73, in s or null)
|
|
16
16
|
self._slicer = None
|
|
17
|
+
# Time left before the next colour change is expected (see M73 C, in s or null)
|
|
18
|
+
self._to_pause = None
|
|
17
19
|
|
|
18
20
|
@property
|
|
19
21
|
def filament(self) -> Union[int, None]:
|
|
@@ -41,3 +43,12 @@ class TimesLeft(ModelObject):
|
|
|
41
43
|
@slicer.setter
|
|
42
44
|
def slicer(self, value):
|
|
43
45
|
self._slicer = int(value) if value is not None else None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def to_pause(self) -> Union[int, None]:
|
|
49
|
+
"""Time left before the next colour change is expected (see M73 C, in s or null)"""
|
|
50
|
+
return self._to_pause
|
|
51
|
+
|
|
52
|
+
@to_pause.setter
|
|
53
|
+
def to_pause(self, value):
|
|
54
|
+
self._to_pause = int(value) if value is not None else None
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Type, List, Dict, Any, Union, Optional
|
|
1
2
|
from .utils import is_model_object
|
|
2
3
|
|
|
4
|
+
T = TypeVar('T')
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
|
|
7
|
+
class ModelCollection(Generic[T], list):
|
|
5
8
|
"""
|
|
6
9
|
Class for storing model object items in a list
|
|
7
10
|
Useful for updating model object items from JSON data (patches)
|
|
8
11
|
"""
|
|
9
12
|
|
|
10
|
-
def __init__(self, item_constructor, value=None):
|
|
13
|
+
def __init__(self, item_constructor: Type[T], value: Optional[List[T]] = None) -> None:
|
|
11
14
|
"""
|
|
12
|
-
|
|
13
15
|
:param item_constructor: Item constructor type that items must derive from
|
|
14
16
|
:param value: Value used to initialize the list from
|
|
15
17
|
"""
|
|
16
18
|
super().__init__()
|
|
17
|
-
self._item_constructor = item_constructor
|
|
19
|
+
self._item_constructor: Type[T] = item_constructor
|
|
18
20
|
|
|
19
21
|
if value is not None:
|
|
20
|
-
self[:] =
|
|
22
|
+
self[:] = []
|
|
23
|
+
for (i, item) in enumerate(value):
|
|
24
|
+
if isinstance(item, self._item_constructor):
|
|
25
|
+
self.append(item)
|
|
26
|
+
else:
|
|
27
|
+
ref_item = self._item_constructor()
|
|
28
|
+
ref_item.update_from_json(item)
|
|
29
|
+
self.append(ref_item)
|
|
21
30
|
|
|
22
|
-
def update_from_json(self, json_element):
|
|
31
|
+
def update_from_json(self, json_element: List[Any]) -> 'ModelCollection[T]':
|
|
23
32
|
"""
|
|
24
33
|
Update this instance from the given data
|
|
25
34
|
:param json_element: JSON data to upgrade this instance from
|
dsf/object_model/move/axis.py
CHANGED
|
@@ -86,6 +86,8 @@ class Axis(ModelObject):
|
|
|
86
86
|
self._percent_current = 100
|
|
87
87
|
# Percentage applied to the motor current during standstill (0..100 or None if not supported)
|
|
88
88
|
self._percent_stst_current = None
|
|
89
|
+
# Motor jerk during the current print only (in mm/s)
|
|
90
|
+
self._printing_jerk = None
|
|
89
91
|
# Reduced accelerations used by Z probing and stall homing moves (in mm/s^2)
|
|
90
92
|
self._reduced_acceleration = 0
|
|
91
93
|
# Maximum speed (in mm/min)
|
|
@@ -242,6 +244,15 @@ class Axis(ModelObject):
|
|
|
242
244
|
def percent_stst_current(self, value):
|
|
243
245
|
self._percent_stst_current = int(value) if value is not None else None
|
|
244
246
|
|
|
247
|
+
@property
|
|
248
|
+
def printing_jerk(self) -> Union[float, None]:
|
|
249
|
+
"""Motor jerk during the current print only (in mm/s)"""
|
|
250
|
+
return self._printing_jerk
|
|
251
|
+
|
|
252
|
+
@printing_jerk.setter
|
|
253
|
+
def printing_jerk(self, value):
|
|
254
|
+
self._printing_jerk = float(value) if value is not None else None
|
|
255
|
+
|
|
245
256
|
@property
|
|
246
257
|
def reduced_acceleration(self) -> float:
|
|
247
258
|
"""Reduced accelerations used by Z probing and stall homing moves (in mm/s^2)"""
|
|
@@ -41,6 +41,8 @@ class Extruder(ModelObject):
|
|
|
41
41
|
self._position = 0
|
|
42
42
|
# Pressure advance
|
|
43
43
|
self._pressure_advance = 0
|
|
44
|
+
# Motor jerk during the current print only (in mm/s)
|
|
45
|
+
self._printing_jerk = None
|
|
44
46
|
# Raw extruder position as commanded by the slicer without extrusion factor applied (in mm)
|
|
45
47
|
self._raw_position = 0
|
|
46
48
|
# Maximum speed (in mm/s)
|
|
@@ -148,6 +150,15 @@ class Extruder(ModelObject):
|
|
|
148
150
|
def pressure_advance(self, value):
|
|
149
151
|
self._pressure_advance = float(value)
|
|
150
152
|
|
|
153
|
+
@property
|
|
154
|
+
def printing_jerk(self) -> Union[float, None]:
|
|
155
|
+
"""Motor jerk during the current print only (in mm/s)"""
|
|
156
|
+
return self._printing_jerk
|
|
157
|
+
|
|
158
|
+
@printing_jerk.setter
|
|
159
|
+
def printing_jerk(self, value):
|
|
160
|
+
self._printing_jerk = float(value) if value is not None else None
|
|
161
|
+
|
|
151
162
|
@property
|
|
152
163
|
def raw_position(self) -> float:
|
|
153
164
|
"""Raw extruder position as commanded by the slicer without extrusion factor applied (in mm)"""
|