UC2-REST 0.2.0.31__tar.gz → 0.2.0.33__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 (47) hide show
  1. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/PKG-INFO +1 -1
  2. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/SOURCES.txt +3 -0
  4. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/UC2Client.py +33 -49
  5. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/__version__.py +1 -1
  6. uc2_rest-0.2.0.33/uc2rest/camera_trigger.py +197 -0
  7. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/can.py +3 -2
  8. uc2_rest-0.2.0.33/uc2rest/canota.py +801 -0
  9. uc2_rest-0.2.0.33/uc2rest/digitalin.py +122 -0
  10. uc2_rest-0.2.0.33/uc2rest/digitalout.py +187 -0
  11. uc2_rest-0.2.0.33/uc2rest/galvo.py +312 -0
  12. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/home.py +60 -10
  13. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/laser.py +1 -1
  14. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/motor.py +94 -10
  15. uc2_rest-0.2.0.33/uc2rest/motor_config.py +315 -0
  16. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/mserial.py +119 -134
  17. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/objective.py +4 -3
  18. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/rotator.py +1 -1
  19. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/state.py +1 -1
  20. uc2_rest-0.2.0.33/uc2rest/wifi.py +44 -0
  21. uc2_rest-0.2.0.31/uc2rest/canota.py +0 -256
  22. uc2_rest-0.2.0.31/uc2rest/digitalout.py +0 -65
  23. uc2_rest-0.2.0.31/uc2rest/galvo.py +0 -116
  24. uc2_rest-0.2.0.31/uc2rest/wifi.py +0 -98
  25. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/LICENSE +0 -0
  26. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/README.md +0 -0
  27. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/dependency_links.txt +0 -0
  28. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/not-zip-safe +0 -0
  29. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/requires.txt +0 -0
  30. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/UC2_REST.egg-info/top_level.txt +0 -0
  31. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/setup.cfg +0 -0
  32. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/setup.py +0 -0
  33. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/MockSerial.py +0 -0
  34. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/__init__.py +0 -0
  35. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/analog.py +0 -0
  36. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/camera.py +0 -0
  37. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/cmdrecorder.py +0 -0
  38. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/gripper.py +0 -0
  39. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/lcddisplay.py +0 -0
  40. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/ledmatrix.py +0 -0
  41. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/logger.py +0 -0
  42. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/message.py +0 -0
  43. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/modules.py +0 -0
  44. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/pid.py +0 -0
  45. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/slm.py +0 -0
  46. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/temperature.py +0 -0
  47. {uc2_rest-0.2.0.31 → uc2_rest-0.2.0.33}/uc2rest/utils.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.33
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.33
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,18 +29,20 @@ 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
33
+
34
+ # requests is no longer used for direct ESP communication (serial-only).
35
+ # Kept as optional import for other modules that may need HTTP (e.g. firmware downloads).
31
36
  try:
32
37
  import requests
33
- except:
34
- print("No requests available - running on pyscript?")
38
+ except Exception:
39
+ requests = None
35
40
 
36
41
  class UC2Client(object):
37
- # headers = {'ESP32-version': '*'}
38
42
  headers={"Content-Type":"application/json"}
39
43
  getmessage = ""
40
44
  is_connected = False
41
45
 
42
- is_wifi = False
43
46
  is_serial = False
44
47
  BAUDRATE = 115200
45
48
 
@@ -50,12 +53,13 @@ class UC2Client(object):
50
53
  This client connects to the UC2-REST microcontroller that can be found here
51
54
  https://github.com/openUC2/UC2-REST
52
55
 
56
+ Communication is via USB/serial only.
57
+ The host/port parameters are deprecated and will be ignored.
58
+
53
59
  generally speaking you send/receive JSON documents that will cause an:
54
60
  1. action => "/XXX_act"
55
61
  2. getting => "/XXX_get"
56
62
  3. setting => "/XXX_set"
