UC2-REST 0.2.0.31__tar.gz → 0.2.0.32__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.31 → uc2_rest-0.2.0.32}/PKG-INFO +1 -1
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/PKG-INFO +1 -1
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/SOURCES.txt +3 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/UC2Client.py +8 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/__version__.py +1 -1
- uc2_rest-0.2.0.32/uc2rest/camera_trigger.py +197 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/can.py +3 -2
- uc2_rest-0.2.0.32/uc2rest/digitalin.py +122 -0
- uc2_rest-0.2.0.32/uc2rest/digitalout.py +187 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/home.py +16 -7
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/laser.py +1 -1
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/motor.py +93 -7
- uc2_rest-0.2.0.32/uc2rest/motor_config.py +315 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/mserial.py +104 -92
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/objective.py +4 -3
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/state.py +1 -1
- uc2_rest-0.2.0.31/uc2rest/digitalout.py +0 -65
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/LICENSE +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/README.md +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/not-zip-safe +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/requires.txt +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/top_level.txt +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/setup.cfg +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/setup.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/MockSerial.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/__init__.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/analog.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/camera.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/canota.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/cmdrecorder.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/galvo.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/gripper.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/lcddisplay.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/ledmatrix.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/logger.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/message.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/modules.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/pid.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/rotator.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/slm.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/temperature.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/utils.py +0 -0
- {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/wifi.py +0 -0
|
@@ -13,9 +13,11 @@ uc2rest/__init__.py
|
|
|
13
13
|
uc2rest/__version__.py
|
|
14
14
|
uc2rest/analog.py
|
|
15
15
|
uc2rest/camera.py
|
|
16
|
+
uc2rest/camera_trigger.py
|
|
16
17
|
uc2rest/can.py
|
|
17
18
|
uc2rest/canota.py
|
|
18
19
|
uc2rest/cmdrecorder.py
|
|
20
|
+
uc2rest/digitalin.py
|
|
19
21
|
uc2rest/digitalout.py
|
|
20
22
|
uc2rest/galvo.py
|
|
21
23
|
uc2rest/gripper.py
|
|
@@ -27,6 +29,7 @@ uc2rest/logger.py
|
|
|
27
29
|
uc2rest/message.py
|
|
28
30
|
uc2rest/modules.py
|
|
29
31
|
uc2rest/motor.py
|
|
32
|
+
uc2rest/motor_config.py
|
|
30
33
|
uc2rest/mserial.py
|
|
31
34
|
uc2rest/objective.py
|
|
32
35
|
uc2rest/pid.py
|
|
@@ -21,6 +21,7 @@ from .camera import Camera
|
|
|
21
21
|
from .analog import Analog
|
|
22
22
|
from .modules import Modules
|
|
23
23
|
from .digitalout import DigitalOut
|
|
24
|
+
from .digitalin import DigitalIn
|
|
24
25
|
from .rotator import Rotator
|
|
25
26
|
from .logger import Logger
|
|
26
27
|
from .cmdrecorder import cmdRecorder
|
|
@@ -28,6 +29,7 @@ from .temperature import Temperature
|
|
|
28
29
|
from .message import Message
|
|
29
30
|
from .can import CAN
|
|
30
31
|
from .canota import CANOTA
|
|
32
|
+
from .camera_trigger import CameraTrigger
|
|
31
33
|
try:
|
|
32
34
|
import requests
|
|
33
35
|
except:
|
|
@@ -149,8 +151,14 @@ class UC2Client(object):
|
|
|
149
151
|
# initialize digital out
|
|
150
152
|
self.digitalout = DigitalOut(self)
|
|
151
153
|
|
|
154
|
+
# initialize digital in
|
|
155
|
+
self.digitalin = DigitalIn(self)
|
|
156
|
+
|
|
152
157
|
# initialize messaging
|
|
153
158
|
self.message = Message(self)
|
|
159
|
+
|
|
160
|
+
# initialize camera trigger callback handler
|
|
161
|
+
self.camera_trigger = CameraTrigger(self)
|
|
154
162
|
|
|
155
163
|
# initialize module controller
|
|
156
164
|
self.modules = Modules(parent=self)
|
|
@@ -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.32"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Camera trigger callback module for UC2-REST.
|
|
3
|
+
|
|
4
|
+
This module handles camera trigger signals from the firmware ({"cam":1})
|
|
5
|
+
to enable software triggering based on hardware events during stage scanning.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import time
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
gTIMEOUT = 1 # seconds to wait for a response from the ESP32
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CameraTrigger(object):
|
|
17
|
+
"""
|
|
18
|
+
This class parses incoming camera trigger signals from the ESP32 firmware.
|
|
19
|
+
|
|
20
|
+
When the firmware sends {"cam":1}, this module triggers registered callbacks
|
|
21
|
+
which can be used for software-triggered image acquisition during stage scanning.
|
|
22
|
+
|
|
23
|
+
Example usage:
|
|
24
|
+
import uc2rest
|
|
25
|
+
|
|
26
|
+
ESP32 = uc2rest.UC2Client(serialport=port, baudrate=500000)
|
|
27
|
+
|
|
28
|
+
# Register callback for camera trigger
|
|
29
|
+
def my_camera_callback(data):
|
|
30
|
+
print(f"Camera trigger received: {data}")
|
|
31
|
+
# Trigger image acquisition here
|
|
32
|
+
|
|
33
|
+
ESP32.camera_trigger.register_callback(0, my_camera_callback)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, parent=None, nCallbacks=10):
|
|
37
|
+
"""
|
|
38
|
+
Initialize camera trigger handler.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
parent: Parent UC2Client instance
|
|
42
|
+
nCallbacks: Maximum number of callback functions to register
|
|
43
|
+
"""
|
|
44
|
+
self._parent = parent
|
|
45
|
+
self.nCallbacks = nCallbacks
|
|
46
|
+
|
|
47
|
+
# Track trigger count for diagnostics
|
|
48
|
+
self._trigger_count = 0
|
|
49
|
+
self._last_trigger_time = None
|
|
50
|
+
|
|
51
|
+
# Initialize callback functions
|
|
52
|
+
self._callbackPerKey = {}
|
|
53
|
+
self.init_callback_functions(self.nCallbacks)
|
|
54
|
+
|
|
55
|
+
# Register callback for camera trigger on serial loop
|
|
56
|
+
if hasattr(self._parent, "serial"):
|
|
57
|
+
self._parent.serial.register_callback(self._callback_camera_trigger, pattern="cam")
|
|
58
|
+
|
|
59
|
+
def _callback_camera_trigger(self, data):
|
|
60
|
+
"""
|
|
61
|
+
Parse camera trigger message from firmware.
|
|
62
|
+
|
|
63
|
+
Expected JSON format:
|
|
64
|
+
{
|
|
65
|
+
"cam": 1 # Trigger signal
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
or with additional data:
|
|
69
|
+
{
|
|
70
|
+
"cam": {
|
|
71
|
+
"trigger": 1,
|
|
72
|
+
"frame_id": 123,
|
|
73
|
+
"illumination": 0
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
data: JSON data dictionary from firmware
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
# Update trigger statistics
|
|
82
|
+
self._trigger_count += 1
|
|
83
|
+
self._last_trigger_time = time.time()
|
|
84
|
+
|
|
85
|
+
# Extract trigger information
|
|
86
|
+
cam_data = data.get("cam", {})
|
|
87
|
+
|
|
88
|
+
# Handle simple trigger ({"cam": 1})
|
|
89
|
+
if isinstance(cam_data, (int, float)):
|
|
90
|
+
trigger_info = {
|
|
91
|
+
"trigger": int(cam_data),
|
|
92
|
+
"frame_id": self._trigger_count,
|
|
93
|
+
"timestamp": self._last_trigger_time
|
|
94
|
+
}
|
|
95
|
+
self._parent.logger.debug(f"Camera trigger received: {trigger_info}")
|
|
96
|
+
else:
|
|
97
|
+
# Handle extended trigger data
|
|
98
|
+
trigger_info = {
|
|
99
|
+
"trigger": cam_data.get("trigger", 1),
|
|
100
|
+
"frame_id": cam_data.get("frame_id", self._trigger_count),
|
|
101
|
+
"illumination": cam_data.get("illumination", -1),
|
|
102
|
+
"timestamp": self._last_trigger_time
|
|
103
|
+
}
|
|
104
|
+
self._parent.logger.debug(f"Camera trigger with data received: {trigger_info}")
|
|
105
|
+
|
|
106
|
+
# Call all registered callbacks
|
|
107
|
+
for key, callback in self._callbackPerKey.items():
|
|
108
|
+
if callback is not None and callable(callback):
|
|
109
|
+
try:
|
|
110
|
+
callback(trigger_info)
|
|
111
|
+
except Exception as callback_error:
|
|
112
|
+
print(f"Error in camera trigger callback {key}: {callback_error}")
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"Error in _callback_camera_trigger: {e}")
|
|
116
|
+
|
|
117
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
118
|
+
"""
|
|
119
|
+
Initialize callback function dictionary.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
nCallbacks: Number of callback slots to create
|
|
123
|
+
"""
|
|
124
|
+
self._callbackPerKey = {}
|
|
125
|
+
self.nCallbacks = nCallbacks
|
|
126
|
+
for i in range(nCallbacks):
|
|
127
|
+
self._callbackPerKey[i] = None
|
|
128
|
+
|
|
129
|
+
def register_callback(self, key, callback):
|
|
130
|
+
"""
|
|
131
|
+
Register a callback function for camera trigger events.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
key: Integer key (0 to nCallbacks-1) for this callback
|
|
135
|
+
callback: Function to call when trigger is received.
|
|
136
|
+
Function signature: callback(trigger_info: dict)
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
def on_camera_trigger(info):
|
|
140
|
+
print(f"Frame {info['frame_id']} triggered at {info['timestamp']}")
|
|
141
|
+
camera.snap_image()
|
|
142
|
+
|
|
143
|
+
ESP32.camera_trigger.register_callback(0, on_camera_trigger)
|
|
144
|
+
"""
|
|
145
|
+
if key < 0 or key >= self.nCallbacks:
|
|
146
|
+
raise ValueError(f"Callback key must be between 0 and {self.nCallbacks-1}")
|
|
147
|
+
self._callbackPerKey[key] = callback
|
|
148
|
+
|
|
149
|
+
def unregister_callback(self, key):
|
|
150
|
+
"""
|
|
151
|
+
Remove a registered callback.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
key: Integer key of callback to remove
|
|
155
|
+
"""
|
|
156
|
+
if key in self._callbackPerKey:
|
|
157
|
+
self._callbackPerKey[key] = None
|
|
158
|
+
|
|
159
|
+
def clear_all_callbacks(self):
|
|
160
|
+
"""Remove all registered callbacks."""
|
|
161
|
+
self.init_callback_functions(self.nCallbacks)
|
|
162
|
+
|
|
163
|
+
def get_trigger_count(self):
|
|
164
|
+
"""
|
|
165
|
+
Get the total number of triggers received since initialization.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
int: Number of camera triggers received
|
|
169
|
+
"""
|
|
170
|
+
return self._trigger_count
|
|
171
|
+
|
|
172
|
+
def get_last_trigger_time(self):
|
|
173
|
+
"""
|
|
174
|
+
Get the timestamp of the last trigger received.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
float or None: Unix timestamp of last trigger, or None if no triggers received
|
|
178
|
+
"""
|
|
179
|
+
return self._last_trigger_time
|
|
180
|
+
|
|
181
|
+
def reset_trigger_count(self):
|
|
182
|
+
"""Reset the trigger counter to zero."""
|
|
183
|
+
self._trigger_count = 0
|
|
184
|
+
self._last_trigger_time = None
|
|
185
|
+
|
|
186
|
+
def get_trigger_stats(self):
|
|
187
|
+
"""
|
|
188
|
+
Get statistics about trigger events.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
dict: Dictionary with trigger statistics
|
|
192
|
+
"""
|
|
193
|
+
return {
|
|
194
|
+
"total_triggers": self._trigger_count,
|
|
195
|
+
"last_trigger_time": self._last_trigger_time,
|
|
196
|
+
"callbacks_registered": sum(1 for cb in self._callbackPerKey.values() if cb is not None)
|
|
197
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
class DigitalIn(object):
|
|
4
|
+
## DigitalIn
|
|
5
|
+
def __init__(self, parent):
|
|
6
|
+
self._parent = parent
|
|
7
|
+
self.nDigitalIns = 3
|
|
8
|
+
self.digitalInValues = np.zeros((self.nDigitalIns), dtype=int)
|
|
9
|
+
|
|
10
|
+
# register a callback function for the digitalin status on the serial loop
|
|
11
|
+
if hasattr(self._parent, "serial"):
|
|
12
|
+
self._parent.serial.register_callback(self._callback_digitalin_status, pattern="digitalin")
|
|
13
|
+
|
|
14
|
+
# announce a function that is called when we receive a digitalin update through the callback
|
|
15
|
+
self._callbackPerKey = {}
|
|
16
|
+
self.nCallbacks = 10
|
|
17
|
+
self._callbackPerKey = self.init_callback_functions(nCallbacks=self.nCallbacks)
|
|
18
|
+
print(self._callbackPerKey)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
22
|
+
''' initialize the callback functions - each key holds a list of callbacks '''
|
|
23
|
+
_callbackPerKey = {}
|
|
24
|
+
self.nCallbacks = nCallbacks
|
|
25
|
+
for i in range(nCallbacks):
|
|
26
|
+
_callbackPerKey[i] = [] # Initialize as list to support multiple callbacks
|
|
27
|
+
return _callbackPerKey
|
|
28
|
+
|
|
29
|
+
def _callback_digitalin_status(self, data):
|
|
30
|
+
''' cast the json in the form:
|
|
31
|
+
++
|
|
32
|
+
{"digitalin":{"digitalinid":1,"digitalinval":1,"isDone":1},"qid":2}
|
|
33
|
+
--
|
|
34
|
+
into the digitalin values array '''
|
|
35
|
+
try:
|
|
36
|
+
digitalin_data = data["digitalin"]
|
|
37
|
+
# Handle both single digitalin and multiple digitalins
|
|
38
|
+
if isinstance(digitalin_data, dict):
|
|
39
|
+
# Single digitalin format: {"digitalinid":1,"digitalinval":1}
|
|
40
|
+
digitalin_id = digitalin_data.get("digitalinid", 0)
|
|
41
|
+
digitalin_val = digitalin_data.get("digitalinval", 0)
|
|
42
|
+
if 0 < digitalin_id <= self.nDigitalIns:
|
|
43
|
+
self.digitalInValues[digitalin_id - 1] = digitalin_val
|
|
44
|
+
elif isinstance(digitalin_data, list):
|
|
45
|
+
# Multiple digitalins format: [{"digitalinid":1,"digitalinval":1}, ...]
|
|
46
|
+
for digitalin in digitalin_data:
|
|
47
|
+
digitalin_id = digitalin.get("digitalinid", 0)
|
|
48
|
+
digitalin_val = digitalin.get("digitalinval", 0)
|
|
49
|
+
if 0 < digitalin_id <= self.nDigitalIns:
|
|
50
|
+
self.digitalInValues[digitalin_id - 1] = digitalin_val
|
|
51
|
+
|
|
52
|
+
# Call all registered callbacks for key 0
|
|
53
|
+
for callback in self._callbackPerKey[0]:
|
|
54
|
+
if callable(callback):
|
|
55
|
+
try:
|
|
56
|
+
callback(self.digitalInValues)
|
|
57
|
+
except Exception as callback_error:
|
|
58
|
+
print(f"Error in callback execution: {callback_error}")
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print("Error in _callback_digitalin_status: ", e)
|
|
61
|
+
|
|
62
|
+
def register_callback(self, key, callbackfct):
|
|
63
|
+
''' register a callback function for a specific key - supports multiple callbacks per key '''
|
|
64
|
+
if key not in self._callbackPerKey:
|
|
65
|
+
self._callbackPerKey[key] = []
|
|
66
|
+
if callbackfct not in self._callbackPerKey[key]: # Avoid duplicate registrations
|
|
67
|
+
self._callbackPerKey[key].append(callbackfct)
|
|
68
|
+
print(f"Registered callback for key {key}. Total callbacks for this key: {len(self._callbackPerKey[key])}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_digitalin(self, digitalinid=1, timeout=1, is_blocking=True):
|
|
72
|
+
"""
|
|
73
|
+
Get the current value of a digital input.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
-----------
|
|
77
|
+
digitalinid : int
|
|
78
|
+
ID of the digital input (1, 2, or 3)
|
|
79
|
+
timeout : float
|
|
80
|
+
Timeout for the request in seconds
|
|
81
|
+
is_blocking : bool
|
|
82
|
+
Whether to wait for a response
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
--------
|
|
86
|
+
dict or None
|
|
87
|
+
Response from the device containing digitalin status
|
|
88
|
+
"""
|
|
89
|
+
path = '/digitalin_get'
|
|
90
|
+
|
|
91
|
+
payload = {
|
|
92
|
+
"task": path,
|
|
93
|
+
"digitalinid": digitalinid
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=timeout)
|
|
97
|
+
return r
|
|
98
|
+
|
|
99
|
+
def act_digitalin(self, timeout=1, is_blocking=False):
|
|
100
|
+
"""
|
|
101
|
+
Trigger the digitalin act function.
|
|
102
|
+
|
|
103
|
+
Parameters:
|
|
104
|
+
-----------
|
|
105
|
+
timeout : float
|
|
106
|
+
Timeout for the request in seconds
|
|
107
|
+
is_blocking : bool
|
|
108
|
+
Whether to wait for a response
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
--------
|
|
112
|
+
dict or None
|
|
113
|
+
Response from the device
|
|
114
|
+
"""
|
|
115
|
+
path = '/digitalin_act'
|
|
116
|
+
|
|
117
|
+
payload = {
|
|
118
|
+
"task": path
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=timeout)
|
|
122
|
+
return r
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
class DigitalOut(object):
|
|
4
|
+
## DigitalOut
|
|
5
|
+
def __init__(self, parent):
|
|
6
|
+
self._parent = parent
|
|
7
|
+
self._logger = parent.logger
|
|
8
|
+
self.nDigitalOuts = 3
|
|
9
|
+
self.digitalOutValues = np.zeros((self.nDigitalOuts), dtype=int)
|
|
10
|
+
|
|
11
|
+
# register a callback function for the digitalout status on the serial loop
|
|
12
|
+
if hasattr(self._parent, "serial"):
|
|
13
|
+
self._parent.serial.register_callback(self._callback_digitalout_status, pattern="digitalout")
|
|
14
|
+
|
|
15
|
+
# announce a function that is called when we receive a digitalout update through the callback
|
|
16
|
+
self._callbackPerKey = {}
|
|
17
|
+
self.nCallbacks = 10
|
|
18
|
+
self._callbackPerKey = self.init_callback_functions(nCallbacks=self.nCallbacks)
|
|
19
|
+
print(self._callbackPerKey)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
23
|
+
''' initialize the callback functions - each key holds a list of callbacks '''
|
|
24
|
+
_callbackPerKey = {}
|
|
25
|
+
self.nCallbacks = nCallbacks
|
|
26
|
+
for i in range(nCallbacks):
|
|
27
|
+
_callbackPerKey[i] = [] # Initialize as list to support multiple callbacks
|
|
28
|
+
return _callbackPerKey
|
|
29
|
+
|
|
30
|
+
def _callback_digitalout_status(self, data):
|
|
31
|
+
''' cast the json in the form:
|
|
32
|
+
++
|
|
33
|
+
{"digitalout":{"digitaloutid":1,"digitaloutval":1,"digitaloutpin":4,"isDone":1},"qid":2}
|
|
34
|
+
--
|
|
35
|
+
into the digitalout values array '''
|
|
36
|
+
try:
|
|
37
|
+
digitalout_data = data["digitalout"]
|
|
38
|
+
# Handle both single digitalout and multiple digitalouts
|
|
39
|
+
if isinstance(digitalout_data, dict):
|
|
40
|
+
# Single digitalout format: {"digitaloutid":1,"digitaloutval":1,"digitaloutpin":4}
|
|
41
|
+
digitalout_id = digitalout_data.get("digitaloutid", 0)
|
|
42
|
+
digitalout_val = digitalout_data.get("digitaloutval", 0)
|
|
43
|
+
if 0 < digitalout_id <= self.nDigitalOuts:
|
|
44
|
+
self.digitalOutValues[digitalout_id - 1] = digitalout_val
|
|
45
|
+
elif isinstance(digitalout_data, list):
|
|
46
|
+
# Multiple digitalouts format: [{"digitaloutid":1,"digitaloutval":1}, ...]
|
|
47
|
+
for digitalout in digitalout_data:
|
|
48
|
+
digitalout_id = digitalout.get("digitaloutid", 0)
|
|
49
|
+
digitalout_val = digitalout.get("digitaloutval", 0)
|
|
50
|
+
if 0 < digitalout_id <= self.nDigitalOuts:
|
|
51
|
+
self.digitalOutValues[digitalout_id - 1] = digitalout_val
|
|
52
|
+
|
|
53
|
+
# Call all registered callbacks for key 0
|
|
54
|
+
for callback in self._callbackPerKey[0]:
|
|
55
|
+
if callable(callback):
|
|
56
|
+
try:
|
|
57
|
+
callback(self.digitalOutValues)
|
|
58
|
+
except Exception as callback_error:
|
|
59
|
+
print(f"Error in callback execution: {callback_error}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print("Error in _callback_digitalout_status: ", e)
|
|
62
|
+
|
|
63
|
+
def register_callback(self, key, callbackfct):
|
|
64
|
+
''' register a callback function for a specific key - supports multiple callbacks per key '''
|
|
65
|
+
if key not in self._callbackPerKey:
|
|
66
|
+
self._callbackPerKey[key] = []
|
|
67
|
+
if callbackfct not in self._callbackPerKey[key]: # Avoid duplicate registrations
|
|
68
|
+
self._callbackPerKey[key].append(callbackfct)
|
|
69
|
+
print(f"Registered callback for key {key}. Total callbacks for this key: {len(self._callbackPerKey[key])}")
|
|
70
|
+
|
|
71
|
+
def setup_digitaloutpin(self, id=1, pin=4):
|
|
72
|
+
path = '/digitalout_set'
|
|
73
|
+
payload = {
|
|
74
|
+
"task": path,
|
|
75
|
+
"digitaloutid": id,
|
|
76
|
+
"digitaloutpin": pin,
|
|
77
|
+
}
|
|
78
|
+
r = self._parent.post_json(path, payload)
|
|
79
|
+
return r
|
|
80
|
+
|
|
81
|
+
def reset_triggertable(self):
|
|
82
|
+
path = '/digitalout_act'
|
|
83
|
+
payload = {
|
|
84
|
+
"task": path,
|
|
85
|
+
"digitaloutistriggerreset": 1,
|
|
86
|
+
}
|
|
87
|
+
r = self._parent.post_json(path, payload)
|
|
88
|
+
return r
|
|
89
|
+
|
|
90
|
+
def set_trigger(self, trigger1=False, delayOn1=0, delayOff1=0, trigger2=False, delayOn2=0, delayOff2=0, trigger3=False, delayOn3=0, delayOff3=0):
|
|
91
|
+
'''
|
|
92
|
+
this stats a trigger table with 3 triggers
|
|
93
|
+
|
|
94
|
+
Parameters:
|
|
95
|
+
|
|
96
|
+
'''
|
|
97
|
+
path = '/digitalout_act'
|
|
98
|
+
|
|
99
|
+
payload = {
|
|
100
|
+
"task":path,
|
|
101
|
+
"digitalout1TriggerDelayOn":delayOn1,
|
|
102
|
+
"digitalout1TriggerDelayOff":delayOff1,
|
|
103
|
+
"digitalout1IsTrigger":trigger1,
|
|
104
|
+
"digitalout2TriggerDelayOn":delayOn2,
|
|
105
|
+
"digitalout2TriggerDelayOff":delayOff2,
|
|
106
|
+
"digitalout2IsTrigger":trigger2,
|
|
107
|
+
"digitalout3TriggerDelayOn":delayOn3,
|
|
108
|
+
"digitalout3TriggerDelayOff":delayOff3,
|
|
109
|
+
"digitalout3IsTrigger":trigger3,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
r = self._parent.post_json(path, payload)
|
|
113
|
+
return r
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def sendTrigger(self, triggerId=0):
|
|
119
|
+
path = '/digital_act'
|
|
120
|
+
|
|
121
|
+
payload = {
|
|
122
|
+
"task": path,
|
|
123
|
+
"digitalid": triggerId,
|
|
124
|
+
"digitalval": -1,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
r = self._parent.post_json(path, payload)
|
|
128
|
+
return r
|
|
129
|
+
|
|
130
|
+
def get_digitalout(self, digitaloutid=1, timeout=1, is_blocking=True):
|
|
131
|
+
"""
|
|
132
|
+
Get the current value and pin of a digital output.
|
|
133
|
+
|
|
134
|
+
Parameters:
|
|
135
|
+
-----------
|
|
136
|
+
digitaloutid : int
|
|
137
|
+
ID of the digital output (1, 2, or 3)
|
|
138
|
+
timeout : float
|
|
139
|
+
Timeout for the request in seconds
|
|
140
|
+
is_blocking : bool
|
|
141
|
+
Whether to wait for a response
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
--------
|
|
145
|
+
dict or None
|
|
146
|
+
Response from the device containing digitalout status (id, val, pin)
|
|
147
|
+
"""
|
|
148
|
+
path = '/digitalout_get'
|
|
149
|
+
|
|
150
|
+
payload = {
|
|
151
|
+
"task": path,
|
|
152
|
+
"digitaloutid": digitaloutid
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=timeout)
|
|
156
|
+
return r
|
|
157
|
+
|
|
158
|
+
def set_digitalout(self, digitaloutid=1, digitaloutval=0, timeout=1, is_blocking=False):
|
|
159
|
+
"""
|
|
160
|
+
Set the value of a digital output. Use digitaloutval=-1 to trigger a pulse (HIGH->LOW).
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
-----------
|
|
164
|
+
digitaloutid : int
|
|
165
|
+
ID of the digital output (1, 2, or 3)
|
|
166
|
+
digitaloutval : int
|
|
167
|
+
Value to set (0=LOW, 1=HIGH, -1=pulse/trigger)
|
|
168
|
+
timeout : float
|
|
169
|
+
Timeout for the request in seconds
|
|
170
|
+
is_blocking : bool
|
|
171
|
+
Whether to wait for a response
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
--------
|
|
175
|
+
dict or None
|
|
176
|
+
Response from the device
|
|
177
|
+
"""
|
|
178
|
+
path = '/digitalout_act'
|
|
179
|
+
|
|
180
|
+
payload = {
|
|
181
|
+
"task": path,
|
|
182
|
+
"digitaloutid": digitaloutid,
|
|
183
|
+
"digitaloutval": digitaloutval
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=timeout)
|
|
187
|
+
return r
|
|
@@ -14,7 +14,8 @@ class Home(object):
|
|
|
14
14
|
|
|
15
15
|
self.nMotors = 4
|
|
16
16
|
self.isHomed = np.zeros((self.nMotors))
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
|
|
18
19
|
# register a callback function for the motor status on the serial loop
|
|
19
20
|
if hasattr(self._parent, "serial"):
|
|
20
21
|
self._parent.serial.register_callback(self._callback_home_status, pattern="home")
|
|
@@ -132,13 +133,17 @@ class Home(object):
|
|
|
132
133
|
)
|
|
133
134
|
"""
|
|
134
135
|
# Convert axes to stepper IDs
|
|
136
|
+
# First get logical motor index, then map through motorAxisOrder to get physical stepper ID
|
|
135
137
|
stepper_ids = []
|
|
138
|
+
motorAxisOrder = self._parent.motor.motorAxisOrder if hasattr(self._parent.motor , 'motorAxisOrder') else [0, 1, 2, 3]
|
|
136
139
|
for axis in axes:
|
|
137
140
|
if isinstance(axis, str):
|
|
138
|
-
|
|
141
|
+
logical_motor_index = self.xyztTo1230(axis)
|
|
139
142
|
else:
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
logical_motor_index = axis
|
|
144
|
+
# Map logical motor index to physical stepper ID
|
|
145
|
+
stepper_id = motorAxisOrder[logical_motor_index]
|
|
146
|
+
stepper_ids.append(stepper_id)
|
|
142
147
|
|
|
143
148
|
num_motors = len(stepper_ids)
|
|
144
149
|
|
|
@@ -171,7 +176,7 @@ class Home(object):
|
|
|
171
176
|
for i, stepper_id in enumerate(stepper_ids):
|
|
172
177
|
# Move in opposite direction
|
|
173
178
|
preMoveDirection = -directions[i]
|
|
174
|
-
preMoveDistanceSteps =
|
|
179
|
+
preMoveDistanceSteps = 100 # steps to move away from endstop
|
|
175
180
|
|
|
176
181
|
# Move away from endstop using the motor controller
|
|
177
182
|
if stepper_id == 1: # X
|
|
@@ -265,7 +270,7 @@ class Home(object):
|
|
|
265
270
|
preMoveDirection = -1
|
|
266
271
|
else:
|
|
267
272
|
preMoveDirection = 1
|
|
268
|
-
preMoveDistanceSteps =
|
|
273
|
+
preMoveDistanceSteps = 100 # steps to move away from endstop
|
|
269
274
|
|
|
270
275
|
# move away from endstop
|
|
271
276
|
if axis == 1 or axis == "X":
|
|
@@ -279,6 +284,10 @@ class Home(object):
|
|
|
279
284
|
else:
|
|
280
285
|
raise ValueError("Invalid axis. Use 'X', 'Y', 'Z', or 'A'.")
|
|
281
286
|
time.sleep(0.5)
|
|
287
|
+
|
|
288
|
+
# Map logical motor index to physical stepper ID through motorAxisOrder
|
|
289
|
+
stepper_id = self._parent.motor.motorAxisOrder[axis]
|
|
290
|
+
|
|
282
291
|
# construct json string
|
|
283
292
|
path = "/home_act"
|
|
284
293
|
|
|
@@ -287,7 +296,7 @@ class Home(object):
|
|
|
287
296
|
"home":{
|
|
288
297
|
"steppers": [
|
|
289
298
|
{
|
|
290
|
-
"stepperid":
|
|
299
|
+
"stepperid": stepper_id,
|
|
291
300
|
"timeout":endstoptimeout,
|
|
292
301
|
"speed":abs(speed),
|
|
293
302
|
"direction":direction,
|
|
@@ -111,7 +111,7 @@ class Laser(object):
|
|
|
111
111
|
|
|
112
112
|
}
|
|
113
113
|
#self._parent.logger.debug("Setting Laser "+str(channel)+", value: "+str(value))
|
|
114
|
-
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout
|
|
114
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=0.5)
|
|
115
115
|
return r
|
|
116
116
|
|
|
117
117
|
def set_laserpin(self, laserid=1, laserpin=0):
|