UC2-REST 0.2.0.29__tar.gz → 0.2.0.31__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.29 → uc2_rest-0.2.0.31}/PKG-INFO +1 -1
  2. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/SOURCES.txt +1 -0
  4. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/UC2Client.py +5 -1
  5. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/__version__.py +1 -1
  6. uc2_rest-0.2.0.31/uc2rest/can.py +142 -0
  7. uc2_rest-0.2.0.31/uc2rest/canota.py +256 -0
  8. uc2_rest-0.2.0.31/uc2rest/home.py +305 -0
  9. uc2_rest-0.2.0.31/uc2rest/laser.py +143 -0
  10. uc2_rest-0.2.0.31/uc2rest/logger.py +45 -0
  11. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/motor.py +545 -83
  12. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/mserial.py +14 -9
  13. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/objective.py +33 -32
  14. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/state.py +1 -0
  15. uc2_rest-0.2.0.29/uc2rest/can.py +0 -33
  16. uc2_rest-0.2.0.29/uc2rest/home.py +0 -105
  17. uc2_rest-0.2.0.29/uc2rest/laser.py +0 -77
  18. uc2_rest-0.2.0.29/uc2rest/logger.py +0 -9
  19. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/LICENSE +0 -0
  20. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/README.md +0 -0
  21. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/dependency_links.txt +0 -0
  22. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/not-zip-safe +0 -0
  23. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/requires.txt +0 -0
  24. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/top_level.txt +0 -0
  25. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/setup.cfg +0 -0
  26. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/setup.py +0 -0
  27. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/MockSerial.py +0 -0
  28. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/__init__.py +0 -0
  29. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/analog.py +0 -0
  30. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/camera.py +0 -0
  31. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/cmdrecorder.py +0 -0
  32. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/digitalout.py +0 -0
  33. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/galvo.py +0 -0
  34. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/gripper.py +0 -0
  35. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/lcddisplay.py +0 -0
  36. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/ledmatrix.py +0 -0
  37. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/message.py +0 -0
  38. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/modules.py +0 -0
  39. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/pid.py +0 -0
  40. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/rotator.py +0 -0
  41. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/slm.py +0 -0
  42. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/temperature.py +0 -0
  43. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/uc2rest/utils.py +0 -0
  44. {uc2_rest-0.2.0.29 → uc2_rest-0.2.0.31}/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.29
3
+ Version: 0.2.0.31
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.29
3
+ Version: 0.2.0.31
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
@@ -14,6 +14,7 @@ uc2rest/__version__.py
14
14
  uc2rest/analog.py
15
15
  uc2rest/camera.py
16
16
  uc2rest/can.py
17
+ uc2rest/canota.py
17
18
  uc2rest/cmdrecorder.py
18
19
  uc2rest/digitalout.py
19
20
  uc2rest/galvo.py
@@ -27,6 +27,7 @@ from .cmdrecorder import cmdRecorder
27
27
  from .temperature import Temperature
28
28
  from .message import Message
29
29
  from .can import CAN
30
+ from .canota import CANOTA
30
31
  try:
31
32
  import requests
32
33
  except:
@@ -56,7 +57,7 @@ class UC2Client(object):
56
57
 
57
58
  you can send commands through wifi/http or usb/serial
