UC2-REST 0.2.0.18__tar.gz → 0.2.0.20__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.18 → uc2_rest-0.2.0.20}/PKG-INFO +1 -1
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/PKG-INFO +1 -1
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/SOURCES.txt +2 -0
- uc2_rest-0.2.0.20/uc2rest/MockSerial.py +74 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/UC2Client.py +6 -3
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/__version__.py +1 -1
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/home.py +1 -1
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/laser.py +1 -2
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/ledmatrix.py +4 -4
- uc2_rest-0.2.0.20/uc2rest/message.py +90 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/motor.py +24 -15
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/mserial.py +93 -31
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/state.py +12 -0
- uc2_rest-0.2.0.20/uc2rest/temperature.py +93 -0
- UC2-REST-0.2.0.18/uc2rest/temperature.py +0 -54
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/LICENSE +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/README.md +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/not-zip-safe +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/requires.txt +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/top_level.txt +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/setup.cfg +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/setup.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/__init__.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/analog.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/camera.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/cmdrecorder.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/config.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/config_.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/digitalout.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/galvo.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/logger.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/modules.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/pid.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/rotator.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/slm.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/updater.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/utils.py +0 -0
- {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/wifi.py +0 -0
|
@@ -7,6 +7,7 @@ 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
|
+
uc2rest/MockSerial.py
|
|
10
11
|
uc2rest/UC2Client.py
|
|
11
12
|
uc2rest/__init__.py
|
|
12
13
|
uc2rest/__version__.py
|
|
@@ -21,6 +22,7 @@ uc2rest/home.py
|
|
|
21
22
|
uc2rest/laser.py
|
|
22
23
|
uc2rest/ledmatrix.py
|
|
23
24
|
uc2rest/logger.py
|
|
25
|
+
uc2rest/message.py
|
|
24
26
|
uc2rest/modules.py
|
|
25
27
|
uc2rest/motor.py
|
|
26
28
|
uc2rest/mserial.py
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from threading import Thread
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
class MockSerial:
|
|
6
|
+
def __init__(self, port, baudrate, timeout=1):
|
|
7
|
+
self.port = port
|
|
8
|
+
self.baudrate = baudrate
|
|
9
|
+
self.timeout = timeout
|
|
10
|
+
self.is_open = False
|
|
11
|
+
self.data_buffer = []
|
|
12
|
+
self.thread = Thread(target=self._simulate_data)
|
|
13
|
+
self.thread.daemon = True
|
|
14
|
+
self.thread.start()
|
|
15
|
+
self.is_open = True
|
|
16
|
+
self.manufacturer = "UC2Mock"
|
|
17
|
+
self.BAUDRATES = -1
|
|
18
|
+
|
|
19
|
+
def flush(self):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def isOpen(self):
|
|
23
|
+
return self.is_open
|
|
24
|
+
|
|
25
|
+
def open(self):
|
|
26
|
+
self.is_open = True
|
|
27
|
+
|
|
28
|
+
def close(self):
|
|
29
|
+
self.is_open = False
|
|
30
|
+
|
|
31
|
+
def readline(self, timeout=1):
|
|
32
|
+
if not self.is_open:
|
|
33
|
+
raise Exception("Device not connected")
|
|
34
|
+
if len(self.data_buffer) == 0:
|
|
35
|
+
return b''
|
|
36
|
+
data = self.data_buffer
|
|
37
|
+
self.data_buffer = self.data_buffer
|
|
38
|
+
time.sleep(.05)
|
|
39
|
+
return bytes(data)
|
|
40
|
+
|
|
41
|
+
def read(self, num_bytes):
|
|
42
|
+
if not self.is_open:
|
|
43
|
+
raise Exception("Device not connected")
|
|
44
|
+
if len(self.data_buffer) == 0:
|
|
45
|
+
return b''
|
|
46
|
+
data = self.data_buffer[:num_bytes]
|
|
47
|
+
self.data_buffer = self.data_buffer[num_bytes:]
|
|
48
|
+
return bytes(data)
|
|
49
|
+
|
|
50
|
+
def write(self, data):
|
|
51
|
+
if not self.is_open:
|
|
52
|
+
raise Exception("Device not connected")
|
|
53
|
+
pass # Do nothing, as it's a mock
|
|
54
|
+
|
|
55
|
+
def _simulate_data(self):
|
|
56
|
+
while self.is_open:
|
|
57
|
+
if random.random() < 0.2: # Simulate occasional data availability
|
|
58
|
+
self.data_buffer.extend([random.randint(0, 255) for _ in range(10)])
|
|
59
|
+
time.sleep(0.1)
|
|
60
|
+
|
|
61
|
+
def __enter__(self):
|
|
62
|
+
self.open()
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
66
|
+
self.close()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == '__main__':
|
|
70
|
+
with MockSerial("COM1", 9600) as ser:
|
|
71
|
+
while True:
|
|
72
|
+
data = ser.readline()
|
|
73
|
+
print(data)
|
|
74
|
+
time.sleep(1)
|
|
@@ -23,7 +23,7 @@ from .rotator import Rotator
|
|
|
23
23
|
from .logger import Logger
|
|
24
24
|
from .cmdrecorder import cmdRecorder
|
|
25
25
|
from .temperature import Temperature
|
|
26
|
-
|
|
26
|
+
from .message import Message
|
|
27
27
|
try:
|
|
28
28
|
import requests
|
|
29
29
|
except:
|
|
@@ -39,7 +39,7 @@ class UC2Client(object):
|
|
|
39
39
|
is_serial = False
|
|
40
40
|
|
|
41
41
|
# BAUDRATE = 500000
|
|
42
|
-
BAUDRATE =
|
|
42
|
+
BAUDRATE = 500000
|
|
43
43
|
|
|
44
44
|
def __init__(self, host=None, port=31950, serialport=None, identity="UC2_Feather", baudrate=BAUDRATE, NLeds=64, SerialManager=None, DEBUG=False, logger=None):
|
|
45
45
|
'''
|
|
@@ -141,6 +141,9 @@ class UC2Client(object):
|
|
|
141
141
|
# initialize digital out
|
|
142
142
|
self.digitalout = DigitalOut(self)
|
|
143
143
|
|
|
144
|
+
# initialize messaging
|
|
145
|
+
self.message = Message(self)
|
|
146
|
+
|
|
144
147
|
# initialize config
|
|
145
148
|
if False: # not self.isPyScript:
|
|
146
149
|
self.config = config(self)
|
|
@@ -173,7 +176,7 @@ class UC2Client(object):
|
|
|
173
176
|
elif self.is_serial or self.isPyScript:
|
|
174
177
|
if timeout <=0:
|
|
175
178
|
getReturn = False
|
|
176
|
-
return self.serial.post_json(path, payload, getReturn=getReturn,
|
|
179
|
+
return self.serial.post_json(path, payload, getReturn=getReturn, nResponses=nResponses, timeout=timeout)
|
|
177
180
|
else:
|
|
178
181
|
self.logger.error("No ESP32 device is connected - check IP or Serial port!")
|
|
179
182
|
return None
|
|
@@ -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.20"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|
|
@@ -6,12 +6,11 @@ class Laser(object):
|
|
|
6
6
|
self.filter_pos_2 = 0
|
|
7
7
|
self.filter_pos_3 = 0
|
|
8
8
|
self.filter_pos_LED = 0
|
|
9
|
-
self._parent.logger.debug("Attention, lasers are on channels 1,2,3")
|
|
10
9
|
|
|
11
10
|
def set_laser(self, channel=1, value=0, auto_filterswitch=False,
|
|
12
11
|
filter_axis=-1, filter_position = None,
|
|
13
12
|
despeckleAmplitude = 0.,
|
|
14
|
-
despecklePeriod=10, timeout=20, is_blocking =
|
|
13
|
+
despecklePeriod=10, timeout=20, is_blocking = False):
|
|
15
14
|
if channel not in (0,1,2,3):
|
|
16
15
|
if channel=="R":
|
|
17
16
|
channel = 1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
-
gTimeout =
|
|
4
|
+
gTimeout = 2
|
|
5
5
|
class LedMatrix(object):
|
|
6
6
|
def __init__(self, parent, NLeds=64):
|
|
7
7
|
#TOOD: This is for the LED matrix only!
|
|
@@ -73,7 +73,7 @@ class LedMatrix(object):
|
|
|
73
73
|
self.currentLedArrayMode = "array"
|
|
74
74
|
return r
|
|
75
75
|
|
|
76
|
-
def send_LEDMatrix_full(self, intensity = (255,255,255), getReturn=
|
|
76
|
+
def send_LEDMatrix_full(self, intensity = (255,255,255), getReturn=True, timeout=gTimeout):
|
|
77
77
|
'''
|
|
78
78
|
set all LEDs with te same RGB value: intensity=(255,255,255)
|
|
79
79
|
'''
|
|
@@ -168,7 +168,7 @@ class LedMatrix(object):
|
|
|
168
168
|
|
|
169
169
|
return self.ledpattern
|
|
170
170
|
|
|
171
|
-
def setAll(self, state, intensity=None, getReturn=
|
|
171
|
+
def setAll(self, state, intensity=None, getReturn=True):
|
|
172
172
|
# fast addressing
|
|
173
173
|
# turns on all LEDs at a certain intensity
|
|
174
174
|
state = np.sum(state)>0
|
|
@@ -179,7 +179,7 @@ class LedMatrix(object):
|
|
|
179
179
|
self.ledpattern = state*np.ones((self.NLeds, 3))
|
|
180
180
|
return self.ledpattern
|
|
181
181
|
|
|
182
|
-
def setIntensity(self, intensity, getReturn=
|
|
182
|
+
def setIntensity(self, intensity, getReturn=True):
|
|
183
183
|
self.intensity = intensity
|
|
184
184
|
self.setPattern(getReturn=getReturn)
|
|
185
185
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
gTIMEOUT = 1 # seconds to wait for a response from the ESP32
|
|
7
|
+
class Message(object):
|
|
8
|
+
'''
|
|
9
|
+
This class only parses incoming messages from the ESP32 that can be used e.g. for triggering events such as taking an image
|
|
10
|
+
or converting hardware inputs such as button presses to software events
|
|
11
|
+
'''
|
|
12
|
+
def __init__(self, parent=None, nCallbacks = 10):
|
|
13
|
+
self._parent = parent
|
|
14
|
+
self.nCallbacks = nCallbacks
|
|
15
|
+
# initialize the callback functions
|
|
16
|
+
self.init_callback_functions(self.nCallbacks)
|
|
17
|
+
|
|
18
|
+
# register a callback function for the motor status on the serial loop
|
|
19
|
+
if hasattr(self._parent, "serial"):
|
|
20
|
+
self._parent.serial.register_callback(self._callback_message, pattern="message")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _callback_message(self, data):
|
|
24
|
+
''' cast the json in the form:
|
|
25
|
+
{
|
|
26
|
+
"message": {
|
|
27
|
+
"key": 1,
|
|
28
|
+
"data": 1
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
into seperated actions that follow this pattern:
|
|
32
|
+
data lookup:
|
|
33
|
+
{
|
|
34
|
+
key | meaning | value
|
|
35
|
+
--------------------------------
|
|
36
|
+
1 | snap image | 0
|
|
37
|
+
2 | exposure time | 0....1000000
|
|
38
|
+
3 | gain | 0...100
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
#%%
|
|
43
|
+
import uc2rest
|
|
44
|
+
import time
|
|
45
|
+
|
|
46
|
+
port = "unknown"
|
|
47
|
+
port = "/dev/cu.SLAB_USBtoUART"
|
|
48
|
+
ESP32 = uc2rest.UC2Client(serialport=port, baudrate=500000, DEBUG=True)
|
|
49
|
+
|
|
50
|
+
# register callback function for key/value pair
|
|
51
|
+
def my_callback_key1(value):
|
|
52
|
+
print("Callback: ", value)
|
|
53
|
+
ESP32.message.register_callback(1, my_callback_key1)
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
time.sleep(.1)
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
'''
|
|
60
|
+
try:
|
|
61
|
+
# parse the message
|
|
62
|
+
key = data["message"]["key"]
|
|
63
|
+
value = data["message"]["data"]
|
|
64
|
+
# trigger the action
|
|
65
|
+
if self._callbackPerKey[key] is not None:
|
|
66
|
+
self._callbackPerKey[key](value) # we call the function with the value
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print("Error in _callback_message: ", e)
|
|
69
|
+
|
|
70
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
71
|
+
''' initialize the callback functions '''
|
|
72
|
+
self._callbackPerKey = {}
|
|
73
|
+
self.nCallbacks = nCallbacks
|
|
74
|
+
for i in range(nCallbacks):
|
|
75
|
+
self._callbackPerKey[i] = None
|
|
76
|
+
|
|
77
|
+
def register_callback(self, key, callback):
|
|
78
|
+
''' register a callback function for a specific key '''
|
|
79
|
+
self._callbackPerKey[key] = callback
|
|
80
|
+
|
|
81
|
+
def trigger_message(self, key:int=1, value:int=1):
|
|
82
|
+
# {"task": "/message_act", "message": 1, "key":1, "value":1}
|
|
83
|
+
path = "/message_act"
|
|
84
|
+
payload = {
|
|
85
|
+
"task": path,
|
|
86
|
+
"key": key,
|
|
87
|
+
"value": value
|
|
88
|
+
}
|
|
89
|
+
r = self._parent.post_json(path, payload, getReturn=False)
|
|
90
|
+
return r
|
|
@@ -9,16 +9,6 @@ class Motor(object):
|
|
|
9
9
|
# indicate if there is any motion happening
|
|
10
10
|
isRunning = False
|
|
11
11
|
|
|
12
|
-
# a dictionary that stores all motor parameters for each dxis
|
|
13
|
-
settingsdict = {"motor": {"steppers":
|
|
14
|
-
[{"stepperid":0,"dir":0,"step":0,"enable":0,"dir_inverted":False,"step_inverted":False,"enable_inverted":False,"speed":0,"speedmax":200000,"max_pos":100000,"min_pos":-100000},
|
|
15
|
-
{"stepperid":1,"dir":0,"step":0,"enable":0,"dir_inverted":False,"step_inverted":False,"enable_inverted":False,"speed":0,"speedmax":200000,"max_pos":100000,"min_pos":-100000},
|
|
16
|
-
{"stepperid":2,"dir":0,"step":0,"enable":0,"dir_inverted":False,"step_inverted":False,"enable_inverted":False,"speed":0,"speedmax":200000,"max_pos":100000,"min_pos":-100000},
|
|
17
|
-
{"stepperid":3,"dir":0,"step":0,"enable":0,"dir_inverted":False,"step_inverted":False,"enable_inverted":False,"speed":0,"speedmax":200000,"max_pos":100000,"min_pos":-100000}]
|
|
18
|
-
}}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
12
|
def __init__(self, parent=None):
|
|
23
13
|
self._parent = parent
|
|
24
14
|
|
|
@@ -55,11 +45,23 @@ class Motor(object):
|
|
|
55
45
|
# register a callback function for the motor status on the serial loop
|
|
56
46
|
if hasattr(self._parent, "serial"):
|
|
57
47
|
self._parent.serial.register_callback(self._callback_motor_status, pattern="steppers")
|
|
58
|
-
|
|
48
|
+
# announce a function that is called when we receive a position update through the callback
|
|
49
|
+
self._callbackPerKey = {}
|
|
50
|
+
self.nCallbacks = 10
|
|
51
|
+
self._callbackPerKey = self.init_callback_functions(nCallbacks=self.nCallbacks) # only one is used for now
|
|
52
|
+
print(self._callbackPerKey)
|
|
59
53
|
# move motor to wake them up #FIXME: Should not be necessary!
|
|
60
54
|
#self.move_stepper(steps=(1,1,1,1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
61
55
|
#self.move_stepper(steps=(-1,-1,-1,-1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
62
56
|
|
|
57
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
58
|
+
''' initialize the callback functions '''
|
|
59
|
+
_callbackPerKey = {}
|
|
60
|
+
self.nCallbacks = nCallbacks
|
|
61
|
+
for i in range(nCallbacks):
|
|
62
|
+
_callbackPerKey[i] = []
|
|
63
|
+
return _callbackPerKey
|
|
64
|
+
|
|
63
65
|
def _callback_motor_status(self, data):
|
|
64
66
|
''' cast the json in the form:
|
|
65
67
|
{
|
|
@@ -77,9 +79,16 @@ class Motor(object):
|
|
|
77
79
|
stepperID = data["steppers"][iMotor]["stepperid"]
|
|
78
80
|
# smart to re-update this variable? Will be updated by motor-sender too
|
|
79
81
|
self._position[stepperID] = data["steppers"][iMotor]["position"]
|
|
82
|
+
if callable(self._callbackPerKey[0]):
|
|
83
|
+
self._callbackPerKey[0](self._position) # we call the function with the value
|
|
80
84
|
except Exception as e:
|
|
81
85
|
print("Error in _callback_motor_status: ", e)
|
|
82
86
|
|
|
87
|
+
def register_callback(self, key, callbackfct):
|
|
88
|
+
''' register a callback function for a specific key '''
|
|
89
|
+
self._callbackPerKey[key] = callbackfct
|
|
90
|
+
|
|
91
|
+
|
|
83
92
|
def setTrigger(self, axis="X", pin=1, offset=0, period=1):
|
|
84
93
|
# {"task": "/motor_act", "setTrig": {"steppers": [{"stepperid": 1, "trigPin": 1, "trigOff":0, "trigPer":1}]}}
|
|
85
94
|
if type(axis) is not int:
|
|
@@ -99,8 +108,8 @@ class Motor(object):
|
|
|
99
108
|
r = self._parent.post_json(path, payload)
|
|
100
109
|
return r
|
|
101
110
|
|
|
102
|
-
# {"task": "/motor_act", "stagescan": {"nStepsLine":
|
|
103
|
-
def startStageScanning(self, nStepsLine=100, dStepsLine=1, nTriggerLine=1, nStepsPixel=100, dStepsPixel=1, nTriggerPixel=1, delayTimeStep=10, nFrames=5):
|
|
111
|
+
# {"task": "/motor_act", "stagescan": {"nStepsLine": 50, "dStepsLine": 1, "nTriggerLine": 1, "nStepsPixel": 50, "dStepsPixel": 1, "nTriggerPixel": 1, "delayTimeStep": 10, "stopped": 0, "nFrames": 50}}"}}
|
|
112
|
+
def startStageScanning(self, nStepsLine=100, dStepsLine=1, nTriggerLine=1, nStepsPixel=100, dStepsPixel=1, nTriggerPixel=1, delayTimeStep=10, nFrames=5, isBlocking = False):
|
|
104
113
|
path = "/motor_act"
|
|
105
114
|
payload = {
|
|
106
115
|
"task": path,
|
|
@@ -115,7 +124,7 @@ class Motor(object):
|
|
|
115
124
|
"stopped": 0,
|
|
116
125
|
"nFrames": nFrames
|
|
117
126
|
}}
|
|
118
|
-
r = self._parent.post_json(path, payload)
|
|
127
|
+
r = self._parent.post_json(path, payload, getReturn=isBlocking)
|
|
119
128
|
return r
|
|
120
129
|
|
|
121
130
|
def stopStageScanning(self):
|
|
@@ -522,7 +531,7 @@ class Motor(object):
|
|
|
522
531
|
r = self._parent.post_json(path, payload)
|
|
523
532
|
return r
|
|
524
533
|
|
|
525
|
-
def get_position(self, axis=None, timeout
|
|
534
|
+
def get_position(self, axis=None, timeout=1):
|
|
526
535
|
# pulls all current positions from the stepper controller
|
|
527
536
|
path = "/motor_get"
|
|
528
537
|
payload = {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import serial
|
|
2
|
+
from serial.tools import list_ports
|
|
2
3
|
import json
|
|
3
4
|
import queue
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
|
|
7
|
-
T_SERIAL_WARMUP =
|
|
8
|
+
T_SERIAL_WARMUP = 2.5
|
|
8
9
|
class Serial:
|
|
9
10
|
def __init__(self, port, baudrate=115200, timeout=5,
|
|
10
11
|
identity="UC2_Feather", parent=None, DEBUG=False):
|
|
@@ -14,7 +15,7 @@ class Serial:
|
|
|
14
15
|
self.baudrate = baudrate
|
|
15
16
|
self.timeout = timeout
|
|
16
17
|
self._parent = parent
|
|
17
|
-
|
|
18
|
+
self.manufacturer = ""
|
|
18
19
|
if self._parent is None:
|
|
19
20
|
import logging
|
|
20
21
|
self._logger = logging.getLogger(__name__)
|
|
@@ -26,18 +27,20 @@ class Serial:
|
|
|
26
27
|
self.identity = identity
|
|
27
28
|
self.DEBUG = DEBUG
|
|
28
29
|
self.is_connected = False
|
|
29
|
-
self.write_timeout =
|
|
30
|
+
self.write_timeout = 1
|
|
30
31
|
self.read_timeout = 0.02
|
|
31
32
|
|
|
32
33
|
self.cmdCallBackFct = None
|
|
33
34
|
|
|
34
35
|
# setup command queue
|
|
35
36
|
self.resetLastCommand = False
|
|
36
|
-
self.
|
|
37
|
+
self.sender_queue = queue.Queue()
|
|
37
38
|
self.responses = {}
|
|
38
39
|
self.commands = {}
|
|
39
40
|
self.lock = threading.Lock()
|
|
40
|
-
|
|
41
|
+
self.serialLock = threading.Lock()
|
|
42
|
+
|
|
43
|
+
# setup callback list for parent modules
|
|
41
44
|
self.callBackList = []
|
|
42
45
|
|
|
43
46
|
# initialize serial connection
|
|
@@ -52,8 +55,10 @@ class Serial:
|
|
|
52
55
|
# free up any old data
|
|
53
56
|
while True:
|
|
54
57
|
try:
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
readLineRaw = ser.readline()
|
|
59
|
+
readLine = readLineRaw.decode('utf-8').strip()
|
|
60
|
+
if self.DEBUG and readLine != "":
|
|
61
|
+
self._logger.debug(readLine)
|
|
57
62
|
if readLine == "" and time.time()-t0 > timeMinimum:
|
|
58
63
|
break
|
|
59
64
|
except Exception as e:
|
|
@@ -101,6 +106,11 @@ class Serial:
|
|
|
101
106
|
self.identifier_counter = 0 # Counter for generating unique identifiers
|
|
102
107
|
self.thread = threading.Thread(target=self._process_commands)
|
|
103
108
|
self.thread.start()
|
|
109
|
+
|
|
110
|
+
# setup sender queue
|
|
111
|
+
self.sender_worker_thread = threading.Thread(target=self._sending_commands)
|
|
112
|
+
self.sender_worker_thread.daemon = True # Ensure the thread exits when the main program does
|
|
113
|
+
self.sender_worker_thread.start()
|
|
104
114
|
return ser
|
|
105
115
|
|
|
106
116
|
def findCorrectSerialDevice(self):
|
|
@@ -110,7 +120,7 @@ class Serial:
|
|
|
110
120
|
It may be that - depending on the OS - the response may be corrupted
|
|
111
121
|
If this is the case try to hard-code the COM port into the config JSON file
|
|
112
122
|
'''
|
|
113
|
-
_available_ports =
|
|
123
|
+
_available_ports = list_ports.comports(include_links=False)
|
|
114
124
|
ports_to_check = ["COM", "/dev/tt", "/dev/a", "/dev/cu.SLA", "/dev/cu.wchusb"]
|
|
115
125
|
descriptions_to_check = ["CH340", "CP2102"]
|
|
116
126
|
|
|
@@ -119,12 +129,14 @@ class Serial:
|
|
|
119
129
|
any(port.description.startswith(allowed_description) for allowed_description in descriptions_to_check):
|
|
120
130
|
if self.tryToConnect(port.device):
|
|
121
131
|
self.is_connected = True
|
|
132
|
+
self.manufacturer = port.manufacturer
|
|
122
133
|
return self.serialdevice
|
|
123
134
|
|
|
124
135
|
self.is_connected = False
|
|
125
136
|
self.serialport = "NotConnected"
|
|
126
137
|
self.serialdevice = None
|
|
127
138
|
self._logger.debug("No USB device connected! Using DUMMY!")
|
|
139
|
+
self.manufacturer = "UC2Mock"
|
|
128
140
|
return None
|
|
129
141
|
|
|
130
142
|
def tryToConnect(self, port):
|
|
@@ -157,7 +169,8 @@ class Serial:
|
|
|
157
169
|
for i in range(500):
|
|
158
170
|
# if we just want to send but not even wait for a response
|
|
159
171
|
mReadline = ser.readline()
|
|
160
|
-
if self.DEBUG
|
|
172
|
+
if self.DEBUG and mReadline != "" and mReadline != "\n" and mReadline != b'' and mReadline != b'\n':
|
|
173
|
+
self._logger.debug("[checkFirmware]: "+str(mReadline))
|
|
161
174
|
if mReadline.decode('utf-8').strip() == "++":
|
|
162
175
|
self._freeSerialBuffer(ser)
|
|
163
176
|
return True
|
|
@@ -168,6 +181,32 @@ class Serial:
|
|
|
168
181
|
self.identifier_counter += 1
|
|
169
182
|
return self.identifier_counter
|
|
170
183
|
|
|
184
|
+
def _enqueue_command(self, json_command):
|
|
185
|
+
"""Add a command to the queue."""
|
|
186
|
+
self.sender_queue.put(json_command)
|
|
187
|
+
|
|
188
|
+
def _sending_commands(self):
|
|
189
|
+
"""Sending commands from the queue with a 1s delay between them."""
|
|
190
|
+
while self.running:
|
|
191
|
+
|
|
192
|
+
# Wait for the next command
|
|
193
|
+
json_command = self.sender_queue.get()
|
|
194
|
+
|
|
195
|
+
# Process the command
|
|
196
|
+
with self.serialLock:
|
|
197
|
+
try:
|
|
198
|
+
if self.DEBUG and json_command!="": self._logger.debug("[SendingCommands]:"+str(json_command))
|
|
199
|
+
self.serialdevice.write(json_command.encode('utf-8'))
|
|
200
|
+
except Exception as e:
|
|
201
|
+
self._logger.error("Failed to write the line in serial: "+str(e))
|
|
202
|
+
|
|
203
|
+
# Signal that the command has been processed
|
|
204
|
+
self.sender_queue.task_done()
|
|
205
|
+
if self.DEBUG: self._logger.debug("[SendingCommands]: Task done")
|
|
206
|
+
|
|
207
|
+
# Wait for .05 second before processing the next command
|
|
208
|
+
time.sleep(0.05)
|
|
209
|
+
|
|
171
210
|
def _process_commands(self):
|
|
172
211
|
buffer = ""
|
|
173
212
|
reading_json = False
|
|
@@ -175,10 +214,9 @@ class Serial:
|
|
|
175
214
|
nLineCountTimeout = 50 # maximum number of lines read before timeout
|
|
176
215
|
lineCounter = 0
|
|
177
216
|
while self.running:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# currentIdentifier, command = self.command_queue.get()
|
|
217
|
+
if self.manufacturer == "UC2Mock":
|
|
218
|
+
self.running = False
|
|
219
|
+
return
|
|
182
220
|
|
|
183
221
|
# device not ready yet
|
|
184
222
|
if self.serialdevice is None:
|
|
@@ -189,13 +227,12 @@ class Serial:
|
|
|
189
227
|
|
|
190
228
|
# if we just want to send but not even wait for a response
|
|
191
229
|
try:
|
|
192
|
-
|
|
230
|
+
with self.serialLock:
|
|
231
|
+
mReadline = self.serialdevice.readline()
|
|
232
|
+
line = mReadline.decode('utf-8').strip()
|
|
233
|
+
if self.DEBUG and line!="": self._logger.debug("[ProcessLines]:"+str(line))
|
|
193
234
|
except Exception as e:
|
|
194
235
|
self._logger.error("Failed to read the line in serial: "+str(e))
|
|
195
|
-
try:
|
|
196
|
-
line = mReadline.decode('utf-8').strip()
|
|
197
|
-
if self.DEBUG and line!="": self._logger.debug("[ProcessLines]:"+str(line))
|
|
198
|
-
except:
|
|
199
236
|
line = ""
|
|
200
237
|
if line == "++":
|
|
201
238
|
reading_json = True
|
|
@@ -219,15 +256,26 @@ class Serial:
|
|
|
219
256
|
self._logger.error("Error: %s" % str(e))
|
|
220
257
|
json_response = {}
|
|
221
258
|
|
|
259
|
+
try: currentIdentifier = json_response["qid"]
|
|
260
|
+
except: pass
|
|
261
|
+
|
|
222
262
|
with self.lock:
|
|
223
|
-
try: currentIdentifier = json_response["qid"]
|
|
224
|
-
except: pass
|
|
225
263
|
try:
|
|
226
264
|
self.responses[currentIdentifier].append(json_response.copy())
|
|
227
265
|
except:
|
|
228
266
|
self.responses[currentIdentifier] = list()
|
|
229
267
|
self.responses[currentIdentifier].append(json_response.copy())
|
|
230
|
-
|
|
268
|
+
buffer = "" # reset buffer
|
|
269
|
+
|
|
270
|
+
# detect a reboot of the device and return the current QIDs
|
|
271
|
+
elif line == "reboot":
|
|
272
|
+
self._logger.warning("Device rebooted")
|
|
273
|
+
self.resetLastCommand = True
|
|
274
|
+
buffer = ""
|
|
275
|
+
lineCounter = 0
|
|
276
|
+
reading_json = False
|
|
277
|
+
self.responses[currentIdentifier].append({"reboot": 1})
|
|
278
|
+
self.responses[currentIdentifier].append({"qid": currentIdentifier})
|
|
231
279
|
|
|
232
280
|
if reading_json:
|
|
233
281
|
buffer += line
|
|
@@ -285,29 +333,33 @@ class Serial:
|
|
|
285
333
|
If nResponses is 1, then the command is sent and the response is returned.
|
|
286
334
|
If nResponses is >1, then the command is sent and a list of responses is returned.
|
|
287
335
|
'''
|
|
288
|
-
t0 = time.time()
|
|
289
336
|
if type(command) == str:
|
|
290
337
|
command = json.loads(command)
|
|
291
338
|
identifier = self._generate_identifier()
|
|
292
339
|
command["qid"] = identifier
|
|
293
|
-
self.command_queue.put((identifier, command))
|
|
294
340
|
try:
|
|
295
341
|
json_command = json.dumps(command)+"\n"
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
342
|
+
#self.serialdevice.flush()
|
|
343
|
+
if self.DEBUG and json_command!="": self._logger.debug("[SendingCommands]:"+str(json_command))
|
|
344
|
+
self.serialdevice.write(json_command.encode('utf-8'))
|
|
345
|
+
#time.sleep(1)
|
|
346
|
+
# we have to queue the commands and give it some time to process
|
|
347
|
+
#self._enqueue_command(json_command)
|
|
348
|
+
|
|
300
349
|
except Exception as e:
|
|
301
350
|
if self.DEBUG: self._logger.error(e)
|
|
302
351
|
return "Failed to Send"
|
|
303
|
-
self.commands[identifier]=command
|
|
304
|
-
if nResponses <= 0 or not self.is_connected or
|
|
352
|
+
self.commands[identifier]=command # FIXME: Need to clear this after the response is received
|
|
353
|
+
if nResponses <= 0 or not self.is_connected or self.manufacturer=="UC2Mock":
|
|
354
|
+
time.sleep(0.1)
|
|
305
355
|
return identifier
|
|
356
|
+
t0 = time.time()
|
|
357
|
+
timeReturnReceived = 0.3
|
|
306
358
|
while self.running:
|
|
307
359
|
time.sleep(0.002)
|
|
308
360
|
if self.resetLastCommand or time.time()-t0>timeout or not self.is_connected:
|
|
309
361
|
self.resetLastCommand = False
|
|
310
|
-
return "communication interrupted"
|
|
362
|
+
return "communication interrupted by timeout or reset: "+str(identifier) + " and code:"+str(self.commands[identifier])
|
|
311
363
|
with self.lock:
|
|
312
364
|
if identifier in self.responses:
|
|
313
365
|
if len(self.responses[identifier])==nResponses:
|
|
@@ -315,6 +367,14 @@ class Serial:
|
|
|
315
367
|
if -identifier in self.responses:
|
|
316
368
|
self._logger.debug("You have sent the wrong command!")
|
|
317
369
|
return "Wrong Command"
|
|
370
|
+
if time.time()-t0>timeReturnReceived and not (identifier in self.responses and len(self.responses[identifier]) > 0):
|
|
371
|
+
self._logger.debug("It takes too long to get a response, we will resend the last command")
|
|
372
|
+
try:
|
|
373
|
+
self.serialdevice.write(json.dumps(self.commands[identifier]).encode('utf-8'))
|
|
374
|
+
time.sleep(0.1)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
self._logger.error("Failed to write the line in serial: "+str(e))
|
|
377
|
+
|
|
318
378
|
|
|
319
379
|
def interruptCurrentSerialCommunication(self):
|
|
320
380
|
self.resetLastCommand = True
|
|
@@ -328,8 +388,10 @@ class Serial:
|
|
|
328
388
|
self.stop()
|
|
329
389
|
self.running = False
|
|
330
390
|
|
|
331
|
-
def reconnect(self):
|
|
391
|
+
def reconnect(self, baudrate=None):
|
|
332
392
|
self.running = False
|
|
393
|
+
if baudrate is not None:
|
|
394
|
+
self.baudrate = baudrate
|
|
333
395
|
try:
|
|
334
396
|
self.serialdevice.close()
|
|
335
397
|
except:
|
|
@@ -84,3 +84,15 @@ class State(object):
|
|
|
84
84
|
except:
|
|
85
85
|
return r
|
|
86
86
|
|
|
87
|
+
|
|
88
|
+
def getHeap(self, timeout=1):
|
|
89
|
+
path = "/state_get"
|
|
90
|
+
payload = {
|
|
91
|
+
"task":path,
|
|
92
|
+
"heap": 1
|
|
93
|
+
}
|
|
94
|
+
r = self._parent.post_json(path, payload, timeout=timeout)
|
|
95
|
+
try:
|
|
96
|
+
return r["heap"]
|
|
97
|
+
except:
|
|
98
|
+
return r
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
class Temperature(object):
|
|
4
|
+
|
|
5
|
+
def __init__(self, parent):
|
|
6
|
+
self._parent = parent
|
|
7
|
+
self._temperature = -273.15
|
|
8
|
+
|
|
9
|
+
self.is_temperature_polling = False
|
|
10
|
+
|
|
11
|
+
# register a callback function for the motor status on the serial loop
|
|
12
|
+
if hasattr(self._parent, "serial"):
|
|
13
|
+
self._parent.serial.register_callback(self._callback_temperature_status, pattern="heat")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
'''
|
|
17
|
+
##############################################################################################################################
|
|
18
|
+
Temperature Controllers
|
|
19
|
+
##############################################################################################################################
|
|
20
|
+
'''
|
|
21
|
+
|
|
22
|
+
def start_temperature_polling(self, period=5):
|
|
23
|
+
self.is_temperature_polling = True
|
|
24
|
+
def _poll_temperature():
|
|
25
|
+
while self.is_temperature_polling:
|
|
26
|
+
path = "/heat_get"
|
|
27
|
+
r = self._parent.get_json(path, timeout=0)
|
|
28
|
+
time.sleep(period)
|
|
29
|
+
threading.Thread(target=_poll_temperature).start()
|
|
30
|
+
|
|
31
|
+
def stop_temperature_polling(self):
|
|
32
|
+
self.is_temperature_polling = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _callback_temperature_status(self, data):
|
|
36
|
+
''' cast the json in the form:
|
|
37
|
+
{
|
|
38
|
+
"qid": 0,
|
|
39
|
+
"heat": 37.0
|
|
40
|
+
}
|
|
41
|
+
into the position array of the motors '''
|
|
42
|
+
try:
|
|
43
|
+
self._temperature = data["heat"]
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print("Error in _callback_temperature_status: ", e)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def stop_heating(self):
|
|
49
|
+
r = self.set_temperature(active=0)
|
|
50
|
+
return r
|
|
51
|
+
|
|
52
|
+
def set_temperature(self, active=1, Kp=1000, Ki=0.1, Kd=0.1, target=37, timeout=600000000, updaterate=1000):
|
|
53
|
+
# {"task": "/heat_act", "active":1, "Kp":1000, "Ki":0.1, "Kd":0.1, "target":37, "timeout":60000000, "updaterate":1000}
|
|
54
|
+
path = "/heat_act"
|
|
55
|
+
payload = {
|
|
56
|
+
"task": path,
|
|
57
|
+
"active": int(active),
|
|
58
|
+
"Kp": Kp,
|
|
59
|
+
"Ki": Ki,
|
|
60
|
+
"Kd": Kd,
|
|
61
|
+
"target": target,
|
|
62
|
+
"timeout": timeout,
|
|
63
|
+
"updaterate": updaterate
|
|
64
|
+
}
|
|
65
|
+
r = self._parent.post_json(path, payload, getReturn=False)
|
|
66
|
+
return r
|
|
67
|
+
|
|
68
|
+
def get_temperature(self, timeout=0.5, isBlocking=False):
|
|
69
|
+
# {"task": "/heat_get"}
|
|
70
|
+
if isBlocking:
|
|
71
|
+
path = "/heat_get"
|
|
72
|
+
r = self._parent.get_json(path, timeout=timeout)
|
|
73
|
+
try:
|
|
74
|
+
r = r["heat"]
|
|
75
|
+
except:
|
|
76
|
+
r = -273.15
|
|
77
|
+
return r
|
|
78
|
+
else:
|
|
79
|
+
return self._temperature
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
|
|
83
|
+
#%%
|
|
84
|
+
import uc2rest
|
|
85
|
+
import time
|
|
86
|
+
port = "/dev/cu.SLAB_USBtoUART"
|
|
87
|
+
ESP32 = uc2rest.UC2Client(serialport=port, DEBUG=True)
|
|
88
|
+
ESP32.temperature.set_temperature(active=1, Kp=1000, Ki=0.1, Kd=0.1, target=37, timeout=600000, updaterate=1000)
|
|
89
|
+
for i in range(10):
|
|
90
|
+
print(ESP32.temperature.get_temperature())
|
|
91
|
+
time.sleep(1)
|
|
92
|
+
ESP32.temperature.stop_heating()
|
|
93
|
+
ESP32.close()
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
class Temperature(object):
|
|
2
|
-
|
|
3
|
-
def __init__(self, parent):
|
|
4
|
-
self._parent = parent
|
|
5
|
-
|
|
6
|
-
'''
|
|
7
|
-
##############################################################################################################################
|
|
8
|
-
Temperature Controllers
|
|
9
|
-
##############################################################################################################################
|
|
10
|
-
'''
|
|
11
|
-
|
|
12
|
-
def stop_heating(self):
|
|
13
|
-
r = self.set_temperature(active=0)
|
|
14
|
-
return r
|
|
15
|
-
|
|
16
|
-
def set_temperature(self, active=1, Kp=1000, Ki=0.1, Kd=0.1, target=37, timeout=600000000, updaterate=1000):
|
|
17
|
-
# {"task": "/heat_act", "active":1, "Kp":1000, "Ki":0.1, "Kd":0.1, "target":37, "timeout":60000000, "updaterate":1000}
|
|
18
|
-
path = "/heat_act"
|
|
19
|
-
payload = {
|
|
20
|
-
"task": path,
|
|
21
|
-
"active": int(active),
|
|
22
|
-
"Kp": Kp,
|
|
23
|
-
"Ki": Ki,
|
|
24
|
-
"Kd": Kd,
|
|
25
|
-
"target": target,
|
|
26
|
-
"timeout": timeout,
|
|
27
|
-
"updaterate": updaterate
|
|
28
|
-
}
|
|
29
|
-
r = self._parent.post_json(path, payload, getReturn=False)
|
|
30
|
-
return r
|
|
31
|
-
|
|
32
|
-
def get_temperature(self, timeout=0.5):
|
|
33
|
-
path = "/heat_get"
|
|
34
|
-
r = self._parent.get_json(path, timeout=timeout)
|
|
35
|
-
try:
|
|
36
|
-
r = r["heat"]
|
|
37
|
-
except:
|
|
38
|
-
r = -273.15
|
|
39
|
-
return r
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if __name__ == "__main__":
|
|
43
|
-
|
|
44
|
-
#%%
|
|
45
|
-
import uc2rest
|
|
46
|
-
import time
|
|
47
|
-
port = "/dev/cu.SLAB_USBtoUART"
|
|
48
|
-
ESP32 = uc2rest.UC2Client(serialport=port, DEBUG=True)
|
|
49
|
-
ESP32.temperature.set_temperature(active=1, Kp=1000, Ki=0.1, Kd=0.1, target=37, timeout=600000, updaterate=1000)
|
|
50
|
-
for i in range(10):
|
|
51
|
-
print(ESP32.temperature.get_temperature())
|
|
52
|
-
time.sleep(1)
|
|
53
|
-
ESP32.temperature.stop_heating()
|
|
54
|
-
ESP32.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|