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.
Files changed (44) hide show
  1. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/PKG-INFO +1 -1
  2. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/SOURCES.txt +3 -0
  4. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/UC2Client.py +8 -0
  5. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/__version__.py +1 -1
  6. uc2_rest-0.2.0.32/uc2rest/camera_trigger.py +197 -0
  7. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/can.py +3 -2
  8. uc2_rest-0.2.0.32/uc2rest/digitalin.py +122 -0
  9. uc2_rest-0.2.0.32/uc2rest/digitalout.py +187 -0
  10. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/home.py +16 -7
  11. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/laser.py +1 -1
  12. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/motor.py +93 -7
  13. uc2_rest-0.2.0.32/uc2rest/motor_config.py +315 -0
  14. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/mserial.py +104 -92
  15. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/objective.py +4 -3
  16. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/state.py +1 -1
  17. uc2_rest-0.2.0.31/uc2rest/digitalout.py +0 -65
  18. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/LICENSE +0 -0
  19. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/README.md +0 -0
  20. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/dependency_links.txt +0 -0
  21. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/not-zip-safe +0 -0
  22. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/requires.txt +0 -0
  23. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/UC2_REST.egg-info/top_level.txt +0 -0
  24. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/setup.cfg +0 -0
  25. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/setup.py +0 -0
  26. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/MockSerial.py +0 -0
  27. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/__init__.py +0 -0
  28. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/analog.py +0 -0
  29. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/camera.py +0 -0
  30. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/canota.py +0 -0
  31. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/cmdrecorder.py +0 -0
  32. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/galvo.py +0 -0
  33. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/gripper.py +0 -0
  34. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/lcddisplay.py +0 -0
  35. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/ledmatrix.py +0 -0
  36. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/logger.py +0 -0
  37. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/message.py +0 -0
  38. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/modules.py +0 -0
  39. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/pid.py +0 -0
  40. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/rotator.py +0 -0
  41. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/slm.py +0 -0
  42. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/temperature.py +0 -0
  43. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/utils.py +0 -0
  44. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.32}/uc2rest/wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.31
3
+ Version: 0.2.0.32
4
4
  Summary: This pacage will help you to drive the ESP32-driven microscopy control modules from UC2
5
5
  Home-page: https://github.com/openUC2/UC2-REST
6
6
  Author: Benedict Diederich
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.31
3
+ Version: 0.2.0.32
4
4
  Summary: This pacage will help you to drive the ESP32-driven microscopy control modules from UC2
5
5
  Home-page: https://github.com/openUC2/UC2-REST
6
6
  Author: Benedict Diederich
@@ -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.31"
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
+ }
@@ -129,9 +129,10 @@ class CAN(object):
129
129
  "tx": 17
130
130
  }
131
131
  """
132
- path = "/can_get"
132
+ path = "/can_act"
133
133
  payload = {
134
- "task": path
134
+ "task": path,
135
+ "scan": True
135
136
  }
136
137
  return self._parent.post_json(
137
138
  path,
@@ -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
- axis_id = self.xyztTo1230(axis)
141
+ logical_motor_index = self.xyztTo1230(axis)
139
142
  else:
140
- axis_id = axis
141
- stepper_ids.append(axis_id)
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 = 1000 # steps to move away from endstop
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 = 1000 # steps to move away from endstop
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": axis,
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=.5)
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):