58
59
  '''
59
- if logger is None:
60
+ if True: #logger is None:
60
61
  self.logger = Logger()
61
62
  else:
62
63
  self.logger = logger
@@ -109,6 +110,9 @@ class UC2Client(object):
109
110
  # initialize CAN
110
111
  self.can = CAN(self)
111
112
 
113
+ # initialize CAN OTA
114
+ self.canota = CANOTA(self)
115
+
112
116
  # initialize gripper
113
117
  self.gripper = Gripper(self)
114
118
 
@@ -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.29"
9
+ __version__ = "v0.2.0.31"
10
10
  __author__ = 'Benedict Diederich'
11
11
  __author_email__ = 'benedictdied@gmail.com'
12
12
  __license__ = 'GPL v3'
@@ -0,0 +1,142 @@
1
+ class CAN(object):
2
+ def __init__(self, parent):
3
+ """
4
+ CANController handles sending commands to a remote CAN device via the parent post_json interface.
5
+
6
+ :param parent: Parent object with post_json(path, payload, getReturn, timeout, nResponses)
7
+ """
8
+ self._parent = parent
9
+
10
+ # Store latest scan results
11
+ self.scanResults = []
12
+ self.deviceCount = 0
13
+
14
+ # Register a callback function for the CAN status on the serial loop
15
+ if hasattr(self._parent, "serial"):
16
+ self._parent.serial.register_callback(self._callback_can_status, pattern="scan")
17
+
18
+ # Announce a function that is called when we receive a CAN scan update through the callback
19
+ self._callbackPerKey = {}
20
+ self.nCallbacks = 10
21
+ self._callbackPerKey = self.init_callback_functions(nCallbacks=self.nCallbacks)
22
+ print(self._callbackPerKey)
23
+
24
+ def init_callback_functions(self, nCallbacks=10):
25
+ """Initialize the callback functions."""
26
+ _callbackPerKey = {}
27
+ self.nCallbacks = nCallbacks
28
+ for i in range(nCallbacks):
29
+ _callbackPerKey[i] = []
30
+ return _callbackPerKey
31
+
32
+ def _callback_can_status(self, data):
33
+ """
34
+ Cast the json in the form:
35
+ {
36
+ "scan": [
37
+ {"canId": 20, "deviceType": 1, "status": 0, "deviceTypeStr": "laser", "statusStr": "idle"},
38
+ {"canId": 10, "deviceType": 0, "status": 0, "deviceTypeStr": "motor", "statusStr": "idle"}
39
+ ],
40
+ "qid": 2,
41
+ "count": 1
42
+ }
43
+ into the scan results array.
44
+ """
45
+ try:
46
+ if "scan" in data:
47
+ self.scanResults = data["scan"]
48
+ self.deviceCount = data.get("count", len(self.scanResults))
49
+
50
+ # Call registered callback function with scan results
51
+ if callable(self._callbackPerKey[0]):
52
+ self._callbackPerKey[0](self.scanResults)
53
+ except Exception as e:
54
+ print("Error in _callback_can_status: ", e)
55
+
56
+ def register_callback(self, key, callbackfct):
57
+ """Register a callback function for a specific key."""
58
+ self._callbackPerKey[key] = callbackfct
59
+
60
+ def reboot_remote(self, qid=1, can_address=0, isBlocking=False, timeout=2):
61
+ """
62
+ Send a reboot signal to the remote CAN device.
63
+
64
+ :param qid: Query ID for the CAN command (default: 1)
65
+ :param isBlocking: If True, wait for response
66
+ :param timeout: Timeout for the command in seconds
67
+ :param can_address: Address of the CAN device to reboot (0 is master)
68
+ :return: Response from the device
69
+ """
70
+ path = "/can_act"
71
+ payload = {
72
+ "task": path,
73
+ "restart": int(can_address)
74
+ }
75
+ nResponses = 1 if isBlocking else 0
76
+ # Send the payload to the parent, which handles the actual communication
77
+ return self._parent.post_json(
78
+ path,
79
+ payload,
80
+ getReturn=isBlocking,
81
+ timeout=timeout if isBlocking else 0,
82
+ nResponses=nResponses
83
+ )
84
+
85
+ def scan(self, qid=1, timeout=5):
86
+ """
87
+ Scan the CAN bus for connected devices.
88
+
89
+ :param qid: Query ID for the CAN command (default: 1)
90
+ :param timeout: Timeout for the scan in seconds (default: 5)
91
+ :return: Response containing scan results with device information
92
+ Example: {
93
+ "scan": [
94
+ {"canId": 10, "deviceType": 0, "deviceTypeStr": "motor", "status": 0, "statusStr": "idle"},
95
+ {"canId": 20, "deviceType": 1, "deviceTypeStr": "laser", "status": 0, "statusStr": "idle"}
96
+ ],
97
+ "qid": 1,
98
+ "count": 2
99
+ }
100
+ """
101
+ path = "/can_act"
102
+ payload = {
103
+ "task": path,
104
+ "scan": True,
105
+ "qid": qid
106
+ }
107
+ return self._parent.post_json(
108
+ path,
109
+ payload,
110
+ getReturn=True,
111
+ timeout=timeout,
112
+ nResponses=2
113
+ )
114
+
115
+ def get_available_devices(self, timeout=2):
116
+ """
117
+ Get list of available CAN devices.
118
+
119
+ :param timeout: Timeout for the command in seconds (default: 2)
120
+ :return: Response containing available CAN IDs and other bus information
121
+ Example: {
122
+ "input": {"task": "/can_get"},
123
+ "address": 1,
124
+ "addresspref": 1,
125
+ "addressgetcan": 1,
126
+ "nonworking": [0,0,0,...],
127
+ "available": [20,0,0,...],
128
+ "rx": 18,
129
+ "tx": 17
130
+ }
131
+ """
132
+ path = "/can_get"
133
+ payload = {
134
+ "task": path
135
+ }
136
+ return self._parent.post_json(
137
+ path,
138
+ payload,
139
+ getReturn=True,
140
+ timeout=timeout,
141
+ nResponses=2
142
+ )
@@ -0,0 +1,256 @@
1
+ import time
2
+ import json
3
+
4
+
5
+ gTIMEOUT = 10 # seconds to wait for a response from the ESP32
6
+
7
+
8
+ class CANOTA(object):
9
+ """
10
+ CAN OTA (Over-The-Air) update controller for UC2 satellite devices.
11
+
12
+ This class handles sending OTA commands to CAN slave devices and processing
13
+ the responses through callbacks. It manages the WiFi connection setup and
14
+ OTA server initialization on remote devices.
15
+
16
+ The OTA process involves:
17
+ 1. Sending OTA command with WiFi credentials to a specific CAN device
18
+ 2. Device connects to WiFi and starts ArduinoOTA server
19
+ 3. Device sends back acknowledgment and IP address via serial
20
+ 4. Device becomes available as UC2-CAN-<HEXID>.local for firmware upload
21
+ """
22
+
23
+ def __init__(self, parent=None, nCallbacks=10):
24
+ """
25
+ Initialize the CAN OTA controller.
26
+
27
+ :param parent: Parent UC2Client object with post_json and serial interfaces
28
+ :param nCallbacks: Number of callback slots to initialize (default: 10)
29
+ """
30
+ self._parent = parent
31
+ self.nCallbacks = nCallbacks
32
+
33
+ # Initialize callback functions for different types of OTA events
34
+ self.init_callback_functions(self.nCallbacks)
35
+
36
+ # Register callback function for OTA status messages on the serial loop
37
+ if hasattr(self._parent, "serial"):
38
+ self._parent.serial.register_callback(self._callback_ota_status, pattern="ota")
39
+
40
+ def init_callback_functions(self, nCallbacks=10):
41
+ """
42
+ Initialize the callback function dictionary.
43
+
44
+ :param nCallbacks: Number of callback slots to create
45
+ """
46
+ self._callbackPerKey = {}
47
+ self.nCallbacks = nCallbacks
48
+ for i in range(nCallbacks):
49
+ self._callbackPerKey[i] = None
50
+
51
+ def _callback_ota_status(self, data):
52
+ """
53
+ Process incoming OTA status messages from CAN devices.
54
+
55
+ Expected message format:
56
+ {
57
+ "ota": {
58
+ "canId": 20,
59
+ "status": 0,
60
+ "statusMsg": "Success",
61
+ "ip": "192.168.2.137",
62
+ "hostname": "UC2-CAN-14.local"
63
+ }
64
+ }
65
+
66
+ Status codes:
67
+ - 0: Success (WiFi connected, OTA server started)
68
+ - 1: WiFi connection failed
69
+ - 2: OTA start failed
70
+
71
+ :param data: JSON data containing OTA status information
72
+ """
73
+ try:
74
+ ota_data = data["ota"]
75
+ can_id = ota_data.get("canId")
76
+ status = ota_data.get("status")
77
+ status_msg = ota_data.get("statusMsg", "")
78
+ ip_address = ota_data.get("ip", "")
79
+ hostname = ota_data.get("hostname", "")
80
+
81
+ # Create a structured response for callbacks
82
+ ota_response = {
83
+ "canId": can_id,
84
+ "status": status,
85
+ "statusMsg": status_msg,
86
+ "ip": ip_address,
87
+ "hostname": hostname,
88
+ "success": status == 0
89
+ }
90
+
91
+ # Trigger callbacks for different events
92
+ # Key 0: General OTA status updates
93
+ if callable(self._callbackPerKey[0]):
94
+ self._callbackPerKey[0](ota_response)
95
+
96
+ # Key for specific CAN ID (use can_id % nCallbacks to avoid overflow)
97
+ if can_id is not None:
98
+ callback_key = (can_id % (self.nCallbacks - 1)) + 1 # Reserve key 0 for general
99
+ if callable(self._callbackPerKey[callback_key]):
100
+ self._callbackPerKey[callback_key](ota_response)
101
+
102
+ except Exception as e:
103
+ print(f"Error in _callback_ota_status: {e}")
104
+
105
+ def register_callback(self, key, callback_function):
106
+ """
107
+ Register a callback function for OTA events.
108
+
109
+ :param key: Callback key (0 for general events, 1-9 for specific CAN IDs)
110
+ :param callback_function: Function to call when OTA event occurs
111
+ Function should accept one parameter: ota_response dict
112
+
113
+ Example:
114
+ def my_ota_callback(ota_response):
115
+ if ota_response["success"]:
116
+ print(f"Device {ota_response['canId']} ready at {ota_response['ip']}")
117
+ else:
118
+ print(f"OTA failed for device {ota_response['canId']}: {ota_response['statusMsg']}")
119
+
120
+ ESP32.canota.register_callback(0, my_ota_callback)
121
+ """
122
+ if 0 <= key < self.nCallbacks:
123
+ self._callbackPerKey[key] = callback_function
124
+ else:
125
+ raise ValueError(f"Callback key must be between 0 and {self.nCallbacks-1}")
126
+
127
+ def start_ota_update(self, can_id, ssid, password, timeout=300000, is_blocking=False,
128
+ response_timeout=gTIMEOUT):
129
+ """
130
+ Send OTA command to a specific CAN slave device.
131
+
132
+ This tells the slave to connect to WiFi and start an OTA server.
133
+ The device will become available as UC2-CAN-<HEXID>.local for firmware upload.
134
+
135
+ :param can_id: CAN ID of the target device (e.g., 11 for Motor X, 20 for Laser)
136
+ :param ssid: WiFi network name
137
+ :param password: WiFi password
138
+ :param timeout: OTA timeout in milliseconds (default: 300000 = 5 minutes)
139
+ :param is_blocking: If True, wait for acknowledgment response
140
+ :param response_timeout: Timeout for response in seconds
141
+ :return: Response from the device (if blocking)
142
+
143
+ Examples:
144
+ # Motor X (CAN ID 11)
145
+ ESP32.canota.start_ota_update(11, "WiFi", "pass123")
146
+
147
+ # Laser (CAN ID 20) with custom timeout
148
+ ESP32.canota.start_ota_update(20, "WiFi", "pass123", timeout=600000)
149
+ """
150
+ path = "/can_act"
151
+ payload = {
152
+ "task": path,
153
+ "ota": {
154
+ "canid": can_id,
155
+ "ssid": ssid,
156
+ "password": password,
157
+ "timeout": timeout
158
+ }
159
+ }
160
+
161
+ # Send the payload to the parent, which handles the actual communication
162
+ nResponses = 1 if is_blocking else 0
163
+ return self._parent.post_json(
164
+ path,
165
+ payload,
166
+ getReturn=is_blocking,
167
+ timeout=response_timeout if is_blocking else 0,
168
+ nResponses=nResponses
169
+ )
170
+
171
+ def start_motor_ota(self, motor_axis, ssid, password, timeout=300000, is_blocking=False):
172
+ """
173
+ Convenience method for starting OTA on motor controllers.
174
+
175
+ :param motor_axis: Motor axis ("X", "Y", "Z") or CAN ID (11, 12, 13)
176
+ :param ssid: WiFi network name
177
+ :param password: WiFi password
178
+ :param timeout: OTA timeout in milliseconds
179
+ :param is_blocking: If True, wait for acknowledgment response
180
+ :return: Response from the device
181
+
182
+ Examples:
183
+ ESP32.canota.start_motor_ota("X", "WiFi", "pass123")
184
+ ESP32.canota.start_motor_ota(11, "WiFi", "pass123") # Same as above
185
+ """
186
+ # Map motor axes to CAN IDs
187
+ motor_can_ids = {
188
+ "X": 11,
189
+ "Y": 12,
190
+ "Z": 13
191
+ }
192
+
193
+ if isinstance(motor_axis, str):
194
+ motor_axis = motor_axis.upper()
195
+ if motor_axis not in motor_can_ids:
196
+ raise ValueError(f"Invalid motor axis '{motor_axis}'. Use 'X', 'Y', or 'Z'")
197
+ can_id = motor_can_ids[motor_axis]
198
+ else:
199
+ can_id = motor_axis
200
+
201
+ return self.start_ota_update(can_id, ssid, password, timeout, is_blocking)
202
+
203
+ def start_led_ota(self, ssid, password, timeout=300000, is_blocking=False):
204
+ """
205
+ Convenience method for starting OTA on LED controller (CAN ID 30).
206
+
207
+ :param ssid: WiFi network name
208
+ :param password: WiFi password
209
+ :param timeout: OTA timeout in milliseconds
210
+ :param is_blocking: If True, wait for acknowledgment response
211
+ :return: Response from the device
212
+ """
213
+ return self.start_ota_update(30, ssid, password, timeout, is_blocking)
214
+
215
+ def start_laser_ota(self, laser_id=0, ssid="", password="", timeout=300000, is_blocking=False):
216
+ """
217
+ Convenience method for starting OTA on laser controller.
218
+
219
+ :param laser_id: Laser ID (0 for main laser, maps to CAN ID 20)
220
+ :param ssid: WiFi network name
221
+ :param password: WiFi password
222
+ :param timeout: OTA timeout in milliseconds
223
+ :param is_blocking: If True, wait for acknowledgment response
224
+ :return: Response from the device
225
+ """
226
+ # Map laser ID to CAN ID (for now, laser 0 = CAN ID 20)
227
+ can_id = 20 + laser_id
228
+ return self.start_ota_update(can_id, ssid, password, timeout, is_blocking)
229
+
230
+ def get_ota_hostname(self, can_id):
231
+ """
232
+ Generate the expected hostname for a CAN device in OTA mode.
233
+
234
+ :param can_id: CAN ID of the device
235
+ :return: Expected hostname (e.g., "UC2-CAN-14.local" for CAN ID 20)
236
+ """
237
+ hex_id = format(can_id, 'X') # Convert to hexadecimal
238
+ return f"UC2-CAN-{hex_id}.local"
239
+
240
+ def get_platformio_upload_command(self, can_id, project_path="."):
241
+ """
242
+ Generate the PlatformIO upload command for OTA firmware update.
243
+
244
+ :param can_id: CAN ID of the target device
245
+ :param project_path: Path to the PlatformIO project (default: current directory)
246
+ :return: PlatformIO upload command string
247
+
248
+ Example:
249
+ cmd = ESP32.canota.get_platformio_upload_command(20)
250
+ # Returns: "platformio run -t upload --upload-port UC2-CAN-14.local"
251
+ """
252
+ hostname = self.get_ota_hostname(can_id)
253
+ if project_path != ".":
254
+ return f"platformio run -t upload --upload-port {hostname} -d {project_path}"
255
+ else:
256
+ return f"platformio run -t upload --upload-port {hostname}"