raydriver 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {raydriver-0.2.0 → raydriver-0.3.0}/Cargo.lock +1 -1
- {raydriver-0.2.0 → raydriver-0.3.0}/Cargo.toml +1 -1
- {raydriver-0.2.0 → raydriver-0.3.0}/PKG-INFO +1 -1
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/parser/__init__.pyi +6 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/types/__init__.pyi +19 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/parser.rs +6 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/grbl/parser.rs +9 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/grbl/types.rs +77 -2
- raydriver-0.2.0/tests/test_parser.py → raydriver-0.3.0/tests/test_grbl_parser.py +11 -0
- raydriver-0.3.0/tests/test_grbl_types.py +171 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/.github/workflows/ci.yml +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/.github/workflows/release.yml +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/.gitignore +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/.pre-commit-config.yaml +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/AGENTS.md +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/Cargo.toml.bak +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/LICENSE +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/Makefile +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/README.md +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/env.sh +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/freeze-dump.txt +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/pyproject.toml +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/__init__.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/__init__.pyi +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/cli/__init__.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/cli/main.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/emulator.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/__init__.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/__init__.pyi +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/parser/__init__.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/parser/py.typed +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/py.typed +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/types/__init__.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/grbl/types/py.typed +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/python/raydriver/py.typed +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/rustfmt.toml +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/bin/stub_gen.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/dialect.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/errors.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/flow.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/mod.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/session.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/transport/mock.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/transport/mod.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/transport/serial.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/transport/telnet.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/grbl/types.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/lib.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/events.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/grbl/mock.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/grbl/mod.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/grbl/session.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/mod.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/pyfuture.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/src/python/runtime.rs +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/tests/conftest.py +0 -0
- {raydriver-0.2.0 → raydriver-0.3.0}/tests/test_cli.py +0 -0
- /raydriver-0.2.0/tests/test_session.py → /raydriver-0.3.0/tests/test_grbl_session.py +0 -0
- /raydriver-0.2.0/tests/test_streaming.py → /raydriver-0.3.0/tests/test_grbl_streaming.py +0 -0
- /raydriver-0.2.0/tests/test_stress.py → /raydriver-0.3.0/tests/test_grbl_stress.py +0 -0
|
@@ -19,6 +19,7 @@ __all__ = [
|
|
|
19
19
|
"parse_grbl_parser_state",
|
|
20
20
|
"parse_grbl_settings",
|
|
21
21
|
"parse_msg",
|
|
22
|
+
"parse_opt_flags",
|
|
22
23
|
"parse_opt_info",
|
|
23
24
|
"parse_probe_line",
|
|
24
25
|
"parse_setting_pairs",
|
|
@@ -87,6 +88,11 @@ def parse_msg(line: builtins.str) -> typing.Optional[tuple[builtins.str, builtin
|
|
|
87
88
|
Parse a `[MSG:key:value]` line into `(key, value)`.
|
|
88
89
|
"""
|
|
89
90
|
|
|
91
|
+
def parse_opt_flags(line: builtins.str) -> typing.Optional[builtins.str]:
|
|
92
|
+
r"""
|
|
93
|
+
Extract the compile option flags from an `[OPT:...]` line.
|
|
94
|
+
"""
|
|
95
|
+
|
|
90
96
|
def parse_opt_info(line: builtins.str) -> typing.Optional[builtins.int]:
|
|
91
97
|
r"""
|
|
92
98
|
Extract the RX buffer size from an `[OPT:...]` line.
|
|
@@ -25,6 +25,7 @@ class DeviceError:
|
|
|
25
25
|
@property
|
|
26
26
|
def description(self) -> builtins.str: ...
|
|
27
27
|
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
|
28
|
+
def __new__(cls, code: builtins.int, title: builtins.str, description: builtins.str) -> DeviceError: ...
|
|
28
29
|
def __repr__(self) -> builtins.str: ...
|
|
29
30
|
|
|
30
31
|
@typing.final
|
|
@@ -34,31 +35,49 @@ class DeviceState:
|
|
|
34
35
|
"""
|
|
35
36
|
@property
|
|
36
37
|
def status(self) -> DeviceStatus: ...
|
|
38
|
+
@status.setter
|
|
39
|
+
def status(self, value: DeviceStatus) -> None: ...
|
|
37
40
|
@property
|
|
38
41
|
def error(self) -> typing.Optional[DeviceError]: ...
|
|
42
|
+
@error.setter
|
|
43
|
+
def error(self, value: typing.Optional[DeviceError]) -> None: ...
|
|
39
44
|
@property
|
|
40
45
|
def machine_pos(self) -> builtins.list[typing.Optional[builtins.float]]:
|
|
41
46
|
r"""
|
|
42
47
|
Machine position in mm; entries may be `None`.
|
|
43
48
|
"""
|
|
49
|
+
@machine_pos.setter
|
|
50
|
+
def machine_pos(self, value: typing.Sequence[typing.Optional[builtins.float]]) -> None: ...
|
|
44
51
|
@property
|
|
45
52
|
def work_pos(self) -> builtins.list[typing.Optional[builtins.float]]:
|
|
46
53
|
r"""
|
|
47
54
|
Work position in mm; entries may be `None`.
|
|
48
55
|
"""
|
|
56
|
+
@work_pos.setter
|
|
57
|
+
def work_pos(self, value: typing.Sequence[typing.Optional[builtins.float]]) -> None: ...
|
|
49
58
|
@property
|
|
50
59
|
def wco(self) -> builtins.list[typing.Optional[builtins.float]]:
|
|
51
60
|
r"""
|
|
52
61
|
Work coordinate offset in mm; entries may be `None`.
|
|
53
62
|
"""
|
|
63
|
+
@wco.setter
|
|
64
|
+
def wco(self, value: typing.Sequence[typing.Optional[builtins.float]]) -> None: ...
|
|
54
65
|
@property
|
|
55
66
|
def feed_rate(self) -> typing.Optional[builtins.int]: ...
|
|
67
|
+
@feed_rate.setter
|
|
68
|
+
def feed_rate(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
56
69
|
@property
|
|
57
70
|
def spindle_speed(self) -> typing.Optional[builtins.int]: ...
|
|
71
|
+
@spindle_speed.setter
|
|
72
|
+
def spindle_speed(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
58
73
|
@property
|
|
59
74
|
def buffer_available(self) -> typing.Optional[builtins.int]: ...
|
|
75
|
+
@buffer_available.setter
|
|
76
|
+
def buffer_available(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
60
77
|
@property
|
|
61
78
|
def buffer_rx_available(self) -> typing.Optional[builtins.int]: ...
|
|
79
|
+
@buffer_rx_available.setter
|
|
80
|
+
def buffer_rx_available(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
62
81
|
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
|
63
82
|
def __new__(cls) -> DeviceState: ...
|
|
64
83
|
def __repr__(self) -> builtins.str: ...
|
|
@@ -262,6 +262,12 @@ pub fn parse_opt_info(line: &str) -> Option<i64> {
|
|
|
262
262
|
.and_then(|caps| caps[3].parse::<i64>().ok())
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
/// Extract the compile option flags from an
|
|
266
|
+
/// `[OPT:<flags>,<planner_buffer_blocks>,<rx_buffer_size>]` line.
|
|
267
|
+
pub fn parse_opt_flags(line: &str) -> Option<String> {
|
|
268
|
+
GRBL_OPT_RE.captures(line).map(|caps| caps[1].to_string())
|
|
269
|
+
}
|
|
270
|
+
|
|
265
271
|
/// Parses the response from a '$G' command to find the active WCS.
|
|
266
272
|
/// Example response: `[G54 G17 G21 G90 G94 M5 M9 T0 F0 S0]`
|
|
267
273
|
pub fn parse_grbl_parser_state(response_lines: &[String]) -> Option<String> {
|
|
@@ -60,6 +60,13 @@ fn parse_opt_info(line: &str) -> Option<i64> {
|
|
|
60
60
|
parser::parse_opt_info(line)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/// Extract the compile option flags from an `[OPT:...]` line.
|
|
64
|
+
#[gen_stub_pyfunction(module = "raydriver.grbl.parser")]
|
|
65
|
+
#[pyfunction]
|
|
66
|
+
fn parse_opt_flags(line: &str) -> Option<String> {
|
|
67
|
+
parser::parse_opt_flags(line)
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
/// Parse a `[MSG:key:value]` line into `(key, value)`.
|
|
64
71
|
#[gen_stub_pyfunction(module = "raydriver.grbl.parser")]
|
|
65
72
|
#[pyfunction]
|
|
@@ -202,6 +209,7 @@ pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
202
209
|
"parse_version",
|
|
203
210
|
"parse_ver",
|
|
204
211
|
"parse_opt_info",
|
|
212
|
+
"parse_opt_flags",
|
|
205
213
|
"parse_msg",
|
|
206
214
|
"extract_device_name",
|
|
207
215
|
"extract_device_name_from_output",
|
|
@@ -225,6 +233,7 @@ pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
225
233
|
m.add_function(pyo3::wrap_pyfunction!(parse_version, m.clone())?)?;
|
|
226
234
|
m.add_function(pyo3::wrap_pyfunction!(parse_ver, m.clone())?)?;
|
|
227
235
|
m.add_function(pyo3::wrap_pyfunction!(parse_opt_info, m.clone())?)?;
|
|
236
|
+
m.add_function(pyo3::wrap_pyfunction!(parse_opt_flags, m.clone())?)?;
|
|
228
237
|
m.add_function(pyo3::wrap_pyfunction!(parse_msg, m.clone())?)?;
|
|
229
238
|
m.add_function(pyo3::wrap_pyfunction!(extract_device_name, m.clone())?)?;
|
|
230
239
|
m.add_function(pyo3::wrap_pyfunction!(
|
|
@@ -18,7 +18,7 @@ pyo3_stub_gen::module_doc!("raydriver.grbl.types", "{}", MODULE_DOC);
|
|
|
18
18
|
|
|
19
19
|
/// Machine state as reported in Grbl status reports.
|
|
20
20
|
#[gen_stub_pyclass_enum]
|
|
21
|
-
#[pyclass(module = "raydriver.grbl.types", eq, eq_int,
|
|
21
|
+
#[pyclass(module = "raydriver.grbl.types", eq, eq_int, from_py_object)]
|
|
22
22
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
23
23
|
pub enum DeviceStatus {
|
|
24
24
|
#[pyo3(name = "UNKNOWN")]
|
|
@@ -78,6 +78,29 @@ impl From<core::DeviceStatus> for DeviceStatus {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
impl From<DeviceStatus> for core::DeviceStatus {
|
|
82
|
+
fn from(status: DeviceStatus) -> Self {
|
|
83
|
+
match status {
|
|
84
|
+
DeviceStatus::Unknown => Self::Unknown,
|
|
85
|
+
DeviceStatus::Idle => Self::Idle,
|
|
86
|
+
DeviceStatus::Run => Self::Run,
|
|
87
|
+
DeviceStatus::Hold => Self::Hold,
|
|
88
|
+
DeviceStatus::Jog => Self::Jog,
|
|
89
|
+
DeviceStatus::Alarm => Self::Alarm,
|
|
90
|
+
DeviceStatus::Door => Self::Door,
|
|
91
|
+
DeviceStatus::Check => Self::Check,
|
|
92
|
+
DeviceStatus::Home => Self::Home,
|
|
93
|
+
DeviceStatus::Sleep => Self::Sleep,
|
|
94
|
+
DeviceStatus::Tool => Self::Tool,
|
|
95
|
+
DeviceStatus::Queue => Self::Queue,
|
|
96
|
+
DeviceStatus::Lock => Self::Lock,
|
|
97
|
+
DeviceStatus::Unlock => Self::Unlock,
|
|
98
|
+
DeviceStatus::Cycle => Self::Cycle,
|
|
99
|
+
DeviceStatus::Test => Self::Test,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
#[gen_stub_pymethods]
|
|
82
105
|
#[pymethods]
|
|
83
106
|
impl DeviceStatus {
|
|
@@ -110,7 +133,7 @@ impl DeviceStatus {
|
|
|
110
133
|
|
|
111
134
|
/// An error or alarm with code, title and description.
|
|
112
135
|
#[gen_stub_pyclass]
|
|
113
|
-
#[pyclass(module = "raydriver.grbl.types", eq,
|
|
136
|
+
#[pyclass(module = "raydriver.grbl.types", eq, from_py_object)]
|
|
114
137
|
#[derive(Clone, PartialEq)]
|
|
115
138
|
pub struct DeviceError {
|
|
116
139
|
pub(crate) inner: core::DeviceError,
|
|
@@ -125,6 +148,13 @@ impl DeviceError {
|
|
|
125
148
|
#[gen_stub_pymethods]
|
|
126
149
|
#[pymethods]
|
|
127
150
|
impl DeviceError {
|
|
151
|
+
#[new]
|
|
152
|
+
fn new_py(code: i32, title: String, description: String) -> Self {
|
|
153
|
+
Self {
|
|
154
|
+
inner: core::DeviceError::new(code, &title, &description),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
128
158
|
#[getter]
|
|
129
159
|
fn code(&self) -> i32 {
|
|
130
160
|
self.inner.code
|
|
@@ -174,49 +204,94 @@ impl DeviceState {
|
|
|
174
204
|
self.inner.status.into()
|
|
175
205
|
}
|
|
176
206
|
|
|
207
|
+
#[setter]
|
|
208
|
+
fn set_status(&mut self, status: DeviceStatus) {
|
|
209
|
+
self.inner.status = status.into();
|
|
210
|
+
}
|
|
211
|
+
|
|
177
212
|
#[getter]
|
|
178
213
|
fn error(&self) -> Option<DeviceError> {
|
|
179
214
|
self.inner.error.clone().map(DeviceError::new)
|
|
180
215
|
}
|
|
181
216
|
|
|
217
|
+
#[setter]
|
|
218
|
+
fn set_error(&mut self, error: Option<&DeviceError>) {
|
|
219
|
+
self.inner.error = error.map(|e| e.inner.clone());
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
/// Machine position in mm; entries may be `None`.
|
|
183
223
|
#[getter]
|
|
184
224
|
fn machine_pos(&self) -> Vec<Option<f64>> {
|
|
185
225
|
self.inner.machine_pos.clone()
|
|
186
226
|
}
|
|
187
227
|
|
|
228
|
+
#[setter]
|
|
229
|
+
fn set_machine_pos(&mut self, pos: Vec<Option<f64>>) {
|
|
230
|
+
self.inner.machine_pos = pos;
|
|
231
|
+
}
|
|
232
|
+
|
|
188
233
|
/// Work position in mm; entries may be `None`.
|
|
189
234
|
#[getter]
|
|
190
235
|
fn work_pos(&self) -> Vec<Option<f64>> {
|
|
191
236
|
self.inner.work_pos.clone()
|
|
192
237
|
}
|
|
193
238
|
|
|
239
|
+
#[setter]
|
|
240
|
+
fn set_work_pos(&mut self, pos: Vec<Option<f64>>) {
|
|
241
|
+
self.inner.work_pos = pos;
|
|
242
|
+
}
|
|
243
|
+
|
|
194
244
|
/// Work coordinate offset in mm; entries may be `None`.
|
|
195
245
|
#[getter]
|
|
196
246
|
fn wco(&self) -> Vec<Option<f64>> {
|
|
197
247
|
self.inner.wco.clone()
|
|
198
248
|
}
|
|
199
249
|
|
|
250
|
+
#[setter]
|
|
251
|
+
fn set_wco(&mut self, wco: Vec<Option<f64>>) {
|
|
252
|
+
self.inner.wco = wco;
|
|
253
|
+
}
|
|
254
|
+
|
|
200
255
|
#[getter]
|
|
201
256
|
fn feed_rate(&self) -> Option<i64> {
|
|
202
257
|
self.inner.feed_rate
|
|
203
258
|
}
|
|
204
259
|
|
|
260
|
+
#[setter]
|
|
261
|
+
fn set_feed_rate(&mut self, feed_rate: Option<i64>) {
|
|
262
|
+
self.inner.feed_rate = feed_rate;
|
|
263
|
+
}
|
|
264
|
+
|
|
205
265
|
#[getter]
|
|
206
266
|
fn spindle_speed(&self) -> Option<i64> {
|
|
207
267
|
self.inner.spindle_speed
|
|
208
268
|
}
|
|
209
269
|
|
|
270
|
+
#[setter]
|
|
271
|
+
fn set_spindle_speed(&mut self, spindle_speed: Option<i64>) {
|
|
272
|
+
self.inner.spindle_speed = spindle_speed;
|
|
273
|
+
}
|
|
274
|
+
|
|
210
275
|
#[getter]
|
|
211
276
|
fn buffer_available(&self) -> Option<i64> {
|
|
212
277
|
self.inner.buffer_available
|
|
213
278
|
}
|
|
214
279
|
|
|
280
|
+
#[setter]
|
|
281
|
+
fn set_buffer_available(&mut self, buffer_available: Option<i64>) {
|
|
282
|
+
self.inner.buffer_available = buffer_available;
|
|
283
|
+
}
|
|
284
|
+
|
|
215
285
|
#[getter]
|
|
216
286
|
fn buffer_rx_available(&self) -> Option<i64> {
|
|
217
287
|
self.inner.buffer_rx_available
|
|
218
288
|
}
|
|
219
289
|
|
|
290
|
+
#[setter]
|
|
291
|
+
fn set_buffer_rx_available(&mut self, buffer_rx_available: Option<i64>) {
|
|
292
|
+
self.inner.buffer_rx_available = buffer_rx_available;
|
|
293
|
+
}
|
|
294
|
+
|
|
220
295
|
fn __repr__(&self) -> String {
|
|
221
296
|
format!(
|
|
222
297
|
"DeviceState(status={}, feed_rate={:?})",
|
|
@@ -14,6 +14,7 @@ from raydriver.grbl.parser import (
|
|
|
14
14
|
parse_grbl_parser_state,
|
|
15
15
|
parse_grbl_settings,
|
|
16
16
|
parse_msg,
|
|
17
|
+
parse_opt_flags,
|
|
17
18
|
parse_opt_info,
|
|
18
19
|
parse_probe_line,
|
|
19
20
|
parse_setting_pairs,
|
|
@@ -336,6 +337,16 @@ class TestOptInfo:
|
|
|
336
337
|
assert parse_opt_info("ok") is None
|
|
337
338
|
|
|
338
339
|
|
|
340
|
+
class TestOptFlags:
|
|
341
|
+
def test_valid(self):
|
|
342
|
+
assert parse_opt_flags("[OPT:V,15,127]") == "V"
|
|
343
|
+
assert parse_opt_flags("[OPT:VMPH,63,511]") == "VMPH"
|
|
344
|
+
|
|
345
|
+
def test_invalid(self):
|
|
346
|
+
assert parse_opt_flags("[VER:1.1h:]") is None
|
|
347
|
+
assert parse_opt_flags("ok") is None
|
|
348
|
+
|
|
349
|
+
|
|
339
350
|
class TestParseMsg:
|
|
340
351
|
def test_valid(self):
|
|
341
352
|
assert parse_msg("[MSG:machine:Ortur]") == ("machine", "Ortur")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Tests for the shared value types: DeviceState, DeviceStatus and
|
|
2
|
+
DeviceError.
|
|
3
|
+
|
|
4
|
+
Covers the Python-facing construction surface (setters and the
|
|
5
|
+
DeviceError constructor) and the merge/round-trip contract that
|
|
6
|
+
embedders rely on when they keep their own state object and feed it
|
|
7
|
+
back to ``parse_state`` as the default.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from raydriver.grbl.parser import parse_state
|
|
11
|
+
from raydriver.grbl.types import DeviceError, DeviceState, DeviceStatus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_state(**overrides) -> DeviceState:
|
|
15
|
+
"""A fully populated DeviceState, built through the Python
|
|
16
|
+
setters, with optional per-field overrides."""
|
|
17
|
+
state = DeviceState()
|
|
18
|
+
state.status = overrides.get("status", DeviceStatus.RUN)
|
|
19
|
+
state.error = overrides.get("error", None)
|
|
20
|
+
state.machine_pos = overrides.get("machine_pos", [11.0, 22.0, 33.0])
|
|
21
|
+
state.work_pos = overrides.get("work_pos", [10.0, 20.0, 30.0])
|
|
22
|
+
state.wco = overrides.get("wco", [1.0, 2.0, 3.0])
|
|
23
|
+
state.feed_rate = overrides.get("feed_rate", 500)
|
|
24
|
+
state.spindle_speed = overrides.get("spindle_speed", 1000)
|
|
25
|
+
state.buffer_available = overrides.get("buffer_available", 62)
|
|
26
|
+
state.buffer_rx_available = overrides.get("buffer_rx_available", 100)
|
|
27
|
+
return state
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestDeviceStateDefaults:
|
|
31
|
+
def test_fresh_state_defaults(self):
|
|
32
|
+
state = DeviceState()
|
|
33
|
+
assert state.status == DeviceStatus.UNKNOWN
|
|
34
|
+
assert state.error is None
|
|
35
|
+
assert state.machine_pos == [None, None, None]
|
|
36
|
+
assert state.work_pos == [None, None, None]
|
|
37
|
+
assert state.wco == [0.0, 0.0, 0.0]
|
|
38
|
+
assert state.feed_rate is None
|
|
39
|
+
assert state.spindle_speed is None
|
|
40
|
+
assert state.buffer_available is None
|
|
41
|
+
assert state.buffer_rx_available is None
|
|
42
|
+
|
|
43
|
+
def test_status_name(self):
|
|
44
|
+
assert DeviceStatus.ALARM.name == "ALARM"
|
|
45
|
+
assert parse_state("<Idle|MPos:1,2,3>").status.name == "IDLE"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestDeviceStateSetters:
|
|
49
|
+
def test_set_all_fields(self):
|
|
50
|
+
state = build_state()
|
|
51
|
+
assert state.status == DeviceStatus.RUN
|
|
52
|
+
assert state.machine_pos == [11.0, 22.0, 33.0]
|
|
53
|
+
assert state.work_pos == [10.0, 20.0, 30.0]
|
|
54
|
+
assert state.wco == [1.0, 2.0, 3.0]
|
|
55
|
+
assert state.feed_rate == 500
|
|
56
|
+
assert state.spindle_speed == 1000
|
|
57
|
+
assert state.buffer_available == 62
|
|
58
|
+
assert state.buffer_rx_available == 100
|
|
59
|
+
|
|
60
|
+
def test_set_error(self):
|
|
61
|
+
state = DeviceState()
|
|
62
|
+
error = DeviceError(20, "Unsupported Command", "details")
|
|
63
|
+
state.error = error
|
|
64
|
+
assert state.error == error
|
|
65
|
+
assert state.error.code == 20
|
|
66
|
+
assert state.error.title == "Unsupported Command"
|
|
67
|
+
assert state.error.description == "details"
|
|
68
|
+
|
|
69
|
+
def test_clear_error_with_none(self):
|
|
70
|
+
state = build_state(error=DeviceError(1, "t", "d"))
|
|
71
|
+
state.error = None
|
|
72
|
+
assert state.error is None
|
|
73
|
+
|
|
74
|
+
def test_unknown_entries_survive_setter(self):
|
|
75
|
+
state = DeviceState()
|
|
76
|
+
state.machine_pos = [1.0, None, 3.0]
|
|
77
|
+
assert state.machine_pos == [1.0, None, 3.0]
|
|
78
|
+
|
|
79
|
+
def test_status_from_member_lookup(self):
|
|
80
|
+
state = DeviceState()
|
|
81
|
+
state.status = getattr(DeviceStatus, "HOLD")
|
|
82
|
+
assert state.status == DeviceStatus.HOLD
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestDeviceStateEquality:
|
|
86
|
+
def test_equal_states_compare_equal(self):
|
|
87
|
+
assert build_state() == build_state()
|
|
88
|
+
|
|
89
|
+
def test_status_difference_compares_unequal(self):
|
|
90
|
+
assert build_state() != build_state(status=DeviceStatus.IDLE)
|
|
91
|
+
|
|
92
|
+
def test_error_difference_compares_unequal(self):
|
|
93
|
+
plain = build_state()
|
|
94
|
+
failing = build_state(error=DeviceError(9, "t", "d"))
|
|
95
|
+
assert plain != failing
|
|
96
|
+
|
|
97
|
+
def test_position_difference_compares_unequal(self):
|
|
98
|
+
assert build_state() != build_state(machine_pos=[1.0, 2.0, 3.0])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TestDeviceError:
|
|
102
|
+
def test_constructor_and_getters(self):
|
|
103
|
+
error = DeviceError(9, "Commands Locked", "Clear the alarm state.")
|
|
104
|
+
assert error.code == 9
|
|
105
|
+
assert error.title == "Commands Locked"
|
|
106
|
+
assert error.description == "Clear the alarm state."
|
|
107
|
+
|
|
108
|
+
def test_equality(self):
|
|
109
|
+
assert DeviceError(1, "a", "b") == DeviceError(1, "a", "b")
|
|
110
|
+
assert DeviceError(1, "a", "b") != DeviceError(2, "a", "b")
|
|
111
|
+
|
|
112
|
+
def test_repr_contains_code_and_title(self):
|
|
113
|
+
text = repr(DeviceError(7, "Memory Error", "d"))
|
|
114
|
+
assert "7" in text
|
|
115
|
+
assert "Memory Error" in text
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestParseStateWithBuiltDefault:
|
|
119
|
+
"""parse_state merges the report into a caller-provided default;
|
|
120
|
+
the default may be a state constructed through the Python
|
|
121
|
+
setters (the round-trip embedders depend on)."""
|
|
122
|
+
|
|
123
|
+
def test_report_updates_status_of_built_state(self):
|
|
124
|
+
state = parse_state(
|
|
125
|
+
"<Idle|MPos:1.0,2.0,3.0>", build_state(status=DeviceStatus.ALARM)
|
|
126
|
+
)
|
|
127
|
+
assert state.status == DeviceStatus.IDLE
|
|
128
|
+
assert state.error is None
|
|
129
|
+
|
|
130
|
+
def test_report_keeps_unreported_fields_of_built_state(self):
|
|
131
|
+
state = parse_state(
|
|
132
|
+
"<Idle|WPos:10.0,20.0,30.0>",
|
|
133
|
+
build_state(feed_rate=500, wco=[1.0, 2.0, 3.0]),
|
|
134
|
+
)
|
|
135
|
+
assert state.feed_rate == 500
|
|
136
|
+
assert state.machine_pos == [11.0, 22.0, 33.0]
|
|
137
|
+
|
|
138
|
+
def test_alarm_report_sets_error_on_built_state(self):
|
|
139
|
+
state = parse_state("<Alarm:1|FS:500,0>", build_state(feed_rate=100))
|
|
140
|
+
assert state.status == DeviceStatus.ALARM
|
|
141
|
+
assert state.error is not None
|
|
142
|
+
assert state.error.code == 1
|
|
143
|
+
assert state.feed_rate == 500
|
|
144
|
+
|
|
145
|
+
def test_repeated_merge_is_stable(self):
|
|
146
|
+
merged = build_state()
|
|
147
|
+
for _ in range(3):
|
|
148
|
+
merged = parse_state("<Hold|MPos:11.0,22.0,33.0>", merged)
|
|
149
|
+
assert merged.status == DeviceStatus.HOLD
|
|
150
|
+
assert merged.machine_pos == [11.0, 22.0, 33.0]
|
|
151
|
+
assert merged.feed_rate == 500
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestStateRoundTrip:
|
|
155
|
+
"""Copying a parsed state field-by-field through the setters
|
|
156
|
+
reproduces it exactly — the contract the Rayforge conversion
|
|
157
|
+
helpers implement."""
|
|
158
|
+
|
|
159
|
+
def test_copy_via_setters_reproduces_parsed_state(self):
|
|
160
|
+
parsed = parse_state("<Run|MPos:1.0,2.0,3.0|FS:800,50|Bf:14,120>")
|
|
161
|
+
copy = DeviceState()
|
|
162
|
+
copy.status = parsed.status
|
|
163
|
+
copy.error = parsed.error
|
|
164
|
+
copy.machine_pos = parsed.machine_pos
|
|
165
|
+
copy.work_pos = parsed.work_pos
|
|
166
|
+
copy.wco = parsed.wco
|
|
167
|
+
copy.feed_rate = parsed.feed_rate
|
|
168
|
+
copy.spindle_speed = parsed.spindle_speed
|
|
169
|
+
copy.buffer_available = parsed.buffer_available
|
|
170
|
+
copy.buffer_rx_available = parsed.buffer_rx_available
|
|
171
|
+
assert copy == parsed
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|