57
-
58
- you can send commands through wifi/http or usb/serial
59
63
  '''
60
64
  if True: #logger is None:
61
65
  self.logger = Logger()
@@ -65,28 +69,27 @@ class UC2Client(object):
65
69
  # perhaps we are in the browser?
66
70
  self.isPyScript = isPyScript
67
71
 
68
- # initialize communication channel (# connect to wifi or usb)
72
+ # Deprecation notice for WiFi mode
73
+ if host is not None and serialport is None and SerialManager is None:
74
+ self.logger.warning(
75
+ "WiFi/HTTP communication has been removed. "
76
+ "Please use serialport= instead. Ignoring host parameter."
77
+ )
78
+
79
+ # initialize communication channel (serial only)
69
80
  if serialport is not None:
70
81
  # use USB connection
71
82
  self.serial = Serial(serialport, baudrate, parent=self, identity=identity, DEBUG=DEBUG, skipFirmwareCheck=skipFirmwareCheck)
72
83
  self.is_serial = True
73
84
  self.is_connected = self.serial.is_connected
74
85
  self.serial.DEBUG = DEBUG
75
- elif host is not None:
76
- # use client in wireless mode
77
- self.is_wifi = True
78
- self.host = host
79
- self.port = port
80
-
81
- # check if host is up
82
- self.logger.debug(f"Connecting to microscope {self.host}:{self.port}")
83
- #self.is_connected = self.isConnected()
84
86
  elif SerialManager is not None:
85
- # we are trying to access the controller from .a web browser
87
+ # we are trying to access the controller from a web browser
86
88
  self.serial = SerialManagerWrapper(SerialManager, parent=self)
87
89
  self.isPyScript = True
90
+ self.is_serial = True
88
91
  else:
89
- self.logger.error("No ESP32 device is connected - check IP or Serial port!")
92
+ self.logger.error("No ESP32 device is connected - please provide a serialport!")
90
93
 
91
94
 
92
95
  # import libraries depending on API version
@@ -149,49 +152,30 @@ class UC2Client(object):
149
152
  # initialize digital out
150
153
  self.digitalout = DigitalOut(self)
151
154
 
155
+ # initialize digital in
156
+ self.digitalin = DigitalIn(self)
157
+
152
158
  # initialize messaging
153
159
  self.message = Message(self)
160
+
161
+ # initialize camera trigger callback handler
162
+ self.camera_trigger = CameraTrigger(self)
154
163
 
155
164
  # initialize module controller
156
165
  self.modules = Modules(parent=self)
157
166
 
158
167
  def post_json(self, path, payload, getReturn=True, nResponses=1, timeout=1):
159
- if self.is_wifi:
160
- # FIXME: this is not working
161
- url = f"http://{self.host}:{self.port}{path}"
162
- try:
163
- if timeout==0: timeout=.2
164
- r = requests.post(url, json=payload, headers=self.headers, timeout=timeout)
165
- returnMessage = r.json()
166
- returnMessage["success"] = r.status_code==200
167
- except Exception as e:
168
- print(e)
169
- returnMessage = {}
170
- returnMessage["error"] = str(e)
171
- returnMessage["success"] = 0
172
- return returnMessage
173
- elif self.is_serial or self.isPyScript:
174
- if timeout <=0:
175
- getReturn = False
176
- return self.serial.post_json(path, payload, getReturn=getReturn, nResponses=nResponses, timeout=timeout)
177
- else:
178
- self.logger.error("No ESP32 device is connected - check IP or Serial port!")
179
- return None
168
+ if timeout <=0:
169
+ getReturn = False
170
+ return self.serial.post_json(path, payload, getReturn=getReturn, nResponses=nResponses, timeout=timeout)
180
171
 
181
172
  def get_json(self, path, getReturn=True, timeout=1):
182
- if self.is_wifi:
183
- # FIXME: this is not working
184
- url = f"http://{self.host}:{self.port}{path}"
185
- r = requests.get(url, headers=self.headers, timeout=timeout)
186
- return r.json()
187
- elif self.is_serial or self.isPyScript:
188
- # timeout is not used anymore
189
- if timeout <=0:
173
+ if self.is_serial or self.isPyScript:
174
+ if timeout <= 0:
190
175
  getReturn = False
191
176
  return self.serial.post_json(path, payload=None, getReturn=getReturn, nResponses=1, timeout=timeout)
192
- #return self.serial.read_json()<
193
177
  else:
194
- self.logger.error("No ESP32 device is connected - check IP or Serial port!")
178
+ self.logger.error("No ESP32 device is connected - serial not initialized!")
195
179
  return None
196
180
 
197
181
  def setDebugging(self, debug=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.31"
9
+ __version__ = "v0.2.0.33"
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,