UC2-REST 0.2.0.39__tar.gz → 0.2.0.41__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.
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/PKG-INFO +1 -1
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/PKG-INFO +1 -1
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/SOURCES.txt +3 -0
- uc2_rest-0.2.0.41/tests/test_canota_reconnect.py +122 -0
- uc2_rest-0.2.0.41/tests/test_stage_scan.py +213 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/__version__.py +1 -1
- uc2_rest-0.2.0.41/uc2rest/aio.py +360 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/camera_trigger.py +4 -1
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/can.py +1 -1
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/canota.py +26 -6
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/galvo.py +67 -15
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/home.py +8 -3
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/laser.py +24 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/motor.py +497 -12
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/mserial.py +14 -9
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/state.py +12 -4
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/LICENSE +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/README.md +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/not-zip-safe +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/requires.txt +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/UC2_REST.egg-info/top_level.txt +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/setup.cfg +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/setup.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/MockSerial.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/UC2Client.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/__init__.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/analog.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/camera.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/cmdrecorder.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/digitalin.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/digitalout.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/fan.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/gpio.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/gripper.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/i2c.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/lcddisplay.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/ledmatrix.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/logger.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/message.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/modules.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/motor_config.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/objective.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/pid.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/ptz.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/rotator.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/slm.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/temperature.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/utils.py +0 -0
- {uc2_rest-0.2.0.39 → uc2_rest-0.2.0.41}/uc2rest/wifi.py +0 -0
|
@@ -7,10 +7,13 @@ UC2_REST.egg-info/dependency_links.txt
|
|
|
7
7
|
UC2_REST.egg-info/not-zip-safe
|
|
8
8
|
UC2_REST.egg-info/requires.txt
|
|
9
9
|
UC2_REST.egg-info/top_level.txt
|
|
10
|
+
tests/test_canota_reconnect.py
|
|
11
|
+
tests/test_stage_scan.py
|
|
10
12
|
uc2rest/MockSerial.py
|
|
11
13
|
uc2rest/UC2Client.py
|
|
12
14
|
uc2rest/__init__.py
|
|
13
15
|
uc2rest/__version__.py
|
|
16
|
+
uc2rest/aio.py
|
|
14
17
|
uc2rest/analog.py
|
|
15
18
|
uc2rest/camera.py
|
|
16
19
|
uc2rest/camera_trigger.py
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""How the CAN streaming OTA worker hands the port back to the parent link.
|
|
2
|
+
|
|
3
|
+
Run: python -m unittest tests.test_canota_reconnect (from the repo root)
|
|
4
|
+
|
|
5
|
+
The parent link must come back at its own baudrate, and without the DTR/RTS
|
|
6
|
+
pulse (a hard reset of the master) when the master itself ended the session.
|
|
7
|
+
It keeps the reset when the host gave up, because the master may then still
|
|
8
|
+
be in binary receive mode.
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
import unittest
|
|
14
|
+
from unittest import mock
|
|
15
|
+
|
|
16
|
+
import uc2rest.canota as canota
|
|
17
|
+
from uc2rest.canota import CANOTA
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FakeStreamPort:
|
|
21
|
+
"""The raw pyserial port the worker opens for streaming. `reply` maps the
|
|
22
|
+
/ota_start preamble to the master's answer (None = no answer)."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, reply, ack=True, final=None):
|
|
25
|
+
self.reply, self.ack, self.final = reply, ack, final
|
|
26
|
+
self.rx = bytearray()
|
|
27
|
+
self.received = 0
|
|
28
|
+
self.pending = b"" # final status, sent once the last ACK was read
|
|
29
|
+
|
|
30
|
+
def _emit(self, obj):
|
|
31
|
+
self.rx += (json.dumps(obj) + "\n").encode()
|
|
32
|
+
|
|
33
|
+
def write(self, data):
|
|
34
|
+
if b"/ota_start" in data:
|
|
35
|
+
if self.reply is not None:
|
|
36
|
+
self._emit(self.reply)
|
|
37
|
+
return
|
|
38
|
+
self.received += len(data)
|
|
39
|
+
if self.ack:
|
|
40
|
+
self._emit({"ota_rx": self.received})
|
|
41
|
+
if self.final and self.received >= self.reply.get("size", 0):
|
|
42
|
+
self.pending = (json.dumps(self.final) + "\n").encode()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def in_waiting(self):
|
|
46
|
+
if not self.rx and self.pending:
|
|
47
|
+
self.rx, self.pending = bytearray(self.pending), b""
|
|
48
|
+
return len(self.rx)
|
|
49
|
+
|
|
50
|
+
def read(self, n=1):
|
|
51
|
+
out, self.rx = bytes(self.rx[:n]), self.rx[n:]
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
def flush(self): pass
|
|
55
|
+
def reset_input_buffer(self): pass
|
|
56
|
+
def isOpen(self): return True
|
|
57
|
+
def close(self): pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _Parent:
|
|
61
|
+
def __init__(self, baudrate):
|
|
62
|
+
self.serial = mock.Mock(serialport="/dev/ttyTEST", baudrate=baudrate)
|
|
63
|
+
self.serial.serialdevice.isOpen.return_value = True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
FIRMWARE = b"\xe9" + bytes(4999) # two 4 KB chunks
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ReconnectTest(unittest.TestCase):
|
|
70
|
+
def setUp(self):
|
|
71
|
+
fd, self.firmware = tempfile.mkstemp(suffix=".bin")
|
|
72
|
+
os.write(fd, FIRMWARE)
|
|
73
|
+
os.close(fd)
|
|
74
|
+
self.addCleanup(os.remove, self.firmware)
|
|
75
|
+
|
|
76
|
+
def run_upload(self, port, parent_baud=115200):
|
|
77
|
+
parent = _Parent(parent_baud)
|
|
78
|
+
opened = []
|
|
79
|
+
|
|
80
|
+
def open_port(p, baud, timeout=None):
|
|
81
|
+
opened.append(baud)
|
|
82
|
+
return port
|
|
83
|
+
|
|
84
|
+
with mock.patch.object(canota.serial, "Serial", side_effect=open_port), \
|
|
85
|
+
mock.patch.object(canota.time, "sleep"), \
|
|
86
|
+
mock.patch.object(canota, "READY_TIMEOUT_S", 0.2), \
|
|
87
|
+
mock.patch.object(canota, "ACK_TIMEOUT_S", 0.2), \
|
|
88
|
+
mock.patch.object(canota, "FLASH_TIMEOUT_S", 0.2):
|
|
89
|
+
ok = CANOTA(parent).start_can_streaming_ota_blocking(
|
|
90
|
+
can_id=11, firmware_path=self.firmware)
|
|
91
|
+
return ok, opened, parent.serial.openDevice
|
|
92
|
+
|
|
93
|
+
def test_master_refusal_restores_without_reset_at_link_baud(self):
|
|
94
|
+
ok, opened, reopen = self.run_upload(
|
|
95
|
+
FakeStreamPort({"ota_status": "error", "error": "slave_unreachable"}))
|
|
96
|
+
self.assertFalse(ok)
|
|
97
|
+
self.assertEqual(opened, [115200]) # streamed at the link's baud
|
|
98
|
+
reopen.assert_called_once_with("/dev/ttyTEST", 115200, reset=False)
|
|
99
|
+
|
|
100
|
+
def test_success_restores_without_reset(self):
|
|
101
|
+
size = len(FIRMWARE)
|
|
102
|
+
port = FakeStreamPort({"ota_status": "ready", "size": size},
|
|
103
|
+
final={"ota_status": "success"})
|
|
104
|
+
ok, _, reopen = self.run_upload(port, parent_baud=921600)
|
|
105
|
+
self.assertTrue(ok)
|
|
106
|
+
reopen.assert_called_once_with("/dev/ttyTEST", 921600, reset=False)
|
|
107
|
+
|
|
108
|
+
def test_silent_master_keeps_the_reset(self):
|
|
109
|
+
ok, _, reopen = self.run_upload(FakeStreamPort(reply=None))
|
|
110
|
+
self.assertFalse(ok)
|
|
111
|
+
reopen.assert_called_once_with("/dev/ttyTEST", 115200, reset=True)
|
|
112
|
+
|
|
113
|
+
def test_missing_acks_keep_the_reset(self):
|
|
114
|
+
size = len(FIRMWARE)
|
|
115
|
+
ok, _, reopen = self.run_upload(
|
|
116
|
+
FakeStreamPort({"ota_status": "ready", "size": size}, ack=False))
|
|
117
|
+
self.assertFalse(ok)
|
|
118
|
+
reopen.assert_called_once_with("/dev/ttyTEST", 115200, reset=True)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
unittest.main()
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""stagescan payload + completion callback, without hardware.
|
|
2
|
+
|
|
3
|
+
Run: python -m unittest tests.test_stage_scan (from the repo root)
|
|
4
|
+
"""
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from uc2rest.motor import Motor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _Parent:
|
|
12
|
+
"""Just enough of UC2Client for Motor: a serial with register_callback and post_json."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.serial = mock.Mock()
|
|
16
|
+
self.logger = mock.Mock()
|
|
17
|
+
self.sent = []
|
|
18
|
+
|
|
19
|
+
def post_json(self, path, payload, **kwargs):
|
|
20
|
+
self.sent.append((path, payload))
|
|
21
|
+
return {"qid": 1, "return": 1}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StageScanPayloadTest(unittest.TestCase):
|
|
25
|
+
def setUp(self):
|
|
26
|
+
self.parent = _Parent()
|
|
27
|
+
self.motor = Motor(parent=self.parent)
|
|
28
|
+
self.motor.stepSizeX, self.motor.stepSizeY, self.motor.stepSizeZ = 2.0, 4.0, 0.5
|
|
29
|
+
|
|
30
|
+
def _scan(self):
|
|
31
|
+
self.assertEqual(len(self.parent.sent), 1)
|
|
32
|
+
path, payload = self.parent.sent[0]
|
|
33
|
+
self.assertEqual(path, "/motor_act")
|
|
34
|
+
self.assertEqual(payload["task"], "/motor_act")
|
|
35
|
+
return payload["stagescan"]
|
|
36
|
+
|
|
37
|
+
def test_positions_are_converted_to_steps(self):
|
|
38
|
+
self.motor.start_stage_scanning(xstart=10, xstep=20, nx=3, ystart=40, ystep=8, ny=2,
|
|
39
|
+
zstart=1, zstep=2, nz=4)
|
|
40
|
+
s = self._scan()
|
|
41
|
+
self.assertEqual((s["xStart"], s["xStep"], s["nX"]), (5, 10, 3))
|
|
42
|
+
self.assertEqual((s["yStart"], s["yStep"], s["nY"]), (10, 2, 2))
|
|
43
|
+
self.assertEqual((s["zStart"], s["zStep"], s["nZ"]), (2, 4, 4))
|
|
44
|
+
|
|
45
|
+
def test_firmware_keys_and_defaults(self):
|
|
46
|
+
self.motor.start_stage_scanning(tsettle=90, tExposure=50)
|
|
47
|
+
s = self._scan()
|
|
48
|
+
self.assertEqual((s["tPre"], s["tPost"]), (90, 50))
|
|
49
|
+
self.assertEqual(s["speed"], 20000)
|
|
50
|
+
self.assertEqual(s["acceleration"], self.motor.DEFAULT_ACCELERATION)
|
|
51
|
+
self.assertNotIn("accel", s) # the firmware reads "acceleration"
|
|
52
|
+
self.assertNotIn("tTrig", s) # firmware default unless requested
|
|
53
|
+
self.assertEqual((s["zicZac"], s["nonstop"]), (1, 0))
|
|
54
|
+
|
|
55
|
+
def test_illumination_keeps_its_channel_index(self):
|
|
56
|
+
self.motor.start_stage_scanning(illumination=[0, 50], led=None)
|
|
57
|
+
s = self._scan()
|
|
58
|
+
self.assertEqual(s["illumination"], [0, 50, 0, 0, 0]) # padded at the end, never shifted
|
|
59
|
+
self.assertEqual(s["led"], 0)
|
|
60
|
+
self.parent.sent.clear()
|
|
61
|
+
self.motor.start_stage_scanning(illumination=(1, 2, 3, 4, 5, 6), led=255, tTrig=2, nonstop=True, zicZac=False)
|
|
62
|
+
s = self._scan()
|
|
63
|
+
self.assertEqual(s["illumination"], [1, 2, 3, 4, 5])
|
|
64
|
+
self.assertEqual((s["led"], s["tTrig"], s["nonstop"], s["zicZac"]), (255, 2, 1, 0))
|
|
65
|
+
|
|
66
|
+
def test_stop_payload(self):
|
|
67
|
+
self.motor.stop_stage_scanning()
|
|
68
|
+
self.assertEqual(self._scan(), {"stopped": 1})
|
|
69
|
+
|
|
70
|
+
def test_completion_callback_sets_flag_and_notifies(self):
|
|
71
|
+
got = []
|
|
72
|
+
self.motor.register_stagescan_callback(got.append)
|
|
73
|
+
self.motor.start_stage_scanning()
|
|
74
|
+
self.assertFalse(self.motor._stagescan_complete)
|
|
75
|
+
self.motor._callback_stagescan_complete({"stagescan": 1, "qid": 7, "success": 1})
|
|
76
|
+
self.assertTrue(self.motor._stagescan_complete)
|
|
77
|
+
self.assertEqual(got, [{"stagescan": 1, "qid": 7, "success": 1}])
|
|
78
|
+
self.motor.unregister_stagescan_callback(got.append)
|
|
79
|
+
self.motor._callback_stagescan_complete({"stagescan": 1, "qid": 8, "success": 1})
|
|
80
|
+
self.assertEqual(len(got), 1)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
unittest.main()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CameraTriggerNotificationTest(unittest.TestCase):
|
|
88
|
+
"""{"cam":1} (old firmware) and {"cam":1,"frame":n} (new) both reach the callback."""
|
|
89
|
+
|
|
90
|
+
def setUp(self):
|
|
91
|
+
from uc2rest.camera_trigger import CameraTrigger
|
|
92
|
+
self.parent = _Parent()
|
|
93
|
+
self.trigger = CameraTrigger(parent=self.parent)
|
|
94
|
+
self.got = []
|
|
95
|
+
self.trigger.register_callback(0, self.got.append)
|
|
96
|
+
|
|
97
|
+
def test_plain_trigger_counts_from_one(self):
|
|
98
|
+
self.trigger._callback_camera_trigger({"cam": 1})
|
|
99
|
+
self.trigger._callback_camera_trigger({"cam": 1})
|
|
100
|
+
self.assertEqual([g["frame_id"] for g in self.got], [1, 2])
|
|
101
|
+
self.assertNotIn("frame", self.got[0])
|
|
102
|
+
|
|
103
|
+
def test_numbered_trigger_passes_firmware_frame_through(self):
|
|
104
|
+
self.trigger._callback_camera_trigger({"cam": 1, "frame": 0})
|
|
105
|
+
self.trigger._callback_camera_trigger({"cam": 1, "frame": 5})
|
|
106
|
+
self.assertEqual([g["frame"] for g in self.got], [0, 5])
|
|
107
|
+
self.assertEqual([g["frame_id"] for g in self.got], [1, 2])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class StrobeSweepTest(unittest.TestCase):
|
|
111
|
+
"""Strobed sweep payloads, unit conversion and event parsing."""
|
|
112
|
+
|
|
113
|
+
def setUp(self):
|
|
114
|
+
self.parent = _Parent()
|
|
115
|
+
self.motor = Motor(parent=self.parent)
|
|
116
|
+
self.motor.stepSizeX = 0.5
|
|
117
|
+
self.motor.direction[1] = -1
|
|
118
|
+
|
|
119
|
+
def _sweep(self):
|
|
120
|
+
path, payload = self.parent.sent[-1]
|
|
121
|
+
self.assertEqual(path, "/motor_act")
|
|
122
|
+
return payload["strobesweep"]
|
|
123
|
+
|
|
124
|
+
def test_registers_pattern_callbacks(self):
|
|
125
|
+
patterns = [c.kwargs.get("pattern") for c in self.parent.serial.register_callback.call_args_list]
|
|
126
|
+
self.assertIn("strobesweep", patterns)
|
|
127
|
+
self.assertIn("modules", patterns)
|
|
128
|
+
|
|
129
|
+
def test_start_payload_converts_target_to_steps(self):
|
|
130
|
+
self.motor.start_strobe_sweep(axis="X", target=1000.0, speed=4000, period_us=20000,
|
|
131
|
+
laser=4, delay_us=1200, width_us=20, report=8)
|
|
132
|
+
s = self._sweep()
|
|
133
|
+
self.assertEqual(s["axis"], 1)
|
|
134
|
+
self.assertEqual(s["target"], -2000) # 1000 µm / 0.5 µm/step, direction -1
|
|
135
|
+
self.assertEqual((s["speed"], s["periodUs"], s["trigUs"]), (4000, 20000, 100))
|
|
136
|
+
self.assertEqual((s["laser"], s["delayUs"], s["widthUs"]), (4, 1200, 20))
|
|
137
|
+
self.assertEqual((s["latch"], s["report"], s["maxFrames"]), (1, 8, 0))
|
|
138
|
+
self.assertNotIn("acceleration", s)
|
|
139
|
+
|
|
140
|
+
def test_strobe_keys_only_when_both_given(self):
|
|
141
|
+
self.motor.start_strobe_sweep(axis="X", target=0, laser=4, delay_us=1200)
|
|
142
|
+
s = self._sweep()
|
|
143
|
+
self.assertNotIn("delayUs", s)
|
|
144
|
+
self.assertNotIn("widthUs", s)
|
|
145
|
+
|
|
146
|
+
def test_relative_target_and_calibration_mode(self):
|
|
147
|
+
self.motor.currentPosition[1] = 250.0
|
|
148
|
+
self.motor.start_strobe_sweep(axis=1, target=-50.0, is_absolute=False, max_frames=12, latch=False)
|
|
149
|
+
s = self._sweep()
|
|
150
|
+
self.assertEqual(s["target"], -400) # (250 - 50) / 0.5 * -1
|
|
151
|
+
self.assertEqual((s["maxFrames"], s["latch"], s["laser"]), (12, 0, -1))
|
|
152
|
+
|
|
153
|
+
def test_stop_payload(self):
|
|
154
|
+
self.motor.stop_strobe_sweep()
|
|
155
|
+
self.assertEqual(self._sweep(), {"stopped": 1})
|
|
156
|
+
|
|
157
|
+
def test_report_and_done_events(self):
|
|
158
|
+
events = []
|
|
159
|
+
self.motor.register_strobesweep_callback(events.append)
|
|
160
|
+
self.motor.start_strobe_sweep(axis="X", target=10)
|
|
161
|
+
self.assertTrue(self.motor.is_strobe_sweep_running())
|
|
162
|
+
self.motor._callback_strobesweep({"strobesweep": {"n": [1, 2, 4], "x": [100, 102, 106]}})
|
|
163
|
+
self.motor._callback_strobesweep({"strobesweep": True, "frames": 4, "camera": 4, "flashes": 4,
|
|
164
|
+
"positions": 3, "latch": 1, "strobe": 1, "aborted": 0,
|
|
165
|
+
"success": 1, "qid": 5})
|
|
166
|
+
self.assertEqual(events[0], {"type": "report", "n": [1, 2, 4], "x": [-50.0, -51.0, -53.0],
|
|
167
|
+
"x_steps": [100, 102, 106]})
|
|
168
|
+
self.assertEqual(events[1]["type"], "done")
|
|
169
|
+
self.assertEqual((events[1]["frames"], events[1]["success"]), (4, 1))
|
|
170
|
+
self.assertFalse(self.motor.is_strobe_sweep_running())
|
|
171
|
+
self.motor.unregister_strobesweep_callback(events.append)
|
|
172
|
+
self.motor._callback_strobesweep({"strobesweep": {"n": [5], "x": [1]}})
|
|
173
|
+
self.assertEqual(len(events), 2)
|
|
174
|
+
|
|
175
|
+
def test_capability_probe(self):
|
|
176
|
+
# new firmware answers /modules_get with the flag; the answer arrives by pattern
|
|
177
|
+
def answer(path, payload, **kwargs):
|
|
178
|
+
self.parent.sent.append((path, payload))
|
|
179
|
+
self.motor._callback_modules({"modules": {"motor": 1, "strobesweep": 1}})
|
|
180
|
+
self.parent.post_json = answer
|
|
181
|
+
self.assertTrue(self.motor.has_strobe_sweep(timeout=0.2))
|
|
182
|
+
self.assertEqual(self.parent.sent[-1][1]["task"], "/modules_get")
|
|
183
|
+
|
|
184
|
+
def test_capability_probe_old_firmware(self):
|
|
185
|
+
def old(path, payload, **kwargs):
|
|
186
|
+
self.motor._callback_modules({"modules": {"motor": 1}})
|
|
187
|
+
self.parent.post_json = old
|
|
188
|
+
self.assertFalse(self.motor.has_strobe_sweep(timeout=0.2))
|
|
189
|
+
self.parent.post_json = lambda *a, **k: None # no answer at all
|
|
190
|
+
self.assertFalse(self.motor.has_strobe_sweep(timeout=0.05))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class LaserStrobeTest(unittest.TestCase):
|
|
194
|
+
def setUp(self):
|
|
195
|
+
from uc2rest.laser import Laser
|
|
196
|
+
self.parent = _Parent()
|
|
197
|
+
self.laser = Laser(parent=self.parent)
|
|
198
|
+
|
|
199
|
+
def test_payload_and_answer(self):
|
|
200
|
+
self.parent.post_json = lambda path, payload, **kw: (
|
|
201
|
+
self.parent.sent.append((path, payload)) or
|
|
202
|
+
[{"strobe": {"supported": 1, "enabled": 1, "delayUs": 1200, "widthUs": 20, "count": 0},
|
|
203
|
+
"return": 1, "qid": 3}])
|
|
204
|
+
r = self.laser.set_strobe(4, enable=True, delay_us=1200, width_us=20)
|
|
205
|
+
path, payload = self.parent.sent[-1]
|
|
206
|
+
self.assertEqual(path, "/laser_act")
|
|
207
|
+
self.assertEqual(payload["LASERid"], 4)
|
|
208
|
+
self.assertEqual(payload["strobe"], {"enable": 1, "delayUs": 1200, "widthUs": 20})
|
|
209
|
+
self.assertEqual(r["strobe"]["enabled"], 1)
|
|
210
|
+
|
|
211
|
+
def test_old_firmware_returns_none(self):
|
|
212
|
+
self.parent.post_json = lambda *a, **k: "communication interrupted by timeout or reset: 3"
|
|
213
|
+
self.assertIsNone(self.laser.set_strobe(4, enable=False))
|
|
@@ -6,7 +6,7 @@ __version__.py
|
|
|
6
6
|
|
|
7
7
|
__title__ = 'UC2-REST'
|
|
8
8
|
__description__ = 'This pacage will help you to drive the ESP32-driven microscopy control modules from UC2'
|
|
9
|
-
__version__ = "v0.2.0.
|
|
9
|
+
__version__ = "v0.2.0.41"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|